2009-09-11 12:28:47 +04:00
/*
* Enable Asynchronous Notification via SCLP .
*
* Copyright IBM Corp . 2009
* Author ( s ) : Hans - Joachim Picht < hans @ linux . vnet . ibm . com >
*
*/
# include <linux/init.h>
# include <linux/module.h>
# include <linux/device.h>
# include <linux/stat.h>
# include <linux/string.h>
# include <linux/ctype.h>
# include <linux/kmod.h>
# include <linux/err.h>
# include <linux/errno.h>
# include <linux/proc_fs.h>
# include <linux/sysctl.h>
# include <linux/utsname.h>
# include "sclp.h"
static int callhome_enabled ;
static struct sclp_req * request ;
static struct sclp_async_sccb * sccb ;
static int sclp_async_send_wait ( char * message ) ;
static struct ctl_table_header * callhome_sysctl_header ;
static DEFINE_SPINLOCK ( sclp_async_lock ) ;
static char nodename [ 64 ] ;
# define SCLP_NORMAL_WRITE 0x00
struct async_evbuf {
struct evbuf_header header ;
u64 reserved ;
u8 rflags ;
u8 empty ;
u8 rtype ;
u8 otype ;
char comp_id [ 12 ] ;
char data [ 3000 ] ; /* there is still some space left */
} __attribute__ ( ( packed ) ) ;
struct sclp_async_sccb {
struct sccb_header header ;
struct async_evbuf evbuf ;
} __attribute__ ( ( packed ) ) ;
static struct sclp_register sclp_async_register = {
. send_mask = EVTYP_ASYNC_MASK ,
} ;
static int call_home_on_panic ( struct notifier_block * self ,
unsigned long event , void * data )
{
strncat ( data , nodename , strlen ( nodename ) ) ;
sclp_async_send_wait ( data ) ;
return NOTIFY_DONE ;
}
static struct notifier_block call_home_panic_nb = {
. notifier_call = call_home_on_panic ,
. priority = INT_MAX ,
} ;
2009-10-14 14:43:49 +04:00
static int proc_handler_callhome ( struct ctl_table * ctl , int write ,
2009-09-11 12:28:47 +04:00
void __user * buffer , size_t * count ,
loff_t * ppos )
{
unsigned long val ;
int len , rc ;
char buf [ 2 ] ;
if ( ! * count | ( * ppos & & ! write ) ) {
* count = 0 ;
return 0 ;
}
if ( ! write ) {
len = sprintf ( buf , " %d \n " , callhome_enabled ) ;
buf [ len ] = ' \0 ' ;
rc = copy_to_user ( buffer , buf , sizeof ( buf ) ) ;
if ( rc ! = 0 )
return - EFAULT ;
} else {
len = * count ;
rc = copy_from_user ( buf , buffer , sizeof ( buf ) ) ;
if ( rc ! = 0 )
return - EFAULT ;
if ( strict_strtoul ( buf , 0 , & val ) ! = 0 )
return - EINVAL ;
if ( val ! = 0 & & val ! = 1 )
return - EINVAL ;
callhome_enabled = val ;
}
* count = len ;
* ppos + = len ;
return 0 ;
}
static struct ctl_table callhome_table [ ] = {
{
. procname = " callhome " ,
. mode = 0644 ,
2009-10-14 14:43:49 +04:00
. proc_handler = proc_handler_callhome ,
2009-09-11 12:28:47 +04:00
} ,
{ . ctl_name = 0 }
} ;
static struct ctl_table kern_dir_table [ ] = {
{
. ctl_name = CTL_KERN ,
. procname = " kernel " ,
. maxlen = 0 ,
. mode = 0555 ,
. child = callhome_table ,
} ,
{ . ctl_name = 0 }
} ;
/*
* Function used to transfer asynchronous notification
* records which waits for send completion
*/
static int sclp_async_send_wait ( char * message )
{
struct async_evbuf * evb ;
int rc ;
unsigned long flags ;
if ( ! callhome_enabled )
return 0 ;
sccb - > evbuf . header . type = EVTYP_ASYNC ;
sccb - > evbuf . rtype = 0xA5 ;
sccb - > evbuf . otype = 0x00 ;
evb = & sccb - > evbuf ;
request - > command = SCLP_CMDW_WRITE_EVENT_DATA ;
request - > sccb = sccb ;
request - > status = SCLP_REQ_FILLED ;
strncpy ( sccb - > evbuf . data , message , sizeof ( sccb - > evbuf . data ) ) ;
/*
* Retain Queue
* e . g . 5639 CC140 500 Red Hat RHEL5 Linux for zSeries ( RHEL AS )
*/
strncpy ( sccb - > evbuf . comp_id , " 000000000 " , sizeof ( sccb - > evbuf . comp_id ) ) ;
sccb - > evbuf . header . length = sizeof ( sccb - > evbuf ) ;
sccb - > header . length = sizeof ( sccb - > evbuf ) + sizeof ( sccb - > header ) ;
sccb - > header . function_code = SCLP_NORMAL_WRITE ;
rc = sclp_add_request ( request ) ;
if ( rc )
return rc ;
spin_lock_irqsave ( & sclp_async_lock , flags ) ;
while ( request - > status ! = SCLP_REQ_DONE & &
request - > status ! = SCLP_REQ_FAILED ) {
sclp_sync_wait ( ) ;
}
spin_unlock_irqrestore ( & sclp_async_lock , flags ) ;
if ( request - > status ! = SCLP_REQ_DONE )
return - EIO ;
rc = ( ( struct sclp_async_sccb * )
request - > sccb ) - > header . response_code ;
if ( rc ! = 0x0020 )
return - EIO ;
if ( evb - > header . flags ! = 0x80 )
return - EIO ;
return rc ;
}
static int __init sclp_async_init ( void )
{
int rc ;
rc = sclp_register ( & sclp_async_register ) ;
if ( rc )
return rc ;
callhome_sysctl_header = register_sysctl_table ( kern_dir_table ) ;
if ( ! callhome_sysctl_header ) {
rc = - ENOMEM ;
goto out_sclp ;
}
if ( ! ( sclp_async_register . sclp_receive_mask & EVTYP_ASYNC_MASK ) ) {
rc = - EOPNOTSUPP ;
goto out_sclp ;
}
rc = - ENOMEM ;
request = kzalloc ( sizeof ( struct sclp_req ) , GFP_KERNEL ) ;
if ( ! request )
goto out_sys ;
sccb = ( struct sclp_async_sccb * ) get_zeroed_page ( GFP_KERNEL | GFP_DMA ) ;
if ( ! sccb )
goto out_mem ;
rc = atomic_notifier_chain_register ( & panic_notifier_list ,
& call_home_panic_nb ) ;
if ( rc )
goto out_mem ;
strncpy ( nodename , init_utsname ( ) - > nodename , 64 ) ;
return 0 ;
out_mem :
kfree ( request ) ;
free_page ( ( unsigned long ) sccb ) ;
out_sys :
unregister_sysctl_table ( callhome_sysctl_header ) ;
out_sclp :
sclp_unregister ( & sclp_async_register ) ;
return rc ;
}
module_init ( sclp_async_init ) ;
static void __exit sclp_async_exit ( void )
{
atomic_notifier_chain_unregister ( & panic_notifier_list ,
& call_home_panic_nb ) ;
unregister_sysctl_table ( callhome_sysctl_header ) ;
sclp_unregister ( & sclp_async_register ) ;
free_page ( ( unsigned long ) sccb ) ;
kfree ( request ) ;
}
module_exit ( sclp_async_exit ) ;
MODULE_AUTHOR ( " Copyright IBM Corp. 2009 " ) ;
MODULE_AUTHOR ( " Hans-Joachim Picht <hans@linux.vnet.ibm.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_DESCRIPTION ( " SCLP Asynchronous Notification Records " ) ;