Re: [PATCH v6] softirq: Preserve interrupt context during IRQ exit
From: Steven Rostedt
Date: Wed Oct 14 2026 - 12:05:40 EST
On Sun, 11 Oct 2026 08:07:42 +0200
Karl Mehltretter <kmehltretter@xxxxxxxxx> wrote:
> __irq_exit_rcu() removes HARDIRQ_OFFSET from preempt_count() before
> the IRQ exit work. On the outermost IRQ exit, with no NMI or softirq
> state remaining, this makes context checks report task context. The
> softirq-dispatch, timer-wakeup and tick-exit checks account for the early
> decrement, but other code reached in this window sees the wrong context:
>
> - ftrace, perf and the ring buffer record task context and use the
> task recursion and context slots.
> - KCSAN attributes accesses to the interrupted task. KMSAN uses and
> changes the interrupted task's KMSAN context. KCOV and the printk
> caller id see a task.
> - On PREEMPT_RT can_spin_trylock() and local_trylock() reject hard
> interrupt context to avoid interfering with PI when the interrupted
> task is blocked on a lock. Those checks do not reject calls made in
> this window. BPF programs attached to sched_waking or sched_wakeup
> can reach the trylock path through kmalloc_nolock().
> - An oops kills the interrupted task instead of ending in "Fatal
> exception in interrupt".
>
> The early decrement predates git. No failure caused by this
> misclassification is known. lockdep is unaffected because
> lockdep_hardirq_exit() is the last operation in irq_exit().
>
> Keep HARDIRQ_OFFSET until the end of __irq_exit_rcu(), including
> tick_irq_exit(). Softirq handlers must run in BH context, so replace
> HARDIRQ_OFFSET with SOFTIRQ_OFFSET while they run and restore it
> afterwards. Use one raw counter update for each transition to preserve
> the preemption disable location recorded by irq_enter_rcu(), and update
> lockdep explicitly. Detect IRQ exit with in_hardirq() in
> softirq_handle_begin() because __do_softirq() can be reached through
> do_softirq_own_stack() and cannot take an argument.
>
> Adjust the exit checks for HARDIRQ_OFFSET still being present. Use
> irq_count() == HARDIRQ_OFFSET for softirq dispatch, as in irq_enter_rcu().
> Use hardirq_count() == HARDIRQ_OFFSET for the timer thread wakeup and
> tick_irq_exit(). NMI exits use a separate path. An IRQ nested in an NMI
> has an extra HARDIRQ_OFFSET from NMI entry, so it is excluded too. The
> timer thread check must ignore softirq_count() to allow wakeups when an
> IRQ interrupts softirq processing or a BH-disabled section. The timer
> thread handles all pending softirqs, including any raised during its
> wakeup.
>
> A softirq raised from a tracepoint on the final preempt_count_sub()
> waits for the next interrupt exit and can trigger NOHZ tick-stop
> warnings. Handling that instrumentation case does not justify a check
> on every interrupt exit. A tracepoint on tick_irq_exit() can also raise
> a softirq after the normal softirq handling has finished.
>
> The number of counter updates and interrupt time accounting are
> unchanged. The preemptoff tracer now reports the interrupt and subsequent
> softirq processing as one section. With nofuncgraph-irqs, function graph
> tracing also skips the IRQ exit work, including the __do_softirq() frame.
>
> Suggested-by: Peter Zijlstra <peterz@xxxxxxxxxxxxx>
> Link: https://lore.kernel.org/r/20260813130826.GW687043@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
> Assisted-by: LLM
> Signed-off-by: Karl Mehltretter <kmehltretter@xxxxxxxxx>
> ---
>
> Changes in v6:
> - Keep HARDIRQ_OFFSET through tick_irq_exit() and use
> hardirq_count() == HARDIRQ_OFFSET for its nesting check (Frederic).
> - Update entry.rst to document NOHZ handling before the final
> preemption-count decrement.
> - Test in_hardirq() directly in softirq_handle_begin() (Frederic).
> - Drop the in_nmi() test from the timer thread wakeup condition
> (Frederic). NMI entry adds HARDIRQ_OFFSET, so the equality check
> excludes IRQs nested in an NMI.
> - Drop the earlier Reviewed-by tags pending reconfirmation of the
> tick_irq_exit() behavior change.
>
> Testing:
> QEMU IRQ-exit probes (v5/v6):
>
> v5 v6
> Tick-exit calls (hardirq/total) 0/5553 5339/5339
> Tick-exit function-graph records 5555 0
> KMSAN selections (task/total) 427205/3492324 0/2992433
>
> The two tick-exit rows count tick_nohz_irq_exit(); function-graph
> tracing used nofuncgraph-irqs. Captures had timer activity and zero
> trace loss.
> Each KMSAN kernel passed all 28 KUnit tests; those tests generated all
> KMSAN reports.
>
> Additional testing covered 67 guest boots and 10 kernel/softirq.o builds
> across five architectures, including RT/non-RT stress, KCOV
> configurations and 600-second rcutorture.
> The KCSAN probe deliberately triggered reports to check IRQ-exit
> context.
>
> Matched configurations of the unpatched base and v6 were compared on
> Pi 400, Pi 500+, BeagleV Ahead, AMD/KVM and UP ARMv6 Pi 1B+. Pi 500+ and
> AMD tick-exit traces confirmed hardirq context with v6 and zero trace
> loss. The NOHZ_FULL tick stopped on both versions on Pi 500+ and BeagleV
> Ahead. Pi 1B+ base/v6 workloads completed without reported errors.
>
> On physical SAM9X75 (cpuidle-at91), base/v6 tick-exit traces showed
> hardirq flags in 0/556 and 540/540 calls, with 558 and zero
> function-graph records under nofuncgraph-irqs. Both kernels showed idle
> and Ethernet activity, with zero trace loss.
>
> On Pi 500+, accounted CPU time exceeded wall time under intermittent
> workloads on both the unpatched base and v6. No v6-specific regression
> was established.
>
LGTM.
So this handles the transition between hard irq and softirq?
I wish there was a way to get preempt count updated earlier on entering a
hard irq so I can get rid of the transition bit in the trace recursion. As
there's still places that can trigger a trace in the hard irq before
preempt count is updated.
-- Steve