Re: [PATCH v10 7/9] perf: RISC-V: add support for SSE event
From: Himanshu Chauhan
Date: Fri Sep 18 2026 - 01:32:40 EST
Hi Zhanpeng Zhang,
On Fri, Sep 11, 2026 at 08:14:54PM +0800, Zhanpeng Zhang wrote:
> From: Clément Léger <cleger@xxxxxxxxxxxx>
>
> Register a handler for the local PMU overflow SSE event so that RISC-V
> perf can receive overflows even when normal S-mode interrupts are masked.
> Reuse the existing overflow handler and pass it the interrupted pt_regs
> rebuilt by the architecture SSE entry path.
>
> Select the delivery mechanism once during PMU probe. Prefer SSE when its
> event can be registered and enabled. If the extension or PMU event is
> explicitly unsupported, use the ordinary PMU interrupt. Do not enable the
> interrupt after any other SSE setup failure or when a crash kernel may
> have inherited firmware state. Install the PMU enable and disable
> callbacks only after SSE delivery becomes active.
>
> Keep the local PMU SSE event disabled across CPU power management. On
> entry, the generic SSE notifier masks the hart before the lower-priority
> PMU notifier disables the event and stops the counters. On exit, the SSE
> notifier first unmasks the hart while the event remains disabled. The PMU
> notifier then restores counters and event userpage state before enabling
> the event. An unmask failure stops the notifier chain and leaves the
> counters stopped. Ordinary PMU interrupts retain their existing notifier
> ordering.
>
> An SSE overflow can arrive as soon as the event is enabled during probe.
> Publish the counter mask before SSE setup, so an early handler can stop
> the counter source even before perf starts admitting normal samples.
> After a real overflow, restart only counters whose perf state is still
> running. Honor a non-zero return from perf_event_overflow() and leave
> throttled events stopped.
>
> Guest attribution is not part of this version. Detect an interrupted guest
> from hstatus.SPV and skip its sample while still updating the period and
> counter state, rather than exposing guest state as a host sample.
>
> The perf PMU callbacks cannot return errors. If an SSE transition or
> interrupted-context read fails, latch the failure per CPU and stop its
> mapped events through the normal perf state transitions. Do not reset the
> firmware counter mapping behind perf, restart a failed event, or attempt a
> runtime switch to IRQ delivery.
>
> During cleanup, close callback admission and synchronously drain each CPU
> before disabling and unregistering the SSE event. If firmware refuses the
> cleanup, stop the counter source and transfer the event to the SSE core so
> later CPU hotplug or shutdown processing can retry without using freed PMU
> callback state.
>
> Signed-off-by: Clément Léger <cleger@xxxxxxxxxxxx>
> Co-developed-by: Himanshu Chauhan <himanshu.chauhan@xxxxxxxxxxxxxxxx>
> Signed-off-by: Himanshu Chauhan <himanshu.chauhan@xxxxxxxxxxxxxxxx>
> Co-developed-by: Zhanpeng Zhang <zhangzhanpeng.jasper@xxxxxxxxxxxxx>
> Signed-off-by: Zhanpeng Zhang <zhangzhanpeng.jasper@xxxxxxxxxxxxx>
> ---
> Documentation/arch/riscv/index.rst | 1 +
> Documentation/arch/riscv/pmu-sse.rst | 55 +++
> MAINTAINERS | 1 +
> drivers/firmware/riscv/riscv_sbi_sse.c | 9 +
> drivers/perf/Kconfig | 11 +
> drivers/perf/riscv_pmu.c | 14 +-
> drivers/perf/riscv_pmu_sbi.c | 544 ++++++++++++++++++++-----
> include/linux/perf/riscv_pmu.h | 20 +-
> include/linux/riscv_sbi_sse.h | 6 +
> 9 files changed, 566 insertions(+), 95 deletions(-)
> create mode 100644 Documentation/arch/riscv/pmu-sse.rst
>
Thanks for sending v10. I was waiting on your series so that I can send the RAS series
based on it. I was trying to apply the series on v7.3-rc3 but this particular patch fails.
I used b4 shazam to pull the series.
Could you once check?
Thanks
Regards
Himanshu
> diff --git a/Documentation/arch/riscv/index.rst b/Documentation/arch/riscv/index.rst
> index ac535c52d509..5cb909c83a88 100644
> --- a/Documentation/arch/riscv/index.rst
> +++ b/Documentation/arch/riscv/index.rst
> @@ -11,6 +11,7 @@ RISC-V architecture
> vm-layout
> hwprobe
> patch-acceptance
> + pmu-sse
> uabi
> vector
> cmodx
> diff --git a/Documentation/arch/riscv/pmu-sse.rst b/Documentation/arch/riscv/pmu-sse.rst
> new file mode 100644
> index 000000000000..b7458a9d0116
> --- /dev/null
> +++ b/Documentation/arch/riscv/pmu-sse.rst
> @@ -0,0 +1,55 @@
> +.. SPDX-License-Identifier: GPL-2.0
> +
> +========================================
> +RISC-V PMU overflow delivery through SSE
> +========================================
> +
> +When ``CONFIG_RISCV_PMU_SBI_SSE`` is enabled and firmware provides the local
> +PMU overflow event, the RISC-V SBI PMU driver uses Supervisor Software Events
> +(SSE) to deliver counter overflows.
> +
> +Delivery selection
> +==================
> +
> +The delivery mechanism is selected once while the PMU device is probed. The
> +driver first tries to register and enable the local PMU overflow SSE event. It
> +tries the ordinary PMU interrupt only when the SSE extension or the local PMU
> +event is explicitly unsupported. It does not change the delivery mechanism
> +after the PMU has been registered.
> +
> +Other SSE setup errors do not prove that firmware released the overflow route,
> +so the driver does not enable the ordinary PMU interrupt. The PMU remains
> +available for counting but not sampling. If setup retained an SSE event, the
> +PMU keeps quiescing that event around perf scheduling changes without rearming
> +it. This preserves callback ownership without creating two possible delivery
> +mechanisms for the same hardware overflow.
> +
> +Interrupted context
> +===================
> +
> +SSE enters Linux through a supervisor handler context constructed by firmware.
> +The entry contract preserves the interrupted GPRs except ``a6`` and ``a7``,
> +which Linux reads from the interrupted-register event attributes. Linux then
> +reconstructs the interrupted ``pt_regs`` and publishes it while dispatching the
> +event.
> +
> +Perf uses that context for register samples and callchains. Kernel stack
> +walking verifies that the interrupted frame belongs to the current task stack
> +before dereferencing it. For sensitive entry paths and IRQ stacks, Linux
> +records the interrupted PC without walking a stack whose bounds cannot be
> +proved. User callchains use the existing no-fault user unwinder. A DWARF
> +raw user-stack copy from an SSE handler must not take a page fault, so it
> +walks the current task's page tables with fast-only GUP, copies each resident
> +page through its kernel mapping, and stops at the first non-resident page,
> +preserving perf's truncated-user-stack semantics.
> +
> +CPU power management
> +====================
> +
> +The PMU and SSE CPU power-management callbacks are ordered according to the
> +selected delivery mechanism. With SSE delivery, SSE events are masked before
> +the lower-priority PMU callback disables the local PMU event and stops the
> +counters on entry. On exit, the hart is unmasked while the local PMU event is
> +still disabled. The PMU callback then restores the counters and enables the
> +event, so an unmask failure leaves the counters stopped. The ordinary
> +interrupt path retains the existing PMU ordering.
> diff --git a/MAINTAINERS b/MAINTAINERS
> index d11cd44d2449..91b4e32a1fb2 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -23392,6 +23392,7 @@ M: Atish Patra <atish.patra@xxxxxxxxx>
> R: Anup Patel <anup@xxxxxxxxxxxxxx>
> L: linux-riscv@xxxxxxxxxxxxxxxxxxx
> S: Supported
> +F: Documentation/arch/riscv/pmu-sse.rst
> F: drivers/perf/riscv_pmu.c
> F: drivers/perf/riscv_pmu_legacy.c
> F: drivers/perf/riscv_pmu_sbi.c
> diff --git a/drivers/firmware/riscv/riscv_sbi_sse.c b/drivers/firmware/riscv/riscv_sbi_sse.c
> index e81cfb4b0c56..c7d2a2def00a 100644
> --- a/drivers/firmware/riscv/riscv_sbi_sse.c
> +++ b/drivers/firmware/riscv/riscv_sbi_sse.c
> @@ -161,6 +161,15 @@ static int sse_sbi_event_func(struct sse_event *event, unsigned long func)
> return 0;
> }
>
> +bool sse_event_is_enabled_local(struct sse_event *event)
> +{
> + if (!sse_event_is_global(event->evt_id))
> + lockdep_assert_preemption_disabled();
> +
> + return sse_get_reg_evt(event)->is_enabled;
> +}
> +EXPORT_SYMBOL_GPL(sse_event_is_enabled_local);
> +
> int sse_event_disable_local(struct sse_event *event)
> {
> if (!sse_event_is_global(event->evt_id))
> diff --git a/drivers/perf/Kconfig b/drivers/perf/Kconfig
> index 245e7bb763b9..10b047856121 100644
> --- a/drivers/perf/Kconfig
> +++ b/drivers/perf/Kconfig
> @@ -105,6 +105,17 @@ config RISCV_PMU_SBI
> full perf feature support i.e. counter overflow, privilege mode
> filtering, counter configuration.
>
> +config RISCV_PMU_SBI_SSE
> + depends on RISCV_PMU_SBI && RISCV_SBI_SSE
> + bool "RISC-V PMU SSE events"
> + default n
> + help
> + Say y if you want to use SSE events to deliver PMU interrupts. This
> + provides a way to profile the kernel at any level by using NMI-like
> + SSE events. SSE events being really intrusive, this option allows
> + to select it only if needed. See Documentation/arch/riscv/pmu-sse.rst
> + for interrupted-context limitations.
> +
> config STARFIVE_STARLINK_PMU
> depends on ARCH_STARFIVE || COMPILE_TEST
> depends on 64BIT
> diff --git a/drivers/perf/riscv_pmu.c b/drivers/perf/riscv_pmu.c
> index 8e3cd0f35336..1f65c640b693 100644
> --- a/drivers/perf/riscv_pmu.c
> +++ b/drivers/perf/riscv_pmu.c
> @@ -13,6 +13,7 @@
> #include <linux/irqdesc.h>
> #include <linux/perf/riscv_pmu.h>
> #include <linux/printk.h>
> +#include <linux/riscv_sbi_sse.h>
> #include <linux/smp.h>
> #include <linux/sched_clock.h>
>
> @@ -247,6 +248,11 @@ void riscv_pmu_start(struct perf_event *event, int flags)
> if (flags & PERF_EF_RELOAD)
> WARN_ON_ONCE(!(event->hw.state & PERF_HES_UPTODATE));
>
> +#ifdef CONFIG_RISCV_PMU_SBI_SSE
> + if (unlikely(this_cpu_ptr(rvpmu->hw_events)->sse_failed))
> + return;
> +#endif
> +
> hwc->state = 0;
> riscv_pmu_event_set_period(event);
> init_val = local64_read(&hwc->prev_count) & max_period;
> @@ -306,6 +312,7 @@ static int riscv_pmu_event_init(struct perf_event *event)
> struct hw_perf_event *hwc = &event->hw;
> struct riscv_pmu *rvpmu = to_riscv_pmu(event->pmu);
> int mapped_event;
> + int ret;
> u64 event_config = 0;
> uint64_t cmask;
>
> @@ -331,8 +338,11 @@ static int riscv_pmu_event_init(struct perf_event *event)
> hwc->idx = -1;
> hwc->event_base = mapped_event;
>
> - if (rvpmu->event_init)
> - rvpmu->event_init(event);
> + if (rvpmu->event_init) {
> + ret = rvpmu->event_init(event);
> + if (ret)
> + return ret;
> + }
>
> if (!is_sampling_event(event)) {
> /*
> diff --git a/drivers/perf/riscv_pmu_sbi.c b/drivers/perf/riscv_pmu_sbi.c
> index 50220f7b46d9..7e971b47730c 100644
> --- a/drivers/perf/riscv_pmu_sbi.c
> +++ b/drivers/perf/riscv_pmu_sbi.c
> @@ -16,13 +16,16 @@
> #include <linux/irqdomain.h>
> #include <linux/of_irq.h>
> #include <linux/of.h>
> +#include <linux/riscv_sbi_sse.h>
> #include <linux/cpu_pm.h>
> +#include <linux/crash_dump.h>
> #include <linux/sched/clock.h>
> #include <linux/soc/andes/irq.h>
> #include <linux/workqueue.h>
>
> #include <asm/errata_list.h>
> #include <asm/sbi.h>
> +#include <asm/sse.h>
> #include <asm/cpufeature.h>
> #include <asm/vendor_extensions.h>
> #include <asm/vendor_extensions/andes.h>
> @@ -95,7 +98,6 @@ static bool riscv_pmu_use_irq;
> static unsigned int riscv_pmu_irq_num;
> static unsigned int riscv_pmu_irq_mask;
> static unsigned int riscv_pmu_irq;
> -
> /* Cache the available counters in a bitmask */
> static unsigned long cmask;
>
> @@ -896,14 +898,24 @@ static int pmu_sbi_get_ctrinfo(int nctr, unsigned long *mask)
> return 0;
> }
>
> -static inline void pmu_sbi_stop_all(struct riscv_pmu *pmu)
> +static inline void pmu_sbi_stop_all_mask(unsigned long ctr_mask)
> {
> /*
> * No need to check the error because we are disabling all the counters
> * which may include counters that are not enabled yet.
> */
> sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_STOP,
> - 0, pmu->cmask, SBI_PMU_STOP_FLAG_RESET, 0, 0, 0);
> + 0, ctr_mask, SBI_PMU_STOP_FLAG_RESET, 0, 0, 0);
> +}
> +
> +static inline void pmu_sbi_stop_all(struct riscv_pmu *pmu)
> +{
> + pmu_sbi_stop_all_mask(pmu->cmask);
> +}
> +
> +static void pmu_sbi_stop_all_cpu(void *info)
> +{
> + pmu_sbi_stop_all(info);
> }
>
> static inline void pmu_sbi_stop_hw_ctrs(struct riscv_pmu *pmu)
> @@ -953,7 +965,7 @@ static inline void pmu_sbi_stop_hw_ctrs(struct riscv_pmu *pmu)
> static inline void pmu_sbi_start_ovf_ctrs_sbi(struct cpu_hw_events *cpu_hw_evt,
> u64 ctr_ovf_mask)
> {
> - int idx = 0, i;
> + int idx, i;
> struct perf_event *event;
> unsigned long flag = SBI_PMU_START_FLAG_SET_INIT_VALUE;
> unsigned long ctr_start_mask = 0;
> @@ -962,7 +974,19 @@ static inline void pmu_sbi_start_ovf_ctrs_sbi(struct cpu_hw_events *cpu_hw_evt,
> u64 init_val = 0;
>
> for (i = 0; i < BITS_TO_LONGS(RISCV_MAX_COUNTERS); i++) {
> - ctr_start_mask = cpu_hw_evt->used_hw_ctrs[i] & ~ctr_ovf_mask;
> + unsigned long ctr_ovf_mask_word;
> + int lidx;
> +
> + ctr_ovf_mask_word = ctr_ovf_mask >> (i * BITS_PER_LONG);
> + ctr_start_mask = 0;
> + for_each_set_bit(idx, &cpu_hw_evt->used_hw_ctrs[i], BITS_PER_LONG) {
> + lidx = idx + i * BITS_PER_LONG;
> + event = cpu_hw_evt->events[lidx];
> + if (event && !(event->hw.state & PERF_HES_STOPPED))
> + ctr_start_mask |= BIT(idx);
> + }
> + ctr_start_mask &= ~ctr_ovf_mask_word;
> +
> /* Start all the counters that did not overflow in a single shot */
> if (ctr_start_mask) {
> sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_START, i * BITS_PER_LONG,
> @@ -971,23 +995,25 @@ static inline void pmu_sbi_start_ovf_ctrs_sbi(struct cpu_hw_events *cpu_hw_evt,
> }
>
> /* Reinitialize and start all the counter that overflowed */
> - while (ctr_ovf_mask) {
> - if (ctr_ovf_mask & 0x01) {
> - event = cpu_hw_evt->events[idx];
> - hwc = &event->hw;
> - max_period = riscv_pmu_ctr_get_width_mask(event);
> - init_val = local64_read(&hwc->prev_count) & max_period;
> + for (idx = 0; idx < RISCV_MAX_COUNTERS; idx++) {
> + if (!(ctr_ovf_mask & BIT_ULL(idx)))
> + continue;
> +
> + event = cpu_hw_evt->events[idx];
> + if (!event || event->hw.state & PERF_HES_STOPPED)
> + continue;
> +
> + hwc = &event->hw;
> + max_period = riscv_pmu_ctr_get_width_mask(event);
> + init_val = local64_read(&hwc->prev_count) & max_period;
> #if defined(CONFIG_32BIT)
> - sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_START, idx, 1,
> - flag, init_val, init_val >> 32, 0);
> + sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_START, idx, 1,
> + flag, init_val, init_val >> 32, 0);
> #else
> - sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_START, idx, 1,
> - flag, init_val, 0, 0);
> + sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_START, idx, 1,
> + flag, init_val, 0, 0);
> #endif
> - perf_event_update_userpage(event);
> - }
> - ctr_ovf_mask = ctr_ovf_mask >> 1;
> - idx++;
> + perf_event_update_userpage(event);
> }
> }
>
> @@ -1002,7 +1028,7 @@ static inline void pmu_sbi_start_ovf_ctrs_snapshot(struct cpu_hw_events *cpu_hw_
> struct riscv_pmu_snapshot_data *sdata = cpu_hw_evt->snapshot_addr;
>
> for_each_set_bit(idx, cpu_hw_evt->used_hw_ctrs, RISCV_MAX_COUNTERS) {
> - if (ctr_ovf_mask & BIT(idx)) {
> + if (ctr_ovf_mask & BIT_ULL(idx)) {
> event = cpu_hw_evt->events[idx];
> hwc = &event->hw;
> max_period = riscv_pmu_ctr_get_width_mask(event);
> @@ -1016,31 +1042,111 @@ static inline void pmu_sbi_start_ovf_ctrs_snapshot(struct cpu_hw_events *cpu_hw_
> }
>
> for (i = 0; i < BITS_TO_LONGS(RISCV_MAX_COUNTERS); i++) {
> + unsigned long ctr_start_mask = 0;
> + int lidx;
> +
> /* Restore the counter values to relative indices for used hw counters */
> - for_each_set_bit(idx, &cpu_hw_evt->used_hw_ctrs[i], BITS_PER_LONG)
> - sdata->ctr_values[idx] =
> - cpu_hw_evt->snapshot_cval_shcopy[idx + i * BITS_PER_LONG];
> + for_each_set_bit(idx, &cpu_hw_evt->used_hw_ctrs[i], BITS_PER_LONG) {
> + lidx = idx + i * BITS_PER_LONG;
> + event = cpu_hw_evt->events[lidx];
> + if (event && !(event->hw.state & PERF_HES_STOPPED))
> + ctr_start_mask |= BIT(idx);
> +
> + sdata->ctr_values[idx] = cpu_hw_evt->snapshot_cval_shcopy[lidx];
> + }
> +
> /* Start all the counters in a single shot */
> - sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_START, idx * BITS_PER_LONG,
> - cpu_hw_evt->used_hw_ctrs[i], flag, 0, 0, 0);
> + if (ctr_start_mask)
> + sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_START,
> + i * BITS_PER_LONG, ctr_start_mask, flag, 0, 0, 0);
> }
> }
>
> +static bool pmu_sbi_sse_failed(struct cpu_hw_events *cpu_hw_evt)
> +{
> +#ifdef CONFIG_RISCV_PMU_SBI_SSE
> + return READ_ONCE(cpu_hw_evt->sse_failed);
> +#else
> + return false;
> +#endif
> +}
> +
> static void pmu_sbi_start_overflow_mask(struct riscv_pmu *pmu,
> u64 ctr_ovf_mask)
> {
> struct cpu_hw_events *cpu_hw_evt = this_cpu_ptr(pmu->hw_events);
>
> + if (unlikely(pmu_sbi_sse_failed(cpu_hw_evt)))
> + return;
> +
> if (sbi_pmu_snapshot_available())
> pmu_sbi_start_ovf_ctrs_snapshot(cpu_hw_evt, ctr_ovf_mask);
> else
> pmu_sbi_start_ovf_ctrs_sbi(cpu_hw_evt, ctr_ovf_mask);
> }
>
> -static irqreturn_t pmu_sbi_ovf_handler(int irq, void *dev)
> +#ifdef CONFIG_RISCV_PMU_SBI_SSE
> +/*
> + * A local SSE delivery failure makes the current PMU state unsafe to resume.
> + * Latch the failure before stopping mapped events so the SSE transition and
> + * overflow restart paths cannot undo the fail-safe while they are quiesced.
> + */
> +static void pmu_sbi_fail_sse(struct riscv_pmu *pmu, const char *op, int ret)
> +{
> + struct cpu_hw_events *cpu_hw_evt = this_cpu_ptr(pmu->hw_events);
> + struct perf_event *event;
> + int idx;
> +
> + if (READ_ONCE(cpu_hw_evt->sse_failed))
> + return;
> +
> + WRITE_ONCE(cpu_hw_evt->sse_failed, true);
> + pr_err_ratelimited("failed to %s local PMU SSE event: %d; stopping counters\n",
> + op, ret);
> +
> + for (idx = 0; idx < RISCV_MAX_COUNTERS; idx++) {
> + event = cpu_hw_evt->events[idx];
> + if (event)
> + riscv_pmu_stop(event, PERF_EF_UPDATE);
> + }
> +}
> +
> +static void pmu_sbi_sse_disable(struct pmu *pmu)
> +{
> + struct riscv_pmu *rvpmu = to_riscv_pmu(pmu);
> + struct cpu_hw_events *cpu_hw_evt = this_cpu_ptr(rvpmu->hw_events);
> + int ret;
> +
> + if (!READ_ONCE(rvpmu->sse_active) ||
> + READ_ONCE(cpu_hw_evt->sse_failed))
> + return;
> +
> + ret = sse_event_disable_local(rvpmu->sse_evt);
> + if (unlikely(ret))
> + pmu_sbi_fail_sse(rvpmu, "disable", ret);
> +}
> +
> +static void pmu_sbi_sse_enable(struct pmu *pmu)
> +{
> + struct riscv_pmu *rvpmu = to_riscv_pmu(pmu);
> + struct cpu_hw_events *cpu_hw_evt = this_cpu_ptr(rvpmu->hw_events);
> + int ret;
> +
> + if (!READ_ONCE(rvpmu->sse_active) ||
> + READ_ONCE(cpu_hw_evt->sse_failed))
> + return;
> +
> + ret = sse_event_enable_local(rvpmu->sse_evt);
> + if (unlikely(ret))
> + pmu_sbi_fail_sse(rvpmu, "enable", ret);
> +}
> +#endif
> +
> +static irqreturn_t pmu_sbi_ovf_handler(struct cpu_hw_events *cpu_hw_evt,
> + struct pt_regs *regs, bool from_sse,
> + bool from_guest)
> {
> struct perf_sample_data data;
> - struct pt_regs *regs;
> struct hw_perf_event *hw_evt;
> union sbi_pmu_ctr_info *info;
> int lidx, hidx, fidx;
> @@ -1048,28 +1154,38 @@ static irqreturn_t pmu_sbi_ovf_handler(int irq, void *dev)
> struct perf_event *event;
> u64 overflow;
> u64 overflowed_ctrs = 0;
> - struct cpu_hw_events *cpu_hw_evt = dev;
> u64 start_clock = sched_clock();
> struct riscv_pmu_snapshot_data *sdata = cpu_hw_evt->snapshot_addr;
>
> if (WARN_ON_ONCE(!cpu_hw_evt))
> return IRQ_NONE;
>
> - /* Firmware counter don't support overflow yet */
> + /*
> + * SSE can arrive before perf installs an event. The early exits below
> + * must stop the PMU source before firmware completes the SSE.
> + */
> fidx = find_first_bit(cpu_hw_evt->used_hw_ctrs, RISCV_MAX_COUNTERS);
> if (fidx == RISCV_MAX_COUNTERS) {
> - csr_clear(CSR_SIP, BIT(riscv_pmu_irq_num));
> + if (from_sse)
> + pmu_sbi_stop_all_mask(cmask);
> + else
> + csr_clear(CSR_SIP, BIT(riscv_pmu_irq_num));
> return IRQ_NONE;
> }
>
> event = cpu_hw_evt->events[fidx];
> if (!event) {
> - ALT_SBI_PMU_OVF_CLEAR_PENDING(riscv_pmu_irq_mask);
> + if (from_sse)
> + pmu_sbi_stop_all_mask(cmask);
> + else
> + ALT_SBI_PMU_OVF_CLEAR_PENDING(riscv_pmu_irq_mask);
> return IRQ_NONE;
> }
>
> pmu = to_riscv_pmu(event->pmu);
> pmu_sbi_stop_hw_ctrs(pmu);
> + if (unlikely(pmu_sbi_sse_failed(cpu_hw_evt)))
> + return IRQ_NONE;
>
> /* Overflow status register should only be read after counter are stopped */
> if (sbi_pmu_snapshot_available())
> @@ -1079,15 +1195,17 @@ static irqreturn_t pmu_sbi_ovf_handler(int irq, void *dev)
>
> /*
> * Overflow interrupt pending bit should only be cleared after stopping
> - * all the counters to avoid any race condition.
> + * all the counters to avoid any race condition. When using SSE,
> + * interrupt is cleared when stopping counters.
> */
> - ALT_SBI_PMU_OVF_CLEAR_PENDING(riscv_pmu_irq_mask);
> + if (!from_sse)
> + ALT_SBI_PMU_OVF_CLEAR_PENDING(riscv_pmu_irq_mask);
>
> /* No overflow bit is set */
> - if (!overflow)
> + if (!overflow) {
> + pmu_sbi_start_overflow_mask(pmu, 0);
> return IRQ_NONE;
> -
> - regs = get_irq_regs();
> + }
>
> for_each_set_bit(lidx, cpu_hw_evt->used_hw_ctrs, RISCV_MAX_COUNTERS) {
> struct perf_event *event = cpu_hw_evt->events[lidx];
> @@ -1109,14 +1227,20 @@ static irqreturn_t pmu_sbi_ovf_handler(int irq, void *dev)
> hidx = info->csr - CSR_CYCLE;
>
> /* check if the corresponding bit is set in scountovf or overflow mask in shmem */
> - if (!(overflow & BIT(hidx)))
> + if (!(overflow & BIT_ULL(hidx)))
> continue;
>
> +#ifdef CONFIG_CPU_PM
> + /* Do not let CPU-PM resume override this overflow decision. */
> + if (from_sse)
> + clear_bit(lidx, cpu_hw_evt->pm_resume_hw_ctrs);
> +#endif
> +
> /*
> * Keep a track of overflowed counters so that they can be started
> * with updated initial value.
> */
> - overflowed_ctrs |= BIT(lidx);
> + overflowed_ctrs |= BIT_ULL(lidx);
> hw_evt = &event->hw;
> /* Update the event states here so that we know the state while reading */
> hw_evt->state |= PERF_HES_STOPPED;
> @@ -1124,6 +1248,14 @@ static irqreturn_t pmu_sbi_ovf_handler(int irq, void *dev)
> hw_evt->state |= PERF_HES_UPTODATE;
> perf_sample_data_init(&data, 0, hw_evt->last_period);
> if (riscv_pmu_event_set_period(event)) {
> + int overflow_ret;
> +
> + /* Guest attribution and guest stack sampling are not supported yet. */
> + if (from_guest) {
> + hw_evt->state = 0;
> + continue;
> + }
> +
> /*
> * Unlike other ISAs, RISC-V don't have to disable interrupts
> * to avoid throttling here. As per the specification, the
> @@ -1132,128 +1264,291 @@ static irqreturn_t pmu_sbi_ovf_handler(int irq, void *dev)
> * TODO: We will need to stop the guest counters once
> * virtualization support is added.
> */
> - perf_event_overflow(event, &data, regs);
> + overflow_ret = perf_event_overflow(event, &data, regs);
> + if (!overflow_ret)
> + hw_evt->state = 0;
> + } else {
> + hw_evt->state = 0;
> }
> - /* Reset the state as we are going to start the counter after the loop */
> - hw_evt->state = 0;
> }
>
> pmu_sbi_start_overflow_mask(pmu, overflowed_ctrs);
> +
> perf_sample_event_took(sched_clock() - start_clock);
>
> return IRQ_HANDLED;
> }
>
> -static int pmu_sbi_starting_cpu(unsigned int cpu, struct hlist_node *node)
> +static irqreturn_t pmu_sbi_ovf_irq_handler(int irq, void *dev)
> {
> - struct riscv_pmu *pmu = hlist_entry_safe(node, struct riscv_pmu, node);
> - struct cpu_hw_events *cpu_hw_evt = this_cpu_ptr(pmu->hw_events);
> -
> - /*
> - * We keep enabling userspace access to CYCLE, TIME and INSTRET via the
> - * legacy option but that will be removed in the future.
> - */
> - if (sysctl_perf_user_access == SYSCTL_LEGACY)
> - csr_write(CSR_SCOUNTEREN, 0x7);
> - else
> - csr_write(CSR_SCOUNTEREN, 0x2);
> + return pmu_sbi_ovf_handler(dev, get_irq_regs(), false, false);
> +}
>
> - /* Stop all the counters so that they can be enabled from perf */
> - pmu_sbi_stop_all(pmu);
> +#ifdef CONFIG_RISCV_PMU_SBI_SSE
> +static int pmu_sbi_ovf_sse_handler(u32 evt, void *arg, struct pt_regs *regs)
> +{
> + const struct riscv_sse_interrupted_context *context;
> + struct riscv_pmu *pmu = arg;
> + struct cpu_hw_events *hw_event = raw_cpu_ptr(pmu->hw_events);
> + bool from_guest;
> +
> + if (unlikely(!READ_ONCE(pmu->sse_active))) {
> + pmu_sbi_stop_all(pmu);
> + return -EIO;
> + }
>
> - if (riscv_pmu_use_irq) {
> - cpu_hw_evt->irq = riscv_pmu_irq;
> - ALT_SBI_PMU_OVF_CLEAR_PENDING(riscv_pmu_irq_mask);
> - enable_percpu_irq(riscv_pmu_irq, IRQ_TYPE_NONE);
> + if (unlikely(!regs)) {
> + pmu_sbi_fail_sse(pmu, "read interrupted context for", -EIO);
> + return -EIO;
> }
>
> - if (sbi_pmu_snapshot_available())
> - return pmu_sbi_snapshot_setup(pmu, cpu);
> + context = riscv_sse_get_interrupted_context();
> + from_guest = context && context->regs == regs &&
> + (context->hstatus & HSTATUS_SPV);
> + pmu_sbi_ovf_handler(hw_event, regs, true, from_guest);
>
> return 0;
> }
>
> -static int pmu_sbi_dying_cpu(unsigned int cpu, struct hlist_node *node)
> +static int pmu_sbi_setup_sse(struct riscv_pmu *pmu)
> {
> - if (riscv_pmu_use_irq) {
> - disable_percpu_irq(riscv_pmu_irq);
> - }
> + int ret;
> + struct sse_event *evt;
>
> - /* Disable all counters access for user mode now */
> - csr_write(CSR_SCOUNTEREN, 0x0);
> + evt = sse_event_register(SBI_SSE_EVENT_LOCAL_PMU_OVERFLOW, 0,
> + pmu_sbi_ovf_sse_handler, pmu);
> + if (IS_ERR(evt))
> + return PTR_ERR(evt);
> + pmu->sse_evt = evt;
>
> - if (sbi_pmu_snapshot_available())
> - return pmu_sbi_snapshot_disable();
> + ret = sse_event_enable(evt);
> + if (ret) {
> + int cleanup_ret;
>
> - return 0;
> + cleanup_ret = sse_event_disable(evt);
> + if (cleanup_ret) {
> + pr_warn("failed to disable SSE event after setup error: %d\n",
> + cleanup_ret);
> + return cleanup_ret;
> + }
> +
> + cleanup_ret = sse_event_unregister(evt);
> +
> + if (cleanup_ret) {
> + pr_warn("failed to unregister SSE event: %d\n",
> + cleanup_ret);
> + } else {
> + pmu->sse_evt = NULL;
> + }
> + return cleanup_ret ?: ret;
> + }
> +
> + WRITE_ONCE(pmu->sse_active, true);
> + pr_info("using SSE for PMU event delivery\n");
> +
> + return ret;
> }
>
> -static int pmu_sbi_setup_irqs(struct riscv_pmu *pmu, struct platform_device *pdev)
> +static void pmu_sbi_cleanup_sse(struct riscv_pmu *pmu)
> {
> + struct sse_event *sse_evt;
> int ret;
> - struct cpu_hw_events __percpu *hw_events = pmu->hw_events;
> - struct irq_domain *domain = NULL;
>
> + sse_evt = pmu->sse_evt;
> + if (!sse_evt)
> + return;
> + /*
> + * Close callback admission before draining each CPU. PMU callbacks run
> + * with local interrupts disabled, so the synchronous IPI cannot complete
> + * until a callback that observed the old state has returned.
> + */
> + WRITE_ONCE(pmu->sse_active, false);
> + on_each_cpu(pmu_sbi_stop_all_cpu, pmu, 1);
> +
> + ret = sse_event_disable(sse_evt);
> + if (ret) {
> + pr_warn("failed to disable SSE event: %d\n", ret);
> + goto retain;
> + }
> +
> + ret = sse_event_unregister(sse_evt);
> + if (ret) {
> + pr_warn("failed to unregister SSE event: %d\n", ret);
> + goto retain;
> + }
> +
> + pmu->sse_evt = NULL;
> + return;
> +
> +retain:
> + sse_event_cleanup(sse_evt);
> + pmu->sse_evt = NULL;
> +}
> +
> +static bool pmu_sbi_sse_state_retained(struct riscv_pmu *pmu)
> +{
> + return pmu->sse_evt;
> +}
> +#else
> +static int pmu_sbi_setup_sse(struct riscv_pmu *pmu)
> +{
> + return -EOPNOTSUPP;
> +}
> +
> +static void pmu_sbi_cleanup_sse(struct riscv_pmu *pmu) {}
> +
> +static bool pmu_sbi_sse_state_retained(struct riscv_pmu *pmu)
> +{
> + return false;
> +}
> +#endif
> +
> +static bool pmu_sbi_select_irq(void)
> +{
> if (riscv_isa_extension_available(NULL, SSCOFPMF)) {
> riscv_pmu_irq_num = RV_IRQ_PMU;
> - riscv_pmu_use_irq = true;
> + return true;
> } else if (IS_ENABLED(CONFIG_ERRATA_THEAD_PMU) &&
> riscv_cached_mvendorid(0) == THEAD_VENDOR_ID &&
> riscv_cached_marchid(0) == 0 &&
> riscv_cached_mimpid(0) == 0) {
> riscv_pmu_irq_num = THEAD_C9XX_RV_IRQ_PMU;
> - riscv_pmu_use_irq = true;
> + return true;
> } else if (riscv_has_vendor_extension_unlikely(ANDES_VENDOR_ID,
> RISCV_ISA_VENDOR_EXT_XANDESPMU) &&
> IS_ENABLED(CONFIG_ANDES_CUSTOM_PMU)) {
> riscv_pmu_irq_num = ANDES_SLI_CAUSE_BASE + ANDES_RV_IRQ_PMOVI;
> - riscv_pmu_use_irq = true;
> + return true;
> }
>
> - riscv_pmu_irq_mask = BIT(riscv_pmu_irq_num % BITS_PER_LONG);
> + return false;
> +}
>
> - if (!riscv_pmu_use_irq)
> +static int pmu_sbi_setup_irq(struct riscv_pmu *pmu)
> +{
> + struct cpu_hw_events __percpu *hw_events = pmu->hw_events;
> + struct irq_domain *domain;
> + int ret;
> +
> + if (!pmu_sbi_select_irq())
> return -EOPNOTSUPP;
>
> + riscv_pmu_irq_mask = BIT(riscv_pmu_irq_num % BITS_PER_LONG);
> +
> domain = irq_find_matching_fwnode(riscv_get_intc_hwnode(),
> DOMAIN_BUS_ANY);
> if (!domain) {
> pr_err("Failed to find INTC IRQ root domain\n");
> - ret = -ENODEV;
> - goto err;
> + return -ENODEV;
> }
>
> riscv_pmu_irq = irq_create_mapping(domain, riscv_pmu_irq_num);
> if (!riscv_pmu_irq) {
> pr_err("Failed to map PMU interrupt for node\n");
> - ret = -ENODEV;
> - goto err;
> + return -ENODEV;
> }
>
> - ret = request_percpu_irq(riscv_pmu_irq, pmu_sbi_ovf_handler, "riscv-pmu", hw_events);
> + ret = request_percpu_irq(riscv_pmu_irq, pmu_sbi_ovf_irq_handler,
> + "riscv-pmu", hw_events);
> if (ret) {
> pr_err("registering percpu irq failed [%d]\n", ret);
> irq_dispose_mapping(riscv_pmu_irq);
> riscv_pmu_irq = 0;
> - goto err;
> + return ret;
> }
>
> return 0;
> -err:
> - riscv_pmu_use_irq = false;
> - return ret;
> +}
> +
> +static int pmu_sbi_starting_cpu(unsigned int cpu, struct hlist_node *node)
> +{
> + struct riscv_pmu *pmu = hlist_entry_safe(node, struct riscv_pmu, node);
> + struct cpu_hw_events *cpu_hw_evt = this_cpu_ptr(pmu->hw_events);
> +
> + /*
> + * We keep enabling userspace access to CYCLE, TIME and INSTRET via the
> + * legacy option but that will be removed in the future.
> + */
> + if (sysctl_perf_user_access == SYSCTL_LEGACY)
> + csr_write(CSR_SCOUNTEREN, 0x7);
> + else
> + csr_write(CSR_SCOUNTEREN, 0x2);
> +
> + /* Stop all the counters so that they can be enabled from perf */
> + pmu_sbi_stop_all(pmu);
> +
> + if (riscv_pmu_use_irq) {
> + cpu_hw_evt->irq = riscv_pmu_irq;
> + ALT_SBI_PMU_OVF_CLEAR_PENDING(riscv_pmu_irq_mask);
> + enable_percpu_irq(riscv_pmu_irq, IRQ_TYPE_NONE);
> + }
> +
> + if (sbi_pmu_snapshot_available())
> + return pmu_sbi_snapshot_setup(pmu, cpu);
> +
> + return 0;
> +}
> +
> +static int pmu_sbi_dying_cpu(unsigned int cpu, struct hlist_node *node)
> +{
> + if (riscv_pmu_use_irq)
> + disable_percpu_irq(riscv_pmu_irq);
> +
> + /* Disable all counters access for user mode now */
> + csr_write(CSR_SCOUNTEREN, 0x0);
> +
> + if (sbi_pmu_snapshot_available())
> + return pmu_sbi_snapshot_disable();
> +
> + return 0;
> +}
> +
> +static int pmu_sbi_setup_irqs(struct riscv_pmu *pmu, struct platform_device *pdev)
> +{
> + int irq_ret;
> + int sse_ret;
> +
> + /* Do not claim an IRQ route while a crash kernel leaves SSE state intact. */
> + if (is_kdump_kernel() && riscv_sse_available()) {
> + pr_warn("PMU delivery unavailable with retained crash-kernel SSE state\n");
> + return -EUCLEAN;
> + }
> +
> + sse_ret = pmu_sbi_setup_sse(pmu);
> + if (!sse_ret) {
> + riscv_pmu_use_irq = false;
> + return 0;
> + }
> + if (pmu_sbi_sse_state_retained(pmu)) {
> + pr_err("PMU-SSE setup failed with firmware state retained: %d\n",
> + sse_ret);
> + return -EUCLEAN;
> + }
> + /* Only an explicitly unsupported SSE path proves IRQ fallback is safe. */
> + if (sse_ret != -EOPNOTSUPP) {
> + pr_err("PMU-SSE setup failed: %d\n", sse_ret);
> + return sse_ret;
> + }
> +
> + irq_ret = pmu_sbi_setup_irq(pmu);
> + if (!irq_ret) {
> + riscv_pmu_use_irq = true;
> + return 0;
> + }
> + return irq_ret;
> }
>
> #ifdef CONFIG_CPU_PM
> -static int riscv_pm_pmu_notify(struct notifier_block *b, unsigned long cmd,
> - void *v)
> +static int riscv_pm_pmu_update(struct riscv_pmu *rvpmu, unsigned long cmd)
> {
> - struct riscv_pmu *rvpmu = container_of(b, struct riscv_pmu, riscv_pm_nb);
> struct cpu_hw_events *cpuc = this_cpu_ptr(rvpmu->hw_events);
> bool enabled = !bitmap_empty(cpuc->used_hw_ctrs, RISCV_MAX_COUNTERS);
> struct perf_event *event;
> int idx;
>
> + if (cmd == CPU_PM_ENTER)
> + bitmap_zero(cpuc->pm_resume_hw_ctrs, RISCV_MAX_COUNTERS);
> +
> if (!enabled)
> return NOTIFY_OK;
>
> @@ -1264,6 +1559,8 @@ static int riscv_pm_pmu_notify(struct notifier_block *b, unsigned long cmd,
>
> switch (cmd) {
> case CPU_PM_ENTER:
> + if (!(event->hw.state & PERF_HES_STOPPED))
> + set_bit(idx, cpuc->pm_resume_hw_ctrs);
> /*
> * Stop and update the counter
> */
> @@ -1271,6 +1568,8 @@ static int riscv_pm_pmu_notify(struct notifier_block *b, unsigned long cmd,
> break;
> case CPU_PM_EXIT:
> case CPU_PM_ENTER_FAILED:
> + if (!test_and_clear_bit(idx, cpuc->pm_resume_hw_ctrs))
> + break;
> /*
> * Restore and enable the counter.
> */
> @@ -1284,9 +1583,59 @@ static int riscv_pm_pmu_notify(struct notifier_block *b, unsigned long cmd,
> return NOTIFY_OK;
> }
>
> +static int riscv_pm_pmu_notify(struct notifier_block *b,
> + unsigned long cmd, void *v)
> +{
> + struct riscv_pmu *rvpmu = container_of(b, struct riscv_pmu,
> + riscv_pm_nb);
> +
> +#ifdef CONFIG_RISCV_PMU_SBI_SSE
> + struct cpu_hw_events *cpuc = this_cpu_ptr(rvpmu->hw_events);
> + int ret;
> +
> + if (!riscv_pmu_use_irq && READ_ONCE(rvpmu->sse_active)) {
> + switch (cmd) {
> + case CPU_PM_ENTER:
> + cpuc->pm_resume_sse =
> + sse_event_is_enabled_local(rvpmu->sse_evt);
> + if (cpuc->pm_resume_sse) {
> + ret = sse_event_disable_local(rvpmu->sse_evt);
> + if (ret) {
> + cpuc->pm_resume_sse = false;
> + pmu_sbi_fail_sse(rvpmu, "disable for CPU PM",
> + ret);
> + return notifier_from_errno(ret);
> + }
> + }
> + break;
> + case CPU_PM_EXIT:
> + case CPU_PM_ENTER_FAILED:
> + ret = riscv_pm_pmu_update(rvpmu, cmd);
> + if (!cpuc->pm_resume_sse)
> + return ret;
> +
> + cpuc->pm_resume_sse = false;
> + ret = sse_event_enable_local(rvpmu->sse_evt);
> + if (ret) {
> + pmu_sbi_fail_sse(rvpmu, "enable after CPU PM", ret);
> + return notifier_from_errno(ret);
> + }
> + return NOTIFY_OK;
> + default:
> + break;
> + }
> + }
> +#endif
> +
> + return riscv_pm_pmu_update(rvpmu, cmd);
> +}
> +
> static int riscv_pm_pmu_register(struct riscv_pmu *pmu)
> {
> pmu->riscv_pm_nb.notifier_call = riscv_pm_pmu_notify;
> + /* Keep PMU-SSE disabled until counters and userpage state are restored. */
> + pmu->riscv_pm_nb.priority = riscv_pmu_use_irq ? 1 : -1;
> +
> return cpu_pm_register_notifier(&pmu->riscv_pm_nb);
> }
>
> @@ -1301,6 +1650,8 @@ static inline void riscv_pm_pmu_unregister(struct riscv_pmu *pmu) { }
>
> static void riscv_pmu_destroy(struct riscv_pmu *pmu)
> {
> + pmu_sbi_cleanup_sse(pmu);
> +
> if (sbi_v2_available) {
> if (sbi_pmu_snapshot_available()) {
> pmu_sbi_snapshot_disable();
> @@ -1312,7 +1663,7 @@ static void riscv_pmu_destroy(struct riscv_pmu *pmu)
> cpuhp_state_remove_instance(CPUHP_AP_PERF_RISCV_STARTING, &pmu->node);
> }
>
> -static void pmu_sbi_event_init(struct perf_event *event)
> +static int pmu_sbi_event_init(struct perf_event *event)
> {
> /*
> * The permissions are set at event_init so that we do not depend
> @@ -1324,6 +1675,8 @@ static void pmu_sbi_event_init(struct perf_event *event)
> event->hw.flags |= PERF_EVENT_FLAG_USER_ACCESS;
> else
> event->hw.flags |= PERF_EVENT_FLAG_LEGACY;
> +
> + return 0;
> }
>
> static void pmu_sbi_event_mapped(struct perf_event *event, struct mm_struct *mm)
> @@ -1453,6 +1806,7 @@ static int pmu_sbi_device_probe(struct platform_device *pdev)
> /* cache all the information about counters now */
> if (pmu_sbi_get_ctrinfo(num_counters, &cmask))
> goto out_free;
> + pmu->cmask = cmask;
>
> ret = pmu_sbi_setup_irqs(pmu, pdev);
> if (ret < 0) {
> @@ -1462,9 +1816,15 @@ static int pmu_sbi_device_probe(struct platform_device *pdev)
> }
> irq_requested = (ret == 0);
>
> +#ifdef CONFIG_RISCV_PMU_SBI_SSE
> + if (pmu->sse_active) {
> + pmu->pmu.pmu_enable = pmu_sbi_sse_enable;
> + pmu->pmu.pmu_disable = pmu_sbi_sse_disable;
> + }
> +#endif
> +
> pmu->pmu.attr_groups = riscv_pmu_attr_groups;
> pmu->pmu.parent = &pdev->dev;
> - pmu->cmask = cmask;
> pmu->ctr_start = pmu_sbi_ctr_start;
> pmu->ctr_stop = pmu_sbi_ctr_stop;
> pmu->event_map = pmu_sbi_event_map;
> diff --git a/include/linux/perf/riscv_pmu.h b/include/linux/perf/riscv_pmu.h
> index f82a28040594..fb913ce7f65e 100644
> --- a/include/linux/perf/riscv_pmu.h
> +++ b/include/linux/perf/riscv_pmu.h
> @@ -28,6 +28,8 @@
>
> #define RISCV_PMU_CONFIG1_GUEST_EVENTS 0x1
>
> +struct sse_event;
> +
> struct cpu_hw_events {
> /* currently enabled events */
> int n_events;
> @@ -39,6 +41,18 @@ struct cpu_hw_events {
> DECLARE_BITMAP(used_hw_ctrs, RISCV_MAX_COUNTERS);
> /* currently enabled firmware counters */
> DECLARE_BITMAP(used_fw_ctrs, RISCV_MAX_COUNTERS);
> +#ifdef CONFIG_RISCV_PMU_SBI_SSE
> + /* Keep counters stopped after an unrecoverable SSE transition failure. */
> + bool sse_failed;
> +#endif
> +#ifdef CONFIG_CPU_PM
> + /* Counters stopped by CPU PM and still waiting to be restored. */
> + DECLARE_BITMAP(pm_resume_hw_ctrs, RISCV_MAX_COUNTERS);
> +#ifdef CONFIG_RISCV_PMU_SBI_SSE
> + /* Restore the local PMU SSE event after counters and userpage state. */
> + bool pm_resume_sse;
> +#endif
> +#endif
> /* The virtual address of the shared memory where counter snapshot will be taken */
> void *snapshot_addr;
> /* The physical address of the shared memory where counter snapshot will be taken */
> @@ -54,6 +68,10 @@ struct riscv_pmu {
> char *name;
>
> irqreturn_t (*handle_irq)(int irq_num, void *dev);
> +#ifdef CONFIG_RISCV_PMU_SBI_SSE
> + struct sse_event *sse_evt;
> + bool sse_active;
> +#endif
>
> unsigned long cmask;
> u64 (*ctr_read)(struct perf_event *event);
> @@ -63,7 +81,7 @@ struct riscv_pmu {
> void (*ctr_start)(struct perf_event *event, u64 init_val);
> void (*ctr_stop)(struct perf_event *event, unsigned long flag);
> int (*event_map)(struct perf_event *event, u64 *config);
> - void (*event_init)(struct perf_event *event);
> + int (*event_init)(struct perf_event *event);
> void (*event_mapped)(struct perf_event *event, struct mm_struct *mm);
> void (*event_unmapped)(struct perf_event *event, struct mm_struct *mm);
> uint8_t (*csr_index)(struct perf_event *event);
> diff --git a/include/linux/riscv_sbi_sse.h b/include/linux/riscv_sbi_sse.h
> index 774a782e556d..f6b3881c8884 100644
> --- a/include/linux/riscv_sbi_sse.h
> +++ b/include/linux/riscv_sbi_sse.h
> @@ -42,6 +42,7 @@ int sse_event_enable(struct sse_event *sse_evt);
> int sse_event_disable(struct sse_event *sse_evt);
>
> /* Local events require the caller to remain on the current CPU. */
> +bool sse_event_is_enabled_local(struct sse_event *sse_evt);
> int sse_event_enable_local(struct sse_event *sse_evt);
> int sse_event_disable_local(struct sse_event *sse_evt);
>
> @@ -76,6 +77,11 @@ static inline int sse_event_disable(struct sse_event *sse_evt)
> return -EOPNOTSUPP;
> }
>
> +static inline bool sse_event_is_enabled_local(struct sse_event *sse_evt)
> +{
> + return false;
> +}
> +
> static inline int sse_event_enable_local(struct sse_event *sse_evt)
> {
> return -EOPNOTSUPP;
> --
> 2.50.1 (Apple Git-155)
>