timers: Fix nextevt calculation when no timers are pending
authorAnna-Maria Behnsen <anna-maria@linutronix.de>
Fri, 1 Dec 2023 09:26:34 +0000 (10:26 +0100)
committerThomas Gleixner <tglx@linutronix.de>
Wed, 20 Dec 2023 15:49:39 +0000 (16:49 +0100)
When no timer is queued into an empty timer base, the next_expiry will not
be updated. It was originally calculated as

  base->clk + NEXT_TIMER_MAX_DELTA

When the timer base stays empty long enough (> NEXT_TIMER_MAX_DELTA), the
next_expiry value of the empty base suggests that there is a timer pending
soon. This might be more a kind of a theoretical problem, but the fix
doesn't hurt.

Use only base->next_expiry value as nextevt when timers are
pending. Otherwise nextevt will be jiffies + NEXT_TIMER_MAX_DELTA. As all
information is in place, update base->next_expiry value of the empty timer
base as well.

Signed-off-by: Anna-Maria Behnsen <anna-maria@linutronix.de>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Reviewed-by: Frederic Weisbecker <frederic@kernel.org>
Link: https://lore.kernel.org/r/20231201092654.34614-13-anna-maria@linutronix.de
kernel/time/timer.c

index cf51655add64d30b6ab0d5d6df57d283ceec04be..352b161113cda6856a6fac08e9cc5eb01818a056 100644 (file)
@@ -1922,8 +1922,8 @@ static u64 cmp_next_hrtimer_event(u64 basem, u64 expires)
 u64 get_next_timer_interrupt(unsigned long basej, u64 basem)
 {
        struct timer_base *base = this_cpu_ptr(&timer_bases[BASE_STD]);
+       unsigned long nextevt = basej + NEXT_TIMER_MAX_DELTA;
        u64 expires = KTIME_MAX;
-       unsigned long nextevt;
        bool was_idle;
 
        /*
@@ -1936,7 +1936,6 @@ u64 get_next_timer_interrupt(unsigned long basej, u64 basem)
        raw_spin_lock(&base->lock);
        if (base->next_expiry_recalc)
                next_expiry_recalc(base);
-       nextevt = base->next_expiry;
 
        /*
         * We have a fresh next event. Check whether we can forward the
@@ -1945,10 +1944,20 @@ u64 get_next_timer_interrupt(unsigned long basej, u64 basem)
        __forward_timer_base(base, basej);
 
        if (base->timers_pending) {
+               nextevt = base->next_expiry;
+
                /* If we missed a tick already, force 0 delta */
                if (time_before(nextevt, basej))
                        nextevt = basej;
                expires = basem + (u64)(nextevt - basej) * TICK_NSEC;
+       } else {
+               /*
+                * Move next_expiry for the empty base into the future to
+                * prevent a unnecessary raise of the timer softirq when the
+                * next_expiry value will be reached even if there is no timer
+                * pending.
+                */
+               base->next_expiry = nextevt;
        }
 
        /*