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 207c33d..107e1d2 100644 (file)
@@ -135,6 +135,8 @@ EXPORT_SYMBOL_GPL(ehci_cf_port_reset_rwsem);
 #define HUB_DEBOUNCE_STABLE     100
 
 
+static int usb_reset_and_verify_device(struct usb_device *udev);
+
 static inline char *portspeed(int portstatus)
 {
        if (portstatus & (1 << USB_PORT_FEAT_HIGHSPEED))
@@ -1820,9 +1822,15 @@ static int check_port_resume_type(struct usb_device *udev,
                        status = -ENODEV;
        }
 
-       /* Can't do a normal resume if the port isn't enabled */
-       else if (!(portstatus & USB_PORT_STAT_ENABLE) && !udev->reset_resume)
-               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,
@@ -1971,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,
@@ -1982,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) {
@@ -2030,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
@@ -2140,7 +2156,7 @@ int usb_port_resume(struct usb_device *udev)
                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;
 }
@@ -2321,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.
  */
@@ -2638,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
  */
@@ -2712,7 +2728,7 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
 #endif
 
                } else {
-                       status = usb_reset_composite_device(udev);
+                       status = usb_reset_device(udev);
                }
                usb_unlock_device(udev);
 
@@ -2940,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);
+                       ret = usb_reset_device(hdev);
                        if (ret) {
                                dev_dbg (hub_dev,
                                        "error resetting hub: %d\n", ret);
@@ -3233,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
@@ -3262,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;
@@ -3350,24 +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)
  *
  * 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)
+int usb_reset_device(struct usb_device *udev)
 {
        int ret;
        int i;
@@ -3387,33 +3407,43 @@ int usb_reset_composite_device(struct usb_device *udev)
                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);