Re: [PATCH v18 06/13] PCI: Establish common CXL Port protocol error flow
Jonathan Cameron <[email protected]>
| Newsgroups | org.kernel.vger.linux-cxl,org.kernel.vger.linux-acpi,org.kernel.vger.linux-doc,org.kernel.vger.linux-kernel,org.kernel.vger.linux-pci,org.ozlabs.lists.linuxppc-dev |
|---|---|
| Message-ID | <20260721000520.00339b52@jic23-huawei> |
On Fri, 17 Jul 2026 17:26:59 -0500 Terry Bowman <[email protected]> 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 <[email protected]> > Signed-off-by: Dan Williams <[email protected]> > Signed-off-by: Terry Bowman <[email protected]> > A few minor things about the code inline. Thanks, Jonathan > 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; > +} ... > @@ -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. > + 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"); ... > @@ -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); > +} > 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