2005-04-26 00:29:46 +04:00
/*
* SN Platform system controller communication support
*
* This file is subject to the terms and conditions of the GNU General Public
* License . See the file " COPYING " in the main directory of this archive
* for more details .
*
* Copyright ( C ) 2004 Silicon Graphics , Inc . All rights reserved .
*/
/*
* System controller event handler
*
* These routines deal with environmental events arriving from the
* system controllers .
*/
# include <linux/interrupt.h>
# include <linux/sched.h>
# include <linux/byteorder/generic.h>
# include <asm/sn/sn_sal.h>
2005-08-16 00:00:00 +04:00
# include <asm/unaligned.h>
2005-04-26 00:29:46 +04:00
# include "snsc.h"
static struct subch_data_s * event_sd ;
void scdrv_event ( unsigned long ) ;
DECLARE_TASKLET ( sn_sysctl_event , scdrv_event , 0 ) ;
/*
* scdrv_event_interrupt
*
* Pull incoming environmental events off the physical link to the
* system controller and put them in a temporary holding area in SAL .
* Schedule scdrv_event ( ) to move them along to their ultimate
* destination .
*/
static irqreturn_t
scdrv_event_interrupt ( int irq , void * subch_data , struct pt_regs * regs )
{
struct subch_data_s * sd = subch_data ;
unsigned long flags ;
int status ;
spin_lock_irqsave ( & sd - > sd_rlock , flags ) ;
status = ia64_sn_irtr_intr ( sd - > sd_nasid , sd - > sd_subch ) ;
if ( ( status > 0 ) & & ( status & SAL_IROUTER_INTR_RECV ) ) {
tasklet_schedule ( & sn_sysctl_event ) ;
}
spin_unlock_irqrestore ( & sd - > sd_rlock , flags ) ;
return IRQ_HANDLED ;
}
/*
* scdrv_parse_event
*
* Break an event ( as read from SAL ) into useful pieces so we can decide
* what to do with it .
*/
static int
scdrv_parse_event ( char * event , int * src , int * code , int * esp_code , char * desc )
{
char * desc_end ;
2005-08-16 00:00:00 +04:00
__be32 from_buf ;
2005-04-26 00:29:46 +04:00
/* record event source address */
2005-08-16 00:00:00 +04:00
from_buf = get_unaligned ( ( __be32 * ) event ) ;
* src = be32_to_cpup ( & from_buf ) ;
2005-04-26 00:29:46 +04:00
event + = 4 ; /* move on to event code */
/* record the system controller's event code */
2005-08-16 00:00:00 +04:00
from_buf = get_unaligned ( ( __be32 * ) event ) ;
* code = be32_to_cpup ( & from_buf ) ;
2005-04-26 00:29:46 +04:00
event + = 4 ; /* move on to event arguments */
/* how many arguments are in the packet? */
if ( * event + + ! = 2 ) {
/* if not 2, give up */
return - 1 ;
}
/* parse out the ESP code */
if ( * event + + ! = IR_ARG_INT ) {
/* not an integer argument, so give up */
return - 1 ;
}
2005-08-16 00:00:00 +04:00
from_buf = get_unaligned ( ( __be32 * ) event ) ;
* esp_code = be32_to_cpup ( & from_buf ) ;
2005-04-26 00:29:46 +04:00
event + = 4 ;
/* parse out the event description */
if ( * event + + ! = IR_ARG_ASCII ) {
/* not an ASCII string, so give up */
return - 1 ;
}
event [ CHUNKSIZE - 1 ] = ' \0 ' ; /* ensure this string ends! */
event + = 2 ; /* skip leading CR/LF */
desc_end = desc + sprintf ( desc , " %s " , event ) ;
/* strip trailing CR/LF (if any) */
for ( desc_end - - ;
( desc_end ! = desc ) & & ( ( * desc_end = = 0xd ) | | ( * desc_end = = 0xa ) ) ;
desc_end - - ) {
* desc_end = ' \0 ' ;
}
return 0 ;
}
/*
* scdrv_event_severity
*
* Figure out how urgent a message we should write to the console / syslog
* via printk .
*/
static char *
scdrv_event_severity ( int code )
{
int ev_class = ( code & EV_CLASS_MASK ) ;
int ev_severity = ( code & EV_SEVERITY_MASK ) ;
char * pk_severity = KERN_NOTICE ;
switch ( ev_class ) {
case EV_CLASS_POWER :
switch ( ev_severity ) {
case EV_SEVERITY_POWER_LOW_WARNING :
case EV_SEVERITY_POWER_HIGH_WARNING :
pk_severity = KERN_WARNING ;
break ;
case EV_SEVERITY_POWER_HIGH_FAULT :
case EV_SEVERITY_POWER_LOW_FAULT :
pk_severity = KERN_ALERT ;
break ;
}
break ;
case EV_CLASS_FAN :
switch ( ev_severity ) {
case EV_SEVERITY_FAN_WARNING :
pk_severity = KERN_WARNING ;
break ;
case EV_SEVERITY_FAN_FAULT :
pk_severity = KERN_CRIT ;
break ;
}
break ;
case EV_CLASS_TEMP :
switch ( ev_severity ) {
case EV_SEVERITY_TEMP_ADVISORY :
pk_severity = KERN_WARNING ;
break ;
case EV_SEVERITY_TEMP_CRITICAL :
pk_severity = KERN_CRIT ;
break ;
case EV_SEVERITY_TEMP_FAULT :
pk_severity = KERN_ALERT ;
break ;
}
break ;
case EV_CLASS_ENV :
pk_severity = KERN_ALERT ;
break ;
case EV_CLASS_TEST_FAULT :
pk_severity = KERN_ALERT ;
break ;
case EV_CLASS_TEST_WARNING :
pk_severity = KERN_WARNING ;
break ;
case EV_CLASS_PWRD_NOTIFY :
pk_severity = KERN_ALERT ;
break ;
}
return pk_severity ;
}
/*
* scdrv_dispatch_event
*
* Do the right thing with an incoming event . That ' s often nothing
* more than printing it to the system log . For power - down notifications
* we start a graceful shutdown .
*/
static void
scdrv_dispatch_event ( char * event , int len )
{
int code , esp_code , src ;
char desc [ CHUNKSIZE ] ;
char * severity ;
if ( scdrv_parse_event ( event , & src , & code , & esp_code , desc ) < 0 ) {
/* ignore uninterpretible event */
return ;
}
/* how urgent is the message? */
severity = scdrv_event_severity ( code ) ;
if ( ( code & EV_CLASS_MASK ) = = EV_CLASS_PWRD_NOTIFY ) {
struct task_struct * p ;
/* give a SIGPWR signal to init proc */
/* first find init's task */
read_lock ( & tasklist_lock ) ;
for_each_process ( p ) {
if ( p - > pid = = 1 )
break ;
}
if ( p ) { /* we found init's task */
printk ( KERN_EMERG " Power off indication received. Initiating power fail sequence... \n " ) ;
force_sig ( SIGPWR , p ) ;
} else { /* failed to find init's task - just give message(s) */
printk ( KERN_WARNING " Failed to find init proc to handle power off! \n " ) ;
printk ( " %s|$(0x%x)%s \n " , severity , esp_code , desc ) ;
}
read_unlock ( & tasklist_lock ) ;
} else {
/* print to system log */
printk ( " %s|$(0x%x)%s \n " , severity , esp_code , desc ) ;
}
}
/*
* scdrv_event
*
* Called as a tasklet when an event arrives from the L1 . Read the event
* from where it ' s temporarily stored in SAL and call scdrv_dispatch_event ( )
* to send it on its way . Keep trying to read events until SAL indicates
* that there are no more immediately available .
*/
void
scdrv_event ( unsigned long dummy )
{
int status ;
int len ;
unsigned long flags ;
struct subch_data_s * sd = event_sd ;
/* anything to read? */
len = CHUNKSIZE ;
spin_lock_irqsave ( & sd - > sd_rlock , flags ) ;
status = ia64_sn_irtr_recv ( sd - > sd_nasid , sd - > sd_subch ,
sd - > sd_rb , & len ) ;
while ( ! ( status < 0 ) ) {
spin_unlock_irqrestore ( & sd - > sd_rlock , flags ) ;
scdrv_dispatch_event ( sd - > sd_rb , len ) ;
len = CHUNKSIZE ;
spin_lock_irqsave ( & sd - > sd_rlock , flags ) ;
status = ia64_sn_irtr_recv ( sd - > sd_nasid , sd - > sd_subch ,
sd - > sd_rb , & len ) ;
}
spin_unlock_irqrestore ( & sd - > sd_rlock , flags ) ;
}
/*
* scdrv_event_init
*
* Sets up a system controller subchannel to begin receiving event
* messages . This is sort of a specialized version of scdrv_open ( )
* in drivers / char / sn_sysctl . c .
*/
void
scdrv_event_init ( struct sysctl_data_s * scd )
{
int rv ;
event_sd = kmalloc ( sizeof ( struct subch_data_s ) , GFP_KERNEL ) ;
if ( event_sd = = NULL ) {
printk ( KERN_WARNING " %s: couldn't allocate subchannel info "
" for event monitoring \n " , __FUNCTION__ ) ;
return ;
}
/* initialize subch_data_s fields */
memset ( event_sd , 0 , sizeof ( struct subch_data_s ) ) ;
event_sd - > sd_nasid = scd - > scd_nasid ;
spin_lock_init ( & event_sd - > sd_rlock ) ;
/* ask the system controllers to send events to this node */
event_sd - > sd_subch = ia64_sn_sysctl_event_init ( scd - > scd_nasid ) ;
if ( event_sd - > sd_subch < 0 ) {
kfree ( event_sd ) ;
printk ( KERN_WARNING " %s: couldn't open event subchannel \n " ,
__FUNCTION__ ) ;
return ;
}
/* hook event subchannel up to the system controller interrupt */
rv = request_irq ( SGI_UART_VECTOR , scdrv_event_interrupt ,
SA_SHIRQ | SA_INTERRUPT ,
" system controller events " , event_sd ) ;
if ( rv ) {
printk ( KERN_WARNING " %s: irq request failed (%d) \n " ,
__FUNCTION__ , rv ) ;
ia64_sn_irtr_close ( event_sd - > sd_nasid , event_sd - > sd_subch ) ;
kfree ( event_sd ) ;
return ;
}
}