root/drivers/firmware/efi/cper.c
// 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);