xref: /freebsd/usr.sbin/acpi/einj/einj.c (revision 81bab70dc6205032e58d15a87ccc8549f900316d)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2025 Netflix, Inc.
5  * Written by: John Baldwin <jhb@FreeBSD.org>
6  */
7 
8 #include <sys/param.h>
9 #include <sys/ioctl.h>
10 #include <err.h>
11 #include <fcntl.h>
12 #include <inttypes.h>
13 #include <stdio.h>
14 #include <stdlib.h>
15 
16 #pragma GCC diagnostic push
17 #pragma GCC diagnostic ignored "-Wunused-parameter"
18 #include <contrib/dev/acpica/include/acpi.h>
19 #include <contrib/dev/acpica/include/actbl1.h>
20 #pragma GCC diagnostic pop
21 
22 #include <dev/acpica/acpiio.h>
23 #include <dev/pci/pcireg.h>
24 
25 static int fd;
26 
27 static struct error_type_info {
28 	const char *name;
29 	uint32_t bit;
30 	const char *description;
31 } error_types[] = {
32 	{ "cpu.cor", ACPI_EINJ_PROCESSOR_CORRECTABLE, "Processor Correctable" },
33 	{ "cpu.uc", ACPI_EINJ_PROCESSOR_UNCORRECTABLE,
34 	  "Processor Uncorrectable Non-Fatal" },
35 	{ "cpu.fatal", ACPI_EINJ_PROCESSOR_FATAL,
36 	  "Processor Uncorrectable Fatal" },
37 	{ "mem.cor", ACPI_EINJ_MEMORY_CORRECTABLE, "Memory Correctable" },
38 	{ "mem.uc", ACPI_EINJ_MEMORY_UNCORRECTABLE,
39 	  "Memory Uncorrectable Non-Fatal" },
40 	{ "mem.fatal", ACPI_EINJ_MEMORY_FATAL, "Memory Uncorrectable Fatal" },
41 	{ "pcie.cor", ACPI_EINJ_PCIX_CORRECTABLE, "PCI Express Correctable" },
42 	{ "pcie.uc", ACPI_EINJ_PCIX_UNCORRECTABLE,
43 	  "PCI Express Uncorrectable Non-Fatal" },
44 	{ "pcie.fatal", ACPI_EINJ_PCIX_FATAL,
45 	  "PCI Express Uncorrectable Fatal" },
46 	{ "platform.cor", ACPI_EINJ_PLATFORM_CORRECTABLE,
47 	  "Platform Correctable" },
48 	{ "platform.uc", ACPI_EINJ_PLATFORM_UNCORRECTABLE,
49 	  "Platform Uncorrectable Non-Fatal" },
50 	{ "platform.fatal", ACPI_EINJ_PLATFORM_FATAL,
51 	  "Platform Uncorrectable Fatal" },
52 	{ "cxl.cache.cor", ACPI_EINJ_CXL_CACHE_CORRECTABLE,
53 	  "CXL.cache Correctable" },
54 	{ "cxl.cache.uc", ACPI_EINJ_CXL_CACHE_UNCORRECTABLE,
55 	  "CXL.cache Uncorrectable Non-Fatal" },
56 	{ "cxl.cache.fatal", ACPI_EINJ_CXL_CACHE_FATAL,
57 	  "CXL.cache Uncorrectable Fatal" },
58 	{ "cxl.mem.cor", ACPI_EINJ_CXL_MEM_CORRECTABLE, "CXL.mem Correctable" },
59 	{ "cxl.mem.uc", ACPI_EINJ_CXL_MEM_UNCORRECTABLE,
60 	  "CXL.mem Uncorrectable Non-Fatal" },
61 	{ "cxl.mem.fatal", ACPI_EINJ_CXL_MEM_FATAL,
62 	  "CXL.mem Uncorrectable Fatal" },
63 	{ "vendor", ACPI_EINJ_VENDOR_DEFINED, "Vendor Defined" },
64 };
65 
66 static void
usage(void)67 usage(void)
68 {
69 	fprintf(stderr, "usage:\n"
70 	    "\teinj list\n"
71 	    "\teinj inject -a <address> -C <cpu> -P <dbsf> <type>\n");
72 	exit(1);
73 }
74 
75 static void
print_vendor_struct(uint32_t vendor_length)76 print_vendor_struct(uint32_t vendor_length)
77 {
78 	struct acpi_einj_vendor_info vi;
79 	ACPI_EINJ_VENDOR *vendor;
80 	uint32_t dbsf;
81 
82 	vi.buf = malloc(vendor_length);
83 	vi.len = vendor_length;
84 	if (ioctl(fd, ACPIIO_EINJ_GET_VENDOR, &vi) == -1) {
85 		warn("ACPIIO_EINJ_GET_VENDOR");
86 		free(vi.buf);
87 		return;
88 	}
89 
90 	vendor = vi.buf;
91 	dbsf = vendor->PcieId;
92 
93 	printf("Vendor Extension:\n");
94 	printf("\tLength:\t%u\n", vendor->Length);
95 	printf("\tPCIe Id:\t%u:%u:%u:%u\n", dbsf >> 24,
96 	    (dbsf >> 16) & 0xff, (dbsf >> 11) & 0x1f, (dbsf >> 8) & 7);
97 	printf("\tVendor Id:\t%04x\n", vendor->VendorId);
98 	printf("\tDevice Id:\t%04x\n", vendor->DeviceId);
99 	printf("\tRevision:\t%02x\n", vendor->RevisionId);
100 	free(vi.buf);
101 }
102 
103 static void
list_error_types(void)104 list_error_types(void)
105 {
106 	struct acpi_einj_info info;
107 
108 	if (ioctl(fd, ACPIIO_EINJ_GET_INFO, &info) == -1)
109 		err(1, "ACPIIO_EINJ_GET_INFO");
110 
111 	printf("Supported errors <%#x>:\n", info.error_type);
112 	for (u_int i = 0; i < nitems(error_types); i++) {
113 		struct error_type_info *eti = &error_types[i];
114 
115 		if ((info.error_type & eti->bit) == 0)
116 			continue;
117 		printf("\t%s (%s)\n", eti->name, eti->description);
118 	}
119 
120 	if ((info.error_type & ACPI_EINJ_VENDOR_DEFINED) != 0 &&
121 	    info.vendor_length != 0) {
122 		printf("\n");
123 		print_vendor_struct(info.vendor_length);
124 	}
125 }
126 
127 static uint32_t
parse_dbsf(const char * arg)128 parse_dbsf(const char *arg)
129 {
130 	char *cp;
131 	unsigned long val[4];
132 	u_int i;
133 
134 	/*
135 	 * Permit the same format as pciconf.  Eventually this should
136 	 * accept PCI device names, too.
137 	 */
138 	if (strncmp(arg, "pci", 3) == 0)
139 		arg += 3;
140 
141 	/* Read up to 4 colon-separated values. */
142 	for (i = 0;; i++) {
143 		val[i] = strtoul(arg, &cp, 10);
144 		if (cp == arg || val[i] > 255)
145 			goto error;
146 		if (*cp == ':') {
147 			if (i == nitems(val) - 1)
148 				goto error;
149 			arg = cp + 1;
150 			continue;
151 		}
152 		if (*cp == '\0') {
153 			if (i < 3)
154 				goto error;
155 			break;
156 		}
157 		goto error;
158 	}
159 
160 	/* If no domain is given, assume a domain of 0. */
161 	if (i == 3) {
162 		val[3] = val[2];
163 		val[2] = val[1];
164 		val[1] = val[0];
165 		val[0] = 0;
166 	}
167 
168 	/*
169 	 * To handle ARI, only limit function numbers if the slot is
170 	 * non-zero.
171 	 */
172 	if (val[2] != 0 && val[3] > PCI_FUNCMAX)
173 		goto error;
174 
175 	return (val[0] << 24 | val[1] << 16 | val[2] << 3 | val[3]);
176 
177 error:
178 	errx(1, "invalid PCIe ID");
179 }
180 
181 static void
inject_error(int argc,char * argv[])182 inject_error(int argc, char *argv[])
183 {
184 	struct acpi_einj_error einj;
185 	char *cp;
186 	uintmax_t value;
187 	int ch;
188 
189 	memset(&einj, 0, sizeof(einj));
190 	while ((ch = getopt(argc, argv, "a:C:P:")) != -1) {
191 		switch (ch) {
192 		case 'a':
193 			einj.memory_address = strtoumax(optarg, &cp, 0);
194 			if (*cp != '\0')
195 				errx(1, "invalid address");
196 			einj.address_flags |= ACPI_EINJ_MEMADDRESS_VALID;
197 			break;
198 		case 'C':
199 			value = strtoumax(optarg, &cp, 0);
200 			if (*cp != '\0' || value > UINT32_MAX)
201 				errx(1, "invalid APIC ID");
202 			einj.apic_id = value;
203 			einj.address_flags |= ACPI_EINJ_APICID_VALID;
204 			break;
205 		case 'P':
206 			einj.pcie_id = parse_dbsf(optarg);
207 			einj.address_flags |= ACPI_EINJ_PCIE_VALID;
208 			break;
209 		default:
210 			usage();
211 		}
212 	}
213 	argc -= optind;
214 	argv += optind;
215 
216 	if (argc != 1)
217 		usage();
218 
219 	if (strcmp(argv[0], "vendor") == 0)
220 		errx(1, "vendor errors are not supported");
221 
222 	for (u_int i = 0; i < nitems(error_types); i++) {
223 		struct error_type_info *eti = &error_types[i];
224 
225 		if (strcmp(argv[0], eti->name) == 0) {
226 			einj.error_type = eti->bit;
227 			break;
228 		}
229 	}
230 	if (einj.error_type == 0)
231 		errx(1, "unknown error type");
232 
233 	if (ioctl(fd, ACPIIO_EINJ_SET_ERROR, &einj) == -1)
234 		err(1, "ACPIIO_EINJ_SET_ERROR");
235 }
236 
237 int
main(int argc,char * argv[])238 main(int argc, char *argv[])
239 {
240 	if (argc < 2)
241 		usage();
242 
243 	fd = open("/dev/acpi", O_RDWR);
244 	if (fd == -1)
245 		err(1, "/dev/acpi");
246 
247 	if (strcasecmp(argv[1], "list") == 0)
248 		list_error_types();
249 	else if (strcasecmp(argv[1], "inject") == 0)
250 		inject_error(argc - 1, argv + 1);
251 	else
252 		usage();
253 
254 	close(fd);
255 	return (0);
256 }
257