Now that all ID register fields are exposed as properties, refactor kvm_arm_writable_idregs_to_cpreg_list() as kvm_arm_write_idregs_to_cpreg_list() and process each field individually. kvm_arm_writable_idregs_to_cpreg_list() assumed that only writable fields were exposed as SYSREG_ properties.
Copy only fields advertised as writable by KVM instead of replacing the whole register. For registers absent from the KVM cpreg list, compare the requested value against the host value captured during probing. Return an error when a non-writable field differs from the host value, failing vCPU init. Signed-off-by: Khushit Shah <[email protected]> --- target/arm/cpu-idregs.c | 13 +++++++ target/arm/cpu-idregs.h | 2 ++ target/arm/kvm.c | 79 +++++++++++++++++++++++++++++++++-------- target/arm/trace-events | 2 +- 4 files changed, 80 insertions(+), 16 deletions(-) diff --git a/target/arm/cpu-idregs.c b/target/arm/cpu-idregs.c index 199d593153..d58b8ed063 100644 --- a/target/arm/cpu-idregs.c +++ b/target/arm/cpu-idregs.c @@ -7,6 +7,7 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ #include "qemu/osdep.h" +#include "qemu/bitops.h" #include "qemu/error-report.h" #include "qapi/error.h" #include "cpu.h" @@ -94,3 +95,15 @@ ARM64SysReg arm64_id_regs[NUM_ID_IDX] = { #include "cpu-idregs.h.inc" }; +uint64_t arm_get_field_mask(const ARM64SysRegField *field) +{ + return MAKE_64BIT_MASK(field->shift, field->length); +} + +bool arm_field_is_writable(const ARM64SysRegField *field) +{ + uint64_t field_mask = arm_get_field_mask(field); + + return (arm64_id_regs[field->index].writable_mask & field_mask) + == field_mask; +} diff --git a/target/arm/cpu-idregs.h b/target/arm/cpu-idregs.h index 98c7a7518f..aab1a79db1 100644 --- a/target/arm/cpu-idregs.h +++ b/target/arm/cpu-idregs.h @@ -33,4 +33,6 @@ typedef struct ARM64SysReg { */ extern ARM64SysReg arm64_id_regs[NUM_ID_IDX]; +uint64_t arm_get_field_mask(const ARM64SysRegField *field); +bool arm_field_is_writable(const ARM64SysRegField *field); #endif diff --git a/target/arm/kvm.c b/target/arm/kvm.c index 07df6626d4..2d94b3a884 100644 --- a/target/arm/kvm.c +++ b/target/arm/kvm.c @@ -1006,6 +1006,16 @@ static uint64_t *kvm_arm_get_cpreg_ptr(ARMCPU *cpu, uint64_t regidx) return &cpu->cpreg_values[res - cpu->cpreg_indexes]; } +/* Like kvm_arm_get_cpreg_ptr, but returns NULL if the register is not found. */ +static uint64_t *kvm_arm_find_cpreg_ptr(ARMCPU *cpu, uint64_t regidx) +{ + uint64_t *res; + res = bsearch(®idx, cpu->cpreg_indexes, cpu->cpreg_array_len, + sizeof(uint64_t), compare_u64); + + return res ? &cpu->cpreg_values[res - cpu->cpreg_indexes] : NULL; +} + /** * kvm_arm_reg_syncs_via_cpreg_list: * @regidx: KVM register index @@ -1308,31 +1318,67 @@ bool kvm_arm_cpu_post_load(ARMCPU *cpu) } /* - * Copy writable ID regs from isar.idregs[] to cpreg_list - * in case their value differs from the original init cpreg value + * Copy writable ID reg fields from isar.idregs[] into the KVM cpreg list, + * so the subsequent write_list_to_kvmstate() pushes them to KVM. + * Only writable fields are copied. + * Returns -1 if any vCPU's ID reg value differs from the host value and the + * field is not writable. */ -static void kvm_arm_writable_idregs_to_cpreg_list(ARMCPU *cpu) +static int kvm_arm_write_idregs_to_cpreg_list(ARMCPU *cpu) { for (int i = 0; i < NUM_ID_IDX; i++) { ARM64SysReg *sysregdesc = &arm64_id_regs[i]; ARMSysRegs sysreg = id_register_sysreg[i]; - uint64_t previous, new; + uint64_t writable_mask = sysregdesc->writable_mask; + uint64_t desired = cpu->isar.idregs[i]; + uint64_t previous, updated; uint64_t *cpreg; - if (!sysregdesc->writable_mask) { + cpreg = kvm_arm_find_cpreg_ptr(cpu, idregs_sysreg_to_kvm_reg(sysreg)); + /* + * Registers such as DCZID_EL0 are not exposed through the cpreg + * list and are therefore not writable. Use the host value + * 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]; + + if (previous == desired) { continue; } - cpreg = kvm_arm_get_cpreg_ptr(cpu, idregs_sysreg_to_kvm_reg(sysreg)); - previous = *cpreg; - new = cpu->isar.idregs[i]; + for (int j = 0; j < sysregdesc->fields_count; j++) { + const ARM64SysRegField *field = &sysregdesc->fields[j]; + uint64_t field_mask = arm_get_field_mask(field); + uint64_t prev_val = previous & field_mask; + uint64_t new_val = desired & field_mask; + + if (prev_val == new_val) { + continue; + } + + if (!arm_field_is_writable(field)) { + error_report("%s.%s is not writable: host=0x%" PRIx64 + ", requested=0x%" PRIx64, + sysregdesc->name, field->name, + prev_val >> field->shift, new_val >> field->shift); + return -1; + } + } + + if (!cpreg || !writable_mask) { + continue; + } - if (previous != new) { - *cpreg = new; - trace_kvm_arm_writable_idregs_to_cpreg_list(sysregdesc->name, - previous, new); - } + updated = (previous & ~writable_mask) | (desired & writable_mask); + if (updated != previous) { + *cpreg = updated; + trace_kvm_arm_write_idregs_to_cpreg_list(sysregdesc->name, + previous, updated); + } } + + return 0; } void kvm_arm_reset_vcpu(ARMCPU *cpu) @@ -2293,8 +2339,11 @@ int kvm_arch_init_vcpu(CPUState *cs) if (ret) { return ret; } - /* overwrite writable ID regs with their updated property values */ - kvm_arm_writable_idregs_to_cpreg_list(cpu); + /* 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/trace-events b/target/arm/trace-events index 2bb7766f7f..7b54e34ad9 100644 --- a/target/arm/trace-events +++ b/target/arm/trace-events @@ -15,7 +15,7 @@ arm_gt_update_irq(int timer, int irqstate) "gt_update_irq: timer %d irqstate %d" kvm_arm_fixup_msi_route(uint64_t iova, uint64_t gpa) "MSI iova = 0x%"PRIx64" is translated into 0x%"PRIx64 get_host_cpu_idregs(const char *name, uint64_t value) "scratch vcpu host value for %s is 0x%"PRIx64 get_host_cpu_idreg_not_found(const char *name) "host %s register not readable from KVM" -kvm_arm_writable_idregs_to_cpreg_list(const char *name, uint64_t previous, uint64_t new) "%s overwrite default 0x%"PRIx64" with 0x%"PRIx64 +kvm_arm_write_idregs_to_cpreg_list(const char *name, uint64_t previous, uint64_t new) "%s overwrite default 0x%"PRIx64" with 0x%"PRIx64 kvm_idreg_write_scratch_vcpu(const char *name, uint64_t oldfv, uint64_t newfv, uint64_t actualfv) "successful dry-run for %s old=0x%"PRIx64" new=0x%"PRIx64" actual=0x%"PRIx64 # cpu64.c -- 2.52.0
