Linux CXL
 help / color / mirror / Atom feed
From: Jonathan Cameron <jic23@kernel.org>
To: Dave Jiang <dave.jiang@intel.com>
Cc: linux-cxl@vger.kernel.org, linux-pci@vger.kernel.org,
	linux-acpi@vger.kernel.org, bhelgaas@google.com,
	ilpo.jarvinen@linux.intel.com, rafael@kernel.org,
	lenb@kernel.org, bp@alien8.de, guohanjun@huawei.com,
	mchehab@kernel.org, xueshuai@linux.alibaba.com,
	terry.bowman@amd.com, sashiko-bot@kernel.org
Subject: Re: [PATCH] PCI/AER: Map a raw AER Capability image field by field
Date: Mon, 7 Sep 2026 20:35:47 +0100	[thread overview]
Message-ID: <20260907203547.00da6a44@jic23-huawei> (raw)
In-Reply-To: <20260904232609.1729229-1-dave.jiang@intel.com>

On Fri,  4 Sep 2026 16:26:09 -0700
Dave Jiang <dave.jiang@intel.com> wrote:

> struct aer_capability_regs is not the hardware layout: struct
> pcie_tlp_log carries ten extra DWORDs for Flit mode plus the
> software-only header_len and flit, so it spans 60 bytes where the
> Header Log it stands in for is 16.

Good to see this really odd thing being cleaned up..

> 
> extlog_print_pcie(), ghes_handle_aer() and cxl_rch_get_aer_info() work
> around that by stopping the copy 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 in all three images, but nothing
> reads them, 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 natively.
> Clamp the logged length: Logged TLP Size is 5 bits wide, so an
> untrusted value reaches 31 where dw[] holds 14 entries and
> pcie_print_tlp_log() walks header_len unbounded.
> 
> Convert all three callers. cxl_rch_get_aer_info() reads the capability
> from MMIO, so also shrink its ioremap to PCIE_AER_CAP_HW_SIZE; nothing
> touches the RCRB AER block past the TLP Prefix Log.
> 
> 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: Claude:claude-opus-5

Assisted-by: LLM
only.  I missed this but someone else pointed it out in a review
I read yesterday - the docs have been updated to cover this.

> Signed-off-by: Dave Jiang <dave.jiang@intel.com>
> 
Just trivial stuff inline.  On the whole looks good to me

Thanks,

Jonathan

> ---
> Depends on "[PATCH v6 00/13] ACPI: APEI: GHES: Collection of fixes for
> issues reported by sashiko"; please apply after that series:

I see that one got picked up by Rafael.  FWIW I took another look and
all looked good to me as well.

> 
> - Patches 5 and 12 are build dependencies. This patch rewrites the
>   aer_info copies they add. Before them extlog_print_pcie() has a
>   struct aer_capability_regs * rather than the local struct, and
>   ghes_handle_aer() keeps aer_info as a u8 * cast at the
>   aer_recover_queue() call.
> - Patch 7 is a functional dependency: it fixes the ACPI_APEI_PCIEAER
>   guard typo, without which extlog_print_pcie() compiles out.
> 
> The drivers/pci and drivers/cxl changes do not depend on that series.
> 
> No Fixes: tag on purpose: this is not stable material. Patches 5 and
> 12 closed the out-of-bounds reads; what is left is diagnostic
> completeness, and the fix adds a new exported helper and rewrites
> three call sites.
> 
> This is the follow-up Rafael asked for in the Link: above, and also
> covers the pre-existing ras_rch.c findings reported there.
> ---
>  drivers/acpi/acpi_extlog.c | 15 +-----
>  drivers/acpi/apei/ghes.c   | 16 +------
>  drivers/cxl/core/ras_rch.c | 26 ++++-------
>  drivers/pci/pcie/tlp.c     | 94 ++++++++++++++++++++++++++++++++++++++
>  include/linux/aer.h        | 10 ++++
>  5 files changed, 116 insertions(+), 45 deletions(-)
> 
> diff --git a/drivers/acpi/acpi_extlog.c b/drivers/acpi/acpi_extlog.c
> index 9e61354a807b..c83ded0ced52 100644
> --- a/drivers/acpi/acpi_extlog.c
> +++ b/drivers/acpi/acpi_extlog.c
> @@ -156,19 +156,8 @@ 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));

There is already precedence in this file for going over 80 chars so I'd
just put that on one line 
	aer_cap_regs_unpack(&aer_regs, pcie_err->aer_info, sizeof(pcie_err->aer_info));
or if you want to keep it short, group be relationship.
	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;
> diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c
> index 08c985e729d6..5bba3cfdb006 100644
> --- a/drivers/acpi/apei/ghes.c
> +++ b/drivers/acpi/apei/ghes.c
> @@ -668,20 +668,8 @@ 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));

Similar to above.

>  
>  		aer_recover_queue(pcie_err->device_id.segment,
>  				  pcie_err->device_id.bus,
> diff --git a/drivers/cxl/core/ras_rch.c b/drivers/cxl/core/ras_rch.c
> index e0e01aa5eba6..bef1550f91cc 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);

Wasn't this wrong before?  I guess maybe didn't matter but seems superficially
unrelated to the rest of this patch.

>  	}
>  }
>  
> @@ -59,30 +59,20 @@ 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.
> +	 * A flat copy cannot fill the struct; aer_cap_regs_unpack() places
> +	 * the registers and zeroes the software-only tail.
>  	 */
> -	int read_cnt = (PCI_ERR_HEADER_LOG + 16) / sizeof(u32);
> -	u32 *aer_regs_buf = (u32 *)aer_regs;
> +	u32 raw[PCIE_AER_CAP_HW_SIZE / sizeof(u32)];
>  	int n;

Move this into the loop declaration?  Might as well whilst you
are touching this code anyway as trivial cleanup.

>  
>  	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().
> -	 */
> -	memset(aer_regs, 0, sizeof(*aer_regs));
> -
>  	/* Use readl() to guarantee 32-bit accesses */
> -	for (n = 0; n < read_cnt; n++)
> -		aer_regs_buf[n] = readl(aer_base + n * sizeof(u32));
> +	for (n = 0; n < ARRAY_SIZE(raw); n++)
> +		raw[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/pci/pcie/tlp.c b/drivers/pci/pcie/tlp.c
> index 71f8fc9ea2ed..db9a5cd5b2ad 100644
> --- a/drivers/pci/pcie/tlp.c
> +++ b/drivers/pci/pcie/tlp.c
> @@ -8,6 +8,8 @@
>  #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>
>  
> @@ -92,6 +94,98 @@ int pcie_read_tlp_log(struct pci_dev *dev, int where, int where2,
>  	return 0;
>  }
>  
> +/* The registers ahead of the Header Log are the only ones that map 1:1. */
> +static_assert(offsetof(struct aer_capability_regs, header_log) ==
> +	      PCI_ERR_HEADER_LOG);
> +/* The TLP Prefix Log registers carry dw[4..13] in Flit mode; cover all of it. */
> +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
> + * @raw_len: Number of 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 every register behind it. Map them individually instead, taking
> + * the TLP Log layout from the Flit bit as pcie_read_tlp_log() does natively
> + * and clamping the firmware-supplied log length.
> + *
> + * Registers beyond @raw_len are left zero.

Maybe say why raw_len might vary?  I guess it doesn't matter that much.

> + */
> +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;
> +	memcpy(regs, raw, PCI_ERR_HEADER_LOG);
> +
> +	flit = !!(regs->cap_control & PCI_ERR_CAP_TLP_LOG_FLIT);

There is precedence in this file for using FIELD_GET() - and you use it just below. 
Personally I find that more readable than !! if you need to force it to
0/1

> +	if (flit) {
> +		tlp_len = FIELD_GET(PCI_ERR_CAP_TLP_LOG_SIZE, regs->cap_control);
> +	} else {
> +		/*
> +		 * Header Log plus the TLP Prefix Log, which dw[4..7] alias.
> +		 * Take all four prefix registers 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;
> +	}
> +
> +	if (tlp_len > ARRAY_SIZE(regs->header_log.dw))
> +		tlp_len = ARRAY_SIZE(regs->header_log.dw);

	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;
> +		memcpy(&regs->header_log.dw[i], raw + off, sizeof(u32));
> +	}
> +
> +	/*
> +	 * @i is what was actually filled, short of @tlp_len if @raw_len ran
> +	 * out. Non-Flit length needs the TLP parsed, so cap it at 4 as the
> +	 * native path does.
> +	 */
> +	if (flit)
> +		regs->header_log.header_len = i;
> +	else
> +		regs->header_log.header_len =
> +			min_t(unsigned int, i, PCIE_STD_NUM_TLP_HEADERLOG);

min() should be fine given it's unsigned and a small constant.
Maybe I'm missing something - seems the LLMs are a bit keen on 

> +	regs->header_log.flit = flit;
> +
> +	if (raw_len >= PCI_ERR_ROOT_COMMAND + sizeof(regs->root_command))
> +		memcpy(&regs->root_command, raw + PCI_ERR_ROOT_COMMAND,
> +		       sizeof(regs->root_command));
> +	if (raw_len >= PCI_ERR_ROOT_STATUS + sizeof(regs->root_status))
> +		memcpy(&regs->root_status, raw + PCI_ERR_ROOT_STATUS,
> +		       sizeof(regs->root_status));
> +	if (raw_len >= PCI_ERR_ROOT_ERR_SRC + sizeof(regs->cor_err_source) +
> +		       sizeof(regs->uncor_err_source)) {
> +		memcpy(&regs->cor_err_source, raw + PCI_ERR_ROOT_ERR_SRC,
> +		       sizeof(regs->cor_err_source));
> +		memcpy(&regs->uncor_err_source,
> +		       raw + PCI_ERR_ROOT_ERR_SRC + sizeof(regs->cor_err_source),
> +		       sizeof(regs->uncor_err_source));
> +	}
> +}
> +EXPORT_SYMBOL_GPL(aer_cap_regs_unpack);
> +
>  #define EE_PREFIX_STR " E-E Prefixes:"
>  
>  /**
> diff --git a/include/linux/aer.h b/include/linux/aer.h
> index df0f5c382286..140a5923fe8a 100644
> --- a/include/linux/aer.h
> +++ b/include/linux/aer.h
> @@ -24,6 +24,14 @@
>  #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, far enough to hold

long enough

> + * the longest TLP Log the Flit mode registers can carry. struct
> + * aer_capability_regs below is larger and differently laid out; use
> + * aer_cap_regs_unpack() to convert.
> + */
> +#define PCIE_AER_CAP_HW_SIZE		96
> +
>  struct pci_dev;
>  
>  struct pcie_tlp_log {
> @@ -68,6 +76,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);
> 
> base-commit: 86adf9d0f7b074e0a1310c1bc4db66cac5c83eec


  reply	other threads:[~2026-09-07 19:35 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-04 23:26 [PATCH] PCI/AER: Map a raw AER Capability image field by field Dave Jiang
2026-09-07 19:35 ` Jonathan Cameron [this message]
2026-09-08 16:27   ` Dave Jiang

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260907203547.00da6a44@jic23-huawei \
    --to=jic23@kernel.org \
    --cc=bhelgaas@google.com \
    --cc=bp@alien8.de \
    --cc=dave.jiang@intel.com \
    --cc=guohanjun@huawei.com \
    --cc=ilpo.jarvinen@linux.intel.com \
    --cc=lenb@kernel.org \
    --cc=linux-acpi@vger.kernel.org \
    --cc=linux-cxl@vger.kernel.org \
    --cc=linux-pci@vger.kernel.org \
    --cc=mchehab@kernel.org \
    --cc=rafael@kernel.org \
    --cc=sashiko-bot@kernel.org \
    --cc=terry.bowman@amd.com \
    --cc=xueshuai@linux.alibaba.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox