[PATCH v8 03/11] KVM: arm64: selftests: Cover PMU state in MDCR_EL2
From: Akihiko Odaki
Date: Fri Jul 10 2026 - 07:20:52 EST
Add MDCR_EL2 coverage to vpmu_counter_access when EL2 is available.
For each configured PMCR_EL0.N value, verify that HPMN tracks the
configured counter count, HPME can be toggled without disturbing HPMN,
and KVM_SET_ONE_REG rejects an out-of-range HPMN without changing
MDCR_EL2. Reset the vCPU and verify that HPMN is restored from the
configured count.
Assisted-by: Codex:gpt-5.5
Signed-off-by: Akihiko Odaki <odaki@xxxxxxxxxxxxxxxxxxxxxx>
---
.../selftests/kvm/arm64/vpmu_counter_access.c | 76 ++++++++++++++++++++++
1 file changed, 76 insertions(+)
diff --git a/tools/testing/selftests/kvm/arm64/vpmu_counter_access.c b/tools/testing/selftests/kvm/arm64/vpmu_counter_access.c
index 22223395969e..25203ea117b0 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;
@@ -583,6 +591,73 @@ 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 mdcr, old_mdcr;
+ int ret;
+
+ pr_debug("MDCR_EL2 test with pmcr_n %lu\n", pmcr_n);
+
+ test_create_vpmu_vm_with_nr_counters(pmcr_n, false);
+ 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));
+ TEST_ASSERT(FIELD_GET(MDCR_EL2_HPMN, mdcr) == pmcr_n,
+ "MDCR_EL2.HPMN is not updated after PMU_V3_SET_NR_COUNTERS (expected %lu, got %lu)",
+ pmcr_n, FIELD_GET(MDCR_EL2_HPMN, mdcr));
+
+ old_mdcr = mdcr;
+ vcpu_set_reg(vcpu, KVM_ARM64_SYS_REG(SYS_MDCR_EL2),
+ old_mdcr ^ MDCR_EL2_HPME);
+
+ mdcr = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_MDCR_EL2));
+ TEST_ASSERT(mdcr == (old_mdcr ^ MDCR_EL2_HPME),
+ "MDCR_EL2 was not properly updated after HPME write (expected 0x%lx, got 0x%lx)",
+ old_mdcr ^ MDCR_EL2_HPME, mdcr);
+
+ if (pmcr_n < FIELD_MAX(MDCR_EL2_HPMN)) {
+ errno = 0;
+ old_mdcr = mdcr;
+ ret = __vcpu_set_reg(vcpu, KVM_ARM64_SYS_REG(SYS_MDCR_EL2),
+ u64_replace_bits(mdcr, pmcr_n + 1, MDCR_EL2_HPMN));
+ TEST_ASSERT(ret == -1 && errno == EINVAL,
+ "Setting MDCR_EL2.HPMN to %lu unexpectedly succeeded",
+ pmcr_n + 1);
+
+ mdcr = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_MDCR_EL2));
+ TEST_ASSERT(mdcr == old_mdcr,
+ "MDCR_EL2 changed after failed HPMN write (expected 0x%lx, got 0x%lx)",
+ old_mdcr, mdcr);
+ }
+
+ old_mdcr = mdcr;
+ vcpu_set_reg(vcpu, KVM_ARM64_SYS_REG(SYS_MDCR_EL2),
+ u64_replace_bits(mdcr, 0, MDCR_EL2_HPMN));
+
+ mdcr = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_MDCR_EL2));
+ TEST_ASSERT(mdcr == u64_replace_bits(old_mdcr, 0, MDCR_EL2_HPMN),
+ "MDCR_EL2 was not properly updated after HPMN write (expected 0x%lx, got 0x%lx)",
+ u64_replace_bits(old_mdcr, 0, MDCR_EL2_HPMN), 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 updated 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.
@@ -634,6 +709,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