aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb/core/hub.c
diff options
context:
space:
mode:
authorSarah Sharp <sarah.a.sharp@linux.intel.com>2011-09-14 14:24:52 -0700
committerGreg Kroah-Hartman <gregkh@suse.de>2011-09-20 12:33:50 -0700
commit10d674a82e553cb8a1f41027bb3c3e309b3f6804 (patch)
treec79c27a71672d51ed935ca23b8faeb507664a4a9 /drivers/usb/core/hub.c
parent2b69899934c63b7b9432568584fb4c4a2924f40c (diff)
USB: When hot reset for USB3 fails, try warm reset.
When a hot reset (standard USB port reset) fails on a USB 3.0 port, the host controller transitions to the "Error" state. It reports the port link state as "Inactive", sets the link state change flag, and (if the device disconnects) also reports the disconnect and connect change status. It's also supposed to transition the link state to "RxDetect", but the NEC µPD720200 xHCI host does not. Unfortunately, Harald found that the combination of the NEC µPD720200 and a LogiLink USB 3.0 to SATA adapter triggered this issue. The USB core would reset the device, the port would go into this error state, and the device would never be enumerated. This combination works under Windows, but not under Linux. When a hot reset fails on a USB 3.0 port, and the link state is reported as Inactive, fall back to a warm port reset instead. Harald confirms that with a warm port reset (along with all the change bits being correctly cleared), the USB 3.0 device will successfully enumerate. Harald also had to add two other patches ("xhci: Set change bit when warm reset change is set." and "usbcore: refine warm reset logic") to make this setup work. Since the warm reset refinement patch is not destined for the stable kernels (it's too big), this patch should not be backported either. This fixes https://bugzilla.kernel.org/show_bug.cgi?id=41752 Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Tested-by: Harald Brennich <harald.brennich@gmx.de> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/usb/core/hub.c')
-rw-r--r--drivers/usb/core/hub.c43
1 files changed, 43 insertions, 0 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index aa336f736f0..1c155123c32 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -2029,6 +2029,17 @@ static unsigned hub_is_wusb(struct usb_hub *hub)
#define HUB_LONG_RESET_TIME 200
#define HUB_RESET_TIMEOUT 500
+static int hub_port_reset(struct usb_hub *hub, int port1,
+ struct usb_device *udev, unsigned int delay, bool warm);
+
+/* Is a USB 3.0 port in the Inactive state? */
+static bool hub_port_inactive(struct usb_hub *hub, u16 portstatus)
+{
+ return hub_is_superspeed(hub->hdev) &&
+ (portstatus & USB_PORT_STAT_LINK_STATE) ==
+ USB_SS_PORT_LS_SS_INACTIVE;
+}
+
static int hub_port_wait_reset(struct usb_hub *hub, int port1,
struct usb_device *udev, unsigned int delay, bool warm)
{
@@ -2052,6 +2063,38 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
* when the port appears not to be connected.
*/
if (!warm) {
+ /*
+ * Some buggy devices can cause an NEC host controller
+ * to transition to the "Error" state after a hot port
+ * reset. This will show up as the port state in
+ * "Inactive", and the port may also report a
+ * disconnect. Forcing a warm port reset seems to make
+ * the device work.
+ *
+ * See https://bugzilla.kernel.org/show_bug.cgi?id=41752
+ */
+ if (hub_port_inactive(hub, portstatus)) {
+ int ret;
+
+ if ((portchange & USB_PORT_STAT_C_CONNECTION))
+ clear_port_feature(hub->hdev, port1,
+ USB_PORT_FEAT_C_CONNECTION);
+ if (portchange & USB_PORT_STAT_C_LINK_STATE)
+ clear_port_feature(hub->hdev, port1,
+ USB_PORT_FEAT_C_PORT_LINK_STATE);
+ if (portchange & USB_PORT_STAT_C_RESET)
+ clear_port_feature(hub->hdev, port1,
+ USB_PORT_FEAT_C_RESET);
+ dev_dbg(hub->intfdev, "hot reset failed, warm reset port %d\n",
+ port1);
+ ret = hub_port_reset(hub, port1,
+ udev, HUB_BH_RESET_TIME,
+ true);
+ if ((portchange & USB_PORT_STAT_C_CONNECTION))
+ clear_port_feature(hub->hdev, port1,
+ USB_PORT_FEAT_C_CONNECTION);
+ return ret;
+ }
/* Device went away? */
if (!(portstatus & USB_PORT_STAT_CONNECTION))
return -ENOTCONN;