mirror of
https://github.com/freebsd/freebsd-src.git
synced 2026-11-07 04:36:52 -10:00
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:
1 parent
bc49842769
commit
81bab70dc6
4 files changed
+334
-1
No files matched your search
@@ -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>
|
||||
@@ -0,0 +1,4 @@
|
||||
PROG= einj
|
||||
MAN= einj.8
|
||||
|
||||
.include <bsd.prog.mk>
|
||||
@@ -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 .
|
||||
@@ -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);
|
||||
}
|
||||
Reference in new issue
Block a user