2005-04-16 15:20:36 -07:00
/*
2012-07-20 11:15:04 +02:00
* Copyright IBM Corp . 2004 , 2011
2011-05-26 09:48:24 +02:00
* Author ( s ) : Martin Schwidefsky < schwidefsky @ de . ibm . com > ,
* Holger Smolinski < Holger . Smolinski @ de . ibm . com > ,
* Thomas Spatzier < tspat @ de . ibm . com > ,
2005-04-16 15:20:36 -07:00
*
* This file contains interrupt related functions .
*/
# include <linux/kernel_stat.h>
# include <linux/interrupt.h>
# include <linux/seq_file.h>
2007-02-05 21:16:44 +01:00
# include <linux/proc_fs.h>
# include <linux/profile.h>
2011-05-26 09:48:24 +02:00
# include <linux/module.h>
# include <linux/kernel.h>
# include <linux/ftrace.h>
# include <linux/errno.h>
# include <linux/slab.h>
# include <linux/cpu.h>
# include <asm/irq_regs.h>
# include <asm/cputime.h>
# include <asm/lowcore.h>
# include <asm/irq.h>
# include "entry.h"
2005-04-16 15:20:36 -07:00
2011-01-05 12:47:28 +01:00
struct irq_class {
char * name ;
char * desc ;
} ;
static const struct irq_class intrclass_names [ ] = {
{ . name = " EXT " } ,
{ . name = " I/O " } ,
{ . name = " CLK " , . desc = " [EXT] Clock Comparator " } ,
2011-10-30 15:17:19 +01:00
{ . name = " EXC " , . desc = " [EXT] External Call " } ,
{ . name = " EMS " , . desc = " [EXT] Emergency Signal " } ,
2011-01-05 12:47:28 +01:00
{ . name = " TMR " , . desc = " [EXT] CPU Timer " } ,
{ . name = " TAL " , . desc = " [EXT] Timing Alert " } ,
{ . name = " PFL " , . desc = " [EXT] Pseudo Page Fault " } ,
{ . name = " DSD " , . desc = " [EXT] DASD Diag " } ,
{ . name = " VRT " , . desc = " [EXT] Virtio " } ,
{ . name = " SCP " , . desc = " [EXT] Service Call " } ,
{ . name = " IUC " , . desc = " [EXT] IUCV " } ,
2012-05-09 16:27:40 +02:00
{ . name = " CMS " , . desc = " [EXT] CPU-Measurement: Sampling " } ,
{ . name = " CMC " , . desc = " [EXT] CPU-Measurement: Counter " } ,
2011-10-30 15:16:04 +01:00
{ . name = " CIO " , . desc = " [I/O] Common I/O Layer Interrupt " } ,
2011-01-05 12:47:29 +01:00
{ . name = " QAI " , . desc = " [I/O] QDIO Adapter Interrupt " } ,
2011-01-05 12:47:30 +01:00
{ . name = " DAS " , . desc = " [I/O] DASD " } ,
2011-01-05 12:47:31 +01:00
{ . name = " C15 " , . desc = " [I/O] 3215 " } ,
2011-01-05 12:47:32 +01:00
{ . name = " C70 " , . desc = " [I/O] 3270 " } ,
2011-01-05 12:47:33 +01:00
{ . name = " TAP " , . desc = " [I/O] Tape " } ,
2011-01-05 12:47:34 +01:00
{ . name = " VMR " , . desc = " [I/O] Unit Record Devices " } ,
2011-01-05 12:47:35 +01:00
{ . name = " LCS " , . desc = " [I/O] LCS " } ,
2011-01-05 12:47:36 +01:00
{ . name = " CLW " , . desc = " [I/O] CLAW " } ,
2011-01-05 12:47:37 +01:00
{ . name = " CTC " , . desc = " [I/O] CTC " } ,
2011-01-05 12:47:38 +01:00
{ . name = " APB " , . desc = " [I/O] AP Bus " } ,
2011-10-30 15:16:54 +01:00
{ . name = " CSC " , . desc = " [I/O] CHSC Subchannel " } ,
2011-01-05 12:47:28 +01:00
{ . name = " NMI " , . desc = " [NMI] Machine Check " } ,
} ;
2005-04-16 15:20:36 -07:00
/*
* show_interrupts is needed by / proc / interrupts .
*/
int show_interrupts ( struct seq_file * p , void * v )
{
int i = * ( loff_t * ) v , j ;
2008-05-15 16:52:39 +02:00
get_online_cpus ( ) ;
2005-04-16 15:20:36 -07:00
if ( i = = 0 ) {
seq_puts ( p , " " ) ;
for_each_online_cpu ( j )
seq_printf ( p , " CPU%d " , j ) ;
seq_putc ( p , ' \n ' ) ;
}
if ( i < NR_IRQS ) {
2011-01-05 12:47:28 +01:00
seq_printf ( p , " %s: " , intrclass_names [ i ] . name ) ;
2005-04-16 15:20:36 -07:00
# ifndef CONFIG_SMP
seq_printf ( p , " %10u " , kstat_irqs ( i ) ) ;
# else
for_each_online_cpu ( j )
seq_printf ( p , " %10u " , kstat_cpu ( j ) . irqs [ i ] ) ;
# endif
2011-01-05 12:47:28 +01:00
if ( intrclass_names [ i ] . desc )
seq_printf ( p , " %s " , intrclass_names [ i ] . desc ) ;
2005-04-16 15:20:36 -07:00
seq_putc ( p , ' \n ' ) ;
}
2008-05-15 16:52:39 +02:00
put_online_cpus ( ) ;
2005-04-16 15:20:36 -07:00
return 0 ;
}
/*
* Switch to the asynchronous interrupt stack for softirq execution .
*/
asmlinkage void do_softirq ( void )
{
unsigned long flags , old , new ;
if ( in_interrupt ( ) )
return ;
local_irq_save ( flags ) ;
if ( local_softirq_pending ( ) ) {
/* Get current stack pointer. */
asm volatile ( " la %0,0(15) " : " = a " (old)) ;
/* Check against async. stack address range. */
new = S390_lowcore . async_stack ;
if ( ( ( new - old ) > > ( PAGE_SHIFT + THREAD_ORDER ) ) ! = 0 ) {
/* Need to switch to the async. stack. */
new - = STACK_FRAME_OVERHEAD ;
( ( struct stack_frame * ) new ) - > back_chain = old ;
asm volatile ( " la 15,0(%0) \n "
" basr 14,%2 \n "
" la 15,0(%1) \n "
: : " a " ( new ) , " a " ( old ) ,
" a " ( __do_softirq )
: " 0 " , " 1 " , " 2 " , " 3 " , " 4 " , " 5 " , " 14 " ,
" cc " , " memory " ) ;
2012-04-11 14:28:09 +02:00
} else {
2005-04-16 15:20:36 -07:00
/* We are already on the async stack. */
__do_softirq ( ) ;
2012-04-11 14:28:09 +02:00
}
2005-04-16 15:20:36 -07:00
}
local_irq_restore ( flags ) ;
}
2007-02-05 21:16:44 +01:00
2009-02-11 10:37:29 +01:00
# ifdef CONFIG_PROC_FS
2007-02-05 21:16:44 +01:00
void init_irq_proc ( void )
{
struct proc_dir_entry * root_irq_dir ;
root_irq_dir = proc_mkdir ( " irq " , NULL ) ;
create_prof_cpu_mask ( root_irq_dir ) ;
}
2009-02-11 10:37:29 +01:00
# endif
2011-05-26 09:48:24 +02:00
/*
2011-07-24 10:48:27 +02:00
* ext_int_hash [ index ] is the list head for all external interrupts that hash
* to this index .
2011-05-26 09:48:24 +02:00
*/
2011-07-24 10:48:27 +02:00
static struct list_head ext_int_hash [ 256 ] ;
2011-05-26 09:48:24 +02:00
struct ext_int_info {
ext_int_handler_t handler ;
u16 code ;
2011-07-24 10:48:27 +02:00
struct list_head entry ;
struct rcu_head rcu ;
2011-05-26 09:48:24 +02:00
} ;
2011-07-24 10:48:27 +02:00
/* ext_int_hash_lock protects the handler lists for external interrupts */
DEFINE_SPINLOCK ( ext_int_hash_lock ) ;
static void __init init_external_interrupts ( void )
{
int idx ;
for ( idx = 0 ; idx < ARRAY_SIZE ( ext_int_hash ) ; idx + + )
INIT_LIST_HEAD ( & ext_int_hash [ idx ] ) ;
}
2011-05-26 09:48:24 +02:00
static inline int ext_hash ( u16 code )
{
return ( code + ( code > > 9 ) ) & 0xff ;
}
int register_external_interrupt ( u16 code , ext_int_handler_t handler )
{
struct ext_int_info * p ;
2011-07-24 10:48:27 +02:00
unsigned long flags ;
2011-05-26 09:48:24 +02:00
int index ;
p = kmalloc ( sizeof ( * p ) , GFP_ATOMIC ) ;
if ( ! p )
return - ENOMEM ;
p - > code = code ;
p - > handler = handler ;
index = ext_hash ( code ) ;
2011-07-24 10:48:27 +02:00
spin_lock_irqsave ( & ext_int_hash_lock , flags ) ;
list_add_rcu ( & p - > entry , & ext_int_hash [ index ] ) ;
spin_unlock_irqrestore ( & ext_int_hash_lock , flags ) ;
2011-05-26 09:48:24 +02:00
return 0 ;
}
EXPORT_SYMBOL ( register_external_interrupt ) ;
int unregister_external_interrupt ( u16 code , ext_int_handler_t handler )
{
2011-07-24 10:48:27 +02:00
struct ext_int_info * p ;
unsigned long flags ;
int index = ext_hash ( code ) ;
2011-05-26 09:48:24 +02:00
2011-07-24 10:48:27 +02:00
spin_lock_irqsave ( & ext_int_hash_lock , flags ) ;
2012-04-11 14:28:09 +02:00
list_for_each_entry_rcu ( p , & ext_int_hash [ index ] , entry ) {
2011-07-24 10:48:27 +02:00
if ( p - > code = = code & & p - > handler = = handler ) {
list_del_rcu ( & p - > entry ) ;
2012-01-06 16:59:51 -08:00
kfree_rcu ( p , rcu ) ;
2011-07-24 10:48:27 +02:00
}
2012-04-11 14:28:09 +02:00
}
2011-07-24 10:48:27 +02:00
spin_unlock_irqrestore ( & ext_int_hash_lock , flags ) ;
2011-05-26 09:48:24 +02:00
return 0 ;
}
EXPORT_SYMBOL ( unregister_external_interrupt ) ;
2012-03-11 11:59:31 -04:00
void __irq_entry do_extint ( struct pt_regs * regs , struct ext_code ext_code ,
2011-05-26 09:48:24 +02:00
unsigned int param32 , unsigned long param64 )
{
struct pt_regs * old_regs ;
struct ext_int_info * p ;
int index ;
old_regs = set_irq_regs ( regs ) ;
irq_enter ( ) ;
2012-04-11 14:28:09 +02:00
if ( S390_lowcore . int_clock > = S390_lowcore . clock_comparator ) {
2011-05-26 09:48:24 +02:00
/* Serve timer interrupts first. */
clock_comparator_work ( ) ;
2012-04-11 14:28:09 +02:00
}
2011-05-26 09:48:24 +02:00
kstat_cpu ( smp_processor_id ( ) ) . irqs [ EXTERNAL_INTERRUPT ] + + ;
2012-03-11 11:59:31 -04:00
if ( ext_code . code ! = 0x1004 )
2011-05-26 09:48:24 +02:00
__get_cpu_var ( s390_idle ) . nohz_delay = 1 ;
2011-07-24 10:48:27 +02:00
2012-03-11 11:59:31 -04:00
index = ext_hash ( ext_code . code ) ;
2011-07-24 10:48:27 +02:00
rcu_read_lock ( ) ;
list_for_each_entry_rcu ( p , & ext_int_hash [ index ] , entry )
2012-03-11 11:59:31 -04:00
if ( likely ( p - > code = = ext_code . code ) )
p - > handler ( ext_code , param32 , param64 ) ;
2011-07-24 10:48:27 +02:00
rcu_read_unlock ( ) ;
2011-05-26 09:48:24 +02:00
irq_exit ( ) ;
set_irq_regs ( old_regs ) ;
}
2011-07-24 10:48:27 +02:00
void __init init_IRQ ( void )
{
init_external_interrupts ( ) ;
}
2011-05-26 09:48:24 +02:00
static DEFINE_SPINLOCK ( sc_irq_lock ) ;
static int sc_irq_refcount ;
void service_subclass_irq_register ( void )
{
spin_lock ( & sc_irq_lock ) ;
if ( ! sc_irq_refcount )
ctl_set_bit ( 0 , 9 ) ;
sc_irq_refcount + + ;
spin_unlock ( & sc_irq_lock ) ;
}
EXPORT_SYMBOL ( service_subclass_irq_register ) ;
void service_subclass_irq_unregister ( void )
{
spin_lock ( & sc_irq_lock ) ;
sc_irq_refcount - - ;
if ( ! sc_irq_refcount )
ctl_clear_bit ( 0 , 9 ) ;
spin_unlock ( & sc_irq_lock ) ;
}
EXPORT_SYMBOL ( service_subclass_irq_unregister ) ;
2012-03-23 11:13:05 +01:00
static DEFINE_SPINLOCK ( ma_subclass_lock ) ;
static int ma_subclass_refcount ;
void measurement_alert_subclass_register ( void )
{
spin_lock ( & ma_subclass_lock ) ;
if ( ! ma_subclass_refcount )
ctl_set_bit ( 0 , 5 ) ;
ma_subclass_refcount + + ;
spin_unlock ( & ma_subclass_lock ) ;
}
EXPORT_SYMBOL ( measurement_alert_subclass_register ) ;
void measurement_alert_subclass_unregister ( void )
{
spin_lock ( & ma_subclass_lock ) ;
ma_subclass_refcount - - ;
if ( ! ma_subclass_refcount )
ctl_clear_bit ( 0 , 5 ) ;
spin_unlock ( & ma_subclass_lock ) ;
}
EXPORT_SYMBOL ( measurement_alert_subclass_unregister ) ;