hrtimers: Prevent hrtimer interrupt starvation
Subsystems (11)
Thomas Gleixner
maintainercover hrtimers: Prevent hrtimer interrupt starvation
Calvin reported an odd NMI watchdog lockup which claims that the CPU locked up in user space:
https://lore.kernel.org/lkml/[email protected]/
He provided a reproducer, which sets up a timerfd based timer and then rearms it in a loop with an absolute expiry time of 1ns.
As the expiry time is in the past, the timer ends up as the first expiring timer in the per CPU hrtimer base and the clockevent device is programmed with the minimum delta value. If the machine is fast enough, this ends up in a endless loop of programming the delta value to the minimum value defined by the clock event device, before the timer interrupt can fire, which starves the interrupt and consequently triggers the lockup detector because the hrtimer callback of the lockup mechanism is never invoked.
The first patch in the series changes the clockevent set next event mechanism to prevent reprogramming of the clockevent device when the minimum delta value was programmed unless the new delta is larger than that. It's a less convoluted variant of the patch which was posted in the above linked thread and was confirmed to prevent the starvation problem.
But that's only to be considered the last resort because it results in an insane amount of avoidable hrtimer interrupts.
The problem of user controlled timers is that the input value is only sanity checked vs. validity of the provided timespec and clamped to be in the maximum allowable range. But for performance reasons for in kernel usage there is no check whether a to be armed timer might have been expired already at enqueue time.
The rest of the series addresses this by providing a separate interface to arm user controlled timers. This works the same way as the existing hrtimer_start_range_ns(), but in case that the timer ends up as the first timer in the clock base after enqueue it provides additional checks:
- Whether the timer becomes the first expiring timer in the CPU base.
If not the timer is considered to expire in the future as there is already an earlier event programmed.
- Whether the timer has expired already by comparing the expiry value against current time.
If it is expired, the timer is removed from the clock base and the function returns false, so that the caller can handle it. That's required because the function cannot invoke the callback as that might need to acquire a lock which is held by the caller.
This function is then used for the user controlled timer arming interfaces mainly by converting hrtimer sleeper over to it. That affects a few in kernel users too, but the overhead is minimal in that case and it spares a tedious whack the mole game all over the tree.
The other usage sites in posixtimers, alarmtimers and timerfd are converted as well, which should cover the vast majority of user space controllable timers as far as my investigation goes.
The series applies against Linux tree and is also available from git:
git://git.kernel.org/pub/scm/linux/kernel/git/tglx/devel.git hrtimer-exp-v1
There needs to be some discussion about the scope of backporting. The first patch preventing the stall is obviously a backport candidate. The remaining series can be obviously argued about, but in my opinion it should be backported as well as it prevents stupid or malicious user space from generating tons of pointless timer interrupts.
Thanks,
tglx
--- drivers/power/supply/charger-manager.c | 12 +- fs/timerfd.c | 115 +++++++++++++++----------- include/linux/alarmtimer.h | 9 +- include/linux/clockchips.h | 2 include/linux/hrtimer.h | 20 +++- include/trace/events/timer.h | 13 +++ kernel/time/alarmtimer.c | 70 +++++++--------- kernel/time/clockevents.c | 23 +++-- kernel/time/hrtimer.c | 142 +++++++++++++++++++++++++++++---- kernel/time/posix-cpu-timers.c | 18 ++-- kernel/time/posix-timers.c | 35 +++++--- kernel/time/posix-timers.h | 4 kernel/time/tick-common.c | 1 kernel/time/tick-sched.c | 1 net/netfilter/xt_IDLETIMER.c | 24 ++++- 15 files changed, 341 insertions(+), 148 deletions(-)
Thomas Gleixner
maintainerPATCH 1/12 clockevents: Prevent timer interrupt starvation
From: Thomas Gleixner <[email protected]>
Calvin reported an odd NMI watchdog lockup which claims that the CPU locked up in user space. He provided a reproducer, which sets up a timerfd based timer and then rearms it in a loop with an absolute expiry time of 1ns.
As the expiry time is in the past, the timer ends up as the first expiring timer in the per CPU hrtimer base and the clockevent device is programmed with the minimum delta value. If the machine is fast enough, this ends up in a endless loop of programming the delta value to the minimum value defined by the clock event device, before the timer interrupt can fire, which starves the interrupt and consequently triggers the lockup detector because the hrtimer callback of the lockup mechanism is never invoked.
As a first step to prevent this, avoid reprogramming the clock event device when:
- a forced minimum delta event is pending
- the new expiry delta is less then or equal to the minimum delta
Thanks to Calvin for providing the reproducer and to Borislav for testing and providing data from his Zen5 machine.
The problem is not limited to Zen5, but depending on the underlying clock event device (e.g. TSC deadline timer on Intel) and the CPU speed not necessarily observable.
This change serves only as the last resort and further changes will be made to prevent this scenario earlier in the call chain as far as possible.
---
V2: Simplified the clockevents code - Peter
--- include/linux/clockchips.h | 2 ++ kernel/time/clockevents.c | 23 +++++++++++++++-------- kernel/time/hrtimer.c | 1 + kernel/time/tick-common.c | 1 + kernel/time/tick-sched.c | 1 + 5 files changed, 20 insertions(+), 8 deletions(-)
include/linux/clockchips.h +2 -0
@@ -80,6 +80,7 @@ enum clock_event_state {* @shift: nanoseconds to cycles divisor (power of two) * @state_use_accessors:current state of the device, assigned by the core code * @features: features +* @next_event_forced: True if the last programming was a forced event * @retries: number of forced programming retries * @set_state_periodic: switch state to periodic * @set_state_oneshot: switch state to oneshot
@@ -108,6 +109,7 @@ struct clock_event_device {u32 shift;enum clock_event_state state_use_accessors;unsigned int features; +unsigned int next_event_forced;unsigned long retries;int (*set_state_periodic)(struct clock_event_device *);
kernel/time/clockevents.c +15 -8
@@ -172,6 +172,7 @@ void clockevents_shutdown(struct clock_e {clockevents_switch_state (dev,CLOCK_EVT_STATE_SHUTDOWN ); dev->next_event =KTIME_MAX ; + dev->next_event_forced =0 ; }/**
@@ -305,7 +306,6 @@ int clockevents_program_event(struct clo {unsigned long long clc;int64_t delta; -int rc;if (WARN_ON_ONCE (expires <0 ))return -ETIME ;
@@ -324,16 +324,23 @@ int clockevents_program_event(struct cloreturn dev->set_next_ktime (expires, dev); delta =ktime_to_ns (ktime_sub (expires,ktime_get ())); -if (delta <=0 ) -return force ?clockevents_program_min_delta (dev) : -ETIME ; - delta =min (delta, (int64_t ) dev->max_delta_ns); - delta =max (delta, (int64_t ) dev->min_delta_ns); +if (delta > (int64_t )dev->min_delta_ns) { + delta =min (delta, (int64_t ) dev->max_delta_ns); + clc = ((unsigned long long ) delta * dev->mult) >> dev->shift; +if (!dev->set_next_event ((unsigned long ) clc, dev)) +return 0 ; + } - clc = ((unsigned long long ) delta * dev->mult) >> dev->shift; - rc = dev->set_next_event ((unsigned long ) clc, dev); +if (dev->next_event_forced) +return 0 ; -return (rc && force) ?clockevents_program_min_delta (dev) : rc; +if (dev->set_next_event (dev->min_delta_ticks, dev)) { +if (!force ||clockevents_program_min_delta (dev)) +return -ETIME ; + } + dev->next_event_forced =1 ; +return 0 ; }/*
kernel/time/hrtimer.c +1 -0
@@ -1888,6 +1888,7 @@ void hrtimer_interrupt(struct clock_evenBUG_ON (!cpu_base->hres_active); cpu_base->nr_events++; dev->next_event =KTIME_MAX ; + dev->next_event_forced =0 ;raw_spin_lock_irqsave (&cpu_base->lock, flags); entry_time = now =hrtimer_update_base (cpu_base);
kernel/time/tick-common.c +1 -0
@@ -110,6 +110,7 @@ void tick_handle_periodic(struct clock_eint cpu =smp_processor_id ();ktime_t next = dev->next_event; + dev->next_event_forced =0 ;tick_periodic (cpu);/*
kernel/time/tick-sched.c +1 -0
@@ -1513,6 +1513,7 @@ static void tick_nohz_lowres_handler(strstruct tick_sched *ts =this_cpu_ptr (&tick_cpu_sched); dev->next_event =KTIME_MAX ; + dev->next_event_forced =0 ;if (likely (tick_nohz_handler (&ts->sched_timer) ==HRTIMER_RESTART ))tick_program_event (hrtimer_get_expires (&ts->sched_timer),1 );
Re: [patch 01/12] clockevents: Prevent timer interrupt starvation
Quoted 28 lines · Thomas Gleixner
kernel/time/clockevents.c +6
-7@@ -324,16 +324,23 @@ int clockevents_program_event(struct cloreturn dev->set_next_ktime (expires, dev); delta =ktime_to_ns (ktime_sub (expires,ktime_get ())); -if (delta <=0 ) -return force ?clockevents_program_min_delta (dev) : -ETIME ; - delta =min (delta, (int64_t ) dev->max_delta_ns); - delta =max (delta, (int64_t ) dev->min_delta_ns); - clc = ((unsigned long long ) delta * dev->mult) >> dev->shift; - rc = dev->set_next_event ((unsigned long ) clc, dev); -return (rc && force) ?clockevents_program_min_delta (dev) : rc; }@@ -324,16 +324,23 @@ int clockevents_program_event(struct cloreturn dev->set_next_ktime (expires, dev); delta =ktime_to_ns (ktime_sub (expires,ktime_get ())); +if (delta > (int64_t )dev->min_delta_ns) { + delta =min (delta, (int64_t ) dev->max_delta_ns); + clc = ((unsigned long long ) delta * dev->mult) >> dev->shift; +if (!dev->set_next_event ((unsigned long ) clc, dev)) +return 0 ; + }
kernel/time/clockevents.c int clockevents_program_event(struct clo · line 344
Quoted 10 lines
kernel/time/clockevents.c +8
-0+if (dev->next_event_forced) +return 0 ; +if (dev->set_next_event (dev->min_delta_ticks, dev)) { +if (!force ||clockevents_program_min_delta (dev)) +return -ETIME ; + } + dev->next_event_forced =1 ; +return 0 ; }
Looking at the implementation of clockevents_program_min_delta() doing that dev->set_next_event(dev->min_delta_ticks,) right before it seems a bit daft.
But yes, this is effectively also what the old code did.
The only thing that seems to be different, is that the old code would return the ->set_next_event() error code, rather than 0 in the !force case.
Thomas Gleixner
maintainerRe: [patch 01/12] clockevents: Prevent timer interrupt starvation
Quoted 28 lines · Peter Zijlstra
Quoted 12 lines · Thomas Gleixner
kernel/time/clockevents.c +6
-0@@ -324,16 +324,23 @@ int clockevents_program_event(struct cloreturn dev->set_next_ktime (expires, dev); delta =ktime_to_ns (ktime_sub (expires,ktime_get ())); +if (delta > (int64_t )dev->min_delta_ns) { + delta =min (delta, (int64_t ) dev->max_delta_ns); + clc = ((unsigned long long ) delta * dev->mult) >> dev->shift; +if (!dev->set_next_event ((unsigned long ) clc, dev)) +return 0 ; + }kernel/time/clockevents.c int clockevents_program_event(struct clo · line 344
Quoted 10 lines
kernel/time/clockevents.c +8
-0+if (dev->next_event_forced) +return 0 ; +if (dev->set_next_event (dev->min_delta_ticks, dev)) { +if (!force ||clockevents_program_min_delta (dev)) +return -ETIME ; + } + dev->next_event_forced =1 ; +return 0 ; }Looking at the implementation of
clockevents_program_min_delta()doing thatdev->set_next_event(dev->min_delta_ticks,)right before it seems a bit daft.
Quoted 1 line
But yes, this is effectively also what the old code did.
yes. I looked at that and didn't come up with a good plan.
Quoted 3 lines
The only thing that seems to be different, is that the old code would return the ->set_next_event() error code, rather than 0 in the !force case.
You mean when dev->next_event_forced is set and the set_event() callback above failed?
Thanks,
tglx
Re: [patch 01/12] clockevents: Prevent timer interrupt starvation
Quoted 31 lines · Thomas Gleixner
Quoted 28 lines · Peter Zijlstra
Quoted 12 lines · Thomas Gleixner
kernel/time/clockevents.c +6
-0@@ -324,16 +324,23 @@ int clockevents_program_event(struct cloreturn dev->set_next_ktime (expires, dev); delta =ktime_to_ns (ktime_sub (expires,ktime_get ())); +if (delta > (int64_t )dev->min_delta_ns) { + delta =min (delta, (int64_t ) dev->max_delta_ns); + clc = ((unsigned long long ) delta * dev->mult) >> dev->shift; +if (!dev->set_next_event ((unsigned long ) clc, dev)) +return 0 ; + }kernel/time/clockevents.c int clockevents_program_event(struct clo · line 344
Quoted 10 lines
kernel/time/clockevents.c +8
-0+if (dev->next_event_forced) +return 0 ; +if (dev->set_next_event (dev->min_delta_ticks, dev)) { +if (!force ||clockevents_program_min_delta (dev)) +return -ETIME ; + } + dev->next_event_forced =1 ; +return 0 ; }Looking at the implementation of
clockevents_program_min_delta()doing thatdev->set_next_event(dev->min_delta_ticks,)right before it seems a bit daft.Quoted 1 line
But yes, this is effectively also what the old code did.
Quoted 8 lines
yes. I looked at that and didn't come up with a good plan.
Quoted 3 lines
The only thing that seems to be different, is that the old code would return the ->set_next_event() error code, rather than 0 in the !force case.
You mean when
dev->next_event_forcedis set and theset_event()callback above failed?
next_event_foced = 0;
force = 0;
Then the old code would return rc (return value of ->set_next_event), while the new code will return -ETIME.
(not 0 like I said).
I suppose ->set_next_event() will only ever fail with -ETIME?
Thomas Gleixner
maintainerRe: [patch 01/12] clockevents: Prevent timer interrupt starvation
Quoted 8 lines · Peter Zijlstra
Quoted 6 lines · Thomas Gleixner
Quoted 3 lines
The only thing that seems to be different, is that the old code would return the ->set_next_event() error code, rather than 0 in the !force case.
You mean when
dev->next_event_forcedis set and theset_event()callback above failed?
Quoted 7 lines
next_event_foced= 0;
force = 0;Then the old code would return rc (return value of ->set_next_event), while the new code will return
-ETIME.(not 0 like I said).
Ah. Now it makes sense :)
Quoted 1 line
I suppose ->set_next_event() will only ever fail with
-ETIME?
Yes.
Frederic Weisbecker
maintainerRe: [patch 01/12] clockevents: Prevent timer interrupt starvation
In reply to Thomas GleixnerQuoted 37 lines · Thomas Gleixner
From: Thomas Gleixner <[email protected]>
Calvin reported an odd NMI watchdog lockup which claims that the CPU locked up in user space. He provided a reproducer, which sets up a timerfd based timer and then rearms it in a loop with an absolute expiry time of 1ns.
As the expiry time is in the past, the timer ends up as the first expiring timer in the per CPU hrtimer base and the clockevent device is programmed with the minimum delta value. If the machine is fast enough, this ends up in a endless loop of programming the delta value to the minimum value defined by the clock event device, before the timer interrupt can fire, which starves the interrupt and consequently triggers the lockup detector because the hrtimer callback of the lockup mechanism is never invoked.
As a first step to prevent this, avoid reprogramming the clock event device when:
- a forced minimum delta event is pending
- the new expiry delta is less then or equal to the minimum deltaThanks to Calvin for providing the reproducer and to Borislav for testing and providing data from his Zen5 machine.
The problem is not limited to Zen5, but depending on the underlying clock event device (e.g. TSC deadline timer on Intel) and the CPU speed not necessarily observable.
This change serves only as the last resort and further changes will be made to prevent this scenario earlier in the call chain as far as possible.
Fixes d316c57ff6bf ("[PATCH] clockevents: add core functionality")Reported-by Calvin Owens <[email protected]>Signed-off-by Thomas Gleixner <[email protected]>Cc Peter Zijlstra <[email protected]>Cc Anna-Maria Behnsen <[email protected]>Cc Frederic Weisbecker <[email protected]>Cc Ingo Molnar <[email protected]>
Quoted 2 lines
---
V2: Simplified the clockevents code - Peter
Isn't it possible to rely on dev->next_event instead? In the above scenario, subsequent 0 delta would not reprogram if dev->next_event is already below the new call to ktime_get() ?
Thanks.
Signature · 2 lines
Frederic Weisbecker SUSE Labs
Thomas Gleixner
maintainerRe: [patch 01/12] clockevents: Prevent timer interrupt starvation
Quoted 41 lines · Frederic Weisbecker
Quoted 37 lines · Thomas Gleixner
From: Thomas Gleixner <[email protected]>
Calvin reported an odd NMI watchdog lockup which claims that the CPU locked up in user space. He provided a reproducer, which sets up a timerfd based timer and then rearms it in a loop with an absolute expiry time of 1ns.
As the expiry time is in the past, the timer ends up as the first expiring timer in the per CPU hrtimer base and the clockevent device is programmed with the minimum delta value. If the machine is fast enough, this ends up in a endless loop of programming the delta value to the minimum value defined by the clock event device, before the timer interrupt can fire, which starves the interrupt and consequently triggers the lockup detector because the hrtimer callback of the lockup mechanism is never invoked.
As a first step to prevent this, avoid reprogramming the clock event device when:
- a forced minimum delta event is pending
- the new expiry delta is less then or equal to the minimum deltaThanks to Calvin for providing the reproducer and to Borislav for testing and providing data from his Zen5 machine.
The problem is not limited to Zen5, but depending on the underlying clock event device (e.g. TSC deadline timer on Intel) and the CPU speed not necessarily observable.
This change serves only as the last resort and further changes will be made to prevent this scenario earlier in the call chain as far as possible.
Fixes d316c57ff6bf ("[PATCH] clockevents: add core functionality")Reported-by Calvin Owens <[email protected]>Signed-off-by Thomas Gleixner <[email protected]>Cc Peter Zijlstra <[email protected]>Cc Anna-Maria Behnsen <[email protected]>Cc Frederic Weisbecker <[email protected]>Cc Ingo Molnar <[email protected]>Quoted 2 lines
---
V2: Simplified the clockevents code - Peter
Quoted 3 lines
Isn't it possible to rely on
dev->next_eventinstead? In the above scenario, subsequent 0 delta would not reprogram ifdev->next_eventis already below the new call toktime_get()?
It does if force is set and that is set when hrtimer calls into it:
if (delta <= 0) return force ? clockevents_program_min_delta(dev) : -ETIME;
I can't change that for various reasons.
But we always need the flag which tells us that the programming was forced in order to prevent the above scenario. And delta <= 0 is not the only way how to achieve that. You can have a delta > 0 and < min_delta anc achieve the same effect. That needs more effort on the callsite, but it's trivially doable as the systemcall to reprogram time is pretty constant.
As I had to introduce the flag and prevent the other scenraio I just consolidated everything into one code path.
Thanks,
tglx
Thomas Gleixner
maintainerRe: [patch 01/12] clockevents: Prevent timer interrupt starvation
Quoted 3 lines · Thomas Gleixner
On Tue, Apr 07 2026 at 16:00, Frederic Weisbecker wrote:
As I had to introduce the flag and prevent the other scenraio I just consolidated everything into one code path.
Just for the record. This whole -ETIME handling is restricted to HPET like devices where the hardware people decided it's a brilliant idea to build a 'equal' comparator and then followed up by making the write posted to reduce the costs of the write. The original direct write was already flawed vs. NMI/SMI, but the delayed commit into the comparator made it insanely broken.
AFAICT that's a handful devices in the zoo of clockevent IPs the kernel supports, so I'm really not too worried about the impact of this change. Any sane hardware will never hit that code path so no point for optimizing for it.
Thanks,
tglx
Thomas Gleixner
maintainerRe: [patch 01/12] clockevents: Prevent timer interrupt starvation
In reply to Thomas GleixnerCalvin!
Quoted 22 lines · Thomas Gleixner
From: Thomas Gleixner <[email protected]>
Calvin reported an odd NMI watchdog lockup which claims that the CPU locked up in user space. He provided a reproducer, which sets up a timerfd based timer and then rearms it in a loop with an absolute expiry time of 1ns.
As the expiry time is in the past, the timer ends up as the first expiring timer in the per CPU hrtimer base and the clockevent device is programmed with the minimum delta value. If the machine is fast enough, this ends up in a endless loop of programming the delta value to the minimum value defined by the clock event device, before the timer interrupt can fire, which starves the interrupt and consequently triggers the lockup detector because the hrtimer callback of the lockup mechanism is never invoked.
As a first step to prevent this, avoid reprogramming the clock event device when:
- a forced minimum delta event is pending
- the new expiry delta is less then or equal to the minimum deltaThanks to Calvin for providing the reproducer and to Borislav for testing and providing data from his Zen5 machine.
Quoted 6 lines
The problem is not limited to Zen5, but depending on the underlying clock event device (e.g. TSC deadline timer on Intel) and the CPU speed not necessarily observable.
This change serves only as the last resort and further changes will be made to prevent this scenario earlier in the call chain as far as possible.
It'd be great if you could re-test this one independently of the other changes, so we can get that on the way ASAP.
Thanks,
tglx
[tip: timers/urgent] clockevents: Prevent timer interrupt starvation
In reply to Thomas GleixnerThe following commit has been merged into the timers/urgent branch of tip:
The following commit has been merged into the timers/urgent branch of tip:
Commit-ID: b81207470caf597a518cd3706844810a7ed7cbe4 Gitweb: https://git.kernel.org/tip/b81207470caf597a518cd3706844810a7ed7cbe4 Author: Thomas Gleixner <[email protected]> AuthorDate: Tue, 07 Apr 2026 10:54:17 +02:00 Committer: Thomas Gleixner <[email protected]> CommitterDate: Tue, 07 Apr 2026 20:07:19 +02:00
clockevents: Prevent timer interrupt starvation
Calvin reported an odd NMI watchdog lockup which claims that the CPU locked up in user space. He provided a reproducer, which sets up a timerfd based timer and then rearms it in a loop with an absolute expiry time of 1ns.
As the expiry time is in the past, the timer ends up as the first expiring timer in the per CPU hrtimer base and the clockevent device is programmed with the minimum delta value. If the machine is fast enough, this ends up in a endless loop of programming the delta value to the minimum value defined by the clock event device, before the timer interrupt can fire, which starves the interrupt and consequently triggers the lockup detector because the hrtimer callback of the lockup mechanism is never invoked.
As a first step to prevent this, avoid reprogramming the clock event device when:
- a forced minimum delta event is pending
- the new expiry delta is less then or equal to the minimum delta
Thanks to Calvin for providing the reproducer and to Borislav for testing and providing data from his Zen5 machine.
The problem is not limited to Zen5, but depending on the underlying clock event device (e.g. TSC deadline timer on Intel) and the CPU speed not necessarily observable.
This change serves only as the last resort and further changes will be made to prevent this scenario earlier in the call chain as far as possible.
--- include/linux/clockchips.h | 2 ++ kernel/time/clockevents.c | 23 +++++++++++++++-------- kernel/time/hrtimer.c | 1 + kernel/time/tick-common.c | 1 + kernel/time/tick-sched.c | 1 + 5 files changed, 20 insertions(+), 8 deletions(-)
include/linux/clockchips.h +2 -0
@@ -80,6 +80,7 @@ enum clock_event_state {* @shift: nanoseconds to cycles divisor (power of two) * @state_use_accessors:current state of the device, assigned by the core code * @features: features +* @next_event_forced: True if the last programming was a forced event * @retries: number of forced programming retries * @set_state_periodic: switch state to periodic * @set_state_oneshot: switch state to oneshot
@@ -108,6 +109,7 @@ struct clock_event_device {u32 shift;enum clock_event_state state_use_accessors;unsigned int features; +unsigned int next_event_forced;unsigned long retries;int (*set_state_periodic)(struct clock_event_device *);
kernel/time/clockevents.c +15 -8
@@ -172,6 +172,7 @@ void clockevents_shutdown(struct clock_event_device *dev) {clockevents_switch_state (dev,CLOCK_EVT_STATE_SHUTDOWN ); dev->next_event =KTIME_MAX ; + dev->next_event_forced =0 ; }/**
@@ -305,7 +306,6 @@ int clockevents_program_event(struct clock_event_device *dev, ktime_t expires, {unsigned long long clc;int64_t delta; -int rc;if (WARN_ON_ONCE (expires <0 ))return -ETIME ;
@@ -324,16 +324,23 @@ int clockevents_program_event(struct clock_event_device *dev, ktime_t expires,return dev->set_next_ktime (expires, dev); delta =ktime_to_ns (ktime_sub (expires,ktime_get ())); -if (delta <=0 ) -return force ?clockevents_program_min_delta (dev) : -ETIME ; - delta =min (delta, (int64_t ) dev->max_delta_ns); - delta =max (delta, (int64_t ) dev->min_delta_ns); +if (delta > (int64_t )dev->min_delta_ns) { + delta =min (delta, (int64_t ) dev->max_delta_ns); + clc = ((unsigned long long ) delta * dev->mult) >> dev->shift; +if (!dev->set_next_event ((unsigned long ) clc, dev)) +return 0 ; + } - clc = ((unsigned long long ) delta * dev->mult) >> dev->shift; - rc = dev->set_next_event ((unsigned long ) clc, dev); +if (dev->next_event_forced) +return 0 ; -return (rc && force) ?clockevents_program_min_delta (dev) : rc; +if (dev->set_next_event (dev->min_delta_ticks, dev)) { +if (!force ||clockevents_program_min_delta (dev)) +return -ETIME ; + } + dev->next_event_forced =1 ; +return 0 ; }/*
kernel/time/hrtimer.c +1 -0
@@ -1888,6 +1888,7 @@ void hrtimer_interrupt(struct clock_event_device *dev)BUG_ON (!cpu_base->hres_active); cpu_base->nr_events++; dev->next_event =KTIME_MAX ; + dev->next_event_forced =0 ;raw_spin_lock_irqsave (&cpu_base->lock, flags); entry_time = now =hrtimer_update_base (cpu_base);
kernel/time/tick-common.c +1 -0
@@ -110,6 +110,7 @@ void tick_handle_periodic(struct clock_event_device *dev)int cpu =smp_processor_id ();ktime_t next = dev->next_event; + dev->next_event_forced =0 ;tick_periodic (cpu);/*
kernel/time/tick-sched.c +1 -0
@@ -1513,6 +1513,7 @@ static void tick_nohz_lowres_handler(struct clock_event_device *dev)struct tick_sched *ts =this_cpu_ptr (&tick_cpu_sched); dev->next_event =KTIME_MAX ; + dev->next_event_forced =0 ;if (likely (tick_nohz_handler (&ts->sched_timer) ==HRTIMER_RESTART ))tick_program_event (hrtimer_get_expires (&ts->sched_timer),1 );
Re: [patch 01/12] clockevents: Prevent timer interrupt starvation
In reply to Thomas GleixnerHi Thomas,
Quoted 14 lines · Thomas Gleixner
From: Thomas Gleixner <[email protected]>
Calvin reported an odd NMI watchdog lockup which claims that the CPU locked up in user space. He provided a reproducer, which sets up a timerfd based timer and then rearms it in a loop with an absolute expiry time of 1ns.
As the expiry time is in the past, the timer ends up as the first expiring timer in the per CPU hrtimer base and the clockevent device is programmed with the minimum delta value. If the machine is fast enough, this ends up in a endless loop of programming the delta value to the minimum value defined by the clock event device, before the timer interrupt can fire, which starves the interrupt and consequently triggers the lockup detector because the hrtimer callback of the lockup mechanism is never invoked.
Quoted 4 lines
As a first step to prevent this, avoid reprogramming the clock event device when:
- a forced minimum delta event is pending
- the new expiry delta is less then or equal to the minimum delta
with this patch now in the tip tree my QEMU/virtme-ng based machine fails to boot. The startup seems to freeze in:start_kernel() -> rest_init() -> schedule_preempt_disabled() -> schedule()
CONFIG_GENERIC_CLOCKEVENTS=yCONFIG_GENERIC_CLOCKEVENTS_BROADCAST=yCONFIG_GENERIC_CLOCKEVENTS_BROADCAST_IDLE=yCONFIG_GENERIC_CLOCKEVENTS_MIN_ADJUST=yCONFIG_HZ=1000
CPU: i5-1135G7
clock event device: lapic-deadline
The clockevent device is still reprogrammed each millisecond, presumably for the tick.
(...)
Thomas
Re: [patch 01/12] clockevents: Prevent timer interrupt starvation
Quoted 32 lines · Thomas Weißschuh
Hi Thomas,
Quoted 14 lines · Thomas Gleixner
From: Thomas Gleixner <[email protected]>
Calvin reported an odd NMI watchdog lockup which claims that the CPU locked up in user space. He provided a reproducer, which sets up a timerfd based timer and then rearms it in a loop with an absolute expiry time of 1ns.
As the expiry time is in the past, the timer ends up as the first expiring timer in the per CPU hrtimer base and the clockevent device is programmed with the minimum delta value. If the machine is fast enough, this ends up in a endless loop of programming the delta value to the minimum value defined by the clock event device, before the timer interrupt can fire, which starves the interrupt and consequently triggers the lockup detector because the hrtimer callback of the lockup mechanism is never invoked.
Quoted 4 lines
As a first step to prevent this, avoid reprogramming the clock event device when:
- a forced minimum delta event is pending
- the new expiry delta is less then or equal to the minimum deltawith this patch now in the tip tree my QEMU/virtme-ng based machine fails to boot. The startup seems to freeze in:
start_kernel()->rest_init()->schedule_preempt_disabled()->schedule()
CONFIG_GENERIC_CLOCKEVENTS=yCONFIG_GENERIC_CLOCKEVENTS_BROADCAST=yCONFIG_GENERIC_CLOCKEVENTS_BROADCAST_IDLE=yCONFIG_GENERIC_CLOCKEVENTS_MIN_ADJUST=yCONFIG_HZ=1000
Quoted 7 lines
CPU: i5-1135G7
clock event device: lapic-deadlineThe clockevent device is still reprogrammed each millisecond, presumably for the tick.
(...)
This fixes the issue for me:
kernel/time/clockevents.c +1 -1
@@ -369,7 +369,7 @@ int clockevents_program_event(struct clock_event_device *dev, ktime_t expires, bif (dev->next_event_forced)return 0 ; -if (dev->set_next_event (dev->min_delta_ticks, dev)) { +if (dev->set_next_event (dev->min_delta_ns, dev)) {if (!force ||clockevents_program_min_delta (dev))return -ETIME ; }
Thomas
Thomas Gleixner
maintainerRe: [patch 01/12] clockevents: Prevent timer interrupt starvation
kernel/time/clockevents.c int clockevents_program_event(struct clock_event_device *dev, ktime_t expires, b · line 372
Quoted 9 lines · Thomas Weißschuh
On Wed, Apr 08, 2026 at 02:41:20PM +0200, Thomas Weißschuh wrote:
kernel/time/clockevents.c +1
-1@@ -369,7 +369,7 @@ int clockevents_program_event(struct clock_event_device *dev, ktime_t expires, bif (dev->next_event_forced)return 0 ; -if (dev->set_next_event (dev->min_delta_ticks, dev)) { +if (dev->set_next_event (dev->min_delta_ns, dev)) {
That's wrong as the callback expects cycles (ticks) not nanoseconds.
I've just pushed out an updated version to tip timers/urgent which addresses a potentially related issue. Delta patch below.
Thanks,
tglx
---
kernel/time/clockevents.c +2 -0
@@ -324,6 +324,8 @@ int clockevents_program_event(struct cloreturn dev->set_next_ktime (expires, dev); delta =ktime_to_ns (ktime_sub (expires,ktime_get ())); +if (delta <=0 && !force) +return -ETIME ;if (delta > (int64_t )dev->min_delta_ns) { delta =min (delta, (int64_t ) dev->max_delta_ns);
Re: [patch 01/12] clockevents: Prevent timer interrupt starvation
In reply to Thomas GleixnerQuoted 29 lines · Thomas Gleixner
From: Thomas Gleixner <[email protected]>
Calvin reported an odd NMI watchdog lockup which claims that the CPU locked up in user space. He provided a reproducer, which sets up a timerfd based timer and then rearms it in a loop with an absolute expiry time of 1ns.
As the expiry time is in the past, the timer ends up as the first expiring timer in the per CPU hrtimer base and the clockevent device is programmed with the minimum delta value. If the machine is fast enough, this ends up in a endless loop of programming the delta value to the minimum value defined by the clock event device, before the timer interrupt can fire, which starves the interrupt and consequently triggers the lockup detector because the hrtimer callback of the lockup mechanism is never invoked.
As a first step to prevent this, avoid reprogramming the clock event device when:
- a forced minimum delta event is pending
- the new expiry delta is less then or equal to the minimum deltaThanks to Calvin for providing the reproducer and to Borislav for testing and providing data from his Zen5 machine.
The problem is not limited to Zen5, but depending on the underlying clock event device (e.g. TSC deadline timer on Intel) and the CPU speed not necessarily observable.
This change serves only as the last resort and further changes will be made to prevent this scenario earlier in the call chain as far as possible.
Quoted 8 lines
Fixes d316c57ff6bf ("[PATCH] clockevents: add core functionality")Reported-by Calvin Owens <[email protected]>Signed-off-by Thomas Gleixner <[email protected]>Cc Peter Zijlstra <[email protected]>Cc Anna-Maria Behnsen <[email protected]>Cc Frederic Weisbecker <[email protected]>Cc Ingo Molnar <[email protected]>
Signature · 2 lines
Frederic Weisbecker SUSE Labs
[tip: timers/urgent] clockevents: Prevent timer interrupt starvation
In reply to Thomas GleixnerThe following commit has been merged into the timers/urgent branch of tip:
The following commit has been merged into the timers/urgent branch of tip:
Commit-ID: 1c2eabb8805d9fd79a19de5c76d4a64c9ad3cdf4 Gitweb: https://git.kernel.org/tip/1c2eabb8805d9fd79a19de5c76d4a64c9ad3cdf4 Author: Thomas Gleixner <[email protected]> AuthorDate: Tue, 07 Apr 2026 10:54:17 +02:00 Committer: Thomas Gleixner <[email protected]> CommitterDate: Wed, 08 Apr 2026 17:06:11 +02:00
clockevents: Prevent timer interrupt starvation
Calvin reported an odd NMI watchdog lockup which claims that the CPU locked up in user space. He provided a reproducer, which sets up a timerfd based timer and then rearms it in a loop with an absolute expiry time of 1ns.
As the expiry time is in the past, the timer ends up as the first expiring timer in the per CPU hrtimer base and the clockevent device is programmed with the minimum delta value. If the machine is fast enough, this ends up in a endless loop of programming the delta value to the minimum value defined by the clock event device, before the timer interrupt can fire, which starves the interrupt and consequently triggers the lockup detector because the hrtimer callback of the lockup mechanism is never invoked.
As a first step to prevent this, avoid reprogramming the clock event device when:
- a forced minimum delta event is pending
- the new expiry delta is less then or equal to the minimum delta
Thanks to Calvin for providing the reproducer and to Borislav for testing and providing data from his Zen5 machine.
The problem is not limited to Zen5, but depending on the underlying clock event device (e.g. TSC deadline timer on Intel) and the CPU speed not necessarily observable.
This change serves only as the last resort and further changes will be made to prevent this scenario earlier in the call chain as far as possible.
[ tglx: Updated to restore the old behaviour vs. !force and delta <= 0 ]
--- include/linux/clockchips.h | 2 ++ kernel/time/clockevents.c | 27 +++++++++++++++++++-------- kernel/time/hrtimer.c | 1 + kernel/time/tick-common.c | 1 + kernel/time/tick-sched.c | 1 + 5 files changed, 24 insertions(+), 8 deletions(-)
include/linux/clockchips.h +2 -0
@@ -80,6 +80,7 @@ enum clock_event_state {* @shift: nanoseconds to cycles divisor (power of two) * @state_use_accessors:current state of the device, assigned by the core code * @features: features +* @next_event_forced: True if the last programming was a forced event * @retries: number of forced programming retries * @set_state_periodic: switch state to periodic * @set_state_oneshot: switch state to oneshot
@@ -108,6 +109,7 @@ struct clock_event_device {u32 shift;enum clock_event_state state_use_accessors;unsigned int features; +unsigned int next_event_forced;unsigned long retries;int (*set_state_periodic)(struct clock_event_device *);
kernel/time/clockevents.c +19 -8
@@ -172,6 +172,7 @@ void clockevents_shutdown(struct clock_event_device *dev) {clockevents_switch_state (dev,CLOCK_EVT_STATE_SHUTDOWN ); dev->next_event =KTIME_MAX ; + dev->next_event_forced =0 ; }/**
@@ -305,7 +306,6 @@ int clockevents_program_event(struct clock_event_device *dev, ktime_t expires, {unsigned long long clc;int64_t delta; -int rc;if (WARN_ON_ONCE (expires <0 ))return -ETIME ;
@@ -324,16 +324,27 @@ int clockevents_program_event(struct clock_event_device *dev, ktime_t expires,return dev->set_next_ktime (expires, dev); delta =ktime_to_ns (ktime_sub (expires,ktime_get ())); -if (delta <=0 ) -return force ?clockevents_program_min_delta (dev) : -ETIME ; - delta =min (delta, (int64_t ) dev->max_delta_ns); - delta =max (delta, (int64_t ) dev->min_delta_ns); +/* Required for tick_periodic() during early boot */ +if (delta <=0 && !force) +return -ETIME ; + +if (delta > (int64_t )dev->min_delta_ns) { + delta =min (delta, (int64_t ) dev->max_delta_ns); + clc = ((unsigned long long ) delta * dev->mult) >> dev->shift; +if (!dev->set_next_event ((unsigned long ) clc, dev)) +return 0 ; + } - clc = ((unsigned long long ) delta * dev->mult) >> dev->shift; - rc = dev->set_next_event ((unsigned long ) clc, dev); +if (dev->next_event_forced) +return 0 ; -return (rc && force) ?clockevents_program_min_delta (dev) : rc; +if (dev->set_next_event (dev->min_delta_ticks, dev)) { +if (!force ||clockevents_program_min_delta (dev)) +return -ETIME ; + } + dev->next_event_forced =1 ; +return 0 ; }/*
kernel/time/hrtimer.c +1 -0
@@ -1888,6 +1888,7 @@ void hrtimer_interrupt(struct clock_event_device *dev)BUG_ON (!cpu_base->hres_active); cpu_base->nr_events++; dev->next_event =KTIME_MAX ; + dev->next_event_forced =0 ;raw_spin_lock_irqsave (&cpu_base->lock, flags); entry_time = now =hrtimer_update_base (cpu_base);
kernel/time/tick-common.c +1 -0
@@ -110,6 +110,7 @@ void tick_handle_periodic(struct clock_event_device *dev)int cpu =smp_processor_id ();ktime_t next = dev->next_event; + dev->next_event_forced =0 ;tick_periodic (cpu);/*
kernel/time/tick-sched.c +1 -0
@@ -1513,6 +1513,7 @@ static void tick_nohz_lowres_handler(struct clock_event_device *dev)struct tick_sched *ts =this_cpu_ptr (&tick_cpu_sched); dev->next_event =KTIME_MAX ; + dev->next_event_forced =0 ;if (likely (tick_nohz_handler (&ts->sched_timer) ==HRTIMER_RESTART ))tick_program_event (hrtimer_get_expires (&ts->sched_timer),1 );
Re: [patch 01/12] clockevents: Prevent timer interrupt starvation
In reply to Thomas GleixnerHi Thomas,
Quoted 29 lines · Thomas Gleixner
From: Thomas Gleixner <[email protected]>
Calvin reported an odd NMI watchdog lockup which claims that the CPU locked up in user space. He provided a reproducer, which sets up a timerfd based timer and then rearms it in a loop with an absolute expiry time of 1ns.
As the expiry time is in the past, the timer ends up as the first expiring timer in the per CPU hrtimer base and the clockevent device is programmed with the minimum delta value. If the machine is fast enough, this ends up in a endless loop of programming the delta value to the minimum value defined by the clock event device, before the timer interrupt can fire, which starves the interrupt and consequently triggers the lockup detector because the hrtimer callback of the lockup mechanism is never invoked.
As a first step to prevent this, avoid reprogramming the clock event device when:
- a forced minimum delta event is pending
- the new expiry delta is less then or equal to the minimum deltaThanks to Calvin for providing the reproducer and to Borislav for testing and providing data from his Zen5 machine.
The problem is not limited to Zen5, but depending on the underlying clock event device (e.g. TSC deadline timer on Intel) and the CPU speed not necessarily observable.
This change serves only as the last resort and further changes will be made to prevent this scenario earlier in the call chain as far as possible.
Quoted 8 lines
Fixes d316c57ff6bf ("[PATCH] clockevents: add core functionality")Reported-by Calvin Owens <[email protected]>Signed-off-by Thomas Gleixner <[email protected]>Cc Peter Zijlstra <[email protected]>Cc Anna-Maria Behnsen <[email protected]>Cc Frederic Weisbecker <[email protected]>Cc Ingo Molnar <[email protected]>
This change in -next as commit 1c2eabb8805d ("clockevents: Prevent timer interrupt starvation") appears to make one of my test machines consistently lock up on boot (at least I never get to userspace). Most of the time I get stall messages such as
rcu: INFO: rcu_preempt detected stalls on CPUs/tasks: rcu: 14-...!: (20 GPs behind) idle=f380/0/0x0 softirq=1272/1273 fqs=4 (false positive?) rcu: (detected by 2, t=60002 jiffies, g=3673, q=12382 ncpus=16) Sending NMI from CPU 2 to CPUs 14: NMI backtrace for cpu 14 skipped: idling at cpu_idle_poll.isra.0+0x50/0x170 rcu: rcu_preempt kthread timer wakeup didn't happen for 59984 jiffies! g3673 f0x0 RCU_GP_WAIT_FQS(5) ->state=0x402 rcu: Possible timer handling issue on cpu=4 timer-softirq=170 rcu: rcu_preempt kthread starved for 59987 jiffies! g3673 f0x0 RCU_GP_WAIT_FQS(5) ->state=0x402 ->cpu=4 rcu: Unless rcu_preempt kthread gets sufficient CPU time, OOM is now expected behavior. rcu: RCU grace-period kthread stack dump: task:rcu_preempt state:I stack:0 pid:16 tgid:16 ppid:2 task_flags:0x208040 flags:0x00000010 Call trace: __switch_to+0x100/0x1c8 (T) __schedule+0x2b0/0x710 schedule+0x3c/0xc0 schedule_timeout+0x88/0x128 rcu_gp_fqs_loop+0x12c/0x640 rcu_gp_kthread+0x308/0x350 kthread+0x10c/0x128 ret_from_fork+0x10/0x20 rcu: Stack dump where RCU GP kthread last ran: Sending NMI from CPU 2 to CPUs 4: NMI backtrace for cpu 4 skipped: idling at cpu_idle_poll.isra.0+0x50/0x170 rcu: INFO: rcu_preempt detected stalls on CPUs/tasks: rcu: 0-...!: (21 GPs behind) idle=a4a0/0/0x0 softirq=1775/1776 fqs=0 (false positive?) rcu: 3-...!: (28 GPs behind) idle=5b00/0/0x0 softirq=1437/1438 fqs=0 (false positive?) rcu: 7-...!: (21 GPs behind) idle=0c18/0/0x0 softirq=1658/1659 fqs=0 (false positive?) rcu: 8-...!: (21 GPs behind) idle=1418/0/0x0 softirq=1231/1231 fqs=0 (false positive?) rcu: 9-...!: (18 GPs behind) idle=1288/0/0x0 softirq=1440/1440 fqs=0 (false positive?) rcu: 12-...!: (21 GPs behind) idle=ae70/0/0x0 softirq=1339/1339 fqs=0 (false positive?) rcu: 13-...!: (28 GPs behind) idle=02c8/0/0x0 softirq=1785/1787 fqs=0 (false positive?) rcu: 14-...!: (21 GPs behind) idle=f428/0/0x0 softirq=1272/1273 fqs=0 (false positive?) rcu: 15-...!: (21 GPs behind) idle=0fb8/0/0x0 softirq=1562/1562 fqs=0 (false positive?) rcu: (detected by 5, t=60002 jiffies, g=3677, q=12637 ncpus=16) Sending NMI from CPU 5 to CPUs 0: NMI backtrace for cpu 0 skipped: idling at cpu_idle_poll.isra.0+0x38/0x170 Sending NMI from CPU 5 to CPUs 3: NMI backtrace for cpu 3 skipped: idling at cpu_idle_poll.isra.0+0x38/0x170 Sending NMI from CPU 5 to CPUs 7: NMI backtrace for cpu 7 skipped: idling at cpu_idle_poll.isra.0+0x40/0x170 Sending NMI from CPU 5 to CPUs 8: NMI backtrace for cpu 8 skipped: idling at cpu_idle_poll.isra.0+0x40/0x170 Sending NMI from CPU 5 to CPUs 9: NMI backtrace for cpu 9 skipped: idling at cpu_idle_poll.isra.0+0x40/0x170 Sending NMI from CPU 5 to CPUs 12: NMI backtrace for cpu 12 skipped: idling at cpu_idle_poll.isra.0+0x40/0x170 Sending NMI from CPU 5 to CPUs 13: NMI backtrace for cpu 13 skipped: idling at cpu_idle_poll.isra.0+0x50/0x170 Sending NMI from CPU 5 to CPUs 14: NMI backtrace for cpu 14 skipped: idling at cpu_idle_poll.isra.0+0x50/0x170 Sending NMI from CPU 5 to CPUs 15: NMI backtrace for cpu 15 skipped: idling at cpu_idle_poll.isra.0+0x38/0x170 rcu: rcu_preempt kthread timer wakeup didn't happen for 60008 jiffies! g3677 f0x0 RCU_GP_WAIT_FQS(5) ->state=0x402 rcu: Possible timer handling issue on cpu=4 timer-softirq=170 rcu: rcu_preempt kthread starved for 60011 jiffies! g3677 f0x0 RCU_GP_WAIT_FQS(5) ->state=0x402 ->cpu=4 rcu: Unless rcu_preempt kthread gets sufficient CPU time, OOM is now expected behavior. rcu: RCU grace-period kthread stack dump: task:rcu_preempt state:I stack:0 pid:16 tgid:16 ppid:2 task_flags:0x208040 flags:0x00000010 Call trace: __switch_to+0x100/0x1c8 (T) __schedule+0x2b0/0x710 schedule+0x3c/0xc0 schedule_timeout+0x88/0x128 rcu_gp_fqs_loop+0x12c/0x640 rcu_gp_kthread+0x308/0x350 kthread+0x10c/0x128 ret_from_fork+0x10/0x20 rcu: Stack dump where RCU GP kthread last ran: Sending NMI from CPU 5 to CPUs 4: NMI backtrace for cpu 4 CPU: 4 UID: 0 PID: 0 Comm: swapper/4 Not tainted 7.0.0-rc7-next-20260409 #1 PREEMPT(lazy) Hardware name: SolidRun Ltd. SolidRun CEX7 Platform, BIOS EDK II Jun 21 2022 pstate: 60400005 (nZCv daif +PAN -UAO -TCO -DIT -SSBS BTYPE=--) pc : tick_check_broadcast_expired+0x4/0x40 lr : cpu_idle_poll.isra.0+0x54/0x170 sp : ffff80008017be20 x29: ffff80008017be20 x28: 0000000000000000 x27: 0000000000000000 x26: 0000000000000000 x25: 0000000000000000 x24: 0000000000000000 x23: 00000000000000c0 x22: ffffb3ce21fad000 x21: 0000000000000004 x20: ffffb3ce21fadd50 x19: ffffb3ce21fad000 x18: 0000000000000004 x17: 0000000000000000 x16: 0000000000000000 x15: ffffb3ce21fb3b98 x14: ffffb3ce21788180 x13: 0000000000000000 x12: 000000124d69be59 x11: 00000000000000c0 x10: 0000000000001c80 x9 : ffffb3ce1f8a6e68 x8 : 0000000000000000 x7 : 0000000000000000 x6 : 0000000000000004 x5 : ffff00275c3682c8 x4 : 0000000000020a3c x3 : 0000000000000000 x2 : 0000000000000004 x1 : ffffb3ce223ca0c0 x0 : ffff002020da2140 Call trace: tick_check_broadcast_expired+0x4/0x40 (P) do_idle+0x64/0x130 cpu_startup_entry+0x40/0x50 secondary_start_kernel+0xe4/0x128 __secondary_switched+0xc0/0xc8 rcu: INFO: rcu_preempt detected stalls on CPUs/tasks: rcu: 0-...!: (22 GPs behind) idle=ae48/0/0x0 softirq=1775/1776 fqs=0 (false positive?) rcu: 3-...!: (29 GPs behind) idle=7ce8/0/0x0 softirq=1437/1438 fqs=0 (false positive?) rcu: 7-...!: (22 GPs behind) idle=0df8/0/0x0 softirq=1658/1659 fqs=0 (false positive?) rcu: 8-...!: (22 GPs behind) idle=1548/0/0x0 softirq=1231/1231 fqs=0 (false positive?) rcu: 9-...!: (19 GPs behind) idle=1360/0/0x0 softirq=1440/1440 fqs=0 (false positive?) rcu: 12-...!: (22 GPs behind) idle=af40/0/0x0 softirq=1339/1339 fqs=0 (false positive?) rcu: 13-...!: (29 GPs behind) idle=04e0/0/0x0 softirq=1785/1787 fqs=0 (false positive?) rcu: 14-...!: (22 GPs behind) idle=f528/0/0x0 softirq=1272/1273 fqs=0 (false positive?) rcu: 15-...!: (22 GPs behind) idle=0fd8/0/0x0 softirq=1562/1562 fqs=0 (false positive?) rcu: (detected by 5, t=60002 jiffies, g=3681, q=13149 ncpus=16)
but other times, there is no output after it locks up. Is there any initial information I can provide to help debug this? Reverting the change on top of next-20260409 avoids the issue.
Cheers,
Nathan
# bad: [3fa7d958829eb9bc3b469ed07f11de3d2804ef71] Add linux-next specific files for 20260409
# good: [7f87a5ea75f011d2c9bc8ac0167e5e2d1adb1594] Merge tag 'hid-for-linus-2026040801' of git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid
git bisect start '3fa7d958829eb9bc3b469ed07f11de3d2804ef71' '7f87a5ea75f011d2c9bc8ac0167e5e2d1adb1594'
# bad: [443e04732ac2cdc17e3b90aa2345730a298fab37] Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git
git bisect bad 443e04732ac2cdc17e3b90aa2345730a298fab37
# bad: [ea33e83d9fa24b34e79c8df57b8927a8d94deb15] Merge branch 'xtensa-for-next' of https://github.com/jcmvbkbc/linux-xtensa.git
git bisect bad ea33e83d9fa24b34e79c8df57b8927a8d94deb15
# bad: [429057750b3d3a7477df48d17aa605dc47bc2344] Merge branch 'for-next/perf' of https://git.kernel.org/pub/scm/linux/kernel/git/will/linux.git
git bisect bad 429057750b3d3a7477df48d17aa605dc47bc2344
# bad: [e98894f89da72f392141d9eecf1c7a8f13faa67f] Merge branch 'mm-stable' of https://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm
git bisect bad e98894f89da72f392141d9eecf1c7a8f13faa67f
# good: [668937b7b2256f4b2a982e8f69b07d9ee8f81d36] mm: allow handling of stacked mmap_prepare hooks in more drivers
git bisect good 668937b7b2256f4b2a982e8f69b07d9ee8f81d36
# good: [a0fbc8dd44a27011537268e2a974b1180b848796] Merge branch 'dma-mapping-fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/mszyprowski/linux.git
git bisect good a0fbc8dd44a27011537268e2a974b1180b848796
# good: [8a23051ed8584215b22368e9501f771ef98f0c1d] Merge tag 'pin-init-v7.1' of https://github.com/Rust-for-Linux/linux into rust-next git bisect good 8a23051ed8584215b22368e9501f771ef98f0c1d
# good: [716b25a9dc20f4fb94d521581331a0565a43f3bb] Merge branch 'urgent' of https://git.kernel.org/pub/scm/linux/kernel/git/efi/efi.git
git bisect good 716b25a9dc20f4fb94d521581331a0565a43f3bb
# bad: [1a49dc272e25dae6cbb506a02bb70e0201a1498e] Merge branch 'tip/urgent' of https://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git
git bisect bad 1a49dc272e25dae6cbb506a02bb70e0201a1498e
# good: [30023353b2171cd36b10615a788a985f5caa29e3] Merge branch into tip/master: 'sched/urgent'
git bisect good 30023353b2171cd36b10615a788a985f5caa29e3
# good: [34ef164adaf00982d5f45037a7e37689c4555271] Merge branch 'i2c/i2c-host-fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/andi.shyti/linux.git
git bisect good 34ef164adaf00982d5f45037a7e37689c4555271
# bad: [4fc7108ff756267ad53ecdeaa1e847d378887511] Merge branch into tip/master: 'timers/urgent'
git bisect bad 4fc7108ff756267ad53ecdeaa1e847d378887511
# bad: [1c2eabb8805d9fd79a19de5c76d4a64c9ad3cdf4] clockevents: Prevent timer interrupt starvation
git bisect bad 1c2eabb8805d9fd79a19de5c76d4a64c9ad3cdf4
# good: [82b915051d32a68ea3bbe261c93f5620699ff047] tick/nohz: Fix inverted return value in check_tick_dependency() fast path
git bisect good 82b915051d32a68ea3bbe261c93f5620699ff047
# first bad commit: [1c2eabb8805d9fd79a19de5c76d4a64c9ad3cdf4] clockevents: Prevent timer interrupt starvation
Thomas Gleixner
maintainerRe: [patch 01/12] clockevents: Prevent timer interrupt starvation
Quoted 6 lines · Nathan Chancellor
Quoted 1 line
This change in -next as commit
1c2eabb8805d("clockevents: Prevent timer interrupt starvation") appears to make one of my test machines consistently lock up on boot (at least I never get to userspace). Most of the time I get stall messages such as
Can you please retest the changes I just pushed out into:
git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git timers/urgent
namely: d6e152d905bd ("clockevents: Prevent timer interrupt starvation")
Thanks,
tglx
Re: [patch 01/12] clockevents: Prevent timer interrupt starvation
Quoted 8 lines · Thomas Gleixner
Quoted 6 lines · Nathan Chancellor
Quoted 1 line
This change in -next as commit
1c2eabb8805d("clockevents: Prevent timer interrupt starvation") appears to make one of my test machines consistently lock up on boot (at least I never get to userspace). Most of the time I get stall messages such as
Quoted 5 lines
Can you please retest the changes I just pushed out into:
git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.gittimers/urgentnamely:
d6e152d905bd("clockevents: Prevent timer interrupt starvation")
Hah, funny timing. I can confirm that the following diff against the version in -next appears to fix the issue for me. Thanks for the quick response!
Cheers,
Nathan
kernel/time/tick-broadcast.c +7 -1
@@ -76,8 +76,10 @@ const struct clock_event_device *tick_get_wakeup_device(int cpu)*/ static void tick_broadcast_start_periodic (struct clock_event_device *bc) { -if (bc) +if (bc) { + bc->next_event_forced =0 ;tick_setup_periodic (bc,1 ); + } }/*
@@ -403,6 +405,7 @@ static void tick_handle_periodic_broadcast(struct clock_event_device *dev)bool bc_local;raw_spin_lock (&tick_broadcast_lock); + tick_broadcast_device.evtdev->next_event_forced =0 ;/* Handle spurious interrupts gracefully */ if (clockevent_state_shutdown (tick_broadcast_device.evtdev)) {
@@ -696,6 +699,7 @@ static void tick_handle_oneshot_broadcast(struct clock_event_device *dev)raw_spin_lock (&tick_broadcast_lock); dev->next_event =KTIME_MAX ; + tick_broadcast_device.evtdev->next_event_forced =0 ; next_event =KTIME_MAX ;cpumask_clear (tmpmask); now =ktime_get ();
@@ -1063,6 +1067,7 @@ static void tick_broadcast_setup_oneshot(struct clock_event_device *bc, bc->event_handler = tick_handle_oneshot_broadcast; + bc->next_event_forced =0 ; bc->next_event =KTIME_MAX ;/*
@@ -1175,6 +1180,7 @@ void hotplug_cpu__broadcast_tick_pull(int deadcpu) }/* This moves the broadcast assignment to this CPU: */ + bc->next_event_forced =0 ;clockevents_program_event (bc, bc->next_event,1 ); }raw_spin_unlock_irqrestore (&tick_broadcast_lock, flags);
[tip: timers/urgent] clockevents: Prevent timer interrupt starvation
In reply to Thomas GleixnerThe following commit has been merged into the timers/urgent branch of tip:
The following commit has been merged into the timers/urgent branch of tip:
Commit-ID: d6e152d905bdb1f32f9d99775e2f453350399a6a Gitweb: https://git.kernel.org/tip/d6e152d905bdb1f32f9d99775e2f453350399a6a Author: Thomas Gleixner <[email protected]> AuthorDate: Tue, 07 Apr 2026 10:54:17 +02:00 Committer: Thomas Gleixner <[email protected]> CommitterDate: Fri, 10 Apr 2026 22:45:38 +02:00
clockevents: Prevent timer interrupt starvation
Calvin reported an odd NMI watchdog lockup which claims that the CPU locked up in user space. He provided a reproducer, which sets up a timerfd based timer and then rearms it in a loop with an absolute expiry time of 1ns.
As the expiry time is in the past, the timer ends up as the first expiring timer in the per CPU hrtimer base and the clockevent device is programmed with the minimum delta value. If the machine is fast enough, this ends up in a endless loop of programming the delta value to the minimum value defined by the clock event device, before the timer interrupt can fire, which starves the interrupt and consequently triggers the lockup detector because the hrtimer callback of the lockup mechanism is never invoked.
As a first step to prevent this, avoid reprogramming the clock event device when:
- a forced minimum delta event is pending
- the new expiry delta is less then or equal to the minimum delta
Thanks to Calvin for providing the reproducer and to Borislav for testing and providing data from his Zen5 machine.
The problem is not limited to Zen5, but depending on the underlying clock event device (e.g. TSC deadline timer on Intel) and the CPU speed not necessarily observable.
This change serves only as the last resort and further changes will be made to prevent this scenario earlier in the call chain as far as possible.
[ tglx: Updated to restore the old behaviour vs. !force and delta <= 0 and
fixed up the tick-broadcast handlers as pointed out by Borislav ]
--- include/linux/clockchips.h | 2 ++ kernel/time/clockevents.c | 27 +++++++++++++++++++-------- kernel/time/hrtimer.c | 1 + kernel/time/tick-broadcast.c | 8 +++++++- kernel/time/tick-common.c | 1 + kernel/time/tick-sched.c | 1 + 6 files changed, 31 insertions(+), 9 deletions(-)
include/linux/clockchips.h +2 -0
@@ -80,6 +80,7 @@ enum clock_event_state {* @shift: nanoseconds to cycles divisor (power of two) * @state_use_accessors:current state of the device, assigned by the core code * @features: features +* @next_event_forced: True if the last programming was a forced event * @retries: number of forced programming retries * @set_state_periodic: switch state to periodic * @set_state_oneshot: switch state to oneshot
@@ -108,6 +109,7 @@ struct clock_event_device {u32 shift;enum clock_event_state state_use_accessors;unsigned int features; +unsigned int next_event_forced;unsigned long retries;int (*set_state_periodic)(struct clock_event_device *);
kernel/time/clockevents.c +19 -8
@@ -172,6 +172,7 @@ void clockevents_shutdown(struct clock_event_device *dev) {clockevents_switch_state (dev,CLOCK_EVT_STATE_SHUTDOWN ); dev->next_event =KTIME_MAX ; + dev->next_event_forced =0 ; }/**
@@ -305,7 +306,6 @@ int clockevents_program_event(struct clock_event_device *dev, ktime_t expires, {unsigned long long clc;int64_t delta; -int rc;if (WARN_ON_ONCE (expires <0 ))return -ETIME ;
@@ -324,16 +324,27 @@ int clockevents_program_event(struct clock_event_device *dev, ktime_t expires,return dev->set_next_ktime (expires, dev); delta =ktime_to_ns (ktime_sub (expires,ktime_get ())); -if (delta <=0 ) -return force ?clockevents_program_min_delta (dev) : -ETIME ; - delta =min (delta, (int64_t ) dev->max_delta_ns); - delta =max (delta, (int64_t ) dev->min_delta_ns); +/* Required for tick_periodic() during early boot */ +if (delta <=0 && !force) +return -ETIME ; + +if (delta > (int64_t )dev->min_delta_ns) { + delta =min (delta, (int64_t ) dev->max_delta_ns); + clc = ((unsigned long long ) delta * dev->mult) >> dev->shift; +if (!dev->set_next_event ((unsigned long ) clc, dev)) +return 0 ; + } - clc = ((unsigned long long ) delta * dev->mult) >> dev->shift; - rc = dev->set_next_event ((unsigned long ) clc, dev); +if (dev->next_event_forced) +return 0 ; -return (rc && force) ?clockevents_program_min_delta (dev) : rc; +if (dev->set_next_event (dev->min_delta_ticks, dev)) { +if (!force ||clockevents_program_min_delta (dev)) +return -ETIME ; + } + dev->next_event_forced =1 ; +return 0 ; }/*
kernel/time/hrtimer.c +1 -0
@@ -1888,6 +1888,7 @@ void hrtimer_interrupt(struct clock_event_device *dev)BUG_ON (!cpu_base->hres_active); cpu_base->nr_events++; dev->next_event =KTIME_MAX ; + dev->next_event_forced =0 ;raw_spin_lock_irqsave (&cpu_base->lock, flags); entry_time = now =hrtimer_update_base (cpu_base);
kernel/time/tick-broadcast.c +7 -1
@@ -76,8 +76,10 @@ const struct clock_event_device *tick_get_wakeup_device(int cpu)*/ static void tick_broadcast_start_periodic (struct clock_event_device *bc) { -if (bc) +if (bc) { + bc->next_event_forced =0 ;tick_setup_periodic (bc,1 ); + } }/*
@@ -403,6 +405,7 @@ static void tick_handle_periodic_broadcast(struct clock_event_device *dev)bool bc_local;raw_spin_lock (&tick_broadcast_lock); + tick_broadcast_device.evtdev->next_event_forced =0 ;/* Handle spurious interrupts gracefully */ if (clockevent_state_shutdown (tick_broadcast_device.evtdev)) {
@@ -696,6 +699,7 @@ static void tick_handle_oneshot_broadcast(struct clock_event_device *dev)raw_spin_lock (&tick_broadcast_lock); dev->next_event =KTIME_MAX ; + tick_broadcast_device.evtdev->next_event_forced =0 ; next_event =KTIME_MAX ;cpumask_clear (tmpmask); now =ktime_get ();
@@ -1063,6 +1067,7 @@ static void tick_broadcast_setup_oneshot(struct clock_event_device *bc, bc->event_handler = tick_handle_oneshot_broadcast; + bc->next_event_forced =0 ; bc->next_event =KTIME_MAX ;/*
@@ -1175,6 +1180,7 @@ void hotplug_cpu__broadcast_tick_pull(int deadcpu) }/* This moves the broadcast assignment to this CPU: */ + bc->next_event_forced =0 ;clockevents_program_event (bc, bc->next_event,1 ); }raw_spin_unlock_irqrestore (&tick_broadcast_lock, flags);
kernel/time/tick-common.c +1 -0
@@ -110,6 +110,7 @@ void tick_handle_periodic(struct clock_event_device *dev)int cpu =smp_processor_id ();ktime_t next = dev->next_event; + dev->next_event_forced =0 ;tick_periodic (cpu);/*
kernel/time/tick-sched.c +1 -0
@@ -1513,6 +1513,7 @@ static void tick_nohz_lowres_handler(struct clock_event_device *dev)struct tick_sched *ts =this_cpu_ptr (&tick_cpu_sched); dev->next_event =KTIME_MAX ; + dev->next_event_forced =0 ;if (likely (tick_nohz_handler (&ts->sched_timer) ==HRTIMER_RESTART ))tick_program_event (hrtimer_get_expires (&ts->sched_timer),1 );
The "clockevents: Prevent timer interrupt starvation" patch causes lockups
In reply to Thomas GleixnerQuoted 138 lines · Thomas Gleixner
From: Thomas Gleixner <[email protected]>
Calvin reported an odd NMI watchdog lockup which claims that the CPU locked up in user space. He provided a reproducer, which sets up a timerfd based timer and then rearms it in a loop with an absolute expiry time of 1ns.
As the expiry time is in the past, the timer ends up as the first expiring timer in the per CPU hrtimer base and the clockevent device is programmed with the minimum delta value. If the machine is fast enough, this ends up in a endless loop of programming the delta value to the minimum value defined by the clock event device, before the timer interrupt can fire, which starves the interrupt and consequently triggers the lockup detector because the hrtimer callback of the lockup mechanism is never invoked.
As a first step to prevent this, avoid reprogramming the clock event device when:
- a forced minimum delta event is pending
- the new expiry delta is less then or equal to the minimum deltaThanks to Calvin for providing the reproducer and to Borislav for testing and providing data from his Zen5 machine.
The problem is not limited to Zen5, but depending on the underlying clock event device (e.g. TSC deadline timer on Intel) and the CPU speed not necessarily observable.
This change serves only as the last resort and further changes will be made to prevent this scenario earlier in the call chain as far as possible.
Fixes d316c57ff6bf ("[PATCH] clockevents: add core functionality")Reported-by Calvin Owens <[email protected]>Signed-off-by Thomas Gleixner <[email protected]>Cc Peter Zijlstra <[email protected]>Cc Anna-Maria Behnsen <[email protected]>Cc Frederic Weisbecker <[email protected]>Cc Ingo Molnar <[email protected]>---
V2: Simplified the clockevents code - Peter--- include/linux/clockchips.h | 2 ++ kernel/time/clockevents.c | 23 +++++++++++++++-------- kernel/time/hrtimer.c | 1 + kernel/time/tick-common.c | 1 + kernel/time/tick-sched.c | 1 + 5 files changed, 20 insertions(+), 8 deletions(-)include/linux/clockchips.h +2
-0@@ -80,6 +80,7 @@ enum clock_event_state {* @shift: nanoseconds to cycles divisor (power of two) * @state_use_accessors:current state of the device, assigned by the core code * @features: features +* @next_event_forced: True if the last programming was a forced event * @retries: number of forced programming retries * @set_state_periodic: switch state to periodic * @set_state_oneshot: switch state to oneshot @@ -108,6 +109,7 @@ struct clock_event_device {u32 shift;enum clock_event_state state_use_accessors;unsigned int features; +unsigned int next_event_forced;unsigned long retries;int (*set_state_periodic)(struct clock_event_device *);kernel/time/clockevents.c +15
-8@@ -172,6 +172,7 @@ void clockevents_shutdown(struct clock_e {clockevents_switch_state (dev,CLOCK_EVT_STATE_SHUTDOWN ); dev->next_event =KTIME_MAX ; + dev->next_event_forced =0 ; }/** @@ -305,7 +306,6 @@ int clockevents_program_event(struct clo {unsigned long long clc;int64_t delta; -int rc;if (WARN_ON_ONCE (expires <0 ))return -ETIME ;@@ -324,16 +324,23 @@ int clockevents_program_event(struct cloreturn dev->set_next_ktime (expires, dev); delta =ktime_to_ns (ktime_sub (expires,ktime_get ())); -if (delta <=0 ) -return force ?clockevents_program_min_delta (dev) : -ETIME ; - delta =min (delta, (int64_t ) dev->max_delta_ns); - delta =max (delta, (int64_t ) dev->min_delta_ns); +if (delta > (int64_t )dev->min_delta_ns) { + delta =min (delta, (int64_t ) dev->max_delta_ns); + clc = ((unsigned long long ) delta * dev->mult) >> dev->shift; +if (!dev->set_next_event ((unsigned long ) clc, dev)) +return 0 ; + } - clc = ((unsigned long long ) delta * dev->mult) >> dev->shift; - rc = dev->set_next_event ((unsigned long ) clc, dev); +if (dev->next_event_forced) +return 0 ; -return (rc && force) ?clockevents_program_min_delta (dev) : rc; +if (dev->set_next_event (dev->min_delta_ticks, dev)) { +if (!force ||clockevents_program_min_delta (dev)) +return -ETIME ; + } + dev->next_event_forced =1 ; +return 0 ; }/* kernel/time/hrtimer.c +1
-0@@ -1888,6 +1888,7 @@ void hrtimer_interrupt(struct clock_evenBUG_ON (!cpu_base->hres_active); cpu_base->nr_events++; dev->next_event =KTIME_MAX ; + dev->next_event_forced =0 ;raw_spin_lock_irqsave (&cpu_base->lock, flags); entry_time = now =hrtimer_update_base (cpu_base);kernel/time/tick-common.c +1
-0@@ -110,6 +110,7 @@ void tick_handle_periodic(struct clock_eint cpu =smp_processor_id ();ktime_t next = dev->next_event; + dev->next_event_forced =0 ;tick_periodic (cpu);/*
kernel/time/tick-sched.c static void tick_nohz_lowres_handler(str · line 1519
Quoted 7 lines
kernel/time/tick-sched.c +1
-0@@ -1513,6 +1513,7 @@ static void tick_nohz_lowres_handler(strstruct tick_sched *ts =this_cpu_ptr (&tick_cpu_sched); dev->next_event =KTIME_MAX ; + dev->next_event_forced =0 ;if (likely (tick_nohz_handler (&ts->sched_timer) ==HRTIMER_RESTART ))tick_program_event (hrtimer_get_expires (&ts->sched_timer),1 );
Hello.
Sorry, but this patch as of 7.0 introduced *severe* periodic lockups on my Ryzen 7700X machine. I see such messages in the log:
clocksource: Long readout interval, skipping watchdog check: cs_nsec: 2897344852 wd_nsec: 2897356996
Reverting d6e152d905bdb1f32f9d99775e2f453350399a6a for mainline fixes the issue for me.
Re: The "clockevents: Prevent timer interrupt starvation" patch causes lockups
Quoted 7 lines · Hanabishi
Hello.
Sorry, but this patch as of 7.0 introduced *severe* periodic lockups on my Ryzen 7700X machine.
I see such messages in the log:
Quoted 6 lines
clocksource: Long readout interval, skipping watchdog check:
cs_nsec:
2897344852wd_nsec: 2897356996Reverting
d6e152d905bdb1f32f9d99775e2f453350399a6afor mainline fixes the issue for me.
Hi maintainers,
several users from CachyOS has reported this regression as well. We landed on the same bisection. One of the users that could reproduce this reliably reproduced this just by watching a YouTube video in a browser, and observed freezes and stutters when interacting with the system.
I had an LLM generate a fix (patch attached), and it fixed the regression for that user. Full disclosure: it is written completely by AI, and I am also not familiar with this subsystem. I just hope that this patch can be helpful in fixing the regression.
Please don't hesitate to tell me off if utilizing AI in this way is not helpful, so I can keep this in mind for future contributions.
Signature · 2 lines
Regards, Eric
kernel/time/clockevents.c +3 -1
@@ -332,8 +332,10 @@ int clockevents_program_event(struct clock_event_device *dev, ktime_t expires,if (delta > (int64_t )dev->min_delta_ns) { delta =min (delta, (int64_t ) dev->max_delta_ns); clc = ((unsigned long long ) delta * dev->mult) >> dev->shift; -if (!dev->set_next_event ((unsigned long ) clc, dev)) +if (!dev->set_next_event ((unsigned long ) clc, dev)) { + dev->next_event_forced =0 ;return 0 ; + } }if (dev->next_event_forced)
kernel/time/tick-oneshot.c +2 -0
@@ -34,6 +34,7 @@ int tick_program_event(ktime_t expires, int force)*/ clockevents_switch_state (dev,CLOCK_EVT_STATE_ONESHOT_STOPPED ); dev->next_event =KTIME_MAX ; + dev->next_event_forced =0 ;return 0 ; }
@@ -43,6 +44,7 @@ int tick_program_event(ktime_t expires, int force)* before using it. */ clockevents_switch_state (dev,CLOCK_EVT_STATE_ONESHOT ); + dev->next_event_forced =0 ; }return clockevents_program_event (dev, expires, force);
Re: The "clockevents: Prevent timer interrupt starvation" patch causes lockups
Quoted 15 lines · Eric Naim
Quoted 7 lines · Hanabishi
Hello.
Sorry, but this patch as of 7.0 introduced *severe* periodic lockups on my Ryzen 7700X machine.
I see such messages in the log:Quoted 6 lines
clocksource: Long readout interval, skipping watchdog check:
cs_nsec:
2897344852wd_nsec: 2897356996Reverting
d6e152d905bdb1f32f9d99775e2f453350399a6afor mainline fixes the issue for me.
Quoted 6 lines
Hi maintainers,
several users from CachyOS has reported this regression as well. We landed on the same bisection. One of the users that could reproduce this reliably reproduced this just by watching a YouTube video in a browser, and observed freezes and stutters when interacting with the system.
Huh, I can't reproduce this at all across 10+ machines. Can you share the Kconfig you're seeing this on?
Thanks,
Calvin
Re: The "clockevents: Prevent timer interrupt starvation" patch causes lockups
Quoted 18 lines · Calvin Owens
Quoted 17 lines · Eric Naim
Quoted 7 lines · Hanabishi
Hello.
Sorry, but this patch as of 7.0 introduced *severe* periodic lockups on my Ryzen 7700X machine.
I see such messages in the log:Quoted 6 lines
clocksource: Long readout interval, skipping watchdog check:
cs_nsec:
2897344852wd_nsec: 2897356996Reverting
d6e152d905bdb1f32f9d99775e2f453350399a6afor mainline fixes the issue for me.Hi maintainers,
Quoted 7 lines
Quoted 4 lines
several users from CachyOS has reported this regression as well. We landed on the same bisection. One of the users that could reproduce this reliably reproduced this just by watching a YouTube video in a browser, and observed freezes and stutters when interacting with the system.
Huh, I can't reproduce this at all across 10+ machines. Can you share the
Kconfigyou're seeing this on?
Right, here it is [1]. CachyOS does carry a lot of downstream patches, but I made sure to reproduce this on mainline before reporting here.
Quoted 3 lines
Thanks,
Calvin
Signature · 2 lines
Regards, Eric
Frederic Weisbecker
maintainerRe: The "clockevents: Prevent timer interrupt starvation" patch causes lockups
In reply to Eric NaimQuoted 64 lines · Eric Naim
Quoted 7 lines · Hanabishi
Hello.
Sorry, but this patch as of 7.0 introduced *severe* periodic lockups on my Ryzen 7700X machine.
I see such messages in the log:Quoted 6 lines
clocksource: Long readout interval, skipping watchdog check:
cs_nsec:
2897344852wd_nsec: 2897356996Reverting
d6e152d905bdb1f32f9d99775e2f453350399a6afor mainline fixes the issue for me.Hi maintainers,
several users from CachyOS has reported this regression as well. We landed on the same bisection. One of the users that could reproduce this reliably reproduced this just by watching a YouTube video in a browser, and observed freezes and stutters when interacting with the system.
I had an LLM generate a fix (patch attached), and it fixed the regression for that user. Full disclosure: it is written completely by AI, and I am also not familiar with this subsystem. I just hope that this patch can be helpful in fixing the regression.
Please don't hesitate to tell me off if utilizing AI in this way is not helpful, so I can keep this in mind for future contributions.
Signature · 2 lines
Regards, Erickernel/time/clockevents.c +3
-1@@ -332,8 +332,10 @@ int clockevents_program_event(struct clock_event_device *dev, ktime_t expires,if (delta > (int64_t )dev->min_delta_ns) { delta =min (delta, (int64_t ) dev->max_delta_ns); clc = ((unsigned long long ) delta * dev->mult) >> dev->shift; -if (!dev->set_next_event ((unsigned long ) clc, dev)) +if (!dev->set_next_event ((unsigned long ) clc, dev)) { + dev->next_event_forced =0 ;return 0 ; + } }if (dev->next_event_forced)kernel/time/tick-oneshot.c +1
-0@@ -34,6 +34,7 @@ int tick_program_event(ktime_t expires, int force)*/ clockevents_switch_state (dev,CLOCK_EVT_STATE_ONESHOT_STOPPED ); dev->next_event =KTIME_MAX ; + dev->next_event_forced =0 ;return 0 ; }
kernel/time/tick-oneshot.c int tick_program_event(ktime_t expires, int force) · line 50
Quoted 7 lines
kernel/time/tick-oneshot.c +1
-0@@ -43,6 +44,7 @@ int tick_program_event(ktime_t expires, int force)* before using it. */ clockevents_switch_state (dev,CLOCK_EVT_STATE_ONESHOT ); + dev->next_event_forced =0 ; }return clockevents_program_event (dev, expires, force);
That diff suggest that dev->next_event_forced is not properly cleared by a handler or when the device is stopped.
For example it's not cleared when the device is oneshot stopped.
It's also not cleared when the device is detached (though that shouldn't matter much) and also when the broadcast wakeup thing is used.
Can you try the following?
kernel/time/clockevents.c +5 -2
@@ -100,6 +100,7 @@ static int __clockevents_switch_state(struct clock_event_device *dev,/* The clockevent device is getting replaced. Shut it down. */ case CLOCK_EVT_STATE_SHUTDOWN : + dev->next_event_forced =0 ;if (dev->set_state_shutdown)return dev->set_state_shutdown (dev);return 0 ;
@@ -127,10 +128,12 @@ static int __clockevents_switch_state(struct clock_event_device *dev, clockevent_get_state(dev)))return -EINVAL ; -if (dev->set_state_oneshot_stopped) +if (dev->set_state_oneshot_stopped) { + dev->next_event_forced =0 ;return dev->set_state_oneshot_stopped (dev); -else + }else {return -ENOSYS ; + }default :return -ENOSYS ;
kernel/time/tick-broadcast.c +1 -0
@@ -108,6 +108,7 @@ static struct clock_event_device *tick_get_oneshot_wakeup_device(int cpu)static void tick_oneshot_wakeup_handler (struct clock_event_device *wd) { + wd->next_event_forced =0 ;/* * If we woke up early and the tick was reprogrammed in the * meantime then this may be spurious but harmless.
Re: The "clockevents: Prevent timer interrupt starvation" patch causes lockups
Quoted 34 lines · Frederic Weisbecker
Can you try the following?
kernel/time/clockevents.c +5
-2@@ -100,6 +100,7 @@ static int __clockevents_switch_state(struct clock_event_device *dev,/* The clockevent device is getting replaced. Shut it down. */ case CLOCK_EVT_STATE_SHUTDOWN : + dev->next_event_forced =0 ;if (dev->set_state_shutdown)return dev->set_state_shutdown (dev);return 0 ;@@ -127,10 +128,12 @@ static int __clockevents_switch_state(struct clock_event_device *dev, clockevent_get_state(dev)))return -EINVAL ; -if (dev->set_state_oneshot_stopped) +if (dev->set_state_oneshot_stopped) { + dev->next_event_forced =0 ;return dev->set_state_oneshot_stopped (dev); -else + }else {return -ENOSYS ; + }default :return -ENOSYS ;kernel/time/tick-broadcast.c +0
-0
kernel/time/tick-broadcast.c static struct clock_event_device *tick_get_oneshot_wakeup_device(int cpu) · line 114
Quoted 7 lines
kernel/time/tick-broadcast.c +1
-0@@ -108,6 +108,7 @@ static struct clock_event_device *tick_get_oneshot_wakeup_device(int cpu)static void tick_oneshot_wakeup_handler (struct clock_event_device *wd) { + wd->next_event_forced =0 ;/* * If we woke up early and the tick was reprogrammed in the * meantime then this may be spurious but harmless.
This patch doesn't help me unfortunately. Thanks.
Thomas Gleixner
maintainerRe: The "clockevents: Prevent timer interrupt starvation" patch causes lockups
Quoted 3 lines · Hanabishi
On 14/04/2026 18:04, Frederic Weisbecker wrote:
This patch doesn't help me unfortunately. Thanks.
The one below should cover all possible holes.
Thanks,
tglx
---
kernel/time/clockevents.c +6 -1
@@ -94,6 +94,9 @@ static int __clockevents_switch_state(struct clock_event_device *dev,if (dev->features &CLOCK_EVT_FEAT_DUMMY )return 0 ; +/* On state transitions clear the forced flag unconditionally */ + dev->next_event_forced =0 ; +/* Transition with new state-specific callbacks */ switch (state) {case CLOCK_EVT_STATE_DETACHED :
@@ -366,8 +369,10 @@ int clockevents_program_event(struct clock_event_device *dev, ktime_t expires, bif (delta > (int64_t )dev->min_delta_ns) { delta =min (delta, (int64_t ) dev->max_delta_ns); cycles = ((u64 )delta * dev->mult) >> dev->shift; -if (!dev->set_next_event ((unsigned long ) cycles, dev)) +if (!dev->set_next_event ((unsigned long ) cycles, dev)) { + dev->next_event_forced =0 ;return 0 ; + } }if (dev->next_event_forced)
kernel/time/tick-broadcast.c +1 -0
@@ -108,6 +108,7 @@ static struct clock_event_device *tick_get_oneshot_wakeup_device(int cpu)static void tick_oneshot_wakeup_handler (struct clock_event_device *wd) { + wd->next_event_forced =0 ;/* * If we woke up early and the tick was reprogrammed in the * meantime then this may be spurious but harmless.
Re: The "clockevents: Prevent timer interrupt starvation" patch causes lockups
Quoted 37 lines · Thomas Gleixner
The one below should cover all possible holes.
Thanks,
tglx
---kernel/time/clockevents.c +6
-1@@ -94,6 +94,9 @@ static int __clockevents_switch_state(struct clock_event_device *dev,if (dev->features &CLOCK_EVT_FEAT_DUMMY )return 0 ; +/* On state transitions clear the forced flag unconditionally */ + dev->next_event_forced =0 ; +/* Transition with new state-specific callbacks */ switch (state) {case CLOCK_EVT_STATE_DETACHED :@@ -366,8 +369,10 @@ int clockevents_program_event(struct clock_event_device *dev, ktime_t expires, bif (delta > (int64_t )dev->min_delta_ns) { delta =min (delta, (int64_t ) dev->max_delta_ns); cycles = ((u64 )delta * dev->mult) >> dev->shift; -if (!dev->set_next_event ((unsigned long ) cycles, dev)) +if (!dev->set_next_event ((unsigned long ) cycles, dev)) { + dev->next_event_forced =0 ;return 0 ; + } }if (dev->next_event_forced)kernel/time/tick-broadcast.c +0
-0
kernel/time/tick-broadcast.c static struct clock_event_device *tick_get_oneshot_wakeup_device(int cpu) · line 114
Quoted 7 lines
kernel/time/tick-broadcast.c +1
-0@@ -108,6 +108,7 @@ static struct clock_event_device *tick_get_oneshot_wakeup_device(int cpu)static void tick_oneshot_wakeup_handler (struct clock_event_device *wd) { + wd->next_event_forced =0 ;/* * If we woke up early and the tick was reprogrammed in the * meantime then this may be spurious but harmless.
Ok, it does fix the problem! Thank you.
The patch itself does not apply cleanly for 7.0 though and I had to adapt it a bit.
Eric Naim
Tested-byRe: The "clockevents: Prevent timer interrupt starvation" patch causes lockups
Quoted 41 lines · Hanabishi
Quoted 40 lines · Thomas Gleixner
The one below should cover all possible holes.
Thanks,
tglx
---kernel/time/clockevents.c +0
-0@@ -94,6 +94,9 @@ static int __clockevents_switch_state(struct clock_event_device *dev, if (dev->features & CLOCK_EVT_FEAT_DUMMY) return 0;+ /* On state transitions clear the forced flag unconditionally */ +
dev->next_event_forced= 0;
+
/* Transition with new state-specific callbacks */ switch (state) {
caseCLOCK_EVT_STATE_DETACHED:kernel/time/clockevents.c +3
-1@@ -366,8 +369,10 @@ int clockevents_program_event(struct clock_event_device *dev, ktime_t expires, b if (delta > (int64_t)dev->min_delta_ns) { delta = min(delta, (int64_t) dev->max_delta_ns); cycles = ((u64)delta * dev->mult) >> dev->shift; - if (!dev->set_next_event((unsigned long) cycles, dev)) + if (!dev->set_next_event((unsigned long) cycles, dev)) { + dev->next_event_forced = 0; return 0; + } } if (dev->next_event_forced)kernel/time/tick-broadcast.c +0
-0@@ -108,6 +108,7 @@ static struct clock_event_device *tick_get_oneshot_wakeup_device(int cpu) static void tick_oneshot_wakeup_handler(struct clock_event_device *wd) {
Quoted 9 lines
kernel/time/tick-broadcast.c static struct clock_event_device *tick_get_oneshot_wakeup_device(int cpu) · line 114
Quoted 4 lines
kernel/time/tick-broadcast.c +1
-0+ wd->next_event_forced = 0; /** If we woke up early and the tick was reprogrammed in the * meantime then this may be spurious but harmless. Ok, it does fix the problem! Thank you.
The patch itself does not apply cleanly for 7.0 though and I had to adapt it a bit.
Sorry for the delay folks! This patch fixes the regression and a user recently confirmed it as well.
Feel free to add:
Signature · 2 lines
Regards, Eric
[tip: timers/urgent] clockevents: Add missing resets of the next_event_forced flag
In reply to Thomas GleixnerThe following commit has been merged into the timers/urgent branch of tip:
The following commit has been merged into the timers/urgent branch of tip:
Commit-ID: 4096fd0e8eaea13ebe5206700b33f49635ae18e5 Gitweb: https://git.kernel.org/tip/4096fd0e8eaea13ebe5206700b33f49635ae18e5 Author: Thomas Gleixner <[email protected]> AuthorDate: Tue, 14 Apr 2026 22:55:01 +02:00 Committer: Thomas Gleixner <[email protected]> CommitterDate: Thu, 16 Apr 2026 21:22:04 +02:00
clockevents: Add missing resets of the next_event_forced flag
The prevention mechanism against timer interrupt starvation missed to reset the next_event_forced flag in a couple of places:
- When the clock event state changes. That can cause the flag to be stale over a shutdown/startup sequence
- When a non-forced event is armed, which then prevents rearming before that event. If that event is far out in the future this will cause missed timer interrupts.
- In the suspend wakeup handler.
That led to stalls which have been reported by several people.
Add the missing resets, which fixes the problems for the reporters.
--- kernel/time/clockevents.c | 7 ++++++- kernel/time/tick-broadcast.c | 1 + 2 files changed, 7 insertions(+), 1 deletion(-)
kernel/time/clockevents.c +6 -1
@@ -94,6 +94,9 @@ static int __clockevents_switch_state(struct clock_event_device *dev,if (dev->features &CLOCK_EVT_FEAT_DUMMY )return 0 ; +/* On state transitions clear the forced flag unconditionally */ + dev->next_event_forced =0 ; +/* Transition with new state-specific callbacks */ switch (state) {case CLOCK_EVT_STATE_DETACHED :
@@ -366,8 +369,10 @@ int clockevents_program_event(struct clock_event_device *dev, ktime_t expires, bif (delta > (int64_t )dev->min_delta_ns) { delta =min (delta, (int64_t ) dev->max_delta_ns); cycles = ((u64 )delta * dev->mult) >> dev->shift; -if (!dev->set_next_event ((unsigned long ) cycles, dev)) +if (!dev->set_next_event ((unsigned long ) cycles, dev)) { + dev->next_event_forced =0 ;return 0 ; + } }if (dev->next_event_forced)
kernel/time/tick-broadcast.c +1 -0
@@ -108,6 +108,7 @@ static struct clock_event_device *tick_get_oneshot_wakeup_device(int cpu)static void tick_oneshot_wakeup_handler (struct clock_event_device *wd) { + wd->next_event_forced =0 ;/* * If we woke up early and the tick was reprogrammed in the * meantime then this may be spurious but harmless.
Re: [tip: timers/urgent] clockevents: Add missing resets of the next_event_forced flag
Quoted 10 lines · On 4/16/26 21:26, tip-bot2 for Thomas Gleixner wrote:
The following commit has been merged into the timers/urgent branch of tip:
Commit-ID: 4096fd0e8eaea13ebe5206700b33f49635ae18e5 Gitweb: https://git.kernel.org/tip/4096fd0e8eaea13ebe5206700b33f49635ae18e5 Author: Thomas Gleixner <[email protected]> AuthorDate: Tue, 14 Apr 2026 22:55:01 +02:00 Committer: Thomas Gleixner <[email protected]> CommitterDate: Thu, 16 Apr 2026 21:22:04 +02:00clockevents: Add missing resets of the
next_event_forcedflag
Just wondering: what's the plan to mainline this? I wonder if this is worth mainlining rather quickly and the tell the stable team right afterwards to queue it up for 7.0.1, as in addition to the two affected people in this thread (one of which stated that "several users from CachyOS reported this regression as well") I noticed three more 7.0 bug reports in the past few days that likely are fixed by the quoted patch:
https://gitlab.freedesktop.org/drm/amd/-/work_items/5178#note_3432195
https://bugzilla.kernel.org/show_bug.cgi?id=221370
https://bugzilla.kernel.org/show_bug.cgi?id=221377
Ciao, Thorsten
Quoted 69 lines
The prevention mechanism against timer interrupt starvation missed to reset the
next_event_forcedflag in a couple of places:- When the clock event state changes. That can cause the flag to be stale over a shutdown/startup sequence
- When a non-forced event is armed, which then prevents rearming before that event. If that event is far out in the future this will cause missed timer interrupts.
- In the suspend wakeup handler.
That led to stalls which have been reported by several people.
Add the missing resets, which fixes the problems for the reporters.
Fixes d6e152d905bd ("clockevents: Prevent timer interrupt starvation")Reported-by Hanabishi <[email protected]>Reported-by Eric Naim <[email protected]>Signed-off-by Thomas Gleixner <[email protected]>Tested-by Hanabishi <[email protected]>Tested-by Eric Naim <[email protected]>--- kernel/time/clockevents.c | 7 ++++++- kernel/time/tick-broadcast.c | 1 + 2 files changed, 7 insertions(+), 1 deletion(-)kernel/time/clockevents.c +6
-1@@ -94,6 +94,9 @@ static int __clockevents_switch_state(struct clock_event_device *dev,if (dev->features &CLOCK_EVT_FEAT_DUMMY )return 0 ; +/* On state transitions clear the forced flag unconditionally */ + dev->next_event_forced =0 ; +/* Transition with new state-specific callbacks */ switch (state) {case CLOCK_EVT_STATE_DETACHED :@@ -366,8 +369,10 @@ int clockevents_program_event(struct clock_event_device *dev, ktime_t expires, bif (delta > (int64_t )dev->min_delta_ns) { delta =min (delta, (int64_t ) dev->max_delta_ns); cycles = ((u64 )delta * dev->mult) >> dev->shift; -if (!dev->set_next_event ((unsigned long ) cycles, dev)) +if (!dev->set_next_event ((unsigned long ) cycles, dev)) { + dev->next_event_forced =0 ;return 0 ; + } }if (dev->next_event_forced)kernel/time/tick-broadcast.c +1
-0@@ -108,6 +108,7 @@ static struct clock_event_device *tick_get_oneshot_wakeup_device(int cpu)static void tick_oneshot_wakeup_handler (struct clock_event_device *wd) { + wd->next_event_forced =0 ;/* * If we woke up early and the tick was reprogrammed in the * meantime then this may be spurious but harmless.
Thomas Gleixner
maintainerRe: [tip: timers/urgent] clockevents: Add missing resets of the next_event_forced flag
Quoted 10 lines · Linux regression tracking (Thorsten Leemhuis)
Quoted 9 lines · On 4/16/26 21:26, tip-bot2 for Thomas Gleixner wrote:
The following commit has been merged into the timers/urgent branch of tip:
Commit-ID: 4096fd0e8eaea13ebe5206700b33f49635ae18e5 Gitweb: https://git.kernel.org/tip/4096fd0e8eaea13ebe5206700b33f49635ae18e5 Author: Thomas Gleixner <[email protected]> AuthorDate: Tue, 14 Apr 2026 22:55:01 +02:00 Committer: Thomas Gleixner <[email protected]> CommitterDate: Thu, 16 Apr 2026 21:22:04 +02:00
Quoted 8 lines
Quoted 1 line
clockevents: Add missing resets of the
next_event_forcedflagJust wondering: what's the plan to mainline this? I wonder if this is worth mainlining rather quickly and the tell the stable team right afterwards to queue it up for 7.0.1, as in addition to the two affected people in this thread (one of which stated that "several users from CachyOS reported this regression as well") I noticed three more 7.0 bug reports in the past few days that likely are fixed by the quoted patch:
It's in Linus tree and I asked the stable folks to withhold the original patch which it fixes, so they can queue both at once.
Thanks,
tglx
Re: [tip: timers/urgent] clockevents: Add missing resets of the next_event_forced flag
Quoted 20 lines · Thomas Gleixner
Quoted 10 lines · Linux regression tracking (Thorsten Leemhuis)
Quoted 9 lines · On 4/16/26 21:26, tip-bot2 for Thomas Gleixner wrote:
The following commit has been merged into the timers/urgent branch of tip:
Commit-ID: 4096fd0e8eaea13ebe5206700b33f49635ae18e5 Gitweb: https://git.kernel.org/tip/4096fd0e8eaea13ebe5206700b33f49635ae18e5 Author: Thomas Gleixner <[email protected]> AuthorDate: Tue, 14 Apr 2026 22:55:01 +02:00 Committer: Thomas Gleixner <[email protected]> CommitterDate: Thu, 16 Apr 2026 21:22:04 +02:00Quoted 8 lines
Quoted 1 line
clockevents: Add missing resets of the
next_event_forcedflagJust wondering: what's the plan to mainline this? I wonder if this is worth mainlining rather quickly and the tell the stable team right afterwards to queue it up for 7.0.1, as in addition to the two affected people in this thread (one of which stated that "several users from CachyOS reported this regression as well") I noticed three more 7.0 bug reports in the past few days that likely are fixed by the quoted patch:
Quoted 2 lines
It's in Linus tree and I asked the stable folks to withhold the original patch which it fixes, so they can queue both at once.
Yeah, I noticed, and many thx! Also many thx for planning the backport, this is great. But that "original patch" is already in 7.0, which makes me wonder:
Should we ask Greg (now CCed) to include a backport (once it exists) for 7.0.1, even if that is in testing already and might mean that this needs another stable-rc or delayed? Because in addition to those three reports I mentioned earlier I noticed one more today:
https://bugzilla.kernel.org/show_bug.cgi?id=221377
And maybe this is the same issue, too:
https://bugzilla.kernel.org/show_bug.cgi?id=221388
IOW: quite a few people are hitting this.
Ciao, Thorsten
Greg KH
maintainerRe: [tip: timers/urgent] clockevents: Add missing resets of the next_event_forced flag
Quoted 34 lines · Thorsten Leemhuis
Quoted 20 lines · Thomas Gleixner
Quoted 10 lines · Linux regression tracking (Thorsten Leemhuis)
Quoted 9 lines · On 4/16/26 21:26, tip-bot2 for Thomas Gleixner wrote:
The following commit has been merged into the timers/urgent branch of tip:
Commit-ID: 4096fd0e8eaea13ebe5206700b33f49635ae18e5 Gitweb: https://git.kernel.org/tip/4096fd0e8eaea13ebe5206700b33f49635ae18e5 Author: Thomas Gleixner <[email protected]> AuthorDate: Tue, 14 Apr 2026 22:55:01 +02:00 Committer: Thomas Gleixner <[email protected]> CommitterDate: Thu, 16 Apr 2026 21:22:04 +02:00Quoted 8 lines
Quoted 1 line
clockevents: Add missing resets of the
next_event_forcedflagJust wondering: what's the plan to mainline this? I wonder if this is worth mainlining rather quickly and the tell the stable team right afterwards to queue it up for 7.0.1, as in addition to the two affected people in this thread (one of which stated that "several users from CachyOS reported this regression as well") I noticed three more 7.0 bug reports in the past few days that likely are fixed by the quoted patch:
Quoted 2 lines
It's in Linus tree and I asked the stable folks to withhold the original patch which it fixes, so they can queue both at once.
Yeah, I noticed, and many thx! Also many thx for planning the backport, this is great. But that "original patch" is already in 7.0, which makes me wonder:
Should we ask Greg (now CCed) to include a backport (once it exists) for 7.0.1, even if that is in testing already and might mean that this needs another stable-rc or delayed? Because in addition to those three reports I mentioned earlier I noticed one more today:
https://bugzilla.kernel.org/show_bug.cgi?id=221377
Quoted 4 lines
And maybe this is the same issue, too:
https://bugzilla.kernel.org/show_bug.cgi?id=221388IOW: quite a few people are hitting this.
I've already dropped this from all of the other stable queues. If you want me to pick up something from linux-next now, for 7.0.1, I'll be glad to do so, just let me know.
thanks,
greg k-h
Greg KH
maintainerRe: [tip: timers/urgent] clockevents: Add missing resets of the next_event_forced flag
Quoted 35 lines · Greg KH
Quoted 34 lines · Thorsten Leemhuis
Quoted 20 lines · Thomas Gleixner
Quoted 10 lines · Linux regression tracking (Thorsten Leemhuis)
Quoted 9 lines · On 4/16/26 21:26, tip-bot2 for Thomas Gleixner wrote:
The following commit has been merged into the timers/urgent branch of tip:
Commit-ID: 4096fd0e8eaea13ebe5206700b33f49635ae18e5 Gitweb: https://git.kernel.org/tip/4096fd0e8eaea13ebe5206700b33f49635ae18e5 Author: Thomas Gleixner <[email protected]> AuthorDate: Tue, 14 Apr 2026 22:55:01 +02:00 Committer: Thomas Gleixner <[email protected]> CommitterDate: Thu, 16 Apr 2026 21:22:04 +02:00Quoted 8 lines
Quoted 1 line
clockevents: Add missing resets of the
next_event_forcedflagJust wondering: what's the plan to mainline this? I wonder if this is worth mainlining rather quickly and the tell the stable team right afterwards to queue it up for 7.0.1, as in addition to the two affected people in this thread (one of which stated that "several users from CachyOS reported this regression as well") I noticed three more 7.0 bug reports in the past few days that likely are fixed by the quoted patch:
Quoted 2 lines
It's in Linus tree and I asked the stable folks to withhold the original patch which it fixes, so they can queue both at once.
Yeah, I noticed, and many thx! Also many thx for planning the backport, this is great. But that "original patch" is already in 7.0, which makes me wonder:
Should we ask Greg (now CCed) to include a backport (once it exists) for 7.0.1, even if that is in testing already and might mean that this needs another stable-rc or delayed? Because in addition to those three reports I mentioned earlier I noticed one more today:
https://bugzilla.kernel.org/show_bug.cgi?id=221377
Quoted 8 lines
Quoted 4 lines
And maybe this is the same issue, too:
https://bugzilla.kernel.org/show_bug.cgi?id=221388IOW: quite a few people are hitting this.
I've already dropped this from all of the other stable queues. If you want me to pick up something from linux-next now, for 7.0.1, I'll be glad to do so, just let me know.
Ah, nevermind, I see Thomas sent this right before I wrote this email, I'll go pick it up after my morning coffee kicks in :)
thanks,
greg k-h
Thomas Gleixner
maintainerPATCH 2/12 hrtimer: Provide hrtimer_start_range_ns_user()
In reply to Thomas GleixnerCalvin reported an odd NMI watchdog lockup which claims that the CPU locked up in user space. He provided a reproducer, which set's up a timerfd based timer and then rearms it in a loop with an absolute expiry time of 1ns.
As the expiry time is in the past, the timer ends up as the first expiring timer in the per CPU hrtimer base and the clockevent device is programmed with the minimum delta value. If the machine is fast enough, this ends up in a endless loop of programming the delta value to the minimum value defined by the clock event device, before the timer interrupt can fire, which starves the interrupt and consequently triggers the lockup detector because the hrtimer callback of the lockup mechanism is never invoked.
The clockevents code already has a last resort mechanism to prevent that, but it's sensible to catch such issues before trying to reprogram the clock event device.
Provide a variant of hrtimer_start_range_ns(), which sanity checks the timer after queueing it. It does not so before because the timer might be armed and therefore needs to be dequeued. also we optimize for the latest possible point to check, so that the clock event prevention is avoided as much as possible.
If the timer is already expired _before_ the clock event is reprogrammed, remove the timer from the queue and signal to the caller that the operation failed by returning false.
That allows the caller to take immediate action without going through the loops and hoops of the hrtimer interrupt.
The queueing code can't invoke the timer callback as the caller might hold a lock which is taken in the callback.
Add a tracepoint which allows to analyze the expired at start situation.
--- include/linux/hrtimer.h | 20 +++++- include/trace/events/timer.h | 13 ++++ kernel/time/hrtimer.c | 135 ++++++++++++++++++++++++++++++++++++++----- 3 files changed, 151 insertions(+), 17 deletions(-)
include/linux/hrtimer.h +17 -3
@@ -230,6 +230,9 @@ static inline void destroy_hrtimer_on_stextern void hrtimer_start_range_ns (struct hrtimer *timer,ktime_t tim,u64 range_ns,const enum hrtimer_mode mode); +extern bool hrtimer_start_range_ns_user (struct hrtimer *timer,ktime_t tim, +u64 range_ns,const enum hrtimer_mode mode); +/** * hrtimer_start - (re)start an hrtimer * @timer: the timer to be added
@@ -247,17 +250,28 @@ static inline void hrtimer_start(structextern int hrtimer_cancel (struct hrtimer *timer);extern int hrtimer_try_to_cancel (struct hrtimer *timer); -static inline void hrtimer_start_expires (struct hrtimer *timer, -enum hrtimer_mode mode) +static inline void hrtimer_start_expires (struct hrtimer *timer,enum hrtimer_mode mode) { -u64 delta;ktime_t soft, hard; +u64 delta; + soft =hrtimer_get_softexpires (timer); hard =hrtimer_get_expires (timer); delta =ktime_to_ns (ktime_sub (hard, soft));hrtimer_start_range_ns (timer, soft, delta, mode); } +static inline bool hrtimer_start_expires_user (struct hrtimer *timer,enum hrtimer_mode mode) +{ +ktime_t soft, hard; +u64 delta; + + soft =hrtimer_get_softexpires (timer); + hard =hrtimer_get_expires (timer); + delta =ktime_to_ns (ktime_sub (hard, soft)); +return hrtimer_start_range_ns_user (timer, soft, delta, mode); +} +void hrtimer_sleeper_start_expires (struct hrtimer_sleeper *sl,enum hrtimer_mode mode);
include/trace/events/timer.h +13 -0
@@ -297,6 +297,19 @@ DECLARE_EVENT_CLASS(hrtimer_class, );/** +* hrtimer_start_expired - Invoked when a expired timer was started +* @hrtimer: pointer to struct hrtimer +* +* Preceeded by a hrtimer_start tracepoint. +*/ +DEFINE_EVENT (hrtimer_class, hrtimer_start_expired, + + TP_PROTO(struct hrtimer *hrtimer), + +TP_ARGS (hrtimer) +); + +/** * hrtimer_expire_exit - called immediately after the hrtimer callback returns * @hrtimer: pointer to struct hrtimer *
kernel/time/hrtimer.c +121 -14
167 lines not on this page · raw message
Re: [patch 02/12] hrtimer: Provide hrtimer_start_range_ns_user()
Quoted 28 lines · Thomas Gleixner
kernel/time/hrtimer.c +9
-11+enum { +HRTIMER_REPROGRAM_NONE , +HRTIMER_REPROGRAM , +HRTIMER_REPROGRAM_FORCE , +}; +static int hrtimer_start_range_ns_common (struct hrtimer *timer,ktime_t tim, +u64 delta_ns,const enum hrtimer_mode mode, +struct hrtimer_clock_base *base)@@ -1315,25 +1337,110 @@ void hrtimer_start_range_ns(struct hrtimstruct hrtimer_clock_base *base;unsigned long flags; -/* -* Check whether the HRTIMER_MODE_SOFT bit and hrtimer.is_soft -* match on CONFIG_PREEMPT_RT = n. With PREEMPT_RT check the hard -* expiry mode because unmarked timers are moved to softirq expiry. -*/ -if (!IS_ENABLED (CONFIG_PREEMPT_RT )) -WARN_ON_ONCE (!(mode &HRTIMER_MODE_SOFT ) ^ !timer->is_soft); -else -WARN_ON_ONCE (!(mode &HRTIMER_MODE_HARD ) ^ !timer->is_hard); - base =lock_hrtimer_base (timer, &flags); -if (__hrtimer_start_range_ns (timer, tim, delta_ns, mode, base)) +switch (hrtimer_start_range_ns_common (timer, tim, delta_ns, mode, base)) {
kernel/time/hrtimer.c void hrtimer_start_range_ns(struct hrtim · line 1352
Quoted 10 lines
kernel/time/hrtimer.c +6
-0+case HRTIMER_REPROGRAM :hrtimer_reprogram (timer, true); +break ; +case HRTIMER_REPROGRAM_FORCE : +hrtimer_force_reprogram (timer->base->cpu_base,1 ); +break ; + }unlock_hrtimer_base (timer, &flags); }
Something is going to figure out that hrtimer_start_range_ns_common() is really returning that enum and then complain you don't handle NONE :-)
Anyway, to me it would make sense to instead pass that value to hrtimer_reprogram() as the second argument. But this works I suppose.
Thomas Gleixner
maintainerRe: [patch 02/12] hrtimer: Provide hrtimer_start_range_ns_user()
Quoted 11 lines · Peter Zijlstra
Quoted 10 lines · Thomas Gleixner
kernel/time/hrtimer.c +7
-1-if (__hrtimer_start_range_ns (timer, tim, delta_ns, mode, base)) +switch (hrtimer_start_range_ns_common (timer, tim, delta_ns, mode, base)) { +case HRTIMER_REPROGRAM :hrtimer_reprogram (timer, true); +break ; +case HRTIMER_REPROGRAM_FORCE : +hrtimer_force_reprogram (timer->base->cpu_base,1 ); +break ; + }
Quoted 5 lines
Quoted 2 lines
unlock_hrtimer_base(timer, &flags);
}Something is going to figure out that
hrtimer_start_range_ns_common()is really returning that enum and then complain you don't handle NONE :-)
:)
Quoted 2 lines
Anyway, to me it would make sense to instead pass that value to
hrtimer_reprogram()as the second argument. But this works I suppose.
I can do that too. Splitting it this way made me more comfortable to validate the logic I was implementing.
Re: [patch 02/12] hrtimer: Provide hrtimer_start_range_ns_user()
In reply to Thomas GleixnerQuoted 78 lines · Thomas Gleixner
kernel/time/hrtimer.c +78
-0+static inline bool hrtimer_check_user_timer (struct hrtimer *timer) +{ +struct hrtimer_cpu_base *cpu_base = timer->base->cpu_base; +ktime_t expires; + +/* +* This uses soft expires because that's the user provided +* expiry time, while expires can be further in the past +* due to a slack value added to the user expiry time. +*/ + expires =hrtimer_get_softexpires (timer); + +/* Convert to monotonic */ + expires =ktime_sub (expires, timer->base->offset); + +/* +* Check whether this timer will end up as the first expiring timer in +* the CPU base. If not, no further checks required as it's then +* guaranteed to expire in the future. +*/ +if (expires >= cpu_base->expires_next) +return true; + +/* Validate that the expiry time is in the future. */ +if (expires >ktime_get ()) +return true; + +debug_deactivate (timer); +__remove_hrtimer (timer, timer->base,HRTIMER_STATE_INACTIVE , false); +trace_hrtimer_start_expired (timer); +return false; +} + +static bool hrtimer_reprogram_user (struct hrtimer *timer) +{ +if (!hrtimer_check_user_timer (timer)) +return false; +hrtimer_reprogram (timer, true); +return true; +} + +static bool hrtimer_force_reprogram_user (struct hrtimer *timer) +{ +bool ret =hrtimer_check_user_timer (timer); + +/* +* The base must always be reevaluated, independent of the result +* above because the timer was the first pending timer. +*/ +hrtimer_force_reprogram (timer->base->cpu_base,1 ); +return ret; +} + +/** +* hrtimer_start_range_ns_user - (re)start an user controlled hrtimer +* @timer: the timer to be added +* @tim: expiry time +* @delta_ns: "slack" range for the timer +* @mode: timer mode: absolute (HRTIMER_MODE_ABS) or +* relative (HRTIMER_MODE_REL), and pinned (HRTIMER_MODE_PINNED); +* softirq based mode is considered for debug purpose only! +* +* Returns: True when the timer was queued, false if it was already expired +* +* This function cannot invoke the timer callback for expired timers as it might +* be called under a lock which the timer callback needs to acquire. So the +* caller has to handle that case. +*/ +bool hrtimer_start_range_ns_user (struct hrtimer *timer,ktime_t tim, +u64 delta_ns,const enum hrtimer_mode mode) +{ +struct hrtimer_clock_base *base; +unsigned long flags; +bool ret = true; + + base =lock_hrtimer_base (timer, &flags); +switch (hrtimer_start_range_ns_common (timer, tim, delta_ns, mode, base)) { +case HRTIMER_REPROGRAM :
kernel/time/hrtimer.c void hrtimer_start_range_ns(struct hrtim · line 1442
Quoted 10 lines
kernel/time/hrtimer.c +10
-0+ ret =hrtimer_reprogram_user (timer); +break ; +case HRTIMER_REPROGRAM_FORCE : + ret =hrtimer_force_reprogram_user (timer); +break ; + } +unlock_hrtimer_base (timer, &flags); +return ret; +} +EXPORT_SYMBOL_GPL (hrtimer_start_range_ns_user);
Can we do that hrtimer_check_user_timer() in hrtimer_start_range_ns_user() and then not duplicate hrtimer_*reprogram() ?
Thomas Gleixner
maintainerRe: [patch 02/12] hrtimer: Provide hrtimer_start_range_ns_user()
Quoted 8 lines · Peter Zijlstra
kernel/time/hrtimer.c void hrtimer_start_range_ns(struct hrtim · line 1442
Quoted 3 lines · Thomas Gleixner
kernel/time/hrtimer.c +3
-0+return ret; +} +EXPORT_SYMBOL_GPL (hrtimer_start_range_ns_user);Can we do that
hrtimer_check_user_timer()inhrtimer_start_range_ns_user()and then not duplicate hrtimer_*reprogram() ?
We probably can. Let me have a look.
Thomas Gleixner
maintainerPATCH 3/12 hrtimer: Use hrtimer_start_expires_user() for hrtimer sleepers
In reply to Thomas GleixnerMost hrtimer sleepers are user controlled and user space can hand arbitrary expiry values in as long as they are valid timespecs. If the expiry value is in the past then this requires a full loop through reprogramming the clock event device, taking the hrtimer interrupt, waking the task and reprogram again.
Use hrtimer_start_expires_user() which avoids the full round trip by checking the timer for expiry on enqueue.
--- kernel/time/hrtimer.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-)
kernel/time/hrtimer.c +5 -1
@@ -2152,7 +2152,11 @@ void hrtimer_sleeper_start_expires(strucif (IS_ENABLED (CONFIG_PREEMPT_RT ) && sl->timer.is_hard) mode |=HRTIMER_MODE_HARD ; -hrtimer_start_expires (&sl->timer, mode); +/* If already expired, clear the task pointer and set current state to running */ +if (!hrtimer_start_expires_user (&sl->timer, mode)) { + sl->task =NULL ; +__set_current_state (TASK_RUNNING ); + } }EXPORT_SYMBOL_GPL (hrtimer_sleeper_start_expires);
Re: [patch 03/12] hrtimer: Use hrtimer_start_expires_user() for hrtimer sleepers
Quoted 6 lines · Thomas Gleixner
Most hrtimer sleepers are user controlled and user space can hand arbitrary expiry values in as long as they are valid timespecs. If the expiry value is in the past then this requires a full loop through reprogramming the clock event device, taking the hrtimer interrupt, waking the task and reprogram again.
Quoted 6 lines
Use
hrtimer_start_expires_user()which avoids the full round trip by checking the timer for expiry on enqueue.Signed-off-by Thomas Gleixner <[email protected]>Cc Anna-Maria Behnsen <[email protected]>Cc Frederic Weisbecker <[email protected]>
Quoted 20 lines
--- kernel/time/hrtimer.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-)kernel/time/hrtimer.c +5
-1@@ -2152,7 +2152,11 @@ void hrtimer_sleeper_start_expires(strucif (IS_ENABLED (CONFIG_PREEMPT_RT ) && sl->timer.is_hard) mode |=HRTIMER_MODE_HARD ; -hrtimer_start_expires (&sl->timer, mode); +/* If already expired, clear the task pointer and set current state to running */ +if (!hrtimer_start_expires_user (&sl->timer, mode)) { + sl->task =NULL ; +__set_current_state (TASK_RUNNING ); + } }EXPORT_SYMBOL_GPL (hrtimer_sleeper_start_expires);
Thomas Gleixner
maintainerPATCH 4/12 posix-timers: Expand timer_[re]arm() callbacks with a boolean return value
In reply to Thomas GleixnerIn order to catch expiry times which are already in the past the timer_arm() and timer_rearm() callbacks need to be able to report back to the caller whether the timer has been queued or not.
Change the function signature and let all implementations return true for now. While at it simplify posix_cpu_timer_rearm().
No functional change intended.
--- kernel/time/alarmtimer.c | 6 ++++-- kernel/time/posix-cpu-timers.c | 18 ++++++++++-------- kernel/time/posix-timers.c | 6 ++++-- kernel/time/posix-timers.h | 4 ++-- 4 files changed, 20 insertions(+), 14 deletions(-)
kernel/time/alarmtimer.c +4 -2
@@ -523,12 +523,13 @@ static void alarm_handle_timer(struct al* alarm_timer_rearm - Posix timer callback for rearming timer * @timr: Pointer to the posixtimer data struct */ -static void alarm_timer_rearm (struct k_itimer *timr) +static bool alarm_timer_rearm (struct k_itimer *timr) {struct alarm *alarm = &timr->it.alarm.alarmtimer; timr->it_overrun +=alarm_forward_now (alarm, timr->it_interval);alarm_start (alarm, alarm->node.expires); +return true; }/**
@@ -584,7 +585,7 @@ static void alarm_timer_wait_running(str* @absolute: Expiry value is absolute time * @sigev_none: Posix timer does not deliver signals */ -static void alarm_timer_arm (struct k_itimer *timr,ktime_t expires, +static bool alarm_timer_arm (struct k_itimer *timr,ktime_t expires,bool absolute,bool sigev_none) {struct alarm *alarm = &timr->it.alarm.alarmtimer;
@@ -596,6 +597,7 @@ static void alarm_timer_arm(struct k_iti alarm->node.expires = expires; else alarm_start(&timr->it.alarm.alarmtimer, expires); +return true; }/**
kernel/time/posix-cpu-timers.c +10 -8
@@ -19,7 +19,7 @@#include "posix-timers.h" -static void posix_cpu_timer_rearm (struct k_itimer *timer); +static bool posix_cpu_timer_rearm (struct k_itimer *timer);void posix_cputimers_group_init (struct posix_cputimers *pct,u64 cpu_limit) {
@@ -1011,24 +1011,27 @@ static void check_process_timers(struct/* * This is called from the signal code (via posixtimer_rearm) * when the last timer signal was delivered and we have to reload the timer. +* +* Return true unconditionally so the core code assumes the timer to be +* armed. Otherwise it would requeue the signal. */ -static void posix_cpu_timer_rearm (struct k_itimer *timer) +static bool posix_cpu_timer_rearm (struct k_itimer *timer) {clockid_t clkid =CPUCLOCK_WHICH (timer->it_clock); -struct task_struct *p;struct sighand_struct *sighand; +struct task_struct *p;unsigned long flags;u64 now; -rcu_read_lock (); +guard (rcu)(); p =cpu_timer_task_rcu (timer);if (!p) - goto out; +return true;/* Protect timer list r/w in arm_timer() */ sighand =lock_task_sighand (p, &flags);if (unlikely (sighand ==NULL )) - goto out; +return true;/* * Fetch the current sample and update the timer's expiry time.
@@ -1045,8 +1048,7 @@ static void posix_cpu_timer_rearm(struct*/ arm_timer (timer, p);unlock_task_sighand (p, &flags); -out: -rcu_read_unlock (); +return true; }/**
kernel/time/posix-timers.c +4 -2
@@ -288,12 +288,13 @@ static inline int timer_overrun_to_int(sreturn (int )timr->it_overrun_last; } -static void common_hrtimer_rearm (struct k_itimer *timr) +static bool common_hrtimer_rearm (struct k_itimer *timr) {struct hrtimer *timer = &timr->it.real.timer; timr->it_overrun +=hrtimer_forward_now (timer, timr->it_interval);hrtimer_restart (timer); +return true; }static bool __posixtimer_deliver_signal (struct kernel_siginfo *info,struct k_itimer *timr)
@@ -795,7 +796,7 @@ SYSCALL_DEFINE1(timer_getoverrun, timer_return timer_overrun_to_int (scoped_timer); } -static void common_hrtimer_arm (struct k_itimer *timr,ktime_t expires, +static bool common_hrtimer_arm (struct k_itimer *timr,ktime_t expires,bool absolute,bool sigev_none) {struct hrtimer *timer = &timr->it.real.timer;
@@ -822,6 +823,7 @@ static void common_hrtimer_arm(struct k_if (!sigev_none)hrtimer_start_expires (timer,HRTIMER_MODE_ABS ); +return true; }static int common_hrtimer_try_to_cancel (struct k_itimer *timr)
kernel/time/posix-timers.h +2 -2
@@ -27,11 +27,11 @@ struct k_clock {int (*timer_del)(struct k_itimer *timr);void (*timer_get)(struct k_itimer *timr,struct itimerspec64 *cur_setting); -void (*timer_rearm)(struct k_itimer *timr); +bool (*timer_rearm)(struct k_itimer *timr);s64 (*timer_forward)(struct k_itimer *timr,ktime_t now);ktime_t (*timer_remaining)(struct k_itimer *timr,ktime_t now);int (*timer_try_to_cancel)(struct k_itimer *timr); -void (*timer_arm)(struct k_itimer *timr,ktime_t expires, +bool (*timer_arm)(struct k_itimer *timr,ktime_t expires,bool absolute,bool sigev_none);void (*timer_wait_running)(struct k_itimer *timr); };
Re: [patch 04/12] posix-timers: Expand timer_[re]arm() callbacks with a boolean return value
Quoted 10 lines · Thomas Gleixner
In order to catch expiry times which are already in the past the
timer_arm()andtimer_rearm()callbacks need to be able to report back to the caller whether the timer has been queued or not.Change the function signature and let all implementations return true for now. While at it simplify
posix_cpu_timer_rearm().No functional change intended.
Signed-off-by Thomas Gleixner <[email protected]>
Re: [patch 04/12] posix-timers: Expand timer_[re]arm() callbacks with a boolean return value
In reply to Thomas GleixnerQuoted 8 lines · Thomas Gleixner
In order to catch expiry times which are already in the past the
timer_arm()andtimer_rearm()callbacks need to be able to report back to the caller whether the timer has been queued or not.Change the function signature and let all implementations return true for now. While at it simplify
posix_cpu_timer_rearm().
Quoted 7 lines
No functional change intended.
Signed-off-by Thomas Gleixner <[email protected]>Cc John Stultz <[email protected]>Cc Stephen Boyd <[email protected]>Cc Anna-Maria Behnsen <[email protected]>Cc Frederic Weisbecker <[email protected]>
Thomas Gleixner
maintainerPATCH 5/12 posix-timers: Handle the timer_[re]arm() return value
In reply to Thomas GleixnerThe [re]arm callbacks will return true when the timer was queued and false if it was already expired at enqueue time.
In both cases the call sites can trivially queue the signal right there, when the timer was already expired.
--- kernel/time/posix-timers.c | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-)
kernel/time/posix-timers.c +17 -5
@@ -299,6 +299,8 @@ static bool common_hrtimer_rearm(structstatic bool __posixtimer_deliver_signal (struct kernel_siginfo *info,struct k_itimer *timr) { +bool queued; +guard (spinlock)(&timr->it_lock);/*
@@ -312,12 +314,18 @@ static bool __posixtimer_deliver_signal(if (!timr->it_interval ||WARN_ON_ONCE (timr->it_status !=POSIX_TIMER_REQUEUE_PENDING ))return true; - timr->kclock->timer_rearm (timr); - timr->it_status =POSIX_TIMER_ARMED ; +/* timer_rearm() updates timr::it_overrun */ + queued = timr->kclock->timer_rearm (timr); + timr->it_overrun_last = timr->it_overrun; timr->it_overrun =-1LL ; ++timr->it_signal_seq; info->si_overrun =timer_overrun_to_int (timr); + +if (queued) + timr->it_status =POSIX_TIMER_ARMED ; +else +posix_timer_queue_signal (timr);return true; }
@@ -905,9 +913,13 @@ int common_timer_set(struct k_itimer *ti expires =timens_ktime_to_host (timr->it_clock, expires); sigev_none = timr->it_sigev_notify ==SIGEV_NONE ; - kc->timer_arm (timr, expires, flags &TIMER_ABSTIME , sigev_none); -if (!sigev_none) - timr->it_status =POSIX_TIMER_ARMED ; +if (kc->timer_arm (timr, expires, flags &TIMER_ABSTIME , sigev_none)) { +if (!sigev_none) + timr->it_status =POSIX_TIMER_ARMED ; + }else { +/* Timer was already expired, queue the signal */ +posix_timer_queue_signal (timr); + }return 0 ; }
Re: [patch 05/12] posix-timers: Handle the timer_[re]arm() return value
Quoted 7 lines · Thomas Gleixner
The [re]arm callbacks will return true when the timer was queued and false if it was already expired at enqueue time.
In both cases the call sites can trivially queue the signal right there, when the timer was already expired.
Signed-off-by Thomas Gleixner <[email protected]>
Thomas Gleixner
maintainerPATCH 6/12 posix-timers: Switch to hrtimer_start_expires_user()
In reply to Thomas GleixnerSwitch the arm and rearm callbacks for hrtimer based posix timers over to hrtimer_start_expires_user() so that already expired timers are not queued. Hand the result back to the caller, which then queues the signal.
--- kernel/time/posix-timers.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-)
kernel/time/posix-timers.c +6 -5
@@ -293,8 +293,7 @@ static bool common_hrtimer_rearm(structstruct hrtimer *timer = &timr->it.real.timer; timr->it_overrun +=hrtimer_forward_now (timer, timr->it_interval); -hrtimer_restart (timer); -return true; +return hrtimer_start_expires_user (timer,HRTIMER_MODE_ABS ); }static bool __posixtimer_deliver_signal (struct kernel_siginfo *info,struct k_itimer *timr)
@@ -829,9 +828,11 @@ static bool common_hrtimer_arm(struct k_ expires =ktime_add_safe (expires,hrtimer_cb_get_time (timer));hrtimer_set_expires (timer, expires); -if (!sigev_none) -hrtimer_start_expires (timer,HRTIMER_MODE_ABS ); -return true; +/* For sigev_none pretend that the timer is queued */ +if (sigev_none) +return true; + +return hrtimer_start_expires_user (timer,HRTIMER_MODE_ABS ); }static int common_hrtimer_try_to_cancel (struct k_itimer *timr)
Re: [patch 06/12] posix-timers: Switch to hrtimer_start_expires_user()
Quoted 5 lines · Thomas Gleixner
Switch the arm and rearm callbacks for hrtimer based posix timers over to
hrtimer_start_expires_user()so that already expired timers are not queued. Hand the result back to the caller, which then queues the signal.Signed-off-by Thomas Gleixner <[email protected]>
Thomas Gleixner
maintainerPATCH 7/12 alarmtimer: Provide alarmtimer_start()
In reply to Thomas GleixnerAlarm timers utilize hrtimers for normal operation and only switch to the RTC on suspend. In order to catch already expired timers early and without going through a timer interrupt cycle, provide a new start function which internally uses hrtimer_start_range_ns_user().
If hrtimer_start_range_ns_user() detects an already expired timer, it does not queue it. In that case remove the timer from the alarm base as well.
Return the status queued or not back to the caller to handle the early expiry.
--- include/linux/alarmtimer.h | 6 ++++++ kernel/time/alarmtimer.c | 28 ++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+)
include/linux/alarmtimer.h +6 -0
@@ -42,8 +42,14 @@ struct alarm {void *data; }; +static __always_inline ktime_t alarm_get_expires(struct alarm *alarm) +{ +return alarm->node.expires; +} +void alarm_init (struct alarm *alarm,enum alarmtimer_type type,void (*function)(struct alarm *,ktime_t )); +bool alarmtimer_start (struct alarm *alarm,ktime_t expires,bool relative);void alarm_start (struct alarm *alarm,ktime_t start);void alarm_start_relative (struct alarm *alarm,ktime_t start);void alarm_restart (struct alarm *alarm);
kernel/time/alarmtimer.c +28 -0
@@ -365,6 +365,34 @@ void alarm_start_relative(struct alarm * }EXPORT_SYMBOL_GPL (alarm_start_relative); +/** +* alarmtimer_start - Sets an alarm to fire +* @alarm: Pointer to alarm to set +* @expires: Expiry time +* @relative: True if @expires is relative +* +* Returns: True if the alarm was queued. False if it already expired +*/ +bool alarmtimer_start (struct alarm *alarm,ktime_t expires,bool relative) +{ +struct alarm_base *base = &alarm_bases[alarm->type]; + +if (relative) + expires =ktime_add_safe (expires, base->get_ktime ()); + +trace_alarmtimer_start (alarm, base->get_ktime ()); + +guard (spinlock_irqsave)(&base->lock); + alarm->node.expires = expires; +alarmtimer_enqueue (base, alarm); +if (!hrtimer_start_range_ns_user (&alarm->timer, expires,0 ,HRTIMER_MODE_ABS )) { +alarmtimer_dequeue (base, alarm); +return false; + } +return true; +} +EXPORT_SYMBOL_GPL (alarmtimer_start); +void alarm_restart (struct alarm *alarm) {struct alarm_base *base = &alarm_bases[alarm->type];
Re: [patch 07/12] alarmtimer: Provide alarmtimer_start()
Quoted 5 lines · Thomas Gleixner
Alarm timers utilize hrtimers for normal operation and only switch to the RTC on suspend. In order to catch already expired timers early and without going through a timer interrupt cycle, provide a new start function which internally uses
hrtimer_start_range_ns_user().
Quoted 7 lines
If
hrtimer_start_range_ns_user()detects an already expired timer, it does not queue it. In that case remove the timer from the alarm base as well.Return the status queued or not back to the caller to handle the early expiry.
Signed-off-by Thomas Gleixner <[email protected]>
Not familiar with this code, but my head hurts from the:
alarm_alarm_timer_alarmtimer_
prefixes, what's what?
Thomas Gleixner
maintainerRe: [patch 07/12] alarmtimer: Provide alarmtimer_start()
Quoted 14 lines · Peter Zijlstra
Quoted 5 lines · Thomas Gleixner
Alarm timers utilize hrtimers for normal operation and only switch to the RTC on suspend. In order to catch already expired timers early and without going through a timer interrupt cycle, provide a new start function which internally uses
hrtimer_start_range_ns_user().Quoted 7 lines
If
hrtimer_start_range_ns_user()detects an already expired timer, it does not queue it. In that case remove the timer from the alarm base as well.Return the status queued or not back to the caller to handle the early expiry.
Signed-off-by Thomas Gleixner <[email protected]>
Quoted 7 lines
Not familiar with this code, but my head hurts from the:
alarm_alarm_timer_alarmtimer_prefixes, what's what?
Yeah. I should have named it alarm_timer_start(). Let me fix this.
Re: [patch 07/12] alarmtimer: Provide alarmtimer_start()
In reply to Thomas GleixnerQuoted 9 lines · Thomas Gleixner
Alarm timers utilize hrtimers for normal operation and only switch to the RTC on suspend. In order to catch already expired timers early and without going through a timer interrupt cycle, provide a new start function which internally uses
hrtimer_start_range_ns_user().If
hrtimer_start_range_ns_user()detects an already expired timer, it does not queue it. In that case remove the timer from the alarm base as well.
Quoted 6 lines
Return the status queued or not back to the caller to handle the early expiry.
Signed-off-by Thomas Gleixner <[email protected]>Cc John Stultz <[email protected]>Cc Stephen Boyd <[email protected]>
suggested name change)
Thomas Gleixner
maintainerPATCH 8/12 alarmtimer: Convert posix timer functions to alarmtimer_start()
In reply to Thomas GleixnerUse the new alarmtimer_start() for arming and rearming posix interval timers and for clock_nanosleep() so that already expired timers do not go through the full timer interrupt cycle.
--- kernel/time/alarmtimer.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-)
kernel/time/alarmtimer.c +13 -7
@@ -556,8 +556,7 @@ static bool alarm_timer_rearm(struct k_istruct alarm *alarm = &timr->it.alarm.alarmtimer; timr->it_overrun +=alarm_forward_now (alarm, timr->it_interval); -alarm_start (alarm, alarm->node.expires); -return true; +return alarmtimer_start (alarm, alarm->node.expires, false); }/**
@@ -621,11 +620,16 @@ static bool alarm_timer_arm(struct k_itiif (!absolute) expires =ktime_add_safe (expires, base->get_ktime ()); -if (sigev_none) + +/* +* sigev_none needs to update the expires value and pretend +* that the timer is queued +*/ +if (sigev_none) { alarm->node.expires = expires; -else -alarm_start (&timr->it.alarm.alarmtimer, expires); -return true; +return true; + } +return alarmtimer_start (&timr->it.alarm.alarmtimer, expires, false); }/**
@@ -732,7 +736,9 @@ static int alarmtimer_do_nsleep(struct a alarm->data = (void *)current; do { set_current_state(TASK_INTERRUPTIBLE); - alarm_start(alarm, absexp); + if (!alarmtimer_start(alarm, absexp, false)) + alarm->data = NULL; + if (likely(alarm->data)) schedule();
Re: [patch 08/12] alarmtimer: Convert posix timer functions to alarmtimer_start()
Quoted 11 lines · Thomas Gleixner
Use the new
alarmtimer_start()for arming and rearming posix interval timers and forclock_nanosleep()so that already expired timers do not go through the full timer interrupt cycle.Signed-off-by Thomas Gleixner <[email protected]>Cc John Stultz <[email protected]>Cc Stephen Boyd <[email protected]>--- kernel/time/alarmtimer.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-)
Thomas Gleixner
maintainerPATCH 9/12 fs/timerfd: Use the new alarm/hrtimer functions
In reply to Thomas GleixnerLike any other user controlled interface, timerfd based timers can be programmed with expiry times in the past or vary small intervals.
Both hrtimer and alarmtimer provide new interfaces which return the queued state of the timer. If the timer was already expired, then let the callsite handle the timerfd context update so that the full round trip through the hrtimer interrupt is avoided.
--- fs/timerfd.c | 115 +++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 66 insertions(+), 49 deletions(-)
fs/timerfd.c +66 -49
@@ -55,6 +55,15 @@ static inline bool isalarm(struct timerf ctx->clockid ==CLOCK_BOOTTIME_ALARM ; } +static void __timerfd_triggered (struct timerfd_ctx *ctx) +{ +lockdep_assert_held (&ctx->wqh.lock); + + ctx->expired =1 ; + ctx->ticks++; +wake_up_locked_poll (&ctx->wqh,EPOLLIN ); +} +/* * This gets called when the timer event triggers. We set the "expired" * flag, but we do not re-arm the timer (in case it's necessary,
@@ -62,13 +71,8 @@ static inline bool isalarm(struct timerf*/ static void timerfd_triggered (struct timerfd_ctx *ctx) { -unsigned long flags; - -spin_lock_irqsave (&ctx->wqh.lock, flags); - ctx->expired =1 ; - ctx->ticks++; -wake_up_locked_poll (&ctx->wqh,EPOLLIN ); -spin_unlock_irqrestore (&ctx->wqh.lock, flags); +guard (spinlock_irqsave)(&ctx->wqh.lock); +__timerfd_triggered (ctx); }static enum hrtimer_restart timerfd_tmrproc (struct hrtimer *htmr)
@@ -184,15 +188,52 @@ static ktime_t timerfd_get_remaining(strreturn remaining <0 ?0 : remaining; } +static void timerfd_alarm_start (struct timerfd_ctx *ctx,ktime_t exp,bool relative) +{ +/* Start the timer. If it's expired already, handle the callback. */ +if (!alarmtimer_start (&ctx->t.alarm, exp, relative)) +__timerfd_triggered (ctx); +} + +static u64 timerfd_alarm_restart (struct timerfd_ctx *ctx) +{ +u64 ticks =alarm_forward_now (&ctx->t.alarm, ctx->tintv) -1 ; + +timerfd_alarm_start (ctx,alarm_get_expires (&ctx->t.alarm), false); +return ticks; +} + +static void timerfd_hrtimer_start (struct timerfd_ctx *ctx,ktime_t exp, +const enum hrtimer_mode mode) +{ +/* Start the timer. If it's expired already, handle the callback. */ +if (!hrtimer_start_range_ns_user (&ctx->t.tmr, exp,0 , mode)) +__timerfd_triggered (ctx); +} + +static u64 timerfd_hrtimer_restart (struct timerfd_ctx *ctx) +{ +u64 ticks =hrtimer_forward_now (&ctx->t.tmr, ctx->tintv) -1 ; + +timerfd_hrtimer_start (ctx,hrtimer_get_expires (&ctx->t.tmr),HRTIMER_MODE_ABS ); +return ticks; +} + +static u64 timerfd_restart (struct timerfd_ctx *ctx) +{ +if (isalarm (ctx)) +return timerfd_alarm_restart (ctx); +return timerfd_hrtimer_restart (ctx); +} +static int timerfd_setup (struct timerfd_ctx *ctx,int flags,const struct itimerspec64 *ktmr) { +int clockid = ctx->clockid;enum hrtimer_mode htmode;ktime_t texp; -int clockid = ctx->clockid; - htmode = (flags &TFD_TIMER_ABSTIME ) ? -HRTIMER_MODE_ABS :HRTIMER_MODE_REL ; + htmode = (flags &TFD_TIMER_ABSTIME ) ?HRTIMER_MODE_ABS :HRTIMER_MODE_REL ; texp =timespec64_to_ktime (ktmr->it_value); ctx->expired =0 ;
@@ -206,20 +247,15 @@ static int timerfd_setup(struct timerfd_ timerfd_alarmproc); }else {hrtimer_setup (&ctx->t.tmr, timerfd_tmrproc, clockid, htmode); -hrtimer_set_expires (&ctx->t.tmr, texp); }if (texp !=0 ) {if (flags &TFD_TIMER_ABSTIME ) texp =timens_ktime_to_host (clockid, texp); -if (isalarm (ctx)) { -if (flags &TFD_TIMER_ABSTIME ) -alarm_start (&ctx->t.alarm, texp); -else -alarm_start_relative (&ctx->t.alarm, texp); - }else { -hrtimer_start (&ctx->t.tmr, texp, htmode); - } +if (isalarm (ctx)) +timerfd_alarm_start (ctx, texp, !(flags &TFD_TIMER_ABSTIME )); +else +timerfd_hrtimer_start (ctx, texp, htmode);if (timerfd_canceled (ctx))return -ECANCELED ;
@@ -287,27 +323,19 @@ static ssize_t timerfd_read_iter(struct }if (ctx->ticks) { - ticks = ctx->ticks; +unsigned int expired = ctx->expired; -if (ctx->expired && ctx->tintv) { -/* -* If tintv != 0, this is a periodic timer that -* needs to be re-armed. We avoid doing it in the timer -* callback to avoid DoS attacks specifying a very -* short timer period. -*/ -if (isalarm (ctx)) { - ticks +=alarm_forward_now ( - &ctx->t.alarm, ctx->tintv) -1 ; -alarm_restart (&ctx->t.alarm); - }else { - ticks +=hrtimer_forward_now (&ctx->t.tmr, - ctx->tintv) -1 ; -hrtimer_restart (&ctx->t.tmr); - } - } + ticks = ctx->ticks; ctx->expired =0 ; ctx->ticks =0 ; + +/* +* If tintv != 0, this is a periodic timer that needs to be +* re-armed. We avoid doing it in the timer callback to avoid +* DoS attacks specifying a very short timer period. +*/ +if (expired && ctx->tintv) + ticks +=timerfd_restart (ctx); }spin_unlock_irq (&ctx->wqh.lock);if (ticks) {
@@ -526,18 +554,7 @@ static int do_timerfd_gettime(int ufd, sspin_lock_irq (&ctx->wqh.lock);if (ctx->expired && ctx->tintv) { ctx->expired =0 ; - -if (isalarm (ctx)) { - ctx->ticks += -alarm_forward_now ( - &ctx->t.alarm, ctx->tintv) -1 ; -alarm_restart (&ctx->t.alarm); - }else { - ctx->ticks += -hrtimer_forward_now (&ctx->t.tmr, ctx->tintv) - -1 ; -hrtimer_restart (&ctx->t.tmr); - } + ctx->ticks +=timerfd_restart (ctx); } t->it_value =ktime_to_timespec64 (timerfd_get_remaining (ctx)); t->it_interval =ktime_to_timespec64 (ctx->tintv);
Re: [patch 09/12] fs/timerfd: Use the new alarm/hrtimer functions
fs/timerfd.c static ktime_t timerfd_get_remaining(str · line 200
Quoted 3 lines · Thomas Gleixner
fs/timerfd.c +3
-0+static u64 timerfd_alarm_restart (struct timerfd_ctx *ctx) +{ +u64 ticks =alarm_forward_now (&ctx->t.alarm, ctx->tintv) -1 ;
(still confused on the alarm_forward_now() vs alarmtimer_start() namespacing)
Quoted 56 lines
fs/timerfd.c +29
-17+ +timerfd_alarm_start (ctx,alarm_get_expires (&ctx->t.alarm), false); +return ticks; +} + +static void timerfd_hrtimer_start (struct timerfd_ctx *ctx,ktime_t exp, +const enum hrtimer_mode mode) +{ +/* Start the timer. If it's expired already, handle the callback. */ +if (!hrtimer_start_range_ns_user (&ctx->t.tmr, exp,0 , mode)) +__timerfd_triggered (ctx); +} + +static u64 timerfd_hrtimer_restart (struct timerfd_ctx *ctx) +{ +u64 ticks =hrtimer_forward_now (&ctx->t.tmr, ctx->tintv) -1 ; + +timerfd_hrtimer_start (ctx,hrtimer_get_expires (&ctx->t.tmr),HRTIMER_MODE_ABS ); +return ticks; +} -if (ctx->expired && ctx->tintv) { -/* -* If tintv != 0, this is a periodic timer that -* needs to be re-armed. We avoid doing it in the timer -* callback to avoid DoS attacks specifying a very -* short timer period. -*/ -if (isalarm (ctx)) { - ticks +=alarm_forward_now ( - &ctx->t.alarm, ctx->tintv) -1 ; -alarm_restart (&ctx->t.alarm); - }else { - ticks +=hrtimer_forward_now (&ctx->t.tmr, - ctx->tintv) -1 ; -hrtimer_restart (&ctx->t.tmr); - } - } + ticks = ctx->ticks; ctx->expired =0 ; ctx->ticks =0 ; + +/* +* If tintv != 0, this is a periodic timer that needs to be +* re-armed. We avoid doing it in the timer callback to avoid +* DoS attacks specifying a very short timer period. +*/ +if (expired && ctx->tintv) + ticks +=timerfd_restart (ctx); }spin_unlock_irq (&ctx->wqh.lock);if (ticks) {@@ -526,18 +554,7 @@ static int do_timerfd_gettime(int ufd, sspin_lock_irq (&ctx->wqh.lock);if (ctx->expired && ctx->tintv) { ctx->expired =0 ;
fs/timerfd.c static int do_timerfd_gettime(int ufd, s · line 538
Quoted 10 lines
fs/timerfd.c +0
-10- -if (isalarm (ctx)) { - ctx->ticks += -alarm_forward_now ( - &ctx->t.alarm, ctx->tintv) -1 ; -alarm_restart (&ctx->t.alarm); - }else { - ctx->ticks += -hrtimer_forward_now (&ctx->t.tmr, ctx->tintv) - -1 ;
(argh!)
fs/timerfd.c static int do_timerfd_gettime(int ufd, s · line 560
Quoted 6 lines
fs/timerfd.c +1
-2-hrtimer_restart (&ctx->t.tmr); - } + ctx->ticks +=timerfd_restart (ctx); } t->it_value =ktime_to_timespec64 (timerfd_get_remaining (ctx)); t->it_interval =ktime_to_timespec64 (ctx->tintv);
What's with the -1 thing?
Anyway, this looks about right.
Thomas Gleixner
maintainerRe: [patch 09/12] fs/timerfd: Use the new alarm/hrtimer functions
Quoted 6 lines · Peter Zijlstra
fs/timerfd.c static int do_timerfd_gettime(int ufd, s · line 538
Quoted 3 lines
fs/timerfd.c +0
-3- ctx->ticks += -hrtimer_forward_now (&ctx->t.tmr, ctx->tintv) - -1 ;(argh!)
Quoted 8 lines
fs/timerfd.c static int do_timerfd_gettime(int ufd, s · line 560
Quoted 6 lines
fs/timerfd.c +1
-2-hrtimer_restart (&ctx->t.tmr); - } + ctx->ticks +=timerfd_restart (ctx); } t->it_value =ktime_to_timespec64 (timerfd_get_remaining (ctx)); t->it_interval =ktime_to_timespec64 (ctx->tintv);What's with the -1 thing?
Magic :)
Reading the timerfd returns the number of expired ticks since the last read or since the timer was armed.
The expiry callback increments ticks by one, hrtimer_forward_now() returns the number of expired ticks relative to the previous expiry time. So it would double account that.
Not pretty, but we need to increment ticks in the callback because of non-interval timers as for those we don't invoke the forwarding.
Thanks,
tglx
Thomas Gleixner
maintainerPATCH 10/12 power: supply: charger-manager: Switch to alarmtimer_start()
In reply to Thomas GleixnerThe existing alarm_start() interface is replaced with the new alarmtimer_start() mechanism, which does not longer queue an already expired timer and returns the state. Adjust the code to utilize this.
No functional change intended.
--- drivers/power/supply/charger-manager.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-)
drivers/power/supply/charger-manager.c +7 -5
@@ -881,7 +881,7 @@ static bool cm_setup_timer(void)mutex_unlock (&cm_list_mtx);if (timer_req && cm_timer) { -ktime_t now, add; +ktime_t exp;/* * Set alarm with the polling interval (wakeup_ms)
@@ -893,14 +893,16 @@ static bool cm_setup_timer(void)pr_info ("Charger Manager wakeup timer: %u ms\n" , wakeup_ms); - now =ktime_get_boottime (); - add =ktime_set (wakeup_ms /MSEC_PER_SEC , + exp =ktime_set (wakeup_ms /MSEC_PER_SEC , (wakeup_ms %MSEC_PER_SEC ) *NSEC_PER_MSEC ); -alarm_start (cm_timer,ktime_add (now, add)); cm_suspend_duration_ms = wakeup_ms; -return true; +/* +* The timer should always be queued as the timeout is at least +* two seconds out. Handle it correctly nevertheless. +*/ +return alarmtimer_start (cm_timer, exp, true); }return false; }
Re: [patch 10/12] power: supply: charger-manager: Switch to alarmtimer_start()
drivers/power/supply/charger-manager.c static bool cm_setup_timer(void) · line 897
Quoted 2 lines · Thomas Gleixner
drivers/power/supply/charger-manager.c +1
-0+ exp =ktime_set (wakeup_ms /MSEC_PER_SEC , (wakeup_ms %MSEC_PER_SEC ) *NSEC_PER_MSEC );
Surely we can write this less insane?
exp = wakeup_ms * NSEC_PER_MSEC;
comes to mind? And yes, we then seem to loose that KTIME_SEC_MAX check, but urgh.
Thomas Gleixner
maintainerPATCH 11/12 netfilter: xt_IDLETIMER: Switch to alarmtimer_start()
In reply to Thomas GleixnerThe existing alarm-start() interface is replaced with the new alarmtimer_start() mechanism, which does not longer queue an already expired timer and returns the state.
Adjust the code to utilize this so it schedules the work in the case that the timer was already expired. Unlikely to happen as the timeout is at least a second, but not impossible especially with virtualization.
No functional change intended
--- net/netfilter/xt_IDLETIMER.c | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-)
net/netfilter/xt_IDLETIMER.c +18 -6
@@ -115,6 +115,21 @@ static void idletimer_tg_alarmproc(strucschedule_work (&timer->work); } +static void idletimer_start_alarm_ktime (struct idletimer_tg *timer,ktime_t timeout) +{ +/* +* The timer should always be queued as @tout it should be least one +* second, but handle it correctly in any case. Virt will manage! +*/ +if (!alarmtimer_start (&timer->alarm, timeout, true)) +schedule_work (&timer->work); +} + +static void idletimer_start_alarm_sec (struct idletimer_tg *timer,unsigned int seconds) +{ +idletimer_start_alarm_ktime (timer,ktime_set (seconds,0 )); +} +static int idletimer_check_sysfs_name (const char *name,unsigned int size) {int ret;
@@ -220,12 +235,10 @@ static int idletimer_tg_create_v1(structINIT_WORK (&info->timer->work, idletimer_tg_work);if (info->timer->timer_type &XT_IDLETIMER_ALARM ) { -ktime_t tout;alarm_init (&info->timer->alarm,ALARM_BOOTTIME , idletimer_tg_alarmproc); info->timer->alarm.data = info->timer; - tout =ktime_set (info->timeout,0 ); -alarm_start_relative (&info->timer->alarm, tout); +idletimer_start_alarm_sec (info->timer, info->timeout); }else {timer_setup (&info->timer->timer, idletimer_tg_expired,0 ); mod_timer(&info->timer->timer,
@@ -271,8 +284,7 @@ static unsigned int idletimer_tg_target_ info->label, info->timeout);if (info->timer->timer_type &XT_IDLETIMER_ALARM ) { -ktime_t tout =ktime_set (info->timeout,0 ); -alarm_start_relative (&info->timer->alarm, tout); +idletimer_start_alarm_sec (info->timer, info->timeout); }else {mod_timer (&info->timer->timer,secs_to_jiffies (info->timeout) + jiffies);
@@ -384,7 +396,7 @@ static int idletimer_tg_checkentry_v1(coif (ktimespec.tv_sec >0 ) {pr_debug ("time_expiry_remaining %lld\n" , ktimespec.tv_sec); -alarm_start_relative (&info->timer->alarm, tout); +idletimer_start_alarm_ktime (info->timer, tout); } }else { mod_timer(&info->timer->timer,
Thomas Gleixner
maintainerPATCH 12/12 alarmtimer: Remove unused interfaces
In reply to Thomas GleixnerAll alarmtimer users are converted to alarmtimer_start(). Remove the now unused interfaces.
--- include/linux/alarmtimer.h | 3 --- kernel/time/alarmtimer.c | 44 -------------------------------------------- 2 files changed, 47 deletions(-)
include/linux/alarmtimer.h +0 -3
@@ -50,9 +50,6 @@ static __always_inline ktime_t alarm_getvoid alarm_init (struct alarm *alarm,enum alarmtimer_type type,void (*function)(struct alarm *,ktime_t ));bool alarmtimer_start (struct alarm *alarm,ktime_t expires,bool relative); -void alarm_start (struct alarm *alarm,ktime_t start); -void alarm_start_relative (struct alarm *alarm,ktime_t start); -void alarm_restart (struct alarm *alarm);int alarm_try_to_cancel (struct alarm *alarm);int alarm_cancel (struct alarm *alarm);
kernel/time/alarmtimer.c +0 -44
56 lines not on this page · raw message
Re: [patch 12/12] alarmtimer: Remove unused interfaces
Quoted 7 lines · Thomas Gleixner
All alarmtimer users are converted to
alarmtimer_start(). Remove the now unused interfaces.Signed-off-by Thomas Gleixner <[email protected]>Cc John Stultz <[email protected]>Cc Stephen Boyd <[email protected]>
Thomas Gleixner
maintainerQuoted 5 lines · Thomas Gleixner
There needs to be some discussion about the scope of backporting. The first patch preventing the stall is obviously a backport candidate. The remaining series can be obviously argued about, but in my opinion it should be backported as well as it prevents stupid or malicious user space from generating tons of pointless timer interrupts.
Peter and me just discussed it over IRC. With the clockevents prevention in place, the effect of stupid/malicious code is pretty much affecting only the user space task itself. As the timer is forced to expire once the clockevent device has been force armed, it won't have other side effects as device interrupts or IPIs are not blocked out and in the worst case marginally delayed by the high frequency timer interrupt.
Once the task is scheduled out that subsides as there is nothing which re-arms the timer anymore.
So we should be fine with backporting the clockevents fix and leave the other parts of the series for upstream only. I still need to investigate how all of that affects the pending changes vs. TSC deadline timer (and similar devices) which are not going to reach that modified clockevents code anymore.
Thanks,
tglx
Thomas Gleixner
maintainerQuoted 14 lines · Thomas Gleixner
Quoted 5 lines · Thomas Gleixner
There needs to be some discussion about the scope of backporting. The first patch preventing the stall is obviously a backport candidate. The remaining series can be obviously argued about, but in my opinion it should be backported as well as it prevents stupid or malicious user space from generating tons of pointless timer interrupts.
Peter and me just discussed it over IRC. With the clockevents prevention in place, the effect of stupid/malicious code is pretty much affecting only the user space task itself. As the timer is forced to expire once the clockevent device has been force armed, it won't have other side effects as device interrupts or IPIs are not blocked out and in the worst case marginally delayed by the high frequency timer interrupt.
Quoted 8 lines
Once the task is scheduled out that subsides as there is nothing which re-arms the timer anymore.
So we should be fine with backporting the clockevents fix and leave the other parts of the series for upstream only. I still need to investigate how all of that affects the pending changes vs. TSC deadline timer (and similar devices) which are not going to reach that modified clockevents code anymore.
It's pretty much the same as the above with the difference that a timer armed in the past will result in an instantaneous interrupt as the coupled event devices must provide a less than or equal comparator. So again the task can only delay itself with hrtimer interrupts.
Thanks,
tglx
Quoted 7 lines · Thomas Gleixner
Calvin reported an odd NMI watchdog lockup which claims that the CPU locked up in user space:
https://lore.kernel.org/lkml/[email protected]/
He provided a reproducer, which sets up a timerfd based timer and then rearms it in a loop with an absolute expiry time of 1ns.
The original AMD machines survive the reproducer with this series.
I'm happy to test subsets of it and stable backports too, if that's helpful, just let me know.
Thanks,
Calvin
Quoted 82 lines
As the expiry time is in the past, the timer ends up as the first expiring timer in the per CPU hrtimer base and the clockevent device is programmed with the minimum delta value. If the machine is fast enough, this ends up in a endless loop of programming the delta value to the minimum value defined by the clock event device, before the timer interrupt can fire, which starves the interrupt and consequently triggers the lockup detector because the hrtimer callback of the lockup mechanism is never invoked.
The first patch in the series changes the clockevent set next event mechanism to prevent reprogramming of the clockevent device when the minimum delta value was programmed unless the new delta is larger than that. It's a less convoluted variant of the patch which was posted in the above linked thread and was confirmed to prevent the starvation problem.
But that's only to be considered the last resort because it results in an insane amount of avoidable hrtimer interrupts.
The problem of user controlled timers is that the input value is only sanity checked vs. validity of the provided timespec and clamped to be in the maximum allowable range. But for performance reasons for in kernel usage there is no check whether a to be armed timer might have been expired already at enqueue time.
The rest of the series addresses this by providing a separate interface to arm user controlled timers. This works the same way as the existing
hrtimer_start_range_ns(), but in case that the timer ends up as the first timer in the clock base after enqueue it provides additional checks:- Whether the timer becomes the first expiring timer in the CPU base.
If not the timer is considered to expire in the future as there is already an earlier event programmed.
- Whether the timer has expired already by comparing the expiry value against current time.
If it is expired, the timer is removed from the clock base and the function returns false, so that the caller can handle it. That's required because the function cannot invoke the callback as that might need to acquire a lock which is held by the caller.
This function is then used for the user controlled timer arming interfaces mainly by converting hrtimer sleeper over to it. That affects a few in kernel users too, but the overhead is minimal in that case and it spares a tedious whack the mole game all over the tree.
The other usage sites in posixtimers, alarmtimers and timerfd are converted as well, which should cover the vast majority of user space controllable timers as far as my investigation goes.
The series applies against Linux tree and is also available from git:
git://git.kernel.org/pub/scm/linux/kernel/git/tglx/devel.githrtimer-exp-v1There needs to be some discussion about the scope of backporting. The first patch preventing the stall is obviously a backport candidate. The remaining series can be obviously argued about, but in my opinion it should be backported as well as it prevents stupid or malicious user space from generating tons of pointless timer interrupts.
Thanks,
tglx
--- drivers/power/supply/charger-manager.c | 12 +- fs/timerfd.c | 115 +++++++++++++++----------- include/linux/alarmtimer.h | 9 +- include/linux/clockchips.h | 2 include/linux/hrtimer.h | 20 +++- include/trace/events/timer.h | 13 +++ kernel/time/alarmtimer.c | 70 +++++++--------- kernel/time/clockevents.c | 23 +++-- kernel/time/hrtimer.c | 142 +++++++++++++++++++++++++++++---- kernel/time/posix-cpu-timers.c | 18 ++-- kernel/time/posix-timers.c | 35 +++++--- kernel/time/posix-timers.h | 4 kernel/time/tick-common.c | 1 kernel/time/tick-sched.c | 1 net/netfilter/xt_IDLETIMER.c | 24 ++++- 15 files changed, 341 insertions(+), 148 deletions(-)
Thomas Gleixner
maintainerQuoted 10 lines · Calvin Owens
Quoted 2 lines · Thomas Gleixner
He provided a reproducer, which sets up a timerfd based timer and then rearms it in a loop with an absolute expiry time of 1ns.
The original AMD machines survive the reproducer with this series.
Tested-by Calvin Owens <[email protected]>I'm happy to test subsets of it and stable backports too, if that's helpful, just let me know.
We'll only backport the first patch, so confirming that it still prevents the issue would be nice. The rest is slated for upstream only.
Thanks,
tglx
Quoted 7 lines · Thomas Gleixner
Quoted 6 lines · Calvin Owens
Quoted 2 lines · Thomas Gleixner
He provided a reproducer, which sets up a timerfd based timer and then rearms it in a loop with an absolute expiry time of 1ns.
The original AMD machines survive the reproducer with this series.
Quoted 7 lines
Quoted 4 lines
Tested-by Calvin Owens <[email protected]>I'm happy to test subsets of it and stable backports too, if that's helpful, just let me know.
We'll only backport the first patch, so confirming that it still prevents the issue would be nice. The rest is slated for upstream only.
Confirmed, [1/12] alone passes.
Thanks,
Calvin
Thomas Gleixner
maintainerQuoted 10 lines · Calvin Owens
Quoted 9 lines · Thomas Gleixner
Quoted 8 lines · Calvin Owens
Quoted 2 lines · Thomas Gleixner
He provided a reproducer, which sets up a timerfd based timer and then rearms it in a loop with an absolute expiry time of 1ns.
The original AMD machines survive the reproducer with this series.
Tested-by Calvin Owens <[email protected]>
Quoted 7 lines
Quoted 5 lines
Quoted 2 lines
I'm happy to test subsets of it and stable backports too, if that's helpful, just let me know.
We'll only backport the first patch, so confirming that it still prevents the issue would be nice. The rest is slated for upstream only.
Confirmed, [1/12] alone passes.
Thanks a lot for all your help. Very appreciated.
tglx