2014-10-02 04:54:12 +02:00
|
|
|
/*
|
|
|
|
* Greybus interfaces
|
|
|
|
*
|
|
|
|
* Copyright 2014 Google Inc.
|
|
|
|
*
|
|
|
|
* Released under the GPLv2 only.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "greybus.h"
|
|
|
|
|
2014-10-24 11:34:46 +02:00
|
|
|
static ssize_t device_id_show(struct device *dev, struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
struct gb_interface *interface = to_gb_interface(dev);
|
|
|
|
|
|
|
|
return sprintf(buf, "%d", interface->device_id);
|
|
|
|
}
|
|
|
|
static DEVICE_ATTR_RO(device_id);
|
|
|
|
|
|
|
|
static struct attribute *interface_attrs[] = {
|
|
|
|
&dev_attr_device_id.attr,
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
|
|
|
ATTRIBUTE_GROUPS(interface);
|
|
|
|
|
|
|
|
static void gb_interface_release(struct device *dev)
|
|
|
|
{
|
|
|
|
struct gb_interface *interface = to_gb_interface(dev);
|
|
|
|
|
|
|
|
kfree(interface);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct device_type greybus_interface_type = {
|
|
|
|
.name = "greybus_interface",
|
|
|
|
.release = gb_interface_release,
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2014-10-02 04:54:12 +02:00
|
|
|
/* XXX This could be per-host device or per-module */
|
|
|
|
static DEFINE_SPINLOCK(gb_interfaces_lock);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* A Greybus interface represents a UniPro device present on a
|
|
|
|
* module. For Project Ara, each active Interface Block on a module
|
|
|
|
* implements a UniPro device, and therefore a Greybus interface. A
|
|
|
|
* Greybus module has at least one interface, but can have two (or
|
|
|
|
* even more).
|
|
|
|
*
|
|
|
|
* Create a gb_interface structure to represent a discovered
|
|
|
|
* interface. Returns a pointer to the new interface or a null
|
|
|
|
* pointer if a failure occurs due to memory exhaustion.
|
|
|
|
*/
|
|
|
|
struct gb_interface *
|
|
|
|
gb_interface_create(struct gb_module *gmod, u8 interface_id)
|
|
|
|
{
|
|
|
|
struct gb_interface *interface;
|
2014-10-24 11:34:46 +02:00
|
|
|
int retval;
|
2014-10-02 04:54:12 +02:00
|
|
|
|
|
|
|
interface = kzalloc(sizeof(*interface), GFP_KERNEL);
|
|
|
|
if (!interface)
|
|
|
|
return NULL;
|
|
|
|
|
2014-10-27 10:58:54 +01:00
|
|
|
interface->gmod = gmod;
|
2014-10-02 19:30:02 +02:00
|
|
|
interface->id = interface_id;
|
2014-10-24 11:34:46 +02:00
|
|
|
interface->device_id = 0xff; /* Invalid device id to start with */
|
2014-10-03 21:14:22 +02:00
|
|
|
INIT_LIST_HEAD(&interface->connections);
|
2014-10-02 04:54:12 +02:00
|
|
|
|
2014-10-24 11:34:46 +02:00
|
|
|
/* Build up the interface device structures and register it with the
|
|
|
|
* driver core */
|
|
|
|
interface->dev.parent = &gmod->dev;
|
|
|
|
interface->dev.bus = &greybus_bus_type;
|
|
|
|
interface->dev.type = &greybus_interface_type;
|
|
|
|
interface->dev.groups = interface_groups;
|
|
|
|
device_initialize(&interface->dev);
|
|
|
|
dev_set_name(&interface->dev, "%d:%d", gmod->module_id, interface_id);
|
|
|
|
|
|
|
|
retval = device_add(&interface->dev);
|
|
|
|
if (retval) {
|
2014-11-05 23:03:12 +01:00
|
|
|
pr_err("failed to add interface device for id 0x%02hhx\n",
|
|
|
|
interface_id);
|
2014-11-13 13:44:38 +01:00
|
|
|
put_device(&interface->dev);
|
2014-10-24 11:34:46 +02:00
|
|
|
kfree(interface);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2014-10-02 04:54:12 +02:00
|
|
|
spin_lock_irq(&gb_interfaces_lock);
|
|
|
|
list_add_tail(&interface->links, &gmod->interfaces);
|
|
|
|
spin_unlock_irq(&gb_interfaces_lock);
|
|
|
|
|
|
|
|
return interface;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Tear down a previously set up interface.
|
|
|
|
*/
|
2014-10-24 11:34:46 +02:00
|
|
|
void gb_interface_destroy(struct gb_module *gmod)
|
2014-10-02 04:54:12 +02:00
|
|
|
{
|
2014-10-24 11:34:46 +02:00
|
|
|
struct gb_interface *interface;
|
|
|
|
struct gb_interface *temp;
|
|
|
|
|
|
|
|
if (WARN_ON(!gmod))
|
2014-10-02 04:54:12 +02:00
|
|
|
return;
|
|
|
|
|
|
|
|
spin_lock_irq(&gb_interfaces_lock);
|
2014-10-24 11:34:46 +02:00
|
|
|
list_for_each_entry_safe(interface, temp, &gmod->interfaces, links) {
|
|
|
|
list_del(&interface->links);
|
|
|
|
gb_interface_connections_exit(interface);
|
|
|
|
device_del(&interface->dev);
|
|
|
|
}
|
2014-10-02 04:54:12 +02:00
|
|
|
spin_unlock_irq(&gb_interfaces_lock);
|
|
|
|
}
|
2014-10-16 13:35:35 +02:00
|
|
|
|
2014-11-14 12:55:08 +01:00
|
|
|
int gb_interface_init(struct gb_module *gmod, u8 interface_id, u8 device_id)
|
|
|
|
{
|
|
|
|
struct gb_interface *interface;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
interface = gb_interface_find(gmod, interface_id);
|
|
|
|
if (!interface) {
|
|
|
|
dev_err(gmod->hd->parent, "module %hhu not found\n",
|
|
|
|
interface_id);
|
|
|
|
return -ENOENT;
|
|
|
|
}
|
|
|
|
interface->device_id = device_id;
|
|
|
|
|
|
|
|
ret = svc_set_route_send(interface, gmod->hd);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(gmod->hd->parent, "failed to set route (%d)\n", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = gb_interface_connections_init(interface);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(gmod->hd->parent, "module interface init error %d\n",
|
|
|
|
ret);
|
|
|
|
/* XXX clear route */
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-10-22 04:43:30 +02:00
|
|
|
struct gb_interface *gb_interface_find(struct gb_module *module,
|
|
|
|
u8 interface_id)
|
|
|
|
{
|
|
|
|
struct gb_interface *interface;
|
|
|
|
|
2014-10-24 11:34:46 +02:00
|
|
|
spin_lock_irq(&gb_interfaces_lock);
|
2014-10-22 04:43:30 +02:00
|
|
|
list_for_each_entry(interface, &module->interfaces, links)
|
2014-10-24 11:34:46 +02:00
|
|
|
if (interface->id == interface_id) {
|
|
|
|
spin_unlock_irq(&gb_interfaces_lock);
|
2014-10-22 04:43:30 +02:00
|
|
|
return interface;
|
2014-10-24 11:34:46 +02:00
|
|
|
}
|
|
|
|
spin_unlock_irq(&gb_interfaces_lock);
|
2014-10-22 04:43:30 +02:00
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2014-10-16 13:35:35 +02:00
|
|
|
int gb_interface_connections_init(struct gb_interface *interface)
|
|
|
|
{
|
|
|
|
struct gb_connection *connection;
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
list_for_each_entry(connection, &interface->connections,
|
|
|
|
interface_links) {
|
|
|
|
ret = gb_connection_init(connection);
|
|
|
|
if (ret)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
2014-10-21 06:01:04 +02:00
|
|
|
|
|
|
|
void gb_interface_connections_exit(struct gb_interface *interface)
|
|
|
|
{
|
|
|
|
struct gb_connection *connection;
|
|
|
|
struct gb_connection *next;
|
|
|
|
|
|
|
|
list_for_each_entry_safe(connection, next, &interface->connections,
|
|
|
|
interface_links) {
|
|
|
|
gb_connection_exit(connection);
|
|
|
|
gb_connection_destroy(connection);
|
|
|
|
}
|
|
|
|
}
|