2010-10-04 13:37:26 +04:00
/*
* Xen Watchdog Driver
*
* ( c ) Copyright 2010 Novell , Inc .
*
* 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 .
*/
2012-02-16 03:06:19 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2010-10-04 13:37:26 +04:00
# define DRV_NAME "wdt"
# define DRV_VERSION "0.01"
# include <linux/bug.h>
# include <linux/errno.h>
# include <linux/fs.h>
# include <linux/hrtimer.h>
# include <linux/kernel.h>
# include <linux/ktime.h>
# include <linux/init.h>
# include <linux/miscdevice.h>
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/platform_device.h>
# include <linux/spinlock.h>
# include <linux/uaccess.h>
# include <linux/watchdog.h>
# include <xen/xen.h>
# include <asm/xen/hypercall.h>
# include <xen/interface/sched.h>
static struct platform_device * platform_device ;
static DEFINE_SPINLOCK ( wdt_lock ) ;
static struct sched_watchdog wdt ;
static __kernel_time_t wdt_expires ;
static bool is_active , expect_release ;
# define WATCHDOG_TIMEOUT 60 /* in seconds */
static unsigned int timeout = WATCHDOG_TIMEOUT ;
module_param ( timeout , uint , S_IRUGO ) ;
MODULE_PARM_DESC ( timeout , " Watchdog timeout in seconds "
" (default= " __MODULE_STRING ( WATCHDOG_TIMEOUT ) " ) " ) ;
static bool nowayout = WATCHDOG_NOWAYOUT ;
module_param ( nowayout , bool , S_IRUGO ) ;
MODULE_PARM_DESC ( nowayout , " Watchdog cannot be stopped once started "
" (default= " __MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;
static inline __kernel_time_t set_timeout ( void )
{
wdt . timeout = timeout ;
return ktime_to_timespec ( ktime_get ( ) ) . tv_sec + timeout ;
}
static int xen_wdt_start ( void )
{
__kernel_time_t expires ;
int err ;
spin_lock ( & wdt_lock ) ;
expires = set_timeout ( ) ;
if ( ! wdt . id )
err = HYPERVISOR_sched_op ( SCHEDOP_watchdog , & wdt ) ;
else
err = - EBUSY ;
if ( err > 0 ) {
wdt . id = err ;
wdt_expires = expires ;
err = 0 ;
} else
BUG_ON ( ! err ) ;
spin_unlock ( & wdt_lock ) ;
return err ;
}
static int xen_wdt_stop ( void )
{
int err = 0 ;
spin_lock ( & wdt_lock ) ;
wdt . timeout = 0 ;
if ( wdt . id )
err = HYPERVISOR_sched_op ( SCHEDOP_watchdog , & wdt ) ;
if ( ! err )
wdt . id = 0 ;
spin_unlock ( & wdt_lock ) ;
return err ;
}
static int xen_wdt_kick ( void )
{
__kernel_time_t expires ;
int err ;
spin_lock ( & wdt_lock ) ;
expires = set_timeout ( ) ;
if ( wdt . id )
err = HYPERVISOR_sched_op ( SCHEDOP_watchdog , & wdt ) ;
else
err = - ENXIO ;
if ( ! err )
wdt_expires = expires ;
spin_unlock ( & wdt_lock ) ;
return err ;
}
static int xen_wdt_open ( struct inode * inode , struct file * file )
{
int err ;
/* /dev/watchdog can only be opened once */
if ( xchg ( & is_active , true ) )
return - EBUSY ;
err = xen_wdt_start ( ) ;
if ( err = = - EBUSY )
err = xen_wdt_kick ( ) ;
return err ? : nonseekable_open ( inode , file ) ;
}
static int xen_wdt_release ( struct inode * inode , struct file * file )
{
2012-03-19 13:32:28 +04:00
int err = 0 ;
2010-10-04 13:37:26 +04:00
if ( expect_release )
2012-03-19 13:32:28 +04:00
err = xen_wdt_stop ( ) ;
2010-10-04 13:37:26 +04:00
else {
2012-02-16 03:06:19 +04:00
pr_crit ( " unexpected close, not stopping watchdog! \n " ) ;
2010-10-04 13:37:26 +04:00
xen_wdt_kick ( ) ;
}
2012-03-19 13:32:28 +04:00
is_active = err ;
2010-10-04 13:37:26 +04:00
expect_release = false ;
2012-03-19 13:32:28 +04:00
return err ;
2010-10-04 13:37:26 +04:00
}
static ssize_t xen_wdt_write ( struct file * file , const char __user * data ,
size_t len , loff_t * ppos )
{
/* See if we got the magic character 'V' and reload the timer */
if ( len ) {
if ( ! nowayout ) {
size_t i ;
/* in case it was set long ago */
expect_release = false ;
/* scan to see whether or not we got the magic
character */
for ( i = 0 ; i ! = len ; i + + ) {
char c ;
if ( get_user ( c , data + i ) )
return - EFAULT ;
if ( c = = ' V ' )
expect_release = true ;
}
}
/* someone wrote to us, we should reload the timer */
xen_wdt_kick ( ) ;
}
return len ;
}
static long xen_wdt_ioctl ( struct file * file , unsigned int cmd ,
unsigned long arg )
{
int new_options , retval = - EINVAL ;
int new_timeout ;
int __user * argp = ( void __user * ) arg ;
static const struct watchdog_info ident = {
. options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE ,
. firmware_version = 0 ,
. identity = DRV_NAME ,
} ;
switch ( cmd ) {
case WDIOC_GETSUPPORT :
return copy_to_user ( argp , & ident , sizeof ( ident ) ) ? - EFAULT : 0 ;
case WDIOC_GETSTATUS :
case WDIOC_GETBOOTSTATUS :
return put_user ( 0 , argp ) ;
case WDIOC_SETOPTIONS :
if ( get_user ( new_options , argp ) )
return - EFAULT ;
if ( new_options & WDIOS_DISABLECARD )
retval = xen_wdt_stop ( ) ;
if ( new_options & WDIOS_ENABLECARD ) {
retval = xen_wdt_start ( ) ;
if ( retval = = - EBUSY )
retval = xen_wdt_kick ( ) ;
}
return retval ;
case WDIOC_KEEPALIVE :
xen_wdt_kick ( ) ;
return 0 ;
case WDIOC_SETTIMEOUT :
if ( get_user ( new_timeout , argp ) )
return - EFAULT ;
if ( ! new_timeout )
return - EINVAL ;
timeout = new_timeout ;
xen_wdt_kick ( ) ;
/* fall through */
case WDIOC_GETTIMEOUT :
return put_user ( timeout , argp ) ;
case WDIOC_GETTIMELEFT :
retval = wdt_expires - ktime_to_timespec ( ktime_get ( ) ) . tv_sec ;
return put_user ( retval , argp ) ;
}
return - ENOTTY ;
}
static const struct file_operations xen_wdt_fops = {
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. write = xen_wdt_write ,
. unlocked_ioctl = xen_wdt_ioctl ,
. open = xen_wdt_open ,
. release = xen_wdt_release ,
} ;
static struct miscdevice xen_wdt_miscdev = {
. minor = WATCHDOG_MINOR ,
. name = " watchdog " ,
. fops = & xen_wdt_fops ,
} ;
static int __devinit xen_wdt_probe ( struct platform_device * dev )
{
struct sched_watchdog wd = { . id = ~ 0 } ;
int ret = HYPERVISOR_sched_op ( SCHEDOP_watchdog , & wd ) ;
switch ( ret ) {
case - EINVAL :
if ( ! timeout ) {
timeout = WATCHDOG_TIMEOUT ;
2012-02-16 03:06:19 +04:00
pr_info ( " timeout value invalid, using %d \n " , timeout ) ;
2010-10-04 13:37:26 +04:00
}
ret = misc_register ( & xen_wdt_miscdev ) ;
if ( ret ) {
2012-02-16 03:06:19 +04:00
pr_err ( " cannot register miscdev on minor=%d (%d) \n " ,
2010-10-04 13:37:26 +04:00
WATCHDOG_MINOR , ret ) ;
break ;
}
2012-02-16 03:06:19 +04:00
pr_info ( " initialized (timeout=%ds, nowayout=%d) \n " ,
timeout , nowayout ) ;
2010-10-04 13:37:26 +04:00
break ;
case - ENOSYS :
2012-02-16 03:06:19 +04:00
pr_info ( " not supported \n " ) ;
2010-10-04 13:37:26 +04:00
ret = - ENODEV ;
break ;
default :
2012-02-16 03:06:19 +04:00
pr_info ( " bogus return value %d \n " , ret ) ;
2010-10-04 13:37:26 +04:00
break ;
}
return ret ;
}
static int __devexit xen_wdt_remove ( struct platform_device * dev )
{
/* Stop the timer before we leave */
if ( ! nowayout )
xen_wdt_stop ( ) ;
misc_deregister ( & xen_wdt_miscdev ) ;
return 0 ;
}
static void xen_wdt_shutdown ( struct platform_device * dev )
{
xen_wdt_stop ( ) ;
}
static int xen_wdt_suspend ( struct platform_device * dev , pm_message_t state )
{
2012-03-19 13:30:33 +04:00
typeof ( wdt . id ) id = wdt . id ;
int rc = xen_wdt_stop ( ) ;
wdt . id = id ;
return rc ;
2010-10-04 13:37:26 +04:00
}
static int xen_wdt_resume ( struct platform_device * dev )
{
2012-03-19 13:30:33 +04:00
if ( ! wdt . id )
return 0 ;
wdt . id = 0 ;
2010-10-04 13:37:26 +04:00
return xen_wdt_start ( ) ;
}
static struct platform_driver xen_wdt_driver = {
. probe = xen_wdt_probe ,
. remove = __devexit_p ( xen_wdt_remove ) ,
. shutdown = xen_wdt_shutdown ,
. suspend = xen_wdt_suspend ,
. resume = xen_wdt_resume ,
. driver = {
. owner = THIS_MODULE ,
. name = DRV_NAME ,
} ,
} ;
static int __init xen_wdt_init_module ( void )
{
int err ;
if ( ! xen_domain ( ) )
return - ENODEV ;
2012-02-16 03:06:19 +04:00
pr_info ( " Xen WatchDog Timer Driver v%s \n " , DRV_VERSION ) ;
2010-10-04 13:37:26 +04:00
err = platform_driver_register ( & xen_wdt_driver ) ;
if ( err )
return err ;
platform_device = platform_device_register_simple ( DRV_NAME ,
- 1 , NULL , 0 ) ;
if ( IS_ERR ( platform_device ) ) {
err = PTR_ERR ( platform_device ) ;
platform_driver_unregister ( & xen_wdt_driver ) ;
}
return err ;
}
static void __exit xen_wdt_cleanup_module ( void )
{
platform_device_unregister ( platform_device ) ;
platform_driver_unregister ( & xen_wdt_driver ) ;
2012-02-16 03:06:19 +04:00
pr_info ( " module unloaded \n " ) ;
2010-10-04 13:37:26 +04:00
}
module_init ( xen_wdt_init_module ) ;
module_exit ( xen_wdt_cleanup_module ) ;
MODULE_AUTHOR ( " Jan Beulich <jbeulich@novell.com> " ) ;
MODULE_DESCRIPTION ( " Xen WatchDog Timer Driver " ) ;
MODULE_VERSION ( DRV_VERSION ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS_MISCDEV ( WATCHDOG_MINOR ) ;