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 <[email protected]>
---
 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 [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to