The branch main has been updated by jhb: URL: https://cgit.FreeBSD.org/src/commit/?id=81bab70dc6205032e58d15a87ccc8549f900316d
commit 81bab70dc6205032e58d15a87ccc8549f900316d Author: John Baldwin <[email protected]> AuthorDate: 2026-07-07 18:15:49 +0000 Commit: John Baldwin <[email protected]> CommitDate: 2026-07-07 18:15:49 +0000 einj: Tool to manage APEI error injection This tool supports two commands. The list command outputs a summary of injectable errors supported by the current system. The inject command injects the requested error. Reviewed by: gallatin, imp Sponsored by: Netflix Differential Revision: https://reviews.freebsd.org/D58026 --- usr.sbin/acpi/Makefile | 2 +- usr.sbin/acpi/einj/Makefile | 4 + usr.sbin/acpi/einj/einj.8 | 73 +++++++++++++ usr.sbin/acpi/einj/einj.c | 256 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 334 insertions(+), 1 deletion(-) diff --git a/usr.sbin/acpi/Makefile b/usr.sbin/acpi/Makefile index c44a4146f44b..fb1216b43f60 100644 --- a/usr.sbin/acpi/Makefile +++ b/usr.sbin/acpi/Makefile @@ -1,6 +1,6 @@ # Makefile for acpi tools # $Id: Makefile,v 1.1 2000/07/14 18:16:22 iwasaki Exp $ -SUBDIR= acpiconf acpidb acpidump iasl +SUBDIR= acpiconf acpidb acpidump einj iasl .include <bsd.subdir.mk> diff --git a/usr.sbin/acpi/einj/Makefile b/usr.sbin/acpi/einj/Makefile new file mode 100644 index 000000000000..4a0a9900f3b1 --- /dev/null +++ b/usr.sbin/acpi/einj/Makefile @@ -0,0 +1,4 @@ +PROG= einj +MAN= einj.8 + +.include <bsd.prog.mk> diff --git a/usr.sbin/acpi/einj/einj.8 b/usr.sbin/acpi/einj/einj.8 new file mode 100644 index 000000000000..06ac0988158a --- /dev/null +++ b/usr.sbin/acpi/einj/einj.8 @@ -0,0 +1,73 @@ +.\" +.\" Copyright (c) 2025 Netflix, Inc. +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.Dd December 17, 2025 +.Dt EINJ 8 +.Os +.Sh NAME +.Nm einj +.Nd ACPI Platform Error Interface error injection +.Sh SYNOPSIS +.Nm +.Cm list +.Nm +.Cm inject +.Op Fl a Ar address +.Op Fl C Ar APIC ID +.Oo Fl P Ar device +.Sm off +.Cm pci Oo Ar domain \&: Oc Ar bus \&: Ar slot \&: Ar function +.Sm on +.Oc +.Ar type +.Sh DESCRIPTION +The +.Nm +utility provides a command line interface to the ACPI Platform Error Interface +.Pq APEI +error injection facility provided by +.Xr acpi_einj 4 . +.Pp +The +.Cm list +command outputs the set of platform errors that can be injected on the +current host. +.Pp +The +.Cm inject +command injects a single platform error. +The +.Ar type +argument specifies the type of error to inject, +for example +.Dq mem.cor +to inject a correctable memory error. +Vendor-specific errors are not supported. +.Pp +The +.Ar address +argument sets a specific address to use with memory errors. +.Pp +The +.Ar APIC ID +argument sets a physical processor ID for processor errors. +.Pp +The +.Ar device +argument sets the target for PCI device errors. +The argument must follow the format +.Sm off +.Cm pci Oo Ar domain \&: Oc Ar bus \&: Ar slot \&: Ar function +.Sm on . +.Sh EXAMPLES +Inject a fatal uncorrectable memory error for the physical address 0x12345678: +.Pp +.Dl einj inject -a 0x12345678 mem.fatal +.Sh SEE ALSO +.Xr acpi_einj 4 +.Sh HISTORY +The +.Nm +utility first appeared in +.Fx 16.0 . diff --git a/usr.sbin/acpi/einj/einj.c b/usr.sbin/acpi/einj/einj.c new file mode 100644 index 000000000000..38e0d129a2e5 --- /dev/null +++ b/usr.sbin/acpi/einj/einj.c @@ -0,0 +1,256 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 Netflix, Inc. + * Written by: John Baldwin <[email protected]> + */ + +#include <sys/param.h> +#include <sys/ioctl.h> +#include <err.h> +#include <fcntl.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +#include <contrib/dev/acpica/include/acpi.h> +#include <contrib/dev/acpica/include/actbl1.h> +#pragma GCC diagnostic pop + +#include <dev/acpica/acpiio.h> +#include <dev/pci/pcireg.h> + +static int fd; + +static struct error_type_info { + const char *name; + uint32_t bit; + const char *description; +} error_types[] = { + { "cpu.cor", ACPI_EINJ_PROCESSOR_CORRECTABLE, "Processor Correctable" }, + { "cpu.uc", ACPI_EINJ_PROCESSOR_UNCORRECTABLE, + "Processor Uncorrectable Non-Fatal" }, + { "cpu.fatal", ACPI_EINJ_PROCESSOR_FATAL, + "Processor Uncorrectable Fatal" }, + { "mem.cor", ACPI_EINJ_MEMORY_CORRECTABLE, "Memory Correctable" }, + { "mem.uc", ACPI_EINJ_MEMORY_UNCORRECTABLE, + "Memory Uncorrectable Non-Fatal" }, + { "mem.fatal", ACPI_EINJ_MEMORY_FATAL, "Memory Uncorrectable Fatal" }, + { "pcie.cor", ACPI_EINJ_PCIX_CORRECTABLE, "PCI Express Correctable" }, + { "pcie.uc", ACPI_EINJ_PCIX_UNCORRECTABLE, + "PCI Express Uncorrectable Non-Fatal" }, + { "pcie.fatal", ACPI_EINJ_PCIX_FATAL, + "PCI Express Uncorrectable Fatal" }, + { "platform.cor", ACPI_EINJ_PLATFORM_CORRECTABLE, + "Platform Correctable" }, + { "platform.uc", ACPI_EINJ_PLATFORM_UNCORRECTABLE, + "Platform Uncorrectable Non-Fatal" }, + { "platform.fatal", ACPI_EINJ_PLATFORM_FATAL, + "Platform Uncorrectable Fatal" }, + { "cxl.cache.cor", ACPI_EINJ_CXL_CACHE_CORRECTABLE, + "CXL.cache Correctable" }, + { "cxl.cache.uc", ACPI_EINJ_CXL_CACHE_UNCORRECTABLE, + "CXL.cache Uncorrectable Non-Fatal" }, + { "cxl.cache.fatal", ACPI_EINJ_CXL_CACHE_FATAL, + "CXL.cache Uncorrectable Fatal" }, + { "cxl.mem.cor", ACPI_EINJ_CXL_MEM_CORRECTABLE, "CXL.mem Correctable" }, + { "cxl.mem.uc", ACPI_EINJ_CXL_MEM_UNCORRECTABLE, + "CXL.mem Uncorrectable Non-Fatal" }, + { "cxl.mem.fatal", ACPI_EINJ_CXL_MEM_FATAL, + "CXL.mem Uncorrectable Fatal" }, + { "vendor", ACPI_EINJ_VENDOR_DEFINED, "Vendor Defined" }, +}; + +static void +usage(void) +{ + fprintf(stderr, "usage:\n" + "\teinj list\n" + "\teinj inject -a <address> -C <cpu> -P <dbsf> <type>\n"); + exit(1); +} + +static void +print_vendor_struct(uint32_t vendor_length) +{ + struct acpi_einj_vendor_info vi; + ACPI_EINJ_VENDOR *vendor; + uint32_t dbsf; + + vi.buf = malloc(vendor_length); + vi.len = vendor_length; + if (ioctl(fd, ACPIIO_EINJ_GET_VENDOR, &vi) == -1) { + warn("ACPIIO_EINJ_GET_VENDOR"); + free(vi.buf); + return; + } + + vendor = vi.buf; + dbsf = vendor->PcieId; + + printf("Vendor Extension:\n"); + printf("\tLength:\t%u\n", vendor->Length); + printf("\tPCIe Id:\t%u:%u:%u:%u\n", dbsf >> 24, + (dbsf >> 16) & 0xff, (dbsf >> 11) & 0x1f, (dbsf >> 8) & 7); + printf("\tVendor Id:\t%04x\n", vendor->VendorId); + printf("\tDevice Id:\t%04x\n", vendor->DeviceId); + printf("\tRevision:\t%02x\n", vendor->RevisionId); + free(vi.buf); +} + +static void +list_error_types(void) +{ + struct acpi_einj_info info; + + if (ioctl(fd, ACPIIO_EINJ_GET_INFO, &info) == -1) + err(1, "ACPIIO_EINJ_GET_INFO"); + + printf("Supported errors <%#x>:\n", info.error_type); + for (u_int i = 0; i < nitems(error_types); i++) { + struct error_type_info *eti = &error_types[i]; + + if ((info.error_type & eti->bit) == 0) + continue; + printf("\t%s (%s)\n", eti->name, eti->description); + } + + if ((info.error_type & ACPI_EINJ_VENDOR_DEFINED) != 0 && + info.vendor_length != 0) { + printf("\n"); + print_vendor_struct(info.vendor_length); + } +} + +static uint32_t +parse_dbsf(const char *arg) +{ + char *cp; + unsigned long val[4]; + u_int i; + + /* + * Permit the same format as pciconf. Eventually this should + * accept PCI device names, too. + */ + if (strncmp(arg, "pci", 3) == 0) + arg += 3; + + /* Read up to 4 colon-separated values. */ + for (i = 0;; i++) { + val[i] = strtoul(arg, &cp, 10); + if (cp == arg || val[i] > 255) + goto error; + if (*cp == ':') { + if (i == nitems(val) - 1) + goto error; + arg = cp + 1; + continue; + } + if (*cp == '\0') { + if (i < 3) + goto error; + break; + } + goto error; + } + + /* If no domain is given, assume a domain of 0. */ + if (i == 3) { + val[3] = val[2]; + val[2] = val[1]; + val[1] = val[0]; + val[0] = 0; + } + + /* + * To handle ARI, only limit function numbers if the slot is + * non-zero. + */ + if (val[2] != 0 && val[3] > PCI_FUNCMAX) + goto error; + + return (val[0] << 24 | val[1] << 16 | val[2] << 3 | val[3]); + +error: + errx(1, "invalid PCIe ID"); +} + +static void +inject_error(int argc, char *argv[]) +{ + struct acpi_einj_error einj; + char *cp; + uintmax_t value; + int ch; + + memset(&einj, 0, sizeof(einj)); + while ((ch = getopt(argc, argv, "a:C:P:")) != -1) { + switch (ch) { + case 'a': + einj.memory_address = strtoumax(optarg, &cp, 0); + if (*cp != '\0') + errx(1, "invalid address"); + einj.address_flags |= ACPI_EINJ_MEMADDRESS_VALID; + break; + case 'C': + value = strtoumax(optarg, &cp, 0); + if (*cp != '\0' || value > UINT32_MAX) + errx(1, "invalid APIC ID"); + einj.apic_id = value; + einj.address_flags |= ACPI_EINJ_APICID_VALID; + break; + case 'P': + einj.pcie_id = parse_dbsf(optarg); + einj.address_flags |= ACPI_EINJ_PCIE_VALID; + break; + default: + usage(); + } + } + argc -= optind; + argv += optind; + + if (argc != 1) + usage(); + + if (strcmp(argv[0], "vendor") == 0) + errx(1, "vendor errors are not supported"); + + for (u_int i = 0; i < nitems(error_types); i++) { + struct error_type_info *eti = &error_types[i]; + + if (strcmp(argv[0], eti->name) == 0) { + einj.error_type = eti->bit; + break; + } + } + if (einj.error_type == 0) + errx(1, "unknown error type"); + + if (ioctl(fd, ACPIIO_EINJ_SET_ERROR, &einj) == -1) + err(1, "ACPIIO_EINJ_SET_ERROR"); +} + +int +main(int argc, char *argv[]) +{ + if (argc < 2) + usage(); + + fd = open("/dev/acpi", O_RDWR); + if (fd == -1) + err(1, "/dev/acpi"); + + if (strcasecmp(argv[1], "list") == 0) + list_error_types(); + else if (strcasecmp(argv[1], "inject") == 0) + inject_error(argc - 1, argv + 1); + else + usage(); + + close(fd); + return (0); +}
