|
@@ -2538,73 +2538,35 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
|
|
|
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.
|
|
|
+ if (hub_port_warm_reset_required(hub, portstatus))
|
|
|
+ return -ENOTCONN;
|
|
|
+
|
|
|
+ /* Device went away? */
|
|
|
+ if (!(portstatus & USB_PORT_STAT_CONNECTION))
|
|
|
+ return -ENOTCONN;
|
|
|
+
|
|
|
+ /* bomb out completely if the connection bounced. A USB 3.0
|
|
|
+ * connection may bounce if multiple warm resets were issued,
|
|
|
+ * but the device may have successfully re-connected. Ignore it.
|
|
|
*/
|
|
|
- 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_warm_reset_required(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;
|
|
|
-
|
|
|
- /* bomb out completely if the connection bounced */
|
|
|
- if ((portchange & USB_PORT_STAT_C_CONNECTION))
|
|
|
- return -ENOTCONN;
|
|
|
-
|
|
|
- if ((portstatus & USB_PORT_STAT_ENABLE)) {
|
|
|
- if (!udev)
|
|
|
- return 0;
|
|
|
-
|
|
|
- if (hub_is_wusb(hub))
|
|
|
- udev->speed = USB_SPEED_WIRELESS;
|
|
|
- else if (hub_is_superspeed(hub->hdev))
|
|
|
- udev->speed = USB_SPEED_SUPER;
|
|
|
- else if (portstatus & USB_PORT_STAT_HIGH_SPEED)
|
|
|
- udev->speed = USB_SPEED_HIGH;
|
|
|
- else if (portstatus & USB_PORT_STAT_LOW_SPEED)
|
|
|
- udev->speed = USB_SPEED_LOW;
|
|
|
- else
|
|
|
- udev->speed = USB_SPEED_FULL;
|
|
|
+ if (!hub_is_superspeed(hub->hdev) &&
|
|
|
+ (portchange & USB_PORT_STAT_C_CONNECTION))
|
|
|
+ return -ENOTCONN;
|
|
|
+
|
|
|
+ if ((portstatus & USB_PORT_STAT_ENABLE)) {
|
|
|
+ if (!udev)
|
|
|
return 0;
|
|
|
- }
|
|
|
- } else {
|
|
|
- if (!(portstatus & USB_PORT_STAT_CONNECTION) ||
|
|
|
- hub_port_warm_reset_required(hub,
|
|
|
- portstatus))
|
|
|
- return -ENOTCONN;
|
|
|
|
|
|
+ if (hub_is_wusb(hub))
|
|
|
+ udev->speed = USB_SPEED_WIRELESS;
|
|
|
+ else if (hub_is_superspeed(hub->hdev))
|
|
|
+ udev->speed = USB_SPEED_SUPER;
|
|
|
+ else if (portstatus & USB_PORT_STAT_HIGH_SPEED)
|
|
|
+ udev->speed = USB_SPEED_HIGH;
|
|
|
+ else if (portstatus & USB_PORT_STAT_LOW_SPEED)
|
|
|
+ udev->speed = USB_SPEED_LOW;
|
|
|
+ else
|
|
|
+ udev->speed = USB_SPEED_FULL;
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
@@ -2622,23 +2584,21 @@ delay:
|
|
|
}
|
|
|
|
|
|
static void hub_port_finish_reset(struct usb_hub *hub, int port1,
|
|
|
- struct usb_device *udev, int *status, bool warm)
|
|
|
+ struct usb_device *udev, int *status)
|
|
|
{
|
|
|
switch (*status) {
|
|
|
case 0:
|
|
|
- if (!warm) {
|
|
|
- struct usb_hcd *hcd;
|
|
|
- /* TRSTRCY = 10 ms; plus some extra */
|
|
|
- msleep(10 + 40);
|
|
|
- if (udev) {
|
|
|
- update_devnum(udev, 0);
|
|
|
- hcd = bus_to_hcd(udev->bus);
|
|
|
- /* The xHC may think the device is already
|
|
|
- * reset, so ignore the status.
|
|
|
- */
|
|
|
- if (hcd->driver->reset_device)
|
|
|
- hcd->driver->reset_device(hcd, udev);
|
|
|
- }
|
|
|
+ /* TRSTRCY = 10 ms; plus some extra */
|
|
|
+ msleep(10 + 40);
|
|
|
+ if (udev) {
|
|
|
+ struct usb_hcd *hcd = bus_to_hcd(udev->bus);
|
|
|
+
|
|
|
+ update_devnum(udev, 0);
|
|
|
+ /* The xHC may think the device is already reset,
|
|
|
+ * so ignore the status.
|
|
|
+ */
|
|
|
+ if (hcd->driver->reset_device)
|
|
|
+ hcd->driver->reset_device(hcd, udev);
|
|
|
}
|
|
|
/* FALL THROUGH */
|
|
|
case -ENOTCONN:
|
|
@@ -2651,8 +2611,10 @@ static void hub_port_finish_reset(struct usb_hub *hub, int port1,
|
|
|
USB_PORT_FEAT_C_BH_PORT_RESET);
|
|
|
clear_port_feature(hub->hdev, port1,
|
|
|
USB_PORT_FEAT_C_PORT_LINK_STATE);
|
|
|
+ clear_port_feature(hub->hdev, port1,
|
|
|
+ USB_PORT_FEAT_C_CONNECTION);
|
|
|
}
|
|
|
- if (!warm && udev)
|
|
|
+ if (udev)
|
|
|
usb_set_device_state(udev, *status
|
|
|
? USB_STATE_NOTATTACHED
|
|
|
: USB_STATE_DEFAULT);
|
|
@@ -2665,6 +2627,7 @@ static int hub_port_reset(struct usb_hub *hub, int port1,
|
|
|
struct usb_device *udev, unsigned int delay, bool warm)
|
|
|
{
|
|
|
int i, status;
|
|
|
+ u16 portchange, portstatus;
|
|
|
|
|
|
if (!hub_is_superspeed(hub->hdev)) {
|
|
|
if (warm) {
|
|
@@ -2696,10 +2659,33 @@ static int hub_port_reset(struct usb_hub *hub, int port1,
|
|
|
status);
|
|
|
}
|
|
|
|
|
|
- /* return on disconnect or reset */
|
|
|
+ /* Check for disconnect or reset */
|
|
|
if (status == 0 || status == -ENOTCONN || status == -ENODEV) {
|
|
|
- hub_port_finish_reset(hub, port1, udev, &status, warm);
|
|
|
- goto done;
|
|
|
+ hub_port_finish_reset(hub, port1, udev, &status);
|
|
|
+
|
|
|
+ if (!hub_is_superspeed(hub->hdev))
|
|
|
+ goto done;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * If a USB 3.0 device migrates from reset to an error
|
|
|
+ * state, re-issue the warm reset.
|
|
|
+ */
|
|
|
+ if (hub_port_status(hub, port1,
|
|
|
+ &portstatus, &portchange) < 0)
|
|
|
+ goto done;
|
|
|
+
|
|
|
+ if (!hub_port_warm_reset_required(hub, portstatus))
|
|
|
+ goto done;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * If the port is in SS.Inactive or Compliance Mode, the
|
|
|
+ * hot or warm reset failed. Try another warm reset.
|
|
|
+ */
|
|
|
+ if (!warm) {
|
|
|
+ dev_dbg(hub->intfdev, "hot reset failed, warm reset port %d\n",
|
|
|
+ port1);
|
|
|
+ warm = true;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
dev_dbg (hub->intfdev,
|