git: 81bab70dc620 - main - einj: Tool to manage APEI error injection
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Tue, 07 Jul 2026 19:18:19 UTC
The branch main has been updated by jhb:
URL: https://cgit.FreeBSD.org/src/commit/?id=81bab70dc6205032e58d15a87ccc8549f900316d
commit 81bab70dc6205032e58d15a87ccc8549f900316d
Author: John Baldwin <jhb@FreeBSD.org>
AuthorDate: 2026-07-07 18:15:49 +0000
Commit: John Baldwin <jhb@FreeBSD.org>
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 <jhb@FreeBSD.org>
+ */
+
+#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);
+}