* [RFC PATCH v2 01/13] arm64: Always inline raw preempt count updates
2026-10-28 3:31 [RFC PATCH v2 00/13] entry: Classify very early hardirq entry and return-tail callbacks Karl Mehltretter
@ 2026-10-28 3:31 ` Karl Mehltretter
2026-10-28 3:31 ` [RFC PATCH v2 02/13] arm64: percpu: Annotate __kern_my_cpu_offset() as '__always_inline' Karl Mehltretter
` (11 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Karl Mehltretter @ 2026-10-28 3:31 UTC (permalink / raw)
To: Steven Rostedt, Peter Zijlstra, Thomas Gleixner,
Sebastian Andrzej Siewior
Cc: Hongyan Xia, Catalin Marinas, Will Deacon, Mark Rutland,
Ingo Molnar, Andy Lutomirski, Frederic Weisbecker,
Paul E. McKenney, Boqun Feng, Waiman Long, Juri Lelli,
Vincent Guittot, Dietmar Eggemann, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Jonathan Corbet, Shuah Khan,
Randy Dunlap, Andrew Morton, Nathan Chancellor, Nick Desaulniers,
Bill Wendling, Justin Stitt, linux-arm-kernel, linux-trace-kernel,
linux-rt-devel, linux-doc, llvm, linux-kernel
From: Hongyan Xia <hongyan.xia@transsion.com>
Clang can outline the raw preempt count helpers from noinstr callers into
ordinary instrumentable text. Force the add and subtract operations inline
so entry code performs the count update in its own noinstr section.
This is the preempt-count hunk from Hongyan Xia's pending Kprobe noinstr
RFC. Keep it as a separate prerequisite so the early-hardirq work neither
hides nor claims that fix.
Link: https://lore.kernel.org/all/c3a90368dd6c096af9d918e036a27728696fb8a4.1786603168.git.hongyan.xia@transsion.com/
Signed-off-by: Hongyan Xia <hongyan.xia@transsion.com>
---
arch/arm64/include/asm/preempt.h | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/include/asm/preempt.h b/arch/arm64/include/asm/preempt.h
index 75a592b7f47f7..f3a5c2cd1936d 100644
--- a/arch/arm64/include/asm/preempt.h
+++ b/arch/arm64/include/asm/preempt.h
@@ -41,14 +41,14 @@ static inline bool test_preempt_need_resched(void)
return !current_thread_info()->preempt.need_resched;
}
-static inline void __preempt_count_add(int val)
+static __always_inline void __preempt_count_add(int val)
{
u32 pc = READ_ONCE(current_thread_info()->preempt.count);
pc += val;
WRITE_ONCE(current_thread_info()->preempt.count, pc);
}
-static inline void __preempt_count_sub(int val)
+static __always_inline void __preempt_count_sub(int val)
{
u32 pc = READ_ONCE(current_thread_info()->preempt.count);
pc -= val;
--
2.39.5 (Apple Git-154)
^ permalink raw reply related [flat|nested] 14+ messages in thread* [RFC PATCH v2 02/13] arm64: percpu: Annotate __kern_my_cpu_offset() as '__always_inline'
2026-10-28 3:31 [RFC PATCH v2 00/13] entry: Classify very early hardirq entry and return-tail callbacks Karl Mehltretter
2026-10-28 3:31 ` [RFC PATCH v2 01/13] arm64: Always inline raw preempt count updates Karl Mehltretter
@ 2026-10-28 3:31 ` Karl Mehltretter
2026-10-28 3:31 ` [RFC PATCH v2 03/13] sched: Split preempt-count bookkeeping from raw updates Karl Mehltretter
` (10 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Karl Mehltretter @ 2026-10-28 3:31 UTC (permalink / raw)
To: Steven Rostedt, Peter Zijlstra, Thomas Gleixner,
Sebastian Andrzej Siewior
Cc: Will Deacon, Catalin Marinas, Mark Rutland, Ingo Molnar,
Andy Lutomirski, Frederic Weisbecker, Paul E. McKenney,
Boqun Feng, Waiman Long, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Ben Segall, Mel Gorman, Valentin Schneider,
K Prateek Nayak, Hongyan Xia, Jonathan Corbet, Shuah Khan,
Randy Dunlap, Andrew Morton, Nathan Chancellor, Nick Desaulniers,
Bill Wendling, Justin Stitt, linux-arm-kernel, linux-trace-kernel,
linux-rt-devel, linux-doc, llvm, linux-kernel, Linus Walleij,
Jinjie Ruan
From: Will Deacon <will@kernel.org>
Allow the use of per-cpu variables from noinstr functions by marking
__kern_my_cpu_offset() as '__always_inline'. This will later be relied
upon to detect stack overflow of the overflow stack from noinstr C code.
Signed-off-by: Will Deacon <will@kernel.org>
Reviewed-by: Linus Walleij <linusw@kernel.org>
Reviewed-by: Jinjie Ruan <ruanjinjie@huawei.com>
Link: https://lore.kernel.org/r/20260918161407.2300-12-will@kernel.org
---
arch/arm64/include/asm/percpu.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/arch/arm64/include/asm/percpu.h b/arch/arm64/include/asm/percpu.h
index 3cc316091d665..105f659bf8061 100644
--- a/arch/arm64/include/asm/percpu.h
+++ b/arch/arm64/include/asm/percpu.h
@@ -37,7 +37,7 @@ static inline unsigned long __hyp_my_cpu_offset(void)
"mrs " dst ", tpidr_el2", \
ARM64_HAS_VIRT_HOST_EXTN)
-static inline unsigned long __kern_my_cpu_offset(void)
+static __always_inline unsigned long __kern_my_cpu_offset(void)
{
unsigned long off;
--
2.39.5 (Apple Git-154)
^ permalink raw reply related [flat|nested] 14+ messages in thread* [RFC PATCH v2 03/13] sched: Split preempt-count bookkeeping from raw updates
2026-10-28 3:31 [RFC PATCH v2 00/13] entry: Classify very early hardirq entry and return-tail callbacks Karl Mehltretter
2026-10-28 3:31 ` [RFC PATCH v2 01/13] arm64: Always inline raw preempt count updates Karl Mehltretter
2026-10-28 3:31 ` [RFC PATCH v2 02/13] arm64: percpu: Annotate __kern_my_cpu_offset() as '__always_inline' Karl Mehltretter
@ 2026-10-28 3:31 ` Karl Mehltretter
2026-10-28 3:31 ` [RFC PATCH v2 04/13] lockdep: Use raw access for pinned hardirq state Karl Mehltretter
` (9 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Karl Mehltretter @ 2026-10-28 3:31 UTC (permalink / raw)
To: Steven Rostedt, Peter Zijlstra, Thomas Gleixner,
Sebastian Andrzej Siewior
Cc: Karl Mehltretter, Catalin Marinas, Will Deacon, Mark Rutland,
Ingo Molnar, Andy Lutomirski, Frederic Weisbecker,
Paul E. McKenney, Boqun Feng, Waiman Long, Juri Lelli,
Vincent Guittot, Dietmar Eggemann, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Hongyan Xia, Jonathan Corbet,
Shuah Khan, Randy Dunlap, Andrew Morton, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt, linux-arm-kernel,
linux-trace-kernel, linux-rt-devel, linux-doc, llvm, linux-kernel
Very early hardirq entry code needs to update the preempt count before
instrumentable debug and tracing code can run. Conversely, hardirq return
code needs to finish instrumentable validation and latency tracing before
raw teardown.
Add a small hardirq-entry header with irqentry_hardirq_count_start() to
stage the raw HARDIRQ_OFFSET addition and record whether it was applied in
an opaque cookie. With DEBUG_PREEMPT and without separate reschedule bits,
route an already-negative count through a noinstr slow helper which
preserves the existing DEBUG_LOCKS_WARN_ON() oops short circuit. Add
irqentry_hardirq_count_finish() to report a rejected addition or complete
post-add debug checks and latency tracing, and add
irqentry_hardirq_count_sub_prepare() for validation and tracing before a
raw subtraction. Keep preempt_count_add() and preempt_count_sub() behavior
and ordering unchanged.
Keep latency tracing in each calling path so CALLER_ADDR0 identifies the
caller. Force the start and stop helpers inline because trace provenance
depends on their stack frames.
Assisted-by: LLM
---
include/linux/hardirq.h | 1 +
include/linux/irqentry_hardirq.h | 81 +++++++++++++++++++++++++
kernel/sched/core.c | 101 +++++++++++++++++++++++++++----
3 files changed, 171 insertions(+), 12 deletions(-)
create mode 100644 include/linux/irqentry_hardirq.h
diff --git a/include/linux/hardirq.h b/include/linux/hardirq.h
index 2648c49cd8f6c..dcbda3c1d5636 100644
--- a/include/linux/hardirq.h
+++ b/include/linux/hardirq.h
@@ -4,6 +4,7 @@
#include <linux/context_tracking_state.h>
#include <linux/preempt.h>
+#include <linux/irqentry_hardirq.h>
#include <linux/lockdep.h>
#include <linux/ftrace_irq.h>
#include <linux/sched.h>
diff --git a/include/linux/irqentry_hardirq.h b/include/linux/irqentry_hardirq.h
new file mode 100644
index 0000000000000..1937fc972e846
--- /dev/null
+++ b/include/linux/irqentry_hardirq.h
@@ -0,0 +1,81 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __LINUX_IRQENTRY_HARDIRQ_H
+#define __LINUX_IRQENTRY_HARDIRQ_H
+
+#include <linux/preempt.h>
+
+struct irqentry_hardirq_cookie {
+#if defined(CONFIG_DEBUG_PREEMPT) && \
+ !defined(CONFIG_HAS_SEPARATE_PREEMPT_RESCHED_BITS)
+ bool applied;
+#endif
+};
+
+static __always_inline bool
+irqentry_hardirq_count_applied(struct irqentry_hardirq_cookie cookie)
+{
+#if defined(CONFIG_DEBUG_PREEMPT) && \
+ !defined(CONFIG_HAS_SEPARATE_PREEMPT_RESCHED_BITS)
+ return cookie.applied;
+#else
+ return true;
+#endif
+}
+
+static __always_inline bool irqentry_hardirq_count_valid(void)
+{
+#ifdef CONFIG_DEBUG_PREEMPT
+ /*
+ * Without separate reschedule bits, preempt_count() excludes any
+ * shared reschedule bit. A negative result therefore denotes an
+ * existing underflow.
+ */
+ if (!IS_ENABLED(CONFIG_HAS_SEPARATE_PREEMPT_RESCHED_BITS) &&
+ preempt_count() < 0)
+ return false;
+#endif
+ return true;
+}
+
+#if defined(CONFIG_DEBUG_PREEMPT) && \
+ !defined(CONFIG_HAS_SEPARATE_PREEMPT_RESCHED_BITS)
+struct irqentry_hardirq_cookie noinstr
+__irqentry_hardirq_count_start_slow(void);
+#endif
+
+/*
+ * Attempt the non-instrumentable part of a staged hardirq-count addition and
+ * record whether it was applied. The returned cookie must be passed to the
+ * matching irqentry_hardirq_count_finish() and subtraction preparation. A
+ * rejected cookie attempts deferred reporting and suppresses post-add
+ * bookkeeping and the matching subtraction regardless of warning
+ * suppression.
+ */
+static __always_inline struct irqentry_hardirq_cookie
+irqentry_hardirq_count_start(void)
+{
+ struct irqentry_hardirq_cookie cookie = { };
+
+#if defined(CONFIG_DEBUG_PREEMPT) && \
+ !defined(CONFIG_HAS_SEPARATE_PREEMPT_RESCHED_BITS)
+ cookie.applied = true;
+ /* DEBUG_LOCKS_WARN_ON() permits the addition during an oops. */
+ if (unlikely(!irqentry_hardirq_count_valid()))
+ return __irqentry_hardirq_count_start_slow();
+#endif
+ __preempt_count_add(HARDIRQ_OFFSET);
+
+ return cookie;
+}
+
+#if defined(CONFIG_PREEMPTION) && (defined(CONFIG_DEBUG_PREEMPT) || \
+ defined(CONFIG_TRACE_PREEMPT_TOGGLE))
+void irqentry_hardirq_count_finish(struct irqentry_hardirq_cookie cookie);
+bool irqentry_hardirq_count_sub_prepare(void);
+#else
+static __always_inline void
+irqentry_hardirq_count_finish(struct irqentry_hardirq_cookie cookie) { }
+static __always_inline bool irqentry_hardirq_count_sub_prepare(void) { return true; }
+#endif
+
+#endif /* __LINUX_IRQENTRY_HARDIRQ_H */
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 04b62c16fa8ca..7e2da8fc32b6d 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -6028,7 +6028,7 @@ static inline void sched_tick_stop(int cpu) { }
* If the value passed in is equal to the current preempt count
* then we just disabled preemption. Start timing the latency.
*/
-static inline void preempt_latency_start(int val)
+static __always_inline void preempt_latency_start(int val)
{
if (preempt_count() == val) {
unsigned long ip = get_lock_parent_ip();
@@ -6039,6 +6039,62 @@ static inline void preempt_latency_start(int val)
}
}
+static __always_inline void preempt_count_add_debug_checks(void)
+{
+#ifdef CONFIG_DEBUG_PREEMPT
+ DEBUG_LOCKS_WARN_ON((preempt_count() & PREEMPT_MASK) >=
+ PREEMPT_MASK - 10);
+#endif
+}
+
+#if defined(CONFIG_DEBUG_PREEMPT) && \
+ !defined(CONFIG_HAS_SEPARATE_PREEMPT_RESCHED_BITS)
+/*
+ * Preserve the DEBUG_LOCKS_WARN_ON() add-or-reject decision without invoking
+ * instrumented reporting from noinstr entry code. The caller reaches this
+ * only after observing an already-negative count. Recheck after testing oops
+ * state to retain the original short-circuit order.
+ */
+struct irqentry_hardirq_cookie noinstr
+__irqentry_hardirq_count_start_slow(void)
+{
+ struct irqentry_hardirq_cookie cookie = { };
+
+ if (!READ_ONCE(oops_in_progress) && !irqentry_hardirq_count_valid())
+ return cookie;
+
+ __preempt_count_add(HARDIRQ_OFFSET);
+ cookie.applied = true;
+ return cookie;
+}
+NOKPROBE_SYMBOL(__irqentry_hardirq_count_start_slow);
+#endif
+
+/**
+ * irqentry_hardirq_count_finish - Finish a staged hardirq-count addition
+ * @cookie: Result of the matching irqentry_hardirq_count_start()
+ *
+ * Attempt to report a rejected addition and skip its post-add work, or
+ * complete the post-add debug checks and latency tracing for an applied
+ * addition, after an entry path had to stage the update before instrumentable
+ * code could run.
+ */
+void irqentry_hardirq_count_finish(struct irqentry_hardirq_cookie cookie)
+{
+#if defined(CONFIG_DEBUG_PREEMPT) && \
+ !defined(CONFIG_HAS_SEPARATE_PREEMPT_RESCHED_BITS)
+ bool applied = irqentry_hardirq_count_applied(cookie);
+
+ if (!applied) {
+ DEBUG_LOCKS_WARN_ON(!applied);
+ return;
+ }
+#endif
+ preempt_count_add_debug_checks();
+ preempt_latency_start(HARDIRQ_OFFSET);
+}
+NOKPROBE_SYMBOL(irqentry_hardirq_count_finish);
+
void preempt_count_add(int val)
{
#ifdef CONFIG_DEBUG_PREEMPT
@@ -6054,13 +6110,8 @@ void preempt_count_add(int val)
return;
#endif
__preempt_count_add(val);
-#ifdef CONFIG_DEBUG_PREEMPT
- /*
- * Spinlock count overflowing soon?
- */
- DEBUG_LOCKS_WARN_ON((preempt_count() & PREEMPT_MASK) >=
- PREEMPT_MASK - 10);
-#endif
+ preempt_count_add_debug_checks();
+ /* Keep latency attribution in the preempt_count_add() stack frame. */
preempt_latency_start(val);
}
EXPORT_SYMBOL(preempt_count_add);
@@ -6070,13 +6121,13 @@ NOKPROBE_SYMBOL(preempt_count_add);
* If the value passed in equals to the current preempt count
* then we just enabled preemption. Stop timing the latency.
*/
-static inline void preempt_latency_stop(int val)
+static __always_inline void preempt_latency_stop(int val)
{
if (preempt_count() == val)
trace_preempt_on(CALLER_ADDR0, get_lock_parent_ip());
}
-void preempt_count_sub(int val)
+static __always_inline bool preempt_count_sub_debug_checks(int val)
{
#ifdef CONFIG_DEBUG_PREEMPT
/*
@@ -6086,15 +6137,41 @@ void preempt_count_sub(int val)
unsigned int pc = preempt_count();
if (DEBUG_LOCKS_WARN_ON(pc - uval > pc))
- return;
+ return false;
/*
* Is the spinlock portion underflowing?
*/
if (DEBUG_LOCKS_WARN_ON((val < PREEMPT_MASK) &&
!(preempt_count() & PREEMPT_MASK)))
- return;
+ return false;
#endif
+ return true;
+}
+
+/**
+ * irqentry_hardirq_count_sub_prepare - Prepare a raw hardirq-count subtraction
+ *
+ * Run debug checks and latency tracing before an entry path performs a raw
+ * preempt count subtraction after all instrumentable code has returned.
+ *
+ * Return: true when the caller should perform the subtraction.
+ */
+bool irqentry_hardirq_count_sub_prepare(void)
+{
+ if (!preempt_count_sub_debug_checks(HARDIRQ_OFFSET))
+ return false;
+
+ preempt_latency_stop(HARDIRQ_OFFSET);
+ return true;
+}
+NOKPROBE_SYMBOL(irqentry_hardirq_count_sub_prepare);
+
+void preempt_count_sub(int val)
+{
+ if (!preempt_count_sub_debug_checks(val))
+ return;
+ /* Keep latency attribution in the preempt_count_sub() stack frame. */
preempt_latency_stop(val);
__preempt_count_sub(val);
}
--
2.39.5 (Apple Git-154)
^ permalink raw reply related [flat|nested] 14+ messages in thread* [RFC PATCH v2 04/13] lockdep: Use raw access for pinned hardirq state
2026-10-28 3:31 [RFC PATCH v2 00/13] entry: Classify very early hardirq entry and return-tail callbacks Karl Mehltretter
` (2 preceding siblings ...)
2026-10-28 3:31 ` [RFC PATCH v2 03/13] sched: Split preempt-count bookkeeping from raw updates Karl Mehltretter
@ 2026-10-28 3:31 ` Karl Mehltretter
2026-10-28 3:31 ` [RFC PATCH v2 05/13] entry: Add the pre-counted ordinary-hardirq entry/exit contract Karl Mehltretter
` (8 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Karl Mehltretter @ 2026-10-28 3:31 UTC (permalink / raw)
To: Steven Rostedt, Peter Zijlstra, Thomas Gleixner,
Sebastian Andrzej Siewior
Cc: Karl Mehltretter, Catalin Marinas, Will Deacon, Mark Rutland,
Ingo Molnar, Andy Lutomirski, Frederic Weisbecker,
Paul E. McKenney, Boqun Feng, Waiman Long, Juri Lelli,
Vincent Guittot, Dietmar Eggemann, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Hongyan Xia, Jonathan Corbet,
Shuah Khan, Randy Dunlap, Andrew Morton, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt, linux-arm-kernel,
linux-trace-kernel, linux-rt-devel, linux-doc, llvm, linux-kernel
irqentry_nmi_enter() reads lockdep's hardirq state before __nmi_enter()
establishes NMI context. Arm64 does the same for its NMI-like EL1 debug
entry. Both paths run with interrupts disabled and cannot migrate.
lockdep_hardirqs_enabled() uses this_cpu_read(), whose preemption checks
are unnecessary for these callers. Add a raw accessor and use it in the
two entry paths and in lockdep_hardirqs_off(). Keep the existing accessor
for callers which are not already pinned.
Check the interrupt state before accessing per-CPU state in
lockdep_hardirqs_off(), independently of warning suppression.
On arm64, this change relies on __kern_my_cpu_offset() being forced inline
by the earlier prerequisite patch so the call chain remains noinstr-safe.
Assisted-by: LLM
---
arch/arm64/kernel/entry-common.c | 6 +++---
include/linux/irqflags.h | 3 +++
kernel/entry/common.c | 2 +-
kernel/locking/lockdep.c | 6 ++++--
4 files changed, 11 insertions(+), 6 deletions(-)
diff --git a/arch/arm64/kernel/entry-common.c b/arch/arm64/kernel/entry-common.c
index 11d3f888fcd87..003f84109a913 100644
--- a/arch/arm64/kernel/entry-common.c
+++ b/arch/arm64/kernel/entry-common.c
@@ -177,9 +177,9 @@ asmlinkage void noinstr asm_exit_to_user_mode(struct pt_regs *regs)
*/
static noinstr irqentry_state_t arm64_enter_el1_dbg(struct pt_regs *regs)
{
- irqentry_state_t state;
-
- state.lockdep = lockdep_hardirqs_enabled();
+ irqentry_state_t state = {
+ .lockdep = lockdep_hardirqs_enabled_raw(),
+ };
lockdep_hardirqs_off(CALLER_ADDR0);
ct_nmi_enter();
diff --git a/include/linux/irqflags.h b/include/linux/irqflags.h
index 57b074e0cfbbb..5bc179a88e513 100644
--- a/include/linux/irqflags.h
+++ b/include/linux/irqflags.h
@@ -52,6 +52,8 @@ extern void trace_hardirqs_off(void);
# define lockdep_hardirq_context() (raw_cpu_read(hardirq_context))
# define lockdep_softirq_context(p) ((p)->softirq_context)
# define lockdep_hardirqs_enabled() (this_cpu_read(hardirqs_enabled))
+/* The caller must already be pinned to the current CPU. */
+# define lockdep_hardirqs_enabled_raw() (raw_cpu_read(hardirqs_enabled))
# define lockdep_softirqs_enabled(p) ((p)->softirqs_enabled)
# define lockdep_hardirq_enter() \
do { \
@@ -113,6 +115,7 @@ do { \
# define lockdep_hardirq_context() 0
# define lockdep_softirq_context(p) 0
# define lockdep_hardirqs_enabled() 0
+# define lockdep_hardirqs_enabled_raw() 0
# define lockdep_softirqs_enabled(p) 0
# define lockdep_hardirq_enter() do { } while (0)
# define lockdep_hardirq_threaded() do { } while (0)
diff --git a/kernel/entry/common.c b/kernel/entry/common.c
index e234b04373fea..2f5229132466c 100644
--- a/kernel/entry/common.c
+++ b/kernel/entry/common.c
@@ -158,7 +158,7 @@ irqentry_state_t noinstr irqentry_nmi_enter(struct pt_regs *regs)
{
irqentry_state_t irq_state;
- irq_state.lockdep = lockdep_hardirqs_enabled();
+ irq_state.lockdep = lockdep_hardirqs_enabled_raw();
__nmi_enter();
lockdep_hardirqs_off(CALLER_ADDR0);
diff --git a/kernel/locking/lockdep.c b/kernel/locking/lockdep.c
index c3dc84a7cef26..01dba8fb32a90 100644
--- a/kernel/locking/lockdep.c
+++ b/kernel/locking/lockdep.c
@@ -4543,10 +4543,12 @@ void noinstr lockdep_hardirqs_off(unsigned long ip)
* So we're supposed to get called after you mask local IRQs, but for
* some reason the hardware doesn't quite think you did a proper job.
*/
- if (DEBUG_LOCKS_WARN_ON(!irqs_disabled()))
+ if (unlikely(!irqs_disabled())) {
+ DEBUG_LOCKS_WARN_ON(1);
return;
+ }
- if (lockdep_hardirqs_enabled()) {
+ if (lockdep_hardirqs_enabled_raw()) {
struct irqtrace_events *trace = ¤t->irqtrace;
/*
--
2.39.5 (Apple Git-154)
^ permalink raw reply related [flat|nested] 14+ messages in thread* [RFC PATCH v2 05/13] entry: Add the pre-counted ordinary-hardirq entry/exit contract
2026-10-28 3:31 [RFC PATCH v2 00/13] entry: Classify very early hardirq entry and return-tail callbacks Karl Mehltretter
` (3 preceding siblings ...)
2026-10-28 3:31 ` [RFC PATCH v2 04/13] lockdep: Use raw access for pinned hardirq state Karl Mehltretter
@ 2026-10-28 3:31 ` Karl Mehltretter
2026-10-28 3:31 ` [RFC PATCH v2 06/13] entry: Skip inactive IRQ-return reschedule calls Karl Mehltretter
` (7 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Karl Mehltretter @ 2026-10-28 3:31 UTC (permalink / raw)
To: Steven Rostedt, Peter Zijlstra, Thomas Gleixner,
Sebastian Andrzej Siewior
Cc: Karl Mehltretter, Catalin Marinas, Will Deacon, Mark Rutland,
Ingo Molnar, Andy Lutomirski, Frederic Weisbecker,
Paul E. McKenney, Boqun Feng, Waiman Long, Juri Lelli,
Vincent Guittot, Dietmar Eggemann, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Hongyan Xia, Jonathan Corbet,
Shuah Khan, Randy Dunlap, Andrew Morton, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt, linux-arm-kernel,
linux-trace-kernel, linux-rt-devel, linux-doc, llvm, linux-kernel
Architectures which identify ordinary hardirqs before generic entry need
to install HARDIRQ_OFFSET before context tracking and tracing callbacks
run. Lockdep hardirq state must agree with that count. On exit, both states
must remain installed until every traceable callback has returned.
Add irqentry_hardirq_enter() to stage the raw count update, record whether
it was applied in irqentry_state_t, and establish lockdep state before
instrumentable entry work. irq_enter_rcu_preempt_counted() finishes the
staged count update while preserving the existing timer and hardirq
accounting boundary.
Add a retained exit path which runs IRQ-exit processing and instrumentable
teardown preparation before returning to an immediate conditional raw count
and lockdep teardown. This keeps function-graph return callbacks in hardirq
context.
A return callback after the final softirq drain can raise regular or
forced-thread timer work while hardirq state still suppresses the usual
wakeup. Recheck ksoftirqd, ktimerd, then ksoftirqd after the last traceable
teardown step. Record wake requests made by the main exit path so the
closure does not repeat them.
Keep the existing entry and exit interfaces unchanged. Architectures opt
in only after distinguishing ordinary hardirqs from NMI-like entry. They
must not place instrumentable code between the retained-exit helper and
raw teardown.
Assisted-by: LLM
---
include/linux/hardirq.h | 31 ++++++
include/linux/interrupt.h | 2 +-
include/linux/irq-entry-common.h | 62 +++++++++++-
include/linux/irqflags.h | 9 +-
kernel/entry/common.c | 37 +++++++
kernel/softirq.c | 160 ++++++++++++++++++++++++++-----
6 files changed, 271 insertions(+), 30 deletions(-)
diff --git a/include/linux/hardirq.h b/include/linux/hardirq.h
index dcbda3c1d5636..d4b2754468c09 100644
--- a/include/linux/hardirq.h
+++ b/include/linux/hardirq.h
@@ -26,6 +26,37 @@ static __always_inline void rcu_irq_enter_check_tick(void)
__rcu_irq_enter_check_tick();
}
+void irq_enter_rcu_preempt_counted(struct irqentry_hardirq_cookie cookie);
+bool irq_exit_rcu_preempt_counted_prepare(struct irqentry_hardirq_cookie cookie);
+
+/*
+ * Prepare every potentially instrumentable part of hardirq teardown before
+ * the raw count and lockdep updates. The caller must invoke the matching
+ * finish helper without passing through instrumentable code.
+ */
+static __always_inline bool
+irqentry_hardirq_exit_prepare(struct irqentry_hardirq_cookie cookie)
+{
+ bool do_sub;
+
+ /*
+ * Preempt-on tracing can raise deferred work. The caller must perform
+ * the final worker closure before conditionally removing an applied
+ * raw hardirq count.
+ */
+ do_sub = irqentry_hardirq_count_applied(cookie) &&
+ irqentry_hardirq_count_sub_prepare();
+ lockdep_hardirq_exit_prepare();
+ return do_sub;
+}
+
+static __always_inline void irqentry_hardirq_exit_finish(bool do_sub)
+{
+ if (do_sub)
+ __preempt_count_sub(HARDIRQ_OFFSET);
+ lockdep_hardirq_exit_finish();
+}
+
/*
* It is safe to do non-atomic ops on ->hardirq_context,
* because NMI handlers may not preempt and the ops are
diff --git a/include/linux/interrupt.h b/include/linux/interrupt.h
index ee2dd5349697d..6143ef6074ad6 100644
--- a/include/linux/interrupt.h
+++ b/include/linux/interrupt.h
@@ -640,7 +640,7 @@ DECLARE_PER_CPU(struct task_struct *, ktimerd);
DECLARE_PER_CPU(unsigned long, pending_timer_softirq);
void raise_ktimers_thread(unsigned int nr);
-static inline unsigned int local_timers_pending_force_th(void)
+static __always_inline unsigned int local_timers_pending_force_th(void)
{
return __this_cpu_read(pending_timer_softirq);
}
diff --git a/include/linux/irq-entry-common.h b/include/linux/irq-entry-common.h
index d5a66f54d70b5..289e308d2d46c 100644
--- a/include/linux/irq-entry-common.h
+++ b/include/linux/irq-entry-common.h
@@ -5,6 +5,8 @@
#include <linux/context_tracking.h>
#include <linux/hrtimer_rearm.h>
#include <linux/kmsan.h>
+#include <linux/preempt.h>
+#include <linux/irqentry_hardirq.h>
#include <linux/rseq_entry.h>
#include <linux/static_call_types.h>
#include <linux/syscalls.h>
@@ -76,8 +78,9 @@ static __always_inline bool arch_in_rcu_eqs(void) { return false; }
#endif
/**
- * enter_from_user_mode - Establish state when coming from user mode
+ * __enter_from_user_mode - Establish state when coming from user mode
* @regs: Pointer to currents pt_regs
+ * @hardirq: Whether to establish hardirq lockdep context before tracing
*
* Syscall/interrupt entry disables interrupts, but user mode is traced as
* interrupts enabled. Also with NO_HZ_FULL RCU might be idle.
@@ -95,10 +98,13 @@ static __always_inline bool arch_in_rcu_eqs(void) { return false; }
* done between establishing state and enabling interrupts. The caller must
* enable interrupts before invoking syscall_enter_from_user_mode_work().
*/
-static __always_inline void enter_from_user_mode(struct pt_regs *regs)
+static __always_inline void __enter_from_user_mode(struct pt_regs *regs,
+ bool hardirq)
{
arch_enter_from_user_mode(regs);
lockdep_hardirqs_off(CALLER_ADDR0);
+ if (hardirq)
+ lockdep_hardirq_enter();
CT_WARN_ON(__ct_state() != CT_STATE_USER);
user_exit_irqoff();
@@ -109,6 +115,11 @@ static __always_inline void enter_from_user_mode(struct pt_regs *regs)
instrumentation_end();
}
+static __always_inline void enter_from_user_mode(struct pt_regs *regs)
+{
+ __enter_from_user_mode(regs, false);
+}
+
/**
* arch_exit_to_user_mode_work - Architecture specific TIF work for exit
* to user mode.
@@ -317,6 +328,12 @@ static __always_inline void irqentry_exit_to_user_mode(struct pt_regs *regs)
exit_to_user_mode();
}
+enum irqentry_hardirq_state {
+ IRQENTRY_HARDIRQ_NONE,
+ IRQENTRY_HARDIRQ_APPLIED,
+ IRQENTRY_HARDIRQ_REJECTED,
+};
+
#ifndef irqentry_state
/**
* struct irqentry_state - Opaque object for exception state storage
@@ -324,6 +341,8 @@ static __always_inline void irqentry_exit_to_user_mode(struct pt_regs *regs)
* exit path has to invoke ct_irq_exit().
* @lockdep: Used exclusively in the irqentry_nmi_*() calls; ensures that
* lockdep state is restored correctly on exit from nmi.
+ * @hardirq: Encodes whether the current staged hardirq-count attempt was
+ * applied.
*
* This opaque object is filled in by the irqentry_*_enter() functions and
* must be passed back into the corresponding irqentry_*_exit() functions
@@ -338,9 +357,32 @@ typedef struct irqentry_state {
bool exit_rcu;
bool lockdep;
};
+ u8 hardirq;
} irqentry_state_t;
#endif
+irqentry_state_t noinstr irqentry_hardirq_enter(struct pt_regs *regs);
+
+static __always_inline struct irqentry_hardirq_cookie
+irqentry_hardirq_count_state(irqentry_state_t state)
+{
+ struct irqentry_hardirq_cookie cookie = { };
+
+#if defined(CONFIG_DEBUG_PREEMPT) && \
+ !defined(CONFIG_HAS_SEPARATE_PREEMPT_RESCHED_BITS)
+ cookie.applied = state.hardirq == IRQENTRY_HARDIRQ_APPLIED;
+#endif
+ return cookie;
+}
+
+static __always_inline void
+irqentry_hardirq_count_state_set(irqentry_state_t *state,
+ struct irqentry_hardirq_cookie cookie)
+{
+ state->hardirq = irqentry_hardirq_count_applied(cookie) ?
+ IRQENTRY_HARDIRQ_APPLIED : IRQENTRY_HARDIRQ_REJECTED;
+}
+
/**
* irqentry_exit_cond_resched - Conditionally reschedule on return from interrupt
*
@@ -349,8 +391,9 @@ typedef struct irqentry_state {
void irqentry_exit_cond_resched(void);
/**
- * irqentry_enter_from_kernel_mode - Establish state before invoking the irq handler
+ * __irqentry_enter_from_kernel_mode - Establish state before invoking an exception
* @regs: Pointer to currents pt_regs
+ * @hardirq: Whether to establish hardirq lockdep context before tracing
*
* Invoked from architecture specific entry code with interrupts disabled.
* Can only be called when the interrupt entry came from kernel mode. The
@@ -364,7 +407,8 @@ void irqentry_exit_cond_resched(void);
*
* Returns: An opaque object that must be passed to irqentry_exit_to_kernel_mode().
*/
-static __always_inline irqentry_state_t irqentry_enter_from_kernel_mode(struct pt_regs *regs)
+static __always_inline irqentry_state_t
+__irqentry_enter_from_kernel_mode(struct pt_regs *regs, bool hardirq)
{
irqentry_state_t ret = {
.exit_rcu = false,
@@ -401,6 +445,8 @@ static __always_inline irqentry_state_t irqentry_enter_from_kernel_mode(struct p
* as in irqentry_enter_from_user_mode().
*/
lockdep_hardirqs_off(CALLER_ADDR0);
+ if (hardirq)
+ lockdep_hardirq_enter();
ct_irq_enter();
instrumentation_begin();
kmsan_unpoison_entry_regs(regs);
@@ -418,6 +464,8 @@ static __always_inline irqentry_state_t irqentry_enter_from_kernel_mode(struct p
* in having another one here.
*/
lockdep_hardirqs_off(CALLER_ADDR0);
+ if (hardirq)
+ lockdep_hardirq_enter();
instrumentation_begin();
kmsan_unpoison_entry_regs(regs);
rcu_irq_enter_check_tick();
@@ -427,6 +475,12 @@ static __always_inline irqentry_state_t irqentry_enter_from_kernel_mode(struct p
return ret;
}
+static __always_inline irqentry_state_t
+irqentry_enter_from_kernel_mode(struct pt_regs *regs)
+{
+ return __irqentry_enter_from_kernel_mode(regs, false);
+}
+
/**
* irqentry_exit_to_kernel_mode_preempt - Run preempt checks on return to kernel mode
* @regs: Pointer to current's pt_regs
diff --git a/include/linux/irqflags.h b/include/linux/irqflags.h
index 5bc179a88e513..e98313a8ad307 100644
--- a/include/linux/irqflags.h
+++ b/include/linux/irqflags.h
@@ -64,9 +64,14 @@ do { \
do { \
current->hardirq_threaded = 1; \
} while (0)
+# define lockdep_hardirq_exit_prepare() \
+ __this_cpu_preempt_check("add")
+# define lockdep_hardirq_exit_finish() \
+ raw_cpu_dec(hardirq_context)
# define lockdep_hardirq_exit() \
do { \
- __this_cpu_dec(hardirq_context); \
+ lockdep_hardirq_exit_prepare(); \
+ lockdep_hardirq_exit_finish(); \
} while (0)
# define lockdep_hrtimer_enter(__hrtimer) \
@@ -119,6 +124,8 @@ do { \
# define lockdep_softirqs_enabled(p) 0
# define lockdep_hardirq_enter() do { } while (0)
# define lockdep_hardirq_threaded() do { } while (0)
+# define lockdep_hardirq_exit_prepare() do { } while (0)
+# define lockdep_hardirq_exit_finish() do { } while (0)
# define lockdep_hardirq_exit() do { } while (0)
# define lockdep_softirq_enter() do { } while (0)
# define lockdep_softirq_exit() do { } while (0)
diff --git a/kernel/entry/common.c b/kernel/entry/common.c
index 2f5229132466c..9d207c37c5707 100644
--- a/kernel/entry/common.c
+++ b/kernel/entry/common.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/futex.h>
+#include <linux/hardirq.h>
#include <linux/highmem.h>
#include <linux/irq-entry-common.h>
#include <linux/jump_label.h>
@@ -120,6 +121,42 @@ noinstr irqentry_state_t irqentry_enter(struct pt_regs *regs)
return irqentry_enter_from_kernel_mode(regs);
}
+/**
+ * irqentry_hardirq_enter - Establish state for ordinary hardirq entry
+ * @regs: Pointer to the interrupted register state
+ *
+ * Attempt a staged raw hardirq-count addition before anything instrumentable
+ * can run and record whether it was applied. Then establish IRQ-disabled and
+ * hardirq lockdep state before context tracking or irqflag tracing can invoke
+ * callbacks. The caller must have distinguished an ordinary hardirq from
+ * NMI-like entry and must finish the staged attempt with
+ * irq_enter_rcu_preempt_counted().
+ *
+ * Returns: An opaque object for the matching irqentry exit operation.
+ */
+noinstr irqentry_state_t irqentry_hardirq_enter(struct pt_regs *regs)
+{
+ struct irqentry_hardirq_cookie cookie;
+ irqentry_state_t ret;
+
+ cookie = irqentry_hardirq_count_start();
+
+ if (user_mode(regs)) {
+ ret = (irqentry_state_t) {
+ .exit_rcu = false,
+ };
+ irqentry_hardirq_count_state_set(&ret, cookie);
+
+ __enter_from_user_mode(regs, true);
+ rseq_note_user_irq_entry();
+ return ret;
+ }
+
+ ret = __irqentry_enter_from_kernel_mode(regs, true);
+ irqentry_hardirq_count_state_set(&ret, cookie);
+ return ret;
+}
+
/**
* arch_irqentry_exit_need_resched - Architecture specific need resched function
*
diff --git a/kernel/softirq.c b/kernel/softirq.c
index 7fc2200392cdf..169c559832501 100644
--- a/kernel/softirq.c
+++ b/kernel/softirq.c
@@ -14,6 +14,7 @@
#include <linux/kernel_stat.h>
#include <linux/interrupt.h>
#include <linux/init.h>
+#include <linux/instrumentation.h>
#include <linux/local_lock.h>
#include <linux/mm.h>
#include <linux/notifier.h>
@@ -353,15 +354,18 @@ static inline void ksoftirqd_run_end(void)
static inline bool softirq_handle_begin(void) { return false; }
static inline void softirq_handle_end(bool from_irq_exit) { }
-static inline bool should_wake_ksoftirqd(void)
+static __always_inline bool should_wake_ksoftirqd(void)
{
return !this_cpu_read(softirq_ctrl.cnt);
}
-static inline void invoke_softirq(void)
+static inline bool invoke_softirq(void)
{
- if (should_wake_ksoftirqd())
- wakeup_softirqd();
+ if (!should_wake_ksoftirqd())
+ return false;
+
+ wakeup_softirqd();
+ return true;
}
#define SCHED_SOFTIRQ_MASK BIT(SCHED_SOFTIRQ)
@@ -525,12 +529,12 @@ static inline void ksoftirqd_run_end(void)
local_irq_enable();
}
-static inline bool should_wake_ksoftirqd(void)
+static __always_inline bool should_wake_ksoftirqd(void)
{
return true;
}
-static inline void invoke_softirq(void)
+static inline bool invoke_softirq(void)
{
if (!force_irqthreads() || !__this_cpu_read(ksoftirqd)) {
#ifdef CONFIG_HAVE_IRQ_EXIT_ON_IRQ_STACK
@@ -548,9 +552,11 @@ static inline void invoke_softirq(void)
*/
do_softirq_own_stack();
#endif
- } else {
- wakeup_softirqd();
+ return false;
}
+
+ wakeup_softirqd();
+ return true;
}
asmlinkage __visible void do_softirq(void)
@@ -703,13 +709,8 @@ asmlinkage __visible void __softirq_entry __do_softirq(void)
handle_softirqs(false);
}
-/**
- * irq_enter_rcu - Enter an interrupt context with RCU watching
- */
-void irq_enter_rcu(void)
+static __always_inline void irq_enter_rcu_common(void)
{
- __irq_enter_raw();
-
/*
* If this is a nested interrupt that hits the exit_to_user_mode_loop
* where it has enabled interrupts but before it has hit schedule() we
@@ -724,6 +725,32 @@ void irq_enter_rcu(void)
account_hardirq_enter(current);
}
+/**
+ * irq_enter_rcu_preempt_counted - Finish staged hardirq entry
+ * @cookie: Staged hardirq-count cookie from irqentry_hardirq_enter()
+ *
+ * Finish an early staged hardirq preempt-count attempt, then establish the
+ * remaining timer and accounting state. The matching lockdep hardirq state
+ * was established by irqentry_hardirq_enter() before entry tracing. An
+ * applied update receives post-add bookkeeping. A rejected update attempts
+ * deferred reporting and skips that bookkeeping even if reporting is
+ * suppressed.
+ */
+void irq_enter_rcu_preempt_counted(struct irqentry_hardirq_cookie cookie)
+{
+ irqentry_hardirq_count_finish(cookie);
+ irq_enter_rcu_common();
+}
+
+/**
+ * irq_enter_rcu - Enter an interrupt context with RCU watching
+ */
+void irq_enter_rcu(void)
+{
+ __irq_enter_raw();
+ irq_enter_rcu_common();
+}
+
/**
* irq_enter - Enter an interrupt context including RCU update
*/
@@ -764,8 +791,56 @@ static inline void wake_timersd(void) { }
#endif
-static inline void __irq_exit_rcu(void)
+static __always_inline bool irq_exit_wake_ksoftirqd(void)
{
+ if (irq_count() != HARDIRQ_OFFSET || !local_softirq_pending() ||
+ !should_wake_ksoftirqd())
+ return false;
+
+ wakeup_softirqd();
+ return true;
+}
+
+static __always_inline bool irq_exit_wake_timersd(void)
+{
+ if (!IS_ENABLED(CONFIG_IRQ_FORCED_THREADING) || !force_irqthreads() ||
+ !local_timers_pending_force_th() ||
+ hardirq_count() != HARDIRQ_OFFSET)
+ return false;
+
+ wake_timersd();
+ return true;
+}
+
+enum irq_exit_wake_flags {
+ IRQ_EXIT_WAKE_KSOFTIRQD = BIT(0),
+ IRQ_EXIT_WAKE_KTIMERD = BIT(1),
+};
+
+static __always_inline void
+irq_exit_wake_late_softirqs(unsigned int wake_flags)
+{
+ bool ksoftirqd_wake_requested = wake_flags & IRQ_EXIT_WAKE_KSOFTIRQD;
+ bool ktimerd_wake_requested = wake_flags & IRQ_EXIT_WAKE_KTIMERD;
+
+ /*
+ * A wakeup can be traced, and its callback can raise work for the other
+ * worker. Check ksoftirqd, then ktimerd, then ksoftirqd again. Skip a
+ * worker whose direct wake request was recorded above: it cannot run on
+ * this CPU before hardirq teardown, so later work remains visible to it.
+ */
+ if (!ksoftirqd_wake_requested)
+ ksoftirqd_wake_requested = irq_exit_wake_ksoftirqd();
+ if (!ktimerd_wake_requested)
+ ktimerd_wake_requested = irq_exit_wake_timersd();
+ if (!ksoftirqd_wake_requested && ktimerd_wake_requested)
+ irq_exit_wake_ksoftirqd();
+}
+
+static __always_inline unsigned int __irq_exit_rcu(bool keep_hardirq_count)
+{
+ unsigned int wake_flags = 0;
+
#ifndef __ARCH_IRQ_EXIT_IRQS_DISABLED
local_irq_disable();
#else
@@ -774,7 +849,8 @@ static inline void __irq_exit_rcu(void)
account_hardirq_exit(current);
/*
- * HARDIRQ_OFFSET is still set. Only the outermost interrupt handles
+ * HARDIRQ_OFFSET normally remains set. A rejected staged addition leaves
+ * an already-invalid count unchanged. Only the outermost interrupt handles
* softirqs, and only if it did not hit a softirq or BH disabled section.
*/
if (irq_count() == HARDIRQ_OFFSET && local_softirq_pending()) {
@@ -783,20 +859,56 @@ static inline void __irq_exit_rcu(void)
* before enabling interrupts to run softirq.
*/
hrtimer_rearm_deferred();
- invoke_softirq();
+ if (invoke_softirq() && keep_hardirq_count)
+ wake_flags |= IRQ_EXIT_WAKE_KSOFTIRQD;
}
/*
* Wake the timer thread even if the interrupt hit a softirq or a
* section with BHs disabled. Only the outermost interrupt can wake it.
*/
- if (IS_ENABLED(CONFIG_IRQ_FORCED_THREADING) && force_irqthreads() &&
- local_timers_pending_force_th() &&
- hardirq_count() == HARDIRQ_OFFSET)
- wake_timersd();
+ if (irq_exit_wake_timersd() && keep_hardirq_count)
+ wake_flags |= IRQ_EXIT_WAKE_KTIMERD;
tick_irq_exit();
- preempt_count_sub(HARDIRQ_OFFSET);
+ if (!keep_hardirq_count)
+ preempt_count_sub(HARDIRQ_OFFSET);
+
+ return wake_flags;
+}
+
+/**
+ * irq_exit_rcu_preempt_counted_prepare - Process IRQ exit and prepare teardown
+ * @cookie: Staged hardirq-count cookie from irqentry_hardirq_enter()
+ *
+ * The caller staged a hardirq-count attempt and established lockdep state
+ * before instrumentable IRQ entry. Carry any applied count across traceable
+ * IRQ exit. Softirq handling temporarily replaces HARDIRQ_OFFSET with
+ * SOFTIRQ_OFFSET and restores it afterwards. The caller must invoke the raw
+ * teardown helper immediately after return. A rejected staged-add state makes
+ * that helper skip the matching subtraction.
+ *
+ * Return callbacks can raise regular or forced-thread timer softirqs after
+ * __irq_exit_rcu() performed its final drain and worker wakeups. Recheck both
+ * queues after all traceable preparation. Worker wakeups are traceable too,
+ * so order the rechecks such that a callback which raises work for the other
+ * worker cannot leave it asleep. Do not repeat a direct worker wake
+ * recorded by __irq_exit_rcu(); the worker cannot run on this CPU before
+ * hardirq teardown.
+ */
+bool noinstr
+irq_exit_rcu_preempt_counted_prepare(struct irqentry_hardirq_cookie cookie)
+{
+ unsigned int wake_flags;
+ bool do_sub;
+
+ instrumentation_begin();
+ wake_flags = __irq_exit_rcu(true);
+ do_sub = irqentry_hardirq_exit_prepare(cookie);
+ irq_exit_wake_late_softirqs(wake_flags);
+ instrumentation_end();
+
+ return do_sub;
}
/**
@@ -806,7 +918,7 @@ static inline void __irq_exit_rcu(void)
*/
void irq_exit_rcu(void)
{
- __irq_exit_rcu();
+ __irq_exit_rcu(false);
/* must be last! */
lockdep_hardirq_exit();
}
@@ -818,7 +930,7 @@ void irq_exit_rcu(void)
*/
void irq_exit(void)
{
- __irq_exit_rcu();
+ __irq_exit_rcu(false);
ct_irq_exit();
/* must be last! */
lockdep_hardirq_exit();
--
2.39.5 (Apple Git-154)
^ permalink raw reply related [flat|nested] 14+ messages in thread* [RFC PATCH v2 06/13] entry: Skip inactive IRQ-return reschedule calls
2026-10-28 3:31 [RFC PATCH v2 00/13] entry: Classify very early hardirq entry and return-tail callbacks Karl Mehltretter
` (4 preceding siblings ...)
2026-10-28 3:31 ` [RFC PATCH v2 05/13] entry: Add the pre-counted ordinary-hardirq entry/exit contract Karl Mehltretter
@ 2026-10-28 3:31 ` Karl Mehltretter
2026-10-28 3:31 ` [RFC PATCH v2 07/13] context_tracking: Split IRQ-exit preparation from the EQS transition Karl Mehltretter
` (6 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Karl Mehltretter @ 2026-10-28 3:31 UTC (permalink / raw)
To: Steven Rostedt, Peter Zijlstra, Thomas Gleixner,
Sebastian Andrzej Siewior
Cc: Karl Mehltretter, Catalin Marinas, Will Deacon, Mark Rutland,
Ingo Molnar, Andy Lutomirski, Frederic Weisbecker,
Paul E. McKenney, Boqun Feng, Waiman Long, Juri Lelli,
Vincent Guittot, Dietmar Eggemann, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Hongyan Xia, Jonathan Corbet,
Shuah Khan, Randy Dunlap, Andrew Morton, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt, linux-arm-kernel,
linux-trace-kernel, linux-rt-devel, linux-doc, llvm, linux-kernel
irqentry_exit_to_kernel_mode_preempt() calls the reschedule helper when
preemption is enabled even if the preempt count is nonzero. The helper
returns immediately, but its call and return remain traceable after hardirq
context has been removed.
Check the preempt count in the inline caller and avoid the inactive call.
Keep the existing check in the helper for its other callers. This does not
change whether IRQ return schedules.
Arm64 invokes this caller from noinstr code. Force its preempt_count()
implementation inline so Clang cannot outline the new check into
instrumentable text.
Assisted-by: LLM
---
arch/arm64/include/asm/preempt.h | 2 +-
include/linux/irq-entry-common.h | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/include/asm/preempt.h b/arch/arm64/include/asm/preempt.h
index f3a5c2cd1936d..3e9bcc6e76625 100644
--- a/arch/arm64/include/asm/preempt.h
+++ b/arch/arm64/include/asm/preempt.h
@@ -7,7 +7,7 @@
#define PREEMPT_NEED_RESCHED BIT(32)
#define PREEMPT_ENABLED (PREEMPT_NEED_RESCHED)
-static inline int preempt_count(void)
+static __always_inline int preempt_count(void)
{
return READ_ONCE(current_thread_info()->preempt.count);
}
diff --git a/include/linux/irq-entry-common.h b/include/linux/irq-entry-common.h
index 289e308d2d46c..e1fd9155410c1 100644
--- a/include/linux/irq-entry-common.h
+++ b/include/linux/irq-entry-common.h
@@ -503,7 +503,7 @@ static inline void irqentry_exit_to_kernel_mode_preempt(struct pt_regs *regs,
if (regs_irqs_disabled(regs) || state.exit_rcu)
return;
- if (IS_ENABLED(CONFIG_PREEMPTION))
+ if (IS_ENABLED(CONFIG_PREEMPTION) && !preempt_count())
irqentry_exit_cond_resched();
}
--
2.39.5 (Apple Git-154)
^ permalink raw reply related [flat|nested] 14+ messages in thread* [RFC PATCH v2 07/13] context_tracking: Split IRQ-exit preparation from the EQS transition
2026-10-28 3:31 [RFC PATCH v2 00/13] entry: Classify very early hardirq entry and return-tail callbacks Karl Mehltretter
` (5 preceding siblings ...)
2026-10-28 3:31 ` [RFC PATCH v2 06/13] entry: Skip inactive IRQ-return reschedule calls Karl Mehltretter
@ 2026-10-28 3:31 ` Karl Mehltretter
2026-10-28 3:31 ` [RFC PATCH v2 08/13] lockdep: Prepare IRQ-on state from a hardirq return tail Karl Mehltretter
` (5 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Karl Mehltretter @ 2026-10-28 3:31 UTC (permalink / raw)
To: Steven Rostedt, Peter Zijlstra, Thomas Gleixner,
Sebastian Andrzej Siewior
Cc: Karl Mehltretter, Catalin Marinas, Will Deacon, Mark Rutland,
Ingo Molnar, Andy Lutomirski, Frederic Weisbecker,
Paul E. McKenney, Boqun Feng, Waiman Long, Juri Lelli,
Vincent Guittot, Dietmar Eggemann, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Hongyan Xia, Jonathan Corbet,
Shuah Khan, Randy Dunlap, Andrew Morton, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt, linux-arm-kernel,
linux-trace-kernel, linux-rt-devel, linux-doc, llvm, linux-kernel
ct_irq_exit() combines traceable nesting and tracepoint work with the final
transition to an RCU extended quiescent state. A pre-counted ordinary
hardirq return needs to run that preparation before its synthetic hardirq
classification is removed, but it must enter the quiescent state only after
the raw count and lockdep teardown.
Split IRQ exit into prepare and finish phases. The prepare phase validates
and updates interrupt nesting, then emits the tracepoint while RCU is
watching. The finish phase performs the raw quiescent-state transition and
RCU Tasks bookkeeping.
Keep ct_irq_exit() and ct_nmi_exit() as adjacent prepare and finish
wrappers. Existing IRQ and NMI callers retain their ordering and behavior.
Assisted-by: LLM
---
include/linux/context_tracking_irq.h | 32 +++++++++++-
kernel/context_tracking.c | 78 ++++++++++++++++++++++------
2 files changed, 93 insertions(+), 17 deletions(-)
diff --git a/include/linux/context_tracking_irq.h b/include/linux/context_tracking_irq.h
index 197916ee91a4b..b038cd1249148 100644
--- a/include/linux/context_tracking_irq.h
+++ b/include/linux/context_tracking_irq.h
@@ -2,8 +2,23 @@
#ifndef _LINUX_CONTEXT_TRACKING_IRQ_H
#define _LINUX_CONTEXT_TRACKING_IRQ_H
+#include <linux/types.h>
+
+/**
+ * struct ct_irq_exit_state - Opaque context-tracking IRQ-exit state
+ * @exit_rcu: The interrupted context was in an RCU extended quiescent state
+ *
+ * This object is filled in by ct_irq_exit_prepare() and must be passed to the
+ * matching ct_irq_exit_finish(). Callers must consider its members private.
+ */
+struct ct_irq_exit_state {
+ bool exit_rcu;
+};
+
#ifdef CONFIG_CONTEXT_TRACKING_IDLE
void ct_irq_enter(void);
+struct ct_irq_exit_state ct_irq_exit_prepare(void);
+void ct_irq_exit_finish(struct ct_irq_exit_state state);
void ct_irq_exit(void);
void ct_irq_enter_irqson(void);
void ct_irq_exit_irqson(void);
@@ -11,7 +26,22 @@ void ct_nmi_enter(void);
void ct_nmi_exit(void);
#else
static __always_inline void ct_irq_enter(void) { }
-static __always_inline void ct_irq_exit(void) { }
+static __always_inline struct ct_irq_exit_state ct_irq_exit_prepare(void)
+{
+ return (struct ct_irq_exit_state) { };
+}
+
+static __always_inline void ct_irq_exit_finish(struct ct_irq_exit_state state)
+{
+ (void)state;
+}
+
+static __always_inline void ct_irq_exit(void)
+{
+ struct ct_irq_exit_state state = ct_irq_exit_prepare();
+
+ ct_irq_exit_finish(state);
+}
static inline void ct_irq_enter_irqson(void) { }
static inline void ct_irq_exit_irqson(void) { }
static __always_inline void ct_nmi_enter(void) { }
diff --git a/kernel/context_tracking.c b/kernel/context_tracking.c
index a743e7ffa6c00..80ed8ec606c6a 100644
--- a/kernel/context_tracking.c
+++ b/kernel/context_tracking.c
@@ -169,20 +169,10 @@ static void noinstr ct_kernel_enter(bool user, int offset)
instrumentation_end();
}
-/**
- * ct_nmi_exit - inform RCU of exit from NMI context
- *
- * If we are returning from the outermost NMI handler that interrupted an
- * RCU-idle period, update ct->state and ct->nmi_nesting
- * to let the RCU grace-period handling know that the CPU is back to
- * being RCU-idle.
- *
- * If you add or remove a call to ct_nmi_exit(), be sure to test
- * with CONFIG_RCU_EQS_DEBUG=y.
- */
-void noinstr ct_nmi_exit(void)
+static struct ct_irq_exit_state noinstr __ct_nmi_exit_prepare(void)
{
struct context_tracking *ct = this_cpu_ptr(&context_tracking);
+ struct ct_irq_exit_state state = { };
instrumentation_begin();
/*
@@ -203,7 +193,7 @@ void noinstr ct_nmi_exit(void)
WRITE_ONCE(ct->nmi_nesting, /* No store tearing. */
ct_nmi_nesting() - 2);
instrumentation_end();
- return;
+ return state;
}
/* This NMI interrupted an RCU-idle CPU, restore RCU-idleness. */
@@ -213,6 +203,15 @@ void noinstr ct_nmi_exit(void)
// instrumentation for the noinstr ct_kernel_exit_state()
instrument_atomic_write(&ct->state, sizeof(ct->state));
instrumentation_end();
+ state.exit_rcu = true;
+
+ return state;
+}
+
+static void noinstr __ct_nmi_exit_finish(struct ct_irq_exit_state state)
+{
+ if (!state.exit_rcu)
+ return;
// RCU is watching here ...
ct_kernel_exit_state(CT_RCU_WATCHING);
@@ -222,6 +221,24 @@ void noinstr ct_nmi_exit(void)
rcu_task_exit();
}
+/**
+ * ct_nmi_exit - inform RCU of exit from NMI context
+ *
+ * If we are returning from the outermost NMI handler that interrupted an
+ * RCU-idle period, update ct->state and ct->nmi_nesting
+ * to let the RCU grace-period handling know that the CPU is back to
+ * being RCU-idle.
+ *
+ * If you add or remove a call to ct_nmi_exit(), be sure to test
+ * with CONFIG_RCU_EQS_DEBUG=y.
+ */
+void noinstr ct_nmi_exit(void)
+{
+ struct ct_irq_exit_state state = __ct_nmi_exit_prepare();
+
+ __ct_nmi_exit_finish(state);
+}
+
/**
* ct_nmi_enter - inform RCU of entry to NMI context
*
@@ -348,7 +365,35 @@ noinstr void ct_irq_enter(void)
}
/**
- * ct_irq_exit - inform RCU that current CPU is exiting irq towards idle
+ * ct_irq_exit_prepare - prepare to exit an IRQ towards idle
+ *
+ * Perform the traceable part of an IRQ exit and return an opaque object for
+ * ct_irq_exit_finish(). RCU remains watching until the matching finish call.
+ * The caller must keep interrupts disabled, must not schedule or migrate, and
+ * must invoke ct_irq_exit_finish() exactly once.
+ *
+ * Return: An opaque object to pass to ct_irq_exit_finish().
+ */
+struct ct_irq_exit_state noinstr ct_irq_exit_prepare(void)
+{
+ lockdep_assert_irqs_disabled();
+ return __ct_nmi_exit_prepare();
+}
+
+/**
+ * ct_irq_exit_finish - finish exiting an IRQ towards idle
+ * @state: Return value from the matching ct_irq_exit_prepare()
+ *
+ * Complete a pending transition to an RCU extended quiescent state. Nothing
+ * traceable may run between this function and the low-level return path.
+ */
+void noinstr ct_irq_exit_finish(struct ct_irq_exit_state state)
+{
+ __ct_nmi_exit_finish(state);
+}
+
+/**
+ * ct_irq_exit - inform RCU that current CPU is exiting IRQ towards idle
*
* Exit from an interrupt handler, which might possibly result in entering
* idle mode, in other words, leaving the mode in which read-side critical
@@ -368,8 +413,9 @@ noinstr void ct_irq_enter(void)
*/
noinstr void ct_irq_exit(void)
{
- lockdep_assert_irqs_disabled();
- ct_nmi_exit();
+ struct ct_irq_exit_state state = ct_irq_exit_prepare();
+
+ ct_irq_exit_finish(state);
}
/*
--
2.39.5 (Apple Git-154)
^ permalink raw reply related [flat|nested] 14+ messages in thread* [RFC PATCH v2 08/13] lockdep: Prepare IRQ-on state from a hardirq return tail
2026-10-28 3:31 [RFC PATCH v2 00/13] entry: Classify very early hardirq entry and return-tail callbacks Karl Mehltretter
` (6 preceding siblings ...)
2026-10-28 3:31 ` [RFC PATCH v2 07/13] context_tracking: Split IRQ-exit preparation from the EQS transition Karl Mehltretter
@ 2026-10-28 3:31 ` Karl Mehltretter
2026-10-28 3:31 ` [RFC PATCH v2 09/13] entry: Carry ordinary-hardirq provenance through kernel return Karl Mehltretter
` (4 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Karl Mehltretter @ 2026-10-28 3:31 UTC (permalink / raw)
To: Steven Rostedt, Peter Zijlstra, Thomas Gleixner,
Sebastian Andrzej Siewior
Cc: Karl Mehltretter, Catalin Marinas, Will Deacon, Mark Rutland,
Ingo Molnar, Andy Lutomirski, Frederic Weisbecker,
Paul E. McKenney, Boqun Feng, Waiman Long, Juri Lelli,
Vincent Guittot, Dietmar Eggemann, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Hongyan Xia, Jonathan Corbet,
Shuah Khan, Randy Dunlap, Andrew Morton, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt, linux-arm-kernel,
linux-trace-kernel, linux-rt-devel, linux-doc, llvm, linux-kernel
lockdep_hardirqs_on_prepare() rejects any live hardirq context. An ordinary
hardirq return tail needs to retain synthetic hardirq nesting through
traceable IRQ-on preparation. This keeps callbacks classified as hardirq
work.
Factor the common preparation and add a return-tail variant which requires
the synthetic hardirq context to be present. An interrupted outer hardirq
can contribute additional nesting. Keep the ordinary helper restricted to
zero. Neither helper changes IRQ or hardirq state. The caller must remove
the tail context before lockdep_hardirqs_on().
Assisted-by: LLM
---
include/linux/irqflags.h | 2 ++
kernel/locking/lockdep.c | 43 +++++++++++++++++++++++++++++-----------
2 files changed, 33 insertions(+), 12 deletions(-)
diff --git a/include/linux/irqflags.h b/include/linux/irqflags.h
index e98313a8ad307..c7601a173e55b 100644
--- a/include/linux/irqflags.h
+++ b/include/linux/irqflags.h
@@ -25,6 +25,7 @@ struct task_struct;
extern void lockdep_softirqs_on(unsigned long ip);
extern void lockdep_softirqs_off(unsigned long ip);
extern void lockdep_hardirqs_on_prepare(void);
+void lockdep_hardirqs_on_prepare_irqtail(void);
extern void lockdep_hardirqs_on(unsigned long ip);
extern void lockdep_hardirqs_off(unsigned long ip);
extern void lockdep_cleanup_dead_cpu(unsigned int cpu,
@@ -33,6 +34,7 @@ struct task_struct;
static inline void lockdep_softirqs_on(unsigned long ip) { }
static inline void lockdep_softirqs_off(unsigned long ip) { }
static inline void lockdep_hardirqs_on_prepare(void) { }
+static inline void lockdep_hardirqs_on_prepare_irqtail(void) { }
static inline void lockdep_hardirqs_on(unsigned long ip) { }
static inline void lockdep_hardirqs_off(unsigned long ip) { }
static inline void lockdep_cleanup_dead_cpu(unsigned int cpu,
diff --git a/kernel/locking/lockdep.c b/kernel/locking/lockdep.c
index 01dba8fb32a90..2ecd506715fa7 100644
--- a/kernel/locking/lockdep.c
+++ b/kernel/locking/lockdep.c
@@ -4395,15 +4395,7 @@ static void __trace_hardirqs_on_caller(void)
mark_held_locks(curr, LOCK_ENABLED_SOFTIRQ);
}
-/**
- * lockdep_hardirqs_on_prepare - Prepare for enabling interrupts
- *
- * Invoked before a possible transition to RCU idle from exit to user or
- * guest mode. This ensures that all RCU operations are done before RCU
- * stops watching. After the RCU transition lockdep_hardirqs_on() has to be
- * invoked to set the final state.
- */
-void lockdep_hardirqs_on_prepare(void)
+static void __lockdep_hardirqs_on_prepare(bool expect_hardirq)
{
if (unlikely(!debug_locks))
return;
@@ -4442,10 +4434,11 @@ void lockdep_hardirqs_on_prepare(void)
return;
/*
- * Can't allow enabling interrupts while in an interrupt handler,
- * that's general bad form and such. Recursion, limited stack etc..
+ * Ordinary IRQ enable requires no hardirq context. The interrupt return
+ * tail variant requires its synthetic context, possibly nested inside
+ * an interrupted outer hardirq.
*/
- if (DEBUG_LOCKS_WARN_ON(lockdep_hardirq_context()))
+ if (DEBUG_LOCKS_WARN_ON(!!lockdep_hardirq_context() != expect_hardirq))
return;
current->hardirq_chain_key = current->curr_chain_key;
@@ -4454,8 +4447,34 @@ void lockdep_hardirqs_on_prepare(void)
__trace_hardirqs_on_caller();
lockdep_recursion_finish();
}
+
+/**
+ * lockdep_hardirqs_on_prepare - Prepare for enabling interrupts
+ *
+ * Invoked before a possible transition to RCU idle from exit to user or
+ * guest mode. This ensures that all RCU operations are done before RCU
+ * stops watching. After the RCU transition lockdep_hardirqs_on() has to be
+ * invoked to set the final state.
+ */
+void lockdep_hardirqs_on_prepare(void)
+{
+ __lockdep_hardirqs_on_prepare(false);
+}
EXPORT_SYMBOL_GPL(lockdep_hardirqs_on_prepare);
+/**
+ * lockdep_hardirqs_on_prepare_irqtail - Prepare an interrupt return tail
+ *
+ * Like lockdep_hardirqs_on_prepare(), but invoked while the synthetic lockdep
+ * hardirq context covering an interrupt return tail is still active. An
+ * interrupted outer hardirq can contribute additional nesting. The caller
+ * must remove the tail context before lockdep_hardirqs_on().
+ */
+void lockdep_hardirqs_on_prepare_irqtail(void)
+{
+ __lockdep_hardirqs_on_prepare(true);
+}
+
void noinstr lockdep_hardirqs_on(unsigned long ip)
{
struct irqtrace_events *trace = ¤t->irqtrace;
--
2.39.5 (Apple Git-154)
^ permalink raw reply related [flat|nested] 14+ messages in thread* [RFC PATCH v2 09/13] entry: Carry ordinary-hardirq provenance through kernel return
2026-10-28 3:31 [RFC PATCH v2 00/13] entry: Classify very early hardirq entry and return-tail callbacks Karl Mehltretter
` (7 preceding siblings ...)
2026-10-28 3:31 ` [RFC PATCH v2 08/13] lockdep: Prepare IRQ-on state from a hardirq return tail Karl Mehltretter
@ 2026-10-28 3:31 ` Karl Mehltretter
2026-10-28 3:31 ` [RFC PATCH v2 10/13] entry: Classify ordinary hardirq return-tail work as hardirq context Karl Mehltretter
` (3 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Karl Mehltretter @ 2026-10-28 3:31 UTC (permalink / raw)
To: Steven Rostedt, Peter Zijlstra, Thomas Gleixner,
Sebastian Andrzej Siewior
Cc: Karl Mehltretter, Catalin Marinas, Will Deacon, Mark Rutland,
Ingo Molnar, Andy Lutomirski, Frederic Weisbecker,
Paul E. McKenney, Boqun Feng, Waiman Long, Juri Lelli,
Vincent Guittot, Dietmar Eggemann, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Hongyan Xia, Jonathan Corbet,
Shuah Khan, Randy Dunlap, Andrew Morton, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt, linux-arm-kernel,
linux-trace-kernel, linux-rt-devel, linux-doc, llvm, linux-kernel
The entry state can cross hardirq teardown and a possible reschedule before
the architecture completes its return tail. At that point preempt_count()
no longer identifies whether the original entry was an ordinary hardirq, a
synchronous exception, or an NMI-like event.
Initialize the hardirq state in every entry path so irqentry_state_t
distinguishes ordinary hardirqs from other entries across kernel return
while keeping the two-byte generic state layout. A later return tail can
restore hardirq classification only for entries which established the
pre-counted hardirq lifecycle.
Assisted-by: LLM
---
include/linux/irq-entry-common.h | 6 ++++--
kernel/entry/common.c | 6 +++++-
2 files changed, 9 insertions(+), 3 deletions(-)
diff --git a/include/linux/irq-entry-common.h b/include/linux/irq-entry-common.h
index e1fd9155410c1..6f649f6613312 100644
--- a/include/linux/irq-entry-common.h
+++ b/include/linux/irq-entry-common.h
@@ -341,8 +341,8 @@ enum irqentry_hardirq_state {
* exit path has to invoke ct_irq_exit().
* @lockdep: Used exclusively in the irqentry_nmi_*() calls; ensures that
* lockdep state is restored correctly on exit from nmi.
- * @hardirq: Encodes whether the current staged hardirq-count attempt was
- * applied.
+ * @hardirq: Encodes ordinary-hardirq provenance and whether the current
+ * staged hardirq-count attempt was applied.
*
* This opaque object is filled in by the irqentry_*_enter() functions and
* must be passed back into the corresponding irqentry_*_exit() functions
@@ -412,6 +412,8 @@ __irqentry_enter_from_kernel_mode(struct pt_regs *regs, bool hardirq)
{
irqentry_state_t ret = {
.exit_rcu = false,
+ .hardirq = hardirq ? IRQENTRY_HARDIRQ_APPLIED :
+ IRQENTRY_HARDIRQ_NONE,
};
/*
diff --git a/kernel/entry/common.c b/kernel/entry/common.c
index 9d207c37c5707..c7814c51bb35d 100644
--- a/kernel/entry/common.c
+++ b/kernel/entry/common.c
@@ -112,6 +112,7 @@ noinstr irqentry_state_t irqentry_enter(struct pt_regs *regs)
if (user_mode(regs)) {
irqentry_state_t ret = {
.exit_rcu = false,
+ .hardirq = IRQENTRY_HARDIRQ_NONE,
};
irqentry_enter_from_user_mode(regs);
@@ -144,6 +145,7 @@ noinstr irqentry_state_t irqentry_hardirq_enter(struct pt_regs *regs)
if (user_mode(regs)) {
ret = (irqentry_state_t) {
.exit_rcu = false,
+ .hardirq = IRQENTRY_HARDIRQ_APPLIED,
};
irqentry_hardirq_count_state_set(&ret, cookie);
@@ -193,7 +195,9 @@ noinstr void irqentry_exit(struct pt_regs *regs, irqentry_state_t state)
irqentry_state_t noinstr irqentry_nmi_enter(struct pt_regs *regs)
{
- irqentry_state_t irq_state;
+ irqentry_state_t irq_state = {
+ .hardirq = IRQENTRY_HARDIRQ_NONE,
+ };
irq_state.lockdep = lockdep_hardirqs_enabled_raw();
--
2.39.5 (Apple Git-154)
^ permalink raw reply related [flat|nested] 14+ messages in thread* [RFC PATCH v2 10/13] entry: Classify ordinary hardirq return-tail work as hardirq context
2026-10-28 3:31 [RFC PATCH v2 00/13] entry: Classify very early hardirq entry and return-tail callbacks Karl Mehltretter
` (8 preceding siblings ...)
2026-10-28 3:31 ` [RFC PATCH v2 09/13] entry: Carry ordinary-hardirq provenance through kernel return Karl Mehltretter
@ 2026-10-28 3:31 ` Karl Mehltretter
2026-10-28 3:31 ` [RFC PATCH v2 11/13] arm64: Use the pre-counted hardirq lifecycle Karl Mehltretter
` (2 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Karl Mehltretter @ 2026-10-28 3:31 UTC (permalink / raw)
To: Steven Rostedt, Peter Zijlstra, Thomas Gleixner,
Sebastian Andrzej Siewior
Cc: Karl Mehltretter, Catalin Marinas, Will Deacon, Mark Rutland,
Ingo Molnar, Andy Lutomirski, Frederic Weisbecker,
Paul E. McKenney, Boqun Feng, Waiman Long, Juri Lelli,
Vincent Guittot, Dietmar Eggemann, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Hongyan Xia, Jonathan Corbet,
Shuah Khan, Randy Dunlap, Andrew Morton, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt, linux-arm-kernel,
linux-trace-kernel, linux-rt-devel, linux-doc, llvm, linux-kernel
An ordinary hardirq removes its original count and lockdep state before the
return path may preempt. Traceable return-tail callbacks then appear to run
in the interrupted task or softirq context and can collide with recursion
state owned by that context.
Use the saved ordinary-hardirq provenance to stage a fresh hardirq-count
attempt and restore lockdep classification after the preemption decision.
Keep it across traceable return-tail work, deferred hrtimer rearming,
IRQ-on preparation, and context-tracking preparation.
Before removing the raw tail state, prepare the conditional count and
lockdep teardown and close late regular or forced-thread softirq work.
Complete any pending RCU quiescent-state transition only after that raw
teardown.
Do not repeat interrupt accounting, RCU or tick entry. Existing non-hardirq
return paths remain unchanged.
Assisted-by: LLM
---
include/linux/hardirq.h | 1 +
include/linux/irq-entry-common.h | 64 ++++++++++++++++++++++++++++++--
kernel/softirq.c | 21 +++++++++++
3 files changed, 83 insertions(+), 3 deletions(-)
diff --git a/include/linux/hardirq.h b/include/linux/hardirq.h
index d4b2754468c09..22b312e674579 100644
--- a/include/linux/hardirq.h
+++ b/include/linux/hardirq.h
@@ -28,6 +28,7 @@ static __always_inline void rcu_irq_enter_check_tick(void)
void irq_enter_rcu_preempt_counted(struct irqentry_hardirq_cookie cookie);
bool irq_exit_rcu_preempt_counted_prepare(struct irqentry_hardirq_cookie cookie);
+void noinstr irqentry_hardirq_tail_exit(struct irqentry_hardirq_cookie cookie);
/*
* Prepare every potentially instrumentable part of hardirq teardown before
diff --git a/include/linux/irq-entry-common.h b/include/linux/irq-entry-common.h
index 6f649f6613312..f51aafc1b49fd 100644
--- a/include/linux/irq-entry-common.h
+++ b/include/linux/irq-entry-common.h
@@ -383,6 +383,32 @@ irqentry_hardirq_count_state_set(irqentry_state_t *state,
IRQENTRY_HARDIRQ_APPLIED : IRQENTRY_HARDIRQ_REJECTED;
}
+/**
+ * irqentry_hardirq_tail_enter - Stage ordinary-hardirq return-tail state
+ * @state: Pointer to state returned by the matching irqentry_hardirq_enter()
+ *
+ * Make a fresh staged hardirq-count attempt and establish the lockdep
+ * classification which traceable return-tail work needs. Accounting, RCU and
+ * tick handling belong to the completed interrupt and must not be repeated
+ * here.
+ */
+static __always_inline void
+irqentry_hardirq_tail_enter(irqentry_state_t *state)
+{
+ struct irqentry_hardirq_cookie cookie;
+
+ if (!state->hardirq)
+ return;
+
+ cookie = irqentry_hardirq_count_start();
+ lockdep_hardirq_enter();
+
+ instrumentation_begin();
+ irqentry_hardirq_count_finish(cookie);
+ instrumentation_end();
+ irqentry_hardirq_count_state_set(state, cookie);
+}
+
/**
* irqentry_exit_cond_resched - Conditionally reschedule on return from interrupt
*
@@ -519,7 +545,10 @@ static inline void irqentry_exit_to_kernel_mode_preempt(struct pt_regs *regs,
*
* The rearm on return to user mode needs no such annotation: no locks are
* held there, so it uses the same lock chains as the rearm in hardirq
- * context.
+ * context. Ordinary-hardirq return paths do not use this wrapper: their saved
+ * provenance makes a fresh hardirq-count attempt and establishes lockdep
+ * nesting across the whole traceable tail. They call
+ * hrtimer_rearm_deferred() directly.
*/
static __always_inline void irqentry_hrtimer_rearm_deferred(void)
{
@@ -534,7 +563,9 @@ static __always_inline void irqentry_hrtimer_rearm_deferred(void)
* @state: Return value from matching call to irqentry_enter_from_kernel_mode()
*
* This is to be invoked after irqentry_exit_to_kernel_mode_preempt() and before
- * actually returning to the interrupted context.
+ * actually returning to the interrupted context. If @state records ordinary
+ * hardirq provenance, the caller must first invoke
+ * irqentry_hardirq_tail_enter() exactly once.
*
* There are no requirements for the CPU state other than being able to complete
* the tracing, lockdep and RCU state transitions. After this function returns
@@ -543,6 +574,31 @@ static __always_inline void irqentry_hrtimer_rearm_deferred(void)
static __always_inline void
irqentry_exit_to_kernel_mode_after_preempt(struct pt_regs *regs, irqentry_state_t state)
{
+ struct ct_irq_exit_state ct_state = { };
+
+ if (state.hardirq) {
+ if (!regs_irqs_disabled(regs)) {
+ instrumentation_begin();
+ hrtimer_rearm_deferred();
+ /* Tell the tracer that the return will enable IRQs. */
+ trace_hardirqs_on_prepare();
+ lockdep_hardirqs_on_prepare_irqtail();
+ instrumentation_end();
+ }
+
+ if (state.exit_rcu)
+ ct_state = ct_irq_exit_prepare();
+
+ irqentry_hardirq_tail_exit(irqentry_hardirq_count_state(state));
+
+ if (state.exit_rcu)
+ ct_irq_exit_finish(ct_state);
+
+ if (!regs_irqs_disabled(regs))
+ lockdep_hardirqs_on(CALLER_ADDR0);
+ return;
+ }
+
if (!regs_irqs_disabled(regs)) {
/*
* If RCU was not watching on entry this needs to be done
@@ -584,7 +640,8 @@ irqentry_exit_to_kernel_mode_after_preempt(struct pt_regs *regs, irqentry_state_
*
* This is the counterpart of irqentry_enter_from_kernel_mode() and combines
* the calls to irqentry_exit_to_kernel_mode_preempt() and
- * irqentry_exit_to_kernel_mode_after_preempt().
+ * irqentry_exit_to_kernel_mode_after_preempt(). It also establishes the
+ * conditional hardirq return tail between those calls.
*
* The requirement for the CPU state is that it can schedule. After the function
* returns the tracing, lockdep and RCU state transitions are completed and the
@@ -599,6 +656,7 @@ static __always_inline void irqentry_exit_to_kernel_mode(struct pt_regs *regs,
irqentry_exit_to_kernel_mode_preempt(regs, state);
instrumentation_end();
+ irqentry_hardirq_tail_enter(&state);
irqentry_exit_to_kernel_mode_after_preempt(regs, state);
}
diff --git a/kernel/softirq.c b/kernel/softirq.c
index 169c559832501..85b167db37c29 100644
--- a/kernel/softirq.c
+++ b/kernel/softirq.c
@@ -837,6 +837,27 @@ irq_exit_wake_late_softirqs(unsigned int wake_flags)
irq_exit_wake_ksoftirqd();
}
+/**
+ * irqentry_hardirq_tail_exit - Close work and remove return-tail state
+ * @cookie: Staged hardirq-count cookie from irqentry_hardirq_tail_enter()
+ *
+ * Run every instrumentable teardown operation, including the final worker
+ * closure, before conditionally removing any applied raw hardirq count and
+ * ending lockdep nesting.
+ */
+void noinstr
+irqentry_hardirq_tail_exit(struct irqentry_hardirq_cookie cookie)
+{
+ bool do_sub;
+
+ instrumentation_begin();
+ do_sub = irqentry_hardirq_exit_prepare(cookie);
+ irq_exit_wake_late_softirqs(0);
+ instrumentation_end();
+
+ irqentry_hardirq_exit_finish(do_sub);
+}
+
static __always_inline unsigned int __irq_exit_rcu(bool keep_hardirq_count)
{
unsigned int wake_flags = 0;
--
2.39.5 (Apple Git-154)
^ permalink raw reply related [flat|nested] 14+ messages in thread* [RFC PATCH v2 11/13] arm64: Use the pre-counted hardirq lifecycle
2026-10-28 3:31 [RFC PATCH v2 00/13] entry: Classify very early hardirq entry and return-tail callbacks Karl Mehltretter
` (9 preceding siblings ...)
2026-10-28 3:31 ` [RFC PATCH v2 10/13] entry: Classify ordinary hardirq return-tail work as hardirq context Karl Mehltretter
@ 2026-10-28 3:31 ` Karl Mehltretter
2026-10-28 3:31 ` [RFC PATCH v2 12/13] Documentation: entry: Describe " Karl Mehltretter
2026-10-28 3:31 ` [RFC PATCH v2 13/13] locking/selftest: Exercise hardirq return-tail preparation Karl Mehltretter
12 siblings, 0 replies; 14+ messages in thread
From: Karl Mehltretter @ 2026-10-28 3:31 UTC (permalink / raw)
To: Steven Rostedt, Peter Zijlstra, Thomas Gleixner,
Sebastian Andrzej Siewior
Cc: Karl Mehltretter, Catalin Marinas, Will Deacon, Mark Rutland,
Ingo Molnar, Andy Lutomirski, Frederic Weisbecker,
Paul E. McKenney, Boqun Feng, Waiman Long, Juri Lelli,
Vincent Guittot, Dietmar Eggemann, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Hongyan Xia, Jonathan Corbet,
Shuah Khan, Randy Dunlap, Andrew Morton, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt, linux-arm-kernel,
linux-trace-kernel, linux-rt-devel, linux-doc, llvm, linux-kernel
Ordinary EL0 and EL1 interrupt paths establish generic entry state before
irq_enter_rcu() adds HARDIRQ_OFFSET and lockdep nesting. Callbacks in that
early window are therefore not classified as hardirq work. Their exit paths
also drop the classification before function-graph return callbacks and the
kernel return tail have completed.
After the EL1 pseudo-NMI decision, enter ordinary interrupts through
irqentry_hardirq_enter(). Finish the staged count update and delayed timer
and accounting work with irq_enter_rcu_preempt_counted(). Use the retained
exit preparation followed immediately by conditional raw count and lockdep
teardown.
For kernel returns, preserve the existing per-CPU register adjustment, then
stage hardirq return-tail classification before MTE exit tracing. Keep the
pseudo-NMI lifecycle unchanged.
The noinstr path also depends on the raw preempt-count and
__kern_my_cpu_offset() inlining prerequisites earlier in this series.
Link: https://lore.kernel.org/all/c3a90368dd6c096af9d918e036a27728696fb8a4.1786603168.git.hongyan.xia@transsion.com/
Link: https://lore.kernel.org/r/20260918161407.2300-12-will@kernel.org
Assisted-by: LLM
---
arch/arm64/kernel/entry-common.c | 28 ++++++++++++++++++++++------
1 file changed, 22 insertions(+), 6 deletions(-)
diff --git a/arch/arm64/kernel/entry-common.c b/arch/arm64/kernel/entry-common.c
index 003f84109a913..d70cffffe71c6 100644
--- a/arch/arm64/kernel/entry-common.c
+++ b/arch/arm64/kernel/entry-common.c
@@ -94,6 +94,8 @@ static void noinstr __arm64_exit_to_kernel_mode(struct pt_regs *regs,
{
local_daif_mask();
irqentry_exit_pcpu_adjust(regs);
+ /* arm64 uses the split exit API, so enter the tail before MTE tracing. */
+ irqentry_hardirq_tail_enter(&state);
mte_check_tfsr_exit();
irqentry_exit_to_kernel_mode_after_preempt(regs, state);
}
@@ -560,13 +562,19 @@ static __always_inline void __el1_pnmi(struct pt_regs *regs,
static __always_inline void __el1_irq(struct pt_regs *regs,
void (*handler)(struct pt_regs *))
{
+ struct irqentry_hardirq_cookie cookie;
irqentry_state_t state;
+ bool do_sub;
- state = arm64_enter_from_kernel_mode(regs);
+ state = irqentry_hardirq_enter(regs);
+ cookie = irqentry_hardirq_count_state(state);
+ mte_check_tfsr_entry();
+ mte_disable_tco_entry(current);
- irq_enter_rcu();
+ irq_enter_rcu_preempt_counted(cookie);
do_interrupt_handler(regs, handler);
- irq_exit_rcu();
+ do_sub = irq_exit_rcu_preempt_counted_prepare(cookie);
+ irqentry_hardirq_exit_finish(do_sub);
arm64_exit_to_kernel_mode_preempt(regs, state);
}
@@ -877,16 +885,24 @@ asmlinkage void noinstr el0t_64_sync_handler(struct pt_regs *regs)
static void noinstr el0_interrupt(struct pt_regs *regs,
void (*handler)(struct pt_regs *))
{
- arm64_enter_from_user_mode(regs);
+ struct irqentry_hardirq_cookie cookie;
+ irqentry_state_t state;
+ bool do_sub;
+
+ state = irqentry_hardirq_enter(regs);
+ cookie = irqentry_hardirq_count_state(state);
+ mte_disable_tco_entry(current);
+ sme_enter_from_user_mode();
write_sysreg(DAIF_PROCCTX_NOIRQ, daif);
if (regs->pc & BIT(55))
arm64_apply_bp_hardening();
- irq_enter_rcu();
+ irq_enter_rcu_preempt_counted(cookie);
do_interrupt_handler(regs, handler);
- irq_exit_rcu();
+ do_sub = irq_exit_rcu_preempt_counted_prepare(cookie);
+ irqentry_hardirq_exit_finish(do_sub);
arm64_exit_to_user_mode(regs);
}
--
2.39.5 (Apple Git-154)
^ permalink raw reply related [flat|nested] 14+ messages in thread* [RFC PATCH v2 12/13] Documentation: entry: Describe the pre-counted hardirq lifecycle
2026-10-28 3:31 [RFC PATCH v2 00/13] entry: Classify very early hardirq entry and return-tail callbacks Karl Mehltretter
` (10 preceding siblings ...)
2026-10-28 3:31 ` [RFC PATCH v2 11/13] arm64: Use the pre-counted hardirq lifecycle Karl Mehltretter
@ 2026-10-28 3:31 ` Karl Mehltretter
2026-10-28 3:31 ` [RFC PATCH v2 13/13] locking/selftest: Exercise hardirq return-tail preparation Karl Mehltretter
12 siblings, 0 replies; 14+ messages in thread
From: Karl Mehltretter @ 2026-10-28 3:31 UTC (permalink / raw)
To: Steven Rostedt, Peter Zijlstra, Thomas Gleixner,
Sebastian Andrzej Siewior
Cc: Karl Mehltretter, Catalin Marinas, Will Deacon, Mark Rutland,
Ingo Molnar, Andy Lutomirski, Frederic Weisbecker,
Paul E. McKenney, Boqun Feng, Waiman Long, Juri Lelli,
Vincent Guittot, Dietmar Eggemann, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Hongyan Xia, Jonathan Corbet,
Shuah Khan, Randy Dunlap, Andrew Morton, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt, linux-arm-kernel,
linux-trace-kernel, linux-rt-devel, linux-doc, llvm, linux-kernel
The entry documentation covers only the traditional irq_enter_rcu()
path. It does not explain the early-counted entry contract or the retained
and synthetic hardirq exit phases.
Document the staged raw count and lockdep entry, delayed traceable
bookkeeping and rejected-add handling, hardirq accounting,
hardirq-to-softirq classification switch, retained real IRQ exit, possible
preemption and migration, and the classification-only return tail. Also
cover late worker closure, conditional raw teardown and context-tracking
order.
Record that arm64 excludes pseudo-NMIs from this lifecycle and identify its
external noinstr inlining prerequisites.
Assisted-by: LLM
---
Documentation/core-api/entry.rst | 89 ++++++++++++++++++++++++++++----
1 file changed, 79 insertions(+), 10 deletions(-)
diff --git a/Documentation/core-api/entry.rst b/Documentation/core-api/entry.rst
index 09942135aad18..7153cc4120192 100644
--- a/Documentation/core-api/entry.rst
+++ b/Documentation/core-api/entry.rst
@@ -193,21 +193,14 @@ Note that the invocation of the actual interrupt handler is within a
irq_enter_rcu() and irq_exit_rcu() pair.
irq_enter_rcu() updates the preemption count which makes in_hardirq()
-return true, handles NOHZ tick state and interrupt time accounting. This
-means that up to the point where irq_enter_rcu() is invoked in_hardirq()
-returns false.
+return true, handles NOHZ tick state and interrupt time accounting. On this
+entry path, in_hardirq() therefore returns false until irq_enter_rcu() is
+invoked.
irq_exit_rcu() handles interrupt time accounting, handles soft interrupts if
possible, handles the NOHZ tick state and finally undoes the preemption count
update.
-In theory, the preemption count could be updated in irqentry_enter(). In
-practice, deferring this update to irq_enter_rcu() allows the preemption-count
-code to be traced, while also maintaining symmetry with irq_exit_rcu() and
-irqentry_exit(), which are described in the next paragraph. The only downside
-is that the early entry code up to irq_enter_rcu() must be aware that the
-preemption count has not yet been updated with the HARDIRQ_OFFSET state.
-
Note that soft interrupt handlers must run in BH context rather than in hard
interrupt context. irq_exit_rcu() therefore replaces HARDIRQ_OFFSET with
SOFTIRQ_OFFSET in the preemption count while it handles soft interrupts and
@@ -216,6 +209,82 @@ is still attributed to the interrupt. HARDIRQ_OFFSET is removed before
irq_exit_rcu() returns because irqentry_exit() might schedule, which requires
that HARDIRQ_OFFSET has been removed from the preemption count.
+Pre-counted hardirq lifecycle
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Architectures which need hardirq state before early context-tracking or
+tracing callbacks can use the pre-counted lifecycle. It applies only to an
+ordinary hard interrupt after the architecture has distinguished it from an
+NMI-like event:
+
+ * irqentry_hardirq_enter() attempts to add HARDIRQ_OFFSET with a raw update
+ and records whether the update was applied. It establishes IRQ-disabled
+ and lockdep hardirq state before instrumentable entry work. It then runs
+ the generic entry and context-tracking path, including any required
+ transition out of an RCU extended quiescent state.
+
+ * irq_enter_rcu_preempt_counted() later finishes the staged attempt. For an
+ applied update it performs traceable debug and latency bookkeeping. For a
+ rejected update it attempts deferred reporting and skips post-add
+ bookkeeping. It also performs the deferred timer repair, NOHZ tick
+ handling and interrupt accounting from irq_enter_rcu().
+
+ * irq_exit_rcu_preempt_counted_prepare() performs interrupt accounting,
+ softirq handling and NOHZ tick handling while carrying any applied
+ hardirq count across the traceable exit path. When an applied outer
+ hardirq invokes softirqs, both the preempt-count and lockdep
+ classifications temporarily switch from hardirq to softirq and back. The
+ architecture immediately follows the preparation with
+ irqentry_hardirq_exit_finish(), which performs the conditional raw-count
+ subtraction and lockdep teardown.
+
+The staged addition carries an opaque state into its instrumentable finish and
+matching teardown. With DEBUG_PREEMPT and no separate reschedule bits, an
+already-negative count follows the legacy DEBUG_LOCKS_WARN_ON() rule. During
+an oops the warning condition is bypassed and the addition is applied.
+Otherwise the attempt is rejected and leaves the count unchanged. Its finish
+attempts deferred reporting, which an intervening oops can suppress. A
+rejected token always skips post-add bookkeeping and the matching subtraction,
+independently of whether reporting produced a warning.
+
+The real interrupt is complete after that teardown. Return-to-kernel
+preemption can schedule and migrate the task before the architecture-specific
+tail runs, so the current preemption count cannot identify the original entry
+class. irqentry_state_t retains that provenance instead.
+
+For an ordinary hardirq returning to kernel mode,
+irqentry_hardirq_tail_enter() makes a fresh staged attempt to add
+HARDIRQ_OFFSET and establishes lockdep hardirq nesting after the preemption
+decision. This synthetic classification covers traceable architecture tail
+work, deferred timer rearming, IRQ-on preparation and context-tracking
+preparation. It does not repeat interrupt accounting, RCU or tick entry.
+IRQ-on preparation requires this synthetic lockdep nesting to remain present.
+An interrupted outer hardirq can contribute additional nesting.
+
+irqentry_hardirq_tail_exit() performs the final traceable teardown preparation
+and then conditionally removes any applied raw synthetic count. For an applied
+staged count at outermost hardirq depth, both retained exit points recheck
+regular softirq work and forced-thread timer work raised by late callbacks and
+wake the corresponding workers before ending hardirq state. If the interrupted
+context was in an RCU extended quiescent state,
+ct_irq_exit_prepare() runs before the raw tail teardown and
+ct_irq_exit_finish() completes the transition afterwards.
+
+On arm64 the ordinary lifecycle is selected only after the pseudo-NMI
+decision. Pseudo-NMIs continue to use irqentry_nmi_enter() and
+irqentry_nmi_exit(). All raw transitions are in noinstr paths, so
+preempt_count(), the raw preempt-count update helpers and
+__kern_my_cpu_offset() must remain inline. The arm64 conversion includes the
+first requirement and depends on the `arm64 raw preempt-count inlining
+prerequisite`_ and `arm64 per-CPU offset inlining prerequisite`_ for the
+others.
+
+.. _`arm64 raw preempt-count inlining prerequisite`:
+ https://lore.kernel.org/all/c3a90368dd6c096af9d918e036a27728696fb8a4.1786603168.git.hongyan.xia@transsion.com/
+
+.. _`arm64 per-CPU offset inlining prerequisite`:
+ https://lore.kernel.org/r/20260918161407.2300-12-will@kernel.org
+
Even though interrupt handlers are expected to run with local interrupts
disabled, interrupt nesting is common from an entry/exit perspective. For
example, softirq handling happens within an irqentry_{enter,exit}() block with
--
2.39.5 (Apple Git-154)
^ permalink raw reply related [flat|nested] 14+ messages in thread* [RFC PATCH v2 13/13] locking/selftest: Exercise hardirq return-tail preparation
2026-10-28 3:31 [RFC PATCH v2 00/13] entry: Classify very early hardirq entry and return-tail callbacks Karl Mehltretter
` (11 preceding siblings ...)
2026-10-28 3:31 ` [RFC PATCH v2 12/13] Documentation: entry: Describe " Karl Mehltretter
@ 2026-10-28 3:31 ` Karl Mehltretter
12 siblings, 0 replies; 14+ messages in thread
From: Karl Mehltretter @ 2026-10-28 3:31 UTC (permalink / raw)
To: Steven Rostedt, Peter Zijlstra, Thomas Gleixner,
Sebastian Andrzej Siewior
Cc: Karl Mehltretter, Catalin Marinas, Will Deacon, Mark Rutland,
Ingo Molnar, Andy Lutomirski, Frederic Weisbecker,
Paul E. McKenney, Boqun Feng, Waiman Long, Juri Lelli,
Vincent Guittot, Dietmar Eggemann, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Hongyan Xia, Jonathan Corbet,
Shuah Khan, Randy Dunlap, Andrew Morton, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt, linux-arm-kernel,
linux-trace-kernel, linux-rt-devel, linux-doc, llvm, linux-kernel
The return-tail lockdep preparation must see a live hardirq context. A zero
depth must fail. Depth one and nested contexts must pass. The ordinary
preparation helper must continue to reject a live hardirq context.
Add focused boot-time lockdep cases for those four states. Skip the final
IRQ-state transition after an expected preparation failure so that each
failure remains attributable to the preparation invariant.
Assisted-by: LLM
---
lib/locking-selftest.c | 53 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 53 insertions(+)
diff --git a/lib/locking-selftest.c b/lib/locking-selftest.c
index c3d976c801bb4..7e26b757344a4 100644
--- a/lib/locking-selftest.c
+++ b/lib/locking-selftest.c
@@ -222,6 +222,55 @@ static void init_shared_classes(void)
local_irq_enable(); \
local_bh_enable();
+static void irqtail_prepare_test_enter(unsigned int nr)
+{
+ unsigned int i;
+
+ local_irq_disable();
+ for (i = 0; i < nr; i++)
+ __irq_enter_raw();
+ trace_hardirqs_on_prepare();
+}
+
+static void irqtail_prepare_test_exit(unsigned int nr)
+{
+ unsigned int i;
+
+ for (i = 0; i < nr; i++)
+ __irq_exit_raw();
+ if (debug_locks)
+ lockdep_hardirqs_on(_THIS_IP_);
+ raw_local_irq_enable();
+}
+
+static void prepare_irqtail_0(void)
+{
+ irqtail_prepare_test_enter(0);
+ lockdep_hardirqs_on_prepare_irqtail();
+ irqtail_prepare_test_exit(0);
+}
+
+static void prepare_irqtail_1(void)
+{
+ irqtail_prepare_test_enter(1);
+ lockdep_hardirqs_on_prepare_irqtail();
+ irqtail_prepare_test_exit(1);
+}
+
+static void prepare_irqtail_2(void)
+{
+ irqtail_prepare_test_enter(2);
+ lockdep_hardirqs_on_prepare_irqtail();
+ irqtail_prepare_test_exit(2);
+}
+
+static void prepare_regular_1(void)
+{
+ irqtail_prepare_test_enter(1);
+ lockdep_hardirqs_on_prepare();
+ irqtail_prepare_test_exit(1);
+}
+
/*
* Shortcuts for lock/unlock API variants, to keep
* the testcases compact:
@@ -2934,6 +2983,10 @@ void locking_selftest(void)
DO_TESTCASE_6x2x2RW("irq read-recursion", irq_read_recursion);
DO_TESTCASE_6x2x2RW("irq read-recursion #2", irq_read_recursion2);
DO_TESTCASE_6x2x2RW("irq read-recursion #3", irq_read_recursion3);
+ DO_TESTCASE_1B("IRQ tail prepare", prepare_irqtail, 0);
+ DO_TESTCASE_1("IRQ tail prepare", prepare_irqtail, 1);
+ DO_TESTCASE_1("IRQ tail prepare", prepare_irqtail, 2);
+ DO_TESTCASE_1B("regular IRQ prepare", prepare_regular, 1);
ww_tests();
--
2.39.5 (Apple Git-154)
^ permalink raw reply related [flat|nested] 14+ messages in thread