// SPDX-License-Identifier: GPL-2.0-or-later/* * PCI Error Recovery Driver for RPA-compliant PPC64 platform. * Copyright IBM Corp. 2004 2005 * Copyright Linas Vepstas <linas@linas.org> 2004, 2005 * * Send comments and feedback to Linas Vepstas <linas@austin.ibm.com> */#include <linux/delay.h>#include <linux/interrupt.h>#include <linux/irq.h>#include <linux/module.h>#include <linux/pci.h>#include <linux/pci_hotplug.h>#include <asm/eeh.h>#include <asm/eeh_event.h>#include <asm/ppc-pci.h>#include <asm/pci-bridge.h>#include <asm/rtas.h> struct eeh_rmv_data { struct list_head removed_vf_list; int removed_dev_count;}; static int eeh_result_priority(enum pci_ers_result result){ switch (result) { case PCI_ERS_RESULT_NONE: return 1; case PCI_ERS_RESULT_NO_AER_DRIVER: return 2; case PCI_ERS_RESULT_RECOVERED: return 3; case PCI_ERS_RESULT_CAN_RECOVER: return 4; case PCI_ERS_RESULT_DISCONNECT: return 5; case PCI_ERS_RESULT_NEED_RESET: return 6; default: WARN_ONCE(1, "Unknown pci_ers_result value: %d\n", result); return 0; }}; static 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", result); return "unknown"; }}; static enum pci_ers_result pci_ers_merge_result(enum pci_ers_result old, enum pci_ers_result new){ if (eeh_result_priority(new) > eeh_result_priority(old)) return new; return old;} static bool eeh_dev_removed(struct eeh_dev *edev){ return !edev || (edev->mode & EEH_DEV_REMOVED);} static bool eeh_edev_actionable(struct eeh_dev *edev){ if (!edev->pdev) return false; if (edev->pdev->error_state == pci_channel_io_perm_failure) return false; if (eeh_dev_removed(edev)) return false; if (eeh_pe_passed(edev->pe)) return false; return true;} /** * eeh_pcid_get - Get the PCI device driver * @pdev: PCI device * * The function is used to retrieve the PCI device driver for * the indicated PCI device. Besides, we will increase the reference * of the PCI device driver to prevent that being unloaded on * the fly. Otherwise, kernel crash would be seen. */static inline struct pci_driver *eeh_pcid_get(struct pci_dev *pdev){ if (!pdev || !pdev->dev.driver) return NULL; if (!try_module_get(pdev->dev.driver->owner)) return NULL; return to_pci_driver(pdev->dev.driver);} /** * eeh_pcid_put - Dereference on the PCI device driver * @pdev: PCI device * * The function is called to do dereference on the PCI device * driver of the indicated PCI device. */static inline void eeh_pcid_put(struct pci_dev *pdev){ if (!pdev || !pdev->dev.driver) return; module_put(pdev->dev.driver->owner);} /** * eeh_disable_irq - Disable interrupt for the recovering device * @dev: PCI device * * This routine must be called when reporting temporary or permanent * error to the particular PCI device to disable interrupt of that * device. If the device has enabled MSI or MSI-X interrupt, we needn't * do real work because EEH should freeze DMA transfers for those PCI * devices encountering EEH errors, which includes MSI or MSI-X. */static void eeh_disable_irq(struct eeh_dev *edev){ /* Don't disable MSI and MSI-X interrupts. They are * effectively disabled by the DMA Stopped state * when an EEH error occurs. */ if (edev->pdev->msi_enabled || edev->pdev->msix_enabled) return; if (!irq_has_action(edev->pdev->irq)) return; edev->mode |= EEH_DEV_IRQ_DISABLED; disable_irq_nosync(edev->pdev->irq);} /** * eeh_enable_irq - Enable interrupt for the recovering device * @dev: PCI device * * This routine must be called to enable interrupt while failed * device could be resumed. */static void eeh_enable_irq(struct eeh_dev *edev){ if ((edev->mode) & EEH_DEV_IRQ_DISABLED) { edev->mode &= ~EEH_DEV_IRQ_DISABLED; /* * FIXME !!!!! * * This is just ass backwards. This maze has * unbalanced irq_enable/disable calls. So instead of * finding the root cause it works around the warning * in the irq_enable code by conditionally calling * into it. * * That's just wrong.The warning in the core code is * there to tell people to fix their asymmetries in * their own code, not by abusing the core information * to avoid it. * * I so wish that the assymetry would be the other way * round and a few more irq_disable calls render that * shit unusable forever. * * tglx */ if (irqd_irq_disabled(irq_get_irq_data(edev->pdev->irq))) enable_irq(edev->pdev->irq); }} static void eeh_dev_save_state(struct eeh_dev *edev, void *userdata){ struct pci_dev *pdev; if (!edev) return; /* * We cannot access the config space on some adapters. * Otherwise, it will cause fenced PHB. We don't save * the content in their config space and will restore * from the initial config space saved when the EEH * device is created. */ if (edev->pe && (edev->pe->state & EEH_PE_CFG_RESTRICTED)) return; pdev = eeh_dev_to_pci_dev(edev); if (!pdev) return; pci_save_state(pdev);} static void eeh_set_channel_state(struct eeh_pe *root, pci_channel_state_t s){ struct eeh_pe *pe; struct eeh_dev *edev, *tmp; eeh_for_each_pe(root, pe) eeh_pe_for_each_dev(pe, edev, tmp) if (eeh_edev_actionable(edev)) edev->pdev->error_state = s;} static void eeh_set_irq_state(struct eeh_pe *root, bool enable){ struct eeh_pe *pe; struct eeh_dev *edev, *tmp; eeh_for_each_pe(root, pe) { eeh_pe_for_each_dev(pe, edev, tmp) { if (!eeh_edev_actionable(edev)) continue; if (!eeh_pcid_get(edev->pdev)) continue; if (enable) eeh_enable_irq(edev); else eeh_disable_irq(edev); eeh_pcid_put(edev->pdev); } }} typedef enum pci_ers_result (*eeh_report_fn)(struct eeh_dev *, struct pci_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 *pdev; struct pci_driver *driver; enum pci_ers_result new_result; pdev = edev->pdev; if (pdev) get_device(&pdev->dev); if (!pdev) { eeh_edev_info(edev, "no device"); *result = PCI_ERS_RESULT_DISCONNECT; return; } device_lock(&pdev->dev); if (eeh_edev_actionable(edev)) { driver = eeh_pcid_get(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, pdev, 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(pdev); } else { eeh_edev_info(edev, "not actionable (%d,%d,%d)", !!pdev, !eeh_dev_removed(edev), !eeh_pe_passed(edev->pe)); } device_unlock(&pdev->dev); if (edev->pdev != pdev) eeh_edev_warn(edev, "Device changed during processing!\n"); put_device(&pdev->dev);} 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; 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);} /** * 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_dev *pdev, struct pci_driver *driver){ enum pci_ers_result rc; 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(pdev, pci_channel_io_frozen); edev->in_error = true; pci_uevent_ers(pdev, rc); return rc;} /** * eeh_report_mmio_enabled - Tell drivers that MMIO has been enabled * @edev: eeh device * @driver: device's PCI driver * * Tells each device driver that IO ports, MMIO and config space I/O * are now enabled. */static enum pci_ers_result eeh_report_mmio_enabled(struct eeh_dev *edev, struct pci_dev *pdev, struct pci_driver *driver){ 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(pdev);} /** * eeh_report_reset - Tell device that slot has been reset * @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 enum pci_ers_result eeh_report_reset(struct eeh_dev *edev, struct pci_dev *pdev, struct pci_driver *driver){ 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(pdev);} static void eeh_dev_restore_state(struct eeh_dev *edev, void *userdata){ struct pci_dev *pdev; if (!edev) return; pci_lock_rescan_remove(); /* * The content in the config space isn't saved because * the blocked config space on some adapters. We have * to restore the initial saved config space when the * EEH device is created. */ if (edev->pe && (edev->pe->state & EEH_PE_CFG_RESTRICTED)) { if (list_is_last(&edev->entry, &edev->pe->edevs)) eeh_pe_restore_bars(edev->pe); pci_unlock_rescan_remove(); return; } pdev = eeh_dev_to_pci_dev(edev); if (!pdev) { pci_unlock_rescan_remove(); return; } pci_restore_state(pdev); pci_unlock_rescan_remove();} /** * eeh_report_resume - Tell device to resume normal operations * @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 enum pci_ers_result eeh_report_resume(struct eeh_dev *edev, struct pci_dev *pdev, struct pci_driver *driver){ if (!driver->err_handler->resume || !edev->in_error) return PCI_ERS_RESULT_NONE; eeh_edev_info(edev, "Invoking %s->resume()", driver->name); driver->err_handler->resume(pdev); pci_uevent_ers(edev->pdev, PCI_ERS_RESULT_RECOVERED);#ifdef CONFIG_PCI_IOV if (eeh_ops->notify_resume) eeh_ops->notify_resume(edev);#endif return PCI_ERS_RESULT_NONE;} /** * eeh_report_failure - Tell device driver that device is dead. * @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 enum pci_ers_result eeh_report_failure(struct eeh_dev *edev, struct pci_dev *pdev, struct pci_driver *driver){ enum pci_ers_result rc; if (!driver->err_handler->error_detected) return PCI_ERS_RESULT_NONE; eeh_edev_info(edev, "Invoking %s->error_detected(permanent failure)", driver->name); rc = driver->err_handler->error_detected(pdev, pci_channel_io_perm_failure); pci_uevent_ers(pdev, PCI_ERS_RESULT_DISCONNECT); return rc;} static void *eeh_add_virt_device(struct eeh_dev *edev){ struct pci_driver *driver; struct pci_dev *dev = eeh_dev_to_pci_dev(edev); if (!(edev->physfn)) { eeh_edev_warn(edev, "Not for VF\n"); return NULL; } driver = eeh_pcid_get(dev); if (driver) { if (driver->err_handler) { eeh_pcid_put(dev); return NULL; } eeh_pcid_put(dev); } #ifdef CONFIG_PCI_IOV pci_iov_add_virtfn(edev->physfn, edev->vf_index);#endif return NULL;} static void eeh_rmv_device(struct eeh_dev *edev, void *userdata){ struct pci_driver *driver; struct pci_dev *dev = eeh_dev_to_pci_dev(edev); struct eeh_rmv_data *rmv_data = (struct eeh_rmv_data *)userdata; /* * Actually, we should remove the PCI bridges as well. * However, that's lots of complexity to do that, * particularly some of devices under the bridge mightLines 1–500 of 1246. Load more
// SPDX-License-Identifier: GPL-2.0-or-later /* * PCI Error Recovery Driver for RPA-compliant PPC64 platform. * Copyright IBM Corp. 2004 2005 * Copyright Linas Vepstas <linas@linas.org> 2004, 2005 * * Send comments and feedback to Linas Vepstas <linas@austin.ibm.com> */ #include <linux/delay.h> #include <linux/interrupt.h> #include <linux/irq.h> #include <linux/module.h> #include <linux/pci.h> #include <linux/pci_hotplug.h> #include <asm/eeh.h> #include <asm/eeh_event.h> #include <asm/ppc-pci.h> #include <asm/pci-bridge.h> #include <asm/rtas.h> struct eeh_rmv_data { struct list_head removed_vf_list; int removed_dev_count; }; static int eeh_result_priority(enum pci_ers_result result) { switch (result) { case PCI_ERS_RESULT_NONE: return 1; case PCI_ERS_RESULT_NO_AER_DRIVER: return 2; case PCI_ERS_RESULT_RECOVERED: return 3; case PCI_ERS_RESULT_CAN_RECOVER: return 4; case PCI_ERS_RESULT_DISCONNECT: return 5; case PCI_ERS_RESULT_NEED_RESET: return 6; default: WARN_ONCE(1, "Unknown pci_ers_result value: %d\n", result); return 0; } }; static 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", result); return "unknown"; } }; static enum pci_ers_result pci_ers_merge_result(enum pci_ers_result old, enum pci_ers_result new) { if (eeh_result_priority(new) > eeh_result_priority(old)) return new; return old; } static bool eeh_dev_removed(struct eeh_dev *edev) { return !edev || (edev->mode & EEH_DEV_REMOVED); } static bool eeh_edev_actionable(struct eeh_dev *edev) { if (!edev->pdev) return false; if (edev->pdev->error_state == pci_channel_io_perm_failure) return false; if (eeh_dev_removed(edev)) return false; if (eeh_pe_passed(edev->pe)) return false; return true; } /** * eeh_pcid_get - Get the PCI device driver * @pdev: PCI device * * The function is used to retrieve the PCI device driver for * the indicated PCI device. Besides, we will increase the reference * of the PCI device driver to prevent that being unloaded on * the fly. Otherwise, kernel crash would be seen. */ static inline struct pci_driver *eeh_pcid_get(struct pci_dev *pdev) { if (!pdev || !pdev->dev.driver) return NULL; if (!try_module_get(pdev->dev.driver->owner)) return NULL; return to_pci_driver(pdev->dev.driver); } /** * eeh_pcid_put - Dereference on the PCI device driver * @pdev: PCI device * * The function is called to do dereference on the PCI device * driver of the indicated PCI device. */ static inline void eeh_pcid_put(struct pci_dev *pdev) { if (!pdev || !pdev->dev.driver) return; module_put(pdev->dev.driver->owner); } /** * eeh_disable_irq - Disable interrupt for the recovering device * @dev: PCI device * * This routine must be called when reporting temporary or permanent * error to the particular PCI device to disable interrupt of that * device. If the device has enabled MSI or MSI-X interrupt, we needn't * do real work because EEH should freeze DMA transfers for those PCI * devices encountering EEH errors, which includes MSI or MSI-X. */ static void eeh_disable_irq(struct eeh_dev *edev) { /* Don't disable MSI and MSI-X interrupts. They are * effectively disabled by the DMA Stopped state * when an EEH error occurs. */ if (edev->pdev->msi_enabled || edev->pdev->msix_enabled) return; if (!irq_has_action(edev->pdev->irq)) return; edev->mode |= EEH_DEV_IRQ_DISABLED; disable_irq_nosync(edev->pdev->irq); } /** * eeh_enable_irq - Enable interrupt for the recovering device * @dev: PCI device * * This routine must be called to enable interrupt while failed * device could be resumed. */ static void eeh_enable_irq(struct eeh_dev *edev) { if ((edev->mode) & EEH_DEV_IRQ_DISABLED) { edev->mode &= ~EEH_DEV_IRQ_DISABLED; /* * FIXME !!!!! * * This is just ass backwards. This maze has * unbalanced irq_enable/disable calls. So instead of * finding the root cause it works around the warning * in the irq_enable code by conditionally calling * into it. * * That's just wrong.The warning in the core code is * there to tell people to fix their asymmetries in * their own code, not by abusing the core information * to avoid it. * * I so wish that the assymetry would be the other way * round and a few more irq_disable calls render that * shit unusable forever. * * tglx */ if (irqd_irq_disabled(irq_get_irq_data(edev->pdev->irq))) enable_irq(edev->pdev->irq); } } static void eeh_dev_save_state(struct eeh_dev *edev, void *userdata) { struct pci_dev *pdev; if (!edev) return; /* * We cannot access the config space on some adapters. * Otherwise, it will cause fenced PHB. We don't save * the content in their config space and will restore * from the initial config space saved when the EEH * device is created. */ if (edev->pe && (edev->pe->state & EEH_PE_CFG_RESTRICTED)) return; pdev = eeh_dev_to_pci_dev(edev); if (!pdev) return; pci_save_state(pdev); } static void eeh_set_channel_state(struct eeh_pe *root, pci_channel_state_t s) { struct eeh_pe *pe; struct eeh_dev *edev, *tmp; eeh_for_each_pe(root, pe) eeh_pe_for_each_dev(pe, edev, tmp) if (eeh_edev_actionable(edev)) edev->pdev->error_state = s; } static void eeh_set_irq_state(struct eeh_pe *root, bool enable) { struct eeh_pe *pe; struct eeh_dev *edev, *tmp; eeh_for_each_pe(root, pe) { eeh_pe_for_each_dev(pe, edev, tmp) { if (!eeh_edev_actionable(edev)) continue; if (!eeh_pcid_get(edev->pdev)) continue; if (enable) eeh_enable_irq(edev); else eeh_disable_irq(edev); eeh_pcid_put(edev->pdev); } } } typedef enum pci_ers_result (*eeh_report_fn)(struct eeh_dev *, struct pci_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 *pdev; struct pci_driver *driver; enum pci_ers_result new_result; pdev = edev->pdev; if (pdev) get_device(&pdev->dev); if (!pdev) { eeh_edev_info(edev, "no device"); *result = PCI_ERS_RESULT_DISCONNECT; return; } device_lock(&pdev->dev); if (eeh_edev_actionable(edev)) { driver = eeh_pcid_get(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, pdev, 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(pdev); } else { eeh_edev_info(edev, "not actionable (%d,%d,%d)", !!pdev, !eeh_dev_removed(edev), !eeh_pe_passed(edev->pe)); } device_unlock(&pdev->dev); if (edev->pdev != pdev) eeh_edev_warn(edev, "Device changed during processing!\n"); put_device(&pdev->dev); } 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; 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); } /** * 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_dev *pdev, struct pci_driver *driver) { enum pci_ers_result rc; 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(pdev, pci_channel_io_frozen); edev->in_error = true; pci_uevent_ers(pdev, rc); return rc; } /** * eeh_report_mmio_enabled - Tell drivers that MMIO has been enabled * @edev: eeh device * @driver: device's PCI driver * * Tells each device driver that IO ports, MMIO and config space I/O * are now enabled. */ static enum pci_ers_result eeh_report_mmio_enabled(struct eeh_dev *edev, struct pci_dev *pdev, struct pci_driver *driver) { 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(pdev); } /** * eeh_report_reset - Tell device that slot has been reset * @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 enum pci_ers_result eeh_report_reset(struct eeh_dev *edev, struct pci_dev *pdev, struct pci_driver *driver) { 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(pdev); } static void eeh_dev_restore_state(struct eeh_dev *edev, void *userdata) { struct pci_dev *pdev; if (!edev) return; pci_lock_rescan_remove(); /* * The content in the config space isn't saved because * the blocked config space on some adapters. We have * to restore the initial saved config space when the * EEH device is created. */ if (edev->pe && (edev->pe->state & EEH_PE_CFG_RESTRICTED)) { if (list_is_last(&edev->entry, &edev->pe->edevs)) eeh_pe_restore_bars(edev->pe); pci_unlock_rescan_remove(); return; } pdev = eeh_dev_to_pci_dev(edev); if (!pdev) { pci_unlock_rescan_remove(); return; } pci_restore_state(pdev); pci_unlock_rescan_remove(); } /** * eeh_report_resume - Tell device to resume normal operations * @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 enum pci_ers_result eeh_report_resume(struct eeh_dev *edev, struct pci_dev *pdev, struct pci_driver *driver) { if (!driver->err_handler->resume || !edev->in_error) return PCI_ERS_RESULT_NONE; eeh_edev_info(edev, "Invoking %s->resume()", driver->name); driver->err_handler->resume(pdev); pci_uevent_ers(edev->pdev, PCI_ERS_RESULT_RECOVERED); #ifdef CONFIG_PCI_IOV if (eeh_ops->notify_resume) eeh_ops->notify_resume(edev); #endif return PCI_ERS_RESULT_NONE; } /** * eeh_report_failure - Tell device driver that device is dead. * @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 enum pci_ers_result eeh_report_failure(struct eeh_dev *edev, struct pci_dev *pdev, struct pci_driver *driver) { enum pci_ers_result rc; if (!driver->err_handler->error_detected) return PCI_ERS_RESULT_NONE; eeh_edev_info(edev, "Invoking %s->error_detected(permanent failure)", driver->name); rc = driver->err_handler->error_detected(pdev, pci_channel_io_perm_failure); pci_uevent_ers(pdev, PCI_ERS_RESULT_DISCONNECT); return rc; } static void *eeh_add_virt_device(struct eeh_dev *edev) { struct pci_driver *driver; struct pci_dev *dev = eeh_dev_to_pci_dev(edev); if (!(edev->physfn)) { eeh_edev_warn(edev, "Not for VF\n"); return NULL; } driver = eeh_pcid_get(dev); if (driver) { if (driver->err_handler) { eeh_pcid_put(dev); return NULL; } eeh_pcid_put(dev); } #ifdef CONFIG_PCI_IOV pci_iov_add_virtfn(edev->physfn, edev->vf_index); #endif return NULL; } static void eeh_rmv_device(struct eeh_dev *edev, void *userdata) { struct pci_driver *driver; struct pci_dev *dev = eeh_dev_to_pci_dev(edev); struct eeh_rmv_data *rmv_data = (struct eeh_rmv_data *)userdata; /* * Actually, we should remove the PCI bridges as well. * However, that's lots of complexity to do that, * particularly some of devices under the bridge might