Home | History | Annotate | Line # | Download | only in acpi
      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
    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
    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
    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
    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
    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
    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