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(&regidx, 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


Reply via email to