1 /*
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2026 Abdelkader Boudih <seuros@FreeBSD.org>
5 */
6
7 /*
8 * intel_pmc -- Intel Performance Management Controller (PMC) driver
9 *
10 * Exposes information and controls from the PMC of Intel platforms.
11 * Only the Sunrise Point (SPT) platform, paired with Skylake
12 * generation processors, is currently supported; everything below
13 * describes that hardware only.
14 *
15 * The driver reports the cumulative time spent in low power mode
16 * (S0ix, Modern Standby) while in S0, and lets the administrator
17 * force the PMC to ignore IP blocks whose Latency Tolerance
18 * Reporting (LTR) prevents the platform from entering S0ix.
19 *
20 * The PMC register space (PWRM) is mapped via the PWRM_BASE register
21 * at PCI config offset 0x48, not through BAR0. See Intel 100 Series
22 * Chipset Family PCH Datasheet Vol 2, section 4.3.53.
23 *
24 * The SLP_S0_RESIDENCY counter increments (100 us per step) while the
25 * PCH SLP_S0 signal is asserted. Per the datasheet, SLP_S0 asserts
26 * when the PCH is idle and the processor is in C10 state; in general
27 * this also requires ModPhy lanes power-gated and PLLs idle (Linux
28 * commit b740d2e9233cb). A counter that never advances indicates
29 * S0ix transitions are not occurring. The counter is 32-bit and
30 * wraps after approximately 5 days of cumulative S0ix residency.
31 *
32 * LTR_IGNORE (PWRM+0x30C) has one bit per IP block. Setting a bit
33 * tells the PMC to ignore that IP's LTR requirements, allowing S0ix
34 * entry even if the IP would otherwise block it. A clear bit means
35 * the IP's LTR requirements are considered and can prevent S0ix.
36 *
37 * SPT bit assignments (0-16):
38 * 0=SOUTHPORT_A 1=SOUTHPORT_B 2=SATA
39 * 3=GIGABIT_ETHERNET 4=XHCI 5=reserved
40 * 6=ME 7=EVA 8=SOUTHPORT_C
41 * 9=HD_AUDIO 10=LPSS 11=SOUTHPORT_D
42 * 12=SOUTHPORT_E 13=CAMERA 14=ESPI
43 * 15=SCC 16=ISH
44 *
45 * Ref: Intel 100 Series Chipset Family PCH Datasheet Vol 2, section 4.3
46 * Ref: Linux commit 9c2ee19987ef (initial SPT PMC support)
47 * Ref: Linux commit 2eb150558bb7 (LTR IP block documentation)
48 */
49
50 #include <sys/param.h>
51 #include <sys/bus.h>
52 #include <sys/errno.h>
53 #include <sys/kernel.h>
54 #include <sys/module.h>
55 #include <sys/rman.h>
56 #include <sys/sysctl.h>
57 #include <sys/systm.h>
58
59 #include <machine/bus.h>
60 #include <machine/resource.h>
61
62 #include <vm/vm.h>
63 #include <vm/pmap.h>
64 #include <machine/pmap.h>
65
66 #include <dev/pci/pcireg.h>
67 #include <dev/pci/pcivar.h>
68
69 /*
70 * Sunrise Point (SPT) PMC Register Definitions
71 * Reference: Intel 100 Series Chipset Family PCH Datasheet Vol 2.
72 * LTR_IGNORE is not documented there; the offset comes from Linux
73 * commit 9c2ee19987ef (initial SPT PMC support).
74 */
75 #define SPT_PMC_ACTL_OFFSET 0x44 /* ACPI Control */
76 #define SPT_PMC_ACTL_PWRM_EN (1u << 8) /* PWRM decode enabled */
77 #define SPT_PMC_PWRM_BASE_OFFSET 0x48 /* PCI config offset for PWRM */
78 #define SPT_PMC_PWRM_BASE_MASK 0xfffff000
79 #define SPT_PMC_MMIO_SIZE 0x1000
80
81 #define SPT_PMC_SLP_S0_RES_OFFSET 0x13C
82 #define SPT_PMC_SLP_S0_RES_STEP 100 /* 100us per datasheet */
83 #define SPT_PMC_LTR_IGNORE_OFFSET 0x30C
84 #define SPT_PMC_LTR_IGNORE_BITS 17 /* bits 0-16 are valid IP blocks */
85
86 struct intel_pmc_softc {
87 bus_space_tag_t bst;
88 bus_space_handle_t bsh;
89 };
90
91 /*
92 * Only Sunrise Point - other generations have different layouts.
93 * 0xa121 is documented in the Intel 100 Series Chipset Family PCH
94 * Datasheet Vol 2. Sunrise Point-LP has no public datasheet; 0x9d21
95 * comes from the pci_vendors database, where Linux also sourced it.
96 */
97 static const struct pci_device_table intel_pmc_devices[] = {
98 { PCI_DEV(0x8086, 0x9d21), PCI_DESCR("Sunrise Point-LP PMC")},
99 { PCI_DEV(0x8086, 0xa121), PCI_DESCR("Sunrise Point-H PMC")},
100 };
101
102 static inline uint32_t
intel_pmc_read(struct intel_pmc_softc * sc,uint32_t offset)103 intel_pmc_read(struct intel_pmc_softc *sc, uint32_t offset)
104 {
105
106 return (bus_space_read_4(sc->bst, sc->bsh, offset));
107 }
108
109 static inline void
intel_pmc_write(struct intel_pmc_softc * sc,uint32_t offset,uint32_t val)110 intel_pmc_write(struct intel_pmc_softc *sc, uint32_t offset, uint32_t val)
111 {
112
113 bus_space_write_4(sc->bst, sc->bsh, offset, val);
114 }
115
116 static int
intel_pmc_slp_s0_sysctl(SYSCTL_HANDLER_ARGS)117 intel_pmc_slp_s0_sysctl(SYSCTL_HANDLER_ARGS)
118 {
119 struct intel_pmc_softc *sc = oidp->oid_arg1;
120 uint64_t val, usec;
121
122 val = intel_pmc_read(sc, SPT_PMC_SLP_S0_RES_OFFSET);
123 usec = val * SPT_PMC_SLP_S0_RES_STEP;
124 return (sysctl_handle_64(oidp, &usec, 0, req));
125 }
126
127 static int
intel_pmc_ltr_ignore_sysctl(SYSCTL_HANDLER_ARGS)128 intel_pmc_ltr_ignore_sysctl(SYSCTL_HANDLER_ARGS)
129 {
130 struct intel_pmc_softc *sc = oidp->oid_arg1;
131 uint32_t val;
132 int error;
133
134 val = intel_pmc_read(sc, SPT_PMC_LTR_IGNORE_OFFSET);
135 error = sysctl_handle_32(oidp, &val, 0, req);
136 if (error != 0 || req->newptr == NULL)
137 return (error);
138 /* Only bits 0-16 are valid IP blocks */
139 val &= (1u << SPT_PMC_LTR_IGNORE_BITS) - 1;
140 intel_pmc_write(sc, SPT_PMC_LTR_IGNORE_OFFSET, val);
141 return (0);
142 }
143
144 static int
intel_pmc_probe(device_t dev)145 intel_pmc_probe(device_t dev)
146 {
147 const struct pci_device_table *tbl;
148
149 tbl = PCI_MATCH(dev, intel_pmc_devices);
150 if (tbl == NULL)
151 return (ENXIO);
152 device_set_desc(dev, tbl->descr);
153 return (BUS_PROBE_DEFAULT);
154 }
155
156 static int
intel_pmc_attach(device_t dev)157 intel_pmc_attach(device_t dev)
158 {
159 struct intel_pmc_softc *sc = device_get_softc(dev);
160 struct sysctl_ctx_list *ctx;
161 struct sysctl_oid *tree;
162 vm_paddr_t pwrm_base;
163
164 if ((pci_read_config(dev, SPT_PMC_ACTL_OFFSET, 4) &
165 SPT_PMC_ACTL_PWRM_EN) == 0) {
166 device_printf(dev, "PWRM decode not enabled by firmware\n");
167 return (ENXIO);
168 }
169 pwrm_base = pci_read_config(dev, SPT_PMC_PWRM_BASE_OFFSET, 4) &
170 SPT_PMC_PWRM_BASE_MASK;
171
172 sc->bst = X86_BUS_SPACE_MEM;
173 sc->bsh = (bus_space_handle_t)pmap_mapdev(pwrm_base,
174 SPT_PMC_MMIO_SIZE);
175
176 ctx = device_get_sysctl_ctx(dev);
177 tree = device_get_sysctl_tree(dev);
178
179 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
180 "ltr_ignore",
181 CTLTYPE_U32 | CTLFLAG_RW | CTLFLAG_MPSAFE,
182 sc, 0, intel_pmc_ltr_ignore_sysctl, "IU",
183 "LTR ignore mask: set bit = ignore IP, allowing S0ix");
184
185 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
186 "slp_s0_residency",
187 CTLTYPE_U64 | CTLFLAG_RD | CTLFLAG_MPSAFE,
188 sc, 0, intel_pmc_slp_s0_sysctl, "QU",
189 "SLP_S0 residency time in microseconds");
190
191 return (0);
192 }
193
194 static int
intel_pmc_detach(device_t dev)195 intel_pmc_detach(device_t dev)
196 {
197 struct intel_pmc_softc *sc = device_get_softc(dev);
198
199 pmap_unmapdev((void *)sc->bsh, SPT_PMC_MMIO_SIZE);
200 return (0);
201 }
202
203 static device_method_t intel_pmc_methods[] = {
204 DEVMETHOD(device_probe, intel_pmc_probe),
205 DEVMETHOD(device_attach, intel_pmc_attach),
206 DEVMETHOD(device_detach, intel_pmc_detach),
207 DEVMETHOD_END
208 };
209
210 static driver_t intel_pmc_driver = {
211 "intel_pmc",
212 intel_pmc_methods,
213 sizeof(struct intel_pmc_softc)
214 };
215
216 DRIVER_MODULE(intel_pmc, pci, intel_pmc_driver, 0, 0);
217 PCI_PNP_INFO(intel_pmc_devices);
218 MODULE_VERSION(intel_pmc, 1);
219 MODULE_DEPEND(intel_pmc, pci, 1, 1, 1);
220