PCI/AER: Map a raw AER Capability image field by field

struct aer_capability_regs is not the hardware layout: the embedded
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.

extlog_print_pcie(), ghes_handle_aer() and cxl_rch_get_aer_info() work
around that by stopping at the Header Log, dropping everything past
offset 44. Flit mode is never decoded either: the Flit bit and Logged
TLP Size sit at offset 0x18, unread, so the Flit DWORDs at 0x38 are lost
and the log prints as non-Flit.

Add aer_cap_regs_unpack() to map the registers individually, taking the
TLP Log layout from the Flit bit as pcie_read_tlp_log() does. Clamp the
logged length: Logged TLP Size is 5 bits wide, so an untrusted value
reaches 31 where dw[] holds 14 and pcie_print_tlp_log() walks
header_len unbounded.

Take the image little-endian, as both sources are: CPER by definition,
PCIe registers on the wire. Read it with get_unaligned_le32(), since a
CPER section sits wherever firmware put it, and take the two source IDs
out of the Error Source Identification register by bit position rather
than byte offset. cxl_rch_get_aer_info() uses readl(), so it hands back
__le32.

Convert all three callers. cxl_rch_get_aer_info() now reads the whole
MMIO block, so size its ioremap by the hardware registers (96 bytes)
rather than by sizeof(struct aer_capability_regs) (100). Shrinking the
mapping is safe: the read it replaces stopped at 44 bytes.

Reported-by: sashiko-bot@kernel.org
Closes: https://sashiko.dev/#/patchset/20260904172337.1409775-1-dave.jiang@intel.com?part=5
Link: https://lore.kernel.org/linux-cxl/CAJZ5v0hDv11cPuztPZsaDd7uwD_49KznJy=tzuRO+dZc=CnAEQ@mail.gmail.com/
Assisted-by: LLM
Signed-off-by: Dave Jiang <dave.jiang@intel.com>
Reviewed-by: Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
Link: https://patch.msgid.link/20260911164705.841407-1-dave.jiang@intel.com
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
This commit is contained in:
Dave Jiang 2026-09-11 09:47:05 -07:00 committed by Rafael J. Wysocki
parent 13e5c292e0
commit 1a58c47fae
5 changed files with 107 additions and 46 deletions

View File

@ -156,19 +156,7 @@ static void extlog_print_pcie(struct cper_sec_pcie *pcie_err,
aer_severity = cper_severity_to_aer(severity);
/*
* struct pcie_tlp_log is larger than the hardware layout, so aer_info
* only maps onto the struct up to the four Header Log DWORDs. Copy that
* much, then place the TLP Prefix Log from where the hardware keeps it.
* Everything else stays zero: nothing reads root_command, root_status or
* the error source IDs, and header_len and flit are software-only.
*/
memcpy(&aer_regs, pcie_err->aer_info,
offsetof(struct aer_capability_regs, header_log) +
PCIE_STD_NUM_TLP_HEADERLOG * sizeof(u32));
memcpy(aer_regs.header_log.prefix,
pcie_err->aer_info + PCI_ERR_PREFIX_LOG,
sizeof(aer_regs.header_log.prefix));
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;

View File

@ -668,20 +668,7 @@ static void ghes_handle_aer(struct acpi_hest_generic_data *gdata)
if (!aer_info)
return;
/*
* Map aer_info onto the struct as extlog_print_pcie() does:
* copy up to the four Header Log DWORDs, then place the TLP
* Prefix Log from where the hardware keeps it. The rest stays
* zero, so firmware cannot drive the pcie_print_tlp_log() loop
* over dw[] out of bounds.
*/
memset(aer_info, 0, sizeof(struct aer_capability_regs));
memcpy(aer_info, pcie_err->aer_info,
offsetof(struct aer_capability_regs, header_log) +
PCIE_STD_NUM_TLP_HEADERLOG * sizeof(u32));
memcpy(aer_info->header_log.prefix,
pcie_err->aer_info + PCI_ERR_PREFIX_LOG,
sizeof(aer_info->header_log.prefix));
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,

View File

@ -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);

View File

@ -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:"
/**

View File

@ -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);