|
@@ -60,6 +60,44 @@ static int eeh_result_priority(enum pci_ers_result result)
|
|
|
}
|
|
|
};
|
|
|
|
|
|
+const char *pci_ers_result_name(enum pci_ers_result result)
|
|
|
+{
|
|
|
+ switch (result) {
|
|
|
+ case PCI_ERS_RESULT_NONE:
|
|
|
+ return "none";
|
|
|
+ case PCI_ERS_RESULT_CAN_RECOVER:
|
|
|
+ return "can recover";
|
|
|
+ case PCI_ERS_RESULT_NEED_RESET:
|
|
|
+ return "need reset";
|
|
|
+ case PCI_ERS_RESULT_DISCONNECT:
|
|
|
+ return "disconnect";
|
|
|
+ case PCI_ERS_RESULT_RECOVERED:
|
|
|
+ return "recovered";
|
|
|
+ case PCI_ERS_RESULT_NO_AER_DRIVER:
|
|
|
+ return "no AER driver";
|
|
|
+ default:
|
|
|
+ WARN_ONCE(1, "Unknown result type: %d\n", (int)result);
|
|
|
+ return "unknown";
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+static __printf(2, 3) void eeh_edev_info(const struct eeh_dev *edev,
|
|
|
+ const char *fmt, ...)
|
|
|
+{
|
|
|
+ struct va_format vaf;
|
|
|
+ va_list args;
|
|
|
+
|
|
|
+ va_start(args, fmt);
|
|
|
+
|
|
|
+ vaf.fmt = fmt;
|
|
|
+ vaf.va = &args;
|
|
|
+
|
|
|
+ printk(KERN_INFO "EEH: PE#%x (PCI %s): %pV\n", edev->pe_config_addr,
|
|
|
+ edev->pdev ? dev_name(&edev->pdev->dev) : "none", &vaf);
|
|
|
+
|
|
|
+ va_end(args);
|
|
|
+}
|
|
|
+
|
|
|
static enum pci_ers_result pci_ers_merge_result(enum pci_ers_result old,
|
|
|
enum pci_ers_result new)
|
|
|
{
|
|
@@ -235,123 +273,117 @@ static void eeh_set_irq_state(struct eeh_pe *root, bool enable)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-/**
|
|
|
- * eeh_report_error - Report pci error to each device driver
|
|
|
- * @data: eeh device
|
|
|
- * @userdata: return value
|
|
|
- *
|
|
|
- * Report an EEH error to each device driver, collect up and
|
|
|
- * merge the device driver responses. Cumulative response
|
|
|
- * passed back in "userdata".
|
|
|
- */
|
|
|
-static void *eeh_report_error(struct eeh_dev *edev, void *userdata)
|
|
|
+typedef enum pci_ers_result (*eeh_report_fn)(struct eeh_dev *,
|
|
|
+ struct pci_driver *);
|
|
|
+static void eeh_pe_report_edev(struct eeh_dev *edev, eeh_report_fn fn,
|
|
|
+ enum pci_ers_result *result)
|
|
|
{
|
|
|
- struct pci_dev *dev = eeh_dev_to_pci_dev(edev);
|
|
|
- enum pci_ers_result rc, *res = userdata;
|
|
|
struct pci_driver *driver;
|
|
|
+ enum pci_ers_result new_result;
|
|
|
+
|
|
|
+ device_lock(&edev->pdev->dev);
|
|
|
+ if (eeh_edev_actionable(edev)) {
|
|
|
+ driver = eeh_pcid_get(edev->pdev);
|
|
|
+
|
|
|
+ if (!driver)
|
|
|
+ eeh_edev_info(edev, "no driver");
|
|
|
+ else if (!driver->err_handler)
|
|
|
+ eeh_edev_info(edev, "driver not EEH aware");
|
|
|
+ else if (edev->mode & EEH_DEV_NO_HANDLER)
|
|
|
+ eeh_edev_info(edev, "driver bound too late");
|
|
|
+ else {
|
|
|
+ new_result = fn(edev, driver);
|
|
|
+ eeh_edev_info(edev, "%s driver reports: '%s'",
|
|
|
+ driver->name,
|
|
|
+ pci_ers_result_name(new_result));
|
|
|
+ if (result)
|
|
|
+ *result = pci_ers_merge_result(*result,
|
|
|
+ new_result);
|
|
|
+ }
|
|
|
+ if (driver)
|
|
|
+ eeh_pcid_put(edev->pdev);
|
|
|
+ } else {
|
|
|
+ eeh_edev_info(edev, "not actionable (%d,%d,%d)", !!edev->pdev,
|
|
|
+ !eeh_dev_removed(edev), !eeh_pe_passed(edev->pe));
|
|
|
+ }
|
|
|
+ device_unlock(&edev->pdev->dev);
|
|
|
+}
|
|
|
|
|
|
- if (!eeh_edev_actionable(edev))
|
|
|
- return NULL;
|
|
|
+static void eeh_pe_report(const char *name, struct eeh_pe *root,
|
|
|
+ eeh_report_fn fn, enum pci_ers_result *result)
|
|
|
+{
|
|
|
+ struct eeh_pe *pe;
|
|
|
+ struct eeh_dev *edev, *tmp;
|
|
|
|
|
|
- device_lock(&dev->dev);
|
|
|
+ pr_info("EEH: Beginning: '%s'\n", name);
|
|
|
+ eeh_for_each_pe(root, pe) eeh_pe_for_each_dev(pe, edev, tmp)
|
|
|
+ eeh_pe_report_edev(edev, fn, result);
|
|
|
+ if (result)
|
|
|
+ pr_info("EEH: Finished:'%s' with aggregate recovery state:'%s'\n",
|
|
|
+ name, pci_ers_result_name(*result));
|
|
|
+ else
|
|
|
+ pr_info("EEH: Finished:'%s'", name);
|
|
|
+}
|
|
|
|
|
|
- driver = eeh_pcid_get(dev);
|
|
|
- if (!driver) goto out_no_dev;
|
|
|
+/**
|
|
|
+ * eeh_report_error - Report pci error to each device driver
|
|
|
+ * @edev: eeh device
|
|
|
+ * @driver: device's PCI driver
|
|
|
+ *
|
|
|
+ * Report an EEH error to each device driver.
|
|
|
+ */
|
|
|
+static enum pci_ers_result eeh_report_error(struct eeh_dev *edev,
|
|
|
+ struct pci_driver *driver)
|
|
|
+{
|
|
|
+ enum pci_ers_result rc;
|
|
|
+ struct pci_dev *dev = edev->pdev;
|
|
|
|
|
|
- if (!driver->err_handler ||
|
|
|
- !driver->err_handler->error_detected)
|
|
|
- goto out;
|
|
|
+ if (!driver->err_handler->error_detected)
|
|
|
+ return PCI_ERS_RESULT_NONE;
|
|
|
|
|
|
+ eeh_edev_info(edev, "Invoking %s->error_detected(IO frozen)",
|
|
|
+ driver->name);
|
|
|
rc = driver->err_handler->error_detected(dev, pci_channel_io_frozen);
|
|
|
|
|
|
- *res = pci_ers_merge_result(*res, rc);
|
|
|
-
|
|
|
edev->in_error = true;
|
|
|
pci_uevent_ers(dev, PCI_ERS_RESULT_NONE);
|
|
|
-
|
|
|
-out:
|
|
|
- eeh_pcid_put(dev);
|
|
|
-out_no_dev:
|
|
|
- device_unlock(&dev->dev);
|
|
|
- return NULL;
|
|
|
+ return rc;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* eeh_report_mmio_enabled - Tell drivers that MMIO has been enabled
|
|
|
- * @data: eeh device
|
|
|
- * @userdata: return value
|
|
|
+ * @edev: eeh device
|
|
|
+ * @driver: device's PCI driver
|
|
|
*
|
|
|
* Tells each device driver that IO ports, MMIO and config space I/O
|
|
|
- * are now enabled. Collects up and merges the device driver responses.
|
|
|
- * Cumulative response passed back in "userdata".
|
|
|
+ * are now enabled.
|
|
|
*/
|
|
|
-static void *eeh_report_mmio_enabled(struct eeh_dev *edev, void *userdata)
|
|
|
+static enum pci_ers_result eeh_report_mmio_enabled(struct eeh_dev *edev,
|
|
|
+ struct pci_driver *driver)
|
|
|
{
|
|
|
- struct pci_dev *dev = eeh_dev_to_pci_dev(edev);
|
|
|
- enum pci_ers_result rc, *res = userdata;
|
|
|
- struct pci_driver *driver;
|
|
|
-
|
|
|
- if (!eeh_edev_actionable(edev))
|
|
|
- return NULL;
|
|
|
-
|
|
|
- device_lock(&dev->dev);
|
|
|
- driver = eeh_pcid_get(dev);
|
|
|
- if (!driver) goto out_no_dev;
|
|
|
-
|
|
|
- if (!driver->err_handler ||
|
|
|
- !driver->err_handler->mmio_enabled ||
|
|
|
- (edev->mode & EEH_DEV_NO_HANDLER))
|
|
|
- goto out;
|
|
|
-
|
|
|
- rc = driver->err_handler->mmio_enabled(dev);
|
|
|
-
|
|
|
- *res = pci_ers_merge_result(*res, rc);
|
|
|
-
|
|
|
-out:
|
|
|
- eeh_pcid_put(dev);
|
|
|
-out_no_dev:
|
|
|
- device_unlock(&dev->dev);
|
|
|
- return NULL;
|
|
|
+ if (!driver->err_handler->mmio_enabled)
|
|
|
+ return PCI_ERS_RESULT_NONE;
|
|
|
+ eeh_edev_info(edev, "Invoking %s->mmio_enabled()", driver->name);
|
|
|
+ return driver->err_handler->mmio_enabled(edev->pdev);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* eeh_report_reset - Tell device that slot has been reset
|
|
|
- * @data: eeh device
|
|
|
- * @userdata: return value
|
|
|
+ * @edev: eeh device
|
|
|
+ * @driver: device's PCI driver
|
|
|
*
|
|
|
* This routine must be called while EEH tries to reset particular
|
|
|
* PCI device so that the associated PCI device driver could take
|
|
|
* some actions, usually to save data the driver needs so that the
|
|
|
* driver can work again while the device is recovered.
|
|
|
*/
|
|
|
-static void *eeh_report_reset(struct eeh_dev *edev, void *userdata)
|
|
|
+static enum pci_ers_result eeh_report_reset(struct eeh_dev *edev,
|
|
|
+ struct pci_driver *driver)
|
|
|
{
|
|
|
- struct pci_dev *dev = eeh_dev_to_pci_dev(edev);
|
|
|
- enum pci_ers_result rc, *res = userdata;
|
|
|
- struct pci_driver *driver;
|
|
|
-
|
|
|
- if (!eeh_edev_actionable(edev))
|
|
|
- return NULL;
|
|
|
-
|
|
|
- device_lock(&dev->dev);
|
|
|
-
|
|
|
- driver = eeh_pcid_get(dev);
|
|
|
- if (!driver) goto out_no_dev;
|
|
|
-
|
|
|
- if (!driver->err_handler ||
|
|
|
- !driver->err_handler->slot_reset ||
|
|
|
- (edev->mode & EEH_DEV_NO_HANDLER) ||
|
|
|
- (!edev->in_error))
|
|
|
- goto out;
|
|
|
-
|
|
|
- rc = driver->err_handler->slot_reset(dev);
|
|
|
- *res = pci_ers_merge_result(*res, rc);
|
|
|
-
|
|
|
-out:
|
|
|
- eeh_pcid_put(dev);
|
|
|
-out_no_dev:
|
|
|
- device_unlock(&dev->dev);
|
|
|
- return NULL;
|
|
|
+ if (!driver->err_handler->slot_reset || !edev->in_error)
|
|
|
+ return PCI_ERS_RESULT_NONE;
|
|
|
+ eeh_edev_info(edev, "Invoking %s->slot_reset()", driver->name);
|
|
|
+ return driver->err_handler->slot_reset(edev->pdev);
|
|
|
}
|
|
|
|
|
|
static void *eeh_dev_restore_state(struct eeh_dev *edev, void *userdata)
|
|
@@ -384,84 +416,53 @@ static void *eeh_dev_restore_state(struct eeh_dev *edev, void *userdata)
|
|
|
|
|
|
/**
|
|
|
* eeh_report_resume - Tell device to resume normal operations
|
|
|
- * @data: eeh device
|
|
|
- * @userdata: return value
|
|
|
+ * @edev: eeh device
|
|
|
+ * @driver: device's PCI driver
|
|
|
*
|
|
|
* This routine must be called to notify the device driver that it
|
|
|
* could resume so that the device driver can do some initialization
|
|
|
* to make the recovered device work again.
|
|
|
*/
|
|
|
-static void *eeh_report_resume(struct eeh_dev *edev, void *userdata)
|
|
|
+static enum pci_ers_result eeh_report_resume(struct eeh_dev *edev,
|
|
|
+ struct pci_driver *driver)
|
|
|
{
|
|
|
- struct pci_dev *dev = eeh_dev_to_pci_dev(edev);
|
|
|
- bool was_in_error;
|
|
|
- struct pci_driver *driver;
|
|
|
-
|
|
|
- if (!eeh_edev_actionable(edev))
|
|
|
- return NULL;
|
|
|
-
|
|
|
- device_lock(&dev->dev);
|
|
|
-
|
|
|
- driver = eeh_pcid_get(dev);
|
|
|
- if (!driver) goto out_no_dev;
|
|
|
+ if (!driver->err_handler->resume || !edev->in_error)
|
|
|
+ return PCI_ERS_RESULT_NONE;
|
|
|
|
|
|
- was_in_error = edev->in_error;
|
|
|
- edev->in_error = false;
|
|
|
+ eeh_edev_info(edev, "Invoking %s->resume()", driver->name);
|
|
|
+ driver->err_handler->resume(edev->pdev);
|
|
|
|
|
|
- if (!driver->err_handler ||
|
|
|
- !driver->err_handler->resume ||
|
|
|
- (edev->mode & EEH_DEV_NO_HANDLER) || !was_in_error) {
|
|
|
- goto out;
|
|
|
- }
|
|
|
-
|
|
|
- driver->err_handler->resume(dev);
|
|
|
-
|
|
|
- pci_uevent_ers(dev, PCI_ERS_RESULT_RECOVERED);
|
|
|
-out:
|
|
|
- eeh_pcid_put(dev);
|
|
|
+ pci_uevent_ers(edev->pdev, PCI_ERS_RESULT_RECOVERED);
|
|
|
#ifdef CONFIG_PCI_IOV
|
|
|
if (eeh_ops->notify_resume && eeh_dev_to_pdn(edev))
|
|
|
eeh_ops->notify_resume(eeh_dev_to_pdn(edev));
|
|
|
#endif
|
|
|
-out_no_dev:
|
|
|
- device_unlock(&dev->dev);
|
|
|
- return NULL;
|
|
|
+ return PCI_ERS_RESULT_NONE;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* eeh_report_failure - Tell device driver that device is dead.
|
|
|
- * @data: eeh device
|
|
|
- * @userdata: return value
|
|
|
+ * @edev: eeh device
|
|
|
+ * @driver: device's PCI driver
|
|
|
*
|
|
|
* This informs the device driver that the device is permanently
|
|
|
* dead, and that no further recovery attempts will be made on it.
|
|
|
*/
|
|
|
-static void *eeh_report_failure(struct eeh_dev *edev, void *userdata)
|
|
|
+static enum pci_ers_result eeh_report_failure(struct eeh_dev *edev,
|
|
|
+ struct pci_driver *driver)
|
|
|
{
|
|
|
- struct pci_dev *dev = eeh_dev_to_pci_dev(edev);
|
|
|
- struct pci_driver *driver;
|
|
|
-
|
|
|
- if (!eeh_edev_actionable(edev))
|
|
|
- return NULL;
|
|
|
-
|
|
|
- device_lock(&dev->dev);
|
|
|
- dev->error_state = pci_channel_io_perm_failure;
|
|
|
-
|
|
|
- driver = eeh_pcid_get(dev);
|
|
|
- if (!driver) goto out_no_dev;
|
|
|
+ enum pci_ers_result rc;
|
|
|
|
|
|
- if (!driver->err_handler ||
|
|
|
- !driver->err_handler->error_detected)
|
|
|
- goto out;
|
|
|
+ if (!driver->err_handler->error_detected)
|
|
|
+ return PCI_ERS_RESULT_NONE;
|
|
|
|
|
|
- driver->err_handler->error_detected(dev, pci_channel_io_perm_failure);
|
|
|
+ eeh_edev_info(edev, "Invoking %s->error_detected(permanent failure)",
|
|
|
+ driver->name);
|
|
|
+ rc = driver->err_handler->error_detected(edev->pdev,
|
|
|
+ pci_channel_io_perm_failure);
|
|
|
|
|
|
- pci_uevent_ers(dev, PCI_ERS_RESULT_DISCONNECT);
|
|
|
-out:
|
|
|
- eeh_pcid_put(dev);
|
|
|
-out_no_dev:
|
|
|
- device_unlock(&dev->dev);
|
|
|
- return NULL;
|
|
|
+ pci_uevent_ers(edev->pdev, PCI_ERS_RESULT_DISCONNECT);
|
|
|
+ return rc;
|
|
|
}
|
|
|
|
|
|
static void *eeh_add_virt_device(void *data, void *userdata)
|
|
@@ -823,7 +824,8 @@ void eeh_handle_normal_event(struct eeh_pe *pe)
|
|
|
pr_info("EEH: Notify device drivers to shutdown\n");
|
|
|
eeh_set_channel_state(pe, pci_channel_io_frozen);
|
|
|
eeh_set_irq_state(pe, false);
|
|
|
- eeh_pe_dev_traverse(pe, eeh_report_error, &result);
|
|
|
+ eeh_pe_report("error_detected(IO frozen)", pe, eeh_report_error,
|
|
|
+ &result);
|
|
|
if ((pe->type & EEH_PE_PHB) &&
|
|
|
result != PCI_ERS_RESULT_NONE &&
|
|
|
result != PCI_ERS_RESULT_NEED_RESET)
|
|
@@ -870,7 +872,8 @@ void eeh_handle_normal_event(struct eeh_pe *pe)
|
|
|
result = PCI_ERS_RESULT_NEED_RESET;
|
|
|
} else {
|
|
|
pr_info("EEH: Notify device drivers to resume I/O\n");
|
|
|
- eeh_pe_dev_traverse(pe, eeh_report_mmio_enabled, &result);
|
|
|
+ eeh_pe_report("mmio_enabled", pe,
|
|
|
+ eeh_report_mmio_enabled, &result);
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -915,7 +918,7 @@ void eeh_handle_normal_event(struct eeh_pe *pe)
|
|
|
result = PCI_ERS_RESULT_NONE;
|
|
|
eeh_set_channel_state(pe, pci_channel_io_normal);
|
|
|
eeh_set_irq_state(pe, true);
|
|
|
- eeh_pe_dev_traverse(pe, eeh_report_reset, &result);
|
|
|
+ eeh_pe_report("slot_reset", pe, eeh_report_reset, &result);
|
|
|
}
|
|
|
|
|
|
/* All devices should claim they have recovered by now. */
|
|
@@ -938,11 +941,13 @@ void eeh_handle_normal_event(struct eeh_pe *pe)
|
|
|
pr_info("EEH: Notify device driver to resume\n");
|
|
|
eeh_set_channel_state(pe, pci_channel_io_normal);
|
|
|
eeh_set_irq_state(pe, true);
|
|
|
- eeh_pe_dev_traverse(pe, eeh_report_resume, NULL);
|
|
|
-
|
|
|
- eeh_for_each_pe(pe, tmp_pe)
|
|
|
- eeh_pe_for_each_dev(tmp_pe, edev, tmp)
|
|
|
+ eeh_pe_report("resume", pe, eeh_report_resume, NULL);
|
|
|
+ eeh_for_each_pe(pe, tmp_pe) {
|
|
|
+ eeh_pe_for_each_dev(tmp_pe, edev, tmp) {
|
|
|
edev->mode &= ~EEH_DEV_NO_HANDLER;
|
|
|
+ edev->in_error = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
pr_info("EEH: Recovery successful.\n");
|
|
|
goto final;
|
|
@@ -962,7 +967,8 @@ hard_fail:
|
|
|
/* Notify all devices that they're about to go down. */
|
|
|
eeh_set_channel_state(pe, pci_channel_io_perm_failure);
|
|
|
eeh_set_irq_state(pe, false);
|
|
|
- eeh_pe_dev_traverse(pe, eeh_report_failure, NULL);
|
|
|
+ eeh_pe_report("error_detected(permanent failure)", pe,
|
|
|
+ eeh_report_failure, NULL);
|
|
|
|
|
|
/* Mark the PE to be removed permanently */
|
|
|
eeh_pe_state_mark(pe, EEH_PE_REMOVED);
|
|
@@ -1072,7 +1078,8 @@ void eeh_handle_special_event(void)
|
|
|
/* Notify all devices to be down */
|
|
|
eeh_pe_state_clear(pe, EEH_PE_PRI_BUS);
|
|
|
eeh_set_channel_state(pe, pci_channel_io_perm_failure);
|
|
|
- eeh_pe_dev_traverse(pe,
|
|
|
+ eeh_pe_report(
|
|
|
+ "error_detected(permanent failure)", pe,
|
|
|
eeh_report_failure, NULL);
|
|
|
bus = eeh_pe_bus_get(phb_pe);
|
|
|
if (!bus) {
|