* [PATCH v2] softirq: Preserve interrupt context during IRQ exit
@ 2026-09-05 2:32 Karl Mehltretter
2026-09-17 15:21 ` Sebastian Andrzej Siewior
0 siblings, 1 reply; 5+ messages in thread
From: Karl Mehltretter @ 2026-09-05 2:32 UTC (permalink / raw)
To: Peter Zijlstra, Thomas Gleixner
Cc: Karl Mehltretter, Sebastian Andrzej Siewior, Frederic Weisbecker,
Clark Williams, Steven Rostedt, Boqun Feng, Lyude Paul,
Joel Fernandes, Alexander Potapenko, Marco Elver, linux-kernel,
linux-rt-devel
__irq_exit_rcu() drops HARDIRQ_OFFSET before deferred hrtimer rearm,
entry into softirq dispatch, and timersd wakeup. The rearm, wakeup, and
softirq entry code before softirq_handle_begin() are still on the IRQ
return path, but in_task() reports task context.
Context-sensitive code called from this window therefore sees task
context. ftrace records normal-context flags and selects its normal
recursion slot. KCSAN attributes IRQ-exit accesses to the interrupted
task, while KMSAN can select and modify that task's metadata.
Keep HARDIRQ_OFFSET across this IRQ-exit window. For direct softirq
handling, replace it with SOFTIRQ_OFFSET and restore it afterwards. Other
__do_softirq() call paths keep their existing accounting.
With hardirq context retained, ftrace records hardirq context, KCSAN uses
interrupt attribution, and KMSAN no longer uses the interrupted task's
state.
Test softirq eligibility before removing HARDIRQ_OFFSET with
irq_count() == HARDIRQ_OFFSET. This preserves the old !in_interrupt()
semantics, including PREEMPT_RT's task-local softirq-disable state. For
timersd, require exactly one hardirq nesting level and no NMI, preserving
the old predicate.
Drop HARDIRQ_OFFSET before tick_irq_exit(), as before. Preempt-off tracing
now covers the IRQ-exit work continuously instead of showing an artificial
on/off transition.
Suggested-by: Peter Zijlstra <peterz@infradead.org>
Link: https://lore.kernel.org/r/20260813130826.GW687043@noisy.programming.kicks-ass.net
Assisted-by: LLM
Signed-off-by: Karl Mehltretter <kmehltretter@gmail.com>
---
Changes since v1:
- Warn if the hardirq-to-softirq transition does not produce exactly one
SOFTIRQ_OFFSET, and update lockdep state unconditionally (Frederic).
- Tighten the commit message around the affected IRQ-exit window, the
timersd predicate and preempt-off tracing.
- Add exact-v2 QEMU, sanitizer, hardware and idle-latency results to the
review notes.
v1: https://lore.kernel.org/r/20260903112737.49551-1-kmehltretter@gmail.com
The exact v2 source passed non-RT, threadirqs and PREEMPT_RT x86-64 QEMU
boot/stress, including CPU hotplug, with panic_on_warn=1, lockdep and IRQ
tracing. The same three modes passed a continuous context-invariant test;
neither new warning fired.
KCSAN attributed all 25 target reports to interrupt context. KMSAN selected
or changed task state zero times in 24K IRQ-exit windows and passed all 28
KUnit tests.
vmlinux linked successfully for arm64, ARM, RISC-V and s390.
The exact v2 TIP source passed config-identical A/B boot/stress on a Pi 400
(Cortex-A72, arm64) with panic_on_warn=1: 2000/2000 ping, no boot splat and
no new dmesg output. Focused ftrace, printk/fault-injection and continuous-
invariant diagnostics also passed on the same source.
A second A/B on the Pi 400 with threadirqs enabled also passed. Four
ktimers threads were active on each side, 2000/2000 ping completed, and the
dmesg buffer remained empty after the workload.
A four-boot ABBA cyclictest run on the Pi found no idle timer-latency
regression. Across 6M samples per side, p99 was 5 us on both kernels and
p99.9 was 7 us on the baseline versus 5 us patched.
For additional portability coverage, the mainline v2 adaptation passed
config-identical A/B boot/stress on a SAM9X75 (ARM926EJ-S/ARMv5TEJ) with
panic_on_warn=1, 1000/1000 ping, no boot splat and no new dmesg output.
kernel/softirq.c | 48 ++++++++++++++++++++++++++++++++++++------------
1 file changed, 36 insertions(+), 12 deletions(-)
diff --git a/kernel/softirq.c b/kernel/softirq.c
index 5d02c36c40e3..7381b6b4d551 100644
--- a/kernel/softirq.c
+++ b/kernel/softirq.c
@@ -350,8 +350,8 @@ static inline void ksoftirqd_run_end(void)
local_irq_enable();
}
-static inline void softirq_handle_begin(void) { }
-static inline void softirq_handle_end(void) { }
+static inline bool softirq_handle_begin(void) { return false; }
+static inline void softirq_handle_end(bool from_hardirq) { }
static inline bool should_wake_ksoftirqd(void)
{
@@ -481,15 +481,35 @@ void __local_bh_enable_ip(unsigned long ip, unsigned int cnt)
}
EXPORT_SYMBOL(__local_bh_enable_ip);
-static inline void softirq_handle_begin(void)
+static inline bool softirq_handle_begin(void)
{
- __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);
+ bool from_hardirq = in_hardirq();
+
+ if (!from_hardirq) {
+ __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);
+ return false;
+ }
+
+ /* Replace the retained hardirq context with normal softirq context. */
+ __preempt_count_add((int)SOFTIRQ_OFFSET - (int)HARDIRQ_OFFSET);
+ WARN_ON_ONCE(softirq_count() != SOFTIRQ_OFFSET);
+ lockdep_softirqs_off(_RET_IP_);
+
+ return true;
}
-static inline void softirq_handle_end(void)
+static inline void softirq_handle_end(bool from_hardirq)
{
- __local_bh_enable(SOFTIRQ_OFFSET);
- WARN_ON_ONCE(in_interrupt());
+ if (!from_hardirq) {
+ __local_bh_enable(SOFTIRQ_OFFSET);
+ WARN_ON_ONCE(in_interrupt());
+ return;
+ }
+
+ WARN_ON_ONCE(softirq_count() != SOFTIRQ_OFFSET);
+ lockdep_softirqs_on(_RET_IP_);
+ __preempt_count_sub((int)SOFTIRQ_OFFSET - (int)HARDIRQ_OFFSET);
+ WARN_ON_ONCE(!in_hardirq());
}
static inline void ksoftirqd_run_begin(void)
@@ -605,6 +625,7 @@ static void handle_softirqs(bool ksirqd)
unsigned long old_flags = current->flags;
int max_restart = MAX_SOFTIRQ_RESTART;
struct softirq_action *h;
+ bool from_hardirq;
bool in_hardirq;
__u32 pending;
int softirq_bit;
@@ -618,7 +639,7 @@ static void handle_softirqs(bool ksirqd)
pending = local_softirq_pending();
- softirq_handle_begin();
+ from_hardirq = softirq_handle_begin();
in_hardirq = lockdep_softirq_start();
account_softirq_enter(current);
@@ -670,7 +691,7 @@ static void handle_softirqs(bool ksirqd)
account_softirq_exit(current);
lockdep_softirq_end(in_hardirq);
- softirq_handle_end();
+ softirq_handle_end(from_hardirq);
current_restore_flags(old_flags, PF_MEMALLOC);
}
@@ -740,6 +761,8 @@ static inline void wake_timersd(void) { }
#endif
+#define IRQ_EXIT_TIMERS (NMI_MASK | HARDIRQ_MASK)
+
static inline void __irq_exit_rcu(void)
{
#ifndef __ARCH_IRQ_EXIT_IRQS_DISABLED
@@ -748,8 +771,7 @@ static inline void __irq_exit_rcu(void)
lockdep_assert_irqs_disabled();
#endif
account_hardirq_exit(current);
- preempt_count_sub(HARDIRQ_OFFSET);
- if (!in_interrupt() && local_softirq_pending()) {
+ if (irq_count() == HARDIRQ_OFFSET && local_softirq_pending()) {
/*
* If we left hrtimers unarmed, make sure to arm them now,
* before enabling interrupts to run softirq.
@@ -759,9 +781,11 @@ static inline void __irq_exit_rcu(void)
}
if (IS_ENABLED(CONFIG_IRQ_FORCED_THREADING) && force_irqthreads() &&
- local_timers_pending_force_th() && !(in_nmi() | in_hardirq()))
+ local_timers_pending_force_th() &&
+ (preempt_count() & IRQ_EXIT_TIMERS) == HARDIRQ_OFFSET)
wake_timersd();
+ preempt_count_sub(HARDIRQ_OFFSET);
tick_irq_exit();
}
base-commit: 2af470916a208b576ac9975d221d9a378cf8ace9
--
2.53.0
^ permalink raw reply [flat|nested] 5+ messages in thread* Re: [PATCH v2] softirq: Preserve interrupt context during IRQ exit
2026-09-05 2:32 [PATCH v2] softirq: Preserve interrupt context during IRQ exit Karl Mehltretter
@ 2026-09-17 15:21 ` Sebastian Andrzej Siewior
2026-09-19 7:51 ` Karl Mehltretter
0 siblings, 1 reply; 5+ messages in thread
From: Sebastian Andrzej Siewior @ 2026-09-17 15:21 UTC (permalink / raw)
To: Karl Mehltretter
Cc: Peter Zijlstra, Thomas Gleixner, Frederic Weisbecker,
Clark Williams, Steven Rostedt, Boqun Feng, Lyude Paul,
Joel Fernandes, Alexander Potapenko, Marco Elver, linux-kernel,
linux-rt-devel
On 2026-09-05 04:32:10 [+0200], Karl Mehltretter wrote:
> __irq_exit_rcu() drops HARDIRQ_OFFSET before deferred hrtimer rearm,
> entry into softirq dispatch, and timersd wakeup. The rearm, wakeup, and
> softirq entry code before softirq_handle_begin() are still on the IRQ
> return path, but in_task() reports task context.
>
> Context-sensitive code called from this window therefore sees task
> context. ftrace records normal-context flags and selects its normal
> recursion slot. KCSAN attributes IRQ-exit accesses to the interrupted
> task, while KMSAN can select and modify that task's metadata.
The breakage is limited to KCSAN & friends within the window during
transition to softirq and out. There is nothing else? Well, the timer
wake looks wrong in trace, noted.
…
> --- a/kernel/softirq.c
> +++ b/kernel/softirq.c
> @@ -481,15 +481,35 @@ void __local_bh_enable_ip(unsigned long ip, unsigned int cnt)
> }
> EXPORT_SYMBOL(__local_bh_enable_ip);
>
> -static inline void softirq_handle_begin(void)
> +static inline bool softirq_handle_begin(void)
> {
> - __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);
> + bool from_hardirq = in_hardirq();
> +
> + if (!from_hardirq) {
> + __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);
> + return false;
> + }
> +
> + /* Replace the retained hardirq context with normal softirq context. */
/*
* This is only entered on return from interrupt. Preemption disabled
* locations remains unchanged, the context (-HARDIRQ +SOFTIRQ) is
* updated and lockdep is let known.
*/
> + __preempt_count_add((int)SOFTIRQ_OFFSET - (int)HARDIRQ_OFFSET);
The casts look odd. We need this? It is defined as long, yes, but
preempt_count accepts an int only so it will throw the upper bits away.
> + WARN_ON_ONCE(softirq_count() != SOFTIRQ_OFFSET);
I think we want to ensure that irq_count() == SOFTIRQ_OFFSET
at this point. Only the lower preemption bits may differ, everything
else should be as we expect it. softirq_count() would drop the HARDIRQ
bits.
> + lockdep_softirqs_off(_RET_IP_);
> +
> + return true;
> }
>
> -static inline void softirq_handle_end(void)
> +static inline void softirq_handle_end(bool from_hardirq)
> {
> - __local_bh_enable(SOFTIRQ_OFFSET);
> - WARN_ON_ONCE(in_interrupt());
> + if (!from_hardirq) {
> + __local_bh_enable(SOFTIRQ_OFFSET);
> + WARN_ON_ONCE(in_interrupt());
> + return;
> + }
> +
> + WARN_ON_ONCE(softirq_count() != SOFTIRQ_OFFSET);
> + lockdep_softirqs_on(_RET_IP_);
> + __preempt_count_sub((int)SOFTIRQ_OFFSET - (int)HARDIRQ_OFFSET);
> + WARN_ON_ONCE(!in_hardirq());
that is quite some WARN_ON_ONCE. We would like to see just
HARDIRQ_OFFSET at the end. Or SOFTIRQ_OFFSET before the end. One should
be enough or the math is wrong.
> }
>
> static inline void ksoftirqd_run_begin(void)
> @@ -740,6 +761,8 @@ static inline void wake_timersd(void) { }
>
> #endif
>
> +#define IRQ_EXIT_TIMERS (NMI_MASK | HARDIRQ_MASK)
> +
> static inline void __irq_exit_rcu(void)
> {
> #ifndef __ARCH_IRQ_EXIT_IRQS_DISABLED
> @@ -748,8 +771,7 @@ static inline void __irq_exit_rcu(void)
> lockdep_assert_irqs_disabled();
> #endif
> account_hardirq_exit(current);
> - preempt_count_sub(HARDIRQ_OFFSET);
> - if (!in_interrupt() && local_softirq_pending()) {
> + if (irq_count() == HARDIRQ_OFFSET && local_softirq_pending()) {
irq_enter_rcu() did preempt_count_add(HARDIRQ_OFFSET), did record
task_struct::preempt_disable_ip. Due to the split, it does not recording
the softirq handling as disabling preemption point but keeps the
original until the end. This looks like an improvement.
> /*
> * If we left hrtimers unarmed, make sure to arm them now,
> * before enabling interrupts to run softirq.
> @@ -759,9 +781,11 @@ static inline void __irq_exit_rcu(void)
> }
>
> if (IS_ENABLED(CONFIG_IRQ_FORCED_THREADING) && force_irqthreads() &&
> - local_timers_pending_force_th() && !(in_nmi() | in_hardirq()))
> + local_timers_pending_force_th() &&
> + (preempt_count() & IRQ_EXIT_TIMERS) == HARDIRQ_OFFSET)
Why is this preempt_count() instead irq_count(). Why is there
IRQ_EXIT_TIMERS? It is almost as the first check except now we would
like to ignore the additional softirq_count().
> wake_timersd();
>
> + preempt_count_sub(HARDIRQ_OFFSET);
> tick_irq_exit();
> }
>
>
> base-commit: 2af470916a208b576ac9975d221d9a378cf8ace9
Sebastian
^ permalink raw reply [flat|nested] 5+ messages in thread* Re: [PATCH v2] softirq: Preserve interrupt context during IRQ exit
2026-09-17 15:21 ` Sebastian Andrzej Siewior
@ 2026-09-19 7:51 ` Karl Mehltretter
2026-09-21 13:50 ` Sebastian Andrzej Siewior
0 siblings, 1 reply; 5+ messages in thread
From: Karl Mehltretter @ 2026-09-19 7:51 UTC (permalink / raw)
To: Sebastian Andrzej Siewior
Cc: Peter Zijlstra, Thomas Gleixner, Frederic Weisbecker,
Clark Williams, Steven Rostedt, Boqun Feng, Lyude Paul,
Joel Fernandes, Alexander Potapenko, Marco Elver, linux-kernel,
linux-rt-devel
On 2026-09-17 17:21:50 [+0200], Sebastian Andrzej Siewior wrote:
> The breakage is limited to KCSAN & friends within the window during
> transition to softirq and out. There is nothing else? Well, the timer
> wake looks wrong in trace, noted.
I know of nothing that is broken today. It is more than instrumentation
though. Code that reads the context from preempt_count sees the
interrupted task in that window. Besides ftrace, KCSAN, KMSAN, KCOV and
the printk caller id I found:
- can_spin_trylock() and local_trylock() on RT refuse hard interrupt
context. A trylock on top of a task that is blocked on a lock
confuses the PI code. In that window they do not refuse. BPF attached
to sched_waking or sched_wakeup reaches them through kmalloc_nolock().
- oops_end() and make_task_dead() test in_interrupt(). Today an oops in
that window is treated like an oops in task context and kills the
interrupted task. With HARDIRQ_OFFSET set it panics with "Fatal
exception in interrupt", like an oops in the handler itself.
- rcu_read_unlock_special() and raise_softirq_irqoff(). See the end of
this mail.
I'll list these in the changelog. I will also say what the patch does
not cover. tick_irq_exit() and the other deferred rearm sites still run
after HARDIRQ_OFFSET is removed.
> /*
> * This is only entered on return from interrupt. Preemption disabled
> * locations remains unchanged, the context (-HARDIRQ +SOFTIRQ) is
> * updated and lockdep is let known.
> */
I'll use it, reworded, and add two points. in_hardirq() is sampled
because __do_softirq() is reached through do_softirq_own_stack() on
several architectures and cannot take an argument. The raw operation is
used because the preemption disabled section from irq_enter_rcu()
continues.
> The casts look odd. We need this? It is defined as long, yes, but
> preempt_count accepts an int only so it will throw the upper bits away.
The value is the same. Without the casts gcc warns:
warning: overflow in conversion from 'long unsigned int' to 'int'
changes value from '18446744073692774656' to '-16776960' [-Woverflow]
-Woverflow is on by default. I'll swap the operands:
__preempt_count_sub(HARDIRQ_OFFSET - SOFTIRQ_OFFSET);
and use __preempt_count_add() at the end. The difference is positive
and fits an int. No cast and no warning.
> I think we want to ensure that irq_count() == SOFTIRQ_OFFSET
Will do. This path is !PREEMPT_RT only, so irq_count() is the plain
preempt_count() one.
> that is quite some WARN_ON_ONCE. We would like to see just
> HARDIRQ_OFFSET at the end. Or SOFTIRQ_OFFSET before the end. One should
> be enough or the math is wrong.
softirq_handle_end() will have one. It checks irq_count() ==
HARDIRQ_OFFSET after the addition. In softirq_handle_begin() I will move
lockdep_softirqs_off() before the assertion. Then the lockdep softirq
state is consistent if the assertion fires and printk runs.
> irq_enter_rcu() did preempt_count_add(HARDIRQ_OFFSET), did record
> task_struct::preempt_disable_ip. [...] This looks like an improvement.
Yes. The preemptoff tracer changes too. It reports the hard interrupt
and the softirq processing after it as one section. I'll add both to
the changelog.
> Why is this preempt_count() instead irq_count. Why is there
> IRQ_EXIT_TIMERS? It is almost as the first check except now we would
> like to ignore the additional softirq_count().
Yes, that is the intent. irq_count() would skip the wakeup when the
interrupt hit a BH disabled or softirq serving section. The old test
did not skip it, and nothing else handles pending_timer_softirq.
I'll drop the macro and the raw preempt_count() and use
!in_nmi() && hardirq_count() == HARDIRQ_OFFSET
This is the old test, evaluated before HARDIRQ_OFFSET is removed. It
reads like the first check without softirq_count(). I'll add a comment
that says why softirq_count() is left out.
I also want to change the order in your code. With HARDIRQ_OFFSET set,
raise_softirq_irqoff() does not wake ksoftirqd. rcu_read_unlock_special()
raises RCU_SOFTIRQ instead of setting NEED_RESCHED. Both assume that
interrupt exit handles pending softirqs. In v2 that is not true for a
softirq raised inside wake_timersd(), because the wakeup comes after the
pending check. The timer thread would handle it, because run_ktimerd()
handles all vectors. I do not want to rely on that, but wake the timer
thread first:
if (IS_ENABLED(CONFIG_IRQ_FORCED_THREADING) && force_irqthreads() &&
local_timers_pending_force_th() &&
!in_nmi() && hardirq_count() == HARDIRQ_OFFSET)
wake_timersd();
if (irq_count() == HARDIRQ_OFFSET && local_softirq_pending()) {
hrtimer_rearm_deferred();
invoke_softirq();
}
preempt_count_sub(HARDIRQ_OFFSET);
tick_irq_exit();
Today the wakeup already runs before the rearm when no softirq is
pending. Does the old order have a reason that I do not see? Then I
keep it and document that the timer thread handles such a softirq.
Karl
^ permalink raw reply [flat|nested] 5+ messages in thread* Re: [PATCH v2] softirq: Preserve interrupt context during IRQ exit
2026-09-19 7:51 ` Karl Mehltretter
@ 2026-09-21 13:50 ` Sebastian Andrzej Siewior
2026-09-23 0:16 ` Karl Mehltretter
0 siblings, 1 reply; 5+ messages in thread
From: Sebastian Andrzej Siewior @ 2026-09-21 13:50 UTC (permalink / raw)
To: Karl Mehltretter
Cc: Peter Zijlstra, Thomas Gleixner, Frederic Weisbecker,
Clark Williams, Steven Rostedt, Boqun Feng, Lyude Paul,
Joel Fernandes, Alexander Potapenko, Marco Elver, linux-kernel,
linux-rt-devel
On 2026-09-19 09:51:05 [+0200], Karl Mehltretter wrote:
> On 2026-09-17 17:21:50 [+0200], Sebastian Andrzej Siewior wrote:
> > The breakage is limited to KCSAN & friends within the window during
> > transition to softirq and out. There is nothing else? Well, the timer
> > wake looks wrong in trace, noted.
>
> I know of nothing that is broken today. It is more than instrumentation
> though. Code that reads the context from preempt_count sees the
> interrupted task in that window. Besides ftrace, KCSAN, KMSAN, KCOV and
> the printk caller id I found:
>
> - can_spin_trylock() and local_trylock() on RT refuse hard interrupt
> context. A trylock on top of a task that is blocked on a lock
> confuses the PI code. In that window they do not refuse. BPF attached
> to sched_waking or sched_wakeup reaches them through kmalloc_nolock().
>
> - oops_end() and make_task_dead() test in_interrupt(). Today an oops in
> that window is treated like an oops in task context and kills the
> interrupted task. With HARDIRQ_OFFSET set it panics with "Fatal
> exception in interrupt", like an oops in the handler itself.
>
> - rcu_read_unlock_special() and raise_softirq_irqoff(). See the end of
> this mail.
>
> I'll list these in the changelog. I will also say what the patch does
> not cover. tick_irq_exit() and the other deferred rearm sites still run
> after HARDIRQ_OFFSET is removed.
The "important" part is this fixing something that is broken today or is
it just avoiding fallout. We don't have any memory allocations/ locking
in the mentioned window as far as I know. That would fix things, just
avoid fallout.
The wake-up in that window does record wrong flags in the recorded trace
but I am unsure if this mandates a fix-me-backport for instance.
> The value is the same. Without the casts gcc warns:
>
> warning: overflow in conversion from 'long unsigned int' to 'int'
> changes value from '18446744073692774656' to '-16776960' [-Woverflow]
>
> -Woverflow is on by default. I'll swap the operands:
Is this some gcc-17 thing? I don't remember that I saw it and I did test
that.
> > that is quite some WARN_ON_ONCE. We would like to see just
> > HARDIRQ_OFFSET at the end. Or SOFTIRQ_OFFSET before the end. One should
> > be enough or the math is wrong.
>
> softirq_handle_end() will have one. It checks irq_count() ==
> HARDIRQ_OFFSET after the addition. In softirq_handle_begin() I will move
> lockdep_softirqs_off() before the assertion. Then the lockdep softirq
> state is consistent if the assertion fires and printk runs.
Right. I mean you have one state and this what you want test for. I
don't think it make sense to test before and after arithmetics.
I am just not sure if those warnings should be hidden behind
CONFIG_DEBUG_PREEMPT similar as preempt_count_add() does it. Maybe it is
not hot-enough-path to worry about it.
> > Why is this preempt_count() instead irq_count. Why is there
> > IRQ_EXIT_TIMERS? It is almost as the first check except now we would
> > like to ignore the additional softirq_count().
>
> Yes, that is the intent. irq_count() would skip the wakeup when the
> interrupt hit a BH disabled or softirq serving section. The old test
> did not skip it, and nothing else handles pending_timer_softirq.
I am slightly unsure but I think we want the wakeup of the timer thread
even if we are in a bh-disabled section. If the current task is a
SCHED_OTHER then the wake-up preempt it. If the thread is already woken
then the wake-up will do nothing.
> I'll drop the macro and the raw preempt_count() and use
>
> !in_nmi() && hardirq_count() == HARDIRQ_OFFSET
>
> This is the old test, evaluated before HARDIRQ_OFFSET is removed. It
> reads like the first check without softirq_count(). I'll add a comment
> that says why softirq_count() is left out.
>
> I also want to change the order in your code. With HARDIRQ_OFFSET set,
> raise_softirq_irqoff() does not wake ksoftirqd. rcu_read_unlock_special()
> raises RCU_SOFTIRQ instead of setting NEED_RESCHED. Both assume that
It depends if RCU uses softirq _and_ BH was disabled. So I don't see
what is wrong with that.
Anyway, one step at a time with some reasoning why.
> interrupt exit handles pending softirqs. In v2 that is not true for a
> softirq raised inside wake_timersd(), because the wakeup comes after the
> pending check. The timer thread would handle it, because run_ktimerd()
> handles all vectors. I do not want to rely on that, but wake the timer
> thread first:
>
> if (IS_ENABLED(CONFIG_IRQ_FORCED_THREADING) && force_irqthreads() &&
> local_timers_pending_force_th() &&
> !in_nmi() && hardirq_count() == HARDIRQ_OFFSET)
> wake_timersd();
>
> if (irq_count() == HARDIRQ_OFFSET && local_softirq_pending()) {
> hrtimer_rearm_deferred();
> invoke_softirq();
> }
>
> preempt_count_sub(HARDIRQ_OFFSET);
> tick_irq_exit();
>
> Today the wakeup already runs before the rearm when no softirq is
> pending. Does the old order have a reason that I do not see? Then I
> keep it and document that the timer thread handles such a softirq.
This only matters for the threadirq case. Here invoke_softirq() will
only wake ksoftirqd and wake_timersd() will only wake the ktimers
thread. There will be no new softirqs added to the mask. This currently
is an ugly catch-all for both sides. Ideally only the softirqs raised by
task X should be handled by task X but the first one will do everything.
> Karl
Sebastian
^ permalink raw reply [flat|nested] 5+ messages in thread* Re: [PATCH v2] softirq: Preserve interrupt context during IRQ exit
2026-09-21 13:50 ` Sebastian Andrzej Siewior
@ 2026-09-23 0:16 ` Karl Mehltretter
0 siblings, 0 replies; 5+ messages in thread
From: Karl Mehltretter @ 2026-09-23 0:16 UTC (permalink / raw)
To: Sebastian Andrzej Siewior
Cc: Peter Zijlstra, Thomas Gleixner, Frederic Weisbecker,
Clark Williams, Steven Rostedt, Boqun Feng, Lyude Paul,
Joel Fernandes, Alexander Potapenko, Marco Elver, linux-kernel,
linux-rt-devel
On Mon, Sep 21, 2026 at 03:50:15PM +0100, Sebastian Andrzej Siewior wrote:
> The "important" part is this fixing something that is broken today or is
> it just avoiding fallout. We don't have any memory allocations/ locking
> in the mentioned window as far as I know. That would fix things, just
> avoid fallout.
Instrumentation sees the wrong context today, the rest is avoiding
fallout.
There is some locking in the window though. With threaded interrupts,
and always on RT, the window wakes ksoftirqd and ktimers. The tracepoints
of those wakeups then run as if they were in the interrupted task. On RT
can_spin_trylock() does not refuse in there.
> > -Woverflow is on by default. I'll swap the operands:
>
> Is this some gcc-17 thing? I don't remember that I saw it and I did test
> that.
gcc 15.2. Without the casts x86_64_defconfig fails here, because it sets
CONFIG_WERROR:
include/linux/preempt.h:78:25: error: overflow in conversion from
'long unsigned int' to 'int' changes value from '18446744073692774656'
to '-16776960' [-Werror=overflow]
clang 21 warns as well (-Wconstant-conversion). With the operands
swapped the value is positive and fits an int, so no cast is needed.
> > HARDIRQ_OFFSET after the addition. In softirq_handle_begin() I will move
> > lockdep_softirqs_off() before the assertion. Then the lockdep softirq
> > state is consistent if the assertion fires and printk runs.
>
> Right. I mean you have one state and this what you want test for. I
> don't think it make sense to test before and after arithmetics.
> I am just not sure if those warnings should be hidden behind
> CONFIG_DEBUG_PREEMPT similar as preempt_count_add() does it. Maybe it is
> not hot-enough-path to worry about it.
I'll keep them for now. They only run when softirqs are handled on irq
exit, so much less often than preempt_count_add(), and the softirq
handlers run right after them. With both checks softirq.o has 8 more
instructions on x86-64 and 11 on arm64 on that path.
> > Yes, that is the intent. irq_count() would skip the wakeup when the
> > interrupt hit a BH disabled or softirq serving section. The old test
> > did not skip it, and nothing else handles pending_timer_softirq.
>
> I am slightly unsure but I think we want the wakeup of the timer thread
> even if we are in a bh-disabled section. If the current task is a
> SCHED_OTHER then the wake-up preempt it. If the thread is already woken
> then the wake-up will do nothing.
Yes. I'll use !in_nmi() && hardirq_count() == HARDIRQ_OFFSET, without
softirq_count().
> > Today the wakeup already runs before the rearm when no softirq is
> > pending. Does the old order have a reason that I do not see? Then I
> > keep it and document that the timer thread handles such a softirq.
>
> This only matters for the threadirq case. Here invoke_softirq() will
> only wake ksoftirqd and wake_timersd() will only wake the ktimers
> thread. There will be no new softirqs added to the mask. This currently
> is an ugly catch-all for both sides. Ideally only the softirqs raised by
> task X should be handled by task X but the first one will do everything.
OK, I'll drop that change.
Thanks,
Karl
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-09-23 0:16 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-05 2:32 [PATCH v2] softirq: Preserve interrupt context during IRQ exit Karl Mehltretter
2026-09-17 15:21 ` Sebastian Andrzej Siewior
2026-09-19 7:51 ` Karl Mehltretter
2026-09-21 13:50 ` Sebastian Andrzej Siewior
2026-09-23 0:16 ` Karl Mehltretter
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®