xref: /freebsd/sys/dev/intel/intel_pmc.c (revision dd9e49a10bc14f7d876426d7d85fed0d1fe9699d)
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