USB: fix race in autosuspend reschedule

This patch (as1002) fixes a small race which can occur when a driver
expects usbcore to reschedule an autosuspend request.  If the request
arrives too late, it won't be rescheduled.  The patch adds an extra
argument to autosuspend_check(), indicating that a reschedule is
needed no matter how much time has elapsed.

It also tries to avoid letting asynchronous changes to the value of
jiffies cause a delay to become negative, by caching a local copy of
the current time.

Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
This commit is contained in:
Alan Stern 2007-10-11 16:47:36 -04:00 committed by Greg Kroah-Hartman
parent 58ed7b94d9
commit d1aa3e6aa8

View File

@ -950,11 +950,11 @@ static int usb_resume_interface(struct usb_interface *intf, int reset_resume)
#ifdef CONFIG_USB_SUSPEND
/* Internal routine to check whether we may autosuspend a device. */
static int autosuspend_check(struct usb_device *udev)
static int autosuspend_check(struct usb_device *udev, int reschedule)
{
int i;
struct usb_interface *intf;
unsigned long suspend_time;
unsigned long suspend_time, j;
/* For autosuspend, fail fast if anything is in use or autosuspend
* is disabled. Also fail if any interfaces require remote wakeup
@ -996,20 +996,20 @@ static int autosuspend_check(struct usb_device *udev)
}
/* If everything is okay but the device hasn't been idle for long
* enough, queue a delayed autosuspend request.
* enough, queue a delayed autosuspend request. If the device
* _has_ been idle for long enough and the reschedule flag is set,
* likewise queue a delayed (1 second) autosuspend request.
*/
if (time_after(suspend_time, jiffies)) {
j = jiffies;
if (time_before(j, suspend_time))
reschedule = 1;
else
suspend_time = j + HZ;
if (reschedule) {
if (!timer_pending(&udev->autosuspend.timer)) {
/* The value of jiffies may change between the
* time_after() comparison above and the subtraction
* below. That's okay; the system behaves sanely
* when a timer is registered for the present moment
* or for the past.
*/
queue_delayed_work(ksuspend_usb_wq, &udev->autosuspend,
round_jiffies_relative(suspend_time - jiffies));
}
round_jiffies_relative(suspend_time - j));
}
return -EAGAIN;
}
return 0;
@ -1017,7 +1017,7 @@ static int autosuspend_check(struct usb_device *udev)
#else
static inline int autosuspend_check(struct usb_device *udev)
static inline int autosuspend_check(struct usb_device *udev, int reschedule)
{
return 0;
}
@ -1074,7 +1074,7 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg)
udev->do_remote_wakeup = device_may_wakeup(&udev->dev);
if (udev->auto_pm) {
status = autosuspend_check(udev);
status = autosuspend_check(udev, 0);
if (status < 0)
goto done;
}
@ -1100,7 +1100,7 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg)
/* Try another autosuspend when the interfaces aren't busy */
if (udev->auto_pm)
autosuspend_check(udev);
autosuspend_check(udev, status == -EBUSY);
/* If the suspend succeeded then prevent any more URB submissions,
* flush any outstanding URBs, and propagate the suspend up the tree.