2019-05-27 09:55:01 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
2005-04-17 02:20:36 +04:00
/*
* MachZ ZF - Logic Watchdog Timer driver for Linux
*
* The author does NOT admit liability nor provide warranty for
* any of this software . This material is provided " AS-IS " in
* the hope that it may be useful for others .
*
* Author : Fernando Fuganti < fuganti @ conectiva . com . br >
*
* Based on sbc60xxwdt . c by Jakob Oestergaard
*
* We have two timers ( wd # 1 , wd # 2 ) driven by a 32 KHz clock with the
* following periods :
* wd # 1 - 2 seconds ;
* wd # 2 - 7.2 ms ;
* After the expiration of wd # 1 , it can generate a NMI , SCI , SMI , or
tree-wide: fix assorted typos all over the place
That is "success", "unknown", "through", "performance", "[re|un]mapping"
, "access", "default", "reasonable", "[con]currently", "temperature"
, "channel", "[un]used", "application", "example","hierarchy", "therefore"
, "[over|under]flow", "contiguous", "threshold", "enough" and others.
Signed-off-by: André Goddard Rosa <andre.goddard@gmail.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
2009-11-14 18:09:05 +03:00
* a system RESET and it starts wd # 2 that unconditionally will RESET
2005-04-17 02:20:36 +04:00
* the system when the counter reaches zero .
*
* 14 - Dec - 2001 Matt Domsch < Matt_Domsch @ dell . com >
* Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT
*/
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/types.h>
# include <linux/timer.h>
# include <linux/jiffies.h>
# include <linux/miscdevice.h>
# include <linux/watchdog.h>
# include <linux/fs.h>
# include <linux/ioport.h>
# include <linux/notifier.h>
# include <linux/reboot.h>
# include <linux/init.h>
2008-05-19 17:06:59 +04:00
# include <linux/io.h>
# include <linux/uaccess.h>
2005-04-17 02:20:36 +04:00
/* ports */
# define ZF_IOBASE 0x218
# define INDEX 0x218
# define DATA_B 0x219
# define DATA_W 0x21A
# define DATA_D 0x21A
/* indexes */ /* size */
# define ZFL_VERSION 0x02 /* 16 */
2011-02-23 23:04:38 +03:00
# define CONTROL 0x10 /* 16 */
2005-04-17 02:20:36 +04:00
# define STATUS 0x12 /* 8 */
# define COUNTER_1 0x0C /* 16 */
# define COUNTER_2 0x0E /* 8 */
# define PULSE_LEN 0x0F /* 8 */
/* controls */
# define ENABLE_WD1 0x0001
# define ENABLE_WD2 0x0002
# define RESET_WD1 0x0010
# define RESET_WD2 0x0020
# define GEN_SCI 0x0100
# define GEN_NMI 0x0200
# define GEN_SMI 0x0400
# define GEN_RESET 0x0800
/* utilities */
# define WD1 0
# define WD2 1
# define zf_writew(port, data) { outb(port, INDEX); outw(data, DATA_W); }
# define zf_writeb(port, data) { outb(port, INDEX); outb(data, DATA_B); }
# define zf_get_ZFL_version() zf_readw(ZFL_VERSION)
static unsigned short zf_readw ( unsigned char port )
{
outb ( port , INDEX ) ;
return inw ( DATA_W ) ;
}
MODULE_AUTHOR ( " Fernando Fuganti <fuganti@conectiva.com.br> " ) ;
MODULE_DESCRIPTION ( " MachZ ZF-Logic Watchdog driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
2012-03-05 19:51:11 +04:00
static bool nowayout = WATCHDOG_NOWAYOUT ;
module_param ( nowayout , bool , 0 ) ;
2008-05-19 17:06:59 +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
# define PFX "machzwd"
2009-12-26 21:55:22 +03:00
static const struct watchdog_info zf_info = {
2005-04-17 02:20:36 +04:00
. options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE ,
. firmware_version = 1 ,
. identity = " ZF-Logic watchdog " ,
} ;
/*
* action refers to action taken when watchdog resets
* 0 = GEN_RESET
* 1 = GEN_SMI
* 2 = GEN_NMI
* 3 = GEN_SCI
* defaults to GEN_RESET ( 0 )
*/
2008-05-19 17:06:59 +04:00
static int action ;
2005-04-17 02:20:36 +04:00
module_param ( action , int , 0 ) ;
2009-04-15 00:20:07 +04:00
MODULE_PARM_DESC ( action , " after watchdog resets, generate: "
" 0 = RESET(*) 1 = SMI 2 = NMI 3 = SCI " ) ;
2005-04-17 02:20:36 +04:00
2017-08-28 21:28:21 +03:00
static void zf_ping ( struct timer_list * unused ) ;
2007-02-08 20:39:36 +03:00
2005-04-17 02:20:36 +04:00
static int zf_action = GEN_RESET ;
static unsigned long zf_is_open ;
static char zf_expect_close ;
2007-11-02 02:27:08 +03:00
static DEFINE_SPINLOCK ( zf_port_lock ) ;
2017-10-05 02:27:04 +03:00
static DEFINE_TIMER ( zf_timer , zf_ping ) ;
2008-05-19 17:06:59 +04:00
static unsigned long next_heartbeat ;
2005-04-17 02:20:36 +04:00
/* timeout for user land heart beat (10 seconds) */
# define ZF_USER_TIMEO (HZ*10)
/* timeout for hardware watchdog (~500ms) */
# define ZF_HW_TIMEO (HZ / 2)
/* number of ticks on WD#1 (driven by a 32KHz clock, 2s) */
# define ZF_CTIMEOUT 0xffff
# ifndef ZF_DEBUG
2012-02-16 03:06:19 +04:00
# define dprintk(format, args...)
2005-04-17 02:20:36 +04:00
# else
2012-02-16 03:06:19 +04:00
# define dprintk(format, args...) \
pr_debug ( " :%s:%d: " format , __func__ , __LINE__ , # # args )
2005-04-17 02:20:36 +04:00
# endif
static inline void zf_set_status ( unsigned char new )
{
zf_writeb ( STATUS , new ) ;
}
/* CONTROL register functions */
static inline unsigned short zf_get_control ( void )
{
return zf_readw ( CONTROL ) ;
}
static inline void zf_set_control ( unsigned short new )
{
zf_writew ( CONTROL , new ) ;
}
/* WD#? counter functions */
/*
* Just set counter value
*/
static inline void zf_set_timer ( unsigned short new , unsigned char n )
{
2008-05-19 17:06:59 +04:00
switch ( n ) {
case WD1 :
zf_writew ( COUNTER_1 , new ) ;
2020-07-07 20:11:21 +03:00
fallthrough ;
2008-05-19 17:06:59 +04:00
case WD2 :
zf_writeb ( COUNTER_2 , new > 0xff ? 0xff : new ) ;
default :
return ;
2005-04-17 02:20:36 +04:00
}
}
/*
* stop hardware timer
*/
static void zf_timer_off ( void )
{
unsigned int ctrl_reg = 0 ;
unsigned long flags ;
/* stop internal ping */
del_timer_sync ( & zf_timer ) ;
spin_lock_irqsave ( & zf_port_lock , flags ) ;
/* stop watchdog timer */
ctrl_reg = zf_get_control ( ) ;
ctrl_reg | = ( ENABLE_WD1 | ENABLE_WD2 ) ; /* disable wd1 and wd2 */
ctrl_reg & = ~ ( ENABLE_WD1 | ENABLE_WD2 ) ;
zf_set_control ( ctrl_reg ) ;
spin_unlock_irqrestore ( & zf_port_lock , flags ) ;
2012-02-16 03:06:19 +04:00
pr_info ( " Watchdog timer is now disabled \n " ) ;
2005-04-17 02:20:36 +04:00
}
/*
* start hardware timer
*/
static void zf_timer_on ( void )
{
unsigned int ctrl_reg = 0 ;
unsigned long flags ;
spin_lock_irqsave ( & zf_port_lock , flags ) ;
zf_writeb ( PULSE_LEN , 0xff ) ;
zf_set_timer ( ZF_CTIMEOUT , WD1 ) ;
/* user land ping */
next_heartbeat = jiffies + ZF_USER_TIMEO ;
/* start the timer for internal ping */
2007-02-08 20:39:36 +03:00
mod_timer ( & zf_timer , jiffies + ZF_HW_TIMEO ) ;
2005-04-17 02:20:36 +04:00
/* start watchdog timer */
ctrl_reg = zf_get_control ( ) ;
ctrl_reg | = ( ENABLE_WD1 | zf_action ) ;
zf_set_control ( ctrl_reg ) ;
spin_unlock_irqrestore ( & zf_port_lock , flags ) ;
2012-02-16 03:06:19 +04:00
pr_info ( " Watchdog timer is now enabled \n " ) ;
2005-04-17 02:20:36 +04:00
}
2017-08-28 21:28:21 +03:00
static void zf_ping ( struct timer_list * unused )
2005-04-17 02:20:36 +04:00
{
unsigned int ctrl_reg = 0 ;
unsigned long flags ;
zf_writeb ( COUNTER_2 , 0xff ) ;
2008-05-19 17:06:59 +04:00
if ( time_before ( jiffies , next_heartbeat ) ) {
2005-04-17 02:20:36 +04:00
dprintk ( " time_before: %ld \n " , next_heartbeat - jiffies ) ;
/*
* reset event is activated by transition from 0 to 1 on
* RESET_WD1 bit and we assume that it is already zero . . .
*/
spin_lock_irqsave ( & zf_port_lock , flags ) ;
ctrl_reg = zf_get_control ( ) ;
ctrl_reg | = RESET_WD1 ;
zf_set_control ( ctrl_reg ) ;
/* ...and nothing changes until here */
ctrl_reg & = ~ ( RESET_WD1 ) ;
zf_set_control ( ctrl_reg ) ;
spin_unlock_irqrestore ( & zf_port_lock , flags ) ;
2007-02-08 20:39:36 +03:00
mod_timer ( & zf_timer , jiffies + ZF_HW_TIMEO ) ;
2008-05-19 17:06:59 +04:00
} else
2012-02-16 03:06:19 +04:00
pr_crit ( " I will reset your machine \n " ) ;
2005-04-17 02:20:36 +04:00
}
static ssize_t zf_write ( struct file * file , const char __user * buf , size_t count ,
loff_t * ppos )
{
/* See if we got the magic character */
2008-05-19 17:06:59 +04:00
if ( count ) {
2005-04-17 02:20:36 +04:00
/*
* no need to check for close confirmation
* no way to disable watchdog ; )
*/
if ( ! nowayout ) {
size_t ofs ;
/*
* note : just in case someone wrote the magic character
* five months ago . . .
*/
zf_expect_close = 0 ;
/* now scan */
2008-05-19 17:06:59 +04:00
for ( ofs = 0 ; ofs ! = count ; ofs + + ) {
2005-04-17 02:20:36 +04:00
char c ;
if ( get_user ( c , buf + ofs ) )
return - EFAULT ;
2008-05-19 17:06:59 +04:00
if ( c = = ' V ' ) {
2005-04-17 02:20:36 +04:00
zf_expect_close = 42 ;
dprintk ( " zf_expect_close = 42 \n " ) ;
}
}
}
/*
* Well , anyhow someone wrote to us ,
* we should return that favour
*/
next_heartbeat = jiffies + ZF_USER_TIMEO ;
dprintk ( " user ping at %ld \n " , jiffies ) ;
}
return count ;
}
2008-05-19 17:06:59 +04:00
static long zf_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 ;
2007-03-18 12:26:10 +03:00
switch ( cmd ) {
case WDIOC_GETSUPPORT :
if ( copy_to_user ( argp , & zf_info , sizeof ( zf_info ) ) )
return - EFAULT ;
break ;
case WDIOC_GETSTATUS :
2007-07-21 17:42:18 +04:00
case WDIOC_GETBOOTSTATUS :
2007-03-18 12:26:10 +03:00
return put_user ( 0 , p ) ;
case WDIOC_KEEPALIVE :
2019-04-06 16:14:15 +03:00
zf_ping ( NULL ) ;
2007-03-18 12:26:10 +03:00
break ;
default :
return - ENOTTY ;
2005-04-17 02:20:36 +04:00
}
return 0 ;
}
static int zf_open ( struct inode * inode , struct file * file )
{
2008-05-19 17:06:59 +04:00
if ( test_and_set_bit ( 0 , & zf_is_open ) )
2005-04-17 02:20:36 +04:00
return - EBUSY ;
if ( nowayout )
__module_get ( THIS_MODULE ) ;
zf_timer_on ( ) ;
2019-03-26 23:51:19 +03:00
return stream_open ( inode , file ) ;
2005-04-17 02:20:36 +04:00
}
static int zf_close ( struct inode * inode , struct file * file )
{
2008-05-19 17:06:59 +04:00
if ( zf_expect_close = = 42 )
2005-04-17 02:20:36 +04:00
zf_timer_off ( ) ;
2008-05-19 17:06:59 +04:00
else {
2005-04-17 02:20:36 +04:00
del_timer ( & zf_timer ) ;
2012-02-16 03:06:19 +04:00
pr_err ( " device file closed unexpectedly. Will not stop the WDT! \n " ) ;
2005-04-17 02:20:36 +04:00
}
clear_bit ( 0 , & zf_is_open ) ;
zf_expect_close = 0 ;
return 0 ;
}
/*
* Notifier for system down
*/
static int zf_notify_sys ( struct notifier_block * this , unsigned long code ,
void * unused )
{
2008-05-19 17:06:59 +04:00
if ( code = = SYS_DOWN | | code = = SYS_HALT )
2005-04-17 02:20:36 +04:00
zf_timer_off ( ) ;
return NOTIFY_DONE ;
}
2006-07-03 11:24:21 +04:00
static const struct file_operations zf_fops = {
2008-05-19 17:06:59 +04:00
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. write = zf_write ,
. unlocked_ioctl = zf_ioctl ,
2019-06-03 15:23:09 +03:00
. compat_ioctl = compat_ptr_ioctl ,
2008-05-19 17:06:59 +04:00
. open = zf_open ,
. release = zf_close ,
2005-04-17 02:20:36 +04:00
} ;
static struct miscdevice zf_miscdev = {
. minor = WATCHDOG_MINOR ,
. name = " watchdog " ,
. fops = & zf_fops ,
} ;
2008-05-19 17:06:59 +04:00
2005-04-17 02:20:36 +04:00
/*
* The device needs to learn about soft shutdowns in order to
* turn the timebomb registers off .
*/
static struct notifier_block zf_notifier = {
. notifier_call = zf_notify_sys ,
} ;
static void __init zf_show_action ( int act )
{
2010-09-14 08:23:59 +04:00
static const char * const str [ ] = { " RESET " , " SMI " , " NMI " , " SCI " } ;
2005-04-17 02:20:36 +04:00
2012-02-16 03:06:19 +04:00
pr_info ( " Watchdog using action = %s \n " , str [ act ] ) ;
2005-04-17 02:20:36 +04:00
}
static int __init zf_init ( void )
{
int ret ;
2012-02-16 03:06:19 +04:00
pr_info ( " MachZ ZF-Logic Watchdog driver initializing \n " ) ;
2005-04-17 02:20:36 +04:00
ret = zf_get_ZFL_version ( ) ;
2008-05-19 17:06:59 +04:00
if ( ! ret | | ret = = 0xffff ) {
2012-02-16 03:06:19 +04:00
pr_warn ( " no ZF-Logic found \n " ) ;
2005-04-17 02:20:36 +04:00
return - ENODEV ;
}
2008-05-19 17:06:59 +04:00
if ( action < = 3 & & action > = 0 )
zf_action = zf_action > > action ;
else
2005-04-17 02:20:36 +04:00
action = 0 ;
zf_show_action ( action ) ;
2008-05-19 17:06:59 +04:00
if ( ! request_region ( ZF_IOBASE , 3 , " MachZ ZFL WDT " ) ) {
2012-02-16 03:06:19 +04:00
pr_err ( " cannot reserve I/O ports at %d \n " , ZF_IOBASE ) ;
2005-04-17 02:20:36 +04:00
ret = - EBUSY ;
goto no_region ;
}
ret = register_reboot_notifier ( & zf_notifier ) ;
2008-05-19 17:06:59 +04:00
if ( ret ) {
2012-02-16 03:06:19 +04:00
pr_err ( " can't register reboot notifier (err=%d) \n " , ret ) ;
2005-04-17 02:20:36 +04:00
goto no_reboot ;
}
2007-03-24 15:58:12 +03:00
ret = misc_register ( & zf_miscdev ) ;
2008-05-19 17:06:59 +04:00
if ( ret ) {
2012-02-16 03:06:19 +04:00
pr_err ( " can't misc_register on minor=%d \n " , WATCHDOG_MINOR ) ;
2007-03-24 15:58:12 +03:00
goto no_misc ;
}
2005-04-17 02:20:36 +04:00
zf_set_status ( 0 ) ;
zf_set_control ( 0 ) ;
return 0 ;
2007-03-24 15:58:12 +03:00
no_misc :
unregister_reboot_notifier ( & zf_notifier ) ;
2005-04-17 02:20:36 +04:00
no_reboot :
release_region ( ZF_IOBASE , 3 ) ;
no_region :
return ret ;
}
static void __exit zf_exit ( void )
{
zf_timer_off ( ) ;
misc_deregister ( & zf_miscdev ) ;
unregister_reboot_notifier ( & zf_notifier ) ;
release_region ( ZF_IOBASE , 3 ) ;
}
module_init ( zf_init ) ;
module_exit ( zf_exit ) ;