Prevent a recovery result of: "hub 3-0:1.0: Cannot enable port 1. Maybe the USB cable is bad?" Once a populated port has been resumed, do not allow it to be suspended again until it has gone through a recovery cycle (reset_resume). It has been observed that bouncing power without an intervening recovery results in the endpoint device failing to reconnect. Force an autosuspend timeout and a recovery of the port before allowing power to be removed again. Signed-off-by: Dan Williams <dan.j.williams@xxxxxxxxx> --- drivers/usb/core/hub.h | 1 + drivers/usb/core/port.c | 23 ++++++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletions(-) diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h index 733d4ef53683..64958ff59ad4 100644 --- a/drivers/usb/core/hub.h +++ b/drivers/usb/core/hub.h @@ -87,6 +87,7 @@ struct usb_port { struct usb_device *child; struct device dev; struct dev_state *port_owner; + struct work_struct ratelimit_work; enum usb_port_connect_type connect_type; u8 portnum; }; diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c index 977e5b137b79..4f63c49df162 100644 --- a/drivers/usb/core/port.c +++ b/drivers/usb/core/port.c @@ -97,9 +97,24 @@ static void usb_port_device_release(struct device *dev) { struct usb_port *port_dev = to_usb_port(dev); + cancel_work_sync(&port_dev->ratelimit_work); kfree(port_dev); } +static void pm_ping_child(struct work_struct *w) +{ + struct usb_port *port_dev; + struct usb_device *udev; + + port_dev = container_of(w, typeof(*port_dev), ratelimit_work); + udev = usb_port_get_child(port_dev); + if (udev) { + pm_runtime_get_sync(&udev->dev); + pm_runtime_put_autosuspend(&udev->dev); + } + usb_port_put_child(udev); +} + #ifdef CONFIG_PM_RUNTIME static int usb_port_runtime_resume(struct device *dev) { @@ -117,9 +132,13 @@ static int usb_port_runtime_resume(struct device *dev) if (test_bit(port1, hub->poweroff_bits)) retval = usb_set_port_feature(hdev, port1, USB_PORT_FEAT_POWER); - /* no child? we're done recovering this port */ + /* no child? we're done recovering this port, otherwise try to + * recover the device connection to rate limit power toggling + */ if (!port_dev->child) usb_clear_port_poweroff(hub, port1); + else + schedule_work(&port_dev->ratelimit_work); usb_autopm_put_interface(intf); return retval; @@ -156,6 +175,7 @@ static int usb_port_runtime_suspend(struct device *dev) int port1 = port_dev->portnum; int retval; + flush_work(&port_dev->ratelimit_work); if (!hub) return -EINVAL; @@ -251,6 +271,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->ratelimit_work, pm_ping_child); dev_set_name(&port_dev->dev, "port%d", port1); retval = device_register(&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