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.willi...@intel.com>
---
 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 majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to