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