diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index 79c8398f3938..d1f5b42fa48d 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -632,6 +632,11 @@ pci_ers_result_t pcie_do_recovery(struct pci_dev *dev,
 		pci_channel_state_t state,
 		pci_ers_result_t (*reset_subordinates)(struct pci_dev *pdev));
 
+/* CXL error reporting and recovery */
+pci_ers_result_t cxl_do_recovery(struct pci_dev *dev,
+		pci_channel_state_t state,
+		pci_ers_result_t (*reset_subordinates)(struct pci_dev *pdev));
+
 bool pcie_wait_for_link(struct pci_dev *pdev, bool active);
 int pcie_retrain_link(struct pci_dev *pdev, bool use_lt);
 
diff --git a/drivers/pci/pcie/aer.c b/drivers/pci/pcie/aer.c
index 9b2872c8e20d..81a19028c4e7 100644
--- a/drivers/pci/pcie/aer.c
+++ b/drivers/pci/pcie/aer.c
@@ -1060,7 +1060,10 @@ static void cxl_handle_error(struct pci_dev *dev, struct aer_err_info *info)
 		if (cxl_port_hndlrs && cxl_port_hndlrs->cor_error_detected)
 			cxl_port_hndlrs->cor_error_detected(dev);
 		pcie_clear_device_status(dev);
-	}
+	} else if (info->severity == AER_NONFATAL)
+		cxl_do_recovery(dev, pci_channel_io_normal, aer_root_reset);
+	else if (info->severity == AER_FATAL)
+		cxl_do_recovery(dev, pci_channel_io_frozen, aer_root_reset);
 }
 
 static int handles_cxl_error_iter(struct pci_dev *dev, void *data)
diff --git a/drivers/pci/pcie/err.c b/drivers/pci/pcie/err.c
index 31090770fffc..de12f2eb19ef 100644
--- a/drivers/pci/pcie/err.c
+++ b/drivers/pci/pcie/err.c
@@ -86,6 +86,63 @@ static int report_error_detected(struct pci_dev *dev,
 	return 0;
 }
 
+static int cxl_report_error_detected(struct pci_dev *dev,
+				     pci_channel_state_t state,
+				     enum pci_ers_result *result)
+{
+	struct cxl_port_err_hndlrs *cxl_port_hndlrs;
+	struct pci_driver *pdrv;
+	pci_ers_result_t vote;
+
+	device_lock(&dev->dev);
+	cxl_port_hndlrs = find_cxl_port_hndlrs();
+	pdrv = dev->driver;
+	if (pci_dev_is_disconnected(dev)) {
+		vote = PCI_ERS_RESULT_DISCONNECT;
+	} else if (!pci_dev_set_io_state(dev, state)) {
+		pci_info(dev, "can't recover (state transition %u -> %u invalid)\n",
+			dev->error_state, state);
+		vote = PCI_ERS_RESULT_NONE;
+	} else if (!cxl_port_hndlrs || !cxl_port_hndlrs->error_detected) {
+		if (dev->hdr_type != PCI_HEADER_TYPE_BRIDGE) {
+			vote = PCI_ERS_RESULT_NO_AER_DRIVER;
+			pci_info(dev, "can't recover (no error_detected callback)\n");
+		} else {
+			vote = PCI_ERS_RESULT_NONE;
+		}
+	} else {
+		vote = cxl_port_hndlrs->error_detected(dev, state);
+	}
+	pci_uevent_ers(dev, vote);
+	*result = merge_result(*result, vote);
+	device_unlock(&dev->dev);
+	return 0;
+}
+
+static int cxl_report_frozen_detected(struct pci_dev *dev, void *data)
+{
+	/*
+	 * CXL endpoints report using pci_dev::err_handlers.
+	 * CXL PCIe ports report using aer_rpc::cxl_port_err_handlers.
+	 */
+	if (pci_pcie_type(dev) == PCI_EXP_TYPE_ENDPOINT)
+		return report_error_detected(dev, pci_channel_io_frozen, data);
+	else
+		return cxl_report_error_detected(dev, pci_channel_io_frozen, data);
+}
+
+static int cxl_report_normal_detected(struct pci_dev *dev, void *data)
+{
+	/*
+	 * CXL endpoints report using pci_dev::err_handlers.
+	 * CXL PCIe ports report using aer_rpc::cxl_port_err_handlers.
+	 */
+	if (pci_pcie_type(dev) == PCI_EXP_TYPE_ENDPOINT)
+		return report_error_detected(dev, pci_channel_io_normal, data);
+	else
+		return cxl_report_error_detected(dev, pci_channel_io_normal, data);
+}
+
 static int pci_pm_runtime_get_sync(struct pci_dev *pdev, void *data)
 {
 	pm_runtime_get_sync(&pdev->dev);
@@ -188,6 +245,28 @@ static void pci_walk_bridge(struct pci_dev *bridge,
 		cb(bridge, userdata);
 }
 
+/**
+ * cxl_walk_bridge - walk bridges potentially AER affected
+ * @bridge:	bridge which may be a Port, an RCEC, or an RCiEP
+ * @cb:		callback to be called for each device found
+ * @userdata:	arbitrary pointer to be passed to callback
+ *
+ * If the device provided is a bridge, walk the subordinate bus, including
+ * the device itself and any bridged devices on buses under this bus.  Call
+ * the provided callback on each device found.
+ *
+ * If the device provided has no subordinate bus, e.g., an RCEC or RCiEP,
+ * call the callback on the device itself.
+ */
+static void cxl_walk_bridge(struct pci_dev *bridge,
+			    int (*cb)(struct pci_dev *, void *),
+			    void *userdata)
+{
+	cb(bridge, userdata);
+	if (bridge->subordinate)
+		pci_walk_bus(bridge->subordinate, cb, userdata);
+}
+
 pci_ers_result_t pcie_do_recovery(struct pci_dev *dev,
 		pci_channel_state_t state,
 		pci_ers_result_t (*reset_subordinates)(struct pci_dev *pdev))
@@ -276,3 +355,74 @@ pci_ers_result_t pcie_do_recovery(struct pci_dev *dev,
 
 	return status;
 }
+
+pci_ers_result_t cxl_do_recovery(struct pci_dev *bridge,
+				 pci_channel_state_t state,
+				 pci_ers_result_t (*reset_subordinates)(struct pci_dev *pdev))
+{
+	struct pci_host_bridge *host = pci_find_host_bridge(bridge->bus);
+	pci_ers_result_t status = PCI_ERS_RESULT_CAN_RECOVER;
+	int type = pci_pcie_type(bridge);
+
+	if ((type != PCI_EXP_TYPE_ROOT_PORT) &&
+	    (type != PCI_EXP_TYPE_RC_EC) &&
+	    (type != PCI_EXP_TYPE_DOWNSTREAM) &&
+	    (type != PCI_EXP_TYPE_UPSTREAM)) {
+		pci_dbg(bridge, "Unsupported device type (%x)\n", type);
+		return status;
+	}
+
+	cxl_walk_bridge(bridge, pci_pm_runtime_get_sync, NULL);
+
+	pci_dbg(bridge, "broadcast error_detected message\n");
+	if (state == pci_channel_io_frozen) {
+		cxl_walk_bridge(bridge, cxl_report_frozen_detected, &status);
+		if (reset_subordinates(bridge) != PCI_ERS_RESULT_RECOVERED) {
+			pci_warn(bridge, "subordinate device reset failed\n");
+			goto failed;
+		}
+	} else {
+		cxl_walk_bridge(bridge, cxl_report_normal_detected, &status);
+	}
+
+	if (status == PCI_ERS_RESULT_PANIC)
+		panic("CXL cachemem error. Invoking panic");
+
+	if (status == PCI_ERS_RESULT_CAN_RECOVER) {
+		status = PCI_ERS_RESULT_RECOVERED;
+		pci_dbg(bridge, "broadcast mmio_enabled message\n");
+		cxl_walk_bridge(bridge, report_mmio_enabled, &status);
+	}
+
+	if (status == PCI_ERS_RESULT_NEED_RESET) {
+		status = PCI_ERS_RESULT_RECOVERED;
+		pci_dbg(bridge, "broadcast slot_reset message\n");
+		report_slot_reset(bridge, &status);
+		pci_walk_bridge(bridge, report_slot_reset, &status);
+	}
+
+	if (status != PCI_ERS_RESULT_RECOVERED)
+		goto failed;
+
+	pci_dbg(bridge, "broadcast resume message\n");
+	cxl_walk_bridge(bridge, report_resume, &status);
+
+	if (host->native_aer || pcie_ports_native) {
+		pcie_clear_device_status(bridge);
+		pci_aer_clear_nonfatal_status(bridge);
+	}
+
+	cxl_walk_bridge(bridge, pci_pm_runtime_put, NULL);
+
+	pci_info(bridge, "device recovery successful\n");
+	return status;
+
+failed:
+	cxl_walk_bridge(bridge, pci_pm_runtime_put, NULL);
+
+	pci_uevent_ers(bridge, PCI_ERS_RESULT_DISCONNECT);
+
+	pci_info(bridge, "device recovery failed\n");
+
+	return status;
+}
