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>
This commit is contained in:
parent
c420bc9f09
commit
d5d4db704b
|
@ -1681,7 +1681,7 @@ void usb_remove_hcd(struct usb_hcd *hcd)
|
||||||
spin_unlock_irq (&hcd_root_hub_lock);
|
spin_unlock_irq (&hcd_root_hub_lock);
|
||||||
|
|
||||||
#ifdef CONFIG_PM
|
#ifdef CONFIG_PM
|
||||||
flush_workqueue(ksuspend_usb_wq);
|
cancel_work_sync(&hcd->wakeup_work);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
mutex_lock(&usb_bus_list_lock);
|
mutex_lock(&usb_bus_list_lock);
|
||||||
|
|
|
@ -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)
|
* usb_disconnect - disconnect a device (usbcore-internal)
|
||||||
* @pdev: pointer to device being disconnected
|
* @pdev: pointer to device being disconnected
|
||||||
|
@ -1224,13 +1248,7 @@ void usb_disconnect(struct usb_device **pdev)
|
||||||
*pdev = NULL;
|
*pdev = NULL;
|
||||||
spin_unlock_irq(&device_state_lock);
|
spin_unlock_irq(&device_state_lock);
|
||||||
|
|
||||||
/* Decrement the parent's count of unsuspended children */
|
usb_stop_pm(udev);
|
||||||
if (udev->parent) {
|
|
||||||
usb_pm_lock(udev);
|
|
||||||
if (!udev->discon_suspended)
|
|
||||||
usb_autosuspend_device(udev->parent);
|
|
||||||
usb_pm_unlock(udev);
|
|
||||||
}
|
|
||||||
|
|
||||||
put_device(&udev->dev);
|
put_device(&udev->dev);
|
||||||
}
|
}
|
||||||
|
|
|
@ -184,10 +184,6 @@ static void usb_release_dev(struct device *dev)
|
||||||
|
|
||||||
udev = to_usb_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_destroy_configuration(udev);
|
||||||
usb_put_hcd(bus_to_hcd(udev->bus));
|
usb_put_hcd(bus_to_hcd(udev->bus));
|
||||||
kfree(udev->product);
|
kfree(udev->product);
|
||||||
|
|
Loading…
Reference in New Issue