timers: Fix usleep_range() in the context of wake_up_process()
authorDouglas Anderson <dianders@chromium.org>
Fri, 21 Oct 2016 15:58:50 +0000 (08:58 -0700)
committerThomas Gleixner <tglx@linutronix.de>
Wed, 26 Oct 2016 11:14:46 +0000 (13:14 +0200)
Users of usleep_range() expect that it will _never_ return in less time
than the minimum passed parameter. However, nothing in the code ensures
this, when the sleeping task is woken by wake_up_process() or any other
mechanism which can wake a task from uninterruptible state.

Neither usleep_range() nor schedule_hrtimeout_range*() have any protection
against wakeups. schedule_hrtimeout_range*() is designed this way despite
the fact that the API documentation does not mention it.

msleep() already has code to handle this case since it will loop as long
as there was still time left.  usleep_range() has no such loop, add it.

Presumably this problem was not detected before because usleep_range() is
only used in a few places and the function is mostly used in contexts which
are not exposed to wakeups of any form.

An effort was made to look for users relying on the old behavior by
looking for usleep_range() in the same file as wake_up_process().
No problems were found by this search, though it is conceivable that
someone could have put the sleep and wakeup in two different files.

An effort was made to ask several upstream maintainers if they were aware
of people relying on wake_up_process() to wake up usleep_range(). No
maintainers were aware of that but they were aware of many people relying
on usleep_range() never returning before the minimum.

Reported-by: Tao Huang <huangtao@rock-chips.com>
Signed-off-by: Douglas Anderson <dianders@chromium.org>
Cc: heiko@sntech.de
Cc: broonie@kernel.org
Cc: briannorris@chromium.org
Cc: Andreas Mohr <andi@lisas.de>
Cc: linux-rockchip@lists.infradead.org
Cc: tony.xie@rock-chips.com
Cc: John Stultz <john.stultz@linaro.org>
Cc: djkurtz@chromium.org
Cc: linux@roeck-us.net
Cc: tskd08@gmail.com
Link: http://lkml.kernel.org/r/1477065531-30342-1-git-send-email-dianders@chromium.org
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
kernel/time/timer.c

index 2d47980a1bc423df44e8e1324537f4e185b338ae..12681c9a76834b9b74a824aab7103e04cbb6cc57 100644 (file)
@@ -1896,16 +1896,6 @@ unsigned long msleep_interruptible(unsigned int msecs)
 
 EXPORT_SYMBOL(msleep_interruptible);
 
-static void __sched do_usleep_range(unsigned long min, unsigned long max)
-{
-       ktime_t kmin;
-       u64 delta;
-
-       kmin = ktime_set(0, min * NSEC_PER_USEC);
-       delta = (u64)(max - min) * NSEC_PER_USEC;
-       schedule_hrtimeout_range(&kmin, delta, HRTIMER_MODE_REL);
-}
-
 /**
  * usleep_range - Sleep for an approximate time
  * @min: Minimum time in usecs to sleep
@@ -1919,7 +1909,14 @@ static void __sched do_usleep_range(unsigned long min, unsigned long max)
  */
 void __sched usleep_range(unsigned long min, unsigned long max)
 {
-       __set_current_state(TASK_UNINTERRUPTIBLE);
-       do_usleep_range(min, max);
+       ktime_t exp = ktime_add_us(ktime_get(), min);
+       u64 delta = (u64)(max - min) * NSEC_PER_USEC;
+
+       for (;;) {
+               __set_current_state(TASK_UNINTERRUPTIBLE);
+               /* Do not return before the requested sleep time has elapsed */
+               if (!schedule_hrtimeout_range(&exp, delta, HRTIMER_MODE_ABS))
+                       break;
+       }
 }
 EXPORT_SYMBOL(usleep_range);