Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d1aa3e6a authored by Alan Stern's avatar Alan Stern Committed by Greg Kroah-Hartman
Browse files

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: default avatarAlan Stern <stern@rowland.harvard.edu>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent 58ed7b94
Loading
Loading
Loading
Loading
+16 −16
Original line number Diff line number Diff line
@@ -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,19 +996,19 @@ 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;
	}
@@ -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.