Writeback modified cpu->isar.idregs[] to KVM after the model is realized. Warn if modified bits are not writable by KVM. To facilitate this, add writable_mask to ArmIdReg struct and populate it from KVM_ARM_GET_REG_WRITABLE_MASKS during scratch VM creation.
Co-authored-by: Khushit Shah <[email protected]> Signed-off-by: Shaju Abraham <[email protected]> --- target/arm/cpu-idregs.c | 1 + target/arm/cpu-idregs.h | 1 + target/arm/kvm.c | 160 +++++++++++++++++++++++++++++++++++++++- target/arm/trace-events | 1 + 4 files changed, 162 insertions(+), 1 deletion(-) diff --git a/target/arm/cpu-idregs.c b/target/arm/cpu-idregs.c index 5ffdeb5f21..e8988b7392 100644 --- a/target/arm/cpu-idregs.c +++ b/target/arm/cpu-idregs.c @@ -68,6 +68,7 @@ .name = #reg, \ .fields = reg##_fields, \ .fields_count = ARRAY_SIZE(reg##_fields), \ + .writable_mask = 0, \ }, ArmIdReg arm_idregs[NUM_ID_IDX] = { diff --git a/target/arm/cpu-idregs.h b/target/arm/cpu-idregs.h index 0127bc0a95..eb0d8a1280 100644 --- a/target/arm/cpu-idregs.h +++ b/target/arm/cpu-idregs.h @@ -37,6 +37,7 @@ typedef struct ArmIdReg { const char *name; struct ArmIdRegField *fields; uint32_t fields_count; + uint64_t writable_mask; } ArmIdReg; /* Map short register names to canonical _EL1/_EL0 IDX values */ diff --git a/target/arm/kvm.c b/target/arm/kvm.c index 7d194ea112..dc64cfbeb6 100644 --- a/target/arm/kvm.c +++ b/target/arm/kvm.c @@ -28,6 +28,7 @@ #include "kvm_arm.h" #include "cpu.h" #include "cpu-sysregs.h" +#include "cpu-idregs.h" #include "trace.h" #include "internals.h" #include "hw/pci/pci.h" @@ -66,6 +67,12 @@ typedef struct ARMHostCPUFeatures { static ARMHostCPUFeatures arm_host_cpu_features; +#define DEF(NAME, OP0, OP1, CRN, CRM, OP2) [NAME##_IDX] = #NAME, +const char * const sysreg_names[NUM_ID_IDX] = { +#include "cpu-sysregs.h.inc" +}; +#undef DEF + /** * kvm_arm_vcpu_init: * @cpu: ARMCPU @@ -244,6 +251,63 @@ static int get_host_cpu_reg(int fd, ARMHostCPUFeatures *ahcf, return ret; } +static int get_host_cpu_idregs_all(int fd, ARMHostCPUFeatures *ahcf) +{ + int err = 0, i; + for (i = 0; i < NUM_ID_IDX; i++) { + /* Skip registers whose plumbing is not yet added. */ + if (!arm_idregs[i].name) { + continue; + } + + err |= get_host_cpu_reg(fd, ahcf, i); + } + return err; +} + +static int idregs_idx_to_kvm_idx(ARMIDRegisterIdx idx) +{ + ARMSysRegs sysreg = id_register_sysreg[idx]; + + return KVM_ARM_FEATURE_ID_RANGE_IDX( + (sysreg >> CP_REG_ARM64_SYSREG_OP0_SHIFT) & 0x3, + (sysreg >> CP_REG_ARM64_SYSREG_OP1_SHIFT) & 0x7, + (sysreg >> CP_REG_ARM64_SYSREG_CRN_SHIFT) & 0xf, + (sysreg >> CP_REG_ARM64_SYSREG_CRM_SHIFT) & 0xf, + (sysreg >> CP_REG_ARM64_SYSREG_OP2_SHIFT) & 0x7); +} + +static int get_writable_id_regs(int vmfd) +{ + int cap, ret, i; + uint64_t regs[KVM_ARM_FEATURE_ID_RANGE_SIZE] = { 0 }; + struct reg_mask_range range = { + .addr = (uint64_t)(uintptr_t)regs, + .range = KVM_ARM_FEATURE_ID_RANGE, + }; + + cap = ioctl(vmfd, KVM_CHECK_EXTENSION, + KVM_CAP_ARM_SUPPORTED_REG_MASK_RANGES); + if (cap <= 0 || !(cap & (1 << KVM_ARM_FEATURE_ID_RANGE))) { + return -ENOSYS; + } + + ret = ioctl(vmfd, KVM_ARM_GET_REG_WRITABLE_MASKS, &range); + if (ret) { + return -errno; + } + + for (i = 0; i < NUM_ID_IDX; i++) { + int kidx = idregs_idx_to_kvm_idx(i); + + if (kidx < 0 || kidx >= KVM_ARM_FEATURE_ID_RANGE_SIZE) { + continue; + } + arm_idregs[i].writable_mask = regs[kidx]; + } + return 0; +} + static uint32_t kvm_arm_sve_get_vls(int fd) { uint64_t vls[KVM_ARM64_SVE_VLS_WORDS]; @@ -455,6 +519,22 @@ static void kvm_arm_get_host_cpu_features(ARMHostCPUFeatures *ahcf) arm_host_cpu_features.sve_vq_supported = kvm_arm_sve_get_vls(fd); } } + /* + * Try to read all the ID registers. KVM does not yet support it + * for all registers, hence ignore the errors. + */ + get_host_cpu_idregs_all(fd, ahcf); + + { + int wret = get_writable_id_regs(fdarray[1]); + if (wret) { + warn_report("KVM_ARM_GET_REG_WRITABLE_MASKS" + "%s: %s", + wret == -ENOSYS ? " unsupported" + : " failed", + strerror(-wret)); + } + } kvm_arm_destroy_scratch_host_vcpu(fdarray); @@ -1080,6 +1160,71 @@ void kvm_arm_cpu_pre_save(ARMCPU *cpu) } } +/* same as kvm_arm_get_cpreg_ptr() but can return NULL. */ +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); + if (!res) { + return NULL; + } + return &cpu->cpreg_values[res - cpu->cpreg_indexes]; +} + +static void kvm_arm_writeback_idregs(ARMCPU *cpu) +{ + for (int i = 0; i < NUM_ID_IDX; i++) { + uint64_t kvm_reg = idregs_sysreg_to_kvm_reg(id_register_sysreg[i]); + uint64_t *cpreg = kvm_arm_find_cpreg_ptr(cpu, kvm_reg); + const char *name = arm_idregs[i].name; + uint64_t writable_mask, previous, desired, diff; + + if (!cpreg) { + warn_report("KVM does not expose ID register slot %d " + "(kvm_reg=0x%" PRIx64 "), %s; skipping writeback", + i, kvm_reg, sysreg_names[i]); + continue; + } + + if (!name) { + /* No field table, don't push back. */ + warn_report("ID register slot %d " + "(kvm_reg=0x%" PRIx64 "), %s: " + "no field table in cpu-idregs.inc.h", + i, kvm_reg, sysreg_names[i]); + continue; + } + + writable_mask = arm_idregs[i].writable_mask; + previous = *cpreg; + desired = cpu->isar.idregs[i]; + diff = previous ^ desired; + + if (!diff) { + continue; + } + + if (diff & ~writable_mask) { + warn_report("%s: non-writable bits differ: " + "kvm=0x%016" PRIx64 + " desired=0x%016" PRIx64 + " diff=0x%016" PRIx64 + " writable=0x%016" PRIx64, + name, previous, desired, + diff & ~writable_mask, + writable_mask); + } + + if (diff & writable_mask) { + *cpreg = (previous & ~writable_mask) | + (desired & writable_mask); + trace_kvm_arm_writeback_idreg(name, previous, *cpreg); + } + } +} + bool kvm_arm_cpu_post_load(ARMCPU *cpu) { if (!write_list_to_kvmstate(cpu, KVM_PUT_FULL_STATE)) { @@ -1116,6 +1261,10 @@ void kvm_arm_reset_vcpu(ARMCPU *cpu) fprintf(stderr, "write_kvmstate_to_list failed\n"); abort(); } + + /* Re-apply named-model ID register overrides after KVM_ARM_VCPU_INIT. */ + kvm_arm_writeback_idregs(cpu); + /* * Sync the reset values also into the CPUState. This is necessary * because the next thing we do will be a kvm_arch_put_registers() @@ -2051,7 +2200,16 @@ int kvm_arch_init_vcpu(CPUState *cs) } cpu->mp_affinity = mpidr & ARM64_AFFINITY_MASK; - return kvm_arm_init_cpreg_list(cpu); + ret = kvm_arm_init_cpreg_list(cpu); + if (ret) { + return ret; + } + + /* Apply named-model ID register overrides on top of KVM's defaults. */ + kvm_arm_writeback_idregs(cpu); + write_list_to_kvmstate(cpu, KVM_PUT_FULL_STATE); + + return 0; } int kvm_arch_destroy_vcpu(CPUState *cs) diff --git a/target/arm/trace-events b/target/arm/trace-events index 8502fb3265..975236b24f 100644 --- a/target/arm/trace-events +++ b/target/arm/trace-events @@ -13,6 +13,7 @@ arm_gt_update_irq(int timer, int irqstate) "gt_update_irq: timer %d irqstate %d" # kvm.c kvm_arm_fixup_msi_route(uint64_t iova, uint64_t gpa) "MSI iova = 0x%"PRIx64" is translated into 0x%"PRIx64 +kvm_arm_writeback_idreg(const char *name, uint64_t previous, uint64_t desired) "%s overwrite 0x%"PRIx64" with 0x%"PRIx64 # cpu.c arm_cpu_reset(uint64_t mp_aff) "cpu %" PRIu64 -- 2.52.0
