Occasionally when a USB 3.0 device is disconnected, the roothub port
goes into the SS.Inactive state, rather than reporting a disconnect.  A
warm reset is the only way to get out of this state.  khubd notices the
link state in hub_port_events(), and since a udev is active on that
port, it calls into usb_reset_and_verify_device().

USB 3.0 Link PM is disabled before the device is reset, in order to
balance out the LPM ref counts.  That code will also issue two control
transfers to disable the U1 and U2 timeouts.  On some USB host
controllers (at least the Intel ones, possibly others), issuing a
control transfer to a disconnected port causes the transfer to timeout,
rather than immediately returning with a transfer error.  Each control
transfer takes five seconds to timeout and be canceled.

[   89.551350] hub 2-0:1.0: state 7 ports 4 chg 0000 evt 0004
[   89.551642] hub 2-0:1.0: warm reset port 2
[   94.549887] usb 2-2: Disable of device-initiated U1 failed.
[   99.548027] usb 2-2: Disable of device-initiated U2 failed.

The end result is that USB device disconnect is delayed by ten seconds,
and khubd won't be able to service other ports until the disconnect is
handled.  Work around this by checking the status of the device's port,
and not sending the U1/U2 disable control transfers if the device is
disconnected.

Signed-off-by: Sarah Sharp <[email protected]>
Tested-by: Girish Nandibasappa <[email protected]>
Tested-by: Hemanth Venkatesh Murthy <[email protected]>
---
 drivers/usb/core/hub.c | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 92e052db27ac..5eea4a5475a4 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -2521,6 +2521,22 @@ static bool hub_port_warm_reset_required(struct usb_hub 
*hub, u16 portstatus)
                  USB_SS_PORT_LS_COMP_MOD)) ;
 }
 
+static bool hub_is_device_disconnected(struct usb_device *udev)
+{
+       u16 portstatus;
+       u16 portchange;
+       int ret;
+       struct usb_hub *hub;
+
+       hub = usb_hub_to_struct_hub(udev->parent);
+       ret = hub_port_status(hub, udev->portnum, &portstatus, &portchange);
+       if (ret < 0)
+               return true;
+       if ((portstatus & USB_PORT_STAT_CONNECTION))
+               return false;
+       return true;
+}
+
 static int hub_port_wait_reset(struct usb_hub *hub, int port1,
                        struct usb_device *udev, unsigned int delay, bool warm)
 {
@@ -3513,6 +3529,13 @@ static int usb_set_device_initiated_lpm(struct 
usb_device *udev,
                                usb3_lpm_names[state]);
                return 0;
        }
+       if (hub_is_device_disconnected(udev)) {
+               dev_dbg(&udev->dev, "%s: Can't %s %s state "
+                               "for disconnected device.\n",
+                               __func__, enable ? "enable" : "disable",
+                               usb3_lpm_names[state]);
+               return 0;
+       }
 
        if (enable) {
                /*
-- 
1.8.3.3

--
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