USB: replace flush_workqueue with cancel_sync_work

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>

authored by Alan Stern and committed by Linus Torvalds d5d4db70 c420bc9f

+26 -12
+1 -1
drivers/usb/core/hcd.c
··· 1681 spin_unlock_irq (&hcd_root_hub_lock); 1682 1683 #ifdef CONFIG_PM 1684 - flush_workqueue(ksuspend_usb_wq); 1685 #endif 1686 1687 mutex_lock(&usb_bus_list_lock);
··· 1681 spin_unlock_irq (&hcd_root_hub_lock); 1682 1683 #ifdef CONFIG_PM 1684 + cancel_work_sync(&hcd->wakeup_work); 1685 #endif 1686 1687 mutex_lock(&usb_bus_list_lock);
+25 -7
drivers/usb/core/hub.c
··· 1158 } 1159 } 1160 1161 /** 1162 * usb_disconnect - disconnect a device (usbcore-internal) 1163 * @pdev: pointer to device being disconnected ··· 1248 *pdev = NULL; 1249 spin_unlock_irq(&device_state_lock); 1250 1251 - /* Decrement the parent's count of unsuspended children */ 1252 - if (udev->parent) { 1253 - usb_pm_lock(udev); 1254 - if (!udev->discon_suspended) 1255 - usb_autosuspend_device(udev->parent); 1256 - usb_pm_unlock(udev); 1257 - } 1258 1259 put_device(&udev->dev); 1260 }
··· 1158 } 1159 } 1160 1161 + #ifdef CONFIG_USB_SUSPEND 1162 + 1163 + static void usb_stop_pm(struct usb_device *udev) 1164 + { 1165 + /* Synchronize with the ksuspend thread to prevent any more 1166 + * autosuspend requests from being submitted, and decrement 1167 + * the parent's count of unsuspended children. 1168 + */ 1169 + usb_pm_lock(udev); 1170 + if (udev->parent && !udev->discon_suspended) 1171 + usb_autosuspend_device(udev->parent); 1172 + usb_pm_unlock(udev); 1173 + 1174 + /* Stop any autosuspend requests already submitted */ 1175 + cancel_rearming_delayed_work(&udev->autosuspend); 1176 + } 1177 + 1178 + #else 1179 + 1180 + static inline void usb_stop_pm(struct usb_device *udev) 1181 + { } 1182 + 1183 + #endif 1184 + 1185 /** 1186 * usb_disconnect - disconnect a device (usbcore-internal) 1187 * @pdev: pointer to device being disconnected ··· 1224 *pdev = NULL; 1225 spin_unlock_irq(&device_state_lock); 1226 1227 + usb_stop_pm(udev); 1228 1229 put_device(&udev->dev); 1230 }
-4
drivers/usb/core/usb.c
··· 184 185 udev = to_usb_device(dev); 186 187 - #ifdef CONFIG_USB_SUSPEND 188 - cancel_delayed_work(&udev->autosuspend); 189 - flush_workqueue(ksuspend_usb_wq); 190 - #endif 191 usb_destroy_configuration(udev); 192 usb_put_hcd(bus_to_hcd(udev->bus)); 193 kfree(udev->product);
··· 184 185 udev = to_usb_device(dev); 186 187 usb_destroy_configuration(udev); 188 usb_put_hcd(bus_to_hcd(udev->bus)); 189 kfree(udev->product);