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