// 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->