git: dd9e49a10bc1 - main - intel/intelpmc: Add Intel PMC Core driver

From: Abdelkader Boudih <seuros_at_FreeBSD.org>
Date: Sun, 13 Sep 2026 20:36:36 UTC
The branch main has been updated by seuros:

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

commit dd9e49a10bc14f7d876426d7d85fed0d1fe9699d
Author:     Abdelkader Boudih <seuros@FreeBSD.org>
AuthorDate: 2026-09-13 20:36:16 +0000
Commit:     Abdelkader Boudih <seuros@FreeBSD.org>
CommitDate: 2026-09-13 20:36:16 +0000

    intel/intelpmc: Add Intel PMC Core driver
    
    Add driver for Intel Power Management Controller (PMC) found on Sunrise Point PCH chipsets.
    This device exposes S0ix sleep state residency counters and power management status.
    
    Sysctls provided:
      dev.intelpmc.0.slp_s0_residency_us - Time in deepest sleep (us)
      dev.intelpmc.0.ltr_ignore          - LTR ignore mask
      dev.intelpmc.0.pm_cfg              - PM configuration register
      dev.intelpmc.0.pm_sts              - PM status register
      dev.intelpmc.0.access_denied       - Firmware lock status
    
    Supported devices for now:
      - Sunrise Point-LP (0x9D21)
      - Sunrise Point-H (0xA121)
    
    Note: Later PCH generations (Cannon Lake, Tiger Lake, ect.) have different PMC register layouts according to the datasheet and would need per-generation tables.
    I avoided adding untested hardware in case they have a quirk.
    
    Reviewed by:    olce, adrian
    Differential Revision:  https://reviews.freebsd.org/D54881
---
 share/man/man4/Makefile        |   1 +
 share/man/man4/intel_pmc.4     |  83 ++++++++++++++++
 sys/conf/files.x86             |   1 +
 sys/dev/intel/intel_pmc.c      | 219 +++++++++++++++++++++++++++++++++++++++++
 sys/modules/Makefile           |   2 +
 sys/modules/intel_pmc/Makefile |   6 ++
 6 files changed, 312 insertions(+)

diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile
index 07307c59308c..7a5ef9f397eb 100644
--- a/share/man/man4/Makefile
+++ b/share/man/man4/Makefile
@@ -262,6 +262,7 @@ MAN=	aac.4 \
 	${_imcsmb.4} \
 	inet.4 \
 	inet6.4 \
+	intel_pmc.4 \
 	intpm.4 \
 	intro.4 \
 	${_io.4} \
diff --git a/share/man/man4/intel_pmc.4 b/share/man/man4/intel_pmc.4
new file mode 100644
index 000000000000..1f12e8532675
--- /dev/null
+++ b/share/man/man4/intel_pmc.4
@@ -0,0 +1,83 @@
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.\" Copyright (c) 2026 Abdelkader Boudih <seuros@FreeBSD.org>
+.\"
+.Dd September 7, 2026
+.Dt INTEL_PMC 4
+.Os
+.Sh NAME
+.Nm intel_pmc
+.Nd Intel Performance Management Controller driver
+.Sh SYNOPSIS
+.Cd "device intel_pmc"
+.Sh DESCRIPTION
+The
+.Nm
+driver exposes information and controls from the Performance
+Management Controller (PMC) of Intel platforms.
+Presently, it supports the Intel Sunrise Point platform (Skylake CPU
+generation), providing access to debug information and controls
+related to S0ix (Modern Standby).
+.Pp
+The cumulative time spent in low power mode while in S0 is reported,
+and the administrator can force the platform to ignore IP blocks
+whose Latency Tolerance Reporting (LTR) prevents the platform from
+entering S0ix.
+.Sh HARDWARE
+The
+.Nm
+driver supports the PMC block found in the following Intel Platform
+Controller Hubs:
+.Pp
+.Bl -bullet -compact
+.It
+Intel Sunrise Point-LP PMC (6th/7th Gen Core mobile, PCI ID 0x8086:0x9d21)
+.It
+Intel Sunrise Point-H PMC (6th/7th Gen Core desktop/server,
+PCI ID 0x8086:0xa121)
+.El
+.Sh SYSCTLS
+The following
+.Xr sysctl 8
+variables are available:
+.Bl -tag -width indent
+.It Va dev.intel_pmc.%d.ltr_ignore Pq read-write
+Latency Tolerance Reporting (LTR) ignore mask.
+Each bit corresponds to one IP block (bits 0-16).
+Setting a bit tells the PMC to ignore that IP's LTR requirements,
+allowing S0ix entry even if the IP would otherwise block it.
+A clear bit means the IP's requirements are considered and can
+prevent S0ix.
+.Pp
+Sunrise Point bit assignments:
+SOUTHPORT_A, SOUTHPORT_B, SATA, GIGABIT_ETHERNET, XHCI, reserved,
+ME, EVA, SOUTHPORT_C, HD_AUDIO, LPSS, SOUTHPORT_D, SOUTHPORT_E,
+CAMERA, ESPI, SCC, ISH.
+.It Va dev.intel_pmc.%d.slp_s0_residency Pq read-only
+Cumulative time in microseconds that the SLP_S0 signal has been
+asserted (100\ us per counter step).
+Per the datasheet, SLP_S0 asserts when the PCH is idle and the
+processor is in C10 state.
+A value that never advances indicates S0ix transitions are not occurring.
+.Pp
+The counter is 32-bit and wraps after approximately 5 days of
+cumulative S0ix residency.
+.El
+.Sh SEE ALSO
+.Xr acpi 4 ,
+.Xr pci 4 ,
+.Xr sysctl 8
+.Pp
+Intel 100 Series Chipset Family PCH Datasheet, Volume 2, Section 4.3.
+.Sh HISTORY
+The
+.Nm
+driver first appeared in
+.Fx 16.0 .
+.Sh AUTHORS
+.An Abdelkader Boudih Aq Mt seuros@FreeBSD.org
+.Sh CAVEATS
+Only Sunrise Point PCH is supported.
+Other PCH generations (Cannon Point, Tiger Lake, etc.) have different
+register layouts and are not handled by this driver.
diff --git a/sys/conf/files.x86 b/sys/conf/files.x86
index bb52975598ab..d0c131bf0730 100644
--- a/sys/conf/files.x86
+++ b/sys/conf/files.x86
@@ -159,6 +159,7 @@ dev/ichwd/i6300esbwd.c		optional	ichwd
 dev/ichwd/ichwd.c		optional	ichwd
 dev/imcsmb/imcsmb.c		optional	imcsmb
 dev/imcsmb/imcsmb_pci.c		optional	imcsmb pci
+dev/intel/intel_pmc.c	optional	intel_pmc pci
 dev/intel/pchtherm.c		optional	pchtherm
 dev/intel/spi.c			optional	intelspi
 dev/intel/spi_pci.c			optional	intelspi pci
diff --git a/sys/dev/intel/intel_pmc.c b/sys/dev/intel/intel_pmc.c
new file mode 100644
index 000000000000..ee437cc08b5d
--- /dev/null
+++ b/sys/dev/intel/intel_pmc.c
@@ -0,0 +1,219 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Abdelkader Boudih <seuros@FreeBSD.org>
+ */
+
+/*
+ * intel_pmc -- Intel Performance Management Controller (PMC) driver
+ *
+ * Exposes information and controls from the PMC of Intel platforms.
+ * Only the Sunrise Point (SPT) platform, paired with Skylake
+ * generation processors, is currently supported; everything below
+ * describes that hardware only.
+ *
+ * The driver reports the cumulative time spent in low power mode
+ * (S0ix, Modern Standby) while in S0, and lets the administrator
+ * force the PMC to ignore IP blocks whose Latency Tolerance
+ * Reporting (LTR) prevents the platform from entering S0ix.
+ *
+ * The PMC register space (PWRM) is mapped via the PWRM_BASE register
+ * at PCI config offset 0x48, not through BAR0.  See Intel 100 Series
+ * Chipset Family PCH Datasheet Vol 2, section 4.3.53.
+ *
+ * The SLP_S0_RESIDENCY counter increments (100 us per step) while the
+ * PCH SLP_S0 signal is asserted.  Per the datasheet, SLP_S0 asserts
+ * when the PCH is idle and the processor is in C10 state; in general
+ * this also requires ModPhy lanes power-gated and PLLs idle (Linux
+ * commit b740d2e9233cb).  A counter that never advances indicates
+ * S0ix transitions are not occurring.  The counter is 32-bit and
+ * wraps after approximately 5 days of cumulative S0ix residency.
+ *
+ * LTR_IGNORE (PWRM+0x30C) has one bit per IP block.  Setting a bit
+ * tells the PMC to ignore that IP's LTR requirements, allowing S0ix
+ * entry even if the IP would otherwise block it.  A clear bit means
+ * the IP's LTR requirements are considered and can prevent S0ix.
+ *
+ * SPT bit assignments (0-16):
+ *   0=SOUTHPORT_A     1=SOUTHPORT_B     2=SATA
+ *   3=GIGABIT_ETHERNET 4=XHCI           5=reserved
+ *   6=ME              7=EVA             8=SOUTHPORT_C
+ *   9=HD_AUDIO       10=LPSS           11=SOUTHPORT_D
+ *  12=SOUTHPORT_E    13=CAMERA         14=ESPI
+ *  15=SCC            16=ISH
+ *
+ * Ref: Intel 100 Series Chipset Family PCH Datasheet Vol 2, section 4.3
+ * Ref: Linux commit 9c2ee19987ef (initial SPT PMC support)
+ * Ref: Linux commit 2eb150558bb7 (LTR IP block documentation)
+ */
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/errno.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/rman.h>
+#include <sys/sysctl.h>
+#include <sys/systm.h>
+
+#include <machine/bus.h>
+#include <machine/resource.h>
+
+#include <vm/vm.h>
+#include <vm/pmap.h>
+#include <machine/pmap.h>
+
+#include <dev/pci/pcireg.h>
+#include <dev/pci/pcivar.h>
+
+/*
+ * Sunrise Point (SPT) PMC Register Definitions
+ * Reference: Intel 100 Series Chipset Family PCH Datasheet Vol 2.
+ * LTR_IGNORE is not documented there; the offset comes from Linux
+ * commit 9c2ee19987ef (initial SPT PMC support).
+ */
+#define SPT_PMC_ACTL_OFFSET		0x44	/* ACPI Control */
+#define SPT_PMC_ACTL_PWRM_EN		(1u << 8) /* PWRM decode enabled */
+#define SPT_PMC_PWRM_BASE_OFFSET	0x48	/* PCI config offset for PWRM */
+#define SPT_PMC_PWRM_BASE_MASK		0xfffff000
+#define SPT_PMC_MMIO_SIZE		0x1000
+
+#define SPT_PMC_SLP_S0_RES_OFFSET	0x13C
+#define SPT_PMC_SLP_S0_RES_STEP	100	/* 100us per datasheet */
+#define SPT_PMC_LTR_IGNORE_OFFSET	0x30C
+#define SPT_PMC_LTR_IGNORE_BITS	17	/* bits 0-16 are valid IP blocks */
+
+struct intel_pmc_softc {
+	bus_space_tag_t		bst;
+	bus_space_handle_t	bsh;
+};
+
+/*
+ * Only Sunrise Point - other generations have different layouts.
+ * 0xa121 is documented in the Intel 100 Series Chipset Family PCH
+ * Datasheet Vol 2.  Sunrise Point-LP has no public datasheet; 0x9d21
+ * comes from the pci_vendors database, where Linux also sourced it.
+ */
+static const struct pci_device_table intel_pmc_devices[] = {
+	{ PCI_DEV(0x8086, 0x9d21), PCI_DESCR("Sunrise Point-LP PMC")},
+	{ PCI_DEV(0x8086, 0xa121), PCI_DESCR("Sunrise Point-H PMC")},
+};
+
+static inline uint32_t
+intel_pmc_read(struct intel_pmc_softc *sc, uint32_t offset)
+{
+
+	return (bus_space_read_4(sc->bst, sc->bsh, offset));
+}
+
+static inline void
+intel_pmc_write(struct intel_pmc_softc *sc, uint32_t offset, uint32_t val)
+{
+
+	bus_space_write_4(sc->bst, sc->bsh, offset, val);
+}
+
+static int
+intel_pmc_slp_s0_sysctl(SYSCTL_HANDLER_ARGS)
+{
+	struct intel_pmc_softc *sc = oidp->oid_arg1;
+	uint64_t val, usec;
+
+	val = intel_pmc_read(sc, SPT_PMC_SLP_S0_RES_OFFSET);
+	usec = val * SPT_PMC_SLP_S0_RES_STEP;
+	return (sysctl_handle_64(oidp, &usec, 0, req));
+}
+
+static int
+intel_pmc_ltr_ignore_sysctl(SYSCTL_HANDLER_ARGS)
+{
+	struct intel_pmc_softc *sc = oidp->oid_arg1;
+	uint32_t val;
+	int error;
+
+	val = intel_pmc_read(sc, SPT_PMC_LTR_IGNORE_OFFSET);
+	error = sysctl_handle_32(oidp, &val, 0, req);
+	if (error != 0 || req->newptr == NULL)
+		return (error);
+	/* Only bits 0-16 are valid IP blocks */
+	val &= (1u << SPT_PMC_LTR_IGNORE_BITS) - 1;
+	intel_pmc_write(sc, SPT_PMC_LTR_IGNORE_OFFSET, val);
+	return (0);
+}
+
+static int
+intel_pmc_probe(device_t dev)
+{
+	const struct pci_device_table *tbl;
+
+	tbl = PCI_MATCH(dev, intel_pmc_devices);
+	if (tbl == NULL)
+		return (ENXIO);
+	device_set_desc(dev, tbl->descr);
+	return (BUS_PROBE_DEFAULT);
+}
+
+static int
+intel_pmc_attach(device_t dev)
+{
+	struct intel_pmc_softc *sc = device_get_softc(dev);
+	struct sysctl_ctx_list *ctx;
+	struct sysctl_oid *tree;
+	vm_paddr_t pwrm_base;
+
+	if ((pci_read_config(dev, SPT_PMC_ACTL_OFFSET, 4) &
+	    SPT_PMC_ACTL_PWRM_EN) == 0) {
+		device_printf(dev, "PWRM decode not enabled by firmware\n");
+		return (ENXIO);
+	}
+	pwrm_base = pci_read_config(dev, SPT_PMC_PWRM_BASE_OFFSET, 4) &
+	    SPT_PMC_PWRM_BASE_MASK;
+
+	sc->bst = X86_BUS_SPACE_MEM;
+	sc->bsh = (bus_space_handle_t)pmap_mapdev(pwrm_base,
+	    SPT_PMC_MMIO_SIZE);
+
+	ctx = device_get_sysctl_ctx(dev);
+	tree = device_get_sysctl_tree(dev);
+
+	SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
+	    "ltr_ignore",
+	    CTLTYPE_U32 | CTLFLAG_RW | CTLFLAG_MPSAFE,
+	    sc, 0, intel_pmc_ltr_ignore_sysctl, "IU",
+	    "LTR ignore mask: set bit = ignore IP, allowing S0ix");
+
+	SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
+	    "slp_s0_residency",
+	    CTLTYPE_U64 | CTLFLAG_RD | CTLFLAG_MPSAFE,
+	    sc, 0, intel_pmc_slp_s0_sysctl, "QU",
+	    "SLP_S0 residency time in microseconds");
+
+	return (0);
+}
+
+static int
+intel_pmc_detach(device_t dev)
+{
+	struct intel_pmc_softc *sc = device_get_softc(dev);
+
+	pmap_unmapdev((void *)sc->bsh, SPT_PMC_MMIO_SIZE);
+	return (0);
+}
+
+static device_method_t intel_pmc_methods[] = {
+	DEVMETHOD(device_probe,		intel_pmc_probe),
+	DEVMETHOD(device_attach,	intel_pmc_attach),
+	DEVMETHOD(device_detach,	intel_pmc_detach),
+	DEVMETHOD_END
+};
+
+static driver_t intel_pmc_driver = {
+	"intel_pmc",
+	intel_pmc_methods,
+	sizeof(struct intel_pmc_softc)
+};
+
+DRIVER_MODULE(intel_pmc, pci, intel_pmc_driver, 0, 0);
+PCI_PNP_INFO(intel_pmc_devices);
+MODULE_VERSION(intel_pmc, 1);
+MODULE_DEPEND(intel_pmc, pci, 1, 1, 1);
diff --git a/sys/modules/Makefile b/sys/modules/Makefile
index 2c3553aea2f3..0b196b72c079 100644
--- a/sys/modules/Makefile
+++ b/sys/modules/Makefile
@@ -178,6 +178,7 @@ SUBDIR=	\
 	${_igc} \
 	imgact_binmisc \
 	${_imx} \
+	${_intel_pmc} \
 	${_intelspi} \
 	${_io} \
 	${_ioat} \
@@ -831,6 +832,7 @@ _hptrr=		hptrr
 _hyperv=	hyperv
 _ichwd=		ichwd
 _ida=		ida
+_intel_pmc=	intel_pmc
 _intelspi=	intelspi
 _ips=		ips
 _isci=		isci
diff --git a/sys/modules/intel_pmc/Makefile b/sys/modules/intel_pmc/Makefile
new file mode 100644
index 000000000000..5f68a19f4b3e
--- /dev/null
+++ b/sys/modules/intel_pmc/Makefile
@@ -0,0 +1,6 @@
+.PATH: ${SRCTOP}/sys/dev/intel
+KMOD=	intel_pmc
+SRCS=	intel_pmc.c
+SRCS+=	device_if.h bus_if.h pci_if.h
+
+.include <bsd.kmod.mk>