/* * Greybus connections * * Copyright 2014 Google Inc. * * Released under the GPLv2 only. */ #include #include "kernel_ver.h" #include "greybus.h" static DEFINE_SPINLOCK(gb_connections_lock); struct gb_connection *gb_hd_connection_find(struct greybus_host_device *hd, u16 cport_id) { struct gb_connection *connection = NULL; spin_lock_irq(&gb_connections_lock); list_for_each_entry(connection, &hd->connections, hd_links) if (connection->hd_cport_id == cport_id) goto found; connection = NULL; found: spin_unlock_irq(&gb_connections_lock); return connection; } /* * Callback from the host driver to let us know that data has been * received on the interface. */ void greybus_data_rcvd(struct greybus_host_device *hd, u16 cport_id, u8 *data, size_t length) { struct gb_connection *connection; connection = gb_hd_connection_find(hd, cport_id); if (!connection) { dev_err(hd->parent, "nonexistent connection (%zu bytes dropped)\n", length); return; } gb_connection_recv(connection, data, length); } EXPORT_SYMBOL_GPL(greybus_data_rcvd); /* * Allocate an available CPort Id for use for the host side of the * given connection. The lowest-available id is returned, so the * first call is guaranteed to allocate CPort Id 0. * * Assigns the connection's hd_cport_id and returns true if successful. * Returns false otherwise. */ static bool gb_connection_hd_cport_id_alloc(struct gb_connection *connection) { struct ida *ida = &connection->hd->cport_id_map; int id; spin_lock_irq(&gb_connections_lock); id = ida_simple_get(ida, 0, HOST_DEV_CPORT_ID_MAX, GFP_KERNEL); spin_unlock_irq(&gb_connections_lock); if (id < 0) return false; connection->hd_cport_id = (u16)id; return true; } /* * Free a previously-allocated CPort Id on the given host device. */ static void gb_connection_hd_cport_id_free(struct gb_connection *connection) { struct ida *ida = &connection->hd->cport_id_map; spin_lock_irq(&gb_connections_lock); ida_simple_remove(ida, connection->hd_cport_id); spin_unlock_irq(&gb_connections_lock); connection->hd_cport_id = CPORT_ID_BAD; } static ssize_t state_show(struct device *dev, struct device_attribute *attr, char *buf) { struct gb_connection *connection = to_gb_connection(dev); return sprintf(buf, "%d", connection->state); } static DEVICE_ATTR_RO(state); static ssize_t protocol_id_show(struct device *dev, struct device_attribute *attr, char *buf) { struct gb_connection *connection = to_gb_connection(dev); return sprintf(buf, "%d", connection->protocol->id); } static DEVICE_ATTR_RO(protocol_id); static struct attribute *connection_attrs[] = { &dev_attr_state.attr, &dev_attr_protocol_id.attr, NULL, }; ATTRIBUTE_GROUPS(connection); static void gb_connection_release(struct device *dev) { struct gb_connection *connection = to_gb_connection(dev); kfree(connection); } struct device_type greybus_connection_type = { .name = "greybus_connection", .release = gb_connection_release, }; /* * Set up a Greybus connection, representing the bidirectional link * between a CPort on a (local) Greybus host device and a CPort on * another Greybus module. * * A connection also maintains the state of operations sent over the * connection. * * Returns a pointer to the new connection if successful, or a null * pointer otherwise. */ struct gb_connection *gb_connection_create(struct gb_interface *interface, u16 cport_id, u8 protocol_id) { struct gb_connection *connection; struct greybus_host_device *hd; int retval; u8 major = 0; u8 minor = 1; connection = kzalloc(sizeof(*connection), GFP_KERNEL); if (!connection) return NULL; /* XXX Will have to establish connections to get version */ connection->protocol = gb_protocol_get(protocol_id, major, minor); if (!connection->protocol) { pr_err("protocol 0x%02hhx not found\n", protocol_id); kfree(connection); return NULL; } hd = interface->gmod->hd; connection->hd = hd; if (!gb_connection_hd_cport_id_alloc(connection)) { gb_protocol_put(connection->protocol); kfree(connection); return NULL; } connection->interface = interface; connection->interface_cport_id = cport_id; connection->state = GB_CONNECTION_STATE_DISABLED; connection->dev.parent = &interface->dev; connection->dev.bus = &greybus_bus_type; connection->dev.type = &greybus_connection_type; connection->dev.groups = connection_groups; device_initialize(&connection->dev); dev_set_name(&connection->dev, "%s:%d", dev_name(&interface->dev), cport_id); retval = device_add(&connection->dev); if (retval) { pr_err("failed to add connection device for cport 0x%04hx\n", cport_id); gb_connection_hd_cport_id_free(connection); gb_protocol_put(connection->protocol); put_device(&connection->dev); kfree(connection); return NULL; } spin_lock_irq(&gb_connections_lock); list_add_tail(&connection->hd_links, &hd->connections); list_add_tail(&connection->interface_links, &interface->connections); spin_unlock_irq(&gb_connections_lock); INIT_LIST_HEAD(&connection->operations); INIT_LIST_HEAD(&connection->pending); return connection; } /* * Tear down a previously set up connection. */ void gb_connection_destroy(struct gb_connection *connection) { struct gb_operation *operation; struct gb_operation *next; if (WARN_ON(!connection)) return; /* XXX Need to wait for any outstanding requests to complete */ if (WARN_ON(!list_empty(&connection->operations))) { list_for_each_entry_safe(operation, next, &connection->operations, links) gb_operation_cancel(operation, -ESHUTDOWN); } spin_lock_irq(&gb_connections_lock); list_del(&connection->interface_links); list_del(&connection->hd_links); spin_unlock_irq(&gb_connections_lock); gb_connection_hd_cport_id_free(connection); gb_protocol_put(connection->protocol); device_del(&connection->dev); } void gb_connection_err(struct gb_connection *connection, const char *fmt, ...) { struct va_format vaf; va_list args; va_start(args, fmt); vaf.fmt = fmt; vaf.va = &args; pr_err("greybus: [%hhu:%hhu:%hu]: %pV\n", connection->interface->gmod->module_id, connection->interface->id, connection->interface_cport_id, &vaf); va_end(args); } int gb_connection_init(struct gb_connection *connection) { int ret; if (!connection->protocol) { gb_connection_err(connection, "uninitialized connection"); return -EIO; } /* Need to enable the connection to initialize it */ connection->state = GB_CONNECTION_STATE_ENABLED; ret = connection->protocol->connection_init(connection); if (ret) connection->state = GB_CONNECTION_STATE_ERROR; return ret; } void gb_connection_exit(struct gb_connection *connection) { if (!connection->protocol) { gb_connection_err(connection, "uninitialized connection"); return; } connection->state = GB_CONNECTION_STATE_DESTROYING; connection->protocol->connection_exit(connection); }