diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile --- a/share/man/man4/Makefile +++ b/share/man/man4/Makefile @@ -252,6 +252,7 @@ ${_imcsmb.4} \ inet.4 \ inet6.4 \ + intel_pmc_core.4 \ intpm.4 \ intro.4 \ ${_io.4} \ diff --git a/share/man/man4/intel_pmc_core.4 b/share/man/man4/intel_pmc_core.4 new file mode 100644 --- /dev/null +++ b/share/man/man4/intel_pmc_core.4 @@ -0,0 +1,88 @@ +.\"- +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.\" Copyright (c) 2026 Abdelkader Boudih +.\" +.Dd February 23, 2026 +.Dt INTEL_PMC_CORE 4 +.Os +.Sh NAME +.Nm intel_pmc_core +.Nd Intel Power Management Controller Core driver +.Sh SYNOPSIS +To compile this driver into the kernel, +place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device intel_pmc_core" +.Ed +.Pp +Alternatively, to load the driver as a +module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +intel_pmc_core_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +driver exposes sleep state residency counters and power gating status +for Intel Sunrise Point PCH (Platform Controller Hub) via +.Xr sysctl 8 . +.Pp +The driver supports debugging S0ix (Modern Standby) power states by +providing access to the cumulative time the system has spent in any +S0ix sub-state (S0i1, S0i2, S0i3). +.Pp +If firmware has locked access to the PMC registers, the driver detects +this condition at attach time and reports it via the +.Va access_denied +sysctl. +All register reads return zero when access is denied. +.Sh HARDWARE +The +.Nm +driver supports the following devices: +.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_core.%d.access_denied +Set to 1 if firmware has denied access to PMC registers. +.It Va dev.intel_pmc_core.%d.slp_s0_residency +Cumulative time spent in any S0ix sub-state, in microseconds. +The counter increments in 100\ us steps per the Intel 100 Series +Chipset Family PCH Datasheet. +.It Va dev.intel_pmc_core.%d.ltr_ignore +Latency Tolerance Reporting (LTR) ignore mask. +.It Va dev.intel_pmc_core.%d.pm_cfg +Power Management configuration register (PM_CFG). +.It Va dev.intel_pmc_core.%d.pm_sts +Power Management status register (PM_STS). +.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.53. +.Sh HISTORY +The +.Nm +driver first appeared in +.Fx 16.0 . +.Sh AUTHORS +.An Abdelkader Boudih Aq Mt freebsd@seuros.com +.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 --- a/sys/conf/files.x86 +++ b/sys/conf/files.x86 @@ -149,6 +149,7 @@ dev/ichwd/ichwd.c optional ichwd dev/imcsmb/imcsmb.c optional imcsmb dev/imcsmb/imcsmb_pci.c optional imcsmb pci +dev/intel/intel_pmc_core.c optional intel_pmc_core 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_core.c b/sys/dev/intel/intel_pmc_core.c new file mode 100644 --- /dev/null +++ b/sys/dev/intel/intel_pmc_core.c @@ -0,0 +1,224 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2026 Abdelkader Boudih + */ + +/* + * Intel PMC (Power Management Controller) Core driver + * + * Exposes sleep state residency counters and power gating status + * for debugging S0ix (Modern Standby) power states. + * + * Register offsets from Intel 100 Series Chipset Family PCH Datasheet Vol 2 + * Section 4.3.53 describes the SLP_S0_RESIDENCY_COUNTER (offset 0x13C). + * + * The residency counter tracks cumulative time spent in any S0ix sub-state + * (S0i1, S0i2, S0i3), not specifically the deepest sleep state. + * + * Counter increment: The datasheet specifies 100us granularity. Linux uses + * 104us (0x68), probably a hardware quirk on untested vendors/revisions. + * + * NOTE: This driver only supports Sunrise Point (different PCH generations + * have different register layouts). Some firmware/BIOS implementations + * lock PMC access - if registers read as 0xFFFFFFFF, access is denied. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +/* + * Sunrise Point (SPT) PMC Register Definitions + * Reference: Intel 100 Series Chipset Family PCH Datasheet Vol 2 + */ +#define SPT_PMC_PM_CFG_OFFSET 0x18 +#define SPT_PMC_PM_STS_OFFSET 0x1C +#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 + +struct intel_pmc_core_softc { + int rid; + struct resource *res; + bus_space_tag_t bst; + bus_space_handle_t bsh; + int access_denied; +}; + +static const struct pci_device_table intel_pmc_core_devices[] = { + /* Only Sunrise Point - other generations have different layouts */ + { PCI_DEV(0x8086, 0x9d21), PCI_DESCR("Sunrise Point-LP PMC")}, + { PCI_DEV(0x8086, 0xa121), PCI_DESCR("Sunrise Point-H PMC")}, +}; + +static inline uint32_t +pmc_read(struct intel_pmc_core_softc *sc, uint32_t offset) +{ + return (bus_space_read_4(sc->bst, sc->bsh, offset)); +} + +static inline uint32_t +pmc_read_safe(struct intel_pmc_core_softc *sc, uint32_t offset) +{ + if (sc->access_denied) + return (0); + return (pmc_read(sc, offset)); +} + +static int +pmc_core_slp_s0_sysctl(SYSCTL_HANDLER_ARGS) +{ + struct intel_pmc_core_softc *sc = oidp->oid_arg1; + uint64_t val, usec; + + val = pmc_read_safe(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 +pmc_core_ltr_ignore_sysctl(SYSCTL_HANDLER_ARGS) +{ + struct intel_pmc_core_softc *sc = oidp->oid_arg1; + uint32_t val; + + val = pmc_read_safe(sc, SPT_PMC_LTR_IGNORE_OFFSET); + return (sysctl_handle_32(oidp, &val, 0, req)); +} + +static int +pmc_core_pm_cfg_sysctl(SYSCTL_HANDLER_ARGS) +{ + struct intel_pmc_core_softc *sc = oidp->oid_arg1; + uint32_t val; + + val = pmc_read_safe(sc, SPT_PMC_PM_CFG_OFFSET); + return (sysctl_handle_32(oidp, &val, 0, req)); +} + +static int +pmc_core_pm_sts_sysctl(SYSCTL_HANDLER_ARGS) +{ + struct intel_pmc_core_softc *sc = oidp->oid_arg1; + uint32_t val; + + val = pmc_read_safe(sc, SPT_PMC_PM_STS_OFFSET); + return (sysctl_handle_32(oidp, &val, 0, req)); +} + +static int +intel_pmc_core_probe(device_t dev) +{ + const struct pci_device_table *tbl; + + tbl = PCI_MATCH(dev, intel_pmc_core_devices); + if (tbl == NULL) + return (ENXIO); + device_set_desc(dev, tbl->descr); + return (BUS_PROBE_DEFAULT); +} + +static int +intel_pmc_core_attach(device_t dev) +{ + struct intel_pmc_core_softc *sc = device_get_softc(dev); + struct sysctl_ctx_list *ctx; + struct sysctl_oid *tree; + uint32_t pm_cfg, pm_sts, ltr_ign, slp_s0; + + sc->rid = PCIR_BAR(0); + sc->res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, + &sc->rid, RF_ACTIVE); + if (sc->res == NULL) { + device_printf(dev, "cannot allocate PMC BAR0\n"); + return (ENOMEM); + } + + sc->bst = rman_get_bustag(sc->res); + sc->bsh = rman_get_bushandle(sc->res); + + /* + * Check multiple registers to detect firmware lock. + * All reading 0xFFFFFFFF indicates access denied. + */ + pm_cfg = pmc_read(sc, SPT_PMC_PM_CFG_OFFSET); + pm_sts = pmc_read(sc, SPT_PMC_PM_STS_OFFSET); + ltr_ign = pmc_read(sc, SPT_PMC_LTR_IGNORE_OFFSET); + slp_s0 = pmc_read(sc, SPT_PMC_SLP_S0_RES_OFFSET); + + if (pm_cfg == 0xFFFFFFFF && pm_sts == 0xFFFFFFFF && + ltr_ign == 0xFFFFFFFF && slp_s0 == 0xFFFFFFFF) { + sc->access_denied = 1; + device_printf(dev, + "PMC access denied by firmware (S0ix not supported)\n"); + } else + sc->access_denied = 0; + + ctx = device_get_sysctl_ctx(dev); + tree = device_get_sysctl_tree(dev); + + SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, + "access_denied", CTLFLAG_RD | CTLFLAG_MPSAFE, &sc->access_denied, 0, + "Firmware denied access to PMC registers"); + + SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, + "slp_s0_residency", CTLTYPE_U64 | CTLFLAG_RD | CTLFLAG_MPSAFE, + sc, 0, pmc_core_slp_s0_sysctl, "QU", + "Cumulative time in any S0ix sub-state (microseconds)"); + + SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, + "ltr_ignore", CTLTYPE_U32 | CTLFLAG_RD | CTLFLAG_MPSAFE, + sc, 0, pmc_core_ltr_ignore_sysctl, "IU", + "LTR ignore mask"); + + SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, + "pm_cfg", CTLTYPE_U32 | CTLFLAG_RD | CTLFLAG_MPSAFE, + sc, 0, pmc_core_pm_cfg_sysctl, "IU", + "Power Management configuration"); + + SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, + "pm_sts", CTLTYPE_U32 | CTLFLAG_RD | CTLFLAG_MPSAFE, + sc, 0, pmc_core_pm_sts_sysctl, "IU", + "Power Management status"); + + return (0); +} + +static int +intel_pmc_core_detach(device_t dev) +{ + struct intel_pmc_core_softc *sc = device_get_softc(dev); + + bus_release_resource(dev, SYS_RES_MEMORY, sc->rid, sc->res); + return (0); +} + +static device_method_t intel_pmc_core_methods[] = { + DEVMETHOD(device_probe, intel_pmc_core_probe), + DEVMETHOD(device_attach, intel_pmc_core_attach), + DEVMETHOD(device_detach, intel_pmc_core_detach), + DEVMETHOD_END +}; + +static driver_t intel_pmc_core_driver = { + "intel_pmc_core", + intel_pmc_core_methods, + sizeof(struct intel_pmc_core_softc) +}; + +DRIVER_MODULE(intel_pmc_core, pci, intel_pmc_core_driver, 0, 0); +PCI_PNP_INFO(intel_pmc_core_devices); +MODULE_VERSION(intel_pmc_core, 1); +MODULE_DEPEND(intel_pmc_core, pci, 1, 1, 1); diff --git a/sys/modules/Makefile b/sys/modules/Makefile --- a/sys/modules/Makefile +++ b/sys/modules/Makefile @@ -174,6 +174,7 @@ ${_igc} \ imgact_binmisc \ ${_imx} \ + ${_intel_pmc_core} \ ${_intelspi} \ ${_io} \ ${_ioat} \ @@ -814,6 +815,7 @@ _hyperv= hyperv _ichwd= ichwd _ida= ida +_intel_pmc_core= intel_pmc_core _intelspi= intelspi _ips= ips _isci= isci diff --git a/sys/modules/intel_pmc_core/Makefile b/sys/modules/intel_pmc_core/Makefile new file mode 100644 --- /dev/null +++ b/sys/modules/intel_pmc_core/Makefile @@ -0,0 +1,6 @@ +.PATH: ${SRCTOP}/sys/dev/intel +KMOD= intel_pmc_core +SRCS= intel_pmc_core.c +SRCS+= device_if.h bus_if.h pci_if.h + +.include