mirror of
https://github.com/Fishwaldo/Star64_linux.git
synced 2025-06-06 22:58:29 +00:00
PCI/AER: Factor out error reporting to drivers/pci/pcie/err.c
Move the error reporting callbacks from aerdrv_core.c to err.c, where they can be used by DPC in addition to AER. As part of aerdrv_core.c, these callbacks were built under CONFIG_PCIEAER. Moving them to the new err.c means they will now be built under CONFIG_PCIEPORTBUS, so adjust the definition of pci_uevent_ers() to match. Signed-off-by: Oza Pawandeep <poza@codeaurora.org> [bhelgaas: in reset_link(), initialize "driver" even if CONFIG_PCIEAER is unset, update pci_uevent_ers() #ifdef wrapper] Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
This commit is contained in:
parent
d25e28e8d2
commit
2e28bc84cf
8 changed files with 398 additions and 366 deletions
|
@ -1535,7 +1535,7 @@ static int pci_uevent(struct device *dev, struct kobj_uevent_env *env)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(CONFIG_PCIEAER) || defined(CONFIG_EEH)
|
#if defined(CONFIG_PCIEPORTBUS) || defined(CONFIG_EEH)
|
||||||
/**
|
/**
|
||||||
* pci_uevent_ers - emit a uevent during recovery path of PCI device
|
* pci_uevent_ers - emit a uevent during recovery path of PCI device
|
||||||
* @pdev: PCI device undergoing error recovery
|
* @pdev: PCI device undergoing error recovery
|
||||||
|
|
|
@ -353,6 +353,10 @@ static inline resource_size_t pci_resource_alignment(struct pci_dev *dev,
|
||||||
|
|
||||||
void pci_enable_acs(struct pci_dev *dev);
|
void pci_enable_acs(struct pci_dev *dev);
|
||||||
|
|
||||||
|
/* PCI error reporting and recovery */
|
||||||
|
void pcie_do_fatal_recovery(struct pci_dev *dev);
|
||||||
|
void pcie_do_nonfatal_recovery(struct pci_dev *dev);
|
||||||
|
|
||||||
bool pcie_wait_for_link(struct pci_dev *pdev, bool active);
|
bool pcie_wait_for_link(struct pci_dev *pdev, bool active);
|
||||||
#ifdef CONFIG_PCIEASPM
|
#ifdef CONFIG_PCIEASPM
|
||||||
void pcie_aspm_init_link_state(struct pci_dev *pdev);
|
void pcie_aspm_init_link_state(struct pci_dev *pdev);
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
#
|
#
|
||||||
# Makefile for PCI Express features and port driver
|
# Makefile for PCI Express features and port driver
|
||||||
|
|
||||||
pcieportdrv-y := portdrv_core.o portdrv_pci.o
|
pcieportdrv-y := portdrv_core.o portdrv_pci.o err.o
|
||||||
|
|
||||||
obj-$(CONFIG_PCIEPORTBUS) += pcieportdrv.o
|
obj-$(CONFIG_PCIEPORTBUS) += pcieportdrv.o
|
||||||
|
|
||||||
|
|
|
@ -76,36 +76,6 @@ struct aer_rpc {
|
||||||
*/
|
*/
|
||||||
};
|
};
|
||||||
|
|
||||||
struct aer_broadcast_data {
|
|
||||||
enum pci_channel_state state;
|
|
||||||
enum pci_ers_result result;
|
|
||||||
};
|
|
||||||
|
|
||||||
static inline pci_ers_result_t merge_result(enum pci_ers_result orig,
|
|
||||||
enum pci_ers_result new)
|
|
||||||
{
|
|
||||||
if (new == PCI_ERS_RESULT_NO_AER_DRIVER)
|
|
||||||
return PCI_ERS_RESULT_NO_AER_DRIVER;
|
|
||||||
|
|
||||||
if (new == PCI_ERS_RESULT_NONE)
|
|
||||||
return orig;
|
|
||||||
|
|
||||||
switch (orig) {
|
|
||||||
case PCI_ERS_RESULT_CAN_RECOVER:
|
|
||||||
case PCI_ERS_RESULT_RECOVERED:
|
|
||||||
orig = new;
|
|
||||||
break;
|
|
||||||
case PCI_ERS_RESULT_DISCONNECT:
|
|
||||||
if (new == PCI_ERS_RESULT_NEED_RESET)
|
|
||||||
orig = PCI_ERS_RESULT_NEED_RESET;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return orig;
|
|
||||||
}
|
|
||||||
|
|
||||||
extern struct bus_type pcie_port_bus_type;
|
extern struct bus_type pcie_port_bus_type;
|
||||||
void aer_isr(struct work_struct *work);
|
void aer_isr(struct work_struct *work);
|
||||||
void aer_print_error(struct pci_dev *dev, struct aer_err_info *info);
|
void aer_print_error(struct pci_dev *dev, struct aer_err_info *info);
|
||||||
|
|
|
@ -228,191 +228,6 @@ static bool find_source_device(struct pci_dev *parent,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int report_error_detected(struct pci_dev *dev, void *data)
|
|
||||||
{
|
|
||||||
pci_ers_result_t vote;
|
|
||||||
const struct pci_error_handlers *err_handler;
|
|
||||||
struct aer_broadcast_data *result_data;
|
|
||||||
result_data = (struct aer_broadcast_data *) data;
|
|
||||||
|
|
||||||
device_lock(&dev->dev);
|
|
||||||
dev->error_state = result_data->state;
|
|
||||||
|
|
||||||
if (!dev->driver ||
|
|
||||||
!dev->driver->err_handler ||
|
|
||||||
!dev->driver->err_handler->error_detected) {
|
|
||||||
if (result_data->state == pci_channel_io_frozen &&
|
|
||||||
dev->hdr_type != PCI_HEADER_TYPE_BRIDGE) {
|
|
||||||
/*
|
|
||||||
* In case of fatal recovery, if one of down-
|
|
||||||
* stream device has no driver. We might be
|
|
||||||
* unable to recover because a later insmod
|
|
||||||
* of a driver for this device is unaware of
|
|
||||||
* its hw state.
|
|
||||||
*/
|
|
||||||
pci_printk(KERN_DEBUG, dev, "device has %s\n",
|
|
||||||
dev->driver ?
|
|
||||||
"no AER-aware driver" : "no driver");
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If there's any device in the subtree that does not
|
|
||||||
* have an error_detected callback, returning
|
|
||||||
* PCI_ERS_RESULT_NO_AER_DRIVER prevents calling of
|
|
||||||
* the subsequent mmio_enabled/slot_reset/resume
|
|
||||||
* callbacks of "any" device in the subtree. All the
|
|
||||||
* devices in the subtree are left in the error state
|
|
||||||
* without recovery.
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (dev->hdr_type != PCI_HEADER_TYPE_BRIDGE)
|
|
||||||
vote = PCI_ERS_RESULT_NO_AER_DRIVER;
|
|
||||||
else
|
|
||||||
vote = PCI_ERS_RESULT_NONE;
|
|
||||||
} else {
|
|
||||||
err_handler = dev->driver->err_handler;
|
|
||||||
vote = err_handler->error_detected(dev, result_data->state);
|
|
||||||
pci_uevent_ers(dev, PCI_ERS_RESULT_NONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
result_data->result = merge_result(result_data->result, vote);
|
|
||||||
device_unlock(&dev->dev);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int report_mmio_enabled(struct pci_dev *dev, void *data)
|
|
||||||
{
|
|
||||||
pci_ers_result_t vote;
|
|
||||||
const struct pci_error_handlers *err_handler;
|
|
||||||
struct aer_broadcast_data *result_data;
|
|
||||||
result_data = (struct aer_broadcast_data *) data;
|
|
||||||
|
|
||||||
device_lock(&dev->dev);
|
|
||||||
if (!dev->driver ||
|
|
||||||
!dev->driver->err_handler ||
|
|
||||||
!dev->driver->err_handler->mmio_enabled)
|
|
||||||
goto out;
|
|
||||||
|
|
||||||
err_handler = dev->driver->err_handler;
|
|
||||||
vote = err_handler->mmio_enabled(dev);
|
|
||||||
result_data->result = merge_result(result_data->result, vote);
|
|
||||||
out:
|
|
||||||
device_unlock(&dev->dev);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int report_slot_reset(struct pci_dev *dev, void *data)
|
|
||||||
{
|
|
||||||
pci_ers_result_t vote;
|
|
||||||
const struct pci_error_handlers *err_handler;
|
|
||||||
struct aer_broadcast_data *result_data;
|
|
||||||
result_data = (struct aer_broadcast_data *) data;
|
|
||||||
|
|
||||||
device_lock(&dev->dev);
|
|
||||||
if (!dev->driver ||
|
|
||||||
!dev->driver->err_handler ||
|
|
||||||
!dev->driver->err_handler->slot_reset)
|
|
||||||
goto out;
|
|
||||||
|
|
||||||
err_handler = dev->driver->err_handler;
|
|
||||||
vote = err_handler->slot_reset(dev);
|
|
||||||
result_data->result = merge_result(result_data->result, vote);
|
|
||||||
out:
|
|
||||||
device_unlock(&dev->dev);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int report_resume(struct pci_dev *dev, void *data)
|
|
||||||
{
|
|
||||||
const struct pci_error_handlers *err_handler;
|
|
||||||
|
|
||||||
device_lock(&dev->dev);
|
|
||||||
dev->error_state = pci_channel_io_normal;
|
|
||||||
|
|
||||||
if (!dev->driver ||
|
|
||||||
!dev->driver->err_handler ||
|
|
||||||
!dev->driver->err_handler->resume)
|
|
||||||
goto out;
|
|
||||||
|
|
||||||
err_handler = dev->driver->err_handler;
|
|
||||||
err_handler->resume(dev);
|
|
||||||
pci_uevent_ers(dev, PCI_ERS_RESULT_RECOVERED);
|
|
||||||
out:
|
|
||||||
device_unlock(&dev->dev);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* broadcast_error_message - handle message broadcast to downstream drivers
|
|
||||||
* @dev: pointer to from where in a hierarchy message is broadcasted down
|
|
||||||
* @state: error state
|
|
||||||
* @error_mesg: message to print
|
|
||||||
* @cb: callback to be broadcasted
|
|
||||||
*
|
|
||||||
* Invoked during error recovery process. Once being invoked, the content
|
|
||||||
* of error severity will be broadcasted to all downstream drivers in a
|
|
||||||
* hierarchy in question.
|
|
||||||
*/
|
|
||||||
static pci_ers_result_t broadcast_error_message(struct pci_dev *dev,
|
|
||||||
enum pci_channel_state state,
|
|
||||||
char *error_mesg,
|
|
||||||
int (*cb)(struct pci_dev *, void *))
|
|
||||||
{
|
|
||||||
struct aer_broadcast_data result_data;
|
|
||||||
|
|
||||||
pci_printk(KERN_DEBUG, dev, "broadcast %s message\n", error_mesg);
|
|
||||||
result_data.state = state;
|
|
||||||
if (cb == report_error_detected)
|
|
||||||
result_data.result = PCI_ERS_RESULT_CAN_RECOVER;
|
|
||||||
else
|
|
||||||
result_data.result = PCI_ERS_RESULT_RECOVERED;
|
|
||||||
|
|
||||||
if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) {
|
|
||||||
/*
|
|
||||||
* If the error is reported by a bridge, we think this error
|
|
||||||
* is related to the downstream link of the bridge, so we
|
|
||||||
* do error recovery on all subordinates of the bridge instead
|
|
||||||
* of the bridge and clear the error status of the bridge.
|
|
||||||
*/
|
|
||||||
if (cb == report_error_detected)
|
|
||||||
dev->error_state = state;
|
|
||||||
pci_walk_bus(dev->subordinate, cb, &result_data);
|
|
||||||
if (cb == report_resume) {
|
|
||||||
pci_cleanup_aer_uncorrect_error_status(dev);
|
|
||||||
dev->error_state = pci_channel_io_normal;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
/*
|
|
||||||
* If the error is reported by an end point, we think this
|
|
||||||
* error is related to the upstream link of the end point.
|
|
||||||
*/
|
|
||||||
if (state == pci_channel_io_normal)
|
|
||||||
/*
|
|
||||||
* the error is non fatal so the bus is ok, just invoke
|
|
||||||
* the callback for the function that logged the error.
|
|
||||||
*/
|
|
||||||
cb(dev, &result_data);
|
|
||||||
else
|
|
||||||
pci_walk_bus(dev->bus, cb, &result_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result_data.result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* default_reset_link - default reset function
|
|
||||||
* @dev: pointer to pci_dev data structure
|
|
||||||
*
|
|
||||||
* Invoked when performing link reset on a Downstream Port or a
|
|
||||||
* Root Port with no aer driver.
|
|
||||||
*/
|
|
||||||
static pci_ers_result_t default_reset_link(struct pci_dev *dev)
|
|
||||||
{
|
|
||||||
pci_reset_bridge_secondary_bus(dev);
|
|
||||||
pci_printk(KERN_DEBUG, dev, "downstream link has been reset\n");
|
|
||||||
return PCI_ERS_RESULT_RECOVERED;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int find_aer_service_iter(struct device *device, void *data)
|
static int find_aer_service_iter(struct device *device, void *data)
|
||||||
{
|
{
|
||||||
struct pcie_port_service_driver *service_driver, **drv;
|
struct pcie_port_service_driver *service_driver, **drv;
|
||||||
|
@ -430,7 +245,7 @@ static int find_aer_service_iter(struct device *device, void *data)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct pcie_port_service_driver *find_aer_service(struct pci_dev *dev)
|
struct pcie_port_service_driver *find_aer_service(struct pci_dev *dev)
|
||||||
{
|
{
|
||||||
struct pcie_port_service_driver *drv = NULL;
|
struct pcie_port_service_driver *drv = NULL;
|
||||||
|
|
||||||
|
@ -439,153 +254,6 @@ static struct pcie_port_service_driver *find_aer_service(struct pci_dev *dev)
|
||||||
return drv;
|
return drv;
|
||||||
}
|
}
|
||||||
|
|
||||||
static pci_ers_result_t reset_link(struct pci_dev *dev)
|
|
||||||
{
|
|
||||||
struct pci_dev *udev;
|
|
||||||
pci_ers_result_t status;
|
|
||||||
struct pcie_port_service_driver *driver;
|
|
||||||
|
|
||||||
if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) {
|
|
||||||
/* Reset this port for all subordinates */
|
|
||||||
udev = dev;
|
|
||||||
} else {
|
|
||||||
/* Reset the upstream component (likely downstream port) */
|
|
||||||
udev = dev->bus->self;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Use the aer driver of the component firstly */
|
|
||||||
driver = find_aer_service(udev);
|
|
||||||
|
|
||||||
if (driver && driver->reset_link) {
|
|
||||||
status = driver->reset_link(udev);
|
|
||||||
} else if (udev->has_secondary_link) {
|
|
||||||
status = default_reset_link(udev);
|
|
||||||
} else {
|
|
||||||
pci_printk(KERN_DEBUG, dev, "no link-reset support at upstream device %s\n",
|
|
||||||
pci_name(udev));
|
|
||||||
return PCI_ERS_RESULT_DISCONNECT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status != PCI_ERS_RESULT_RECOVERED) {
|
|
||||||
pci_printk(KERN_DEBUG, dev, "link reset at upstream device %s failed\n",
|
|
||||||
pci_name(udev));
|
|
||||||
return PCI_ERS_RESULT_DISCONNECT;
|
|
||||||
}
|
|
||||||
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* pcie_do_fatal_recovery - handle fatal error recovery process
|
|
||||||
* @dev: pointer to a pci_dev data structure of agent detecting an error
|
|
||||||
*
|
|
||||||
* Invoked when an error is fatal. Once being invoked, removes the devices
|
|
||||||
* beneath this AER agent, followed by reset link e.g. secondary bus reset
|
|
||||||
* followed by re-enumeration of devices.
|
|
||||||
*/
|
|
||||||
static void pcie_do_fatal_recovery(struct pci_dev *dev)
|
|
||||||
{
|
|
||||||
struct pci_dev *udev;
|
|
||||||
struct pci_bus *parent;
|
|
||||||
struct pci_dev *pdev, *temp;
|
|
||||||
pci_ers_result_t result;
|
|
||||||
|
|
||||||
if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE)
|
|
||||||
udev = dev;
|
|
||||||
else
|
|
||||||
udev = dev->bus->self;
|
|
||||||
|
|
||||||
parent = udev->subordinate;
|
|
||||||
pci_lock_rescan_remove();
|
|
||||||
list_for_each_entry_safe_reverse(pdev, temp, &parent->devices,
|
|
||||||
bus_list) {
|
|
||||||
pci_dev_get(pdev);
|
|
||||||
pci_dev_set_disconnected(pdev, NULL);
|
|
||||||
if (pci_has_subordinate(pdev))
|
|
||||||
pci_walk_bus(pdev->subordinate,
|
|
||||||
pci_dev_set_disconnected, NULL);
|
|
||||||
pci_stop_and_remove_bus_device(pdev);
|
|
||||||
pci_dev_put(pdev);
|
|
||||||
}
|
|
||||||
|
|
||||||
result = reset_link(udev);
|
|
||||||
|
|
||||||
if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) {
|
|
||||||
/*
|
|
||||||
* If the error is reported by a bridge, we think this error
|
|
||||||
* is related to the downstream link of the bridge, so we
|
|
||||||
* do error recovery on all subordinates of the bridge instead
|
|
||||||
* of the bridge and clear the error status of the bridge.
|
|
||||||
*/
|
|
||||||
pci_cleanup_aer_uncorrect_error_status(dev);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result == PCI_ERS_RESULT_RECOVERED) {
|
|
||||||
if (pcie_wait_for_link(udev, true))
|
|
||||||
pci_rescan_bus(udev->bus);
|
|
||||||
} else {
|
|
||||||
pci_uevent_ers(dev, PCI_ERS_RESULT_DISCONNECT);
|
|
||||||
pci_info(dev, "AER: Device recovery from fatal error failed\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
pci_unlock_rescan_remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* pcie_do_nonfatal_recovery - handle nonfatal error recovery process
|
|
||||||
* @dev: pointer to a pci_dev data structure of agent detecting an error
|
|
||||||
*
|
|
||||||
* Invoked when an error is nonfatal. Once being invoked, broadcast
|
|
||||||
* error detected message to all downstream drivers within a hierarchy in
|
|
||||||
* question and return the returned code.
|
|
||||||
*/
|
|
||||||
static void pcie_do_nonfatal_recovery(struct pci_dev *dev)
|
|
||||||
{
|
|
||||||
pci_ers_result_t status;
|
|
||||||
enum pci_channel_state state;
|
|
||||||
|
|
||||||
state = pci_channel_io_normal;
|
|
||||||
|
|
||||||
status = broadcast_error_message(dev,
|
|
||||||
state,
|
|
||||||
"error_detected",
|
|
||||||
report_error_detected);
|
|
||||||
|
|
||||||
if (status == PCI_ERS_RESULT_CAN_RECOVER)
|
|
||||||
status = broadcast_error_message(dev,
|
|
||||||
state,
|
|
||||||
"mmio_enabled",
|
|
||||||
report_mmio_enabled);
|
|
||||||
|
|
||||||
if (status == PCI_ERS_RESULT_NEED_RESET) {
|
|
||||||
/*
|
|
||||||
* TODO: Should call platform-specific
|
|
||||||
* functions to reset slot before calling
|
|
||||||
* drivers' slot_reset callbacks?
|
|
||||||
*/
|
|
||||||
status = broadcast_error_message(dev,
|
|
||||||
state,
|
|
||||||
"slot_reset",
|
|
||||||
report_slot_reset);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status != PCI_ERS_RESULT_RECOVERED)
|
|
||||||
goto failed;
|
|
||||||
|
|
||||||
broadcast_error_message(dev,
|
|
||||||
state,
|
|
||||||
"resume",
|
|
||||||
report_resume);
|
|
||||||
|
|
||||||
pci_info(dev, "AER: Device recovery successful\n");
|
|
||||||
return;
|
|
||||||
|
|
||||||
failed:
|
|
||||||
pci_uevent_ers(dev, PCI_ERS_RESULT_DISCONNECT);
|
|
||||||
/* TODO: Should kernel panic here? */
|
|
||||||
pci_info(dev, "AER: Device recovery failed\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* handle_error_source - handle logging error into an event log
|
* handle_error_source - handle logging error into an event log
|
||||||
* @aerdev: pointer to pcie_device data structure of the root port
|
* @aerdev: pointer to pcie_device data structure of the root port
|
||||||
|
|
389
drivers/pci/pcie/err.c
Normal file
389
drivers/pci/pcie/err.c
Normal file
|
@ -0,0 +1,389 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* This file implements the error recovery as a core part of PCIe error
|
||||||
|
* reporting. When a PCIe error is delivered, an error message will be
|
||||||
|
* collected and printed to console, then, an error recovery procedure
|
||||||
|
* will be executed by following the PCI error recovery rules.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2006 Intel Corp.
|
||||||
|
* Tom Long Nguyen (tom.l.nguyen@intel.com)
|
||||||
|
* Zhang Yanmin (yanmin.zhang@intel.com)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/pci.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/pci.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/errno.h>
|
||||||
|
#include <linux/aer.h>
|
||||||
|
#include "portdrv.h"
|
||||||
|
#include "../pci.h"
|
||||||
|
|
||||||
|
struct aer_broadcast_data {
|
||||||
|
enum pci_channel_state state;
|
||||||
|
enum pci_ers_result result;
|
||||||
|
};
|
||||||
|
|
||||||
|
static pci_ers_result_t merge_result(enum pci_ers_result orig,
|
||||||
|
enum pci_ers_result new)
|
||||||
|
{
|
||||||
|
if (new == PCI_ERS_RESULT_NO_AER_DRIVER)
|
||||||
|
return PCI_ERS_RESULT_NO_AER_DRIVER;
|
||||||
|
|
||||||
|
if (new == PCI_ERS_RESULT_NONE)
|
||||||
|
return orig;
|
||||||
|
|
||||||
|
switch (orig) {
|
||||||
|
case PCI_ERS_RESULT_CAN_RECOVER:
|
||||||
|
case PCI_ERS_RESULT_RECOVERED:
|
||||||
|
orig = new;
|
||||||
|
break;
|
||||||
|
case PCI_ERS_RESULT_DISCONNECT:
|
||||||
|
if (new == PCI_ERS_RESULT_NEED_RESET)
|
||||||
|
orig = PCI_ERS_RESULT_NEED_RESET;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return orig;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int report_error_detected(struct pci_dev *dev, void *data)
|
||||||
|
{
|
||||||
|
pci_ers_result_t vote;
|
||||||
|
const struct pci_error_handlers *err_handler;
|
||||||
|
struct aer_broadcast_data *result_data;
|
||||||
|
|
||||||
|
result_data = (struct aer_broadcast_data *) data;
|
||||||
|
|
||||||
|
device_lock(&dev->dev);
|
||||||
|
dev->error_state = result_data->state;
|
||||||
|
|
||||||
|
if (!dev->driver ||
|
||||||
|
!dev->driver->err_handler ||
|
||||||
|
!dev->driver->err_handler->error_detected) {
|
||||||
|
if (result_data->state == pci_channel_io_frozen &&
|
||||||
|
dev->hdr_type != PCI_HEADER_TYPE_BRIDGE) {
|
||||||
|
/*
|
||||||
|
* In case of fatal recovery, if one of down-
|
||||||
|
* stream device has no driver. We might be
|
||||||
|
* unable to recover because a later insmod
|
||||||
|
* of a driver for this device is unaware of
|
||||||
|
* its hw state.
|
||||||
|
*/
|
||||||
|
pci_printk(KERN_DEBUG, dev, "device has %s\n",
|
||||||
|
dev->driver ?
|
||||||
|
"no AER-aware driver" : "no driver");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If there's any device in the subtree that does not
|
||||||
|
* have an error_detected callback, returning
|
||||||
|
* PCI_ERS_RESULT_NO_AER_DRIVER prevents calling of
|
||||||
|
* the subsequent mmio_enabled/slot_reset/resume
|
||||||
|
* callbacks of "any" device in the subtree. All the
|
||||||
|
* devices in the subtree are left in the error state
|
||||||
|
* without recovery.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (dev->hdr_type != PCI_HEADER_TYPE_BRIDGE)
|
||||||
|
vote = PCI_ERS_RESULT_NO_AER_DRIVER;
|
||||||
|
else
|
||||||
|
vote = PCI_ERS_RESULT_NONE;
|
||||||
|
} else {
|
||||||
|
err_handler = dev->driver->err_handler;
|
||||||
|
vote = err_handler->error_detected(dev, result_data->state);
|
||||||
|
pci_uevent_ers(dev, PCI_ERS_RESULT_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
result_data->result = merge_result(result_data->result, vote);
|
||||||
|
device_unlock(&dev->dev);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int report_mmio_enabled(struct pci_dev *dev, void *data)
|
||||||
|
{
|
||||||
|
pci_ers_result_t vote;
|
||||||
|
const struct pci_error_handlers *err_handler;
|
||||||
|
struct aer_broadcast_data *result_data;
|
||||||
|
|
||||||
|
result_data = (struct aer_broadcast_data *) data;
|
||||||
|
|
||||||
|
device_lock(&dev->dev);
|
||||||
|
if (!dev->driver ||
|
||||||
|
!dev->driver->err_handler ||
|
||||||
|
!dev->driver->err_handler->mmio_enabled)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
err_handler = dev->driver->err_handler;
|
||||||
|
vote = err_handler->mmio_enabled(dev);
|
||||||
|
result_data->result = merge_result(result_data->result, vote);
|
||||||
|
out:
|
||||||
|
device_unlock(&dev->dev);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int report_slot_reset(struct pci_dev *dev, void *data)
|
||||||
|
{
|
||||||
|
pci_ers_result_t vote;
|
||||||
|
const struct pci_error_handlers *err_handler;
|
||||||
|
struct aer_broadcast_data *result_data;
|
||||||
|
|
||||||
|
result_data = (struct aer_broadcast_data *) data;
|
||||||
|
|
||||||
|
device_lock(&dev->dev);
|
||||||
|
if (!dev->driver ||
|
||||||
|
!dev->driver->err_handler ||
|
||||||
|
!dev->driver->err_handler->slot_reset)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
err_handler = dev->driver->err_handler;
|
||||||
|
vote = err_handler->slot_reset(dev);
|
||||||
|
result_data->result = merge_result(result_data->result, vote);
|
||||||
|
out:
|
||||||
|
device_unlock(&dev->dev);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int report_resume(struct pci_dev *dev, void *data)
|
||||||
|
{
|
||||||
|
const struct pci_error_handlers *err_handler;
|
||||||
|
|
||||||
|
device_lock(&dev->dev);
|
||||||
|
dev->error_state = pci_channel_io_normal;
|
||||||
|
|
||||||
|
if (!dev->driver ||
|
||||||
|
!dev->driver->err_handler ||
|
||||||
|
!dev->driver->err_handler->resume)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
err_handler = dev->driver->err_handler;
|
||||||
|
err_handler->resume(dev);
|
||||||
|
pci_uevent_ers(dev, PCI_ERS_RESULT_RECOVERED);
|
||||||
|
out:
|
||||||
|
device_unlock(&dev->dev);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* default_reset_link - default reset function
|
||||||
|
* @dev: pointer to pci_dev data structure
|
||||||
|
*
|
||||||
|
* Invoked when performing link reset on a Downstream Port or a
|
||||||
|
* Root Port with no aer driver.
|
||||||
|
*/
|
||||||
|
static pci_ers_result_t default_reset_link(struct pci_dev *dev)
|
||||||
|
{
|
||||||
|
pci_reset_bridge_secondary_bus(dev);
|
||||||
|
pci_printk(KERN_DEBUG, dev, "downstream link has been reset\n");
|
||||||
|
return PCI_ERS_RESULT_RECOVERED;
|
||||||
|
}
|
||||||
|
|
||||||
|
static pci_ers_result_t reset_link(struct pci_dev *dev)
|
||||||
|
{
|
||||||
|
struct pci_dev *udev;
|
||||||
|
pci_ers_result_t status;
|
||||||
|
struct pcie_port_service_driver *driver = NULL;
|
||||||
|
|
||||||
|
if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) {
|
||||||
|
/* Reset this port for all subordinates */
|
||||||
|
udev = dev;
|
||||||
|
} else {
|
||||||
|
/* Reset the upstream component (likely downstream port) */
|
||||||
|
udev = dev->bus->self;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if IS_ENABLED(CONFIG_PCIEAER)
|
||||||
|
/* Use the aer driver of the component firstly */
|
||||||
|
driver = find_aer_service(udev);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (driver && driver->reset_link) {
|
||||||
|
status = driver->reset_link(udev);
|
||||||
|
} else if (udev->has_secondary_link) {
|
||||||
|
status = default_reset_link(udev);
|
||||||
|
} else {
|
||||||
|
pci_printk(KERN_DEBUG, dev, "no link-reset support at upstream device %s\n",
|
||||||
|
pci_name(udev));
|
||||||
|
return PCI_ERS_RESULT_DISCONNECT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status != PCI_ERS_RESULT_RECOVERED) {
|
||||||
|
pci_printk(KERN_DEBUG, dev, "link reset at upstream device %s failed\n",
|
||||||
|
pci_name(udev));
|
||||||
|
return PCI_ERS_RESULT_DISCONNECT;
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* broadcast_error_message - handle message broadcast to downstream drivers
|
||||||
|
* @dev: pointer to from where in a hierarchy message is broadcasted down
|
||||||
|
* @state: error state
|
||||||
|
* @error_mesg: message to print
|
||||||
|
* @cb: callback to be broadcasted
|
||||||
|
*
|
||||||
|
* Invoked during error recovery process. Once being invoked, the content
|
||||||
|
* of error severity will be broadcasted to all downstream drivers in a
|
||||||
|
* hierarchy in question.
|
||||||
|
*/
|
||||||
|
static pci_ers_result_t broadcast_error_message(struct pci_dev *dev,
|
||||||
|
enum pci_channel_state state,
|
||||||
|
char *error_mesg,
|
||||||
|
int (*cb)(struct pci_dev *, void *))
|
||||||
|
{
|
||||||
|
struct aer_broadcast_data result_data;
|
||||||
|
|
||||||
|
pci_printk(KERN_DEBUG, dev, "broadcast %s message\n", error_mesg);
|
||||||
|
result_data.state = state;
|
||||||
|
if (cb == report_error_detected)
|
||||||
|
result_data.result = PCI_ERS_RESULT_CAN_RECOVER;
|
||||||
|
else
|
||||||
|
result_data.result = PCI_ERS_RESULT_RECOVERED;
|
||||||
|
|
||||||
|
if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) {
|
||||||
|
/*
|
||||||
|
* If the error is reported by a bridge, we think this error
|
||||||
|
* is related to the downstream link of the bridge, so we
|
||||||
|
* do error recovery on all subordinates of the bridge instead
|
||||||
|
* of the bridge and clear the error status of the bridge.
|
||||||
|
*/
|
||||||
|
if (cb == report_error_detected)
|
||||||
|
dev->error_state = state;
|
||||||
|
pci_walk_bus(dev->subordinate, cb, &result_data);
|
||||||
|
if (cb == report_resume) {
|
||||||
|
pci_cleanup_aer_uncorrect_error_status(dev);
|
||||||
|
dev->error_state = pci_channel_io_normal;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* If the error is reported by an end point, we think this
|
||||||
|
* error is related to the upstream link of the end point.
|
||||||
|
*/
|
||||||
|
if (state == pci_channel_io_normal)
|
||||||
|
/*
|
||||||
|
* the error is non fatal so the bus is ok, just invoke
|
||||||
|
* the callback for the function that logged the error.
|
||||||
|
*/
|
||||||
|
cb(dev, &result_data);
|
||||||
|
else
|
||||||
|
pci_walk_bus(dev->bus, cb, &result_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result_data.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pcie_do_fatal_recovery - handle fatal error recovery process
|
||||||
|
* @dev: pointer to a pci_dev data structure of agent detecting an error
|
||||||
|
*
|
||||||
|
* Invoked when an error is fatal. Once being invoked, removes the devices
|
||||||
|
* beneath this AER agent, followed by reset link e.g. secondary bus reset
|
||||||
|
* followed by re-enumeration of devices.
|
||||||
|
*/
|
||||||
|
void pcie_do_fatal_recovery(struct pci_dev *dev)
|
||||||
|
{
|
||||||
|
struct pci_dev *udev;
|
||||||
|
struct pci_bus *parent;
|
||||||
|
struct pci_dev *pdev, *temp;
|
||||||
|
pci_ers_result_t result;
|
||||||
|
|
||||||
|
if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE)
|
||||||
|
udev = dev;
|
||||||
|
else
|
||||||
|
udev = dev->bus->self;
|
||||||
|
|
||||||
|
parent = udev->subordinate;
|
||||||
|
pci_lock_rescan_remove();
|
||||||
|
list_for_each_entry_safe_reverse(pdev, temp, &parent->devices,
|
||||||
|
bus_list) {
|
||||||
|
pci_dev_get(pdev);
|
||||||
|
pci_dev_set_disconnected(pdev, NULL);
|
||||||
|
if (pci_has_subordinate(pdev))
|
||||||
|
pci_walk_bus(pdev->subordinate,
|
||||||
|
pci_dev_set_disconnected, NULL);
|
||||||
|
pci_stop_and_remove_bus_device(pdev);
|
||||||
|
pci_dev_put(pdev);
|
||||||
|
}
|
||||||
|
|
||||||
|
result = reset_link(udev);
|
||||||
|
|
||||||
|
if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) {
|
||||||
|
/*
|
||||||
|
* If the error is reported by a bridge, we think this error
|
||||||
|
* is related to the downstream link of the bridge, so we
|
||||||
|
* do error recovery on all subordinates of the bridge instead
|
||||||
|
* of the bridge and clear the error status of the bridge.
|
||||||
|
*/
|
||||||
|
pci_cleanup_aer_uncorrect_error_status(dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result == PCI_ERS_RESULT_RECOVERED) {
|
||||||
|
if (pcie_wait_for_link(udev, true))
|
||||||
|
pci_rescan_bus(udev->bus);
|
||||||
|
pci_info(dev, "Device recovery from fatal error successful\n");
|
||||||
|
} else {
|
||||||
|
pci_uevent_ers(dev, PCI_ERS_RESULT_DISCONNECT);
|
||||||
|
pci_info(dev, "Device recovery from fatal error failed\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
pci_unlock_rescan_remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pcie_do_nonfatal_recovery - handle nonfatal error recovery process
|
||||||
|
* @dev: pointer to a pci_dev data structure of agent detecting an error
|
||||||
|
*
|
||||||
|
* Invoked when an error is nonfatal/fatal. Once being invoked, broadcast
|
||||||
|
* error detected message to all downstream drivers within a hierarchy in
|
||||||
|
* question and return the returned code.
|
||||||
|
*/
|
||||||
|
void pcie_do_nonfatal_recovery(struct pci_dev *dev)
|
||||||
|
{
|
||||||
|
pci_ers_result_t status;
|
||||||
|
enum pci_channel_state state;
|
||||||
|
|
||||||
|
state = pci_channel_io_normal;
|
||||||
|
|
||||||
|
status = broadcast_error_message(dev,
|
||||||
|
state,
|
||||||
|
"error_detected",
|
||||||
|
report_error_detected);
|
||||||
|
|
||||||
|
if (status == PCI_ERS_RESULT_CAN_RECOVER)
|
||||||
|
status = broadcast_error_message(dev,
|
||||||
|
state,
|
||||||
|
"mmio_enabled",
|
||||||
|
report_mmio_enabled);
|
||||||
|
|
||||||
|
if (status == PCI_ERS_RESULT_NEED_RESET) {
|
||||||
|
/*
|
||||||
|
* TODO: Should call platform-specific
|
||||||
|
* functions to reset slot before calling
|
||||||
|
* drivers' slot_reset callbacks?
|
||||||
|
*/
|
||||||
|
status = broadcast_error_message(dev,
|
||||||
|
state,
|
||||||
|
"slot_reset",
|
||||||
|
report_slot_reset);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status != PCI_ERS_RESULT_RECOVERED)
|
||||||
|
goto failed;
|
||||||
|
|
||||||
|
broadcast_error_message(dev,
|
||||||
|
state,
|
||||||
|
"resume",
|
||||||
|
report_resume);
|
||||||
|
|
||||||
|
pci_info(dev, "AER: Device recovery successful\n");
|
||||||
|
return;
|
||||||
|
|
||||||
|
failed:
|
||||||
|
pci_uevent_ers(dev, PCI_ERS_RESULT_DISCONNECT);
|
||||||
|
|
||||||
|
/* TODO: Should kernel panic here? */
|
||||||
|
pci_info(dev, "AER: Device recovery failed\n");
|
||||||
|
}
|
|
@ -112,4 +112,5 @@ static inline bool pcie_pme_no_msi(void) { return false; }
|
||||||
static inline void pcie_pme_interrupt_enable(struct pci_dev *dev, bool en) {}
|
static inline void pcie_pme_interrupt_enable(struct pci_dev *dev, bool en) {}
|
||||||
#endif /* !CONFIG_PCIE_PME */
|
#endif /* !CONFIG_PCIE_PME */
|
||||||
|
|
||||||
|
struct pcie_port_service_driver *find_aer_service(struct pci_dev *dev);
|
||||||
#endif /* _PORTDRV_H_ */
|
#endif /* _PORTDRV_H_ */
|
||||||
|
|
|
@ -2284,7 +2284,7 @@ static inline bool pci_is_thunderbolt_attached(struct pci_dev *pdev)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(CONFIG_PCIEAER) || defined(CONFIG_EEH)
|
#if defined(CONFIG_PCIEPORTBUS) || defined(CONFIG_EEH)
|
||||||
void pci_uevent_ers(struct pci_dev *pdev, enum pci_ers_result err_type);
|
void pci_uevent_ers(struct pci_dev *pdev, enum pci_ers_result err_type);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue