Date: Tue, 28 Jul 2026 15:01:28 +0000 From: Mitchell Horne <mhorne@FreeBSD.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org Cc: Andre Silva <andasilv@amd.com> Subject: git: 3fdd05c61216 - main - libpmc: userland support and pmc.rapl.3 for the RAPL class Message-ID: <6a68c448.1f1ac.2c28beec@gitrepo.freebsd.org>
index | next in thread | raw e-mail
The branch main has been updated by mhorne: URL: https://cgit.FreeBSD.org/src/commit/?id=3fdd05c612169d56c30b90d72533b75383210e81 commit 3fdd05c612169d56c30b90d72533b75383210e81 Author: Andre Silva <andasilv@amd.com> AuthorDate: 2026-07-28 14:52:22 +0000 Commit: Mitchell Horne <mhorne@FreeBSD.org> CommitDate: 2026-07-28 14:54:26 +0000 libpmc: userland support and pmc.rapl.3 for the RAPL class Register PMC_CLASS_RAPL in libpmc: event table, allocator, class-table descriptor, and the event-name/class-listing lookups, all x86-guarded and modeled on the TSC class. Energy events are read-only and unqualified. The class prefix (RAPL-) supplies the friendly spelling, so pmcstat -S rapl-energy-pkg resolves to the canonical ENERGY_PKG event. Add a pmc.rapl.3 manual page documenting the events, counter scope, the microjoule unit and wrap handling, and the NUMA/package domain mapping; link it from pmc.3. Reviewed by: mhorne Discussed with: Ali Mashtizadeh <ali@mashtizadeh.com> Sponsored by: AMD Differential Revision: https://reviews.freebsd.org/D58029 --- lib/libpmc/Makefile | 1 + lib/libpmc/libpmc.c | 35 +++++++++++++ lib/libpmc/pmc.3 | 8 +++ lib/libpmc/pmc.rapl.3 | 138 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 182 insertions(+) diff --git a/lib/libpmc/Makefile b/lib/libpmc/Makefile index 442efdc3d9c0..d86191cc35cb 100644 --- a/lib/libpmc/Makefile +++ b/lib/libpmc/Makefile @@ -77,6 +77,7 @@ MAN+= pmc.iaf.3 MAN+= pmc.ibs.3 MAN+= pmc.ivybridge.3 MAN+= pmc.ivybridgexeon.3 +MAN+= pmc.rapl.3 MAN+= pmc.sandybridge.3 MAN+= pmc.sandybridgeuc.3 MAN+= pmc.sandybridgexeon.3 diff --git a/lib/libpmc/libpmc.c b/lib/libpmc/libpmc.c index 63228773216e..5fc4630540fc 100644 --- a/lib/libpmc/libpmc.c +++ b/lib/libpmc/libpmc.c @@ -58,6 +58,8 @@ static int ibs_allocate_pmc(enum pmc_event _pe, char *_ctrspec, struct pmc_op_pmcallocate *_pmc_config); static int tsc_allocate_pmc(enum pmc_event _pe, char *_ctrspec, struct pmc_op_pmcallocate *_pmc_config); +static int rapl_allocate_pmc(enum pmc_event _pe, char *_ctrspec, + struct pmc_op_pmcallocate *_pmc_config); #endif #if defined(__arm__) static int armv7_allocate_pmc(enum pmc_event _pe, char *_ctrspec, @@ -191,6 +193,11 @@ static const struct pmc_event_descr tsc_event_table[] = __PMC_EV_ALIAS_TSC() }; +static const struct pmc_event_descr rapl_event_table[] = +{ + __PMC_EV_RAPL() +}; + #undef PMC_CLASS_TABLE_DESC #define PMC_CLASS_TABLE_DESC(NAME, CLASS, EVENTS, ALLOCATOR) \ static const struct pmc_class_descr NAME##_class_table_descr = \ @@ -208,6 +215,7 @@ static const struct pmc_class_descr NAME##_class_table_descr = \ PMC_CLASS_TABLE_DESC(k8, K8, k8, k8); PMC_CLASS_TABLE_DESC(ibs, IBS, ibs, ibs); PMC_CLASS_TABLE_DESC(tsc, TSC, tsc, tsc); +PMC_CLASS_TABLE_DESC(rapl, RAPL, rapl, rapl); #endif #if defined(__arm__) PMC_CLASS_TABLE_DESC(cortex_a8, ARMV7, cortex_a8, armv7); @@ -879,6 +887,22 @@ tsc_allocate_pmc(enum pmc_event pe, char *ctrspec, return (0); } + +static int +rapl_allocate_pmc(enum pmc_event pe, char *ctrspec, + struct pmc_op_pmcallocate *pmc_config) +{ + if (pe < PMC_EV_RAPL_FIRST || pe > PMC_EV_RAPL_LAST) + return (-1); + + /* RAPL events must be unqualified. */ + if (ctrspec != NULL && *ctrspec != '\0') + return (-1); + + pmc_config->pm_caps |= PMC_CAP_READ; + + return (0); +} #endif static struct pmc_event_alias generic_aliases[] = { @@ -1435,6 +1459,10 @@ pmc_event_names_of_class(enum pmc_class cl, const char ***eventnames, ev = tsc_event_table; count = PMC_EVENT_TABLE_SIZE(tsc); break; + case PMC_CLASS_RAPL: + ev = rapl_event_table; + count = PMC_EVENT_TABLE_SIZE(rapl); + break; case PMC_CLASS_K8: ev = k8_event_table; count = PMC_EVENT_TABLE_SIZE(k8); @@ -1643,6 +1671,10 @@ pmc_init(void) pmc_class_table[n++] = &tsc_class_table_descr; break; + case PMC_CLASS_RAPL: + pmc_class_table[n++] = &rapl_class_table_descr; + break; + case PMC_CLASS_K8: pmc_class_table[n++] = &k8_class_table_descr; break; @@ -1915,6 +1947,9 @@ _pmc_name_of_event(enum pmc_event pe, enum pmc_cputype cpu) } else if (pe == PMC_EV_TSC_TSC) { ev = tsc_event_table; evfence = tsc_event_table + PMC_EVENT_TABLE_SIZE(tsc); + } else if (pe >= PMC_EV_RAPL_FIRST && pe <= PMC_EV_RAPL_LAST) { + ev = rapl_event_table; + evfence = rapl_event_table + PMC_EVENT_TABLE_SIZE(rapl); } else if ((int)pe >= PMC_EV_SOFT_FIRST && (int)pe <= PMC_EV_SOFT_LAST) { ev = soft_event_table; evfence = soft_event_table + soft_event_info.pm_nevent; diff --git a/lib/libpmc/pmc.3 b/lib/libpmc/pmc.3 index cb28e0b786b9..b04c2d39cf69 100644 --- a/lib/libpmc/pmc.3 +++ b/lib/libpmc/pmc.3 @@ -233,6 +233,12 @@ Family 10h and above. Programmable hardware counters present in .Tn "AMD Athlon64" CPUs. +.It Li PMC_CLASS_RAPL +RAPL energy counters present in +.Tn AMD +and +.Tn Intel +CPUs. .It Li PMC_CLASS_TSC The timestamp counter on i386 and amd64 architecture CPUs. .It Li PMC_CLASS_ARMV7 @@ -498,6 +504,7 @@ following manual pages: .It Li PMC_CLASS_IAP Ta Xr pmc.atom 3 , Xr pmc.core 3 , Xr pmc.core2 3 .It Li PMC_CLASS_IBS Ta Xr pmc.ibs 3 .It Li PMC_CLASS_K8 Ta Xr pmc.amd 3 +.It Li PMC_CLASS_RAPL Ta Xr pmc.rapl 3 .It Li PMC_CLASS_TSC Ta Xr pmc.tsc 3 .El .Ss Event Name Aliases @@ -551,6 +558,7 @@ Doing otherwise is unsupported. .Xr pmc.ibs 3 , .Xr pmc.ivybridge 3 , .Xr pmc.ivybridgexeon 3 , +.Xr pmc.rapl 3 , .Xr pmc.sandybridge 3 , .Xr pmc.sandybridgeuc 3 , .Xr pmc.sandybridgexeon 3 , diff --git a/lib/libpmc/pmc.rapl.3 b/lib/libpmc/pmc.rapl.3 new file mode 100644 index 000000000000..e49d9845bbba --- /dev/null +++ b/lib/libpmc/pmc.rapl.3 @@ -0,0 +1,138 @@ +.\" +.\" Copyright (c) 2026 Advanced Micro Devices, Inc. +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd July 6, 2026 +.Dt PMC.RAPL 3 +.Os +.Sh NAME +.Nm pmc.rapl +.Nd measurements using RAPL energy counters +.Sh LIBRARY +.Lb libpmc +.Sh SYNOPSIS +.In pmc.h +.Sh DESCRIPTION +.Tn AMD +and +.Tn Intel +processors report cumulative energy consumption through the RAPL +(Running Average Power Limit) machine specific registers. +The +.Li PMC_CLASS_RAPL +class exposes these registers as read-only 64-bit counters that may +only be allocated in system-wide counting mode +.Pq Li PMC_MODE_SC . +Counter values are cumulative energy in microjoules. +The kernel scales the raw hardware counts by the hardware energy unit, +so no further conversion is required. +.Pp +A counter's accumulator is not reset when a PMC is allocated or +released; it reflects energy consumed since the counters began +accumulating and is reset only when the +.Xr hwpmc 4 +module is unloaded. +An individual reading is therefore meaningful only as a difference +between two samples. +.Pp +The hardware counters are 32 bits wide and wrap frequently under load. +The RAPL PMC class instead provides virtual 64-bit counters that are +guaranteed to be monotonic, by periodically sampling the hardware +counters while at least one RAPL PMC is allocated. +.Pp +RAPL events do not support further event qualifiers. +.Ss Event Specifiers +The following event names are supported: +.Bl -tag -width indent +.It Li rapl-energy-pkg +Energy consumed by the processor package (socket) the counter is bound +to. +.It Li rapl-energy-cores +On +.Tn AMD +processors, energy consumed by the physical core the counter is bound +to. +On +.Tn Intel +processors, energy consumed by the PP0 (all cores) power plane of the +package, i.e. a package-scoped value. +.It Li rapl-energy-dram +Energy consumed by the DRAM domain of the package. +Present only on +.Tn Intel +processors that implement the DRAM domain, typically server parts. +.El +.Ss Counter Scope +RAPL counters measure package or core power domains, not individual +CPUs: every CPU of a domain reads the same underlying counter. +Consumers should bind one PMC per domain and must not sum readings +from CPUs that share a domain. +.Pp +The class advertises the +.Li PMC_CAP_DOMWIDE +capability, which makes +.Xr pmcstat 8 +allocate one counter per NUMA domain instead of one per CPU. +NUMA domains follow the firmware memory policy and need not match +packages, in either direction: +.Bl -bullet +.It +With several NUMA domains per package (for example +.Tn AMD +NPS2/NPS4 or +.Tn Intel +Sub-NUMA Clustering), +.Xr pmcstat 8 +allocates one counter per domain, so package-scoped counters within +one package alias each other: each reading is correct on its own, +but they must not be summed. +.It +With a single NUMA domain spanning several packages (for example +.Tn AMD +NPS0 memory interleaving, NUMA disabled in firmware, or a kernel +built without NUMA support), +.Xr pmcstat 8 +allocates only one counter, on the first configured CPU, and the +remaining packages are silently not measured. +.El +.Pp +For explicit placement, allocate one system-scope PMC per package +with +.Xr pmc_allocate 3 , +or give +.Xr pmcstat 8 +one +.Fl c Ar cpu +and +.Fl s Ar event +pair per package, for example on a two-socket system whose second +package starts at CPU 64: +.Dl pmcstat -c 0 -s rapl-energy-pkg -c 64 -s rapl-energy-pkg +A single +.Fl c +list naming several CPUs does not achieve this: for +.Li PMC_CAP_DOMWIDE +counters +.Xr pmcstat 8 +honors only the first CPU of the list. +.Ss Privilege +Allocating a RAPL PMC always requires the +.Li PRIV_PMC_SYSTEM +privilege, regardless of the +.Va security.bsd.unprivileged_syspmcs +sysctl: fine-grained energy readings form a power side channel +(PLATYPUS, CVE-2020-8694 and CVE-2020-12912). +Unprivileged allocation requests fail with +.Er EPERM . +.Sh SEE ALSO +.Xr pmc 3 , +.Xr pmc.tsc 3 , +.Xr pmc_allocate 3 , +.Xr pmc_read 3 , +.Xr pmclog 3 , +.Xr hwpmc 4 , +.Xr pmcstat 8 +.Sh HISTORY +The RAPL PMC class first appeared in +.Fx 16.0 .home | help
Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?6a68c448.1f1ac.2c28beec>
