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
53ACPI_MODULE_NAME	("apei")
54
55static bool apei_erst_instvalid(ACPI_WHEA_HEADER *, uint32_t, uint32_t);
56static void apei_erst_instfunc(ACPI_WHEA_HEADER *, struct apei_mapreg *,
57    void *, uint32_t *, uint32_t);
58static 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 */
69static 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 */
99static 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 */
128static 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 */
153static void
154apei_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 */
212void
213apei_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 */
312void
313apei_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 */
329static bool
330apei_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 */
353struct 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 */
374static void
375apei_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 */
530static uint64_t
531apei_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