diff --git a/drivers/perf/cxl_pmu.c b/drivers/perf/cxl_pmu.c index 8232dc566bd5..4b1827d74bde 100644 --- a/drivers/perf/cxl_pmu.c +++ b/drivers/perf/cxl_pmu.c @@ -802,7 +802,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); @@ -810,12 +810,16 @@ 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 mask discards the bit that says the counter wrapped, so a delta of + * one whole period reads back as 0 - the same as no events at all. Only + * the overflow status separates them, and new_cnt >= prev_cnt is that + * case, so add the period back. mask + 1 is 2^counter_width, which comes + * out as 0 for a 64-bit counter and avoids an 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); }