[PATCH v9 5/5] KVM: arm64: Refactor writings for PMUVer/CSV2/CSV3
Suraj Jitindar Singh
sjitindarsingh at gmail.com
Thu Jun 1 18:03:30 PDT 2023
Hi,
With the patch set you posted I get some kvm unit tests failures due to
being unable to update register values from userspace. I propose the
following patch as a solution:
[PATCH 1/2] KVM: arm64: Update id_reg limit value based on per vcpu
flags
There are multiple features the availability of which is
enabled/disabled
and tracked on a per vcpu level in vcpu->arch.flagset e.g. sve,
ptrauth,
and pmu. While the vm wide value of the id regs which represent the
availability of these features is stored in the id_regs kvm struct
their
value needs to be manipulated on a per vcpu basis. This is done at read
time in kvm_arm_read_id_reg().
The value of these per vcpu flags needs to be factored in when
calculating
the id_reg limit value in check_features() as otherwise we can run into
the
following scenario.
[ running on cpu which supports sve ]
1. AA64PFR0.SVE set in id_reg by kvm_arm_init_id_regs() (cpu supports
it
and so is set in value returned from read_sanitised_ftr_reg())
2. vcpus created without sve feature enabled
3. vmm reads AA64PFR0 and attempts to write the same value back
(writing the same value back is allowed)
4. write fails in check_features() as limit has AA64PFR0.SVE set
however it
is not set in the value being written and although a lower value is
allowed for this feature it is not in the mask of bits which can be
modified and so much match exactly.
Thus add a step in check_features() to update the limit returned from
id_reg->reset() with the per vcpu features which may have been
enabled/disabled at vcpu creation time after the id_regs were
initialised.
Split this update into a new function named kvm_arm_update_id_reg() so
it
can be called from check_features() as well as kvm_arm_read_id_reg() to
dedup code.
While we're here there are features which are masked in
kvm_arm_update_id_reg() which cannot change through out a vms
lifecycle.
Thus rather than masking them each time the register is read, mask them
at
id_reg init time so that the value in the kvm id_reg reflects the state
of
support for that feature.
Move masking of AA64PFR0_EL1.GIC and AA64PFR0_EL1.AMU into
read_sanitised_id_aa64pfr0_el1().
Create read_sanitised_id_aa64pfr1_el1() and mask AA64PFR1_EL1.SME.
Create read_sanitised_id_[mmfr4|aa64mmfr2] and mask CCIDX.
Finally remove set_id_aa64pfr0_el1() as all it does is mask
AA64PFR0_EL1_CS[2|3]. The limit for these fields is already set
according
to cpu support in read_sanitised_id_aa64pfr0_el1() and then checked
when
writing the register in check_features() as such there is no need to
perform the check twice.
Signed-off-by: Suraj Jitindar Singh <surajjs at amazon.com>
---
arch/arm64/kvm/sys_regs.c | 113 ++++++++++++++++++++++++--------------
1 file changed, 73 insertions(+), 40 deletions(-)
diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
index bec02ba45ee7..ca793cd692fe 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -42,6 +42,7 @@
*/
static int set_id_reg(struct kvm_vcpu *vcpu, const struct sys_reg_desc
*rd, u64 val);
+static u64 kvm_arm_update_id_reg(const struct kvm_vcpu *vcpu, u32 id,
u64 val);
static u64 kvm_arm_read_id_reg(const struct kvm_vcpu *vcpu, u32 id);
static u64 sys_reg_to_index(const struct sys_reg_desc *reg);
@@ -1241,6 +1242,7 @@ static int arm64_check_features(struct kvm_vcpu
*vcpu,
/* For hidden and unallocated idregs without reset, only val =
0 is allowed. */
if (rd->reset) {
limit = rd->reset(vcpu, rd);
+ limit = kvm_arm_update_id_reg(vcpu, id, limit);
ftr_reg = get_arm64_ftr_reg(id);
if (!ftr_reg)
return -EINVAL;
@@ -1317,24 +1319,17 @@ static u64
general_read_kvm_sanitised_reg(struct kvm_vcpu *vcpu, const struct sy
return read_sanitised_ftr_reg(reg_to_encoding(rd));
}
-static u64 kvm_arm_read_id_reg(const struct kvm_vcpu *vcpu, u32 id)
+/* Provide an updated value for an ID register based on per vcpu flags
*/
+static u64 kvm_arm_update_id_reg(const struct kvm_vcpu *vcpu, u32 id,
u64 val)
{
- u64 val = IDREG(vcpu->kvm, id);
-
switch (id) {
case SYS_ID_AA64PFR0_EL1:
if (!vcpu_has_sve(vcpu))
val &=
~ARM64_FEATURE_MASK(ID_AA64PFR0_EL1_SVE);
- if (kvm_vgic_global_state.type == VGIC_V3) {
- val &=
~ARM64_FEATURE_MASK(ID_AA64PFR0_EL1_GIC);
- val |=
FIELD_PREP(ARM64_FEATURE_MASK(ID_AA64PFR0_EL1_GIC), 1);
- }
break;
case SYS_ID_AA64PFR1_EL1:
if (!kvm_has_mte(vcpu->kvm))
val &=
~ARM64_FEATURE_MASK(ID_AA64PFR1_EL1_MTE);
-
- val &= ~ARM64_FEATURE_MASK(ID_AA64PFR1_EL1_SME);
break;
case SYS_ID_AA64ISAR1_EL1:
if (!vcpu_has_ptrauth(vcpu))
@@ -1347,8 +1342,6 @@ static u64 kvm_arm_read_id_reg(const struct
kvm_vcpu *vcpu, u32 id)
if (!vcpu_has_ptrauth(vcpu))
val &=
~(ARM64_FEATURE_MASK(ID_AA64ISAR2_EL1_APA3) |
ARM64_FEATURE_MASK(ID_AA64ISAR2_EL1_GPA3));
- if (!cpus_have_final_cap(ARM64_HAS_WFXT))
- val &=
~ARM64_FEATURE_MASK(ID_AA64ISAR2_EL1_WFxT);
break;
case SYS_ID_AA64DFR0_EL1:
/* Set PMUver to the required version */
@@ -1361,17 +1354,18 @@ static u64 kvm_arm_read_id_reg(const struct
kvm_vcpu *vcpu, u32 id)
val |=
FIELD_PREP(ARM64_FEATURE_MASK(ID_DFR0_EL1_PerfMon),
pmuver_to_perfmon(vcpu_pmuver(vcpu)));
break;
- case SYS_ID_AA64MMFR2_EL1:
- val &= ~ID_AA64MMFR2_EL1_CCIDX_MASK;
- break;
- case SYS_ID_MMFR4_EL1:
- val &= ~ARM64_FEATURE_MASK(ID_MMFR4_EL1_CCIDX);
- break;
}
return val;
}
+static u64 kvm_arm_read_id_reg(const struct kvm_vcpu *vcpu, u32 id)
+{
+ u64 val = IDREG(vcpu->kvm, id);
+
+ return kvm_arm_update_id_reg(vcpu, id, val);
+}
+
/* Read a sanitised cpufeature ID register by sys_reg_desc */
static u64 read_id_reg(const struct kvm_vcpu *vcpu, struct
sys_reg_desc const *r)
{
@@ -1477,34 +1471,28 @@ static u64
read_sanitised_id_aa64pfr0_el1(struct kvm_vcpu *vcpu,
val |=
FIELD_PREP(ARM64_FEATURE_MASK(ID_AA64PFR0_EL1_CSV3), 1);
}
+ if (kvm_vgic_global_state.type == VGIC_V3) {
+ val &= ~ARM64_FEATURE_MASK(ID_AA64PFR0_EL1_GIC);
+ val |=
FIELD_PREP(ARM64_FEATURE_MASK(ID_AA64PFR0_EL1_GIC), 1);
+ }
+
val &= ~ARM64_FEATURE_MASK(ID_AA64PFR0_EL1_AMU);
return val;
}
-static int set_id_aa64pfr0_el1(struct kvm_vcpu *vcpu,
- const struct sys_reg_desc *rd,
- u64 val)
+static u64 read_sanitised_id_aa64pfr1_el1(struct kvm_vcpu *vcpu,
+ const struct sys_reg_desc
*rd)
{
- u8 csv2, csv3;
+ u64 val;
+ u32 id = reg_to_encoding(rd);
- /*
- * Allow AA64PFR0_EL1.CSV2 to be set from userspace as long as
- * it doesn't promise more than what is actually provided (the
- * guest could otherwise be covered in ectoplasmic residue).
- */
- csv2 = cpuid_feature_extract_unsigned_field(val,
ID_AA64PFR0_EL1_CSV2_SHIFT);
- if (csv2 > 1 ||
- (csv2 && arm64_get_spectre_v2_state() !=
SPECTRE_UNAFFECTED))
- return -EINVAL;
+ val = read_sanitised_ftr_reg(id);
- /* Same thing for CSV3 */
- csv3 = cpuid_feature_extract_unsigned_field(val,
ID_AA64PFR0_EL1_CSV3_SHIFT);
- if (csv3 > 1 ||
- (csv3 && arm64_get_meltdown_state() !=
SPECTRE_UNAFFECTED))
- return -EINVAL;
+ /* SME is not supported */
+ val &= ~ARM64_FEATURE_MASK(ID_AA64PFR1_EL1_SME);
- return set_id_reg(vcpu, rd, val);
+ return val;
}
static u64 read_sanitised_id_aa64dfr0_el1(struct kvm_vcpu *vcpu,
@@ -1680,6 +1668,34 @@ static int set_id_dfr0_el1(struct kvm_vcpu
*vcpu,
return ret;
}
+static u64 read_sanitised_id_mmfr4_el1(struct kvm_vcpu *vcpu,
+ const struct sys_reg_desc *rd)
+{
+ u64 val;
+ u32 id = reg_to_encoding(rd);
+
+ val = read_sanitised_ftr_reg(id);
+
+ /* CCIDX is not supported */
+ val &= ~ARM64_FEATURE_MASK(ID_MMFR4_EL1_CCIDX);
+
+ return val;
+}
+
+static u64 read_sanitised_id_aa64mmfr2_el1(struct kvm_vcpu *vcpu,
+ const struct sys_reg_desc
*rd)
+{
+ u64 val;
+ u32 id = reg_to_encoding(rd);
+
+ val = read_sanitised_ftr_reg(id);
+
+ /* CCIDX is not supported */
+ val &= ~ID_AA64MMFR2_EL1_CCIDX_MASK;
+
+ return val;
+}
+
/*
* cpufeature ID register user accessors
*
@@ -2089,7 +2105,14 @@ static const struct sys_reg_desc sys_reg_descs[]
= {
AA32_ID_SANITISED(ID_ISAR3_EL1),
AA32_ID_SANITISED(ID_ISAR4_EL1),
AA32_ID_SANITISED(ID_ISAR5_EL1),
- AA32_ID_SANITISED(ID_MMFR4_EL1),
+ { SYS_DESC(SYS_ID_MMFR4_EL1),
+ .access = access_id_reg,
+ .get_user = get_id_reg,
+ .set_user = set_id_reg,
+ .visibility = aa32_id_visibility,
+ .reset = read_sanitised_id_mmfr4_el1,
+ .val = 0, },
+ ID_HIDDEN(ID_AFR0_EL1),
AA32_ID_SANITISED(ID_ISAR6_EL1),
/* CRm=3 */
@@ -2107,10 +2130,15 @@ static const struct sys_reg_desc
sys_reg_descs[] = {
{ SYS_DESC(SYS_ID_AA64PFR0_EL1),
.access = access_id_reg,
.get_user = get_id_reg,
- .set_user = set_id_aa64pfr0_el1,
+ .set_user = set_id_reg,
.reset = read_sanitised_id_aa64pfr0_el1,
.val = ID_AA64PFR0_EL1_CSV2_MASK |
ID_AA64PFR0_EL1_CSV3_MASK, },
- ID_SANITISED(ID_AA64PFR1_EL1),
+ { SYS_DESC(SYS_ID_AA64PFR1_EL1),
+ .access = access_id_reg,
+ .get_user = get_id_reg,
+ .set_user = set_id_reg,
+ .reset = read_sanitised_id_aa64pfr1_el1,
+ .val = 0, },
ID_UNALLOCATED(4,2),
ID_UNALLOCATED(4,3),
ID_SANITISED(ID_AA64ZFR0_EL1),
@@ -2146,7 +2174,12 @@ static const struct sys_reg_desc sys_reg_descs[]
= {
/* CRm=7 */
ID_SANITISED(ID_AA64MMFR0_EL1),
ID_SANITISED(ID_AA64MMFR1_EL1),
- ID_SANITISED(ID_AA64MMFR2_EL1),
+ { SYS_DESC(SYS_ID_AA64MMFR2_EL1),
+ .access = access_id_reg,
+ .get_user = get_id_reg,
+ .set_user = set_id_reg,
+ .reset = read_sanitised_id_aa64mmfr2_el1,
+ .val = 0, },
ID_UNALLOCATED(7,3),
ID_UNALLOCATED(7,4),
ID_UNALLOCATED(7,5),
More information about the linux-arm-kernel
mailing list