git: bc49842769bd - main - acpi_einj: Support for ACPI error injection
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Tue, 07 Jul 2026 19:18:18 UTC
The branch main has been updated by jhb:
URL: https://cgit.FreeBSD.org/src/commit/?id=bc49842769bd3657a5a1e7de878c0ce8780a2f86
commit bc49842769bd3657a5a1e7de878c0ce8780a2f86
Author: John Baldwin <jhb@FreeBSD.org>
AuthorDate: 2026-07-07 18:15:27 +0000
Commit: John Baldwin <jhb@FreeBSD.org>
CommitDate: 2026-07-07 18:15:27 +0000
acpi_einj: Support for ACPI error injection
This driver parses the ACPI EINJ table and builds a list of
instructions associated with known actions. It then exports ioctls to
fetch the set of supported errors and inject system errors by
executing specific sequences of actions. This can be used to test
error reporting facilities for events such as ECC errors.
Reviewed by: gallatin
Sponsored by: Netflix
Differential Revision: https://reviews.freebsd.org/D58025
---
share/man/man4/Makefile | 1 +
share/man/man4/acpi_einj.4 | 58 +++
sys/dev/acpica/acpi_einj.c | 777 ++++++++++++++++++++++++++++++++++++
sys/dev/acpica/acpiio.h | 39 ++
sys/modules/acpi/Makefile | 1 +
sys/modules/acpi/acpi_einj/Makefile | 7 +
6 files changed, 883 insertions(+)
diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile
index b029d8a64933..775a3e84f933 100644
--- a/share/man/man4/Makefile
+++ b/share/man/man4/Makefile
@@ -17,6 +17,7 @@ MAN= aac.4 \
${_acpi_asus.4} \
${_acpi_asus_wmi.4} \
${_acpi_dock.4} \
+ acpi_einj.4 \
${_acpi_fujitsu.4} \
${_acpi_hp.4} \
${_acpi_ibm.4} \
diff --git a/share/man/man4/acpi_einj.4 b/share/man/man4/acpi_einj.4
new file mode 100644
index 000000000000..e0ec3a39a9b7
--- /dev/null
+++ b/share/man/man4/acpi_einj.4
@@ -0,0 +1,58 @@
+.\"
+.\" Copyright (c) 2025-2026 Netflix, Inc.
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.Dd July 7, 2026
+.Dt ACPI_EINJ 4
+.Os
+.Sh NAME
+.Nm acpi_einj
+.Nd ACPI platform error injection module
+.Sh SYNOPSIS
+.In dev/acpica/acpiio.h
+.Sh DESCRIPTION
+The
+.Nm
+modules provides support for the error injection facility from the ACPI
+Platform Error Interface
+.Pq APEI .
+This module parses the EINJ table to construct a list of instructions
+associated with actions.
+The module provides several
+.Xr ioctl 2
+commands on the
+.Fa /dev/acpi
+character device used by the
+.Xr einj 8
+utility.
+.Sh IOCTLS
+The following commands are supported:
+.Bl -tag -width indent
+.It Dv ACPIIO_EINJ_GET_INFO
+Returns a
+.Vt struct acpi_einj_info
+object to the caller.
+.It Dv ACPIIO_EINJ_GET_VENDOR
+If the host supports the
+.Cm SET_ERROR_TYPE_WITH_ADDRESS
+command that includes a vendor extension structure,
+this command stores a copy of the vendor extension structure in the buffer
+described by the caller-provided
+.Vt struct acpi_einj_vendor_info .
+.It Dv ACPIIO_EINJ_SET_ERROR
+Injects a platform error described by a
+.Vt struct acpi_einj_error
+object provided by the caller.
+.El
+.Sh FILES
+.Bl -tag -width "/boot/kernel/acpi_einj.ko" -compact
+.It Pa /boot/kernel/acpi_einj.ko
+.Sh SEE ALSO
+.Xr acpi 4 ,
+.Xr einj 8
+.El
+.Sh HISTORY
+The
+.Nm
+module first appeared in
+.Fx 16.0 .
diff --git a/sys/dev/acpica/acpi_einj.c b/sys/dev/acpica/acpi_einj.c
new file mode 100644
index 000000000000..ac12cf28b414
--- /dev/null
+++ b/sys/dev/acpica/acpi_einj.c
@@ -0,0 +1,777 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025-2026 Netflix, Inc.
+ * Written by: John Baldwin <jhb@FreeBSD.org>
+ */
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/rman.h>
+
+#include <vm/vm.h>
+#include <vm/pmap.h>
+
+#include <contrib/dev/acpica/include/acpi.h>
+#include <contrib/dev/acpica/include/accommon.h>
+#include <contrib/dev/acpica/include/actables.h>
+
+#include <dev/acpica/acpiio.h>
+#include <dev/acpica/acpivar.h>
+#include <dev/acpica/apeivar.h>
+
+#define ACPI_EINJ_MAX_ACTION (ACPI_EINJV2_GET_ERROR_TYPE)
+
+struct einj_instruction {
+ struct resource_map *res;
+ u_int instruction;
+ u_int flags;
+ u_int size;
+ uint64_t value;
+ uint64_t mask;
+};
+
+struct einj_action {
+ struct einj_instruction *instructions;
+ u_int num_instructions;
+};
+
+struct einj_softc {
+ device_t dev;
+ ACPI_TABLE_EINJ *einj;
+ struct einj_action actions[ACPI_EINJ_MAX_ACTION + 1];
+
+ struct acpi_einj_info info;
+ struct resource_map *address_res;
+ struct resource_map *vendor_res;
+
+ struct sx lock;
+};
+
+static MALLOC_DEFINE(M_EINJ, "einj", "ACPI error injection");
+
+static bool
+einj_validate_instruction(u_int i, ACPI_EINJ_ENTRY *e)
+{
+ ACPI_GENERIC_ADDRESS *gas;
+ UINT8 valid_flags;
+
+ valid_flags = 0;
+ switch (e->WheaHeader.Instruction) {
+ case ACPI_EINJ_WRITE_REGISTER:
+ case ACPI_EINJ_WRITE_REGISTER_VALUE:
+ valid_flags |= ACPI_EINJ_PRESERVE;
+ break;
+ case ACPI_EINJ_READ_REGISTER:
+ case ACPI_EINJ_READ_REGISTER_VALUE:
+ /* PRESERVE_REGISTER is ignored, but not invalid. */
+ valid_flags |= ACPI_EINJ_PRESERVE;
+ break;
+ case ACPI_EINJ_NOOP:
+#if 0
+ /* Not documented. */
+ case ACPI_EINJ_FLUSH_CACHELINE:
+#endif
+ break;
+ default:
+ if (bootverbose)
+ printf("EINJ: Unknown instruction at index %u\n", i);
+ return (false);
+ }
+ if ((e->WheaHeader.Flags & ~valid_flags) != 0) {
+ if (bootverbose)
+ printf("EINJ: Invalid instruction flag at index %u\n",
+ i);
+ return (false);
+ }
+
+ gas = &e->WheaHeader.RegisterRegion;
+ switch (gas->SpaceId) {
+ case ACPI_ADR_SPACE_SYSTEM_MEMORY:
+ case ACPI_ADR_SPACE_SYSTEM_IO:
+ break;
+ default:
+ if (bootverbose)
+ printf("EINJ: Unsupported register address space at index %u\n",
+ i);
+ return (false);
+ }
+
+ /*
+ * The spec seems to suggest sub-bit ranges of registers might
+ * be valid, but punt on handling those until one is actually
+ * encountered in the wild.
+ */
+ if (gas->BitOffset != 0) {
+ if (bootverbose)
+ printf("EINJ: Unsupported register offset at index %u\n",
+ i);
+ return (false);
+ }
+ if (!(gas->BitWidth == 8 || gas->BitWidth == 16 ||
+ gas->BitWidth == 32 || gas->BitWidth == 64)) {
+ if (bootverbose)
+ printf("EINJ: Unsupported register width at index %u\n",
+ i);
+ return (false);
+ }
+ return (true);
+}
+
+static bool
+einj_validate_table(ACPI_TABLE_EINJ *einj)
+{
+ ACPI_EINJ_ENTRY *e;
+ u_int i;
+ bool seen_set_error_type_with_address;
+
+ if (einj->Header.Revision < 1 || einj->Header.Revision > 2) {
+ if (bootverbose)
+ printf("EINJ: Unsupported revision %u\n",
+ einj->Header.Revision);
+ return (false);
+ }
+ if (einj->HeaderLength != sizeof(*einj)) {
+ if (bootverbose)
+ printf("EINJ: Invalid Injection Interface Header Length\n");
+ return (false);
+ }
+ if (einj->Entries == 0) {
+ if (bootverbose)
+ printf("EINJ: No actions\n");
+ return (false);
+ }
+ if (einj->Header.Length != sizeof(*einj) + einj->Entries * sizeof(*e)) {
+ if (bootverbose)
+ printf("EINJ: Invalid table length\n");
+ return (false);
+ }
+
+ e = (ACPI_EINJ_ENTRY *)(einj + 1);
+ seen_set_error_type_with_address = false;
+ for (i = 0; i < einj->Entries; i++, e++) {
+ if (e->WheaHeader.Action > ACPI_EINJ_MAX_ACTION) {
+ if (bootverbose)
+ printf("EINJ: Invalid action at index %u\n", i);
+ return (false);
+ }
+
+ if (e->WheaHeader.Action ==
+ ACPI_EINJ_SET_ERROR_TYPE_WITH_ADDRESS) {
+ if (seen_set_error_type_with_address) {
+ if (bootverbose)
+ printf("EINJ: Multiple SET_ERROR_TYPE_WITH_ADDRESS instructions\n");
+ return (false);
+ }
+ seen_set_error_type_with_address = true;
+ }
+
+ if (!einj_validate_instruction(i, e))
+ return (false);
+ }
+ return (true);
+}
+
+static uint64_t
+einj_read_register(struct einj_instruction *inst)
+{
+ switch (inst->size) {
+ case 1:
+ return (bus_read_1(inst->res, 0));
+ case 2:
+ return (bus_read_2(inst->res, 0));
+ case 4:
+ return (bus_read_4(inst->res, 0));
+ case 8:
+ return (bus_read_8(inst->res, 0));
+ default:
+ __assert_unreachable();
+ }
+}
+
+static void
+einj_write_register(struct einj_instruction *inst, uint64_t value)
+{
+ switch (inst->size) {
+ case 1:
+ bus_write_1(inst->res, 0, value);
+ break;
+ case 2:
+ bus_write_2(inst->res, 0, value);
+ break;
+ case 4:
+ bus_write_4(inst->res, 0, value);
+ break;
+ case 8:
+ bus_write_8(inst->res, 0, value);
+ break;
+ default:
+ __assert_unreachable();
+ }
+}
+
+static uint64_t
+einj_execute_instruction(struct einj_instruction *inst, uint64_t value)
+{
+ uint64_t old;
+
+ switch (inst->instruction) {
+ case ACPI_EINJ_READ_REGISTER:
+ case ACPI_EINJ_READ_REGISTER_VALUE:
+ value = einj_read_register(inst);
+ value &= inst->mask;
+ if (inst->instruction == ACPI_EINJ_READ_REGISTER_VALUE)
+ value = (value == inst->value);
+ break;
+ case ACPI_EINJ_WRITE_REGISTER_VALUE:
+ value = inst->value;
+ /* FALLTHROUGH */
+ case ACPI_EINJ_WRITE_REGISTER:
+ value &= inst->mask;
+ if (inst->flags & ACPI_EINJ_PRESERVE) {
+ old = einj_read_register(inst);
+ value |= (old & ~inst->mask);
+ }
+ einj_write_register(inst, value);
+ break;
+ case ACPI_EINJ_NOOP:
+ break;
+ default:
+ __assert_unreachable();
+ }
+ return (value);
+}
+
+static uint64_t
+einj_execute_instructions(struct einj_instruction *inst, u_int count,
+ uint64_t value)
+{
+ for (u_int i = 0; i < count; i++, inst++) {
+ value = einj_execute_instruction(inst, value);
+ }
+ return (value);
+}
+
+static int
+einj_execute_action(struct einj_softc *sc, u_int action, uint64_t *value)
+{
+ struct einj_action *ea;
+
+ if (action >= nitems(sc->actions))
+ return (ENOENT);
+ ea = &sc->actions[action];
+ if (ea->num_instructions == 0)
+ return (ENOENT);
+
+ *value = einj_execute_instructions(ea->instructions,
+ ea->num_instructions, *value);
+ return (0);
+}
+
+static int
+einj_trigger_error(struct einj_softc *sc, vm_paddr_t table_pa)
+{
+ ACPI_EINJ_TRIGGER *table;
+ ACPI_EINJ_ENTRY *e;
+ size_t table_len;
+ uint64_t value;
+ int error;
+
+ /* Map the header to obtain the full length. */
+ table_len = sizeof(*table);
+ table = pmap_mapbios(table_pa, table_len);
+ if (table == NULL) {
+ device_printf(sc->dev, "failed to map trigger table header\n");
+ return (ENXIO);
+ }
+
+ if (table->HeaderSize != sizeof(*table)) {
+ device_printf(sc->dev, "invalid trigger table header size %u\n",
+ table->HeaderSize);
+ error = ENXIO;
+ goto out;
+ }
+
+ if (table->TableSize <
+ table->HeaderSize + sizeof(*e) * table->EntryCount) {
+ device_printf(sc->dev,
+ "trigger table too small (%u) for %u entries\n",
+ table->TableSize, table->EntryCount);
+ error = ENXIO;
+ goto out;
+ }
+
+ if (table->EntryCount == 0) {
+ error = 0;
+ goto out;
+ }
+
+ /* Map the full table. */
+ pmap_unmapbios(table, table_len);
+ table_len = table->TableSize;
+ table = pmap_mapbios(table_pa, table_len);
+ if (table == NULL) {
+ device_printf(sc->dev, "failed to map trigger table\n");
+ return (ENXIO);
+ }
+
+ value = 0;
+ e = (ACPI_EINJ_ENTRY *)(table + 1);
+ for (u_int i = 0; i < table->EntryCount; i++, e++) {
+ struct einj_instruction ei;
+
+ if (e->WheaHeader.Action != ACPI_EINJ_TRIGGER_ERROR)
+ device_printf(sc->dev,
+ "invalid action %#x for trigger instruction %u\n",
+ e->WheaHeader.Action, i);
+
+ if (e->WheaHeader.Instruction == ACPI_EINJ_NOOP)
+ continue;
+
+ ei.res = apei_map_register(sc->dev,
+ &e->WheaHeader.RegisterRegion);
+ if (ei.res == NULL) {
+ device_printf(sc->dev,
+ "failed to map register for trigger entry %u\n", i);
+ error = ENXIO;
+ goto out;
+ }
+
+ ei.instruction = e->WheaHeader.Instruction;
+ ei.flags = e->WheaHeader.Flags;
+ ei.value = e->WheaHeader.Value;
+ ei.mask = e->WheaHeader.Mask;
+ ei.size = e->WheaHeader.RegisterRegion.BitWidth / 8;
+ value = einj_execute_instruction(&ei, value);
+ apei_unmap_register(sc->dev, ei.res);
+ }
+ error = 0;
+out:
+ pmap_unmapbios(table, table_len);
+ return (error);
+}
+
+static int
+einj_set_error(struct einj_softc *sc, const struct acpi_einj_error *err)
+{
+ uint64_t value;
+ int error;
+
+ if ((err->address_flags & ~(ACPI_EINJ_APICID_VALID |
+ ACPI_EINJ_MEMADDRESS_VALID | ACPI_EINJ_PCIE_VALID)) != 0)
+ return (EINVAL);
+ if (err->address_flags != 0 && sc->address_res == NULL)
+ return (EOPNOTSUPP);
+
+ value = 0;
+ error = einj_execute_action(sc, ACPI_EINJ_BEGIN_OPERATION, &value);
+ if (error != 0) {
+ device_printf(sc->dev, "BEGIN_OPERATION failed\n");
+ return (error);
+ }
+
+ value = err->error_type;
+ if (sc->address_res != NULL) {
+ error = einj_execute_action(sc,
+ ACPI_EINJ_SET_ERROR_TYPE_WITH_ADDRESS, &value);
+ if (error != 0) {
+ device_printf(sc->dev,
+ "SET_ERROR_TYPE_WITH_ADDRESS failed\n");
+ goto out;
+ }
+
+ bus_write_4(sc->address_res,
+ offsetof(ACPI_EINJ_ERROR_TYPE_WITH_ADDR, Flags),
+ err->address_flags);
+ bus_write_4(sc->address_res,
+ offsetof(ACPI_EINJ_ERROR_TYPE_WITH_ADDR, ApicId),
+ err->apic_id);
+ bus_write_8(sc->address_res,
+ offsetof(ACPI_EINJ_ERROR_TYPE_WITH_ADDR, Address),
+ err->memory_address);
+ bus_write_8(sc->address_res,
+ offsetof(ACPI_EINJ_ERROR_TYPE_WITH_ADDR, Range),
+ err->memory_range);
+ bus_write_4(sc->address_res,
+ offsetof(ACPI_EINJ_ERROR_TYPE_WITH_ADDR, PcieId),
+ err->pcie_id);
+ } else {
+ error = einj_execute_action(sc, ACPI_EINJ_SET_ERROR_TYPE,
+ &value);
+ if (error != 0) {
+ device_printf(sc->dev, "SET_ERROR_TYPE failed\n");
+ goto out;
+ }
+ }
+
+ value = 0;
+ error = einj_execute_action(sc, ACPI_EINJ_EXECUTE_OPERATION, &value);
+ if (error != 0) {
+ device_printf(sc->dev, "EXECUTE_OPERATION failed\n");
+ goto out;
+ }
+
+ for (;;) {
+ value = 0;
+ error = einj_execute_action(sc, ACPI_EINJ_CHECK_BUSY_STATUS,
+ &value);
+ if (error != 0) {
+ device_printf(sc->dev, "CHECK_BUSY_STATUS failed\n");
+ goto out;
+ }
+
+ if ((value & 1) == 0)
+ break;
+ DELAY(1000);
+ }
+
+ value = 0;
+ error = einj_execute_action(sc, ACPI_EINJ_GET_COMMAND_STATUS, &value);
+ if (error != 0) {
+ device_printf(sc->dev, "GET_COMMAND_STATUS failed\n");
+ goto out;
+ }
+
+ /* Bits 1:8 of the returned value contain the status code. */
+ switch ((value >> 1) & 0xff) {
+ case ACPI_EINJ_SUCCESS:
+ break;
+ case ACPI_EINJ_FAILURE:
+ device_printf(sc->dev, "injection of error type %#x failed\n",
+ err->error_type);
+ error = ENXIO;
+ goto out;
+ case ACPI_EINJ_INVALID_ACCESS:
+ device_printf(sc->dev,
+ "invalid access while injecting error type %#x\n",
+ err->error_type);
+ error = ENXIO;
+ goto out;
+ default:
+ device_printf(sc->dev,
+ "unknown status %ju while injecting error type %#x\n",
+ (uintmax_t)value, err->error_type);
+ error = ENXIO;
+ goto out;
+ }
+
+ value = 0;
+ error = einj_execute_action(sc, ACPI_EINJ_GET_TRIGGER_TABLE, &value);
+ if (error != 0) {
+ device_printf(sc->dev, "GET_TRIGER_TABLE failed\n");
+ goto out;
+ }
+
+ error = einj_trigger_error(sc, value);
+out:
+ value = 0;
+ (void)einj_execute_action(sc, ACPI_EINJ_END_OPERATION, &value);
+ return (error);
+}
+
+static int
+einj_ioctl(u_long cmd, caddr_t addr, void *arg)
+{
+ struct einj_softc *sc = arg;
+ int error;
+
+ error = 0;
+ switch (cmd) {
+ case ACPIIO_EINJ_GET_INFO:
+ memcpy(addr, &sc->info, sizeof(sc->info));
+ break;
+ case ACPIIO_EINJ_GET_VENDOR: {
+ struct acpi_einj_vendor_info *v = (void *)addr;
+ void *buf;
+
+ if (sc->vendor_res == NULL)
+ return (ENXIO);
+ if (v->len != sc->info.vendor_length)
+ return (EINVAL);
+
+ /*
+ * This assumes the region can be safely read via
+ * 4-byte reads.
+ */
+ buf = malloc(v->len, M_DEVBUF, M_WAITOK);
+ bus_read_region_4(sc->vendor_res, 0, buf, v->len / 4);
+ if (v->len % 4 != 0) {
+ uint32_t offset;
+
+ offset = rounddown2(v->len, 4);
+ bus_read_region_1(sc->vendor_res, offset,
+ (char *)buf + offset, v->len % 4);
+ }
+
+ error = copyout(buf, v->buf, v->len);
+ free(buf, M_DEVBUF);
+ break;
+ }
+ case ACPIIO_EINJ_SET_ERROR:
+ sx_xlock(&sc->lock);
+ error = einj_set_error(sc, (struct acpi_einj_error *)addr);
+ sx_xunlock(&sc->lock);
+ break;
+ default:
+ error = ENOIOCTL;
+ break;
+ }
+ return (error);
+}
+
+static void
+einj_identify(driver_t *driver, device_t parent)
+{
+ ACPI_TABLE_HEADER *einj;
+ ACPI_STATUS status;
+ bool valid;
+
+ if (device_find_child(parent, "einj", DEVICE_UNIT_ANY) != NULL)
+ return;
+
+ status = AcpiGetTable(ACPI_SIG_EINJ, 0, &einj);
+ if (ACPI_FAILURE(status))
+ return;
+ valid = einj_validate_table((ACPI_TABLE_EINJ *)einj);
+ AcpiPutTable(einj);
+ if (!valid)
+ return;
+
+ BUS_ADD_CHILD(parent, 1, "einj", DEVICE_UNIT_ANY);
+}
+
+static bool
+einj_parse_table(struct einj_softc *sc, ACPI_GENERIC_ADDRESS *address_gas)
+{
+ ACPI_EINJ_ENTRY *e;
+ struct resource_map *res;
+ u_int i;
+
+ e = (ACPI_EINJ_ENTRY *)(sc->einj + 1);
+ for (i = 0; i < sc->einj->Entries; i++, e++) {
+ struct einj_action *ea = &sc->actions[e->WheaHeader.Action];
+ struct einj_instruction *ei;
+
+ switch (e->WheaHeader.Instruction) {
+ case ACPI_EINJ_NOOP:
+ res = NULL;
+ break;
+ default:
+ res = apei_map_register(sc->dev,
+ &e->WheaHeader.RegisterRegion);
+ if (res == NULL)
+ return (false);
+ }
+
+ if (e->WheaHeader.Action ==
+ ACPI_EINJ_SET_ERROR_TYPE_WITH_ADDRESS)
+ *address_gas = e->WheaHeader.RegisterRegion;
+
+ ea->instructions = realloc(ea->instructions,
+ sizeof(*ea->instructions) * (ea->num_instructions + 1),
+ M_EINJ, M_WAITOK);
+ ei = &ea->instructions[ea->num_instructions];
+ ei->res = res;
+ ei->instruction = e->WheaHeader.Instruction;
+ ei->flags = e->WheaHeader.Flags;
+ ei->value = e->WheaHeader.Value;
+ ei->mask = e->WheaHeader.Mask;
+ ei->size = e->WheaHeader.RegisterRegion.BitWidth / 8;
+ ea->num_instructions++;
+ }
+ return (true);
+}
+
+/*
+ * SET_ERROR_TYPE_WITH_ADDRESS points to a data structure, but the
+ * original action only points to the first word. Expand the mapping
+ * to cover the entire structure and initialize the cached copy of the
+ * structure.
+ */
+static int
+einj_parse_set_error_with_address(struct einj_softc *sc,
+ const ACPI_GENERIC_ADDRESS *gas)
+{
+ struct resource_map *res;
+ struct einj_action *ea;
+ uint32_t vendor_offset;
+
+ ea = &sc->actions[ACPI_EINJ_SET_ERROR_TYPE_WITH_ADDRESS];
+ if (ea->num_instructions == 0)
+ return (0);
+
+ /* Map the SET_ERROR_TYPE_WITH_ADDRESS structure. */
+ res = apei_map_memory(sc->dev, gas->Address,
+ sizeof(ACPI_EINJ_ERROR_TYPE_WITH_ADDR));
+ if (res == NULL) {
+ device_printf(sc->dev,
+ "failed to map SET_ERROR_TYPE_WITH_ADDRESS structure\n");
+ return (ENXIO);
+ }
+ sc->address_res = res;
+
+ /* Map the Vendor extension structure if it exists. */
+ if ((sc->info.error_type & ACPI_EINJ_VENDOR_DEFINED) == 0)
+ return (0);
+
+ vendor_offset = bus_read_4(res,
+ offsetof(ACPI_EINJ_ERROR_TYPE_WITH_ADDR, VendorStructOffset));
+
+ if (vendor_offset < sizeof(ACPI_EINJ_ERROR_TYPE_WITH_ADDR)) {
+ device_printf(sc->dev,
+ "Vendor Error extension overlaps base structure\n");
+ return (0);
+ }
+
+ res = apei_map_memory(sc->dev, gas->Address + vendor_offset,
+ sizeof(ACPI_EINJ_VENDOR));
+ if (res == NULL) {
+ device_printf(sc->dev,
+ "failed to map Vendor Error extension structure\n");
+ return (0);
+ }
+
+ sc->info.vendor_length =
+ bus_read_4(res, offsetof(ACPI_EINJ_VENDOR, Length));
+
+ if (sc->info.vendor_length < sizeof(ACPI_EINJ_VENDOR)) {
+ if (bootverbose || sc->info.vendor_length != 0)
+ device_printf(sc->dev,
+ "invalid Vendor Error extension structure length\n");
+ apei_unmap_register(sc->dev, res);
+ return (0);
+ }
+
+ if (sc->info.vendor_length > sizeof(ACPI_EINJ_VENDOR)) {
+ apei_unmap_register(sc->dev, res);
+ res = apei_map_memory(sc->dev, gas->Address + vendor_offset,
+ sc->info.vendor_length);
+ if (res == NULL) {
+ device_printf(sc->dev,
+ "failed to map Vendor Error extension structure\n");
+ return (0);
+ }
+ }
+ sc->vendor_res = res;
+
+ return (0);
+}
+
+static void
+einj_cleanup(struct einj_softc *sc)
+{
+ struct einj_action *ea;
+
+ /* This is idempotent if the handler isn't registered. */
+ acpi_deregister_ioctls(einj_ioctl);
+
+ ea = sc->actions;
+ for (u_int i = 0; i < nitems(sc->actions); i++, ea++) {
+ struct einj_instruction *ei;
+
+ ei = ea->instructions;
+ for (u_int j = 0; j < ea->num_instructions; j++, ei++) {
+ if (ei->res != NULL)
+ apei_unmap_register(sc->dev, ei->res);
+ }
+ free(ea->instructions, M_EINJ);
+ }
+
+ if (sc->address_res)
+ apei_unmap_register(sc->dev, sc->address_res);
+ if (sc->vendor_res)
+ apei_unmap_register(sc->dev, sc->vendor_res);
+ AcpiPutTable((ACPI_TABLE_HEADER *)sc->einj);
+ sx_destroy(&sc->lock);
+}
+
+static int
+einj_probe(device_t dev)
+{
+ device_set_desc(dev, "ACPI Error Injection Interface");
+ return (BUS_PROBE_GENERIC);
+}
+
+static int
+einj_attach(device_t dev)
+{
+ struct einj_softc *sc = device_get_softc(dev);
+ ACPI_TABLE_HEADER *hdr;
+ ACPI_GENERIC_ADDRESS address_gas;
+ ACPI_STATUS status;
+ uint64_t value;
+ int error;
+
+ sc->dev = dev;
+ status = AcpiGetTable(ACPI_SIG_EINJ, 0, &hdr);
+ if (ACPI_FAILURE(status)) {
+ device_printf(dev, "Failed to read " ACPI_SIG_EINJ " table\n");
+ return (ENXIO);
+ }
+ sx_init(&sc->lock, "einj");
+ sc->einj = (ACPI_TABLE_EINJ *)hdr;
+
+ if (!einj_validate_table(sc->einj)) {
+ device_printf(dev, "Invalid " ACPI_SIG_EINJ " table\n");
+ goto out;
+ }
+ if (!einj_parse_table(sc, &address_gas))
+ goto out;
+
+ value = 0;
+ error = einj_execute_action(sc, ACPI_EINJ_GET_ERROR_TYPE, &value);
+ if (error != 0) {
+ device_printf(dev, "Failed to fetch supported error types\n");
+ goto out;
+ }
+ sc->info.error_type = value;
+
+ error = einj_parse_set_error_with_address(sc, &address_gas);
+ if (error != 0)
+ goto out;
+
+ error = acpi_register_ioctl(ACPIIO_EINJ_GET_INFO, einj_ioctl, sc);
+ if (error == 0)
+ error = acpi_register_ioctl(ACPIIO_EINJ_GET_VENDOR, einj_ioctl,
+ sc);
+ if (error == 0)
+ error = acpi_register_ioctl(ACPIIO_EINJ_SET_ERROR, einj_ioctl,
+ sc);
+ if (error != 0) {
+ device_printf(dev, "Failed to register ioctl handler\n");
+ goto out;
+ }
+ return (0);
+out:
+ einj_cleanup(sc);
+ return (ENXIO);
+}
+
+static int
+einj_detach(device_t dev)
+{
+ struct einj_softc *sc = device_get_softc(dev);
+
+ einj_cleanup(sc);
+ return (0);
+}
+
+static device_method_t einj_methods[] = {
+ DEVMETHOD(device_identify, einj_identify),
+ DEVMETHOD(device_probe, einj_probe),
+ DEVMETHOD(device_attach, einj_attach),
+ DEVMETHOD(device_detach, einj_detach),
+ DEVMETHOD_END
+};
+
+static driver_t einj_driver = {
+ "einj",
+ einj_methods,
+ sizeof(struct einj_softc)
+};
+
+DRIVER_MODULE(einj, apei, einj_driver, NULL, NULL);
+MODULE_DEPEND(einj, acpi, 1, 1, 1);
diff --git a/sys/dev/acpica/acpiio.h b/sys/dev/acpica/acpiio.h
index 4df049ed196a..4c85ad244462 100644
--- a/sys/dev/acpica/acpiio.h
+++ b/sys/dev/acpica/acpiio.h
@@ -28,6 +28,10 @@
#ifndef _ACPIIO_H_
#define _ACPIIO_H_
+#ifndef _KERNEL
+#include <stdbool.h>
+#endif
+
/*
* Core ACPI subsystem ioctls
*/
@@ -201,6 +205,41 @@ union acpi_battery_ioctl_arg {
/* Get AC adapter status. */
#define ACPIIO_ACAD_GET_STATUS _IOR('A', 1, int)
+/* Error injection commands. */
+
+/* Flags in ACPI_EINJ_ERROR_TYPE_WITH_ADDR.Flags */
+#define ACPI_EINJ_APICID_VALID 0x00000001
+#define ACPI_EINJ_MEMADDRESS_VALID 0x00000002
+#define ACPI_EINJ_PCIE_VALID 0x00000004
+
+struct acpi_einj_info {
+ uint32_t error_type; /* Value from GET_ERROR_TYPE. */
+
+ uint32_t vendor_length;
+
+ /* SET_ERROR_TYPE_WITH_ADDRESS supported. */
+ bool error_address;
+};
+
+struct acpi_einj_vendor_info {
+ void *buf;
+ uint32_t len;
+};
+
+struct acpi_einj_error {
+ uint32_t error_type;
+
+ uint32_t address_flags; /* ACPI_EINJ_*_VALID */
+ uint32_t apic_id;
+ uint64_t memory_address;
+ uint64_t memory_range;
+ uint32_t pcie_id;
+};
+
+#define ACPIIO_EINJ_GET_INFO _IOR('E', 1, struct acpi_einj_info)
+#define ACPIIO_EINJ_GET_VENDOR _IOW('E', 2, struct acpi_einj_vendor_info)
+#define ACPIIO_EINJ_SET_ERROR _IOW('E', 3, struct acpi_einj_error)
+
#ifdef _KERNEL
typedef int (*acpi_ioctl_fn)(u_long cmd, caddr_t addr, void *arg);
extern int acpi_register_ioctl(u_long cmd, acpi_ioctl_fn fn, void *arg);
diff --git a/sys/modules/acpi/Makefile b/sys/modules/acpi/Makefile
index 265e6bd6cdcb..25ce293a9fa5 100644
--- a/sys/modules/acpi/Makefile
+++ b/sys/modules/acpi/Makefile
@@ -17,6 +17,7 @@ SUBDIR= \
.endif
SUBDIR+= \
+ acpi_einj \
acpi_ged
.include <bsd.subdir.mk>
diff --git a/sys/modules/acpi/acpi_einj/Makefile b/sys/modules/acpi/acpi_einj/Makefile
new file mode 100644
index 000000000000..14ff9ac1eac3
--- /dev/null
+++ b/sys/modules/acpi/acpi_einj/Makefile
@@ -0,0 +1,7 @@
+.PATH: ${SRCTOP}/sys/dev/acpica
+
+KMOD= acpi_einj
+SRCS= acpi_einj.c
+SRCS+= opt_acpi.h apei_if.h bus_if.h device_if.h
+
+.include <bsd.kmod.mk>