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 is created with KVM_ARM_VCPU_HAS_EL2, which makes the kernel run limit_nv_id_reg() on that vCPU and force several fields (e.g. ID_AA64MMFR4_EL1.NV_frac, parts of ID_AA64DFR0_EL1) to values that match KVM's nested-virt capabilities rather than the raw host.
If the actual vCPU is later created without EL2 (e.g. -machine virt,virtualization=off), the EL2-on values cached above no longer match what the kernel exposes for the real vCPU and ID-register writeback fails. Fix this by caching two host views, one probed with EL2 and one without, and selecting the appropriate view at every use site. For -cpu host (and -cpu max) without EL2 we additionally rebase cpu->isar.idregs[] from the EL2-on view it was initialized with onto the EL2-off view via kvm_arm_rebase_host_idregs(), so each field that the user has not explicitly touched picks up the value that the real vCPU will actually see. Corner case: the rebase compares the post-init field value against the EL2-on host value to decide whether the user set it. If the user directly or indirectly set field X=i and the EL2-on view also reports feat_X=i but the EL2-off view reports feat_X=j, the rebase cannot distinguish "user asked for i" from "inherited i from host" and will overwrite with j. This only affects -cpu host/max without EL2 on NV enabled kernels where the user-requested value happens to match the EL2-on host view, so we avoid the complexity of user set values tracking and accept the loss here. Named models are not affected with the issue as they already start with default-zeroed view. Signed-off-by: Khushit Shah <[email protected]> --- target/arm/arm-cpu-models.c | 14 +++++- target/arm/arm-qmp-cmds.c | 2 +- target/arm/cpu64.c | 2 +- target/arm/kvm-stub.c | 4 +- target/arm/kvm.c | 93 ++++++++++++++++++++++++++++++------- target/arm/kvm_arm.h | 5 +- 6 files changed, 94 insertions(+), 26 deletions(-) diff --git a/target/arm/arm-cpu-models.c b/target/arm/arm-cpu-models.c index 44e7d3cfa2..a41f1f1169 100644 --- a/target/arm/arm-cpu-models.c +++ b/target/arm/arm-cpu-models.c @@ -459,7 +459,12 @@ static void arm_named_cpu_initfn(Object *obj) return; } - kvm_arm_set_cpu_features_from_host(cpu); + /* + * Start with the host view without EL2. + * If nested virt is needed user must provide values for all the + * blockers. + */ + kvm_arm_set_cpu_features_from_host(cpu, false); if (!arm_feature(&cpu->env, ARM_FEATURE_AARCH64)) { return; } @@ -481,10 +486,15 @@ void arm_cpu_class_check_missing_features(ARMCPUClass *acc, strList **blockers) Object *obj = object_new_with_class(OBJECT_CLASS(acc)); ARMCPU *cpu = ARM_CPU(obj); + const ARMISARegisters *host_isar; + if (!kvm_enabled() || !arm_find_model(acc->info->name)) { goto out; } + /* Use the host view without EL2 to calculate blockers. */ + host_isar = kvm_arm_get_host_isar(false); + if (cpu_isar_feature(aa64_sve, cpu)) { arm_cpu_sve_finalize(cpu, &err); if (err) { @@ -505,7 +515,7 @@ void arm_cpu_class_check_missing_features(ARMCPUClass *acc, strList **blockers) } } - aarch64_idregs_get_blockers(cpu, kvm_arm_get_host_isar(), blockers); + aarch64_idregs_get_blockers(cpu, host_isar, blockers); out: object_unref(obj); } diff --git a/target/arm/arm-qmp-cmds.c b/target/arm/arm-qmp-cmds.c index 6f11681c87..8dcff6c22c 100644 --- a/target/arm/arm-qmp-cmds.c +++ b/target/arm/arm-qmp-cmds.c @@ -102,7 +102,7 @@ CpuPropertyInfoList *qmp_query_cpu_props_info(Error **errp) return NULL; } - host_isar = kvm_arm_get_host_isar(); + host_isar = kvm_arm_get_host_isar(false); /* * Spin up a scratch "host" vCPU object and walk its QOM properties. diff --git a/target/arm/cpu64.c b/target/arm/cpu64.c index f41bcf2e81..c7556bfd52 100644 --- a/target/arm/cpu64.c +++ b/target/arm/cpu64.c @@ -1050,7 +1050,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); #ifndef CONFIG_USER_ONLY diff --git a/target/arm/kvm-stub.c b/target/arm/kvm-stub.c index eb8fdda236..1a713cb904 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(); } @@ -120,7 +120,7 @@ char *kvm_print_register_name(uint64_t regidx) g_assert_not_reached(); } -const ARMISARegisters *kvm_arm_get_host_isar(void) +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 b909a11dfd..f2635980eb 100644 --- a/target/arm/kvm.c +++ b/target/arm/kvm.c @@ -67,7 +67,7 @@ typedef struct ARMHostCPUFeatures { const char *dtb_compatible; } ARMHostCPUFeatures; -static ARMHostCPUFeatures arm_host_cpu_features; +static ARMHostCPUFeatures arm_host_cpu_features[2]; /** * kvm_arm_vcpu_init: @@ -363,8 +363,7 @@ static int get_host_cpu_idregs(int fd, ARMHostCPUFeatures *ahcf) } -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 @@ -404,7 +403,7 @@ 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; } @@ -553,7 +552,7 @@ 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); } } @@ -604,7 +603,7 @@ static int kvm_arm_get_writable_id_regs(uint64_t *idregmap) return 0; } -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; uint64_t *writable_map; @@ -629,11 +628,11 @@ void kvm_arm_set_cpu_features_from_host(ARMCPU *cpu) } g_free(writable_map); - 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) { /* @@ -644,18 +643,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) { - 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); } - return &arm_host_cpu_features.isar; + return &arm_host_cpu_features[with_el2].isar; } static bool kvm_no_adjvtime_get(Object *obj, Error **errp) @@ -1438,6 +1437,7 @@ static int kvm_arm_write_idregs_to_cpreg_list(ARMCPU *cpu) uint64_t writable_mask = sysregdesc->writable_mask; uint64_t desired = cpu->isar.idregs[i]; uint64_t previous, updated; + bool has_el2 = cpu->has_el2; uint64_t *cpreg; cpreg = kvm_arm_find_cpreg_ptr(cpu, idregs_sysreg_to_kvm_reg(sysreg)); @@ -1447,7 +1447,7 @@ static int kvm_arm_write_idregs_to_cpreg_list(ARMCPU *cpu) * snapshotted at probe time as the reference to check the vCPU ID * regs have the same value. */ - previous = cpreg ? *cpreg : arm_host_cpu_features.isar.idregs[i]; + previous = cpreg ? *cpreg : kvm_arm_get_host_isar(has_el2)->idregs[i]; if (previous == desired) { continue; @@ -2332,6 +2332,50 @@ bool kvm_arm_mte_supported(void) return kvm_check_extension(kvm_state, KVM_CAP_ARM_MTE); } +/* + * Rebase cpu->isar.idregs[] on top of new_isar given the initial_isar. + * + * For fields in cpu->isar.idregs[] that are same as initial_isar, update + * the field with the new_isar values. + */ +static void kvm_arm_rebase_host_idregs(ARMCPU *cpu, + const ARMISARegisters *new_isar, + const ARMISARegisters *initial_isar) +{ + for (int i = 0; i < NUM_ID_IDX; i++) { + ARM64SysReg *sysregdesc = &arm64_id_regs[i]; + + for (int j = 0; j < sysregdesc->fields_count; j++) { + const ARM64SysRegField *field = &sysregdesc->fields[j]; + uint64_t idreg_val = extract64(cpu->isar.idregs[i], + field->shift, field->length); + uint64_t initial_val = extract64(initial_isar->idregs[i], + field->shift, field->length); + uint64_t new_val = extract64(new_isar->idregs[i], + field->shift, field->length); + + if (idreg_val == initial_val && new_val != idreg_val) { + /* + * The cpu->isar.idregs[]'s field val is same as + * initial_isar.idregs[]'s field val, which was used to + * initialise it. Hence, override it with the new_isar[] + * value. User might have explicitly asked for some value + * which matched the host value without EL2 in this case, + * that value is lost. As rebase is needed in very specific + * case, we avoid that complication. + */ + cpu->isar.idregs[i] = deposit64(cpu->isar.idregs[i], + field->shift, field->length, + new_val); + + warn_report("KVM ID %s.%s value changed from 0x%016" + PRIx64 " to 0x%016" PRIx64, + sysregdesc->name, field->name, idreg_val, new_val); + } + } + } +} + QEMU_BUILD_BUG_ON(KVM_ARM64_SVE_VQ_MIN != 1); static int kvm_arm_sve_set_vls(ARMCPU *cpu) @@ -2450,11 +2494,24 @@ int kvm_arch_init_vcpu(CPUState *cs) if (ret) { return ret; } + + /* For -cpu host/max, rebase the idregs[] on top of cpregs[] */ + if ((object_dynamic_cast(OBJECT(cpu), TYPE_ARM_HOST_CPU) + || object_dynamic_cast(OBJECT(cpu), TYPE_ARM_MAX_CPU)) + && !cpu->has_el2 + && kvm_arm_el2_supported()) { + /* scratch vCPU is created with EL2-enabled for -cpu host */ + kvm_arm_rebase_host_idregs(cpu, + kvm_arm_get_host_isar(false), + kvm_arm_get_host_isar(true)); + } + /* overwrite ID reg fields with their updated property values */ ret = kvm_arm_write_idregs_to_cpreg_list(cpu); if (ret) { return ret; } + ret = write_list_to_kvmstate(cpu, KVM_PUT_FULL_STATE); if (!ret) { return -1; diff --git a/target/arm/kvm_arm.h b/target/arm/kvm_arm.h index 6409bec735..847efb1c8d 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: @@ -276,7 +277,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
