xref: /netbsd-src/sys/dev/acpi/apei_erst.c (revision 95e606572bb667a06ba686a8c38fd5ea2a580cd2)
1 /*	$NetBSD: apei_erst.c,v 1.3 2024/03/22 20:48:14 riastradh Exp $	*/
2 
3 /*-
4  * Copyright (c) 2024 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
17  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
18  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
20  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  * POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 /*
30  * APEI ERST -- Error Record Serialization Table
31  *
32  * https://uefi.org/specs/ACPI/6.5/18_Platform_Error_Interfaces.html#error-serialization
33  *
34  * XXX Expose this through a /dev node with ioctls and/or through a
35  * file system.
36  */
37 
38 #include <sys/cdefs.h>
39 __KERNEL_RCSID(0, "$NetBSD: apei_erst.c,v 1.3 2024/03/22 20:48:14 riastradh Exp $");
40 
41 #include <sys/param.h>
42 #include <sys/types.h>
43 
44 #include <sys/systm.h>
45 
46 #include <dev/acpi/acpivar.h>
47 #include <dev/acpi/apei_erstvar.h>
48 #include <dev/acpi/apei_interp.h>
49 #include <dev/acpi/apei_reg.h>
50 #include <dev/acpi/apeivar.h>
51 
52 #define	_COMPONENT	ACPI_RESOURCE_COMPONENT
53 ACPI_MODULE_NAME	("apei")
54 
55 static bool apei_erst_instvalid(ACPI_WHEA_HEADER *, uint32_t, uint32_t);
56 static void apei_erst_instfunc(ACPI_WHEA_HEADER *, struct apei_mapreg *,
57     void *, uint32_t *, uint32_t);
58 static uint64_t apei_erst_act(struct apei_softc *, enum AcpiErstActions,
59     uint64_t);
60 
61 /*
62  * apei_erst_action
63  *
64  *	Symbolic names of the APEI ERST (Error Record Serialization
65  *	Table) logical actions are taken (and downcased) from:
66  *
67  *	https://uefi.org/specs/ACPI/6.5/18_Platform_Error_Interfaces.html#error-record-serialization-actions-table
68  */
69 static const char *const apei_erst_action[] = {
70 	[ACPI_ERST_BEGIN_WRITE] = "begin_write_operation",
71 	[ACPI_ERST_BEGIN_READ] = "begin_read_operation",
72 	[ACPI_ERST_BEGIN_CLEAR] = "begin_clear_operation",
73 	[ACPI_ERST_END] = "end_operation",
74 	[ACPI_ERST_SET_RECORD_OFFSET] = "set_record_offset",
75 	[ACPI_ERST_EXECUTE_OPERATION] = "execute_operation",
76 	[ACPI_ERST_CHECK_BUSY_STATUS] = "check_busy_status",
77 	[ACPI_ERST_GET_COMMAND_STATUS] = "get_command_status",
78 	[ACPI_ERST_GET_RECORD_ID] = "get_record_identifier",
79 	[ACPI_ERST_SET_RECORD_ID] = "set_record_identifier",
80 	[ACPI_ERST_GET_RECORD_COUNT] = "get_record_count",
81 	[ACPI_ERST_BEGIN_DUMMY_WRIITE] = "begin_dummy_write_operation",
82 	[ACPI_ERST_NOT_USED] = "reserved",
83 	[ACPI_ERST_GET_ERROR_RANGE] = "get_error_log_address_range",
84 	[ACPI_ERST_GET_ERROR_LENGTH] = "get_error_log_address_range_length",
85 	[ACPI_ERST_GET_ERROR_ATTRIBUTES] =
86 	    "get_error_log_address_range_attributes",
87 	[ACPI_ERST_EXECUTE_TIMINGS] = "get_execute_operations_timings",
88 };
89 
90 /*
91  * apei_erst_instruction
92  *
93  *	Symbolic names of the APEI ERST (Error Record Serialization
94  *	Table) instructions to implement logical actions are taken (and
95  *	downcased) from:
96  *
97  *	https://uefi.org/specs/ACPI/6.5/18_Platform_Error_Interfaces.html#serialization-instructions
98  */
99 static const char *apei_erst_instruction[] = {
100 	[ACPI_ERST_READ_REGISTER] = "read_register",
101 	[ACPI_ERST_READ_REGISTER_VALUE] = "read_register_value",
102 	[ACPI_ERST_WRITE_REGISTER] = "write_register",
103 	[ACPI_ERST_WRITE_REGISTER_VALUE] = "write_register_value",
104 	[ACPI_ERST_NOOP] = "noop",
105 	[ACPI_ERST_LOAD_VAR1] = "load_var1",
106 	[ACPI_ERST_LOAD_VAR2] = "load_var2",
107 	[ACPI_ERST_STORE_VAR1] = "store_var1",
108 	[ACPI_ERST_ADD] = "add",
109 	[ACPI_ERST_SUBTRACT] = "subtract",
110 	[ACPI_ERST_ADD_VALUE] = "add_value",
111 	[ACPI_ERST_SUBTRACT_VALUE] = "subtract_value",
112 	[ACPI_ERST_STALL] = "stall",
113 	[ACPI_ERST_STALL_WHILE_TRUE] = "stall_while_true",
114 	[ACPI_ERST_SKIP_NEXT_IF_TRUE] = "skip_next_instruction_if_true",
115 	[ACPI_ERST_GOTO] = "goto",
116 	[ACPI_ERST_SET_SRC_ADDRESS_BASE] = "set_src_address_base",
117 	[ACPI_ERST_SET_DST_ADDRESS_BASE] = "set_dst_address_base",
118 	[ACPI_ERST_MOVE_DATA] = "move_data",
119 };
120 
121 /*
122  * apei_erst_instreg
123  *
124  *	Table of which isntructions use a register operand.
125  *
126  *	Must match apei_erst_instfunc.
127  */
128 static const bool apei_erst_instreg[] = {
129 	[ACPI_ERST_READ_REGISTER] = true,
130 	[ACPI_ERST_READ_REGISTER_VALUE] = true,
131 	[ACPI_ERST_WRITE_REGISTER] = true,
132 	[ACPI_ERST_WRITE_REGISTER_VALUE] = true,
133 	[ACPI_ERST_NOOP] = false,
134 	[ACPI_ERST_LOAD_VAR1] = true,
135 	[ACPI_ERST_LOAD_VAR2] = true,
136 	[ACPI_ERST_STORE_VAR1] = true,
137 	[ACPI_ERST_ADD] = false,
138 	[ACPI_ERST_SUBTRACT] = false,
139 	[ACPI_ERST_ADD_VALUE] = true,
140 	[ACPI_ERST_SUBTRACT_VALUE] = true,
141 	[ACPI_ERST_STALL] = false,
142 	[ACPI_ERST_STALL_WHILE_TRUE] = true,
143 	[ACPI_ERST_SKIP_NEXT_IF_TRUE] = true,
144 	[ACPI_ERST_GOTO] = false,
145 	[ACPI_ERST_SET_SRC_ADDRESS_BASE] = true,
146 	[ACPI_ERST_SET_DST_ADDRESS_BASE] = true,
147 	[ACPI_ERST_MOVE_DATA] = true,
148 };
149 
150 /*
151  * XXX dtrace and kernhist
152  */
153 static void
apei_pmemmove(uint64_t pdst,uint64_t psrc,uint64_t nbytes)154 apei_pmemmove(uint64_t pdst, uint64_t psrc, uint64_t nbytes)
155 {
156 	char *vdst, *vsrc;
157 
158 	aprint_debug("ERST: move"
159 	    " %"PRIu64" bytes from 0x%"PRIx64" to 0x%"PRIx64"\n",
160 	    nbytes, psrc, pdst);
161 
162 	/*
163 	 * Carefully check for overlap.
164 	 */
165 	if (pdst == psrc) {
166 		/*
167 		 * Technically this could happen, I guess!
168 		 */
169 		return;
170 	} else if (pdst < psrc && psrc < pdst + nbytes) {
171 		/*
172 		 *       psrc ______ psrc + nbytes
173 		 *           /      \
174 		 * <--------------------->
175 		 *      \______/
176 		 *  pdst        pdst + nbytes
177 		 */
178 		vdst = AcpiOsMapMemory(pdst, nbytes + (psrc - pdst));
179 		vsrc = vdst + (psrc - pdst);
180 		memmove(vdst, vsrc, nbytes);
181 		AcpiOsUnmapMemory(vdst, nbytes + (psrc - pdst));
182 	} else if (psrc < pdst && pdst < psrc + nbytes) {
183 		/*
184 		 *  psrc ______ psrc + nbytes
185 		 *      /      \
186 		 * <--------------------->
187 		 *           \______/
188 		 *       pdst        pdst + nbytes
189 		 */
190 		vsrc = AcpiOsMapMemory(psrc, nbytes + (pdst - psrc));
191 		vdst = vsrc + (pdst - psrc);
192 		memmove(vdst, vsrc, nbytes);
193 		AcpiOsUnmapMemory(vsrc, nbytes + (pdst - psrc));
194 	} else {
195 		/*
196 		 * No overlap.
197 		 */
198 		vdst = AcpiOsMapMemory(pdst, nbytes);
199 		vsrc = AcpiOsMapMemory(psrc, nbytes);
200 		memcpy(vdst, vsrc, nbytes);
201 		AcpiOsUnmapMemory(vsrc, nbytes);
202 		AcpiOsUnmapMemory(vdst, nbytes);
203 	}
204 }
205 
206 /*
207  * apei_erst_attach(sc)
208  *
209  *	Scan the Error Record Serialization Table to collate the
210  *	instructions for each ERST action.
211  */
212 void
apei_erst_attach(struct apei_softc * sc)213 apei_erst_attach(struct apei_softc *sc)
214 {
215 	ACPI_TABLE_ERST *erst = sc->sc_tab.erst;
216 	struct apei_erst_softc *ssc = &sc->sc_erst;
217 	ACPI_ERST_ENTRY *entry;
218 	uint32_t i, nentries, maxnentries;
219 
220 	/*
221 	 * Verify the table length, table header length, and
222 	 * instruction entry count are all sensible.  If the header is
223 	 * truncated, stop here; if the entries are truncated, stop at
224 	 * the largest integral number of full entries that fits.
225 	 */
226 	if (erst->Header.Length < sizeof(*erst)) {
227 		aprint_error_dev(sc->sc_dev, "ERST: truncated table:"
228 		    " %"PRIu32" < %zu minimum bytes\n",
229 		    erst->Header.Length, sizeof(*erst));
230 		return;
231 	}
232 	if (erst->HeaderLength <
233 	    sizeof(*erst) - offsetof(ACPI_TABLE_ERST, HeaderLength)) {
234 		aprint_error_dev(sc->sc_dev, "ERST: truncated header:"
235 		    " %"PRIu32" < %zu bytes\n",
236 		    erst->HeaderLength,
237 		    sizeof(*erst) - offsetof(ACPI_TABLE_ERST, HeaderLength));
238 		return;
239 	}
240 	nentries = erst->Entries;
241 	maxnentries = (erst->Header.Length - sizeof(*erst))/sizeof(*entry);
242 	if (nentries > maxnentries) {
243 		aprint_error_dev(sc->sc_dev, "ERST: excessive entries:"
244 		    " %"PRIu32", truncating to %"PRIu32"\n",
245 		    nentries, maxnentries);
246 		nentries = maxnentries;
247 	}
248 	if (nentries*sizeof(*entry) < erst->Header.Length - sizeof(*erst)) {
249 		aprint_error_dev(sc->sc_dev, "ERST:"
250 		    " %zu bytes of trailing garbage after last entry\n",
251 		    erst->Header.Length - nentries*sizeof(*entry));
252 	}
253 
254 	/*
255 	 * Create an interpreter for ERST actions.
256 	 */
257 	ssc->ssc_interp = apei_interp_create("ERST",
258 	    apei_erst_action, __arraycount(apei_erst_action),
259 	    apei_erst_instruction, __arraycount(apei_erst_instruction),
260 	    apei_erst_instreg, apei_erst_instvalid, apei_erst_instfunc);
261 
262 	/*
263 	 * Compile the interpreter from the ERST action instruction
264 	 * table.
265 	 */
266 	entry = (ACPI_ERST_ENTRY *)(erst + 1);
267 	for (i = 0; i < nentries; i++, entry++)
268 		apei_interp_pass1_load(ssc->ssc_interp, i, &entry->WheaHeader);
269 	entry = (ACPI_ERST_ENTRY *)(erst + 1);
270 	for (i = 0; i < nentries; i++, entry++) {
271 		apei_interp_pass2_verify(ssc->ssc_interp, i,
272 		    &entry->WheaHeader);
273 	}
274 	apei_interp_pass3_alloc(ssc->ssc_interp);
275 	entry = (ACPI_ERST_ENTRY *)(erst + 1);
276 	for (i = 0; i < nentries; i++, entry++) {
277 		apei_interp_pass4_assemble(ssc->ssc_interp, i,
278 		    &entry->WheaHeader);
279 	}
280 	apei_interp_pass5_verify(ssc->ssc_interp);
281 
282 	/*
283 	 * Print some basic information about the stored records.
284 	 */
285 	uint64_t logaddr = apei_erst_act(sc, ACPI_ERST_GET_ERROR_RANGE, 0);
286 	uint64_t logbytes = apei_erst_act(sc, ACPI_ERST_GET_ERROR_LENGTH, 0);
287 	uint64_t attr = apei_erst_act(sc, ACPI_ERST_GET_ERROR_ATTRIBUTES, 0);
288 	uint64_t nrecords = apei_erst_act(sc, ACPI_ERST_GET_RECORD_COUNT, 0);
289 	char attrbuf[128];
290 
291 	/* XXX define this format somewhere */
292 	snprintb(attrbuf, sizeof(attrbuf), "\177\020"
293 	    "\001"	"NVRAM\0"
294 	    "\002"	"SLOW\0"
295 	    "\0", attr);
296 
297 	aprint_normal_dev(sc->sc_dev, "ERST: %"PRIu64" records in error log"
298 	    " %"PRIu64" bytes @ 0x%"PRIx64" attr=%s\n",
299 	    nrecords, logbytes, logaddr, attrbuf);
300 
301 	/*
302 	 * XXX wire up to sysctl or a file system or something, and/or
303 	 * dmesg or crash dumps
304 	 */
305 }
306 
307 /*
308  * apei_erst_detach(sc)
309  *
310  *	Free software resource allocated for ERST handling.
311  */
312 void
apei_erst_detach(struct apei_softc * sc)313 apei_erst_detach(struct apei_softc *sc)
314 {
315 	struct apei_erst_softc *ssc = &sc->sc_erst;
316 
317 	if (ssc->ssc_interp) {
318 		apei_interp_destroy(ssc->ssc_interp);
319 		ssc->ssc_interp = NULL;
320 	}
321 }
322 
323 /*
324  * apei_erst_instvalid(header, ninst, i)
325  *
326  *	Routine to validate the ith entry, for an action with ninst
327  *	instructions.
328  */
329 static bool
apei_erst_instvalid(ACPI_WHEA_HEADER * header,uint32_t ninst,uint32_t i)330 apei_erst_instvalid(ACPI_WHEA_HEADER *header, uint32_t ninst, uint32_t i)
331 {
332 
333 	switch (header->Instruction) {
334 	case ACPI_ERST_GOTO:
335 		if (header->Value > ninst) {
336 			aprint_error("ERST[%"PRIu32"]:"
337 			    " GOTO(%"PRIu64") out of bounds,"
338 			    " disabling action %"PRIu32" (%s)\n", i,
339 			    header->Value,
340 			    header->Action,
341 			    apei_erst_action[header->Action]);
342 			return false;
343 		}
344 	}
345 	return true;
346 }
347 
348 /*
349  * struct apei_erst_machine
350  *
351  *	Machine state for executing ERST instructions.
352  */
353 struct apei_erst_machine {
354 	struct apei_softc	*sc;
355 	uint64_t		x;	/* in */
356 	uint64_t		y;	/* out */
357 	uint64_t		var1;
358 	uint64_t		var2;
359 	uint64_t		src_base;
360 	uint64_t		dst_base;
361 };
362 
363 /*
364  * apei_erst_instfunc(header, map, cookie, &ip, maxip)
365  *
366  *	Run a single instruction in the service of performing an ERST
367  *	action.  Updates the ERST machine at cookie, and the ip if
368  *	necessary, in place.
369  *
370  *	On entry, ip points to the next instruction after this one
371  *	sequentially; on exit, ip points to the next instruction to
372  *	execute.
373  */
374 static void
apei_erst_instfunc(ACPI_WHEA_HEADER * header,struct apei_mapreg * map,void * cookie,uint32_t * ipp,uint32_t maxip)375 apei_erst_instfunc(ACPI_WHEA_HEADER *header, struct apei_mapreg *map,
376     void *cookie, uint32_t *ipp, uint32_t maxip)
377 {
378 	struct apei_erst_machine *const M = cookie;
379 
380 	/*
381 	 * Abbreviate some of the intermediate quantities to make the
382 	 * instruction logic conciser and more legible.
383 	 */
384 	const uint8_t BitOffset = header->RegisterRegion.BitOffset;
385 	const uint64_t Mask = header->Mask;
386 	const uint64_t Value = header->Value;
387 	ACPI_GENERIC_ADDRESS *const reg = &header->RegisterRegion;
388 	const bool preserve_register = header->Flags & ACPI_ERST_PRESERVE;
389 
390 	aprint_debug_dev(M->sc->sc_dev, "%s: instr=0x%02"PRIx8
391 	    " (%s)"
392 	    " Address=0x%"PRIx64
393 	    " BitOffset=%"PRIu8" Mask=0x%"PRIx64" Value=0x%"PRIx64
394 	    " Flags=0x%"PRIx8"\n",
395 	    __func__, header->Instruction,
396 	    (header->Instruction < __arraycount(apei_erst_instruction)
397 		? apei_erst_instruction[header->Instruction]
398 		: "unknown"),
399 	    reg->Address,
400 	    BitOffset, Mask, Value,
401 	    header->Flags);
402 
403 	/*
404 	 * Zero-initialize the output by default.
405 	 */
406 	M->y = 0;
407 
408 	/*
409 	 * Dispatch the instruction.
410 	 */
411 	switch (header->Instruction) {
412 	case ACPI_ERST_READ_REGISTER:
413 		M->y = apei_read_register(reg, map, Mask);
414 		break;
415 	case ACPI_ERST_READ_REGISTER_VALUE: {
416 		uint64_t v;
417 
418 		v = apei_read_register(reg, map, Mask);
419 		M->y = (v == Value ? 1 : 0);
420 		break;
421 	}
422 	case ACPI_ERST_WRITE_REGISTER:
423 		apei_write_register(reg, map, Mask, preserve_register, M->x);
424 		break;
425 	case ACPI_ERST_WRITE_REGISTER_VALUE:
426 		apei_write_register(reg, map, Mask, preserve_register, Value);
427 		break;
428 	case ACPI_ERST_NOOP:
429 		break;
430 	case ACPI_ERST_LOAD_VAR1:
431 		M->var1 = apei_read_register(reg, map, Mask);
432 		break;
433 	case ACPI_ERST_LOAD_VAR2:
434 		M->var2 = apei_read_register(reg, map, Mask);
435 		break;
436 	case ACPI_ERST_STORE_VAR1:
437 		apei_write_register(reg, map, Mask, preserve_register,
438 		    M->var1);
439 		break;
440 	case ACPI_ERST_ADD:
441 		M->var1 += M->var2;
442 		break;
443 	case ACPI_ERST_SUBTRACT:
444 		/*
445 		 * The specification at
446 		 * https://uefi.org/specs/ACPI/6.5/18_Platform_Error_Interfaces.html#serialization-instructions
447 		 * says:
448 		 *
449 		 *	0x09	SUBTRACT	Subtracts VAR1 from VAR2
450 		 *				and stores the result in
451 		 *				VAR1.
452 		 *
453 		 * So, according to the spec, this is _not_ simply
454 		 *
455 		 *	M->var1 -= M->var2;
456 		 */
457 		M->var1 = M->var2 - M->var1;
458 		break;
459 	case ACPI_ERST_ADD_VALUE: {
460 		uint64_t v;
461 
462 		v = apei_read_register(reg, map, Mask);
463 		v += Value;
464 		apei_write_register(reg, map, Mask, preserve_register, v);
465 		break;
466 	}
467 	case ACPI_ERST_SUBTRACT_VALUE: {
468 		uint64_t v;
469 
470 		v = apei_read_register(reg, map, Mask);
471 		v -= Value;
472 		apei_write_register(reg, map, Mask, preserve_register, v);
473 		break;
474 	}
475 	case ACPI_ERST_STALL:
476 		DELAY(Value);		/* XXX avoid excessive delays */
477 		break;
478 	case ACPI_ERST_STALL_WHILE_TRUE:
479 		for (;;) {
480 			if (apei_read_register(reg, map, Mask) != Value)
481 				break;
482 			DELAY(M->var1);
483 		}
484 		break;
485 	case ACPI_ERST_SKIP_NEXT_IF_TRUE:
486 		/*
487 		 * If reading the register yields Value, skip the next
488 		 * instruction -- unless that would run past the end of
489 		 * the instruction buffer.
490 		 */
491 		if (apei_read_register(reg, map, Mask) == Value) {
492 			if (*ipp < maxip)
493 				(*ipp)++;
494 		}
495 		break;
496 	case ACPI_ERST_GOTO:
497 		if (Value >= maxip) /* paranoia */
498 			*ipp = maxip;
499 		else
500 			*ipp = Value;
501 		break;
502 	case ACPI_ERST_SET_SRC_ADDRESS_BASE:
503 		M->src_base = apei_read_register(reg, map, Mask);
504 		break;
505 	case ACPI_ERST_SET_DST_ADDRESS_BASE:
506 		M->src_base = apei_read_register(reg, map, Mask);
507 		break;
508 	case ACPI_ERST_MOVE_DATA: {
509 		const uint64_t v = apei_read_register(reg, map, Mask);
510 
511 		/*
512 		 * XXX This might not work in nasty contexts unless we
513 		 * pre-allocate a virtual page for the mapping.
514 		 */
515 		apei_pmemmove(M->dst_base + v, M->src_base + v, M->var2);
516 		break;
517 	}
518 	default:		/* XXX unreachable */
519 		break;
520 	}
521 }
522 
523 /*
524  * apei_erst_act(sc, action, x)
525  *
526  *	Perform the named ERST action with input x, by stepping through
527  *	all the instructions defined for the action by the ERST, and
528  *	return the output.
529  */
530 static uint64_t
apei_erst_act(struct apei_softc * sc,enum AcpiErstActions action,uint64_t x)531 apei_erst_act(struct apei_softc *sc, enum AcpiErstActions action, uint64_t x)
532 {
533 	struct apei_erst_softc *const ssc = &sc->sc_erst;
534 	struct apei_erst_machine erst_machine, *const M = &erst_machine;
535 
536 	aprint_debug_dev(sc->sc_dev, "%s: action=%d (%s) input=0x%"PRIx64"\n",
537 	    __func__,
538 	    action,
539 	    (action < __arraycount(apei_erst_action)
540 		? apei_erst_action[action]
541 		: "unknown"),
542 	    x);
543 
544 	/*
545 	 * Initialize the machine to execute the action's instructions.
546 	 */
547 	memset(M, 0, sizeof(*M));
548 	M->sc = sc;
549 	M->x = x;		/* input */
550 	M->y = 0;		/* output */
551 	M->var1 = 0;
552 	M->var2 = 0;
553 	M->src_base = 0;
554 	M->dst_base = 0;
555 
556 	/*
557 	 * Run the interpreter.
558 	 */
559 	apei_interpret(ssc->ssc_interp, action, M);
560 
561 	/*
562 	 * Return the result.
563 	 */
564 	aprint_debug_dev(sc->sc_dev, "%s: output=0x%"PRIx64"\n", __func__,
565 	    M->y);
566 	return M->y;
567 }
568