From bbaca7115167088e8177ab8cc7aafdcb8666dea9 Mon Sep 17 00:00:00 2001
From: Viresh Kumar <viresh.kumar@linaro.org>
Date: Wed, 23 Sep 2015 16:48:08 -0700
Subject: [PATCH] greybus: svc: Allow consecutive hotplug events for the same
 module

There are two cases where the AP may receive hotplug event for an
existing interface, without first getting a hot-unplug request for it.

- bootrom loading the firmware image and booting into that, which
  only generates a hotplug event. i.e. no hot-unplug event, as the
  module never went away.
- Or the firmware on the module crashed and sent hotplug request again
  to the SVC, which got propagated to AP.

Handle such cases by first removing the interface, with a clear print
message shown to the user. And then following the normal hotplug
sequence to add the interface.

Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
Reviewed-by: Johan Hovold <johan@hovoldconsulting.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
---
 drivers/staging/greybus/svc.c | 52 +++++++++++++++++++++++++++--------
 1 file changed, 41 insertions(+), 11 deletions(-)

diff --git a/drivers/staging/greybus/svc.c b/drivers/staging/greybus/svc.c
index 056351fe9449..0e029ce40379 100644
--- a/drivers/staging/greybus/svc.c
+++ b/drivers/staging/greybus/svc.c
@@ -329,6 +329,25 @@ static int gb_svc_hello(struct gb_operation *op)
 	return 0;
 }
 
+static void svc_intf_remove(struct gb_connection *connection,
+			    struct gb_interface *intf)
+{
+	struct greybus_host_device *hd = connection->hd;
+	struct gb_svc *svc = connection->private;
+	u8 intf_id = intf->interface_id;
+	u8 device_id;
+
+	device_id = intf->device_id;
+	gb_interface_remove(hd, intf_id);
+
+	/*
+	 * Destroy the two-way route between the AP and the interface.
+	 */
+	gb_svc_route_destroy(svc, hd->endo->ap_intf_id, intf_id);
+
+	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.
@@ -351,6 +370,27 @@ static void svc_process_hotplug(struct work_struct *work)
 	 */
 	intf_id = hotplug->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(dev, "Removed interface (%hhu) to add it again\n",
+			 intf_id);
+		svc_intf_remove(connection, intf);
+	}
+
 	intf = gb_interface_create(hd, intf_id);
 	if (!intf) {
 		dev_err(dev, "%s: Failed to create interface with id %hhu\n",
@@ -463,8 +503,6 @@ static int gb_svc_intf_hot_unplug_recv(struct gb_operation *op)
 	struct gb_svc_intf_hot_unplug_request *hot_unplug = request->payload;
 	struct greybus_host_device *hd = op->connection->hd;
 	struct device *dev = &op->connection->dev;
-	struct gb_svc *svc = op->connection->private;
-	u8 device_id;
 	struct gb_interface *intf;
 	u8 intf_id;
 
@@ -483,15 +521,7 @@ static int gb_svc_intf_hot_unplug_recv(struct gb_operation *op)
 		return -EINVAL;
 	}
 
-	device_id = intf->device_id;
-	gb_interface_remove(hd, intf_id);
-
-	/*
-	 * Destroy the two-way route between the AP and the interface.
-	 */
-	gb_svc_route_destroy(svc, hd->endo->ap_intf_id, intf_id);
-
-	ida_simple_remove(&svc->device_id_map, device_id);
+	svc_intf_remove(op->connection, intf);
 
 	return 0;
 }
-- 
2.30.2