[PATCH v2 1/2] RISC-V: KVM: Add PMU event filter support
Anup Patel
anup at brainfault.org
Fri Oct 2 22:13:12 PDT 2026
On Fri, Aug 7, 2026 at 11:02 AM Yuhang.chen <yhchen312 at gmail.com> wrote:
>
> Allow userspace to restrict which SBI PMU events a guest is permitted
> to program via the new VM ioctl KVM_SET_PMU_EVENT_FILTER. It takes a
> struct kvm_pmu_event_filter whose events[] array holds SBI PMU event
> indices encoded as (type << 16) | code. The action field selects ALLOW
> (only listed events may be programmed) or DENY (listed events are
> rejected); nevents == 0 clears any active filter.
>
> The filter is enforced in kvm_riscv_vcpu_pmu_ctr_cfg_match(), where a
> disallowed event fails configuration with SBI_ERR_NOT_SUPPORTED. It
> governs new counter configuration only and is not retroactive. The
> filter lives in struct kvm_arch, read via SRCU on the vCPU run path and
> replaced under kvm->lock with synchronize_srcu_expedited(). Event
> indices are masked to their valid bits before comparison, so reserved
> high bits cannot bypass a DENY filter.
>
> Advertise the feature with KVM_CAP_PMU_EVENT_FILTER.
>
> Assisted-by: YuanSheng:deepseek-v4-pro
> Co-developed-by: Quan Zhou <zhouquan at iscas.ac.cn>
> Signed-off-by: Quan Zhou <zhouquan at iscas.ac.cn>
> Signed-off-by: Yuhang.chen <yhchen312 at gmail.com>
> ---
> arch/riscv/include/asm/kvm_host.h | 3 ++
> arch/riscv/include/uapi/asm/kvm.h | 20 +++++++++
> arch/riscv/kvm/vcpu_pmu.c | 31 ++++++++++++++
> arch/riscv/kvm/vm.c | 69 ++++++++++++++++++++++++++++++-
> 4 files changed, 122 insertions(+), 1 deletion(-)
>
> diff --git a/arch/riscv/include/asm/kvm_host.h b/arch/riscv/include/asm/kvm_host.h
> index 60017ceec9d2..1cd3d6a11057 100644
> --- a/arch/riscv/include/asm/kvm_host.h
> +++ b/arch/riscv/include/asm/kvm_host.h
> @@ -95,6 +95,9 @@ struct kvm_arch {
>
> /* KVM_CAP_RISCV_MP_STATE_RESET */
> bool mp_state_reset;
> +
> + /* KVM_SET_PMU_EVENT_FILTER */
> + struct kvm_pmu_event_filter __rcu *pmu_event_filter;
> };
>
> struct kvm_cpu_trap {
> diff --git a/arch/riscv/include/uapi/asm/kvm.h b/arch/riscv/include/uapi/asm/kvm.h
> index 504e73305343..da4f639fa89f 100644
> --- a/arch/riscv/include/uapi/asm/kvm.h
> +++ b/arch/riscv/include/uapi/asm/kvm.h
> @@ -12,6 +12,7 @@
> #ifndef __ASSEMBLER__
>
> #include <linux/types.h>
> +#include <linux/stddef.h>
> #include <asm/bitsperlong.h>
> #include <asm/ptrace.h>
>
> @@ -396,6 +397,25 @@ struct kvm_riscv_sbi_fwft {
> /* One single KVM irqchip, ie. the AIA */
> #define KVM_NR_IRQCHIPS 1
>
> +/* for KVM_CAP_PMU_EVENT_FILTER */
> +#define KVM_PMU_EVENT_ALLOW 0
> +#define KVM_PMU_EVENT_DENY 1
> +
> +/*
> + * For KVM_SET_PMU_EVENT_FILTER: restrict which SBI PMU events a guest may
> + * configure. Each @events entry is a SBI PMU event index (type in bits
> + * 19:16, code in bits 15:0). %KVM_PMU_EVENT_ALLOW permits only listed
> + * events; %KVM_PMU_EVENT_DENY rejects them. Enforced at counter
> + * configuration (SBI PMU COUNTER_CFG_MATCH), not retroactively.
> + */
> +struct kvm_pmu_event_filter {
> + __u32 action;
> + __u32 nevents;
> + __u32 flags;
> + __u32 pad;
> + __DECLARE_FLEX_ARRAY(__u64, events);
> +};
> +
This definition of kvm_pmu_event_filter does not cover SBI PMU
RAW and RAWv2 events.
Maybe you can go with something like below:
struct kvm_pmu_event {
__u32 pad;
__u32 event_idx;
__u64 event_data;
};
struct kvm_pmu_event_filter {
#define KVM_PMU_EVENT_ACTION_ALLOW 0
#define KVM_PMU_EVENT_ACTION_DENY 1
__u32 action;
__u32 nevents;
__u32 pad[2];
__DECLARE_FLEX_ARRAY(struct kvm_pmu_event, events);
};
> #endif
>
> #endif /* __LINUX_KVM_RISCV_H */
> diff --git a/arch/riscv/kvm/vcpu_pmu.c b/arch/riscv/kvm/vcpu_pmu.c
> index bb46dcbfb24d..756040913468 100644
> --- a/arch/riscv/kvm/vcpu_pmu.c
> +++ b/arch/riscv/kvm/vcpu_pmu.c
> @@ -733,6 +733,32 @@ int kvm_riscv_vcpu_pmu_ctr_stop(struct kvm_vcpu *vcpu, unsigned long ctr_base,
> return 0;
> }
>
> +static bool kvm_riscv_pmu_event_allowed(struct kvm *kvm, unsigned long eidx)
> +{
> + struct kvm_pmu_event_filter *filter;
> + bool in_list = false;
> + unsigned int i;
> +
> + /* Reserved high bits must not bypass the filter. */
> + eidx &= SBI_PMU_EVENT_IDX_MASK;
> +
> + filter = srcu_dereference(kvm->arch.pmu_event_filter, &kvm->srcu);
> + if (!filter)
> + return true;
> +
> + for (i = 0; i < filter->nevents; i++) {
> + if ((unsigned long)filter->events[i] == eidx) {
> + in_list = true;
> + break;
> + }
> + }
> +
> + /* ALLOW: permit only listed events; DENY: reject them. */
> + if (filter->action == KVM_PMU_EVENT_ALLOW)
> + return in_list;
> + return !in_list;
> +}
> +
> int kvm_riscv_vcpu_pmu_ctr_cfg_match(struct kvm_vcpu *vcpu, unsigned long ctr_base,
> unsigned long ctr_mask, unsigned long flags,
> unsigned long eidx, u64 evtdata,
> @@ -773,6 +799,11 @@ int kvm_riscv_vcpu_pmu_ctr_cfg_match(struct kvm_vcpu *vcpu, unsigned long ctr_ba
> goto out;
> }
>
> + if (!kvm_riscv_pmu_event_allowed(vcpu->kvm, eidx)) {
> + sbiret = SBI_ERR_NOT_SUPPORTED;
> + goto out;
> + }
> +
> /*
> * SKIP_MATCH flag indicates the caller is aware of the assigned counter
> * for this event. Just do a sanity check if it already marked used.
> diff --git a/arch/riscv/kvm/vm.c b/arch/riscv/kvm/vm.c
> index a9f083feeb76..a5a3a2182d1e 100644
> --- a/arch/riscv/kvm/vm.c
> +++ b/arch/riscv/kvm/vm.c
> @@ -53,6 +53,8 @@ void kvm_arch_destroy_vm(struct kvm *kvm)
> {
> kvm_destroy_vcpus(kvm);
>
> + kfree(srcu_dereference_check(kvm->arch.pmu_event_filter, &kvm->srcu, 1));
> +
> kvm_riscv_aia_destroy_vm(kvm);
> }
>
> @@ -187,6 +189,7 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
> case KVM_CAP_MP_STATE:
> case KVM_CAP_IMMEDIATE_EXIT:
> case KVM_CAP_SET_GUEST_DEBUG:
> + case KVM_CAP_PMU_EVENT_FILTER:
> r = 1;
> break;
> case KVM_CAP_NR_VCPUS:
> @@ -265,7 +268,71 @@ int kvm_vm_ioctl_enable_cap(struct kvm *kvm, struct kvm_enable_cap *cap)
> }
> }
>
> +#define KVM_PMU_EVENT_FILTER_MAX_EVENTS 256
> +
> +static int kvm_riscv_vm_ioctl_set_pmu_event_filter(struct kvm *kvm,
> + void __user *argp)
> +{
> + struct kvm_pmu_event_filter __user *user_filter = argp;
> + struct kvm_pmu_event_filter *filter, tmp;
> + size_t size;
> + int r = 0;
> +
> + if (copy_from_user(&tmp, user_filter, sizeof(tmp)))
> + return -EFAULT;
> +
> + if (tmp.action != KVM_PMU_EVENT_ALLOW &&
> + tmp.action != KVM_PMU_EVENT_DENY)
> + return -EINVAL;
> +
> + if (tmp.flags)
> + return -EINVAL;
> +
> + if (tmp.pad)
> + return -EINVAL;
> +
> + if (tmp.nevents > KVM_PMU_EVENT_FILTER_MAX_EVENTS)
> + return -E2BIG;
> +
> + size = struct_size(filter, events, tmp.nevents);
> + filter = kzalloc(size, GFP_KERNEL_ACCOUNT);
> + if (!filter)
> + return -ENOMEM;
> +
> + filter->action = tmp.action;
> + filter->nevents = tmp.nevents;
> + filter->flags = tmp.flags;
> +
> + if (copy_from_user(filter->events, user_filter->events,
> + flex_array_size(filter, events, filter->nevents))) {
> + r = -EFAULT;
> + goto cleanup;
> + }
> +
> + mutex_lock(&kvm->lock);
> + filter = rcu_replace_pointer(kvm->arch.pmu_event_filter, filter,
> + mutex_is_locked(&kvm->lock));
> + mutex_unlock(&kvm->lock);
> + synchronize_srcu_expedited(&kvm->srcu);
> +
> +cleanup:
> + kfree(filter);
> + return r;
> +}
> +
> int kvm_arch_vm_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg)
> {
> - return -EINVAL;
> + struct kvm *kvm = filp->private_data;
> + void __user *argp = (void __user *)arg;
> + int r;
> +
> + switch (ioctl) {
> + case KVM_SET_PMU_EVENT_FILTER:
> + r = kvm_riscv_vm_ioctl_set_pmu_event_filter(kvm, argp);
> + break;
> + default:
> + r = -EINVAL;
> + }
> +
> + return r;
> }
> --
> 2.34.1
>
Regards,
Anup
More information about the linux-riscv
mailing list