Merge branch 'release-2.6.27' of git://git.kernel.org/pub/scm/linux/kernel/git/ak...
[pandora-kernel.git] / drivers / usb / core / hub.c
index 94789be..107e1d2 100644 (file)
@@ -72,7 +72,6 @@ struct usb_hub {
 
        unsigned                limited_power:1;
        unsigned                quiescing:1;
-       unsigned                activating:1;
        unsigned                disconnected:1;
 
        unsigned                has_indicators:1;
@@ -131,6 +130,12 @@ MODULE_PARM_DESC(use_both_schemes,
 DECLARE_RWSEM(ehci_cf_port_reset_rwsem);
 EXPORT_SYMBOL_GPL(ehci_cf_port_reset_rwsem);
 
+#define HUB_DEBOUNCE_TIMEOUT   1500
+#define HUB_DEBOUNCE_STEP        25
+#define HUB_DEBOUNCE_STABLE     100
+
+
+static int usb_reset_and_verify_device(struct usb_device *udev);
 
 static inline char *portspeed(int portstatus)
 {
@@ -535,37 +540,6 @@ static void hub_power_on(struct usb_hub *hub)
        msleep(max(pgood_delay, (unsigned) 100));
 }
 
-static void hub_quiesce(struct usb_hub *hub)
-{
-       /* (nonblocking) khubd and related activity won't re-trigger */
-       hub->quiescing = 1;
-       hub->activating = 0;
-
-       /* (blocking) stop khubd and related activity */
-       usb_kill_urb(hub->urb);
-       if (hub->has_indicators)
-               cancel_delayed_work_sync(&hub->leds);
-       if (hub->tt.hub)
-               cancel_work_sync(&hub->tt.kevent);
-}
-
-static void hub_activate(struct usb_hub *hub)
-{
-       int     status;
-
-       hub->quiescing = 0;
-       hub->activating = 1;
-
-       status = usb_submit_urb(hub->urb, GFP_NOIO);
-       if (status < 0)
-               dev_err(hub->intfdev, "activate --> %d\n", status);
-       if (hub->has_indicators && blinkenlights)
-               schedule_delayed_work(&hub->leds, LED_CYCLE_PERIOD);
-
-       /* scan all ports ASAP */
-       kick_khubd(hub);
-}
-
 static int hub_hub_status(struct usb_hub *hub,
                u16 *status, u16 *change)
 {
@@ -624,143 +598,149 @@ static void hub_port_logical_disconnect(struct usb_hub *hub, int port1)
        kick_khubd(hub);
 }
 
-/* caller has locked the hub device */
-static void hub_stop(struct usb_hub *hub)
+enum hub_activation_type {
+       HUB_INIT, HUB_POST_RESET, HUB_RESUME, HUB_RESET_RESUME
+};
+
+static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
 {
        struct usb_device *hdev = hub->hdev;
-       int i;
+       int port1;
+       int status;
+       bool need_debounce_delay = false;
 
-       /* Disconnect all the children */
-       for (i = 0; i < hdev->maxchild; ++i) {
-               if (hdev->children[i])
-                       usb_disconnect(&hdev->children[i]);
-       }
-       hub_quiesce(hub);
-}
+       /* After a resume, port power should still be on.
+        * For any other type of activation, turn it on.
+        */
+       if (type != HUB_RESUME)
+               hub_power_on(hub);
 
-#define HUB_RESET              1
-#define HUB_RESUME             2
-#define HUB_RESET_RESUME       3
+       /* Check each port and set hub->change_bits to let khubd know
+        * which ports need attention.
+        */
+       for (port1 = 1; port1 <= hdev->maxchild; ++port1) {
+               struct usb_device *udev = hdev->children[port1-1];
+               u16 portstatus, portchange;
 
-#ifdef CONFIG_PM
+               portstatus = portchange = 0;
+               status = hub_port_status(hub, port1, &portstatus, &portchange);
+               if (udev || (portstatus & USB_PORT_STAT_CONNECTION))
+                       dev_dbg(hub->intfdev,
+                                       "port %d: status %04x change %04x\n",
+                                       port1, portstatus, portchange);
 
-/* Try to identify which devices need USB-PERSIST handling */
-static int persistent_device(struct usb_device *udev)
-{
-       int i;
-       int retval;
-       struct usb_host_config *actconfig;
+               /* After anything other than HUB_RESUME (i.e., initialization
+                * or any sort of reset), every port should be disabled.
+                * Unconnected ports should likewise be disabled (paranoia),
+                * and so should ports for which we have no usb_device.
+                */
+               if ((portstatus & USB_PORT_STAT_ENABLE) && (
+                               type != HUB_RESUME ||
+                               !(portstatus & USB_PORT_STAT_CONNECTION) ||
+                               !udev ||
+                               udev->state == USB_STATE_NOTATTACHED)) {
+                       clear_port_feature(hdev, port1, USB_PORT_FEAT_ENABLE);
+                       portstatus &= ~USB_PORT_STAT_ENABLE;
+               }
 
-       /* Explicitly not marked persistent? */
-       if (!udev->persist_enabled)
-               return 0;
+               /* Clear status-change flags; we'll debounce later */
+               if (portchange & USB_PORT_STAT_C_CONNECTION) {
+                       need_debounce_delay = true;
+                       clear_port_feature(hub->hdev, port1,
+                                       USB_PORT_FEAT_C_CONNECTION);
+               }
+               if (portchange & USB_PORT_STAT_C_ENABLE) {
+                       need_debounce_delay = true;
+                       clear_port_feature(hub->hdev, port1,
+                                       USB_PORT_FEAT_C_ENABLE);
+               }
 
-       /* No active config? */
-       actconfig = udev->actconfig;
-       if (!actconfig)
-               return 0;
+               if (!udev || udev->state == USB_STATE_NOTATTACHED) {
+                       /* Tell khubd to disconnect the device or
+                        * check for a new connection
+                        */
+                       if (udev || (portstatus & USB_PORT_STAT_CONNECTION))
+                               set_bit(port1, hub->change_bits);
+
+               } else if (portstatus & USB_PORT_STAT_ENABLE) {
+                       /* The power session apparently survived the resume.
+                        * If there was an overcurrent or suspend change
+                        * (i.e., remote wakeup request), have khubd
+                        * take care of it.
+                        */
+                       if (portchange)
+                               set_bit(port1, hub->change_bits);
 
-       /* FIXME! We should check whether it's open here or not! */
+               } else if (udev->persist_enabled) {
+#ifdef CONFIG_PM
+                       udev->reset_resume = 1;
+#endif
+                       set_bit(port1, hub->change_bits);
 
-       /*
-        * Check that all the interface drivers have a
-        * 'reset_resume' entrypoint
+               } else {
+                       /* The power session is gone; tell khubd */
+                       usb_set_device_state(udev, USB_STATE_NOTATTACHED);
+                       set_bit(port1, hub->change_bits);
+               }
+       }
+
+       /* If no port-status-change flags were set, we don't need any
+        * debouncing.  If flags were set we can try to debounce the
+        * ports all at once right now, instead of letting khubd do them
+        * one at a time later on.
+        *
+        * If any port-status changes do occur during this delay, khubd
+        * will see them later and handle them normally.
         */
-       retval = 0;
-       for (i = 0; i < actconfig->desc.bNumInterfaces; i++) {
-               struct usb_interface *intf;
-               struct usb_driver *driver;
+       if (need_debounce_delay)
+               msleep(HUB_DEBOUNCE_STABLE);
 
-               intf = actconfig->interface[i];
-               if (!intf->dev.driver)
-                       continue;
-               driver = to_usb_driver(intf->dev.driver);
-               if (!driver->reset_resume)
-                       return 0;
-               /*
-                * We have at least one driver, and that one
-                * has a reset_resume method.
-                */
-               retval = 1;
-       }
-       return retval;
-}
+       hub->quiescing = 0;
 
-static void hub_restart(struct usb_hub *hub, int type)
-{
-       struct usb_device *hdev = hub->hdev;
-       int port1;
+       status = usb_submit_urb(hub->urb, GFP_NOIO);
+       if (status < 0)
+               dev_err(hub->intfdev, "activate --> %d\n", status);
+       if (hub->has_indicators && blinkenlights)
+               schedule_delayed_work(&hub->leds, LED_CYCLE_PERIOD);
 
-       /* Check each of the children to see if they require
-        * USB-PERSIST handling or disconnection.  Also check
-        * each unoccupied port to make sure it is still disabled.
-        */
-       for (port1 = 1; port1 <= hdev->maxchild; ++port1) {
-               struct usb_device *udev = hdev->children[port1-1];
-               int status = 0;
-               u16 portstatus, portchange;
+       /* Scan all ports that need attention */
+       kick_khubd(hub);
+}
 
-               if (!udev || udev->state == USB_STATE_NOTATTACHED) {
-                       if (type != HUB_RESET) {
-                               status = hub_port_status(hub, port1,
-                                               &portstatus, &portchange);
-                               if (status == 0 && (portstatus &
-                                               USB_PORT_STAT_ENABLE))
-                                       clear_port_feature(hdev, port1,
-                                                       USB_PORT_FEAT_ENABLE);
-                       }
-                       continue;
-               }
+enum hub_quiescing_type {
+       HUB_DISCONNECT, HUB_PRE_RESET, HUB_SUSPEND
+};
 
-               /* Was the power session lost while we were suspended? */
-               switch (type) {
-               case HUB_RESET_RESUME:
-                       portstatus = 0;
-                       portchange = USB_PORT_STAT_C_CONNECTION;
-                       break;
+static void hub_quiesce(struct usb_hub *hub, enum hub_quiescing_type type)
+{
+       struct usb_device *hdev = hub->hdev;
+       int i;
 
-               case HUB_RESET:
-               case HUB_RESUME:
-                       status = hub_port_status(hub, port1,
-                                       &portstatus, &portchange);
-                       break;
-               }
+       /* khubd and related activity won't re-trigger */
+       hub->quiescing = 1;
 
-               /* For "USB_PERSIST"-enabled children we must
-                * mark the child device for reset-resume and
-                * turn off the various status changes to prevent
-                * khubd from disconnecting it later.
-                */
-               if (status == 0 && !(portstatus & USB_PORT_STAT_ENABLE) &&
-                               persistent_device(udev)) {
-                       if (portchange & USB_PORT_STAT_C_ENABLE)
-                               clear_port_feature(hub->hdev, port1,
-                                               USB_PORT_FEAT_C_ENABLE);
-                       if (portchange & USB_PORT_STAT_C_CONNECTION)
-                               clear_port_feature(hub->hdev, port1,
-                                               USB_PORT_FEAT_C_CONNECTION);
-                       udev->reset_resume = 1;
+       if (type != HUB_SUSPEND) {
+               /* Disconnect all the children */
+               for (i = 0; i < hdev->maxchild; ++i) {
+                       if (hdev->children[i])
+                               usb_disconnect(&hdev->children[i]);
                }
-
-               /* Otherwise for a reset_resume we must disconnect the child,
-                * but as we may not lock the child device here
-                * we have to do a "logical" disconnect.
-                */
-               else if (type == HUB_RESET_RESUME)
-                       hub_port_logical_disconnect(hub, port1);
        }
 
-       hub_activate(hub);
+       /* Stop khubd and related activity */
+       usb_kill_urb(hub->urb);
+       if (hub->has_indicators)
+               cancel_delayed_work_sync(&hub->leds);
+       if (hub->tt.hub)
+               cancel_work_sync(&hub->tt.kevent);
 }
 
-#endif /* CONFIG_PM */
-
 /* caller has locked the hub device */
 static int hub_pre_reset(struct usb_interface *intf)
 {
        struct usb_hub *hub = usb_get_intfdata(intf);
 
-       hub_stop(hub);
+       hub_quiesce(hub, HUB_PRE_RESET);
        return 0;
 }
 
@@ -769,8 +749,7 @@ static int hub_post_reset(struct usb_interface *intf)
 {
        struct usb_hub *hub = usb_get_intfdata(intf);
 
-       hub_power_on(hub);
-       hub_activate(hub);
+       hub_activate(hub, HUB_POST_RESET);
        return 0;
 }
 
@@ -1016,8 +995,7 @@ static int hub_configure(struct usb_hub *hub,
        if (hub->has_indicators && blinkenlights)
                hub->indicator [0] = INDICATOR_CYCLE;
 
-       hub_power_on(hub);
-       hub_activate(hub);
+       hub_activate(hub, HUB_INIT);
        return 0;
 
 fail:
@@ -1049,7 +1027,7 @@ static void hub_disconnect(struct usb_interface *intf)
 
        /* Disconnect all children and quiesce the hub */
        hub->error = 0;
-       hub_stop(hub);
+       hub_quiesce(hub, HUB_DISCONNECT);
 
        usb_set_intfdata (intf, NULL);
 
@@ -1075,6 +1053,12 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
        desc = intf->cur_altsetting;
        hdev = interface_to_usbdev(intf);
 
+       if (hdev->level == MAX_TOPO_LEVEL) {
+               dev_err(&intf->dev, "Unsupported bus topology: "
+                               "hub nested too deep\n");
+               return -E2BIG;
+       }
+
 #ifdef CONFIG_USB_OTG_BLACKLIST_HUB
        if (hdev->parent) {
                dev_warn(&intf->dev, "ignoring external hub\n");
@@ -1821,6 +1805,51 @@ static int hub_port_reset(struct usb_hub *hub, int port1,
 
 #ifdef CONFIG_PM
 
+#define MASK_BITS      (USB_PORT_STAT_POWER | USB_PORT_STAT_CONNECTION | \
+                               USB_PORT_STAT_SUSPEND)
+#define WANT_BITS      (USB_PORT_STAT_POWER | USB_PORT_STAT_CONNECTION)
+
+/* Determine whether the device on a port is ready for a normal resume,
+ * is ready for a reset-resume, or should be disconnected.
+ */
+static int check_port_resume_type(struct usb_device *udev,
+               struct usb_hub *hub, int port1,
+               int status, unsigned portchange, unsigned portstatus)
+{
+       /* Is the device still present? */
+       if (status || (portstatus & MASK_BITS) != WANT_BITS) {
+               if (status >= 0)
+                       status = -ENODEV;
+       }
+
+       /* Can't do a normal resume if the port isn't enabled,
+        * so try a reset-resume instead.
+        */
+       else if (!(portstatus & USB_PORT_STAT_ENABLE) && !udev->reset_resume) {
+               if (udev->persist_enabled)
+                       udev->reset_resume = 1;
+               else
+                       status = -ENODEV;
+       }
+
+       if (status) {
+               dev_dbg(hub->intfdev,
+                               "port %d status %04x.%04x after resume, %d\n",
+                               port1, portchange, portstatus, status);
+       } else if (udev->reset_resume) {
+
+               /* Late port handoff can set status-change bits */
+               if (portchange & USB_PORT_STAT_C_CONNECTION)
+                       clear_port_feature(hub->hdev, port1,
+                                       USB_PORT_FEAT_C_CONNECTION);
+               if (portchange & USB_PORT_STAT_C_ENABLE)
+                       clear_port_feature(hub->hdev, port1,
+                                       USB_PORT_FEAT_C_ENABLE);
+       }
+
+       return status;
+}
+
 #ifdef CONFIG_USB_SUSPEND
 
 /*
@@ -1950,7 +1979,8 @@ static int finish_port_resume(struct usb_device *udev)
         * resumed.
         */
        if (udev->reset_resume)
-               status = usb_reset_device(udev);
+ retry_reset_resume:
+               status = usb_reset_and_verify_device(udev);
 
        /* 10.5.4.5 says be sure devices in the tree are still there.
         * For now let's assume the device didn't go crazy on resume,
@@ -1961,6 +1991,13 @@ static int finish_port_resume(struct usb_device *udev)
                status = usb_get_status(udev, USB_RECIP_DEVICE, 0, &devstatus);
                if (status >= 0)
                        status = (status > 0 ? 0 : -ENODEV);
+
+               /* If a normal resume failed, try doing a reset-resume */
+               if (status && !udev->reset_resume && udev->persist_enabled) {
+                       dev_dbg(&udev->dev, "retry with reset-resume\n");
+                       udev->reset_resume = 1;
+                       goto retry_reset_resume;
+               }
        }
 
        if (status) {
@@ -2009,7 +2046,7 @@ static int finish_port_resume(struct usb_device *udev)
  * to it will be lost.  Using the USB_PERSIST facility, the device can be
  * made to appear as if it had not disconnected.
  *
- * This facility can be dangerous.  Although usb_reset_device() makes
+ * This facility can be dangerous.  Although usb_reset_and_verify_device() makes
  * every effort to insure that the same device is present after the
  * reset as before, it cannot provide a 100% guarantee.  Furthermore it's
  * quite possible for a device to remain unaltered but its media to be
@@ -2025,7 +2062,6 @@ int usb_port_resume(struct usb_device *udev)
        int             port1 = udev->portnum;
        int             status;
        u16             portchange, portstatus;
-       unsigned        mask_flags, want_flags;
 
        /* Skip the initial Clear-Suspend step for a remote wakeup */
        status = hub_port_status(hub, port1, &portstatus, &portchange);
@@ -2054,33 +2090,23 @@ int usb_port_resume(struct usb_device *udev)
                 */
                status = hub_port_status(hub, port1, &portstatus, &portchange);
 
- SuspendCleared:
-               if (udev->reset_resume)
-                       want_flags = USB_PORT_STAT_POWER
-                                       | USB_PORT_STAT_CONNECTION;
-               else
-                       want_flags = USB_PORT_STAT_POWER
-                                       | USB_PORT_STAT_CONNECTION
-                                       | USB_PORT_STAT_ENABLE;
-               mask_flags = want_flags | USB_PORT_STAT_SUSPEND;
+               /* TRSMRCY = 10 msec */
+               msleep(10);
+       }
 
-               if (status < 0 || (portstatus & mask_flags) != want_flags) {
-                       dev_dbg(hub->intfdev,
-                               "port %d status %04x.%04x after resume, %d\n",
-                               port1, portchange, portstatus, status);
-                       if (status >= 0)
-                               status = -ENODEV;
-               } else {
-                       if (portchange & USB_PORT_STAT_C_SUSPEND)
-                               clear_port_feature(hub->hdev, port1,
-                                               USB_PORT_FEAT_C_SUSPEND);
-                       /* TRSMRCY = 10 msec */
-                       msleep(10);
-               }
+ SuspendCleared:
+       if (status == 0) {
+               if (portchange & USB_PORT_STAT_C_SUSPEND)
+                       clear_port_feature(hub->hdev, port1,
+                                       USB_PORT_FEAT_C_SUSPEND);
        }
 
        clear_bit(port1, hub->busy_bits);
+       if (!hub->hdev->parent && !hub->busy_bits[0])
+               usb_enable_root_hub_irq(hub->hdev->bus);
 
+       status = check_port_resume_type(udev,
+                       hub, port1, status, portchange, portstatus);
        if (status == 0)
                status = finish_port_resume(udev);
        if (status < 0) {
@@ -2090,17 +2116,16 @@ int usb_port_resume(struct usb_device *udev)
        return status;
 }
 
+/* caller has locked udev */
 static int remote_wakeup(struct usb_device *udev)
 {
        int     status = 0;
 
-       usb_lock_device(udev);
        if (udev->state == USB_STATE_SUSPENDED) {
                dev_dbg(&udev->dev, "usb %sresume\n", "wakeup-");
                usb_mark_last_busy(udev);
                status = usb_external_resume_device(udev);
        }
-       usb_unlock_device(udev);
        return status;
 }
 
@@ -2113,14 +2138,25 @@ int usb_port_suspend(struct usb_device *udev)
        return 0;
 }
 
+/* However we may need to do a reset-resume */
+
 int usb_port_resume(struct usb_device *udev)
 {
-       int status = 0;
+       struct usb_hub  *hub = hdev_to_hub(udev->parent);
+       int             port1 = udev->portnum;
+       int             status;
+       u16             portchange, portstatus;
+
+       status = hub_port_status(hub, port1, &portstatus, &portchange);
+       status = check_port_resume_type(udev,
+                       hub, port1, status, portchange, portstatus);
 
-       /* However we may need to do a reset-resume */
-       if (udev->reset_resume) {
+       if (status) {
+               dev_dbg(&udev->dev, "can't resume, status %d\n", status);
+               hub_port_logical_disconnect(hub, port1);
+       } else if (udev->reset_resume) {
                dev_dbg(&udev->dev, "reset-resume\n");
-               status = usb_reset_device(udev);
+               status = usb_reset_and_verify_device(udev);
        }
        return status;
 }
@@ -2154,7 +2190,7 @@ static int hub_suspend(struct usb_interface *intf, pm_message_t msg)
        dev_dbg(&intf->dev, "%s\n", __func__);
 
        /* stop khubd and related activity */
-       hub_quiesce(hub);
+       hub_quiesce(hub, HUB_SUSPEND);
        return 0;
 }
 
@@ -2163,7 +2199,7 @@ static int hub_resume(struct usb_interface *intf)
        struct usb_hub *hub = usb_get_intfdata(intf);
 
        dev_dbg(&intf->dev, "%s\n", __func__);
-       hub_restart(hub, HUB_RESUME);
+       hub_activate(hub, HUB_RESUME);
        return 0;
 }
 
@@ -2172,8 +2208,7 @@ static int hub_reset_resume(struct usb_interface *intf)
        struct usb_hub *hub = usb_get_intfdata(intf);
 
        dev_dbg(&intf->dev, "%s\n", __func__);
-       hub_power_on(hub);
-       hub_restart(hub, HUB_RESET_RESUME);
+       hub_activate(hub, HUB_RESET_RESUME);
        return 0;
 }
 
@@ -2223,11 +2258,6 @@ static inline int remote_wakeup(struct usb_device *udev)
  * every 25ms for transient disconnects.  When the port status has been
  * unchanged for 100ms it returns the port status.
  */
-
-#define HUB_DEBOUNCE_TIMEOUT   1500
-#define HUB_DEBOUNCE_STEP        25
-#define HUB_DEBOUNCE_STABLE     100
-
 static int hub_port_debounce(struct usb_hub *hub, int port1)
 {
        int ret;
@@ -2307,7 +2337,7 @@ static int hub_set_address(struct usb_device *udev, int devnum)
  * Returns device in USB_STATE_ADDRESS, except on error.
  *
  * If this is called for an already-existing device (as part of
- * usb_reset_device), the caller must own the device lock.  For a
+ * usb_reset_and_verify_device), the caller must own the device lock.  For a
  * newly detected device that is not accessible through any global
  * pointers, it's not necessary to lock the device.
  */
@@ -2624,7 +2654,7 @@ hub_power_remaining (struct usb_hub *hub)
  * This routine is called when:
  *     a port connection-change occurs;
  *     a port enable-change occurs (often caused by EMI);
- *     usb_reset_device() encounters changed descriptors (as from
+ *     usb_reset_and_verify_device() encounters changed descriptors (as from
  *             a firmware download)
  * caller already locked the hub
  */
@@ -2634,9 +2664,11 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
        struct usb_device *hdev = hub->hdev;
        struct device *hub_dev = hub->intfdev;
        struct usb_hcd *hcd = bus_to_hcd(hdev->bus);
-       u16 wHubCharacteristics = le16_to_cpu(hub->descriptor->wHubCharacteristics);
+       unsigned wHubCharacteristics =
+                       le16_to_cpu(hub->descriptor->wHubCharacteristics);
+       struct usb_device *udev;
        int status, i;
+
        dev_dbg (hub_dev,
                "port %d, status %04x, change %04x, %s\n",
                port1, portstatus, portchange, portspeed (portstatus));
@@ -2645,30 +2677,73 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
                set_port_led(hub, port1, HUB_LED_AUTO);
                hub->indicator[port1-1] = INDICATOR_AUTO;
        }
-       /* Disconnect any existing devices under this port */
-       if (hdev->children[port1-1])
-               usb_disconnect(&hdev->children[port1-1]);
-       clear_bit(port1, hub->change_bits);
 
 #ifdef CONFIG_USB_OTG
        /* during HNP, don't repeat the debounce */
        if (hdev->bus->is_b_host)
-               portchange &= ~USB_PORT_STAT_C_CONNECTION;
+               portchange &= ~(USB_PORT_STAT_C_CONNECTION |
+                               USB_PORT_STAT_C_ENABLE);
 #endif
 
-       if (portchange & USB_PORT_STAT_C_CONNECTION) {
+       /* Try to use the debounce delay for protection against
+        * port-enable changes caused, for example, by EMI.
+        */
+       if (portchange & (USB_PORT_STAT_C_CONNECTION |
+                               USB_PORT_STAT_C_ENABLE)) {
                status = hub_port_debounce(hub, port1);
                if (status < 0) {
                        if (printk_ratelimit())
                                dev_err (hub_dev, "connect-debounce failed, "
                                                "port %d disabled\n", port1);
-                       goto done;
+                       portstatus &= ~USB_PORT_STAT_CONNECTION;
+               } else {
+                       portstatus = status;
                }
-               portstatus = status;
        }
 
-       /* Return now if nothing is connected */
+       /* Try to resuscitate an existing device */
+       udev = hdev->children[port1-1];
+       if ((portstatus & USB_PORT_STAT_CONNECTION) && udev &&
+                       udev->state != USB_STATE_NOTATTACHED) {
+
+               usb_lock_device(udev);
+               if (portstatus & USB_PORT_STAT_ENABLE) {
+                       status = 0;             /* Nothing to do */
+               } else if (!udev->persist_enabled) {
+                       status = -ENODEV;       /* Mustn't resuscitate */
+
+#ifdef CONFIG_USB_SUSPEND
+               } else if (udev->state == USB_STATE_SUSPENDED) {
+                       /* For a suspended device, treat this as a
+                        * remote wakeup event.
+                        */
+                       if (udev->do_remote_wakeup)
+                               status = remote_wakeup(udev);
+
+                       /* Otherwise leave it be; devices can't tell the
+                        * difference between suspended and disabled.
+                        */
+                       else
+                               status = 0;
+#endif
+
+               } else {
+                       status = usb_reset_device(udev);
+               }
+               usb_unlock_device(udev);
+
+               if (status == 0) {
+                       clear_bit(port1, hub->change_bits);
+                       return;
+               }
+       }
+
+       /* Disconnect any existing devices under this port */
+       if (udev)
+               usb_disconnect(&hdev->children[port1-1]);
+       clear_bit(port1, hub->change_bits);
+
+       /* Return now if debouncing failed or nothing is connected */
        if (!(portstatus & USB_PORT_STAT_CONNECTION)) {
 
                /* maybe switch power back on (e.g. root hub was reset) */
@@ -2682,7 +2757,6 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
        }
 
        for (i = 0; i < SET_CONFIG_TRIES; i++) {
-               struct usb_device *udev;
 
                /* reallocate for each attempt, since references
                 * to the previous one can escape in various ways
@@ -2863,7 +2937,7 @@ static void hub_events(void)
                /* If the hub has died, clean up after it */
                if (hdev->state == USB_STATE_NOTATTACHED) {
                        hub->error = -ENODEV;
-                       hub_stop(hub);
+                       hub_quiesce(hub, HUB_DISCONNECT);
                        goto loop;
                }
 
@@ -2882,7 +2956,7 @@ static void hub_events(void)
                        dev_dbg (hub_dev, "resetting for error %d\n",
                                hub->error);
 
-                       ret = usb_reset_composite_device(hdev, intf);
+                       ret = usb_reset_device(hdev);
                        if (ret) {
                                dev_dbg (hub_dev,
                                        "error resetting hub: %d\n", ret);
@@ -2899,7 +2973,7 @@ static void hub_events(void)
                                continue;
                        connect_change = test_bit(i, hub->change_bits);
                        if (!test_and_clear_bit(i, hub->event_bits) &&
-                                       !connect_change && !hub->activating)
+                                       !connect_change)
                                continue;
 
                        ret = hub_port_status(hub, i,
@@ -2907,11 +2981,6 @@ static void hub_events(void)
                        if (ret < 0)
                                continue;
 
-                       if (hub->activating && !hdev->children[i-1] &&
-                                       (portstatus &
-                                               USB_PORT_STAT_CONNECTION))
-                               connect_change = 1;
-
                        if (portchange & USB_PORT_STAT_C_CONNECTION) {
                                clear_port_feature(hdev, i,
                                        USB_PORT_FEAT_C_CONNECTION);
@@ -2946,11 +3015,16 @@ static void hub_events(void)
                        }
 
                        if (portchange & USB_PORT_STAT_C_SUSPEND) {
+                               struct usb_device *udev;
+
                                clear_port_feature(hdev, i,
                                        USB_PORT_FEAT_C_SUSPEND);
-                               if (hdev->children[i-1]) {
+                               udev = hdev->children[i-1];
+                               if (udev) {
+                                       usb_lock_device(udev);
                                        ret = remote_wakeup(hdev->
                                                        children[i-1]);
+                                       usb_unlock_device(udev);
                                        if (ret < 0)
                                                connect_change = 1;
                                } else {
@@ -3007,7 +3081,10 @@ static void hub_events(void)
                        }
                }
 
-               hub->activating = 0;
+               /* If this is a root hub, tell the HCD it's okay to
+                * re-enable port-change interrupts now. */
+               if (!hdev->parent && !hub->busy_bits[0])
+                       usb_enable_root_hub_irq(hdev->bus);
 
 loop_autopm:
                /* Allow autosuspend if we're not going to run again */
@@ -3172,12 +3249,12 @@ static int descriptors_changed(struct usb_device *udev,
 }
 
 /**
- * usb_reset_device - perform a USB port reset to reinitialize a device
+ * usb_reset_and_verify_device - perform a USB port reset to reinitialize a device
  * @udev: device to reset (not in SUSPENDED or NOTATTACHED state)
  *
  * WARNING - don't use this routine to reset a composite device
  * (one with multiple interfaces owned by separate drivers)!
- * Use usb_reset_composite_device() instead.
+ * Use usb_reset_device() instead.
  *
  * Do a port reset, reassign the device's address, and establish its
  * former operating configuration.  If the reset fails, or the device's
@@ -3201,7 +3278,7 @@ static int descriptors_changed(struct usb_device *udev,
  * holding the device lock because these tasks should always call
  * usb_autopm_resume_device(), thereby preventing any unwanted autoresume.
  */
-int usb_reset_device(struct usb_device *udev)
+static int usb_reset_and_verify_device(struct usb_device *udev)
 {
        struct usb_device               *parent_hdev = udev->parent;
        struct usb_hub                  *parent_hub;
@@ -3234,6 +3311,8 @@ int usb_reset_device(struct usb_device *udev)
                        break;
        }
        clear_bit(port1, parent_hub->busy_bits);
+       if (!parent_hdev->parent && !parent_hub->busy_bits[0])
+               usb_enable_root_hub_irq(parent_hdev->bus);
 
        if (ret < 0)
                goto re_enumerate;
@@ -3287,26 +3366,28 @@ re_enumerate:
        hub_port_logical_disconnect(parent_hub, port1);
        return -ENODEV;
 }
-EXPORT_SYMBOL_GPL(usb_reset_device);
 
 /**
- * usb_reset_composite_device - warn interface drivers and perform a USB port reset
+ * usb_reset_device - warn interface drivers and perform a USB port reset
  * @udev: device to reset (not in SUSPENDED or NOTATTACHED state)
- * @iface: interface bound to the driver making the request (optional)
  *
  * Warns all drivers bound to registered interfaces (using their pre_reset
  * method), performs the port reset, and then lets the drivers know that
  * the reset is over (using their post_reset method).
  *
- * Return value is the same as for usb_reset_device().
+ * Return value is the same as for usb_reset_and_verify_device().
  *
  * The caller must own the device lock.  For example, it's safe to use
  * this from a driver probe() routine after downloading new firmware.
  * For calls that might not occur during probe(), drivers should lock
  * the device using usb_lock_device_for_reset().
+ *
+ * If an interface is currently being probed or disconnected, we assume
+ * its driver knows how to handle resets.  For all other interfaces,
+ * if the driver doesn't have pre_reset and post_reset methods then
+ * we attempt to unbind it and rebind afterward.
  */
-int usb_reset_composite_device(struct usb_device *udev,
-               struct usb_interface *iface)
+int usb_reset_device(struct usb_device *udev)
 {
        int ret;
        int i;
@@ -3322,40 +3403,47 @@ int usb_reset_composite_device(struct usb_device *udev,
        /* Prevent autosuspend during the reset */
        usb_autoresume_device(udev);
 
-       if (iface && iface->condition != USB_INTERFACE_BINDING)
-               iface = NULL;
-
        if (config) {
                for (i = 0; i < config->desc.bNumInterfaces; ++i) {
                        struct usb_interface *cintf = config->interface[i];
                        struct usb_driver *drv;
+                       int unbind = 0;
 
                        if (cintf->dev.driver) {
                                drv = to_usb_driver(cintf->dev.driver);
-                               if (drv->pre_reset)
-                                       (drv->pre_reset)(cintf);
-       /* FIXME: Unbind if pre_reset returns an error or isn't defined */
+                               if (drv->pre_reset && drv->post_reset)
+                                       unbind = (drv->pre_reset)(cintf);
+                               else if (cintf->condition ==
+                                               USB_INTERFACE_BOUND)
+                                       unbind = 1;
+                               if (unbind)
+                                       usb_forced_unbind_intf(cintf);
                        }
                }
        }
 
-       ret = usb_reset_device(udev);
+       ret = usb_reset_and_verify_device(udev);
 
        if (config) {
                for (i = config->desc.bNumInterfaces - 1; i >= 0; --i) {
                        struct usb_interface *cintf = config->interface[i];
                        struct usb_driver *drv;
+                       int rebind = cintf->needs_binding;
 
-                       if (cintf->dev.driver) {
+                       if (!rebind && cintf->dev.driver) {
                                drv = to_usb_driver(cintf->dev.driver);
                                if (drv->post_reset)
-                                       (drv->post_reset)(cintf);
-       /* FIXME: Unbind if post_reset returns an error or isn't defined */
+                                       rebind = (drv->post_reset)(cintf);
+                               else if (cintf->condition ==
+                                               USB_INTERFACE_BOUND)
+                                       rebind = 1;
                        }
+                       if (rebind)
+                               usb_rebind_intf(cintf);
                }
        }
 
        usb_autosuspend_device(udev);
        return ret;
 }
-EXPORT_SYMBOL_GPL(usb_reset_composite_device);
+EXPORT_SYMBOL_GPL(usb_reset_device);