[RFC PATCH v2 12/45] arm64: interrupts: Add common exception state helpers

Jinjie Ruan ruanjinjie at huawei.com
Tue Jul 28 01:20:56 PDT 2026



在 2026/7/28 0:34, Vladimir Murzin 写道:
> From: Ada Couprie Diaz <ada.coupriediaz at arm.com>
> 
> The entry code and the rest of the kernel require different interrupt
> masking APIs, but all need a common representation of the hardware
> exception state.
> 
> Introduce exception contexts that map DAIF and PMR state to the
> corresponding masking context. Provide helpers to translate between
> exception contexts and hardware state, verify the current state, and
> update the hardware state.
> 
> Updating PMR is unnecessary when a caller knows that its value has not
> changed. Allow such callers to avoid the update, while providing a
> force option for callers that cannot rely on the previous PMR state.
> 
> Entry specific and general purpose masking APIs will be built on these
> helpers in subsequent patches.
> 
> Signed-off-by: Ada Couprie Diaz <ada.coupriediaz at arm.com>
> Signed-off-by: Vladimir Murzin <vladimir.murzin at arm.com>
> ---
>  .../include/asm/interrupts/common_flags.h     | 208 ++++++++++++++++++
>  1 file changed, 208 insertions(+)
>  create mode 100644 arch/arm64/include/asm/interrupts/common_flags.h
> 
> diff --git a/arch/arm64/include/asm/interrupts/common_flags.h b/arch/arm64/include/asm/interrupts/common_flags.h
> new file mode 100644
> index 000000000000..f034dcafccbc
> --- /dev/null
> +++ b/arch/arm64/include/asm/interrupts/common_flags.h
> @@ -0,0 +1,208 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2025 Arm Ltd.
> + */
> +#ifndef __ASM_INTERRUPTS_COMMON_FLAGS_H
> +#define __ASM_INTERRUPTS_COMMON_FLAGS_H
> +
> +#include <asm/arch_gicv3.h>
> +#include <asm/bug.h>
> +#include <asm/cpufeature.h>
> +#include <asm/ptrace.h>
> +#include <asm/sysreg.h>
> +#include <asm/irqflags.h>
> +
> +#define DAIF_PROCCTX		0
> +#define DAIF_PROCCTX_NOIRQ	(PSR_I_BIT | PSR_F_BIT)
> +#define DAIF_ERRCTX		(PSR_A_BIT | PSR_I_BIT | PSR_F_BIT)
> +#define DAIF_MASK		(PSR_D_BIT | PSR_A_BIT | PSR_I_BIT | PSR_F_BIT)
> +
> +/*
> + * Exception context mapping
> + *
> + * pseudo-NMI
> + *
> + * CRITICAL -> DAIF + IRQON  (corresponds to the state on exception entry)
> + * ERROR    ->  AIF + IRQON
> + * NONMI    ->   IF + IRQON
> + * NOIRQ    ->    0 + IRQOFF
> + * PROCESS  ->    0 + IRQON
> + *
> + * Otherwise
> + *
> + * CRITICAL -> DAIF           (corresponds to the state on exception entry)
> + * ERROR    ->  AIF
> + * NONMI    ->   IF
> + * NOIRQ    ->   IF
> + * PROCESS  ->    0
> + */
> +typedef enum arm64_exc_context {
> +	PROCESS_CONTEXT,
> +	NOIRQ_CONTEXT,
> +	NONMI_CONTEXT,
> +	ERROR_CONTEXT,
> +	CRITICAL_CONTEXT,
> +} arm64_exc_context_t;
> +
> +static __always_inline
> +arm64_exc_hwstate_t __arm64_exc_hwstate_of_process_context(void)
> +{
> +	if (system_uses_irq_prio_masking())
> +		return (arm64_exc_hwstate_t){.daif=DAIF_PROCCTX, .pmr=GIC_PRIO_IRQON};
> +
> +	return (arm64_exc_hwstate_t){.daif=DAIF_PROCCTX};
> +}
> +
> +static __always_inline
> +arm64_exc_hwstate_t __arm64_exc_hwstate_of_noirq_context(void)
> +{
> +	if (system_uses_irq_prio_masking())
> +		return (arm64_exc_hwstate_t){.daif=DAIF_PROCCTX, .pmr=GIC_PRIO_IRQOFF};
> +
> +	return (arm64_exc_hwstate_t){.daif=DAIF_PROCCTX_NOIRQ};
> +}
> +
> +static __always_inline
> +arm64_exc_hwstate_t __arm64_exc_hwstate_of_nonmi_context(void)
> +{
> +	if (system_uses_irq_prio_masking())
> +		return (arm64_exc_hwstate_t){.daif=DAIF_PROCCTX_NOIRQ, .pmr=GIC_PRIO_IRQON};
> +
> +	return (arm64_exc_hwstate_t){.daif=DAIF_PROCCTX_NOIRQ};
> +}
> +
> +static __always_inline
> +arm64_exc_hwstate_t __arm64_exc_hwstate_of_error_context(void)
> +{
> +	if (system_uses_irq_prio_masking())
> +		return (arm64_exc_hwstate_t){.daif=DAIF_ERRCTX, .pmr=GIC_PRIO_IRQON};
> +
> +	return (arm64_exc_hwstate_t){.daif=DAIF_ERRCTX};
> +}
> +
> +static __always_inline
> +arm64_exc_hwstate_t __arm64_exc_hwstate_of_critical_context(void)
> +{
> +	if (system_uses_irq_prio_masking())
> +		return (arm64_exc_hwstate_t){.daif=DAIF_MASK, .pmr=GIC_PRIO_IRQON};
> +
> +	return (arm64_exc_hwstate_t){.daif=DAIF_MASK};
> +}
> +
> +static __always_inline
> +arm64_exc_hwstate_t arm64_exc_hwstate_of_context(arm64_exc_context_t context) {
> +	switch (context) {
> +	case PROCESS_CONTEXT:
> +		return __arm64_exc_hwstate_of_process_context();
> +	case NOIRQ_CONTEXT:
> +		return __arm64_exc_hwstate_of_noirq_context();
> +	case NONMI_CONTEXT:
> +		return __arm64_exc_hwstate_of_nonmi_context();
> +	case ERROR_CONTEXT:
> +		return __arm64_exc_hwstate_of_error_context();
> +	case CRITICAL_CONTEXT:
> +		return __arm64_exc_hwstate_of_critical_context();
> +	default:
> +		BUG();
> +	}
> +}

Hi Vladimir,

Using two tables as below to map different contexts to the hwstate
status makes the code appear more concise and clear. What do you think?

--- /dev/null
+++ b/arch/arm64/include/asm/interrupts/common_flags.h
+typedef enum arm64_exc_context {
+       PROCESS_CONTEXT,
+       NOIRQ_CONTEXT,
+       NONMI_CONTEXT,
+       ERROR_CONTEXT,
+       CRITICAL_CONTEXT,
+       NR_CONTEXTS,
+} arm64_exc_context_t;

+static const arm64_exc_hwstate_t daif_context_hwstate[] = {
+       [PROCESS_CONTEXT]  = { .daif = DAIF_PROCCTX },
+       [NOIRQ_CONTEXT]    = { .daif = DAIF_PROCCTX_NOIRQ },
+       [NONMI_CONTEXT]    = { .daif = DAIF_PROCCTX_NOIRQ },
+       [ERROR_CONTEXT]    = { .daif = DAIF_ERRCTX },
+       [CRITICAL_CONTEXT] = { .daif = DAIF_MASK },
+};
+
+static const arm64_exc_hwstate_t pseudo_nmi_context_hwstate[] = {
+       [PROCESS_CONTEXT]  = { .daif = DAIF_PROCCTX,       .pmr =
GIC_PRIO_IRQON },
+       [NOIRQ_CONTEXT]    = { .daif = DAIF_PROCCTX,       .pmr =
GIC_PRIO_IRQOFF},
+       [NONMI_CONTEXT]    = { .daif = DAIF_PROCCTX_NOIRQ, .pmr =
GIC_PRIO_IRQON },
+       [ERROR_CONTEXT]    = { .daif = DAIF_ERRCTX,        .pmr =
GIC_PRIO_IRQON },
+       [CRITICAL_CONTEXT] = { .daif = DAIF_MASK,          .pmr =
GIC_PRIO_IRQON },
+};
+
+static __always_inline
+arm64_exc_hwstate_t arm64_exc_hwstate_of_context(arm64_exc_context_t
context)
+{
+       if (unlikely((unsigned int)context >= NR_CONTEXTS))
+               BUG();
+
+       if (system_uses_irq_prio_masking())
+               return pnmi_table[context];
+
+       return legacy_table[context];
+}
+

> +
> +static __always_inline
> +arm64_exc_hwstate_t arm64_inherit_exc_hwstate(struct pt_regs *regs)
> +{
> +	arm64_exc_hwstate_t state = {.daif=regs->pstate & DAIF_MASK};
> +
> +	if (system_uses_irq_prio_masking())
> +		state.pmr = regs->pmr;
> +
> +	return state;
> +}
> +
> +static __always_inline
> +void arm64_debug_exc_hwstate(arm64_exc_hwstate_t expected)
> +{
> +	arm64_exc_hwstate_t actual;
> +
> +	if (!IS_ENABLED(CONFIG_DEBUG_IRQFLAGS))
> +		return;
> +
> +	actual.flags = arch_local_save_flags();
> +
> +	if (expected.flags == actual.flags)
> +		return;
> +
> +	if (system_uses_irq_prio_masking()) {
> +		WARN_ONCE(1, "Unexpected DAIF+PMR: 0x%x + 0x%x (expected 0x%x + 0x%x)\n",
> +			  actual.daif, actual.pmr, expected.daif, expected.pmr);
> +	} else {
> +		WARN_ONCE(1, "Unexpected DAIF: 0x%x (expected 0x%x)\n",
> +			  actual.daif, expected.daif);
> +	}
> +}
> +
> +static __always_inline
> +void arm64_debug_exc_context(arm64_exc_context_t context)
> +{
> +	arm64_exc_hwstate_t expected = arm64_exc_hwstate_of_context(context);
> +
> +	arm64_debug_exc_hwstate(expected);
> +}
> +
> +static __always_inline
> +void __arm64_update_exc_hwstate(arm64_exc_hwstate_t hwstate, bool force)
> +{
> +	barrier();

I don't understand the purpose of the barrier at the beginning and end
of the function. Is this fix also required for the original
implementation of local_daif_restore()?

> +
> +	if (system_uses_irq_prio_masking() &&
> +	    hwstate.pmr == GIC_PRIO_IRQOFF &&
> +	    force) {
> +		/*
> +		 * There has been concern that the write to daif
> +		 * might be reordered before this write to PMR.
> +		 * From the ARM ARM DDI 0487D.a, section D1.7.1
> +		 * "Accessing PSTATE fields":
> +		 *   Writes to the PSTATE fields have side-effects on
> +		 *   various aspects of the PE operation. All of these
> +		 *   side-effects are guaranteed:
> +		 *     - Not to be visible to earlier instructions in
> +		 *       the execution stream.
> +		 *     - To be visible to later instructions in the
> +		 *       execution stream
> +		 *
> +		 * Also, writes to PMR are self-synchronizing, so no
> +		 * interrupts with a lower priority than PMR is signaled
> +		 * to the PE after the write.
> +		 *
> +		 * So we don't need additional synchronization here.
> +		 */
> +		write_sysreg_s(hwstate.pmr, SYS_ICC_PMR_EL1);
> +	}
> +
> +	barrier();

This barrier seems redundant and inconsistent with the comment above.

> +	write_sysreg(hwstate.daif, daif);
> +	barrier();
> +
> +	if (system_uses_irq_prio_masking() &&
> +	    hwstate.pmr == GIC_PRIO_IRQON &&
> +	    force) {
> +		write_sysreg_s(hwstate.pmr, SYS_ICC_PMR_EL1);
> +		pmr_sync();
> +	}
> +
> +	barrier();
> +}

If we no use pesudo NMI, the barrier is not nessary,

static __always_inline
void __arm64_update_exc_hwstate(arm64_exc_hwstate_t hwstate, bool force)
{
    if (system_uses_irq_prio_masking() && force) {
        if (hwstate.pmr == GIC_PRIO_IRQOFF)
            write_sysreg_s(hwstate.pmr, SYS_ICC_PMR_EL1);

        write_sysreg(hwstate.daif, daif);
        if (hwstate.pmr == GIC_PRIO_IRQON) {
            barrier();
            write_sysreg_s(hwstate.pmr, SYS_ICC_PMR_EL1);
            pmr_sync();
        }
    } else {
        write_sysreg(hwstate.daif, daif);
    }
}

otherwise, LGTM
Reviewed-by: Jinjie Ruan <ruanjinjie at huawei.com>

> +
> +static __always_inline
> +void arm64_update_exc_hwstate(arm64_exc_hwstate_t hwstate)
> +{
> +	__arm64_update_exc_hwstate(hwstate, true);
> +}
> +
> +static __always_inline
> +void arm64_update_exc_context(arm64_exc_context_t context)
> +{
> +	arm64_exc_hwstate_t hwstate = arm64_exc_hwstate_of_context(context);
> +
> +	arm64_update_exc_hwstate(hwstate);
> +}
> +#endif /* __ASM_INTERRUPTS_COMMON_FLAGS_H */




More information about the linux-arm-kernel mailing list