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
--
kvm-riscv mailing list
kvm-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/kvm-riscv
WARNING: multiple messages have this Message-ID (diff)
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
WARNING: multiple messages have this Message-ID (diff)
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
next prev parent reply other threads:[~2026-08-07 5:33 UTC|newest]
Thread overview: 11+ 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 ` Yuhang.chen
2026-08-07 5:32 ` Yuhang.chen
2026-08-07 5:32 ` [PATCH v2 1/2] " Yuhang.chen
2026-08-07 5:32 ` Yuhang.chen
2026-08-07 5:32 ` Yuhang.chen
2026-08-07 5:55 ` sashiko-bot
2026-08-07 5:32 ` Yuhang.chen [this message]
2026-08-07 5:32 ` [PATCH v2 2/2] RISC-V: KVM: selftests: Add PMU event filter test Yuhang.chen
2026-08-07 5:32 ` Yuhang.chen
2026-08-07 5:40 ` sashiko-bot
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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.