]> git.proxmox.com Git - mirror_ubuntu-disco-kernel.git/commitdiff
USB: add USB-Persist facility
authorAlan Stern <stern@rowland.harvard.edu>
Fri, 4 May 2007 15:52:20 +0000 (11:52 -0400)
committerGreg Kroah-Hartman <gregkh@suse.de>
Thu, 12 Jul 2007 23:29:47 +0000 (16:29 -0700)
This patch (as886) adds the controversial USB-persist facility,
allowing USB devices to persist across a power loss during system
suspend.

The facility is controlled by a new Kconfig option (with appropriate
warnings about the potential dangers); when the option is off the
behavior will remain the same as it is now.  But when the option is
on, people will be able to use suspend-to-disk and keep their USB
filesystems intact -- something particularly valuable for small
machines where the root filesystem is on a USB device!

Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Documentation/power/swsusp.txt
Documentation/usb/persist.txt [new file with mode: 0644]
drivers/hid/usbhid/hid-core.c
drivers/usb/core/Kconfig
drivers/usb/core/driver.c
drivers/usb/core/generic.c
drivers/usb/core/hub.c
drivers/usb/core/usb.h
drivers/usb/storage/usb.c
include/linux/usb.h

index 5b8d6953f05e741b6b6a3dd88e1ee0c6681f680a..152b510d1bbb5560945e3550c9b0276f7aa452ab 100644 (file)
@@ -393,6 +393,9 @@ safest thing is to unmount all filesystems on removable media (such USB,
 Firewire, CompactFlash, MMC, external SATA, or even IDE hotplug bays)
 before suspending; then remount them after resuming.
 
+There is a work-around for this problem.  For more information, see
+Documentation/usb/persist.txt.
+
 Q: I upgraded the kernel from 2.6.15 to 2.6.16. Both kernels were
 compiled with the similar configuration files. Anyway I found that
 suspend to disk (and resume) is much slower on 2.6.16 compared to
diff --git a/Documentation/usb/persist.txt b/Documentation/usb/persist.txt
new file mode 100644 (file)
index 0000000..6dcd5f8
--- /dev/null
@@ -0,0 +1,144 @@
+               USB device persistence during system suspend
+
+                  Alan Stern <stern@rowland.harvard.edu>
+
+                September 2, 2006 (Updated March 27, 2007)
+
+
+       What is the problem?
+
+According to the USB specification, when a USB bus is suspended the
+bus must continue to supply suspend current (around 1-5 mA).  This
+is so that devices can maintain their internal state and hubs can
+detect connect-change events (devices being plugged in or unplugged).
+The technical term is "power session".
+
+If a USB device's power session is interrupted then the system is
+required to behave as though the device has been unplugged.  It's a
+conservative approach; in the absence of suspend current the computer
+has no way to know what has actually happened.  Perhaps the same
+device is still attached or perhaps it was removed and a different
+device plugged into the port.  The system must assume the worst.
+
+By default, Linux behaves according to the spec.  If a USB host
+controller loses power during a system suspend, then when the system
+wakes up all the devices attached to that controller are treated as
+though they had disconnected.  This is always safe and it is the
+"officially correct" thing to do.
+
+For many sorts of devices this behavior doesn't matter in the least.
+If the kernel wants to believe that your USB keyboard was unplugged
+while the system was asleep and a new keyboard was plugged in when the
+system woke up, who cares?  It'll still work the same when you type on
+it.
+
+Unfortunately problems _can_ arise, particularly with mass-storage
+devices.  The effect is exactly the same as if the device really had
+been unplugged while the system was suspended.  If you had a mounted
+filesystem on the device, you're out of luck -- everything in that
+filesystem is now inaccessible.  This is especially annoying if your
+root filesystem was located on the device, since your system will
+instantly crash.
+
+Loss of power isn't the only mechanism to worry about.  Anything that
+interrupts a power session will have the same effect.  For example,
+even though suspend current may have been maintained while the system
+was asleep, on many systems during the initial stages of wakeup the
+firmware (i.e., the BIOS) resets the motherboard's USB host
+controllers.  Result: all the power sessions are destroyed and again
+it's as though you had unplugged all the USB devices.  Yes, it's
+entirely the BIOS's fault, but that doesn't do _you_ any good unless
+you can convince the BIOS supplier to fix the problem (lots of luck!).
+
+On many systems the USB host controllers will get reset after a
+suspend-to-RAM.  On almost all systems, no suspend current is
+available during suspend-to-disk (also known as swsusp).  You can
+check the kernel log after resuming to see if either of these has
+happened; look for lines saying "root hub lost power or was reset".
+
+In practice, people are forced to unmount any filesystems on a USB
+device before suspending.  If the root filesystem is on a USB device,
+the system can't be suspended at all.  (All right, it _can_ be
+suspended -- but it will crash as soon as it wakes up, which isn't
+much better.)
+
+
+       What is the solution?
+
+Setting CONFIG_USB_PERSIST will cause the kernel to work around these
+issues.  It enables a mode in which the core USB device data
+structures are allowed to persist across a power-session disruption.
+It works like this.  If the kernel sees that a USB host controller is
+not in the expected state during resume (i.e., if the controller was
+reset or otherwise had lost power) then it applies a persistence check
+to each of the USB devices below that controller.  It doesn't try to
+resume the device; that can't work once the power session is gone.
+Instead it issues a USB port reset and then re-enumerates the device.
+(This is exactly the same thing that happens whenever a USB device is
+reset.)  If the re-enumeration shows that the device now attached to
+that port has the same descriptors as before, including the Vendor and
+Product IDs, then the kernel continues to use the same device
+structure.  In effect, the kernel treats the device as though it had
+merely been reset instead of unplugged.
+
+If no device is now attached to the port, or if the descriptors are
+different from what the kernel remembers, then the treatment is what
+you would expect.  The kernel destroys the old device structure and
+behaves as though the old device had been unplugged and a new device
+plugged in, just as it would without the CONFIG_USB_PERSIST option.
+
+The end result is that the USB device remains available and usable.
+Filesystem mounts and memory mappings are unaffected, and the world is
+now a good and happy place.
+
+
+       Is this the best solution?
+
+Perhaps not.  Arguably, keeping track of mounted filesystems and
+memory mappings across device disconnects should be handled by a
+centralized Logical Volume Manager.  Such a solution would allow you
+to plug in a USB flash device, create a persistent volume associated
+with it, unplug the flash device, plug it back in later, and still
+have the same persistent volume associated with the device.  As such
+it would be more far-reaching than CONFIG_USB_PERSIST.
+
+On the other hand, writing a persistent volume manager would be a big
+job and using it would require significant input from the user.  This
+solution is much quicker and easier -- and it exists now, a giant
+point in its favor!
+
+Furthermore, the USB_PERSIST option applies to _all_ USB devices, not
+just mass-storage devices.  It might turn out to be equally useful for
+other device types, such as network interfaces.
+
+
+       WARNING: Using CONFIG_USB_PERSIST can be dangerous!!
+
+When recovering an interrupted power session the kernel does its best
+to make sure the USB device hasn't been changed; that is, the same
+device is still plugged into the port as before.  But the checks
+aren't guaranteed to be 100% accurate.
+
+If you replace one USB device with another of the same type (same
+manufacturer, same IDs, and so on) there's an excellent chance the
+kernel won't detect the change.  Serial numbers and other strings are
+not compared.  In many cases it wouldn't help if they were, because
+manufacturers frequently omit serial numbers entirely in their
+devices.
+
+Furthermore it's quite possible to leave a USB device exactly the same
+while changing its media.  If you replace the flash memory card in a
+USB card reader while the system is asleep, the kernel will have no
+way to know you did it.  The kernel will assume that nothing has
+happened and will continue to use the partition tables, inodes, and
+memory mappings for the old card.
+
+If the kernel gets fooled in this way, it's almost certain to cause
+data corruption and to crash your system.  You'll have no one to blame
+but yourself.
+
+YOU HAVE BEEN WARNED!  USE AT YOUR OWN RISK!
+
+That having been said, most of the time there shouldn't be any trouble
+at all.  The "persist" feature can be extremely useful.  Make the most
+of it.
index 3afa4a5035b7128b6e84fdc835637bdb9c690b74..e221b0d1f667d416644dde421c51b3105f7b2719 100644 (file)
@@ -1015,7 +1015,7 @@ static void hid_pre_reset(struct usb_interface *intf)
        hid_suspend(intf, PMSG_ON);
 }
 
-static void hid_post_reset(struct usb_interface *intf)
+static void hid_post_reset(struct usb_interface *intf, int reset_resume)
 {
        struct usb_device *dev = interface_to_usbdev (intf);
 
index 346fc030c929ae172b6658df8f1a2b54113e1ac3..5113ef4cb7f6f24961cd2787a742e108fe485d2d 100644 (file)
@@ -86,6 +86,28 @@ config USB_SUSPEND
 
          If you are unsure about this, say N here.
 
+config USB_PERSIST
+       bool "USB device persistence during system suspend (DANGEROUS)"
+       depends on USB && PM && EXPERIMENTAL
+       default n
+       help
+         If you say Y here, USB device data structures will remain
+         persistent across system suspend, even if the USB bus loses
+         power.  (This includes software-suspend, also known as swsusp,
+         or suspend-to-disk.)  The devices will reappear as if by magic
+         when the system wakes up, with no need to unmount USB filesystems,
+         rmmod host-controller drivers, or do anything else.
+
+               WARNING: This option can be dangerous!
+
+         If a USB device is replaced by another of the same type while
+         the system is asleep, there's a good chance the kernel won't
+         detect the change.  Likewise if the media in a USB storage
+         device is replaced.  When this happens it's almost certain to
+         cause data corruption and maybe even crash your system.
+
+         If you are unsure, say N here.
+
 config USB_OTG
        bool
        depends on USB && EXPERIMENTAL
index e8b447e06c54d2c08b93668fca6c862700bddb3b..12dd986bdffdade37b7ad99b343185efb1474d71 100644 (file)
@@ -824,8 +824,9 @@ static int usb_resume_device(struct usb_device *udev)
        struct usb_device_driver        *udriver;
        int                             status = 0;
 
-       if (udev->state == USB_STATE_NOTATTACHED ||
-                       udev->state != USB_STATE_SUSPENDED)
+       if (udev->state == USB_STATE_NOTATTACHED)
+               goto done;
+       if (udev->state != USB_STATE_SUSPENDED && !udev->reset_resume)
                goto done;
 
        /* Can't resume it if it doesn't have a driver. */
@@ -882,7 +883,7 @@ done:
 }
 
 /* Caller has locked intf's usb_device's pm_mutex */
-static int usb_resume_interface(struct usb_interface *intf)
+static int usb_resume_interface(struct usb_interface *intf, int reset_resume)
 {
        struct usb_driver       *driver;
        int                     status = 0;
@@ -902,21 +903,21 @@ static int usb_resume_interface(struct usb_interface *intf)
        }
        driver = to_usb_driver(intf->dev.driver);
 
-       if (driver->resume) {
+       if (reset_resume && driver->post_reset)
+               driver->post_reset(intf, reset_resume);
+       else if (driver->resume) {
                status = driver->resume(intf);
                if (status)
                        dev_err(&intf->dev, "%s error %d\n",
                                        "resume", status);
-               else
-                       mark_active(intf);
-       } else {
+       } else
                dev_warn(&intf->dev, "no resume for driver %s?\n",
                                driver->name);
-               mark_active(intf);
-       }
 
 done:
        // dev_dbg(&intf->dev, "%s: status %d\n", __FUNCTION__, status);
+       if (status == 0)
+               mark_active(intf);
        return status;
 }
 
@@ -1063,7 +1064,7 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg)
        if (status != 0) {
                while (--i >= 0) {
                        intf = udev->actconfig->interface[i];
-                       usb_resume_interface(intf);
+                       usb_resume_interface(intf, 0);
                }
 
                /* Try another autosuspend when the interfaces aren't busy */
@@ -1162,20 +1163,21 @@ static int usb_resume_both(struct usb_device *udev)
                }
        } else {
 
-               /* Needed only for setting udev->dev.power.power_state.event
-                * and for possible debugging message. */
+               /* Needed for setting udev->dev.power.power_state.event,
+                * for possible debugging message, and for reset_resume. */
                status = usb_resume_device(udev);
        }
 
        if (status == 0 && udev->actconfig) {
                for (i = 0; i < udev->actconfig->desc.bNumInterfaces; i++) {
                        intf = udev->actconfig->interface[i];
-                       usb_resume_interface(intf);
+                       usb_resume_interface(intf, udev->reset_resume);
                }
        }
 
  done:
        // dev_dbg(&udev->dev, "%s: status %d\n", __FUNCTION__, status);
+       udev->reset_resume = 0;
        return status;
 }
 
@@ -1510,8 +1512,15 @@ static int usb_resume(struct device *dev)
        if (!is_usb_device(dev))        /* Ignore PM for interfaces */
                return 0;
        udev = to_usb_device(dev);
-       if (udev->autoresume_disabled)
-               return -EPERM;
+
+       /* If autoresume is disabled then we also want to prevent resume
+        * during system wakeup.  However, a "persistent-device" reset-resume
+        * after power loss counts as a wakeup event.  So allow a
+        * reset-resume to occur if remote wakeup is enabled. */
+       if (udev->autoresume_disabled) {
+               if (!(udev->reset_resume && udev->do_remote_wakeup))
+                       return -EPERM;
+       }
        return usb_external_resume_device(udev);
 }
 
index 7cbf992adccd4033aaa066cd34208a17454d314e..d363b0ea73459884162f419fadeb2738d19ef8f2 100644 (file)
@@ -217,7 +217,10 @@ static int generic_resume(struct usb_device *udev)
 {
        int rc;
 
-       rc = usb_port_resume(udev);
+       if (udev->reset_resume)
+               rc = usb_reset_suspended_device(udev);
+       else
+               rc = usb_port_resume(udev);
 
        /* Root hubs don't have upstream ports to resume or reset,
         * so the line above won't do much for them.  We have to
index 77a6627b18d2da8e4422e5cdf241dab85939e937..51d2d304568b9b40daab6f750587379ca53d4216 100644 (file)
@@ -553,45 +553,121 @@ static int hub_hub_status(struct usb_hub *hub,
 static int hub_port_disable(struct usb_hub *hub, int port1, int set_state)
 {
        struct usb_device *hdev = hub->hdev;
-       int ret;
+       int ret = 0;
 
-       if (hdev->children[port1-1] && set_state) {
+       if (hdev->children[port1-1] && set_state)
                usb_set_device_state(hdev->children[port1-1],
                                USB_STATE_NOTATTACHED);
-       }
-       ret = clear_port_feature(hdev, port1, USB_PORT_FEAT_ENABLE);
+       if (!hub->error)
+               ret = clear_port_feature(hdev, port1, USB_PORT_FEAT_ENABLE);
        if (ret)
                dev_err(hub->intfdev, "cannot disable port %d (err = %d)\n",
-                       port1, ret);
-
+                               port1, ret);
        return ret;
 }
 
+/*
+ * Disable a port and mark a logical connnect-change event, so that some
+ * time later khubd will disconnect() any existing usb_device on the port
+ * and will re-enumerate if there actually is a device attached.
+ */
+static void hub_port_logical_disconnect(struct usb_hub *hub, int port1)
+{
+       dev_dbg(hub->intfdev, "logical disconnect on port %d\n", port1);
+       hub_port_disable(hub, port1, 1);
 
-/* caller has locked the hub device */
-static void hub_pre_reset(struct usb_interface *intf)
+       /* FIXME let caller ask to power down the port:
+        *  - some devices won't enumerate without a VBUS power cycle
+        *  - SRP saves power that way
+        *  - ... new call, TBD ...
+        * That's easy if this hub can switch power per-port, and
+        * khubd reactivates the port later (timer, SRP, etc).
+        * Powerdown must be optional, because of reset/DFU.
+        */
+
+       set_bit(port1, hub->change_bits);
+       kick_khubd(hub);
+}
+
+static void disconnect_all_children(struct usb_hub *hub, int logical)
 {
-       struct usb_hub *hub = usb_get_intfdata(intf);
        struct usb_device *hdev = hub->hdev;
        int port1;
 
        for (port1 = 1; port1 <= hdev->maxchild; ++port1) {
-               if (hdev->children[port1 - 1]) {
-                       usb_disconnect(&hdev->children[port1 - 1]);
-                       if (hub->error == 0)
-                               hub_port_disable(hub, port1, 0);
+               if (hdev->children[port1-1]) {
+                       if (logical)
+                               hub_port_logical_disconnect(hub, port1);
+                       else
+                               usb_disconnect(&hdev->children[port1-1]);
+               }
+       }
+}
+
+#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)
+{
+       struct usb_device *hdev = hub->hdev;
+       int port1;
+
+       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);
                }
        }
+}
+
+#else
+
+#define USB_PERSIST    0
+
+static inline void mark_children_for_reset_resume(struct usb_hub *hub)
+{ }
+
+#endif /* CONFIG_USB_PERSIST */
+
+/* caller has locked the hub device */
+static void hub_pre_reset(struct usb_interface *intf)
+{
+       struct usb_hub *hub = usb_get_intfdata(intf);
+
+       /* This routine doesn't run as part of a reset-resume, so it's safe
+        * to disconnect all the drivers below the hub.
+        */
+       disconnect_all_children(hub, 0);
        hub_quiesce(hub);
 }
 
 /* caller has locked the hub device */
-static void hub_post_reset(struct usb_interface *intf)
+static void hub_post_reset(struct usb_interface *intf, int reset_resume)
 {
        struct usb_hub *hub = usb_get_intfdata(intf);
 
-       hub_activate(hub);
        hub_power_on(hub);
+       if (reset_resume) {
+               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);
 }
 
 
@@ -1053,33 +1129,64 @@ void usb_set_device_state(struct usb_device *udev,
 
 #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 all the children of the root hub
- * as NOTATTACHED and marks logical connect-change events on their ports.
+ * 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)
 {
-       struct usb_hub *hub;
-       int port1;
-       unsigned long flags;
-
        dev_warn(&rhdev->dev, "root hub lost power or was reset\n");
-
-       spin_lock_irqsave(&device_state_lock, flags);
-       hub = hdev_to_hub(rhdev);
-       for (port1 = 1; port1 <= rhdev->maxchild; ++port1) {
-               if (rhdev->children[port1 - 1]) {
-                       recursively_mark_NOTATTACHED(
-                                       rhdev->children[port1 - 1]);
-                       set_bit(port1, hub->change_bits);
-               }
-       }
-       spin_unlock_irqrestore(&device_state_lock, flags);
+       rhdev->reset_resume = 1;
 }
 EXPORT_SYMBOL_GPL(usb_root_hub_lost_power);
 
@@ -1513,29 +1620,6 @@ static int hub_port_reset(struct usb_hub *hub, int port1,
        return status;
 }
 
-/*
- * Disable a port and mark a logical connnect-change event, so that some
- * time later khubd will disconnect() any existing usb_device on the port
- * and will re-enumerate if there actually is a device attached.
- */
-static void hub_port_logical_disconnect(struct usb_hub *hub, int port1)
-{
-       dev_dbg(hub->intfdev, "logical disconnect on port %d\n", port1);
-       hub_port_disable(hub, port1, 1);
-
-       /* FIXME let caller ask to power down the port:
-        *  - some devices won't enumerate without a VBUS power cycle
-        *  - SRP saves power that way
-        *  - ... new call, TBD ...
-        * That's easy if this hub can switch power per-port, and
-        * khubd reactivates the port later (timer, SRP, etc).
-        * Powerdown must be optional, because of reset/DFU.
-        */
-
-       set_bit(port1, hub->change_bits);
-       kick_khubd(hub);
-}
-
 #ifdef CONFIG_PM
 
 #ifdef CONFIG_USB_SUSPEND
@@ -3018,7 +3102,7 @@ int usb_reset_composite_device(struct usb_device *udev,
                                        cintf->dev.driver) {
                                drv = to_usb_driver(cintf->dev.driver);
                                if (drv->post_reset)
-                                       (drv->post_reset)(cintf);
+                                       (drv->post_reset)(cintf, 0);
                        }
                        if (cintf != iface)
                                up(&cintf->dev.sem);
index 6f361df374fc61a42f9aa6189bb3af1a6764d8ec..1a4862886733c6e6999b5fa02f2fd86bf674f9f2 100644 (file)
@@ -36,6 +36,7 @@ extern void usb_host_cleanup(void);
 extern void usb_autosuspend_work(struct work_struct *work);
 extern int usb_port_suspend(struct usb_device *dev);
 extern int usb_port_resume(struct usb_device *dev);
+extern int usb_reset_suspended_device(struct usb_device *udev);
 extern int usb_external_suspend_device(struct usb_device *udev,
                pm_message_t msg);
 extern int usb_external_resume_device(struct usb_device *udev);
index df5dc186aef521dc305485adef5a8343ae701778..be4cd8fe4ce615226596f7115c169f720b6a36ff 100644 (file)
@@ -236,7 +236,7 @@ static void storage_pre_reset(struct usb_interface *iface)
        mutex_lock(&us->dev_mutex);
 }
 
-static void storage_post_reset(struct usb_interface *iface)
+static void storage_post_reset(struct usb_interface *iface, int reset_resume)
 {
        struct us_data *us = usb_get_intfdata(iface);
 
@@ -249,7 +249,11 @@ static void storage_post_reset(struct usb_interface *iface)
 
        /* FIXME: Notify the subdrivers that they need to reinitialize
         * the device */
-       mutex_unlock(&us->dev_mutex);
+
+       /* If this is a reset-resume then the pre_reset routine wasn't
+        * called, so we don't need to unlock the mutex. */
+       if (!reset_resume)
+               mutex_unlock(&us->dev_mutex);
 }
 
 /*
index 56aa2ee21f1b284659e6164bcc9f2dea59ada2c1..3d63e0c2dd70975e66080ebe5141beaf24f79b68 100644 (file)
@@ -403,6 +403,7 @@ struct usb_device {
 
        unsigned auto_pm:1;             /* autosuspend/resume in progress */
        unsigned do_remote_wakeup:1;    /* remote wakeup should be enabled */
+       unsigned reset_resume:1;        /* needs reset instead of resume */
        unsigned autosuspend_disabled:1; /* autosuspend and autoresume */
        unsigned autoresume_disabled:1;  /*  disabled by the user */
 #endif
@@ -819,7 +820,10 @@ struct usbdrv_wrap {
  * @pre_reset: Called by usb_reset_composite_device() when the device
  *     is about to be reset.
  * @post_reset: Called by usb_reset_composite_device() after the device
- *     has been reset.
+ *     has been reset, or in lieu of @resume following a reset-resume
+ *     (i.e., the device is reset instead of being resumed, as might
+ *     happen if power was lost).  The second argument tells which is
+ *     the reason.
  * @id_table: USB drivers use ID table to support hotplugging.
  *     Export this with MODULE_DEVICE_TABLE(usb,...).  This must be set
  *     or your driver's probe function will never get called.
@@ -861,7 +865,7 @@ struct usb_driver {
        int (*resume) (struct usb_interface *intf);
 
        void (*pre_reset) (struct usb_interface *intf);
-       void (*post_reset) (struct usb_interface *intf);
+       void (*post_reset) (struct usb_interface *intf, int reset_resume);
 
        const struct usb_device_id *id_table;