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

Fuad Tabba <[email protected]>
Newsgroups dev.linux.lists.kvmarm,org.infradead.lists.linux-arm-kernel,org.kernel.vger.kvm,org.kernel.vger.linux-doc,org.kernel.vger.linux-hardening,org.kernel.vger.linux-kernel,org.kernel.vger.linux-kselftest
Message-ID <CA+EHjTyTOCyZRRsGj3hbzk6H=ZPZTfTCYQpv+o0MpcoLAXS95A@mail.gmail.com>
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
>
>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.