1 /*
2 * Copyright (c) 2026 Abdelkader Boudih <seuros@FreeBSD.org>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7 /*
8 * hfstsfd(4) - detects HECI hidden via the PCH Function Disable register.
9 *
10 * The MEI PCI function never enumerates when firmware (coreboot's
11 * PCH_DISABLE_MEI1/2) has disabled it, so there is no PCI device instance
12 * for hfsts_attach() to run against. This is an isa-attached identify
13 * driver instead, modeled on ichwd_identify() in sys/dev/ichwd/ichwd.c,
14 * which faces the same problem for the ICH watchdog.
15 *
16 * RCBA (LPC bridge config offset 0xF0) and FD2 (RCBA+0x3428, bit 1 =
17 * MEI1 disabled, bit 2 = MEI2 disabled) use this layout on Ibex Peak,
18 * Cougar Point, Panther Point, Lynx Point, and Wildcat Point. identify()
19 * checks the LPC bridge's device ID against hfsts_fd_lpc_ids[] and only adds
20 * a child for those generations, instead of assuming that every PCH's
21 * disable mechanism or ME capability is known.
22 */
23
24 #include <sys/param.h>
25 #include <sys/bus.h>
26 #include <sys/kernel.h>
27 #include <sys/module.h>
28 #include <sys/sysctl.h>
29
30 #include <vm/vm.h>
31 #include <vm/pmap.h>
32
33 #include <machine/bus.h>
34 #include <machine/pmap.h>
35
36 #include <dev/pci/pcivar.h>
37 #include <dev/ichwd/ichwd.h>
38 #include <dev/intel/hfsts.h>
39
40 #define HFSTS_FD_RCBA_OFFSET 0xf0
41 #define HFSTS_FD_RCBA_EN 0x00000001
42 #define HFSTS_FD_RCBA_MASK 0xffffc000
43 #define HFSTS_FD_FD2_OFFSET 0x3428
44 #define HFSTS_FD_FD2_MEI1_DIS 0x00000002
45 #define HFSTS_FD_FD2_MEI2_DIS 0x00000004
46 /* Page containing FD2, and FD2's offset within that mapped page. */
47 #define HFSTS_FD_FD2_PAGE (HFSTS_FD_FD2_OFFSET & ~(vm_paddr_t)PAGE_MASK)
48 #define HFSTS_FD_FD2_PAGE_OFF (HFSTS_FD_FD2_OFFSET & PAGE_MASK)
49
50 static const uint16_t hfsts_fd_lpc_ids[] = {
51 DEVICEID_LPT0, DEVICEID_LPT1, DEVICEID_LPT2, DEVICEID_LPT3,
52 DEVICEID_LPT4, DEVICEID_LPT5, DEVICEID_LPT6, DEVICEID_LPT7,
53 DEVICEID_LPT8, DEVICEID_LPT9, DEVICEID_LPT10, DEVICEID_LPT11,
54 DEVICEID_LPT12, DEVICEID_LPT13, DEVICEID_LPT14, DEVICEID_LPT15,
55 DEVICEID_LPT16, DEVICEID_LPT17, DEVICEID_LPT18, DEVICEID_LPT19,
56 DEVICEID_LPT20, DEVICEID_LPT21, DEVICEID_LPT22, DEVICEID_LPT23,
57 DEVICEID_LPT24, DEVICEID_LPT25, DEVICEID_LPT26, DEVICEID_LPT27,
58 DEVICEID_LPT28, DEVICEID_LPT29, DEVICEID_LPT30, DEVICEID_LPT31,
59 DEVICEID_LPT_LP0, DEVICEID_LPT_LP1, DEVICEID_LPT_LP2,
60 DEVICEID_LPT_LP3, DEVICEID_LPT_LP4, DEVICEID_LPT_LP5,
61 DEVICEID_LPT_LP6, DEVICEID_LPT_LP7,
62 DEVICEID_WCPT1, DEVICEID_WCPT2, DEVICEID_WCPT3, DEVICEID_WCPT4,
63 DEVICEID_WCPT6,
64 DEVICEID_WCPT_LP1, DEVICEID_WCPT_LP2, DEVICEID_WCPT_LP3,
65 DEVICEID_WCPT_LP5, DEVICEID_WCPT_LP6, DEVICEID_WCPT_LP7,
66 DEVICEID_WCPT_LP9,
67 DEVICEID_CPT0, DEVICEID_CPT1, DEVICEID_CPT2, DEVICEID_CPT3,
68 DEVICEID_CPT4, DEVICEID_CPT5, DEVICEID_CPT6, DEVICEID_CPT7,
69 DEVICEID_CPT8, DEVICEID_CPT9, DEVICEID_CPT10, DEVICEID_CPT11,
70 DEVICEID_CPT12, DEVICEID_CPT13, DEVICEID_CPT14, DEVICEID_CPT15,
71 DEVICEID_CPT16, DEVICEID_CPT17, DEVICEID_CPT18, DEVICEID_CPT19,
72 DEVICEID_CPT20, DEVICEID_CPT21, DEVICEID_CPT22, DEVICEID_CPT23,
73 DEVICEID_CPT24, DEVICEID_CPT25, DEVICEID_CPT26, DEVICEID_CPT27,
74 DEVICEID_CPT28, DEVICEID_CPT29, DEVICEID_CPT30, DEVICEID_CPT31,
75 DEVICEID_PPT0, DEVICEID_PPT1, DEVICEID_PPT2, DEVICEID_PPT3,
76 DEVICEID_PPT4, DEVICEID_PPT5, DEVICEID_PPT6, DEVICEID_PPT7,
77 DEVICEID_PPT8, DEVICEID_PPT9, DEVICEID_PPT10, DEVICEID_PPT11,
78 DEVICEID_PPT12, DEVICEID_PPT13, DEVICEID_PPT14, DEVICEID_PPT15,
79 DEVICEID_PPT16, DEVICEID_PPT17, DEVICEID_PPT18, DEVICEID_PPT19,
80 DEVICEID_PPT20, DEVICEID_PPT21, DEVICEID_PPT22, DEVICEID_PPT23,
81 DEVICEID_PPT24, DEVICEID_PPT25, DEVICEID_PPT26, DEVICEID_PPT27,
82 DEVICEID_PPT28, DEVICEID_PPT29, DEVICEID_PPT30, DEVICEID_PPT31,
83 DEVICEID_PCH, DEVICEID_PCHM, DEVICEID_P55, DEVICEID_PM55,
84 DEVICEID_H55, DEVICEID_QM57, DEVICEID_H57, DEVICEID_HM55,
85 DEVICEID_Q57, DEVICEID_HM57, DEVICEID_PCHMSFF, DEVICEID_QS57,
86 };
87
88 struct hfsts_fd_softc {
89 device_t sc_dev;
90 char sc_status[24];
91 uint32_t sc_fd2;
92 uint32_t sc_mei1_disabled;
93 uint32_t sc_mei2_disabled;
94 bool sc_checked;
95 };
96
97 static bool
hfsts_fd_lpc_known(uint16_t devid)98 hfsts_fd_lpc_known(uint16_t devid)
99 {
100 u_int i;
101
102 for (i = 0; i < nitems(hfsts_fd_lpc_ids); i++) {
103 if (hfsts_fd_lpc_ids[i] == devid)
104 return (true);
105 }
106 return (false);
107 }
108
109 static void
hfsts_fd_identify(driver_t * driver,device_t parent)110 hfsts_fd_identify(driver_t *driver, device_t parent)
111 {
112 device_t isab;
113
114 if (device_find_child(parent, driver->name, 0) != NULL)
115 return;
116
117 isab = device_get_parent(parent);
118 if (!is_pci_device(isab) || pci_get_vendor(isab) != VENDORID_INTEL ||
119 !hfsts_fd_lpc_known(pci_get_device(isab)))
120 return;
121
122 /* MEI enumerated on PCI, so it is not firmware-hidden. */
123 if (hfsts_pci_present())
124 return;
125
126 BUS_ADD_CHILD(parent, 0, driver->name, 0);
127 }
128
129 static int
hfsts_fd_probe(device_t dev)130 hfsts_fd_probe(device_t dev)
131 {
132
133 device_set_desc(dev, "Intel ME Function Disable check");
134 return (BUS_PROBE_NOWILDCARD);
135 }
136
137 static void
hfsts_fd_set_status(struct hfsts_fd_softc * sc,const char * status)138 hfsts_fd_set_status(struct hfsts_fd_softc *sc, const char *status)
139 {
140
141 strlcpy(sc->sc_status, status, sizeof(sc->sc_status));
142 }
143
144 static int
hfsts_fd_attach(device_t dev)145 hfsts_fd_attach(device_t dev)
146 {
147 struct hfsts_fd_softc *sc;
148 struct sysctl_ctx_list *ctx;
149 struct sysctl_oid *tree;
150 device_t isab;
151 vm_paddr_t rcba;
152 bus_space_handle_t bsh;
153 uint32_t fd2;
154
155 sc = device_get_softc(dev);
156 sc->sc_dev = dev;
157
158 isab = device_get_parent(device_get_parent(dev));
159
160 rcba = pci_read_config(isab, HFSTS_FD_RCBA_OFFSET, 4);
161 if ((rcba & HFSTS_FD_RCBA_EN) == 0 || (rcba & HFSTS_FD_RCBA_MASK) == 0) {
162 device_printf(dev,
163 "RCBA not enabled, cannot read Function Disable register\n");
164 hfsts_fd_set_status(sc, "rcba-disabled");
165 goto done;
166 }
167
168 bsh = (bus_space_handle_t)pmap_mapdev(
169 (rcba & HFSTS_FD_RCBA_MASK) + HFSTS_FD_FD2_PAGE, PAGE_SIZE);
170 fd2 = bus_space_read_4(X86_BUS_SPACE_MEM, bsh, HFSTS_FD_FD2_PAGE_OFF);
171 pmap_unmapdev((void *)bsh, PAGE_SIZE);
172
173 sc->sc_fd2 = fd2;
174 sc->sc_mei1_disabled = (fd2 & HFSTS_FD_FD2_MEI1_DIS) != 0;
175 sc->sc_mei2_disabled = (fd2 & HFSTS_FD_FD2_MEI2_DIS) != 0;
176 sc->sc_checked = true;
177 hfsts_fd_set_status(sc, "checked");
178
179 if (bootverbose) {
180 if (sc->sc_mei1_disabled) {
181 device_printf(dev, "HECI1 disabled via PCH Function "
182 "Disable register (silicon present, hidden by "
183 "firmware)\n");
184 } else {
185 device_printf(dev, "HECI1 not disabled - MEI should "
186 "be enumerable as a normal PCI function\n");
187 }
188 if (sc->sc_mei2_disabled)
189 device_printf(dev, "HECI2 disabled via PCH Function "
190 "Disable register\n");
191 }
192
193 done:
194 ctx = device_get_sysctl_ctx(dev);
195 tree = device_get_sysctl_tree(dev);
196
197 SYSCTL_ADD_STRING(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "status",
198 CTLFLAG_RD, sc->sc_status, 0,
199 "Function Disable check result");
200 if (!sc->sc_checked)
201 return (0);
202 SYSCTL_ADD_U32(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "fd2",
203 CTLFLAG_RD, &sc->sc_fd2, 0,
204 "Function Disable 2 register (raw)");
205 SYSCTL_ADD_U32(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "mei1_disabled",
206 CTLFLAG_RD, &sc->sc_mei1_disabled, 0,
207 "HECI1 disabled via Function Disable register");
208 SYSCTL_ADD_U32(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "mei2_disabled",
209 CTLFLAG_RD, &sc->sc_mei2_disabled, 0,
210 "HECI2 disabled via Function Disable register");
211
212 return (0);
213 }
214
215 static int
hfsts_fd_detach(device_t dev)216 hfsts_fd_detach(device_t dev)
217 {
218
219 return (0);
220 }
221
222 static device_method_t hfsts_fd_methods[] = {
223 DEVMETHOD(device_identify, hfsts_fd_identify),
224 DEVMETHOD(device_probe, hfsts_fd_probe),
225 DEVMETHOD(device_attach, hfsts_fd_attach),
226 DEVMETHOD(device_detach, hfsts_fd_detach),
227 DEVMETHOD_END
228 };
229
230 static driver_t hfsts_fd_driver = {
231 "hfstsfd",
232 hfsts_fd_methods,
233 sizeof(struct hfsts_fd_softc)
234 };
235
236 DRIVER_MODULE(hfstsfd, isa, hfsts_fd_driver, 0, 0);
237 MODULE_VERSION(hfstsfd, 1);
238