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);
+}

Reply via email to