2005-04-17 02:20:36 +04:00
/*
* MachZ ZF - Logic Watchdog Timer driver for Linux
*
*
* 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 .
*
* 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
* a system RESET and it starts wd # 2 that unconditionaly will RESET
* 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
*/
# 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>
# include <asm/io.h>
# include <asm/uaccess.h>
# include <asm/system.h>
/* 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 */
# define CONTROL 0x10 /* 16 */
# 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 " ) ;
MODULE_ALIAS_MISCDEV ( WATCHDOG_MINOR ) ;
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 ) ;
2007-01-27 22:54:24 +03: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"
static struct watchdog_info zf_info = {
. 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 )
*/
static int action = 0 ;
module_param ( action , int , 0 ) ;
MODULE_PARM_DESC ( action , " after watchdog resets, generate: 0 = RESET(*) 1 = SMI 2 = NMI 3 = SCI " ) ;
2007-02-08 20:39:36 +03:00
static void zf_ping ( unsigned long data ) ;
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_lock ) ;
static DEFINE_SPINLOCK ( zf_port_lock ) ;
2007-02-08 20:39:36 +03:00
static DEFINE_TIMER ( zf_timer , zf_ping , 0 , 0 ) ;
2005-04-17 02:20:36 +04:00
static unsigned long next_heartbeat = 0 ;
/* 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
# define dprintk(format, args...)
# else
2008-03-06 05:24:58 +03:00
# define dprintk(format, args...) printk(KERN_DEBUG PFX ":%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 )
{
switch ( n ) {
case WD1 :
zf_writew ( COUNTER_1 , new ) ;
case WD2 :
zf_writeb ( COUNTER_2 , new > 0xff ? 0xff : new ) ;
default :
return ;
}
}
/*
* 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 ) ;
printk ( KERN_INFO PFX " : Watchdog timer is now disabled \n " ) ;
}
/*
* 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 ) ;
printk ( KERN_INFO PFX " : Watchdog timer is now enabled \n " ) ;
}
static void zf_ping ( unsigned long data )
{
unsigned int ctrl_reg = 0 ;
unsigned long flags ;
zf_writeb ( COUNTER_2 , 0xff ) ;
if ( time_before ( jiffies , next_heartbeat ) ) {
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 ) ;
2005-04-17 02:20:36 +04:00
} else {
printk ( KERN_CRIT PFX " : I will reset your machine \n " ) ;
}
}
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 */
if ( count ) {
/*
* 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 */
for ( ofs = 0 ; ofs ! = count ; ofs + + ) {
char c ;
if ( get_user ( c , buf + ofs ) )
return - EFAULT ;
if ( c = = ' V ' ) {
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 ;
}
static int zf_ioctl ( struct inode * inode , struct file * file , unsigned int cmd ,
unsigned long arg )
{
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 ;
2005-04-17 02:20:36 +04:00
2007-03-18 12:26:10 +03:00
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 ) ;
2005-04-17 02:20:36 +04:00
2007-03-18 12:26:10 +03:00
case WDIOC_KEEPALIVE :
zf_ping ( 0 ) ;
break ;
2005-04-17 02:20:36 +04:00
2007-03-18 12:26:10 +03:00
default :
return - ENOTTY ;
2005-04-17 02:20:36 +04:00
}
return 0 ;
}
static int zf_open ( struct inode * inode , struct file * file )
{
spin_lock ( & zf_lock ) ;
if ( test_and_set_bit ( 0 , & zf_is_open ) ) {
spin_unlock ( & zf_lock ) ;
return - EBUSY ;
}
if ( nowayout )
__module_get ( THIS_MODULE ) ;
spin_unlock ( & zf_lock ) ;
zf_timer_on ( ) ;
return nonseekable_open ( inode , file ) ;
}
static int zf_close ( struct inode * inode , struct file * file )
{
if ( zf_expect_close = = 42 ) {
zf_timer_off ( ) ;
} else {
del_timer ( & zf_timer ) ;
printk ( KERN_ERR PFX " : device file closed unexpectedly. Will not stop the WDT! \n " ) ;
}
spin_lock ( & zf_lock ) ;
clear_bit ( 0 , & zf_is_open ) ;
spin_unlock ( & zf_lock ) ;
zf_expect_close = 0 ;
return 0 ;
}
/*
* Notifier for system down
*/
static int zf_notify_sys ( struct notifier_block * this , unsigned long code ,
void * unused )
{
if ( code = = SYS_DOWN | | code = = SYS_HALT ) {
zf_timer_off ( ) ;
}
return NOTIFY_DONE ;
}
2006-07-03 11:24:21 +04:00
static const struct file_operations zf_fops = {
2005-04-17 02:20:36 +04:00
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. write = zf_write ,
. ioctl = zf_ioctl ,
. open = zf_open ,
. release = zf_close ,
} ;
static struct miscdevice zf_miscdev = {
. minor = WATCHDOG_MINOR ,
. name = " watchdog " ,
. fops = & zf_fops ,
} ;
/*
* 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 )
{
char * str [ ] = { " RESET " , " SMI " , " NMI " , " SCI " } ;
printk ( KERN_INFO PFX " : Watchdog using action = %s \n " , str [ act ] ) ;
}
static int __init zf_init ( void )
{
int ret ;
printk ( KERN_INFO PFX " : MachZ ZF-Logic Watchdog driver initializing. \n " ) ;
ret = zf_get_ZFL_version ( ) ;
2006-08-01 22:06:43 +04:00
if ( ( ! ret ) | | ( ret = = 0xffff ) ) {
2005-04-17 02:20:36 +04:00
printk ( KERN_WARNING PFX " : no ZF-Logic found \n " ) ;
return - ENODEV ;
}
if ( ( action < = 3 ) & & ( action > = 0 ) ) {
zf_action = zf_action > > action ;
} else
action = 0 ;
zf_show_action ( action ) ;
if ( ! request_region ( ZF_IOBASE , 3 , " MachZ ZFL WDT " ) ) {
printk ( KERN_ERR " cannot reserve I/O ports at %d \n " ,
ZF_IOBASE ) ;
ret = - EBUSY ;
goto no_region ;
}
ret = register_reboot_notifier ( & zf_notifier ) ;
if ( ret ) {
printk ( KERN_ERR " can't register reboot notifier (err=%d) \n " ,
ret ) ;
goto no_reboot ;
}
2007-03-24 15:58:12 +03:00
ret = misc_register ( & zf_miscdev ) ;
if ( ret ) {
printk ( KERN_ERR " can't misc_register on minor=%d \n " ,
WATCHDOG_MINOR ) ;
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 ) ;