| Message ID | 20260902133933.2992457-3-terry.bowman@amd.com |
|---|---|
| State | New |
| Headers | show |
| Series | Enable CXL PCIe Port Protocol Error handling and logging | expand |
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] CXL protocol errors on RCD Endpoints bypass the AER-CXL kfifo, leading to dropped correctable errors and UCE race conditions. -- commit 6e514f9fb46669ba2eab05ced2511b382df00bce Author: Terry Bowman <terry.bowman@amd.com> PCI: Establish common CXL Port protocol error flow This commit establishes a single CXL protocol error path shared by Virtual Hierarchy and Restricted CXL Host topologies. It routes CXL protocol errors, gated by is_cxl_error(), through an AER-CXL kfifo to a core consumer for logging and recovery to ensure synchronization. > diff --git a/drivers/pci/pcie/aer_cxl_rch.c b/drivers/pci/pcie/aer_cxl_rch.c > --- a/drivers/pci/pcie/aer_cxl_rch.c > +++ b/drivers/pci/pcie/aer_cxl_rch.c [ ... ] > -void cxl_rch_handle_error(struct pci_dev *dev, struct aer_err_info *info) > +bool cxl_rch_handle_error(struct pci_dev *dev, struct aer_err_info *info) > { > + struct cxl_rch_error_ctx ctx = { .info = info }; > + > /* > - * Internal errors of an RCEC indicate an AER error in an > - * RCH's downstream port. Check and handle them in the CXL.mem > - * device driver. > + * An RCEC AER internal error indicates an error in an > + * associated RCH Downstream Port or RCD device or both. > + * Forward to the cxl_core module for handling. > */ > if (pci_pcie_type(dev) == PCI_EXP_TYPE_RC_EC && > is_aer_internal_error(info)) > - pcie_walk_rcec(dev, cxl_rch_handle_error_iter, info); > + pcie_walk_rcec(dev, cxl_rch_handle_error_iter, &ctx); > + > + return ctx.enqueued; > } [Severity: High] Does this check accidentally skip errors logged directly by the RCD itself? When an RCD detects an error, it logs it in its own AER capability and the RCEC raises the interrupt. The AER driver's find_source_device() then walks the RCEC's children, finds the RCD with the set AER status, and calls handle_error_source() with the RCD device. Because this condition strictly checks for PCI_EXP_TYPE_RC_EC, it intercepts errors from the RCEC itself (like RCH Downstream Port errors), but bypasses handling when the RCD device itself is passed in. > diff --git a/drivers/pci/pcie/aer_cxl_vh.c b/drivers/pci/pcie/aer_cxl_vh.c > --- a/drivers/pci/pcie/aer_cxl_vh.c > +++ b/drivers/pci/pcie/aer_cxl_vh.c [ ... ] > @@ -54,8 +54,22 @@ bool is_cxl_error(struct pci_dev *pdev, struct aer_err_info *info) > if (!info || !info->is_cxl) > return false; > > - if (pci_pcie_type(pdev) != PCI_EXP_TYPE_ENDPOINT) > + /* > + * RCD (PCI_EXP_TYPE_RC_END) is not included here because RCDs > + * report errors on behalf of upstream RCH Downstream Port and thus > + * require a unique discovery detailed in CXL4.0 spec (12.2.1.1). > + * The RCH device error discovery and RCD forwarding flow begins > + * in cxl_rch_handle_error(). > + */ > + switch (pci_pcie_type(pdev)) { > + case PCI_EXP_TYPE_ENDPOINT: > + case PCI_EXP_TYPE_ROOT_PORT: > + case PCI_EXP_TYPE_UPSTREAM: > + case PCI_EXP_TYPE_DOWNSTREAM: > + break; > + default: > return false; > + } > > return is_aer_internal_error(info); > } [Severity: High] Since RCDs are excluded here, does this bypass the AER-CXL kfifo entirely for RCD protocol errors? As noted above, cxl_rch_handle_error() skips the RCD device. With the RCD also falling through to the default case here and returning false, these errors are never enqueued. This results in correctable errors being silently dropped without being traced or cleared (as the legacy cor_error_detected fallback was removed). For uncorrectable errors, it bypasses the kfifo's synchronization, defeating the race-condition fix and leaving the system vulnerable to teardown races.
On 9/2/2026 8:57 AM, sashiko-bot@kernel.org wrote: > Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: > - [High] CXL protocol errors on RCD Endpoints bypass the AER-CXL kfifo, leading to dropped correctable errors and UCE race conditions. > -- > > commit 6e514f9fb46669ba2eab05ced2511b382df00bce > Author: Terry Bowman <terry.bowman@amd.com> > > PCI: Establish common CXL Port protocol error flow > > This commit establishes a single CXL protocol error path shared by Virtual > Hierarchy and Restricted CXL Host topologies. It routes CXL protocol errors, > gated by is_cxl_error(), through an AER-CXL kfifo to a core consumer for > logging and recovery to ensure synchronization. > >> diff --git a/drivers/pci/pcie/aer_cxl_rch.c b/drivers/pci/pcie/aer_cxl_rch.c >> --- a/drivers/pci/pcie/aer_cxl_rch.c >> +++ b/drivers/pci/pcie/aer_cxl_rch.c > [ ... ] >> -void cxl_rch_handle_error(struct pci_dev *dev, struct aer_err_info *info) >> +bool cxl_rch_handle_error(struct pci_dev *dev, struct aer_err_info *info) >> { >> + struct cxl_rch_error_ctx ctx = { .info = info }; >> + >> /* >> - * Internal errors of an RCEC indicate an AER error in an >> - * RCH's downstream port. Check and handle them in the CXL.mem >> - * device driver. >> + * An RCEC AER internal error indicates an error in an >> + * associated RCH Downstream Port or RCD device or both. >> + * Forward to the cxl_core module for handling. >> */ >> if (pci_pcie_type(dev) == PCI_EXP_TYPE_RC_EC && >> is_aer_internal_error(info)) >> - pcie_walk_rcec(dev, cxl_rch_handle_error_iter, info); >> + pcie_walk_rcec(dev, cxl_rch_handle_error_iter, &ctx); >> + >> + return ctx.enqueued; >> } > > [Severity: High] > Does this check accidentally skip errors logged directly by the RCD itself? > No. RCH and RCD errors are reported by the platform to the AER agent using RCECs. SW must 'discover' which RCiEP(s) managed by the RCEC have an error. > When an RCD detects an error, it logs it in its own AER capability and the > RCEC raises the interrupt. The AER driver's find_source_device() then walks > the RCEC's children, finds the RCD with the set AER status, and calls > handle_error_source() with the RCD device. > > Because this condition strictly checks for PCI_EXP_TYPE_RC_EC, it intercepts > errors from the RCEC itself (like RCH Downstream Port errors), but bypasses > handling when the RCD device itself is passed in. > >> diff --git a/drivers/pci/pcie/aer_cxl_vh.c b/drivers/pci/pcie/aer_cxl_vh.c >> --- a/drivers/pci/pcie/aer_cxl_vh.c >> +++ b/drivers/pci/pcie/aer_cxl_vh.c > [ ... ] >> @@ -54,8 +54,22 @@ bool is_cxl_error(struct pci_dev *pdev, struct aer_err_info *info) >> if (!info || !info->is_cxl) >> return false; >> >> - if (pci_pcie_type(pdev) != PCI_EXP_TYPE_ENDPOINT) >> + /* >> + * RCD (PCI_EXP_TYPE_RC_END) is not included here because RCDs >> + * report errors on behalf of upstream RCH Downstream Port and thus >> + * require a unique discovery detailed in CXL4.0 spec (12.2.1.1). >> + * The RCH device error discovery and RCD forwarding flow begins >> + * in cxl_rch_handle_error(). >> + */ >> + switch (pci_pcie_type(pdev)) { >> + case PCI_EXP_TYPE_ENDPOINT: >> + case PCI_EXP_TYPE_ROOT_PORT: >> + case PCI_EXP_TYPE_UPSTREAM: >> + case PCI_EXP_TYPE_DOWNSTREAM: >> + break; >> + default: >> return false; >> + } >> >> return is_aer_internal_error(info); >> } > > [Severity: High] > Since RCDs are excluded here, does this bypass the AER-CXL kfifo entirely for > RCD protocol errors? > is_cxl_error() is used to verify VH devices and is intentionally not used for RCH/RCDs. Below is the flow for RCH/RCDs: RCEC internal error → cxl_rch_handle_error(RC_EC) → pcie_walk_rcec() → cxl_rch_handle_error_iter(RCD) → is_cxl_mem_dev(RCD) && cxl_error_is_native(RCD) → cxl_forward_error(RCD, info) ← ENQUEUED to the kfifo CXL r4.0 12.2.1.1 addresses RCH/RCD error handling and provides contrast to VH mode. -Terry > As noted above, cxl_rch_handle_error() skips the RCD device. With the RCD > also falling through to the default case here and returning false, these > errors are never enqueued. > > This results in correctable errors being silently dropped without being > traced or cleared (as the legacy cor_error_detected fallback was removed). > For uncorrectable errors, it bypasses the kfifo's synchronization, defeating > the race-condition fix and leaving the system vulnerable to teardown races. >
On 9/2/2026 8:39 AM, Terry Bowman wrote: > Establish a single CXL protocol error path shared by CXL Virtual > Hierarchy (VH) and Restricted CXL Host (RCH) topologies. AER dispatch in > handle_error_source() routes CXL protocol errors, gated by > is_cxl_error(), through the AER-CXL kfifo to a cxl_core consumer for > logging and recovery. Producer and consumer go live together so no CXL > error is silently dropped across a bisect. > > is_cxl_error() expands from Endpoint-only to also cover Root Port, > Upstream Port, and Downstream Port. RCDs report on behalf of an upstream > RCH Downstream Port and instead reach the kfifo via > cxl_rch_handle_error(). > > For uncorrectable errors, cxl_proto_err_wait_for_empty() drains the CXL > plane (RAS read, panic policy, state clear) before pci_aer_handle_error() > drives PCIe recovery, so recovery does not tear down RAS iomaps while the > consumer is still reading them. Correctable errors run asynchronously. > > Panic policy: cxl_do_recovery() panics on a confirmed UCE, and also when > the RAS registers cannot be mapped -- an unconfirmable UCE is treated > conservatively as fatal since CXL.mem coherency may be lost. A > mapped-but-clear status is logged as spurious with no panic. > > to_ras_base() centralizes RAS base lookup (dport->regs.ras for > Root/Downstream Ports, port->regs.ras otherwise) and provides an > injection point for RAS status simulation during testing. The > cxl_cor_error_detected() AER callback is removed; correctable Endpoint > errors now route through the kfifo like every other CXL protocol error. > > Update cxl_handle_rdport_errors() with locking to prevent dport from > being freed and RAS from being unmapped. > > At this step cxl_handle_rdport_errors() still dispatches a single > severity per pass (matching the pre-series baseline). The following > patch, "cxl/ras: Handle RCH correctable and uncorrectable errors in one > pass", processes a simultaneously signalled CE and UCE together. > > Co-developed-by: Dan Williams <djbw@kernel.org> > Signed-off-by: Dan Williams <djbw@kernel.org> > Signed-off-by: Terry Bowman <terry.bowman@amd.com> > > --- > One small nit, but otherwise LGTM: Reviewed-by: Ben Cheatham <benjamin.cheatham@amd.com> ... > 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); > - struct cxl_memdev *cxlmd = cxlds->cxlmd; > - struct device *dev = &cxlmd->dev; > - bool ue; > + struct cxl_port *port __free(put_cxl_port) = find_cxl_port_by_uport(&pdev->dev); > + bool ue = false; > + > + if (!port) > + return PCI_ERS_RESULT_DISCONNECT; > + > + if (is_cxl_restricted(pdev)) > + cxl_handle_rdport_errors(pdev); > > - scoped_guard(device, dev) { > - if (!dev->driver) { > + scoped_guard(device, &port->dev) { > + 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 PCI_ERS_RESULT_DISCONNECT; > } > > - if (cxlds->rcd) > - cxl_handle_rdport_errors(cxlds); > /* > - * A frozen channel indicates an impending reset which is fatal to > - * CXL.mem operation, and will likely crash the system. On the off > - * chance the situation is recoverable dump the status of the RAS > - * capability registers and bounce the active state of the memdev. > + * The CXL RAS read is unconditional regardless of channel > + * state. Any uncorrectable error bit set in the CXL RAS > + * status register triggers a panic below because CXL.mem > + * cache coherency is already lost; continuing risks silent > + * data corruption. > */ > - ue = cxl_handle_ras(&cxlds->cxlmd->dev, cxlmd->endpoint->regs.ras); > + ue = cxl_handle_ras(port->uport_dev, to_ras_base(port, NULL)); > } > > + /* > + * CXL.mem UCE means cache coherency is lost. Continuing risks > + * silent data corruption. > + */ Don't need this comment and the last sentence in the comment above. > + if (ue) > + panic("CXL cachemem error"); > + > switch (state) { > case pci_channel_io_normal: > - if (ue) { > - device_release_driver(dev); > - return PCI_ERS_RESULT_NEED_RESET; > - } > return PCI_ERS_RESULT_CAN_RECOVER; > case pci_channel_io_frozen: > dev_warn(&pdev->dev, > "%s: frozen state error detected, disable CXL.mem\n", > - dev_name(dev)); > - device_release_driver(dev); > + dev_name(port->uport_dev)); > + device_release_driver(port->uport_dev); > return PCI_ERS_RESULT_NEED_RESET; > case pci_channel_io_perm_failure: > dev_warn(&pdev->dev, > @@ -335,3 +371,82 @@ pci_ers_result_t cxl_error_detected(struct pci_dev *pdev, > return PCI_ERS_RESULT_NEED_RESET; > } > EXPORT_SYMBOL_NS_GPL(cxl_error_detected, "CXL");
On Wed, 2 Sep 2026 08:39:26 -0500 Terry Bowman <terry.bowman@amd.com> wrote: > Establish a single CXL protocol error path shared by CXL Virtual > Hierarchy (VH) and Restricted CXL Host (RCH) topologies. AER dispatch in > handle_error_source() routes CXL protocol errors, gated by > is_cxl_error(), through the AER-CXL kfifo to a cxl_core consumer for > logging and recovery. Producer and consumer go live together so no CXL > error is silently dropped across a bisect. > > is_cxl_error() expands from Endpoint-only to also cover Root Port, > Upstream Port, and Downstream Port. RCDs report on behalf of an upstream > RCH Downstream Port and instead reach the kfifo via > cxl_rch_handle_error(). > > For uncorrectable errors, cxl_proto_err_wait_for_empty() drains the CXL > plane (RAS read, panic policy, state clear) before pci_aer_handle_error() > drives PCIe recovery, so recovery does not tear down RAS iomaps while the > consumer is still reading them. Correctable errors run asynchronously. > > Panic policy: cxl_do_recovery() panics on a confirmed UCE, and also when > the RAS registers cannot be mapped -- an unconfirmable UCE is treated > conservatively as fatal since CXL.mem coherency may be lost. A > mapped-but-clear status is logged as spurious with no panic. > > to_ras_base() centralizes RAS base lookup (dport->regs.ras for > Root/Downstream Ports, port->regs.ras otherwise) and provides an > injection point for RAS status simulation during testing. The > cxl_cor_error_detected() AER callback is removed; correctable Endpoint > errors now route through the kfifo like every other CXL protocol error. > > Update cxl_handle_rdport_errors() with locking to prevent dport from > being freed and RAS from being unmapped. > > At this step cxl_handle_rdport_errors() still dispatches a single > severity per pass (matching the pre-series baseline). The following > patch, "cxl/ras: Handle RCH correctable and uncorrectable errors in one > pass", processes a simultaneously signalled CE and UCE together. > > Co-developed-by: Dan Williams <djbw@kernel.org> > Signed-off-by: Dan Williams <djbw@kernel.org> > Signed-off-by: Terry Bowman <terry.bowman@amd.com> > Reviewed-by: Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
On 9/2/2026 3:57 PM, Cheatham, Benjamin wrote: > On 9/2/2026 8:39 AM, Terry Bowman wrote: >> Establish a single CXL protocol error path shared by CXL Virtual >> Hierarchy (VH) and Restricted CXL Host (RCH) topologies. AER dispatch in >> handle_error_source() routes CXL protocol errors, gated by >> is_cxl_error(), through the AER-CXL kfifo to a cxl_core consumer for >> logging and recovery. Producer and consumer go live together so no CXL >> error is silently dropped across a bisect. >> >> is_cxl_error() expands from Endpoint-only to also cover Root Port, >> Upstream Port, and Downstream Port. RCDs report on behalf of an upstream >> RCH Downstream Port and instead reach the kfifo via >> cxl_rch_handle_error(). >> >> For uncorrectable errors, cxl_proto_err_wait_for_empty() drains the CXL >> plane (RAS read, panic policy, state clear) before pci_aer_handle_error() >> drives PCIe recovery, so recovery does not tear down RAS iomaps while the >> consumer is still reading them. Correctable errors run asynchronously. >> >> Panic policy: cxl_do_recovery() panics on a confirmed UCE, and also when >> the RAS registers cannot be mapped -- an unconfirmable UCE is treated >> conservatively as fatal since CXL.mem coherency may be lost. A >> mapped-but-clear status is logged as spurious with no panic. >> >> to_ras_base() centralizes RAS base lookup (dport->regs.ras for >> Root/Downstream Ports, port->regs.ras otherwise) and provides an >> injection point for RAS status simulation during testing. The >> cxl_cor_error_detected() AER callback is removed; correctable Endpoint >> errors now route through the kfifo like every other CXL protocol error. >> >> Update cxl_handle_rdport_errors() with locking to prevent dport from >> being freed and RAS from being unmapped. >> >> At this step cxl_handle_rdport_errors() still dispatches a single >> severity per pass (matching the pre-series baseline). The following >> patch, "cxl/ras: Handle RCH correctable and uncorrectable errors in one >> pass", processes a simultaneously signalled CE and UCE together. >> >> Co-developed-by: Dan Williams <djbw@kernel.org> >> Signed-off-by: Dan Williams <djbw@kernel.org> >> Signed-off-by: Terry Bowman <terry.bowman@amd.com> >> >> --- >> > > One small nit, but otherwise LGTM: > Reviewed-by: Ben Cheatham <benjamin.cheatham@amd.com> > > ... > >> 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); >> - struct cxl_memdev *cxlmd = cxlds->cxlmd; >> - struct device *dev = &cxlmd->dev; >> - bool ue; >> + struct cxl_port *port __free(put_cxl_port) = find_cxl_port_by_uport(&pdev->dev); >> + bool ue = false; >> + >> + if (!port) >> + return PCI_ERS_RESULT_DISCONNECT; >> + >> + if (is_cxl_restricted(pdev)) >> + cxl_handle_rdport_errors(pdev); >> >> - scoped_guard(device, dev) { >> - if (!dev->driver) { >> + scoped_guard(device, &port->dev) { >> + 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 PCI_ERS_RESULT_DISCONNECT; >> } >> >> - if (cxlds->rcd) >> - cxl_handle_rdport_errors(cxlds); >> /* >> - * A frozen channel indicates an impending reset which is fatal to >> - * CXL.mem operation, and will likely crash the system. On the off >> - * chance the situation is recoverable dump the status of the RAS >> - * capability registers and bounce the active state of the memdev. >> + * The CXL RAS read is unconditional regardless of channel >> + * state. Any uncorrectable error bit set in the CXL RAS >> + * status register triggers a panic below because CXL.mem >> + * cache coherency is already lost; continuing risks silent >> + * data corruption. >> */ >> - ue = cxl_handle_ras(&cxlds->cxlmd->dev, cxlmd->endpoint->regs.ras); >> + ue = cxl_handle_ras(port->uport_dev, to_ras_base(port, NULL)); >> } >> >> + /* >> + * CXL.mem UCE means cache coherency is lost. Continuing risks >> + * silent data corruption. >> + */ > > Don't need this comment and the last sentence in the comment above. I'll keep the panic-site comment (it's the "why" at the point of consequence) and will remove the duplicated coherency/corruption sentence from the block comment above (done in 2/9) so there's no duplication at any point in the series. -Terry >> + if (ue) >> + panic("CXL cachemem error"); >> + >> switch (state) { >> case pci_channel_io_normal: >> - if (ue) { >> - device_release_driver(dev); >> - return PCI_ERS_RESULT_NEED_RESET; >> - } >> return PCI_ERS_RESULT_CAN_RECOVER; >> case pci_channel_io_frozen: >> dev_warn(&pdev->dev, >> "%s: frozen state error detected, disable CXL.mem\n", >> - dev_name(dev)); >> - device_release_driver(dev); >> + dev_name(port->uport_dev)); >> + device_release_driver(port->uport_dev); >> return PCI_ERS_RESULT_NEED_RESET; >> case pci_channel_io_perm_failure: >> dev_warn(&pdev->dev, >> @@ -335,3 +371,82 @@ pci_ers_result_t cxl_error_detected(struct pci_dev *pdev, >> return PCI_ERS_RESULT_NEED_RESET; >> } >> EXPORT_SYMBOL_NS_GPL(cxl_error_detected, "CXL");
diff --git a/drivers/cxl/core/core.h b/drivers/cxl/core/core.h index 35eaf636adc9c..645824167f788 100644 --- a/drivers/cxl/core/core.h +++ b/drivers/cxl/core/core.h @@ -188,10 +188,13 @@ static inline struct device *dport_to_host(struct cxl_dport *dport) void cxl_ras_init(void); void cxl_ras_exit(void); bool cxl_handle_ras(struct device *dev, void __iomem *ras_base); +void cxl_do_recovery(struct pci_dev *pdev, struct cxl_port *port, + struct cxl_dport *dport); void cxl_handle_cor_ras(struct device *dev, void __iomem *ras_base); void cxl_dport_map_rch_aer(struct cxl_dport *dport); void cxl_disable_rch_root_ints(struct cxl_dport *dport); -void cxl_handle_rdport_errors(struct cxl_dev_state *cxlds); +void cxl_handle_rdport_errors(struct pci_dev *pdev); +void __iomem *to_ras_base(struct cxl_port *port, struct cxl_dport *dport); void devm_cxl_dport_ras_setup(struct cxl_dport *dport); #else static inline void cxl_ras_init(void) { } @@ -200,14 +203,24 @@ static inline bool cxl_handle_ras(struct device *dev, void __iomem *ras_base) { return false; } +static inline void cxl_do_recovery(struct pci_dev *pdev, struct cxl_port *port, + struct cxl_dport *dport) { } static inline void cxl_handle_cor_ras(struct device *dev, void __iomem *ras_base) { } static inline void cxl_dport_map_rch_aer(struct cxl_dport *dport) { } static inline void cxl_disable_rch_root_ints(struct cxl_dport *dport) { } -static inline void cxl_handle_rdport_errors(struct cxl_dev_state *cxlds) { } +static inline void cxl_handle_rdport_errors(struct pci_dev *pdev) { } +static inline void __iomem *to_ras_base(struct cxl_port *port, + struct cxl_dport *dport) +{ + return NULL; +} static inline void devm_cxl_dport_ras_setup(struct cxl_dport *dport) { } #endif /* CONFIG_CXL_RAS */ int cxl_gpf_port_setup(struct cxl_dport *dport); +struct cxl_port *find_cxl_port_by_dport(struct device *dport_dev, + struct cxl_dport **dport); +struct cxl_port *find_cxl_port_by_uport(struct device *uport_dev); struct cxl_hdm; int cxl_hdm_decode_init(struct cxl_dev_state *cxlds, struct cxl_hdm *cxlhdm, diff --git a/drivers/cxl/core/port.c b/drivers/cxl/core/port.c index 625e4aa427db0..9a746be6f1967 100644 --- a/drivers/cxl/core/port.c +++ b/drivers/cxl/core/port.c @@ -1400,8 +1400,8 @@ static struct cxl_port *__find_cxl_port_by_dport(struct cxl_find_port_ctx *ctx) * 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_dport(struct device *dport_dev, - struct cxl_dport **dport) +struct cxl_port *find_cxl_port_by_dport(struct device *dport_dev, + struct cxl_dport **dport) { struct cxl_find_port_ctx ctx = { .dport_dev = dport_dev, @@ -1596,7 +1596,7 @@ static int match_port_by_uport(struct device *dev, const void *data) * Function takes a device reference on the port device. Caller should do a * put_device() when done. */ -static struct cxl_port *find_cxl_port_by_uport(struct device *uport_dev) +struct cxl_port *find_cxl_port_by_uport(struct device *uport_dev) { struct device *dev; diff --git a/drivers/cxl/core/ras.c b/drivers/cxl/core/ras.c index e307361bb39e4..02c791f149270 100644 --- a/drivers/cxl/core/ras.c +++ b/drivers/cxl/core/ras.c @@ -77,6 +77,35 @@ 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: optional output; if non-NULL, set to the matched dport for + * Root Port and Downstream Port lookups, NULL for all other types. + * + * 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; +} + 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, @@ -129,16 +158,6 @@ static void cxl_cper_prot_err_work_fn(struct work_struct *work) } static DECLARE_WORK(cxl_cper_prot_err_work, cxl_cper_prot_err_work_fn); -void cxl_ras_init(void) -{ - cxl_cper_register_prot_err_work(&cxl_cper_prot_err_work); -} - -void cxl_ras_exit(void) -{ - cxl_cper_unregister_prot_err_work(); -} - static void cxl_dport_map_ras(struct cxl_dport *dport) { struct cxl_register_map *map = &dport->reg_map; @@ -195,6 +214,32 @@ void devm_cxl_port_ras_setup(struct cxl_port *port) } EXPORT_SYMBOL_NS_GPL(devm_cxl_port_ras_setup, "CXL"); +void __iomem *to_ras_base(struct cxl_port *port, struct cxl_dport *dport) +{ + if (!port) + return NULL; + + if (dport) + return dport->regs.ras; + + return port->regs.ras; +} + +void cxl_do_recovery(struct pci_dev *pdev, struct cxl_port *port, struct cxl_dport *dport) +{ + struct device *dev = dport ? dport->dport_dev : port->uport_dev; + void __iomem *ras_base = to_ras_base(port, dport); + + if (!ras_base) + panic("CXL: UCE with unmapped RAS registers"); + + if (cxl_handle_ras(dev, ras_base)) + panic("CXL cachemem error"); + + dev_dbg(&pdev->dev, + "CXL UCE signaled but no CXL RAS status bits set\n"); +} + void cxl_handle_cor_ras(struct device *dev, void __iomem *ras_base) { void __iomem *addr; @@ -207,7 +252,10 @@ void cxl_handle_cor_ras(struct device *dev, void __iomem *ras_base) status = readl(addr); if (status & CXL_RAS_CORRECTABLE_STATUS_MASK) { writel(status & CXL_RAS_CORRECTABLE_STATUS_MASK, addr); - trace_cxl_aer_correctable_error(to_cxl_memdev(dev), status); + if (is_cxl_memdev(dev)) + trace_cxl_aer_correctable_error(to_cxl_memdev(dev), status); + else + trace_cxl_port_aer_correctable_error(dev, status); } } @@ -259,73 +307,61 @@ bool cxl_handle_ras(struct device *dev, void __iomem *ras_base) } header_log_copy(ras_base, hl); - trace_cxl_aer_uncorrectable_error(to_cxl_memdev(dev), status, fe, hl); + if (is_cxl_memdev(dev)) + trace_cxl_aer_uncorrectable_error(to_cxl_memdev(dev), status, fe, hl); + else + trace_cxl_port_aer_uncorrectable_error(dev, status, fe, hl); + writel(status & CXL_RAS_UNCORRECTABLE_STATUS_MASK, addr); return true; } -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; - - scoped_guard(device, dev) { - if (!dev->driver) { - dev_warn(&pdev->dev, - "%s: memdev disabled, abort error handling\n", - dev_name(dev)); - return; - } - - if (cxlds->rcd) - cxl_handle_rdport_errors(cxlds); - - cxl_handle_cor_ras(&cxlds->cxlmd->dev, cxlmd->endpoint->regs.ras); - } -} -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); - struct cxl_memdev *cxlmd = cxlds->cxlmd; - struct device *dev = &cxlmd->dev; - bool ue; + struct cxl_port *port __free(put_cxl_port) = find_cxl_port_by_uport(&pdev->dev); + bool ue = false; + + if (!port) + return PCI_ERS_RESULT_DISCONNECT; + + if (is_cxl_restricted(pdev)) + cxl_handle_rdport_errors(pdev); - scoped_guard(device, dev) { - if (!dev->driver) { + scoped_guard(device, &port->dev) { + 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 PCI_ERS_RESULT_DISCONNECT; } - if (cxlds->rcd) - cxl_handle_rdport_errors(cxlds); /* - * A frozen channel indicates an impending reset which is fatal to - * CXL.mem operation, and will likely crash the system. On the off - * chance the situation is recoverable dump the status of the RAS - * capability registers and bounce the active state of the memdev. + * The CXL RAS read is unconditional regardless of channel + * state. Any uncorrectable error bit set in the CXL RAS + * status register triggers a panic below because CXL.mem + * cache coherency is already lost; continuing risks silent + * data corruption. */ - ue = cxl_handle_ras(&cxlds->cxlmd->dev, cxlmd->endpoint->regs.ras); + ue = cxl_handle_ras(port->uport_dev, to_ras_base(port, NULL)); } + /* + * CXL.mem UCE means cache coherency is lost. Continuing risks + * silent data corruption. + */ + if (ue) + panic("CXL cachemem error"); + switch (state) { case pci_channel_io_normal: - if (ue) { - device_release_driver(dev); - return PCI_ERS_RESULT_NEED_RESET; - } return PCI_ERS_RESULT_CAN_RECOVER; case pci_channel_io_frozen: dev_warn(&pdev->dev, "%s: frozen state error detected, disable CXL.mem\n", - dev_name(dev)); - device_release_driver(dev); + dev_name(port->uport_dev)); + device_release_driver(port->uport_dev); return PCI_ERS_RESULT_NEED_RESET; case pci_channel_io_perm_failure: dev_warn(&pdev->dev, @@ -335,3 +371,82 @@ pci_ers_result_t cxl_error_detected(struct pci_dev *pdev, return PCI_ERS_RESULT_NEED_RESET; } EXPORT_SYMBOL_NS_GPL(cxl_error_detected, "CXL"); + +static void cxl_handle_proto_error(struct pci_dev *pdev, struct cxl_port *port, + struct cxl_dport *dport, int severity) +{ + struct device *dev = dport ? dport->dport_dev : port->uport_dev; + + if (severity == AER_CORRECTABLE) + cxl_handle_cor_ras(dev, to_ras_base(port, dport)); + else + cxl_do_recovery(pdev, port, dport); +} + +static void __cxl_proto_err_work_fn(struct cxl_proto_err_work_data *wd) +{ + struct cxl_dport *dport; + struct device *host; + + /* + * For RCD devices, handle RCH Downstream Port errors first. + * cxl_handle_rdport_errors() does its own port lookup and locking, + * keeping the Downstream Port lock separate from the Endpoint Port + * lock taken below. + */ + if (is_cxl_restricted(wd->pdev)) + cxl_handle_rdport_errors(wd->pdev); + + 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; + } + + host = is_cxl_root(port) ? port->uport_dev : &port->dev; + + guard(device)(host); + if (!host->driver) { + dev_err_ratelimited(host, + "Port host device is unbound, abort error handling\n"); + return; + } + + 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); +} + +static void cxl_proto_err_work_fn(struct work_struct *work) +{ + struct cxl_proto_err_work_data wd; + + for_each_cxl_proto_err(&wd, __cxl_proto_err_work_fn); +} + +static DECLARE_WORK(cxl_proto_err_work, cxl_proto_err_work_fn); + +static void cxl_proto_err_do_flush(void) +{ + flush_work(&cxl_proto_err_work); +} + +void cxl_ras_init(void) +{ + cxl_cper_register_prot_err_work(&cxl_cper_prot_err_work); + cxl_register_proto_err_work(&cxl_proto_err_work, + cxl_proto_err_do_flush); +} + +void cxl_ras_exit(void) +{ + cxl_unregister_proto_err_work(); + cxl_cper_unregister_prot_err_work(); +} diff --git a/drivers/cxl/core/ras_rch.c b/drivers/cxl/core/ras_rch.c index e0e01aa5eba6c..ddaa3d7781678 100644 --- a/drivers/cxl/core/ras_rch.c +++ b/drivers/cxl/core/ras_rch.c @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* Copyright(c) 2025 AMD Corporation. All rights reserved. */ -#include <linux/types.h> #include <linux/aer.h> #include "cxl.h" #include "core.h" @@ -110,18 +109,27 @@ static bool cxl_rch_get_aer_severity(struct aer_capability_regs *aer_regs, return false; } -void cxl_handle_rdport_errors(struct cxl_dev_state *cxlds) +void cxl_handle_rdport_errors(struct pci_dev *pdev) { - struct pci_dev *pdev = to_pci_dev(cxlds->dev); struct aer_capability_regs aer_regs; struct cxl_dport *dport; int severity; - struct cxl_port *port __free(put_cxl_port) = - cxl_pci_find_port(pdev, &dport); + struct cxl_port *port __free(put_cxl_port) = cxl_pci_find_port(pdev, NULL); if (!port) return; + /* + * The RCH Downstream Port is the Root Port's dport + * (dport free and RAS iomap) is hosted on the CXL Host Bridge + * (port->uport_dev), not &port->dev. Hold that device's lock so the + * dport cannot be freed and its registers unmapped while in use here. + */ + guard(device)(port->uport_dev); + dport = cxl_find_dport_by_dev(port, pdev->dev.parent); + if (!dport) + return; + if (!cxl_rch_get_aer_info(dport->regs.dport_aer, &aer_regs)) return; @@ -130,7 +138,7 @@ void cxl_handle_rdport_errors(struct cxl_dev_state *cxlds) pci_print_aer(pdev, severity, &aer_regs); if (severity == AER_CORRECTABLE) - cxl_handle_cor_ras(&cxlds->cxlmd->dev, dport->regs.ras); + cxl_handle_cor_ras(dport->dport_dev, to_ras_base(port, dport)); else - cxl_handle_ras(&cxlds->cxlmd->dev, dport->regs.ras); + cxl_do_recovery(pdev, dport->port, dport); } diff --git a/drivers/cxl/cxlpci.h b/drivers/cxl/cxlpci.h index 110ec9c44f09f..606fadd2476f3 100644 --- a/drivers/cxl/cxlpci.h +++ b/drivers/cxl/cxlpci.h @@ -79,14 +79,11 @@ struct cxl_dev_state; void read_cdat_data(struct cxl_port *port); #ifdef CONFIG_CXL_RAS -void cxl_cor_error_detected(struct pci_dev *pdev); pci_ers_result_t cxl_error_detected(struct pci_dev *pdev, pci_channel_state_t state); void devm_cxl_dport_rch_ras_setup(struct cxl_dport *dport); void devm_cxl_port_ras_setup(struct cxl_port *port); #else -static inline void cxl_cor_error_detected(struct pci_dev *pdev) { } - static inline pci_ers_result_t cxl_error_detected(struct pci_dev *pdev, pci_channel_state_t state) { diff --git a/drivers/cxl/pci.c b/drivers/cxl/pci.c index c7c91e8dc51dc..fe11be7fd9aab 100644 --- a/drivers/cxl/pci.c +++ b/drivers/cxl/pci.c @@ -1002,7 +1002,6 @@ static const struct pci_error_handlers cxl_error_handlers = { .error_detected = cxl_error_detected, .slot_reset = cxl_slot_reset, .resume = cxl_error_resume, - .cor_error_detected = cxl_cor_error_detected, .reset_done = cxl_reset_done, }; diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index ba3c3fddddc23..6c7decfb171b0 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -1344,7 +1344,6 @@ void pci_restore_aer_state(struct pci_dev *dev); static inline void pci_no_aer(void) { } static inline void pci_aer_init(struct pci_dev *d) { } static inline void pci_aer_exit(struct pci_dev *d) { } -static inline void pci_aer_clear_fatal_status(struct pci_dev *dev) { } static inline int pci_aer_clear_status(struct pci_dev *dev) { return -EINVAL; } static inline int pci_aer_raw_clear_status(struct pci_dev *dev) { return -EINVAL; } static inline void pci_save_aer_state(struct pci_dev *dev) { } diff --git a/drivers/pci/pcie/aer.c b/drivers/pci/pcie/aer.c index 21dfc9c933d77..8c998cffa89e4 100644 --- a/drivers/pci/pcie/aer.c +++ b/drivers/pci/pcie/aer.c @@ -1328,7 +1328,18 @@ 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) { - cxl_rch_handle_error(dev, info); + bool cxl_pending = 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_wait_for_empty(); + pci_aer_handle_error(dev, info); pci_dev_put(dev); } diff --git a/drivers/pci/pcie/aer_cxl_rch.c b/drivers/pci/pcie/aer_cxl_rch.c index e471eefec9c40..ab31c4281b483 100644 --- a/drivers/pci/pcie/aer_cxl_rch.c +++ b/drivers/pci/pcie/aer_cxl_rch.c @@ -34,42 +34,37 @@ static bool cxl_error_is_native(struct pci_dev *dev) return (pcie_ports_native || host->native_aer); } +struct cxl_rch_error_ctx { + struct aer_err_info *info; + bool enqueued; +}; + static int cxl_rch_handle_error_iter(struct pci_dev *dev, void *data) { - struct aer_err_info *info = (struct aer_err_info *)data; - const struct pci_error_handlers *err_handler; + struct cxl_rch_error_ctx *ctx = data; if (!is_cxl_mem_dev(dev) || !cxl_error_is_native(dev)) return 0; - guard(device)(&dev->dev); - - err_handler = dev->driver ? dev->driver->err_handler : NULL; - if (!err_handler) - return 0; - - if (info->severity == AER_CORRECTABLE) { - if (err_handler->cor_error_detected) - err_handler->cor_error_detected(dev); - } else if (err_handler->error_detected) { - if (info->severity == AER_NONFATAL) - err_handler->error_detected(dev, pci_channel_io_normal); - else if (info->severity == AER_FATAL) - err_handler->error_detected(dev, pci_channel_io_frozen); - } + if (cxl_forward_error(dev, ctx->info)) + ctx->enqueued = true; return 0; } -void cxl_rch_handle_error(struct pci_dev *dev, struct aer_err_info *info) +bool cxl_rch_handle_error(struct pci_dev *dev, struct aer_err_info *info) { + struct cxl_rch_error_ctx ctx = { .info = info }; + /* - * Internal errors of an RCEC indicate an AER error in an - * RCH's downstream port. Check and handle them in the CXL.mem - * device driver. + * An RCEC AER internal error indicates an error in an + * associated RCH Downstream Port or RCD device or both. + * Forward to the cxl_core module for handling. */ if (pci_pcie_type(dev) == PCI_EXP_TYPE_RC_EC && is_aer_internal_error(info)) - pcie_walk_rcec(dev, cxl_rch_handle_error_iter, info); + pcie_walk_rcec(dev, cxl_rch_handle_error_iter, &ctx); + + return ctx.enqueued; } static int handles_cxl_error_iter(struct pci_dev *dev, void *data) diff --git a/drivers/pci/pcie/aer_cxl_vh.c b/drivers/pci/pcie/aer_cxl_vh.c index 9fc12d4e644bc..d04296c01e446 100644 --- a/drivers/pci/pcie/aer_cxl_vh.c +++ b/drivers/pci/pcie/aer_cxl_vh.c @@ -54,8 +54,22 @@ bool is_cxl_error(struct pci_dev *pdev, struct aer_err_info *info) if (!info || !info->is_cxl) return false; - if (pci_pcie_type(pdev) != PCI_EXP_TYPE_ENDPOINT) + /* + * RCD (PCI_EXP_TYPE_RC_END) is not included here because RCDs + * report errors on behalf of upstream RCH Downstream Port and thus + * require a unique discovery detailed in CXL4.0 spec (12.2.1.1). + * The RCH device error discovery and RCD forwarding flow begins + * in cxl_rch_handle_error(). + */ + switch (pci_pcie_type(pdev)) { + case PCI_EXP_TYPE_ENDPOINT: + case PCI_EXP_TYPE_ROOT_PORT: + case PCI_EXP_TYPE_UPSTREAM: + case PCI_EXP_TYPE_DOWNSTREAM: + break; + default: return false; + } return is_aer_internal_error(info); } diff --git a/drivers/pci/pcie/portdrv.h b/drivers/pci/pcie/portdrv.h index 357310916088f..b13b9ac9571dc 100644 --- a/drivers/pci/pcie/portdrv.h +++ b/drivers/pci/pcie/portdrv.h @@ -128,14 +128,14 @@ struct aer_err_info; #ifdef CONFIG_CXL_RAS bool is_aer_internal_error(struct aer_err_info *info); -void cxl_rch_handle_error(struct pci_dev *dev, struct aer_err_info *info); +bool cxl_rch_handle_error(struct pci_dev *dev, struct aer_err_info *info); void cxl_rch_enable_rcec(struct pci_dev *rcec); bool is_cxl_error(struct pci_dev *pdev, struct aer_err_info *info); bool cxl_forward_error(struct pci_dev *pdev, struct aer_err_info *info); void cxl_proto_err_wait_for_empty(void); #else static inline bool is_aer_internal_error(struct aer_err_info *info) { return false; } -static inline void cxl_rch_handle_error(struct pci_dev *dev, struct aer_err_info *info) { } +static inline bool cxl_rch_handle_error(struct pci_dev *dev, struct aer_err_info *info) { return false; } static inline void cxl_rch_enable_rcec(struct pci_dev *rcec) { } static inline bool is_cxl_error(struct pci_dev *pdev, struct aer_err_info *info) { return false; } static inline bool cxl_forward_error(struct pci_dev *pdev, struct aer_err_info *info) { return false; }