On Mon, 20 Jul 2026 at 06:39, Akihiko Odaki <[email protected]> wrote: > > Assert the following: > - FIXED_COUNTERS_ONLY is unset at initialization. > - FIXED_COUNTERS_ONLY can be set. > - Setting an event filter when FIXED_COUNTERS_ONLY has already been set > results in EBUSY. > - Setting FIXED_COUNTERS_ONLY when an event filter has already been set > results in EBUSY. > - Setting FIXED_COUNTERS_ONLY when a VCPU has already run results in > EBUSY. > > Run the existing PMU and MDCR_EL2 tests in fixed-only mode. Verify that > enabling the mode preserves the userspace MDCR_EL2 value until a > subsequent KVM_ARM_VCPU_INIT resets HPMN to zero. > > Assisted-by: Codex:gpt-5.5 > Signed-off-by: Akihiko Odaki <[email protected]>
Reviewed-by: Fuad Tabba <[email protected]> Cheers, /fuad > --- > .../selftests/kvm/arm64/vpmu_counter_access.c | 166 > +++++++++++++++++---- > 1 file changed, 137 insertions(+), 29 deletions(-) > > diff --git a/tools/testing/selftests/kvm/arm64/vpmu_counter_access.c > b/tools/testing/selftests/kvm/arm64/vpmu_counter_access.c > index 73f04e4c6ca3..b5f8ce8d3451 100644 > --- a/tools/testing/selftests/kvm/arm64/vpmu_counter_access.c > +++ b/tools/testing/selftests/kvm/arm64/vpmu_counter_access.c > @@ -411,12 +411,7 @@ static void create_vpmu_vm(void *guest_code) > { > struct kvm_vcpu_init init; > u8 pmuver, ec; > - u64 dfr0, irq = 23; > - struct kvm_device_attr irq_attr = { > - .group = KVM_ARM_VCPU_PMU_V3_CTRL, > - .attr = KVM_ARM_VCPU_PMU_V3_IRQ, > - .addr = (u64)&irq, > - }; > + u64 dfr0; > > /* The test creates the vpmu_vm multiple times. Ensure a clean state > */ > memset(&vpmu_vm, 0, sizeof(vpmu_vm)); > @@ -442,8 +437,6 @@ static void create_vpmu_vm(void *guest_code) > TEST_ASSERT(pmuver != ID_AA64DFR0_EL1_PMUVer_IMP_DEF && > pmuver >= ID_AA64DFR0_EL1_PMUVer_IMP, > "Unexpected PMUVER (0x%x) on the vCPU with PMUv3", > pmuver); > - > - vcpu_ioctl(vpmu_vm.vcpu, KVM_SET_DEVICE_ATTR, &irq_attr); > } > > static void destroy_vpmu_vm(void) > @@ -494,13 +487,22 @@ static void set_nr_counters(struct kvm_vcpu *vcpu, > } > > static void test_create_vpmu_vm_with_nr_counters(unsigned int nr_counters, > + bool fixed_counters_only, > bool expect_fail) > { > struct kvm_vcpu *vcpu; > + u64 irq = 23; > > create_vpmu_vm(guest_code); > vcpu = vpmu_vm.vcpu; > > + if (fixed_counters_only) > + vcpu_device_attr_set(vcpu, KVM_ARM_VCPU_PMU_V3_CTRL, > + KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY, > NULL); > + > + vcpu_device_attr_set(vcpu, KVM_ARM_VCPU_PMU_V3_CTRL, > + KVM_ARM_VCPU_PMU_V3_IRQ, &irq); > + > 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); > @@ -510,15 +512,15 @@ static void > test_create_vpmu_vm_with_nr_counters(unsigned int nr_counters, > * Create a guest with one vCPU, set the PMCR_EL0.N for the vCPU to @pmcr_n, > * and run the test. > */ > -static void run_access_test(u64 pmcr_n) > +static void run_access_test(u64 pmcr_n, bool fixed_counters_only) > { > u64 sp; > struct kvm_vcpu *vcpu; > struct kvm_vcpu_init init; > > - pr_debug("Test with pmcr_n %lu\n", pmcr_n); > + pr_debug("Test with pmcr_n %lu, fixed_counters_only %d\n", pmcr_n, > fixed_counters_only); > > - test_create_vpmu_vm_with_nr_counters(pmcr_n, false); > + test_create_vpmu_vm_with_nr_counters(pmcr_n, fixed_counters_only, > false); > vcpu = vpmu_vm.vcpu; > > /* Save the initial sp to restore them later to run the guest again */ > @@ -552,14 +554,14 @@ static struct pmreg_sets validity_check_reg_sets[] = { > * Create a VM, and check if KVM handles the userspace accesses of > * the PMU register sets in @validity_check_reg_sets[] correctly. > */ > -static void run_pmregs_validity_test(u64 pmcr_n) > +static void run_pmregs_validity_test(u64 pmcr_n, bool fixed_counters_only) > { > int i; > struct kvm_vcpu *vcpu; > u64 set_reg_id, clr_reg_id, reg_val; > u64 valid_counters_mask, max_counters_mask; > > - test_create_vpmu_vm_with_nr_counters(pmcr_n, false); > + test_create_vpmu_vm_with_nr_counters(pmcr_n, fixed_counters_only, > false); > vcpu = vpmu_vm.vcpu; > > valid_counters_mask = get_counters_mask(pmcr_n); > @@ -604,13 +606,15 @@ static void run_pmregs_validity_test(u64 pmcr_n) > destroy_vpmu_vm(); > } > > -static void run_mdcr_el2_validity_test(u64 pmcr_n) > +static void run_mdcr_el2_validity_test(u64 pmcr_n, bool fixed_counters_only) > { > struct kvm_vcpu_init init; > struct kvm_vcpu *vcpu; > u64 expected_mdcr, mdcr; > + u64 irq = 23; > > - pr_debug("MDCR_EL2 test with pmcr_n %lu\n", pmcr_n); > + pr_debug("MDCR_EL2 test with pmcr_n %lu, fixed_counters_only %d\n", > + pmcr_n, fixed_counters_only); > > create_vpmu_vm(guest_code); > if (!vm_supports_el2(vpmu_vm.vm)) { > @@ -631,6 +635,16 @@ static void run_mdcr_el2_validity_test(u64 pmcr_n) > "MDCR_EL2 was not properly updated after HPMN write > (expected 0x%lx, got 0x%lx)", > expected_mdcr, mdcr); > > + if (fixed_counters_only) { > + vcpu_device_attr_set(vcpu, KVM_ARM_VCPU_PMU_V3_CTRL, > + KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY, > NULL); > + > + mdcr = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_MDCR_EL2)); > + TEST_ASSERT(mdcr == expected_mdcr, > + "MDCR_EL2 changed after > PMU_V3_FIXED_COUNTERS_ONLY (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)); > @@ -638,6 +652,9 @@ static void run_mdcr_el2_validity_test(u64 pmcr_n) > "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_IRQ, &irq); > + > vcpu_device_attr_set(vcpu, KVM_ARM_VCPU_PMU_V3_CTRL, > KVM_ARM_VCPU_PMU_V3_INIT, NULL); > > @@ -672,11 +689,11 @@ static void run_mdcr_el2_validity_test(u64 pmcr_n) > * the vCPU to @pmcr_n, which is larger than the host value. > * The attempt should fail as @pmcr_n is too big to set for the vCPU. > */ > -static void run_error_test(u64 pmcr_n) > +static void run_error_test(u64 pmcr_n, bool fixed_counters_only) > { > pr_debug("Error test with pmcr_n %lu (larger than the host)\n", > pmcr_n); > > - test_create_vpmu_vm_with_nr_counters(pmcr_n, true); > + test_create_vpmu_vm_with_nr_counters(pmcr_n, fixed_counters_only, > true); > destroy_vpmu_vm(); > } > > @@ -737,25 +754,116 @@ static void test_set_nr_counters_after_vcpu_run(void) > kvm_vm_free(vm); > } > > -int main(void) > +static void test_config(u64 pmcr_n, bool fixed_counters_only) > +{ > + u64 i; > + > + for (i = 0; i <= pmcr_n; i++) { > + run_access_test(i, fixed_counters_only); > + run_pmregs_validity_test(i, fixed_counters_only); > + run_mdcr_el2_validity_test(i, fixed_counters_only); > + } > + > + for (i = pmcr_n + 1; i < ARMV8_PMU_MAX_COUNTERS; i++) > + run_error_test(i, fixed_counters_only); > +} > + > +static void test_fixed_counters_only(void) > { > - u64 i, pmcr_n; > + struct kvm_pmu_event_filter filter = { .nevents = 0 }; > + struct kvm_vm *vm; > + struct kvm_vcpu *running_vcpu; > + struct kvm_vcpu *stopped_vcpu; > + struct kvm_vcpu_init init; > + int ret; > + u64 irq = 23; > + > + create_vpmu_vm(guest_code); > + ret = __vcpu_has_device_attr(vpmu_vm.vcpu, KVM_ARM_VCPU_PMU_V3_CTRL, > + KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY); > + if (ret) { > + TEST_ASSERT(ret == -1 && errno == ENXIO, > + KVM_IOCTL_ERROR(KVM_HAS_DEVICE_ATTR, ret)); > + destroy_vpmu_vm(); > + return; > + } > + > + /* Assert that FIXED_COUNTERS_ONLY is unset at initialization. */ > + ret = __vcpu_device_attr_get(vpmu_vm.vcpu, KVM_ARM_VCPU_PMU_V3_CTRL, > + KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY, > NULL); > + TEST_ASSERT(ret == -1 && errno == ENXIO, > + KVM_IOCTL_ERROR(KVM_GET_DEVICE_ATTR, ret)); > + > + /* Assert that setting FIXED_COUNTERS_ONLY succeeds. */ > + vcpu_device_attr_set(vpmu_vm.vcpu, KVM_ARM_VCPU_PMU_V3_CTRL, > + KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY, NULL); > + > + /* Assert that FIXED_COUNTERS_ONLY is set. */ > + vcpu_device_attr_get(vpmu_vm.vcpu, KVM_ARM_VCPU_PMU_V3_CTRL, > + KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY, NULL); > + > + /* > + * Setting an event filter when FIXED_COUNTERS_ONLY has already been > set > + * results in EBUSY. > + */ > + ret = __vcpu_device_attr_set(vpmu_vm.vcpu, KVM_ARM_VCPU_PMU_V3_CTRL, > + KVM_ARM_VCPU_PMU_V3_FILTER, &filter); > + TEST_ASSERT(ret == -1 && errno == EBUSY, > + KVM_IOCTL_ERROR(KVM_SET_DEVICE_ATTR, ret)); > + > + destroy_vpmu_vm(); > + > + create_vpmu_vm(guest_code); > + > + /* > + * Assert that setting FIXED_COUNTERS_ONLY when an event filter has > + * already been set results in EBUSY. > + */ > + vcpu_device_attr_set(vpmu_vm.vcpu, KVM_ARM_VCPU_PMU_V3_CTRL, > + KVM_ARM_VCPU_PMU_V3_FILTER, &filter); > + > + ret = __vcpu_device_attr_set(vpmu_vm.vcpu, KVM_ARM_VCPU_PMU_V3_CTRL, > + KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY, > NULL); > + TEST_ASSERT(ret == -1 && errno == EBUSY, > + KVM_IOCTL_ERROR(KVM_SET_DEVICE_ATTR, ret)); > + > + destroy_vpmu_vm(); > + > + /* > + * Assert that setting FIXED_COUNTERS_ONLY when a VCPU has already run > + * results in EBUSY. > + */ > + vm = vm_create(2); > + vm_ioctl(vm, KVM_ARM_PREFERRED_TARGET, &init); > + init.features[0] |= (1 << KVM_ARM_VCPU_PMU_V3); > + running_vcpu = aarch64_vcpu_add(vm, 0, &init, guest_code_done); > + stopped_vcpu = aarch64_vcpu_add(vm, 1, &init, guest_code_done); > + kvm_arch_vm_finalize_vcpus(vm); > + vcpu_device_attr_set(running_vcpu, KVM_ARM_VCPU_PMU_V3_CTRL, > + KVM_ARM_VCPU_PMU_V3_IRQ, &irq); > + vcpu_device_attr_set(running_vcpu, KVM_ARM_VCPU_PMU_V3_CTRL, > + KVM_ARM_VCPU_PMU_V3_INIT, NULL); > + vcpu_run(running_vcpu); > + > + ret = __vcpu_device_attr_set(stopped_vcpu, KVM_ARM_VCPU_PMU_V3_CTRL, > + KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY, > NULL); > + TEST_ASSERT(ret == -1 && errno == EBUSY, > + KVM_IOCTL_ERROR(KVM_SET_DEVICE_ATTR, ret)); > + > + kvm_vm_free(vm); > + > + test_config(0, true); > +} > > +int main(void) > +{ > TEST_REQUIRE(kvm_has_cap(KVM_CAP_ARM_PMU_V3)); > TEST_REQUIRE(kvm_supports_vgic_v3()); > TEST_REQUIRE(kvm_supports_nr_counters_attr()); > > test_set_nr_counters_after_vcpu_run(); > - > - pmcr_n = get_pmcr_n_limit(); > - 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++) > - run_error_test(i); > + test_config(get_pmcr_n_limit(), false); > + test_fixed_counters_only(); > > return 0; > } > > -- > 2.55.0 > >

