greybus: svc: Handle hotplug request from a workqueue

This change “greybus: svc: Handle hotplug request from a workqueue” in Linux kernel is authored by Viresh Kumar <viresh.kumar [at] linaro.org> on Thu Aug 6 12:44:55 2015 +0530.

greybus: svc: Handle hotplug request from a workqueue

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 to make other hotplug events to not wait for all this to
finish, handle most of module hotplug stuff outside of the hotplug
callback, with help of a workqueue.

Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
Reviewed-by: Alex Elder <elder@linaro.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>

This Linux change may have been applied to various maintained Linux releases and you can find Linux releases including commit 067906f.

There are 77 lines of Linux source code added/deleted in this change. Code changes to Linux kernel are as follows.

 drivers/staging/greybus/svc.c | 77 ++++++++++++++++++++++++++++++++-----------
 1 file changed, 57 insertions(+), 20 deletions(-)

diff --git a/drivers/staging/greybus/svc.c b/drivers/staging/greybus/svc.c
index 4d59cbb..4bf55e0 100644
--- a/drivers/staging/greybus/svc.c
+++ b/drivers/staging/greybus/svc.c
@@ -8,6 +8,7 @@
  */
 
 #include "greybus.h"
+#include <linux/workqueue.h>
 
 #define CPORT_FLAGS_E2EFC       (1)
 #define CPORT_FLAGS_CSD_N       (2)
@@ -19,6 +20,12 @@ struct gb_svc {
 	u8			version_minor;
 };
 
+struct svc_hotplug {
+	struct work_struct work;
+	struct gb_connection *connection;
+	struct gb_svc_intf_hotplug_request data;
+};
+
 static struct ida greybus_svc_device_id_map;
 
 /*
@@ -253,13 +260,19 @@ static int gb_svc_hello(struct gb_operation *op)
 	return 0;
 }
 
-static int gb_svc_intf_hotplug_recv(struct gb_operation *op)
+/*
+ * '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)
 {
-	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;
+	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 *svc = connection->private;
+	struct greybus_host_device *hd = connection->bundle->intf->hd;
+	struct device *dev = &connection->dev;
 	struct gb_interface *intf;
 	u8 intf_id, device_id;
 	u32 unipro_mfg_id;
@@ -268,19 +281,8 @@ static int gb_svc_intf_hotplug_recv(struct gb_operation *op)
 	u32 ara_prod_id;
 	int ret;
 
-	if (request->payload_size < sizeof(*hotplug)) {
-		dev_err(dev, "%s: short hotplug request received (%zu < %zu)n",
-			__func__, request->payload_size, sizeof(*hotplug));
-		return -EINVAL;
-	}
-
 	/*
 	 * 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);
@@ -293,7 +295,7 @@ static int gb_svc_intf_hotplug_recv(struct gb_operation *op)
 	if (!intf) {
 		dev_err(dev, "%s: Failed to create interface with id %hhun",
 			__func__, intf_id);
-		return -EINVAL;
+		goto free_svc_hotplug;
 	}
 
 	/*
@@ -346,7 +348,7 @@ static int gb_svc_intf_hotplug_recv(struct gb_operation *op)
 		goto svc_id_free;
 	}
 
-	return 0;
+	goto free_svc_hotplug;
 
 svc_id_free:
 	/*
@@ -357,8 +359,43 @@ static int gb_svc_intf_hotplug_recv(struct gb_operation *op)
 	ida_simple_remove(&greybus_svc_device_id_map, device_id);
 destroy_interface:
 	gb_interface_remove(hd, intf_id);
+free_svc_hotplug:
+	kfree(svc_hotplug);
+}
 
-	return ret;
+/*
+ * 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 to make other hotplug events to not 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;
+
+	if (request->payload_size < sizeof(svc_hotplug->data)) {
+		dev_err(&op->connection->dev,
+			"%s: short hotplug request received (%zu < %zu)n",
+			__func__, request->payload_size,
+			sizeof(svc_hotplug->data));
+		return -EINVAL;
+	}
+
+	svc_hotplug = kmalloc(sizeof(*svc_hotplug), GFP_ATOMIC);
+	if (!svc_hotplug)
+		return -ENOMEM;
+
+	svc_hotplug->connection = op->connection;
+	memcpy(&svc_hotplug->data, op->request->payload, sizeof(svc_hotplug->data));
+
+	INIT_WORK(&svc_hotplug->work, svc_process_hotplug);
+	queue_work(system_unbound_wq, &svc_hotplug->work);
+
+	return 0;
 }
 
 static int gb_svc_intf_hot_unplug_recv(struct gb_operation *op)

The commit for this change in Linux stable tree is 067906f (patch).

Leave a Reply

Your email address will not be published. Required fields are marked *