git: e42703f5c4b2 - main - hwpmc: handle counter wraparound for process-mode counting PMCs
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Mon, 20 Jul 2026 10:25:13 UTC
The branch main has been updated by netchild:
URL: https://cgit.FreeBSD.org/src/commit/?id=e42703f5c4b2d3a869b17e2cb8670f1b6359c8cf
commit e42703f5c4b2d3a869b17e2cb8670f1b6359c8cf
Author: Alexander Leidinger <netchild@FreeBSD.org>
AuthorDate: 2026-07-19 07:38:52 +0000
Commit: Alexander Leidinger <netchild@FreeBSD.org>
CommitDate: 2026-07-20 10:24:18 +0000
hwpmc: handle counter wraparound for process-mode counting PMCs
The accumulated count of a process-mode counting PMC is kept in a
64-bit software counter and seeded into the hardware counter at every
context switch in. Hardware counters are narrower than that - each
PMC class discovers and records its own counter width, e.g. 48 bits
on current x86 (queried from CPUID on Intel, architectural on AMD) -
so once the accumulated count approaches the end of the hardware
counter range, the counter wraps during a time slice and the value
read back at switch out is smaller than the value seeded. The
increment was computed assuming a full 64-bit counter: on INVARIANTS
kernels a long enough counting run panics with "negative increment"
the moment the accumulated count first crosses the hardware counter
range, and on other kernels the totals silently lose a full counter
range per wrap.
Compute the increment modulo the per-class hardware counter width
instead, in both places that accumulate switch-out deltas.
Reviewed by: adrian
MFC after: 2 weeks
Assisted-by: Claude Code (Fable 5)
Differential Revision: https://reviews.freebsd.org/D58340
---
sys/dev/hwpmc/hwpmc_mod.c | 46 ++++++++++++++++++++++++++++++----------------
1 file changed, 30 insertions(+), 16 deletions(-)
diff --git a/sys/dev/hwpmc/hwpmc_mod.c b/sys/dev/hwpmc/hwpmc_mod.c
index 9533cb81b4a1..086a05657cfe 100644
--- a/sys/dev/hwpmc/hwpmc_mod.c
+++ b/sys/dev/hwpmc/hwpmc_mod.c
@@ -1594,6 +1594,24 @@ pmc_process_csw_in(struct thread *td)
critical_exit();
}
+/*
+ * Compute the change in a counter's value since it was last written.
+ * The hardware counter is only pcd_width bits wide and wraps around,
+ * while the value seeded into it may occupy the full 64-bit range, so
+ * take the difference modulo the counter width.
+ */
+static pmc_value_t
+pmc_delta(const struct pmc_classdep *pcd, pmc_value_t newvalue,
+ pmc_value_t oldvalue)
+{
+ pmc_value_t delta;
+
+ delta = newvalue - oldvalue;
+ if (pcd->pcd_width < 64)
+ delta &= ((pmc_value_t)1 << pcd->pcd_width) - 1;
+ return (delta);
+}
+
/*
* Thread context switch OUT.
*/
@@ -1606,8 +1624,7 @@ pmc_process_csw_out(struct thread *td)
struct pmc_process *pp;
struct pmc_thread *pt = NULL;
struct proc *p;
- pmc_value_t newvalue;
- int64_t tmp;
+ pmc_value_t newvalue, tmp;
enum pmc_mode mode;
int cpu;
u_int adjri, ri;
@@ -1745,23 +1762,19 @@ pmc_process_csw_out(struct thread *td)
}
mtx_pool_unlock_spin(pmc_mtxpool, pm);
} else {
- tmp = newvalue - PMC_PCPU_SAVED(cpu, ri);
+ /*
+ * For counting process-virtual PMCs, the
+ * hardware counter's value increases
+ * monotonically modulo the counter width;
+ * pmc_delta() recovers the increment even
+ * when the counter wrapped during the run.
+ */
+ tmp = pmc_delta(pcd, newvalue,
+ PMC_PCPU_SAVED(cpu, ri));
PMCDBG3(CSW,SWO,1,"cpu=%d ri=%d tmp=%jd (count)",
cpu, ri, tmp);
- /*
- * For counting process-virtual PMCs,
- * we expect the count to be
- * increasing monotonically, modulo a 64
- * bit wraparound.
- */
- KASSERT(tmp >= 0,
- ("[pmc,%d] negative increment cpu=%d "
- "ri=%d newvalue=%jx saved=%jx "
- "incr=%jx", __LINE__, cpu, ri,
- newvalue, PMC_PCPU_SAVED(cpu, ri), tmp));
-
mtx_pool_lock_spin(pmc_mtxpool, pm);
pm->pm_gv.pm_savedvalue += tmp;
pp->pp_pmcs[ri].pp_pmcval += tmp;
@@ -5177,7 +5190,8 @@ pmc_process_exit(void *arg __unused, struct proc *p)
if (PMC_TO_MODE(pm) == PMC_MODE_TC) {
pcd->pcd_read_pmc(cpu, adjri, pm,
&newvalue);
- tmp = newvalue - PMC_PCPU_SAVED(cpu, ri);
+ tmp = pmc_delta(pcd, newvalue,
+ PMC_PCPU_SAVED(cpu, ri));
mtx_pool_lock_spin(pmc_mtxpool, pm);
pm->pm_gv.pm_savedvalue += tmp;