Re: [PATCH v9 08/16] KVM: arm64: selftests: Cover PMU state in MDCR_EL2

From: Fuad Tabba

Date: Wed Jul 29 2026 - 13:56:36 EST


On Mon, 20 Jul 2026 at 06:44, Akihiko Odaki
<odaki@xxxxxxxxxxxxxxxxxxxxxx> wrote:
>
> Add MDCR_EL2 coverage to vpmu_counter_access when EL2 is available.
>
> For each configured PMCR_EL0.N value, verify that an arbitrary HPMN
> value, including one above the configured counter count, round-trips
> through KVM_SET_ONE_REG and survives SET_NR_COUNTERS. Also verify that
> HPME can be toggled without disturbing HPMN and that KVM_ARM_VCPU_INIT
> resets HPMN from the configured count.
>
> Assisted-by: Codex:gpt-5.5
> Signed-off-by: Akihiko Odaki <odaki@xxxxxxxxxxxxxxxxxxxxxx>

Reviewed-by: Fuad Tabba <fuad.tabba@xxxxxxxxx>

Cheers,
/fuad

> ---
> .../selftests/kvm/arm64/vpmu_counter_access.c | 90 ++++++++++++++++++++--
> 1 file changed, 85 insertions(+), 5 deletions(-)
>
> diff --git a/tools/testing/selftests/kvm/arm64/vpmu_counter_access.c b/tools/testing/selftests/kvm/arm64/vpmu_counter_access.c
> index 3b5bb421f401..73f04e4c6ca3 100644
> --- a/tools/testing/selftests/kvm/arm64/vpmu_counter_access.c
> +++ b/tools/testing/selftests/kvm/arm64/vpmu_counter_access.c
> @@ -25,6 +25,14 @@
> /* The cycle counter bit position that's common among the PMU registers */
> #define ARMV8_PMU_CYCLE_IDX 31
>
> +#ifndef MDCR_EL2_HPMN
> +#define MDCR_EL2_HPMN GENMASK_ULL(4, 0)
> +#endif
> +
> +#ifndef MDCR_EL2_HPME
> +#define MDCR_EL2_HPME BIT_ULL(7)
> +#endif
> +
> struct vpmu_vm {
> struct kvm_vm *vm;
> struct kvm_vcpu *vcpu;
> @@ -466,15 +474,12 @@ static void guest_code_done(void)
> GUEST_DONE();
> }
>
> -static void test_create_vpmu_vm_with_nr_counters(unsigned int nr_counters, bool expect_fail)
> +static void set_nr_counters(struct kvm_vcpu *vcpu,
> + unsigned int nr_counters, bool expect_fail)
> {
> - struct kvm_vcpu *vcpu;
> unsigned int prev;
> int ret;
>
> - create_vpmu_vm(guest_code);
> - vcpu = vpmu_vm.vcpu;
> -
> prev = get_pmcr_n(vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_PMCR_EL0)));
>
> ret = __vcpu_device_attr_set(vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,
> @@ -486,6 +491,17 @@ static void test_create_vpmu_vm_with_nr_counters(unsigned int nr_counters, bool
> nr_counters, prev);
> else
> TEST_ASSERT(!ret, KVM_IOCTL_ERROR(KVM_SET_DEVICE_ATTR, ret));
> +}
> +
> +static void test_create_vpmu_vm_with_nr_counters(unsigned int nr_counters,
> + bool expect_fail)
> +{
> + struct kvm_vcpu *vcpu;
> +
> + create_vpmu_vm(guest_code);
> + vcpu = vpmu_vm.vcpu;
> +
> + set_nr_counters(vcpu, nr_counters, expect_fail);
>
> vcpu_device_attr_set(vcpu, KVM_ARM_VCPU_PMU_V3_CTRL, KVM_ARM_VCPU_PMU_V3_INIT, NULL);
> }
> @@ -588,6 +604,69 @@ static void run_pmregs_validity_test(u64 pmcr_n)
> destroy_vpmu_vm();
> }
>
> +static void run_mdcr_el2_validity_test(u64 pmcr_n)
> +{
> + struct kvm_vcpu_init init;
> + struct kvm_vcpu *vcpu;
> + u64 expected_mdcr, mdcr;
> +
> + pr_debug("MDCR_EL2 test with pmcr_n %lu\n", pmcr_n);
> +
> + create_vpmu_vm(guest_code);
> + if (!vm_supports_el2(vpmu_vm.vm)) {
> + destroy_vpmu_vm();
> + return;
> + }
> +
> + vcpu = vpmu_vm.vcpu;
> +
> + mdcr = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_MDCR_EL2));
> + expected_mdcr = u64_replace_bits(mdcr, FIELD_MAX(MDCR_EL2_HPMN),
> + MDCR_EL2_HPMN);
> + vcpu_set_reg(vcpu, KVM_ARM64_SYS_REG(SYS_MDCR_EL2),
> + expected_mdcr);
> +
> + mdcr = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_MDCR_EL2));
> + TEST_ASSERT(mdcr == expected_mdcr,
> + "MDCR_EL2 was not properly updated after HPMN write (expected 0x%lx, got 0x%lx)",
> + expected_mdcr, mdcr);
> +
> + set_nr_counters(vcpu, pmcr_n, false);
> +
> + mdcr = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_MDCR_EL2));
> + TEST_ASSERT(mdcr == expected_mdcr,
> + "MDCR_EL2 changed after PMU_V3_SET_NR_COUNTERS (expected 0x%lx, got 0x%lx)",
> + expected_mdcr, mdcr);
> +
> + vcpu_device_attr_set(vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,
> + KVM_ARM_VCPU_PMU_V3_INIT, NULL);
> +
> + mdcr = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_MDCR_EL2));
> + TEST_ASSERT(mdcr == expected_mdcr,
> + "MDCR_EL2 changed after PMU_V3_INIT (expected 0x%lx, got 0x%lx)",
> + expected_mdcr, mdcr);
> +
> + expected_mdcr = mdcr ^ MDCR_EL2_HPME;
> + vcpu_set_reg(vcpu, KVM_ARM64_SYS_REG(SYS_MDCR_EL2),
> + expected_mdcr);
> +
> + mdcr = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_MDCR_EL2));
> + TEST_ASSERT(mdcr == expected_mdcr,
> + "MDCR_EL2 was not properly updated after HPME write (expected 0x%lx, got 0x%lx)",
> + expected_mdcr, mdcr);
> +
> + kvm_get_default_vcpu_target(vpmu_vm.vm, &init);
> + init.features[0] |= (1 << KVM_ARM_VCPU_PMU_V3);
> + aarch64_vcpu_setup(vcpu, &init);
> +
> + mdcr = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_MDCR_EL2));
> + TEST_ASSERT(FIELD_GET(MDCR_EL2_HPMN, mdcr) == pmcr_n,
> + "MDCR_EL2.HPMN is not reset after INIT (expected %lu, got %lu)",
> + pmcr_n, FIELD_GET(MDCR_EL2_HPMN, mdcr));
> +
> + destroy_vpmu_vm();
> +}
> +
> /*
> * Create a guest with one vCPU, and attempt to set the PMCR_EL0.N for
> * the vCPU to @pmcr_n, which is larger than the host value.
> @@ -672,6 +751,7 @@ int main(void)
> for (i = 0; i <= pmcr_n; i++) {
> run_access_test(i);
> run_pmregs_validity_test(i);
> + run_mdcr_el2_validity_test(i);
> }
>
> for (i = pmcr_n + 1; i < ARMV8_PMU_MAX_COUNTERS; i++)
>
> --
> 2.55.0
>
>