Commit 284a3c2924 for qemu.org
commit 284a3c29246097ce8d0afd8676371c97cd796188
Author: Peter Maydell <peter.maydell@linaro.org>
Date: Mon Aug 17 13:38:35 2026 +0100
target/arm: Implement CPACR.ASEDIS and HCPTR.TASE
When executing at AArch32, there are optional trap bits for Neon
instructions in CPACR and HCPTR. We don't currently implement these.
Now we have a separate code path for access checks for Neon insns, we
can straightforwardly add the check there. We need to track the
target EL for Neon-specific trapping in a new TB flag.
Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/1499
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
Message-id: 20260817123838.1578060-6-peter.maydell@linaro.org
diff --git a/target/arm/cpu.h b/target/arm/cpu.h
index 4f07332543..afc3fda3b8 100644
--- a/target/arm/cpu.h
+++ b/target/arm/cpu.h
@@ -2506,6 +2506,12 @@ FIELD(TBFLAG_A32, NS, 10, 1)
* This requires an SME trap from AArch32 mode when using NEON.
*/
FIELD(TBFLAG_A32, SME_TRAP_NONSTREAMING, 11, 1)
+/*
+ * Target EL for a Neon-disabled exception via CPACR.ASEDIS, HCPTR.TASE.
+ * If FPEXC_EL indicates a trap to a lower EL than this, that will
+ * take precedence.
+ */
+FIELD(TBFLAG_A32, NEONEXC_EL, 12, 2)
/*
* Bit usage when in AArch32 state, for M-profile only.
diff --git a/target/arm/tcg/hflags.c b/target/arm/tcg/hflags.c
index 0716ca98fd..14824e3dff 100644
--- a/target/arm/tcg/hflags.c
+++ b/target/arm/tcg/hflags.c
@@ -164,6 +164,86 @@ static bool sme_fa64(CPUARMState *env, int el)
return true;
}
+static int neon_exception_el(CPUARMState *env, int cur_el)
+{
+ /*
+ * Return the EL to trap to for A32 Neon specific traps
+ * (CPACR.ASEDIS and HCPTR.TASE). In the pseudocode these are
+ * checked in the same function as the more general trap bits that
+ * we handle in fp_exception_el(). Fortunately it is always the
+ * case that if the trap/enable bits specify taking an exception
+ * to different ELs for the Neon-specific insns and the general fp
+ * insns then the trap to the lower of the two ELs has priority,
+ * so we can calculate the two target ELs separately and pick the
+ * right destination later. Compare AArch32_CheckAdvSIMDOrFPEnabled().
+ *
+ * CPACR doesn't exist before v6, but neither does Neon, so we can
+ * assume that if we're here testing this then the register exists.
+ * HCPTR always exists if EL2 is present.
+ */
+ uint64_t hcr_el2 = arm_hcr_el2_eff(env);
+ bool cpacr_asedis = FIELD_EX64(env->cp15.cpacr_el1, CPACR, ASEDIS);
+ bool hcptr_tase = FIELD_EX64(env->cp15.cptr_el[2], HCPTR, TASE);
+ bool have_aarch32_el3 =
+ arm_feature(env, ARM_FEATURE_EL3) && !arm_el_is_aa64(env, 3);
+
+ if (!arm_feature(env, ARM_FEATURE_NEON_TRAPS)) {
+ /* This CPU doesn't implement the trap bits (Cortex-A8) */
+ return 0;
+ }
+
+ if (arm_feature(env, ARM_FEATURE_EL2) && arm_el_is_aa64(env, 2)) {
+ /*
+ * The AArch64 CPTR_EL2 has no equivalent to HCPTR.TASE; only
+ * an AArch32 EL2 can trap Neon specifically.
+ */
+ hcptr_tase = false;
+ }
+
+ /*
+ * We know we're in AArch32, but if this is EL0 and EL1 is AArch64
+ * then CPACR_EL1 applies rather than CPACR, and it doesn't have
+ * ASEDIS (instead using the same bit for TCPAC).
+ */
+ if (cur_el == 0 && arm_el_is_aa64(env, 1)) {
+ cpacr_asedis = false;
+ }
+
+ /* CPACR is ignored if E2H+TGE are both set */
+ if ((hcr_el2 & (HCR_E2H | HCR_TGE)) == (HCR_E2H | HCR_TGE)) {
+ cpacr_asedis = false;
+ }
+
+ /*
+ * NSACR.NSASEDIS makes the effective values of HCPTR.TASE and
+ * CPACR.ASEDIS be 1 in NonSecure state. NSACR has no
+ * effect unless EL3 exists and is AArch32.
+ */
+ if (have_aarch32_el3 && cur_el <= 2 && !arm_is_secure_below_el3(env)) {
+ if (FIELD_EX32(env->cp15.nsacr, NSACR, NSASEDIS)) {
+ cpacr_asedis = true;
+ hcptr_tase = true;
+ }
+ }
+
+ if (cpacr_asedis) {
+ if (have_aarch32_el3 && (cur_el == 3 || arm_is_secure_below_el3(env))) {
+ /* Trap from Secure PL0 or PL1 to Secure PL1 */
+ return 3;
+ }
+ if (cur_el <= 1) {
+ /* trap from EL0 or EL1 to EL1 */
+ return 1;
+ }
+ }
+
+ /* HCPTR.TASE traps to EL2, including for execution at EL2 */
+ if (hcptr_tase && cur_el <= 2) {
+ return 2;
+ }
+ return 0;
+}
+
static CPUARMTBFlags rebuild_hflags_a32(CPUARMState *env, int fp_el,
ARMMMUIdx mmu_idx)
{
@@ -209,6 +289,8 @@ static CPUARMTBFlags rebuild_hflags_a32(CPUARMState *env, int fp_el,
DP_TBFLAG_A32(flags, SME_TRAP_NONSTREAMING, 1);
}
+ DP_TBFLAG_A32(flags, NEONEXC_EL, neon_exception_el(env, el));
+
return rebuild_hflags_common_32(env, fp_el, mmu_idx, flags);
}
diff --git a/target/arm/tcg/translate-vfp.c b/target/arm/tcg/translate-vfp.c
index 6e944a0322..4db4b14841 100644
--- a/target/arm/tcg/translate-vfp.c
+++ b/target/arm/tcg/translate-vfp.c
@@ -306,15 +306,25 @@ bool vfp_access_check(DisasContext *s)
/*
* Access check for Neon; this is for instructions which can be
- * trapped by CPACR.ASEDIS and HCPTR.TASE. Support for those traps
- * is optional and we currently do not implement them, so this
- * is identical to a VFP access check for now.
+ * trapped by CPACR.ASEDIS and HCPTR.TASE.
*/
bool neon_access_check(DisasContext *s)
{
if (arm_dc_feature(s, ARM_FEATURE_M)) {
return vfp_access_check_m(s, false);
} else {
+ /*
+ * If the Neon-specific trap bits request a trap to a lower EL
+ * than the general FP trap bits, the trap to the lower EL
+ * has priority.
+ */
+ if (s->neon_excp_el &&
+ (!s->fp_excp_el || s->neon_excp_el < s->fp_excp_el)) {
+ uint32_t syn = syn_a32_fp_access_trap(1, 0xe, 1, 0);
+
+ gen_exception_insn_el(s, 0, EXCP_UDEF, syn, s->neon_excp_el);
+ return false;
+ }
return vfp_access_check_a(s, false, true);
}
}
diff --git a/target/arm/tcg/translate.c b/target/arm/tcg/translate.c
index 1770428d3c..7306a1389c 100644
--- a/target/arm/tcg/translate.c
+++ b/target/arm/tcg/translate.c
@@ -6403,6 +6403,7 @@ static void arm_tr_init_disas_context(DisasContextBase *dcbase, CPUState *cs)
dc->vec_stride = EX_TBFLAG_A32(tb_flags, VECSTRIDE);
dc->sme_trap_nonstreaming =
EX_TBFLAG_A32(tb_flags, SME_TRAP_NONSTREAMING);
+ dc->neon_excp_el = EX_TBFLAG_A32(tb_flags, NEONEXC_EL);
}
dc->lse2 = false; /* applies only to aarch64 */
dc->cp_regs = cpu->cp_regs;
diff --git a/target/arm/tcg/translate.h b/target/arm/tcg/translate.h
index a3d03159ad..cce84f29c4 100644
--- a/target/arm/tcg/translate.h
+++ b/target/arm/tcg/translate.h
@@ -88,6 +88,7 @@ typedef struct DisasContext {
int sve_excp_el; /* SVE exception EL or 0 if enabled */
int sme_excp_el; /* SME exception EL or 0 if enabled */
int zt0_excp_el; /* ZT0 exception EL or 0 if enabled */
+ int neon_excp_el; /* A32 Neon exception EL or 0 if enabled */
int vl; /* current vector length in bytes */
int svl; /* current streaming vector length in bytes */
int max_svl; /* maximum implemented streaming vector length */