target/arm: Implement CPACR.D32DIS

On some v7A CPUs, CPACR.D32DIS is a bit allowing the guest to make
VFP instructions that touch registers D16..D31 UNDEF.  Whether the
CPU implements this or not is IMPDEF, and the only two CPUs we
implement which have this are the Cortex-A7 and Cortex-A9.  In v8A
the bit is no longer defined at all.

Since the only kind of trapping that needs to be done is a simple
UNDEF, this is straightforward enough to implement.

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-8-peter.maydell@linaro.org
diff --git a/target/arm/cpu.h b/target/arm/cpu.h
index afc3fda..e8dfc31 100644
--- a/target/arm/cpu.h
+++ b/target/arm/cpu.h
@@ -2184,6 +2184,8 @@
      * ARM_FEATURE_NEON CPUs except the Cortex-A8.
      */
     ARM_FEATURE_NEON_TRAPS,
+    /* Does the CPU implement CPACR.D32DIS ? */
+    ARM_FEATURE_D32DIS,
 };
 
 static inline int arm_feature(const CPUARMState *env, int feature)
@@ -2512,6 +2514,8 @@
  * take precedence.
  */
 FIELD(TBFLAG_A32, NEONEXC_EL, 12, 2)
+/* Should VFP insns touching D16..D31 UNDEF? (CPACR.D32DIS) */
+FIELD(TBFLAG_A32, D32DIS, 14, 1)
 
 /*
  * Bit usage when in AArch32 state, for M-profile only.
diff --git a/target/arm/helper.c b/target/arm/helper.c
index 2dfbeae..4f30a94 100644
--- a/target/arm/helper.c
+++ b/target/arm/helper.c
@@ -573,8 +573,7 @@
          */
         if (cpu_isar_feature(aa32_vfp_simd, env_archcpu(env))) {
             /* VFP coprocessor: cp10 & cp11 [23:20] */
-            mask |= R_CPACR_D32DIS_MASK |
-                    R_CPACR_CP11_MASK |
+            mask |= R_CPACR_CP11_MASK |
                     R_CPACR_CP10_MASK;
 
             if (!arm_feature(env, ARM_FEATURE_NEON)) {
@@ -596,6 +595,13 @@
             if (!cpu_isar_feature(aa32_simd_r32, env_archcpu(env))) {
                 /* D32DIS [30] is RAO/WI if D16-31 are not implemented. */
                 value |= R_CPACR_D32DIS_MASK;
+                mask |= R_CPACR_D32DIS_MASK;
+            } else if (arm_feature(env, ARM_FEATURE_D32DIS)) {
+                /*
+                 * Bit is present unless CPU doesn't implement D32DIS,
+                 * in which case it is RAZ/WI.
+                 */
+                mask |= R_CPACR_D32DIS_MASK;
             }
         }
         value &= mask;
@@ -605,7 +611,7 @@
      * For A-profile AArch32 EL3 (but not M-profile secure mode), if NSACR.CP10
      * is 0 then CPACR.{CP11,CP10} ignore writes and read as 0b00.
      * Similarly, if NSACR.NSASEDIS is 1 then CPACR.ASEDIS ignores writes
-     * and reads as 1.
+     * and reads as 1, and NSACR.NSD32DIS makes CPACR.D32DIS behave as RAO/WI.
      */
     if (arm_feature(env, ARM_FEATURE_EL3) && !arm_el_is_aa64(env, 3) &&
         !arm_is_secure(env)) {
@@ -617,6 +623,10 @@
             mask = R_CPACR_ASEDIS_MASK;
             value = (value & ~mask) | (env->cp15.cpacr_el1 & mask);
         }
+        if (FIELD_EX32(env->cp15.nsacr, NSACR, NSD32DIS)) {
+            mask = R_CPACR_D32DIS_MASK;
+            value = (value & ~mask) | (env->cp15.cpacr_el1 & mask);
+        }
     }
 
     env->cp15.cpacr_el1 = value;
@@ -639,6 +649,9 @@
         if (FIELD_EX32(env->cp15.nsacr, NSACR, NSASEDIS)) {
             value |= R_CPACR_ASEDIS_MASK;
         }
+        if (FIELD_EX32(env->cp15.nsacr, NSACR, NSD32DIS)) {
+            value |= R_CPACR_D32DIS_MASK;
+        }
     }
     return value;
 }
diff --git a/target/arm/tcg/cpu32.c b/target/arm/tcg/cpu32.c
index 724a954..bc07e33 100644
--- a/target/arm/tcg/cpu32.c
+++ b/target/arm/tcg/cpu32.c
@@ -336,6 +336,7 @@
      */
     set_feature(&cpu->env, ARM_FEATURE_V7MP);
     set_feature(&cpu->env, ARM_FEATURE_CBAR);
+    set_feature(&cpu->env, ARM_FEATURE_D32DIS);
     cpu->midr = 0x410fc090;
     cpu->reset_fpsid = 0x41033090;
     cpu->isar.mvfr0 = 0x11110222;
@@ -403,6 +404,7 @@
     set_feature(&cpu->env, ARM_FEATURE_EL2);
     set_feature(&cpu->env, ARM_FEATURE_EL3);
     set_feature(&cpu->env, ARM_FEATURE_PMU);
+    set_feature(&cpu->env, ARM_FEATURE_D32DIS);
     cpu->midr = 0x410fc075;
     cpu->reset_fpsid = 0x41023075;
     cpu->isar.mvfr0 = 0x10110222;
diff --git a/target/arm/tcg/hflags.c b/target/arm/tcg/hflags.c
index 14824e3..296ec81 100644
--- a/target/arm/tcg/hflags.c
+++ b/target/arm/tcg/hflags.c
@@ -244,6 +244,24 @@
     return 0;
 }
 
+static bool arm_d32dis(CPUARMState *env, int cur_el)
+{
+    bool cpacr_d32dis = FIELD_EX64(env->cp15.cpacr_el1, CPACR, D32DIS);
+
+    if (!arm_feature(env, ARM_FEATURE_D32DIS)) {
+        return false;
+    }
+
+    /* If NSACR.NSD32DIS is set, CPACR.D32DIS acts as 1 in NonSecure */
+    if ((arm_feature(env, ARM_FEATURE_EL3) && !arm_el_is_aa64(env, 3) &&
+         cur_el <= 2 && !arm_is_secure_below_el3(env))) {
+        if (FIELD_EX32(env->cp15.nsacr, NSACR, NSD32DIS)) {
+            cpacr_d32dis = true;
+        }
+    }
+    return cpacr_d32dis;
+}
+
 static CPUARMTBFlags rebuild_hflags_a32(CPUARMState *env, int fp_el,
                                         ARMMMUIdx mmu_idx)
 {
@@ -291,6 +309,8 @@
 
     DP_TBFLAG_A32(flags, NEONEXC_EL, neon_exception_el(env, el));
 
+    DP_TBFLAG_A32(flags, D32DIS, arm_d32dis(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 a8d470e..0a87a01 100644
--- a/target/arm/tcg/translate-vfp.c
+++ b/target/arm/tcg/translate-vfp.c
@@ -210,8 +210,7 @@
 /*
  * Return true if a VFP insn is OK to access the registers indicated
  * by regmask, false if it should UNDEF. This checks whether the
- * D16-D31 regs are implemented by the CPU. Eventually we will also check
- * CPACR.D32DIS.
+ * D16-D31 regs are implemented by the CPU and not disabled by CPACR.D32DIS.
  * Note that Neon insns accessing D16..D31 do not need to check D32DIS,
  * so this function is for VFP insns only.
  *
@@ -219,9 +218,7 @@
  */
 static bool vfp_dregs_ok(DisasContext *s, int dregmask)
 {
-    int invalid_dreg_mask = dc_isar_feature(aa32_simd_r32, s) ? 0 : 0x10;
-
-    return !(dregmask & invalid_dreg_mask);
+    return !(dregmask & s->invalid_vfp_dreg_mask);
 }
 
 /*
diff --git a/target/arm/tcg/translate.c b/target/arm/tcg/translate.c
index 7306a13..90eb6f3 100644
--- a/target/arm/tcg/translate.c
+++ b/target/arm/tcg/translate.c
@@ -6342,6 +6342,7 @@
     ARMCPU *cpu = env_archcpu(env);
     CPUARMTBFlags tb_flags = arm_tbflags_from_tb(dc->base.tb);
     uint32_t condexec, core_mmu_idx;
+    bool d32dis = false;
 
     dc->isar = &cpu->isar;
     dc->condjmp = 0;
@@ -6404,7 +6405,12 @@
         dc->sme_trap_nonstreaming =
             EX_TBFLAG_A32(tb_flags, SME_TRAP_NONSTREAMING);
         dc->neon_excp_el = EX_TBFLAG_A32(tb_flags, NEONEXC_EL);
+        d32dis = EX_TBFLAG_A32(tb_flags, D32DIS);
     }
+
+    dc->invalid_vfp_dreg_mask =
+        (d32dis || !dc_isar_feature(aa32_simd_r32, dc)) ? 0x10 : 0;
+
     dc->lse2 = false; /* applies only to aarch64 */
     dc->cp_regs = cpu->cp_regs;
     dc->features = env->features;
diff --git a/target/arm/tcg/translate.h b/target/arm/tcg/translate.h
index cce84f2..b08bded 100644
--- a/target/arm/tcg/translate.h
+++ b/target/arm/tcg/translate.h
@@ -94,6 +94,7 @@
     int max_svl;     /* maximum implemented streaming vector length */
     int max_any_vl;  /* maximum implemented vector length */
     bool vfp_enabled; /* FP enabled via FPSCR.EN */
+    int invalid_vfp_dreg_mask; /* mask for whether VFP D16..D31 should UNDEF */
     int vec_len;
     int vec_stride;
     bool v7m_handler_mode;