diff --git a/Documentation/firmware-guide/acpi/apei/einj.rst b/Documentation/firmware-guide/acpi/apei/einj.rst index 7d8435d35a18..70fa59bd97d8 100644 --- a/Documentation/firmware-guide/acpi/apei/einj.rst +++ b/Documentation/firmware-guide/acpi/apei/einj.rst @@ -180,8 +180,10 @@ function are specified using param1:: | segment | bus | device | function | reserved | +-------------------------------------------------+ -Anyway, you get the idea, if there's doubt just take a look at the code -in drivers/acpi/apei/einj.c. +Anyway, you get the idea, if there's doubt just take a look at the code: +drivers/acpi/apei/einj-core.c holds the core logic, while CXL-specific +handling lives in drivers/acpi/apei/einj-cxl.c and the shared plumbing +in drivers/acpi/apei/apei-internal.h. An ACPI 5.0 BIOS may also allow vendor-specific errors to be injected. In this case a file named vendor will contain identifying information diff --git a/drivers/acpi/acpi_extlog.c b/drivers/acpi/acpi_extlog.c index 7ad3b36013cc..bf5f7b1a9aea 100644 --- a/drivers/acpi/acpi_extlog.c +++ b/drivers/acpi/acpi_extlog.c @@ -134,22 +134,30 @@ static int print_extlog_rcd(const char *pfx, } static void extlog_print_pcie(struct cper_sec_pcie *pcie_err, - int severity) + int severity, u32 len) { -#ifdef ACPI_APEI_PCIEAER - struct aer_capability_regs *aer; +#ifdef CONFIG_ACPI_APEI_PCIEAER + struct aer_capability_regs aer_regs = {}; struct pci_dev *pdev; unsigned int devfn; unsigned int bus; int aer_severity; int domain; + if (len < sizeof(*pcie_err)) { + pr_warn_ratelimited(FW_WARN + "PCIe error section too small (%u)\n", len); + return; + } + if (!(pcie_err->validation_bits & CPER_PCIE_VALID_DEVICE_ID && pcie_err->validation_bits & CPER_PCIE_VALID_AER_INFO)) return; aer_severity = cper_severity_to_aer(severity); - aer = (struct aer_capability_regs *)pcie_err->aer_info; + + aer_cap_regs_unpack(&aer_regs, pcie_err->aer_info, sizeof(pcie_err->aer_info)); + domain = pcie_err->device_id.segment; bus = pcie_err->device_id.bus; devfn = PCI_DEVFN(pcie_err->device_id.device, @@ -158,28 +166,11 @@ static void extlog_print_pcie(struct cper_sec_pcie *pcie_err, if (!pdev) return; - pci_print_aer(pdev, aer_severity, aer); + pci_print_aer(pdev, aer_severity, &aer_regs); pci_dev_put(pdev); #endif } -static void -extlog_cxl_cper_handle_prot_err(struct cxl_cper_sec_prot_err *prot_err, - int severity) -{ -#ifdef ACPI_APEI_PCIEAER - struct cxl_cper_prot_err_work_data wd; - - if (cxl_cper_sec_prot_err_valid(prot_err)) - return; - - if (cxl_cper_setup_prot_err_work_data(&wd, prot_err, severity)) - return; - - cxl_cper_handle_prot_err(&wd); -#endif -} - static int extlog_print(struct notifier_block *nb, unsigned long val, void *data) { @@ -208,6 +199,15 @@ static int extlog_print(struct notifier_block *nb, unsigned long val, tmp = (struct acpi_hest_generic_status *)elog_buf; + /* + * Bound the length before cper_estatus_check() walks the sections: it + * iterates over data_length, which is not yet known to fit elog_buf. + * cper_estatus_check_header() then rejects a length that wrapped, which + * the bound cannot see. + */ + if (cper_estatus_len(tmp) > ELOG_ENTRY_LEN || cper_estatus_check(tmp)) + return NOTIFY_DONE; + if (!ras_userspace_consumers()) { print_extlog_rcd(NULL, tmp, cpu); goto out; @@ -235,12 +235,14 @@ static int extlog_print(struct notifier_block *nb, unsigned long val, struct cxl_cper_sec_prot_err *prot_err = acpi_hest_get_payload(gdata); - extlog_cxl_cper_handle_prot_err(prot_err, - gdata->error_severity); + cxl_cper_post_prot_err(prot_err, + gdata->error_severity, + gdata->error_data_length); } else if (guid_equal(sec_type, &CPER_SEC_PCIE)) { struct cper_sec_pcie *pcie_err = acpi_hest_get_payload(gdata); - extlog_print_pcie(pcie_err, gdata->error_severity); + extlog_print_pcie(pcie_err, gdata->error_severity, + gdata->error_data_length); } else { void *err = acpi_hest_get_payload(gdata); diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c index fe10ab0e02f6..663dc2e601cf 100644 --- a/drivers/acpi/apei/ghes.c +++ b/drivers/acpi/apei/ghes.c @@ -603,7 +603,7 @@ static bool ghes_handle_arm_hw_error(struct acpi_hest_generic_data *gdata, * and don't filter out 'corrected' error here. */ if (is_cache && has_pa) { - queued = ghes_do_memory_failure(err_info->physical_fault_addr, flags); + queued |= ghes_do_memory_failure(err_info->physical_fault_addr, flags); p += err_info->length; continue; } @@ -642,11 +642,14 @@ static void ghes_handle_aer(struct acpi_hest_generic_data *gdata) #ifdef CONFIG_ACPI_APEI_PCIEAER struct cper_sec_pcie *pcie_err = acpi_hest_get_payload(gdata); + if (gdata->error_data_length < sizeof(*pcie_err)) + return; + if (pcie_err->validation_bits & CPER_PCIE_VALID_DEVICE_ID && pcie_err->validation_bits & CPER_PCIE_VALID_AER_INFO) { + struct aer_capability_regs *aer_info; unsigned int devfn; int aer_severity; - u8 *aer_info; devfn = PCI_DEVFN(pcie_err->device_id.device, pcie_err->device_id.function); @@ -664,13 +667,12 @@ static void ghes_handle_aer(struct acpi_hest_generic_data *gdata) sizeof(struct aer_capability_regs)); if (!aer_info) return; - memcpy(aer_info, pcie_err->aer_info, sizeof(struct aer_capability_regs)); + + aer_cap_regs_unpack(aer_info, pcie_err->aer_info, sizeof(pcie_err->aer_info)); aer_recover_queue(pcie_err->device_id.segment, pcie_err->device_id.bus, - devfn, aer_severity, - (struct aer_capability_regs *) - aer_info); + devfn, aer_severity, aer_info); } #endif } @@ -752,13 +754,13 @@ static DEFINE_KFIFO(cxl_cper_prot_err_fifo, struct cxl_cper_prot_err_work_data, static DEFINE_RAW_SPINLOCK(cxl_cper_prot_err_work_lock); struct work_struct *cxl_cper_prot_err_work; -static void cxl_cper_post_prot_err(struct cxl_cper_sec_prot_err *prot_err, - int severity) +void cxl_cper_post_prot_err(struct cxl_cper_sec_prot_err *prot_err, + int severity, u32 len) { #ifdef CONFIG_ACPI_APEI_PCIEAER struct cxl_cper_prot_err_work_data wd; - if (cxl_cper_sec_prot_err_valid(prot_err)) + if (cxl_cper_sec_prot_err_valid(prot_err, len)) return; guard(raw_spinlock_irqsave)(&cxl_cper_prot_err_work_lock); @@ -777,6 +779,7 @@ static void cxl_cper_post_prot_err(struct cxl_cper_sec_prot_err *prot_err, schedule_work(cxl_cper_prot_err_work); #endif } +EXPORT_SYMBOL_FOR_MODULES(cxl_cper_post_prot_err, "acpi_extlog"); void cxl_cper_register_prot_err_work(struct work_struct *work) { @@ -823,10 +826,15 @@ static DEFINE_RAW_SPINLOCK(cxl_cper_work_lock); struct work_struct *cxl_cper_work; static void cxl_cper_post_event(enum cxl_event_type event_type, - struct cxl_cper_event_rec *rec) + struct cxl_cper_event_rec *rec, u32 len) { struct cxl_cper_work_data wd; + if (len < sizeof(*rec)) { + pr_err(FW_WARN "CXL CPER section too small (%u)\n", len); + return; + } + if (rec->hdr.length <= sizeof(rec->hdr) || rec->hdr.length > sizeof(*rec)) { pr_err(FW_WARN "CXL CPER Invalid section length (%u)\n", @@ -923,6 +931,28 @@ static void ghes_log_hwerr(int sev, guid_t *sec_type) hwerr_log_error_type(HWERR_RECOV_OTHERS); } +/* + * The fields from "extended" on are absent from the 73-byte UEFI 2.1/2.2 + * layout that older firmware still emits. Return the length needed for the + * fields the validation bits claim, so over-claiming is rejected without + * rejecting an honest short record. + */ +static u32 ghes_mem_err_min_len(u64 validation_bits) +{ + u32 len = sizeof(struct cper_sec_mem_err_old); + + if (validation_bits & (CPER_MEM_VALID_ROW_EXT | CPER_MEM_VALID_CHIP_ID)) + len = offsetof(struct cper_sec_mem_err, rank); + if (validation_bits & CPER_MEM_VALID_RANK_NUMBER) + len = offsetof(struct cper_sec_mem_err, mem_array_handle); + if (validation_bits & CPER_MEM_VALID_CARD_HANDLE) + len = offsetof(struct cper_sec_mem_err, mem_dev_handle); + if (validation_bits & CPER_MEM_VALID_MODULE_HANDLE) + len = sizeof(struct cper_sec_mem_err); + + return len; +} + static void ghes_do_proc(struct ghes *ghes, const struct acpi_hest_generic_status *estatus) { @@ -948,30 +978,53 @@ static void ghes_do_proc(struct ghes *ghes, if (guid_equal(sec_type, &CPER_SEC_PLATFORM_MEM)) { struct cper_sec_mem_err *mem_err = acpi_hest_get_payload(gdata); + /* + * Check once for all three consumers below. The 73-byte + * UEFI 2.1/2.2 layout is the floor, matching + * cper_estatus_print_section() and making + * validation_bits safe to read. + */ + if (gdata->error_data_length < + sizeof(struct cper_sec_mem_err_old)) + continue; + + /* Then require what the claimed fields actually need. */ + if (gdata->error_data_length < + ghes_mem_err_min_len(mem_err->validation_bits)) { + pr_warn_ratelimited(FW_WARN GHES_PFX + "memory error section too small (%u) for the fields it claims\n", + gdata->error_data_length); + continue; + } + atomic_notifier_call_chain(&ghes_report_chain, sev, mem_err); arch_apei_report_mem_error(sev, mem_err); - queued = ghes_handle_memory_failure(gdata, sev, sync); + queued |= ghes_handle_memory_failure(gdata, sev, sync); } else if (guid_equal(sec_type, &CPER_SEC_PCIE)) { ghes_handle_aer(gdata); } else if (guid_equal(sec_type, &CPER_SEC_PROC_ARM)) { - queued = ghes_handle_arm_hw_error(gdata, sev, sync); + queued |= ghes_handle_arm_hw_error(gdata, sev, sync); } else if (guid_equal(sec_type, &CPER_SEC_CXL_PROT_ERR)) { struct cxl_cper_sec_prot_err *prot_err = acpi_hest_get_payload(gdata); - cxl_cper_post_prot_err(prot_err, gdata->error_severity); + cxl_cper_post_prot_err(prot_err, gdata->error_severity, + gdata->error_data_length); } else if (guid_equal(sec_type, &CPER_SEC_CXL_GEN_MEDIA_GUID)) { struct cxl_cper_event_rec *rec = acpi_hest_get_payload(gdata); - cxl_cper_post_event(CXL_CPER_EVENT_GEN_MEDIA, rec); + cxl_cper_post_event(CXL_CPER_EVENT_GEN_MEDIA, rec, + gdata->error_data_length); } else if (guid_equal(sec_type, &CPER_SEC_CXL_DRAM_GUID)) { struct cxl_cper_event_rec *rec = acpi_hest_get_payload(gdata); - cxl_cper_post_event(CXL_CPER_EVENT_DRAM, rec); + cxl_cper_post_event(CXL_CPER_EVENT_DRAM, rec, + gdata->error_data_length); } else if (guid_equal(sec_type, &CPER_SEC_CXL_MEM_MODULE_GUID)) { struct cxl_cper_event_rec *rec = acpi_hest_get_payload(gdata); - cxl_cper_post_event(CXL_CPER_EVENT_MEM_MODULE, rec); + cxl_cper_post_event(CXL_CPER_EVENT_MEM_MODULE, rec, + gdata->error_data_length); } else { void *err = acpi_hest_get_payload(gdata); diff --git a/drivers/acpi/apei/ghes_helpers.c b/drivers/acpi/apei/ghes_helpers.c index bc7111b740af..df41b993f413 100644 --- a/drivers/acpi/apei/ghes_helpers.c +++ b/drivers/acpi/apei/ghes_helpers.c @@ -5,8 +5,15 @@ #include #include -int cxl_cper_sec_prot_err_valid(struct cxl_cper_sec_prot_err *prot_err) +int cxl_cper_sec_prot_err_valid(struct cxl_cper_sec_prot_err *prot_err, u32 len) { + if (len < sizeof(*prot_err)) { + pr_err_ratelimited(FW_WARN + "CXL CPER prot err section too small (%u)\n", + len); + return -EINVAL; + } + if (!(prot_err->valid_bits & PROT_ERR_VALID_AGENT_ADDRESS)) { pr_err_ratelimited("CXL CPER invalid agent type\n"); return -EINVAL; @@ -23,6 +30,15 @@ int cxl_cper_sec_prot_err_valid(struct cxl_cper_sec_prot_err *prot_err) return -EINVAL; } + /* The RAS Capability block sits after a firmware-sized DVSEC. */ + if (sizeof(*prot_err) + prot_err->dvsec_len + + sizeof(struct cxl_ras_capability_regs) > len) { + pr_err_ratelimited(FW_WARN + "CXL CPER prot err DVSEC (%u) overruns section (%u)\n", + prot_err->dvsec_len, len); + return -EINVAL; + } + if ((prot_err->agent_type == RCD || prot_err->agent_type == DEVICE || prot_err->agent_type == LD || prot_err->agent_type == FMLD) && !(prot_err->valid_bits & PROT_ERR_VALID_SERIAL_NUMBER)) diff --git a/drivers/acpi/pci_link.c b/drivers/acpi/pci_link.c index e6ed13aee48d..84bfcdf242dd 100644 --- a/drivers/acpi/pci_link.c +++ b/drivers/acpi/pci_link.c @@ -787,6 +787,7 @@ static void acpi_pci_link_remove(struct acpi_device *device) list_del(&link->list); mutex_unlock(&acpi_link_lock); + device->driver_data = NULL; kfree(link); } diff --git a/drivers/cxl/core/ras.c b/drivers/cxl/core/ras.c index e307361bb39e..c91db125a971 100644 --- a/drivers/cxl/core/ras.c +++ b/drivers/cxl/core/ras.c @@ -77,7 +77,7 @@ static int match_memdev_by_parent(struct device *dev, const void *uport) return 0; } -void cxl_cper_handle_prot_err(struct cxl_cper_prot_err_work_data *data) +static void cxl_cper_handle_prot_err(struct cxl_cper_prot_err_work_data *data) { unsigned int devfn = PCI_DEVFN(data->prot_err.agent_addr.device, data->prot_err.agent_addr.function); @@ -118,7 +118,6 @@ void cxl_cper_handle_prot_err(struct cxl_cper_prot_err_work_data *data) else cxl_cper_trace_uncorr_prot_err(cxlmd, data->ras_cap); } -EXPORT_SYMBOL_GPL(cxl_cper_handle_prot_err); static void cxl_cper_prot_err_work_fn(struct work_struct *work) { diff --git a/drivers/cxl/core/ras_rch.c b/drivers/cxl/core/ras_rch.c index e0e01aa5eba6..50abbdc80722 100644 --- a/drivers/cxl/core/ras_rch.c +++ b/drivers/cxl/core/ras_rch.c @@ -19,7 +19,7 @@ void cxl_dport_map_rch_aer(struct cxl_dport *dport) aer_phys = aer_cap + dport->rcrb.base; dport->regs.dport_aer = devm_cxl_iomap_block(host, aer_phys, - sizeof(struct aer_capability_regs)); + PCIE_AER_CAP_HW_SIZE); } } @@ -58,31 +58,20 @@ void cxl_disable_rch_root_ints(struct cxl_dport *dport) static bool cxl_rch_get_aer_info(void __iomem *aer_base, struct aer_capability_regs *aer_regs) { - /* - * Bound the copy to the physically-defined AER registers (header - * through the 16-byte Header Log). struct aer_capability_regs is a - * software layout whose embedded struct pcie_tlp_log is larger than - * the on-wire AER capability; copying sizeof(*aer_regs) would - * over-read the RCRB-mapped MMIO block. - */ - int read_cnt = (PCI_ERR_HEADER_LOG + 16) / sizeof(u32); - u32 *aer_regs_buf = (u32 *)aer_regs; - int n; + /* A flat copy cannot fill the struct; aer_cap_regs_unpack() places it. */ + __le32 raw[PCIE_AER_CAP_HW_SIZE / sizeof(__le32)]; if (!aer_base) return false; /* - * Zero the destination so the software-only tail fields - * (e.g. header_log.header_len) are deterministic rather than - * left as uninitialized stack, which could drive a bogus loop - * length in pcie_print_tlp_log(). + * Use readl() to guarantee 32-bit accesses; it returns host order, so + * put the registers back little-endian for aer_cap_regs_unpack(). */ - memset(aer_regs, 0, sizeof(*aer_regs)); + for (int n = 0; n < ARRAY_SIZE(raw); n++) + raw[n] = cpu_to_le32(readl(aer_base + n * sizeof(u32))); - /* Use readl() to guarantee 32-bit accesses */ - for (n = 0; n < read_cnt; n++) - aer_regs_buf[n] = readl(aer_base + n * sizeof(u32)); + aer_cap_regs_unpack(aer_regs, raw, sizeof(raw)); writel(aer_regs->uncor_status, aer_base + PCI_ERR_UNCOR_STATUS); writel(aer_regs->cor_status, aer_base + PCI_ERR_COR_STATUS); diff --git a/drivers/firmware/efi/cper.c b/drivers/firmware/efi/cper.c index 06b4fdb59917..13b3e72c3b62 100644 --- a/drivers/firmware/efi/cper.c +++ b/drivers/firmware/efi/cper.c @@ -389,10 +389,28 @@ void cper_mem_err_pack(const struct cper_sec_mem_err *mem, 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; + + /* + * These four sit past the end of the UEFI 2.1/2.2 layout, which older + * firmware still emits, so reading them unconditionally runs off a + * short record. Every consumer of the compact record gates them on the + * same validation bits, so leave them zero when firmware does not + * claim them. + */ + cmem->extended = 0; + cmem->rank = 0; + cmem->mem_array_handle = 0; + cmem->mem_dev_handle = 0; + + if (mem->validation_bits & + (CPER_MEM_VALID_ROW_EXT | CPER_MEM_VALID_CHIP_ID)) + cmem->extended = mem->extended; + if (mem->validation_bits & CPER_MEM_VALID_RANK_NUMBER) + cmem->rank = mem->rank; + if (mem->validation_bits & CPER_MEM_VALID_CARD_HANDLE) + cmem->mem_array_handle = mem->mem_array_handle; + if (mem->validation_bits & CPER_MEM_VALID_MODULE_HANDLE) + cmem->mem_dev_handle = mem->mem_dev_handle; } EXPORT_SYMBOL_GPL(cper_mem_err_pack); @@ -745,6 +763,17 @@ int cper_estatus_check_header(const struct acpi_hest_generic_status *estatus) estatus->raw_data_offset < sizeof(*estatus) + estatus->data_length) return -EINVAL; + /* + * cper_estatus_len() sums these into a u32, and a wrapped sum reads + * back smaller than the record. Reject a length that cannot be + * expressed so no caller is handed the short value. + */ + if ((u64)sizeof(*estatus) + estatus->data_length > U32_MAX) + return -EINVAL; + if (estatus->raw_data_length && + (u64)estatus->raw_data_offset + estatus->raw_data_length > U32_MAX) + return -EINVAL; + return 0; } EXPORT_SYMBOL_GPL(cper_estatus_check_header); @@ -752,7 +781,7 @@ 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; + unsigned int data_len; int rc; rc = cper_estatus_check_header(estatus); @@ -762,10 +791,18 @@ int cper_estatus_check(const struct acpi_hest_generic_status *estatus) data_len = estatus->data_length; apei_estatus_for_each_section(estatus, gdata) { - if (acpi_hest_get_size(gdata) > data_len) + int record_size; + + /* + * The helpers sum these as a signed int, so a + * huge error_data_length wraps small rather than large and the + * walk then advances by that wrapped value. Reject a size an + * int cannot carry. + */ + if (check_add_overflow(acpi_hest_get_size(gdata), + gdata->error_data_length, &record_size)) return -EINVAL; - record_size = acpi_hest_get_record_size(gdata); if (record_size > data_len) return -EINVAL; diff --git a/drivers/pci/pcie/tlp.c b/drivers/pci/pcie/tlp.c index 71f8fc9ea2ed..7f94a09ac7c7 100644 --- a/drivers/pci/pcie/tlp.c +++ b/drivers/pci/pcie/tlp.c @@ -8,8 +8,11 @@ #include #include #include +#include +#include #include #include +#include #include "../pci.h" @@ -92,6 +95,91 @@ int pcie_read_tlp_log(struct pci_dev *dev, int where, int where2, return 0; } +/* The Prefix Log registers hold dw[4..13] in Flit mode. */ +static_assert((PCI_ERR_PREFIX_LOG + + (PCIE_STD_MAX_TLP_HEADERLOG - PCIE_STD_NUM_TLP_HEADERLOG) * + sizeof(u32)) == PCIE_AER_CAP_HW_SIZE); + +/** + * aer_cap_regs_unpack - Convert a raw AER Capability image to the kernel layout + * @regs: Destination, fully initialised + * @raw: AER Capability register block in hardware order, little-endian + * @raw_len: Bytes readable at @raw + * + * struct aer_capability_regs is not the hardware layout: struct pcie_tlp_log + * spans 60 bytes where the Header Log it stands in for is 16, so a flat copy + * misplaces everything behind it. Map the registers one by one instead, + * taking the TLP Log layout from the Flit bit as pcie_read_tlp_log() does. + * + * @raw is not necessarily aligned. Registers past @raw_len are left zero, so + * a short image cannot be read past. + */ +void aer_cap_regs_unpack(struct aer_capability_regs *regs, const void *raw, + size_t raw_len) +{ + unsigned int i, tlp_len; + bool flit; + + memset(regs, 0, sizeof(*regs)); + + if (raw_len < PCI_ERR_HEADER_LOG) + return; + + /* The Extended Capability Header, at offset 0, has no define */ + regs->header = get_unaligned_le32(raw); + regs->uncor_status = get_unaligned_le32(raw + PCI_ERR_UNCOR_STATUS); + regs->uncor_mask = get_unaligned_le32(raw + PCI_ERR_UNCOR_MASK); + regs->uncor_severity = get_unaligned_le32(raw + PCI_ERR_UNCOR_SEVER); + regs->cor_status = get_unaligned_le32(raw + PCI_ERR_COR_STATUS); + regs->cor_mask = get_unaligned_le32(raw + PCI_ERR_COR_MASK); + regs->cap_control = get_unaligned_le32(raw + PCI_ERR_CAP); + + flit = FIELD_GET(PCI_ERR_CAP_TLP_LOG_FLIT, regs->cap_control); + if (flit) { + tlp_len = FIELD_GET(PCI_ERR_CAP_TLP_LOG_SIZE, regs->cap_control); + } else { + /* + * dw[4..7] alias the Prefix Log. Take all four whatever + * eetlp_prefix_max says; pcie_print_tlp_log() stops at the + * first zero one. + */ + tlp_len = PCIE_STD_NUM_TLP_HEADERLOG + PCIE_STD_MAX_TLP_PREFIXLOG; + } + + tlp_len = min(tlp_len, ARRAY_SIZE(regs->header_log.dw)); + + for (i = 0; i < tlp_len; i++) { + unsigned int off; + + if (i < PCIE_STD_NUM_TLP_HEADERLOG) + off = PCI_ERR_HEADER_LOG + i * sizeof(u32); + else + off = PCI_ERR_PREFIX_LOG + + (i - PCIE_STD_NUM_TLP_HEADERLOG) * sizeof(u32); + + if (off + sizeof(u32) > raw_len) + break; + regs->header_log.dw[i] = get_unaligned_le32(raw + off); + } + + /* @i may be short of tlp_len; non-Flit needs the TLP parsed, so cap at 4 */ + regs->header_log.header_len = flit ? i : min(i, PCIE_STD_NUM_TLP_HEADERLOG); + regs->header_log.flit = flit; + + if (raw_len >= PCI_ERR_ROOT_COMMAND + sizeof(u32)) + regs->root_command = get_unaligned_le32(raw + PCI_ERR_ROOT_COMMAND); + if (raw_len >= PCI_ERR_ROOT_STATUS + sizeof(u32)) + regs->root_status = get_unaligned_le32(raw + PCI_ERR_ROOT_STATUS); + if (raw_len >= PCI_ERR_ROOT_ERR_SRC + sizeof(u32)) { + u32 src = get_unaligned_le32(raw + PCI_ERR_ROOT_ERR_SRC); + + /* One register: ERR_COR in 15:0, ERR_FATAL/NONFATAL in 31:16 */ + regs->cor_err_source = src; + regs->uncor_err_source = src >> 16; + } +} +EXPORT_SYMBOL_GPL(aer_cap_regs_unpack); + #define EE_PREFIX_STR " E-E Prefixes:" /** diff --git a/include/acpi/ghes.h b/include/acpi/ghes.h index 8d7e5caef3f1..7acf209061ea 100644 --- a/include/acpi/ghes.h +++ b/include/acpi/ghes.h @@ -85,6 +85,10 @@ int devm_ghes_register_vendor_record_notifier(struct device *dev, struct list_head *ghes_get_devices(void); void ghes_estatus_pool_region_free(unsigned long addr, u32 size); + +struct cxl_cper_sec_prot_err; +void cxl_cper_post_prot_err(struct cxl_cper_sec_prot_err *prot_err, + int severity, u32 len); #else static inline struct list_head *ghes_get_devices(void) { return NULL; } diff --git a/include/cxl/event.h b/include/cxl/event.h index b5673384d930..a9f5c2381cc9 100644 --- a/include/cxl/event.h +++ b/include/cxl/event.h @@ -312,13 +312,13 @@ static inline int cxl_cper_prot_err_kfifo_get(struct cxl_cper_prot_err_work_data #endif #ifdef CONFIG_ACPI_APEI_PCIEAER -int cxl_cper_sec_prot_err_valid(struct cxl_cper_sec_prot_err *prot_err); +int cxl_cper_sec_prot_err_valid(struct cxl_cper_sec_prot_err *prot_err, u32 len); int cxl_cper_setup_prot_err_work_data(struct cxl_cper_prot_err_work_data *wd, struct cxl_cper_sec_prot_err *prot_err, int severity); #else static inline int -cxl_cper_sec_prot_err_valid(struct cxl_cper_sec_prot_err *prot_err) +cxl_cper_sec_prot_err_valid(struct cxl_cper_sec_prot_err *prot_err, u32 len) { return -EOPNOTSUPP; } @@ -331,6 +331,4 @@ cxl_cper_setup_prot_err_work_data(struct cxl_cper_prot_err_work_data *wd, } #endif -void cxl_cper_handle_prot_err(struct cxl_cper_prot_err_work_data *wd); - #endif /* _LINUX_CXL_EVENT_H */ diff --git a/include/linux/aer.h b/include/linux/aer.h index df0f5c382286..043ee76b6f53 100644 --- a/include/linux/aer.h +++ b/include/linux/aer.h @@ -24,6 +24,13 @@ #define PCIE_STD_MAX_TLP_PREFIXLOG 4 #define PCIE_STD_MAX_TLP_HEADERLOG (PCIE_STD_NUM_TLP_HEADERLOG + 10) +/* + * Size of the AER Capability register block in hardware, long enough for the + * longest Flit mode TLP Log. struct aer_capability_regs below is larger and + * laid out differently; use aer_cap_regs_unpack() to convert. + */ +#define PCIE_AER_CAP_HW_SIZE 96 + struct pci_dev; struct pcie_tlp_log { @@ -68,6 +75,8 @@ static inline void pci_aer_unmask_internal_errors(struct pci_dev *dev) { } void pci_print_aer(struct pci_dev *dev, int aer_severity, struct aer_capability_regs *aer); +void aer_cap_regs_unpack(struct aer_capability_regs *regs, const void *raw, + size_t raw_len); int cper_severity_to_aer(int cper_severity); void aer_recover_queue(int domain, unsigned int bus, unsigned int devfn, int severity, struct aer_capability_regs *aer_regs);