xref: /freebsd/sys/dev/intel/hfsts.c (revision a6deeaa2fb3b28a61ae56a85f2275f67fcf23262)
1 /*
2  * Copyright (c) 2026 Abdelkader Boudih <seuros@FreeBSD.org>
3  *
4  * SPDX-License-Identifier: BSD-2-Clause
5  */
6 
7 /*
8  * hfsts(4) - Intel Management Engine Interface (MEI/HECI) debug probe.
9  *
10  * This is deliberately *not* a full MEI host-client driver: it only
11  * attaches to the PCI function and decodes the Host Firmware Status
12  * registers (HFSTS1-6, as supported by the device generation), which live
13  * entirely in PCI configuration space.
14  * BAR0 (the circular-buffer messaging ring) is never mapped.
15  *
16  * Register counts and layout follow Linux drivers/misc/mei/hw-me.c:
17  * ICH10 provides HFS1, PCH5 through PCH7 provide HFS1-2, and PCH8 and
18  * later provide HFS1-6 at PCI config offsets 0x40/0x48/0x60/0x64/0x68/0x6c.
19  */
20 
21 #include <sys/param.h>
22 #include <sys/bus.h>
23 #include <sys/kernel.h>
24 #include <sys/module.h>
25 #include <sys/sysctl.h>
26 
27 #include <dev/pci/pcivar.h>
28 #include <dev/pci/pcireg.h>
29 #include <dev/ichwd/ichwd.h>
30 #include <dev/intel/hfsts.h>
31 
32 /* Host Firmware Status Registers - PCI configuration space offsets. */
33 #define HFSTS_CFG_HFS_1		0x40
34 #define   HFSTS_HFS_1_CWS_MSK		0x0000000f
35 #define   HFSTS_HFS_1_MFG_MODE_MSK	0x00000010
36 #define   HFSTS_HFS_1_FPT_BAD_MSK		0x00000020
37 #define   HFSTS_HFS_1_OPSTATE_MSK	0x000001c0
38 #define   HFSTS_HFS_1_OPSTATE_SHIFT	6
39 #define   HFSTS_HFS_1_FW_INIT_CMPL_MSK	0x00000200
40 #define   HFSTS_HFS_1_BUP_FAIL_MSK	0x00000400
41 #define   HFSTS_HFS_1_UPDATE_INPROG_MSK	0x00000800
42 #define   HFSTS_HFS_1_ERROR_MSK		0x0000f000
43 #define   HFSTS_HFS_1_ERROR_SHIFT	12
44 #define   HFSTS_HFS_1_OPMODE_MSK		0x000f0000
45 #define   HFSTS_HFS_1_OPMODE_SHIFT	16
46 #define   HFSTS_HFS_1_BOOT_OPT_MSK	0x01000000
47 #define   HFSTS_HFS_1_D0I3_MSK		0x80000000
48 #define HFSTS_CFG_HFS_2		0x48
49 #define   HFSTS_HFS_2_PM_EVENT_MSK	0x0f000000
50 #define   HFSTS_HFS_2_PM_EVENT_SHIFT	24
51 #define HFSTS_CFG_HFS_3		0x60
52 #define   HFSTS_HFS_3_FW_SKU_MSK		0x00000070
53 #define   HFSTS_HFS_3_FW_SKU_SHIFT	4
54 #define HFSTS_CFG_HFS_4		0x64
55 #define HFSTS_CFG_HFS_5		0x68
56 #define HFSTS_CFG_HFS_6		0x6c
57 
58 /* HFS1 field values.  Operation state and mode are sparse encodings. */
59 #define HFSTS_HFS_CWS_RESET		0
60 #define HFSTS_HFS_CWS_INIT		1
61 #define HFSTS_HFS_CWS_RECOVERY		2
62 #define HFSTS_HFS_CWS_TEST		3
63 #define HFSTS_HFS_CWS_DISABLED		4
64 #define HFSTS_HFS_CWS_NORMAL		5
65 #define HFSTS_HFS_CWS_WAIT		6
66 #define HFSTS_HFS_CWS_TRANS		7
67 #define HFSTS_HFS_CWS_INVALID		8
68 
69 #define HFSTS_HFS_OPSTATE_PREBOOT		0
70 #define HFSTS_HFS_OPSTATE_M0_UMA		1
71 #define HFSTS_HFS_OPSTATE_M3		4
72 #define HFSTS_HFS_OPSTATE_M0		5
73 #define HFSTS_HFS_OPSTATE_BRINGUP	6
74 #define HFSTS_HFS_OPSTATE_IMAGE_ERROR	7
75 
76 #define HFSTS_HFS_OPMODE_NORMAL		0
77 #define HFSTS_HFS_OPMODE_DEBUG		2
78 #define HFSTS_HFS_OPMODE_TEMP_DISABLED	3
79 #define HFSTS_HFS_OPMODE_JUMPER_OVERRIDE	4
80 #define HFSTS_HFS_OPMODE_MEI_OVERRIDE	5
81 #define HFSTS_HFS_OPMODE_SPS		15
82 
83 #define HFSTS_HFS_ERROR_NONE		0
84 #define HFSTS_HFS_ERROR_UNCATEGORIZED	1
85 #define HFSTS_HFS_ERROR_DISABLED		2
86 #define HFSTS_HFS_ERROR_IMAGE		3
87 #define HFSTS_HFS_ERROR_DEBUG		4
88 
89 static const char *const hfsts_cws_values[] = {
90 	[HFSTS_HFS_CWS_RESET] =		"Reset",
91 	[HFSTS_HFS_CWS_INIT] =		"Initializing",
92 	[HFSTS_HFS_CWS_RECOVERY] =	"Recovery",
93 	[HFSTS_HFS_CWS_TEST] =		"Test",
94 	[HFSTS_HFS_CWS_DISABLED] =	"Disabled",
95 	[HFSTS_HFS_CWS_NORMAL] =		"Normal",
96 	[HFSTS_HFS_CWS_WAIT] =		"Platform Disable Wait",
97 	[HFSTS_HFS_CWS_TRANS] =		"OP State Transition",
98 	[HFSTS_HFS_CWS_INVALID] =	"Invalid CPU Plugged In",
99 };
100 
101 static const char *const hfsts_opstate_values[] = {
102 	[HFSTS_HFS_OPSTATE_PREBOOT] =	"Preboot",
103 	[HFSTS_HFS_OPSTATE_M0_UMA] =	"M0 (UMA)",
104 	[HFSTS_HFS_OPSTATE_M3] =		"M3 (no UMA)",
105 	[HFSTS_HFS_OPSTATE_M0] =		"M0 (no UMA)",
106 	[HFSTS_HFS_OPSTATE_BRINGUP] =	"Bring up",
107 	[HFSTS_HFS_OPSTATE_IMAGE_ERROR] = "M0 (invalid firmware image)",
108 };
109 
110 static const char *const hfsts_opmode_values[] = {
111 	[HFSTS_HFS_OPMODE_NORMAL] =	"Normal",
112 	[HFSTS_HFS_OPMODE_DEBUG] =	"Debug",
113 	[HFSTS_HFS_OPMODE_TEMP_DISABLED] = "Temporarily disabled",
114 	[HFSTS_HFS_OPMODE_JUMPER_OVERRIDE] = "Security override (jumper)",
115 	[HFSTS_HFS_OPMODE_MEI_OVERRIDE] = "Security override (MEI)",
116 	[HFSTS_HFS_OPMODE_SPS] =		"Server Platform Services",
117 };
118 
119 static const char *const hfsts_error_values[] = {
120 	[HFSTS_HFS_ERROR_NONE] =		"None",
121 	[HFSTS_HFS_ERROR_UNCATEGORIZED] = "Uncategorized",
122 	[HFSTS_HFS_ERROR_DISABLED] =	"Disabled",
123 	[HFSTS_HFS_ERROR_IMAGE] =	"Firmware image",
124 	[HFSTS_HFS_ERROR_DEBUG] =	"Debug",
125 };
126 
127 static const char *
128 hfsts_decode(const char *const *tbl, size_t tbl_sz, uint32_t idx)
129 {
130 
131 	if (idx >= tbl_sz || tbl[idx] == NULL)
132 		return ("Unknown");
133 	return (tbl[idx]);
134 }
135 #define HFSTS_DECODE(tbl, idx)	hfsts_decode((tbl), nitems(tbl), (idx))
136 
137 /* HFS1-6 PCI configuration offsets, indexed 0-5. */
138 static const uint32_t hfsts_hfs_off[6] = {
139 	HFSTS_CFG_HFS_1, HFSTS_CFG_HFS_2, HFSTS_CFG_HFS_3,
140 	HFSTS_CFG_HFS_4, HFSTS_CFG_HFS_5, HFSTS_CFG_HFS_6,
141 };
142 
143 /*
144  * All HFS fields are re-read from PCI configuration space on every sysctl
145  * access: the ME can transition CWS/opstate/error post-boot (watchdog
146  * resets, HAP toggles, S0i3/M0-M3 power events), so an attach-time snapshot
147  * would go stale under a running system.
148  */
149 static uint32_t
150 hfsts_read_hfs(device_t dev, u_int idx)
151 {
152 
153 	return (pci_read_config(dev, hfsts_hfs_off[idx], 4));
154 }
155 
156 /*
157  * One-word summary for the log/sysctl consumer who doesn't want to parse
158  * the raw HFS fields: Absent (register read didn't land on a real state),
159  * Disabled (ME explicitly turned off), Active (busy, transitional, or in an
160  * exceptional mode), or Normal (steady-state, with no reported error).
161  */
162 static const char *
163 hfsts_state_summary(uint32_t cws, uint32_t opmode, uint32_t error,
164 	    uint32_t mfg_mode, uint32_t fpt_bad, uint32_t bup_fail,
165 	    uint32_t fw_init_cmpl, uint32_t update_inprog)
166 {
167 
168 	if (cws >= nitems(hfsts_cws_values) || hfsts_cws_values[cws] == NULL)
169 		return ("Absent");
170 	if (cws == HFSTS_HFS_CWS_DISABLED || cws == HFSTS_HFS_CWS_WAIT ||
171 	    opmode == HFSTS_HFS_OPMODE_TEMP_DISABLED ||
172 	    error == HFSTS_HFS_ERROR_DISABLED)
173 		return ("Disabled");
174 	if (cws != HFSTS_HFS_CWS_NORMAL || opmode != HFSTS_HFS_OPMODE_NORMAL ||
175 	    error != HFSTS_HFS_ERROR_NONE || mfg_mode || fpt_bad || bup_fail ||
176 	    !fw_init_cmpl || update_inprog)
177 		return ("Active");
178 	return ("Normal");
179 }
180 
181 struct hfsts_device {
182 	uint16_t	device;
183 	uint8_t		hfs_count;
184 	const char	*name;
185 };
186 
187 /*
188  * Intel MEI/HECI PCI functions with usable Host Firmware Status registers.
189  * ICH10 has HFS1, PCH5 through PCH7 have HFS1-2, and PCH8 and newer have
190  * HFS1-6.  Legacy ICH devices, for which no HFS register is defined, are
191  * deliberately not matched.
192  */
193 #define HFSTS_DEV_ICH10(id, desc)	{ (id), 1, (desc) }
194 #define HFSTS_DEV_PCH(id, desc)		{ (id), 2, (desc) }
195 #define HFSTS_DEV_PCH8(id, desc)	{ (id), 6, (desc) }
196 
197 static const struct hfsts_device hfsts_devices[] = {
198 	HFSTS_DEV_ICH10(0x2e04, "Eaglelake MEI"),
199 	HFSTS_DEV_ICH10(0x2e14, "Eaglelake MEI"),
200 	HFSTS_DEV_ICH10(0x2e24, "Eaglelake MEI"),
201 	HFSTS_DEV_ICH10(0x2e34, "Eaglelake MEI"),
202 	HFSTS_DEV_PCH(0x3b64, "Calpella MEI"),
203 	HFSTS_DEV_PCH(0x3b65, "Calpella MEI"),
204 	HFSTS_DEV_PCH(0x1c3a, "Cougar Point MEI"),
205 	HFSTS_DEV_PCH(0x1d3a, "C600/X79 Patsburg MEI"),
206 	HFSTS_DEV_PCH(0x1e3a, "Panther Point MEI"),
207 	HFSTS_DEV_PCH(0x1cba, "Panther Point MEI"),
208 	HFSTS_DEV_PCH(0x1dba, "Panther Point MEI"),
209 	HFSTS_DEV_PCH8(0x8c3a, "Lynx Point H MEI"),
210 	HFSTS_DEV_PCH8(0x8d3a, "Lynx Point Wellsburg MEI"),
211 	HFSTS_DEV_PCH8(0x9c3a, "Lynx Point LP MEI"),
212 	HFSTS_DEV_PCH8(0x8cba, "Lynx Point H Refresh MEI"),
213 	HFSTS_DEV_PCH8(0x9cba, "Wildcat Point LP MEI"),
214 	HFSTS_DEV_PCH8(0x9cbb, "Wildcat Point LP 2 MEI"),
215 	HFSTS_DEV_PCH8(0x9d3a, "Sunrise Point MEI"),
216 	HFSTS_DEV_PCH8(0x9d3b, "Sunrise Point 2 MEI"),
217 	HFSTS_DEV_PCH8(0x9d3e, "Sunrise Point 3 (iTouch) MEI"),
218 	HFSTS_DEV_PCH8(0xa13a, "Sunrise Point H MEI"),
219 	HFSTS_DEV_PCH8(0xa13b, "Sunrise Point H 2 MEI"),
220 	HFSTS_DEV_PCH8(0xa1ba, "Lewisburg (SPT) MEI"),
221 	HFSTS_DEV_PCH8(0x1a9a, "Broxton M MEI"),
222 	HFSTS_DEV_PCH8(0x5a9a, "Apollo Lake I MEI"),
223 	HFSTS_DEV_PCH8(0x19e5, "Denverton IE MEI"),
224 	HFSTS_DEV_PCH8(0x319a, "Gemini Lake MEI"),
225 	HFSTS_DEV_PCH8(0xa2ba, "Kaby Point MEI"),
226 	HFSTS_DEV_PCH8(0xa2bb, "Kaby Point 2 MEI"),
227 	HFSTS_DEV_PCH8(0xa2be, "Kaby Point 3 (iTouch) MEI"),
228 	HFSTS_DEV_PCH8(0x9de0, "Cannon Point LP MEI"),
229 	HFSTS_DEV_PCH8(0x9de4, "Cannon Point LP 3 (iTouch) MEI"),
230 	HFSTS_DEV_PCH8(0xa360, "Cannon Point H MEI"),
231 	HFSTS_DEV_PCH8(0xa364, "Cannon Point H 3 (iTouch) MEI"),
232 	HFSTS_DEV_PCH8(0x02e0, "Comet Point LP MEI"),
233 	HFSTS_DEV_PCH8(0x02e4, "Comet Point LP 3 (iTouch) MEI"),
234 	HFSTS_DEV_PCH8(0xa3ba, "Comet Point Lake V MEI"),
235 	HFSTS_DEV_PCH8(0x06e0, "Comet Lake H MEI"),
236 	HFSTS_DEV_PCH8(0x06e4, "Comet Lake H 3 (iTouch) MEI"),
237 	HFSTS_DEV_PCH8(0x18d3, "Cedar Fork MEI"),
238 	HFSTS_DEV_PCH8(0x34e0, "Ice Lake Point LP MEI"),
239 	HFSTS_DEV_PCH8(0x38e0, "Ice Lake Point N MEI"),
240 	HFSTS_DEV_PCH8(0x4de0, "Jasper Lake Point N MEI"),
241 	HFSTS_DEV_PCH8(0xa0e0, "Tiger Lake Point LP MEI"),
242 	HFSTS_DEV_PCH8(0x43e0, "Tiger Lake Point H MEI"),
243 	HFSTS_DEV_PCH8(0x4b70, "Mule Creek Canyon (EHL) MEI"),
244 	HFSTS_DEV_PCH8(0x4b75, "Mule Creek Canyon 4 (EHL) MEI"),
245 	HFSTS_DEV_PCH8(0x1be0, "Emmitsburg WS MEI"),
246 	HFSTS_DEV_PCH8(0x7ae8, "Alder Lake Point S MEI"),
247 	HFSTS_DEV_PCH8(0x7a60, "Alder Lake Point LP MEI"),
248 	HFSTS_DEV_PCH8(0x51e0, "Alder Lake Point P MEI"),
249 	HFSTS_DEV_PCH8(0x54e0, "Alder Lake Point N MEI"),
250 	HFSTS_DEV_PCH8(0x7a68, "Raptor Lake Point S MEI"),
251 	HFSTS_DEV_PCH8(0x7e70, "Meteor Lake Point M MEI"),
252 	HFSTS_DEV_PCH8(0x7f68, "Arrow Lake Point S MEI"),
253 	HFSTS_DEV_PCH8(0x7770, "Arrow Lake Point H MEI"),
254 	HFSTS_DEV_PCH8(0xa870, "Lunar Lake Point M MEI"),
255 	HFSTS_DEV_PCH8(0xe370, "Panther Lake H MEI"),
256 	HFSTS_DEV_PCH8(0xe470, "Panther Lake P MEI"),
257 	HFSTS_DEV_PCH8(0x4d70, "Wildcat Lake P MEI"),
258 	HFSTS_DEV_PCH8(0x6e68, "Nova Lake Point S MEI"),
259 	HFSTS_DEV_PCH8(0xd370, "Nova Lake Point H MEI"),
260 	{ 0, 0, NULL }
261 };
262 
263 static const struct hfsts_device *
264 hfsts_find_device(device_t dev)
265 {
266 	const struct hfsts_device *id;
267 
268 	for (id = hfsts_devices; id->name != NULL; id++) {
269 		if (pci_get_device(dev) == id->device)
270 			return (id);
271 	}
272 	return (NULL);
273 }
274 
275 /*
276  * The isa-side Function Disable check is only meaningful when no known
277  * MEI/HECI function enumerated on PCI.
278  */
279 bool
280 hfsts_pci_present(void)
281 {
282 	const struct hfsts_device *id;
283 
284 	for (id = hfsts_devices; id->name != NULL; id++) {
285 		if (pci_find_device(VENDORID_INTEL, id->device) != NULL)
286 			return (true);
287 	}
288 	return (false);
289 }
290 
291 struct hfsts_softc {
292 	device_t		sc_dev;
293 	struct sysctl_ctx_list	*sc_sysctlctx;
294 	struct sysctl_oid	*sc_sysctlnode;
295 	uint8_t			sc_hfs_count;
296 };
297 
298 /* Simple masked/shifted HFS sub-fields, read live via a common handler. */
299 enum hfsts_field_id {
300 	HFSTS_FLD_D0I3_SUPPORTED,
301 	HFSTS_FLD_OPMODE,
302 	HFSTS_FLD_PM_EVENT,
303 	HFSTS_FLD_FW_SKU,
304 	HFSTS_FLD_MFG_MODE,
305 	HFSTS_FLD_UPDATE_INPROG,
306 };
307 
308 struct hfsts_field {
309 	uint8_t		hfs_idx;
310 	uint32_t	mask;
311 	uint8_t		shift;
312 };
313 
314 static const struct hfsts_field hfsts_fields[] = {
315 	[HFSTS_FLD_D0I3_SUPPORTED] = { 0, HFSTS_HFS_1_D0I3_MSK, 31 },
316 	[HFSTS_FLD_OPMODE] =	{ 0, HFSTS_HFS_1_OPMODE_MSK,
317 				    HFSTS_HFS_1_OPMODE_SHIFT },
318 	[HFSTS_FLD_PM_EVENT] =	{ 1, HFSTS_HFS_2_PM_EVENT_MSK,
319 				    HFSTS_HFS_2_PM_EVENT_SHIFT },
320 	[HFSTS_FLD_FW_SKU] =	{ 2, HFSTS_HFS_3_FW_SKU_MSK,
321 				    HFSTS_HFS_3_FW_SKU_SHIFT },
322 	[HFSTS_FLD_MFG_MODE] =	{ 0, HFSTS_HFS_1_MFG_MODE_MSK, 4 },
323 	[HFSTS_FLD_UPDATE_INPROG] = { 0, HFSTS_HFS_1_UPDATE_INPROG_MSK, 11 },
324 };
325 
326 static int
327 hfsts_sysctl_raw(SYSCTL_HANDLER_ARGS)
328 {
329 	struct hfsts_softc *sc = arg1;
330 	uint32_t val;
331 
332 	val = hfsts_read_hfs(sc->sc_dev, (u_int)arg2);
333 	return (sysctl_handle_32(oidp, &val, 0, req));
334 }
335 
336 static int
337 hfsts_sysctl_field(SYSCTL_HANDLER_ARGS)
338 {
339 	struct hfsts_softc *sc = arg1;
340 	const struct hfsts_field *f = &hfsts_fields[arg2];
341 	uint32_t val;
342 
343 	val = (hfsts_read_hfs(sc->sc_dev, f->hfs_idx) & f->mask) >> f->shift;
344 	return (sysctl_handle_32(oidp, &val, 0, req));
345 }
346 
347 /* HFS1 sub-fields that decode to a string via a lookup table. */
348 enum hfsts_decode_id {
349 	HFSTS_DEC_CWS,
350 	HFSTS_DEC_OPSTATE,
351 	HFSTS_DEC_OPMODE,
352 	HFSTS_DEC_ERROR,
353 };
354 
355 struct hfsts_decode {
356 	uint32_t		mask;
357 	uint8_t			shift;
358 	const char *const	*tbl;
359 	size_t			tbl_sz;
360 };
361 
362 static const struct hfsts_decode hfsts_decodes[] = {
363 	[HFSTS_DEC_CWS] =     { HFSTS_HFS_1_CWS_MSK, 0,
364 				    hfsts_cws_values, nitems(hfsts_cws_values) },
365 	[HFSTS_DEC_OPSTATE] = { HFSTS_HFS_1_OPSTATE_MSK,
366 				    HFSTS_HFS_1_OPSTATE_SHIFT,
367 				    hfsts_opstate_values,
368 				    nitems(hfsts_opstate_values) },
369 	[HFSTS_DEC_OPMODE] =  { HFSTS_HFS_1_OPMODE_MSK,
370 				    HFSTS_HFS_1_OPMODE_SHIFT,
371 				    hfsts_opmode_values,
372 				    nitems(hfsts_opmode_values) },
373 	[HFSTS_DEC_ERROR] =   { HFSTS_HFS_1_ERROR_MSK, HFSTS_HFS_1_ERROR_SHIFT,
374 				    hfsts_error_values, nitems(hfsts_error_values) },
375 };
376 
377 static int
378 hfsts_sysctl_decode(SYSCTL_HANDLER_ARGS)
379 {
380 	struct hfsts_softc *sc = arg1;
381 	const struct hfsts_decode *d = &hfsts_decodes[arg2];
382 	uint32_t val;
383 
384 	val = (hfsts_read_hfs(sc->sc_dev, 0) & d->mask) >> d->shift;
385 	return (SYSCTL_OUT_STR(req, hfsts_decode(d->tbl, d->tbl_sz, val)));
386 }
387 
388 static int
389 hfsts_sysctl_summary(SYSCTL_HANDLER_ARGS)
390 {
391 	struct hfsts_softc *sc = arg1;
392 	uint32_t hfs1, cws, opmode, error;
393 	uint32_t mfg_mode, fpt_bad, bup_fail, fw_init_cmpl, update_inprog;
394 
395 	hfs1 = hfsts_read_hfs(sc->sc_dev, 0);
396 	cws = hfs1 & HFSTS_HFS_1_CWS_MSK;
397 	opmode = (hfs1 & HFSTS_HFS_1_OPMODE_MSK) >> HFSTS_HFS_1_OPMODE_SHIFT;
398 	error = (hfs1 & HFSTS_HFS_1_ERROR_MSK) >> HFSTS_HFS_1_ERROR_SHIFT;
399 	mfg_mode = (hfs1 & HFSTS_HFS_1_MFG_MODE_MSK) != 0;
400 	fpt_bad = (hfs1 & HFSTS_HFS_1_FPT_BAD_MSK) != 0;
401 	bup_fail = (hfs1 & HFSTS_HFS_1_BUP_FAIL_MSK) != 0;
402 	fw_init_cmpl = (hfs1 & HFSTS_HFS_1_FW_INIT_CMPL_MSK) != 0;
403 	update_inprog = (hfs1 & HFSTS_HFS_1_UPDATE_INPROG_MSK) != 0;
404 
405 	return (SYSCTL_OUT_STR(req, hfsts_state_summary(cws, opmode, error,
406 	    mfg_mode, fpt_bad, bup_fail, fw_init_cmpl, update_inprog)));
407 }
408 
409 /* Table-driven sysctl registration; skipped when sc_hfs_count < min_hfs. */
410 typedef int hfsts_sysctl_handler_t(SYSCTL_HANDLER_ARGS);
411 
412 struct hfsts_sysctl_def {
413 	const char		*name;
414 	int			ctltype;
415 	intmax_t		arg2;
416 	hfsts_sysctl_handler_t	*handler;
417 	const char		*fmt;
418 	const char		*descr;
419 	uint8_t			min_hfs;
420 };
421 
422 static const struct hfsts_sysctl_def hfsts_sysctls[] = {
423 	{ "hfs1", CTLTYPE_U32, 0, hfsts_sysctl_raw, "IU",
424 	    "Host Firmware Status 1 (raw, live)", 1 },
425 	{ "hfs2", CTLTYPE_U32, 1, hfsts_sysctl_raw, "IU",
426 	    "Host Firmware Status 2 (raw, live)", 2 },
427 	{ "hfs3", CTLTYPE_U32, 2, hfsts_sysctl_raw, "IU",
428 	    "Host Firmware Status 3 (raw, live)", 6 },
429 	{ "hfs4", CTLTYPE_U32, 3, hfsts_sysctl_raw, "IU",
430 	    "Host Firmware Status 4 (raw, live)", 6 },
431 	{ "hfs5", CTLTYPE_U32, 4, hfsts_sysctl_raw, "IU",
432 	    "Host Firmware Status 5 (raw, live)", 6 },
433 	{ "hfs6", CTLTYPE_U32, 5, hfsts_sysctl_raw, "IU",
434 	    "Host Firmware Status 6 (raw, live)", 6 },
435 	{ "d0i3_supported", CTLTYPE_U32, HFSTS_FLD_D0I3_SUPPORTED,
436 	    hfsts_sysctl_field, "IU",
437 	    "ME D0i3 capability (from HFS1, live)", 6 },
438 	{ "opmode", CTLTYPE_U32, HFSTS_FLD_OPMODE, hfsts_sysctl_field, "IU",
439 	    "ME operation mode (from HFS1, live)", 1 },
440 	{ "pm_event", CTLTYPE_U32, HFSTS_FLD_PM_EVENT, hfsts_sysctl_field,
441 	    "IU", "ME power management event (from HFS2, live)", 2 },
442 	{ "fw_sku", CTLTYPE_U32, HFSTS_FLD_FW_SKU, hfsts_sysctl_field, "IU",
443 	    "ME firmware SKU (from HFS3, live)", 6 },
444 	{ "mfg_mode", CTLTYPE_U32, HFSTS_FLD_MFG_MODE, hfsts_sysctl_field,
445 	    "IU", "ME running in Manufacturing Mode (from HFS1, live)", 1 },
446 	{ "update_in_progress", CTLTYPE_U32, HFSTS_FLD_UPDATE_INPROG,
447 	    hfsts_sysctl_field, "IU",
448 	    "ME firmware update in progress (from HFS1, live)", 1 },
449 	{ "state", CTLTYPE_STRING, HFSTS_DEC_CWS, hfsts_sysctl_decode, "A",
450 	    "ME Current Working State (from HFS1, live)", 1 },
451 	{ "opstate", CTLTYPE_STRING, HFSTS_DEC_OPSTATE, hfsts_sysctl_decode,
452 	    "A", "ME Current Operation State (from HFS1, live)", 1 },
453 	{ "opmode_str", CTLTYPE_STRING, HFSTS_DEC_OPMODE, hfsts_sysctl_decode,
454 	    "A", "ME Current Operation Mode (from HFS1, live)", 1 },
455 	{ "error", CTLTYPE_STRING, HFSTS_DEC_ERROR, hfsts_sysctl_decode, "A",
456 	    "ME Error Code (from HFS1, live)", 1 },
457 	{ "summary", CTLTYPE_STRING, 0, hfsts_sysctl_summary, "A",
458 	    "One-word ME state summary (live)", 1 },
459 };
460 
461 static int
462 hfsts_probe(device_t dev)
463 {
464 	const struct hfsts_device *id;
465 
466 	if (pci_get_vendor(dev) != VENDORID_INTEL)
467 		return (ENXIO);
468 
469 	id = hfsts_find_device(dev);
470 	if (id == NULL)
471 		return (ENXIO);
472 	device_set_desc(dev, id->name);
473 	return (BUS_PROBE_GENERIC);
474 }
475 
476 static int
477 hfsts_attach(device_t dev)
478 {
479 	const struct hfsts_device *id;
480 	struct hfsts_softc *sc;
481 	struct sysctl_oid_list *children;
482 	const char *summary;
483 	uint32_t hfs1;
484 	uint32_t opmode;
485 	uint32_t cws, opstate, error, mfg_mode, fpt_bad, fw_init_cmpl;
486 	uint32_t bup_fail, update_inprog, boot_opt;
487 	u_int i;
488 
489 	sc = device_get_softc(dev);
490 	sc->sc_dev = dev;
491 	id = hfsts_find_device(dev);
492 	if (id == NULL)
493 		return (ENXIO);
494 	sc->sc_hfs_count = id->hfs_count;
495 
496 	/* One-shot read for the attach-time log line only; sysctls re-read. */
497 	hfs1 = hfsts_read_hfs(dev, 0);
498 	opmode = (hfs1 & HFSTS_HFS_1_OPMODE_MSK) >> HFSTS_HFS_1_OPMODE_SHIFT;
499 	cws = hfs1 & HFSTS_HFS_1_CWS_MSK;
500 	opstate = (hfs1 & HFSTS_HFS_1_OPSTATE_MSK) >> HFSTS_HFS_1_OPSTATE_SHIFT;
501 	error = (hfs1 & HFSTS_HFS_1_ERROR_MSK) >> HFSTS_HFS_1_ERROR_SHIFT;
502 	mfg_mode = (hfs1 & HFSTS_HFS_1_MFG_MODE_MSK) != 0;
503 	fpt_bad = (hfs1 & HFSTS_HFS_1_FPT_BAD_MSK) != 0;
504 	fw_init_cmpl = (hfs1 & HFSTS_HFS_1_FW_INIT_CMPL_MSK) != 0;
505 	bup_fail = (hfs1 & HFSTS_HFS_1_BUP_FAIL_MSK) != 0;
506 	update_inprog = (hfs1 & HFSTS_HFS_1_UPDATE_INPROG_MSK) != 0;
507 	boot_opt = (hfs1 & HFSTS_HFS_1_BOOT_OPT_MSK) != 0;
508 	summary = hfsts_state_summary(cws, opmode, error, mfg_mode, fpt_bad,
509 	    bup_fail, fw_init_cmpl, update_inprog);
510 
511 	if (bootverbose) {
512 		device_printf(dev,
513 		    "state=%s opstate=\"%s\" opmode=\"%s\" error=\"%s\" "
514 		    "mfg_mode=%u fpt_bad=%u fw_init_complete=%u "
515 		    "update_in_progress=%u boot_options_present=%u\n",
516 		    HFSTS_DECODE(hfsts_cws_values, cws),
517 		    HFSTS_DECODE(hfsts_opstate_values, opstate),
518 		    HFSTS_DECODE(hfsts_opmode_values, opmode),
519 		    HFSTS_DECODE(hfsts_error_values, error),
520 		    mfg_mode, fpt_bad, fw_init_cmpl, update_inprog, boot_opt);
521 	}
522 
523 	device_printf(dev, "ME state: %s\n", summary);
524 
525 	sc->sc_sysctlctx = device_get_sysctl_ctx(dev);
526 	sc->sc_sysctlnode = device_get_sysctl_tree(dev);
527 	children = SYSCTL_CHILDREN(sc->sc_sysctlnode);
528 
529 	for (i = 0; i < nitems(hfsts_sysctls); i++) {
530 		const struct hfsts_sysctl_def *d = &hfsts_sysctls[i];
531 
532 		if (sc->sc_hfs_count < d->min_hfs)
533 			continue;
534 		sysctl_add_oid(sc->sc_sysctlctx, children, OID_AUTO, d->name,
535 		    d->ctltype | CTLFLAG_RD | CTLFLAG_MPSAFE, sc, d->arg2,
536 		    d->handler, d->fmt, __DESCR(d->descr), NULL);
537 	}
538 
539 	return (0);
540 }
541 
542 static int
543 hfsts_detach(device_t dev)
544 {
545 
546 	return (0);
547 }
548 
549 static device_method_t hfsts_methods[] = {
550 	DEVMETHOD(device_probe,	hfsts_probe),
551 	DEVMETHOD(device_attach,	hfsts_attach),
552 	DEVMETHOD(device_detach,	hfsts_detach),
553 	DEVMETHOD_END
554 };
555 
556 static driver_t hfsts_driver = {
557 	"hfsts",
558 	hfsts_methods,
559 	sizeof(struct hfsts_softc)
560 };
561 
562 DRIVER_MODULE(hfsts, pci, hfsts_driver, 0, 0);
563 MODULE_VERSION(hfsts, 1);
564 MODULE_DEPEND(hfsts, pci, 1, 1, 1);
565