usb: don't update devnum for wusb devices
[safe/jmp/linux-2.6] / drivers / usb / core / hub.c
index 1b17f63..eb57fcc 100644 (file)
@@ -23,7 +23,6 @@
 #include <linux/mutex.h>
 #include <linux/freezer.h>
 
-#include <asm/semaphore.h>
 #include <asm/uaccess.h>
 #include <asm/byteorder.h>
 
 #include "hcd.h"
 #include "hub.h"
 
-#ifdef CONFIG_USB_PERSIST
-#define        USB_PERSIST     1
-#else
-#define        USB_PERSIST     0
+/* if we are in debug mode, always announce new devices */
+#ifdef DEBUG
+#ifndef CONFIG_USB_ANNOUNCE_NEW_DEVICES
+#define CONFIG_USB_ANNOUNCE_NEW_DEVICES
+#endif
 #endif
 
 struct usb_hub {
@@ -327,6 +327,27 @@ static int get_port_status(struct usb_device *hdev, int port1,
        return status;
 }
 
+static int hub_port_status(struct usb_hub *hub, int port1,
+               u16 *status, u16 *change)
+{
+       int ret;
+
+       mutex_lock(&hub->status_mutex);
+       ret = get_port_status(hub->hdev, port1, &hub->status->port);
+       if (ret < 4) {
+               dev_err(hub->intfdev,
+                       "%s failed (err = %d)\n", __func__, ret);
+               if (ret >= 0)
+                       ret = -EIO;
+       } else {
+               *status = le16_to_cpu(hub->status->port.wPortStatus);
+               *change = le16_to_cpu(hub->status->port.wPortChange);
+               ret = 0;
+       }
+       mutex_unlock(&hub->status_mutex);
+       return ret;
+}
+
 static void kick_khubd(struct usb_hub *hub)
 {
        unsigned long   flags;
@@ -487,6 +508,7 @@ void usb_hub_tt_clear_buffer (struct usb_device *udev, int pipe)
        schedule_work (&tt->kevent);
        spin_unlock_irqrestore (&tt->lock, flags);
 }
+EXPORT_SYMBOL_GPL(usb_hub_tt_clear_buffer);
 
 static void hub_power_on(struct usb_hub *hub)
 {
@@ -553,7 +575,7 @@ static int hub_hub_status(struct usb_hub *hub,
        ret = get_hub_status(hub->hdev, &hub->status->hub);
        if (ret < 0)
                dev_err (hub->intfdev,
-                       "%s failed (err = %d)\n", __FUNCTION__, ret);
+                       "%s failed (err = %d)\n", __func__, ret);
        else {
                *status = le16_to_cpu(hub->status->hub.wHubStatus);
                *change = le16_to_cpu(hub->status->hub.wHubChange); 
@@ -603,9 +625,8 @@ static void hub_port_logical_disconnect(struct usb_hub *hub, int port1)
 }
 
 /* caller has locked the hub device */
-static int hub_pre_reset(struct usb_interface *intf)
+static void hub_stop(struct usb_hub *hub)
 {
-       struct usb_hub *hub = usb_get_intfdata(intf);
        struct usb_device *hdev = hub->hdev;
        int i;
 
@@ -615,6 +636,89 @@ static int hub_pre_reset(struct usb_interface *intf)
                        usb_disconnect(&hdev->children[i]);
        }
        hub_quiesce(hub);
+}
+
+#define HUB_RESET              1
+#define HUB_RESUME             2
+#define HUB_RESET_RESUME       3
+
+#ifdef CONFIG_PM
+
+static void hub_restart(struct usb_hub *hub, int type)
+{
+       struct usb_device *hdev = hub->hdev;
+       int port1;
+
+       /* 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;
+
+               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;
+               }
+
+               /* Was the power session lost while we were suspended? */
+               switch (type) {
+               case HUB_RESET_RESUME:
+                       portstatus = 0;
+                       portchange = USB_PORT_STAT_C_CONNECTION;
+                       break;
+
+               case HUB_RESET:
+               case HUB_RESUME:
+                       status = hub_port_status(hub, port1,
+                                       &portstatus, &portchange);
+                       break;
+               }
+
+               /* 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 (udev->persist_enabled && status == 0 &&
+                               !(portstatus & USB_PORT_STAT_ENABLE)) {
+                       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;
+               }
+
+               /* 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);
+}
+
+#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);
        return 0;
 }
 
@@ -903,7 +1007,7 @@ static void hub_disconnect(struct usb_interface *intf)
 
        /* Disconnect all children and quiesce the hub */
        hub->error = 0;
-       hub_pre_reset(intf);
+       hub_stop(hub);
 
        usb_set_intfdata (intf, NULL);
 
@@ -1027,8 +1131,10 @@ static void recursively_mark_NOTATTACHED(struct usb_device *udev)
                if (udev->children[i])
                        recursively_mark_NOTATTACHED(udev->children[i]);
        }
-       if (udev->state == USB_STATE_SUSPENDED)
+       if (udev->state == USB_STATE_SUSPENDED) {
                udev->discon_suspended = 1;
+               udev->active_duration -= jiffies;
+       }
        udev->state = USB_STATE_NOTATTACHED;
 }
 
@@ -1077,27 +1183,54 @@ void usb_set_device_state(struct usb_device *udev,
                        else
                                device_init_wakeup(&udev->dev, 0);
                }
+               if (udev->state == USB_STATE_SUSPENDED &&
+                       new_state != USB_STATE_SUSPENDED)
+                       udev->active_duration -= jiffies;
+               else if (new_state == USB_STATE_SUSPENDED &&
+                               udev->state != USB_STATE_SUSPENDED)
+                       udev->active_duration += jiffies;
                udev->state = new_state;
        } else
                recursively_mark_NOTATTACHED(udev);
        spin_unlock_irqrestore(&device_state_lock, flags);
 }
 
+/*
+ * WUSB devices are simple: they have no hubs behind, so the mapping
+ * device <-> virtual port number becomes 1:1. Why? to simplify the
+ * life of the device connection logic in
+ * drivers/usb/wusbcore/devconnect.c. When we do the initial secret
+ * handshake we need to assign a temporary address in the unauthorized
+ * space. For simplicity we use the first virtual port number found to
+ * be free [drivers/usb/wusbcore/devconnect.c:wusbhc_devconnect_ack()]
+ * and that becomes it's address [X < 128] or its unauthorized address
+ * [X | 0x80].
+ *
+ * We add 1 as an offset to the one-based USB-stack port number
+ * (zero-based wusb virtual port index) for two reasons: (a) dev addr
+ * 0 is reserved by USB for default address; (b) Linux's USB stack
+ * uses always #1 for the root hub of the controller. So USB stack's
+ * port #1, which is wusb virtual-port #0 has address #2.
+ */
 static void choose_address(struct usb_device *udev)
 {
        int             devnum;
        struct usb_bus  *bus = udev->bus;
 
        /* If khubd ever becomes multithreaded, this will need a lock */
-
-       /* Try to allocate the next devnum beginning at bus->devnum_next. */
-       devnum = find_next_zero_bit(bus->devmap.devicemap, 128,
-                       bus->devnum_next);
-       if (devnum >= 128)
-               devnum = find_next_zero_bit(bus->devmap.devicemap, 128, 1);
-
-       bus->devnum_next = ( devnum >= 127 ? 1 : devnum + 1);
-
+       if (udev->wusb) {
+               devnum = udev->portnum + 1;
+               BUG_ON(test_bit(devnum, bus->devmap.devicemap));
+       } else {
+               /* Try to allocate the next devnum beginning at
+                * bus->devnum_next. */
+               devnum = find_next_zero_bit(bus->devmap.devicemap, 128,
+                                           bus->devnum_next);
+               if (devnum >= 128)
+                       devnum = find_next_zero_bit(bus->devmap.devicemap,
+                                                   128, 1);
+               bus->devnum_next = ( devnum >= 127 ? 1 : devnum + 1);
+       }
        if (devnum < 128) {
                set_bit(devnum, bus->devmap.devicemap);
                udev->devnum = devnum;
@@ -1112,6 +1245,13 @@ static void release_address(struct usb_device *udev)
        }
 }
 
+static void update_address(struct usb_device *udev, int devnum)
+{
+       /* The address for a WUSB device is managed by wusbcore. */
+       if (!udev->wusb)
+               udev->devnum = devnum;
+}
+
 #ifdef CONFIG_USB_SUSPEND
 
 static void usb_stop_pm(struct usb_device *udev)
@@ -1158,7 +1298,7 @@ void usb_disconnect(struct usb_device **pdev)
        int                     i;
 
        if (!udev) {
-               pr_debug ("%s nodev\n", __FUNCTION__);
+               pr_debug ("%s nodev\n", __func__);
                return;
        }
 
@@ -1207,7 +1347,7 @@ void usb_disconnect(struct usb_device **pdev)
        put_device(&udev->dev);
 }
 
-#ifdef DEBUG
+#ifdef CONFIG_USB_ANNOUNCE_NEW_DEVICES
 static void show_string(struct usb_device *udev, char *id, char *string)
 {
        if (!string)
@@ -1215,12 +1355,24 @@ static void show_string(struct usb_device *udev, char *id, char *string)
        dev_printk(KERN_INFO, &udev->dev, "%s: %s\n", id, string);
 }
 
+static void announce_device(struct usb_device *udev)
+{
+       dev_info(&udev->dev, "New USB device found, idVendor=%04x, idProduct=%04x\n",
+               le16_to_cpu(udev->descriptor.idVendor),
+               le16_to_cpu(udev->descriptor.idProduct));
+       dev_info(&udev->dev, "New USB device strings: Mfr=%d, Product=%d, "
+               "SerialNumber=%d\n",
+               udev->descriptor.iManufacturer,
+               udev->descriptor.iProduct,
+               udev->descriptor.iSerialNumber);
+       show_string(udev, "Product", udev->product);
+       show_string(udev, "Manufacturer", udev->manufacturer);
+       show_string(udev, "SerialNumber", udev->serial);
+}
 #else
-static inline void show_string(struct usb_device *udev, char *id, char *string)
-{}
+static inline void announce_device(struct usb_device *udev) { }
 #endif
 
-
 #ifdef CONFIG_USB_OTG
 #include "otg_whitelist.h"
 #endif
@@ -1390,14 +1542,7 @@ int usb_new_device(struct usb_device *udev)
        }
 
        /* Tell the world! */
-       dev_dbg(&udev->dev, "new device strings: Mfr=%d, Product=%d, "
-               "SerialNumber=%d\n",
-               udev->descriptor.iManufacturer,
-               udev->descriptor.iProduct,
-               udev->descriptor.iSerialNumber);
-       show_string(udev, "Product", udev->product);
-       show_string(udev, "Manufacturer", udev->manufacturer);
-       show_string(udev, "SerialNumber", udev->serial);
+       announce_device(udev);
        return err;
 
 fail:
@@ -1490,28 +1635,6 @@ out_authorized:
 }
 
 
-static int hub_port_status(struct usb_hub *hub, int port1,
-                              u16 *status, u16 *change)
-{
-       int ret;
-
-       mutex_lock(&hub->status_mutex);
-       ret = get_port_status(hub->hdev, port1, &hub->status->port);
-       if (ret < 4) {
-               dev_err (hub->intfdev,
-                       "%s failed (err = %d)\n", __FUNCTION__, ret);
-               if (ret >= 0)
-                       ret = -EIO;
-       } else {
-               *status = le16_to_cpu(hub->status->port.wPortStatus);
-               *change = le16_to_cpu(hub->status->port.wPortChange); 
-               ret = 0;
-       }
-       mutex_unlock(&hub->status_mutex);
-       return ret;
-}
-
-
 /* Returns 1 if @hub is a WUSB root hub, 0 otherwise */
 static unsigned hub_is_wusb(struct usb_hub *hub)
 {
@@ -1617,7 +1740,7 @@ static int hub_port_reset(struct usb_hub *hub, int port1,
                case 0:
                        /* TRSTRCY = 10 ms; plus some extra */
                        msleep(10 + 40);
-                       udev->devnum = 0;       /* Device now at address 0 */
+                       update_address(udev, 0);
                        /* FALL THROUGH */
                case -ENOTCONN:
                case -ENODEV:
@@ -1822,9 +1945,8 @@ static int finish_port_resume(struct usb_device *udev)
  * the host and the device is the same as it was when the device
  * suspended.
  *
- * If CONFIG_USB_PERSIST and @udev->reset_resume are both set then this
- * routine won't check that the port is still enabled.  Furthermore,
- * if @udev->reset_resume is set then finish_port_resume() above will
+ * If @udev->reset_resume is set then this routine won't check that the
+ * port is still enabled.  Furthermore, finish_port_resume() above will
  * reset @udev.  The end result is that a broken power session can be
  * recovered and @udev will appear to persist across a loss of VBUS power.
  *
@@ -1836,8 +1958,8 @@ 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 is inherently dangerous.  Although usb_reset_device()
- * makes every effort to insure that the same device is present after the
+ * This facility can be dangerous.  Although usb_reset_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
  * changed.  If the user replaces a flash memory card while the system is
@@ -1882,7 +2004,7 @@ int usb_port_resume(struct usb_device *udev)
                status = hub_port_status(hub, port1, &portstatus, &portchange);
 
  SuspendCleared:
-               if (USB_PERSIST && udev->reset_resume)
+               if (udev->reset_resume)
                        want_flags = USB_PORT_STAT_POWER
                                        | USB_PORT_STAT_CONNECTION;
                else
@@ -1907,8 +2029,6 @@ int usb_port_resume(struct usb_device *udev)
        }
 
        clear_bit(port1, hub->busy_bits);
-       if (!hub->hdev->parent && !hub->busy_bits[0])
-               usb_enable_root_hub_irq(hub->hdev->bus);
 
        if (status == 0)
                status = finish_port_resume(udev);
@@ -1980,7 +2100,7 @@ static int hub_suspend(struct usb_interface *intf, pm_message_t msg)
                }
        }
 
-       dev_dbg(&intf->dev, "%s\n", __FUNCTION__);
+       dev_dbg(&intf->dev, "%s\n", __func__);
 
        /* stop khubd and related activity */
        hub_quiesce(hub);
@@ -1989,49 +2109,20 @@ static int hub_suspend(struct usb_interface *intf, pm_message_t msg)
 
 static int hub_resume(struct usb_interface *intf)
 {
-       struct usb_hub          *hub = usb_get_intfdata (intf);
-
-       dev_dbg(&intf->dev, "%s\n", __FUNCTION__);
+       struct usb_hub *hub = usb_get_intfdata(intf);
 
-       /* tell khubd to look for changes on this hub */
-       hub_activate(hub);
+       dev_dbg(&intf->dev, "%s\n", __func__);
+       hub_restart(hub, HUB_RESUME);
        return 0;
 }
 
 static int hub_reset_resume(struct usb_interface *intf)
 {
        struct usb_hub *hub = usb_get_intfdata(intf);
-       struct usb_device *hdev = hub->hdev;
-       int port1;
 
+       dev_dbg(&intf->dev, "%s\n", __func__);
        hub_power_on(hub);
-
-       for (port1 = 1; port1 <= hdev->maxchild; ++port1) {
-               struct usb_device *child = hdev->children[port1-1];
-
-               if (child) {
-
-                       /* For "USB_PERSIST"-enabled children we must
-                        * mark the child device for reset-resume and
-                        * turn off the connect-change status to prevent
-                        * khubd from disconnecting it later.
-                        */
-                       if (USB_PERSIST && child->persist_enabled) {
-                               child->reset_resume = 1;
-                               clear_port_feature(hdev, port1,
-                                               USB_PORT_FEAT_C_CONNECTION);
-
-                       /* Otherwise we must disconnect the child,
-                        * but as we may not lock the child device here
-                        * we have to do a "logical" disconnect.
-                        */
-                       } else {
-                               hub_port_logical_disconnect(hub, port1);
-                       }
-               }
-       }
-
-       hub_activate(hub);
+       hub_restart(hub, HUB_RESET_RESUME);
        return 0;
 }
 
@@ -2041,10 +2132,10 @@ static int hub_reset_resume(struct usb_interface *intf)
  *
  * The USB host controller driver calls this function when its root hub
  * is resumed and Vbus power has been interrupted or the controller
- * has been reset.  The routine marks @rhdev as having lost power.  When
- * the hub driver is resumed it will take notice; if CONFIG_USB_PERSIST
- * is enabled then it will carry out power-session recovery, otherwise
- * it will disconnect all the child devices.
+ * has been reset.  The routine marks @rhdev as having lost power.
+ * When the hub driver is resumed it will take notice and carry out
+ * power-session recovery for all the "USB-PERSIST"-enabled child devices;
+ * the others will be disconnected.
  */
 void usb_root_hub_lost_power(struct usb_device *rhdev)
 {
@@ -2127,12 +2218,13 @@ static int hub_port_debounce(struct usb_hub *hub, int port1)
        return portstatus;
 }
 
-static void ep0_reinit(struct usb_device *udev)
+void usb_ep0_reinit(struct usb_device *udev)
 {
        usb_disable_endpoint(udev, 0 + USB_DIR_IN);
        usb_disable_endpoint(udev, 0 + USB_DIR_OUT);
        usb_enable_endpoint(udev, &udev->ep0);
 }
+EXPORT_SYMBOL_GPL(usb_ep0_reinit);
 
 #define usb_sndaddr0pipe()     (PIPE_CONTROL << 30)
 #define usb_rcvaddr0pipe()     ((PIPE_CONTROL << 30) | USB_DIR_IN)
@@ -2151,9 +2243,10 @@ static int hub_set_address(struct usb_device *udev, int devnum)
                USB_REQ_SET_ADDRESS, 0, devnum, 0,
                NULL, 0, USB_CTRL_SET_TIMEOUT);
        if (retval == 0) {
-               udev->devnum = devnum;  /* Device now using proper address */
+               /* Device now using proper address. */
+               update_address(udev, devnum);
                usb_set_device_state(udev, USB_STATE_ADDRESS);
-               ep0_reinit(udev);
+               usb_ep0_reinit(udev);
        }
        return retval;
 }
@@ -2335,26 +2428,33 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
 #undef GET_DESCRIPTOR_BUFSIZE
                }
 
-               for (j = 0; j < SET_ADDRESS_TRIES; ++j) {
-                       retval = hub_set_address(udev, devnum);
-                       if (retval >= 0)
+               /*
+                * If device is WUSB, we already assigned an
+                * unauthorized address in the Connect Ack sequence;
+                * authorization will assign the final address.
+                */
+               if (udev->wusb == 0) {
+                       for (j = 0; j < SET_ADDRESS_TRIES; ++j) {
+                               retval = hub_set_address(udev, devnum);
+                               if (retval >= 0)
+                                       break;
+                               msleep(200);
+                       }
+                       if (retval < 0) {
+                               dev_err(&udev->dev,
+                                       "device not accepting address %d, error %d\n",
+                                       devnum, retval);
+                               goto fail;
+                       }
+
+                       /* cope with hardware quirkiness:
+                        *  - let SET_ADDRESS settle, some device hardware wants it
+                        *  - read ep0 maxpacket even for high and low speed,
+                        */
+                       msleep(10);
+                       if (USE_NEW_SCHEME(retry_counter))
                                break;
-                       msleep(200);
-               }
-               if (retval < 0) {
-                       dev_err(&udev->dev,
-                               "device not accepting address %d, error %d\n",
-                               devnum, retval);
-                       goto fail;
-               }
-               /* cope with hardware quirkiness:
-                *  - let SET_ADDRESS settle, some device hardware wants it
-                *  - read ep0 maxpacket even for high and low speed,
-                */
-               msleep(10);
-               if (USE_NEW_SCHEME(retry_counter))
-                       break;
+               }
 
                retval = usb_get_device_descriptor(udev, 8);
                if (retval < 8) {
@@ -2371,7 +2471,7 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
        if (retval)
                goto fail;
 
-       i = udev->descriptor.bMaxPacketSize0 == 0xff?
+       i = udev->descriptor.bMaxPacketSize0 == 0xff?   /* wusb device? */
            512 : udev->descriptor.bMaxPacketSize0;
        if (le16_to_cpu(udev->ep0.desc.wMaxPacketSize) != i) {
                if (udev->speed != USB_SPEED_FULL ||
@@ -2382,7 +2482,7 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
                }
                dev_dbg(&udev->dev, "ep0 maxpacket = %d\n", i);
                udev->ep0.desc.wMaxPacketSize = cpu_to_le16(i);
-               ep0_reinit(udev);
+               usb_ep0_reinit(udev);
        }
   
        retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE);
@@ -2399,7 +2499,7 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
 fail:
        if (retval) {
                hub_port_disable(hub, port1, 0);
-               udev->devnum = devnum;  /* for disconnect processing */
+               update_address(udev, devnum);   /* for disconnect processing */
        }
        mutex_unlock(&usb_address0_mutex);
        return retval;
@@ -2482,6 +2582,7 @@ 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);
        int status, i;
  
@@ -2547,6 +2648,7 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
                udev->speed = USB_SPEED_UNKNOWN;
                udev->bus_mA = hub->mA_per_port;
                udev->level = hdev->level + 1;
+               udev->wusb = hub_is_wusb(hub);
 
                /* set the address */
                choose_address(udev);
@@ -2636,15 +2738,18 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
 loop_disable:
                hub_port_disable(hub, port1, 1);
 loop:
-               ep0_reinit(udev);
+               usb_ep0_reinit(udev);
                release_address(udev);
                usb_put_dev(udev);
                if ((status == -ENOTCONN) || (status == -ENOTSUPP))
                        break;
        }
+       dev_err(hub_dev, "unable to enumerate USB device on port %d\n", port1);
  
 done:
        hub_port_disable(hub, port1, 1);
+       if (hcd->driver->relinquish_port && !hub->hdev->parent)
+               hcd->driver->relinquish_port(hcd, port1);
 }
 
 static void hub_events(void)
@@ -2703,7 +2808,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_pre_reset(intf);
+                       hub_stop(hub);
                        goto loop;
                }
 
@@ -2849,11 +2954,6 @@ 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 */
                if (list_empty(&hub->event_list))
@@ -2867,7 +2967,13 @@ loop:
 
 static int hub_thread(void *__unused)
 {
+       /* khubd needs to be freezable to avoid intefering with USB-PERSIST
+        * port handover.  Otherwise it might see that a full-speed device
+        * was gone before the EHCI controller had handed its port over to
+        * the companion full-speed controller.
+        */
        set_freezable();
+
        do {
                hub_events();
                wait_event_freezable(khubd_wait,
@@ -2936,16 +3042,36 @@ void usb_hub_cleanup(void)
        usb_deregister(&hub_driver);
 } /* usb_hub_cleanup() */
 
-static int config_descriptors_changed(struct usb_device *udev)
+static int descriptors_changed(struct usb_device *udev,
+               struct usb_device_descriptor *old_device_descriptor)
 {
-       unsigned                        index;
-       unsigned                        len = 0;
-       struct usb_config_descriptor    *buf;
+       int             changed = 0;
+       unsigned        index;
+       unsigned        serial_len = 0;
+       unsigned        len;
+       unsigned        old_length;
+       int             length;
+       char            *buf;
+
+       if (memcmp(&udev->descriptor, old_device_descriptor,
+                       sizeof(*old_device_descriptor)) != 0)
+               return 1;
+
+       /* Since the idVendor, idProduct, and bcdDevice values in the
+        * device descriptor haven't changed, we will assume the
+        * Manufacturer and Product strings haven't changed either.
+        * But the SerialNumber string could be different (e.g., a
+        * different flash card of the same brand).
+        */
+       if (udev->serial)
+               serial_len = strlen(udev->serial) + 1;
 
+       len = serial_len;
        for (index = 0; index < udev->descriptor.bNumConfigurations; index++) {
-               if (len < le16_to_cpu(udev->config[index].desc.wTotalLength))
-                       len = le16_to_cpu(udev->config[index].desc.wTotalLength);
+               old_length = le16_to_cpu(udev->config[index].desc.wTotalLength);
+               len = max(len, old_length);
        }
+
        buf = kmalloc(len, GFP_NOIO);
        if (buf == NULL) {
                dev_err(&udev->dev, "no mem to re-read configs after reset\n");
@@ -2953,25 +3079,41 @@ static int config_descriptors_changed(struct usb_device *udev)
                return 1;
        }
        for (index = 0; index < udev->descriptor.bNumConfigurations; index++) {
-               int length;
-               int old_length = le16_to_cpu(udev->config[index].desc.wTotalLength);
-
+               old_length = le16_to_cpu(udev->config[index].desc.wTotalLength);
                length = usb_get_descriptor(udev, USB_DT_CONFIG, index, buf,
                                old_length);
-               if (length < old_length) {
+               if (length != old_length) {
                        dev_dbg(&udev->dev, "config index %d, error %d\n",
                                        index, length);
+                       changed = 1;
                        break;
                }
                if (memcmp (buf, udev->rawdescriptors[index], old_length)
                                != 0) {
                        dev_dbg(&udev->dev, "config index %d changed (#%d)\n",
-                               index, buf->bConfigurationValue);
+                               index,
+                               ((struct usb_config_descriptor *) buf)->
+                                       bConfigurationValue);
+                       changed = 1;
                        break;
                }
        }
+
+       if (!changed && serial_len) {
+               length = usb_string(udev, udev->descriptor.iSerialNumber,
+                               buf, serial_len);
+               if (length + 1 != serial_len) {
+                       dev_dbg(&udev->dev, "serial string error %d\n",
+                                       length);
+                       changed = 1;
+               } else if (memcmp(buf, udev->serial, length) != 0) {
+                       dev_dbg(&udev->dev, "serial string changed\n");
+                       changed = 1;
+               }
+       }
+
        kfree(buf);
-       return index != udev->descriptor.bNumConfigurations;
+       return changed;
 }
 
 /**
@@ -3021,7 +3163,7 @@ int usb_reset_device(struct usb_device *udev)
 
        if (!parent_hdev) {
                /* this requires hcd-specific logic; see OHCI hc_restart() */
-               dev_dbg(&udev->dev, "%s for root hub!\n", __FUNCTION__);
+               dev_dbg(&udev->dev, "%s for root hub!\n", __func__);
                return -EISDIR;
        }
        parent_hub = hdev_to_hub(parent_hdev);
@@ -3031,21 +3173,18 @@ int usb_reset_device(struct usb_device *udev)
 
                /* ep0 maxpacket size may change; let the HCD know about it.
                 * Other endpoints will be handled by re-enumeration. */
-               ep0_reinit(udev);
+               usb_ep0_reinit(udev);
                ret = hub_port_init(parent_hub, udev, port1, i);
                if (ret >= 0 || ret == -ENOTCONN || ret == -ENODEV)
                        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;
  
        /* Device might have changed firmware (DFU or similar) */
-       if (memcmp(&udev->descriptor, &descriptor, sizeof descriptor)
-                       || config_descriptors_changed (udev)) {
+       if (descriptors_changed(udev, &descriptor)) {
                dev_info(&udev->dev, "device firmware changed\n");
                udev->descriptor = descriptor;  /* for disconnect() calls */
                goto re_enumerate;
@@ -3093,7 +3232,7 @@ re_enumerate:
        hub_port_logical_disconnect(parent_hub, port1);
        return -ENODEV;
 }
-EXPORT_SYMBOL(usb_reset_device);
+EXPORT_SYMBOL_GPL(usb_reset_device);
 
 /**
  * usb_reset_composite_device - warn interface drivers and perform a USB port reset
@@ -3110,16 +3249,12 @@ EXPORT_SYMBOL(usb_reset_device);
  * 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().
- *
- * The interface locks are acquired during the pre_reset stage and released
- * during the post_reset stage.  However if iface is not NULL and is
- * currently being probed, we assume that the caller already owns its
- * lock.
  */
 int usb_reset_composite_device(struct usb_device *udev,
                struct usb_interface *iface)
 {
        int ret;
+       int i;
        struct usb_host_config *config = udev->actconfig;
 
        if (udev->state == USB_STATE_NOTATTACHED ||
@@ -3136,16 +3271,11 @@ int usb_reset_composite_device(struct usb_device *udev,
                iface = NULL;
 
        if (config) {
-               int i;
-               struct usb_interface *cintf;
-               struct usb_driver *drv;
-
                for (i = 0; i < config->desc.bNumInterfaces; ++i) {
-                       cintf = config->interface[i];
-                       if (cintf != iface)
-                               down(&cintf->dev.sem);
-                       if (device_is_registered(&cintf->dev) &&
-                                       cintf->dev.driver) {
+                       struct usb_interface *cintf = config->interface[i];
+                       struct usb_driver *drv;
+
+                       if (cintf->dev.driver) {
                                drv = to_usb_driver(cintf->dev.driver);
                                if (drv->pre_reset)
                                        (drv->pre_reset)(cintf);
@@ -3157,25 +3287,20 @@ int usb_reset_composite_device(struct usb_device *udev,
        ret = usb_reset_device(udev);
 
        if (config) {
-               int i;
-               struct usb_interface *cintf;
-               struct usb_driver *drv;
-
                for (i = config->desc.bNumInterfaces - 1; i >= 0; --i) {
-                       cintf = config->interface[i];
-                       if (device_is_registered(&cintf->dev) &&
-                                       cintf->dev.driver) {
+                       struct usb_interface *cintf = config->interface[i];
+                       struct usb_driver *drv;
+
+                       if (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 */
                        }
-                       if (cintf != iface)
-                               up(&cintf->dev.sem);
                }
        }
 
        usb_autosuspend_device(udev);
        return ret;
 }
-EXPORT_SYMBOL(usb_reset_composite_device);
+EXPORT_SYMBOL_GPL(usb_reset_composite_device);