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

