[RFC PATCH 36/36] irqchip/gic-v3: Implement FEAT_GICv3_NMI support

Vladimir Murzin vladimir.murzin at arm.com
Wed Jul 22 06:39:59 PDT 2026


On 7/21/26 03:25, Jinjie Ruan wrote:
> 
> On 7/9/2026 8:13 PM, Vladimir Murzin wrote:
>> From: Lorenzo Pieralisi <lpieralisi at kernel.org>
>>
>> The FEAT_GICv3_NMI GIC feature coupled with the CPU FEAT_NMI enables
>> handling NMI interrupts in HW on aarch64, by adding a superpriority
>> interrupt to the existing GIC priority scheme.
>>
>> Implement GIC driver support for the FEAT_GICv3_NMI feature.
>>
>> Rename gic_supports_nmi() helper function to gic_supports_pseudo_nmis()
>> to make the pseudo NMIs code path clearer and more explicit.
>>
>> Check, through the ARM64 capabilitity infrastructure, if support
>> for FEAT_NMI was detected on the core and the system has not overridden
>> the detection and forced pseudo-NMIs enablement.
>>
>> If FEAT_NMI is detected, it was not overridden (check embedded in the
>> system_uses_nmi() call) and the GIC supports the FEAT_GICv3_NMI
>> feature, initialize NMIs related HW GIC registers and route irq to NMI
>> handling logic.
>>
>> Signed-off-by: Lorenzo Pieralisi <lpieralisi at kernel.org>
>> 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>
>> ---
>>  drivers/irqchip/irq-gic-v3.c       | 122 +++++++++++++++++++++++++----
>>  include/linux/irqchip/arm-gic-v3.h |   4 +
>>  2 files changed, 111 insertions(+), 15 deletions(-)
>>
>> diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c
>> index 94c6a3f2b009..625be4fc06b4 100644
>> --- a/drivers/irqchip/irq-gic-v3.c
>> +++ b/drivers/irqchip/irq-gic-v3.c
>> @@ -64,6 +64,7 @@ struct gic_chip_data {
>>  	u32			nr_redist_regions;
>>  	u64			flags;
>>  	bool			has_rss;
>> +	bool			has_nmi;
>>  	unsigned int		ppi_nr;
>>  	struct partition_affinity *parts;
>>  	unsigned int		nr_parts;
>> @@ -87,7 +88,7 @@ static DEFINE_STATIC_KEY_TRUE(supports_deactivate_key);
>>  #define GIC_LINE_NR	min(GICD_TYPER_SPIS(gic_data.rdists.gicd_typer), 1020U)
>>  #define GIC_ESPI_NR	GICD_TYPER_ESPIS(gic_data.rdists.gicd_typer)
>>  
>> -static bool nmi_support_forbidden;
>> +static bool pnmi_support_forbidden;
>>  
>>  /*
>>   * There are 16 SGIs, though we only actually use 8 in Linux. The other 8 SGIs
>> @@ -185,7 +186,7 @@ static void __init gic_prio_init(void)
>>  				pr_warn("Broken GIC integration, security disabled\n");
>>  		} else {
>>  			pr_warn("Broken GIC integration, pNMI forbidden\n");
>> -			nmi_support_forbidden = true;
>> +			pnmi_support_forbidden = true;
>>  		}
>>  	}
>>  
>> @@ -252,6 +253,37 @@ enum gic_intid_range {
>>  	__INVALID_RANGE__
>>  };
>>  
>> +#ifdef CONFIG_ARM64
>> +#include <asm/cpufeature.h>
>> +
>> +static inline bool gic_supports_v3_3_nmi(void)
>> +{
>> +	return gic_data.has_nmi && system_uses_nmi();
>> +}
>> +
>> +static inline u64 gic_read_nmiar(void)
>> +{
>> +	u64 irqstat;
>> +
>> +	irqstat = read_sysreg_s(SYS_ICC_NMIAR1_EL1);
>> +
>> +	dsb(sy);
>> +
>> +	return irqstat;
>> +}
>> +
>> +#else
>> +static inline bool gic_supports_v3_3_nmi(void)
>> +{
>> +	return false;
>> +}
>> +
>> +static inline u64 gic_read_nmiar(void)
>> +{
>> +	BUG();
>> +}
> I think the BUG() here is unnecessary because arm32 will not detect the
> feat_nmi feature; a warning or an empty statement would be sufficient.
> 

It is correct that arm32 should never reach this code. If it
does, that indicates a bug.

A warning or an empty stub would not help:
- execution cannot safely continue from this point
- the compiler requires a return statement for a non-void
  function.

Therefore, BUG() is appropriate here.

>> +#endif
>> +
>>  static enum gic_intid_range __get_intid_range(irq_hw_number_t hwirq)
>>  {
>>  	switch (hwirq) {
>> @@ -438,6 +470,8 @@ static u32 convert_offset_index(struct irq_data *d, u32 offset, u32 *index)
>>  			return GICD_ICFGRnE;
>>  		case GICD_IROUTER:
>>  			return GICD_IROUTERnE;
>> +		case GICD_INMIR:
>> +			return GICD_INMIRnE;
>>  		default:
>>  			break;
>>  		}
>> @@ -467,6 +501,42 @@ static int gic_peek_irq(struct irq_data *d, u32 offset)
>>  	return !!(readl_relaxed(base + offset + (index / 32) * 4) & mask);
>>  }
>>  
>> +static DEFINE_RAW_SPINLOCK(irq_controller_lock);
>> +
>> +static void gic_irq_configure_nmi(struct irq_data *d, bool enable)
>> +{
>> +	void __iomem *base, *addr;
>> +	u32 offset, index, mask, val;
>> +
>> +	offset = convert_offset_index(d, GICD_INMIR, &index);
>> +	mask = 1 << (index % 32);
>> +
>> +	if (gic_irq_in_rdist(d))
>> +		base = gic_data_rdist_sgi_base();
>> +	else
>> +		base = gic_dist_base_alias(d);
>> +
>> +	addr = base + offset + (index / 32) * 4;
>> +
>> +	raw_spin_lock(&irq_controller_lock);
>> +
>> +	val = readl_relaxed(addr);
>> +	val = enable ? (val | mask) : (val & ~mask);
>> +	writel_relaxed(val, addr);
>> +
>> +	raw_spin_unlock(&irq_controller_lock);
>> +}
>> +
>> +static void gic_irq_enable_nmi(struct irq_data *d)
>> +{
>> +	gic_irq_configure_nmi(d, true);
>> +}
>> +
>> +static void gic_irq_disable_nmi(struct irq_data *d)
>> +{
>> +	gic_irq_configure_nmi(d, false);
>> +}
>> +
>>  static void gic_poke_irq(struct irq_data *d, u32 offset)
>>  {
>>  	void __iomem *base;
>> @@ -512,7 +582,7 @@ static void gic_unmask_irq(struct irq_data *d)
>>  	gic_poke_irq(d, GICD_ISENABLER);
>>  }
>>  
>> -static inline bool gic_supports_nmi(void)
>> +static inline bool gic_supports_pseudo_nmis(void)
>>  {
>>  	return IS_ENABLED(CONFIG_ARM64_PSEUDO_NMI) &&
>>  	       static_branch_likely(&supports_pseudo_nmis);
>> @@ -598,7 +668,7 @@ static int gic_irq_nmi_setup(struct irq_data *d)
>>  {
>>  	struct irq_desc *desc = irq_to_desc(d->irq);
>>  
>> -	if (!gic_supports_nmi())
>> +	if (!gic_supports_pseudo_nmis() && !gic_supports_v3_3_nmi())
>>  		return -EINVAL;
>>  
>>  	if (gic_peek_irq(d, GICD_ISENABLER)) {
>> @@ -617,7 +687,10 @@ static int gic_irq_nmi_setup(struct irq_data *d)
>>  	if (!gic_irq_in_rdist(d))
>>  		desc->handle_irq = handle_fasteoi_nmi;
>>  
>> -	gic_irq_set_prio(d, dist_prio_nmi);
>> +	if (gic_supports_v3_3_nmi())
>> +		gic_irq_enable_nmi(d);
>> +	else
>> +		gic_irq_set_prio(d, dist_prio_nmi);
>>  
>>  	return 0;
>>  }
>> @@ -626,7 +699,7 @@ static void gic_irq_nmi_teardown(struct irq_data *d)
>>  {
>>  	struct irq_desc *desc = irq_to_desc(d->irq);
>>  
>> -	if (WARN_ON(!gic_supports_nmi()))
>> +	if (WARN_ON(!gic_supports_pseudo_nmis() && !gic_supports_v3_3_nmi()))
>>  		return;
>>  
>>  	if (gic_peek_irq(d, GICD_ISENABLER)) {
>> @@ -645,7 +718,10 @@ static void gic_irq_nmi_teardown(struct irq_data *d)
>>  	if (!gic_irq_in_rdist(d))
>>  		desc->handle_irq = handle_fasteoi_irq;
>>  
>> -	gic_irq_set_prio(d, dist_prio_irq);
>> +	if (gic_supports_v3_3_nmi())
>> +		gic_irq_disable_nmi(d);
>> +	else
>> +		gic_irq_set_prio(d, dist_prio_irq);
>>  }
>>  
>>  static bool gic_arm64_erratum_2941627_needed(struct irq_data *d)
>> @@ -804,7 +880,7 @@ static inline void gic_complete_ack(u32 irqnr)
>>  
>>  static bool gic_rpr_is_nmi_prio(void)
>>  {
>> -	if (!gic_supports_nmi())
>> +	if (!gic_supports_pseudo_nmis())
>>  		return false;
>>  
>>  	return unlikely(gic_read_rpr() == GICV3_PRIO_NMI);
>> @@ -836,7 +912,8 @@ static void __gic_handle_nmi(u32 irqnr, struct pt_regs *regs)
>>  	gic_complete_ack(irqnr);
>>  
>>  	if (generic_handle_domain_nmi(gic_data.domain, irqnr)) {
>> -		WARN_ONCE(true, "Unexpected pseudo-NMI (irqnr %u)\n", irqnr);
>> +		WARN_ONCE(true, "Unexpected %sNMI (irqnr %u)\n",
>> +			  gic_supports_pseudo_nmis() ? "pseudo-" : "", irqnr);
>>  		gic_deactivate_unhandled(irqnr);
>>  	}
>>  }
>> @@ -911,7 +988,11 @@ static void __gic_handle_irq_from_irqsoff(struct pt_regs *regs)
>>  
>>  static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
>>  {
>> -	if (unlikely(gic_supports_nmi() && !interrupts_enabled(regs)))
>> +	if (gic_supports_v3_3_nmi() && in_nmi()) {
>> +		u32 irqnr = gic_read_nmiar();
>> +
>> +		__gic_handle_nmi(irqnr, regs);
>> +	} else if (unlikely(gic_supports_pseudo_nmis() && !interrupts_enabled(regs)))
>>  		__gic_handle_irq_from_irqsoff(regs);
> Can we rename __gic_handle_irq_from_irqsoff to gic_handle_pseudo_nmi,
> and then encapsulate the logic for obtaining the pseudo-NMI interrupt
> number into a function similar to gic_read_nmiar(), such as
> gic_read_pseudo_nmi_irqnr()? This would make the code look cleaner.
> 
> 
> For example, similar to the following code:
> 
> diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c
> index 2835e8f342be..1184f9446f8c 100644
> --- a/drivers/irqchip/irq-gic-v3.c
> +++ b/drivers/irqchip/irq-gic-v3.c
> @@ -260,28 +260,11 @@ bool gic_supports_v3_3_nmi(void)
>  {
>         return gic_data.has_nmi && system_uses_nmi();
>  }
> -
> -static inline u64 gic_read_nmiar(void)
> -{
> -       u64 irqstat;
> -
> -       irqstat = read_sysreg_s(SYS_ICC_NMIAR1_EL1);
> -
> -       dsb(sy);
> -
> -       return irqstat;
> -}
> -
>  #else
>  static inline bool gic_supports_v3_3_nmi(void)
>  {
>         return false;
>  }
> -
> -static inline u64 gic_read_nmiar(void)
> -{
> -       BUG();
> -}
>  #endif
> 
>  static enum gic_intid_range __get_intid_range(irq_hw_number_t hwirq)
> @@ -961,7 +944,7 @@ static void __gic_handle_irq_from_irqson(struct
> pt_regs *regs)
>   *
>   * The entry code has performed NMI entry.
>   */
> -static void __gic_handle_irq_from_irqsoff(struct pt_regs *regs)
> +static u32 gic_read_pseudo_nmi_irqnr(struct pt_regs *regs)
>  {
>         u64 pmr;
>         u32 irqnr;
> @@ -985,19 +968,42 @@ static void __gic_handle_irq_from_irqsoff(struct
> pt_regs *regs)
>         irqnr = gic_read_iar();
>         gic_write_pmr(pmr);
> 
> +       return irqnr;
> +}
> +
> +#ifdef CONFIG_ARM64
> +static inline u64 gic_read_nmiar(void)
> +{
> +       u64 irqstat;
> +
> +       irqstat = read_sysreg_s(SYS_ICC_NMIAR1_EL1);
> +
> +       dsb(sy);
> +
> +       return irqstat;
> +}
> +#else
> +static inline u64 gic_read_nmiar(void) { }
> +#endif
> +
> +static void gic_handle_nmi(struct pt_regs *regs)
> +{
> +       u32 irqnr;
> +
> +       if (gic_supports_v3_3_nmi())
> +               irqnr = gic_read_nmiar();
> +       else if (unlikely(gic_supports_pseudo_nmis()))
> +               irqnr = gic_read_pseudo_nmi_irqnr(regs);
> +

There is now a path where irqnr is left uninitialized, which
would upset any tool capable of static analysis :(

>         __gic_handle_nmi(irqnr, regs);
>  }
> 
>  static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
>  {
> -       if (gic_supports_v3_3_nmi() && in_nmi()) {
> -               u32 irqnr = gic_read_nmiar();
> +       if (in_nmi() || !interrupts_enabled(regs))
> +               return gic_handle_nmi(regs);
> 
> -               __gic_handle_nmi(irqnr, regs);
> -       } else if (unlikely(gic_supports_pseudo_nmis() &&
> !interrupts_enabled(regs)))
> -               __gic_handle_irq_from_irqsoff(regs);
> -       else
> -               __gic_handle_irq_from_irqson(regs);
> +       __gic_handle_irq_from_irqson(regs);
>  }
> 
> otherwise, LGTM
> Reviewed-by: Jinjie Ruan <ruanjinjie at huawei.com>
> 
> 

Thanks, but I'm inclined to keep the code as is for now.

Vladimir

>>  	else
>>  		__gic_handle_irq_from_irqson(regs);
>> @@ -1162,7 +1243,7 @@ static void gic_cpu_sys_reg_init(void)
>>  	/* Set priority mask register */
>>  	if (!gic_prio_masking_enabled()) {
>>  		write_gicreg(DEFAULT_PMR_VALUE, ICC_PMR_EL1);
>> -	} else if (gic_supports_nmi()) {
>> +	} else if (gic_supports_pseudo_nmis()) {
>>  		/*
>>  		 * Check that all CPUs use the same priority space.
>>  		 *
>> @@ -1955,15 +2036,25 @@ static const struct gic_quirk gic_quirks[] = {
>>  	}
>>  };
>>  
>> -static void gic_enable_nmi_support(void)
>> +static void gic_enable_pseudo_nmis(void)
>>  {
>> -	if (!gic_prio_masking_enabled() || nmi_support_forbidden)
>> -		return;
>> -
>>  	pr_info("Pseudo-NMIs enabled using %s ICC_PMR_EL1 synchronisation\n",
>>  		gic_has_relaxed_pmr_sync() ? "relaxed" : "forced");
>>  
>>  	static_branch_enable(&supports_pseudo_nmis);
>> +}
>> +
>> +static void gic_enable_nmi_support(void)
>> +{
>> +	if ((!gic_prio_masking_enabled() || pnmi_support_forbidden) && !gic_supports_v3_3_nmi())
>> +		return;
>> +
>> +	/*
>> +	 * Initialize pseudo-NMIs only if GIC driver cannot take advantage
>> +	 * of core (FEAT_NMI) and GIC (FEAT_GICv3_NMI) in HW
>> +	 */
>> +	if (!gic_supports_v3_3_nmi())
>> +		gic_enable_pseudo_nmis();
>>  
>>  	if (static_branch_likely(&supports_deactivate_key))
>>  		gic_eoimode1_chip.flags |= IRQCHIP_SUPPORTS_NMI;
>> @@ -2032,6 +2123,7 @@ static int __init gic_init_bases(phys_addr_t dist_phys_base,
>>  	irq_domain_update_bus_token(gic_data.domain, DOMAIN_BUS_WIRED);
>>  
>>  	gic_data.has_rss = !!(typer & GICD_TYPER_RSS);
>> +	gic_data.has_nmi = !!(typer & GICD_TYPER_NMI);
>>  
>>  	if (typer & GICD_TYPER_MBIS) {
>>  		err = mbi_init(handle, gic_data.domain);
>> diff --git a/include/linux/irqchip/arm-gic-v3.h b/include/linux/irqchip/arm-gic-v3.h
>> index ea5fd2374ebe..ebccd76cadda 100644
>> --- a/include/linux/irqchip/arm-gic-v3.h
>> +++ b/include/linux/irqchip/arm-gic-v3.h
>> @@ -30,6 +30,7 @@
>>  #define GICD_ICFGR			0x0C00
>>  #define GICD_IGRPMODR			0x0D00
>>  #define GICD_NSACR			0x0E00
>> +#define GICD_INMIR			0x0F80
>>  #define GICD_IGROUPRnE			0x1000
>>  #define GICD_ISENABLERnE		0x1200
>>  #define GICD_ICENABLERnE		0x1400
>> @@ -39,6 +40,7 @@
>>  #define GICD_ICACTIVERnE		0x1C00
>>  #define GICD_IPRIORITYRnE		0x2000
>>  #define GICD_ICFGRnE			0x3000
>> +#define GICD_INMIRnE			0x3B00
>>  #define GICD_IROUTER			0x6000
>>  #define GICD_IROUTERnE			0x8000
>>  #define GICD_IDREGS			0xFFD0
>> @@ -83,6 +85,7 @@
>>  #define GICD_TYPER_LPIS			(1U << 17)
>>  #define GICD_TYPER_MBIS			(1U << 16)
>>  #define GICD_TYPER_ESPI			(1U << 8)
>> +#define GICD_TYPER_NMI			(1U << 9)
>>  
>>  #define GICD_TYPER_ID_BITS(typer)	((((typer) >> 19) & 0x1f) + 1)
>>  #define GICD_TYPER_NUM_LPIS(typer)	((((typer) >> 11) & 0x1f) + 1)
>> @@ -238,6 +241,7 @@
>>  #define GICR_ICFGR0			GICD_ICFGR
>>  #define GICR_IGRPMODR0			GICD_IGRPMODR
>>  #define GICR_NSACR			GICD_NSACR
>> +#define GICR_INMIR0			GICD_INMIR
>>  
>>  #define GICR_TYPER_PLPIS		(1U << 0)
>>  #define GICR_TYPER_VLPIS		(1U << 1)
> 




More information about the linux-arm-kernel mailing list