Re: [PATCH v10 7/9] perf: RISC-V: add support for SSE event
Himanshu Chauhan <[email protected]>
| Newsgroups | org.kernel.vger.linux-doc,org.infradead.lists.linux-arm-kernel,org.infradead.lists.linux-riscv,org.kernel.vger.linux-kernel,org.kernel.vger.linux-kselftest,org.kernel.vger.linux-perf-users |
|---|---|
| Message-ID | <[email protected]> |
Hi Zhanpeng Zhang, On Fri, Sep 11, 2026 at 08:14:54PM +0800, Zhanpeng Zhang wrote: > From: Clément Léger <[email protected]> > > 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 <[email protected]> > Co-developed-by: Himanshu Chauhan <[email protected]> > Signed-off-by: Himanshu Chauhan <[email protected]> > Co-developed-by: Zhanpeng Zhang <[email protected]> > Signed-off-by: Zhanpeng Zhang <[email protected]> > --- > 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 <[email protected]> > R: Anup Patel <[email protected]> > L: [email protected] > 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) >