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 --- .../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