2005-04-17 02:20:36 +04:00
/*
* 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>
2005-10-31 02:03:48 +03:00
# include <linux/slab.h>
2005-04-17 02:20:36 +04:00
# include <linux/pcieport_if.h>
2006-07-31 11:26:16 +04:00
# include <linux/aer.h>
2005-04-17 02:20:36 +04:00
# include "portdrv.h"
2006-07-31 11:26:16 +04:00
# include "aer/aerdrv.h"
2005-04-17 02:20:36 +04:00
/*
* Version Information
*/
# define DRIVER_VERSION "v1.0"
# define DRIVER_AUTHOR "tom.l.nguyen@intel.com"
2009-12-03 20:00:10 +03:00
# define DRIVER_DESC "PCIe Port Bus Driver"
2005-04-17 02:20:36 +04:00
MODULE_AUTHOR ( DRIVER_AUTHOR ) ;
MODULE_DESCRIPTION ( DRIVER_DESC ) ;
MODULE_LICENSE ( " GPL " ) ;
/* global data */
2006-07-31 11:26:16 +04:00
static int pcie_portdrv_restore_config ( struct pci_dev * dev )
{
int retval ;
retval = pci_enable_device ( dev ) ;
if ( retval )
return retval ;
pci_set_master ( dev ) ;
return 0 ;
}
2006-09-28 10:35:59 +04:00
# ifdef CONFIG_PM
2009-12-15 05:00:08 +03:00
static const struct dev_pm_ops pcie_portdrv_pm_ops = {
2009-02-16 00:32:48 +03:00
. suspend = pcie_port_device_suspend ,
. resume = pcie_port_device_resume ,
. freeze = pcie_port_device_suspend ,
. thaw = pcie_port_device_resume ,
. poweroff = pcie_port_device_suspend ,
. restore = pcie_port_device_resume ,
} ;
2006-07-31 11:26:16 +04:00
2009-02-16 00:32:48 +03:00
# define PCIE_PORTDRV_PM_OPS (&pcie_portdrv_pm_ops)
2008-12-27 18:28:58 +03:00
2009-02-16 00:32:48 +03:00
# else /* !PM */
# define PCIE_PORTDRV_PM_OPS NULL
# endif /* !PM */
2006-07-31 11:26:16 +04:00
2005-04-17 02:20:36 +04:00
/*
* 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 .
*
*/
2009-11-25 15:00:53 +03:00
static int __devinit pcie_portdrv_probe ( struct pci_dev * dev ,
const struct pci_device_id * id )
2005-04-17 02:20:36 +04:00
{
2009-11-25 15:00:53 +03:00
int status ;
2005-04-17 02:20:36 +04:00
2009-11-25 15:00:53 +03:00
if ( ! pci_is_pcie ( dev ) | |
( ( dev - > pcie_type ! = PCI_EXP_TYPE_ROOT_PORT ) & &
( dev - > pcie_type ! = PCI_EXP_TYPE_UPSTREAM ) & &
( dev - > pcie_type ! = PCI_EXP_TYPE_DOWNSTREAM ) ) )
return - ENODEV ;
2005-04-17 02:20:36 +04:00
2006-12-06 16:07:30 +03:00
if ( ! dev - > irq & & dev - > pin ) {
2008-08-26 01:45:20 +04:00
dev_warn ( & dev - > dev , " device [%04x:%04x] has invalid IRQ; "
2008-06-13 20:52:13 +04:00
" check vendor BIOS \n " , dev - > vendor , dev - > device ) ;
2005-04-17 02:20:36 +04:00
}
2009-01-13 16:42:01 +03:00
status = pcie_port_device_register ( dev ) ;
if ( status )
return status ;
2005-04-17 02:20:36 +04:00
2009-01-13 16:43:07 +03:00
pci_save_state ( dev ) ;
2006-07-31 11:26:16 +04:00
2005-04-17 02:20:36 +04:00
return 0 ;
}
static void pcie_portdrv_remove ( struct pci_dev * dev )
{
pcie_port_device_remove ( dev ) ;
2009-03-08 05:35:47 +03:00
pci_disable_device ( dev ) ;
2005-04-17 02:20:36 +04:00
}
2006-07-31 11:26:16 +04:00
static int error_detected_iter ( struct device * device , void * data )
2006-07-06 18:05:51 +04:00
{
2006-07-31 11:26:16 +04:00
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 ;
2006-07-06 18:05:51 +04:00
}
2006-07-31 11:26:16 +04:00
static pci_ers_result_t pcie_portdrv_error_detected ( struct pci_dev * dev ,
enum pci_channel_state error )
2006-07-06 18:05:51 +04:00
{
2006-07-31 11:26:16 +04:00
struct aer_broadcast_data result_data =
{ error , PCI_ERS_RESULT_CAN_RECOVER } ;
2006-08-28 22:43:25 +04:00
int retval ;
2006-07-31 11:26:16 +04:00
2006-08-28 22:43:25 +04:00
/* can not fail */
retval = device_for_each_child ( & dev - > dev , & result_data , error_detected_iter ) ;
2006-07-31 11:26:16 +04:00
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 ) ;
}
}
2006-07-06 18:05:51 +04:00
return 0 ;
}
2006-07-31 11:26:16 +04:00
static pci_ers_result_t pcie_portdrv_mmio_enabled ( struct pci_dev * dev )
2005-04-17 02:20:36 +04:00
{
2006-07-31 11:26:16 +04:00
pci_ers_result_t status = PCI_ERS_RESULT_RECOVERED ;
2006-08-28 22:43:25 +04:00
int retval ;
2005-06-22 20:09:54 +04:00
2006-08-28 22:43:25 +04:00
/* get true return value from &status */
retval = device_for_each_child ( & dev - > dev , & status , mmio_enabled_iter ) ;
2006-07-31 11:26:16 +04:00
return status ;
2005-04-17 02:20:36 +04:00
}
2006-07-31 11:26:16 +04:00
static int slot_reset_iter ( struct device * device , void * data )
2005-04-17 02:20:36 +04:00
{
2006-07-31 11:26:16 +04:00
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 )
{
2009-04-30 10:48:29 +04:00
pci_ers_result_t status = PCI_ERS_RESULT_RECOVERED ;
2006-08-28 22:43:25 +04:00
int retval ;
2006-07-31 11:26:16 +04:00
/* If fatal, restore cfg space for possible link reset at upstream */
if ( dev - > error_state = = pci_channel_io_frozen ) {
2009-09-15 00:25:11 +04:00
dev - > state_saved = true ;
2008-12-27 18:28:58 +03:00
pci_restore_state ( dev ) ;
2006-07-31 11:26:16 +04:00
pcie_portdrv_restore_config ( dev ) ;
pci_enable_pcie_error_reporting ( dev ) ;
}
2006-08-28 22:43:25 +04:00
/* get true return value from &status */
retval = device_for_each_child ( & dev - > dev , & status , slot_reset_iter ) ;
2006-07-31 11:26:16 +04:00
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 )
{
2006-08-28 22:43:25 +04:00
int retval ;
/* nothing to do with error value, if it ever happens */
retval = device_for_each_child ( & dev - > dev , NULL , resume_iter ) ;
2005-04-17 02:20:36 +04:00
}
/*
* 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 ) ;
2006-07-31 11:26:16 +04:00
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 ,
} ;
2007-02-27 12:19:17 +03:00
static struct pci_driver pcie_portdriver = {
2009-10-06 02:47:34 +04:00
. name = " pcieport " ,
2005-04-17 02:20:36 +04:00
. id_table = & port_pci_ids [ 0 ] ,
. probe = pcie_portdrv_probe ,
. remove = pcie_portdrv_remove ,
2006-07-31 11:26:16 +04:00
. err_handler = & pcie_portdrv_err_handler ,
2009-02-16 00:32:48 +03:00
. driver . pm = PCIE_PORTDRV_PM_OPS ,
2005-04-17 02:20:36 +04:00
} ;
static int __init pcie_portdrv_init ( void )
{
2006-07-09 09:58:25 +04:00
int retval ;
2005-04-17 02:20:36 +04:00
2006-07-09 09:58:25 +04:00
retval = pcie_port_bus_register ( ) ;
if ( retval ) {
printk ( KERN_WARNING " PCIE: bus_register error: %d \n " , retval ) ;
goto out ;
}
2007-02-27 12:19:17 +03:00
retval = pci_register_driver ( & pcie_portdriver ) ;
2005-04-17 02:20:36 +04:00
if ( retval )
pcie_port_bus_unregister ( ) ;
2006-07-09 09:58:25 +04:00
out :
2005-04-17 02:20:36 +04:00
return retval ;
}
static void __exit pcie_portdrv_exit ( void )
{
2007-02-27 12:19:17 +03:00
pci_unregister_driver ( & pcie_portdriver ) ;
2005-04-17 02:20:36 +04:00
pcie_port_bus_unregister ( ) ;
}
module_init ( pcie_portdrv_init ) ;
module_exit ( pcie_portdrv_exit ) ;