mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
Merge branches 'acpi-pci' and 'acpi-apei'
Merge an ACPI support change related to PCI and ACPI APEI support updates for 7.4: - Clear driver_data before free in the ACPI PCI Link driver to prevent a dangling pointer from being used later (Jiale Yao) - Accumulate the queued flag across error sections of a CPER record in ghes_do_proc() to prevent it from being cleared by a later section or entry that does not queue memory-failure work (Breno Leitao) - Map a raw PCI AER Capability image field by field to prevent information loss from occurring while processing it (Dave Jiang) - Update a stale driver source reference in the ACPI APEI EINJ driver documentation (Prabhakar Pujeri) - Reject CPER records with an out-of-range error_data_length or an error status block length wrapping a u32 in the EFI CPER driver (Dave Jiang) - Address multiple assorted issues in the ACPI extlog driver (Dave Jiang) - Address two issues related to CXL in the ACPI APEI GHES driver (Dave Jiang) - Read only validated fields in cper_mem_err_pack() (Dave Jiang) - Validate memory error section length before payload access, prevent writes past array bounds from occurring and sanitize software metadata in the ACPI APEI GHES driver (Dave Jiang) - Make cxl_cper_handle_prot_err() static (Dave Jiang) * acpi-pci: ACPI: PCI: pci_link: clear driver_data before free * acpi-apei: ACPI: APEI: accumulate the queued flag across error sections PCI/AER: Map a raw AER Capability image field by field Documentation: ACPI: EINJ: Update stale driver source reference cxl/ras: Make cxl_cper_handle_prot_err() static ACPI: APEI: GHES: Bound AER info copy and sanitize software metadata ACPI: APEI: GHES: Validate memory error section length before payload access efi/cper: Read only validated fields in cper_mem_err_pack() ACPI: APEI: GHES: Validate CXL protocol error section length before RAS cap copy ACPI: APEI: GHES: Bound CXL event record copy to the firmware section length ACPI: extlog: Fix CONFIG_ACPI_APEI_PCIEAER guard typo ACPI: extlog: Validate PCIe error section length before payload access ACPI: extlog: Avoid populating software AER metadata from raw hardware buffer ACPI: extlog: Defer CXL protocol error handling to avoid lock inversion ACPI: extlog: Validate elog record length before walking sections efi/cper: Reject an error status block length that wraps a u32 efi/cper: Reject CPER records with an out-of-range error_data_length
This commit is contained in:
commit
6a71f067f7
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -5,8 +5,15 @@
|
|||
#include <linux/aer.h>
|
||||
#include <cxl/event.h>
|
||||
|
||||
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))
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 <acpi/ghes.h> 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;
|
||||
|
||||
|
|
|
|||
|
|
@ -8,8 +8,11 @@
|
|||
#include <linux/aer.h>
|
||||
#include <linux/array_size.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/build_bug.h>
|
||||
#include <linux/minmax.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/unaligned.h>
|
||||
|
||||
#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:"
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user