usbcore: refine warm reset logic
authorAndiry Xu <andiry.xu@amd.com>
Tue, 13 Sep 2011 23:41:11 +0000 (16:41 -0700)
committerGreg Kroah-Hartman <gregkh@suse.de>
Tue, 20 Sep 2011 19:33:49 +0000 (12:33 -0700)
Current waiting time for warm(BH) reset in hub_port_warm_reset() is too short
for xHC host to complete the warm reset and report a BH reset change.

This patch increases the waiting time for warm reset and merges the function
into hub_port_reset(), so it can handle both cold reset and warm reset, and
factor out hub_port_finish_reset() to make the code looks cleaner.

This fixes the issue that driver fails to clear BH reset change and port is
"dead".

Signed-off-by: Andiry Xu <andiry.xu@amd.com>
Acked-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/usb/core/hub.c

index 3edc01bc41f7635584e97a4ff8c18070aeb5279f..aa336f736f0c97e9574b4cae75d0603c4173f44b 100644 (file)
@@ -2025,11 +2025,12 @@ static unsigned hub_is_wusb(struct usb_hub *hub)
 
 #define HUB_ROOT_RESET_TIME    50      /* times are in msec */
 #define HUB_SHORT_RESET_TIME   10
+#define HUB_BH_RESET_TIME      50
 #define HUB_LONG_RESET_TIME    200
 #define HUB_RESET_TIMEOUT      500
 
 static int hub_port_wait_reset(struct usb_hub *hub, int port1,
-                               struct usb_device *udev, unsigned int delay)
+                       struct usb_device *udev, unsigned int delay, bool warm)
 {
        int delay_time, ret;
        u16 portstatus;
@@ -2046,28 +2047,39 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
                if (ret < 0)
                        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 we`ve finished resetting, then break out of the loop */
-               if (!(portstatus & USB_PORT_STAT_RESET) &&
-                   (portstatus & USB_PORT_STAT_ENABLE)) {
-                       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;
+               /*
+                * Some buggy devices require a warm reset to be issued even
+                * when the port appears not to be connected.
+                */
+               if (!warm) {
+                       /* 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 we`ve finished resetting, then break out of
+                        * the loop
+                        */
+                       if (!(portstatus & USB_PORT_STAT_RESET) &&
+                           (portstatus & USB_PORT_STAT_ENABLE)) {
+                               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;
+                       }
+               } else {
+                       if (portchange & USB_PORT_STAT_C_BH_RESET)
+                               return 0;
                }
 
                /* switch to the long delay after two short delay failures */
@@ -2075,35 +2087,84 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
                        delay = HUB_LONG_RESET_TIME;
 
                dev_dbg (hub->intfdev,
-                       "port %d not reset yet, waiting %dms\n",
-                       port1, delay);
+                       "port %d not %sreset yet, waiting %dms\n",
+                       port1, warm ? "warm " : "", delay);
        }
 
        return -EBUSY;
 }
 
+static void hub_port_finish_reset(struct usb_hub *hub, int port1,
+                       struct usb_device *udev, int *status, bool warm)
+{
+       switch (*status) {
+       case 0:
+               if (!warm) {
+                       struct usb_hcd *hcd;
+                       /* TRSTRCY = 10 ms; plus some extra */
+                       msleep(10 + 40);
+                       update_devnum(udev, 0);
+                       hcd = bus_to_hcd(udev->bus);
+                       if (hcd->driver->reset_device) {
+                               *status = hcd->driver->reset_device(hcd, udev);
+                               if (*status < 0) {
+                                       dev_err(&udev->dev, "Cannot reset "
+                                                       "HCD device state\n");
+                                       break;
+                               }
+                       }
+               }
+               /* FALL THROUGH */
+       case -ENOTCONN:
+       case -ENODEV:
+               clear_port_feature(hub->hdev,
+                               port1, USB_PORT_FEAT_C_RESET);
+               /* FIXME need disconnect() for NOTATTACHED device */
+               if (warm) {
+                       clear_port_feature(hub->hdev, port1,
+                                       USB_PORT_FEAT_C_BH_PORT_RESET);
+                       clear_port_feature(hub->hdev, port1,
+                                       USB_PORT_FEAT_C_PORT_LINK_STATE);
+               } else {
+                       usb_set_device_state(udev, *status
+                                       ? USB_STATE_NOTATTACHED
+                                       : USB_STATE_DEFAULT);
+               }
+               break;
+       }
+}
+
+/* Handle port reset and port warm(BH) reset (for USB3 protocol ports) */
 static int hub_port_reset(struct usb_hub *hub, int port1,
-                               struct usb_device *udev, unsigned int delay)
+                       struct usb_device *udev, unsigned int delay, bool warm)
 {
        int i, status;
-       struct usb_hcd *hcd;
 
-       hcd = bus_to_hcd(udev->bus);
-       /* Block EHCI CF initialization during the port reset.
-        * Some companion controllers don't like it when they mix.
-        */
-       down_read(&ehci_cf_port_reset_rwsem);
+       if (!warm) {
+               /* Block EHCI CF initialization during the port reset.
+                * Some companion controllers don't like it when they mix.
+                */
+               down_read(&ehci_cf_port_reset_rwsem);
+       } else {
+               if (!hub_is_superspeed(hub->hdev)) {
+                       dev_err(hub->intfdev, "only USB3 hub support "
+                                               "warm reset\n");
+                       return -EINVAL;
+               }
+       }
 
        /* Reset the port */
        for (i = 0; i < PORT_RESET_TRIES; i++) {
-               status = set_port_feature(hub->hdev,
-                               port1, USB_PORT_FEAT_RESET);
-               if (status)
+               status = set_port_feature(hub->hdev, port1, (warm ?
+                                       USB_PORT_FEAT_BH_PORT_RESET :
+                                       USB_PORT_FEAT_RESET));
+               if (status) {
                        dev_err(hub->intfdev,
-                                       "cannot reset port %d (err = %d)\n",
-                                       port1, status);
-               else {
-                       status = hub_port_wait_reset(hub, port1, udev, delay);
+                                       "cannot %sreset port %d (err = %d)\n",
+                                       warm ? "warm " : "", port1, status);
+               } else {
+                       status = hub_port_wait_reset(hub, port1, udev, delay,
+                                                               warm);
                        if (status && status != -ENOTCONN)
                                dev_dbg(hub->intfdev,
                                                "port_wait_reset: err = %d\n",
@@ -2111,34 +2172,14 @@ static int hub_port_reset(struct usb_hub *hub, int port1,
                }
 
                /* return on disconnect or reset */
-               switch (status) {
-               case 0:
-                       /* TRSTRCY = 10 ms; plus some extra */
-                       msleep(10 + 40);
-                       update_devnum(udev, 0);
-                       if (hcd->driver->reset_device) {
-                               status = hcd->driver->reset_device(hcd, udev);
-                               if (status < 0) {
-                                       dev_err(&udev->dev, "Cannot reset "
-                                                       "HCD device state\n");
-                                       break;
-                               }
-                       }
-                       /* FALL THROUGH */
-               case -ENOTCONN:
-               case -ENODEV:
-                       clear_port_feature(hub->hdev,
-                               port1, USB_PORT_FEAT_C_RESET);
-                       /* FIXME need disconnect() for NOTATTACHED device */
-                       usb_set_device_state(udev, status
-                                       ? USB_STATE_NOTATTACHED
-                                       : USB_STATE_DEFAULT);
+               if (status == 0 || status == -ENOTCONN || status == -ENODEV) {
+                       hub_port_finish_reset(hub, port1, udev, &status, warm);
                        goto done;
                }
 
                dev_dbg (hub->intfdev,
-                       "port %d not enabled, trying reset again...\n",
-                       port1);
+                       "port %d not enabled, trying %sreset again...\n",
+                       port1, warm ? "warm " : "");
                delay = HUB_LONG_RESET_TIME;
        }
 
@@ -2146,45 +2187,11 @@ static int hub_port_reset(struct usb_hub *hub, int port1,
                "Cannot enable port %i.  Maybe the USB cable is bad?\n",
                port1);
 
- done:
-       up_read(&ehci_cf_port_reset_rwsem);
-       return status;
-}
-
-/* Warm reset a USB3 protocol port */
-static int hub_port_warm_reset(struct usb_hub *hub, int port)
-{
-       int ret;
-       u16 portstatus, portchange;
-
-       if (!hub_is_superspeed(hub->hdev)) {
-               dev_err(hub->intfdev, "only USB3 hub support warm reset\n");
-               return -EINVAL;
-       }
-
-       /* Warm reset the port */
-       ret = set_port_feature(hub->hdev,
-                               port, USB_PORT_FEAT_BH_PORT_RESET);
-       if (ret) {
-               dev_err(hub->intfdev, "cannot warm reset port %d\n", port);
-               return ret;
-       }
-
-       msleep(20);
-       ret = hub_port_status(hub, port, &portstatus, &portchange);
-
-       if (portchange & USB_PORT_STAT_C_RESET)
-               clear_port_feature(hub->hdev, port, USB_PORT_FEAT_C_RESET);
-
-       if (portchange & USB_PORT_STAT_C_BH_RESET)
-               clear_port_feature(hub->hdev, port,
-                                       USB_PORT_FEAT_C_BH_PORT_RESET);
-
-       if (portchange & USB_PORT_STAT_C_LINK_STATE)
-               clear_port_feature(hub->hdev, port,
-                                       USB_PORT_FEAT_C_PORT_LINK_STATE);
+done:
+       if (!warm)
+               up_read(&ehci_cf_port_reset_rwsem);
 
-       return ret;
+       return status;
 }
 
 /* Check if a port is power on */
@@ -2814,7 +2821,7 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
 
        /* Reset the device; full speed may morph to high speed */
        /* FIXME a USB 2.0 device may morph into SuperSpeed on reset. */
-       retval = hub_port_reset(hub, port1, udev, delay);
+       retval = hub_port_reset(hub, port1, udev, delay, false);
        if (retval < 0)         /* error or disconnect */
                goto fail;
        /* success, speed is known */
@@ -2935,7 +2942,7 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
                                        buf->bMaxPacketSize0;
                        kfree(buf);
 
-                       retval = hub_port_reset(hub, port1, udev, delay);
+                       retval = hub_port_reset(hub, port1, udev, delay, false);
                        if (retval < 0)         /* error or disconnect */
                                goto fail;
                        if (oldspeed != udev->speed) {
@@ -3556,7 +3563,8 @@ static void hub_events(void)
                                (portstatus & USB_PORT_STAT_LINK_STATE)
                                        == USB_SS_PORT_LS_SS_INACTIVE) {
                                dev_dbg(hub_dev, "warm reset port %d\n", i);
-                               hub_port_warm_reset(hub, i);
+                               hub_port_reset(hub, i, NULL,
+                                               HUB_BH_RESET_TIME, true);
                        }
 
                        if (connect_change)