[PATCH v4 12/14] usb: guarantee child device resume on port poweron

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

 



Make port power recovery behave similarly to the power session recovery
that occurs after a system / hub suspend event.  Arrange for a
usb_device to always complete a usb_port_resume() run prior to the next
khubd run.  This serves several purposes:

1/ The device may need a reset on power-session loss, without this
   change port power-on recovery exposes khubd to scenarios that
   usb_port_resume() is set to handle.  Also, testing showed that USB3
   devices that are not reset on power-session loss may eventually
   downgrade their connection to the USB2 pins.

2/ This mechanism rate limits port power toggling.  The minimum port
   power on/off period is now gated by the child device suspend/resume
   latency.  This mitigates devices downgrading their connection on
   perceived instability of the host connection.  This ratelimiting is
   really only relevant to port power control testing, but it is a nice
   side effect of closing the above race.

3/ Going forward if we find that power-session recovery requires
   warm-resets (http://marc.info/?t=138659232900003&r=1&w=2) that is
   something usb_port_resume() can drive and handle before khubd's next
   evaluation of the portstatus.

4/ If the device *was* disconnected the only time we'll know for sure is
   after a failed resume, so it's necessary for
   usb_port_runtime_resume() to expedite a usb_port_resume() to clean up
   the removed device.

1, 2, and 4 are not a problem in the system resume case because,
although the power-session is lost, khubd is frozen until after device
resume.  For the runtime pm case we can use runtime-pm-synchronization
to guarantee the same sequence of events.  When a ->resume_child request
is set in usb_port_runtime_resume() the port device is in the
RPM_RESUMING state.  khubd executes a pm_runtime_barrier() on the port
device to flush the port recovery, holds the port active while it
resumes the child, and completes child device resume before acting on
the current portstatus.

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

diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index a310028e210d..9a505978ab92 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -4767,6 +4767,23 @@ static void port_event(struct usb_hub *hub, int port1)
 	pm_runtime_barrier(&port_dev->dev);
 	usb_lock_port(port_dev);
 	do if (pm_runtime_active(&port_dev->dev)) {
+
+		/* service child resume requests on behalf of
+		 * usb_port_runtime_resume()
+		 */
+		if (port_dev->resume_child && udev) {
+			usb_unlock_port(port_dev);
+
+			usb_lock_device(udev);
+			usb_autoresume_device(udev);
+			usb_autosuspend_device(udev);
+			usb_unlock_device(udev);
+
+			pm_runtime_put(&port_dev->dev);
+			usb_lock_port(port_dev);
+		}
+		port_dev->resume_child = 0;
+
 		/* re-read portstatus now that we are in-sync with
 		 * usb_port_{suspend|resume}
 		 */
diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h
index e965474f2575..b4f397bc6957 100644
--- a/drivers/usb/core/hub.h
+++ b/drivers/usb/core/hub.h
@@ -91,6 +91,7 @@ struct usb_port_location {
  * @status_lock: synchronize port_event() vs usb_port_{suspend|resume}
  * @portnum: port index num based one
  * @power_is_on: port's power state
+ * @resume_child: set at resume to sync khubd with child recovery
  * @did_runtime_put: port has done pm_runtime_put().
  */
 struct usb_port {
@@ -103,6 +104,7 @@ struct usb_port {
 	struct mutex status_lock;
 	u8 portnum;
 	unsigned power_is_on:1;
+	unsigned resume_child:1;
 	unsigned did_runtime_put:1;
 };
 
diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c
index be9c4486816a..be1e18355fec 100644
--- a/drivers/usb/core/port.c
+++ b/drivers/usb/core/port.c
@@ -105,6 +105,13 @@ static int usb_port_runtime_resume(struct device *dev)
 		if (retval < 0)
 			dev_dbg(&port_dev->dev, "can't get reconnection after setting port  power on, status %d\n",
 					retval);
+
+		/* keep this port awake until we have had a chance to recover
+		 * the child
+		 */
+		pm_runtime_get_noresume(&port_dev->dev);
+		port_dev->resume_child = 1;
+		usb_kick_khubd(hdev);
 		retval = 0;
 	}
 

--
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