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
