If we create a normally-AArch64 CPU and configure it with
aarch64=off, this will by default leave all the AArch64 ID register
values in its ARMISARegisters struct untouched. That in turn means
that tests of cpu_isar_feature(aa64_something, cpu) will return true.
Until now we have had a design policy that you shouldn't check an
aa64_ feature unless you know that the CPU has AArch64; but this is
quite fragile as it's easy to forget and only causes a problem in the
corner case where AArch64 was turned off. In particular, when we
extend the ability to disable AArch64 from only KVM to also TCG there
are many more aa64 feature check points which we would otherwise have
to audit for whether they needed to be guarded with a check on
ARM_FEATURE_AARCH64.
Instead, make the CPU realize function zero out all the 64-bit ID
registers if a TCG CPU doesn't have AArch64; this will make aa64_
feature tests generally return false.
We only do this for TCG because only TCG really needs it, and for
KVM it might be confusing to have QEMU's idea of the ID registers
be different from KVM's.
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
Message-id: 20260416165353.589569-2-peter.maydell@linaro.org
---
target/arm/cpu.c | 35 +++++++++++++++++++++++++++++++++++
target/arm/cpu.h | 3 ++-
2 files changed, 37 insertions(+), 1 deletion(-)
diff --git a/target/arm/cpu.c b/target/arm/cpu.c
index b62de8addf..6705ee9db7 100644
--- a/target/arm/cpu.c
+++ b/target/arm/cpu.c
@@ -1606,6 +1606,27 @@ void arm_cpu_finalize_features(ARMCPU *cpu, Error **errp)
}
}
+static void arm_clear_aarch64_idregs(ARMCPU *cpu)
+{
+ /* Zero out all the AArch64 ID registers in ARMISARegisters */
+ SET_IDREG(&cpu->isar, ID_AA64ISAR0, 0);
+ SET_IDREG(&cpu->isar, ID_AA64ISAR1, 0);
+ SET_IDREG(&cpu->isar, ID_AA64ISAR2, 0);
+ SET_IDREG(&cpu->isar, ID_AA64PFR0, 0);
+ SET_IDREG(&cpu->isar, ID_AA64PFR1, 0);
+ SET_IDREG(&cpu->isar, ID_AA64PFR2, 0);
+ SET_IDREG(&cpu->isar, ID_AA64MMFR0, 0);
+ SET_IDREG(&cpu->isar, ID_AA64MMFR1, 0);
+ SET_IDREG(&cpu->isar, ID_AA64MMFR2, 0);
+ SET_IDREG(&cpu->isar, ID_AA64MMFR3, 0);
+ SET_IDREG(&cpu->isar, ID_AA64DFR0, 0);
+ SET_IDREG(&cpu->isar, ID_AA64DFR1, 0);
+ SET_IDREG(&cpu->isar, ID_AA64AFR0, 0);
+ SET_IDREG(&cpu->isar, ID_AA64AFR1, 0);
+ SET_IDREG(&cpu->isar, ID_AA64ZFR0, 0);
+ SET_IDREG(&cpu->isar, ID_AA64SMFR0, 0);
+}
+
static void arm_cpu_realizefn(DeviceState *dev, Error **errp)
{
CPUState *cs = CPU(dev);
@@ -1733,6 +1754,20 @@ static void arm_cpu_realizefn(DeviceState *dev, Error **errp)
}
#endif
+ /*
+ * A TCG aarch64=off CPU has no AArch64 at all, so we clear out the
+ * ID registers to avoid cpu_isar_feature(aa64_something, cpu) tests
+ * incorrectly returning true. We don't do this for other accelerators
+ * (which in practice means "for KVM", since no others have AArch32
+ * guest support) because from KVM's point of view the AArch64 ID
+ * registers still exist and must have their correct values. So we
+ * avoid clearing them out so that we don't have QEMU and KVM with
+ * different ideas of the ID registers.
+ */
+ if (tcg_enabled() && !arm_feature(env, ARM_FEATURE_AARCH64)) {
+ arm_clear_aarch64_idregs(cpu);
+ }
+
#ifdef CONFIG_USER_ONLY
/*
* User mode relies on IC IVAU instructions to catch modification of
diff --git a/target/arm/cpu.h b/target/arm/cpu.h
index 657ff4ab20..ab6bacf4aa 100644
--- a/target/arm/cpu.h
+++ b/target/arm/cpu.h
@@ -1080,7 +1080,8 @@ struct ArchCPU {
* Note that if you add an ID register to the ARMISARegisters struct
* you need to also update the 32-bit and 64-bit versions of the
* kvm_arm_get_host_cpu_features() function to correctly populate the
- * field by reading the value from the KVM vCPU.
+ * field by reading the value from the KVM vCPU. If it is an AArch64
+ * ID register then you also must update arm_clear_aarch64_idregs().
*/
struct ARMISARegisters {
uint32_t mvfr0;
--
2.43.0