#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: apei_erst.c,v 1.5 2025/12/10 21:33:02 andvar Exp $");
#include <sys/param.h>
#include <sys/types.h>
#include <sys/systm.h>
#include <dev/acpi/acpivar.h>
#include <dev/acpi/apei_erstvar.h>
#include <dev/acpi/apei_interp.h>
#include <dev/acpi/apei_reg.h>
#include <dev/acpi/apeivar.h>
#define _COMPONENT ACPI_RESOURCE_COMPONENT
ACPI_MODULE_NAME ("apei")
static bool apei_erst_instvalid(ACPI_WHEA_HEADER *, uint32_t, uint32_t);
static void apei_erst_instfunc(ACPI_WHEA_HEADER *, struct apei_mapreg *,
void *, uint32_t *, uint32_t);
static uint64_t apei_erst_act(struct apei_softc *, enum AcpiErstActions,
uint64_t);
static const char *const apei_erst_action[] = {
[ACPI_ERST_BEGIN_WRITE] = "begin_write_operation",
[ACPI_ERST_BEGIN_READ] = "begin_read_operation",
[ACPI_ERST_BEGIN_CLEAR] = "begin_clear_operation",
[ACPI_ERST_END] = "end_operation",
[ACPI_ERST_SET_RECORD_OFFSET] = "set_record_offset",
[ACPI_ERST_EXECUTE_OPERATION] = "execute_operation",
[ACPI_ERST_CHECK_BUSY_STATUS] = "check_busy_status",
[ACPI_ERST_GET_COMMAND_STATUS] = "get_command_status",
[ACPI_ERST_GET_RECORD_ID] = "get_record_identifier",
[ACPI_ERST_SET_RECORD_ID] = "set_record_identifier",
[ACPI_ERST_GET_RECORD_COUNT] = "get_record_count",
[ACPI_ERST_BEGIN_DUMMY_WRIITE] = "begin_dummy_write_operation",
[ACPI_ERST_NOT_USED] = "reserved",
[ACPI_ERST_GET_ERROR_RANGE] = "get_error_log_address_range",
[ACPI_ERST_GET_ERROR_LENGTH] = "get_error_log_address_range_length",
[ACPI_ERST_GET_ERROR_ATTRIBUTES] =
"get_error_log_address_range_attributes",
[ACPI_ERST_EXECUTE_TIMINGS] = "get_execute_operations_timings",
};
static const char *apei_erst_instruction[] = {
[ACPI_ERST_READ_REGISTER] = "read_register",
[ACPI_ERST_READ_REGISTER_VALUE] = "read_register_value",
[ACPI_ERST_WRITE_REGISTER] = "write_register",
[ACPI_ERST_WRITE_REGISTER_VALUE] = "write_register_value",
[ACPI_ERST_NOOP] = "noop",
[ACPI_ERST_LOAD_VAR1] = "load_var1",
[ACPI_ERST_LOAD_VAR2] = "load_var2",
[ACPI_ERST_STORE_VAR1] = "store_var1",
[ACPI_ERST_ADD] = "add",
[ACPI_ERST_SUBTRACT] = "subtract",
[ACPI_ERST_ADD_VALUE] = "add_value",
[ACPI_ERST_SUBTRACT_VALUE] = "subtract_value",
[ACPI_ERST_STALL] = "stall",
[ACPI_ERST_STALL_WHILE_TRUE] = "stall_while_true",
[ACPI_ERST_SKIP_NEXT_IF_TRUE] = "skip_next_instruction_if_true",
[ACPI_ERST_GOTO] = "goto",
[ACPI_ERST_SET_SRC_ADDRESS_BASE] = "set_src_address_base",
[ACPI_ERST_SET_DST_ADDRESS_BASE] = "set_dst_address_base",
[ACPI_ERST_MOVE_DATA] = "move_data",
};
static const bool apei_erst_instreg[] = {
[ACPI_ERST_READ_REGISTER] = true,
[ACPI_ERST_READ_REGISTER_VALUE] = true,
[ACPI_ERST_WRITE_REGISTER] = true,
[ACPI_ERST_WRITE_REGISTER_VALUE] = true,
[ACPI_ERST_NOOP] = false,
[ACPI_ERST_LOAD_VAR1] = true,
[ACPI_ERST_LOAD_VAR2] = true,
[ACPI_ERST_STORE_VAR1] = true,
[ACPI_ERST_ADD] = false,
[ACPI_ERST_SUBTRACT] = false,
[ACPI_ERST_ADD_VALUE] = true,
[ACPI_ERST_SUBTRACT_VALUE] = true,
[ACPI_ERST_STALL] = false,
[ACPI_ERST_STALL_WHILE_TRUE] = true,
[ACPI_ERST_SKIP_NEXT_IF_TRUE] = true,
[ACPI_ERST_GOTO] = false,
[ACPI_ERST_SET_SRC_ADDRESS_BASE] = true,
[ACPI_ERST_SET_DST_ADDRESS_BASE] = true,
[ACPI_ERST_MOVE_DATA] = true,
};
static void
apei_pmemmove(uint64_t pdst, uint64_t psrc, uint64_t nbytes)
{
char *vdst, *vsrc;
aprint_debug("ERST: move"
" %"PRIu64" bytes from 0x%"PRIx64" to 0x%"PRIx64"\n",
nbytes, psrc, pdst);
if (pdst == psrc) {
return;
} else if (pdst < psrc && psrc < pdst + nbytes) {
vdst = AcpiOsMapMemory(pdst, nbytes + (psrc - pdst));
vsrc = vdst + (psrc - pdst);
memmove(vdst, vsrc, nbytes);
AcpiOsUnmapMemory(vdst, nbytes + (psrc - pdst));
} else if (psrc < pdst && pdst < psrc + nbytes) {
vsrc = AcpiOsMapMemory(psrc, nbytes + (pdst - psrc));
vdst = vsrc + (pdst - psrc);
memmove(vdst, vsrc, nbytes);
AcpiOsUnmapMemory(vsrc, nbytes + (pdst - psrc));
} else {
vdst = AcpiOsMapMemory(pdst, nbytes);
vsrc = AcpiOsMapMemory(psrc, nbytes);
memcpy(vdst, vsrc, nbytes);
AcpiOsUnmapMemory(vsrc, nbytes);
AcpiOsUnmapMemory(vdst, nbytes);
}
}
void
apei_erst_attach(struct apei_softc *sc)
{
ACPI_TABLE_ERST *erst = sc->sc_tab.erst;
struct apei_erst_softc *ssc = &sc->sc_erst;
ACPI_ERST_ENTRY *entry;
uint32_t i, nentries, maxnentries;
if (erst->Header.Length < sizeof(*erst)) {
aprint_error_dev(sc->sc_dev, "ERST: truncated table:"
" %"PRIu32" < %zu minimum bytes\n",
erst->Header.Length, sizeof(*erst));
return;
}
if (erst->HeaderLength <
sizeof(*erst) - offsetof(ACPI_TABLE_ERST, HeaderLength)) {
aprint_error_dev(sc->sc_dev, "ERST: truncated header:"
" %"PRIu32" < %zu bytes\n",
erst->HeaderLength,
sizeof(*erst) - offsetof(ACPI_TABLE_ERST, HeaderLength));
return;
}
nentries = erst->Entries;
maxnentries = (erst->Header.Length - sizeof(*erst))/sizeof(*entry);
if (nentries > maxnentries) {
aprint_error_dev(sc->sc_dev, "ERST: excessive entries:"
" %"PRIu32", truncating to %"PRIu32"\n",
nentries, maxnentries);
nentries = maxnentries;
}
if (nentries*sizeof(*entry) < erst->Header.Length - sizeof(*erst)) {
aprint_error_dev(sc->sc_dev, "ERST:"
" %zu bytes of trailing garbage after last entry\n",
erst->Header.Length - nentries*sizeof(*entry));
}
ssc->ssc_interp = apei_interp_create("ERST",
apei_erst_action, __arraycount(apei_erst_action),
apei_erst_instruction, __arraycount(apei_erst_instruction),
apei_erst_instreg, apei_erst_instvalid, apei_erst_instfunc);
entry = (ACPI_ERST_ENTRY *)(erst + 1);
for (i = 0; i < nentries; i++, entry++)
apei_interp_pass1_load(ssc->ssc_interp, i, &entry->WheaHeader);
entry = (ACPI_ERST_ENTRY *)(erst + 1);
for (i = 0; i < nentries; i++, entry++) {
apei_interp_pass2_verify(ssc->ssc_interp, i,
&entry->WheaHeader);
}
apei_interp_pass3_alloc(ssc->ssc_interp);
entry = (ACPI_ERST_ENTRY *)(erst + 1);
for (i = 0; i < nentries; i++, entry++) {
apei_interp_pass4_assemble(ssc->ssc_interp, i,
&entry->WheaHeader);
}
apei_interp_pass5_verify(ssc->ssc_interp);
uint64_t logaddr = apei_erst_act(sc, ACPI_ERST_GET_ERROR_RANGE, 0);
uint64_t logbytes = apei_erst_act(sc, ACPI_ERST_GET_ERROR_LENGTH, 0);
uint64_t attr = apei_erst_act(sc, ACPI_ERST_GET_ERROR_ATTRIBUTES, 0);
uint64_t nrecords = apei_erst_act(sc, ACPI_ERST_GET_RECORD_COUNT, 0);
char attrbuf[128];
snprintb(attrbuf, sizeof(attrbuf), "\177\020"
"b\001" "NVRAM\0"
"b\002" "SLOW\0"
"\0", attr);
aprint_normal_dev(sc->sc_dev, "ERST: %"PRIu64" records in error log"
" %"PRIu64" bytes @ 0x%"PRIx64" attr=%s\n",
nrecords, logbytes, logaddr, attrbuf);
}
void
apei_erst_detach(struct apei_softc *sc)
{
struct apei_erst_softc *ssc = &sc->sc_erst;
if (ssc->ssc_interp) {
apei_interp_destroy(ssc->ssc_interp);
ssc->ssc_interp = NULL;
}
}
static bool
apei_erst_instvalid(ACPI_WHEA_HEADER *header, uint32_t ninst, uint32_t i)
{
switch (header->Instruction) {
case ACPI_ERST_GOTO:
if (header->Value > ninst) {
aprint_error("ERST[%"PRIu32"]:"
" GOTO(%"PRIu64") out of bounds,"
" disabling action %"PRIu32" (%s)\n", i,
header->Value,
header->Action,
apei_erst_action[header->Action]);
return false;
}
}
return true;
}
struct apei_erst_machine {
struct apei_softc *sc;
uint64_t x;
uint64_t y;
uint64_t var1;
uint64_t var2;
uint64_t src_base;
uint64_t dst_base;
};
static void
apei_erst_instfunc(ACPI_WHEA_HEADER *header, struct apei_mapreg *map,
void *cookie, uint32_t *ipp, uint32_t maxip)
{
struct apei_erst_machine *const M = cookie;
const uint8_t BitOffset = header->RegisterRegion.BitOffset;
const uint64_t Mask = header->Mask;
const uint64_t Value = header->Value;
ACPI_GENERIC_ADDRESS *const reg = &header->RegisterRegion;
const bool preserve_register = header->Flags & ACPI_ERST_PRESERVE;
aprint_debug_dev(M->sc->sc_dev, "%s: instr=0x%02"PRIx8
" (%s)"
" Address=0x%"PRIx64
" BitOffset=%"PRIu8" Mask=0x%"PRIx64" Value=0x%"PRIx64
" Flags=0x%"PRIx8"\n",
__func__, header->Instruction,
(header->Instruction < __arraycount(apei_erst_instruction)
? apei_erst_instruction[header->Instruction]
: "unknown"),
reg->Address,
BitOffset, Mask, Value,
header->Flags);
M->y = 0;
switch (header->Instruction) {
case ACPI_ERST_READ_REGISTER:
M->y = apei_read_register(reg, map, Mask);
break;
case ACPI_ERST_READ_REGISTER_VALUE: {
uint64_t v;
v = apei_read_register(reg, map, Mask);
M->y = (v == Value ? 1 : 0);
break;
}
case ACPI_ERST_WRITE_REGISTER:
apei_write_register(reg, map, Mask, preserve_register, M->x);
break;
case ACPI_ERST_WRITE_REGISTER_VALUE:
apei_write_register(reg, map, Mask, preserve_register, Value);
break;
case ACPI_ERST_NOOP:
break;
case ACPI_ERST_LOAD_VAR1:
M->var1 = apei_read_register(reg, map, Mask);
break;
case ACPI_ERST_LOAD_VAR2:
M->var2 = apei_read_register(reg, map, Mask);
break;
case ACPI_ERST_STORE_VAR1:
apei_write_register(reg, map, Mask, preserve_register,
M->var1);
break;
case ACPI_ERST_ADD:
M->var1 += M->var2;
break;
case ACPI_ERST_SUBTRACT:
M->var1 = M->var2 - M->var1;
break;
case ACPI_ERST_ADD_VALUE: {
uint64_t v;
v = apei_read_register(reg, map, Mask);
v += Value;
apei_write_register(reg, map, Mask, preserve_register, v);
break;
}
case ACPI_ERST_SUBTRACT_VALUE: {
uint64_t v;
v = apei_read_register(reg, map, Mask);
v -= Value;
apei_write_register(reg, map, Mask, preserve_register, v);
break;
}
case ACPI_ERST_STALL:
DELAY(Value);
break;
case ACPI_ERST_STALL_WHILE_TRUE:
for (;;) {
if (apei_read_register(reg, map, Mask) != Value)
break;
DELAY(M->var1);
}
break;
case ACPI_ERST_SKIP_NEXT_IF_TRUE:
if (apei_read_register(reg, map, Mask) == Value) {
if (*ipp < maxip)
(*ipp)++;
}
break;
case ACPI_ERST_GOTO:
if (Value >= maxip)
*ipp = maxip;
else
*ipp = Value;
break;
case ACPI_ERST_SET_SRC_ADDRESS_BASE:
M->src_base = apei_read_register(reg, map, Mask);
break;
case ACPI_ERST_SET_DST_ADDRESS_BASE:
M->src_base = apei_read_register(reg, map, Mask);
break;
case ACPI_ERST_MOVE_DATA: {
const uint64_t v = apei_read_register(reg, map, Mask);
apei_pmemmove(M->dst_base + v, M->src_base + v, M->var2);
break;
}
default:
break;
}
}
static uint64_t
apei_erst_act(struct apei_softc *sc, enum AcpiErstActions action, uint64_t x)
{
struct apei_erst_softc *const ssc = &sc->sc_erst;
struct apei_erst_machine erst_machine, *const M = &erst_machine;
aprint_debug_dev(sc->sc_dev, "%s: action=%d (%s) input=0x%"PRIx64"\n",
__func__,
action,
(action < __arraycount(apei_erst_action)
? apei_erst_action[action]
: "unknown"),
x);
memset(M, 0, sizeof(*M));
M->sc = sc;
M->x = x;
M->y = 0;
M->var1 = 0;
M->var2 = 0;
M->src_base = 0;
M->dst_base = 0;
apei_interpret(ssc->ssc_interp, action, M);
aprint_debug_dev(sc->sc_dev, "%s: output=0x%"PRIx64"\n", __func__,
M->y);
return M->y;
}