2005-04-17 02:20:36 +04:00
/*
* SoftDog 0.07 : A Software Watchdog Device
*
2009-03-18 11:35:09 +03:00
* ( c ) Copyright 1996 Alan Cox < alan @ lxorguk . ukuu . org . uk > ,
* All Rights Reserved .
2005-04-17 02:20:36 +04: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 .
*
* Neither Alan Cox nor CymruNet Ltd . admit liability nor provide
* warranty for any of this software . This material is provided
* " AS-IS " and at no charge .
*
* ( c ) Copyright 1995 Alan Cox < alan @ lxorguk . ukuu . org . uk >
*
* Software only watchdog driver . Unlike its big brother the WDT501P
* driver this won ' t always recover a failed machine .
*
* 03 / 96 : Angelo Haritsis < ah @ doc . ic . ac . uk > :
* Modularised .
* Added soft_margin ; use upon insmod to change the timer delay .
* NB : uses same minor as wdt ( WATCHDOG_MINOR ) ; we could use separate
* minors .
*
* 19980911 Alan Cox
* Made SMP safe for 2.3 . x
*
* 20011127 Joel Becker ( jlbec @ evilplan . org >
* Added soft_noboot ; Allows testing the softdog trigger without
* requiring a recompile .
* Added WDIOC_GETTIMEOUT and WDIOC_SETTIMOUT .
*
* 20020530 Joel Becker < joel . becker @ oracle . com >
2009-03-18 11:35:09 +03:00
* Added Matt Domsch ' s nowayout module option .
2005-04-17 02:20:36 +04:00
*/
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/types.h>
# include <linux/timer.h>
# include <linux/miscdevice.h>
# include <linux/watchdog.h>
# include <linux/fs.h>
# include <linux/notifier.h>
# include <linux/reboot.h>
# include <linux/init.h>
2005-10-31 02:03:48 +03:00
# include <linux/jiffies.h>
2008-05-19 17:09:06 +04:00
# include <linux/uaccess.h>
2005-04-17 02:20:36 +04:00
# define PFX "SoftDog: "
# define TIMER_MARGIN 60 /* Default is 60 seconds */
static int soft_margin = TIMER_MARGIN ; /* in seconds */
module_param ( soft_margin , int , 0 ) ;
2008-05-19 17:09:06 +04:00
MODULE_PARM_DESC ( soft_margin ,
" Watchdog soft_margin in seconds. (0 < soft_margin < 65536, default= "
__MODULE_STRING ( TIMER_MARGIN ) " ) " ) ;
2005-04-17 02:20:36 +04:00
2005-07-27 22:43:58 +04:00
static int nowayout = WATCHDOG_NOWAYOUT ;
2005-04-17 02:20:36 +04:00
module_param ( nowayout , int , 0 ) ;
2008-05-19 17:09:06 +04:00
MODULE_PARM_DESC ( nowayout ,
" Watchdog cannot be stopped once started (default= "
__MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;
2005-04-17 02:20:36 +04:00
# ifdef ONLY_TESTING
static int soft_noboot = 1 ;
# else
static int soft_noboot = 0 ;
# endif /* ONLY_TESTING */
module_param ( soft_noboot , int , 0 ) ;
2009-04-15 00:20:07 +04:00
MODULE_PARM_DESC ( soft_noboot ,
" Softdog action, set to 1 to ignore reboots, 0 to reboot "
" (default depends on ONLY_TESTING) " ) ;
2005-04-17 02:20:36 +04:00
/*
* Our timer
*/
static void watchdog_fire ( unsigned long ) ;
static struct timer_list watchdog_ticktock =
TIMER_INITIALIZER ( watchdog_fire , 0 , 0 ) ;
2005-08-19 16:14:07 +04:00
static unsigned long driver_open , orphan_timer ;
2005-04-17 02:20:36 +04:00
static char expect_close ;
/*
* If the timer expires . .
*/
static void watchdog_fire ( unsigned long data )
{
2005-08-19 16:14:07 +04:00
if ( test_and_clear_bit ( 0 , & orphan_timer ) )
module_put ( THIS_MODULE ) ;
2005-04-17 02:20:36 +04:00
if ( soft_noboot )
printk ( KERN_CRIT PFX " Triggered - Reboot ignored. \n " ) ;
2008-05-19 17:09:06 +04:00
else {
2005-04-17 02:20:36 +04:00
printk ( KERN_CRIT PFX " Initiating system reboot. \n " ) ;
2005-07-27 08:41:38 +04:00
emergency_restart ( ) ;
2005-04-17 02:20:36 +04:00
printk ( KERN_CRIT PFX " Reboot didn't ????? \n " ) ;
}
}
/*
* Softdog operations
*/
static int softdog_keepalive ( void )
{
mod_timer ( & watchdog_ticktock , jiffies + ( soft_margin * HZ ) ) ;
return 0 ;
}
static int softdog_stop ( void )
{
del_timer ( & watchdog_ticktock ) ;
return 0 ;
}
static int softdog_set_heartbeat ( int t )
{
if ( ( t < 0x0001 ) | | ( t > 0xFFFF ) )
return - EINVAL ;
soft_margin = t ;
return 0 ;
}
/*
* / dev / watchdog handling
*/
static int softdog_open ( struct inode * inode , struct file * file )
{
2005-08-19 16:14:07 +04:00
if ( test_and_set_bit ( 0 , & driver_open ) )
2005-04-17 02:20:36 +04:00
return - EBUSY ;
2005-08-19 16:14:07 +04:00
if ( ! test_and_clear_bit ( 0 , & orphan_timer ) )
2005-04-17 02:20:36 +04:00
__module_get ( THIS_MODULE ) ;
/*
* Activate timer
*/
softdog_keepalive ( ) ;
return nonseekable_open ( inode , file ) ;
}
static int softdog_release ( struct inode * inode , struct file * file )
{
/*
* Shut off the timer .
* Lock it in if it ' s a module and we set nowayout
*/
if ( expect_close = = 42 ) {
softdog_stop ( ) ;
2005-08-19 16:14:07 +04:00
module_put ( THIS_MODULE ) ;
2005-04-17 02:20:36 +04:00
} else {
2008-05-19 17:09:06 +04:00
printk ( KERN_CRIT PFX
" Unexpected close, not stopping watchdog! \n " ) ;
2005-08-19 16:14:07 +04:00
set_bit ( 0 , & orphan_timer ) ;
2005-04-17 02:20:36 +04:00
softdog_keepalive ( ) ;
}
2005-08-19 16:14:07 +04:00
clear_bit ( 0 , & driver_open ) ;
2005-04-17 02:20:36 +04:00
expect_close = 0 ;
return 0 ;
}
2008-05-19 17:09:06 +04:00
static ssize_t softdog_write ( struct file * file , const char __user * data ,
size_t len , loff_t * ppos )
2005-04-17 02:20:36 +04:00
{
/*
* Refresh the timer .
*/
2008-05-19 17:09:06 +04:00
if ( len ) {
2005-04-17 02:20:36 +04:00
if ( ! nowayout ) {
size_t i ;
/* In case it was set long ago */
expect_close = 0 ;
for ( i = 0 ; i ! = len ; i + + ) {
char c ;
if ( get_user ( c , data + i ) )
return - EFAULT ;
if ( c = = ' V ' )
expect_close = 42 ;
}
}
softdog_keepalive ( ) ;
}
return len ;
}
2008-05-19 17:09:06 +04:00
static long softdog_ioctl ( struct file * file , unsigned int cmd ,
unsigned long arg )
2005-04-17 02:20:36 +04:00
{
void __user * argp = ( void __user * ) arg ;
int __user * p = argp ;
int new_margin ;
2008-05-19 17:09:06 +04:00
static const struct watchdog_info ident = {
2005-04-17 02:20:36 +04:00
. options = WDIOF_SETTIMEOUT |
WDIOF_KEEPALIVEPING |
WDIOF_MAGICCLOSE ,
. firmware_version = 0 ,
. identity = " Software Watchdog " ,
} ;
switch ( cmd ) {
2008-05-19 17:09:06 +04:00
case WDIOC_GETSUPPORT :
return copy_to_user ( argp , & ident , sizeof ( ident ) ) ? - EFAULT : 0 ;
case WDIOC_GETSTATUS :
case WDIOC_GETBOOTSTATUS :
return put_user ( 0 , p ) ;
case WDIOC_KEEPALIVE :
softdog_keepalive ( ) ;
return 0 ;
case WDIOC_SETTIMEOUT :
if ( get_user ( new_margin , p ) )
return - EFAULT ;
if ( softdog_set_heartbeat ( new_margin ) )
return - EINVAL ;
softdog_keepalive ( ) ;
/* Fall */
case WDIOC_GETTIMEOUT :
return put_user ( soft_margin , p ) ;
2008-07-18 15:41:17 +04:00
default :
return - ENOTTY ;
2005-04-17 02:20:36 +04:00
}
}
/*
* Notifier for system down
*/
static int softdog_notify_sys ( struct notifier_block * this , unsigned long code ,
void * unused )
{
2008-05-19 17:09:06 +04:00
if ( code = = SYS_DOWN | | code = = SYS_HALT )
2005-04-17 02:20:36 +04:00
/* Turn the WDT off */
softdog_stop ( ) ;
return NOTIFY_DONE ;
}
/*
* Kernel Interfaces
*/
2006-07-03 11:24:21 +04:00
static const struct file_operations softdog_fops = {
2005-04-17 02:20:36 +04:00
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. write = softdog_write ,
2008-05-19 17:09:06 +04:00
. unlocked_ioctl = softdog_ioctl ,
2005-04-17 02:20:36 +04:00
. open = softdog_open ,
. release = softdog_release ,
} ;
static struct miscdevice softdog_miscdev = {
. minor = WATCHDOG_MINOR ,
. name = " watchdog " ,
. fops = & softdog_fops ,
} ;
static struct notifier_block softdog_notifier = {
. notifier_call = softdog_notify_sys ,
} ;
2009-04-15 00:20:07 +04:00
static char banner [ ] __initdata = KERN_INFO " Software Watchdog Timer: 0.07 "
" initialized. soft_noboot=%d soft_margin=%d sec (nowayout= %d) \n " ;
2005-04-17 02:20:36 +04:00
static int __init watchdog_init ( void )
{
int ret ;
2008-05-19 17:09:06 +04:00
/* Check that the soft_margin value is within it's range;
if not reset to the default */
2005-04-17 02:20:36 +04:00
if ( softdog_set_heartbeat ( soft_margin ) ) {
softdog_set_heartbeat ( TIMER_MARGIN ) ;
2008-05-19 17:09:06 +04:00
printk ( KERN_INFO PFX
" soft_margin must be 0 < soft_margin < 65536, using %d \n " ,
2005-04-17 02:20:36 +04:00
TIMER_MARGIN ) ;
}
ret = register_reboot_notifier ( & softdog_notifier ) ;
if ( ret ) {
2008-05-19 17:09:06 +04:00
printk ( KERN_ERR PFX
" cannot register reboot notifier (err=%d) \n " , ret ) ;
2005-04-17 02:20:36 +04:00
return ret ;
}
ret = misc_register ( & softdog_miscdev ) ;
if ( ret ) {
2008-05-19 17:09:06 +04:00
printk ( KERN_ERR PFX
" cannot register miscdev on minor=%d (err=%d) \n " ,
WATCHDOG_MINOR , ret ) ;
2005-04-17 02:20:36 +04:00
unregister_reboot_notifier ( & softdog_notifier ) ;
return ret ;
}
printk ( banner , soft_noboot , soft_margin , nowayout ) ;
return 0 ;
}
static void __exit watchdog_exit ( void )
{
misc_deregister ( & softdog_miscdev ) ;
unregister_reboot_notifier ( & softdog_notifier ) ;
}
module_init ( watchdog_init ) ;
module_exit ( watchdog_exit ) ;
MODULE_AUTHOR ( " Alan Cox " ) ;
MODULE_DESCRIPTION ( " Software Watchdog Device Driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS_MISCDEV ( WATCHDOG_MINOR ) ;