greybus: svc: drop legacy-protocol dependency
[cascardo/linux.git] / drivers / staging / greybus / svc.c
index 11fa8c9..fcdee90 100644 (file)
@@ -7,7 +7,6 @@
  * Released under the GPLv2 only.
  */
 
-#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 #include <linux/workqueue.h>
 
 #include "greybus.h"
 #define CPORT_FLAGS_CSV_N       BIT(2)
 
 
-struct svc_hotplug {
+struct gb_svc_deferred_request {
        struct work_struct work;
-       struct gb_connection *connection;
-       struct gb_svc_intf_hotplug_request data;
+       struct gb_operation *operation;
 };
 
 
@@ -42,9 +40,38 @@ static ssize_t ap_intf_id_show(struct device *dev,
 }
 static DEVICE_ATTR_RO(ap_intf_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_svc *svc = to_gb_svc(dev);
+       unsigned short intf_id;
+       int ret;
+
+       ret = kstrtou16(buf, 10, &intf_id);
+       if (ret < 0)
+               return ret;
+
+       dev_warn(dev, "Forcibly trying to eject interface %d\n", intf_id);
+
+       ret = gb_svc_intf_eject(svc, intf_id);
+       if (ret < 0)
+               return ret;
+
+       return len;
+}
+static DEVICE_ATTR_WO(intf_eject);
+
 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);
@@ -71,6 +98,23 @@ int gb_svc_intf_reset(struct gb_svc *svc, u8 intf_id)
 }
 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)
 {
@@ -87,16 +131,16 @@ int gb_svc_dme_peer_get(struct gb_svc *svc, u8 intf_id, u16 attr, u16 selector,
                                &request, sizeof(request),
                                &response, sizeof(response));
        if (ret) {
-               pr_err("failed to get DME attribute (%hhu %hx %hu) %d\n",
-                      intf_id, attr, selector, 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) {
-               pr_err("Unipro error %hu while getting DME attribute (%hhu %hx %hu)\n",
-                      result, intf_id, attr, selector);
-               return -EINVAL;
+               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)
@@ -123,16 +167,16 @@ int gb_svc_dme_peer_set(struct gb_svc *svc, u8 intf_id, u16 attr, u16 selector,
                                &request, sizeof(request),
                                &response, sizeof(response));
        if (ret) {
-               pr_err("failed to set DME attribute (%hhu %hx %hu %u) %d\n",
-                      intf_id, attr, selector, value, 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) {
-               pr_err("Unipro error %hu while setting DME attribute (%hhu %hx %hu %u)\n",
-                      result, intf_id, attr, selector, value);
-               return -EINVAL;
+               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;
@@ -141,8 +185,8 @@ 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. AP needs to read and clear it, after reading a non-zero
- * value from it.
+ * 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.
@@ -152,10 +196,22 @@ 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;
 
-       /* Read and clear boot status in T_TstSrcIncrement */
-       ret = gb_svc_dme_peer_get(hd->svc, intf->interface_id,
-                                 DME_ATTR_T_TST_SRC_INCREMENT,
+       /*
+        * 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)
@@ -171,19 +227,22 @@ static int gb_svc_read_and_clear_module_boot_status(struct gb_interface *intf)
        }
 
        /*
-        * Check if the module needs to boot from unipro.
+        * 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: Add code to find if we are on ES2 or ES3 to have separate
-        * checks.
+        * FIXME: Remove ES2 support from the kernel entirely.
         */
-       if (value == DME_TSI_UNIPRO_BOOT_STARTED ||
-           value == DME_TSI_FALLBACK_UNIPRO_BOOT_STARTED)
+       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,
-                                  DME_ATTR_T_TST_SRC_INCREMENT,
+       return gb_svc_dme_peer_set(hd->svc, intf->interface_id, attr,
                                   DME_ATTR_SELECTOR_INDEX, 0);
 }
 
@@ -233,9 +292,10 @@ void gb_svc_connection_destroy(struct gb_svc *svc, u8 intf1_id, u16 cport1_id,
 
        ret = gb_operation_sync(connection, GB_SVC_TYPE_CONN_DESTROY,
                                &request, sizeof(request), NULL, 0);
-       if (ret)
-               pr_err("failed to destroy connection (%hhu:%hu %hhu:%hu) %d\n",
-                      intf1_id, cport1_id, intf2_id, cport2_id, ret);
+       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);
 
@@ -265,20 +325,51 @@ static void gb_svc_route_destroy(struct gb_svc *svc, u8 intf1_id, u8 intf2_id)
 
        ret = gb_operation_sync(svc->connection, GB_SVC_TYPE_ROUTE_DESTROY,
                                &request, sizeof(request), NULL, 0);
-       if (ret)
-               pr_err("failed to destroy route (%hhu %hhu) %d\n",
-                       intf1_id, intf2_id, ret);
+       if (ret) {
+               dev_err(&svc->dev, "failed to destroy route (%u %u): %d\n",
+                               intf1_id, intf2_id, ret);
+       }
+}
+
+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)
+{
+       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_intf_set_power_mode);
 
 static int gb_svc_version_request(struct gb_operation *op)
 {
        struct gb_connection *connection = op->connection;
+       struct gb_svc *svc = connection->private;
        struct gb_protocol_version_request *request;
        struct gb_protocol_version_response *response;
 
        if (op->request->payload_size < sizeof(*request)) {
-               pr_err("%d: short version request (%zu < %zu)\n",
-                               connection->intf_cport_id,
+               dev_err(&svc->dev, "short version request (%zu < %zu)\n",
                                op->request->payload_size,
                                sizeof(*request));
                return -EINVAL;
@@ -287,20 +378,16 @@ static int gb_svc_version_request(struct gb_operation *op)
        request = op->request->payload;
 
        if (request->major > GB_SVC_VERSION_MAJOR) {
-               pr_err("%d: unsupported major version (%hhu > %hhu)\n",
-                      connection->intf_cport_id, request->major,
-                      GB_SVC_VERSION_MAJOR);
+               dev_warn(&svc->dev, "unsupported major version (%u > %u)\n",
+                               request->major, GB_SVC_VERSION_MAJOR);
                return -ENOTSUPP;
        }
 
        connection->module_major = request->major;
        connection->module_minor = request->minor;
 
-       if (!gb_operation_response_alloc(op, sizeof(*response), GFP_KERNEL)) {
-               pr_err("%d: error allocating response\n",
-                      connection->intf_cport_id);
+       if (!gb_operation_response_alloc(op, sizeof(*response), GFP_KERNEL))
                return -ENOMEM;
-       }
 
        response = op->response->payload;
        response->major = connection->module_major;
@@ -313,18 +400,13 @@ static int gb_svc_hello(struct gb_operation *op)
 {
        struct gb_connection *connection = op->connection;
        struct gb_svc *svc = connection->private;
-       struct gb_host_device *hd = connection->hd;
        struct gb_svc_hello_request *hello_request;
        int ret;
 
-       /*
-        * 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)) {
-               pr_err("%d: Illegal size of hello request (%zu < %zu)\n",
-                      connection->intf_cport_id, 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;
        }
 
@@ -338,22 +420,16 @@ static int gb_svc_hello(struct gb_operation *op)
                return ret;
        }
 
-       /* Setup Endo */
-       ret = greybus_endo_setup(hd, svc->endo_id, svc->ap_intf_id);
-       if (ret)
-               return ret;
-
        return 0;
 }
 
-static void svc_intf_remove(struct gb_connection *connection,
-                           struct gb_interface *intf)
+static void gb_svc_intf_remove(struct gb_svc *svc, struct gb_interface *intf)
 {
-       struct gb_svc *svc = connection->private;
        u8 intf_id = intf->interface_id;
-       u8 device_id;
+       u8 device_id = intf->device_id;
+
+       intf->disconnected = true;
 
-       device_id = intf->device_id;
        gb_interface_remove(intf);
 
        /*
@@ -364,26 +440,21 @@ static void svc_intf_remove(struct gb_connection *connection,
        ida_simple_remove(&svc->device_id_map, device_id);
 }
 
-/*
- * 'struct svc_hotplug' should be freed by svc_process_hotplug() before it
- * returns, irrespective of success or Failure in bringing up the module.
- */
-static void svc_process_hotplug(struct work_struct *work)
+static void gb_svc_process_intf_hotplug(struct gb_operation *operation)
 {
-       struct svc_hotplug *svc_hotplug = container_of(work, struct svc_hotplug,
-                                                      work);
-       struct gb_svc_intf_hotplug_request *hotplug = &svc_hotplug->data;
-       struct gb_connection *connection = svc_hotplug->connection;
+       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;
        int ret;
 
-       /*
-        * Grab the information we need.
-        */
-       intf_id = hotplug->intf_id;
+       /* The request message size has already been verified. */
+       request = operation->request->payload;
+       intf_id = request->intf_id;
+
+       dev_dbg(&svc->dev, "%s - id = %u\n", __func__, intf_id);
 
        intf = gb_interface_find(hd, intf_id);
        if (intf) {
@@ -401,26 +472,30 @@ static void svc_process_hotplug(struct work_struct *work)
                 * Remove the interface and add it again, and let user know
                 * about this with a print message.
                 */
-               pr_info("%d: Removed interface (%hhu) to add it again\n",
-                       connection->intf_cport_id, intf_id);
-               svc_intf_remove(connection, intf);
+               dev_info(&svc->dev, "removing interface %u to add it again\n",
+                               intf_id);
+               gb_svc_intf_remove(svc, intf);
        }
 
        intf = gb_interface_create(hd, intf_id);
        if (!intf) {
-               pr_err("%d: Failed to create interface with id %hhu\n",
-                      connection->intf_cport_id, intf_id);
-               goto free_svc_hotplug;
+               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)
+       if (ret) {
+               dev_err(&svc->dev, "failed to clear boot status of interface %u: %d\n",
+                               intf_id, ret);
                goto destroy_interface;
-
-       intf->unipro_mfg_id = le32_to_cpu(hotplug->data.unipro_mfg_id);
-       intf->unipro_prod_id = le32_to_cpu(hotplug->data.unipro_prod_id);
-       intf->vendor_id = le32_to_cpu(hotplug->data.ara_vend_id);
-       intf->product_id = le32_to_cpu(hotplug->data.ara_prod_id);
+       }
 
        /*
         * Create a device id for the interface:
@@ -434,15 +509,15 @@ static void svc_process_hotplug(struct work_struct *work)
                                   GB_DEVICE_ID_MODULES_START, 0, GFP_KERNEL);
        if (device_id < 0) {
                ret = device_id;
-               pr_err("%d: Failed to allocate device id for interface with id %hhu (%d)\n",
-                      connection->intf_cport_id, intf_id, ret);
+               dev_err(&svc->dev, "failed to allocate device id for interface %u: %d\n",
+                               intf_id, ret);
                goto destroy_interface;
        }
 
        ret = gb_svc_intf_device_id(svc, intf_id, device_id);
        if (ret) {
-               pr_err("%d: Device id operation failed, interface %hhu device_id %hhu (%d)\n",
-                      connection->intf_cport_id, 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;
        }
 
@@ -452,19 +527,19 @@ static void svc_process_hotplug(struct work_struct *work)
        ret = gb_svc_route_create(svc, svc->ap_intf_id, GB_DEVICE_ID_AP,
                                  intf_id, device_id);
        if (ret) {
-               pr_err("%d: Route create operation failed, interface %hhu device_id %hhu (%d)\n",
-                      connection->intf_cport_id, 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;
        }
 
        ret = gb_interface_init(intf, device_id);
        if (ret) {
-               pr_err("%d: Failed to initialize interface, interface %hhu device_id %hhu (%d)\n",
-                      connection->intf_cport_id, intf_id, device_id, ret);
+               dev_err(&svc->dev, "failed to initialize interface %u (device id %u): %d\n",
+                               intf_id, device_id, ret);
                goto destroy_route;
        }
 
-       goto free_svc_hotplug;
+       return;
 
 destroy_route:
        gb_svc_route_destroy(svc, svc->ap_intf_id, intf_id);
@@ -477,8 +552,76 @@ ida_put:
        ida_simple_remove(&svc->device_id_map, device_id);
 destroy_interface:
        gb_interface_remove(intf);
-free_svc_hotplug:
-       kfree(svc_hotplug);
+}
+
+static void gb_svc_process_intf_hot_unplug(struct gb_operation *operation)
+{
+       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;
+
+       /* The request message size has already been verified. */
+       request = operation->request->payload;
+       intf_id = request->intf_id;
+
+       dev_dbg(&svc->dev, "%s - id = %u\n", __func__, intf_id);
+
+       intf = gb_interface_find(hd, intf_id);
+       if (!intf) {
+               dev_warn(&svc->dev, "could not find hot-unplug interface %u\n",
+                               intf_id);
+               return;
+       }
+
+       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;
 }
 
 /*
@@ -486,74 +629,56 @@ free_svc_hotplug:
  * initialization on the module side. Over that, we may also need to download
  * the firmware first and flash that on the module.
  *
- * In order to make other hotplug events to not wait for all this to finish,
+ * 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_message *request = op->request;
-       struct svc_hotplug *svc_hotplug;
+       struct gb_svc *svc = op->connection->private;
+       struct gb_svc_intf_hotplug_request *request;
 
-       if (request->payload_size < sizeof(svc_hotplug->data)) {
-               pr_err("%d: short hotplug request received (%zu < %zu)\n",
-                      op->connection->intf_cport_id, request->payload_size,
-                      sizeof(svc_hotplug->data));
+       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;
        }
 
-       svc_hotplug = kmalloc(sizeof(*svc_hotplug), GFP_KERNEL);
-       if (!svc_hotplug)
-               return -ENOMEM;
-
-       svc_hotplug->connection = op->connection;
-       memcpy(&svc_hotplug->data, op->request->payload, sizeof(svc_hotplug->data));
+       request = op->request->payload;
 
-       INIT_WORK(&svc_hotplug->work, svc_process_hotplug);
-       queue_work(system_unbound_wq, &svc_hotplug->work);
+       dev_dbg(&svc->dev, "%s - id = %u\n", __func__, request->intf_id);
 
-       return 0;
+       return gb_svc_queue_deferred_request(op);
 }
 
 static int gb_svc_intf_hot_unplug_recv(struct gb_operation *op)
 {
-       struct gb_message *request = op->request;
-       struct gb_svc_intf_hot_unplug_request *hot_unplug = request->payload;
-       struct gb_host_device *hd = op->connection->hd;
-       struct gb_interface *intf;
-       u8 intf_id;
+       struct gb_svc *svc = op->connection->private;
+       struct gb_svc_intf_hot_unplug_request *request;
 
-       if (request->payload_size < sizeof(*hot_unplug)) {
-               pr_err("connection %d: short hot unplug request received (%zu < %zu)\n",
-                      op->connection->intf_cport_id, request->payload_size,
-                      sizeof(*hot_unplug));
+       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;
        }
 
-       intf_id = hot_unplug->intf_id;
-
-       intf = gb_interface_find(hd, intf_id);
-       if (!intf) {
-               pr_err("connection %d: Couldn't find interface for id %hhu\n",
-                      op->connection->intf_cport_id, intf_id);
-               return -EINVAL;
-       }
+       request = op->request->payload;
 
-       svc_intf_remove(op->connection, intf);
+       dev_dbg(&svc->dev, "%s - id = %u\n", __func__, request->intf_id);
 
-       return 0;
+       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)) {
-               pr_err("connection %d: short reset request received (%zu < %zu)\n",
-                      op->connection->intf_cport_id, request->payload_size,
-                      sizeof(*reset));
+               dev_warn(&svc->dev, "short reset request received (%zu < %zu)\n",
+                               request->payload_size, sizeof(*reset));
                return -EINVAL;
        }
        reset = request->payload;
@@ -565,10 +690,11 @@ 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_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;
 
        /*
@@ -597,8 +723,8 @@ static int gb_svc_request_recv(u8 type, struct gb_operation *op)
        }
 
        if (ret) {
-               pr_warn("connection %d: unexpected SVC request 0x%02x received (state %u)\n",
-                       connection->intf_cport_id, type, svc->state);
+               dev_warn(&svc->dev, "unexpected request 0x%02x received (state %u)\n",
+                               type, svc->state);
                return ret;
        }
 
@@ -620,8 +746,7 @@ static int gb_svc_request_recv(u8 type, struct gb_operation *op)
        case GB_SVC_TYPE_INTF_RESET:
                return gb_svc_intf_reset_recv(op);
        default:
-               pr_err("connection %d: unsupported request: %hhu\n",
-                      connection->intf_cport_id, type);
+               dev_warn(&svc->dev, "unsupported request 0x%02x\n", type);
                return -EINVAL;
        }
 }
@@ -630,7 +755,10 @@ 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);
 }
 
@@ -639,14 +767,19 @@ struct device_type greybus_svc_type = {
        .release        = gb_svc_release,
 };
 
-static int gb_svc_connection_init(struct gb_connection *connection)
+struct gb_svc *gb_svc_create(struct gb_host_device *hd)
 {
-       struct gb_host_device *hd = connection->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;
@@ -659,38 +792,51 @@ static int gb_svc_connection_init(struct gb_connection *connection)
 
        ida_init(&svc->device_id_map);
        svc->state = GB_SVC_STATE_RESET;
-       svc->connection = connection;
-       connection->private = svc;
+       svc->hd = hd;
+
+       svc->connection = gb_connection_create_static(hd, GB_SVC_CPORT_ID,
+                                                       GREYBUS_PROTOCOL_SVC);
+       if (!svc->connection) {
+               dev_err(&svc->dev, "failed to create connection\n");
+               put_device(&svc->dev);
+               return NULL;
+       }
 
-       hd->svc = svc;
+       svc->connection->private = svc;
 
-       return 0;
+       return svc;
 }
 
-static void gb_svc_connection_exit(struct gb_connection *connection)
+int gb_svc_add(struct gb_svc *svc)
 {
-       struct gb_svc *svc = connection->private;
+       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, gb_svc_request_handler);
+       if (ret)
+               return ret;
+
+       return 0;
+}
 
+void gb_svc_del(struct gb_svc *svc)
+{
+       /*
+        * The SVC device may have been registered from the request handler.
+        */
        if (device_is_registered(&svc->dev))
                device_del(&svc->dev);
 
-       connection->hd->svc = NULL;
-       connection->private = NULL;
+       gb_connection_disable(svc->connection);
 
-       put_device(&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,
-       .flags                  = GB_PROTOCOL_SKIP_CONTROL_CONNECTED |
-                                 GB_PROTOCOL_SKIP_CONTROL_DISCONNECTED |
-                                 GB_PROTOCOL_NO_BUNDLE |
-                                 GB_PROTOCOL_SKIP_VERSION,
-};
-gb_builtin_protocol_driver(svc_protocol);
+void gb_svc_put(struct gb_svc *svc)
+{
+       put_device(&svc->dev);
+}