[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