2005-04-17 02:20:36 +04:00
/*
* Watchdog implementation based on z / VM Watchdog Timer API
*
* The user space watchdog daemon can use this driver as
* / dev / vmwatchdog to have z / VM execute the specified CP
* command when the timeout expires . The default command is
* " IPL " , which which cause an immediate reboot .
*/
# include <linux/init.h>
# include <linux/fs.h>
# include <linux/kernel.h>
# include <linux/miscdevice.h>
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/watchdog.h>
# include <asm/ebcdic.h>
# include <asm/io.h>
# include <asm/uaccess.h>
# define MAX_CMDLEN 240
# define MIN_INTERVAL 15
static char vmwdt_cmd [ MAX_CMDLEN ] = " IPL " ;
static int vmwdt_conceal ;
2005-07-27 22:43:58 +04:00
static int vmwdt_nowayout = WATCHDOG_NOWAYOUT ;
2005-04-17 02:20:36 +04:00
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Arnd Bergmann <arndb@de.ibm.com> " ) ;
MODULE_DESCRIPTION ( " z/VM Watchdog Timer " ) ;
module_param_string ( cmd , vmwdt_cmd , MAX_CMDLEN , 0644 ) ;
MODULE_PARM_DESC ( cmd , " CP command that is run when the watchdog triggers " ) ;
module_param_named ( conceal , vmwdt_conceal , bool , 0644 ) ;
MODULE_PARM_DESC ( conceal , " Enable the CONCEAL CP option while the watchdog "
" is active " ) ;
module_param_named ( nowayout , vmwdt_nowayout , bool , 0 ) ;
MODULE_PARM_DESC ( nowayout , " Watchdog cannot be stopped once started "
" (default=CONFIG_WATCHDOG_NOWAYOUT) " ) ;
MODULE_ALIAS_MISCDEV ( WATCHDOG_MINOR ) ;
static unsigned int vmwdt_interval = 60 ;
static unsigned long vmwdt_is_open ;
static int vmwdt_expect_close ;
enum vmwdt_func {
/* function codes */
wdt_init = 0 ,
wdt_change = 1 ,
wdt_cancel = 2 ,
/* flags */
wdt_conceal = 0x80000000 ,
} ;
static int __diag288 ( enum vmwdt_func func , unsigned int timeout ,
char * cmd , size_t len )
{
register unsigned long __func asm ( " 2 " ) ;
register unsigned long __timeout asm ( " 3 " ) ;
register unsigned long __cmdp asm ( " 4 " ) ;
register unsigned long __cmdl asm ( " 5 " ) ;
int err ;
__func = func ;
__timeout = timeout ;
__cmdp = virt_to_phys ( cmd ) ;
__cmdl = len ;
err = 0 ;
asm volatile (
2006-01-06 11:19:28 +03:00
# ifdef CONFIG_64BIT
2005-04-17 02:20:36 +04:00
" diag %2,%4,0x288 \n "
" 1: \n "
" .section .fixup, \" ax \" \n "
" 2: lghi %0,%1 \n "
" jg 1b \n "
" .previous \n "
" .section __ex_table, \" a \" \n "
" .align 8 \n "
" .quad 1b,2b \n "
" .previous \n "
# else
" diag %2,%4,0x288 \n "
" 1: \n "
" .section .fixup, \" ax \" \n "
" 2: lhi %0,%1 \n "
" bras 1,3f \n "
" .long 1b \n "
" 3: l 1,0(1) \n "
" br 1 \n "
" .previous \n "
" .section __ex_table, \" a \" \n "
" .align 4 \n "
" .long 1b,2b \n "
" .previous \n "
# endif
: " +&d " ( err )
: " i " ( - EINVAL ) , " d " ( __func ) , " d " ( __timeout ) ,
" d " ( __cmdp ) , " d " ( __cmdl )
: " 1 " , " cc " ) ;
return err ;
}
static int vmwdt_keepalive ( void )
{
/* we allocate new memory every time to avoid having
* to track the state . static allocation is not an
* option since that might not be contiguous in real
* storage in case of a modular build */
static char * ebc_cmd ;
size_t len ;
int ret ;
unsigned int func ;
ebc_cmd = kmalloc ( MAX_CMDLEN , GFP_KERNEL ) ;
if ( ! ebc_cmd )
return - ENOMEM ;
len = strlcpy ( ebc_cmd , vmwdt_cmd , MAX_CMDLEN ) ;
ASCEBC ( ebc_cmd , MAX_CMDLEN ) ;
EBC_TOUPPER ( ebc_cmd , MAX_CMDLEN ) ;
func = vmwdt_conceal ? ( wdt_init | wdt_conceal ) : wdt_init ;
ret = __diag288 ( func , vmwdt_interval , ebc_cmd , len ) ;
kfree ( ebc_cmd ) ;
if ( ret ) {
printk ( KERN_WARNING " %s: problem setting interval %d, "
" cmd %s \n " , __FUNCTION__ , vmwdt_interval ,
vmwdt_cmd ) ;
}
return ret ;
}
static int vmwdt_disable ( void )
{
int ret = __diag288 ( wdt_cancel , 0 , " " , 0 ) ;
if ( ret ) {
printk ( KERN_WARNING " %s: problem disabling watchdog \n " ,
__FUNCTION__ ) ;
}
return ret ;
}
static int __init vmwdt_probe ( void )
{
/* there is no real way to see if the watchdog is supported,
* so we try initializing it with a NOP command ( " BEGIN " )
* that won ' t cause any harm even if the following disable
* fails for some reason */
static char __initdata ebc_begin [ ] = {
194 , 197 , 199 , 201 , 213
} ;
if ( __diag288 ( wdt_init , 15 , ebc_begin , sizeof ( ebc_begin ) ) ! = 0 ) {
printk ( KERN_INFO " z/VM watchdog not available \n " ) ;
return - EINVAL ;
}
return vmwdt_disable ( ) ;
}
static int vmwdt_open ( struct inode * i , struct file * f )
{
int ret ;
if ( test_and_set_bit ( 0 , & vmwdt_is_open ) )
return - EBUSY ;
ret = vmwdt_keepalive ( ) ;
if ( ret )
clear_bit ( 0 , & vmwdt_is_open ) ;
return ret ? ret : nonseekable_open ( i , f ) ;
}
static int vmwdt_close ( struct inode * i , struct file * f )
{
if ( vmwdt_expect_close = = 42 )
vmwdt_disable ( ) ;
vmwdt_expect_close = 0 ;
clear_bit ( 0 , & vmwdt_is_open ) ;
return 0 ;
}
static struct watchdog_info vmwdt_info = {
. options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE ,
. firmware_version = 0 ,
. identity = " z/VM Watchdog Timer " ,
} ;
static int vmwdt_ioctl ( struct inode * i , struct file * f ,
unsigned int cmd , unsigned long arg )
{
switch ( cmd ) {
case WDIOC_GETSUPPORT :
if ( copy_to_user ( ( void __user * ) arg , & vmwdt_info ,
sizeof ( vmwdt_info ) ) )
return - EFAULT ;
return 0 ;
case WDIOC_GETSTATUS :
case WDIOC_GETBOOTSTATUS :
return put_user ( 0 , ( int * ) arg ) ;
case WDIOC_GETTEMP :
return - EINVAL ;
case WDIOC_SETOPTIONS :
{
int options , ret ;
if ( get_user ( options , ( int __user * ) arg ) )
return - EFAULT ;
ret = - EINVAL ;
if ( options & WDIOS_DISABLECARD ) {
ret = vmwdt_disable ( ) ;
if ( ret )
return ret ;
}
if ( options & WDIOS_ENABLECARD ) {
ret = vmwdt_keepalive ( ) ;
}
return ret ;
}
case WDIOC_GETTIMEOUT :
return put_user ( vmwdt_interval , ( int __user * ) arg ) ;
case WDIOC_SETTIMEOUT :
{
int interval ;
if ( get_user ( interval , ( int __user * ) arg ) )
return - EFAULT ;
if ( interval < MIN_INTERVAL )
return - EINVAL ;
vmwdt_interval = interval ;
}
return vmwdt_keepalive ( ) ;
case WDIOC_KEEPALIVE :
return vmwdt_keepalive ( ) ;
}
return - EINVAL ;
}
static ssize_t vmwdt_write ( struct file * f , const char __user * buf ,
size_t count , loff_t * ppos )
{
if ( count ) {
if ( ! vmwdt_nowayout ) {
size_t i ;
/* note: just in case someone wrote the magic character
* five months ago . . . */
vmwdt_expect_close = 0 ;
for ( i = 0 ; i ! = count ; i + + ) {
char c ;
if ( get_user ( c , buf + i ) )
return - EFAULT ;
if ( c = = ' V ' )
vmwdt_expect_close = 42 ;
}
}
/* someone wrote to us, we should restart timer */
vmwdt_keepalive ( ) ;
}
return count ;
}
static struct file_operations vmwdt_fops = {
. open = & vmwdt_open ,
. release = & vmwdt_close ,
. ioctl = & vmwdt_ioctl ,
. write = & vmwdt_write ,
. owner = THIS_MODULE ,
} ;
static struct miscdevice vmwdt_dev = {
. minor = WATCHDOG_MINOR ,
. name = " watchdog " ,
. fops = & vmwdt_fops ,
} ;
static int __init vmwdt_init ( void )
{
int ret ;
ret = vmwdt_probe ( ) ;
if ( ret )
return ret ;
return misc_register ( & vmwdt_dev ) ;
}
module_init ( vmwdt_init ) ;
static void __exit vmwdt_exit ( void )
{
WARN_ON ( misc_deregister ( & vmwdt_dev ) ! = 0 ) ;
}
module_exit ( vmwdt_exit ) ;