2005-04-16 15:20:36 -07:00
/*
* 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>
2005-10-30 15:03:48 -08:00
# include <linux/string.h>
# include <linux/slab.h>
2005-04-16 15:20:36 -07:00
# include <linux/pcieport_if.h>
# include "portdrv.h"
extern int pcie_mch_quirk ; /* MSI-quirk Indicator */
2009-01-01 19:48:55 +01:00
/**
* release_pcie_device - free PCI Express port service device structure
* @ dev : Port service device to release
*
* Invoked automatically when device is being removed in response to
* device_unregister ( dev ) . Release all resources being claimed .
2005-04-16 15:20:36 -07:00
*/
static void release_pcie_device ( struct device * dev )
{
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 ;
}
2009-01-01 19:48:55 +01:00
/**
* assign_interrupt_mode - choose interrupt mode for PCI Express port services
* ( INTx , MSI - X , MSI ) and set up vectors
* @ dev : PCI Express port to handle
* @ vectors : Array of interrupt vectors to populate
* @ mask : Bitmask of port capabilities returned by get_port_device_capability ( )
*
* Return value : Interrupt mode associated with the port
*/
2005-04-16 15:20:36 -07:00
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 } } ;
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 ) {
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 ;
}
2009-01-01 19:48:55 +01:00
/**
* get_port_device_capability - discover capabilities of a PCI Express port
* @ dev : PCI Express port to examine
*
* The capabilities are read from the port ' s PCI Express configuration registers
* as described in PCI Express Base Specification 1.0 a sections 7.8 .2 , 7.8 .9 and
* 7.9 - 7.11 .
*
* Return value : Bitmask of discovered port capabilities
*/
2005-04-16 15:20:36 -07:00
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 ;
}
2007-10-24 10:43:23 +08:00
/* PME Capable - root port capability */
if ( ( ( reg16 > > 4 ) & PORT_TYPE_MASK ) = = PCIE_RC_PORT )
2005-04-16 15:20:36 -07:00
services | = PCIE_PORT_SERVICE_PME ;
2008-10-18 17:33:19 -07:00
if ( pci_find_ext_capability ( dev , PCI_EXT_CAP_ID_ERR ) )
services | = PCIE_PORT_SERVICE_AER ;
if ( pci_find_ext_capability ( dev , PCI_EXT_CAP_ID_VC ) )
services | = PCIE_PORT_SERVICE_VC ;
2005-04-16 15:20:36 -07:00
return services ;
}
2009-01-01 19:48:55 +01:00
/**
* pcie_device_init - initialize PCI Express port service device
* @ dev : Port service device to initialize
* @ parent : PCI Express port to associate the service device with
* @ port_type : Type of the port
* @ service_type : Type of service to associate with the service device
* @ irq : Interrupt vector to associate with the service device
* @ irq_mode : Interrupt mode of the service ( INTx , MSI - X , MSI )
*/
2005-04-16 15:20:36 -07:00
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 ;
2005-03-29 13:36:43 -08:00
device - > driver_data = NULL ;
2005-04-16 15:20:36 -07:00
device - > release = release_pcie_device ; /* callback to free pcie dev */
2008-10-30 02:17:49 +01:00
dev_set_name ( device , " %s:pcie%02x " ,
2005-11-14 20:30:50 +03:00
pci_name ( parent ) , get_descriptor_id ( port_type , service_type ) ) ;
2005-04-16 15:20:36 -07:00
device - > parent = & parent - > dev ;
}
2009-01-01 19:48:55 +01:00
/**
* alloc_pcie_device - allocate PCI Express port service device structure
* @ parent : PCI Express port to associate the service device with
* @ port_type : Type of the port
* @ service_type : Type of service to associate with the service device
* @ irq : Interrupt vector to associate with the service device
* @ irq_mode : Interrupt mode of the service ( INTx , MSI - X , MSI )
*/
2005-03-29 13:36:43 -08:00
static struct pcie_device * alloc_pcie_device ( struct pci_dev * parent ,
2005-04-16 15:20:36 -07:00
int port_type , int service_type , int irq , int irq_mode )
{
struct pcie_device * device ;
2006-02-28 15:34:49 +01:00
device = kzalloc ( sizeof ( struct pcie_device ) , GFP_KERNEL ) ;
2005-04-16 15:20:36 -07:00
if ( ! device )
return NULL ;
pcie_device_init ( parent , device , port_type , service_type , irq , irq_mode ) ;
return device ;
}
2009-01-01 19:48:55 +01:00
/**
* pcie_port_device_probe - check if device is a PCI Express port
* @ dev : Device to check
*/
2005-04-16 15:20:36 -07:00
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 | |
2005-03-29 13:36:43 -08:00
type = = PCIE_SW_DOWNSTREAM_PORT )
2005-04-16 15:20:36 -07:00
return 0 ;
2005-03-29 13:36:43 -08:00
2005-04-16 15:20:36 -07:00
return - ENODEV ;
}
2009-01-01 19:48:55 +01:00
/**
* pcie_port_device_register - register PCI Express port
* @ dev : PCI Express port to register
*
* Allocate the port extension structure and register services associated with
* the port .
*/
2005-04-16 15:20:36 -07:00
int pcie_port_device_register ( struct pci_dev * dev )
{
2005-06-22 09:09:54 -07:00
struct pcie_port_device_ext * p_ext ;
2005-04-16 15:20:36 -07:00
int status , type , capabilities , irq_mode , i ;
int vectors [ PCIE_PORT_DEVICE_MAXSERVICES ] ;
u16 reg16 ;
2005-06-22 09:09:54 -07:00
/* Allocate port device extension */
if ( ! ( p_ext = kmalloc ( sizeof ( struct pcie_port_device_ext ) , GFP_KERNEL ) ) )
return - ENOMEM ;
pci_set_drvdata ( dev , p_ext ) ;
2005-04-16 15:20:36 -07:00
/* Get port type */
2005-03-29 13:36:43 -08:00
pci_read_config_word ( dev ,
pci_find_capability ( dev , PCI_CAP_ID_EXP ) +
2005-04-16 15:20:36 -07:00
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 ) ;
2005-06-22 09:09:54 -07:00
p_ext - > interrupt_mode = irq_mode ;
2005-04-16 15:20:36 -07:00
/* 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 */
2005-03-29 13:36:43 -08:00
type , /* port type */
2005-04-16 15:20:36 -07:00
i , /* service type */
vectors [ i ] , /* irq */
irq_mode /* interrupt mode */ ) ;
2005-03-29 13:36:43 -08:00
if ( child ) {
2005-04-16 15:20:36 -07:00
status = device_register ( & child - > device ) ;
if ( status ) {
kfree ( child ) ;
continue ;
}
get_device ( & child - > device ) ;
}
}
}
return 0 ;
}
# ifdef CONFIG_PM
2005-03-29 13:36:43 -08:00
static int suspend_iter ( struct device * dev , void * data )
2005-04-16 15:20:36 -07:00
{
struct pcie_port_service_driver * service_driver ;
2005-07-07 17:56:40 -07:00
pm_message_t state = * ( pm_message_t * ) data ;
2005-03-29 13:36:43 -08:00
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 ;
}
2005-04-16 15:20:36 -07:00
2009-01-01 19:48:55 +01:00
/**
* pcie_port_device_suspend - suspend port services associated with a PCIe port
* @ dev : PCI Express port to handle
* @ state : Representation of system power management transition in progress
*/
2005-07-07 17:56:40 -07:00
int pcie_port_device_suspend ( struct pci_dev * dev , pm_message_t state )
2005-03-29 13:36:43 -08:00
{
2006-08-28 11:43:25 -07:00
return device_for_each_child ( & dev - > dev , & state , suspend_iter ) ;
2005-04-16 15:20:36 -07:00
}
2005-03-29 13:36:43 -08:00
static int resume_iter ( struct device * dev , void * data )
{
2005-04-16 15:20:36 -07:00
struct pcie_port_service_driver * service_driver ;
2005-03-29 13:36:43 -08:00
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 ) ) ;
2005-04-16 15:20:36 -07:00
}
2005-03-29 13:36:43 -08:00
return 0 ;
}
2005-04-16 15:20:36 -07:00
2009-01-01 19:48:55 +01:00
/**
* pcie_port_device_suspend - resume port services associated with a PCIe port
* @ dev : PCI Express port to handle
*/
2005-03-29 13:36:43 -08:00
int pcie_port_device_resume ( struct pci_dev * dev )
{
2006-08-28 11:43:25 -07:00
return device_for_each_child ( & dev - > dev , NULL , resume_iter ) ;
2005-04-16 15:20:36 -07:00
}
# endif
2005-03-29 13:36:43 -08:00
static int remove_iter ( struct device * dev , void * data )
2005-04-16 15:20:36 -07:00
{
struct pcie_port_service_driver * service_driver ;
2005-03-29 13:36:43 -08:00
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 ) ) ;
2005-04-16 15:20:36 -07:00
}
2005-03-29 13:36:43 -08:00
* ( unsigned long * ) data = ( unsigned long ) dev ;
return 1 ;
2005-04-16 15:20:36 -07:00
}
2005-03-29 13:36:43 -08:00
return 0 ;
}
2009-01-01 19:48:55 +01:00
/**
* pcie_port_device_remove - unregister PCI Express port service devices
* @ dev : PCI Express port the service devices to unregister are associated with
*
* Remove PCI Express port service devices associated with given port and
* disable MSI - X or MSI for the port .
*/
2005-03-29 13:36:43 -08:00
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 ) ;
2005-04-16 15:20:36 -07:00
/* 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 ) ;
}
2009-01-01 19:52:12 +01:00
static int pcie_port_probe_service ( struct device * dev )
2005-04-16 15:20:36 -07:00
{
2009-01-01 19:52:12 +01:00
struct pcie_device * pciedev ;
struct pcie_port_service_driver * driver ;
int status ;
if ( ! dev | | ! dev - > driver )
return - ENODEV ;
driver = to_service_driver ( dev - > driver ) ;
if ( ! driver | | ! driver - > probe )
return - ENODEV ;
pciedev = to_pcie_device ( dev ) ;
status = driver - > probe ( pciedev , driver - > id_table ) ;
if ( ! status ) {
dev_printk ( KERN_DEBUG , dev , " service driver %s loaded \n " ,
driver - > name ) ;
get_device ( dev ) ;
}
return status ;
2005-04-16 15:20:36 -07:00
}
2009-01-01 19:52:12 +01:00
static int pcie_port_remove_service ( struct device * dev )
2005-04-16 15:20:36 -07:00
{
2009-01-01 19:52:12 +01:00
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 ) {
dev_printk ( KERN_DEBUG , dev , " unloading service driver %s \n " ,
driver - > name ) ;
driver - > remove ( pciedev ) ;
put_device ( dev ) ;
}
return 0 ;
2005-04-16 15:20:36 -07:00
}
2009-01-01 19:52:12 +01:00
static void pcie_port_shutdown_service ( struct device * dev ) { }
2005-04-16 15:20:36 -07:00
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 ;
return driver_register ( & new - > driver ) ;
2005-03-29 13:36:43 -08:00
}
2005-04-16 15:20:36 -07:00
2009-01-01 19:52:12 +01:00
void pcie_port_service_unregister ( struct pcie_port_service_driver * drv )
2005-04-16 15:20:36 -07:00
{
2009-01-01 19:52:12 +01:00
driver_unregister ( & drv - > driver ) ;
2005-04-16 15:20:36 -07:00
}
EXPORT_SYMBOL ( pcie_port_service_register ) ;
EXPORT_SYMBOL ( pcie_port_service_unregister ) ;