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 "cper_cxl.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 | /* Fatal errors call __ghes_panic() before AER handler prints this */ |
438 | if ((pcie->validation_bits & CPER_PCIE_VALID_AER_INFO) && |
439 | (gdata->error_severity & CPER_SEV_FATAL)) { |
440 | struct aer_capability_regs *aer; |
441 | |
442 | aer = (struct aer_capability_regs *)pcie->aer_info; |
443 | printk("%saer_uncor_status: 0x%08x, aer_uncor_mask: 0x%08x\n" , |
444 | pfx, aer->uncor_status, aer->uncor_mask); |
445 | printk("%saer_uncor_severity: 0x%08x\n" , |
446 | pfx, aer->uncor_severity); |
447 | printk("%sTLP Header: %08x %08x %08x %08x\n" , pfx, |
448 | aer->header_log.dw[0], aer->header_log.dw[1], |
449 | aer->header_log.dw[2], aer->header_log.dw[3]); |
450 | } |
451 | } |
452 | |
453 | static const char * const fw_err_rec_type_strs[] = { |
454 | "IPF SAL Error Record" , |
455 | "SOC Firmware Error Record Type1 (Legacy CrashLog Support)" , |
456 | "SOC Firmware Error Record Type2" , |
457 | }; |
458 | |
459 | static void cper_print_fw_err(const char *pfx, |
460 | struct acpi_hest_generic_data *gdata, |
461 | const struct cper_sec_fw_err_rec_ref *fw_err) |
462 | { |
463 | void *buf = acpi_hest_get_payload(gdata); |
464 | u32 offset, length = gdata->error_data_length; |
465 | |
466 | printk("%s" "Firmware Error Record Type: %s\n" , pfx, |
467 | fw_err->record_type < ARRAY_SIZE(fw_err_rec_type_strs) ? |
468 | fw_err_rec_type_strs[fw_err->record_type] : "unknown" ); |
469 | printk("%s" "Revision: %d\n" , pfx, fw_err->revision); |
470 | |
471 | /* Record Type based on UEFI 2.7 */ |
472 | if (fw_err->revision == 0) { |
473 | printk("%s" "Record Identifier: %08llx\n" , pfx, |
474 | fw_err->record_identifier); |
475 | } else if (fw_err->revision == 2) { |
476 | printk("%s" "Record Identifier: %pUl\n" , pfx, |
477 | &fw_err->record_identifier_guid); |
478 | } |
479 | |
480 | /* |
481 | * The FW error record may contain trailing data beyond the |
482 | * structure defined by the specification. As the fields |
483 | * defined (and hence the offset of any trailing data) vary |
484 | * with the revision, set the offset to account for this |
485 | * variation. |
486 | */ |
487 | if (fw_err->revision == 0) { |
488 | /* record_identifier_guid not defined */ |
489 | offset = offsetof(struct cper_sec_fw_err_rec_ref, |
490 | record_identifier_guid); |
491 | } else if (fw_err->revision == 1) { |
492 | /* record_identifier not defined */ |
493 | offset = offsetof(struct cper_sec_fw_err_rec_ref, |
494 | record_identifier); |
495 | } else { |
496 | offset = sizeof(*fw_err); |
497 | } |
498 | |
499 | buf += offset; |
500 | length -= offset; |
501 | |
502 | print_hex_dump(level: pfx, prefix_str: "" , prefix_type: DUMP_PREFIX_OFFSET, rowsize: 16, groupsize: 4, buf, len: length, ascii: true); |
503 | } |
504 | |
505 | static void cper_print_tstamp(const char *pfx, |
506 | struct acpi_hest_generic_data_v300 *gdata) |
507 | { |
508 | __u8 hour, min, sec, day, mon, year, century, *timestamp; |
509 | |
510 | if (gdata->validation_bits & ACPI_HEST_GEN_VALID_TIMESTAMP) { |
511 | timestamp = (__u8 *)&(gdata->time_stamp); |
512 | sec = bcd2bin(timestamp[0]); |
513 | min = bcd2bin(timestamp[1]); |
514 | hour = bcd2bin(timestamp[2]); |
515 | day = bcd2bin(timestamp[4]); |
516 | mon = bcd2bin(timestamp[5]); |
517 | year = bcd2bin(timestamp[6]); |
518 | century = bcd2bin(timestamp[7]); |
519 | |
520 | printk("%s%ststamp: %02d%02d-%02d-%02d %02d:%02d:%02d\n" , pfx, |
521 | (timestamp[3] & 0x1 ? "precise " : "imprecise " ), |
522 | century, year, mon, day, hour, min, sec); |
523 | } |
524 | } |
525 | |
526 | struct ignore_section { |
527 | guid_t guid; |
528 | const char *name; |
529 | }; |
530 | |
531 | static const struct ignore_section ignore_sections[] = { |
532 | { .guid = CPER_SEC_CXL_GEN_MEDIA_GUID, .name = "CXL General Media Event" }, |
533 | { .guid = CPER_SEC_CXL_DRAM_GUID, .name = "CXL DRAM Event" }, |
534 | { .guid = CPER_SEC_CXL_MEM_MODULE_GUID, .name = "CXL Memory Module Event" }, |
535 | }; |
536 | |
537 | static void |
538 | cper_estatus_print_section(const char *pfx, struct acpi_hest_generic_data *gdata, |
539 | int sec_no) |
540 | { |
541 | guid_t *sec_type = (guid_t *)gdata->section_type; |
542 | __u16 severity; |
543 | char newpfx[64]; |
544 | |
545 | if (acpi_hest_get_version(gdata) >= 3) |
546 | cper_print_tstamp(pfx, gdata: (struct acpi_hest_generic_data_v300 *)gdata); |
547 | |
548 | severity = gdata->error_severity; |
549 | printk("%s" "Error %d, type: %s\n" , pfx, sec_no, |
550 | cper_severity_str(severity)); |
551 | if (gdata->validation_bits & CPER_SEC_VALID_FRU_ID) |
552 | printk("%s" "fru_id: %pUl\n" , pfx, gdata->fru_id); |
553 | if (gdata->validation_bits & CPER_SEC_VALID_FRU_TEXT) |
554 | printk("%s" "fru_text: %.20s\n" , pfx, gdata->fru_text); |
555 | |
556 | snprintf(buf: newpfx, size: sizeof(newpfx), fmt: "%s " , pfx); |
557 | |
558 | for (int i = 0; i < ARRAY_SIZE(ignore_sections); i++) { |
559 | if (guid_equal(u1: sec_type, u2: &ignore_sections[i].guid)) { |
560 | printk("%ssection_type: %s\n" , newpfx, ignore_sections[i].name); |
561 | return; |
562 | } |
563 | } |
564 | |
565 | if (guid_equal(u1: sec_type, u2: &CPER_SEC_PROC_GENERIC)) { |
566 | struct cper_sec_proc_generic *proc_err = acpi_hest_get_payload(gdata); |
567 | |
568 | printk("%s" "section_type: general processor error\n" , newpfx); |
569 | if (gdata->error_data_length >= sizeof(*proc_err)) |
570 | cper_print_proc_generic(pfx: newpfx, proc: proc_err); |
571 | else |
572 | goto err_section_too_small; |
573 | } else if (guid_equal(u1: sec_type, u2: &CPER_SEC_PLATFORM_MEM)) { |
574 | struct cper_sec_mem_err *mem_err = acpi_hest_get_payload(gdata); |
575 | |
576 | printk("%s" "section_type: memory error\n" , newpfx); |
577 | if (gdata->error_data_length >= |
578 | sizeof(struct cper_sec_mem_err_old)) |
579 | cper_print_mem(pfx: newpfx, mem: mem_err, |
580 | len: gdata->error_data_length); |
581 | else |
582 | goto err_section_too_small; |
583 | } else if (guid_equal(u1: sec_type, u2: &CPER_SEC_PCIE)) { |
584 | struct cper_sec_pcie *pcie = acpi_hest_get_payload(gdata); |
585 | |
586 | printk("%s" "section_type: PCIe error\n" , newpfx); |
587 | if (gdata->error_data_length >= sizeof(*pcie)) |
588 | cper_print_pcie(pfx: newpfx, pcie, gdata); |
589 | else |
590 | goto err_section_too_small; |
591 | #if defined(CONFIG_ARM64) || defined(CONFIG_ARM) |
592 | } else if (guid_equal(sec_type, &CPER_SEC_PROC_ARM)) { |
593 | struct cper_sec_proc_arm *arm_err = acpi_hest_get_payload(gdata); |
594 | |
595 | printk("%ssection_type: ARM processor error\n" , newpfx); |
596 | if (gdata->error_data_length >= sizeof(*arm_err)) |
597 | cper_print_proc_arm(newpfx, arm_err); |
598 | else |
599 | goto err_section_too_small; |
600 | #endif |
601 | #if defined(CONFIG_UEFI_CPER_X86) |
602 | } else if (guid_equal(u1: sec_type, u2: &CPER_SEC_PROC_IA)) { |
603 | struct cper_sec_proc_ia *ia_err = acpi_hest_get_payload(gdata); |
604 | |
605 | printk("%ssection_type: IA32/X64 processor error\n" , newpfx); |
606 | if (gdata->error_data_length >= sizeof(*ia_err)) |
607 | cper_print_proc_ia(pfx: newpfx, proc: ia_err); |
608 | else |
609 | goto err_section_too_small; |
610 | #endif |
611 | } else if (guid_equal(u1: sec_type, u2: &CPER_SEC_FW_ERR_REC_REF)) { |
612 | struct cper_sec_fw_err_rec_ref *fw_err = acpi_hest_get_payload(gdata); |
613 | |
614 | printk("%ssection_type: Firmware Error Record Reference\n" , |
615 | newpfx); |
616 | /* The minimal FW Error Record contains 16 bytes */ |
617 | if (gdata->error_data_length >= SZ_16) |
618 | cper_print_fw_err(pfx: newpfx, gdata, fw_err); |
619 | else |
620 | goto err_section_too_small; |
621 | } else if (guid_equal(u1: sec_type, u2: &CPER_SEC_CXL_PROT_ERR)) { |
622 | struct cper_sec_prot_err *prot_err = acpi_hest_get_payload(gdata); |
623 | |
624 | printk("%ssection_type: CXL Protocol Error\n" , newpfx); |
625 | if (gdata->error_data_length >= sizeof(*prot_err)) |
626 | cper_print_prot_err(pfx: newpfx, prot_err); |
627 | else |
628 | goto err_section_too_small; |
629 | } else { |
630 | const void *err = acpi_hest_get_payload(gdata); |
631 | |
632 | printk("%ssection type: unknown, %pUl\n" , newpfx, sec_type); |
633 | printk("%ssection length: %#x\n" , newpfx, |
634 | gdata->error_data_length); |
635 | print_hex_dump(level: newpfx, prefix_str: "" , prefix_type: DUMP_PREFIX_OFFSET, rowsize: 16, groupsize: 4, buf: err, |
636 | len: gdata->error_data_length, ascii: true); |
637 | } |
638 | |
639 | return; |
640 | |
641 | err_section_too_small: |
642 | pr_err(FW_WARN "error section length is too small\n" ); |
643 | } |
644 | |
645 | void cper_estatus_print(const char *pfx, |
646 | const struct acpi_hest_generic_status *estatus) |
647 | { |
648 | struct acpi_hest_generic_data *gdata; |
649 | int sec_no = 0; |
650 | char newpfx[64]; |
651 | __u16 severity; |
652 | |
653 | severity = estatus->error_severity; |
654 | if (severity == CPER_SEV_CORRECTED) |
655 | printk("%s%s\n" , pfx, |
656 | "It has been corrected by h/w " |
657 | "and requires no further action" ); |
658 | printk("%s" "event severity: %s\n" , pfx, cper_severity_str(severity)); |
659 | snprintf(buf: newpfx, size: sizeof(newpfx), fmt: "%s " , pfx); |
660 | |
661 | apei_estatus_for_each_section(estatus, gdata) { |
662 | cper_estatus_print_section(pfx: newpfx, gdata, sec_no); |
663 | sec_no++; |
664 | } |
665 | } |
666 | EXPORT_SYMBOL_GPL(cper_estatus_print); |
667 | |
668 | int (const struct acpi_hest_generic_status *estatus) |
669 | { |
670 | if (estatus->data_length && |
671 | estatus->data_length < sizeof(struct acpi_hest_generic_data)) |
672 | return -EINVAL; |
673 | if (estatus->raw_data_length && |
674 | estatus->raw_data_offset < sizeof(*estatus) + estatus->data_length) |
675 | return -EINVAL; |
676 | |
677 | return 0; |
678 | } |
679 | EXPORT_SYMBOL_GPL(cper_estatus_check_header); |
680 | |
681 | int cper_estatus_check(const struct acpi_hest_generic_status *estatus) |
682 | { |
683 | struct acpi_hest_generic_data *gdata; |
684 | unsigned int data_len, record_size; |
685 | int rc; |
686 | |
687 | rc = cper_estatus_check_header(estatus); |
688 | if (rc) |
689 | return rc; |
690 | |
691 | data_len = estatus->data_length; |
692 | |
693 | apei_estatus_for_each_section(estatus, gdata) { |
694 | if (acpi_hest_get_size(gdata) > data_len) |
695 | return -EINVAL; |
696 | |
697 | record_size = acpi_hest_get_record_size(gdata); |
698 | if (record_size > data_len) |
699 | return -EINVAL; |
700 | |
701 | data_len -= record_size; |
702 | } |
703 | if (data_len) |
704 | return -EINVAL; |
705 | |
706 | return 0; |
707 | } |
708 | EXPORT_SYMBOL_GPL(cper_estatus_check); |
709 | |