aboutsummaryrefslogtreecommitdiff
path: root/drivers/usb
diff options
context:
space:
mode:
authorSarah Sharp <sarah.a.sharp@linux.intel.com>2012-11-15 14:58:04 -0800
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2013-01-17 08:51:05 -0800
commit870826a75318bfee80967140ce3eb6b55e1e9759 (patch)
treecad4a1146886edc44a8c3df604bd520c9c48ca73 /drivers/usb
parent7bcaf3b265ae4d197d265916268bceed044d289c (diff)
USB: Ignore port state until reset completes.
commit 4f43447e62b37ee19c82a13f72f35b1ca60a74d3 upstream. The port reset code bails out early if the current connect status is cleared (device disconnected). If we're issuing a hot reset, it may also look at the link state before the reset is finished. Section 10.14.2.6 of the USB 3.0 spec says that when a port enters the Error state or Resetting state, the port connection bit retains the value from the previous state. Therefore we can't trust it until the reset finishes. Also, the xHCI spec section 4.19.1.2.5 says software shall ignore the link state while the port is resetting, as it can be in an unknown state. The port state during reset is also unknown for USB 2.0 hubs. The hub sends a reset signal by driving the bus into an SE0 state. This overwhelms the "connect" signal from the device, so the port can't tell whether anything is connected or not. Fix the port reset code to ignore the port link state and current connect bit until the reset finishes, and USB_PORT_STAT_RESET is cleared. Remove the check for USB_PORT_STAT_C_BH_RESET in the warm reset case, because it's redundant. When the warm reset finishes, the port reset bit will be cleared at the same time USB_PORT_STAT_C_BH_RESET is set. Remove the now-redundant check for a cleared USB_PORT_STAT_RESET bit in the code to deal with the finished reset. This patch should be backported to all stable kernels. Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Acked-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/usb')
-rw-r--r--drivers/usb/core/hub.c14
1 files changed, 7 insertions, 7 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 12d2b4f5778..c444c901ec5 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -2203,6 +2203,10 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
if (ret < 0)
return ret;
+ /* The port state is unknown until the reset completes. */
+ if ((portstatus & USB_PORT_STAT_RESET))
+ goto delay;
+
/*
* Some buggy devices require a warm reset to be issued even
* when the port appears not to be connected.
@@ -2248,11 +2252,7 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
if ((portchange & USB_PORT_STAT_C_CONNECTION))
return -ENOTCONN;
- /* if we`ve finished resetting, then break out of
- * the loop
- */
- if (!(portstatus & USB_PORT_STAT_RESET) &&
- (portstatus & USB_PORT_STAT_ENABLE)) {
+ if ((portstatus & USB_PORT_STAT_ENABLE)) {
if (hub_is_wusb(hub))
udev->speed = USB_SPEED_WIRELESS;
else if (hub_is_superspeed(hub->hdev))
@@ -2266,10 +2266,10 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
return 0;
}
} else {
- if (portchange & USB_PORT_STAT_C_BH_RESET)
- return 0;
+ return 0;
}
+delay:
/* switch to the long delay after two short delay failures */
if (delay_time >= 2 * HUB_SHORT_RESET_TIME)
delay = HUB_LONG_RESET_TIME;