timers: Clear timer_base::must_forward_clk with timer_base::lock held
authorGaurav Kohli <gkohli@codeaurora.org>
Thu, 2 Aug 2018 08:51:03 +0000 (14:21 +0530)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 19 Sep 2018 20:43:39 +0000 (22:43 +0200)
commitcb71229f64835c6c570c7697884054e589630bfd
tree4ed43730c7864c5e0918ee373cb1c90546e1ce4f
parentd1060bfcdc72353a48530674d344466eea7a0946
timers: Clear timer_base::must_forward_clk with timer_base::lock held

[ Upstream commit 363e934d8811d799c88faffc5bfca782fd728334 ]

timer_base::must_forward_clock is indicating that the base clock might be
stale due to a long idle sleep.

The forwarding of the base clock takes place in the timer softirq or when a
timer is enqueued to a base which is idle. If the enqueue of timer to an
idle base happens from a remote CPU, then the following race can happen:

  CPU0 CPU1
  run_timer_softirq mod_timer

base = lock_timer_base(timer);
  base->must_forward_clk = false
if (base->must_forward_clk)
            forward(base); -> skipped

enqueue_timer(base, timer, idx);
-> idx is calculated high due to
   stale base
unlock_timer_base(timer);
  base = lock_timer_base(timer);
  forward(base);

The root cause is that timer_base::must_forward_clk is cleared outside the
timer_base::lock held region, so the remote queuing CPU observes it as
cleared, but the base clock is still stale. This can cause large
granularity values for timers, i.e. the accuracy of the expiry time
suffers.

Prevent this by clearing the flag with timer_base::lock held, so that the
forwarding takes place before the cleared flag is observable by a remote
CPU.

Signed-off-by: Gaurav Kohli <gkohli@codeaurora.org>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Cc: john.stultz@linaro.org
Cc: sboyd@kernel.org
Cc: linux-arm-msm@vger.kernel.org
Link: https://lkml.kernel.org/r/1533199863-22748-1-git-send-email-gkohli@codeaurora.org
Signed-off-by: Sasha Levin <alexander.levin@microsoft.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
kernel/time/timer.c