Re: [PATCH v3 7/9] perf/cxl: Unfreeze counters after handling an overflow interrupt

[email protected] Fri, 31 Jul 2026 23:40:39 +0000
Newsgroups org.kernel.vger.linux-cxl,org.kernel.vger.linux-perf-users
Message-ID <[email protected]>
Thank you for your contribution! Sashiko AI review found 2 potential issue(=
s) to consider:

Pre-existing issues:
- [High] Cross-CPU data race on local64_t variables and spurious counter in=
crements during CPU hotplug migration.
- [High] Unregistering the PMU before removing the CPU hotplug instance cre=
ates a Use-After-Free window during driver removal.
--

--- Patch [7]: [PATCH v3 7/9] perf/cxl: Unfreeze counters after handling an=
 overflow interrupt ---
commit d6decd45c5a5019eddbbf261ba3f712c1ad75535
Author: Dave Jiang <[email protected]>

perf/cxl: Unfreeze counters after handling an overflow interrupt

This patch ensures CXL PMU counters are unfrozen after clearing an overflow
interrupt. Previously, counters remained frozen until the next pmu_enable(),
causing events to be lost during that window. It tracks the PMU enabled
state to avoid unfreezing if the PMU was disabled concurrently.

Closes: https://sashiko.dev/#/patchset/20260715191454.459673-1-dave@stgolab=
s.net?part=3D1

> diff --git a/drivers/perf/cxl_pmu.c b/drivers/perf/cxl_pmu.c
> index 6fdc66a01fb67..580a75bc9210a 100644
> --- a/drivers/perf/cxl_pmu.c
> +++ b/drivers/perf/cxl_pmu.c

[ ... ]

> @@ -803,6 +807,9 @@ static irqreturn_t cxl_pmu_irq(int irq, void *data)
> =20
>  	writeq(overflowed, base + CXL_PMU_OVERFLOW_REG);
> =20
> +	if (READ_ONCE(info->enabled))
> +		writeq(0, base + CXL_PMU_FREEZE_REG);
> +
>  	return IRQ_HANDLED;
>  }

[Severity: High]
This is a pre-existing issue, but can this sequence corrupt performance
counters during CPU hotplug?

Looking at cxl_pmu_offline_cpu(), perf_pmu_migrate_context() is called
before the IRQ affinity is updated:

drivers/perf/cxl_pmu.c:cxl_pmu_offline_cpu() {
	...
	perf_pmu_migrate_context(&info->pmu, cpu, target);
	info->on_cpu =3D target;
	/*
	 * CPU HP lock is held so we should be guaranteed that this CPU hasn't
	 * yet gone away.
	 */
	WARN_ON(irq_set_affinity(info->irq, cpumask_of(target)));
	...
}

If an overflow MSI is delivered to the old CPU in this narrow window,
cxl_pmu_irq() runs on the old CPU. If the new CPU has already re-added and
started the event, it has cleared hwc->prev_count to 0.

Because the hardware overflow register is only cleared by the IRQ handler,
the old CPU sees a stale overflow flag and invokes __cxl_pmu_read(). The
condition new_cnt >=3D prev_cnt passes (since prev_cnt is 0), causing it to
erroneously add a full period (mask + 1) to the counter:

drivers/perf/cxl_pmu.c:__cxl_pmu_read() {
	...
	do {
		prev_cnt =3D local64_read(&hwc->prev_count);
		new_cnt =3D cxl_pmu_read_counter(event);
	} while (local64_cmpxchg(&hwc->prev_count, prev_cnt, new_cnt) !=3D prev_cn=
t);

	/* ... */
	mask =3D GENMASK_ULL(info->counter_width - 1, 0);
	delta =3D (new_cnt - prev_cnt) & mask;
	if (overflow && new_cnt >=3D prev_cnt)
		delta +=3D mask + 1;

	local64_add(delta, &event->count);
}

Does this also cause a data race since local64_t operations lack cross-CPU
atomic guarantees?

[Severity: High]
This isn't a bug introduced by this patch, but does the devm teardown order
create a use-after-free window during driver removal?

In cxl_pmu_probe(), the CPU hotplug instance is registered before the PMU:

drivers/perf/cxl_pmu.c:cxl_pmu_probe() {
	...
	rc =3D cpuhp_state_add_instance(cxl_pmu_cpuhp_state_num, &info->node);
	if (rc)
		return rc;

	rc =3D devm_add_action_or_reset(dev, cxl_pmu_cpuhp_remove, info);
	if (rc)
		return rc;

	rc =3D perf_pmu_register(&info->pmu, info->pmu.name, -1);
	if (rc)
		return rc;

	rc =3D devm_add_action_or_reset(dev, cxl_pmu_perf_unregister, info);
	...
}

During driver removal, devm executes actions in LIFO order.
cxl_pmu_perf_unregister() runs first, delegating to perf_pmu_unregister(),
which frees pmu->cpu_pmu_context.

If a CPU is offlined concurrently before cxl_pmu_cpuhp_remove() runs,
cxl_pmu_offline_cpu() is invoked:

drivers/perf/cxl_pmu.c:cxl_pmu_offline_cpu() {
	...
	perf_pmu_migrate_context(&info->pmu, cpu, target);
	...
}

Will perf_pmu_migrate_context() access the freed cpu_pmu_context when it
calls find_get_pmu_context()?

--=20
Sashiko AI review =C2=B7 https://sashiko.dev/#/patchset/20260731232827.4014=
[email protected]?part=3D7