Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F162696499
D57760.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
21 KB
Referenced Files
None
Subscribers
None
D57760.diff
View Options
diff --git a/sys/conf/files.x86 b/sys/conf/files.x86
--- a/sys/conf/files.x86
+++ b/sys/conf/files.x86
@@ -62,6 +62,7 @@
dev/agp/agp_amd64.c optional agp
dev/agp/agp_i810.c optional agp
dev/agp/agp_via.c optional agp
+dev/amdrapl/amdrapl.c optional amdrapl smp
dev/amdsmu/amdsmu.c optional amdsmu pci
dev/amdsbwd/amdsbwd.c optional amdsbwd
dev/amdsmn/amdsmn.c optional amdsmn | amdtemp
diff --git a/sys/dev/amdrapl/amdrapl.c b/sys/dev/amdrapl/amdrapl.c
new file mode 100644
--- /dev/null
+++ b/sys/dev/amdrapl/amdrapl.c
@@ -0,0 +1,697 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Advanced Micro Devices, Inc.
+ *
+ * AMD RAPL energy/power telemetry driver.
+ */
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/callout.h>
+#include <sys/cpu.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/priv.h>
+#include <sys/sbuf.h>
+#include <sys/seqc.h>
+#include <sys/smp.h>
+#include <sys/sysctl.h>
+
+#include <machine/cputypes.h>
+#include <machine/specialreg.h>
+
+#include <x86/x86_smp.h>
+#include <x86/x86_var.h>
+
+#define AMD_RAPL_DRIVER_NAME "amd_rapl"
+
+/* Worst-case package watts for sizing the guard timer; high keeps the wrap-safe interval conservative. */
+#define AMD_RAPL_GUARD_WATT 1000
+
+/* Guard interval clamp band (ms). */
+#define AMD_RAPL_GUARD_MIN_MS 10
+#define AMD_RAPL_GUARD_MAX_MS 60000
+
+#define AMD_RAPL_IDLE_GUARD_MULT 10
+
+/* Ceiling for idle_guard_mult */
+#define AMD_RAPL_IDLE_GUARD_MULT_MAX 1000000
+
+static MALLOC_DEFINE(M_AMDRAPL, "amdrapl", "AMD RAPL driver state");
+
+struct amd_rapl_value {
+ seqc_t seqc;
+ uint64_t prev;
+ sbintime_t prev_time;
+ uint64_t diff;
+ sbintime_t diff_time;
+ uint64_t accum;
+ bool primed;
+ bool lapsed;
+};
+
+struct amd_rapl_softc {
+ struct callout sampling_timer;
+ struct mtx mtx;
+ struct sysctl_ctx_list clist;
+ uint32_t energy_unit;
+ uint64_t max_energy_uj;
+ sbintime_t guard_sbt;
+ sbintime_t lapse_sbt;
+ volatile uint64_t last_read;
+ u_int idle_guard_mult;
+ bool force_guard;
+ bool has_core;
+ bool has_package;
+ bool dying;
+ device_t dev;
+ int npackages;
+ int *cpu_pkg_slot;
+ cpuset_t package_leads;
+ int ncores;
+ int *cpu_core_slot;
+ cpuset_t core_leads;
+ struct amd_rapl_value *core_value;
+ struct amd_rapl_value *package_value;
+};
+
+/*
+ * Convert a raw energy counter to microjoules without overflowing uint64_t.
+ */
+static uint64_t
+amd_rapl_raw_to_uj(struct amd_rapl_softc *sc, uint64_t raw)
+{
+ uint64_t unit, whole, frac;
+
+ unit = 1UL << sc->energy_unit;
+ whole = raw / unit;
+ frac = raw % unit;
+ return (whole * 1000000UL + (frac * 1000000UL) / unit);
+}
+
+static uint64_t
+amd_rapl_count_ujoules(struct amd_rapl_softc *sc, struct amd_rapl_value *val)
+{
+ uint64_t accum;
+ seqc_t s;
+
+ do {
+ s = seqc_read(&val->seqc);
+ accum = val->accum;
+ } while (seqc_consistent(&val->seqc, s) == false);
+ return (amd_rapl_raw_to_uj(sc, accum));
+}
+
+/* Power in milliwatts: energy (uJ) over the measured interval (us). */
+static uint64_t
+amd_rapl_count_watt(struct amd_rapl_softc *sc, struct amd_rapl_value *val)
+{
+ uint64_t dt_us, energy_uj, diff;
+ sbintime_t dt;
+ seqc_t s;
+
+ /* Read diff and diff_time as a pair; a concurrent writer will
+ * be safe */
+ do {
+ s = seqc_read(&val->seqc);
+ diff = val->diff;
+ dt = val->diff_time;
+ } while (seqc_consistent(&val->seqc, s) == false);
+
+ dt_us = sbttous(dt);
+ if (dt_us == 0)
+ return (0);
+ energy_uj = amd_rapl_raw_to_uj(sc, diff);
+ return (energy_uj * 1000 / dt_us);
+}
+
+static void
+amd_rapl_update_delta(struct amd_rapl_softc *sc, struct amd_rapl_value *val,
+ uint64_t cur)
+{
+ sbintime_t now = sbinuptime();
+
+ cur &= UINT32_MAX;
+ seqc_write_begin(&val->seqc);
+ if (!val->primed) {
+ val->prev = cur;
+ val->prev_time = now;
+ val->diff = 0;
+ val->diff_time = 0;
+ val->primed = true;
+ seqc_write_end(&val->seqc);
+ return;
+ }
+ /* Skip sub-ms re-samples. */
+ if (now - val->prev_time < SBT_1MS) {
+ seqc_write_end(&val->seqc);
+ return;
+ }
+ if (cur >= val->prev)
+ val->diff = cur - val->prev;
+ else
+ val->diff = (UINT32_MAX - val->prev) + cur + 1;
+ val->diff_time = now - val->prev_time;
+
+ /* Wrap correction recovers only one 32-bit wrap. */
+ if (val->diff_time > sc->lapse_sbt)
+ val->lapsed = true;
+ val->accum += val->diff;
+ val->prev = cur;
+ val->prev_time = now;
+ seqc_write_end(&val->seqc);
+}
+
+static void
+amd_rapl_read_core_energy(void *arg)
+{
+ struct amd_rapl_softc *sc = arg;
+ uint64_t cur;
+
+ if (rdmsr_safe(MSR_AMD_CORE_ENERGY_STATUS, &cur) != 0)
+ return;
+ amd_rapl_update_delta(sc, &sc->core_value[sc->cpu_core_slot[curcpu]],
+ cur);
+}
+
+/*
+ * Sample every core counter. The per-core MSR is shared by a core's SMT
+ * siblings, so rendezvous only the core leads (one CPU per physical core), not
+ * all_cpus. The rendezvous IPI mutex serializes invocations; do NOT hold
+ * sc->mtx (no rendezvous under it).
+ */
+static void
+amd_rapl_sample_cores(struct amd_rapl_softc *sc)
+{
+ smp_rendezvous_cpus(sc->core_leads, smp_no_rendezvous_barrier,
+ amd_rapl_read_core_energy, smp_no_rendezvous_barrier, sc);
+}
+
+/* Read this CPU's package MSR and put it into its slot. */
+static void
+amd_rapl_read_package_energy(void *arg)
+{
+ struct amd_rapl_softc *sc = arg;
+ uint64_t cur;
+
+ if (rdmsr_safe(MSR_AMD_PKG_ENERGY_STATUS, &cur) != 0)
+ return;
+ amd_rapl_update_delta(sc, &sc->package_value[sc->cpu_pkg_slot[curcpu]],
+ cur);
+}
+
+/*
+ * Sample every package counter. Socket's MSR, so rendezvous only the
+ * package leads, must NOT hold sc->mtx.
+ */
+static void
+amd_rapl_sample_package(struct amd_rapl_softc *sc)
+{
+ smp_rendezvous_cpus(sc->package_leads, smp_no_rendezvous_barrier,
+ amd_rapl_read_package_energy, smp_no_rendezvous_barrier, sc);
+}
+
+static void amd_rapl_sample(void *arg);
+
+/* Clamp idle_guard_mult to [2, MAX] so mult * guard_sbt cannot overflow. */
+static u_int
+amd_rapl_clamp_mult(u_int mult)
+{
+ if (mult < 2)
+ return (2);
+ if (mult > AMD_RAPL_IDLE_GUARD_MULT_MAX)
+ return (AMD_RAPL_IDLE_GUARD_MULT_MAX);
+ return (mult);
+}
+
+/* Arm the guard callout. Caller holds sc->mtx. */
+static void
+amd_rapl_schedule_guard(struct amd_rapl_softc *sc)
+{
+ callout_reset_sbt(&sc->sampling_timer, sc->guard_sbt, sc->guard_sbt / 10,
+ amd_rapl_sample, sc, 0);
+}
+
+/* Periodic overflow guard: bounds 32-bit wrap during read silence. */
+static void
+amd_rapl_sample(void *arg)
+{
+ struct amd_rapl_softc *sc = arg;
+ sbintime_t now, last;
+ u_int mult;
+
+ mtx_unlock(&sc->mtx);
+ if (sc->has_core)
+ amd_rapl_sample_cores(sc);
+ if (sc->has_package)
+ amd_rapl_sample_package(sc);
+
+ /*
+ * Demand-gate: fire while consumers read; self-disarm after
+ * idle_guard_mult quiet periods unless force_guard.
+ */
+ now = sbinuptime();
+ last = (sbintime_t)atomic_load_acq_64(&sc->last_read);
+ mult = amd_rapl_clamp_mult(sc->idle_guard_mult);
+
+ /* Reschedule under sc->mtx. Once dying is set, never re-arm */
+ mtx_lock(&sc->mtx);
+ if (!sc->dying &&
+ (sc->force_guard || (now - last) < (sbintime_t)mult * sc->guard_sbt))
+ amd_rapl_schedule_guard(sc);
+ mtx_unlock(&sc->mtx);
+}
+
+/*
+ * Re-arm the guard if not already pending. Requires sc->mtx.
+ */
+static void
+amd_rapl_arm_guard(struct amd_rapl_softc *sc)
+{
+ mtx_lock(&sc->mtx);
+ if (!sc->dying && !callout_pending(&sc->sampling_timer))
+ amd_rapl_schedule_guard(sc);
+ mtx_unlock(&sc->mtx);
+}
+
+/*
+ * Per-core read entry: sample, stamp last_read, keep the guard armed.
+ */
+static void
+amd_rapl_note_read_cores(struct amd_rapl_softc *sc)
+{
+ /* Suspended: leave the baseline for resume to re-prime. */
+ if (sc->dying)
+ return;
+ amd_rapl_sample_cores(sc);
+ atomic_store_rel_64(&sc->last_read, (uint64_t)sbinuptime());
+ amd_rapl_arm_guard(sc);
+}
+
+/*
+ * Per-package read entry: sample, stamp last_read, keep the guard armed.
+ */
+static void
+amd_rapl_note_read_package(struct amd_rapl_softc *sc)
+{
+ /* See amd_rapl_note_read_cores(). */
+ if (sc->dying)
+ return;
+ amd_rapl_sample_package(sc);
+ atomic_store_rel_64(&sc->last_read, (uint64_t)sbinuptime());
+ amd_rapl_arm_guard(sc);
+}
+
+/* force_guard write hook. */
+static int
+sysctl_amd_rapl_force_guard(SYSCTL_HANDLER_ARGS)
+{
+ struct amd_rapl_softc *sc = arg1;
+ int error;
+
+ error = sysctl_handle_bool(oidp, &sc->force_guard, 0, req);
+ if (error != 0 || req->newptr == NULL)
+ return (error);
+ if (sc->force_guard)
+ amd_rapl_arm_guard(sc);
+ return (0);
+}
+
+/* Accessor for the lapsed status OIDs. */
+static uint64_t
+amd_rapl_count_lapsed(struct amd_rapl_softc *sc __unused,
+ struct amd_rapl_value *val)
+{
+ return (val->lapsed ? 1 : 0);
+}
+
+enum amd_rapl_oid_id {
+ AMD_RAPL_OID_PKG_W,
+ AMD_RAPL_OID_CORE_W,
+ AMD_RAPL_OID_PKG_UJ,
+ AMD_RAPL_OID_CORE_UJ,
+ AMD_RAPL_OID_PKG_LAPSED,
+ AMD_RAPL_OID_CORE_LAPSED,
+};
+
+struct amd_rapl_oid {
+ bool is_core;
+ bool sample;
+ uint64_t (*get)(struct amd_rapl_softc *, struct amd_rapl_value *);
+};
+
+static const struct amd_rapl_oid amd_rapl_oids[] = {
+ [AMD_RAPL_OID_PKG_W] = { false, true, amd_rapl_count_watt },
+ [AMD_RAPL_OID_CORE_W] = { true, true, amd_rapl_count_watt },
+ [AMD_RAPL_OID_PKG_UJ] = { false, true, amd_rapl_count_ujoules },
+ [AMD_RAPL_OID_CORE_UJ] = { true, true, amd_rapl_count_ujoules },
+ [AMD_RAPL_OID_PKG_LAPSED] = { false, false, amd_rapl_count_lapsed },
+ [AMD_RAPL_OID_CORE_LAPSED] = { true, false, amd_rapl_count_lapsed },
+};
+
+/*
+ * One handler for every per-domain CSV sysctl, keyed by arg2 (an amd_rapl_oids
+ * index). Energy/power OIDs are a power side channel (PLATYPUS, CVE-2020-8694),
+ * so they require privilege and sample on read; the lapsed status OIDs are an
+ * unprivileged bare poll that neither samples nor re-arms, so it costs no IPIs.
+ */
+static int
+sysctl_amd_rapl_display(SYSCTL_HANDLER_ARGS)
+{
+ const struct amd_rapl_oid *o = &amd_rapl_oids[arg2];
+ struct amd_rapl_softc *sc = arg1;
+ struct amd_rapl_value *value;
+ struct sbuf sbs, *sb;
+ int err, i, n;
+
+ value = o->is_core ? sc->core_value : sc->package_value;
+ n = o->is_core ? sc->ncores : sc->npackages;
+ if (o->sample) {
+ err = priv_check(req->td, PRIV_DRIVER);
+ if (err != 0)
+ return (err);
+ /* Size pass. */
+ if (req->oldptr == NULL)
+ return (SYSCTL_OUT(req, 0, 21 * n + 1));
+ if (o->is_core)
+ amd_rapl_note_read_cores(sc);
+ else
+ amd_rapl_note_read_package(sc);
+ }
+ sb = sbuf_new_for_sysctl(&sbs, NULL, 0, req);
+ for (i = 0; i < n; i++)
+ sbuf_printf(sb, i == 0 ? "%ju" : ",%ju",
+ (uintmax_t)o->get(sc, &value[i]));
+ err = sbuf_finish(sb);
+ sbuf_delete(sb);
+ return (err);
+}
+
+static void
+amd_rapl_identify(driver_t *driver, device_t parent)
+{
+ device_t child;
+
+ /* One global instance. */
+ if (device_get_unit(parent) != 0)
+ return;
+ if (cpu_vendor_id != CPU_VENDOR_AMD &&
+ cpu_vendor_id != CPU_VENDOR_HYGON)
+ return;
+ if (!(amd_pminfo & AMDPM_RAPL))
+ return;
+ if (device_find_child(parent, AMD_RAPL_DRIVER_NAME, DEVICE_UNIT_ANY) !=
+ NULL)
+ return;
+ child = device_add_child(parent, AMD_RAPL_DRIVER_NAME, DEVICE_UNIT_ANY);
+ if (child == NULL)
+ device_printf(parent,
+ "add " AMD_RAPL_DRIVER_NAME " child failed\n");
+}
+
+static int
+amd_rapl_probe(device_t dev)
+{
+ if (cpu_vendor_id != CPU_VENDOR_AMD &&
+ cpu_vendor_id != CPU_VENDOR_HYGON)
+ return (ENXIO);
+ if (!(amd_pminfo & AMDPM_RAPL))
+ return (ENXIO);
+ if (resource_disabled(AMD_RAPL_DRIVER_NAME, 0))
+ return (ENXIO);
+ /* Only attach the first unit. */
+ if (device_get_unit(dev) != 0)
+ return (ENXIO);
+ device_set_desc(dev, "AMD RAPL");
+ return (0);
+
+}
+
+/*
+ * Map each CPU to a slot and pick one lead CPU per slot, so a socket-scoped or
+ * SMT-shared MSR is sampled once per real socket/core.
+ */
+static void
+amd_rapl_build_map(int (*id_of)(int cpu), int **cpu_slot, int *count,
+ cpuset_t *leads)
+{
+ int *id_of_slot;
+ int cpu, i, id, slot, n;
+
+ *cpu_slot = malloc(sizeof(int) * (mp_maxid + 1), M_AMDRAPL,
+ M_WAITOK | M_ZERO);
+ id_of_slot = malloc(sizeof(int) * mp_ncpus, M_TEMP, M_WAITOK | M_ZERO);
+ CPU_ZERO(leads);
+ n = 0;
+ CPU_FOREACH(cpu) {
+ id = id_of(cpu);
+ slot = -1;
+ for (i = 0; i < n; i++) {
+ if (id_of_slot[i] == id) {
+ slot = i;
+ break;
+ }
+ }
+ if (slot == -1) {
+ slot = n++;
+ id_of_slot[slot] = id;
+ CPU_SET(cpu, leads);
+ }
+ (*cpu_slot)[cpu] = slot;
+ }
+ *count = n;
+ free(id_of_slot, M_TEMP);
+}
+
+/* RAPL MSR presence probe. */
+static bool
+amd_rapl_msr_present(u_int msr, u_int cpuid)
+{
+ uint64_t v;
+
+ return (x86_msr_op(msr, MSR_OP_RENDEZVOUS_ONE | MSR_OP_READ |
+ MSR_OP_SAFE | MSR_OP_CPUID(cpuid), 0, &v) == 0);
+}
+
+/*
+ * Guard interval from the hardware energy unit. The counter wraps after
+ * max_energy_uj; at AMD_RAPL_GUARD_WATT that is max_energy_uj/(P*1e6) seconds,
+ * so sample at half that to avoid an unseen double-wrap.
+ */
+static sbintime_t
+amd_rapl_guard_sbt(struct amd_rapl_softc *sc)
+{
+ uint64_t guard_ms;
+
+ guard_ms = sc->max_energy_uj / (2000ULL * AMD_RAPL_GUARD_WATT);
+ if (guard_ms < AMD_RAPL_GUARD_MIN_MS)
+ guard_ms = AMD_RAPL_GUARD_MIN_MS;
+ else if (guard_ms > AMD_RAPL_GUARD_MAX_MS)
+ guard_ms = AMD_RAPL_GUARD_MAX_MS;
+ return (guard_ms * SBT_1MS);
+}
+
+static int
+amd_rapl_attach(device_t dev)
+{
+ struct amd_rapl_softc *sc = device_get_softc(dev);
+ u_int probe_cpu = cpu_get_pcpu(dev)->pc_cpuid;
+ uint64_t value;
+ int error;
+
+ sc->dev = dev;
+
+ /* Read the power-unit MSR fault-safely. */
+ error = x86_msr_op(MSR_AMD_RAPL_POWER_UNIT,
+ MSR_OP_RENDEZVOUS_ONE | MSR_OP_READ | MSR_OP_SAFE |
+ MSR_OP_CPUID(probe_cpu),
+ 0, &value);
+ if (error != 0) {
+ device_printf(dev, "power-unit MSR (0x%x) read faulted: %d\n",
+ MSR_AMD_RAPL_POWER_UNIT, error);
+ return (ENXIO);
+ }
+ sc->energy_unit = (value >> 8) & 0x1f;
+ sc->max_energy_uj = amd_rapl_raw_to_uj(sc, UINT32_MAX);
+ sc->guard_sbt = amd_rapl_guard_sbt(sc);
+
+ /* Lapse threshold = one wrap period = twice the guard interval. */
+ sc->lapse_sbt = 2 * sc->guard_sbt;
+ sc->idle_guard_mult = AMD_RAPL_IDLE_GUARD_MULT;
+ sc->force_guard = false;
+ sc->dying = false;
+ sc->has_package = amd_rapl_msr_present(MSR_AMD_PKG_ENERGY_STATUS,
+ probe_cpu);
+ sc->has_core = amd_rapl_msr_present(MSR_AMD_CORE_ENERGY_STATUS,
+ probe_cpu);
+ if (!sc->has_package && !sc->has_core) {
+ device_printf(dev,
+ "no RAPL energy MSR responded; not attaching\n");
+ return (ENXIO);
+ }
+ atomic_store_rel_64(&sc->last_read, (uint64_t)sbinuptime());
+ /* Package = physical socket (cpu_get_pkg_id). */
+ amd_rapl_build_map(cpu_get_pkg_id, &sc->cpu_pkg_slot, &sc->npackages,
+ &sc->package_leads);
+ /* Core = physical core (cpu_get_core_id); SMT siblings share a slot. */
+ amd_rapl_build_map(cpu_get_core_id, &sc->cpu_core_slot, &sc->ncores,
+ &sc->core_leads);
+ sc->core_value = malloc(sizeof(struct amd_rapl_value) * sc->ncores,
+ M_AMDRAPL, M_WAITOK | M_ZERO);
+ sc->package_value = malloc(sizeof(struct amd_rapl_value) * sc->npackages,
+ M_AMDRAPL, M_WAITOK | M_ZERO);
+ mtx_init(&sc->mtx, AMD_RAPL_DRIVER_NAME, NULL, MTX_DEF);
+ callout_init_mtx(&sc->sampling_timer, &sc->mtx, CALLOUT_RETURNUNLOCKED);
+ sysctl_ctx_init(&sc->clist);
+ if (sc->has_package) {
+ SYSCTL_ADD_PROC(&sc->clist,
+ SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
+ "package_mwatt",
+ CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE,
+ sc, AMD_RAPL_OID_PKG_W, sysctl_amd_rapl_display, "A",
+ "Average package power in milliwatts over the most recent "
+ "sampling interval (the previous read or overflow-guard "
+ "tick), comma-separated per domain");
+ SYSCTL_ADD_PROC(&sc->clist,
+ SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
+ "package_energy_uj",
+ CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE,
+ sc, AMD_RAPL_OID_PKG_UJ, sysctl_amd_rapl_display, "A",
+ "Cumulative package energy in microjoules, comma-separated per "
+ "domain (cumulative across active-monitoring windows unless "
+ "force_guard=1)");
+ SYSCTL_ADD_PROC(&sc->clist,
+ SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
+ "package_energy_lapsed",
+ CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE,
+ sc, AMD_RAPL_OID_PKG_LAPSED, sysctl_amd_rapl_display, "A",
+ "Per-domain 0/1: 1 means a sample gap exceeded one worst-case "
+ "wrap period and package_energy_uj may have lost a counter "
+ "wrap (see force_guard)");
+ }
+ if (sc->has_core) {
+ SYSCTL_ADD_PROC(&sc->clist,
+ SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
+ "cores_mwatt",
+ CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE,
+ sc, AMD_RAPL_OID_CORE_W, sysctl_amd_rapl_display, "A",
+ "Average power in milliwatts over the most recent sampling "
+ "interval (the previous read or overflow-guard tick), "
+ "comma-separated per physical core");
+ SYSCTL_ADD_PROC(&sc->clist,
+ SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
+ "cores_energy_uj",
+ CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE,
+ sc, AMD_RAPL_OID_CORE_UJ, sysctl_amd_rapl_display, "A",
+ "Cumulative per-core energy in microjoules, comma-separated per "
+ "physical core (cumulative across active-monitoring windows "
+ "unless force_guard=1)");
+ SYSCTL_ADD_PROC(&sc->clist,
+ SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
+ "cores_energy_lapsed",
+ CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE,
+ sc, AMD_RAPL_OID_CORE_LAPSED, sysctl_amd_rapl_display, "A",
+ "Per-core 0/1: 1 means a sample gap exceeded one worst-case "
+ "wrap period and cores_energy_uj may have lost a counter "
+ "wrap (see force_guard)");
+ }
+ SYSCTL_ADD_UINT(&sc->clist,
+ SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
+ "energy_unit", CTLFLAG_RD, &sc->energy_unit, 0,
+ "RAPL energy unit as a power-of-two shift (1 count = 1/2^unit J)");
+ SYSCTL_ADD_U64(&sc->clist,
+ SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
+ "max_energy_uj", CTLFLAG_RD, &sc->max_energy_uj, 0,
+ "Energy in microjoules of the maximum hardware counter value (2^32-1)");
+ SYSCTL_ADD_PROC(&sc->clist,
+ SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
+ "force_guard", CTLTYPE_U8 | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ sc, 0, sysctl_amd_rapl_force_guard, "CU",
+ "Pin the overflow-guard sampler always-on. When 0 (default) the guard "
+ "self-disarms while no consumer is reading and *_energy_uj counters are "
+ "cumulative across active-monitoring windows rather than since attach");
+ SYSCTL_ADD_UINT(&sc->clist,
+ SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
+ "idle_guard_mult", CTLFLAG_RWTUN, &sc->idle_guard_mult, 0,
+ "Idle disarm threshold as a multiple of the guard interval (>=2)");
+ sc->idle_guard_mult = amd_rapl_clamp_mult(sc->idle_guard_mult);
+ mtx_lock(&sc->mtx);
+ amd_rapl_schedule_guard(sc);
+ mtx_unlock(&sc->mtx);
+ return (0);
+}
+
+static int
+amd_rapl_detach(device_t dev)
+{
+ struct amd_rapl_softc *sc = device_get_softc(dev);
+
+ /*
+ * Order matters:
+ * 1. sysctl_ctx_free() unregisters OIDs and drains in-flight handlers.
+ * 2. sc->dying stops amd_rapl_sample() rescheduling.
+ */
+ sysctl_ctx_free(&sc->clist);
+ mtx_lock(&sc->mtx);
+ sc->dying = true;
+ mtx_unlock(&sc->mtx);
+ callout_drain(&sc->sampling_timer);
+ mtx_destroy(&sc->mtx);
+ free(sc->cpu_pkg_slot, M_AMDRAPL);
+ free(sc->cpu_core_slot, M_AMDRAPL);
+ free(sc->core_value, M_AMDRAPL);
+ free(sc->package_value, M_AMDRAPL);
+ return (0);
+}
+
+static int
+amd_rapl_suspend(device_t dev)
+{
+ struct amd_rapl_softc *sc = device_get_softc(dev);
+
+ mtx_lock(&sc->mtx);
+ sc->dying = true;
+ mtx_unlock(&sc->mtx);
+ callout_drain(&sc->sampling_timer);
+ return (0);
+}
+
+static int
+amd_rapl_resume(device_t dev)
+{
+ struct amd_rapl_softc *sc = device_get_softc(dev);
+ int i;
+
+ /* Re-baseline so the first post-resume sample is a zero delta. */
+ for (i = 0; i < sc->ncores; i++)
+ sc->core_value[i].primed = false;
+ for (i = 0; i < sc->npackages; i++)
+ sc->package_value[i].primed = false;
+ atomic_store_rel_64(&sc->last_read, (uint64_t)sbinuptime());
+ mtx_lock(&sc->mtx);
+ sc->dying = false;
+ amd_rapl_schedule_guard(sc);
+ mtx_unlock(&sc->mtx);
+ return (0);
+}
+
+static device_method_t amd_rapl_methods[] = {
+ DEVMETHOD(device_identify, amd_rapl_identify),
+ DEVMETHOD(device_probe, amd_rapl_probe),
+ DEVMETHOD(device_attach, amd_rapl_attach),
+ DEVMETHOD(device_detach, amd_rapl_detach),
+ DEVMETHOD(device_suspend, amd_rapl_suspend),
+ DEVMETHOD(device_resume, amd_rapl_resume),
+ DEVMETHOD_END
+};
+
+static driver_t amd_rapl_driver = {
+ AMD_RAPL_DRIVER_NAME,
+ amd_rapl_methods,
+ sizeof(struct amd_rapl_softc),
+};
+
+DRIVER_MODULE(amd_rapl, cpu, amd_rapl_driver, 0, 0);
diff --git a/sys/modules/Makefile b/sys/modules/Makefile
--- a/sys/modules/Makefile
+++ b/sys/modules/Makefile
@@ -34,6 +34,7 @@
alq \
${_amd_ecc_inject} \
${_amdgpio} \
+ ${_amdrapl} \
${_amdsmu} \
${_amdsbwd} \
${_amdsmn} \
@@ -800,6 +801,7 @@
_aesni= aesni
.endif
_amd_ecc_inject=amd_ecc_inject
+_amdrapl= amdrapl
_amdsmu= amdsmu
_amdsbwd= amdsbwd
_amdsmn= amdsmn
diff --git a/sys/modules/amdrapl/Makefile b/sys/modules/amdrapl/Makefile
new file mode 100644
--- /dev/null
+++ b/sys/modules/amdrapl/Makefile
@@ -0,0 +1,8 @@
+.PATH: ${SRCTOP}/sys/dev/amdrapl
+
+KMOD= amdrapl
+SRCS= amdrapl.c bus_if.h device_if.h
+
+EXPORT_SYMS= YES
+
+.include <bsd.kmod.mk>
diff --git a/sys/x86/conf/NOTES b/sys/x86/conf/NOTES
--- a/sys/x86/conf/NOTES
+++ b/sys/x86/conf/NOTES
@@ -629,6 +629,9 @@
#
device superio
+# Running Average Power Limit
+device amdrapl
+
#####################################################################
# More undocumented options for linting.
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Thu, Jul 16, 9:23 PM (10 h, 38 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
35149313
Default Alt Text
D57760.diff (21 KB)
Attached To
Mode
D57760: amdrapl: add AMD RAPL energy/power telemetry driver
Attached
Detach File
Event Timeline
Log In to Comment