[RFC PATCH 31/36] arm64: nmi: Add handling of superpriority interrupts as NMIs

Vladimir Murzin vladimir.murzin at arm.com
Thu Jul 23 03:29:28 PDT 2026


On 7/21/26 09:33, Jinjie Ruan wrote:
> 
> On 7/9/2026 8:13 PM, Vladimir Murzin wrote:
>> From: Mark Brown <broonie at kernel.org>
>>
>> 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 is configured with superpriority, we enter EL1 as we
>> do for any other interrupt. The presence of a superpriority interrupt
>> is indicated by a status bit in ISR_EL1. We check this bit before
>> unmasking interrupts in elX_interrupt(), and if a superpriority
>> interrupt is pending, we handle it as an NMI. Otherwise, normal
>> interrupts are handled as usual.
>>
>> Since superpriority interrupts are always handled as NMIs, the
>> interrupt controller can rely on in_nmi() to distinguish them from
>> ordinary interrupts.
>>
>> Enable IPIs to use superpriority interrupts as NMIs, matching the
>> existing pseudo-NMI behaviour.
>> Signed-off-by: Mark Brown <broonie at kernel.org>
>> Signed-off-by: Ada Couprie Diaz <ada.coupriediaz at arm.com>
>> Signed-off-by: Vladimir Murzin <vladimir.murzin at arm.com>
>> ---
>>  arch/arm64/include/asm/entry-common.h |  7 ++++
>>  arch/arm64/kernel/entry-common.c      | 59 ++++++++++++++++++---------
>>  arch/arm64/kernel/smp.c               |  2 +-
>>  3 files changed, 48 insertions(+), 20 deletions(-)
>>
>> diff --git a/arch/arm64/include/asm/entry-common.h b/arch/arm64/include/asm/entry-common.h
>> index 73d82a8d8e95..0681ba91ac3b 100644
>> --- a/arch/arm64/include/asm/entry-common.h
>> +++ b/arch/arm64/include/asm/entry-common.h
>> @@ -37,6 +37,13 @@ static inline bool arch_irqentry_exit_need_resched(void)
>>  	if (system_uses_irq_prio_masking() && read_sysreg(daif))
>>  		return false;
>>  
>> +	/*
>> +	 * If AllInt is set then we must have handled an NMI, so skip
>> +	 * preemption
>> +	 */
>> +	if (system_uses_nmi() && read_sysreg_s(SYS_ALLINT))
>> +		return false;
>> +
>>  	/*
>>  	 * Preempting a task from an IRQ means we leave copies of PSTATE
>>  	 * on the stack. cpufeature's enable calls may modify PSTATE, but
>> diff --git a/arch/arm64/kernel/entry-common.c b/arch/arm64/kernel/entry-common.c
>> index a13653b228b7..de71d5a3a6a1 100644
>> --- a/arch/arm64/kernel/entry-common.c
>> +++ b/arch/arm64/kernel/entry-common.c
>> @@ -525,8 +525,8 @@ asmlinkage void noinstr el1h_64_sync_handler(struct pt_regs *regs)
>>  	arm64_debug_exc_context(CRITICAL_CONTEXT);
>>  }
>>  
>> -static __always_inline void __el1_pnmi(struct pt_regs *regs,
>> -				       void (*handler)(struct pt_regs *))
>> +static __always_inline void __el1_nmi(struct pt_regs *regs,
>> +				      void (*handler)(struct pt_regs *))
>>  {
>>  	arm64_exc_hwstate_t hwstate;
>>  	irqentry_state_t state;
>> @@ -545,7 +545,10 @@ static __always_inline void __el1_irq(struct pt_regs *regs,
>>  
>>  	state = arm64_enter_from_kernel_mode(regs);
>>  
>> -	arm64_unmask_exc_context(NONMI_CONTEXT);
>> +	if (system_uses_nmi())
>> +		arm64_unmask_exc_context(NOIRQ_CONTEXT);
>> +	else
>> +		arm64_unmask_exc_context(NONMI_CONTEXT);
> if (gic_supports_pseudo_nmis())
> 	arm64_unmask_exc_context(NONMI_CONTEXT);
> else
> 	arm64_unmask_exc_context(NOIRQ_CONTEXT);
> 
>>  
>>  	irq_enter_rcu();
>>  	do_interrupt_handler(regs, handler);
>> @@ -565,8 +568,11 @@ static __always_inline void __el1_irq(struct pt_regs *regs,
>>  static void noinstr el1_interrupt(struct pt_regs *regs,
>>  				  void (*handler)(struct pt_regs *))
>>  {
>> -	if (IS_ENABLED(CONFIG_ARM64_PSEUDO_NMI) && regs_irqs_disabled(regs))
>> -		__el1_pnmi(regs, handler);
>> +	/* Is there a NMI to handle? */
>> +	if (regs_irqs_disabled(regs))
>> +		__el1_nmi(regs, handler);
>> +	else if (system_uses_nmi() && (read_sysreg(isr_el1) & (ISR_EL1_IS | ISR_EL1_FS)))
>> +		__el1_nmi(regs, handler);
> I think it would be more readable to add a helper below.
> 
> static inline bool is_nmi(regs)
> {
> 	if (regs_irqs_disabled(regs))
> 		return true;
> 
> 	if (system_uses_nmi())
> 		return read_sysreg(isr_el1) & (ISR_EL1_IS | ISR_EL1_FS);
> 
> 	return false;
> }
> 

Makes sense, yet I would keep regs_irqs_disabled() limited to EL1,
since it is very tempting to reuse the helper for EL0 interrupts,
where the regs_irqs_disabled() check is redundant.

>>  static void noinstr el1_interrupt(struct pt_regs *regs,
>>  				  void (*handler)(struct pt_regs *))
>>  {
>> -	if (IS_ENABLED(CONFIG_ARM64_PSEUDO_NMI) && regs_irqs_disabled(regs))
>> -		__el1_pnmi(regs, handler);
>> +	/* Is there a NMI to handle? */
>> +	if (is_nmi(regs))
>> +		__el1_nmi(regs, handler);
>>  	else
>>  		__el1_irq(regs, handler);
>>  
>> @@ -906,24 +912,39 @@ 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);
>> -
>> -	arm64_unmask_exc_context(NONMI_CONTEXT);
>> -
>>  	if (regs->pc & BIT(55))
>>  		arm64_apply_bp_hardening();
>>  
>> -	irq_enter_rcu();
>> -	do_interrupt_handler(regs, handler);
>> -	irq_exit_rcu();
>> +	/* Is there a NMI to handle? */
>> +	if (system_uses_nmi() && (read_sysreg(isr_el1) & (ISR_EL1_IS | ISR_EL1_FS))) {
>> +		irqentry_state_t state;
>> +		arm64_exc_hwstate_t hwstate;
>> +
>> +		state = irqentry_nmi_enter(regs);
>> +		hwstate = arm64_unmask_exc_context(NONMI_CONTEXT);
>> +		do_interrupt_handler(regs, handler);
>> +		arm64_mask_exc_context(hwstate);
>> +		irqentry_nmi_exit(regs, state);
>> +	} else {
>> +		arm64_enter_from_user_mode(regs);
>> +
>> +		if (system_uses_nmi())
>> +			arm64_unmask_exc_context(NOIRQ_CONTEXT);
>> +		else
>> +			arm64_unmask_exc_context(NONMI_CONTEXT);
> if (gic_supports_pseudo_nmis())
> 	arm64_unmask_exc_context(NONMI_CONTEXT);
> else
> 	arm64_unmask_exc_context(NOIRQ_CONTEXT);
> 
>> +
>> +		irq_enter_rcu();
>> +		do_interrupt_handler(regs, handler);
>> +		irq_exit_rcu();
>> +		/*
>> +		 * For the same reason as in el1_irq() we effectivly
>> +		 * have NOIRQ_CONTEXT on return from handler - keep
>> +		 * track of it
>> +		 */
>> +		arm64_debug_exc_context(NOIRQ_CONTEXT);
>> +		arm64_exit_to_user_mode(regs, arm64_exc_hwstate_of_context(NOIRQ_CONTEXT));
> Here we assert that we are in NOIRQ_CONTEXT, and then we switch to
> NOIRQ_CONTEXT again. Is this redundant?
> 

Well, it is redundant prior to the whole rework, but after the rework
it becomes a no-op because both the previous and next exception states
are the same, so no real work is done. :)

>> +	}
> Can you split them into two sub-functions, such as el0_nmi and el0_irq
> as shown below, so that it looks clearer?
> 

Works for me, I'll update in the next iteration.

> static inline void el0_nmi(struct pt_regs *regs, void (*handler)(struct
> pt_regs *))
> {
> 	arm64_exc_hwstate_t hwstate;
> 	irqentry_state_t state;
> 	
> 	state = irqentry_nmi_enter(regs);
> 	hwstate = arm64_unmask_exc_context(NONMI_CONTEXT);
> 	do_interrupt_handler(regs, handler);
> 	arm64_mask_exc_context(hwstate);
> 	irqentry_nmi_exit(regs, state);
> }
> 
> static inline void el0_irq(struct pt_regs *regs, void (*handler)(struct
> pt_regs *))
> {
> 	arm64_enter_from_user_mode(regs);
> 
> 	if (gic_supports_pseudo_nmis())
> 		arm64_unmask_exc_context(NONMI_CONTEXT);
> 	else
> 		arm64_unmask_exc_context(NOIRQ_CONTEXT);
> 
> 	irq_enter_rcu();
> 	do_interrupt_handler(regs, handler);
> 	irq_exit_rcu();
> 	/*
> 	 * For the same reason as in el1_irq() we effectivly
>     	 * have NOIRQ_CONTEXT on return from handler - keep
>          * track of it
>       	 */
> 	arm64_debug_exc_context(NOIRQ_CONTEXT);			
> 	arm64_exit_to_user_mode(regs, 	
> arm64_exc_hwstate_of_context(NOIRQ_CONTEXT));
> }
> 
> static void noinstr el0_interrupt(struct pt_regs *regs,
>  				  void (*handler)(struct pt_regs *))
> {
> 	if (is_nmi(regs))
> 		el0_nmi(regs);
> 	else
> 		el0_irq(regs);
> }
> 
> otherwise, LGTM
> Reviewed-by: Jinjie Ruan <ruanjinjie at huawei.com>
> 
> 

Cheers
Vladimir

>>  
>> -	/*
>> -	 * For the same reason as in el1_irq() we effectivly
>> -	 * have NOIRQ_CONTEXT on return from handler - keep
>> -	 * track of it
>> -	 */
>> -	arm64_debug_exc_context(NOIRQ_CONTEXT);
>> -	arm64_exit_to_user_mode(regs, arm64_exc_hwstate_of_context(NOIRQ_CONTEXT));
>>  	arm64_debug_exc_context(CRITICAL_CONTEXT);
>>  }
>>  
>> diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c
>> index 257d50529d14..80e35a8e5c8f 100644
>> --- a/arch/arm64/kernel/smp.c
>> +++ b/arch/arm64/kernel/smp.c
>> @@ -1035,7 +1035,7 @@ static void smp_cross_call(const struct cpumask *target, unsigned int ipinr)
>>  
>>  static bool ipi_should_be_nmi(enum ipi_msg_type ipi)
>>  {
>> -	if (!system_uses_irq_prio_masking())
>> +	if (!system_uses_nmi() && !system_uses_irq_prio_masking())
>>  		return false;
>>  
>>  	switch (ipi) {
> 




More information about the linux-arm-kernel mailing list