Linux Perf Users
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Dave Jiang" <dave.jiang@intel.com>
Cc: linux-cxl@vger.kernel.org, linux-perf-users@vger.kernel.org
Subject: Re: [RESEND PATCH v4 08/11] perf/cxl: Unfreeze counters after handling an overflow interrupt
Date: Wed, 05 Aug 2026 16:15:57 +0000	[thread overview]
Message-ID: <20260805161558.D1C471F000E9@smtp.kernel.org> (raw)
In-Reply-To: <20260805155911.1304807-9-dave.jiang@intel.com>

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:

New issues:
- [High] A cross-CPU TOCTOU race condition during CPU migration can leave the PMU permanently unfrozen when it is supposed to be disabled, leading to a specification violation where a running counter is written to.

Pre-existing issues:
- [High] `__cxl_pmu_read` is vulnerable to a cross-CPU data race on `hwc->prev_count` during CPU migration because it relies on `local64_cmpxchg`.
- [High] Pending overflow interrupts can be incorrectly attributed to newly scheduled events on the same counter, corrupting their counts by adding a spurious full period.
- [High] Writing 1s to RsvdZ (Reserved, must be zero) bits in the Freeze register violates the CXL specification and risks fatal hardware errors.
- [High] Concurrent PMU reads and overflows cause massive double-counting or loss of events due to stateless overflow handling.
--

--- Patch [8]: [RESEND PATCH v4 08/11] perf/cxl: Unfreeze counters after handling an overflow interrupt ---
commit 77054eb72889cfc4fca68e72d632bb6f5cfbe02d
Author: Dave Jiang <dave.jiang@intel.com>

perf/cxl: Unfreeze counters after handling an overflow interrupt

The counters run with Freeze on Overflow set, so one overflow freezes every
counter in the block. cxl_pmu_irq() reads the overflowed counters and clears
the overflow status but never unfreezes, so everything stays frozen until the
next pmu_enable(). This patch unfreezes after clearing the status, unless the
PMU has been disabled in the meantime.

Closes: https://sashiko.dev/#/patchset/20260715191454.459673-1-dave@stgolabs.net?part=1

> diff --git a/drivers/perf/cxl_pmu.c b/drivers/perf/cxl_pmu.c
> index 448e1da3d59f9..a081fcba69917 100644
> --- a/drivers/perf/cxl_pmu.c
> +++ b/drivers/perf/cxl_pmu.c

[ ... ]

> @@ -604,6 +607,7 @@ static void cxl_pmu_disable(struct pmu *pmu)
>  	struct cxl_pmu_info *info = pmu_to_cxl_pmu_info(pmu);
>  	void __iomem *base = info->base;
>  
> +	WRITE_ONCE(info->enabled, false);
>  	/*
>  	 * Whilst bits above number of counters are RsvdZ
>  	 * they are unlikely to be repurposed given
>  	 * number of counters is allowed to be 64 leaving
>  	 * no reserved bits.  Hence this is only slightly
>  	 * naughty.
>  	 */
>  	writeq(GENMASK_ULL(63, 0), base + CXL_PMU_FREEZE_REG);

[Severity: High]
This isn't a bug introduced by this patch, but does writing a full 64-bit mask
to the freeze register risk fatal hardware errors on strict CXL implementations?

In cxl_pmu_disable(), the driver writes GENMASK_ULL(63, 0) to
CXL_PMU_FREEZE_REG to freeze all counters.

According to the CXL 3.0 specification, bits above the implemented number
of counters (info->num_counters) are RsvdZ, and software is strictly
required to write 0 to these fields.

Writing 1s to reserved fields could cause AER faults or Poisoned transactions.

[ ... ]

> @@ -802,6 +806,21 @@ static irqreturn_t cxl_pmu_irq(int irq, void *data)
>  
>  	writeq(overflowed, base + CXL_PMU_OVERFLOW_REG);
>  
> +	/*
> +	 * An overflow freezes every counter in the CPMU, so unfreeze once the
> +	 * overflowed ones have been read and their status cleared. Otherwise
> +	 * they stay frozen until the next pmu_enable() and events are lost.
> +	 *
> +	 * Not while the PMU is disabled, so as not to undo an intentional freeze.
> +	 * The check is advisory, not exclusive: pmu_disable() normally runs on
> +	 * info->on_cpu with interrupts off, where the pinned handler cannot
> +	 * preempt it. In the one window where it does not - the migration in
> +	 * cxl_pmu_offline_cpu() - the counters are legitimately running again,
> +	 * so unfreezing is correct there anyway.
> +	 */
> +	if (READ_ONCE(info->enabled))

[Severity: High]
Could this advisory check on info->enabled cause a cross-CPU race condition
during CPU migration?

During CPU hotplug migration, perf_pmu_migrate_context() in
cxl_pmu_offline_cpu() moves events to the target CPU, but the IRQ
affinity is not updated until afterwards.

If the IRQ fires on the old CPU, it can read a stale true for info->enabled
concurrently with the target CPU executing cxl_pmu_disable(), which sets
it to false and freezes the PMU.

The old CPU would then write 0 to unfreeze the PMU here. The target CPU,
expecting the PMU to be frozen, proceeds to cxl_pmu_event_start(), enabling
the counter via CXL_PMU_COUNTER_CFG_ENABLE and writing 0 to
CXL_PMU_COUNTER_REG.

Because the PMU was incorrectly unfrozen, the counter starts immediately,
leading to a write to a running counter, which violates the CXL 3.0 spec.

> +		writeq(0, base + CXL_PMU_FREEZE_REG);
> +
>  	return IRQ_HANDLED;
>  }

I also noticed a few other pre-existing issues in this file while reviewing.

[Severity: High]
This isn't a bug introduced by this patch, but is the use of local64_cmpxchg
in __cxl_pmu_read() safe across multiple CPUs?

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

During the CPU hotplug migration window in cxl_pmu_offline_cpu(), events
are migrated to the target CPU before IRQ affinity is updated via
irq_set_affinity().

If an overflow IRQ fires in this window, it runs on the old CPU while the
target CPU can concurrently execute cxl_pmu_read().

Since local64_t only guarantees atomicity for local CPU accesses, both CPUs
iterating over the same event and concurrently executing local64_cmpxchg()
could cause a data race and silent counter corruption.

[Severity: High]
This isn't a bug introduced by this patch, but can pending overflow interrupts
be incorrectly attributed to newly scheduled events?

When a counter overflows, the hardware asserts an interrupt and sets the
corresponding bit in CXL_PMU_OVERFLOW_REG.

If the current event is deleted before the IRQ is serviced, neither
cxl_pmu_event_del() nor cxl_pmu_event_start() clears the overflow bit:

drivers/perf/cxl_pmu.c:cxl_pmu_event_start() {
    ...
    local64_set(&hwc->prev_count, 0);
    writeq(0, base + CXL_PMU_COUNTER_REG(hwc->idx));
    ...
}

When cxl_pmu_event_add() maps a new event to the same counter index, and
the pending IRQ is serviced, cxl_pmu_irq() calls __cxl_pmu_read(event, true).

For the new event, both new_cnt and prev_cnt are near 0, meaning the
following check is satisfied:

drivers/perf/cxl_pmu.c:__cxl_pmu_read() {
    ...
    if (overflow && new_cnt >= prev_cnt)
        delta += mask + 1;
    ...
}

Could this cause the driver to falsely believe a wrap occurred and corrupt
the count by adding a spurious full period?

[Severity: High]
This isn't a bug introduced by this patch, but does the stateless overflow
handling in __cxl_pmu_read() cause double-counting or lost events?

drivers/perf/cxl_pmu.c:__cxl_pmu_read() {
    ...
    delta = (new_cnt - prev_cnt) & mask;
    if (overflow && new_cnt >= prev_cnt)
        delta += mask + 1;
    ...
}

If a polled read (overflow = false) occurs after the counter wraps and
freezes, but before the IRQ handler runs, it adds the correct delta and
updates prev_cnt.

When the IRQ handler subsequently runs, new_cnt and prev_cnt will be equal,
satisfying new_cnt >= prev_cnt.

Wouldn't the IRQ handler then erroneously add a full period on top of the
already correct count? Conversely, if the event is deleted before the IRQ
handler runs, could the full period be permanently lost?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260805155911.1304807-1-dave.jiang@intel.com?part=8

  reply	other threads:[~2026-08-05 16:16 UTC|newest]

Thread overview: 19+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-05 15:59 [RESEND PATCH v4 00/11] perf/cxlpmu: Misc sashiko raised issues fixes Dave Jiang
2026-08-05 15:59 ` [RESEND PATCH v4 01/11] perf/cxl: Program the requested event group on configurable counters Dave Jiang
2026-08-05 16:23   ` sashiko-bot
2026-08-05 15:59 ` [RESEND PATCH v4 02/11] perf/cxl: Clear stale event fields before reprogramming a counter Dave Jiang
2026-08-05 16:12   ` sashiko-bot
2026-08-05 15:59 ` [RESEND PATCH v4 03/11] perf/cxl: Fix the counter overflow delta fixup Dave Jiang
2026-08-05 15:59 ` [RESEND PATCH v4 04/11] perf/cxl: Accept an overflow interrupt on MSI message number 0 Dave Jiang
2026-08-05 15:59 ` [RESEND PATCH v4 05/11] perf/cxl: Split the MSI vector out of info->irq Dave Jiang
2026-08-05 15:59 ` [RESEND PATCH v4 06/11] cxl/pci: Add the PMUs after configuring events Dave Jiang
2026-08-05 16:51   ` Alison Schofield
2026-08-05 15:59 ` [RESEND PATCH v4 07/11] perf/cxl: Don't share the overflow interrupt, and keep it pinned Dave Jiang
2026-08-05 15:59 ` [RESEND PATCH v4 08/11] perf/cxl: Unfreeze counters after handling an overflow interrupt Dave Jiang
2026-08-05 16:15   ` sashiko-bot [this message]
2026-08-05 16:35     ` Dave Jiang
2026-08-05 15:59 ` [RESEND PATCH v4 09/11] perf/cxl: Validate the hardware-reported counter width Dave Jiang
2026-08-05 16:16   ` sashiko-bot
2026-08-05 15:59 ` [RESEND PATCH v4 10/11] perf/cxl: Don't log through pmu.dev in the overflow interrupt handler Dave Jiang
2026-08-05 15:59 ` [RESEND PATCH v4 11/11] perf/cxl: Clear stale overflow status before using a counter Dave Jiang
2026-08-05 16:21   ` 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=20260805161558.D1C471F000E9@smtp.kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=dave.jiang@intel.com \
    --cc=linux-cxl@vger.kernel.org \
    --cc=linux-perf-users@vger.kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    /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