All of lore.kernel.org
 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: 48+ 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 ` Zhanpeng Zhang
2026-10-29  7:48 ` [PATCH v12 01/11] riscv: add SBI SSE extension definitions Zhanpeng Zhang
2026-10-29  7:48   ` 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  7:48   ` Zhanpeng Zhang
2026-10-29 12:12   ` sashiko-bot
2026-10-29 13:48   ` Himanshu Chauhan
2026-10-29 13:48     ` Himanshu Chauhan
2026-10-29 14:46     ` Zhanpeng Zhang
2026-10-29 14:46       ` Zhanpeng Zhang
2026-10-29  7:48 ` [PATCH v12 03/11] riscv: sse: mask events during shutdown and kexec Zhanpeng Zhang
2026-10-29  7:48   ` Zhanpeng Zhang
2026-10-29 11:49   ` Himanshu Chauhan
2026-10-29 11:49     ` Himanshu Chauhan
2026-10-29 12:00     ` Zhanpeng Zhang
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  7:48   ` 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  7:48   ` 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  7:48   ` 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  7:48   ` 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  7:48   ` 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:38   ` Himanshu Chauhan
2026-10-29 11:55   ` [External] " Zhanpeng Zhang
2026-10-29 11:55     ` 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   ` Zhanpeng Zhang
2026-10-29 23:09   ` Conor Dooley
2026-10-29 23:09     ` Conor Dooley
2026-10-29 11:49 ` [PATCH v12 10/11] selftests/riscv: add SSE test module Zhanpeng Zhang
2026-10-29 11:49   ` 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 11:49   ` 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.