[PATCH v6 4/4] KVM: arm64: selftests: Test PMU_V3_FIXED_COUNTERS_ONLY

Akihiko Odaki odaki at rsg.ci.i.u-tokyo.ac.jp
Mon Apr 13 01:19:07 PDT 2026


On 2026/04/13 17:07, Akihiko Odaki wrote:
> Assert the following:
> - KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY is unset at initialization.
> - KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY can be set.
> - Setting KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY for the first time
>    after setting an event filter results in EBUSY.
> - KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY can be set again even if an
>    event filter has already been set.
> - Setting KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY after running a VCPU
>    results in EBUSY.
> - The existing test cases pass with
>    KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY set.
> 
> Signed-off-by: Akihiko Odaki <odaki at rsg.ci.i.u-tokyo.ac.jp>
> ---
>   .../selftests/kvm/arm64/vpmu_counter_access.c      | 147 +++++++++++++++++----
>   1 file changed, 123 insertions(+), 24 deletions(-)
> 
> diff --git a/tools/testing/selftests/kvm/arm64/vpmu_counter_access.c b/tools/testing/selftests/kvm/arm64/vpmu_counter_access.c
> index ae36325c022f..6e2bf3ad63b2 100644
> --- a/tools/testing/selftests/kvm/arm64/vpmu_counter_access.c
> +++ b/tools/testing/selftests/kvm/arm64/vpmu_counter_access.c
> @@ -403,12 +403,7 @@ static void create_vpmu_vm(void *guest_code)
>   {
>   	struct kvm_vcpu_init init;
>   	uint8_t pmuver, ec;
> -	uint64_t dfr0, irq = 23;
> -	struct kvm_device_attr irq_attr = {
> -		.group = KVM_ARM_VCPU_PMU_V3_CTRL,
> -		.attr = KVM_ARM_VCPU_PMU_V3_IRQ,
> -		.addr = (uint64_t)&irq,
> -	};
> +	uint64_t dfr0;
>   
>   	/* The test creates the vpmu_vm multiple times. Ensure a clean state */
>   	memset(&vpmu_vm, 0, sizeof(vpmu_vm));
> @@ -434,8 +429,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)
> @@ -461,15 +454,25 @@ static void run_vcpu(struct kvm_vcpu *vcpu, uint64_t pmcr_n)
>   	}
>   }
>   
> -static void test_create_vpmu_vm_with_nr_counters(unsigned int nr_counters, bool expect_fail)
> +static void test_create_vpmu_vm_with_nr_counters(unsigned int nr_counters,
> +						 bool fixed_counters_only,
> +						 bool expect_fail)
>   {
>   	struct kvm_vcpu *vcpu;
>   	unsigned int prev;
>   	int ret;
> +	uint64_t 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);
> +
>   	prev = get_pmcr_n(vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_PMCR_EL0)));
>   
>   	ret = __vcpu_device_attr_set(vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,
> @@ -489,15 +492,15 @@ static void test_create_vpmu_vm_with_nr_counters(unsigned int nr_counters, bool
>    * 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(uint64_t pmcr_n)
> +static void run_access_test(uint64_t pmcr_n, bool fixed_counters_only)
>   {
>   	uint64_t 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 */
> @@ -531,14 +534,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(uint64_t pmcr_n)
> +static void run_pmregs_validity_test(uint64_t pmcr_n, bool fixed_counters_only)
>   {
>   	int i;
>   	struct kvm_vcpu *vcpu;
>   	uint64_t set_reg_id, clr_reg_id, reg_val;
>   	uint64_t 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);
> @@ -588,11 +591,11 @@ static void run_pmregs_validity_test(uint64_t 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(uint64_t pmcr_n)
> +static void run_error_test(uint64_t 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();
>   }
>   
> @@ -622,22 +625,118 @@ static bool kvm_supports_nr_counters_attr(void)
>   	return supported;
>   }
>   
> +static void test_config(uint64_t pmcr_n, bool fixed_counters_only)
> +{
> +	uint64_t i;
> +
> +	for (i = 0; i <= pmcr_n; i++) {
> +		run_access_test(i, fixed_counters_only);
> +		run_pmregs_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(uint64_t 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;
> +	uint64_t 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 getting FIXED_COUNTERS_ONLY succeeds. */
> +	vcpu_device_attr_get(vpmu_vm.vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,
> +			     KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY, NULL);
> +
> +	/*
> +	 * Assert that setting FIXED_COUNTERS_ONLY again succeeds even if an
> +	 * event filter has already been set.
> +	 */
> +	vcpu_device_attr_set(vpmu_vm.vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,
> +			     KVM_ARM_VCPU_PMU_V3_FILTER, &filter);
> +
> +	vcpu_device_attr_set(vpmu_vm.vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,
> +			     KVM_ARM_VCPU_PMU_V3_FIXED_COUNTERS_ONLY, NULL);
> +
> +	destroy_vpmu_vm();
> +
> +	create_vpmu_vm(guest_code);
> +
> +	/*
> +	 * Assert that setting FIXED_COUNTERS_ONLY results in EBUSY if an event
> +	 * filter has already been set while FIXED_COUNTERS_ONLY has not.
> +	 */
> +	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 after running a VCPU 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);
> +	stopped_vcpu = aarch64_vcpu_add(vm, 1, &init, guest_code);
> +	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);
> +	run_vcpu(running_vcpu, pmcr_n);

Sorry, I sent a series before testing by mistake. This hangs the test 
and I'll send a fix later. Anything else is fine and ready for review.

Regards,
Akihiko Odaki

> +
> +	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)
>   {
> -	uint64_t i, pmcr_n;
> +	uint64_t pmcr_n;
>   
>   	TEST_REQUIRE(kvm_has_cap(KVM_CAP_ARM_PMU_V3));
>   	TEST_REQUIRE(kvm_supports_vgic_v3());
>   	TEST_REQUIRE(kvm_supports_nr_counters_attr());
>   
>   	pmcr_n = get_pmcr_n_limit();
> -	for (i = 0; i <= pmcr_n; i++) {
> -		run_access_test(i);
> -		run_pmregs_validity_test(i);
> -	}
> -
> -	for (i = pmcr_n + 1; i < ARMV8_PMU_MAX_COUNTERS; i++)
> -		run_error_test(i);
> +	test_config(pmcr_n, false);
> +	test_fixed_counters_only(pmcr_n);
>   
>   	return 0;
>   }
> 




More information about the linux-arm-kernel mailing list