USB: replace flush_workqueue with cancel_sync_work
authorAlan Stern <stern@rowland.harvard.edu>
Tue, 29 May 2007 20:34:52 +0000 (16:34 -0400)
committerLinus Torvalds <torvalds@woody.linux-foundation.org>
Tue, 29 May 2007 20:39:07 +0000 (13:39 -0700)
This patch (as912) replaces a couple of calls to flush_workqueue()
with cancel_sync_work() and cancel_rearming_delayed_work().  Using a
more directed approach allows us to avoid some nasty deadlocks.  The
prime example occurs when a first-level device (the parent is a root
hub) is removed while at the same time the root hub gets a remote
wakeup request.  khubd would try to flush the autosuspend workqueue
while holding the root-hub's lock, and the remote-wakeup workqueue
routine would be waiting to lock the root hub.

The patch also reorganizes the power management portion of
usb_disconnect(), separating it out into its own routine.  The
autosuspend workqueue entry is cancelled immediately instead of
waiting for the device's release routine.  In addition,
synchronization with the autosuspend thread is carried out even for
root hubs (an oversight in the original code).

Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Greg KH <gregkh@suse.de>
Cc: Mark Lord <lkml@rtr.ca>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
drivers/usb/core/hcd.c
drivers/usb/core/hub.c
drivers/usb/core/usb.c

index e277258df3827d3a1a6a1b6c7532389346af6d72..8969e42434b9ca2a2b2a54ea9c7aab0bcccd642a 100644 (file)
@@ -1681,7 +1681,7 @@ void usb_remove_hcd(struct usb_hcd *hcd)
        spin_unlock_irq (&hcd_root_hub_lock);
 
 #ifdef CONFIG_PM
-       flush_workqueue(ksuspend_usb_wq);
+       cancel_work_sync(&hcd->wakeup_work);
 #endif
 
        mutex_lock(&usb_bus_list_lock);
index caaa46f2dec724214ca33fc476058aee6d93a7df..24f10a19dbdbc1bbbf6befd31da7018078cfa186 100644 (file)
@@ -1158,6 +1158,30 @@ static void release_address(struct usb_device *udev)
        }
 }
 
+#ifdef CONFIG_USB_SUSPEND
+
+static void usb_stop_pm(struct usb_device *udev)
+{
+       /* Synchronize with the ksuspend thread to prevent any more
+        * autosuspend requests from being submitted, and decrement
+        * the parent's count of unsuspended children.
+        */
+       usb_pm_lock(udev);
+       if (udev->parent && !udev->discon_suspended)
+               usb_autosuspend_device(udev->parent);
+       usb_pm_unlock(udev);
+
+       /* Stop any autosuspend requests already submitted */
+       cancel_rearming_delayed_work(&udev->autosuspend);
+}
+
+#else
+
+static inline void usb_stop_pm(struct usb_device *udev)
+{ }
+
+#endif
+
 /**
  * usb_disconnect - disconnect a device (usbcore-internal)
  * @pdev: pointer to device being disconnected
@@ -1224,13 +1248,7 @@ void usb_disconnect(struct usb_device **pdev)
        *pdev = NULL;
        spin_unlock_irq(&device_state_lock);
 
-       /* Decrement the parent's count of unsuspended children */
-       if (udev->parent) {
-               usb_pm_lock(udev);
-               if (!udev->discon_suspended)
-                       usb_autosuspend_device(udev->parent);
-               usb_pm_unlock(udev);
-       }
+       usb_stop_pm(udev);
 
        put_device(&udev->dev);
 }
index 80627b6a2bf969a3860ca28350ae051eb123c15a..4a6299bd00478156417a7ed757ffc0d261bdc736 100644 (file)
@@ -184,10 +184,6 @@ static void usb_release_dev(struct device *dev)
 
        udev = to_usb_device(dev);
 
-#ifdef CONFIG_USB_SUSPEND
-       cancel_delayed_work(&udev->autosuspend);
-       flush_workqueue(ksuspend_usb_wq);
-#endif
        usb_destroy_configuration(udev);
        usb_put_hcd(bus_to_hcd(udev->bus));
        kfree(udev->product);