[RFC PATCH v3 14/19] target/arm/kvm: compute supported values for ID register fields

Khushit Shah <[email protected]>
Newsgroups org.nongnu.qemu-arm,dev.linux.lists.kvmarm,org.nongnu.qemu-devel
Message-ID <[email protected]>
Add arm_field_get_supported_values() which, for a given ID register
field, builds the set of values KVM allows it to take on the live host.

Non-writable fields are pinned to the host value. Writable fields follow
their per field constraints. This function likely needs change 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.

Cross-field constraints are not modelled; for example ID_AA64ZFR0_EL1
is gated by SVE. Reproducing every inter-field dependency in QEMU
would only duplicate KVM's logic and drift out of sync with it.

Signed-off-by: Khushit Shah <[email protected]>
---
 target/arm/kvm.c     | 99 ++++++++++++++++++++++++++++++++++++++++++++
 target/arm/kvm_arm.h | 26 ++++++++++++
 2 files changed, 125 insertions(+)

diff --git a/target/arm/kvm.c b/target/arm/kvm.c
index c38b99cfce..8f452f9570 100644
--- a/target/arm/kvm.c
+++ b/target/arm/kvm.c
@@ -1262,6 +1262,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 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].value;
+                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 133a026036..9f15c91c4e 100644
--- a/target/arm/kvm_arm.h
+++ b/target/arm/kvm_arm.h
@@ -143,6 +143,32 @@ void kvm_arm_set_cpu_features_from_host(ARMCPU *cpu);
 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;
+
+/**
+ * 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 arm_field_get_supported_values(const ARM64SysRegField *field,
+                                    const ARMISARegisters *host_isar,
+                                    ArmFieldValueSet **value_set);
 
 /**
  * kvm_arm_steal_time_finalize:
-- 
2.52.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.