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)