// SPDX-License-Identifier: GPL-2.0
/*
* Greybus connections
*
* Copyright 2014 Google Inc.
* Copyright 2014 Linaro Ltd.
*/
#include <linux/workqueue.h>
#include <linux/greybus.h>
#include "greybus_trace.h"
#define GB_CONNECTION_CPORT_QUIESCE_TIMEOUT 1000
static void gb_connection_kref_release(struct kref *kref);
static DEFINE_SPINLOCK(gb_connections_lock);
static DEFINE_MUTEX(gb_connection_mutex);
/* Caller holds gb_connection_mutex. */
static bool gb_connection_cport_in_use(struct gb_interface *intf, u16 cport_id)
{
struct gb_host_device *hd = intf->hd;
struct gb_connection *connection;
list_for_each_entry(connection, &hd->connections, hd_links) {
if (connection->intf == intf &&
connection->intf_cport_id == cport_id)
return true;
}
return false;
}
static void gb_connection_get(struct gb_connection *connection)
{
kref_get(&connection->kref);
trace_gb_connection_get(connection);
}
static void gb_connection_put(struct gb_connection *connection)
{
trace_gb_connection_put(connection);
kref_put(&connection->kref, gb_connection_kref_release);
}
/*
* Returns a reference-counted pointer to the connection if found.
*/
static struct gb_connection *
gb_connection_hd_find(struct gb_host_device *hd, u16 cport_id)
{
struct gb_connection *connection;
unsigned long flags;
spin_lock_irqsave(&gb_connections_lock, flags);
list_for_each_entry(connection, &hd->connections, hd_links)
if (connection->hd_cport_id == cport_id) {
gb_connection_get(connection);
goto found;
}
connection = NULL;
found:
spin_unlock_irqrestore(&gb_connections_lock, flags);
return connection;
}
/*
* Callback from the host driver to let us know that data has been
* received on the bundle.
*/
void greybus_data_rcvd(struct gb_host_device *hd, u16 cport_id,
u8 *data, size_t length)
{
struct gb_connection *connection;
trace_gb_hd_in(hd);
connection = gb_connection_hd_find(hd, cport_id);
if (!connection) {
dev_err(&hd->dev,
"nonexistent connection (%zu bytes dropped)\n", length);
return;
}
gb_connection_recv(connection, data, length);
gb_connection_put(connection);
}
EXPORT_SYMBOL_GPL(greybus_data_rcvd);
static void gb_connection_kref_release(struct kref *kref)
{
struct gb_connection *connection;
connection = container_of(kref, struct gb_connection, kref);
trace_gb_connection_release(connection);
kfree(connection);
}
static void gb_connection_init_name(struct gb_connection *connection)
{
u16 hd_cport_id = connection->hd_cport_id;
u16 cport_id = 0;
u8 intf_id = 0;
if (connection->intf) {
intf_id = connection->intf->interface_id;
cport_id = connection->intf_cport_id;
}
snprintf(connection->name, sizeof(connection->name),
"%u/%u:%u", hd_cport_id, intf_id, cport_id);
}
/*
* _gb_connection_create() - create a Greybus connection
* @hd: host device of the connection
* @hd_cport_id: host-device cport id, or -1 for dynamic allocation
* @intf: remote interface, or NULL for static connections
* @bundle: remote-interface bundle (may be NULL)
* @cport_id: remote-interface cport id, or 0 for static connections
* @handler: request handler (may be NULL)
* @flags: connection flags
*
* Create a Greybus connection, representing the bidirectional link
* between a CPort on a (local) Greybus host device and a CPort on
* another Greybus interface.
*
* A connection also maintains the state of operations sent over the
* connection.
*
* Serialised against concurrent create and destroy using the
* gb_connection_mutex.
*
* Return: A pointer to the new connection if successful, or an ERR_PTR
* otherwise.
*/
static struct gb_connection *
_gb_connection_create(struct gb_host_device *hd, int hd_cport_id,
struct gb_interface *intf,
struct gb_bundle *bundle, int cport_id,
gb_request_handler_t handler,
unsigned long flags)
{
struct gb_connection *connection;
int ret;
mutex_lock(&gb_connection_mutex);
if (intf && gb_connection_cport_in_use(intf, cport_id)) {
dev_err(&intf->dev, "cport %u already in use\n", cport_id);
ret = -EBUSY;
goto err_unlock;
}
ret = gb_hd_cport_allocate(hd, hd_cport_id, flags);
if (ret < 0) {
dev_err(&hd->dev, "failed to allocate cport: %d\n", ret);
goto err_unlock;
}
hd_cport_id = ret;
connection = kzalloc(sizeof(*connection), GFP_KERNEL);
if (!connection) {
ret = -ENOMEM;
goto err_hd_cport_release;
}
connection->hd_cport_id = hd_cport_id;
connection->intf_cport_id = cport_id;
connection->hd = hd;
connection->intf = intf;
connection->bundle = bundle;
connection->handler = handler;
connection->flags = flags;
if (intf