git: 1e0ae0d055d2 - stable/15 - hwpmc: handle counter wraparound for process-mode counting PMCs

Alexander Leidinger <[email protected]> Mon, 03 Aug 2026 17:29:43 +0000
Newsgroups gmane.os.freebsd.devel.cvs.src
Message-ID <6a70d007.47043.295ac39e__12383.8198416465$1785778226$gmane$org@gitrepo.freebsd.org>
The branch stable/15 has been updated by netchild:

URL: https://cgit.FreeBSD.org/src/commit/?id=1e0ae0d055d2f91db5a9c2cf19459621b470b294

commit 1e0ae0d055d2f91db5a9c2cf19459621b470b294
Author:     Alexander Leidinger <[email protected]>
AuthorDate: 2026-07-19 07:38:52 +0000
Commit:     Alexander Leidinger <[email protected]>
CommitDate: 2026-08-03 17:29:24 +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
    
    (cherry picked from commit e42703f5c4b2d3a869b17e2cb8670f1b6359c8cf)
---
 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 04e23ae24c31..c8b193eee944 100644
--- a/sys/dev/hwpmc/hwpmc_mod.c
+++ b/sys/dev/hwpmc/hwpmc_mod.c
@@ -1589,6 +1589,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.
  */
@@ -1601,8 +1619,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;
@@ -1740,23 +1757,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;
@@ -5067,7 +5080,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;