2005-04-17 02:20:36 +04:00
/*
* pci_irq . c - ACPI PCI Interrupt Routing ( $ Revision : 11 $ )
*
* Copyright ( C ) 2001 , 2002 Andy Grover < andrew . grover @ intel . com >
* Copyright ( C ) 2001 , 2002 Paul Diefenbaugh < paul . s . diefenbaugh @ intel . com >
* Copyright ( C ) 2002 Dominik Brodowski < devel @ brodo . de >
*
* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or ( at
* your option ) any later version .
*
* This program is distributed in the hope that it will be useful , but
* WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
* General Public License for more details .
*
* You should have received a copy of the GNU General Public License along
* with this program ; if not , write to the Free Software Foundation , Inc . ,
* 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA .
*
* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
*/
# include <linux/config.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/init.h>
# include <linux/types.h>
# include <linux/proc_fs.h>
# include <linux/spinlock.h>
# include <linux/pm.h>
# include <linux/pci.h>
# include <linux/acpi.h>
# include <acpi/acpi_bus.h>
# include <acpi/acpi_drivers.h>
# define _COMPONENT ACPI_PCI_COMPONENT
ACPI_MODULE_NAME ( " pci_irq " )
static struct acpi_prt_list acpi_prt ;
static DEFINE_SPINLOCK ( acpi_prt_lock ) ;
/* --------------------------------------------------------------------------
PCI IRQ Routing Table ( PRT ) Support
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
static struct acpi_prt_entry *
acpi_pci_irq_find_prt_entry (
int segment ,
int bus ,
int device ,
int pin )
{
struct list_head * node = NULL ;
struct acpi_prt_entry * entry = NULL ;
ACPI_FUNCTION_TRACE ( " acpi_pci_irq_find_prt_entry " ) ;
if ( ! acpi_prt . count )
return_PTR ( NULL ) ;
/*
* Parse through all PRT entries looking for a match on the specified
* PCI device ' s segment , bus , device , and pin ( don ' t care about func ) .
*
*/
spin_lock ( & acpi_prt_lock ) ;
list_for_each ( node , & acpi_prt . entries ) {
entry = list_entry ( node , struct acpi_prt_entry , node ) ;
if ( ( segment = = entry - > id . segment )
& & ( bus = = entry - > id . bus )
& & ( device = = entry - > id . device )
& & ( pin = = entry - > pin ) ) {
spin_unlock ( & acpi_prt_lock ) ;
return_PTR ( entry ) ;
}
}
spin_unlock ( & acpi_prt_lock ) ;
return_PTR ( NULL ) ;
}
static int
acpi_pci_irq_add_entry (
acpi_handle handle ,
int segment ,
int bus ,
struct acpi_pci_routing_table * prt )
{
struct acpi_prt_entry * entry = NULL ;
ACPI_FUNCTION_TRACE ( " acpi_pci_irq_add_entry " ) ;
if ( ! prt )
return_VALUE ( - EINVAL ) ;
entry = kmalloc ( sizeof ( struct acpi_prt_entry ) , GFP_KERNEL ) ;
if ( ! entry )
return_VALUE ( - ENOMEM ) ;
memset ( entry , 0 , sizeof ( struct acpi_prt_entry ) ) ;
entry - > id . segment = segment ;
entry - > id . bus = bus ;
entry - > id . device = ( prt - > address > > 16 ) & 0xFFFF ;
entry - > id . function = prt - > address & 0xFFFF ;
entry - > pin = prt - > pin ;
/*
* Type 1 : Dynamic
* - - - - - - - - - - - - - - -
* The ' source ' field specifies the PCI interrupt link device used to
* configure the IRQ assigned to this slot | dev | pin . The ' source_index '
* indicates which resource descriptor in the resource template ( of
* the link device ) this interrupt is allocated from .
*
* NOTE : Don ' t query the Link Device for IRQ information at this time
* because Link Device enumeration may not have occurred yet
* ( e . g . exists somewhere ' below ' this _PRT entry in the ACPI
* namespace ) .
*/
if ( prt - > source [ 0 ] ) {
acpi_get_handle ( handle , prt - > source , & entry - > link . handle ) ;
entry - > link . index = prt - > source_index ;
}
/*
* Type 2 : Static
* - - - - - - - - - - - - - -
* The ' source ' field is NULL , and the ' source_index ' field specifies
* the IRQ value , which is hardwired to specific interrupt inputs on
* the interrupt controller .
*/
else
entry - > link . index = prt - > source_index ;
ACPI_DEBUG_PRINT_RAW ( ( ACPI_DB_INFO ,
" %02X:%02X:%02X[%c] -> %s[%d] \n " ,
entry - > id . segment , entry - > id . bus , entry - > id . device ,
( ' A ' + entry - > pin ) , prt - > source , entry - > link . index ) ) ;
spin_lock ( & acpi_prt_lock ) ;
list_add_tail ( & entry - > node , & acpi_prt . entries ) ;
acpi_prt . count + + ;
spin_unlock ( & acpi_prt_lock ) ;
return_VALUE ( 0 ) ;
}
static void
acpi_pci_irq_del_entry (
int segment ,
int bus ,
struct acpi_prt_entry * entry )
{
if ( segment = = entry - > id . segment & & bus = = entry - > id . bus ) {
acpi_prt . count - - ;
list_del ( & entry - > node ) ;
kfree ( entry ) ;
}
}
int
acpi_pci_irq_add_prt (
acpi_handle handle ,
int segment ,
int bus )
{
acpi_status status = AE_OK ;
char * pathname = NULL ;
struct acpi_buffer buffer = { 0 , NULL } ;
struct acpi_pci_routing_table * prt = NULL ;
struct acpi_pci_routing_table * entry = NULL ;
static int first_time = 1 ;
ACPI_FUNCTION_TRACE ( " acpi_pci_irq_add_prt " ) ;
pathname = ( char * ) kmalloc ( ACPI_PATHNAME_MAX , GFP_KERNEL ) ;
if ( ! pathname )
return_VALUE ( - ENOMEM ) ;
memset ( pathname , 0 , ACPI_PATHNAME_MAX ) ;
if ( first_time ) {
acpi_prt . count = 0 ;
INIT_LIST_HEAD ( & acpi_prt . entries ) ;
first_time = 0 ;
}
/*
* NOTE : We ' re given a ' handle ' to the _PRT object ' s parent device
* ( either a PCI root bridge or PCI - PCI bridge ) .
*/
buffer . length = ACPI_PATHNAME_MAX ;
buffer . pointer = pathname ;
acpi_get_name ( handle , ACPI_FULL_PATHNAME , & buffer ) ;
printk ( KERN_DEBUG " ACPI: PCI Interrupt Routing Table [%s._PRT] \n " ,
pathname ) ;
/*
* Evaluate this _PRT and add its entries to our global list ( acpi_prt ) .
*/
buffer . length = 0 ;
buffer . pointer = NULL ;
kfree ( pathname ) ;
status = acpi_get_irq_routing_table ( handle , & buffer ) ;
if ( status ! = AE_BUFFER_OVERFLOW ) {
ACPI_DEBUG_PRINT ( ( ACPI_DB_ERROR , " Error evaluating _PRT [%s] \n " ,
acpi_format_exception ( status ) ) ) ;
return_VALUE ( - ENODEV ) ;
}
prt = kmalloc ( buffer . length , GFP_KERNEL ) ;
if ( ! prt ) {
return_VALUE ( - ENOMEM ) ;
}
memset ( prt , 0 , buffer . length ) ;
buffer . pointer = prt ;
status = acpi_get_irq_routing_table ( handle , & buffer ) ;
if ( ACPI_FAILURE ( status ) ) {
ACPI_DEBUG_PRINT ( ( ACPI_DB_ERROR , " Error evaluating _PRT [%s] \n " ,
acpi_format_exception ( status ) ) ) ;
kfree ( buffer . pointer ) ;
return_VALUE ( - ENODEV ) ;
}
entry = prt ;
while ( entry & & ( entry - > length > 0 ) ) {
acpi_pci_irq_add_entry ( handle , segment , bus , entry ) ;
entry = ( struct acpi_pci_routing_table * )
( ( unsigned long ) entry + entry - > length ) ;
}
kfree ( prt ) ;
return_VALUE ( 0 ) ;
}
void
acpi_pci_irq_del_prt ( int segment , int bus )
{
struct list_head * node = NULL , * n = NULL ;
struct acpi_prt_entry * entry = NULL ;
if ( ! acpi_prt . count ) {
return ;
}
printk ( KERN_DEBUG " ACPI: Delete PCI Interrupt Routing Table for %x:%x \n " ,
segment , bus ) ;
spin_lock ( & acpi_prt_lock ) ;
list_for_each_safe ( node , n , & acpi_prt . entries ) {
entry = list_entry ( node , struct acpi_prt_entry , node ) ;
acpi_pci_irq_del_entry ( segment , bus , entry ) ;
}
spin_unlock ( & acpi_prt_lock ) ;
}
/* --------------------------------------------------------------------------
PCI Interrupt Routing Support
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2005-07-28 07:02:00 +04:00
typedef int ( * irq_lookup_func ) ( struct acpi_prt_entry * , int * , int * , char * * ) ;
2005-04-17 02:20:36 +04:00
2005-07-28 07:02:00 +04:00
static int
acpi_pci_allocate_irq ( struct acpi_prt_entry * entry ,
int * edge_level ,
int * active_high_low ,
char * * link )
{
int irq ;
ACPI_FUNCTION_TRACE ( " acpi_pci_allocate_irq " ) ;
if ( entry - > link . handle ) {
irq = acpi_pci_link_allocate_irq ( entry - > link . handle ,
entry - > link . index , edge_level , active_high_low , link ) ;
if ( irq < 0 ) {
ACPI_DEBUG_PRINT ( ( ACPI_DB_WARN , " Invalid IRQ link routing entry \n " ) ) ;
return_VALUE ( - 1 ) ;
}
} else {
irq = entry - > link . index ;
* edge_level = ACPI_LEVEL_SENSITIVE ;
* active_high_low = ACPI_ACTIVE_LOW ;
}
ACPI_DEBUG_PRINT ( ( ACPI_DB_INFO , " Found IRQ %d \n " , irq ) ) ;
return_VALUE ( irq ) ;
}
static int
acpi_pci_free_irq ( struct acpi_prt_entry * entry ,
int * edge_level ,
int * active_high_low ,
char * * link )
{
int irq ;
ACPI_FUNCTION_TRACE ( " acpi_pci_free_irq " ) ;
if ( entry - > link . handle ) {
irq = acpi_pci_link_free_irq ( entry - > link . handle ) ;
} else {
irq = entry - > link . index ;
}
return_VALUE ( irq ) ;
}
2005-04-17 02:20:36 +04:00
/*
* acpi_pci_irq_lookup
* success : return IRQ > = 0
* failure : return - 1
*/
static int
acpi_pci_irq_lookup (
struct pci_bus * bus ,
int device ,
int pin ,
int * edge_level ,
int * active_high_low ,
2005-07-28 07:02:00 +04:00
char * * link ,
irq_lookup_func func )
2005-04-17 02:20:36 +04:00
{
struct acpi_prt_entry * entry = NULL ;
int segment = pci_domain_nr ( bus ) ;
int bus_nr = bus - > number ;
2005-07-28 07:02:00 +04:00
int ret ;
2005-04-17 02:20:36 +04:00
ACPI_FUNCTION_TRACE ( " acpi_pci_irq_lookup " ) ;
ACPI_DEBUG_PRINT ( ( ACPI_DB_INFO ,
" Searching for PRT entry for %02x:%02x:%02x[%c] \n " ,
segment , bus_nr , device , ( ' A ' + pin ) ) ) ;
entry = acpi_pci_irq_find_prt_entry ( segment , bus_nr , device , pin ) ;
if ( ! entry ) {
ACPI_DEBUG_PRINT ( ( ACPI_DB_INFO , " PRT entry not found \n " ) ) ;
return_VALUE ( - 1 ) ;
}
2005-07-28 07:02:00 +04:00
ret = func ( entry , edge_level , active_high_low , link ) ;
return_VALUE ( ret ) ;
2005-04-17 02:20:36 +04:00
}
/*
* acpi_pci_irq_derive
* success : return IRQ > = 0
* failure : return < 0
*/
static int
acpi_pci_irq_derive (
struct pci_dev * dev ,
int pin ,
int * edge_level ,
int * active_high_low ,
2005-07-28 07:02:00 +04:00
char * * link ,
irq_lookup_func func )
2005-04-17 02:20:36 +04:00
{
struct pci_dev * bridge = dev ;
int irq = - 1 ;
u8 bridge_pin = 0 ;
ACPI_FUNCTION_TRACE ( " acpi_pci_irq_derive " ) ;
if ( ! dev )
return_VALUE ( - EINVAL ) ;
/*
* Attempt to derive an IRQ for this device from a parent bridge ' s
* PCI interrupt routing entry ( eg . yenta bridge and add - in card bridge ) .
*/
while ( irq < 0 & & bridge - > bus - > self ) {
pin = ( pin + PCI_SLOT ( bridge - > devfn ) ) % 4 ;
bridge = bridge - > bus - > self ;
if ( ( bridge - > class > > 8 ) = = PCI_CLASS_BRIDGE_CARDBUS ) {
/* PC card has the same IRQ as its cardbridge */
pci_read_config_byte ( bridge , PCI_INTERRUPT_PIN , & bridge_pin ) ;
if ( ! bridge_pin ) {
ACPI_DEBUG_PRINT ( ( ACPI_DB_INFO ,
" No interrupt pin configured for device %s \n " , pci_name ( bridge ) ) ) ;
return_VALUE ( - 1 ) ;
}
/* Pin is from 0 to 3 */
bridge_pin - - ;
pin = bridge_pin ;
}
irq = acpi_pci_irq_lookup ( bridge - > bus , PCI_SLOT ( bridge - > devfn ) ,
2005-07-28 07:02:00 +04:00
pin , edge_level , active_high_low , link , func ) ;
2005-04-17 02:20:36 +04:00
}
if ( irq < 0 ) {
ACPI_DEBUG_PRINT ( ( ACPI_DB_WARN , " Unable to derive IRQ for device %s \n " , pci_name ( dev ) ) ) ;
return_VALUE ( - 1 ) ;
}
ACPI_DEBUG_PRINT ( ( ACPI_DB_INFO , " Derive IRQ %d for device %s from %s \n " ,
irq , pci_name ( dev ) , pci_name ( bridge ) ) ) ;
return_VALUE ( irq ) ;
}
/*
* acpi_pci_irq_enable
* success : return 0
* failure : return < 0
*/
int
acpi_pci_irq_enable (
struct pci_dev * dev )
{
int irq = 0 ;
u8 pin = 0 ;
int edge_level = ACPI_LEVEL_SENSITIVE ;
int active_high_low = ACPI_ACTIVE_LOW ;
char * link = NULL ;
2005-07-28 22:42:00 +04:00
int rc ;
2005-04-17 02:20:36 +04:00
ACPI_FUNCTION_TRACE ( " acpi_pci_irq_enable " ) ;
if ( ! dev )
return_VALUE ( - EINVAL ) ;
pci_read_config_byte ( dev , PCI_INTERRUPT_PIN , & pin ) ;
if ( ! pin ) {
ACPI_DEBUG_PRINT ( ( ACPI_DB_INFO , " No interrupt pin configured for device %s \n " , pci_name ( dev ) ) ) ;
return_VALUE ( 0 ) ;
}
pin - - ;
if ( ! dev - > bus ) {
ACPI_DEBUG_PRINT ( ( ACPI_DB_ERROR , " Invalid (NULL) 'bus' field \n " ) ) ;
return_VALUE ( - ENODEV ) ;
}
/*
* First we check the PCI IRQ routing table ( PRT ) for an IRQ . PRT
* values override any BIOS - assigned IRQs set during boot .
*/
irq = acpi_pci_irq_lookup ( dev - > bus , PCI_SLOT ( dev - > devfn ) , pin ,
2005-07-28 07:02:00 +04:00
& edge_level , & active_high_low , & link , acpi_pci_allocate_irq ) ;
2005-04-17 02:20:36 +04:00
/*
* If no PRT entry was found , we ' ll try to derive an IRQ from the
* device ' s parent bridge .
*/
if ( irq < 0 )
irq = acpi_pci_irq_derive ( dev , pin , & edge_level ,
2005-07-28 07:02:00 +04:00
& active_high_low , & link , acpi_pci_allocate_irq ) ;
2005-04-17 02:20:36 +04:00
/*
* No IRQ known to the ACPI subsystem - maybe the BIOS /
* driver reported one , then use it . Exit in any case .
*/
if ( irq < 0 ) {
printk ( KERN_WARNING PREFIX " PCI Interrupt %s[%c]: no GSI " ,
pci_name ( dev ) , ( ' A ' + pin ) ) ;
/* Interrupt Line values above 0xF are forbidden */
2005-07-02 21:35:33 +04:00
if ( dev - > irq > 0 & & ( dev - > irq < = 0xF ) ) {
2005-04-17 02:20:36 +04:00
printk ( " - using IRQ %d \n " , dev - > irq ) ;
2005-06-25 21:01:36 +04:00
acpi_register_gsi ( dev - > irq , ACPI_LEVEL_SENSITIVE , ACPI_ACTIVE_LOW ) ;
2005-04-17 02:20:36 +04:00
return_VALUE ( 0 ) ;
}
else {
printk ( " \n " ) ;
return_VALUE ( 0 ) ;
}
}
2005-07-28 22:42:00 +04:00
rc = acpi_register_gsi ( irq , edge_level , active_high_low ) ;
if ( rc < 0 ) {
printk ( KERN_WARNING PREFIX " PCI Interrupt %s[%c]: failed "
" to register GSI \n " , pci_name ( dev ) , ( ' A ' + pin ) ) ;
return_VALUE ( rc ) ;
}
dev - > irq = rc ;
2005-04-17 02:20:36 +04:00
printk ( KERN_INFO PREFIX " PCI Interrupt %s[%c] -> " ,
pci_name ( dev ) , ' A ' + pin ) ;
if ( link )
printk ( " Link [%s] -> " , link ) ;
printk ( " GSI %u (%s, %s) -> IRQ %d \n " , irq ,
( edge_level = = ACPI_LEVEL_SENSITIVE ) ? " level " : " edge " ,
( active_high_low = = ACPI_ACTIVE_LOW ) ? " low " : " high " ,
dev - > irq ) ;
return_VALUE ( 0 ) ;
}
EXPORT_SYMBOL ( acpi_pci_irq_enable ) ;
2005-07-28 07:02:00 +04:00
/* FIXME: implement x86/x86_64 version */
void __attribute__ ( ( weak ) ) acpi_unregister_gsi ( u32 i ) { }
2005-04-17 02:20:36 +04:00
void
acpi_pci_irq_disable (
struct pci_dev * dev )
{
int gsi = 0 ;
u8 pin = 0 ;
int edge_level = ACPI_LEVEL_SENSITIVE ;
int active_high_low = ACPI_ACTIVE_LOW ;
ACPI_FUNCTION_TRACE ( " acpi_pci_irq_disable " ) ;
if ( ! dev )
return_VOID ;
pci_read_config_byte ( dev , PCI_INTERRUPT_PIN , & pin ) ;
if ( ! pin )
return_VOID ;
pin - - ;
if ( ! dev - > bus )
return_VOID ;
/*
* First we check the PCI IRQ routing table ( PRT ) for an IRQ .
*/
gsi = acpi_pci_irq_lookup ( dev - > bus , PCI_SLOT ( dev - > devfn ) , pin ,
2005-07-28 07:02:00 +04:00
& edge_level , & active_high_low , NULL , acpi_pci_free_irq ) ;
2005-04-17 02:20:36 +04:00
/*
* If no PRT entry was found , we ' ll try to derive an IRQ from the
* device ' s parent bridge .
*/
if ( gsi < 0 )
gsi = acpi_pci_irq_derive ( dev , pin ,
2005-07-28 07:02:00 +04:00
& edge_level , & active_high_low , NULL , acpi_pci_free_irq ) ;
2005-04-17 02:20:36 +04:00
if ( gsi < 0 )
return_VOID ;
/*
* TBD : It might be worth clearing dev - > irq by magic constant
* ( e . g . PCI_UNDEFINED_IRQ ) .
*/
printk ( KERN_INFO PREFIX " PCI interrupt for device %s disabled \n " ,
pci_name ( dev ) ) ;
acpi_unregister_gsi ( gsi ) ;
return_VOID ;
}