From: "Yuhang.chen" <yhchen312@gmail.com>
To: anup@brainfault.org
Cc: atish.patra@linux.dev, palmer@dabbelt.com, pjw@kernel.org,
aou@eecs.berkeley.edu, alex@ghiti.fr, pbonzini@redhat.com,
shuah@kernel.org, kvm@vger.kernel.org,
kvm-riscv@lists.infradead.org, linux-riscv@lists.infradead.org,
linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org,
zhouquan@iscas.ac.cn, "Yuhang.chen" <yhchen312@gmail.com>
Subject: [PATCH v2 2/2] RISC-V: KVM: selftests: Add PMU event filter test
Date: Fri, 7 Aug 2026 13:32:27 +0800 [thread overview]
Message-ID: <20260807053227.341700-3-yhchen312@gmail.com> (raw)
In-Reply-To: <20260807053227.341700-1-yhchen312@gmail.com>
Add a selftest that exercises KVM_SET_PMU_EVENT_FILTER on RISC-V. The
guest programs the CPU cycles and instructions SBI PMU events through
SBI_EXT_PMU_COUNTER_CFG_MATCH while the host installs filters with the
ALLOW and DENY actions, asserting that disallowed events return
SBI_ERR_NOT_SUPPORTED and allowed events succeed.
The test also validates ioctl argument rejection: an invalid action, a
non-zero flags field, and an over-large nevents value are each expected
to fail with -EINVAL or -E2BIG. A baseline run verifies PMU
availability and the test skips (KSFT_SKIP) when PMU or the filter
capability is absent.
Assisted-by: YuanSheng:deepseek-v4-pro
Co-developed-by: Quan Zhou <zhouquan@iscas.ac.cn>
Signed-off-by: Quan Zhou <zhouquan@iscas.ac.cn>
Signed-off-by: Yuhang.chen <yhchen312@gmail.com>
---
tools/testing/selftests/kvm/Makefile.kvm | 1 +
.../kvm/riscv/pmu_event_filter_test.c | 199 ++++++++++++++++++
2 files changed, 200 insertions(+)
create mode 100644 tools/testing/selftests/kvm/riscv/pmu_event_filter_test.c
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index d28a057fa6c2..5d2dc3b25eac 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -218,6 +218,7 @@ TEST_GEN_PROGS_s390 += pre_fault_memory_test
TEST_GEN_PROGS_riscv = $(TEST_GEN_PROGS_COMMON)
TEST_GEN_PROGS_riscv += riscv/sbi_pmu_test
TEST_GEN_PROGS_riscv += riscv/ebreak_test
+TEST_GEN_PROGS_riscv += riscv/pmu_event_filter_test
TEST_GEN_PROGS_riscv += access_tracking_perf_test
TEST_GEN_PROGS_riscv += arch_timer
TEST_GEN_PROGS_riscv += coalesced_io_test
diff --git a/tools/testing/selftests/kvm/riscv/pmu_event_filter_test.c b/tools/testing/selftests/kvm/riscv/pmu_event_filter_test.c
new file mode 100644
index 000000000000..1a76fce2aca6
--- /dev/null
+++ b/tools/testing/selftests/kvm/riscv/pmu_event_filter_test.c
@@ -0,0 +1,199 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Test for RISC-V KVM_SET_PMU_EVENT_FILTER.
+ *
+ * Verify that a VM-scoped PMU event filter installed via the
+ * KVM_SET_PMU_EVENT_FILTER ioctl is enforced when a guest configures a
+ * counter through the SBI PMU COUNTER_CFG_MATCH call:
+ *
+ * - with no filter, events are programmable (baseline / PMU probe);
+ * - KVM_PMU_EVENT_DENY rejects the listed events;
+ * - KVM_PMU_EVENT_ALLOW admits only the listed events;
+ * - replacing the filter with an empty DENY list re-enables everything.
+ *
+ * The filter is checked before any perf event is created, so the test only
+ * ever programs the cycle event (always supported by the host PMU) and varies
+ * the filter *list* contents to exercise membership without depending on host
+ * support for other events. Counter management and SBI error reporting happen
+ * in the guest; the host installs filters and checks the reported errors.
+ */
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "kvm_util.h"
+#include "test_util.h"
+#include "processor.h"
+#include "ucall_common.h"
+#include "sbi.h"
+
+/* SBI PMU hardware event indexes (type == HW == 0, so eidx == code). */
+#define EV_CYCLES SBI_PMU_HW_CPU_CYCLES /* 1 */
+#define EV_INSTR SBI_PMU_HW_INSTRUCTIONS /* 2 */
+
+/* Must match KVM_PMU_EVENT_FILTER_MAX_EVENTS in arch/riscv/kvm/vm.c. */
+#define MAX_EVENTS 256
+
+static void guest_code(void)
+{
+ struct sbiret ret;
+ unsigned long ctr;
+ long err;
+
+ for (;;) {
+ /*
+ * Request the fixed cycle counter (cbase=0, cmask=1) for the
+ * cycle event. The host installs (or clears) the filter
+ * before each entry, so the result reflects the active policy.
+ */
+ ret = sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_CFG_MATCH,
+ 0, 1, 0, EV_CYCLES, 0, 0);
+ err = ret.error;
+ ctr = ret.value;
+
+ /* Release the counter on success so the next iteration reuses it. */
+ if (!err)
+ sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_STOP,
+ ctr, 1, SBI_PMU_STOP_FLAG_RESET, 0, 0, 0);
+
+ GUEST_SYNC1(err);
+ }
+}
+
+static struct kvm_pmu_event_filter *
+build_filter(__u32 action, __u32 flags, const __u64 *events, __u32 nevents)
+{
+ struct kvm_pmu_event_filter *f;
+ size_t size = sizeof(*f) + (size_t)nevents * sizeof(__u64);
+
+ f = calloc(1, size);
+ TEST_ASSERT(f, "calloc(pmu_event_filter)");
+ f->action = action;
+ f->nevents = nevents;
+ f->flags = flags;
+ if (nevents && events)
+ memcpy(f->events, events, nevents * sizeof(__u64));
+ return f;
+}
+
+/* Install a filter, asserting success. */
+static void set_filter(struct kvm_vm *vm, __u32 action,
+ const __u64 *events, __u32 nevents)
+{
+ struct kvm_pmu_event_filter *f = build_filter(action, 0, events, nevents);
+
+ vm_ioctl(vm, KVM_SET_PMU_EVENT_FILTER, f);
+ free(f);
+}
+
+/* Install a filter and return the raw ioctl result (for negative tests). */
+static int try_set_filter(struct kvm_vm *vm, __u32 action, __u32 flags,
+ const __u64 *events, __u32 nevents)
+{
+ struct kvm_pmu_event_filter *f = build_filter(action, flags, events, nevents);
+ int ret = __vm_ioctl(vm, KVM_SET_PMU_EVENT_FILTER, f);
+
+ free(f);
+ return ret;
+}
+
+/* Run the guest one step and return the cfg_match error code it reports. */
+static long run_one(struct kvm_vcpu *vcpu)
+{
+ struct ucall uc;
+
+ vcpu_run(vcpu);
+ TEST_ASSERT_EQ(get_ucall(vcpu, &uc), UCALL_SYNC);
+ return (long)uc.args[0];
+}
+
+static void test_filter_case(struct kvm_vm *vm, struct kvm_vcpu *vcpu,
+ __u32 action, const __u64 *events, __u32 nevents,
+ long expect, const char *desc)
+{
+ long err;
+
+ set_filter(vm, action, events, nevents);
+ err = run_one(vcpu);
+ TEST_ASSERT_EQ(err, expect);
+ pr_info("%s: err=%ld (expected %ld)\n", desc, err, expect);
+}
+
+static void test_bad_args(struct kvm_vm *vm)
+{
+ __u64 ev = EV_CYCLES;
+ int ret;
+
+ /* Invalid action. */
+ errno = 0;
+ ret = try_set_filter(vm, 2, 0, &ev, 1);
+ TEST_ASSERT(ret < 0 && errno == EINVAL,
+ "invalid action should fail with EINVAL, got ret=%d errno=%d",
+ ret, errno);
+
+ /* Non-zero flags are not supported. */
+ errno = 0;
+ ret = try_set_filter(vm, KVM_PMU_EVENT_ALLOW, 1, &ev, 1);
+ TEST_ASSERT(ret < 0 && errno == EINVAL,
+ "non-zero flags should fail with EINVAL, got ret=%d errno=%d",
+ ret, errno);
+
+ /* Too many events. */
+ errno = 0;
+ ret = try_set_filter(vm, KVM_PMU_EVENT_DENY, 0, NULL, MAX_EVENTS + 1);
+ TEST_ASSERT(ret < 0 && errno == E2BIG,
+ "nevents > max should fail with E2BIG, got ret=%d errno=%d",
+ ret, errno);
+}
+
+int main(void)
+{
+ struct kvm_vm *vm;
+ struct kvm_vcpu *vcpu;
+ long err;
+
+ TEST_REQUIRE(kvm_has_cap(KVM_CAP_PMU_EVENT_FILTER));
+
+ vm = vm_create_with_one_vcpu(&vcpu, guest_code);
+
+ /*
+ * Baseline / PMU probe: with no filter the cycle event must be
+ * programmable. If it isn't, the host PMU is unusable in this
+ * environment (e.g. Sscofpmf unavailable under TCG); skip the rest.
+ */
+ err = run_one(vcpu);
+ if (err) {
+ pr_info("PMU unavailable (baseline cfg_match err=%ld), skipping\n",
+ err);
+ kvm_vm_free(vm);
+ exit(KSFT_SKIP);
+ }
+
+ /* DENY{cycles}: the cycle event is rejected. */
+ test_filter_case(vm, vcpu, KVM_PMU_EVENT_DENY,
+ &(__u64){ EV_CYCLES }, 1,
+ SBI_ERR_NOT_SUPPORTED, "deny cycles");
+
+ /* ALLOW{cycles}: the cycle event is admitted. */
+ test_filter_case(vm, vcpu, KVM_PMU_EVENT_ALLOW,
+ &(__u64){ EV_CYCLES }, 1,
+ 0, "allow cycles");
+
+ /*
+ * ALLOW{instructions}: cycles is not in the allow list, so it is
+ * rejected. Instructions itself is never programmed, so host support
+ * for it is irrelevant.
+ */
+ test_filter_case(vm, vcpu, KVM_PMU_EVENT_ALLOW,
+ &(__u64){ EV_INSTR }, 1,
+ SBI_ERR_NOT_SUPPORTED, "cycles not in allow{instr}");
+
+ /* Empty DENY list: nothing is denied, cycles is programmable again. */
+ test_filter_case(vm, vcpu, KVM_PMU_EVENT_DENY, NULL, 0,
+ 0, "clear (deny empty)");
+
+ test_bad_args(vm);
+
+ kvm_vm_free(vm);
+ return 0;
+}
--
2.34.1
_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv
prev parent reply other threads:[~2026-08-07 5:33 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-07 5:32 [PATCH v2 0/2] RISC-V: KVM: Add PMU event filter support Yuhang.chen
2026-08-07 5:32 ` [PATCH v2 1/2] " Yuhang.chen
2026-08-07 5:32 ` Yuhang.chen [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260807053227.341700-3-yhchen312@gmail.com \
--to=yhchen312@gmail.com \
--cc=alex@ghiti.fr \
--cc=anup@brainfault.org \
--cc=aou@eecs.berkeley.edu \
--cc=atish.patra@linux.dev \
--cc=kvm-riscv@lists.infradead.org \
--cc=kvm@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=linux-riscv@lists.infradead.org \
--cc=palmer@dabbelt.com \
--cc=pbonzini@redhat.com \
--cc=pjw@kernel.org \
--cc=shuah@kernel.org \
--cc=zhouquan@iscas.ac.cn \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox