greybus: connection: set request handlers at creation
[cascardo/linux.git] / drivers / staging / greybus / svc.c
index f332129..feadb62 100644 (file)
@@ -7,67 +7,78 @@
  * Released under the GPLv2 only.
  */
 
+#include <linux/input.h>
+#include <linux/workqueue.h>
+
 #include "greybus.h"
 
-struct gb_svc {
-       struct gb_connection    *connection;
-       u8                      version_major;
-       u8                      version_minor;
+#define CPORT_FLAGS_E2EFC       BIT(0)
+#define CPORT_FLAGS_CSD_N       BIT(1)
+#define CPORT_FLAGS_CSV_N       BIT(2)
+
+#define SVC_KEY_ARA_BUTTON     KEY_A
+
+struct gb_svc_deferred_request {
+       struct work_struct work;
+       struct gb_operation *operation;
 };
 
-static struct ida greybus_svc_device_id_map;
 
-/*
- * AP's SVC cport is required early to get messages from the SVC. This happens
- * even before the Endo is created and hence any modules or interfaces.
- *
- * This is a temporary connection, used only at initial bootup.
- */
-struct gb_connection *
-gb_ap_svc_connection_create(struct greybus_host_device *hd)
+static ssize_t endo_id_show(struct device *dev,
+                       struct device_attribute *attr, char *buf)
 {
-       struct gb_connection *connection;
+       struct gb_svc *svc = to_gb_svc(dev);
 
-       connection = gb_connection_create_range(hd, NULL, hd->parent,
-                                               GB_SVC_CPORT_ID,
-                                               GREYBUS_PROTOCOL_SVC,
-                                               GB_SVC_CPORT_ID,
-                                               GB_SVC_CPORT_ID + 1);
+       return sprintf(buf, "0x%04x\n", svc->endo_id);
+}
+static DEVICE_ATTR_RO(endo_id);
 
-       return connection;
+static ssize_t ap_intf_id_show(struct device *dev,
+                       struct device_attribute *attr, char *buf)
+{
+       struct gb_svc *svc = to_gb_svc(dev);
+
+       return sprintf(buf, "%u\n", svc->ap_intf_id);
 }
-EXPORT_SYMBOL_GPL(gb_ap_svc_connection_create);
+static DEVICE_ATTR_RO(ap_intf_id);
 
-/*
- * We know endo-type and AP's interface id now, lets create a proper svc
- * connection (and its interface/bundle) now and get rid of the initial
- * 'partially' initialized one svc connection.
- */
-static struct gb_interface *
-gb_ap_interface_create(struct greybus_host_device *hd,
-                      struct gb_connection *connection, u8 interface_id)
+
+// FIXME
+// This is a hack, we need to do this "right" and clean the interface up
+// properly, not just forcibly yank the thing out of the system and hope for the
+// best.  But for now, people want their modules to come out without having to
+// throw the thing to the ground or get out a screwdriver.
+static ssize_t intf_eject_store(struct device *dev,
+                               struct device_attribute *attr, const char *buf,
+                               size_t len)
 {
-       struct gb_interface *intf;
-       struct device *dev = &hd->endo->dev;
+       struct gb_svc *svc = to_gb_svc(dev);
+       unsigned short intf_id;
+       int ret;
 
-       intf = gb_interface_create(hd, interface_id);
-       if (!intf) {
-               dev_err(dev, "%s: Failed to create interface with id %hhu\n",
-                       __func__, interface_id);
-               return NULL;
-       }
+       ret = kstrtou16(buf, 10, &intf_id);
+       if (ret < 0)
+               return ret;
 
-       intf->device_id = GB_DEVICE_ID_AP;
-       svc_update_connection(intf, connection);
+       dev_warn(dev, "Forcibly trying to eject interface %d\n", intf_id);
 
-       /* Its no longer a partially initialized connection */
-       hd->initial_svc_connection = NULL;
+       ret = gb_svc_intf_eject(svc, intf_id);
+       if (ret < 0)
+               return ret;
 
-       return intf;
+       return len;
 }
+static DEVICE_ATTR_WO(intf_eject);
 
-static int intf_device_id_operation(struct gb_svc *svc,
-                               u8 intf_id, u8 device_id)
+static struct attribute *svc_attrs[] = {
+       &dev_attr_endo_id.attr,
+       &dev_attr_ap_intf_id.attr,
+       &dev_attr_intf_eject.attr,
+       NULL,
+};
+ATTRIBUTE_GROUPS(svc);
+
+static int gb_svc_intf_device_id(struct gb_svc *svc, u8 intf_id, u8 device_id)
 {
        struct gb_svc_intf_device_id_request request;
 
@@ -78,7 +89,7 @@ static int intf_device_id_operation(struct gb_svc *svc,
                                 &request, sizeof(request), NULL, 0);
 }
 
-static int intf_reset_operation(struct gb_svc *svc, u8 intf_id)
+int gb_svc_intf_reset(struct gb_svc *svc, u8 intf_id)
 {
        struct gb_svc_intf_reset_request request;
 
@@ -87,39 +98,212 @@ static int intf_reset_operation(struct gb_svc *svc, u8 intf_id)
        return gb_operation_sync(svc->connection, GB_SVC_TYPE_INTF_RESET,
                                 &request, sizeof(request), NULL, 0);
 }
+EXPORT_SYMBOL_GPL(gb_svc_intf_reset);
+
+int gb_svc_intf_eject(struct gb_svc *svc, u8 intf_id)
+{
+       struct gb_svc_intf_eject_request request;
+
+       request.intf_id = intf_id;
+
+       /*
+        * The pulse width for module release in svc is long so we need to
+        * increase the timeout so the operation will not return to soon.
+        */
+       return gb_operation_sync_timeout(svc->connection,
+                                        GB_SVC_TYPE_INTF_EJECT, &request,
+                                        sizeof(request), NULL, 0,
+                                        GB_SVC_EJECT_TIME);
+}
+EXPORT_SYMBOL_GPL(gb_svc_intf_eject);
+
+int gb_svc_dme_peer_get(struct gb_svc *svc, u8 intf_id, u16 attr, u16 selector,
+                       u32 *value)
+{
+       struct gb_svc_dme_peer_get_request request;
+       struct gb_svc_dme_peer_get_response response;
+       u16 result;
+       int ret;
+
+       request.intf_id = intf_id;
+       request.attr = cpu_to_le16(attr);
+       request.selector = cpu_to_le16(selector);
+
+       ret = gb_operation_sync(svc->connection, GB_SVC_TYPE_DME_PEER_GET,
+                               &request, sizeof(request),
+                               &response, sizeof(response));
+       if (ret) {
+               dev_err(&svc->dev, "failed to get DME attribute (%u 0x%04x %u): %d\n",
+                               intf_id, attr, selector, ret);
+               return ret;
+       }
+
+       result = le16_to_cpu(response.result_code);
+       if (result) {
+               dev_err(&svc->dev, "UniPro error while getting DME attribute (%u 0x%04x %u): %u\n",
+                               intf_id, attr, selector, result);
+               return -EIO;
+       }
+
+       if (value)
+               *value = le32_to_cpu(response.attr_value);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(gb_svc_dme_peer_get);
+
+int gb_svc_dme_peer_set(struct gb_svc *svc, u8 intf_id, u16 attr, u16 selector,
+                       u32 value)
+{
+       struct gb_svc_dme_peer_set_request request;
+       struct gb_svc_dme_peer_set_response response;
+       u16 result;
+       int ret;
+
+       request.intf_id = intf_id;
+       request.attr = cpu_to_le16(attr);
+       request.selector = cpu_to_le16(selector);
+       request.value = cpu_to_le32(value);
+
+       ret = gb_operation_sync(svc->connection, GB_SVC_TYPE_DME_PEER_SET,
+                               &request, sizeof(request),
+                               &response, sizeof(response));
+       if (ret) {
+               dev_err(&svc->dev, "failed to set DME attribute (%u 0x%04x %u %u): %d\n",
+                               intf_id, attr, selector, value, ret);
+               return ret;
+       }
+
+       result = le16_to_cpu(response.result_code);
+       if (result) {
+               dev_err(&svc->dev, "UniPro error while setting DME attribute (%u 0x%04x %u %u): %u\n",
+                               intf_id, attr, selector, value, result);
+               return -EIO;
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(gb_svc_dme_peer_set);
+
+/*
+ * T_TstSrcIncrement is written by the module on ES2 as a stand-in for boot
+ * status attribute ES3_INIT_STATUS. AP needs to read and clear it, after
+ * reading a non-zero value from it.
+ *
+ * FIXME: This is module-hardware dependent and needs to be extended for every
+ * type of module we want to support.
+ */
+static int gb_svc_read_and_clear_module_boot_status(struct gb_interface *intf)
+{
+       struct gb_host_device *hd = intf->hd;
+       int ret;
+       u32 value;
+       u16 attr;
+       u8 init_status;
+
+       /*
+        * Check if the module is ES2 or ES3, and choose attr number
+        * appropriately.
+        * FIXME: Remove ES2 support from the kernel entirely.
+        */
+       if (intf->ddbl1_manufacturer_id == ES2_DDBL1_MFR_ID &&
+                               intf->ddbl1_product_id == ES2_DDBL1_PROD_ID)
+               attr = DME_ATTR_T_TST_SRC_INCREMENT;
+       else
+               attr = DME_ATTR_ES3_INIT_STATUS;
+
+       /* Read and clear boot status in ES3_INIT_STATUS */
+       ret = gb_svc_dme_peer_get(hd->svc, intf->interface_id, attr,
+                                 DME_ATTR_SELECTOR_INDEX, &value);
+
+       if (ret)
+               return ret;
+
+       /*
+        * A nonzero boot status indicates the module has finished
+        * booting. Clear it.
+        */
+       if (!value) {
+               dev_err(&intf->dev, "Module not ready yet\n");
+               return -ENODEV;
+       }
+
+       /*
+        * Check if the module needs to boot from UniPro.
+        * For ES2: We need to check lowest 8 bits of 'value'.
+        * For ES3: We need to check highest 8 bits out of 32 of 'value'.
+        * FIXME: Remove ES2 support from the kernel entirely.
+        */
+       if (intf->ddbl1_manufacturer_id == ES2_DDBL1_MFR_ID &&
+                               intf->ddbl1_product_id == ES2_DDBL1_PROD_ID)
+               init_status = value;
+       else
+               init_status = value >> 24;
+
+       if (init_status == DME_DIS_UNIPRO_BOOT_STARTED ||
+                               init_status == DME_DIS_FALLBACK_UNIPRO_BOOT_STARTED)
+               intf->boot_over_unipro = true;
+
+       return gb_svc_dme_peer_set(hd->svc, intf->interface_id, attr,
+                                  DME_ATTR_SELECTOR_INDEX, 0);
+}
 
-static int connection_create_operation(struct gb_svc *svc,
+int gb_svc_connection_create(struct gb_svc *svc,
                                u8 intf1_id, u16 cport1_id,
-                               u8 intf2_id, u16 cport2_id)
+                               u8 intf2_id, u16 cport2_id,
+                               bool boot_over_unipro)
 {
        struct gb_svc_conn_create_request request;
 
        request.intf1_id = intf1_id;
-       request.cport1_id = cport1_id;
+       request.cport1_id = cpu_to_le16(cport1_id);
        request.intf2_id = intf2_id;
-       request.cport2_id = cport2_id;
+       request.cport2_id = cpu_to_le16(cport2_id);
+       /*
+        * XXX: fix connections paramaters to TC0 and all CPort flags
+        * for now.
+        */
+       request.tc = 0;
+
+       /*
+        * We need to skip setting E2EFC and other flags to the connection
+        * create request, for all cports, on an interface that need to boot
+        * over unipro, i.e. interfaces required to download firmware.
+        */
+       if (boot_over_unipro)
+               request.flags = CPORT_FLAGS_CSV_N | CPORT_FLAGS_CSD_N;
+       else
+               request.flags = CPORT_FLAGS_CSV_N | CPORT_FLAGS_E2EFC;
 
        return gb_operation_sync(svc->connection, GB_SVC_TYPE_CONN_CREATE,
                                 &request, sizeof(request), NULL, 0);
 }
+EXPORT_SYMBOL_GPL(gb_svc_connection_create);
 
-static int connection_destroy_operation(struct gb_svc *svc,
-                               u8 intf1_id, u16 cport1_id,
-                               u8 intf2_id, u16 cport2_id)
+void gb_svc_connection_destroy(struct gb_svc *svc, u8 intf1_id, u16 cport1_id,
+                              u8 intf2_id, u16 cport2_id)
 {
        struct gb_svc_conn_destroy_request request;
+       struct gb_connection *connection = svc->connection;
+       int ret;
 
        request.intf1_id = intf1_id;
-       request.cport1_id = cport1_id;
+       request.cport1_id = cpu_to_le16(cport1_id);
        request.intf2_id = intf2_id;
-       request.cport2_id = cport2_id;
+       request.cport2_id = cpu_to_le16(cport2_id);
 
-       return gb_operation_sync(svc->connection, GB_SVC_TYPE_CONN_DESTROY,
-                                &request, sizeof(request), NULL, 0);
+       ret = gb_operation_sync(connection, GB_SVC_TYPE_CONN_DESTROY,
+                               &request, sizeof(request), NULL, 0);
+       if (ret) {
+               dev_err(&svc->dev, "failed to destroy connection (%u:%u %u:%u): %d\n",
+                               intf1_id, cport1_id, intf2_id, cport2_id, ret);
+       }
 }
+EXPORT_SYMBOL_GPL(gb_svc_connection_destroy);
 
-static int route_create_operation(struct gb_svc *svc, u8 intf1_id, u8 dev1_id,
-                                 u8 intf2_id, u8 dev2_id)
+/* Creates bi-directional routes between the devices */
+static int gb_svc_route_create(struct gb_svc *svc, u8 intf1_id, u8 dev1_id,
+                              u8 intf2_id, u8 dev2_id)
 {
        struct gb_svc_route_create_request request;
 
@@ -132,157 +316,201 @@ static int route_create_operation(struct gb_svc *svc, u8 intf1_id, u8 dev1_id,
                                 &request, sizeof(request), NULL, 0);
 }
 
-int gb_svc_intf_device_id(struct gb_svc *svc, u8 intf_id, u8 device_id)
+/* Destroys bi-directional routes between the devices */
+static void gb_svc_route_destroy(struct gb_svc *svc, u8 intf1_id, u8 intf2_id)
 {
-       return intf_device_id_operation(svc, intf_id, device_id);
-}
-EXPORT_SYMBOL_GPL(gb_svc_intf_device_id);
+       struct gb_svc_route_destroy_request request;
+       int ret;
 
-int gb_svc_intf_reset(struct gb_svc *svc, u8 intf_id)
-{
-       return intf_reset_operation(svc, intf_id);
-}
-EXPORT_SYMBOL_GPL(gb_svc_intf_reset);
+       request.intf1_id = intf1_id;
+       request.intf2_id = intf2_id;
 
-int gb_svc_connection_create(struct gb_svc *svc,
-                               u8 intf1_id, u16 cport1_id,
-                               u8 intf2_id, u16 cport2_id)
-{
-       return connection_create_operation(svc, intf1_id, cport1_id,
-                                               intf2_id, cport2_id);
+       ret = gb_operation_sync(svc->connection, GB_SVC_TYPE_ROUTE_DESTROY,
+                               &request, sizeof(request), NULL, 0);
+       if (ret) {
+               dev_err(&svc->dev, "failed to destroy route (%u %u): %d\n",
+                               intf1_id, intf2_id, ret);
+       }
 }
-EXPORT_SYMBOL_GPL(gb_svc_connection_create);
 
-int gb_svc_connection_destroy(struct gb_svc *svc,
-                               u8 intf1_id, u16 cport1_id,
-                               u8 intf2_id, u16 cport2_id)
+int gb_svc_intf_set_power_mode(struct gb_svc *svc, u8 intf_id, u8 hs_series,
+                              u8 tx_mode, u8 tx_gear, u8 tx_nlanes,
+                              u8 rx_mode, u8 rx_gear, u8 rx_nlanes,
+                              u8 flags, u32 quirks)
 {
-       return connection_destroy_operation(svc, intf1_id, cport1_id,
-                                               intf2_id, cport2_id);
+       struct gb_svc_intf_set_pwrm_request request;
+       struct gb_svc_intf_set_pwrm_response response;
+       int ret;
+
+       request.intf_id = intf_id;
+       request.hs_series = hs_series;
+       request.tx_mode = tx_mode;
+       request.tx_gear = tx_gear;
+       request.tx_nlanes = tx_nlanes;
+       request.rx_mode = rx_mode;
+       request.rx_gear = rx_gear;
+       request.rx_nlanes = rx_nlanes;
+       request.flags = flags;
+       request.quirks = cpu_to_le32(quirks);
+
+       ret = gb_operation_sync(svc->connection, GB_SVC_TYPE_INTF_SET_PWRM,
+                               &request, sizeof(request),
+                               &response, sizeof(response));
+       if (ret < 0)
+               return ret;
+
+       return le16_to_cpu(response.result_code);
 }
-EXPORT_SYMBOL_GPL(gb_svc_connection_destroy);
+EXPORT_SYMBOL_GPL(gb_svc_intf_set_power_mode);
 
-int gb_svc_route_create(struct gb_svc *svc, u8 intf1_id, u8 dev1_id,
-                       u8 intf2_id, u8 dev2_id) {
-       return route_create_operation(svc, intf1_id, dev1_id,
-                                     intf2_id, dev2_id);
+int gb_svc_ping(struct gb_svc *svc)
+{
+       return gb_operation_sync(svc->connection, GB_SVC_TYPE_PING,
+                                NULL, 0, NULL, 0);
 }
-EXPORT_SYMBOL_GPL(gb_svc_route_create);
+EXPORT_SYMBOL_GPL(gb_svc_ping);
 
 static int gb_svc_version_request(struct gb_operation *op)
 {
        struct gb_connection *connection = op->connection;
-       struct gb_protocol_version_response *version;
-       struct device *dev = &connection->dev;
+       struct gb_svc *svc = connection->private;
+       struct gb_protocol_version_request *request;
+       struct gb_protocol_version_response *response;
 
-       version = op->request->payload;
+       if (op->request->payload_size < sizeof(*request)) {
+               dev_err(&svc->dev, "short version request (%zu < %zu)\n",
+                               op->request->payload_size,
+                               sizeof(*request));
+               return -EINVAL;
+       }
+
+       request = op->request->payload;
 
-       if (version->major > GB_SVC_VERSION_MAJOR) {
-               dev_err(&connection->dev,
-                       "unsupported major version (%hhu > %hhu)\n",
-                       version->major, GB_SVC_VERSION_MAJOR);
+       if (request->major > GB_SVC_VERSION_MAJOR) {
+               dev_warn(&svc->dev, "unsupported major version (%u > %u)\n",
+                               request->major, GB_SVC_VERSION_MAJOR);
                return -ENOTSUPP;
        }
 
-       if (!gb_operation_response_alloc(op, sizeof(*version), GFP_KERNEL)) {
-               dev_err(dev, "%s: error allocating response\n",
-                               __func__);
+       svc->protocol_major = request->major;
+       svc->protocol_minor = request->minor;
+
+       if (!gb_operation_response_alloc(op, sizeof(*response), GFP_KERNEL))
                return -ENOMEM;
-       }
 
-       version = op->response->payload;
-       version->major = GB_SVC_VERSION_MAJOR;
-       version->minor = GB_SVC_VERSION_MINOR;
+       response = op->response->payload;
+       response->major = svc->protocol_major;
+       response->minor = svc->protocol_minor;
+
        return 0;
 }
 
 static int gb_svc_hello(struct gb_operation *op)
 {
        struct gb_connection *connection = op->connection;
-       struct greybus_host_device *hd = connection->hd;
+       struct gb_svc *svc = connection->private;
        struct gb_svc_hello_request *hello_request;
-       struct device *dev = &connection->dev;
-       struct gb_interface *intf;
-       u16 endo_id;
-       u8 interface_id;
        int ret;
 
-       /* Hello message should be received only during early bootup */
-       WARN_ON(hd->initial_svc_connection != connection);
-
-       /*
-        * SVC sends information about the endo and interface-id on the hello
-        * request, use that to create an endo.
-        */
-       if (op->request->payload_size != sizeof(*hello_request)) {
-               dev_err(dev, "%s: Illegal size of hello request (%zu %zu)\n",
-                       __func__, op->request->payload_size,
-                       sizeof(*hello_request));
+       if (op->request->payload_size < sizeof(*hello_request)) {
+               dev_warn(&svc->dev, "short hello request (%zu < %zu)\n",
+                               op->request->payload_size,
+                               sizeof(*hello_request));
                return -EINVAL;
        }
 
        hello_request = op->request->payload;
-       endo_id = le16_to_cpu(hello_request->endo_id);
-       interface_id = hello_request->interface_id;
+       svc->endo_id = le16_to_cpu(hello_request->endo_id);
+       svc->ap_intf_id = hello_request->interface_id;
 
-       /* Setup Endo */
-       ret = greybus_endo_setup(hd, endo_id, interface_id);
-       if (ret)
+       ret = device_add(&svc->dev);
+       if (ret) {
+               dev_err(&svc->dev, "failed to register svc device: %d\n", ret);
                return ret;
+       }
 
-       /*
-        * Endo and its modules are ready now, fix AP's partially initialized
-        * svc protocol and its connection.
-        */
-       intf = gb_ap_interface_create(hd, connection, interface_id);
-       if (!intf) {
-               gb_endo_remove(hd->endo);
+       ret = input_register_device(svc->input);
+       if (ret) {
+               dev_err(&svc->dev, "failed to register input: %d\n", ret);
+               device_del(&svc->dev);
                return ret;
        }
 
        return 0;
 }
 
-static int gb_svc_intf_hotplug_recv(struct gb_operation *op)
+static void gb_svc_intf_remove(struct gb_svc *svc, struct gb_interface *intf)
 {
-       struct gb_message *request = op->request;
-       struct gb_svc_intf_hotplug_request *hotplug = request->payload;
-       struct gb_svc *svc = op->connection->private;
-       struct greybus_host_device *hd = op->connection->bundle->intf->hd;
-       struct device *dev = &op->connection->dev;
+       u8 intf_id = intf->interface_id;
+       u8 device_id = intf->device_id;
+
+       intf->disconnected = true;
+
+       gb_interface_remove(intf);
+
+       /*
+        * Destroy the two-way route between the AP and the interface.
+        */
+       gb_svc_route_destroy(svc, svc->ap_intf_id, intf_id);
+
+       ida_simple_remove(&svc->device_id_map, device_id);
+}
+
+static void gb_svc_process_intf_hotplug(struct gb_operation *operation)
+{
+       struct gb_svc_intf_hotplug_request *request;
+       struct gb_connection *connection = operation->connection;
+       struct gb_svc *svc = connection->private;
+       struct gb_host_device *hd = connection->hd;
        struct gb_interface *intf;
        u8 intf_id, device_id;
-       u32 unipro_mfg_id;
-       u32 unipro_prod_id;
-       u32 ara_vend_id;
-       u32 ara_prod_id;
        int ret;
 
-       if (request->payload_size < sizeof(*hotplug)) {
-               dev_err(dev, "%s: short hotplug request received\n", __func__);
-               return -EINVAL;
-       }
+       /* The request message size has already been verified. */
+       request = operation->request->payload;
+       intf_id = request->intf_id;
 
-       /*
-        * Grab the information we need.
-        *
-        * XXX I'd really like to acknowledge receipt, and then
-        * XXX continue processing the request.  There's no need
-        * XXX for the SVC to wait.  In fact, it might be best to
-        * XXX have the SVC get acknowledgement before we proceed.
-        */
-       intf_id = hotplug->intf_id;
-       unipro_mfg_id = le32_to_cpu(hotplug->data.unipro_mfg_id);
-       unipro_prod_id = le32_to_cpu(hotplug->data.unipro_prod_id);
-       ara_vend_id = le32_to_cpu(hotplug->data.ara_vend_id);
-       ara_prod_id = le32_to_cpu(hotplug->data.ara_prod_id);
+       dev_dbg(&svc->dev, "%s - id = %u\n", __func__, intf_id);
+
+       intf = gb_interface_find(hd, intf_id);
+       if (intf) {
+               /*
+                * We have received a hotplug request for an interface that
+                * already exists.
+                *
+                * This can happen in cases like:
+                * - bootrom loading the firmware image and booting into that,
+                *   which only generates a hotplug event. i.e. no hot-unplug
+                *   event.
+                * - Or the firmware on the module crashed and sent hotplug
+                *   request again to the SVC, which got propagated to AP.
+                *
+                * Remove the interface and add it again, and let user know
+                * about this with a print message.
+                */
+               dev_info(&svc->dev, "removing interface %u to add it again\n",
+                               intf_id);
+               gb_svc_intf_remove(svc, intf);
+       }
 
-       // FIXME May require firmware download
        intf = gb_interface_create(hd, intf_id);
        if (!intf) {
-               dev_err(dev, "%s: Failed to create interface with id %hhu\n",
-                       __func__, intf_id);
-               return -EINVAL;
+               dev_err(&svc->dev, "failed to create interface %u\n",
+                               intf_id);
+               return;
+       }
+
+       intf->ddbl1_manufacturer_id = le32_to_cpu(request->data.ddbl1_mfr_id);
+       intf->ddbl1_product_id = le32_to_cpu(request->data.ddbl1_prod_id);
+       intf->vendor_id = le32_to_cpu(request->data.ara_vend_id);
+       intf->product_id = le32_to_cpu(request->data.ara_prod_id);
+       intf->serial_number = le64_to_cpu(request->data.serial_number);
+
+       ret = gb_svc_read_and_clear_module_boot_status(intf);
+       if (ret) {
+               dev_err(&svc->dev, "failed to clear boot status of interface %u: %d\n",
+                               intf_id, ret);
+               goto destroy_interface;
        }
 
        /*
@@ -293,85 +521,180 @@ static int gb_svc_intf_hotplug_recv(struct gb_operation *op)
         * XXX Do we need to allocate device ID for SVC or the AP here? And what
         * XXX about an AP with multiple interface blocks?
         */
-       device_id = ida_simple_get(&greybus_svc_device_id_map,
-                                  GB_DEVICE_ID_MODULES_START, 0, GFP_ATOMIC);
+       device_id = ida_simple_get(&svc->device_id_map,
+                                  GB_DEVICE_ID_MODULES_START, 0, GFP_KERNEL);
        if (device_id < 0) {
                ret = device_id;
-               dev_err(dev, "%s: Failed to allocate device id for interface with id %hhu (%d)\n",
-                       __func__, intf_id, ret);
+               dev_err(&svc->dev, "failed to allocate device id for interface %u: %d\n",
+                               intf_id, ret);
                goto destroy_interface;
        }
 
-       ret = intf_device_id_operation(svc, intf_id, device_id);
+       ret = gb_svc_intf_device_id(svc, intf_id, device_id);
        if (ret) {
-               dev_err(dev, "%s: Device id operation failed, interface %hhu device_id %hhu (%d)\n",
-                       __func__, intf_id, device_id, ret);
+               dev_err(&svc->dev, "failed to set device id %u for interface %u: %d\n",
+                               device_id, intf_id, ret);
                goto ida_put;
        }
 
-       ret = gb_interface_init(intf, device_id);
+       /*
+        * Create a two-way route between the AP and the new interface
+        */
+       ret = gb_svc_route_create(svc, svc->ap_intf_id, GB_DEVICE_ID_AP,
+                                 intf_id, device_id);
        if (ret) {
-               dev_err(dev, "%s: Failed to initialize interface, interface %hhu device_id %hhu (%d)\n",
-                       __func__, intf_id, device_id, ret);
+               dev_err(&svc->dev, "failed to create route to interface %u (device id %u): %d\n",
+                               intf_id, device_id, ret);
                goto svc_id_free;
        }
 
-       return 0;
+       ret = gb_interface_init(intf, device_id);
+       if (ret) {
+               dev_err(&svc->dev, "failed to initialize interface %u (device id %u): %d\n",
+                               intf_id, device_id, ret);
+               goto destroy_route;
+       }
 
+       return;
+
+destroy_route:
+       gb_svc_route_destroy(svc, svc->ap_intf_id, intf_id);
 svc_id_free:
        /*
         * XXX Should we tell SVC that this id doesn't belong to interface
         * XXX anymore.
         */
 ida_put:
-       ida_simple_remove(&greybus_svc_device_id_map, device_id);
+       ida_simple_remove(&svc->device_id_map, device_id);
 destroy_interface:
-       gb_interface_remove(hd, intf_id);
-
-       return ret;
+       gb_interface_remove(intf);
 }
 
-static int gb_svc_intf_hot_unplug_recv(struct gb_operation *op)
+static void gb_svc_process_intf_hot_unplug(struct gb_operation *operation)
 {
-       struct gb_message *request = op->request;
-       struct gb_svc_intf_hot_unplug_request *hot_unplug = request->payload;
-       struct greybus_host_device *hd = op->connection->bundle->intf->hd;
-       struct device *dev = &op->connection->dev;
-       u8 device_id;
+       struct gb_svc *svc = operation->connection->private;
+       struct gb_svc_intf_hot_unplug_request *request;
+       struct gb_host_device *hd = operation->connection->hd;
        struct gb_interface *intf;
        u8 intf_id;
 
-       if (request->payload_size < sizeof(*hot_unplug)) {
-               dev_err(&op->connection->dev,
-                       "short hot unplug request received\n");
-               return -EINVAL;
-       }
+       /* The request message size has already been verified. */
+       request = operation->request->payload;
+       intf_id = request->intf_id;
 
-       intf_id = hot_unplug->intf_id;
+       dev_dbg(&svc->dev, "%s - id = %u\n", __func__, intf_id);
 
        intf = gb_interface_find(hd, intf_id);
        if (!intf) {
-               dev_err(dev, "%s: Couldn't find interface for id %hhu\n",
-                       __func__, intf_id);
-               return -EINVAL;
+               dev_warn(&svc->dev, "could not find hot-unplug interface %u\n",
+                               intf_id);
+               return;
        }
 
-       device_id = intf->device_id;
-       gb_interface_remove(hd, intf_id);
-       ida_simple_remove(&greybus_svc_device_id_map, device_id);
+       gb_svc_intf_remove(svc, intf);
+}
+
+static void gb_svc_process_deferred_request(struct work_struct *work)
+{
+       struct gb_svc_deferred_request *dr;
+       struct gb_operation *operation;
+       struct gb_svc *svc;
+       u8 type;
+
+       dr = container_of(work, struct gb_svc_deferred_request, work);
+       operation = dr->operation;
+       svc = operation->connection->private;
+       type = operation->request->header->type;
+
+       switch (type) {
+       case GB_SVC_TYPE_INTF_HOTPLUG:
+               gb_svc_process_intf_hotplug(operation);
+               break;
+       case GB_SVC_TYPE_INTF_HOT_UNPLUG:
+               gb_svc_process_intf_hot_unplug(operation);
+               break;
+       default:
+               dev_err(&svc->dev, "bad deferred request type: 0x%02x\n", type);
+       }
+
+       gb_operation_put(operation);
+       kfree(dr);
+}
+
+static int gb_svc_queue_deferred_request(struct gb_operation *operation)
+{
+       struct gb_svc *svc = operation->connection->private;
+       struct gb_svc_deferred_request *dr;
+
+       dr = kmalloc(sizeof(*dr), GFP_KERNEL);
+       if (!dr)
+               return -ENOMEM;
+
+       gb_operation_get(operation);
+
+       dr->operation = operation;
+       INIT_WORK(&dr->work, gb_svc_process_deferred_request);
+
+       queue_work(svc->wq, &dr->work);
 
        return 0;
 }
 
+/*
+ * Bringing up a module can be time consuming, as that may require lots of
+ * initialization on the module side. Over that, we may also need to download
+ * the firmware first and flash that on the module.
+ *
+ * In order not to make other svc events wait for all this to finish,
+ * handle most of module hotplug stuff outside of the hotplug callback, with
+ * help of a workqueue.
+ */
+static int gb_svc_intf_hotplug_recv(struct gb_operation *op)
+{
+       struct gb_svc *svc = op->connection->private;
+       struct gb_svc_intf_hotplug_request *request;
+
+       if (op->request->payload_size < sizeof(*request)) {
+               dev_warn(&svc->dev, "short hotplug request received (%zu < %zu)\n",
+                               op->request->payload_size, sizeof(*request));
+               return -EINVAL;
+       }
+
+       request = op->request->payload;
+
+       dev_dbg(&svc->dev, "%s - id = %u\n", __func__, request->intf_id);
+
+       return gb_svc_queue_deferred_request(op);
+}
+
+static int gb_svc_intf_hot_unplug_recv(struct gb_operation *op)
+{
+       struct gb_svc *svc = op->connection->private;
+       struct gb_svc_intf_hot_unplug_request *request;
+
+       if (op->request->payload_size < sizeof(*request)) {
+               dev_warn(&svc->dev, "short hot unplug request received (%zu < %zu)\n",
+                               op->request->payload_size, sizeof(*request));
+               return -EINVAL;
+       }
+
+       request = op->request->payload;
+
+       dev_dbg(&svc->dev, "%s - id = %u\n", __func__, request->intf_id);
+
+       return gb_svc_queue_deferred_request(op);
+}
+
 static int gb_svc_intf_reset_recv(struct gb_operation *op)
 {
+       struct gb_svc *svc = op->connection->private;
        struct gb_message *request = op->request;
        struct gb_svc_intf_reset_request *reset;
        u8 intf_id;
 
        if (request->payload_size < sizeof(*reset)) {
-               dev_err(&op->connection->dev,
-                       "short reset request received\n");
+               dev_warn(&svc->dev, "short reset request received (%zu < %zu)\n",
+                               request->payload_size, sizeof(*reset));
                return -EINVAL;
        }
        reset = request->payload;
@@ -383,60 +706,247 @@ static int gb_svc_intf_reset_recv(struct gb_operation *op)
        return 0;
 }
 
-static int gb_svc_request_recv(u8 type, struct gb_operation *op)
+static int gb_svc_key_code_map(struct gb_svc *svc, u16 key_code, u16 *code)
+{
+       switch (key_code) {
+       case GB_KEYCODE_ARA:
+               *code = SVC_KEY_ARA_BUTTON;
+               break;
+       default:
+               dev_warn(&svc->dev, "unknown keycode received: %u\n", key_code);
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int gb_svc_key_event_recv(struct gb_operation *op)
+{
+       struct gb_svc *svc = op->connection->private;
+       struct gb_message *request = op->request;
+       struct gb_svc_key_event_request *key;
+       u16 code;
+       u8 event;
+       int ret;
+
+       if (request->payload_size < sizeof(*key)) {
+               dev_warn(&svc->dev, "short key request received (%zu < %zu)\n",
+                        request->payload_size, sizeof(*key));
+               return -EINVAL;
+       }
+
+       key = request->payload;
+
+       ret = gb_svc_key_code_map(svc, le16_to_cpu(key->key_code), &code);
+       if (ret < 0)
+               return ret;
+
+       event = key->key_event;
+       if ((event != GB_SVC_KEY_PRESSED) && (event != GB_SVC_KEY_RELEASED)) {
+               dev_warn(&svc->dev, "unknown key event received: %u\n", event);
+               return -EINVAL;
+       }
+
+       input_report_key(svc->input, code, (event == GB_SVC_KEY_PRESSED));
+       input_sync(svc->input);
+
+       return 0;
+}
+
+static int gb_svc_request_handler(struct gb_operation *op)
 {
+       struct gb_connection *connection = op->connection;
+       struct gb_svc *svc = connection->private;
+       u8 type = op->type;
+       int ret = 0;
+
+       /*
+        * SVC requests need to follow a specific order (at least initially) and
+        * below code takes care of enforcing that. The expected order is:
+        * - PROTOCOL_VERSION
+        * - SVC_HELLO
+        * - Any other request, but the earlier two.
+        *
+        * Incoming requests are guaranteed to be serialized and so we don't
+        * need to protect 'state' for any races.
+        */
        switch (type) {
-       case GB_SVC_TYPE_PROTOCOL_VERSION:
-               return gb_svc_version_request(op);
+       case GB_REQUEST_TYPE_PROTOCOL_VERSION:
+               if (svc->state != GB_SVC_STATE_RESET)
+                       ret = -EINVAL;
+               break;
        case GB_SVC_TYPE_SVC_HELLO:
-               return gb_svc_hello(op);
+               if (svc->state != GB_SVC_STATE_PROTOCOL_VERSION)
+                       ret = -EINVAL;
+               break;
+       default:
+               if (svc->state != GB_SVC_STATE_SVC_HELLO)
+                       ret = -EINVAL;
+               break;
+       }
+
+       if (ret) {
+               dev_warn(&svc->dev, "unexpected request 0x%02x received (state %u)\n",
+                               type, svc->state);
+               return ret;
+       }
+
+       switch (type) {
+       case GB_REQUEST_TYPE_PROTOCOL_VERSION:
+               ret = gb_svc_version_request(op);
+               if (!ret)
+                       svc->state = GB_SVC_STATE_PROTOCOL_VERSION;
+               return ret;
+       case GB_SVC_TYPE_SVC_HELLO:
+               ret = gb_svc_hello(op);
+               if (!ret)
+                       svc->state = GB_SVC_STATE_SVC_HELLO;
+               return ret;
        case GB_SVC_TYPE_INTF_HOTPLUG:
                return gb_svc_intf_hotplug_recv(op);
        case GB_SVC_TYPE_INTF_HOT_UNPLUG:
                return gb_svc_intf_hot_unplug_recv(op);
        case GB_SVC_TYPE_INTF_RESET:
                return gb_svc_intf_reset_recv(op);
+       case GB_SVC_TYPE_KEY_EVENT:
+               return gb_svc_key_event_recv(op);
        default:
-               dev_err(&op->connection->dev,
-                       "unsupported request: %hhu\n", type);
+               dev_warn(&svc->dev, "unsupported request 0x%02x\n", type);
                return -EINVAL;
        }
 }
 
-static int gb_svc_connection_init(struct gb_connection *connection)
+static struct input_dev *gb_svc_input_create(struct gb_svc *svc)
+{
+       struct input_dev *input_dev;
+
+       input_dev = input_allocate_device();
+       if (!input_dev)
+               return ERR_PTR(-ENOMEM);
+
+       input_dev->name = dev_name(&svc->dev);
+       svc->input_phys = kasprintf(GFP_KERNEL, "greybus-%s/input0",
+                                   input_dev->name);
+       if (!svc->input_phys)
+               goto err_free_input;
+
+       input_dev->phys = svc->input_phys;
+       input_dev->dev.parent = &svc->dev;
+
+       input_set_drvdata(input_dev, svc);
+
+       input_set_capability(input_dev, EV_KEY, SVC_KEY_ARA_BUTTON);
+
+       return input_dev;
+
+err_free_input:
+       input_free_device(svc->input);
+       return ERR_PTR(-ENOMEM);
+}
+
+static void gb_svc_release(struct device *dev)
+{
+       struct gb_svc *svc = to_gb_svc(dev);
+
+       if (svc->connection)
+               gb_connection_destroy(svc->connection);
+       ida_destroy(&svc->device_id_map);
+       destroy_workqueue(svc->wq);
+       kfree(svc->input_phys);
+       kfree(svc);
+}
+
+struct device_type greybus_svc_type = {
+       .name           = "greybus_svc",
+       .release        = gb_svc_release,
+};
+
+struct gb_svc *gb_svc_create(struct gb_host_device *hd)
 {
        struct gb_svc *svc;
 
        svc = kzalloc(sizeof(*svc), GFP_KERNEL);
        if (!svc)
-               return -ENOMEM;
+               return NULL;
+
+       svc->wq = alloc_workqueue("%s:svc", WQ_UNBOUND, 1, dev_name(&hd->dev));
+       if (!svc->wq) {
+               kfree(svc);
+               return NULL;
+       }
+
+       svc->dev.parent = &hd->dev;
+       svc->dev.bus = &greybus_bus_type;
+       svc->dev.type = &greybus_svc_type;
+       svc->dev.groups = svc_groups;
+       svc->dev.dma_mask = svc->dev.parent->dma_mask;
+       device_initialize(&svc->dev);
 
-       svc->connection = connection;
-       connection->private = svc;
+       dev_set_name(&svc->dev, "%d-svc", hd->bus_id);
+
+       ida_init(&svc->device_id_map);
+       svc->state = GB_SVC_STATE_RESET;
+       svc->hd = hd;
+
+       svc->input = gb_svc_input_create(svc);
+       if (IS_ERR(svc->input)) {
+               dev_err(&svc->dev, "failed to create input device: %ld\n",
+                       PTR_ERR(svc->input));
+               goto err_put_device;
+       }
+
+       svc->connection = gb_connection_create_static(hd, GB_SVC_CPORT_ID,
+                                               gb_svc_request_handler);
+       if (IS_ERR(svc->connection)) {
+               dev_err(&svc->dev, "failed to create connection: %ld\n",
+                               PTR_ERR(svc->connection));
+               goto err_free_input;
+       }
 
-       WARN_ON(connection->hd->initial_svc_connection);
-       connection->hd->initial_svc_connection = connection;
+       svc->connection->private = svc;
 
-       ida_init(&greybus_svc_device_id_map);
+       return svc;
+
+err_free_input:
+       input_free_device(svc->input);
+err_put_device:
+       put_device(&svc->dev);
+       return NULL;
+}
+
+int gb_svc_add(struct gb_svc *svc)
+{
+       int ret;
+
+       /*
+        * The SVC protocol is currently driven by the SVC, so the SVC device
+        * is added from the connection request handler when enough
+        * information has been received.
+        */
+       ret = gb_connection_enable(svc->connection);
+       if (ret)
+               return ret;
 
        return 0;
 }
 
-static void gb_svc_connection_exit(struct gb_connection *connection)
+void gb_svc_del(struct gb_svc *svc)
 {
-       struct gb_svc *svc = connection->private;
+       gb_connection_disable(svc->connection);
 
-       connection->private = NULL;
-       kfree(svc);
+       /*
+        * The SVC device and input device may have been registered
+        * from the request handler.
+        */
+       if (device_is_registered(&svc->dev)) {
+               input_unregister_device(svc->input);
+               device_del(&svc->dev);
+       }
+
+       flush_workqueue(svc->wq);
 }
 
-static struct gb_protocol svc_protocol = {
-       .name                   = "svc",
-       .id                     = GREYBUS_PROTOCOL_SVC,
-       .major                  = GB_SVC_VERSION_MAJOR,
-       .minor                  = GB_SVC_VERSION_MINOR,
-       .connection_init        = gb_svc_connection_init,
-       .connection_exit        = gb_svc_connection_exit,
-       .request_recv           = gb_svc_request_recv,
-};
-gb_builtin_protocol_driver(svc_protocol);
+void gb_svc_put(struct gb_svc *svc)
+{
+       put_device(&svc->dev);
+}