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