[PATCH v6 05/13] alpha/perf: ignore overflow interrupts the counter does not account for
From: Matt Turner
Date: Wed Nov 04 2026 - 14:52:21 EST
The interrupt handler adds a full counter period to the count of the
event on the counter that interrupted, which goes wrong in two cases.
Rewriting a counter does not cancel its pending interrupt: wrperfmon has
no way to, and PALcode clears the request only when delivering it. An
interrupt pending when an event is reloaded, or when perf moves another
event onto the counter, is then taken as an overflow of a counter that
has not overflowed. It adds a bogus period to the count and can emit a
false sample.
And alpha_perf_event_update() already adds the period when a read finds
that the counter overflowed before its interrupt was taken. The
interrupt then adds it again.
An overflow leaves the counter just past zero, below the value it was
written with, while a counter that has not overflowed is at or above it.
Ignore an interrupt for a counter in the latter state, unless a read
has already counted the overflow, in which case add nothing. For this to
hold, write the counters at most half their range below overflow, as
armpmu_event_set_period() does. Counting events then take an interrupt
every 2^19 events rather than every 2^20.
Fixes: 979f867191f8 ("alpha: implement HW performance events on the EV67 and later CPUs")
Suggested-by: Ian Rogers <irogers@xxxxxxxxxx>
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Matt Turner <mattst88@xxxxxxxxx>
---
arch/alpha/kernel/perf_event.c | 59 +++++++++++++++++++++++++++++++++++++-----
1 file changed, 52 insertions(+), 7 deletions(-)
diff --git a/arch/alpha/kernel/perf_event.c b/arch/alpha/kernel/perf_event.c
index ecec78c355cd..dd7fc4ac1396 100644
--- a/arch/alpha/kernel/perf_event.c
+++ b/arch/alpha/kernel/perf_event.c
@@ -30,6 +30,9 @@
#define MAX_HWEVENTS 3
#define PMC_NO_INDEX -1
+/* hw_perf_event.flags */
+#define ALPHA_PERF_FLAG_WRAPPED 0x1
+
/* For tracking PMCs and the hw events they monitor on each CPU. */
struct cpu_hw_events {
int enabled;
@@ -315,10 +318,16 @@ static int alpha_perf_event_set_period(struct perf_event *event,
if (unlikely(left < alpha_pmu->pmc_left[idx]))
left = alpha_pmu->pmc_left[idx];
- if (left > (long)alpha_pmu->pmc_max_period[idx])
- left = alpha_pmu->pmc_max_period[idx];
+ /*
+ * Start at most half the counter's range below overflow, so that the
+ * value an overflow leaves, just past zero, is always below the value
+ * written. See alpha_perf_event_ovf().
+ */
+ if (left > (long)(alpha_pmu->pmc_max_period[idx] >> 1))
+ left = alpha_pmu->pmc_max_period[idx] >> 1;
local64_set(&hwc->prev_count, (unsigned long)(-left));
+ hwc->flags &= ~ALPHA_PERF_FLAG_WRAPPED;
alpha_write_pmc(idx, (unsigned long)(-left));
@@ -338,9 +347,9 @@ static int alpha_perf_event_set_period(struct perf_event *event,
* zero. The value passed via ovf by the interrupt handler corrects for
* overflow.
*
- * This can be racey on rare occasions -- a call to this routine can occur
- * with an overflowed counter just before the PMI service routine is called.
- * The check for delta negative hopefully always rectifies this situation.
+ * A call to this routine can occur with an overflowed counter just before
+ * the PMI service routine is called. A negative delta detects this, as the
+ * counter is always written at most half its range below overflow.
*/
static unsigned long alpha_perf_event_update(struct perf_event *event,
struct hw_perf_event *hwc, int idx, long ovf)
@@ -359,10 +368,12 @@ static unsigned long alpha_perf_event_update(struct perf_event *event,
delta = (new_raw_count - (prev_raw_count & alpha_pmu->pmc_count_mask[idx])) + ovf;
/* It is possible on very rare occasions that the PMC has overflowed
- * but the interrupt is yet to come. Detect and fix this situation.
+ * but the interrupt is yet to come. Detect and fix this situation,
+ * and tell the interrupt not to count the overflow again.
*/
if (unlikely(delta < 0)) {
delta += alpha_pmu->pmc_max_period[idx] + 1;
+ hwc->flags |= ALPHA_PERF_FLAG_WRAPPED;
}
local64_add(delta, &event->count);
@@ -835,6 +846,32 @@ void perf_event_print_debug(void)
}
+/*
+ * Return the overflow that an interrupt from counter idx adds to the count,
+ * or -1 if the counter has not overflowed since it was written.
+ *
+ * Rewriting a counter does not cancel its pending interrupt: wrperfmon has
+ * no way to, and PALcode clears the request only when delivering it. So an
+ * interrupt can arrive after the counter was reloaded or given to another
+ * event. The counter is then at or above the value written, whereas an
+ * overflow leaves it just past zero, below that value.
+ */
+static long alpha_perf_event_ovf(struct hw_perf_event *hwc, int idx)
+{
+ unsigned long prev;
+
+ /* alpha_perf_event_update() has already counted this overflow. */
+ if (hwc->flags & ALPHA_PERF_FLAG_WRAPPED)
+ return 0;
+
+ prev = local64_read(&hwc->prev_count) & alpha_pmu->pmc_count_mask[idx];
+ if (alpha_read_pmc(idx) >= prev)
+ return -1;
+
+ return alpha_pmu->pmc_max_period[idx] + 1;
+}
+
+
/*
* Performance Monitoring Interrupt Service Routine called when a PMC
* overflows. The PMC that overflowed is passed in la_ptr.
@@ -847,6 +884,7 @@ static void alpha_perf_event_irq_handler(unsigned long la_ptr,
struct perf_event *event;
struct hw_perf_event *hwc;
int idx, j;
+ long ovf;
__this_cpu_inc(irq_pmi_count);
cpuc = this_cpu_ptr(&cpu_hw_events);
@@ -891,7 +929,14 @@ static void alpha_perf_event_irq_handler(unsigned long la_ptr,
}
hwc = &event->hw;
- alpha_perf_event_update(event, hwc, idx, alpha_pmu->pmc_max_period[idx]+1);
+ ovf = alpha_perf_event_ovf(hwc, idx);
+ if (ovf < 0) {
+ /* The interrupt was pending when the counter was written. */
+ wrperfmon(PERFMON_CMD_ENABLE, cpuc->idx_mask);
+ return;
+ }
+
+ alpha_perf_event_update(event, hwc, idx, ovf);
perf_sample_data_init(&data, 0, hwc->last_period);
if (alpha_perf_event_set_period(event, hwc, idx))
--
2.55.0