diff options
| -rw-r--r-- | share/man/man4/Makefile | 1 | ||||
| -rw-r--r-- | share/man/man4/acpi_einj.4 | 58 | ||||
| -rw-r--r-- | sys/dev/acpica/acpi_einj.c | 777 | ||||
| -rw-r--r-- | sys/dev/acpica/acpiio.h | 39 | ||||
| -rw-r--r-- | sys/modules/acpi/Makefile | 1 | ||||
| -rw-r--r-- | sys/modules/acpi/acpi_einj/Makefile | 7 |
6 files changed, 883 insertions, 0 deletions
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> |
