[PATCH 15/17] arm64/nmi: Add handling of superpriority interrupts as NMIs

From: Jinjie Ruan

Date: Fri Jul 03 2026 - 06:07:41 EST


From: Mark Brown <broonie@xxxxxxxxxx>

Our goal with superpriority interrupts is to use them as NMIs, taking
advantage of the much smaller regions where they are masked to allow
prompt handling of the most time critical interrupts.

When an interrupt configured with superpriority we will enter EL1 as
normal for any interrupt, the presence of a superpriority interrupt is
indicated with a status bit in ISR_EL1. We use this to check for the
presence of a superpriority interrupt before we unmask anything in
elX_interrupt(), reporting without unmasking any interrupts. If no
superpriority interrupt is present then we handle normal interrupts as
normal, superpriority interrupts will be unmasked while doing so as a
result of setting DAIF_PROCCTX.

Both IRQs and FIQs may be configured with superpriority so we handle
both, passing an additional root handler into the elX_interrupt()
function along with the mask for the bit in ISR_EL1 which indicates the
presence of the relevant kind of superpriority interrupt. These root
handlers can be configured by the interrupt controller similarly to the
root handlers for normal interrupts using the newly added
set_handle_nmi_irq() and set_handle_nmi_fiq() functions.

Signed-off-by: Mark Brown <broonie@xxxxxxxxxx>
Signed-off-by: Marc Zyngier <maz@xxxxxxxxxx>
Link: https://lore.kernel.org/r/20221112151708.175147-13-broonie@xxxxxxxxxx
Co-developed-by: Jinjie Ruan <ruanjinjie@xxxxxxxxxx>
Signed-off-by: Jinjie Ruan <ruanjinjie@xxxxxxxxxx>
---
arch/arm64/include/asm/irq.h | 2 ++
arch/arm64/kernel/entry-common.c | 59 +++++++++++++++++++++++++++-----
arch/arm64/kernel/irq.c | 32 +++++++++++++++++
arch/arm64/kernel/process.c | 4 +++
4 files changed, 89 insertions(+), 8 deletions(-)

diff --git a/arch/arm64/include/asm/irq.h b/arch/arm64/include/asm/irq.h
index e93548914c36..bf77769c5dfb 100644
--- a/arch/arm64/include/asm/irq.h
+++ b/arch/arm64/include/asm/irq.h
@@ -13,6 +13,8 @@ void arch_trigger_cpumask_backtrace(const cpumask_t *mask, int exclude_cpu);

struct pt_regs;

+int set_handle_nmi_irq(void (*handle_irq)(struct pt_regs *));
+int set_handle_nmi_fiq(void (*handle_fiq)(struct pt_regs *));
int set_handle_irq(void (*handle_irq)(struct pt_regs *));
#define set_handle_irq set_handle_irq
int set_handle_fiq(void (*handle_fiq)(struct pt_regs *));
diff --git a/arch/arm64/kernel/entry-common.c b/arch/arm64/kernel/entry-common.c
index dc223085e773..4115a5e7c392 100644
--- a/arch/arm64/kernel/entry-common.c
+++ b/arch/arm64/kernel/entry-common.c
@@ -162,6 +162,8 @@ static void do_interrupt_handler(struct pt_regs *regs,
set_irq_regs(old_regs);
}

+extern void (*handle_arch_nmi_irq)(struct pt_regs *);
+extern void (*handle_arch_nmi_fiq)(struct pt_regs *);
extern void (*handle_arch_irq)(struct pt_regs *);
extern void (*handle_arch_fiq)(struct pt_regs *);

@@ -514,6 +516,16 @@ asmlinkage void noinstr el1h_64_sync_handler(struct pt_regs *regs)
}
}

+static __always_inline void __el1_nmi(struct pt_regs *regs,
+ void (*handler)(struct pt_regs *))
+{
+ irqentry_state_t state;
+
+ state = irqentry_nmi_enter(regs);
+ do_interrupt_handler(regs, handler);
+ irqentry_nmi_exit(regs, state);
+}
+
static __always_inline void __el1_pnmi(struct pt_regs *regs,
void (*handler)(struct pt_regs *))
{
@@ -537,11 +549,20 @@ static __always_inline void __el1_irq(struct pt_regs *regs,

arm64_exit_to_kernel_mode(regs, state);
}
-static void noinstr el1_interrupt(struct pt_regs *regs,
- void (*handler)(struct pt_regs *))
+static void noinstr el1_interrupt(struct pt_regs *regs, u64 nmi_flag,
+ void (*handler)(struct pt_regs *),
+ void (*nmi_handler)(struct pt_regs *))
{
struct exception_mask mask = irq_entry_unmask_debug_serror(regs);

+ if (system_uses_nmi()) {
+ /* Is there a NMI to handle? */
+ if (read_sysreg(isr_el1) & nmi_flag) {
+ __el1_nmi(regs, nmi_handler);
+ return;
+ }
+ }
+
if (IS_ENABLED(CONFIG_ARM64_PSEUDO_NMI) && regs_irqs_disabled(regs))
__el1_pnmi(regs, handler);
else
@@ -552,12 +573,12 @@ static void noinstr el1_interrupt(struct pt_regs *regs,

asmlinkage void noinstr el1h_64_irq_handler(struct pt_regs *regs)
{
- el1_interrupt(regs, handle_arch_irq);
+ el1_interrupt(regs, ISR_EL1_IS, handle_arch_irq, handle_arch_nmi_irq);
}

asmlinkage void noinstr el1h_64_fiq_handler(struct pt_regs *regs)
{
- el1_interrupt(regs, handle_arch_fiq);
+ el1_interrupt(regs, ISR_EL1_FS, handle_arch_fiq, handle_arch_nmi_fiq);
}

asmlinkage void noinstr el1h_64_error_handler(struct pt_regs *regs)
@@ -844,13 +865,35 @@ 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 *))
+static void noinstr el0_interrupt(struct pt_regs *regs, u64 nmi_flag,
+ void (*handler)(struct pt_regs *),
+ void (*nmi_handler)(struct pt_regs *))
{
arm64_enter_from_user_mode(regs);

irq_entry_unmask_debug_serror(regs);

+ if (system_uses_nmi()) {
+ irqentry_state_t state;
+
+ /* Is there a NMI to handle? */
+ if (read_sysreg(isr_el1) & nmi_flag) {
+ /*
+ * Any system with FEAT_NMI should not be
+ * affected by Spectre v2 so we don't mitigate
+ * here.
+ */
+ state = irqentry_nmi_enter(regs);
+ do_interrupt_handler(regs, nmi_handler);
+ irqentry_nmi_exit(regs, state);
+
+ _allint_clear();
+
+ arm64_exit_to_user_mode(regs);
+ return;
+ }
+ }
+
if (regs->pc & BIT(55))
arm64_apply_bp_hardening();

@@ -863,7 +906,7 @@ static void noinstr el0_interrupt(struct pt_regs *regs,

static void noinstr __el0_irq_handler_common(struct pt_regs *regs)
{
- el0_interrupt(regs, handle_arch_irq);
+ el0_interrupt(regs, ISR_EL1_IS, handle_arch_irq, handle_arch_nmi_irq);
}

asmlinkage void noinstr el0t_64_irq_handler(struct pt_regs *regs)
@@ -873,7 +916,7 @@ asmlinkage void noinstr el0t_64_irq_handler(struct pt_regs *regs)

static void noinstr __el0_fiq_handler_common(struct pt_regs *regs)
{
- el0_interrupt(regs, handle_arch_fiq);
+ el0_interrupt(regs, ISR_EL1_FS, handle_arch_fiq, handle_arch_nmi_fiq);
}

asmlinkage void noinstr el0t_64_fiq_handler(struct pt_regs *regs)
diff --git a/arch/arm64/kernel/irq.c b/arch/arm64/kernel/irq.c
index fff0efd5a195..54e25b4c0698 100644
--- a/arch/arm64/kernel/irq.c
+++ b/arch/arm64/kernel/irq.c
@@ -84,6 +84,16 @@ void do_softirq_own_stack(void)
}
#endif

+static void default_handle_nmi_irq(struct pt_regs *regs)
+{
+ panic("Superpriority IRQ taken without a root NMI IRQ handler\n");
+}
+
+static void default_handle_nmi_fiq(struct pt_regs *regs)
+{
+ panic("Superpriority FIQ taken without a root NMI FIQ handler\n");
+}
+
static void default_handle_irq(struct pt_regs *regs)
{
panic("IRQ taken without a root IRQ handler\n");
@@ -94,9 +104,31 @@ static void default_handle_fiq(struct pt_regs *regs)
panic("FIQ taken without a root FIQ handler\n");
}

+void (*handle_arch_nmi_irq)(struct pt_regs *) __ro_after_init = default_handle_nmi_irq;
+void (*handle_arch_nmi_fiq)(struct pt_regs *) __ro_after_init = default_handle_nmi_fiq;
void (*handle_arch_irq)(struct pt_regs *) __ro_after_init = default_handle_irq;
void (*handle_arch_fiq)(struct pt_regs *) __ro_after_init = default_handle_fiq;

+int __init set_handle_nmi_irq(void (*handle_nmi_irq)(struct pt_regs *))
+{
+ if (handle_arch_nmi_irq != default_handle_nmi_irq)
+ return -EBUSY;
+
+ handle_arch_nmi_irq = handle_nmi_irq;
+ pr_info("Root superpriority IRQ handler: %ps\n", handle_nmi_irq);
+ return 0;
+}
+
+int __init set_handle_nmi_fiq(void (*handle_nmi_fiq)(struct pt_regs *))
+{
+ if (handle_arch_nmi_fiq != default_handle_nmi_fiq)
+ return -EBUSY;
+
+ handle_arch_nmi_fiq = handle_nmi_fiq;
+ pr_info("Root superpriority FIQ handler: %ps\n", handle_nmi_fiq);
+ return 0;
+}
+
int __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
{
if (handle_arch_irq != default_handle_irq)
diff --git a/arch/arm64/kernel/process.c b/arch/arm64/kernel/process.c
index 1aa8f0f66484..b0244005e20f 100644
--- a/arch/arm64/kernel/process.c
+++ b/arch/arm64/kernel/process.c
@@ -757,6 +757,10 @@ static inline void debug_switch_state(void)
WARN_ONCE(daif_actual != daif_expected,
"Unexpected DAIF value: 0x%lx (expected 0x%lx)\n",
daif_actual, daif_expected);
+
+ if (system_uses_nmi())
+ WARN_ONCE(read_sysreg_s(SYS_ALLINT) & ALLINT_ALLINT,
+ "Unexpected ALLINT set\n");
}
}

--
2.34.1