Linux Perf Users
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Zhanpeng Zhang" <zhangzhanpeng.jasper@bytedance.com>
Cc: linux-perf-users@vger.kernel.org
Subject: Re: [PATCH v12 08/11] perf: RISC-V: add support for SSE event
Date: Thu, 29 Oct 2026 12:19:04 +0000	[thread overview]
Message-ID: <sashiko-outbox-189273@kernel.org> (raw)
In-Reply-To: <20261029074853.35467-9-zhangzhanpeng.jasper@bytedance.com>

Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider:
- [High] perf: RISC-V: unhandled PERF_HES_STOPPED counters in pmu_sbi_ovf_handler()
- [High] perf: RISC-V: unstopped PMU counters in pmu_sbi_ovf_sse_handler() when regs is NULL
- [Medium] perf: riscv: missing sse_event_cleanup() when pmu_sbi_setup_sse() fails
- [Medium] perf: RISC-V: batch counter stop in pmu_sbi_cleanup_sse()
- [Medium] perf: RISC-V: pmu_sbi_fail_sse() clears SCOUNTEREN without updating userpage
- [Medium] perf: RISC-V: pmu_sbi_sse_enable() latches sse_failed on hotplug and reboot
- [Medium] perf: RISC-V: riscv_pm_pmu_notify() ignores latched cpuc->sse_failed state
- [Medium] perf: riscv: lost PMU SSE resume state after CPU_PM_EXIT unmask failure
--

commit 97de0c9d3e465757bf9e40945610083784df9d4b
Author: Clément Léger <cleger@rivosinc.com>

perf: RISC-V: add support for SSE event

This commit adds support for delivering RISC-V SBI PMU counter overflow
events via Supervisor Software Events (SSE), including probe-time delivery
selection, overflow handling, error latching, and CPU power management
integration.

Link: https://lore.kernel.org/r/20261002-reach-cloning-f1594729dbe3@wendy/

> diff --git a/drivers/perf/riscv_pmu_sbi.c b/drivers/perf/riscv_pmu_sbi.c
> index 21a2078eab98a..a984ac57a7b6e 100644
> --- a/drivers/perf/riscv_pmu_sbi.c
> +++ b/drivers/perf/riscv_pmu_sbi.c

[ ... ]

> @@ -936,14 +938,42 @@ static inline void pmu_sbi_stop_all(struct riscv_pmu *pmu)
>  	 * which may include counters that are not enabled yet.
>  	 */
>  	for (i = 0; i < BITS_TO_LONGS(RISCV_MAX_COUNTERS); i++) {
> -		if (!pmu->cmask[i])
> +		if (!ctr_mask[i])
>  			continue;
>  		sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_STOP,
> -			  i * BITS_PER_LONG, pmu->cmask[i],
> +			  i * BITS_PER_LONG, ctr_mask[i],
>  			  SBI_PMU_STOP_FLAG_RESET);
>  	}
>  }
>  
> +static void pmu_sbi_sse_stop_all(const unsigned long *ctr_mask)
> +{
> +	struct sbiret ret;
> +	int i;
> +
> +	/*
> +	 * Stop each counter separately: an already-stopped counter must not
> +	 * prevent firmware from stopping a later, overflowing counter.
> +	 */
> +	for_each_set_bit(i, ctr_mask, RISCV_MAX_COUNTERS) {
> +		ret = sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_STOP,
> +				i, 1, SBI_PMU_STOP_FLAG_RESET);
> +		if (ret.error && ret.error != SBI_ERR_ALREADY_STOPPED)
> +			pr_warn_ratelimited("failed to stop counter %d: %ld\n",
> +					    i, ret.error);
> +	}
> +}
> +
> +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);
> +}

[ ... ]

> @@ -1054,32 +1098,111 @@ static inline void pmu_sbi_start_ovf_ctrs_snapshot(struct cpu_hw_events *cpu_hw_

[ ... ]

> +#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);
> +	}
> +}

[Severity: Medium]
When pmu_sbi_fail_sse() stops mapped events via riscv_pmu_stop(), can a
userspace task with a direct-mapped counter page subsequently fault with
SIGILL?

When an event is started in riscv_pmu_start() (for example during
event_sched_in() or riscv_pm_pmu_update() on CPU_PM_EXIT), pmu_sbi_ctr_start()
sets the counter bit in CSR_SCOUNTEREN and perf_event_update_userpage() sets
a non-zero userpg->index.

If a subsequent SSE call fails and invokes pmu_sbi_fail_sse(), it calls
riscv_pmu_stop() -> pmu_sbi_ctr_stop() -> pmu_sbi_reset_scounteren(), which
clears the counter bit in CSR_SCOUNTEREN and sets PERF_HES_STOPPED:

drivers/perf/riscv_pmu_sbi.c:pmu_sbi_ctr_stop() {
    ...
	if ((hwc->flags & PERF_EVENT_FLAG_USER_ACCESS) &&
	    (hwc->flags & PERF_EVENT_FLAG_USER_READ_CNT))
		pmu_sbi_reset_scounteren((void *)event);
    ...
}

Because neither riscv_pmu_stop() nor pmu_sbi_fail_sse() calls
perf_event_update_userpage(event), userpg->index remains non-zero while
CSR_SCOUNTEREN has the counter bit cleared, and cpu_hw_evt->sse_failed
prevents riscv_pmu_start() from restarting the event. Should
pmu_sbi_fail_sse() call perf_event_update_userpage(event) after
riscv_pmu_stop() so that userpg->index is cleared and userpg->offset is
updated?

> +
> +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);
> +}

[Severity: Medium]
Can pmu_sbi_sse_enable() permanently latch cpu_hw_evt->sse_failed during CPU
hotplug or system reboot?

In drivers/firmware/riscv/riscv_sbi_sse.c, sse_event_enable_local() returns
-ESHUTDOWN once sse_reboot_notifier sets sse_shutting_down, and returns
-EINVAL when reg_evt->is_registered is false on the local CPU:

drivers/firmware/riscv/riscv_sbi_sse.c:sse_event_enable_local() {
    ...
	if (READ_ONCE(sse_shutting_down)) {
		ret = -ESHUTDOWN;
		goto out;
	}

	if (!reg_evt->is_registered) {
		ret = -EINVAL;
		goto out;
	}
    ...
}

During CPU hotplug, CPUHP_AP_RISCV_SSE_ONLINE is in the threaded online
section after CPUHP_AP_ONLINE_IDLE, CPUHP_AP_SCHED_WAIT_EMPTY, and
CPUHP_AP_SMPBOOT_THREADS. On cpu_down, sse_cpu_teardown() sets
reg_evt->is_registered = false before sched_cpu_wait_empty() and
smpboot_park_threads() finish running tasks on that CPU; on cpu_up,
cpuhp/%u and unparked smpboot threads run before sse_cpu_online() registers
the local SSE event. Any context switch of a task with a hardware perf event
in that window calls perf_event_context_sched_out() or
perf_event_context_sched_in() -> perf_pmu_enable() -> pmu_sbi_sse_enable()
-> sse_event_enable_local(), which returns -EINVAL and causes
pmu_sbi_fail_sse() to latch cpu_hw_evt->sse_failed = true (which is never
cleared on a subsequent cpu_up).

Similarly, on reboot or kexec, sse_reboot_nb (priority 0) runs before
perf_reboot_notifier (priority INT_MIN), so perf_event_exit_cpu() triggers
pmu_sbi_sse_enable() after sse_shutting_down is true, failing with
-ESHUTDOWN.

Should pmu_sbi_sse_enable() avoid latching sse_failed when the local SSE
event is not currently registered during hotplug or when SSE is shutting
down?

[ ... ]

> @@ -1153,6 +1288,12 @@ static irqreturn_t pmu_sbi_ovf_handler(int irq, void *dev)
>  		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.
>  		 */
> @@ -1165,6 +1306,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
> @@ -1173,128 +1322,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;
>  	}

[Severity: High]
When perf_event_overflow() returns non-zero and leaves an event with
PERF_HES_STOPPED set while its bit remains set in cpu_hw_evt->used_hw_ctrs,
what happens when a second active counter on the same CPU overflows?

First, pmu_sbi_ovf_handler() calls pmu_sbi_stop_hw_ctrs(), which passes the
unfiltered cpu_hw_evt->used_hw_ctrs[i] mask to a batch
SBI_EXT_PMU_COUNTER_STOP ecall:

drivers/perf/riscv_pmu_sbi.c:pmu_sbi_stop_hw_ctrs() {
    ...
	for (i = 0; i < BITS_TO_LONGS(RISCV_MAX_COUNTERS); i++) {
		/* No need to check the error here as we can't do anything about the error */
		ret = sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_STOP, i * BITS_PER_LONG,
				cpu_hw_evt->used_hw_ctrs[i], flag);
		if (!ret.error && sbi_pmu_snapshot_available()) {
			/* Save the counter values to avoid clobbering */
			for_each_set_bit(idx, &cpu_hw_evt->used_hw_ctrs[i], BITS_PER_LONG)
				cpu_hw_evt->snapshot_cval_shcopy[i * BITS_PER_LONG + idx] =
							sdata->ctr_values[idx];
    ...
}

Because the throttled counter is already stopped in firmware, the batch stop
can return SBI_ERR_ALREADY_STOPPED (or stop early before higher-indexed
counters). When sbi_pmu_snapshot_available() is true, ret.error != 0 skips
copying sdata->ctr_values into snapshot_cval_shcopy after memset() zeroed
snapshot_cval_shcopy, and then overwrites sdata->ctr_values with 0 for all
used_hw_ctrs.

Second, in the overflow loop of pmu_sbi_ovf_handler(), events that already
have PERF_HES_STOPPED set are not skipped:

drivers/perf/riscv_pmu_sbi.c:pmu_sbi_ovf_handler() {
    ...
	for_each_set_bit(lidx, cpu_hw_evt->used_hw_ctrs, RISCV_MAX_COUNTERS) {
		struct perf_event *event = cpu_hw_evt->events[lidx];

		/* Skip if invalid event or user did not request a sampling */
		if (!event || !is_sampling_event(event))
			continue;
    ...
}

Without CSR_SCOUNTOVF (MHPMEVENT_OF) having been cleared by a counter start,
overflow & BIT_ULL(hidx) remains set for the previously stopped counter. On
the subsequent overflow of another counter, pmu_sbi_ovf_handler() visits the
throttled event again: riscv_pmu_event_update() is a no-op because
PERF_HES_UPTODATE is still set, and riscv_pmu_event_set_period() returns 0
because period_left was already made positive on the initial overflow. That
takes the else branch (or the from_guest branch) and clears hw_evt->state =
0, causing pmu_sbi_start_overflow_mask() to prematurely restart the throttled
counter.

Should pmu_sbi_stop_hw_ctrs() and the loop in pmu_sbi_ovf_handler() skip
counters whose event already has PERF_HES_STOPPED set?

>  
>  	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_sse_stop_all(pmu->cmask);
> +		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;
>  	}

[Severity: High]
Can the !regs path in pmu_sbi_ovf_sse_handler() return without stopping the
overflowing counter source, leading to an infinite SSE re-delivery loop when
firmware executes SBI_SSE_EVENT_COMPLETE?

When regs is NULL, pmu_sbi_ovf_sse_handler() bypasses pmu_sbi_ovf_handler()
and relies only on pmu_sbi_fail_sse(). However, there are two cases where
pmu_sbi_fail_sse() does not stop the counter source while
pmu_sbi_ovf_handler() would have:

1. An SSE overflow arrives after pmu_sbi_setup_sse() sets pmu->sse_active =
   true (before pmu_sbi_starting_cpu() stops counters or before perf installs
   an event in cpu_hw_evt->events[]). In pmu_sbi_ovf_handler(), empty
   used_hw_ctrs or a NULL event calls pmu_sbi_sse_stop_all(cmask), whereas
   pmu_sbi_fail_sse() only loops over non-NULL cpu_hw_evt->events[idx] and
   stops nothing.

2. In pmu_sbi_fail_sse(), cpu_hw_evt->sse_failed is set to true before
   pr_err_ratelimited() and before the loop that stops mapped events (for
   example, when sse_event_disable_local() fails in pmu_sbi_sse_disable()). If
   an SSE overflow preempts that path with regs == NULL, pmu_sbi_fail_sse()
   sees READ_ONCE(cpu_hw_evt->sse_failed) == true and returns immediately
   without stopping the overflowing counter, whereas pmu_sbi_ovf_handler()
   calls pmu_sbi_stop_hw_ctrs(pmu) before checking pmu_sbi_sse_failed().

Should the !regs path (or pmu_sbi_fail_sse()) also ensure that unmapped or
still-running hardware counters are stopped before returning from the SSE
handler?

>  
> -	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;
> +	}

[Severity: Medium]
If sse_event_enable(evt) fails in pmu_sbi_setup_sse() and then
sse_event_disable(evt) or sse_event_unregister(evt) also fails, should this
error path hand evt over to sse_event_cleanup(evt)?

When pmu->sse_evt is retained here, pmu_sbi_setup_irqs() sees
pmu_sbi_sse_state_retained(pmu) and returns -EUCLEAN, and
pmu_sbi_device_probe() continues in counting-only mode:

drivers/perf/riscv_pmu_sbi.c:pmu_sbi_device_probe() {
    ...
	ret = pmu_sbi_setup_irqs(pmu, pdev);
	if (ret < 0) {
		pr_info("Perf sampling/filtering is not supported as sscof extension is not available\n");
		pmu->pmu.capabilities |= PERF_PMU_CAP_NO_INTERRUPT;
		pmu->pmu.capabilities |= PERF_PMU_CAP_NO_EXCLUDE;
	}
    ...
}

Because probe succeeds without calling riscv_pmu_destroy() ->
pmu_sbi_cleanup_sse(), sse_event_cleanup(evt) is never called on the retained
event. With evt->cleanup_pending left false, the SSE core's hotplug and PM
callbacks (sse_cpu_online() and sse_restore_local_events()) treat evt as an
active registration and re-register it on CPUs instead of cleaning it up, and
if the event remained enabled on any CPU, a counting-event wrap will invoke
pmu_sbi_ovf_sse_handler() with pmu->sse_active == false and reset active
counters via pmu_sbi_sse_stop_all(pmu->cmask).

> +
> +	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);

[Severity: Medium]
Should pmu_sbi_cleanup_sse() stop counters on each CPU using
pmu_sbi_sse_stop_all(pmu->cmask) instead of pmu_sbi_stop_all_cpu()?

pmu_sbi_stop_all_cpu() calls pmu_sbi_stop_all() ->
pmu_sbi_stop_all_mask(pmu->cmask), which issues a batch
SBI_EXT_PMU_COUNTER_STOP per word. As noted in the commit message, a batch
stop can return SBI_ERR_ALREADY_STOPPED when encountering an already-stopped
counter before stopping higher-numbered counters in pmu->cmask. If
sse_event_disable() or sse_event_unregister() subsequently fails and jumps to
retain, sse_event_cleanup(sse_evt) replaces the PMU SSE handler with the
no-op cleanup handler while a higher-numbered counter could still be running.

[ ... ]

> @@ -1325,9 +1641,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

[Severity: Medium]
Should riscv_pm_pmu_notify() check READ_ONCE(cpuc->sse_failed) before running
the local SSE disable and enable transitions?

If cpuc->sse_failed was latched while the local SSE event remained enabled
(for instance when pmu_sbi_ovf_sse_handler() failed with !regs, or when
sse_event_disable_local() failed in pmu_sbi_sse_disable()), a later
CPU_PM_ENTER will see sse_event_is_enabled_local(rvpmu->sse_evt) == true. If
sse_event_disable_local() then succeeds on CPU_PM_ENTER, cpuc->pm_resume_sse
is set to true and CPU_PM_EXIT will re-enable the local SSE event via
sse_event_enable_local(rvpmu->sse_evt) even though cpuc->sse_failed is latched
(or if sse_event_disable_local() keeps failing, every CPU_PM_ENTER will
return notifier_from_errno(ret) and abort idle entry).

[Severity: Medium]
Also in riscv_pm_pmu_notify() and riscv_pm_pmu_update(), what happens on the
next CPU_PM_ENTER if sse_events_unmask() failed during the preceding
CPU_PM_EXIT?

On CPU_PM_EXIT, sse_pm_notifier() (priority 0) runs before
riscv_pm_pmu_notify() (priority -1). If sse_events_unmask() fails,
sse_pm_notifier() returns notifier_from_errno(ret) with NOTIFY_STOP_MASK,
stopping the notifier chain before riscv_pm_pmu_notify() runs. At that point,
rvpmu->sse_evt is still disabled (is_enabled == false) and the counters
stopped during the prior CPU_PM_ENTER still have PERF_HES_STOPPED set.

When the CPU enters idle again on the next CPU_PM_ENTER,
riscv_pm_pmu_notify() overwrites cpuc->pm_resume_sse with
sse_event_is_enabled_local(rvpmu->sse_evt) (which is now false), and
riscv_pm_pmu_update() zeroes cpuc->pm_resume_hw_ctrs and only records
counters without PERF_HES_STOPPED:

drivers/perf/riscv_pmu_sbi.c:riscv_pm_pmu_update() {
    ...
	if (cmd == CPU_PM_ENTER)
		bitmap_zero(cpuc->pm_resume_hw_ctrs, RISCV_MAX_COUNTERS);
    ...
		case CPU_PM_ENTER:
			if (!(event->hw.state & PERF_HES_STOPPED))
				set_bit(idx, cpuc->pm_resume_hw_ctrs);
    ...
}

Won't that lose both cpuc->pm_resume_sse and cpuc->pm_resume_hw_ctrs, leaving
the counters stopped and the local PMU SSE event disabled on subsequent
CPU_PM_EXIT notifications?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261029074853.35467-1-zhangzhanpeng.jasper@bytedance.com?part=8

  reply	other threads:[~2026-10-29 12:19 UTC|newest]

Thread overview: 26+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-29  7:48 [PATCH v12 00/11] riscv: add SBI Supervisor Software Events support Zhanpeng Zhang
2026-10-29  7:48 ` [PATCH v12 01/11] riscv: add SBI SSE extension definitions Zhanpeng Zhang
2026-10-29 11:53   ` sashiko-bot
2026-10-29  7:48 ` [PATCH v12 02/11] riscv: add support for SBI Supervisor Software Events extension Zhanpeng Zhang
2026-10-29 12:12   ` sashiko-bot
2026-10-29  7:48 ` [PATCH v12 03/11] riscv: sse: mask events during shutdown and kexec Zhanpeng Zhang
2026-10-29 11:49   ` Himanshu Chauhan
2026-10-29 12:00     ` Zhanpeng Zhang
2026-10-29 12:10   ` sashiko-bot
2026-10-29  7:48 ` [PATCH v12 04/11] drivers: firmware: add riscv SSE support Zhanpeng Zhang
2026-10-29 12:19   ` sashiko-bot
2026-10-29  7:48 ` [PATCH v12 05/11] riscv: mm: avoid enabling interrupts for nofault page faults Zhanpeng Zhang
2026-10-29 12:03   ` sashiko-bot
2026-10-29  7:48 ` [PATCH v12 06/11] riscv: enable NMI-safe lockdep irqflags tracking Zhanpeng Zhang
2026-10-29 11:59   ` sashiko-bot
2026-10-29  7:48 ` [PATCH v12 07/11] perf: RISC-V: support callchains with SSE delivery Zhanpeng Zhang
2026-10-29 12:20   ` sashiko-bot
2026-10-29  7:48 ` [PATCH v12 08/11] perf: RISC-V: add support for SSE event Zhanpeng Zhang
2026-10-29 12:19   ` sashiko-bot [this message]
2026-10-29 11:38 ` [PATCH v12 00/11] riscv: add SBI Supervisor Software Events support Himanshu Chauhan
2026-10-29 11:55   ` [External] " Zhanpeng Zhang
2026-10-29 11:49 ` [PATCH v12 09/11] MAINTAINERS: add RISC-V SSE maintainers and file coverage Zhanpeng Zhang
2026-10-29 11:49 ` [PATCH v12 10/11] selftests/riscv: add SSE test module Zhanpeng Zhang
2026-10-29 12:12   ` sashiko-bot
2026-10-29 11:49 ` [PATCH v12 11/11] selftests/riscv: add perf user-stack SSE copy regression test Zhanpeng Zhang
2026-10-29 12:05   ` sashiko-bot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=sashiko-outbox-189273@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=linux-perf-users@vger.kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    --cc=zhangzhanpeng.jasper@bytedance.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox