git: f19f44634afd - main - pmc: Add support for AMD UMC counters

From: Mitchell Horne <mhorne_at_FreeBSD.org>
Date: Thu, 03 Sep 2026 16:04:20 UTC
The branch main has been updated by mhorne:

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

commit f19f44634afd859e445c7b13bbdf715a493b499b
Author:     Ali Mashtizadeh <ali@mashtizadeh.com>
AuthorDate: 2026-08-13 23:08:20 +0000
Commit:     Mitchell Horne <mhorne@FreeBSD.org>
CommitDate: 2026-09-03 16:04:06 +0000

    pmc: Add support for AMD UMC counters
    
    This change adds support for AMD's UMC performance counters.  It is a
    bit more complicated than existing counters because the enable bit has
    moved.  This supports Zen 4 through most Zen 6 chips as UMC counters are
    per-node, where a node does not necessarily translate to a NUMA domain.
    A few follow up changes to PMC will address this limitation.
    
    Reviewed by:    mhorne
    Sponsored by:   Netflix
    Pull Request:   https://github.com/freebsd/freebsd-src/pull/2368
---
 lib/libpmc/libpmc_pmu_util.c    |  7 ++++
 lib/libpmc/pmu-events/jevents.c |  4 +-
 sys/dev/hwpmc/hwpmc_amd.c       | 92 ++++++++++++++++++++++++++++++++---------
 sys/dev/hwpmc/hwpmc_amd.h       | 31 +++++++++++++-
 4 files changed, 112 insertions(+), 22 deletions(-)

diff --git a/lib/libpmc/libpmc_pmu_util.c b/lib/libpmc/libpmc_pmu_util.c
index 050cbc120cd8..647fceabb05d 100644
--- a/lib/libpmc/libpmc_pmu_util.c
+++ b/lib/libpmc/libpmc_pmu_util.c
@@ -230,6 +230,7 @@ struct pmu_event_desc {
 	uint32_t ped_coreid;
 	uint32_t ped_allsources;
 	uint32_t ped_allcores;
+	uint32_t ped_rdwrmask;
 	uint32_t ped_event;
 	uint32_t ped_frontend;
 	uint32_t ped_ldlat;
@@ -378,6 +379,8 @@ pmu_parse_event(struct pmu_event_desc *ped, const char *eventin)
 			ped->ped_allcores = strtol(value, NULL, 0);
 		else if (strcmp(key, "allsources") == 0)
 			ped->ped_allsources = strtol(value, NULL, 0);
+		else if (strcmp(key, "rdwrmask") == 0)
+			ped->ped_rdwrmask = strtol(value, NULL, 0);
 		else if (strcmp(key, "pebs") == 0)
 			ped->ped_pebs = strtol(value, NULL, 10);
 		else {
@@ -589,6 +592,10 @@ pmc_pmu_amd_pmcallocate(const char *event_name, struct pmc_op_pmcallocate *pm,
 			amd->pm_amd_config |=
 			    AMD_PMC_DF2_TO_UNITMASK(ped->ped_umask);
 		}
+	} else if (strcmp("amd_umc", pe->pmu) == 0) {
+		amd->pm_amd_sub_class = PMC_AMD_SUB_CLASS_UMC;
+		amd->pm_amd_config |= AMD_PMC_UMC_TO_EVENTMASK(ped->ped_event);
+		amd->pm_amd_config |= AMD_PMC_UMC_TO_RDWRMASK(ped->ped_rdwrmask);
 	} else {
 		printf("PMC pmu '%s' is not supported!\n", pe->pmu);
 		return (EOPNOTSUPP);
diff --git a/lib/libpmc/pmu-events/jevents.c b/lib/libpmc/pmu-events/jevents.c
index b4d073f13708..7e6b19c1b30b 100644
--- a/lib/libpmc/pmu-events/jevents.c
+++ b/lib/libpmc/pmu-events/jevents.c
@@ -265,6 +265,7 @@ static struct map {
 	/* AMD */
 	{ "L3PMC", "amd_l3" },
 	{ "DFPMC", "amd_df" },
+	{ "UMCPMC", "amd_umc" },
 	/* ARM HiSilicon */
 	{ "hisi_sicl,cpa", "hisi_sicl,cpa"},
 	{ "hisi_sccl,ddrc", "hisi_sccl,ddrc" },
@@ -648,7 +649,8 @@ static int json_events(const char *fn,
 				if (nz)
 					addfield(map, &cmask, "", "cmask=", val);
 			} else if (json_streq(map, field, "RdWrMask")) {
-				/* AMD UMC */
+				if (nz)
+					addfield(map, &cmask, "", "rdwrmask=", val);
 			} else if (json_streq(map, field, "Invert")) {
 				if (nz)
 					addfield(map, &inv, "", "inv=", val);
diff --git a/sys/dev/hwpmc/hwpmc_amd.c b/sys/dev/hwpmc/hwpmc_amd.c
index 1afdb59ed745..5c4b14ad1003 100644
--- a/sys/dev/hwpmc/hwpmc_amd.c
+++ b/sys/dev/hwpmc/hwpmc_amd.c
@@ -64,7 +64,7 @@ struct amd_descr {
 };
 
 static int amd_npmcs;
-static int amd_core_npmcs, amd_l3_npmcs, amd_df_npmcs;
+static int amd_core_npmcs, amd_l3_npmcs, amd_df_npmcs, amd_umc_npmcs;
 static struct amd_descr amd_pmcdesc[AMD_NPMCS_MAX];
 struct amd_event_code_map {
 	enum pmc_event	pe_ev;	 /* enum value */
@@ -186,10 +186,12 @@ static struct amd_cpu **amd_pcpu;
 static uint64_t amd_core_allowed_mask;
 static uint64_t amd_l3_allowed_mask;
 static uint64_t amd_df_allowed_mask;
+static uint64_t amd_umc_allowed_mask;
 
 static uint64_t amd_core_extra_mask;
 static uint64_t amd_l3_extra_mask;
 static uint64_t amd_df_extra_mask;
+static uint64_t amd_umc_extra_mask;
 
 SYSCTL_DECL(_kern_hwpmc);
 
@@ -205,6 +207,10 @@ SYSCTL_U64(_kern_hwpmc, OID_AUTO, amd_df_extra_mask, CTLFLAG_RDTUN,
     &amd_df_extra_mask, 0,
     "Extra allowed bits in AMD DF PMU control (override; default 0)");
 
+SYSCTL_U64(_kern_hwpmc, OID_AUTO, amd_umc_extra_mask, CTLFLAG_RDTUN,
+    &amd_umc_extra_mask, 0,
+    "Extra allowed bits in AMD UMC PMU control (override; default 0)");
+
 static void
 amd_init_policy(void)
 {
@@ -219,6 +225,8 @@ amd_init_policy(void)
 
 	amd_df_allowed_mask = (family <= 0x19) ?
 	    AMD_PMC_DF_FAMILY17_MASK : AMD_PMC_DF_FAMILY1A_MASK;
+
+	amd_umc_allowed_mask = AMD_PMC_UMC_MASK;
 }
 
 static uint64_t
@@ -234,6 +242,8 @@ amd_config_mask(enum sub_class subclass, uint64_t caps)
 		return (amd_l3_allowed_mask | amd_l3_extra_mask);
 	case PMC_AMD_SUB_CLASS_DATA_FABRIC:
 		return (amd_df_allowed_mask | amd_df_extra_mask);
+	case PMC_AMD_SUB_CLASS_UMC:
+		return (amd_umc_allowed_mask | amd_umc_extra_mask);
 	default:
 		return (0);
 	}
@@ -526,16 +536,24 @@ amd_start_pmc(int cpu __diagused, int ri, struct pmc *pm)
 	PMCDBG2(MDP, STA, 1, "amd-start cpu=%d ri=%d", cpu, ri);
 
 	/*
-	 * Triggered by DF counters because all DF MSRs are shared.  We need to
-	 * change the code to honor the per-package flag in the JSON event
-	 * definitions.
+	 * Asserts triggered by DF/UMC counters because all DF/UMC MSRs are
+	 * shared.  While userspace now honors the per-node flags, we should
+	 * enforce this in the kernel.
 	 */
-	KASSERT(AMD_PMC_IS_STOPPED(pd->pm_evsel),
-	    ("[amd,%d] pmc%d,cpu%d: Starting active PMC \"%s\"", __LINE__,
-	    ri, cpu, pd->pm_descr.pd_name));
-
 	/* turn on the PMC ENABLE bit */
-	config = pm->pm_md.pm_amd.pm_amd_evsel | AMD_PMC_ENABLE;
+	if (pd->pm_subclass == PMC_AMD_SUB_CLASS_UMC) {
+		KASSERT(AMD_PMC_UMC_IS_STOPPED(pd->pm_evsel),
+		    ("[amd,%d] pmc%d,cpu%d: Starting active PMC \"%s\"",
+		    __LINE__, ri, cpu, pd->pm_descr.pd_name));
+
+		config = pm->pm_md.pm_amd.pm_amd_evsel | AMD_PMC_UMC_ENABLE;
+	} else {
+		KASSERT(AMD_PMC_IS_STOPPED(pd->pm_evsel),
+		    ("[amd,%d] pmc%d,cpu%d: Starting active PMC \"%s\"",
+		    __LINE__, ri, cpu, pd->pm_descr.pd_name));
+
+		config = pm->pm_md.pm_amd.pm_amd_evsel | AMD_PMC_ENABLE;
+	}
 
 	PMCDBG1(MDP, STA, 2, "amd-start config=0x%x", config);
 
@@ -560,14 +578,22 @@ amd_stop_pmc(int cpu __diagused, int ri, struct pmc *pm)
 
 	pd = &amd_pmcdesc[ri];
 
-	KASSERT(!AMD_PMC_IS_STOPPED(pd->pm_evsel),
-	    ("[amd,%d] PMC%d, CPU%d \"%s\" already stopped",
-		__LINE__, ri, cpu, pd->pm_descr.pd_name));
-
 	PMCDBG1(MDP, STO, 1, "amd-stop ri=%d", ri);
 
 	/* turn off the PMC ENABLE bit */
-	config = pm->pm_md.pm_amd.pm_amd_evsel & ~AMD_PMC_ENABLE;
+	if (pd->pm_subclass == PMC_AMD_SUB_CLASS_UMC) {
+		KASSERT(!AMD_PMC_UMC_IS_STOPPED(pd->pm_evsel),
+		    ("[amd,%d] PMC%d, CPU%d \"%s\" already stopped",
+		    __LINE__, ri, cpu, pd->pm_descr.pd_name));
+
+		config = pm->pm_md.pm_amd.pm_amd_evsel & ~AMD_PMC_UMC_ENABLE;
+	} else {
+		KASSERT(!AMD_PMC_IS_STOPPED(pd->pm_evsel),
+		    ("[amd,%d] PMC%d, CPU%d \"%s\" already stopped",
+		    __LINE__, ri, cpu, pd->pm_descr.pd_name));
+
+		config = pm->pm_md.pm_amd.pm_amd_evsel & ~AMD_PMC_ENABLE;
+	}
 	wrmsr(pd->pm_evsel, config);
 
 	/*
@@ -654,12 +680,22 @@ amd_intr(struct trapframe *tf)
 		v       = pm->pm_sc.pm_reloadcount;
 		config  = rdmsr(evsel);
 
-		KASSERT((config & ~AMD_PMC_ENABLE) ==
-		    (pm->pm_md.pm_amd.pm_amd_evsel & ~AMD_PMC_ENABLE),
-		    ("[amd,%d] config mismatch reg=0x%jx pm=0x%jx", __LINE__,
-			 (uintmax_t)config, (uintmax_t)pm->pm_md.pm_amd.pm_amd_evsel));
 
-		wrmsr(evsel, config & ~AMD_PMC_ENABLE);
+		if (amd_pmcdesc[i].pm_subclass == PMC_AMD_SUB_CLASS_UMC) {
+			KASSERT((config & ~AMD_PMC_UMC_ENABLE) ==
+			    (pm->pm_md.pm_amd.pm_amd_evsel & ~AMD_PMC_UMC_ENABLE),
+			    ("[amd,%d] config mismatch reg=0x%jx pm=0x%jx", __LINE__,
+			    (uintmax_t)config, (uintmax_t)pm->pm_md.pm_amd.pm_amd_evsel));
+
+			wrmsr(evsel, config & ~AMD_PMC_UMC_ENABLE);
+		} else {
+			KASSERT((config & ~AMD_PMC_ENABLE) ==
+			    (pm->pm_md.pm_amd.pm_amd_evsel & ~AMD_PMC_ENABLE),
+			    ("[amd,%d] config mismatch reg=0x%jx pm=0x%jx", __LINE__,
+			    (uintmax_t)config, (uintmax_t)pm->pm_md.pm_amd.pm_amd_evsel));
+
+			wrmsr(evsel, config & ~AMD_PMC_ENABLE);
+		}
 		wrmsr(perfctr, AMD_RELOAD_COUNT_TO_PERFCTR_VALUE(v));
 
 		/* Restart the counter if logging succeeded. */
@@ -953,6 +989,7 @@ pmc_amd_initialize(void)
 	int ncpus, nclasses, i;
 	int family, model, stepping;
 	int error;
+	int pmcs_per_umc;
 
 	/*
 	 * The presence of hardware performance counters on the AMD
@@ -996,12 +1033,16 @@ pmc_amd_initialize(void)
 	}
 	amd_l3_npmcs = AMD_PMC_L3_DEFAULT;
 	amd_df_npmcs = AMD_PMC_DF_DEFAULT;
+	amd_umc_npmcs = 0;
+	pmcs_per_umc = 0;
 
 	if (cpu_exthigh >= CPUID_EXTPERFMON) {
 		do_cpuid(CPUID_EXTPERFMON, regs);
 		if (regs[1] != 0) {
 			amd_core_npmcs = EXTPERFMON_CORE_PMCS(regs[1]);
 			amd_df_npmcs = EXTPERFMON_DF_PMCS(regs[1]);
+			amd_umc_npmcs = EXTPERFMON_UMC_PMCS(regs[1]);
+			pmcs_per_umc = amd_umc_npmcs / popcntq(regs[2]);
 		}
 	}
 
@@ -1064,6 +1105,19 @@ pmc_amd_initialize(void)
 		amd_npmcs += amd_df_npmcs;
 	}
 
+	for (i = 0; i < amd_umc_npmcs; i++) {
+		d = &amd_pmcdesc[amd_npmcs + i];
+		snprintf(d->pm_descr.pd_name, PMC_NAME_MAX,
+		    "K8-UMC%d-%d", i / pmcs_per_umc, i);
+		d->pm_descr.pd_class = PMC_CLASS_K8;
+		d->pm_descr.pd_caps = AMD_PMC_UMC_CAPS;
+		d->pm_descr.pd_width = 48;
+		d->pm_evsel = AMD_PMC_UMC_BASE + 2 * i;
+		d->pm_perfctr = AMD_PMC_UMC_BASE + 2 * i + 1;
+		d->pm_subclass = PMC_AMD_SUB_CLASS_UMC;
+	}
+	amd_npmcs += amd_umc_npmcs;
+
 	/*
 	 * Sanity check that the hardware is safe to use.  Do not read or write
 	 * any of the PMC MSRs until after this check passes.
diff --git a/sys/dev/hwpmc/hwpmc_amd.h b/sys/dev/hwpmc/hwpmc_amd.h
index 616d115ecc25..4a8a5f6b6433 100644
--- a/sys/dev/hwpmc/hwpmc_amd.h
+++ b/sys/dev/hwpmc/hwpmc_amd.h
@@ -35,6 +35,7 @@
 #define	CPUID_EXTPERFMON	0x80000022
 #define	EXTPERFMON_CORE_PMCS(x)	((x) & 0x0F)
 #define	EXTPERFMON_DF_PMCS(x)	(((x) >> 10) & 0x3F)
+#define	EXTPERFMON_UMC_PMCS(x)	(((x) >> 16) & 0xFF)
 
 /* AMD K8 PMCs */
 #define	AMD_PMC_EVSEL_0		0xC0010000
@@ -170,9 +171,34 @@
 	 AMD_PMC_DF2_TO_EVENTMASK(0x7fff) |				\
 	 AMD_PMC_DF2_TO_UNITMASK(0xfff))
 
+/*
+ * UMC counters
+ *
+ * Refer to the following documents:
+ * PPR for AMD Family 1Ah Model 02h C1 57238 Rev. 0.49 March 6, 2026
+ */
+
+#define	AMD_PMC_UMC_BASE	0xC0010800
+#define	AMD_PMC_UMC_MAX		256
+
+#define	AMD_PMC_UMC_CAPS	(PMC_CAP_READ | PMC_CAP_WRITE | \
+	PMC_CAP_QUALIFIER | PMC_CAP_DOMWIDE)
+
+#define	AMD_PMC_UMC_ENABLE	0x80000000
+#define	AMD_PMC_UMC_RDWRMASK	0x00000300
+#define	AMD_PMC_UMC_EVENTMASK	0x000000FF
+
+#define	AMD_PMC_UMC_MASK	(AMD_PMC_UMC_ENABLE | AMD_PMC_UMC_RDWRMASK | \
+				 AMD_PMC_UMC_EVENTMASK)
+
+#define	AMD_PMC_UMC_IS_STOPPED(evsel) ((rdmsr((evsel)) & AMD_PMC_UMC_ENABLE) == 0)
+
+#define	AMD_PMC_UMC_TO_EVENTMASK(x)	((x) & AMD_PMC_UMC_EVENTMASK)
+#define	AMD_PMC_UMC_TO_RDWRMASK(x)	(((x) << 8) & AMD_PMC_UMC_RDWRMASK)
+
 #define	AMD_NPMCS_K8		4
 #define AMD_NPMCS_MAX		(AMD_PMC_CORE_MAX + AMD_PMC_L3_MAX + \
-				 AMD_PMC_DF_MAX)
+				 AMD_PMC_DF_MAX + AMD_PMC_UMC_MAX)
 
 #define AMD_PMC_IS_STOPPED(evsel) ((rdmsr((evsel)) & AMD_PMC_ENABLE) == 0)
 #define AMD_PMC_HAS_OVERFLOWED(pmc) ((rdpmc(pmc) & (1ULL << 47)) == 0)
@@ -183,7 +209,8 @@
 enum sub_class {
 	PMC_AMD_SUB_CLASS_CORE,
 	PMC_AMD_SUB_CLASS_L3_CACHE,
-	PMC_AMD_SUB_CLASS_DATA_FABRIC
+	PMC_AMD_SUB_CLASS_DATA_FABRIC,
+	PMC_AMD_SUB_CLASS_UMC
 };
 
 struct pmc_md_amd_op_pmcallocate {