2019-05-27 09:55:01 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
2005-04-17 02:20:36 +04:00
/* drivers/char/watchdog/scx200_wdt.c
National Semiconductor SCx200 Watchdog support
Copyright ( c ) 2001 , 2002 Christer Weinigel < wingel @ nano - system . com >
Some code taken from :
National Semiconductor PC87307 / PC97307 ( ala SC1200 ) WDT driver
( c ) Copyright 2002 Zwane Mwaikambo < zwane @ commfireservices . com >
The author ( s ) of this software shall not be held liable for damages
of any nature resulting due to the use of this software . This
software is provided AS - IS with no warranties . */
2012-02-16 03:06:19 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2005-04-17 02:20:36 +04:00
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/init.h>
# include <linux/miscdevice.h>
# include <linux/watchdog.h>
# include <linux/notifier.h>
# include <linux/reboot.h>
# include <linux/fs.h>
2007-03-06 13:45:12 +03:00
# include <linux/ioport.h>
2005-04-17 02:20:36 +04:00
# include <linux/scx200.h>
2008-05-19 17:08:49 +04:00
# include <linux/uaccess.h>
# include <linux/io.h>
2005-04-17 02:20:36 +04:00
2012-02-16 03:06:19 +04:00
# define DEBUG
2005-04-17 02:20:36 +04:00
MODULE_AUTHOR ( " Christer Weinigel <wingel@nano-system.com> " ) ;
MODULE_DESCRIPTION ( " NatSemi SCx200 Watchdog Driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
static int margin = 60 ; /* in seconds */
module_param ( margin , int , 0 ) ;
MODULE_PARM_DESC ( margin , " Watchdog margin in seconds " ) ;
2012-03-05 19:51:11 +04:00
static bool nowayout = WATCHDOG_NOWAYOUT ;
module_param ( nowayout , bool , 0 ) ;
2005-04-17 02:20:36 +04:00
MODULE_PARM_DESC ( nowayout , " Disable watchdog shutdown on close " ) ;
static u16 wdto_restart ;
static char expect_close ;
2008-05-19 17:08:49 +04:00
static unsigned long open_lock ;
static DEFINE_SPINLOCK ( scx_lock ) ;
2005-04-17 02:20:36 +04:00
/* Bits of the WDCNFG register */
# define W_ENABLE 0x00fa /* Enable watchdog */
# define W_DISABLE 0x0000 /* Disable watchdog */
/* The scaling factor for the timer, this depends on the value of W_ENABLE */
# define W_SCALE (32768 / 1024)
static void scx200_wdt_ping ( void )
{
2008-05-19 17:08:49 +04:00
spin_lock ( & scx_lock ) ;
2005-04-17 02:20:36 +04:00
outw ( wdto_restart , scx200_cb_base + SCx200_WDT_WDTO ) ;
2008-05-19 17:08:49 +04:00
spin_unlock ( & scx_lock ) ;
2005-04-17 02:20:36 +04:00
}
static void scx200_wdt_update_margin ( void )
{
2012-02-16 03:06:19 +04:00
pr_info ( " timer margin %d seconds \n " , margin ) ;
2005-04-17 02:20:36 +04:00
wdto_restart = margin * W_SCALE ;
}
static void scx200_wdt_enable ( void )
{
2012-02-16 03:06:19 +04:00
pr_debug ( " enabling watchdog timer, wdto_restart = %d \n " , wdto_restart ) ;
2005-04-17 02:20:36 +04:00
2008-05-19 17:08:49 +04:00
spin_lock ( & scx_lock ) ;
2005-04-17 02:20:36 +04:00
outw ( 0 , scx200_cb_base + SCx200_WDT_WDTO ) ;
outb ( SCx200_WDT_WDSTS_WDOVF , scx200_cb_base + SCx200_WDT_WDSTS ) ;
outw ( W_ENABLE , scx200_cb_base + SCx200_WDT_WDCNFG ) ;
2008-05-19 17:08:49 +04:00
spin_unlock ( & scx_lock ) ;
2005-04-17 02:20:36 +04:00
scx200_wdt_ping ( ) ;
}
static void scx200_wdt_disable ( void )
{
2012-02-16 03:06:19 +04:00
pr_debug ( " disabling watchdog timer \n " ) ;
2005-04-17 02:20:36 +04:00
2008-05-19 17:08:49 +04:00
spin_lock ( & scx_lock ) ;
2005-04-17 02:20:36 +04:00
outw ( 0 , scx200_cb_base + SCx200_WDT_WDTO ) ;
outb ( SCx200_WDT_WDSTS_WDOVF , scx200_cb_base + SCx200_WDT_WDSTS ) ;
outw ( W_DISABLE , scx200_cb_base + SCx200_WDT_WDCNFG ) ;
2008-05-19 17:08:49 +04:00
spin_unlock ( & scx_lock ) ;
2005-04-17 02:20:36 +04:00
}
static int scx200_wdt_open ( struct inode * inode , struct file * file )
{
/* only allow one at a time */
2008-05-19 17:08:49 +04:00
if ( test_and_set_bit ( 0 , & open_lock ) )
2005-04-17 02:20:36 +04:00
return - EBUSY ;
scx200_wdt_enable ( ) ;
2019-03-26 23:51:19 +03:00
return stream_open ( inode , file ) ;
2005-04-17 02:20:36 +04:00
}
static int scx200_wdt_release ( struct inode * inode , struct file * file )
{
2008-05-19 17:08:49 +04:00
if ( expect_close ! = 42 )
2012-02-16 03:06:19 +04:00
pr_warn ( " watchdog device closed unexpectedly, will not disable the watchdog timer \n " ) ;
2008-05-19 17:08:49 +04:00
else if ( ! nowayout )
2005-04-17 02:20:36 +04:00
scx200_wdt_disable ( ) ;
expect_close = 0 ;
2008-05-19 17:08:49 +04:00
clear_bit ( 0 , & open_lock ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
static int scx200_wdt_notify_sys ( struct notifier_block * this ,
unsigned long code , void * unused )
{
if ( code = = SYS_HALT | | code = = SYS_POWER_OFF )
if ( ! nowayout )
scx200_wdt_disable ( ) ;
return NOTIFY_DONE ;
}
2008-05-19 17:08:49 +04:00
static struct notifier_block scx200_wdt_notifier = {
2005-04-17 02:20:36 +04:00
. notifier_call = scx200_wdt_notify_sys ,
} ;
static ssize_t scx200_wdt_write ( struct file * file , const char __user * data ,
size_t len , loff_t * ppos )
{
/* check for a magic close character */
2008-05-19 17:08:49 +04:00
if ( len ) {
2005-04-17 02:20:36 +04:00
size_t i ;
scx200_wdt_ping ( ) ;
expect_close = 0 ;
for ( i = 0 ; i < len ; + + i ) {
char c ;
2008-08-07 00:19:41 +04:00
if ( get_user ( c , data + i ) )
2005-04-17 02:20:36 +04:00
return - EFAULT ;
if ( c = = ' V ' )
expect_close = 42 ;
}
return len ;
}
return 0 ;
}
2008-05-19 17:08:49 +04:00
static long scx200_wdt_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 ;
2008-05-19 17:08:49 +04:00
static const struct watchdog_info ident = {
2005-04-17 02:20:36 +04:00
. identity = " NatSemi SCx200 Watchdog " ,
. firmware_version = 1 ,
2009-05-11 22:33:00 +04:00
. options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
WDIOF_MAGICCLOSE ,
2005-04-17 02:20:36 +04:00
} ;
int new_margin ;
switch ( cmd ) {
case WDIOC_GETSUPPORT :
2008-05-19 17:08:49 +04:00
if ( copy_to_user ( argp , & ident , sizeof ( ident ) ) )
2005-04-17 02:20:36 +04:00
return - EFAULT ;
return 0 ;
case WDIOC_GETSTATUS :
case WDIOC_GETBOOTSTATUS :
if ( put_user ( 0 , p ) )
return - EFAULT ;
return 0 ;
case WDIOC_KEEPALIVE :
scx200_wdt_ping ( ) ;
return 0 ;
case WDIOC_SETTIMEOUT :
if ( get_user ( new_margin , p ) )
return - EFAULT ;
if ( new_margin < 1 )
return - EINVAL ;
margin = new_margin ;
scx200_wdt_update_margin ( ) ;
scx200_wdt_ping ( ) ;
2020-07-17 19:40:59 +03:00
fallthrough ;
2005-04-17 02:20:36 +04:00
case WDIOC_GETTIMEOUT :
if ( put_user ( margin , p ) )
return - EFAULT ;
return 0 ;
2008-07-18 15:41:17 +04:00
default :
return - ENOTTY ;
2005-04-17 02:20:36 +04:00
}
}
2006-07-03 11:24:21 +04:00
static const struct file_operations scx200_wdt_fops = {
2008-05-19 17:08:49 +04:00
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. write = scx200_wdt_write ,
. unlocked_ioctl = scx200_wdt_ioctl ,
2019-06-03 15:23:09 +03:00
. compat_ioctl = compat_ptr_ioctl ,
2008-05-19 17:08:49 +04:00
. open = scx200_wdt_open ,
2005-04-17 02:20:36 +04:00
. release = scx200_wdt_release ,
} ;
static struct miscdevice scx200_wdt_miscdev = {
. minor = WATCHDOG_MINOR ,
2008-05-19 17:08:49 +04:00
. name = " watchdog " ,
. fops = & scx200_wdt_fops ,
2005-04-17 02:20:36 +04:00
} ;
static int __init scx200_wdt_init ( void )
{
int r ;
2012-02-16 03:06:19 +04:00
pr_debug ( " NatSemi SCx200 Watchdog Driver \n " ) ;
2005-04-17 02:20:36 +04:00
/* check that we have found the configuration block */
if ( ! scx200_cb_present ( ) )
return - ENODEV ;
if ( ! request_region ( scx200_cb_base + SCx200_WDT_OFFSET ,
SCx200_WDT_SIZE ,
" NatSemi SCx200 Watchdog " ) ) {
2012-02-16 03:06:19 +04:00
pr_warn ( " watchdog I/O region busy \n " ) ;
2005-04-17 02:20:36 +04:00
return - EBUSY ;
}
scx200_wdt_update_margin ( ) ;
scx200_wdt_disable ( ) ;
2007-12-26 23:32:51 +03:00
r = register_reboot_notifier ( & scx200_wdt_notifier ) ;
2005-04-17 02:20:36 +04:00
if ( r ) {
2012-02-16 03:06:19 +04:00
pr_err ( " unable to register reboot notifier \n " ) ;
2005-04-17 02:20:36 +04:00
release_region ( scx200_cb_base + SCx200_WDT_OFFSET ,
SCx200_WDT_SIZE ) ;
return r ;
}
2007-12-26 23:32:51 +03:00
r = misc_register ( & scx200_wdt_miscdev ) ;
2005-04-17 02:20:36 +04:00
if ( r ) {
2007-12-26 23:32:51 +03:00
unregister_reboot_notifier ( & scx200_wdt_notifier ) ;
2005-04-17 02:20:36 +04:00
release_region ( scx200_cb_base + SCx200_WDT_OFFSET ,
SCx200_WDT_SIZE ) ;
return r ;
}
return 0 ;
}
static void __exit scx200_wdt_cleanup ( void )
{
misc_deregister ( & scx200_wdt_miscdev ) ;
2007-12-26 23:32:51 +03:00
unregister_reboot_notifier ( & scx200_wdt_notifier ) ;
2005-04-17 02:20:36 +04:00
release_region ( scx200_cb_base + SCx200_WDT_OFFSET ,
SCx200_WDT_SIZE ) ;
}
module_init ( scx200_wdt_init ) ;
module_exit ( scx200_wdt_cleanup ) ;