From: Dave Jiang <dave.jiang@intel.com>
To: linux-cxl@vger.kernel.org, linux-perf-users@vger.kernel.org
Cc: jic23@kernel.org, will@kernel.org, mark.rutland@arm.com,
dave@stgolabs.net, sashiko-bot@kernel.org
Subject: [PATCH v2 3/9] perf/cxl: Fix the counter overflow delta fixup
Date: Wed, 29 Jul 2026 07:55:49 -0700 [thread overview]
Message-ID: <20260729145555.3919550-4-dave.jiang@intel.com> (raw)
In-Reply-To: <20260729145555.3919550-1-dave.jiang@intel.com>
Counters are configured with Freeze on Overflow and are never reloaded: on
overflow the counter wraps to 0, counts on until the CPMU freezes, and
retains that residual (CXL r4.0 8.2.7.2.3). So the masked subtraction in
__cxl_pmu_read() only spans the wrap when new_cnt < prev_cnt.
The fixup keys off the delta rather than the operands:
delta = (new_cnt - prev_cnt) & GENMASK_ULL(counter_width - 1, 0);
if (overflow && delta < GENMASK_ULL(counter_width - 1, 0))
delta += (1UL << counter_width);
and is wrong both ways. After a mid-period read the subtraction already
spans the wrap, but the guard holds for all but one delta value, so a
second period is added and the event over-counts. After an earlier
overflow read - or after event_start(), which leaves prev_count at 0 -
prev_cnt is a small residual, and once the new residual is greater or
equal only the difference between the two is counted and the period is
lost, which for a residual of 0 means every period after the first.
'perf stat -I' hits the former, plain 'perf stat' the latter.
The shift is also undefined for counter_width == 64, and for >= 32 on
32-bit kernels where 1UL is 32 bits.
Condition the fixup on new_cnt >= prev_cnt, the one case the masked
subtraction cannot express, and use mask + 1 for the period: it is 0 for a
64-bit counter, the correct sum mod 2^64, and needs no shift. A
userspace read landing between the wrap and the handler clearing the
overflow status stays correct because the counter is frozen and cannot
advance between the two reads.
Fixes: 5d7107c72796 ("perf: CXL Performance Monitoring Unit driver")
Reported-by: sashiko-bot@kernel.org
Closes: https://sashiko.dev/#/patchset/20260715191454.459673-1-dave@stgolabs.net?part=1
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Dave Jiang <dave.jiang@intel.com>
---
v2:
- Fix the fixup instead of deleting it. v1 dropped it on the rationale
that the masked subtraction already handles the wrap, which only holds
for new_cnt < prev_cnt; with freeze on overflow and no reload the
overflow path routinely sees new_cnt >= prev_cnt, where v1 silently
dropped a full period (sashiko-bot,
https://lore.kernel.org/linux-cxl/20260728211411.47B2D1F000E9@smtp.kernel.org/).
- Keep the overflow argument and the __cxl_pmu_read()/cxl_pmu_read()
split; v1 folded them together once the argument went unused.
- Use mask + 1 rather than 1ULL << counter_width so the 64-bit counter
case needs no special casing.
---
drivers/perf/cxl_pmu.c | 18 ++++++++++++------
1 file changed, 12 insertions(+), 6 deletions(-)
diff --git a/drivers/perf/cxl_pmu.c b/drivers/perf/cxl_pmu.c
index 3138514157cd..956da8202551 100644
--- a/drivers/perf/cxl_pmu.c
+++ b/drivers/perf/cxl_pmu.c
@@ -690,7 +690,7 @@ static void __cxl_pmu_read(struct perf_event *event, bool overflow)
{
struct cxl_pmu_info *info = pmu_to_cxl_pmu_info(event->pmu);
struct hw_perf_event *hwc = &event->hw;
- u64 new_cnt, prev_cnt, delta;
+ u64 new_cnt, prev_cnt, delta, mask;
do {
prev_cnt = local64_read(&hwc->prev_count);
@@ -698,12 +698,18 @@ static void __cxl_pmu_read(struct perf_event *event, bool overflow)
} while (local64_cmpxchg(&hwc->prev_count, prev_cnt, new_cnt) != prev_cnt);
/*
- * If we know an overflow occur then take that into account.
- * Note counter is not reset as that would lose events
+ * The counter wraps to 0 and is never reloaded, so the masked unsigned
+ * subtraction only recovers a wrap when new_cnt < prev_cnt. On the
+ * overflow path new_cnt >= prev_cnt means the full period elapsed
+ * (prev_cnt is 0 from event_start(), or the residual left by an earlier
+ * overflow), so add the period back. mask + 1 is 2^counter_width, which
+ * evaluates to 0 for a 64-bit counter - the correct sum mod 2^64, and
+ * avoids the undefined 1 << 64.
*/
- delta = (new_cnt - prev_cnt) & GENMASK_ULL(info->counter_width - 1, 0);
- if (overflow && delta < GENMASK_ULL(info->counter_width - 1, 0))
- delta += (1UL << info->counter_width);
+ mask = GENMASK_ULL(info->counter_width - 1, 0);
+ delta = (new_cnt - prev_cnt) & mask;
+ if (overflow && new_cnt >= prev_cnt)
+ delta += mask + 1;
local64_add(delta, &event->count);
}
--
2.55.0
next prev parent reply other threads:[~2026-07-29 14:56 UTC|newest]
Thread overview: 17+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-29 14:55 [PATCH v2 0/9] perf/cxlpmu: Misc sashiko raised issues fixes Dave Jiang
2026-07-29 14:55 ` [PATCH v2 1/9] perf/cxl: Program the requested event group on configurable counters Dave Jiang
2026-07-29 15:06 ` sashiko-bot
2026-07-29 14:55 ` [PATCH v2 2/9] perf/cxl: Clear stale event fields before reprogramming a counter Dave Jiang
2026-07-29 15:08 ` sashiko-bot
2026-07-29 14:55 ` Dave Jiang [this message]
2026-07-29 15:13 ` [PATCH v2 3/9] perf/cxl: Fix the counter overflow delta fixup sashiko-bot
2026-07-29 14:55 ` [PATCH v2 4/9] perf/cxl: Accept an overflow interrupt on MSI message number 0 Dave Jiang
2026-07-29 14:55 ` [PATCH v2 5/9] perf/cxl: Keep the overflow interrupt pinned to the managed CPU Dave Jiang
2026-07-29 15:23 ` sashiko-bot
2026-07-29 14:55 ` [PATCH v2 6/9] perf/cxl: Unfreeze counters after handling an overflow interrupt Dave Jiang
2026-07-29 14:55 ` [PATCH v2 7/9] perf/cxl: Validate the hardware-reported counter width Dave Jiang
2026-07-29 15:11 ` sashiko-bot
2026-07-29 14:55 ` [PATCH v2 8/9] perf/cxl: Don't use pmu.dev in IRQ and hotplug callbacks after unregister Dave Jiang
2026-07-29 15:34 ` sashiko-bot
2026-07-29 14:55 ` [PATCH v2 9/9] perf/cxl: Avoid cpumask_of(-1) when no CPU is assigned Dave Jiang
2026-07-29 15:19 ` 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=20260729145555.3919550-4-dave.jiang@intel.com \
--to=dave.jiang@intel.com \
--cc=dave@stgolabs.net \
--cc=jic23@kernel.org \
--cc=linux-cxl@vger.kernel.org \
--cc=linux-perf-users@vger.kernel.org \
--cc=mark.rutland@arm.com \
--cc=sashiko-bot@kernel.org \
--cc=will@kernel.org \
/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.