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>
2009-01-13 14:38:34 +01:00
# include "../pci.h"
2005-04-16 15:20:36 -07:00
# include "portdrv.h"
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 ) ) ;
}
2009-01-24 00:23:22 +01:00
/**
* pcie_port_msix_add_entry - add entry to given array of MSI - X entries
* @ entries : Array of MSI - X entries
* @ new_entry : Index of the entry to add to the array
* @ nr_entries : Number of entries aleady in the array
*
* Return value : Position of the added entry in the array
*/
static int pcie_port_msix_add_entry (
struct msix_entry * entries , int new_entry , int nr_entries )
{
int j ;
for ( j = 0 ; j < nr_entries ; j + + )
if ( entries [ j ] . entry = = new_entry )
return j ;
entries [ j ] . entry = new_entry ;
return j ;
}
/**
* pcie_port_enable_msix - try to set up MSI - X as interrupt mode for given port
* @ 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 : 0 on success , error code on failure
*/
static int pcie_port_enable_msix ( struct pci_dev * dev , int * vectors , int mask )
{
struct msix_entry * msix_entries ;
int idx [ PCIE_PORT_DEVICE_MAXSERVICES ] ;
int nr_entries , status , pos , i , nvec ;
u16 reg16 ;
u32 reg32 ;
nr_entries = pci_msix_table_size ( dev ) ;
if ( ! nr_entries )
return - EINVAL ;
if ( nr_entries > PCIE_PORT_MAX_MSIX_ENTRIES )
nr_entries = PCIE_PORT_MAX_MSIX_ENTRIES ;
msix_entries = kzalloc ( sizeof ( * msix_entries ) * nr_entries , GFP_KERNEL ) ;
if ( ! msix_entries )
return - ENOMEM ;
/*
* Allocate as many entries as the port wants , so that we can check
* which of them will be useful . Moreover , if nr_entries is correctly
* equal to the number of entries this port actually uses , we ' ll happily
* go through without any tricks .
*/
for ( i = 0 ; i < nr_entries ; i + + )
msix_entries [ i ] . entry = i ;
status = pci_enable_msix ( dev , msix_entries , nr_entries ) ;
if ( status )
goto Exit ;
for ( i = 0 ; i < PCIE_PORT_DEVICE_MAXSERVICES ; i + + )
idx [ i ] = - 1 ;
status = - EIO ;
nvec = 0 ;
if ( mask & ( PCIE_PORT_SERVICE_PME | PCIE_PORT_SERVICE_HP ) ) {
int entry ;
/*
* The code below follows the PCI Express Base Specification 2.0
* stating in Section 6.1 .6 that " PME and Hot-Plug Event
* interrupts ( when both are implemented ) always share the same
* MSI or MSI - X vector , as indicated by the Interrupt Message
* Number field in the PCI Express Capabilities register " , where
* according to Section 7.8 .2 of the specification " For MSI-X,
* the value in this field indicates which MSI - X Table entry is
* used to generate the interrupt message . "
*/
2009-11-11 14:32:42 +09:00
pos = pci_pcie_cap ( dev ) ;
2009-01-24 00:23:22 +01:00
pci_read_config_word ( dev , pos + PCIE_CAPABILITIES_REG , & reg16 ) ;
entry = ( reg16 > > 9 ) & PCIE_PORT_MSI_VECTOR_MASK ;
if ( entry > = nr_entries )
goto Error ;
i = pcie_port_msix_add_entry ( msix_entries , entry , nvec ) ;
if ( i = = nvec )
nvec + + ;
idx [ PCIE_PORT_SERVICE_PME_SHIFT ] = i ;
idx [ PCIE_PORT_SERVICE_HP_SHIFT ] = i ;
}
if ( mask & PCIE_PORT_SERVICE_AER ) {
int entry ;
/*
* The code below follows Section 7.10 .10 of the PCI Express
* Base Specification 2.0 stating that bits 31 - 27 of the Root
* Error Status Register contain a value indicating which of the
* MSI / MSI - X vectors assigned to the port is going to be used
* for AER , where " For MSI-X, the value in this register
* indicates which MSI - X Table entry is used to generate the
* interrupt message . "
*/
pos = pci_find_ext_capability ( dev , PCI_EXT_CAP_ID_ERR ) ;
pci_read_config_dword ( dev , pos + PCI_ERR_ROOT_STATUS , & reg32 ) ;
entry = reg32 > > 27 ;
if ( entry > = nr_entries )
goto Error ;
i = pcie_port_msix_add_entry ( msix_entries , entry , nvec ) ;
if ( i = = nvec )
nvec + + ;
idx [ PCIE_PORT_SERVICE_AER_SHIFT ] = i ;
}
/*
* If nvec is equal to the allocated number of entries , we can just use
* what we have . Otherwise , the port has some extra entries not for the
* services we know and we need to work around that .
*/
if ( nvec = = nr_entries ) {
status = 0 ;
} else {
/* Drop the temporary MSI-X setup */
pci_disable_msix ( dev ) ;
/* Now allocate the MSI-X vectors for real */
status = pci_enable_msix ( dev , msix_entries , nvec ) ;
if ( status )
goto Exit ;
}
for ( i = 0 ; i < PCIE_PORT_DEVICE_MAXSERVICES ; i + + )
vectors [ i ] = idx [ i ] > = 0 ? msix_entries [ idx [ i ] ] . vector : - 1 ;
Exit :
kfree ( msix_entries ) ;
return status ;
Error :
pci_disable_msix ( dev ) ;
goto Exit ;
}
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 )
{
2009-01-24 00:23:22 +01:00
int irq , interrupt_mode = PCIE_PORT_NO_IRQ ;
int i ;
2009-01-13 14:39:39 +01:00
2009-01-24 00:23:22 +01:00
/* Try to use MSI-X if supported */
if ( ! pcie_port_enable_msix ( dev , vectors , mask ) )
return PCIE_PORT_MSIX_MODE ;
/* We're not going to use MSI-X, so try MSI and fall back to INTx */
if ( ! pci_enable_msi ( dev ) )
interrupt_mode = PCIE_PORT_MSI_MODE ;
if ( interrupt_mode = = PCIE_PORT_NO_IRQ & & dev - > pin )
interrupt_mode = PCIE_PORT_INTx_MODE ;
irq = interrupt_mode ! = PCIE_PORT_NO_IRQ ? dev - > irq : - 1 ;
for ( i = 0 ; i < PCIE_PORT_DEVICE_MAXSERVICES ; i + + )
vectors [ i ] = irq ;
vectors [ PCIE_PORT_SERVICE_VC_SHIFT ] = - 1 ;
2005-04-16 15:20:36 -07:00
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 ;
2009-11-11 14:32:42 +09:00
pos = pci_pcie_cap ( dev ) ;
2005-04-16 15:20:36 -07:00
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 ;
2009-01-13 14:38:34 +01:00
}
/* AER capable */
2008-10-18 17:33:19 -07:00
if ( pci_find_ext_capability ( dev , PCI_EXT_CAP_ID_ERR ) )
services | = PCIE_PORT_SERVICE_AER ;
2009-01-13 14:38:34 +01:00
/* VC support */
2008-10-18 17:33:19 -07:00
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
*/
2005-04-16 15:20:36 -07:00
static void pcie_device_init ( struct pci_dev * parent , struct pcie_device * dev ,
2009-01-13 14:38:34 +01:00
int service_type , int irq )
2005-04-16 15:20:36 -07:00
{
2009-01-13 14:38:34 +01:00
struct pcie_port_data * port_data = pci_get_drvdata ( parent ) ;
2005-04-16 15:20:36 -07:00
struct device * device ;
2009-01-13 14:38:34 +01:00
int port_type = port_data - > port_type ;
2005-04-16 15:20:36 -07:00
dev - > port = parent ;
dev - > irq = irq ;
2009-01-13 14:46:46 +01:00
dev - > service = service_type ;
2005-04-16 15:20:36 -07:00
/* Initialize generic device interface */
device = & dev - > device ;
memset ( device , 0 , sizeof ( struct device ) ) ;
device - > bus = & pcie_port_bus_type ;
device - > driver = NULL ;
2009-04-30 14:43:31 -07:00
dev_set_drvdata ( device , 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
*/
2005-03-29 13:36:43 -08:00
static struct pcie_device * alloc_pcie_device ( struct pci_dev * parent ,
2009-01-13 14:38:34 +01:00
int service_type , int irq )
2005-04-16 15:20:36 -07:00
{
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 ;
2009-01-13 14:38:34 +01:00
pcie_device_init ( parent , device , service_type , irq ) ;
2005-04-16 15:20:36 -07:00
return device ;
}
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 )
{
2009-01-13 14:38:34 +01:00
struct pcie_port_data * port_data ;
2009-11-11 14:32:42 +09:00
int status , capabilities , irq_mode , i , nr_serv , pos ;
2005-04-16 15:20:36 -07:00
int vectors [ PCIE_PORT_DEVICE_MAXSERVICES ] ;
u16 reg16 ;
2009-01-13 14:38:34 +01:00
port_data = kzalloc ( sizeof ( * port_data ) , GFP_KERNEL ) ;
if ( ! port_data )
2005-06-22 09:09:54 -07:00
return - ENOMEM ;
2009-01-13 14:38:34 +01:00
pci_set_drvdata ( dev , port_data ) ;
2005-06-22 09:09:54 -07:00
2005-04-16 15:20:36 -07:00
/* Get port type */
2009-11-11 14:32:42 +09:00
pos = pci_pcie_cap ( dev ) ;
pci_read_config_word ( dev , pos + PCIE_CAPABILITIES_REG , & reg16 ) ;
2009-01-13 14:38:34 +01:00
port_data - > port_type = ( reg16 > > 4 ) & PORT_TYPE_MASK ;
2005-04-16 15:20:36 -07:00
capabilities = get_port_device_capability ( dev ) ;
2009-01-13 14:38:34 +01:00
/* Root ports are capable of generating PME too */
if ( port_data - > port_type = = PCIE_RC_PORT )
capabilities | = PCIE_PORT_SERVICE_PME ;
2005-04-16 15:20:36 -07:00
irq_mode = assign_interrupt_mode ( dev , vectors , capabilities ) ;
2009-01-13 14:42:01 +01:00
if ( irq_mode = = PCIE_PORT_NO_IRQ ) {
/*
* Don ' t use service devices that require interrupts if there is
* no way to generate them .
*/
if ( ! ( capabilities & PCIE_PORT_SERVICE_VC ) ) {
status = - ENODEV ;
goto Error ;
}
capabilities = PCIE_PORT_SERVICE_VC ;
}
2009-01-13 14:38:34 +01:00
port_data - > port_irq_mode = irq_mode ;
2005-04-16 15:20:36 -07:00
2009-01-13 14:42:01 +01:00
status = pci_enable_device ( dev ) ;
if ( status )
goto Error ;
pci_set_master ( dev ) ;
2005-04-16 15:20:36 -07:00
/* Allocate child services if any */
2009-01-13 14:42:01 +01:00
for ( i = 0 , nr_serv = 0 ; i < PCIE_PORT_DEVICE_MAXSERVICES ; i + + ) {
2005-04-16 15:20:36 -07:00
struct pcie_device * child ;
2009-01-13 14:39:39 +01:00
int service = 1 < < i ;
2005-04-16 15:20:36 -07:00
2009-01-13 14:39:39 +01:00
if ( ! ( capabilities & service ) )
continue ;
child = alloc_pcie_device ( dev , service , vectors [ i ] ) ;
if ( ! child )
continue ;
status = device_register ( & child - > device ) ;
if ( status ) {
kfree ( child ) ;
continue ;
2005-04-16 15:20:36 -07:00
}
2009-01-13 14:39:39 +01:00
get_device ( & child - > device ) ;
2009-01-13 14:42:01 +01:00
nr_serv + + ;
}
if ( ! nr_serv ) {
pci_disable_device ( dev ) ;
status = - ENODEV ;
goto Error ;
2005-04-16 15:20:36 -07:00
}
2009-01-13 14:39:39 +01:00
2005-04-16 15:20:36 -07:00
return 0 ;
2009-01-13 14:42:01 +01:00
Error :
kfree ( port_data ) ;
return status ;
2005-04-16 15:20:36 -07:00
}
# 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-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 )
2009-02-15 22:32:48 +01:00
service_driver - > suspend ( to_pcie_device ( dev ) ) ;
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 - suspend port services associated with a PCIe port
* @ dev : PCI Express port to handle
*/
2009-02-15 22:32:48 +01:00
int pcie_port_device_suspend ( struct device * dev )
2005-03-29 13:36:43 -08:00
{
2009-02-15 22:32:48 +01:00
return device_for_each_child ( dev , NULL , 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
*/
2009-02-15 22:32:48 +01:00
int pcie_port_device_resume ( struct device * dev )
2005-03-29 13:36:43 -08:00
{
2009-02-15 22:32:48 +01:00
return device_for_each_child ( dev , NULL , resume_iter ) ;
2005-04-16 15:20:36 -07:00
}
2009-02-15 22:32:48 +01:00
# endif /* PM */
2005-04-16 15:20:36 -07:00
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
{
2005-03-29 13:36:43 -08:00
if ( dev - > bus = = & pcie_port_bus_type ) {
2009-02-20 20:16:07 -08:00
put_device ( dev ) ;
device_unregister ( dev ) ;
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 )
{
2009-01-13 14:38:34 +01:00
struct pcie_port_data * port_data = pci_get_drvdata ( dev ) ;
2005-03-29 13:36:43 -08:00
2009-02-20 20:16:07 -08:00
device_for_each_child ( & dev - > dev , NULL , remove_iter ) ;
2009-03-07 21:46:49 -07:00
pci_disable_device ( dev ) ;
2009-01-13 14:38:34 +01:00
switch ( port_data - > port_irq_mode ) {
case PCIE_PORT_MSIX_MODE :
2005-04-16 15:20:36 -07:00
pci_disable_msix ( dev ) ;
2009-01-13 14:38:34 +01:00
break ;
case PCIE_PORT_MSI_MODE :
2005-04-16 15:20:36 -07:00
pci_disable_msi ( dev ) ;
2009-01-13 14:38:34 +01:00
break ;
}
kfree ( port_data ) ;
2005-04-16 15:20:36 -07:00
}
2009-01-01 19:53:32 +01:00
/**
* pcie_port_probe_service - probe driver for given PCI Express port service
* @ dev : PCI Express port service device to probe against
*
* If PCI Express port service driver is registered with
* pcie_port_service_register ( ) , this function will be called by the driver core
* whenever match is found between the driver and a port service device .
*/
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 ) ;
2009-01-13 14:44:19 +01:00
status = driver - > probe ( pciedev ) ;
2009-01-01 19:52:12 +01:00
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:53:32 +01:00
/**
* pcie_port_remove_service - detach driver from given PCI Express port service
* @ dev : PCI Express port service device to handle
*
* If PCI Express port service driver is registered with
* pcie_port_service_register ( ) , this function will be called by the driver core
* when device_unregister ( ) is called for the port service device associated
* with the driver .
*/
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:53:32 +01:00
/**
* pcie_port_shutdown_service - shut down given PCI Express port service
* @ dev : PCI Express port service device to handle
*
* If PCI Express port service driver is registered with
* pcie_port_service_register ( ) , this function will be called by the driver core
* when device_shutdown ( ) is called for the port service device associated
* with the driver .
*/
2009-01-01 19:52:12 +01:00
static void pcie_port_shutdown_service ( struct device * dev ) { }
2009-01-01 19:53:32 +01:00
/**
* pcie_port_service_register - register PCI Express port service driver
* @ new : PCI Express port service driver to register
*/
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:53:32 +01:00
/**
* pcie_port_service_unregister - unregister PCI Express port service driver
* @ drv : PCI Express port service driver to unregister
*/
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 ) ;