aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/pci/pcie/err.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/pci/pcie/err.c')
-rw-r--r--drivers/pci/pcie/err.c33
1 files changed, 31 insertions, 2 deletions
diff --git a/drivers/pci/pcie/err.c b/drivers/pci/pcie/err.c
index 984aa023c753..bb4febcf45ba 100644
--- a/drivers/pci/pcie/err.c
+++ b/drivers/pci/pcie/err.c
@@ -13,6 +13,7 @@
#define dev_fmt(fmt) "AER: " fmt
#include <linux/pci.h>
+#include <linux/pm_runtime.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
@@ -79,6 +80,18 @@ static int report_error_detected(struct pci_dev *dev,
return 0;
}
+static int pci_pm_runtime_get_sync(struct pci_dev *pdev, void *data)
+{
+ pm_runtime_get_sync(&pdev->dev);
+ return 0;
+}
+
+static int pci_pm_runtime_put(struct pci_dev *pdev, void *data)
+{
+ pm_runtime_put(&pdev->dev);
+ return 0;
+}
+
static int report_frozen_detected(struct pci_dev *dev, void *data)
{
return report_error_detected(dev, pci_channel_io_frozen, data);
@@ -176,6 +189,7 @@ pci_ers_result_t pcie_do_recovery(struct pci_dev *dev,
int type = pci_pcie_type(dev);
struct pci_dev *bridge;
pci_ers_result_t status = PCI_ERS_RESULT_CAN_RECOVER;
+ struct pci_host_bridge *host = pci_find_host_bridge(dev->bus);
/*
* If the error was detected by a Root Port, Downstream Port, or
@@ -193,6 +207,8 @@ pci_ers_result_t pcie_do_recovery(struct pci_dev *dev,
else
bridge = pci_upstream_bridge(dev);
+ pci_walk_bridge(bridge, pci_pm_runtime_get_sync, NULL);
+
pci_dbg(bridge, "broadcast error_detected message\n");
if (state == pci_channel_io_frozen) {
pci_walk_bridge(bridge, report_frozen_detected, &status);
@@ -227,13 +243,26 @@ pci_ers_result_t pcie_do_recovery(struct pci_dev *dev,
pci_dbg(bridge, "broadcast resume message\n");
pci_walk_bridge(bridge, report_resume, &status);
- if (pcie_aer_is_native(bridge))
+ /*
+ * If we have native control of AER, clear error status in the Root
+ * Port or Downstream Port that signaled the error. If the
+ * platform retained control of AER, it is responsible for clearing
+ * this status. In that case, the signaling device may not even be
+ * visible to the OS.
+ */
+ if (host->native_aer || pcie_ports_native) {
pcie_clear_device_status(bridge);
- pci_aer_clear_nonfatal_status(bridge);
+ pci_aer_clear_nonfatal_status(bridge);
+ }
+
+ pci_walk_bridge(bridge, pci_pm_runtime_put, NULL);
+
pci_info(bridge, "device recovery successful\n");
return status;
failed:
+ pci_walk_bridge(bridge, pci_pm_runtime_put, NULL);
+
pci_uevent_ers(bridge, PCI_ERS_RESULT_DISCONNECT);
/* TODO: Should kernel panic here? */