[PATCH v3 09/10] usb: make khubd and subsequent suspension wait for port recovery

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



While a device is going through reset-resume khubd may run and trigger a
disconnect since it sees the device not in the suspended state.  This does not
happen in the hub_suspend() case because the hub->urb is dead and we clear the
connect status prior to restarting khubd.  In the port suspend case khubd
remains active.

To close this race window arrange for port resume to trigger a child
device resume and then have khubd wait for that resume to complete
before taking action on the port.  Similar to the hub_resume() case we
request a reset_resume to recover the power session.

This mechanism is also used to rate limit power toggling as the port will not
suspend again until the child device has been given a chance to wake up.  This
mitigates devices downgrading their connection on perceived instability of
the host connection.

Signed-off-by: Dan Williams <dan.j.williams@xxxxxxxxx>
---
 drivers/usb/core/hub.c  |    1 +
 drivers/usb/core/hub.h  |    2 ++
 drivers/usb/core/port.c |   27 +++++++++++++++++++++++++++
 3 files changed, 30 insertions(+), 0 deletions(-)

diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 80661e20de9e..5db2956c9a22 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -4700,6 +4700,7 @@ static struct usb_port *next_active_port(struct usb_hub *hub, int *port1)
 
 		pm_runtime_get_noresume(&port_dev->dev);
 		pm_runtime_barrier(&port_dev->dev);
+		flush_work(&port_dev->resume_work);
 		if (pm_runtime_active(&port_dev->dev))
 			return port_dev;
 
diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h
index 2ba10798c943..333d21495459 100644
--- a/drivers/usb/core/hub.h
+++ b/drivers/usb/core/hub.h
@@ -88,6 +88,7 @@ struct usb_port_location {
  * @peer: related usb2 and usb3 ports (share the same connector)
  * @connect_type: port's connect type
  * @location: opaque representation of platform connector location
+ * @resume_work: arrange for child device resume when port resumes
  * @portnum: port index num based one
  * @power_is_on: port's power state
  * @did_runtime_put: port has done pm_runtime_put().
@@ -99,6 +100,7 @@ struct usb_port {
 	struct usb_port *peer;
 	enum usb_port_connect_type connect_type;
 	struct usb_port_location location;
+	struct work_struct resume_work;
 	u8 portnum;
 	unsigned power_is_on:1;
 	unsigned did_runtime_put:1;
diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c
index 97e4939fee1a..773663312a95 100644
--- a/drivers/usb/core/port.c
+++ b/drivers/usb/core/port.c
@@ -67,9 +67,29 @@ static void usb_port_device_release(struct device *dev)
 {
 	struct usb_port *port_dev = to_usb_port(dev);
 
+	flush_work(&port_dev->resume_work);
 	kfree(port_dev);
 }
 
+static void port_dev_wake_child(struct work_struct *w)
+{
+	struct usb_port *port_dev;
+	struct usb_device *udev;
+
+	port_dev = container_of(w, typeof(*port_dev), resume_work);
+	udev = port_dev->child;
+
+	if (udev) {
+#ifdef CONFIG_PM
+		if (udev->persist_enabled)
+			udev->reset_resume = 1;
+#endif
+		usb_autoresume_device(udev);
+		usb_autosuspend_device(udev);
+	}
+	pm_runtime_put_sync(&port_dev->dev);
+}
+
 #ifdef CONFIG_PM_RUNTIME
 static int usb_port_runtime_resume(struct device *dev)
 {
@@ -111,6 +131,12 @@ static int usb_port_runtime_resume(struct device *dev)
 	if (!hub_is_superspeed(hdev) && peer)
 		pm_runtime_put(&peer->dev);
 
+	/* keep this port awake until we have had a chance to recover
+	 * the child
+	 */
+	pm_runtime_get_noresume(&port_dev->dev);
+	schedule_work(&port_dev->resume_work);
+
 	return retval;
 }
 
@@ -330,6 +356,7 @@ int usb_hub_create_port_device(struct usb_hub *hub, int port1)
 	port_dev->dev.parent = hub->intfdev;
 	port_dev->dev.groups = port_dev_group;
 	port_dev->dev.type = &usb_port_device_type;
+	INIT_WORK(&port_dev->resume_work, port_dev_wake_child);
 	dev_set_name(&port_dev->dev, "port%d", port1);
 	device_initialize(&port_dev->dev);
 

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux