]> err.no Git - linux-2.6/blobdiff - drivers/usb/core/hub.c
Merge git://git.kernel.org/pub/scm/linux/kernel/git/mingo/linux-2.6-sched
[linux-2.6] / drivers / usb / core / hub.c
index 0b8ed414d5cf793b3912e09af6b75c75c37991cc..50e79010401cd42050062d52d1c1e9876cc0aa53 100644 (file)
 #include "hcd.h"
 #include "hub.h"
 
+#ifdef CONFIG_USB_PERSIST
+#define        USB_PERSIST     1
+#else
+#define        USB_PERSIST     0
+#endif
+
 struct usb_hub {
        struct device           *intfdev;       /* the "interface" device */
        struct usb_device       *hdev;
@@ -590,27 +596,18 @@ static void hub_port_logical_disconnect(struct usb_hub *hub, int port1)
        kick_khubd(hub);
 }
 
-static void disconnect_all_children(struct usb_hub *hub, int logical)
-{
-       struct usb_device *hdev = hub->hdev;
-       int port1;
-
-       for (port1 = 1; port1 <= hdev->maxchild; ++port1) {
-               if (hdev->children[port1-1]) {
-                       if (logical)
-                               hub_port_logical_disconnect(hub, port1);
-                       else
-                               usb_disconnect(&hdev->children[port1-1]);
-               }
-       }
-}
-
 /* caller has locked the hub device */
 static int hub_pre_reset(struct usb_interface *intf)
 {
        struct usb_hub *hub = usb_get_intfdata(intf);
+       struct usb_device *hdev = hub->hdev;
+       int i;
 
-       disconnect_all_children(hub, 0);
+       /* Disconnect all the children */
+       for (i = 0; i < hdev->maxchild; ++i) {
+               if (hdev->children[i])
+                       usb_disconnect(&hdev->children[i]);
+       }
        hub_quiesce(hub);
        return 0;
 }
@@ -1080,72 +1077,6 @@ void usb_set_device_state(struct usb_device *udev,
        spin_unlock_irqrestore(&device_state_lock, flags);
 }
 
-
-#ifdef CONFIG_PM
-
-/**
- * usb_reset_suspended_device - reset a suspended device instead of resuming it
- * @udev: device to be reset instead of resumed
- *
- * If a host controller doesn't maintain VBUS suspend current during a
- * system sleep or is reset when the system wakes up, all the USB
- * power sessions below it will be broken.  This is especially troublesome
- * for mass-storage devices containing mounted filesystems, since the
- * device will appear to have disconnected and all the memory mappings
- * to it will be lost.
- *
- * As an alternative, this routine attempts to recover power sessions for
- * devices that are still present by resetting them instead of resuming
- * them.  If all goes well, the devices will appear to persist across the
- * the interruption of the power sessions.
- *
- * This facility is inherently 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
- * asleep, he will have only himself to blame when the filesystem on the
- * new card is corrupted and the system crashes.
- */
-int usb_reset_suspended_device(struct usb_device *udev)
-{
-       int rc = 0;
-
-       dev_dbg(&udev->dev, "usb %sresume\n", "reset-");
-
-       /* After we're done the device won't be suspended any more.
-        * In addition, the reset won't work if udev->state is SUSPENDED.
-        */
-       usb_set_device_state(udev, udev->actconfig
-                       ? USB_STATE_CONFIGURED
-                       : USB_STATE_ADDRESS);
-
-       /* Root hubs don't need to be (and can't be) reset */
-       if (udev->parent)
-               rc = usb_reset_device(udev);
-       return rc;
-}
-
-/**
- * usb_root_hub_lost_power - called by HCD if the root hub lost Vbus power
- * @rhdev: struct usb_device for the root hub
- *
- * 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.
- */
-void usb_root_hub_lost_power(struct usb_device *rhdev)
-{
-       dev_warn(&rhdev->dev, "root hub lost power or was reset\n");
-       rhdev->reset_resume = 1;
-}
-EXPORT_SYMBOL_GPL(usb_root_hub_lost_power);
-
-#endif /* CONFIG_PM */
-
 static void choose_address(struct usb_device *udev)
 {
        int             devnum;
@@ -1672,18 +1603,22 @@ int usb_port_suspend(struct usb_device *udev)
 /*
  * If the USB "suspend" state is in use (rather than "global suspend"),
  * many devices will be individually taken out of suspend state using
- * special" resume" signaling.  These routines kick in shortly after
+ * special "resume" signaling.  This routine kicks in shortly after
  * hardware resume signaling is finished, either because of selective
  * resume (by host) or remote wakeup (by device) ... now see what changed
  * in the tree that's rooted at this device.
+ *
+ * If @udev->reset_resume is set then the device is reset before the
+ * status check is done.
  */
 static int finish_port_resume(struct usb_device *udev)
 {
-       int     status;
+       int     status = 0;
        u16     devstatus;
 
        /* caller owns the udev device lock */
-       dev_dbg(&udev->dev, "finish resume\n");
+       dev_dbg(&udev->dev, "finish %sresume\n",
+                       udev->reset_resume ? "reset-" : "");
 
        /* usb ch9 identifies four variants of SUSPENDED, based on what
         * state the device resumes to.  Linux currently won't see the
@@ -1694,13 +1629,23 @@ static int finish_port_resume(struct usb_device *udev)
                        ? USB_STATE_CONFIGURED
                        : USB_STATE_ADDRESS);
 
+       /* 10.5.4.5 says not to reset a suspended port if the attached
+        * device is enabled for remote wakeup.  Hence the reset
+        * operation is carried out here, after the port has been
+        * resumed.
+        */
+       if (udev->reset_resume)
+               status = usb_reset_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,
         * and device drivers will know about any resume quirks.
         */
-       status = usb_get_status(udev, USB_RECIP_DEVICE, 0, &devstatus);
-       if (status >= 0)
-               status = (status == 2 ? 0 : -ENODEV);
+       if (status == 0) {
+               status = usb_get_status(udev, USB_RECIP_DEVICE, 0, &devstatus);
+               if (status >= 0)
+                       status = (status == 2 ? 0 : -ENODEV);
+       }
 
        if (status) {
                dev_dbg(&udev->dev, "gone after usb resume? status %d\n",
@@ -1735,6 +1680,28 @@ 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
+ * 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.
+ *
+ * For example, if a host controller doesn't maintain VBUS suspend current
+ * during a system sleep or is reset when the system wakes up, all the USB
+ * power sessions below it will be broken.  This is especially troublesome
+ * for mass-storage devices containing mounted filesystems, since the
+ * device will appear to have disconnected and all the memory mappings
+ * 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
+ * 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
+ * asleep, he will have only himself to blame when the filesystem on the
+ * new card is corrupted and the system crashes.
+ *
  * Returns 0 on success, else negative errno.
  */
 int usb_port_resume(struct usb_device *udev)
@@ -1743,6 +1710,7 @@ 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);
@@ -1765,20 +1733,23 @@ int usb_port_resume(struct usb_device *udev)
                                udev->auto_pm ? "auto-" : "");
                msleep(25);
 
-#define LIVE_FLAGS     ( USB_PORT_STAT_POWER \
-                       | USB_PORT_STAT_ENABLE \
-                       | USB_PORT_STAT_CONNECTION)
-
                /* Virtual root hubs can trigger on GET_PORT_STATUS to
                 * stop resume signaling.  Then finish the resume
                 * sequence.
                 */
                status = hub_port_status(hub, port1, &portstatus, &portchange);
-SuspendCleared:
-               if (status < 0
-                               || (portstatus & LIVE_FLAGS) != LIVE_FLAGS
-                               || (portstatus & USB_PORT_STAT_SUSPEND) != 0
-                               ) {
+
+ SuspendCleared:
+               if (USB_PERSIST && 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;
+
+               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);
@@ -1790,18 +1761,19 @@ SuspendCleared:
                                                USB_PORT_FEAT_C_SUSPEND);
                        /* TRSMRCY = 10 msec */
                        msleep(10);
-                       status = finish_port_resume(udev);
                }
        }
-       if (status < 0) {
-               dev_dbg(&udev->dev, "can't resume, status %d\n", status);
-               hub_port_logical_disconnect(hub, port1);
-       }
 
        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);
+       if (status < 0) {
+               dev_dbg(&udev->dev, "can't resume, status %d\n", status);
+               hub_port_logical_disconnect(hub, port1);
+       }
        return status;
 }
 
@@ -1830,7 +1802,14 @@ int usb_port_suspend(struct usb_device *udev)
 
 int usb_port_resume(struct usb_device *udev)
 {
-       return 0;
+       int status = 0;
+
+       /* However we may need to do a reset-resume */
+       if (udev->reset_resume) {
+               dev_dbg(&udev->dev, "reset-resume\n");
+               status = usb_reset_device(udev);
+       }
+       return status;
 }
 
 static inline int remote_wakeup(struct usb_device *udev)
@@ -1884,58 +1863,61 @@ static int hub_resume(struct usb_interface *intf)
        return 0;
 }
 
-#ifdef CONFIG_USB_PERSIST
-
-#define USB_PERSIST    1
-
-/* For "persistent-device" resets we must mark the child devices for reset
- * and turn off a possible connect-change status (so khubd won't disconnect
- * them later).
- */
-static void mark_children_for_reset_resume(struct usb_hub *hub)
+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;
 
+       hub_power_on(hub);
+
        for (port1 = 1; port1 <= hdev->maxchild; ++port1) {
                struct usb_device *child = hdev->children[port1-1];
 
                if (child) {
-                       child->reset_resume = 1;
-                       clear_port_feature(hdev, port1,
-                                       USB_PORT_FEAT_C_CONNECTION);
+
+                       /* 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);
+                       }
                }
        }
-}
-
-#else
-
-#define USB_PERSIST    0
-
-static inline void mark_children_for_reset_resume(struct usb_hub *hub)
-{ }
 
-#endif /* CONFIG_USB_PERSIST */
-
-static int hub_reset_resume(struct usb_interface *intf)
-{
-       struct usb_hub *hub = usb_get_intfdata(intf);
-
-       hub_power_on(hub);
-       if (USB_PERSIST)
-               mark_children_for_reset_resume(hub);
-       else {
-               /* Reset-resume doesn't call pre_reset, so we have to
-                * disconnect the children here.  But we may not lock
-                * the child devices, so we have to do a "logical"
-                * disconnect.
-                */
-               disconnect_all_children(hub, 1);
-       }
        hub_activate(hub);
        return 0;
 }
 
+/**
+ * usb_root_hub_lost_power - called by HCD if the root hub lost Vbus power
+ * @rhdev: struct usb_device for the root hub
+ *
+ * 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.
+ */
+void usb_root_hub_lost_power(struct usb_device *rhdev)
+{
+       dev_warn(&rhdev->dev, "root hub lost power or was reset\n");
+       rhdev->reset_resume = 1;
+}
+EXPORT_SYMBOL_GPL(usb_root_hub_lost_power);
+
 #else  /* CONFIG_PM */
 
 static inline int remote_wakeup(struct usb_device *udev)