Thread (48 messages) 48 messages, 5 authors, 2d ago

Re: [PATCH v18 06/13] PCI: Establish common CXL Port protocol error flow

From: Jonathan Cameron <jic23@kernel.org>
Date: 2026-07-20 23:05:28
Also in: linux-acpi, linux-cxl, linux-doc, linux-pci, lkml

On Fri, 17 Jul 2026 17:26:59 -0500
Terry Bowman [off-list ref] wrote:
Add CXL protocol error dispatch in handle_error_source() using
is_cxl_error() and cxl_forward_error() to route errors through the
AER-CXL kfifo. Expand is_cxl_error() from Endpoint-only to include
Root Port, Upstream Port, and Downstream Port device types. The
producer and consumer go live in the same commit to avoid silently
dropping CXL errors during bisect.
Take a stab at making this description a lot more concise. I'd like
to just be seeing a very brief summary of flow + clearly highlighting
of potentially controversial choices.
For uncorrectable events, call cxl_proto_err_flush() to ensure CXL RAS
registers are read, panic policy is applied, and CXL state is cleared
before pci_aer_handle_error() drives PCIe recovery. Without the flush,
AER recovery can tear down drivers and unmap the CXL RAS iomaps while
the kfifo consumer is still reading them. Correctable events do not
need the flush and run asynchronously. RCH kfifo support is added in
the following patch ("PCI/CXL: Add RCH support to CXL handlers").

Add cxl_handle_proto_error() to dispatch correctable and uncorrectable
errors through the CXL RAS helpers. Add cxl_do_recovery() to coordinate
uncorrectable recovery. Panic when a UCE is confirmed by a successful
CXL RAS status register read. If the RAS registers cannot be read the
UCE cannot be confirmed and panic is not triggered. Gate error handling
on the port driver being bound to avoid processing errors on disabled
devices.

The kfifo consumer holds guard(device)(&port->dev) and checks
port->dev.driver before accessing RAS registers, serializing against
driver unbind and devm iomap teardown. For UCE, cxl_proto_err_flush()
runs the worker synchronously before AER recovery, ensuring the device
is present during RAS register access.

Add to_ras_base() to centralize RAS base lookup: dport->regs.ras for
Root/Downstream Ports, port->regs.ras for Upstream Ports and Endpoints.
Use to_ras_base() to access the CXL devices' RAS registers as it will
provide an avenue to inject status simulation during testing.

Add CXL RAS logging in cxl_handle_cor_ras() and cxl_handle_ras(). The
existing cxl_cor_error_detected() and cxl_error_detected() AER
callbacks remain for all Endpoints and are reworked to use
find_cxl_port_by_uport() and to_ras_base(), with UCE now triggering
panic unconditionally. These callbacks are further updated in the
following patch ("PCI/CXL: Add RCH support to CXL handlers").

Fix a pre-existing race for cxlds between cxl_handle_rdport_errors() and
cxl_memdev_shutdown() by holding a cxlmd device scoped_guard() around
the rdport call. Release the lock before taking the Port lock to avoid
the lock inversion.

Co-developed-by: Dan Williams <djbw@kernel.org>
Signed-off-by: Dan Williams <djbw@kernel.org>
Signed-off-by: Terry Bowman <redacted>
A few minor things about the code inline.

Thanks,

Jonathan
quoted hunk ↗ jump to hunk
diff --git a/drivers/cxl/core/ras.c b/drivers/cxl/core/ras.c
index 135f1997e6f4f..b190e69c2d415 100644
--- a/drivers/cxl/core/ras.c
+++ b/drivers/cxl/core/ras.c
@@ -77,6 +77,36 @@ static int match_memdev_by_parent(struct device *dev, const void *uport)
 	return 0;
 }
 
+
+/**
+ * find_cxl_port_by_dev - Use @dev as hint to do a _by_dport or _by_uport lookup
+ * @dev: generic device that may either be a companion of port or target dport
+ * @dport: output parameter; set to the matched dport for dport-class
I'd state this is optional.
+ * lookups (Root Port, Downstream Port), NULL otherwise.
+ *
+ * Return a 'struct cxl_port' with an elevated reference if found. Use
+ * __free(put_cxl_port) to release.
+ */
+static struct cxl_port *find_cxl_port_by_dev(struct device *dev, struct cxl_dport **dport)
+{
+	if (dport)
+		*dport = NULL;
+	if (!dev_is_pci(dev))
+		return NULL;
+
+	switch (pci_pcie_type(to_pci_dev(dev))) {
+	case PCI_EXP_TYPE_ROOT_PORT:
+	case PCI_EXP_TYPE_DOWNSTREAM:
+		return find_cxl_port_by_dport(dev, dport);
+	case PCI_EXP_TYPE_UPSTREAM:
+	case PCI_EXP_TYPE_ENDPOINT:
+	case PCI_EXP_TYPE_RC_END:
+		return find_cxl_port_by_uport(dev);
+	}
+
+	return NULL;
+}

...
quoted hunk ↗ jump to hunk
@@ -270,22 +326,32 @@ bool cxl_handle_ras(struct device *dev, void __iomem *ras_base)
 
 void cxl_cor_error_detected(struct pci_dev *pdev)
 {
-	struct cxl_dev_state *cxlds = pci_get_drvdata(pdev);
-	struct cxl_memdev *cxlmd = cxlds->cxlmd;
-	struct device *dev = &cxlds->cxlmd->dev;
+	guard(device)(&pdev->dev);
+	if (!pdev->dev.driver)
+		return;
+
+	struct cxl_port *port __free(put_cxl_port) = find_cxl_port_by_uport(&pdev->dev);
+	if (!port)
+		return;
+
+	if (is_cxl_restricted(pdev)) {
+		struct cxl_dev_state *cxlds = pci_get_drvdata(pdev);
+		struct cxl_memdev *cxlmd = cxlds->cxlmd;
 
-	scoped_guard(device, dev) {
-		if (!dev->driver) {
+		scoped_guard(device, &cxlmd->dev) {
+			cxl_handle_rdport_errors(cxlds);
+		}
Unless this gets more complex later you can just use a guard() as exits
scope here anyway.
+	}
+
+	scoped_guard(device, &port->dev) {
Similar here. I haven't read on though for this one so if it's needed
later just ignore me.
quoted hunk ↗ jump to hunk
+		if (!port->dev.driver) {
 			dev_warn(&pdev->dev,
-				 "%s: memdev disabled, abort error handling\n",
-				 dev_name(dev));
+				 "%s: port disabled, abort error handling\n",
+				 dev_name(&port->dev));
 			return;
 		}
 
-		if (cxlds->rcd)
-			cxl_handle_rdport_errors(cxlds);
-
-		cxl_handle_cor_ras(&cxlds->cxlmd->dev, cxlmd->endpoint->regs.ras);
+		cxl_handle_cor_ras(port->uport_dev, to_ras_base(port, NULL));
 	}
 }
 EXPORT_SYMBOL_NS_GPL(cxl_cor_error_detected, "CXL");
@@ -293,42 +359,53 @@ EXPORT_SYMBOL_NS_GPL(cxl_cor_error_detected, "CXL");
 pci_ers_result_t cxl_error_detected(struct pci_dev *pdev,
 				    pci_channel_state_t state)
 {
-	struct cxl_dev_state *cxlds = pci_get_drvdata(pdev);
...
 
+	/*
+	 * CXL.mem UCE means cache coherency is lost. Continuing risks
+	 * silent data corruption across interleaved HDM regions.
It is a problem whether interleave or not.  So maybe just stop
at corruption.
+	 */
+	if (ue)
+		panic("CXL cachemem error");
...
quoted hunk ↗ jump to hunk
@@ -338,3 +415,67 @@ pci_ers_result_t cxl_error_detected(struct pci_dev *pdev,
...
+static void __cxl_proto_err_work_fn(struct cxl_proto_err_work_data *wd)
+{
+	struct cxl_port *port __free(put_cxl_port) = find_cxl_port_by_dev(&wd->pdev->dev, NULL);
+	if (!port) {
+		dev_err_ratelimited(&wd->pdev->dev,
+				    "Failed to find parent port device in CXL topology\n");
+		return;
+	}
I'd put a blank line here.
+	guard(device)(&port->dev);
+	if (!port->dev.driver) {
+		dev_err_ratelimited(&port->dev,
+				    "Port device is unbound, abort error handling\n");
+		return;
+	}
+
+	struct cxl_dport *dport = cxl_find_dport_by_dev(port, &wd->pdev->dev);
+	if (!dport && (pci_pcie_type(wd->pdev) == PCI_EXP_TYPE_ROOT_PORT ||
+		       pci_pcie_type(wd->pdev) == PCI_EXP_TYPE_DOWNSTREAM)) {
+		dev_err_ratelimited(&wd->pdev->dev,
+				    "Failed to find dport device in CXL topology\n");
+		return;
+	}
+
+	cxl_handle_proto_error(wd->pdev, port, dport, wd->severity);
+}
quoted hunk ↗ jump to hunk
diff --git a/drivers/pci/pcie/aer.c b/drivers/pci/pcie/aer.c
index c5bce25df51cb..2d9d40528e709 100644
--- a/drivers/pci/pcie/aer.c
+++ b/drivers/pci/pcie/aer.c
@@ -1185,7 +1185,20 @@ static void pci_aer_handle_error(struct pci_dev *dev, struct aer_err_info *info)
 
 static void handle_error_source(struct pci_dev *dev, struct aer_err_info *info)
 {
+	bool cxl_pending = false;
+
 	cxl_rch_handle_error(dev, info);
+
+	if (is_cxl_error(dev, info))
+		cxl_pending |= cxl_forward_error(dev, info);
+
+	/*
+	 * Wait for UCE CXL work to complete before AER recovery
+	 * tears down the device. CE can run asynchronously.
+	 */
+	if (cxl_pending && info->severity != AER_CORRECTABLE)
+		cxl_proto_err_flush();
This kind of makes me wonder if the naming of _flush() is clear.
Normally I'd kind of expect that to be dumping any queued up errors.

Perhaps cxl_proto_err_wait_for_empty() or something like that?

I guess flush_work() set the precedent so I'm too later on this one!

Jonathan
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help