greybus: core: avoid I/O to disconnected interfaces

Add new helper to disable connections to interfaces that have already
been disconnected (e.g. forcibly removed).

The connection tear-down procedure differs enough depending on whether
the interface is still present or already gone to warrant a dedicated
helper. This will become more obvious with the new tear-down procedure,
which involves I/O on the connection being tore down.

This also simplifies handling of the legacy bootrom, which does not
support the new tear-down operations.

Specifically, this allows us to remove the early control-connection
tear down during interface disable, and also avoids some error messages
currently printed during legacy mode switch (i.e. bootrom
boot-over-UniPro) and forcible removal.

Reviewed-by: Viresh Kumar <viresh.kumar@linaro.org>
Signed-off-by: Johan Hovold <johan@hovoldconsulting.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
diff --git a/drivers/staging/greybus/connection.c b/drivers/staging/greybus/connection.c
index ac3be2f..7e07ef8 100644
--- a/drivers/staging/greybus/connection.c
+++ b/drivers/staging/greybus/connection.c
@@ -654,6 +654,27 @@
 }
 EXPORT_SYMBOL_GPL(gb_connection_disable);
 
+/* Disable a connection without communicating with the remote end. */
+void gb_connection_disable_forced(struct gb_connection *connection)
+{
+	mutex_lock(&connection->mutex);
+
+	if (connection->state == GB_CONNECTION_STATE_DISABLED)
+		goto out_unlock;
+
+	spin_lock_irq(&connection->lock);
+	connection->state = GB_CONNECTION_STATE_DISABLED;
+	gb_connection_cancel_operations(connection, -ESHUTDOWN);
+	spin_unlock_irq(&connection->lock);
+
+	gb_connection_svc_connection_destroy(connection);
+	gb_connection_hd_cport_disable(connection);
+
+out_unlock:
+	mutex_unlock(&connection->mutex);
+}
+EXPORT_SYMBOL_GPL(gb_connection_disable_forced);
+
 /* Caller must have disabled the connection before destroying it. */
 void gb_connection_destroy(struct gb_connection *connection)
 {
diff --git a/drivers/staging/greybus/connection.h b/drivers/staging/greybus/connection.h
index 53ce2845..f159239 100644
--- a/drivers/staging/greybus/connection.h
+++ b/drivers/staging/greybus/connection.h
@@ -80,6 +80,7 @@
 int gb_connection_enable_tx(struct gb_connection *connection);
 void gb_connection_disable_rx(struct gb_connection *connection);
 void gb_connection_disable(struct gb_connection *connection);
+void gb_connection_disable_forced(struct gb_connection *connection);
 
 void greybus_data_rcvd(struct gb_host_device *hd, u16 cport_id,
 			u8 *data, size_t length);
diff --git a/drivers/staging/greybus/control.c b/drivers/staging/greybus/control.c
index b4a1c14..a5effcf 100644
--- a/drivers/staging/greybus/control.c
+++ b/drivers/staging/greybus/control.c
@@ -316,7 +316,10 @@
 {
 	dev_dbg(&control->connection->intf->dev, "%s\n", __func__);
 
-	gb_connection_disable(control->connection);
+	if (control->intf->disconnected)
+		gb_connection_disable_forced(control->connection);
+	else
+		gb_connection_disable(control->connection);
 }
 
 int gb_control_add(struct gb_control *control)
diff --git a/drivers/staging/greybus/core.c b/drivers/staging/greybus/core.c
index b1a7b11..7350c5e 100644
--- a/drivers/staging/greybus/core.c
+++ b/drivers/staging/greybus/core.c
@@ -189,7 +189,7 @@
 
 	list_for_each_entry(connection, &bundle->connections, bundle_links) {
 		if (bundle->intf->disconnected)
-			gb_connection_disable(connection);
+			gb_connection_disable_forced(connection);
 		else
 			gb_connection_disable_rx(connection);
 	}
diff --git a/drivers/staging/greybus/interface.c b/drivers/staging/greybus/interface.c
index c19a09c..d1e2c45 100644
--- a/drivers/staging/greybus/interface.c
+++ b/drivers/staging/greybus/interface.c
@@ -671,13 +671,6 @@
 
 	trace_gb_interface_disable(intf);
 
-	/*
-	 * Disable the control-connection early to avoid operation timeouts
-	 * when the interface is already gone.
-	 */
-	if (intf->disconnected)
-		gb_control_disable(intf->control);
-
 	list_for_each_entry_safe(bundle, next, &intf->bundles, links)
 		gb_bundle_destroy(bundle);