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@xxxxxxxxxxxxxxx> Tested-by: Harald Brennich <harald.brennich@xxxxxx> --- Greg, Here's one more patch for the 3.2 kernel. It relies on Andiry's "usbcore: refine warm reset logic" patch, so it shouldn't be queued for stable. Sarah Sharp drivers/usb/core/hub.c | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 43 insertions(+), 0 deletions(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index b0217dc..58b1d6f 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; -- 1.7.4.1 -- 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