]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
hrtimer: Push reprogramming timers into the interrupt return path
authorPeter Zijlstra <peterz@infradead.org>
Tue, 24 Feb 2026 16:38:18 +0000 (17:38 +0100)
committerPeter Zijlstra <peterz@infradead.org>
Fri, 27 Feb 2026 15:40:14 +0000 (16:40 +0100)
Currently hrtimer_interrupt() runs expired timers, which can re-arm
themselves, after which it computes the next expiration time and
re-programs the hardware.

However, things like HRTICK, a highres timer driving preemption, cannot
re-arm itself at the point of running, since the next task has not been
determined yet. The schedule() in the interrupt return path will switch to
the next task, which then causes a new hrtimer to be programmed.

This then results in reprogramming the hardware at least twice, once after
running the timers, and once upon selecting the new task.

Notably, *both* events happen in the interrupt.

By pushing the hrtimer reprogram all the way into the interrupt return
path, it runs after schedule() picks the new task and the double reprogram
can be avoided.

Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Signed-off-by: Thomas Gleixner <tglx@kernel.org>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Link: https://patch.msgid.link/20260224163431.273488269@kernel.org
include/asm-generic/thread_info_tif.h
include/linux/hrtimer_rearm.h
kernel/time/Kconfig
kernel/time/hrtimer.c

index da1610a78f921272abc3d59791e8d2c568abc10f..528e6fc7efe9eb892adc51a84535718f5d0c97fc 100644 (file)
 #define _TIF_PATCH_PENDING     BIT(TIF_PATCH_PENDING)
 
 #ifdef HAVE_TIF_RESTORE_SIGMASK
-# define TIF_RESTORE_SIGMASK   10      // Restore signal mask in do_signal() */
+# define TIF_RESTORE_SIGMASK   10      // Restore signal mask in do_signal()
 # define _TIF_RESTORE_SIGMASK  BIT(TIF_RESTORE_SIGMASK)
 #endif
 
 #define TIF_RSEQ               11      // Run RSEQ fast path
 #define _TIF_RSEQ              BIT(TIF_RSEQ)
 
+#define TIF_HRTIMER_REARM      12       // re-arm the timer
+#define _TIF_HRTIMER_REARM     BIT(TIF_HRTIMER_REARM)
+
 #endif /* _ASM_GENERIC_THREAD_INFO_TIF_H_ */
index 6293076c03a69c1858146aa61d79525b31d7c3a4..a6f2e5d5e1c7df932cfd3f12170aff8434c224f6 100644 (file)
@@ -3,12 +3,74 @@
 #define _LINUX_HRTIMER_REARM_H
 
 #ifdef CONFIG_HRTIMER_REARM_DEFERRED
-static __always_inline void __hrtimer_rearm_deferred(void) { }
-static __always_inline void hrtimer_rearm_deferred(void) { }
-static __always_inline void hrtimer_rearm_deferred_tif(unsigned long tif_work) { }
+#include <linux/thread_info.h>
+
+void __hrtimer_rearm_deferred(void);
+
+/*
+ * This is purely CPU local, so check the TIF bit first to avoid the overhead of
+ * the atomic test_and_clear_bit() operation for the common case where the bit
+ * is not set.
+ */
+static __always_inline bool hrtimer_test_and_clear_rearm_deferred_tif(unsigned long tif_work)
+{
+       lockdep_assert_irqs_disabled();
+
+       if (unlikely(tif_work & _TIF_HRTIMER_REARM)) {
+               clear_thread_flag(TIF_HRTIMER_REARM);
+               return true;
+       }
+       return false;
+}
+
+#define TIF_REARM_MASK (_TIF_NEED_RESCHED | _TIF_NEED_RESCHED_LAZY | _TIF_HRTIMER_REARM)
+
+/* Invoked from the exit to user before invoking exit_to_user_mode_loop() */
 static __always_inline bool
-hrtimer_rearm_deferred_user_irq(unsigned long *tif_work, const unsigned long tif_mask) { return false; }
-static __always_inline bool hrtimer_test_and_clear_rearm_deferred(void) { return false; }
+hrtimer_rearm_deferred_user_irq(unsigned long *tif_work, const unsigned long tif_mask)
+{
+       /* Help the compiler to optimize the function out for syscall returns */
+       if (!(tif_mask & _TIF_HRTIMER_REARM))
+               return false;
+       /*
+        * Rearm the timer if none of the resched flags is set before going into
+        * the loop which re-enables interrupts.
+        */
+       if (unlikely((*tif_work & TIF_REARM_MASK) == _TIF_HRTIMER_REARM)) {
+               clear_thread_flag(TIF_HRTIMER_REARM);
+               __hrtimer_rearm_deferred();
+               /* Don't go into the loop if HRTIMER_REARM was the only flag */
+               *tif_work &= ~TIF_HRTIMER_REARM;
+               return !*tif_work;
+       }
+       return false;
+}
+
+/* Invoked from the time slice extension decision function */
+static __always_inline void hrtimer_rearm_deferred_tif(unsigned long tif_work)
+{
+       if (hrtimer_test_and_clear_rearm_deferred_tif(tif_work))
+               __hrtimer_rearm_deferred();
+}
+
+/*
+ * This is to be called on all irqentry_exit() paths that will enable
+ * interrupts.
+ */
+static __always_inline void hrtimer_rearm_deferred(void)
+{
+       hrtimer_rearm_deferred_tif(read_thread_flags());
+}
+
+/*
+ * Invoked from the scheduler on entry to __schedule() so it can defer
+ * rearming after the load balancing callbacks which might change hrtick.
+ */
+static __always_inline bool hrtimer_test_and_clear_rearm_deferred(void)
+{
+       return hrtimer_test_and_clear_rearm_deferred_tif(read_thread_flags());
+}
+
 #else  /* CONFIG_HRTIMER_REARM_DEFERRED */
 static __always_inline void __hrtimer_rearm_deferred(void) { }
 static __always_inline void hrtimer_rearm_deferred(void) { }
index b95bfee3f592a693b3a2529ffbf0fbd17345df1a..6d6aace0a69333c1c72fa064302169177d5d9702 100644 (file)
@@ -60,7 +60,9 @@ config GENERIC_CMOS_UPDATE
 
 # Deferred rearming of the hrtimer interrupt
 config HRTIMER_REARM_DEFERRED
-       def_bool n
+       def_bool y
+       depends on GENERIC_ENTRY && HAVE_GENERIC_TIF_BITS
+       depends on HIGH_RES_TIMERS && SCHED_HRTICK
 
 # Select to handle posix CPU timers from task_work
 # and not from the timer interrupt context
index 6f05d2569286ab5661f62d7e0d56fb1f7fcfb9b9..2e5f0e292efb34f8a3449854adb94b6024835d8e 100644 (file)
@@ -1939,10 +1939,9 @@ static __latent_entropy void hrtimer_run_softirq(void)
  * Very similar to hrtimer_force_reprogram(), except it deals with
  * deferred_rearm and hang_detected.
  */
-static void hrtimer_rearm(struct hrtimer_cpu_base *cpu_base, ktime_t now)
+static void hrtimer_rearm(struct hrtimer_cpu_base *cpu_base, ktime_t now,
+                         ktime_t expires_next, bool deferred)
 {
-       ktime_t expires_next = hrtimer_update_next_event(cpu_base);
-
        cpu_base->expires_next = expires_next;
        cpu_base->deferred_rearm = false;
 
@@ -1954,9 +1953,37 @@ static void hrtimer_rearm(struct hrtimer_cpu_base *cpu_base, ktime_t now)
                expires_next = ktime_add_ns(now, 100 * NSEC_PER_MSEC);
                cpu_base->hang_detected = false;
        }
-       hrtimer_rearm_event(expires_next, false);
+       hrtimer_rearm_event(expires_next, deferred);
+}
+
+#ifdef CONFIG_HRTIMER_REARM_DEFERRED
+void __hrtimer_rearm_deferred(void)
+{
+       struct hrtimer_cpu_base *cpu_base = this_cpu_ptr(&hrtimer_bases);
+       ktime_t now, expires_next;
+
+       if (!cpu_base->deferred_rearm)
+               return;
+
+       guard(raw_spinlock)(&cpu_base->lock);
+       now = hrtimer_update_base(cpu_base);
+       expires_next = hrtimer_update_next_event(cpu_base);
+       hrtimer_rearm(cpu_base, now, expires_next, true);
 }
 
+static __always_inline void
+hrtimer_interrupt_rearm(struct hrtimer_cpu_base *cpu_base, ktime_t now, ktime_t expires_next)
+{
+       set_thread_flag(TIF_HRTIMER_REARM);
+}
+#else  /* CONFIG_HRTIMER_REARM_DEFERRED */
+static __always_inline void
+hrtimer_interrupt_rearm(struct hrtimer_cpu_base *cpu_base, ktime_t now, ktime_t expires_next)
+{
+       hrtimer_rearm(cpu_base, now, expires_next, false);
+}
+#endif  /* !CONFIG_HRTIMER_REARM_DEFERRED */
+
 /*
  * High resolution timer interrupt
  * Called with interrupts disabled
@@ -2014,9 +2041,10 @@ retry:
                cpu_base->hang_detected = true;
        }
 
-       hrtimer_rearm(cpu_base, now);
+       hrtimer_interrupt_rearm(cpu_base, now, expires_next);
        raw_spin_unlock_irqrestore(&cpu_base->lock, flags);
 }
+
 #endif /* !CONFIG_HIGH_RES_TIMERS */
 
 /*