| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * UEFI Common Platform Error Record (CPER) support | |
| * | |
| * Copyright (C) 2010, Intel Corp. | |
| * Author: Huang Ying <ying.huang@intel.com> | |
| * | |
| * CPER is the format used to describe platform hardware error by | |
| * various tables, such as ERST, BERT and HEST etc. | |
| * | |
| * For more information about CPER, please refer to Appendix N of UEFI | |
| * Specification version 2.4. | |
| */ | |
| #include <linux/bitmap.h> | |
| #include <linux/kernel.h> | |
| #include <linux/module.h> | |
| #include <linux/time.h> | |
| #include <linux/cper.h> | |
| #include <linux/dmi.h> | |
| #include <linux/acpi.h> | |
| #include <linux/pci.h> | |
| #include <linux/aer.h> | |
| #include <linux/printk.h> | |
| #include <linux/bcd.h> | |
| #include <acpi/ghes.h> | |
| #include <ras/ras_event.h> | |
| #include <cxl/event.h> | |
| /* | |
| * CPER record ID need to be unique even after reboot, because record | |
| * ID is used as index for ERST storage, while CPER records from | |
| * multiple boot may co-exist in ERST. | |
| */ | |
| u64 cper_next_record_id(void) | |
| { | |
| static atomic64_t seq; | |
| if (!atomic64_read(&seq)) { | |
| time64_t time = ktime_get_real_seconds(); | |
| /* | |
| * This code is unlikely to still be needed in year 2106, | |
| * but just in case, let's use a few more bits for timestamps | |
| * after y2038 to be sure they keep increasing monotonically | |
| * for the next few hundred years... | |
| */ | |
| if (time < 0x80000000) | |
| atomic64_set(&seq, (ktime_get_real_seconds()) << 32); | |
| else | |
| atomic64_set(&seq, 0x8000000000000000ull | | |
| ktime_get_real_seconds() << 24); | |
| } | |
| return atomic64_inc_return(&seq); | |
| } | |
| EXPORT_SYMBOL_GPL(cper_next_record_id); | |
| static const char * const severity_strs[] = { | |
| "recoverable", | |
| "fatal", | |
| "corrected", | |
| "info", | |
| }; | |
| const char *cper_severity_str(unsigned int severity) | |
| { | |
| return severity < ARRAY_SIZE(severity_strs) ? | |
| severity_strs[severity] : "unknown"; | |
| } | |
| EXPORT_SYMBOL_GPL(cper_severity_str); | |
| /** | |
| * cper_print_bits - print strings for set bits | |
| * @pfx: prefix for each line, including log level and prefix string | |
| * @bits: bit mask | |
| * @strs: string array, indexed by bit position | |
| * @strs_size: size of the string array: @strs | |
| * | |
| * For each set bit in @bits, print the corresponding string in @strs. | |
| * If the output length is longer than 80, multiple line will be | |
| * printed, with @pfx is printed at the beginning of each line. | |
| */ | |
| void cper_print_bits(const char *pfx, unsigned int bits, | |
| const char * const strs[], unsigned int strs_size) | |
| { | |
| int i, len = 0; | |
| const char *str; | |
| char buf[84]; | |
| for (i = 0; i < strs_size; i++) { | |
| if (!(bits & (1U << i))) | |
| continue; | |
| str = strs[i]; | |
| if (!str) | |
| continue; | |
| if (len && len + strlen(str) + 2 > 80) { | |
| printk("%s\n", buf); | |
| len = 0; | |
| } | |
| if (!len) | |
| len = snprintf(buf, sizeof(buf), "%s%s", pfx, str); | |
| else | |
| len += scnprintf(buf+len, sizeof(buf)-len, ", %s", str); | |
| } | |
| if (len) | |
| printk("%s\n", buf); | |
| } | |
| /** | |
| * cper_bits_to_str - return a string for set bits | |
| * @buf: buffer to store the output string | |
| * @buf_size: size of the output string buffer | |
| * @bits: bit mask | |
| * @strs: string array, indexed by bit position | |
| * @strs_size: size of the string array: @strs | |
| * | |
| * Add to @buf the bitmask in hexadecimal. Then, for each set bit in @bits, | |
| * add the corresponding string describing the bit in @strs to @buf. | |
| * | |
| * A typical example is:: | |
| * | |
| * const char * const bits[] = { | |
| * "bit 3 name", | |
| * "bit 4 name", | |
| * "bit 5 name", | |
| * }; | |
| * char str[120]; | |
| * unsigned int bitmask = BIT(3) | BIT(5); | |
| * #define MASK GENMASK(5,3) | |
| * | |
| * cper_bits_to_str(str, sizeof(str), FIELD_GET(MASK, bitmask), | |
| * bits, ARRAY_SIZE(bits)); | |
| * | |
| * The above code fills the string ``str`` with ``bit 3 name|bit 5 name``. | |
| * | |
| * Return: number of bytes stored or an error code if lower than zero. | |
| */ | |
| int cper_bits_to_str(char *buf, int buf_size, unsigned long bits, | |
| const char * const strs[], unsigned int strs_size) | |
| { | |
| int len = buf_size; | |
| char *str = buf; | |
| int i, size; | |
| *buf = '\0'; | |
| for_each_set_bit(i, &bits, strs_size) { | |
| if (!(bits & BIT_ULL(i))) | |
| continue; | |
| if (*buf && len > 0) { | |
| *str = '|'; | |
| len--; | |
| str++; | |
| } | |
| size = strscpy(str, strs[i], len); | |
| if (size < 0) | |
| return size; | |
| len -= size; | |
| str += size; | |
| } | |
| return buf_size - len; | |
| } | |
| EXPORT_SYMBOL_GPL(cper_bits_to_str); | |
| static const char * const proc_type_strs[] = { | |
| "IA32/X64", | |
| "IA64", | |
| "ARM", | |
| }; | |
| static const char * const proc_isa_strs[] = { | |
| "IA32", | |
| "IA64", | |
| "X64", | |
| "ARM A32/T32", | |
| "ARM A64", | |
| }; | |
| const char * const cper_proc_error_type_strs[] = { | |
| "cache error", | |
| "TLB error", | |
| "bus error", | |
| "micro-architectural error", | |
| }; | |
| static const char * const proc_op_strs[] = { | |
| "unknown or generic", | |
| "data read", | |
| "data write", | |
| "instruction execution", | |
| }; | |
| static const char * const proc_flag_strs[] = { | |
| "restartable", | |
| "precise IP", | |
| "overflow", | |
| "corrected", | |
| }; | |
| static void cper_print_proc_generic(const char *pfx, | |
| const struct cper_sec_proc_generic *proc) | |
| { | |
| if (proc->validation_bits & CPER_PROC_VALID_TYPE) | |
| printk("%s""processor_type: %d, %s\n", pfx, proc->proc_type, | |
| proc->proc_type < ARRAY_SIZE(proc_type_strs) ? | |
| proc_type_strs[proc->proc_type] : "unknown"); | |
| if (proc->validation_bits & CPER_PROC_VALID_ISA) | |
| printk("%s""processor_isa: %d, %s\n", pfx, proc->proc_isa, | |
| proc->proc_isa < ARRAY_SIZE(proc_isa_strs) ? | |
| proc_isa_strs[proc->proc_isa] : "unknown"); | |
| if (proc->validation_bits & CPER_PROC_VALID_ERROR_TYPE) { | |
| printk("%s""error_type: 0x%02x\n", pfx, proc->proc_error_type); | |
| cper_print_bits(pfx, proc->proc_error_type, | |
| cper_proc_error_type_strs, | |
| ARRAY_SIZE(cper_proc_error_type_strs)); | |
| } | |
| if (proc->validation_bits & CPER_PROC_VALID_OPERATION) | |
| printk("%s""operation: %d, %s\n", pfx, proc->operation, | |
| proc->operation < ARRAY_SIZE(proc_op_strs) ? | |
| proc_op_strs[proc->operation] : "unknown"); | |
| if (proc->validation_bits & CPER_PROC_VALID_FLAGS) { | |
| printk("%s""flags: 0x%02x\n", pfx, proc->flags); | |
| cper_print_bits(pfx, proc->flags, proc_flag_strs, | |
| ARRAY_SIZE(proc_flag_strs)); | |
| } | |
| if (proc->validation_bits & CPER_PROC_VALID_LEVEL) | |
| printk("%s""level: %d\n", pfx, proc->level); | |
| if (proc->validation_bits & CPER_PROC_VALID_VERSION) | |
| printk("%s""version_info: 0x%016llx\n", pfx, proc->cpu_version); | |
| if (proc->validation_bits & CPER_PROC_VALID_ID) | |
| printk("%s""processor_id: 0x%016llx\n", pfx, proc->proc_id); | |
| if (proc->validation_bits & CPER_PROC_VALID_TARGET_ADDRESS) | |
| printk("%s""target_address: 0x%016llx\n", | |
| pfx, proc->target_addr); | |
| if (proc->validation_bits & CPER_PROC_VALID_REQUESTOR_ID) | |
| printk("%s""requestor_id: 0x%016llx\n", | |
| pfx, proc->requestor_id); | |
| if (proc->validation_bits & CPER_PROC_VALID_RESPONDER_ID) | |
| printk("%s""responder_id: 0x%016llx\n", | |
| pfx, proc->responder_id); | |
| if (proc->validation_bits & CPER_PROC_VALID_IP) | |
| printk("%s""IP: 0x%016llx\n", pfx, proc->ip); | |
| } | |
| static const char * const mem_err_type_strs[] = { | |
| "unknown", | |
| "no error", | |
| "single-bit ECC", | |
| "multi-bit ECC", | |
| "single-symbol chipkill ECC", | |
| "multi-symbol chipkill ECC", | |
| "master abort", | |
| "target abort", | |
| "parity error", | |
| "watchdog timeout", | |
| "invalid address", | |
| "mirror Broken", | |
| "memory sparing", | |
| "scrub corrected error", | |
| "scrub uncorrected error", | |
| "physical memory map-out event", | |
| }; | |
| const char *cper_mem_err_type_str(unsigned int etype) | |
| { | |
| return etype < ARRAY_SIZE(mem_err_type_strs) ? | |
| mem_err_type_strs[etype] : "unknown"; | |
| } | |
| EXPORT_SYMBOL_GPL(cper_mem_err_type_str); | |
| const char *cper_mem_err_status_str(u64 status) | |
| { | |
| switch ((status >> 8) & 0xff) { | |
| case 1: return "Error detected internal to the component"; | |
| case 4: return "Storage error in DRAM memory"; | |
| case 5: return "Storage error in TLB"; | |
| case 6: return "Storage error in cache"; | |
| case 7: return "Error in one or more functional units"; | |
| case 8: return "Component failed self test"; | |
| case 9: return "Overflow or undervalue of internal queue"; | |
| case 16: return "Error detected in the bus"; | |
| case 17: return "Virtual address not found on IO-TLB or IO-PDIR"; | |
| case 18: return "Improper access error"; | |
| case 19: return "Access to a memory address which is not mapped to any component"; | |
| case 20: return "Loss of Lockstep"; | |
| case 21: return "Response not associated with a request"; | |
| case 22: return "Bus parity error - must also set the A, C, or D Bits"; | |
| case 23: return "Detection of a protocol error"; | |
| case 24: return "Detection of a PATH_ERROR"; | |
| case 25: return "Bus operation timeout"; | |
| case 26: return "A read was issued to data that has been poisoned"; | |
| default: return "Reserved"; | |
| } | |
| } | |
| EXPORT_SYMBOL_GPL(cper_mem_err_status_str); | |
| int cper_mem_err_location(struct cper_mem_err_compact *mem, char *msg) | |
| { | |
| u32 len, n; | |
| if (!msg) | |
| return 0; | |
| n = 0; | |
| len = CPER_REC_LEN; | |
| if (mem->validation_bits & CPER_MEM_VALID_NODE) | |
| n += scnprintf(msg + n, len - n, "node:%d ", mem->node); | |
| if (mem->validation_bits & CPER_MEM_VALID_CARD) | |
| n += scnprintf(msg + n, len - n, "card:%d ", mem->card); | |
| if (mem->validation_bits & CPER_MEM_VALID_MODULE) | |
| n += scnprintf(msg + n, len - n, "module:%d ", mem->module); | |
| if (mem->validation_bits & CPER_MEM_VALID_RANK_NUMBER) | |
| n += scnprintf(msg + n, len - n, "rank:%d ", mem->rank); | |
| if (mem->validation_bits & CPER_MEM_VALID_BANK) | |
| n += scnprintf(msg + n, len - n, "bank:%d ", mem->bank); | |
| if (mem->validation_bits & CPER_MEM_VALID_BANK_GROUP) | |
| n += scnprintf(msg + n, len - n, "bank_group:%d ", | |
| mem->bank >> CPER_MEM_BANK_GROUP_SHIFT); | |
| if (mem->validation_bits & CPER_MEM_VALID_BANK_ADDRESS) | |
| n += scnprintf(msg + n, len - n, "bank_address:%d ", | |
| mem->bank & CPER_MEM_BANK_ADDRESS_MASK); | |
| if (mem->validation_bits & CPER_MEM_VALID_DEVICE) | |
| n += scnprintf(msg + n, len - n, "device:%d ", mem->device); | |
| if (mem->validation_bits & (CPER_MEM_VALID_ROW | CPER_MEM_VALID_ROW_EXT)) { | |
| u32 row = mem->row; | |
| row |= cper_get_mem_extension(mem->validation_bits, mem->extended); | |
| n += scnprintf(msg + n, len - n, "row:%d ", row); | |
| } | |
| if (mem->validation_bits & CPER_MEM_VALID_COLUMN) | |
| n += scnprintf(msg + n, len - n, "column:%d ", mem->column); | |
| if (mem->validation_bits & CPER_MEM_VALID_BIT_POSITION) | |
| n += scnprintf(msg + n, len - n, "bit_position:%d ", | |
| mem->bit_pos); | |
| if (mem->validation_bits & CPER_MEM_VALID_REQUESTOR_ID) | |
| n += scnprintf(msg + n, len - n, "requestor_id:0x%016llx ", | |
| mem->requestor_id); | |
| if (mem->validation_bits & CPER_MEM_VALID_RESPONDER_ID) | |
| n += scnprintf(msg + n, len - n, "responder_id:0x%016llx ", | |
| mem->responder_id); | |
| if (mem->validation_bits & CPER_MEM_VALID_TARGET_ID) | |
| n += scnprintf(msg + n, len - n, "target_id:0x%016llx ", | |
| mem->target_id); | |
| if (mem->validation_bits & CPER_MEM_VALID_CHIP_ID) | |
| n += scnprintf(msg + n, len - n, "chip_id:%d ", | |
| mem->extended >> CPER_MEM_CHIP_ID_SHIFT); | |
| return n; | |
| } | |
| EXPORT_SYMBOL_GPL(cper_mem_err_location); | |
| int cper_dimm_err_location(struct cper_mem_err_compact *mem, char *msg) | |
| { | |
| u32 len, n; | |
| const char *bank = NULL, *device = NULL; | |
| if (!msg || !(mem->validation_bits & CPER_MEM_VALID_MODULE_HANDLE)) | |
| return 0; | |
| len = CPER_REC_LEN; | |
| dmi_memdev_name(mem->mem_dev_handle, &bank, &device); | |
| if (bank && device) | |
| n = snprintf(msg, len, "DIMM location: %s %s ", bank, device); | |
| else | |
| n = snprintf(msg, len, | |
| "DIMM location: not present. DMI handle: 0x%.4x ", | |
| mem->mem_dev_handle); | |
| return n; | |
| } | |
| EXPORT_SYMBOL_GPL(cper_dimm_err_location); | |
| void cper_mem_err_pack(const struct cper_sec_mem_err *mem, | |
| struct cper_mem_err_compact *cmem) | |
| { | |
| cmem->validation_bits = mem->validation_bits; | |
| cmem->node = mem->node; | |
| cmem->card = mem->card; | |
| cmem->module = mem->module; | |
| cmem->bank = mem->bank; | |
| cmem->device = mem->device; | |
| cmem->row = mem->row; | |
| cmem->column = mem->column; | |
| cmem->bit_pos = mem->bit_pos; | |
| cmem->requestor_id = mem->requestor_id; | |
| cmem->responder_id = mem->responder_id; | |
| cmem->target_id = mem->target_id; | |
| cmem->extended = mem->extended; | |
| cmem->rank = mem->rank; | |
| cmem->mem_array_handle = mem->mem_array_handle; | |
| cmem->mem_dev_handle = mem->mem_dev_handle; | |
| } | |
| EXPORT_SYMBOL_GPL(cper_mem_err_pack); | |
| const char *cper_mem_err_unpack(struct trace_seq *p, | |
| struct cper_mem_err_compact *cmem) | |
| { | |
| const char *ret = trace_seq_buffer_ptr(p); | |
| char rcd_decode_str[CPER_REC_LEN]; | |
| if (cper_mem_err_location(cmem, rcd_decode_str)) | |
| trace_seq_printf(p, "%s", rcd_decode_str); | |
| if (cper_dimm_err_location(cmem, rcd_decode_str)) | |
| trace_seq_printf(p, "%s", rcd_decode_str); | |
| trace_seq_putc(p, '\0'); | |
| return ret; | |
| } | |
| static void cper_print_mem(const char *pfx, const struct cper_sec_mem_err *mem, | |
| int len) | |
| { | |
| struct cper_mem_err_compact cmem; | |
| char rcd_decode_str[CPER_REC_LEN]; | |
| /* Don't trust UEFI 2.1/2.2 structure with bad validation bits */ | |
| if (len == sizeof(struct cper_sec_mem_err_old) && | |
| (mem->validation_bits & ~(CPER_MEM_VALID_RANK_NUMBER - 1))) { | |
| pr_err(FW_WARN "valid bits set for fields beyond structure\n"); | |
| return; | |
| } | |
| if (mem->validation_bits & CPER_MEM_VALID_ERROR_STATUS) | |
| printk("%s error_status: %s (0x%016llx)\n", | |
| pfx, cper_mem_err_status_str(mem->error_status), | |
| mem->error_status); | |
| if (mem->validation_bits & CPER_MEM_VALID_PA) | |
| printk("%s""physical_address: 0x%016llx\n", | |
| pfx, mem->physical_addr); | |
| if (mem->validation_bits & CPER_MEM_VALID_PA_MASK) | |
| printk("%s""physical_address_mask: 0x%016llx\n", | |
| pfx, mem->physical_addr_mask); | |
| cper_mem_err_pack(mem, &cmem); | |
| if (cper_mem_err_location(&cmem, rcd_decode_str)) | |
| printk("%s%s\n", pfx, rcd_decode_str); | |
| if (mem->validation_bits & CPER_MEM_VALID_ERROR_TYPE) { | |
| u8 etype = mem->error_type; | |
| printk("%s""error_type: %d, %s\n", pfx, etype, | |
| cper_mem_err_type_str(etype)); | |
| } | |
| if (cper_dimm_err_location(&cmem, rcd_decode_str)) | |
| printk("%s%s\n", pfx, rcd_decode_str); | |
| } | |
| static const char * const pcie_port_type_strs[] = { | |
| "PCIe end point", | |
| "legacy PCI end point", | |
| "unknown", | |
| "unknown", | |
| "root port", | |
| "upstream switch port", | |
| "downstream switch port", | |
| "PCIe to PCI/PCI-X bridge", | |
| "PCI/PCI-X to PCIe bridge", | |
| "root complex integrated endpoint device", | |
| "root complex event collector", | |
| }; | |
| static void cper_print_pcie(const char *pfx, const struct cper_sec_pcie *pcie, | |
| const struct acpi_hest_generic_data *gdata) | |
| { | |
| if (pcie->validation_bits & CPER_PCIE_VALID_PORT_TYPE) | |
| printk("%s""port_type: %d, %s\n", pfx, pcie->port_type, | |
| pcie->port_type < ARRAY_SIZE(pcie_port_type_strs) ? | |
| pcie_port_type_strs[pcie->port_type] : "unknown"); | |
| if (pcie->validation_bits & CPER_PCIE_VALID_VERSION) | |
| printk("%s""version: %d.%d\n", pfx, | |
| pcie->version.major, pcie->version.minor); | |
| if (pcie->validation_bits & CPER_PCIE_VALID_COMMAND_STATUS) | |
| printk("%s""command: 0x%04x, status: 0x%04x\n", pfx, | |
| pcie->command, pcie->status); | |
| if (pcie->validation_bits & CPER_PCIE_VALID_DEVICE_ID) { | |
| const __u8 *p; | |
| printk("%s""device_id: %04x:%02x:%02x.%x\n", pfx, | |
| pcie->device_id.segment, pcie->device_id.bus, | |
| pcie->device_id.device, pcie->device_id.function); | |
| printk("%s""slot: %d\n", pfx, | |
| pcie->device_id.slot >> CPER_PCIE_SLOT_SHIFT); | |
| printk("%s""secondary_bus: 0x%02x\n", pfx, | |
| pcie->device_id.secondary_bus); | |
| printk("%s""vendor_id: 0x%04x, device_id: 0x%04x\n", pfx, | |
| pcie->device_id.vendor_id, pcie->device_id.device_id); | |
| p = pcie->device_id.class_code; | |
| printk("%s""class_code: %02x%02x%02x\n", pfx, p[2], p[1], p[0]); | |
| } | |
| if (pcie->validation_bits & CPER_PCIE_VALID_SERIAL_NUMBER) | |
| printk("%s""serial number: 0x%04x, 0x%04x\n", pfx, | |
| pcie->serial_number.lower, pcie->serial_number.upper); | |
| if (pcie->validation_bits & CPER_PCIE_VALID_BRIDGE_CONTROL_STATUS) | |
| printk( | |
| "%s""bridge: secondary_status: 0x%04x, control: 0x%04x\n", | |
| pfx, pcie->bridge.secondary_status, pcie->bridge.control); | |
| /* | |
| * Print all valid AER info. Record may be from BERT (boot-time) or GHES (run-time). | |
| * | |
| * Fatal errors call __ghes_panic() before AER handler prints this. | |
| */ | |
| if (pcie->validation_bits & CPER_PCIE_VALID_AER_INFO) { | |
| struct aer_capability_regs *aer; | |
| aer = (struct aer_capability_regs *)pcie->aer_info; | |
| printk("%saer_cor_status: 0x%08x, aer_cor_mask: 0x%08x\n", | |
| pfx, aer->cor_status, aer->cor_mask); | |
| printk("%saer_uncor_status: 0x%08x, aer_uncor_mask: 0x%08x\n", | |
| pfx, aer->uncor_status, aer->uncor_mask); | |
| printk("%saer_uncor_severity: 0x%08x\n", | |
| pfx, aer->uncor_severity); | |
| printk("%sTLP Header: %08x %08x %08x %08x\n", pfx, | |
| aer->header_log.dw[0], aer->header_log.dw[1], | |
| aer->header_log.dw[2], aer->header_log.dw[3]); | |
| } | |
| } | |
| static const char * const fw_err_rec_type_strs[] = { | |
| "IPF SAL Error Record", | |
| "SOC Firmware Error Record Type1 (Legacy CrashLog Support)", | |
| "SOC Firmware Error Record Type2", | |
| }; | |
| static void cper_print_fw_err(const char *pfx, | |
| struct acpi_hest_generic_data *gdata, | |
| const struct cper_sec_fw_err_rec_ref *fw_err) | |
| { | |
| void *buf = acpi_hest_get_payload(gdata); | |
| u32 offset, length = gdata->error_data_length; | |
| printk("%s""Firmware Error Record Type: %s\n", pfx, | |
| fw_err->record_type < ARRAY_SIZE(fw_err_rec_type_strs) ? | |
| fw_err_rec_type_strs[fw_err->record_type] : "unknown"); | |
| printk("%s""Revision: %d\n", pfx, fw_err->revision); | |
| /* Record Type based on UEFI 2.7 */ | |
| if (fw_err->revision == 0) { | |
| printk("%s""Record Identifier: %08llx\n", pfx, | |
| fw_err->record_identifier); | |
| } else if (fw_err->revision == 2) { | |
| printk("%s""Record Identifier: %pUl\n", pfx, | |
| &fw_err->record_identifier_guid); | |
| } | |
| /* | |
| * The FW error record may contain trailing data beyond the | |
| * structure defined by the specification. As the fields | |
| * defined (and hence the offset of any trailing data) vary | |
| * with the revision, set the offset to account for this | |
| * variation. | |
| */ | |
| if (fw_err->revision == 0) { | |
| /* record_identifier_guid not defined */ | |
| offset = offsetof(struct cper_sec_fw_err_rec_ref, | |
| record_identifier_guid); | |
| } else if (fw_err->revision == 1) { | |
| /* record_identifier not defined */ | |
| offset = offsetof(struct cper_sec_fw_err_rec_ref, | |
| record_identifier); | |
| } else { | |
| offset = sizeof(*fw_err); | |
| } | |
| if (offset > length) { | |
| printk("%s""error section length is too small: offset=%d, length=%d\n", | |
| pfx, offset, length); | |
| return; | |
| } | |
| buf += offset; | |
| length -= offset; | |
| print_hex_dump(pfx, "", DUMP_PREFIX_OFFSET, 16, 4, buf, length, true); | |
| } | |
| static void cper_print_tstamp(const char *pfx, | |
| struct acpi_hest_generic_data_v300 *gdata) | |
| { | |
| __u8 hour, min, sec, day, mon, year, century, *timestamp; | |
| if (gdata->validation_bits & ACPI_HEST_GEN_VALID_TIMESTAMP) { | |
| timestamp = (__u8 *)&(gdata->time_stamp); | |
| sec = bcd2bin(timestamp[0]); | |
| min = bcd2bin(timestamp[1]); | |
| hour = bcd2bin(timestamp[2]); | |
| day = bcd2bin(timestamp[4]); | |
| mon = bcd2bin(timestamp[5]); | |
| year = bcd2bin(timestamp[6]); | |
| century = bcd2bin(timestamp[7]); | |
| printk("%s%ststamp: %02d%02d-%02d-%02d %02d:%02d:%02d\n", pfx, | |
| (timestamp[3] & 0x1 ? "precise " : "imprecise "), | |
| century, year, mon, day, hour, min, sec); | |
| } | |
| } | |
| struct ignore_section { | |
| guid_t guid; | |
| const char *name; | |
| }; | |
| static const struct ignore_section ignore_sections[] = { | |
| { .guid = CPER_SEC_CXL_GEN_MEDIA_GUID, .name = "CXL General Media Event" }, | |
| { .guid = CPER_SEC_CXL_DRAM_GUID, .name = "CXL DRAM Event" }, | |
| { .guid = CPER_SEC_CXL_MEM_MODULE_GUID, .name = "CXL Memory Module Event" }, | |
| }; | |
| static void | |
| cper_estatus_print_section(const char *pfx, struct acpi_hest_generic_data *gdata, | |
| int sec_no) | |
| { | |
| guid_t *sec_type = (guid_t *)gdata->section_type; | |
| __u16 severity; | |
| char newpfx[64]; | |
| if (acpi_hest_get_version(gdata) >= 3) | |
| cper_print_tstamp(pfx, (struct acpi_hest_generic_data_v300 *)gdata); | |
| severity = gdata->error_severity; | |
| printk("%s""Error %d, type: %s\n", pfx, sec_no, | |
| cper_severity_str(severity)); | |
| if (gdata->validation_bits & CPER_SEC_VALID_FRU_ID) | |
| printk("%s""fru_id: %pUl\n", pfx, gdata->fru_id); | |
| if (gdata->validation_bits & CPER_SEC_VALID_FRU_TEXT) | |
| printk("%s""fru_text: %.20s\n", pfx, gdata->fru_text); | |
| snprintf(newpfx, sizeof(newpfx), "%s ", pfx); | |
| for (int i = 0; i < ARRAY_SIZE(ignore_sections); i++) { | |
| if (guid_equal(sec_type, &ignore_sections[i].guid)) { | |
| printk("%ssection_type: %s\n", newpfx, ignore_sections[i].name); | |
| return; | |
| } | |
| } | |
| if (guid_equal(sec_type, &CPER_SEC_PROC_GENERIC)) { | |
| struct cper_sec_proc_generic *proc_err = acpi_hest_get_payload(gdata); | |
| printk("%s""section_type: general processor error\n", newpfx); | |
| if (gdata->error_data_length >= sizeof(*proc_err)) | |
| cper_print_proc_generic(newpfx, proc_err); | |
| else | |
| goto err_section_too_small; | |
| } else if (guid_equal(sec_type, &CPER_SEC_PLATFORM_MEM)) { | |
| struct cper_sec_mem_err *mem_err = acpi_hest_get_payload(gdata); | |
| printk("%s""section_type: memory error\n", newpfx); | |
| if (gdata->error_data_length >= | |
| sizeof(struct cper_sec_mem_err_old)) | |
| cper_print_mem(newpfx, mem_err, | |
| gdata->error_data_length); | |
| else | |
| goto err_section_too_small; | |
| } else if (guid_equal(sec_type, &CPER_SEC_PCIE)) { | |
| struct cper_sec_pcie *pcie = acpi_hest_get_payload(gdata); | |
| printk("%s""section_type: PCIe error\n", newpfx); | |
| if (gdata->error_data_length >= sizeof(*pcie)) | |
| cper_print_pcie(newpfx, pcie, gdata); | |
| else | |
| goto err_section_too_small; | |
| #if defined(CONFIG_ARM64) || defined(CONFIG_ARM) | |
| } else if (guid_equal(sec_type, &CPER_SEC_PROC_ARM)) { | |
| struct cper_sec_proc_arm *arm_err = acpi_hest_get_payload(gdata); | |
| printk("%ssection_type: ARM processor error\n", newpfx); | |
| if (gdata->error_data_length >= sizeof(*arm_err)) | |
| cper_print_proc_arm(newpfx, arm_err, | |
| gdata->error_data_length); | |
| else | |
| goto err_section_too_small; | |
| #endif | |
| #if defined(CONFIG_UEFI_CPER_X86) | |
| } else if (guid_equal(sec_type, &CPER_SEC_PROC_IA)) { | |
| struct cper_sec_proc_ia *ia_err = acpi_hest_get_payload(gdata); | |
| printk("%ssection_type: IA32/X64 processor error\n", newpfx); | |
| if (gdata->error_data_length >= sizeof(*ia_err)) | |
| cper_print_proc_ia(newpfx, ia_err); | |
| else | |
| goto err_section_too_small; | |
| #endif | |
| } else if (guid_equal(sec_type, &CPER_SEC_FW_ERR_REC_REF)) { | |
| struct cper_sec_fw_err_rec_ref *fw_err = acpi_hest_get_payload(gdata); | |
| printk("%ssection_type: Firmware Error Record Reference\n", | |
| newpfx); | |
| /* The minimal FW Error Record contains 16 bytes */ | |
| if (gdata->error_data_length >= SZ_16) | |
| cper_print_fw_err(newpfx, gdata, fw_err); | |
| else | |
| goto err_section_too_small; | |
| } else if (guid_equal(sec_type, &CPER_SEC_CXL_PROT_ERR)) { | |
| struct cxl_cper_sec_prot_err *prot_err = acpi_hest_get_payload(gdata); | |
| printk("%ssection_type: CXL Protocol Error\n", newpfx); | |
| if (gdata->error_data_length >= sizeof(*prot_err)) | |
| cxl_cper_print_prot_err(newpfx, prot_err); | |
| else | |
| goto err_section_too_small; | |
| } else { | |
| const void *err = acpi_hest_get_payload(gdata); | |
| printk("%ssection type: unknown, %pUl\n", newpfx, sec_type); | |
| printk("%ssection length: %#x\n", newpfx, | |
| gdata->error_data_length); | |
| print_hex_dump(newpfx, "", DUMP_PREFIX_OFFSET, 16, 4, err, | |
| gdata->error_data_length, true); | |
| } | |
| return; | |
| err_section_too_small: | |
| pr_err(FW_WARN "error section length is too small\n"); | |
| } | |
| void cper_estatus_print(const char *pfx, | |
| const struct acpi_hest_generic_status *estatus) | |
| { | |
| struct acpi_hest_generic_data *gdata; | |
| int sec_no = 0; | |
| char newpfx[64]; | |
| __u16 severity; | |
| severity = estatus->error_severity; | |
| if (severity == CPER_SEV_CORRECTED) | |
| printk("%s%s\n", pfx, | |
| "It has been corrected by h/w " | |
| "and requires no further action"); | |
| printk("%s""event severity: %s\n", pfx, cper_severity_str(severity)); | |
| snprintf(newpfx, sizeof(newpfx), "%s ", pfx); | |
| apei_estatus_for_each_section(estatus, gdata) { | |
| cper_estatus_print_section(newpfx, gdata, sec_no); | |
| sec_no++; | |
| } | |
| } | |
| EXPORT_SYMBOL_GPL(cper_estatus_print); | |
| int cper_estatus_check_header(const struct acpi_hest_generic_status *estatus) | |
| { | |
| if (estatus->data_length && | |
| estatus->data_length < sizeof(struct acpi_hest_generic_data)) | |
| return -EINVAL; | |
| if (estatus->raw_data_length && | |
| estatus->raw_data_offset < sizeof(*estatus) + estatus->data_length) | |
| return -EINVAL; | |
| return 0; | |
| } | |
| EXPORT_SYMBOL_GPL(cper_estatus_check_header); | |
| int cper_estatus_check(const struct acpi_hest_generic_status *estatus) | |
| { | |
| struct acpi_hest_generic_data *gdata; | |
| unsigned int data_len, record_size; | |
| int rc; | |
| rc = cper_estatus_check_header(estatus); | |
| if (rc) | |
| return rc; | |
| data_len = estatus->data_length; | |
| apei_estatus_for_each_section(estatus, gdata) { | |
| if (acpi_hest_get_size(gdata) > data_len) | |
| return -EINVAL; | |
| record_size = acpi_hest_get_record_size(gdata); | |
| if (record_size > data_len) | |
| return -EINVAL; | |
| data_len -= record_size; | |
| } | |
| if (data_len) | |
| return -EINVAL; | |
| return 0; | |
| } | |
| EXPORT_SYMBOL_GPL(cper_estatus_check); |