1 | // SPDX-License-Identifier: GPL-2.0 |
---|---|
2 | /* |
3 | * UEFI Common Platform Error Record (CPER) support |
4 | * |
5 | * Copyright (C) 2010, Intel Corp. |
6 | * Author: Huang Ying <ying.huang@intel.com> |
7 | * |
8 | * CPER is the format used to describe platform hardware error by |
9 | * various tables, such as ERST, BERT and HEST etc. |
10 | * |
11 | * For more information about CPER, please refer to Appendix N of UEFI |
12 | * Specification version 2.4. |
13 | */ |
14 | |
15 | #include <linux/kernel.h> |
16 | #include <linux/module.h> |
17 | #include <linux/time.h> |
18 | #include <linux/cper.h> |
19 | #include <linux/dmi.h> |
20 | #include <linux/acpi.h> |
21 | #include <linux/pci.h> |
22 | #include <linux/aer.h> |
23 | #include <linux/printk.h> |
24 | #include <linux/bcd.h> |
25 | #include <acpi/ghes.h> |
26 | #include <ras/ras_event.h> |
27 | #include <cxl/event.h> |
28 | |
29 | /* |
30 | * CPER record ID need to be unique even after reboot, because record |
31 | * ID is used as index for ERST storage, while CPER records from |
32 | * multiple boot may co-exist in ERST. |
33 | */ |
34 | u64 cper_next_record_id(void) |
35 | { |
36 | static atomic64_t seq; |
37 | |
38 | if (!atomic64_read(v: &seq)) { |
39 | time64_t time = ktime_get_real_seconds(); |
40 | |
41 | /* |
42 | * This code is unlikely to still be needed in year 2106, |
43 | * but just in case, let's use a few more bits for timestamps |
44 | * after y2038 to be sure they keep increasing monotonically |
45 | * for the next few hundred years... |
46 | */ |
47 | if (time < 0x80000000) |
48 | atomic64_set(v: &seq, i: (ktime_get_real_seconds()) << 32); |
49 | else |
50 | atomic64_set(v: &seq, i: 0x8000000000000000ull | |
51 | ktime_get_real_seconds() << 24); |
52 | } |
53 | |
54 | return atomic64_inc_return(v: &seq); |
55 | } |
56 | EXPORT_SYMBOL_GPL(cper_next_record_id); |
57 | |
58 | static const char * const severity_strs[] = { |
59 | "recoverable", |
60 | "fatal", |
61 | "corrected", |
62 | "info", |
63 | }; |
64 | |
65 | const char *cper_severity_str(unsigned int severity) |
66 | { |
67 | return severity < ARRAY_SIZE(severity_strs) ? |
68 | severity_strs[severity] : "unknown"; |
69 | } |
70 | EXPORT_SYMBOL_GPL(cper_severity_str); |
71 | |
72 | /* |
73 | * cper_print_bits - print strings for set bits |
74 | * @pfx: prefix for each line, including log level and prefix string |
75 | * @bits: bit mask |
76 | * @strs: string array, indexed by bit position |
77 | * @strs_size: size of the string array: @strs |
78 | * |
79 | * For each set bit in @bits, print the corresponding string in @strs. |
80 | * If the output length is longer than 80, multiple line will be |
81 | * printed, with @pfx is printed at the beginning of each line. |
82 | */ |
83 | void cper_print_bits(const char *pfx, unsigned int bits, |
84 | const char * const strs[], unsigned int strs_size) |
85 | { |
86 | int i, len = 0; |
87 | const char *str; |
88 | char buf[84]; |
89 | |
90 | for (i = 0; i < strs_size; i++) { |
91 | if (!(bits & (1U << i))) |
92 | continue; |
93 | str = strs[i]; |
94 | if (!str) |
95 | continue; |
96 | if (len && len + strlen(str) + 2 > 80) { |
97 | printk("%s\n", buf); |
98 | len = 0; |
99 | } |
100 | if (!len) |
101 | len = snprintf(buf, size: sizeof(buf), fmt: "%s%s", pfx, str); |
102 | else |
103 | len += scnprintf(buf: buf+len, size: sizeof(buf)-len, fmt: ", %s", str); |
104 | } |
105 | if (len) |
106 | printk("%s\n", buf); |
107 | } |
108 | |
109 | static const char * const proc_type_strs[] = { |
110 | "IA32/X64", |
111 | "IA64", |
112 | "ARM", |
113 | }; |
114 | |
115 | static const char * const proc_isa_strs[] = { |
116 | "IA32", |
117 | "IA64", |
118 | "X64", |
119 | "ARM A32/T32", |
120 | "ARM A64", |
121 | }; |
122 | |
123 | const char * const cper_proc_error_type_strs[] = { |
124 | "cache error", |
125 | "TLB error", |
126 | "bus error", |
127 | "micro-architectural error", |
128 | }; |
129 | |
130 | static const char * const proc_op_strs[] = { |
131 | "unknown or generic", |
132 | "data read", |
133 | "data write", |
134 | "instruction execution", |
135 | }; |
136 | |
137 | static const char * const proc_flag_strs[] = { |
138 | "restartable", |
139 | "precise IP", |
140 | "overflow", |
141 | "corrected", |
142 | }; |
143 | |
144 | static void cper_print_proc_generic(const char *pfx, |
145 | const struct cper_sec_proc_generic *proc) |
146 | { |
147 | if (proc->validation_bits & CPER_PROC_VALID_TYPE) |
148 | printk("%s" "processor_type: %d, %s\n", pfx, proc->proc_type, |
149 | proc->proc_type < ARRAY_SIZE(proc_type_strs) ? |
150 | proc_type_strs[proc->proc_type] : "unknown"); |
151 | if (proc->validation_bits & CPER_PROC_VALID_ISA) |
152 | printk("%s" "processor_isa: %d, %s\n", pfx, proc->proc_isa, |
153 | proc->proc_isa < ARRAY_SIZE(proc_isa_strs) ? |
154 | proc_isa_strs[proc->proc_isa] : "unknown"); |
155 | if (proc->validation_bits & CPER_PROC_VALID_ERROR_TYPE) { |
156 | printk("%s" "error_type: 0x%02x\n", pfx, proc->proc_error_type); |
157 | cper_print_bits(pfx, bits: proc->proc_error_type, |
158 | strs: cper_proc_error_type_strs, |
159 | ARRAY_SIZE(cper_proc_error_type_strs)); |
160 | } |
161 | if (proc->validation_bits & CPER_PROC_VALID_OPERATION) |
162 | printk("%s" "operation: %d, %s\n", pfx, proc->operation, |
163 | proc->operation < ARRAY_SIZE(proc_op_strs) ? |
164 | proc_op_strs[proc->operation] : "unknown"); |
165 | if (proc->validation_bits & CPER_PROC_VALID_FLAGS) { |
166 | printk("%s" "flags: 0x%02x\n", pfx, proc->flags); |
167 | cper_print_bits(pfx, bits: proc->flags, strs: proc_flag_strs, |
168 | ARRAY_SIZE(proc_flag_strs)); |
169 | } |
170 | if (proc->validation_bits & CPER_PROC_VALID_LEVEL) |
171 | printk("%s" "level: %d\n", pfx, proc->level); |
172 | if (proc->validation_bits & CPER_PROC_VALID_VERSION) |
173 | printk("%s" "version_info: 0x%016llx\n", pfx, proc->cpu_version); |
174 | if (proc->validation_bits & CPER_PROC_VALID_ID) |
175 | printk("%s" "processor_id: 0x%016llx\n", pfx, proc->proc_id); |
176 | if (proc->validation_bits & CPER_PROC_VALID_TARGET_ADDRESS) |
177 | printk("%s" "target_address: 0x%016llx\n", |
178 | pfx, proc->target_addr); |
179 | if (proc->validation_bits & CPER_PROC_VALID_REQUESTOR_ID) |
180 | printk("%s" "requestor_id: 0x%016llx\n", |
181 | pfx, proc->requestor_id); |
182 | if (proc->validation_bits & CPER_PROC_VALID_RESPONDER_ID) |
183 | printk("%s" "responder_id: 0x%016llx\n", |
184 | pfx, proc->responder_id); |
185 | if (proc->validation_bits & CPER_PROC_VALID_IP) |
186 | printk("%s" "IP: 0x%016llx\n", pfx, proc->ip); |
187 | } |
188 | |
189 | static const char * const mem_err_type_strs[] = { |
190 | "unknown", |
191 | "no error", |
192 | "single-bit ECC", |
193 | "multi-bit ECC", |
194 | "single-symbol chipkill ECC", |
195 | "multi-symbol chipkill ECC", |
196 | "master abort", |
197 | "target abort", |
198 | "parity error", |
199 | "watchdog timeout", |
200 | "invalid address", |
201 | "mirror Broken", |
202 | "memory sparing", |
203 | "scrub corrected error", |
204 | "scrub uncorrected error", |
205 | "physical memory map-out event", |
206 | }; |
207 | |
208 | const char *cper_mem_err_type_str(unsigned int etype) |
209 | { |
210 | return etype < ARRAY_SIZE(mem_err_type_strs) ? |
211 | mem_err_type_strs[etype] : "unknown"; |
212 | } |
213 | EXPORT_SYMBOL_GPL(cper_mem_err_type_str); |
214 | |
215 | const char *cper_mem_err_status_str(u64 status) |
216 | { |
217 | switch ((status >> 8) & 0xff) { |
218 | case 1: return "Error detected internal to the component"; |
219 | case 4: return "Storage error in DRAM memory"; |
220 | case 5: return "Storage error in TLB"; |
221 | case 6: return "Storage error in cache"; |
222 | case 7: return "Error in one or more functional units"; |
223 | case 8: return "Component failed self test"; |
224 | case 9: return "Overflow or undervalue of internal queue"; |
225 | case 16: return "Error detected in the bus"; |
226 | case 17: return "Virtual address not found on IO-TLB or IO-PDIR"; |
227 | case 18: return "Improper access error"; |
228 | case 19: return "Access to a memory address which is not mapped to any component"; |
229 | case 20: return "Loss of Lockstep"; |
230 | case 21: return "Response not associated with a request"; |
231 | case 22: return "Bus parity error - must also set the A, C, or D Bits"; |
232 | case 23: return "Detection of a protocol error"; |
233 | case 24: return "Detection of a PATH_ERROR"; |
234 | case 25: return "Bus operation timeout"; |
235 | case 26: return "A read was issued to data that has been poisoned"; |
236 | default: return "Reserved"; |
237 | } |
238 | } |
239 | EXPORT_SYMBOL_GPL(cper_mem_err_status_str); |
240 | |
241 | int cper_mem_err_location(struct cper_mem_err_compact *mem, char *msg) |
242 | { |
243 | u32 len, n; |
244 | |
245 | if (!msg) |
246 | return 0; |
247 | |
248 | n = 0; |
249 | len = CPER_REC_LEN; |
250 | if (mem->validation_bits & CPER_MEM_VALID_NODE) |
251 | n += scnprintf(buf: msg + n, size: len - n, fmt: "node:%d ", mem->node); |
252 | if (mem->validation_bits & CPER_MEM_VALID_CARD) |
253 | n += scnprintf(buf: msg + n, size: len - n, fmt: "card:%d ", mem->card); |
254 | if (mem->validation_bits & CPER_MEM_VALID_MODULE) |
255 | n += scnprintf(buf: msg + n, size: len - n, fmt: "module:%d ", mem->module); |
256 | if (mem->validation_bits & CPER_MEM_VALID_RANK_NUMBER) |
257 | n += scnprintf(buf: msg + n, size: len - n, fmt: "rank:%d ", mem->rank); |
258 | if (mem->validation_bits & CPER_MEM_VALID_BANK) |
259 | n += scnprintf(buf: msg + n, size: len - n, fmt: "bank:%d ", mem->bank); |
260 | if (mem->validation_bits & CPER_MEM_VALID_BANK_GROUP) |
261 | n += scnprintf(buf: msg + n, size: len - n, fmt: "bank_group:%d ", |
262 | mem->bank >> CPER_MEM_BANK_GROUP_SHIFT); |
263 | if (mem->validation_bits & CPER_MEM_VALID_BANK_ADDRESS) |
264 | n += scnprintf(buf: msg + n, size: len - n, fmt: "bank_address:%d ", |
265 | mem->bank & CPER_MEM_BANK_ADDRESS_MASK); |
266 | if (mem->validation_bits & CPER_MEM_VALID_DEVICE) |
267 | n += scnprintf(buf: msg + n, size: len - n, fmt: "device:%d ", mem->device); |
268 | if (mem->validation_bits & (CPER_MEM_VALID_ROW | CPER_MEM_VALID_ROW_EXT)) { |
269 | u32 row = mem->row; |
270 | |
271 | row |= cper_get_mem_extension(mem_valid: mem->validation_bits, mem_extended: mem->extended); |
272 | n += scnprintf(buf: msg + n, size: len - n, fmt: "row:%d ", row); |
273 | } |
274 | if (mem->validation_bits & CPER_MEM_VALID_COLUMN) |
275 | n += scnprintf(buf: msg + n, size: len - n, fmt: "column:%d ", mem->column); |
276 | if (mem->validation_bits & CPER_MEM_VALID_BIT_POSITION) |
277 | n += scnprintf(buf: msg + n, size: len - n, fmt: "bit_position:%d ", |
278 | mem->bit_pos); |
279 | if (mem->validation_bits & CPER_MEM_VALID_REQUESTOR_ID) |
280 | n += scnprintf(buf: msg + n, size: len - n, fmt: "requestor_id:0x%016llx ", |
281 | mem->requestor_id); |
282 | if (mem->validation_bits & CPER_MEM_VALID_RESPONDER_ID) |
283 | n += scnprintf(buf: msg + n, size: len - n, fmt: "responder_id:0x%016llx ", |
284 | mem->responder_id); |
285 | if (mem->validation_bits & CPER_MEM_VALID_TARGET_ID) |
286 | n += scnprintf(buf: msg + n, size: len - n, fmt: "target_id:0x%016llx ", |
287 | mem->target_id); |
288 | if (mem->validation_bits & CPER_MEM_VALID_CHIP_ID) |
289 | n += scnprintf(buf: msg + n, size: len - n, fmt: "chip_id:%d ", |
290 | mem->extended >> CPER_MEM_CHIP_ID_SHIFT); |
291 | |
292 | return n; |
293 | } |
294 | EXPORT_SYMBOL_GPL(cper_mem_err_location); |
295 | |
296 | int cper_dimm_err_location(struct cper_mem_err_compact *mem, char *msg) |
297 | { |
298 | u32 len, n; |
299 | const char *bank = NULL, *device = NULL; |
300 | |
301 | if (!msg || !(mem->validation_bits & CPER_MEM_VALID_MODULE_HANDLE)) |
302 | return 0; |
303 | |
304 | len = CPER_REC_LEN; |
305 | dmi_memdev_name(handle: mem->mem_dev_handle, bank: &bank, device: &device); |
306 | if (bank && device) |
307 | n = snprintf(buf: msg, size: len, fmt: "DIMM location: %s %s ", bank, device); |
308 | else |
309 | n = snprintf(buf: msg, size: len, |
310 | fmt: "DIMM location: not present. DMI handle: 0x%.4x ", |
311 | mem->mem_dev_handle); |
312 | |
313 | return n; |
314 | } |
315 | EXPORT_SYMBOL_GPL(cper_dimm_err_location); |
316 | |
317 | void cper_mem_err_pack(const struct cper_sec_mem_err *mem, |
318 | struct cper_mem_err_compact *cmem) |
319 | { |
320 | cmem->validation_bits = mem->validation_bits; |
321 | cmem->node = mem->node; |
322 | cmem->card = mem->card; |
323 | cmem->module = mem->module; |
324 | cmem->bank = mem->bank; |
325 | cmem->device = mem->device; |
326 | cmem->row = mem->row; |
327 | cmem->column = mem->column; |
328 | cmem->bit_pos = mem->bit_pos; |
329 | cmem->requestor_id = mem->requestor_id; |
330 | cmem->responder_id = mem->responder_id; |
331 | cmem->target_id = mem->target_id; |
332 | cmem->extended = mem->extended; |
333 | cmem->rank = mem->rank; |
334 | cmem->mem_array_handle = mem->mem_array_handle; |
335 | cmem->mem_dev_handle = mem->mem_dev_handle; |
336 | } |
337 | EXPORT_SYMBOL_GPL(cper_mem_err_pack); |
338 | |
339 | const char *cper_mem_err_unpack(struct trace_seq *p, |
340 | struct cper_mem_err_compact *cmem) |
341 | { |
342 | const char *ret = trace_seq_buffer_ptr(s: p); |
343 | char rcd_decode_str[CPER_REC_LEN]; |
344 | |
345 | if (cper_mem_err_location(cmem, rcd_decode_str)) |
346 | trace_seq_printf(s: p, fmt: "%s", rcd_decode_str); |
347 | if (cper_dimm_err_location(cmem, rcd_decode_str)) |
348 | trace_seq_printf(s: p, fmt: "%s", rcd_decode_str); |
349 | trace_seq_putc(s: p, c: '\0'); |
350 | |
351 | return ret; |
352 | } |
353 | |
354 | static void cper_print_mem(const char *pfx, const struct cper_sec_mem_err *mem, |
355 | int len) |
356 | { |
357 | struct cper_mem_err_compact cmem; |
358 | char rcd_decode_str[CPER_REC_LEN]; |
359 | |
360 | /* Don't trust UEFI 2.1/2.2 structure with bad validation bits */ |
361 | if (len == sizeof(struct cper_sec_mem_err_old) && |
362 | (mem->validation_bits & ~(CPER_MEM_VALID_RANK_NUMBER - 1))) { |
363 | pr_err(FW_WARN "valid bits set for fields beyond structure\n"); |
364 | return; |
365 | } |
366 | if (mem->validation_bits & CPER_MEM_VALID_ERROR_STATUS) |
367 | printk("%s error_status: %s (0x%016llx)\n", |
368 | pfx, cper_mem_err_status_str(mem->error_status), |
369 | mem->error_status); |
370 | if (mem->validation_bits & CPER_MEM_VALID_PA) |
371 | printk("%s" "physical_address: 0x%016llx\n", |
372 | pfx, mem->physical_addr); |
373 | if (mem->validation_bits & CPER_MEM_VALID_PA_MASK) |
374 | printk("%s" "physical_address_mask: 0x%016llx\n", |
375 | pfx, mem->physical_addr_mask); |
376 | cper_mem_err_pack(mem, &cmem); |
377 | if (cper_mem_err_location(&cmem, rcd_decode_str)) |
378 | printk("%s%s\n", pfx, rcd_decode_str); |
379 | if (mem->validation_bits & CPER_MEM_VALID_ERROR_TYPE) { |
380 | u8 etype = mem->error_type; |
381 | printk("%s" "error_type: %d, %s\n", pfx, etype, |
382 | cper_mem_err_type_str(etype)); |
383 | } |
384 | if (cper_dimm_err_location(&cmem, rcd_decode_str)) |
385 | printk("%s%s\n", pfx, rcd_decode_str); |
386 | } |
387 | |
388 | static const char * const pcie_port_type_strs[] = { |
389 | "PCIe end point", |
390 | "legacy PCI end point", |
391 | "unknown", |
392 | "unknown", |
393 | "root port", |
394 | "upstream switch port", |
395 | "downstream switch port", |
396 | "PCIe to PCI/PCI-X bridge", |
397 | "PCI/PCI-X to PCIe bridge", |
398 | "root complex integrated endpoint device", |
399 | "root complex event collector", |
400 | }; |
401 | |
402 | static void cper_print_pcie(const char *pfx, const struct cper_sec_pcie *pcie, |
403 | const struct acpi_hest_generic_data *gdata) |
404 | { |
405 | if (pcie->validation_bits & CPER_PCIE_VALID_PORT_TYPE) |
406 | printk("%s" "port_type: %d, %s\n", pfx, pcie->port_type, |
407 | pcie->port_type < ARRAY_SIZE(pcie_port_type_strs) ? |
408 | pcie_port_type_strs[pcie->port_type] : "unknown"); |
409 | if (pcie->validation_bits & CPER_PCIE_VALID_VERSION) |
410 | printk("%s" "version: %d.%d\n", pfx, |
411 | pcie->version.major, pcie->version.minor); |
412 | if (pcie->validation_bits & CPER_PCIE_VALID_COMMAND_STATUS) |
413 | printk("%s" "command: 0x%04x, status: 0x%04x\n", pfx, |
414 | pcie->command, pcie->status); |
415 | if (pcie->validation_bits & CPER_PCIE_VALID_DEVICE_ID) { |
416 | const __u8 *p; |
417 | printk("%s" "device_id: %04x:%02x:%02x.%x\n", pfx, |
418 | pcie->device_id.segment, pcie->device_id.bus, |
419 | pcie->device_id.device, pcie->device_id.function); |
420 | printk("%s" "slot: %d\n", pfx, |
421 | pcie->device_id.slot >> CPER_PCIE_SLOT_SHIFT); |
422 | printk("%s" "secondary_bus: 0x%02x\n", pfx, |
423 | pcie->device_id.secondary_bus); |
424 | printk("%s" "vendor_id: 0x%04x, device_id: 0x%04x\n", pfx, |
425 | pcie->device_id.vendor_id, pcie->device_id.device_id); |
426 | p = pcie->device_id.class_code; |
427 | printk("%s" "class_code: %02x%02x%02x\n", pfx, p[2], p[1], p[0]); |
428 | } |
429 | if (pcie->validation_bits & CPER_PCIE_VALID_SERIAL_NUMBER) |
430 | printk("%s" "serial number: 0x%04x, 0x%04x\n", pfx, |
431 | pcie->serial_number.lower, pcie->serial_number.upper); |
432 | if (pcie->validation_bits & CPER_PCIE_VALID_BRIDGE_CONTROL_STATUS) |
433 | printk( |
434 | "%s" "bridge: secondary_status: 0x%04x, control: 0x%04x\n", |
435 | pfx, pcie->bridge.secondary_status, pcie->bridge.control); |
436 | |
437 | /* |
438 | * Print all valid AER info. Record may be from BERT (boot-time) or GHES (run-time). |
439 | * |
440 | * Fatal errors call __ghes_panic() before AER handler prints this. |
441 | */ |
442 | if (pcie->validation_bits & CPER_PCIE_VALID_AER_INFO) { |
443 | struct aer_capability_regs *aer; |
444 | |
445 | aer = (struct aer_capability_regs *)pcie->aer_info; |
446 | printk("%saer_cor_status: 0x%08x, aer_cor_mask: 0x%08x\n", |
447 | pfx, aer->cor_status, aer->cor_mask); |
448 | printk("%saer_uncor_status: 0x%08x, aer_uncor_mask: 0x%08x\n", |
449 | pfx, aer->uncor_status, aer->uncor_mask); |
450 | printk("%saer_uncor_severity: 0x%08x\n", |
451 | pfx, aer->uncor_severity); |
452 | printk("%sTLP Header: %08x %08x %08x %08x\n", pfx, |
453 | aer->header_log.dw[0], aer->header_log.dw[1], |
454 | aer->header_log.dw[2], aer->header_log.dw[3]); |
455 | } |
456 | } |
457 | |
458 | static const char * const fw_err_rec_type_strs[] = { |
459 | "IPF SAL Error Record", |
460 | "SOC Firmware Error Record Type1 (Legacy CrashLog Support)", |
461 | "SOC Firmware Error Record Type2", |
462 | }; |
463 | |
464 | static void cper_print_fw_err(const char *pfx, |
465 | struct acpi_hest_generic_data *gdata, |
466 | const struct cper_sec_fw_err_rec_ref *fw_err) |
467 | { |
468 | void *buf = acpi_hest_get_payload(gdata); |
469 | u32 offset, length = gdata->error_data_length; |
470 | |
471 | printk("%s" "Firmware Error Record Type: %s\n", pfx, |
472 | fw_err->record_type < ARRAY_SIZE(fw_err_rec_type_strs) ? |
473 | fw_err_rec_type_strs[fw_err->record_type] : "unknown"); |
474 | printk("%s" "Revision: %d\n", pfx, fw_err->revision); |
475 | |
476 | /* Record Type based on UEFI 2.7 */ |
477 | if (fw_err->revision == 0) { |
478 | printk("%s" "Record Identifier: %08llx\n", pfx, |
479 | fw_err->record_identifier); |
480 | } else if (fw_err->revision == 2) { |
481 | printk("%s" "Record Identifier: %pUl\n", pfx, |
482 | &fw_err->record_identifier_guid); |
483 | } |
484 | |
485 | /* |
486 | * The FW error record may contain trailing data beyond the |
487 | * structure defined by the specification. As the fields |
488 | * defined (and hence the offset of any trailing data) vary |
489 | * with the revision, set the offset to account for this |
490 | * variation. |
491 | */ |
492 | if (fw_err->revision == 0) { |
493 | /* record_identifier_guid not defined */ |
494 | offset = offsetof(struct cper_sec_fw_err_rec_ref, |
495 | record_identifier_guid); |
496 | } else if (fw_err->revision == 1) { |
497 | /* record_identifier not defined */ |
498 | offset = offsetof(struct cper_sec_fw_err_rec_ref, |
499 | record_identifier); |
500 | } else { |
501 | offset = sizeof(*fw_err); |
502 | } |
503 | |
504 | buf += offset; |
505 | length -= offset; |
506 | |
507 | print_hex_dump(level: pfx, prefix_str: "", prefix_type: DUMP_PREFIX_OFFSET, rowsize: 16, groupsize: 4, buf, len: length, ascii: true); |
508 | } |
509 | |
510 | static void cper_print_tstamp(const char *pfx, |
511 | struct acpi_hest_generic_data_v300 *gdata) |
512 | { |
513 | __u8 hour, min, sec, day, mon, year, century, *timestamp; |
514 | |
515 | if (gdata->validation_bits & ACPI_HEST_GEN_VALID_TIMESTAMP) { |
516 | timestamp = (__u8 *)&(gdata->time_stamp); |
517 | sec = bcd2bin(timestamp[0]); |
518 | min = bcd2bin(timestamp[1]); |
519 | hour = bcd2bin(timestamp[2]); |
520 | day = bcd2bin(timestamp[4]); |
521 | mon = bcd2bin(timestamp[5]); |
522 | year = bcd2bin(timestamp[6]); |
523 | century = bcd2bin(timestamp[7]); |
524 | |
525 | printk("%s%ststamp: %02d%02d-%02d-%02d %02d:%02d:%02d\n", pfx, |
526 | (timestamp[3] & 0x1 ? "precise ": "imprecise "), |
527 | century, year, mon, day, hour, min, sec); |
528 | } |
529 | } |
530 | |
531 | struct ignore_section { |
532 | guid_t guid; |
533 | const char *name; |
534 | }; |
535 | |
536 | static const struct ignore_section ignore_sections[] = { |
537 | { .guid = CPER_SEC_CXL_GEN_MEDIA_GUID, .name = "CXL General Media Event"}, |
538 | { .guid = CPER_SEC_CXL_DRAM_GUID, .name = "CXL DRAM Event"}, |
539 | { .guid = CPER_SEC_CXL_MEM_MODULE_GUID, .name = "CXL Memory Module Event"}, |
540 | }; |
541 | |
542 | static void |
543 | cper_estatus_print_section(const char *pfx, struct acpi_hest_generic_data *gdata, |
544 | int sec_no) |
545 | { |
546 | guid_t *sec_type = (guid_t *)gdata->section_type; |
547 | __u16 severity; |
548 | char newpfx[64]; |
549 | |
550 | if (acpi_hest_get_version(gdata) >= 3) |
551 | cper_print_tstamp(pfx, gdata: (struct acpi_hest_generic_data_v300 *)gdata); |
552 | |
553 | severity = gdata->error_severity; |
554 | printk("%s" "Error %d, type: %s\n", pfx, sec_no, |
555 | cper_severity_str(severity)); |
556 | if (gdata->validation_bits & CPER_SEC_VALID_FRU_ID) |
557 | printk("%s" "fru_id: %pUl\n", pfx, gdata->fru_id); |
558 | if (gdata->validation_bits & CPER_SEC_VALID_FRU_TEXT) |
559 | printk("%s" "fru_text: %.20s\n", pfx, gdata->fru_text); |
560 | |
561 | snprintf(buf: newpfx, size: sizeof(newpfx), fmt: "%s ", pfx); |
562 | |
563 | for (int i = 0; i < ARRAY_SIZE(ignore_sections); i++) { |
564 | if (guid_equal(u1: sec_type, u2: &ignore_sections[i].guid)) { |
565 | printk("%ssection_type: %s\n", newpfx, ignore_sections[i].name); |
566 | return; |
567 | } |
568 | } |
569 | |
570 | if (guid_equal(u1: sec_type, u2: &CPER_SEC_PROC_GENERIC)) { |
571 | struct cper_sec_proc_generic *proc_err = acpi_hest_get_payload(gdata); |
572 | |
573 | printk("%s" "section_type: general processor error\n", newpfx); |
574 | if (gdata->error_data_length >= sizeof(*proc_err)) |
575 | cper_print_proc_generic(pfx: newpfx, proc: proc_err); |
576 | else |
577 | goto err_section_too_small; |
578 | } else if (guid_equal(u1: sec_type, u2: &CPER_SEC_PLATFORM_MEM)) { |
579 | struct cper_sec_mem_err *mem_err = acpi_hest_get_payload(gdata); |
580 | |
581 | printk("%s" "section_type: memory error\n", newpfx); |
582 | if (gdata->error_data_length >= |
583 | sizeof(struct cper_sec_mem_err_old)) |
584 | cper_print_mem(pfx: newpfx, mem: mem_err, |
585 | len: gdata->error_data_length); |
586 | else |
587 | goto err_section_too_small; |
588 | } else if (guid_equal(u1: sec_type, u2: &CPER_SEC_PCIE)) { |
589 | struct cper_sec_pcie *pcie = acpi_hest_get_payload(gdata); |
590 | |
591 | printk("%s" "section_type: PCIe error\n", newpfx); |
592 | if (gdata->error_data_length >= sizeof(*pcie)) |
593 | cper_print_pcie(pfx: newpfx, pcie, gdata); |
594 | else |
595 | goto err_section_too_small; |
596 | #if defined(CONFIG_ARM64) || defined(CONFIG_ARM) |
597 | } else if (guid_equal(sec_type, &CPER_SEC_PROC_ARM)) { |
598 | struct cper_sec_proc_arm *arm_err = acpi_hest_get_payload(gdata); |
599 | |
600 | printk("%ssection_type: ARM processor error\n", newpfx); |
601 | if (gdata->error_data_length >= sizeof(*arm_err)) |
602 | cper_print_proc_arm(newpfx, arm_err); |
603 | else |
604 | goto err_section_too_small; |
605 | #endif |
606 | #if defined(CONFIG_UEFI_CPER_X86) |
607 | } else if (guid_equal(u1: sec_type, u2: &CPER_SEC_PROC_IA)) { |
608 | struct cper_sec_proc_ia *ia_err = acpi_hest_get_payload(gdata); |
609 | |
610 | printk("%ssection_type: IA32/X64 processor error\n", newpfx); |
611 | if (gdata->error_data_length >= sizeof(*ia_err)) |
612 | cper_print_proc_ia(pfx: newpfx, proc: ia_err); |
613 | else |
614 | goto err_section_too_small; |
615 | #endif |
616 | } else if (guid_equal(u1: sec_type, u2: &CPER_SEC_FW_ERR_REC_REF)) { |
617 | struct cper_sec_fw_err_rec_ref *fw_err = acpi_hest_get_payload(gdata); |
618 | |
619 | printk("%ssection_type: Firmware Error Record Reference\n", |
620 | newpfx); |
621 | /* The minimal FW Error Record contains 16 bytes */ |
622 | if (gdata->error_data_length >= SZ_16) |
623 | cper_print_fw_err(pfx: newpfx, gdata, fw_err); |
624 | else |
625 | goto err_section_too_small; |
626 | } else if (guid_equal(u1: sec_type, u2: &CPER_SEC_CXL_PROT_ERR)) { |
627 | struct cxl_cper_sec_prot_err *prot_err = acpi_hest_get_payload(gdata); |
628 | |
629 | printk("%ssection_type: CXL Protocol Error\n", newpfx); |
630 | if (gdata->error_data_length >= sizeof(*prot_err)) |
631 | cxl_cper_print_prot_err(pfx: newpfx, prot_err); |
632 | else |
633 | goto err_section_too_small; |
634 | } else { |
635 | const void *err = acpi_hest_get_payload(gdata); |
636 | |
637 | printk("%ssection type: unknown, %pUl\n", newpfx, sec_type); |
638 | printk("%ssection length: %#x\n", newpfx, |
639 | gdata->error_data_length); |
640 | print_hex_dump(level: newpfx, prefix_str: "", prefix_type: DUMP_PREFIX_OFFSET, rowsize: 16, groupsize: 4, buf: err, |
641 | len: gdata->error_data_length, ascii: true); |
642 | } |
643 | |
644 | return; |
645 | |
646 | err_section_too_small: |
647 | pr_err(FW_WARN "error section length is too small\n"); |
648 | } |
649 | |
650 | void cper_estatus_print(const char *pfx, |
651 | const struct acpi_hest_generic_status *estatus) |
652 | { |
653 | struct acpi_hest_generic_data *gdata; |
654 | int sec_no = 0; |
655 | char newpfx[64]; |
656 | __u16 severity; |
657 | |
658 | severity = estatus->error_severity; |
659 | if (severity == CPER_SEV_CORRECTED) |
660 | printk("%s%s\n", pfx, |
661 | "It has been corrected by h/w " |
662 | "and requires no further action"); |
663 | printk("%s" "event severity: %s\n", pfx, cper_severity_str(severity)); |
664 | snprintf(buf: newpfx, size: sizeof(newpfx), fmt: "%s ", pfx); |
665 | |
666 | apei_estatus_for_each_section(estatus, gdata) { |
667 | cper_estatus_print_section(pfx: newpfx, gdata, sec_no); |
668 | sec_no++; |
669 | } |
670 | } |
671 | EXPORT_SYMBOL_GPL(cper_estatus_print); |
672 | |
673 | int cper_estatus_check_header(const struct acpi_hest_generic_status *estatus) |
674 | { |
675 | if (estatus->data_length && |
676 | estatus->data_length < sizeof(struct acpi_hest_generic_data)) |
677 | return -EINVAL; |
678 | if (estatus->raw_data_length && |
679 | estatus->raw_data_offset < sizeof(*estatus) + estatus->data_length) |
680 | return -EINVAL; |
681 | |
682 | return 0; |
683 | } |
684 | EXPORT_SYMBOL_GPL(cper_estatus_check_header); |
685 | |
686 | int cper_estatus_check(const struct acpi_hest_generic_status *estatus) |
687 | { |
688 | struct acpi_hest_generic_data *gdata; |
689 | unsigned int data_len, record_size; |
690 | int rc; |
691 | |
692 | rc = cper_estatus_check_header(estatus); |
693 | if (rc) |
694 | return rc; |
695 | |
696 | data_len = estatus->data_length; |
697 | |
698 | apei_estatus_for_each_section(estatus, gdata) { |
699 | if (acpi_hest_get_size(gdata) > data_len) |
700 | return -EINVAL; |
701 | |
702 | record_size = acpi_hest_get_record_size(gdata); |
703 | if (record_size > data_len) |
704 | return -EINVAL; |
705 | |
706 | data_len -= record_size; |
707 | } |
708 | if (data_len) |
709 | return -EINVAL; |
710 | |
711 | return 0; |
712 | } |
713 | EXPORT_SYMBOL_GPL(cper_estatus_check); |
714 |
Definitions
- cper_next_record_id
- severity_strs
- cper_severity_str
- cper_print_bits
- proc_type_strs
- proc_isa_strs
- cper_proc_error_type_strs
- proc_op_strs
- proc_flag_strs
- cper_print_proc_generic
- mem_err_type_strs
- cper_mem_err_type_str
- cper_mem_err_status_str
- cper_mem_err_location
- cper_dimm_err_location
- cper_mem_err_pack
- cper_mem_err_unpack
- cper_print_mem
- pcie_port_type_strs
- cper_print_pcie
- fw_err_rec_type_strs
- cper_print_fw_err
- cper_print_tstamp
- ignore_section
- ignore_sections
- cper_estatus_print_section
- cper_estatus_print
- cper_estatus_check_header
Improve your Profiling and Debugging skills
Find out more