[PATCH v2 19/37] hrtimer: Unify handling of hrtimer remove

Anna-Maria Gleixner
2017-10-22T21:47:40Z

[-- Attachment #1: hrtimer--Unify-handling-of-hrtimer-remove.patch --]
[-- Type: text/plain, Size: 3094 bytes --]

When the first hrtimer on the current CPU is removed,
hrtimer_force_reprogram() is invoked but only when
CONFIG_HIGH_RES_TIMERS=y and hrtimer_cpu_base.hres_active is set.

hrtimer_force_reprogram() updates hrtimer_cpu_base.expires_next and
reprograms the clock event device. When CONFIG_HIGH_RES_TIMERS=y and
hrtimer_cpu_base.hres_active is set, a pointless hrtimer interrupt can be
prevented.

hrtimer_check_target() makes the 'can remote enqueue' decision. As soon as
hrtimer_check_target() is unconditionally available and
hrtimer_cpu_base.expires_next is updated by hrtimer_reprogram(),
hrtimer_force_reprogram() needs to be available unconditionally as well to
prevent the following scenario with CONFIG_HIGH_RES_TIMERS=n:

- the first hrtimer on this CPU is removed and hrtimer_force_reprogram() is
not executed

- CPU goes idle (next timer is calculated and hrtimers are taken into
account)

- a hrtimer is enqueued remote on the idle CPU: hrtimer_check_target()
compares expiry value and hrtimer_cpu_base.expires_next. The expiry value
is after expires_next, so the hrtimer is enqueued. This timer will fire
late, if it expires before the effective first hrtimer on this CPU and
the comparison was with an outdated expires_next value.

To prevent this scenario, make hrtimer_force_reprogram() unconditional
except the effective reprogramming part, which gets eliminated by the
compiler in the CONFIG_HIGH_RES_TIMERS=n case.

Signed-off-by: Anna-Maria Gleixner <anna-maria@linutronix.de>
---
kernel/time/hrtimer.c | 10 ++++------
1 file changed, 4 insertions(+), 6 deletions(-)

--- a/kernel/time/hrtimer.c
+++ b/kernel/time/hrtimer.c
@@ -530,9 +530,6 @@ hrtimer_force_reprogram(struct hrtimer_c
{
ktime_t expires_next;

- if (!__hrtimer_hres_active(cpu_base))
- return;
-
expires_next = __hrtimer_get_next_event(cpu_base);

if (skip_equal && expires_next == cpu_base->expires_next)
@@ -541,6 +538,9 @@ hrtimer_force_reprogram(struct hrtimer_c
cpu_base->expires_next = expires_next;

/*
+ * If hres is not active, hardware does not have to be
+ * reprogrammed yet.
+ *
* If a hang was detected in the last timer interrupt then we
* leave the hang delay active in the hardware. We want the
* system to make progress. That also prevents the following
@@ -554,7 +554,7 @@ hrtimer_force_reprogram(struct hrtimer_c
* set. So we'd effectivly block all timers until the T2 event
* fires.
*/
- if (cpu_base->hang_detected)
+ if (!__hrtimer_hres_active(cpu_base) || cpu_base->hang_detected)
return;

tick_program_event(cpu_base->expires_next, 1);
@@ -855,7 +855,6 @@ static void __remove_hrtimer(struct hrti
if (!timerqueue_del(&base->active, &timer->node))
cpu_base->active_bases &= ~(1 << base->index);

-#ifdef CONFIG_HIGH_RES_TIMERS
/*
* Note: If reprogram is false we do not update
* cpu_base->next_timer. This happens when we remove the first
@@ -866,7 +865,6 @@ static void __remove_hrtimer(struct hrti
*/
if (reprogram && timer == cpu_base->next_timer)
hrtimer_force_reprogram(cpu_base, 1);
-#endif
}

/*