>From 92b813355251d61ab7941a8df500ab35c0a34193 Mon Sep 17 00:00:00 2001
From: Khushit Shah <[email protected]>
Date: Mon, 25 May 2026 17:51:54 +0530
Subject: [PATCH] target/arm/kvm: cache host ID register views with and
 without EL2

kvm_arm_get_host_cpu_features() probes the host by spinning up a
scratch vCPU and reading its ID registers. When the host kernel
advertises nested virtualization, the scratch was always created with
KVM_ARM_VCPU_HAS_EL2, which makes the kernel run limit_nv_id_reg() on
that vCPU and force several ID register fields (e.g.
ID_AA64MMFR4_EL1.NV_frac, parts of ID_AA64DFR0_EL1, etc.) to values
that match KVM's nested-virt emulation rather than the raw host
capability.

Split the cache into two slots indexed by with_el2 (lazily populated)
and plumb the selector through kvm_arm_set_cpu_features_from_host()
and kvm_arm_get_host_isar(). Callers pick the view that matches their
intent.

Signed-off-by: Khushit Shah <[email protected]>
---
Patch 1, part of RFC v2 for named models for arm.
will not cleanly apply but for the gist of the solution.
---
 target/arm/arm-cpu-models.c |  5 +++--
 target/arm/arm-qmp-cmds.c   |  4 ++--
 target/arm/cpu64.c          |  2 +-
 target/arm/kvm-stub.c       |  7 ++++++-
 target/arm/kvm.c            | 37 +++++++++++++++++++++----------------
 target/arm/kvm_arm.h        |  5 +++--
 6 files changed, 36 insertions(+), 24 deletions(-)

diff --git a/target/arm/arm-cpu-models.c b/target/arm/arm-cpu-models.c
index 9265bb776a..31765be55b 100644
--- a/target/arm/arm-cpu-models.c
+++ b/target/arm/arm-cpu-models.c
@@ -549,7 +549,8 @@ static void arm_named_cpu_initfn(Object *obj)
         return;
     }
 
-    kvm_arm_set_cpu_features_from_host(cpu);
+    /* For now let's not support nested virt for named models */
+    kvm_arm_set_cpu_features_from_host(cpu, false);
     if (!arm_feature(&cpu->env, ARM_FEATURE_AARCH64)) {
         return;
     }
@@ -592,7 +593,7 @@ void arm_cpu_class_check_missing_features(ARMCPUClass *acc, 
strList **blockers)
         }
     }
 
-    arm_idregs_get_blockers(cpu, kvm_arm_get_host_isar(), blockers);
+    arm_idregs_get_blockers(cpu, kvm_arm_get_host_isar(false), blockers);
 out:
     object_unref(obj);
 }
diff --git a/target/arm/arm-qmp-cmds.c b/target/arm/arm-qmp-cmds.c
index 84341a2105..453442ab93 100644
--- a/target/arm/arm-qmp-cmds.c
+++ b/target/arm/arm-qmp-cmds.c
@@ -82,7 +82,7 @@ CpuPropSupportedValues 
*qmp_query_cpu_props_supported_values(Error **errp)
         QList *sv = qlist_new();
 
         arm_prop_append_supported_values(pdesc,
-                                         kvm_arm_get_host_isar(), sv);
+                                         kvm_arm_get_host_isar(false), sv);
         qdict_put_obj(props, pdesc->name, QOBJECT(sv));
     }
 
@@ -119,7 +119,7 @@ CpuPropInfoList 
*qmp_query_arm_cpu_named_model_prop_info(Error **errp)
 
         sv = qlist_new();
         arm_prop_append_supported_values(pdesc,
-                                         kvm_arm_get_host_isar(), sv);
+                                         kvm_arm_get_host_isar(false), sv);
 
         prop_info->supported_values = QOBJECT(sv);
         prop_info->composite = NULL;
diff --git a/target/arm/cpu64.c b/target/arm/cpu64.c
index 109cead3c8..16b4d87dc1 100644
--- a/target/arm/cpu64.c
+++ b/target/arm/cpu64.c
@@ -875,7 +875,7 @@ static void aarch64_host_initfn(Object *obj)
 
 #if defined(CONFIG_KVM)
     kvm_arm_set_cpreg_mig_tolerances(cpu);
-    kvm_arm_set_cpu_features_from_host(cpu);
+    kvm_arm_set_cpu_features_from_host(cpu, true);
     aarch64_add_sve_properties(obj);
     arm_add_cpu_props(obj);
 #elif defined(CONFIG_HVF)
diff --git a/target/arm/kvm-stub.c b/target/arm/kvm-stub.c
index 88cbe8d85c..98f41818ba 100644
--- a/target/arm/kvm-stub.c
+++ b/target/arm/kvm-stub.c
@@ -45,7 +45,7 @@ bool kvm_arm_el2_supported(void)
 /*
  * These functions should never actually be called without KVM support.
  */
-void kvm_arm_set_cpu_features_from_host(ARMCPU *cpu)
+void kvm_arm_set_cpu_features_from_host(ARMCPU *cpu, bool with_el2)
 {
     g_assert_not_reached();
 }
@@ -119,3 +119,8 @@ char *kvm_print_register_name(uint64_t regidx)
 {
     g_assert_not_reached();
 }
+
+const ARMISARegisters *kvm_arm_get_host_isar(bool with_el2)
+{
+    return NULL;
+}
diff --git a/target/arm/kvm.c b/target/arm/kvm.c
index 9f8368712f..c26ed485a3 100644
--- a/target/arm/kvm.c
+++ b/target/arm/kvm.c
@@ -65,7 +65,7 @@ typedef struct ARMHostCPUFeatures {
     const char *dtb_compatible;
 } ARMHostCPUFeatures;
 
-static ARMHostCPUFeatures arm_host_cpu_features;
+static ARMHostCPUFeatures arm_host_cpu_features[2];
 
 #define DEF(NAME, OP0, OP1, CRN, CRM, OP2) [NAME##_IDX] = #NAME,
 const char * const sysreg_names[NUM_ID_IDX] = {
@@ -349,7 +349,7 @@ static uint32_t kvm_arm_sve_get_vls(int fd)
     return vls[0] & MAKE_64BIT_MASK(0, ARM_MAX_VQ);
 }
 
-static void kvm_arm_get_host_cpu_features(ARMHostCPUFeatures *ahcf)
+static void kvm_arm_get_host_cpu_features(ARMHostCPUFeatures *ahcf, bool 
with_el2)
 {
     /* Identify the feature bits corresponding to the host CPU, and
      * fill out the ARMHostCPUClass fields accordingly. To do this
@@ -389,7 +389,7 @@ static void 
kvm_arm_get_host_cpu_features(ARMHostCPUFeatures *ahcf)
      * Ask for EL2 if supported.
      */
     el2_supported = kvm_arm_el2_supported();
-    if (el2_supported) {
+    if (el2_supported && with_el2) {
         init.features[0] |= 1 << KVM_ARM_VCPU_HAS_EL2;
     }
 
@@ -528,7 +528,7 @@ static void 
kvm_arm_get_host_cpu_features(ARMHostCPUFeatures *ahcf)
             err |= get_host_cpu_reg(fd, ahcf, ID_AA64ZFR0_EL1_IDX);
 
             /* Read the set of supported vector lengths. */
-            arm_host_cpu_features.sve_vq_supported = kvm_arm_sve_get_vls(fd);
+            ahcf->sve_vq_supported = kvm_arm_sve_get_vls(fd);
         }
     }
     /*
@@ -586,15 +586,15 @@ static void 
kvm_arm_get_host_cpu_features(ARMHostCPUFeatures *ahcf)
     ahcf->features = features;
 }
 
-void kvm_arm_set_cpu_features_from_host(ARMCPU *cpu)
+void kvm_arm_set_cpu_features_from_host(ARMCPU *cpu, bool with_el2)
 {
     CPUARMState *env = &cpu->env;
 
-    if (!arm_host_cpu_features.dtb_compatible) {
-        kvm_arm_get_host_cpu_features(&arm_host_cpu_features);
+    if (!arm_host_cpu_features[with_el2].dtb_compatible) {
+        kvm_arm_get_host_cpu_features(&arm_host_cpu_features[with_el2], 
with_el2);
     }
 
-    cpu->kvm_target = arm_host_cpu_features.target;
+    cpu->kvm_target = arm_host_cpu_features[with_el2].target;
 
     if (cpu->kvm_target == QEMU_KVM_ARM_TARGET_NONE) {
         /*
@@ -605,15 +605,18 @@ void kvm_arm_set_cpu_features_from_host(ARMCPU *cpu)
         return;
     }
 
-    cpu->dtb_compatible = arm_host_cpu_features.dtb_compatible;
-    cpu->isar = arm_host_cpu_features.isar;
-    cpu->sve_vq.supported = arm_host_cpu_features.sve_vq_supported;
-    env->features = arm_host_cpu_features.features;
+    cpu->dtb_compatible = arm_host_cpu_features[with_el2].dtb_compatible;
+    cpu->isar = arm_host_cpu_features[with_el2].isar;
+    cpu->sve_vq.supported = arm_host_cpu_features[with_el2].sve_vq_supported;
+    env->features = arm_host_cpu_features[with_el2].features;
 }
 
-const ARMISARegisters *kvm_arm_get_host_isar(void)
+const ARMISARegisters *kvm_arm_get_host_isar(bool with_el2)
 {
-    return &arm_host_cpu_features.isar;
+    if (!arm_host_cpu_features[with_el2].dtb_compatible) {
+        kvm_arm_get_host_cpu_features(&arm_host_cpu_features[with_el2], 
with_el2);
+    }
+    return &arm_host_cpu_features[with_el2].isar;
 }
 
 static bool kvm_no_adjvtime_get(Object *obj, Error **errp)
@@ -2331,9 +2334,11 @@ int kvm_arch_init_vcpu(CPUState *cs)
                 "  host=0x%016" PRIx64 "\n",
                 _i, sysreg_names[_i] ? sysreg_names[_i] : "(null)",
                 cpu->isar.idregs[_i],
-                arm_host_cpu_features.isar.idregs[_i]);
+                arm_host_cpu_features[cpu->has_el2].isar.idregs[_i]);
     }
-    if (!arm_idregs_validate_safe_rule(cpu, &arm_host_cpu_features.isar, 
&err)) {
+
+    /* validate the cpu->isar.idregs[] against the ahcf->isar.idregs[], select 
ahcf based on the cpu->has_el2 */
+    if (!arm_idregs_validate_safe_rule(cpu, 
&arm_host_cpu_features[cpu->has_el2].isar, &err)) {
         error_report_err(err);
         return -EINVAL;
     }
diff --git a/target/arm/kvm_arm.h b/target/arm/kvm_arm.h
index 750dbe1ef4..538f87e69e 100644
--- a/target/arm/kvm_arm.h
+++ b/target/arm/kvm_arm.h
@@ -129,11 +129,12 @@ void kvm_arm_destroy_scratch_host_vcpu(int *fdarray);
 /**
  * kvm_arm_set_cpu_features_from_host:
  * @cpu: ARMCPU to set the features for
+ * @with_el2: probe the host with the EL2-enabled scratch vCPU view
  *
  * Set up the ARMCPU struct fields up to match the information probed
  * from the host CPU.
  */
-void kvm_arm_set_cpu_features_from_host(ARMCPU *cpu);
+void kvm_arm_set_cpu_features_from_host(ARMCPU *cpu, bool with_el2);
 
 /**
  * kvm_arm_add_vcpu_properties:
@@ -231,7 +232,7 @@ void kvm_arm_enable_mte(Object *cpuobj, Error **errp);
 
 void arm_cpu_kvm_set_irq(void *arm_cpu, int irq, int level);
 
-const ARMISARegisters *kvm_arm_get_host_isar(void);
+const ARMISARegisters *kvm_arm_get_host_isar(bool with_el2);
 
 void arm_gic_cap_kvm_probe(GICCapability *v2, GICCapability *v3);
 
-- 
2.52.0


Reply via email to