mirror of
https://github.com/FEX-Emu/linux.git
synced 2025-02-21 21:02:24 +00:00
Merge master.kernel.org:/pub/scm/linux/kernel/git/gregkh/pci-2.6
* master.kernel.org:/pub/scm/linux/kernel/git/gregkh/pci-2.6: (28 commits) pciehp - fix wrong return value IA64: PCI: dont disable irq which is not enabled acpiphp: add support for ioapic hot-remove PCI: assign ioapic resource at hotplug acpiphp: disable bridges acpiphp: stop bus device before acpi_bus_trim PCI: add pci_stop_bus_device acpiphp: do not initialize existing ioapics acpiphp: initialize ioapics before starting devices acpiphp: set hpp values before starting devices PCI Hotplug: cleanup pcihp skeleton code. PCI: Restore PCI Express capability registers after PM event PCI: drivers/pci/hotplug/acpiphp_glue.c: make a function static PCI: Multiprobe sanitizer PCI: fix __must_check warnings PCI Hotplug: fix __must_check warnings SHPCHP: fix __must_check warnings PCI-Express AER implemetation: pcie_portdrv error handler PCI-Express AER implemetation: AER core and aerdriver PCI-Express AER implemetation: export pcie_port_bus_type ...
This commit is contained in:
commit
ff0972c26b
253
Documentation/pcieaer-howto.txt
Normal file
253
Documentation/pcieaer-howto.txt
Normal file
@ -0,0 +1,253 @@
|
||||
The PCI Express Advanced Error Reporting Driver Guide HOWTO
|
||||
T. Long Nguyen <tom.l.nguyen@intel.com>
|
||||
Yanmin Zhang <yanmin.zhang@intel.com>
|
||||
07/29/2006
|
||||
|
||||
|
||||
1. Overview
|
||||
|
||||
1.1 About this guide
|
||||
|
||||
This guide describes the basics of the PCI Express Advanced Error
|
||||
Reporting (AER) driver and provides information on how to use it, as
|
||||
well as how to enable the drivers of endpoint devices to conform with
|
||||
PCI Express AER driver.
|
||||
|
||||
1.2 Copyright © Intel Corporation 2006.
|
||||
|
||||
1.3 What is the PCI Express AER Driver?
|
||||
|
||||
PCI Express error signaling can occur on the PCI Express link itself
|
||||
or on behalf of transactions initiated on the link. PCI Express
|
||||
defines two error reporting paradigms: the baseline capability and
|
||||
the Advanced Error Reporting capability. The baseline capability is
|
||||
required of all PCI Express components providing a minimum defined
|
||||
set of error reporting requirements. Advanced Error Reporting
|
||||
capability is implemented with a PCI Express advanced error reporting
|
||||
extended capability structure providing more robust error reporting.
|
||||
|
||||
The PCI Express AER driver provides the infrastructure to support PCI
|
||||
Express Advanced Error Reporting capability. The PCI Express AER
|
||||
driver provides three basic functions:
|
||||
|
||||
- Gathers the comprehensive error information if errors occurred.
|
||||
- Reports error to the users.
|
||||
- Performs error recovery actions.
|
||||
|
||||
AER driver only attaches root ports which support PCI-Express AER
|
||||
capability.
|
||||
|
||||
|
||||
2. User Guide
|
||||
|
||||
2.1 Include the PCI Express AER Root Driver into the Linux Kernel
|
||||
|
||||
The PCI Express AER Root driver is a Root Port service driver attached
|
||||
to the PCI Express Port Bus driver. If a user wants to use it, the driver
|
||||
has to be compiled. Option CONFIG_PCIEAER supports this capability. It
|
||||
depends on CONFIG_PCIEPORTBUS, so pls. set CONFIG_PCIEPORTBUS=y and
|
||||
CONFIG_PCIEAER = y.
|
||||
|
||||
2.2 Load PCI Express AER Root Driver
|
||||
There is a case where a system has AER support in BIOS. Enabling the AER
|
||||
Root driver and having AER support in BIOS may result unpredictable
|
||||
behavior. To avoid this conflict, a successful load of the AER Root driver
|
||||
requires ACPI _OSC support in the BIOS to allow the AER Root driver to
|
||||
request for native control of AER. See the PCI FW 3.0 Specification for
|
||||
details regarding OSC usage. Currently, lots of firmwares don't provide
|
||||
_OSC support while they use PCI Express. To support such firmwares,
|
||||
forceload, a parameter of type bool, could enable AER to continue to
|
||||
be initiated although firmwares have no _OSC support. To enable the
|
||||
walkaround, pls. add aerdriver.forceload=y to kernel boot parameter line
|
||||
when booting kernel. Note that forceload=n by default.
|
||||
|
||||
2.3 AER error output
|
||||
When a PCI-E AER error is captured, an error message will be outputed to
|
||||
console. If it's a correctable error, it is outputed as a warning.
|
||||
Otherwise, it is printed as an error. So users could choose different
|
||||
log level to filter out correctable error messages.
|
||||
|
||||
Below shows an example.
|
||||
+------ PCI-Express Device Error -----+
|
||||
Error Severity : Uncorrected (Fatal)
|
||||
PCIE Bus Error type : Transaction Layer
|
||||
Unsupported Request : First
|
||||
Requester ID : 0500
|
||||
VendorID=8086h, DeviceID=0329h, Bus=05h, Device=00h, Function=00h
|
||||
TLB Header:
|
||||
04000001 00200a03 05010000 00050100
|
||||
|
||||
In the example, 'Requester ID' means the ID of the device who sends
|
||||
the error message to root port. Pls. refer to pci express specs for
|
||||
other fields.
|
||||
|
||||
|
||||
3. Developer Guide
|
||||
|
||||
To enable AER aware support requires a software driver to configure
|
||||
the AER capability structure within its device and to provide callbacks.
|
||||
|
||||
To support AER better, developers need understand how AER does work
|
||||
firstly.
|
||||
|
||||
PCI Express errors are classified into two types: correctable errors
|
||||
and uncorrectable errors. This classification is based on the impacts
|
||||
of those errors, which may result in degraded performance or function
|
||||
failure.
|
||||
|
||||
Correctable errors pose no impacts on the functionality of the
|
||||
interface. The PCI Express protocol can recover without any software
|
||||
intervention or any loss of data. These errors are detected and
|
||||
corrected by hardware. Unlike correctable errors, uncorrectable
|
||||
errors impact functionality of the interface. Uncorrectable errors
|
||||
can cause a particular transaction or a particular PCI Express link
|
||||
to be unreliable. Depending on those error conditions, uncorrectable
|
||||
errors are further classified into non-fatal errors and fatal errors.
|
||||
Non-fatal errors cause the particular transaction to be unreliable,
|
||||
but the PCI Express link itself is fully functional. Fatal errors, on
|
||||
the other hand, cause the link to be unreliable.
|
||||
|
||||
When AER is enabled, a PCI Express device will automatically send an
|
||||
error message to the PCIE root port above it when the device captures
|
||||
an error. The Root Port, upon receiving an error reporting message,
|
||||
internally processes and logs the error message in its PCI Express
|
||||
capability structure. Error information being logged includes storing
|
||||
the error reporting agent's requestor ID into the Error Source
|
||||
Identification Registers and setting the error bits of the Root Error
|
||||
Status Register accordingly. If AER error reporting is enabled in Root
|
||||
Error Command Register, the Root Port generates an interrupt if an
|
||||
error is detected.
|
||||
|
||||
Note that the errors as described above are related to the PCI Express
|
||||
hierarchy and links. These errors do not include any device specific
|
||||
errors because device specific errors will still get sent directly to
|
||||
the device driver.
|
||||
|
||||
3.1 Configure the AER capability structure
|
||||
|
||||
AER aware drivers of PCI Express component need change the device
|
||||
control registers to enable AER. They also could change AER registers,
|
||||
including mask and severity registers. Helper function
|
||||
pci_enable_pcie_error_reporting could be used to enable AER. See
|
||||
section 3.3.
|
||||
|
||||
3.2. Provide callbacks
|
||||
|
||||
3.2.1 callback reset_link to reset pci express link
|
||||
|
||||
This callback is used to reset the pci express physical link when a
|
||||
fatal error happens. The root port aer service driver provides a
|
||||
default reset_link function, but different upstream ports might
|
||||
have different specifications to reset pci express link, so all
|
||||
upstream ports should provide their own reset_link functions.
|
||||
|
||||
In struct pcie_port_service_driver, a new pointer, reset_link, is
|
||||
added.
|
||||
|
||||
pci_ers_result_t (*reset_link) (struct pci_dev *dev);
|
||||
|
||||
Section 3.2.2.2 provides more detailed info on when to call
|
||||
reset_link.
|
||||
|
||||
3.2.2 PCI error-recovery callbacks
|
||||
|
||||
The PCI Express AER Root driver uses error callbacks to coordinate
|
||||
with downstream device drivers associated with a hierarchy in question
|
||||
when performing error recovery actions.
|
||||
|
||||
Data struct pci_driver has a pointer, err_handler, to point to
|
||||
pci_error_handlers who consists of a couple of callback function
|
||||
pointers. AER driver follows the rules defined in
|
||||
pci-error-recovery.txt except pci express specific parts (e.g.
|
||||
reset_link). Pls. refer to pci-error-recovery.txt for detailed
|
||||
definitions of the callbacks.
|
||||
|
||||
Below sections specify when to call the error callback functions.
|
||||
|
||||
3.2.2.1 Correctable errors
|
||||
|
||||
Correctable errors pose no impacts on the functionality of
|
||||
the interface. The PCI Express protocol can recover without any
|
||||
software intervention or any loss of data. These errors do not
|
||||
require any recovery actions. The AER driver clears the device's
|
||||
correctable error status register accordingly and logs these errors.
|
||||
|
||||
3.2.2.2 Non-correctable (non-fatal and fatal) errors
|
||||
|
||||
If an error message indicates a non-fatal error, performing link reset
|
||||
at upstream is not required. The AER driver calls error_detected(dev,
|
||||
pci_channel_io_normal) to all drivers associated within a hierarchy in
|
||||
question. for example,
|
||||
EndPoint<==>DownstreamPort B<==>UpstreamPort A<==>RootPort.
|
||||
If Upstream port A captures an AER error, the hierarchy consists of
|
||||
Downstream port B and EndPoint.
|
||||
|
||||
A driver may return PCI_ERS_RESULT_CAN_RECOVER,
|
||||
PCI_ERS_RESULT_DISCONNECT, or PCI_ERS_RESULT_NEED_RESET, depending on
|
||||
whether it can recover or the AER driver calls mmio_enabled as next.
|
||||
|
||||
If an error message indicates a fatal error, kernel will broadcast
|
||||
error_detected(dev, pci_channel_io_frozen) to all drivers within
|
||||
a hierarchy in question. Then, performing link reset at upstream is
|
||||
necessary. As different kinds of devices might use different approaches
|
||||
to reset link, AER port service driver is required to provide the
|
||||
function to reset link. Firstly, kernel looks for if the upstream
|
||||
component has an aer driver. If it has, kernel uses the reset_link
|
||||
callback of the aer driver. If the upstream component has no aer driver
|
||||
and the port is downstream port, we will use the aer driver of the
|
||||
root port who reports the AER error. As for upstream ports,
|
||||
they should provide their own aer service drivers with reset_link
|
||||
function. If error_detected returns PCI_ERS_RESULT_CAN_RECOVER and
|
||||
reset_link returns PCI_ERS_RESULT_RECOVERED, the error handling goes
|
||||
to mmio_enabled.
|
||||
|
||||
3.3 helper functions
|
||||
|
||||
3.3.1 int pci_find_aer_capability(struct pci_dev *dev);
|
||||
pci_find_aer_capability locates the PCI Express AER capability
|
||||
in the device configuration space. If the device doesn't support
|
||||
PCI-Express AER, the function returns 0.
|
||||
|
||||
3.3.2 int pci_enable_pcie_error_reporting(struct pci_dev *dev);
|
||||
pci_enable_pcie_error_reporting enables the device to send error
|
||||
messages to root port when an error is detected. Note that devices
|
||||
don't enable the error reporting by default, so device drivers need
|
||||
call this function to enable it.
|
||||
|
||||
3.3.3 int pci_disable_pcie_error_reporting(struct pci_dev *dev);
|
||||
pci_disable_pcie_error_reporting disables the device to send error
|
||||
messages to root port when an error is detected.
|
||||
|
||||
3.3.4 int pci_cleanup_aer_uncorrect_error_status(struct pci_dev *dev);
|
||||
pci_cleanup_aer_uncorrect_error_status cleanups the uncorrectable
|
||||
error status register.
|
||||
|
||||
3.4 Frequent Asked Questions
|
||||
|
||||
Q: What happens if a PCI Express device driver does not provide an
|
||||
error recovery handler (pci_driver->err_handler is equal to NULL)?
|
||||
|
||||
A: The devices attached with the driver won't be recovered. If the
|
||||
error is fatal, kernel will print out warning messages. Please refer
|
||||
to section 3 for more information.
|
||||
|
||||
Q: What happens if an upstream port service driver does not provide
|
||||
callback reset_link?
|
||||
|
||||
A: Fatal error recovery will fail if the errors are reported by the
|
||||
upstream ports who are attached by the service driver.
|
||||
|
||||
Q: How does this infrastructure deal with driver that is not PCI
|
||||
Express aware?
|
||||
|
||||
A: This infrastructure calls the error callback functions of the
|
||||
driver when an error happens. But if the driver is not aware of
|
||||
PCI Express, the device might not report its own errors to root
|
||||
port.
|
||||
|
||||
Q: What modifications will that driver need to make it compatible
|
||||
with the PCI Express AER Root driver?
|
||||
|
||||
A: It could call the helper functions to enable AER in devices and
|
||||
cleanup uncorrectable status register. Pls. refer to section 3.3.
|
||||
|
@ -562,7 +562,8 @@ pcibios_enable_device (struct pci_dev *dev, int mask)
|
||||
void
|
||||
pcibios_disable_device (struct pci_dev *dev)
|
||||
{
|
||||
acpi_pci_irq_disable(dev);
|
||||
if (dev->is_enabled)
|
||||
acpi_pci_irq_disable(dev);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -339,7 +339,7 @@ static void __init mpic_scan_ht_pic(struct mpic *mpic, u8 __iomem *devbase,
|
||||
for (pos = readb(devbase + PCI_CAPABILITY_LIST); pos != 0;
|
||||
pos = readb(devbase + pos + PCI_CAP_LIST_NEXT)) {
|
||||
u8 id = readb(devbase + pos + PCI_CAP_LIST_ID);
|
||||
if (id == PCI_CAP_ID_HT_IRQCONF) {
|
||||
if (id == PCI_CAP_ID_HT) {
|
||||
id = readb(devbase + pos + 3);
|
||||
if (id == 0x80)
|
||||
break;
|
||||
|
@ -742,7 +742,6 @@ static int ipath_setup_ht_reset(struct ipath_devdata *dd)
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define HT_CAPABILITY_ID 0x08 /* HT capabilities not defined in kernel */
|
||||
#define HT_INTR_DISC_CONFIG 0x80 /* HT interrupt and discovery cap */
|
||||
#define HT_INTR_REG_INDEX 2 /* intconfig requires indirect accesses */
|
||||
|
||||
@ -973,7 +972,7 @@ static int ipath_setup_ht_config(struct ipath_devdata *dd,
|
||||
* do this early, before we ever enable errors or hardware errors,
|
||||
* mostly to avoid causing the chip to enter freeze mode.
|
||||
*/
|
||||
pos = pci_find_capability(pdev, HT_CAPABILITY_ID);
|
||||
pos = pci_find_capability(pdev, PCI_CAP_ID_HT);
|
||||
if (!pos) {
|
||||
ipath_dev_err(dd, "Couldn't find HyperTransport "
|
||||
"capability; no interrupts\n");
|
||||
@ -996,7 +995,7 @@ static int ipath_setup_ht_config(struct ipath_devdata *dd,
|
||||
else if (cap_type == HT_INTR_DISC_CONFIG)
|
||||
ihandler = set_int_handler(dd, pdev, pos);
|
||||
} while ((pos = pci_find_next_capability(pdev, pos,
|
||||
HT_CAPABILITY_ID)));
|
||||
PCI_CAP_ID_HT)));
|
||||
|
||||
if (!ihandler) {
|
||||
ipath_dev_err(dd, "Couldn't find interrupt handler in "
|
||||
|
@ -77,9 +77,12 @@ pci_bus_alloc_resource(struct pci_bus *bus, struct resource *res,
|
||||
* This adds a single pci device to the global
|
||||
* device list and adds sysfs and procfs entries
|
||||
*/
|
||||
void __devinit pci_bus_add_device(struct pci_dev *dev)
|
||||
int __devinit pci_bus_add_device(struct pci_dev *dev)
|
||||
{
|
||||
device_add(&dev->dev);
|
||||
int retval;
|
||||
retval = device_add(&dev->dev);
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
down_write(&pci_bus_sem);
|
||||
list_add_tail(&dev->global_list, &pci_devices);
|
||||
@ -87,6 +90,7 @@ void __devinit pci_bus_add_device(struct pci_dev *dev)
|
||||
|
||||
pci_proc_attach_device(dev);
|
||||
pci_create_sysfs_dev_files(dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -104,6 +108,7 @@ void __devinit pci_bus_add_device(struct pci_dev *dev)
|
||||
void __devinit pci_bus_add_devices(struct pci_bus *bus)
|
||||
{
|
||||
struct pci_dev *dev;
|
||||
int retval;
|
||||
|
||||
list_for_each_entry(dev, &bus->devices, bus_list) {
|
||||
/*
|
||||
@ -112,7 +117,9 @@ void __devinit pci_bus_add_devices(struct pci_bus *bus)
|
||||
*/
|
||||
if (!list_empty(&dev->global_list))
|
||||
continue;
|
||||
pci_bus_add_device(dev);
|
||||
retval = pci_bus_add_device(dev);
|
||||
if (retval)
|
||||
dev_err(&dev->dev, "Error adding device, continuing\n");
|
||||
}
|
||||
|
||||
list_for_each_entry(dev, &bus->devices, bus_list) {
|
||||
@ -129,10 +136,13 @@ void __devinit pci_bus_add_devices(struct pci_bus *bus)
|
||||
list_add_tail(&dev->subordinate->node,
|
||||
&dev->bus->children);
|
||||
up_write(&pci_bus_sem);
|
||||
}
|
||||
}
|
||||
pci_bus_add_devices(dev->subordinate);
|
||||
|
||||
sysfs_create_link(&dev->subordinate->class_dev.kobj, &dev->dev.kobj, "bridge");
|
||||
retval = sysfs_create_link(&dev->subordinate->class_dev.kobj,
|
||||
&dev->dev.kobj, "bridge");
|
||||
if (retval)
|
||||
dev_err(&dev->dev, "Error creating sysfs "
|
||||
"bridge symlink, continuing...\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -150,6 +150,11 @@ struct acpiphp_attention_info
|
||||
struct module *owner;
|
||||
};
|
||||
|
||||
struct acpiphp_ioapic {
|
||||
struct pci_dev *dev;
|
||||
u32 gsi_base;
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
/* PCI bus bridge HID */
|
||||
#define ACPI_PCI_HOST_HID "PNP0A03"
|
||||
|
@ -53,6 +53,8 @@
|
||||
#include "acpiphp.h"
|
||||
|
||||
static LIST_HEAD(bridge_list);
|
||||
static LIST_HEAD(ioapic_list);
|
||||
static DEFINE_SPINLOCK(ioapic_list_lock);
|
||||
|
||||
#define MY_NAME "acpiphp_glue"
|
||||
|
||||
@ -797,6 +799,7 @@ ioapic_add(acpi_handle handle, u32 lvl, void *context, void **rv)
|
||||
struct pci_dev *pdev;
|
||||
u32 gsi_base;
|
||||
u64 phys_addr;
|
||||
struct acpiphp_ioapic *ioapic;
|
||||
|
||||
/* Evaluate _STA if present */
|
||||
status = acpi_evaluate_integer(handle, "_STA", NULL, &sta);
|
||||
@ -811,41 +814,107 @@ ioapic_add(acpi_handle handle, u32 lvl, void *context, void **rv)
|
||||
if (get_gsi_base(handle, &gsi_base))
|
||||
return AE_OK;
|
||||
|
||||
ioapic = kmalloc(sizeof(*ioapic), GFP_KERNEL);
|
||||
if (!ioapic)
|
||||
return AE_NO_MEMORY;
|
||||
|
||||
pdev = get_apic_pci_info(handle);
|
||||
if (!pdev)
|
||||
return AE_OK;
|
||||
goto exit_kfree;
|
||||
|
||||
if (pci_enable_device(pdev)) {
|
||||
pci_dev_put(pdev);
|
||||
return AE_OK;
|
||||
}
|
||||
if (pci_enable_device(pdev))
|
||||
goto exit_pci_dev_put;
|
||||
|
||||
pci_set_master(pdev);
|
||||
|
||||
if (pci_request_region(pdev, 0, "I/O APIC(acpiphp)")) {
|
||||
pci_disable_device(pdev);
|
||||
pci_dev_put(pdev);
|
||||
return AE_OK;
|
||||
}
|
||||
if (pci_request_region(pdev, 0, "I/O APIC(acpiphp)"))
|
||||
goto exit_pci_disable_device;
|
||||
|
||||
phys_addr = pci_resource_start(pdev, 0);
|
||||
if (acpi_register_ioapic(handle, phys_addr, gsi_base)) {
|
||||
pci_release_region(pdev, 0);
|
||||
pci_disable_device(pdev);
|
||||
pci_dev_put(pdev);
|
||||
if (acpi_register_ioapic(handle, phys_addr, gsi_base))
|
||||
goto exit_pci_release_region;
|
||||
|
||||
ioapic->gsi_base = gsi_base;
|
||||
ioapic->dev = pdev;
|
||||
spin_lock(&ioapic_list_lock);
|
||||
list_add_tail(&ioapic->list, &ioapic_list);
|
||||
spin_unlock(&ioapic_list_lock);
|
||||
|
||||
return AE_OK;
|
||||
|
||||
exit_pci_release_region:
|
||||
pci_release_region(pdev, 0);
|
||||
exit_pci_disable_device:
|
||||
pci_disable_device(pdev);
|
||||
exit_pci_dev_put:
|
||||
pci_dev_put(pdev);
|
||||
exit_kfree:
|
||||
kfree(ioapic);
|
||||
|
||||
return AE_OK;
|
||||
}
|
||||
|
||||
static acpi_status
|
||||
ioapic_remove(acpi_handle handle, u32 lvl, void *context, void **rv)
|
||||
{
|
||||
acpi_status status;
|
||||
unsigned long sta;
|
||||
acpi_handle tmp;
|
||||
u32 gsi_base;
|
||||
struct acpiphp_ioapic *pos, *n, *ioapic = NULL;
|
||||
|
||||
/* Evaluate _STA if present */
|
||||
status = acpi_evaluate_integer(handle, "_STA", NULL, &sta);
|
||||
if (ACPI_SUCCESS(status) && sta != ACPI_STA_ALL)
|
||||
return AE_CTRL_DEPTH;
|
||||
|
||||
/* Scan only PCI bus scope */
|
||||
status = acpi_get_handle(handle, "_HID", &tmp);
|
||||
if (ACPI_SUCCESS(status))
|
||||
return AE_CTRL_DEPTH;
|
||||
|
||||
if (get_gsi_base(handle, &gsi_base))
|
||||
return AE_OK;
|
||||
|
||||
acpi_unregister_ioapic(handle, gsi_base);
|
||||
|
||||
spin_lock(&ioapic_list_lock);
|
||||
list_for_each_entry_safe(pos, n, &ioapic_list, list) {
|
||||
if (pos->gsi_base != gsi_base)
|
||||
continue;
|
||||
ioapic = pos;
|
||||
list_del(&ioapic->list);
|
||||
break;
|
||||
}
|
||||
spin_unlock(&ioapic_list_lock);
|
||||
|
||||
if (!ioapic)
|
||||
return AE_OK;
|
||||
|
||||
pci_release_region(ioapic->dev, 0);
|
||||
pci_disable_device(ioapic->dev);
|
||||
pci_dev_put(ioapic->dev);
|
||||
kfree(ioapic);
|
||||
|
||||
return AE_OK;
|
||||
}
|
||||
|
||||
static int acpiphp_configure_ioapics(acpi_handle handle)
|
||||
{
|
||||
ioapic_add(handle, 0, NULL, NULL);
|
||||
acpi_walk_namespace(ACPI_TYPE_DEVICE, handle,
|
||||
ACPI_UINT32_MAX, ioapic_add, NULL, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int acpiphp_unconfigure_ioapics(acpi_handle handle)
|
||||
{
|
||||
ioapic_remove(handle, 0, NULL, NULL);
|
||||
acpi_walk_namespace(ACPI_TYPE_DEVICE, handle,
|
||||
ACPI_UINT32_MAX, ioapic_remove, NULL, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int power_on_slot(struct acpiphp_slot *slot)
|
||||
{
|
||||
acpi_status status;
|
||||
@ -997,7 +1066,7 @@ acpiphp_bus_add_out:
|
||||
* @handle: handle to acpi namespace
|
||||
*
|
||||
*/
|
||||
int acpiphp_bus_trim(acpi_handle handle)
|
||||
static int acpiphp_bus_trim(acpi_handle handle)
|
||||
{
|
||||
struct acpi_device *device;
|
||||
int retval;
|
||||
@ -1074,10 +1143,11 @@ static int enable_device(struct acpiphp_slot *slot)
|
||||
|
||||
pci_bus_assign_resources(bus);
|
||||
acpiphp_sanitize_bus(bus);
|
||||
acpiphp_set_hpp_values(slot->bridge->handle, bus);
|
||||
list_for_each_entry(func, &slot->funcs, sibling)
|
||||
acpiphp_configure_ioapics(func->handle);
|
||||
pci_enable_bridges(bus);
|
||||
pci_bus_add_devices(bus);
|
||||
acpiphp_set_hpp_values(slot->bridge->handle, bus);
|
||||
acpiphp_configure_ioapics(slot->bridge->handle);
|
||||
|
||||
/* associate pci_dev to our representation */
|
||||
list_for_each (l, &slot->funcs) {
|
||||
@ -1103,6 +1173,16 @@ static int enable_device(struct acpiphp_slot *slot)
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void disable_bridges(struct pci_bus *bus)
|
||||
{
|
||||
struct pci_dev *dev;
|
||||
list_for_each_entry(dev, &bus->devices, bus_list) {
|
||||
if (dev->subordinate) {
|
||||
disable_bridges(dev->subordinate);
|
||||
pci_disable_device(dev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* disable_device - disable a slot
|
||||
@ -1127,6 +1207,19 @@ static int disable_device(struct acpiphp_slot *slot)
|
||||
func->bridge = NULL;
|
||||
}
|
||||
|
||||
if (func->pci_dev) {
|
||||
pci_stop_bus_device(func->pci_dev);
|
||||
if (func->pci_dev->subordinate) {
|
||||
disable_bridges(func->pci_dev->subordinate);
|
||||
pci_disable_device(func->pci_dev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
list_for_each (l, &slot->funcs) {
|
||||
func = list_entry(l, struct acpiphp_func, sibling);
|
||||
|
||||
acpiphp_unconfigure_ioapics(func->handle);
|
||||
acpiphp_bus_trim(func->handle);
|
||||
/* try to remove anyway.
|
||||
* acpiphp_bus_add might have been failed */
|
||||
|
@ -176,7 +176,9 @@ static void pci_rescan_slot(struct pci_dev *temp)
|
||||
struct pci_bus *bus = temp->bus;
|
||||
struct pci_dev *dev;
|
||||
int func;
|
||||
int retval;
|
||||
u8 hdr_type;
|
||||
|
||||
if (!pci_read_config_byte(temp, PCI_HEADER_TYPE, &hdr_type)) {
|
||||
temp->hdr_type = hdr_type & 0x7f;
|
||||
if (!pci_find_slot(bus->number, temp->devfn)) {
|
||||
@ -185,8 +187,12 @@ static void pci_rescan_slot(struct pci_dev *temp)
|
||||
dbg("New device on %s function %x:%x\n",
|
||||
bus->name, temp->devfn >> 3,
|
||||
temp->devfn & 7);
|
||||
pci_bus_add_device(dev);
|
||||
add_slot(dev);
|
||||
retval = pci_bus_add_device(dev);
|
||||
if (retval)
|
||||
dev_err(&dev->dev, "error adding "
|
||||
"device, continuing.\n");
|
||||
else
|
||||
add_slot(dev);
|
||||
}
|
||||
}
|
||||
/* multifunction device? */
|
||||
@ -205,8 +211,12 @@ static void pci_rescan_slot(struct pci_dev *temp)
|
||||
dbg("New device on %s function %x:%x\n",
|
||||
bus->name, temp->devfn >> 3,
|
||||
temp->devfn & 7);
|
||||
pci_bus_add_device(dev);
|
||||
add_slot(dev);
|
||||
retval = pci_bus_add_device(dev);
|
||||
if (retval)
|
||||
dev_err(&dev->dev, "error adding "
|
||||
"device, continuing.\n");
|
||||
else
|
||||
add_slot(dev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -172,8 +172,8 @@ struct hotplug_slot {
|
||||
|
||||
extern int pci_hp_register (struct hotplug_slot *slot);
|
||||
extern int pci_hp_deregister (struct hotplug_slot *slot);
|
||||
extern int pci_hp_change_slot_info (struct hotplug_slot *slot,
|
||||
struct hotplug_slot_info *info);
|
||||
extern int __must_check pci_hp_change_slot_info (struct hotplug_slot *slot,
|
||||
struct hotplug_slot_info *info);
|
||||
extern struct subsystem pci_hotplug_slots_subsys;
|
||||
|
||||
/* PCI Setting Record (Type 0) */
|
||||
|
@ -482,31 +482,95 @@ static int has_test_file (struct hotplug_slot *slot)
|
||||
|
||||
static int fs_add_slot (struct hotplug_slot *slot)
|
||||
{
|
||||
if (has_power_file(slot) == 0)
|
||||
sysfs_create_file(&slot->kobj, &hotplug_slot_attr_power.attr);
|
||||
int retval = 0;
|
||||
|
||||
if (has_attention_file(slot) == 0)
|
||||
sysfs_create_file(&slot->kobj, &hotplug_slot_attr_attention.attr);
|
||||
if (has_power_file(slot) == 0) {
|
||||
retval = sysfs_create_file(&slot->kobj, &hotplug_slot_attr_power.attr);
|
||||
if (retval)
|
||||
goto exit_power;
|
||||
}
|
||||
|
||||
if (has_latch_file(slot) == 0)
|
||||
sysfs_create_file(&slot->kobj, &hotplug_slot_attr_latch.attr);
|
||||
if (has_attention_file(slot) == 0) {
|
||||
retval = sysfs_create_file(&slot->kobj,
|
||||
&hotplug_slot_attr_attention.attr);
|
||||
if (retval)
|
||||
goto exit_attention;
|
||||
}
|
||||
|
||||
if (has_adapter_file(slot) == 0)
|
||||
sysfs_create_file(&slot->kobj, &hotplug_slot_attr_presence.attr);
|
||||
if (has_latch_file(slot) == 0) {
|
||||
retval = sysfs_create_file(&slot->kobj,
|
||||
&hotplug_slot_attr_latch.attr);
|
||||
if (retval)
|
||||
goto exit_latch;
|
||||
}
|
||||
|
||||
if (has_address_file(slot) == 0)
|
||||
sysfs_create_file(&slot->kobj, &hotplug_slot_attr_address.attr);
|
||||
if (has_adapter_file(slot) == 0) {
|
||||
retval = sysfs_create_file(&slot->kobj,
|
||||
&hotplug_slot_attr_presence.attr);
|
||||
if (retval)
|
||||
goto exit_adapter;
|
||||
}
|
||||
|
||||
if (has_max_bus_speed_file(slot) == 0)
|
||||
sysfs_create_file(&slot->kobj, &hotplug_slot_attr_max_bus_speed.attr);
|
||||
if (has_address_file(slot) == 0) {
|
||||
retval = sysfs_create_file(&slot->kobj,
|
||||
&hotplug_slot_attr_address.attr);
|
||||
if (retval)
|
||||
goto exit_address;
|
||||
}
|
||||
|
||||
if (has_max_bus_speed_file(slot) == 0) {
|
||||
retval = sysfs_create_file(&slot->kobj,
|
||||
&hotplug_slot_attr_max_bus_speed.attr);
|
||||
if (retval)
|
||||
goto exit_max_speed;
|
||||
}
|
||||
|
||||
if (has_cur_bus_speed_file(slot) == 0) {
|
||||
retval = sysfs_create_file(&slot->kobj,
|
||||
&hotplug_slot_attr_cur_bus_speed.attr);
|
||||
if (retval)
|
||||
goto exit_cur_speed;
|
||||
}
|
||||
|
||||
if (has_test_file(slot) == 0) {
|
||||
retval = sysfs_create_file(&slot->kobj,
|
||||
&hotplug_slot_attr_test.attr);
|
||||
if (retval)
|
||||
goto exit_test;
|
||||
}
|
||||
|
||||
goto exit;
|
||||
|
||||
exit_test:
|
||||
if (has_cur_bus_speed_file(slot) == 0)
|
||||
sysfs_create_file(&slot->kobj, &hotplug_slot_attr_cur_bus_speed.attr);
|
||||
sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_cur_bus_speed.attr);
|
||||
|
||||
if (has_test_file(slot) == 0)
|
||||
sysfs_create_file(&slot->kobj, &hotplug_slot_attr_test.attr);
|
||||
exit_cur_speed:
|
||||
if (has_max_bus_speed_file(slot) == 0)
|
||||
sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_max_bus_speed.attr);
|
||||
|
||||
return 0;
|
||||
exit_max_speed:
|
||||
if (has_address_file(slot) == 0)
|
||||
sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_address.attr);
|
||||
|
||||
exit_address:
|
||||
if (has_adapter_file(slot) == 0)
|
||||
sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_presence.attr);
|
||||
|
||||
exit_adapter:
|
||||
if (has_latch_file(slot) == 0)
|
||||
sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_latch.attr);
|
||||
|
||||
exit_latch:
|
||||
if (has_attention_file(slot) == 0)
|
||||
sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_attention.attr);
|
||||
|
||||
exit_attention:
|
||||
if (has_power_file(slot) == 0)
|
||||
sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_power.attr);
|
||||
exit_power:
|
||||
exit:
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void fs_remove_slot (struct hotplug_slot *slot)
|
||||
@ -626,8 +690,11 @@ int pci_hp_deregister (struct hotplug_slot *slot)
|
||||
*
|
||||
* Returns 0 if successful, anything else for an error.
|
||||
*/
|
||||
int pci_hp_change_slot_info (struct hotplug_slot *slot, struct hotplug_slot_info *info)
|
||||
int __must_check pci_hp_change_slot_info(struct hotplug_slot *slot,
|
||||
struct hotplug_slot_info *info)
|
||||
{
|
||||
int retval;
|
||||
|
||||
if ((slot == NULL) || (info == NULL))
|
||||
return -ENODEV;
|
||||
|
||||
@ -636,32 +703,60 @@ int pci_hp_change_slot_info (struct hotplug_slot *slot, struct hotplug_slot_info
|
||||
* for the files referring to the fields that have now changed.
|
||||
*/
|
||||
if ((has_power_file(slot) == 0) &&
|
||||
(slot->info->power_status != info->power_status))
|
||||
sysfs_update_file(&slot->kobj, &hotplug_slot_attr_power.attr);
|
||||
(slot->info->power_status != info->power_status)) {
|
||||
retval = sysfs_update_file(&slot->kobj,
|
||||
&hotplug_slot_attr_power.attr);
|
||||
if (retval)
|
||||
return retval;
|
||||
}
|
||||
|
||||
if ((has_attention_file(slot) == 0) &&
|
||||
(slot->info->attention_status != info->attention_status))
|
||||
sysfs_update_file(&slot->kobj, &hotplug_slot_attr_attention.attr);
|
||||
(slot->info->attention_status != info->attention_status)) {
|
||||
retval = sysfs_update_file(&slot->kobj,
|
||||
&hotplug_slot_attr_attention.attr);
|
||||
if (retval)
|
||||
return retval;
|
||||
}
|
||||
|
||||
if ((has_latch_file(slot) == 0) &&
|
||||
(slot->info->latch_status != info->latch_status))
|
||||
sysfs_update_file(&slot->kobj, &hotplug_slot_attr_latch.attr);
|
||||
(slot->info->latch_status != info->latch_status)) {
|
||||
retval = sysfs_update_file(&slot->kobj,
|
||||
&hotplug_slot_attr_latch.attr);
|
||||
if (retval)
|
||||
return retval;
|
||||
}
|
||||
|
||||
if ((has_adapter_file(slot) == 0) &&
|
||||
(slot->info->adapter_status != info->adapter_status))
|
||||
sysfs_update_file(&slot->kobj, &hotplug_slot_attr_presence.attr);
|
||||
(slot->info->adapter_status != info->adapter_status)) {
|
||||
retval = sysfs_update_file(&slot->kobj,
|
||||
&hotplug_slot_attr_presence.attr);
|
||||
if (retval)
|
||||
return retval;
|
||||
}
|
||||
|
||||
if ((has_address_file(slot) == 0) &&
|
||||
(slot->info->address != info->address))
|
||||
sysfs_update_file(&slot->kobj, &hotplug_slot_attr_address.attr);
|
||||
(slot->info->address != info->address)) {
|
||||
retval = sysfs_update_file(&slot->kobj,
|
||||
&hotplug_slot_attr_address.attr);
|
||||
if (retval)
|
||||
return retval;
|
||||
}
|
||||
|
||||
if ((has_max_bus_speed_file(slot) == 0) &&
|
||||
(slot->info->max_bus_speed != info->max_bus_speed))
|
||||
sysfs_update_file(&slot->kobj, &hotplug_slot_attr_max_bus_speed.attr);
|
||||
(slot->info->max_bus_speed != info->max_bus_speed)) {
|
||||
retval = sysfs_update_file(&slot->kobj,
|
||||
&hotplug_slot_attr_max_bus_speed.attr);
|
||||
if (retval)
|
||||
return retval;
|
||||
}
|
||||
|
||||
if ((has_cur_bus_speed_file(slot) == 0) &&
|
||||
(slot->info->cur_bus_speed != info->cur_bus_speed))
|
||||
sysfs_update_file(&slot->kobj, &hotplug_slot_attr_cur_bus_speed.attr);
|
||||
(slot->info->cur_bus_speed != info->cur_bus_speed)) {
|
||||
retval = sysfs_update_file(&slot->kobj,
|
||||
&hotplug_slot_attr_cur_bus_speed.attr);
|
||||
if (retval)
|
||||
return retval;
|
||||
}
|
||||
|
||||
memcpy (slot->info, info, sizeof (struct hotplug_slot_info));
|
||||
|
||||
|
@ -762,14 +762,14 @@ int pciehp_enable_slot(struct slot *p_slot)
|
||||
if (rc || !getstatus) {
|
||||
info("%s: no adapter on slot(%x)\n", __FUNCTION__, p_slot->number);
|
||||
mutex_unlock(&p_slot->ctrl->crit_sect);
|
||||
return 1;
|
||||
return -ENODEV;
|
||||
}
|
||||
if (MRL_SENS(p_slot->ctrl->ctrlcap)) {
|
||||
rc = p_slot->hpc_ops->get_latch_status(p_slot, &getstatus);
|
||||
if (rc || getstatus) {
|
||||
info("%s: latch open on slot(%x)\n", __FUNCTION__, p_slot->number);
|
||||
mutex_unlock(&p_slot->ctrl->crit_sect);
|
||||
return 1;
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
|
||||
@ -778,7 +778,7 @@ int pciehp_enable_slot(struct slot *p_slot)
|
||||
if (rc || getstatus) {
|
||||
info("%s: already enabled on slot(%x)\n", __FUNCTION__, p_slot->number);
|
||||
mutex_unlock(&p_slot->ctrl->crit_sect);
|
||||
return 1;
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&p_slot->ctrl->crit_sect);
|
||||
@ -813,7 +813,7 @@ int pciehp_disable_slot(struct slot *p_slot)
|
||||
if (ret || !getstatus) {
|
||||
info("%s: no adapter on slot(%x)\n", __FUNCTION__, p_slot->number);
|
||||
mutex_unlock(&p_slot->ctrl->crit_sect);
|
||||
return 1;
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
|
||||
@ -822,7 +822,7 @@ int pciehp_disable_slot(struct slot *p_slot)
|
||||
if (ret || getstatus) {
|
||||
info("%s: latch open on slot(%x)\n", __FUNCTION__, p_slot->number);
|
||||
mutex_unlock(&p_slot->ctrl->crit_sect);
|
||||
return 1;
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
|
||||
@ -831,7 +831,7 @@ int pciehp_disable_slot(struct slot *p_slot)
|
||||
if (ret || !getstatus) {
|
||||
info("%s: already disabled slot(%x)\n", __FUNCTION__, p_slot->number);
|
||||
mutex_unlock(&p_slot->ctrl->crit_sect);
|
||||
return 1;
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* PCI Hot Plug Controller Skeleton Driver - 0.2
|
||||
* PCI Hot Plug Controller Skeleton Driver - 0.3
|
||||
*
|
||||
* Copyright (C) 2001,2003 Greg Kroah-Hartman (greg@kroah.com)
|
||||
* Copyright (C) 2001,2003 IBM Corp.
|
||||
@ -21,7 +21,7 @@
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
* This driver is to be used as a skeleton driver to be show how to interface
|
||||
* This driver is to be used as a skeleton driver to show how to interface
|
||||
* with the pci hotplug core easily.
|
||||
*
|
||||
* Send feedback to <greg@kroah.com>
|
||||
@ -58,8 +58,6 @@ static LIST_HEAD(slot_list);
|
||||
#define info(format, arg...) printk(KERN_INFO "%s: " format "\n", MY_NAME , ## arg)
|
||||
#define warn(format, arg...) printk(KERN_WARNING "%s: " format "\n", MY_NAME , ## arg)
|
||||
|
||||
|
||||
|
||||
/* local variables */
|
||||
static int debug;
|
||||
static int num_slots;
|
||||
@ -109,7 +107,6 @@ static int enable_slot(struct hotplug_slot *hotplug_slot)
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
static int disable_slot(struct hotplug_slot *hotplug_slot)
|
||||
{
|
||||
struct slot *slot = hotplug_slot->private;
|
||||
@ -342,7 +339,7 @@ static int __init pcihp_skel_init(void)
|
||||
info(DRIVER_DESC " version: " DRIVER_VERSION "\n");
|
||||
/*
|
||||
* Do specific initialization stuff for your driver here
|
||||
* Like initializing your controller hardware (if any) and
|
||||
* like initializing your controller hardware (if any) and
|
||||
* determining the number of slots you have in the system
|
||||
* right now.
|
||||
*/
|
||||
|
@ -173,7 +173,7 @@ struct controller {
|
||||
#define msg_button_cancel "PCI slot #%s - action canceled due to button press.\n"
|
||||
|
||||
/* sysfs functions for the hotplug controller info */
|
||||
extern void shpchp_create_ctrl_files (struct controller *ctrl);
|
||||
extern int __must_check shpchp_create_ctrl_files(struct controller *ctrl);
|
||||
|
||||
extern int shpchp_sysfs_enable_slot(struct slot *slot);
|
||||
extern int shpchp_sysfs_disable_slot(struct slot *slot);
|
||||
|
@ -449,10 +449,14 @@ static int shpc_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
|
||||
ctrl->speed = PCI_SPEED_33MHz;
|
||||
}
|
||||
|
||||
shpchp_create_ctrl_files(ctrl);
|
||||
rc = shpchp_create_ctrl_files(ctrl);
|
||||
if (rc)
|
||||
goto err_cleanup_slots;
|
||||
|
||||
return 0;
|
||||
|
||||
err_cleanup_slots:
|
||||
cleanup_slots(ctrl);
|
||||
err_out_release_ctlr:
|
||||
ctrl->hpc_ops->release_ctlr(ctrl);
|
||||
err_out_free_ctrl:
|
||||
|
@ -91,9 +91,9 @@ static ssize_t show_ctrl (struct device *dev, struct device_attribute *attr, cha
|
||||
}
|
||||
static DEVICE_ATTR (ctrl, S_IRUGO, show_ctrl, NULL);
|
||||
|
||||
void shpchp_create_ctrl_files (struct controller *ctrl)
|
||||
int __must_check shpchp_create_ctrl_files (struct controller *ctrl)
|
||||
{
|
||||
device_create_file (&ctrl->pci_dev->dev, &dev_attr_ctrl);
|
||||
return device_create_file (&ctrl->pci_dev->dev, &dev_attr_ctrl);
|
||||
}
|
||||
|
||||
void shpchp_remove_ctrl_files(struct controller *ctrl)
|
||||
|
@ -900,6 +900,33 @@ static int msix_capability_init(struct pci_dev *dev,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* pci_msi_supported - check whether MSI may be enabled on device
|
||||
* @dev: pointer to the pci_dev data structure of MSI device function
|
||||
*
|
||||
* MSI must be globally enabled and supported by the device and its root
|
||||
* bus. But, the root bus is not easy to find since some architectures
|
||||
* have virtual busses on top of the PCI hierarchy (for instance the
|
||||
* hypertransport bus), while the actual bus where MSI must be supported
|
||||
* is below. So we test the MSI flag on all parent busses and assume
|
||||
* that no quirk will ever set the NO_MSI flag on a non-root bus.
|
||||
**/
|
||||
static
|
||||
int pci_msi_supported(struct pci_dev * dev)
|
||||
{
|
||||
struct pci_bus *bus;
|
||||
|
||||
if (!pci_msi_enable || !dev || dev->no_msi)
|
||||
return -EINVAL;
|
||||
|
||||
/* check MSI flags of all parent busses */
|
||||
for (bus = dev->bus; bus; bus = bus->parent)
|
||||
if (bus->bus_flags & PCI_BUS_FLAGS_NO_MSI)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* pci_enable_msi - configure device's MSI capability structure
|
||||
* @dev: pointer to the pci_dev data structure of MSI device function
|
||||
@ -912,19 +939,11 @@ static int msix_capability_init(struct pci_dev *dev,
|
||||
**/
|
||||
int pci_enable_msi(struct pci_dev* dev)
|
||||
{
|
||||
struct pci_bus *bus;
|
||||
int pos, temp, status = -EINVAL;
|
||||
int pos, temp, status;
|
||||
u16 control;
|
||||
|
||||
if (!pci_msi_enable || !dev)
|
||||
return status;
|
||||
|
||||
if (dev->no_msi)
|
||||
return status;
|
||||
|
||||
for (bus = dev->bus; bus; bus = bus->parent)
|
||||
if (bus->bus_flags & PCI_BUS_FLAGS_NO_MSI)
|
||||
return -EINVAL;
|
||||
if (pci_msi_supported(dev) < 0)
|
||||
return -EINVAL;
|
||||
|
||||
temp = dev->irq;
|
||||
|
||||
@ -1134,22 +1153,14 @@ static int reroute_msix_table(int head, struct msix_entry *entries, int *nvec)
|
||||
**/
|
||||
int pci_enable_msix(struct pci_dev* dev, struct msix_entry *entries, int nvec)
|
||||
{
|
||||
struct pci_bus *bus;
|
||||
int status, pos, nr_entries, free_vectors;
|
||||
int i, j, temp;
|
||||
u16 control;
|
||||
unsigned long flags;
|
||||
|
||||
if (!pci_msi_enable || !dev || !entries)
|
||||
if (!entries || pci_msi_supported(dev) < 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (dev->no_msi)
|
||||
return -EINVAL;
|
||||
|
||||
for (bus = dev->bus; bus; bus = bus->parent)
|
||||
if (bus->bus_flags & PCI_BUS_FLAGS_NO_MSI)
|
||||
return -EINVAL;
|
||||
|
||||
status = msi_init();
|
||||
if (status < 0)
|
||||
return status;
|
||||
|
@ -56,6 +56,7 @@ store_new_id(struct device_driver *driver, const char *buf, size_t count)
|
||||
subdevice=PCI_ANY_ID, class=0, class_mask=0;
|
||||
unsigned long driver_data=0;
|
||||
int fields=0;
|
||||
int retval = 0;
|
||||
|
||||
fields = sscanf(buf, "%x %x %x %x %x %x %lux",
|
||||
&vendor, &device, &subvendor, &subdevice,
|
||||
@ -82,10 +83,12 @@ store_new_id(struct device_driver *driver, const char *buf, size_t count)
|
||||
spin_unlock(&pdrv->dynids.lock);
|
||||
|
||||
if (get_driver(&pdrv->driver)) {
|
||||
driver_attach(&pdrv->driver);
|
||||
retval = driver_attach(&pdrv->driver);
|
||||
put_driver(&pdrv->driver);
|
||||
}
|
||||
|
||||
if (retval)
|
||||
return retval;
|
||||
return count;
|
||||
}
|
||||
static DRIVER_ATTR(new_id, S_IWUSR, NULL, store_new_id);
|
||||
@ -418,7 +421,11 @@ int __pci_register_driver(struct pci_driver *drv, struct module *owner)
|
||||
drv->driver.bus = &pci_bus_type;
|
||||
drv->driver.owner = owner;
|
||||
drv->driver.kobj.ktype = &pci_driver_kobj_type;
|
||||
drv->driver.multithread_probe = pci_multithread_probe;
|
||||
|
||||
if (pci_multithread_probe)
|
||||
drv->driver.multithread_probe = pci_multithread_probe;
|
||||
else
|
||||
drv->driver.multithread_probe = drv->multithread_probe;
|
||||
|
||||
spin_lock_init(&drv->dynids.lock);
|
||||
INIT_LIST_HEAD(&drv->dynids.list);
|
||||
|
@ -117,6 +117,7 @@ is_enabled_store(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(dev);
|
||||
int retval = 0;
|
||||
|
||||
/* this can crash the machine when done on the "wrong" device */
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
@ -126,11 +127,53 @@ is_enabled_store(struct device *dev, struct device_attribute *attr,
|
||||
pci_disable_device(pdev);
|
||||
|
||||
if (*buf == '1')
|
||||
pci_enable_device(pdev);
|
||||
retval = pci_enable_device(pdev);
|
||||
|
||||
if (retval)
|
||||
return retval;
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
msi_bus_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(dev);
|
||||
|
||||
if (!pdev->subordinate)
|
||||
return 0;
|
||||
|
||||
return sprintf (buf, "%u\n",
|
||||
!(pdev->subordinate->bus_flags & PCI_BUS_FLAGS_NO_MSI));
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
msi_bus_store(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(dev);
|
||||
|
||||
/* bad things may happen if the no_msi flag is changed
|
||||
* while some drivers are loaded */
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return count;
|
||||
|
||||
if (!pdev->subordinate)
|
||||
return count;
|
||||
|
||||
if (*buf == '0') {
|
||||
pdev->subordinate->bus_flags |= PCI_BUS_FLAGS_NO_MSI;
|
||||
dev_warn(&pdev->dev, "forced subordinate bus to not support MSI,"
|
||||
" bad things could happen.\n");
|
||||
}
|
||||
|
||||
if (*buf == '1') {
|
||||
pdev->subordinate->bus_flags &= ~PCI_BUS_FLAGS_NO_MSI;
|
||||
dev_warn(&pdev->dev, "forced subordinate bus to support MSI,"
|
||||
" bad things could happen.\n");
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
struct device_attribute pci_dev_attrs[] = {
|
||||
__ATTR_RO(resource),
|
||||
@ -145,6 +188,7 @@ struct device_attribute pci_dev_attrs[] = {
|
||||
__ATTR(enable, 0600, is_enabled_show, is_enabled_store),
|
||||
__ATTR(broken_parity_status,(S_IRUGO|S_IWUSR),
|
||||
broken_parity_status_show,broken_parity_status_store),
|
||||
__ATTR(msi_bus, 0644, msi_bus_show, msi_bus_store),
|
||||
__ATTR_NULL,
|
||||
};
|
||||
|
||||
@ -384,16 +428,39 @@ pci_mmap_resource(struct kobject *kobj, struct bin_attribute *attr,
|
||||
return pci_mmap_page_range(pdev, vma, mmap_type, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* pci_remove_resource_files - cleanup resource files
|
||||
* @dev: dev to cleanup
|
||||
*
|
||||
* If we created resource files for @dev, remove them from sysfs and
|
||||
* free their resources.
|
||||
*/
|
||||
static void
|
||||
pci_remove_resource_files(struct pci_dev *pdev)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < PCI_ROM_RESOURCE; i++) {
|
||||
struct bin_attribute *res_attr;
|
||||
|
||||
res_attr = pdev->res_attr[i];
|
||||
if (res_attr) {
|
||||
sysfs_remove_bin_file(&pdev->dev.kobj, res_attr);
|
||||
kfree(res_attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* pci_create_resource_files - create resource files in sysfs for @dev
|
||||
* @dev: dev in question
|
||||
*
|
||||
* Walk the resources in @dev creating files for each resource available.
|
||||
*/
|
||||
static void
|
||||
pci_create_resource_files(struct pci_dev *pdev)
|
||||
static int pci_create_resource_files(struct pci_dev *pdev)
|
||||
{
|
||||
int i;
|
||||
int retval;
|
||||
|
||||
/* Expose the PCI resources from this device as files */
|
||||
for (i = 0; i < PCI_ROM_RESOURCE; i++) {
|
||||
@ -416,35 +483,19 @@ pci_create_resource_files(struct pci_dev *pdev)
|
||||
res_attr->size = pci_resource_len(pdev, i);
|
||||
res_attr->mmap = pci_mmap_resource;
|
||||
res_attr->private = &pdev->resource[i];
|
||||
sysfs_create_bin_file(&pdev->dev.kobj, res_attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* pci_remove_resource_files - cleanup resource files
|
||||
* @dev: dev to cleanup
|
||||
*
|
||||
* If we created resource files for @dev, remove them from sysfs and
|
||||
* free their resources.
|
||||
*/
|
||||
static void
|
||||
pci_remove_resource_files(struct pci_dev *pdev)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < PCI_ROM_RESOURCE; i++) {
|
||||
struct bin_attribute *res_attr;
|
||||
|
||||
res_attr = pdev->res_attr[i];
|
||||
if (res_attr) {
|
||||
sysfs_remove_bin_file(&pdev->dev.kobj, res_attr);
|
||||
kfree(res_attr);
|
||||
retval = sysfs_create_bin_file(&pdev->dev.kobj, res_attr);
|
||||
if (retval) {
|
||||
pci_remove_resource_files(pdev);
|
||||
return retval;
|
||||
}
|
||||
} else {
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#else /* !HAVE_PCI_MMAP */
|
||||
static inline void pci_create_resource_files(struct pci_dev *dev) { return; }
|
||||
static inline int pci_create_resource_files(struct pci_dev *dev) { return 0; }
|
||||
static inline void pci_remove_resource_files(struct pci_dev *dev) { return; }
|
||||
#endif /* HAVE_PCI_MMAP */
|
||||
|
||||
@ -529,22 +580,27 @@ static struct bin_attribute pcie_config_attr = {
|
||||
.write = pci_write_config,
|
||||
};
|
||||
|
||||
int pci_create_sysfs_dev_files (struct pci_dev *pdev)
|
||||
int __must_check pci_create_sysfs_dev_files (struct pci_dev *pdev)
|
||||
{
|
||||
struct bin_attribute *rom_attr = NULL;
|
||||
int retval;
|
||||
|
||||
if (!sysfs_initialized)
|
||||
return -EACCES;
|
||||
|
||||
if (pdev->cfg_size < 4096)
|
||||
sysfs_create_bin_file(&pdev->dev.kobj, &pci_config_attr);
|
||||
retval = sysfs_create_bin_file(&pdev->dev.kobj, &pci_config_attr);
|
||||
else
|
||||
sysfs_create_bin_file(&pdev->dev.kobj, &pcie_config_attr);
|
||||
retval = sysfs_create_bin_file(&pdev->dev.kobj, &pcie_config_attr);
|
||||
if (retval)
|
||||
goto err;
|
||||
|
||||
pci_create_resource_files(pdev);
|
||||
retval = pci_create_resource_files(pdev);
|
||||
if (retval)
|
||||
goto err_bin_file;
|
||||
|
||||
/* If the device has a ROM, try to expose it in sysfs. */
|
||||
if (pci_resource_len(pdev, PCI_ROM_RESOURCE)) {
|
||||
struct bin_attribute *rom_attr;
|
||||
|
||||
rom_attr = kzalloc(sizeof(*rom_attr), GFP_ATOMIC);
|
||||
if (rom_attr) {
|
||||
pdev->rom_attr = rom_attr;
|
||||
@ -554,13 +610,28 @@ int pci_create_sysfs_dev_files (struct pci_dev *pdev)
|
||||
rom_attr->attr.owner = THIS_MODULE;
|
||||
rom_attr->read = pci_read_rom;
|
||||
rom_attr->write = pci_write_rom;
|
||||
sysfs_create_bin_file(&pdev->dev.kobj, rom_attr);
|
||||
retval = sysfs_create_bin_file(&pdev->dev.kobj, rom_attr);
|
||||
if (retval)
|
||||
goto err_rom;
|
||||
} else {
|
||||
retval = -ENOMEM;
|
||||
goto err_bin_file;
|
||||
}
|
||||
}
|
||||
/* add platform-specific attributes */
|
||||
pcibios_add_platform_entries(pdev);
|
||||
|
||||
|
||||
return 0;
|
||||
|
||||
err_rom:
|
||||
kfree(rom_attr);
|
||||
err_bin_file:
|
||||
if (pdev->cfg_size < 4096)
|
||||
sysfs_remove_bin_file(&pdev->dev.kobj, &pci_config_attr);
|
||||
else
|
||||
sysfs_remove_bin_file(&pdev->dev.kobj, &pcie_config_attr);
|
||||
err:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -589,10 +660,14 @@ void pci_remove_sysfs_dev_files(struct pci_dev *pdev)
|
||||
static int __init pci_sysfs_init(void)
|
||||
{
|
||||
struct pci_dev *pdev = NULL;
|
||||
|
||||
int retval;
|
||||
|
||||
sysfs_initialized = 1;
|
||||
for_each_pci_dev(pdev)
|
||||
pci_create_sysfs_dev_files(pdev);
|
||||
for_each_pci_dev(pdev) {
|
||||
retval = pci_create_sysfs_dev_files(pdev);
|
||||
if (retval)
|
||||
return retval;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -445,6 +445,51 @@ pci_power_t pci_choose_state(struct pci_dev *dev, pm_message_t state)
|
||||
|
||||
EXPORT_SYMBOL(pci_choose_state);
|
||||
|
||||
static int pci_save_pcie_state(struct pci_dev *dev)
|
||||
{
|
||||
int pos, i = 0;
|
||||
struct pci_cap_saved_state *save_state;
|
||||
u16 *cap;
|
||||
|
||||
pos = pci_find_capability(dev, PCI_CAP_ID_EXP);
|
||||
if (pos <= 0)
|
||||
return 0;
|
||||
|
||||
save_state = kzalloc(sizeof(*save_state) + sizeof(u16) * 4, GFP_KERNEL);
|
||||
if (!save_state) {
|
||||
dev_err(&dev->dev, "Out of memory in pci_save_pcie_state\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
cap = (u16 *)&save_state->data[0];
|
||||
|
||||
pci_read_config_word(dev, pos + PCI_EXP_DEVCTL, &cap[i++]);
|
||||
pci_read_config_word(dev, pos + PCI_EXP_LNKCTL, &cap[i++]);
|
||||
pci_read_config_word(dev, pos + PCI_EXP_SLTCTL, &cap[i++]);
|
||||
pci_read_config_word(dev, pos + PCI_EXP_RTCTL, &cap[i++]);
|
||||
pci_add_saved_cap(dev, save_state);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void pci_restore_pcie_state(struct pci_dev *dev)
|
||||
{
|
||||
int i = 0, pos;
|
||||
struct pci_cap_saved_state *save_state;
|
||||
u16 *cap;
|
||||
|
||||
save_state = pci_find_saved_cap(dev, PCI_CAP_ID_EXP);
|
||||
pos = pci_find_capability(dev, PCI_CAP_ID_EXP);
|
||||
if (!save_state || pos <= 0)
|
||||
return;
|
||||
cap = (u16 *)&save_state->data[0];
|
||||
|
||||
pci_write_config_word(dev, pos + PCI_EXP_DEVCTL, cap[i++]);
|
||||
pci_write_config_word(dev, pos + PCI_EXP_LNKCTL, cap[i++]);
|
||||
pci_write_config_word(dev, pos + PCI_EXP_SLTCTL, cap[i++]);
|
||||
pci_write_config_word(dev, pos + PCI_EXP_RTCTL, cap[i++]);
|
||||
pci_remove_saved_cap(save_state);
|
||||
kfree(save_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* pci_save_state - save the PCI configuration space of a device before suspending
|
||||
* @dev: - PCI device that we're dealing with
|
||||
@ -460,6 +505,8 @@ pci_save_state(struct pci_dev *dev)
|
||||
return i;
|
||||
if ((i = pci_save_msix_state(dev)) != 0)
|
||||
return i;
|
||||
if ((i = pci_save_pcie_state(dev)) != 0)
|
||||
return i;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -473,6 +520,9 @@ pci_restore_state(struct pci_dev *dev)
|
||||
int i;
|
||||
int val;
|
||||
|
||||
/* PCI Express register must be restored first */
|
||||
pci_restore_pcie_state(dev);
|
||||
|
||||
/*
|
||||
* The Base Address register should be programmed before the command
|
||||
* register(s)
|
||||
|
@ -42,7 +42,7 @@ extern void pci_remove_legacy_files(struct pci_bus *bus);
|
||||
/* Lock for read/write access to pci device and bus lists */
|
||||
extern struct rw_semaphore pci_bus_sem;
|
||||
|
||||
#ifdef CONFIG_X86_IO_APIC
|
||||
#ifdef CONFIG_PCI_MSI
|
||||
extern int pci_msi_quirk;
|
||||
#else
|
||||
#define pci_msi_quirk 0
|
||||
|
@ -34,3 +34,4 @@ config HOTPLUG_PCI_PCIE_POLL_EVENT_MODE
|
||||
|
||||
When in doubt, say N.
|
||||
|
||||
source "drivers/pci/pcie/aer/Kconfig"
|
||||
|
@ -5,3 +5,6 @@
|
||||
pcieportdrv-y := portdrv_core.o portdrv_pci.o portdrv_bus.o
|
||||
|
||||
obj-$(CONFIG_PCIEPORTBUS) += pcieportdrv.o
|
||||
|
||||
# Build PCI Express AER if needed
|
||||
obj-$(CONFIG_PCIEAER) += aer/
|
||||
|
12
drivers/pci/pcie/aer/Kconfig
Normal file
12
drivers/pci/pcie/aer/Kconfig
Normal file
@ -0,0 +1,12 @@
|
||||
#
|
||||
# PCI Express Root Port Device AER Configuration
|
||||
#
|
||||
|
||||
config PCIEAER
|
||||
boolean "Root Port Advanced Error Reporting support"
|
||||
depends on PCIEPORTBUS && ACPI
|
||||
default y
|
||||
help
|
||||
This enables PCI Express Root Port Advanced Error Reporting
|
||||
(AER) driver support. Error reporting messages sent to Root
|
||||
Port will be handled by PCI Express AER driver.
|
8
drivers/pci/pcie/aer/Makefile
Normal file
8
drivers/pci/pcie/aer/Makefile
Normal file
@ -0,0 +1,8 @@
|
||||
#
|
||||
# Makefile for PCI-Express Root Port Advanced Error Reporting Driver
|
||||
#
|
||||
|
||||
obj-$(CONFIG_PCIEAER) += aerdriver.o
|
||||
|
||||
aerdriver-objs := aerdrv_errprint.o aerdrv_core.o aerdrv.o aerdrv_acpi.o
|
||||
|
346
drivers/pci/pcie/aer/aerdrv.c
Normal file
346
drivers/pci/pcie/aer/aerdrv.c
Normal file
@ -0,0 +1,346 @@
|
||||
/*
|
||||
* drivers/pci/pcie/aer/aerdrv.c
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU General Public
|
||||
* License. See the file "COPYING" in the main directory of this archive
|
||||
* for more details.
|
||||
*
|
||||
* This file implements the AER root port service driver. The driver will
|
||||
* register an irq handler. When root port triggers an AER interrupt, the irq
|
||||
* handler will collect root port status and schedule a work.
|
||||
*
|
||||
* Copyright (C) 2006 Intel Corp.
|
||||
* Tom Long Nguyen (tom.l.nguyen@intel.com)
|
||||
* Zhang Yanmin (yanmin.zhang@intel.com)
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/pcieport_if.h>
|
||||
|
||||
#include "aerdrv.h"
|
||||
|
||||
/*
|
||||
* Version Information
|
||||
*/
|
||||
#define DRIVER_VERSION "v1.0"
|
||||
#define DRIVER_AUTHOR "tom.l.nguyen@intel.com"
|
||||
#define DRIVER_DESC "Root Port Advanced Error Reporting Driver"
|
||||
MODULE_AUTHOR(DRIVER_AUTHOR);
|
||||
MODULE_DESCRIPTION(DRIVER_DESC);
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
static int __devinit aer_probe (struct pcie_device *dev,
|
||||
const struct pcie_port_service_id *id );
|
||||
static void aer_remove(struct pcie_device *dev);
|
||||
static int aer_suspend(struct pcie_device *dev, pm_message_t state)
|
||||
{return 0;}
|
||||
static int aer_resume(struct pcie_device *dev) {return 0;}
|
||||
static pci_ers_result_t aer_error_detected(struct pci_dev *dev,
|
||||
enum pci_channel_state error);
|
||||
static void aer_error_resume(struct pci_dev *dev);
|
||||
static pci_ers_result_t aer_root_reset(struct pci_dev *dev);
|
||||
|
||||
/*
|
||||
* PCI Express bus's AER Root service driver data structure
|
||||
*/
|
||||
static struct pcie_port_service_id aer_id[] = {
|
||||
{
|
||||
.vendor = PCI_ANY_ID,
|
||||
.device = PCI_ANY_ID,
|
||||
.port_type = PCIE_RC_PORT,
|
||||
.service_type = PCIE_PORT_SERVICE_AER,
|
||||
},
|
||||
{ /* end: all zeroes */ }
|
||||
};
|
||||
|
||||
static struct pci_error_handlers aer_error_handlers = {
|
||||
.error_detected = aer_error_detected,
|
||||
.resume = aer_error_resume,
|
||||
};
|
||||
|
||||
static struct pcie_port_service_driver aerdrv = {
|
||||
.name = "aer",
|
||||
.id_table = &aer_id[0],
|
||||
|
||||
.probe = aer_probe,
|
||||
.remove = aer_remove,
|
||||
|
||||
.suspend = aer_suspend,
|
||||
.resume = aer_resume,
|
||||
|
||||
.err_handler = &aer_error_handlers,
|
||||
|
||||
.reset_link = aer_root_reset,
|
||||
};
|
||||
|
||||
/**
|
||||
* aer_irq - Root Port's ISR
|
||||
* @irq: IRQ assigned to Root Port
|
||||
* @context: pointer to Root Port data structure
|
||||
* @r: pointer struct pt_regs
|
||||
*
|
||||
* Invoked when Root Port detects AER messages.
|
||||
**/
|
||||
static irqreturn_t aer_irq(int irq, void *context, struct pt_regs * r)
|
||||
{
|
||||
unsigned int status, id;
|
||||
struct pcie_device *pdev = (struct pcie_device *)context;
|
||||
struct aer_rpc *rpc = get_service_data(pdev);
|
||||
int next_prod_idx;
|
||||
unsigned long flags;
|
||||
int pos;
|
||||
|
||||
pos = pci_find_aer_capability(pdev->port);
|
||||
/*
|
||||
* Must lock access to Root Error Status Reg, Root Error ID Reg,
|
||||
* and Root error producer/consumer index
|
||||
*/
|
||||
spin_lock_irqsave(&rpc->e_lock, flags);
|
||||
|
||||
/* Read error status */
|
||||
pci_read_config_dword(pdev->port, pos + PCI_ERR_ROOT_STATUS, &status);
|
||||
if (!(status & ROOT_ERR_STATUS_MASKS)) {
|
||||
spin_unlock_irqrestore(&rpc->e_lock, flags);
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
/* Read error source and clear error status */
|
||||
pci_read_config_dword(pdev->port, pos + PCI_ERR_ROOT_COR_SRC, &id);
|
||||
pci_write_config_dword(pdev->port, pos + PCI_ERR_ROOT_STATUS, status);
|
||||
|
||||
/* Store error source for later DPC handler */
|
||||
next_prod_idx = rpc->prod_idx + 1;
|
||||
if (next_prod_idx == AER_ERROR_SOURCES_MAX)
|
||||
next_prod_idx = 0;
|
||||
if (next_prod_idx == rpc->cons_idx) {
|
||||
/*
|
||||
* Error Storm Condition - possibly the same error occurred.
|
||||
* Drop the error.
|
||||
*/
|
||||
spin_unlock_irqrestore(&rpc->e_lock, flags);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
rpc->e_sources[rpc->prod_idx].status = status;
|
||||
rpc->e_sources[rpc->prod_idx].id = id;
|
||||
rpc->prod_idx = next_prod_idx;
|
||||
spin_unlock_irqrestore(&rpc->e_lock, flags);
|
||||
|
||||
/* Invoke DPC handler */
|
||||
schedule_work(&rpc->dpc_handler);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/**
|
||||
* aer_alloc_rpc - allocate Root Port data structure
|
||||
* @dev: pointer to the pcie_dev data structure
|
||||
*
|
||||
* Invoked when Root Port's AER service is loaded.
|
||||
**/
|
||||
static struct aer_rpc* aer_alloc_rpc(struct pcie_device *dev)
|
||||
{
|
||||
struct aer_rpc *rpc;
|
||||
|
||||
if (!(rpc = (struct aer_rpc *)kmalloc(sizeof(struct aer_rpc),
|
||||
GFP_KERNEL)))
|
||||
return NULL;
|
||||
|
||||
memset(rpc, 0, sizeof(struct aer_rpc));
|
||||
/*
|
||||
* Initialize Root lock access, e_lock, to Root Error Status Reg,
|
||||
* Root Error ID Reg, and Root error producer/consumer index.
|
||||
*/
|
||||
rpc->e_lock = SPIN_LOCK_UNLOCKED;
|
||||
|
||||
rpc->rpd = dev;
|
||||
INIT_WORK(&rpc->dpc_handler, aer_isr, (void *)dev);
|
||||
rpc->prod_idx = rpc->cons_idx = 0;
|
||||
mutex_init(&rpc->rpc_mutex);
|
||||
init_waitqueue_head(&rpc->wait_release);
|
||||
|
||||
/* Use PCIE bus function to store rpc into PCIE device */
|
||||
set_service_data(dev, rpc);
|
||||
|
||||
return rpc;
|
||||
}
|
||||
|
||||
/**
|
||||
* aer_remove - clean up resources
|
||||
* @dev: pointer to the pcie_dev data structure
|
||||
*
|
||||
* Invoked when PCI Express bus unloads or AER probe fails.
|
||||
**/
|
||||
static void aer_remove(struct pcie_device *dev)
|
||||
{
|
||||
struct aer_rpc *rpc = get_service_data(dev);
|
||||
|
||||
if (rpc) {
|
||||
/* If register interrupt service, it must be free. */
|
||||
if (rpc->isr)
|
||||
free_irq(dev->irq, dev);
|
||||
|
||||
wait_event(rpc->wait_release, rpc->prod_idx == rpc->cons_idx);
|
||||
|
||||
aer_delete_rootport(rpc);
|
||||
set_service_data(dev, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* aer_probe - initialize resources
|
||||
* @dev: pointer to the pcie_dev data structure
|
||||
* @id: pointer to the service id data structure
|
||||
*
|
||||
* Invoked when PCI Express bus loads AER service driver.
|
||||
**/
|
||||
static int __devinit aer_probe (struct pcie_device *dev,
|
||||
const struct pcie_port_service_id *id )
|
||||
{
|
||||
int status;
|
||||
struct aer_rpc *rpc;
|
||||
struct device *device = &dev->device;
|
||||
|
||||
/* Init */
|
||||
if ((status = aer_init(dev)))
|
||||
return status;
|
||||
|
||||
/* Alloc rpc data structure */
|
||||
if (!(rpc = aer_alloc_rpc(dev))) {
|
||||
printk(KERN_DEBUG "%s: Alloc rpc fails on PCIE device[%s]\n",
|
||||
__FUNCTION__, device->bus_id);
|
||||
aer_remove(dev);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* Request IRQ ISR */
|
||||
if ((status = request_irq(dev->irq, aer_irq, SA_SHIRQ, "aerdrv",
|
||||
dev))) {
|
||||
printk(KERN_DEBUG "%s: Request ISR fails on PCIE device[%s]\n",
|
||||
__FUNCTION__, device->bus_id);
|
||||
aer_remove(dev);
|
||||
return status;
|
||||
}
|
||||
|
||||
rpc->isr = 1;
|
||||
|
||||
aer_enable_rootport(rpc);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* aer_root_reset - reset link on Root Port
|
||||
* @dev: pointer to Root Port's pci_dev data structure
|
||||
*
|
||||
* Invoked by Port Bus driver when performing link reset at Root Port.
|
||||
**/
|
||||
static pci_ers_result_t aer_root_reset(struct pci_dev *dev)
|
||||
{
|
||||
u16 p2p_ctrl;
|
||||
u32 status;
|
||||
int pos;
|
||||
|
||||
pos = pci_find_aer_capability(dev);
|
||||
|
||||
/* Disable Root's interrupt in response to error messages */
|
||||
pci_write_config_dword(dev, pos + PCI_ERR_ROOT_COMMAND, 0);
|
||||
|
||||
/* Assert Secondary Bus Reset */
|
||||
pci_read_config_word(dev, PCI_BRIDGE_CONTROL, &p2p_ctrl);
|
||||
p2p_ctrl |= PCI_CB_BRIDGE_CTL_CB_RESET;
|
||||
pci_write_config_word(dev, PCI_BRIDGE_CONTROL, p2p_ctrl);
|
||||
|
||||
/* De-assert Secondary Bus Reset */
|
||||
p2p_ctrl &= ~PCI_CB_BRIDGE_CTL_CB_RESET;
|
||||
pci_write_config_word(dev, PCI_BRIDGE_CONTROL, p2p_ctrl);
|
||||
|
||||
/*
|
||||
* System software must wait for at least 100ms from the end
|
||||
* of a reset of one or more device before it is permitted
|
||||
* to issue Configuration Requests to those devices.
|
||||
*/
|
||||
msleep(200);
|
||||
printk(KERN_DEBUG "Complete link reset at Root[%s]\n", dev->dev.bus_id);
|
||||
|
||||
/* Enable Root Port's interrupt in response to error messages */
|
||||
pci_read_config_dword(dev, pos + PCI_ERR_ROOT_STATUS, &status);
|
||||
pci_write_config_dword(dev, pos + PCI_ERR_ROOT_STATUS, status);
|
||||
pci_write_config_dword(dev,
|
||||
pos + PCI_ERR_ROOT_COMMAND,
|
||||
ROOT_PORT_INTR_ON_MESG_MASK);
|
||||
|
||||
return PCI_ERS_RESULT_RECOVERED;
|
||||
}
|
||||
|
||||
/**
|
||||
* aer_error_detected - update severity status
|
||||
* @dev: pointer to Root Port's pci_dev data structure
|
||||
* @error: error severity being notified by port bus
|
||||
*
|
||||
* Invoked by Port Bus driver during error recovery.
|
||||
**/
|
||||
static pci_ers_result_t aer_error_detected(struct pci_dev *dev,
|
||||
enum pci_channel_state error)
|
||||
{
|
||||
/* Root Port has no impact. Always recovers. */
|
||||
return PCI_ERS_RESULT_CAN_RECOVER;
|
||||
}
|
||||
|
||||
/**
|
||||
* aer_error_resume - clean up corresponding error status bits
|
||||
* @dev: pointer to Root Port's pci_dev data structure
|
||||
*
|
||||
* Invoked by Port Bus driver during nonfatal recovery.
|
||||
**/
|
||||
static void aer_error_resume(struct pci_dev *dev)
|
||||
{
|
||||
int pos;
|
||||
u32 status, mask;
|
||||
u16 reg16;
|
||||
|
||||
/* Clean up Root device status */
|
||||
pos = pci_find_capability(dev, PCI_CAP_ID_EXP);
|
||||
pci_read_config_word(dev, pos + PCI_EXP_DEVSTA, ®16);
|
||||
pci_write_config_word(dev, pos + PCI_EXP_DEVSTA, reg16);
|
||||
|
||||
/* Clean AER Root Error Status */
|
||||
pos = pci_find_aer_capability(dev);
|
||||
pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, &status);
|
||||
pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_SEVER, &mask);
|
||||
if (dev->error_state == pci_channel_io_normal)
|
||||
status &= ~mask; /* Clear corresponding nonfatal bits */
|
||||
else
|
||||
status &= mask; /* Clear corresponding fatal bits */
|
||||
pci_write_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, status);
|
||||
}
|
||||
|
||||
/**
|
||||
* aer_service_init - register AER root service driver
|
||||
*
|
||||
* Invoked when AER root service driver is loaded.
|
||||
**/
|
||||
static int __init aer_service_init(void)
|
||||
{
|
||||
return pcie_port_service_register(&aerdrv);
|
||||
}
|
||||
|
||||
/**
|
||||
* aer_service_exit - unregister AER root service driver
|
||||
*
|
||||
* Invoked when AER root service driver is unloaded.
|
||||
**/
|
||||
static void __exit aer_service_exit(void)
|
||||
{
|
||||
pcie_port_service_unregister(&aerdrv);
|
||||
}
|
||||
|
||||
module_init(aer_service_init);
|
||||
module_exit(aer_service_exit);
|
125
drivers/pci/pcie/aer/aerdrv.h
Normal file
125
drivers/pci/pcie/aer/aerdrv.h
Normal file
@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright (C) 2006 Intel Corp.
|
||||
* Tom Long Nguyen (tom.l.nguyen@intel.com)
|
||||
* Zhang Yanmin (yanmin.zhang@intel.com)
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _AERDRV_H_
|
||||
#define _AERDRV_H_
|
||||
|
||||
#include <linux/pcieport_if.h>
|
||||
#include <linux/aer.h>
|
||||
|
||||
#define AER_NONFATAL 0
|
||||
#define AER_FATAL 1
|
||||
#define AER_CORRECTABLE 2
|
||||
#define AER_UNCORRECTABLE 4
|
||||
#define AER_ERROR_MASK 0x001fffff
|
||||
#define AER_ERROR(d) (d & AER_ERROR_MASK)
|
||||
|
||||
#define OSC_METHOD_RUN_SUCCESS 0
|
||||
#define OSC_METHOD_NOT_SUPPORTED 1
|
||||
#define OSC_METHOD_RUN_FAILURE 2
|
||||
|
||||
/* Root Error Status Register Bits */
|
||||
#define ROOT_ERR_STATUS_MASKS 0x0f
|
||||
|
||||
#define SYSTEM_ERROR_INTR_ON_MESG_MASK (PCI_EXP_RTCTL_SECEE| \
|
||||
PCI_EXP_RTCTL_SENFEE| \
|
||||
PCI_EXP_RTCTL_SEFEE)
|
||||
#define ROOT_PORT_INTR_ON_MESG_MASK (PCI_ERR_ROOT_CMD_COR_EN| \
|
||||
PCI_ERR_ROOT_CMD_NONFATAL_EN| \
|
||||
PCI_ERR_ROOT_CMD_FATAL_EN)
|
||||
#define ERR_COR_ID(d) (d & 0xffff)
|
||||
#define ERR_UNCOR_ID(d) (d >> 16)
|
||||
|
||||
#define AER_SUCCESS 0
|
||||
#define AER_UNSUCCESS 1
|
||||
#define AER_ERROR_SOURCES_MAX 100
|
||||
|
||||
#define AER_LOG_TLP_MASKS (PCI_ERR_UNC_POISON_TLP| \
|
||||
PCI_ERR_UNC_ECRC| \
|
||||
PCI_ERR_UNC_UNSUP| \
|
||||
PCI_ERR_UNC_COMP_ABORT| \
|
||||
PCI_ERR_UNC_UNX_COMP| \
|
||||
PCI_ERR_UNC_MALF_TLP)
|
||||
|
||||
/* AER Error Info Flags */
|
||||
#define AER_TLP_HEADER_VALID_FLAG 0x00000001
|
||||
#define AER_MULTI_ERROR_VALID_FLAG 0x00000002
|
||||
|
||||
#define ERR_CORRECTABLE_ERROR_MASK 0x000031c1
|
||||
#define ERR_UNCORRECTABLE_ERROR_MASK 0x001ff010
|
||||
|
||||
struct header_log_regs {
|
||||
unsigned int dw0;
|
||||
unsigned int dw1;
|
||||
unsigned int dw2;
|
||||
unsigned int dw3;
|
||||
};
|
||||
|
||||
struct aer_err_info {
|
||||
int severity; /* 0:NONFATAL | 1:FATAL | 2:COR */
|
||||
int flags;
|
||||
unsigned int status; /* COR/UNCOR Error Status */
|
||||
struct header_log_regs tlp; /* TLP Header */
|
||||
};
|
||||
|
||||
struct aer_err_source {
|
||||
unsigned int status;
|
||||
unsigned int id;
|
||||
};
|
||||
|
||||
struct aer_rpc {
|
||||
struct pcie_device *rpd; /* Root Port device */
|
||||
struct work_struct dpc_handler;
|
||||
struct aer_err_source e_sources[AER_ERROR_SOURCES_MAX];
|
||||
unsigned short prod_idx; /* Error Producer Index */
|
||||
unsigned short cons_idx; /* Error Consumer Index */
|
||||
int isr;
|
||||
spinlock_t e_lock; /*
|
||||
* Lock access to Error Status/ID Regs
|
||||
* and error producer/consumer index
|
||||
*/
|
||||
struct mutex rpc_mutex; /*
|
||||
* only one thread could do
|
||||
* recovery on the same
|
||||
* root port hierachy
|
||||
*/
|
||||
wait_queue_head_t wait_release;
|
||||
};
|
||||
|
||||
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)
|
||||
{
|
||||
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 = new;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return orig;
|
||||
}
|
||||
|
||||
extern struct bus_type pcie_port_bus_type;
|
||||
extern void aer_enable_rootport(struct aer_rpc *rpc);
|
||||
extern void aer_delete_rootport(struct aer_rpc *rpc);
|
||||
extern int aer_init(struct pcie_device *dev);
|
||||
extern void aer_isr(void *context);
|
||||
extern void aer_print_error(struct pci_dev *dev, struct aer_err_info *info);
|
||||
extern int aer_osc_setup(struct pci_dev *dev);
|
||||
|
||||
#endif //_AERDRV_H_
|
68
drivers/pci/pcie/aer/aerdrv_acpi.c
Normal file
68
drivers/pci/pcie/aer/aerdrv_acpi.c
Normal file
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Access ACPI _OSC method
|
||||
*
|
||||
* Copyright (C) 2006 Intel Corp.
|
||||
* Tom Long Nguyen (tom.l.nguyen@intel.com)
|
||||
* Zhang Yanmin (yanmin.zhang@intel.com)
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/suspend.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/pci-acpi.h>
|
||||
#include <linux/delay.h>
|
||||
#include "aerdrv.h"
|
||||
|
||||
/**
|
||||
* aer_osc_setup - run ACPI _OSC method
|
||||
*
|
||||
* Return:
|
||||
* Zero if success. Nonzero for otherwise.
|
||||
*
|
||||
* Invoked when PCIE bus loads AER service driver. To avoid conflict with
|
||||
* BIOS AER support requires BIOS to yield AER control to OS native driver.
|
||||
**/
|
||||
int aer_osc_setup(struct pci_dev *dev)
|
||||
{
|
||||
int retval = OSC_METHOD_RUN_SUCCESS;
|
||||
acpi_status status;
|
||||
acpi_handle handle = DEVICE_ACPI_HANDLE(&dev->dev);
|
||||
struct pci_dev *pdev = dev;
|
||||
struct pci_bus *parent;
|
||||
|
||||
while (!handle) {
|
||||
if (!pdev || !pdev->bus->parent)
|
||||
break;
|
||||
parent = pdev->bus->parent;
|
||||
if (!parent->self)
|
||||
/* Parent must be a host bridge */
|
||||
handle = acpi_get_pci_rootbridge_handle(
|
||||
pci_domain_nr(parent),
|
||||
parent->number);
|
||||
else
|
||||
handle = DEVICE_ACPI_HANDLE(
|
||||
&(parent->self->dev));
|
||||
pdev = parent->self;
|
||||
}
|
||||
|
||||
if (!handle)
|
||||
return OSC_METHOD_NOT_SUPPORTED;
|
||||
|
||||
pci_osc_support_set(OSC_EXT_PCI_CONFIG_SUPPORT);
|
||||
status = pci_osc_control_set(handle, OSC_PCI_EXPRESS_AER_CONTROL |
|
||||
OSC_PCI_EXPRESS_CAP_STRUCTURE_CONTROL);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
if (status == AE_SUPPORT)
|
||||
retval = OSC_METHOD_NOT_SUPPORTED;
|
||||
else
|
||||
retval = OSC_METHOD_RUN_FAILURE;
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
758
drivers/pci/pcie/aer/aerdrv_core.c
Normal file
758
drivers/pci/pcie/aer/aerdrv_core.c
Normal file
@ -0,0 +1,758 @@
|
||||
/*
|
||||
* drivers/pci/pcie/aer/aerdrv_core.c
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU General Public
|
||||
* License. See the file "COPYING" in the main directory of this archive
|
||||
* for more details.
|
||||
*
|
||||
* This file implements the core part of PCI-Express AER. When an pci-express
|
||||
* 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/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/suspend.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/pci-acpi.h>
|
||||
#include <linux/delay.h>
|
||||
#include "aerdrv.h"
|
||||
|
||||
static int forceload;
|
||||
module_param(forceload, bool, 0);
|
||||
|
||||
#define PCI_CFG_SPACE_SIZE (0x100)
|
||||
int pci_find_aer_capability(struct pci_dev *dev)
|
||||
{
|
||||
int pos;
|
||||
u32 reg32 = 0;
|
||||
|
||||
/* Check if it's a pci-express device */
|
||||
pos = pci_find_capability(dev, PCI_CAP_ID_EXP);
|
||||
if (!pos)
|
||||
return 0;
|
||||
|
||||
/* Check if it supports pci-express AER */
|
||||
pos = PCI_CFG_SPACE_SIZE;
|
||||
while (pos) {
|
||||
if (pci_read_config_dword(dev, pos, ®32))
|
||||
return 0;
|
||||
|
||||
/* some broken boards return ~0 */
|
||||
if (reg32 == 0xffffffff)
|
||||
return 0;
|
||||
|
||||
if (PCI_EXT_CAP_ID(reg32) == PCI_EXT_CAP_ID_ERR)
|
||||
break;
|
||||
|
||||
pos = reg32 >> 20;
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
int pci_enable_pcie_error_reporting(struct pci_dev *dev)
|
||||
{
|
||||
u16 reg16 = 0;
|
||||
int pos;
|
||||
|
||||
pos = pci_find_capability(dev, PCI_CAP_ID_EXP);
|
||||
if (!pos)
|
||||
return -EIO;
|
||||
|
||||
pci_read_config_word(dev, pos+PCI_EXP_DEVCTL, ®16);
|
||||
reg16 = reg16 |
|
||||
PCI_EXP_DEVCTL_CERE |
|
||||
PCI_EXP_DEVCTL_NFERE |
|
||||
PCI_EXP_DEVCTL_FERE |
|
||||
PCI_EXP_DEVCTL_URRE;
|
||||
pci_write_config_word(dev, pos+PCI_EXP_DEVCTL,
|
||||
reg16);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pci_disable_pcie_error_reporting(struct pci_dev *dev)
|
||||
{
|
||||
u16 reg16 = 0;
|
||||
int pos;
|
||||
|
||||
pos = pci_find_capability(dev, PCI_CAP_ID_EXP);
|
||||
if (!pos)
|
||||
return -EIO;
|
||||
|
||||
pci_read_config_word(dev, pos+PCI_EXP_DEVCTL, ®16);
|
||||
reg16 = reg16 & ~(PCI_EXP_DEVCTL_CERE |
|
||||
PCI_EXP_DEVCTL_NFERE |
|
||||
PCI_EXP_DEVCTL_FERE |
|
||||
PCI_EXP_DEVCTL_URRE);
|
||||
pci_write_config_word(dev, pos+PCI_EXP_DEVCTL,
|
||||
reg16);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pci_cleanup_aer_uncorrect_error_status(struct pci_dev *dev)
|
||||
{
|
||||
int pos;
|
||||
u32 status, mask;
|
||||
|
||||
pos = pci_find_aer_capability(dev);
|
||||
if (!pos)
|
||||
return -EIO;
|
||||
|
||||
pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, &status);
|
||||
pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_SEVER, &mask);
|
||||
if (dev->error_state == pci_channel_io_normal)
|
||||
status &= ~mask; /* Clear corresponding nonfatal bits */
|
||||
else
|
||||
status &= mask; /* Clear corresponding fatal bits */
|
||||
pci_write_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, status);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int find_device_iter(struct device *device, void *data)
|
||||
{
|
||||
struct pci_dev *dev;
|
||||
u16 id = *(unsigned long *)data;
|
||||
u8 secondary, subordinate, d_bus = id >> 8;
|
||||
|
||||
if (device->bus == &pci_bus_type) {
|
||||
dev = to_pci_dev(device);
|
||||
if (id == ((dev->bus->number << 8) | dev->devfn)) {
|
||||
/*
|
||||
* Device ID match
|
||||
*/
|
||||
*(unsigned long*)data = (unsigned long)device;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* If device is P2P, check if it is an upstream?
|
||||
*/
|
||||
if (dev->hdr_type & PCI_HEADER_TYPE_BRIDGE) {
|
||||
pci_read_config_byte(dev, PCI_SECONDARY_BUS,
|
||||
&secondary);
|
||||
pci_read_config_byte(dev, PCI_SUBORDINATE_BUS,
|
||||
&subordinate);
|
||||
if (d_bus >= secondary && d_bus <= subordinate) {
|
||||
*(unsigned long*)data = (unsigned long)device;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* find_source_device - search through device hierarchy for source device
|
||||
* @p_dev: pointer to Root Port pci_dev data structure
|
||||
* @id: device ID of agent who sends an error message to this Root Port
|
||||
*
|
||||
* Invoked when error is detected at the Root Port.
|
||||
**/
|
||||
static struct device* find_source_device(struct pci_dev *parent, u16 id)
|
||||
{
|
||||
struct pci_dev *dev = parent;
|
||||
struct device *device;
|
||||
unsigned long device_addr;
|
||||
int status;
|
||||
|
||||
/* Is Root Port an agent that sends error message? */
|
||||
if (id == ((dev->bus->number << 8) | dev->devfn))
|
||||
return &dev->dev;
|
||||
|
||||
do {
|
||||
device_addr = id;
|
||||
if ((status = device_for_each_child(&dev->dev,
|
||||
&device_addr, find_device_iter))) {
|
||||
device = (struct device*)device_addr;
|
||||
dev = to_pci_dev(device);
|
||||
if (id == ((dev->bus->number << 8) | dev->devfn))
|
||||
return device;
|
||||
}
|
||||
}while (status);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void report_error_detected(struct pci_dev *dev, void *data)
|
||||
{
|
||||
pci_ers_result_t vote;
|
||||
struct pci_error_handlers *err_handler;
|
||||
struct aer_broadcast_data *result_data;
|
||||
result_data = (struct aer_broadcast_data *) data;
|
||||
|
||||
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.
|
||||
*/
|
||||
printk(KERN_DEBUG "Device ID[%s] has %s\n",
|
||||
dev->dev.bus_id, (dev->driver) ?
|
||||
"no AER-aware driver" : "no driver");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
err_handler = dev->driver->err_handler;
|
||||
vote = err_handler->error_detected(dev, result_data->state);
|
||||
result_data->result = merge_result(result_data->result, vote);
|
||||
return;
|
||||
}
|
||||
|
||||
static void report_mmio_enabled(struct pci_dev *dev, void *data)
|
||||
{
|
||||
pci_ers_result_t vote;
|
||||
struct pci_error_handlers *err_handler;
|
||||
struct aer_broadcast_data *result_data;
|
||||
result_data = (struct aer_broadcast_data *) data;
|
||||
|
||||
if (!dev->driver ||
|
||||
!dev->driver->err_handler ||
|
||||
!dev->driver->err_handler->mmio_enabled)
|
||||
return;
|
||||
|
||||
err_handler = dev->driver->err_handler;
|
||||
vote = err_handler->mmio_enabled(dev);
|
||||
result_data->result = merge_result(result_data->result, vote);
|
||||
return;
|
||||
}
|
||||
|
||||
static void report_slot_reset(struct pci_dev *dev, void *data)
|
||||
{
|
||||
pci_ers_result_t vote;
|
||||
struct pci_error_handlers *err_handler;
|
||||
struct aer_broadcast_data *result_data;
|
||||
result_data = (struct aer_broadcast_data *) data;
|
||||
|
||||
if (!dev->driver ||
|
||||
!dev->driver->err_handler ||
|
||||
!dev->driver->err_handler->slot_reset)
|
||||
return;
|
||||
|
||||
err_handler = dev->driver->err_handler;
|
||||
vote = err_handler->slot_reset(dev);
|
||||
result_data->result = merge_result(result_data->result, vote);
|
||||
return;
|
||||
}
|
||||
|
||||
static void report_resume(struct pci_dev *dev, void *data)
|
||||
{
|
||||
struct pci_error_handlers *err_handler;
|
||||
|
||||
dev->error_state = pci_channel_io_normal;
|
||||
|
||||
if (!dev->driver ||
|
||||
!dev->driver->err_handler ||
|
||||
!dev->driver->err_handler->slot_reset)
|
||||
return;
|
||||
|
||||
err_handler = dev->driver->err_handler;
|
||||
err_handler->resume(dev);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* broadcast_error_message - handle message broadcast to downstream drivers
|
||||
* @device: pointer to from where in a hierarchy message is broadcasted down
|
||||
* @api: callback to be broadcasted
|
||||
* @state: error state
|
||||
*
|
||||
* 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,
|
||||
void (*cb)(struct pci_dev *, void *))
|
||||
{
|
||||
struct aer_broadcast_data result_data;
|
||||
|
||||
printk(KERN_DEBUG "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.
|
||||
*/
|
||||
pci_walk_bus(dev->bus, cb, &result_data);
|
||||
}
|
||||
|
||||
return result_data.result;
|
||||
}
|
||||
|
||||
struct find_aer_service_data {
|
||||
struct pcie_port_service_driver *aer_driver;
|
||||
int is_downstream;
|
||||
};
|
||||
|
||||
static int find_aer_service_iter(struct device *device, void *data)
|
||||
{
|
||||
struct device_driver *driver;
|
||||
struct pcie_port_service_driver *service_driver;
|
||||
struct pcie_device *pcie_dev;
|
||||
struct find_aer_service_data *result;
|
||||
|
||||
result = (struct find_aer_service_data *) data;
|
||||
|
||||
if (device->bus == &pcie_port_bus_type) {
|
||||
pcie_dev = to_pcie_device(device);
|
||||
if (pcie_dev->id.port_type == PCIE_SW_DOWNSTREAM_PORT)
|
||||
result->is_downstream = 1;
|
||||
|
||||
driver = device->driver;
|
||||
if (driver) {
|
||||
service_driver = to_service_driver(driver);
|
||||
if (service_driver->id_table->service_type ==
|
||||
PCIE_PORT_SERVICE_AER) {
|
||||
result->aer_driver = service_driver;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void find_aer_service(struct pci_dev *dev,
|
||||
struct find_aer_service_data *data)
|
||||
{
|
||||
int retval;
|
||||
retval = device_for_each_child(&dev->dev, data, find_aer_service_iter);
|
||||
}
|
||||
|
||||
static pci_ers_result_t reset_link(struct pcie_device *aerdev,
|
||||
struct pci_dev *dev)
|
||||
{
|
||||
struct pci_dev *udev;
|
||||
pci_ers_result_t status;
|
||||
struct find_aer_service_data data;
|
||||
|
||||
if (dev->hdr_type & PCI_HEADER_TYPE_BRIDGE)
|
||||
udev = dev;
|
||||
else
|
||||
udev= dev->bus->self;
|
||||
|
||||
data.is_downstream = 0;
|
||||
data.aer_driver = NULL;
|
||||
find_aer_service(udev, &data);
|
||||
|
||||
/*
|
||||
* Use the aer driver of the error agent firstly.
|
||||
* If it hasn't the aer driver, use the root port's
|
||||
*/
|
||||
if (!data.aer_driver || !data.aer_driver->reset_link) {
|
||||
if (data.is_downstream &&
|
||||
aerdev->device.driver &&
|
||||
to_service_driver(aerdev->device.driver)->reset_link) {
|
||||
data.aer_driver =
|
||||
to_service_driver(aerdev->device.driver);
|
||||
} else {
|
||||
printk(KERN_DEBUG "No link-reset support to Device ID"
|
||||
"[%s]\n",
|
||||
dev->dev.bus_id);
|
||||
return PCI_ERS_RESULT_DISCONNECT;
|
||||
}
|
||||
}
|
||||
|
||||
status = data.aer_driver->reset_link(udev);
|
||||
if (status != PCI_ERS_RESULT_RECOVERED) {
|
||||
printk(KERN_DEBUG "Link reset at upstream Device ID"
|
||||
"[%s] failed\n",
|
||||
udev->dev.bus_id);
|
||||
return PCI_ERS_RESULT_DISCONNECT;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* do_recovery - handle nonfatal/fatal error recovery process
|
||||
* @aerdev: pointer to a pcie_device data structure of root port
|
||||
* @dev: pointer to a pci_dev data structure of agent detecting an error
|
||||
* @severity: error severity type
|
||||
*
|
||||
* 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.
|
||||
**/
|
||||
static pci_ers_result_t do_recovery(struct pcie_device *aerdev,
|
||||
struct pci_dev *dev,
|
||||
int severity)
|
||||
{
|
||||
pci_ers_result_t status, result = PCI_ERS_RESULT_RECOVERED;
|
||||
enum pci_channel_state state;
|
||||
|
||||
if (severity == AER_FATAL)
|
||||
state = pci_channel_io_frozen;
|
||||
else
|
||||
state = pci_channel_io_normal;
|
||||
|
||||
status = broadcast_error_message(dev,
|
||||
state,
|
||||
"error_detected",
|
||||
report_error_detected);
|
||||
|
||||
if (severity == AER_FATAL) {
|
||||
result = reset_link(aerdev, dev);
|
||||
if (result != PCI_ERS_RESULT_RECOVERED) {
|
||||
/* TODO: Should panic here? */
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
broadcast_error_message(dev,
|
||||
state,
|
||||
"resume",
|
||||
report_resume);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* handle_error_source - handle logging error into an event log
|
||||
* @aerdev: pointer to pcie_device data structure of the root port
|
||||
* @dev: pointer to pci_dev data structure of error source device
|
||||
* @info: comprehensive error information
|
||||
*
|
||||
* Invoked when an error being detected by Root Port.
|
||||
**/
|
||||
static void handle_error_source(struct pcie_device * aerdev,
|
||||
struct pci_dev *dev,
|
||||
struct aer_err_info info)
|
||||
{
|
||||
pci_ers_result_t status = 0;
|
||||
int pos;
|
||||
|
||||
if (info.severity == AER_CORRECTABLE) {
|
||||
/*
|
||||
* Correctable error does not need software intevention.
|
||||
* No need to go through error recovery process.
|
||||
*/
|
||||
pos = pci_find_aer_capability(dev);
|
||||
if (pos)
|
||||
pci_write_config_dword(dev, pos + PCI_ERR_COR_STATUS,
|
||||
info.status);
|
||||
} else {
|
||||
status = do_recovery(aerdev, dev, info.severity);
|
||||
if (status == PCI_ERS_RESULT_RECOVERED) {
|
||||
printk(KERN_DEBUG "AER driver successfully recovered\n");
|
||||
} else {
|
||||
/* TODO: Should kernel panic here? */
|
||||
printk(KERN_DEBUG "AER driver didn't recover\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* aer_enable_rootport - enable Root Port's interrupts when receiving messages
|
||||
* @rpc: pointer to a Root Port data structure
|
||||
*
|
||||
* Invoked when PCIE bus loads AER service driver.
|
||||
**/
|
||||
void aer_enable_rootport(struct aer_rpc *rpc)
|
||||
{
|
||||
struct pci_dev *pdev = rpc->rpd->port;
|
||||
int pos, aer_pos;
|
||||
u16 reg16;
|
||||
u32 reg32;
|
||||
|
||||
pos = pci_find_capability(pdev, PCI_CAP_ID_EXP);
|
||||
/* Clear PCIE Capability's Device Status */
|
||||
pci_read_config_word(pdev, pos+PCI_EXP_DEVSTA, ®16);
|
||||
pci_write_config_word(pdev, pos+PCI_EXP_DEVSTA, reg16);
|
||||
|
||||
/* Disable system error generation in response to error messages */
|
||||
pci_read_config_word(pdev, pos + PCI_EXP_RTCTL, ®16);
|
||||
reg16 &= ~(SYSTEM_ERROR_INTR_ON_MESG_MASK);
|
||||
pci_write_config_word(pdev, pos + PCI_EXP_RTCTL, reg16);
|
||||
|
||||
aer_pos = pci_find_aer_capability(pdev);
|
||||
/* Clear error status */
|
||||
pci_read_config_dword(pdev, aer_pos + PCI_ERR_ROOT_STATUS, ®32);
|
||||
pci_write_config_dword(pdev, aer_pos + PCI_ERR_ROOT_STATUS, reg32);
|
||||
pci_read_config_dword(pdev, aer_pos + PCI_ERR_COR_STATUS, ®32);
|
||||
pci_write_config_dword(pdev, aer_pos + PCI_ERR_COR_STATUS, reg32);
|
||||
pci_read_config_dword(pdev, aer_pos + PCI_ERR_UNCOR_STATUS, ®32);
|
||||
pci_write_config_dword(pdev, aer_pos + PCI_ERR_UNCOR_STATUS, reg32);
|
||||
|
||||
/* Enable Root Port device reporting error itself */
|
||||
pci_read_config_word(pdev, pos+PCI_EXP_DEVCTL, ®16);
|
||||
reg16 = reg16 |
|
||||
PCI_EXP_DEVCTL_CERE |
|
||||
PCI_EXP_DEVCTL_NFERE |
|
||||
PCI_EXP_DEVCTL_FERE |
|
||||
PCI_EXP_DEVCTL_URRE;
|
||||
pci_write_config_word(pdev, pos+PCI_EXP_DEVCTL,
|
||||
reg16);
|
||||
|
||||
/* Enable Root Port's interrupt in response to error messages */
|
||||
pci_write_config_dword(pdev,
|
||||
aer_pos + PCI_ERR_ROOT_COMMAND,
|
||||
ROOT_PORT_INTR_ON_MESG_MASK);
|
||||
}
|
||||
|
||||
/**
|
||||
* disable_root_aer - disable Root Port's interrupts when receiving messages
|
||||
* @rpc: pointer to a Root Port data structure
|
||||
*
|
||||
* Invoked when PCIE bus unloads AER service driver.
|
||||
**/
|
||||
static void disable_root_aer(struct aer_rpc *rpc)
|
||||
{
|
||||
struct pci_dev *pdev = rpc->rpd->port;
|
||||
u32 reg32;
|
||||
int pos;
|
||||
|
||||
pos = pci_find_aer_capability(pdev);
|
||||
/* Disable Root's interrupt in response to error messages */
|
||||
pci_write_config_dword(pdev, pos + PCI_ERR_ROOT_COMMAND, 0);
|
||||
|
||||
/* Clear Root's error status reg */
|
||||
pci_read_config_dword(pdev, pos + PCI_ERR_ROOT_STATUS, ®32);
|
||||
pci_write_config_dword(pdev, pos + PCI_ERR_ROOT_STATUS, reg32);
|
||||
}
|
||||
|
||||
/**
|
||||
* get_e_source - retrieve an error source
|
||||
* @rpc: pointer to the root port which holds an error
|
||||
*
|
||||
* Invoked by DPC handler to consume an error.
|
||||
**/
|
||||
static struct aer_err_source* get_e_source(struct aer_rpc *rpc)
|
||||
{
|
||||
struct aer_err_source *e_source;
|
||||
unsigned long flags;
|
||||
|
||||
/* Lock access to Root error producer/consumer index */
|
||||
spin_lock_irqsave(&rpc->e_lock, flags);
|
||||
if (rpc->prod_idx == rpc->cons_idx) {
|
||||
spin_unlock_irqrestore(&rpc->e_lock, flags);
|
||||
return NULL;
|
||||
}
|
||||
e_source = &rpc->e_sources[rpc->cons_idx];
|
||||
rpc->cons_idx++;
|
||||
if (rpc->cons_idx == AER_ERROR_SOURCES_MAX)
|
||||
rpc->cons_idx = 0;
|
||||
spin_unlock_irqrestore(&rpc->e_lock, flags);
|
||||
|
||||
return e_source;
|
||||
}
|
||||
|
||||
static int get_device_error_info(struct pci_dev *dev, struct aer_err_info *info)
|
||||
{
|
||||
int pos;
|
||||
|
||||
pos = pci_find_aer_capability(dev);
|
||||
|
||||
/* The device might not support AER */
|
||||
if (!pos)
|
||||
return AER_SUCCESS;
|
||||
|
||||
if (info->severity == AER_CORRECTABLE) {
|
||||
pci_read_config_dword(dev, pos + PCI_ERR_COR_STATUS,
|
||||
&info->status);
|
||||
if (!(info->status & ERR_CORRECTABLE_ERROR_MASK))
|
||||
return AER_UNSUCCESS;
|
||||
} else if (dev->hdr_type & PCI_HEADER_TYPE_BRIDGE ||
|
||||
info->severity == AER_NONFATAL) {
|
||||
|
||||
/* Link is still healthy for IO reads */
|
||||
pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS,
|
||||
&info->status);
|
||||
if (!(info->status & ERR_UNCORRECTABLE_ERROR_MASK))
|
||||
return AER_UNSUCCESS;
|
||||
|
||||
if (info->status & AER_LOG_TLP_MASKS) {
|
||||
info->flags |= AER_TLP_HEADER_VALID_FLAG;
|
||||
pci_read_config_dword(dev,
|
||||
pos + PCI_ERR_HEADER_LOG, &info->tlp.dw0);
|
||||
pci_read_config_dword(dev,
|
||||
pos + PCI_ERR_HEADER_LOG + 4, &info->tlp.dw1);
|
||||
pci_read_config_dword(dev,
|
||||
pos + PCI_ERR_HEADER_LOG + 8, &info->tlp.dw2);
|
||||
pci_read_config_dword(dev,
|
||||
pos + PCI_ERR_HEADER_LOG + 12, &info->tlp.dw3);
|
||||
}
|
||||
}
|
||||
|
||||
return AER_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* aer_isr_one_error - consume an error detected by root port
|
||||
* @p_device: pointer to error root port service device
|
||||
* @e_src: pointer to an error source
|
||||
**/
|
||||
static void aer_isr_one_error(struct pcie_device *p_device,
|
||||
struct aer_err_source *e_src)
|
||||
{
|
||||
struct device *s_device;
|
||||
struct aer_err_info e_info = {0, 0, 0,};
|
||||
int i;
|
||||
u16 id;
|
||||
|
||||
/*
|
||||
* There is a possibility that both correctable error and
|
||||
* uncorrectable error being logged. Report correctable error first.
|
||||
*/
|
||||
for (i = 1; i & ROOT_ERR_STATUS_MASKS ; i <<= 2) {
|
||||
if (i > 4)
|
||||
break;
|
||||
if (!(e_src->status & i))
|
||||
continue;
|
||||
|
||||
/* Init comprehensive error information */
|
||||
if (i & PCI_ERR_ROOT_COR_RCV) {
|
||||
id = ERR_COR_ID(e_src->id);
|
||||
e_info.severity = AER_CORRECTABLE;
|
||||
} else {
|
||||
id = ERR_UNCOR_ID(e_src->id);
|
||||
e_info.severity = ((e_src->status >> 6) & 1);
|
||||
}
|
||||
if (e_src->status &
|
||||
(PCI_ERR_ROOT_MULTI_COR_RCV |
|
||||
PCI_ERR_ROOT_MULTI_UNCOR_RCV))
|
||||
e_info.flags |= AER_MULTI_ERROR_VALID_FLAG;
|
||||
if (!(s_device = find_source_device(p_device->port, id))) {
|
||||
printk(KERN_DEBUG "%s->can't find device of ID%04x\n",
|
||||
__FUNCTION__, id);
|
||||
continue;
|
||||
}
|
||||
if (get_device_error_info(to_pci_dev(s_device), &e_info) ==
|
||||
AER_SUCCESS) {
|
||||
aer_print_error(to_pci_dev(s_device), &e_info);
|
||||
handle_error_source(p_device,
|
||||
to_pci_dev(s_device),
|
||||
e_info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* aer_isr - consume errors detected by root port
|
||||
* @context: pointer to a private data of pcie device
|
||||
*
|
||||
* Invoked, as DPC, when root port records new detected error
|
||||
**/
|
||||
void aer_isr(void *context)
|
||||
{
|
||||
struct pcie_device *p_device = (struct pcie_device *) context;
|
||||
struct aer_rpc *rpc = get_service_data(p_device);
|
||||
struct aer_err_source *e_src;
|
||||
|
||||
mutex_lock(&rpc->rpc_mutex);
|
||||
e_src = get_e_source(rpc);
|
||||
while (e_src) {
|
||||
aer_isr_one_error(p_device, e_src);
|
||||
e_src = get_e_source(rpc);
|
||||
}
|
||||
mutex_unlock(&rpc->rpc_mutex);
|
||||
|
||||
wake_up(&rpc->wait_release);
|
||||
}
|
||||
|
||||
/**
|
||||
* aer_delete_rootport - disable root port aer and delete service data
|
||||
* @rpc: pointer to a root port device being deleted
|
||||
*
|
||||
* Invoked when AER service unloaded on a specific Root Port
|
||||
**/
|
||||
void aer_delete_rootport(struct aer_rpc *rpc)
|
||||
{
|
||||
/* Disable root port AER itself */
|
||||
disable_root_aer(rpc);
|
||||
|
||||
kfree(rpc);
|
||||
}
|
||||
|
||||
/**
|
||||
* aer_init - provide AER initialization
|
||||
* @dev: pointer to AER pcie device
|
||||
*
|
||||
* Invoked when AER service driver is loaded.
|
||||
**/
|
||||
int aer_init(struct pcie_device *dev)
|
||||
{
|
||||
int status;
|
||||
|
||||
/* Run _OSC Method */
|
||||
status = aer_osc_setup(dev->port);
|
||||
|
||||
if(status != OSC_METHOD_RUN_SUCCESS) {
|
||||
printk(KERN_DEBUG "%s: AER service init fails - %s\n",
|
||||
__FUNCTION__,
|
||||
(status == OSC_METHOD_NOT_SUPPORTED) ?
|
||||
"No ACPI _OSC support" : "Run ACPI _OSC fails");
|
||||
|
||||
if (!forceload)
|
||||
return status;
|
||||
}
|
||||
|
||||
return AER_SUCCESS;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL_GPL(pci_find_aer_capability);
|
||||
EXPORT_SYMBOL_GPL(pci_enable_pcie_error_reporting);
|
||||
EXPORT_SYMBOL_GPL(pci_disable_pcie_error_reporting);
|
||||
EXPORT_SYMBOL_GPL(pci_cleanup_aer_uncorrect_error_status);
|
||||
|
248
drivers/pci/pcie/aer/aerdrv_errprint.c
Normal file
248
drivers/pci/pcie/aer/aerdrv_errprint.c
Normal file
@ -0,0 +1,248 @@
|
||||
/*
|
||||
* drivers/pci/pcie/aer/aerdrv_errprint.c
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU General Public
|
||||
* License. See the file "COPYING" in the main directory of this archive
|
||||
* for more details.
|
||||
*
|
||||
* Format error messages and print them to console.
|
||||
*
|
||||
* Copyright (C) 2006 Intel Corp.
|
||||
* Tom Long Nguyen (tom.l.nguyen@intel.com)
|
||||
* Zhang Yanmin (yanmin.zhang@intel.com)
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/suspend.h>
|
||||
|
||||
#include "aerdrv.h"
|
||||
|
||||
#define AER_AGENT_RECEIVER 0
|
||||
#define AER_AGENT_REQUESTER 1
|
||||
#define AER_AGENT_COMPLETER 2
|
||||
#define AER_AGENT_TRANSMITTER 3
|
||||
|
||||
#define AER_AGENT_REQUESTER_MASK (PCI_ERR_UNC_COMP_TIME| \
|
||||
PCI_ERR_UNC_UNSUP)
|
||||
|
||||
#define AER_AGENT_COMPLETER_MASK PCI_ERR_UNC_COMP_ABORT
|
||||
|
||||
#define AER_AGENT_TRANSMITTER_MASK(t, e) (e & (PCI_ERR_COR_REP_ROLL| \
|
||||
((t == AER_CORRECTABLE) ? PCI_ERR_COR_REP_TIMER: 0)))
|
||||
|
||||
#define AER_GET_AGENT(t, e) \
|
||||
((e & AER_AGENT_COMPLETER_MASK) ? AER_AGENT_COMPLETER : \
|
||||
(e & AER_AGENT_REQUESTER_MASK) ? AER_AGENT_REQUESTER : \
|
||||
(AER_AGENT_TRANSMITTER_MASK(t, e)) ? AER_AGENT_TRANSMITTER : \
|
||||
AER_AGENT_RECEIVER)
|
||||
|
||||
#define AER_PHYSICAL_LAYER_ERROR_MASK PCI_ERR_COR_RCVR
|
||||
#define AER_DATA_LINK_LAYER_ERROR_MASK(t, e) \
|
||||
(PCI_ERR_UNC_DLP| \
|
||||
PCI_ERR_COR_BAD_TLP| \
|
||||
PCI_ERR_COR_BAD_DLLP| \
|
||||
PCI_ERR_COR_REP_ROLL| \
|
||||
((t == AER_CORRECTABLE) ? \
|
||||
PCI_ERR_COR_REP_TIMER: 0))
|
||||
|
||||
#define AER_PHYSICAL_LAYER_ERROR 0
|
||||
#define AER_DATA_LINK_LAYER_ERROR 1
|
||||
#define AER_TRANSACTION_LAYER_ERROR 2
|
||||
|
||||
#define AER_GET_LAYER_ERROR(t, e) \
|
||||
((e & AER_PHYSICAL_LAYER_ERROR_MASK) ? \
|
||||
AER_PHYSICAL_LAYER_ERROR : \
|
||||
(e & AER_DATA_LINK_LAYER_ERROR_MASK(t, e)) ? \
|
||||
AER_DATA_LINK_LAYER_ERROR : \
|
||||
AER_TRANSACTION_LAYER_ERROR)
|
||||
|
||||
/*
|
||||
* AER error strings
|
||||
*/
|
||||
static char* aer_error_severity_string[] = {
|
||||
"Uncorrected (Non-Fatal)",
|
||||
"Uncorrected (Fatal)",
|
||||
"Corrected"
|
||||
};
|
||||
|
||||
static char* aer_error_layer[] = {
|
||||
"Physical Layer",
|
||||
"Data Link Layer",
|
||||
"Transaction Layer"
|
||||
};
|
||||
static char* aer_correctable_error_string[] = {
|
||||
"Receiver Error ", /* Bit Position 0 */
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
"Bad TLP ", /* Bit Position 6 */
|
||||
"Bad DLLP ", /* Bit Position 7 */
|
||||
"RELAY_NUM Rollover ", /* Bit Position 8 */
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
"Replay Timer Timeout ", /* Bit Position 12 */
|
||||
"Advisory Non-Fatal ", /* Bit Position 13 */
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static char* aer_uncorrectable_error_string[] = {
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
"Data Link Protocol ", /* Bit Position 4 */
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
"Poisoned TLP ", /* Bit Position 12 */
|
||||
"Flow Control Protocol ", /* Bit Position 13 */
|
||||
"Completion Timeout ", /* Bit Position 14 */
|
||||
"Completer Abort ", /* Bit Position 15 */
|
||||
"Unexpected Completion ", /* Bit Position 16 */
|
||||
"Receiver Overflow ", /* Bit Position 17 */
|
||||
"Malformed TLP ", /* Bit Position 18 */
|
||||
"ECRC ", /* Bit Position 19 */
|
||||
"Unsupported Request ", /* Bit Position 20 */
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static char* aer_agent_string[] = {
|
||||
"Receiver ID",
|
||||
"Requester ID",
|
||||
"Completer ID",
|
||||
"Transmitter ID"
|
||||
};
|
||||
|
||||
static char * aer_get_error_source_name(int severity,
|
||||
unsigned int status,
|
||||
char errmsg_buff[])
|
||||
{
|
||||
int i;
|
||||
char * errmsg = NULL;
|
||||
|
||||
for (i = 0; i < 32; i++) {
|
||||
if (!(status & (1 << i)))
|
||||
continue;
|
||||
|
||||
if (severity == AER_CORRECTABLE)
|
||||
errmsg = aer_correctable_error_string[i];
|
||||
else
|
||||
errmsg = aer_uncorrectable_error_string[i];
|
||||
|
||||
if (!errmsg) {
|
||||
sprintf(errmsg_buff, "Unknown Error Bit %2d ", i);
|
||||
errmsg = errmsg_buff;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return errmsg;
|
||||
}
|
||||
|
||||
static DEFINE_SPINLOCK(logbuf_lock);
|
||||
static char errmsg_buff[100];
|
||||
void aer_print_error(struct pci_dev *dev, struct aer_err_info *info)
|
||||
{
|
||||
char * errmsg;
|
||||
int err_layer, agent;
|
||||
char * loglevel;
|
||||
|
||||
if (info->severity == AER_CORRECTABLE)
|
||||
loglevel = KERN_WARNING;
|
||||
else
|
||||
loglevel = KERN_ERR;
|
||||
|
||||
printk("%s+------ PCI-Express Device Error ------+\n", loglevel);
|
||||
printk("%sError Severity\t\t: %s\n", loglevel,
|
||||
aer_error_severity_string[info->severity]);
|
||||
|
||||
if ( info->status == 0) {
|
||||
printk("%sPCIE Bus Error type\t: (Unaccessible)\n", loglevel);
|
||||
printk("%sUnaccessible Received\t: %s\n", loglevel,
|
||||
info->flags & AER_MULTI_ERROR_VALID_FLAG ?
|
||||
"Multiple" : "First");
|
||||
printk("%sUnregistered Agent ID\t: %04x\n", loglevel,
|
||||
(dev->bus->number << 8) | dev->devfn);
|
||||
} else {
|
||||
err_layer = AER_GET_LAYER_ERROR(info->severity, info->status);
|
||||
printk("%sPCIE Bus Error type\t: %s\n", loglevel,
|
||||
aer_error_layer[err_layer]);
|
||||
|
||||
spin_lock(&logbuf_lock);
|
||||
errmsg = aer_get_error_source_name(info->severity,
|
||||
info->status,
|
||||
errmsg_buff);
|
||||
printk("%s%s\t: %s\n", loglevel, errmsg,
|
||||
info->flags & AER_MULTI_ERROR_VALID_FLAG ?
|
||||
"Multiple" : "First");
|
||||
spin_unlock(&logbuf_lock);
|
||||
|
||||
agent = AER_GET_AGENT(info->severity, info->status);
|
||||
printk("%s%s\t\t: %04x\n", loglevel,
|
||||
aer_agent_string[agent],
|
||||
(dev->bus->number << 8) | dev->devfn);
|
||||
|
||||
printk("%sVendorID=%04xh, DeviceID=%04xh,"
|
||||
" Bus=%02xh, Device=%02xh, Function=%02xh\n",
|
||||
loglevel,
|
||||
dev->vendor,
|
||||
dev->device,
|
||||
dev->bus->number,
|
||||
PCI_SLOT(dev->devfn),
|
||||
PCI_FUNC(dev->devfn));
|
||||
|
||||
if (info->flags & AER_TLP_HEADER_VALID_FLAG) {
|
||||
unsigned char *tlp = (unsigned char *) &info->tlp;
|
||||
printk("%sTLB Header:\n", loglevel);
|
||||
printk("%s%02x%02x%02x%02x %02x%02x%02x%02x"
|
||||
" %02x%02x%02x%02x %02x%02x%02x%02x\n",
|
||||
loglevel,
|
||||
*(tlp + 3), *(tlp + 2), *(tlp + 1), *tlp,
|
||||
*(tlp + 7), *(tlp + 6), *(tlp + 5), *(tlp + 4),
|
||||
*(tlp + 11), *(tlp + 10), *(tlp + 9),
|
||||
*(tlp + 8), *(tlp + 15), *(tlp + 14),
|
||||
*(tlp + 13), *(tlp + 12));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ extern int pcie_port_device_suspend(struct pci_dev *dev, pm_message_t state);
|
||||
extern int pcie_port_device_resume(struct pci_dev *dev);
|
||||
#endif
|
||||
extern void pcie_port_device_remove(struct pci_dev *dev);
|
||||
extern void pcie_port_bus_register(void);
|
||||
extern int pcie_port_bus_register(void);
|
||||
extern void pcie_port_bus_unregister(void);
|
||||
|
||||
#endif /* _PORTDRV_H_ */
|
||||
|
@ -24,6 +24,7 @@ struct bus_type pcie_port_bus_type = {
|
||||
.suspend = pcie_port_bus_suspend,
|
||||
.resume = pcie_port_bus_resume,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(pcie_port_bus_type);
|
||||
|
||||
static int pcie_port_bus_match(struct device *dev, struct device_driver *drv)
|
||||
{
|
||||
|
@ -6,6 +6,7 @@
|
||||
* Copyright (C) Tom Long Nguyen (tom.l.nguyen@intel.com)
|
||||
*/
|
||||
|
||||
#include <linux/compiler.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/kernel.h>
|
||||
@ -339,8 +340,7 @@ static int suspend_iter(struct device *dev, void *data)
|
||||
|
||||
int pcie_port_device_suspend(struct pci_dev *dev, pm_message_t state)
|
||||
{
|
||||
device_for_each_child(&dev->dev, &state, suspend_iter);
|
||||
return 0;
|
||||
return device_for_each_child(&dev->dev, &state, suspend_iter);
|
||||
}
|
||||
|
||||
static int resume_iter(struct device *dev, void *data)
|
||||
@ -358,8 +358,7 @@ static int resume_iter(struct device *dev, void *data)
|
||||
|
||||
int pcie_port_device_resume(struct pci_dev *dev)
|
||||
{
|
||||
device_for_each_child(&dev->dev, NULL, resume_iter);
|
||||
return 0;
|
||||
return device_for_each_child(&dev->dev, NULL, resume_iter);
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -402,9 +401,9 @@ void pcie_port_device_remove(struct pci_dev *dev)
|
||||
pci_disable_msi(dev);
|
||||
}
|
||||
|
||||
void pcie_port_bus_register(void)
|
||||
int __must_check pcie_port_bus_register(void)
|
||||
{
|
||||
bus_register(&pcie_port_bus_type);
|
||||
return bus_register(&pcie_port_bus_type);
|
||||
}
|
||||
|
||||
void pcie_port_bus_unregister(void)
|
||||
|
@ -14,8 +14,10 @@
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/pcieport_if.h>
|
||||
#include <linux/aer.h>
|
||||
|
||||
#include "portdrv.h"
|
||||
#include "aer/aerdrv.h"
|
||||
|
||||
/*
|
||||
* Version Information
|
||||
@ -30,6 +32,43 @@ MODULE_LICENSE("GPL");
|
||||
/* global data */
|
||||
static const char device_name[] = "pcieport-driver";
|
||||
|
||||
static int pcie_portdrv_save_config(struct pci_dev *dev)
|
||||
{
|
||||
return pci_save_state(dev);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int pcie_portdrv_restore_config(struct pci_dev *dev)
|
||||
{
|
||||
int retval;
|
||||
|
||||
pci_restore_state(dev);
|
||||
retval = pci_enable_device(dev);
|
||||
if (retval)
|
||||
return retval;
|
||||
pci_set_master(dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pcie_portdrv_suspend(struct pci_dev *dev, pm_message_t state)
|
||||
{
|
||||
int ret = pcie_port_device_suspend(dev, state);
|
||||
|
||||
if (!ret)
|
||||
ret = pcie_portdrv_save_config(dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pcie_portdrv_resume(struct pci_dev *dev)
|
||||
{
|
||||
pcie_portdrv_restore_config(dev);
|
||||
return pcie_port_device_resume(dev);
|
||||
}
|
||||
#else
|
||||
#define pcie_portdrv_suspend NULL
|
||||
#define pcie_portdrv_resume NULL
|
||||
#endif
|
||||
|
||||
/*
|
||||
* pcie_portdrv_probe - Probe PCI-Express port devices
|
||||
* @dev: PCI-Express port device being probed
|
||||
@ -61,6 +100,10 @@ static int __devinit pcie_portdrv_probe (struct pci_dev *dev,
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
pcie_portdrv_save_config(dev);
|
||||
|
||||
pci_enable_pcie_error_reporting(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -70,39 +113,151 @@ static void pcie_portdrv_remove (struct pci_dev *dev)
|
||||
kfree(pci_get_drvdata(dev));
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int pcie_portdrv_save_config(struct pci_dev *dev)
|
||||
static int error_detected_iter(struct device *device, void *data)
|
||||
{
|
||||
return pci_save_state(dev);
|
||||
}
|
||||
struct pcie_device *pcie_device;
|
||||
struct pcie_port_service_driver *driver;
|
||||
struct aer_broadcast_data *result_data;
|
||||
pci_ers_result_t status;
|
||||
|
||||
static int pcie_portdrv_restore_config(struct pci_dev *dev)
|
||||
{
|
||||
int retval;
|
||||
result_data = (struct aer_broadcast_data *) data;
|
||||
|
||||
if (device->bus == &pcie_port_bus_type && device->driver) {
|
||||
driver = to_service_driver(device->driver);
|
||||
if (!driver ||
|
||||
!driver->err_handler ||
|
||||
!driver->err_handler->error_detected)
|
||||
return 0;
|
||||
|
||||
pcie_device = to_pcie_device(device);
|
||||
|
||||
/* Forward error detected message to service drivers */
|
||||
status = driver->err_handler->error_detected(
|
||||
pcie_device->port,
|
||||
result_data->state);
|
||||
result_data->result =
|
||||
merge_result(result_data->result, status);
|
||||
}
|
||||
|
||||
pci_restore_state(dev);
|
||||
retval = pci_enable_device(dev);
|
||||
if (retval)
|
||||
return retval;
|
||||
pci_set_master(dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pcie_portdrv_suspend (struct pci_dev *dev, pm_message_t state)
|
||||
static pci_ers_result_t pcie_portdrv_error_detected(struct pci_dev *dev,
|
||||
enum pci_channel_state error)
|
||||
{
|
||||
int ret = pcie_port_device_suspend(dev, state);
|
||||
struct aer_broadcast_data result_data =
|
||||
{error, PCI_ERS_RESULT_CAN_RECOVER};
|
||||
int retval;
|
||||
|
||||
if (!ret)
|
||||
ret = pcie_portdrv_save_config(dev);
|
||||
return ret;
|
||||
/* can not fail */
|
||||
retval = device_for_each_child(&dev->dev, &result_data, error_detected_iter);
|
||||
|
||||
return result_data.result;
|
||||
}
|
||||
|
||||
static int pcie_portdrv_resume (struct pci_dev *dev)
|
||||
static int mmio_enabled_iter(struct device *device, void *data)
|
||||
{
|
||||
pcie_portdrv_restore_config(dev);
|
||||
return pcie_port_device_resume(dev);
|
||||
struct pcie_device *pcie_device;
|
||||
struct pcie_port_service_driver *driver;
|
||||
pci_ers_result_t status, *result;
|
||||
|
||||
result = (pci_ers_result_t *) data;
|
||||
|
||||
if (device->bus == &pcie_port_bus_type && device->driver) {
|
||||
driver = to_service_driver(device->driver);
|
||||
if (driver &&
|
||||
driver->err_handler &&
|
||||
driver->err_handler->mmio_enabled) {
|
||||
pcie_device = to_pcie_device(device);
|
||||
|
||||
/* Forward error message to service drivers */
|
||||
status = driver->err_handler->mmio_enabled(
|
||||
pcie_device->port);
|
||||
*result = merge_result(*result, status);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static pci_ers_result_t pcie_portdrv_mmio_enabled(struct pci_dev *dev)
|
||||
{
|
||||
pci_ers_result_t status = PCI_ERS_RESULT_RECOVERED;
|
||||
int retval;
|
||||
|
||||
/* get true return value from &status */
|
||||
retval = device_for_each_child(&dev->dev, &status, mmio_enabled_iter);
|
||||
return status;
|
||||
}
|
||||
|
||||
static int slot_reset_iter(struct device *device, void *data)
|
||||
{
|
||||
struct pcie_device *pcie_device;
|
||||
struct pcie_port_service_driver *driver;
|
||||
pci_ers_result_t status, *result;
|
||||
|
||||
result = (pci_ers_result_t *) data;
|
||||
|
||||
if (device->bus == &pcie_port_bus_type && device->driver) {
|
||||
driver = to_service_driver(device->driver);
|
||||
if (driver &&
|
||||
driver->err_handler &&
|
||||
driver->err_handler->slot_reset) {
|
||||
pcie_device = to_pcie_device(device);
|
||||
|
||||
/* Forward error message to service drivers */
|
||||
status = driver->err_handler->slot_reset(
|
||||
pcie_device->port);
|
||||
*result = merge_result(*result, status);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static pci_ers_result_t pcie_portdrv_slot_reset(struct pci_dev *dev)
|
||||
{
|
||||
pci_ers_result_t status;
|
||||
int retval;
|
||||
|
||||
/* If fatal, restore cfg space for possible link reset at upstream */
|
||||
if (dev->error_state == pci_channel_io_frozen) {
|
||||
pcie_portdrv_restore_config(dev);
|
||||
pci_enable_pcie_error_reporting(dev);
|
||||
}
|
||||
|
||||
/* get true return value from &status */
|
||||
retval = device_for_each_child(&dev->dev, &status, slot_reset_iter);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static int resume_iter(struct device *device, void *data)
|
||||
{
|
||||
struct pcie_device *pcie_device;
|
||||
struct pcie_port_service_driver *driver;
|
||||
|
||||
if (device->bus == &pcie_port_bus_type && device->driver) {
|
||||
driver = to_service_driver(device->driver);
|
||||
if (driver &&
|
||||
driver->err_handler &&
|
||||
driver->err_handler->resume) {
|
||||
pcie_device = to_pcie_device(device);
|
||||
|
||||
/* Forward error message to service drivers */
|
||||
driver->err_handler->resume(pcie_device->port);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void pcie_portdrv_err_resume(struct pci_dev *dev)
|
||||
{
|
||||
int retval;
|
||||
/* nothing to do with error value, if it ever happens */
|
||||
retval = device_for_each_child(&dev->dev, NULL, resume_iter);
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* LINUX Device Driver Model
|
||||
@ -114,6 +269,13 @@ static const struct pci_device_id port_pci_ids[] = { {
|
||||
};
|
||||
MODULE_DEVICE_TABLE(pci, port_pci_ids);
|
||||
|
||||
static struct pci_error_handlers pcie_portdrv_err_handler = {
|
||||
.error_detected = pcie_portdrv_error_detected,
|
||||
.mmio_enabled = pcie_portdrv_mmio_enabled,
|
||||
.slot_reset = pcie_portdrv_slot_reset,
|
||||
.resume = pcie_portdrv_err_resume,
|
||||
};
|
||||
|
||||
static struct pci_driver pcie_portdrv = {
|
||||
.name = (char *)device_name,
|
||||
.id_table = &port_pci_ids[0],
|
||||
@ -121,20 +283,25 @@ static struct pci_driver pcie_portdrv = {
|
||||
.probe = pcie_portdrv_probe,
|
||||
.remove = pcie_portdrv_remove,
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
.suspend = pcie_portdrv_suspend,
|
||||
.resume = pcie_portdrv_resume,
|
||||
#endif /* PM */
|
||||
|
||||
.err_handler = &pcie_portdrv_err_handler,
|
||||
};
|
||||
|
||||
static int __init pcie_portdrv_init(void)
|
||||
{
|
||||
int retval = 0;
|
||||
int retval;
|
||||
|
||||
pcie_port_bus_register();
|
||||
retval = pcie_port_bus_register();
|
||||
if (retval) {
|
||||
printk(KERN_WARNING "PCIE: bus_register error: %d\n", retval);
|
||||
goto out;
|
||||
}
|
||||
retval = pci_register_driver(&pcie_portdrv);
|
||||
if (retval)
|
||||
pcie_port_bus_unregister();
|
||||
out:
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
@ -339,6 +339,7 @@ pci_alloc_child_bus(struct pci_bus *parent, struct pci_dev *bridge, int busnr)
|
||||
{
|
||||
struct pci_bus *child;
|
||||
int i;
|
||||
int retval;
|
||||
|
||||
/*
|
||||
* Allocate a new bus, and inherit stuff from the parent..
|
||||
@ -356,8 +357,13 @@ pci_alloc_child_bus(struct pci_bus *parent, struct pci_dev *bridge, int busnr)
|
||||
|
||||
child->class_dev.class = &pcibus_class;
|
||||
sprintf(child->class_dev.class_id, "%04x:%02x", pci_domain_nr(child), busnr);
|
||||
class_device_register(&child->class_dev);
|
||||
class_device_create_file(&child->class_dev, &class_device_attr_cpuaffinity);
|
||||
retval = class_device_register(&child->class_dev);
|
||||
if (retval)
|
||||
goto error_register;
|
||||
retval = class_device_create_file(&child->class_dev,
|
||||
&class_device_attr_cpuaffinity);
|
||||
if (retval)
|
||||
goto error_file_create;
|
||||
|
||||
/*
|
||||
* Set up the primary, secondary and subordinate
|
||||
@ -375,6 +381,12 @@ pci_alloc_child_bus(struct pci_bus *parent, struct pci_dev *bridge, int busnr)
|
||||
bridge->subordinate = child;
|
||||
|
||||
return child;
|
||||
|
||||
error_file_create:
|
||||
class_device_unregister(&child->class_dev);
|
||||
error_register:
|
||||
kfree(child);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct pci_bus * __devinit pci_add_new_bus(struct pci_bus *parent, struct pci_dev *dev, int busnr)
|
||||
|
@ -577,8 +577,6 @@ static void __init quirk_ioapic_rmw(struct pci_dev *dev)
|
||||
}
|
||||
DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_SI, PCI_ANY_ID, quirk_ioapic_rmw );
|
||||
|
||||
int pci_msi_quirk;
|
||||
|
||||
#define AMD8131_revA0 0x01
|
||||
#define AMD8131_revB0 0x11
|
||||
#define AMD8131_MISC 0x40
|
||||
@ -587,12 +585,6 @@ static void __init quirk_amd_8131_ioapic(struct pci_dev *dev)
|
||||
{
|
||||
unsigned char revid, tmp;
|
||||
|
||||
if (dev->subordinate) {
|
||||
printk(KERN_WARNING "PCI: MSI quirk detected. "
|
||||
"PCI_BUS_FLAGS_NO_MSI set for subordinate bus.\n");
|
||||
dev->subordinate->bus_flags |= PCI_BUS_FLAGS_NO_MSI;
|
||||
}
|
||||
|
||||
if (nr_ioapics == 0)
|
||||
return;
|
||||
|
||||
@ -605,13 +597,6 @@ static void __init quirk_amd_8131_ioapic(struct pci_dev *dev)
|
||||
}
|
||||
}
|
||||
DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_8131_BRIDGE, quirk_amd_8131_ioapic);
|
||||
|
||||
static void __init quirk_svw_msi(struct pci_dev *dev)
|
||||
{
|
||||
pci_msi_quirk = 1;
|
||||
printk(KERN_WARNING "PCI: MSI quirk detected. pci_msi_quirk set.\n");
|
||||
}
|
||||
DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_SERVERWORKS, PCI_DEVICE_ID_SERVERWORKS_GCNB_LE, quirk_svw_msi );
|
||||
#endif /* CONFIG_X86_IO_APIC */
|
||||
|
||||
|
||||
@ -1690,6 +1675,95 @@ static void __devinit quirk_nvidia_ck804_pcie_aer_ext_cap(struct pci_dev *dev)
|
||||
DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_CK804_PCIE,
|
||||
quirk_nvidia_ck804_pcie_aer_ext_cap);
|
||||
|
||||
#ifdef CONFIG_PCI_MSI
|
||||
/* To disable MSI globally */
|
||||
int pci_msi_quirk;
|
||||
|
||||
/* The Serverworks PCI-X chipset does not support MSI. We cannot easily rely
|
||||
* on setting PCI_BUS_FLAGS_NO_MSI in its bus flags because there are actually
|
||||
* some other busses controlled by the chipset even if Linux is not aware of it.
|
||||
* Instead of setting the flag on all busses in the machine, simply disable MSI
|
||||
* globally.
|
||||
*/
|
||||
static void __init quirk_svw_msi(struct pci_dev *dev)
|
||||
{
|
||||
pci_msi_quirk = 1;
|
||||
printk(KERN_WARNING "PCI: MSI quirk detected. pci_msi_quirk set.\n");
|
||||
}
|
||||
DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_SERVERWORKS, PCI_DEVICE_ID_SERVERWORKS_GCNB_LE, quirk_svw_msi);
|
||||
|
||||
/* Disable MSI on chipsets that are known to not support it */
|
||||
static void __devinit quirk_disable_msi(struct pci_dev *dev)
|
||||
{
|
||||
if (dev->subordinate) {
|
||||
printk(KERN_WARNING "PCI: MSI quirk detected. "
|
||||
"PCI_BUS_FLAGS_NO_MSI set for %s subordinate bus.\n",
|
||||
pci_name(dev));
|
||||
dev->subordinate->bus_flags |= PCI_BUS_FLAGS_NO_MSI;
|
||||
}
|
||||
}
|
||||
DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_8131_BRIDGE, quirk_disable_msi);
|
||||
|
||||
/* Go through the list of Hypertransport capabilities and
|
||||
* return 1 if a HT MSI capability is found and enabled */
|
||||
static int __devinit msi_ht_cap_enabled(struct pci_dev *dev)
|
||||
{
|
||||
u8 pos;
|
||||
int ttl;
|
||||
for (pos = pci_find_capability(dev, PCI_CAP_ID_HT), ttl = 48;
|
||||
pos && ttl;
|
||||
pos = pci_find_next_capability(dev, pos, PCI_CAP_ID_HT), ttl--) {
|
||||
u32 cap_hdr;
|
||||
/* MSI mapping section according to Hypertransport spec */
|
||||
if (pci_read_config_dword(dev, pos, &cap_hdr) == 0
|
||||
&& (cap_hdr & 0xf8000000) == 0xa8000000 /* MSI mapping */) {
|
||||
printk(KERN_INFO "PCI: Found HT MSI mapping on %s with capability %s\n",
|
||||
pci_name(dev), cap_hdr & 0x10000 ? "enabled" : "disabled");
|
||||
return (cap_hdr & 0x10000) != 0; /* MSI mapping cap enabled */
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Check the hypertransport MSI mapping to know whether MSI is enabled or not */
|
||||
static void __devinit quirk_msi_ht_cap(struct pci_dev *dev)
|
||||
{
|
||||
if (dev->subordinate && !msi_ht_cap_enabled(dev)) {
|
||||
printk(KERN_WARNING "PCI: MSI quirk detected. "
|
||||
"MSI disabled on chipset %s.\n",
|
||||
pci_name(dev));
|
||||
dev->subordinate->bus_flags |= PCI_BUS_FLAGS_NO_MSI;
|
||||
}
|
||||
}
|
||||
DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_SERVERWORKS, PCI_DEVICE_ID_SERVERWORKS_HT2000_PCIE,
|
||||
quirk_msi_ht_cap);
|
||||
|
||||
/* The nVidia CK804 chipset may have 2 HT MSI mappings.
|
||||
* MSI are supported if the MSI capability set in any of these mappings.
|
||||
*/
|
||||
static void __devinit quirk_nvidia_ck804_msi_ht_cap(struct pci_dev *dev)
|
||||
{
|
||||
struct pci_dev *pdev;
|
||||
|
||||
if (!dev->subordinate)
|
||||
return;
|
||||
|
||||
/* check HT MSI cap on this chipset and the root one.
|
||||
* a single one having MSI is enough to be sure that MSI are supported.
|
||||
*/
|
||||
pdev = pci_find_slot(dev->bus->number, 0);
|
||||
if (dev->subordinate && !msi_ht_cap_enabled(dev)
|
||||
&& !msi_ht_cap_enabled(pdev)) {
|
||||
printk(KERN_WARNING "PCI: MSI quirk detected. "
|
||||
"MSI disabled on chipset %s.\n",
|
||||
pci_name(dev));
|
||||
dev->subordinate->bus_flags |= PCI_BUS_FLAGS_NO_MSI;
|
||||
}
|
||||
}
|
||||
DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_CK804_PCIE,
|
||||
quirk_nvidia_ck804_msi_ht_cap);
|
||||
#endif /* CONFIG_PCI_MSI */
|
||||
|
||||
EXPORT_SYMBOL(pcie_mch_quirk);
|
||||
#ifdef CONFIG_HOTPLUG
|
||||
EXPORT_SYMBOL(pci_fixup_device);
|
||||
|
@ -16,8 +16,11 @@ static void pci_free_resources(struct pci_dev *dev)
|
||||
}
|
||||
}
|
||||
|
||||
static void pci_destroy_dev(struct pci_dev *dev)
|
||||
static void pci_stop_dev(struct pci_dev *dev)
|
||||
{
|
||||
if (!dev->global_list.next)
|
||||
return;
|
||||
|
||||
if (!list_empty(&dev->global_list)) {
|
||||
pci_proc_detach_device(dev);
|
||||
pci_remove_sysfs_dev_files(dev);
|
||||
@ -27,6 +30,11 @@ static void pci_destroy_dev(struct pci_dev *dev)
|
||||
dev->global_list.next = dev->global_list.prev = NULL;
|
||||
up_write(&pci_bus_sem);
|
||||
}
|
||||
}
|
||||
|
||||
static void pci_destroy_dev(struct pci_dev *dev)
|
||||
{
|
||||
pci_stop_dev(dev);
|
||||
|
||||
/* Remove the device from the device lists, and prevent any further
|
||||
* list accesses from this device */
|
||||
@ -119,5 +127,32 @@ void pci_remove_behind_bridge(struct pci_dev *dev)
|
||||
}
|
||||
}
|
||||
|
||||
static void pci_stop_bus_devices(struct pci_bus *bus)
|
||||
{
|
||||
struct list_head *l, *n;
|
||||
|
||||
list_for_each_safe(l, n, &bus->devices) {
|
||||
struct pci_dev *dev = pci_dev_b(l);
|
||||
pci_stop_bus_device(dev);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* pci_stop_bus_device - stop a PCI device and any children
|
||||
* @dev: the device to stop
|
||||
*
|
||||
* Stop a PCI device (detach the driver, remove from the global list
|
||||
* and so on). This also stop any subordinate buses and children in a
|
||||
* depth-first manner.
|
||||
*/
|
||||
void pci_stop_bus_device(struct pci_dev *dev)
|
||||
{
|
||||
if (dev->subordinate)
|
||||
pci_stop_bus_devices(dev->subordinate);
|
||||
|
||||
pci_stop_dev(dev);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(pci_remove_bus_device);
|
||||
EXPORT_SYMBOL(pci_remove_behind_bridge);
|
||||
EXPORT_SYMBOL_GPL(pci_stop_bus_device);
|
||||
|
@ -55,12 +55,19 @@ pbus_assign_resources_sorted(struct pci_bus *bus)
|
||||
list_for_each_entry(dev, &bus->devices, bus_list) {
|
||||
u16 class = dev->class >> 8;
|
||||
|
||||
/* Don't touch classless devices or host bridges or ioapics. */
|
||||
/* Don't touch classless devices or host bridges. */
|
||||
if (class == PCI_CLASS_NOT_DEFINED ||
|
||||
class == PCI_CLASS_BRIDGE_HOST ||
|
||||
class == PCI_CLASS_SYSTEM_PIC)
|
||||
class == PCI_CLASS_BRIDGE_HOST)
|
||||
continue;
|
||||
|
||||
/* Don't touch ioapics if it has the assigned resources. */
|
||||
if (class == PCI_CLASS_SYSTEM_PIC) {
|
||||
res = &dev->resource[0];
|
||||
if (res[0].start || res[1].start || res[2].start ||
|
||||
res[3].start || res[4].start || res[5].start)
|
||||
continue;
|
||||
}
|
||||
|
||||
pdev_sort_resources(dev, &head);
|
||||
}
|
||||
|
||||
|
24
include/linux/aer.h
Normal file
24
include/linux/aer.h
Normal file
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (C) 2006 Intel Corp.
|
||||
* Tom Long Nguyen (tom.l.nguyen@intel.com)
|
||||
* Zhang Yanmin (yanmin.zhang@intel.com)
|
||||
*/
|
||||
|
||||
#ifndef _AER_H_
|
||||
#define _AER_H_
|
||||
|
||||
#if defined(CONFIG_PCIEAER)
|
||||
/* pci-e port driver needs this function to enable aer */
|
||||
extern int pci_enable_pcie_error_reporting(struct pci_dev *dev);
|
||||
extern int pci_find_aer_capability(struct pci_dev *dev);
|
||||
extern int pci_disable_pcie_error_reporting(struct pci_dev *dev);
|
||||
extern int pci_cleanup_aer_uncorrect_error_status(struct pci_dev *dev);
|
||||
#else
|
||||
#define pci_enable_pcie_error_reporting(dev) do { } while (0)
|
||||
#define pci_find_aer_capability(dev) do { } while (0)
|
||||
#define pci_disable_pcie_error_reporting(dev) do { } while (0)
|
||||
#define pci_cleanup_aer_uncorrect_error_status(dev) do { } while (0)
|
||||
#endif
|
||||
|
||||
#endif //_AER_H_
|
||||
|
@ -356,6 +356,8 @@ struct pci_driver {
|
||||
struct pci_error_handlers *err_handler;
|
||||
struct device_driver driver;
|
||||
struct pci_dynids dynids;
|
||||
|
||||
int multithread_probe;
|
||||
};
|
||||
|
||||
#define to_pci_driver(drv) container_of(drv,struct pci_driver, driver)
|
||||
@ -431,7 +433,7 @@ int pci_scan_slot(struct pci_bus *bus, int devfn);
|
||||
struct pci_dev * pci_scan_single_device(struct pci_bus *bus, int devfn);
|
||||
void pci_device_add(struct pci_dev *dev, struct pci_bus *bus);
|
||||
unsigned int pci_scan_child_bus(struct pci_bus *bus);
|
||||
void pci_bus_add_device(struct pci_dev *dev);
|
||||
int __must_check pci_bus_add_device(struct pci_dev *dev);
|
||||
void pci_read_bridge_bases(struct pci_bus *child);
|
||||
struct resource *pci_find_parent_resource(const struct pci_dev *dev, struct resource *res);
|
||||
int pci_get_interrupt_pin(struct pci_dev *dev, struct pci_dev **bridge);
|
||||
@ -439,6 +441,7 @@ extern struct pci_dev *pci_dev_get(struct pci_dev *dev);
|
||||
extern void pci_dev_put(struct pci_dev *dev);
|
||||
extern void pci_remove_bus(struct pci_bus *b);
|
||||
extern void pci_remove_bus_device(struct pci_dev *dev);
|
||||
extern void pci_stop_bus_device(struct pci_dev *dev);
|
||||
void pci_setup_cardbus(struct pci_bus *bus);
|
||||
|
||||
/* Generic PCI functions exported to card drivers */
|
||||
|
@ -1411,6 +1411,7 @@
|
||||
#define PCI_DEVICE_ID_SERVERWORKS_LE 0x0009
|
||||
#define PCI_DEVICE_ID_SERVERWORKS_GCNB_LE 0x0017
|
||||
#define PCI_DEVICE_ID_SERVERWORKS_EPB 0x0103
|
||||
#define PCI_DEVICE_ID_SERVERWORKS_HT2000_PCIE 0x0132
|
||||
#define PCI_DEVICE_ID_SERVERWORKS_OSB4 0x0200
|
||||
#define PCI_DEVICE_ID_SERVERWORKS_CSB5 0x0201
|
||||
#define PCI_DEVICE_ID_SERVERWORKS_CSB6 0x0203
|
||||
|
@ -196,7 +196,7 @@
|
||||
#define PCI_CAP_ID_MSI 0x05 /* Message Signalled Interrupts */
|
||||
#define PCI_CAP_ID_CHSWP 0x06 /* CompactPCI HotSwap */
|
||||
#define PCI_CAP_ID_PCIX 0x07 /* PCI-X */
|
||||
#define PCI_CAP_ID_HT_IRQCONF 0x08 /* HyperTransport IRQ Configuration */
|
||||
#define PCI_CAP_ID_HT 0x08 /* HyperTransport */
|
||||
#define PCI_CAP_ID_VNDR 0x09 /* Vendor specific capability */
|
||||
#define PCI_CAP_ID_SHPC 0x0C /* PCI Standard Hot-Plug Controller */
|
||||
#define PCI_CAP_ID_EXP 0x10 /* PCI Express */
|
||||
|
@ -62,6 +62,12 @@ struct pcie_port_service_driver {
|
||||
int (*suspend) (struct pcie_device *dev, pm_message_t state);
|
||||
int (*resume) (struct pcie_device *dev);
|
||||
|
||||
/* Service Error Recovery Handler */
|
||||
struct pci_error_handlers *err_handler;
|
||||
|
||||
/* Link Reset Capability - AER service driver specific */
|
||||
pci_ers_result_t (*reset_link) (struct pci_dev *dev);
|
||||
|
||||
const struct pcie_port_service_id *id_table;
|
||||
struct device_driver driver;
|
||||
};
|
||||
|
@ -344,12 +344,11 @@ EXPORT_SYMBOL(allocate_resource);
|
||||
*
|
||||
* Returns 0 on success, -EBUSY if the resource can't be inserted.
|
||||
*
|
||||
* This function is equivalent of request_resource when no conflict
|
||||
* This function is equivalent to request_resource when no conflict
|
||||
* happens. If a conflict happens, and the conflicting resources
|
||||
* entirely fit within the range of the new resource, then the new
|
||||
* resource is inserted and the conflicting resources become childs of
|
||||
* the new resource. Otherwise the new resource becomes the child of
|
||||
* the conflicting resource
|
||||
* resource is inserted and the conflicting resources become children of
|
||||
* the new resource.
|
||||
*/
|
||||
int insert_resource(struct resource *parent, struct resource *new)
|
||||
{
|
||||
@ -357,20 +356,21 @@ int insert_resource(struct resource *parent, struct resource *new)
|
||||
struct resource *first, *next;
|
||||
|
||||
write_lock(&resource_lock);
|
||||
begin:
|
||||
result = 0;
|
||||
first = __request_resource(parent, new);
|
||||
if (!first)
|
||||
goto out;
|
||||
|
||||
result = -EBUSY;
|
||||
if (first == parent)
|
||||
goto out;
|
||||
for (;; parent = first) {
|
||||
result = 0;
|
||||
first = __request_resource(parent, new);
|
||||
if (!first)
|
||||
goto out;
|
||||
|
||||
/* Resource fully contained by the clashing resource? Recurse into it */
|
||||
if (first->start <= new->start && first->end >= new->end) {
|
||||
parent = first;
|
||||
goto begin;
|
||||
result = -EBUSY;
|
||||
if (first == parent)
|
||||
goto out;
|
||||
|
||||
if ((first->start > new->start) || (first->end < new->end))
|
||||
break;
|
||||
if ((first->start == new->start) && (first->end == new->end))
|
||||
break;
|
||||
}
|
||||
|
||||
for (next = first; ; next = next->sibling) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user