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 <sarah.a.sharp@xxxxxxxxxxxxxxx> Tested-by: Girish Nandibasappa <girishx.nandibasappa@xxxxxxxxx> Tested-by: Hemanth Venkatesh Murthy <hemanthx.venkatesh.murthy@xxxxxxxxx> --- 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 majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html