Creation of Cybook 2416 (actually Gen4) repository

This commit is contained in:
mlt
2009-12-18 17:10:00 +00:00
committed by godzil
commit 76f20f4d40
13791 changed files with 6812321 additions and 0 deletions

37
drivers/pci/pcie/Kconfig Normal file
View File

@@ -0,0 +1,37 @@
#
# PCI Express Port Bus Configuration
#
config PCIEPORTBUS
bool "PCI Express support"
depends on PCI
help
This automatically enables PCI Express Port Bus support. Users can
choose Native Hot-Plug support, Advanced Error Reporting support,
Power Management Event support and Virtual Channel support to run
on PCI Express Ports (Root or Switch).
#
# Include service Kconfig here
#
config HOTPLUG_PCI_PCIE
tristate "PCI Express Hotplug driver"
depends on HOTPLUG_PCI && PCIEPORTBUS
help
Say Y here if you have a motherboard that supports PCI Express Native
Hotplug
To compile this driver as a module, choose M here: the
module will be called pciehp.
When in doubt, say N.
config HOTPLUG_PCI_PCIE_POLL_EVENT_MODE
bool "Use polling mechanism for hot-plug events (for testing purpose)"
depends on HOTPLUG_PCI_PCIE
help
Say Y here if you want to use the polling mechanism for hot-plug
events for early platform testing.
When in doubt, say N.
source "drivers/pci/pcie/aer/Kconfig"

10
drivers/pci/pcie/Makefile Normal file
View File

@@ -0,0 +1,10 @@
#
# Makefile for PCI-Express PORT Driver
#
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/

View 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.

View 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

View File

@@ -0,0 +1,345 @@
/*
* 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 aerdriver = {
.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
*
* Invoked when Root Port detects AER messages.
**/
static irqreturn_t aer_irq(int irq, void *context)
{
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 = 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);
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, IRQF_SHARED, "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, &reg16);
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(&aerdriver);
}
/**
* 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(&aerdriver);
}
module_init(aer_service_init);
module_exit(aer_service_exit);

View 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 hierarchy
*/
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(struct work_struct *work);
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_

View 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;
}

View 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, &reg32))
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, &reg16);
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, &reg16);
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, &reg16);
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, &reg16);
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, &reg32);
pci_write_config_dword(pdev, aer_pos + PCI_ERR_ROOT_STATUS, reg32);
pci_read_config_dword(pdev, aer_pos + PCI_ERR_COR_STATUS, &reg32);
pci_write_config_dword(pdev, aer_pos + PCI_ERR_COR_STATUS, reg32);
pci_read_config_dword(pdev, aer_pos + PCI_ERR_UNCOR_STATUS, &reg32);
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, &reg16);
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, &reg32);
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
* @work: definition of this work item
*
* Invoked, as DPC, when root port records new detected error
**/
void aer_isr(struct work_struct *work)
{
struct aer_rpc *rpc = container_of(work, struct aer_rpc, dpc_handler);
struct pcie_device *p_device = rpc->rpd;
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);

View 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));
}
}
}

View File

@@ -0,0 +1,47 @@
/*
* File: portdrv.h
* Purpose: PCI Express Port Bus Driver's Internal Data Structures
*
* Copyright (C) 2004 Intel
* Copyright (C) Tom Long Nguyen (tom.l.nguyen@intel.com)
*/
#ifndef _PORTDRV_H_
#define _PORTDRV_H_
#include <linux/compiler.h>
#if !defined(PCI_CAP_ID_PME)
#define PCI_CAP_ID_PME 1
#endif
#if !defined(PCI_CAP_ID_EXP)
#define PCI_CAP_ID_EXP 0x10
#endif
#define PORT_TYPE_MASK 0xf
#define PORT_TO_SLOT_MASK 0x100
#define SLOT_HP_CAPABLE_MASK 0x40
#define PCIE_CAPABILITIES_REG 0x2
#define PCIE_SLOT_CAPABILITIES_REG 0x14
#define PCIE_PORT_DEVICE_MAXSERVICES 4
#define PCI_CFG_SPACE_SIZE 256
#define get_descriptor_id(type, service) (((type - 4) << 4) | service)
struct pcie_port_device_ext {
int interrupt_mode; /* [0:INTx | 1:MSI | 2:MSI-X] */
};
extern struct bus_type pcie_port_bus_type;
extern int pcie_port_device_probe(struct pci_dev *dev);
extern int pcie_port_device_register(struct pci_dev *dev);
#ifdef CONFIG_PM
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 int __must_check pcie_port_bus_register(void);
extern void pcie_port_bus_unregister(void);
#endif /* _PORTDRV_H_ */

View File

@@ -0,0 +1,79 @@
/*
* File: portdrv_bus.c
* Purpose: PCI Express Port Bus Driver's Bus Overloading Functions
*
* Copyright (C) 2004 Intel
* Copyright (C) Tom Long Nguyen (tom.l.nguyen@intel.com)
*/
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/pm.h>
#include <linux/pcieport_if.h>
static int pcie_port_bus_match(struct device *dev, struct device_driver *drv);
static int pcie_port_bus_suspend(struct device *dev, pm_message_t state);
static int pcie_port_bus_resume(struct device *dev);
struct bus_type pcie_port_bus_type = {
.name = "pci_express",
.match = pcie_port_bus_match,
.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)
{
struct pcie_device *pciedev;
struct pcie_port_service_driver *driver;
if (drv->bus != &pcie_port_bus_type || dev->bus != &pcie_port_bus_type)
return 0;
pciedev = to_pcie_device(dev);
driver = to_service_driver(drv);
if ( (driver->id_table->vendor != PCI_ANY_ID &&
driver->id_table->vendor != pciedev->id.vendor) ||
(driver->id_table->device != PCI_ANY_ID &&
driver->id_table->device != pciedev->id.device) ||
(driver->id_table->port_type != PCIE_ANY_PORT &&
driver->id_table->port_type != pciedev->id.port_type) ||
driver->id_table->service_type != pciedev->id.service_type )
return 0;
return 1;
}
static int pcie_port_bus_suspend(struct device *dev, pm_message_t state)
{
struct pcie_device *pciedev;
struct pcie_port_service_driver *driver;
if (!dev || !dev->driver)
return 0;
pciedev = to_pcie_device(dev);
driver = to_service_driver(dev->driver);
if (driver && driver->suspend)
driver->suspend(pciedev, state);
return 0;
}
static int pcie_port_bus_resume(struct device *dev)
{
struct pcie_device *pciedev;
struct pcie_port_service_driver *driver;
if (!dev || !dev->driver)
return 0;
pciedev = to_pcie_device(dev);
driver = to_service_driver(dev->driver);
if (driver && driver->resume)
driver->resume(pciedev);
return 0;
}

View File

@@ -0,0 +1,432 @@
/*
* File: portdrv_core.c
* Purpose: PCI Express Port Bus Driver's Core Functions
*
* Copyright (C) 2004 Intel
* Copyright (C) Tom Long Nguyen (tom.l.nguyen@intel.com)
*/
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/pm.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/pcieport_if.h>
#include "portdrv.h"
extern int pcie_mch_quirk; /* MSI-quirk Indicator */
static int pcie_port_probe_service(struct device *dev)
{
struct pcie_device *pciedev;
struct pcie_port_service_driver *driver;
int status = -ENODEV;
if (!dev || !dev->driver)
return status;
driver = to_service_driver(dev->driver);
if (!driver || !driver->probe)
return status;
pciedev = to_pcie_device(dev);
status = driver->probe(pciedev, driver->id_table);
if (!status) {
printk(KERN_DEBUG "Load service driver %s on pcie device %s\n",
driver->name, dev->bus_id);
get_device(dev);
}
return status;
}
static int pcie_port_remove_service(struct device *dev)
{
struct pcie_device *pciedev;
struct pcie_port_service_driver *driver;
if (!dev || !dev->driver)
return 0;
pciedev = to_pcie_device(dev);
driver = to_service_driver(dev->driver);
if (driver && driver->remove) {
printk(KERN_DEBUG "Unload service driver %s on pcie device %s\n",
driver->name, dev->bus_id);
driver->remove(pciedev);
put_device(dev);
}
return 0;
}
static void pcie_port_shutdown_service(struct device *dev) {}
static int pcie_port_suspend_service(struct device *dev, pm_message_t state)
{
struct pcie_device *pciedev;
struct pcie_port_service_driver *driver;
if (!dev || !dev->driver)
return 0;
pciedev = to_pcie_device(dev);
driver = to_service_driver(dev->driver);
if (driver && driver->suspend)
driver->suspend(pciedev, state);
return 0;
}
static int pcie_port_resume_service(struct device *dev)
{
struct pcie_device *pciedev;
struct pcie_port_service_driver *driver;
if (!dev || !dev->driver)
return 0;
pciedev = to_pcie_device(dev);
driver = to_service_driver(dev->driver);
if (driver && driver->resume)
driver->resume(pciedev);
return 0;
}
/*
* release_pcie_device
*
* Being invoked automatically when device is being removed
* in response to device_unregister(dev) call.
* Release all resources being claimed.
*/
static void release_pcie_device(struct device *dev)
{
printk(KERN_DEBUG "Free Port Service[%s]\n", dev->bus_id);
kfree(to_pcie_device(dev));
}
static int is_msi_quirked(struct pci_dev *dev)
{
int port_type, quirk = 0;
u16 reg16;
pci_read_config_word(dev,
pci_find_capability(dev, PCI_CAP_ID_EXP) +
PCIE_CAPABILITIES_REG, &reg16);
port_type = (reg16 >> 4) & PORT_TYPE_MASK;
switch(port_type) {
case PCIE_RC_PORT:
if (pcie_mch_quirk == 1)
quirk = 1;
break;
case PCIE_SW_UPSTREAM_PORT:
case PCIE_SW_DOWNSTREAM_PORT:
default:
break;
}
return quirk;
}
static int assign_interrupt_mode(struct pci_dev *dev, int *vectors, int mask)
{
int i, pos, nvec, status = -EINVAL;
int interrupt_mode = PCIE_PORT_INTx_MODE;
/* Set INTx as default */
for (i = 0, nvec = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++) {
if (mask & (1 << i))
nvec++;
vectors[i] = dev->irq;
}
/* Check MSI quirk */
if (is_msi_quirked(dev))
return interrupt_mode;
/* Select MSI-X over MSI if supported */
pos = pci_find_capability(dev, PCI_CAP_ID_MSIX);
if (pos) {
struct msix_entry msix_entries[PCIE_PORT_DEVICE_MAXSERVICES] =
{{0, 0}, {0, 1}, {0, 2}, {0, 3}};
printk("%s Found MSIX capability\n", __FUNCTION__);
status = pci_enable_msix(dev, msix_entries, nvec);
if (!status) {
int j = 0;
interrupt_mode = PCIE_PORT_MSIX_MODE;
for (i = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++) {
if (mask & (1 << i))
vectors[i] = msix_entries[j++].vector;
}
}
}
if (status) {
pos = pci_find_capability(dev, PCI_CAP_ID_MSI);
if (pos) {
printk("%s Found MSI capability\n", __FUNCTION__);
status = pci_enable_msi(dev);
if (!status) {
interrupt_mode = PCIE_PORT_MSI_MODE;
for (i = 0;i < PCIE_PORT_DEVICE_MAXSERVICES;i++)
vectors[i] = dev->irq;
}
}
}
return interrupt_mode;
}
static int get_port_device_capability(struct pci_dev *dev)
{
int services = 0, pos;
u16 reg16;
u32 reg32;
pos = pci_find_capability(dev, PCI_CAP_ID_EXP);
pci_read_config_word(dev, pos + PCIE_CAPABILITIES_REG, &reg16);
/* Hot-Plug Capable */
if (reg16 & PORT_TO_SLOT_MASK) {
pci_read_config_dword(dev,
pos + PCIE_SLOT_CAPABILITIES_REG, &reg32);
if (reg32 & SLOT_HP_CAPABLE_MASK)
services |= PCIE_PORT_SERVICE_HP;
}
/* PME Capable */
pos = pci_find_capability(dev, PCI_CAP_ID_PME);
if (pos)
services |= PCIE_PORT_SERVICE_PME;
pos = PCI_CFG_SPACE_SIZE;
while (pos) {
pci_read_config_dword(dev, pos, &reg32);
switch (reg32 & 0xffff) {
case PCI_EXT_CAP_ID_ERR:
services |= PCIE_PORT_SERVICE_AER;
pos = reg32 >> 20;
break;
case PCI_EXT_CAP_ID_VC:
services |= PCIE_PORT_SERVICE_VC;
pos = reg32 >> 20;
break;
default:
pos = 0;
break;
}
}
return services;
}
static void pcie_device_init(struct pci_dev *parent, struct pcie_device *dev,
int port_type, int service_type, int irq, int irq_mode)
{
struct device *device;
dev->port = parent;
dev->interrupt_mode = irq_mode;
dev->irq = irq;
dev->id.vendor = parent->vendor;
dev->id.device = parent->device;
dev->id.port_type = port_type;
dev->id.service_type = (1 << service_type);
/* Initialize generic device interface */
device = &dev->device;
memset(device, 0, sizeof(struct device));
device->bus = &pcie_port_bus_type;
device->driver = NULL;
device->driver_data = NULL;
device->release = release_pcie_device; /* callback to free pcie dev */
snprintf(device->bus_id, sizeof(device->bus_id), "%s:pcie%02x",
pci_name(parent), get_descriptor_id(port_type, service_type));
device->parent = &parent->dev;
}
static struct pcie_device* alloc_pcie_device(struct pci_dev *parent,
int port_type, int service_type, int irq, int irq_mode)
{
struct pcie_device *device;
device = kzalloc(sizeof(struct pcie_device), GFP_KERNEL);
if (!device)
return NULL;
pcie_device_init(parent, device, port_type, service_type, irq,irq_mode);
printk(KERN_DEBUG "Allocate Port Service[%s]\n", device->device.bus_id);
return device;
}
int pcie_port_device_probe(struct pci_dev *dev)
{
int pos, type;
u16 reg;
if (!(pos = pci_find_capability(dev, PCI_CAP_ID_EXP)))
return -ENODEV;
pci_read_config_word(dev, pos + PCIE_CAPABILITIES_REG, &reg);
type = (reg >> 4) & PORT_TYPE_MASK;
if ( type == PCIE_RC_PORT || type == PCIE_SW_UPSTREAM_PORT ||
type == PCIE_SW_DOWNSTREAM_PORT )
return 0;
return -ENODEV;
}
int pcie_port_device_register(struct pci_dev *dev)
{
struct pcie_port_device_ext *p_ext;
int status, type, capabilities, irq_mode, i;
int vectors[PCIE_PORT_DEVICE_MAXSERVICES];
u16 reg16;
/* Allocate port device extension */
if (!(p_ext = kmalloc(sizeof(struct pcie_port_device_ext), GFP_KERNEL)))
return -ENOMEM;
pci_set_drvdata(dev, p_ext);
/* Get port type */
pci_read_config_word(dev,
pci_find_capability(dev, PCI_CAP_ID_EXP) +
PCIE_CAPABILITIES_REG, &reg16);
type = (reg16 >> 4) & PORT_TYPE_MASK;
/* Now get port services */
capabilities = get_port_device_capability(dev);
irq_mode = assign_interrupt_mode(dev, vectors, capabilities);
p_ext->interrupt_mode = irq_mode;
/* Allocate child services if any */
for (i = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++) {
struct pcie_device *child;
if (capabilities & (1 << i)) {
child = alloc_pcie_device(
dev, /* parent */
type, /* port type */
i, /* service type */
vectors[i], /* irq */
irq_mode /* interrupt mode */);
if (child) {
status = device_register(&child->device);
if (status) {
kfree(child);
continue;
}
get_device(&child->device);
}
}
}
return 0;
}
#ifdef CONFIG_PM
static int suspend_iter(struct device *dev, void *data)
{
struct pcie_port_service_driver *service_driver;
pm_message_t state = * (pm_message_t *) data;
if ((dev->bus == &pcie_port_bus_type) &&
(dev->driver)) {
service_driver = to_service_driver(dev->driver);
if (service_driver->suspend)
service_driver->suspend(to_pcie_device(dev), state);
}
return 0;
}
int pcie_port_device_suspend(struct pci_dev *dev, pm_message_t state)
{
return device_for_each_child(&dev->dev, &state, suspend_iter);
}
static int resume_iter(struct device *dev, void *data)
{
struct pcie_port_service_driver *service_driver;
if ((dev->bus == &pcie_port_bus_type) &&
(dev->driver)) {
service_driver = to_service_driver(dev->driver);
if (service_driver->resume)
service_driver->resume(to_pcie_device(dev));
}
return 0;
}
int pcie_port_device_resume(struct pci_dev *dev)
{
return device_for_each_child(&dev->dev, NULL, resume_iter);
}
#endif
static int remove_iter(struct device *dev, void *data)
{
struct pcie_port_service_driver *service_driver;
if (dev->bus == &pcie_port_bus_type) {
if (dev->driver) {
service_driver = to_service_driver(dev->driver);
if (service_driver->remove)
service_driver->remove(to_pcie_device(dev));
}
*(unsigned long*)data = (unsigned long)dev;
return 1;
}
return 0;
}
void pcie_port_device_remove(struct pci_dev *dev)
{
struct device *device;
unsigned long device_addr;
int interrupt_mode = PCIE_PORT_INTx_MODE;
int status;
do {
status = device_for_each_child(&dev->dev, &device_addr, remove_iter);
if (status) {
device = (struct device*)device_addr;
interrupt_mode = (to_pcie_device(device))->interrupt_mode;
put_device(device);
device_unregister(device);
}
} while (status);
/* Switch to INTx by default if MSI enabled */
if (interrupt_mode == PCIE_PORT_MSIX_MODE)
pci_disable_msix(dev);
else if (interrupt_mode == PCIE_PORT_MSI_MODE)
pci_disable_msi(dev);
}
int pcie_port_bus_register(void)
{
return bus_register(&pcie_port_bus_type);
}
void pcie_port_bus_unregister(void)
{
bus_unregister(&pcie_port_bus_type);
}
int pcie_port_service_register(struct pcie_port_service_driver *new)
{
new->driver.name = (char *)new->name;
new->driver.bus = &pcie_port_bus_type;
new->driver.probe = pcie_port_probe_service;
new->driver.remove = pcie_port_remove_service;
new->driver.shutdown = pcie_port_shutdown_service;
new->driver.suspend = pcie_port_suspend_service;
new->driver.resume = pcie_port_resume_service;
return driver_register(&new->driver);
}
void pcie_port_service_unregister(struct pcie_port_service_driver *new)
{
driver_unregister(&new->driver);
}
EXPORT_SYMBOL(pcie_port_service_register);
EXPORT_SYMBOL(pcie_port_service_unregister);

View File

@@ -0,0 +1,315 @@
/*
* File: portdrv_pci.c
* Purpose: PCI Express Port Bus Driver
*
* Copyright (C) 2004 Intel
* Copyright (C) Tom Long Nguyen (tom.l.nguyen@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/slab.h>
#include <linux/pcieport_if.h>
#include <linux/aer.h>
#include "portdrv.h"
#include "aer/aerdrv.h"
/*
* Version Information
*/
#define DRIVER_VERSION "v1.0"
#define DRIVER_AUTHOR "tom.l.nguyen@intel.com"
#define DRIVER_DESC "PCIE Port Bus Driver"
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
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);
}
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;
}
#ifdef CONFIG_PM
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
*
* If detected invokes the pcie_port_device_register() method for
* this port device.
*
*/
static int __devinit pcie_portdrv_probe (struct pci_dev *dev,
const struct pci_device_id *id )
{
int status;
status = pcie_port_device_probe(dev);
if (status)
return status;
if (pci_enable_device(dev) < 0)
return -ENODEV;
pci_set_master(dev);
if (!dev->irq && dev->pin) {
printk(KERN_WARNING
"%s->Dev[%04x:%04x] has invalid IRQ. Check vendor BIOS\n",
__FUNCTION__, dev->vendor, dev->device);
}
if (pcie_port_device_register(dev)) {
pci_disable_device(dev);
return -ENOMEM;
}
pcie_portdrv_save_config(dev);
pci_enable_pcie_error_reporting(dev);
return 0;
}
static void pcie_portdrv_remove (struct pci_dev *dev)
{
pcie_port_device_remove(dev);
kfree(pci_get_drvdata(dev));
}
static int error_detected_iter(struct device *device, void *data)
{
struct pcie_device *pcie_device;
struct pcie_port_service_driver *driver;
struct aer_broadcast_data *result_data;
pci_ers_result_t status;
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);
}
return 0;
}
static pci_ers_result_t pcie_portdrv_error_detected(struct pci_dev *dev,
enum pci_channel_state error)
{
struct aer_broadcast_data result_data =
{error, PCI_ERS_RESULT_CAN_RECOVER};
int retval;
/* can not fail */
retval = device_for_each_child(&dev->dev, &result_data, error_detected_iter);
return result_data.result;
}
static int mmio_enabled_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->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);
}
/*
* LINUX Device Driver Model
*/
static const struct pci_device_id port_pci_ids[] = { {
/* handle any PCI-Express port */
PCI_DEVICE_CLASS(((PCI_CLASS_BRIDGE_PCI << 8) | 0x00), ~0),
}, { /* end: all zeroes */ }
};
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_portdriver = {
.name = (char *)device_name,
.id_table = &port_pci_ids[0],
.probe = pcie_portdrv_probe,
.remove = pcie_portdrv_remove,
.suspend = pcie_portdrv_suspend,
.resume = pcie_portdrv_resume,
.err_handler = &pcie_portdrv_err_handler,
};
static int __init pcie_portdrv_init(void)
{
int retval;
retval = pcie_port_bus_register();
if (retval) {
printk(KERN_WARNING "PCIE: bus_register error: %d\n", retval);
goto out;
}
retval = pci_register_driver(&pcie_portdriver);
if (retval)
pcie_port_bus_unregister();
out:
return retval;
}
static void __exit pcie_portdrv_exit(void)
{
pci_unregister_driver(&pcie_portdriver);
pcie_port_bus_unregister();
}
module_init(pcie_portdrv_init);
module_exit(pcie_portdrv_exit);