From nobody Tue Jul 7 19:18:19 2026 X-Original-To: dev-commits-src-all@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4gvrbv4WJVz6kKfX for ; Tue, 07 Jul 2026 19:18:19 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "YR1" (not verified)) by mx1.freebsd.org (Postfix) with ESMTPS id 4gvrbv2Yvgz3Qp8 for ; Tue, 07 Jul 2026 19:18:19 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1783451899; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=aKVzQohJPIG86EpZWUj7Kt2C7a6G/A0//yaH/c+r7eQ=; b=Jksdro/SiokF4oSBJCsRG7dM2avg607KjygHMtwyNuwEEqYUiqqNBrc9AfZGblv5lx2Eab trBr2mLyA56/elaDj2tvG/HW0INyBG5dbFXhsZahlKXozPmjXKgnuSq8DJPL9J6BNSDqk7 ewDZuDyTIAexDQRGgPT7iUpsuTUSRh9oqMTkQ/0qbfU93YICjCWOKVVGxpku+nNww1zYZx jrGcjMbEUn4Zblmq1X0S122AjTXhTnG1I64DBLDvV3F7N1JnMZUqmW4mW9m8UN8K6VLYmk FvaRtpYIp/31CI/bKo6lEdG1rLcOTjLQoKcx+sCAHiE0h0N51h3fp/KCoiJKtA== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1783451899; a=rsa-sha256; cv=none; b=Mo2D2/l/uA5PwLdLj5U+h42p9l/2NbVo+u4jU+LHFMEjs+0bzQ/ogJsaqlJOD3AOE+aaKR /SuDhXVuOagMpNDzvh5cMaQnxBcBj8U8WkSjxANM+V8RSxDQhC2JAQoCJqTMovy0X1sHhF k6LHvvrNTG2dIcOriuSV4PIJ8bqxVDtqJ+G2cNisE6l+Yv4Oi+T7o1008xYwTqdAZIAbjl Wdo0Yn4UjtyRBo/iuMHSo/x2dzbd2wxZjg7ArqyKK+TL0I7M+/37RixOqpjA8iT/3039sO SQ6CDhsJ/BefF06jKCjtnvqu//W231O/hMM+/8eJMTuIJDbeDj0LEE3HZNxFwA== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1783451899; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=aKVzQohJPIG86EpZWUj7Kt2C7a6G/A0//yaH/c+r7eQ=; b=sKK5UwWGgAoN8cgrksU3NccigQ+XfFjD8epXXbXeVrTXi/qDdM48fW1l31N78kKCiym+l2 KBBa1Rwfnqf33cZrFWBODKlcR/LaCmqkaCzXfWZGML5t7plipS5KWFOwwFwYTkqT5DHY8j eLO5KoM4a95NDKOZ1uScKqtFA8bBRRIMHoRpsyFRvsmGrOWnY92KKku7mHKXvPm6imrwKf vzyAa9Fk4hGKFnoP8hYSDmbVrG5BPMyxBxtZ9hDuQ8R+w9C9owTiBmE6FPTv+bTYBQRfd7 a+IapDPs+9mF3xtVkjNP5opfyXKVzORHn18Vs4NZIA+aFe+2n9IdFyIZJDUgmQ== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4gvrbv1DsgzpJh for ; Tue, 07 Jul 2026 19:18:19 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 3e12e by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Tue, 07 Jul 2026 19:18:19 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: John Baldwin Subject: git: 81bab70dc620 - main - einj: Tool to manage APEI error injection List-Id: Commit messages for all branches of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-all List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-all@freebsd.org Sender: owner-dev-commits-src-all@FreeBSD.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: jhb X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: 81bab70dc6205032e58d15a87ccc8549f900316d Auto-Submitted: auto-generated Date: Tue, 07 Jul 2026 19:18:19 +0000 Message-Id: <6a4d50fb.3e12e.3c22b58d@gitrepo.freebsd.org> The branch main has been updated by jhb: URL: https://cgit.FreeBSD.org/src/commit/?id=81bab70dc6205032e58d15a87ccc8549f900316d commit 81bab70dc6205032e58d15a87ccc8549f900316d Author: John Baldwin AuthorDate: 2026-07-07 18:15:49 +0000 Commit: John Baldwin 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 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 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 + */ + +#include +#include +#include +#include +#include +#include +#include + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +#include +#include +#pragma GCC diagnostic pop + +#include +#include + +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
-C -P \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); +}