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
This commit is contained in:
John Baldwin committed 2026-07-07 14:15:49 -04:00
1 parent bc49842769
commit 81bab70dc6
4 files changed
+334 -1

No files matched your search

+1 -1
View File
@@ -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>
+4
View File
@@ -0,0 +1,4 @@
PROG= einj
MAN= einj.8
.include <bsd.prog.mk>
+73
View File
@@ -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 .
+256
View File
@@ -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);
}