Re: [PATCH v9 16/16] KVM: arm64: selftests: Test PMU_V3_FIXED_COUNTERS_ONLY

From: Fuad Tabba

Date: Wed Jul 29 2026 - 16:02:16 EST


On Mon, 20 Jul 2026 at 06:39, Akihiko Odaki
<odaki@xxxxxxxxxxxxxxxxxxxxxx> 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 <odaki@xxxxxxxxxxxxxxxxxxxxxx>

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

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