git: 81bab70dc620 - main - einj: Tool to manage APEI error injection

From: John Baldwin <jhb_at_FreeBSD.org>
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);
+}