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