2005-06-21 17:15:49 -07:00
/*
* This module supports the iSeries PCI bus interrupt handling
* Copyright ( C ) 20 yy < Robert L Holtorf > < IBM Corp >
2005-06-21 17:15:50 -07:00
* Copyright ( C ) 2004 - 2005 IBM Corporation
2005-06-21 17:15:49 -07:00
*
* 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
*
* Change Activity :
* Created , December 13 , 2000 by Wayne Holm
* End Change Activity
*/
2005-06-21 17:15:50 -07:00
# include <linux/config.h>
2005-04-16 15:20:36 -07:00
# include <linux/pci.h>
# include <linux/init.h>
# include <linux/threads.h>
# include <linux/smp.h>
# include <linux/param.h>
# include <linux/string.h>
# include <linux/bootmem.h>
# include <linux/ide.h>
# include <linux/irq.h>
# include <linux/spinlock.h>
2005-11-16 18:53:29 +11:00
# include <asm/paca.h>
2005-11-02 13:46:07 +11:00
# include <asm/iseries/hv_types.h>
2005-11-02 12:08:31 +11:00
# include <asm/iseries/hv_lp_event.h>
2005-11-02 11:41:12 +11:00
# include <asm/iseries/hv_call_xm.h>
2005-11-16 18:53:29 +11:00
# include <asm/iseries/it_lp_queue.h>
2005-09-28 23:37:01 +10:00
# include "irq.h"
2006-05-19 16:46:28 +10:00
# include "pci.h"
2005-10-14 17:16:17 +10:00
# include "call_pci.h"
2005-04-16 15:20:36 -07:00
2005-11-16 18:53:29 +11:00
# if defined(CONFIG_SMP)
extern void iSeries_smp_message_recv ( struct pt_regs * ) ;
# endif
2006-01-12 13:54:20 +11:00
# ifdef CONFIG_PCI
2005-11-16 17:47:43 +11:00
enum pci_event_type {
pe_bus_created = 0 , /* PHB has been created */
pe_bus_error = 1 , /* PHB has failed */
pe_bus_failed = 2 , /* Msg to Secondary, Primary failed bus */
pe_node_failed = 4 , /* Multi-adapter bridge has failed */
pe_node_recovered = 5 , /* Multi-adapter bridge has recovered */
pe_bus_recovered = 12 , /* PHB has been recovered */
pe_unquiese_bus = 18 , /* Secondary bus unqiescing */
pe_bridge_error = 21 , /* Bridge Error */
pe_slot_interrupt = 22 /* Slot interrupt */
2005-06-21 17:15:50 -07:00
} ;
2005-11-16 17:47:43 +11:00
struct pci_event {
struct HvLpEvent event ;
2005-06-21 17:15:50 -07:00
union {
2005-11-16 17:47:43 +11:00
u64 __align ; /* Align on an 8-byte boundary */
2005-06-21 17:15:50 -07:00
struct {
u32 fisr ;
2005-11-16 17:47:43 +11:00
HvBusNumber bus_number ;
HvSubBusNumber sub_bus_number ;
HvAgentId dev_id ;
} slot ;
struct {
HvBusNumber bus_number ;
HvSubBusNumber sub_bus_number ;
} bus ;
struct {
HvBusNumber bus_number ;
HvSubBusNumber sub_bus_number ;
HvAgentId dev_id ;
} node ;
} data ;
2005-06-21 17:15:50 -07:00
} ;
2005-11-17 18:04:37 +11:00
static DEFINE_SPINLOCK ( pending_irqs_lock ) ;
static int num_pending_irqs ;
static int pending_irqs [ NR_IRQS ] ;
2005-11-16 17:47:43 +11:00
static void int_received ( struct pci_event * event , struct pt_regs * regs )
2005-06-21 17:15:50 -07:00
{
int irq ;
2005-11-16 17:47:43 +11:00
switch ( event - > event . xSubtype ) {
case pe_slot_interrupt :
irq = event - > event . xCorrelationToken ;
2005-11-17 18:04:37 +11:00
if ( irq < NR_IRQS ) {
spin_lock ( & pending_irqs_lock ) ;
pending_irqs [ irq ] + + ;
num_pending_irqs + + ;
spin_unlock ( & pending_irqs_lock ) ;
} else {
printk ( KERN_WARNING " int_received: bad irq number %d \n " ,
irq ) ;
HvCallPci_eoi ( event - > data . slot . bus_number ,
event - > data . slot . sub_bus_number ,
event - > data . slot . dev_id ) ;
}
2005-06-21 17:15:50 -07:00
break ;
/* Ignore error recovery events for now */
2005-11-16 17:47:43 +11:00
case pe_bus_created :
printk ( KERN_INFO " int_received: system bus %d created \n " ,
event - > data . bus . bus_number ) ;
2005-06-21 17:15:50 -07:00
break ;
2005-11-16 17:47:43 +11:00
case pe_bus_error :
case pe_bus_failed :
printk ( KERN_INFO " int_received: system bus %d failed \n " ,
event - > data . bus . bus_number ) ;
2005-06-21 17:15:50 -07:00
break ;
2005-11-16 17:47:43 +11:00
case pe_bus_recovered :
case pe_unquiese_bus :
printk ( KERN_INFO " int_received: system bus %d recovered \n " ,
event - > data . bus . bus_number ) ;
2005-06-21 17:15:50 -07:00
break ;
2005-11-16 17:47:43 +11:00
case pe_node_failed :
case pe_bridge_error :
2005-06-21 17:15:50 -07:00
printk ( KERN_INFO
2005-11-16 17:47:43 +11:00
" int_received: multi-adapter bridge %d/%d/%d failed \n " ,
event - > data . node . bus_number ,
event - > data . node . sub_bus_number ,
event - > data . node . dev_id ) ;
2005-06-21 17:15:50 -07:00
break ;
2005-11-16 17:47:43 +11:00
case pe_node_recovered :
2005-06-21 17:15:50 -07:00
printk ( KERN_INFO
2005-11-16 17:47:43 +11:00
" int_received: multi-adapter bridge %d/%d/%d recovered \n " ,
event - > data . node . bus_number ,
event - > data . node . sub_bus_number ,
event - > data . node . dev_id ) ;
2005-06-21 17:15:50 -07:00
break ;
default :
printk ( KERN_ERR
2005-11-16 17:47:43 +11:00
" int_received: unrecognized event subtype 0x%x \n " ,
event - > event . xSubtype ) ;
2005-06-21 17:15:50 -07:00
break ;
}
}
2005-11-16 17:47:43 +11:00
static void pci_event_handler ( struct HvLpEvent * event , struct pt_regs * regs )
2005-06-21 17:15:50 -07:00
{
2005-11-16 17:47:43 +11:00
if ( event & & ( event - > xType = = HvLpEvent_Type_PciIo ) ) {
2006-01-12 13:47:43 +11:00
if ( hvlpevent_is_int ( event ) )
2005-11-16 17:47:43 +11:00
int_received ( ( struct pci_event * ) event , regs ) ;
2006-01-12 13:47:43 +11:00
else
2005-06-21 17:15:50 -07:00
printk ( KERN_ERR
2005-11-16 17:47:43 +11:00
" pci_event_handler: unexpected ack received \n " ) ;
} else if ( event )
2005-06-21 17:15:50 -07:00
printk ( KERN_ERR
2005-11-16 17:47:43 +11:00
" pci_event_handler: Unrecognized PCI event type 0x%x \n " ,
( int ) event - > xType ) ;
2005-06-21 17:15:50 -07:00
else
2005-11-16 17:47:43 +11:00
printk ( KERN_ERR " pci_event_handler: NULL event received \n " ) ;
2005-06-21 17:15:50 -07:00
}
2005-06-21 17:15:51 -07:00
/*
* This is called by init_IRQ . set in ppc_md . init_IRQ by iSeries_setup . c
* It must be called before the bus walk .
*/
void __init iSeries_init_IRQ ( void )
2005-06-21 17:15:50 -07:00
{
2005-06-21 17:15:51 -07:00
/* Register PCI event handler and open an event path */
2005-11-16 17:47:43 +11:00
int ret ;
ret = HvLpEvent_registerHandler ( HvLpEvent_Type_PciIo ,
& pci_event_handler ) ;
if ( ret = = 0 ) {
ret = HvLpEvent_openPath ( HvLpEvent_Type_PciIo , 0 ) ;
if ( ret ! = 0 )
printk ( KERN_ERR " iseries_init_IRQ: open event path "
" failed with rc 0x%x \n " , ret ) ;
2005-06-21 17:15:50 -07:00
} else
2005-11-16 17:47:43 +11:00
printk ( KERN_ERR " iseries_init_IRQ: register handler "
" failed with rc 0x%x \n " , ret ) ;
2005-04-16 15:20:36 -07:00
}
2005-11-16 18:10:40 +11:00
# define REAL_IRQ_TO_SUBBUS(irq) (((irq) >> 14) & 0xff)
2005-06-21 17:15:49 -07:00
# define REAL_IRQ_TO_BUS(irq) ((((irq) >> 6) & 0xff) + 1)
# define REAL_IRQ_TO_IDSEL(irq) ((((irq) >> 3) & 7) + 1)
# define REAL_IRQ_TO_FUNC(irq) ((irq) & 7)
2005-04-16 15:20:36 -07:00
/*
2005-06-21 17:15:49 -07:00
* This will be called by device drivers ( via enable_IRQ )
* to enable INTA in the bridge interrupt status register .
2005-04-16 15:20:36 -07:00
*/
2005-11-16 17:47:43 +11:00
static void iseries_enable_IRQ ( unsigned int irq )
2005-04-16 15:20:36 -07:00
{
2005-11-16 17:47:43 +11:00
u32 bus , dev_id , function , mask ;
const u32 sub_bus = 0 ;
2005-06-21 17:15:49 -07:00
unsigned int rirq = virt_irq_to_real_map [ irq ] ;
2005-04-16 15:20:36 -07:00
2005-06-21 17:15:49 -07:00
/* The IRQ has already been locked by the caller */
bus = REAL_IRQ_TO_BUS ( rirq ) ;
function = REAL_IRQ_TO_FUNC ( rirq ) ;
2005-11-16 17:47:43 +11:00
dev_id = ( REAL_IRQ_TO_IDSEL ( rirq ) < < 4 ) + function ;
2005-04-16 15:20:36 -07:00
2005-06-21 17:15:49 -07:00
/* Unmask secondary INTA */
mask = 0x80000000 ;
2005-11-16 17:47:43 +11:00
HvCallPci_unmaskInterrupts ( bus , sub_bus , dev_id , mask ) ;
2005-04-16 15:20:36 -07:00
}
2005-11-16 17:47:43 +11:00
/* This is called by iseries_activate_IRQs */
static unsigned int iseries_startup_IRQ ( unsigned int irq )
2005-04-16 15:20:36 -07:00
{
2005-11-16 17:47:43 +11:00
u32 bus , dev_id , function , mask ;
const u32 sub_bus = 0 ;
2005-04-16 15:20:36 -07:00
unsigned int rirq = virt_irq_to_real_map [ irq ] ;
bus = REAL_IRQ_TO_BUS ( rirq ) ;
function = REAL_IRQ_TO_FUNC ( rirq ) ;
2005-11-16 17:47:43 +11:00
dev_id = ( REAL_IRQ_TO_IDSEL ( rirq ) < < 4 ) + function ;
2005-04-16 15:20:36 -07:00
/* Link the IRQ number to the bridge */
2005-11-16 17:47:43 +11:00
HvCallXm_connectBusUnit ( bus , sub_bus , dev_id , irq ) ;
2005-04-16 15:20:36 -07:00
/* Unmask bridge interrupts in the FISR */
mask = 0x01010000 < < function ;
2005-11-16 17:47:43 +11:00
HvCallPci_unmaskFisr ( bus , sub_bus , dev_id , mask ) ;
iseries_enable_IRQ ( irq ) ;
2005-04-16 15:20:36 -07:00
return 0 ;
}
/*
* This is called out of iSeries_fixup to activate interrupt
* generation for usable slots
*/
void __init iSeries_activate_IRQs ( )
{
int irq ;
unsigned long flags ;
for_each_irq ( irq ) {
irq_desc_t * desc = get_irq_desc ( irq ) ;
if ( desc & & desc - > handler & & desc - > handler - > startup ) {
spin_lock_irqsave ( & desc - > lock , flags ) ;
desc - > handler - > startup ( irq ) ;
spin_unlock_irqrestore ( & desc - > lock , flags ) ;
}
2005-06-21 17:15:49 -07:00
}
2005-04-16 15:20:36 -07:00
}
/* this is not called anywhere currently */
2005-11-16 17:47:43 +11:00
static void iseries_shutdown_IRQ ( unsigned int irq )
2005-04-16 15:20:36 -07:00
{
2005-11-16 17:47:43 +11:00
u32 bus , dev_id , function , mask ;
const u32 sub_bus = 0 ;
2005-04-16 15:20:36 -07:00
unsigned int rirq = virt_irq_to_real_map [ irq ] ;
/* irq should be locked by the caller */
bus = REAL_IRQ_TO_BUS ( rirq ) ;
function = REAL_IRQ_TO_FUNC ( rirq ) ;
2005-11-16 17:47:43 +11:00
dev_id = ( REAL_IRQ_TO_IDSEL ( rirq ) < < 4 ) + function ;
2005-04-16 15:20:36 -07:00
/* Invalidate the IRQ number in the bridge */
2005-11-16 17:47:43 +11:00
HvCallXm_connectBusUnit ( bus , sub_bus , dev_id , 0 ) ;
2005-04-16 15:20:36 -07:00
/* Mask bridge interrupts in the FISR */
mask = 0x01010000 < < function ;
2005-11-16 17:47:43 +11:00
HvCallPci_maskFisr ( bus , sub_bus , dev_id , mask ) ;
2005-04-16 15:20:36 -07:00
}
/*
* This will be called by device drivers ( via disable_IRQ )
* to disable INTA in the bridge interrupt status register .
*/
2005-11-16 17:47:43 +11:00
static void iseries_disable_IRQ ( unsigned int irq )
2005-04-16 15:20:36 -07:00
{
2005-11-16 17:47:43 +11:00
u32 bus , dev_id , function , mask ;
const u32 sub_bus = 0 ;
2005-04-16 15:20:36 -07:00
unsigned int rirq = virt_irq_to_real_map [ irq ] ;
/* The IRQ has already been locked by the caller */
bus = REAL_IRQ_TO_BUS ( rirq ) ;
function = REAL_IRQ_TO_FUNC ( rirq ) ;
2005-11-16 17:47:43 +11:00
dev_id = ( REAL_IRQ_TO_IDSEL ( rirq ) < < 4 ) + function ;
2005-04-16 15:20:36 -07:00
/* Mask secondary INTA */
mask = 0x80000000 ;
2005-11-16 17:47:43 +11:00
HvCallPci_maskInterrupts ( bus , sub_bus , dev_id , mask ) ;
2005-04-16 15:20:36 -07:00
}
2005-11-16 17:47:43 +11:00
static void iseries_end_IRQ ( unsigned int irq )
2005-04-16 15:20:36 -07:00
{
2005-11-16 18:10:40 +11:00
unsigned int rirq = virt_irq_to_real_map [ irq ] ;
HvCallPci_eoi ( REAL_IRQ_TO_BUS ( rirq ) , REAL_IRQ_TO_SUBBUS ( rirq ) ,
( REAL_IRQ_TO_IDSEL ( rirq ) < < 4 ) + REAL_IRQ_TO_FUNC ( rirq ) ) ;
2005-04-16 15:20:36 -07:00
}
2005-06-21 17:15:49 -07:00
static hw_irq_controller iSeries_IRQ_handler = {
. typename = " iSeries irq controller " ,
2005-11-16 17:47:43 +11:00
. startup = iseries_startup_IRQ ,
. shutdown = iseries_shutdown_IRQ ,
. enable = iseries_enable_IRQ ,
. disable = iseries_disable_IRQ ,
. end = iseries_end_IRQ
2005-06-21 17:15:49 -07:00
} ;
/*
* This is called out of iSeries_scan_slot to allocate an IRQ for an EADS slot
* It calculates the irq value for the slot .
2005-11-16 18:10:40 +11:00
* Note that sub_bus is always 0 ( at the moment at least ) .
2005-06-21 17:15:49 -07:00
*/
2005-11-16 18:10:40 +11:00
int __init iSeries_allocate_IRQ ( HvBusNumber bus ,
2006-05-19 16:46:28 +10:00
HvSubBusNumber sub_bus , u32 bsubbus )
2005-06-21 17:15:49 -07:00
{
2005-11-10 18:11:19 +11:00
int virtirq ;
unsigned int realirq ;
2006-05-19 16:46:28 +10:00
u8 idsel = ISERIES_GET_DEVICE_FROM_SUBBUS ( bsubbus ) ;
u8 function = ISERIES_GET_FUNCTION_FROM_SUBBUS ( bsubbus ) ;
2005-06-21 17:15:49 -07:00
2005-11-16 18:10:40 +11:00
realirq = ( ( ( ( ( sub_bus < < 8 ) + ( bus - 1 ) ) < < 3 ) + ( idsel - 1 ) ) < < 3 )
+ function ;
2005-11-10 18:11:19 +11:00
virtirq = virt_irq_create_mapping ( realirq ) ;
2005-06-21 17:15:49 -07:00
irq_desc [ virtirq ] . handler = & iSeries_IRQ_handler ;
return virtirq ;
}
2005-11-16 18:53:29 +11:00
2006-01-12 13:54:20 +11:00
# endif /* CONFIG_PCI */
2005-11-16 18:53:29 +11:00
/*
* Get the next pending IRQ .
*/
int iSeries_get_irq ( struct pt_regs * regs )
{
2005-11-17 18:04:37 +11:00
/* -2 means ignore this interrupt */
int irq = - 2 ;
2005-11-16 18:53:29 +11:00
# ifdef CONFIG_SMP
2006-01-13 10:26:42 +11:00
if ( get_lppaca ( ) - > int_dword . fields . ipi_cnt ) {
get_lppaca ( ) - > int_dword . fields . ipi_cnt = 0 ;
2005-11-16 18:53:29 +11:00
iSeries_smp_message_recv ( regs ) ;
}
# endif /* CONFIG_SMP */
if ( hvlpevent_is_pending ( ) )
process_hvlpevents ( regs ) ;
2006-01-12 13:54:20 +11:00
# ifdef CONFIG_PCI
2005-11-17 18:04:37 +11:00
if ( num_pending_irqs ) {
spin_lock ( & pending_irqs_lock ) ;
for ( irq = 0 ; irq < NR_IRQS ; irq + + ) {
if ( pending_irqs [ irq ] ) {
pending_irqs [ irq ] - - ;
num_pending_irqs - - ;
break ;
}
}
spin_unlock ( & pending_irqs_lock ) ;
if ( irq > = NR_IRQS )
irq = - 2 ;
}
2006-01-12 13:54:20 +11:00
# endif
2005-11-17 18:04:37 +11:00
return irq ;
2005-11-16 18:53:29 +11:00
}