1246 lines · 32.8 KB · C
RawDownload
// 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

Lines 1–500 of 1246.