Re: [Patch v3 8/8] perf/x86/intel: Prevent drain_pebs() reentry
"Mi, Dapeng" <[email protected]>
| Newsgroups | gmane.linux.kernel,gmane.linux.kernel.perf.user |
|---|---|
| Message-ID | <[email protected]> |
On 8/10/2026 9:01 PM, Peter Zijlstra wrote: > On Fri, Jul 17, 2026 at 04:03:42PM +0800, Dapeng Mi wrote: >> The PEBS buffer is shared by all events on a CPU, so drain_pebs() must >> not run concurrently. If it is reentered, one instance may observe stale >> buffer state and potentially access out-of-bound memory. >> >> Most invocations happen in NMI context, which naturally prevents reentry. >> However, drain_pebs() is also reachable from process context via >> intel_pmu_drain_pebs_buffer(). >> >> In those paths, the PMU is often already disabled, but not guaranteed. >> For example, __intel_pmu_pebs_disable() only disables the target counter, >> so other active counters can still raise a PMI and interrupt an in-flight >> drain_pebs(). >> >> Introduce __intel_pmu_quiesce() and __intel_pmu_resume() helpers and >> use them in intel_pmu_drain_pebs_buffer() to disable the full PMU >> around the drain_pebs() call, preventing reentry. >> > It is not at all clear to me where the exact recursion happens. (The > word you're looking for was recursion, not concurrent). Yes, the word "concurrently" is not accurate, reentry is the more accurate word. Currently drain_pebs() would be called in two places, one is the in the PMI handler, like handle_pmi_common(). The other place is intel_pmu_drain_pebs_buffer() which is from process context. So when intel_pmu_drain_pebs_buffer() is calling drain_pebs(), if there is an active PEBS event triggering PMI, it would interrupt current in-flight drain_pebs() and lead to drain_pebs() reentry. The good news is the global pmu has been disabled in most places before calling intel_pmu_drain_pebs_buffer(), so no new PMI can be triggered to interrupt current running drain_pebs() helper, but not all places does so, like __intel_pmu_pebs_disable() where only the target counter has been disabled instead of the whole PMU. So it's still possible tjat another active PEBS event triggers PMI and interrupts current running drain_pebs(). Take the intel_pmu_drain_arch_pebs() as an example, base = cpuc->pebs_vaddr; top = cpuc->pebs_vaddr + (index.wr << ARCH_PEBS_INDEX_WR_SHIFT); ------> interrupted here ... index.wr = 0; index.full = 0; index.en = 1; if (cpuc->n_pebs == cpuc->n_large_pebs) index.thresh = ARCH_PEBS_THRESH_MULTI; else index.thresh = ARCH_PEBS_THRESH_SINGLE; wrmsrq(MSR_IA32_PEBS_INDEX, index.whole); Assume the drain_pebs() is interrupted just after reading the top value by a new PEBS PMI and the PMI handler would drain all the PEBS buffer. When the PMI returns and the original drian_pebs() continues to execute but it doesn't know the PEBS buffer has been cleared and may access some stale data and lead to some unexpected errors.