mirror of
https://github.com/torvalds/linux.git
synced 2026-09-22 04:34:03 +02:00
KVM: arm64: Expose PMMIR_EL1.SLOTS under strict PMUv3 UAPI
Introduce a new field pmmir_slots in struct kvm_arch to store PMMIR_EL1.SLOTS. It only saves the actual hardware PMU value when the VMM explicitly selects a PMU under KVM_ARM_VCPU_PMU_V3_STRICT. Otherwise, it stays 0 after allocation. Use this field to implement guest access, userspace get, and userspace set for PMMIR_EL1: - access_pmmir(): uses the value in kvm->arch.pmmir_slots directly. If the VMM selected a PMU and KVM_ARM_VCPU_PMU_V3_STRICT is set, the guest can correctly read the underlying core's SLOTS. Otherwise, it continues to read 0 since the true SLOTS value can be nondeterministic. - get_pmmir(): same as access_pmmir(). - set_pmmir(): only the SLOTS field is writable; a value setting any other bit is rejected with -EINVAL, since get_pmmir() returns SLOTS zero-extended. A value of 0 resets kvm->arch.pmmir_slots to 0 for backward compatibility, as the register is RAZ in older KVM, a value matching the current SLOTS is accepted as a no-op, and anything else is rejected with -EINVAL. Once the VM has run PMMIR_EL1 is immutable, so a mismatching write then returns -EBUSY. The register is now exposed via KVM_GET_REG_LIST for PMUv3 vCPUs, so add it to the get-reg-list selftest's PMU register list. Signed-off-by: Congkai Tan <congkai@amazon.com> Reviewed-by: Geoff Blake <blakgeof@amazon.com> Reviewed-by: Haris Okanovic <harisokn@amazon.com> Reviewed-by: Stanislav Spassov <stanspas@amazon.de> Co-developed-by: Oliver Upton <oupton@kernel.org> Reviewed-by: Fuad Tabba <fuad.tabba@linux.dev> Tested-by: Fuad Tabba <fuad.tabba@linux.dev> Link: https://patch.msgid.link/20260722202702.4165917-2-congkai@amazon.com Signed-off-by: Oliver Upton <oupton@kernel.org>
This commit is contained in:
parent
a13c140cc2
commit
453f6ff04b
|
|
@ -387,6 +387,9 @@ struct kvm_arch {
|
|||
/* Maximum number of counters for the guest */
|
||||
u8 nr_pmu_counters;
|
||||
|
||||
/* PMMIR_EL1.SLOTS value exposed to the guest. */
|
||||
u8 pmmir_slots;
|
||||
|
||||
/* Hypercall features firmware registers' descriptor */
|
||||
struct kvm_smccc_features smccc_feat;
|
||||
struct maple_tree smccc_filter;
|
||||
|
|
|
|||
|
|
@ -106,6 +106,7 @@ struct kvm_regs {
|
|||
#define KVM_ARM_VCPU_PTRAUTH_GENERIC 6 /* VCPU uses generic authentication */
|
||||
#define KVM_ARM_VCPU_HAS_EL2 7 /* Support nested virtualization */
|
||||
#define KVM_ARM_VCPU_HAS_EL2_E2H0 8 /* Limit NV support to E2H RES0 */
|
||||
#define KVM_ARM_VCPU_PMU_V3_STRICT 9 /* No default PMU creation */
|
||||
|
||||
struct kvm_vcpu_init {
|
||||
__u32 target;
|
||||
|
|
|
|||
|
|
@ -1092,6 +1092,17 @@ static int kvm_arm_pmu_v3_set_pmu(struct kvm_vcpu *vcpu, int pmu_id)
|
|||
|
||||
kvm_arm_set_pmu(kvm, arm_pmu);
|
||||
cpumask_copy(kvm->arch.supported_cpus, &arm_pmu->supported_cpus);
|
||||
|
||||
/*
|
||||
* Since a specific PMU is explicitly selected,
|
||||
* PMMIR_EL1.SLOTS is deterministic to the guest.
|
||||
* If KVM_ARM_VCPU_PMU_V3_STRICT is set, snapshot
|
||||
* the value to allow the guest to read it.
|
||||
*/
|
||||
if (kvm_vcpu_has_pmuv3_strict(vcpu))
|
||||
kvm->arch.pmmir_slots =
|
||||
FIELD_GET(ARMV8_PMU_SLOTS,
|
||||
arm_pmu->reg_pmmir);
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1367,6 +1367,64 @@ static bool access_pminten(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool access_pmmir(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
|
||||
const struct sys_reg_desc *r)
|
||||
{
|
||||
if (p->is_write)
|
||||
return write_to_read_only(vcpu, p, r);
|
||||
|
||||
/*
|
||||
* If KVM_ARM_VCPU_PMU_V3_STRICT is set and PMU was explicitly
|
||||
* selected, the underlying hardware SLOTS value was read into this
|
||||
* field. Otherwise, it stays 0. All other PMMIR_EL1 fields are RAZ.
|
||||
*/
|
||||
p->regval = FIELD_PREP(ARMV8_PMU_SLOTS, vcpu->kvm->arch.pmmir_slots);
|
||||
return true;
|
||||
}
|
||||
|
||||
static int get_pmmir(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r,
|
||||
u64 *val)
|
||||
{
|
||||
*val = FIELD_PREP(ARMV8_PMU_SLOTS, vcpu->kvm->arch.pmmir_slots);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int set_pmmir(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r,
|
||||
u64 val)
|
||||
{
|
||||
struct kvm *kvm = vcpu->kvm;
|
||||
u8 slots = FIELD_GET(ARMV8_PMU_SLOTS, val);
|
||||
|
||||
/*
|
||||
* Only the SLOTS field is exposed (get_pmmir returns just that field),
|
||||
* so reject a write that sets any other bit rather than silently
|
||||
* masking it.
|
||||
*/
|
||||
if (val & ~(u64)ARMV8_PMU_SLOTS)
|
||||
return -EINVAL;
|
||||
|
||||
guard(mutex)(&kvm->arch.config_lock);
|
||||
|
||||
/*
|
||||
* Once the VM has started PMMIR_EL1 is immutable. Reject any write
|
||||
* that does not match the current value.
|
||||
*/
|
||||
if (kvm_vm_has_ran_once(kvm))
|
||||
return slots == kvm->arch.pmmir_slots ? 0 : -EBUSY;
|
||||
|
||||
/*
|
||||
* Only SLOTS = 0 is honored for backwards compatibility with the
|
||||
* old RAZ behavior. Reject any non-zero write that does not match
|
||||
* the current value.
|
||||
*/
|
||||
if (!slots)
|
||||
kvm->arch.pmmir_slots = 0;
|
||||
else if (slots != kvm->arch.pmmir_slots)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool access_pmovs(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
|
||||
const struct sys_reg_desc *r)
|
||||
{
|
||||
|
|
@ -3448,7 +3506,8 @@ static const struct sys_reg_desc sys_reg_descs[] = {
|
|||
{ PMU_SYS_REG(PMINTENCLR_EL1),
|
||||
.access = access_pminten, .reg = PMINTENSET_EL1,
|
||||
.get_user = get_pmreg, .set_user = set_pmreg },
|
||||
{ SYS_DESC(SYS_PMMIR_EL1), trap_raz_wi },
|
||||
{ PMU_SYS_REG(PMMIR_EL1), .access = access_pmmir, .reset = NULL,
|
||||
.get_user = get_pmmir, .set_user = set_pmmir },
|
||||
|
||||
{ SYS_DESC(SYS_MAIR_EL1), access_vm_reg, reset_unknown, MAIR_EL1 },
|
||||
{ SYS_DESC(SYS_PIRE0_EL1), NULL, reset_unknown, PIRE0_EL1,
|
||||
|
|
@ -4593,7 +4652,7 @@ static const struct sys_reg_desc cp15_regs[] = {
|
|||
{ CP15_PMU_SYS_REG(HI, 0, 9, 14, 4), .access = access_pmceid },
|
||||
{ CP15_PMU_SYS_REG(HI, 0, 9, 14, 5), .access = access_pmceid },
|
||||
/* PMMIR */
|
||||
{ CP15_PMU_SYS_REG(DIRECT, 0, 9, 14, 6), .access = trap_raz_wi },
|
||||
{ CP15_PMU_SYS_REG(DIRECT, 0, 9, 14, 6), .access = access_pmmir },
|
||||
|
||||
/* PRRR/MAIR0 */
|
||||
{ AA32(LO), Op1( 0), CRn(10), CRm( 2), Op2( 0), access_vm_reg, NULL, MAIR_EL1 },
|
||||
|
|
|
|||
|
|
@ -75,6 +75,9 @@ void kvm_vcpu_pmu_resync_el0(void);
|
|||
#define kvm_vcpu_has_pmu(vcpu) \
|
||||
(vcpu_has_feature(vcpu, KVM_ARM_VCPU_PMU_V3))
|
||||
|
||||
#define kvm_vcpu_has_pmuv3_strict(vcpu) \
|
||||
(vcpu_has_feature(vcpu, KVM_ARM_VCPU_PMU_V3_STRICT))
|
||||
|
||||
/*
|
||||
* Updates the vcpu's view of the pmu events for this cpu.
|
||||
* Must be called before every vcpu run after disabling interrupts, to ensure
|
||||
|
|
@ -160,6 +163,7 @@ static inline u64 kvm_pmu_get_pmceid(struct kvm_vcpu *vcpu, bool pmceid1)
|
|||
}
|
||||
|
||||
#define kvm_vcpu_has_pmu(vcpu) ({ false; })
|
||||
#define kvm_vcpu_has_pmuv3_strict(vcpu) ({ false; })
|
||||
static inline void kvm_pmu_update_vcpu_events(struct kvm_vcpu *vcpu) {}
|
||||
static inline void kvm_vcpu_pmu_restore_guest(struct kvm_vcpu *vcpu) {}
|
||||
static inline void kvm_vcpu_pmu_restore_host(struct kvm_vcpu *vcpu) {}
|
||||
|
|
|
|||
|
|
@ -532,6 +532,7 @@ static __u64 base_regs[] = {
|
|||
static __u64 pmu_regs[] = {
|
||||
ARM64_SYS_REG(3, 0, 9, 14, 1), /* PMINTENSET_EL1 */
|
||||
ARM64_SYS_REG(3, 0, 9, 14, 2), /* PMINTENCLR_EL1 */
|
||||
ARM64_SYS_REG(3, 0, 9, 14, 6), /* PMMIR_EL1 */
|
||||
ARM64_SYS_REG(3, 3, 9, 12, 0), /* PMCR_EL0 */
|
||||
ARM64_SYS_REG(3, 3, 9, 12, 1), /* PMCNTENSET_EL0 */
|
||||
ARM64_SYS_REG(3, 3, 9, 12, 2), /* PMCNTENCLR_EL0 */
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user