Add kvm_arm_field_get_supported_values() which, for a given ID register
field, builds the set of values KVM allows it to take on the given host.

Non-writable fields are pinned to the host value. Writable fields follow
their per-field constraints. This function must be updated when KVM
starts exposing a new writable field that is not lower-safe; hence the
special handling of TGranX_2/SpecSEI/L1Ip/MIDR/REVIDR/AIDR.

Signed-off-by: Khushit Shah <[email protected]>

--------
Note:
- We should have an authoritative static list of "writable" fields in
  QEMU.
---
 target/arm/kvm-stub.c |  7 +++
 target/arm/kvm.c      | 99 +++++++++++++++++++++++++++++++++++++++++++
 target/arm/kvm_arm.h  | 28 ++++++++++++
 3 files changed, 134 insertions(+)

diff --git a/target/arm/kvm-stub.c b/target/arm/kvm-stub.c
index d263adc921..c9cd47bf47 100644
--- a/target/arm/kvm-stub.c
+++ b/target/arm/kvm-stub.c
@@ -144,3 +144,10 @@ void kvm_arm_destroy_scratch_host_vcpu(int *fdarray)
 {
     g_assert_not_reached();
 }
+
+void kvm_arm_field_get_supported_values(const ARM64SysRegField *field,
+                                        const ARMISARegisters *host_isar,
+                                        ArmFieldValueSet **value_set)
+{
+    g_assert_not_reached();
+}
diff --git a/target/arm/kvm.c b/target/arm/kvm.c
index 6f704c0509..77b1bcdf55 100644
--- a/target/arm/kvm.c
+++ b/target/arm/kvm.c
@@ -1324,6 +1324,105 @@ bool kvm_arm_cpu_post_load(ARMCPU *cpu)
     return true;
 }
 
+static bool arm_field_is_signed(const ARM64SysRegField *field)
+{
+    return field_matches(field, ID_AA64MMFR0_EL1_IDX, "TGran4")    ||
+           field_matches(field, ID_AA64MMFR0_EL1_IDX, "TGran64")   ||
+           field_matches(field, ID_MMFR0_EL1_IDX, "InnerShr")      ||
+           field_matches(field, ID_MMFR0_EL1_IDX, "OuterShr")      ||
+           field_matches(field, ID_AA64DFR0_EL1_IDX, "DoubleLock") ||
+           field_matches(field, ID_AA64DFR0_EL1_IDX, "PMUVer")     ||
+           field_matches(field, ID_DFR0_EL1_IDX, "PerfMon")        ||
+           field_matches(field, ID_AA64PFR1_EL1_IDX, "MTE_frac")   ||
+           field_matches(field, ID_AA64MMFR4_EL1_IDX, "E2H0")      ||
+           field_matches(field, ID_DFR1_EL1_IDX, "MTPMU")          ||
+           field_matches(field, ID_AA64PFR0_EL1_IDX, "FP")         ||
+           field_matches(field, ID_AA64PFR0_EL1_IDX, "AdvSIMD");
+}
+
+static void ranges_add(GArray *ranges, uint64_t min, uint64_t max)
+{
+    ArmFieldRange r = { .min = min, .max = max };
+    g_array_append_val(ranges, r);
+}
+
+void kvm_arm_field_get_supported_values(const ARM64SysRegField *field,
+                                        const ARMISARegisters *host_isar,
+                                        ArmFieldValueSet **value_set)
+{
+    bool is_signed = arm_field_is_signed(field);
+    uint64_t host = extract64(host_isar->idregs[field->index],
+                              field->shift, field->length);
+    GArray *ranges = g_array_new(false, false, sizeof(ArmFieldRange));
+
+    /* A non-writable field can only ever hold the host value. */
+    if (!arm_field_is_writable(field)) {
+        ranges_add(ranges, host, host);
+        goto done;
+    }
+
+    if (field_matches(field, ID_AA64MMFR0_EL1_IDX, "TGran4_2")  ||
+        field_matches(field, ID_AA64MMFR0_EL1_IDX, "TGran16_2") ||
+        field_matches(field, ID_AA64MMFR0_EL1_IDX, "TGran64_2")) {
+        /* Either support the host value or the "off" value */
+        ranges_add(ranges, host, host);
+        if (host != 1) { /* 1 = "off" */
+            ranges_add(ranges, 1, 1);
+        }
+    } else if (field_matches(field, CTR_EL0_IDX, "L1Ip")) {
+        /* Only safe to downgrade to VIPT, other values are reserved. */
+        ranges_add(ranges, host, host);
+        if (host != 2) { /* 2 = "VIPT" */
+            ranges_add(ranges, 2, 2);
+        }
+    } else if (field_matches(field, ID_AA64MMFR1_EL1_IDX, "SpecSEI") ||
+               field_matches(field, ID_MMFR4_EL1_IDX, "SpecSEI")) {
+        /* It is safe to upgrade SpecSEI to 1, other values are reserved. */
+        ranges_add(ranges, host, host);
+        if (host != 1) {
+            ranges_add(ranges, 1, 1);
+        }
+    } else if (field->index == MIDR_EL1_IDX ||
+               field->index == REVIDR_EL1_IDX ||
+               field->index == AIDR_EL1_IDX) {
+        /*
+         * No restriction on value that can be set for implementation ID
+         * registers fields.
+         */
+        uint64_t max = 0;
+        if (field->length == 64) {
+            max = ~0ULL;
+        } else {
+            max = (1ULL << field->length) - 1;
+        }
+        ranges_add(ranges, 0, max);
+    } else {
+        /*
+         * After handling the special cases, other writable fields are
+         * either lower-safe or signed lower-safe.
+         */
+        if (field->arch_vals_count) {
+            for (uint32_t i = 0; i < field->arch_vals_count; i++) {
+                uint64_t av = field->arch_vals[i];
+                int64_t v = is_signed ?
+                    sextract64(av, 0, field->length) : (int64_t)av;
+                int64_t hv = is_signed ?
+                    sextract64(host, 0, field->length) : (int64_t)host;
+                if (v <= hv) {
+                    ranges_add(ranges, av, av);
+                }
+            }
+        } else {
+            g_assert(!is_signed); /* No signed field with no arch vals */
+            ranges_add(ranges, 0, host);
+        }
+    }
+done:
+    *value_set = g_new0(ArmFieldValueSet, 1);
+    (*value_set)->n_ranges = ranges->len;
+    (*value_set)->ranges = (ArmFieldRange *)g_array_free(ranges, false);
+}
+
 static bool arm_field_skip_writeback_always(const ARM64SysRegField *field)
 {
     /*
diff --git a/target/arm/kvm_arm.h b/target/arm/kvm_arm.h
index 4e563fab96..9481e56412 100644
--- a/target/arm/kvm_arm.h
+++ b/target/arm/kvm_arm.h
@@ -175,6 +175,34 @@ int kvm_arm_read_sys_reg64(int fd, uint64_t *val,
  */
 void kvm_arm_add_vcpu_properties(ARMCPU *cpu);
 
+typedef struct ARM64SysReg ARM64SysReg;
+typedef struct ARM64SysRegField ARM64SysRegField;
+typedef struct ARMISARegisters ARMISARegisters;
+
+typedef struct ArmFieldRange {
+    uint64_t min;
+    uint64_t max;
+} ArmFieldRange;
+
+typedef struct ArmFieldValueSet {
+    ArmFieldRange *ranges;
+    size_t n_ranges;
+} ArmFieldValueSet;
+
+/**
+ * kvm_arm_field_get_supported_values:
+ * @field: The field to get the supported values for
+ * @host_isar: The host ISAR registers
+ * @value_set: The set of supported values for the @field
+ *
+ * Will be allocated and filled in with the supported values for the @field
+ * based on the host_isar and whether the field is writable or not.
+ * The caller must free the value_set.
+ */
+void kvm_arm_field_get_supported_values(const ARM64SysRegField *field,
+                                        const ARMISARegisters *host_isar,
+                                        ArmFieldValueSet **value_set);
+
 /**
  * kvm_arm_steal_time_finalize:
  * @cpu: ARMCPU for which to finalize kvm-steal-time
-- 
2.52.0


Reply via email to