2005-04-17 02:20:36 +04:00
/*
* sma cpu5 watchdog driver
*
* Copyright ( C ) 2003 Heiko Ronsdorf < hero @ ihg . uni - duisburg . de >
*
* 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 .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*
*/
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/types.h>
# include <linux/errno.h>
# include <linux/miscdevice.h>
# include <linux/fs.h>
# include <linux/init.h>
# include <linux/ioport.h>
# include <linux/timer.h>
2006-01-10 02:59:26 +03:00
# include <linux/completion.h>
2005-10-31 02:03:48 +03:00
# include <linux/jiffies.h>
2008-05-19 17:05:24 +04:00
# include <linux/io.h>
# include <linux/uaccess.h>
2005-04-17 02:20:36 +04:00
# include <linux/watchdog.h>
/* adjustable parameters */
2008-05-19 17:05:24 +04:00
static int verbose ;
2005-04-17 02:20:36 +04:00
static int port = 0x91 ;
static int ticks = 10000 ;
2008-05-19 17:05:24 +04:00
static spinlock_t cpu5wdt_lock ;
2005-04-17 02:20:36 +04:00
# define PFX "cpu5wdt: "
# define CPU5WDT_EXTENT 0x0A
# define CPU5WDT_STATUS_REG 0x00
# define CPU5WDT_TIME_A_REG 0x02
# define CPU5WDT_TIME_B_REG 0x03
# define CPU5WDT_MODE_REG 0x04
# define CPU5WDT_TRIGGER_REG 0x07
# define CPU5WDT_ENABLE_REG 0x08
# define CPU5WDT_RESET_REG 0x09
# define CPU5WDT_INTERVAL (HZ / 10+1)
/* some device data */
static struct {
2006-01-10 02:59:26 +03:00
struct completion stop ;
2008-02-25 15:39:57 +03:00
int running ;
2005-04-17 02:20:36 +04:00
struct timer_list timer ;
2008-02-25 15:39:57 +03:00
int queue ;
2005-04-17 02:20:36 +04:00
int default_ticks ;
unsigned long inuse ;
} cpu5wdt_device ;
/* generic helper functions */
static void cpu5wdt_trigger ( unsigned long unused )
{
2008-05-19 17:05:24 +04:00
if ( verbose > 2 )
2005-04-17 02:20:36 +04:00
printk ( KERN_DEBUG PFX " trigger at %i ticks \n " , ticks ) ;
2008-05-19 17:05:24 +04:00
if ( cpu5wdt_device . running )
2005-04-17 02:20:36 +04:00
ticks - - ;
2008-05-19 17:05:24 +04:00
spin_lock ( & cpu5wdt_lock ) ;
2005-04-17 02:20:36 +04:00
/* keep watchdog alive */
outb ( 1 , port + CPU5WDT_TRIGGER_REG ) ;
/* requeue?? */
2007-02-08 20:39:36 +03:00
if ( cpu5wdt_device . queue & & ticks )
mod_timer ( & cpu5wdt_device . timer , jiffies + CPU5WDT_INTERVAL ) ;
2005-04-17 02:20:36 +04:00
else {
/* ticks doesn't matter anyway */
2006-01-10 02:59:26 +03:00
complete ( & cpu5wdt_device . stop ) ;
2005-04-17 02:20:36 +04:00
}
2008-05-19 17:05:24 +04:00
spin_unlock ( & cpu5wdt_lock ) ;
2005-04-17 02:20:36 +04:00
}
static void cpu5wdt_reset ( void )
{
ticks = cpu5wdt_device . default_ticks ;
2008-05-19 17:05:24 +04:00
if ( verbose )
2005-04-17 02:20:36 +04:00
printk ( KERN_DEBUG PFX " reset (%i ticks) \n " , ( int ) ticks ) ;
}
static void cpu5wdt_start ( void )
{
2008-05-19 17:05:24 +04:00
unsigned long flags ;
spin_lock_irqsave ( & cpu5wdt_lock , flags ) ;
if ( ! cpu5wdt_device . queue ) {
2005-04-17 02:20:36 +04:00
cpu5wdt_device . queue = 1 ;
outb ( 0 , port + CPU5WDT_TIME_A_REG ) ;
outb ( 0 , port + CPU5WDT_TIME_B_REG ) ;
outb ( 1 , port + CPU5WDT_MODE_REG ) ;
outb ( 0 , port + CPU5WDT_RESET_REG ) ;
outb ( 0 , port + CPU5WDT_ENABLE_REG ) ;
2007-02-08 20:39:36 +03:00
mod_timer ( & cpu5wdt_device . timer , jiffies + CPU5WDT_INTERVAL ) ;
2005-04-17 02:20:36 +04:00
}
/* if process dies, counter is not decremented */
cpu5wdt_device . running + + ;
2008-05-19 17:05:24 +04:00
spin_unlock_irqrestore ( & cpu5wdt_lock , flags ) ;
2005-04-17 02:20:36 +04:00
}
static int cpu5wdt_stop ( void )
{
2008-05-19 17:05:24 +04:00
unsigned long flags ;
2005-04-17 02:20:36 +04:00
2008-05-19 17:05:24 +04:00
spin_lock_irqsave ( & cpu5wdt_lock , flags ) ;
if ( cpu5wdt_device . running )
cpu5wdt_device . running = 0 ;
2005-04-17 02:20:36 +04:00
ticks = cpu5wdt_device . default_ticks ;
2008-05-19 17:05:24 +04:00
spin_unlock_irqrestore ( & cpu5wdt_lock , flags ) ;
if ( verbose )
2005-04-17 02:20:36 +04:00
printk ( KERN_CRIT PFX " stop not possible \n " ) ;
return - EIO ;
}
/* filesystem operations */
static int cpu5wdt_open ( struct inode * inode , struct file * file )
{
2008-05-19 17:05:24 +04:00
if ( test_and_set_bit ( 0 , & cpu5wdt_device . inuse ) )
2005-04-17 02:20:36 +04:00
return - EBUSY ;
return nonseekable_open ( inode , file ) ;
}
static int cpu5wdt_release ( struct inode * inode , struct file * file )
{
clear_bit ( 0 , & cpu5wdt_device . inuse ) ;
return 0 ;
}
2008-05-19 17:05:24 +04:00
static long cpu5wdt_ioctl ( struct file * file , unsigned int cmd ,
unsigned long arg )
2005-04-17 02:20:36 +04:00
{
void __user * argp = ( void __user * ) arg ;
2008-05-19 17:05:24 +04:00
int __user * p = argp ;
2005-04-17 02:20:36 +04:00
unsigned int value ;
2009-12-26 21:55:22 +03:00
static const struct watchdog_info ident = {
2005-04-17 02:20:36 +04:00
. options = WDIOF_CARDRESET ,
. identity = " CPU5 WDT " ,
} ;
2008-05-19 17:05:24 +04:00
switch ( cmd ) {
2008-07-18 15:41:17 +04:00
case WDIOC_GETSUPPORT :
if ( copy_to_user ( argp , & ident , sizeof ( ident ) ) )
return - EFAULT ;
2008-05-19 17:05:24 +04:00
break ;
case WDIOC_GETSTATUS :
value = inb ( port + CPU5WDT_STATUS_REG ) ;
value = ( value > > 2 ) & 1 ;
return put_user ( value , p ) ;
case WDIOC_GETBOOTSTATUS :
return put_user ( 0 , p ) ;
case WDIOC_SETOPTIONS :
if ( get_user ( value , p ) )
return - EFAULT ;
if ( value & WDIOS_ENABLECARD )
cpu5wdt_start ( ) ;
if ( value & WDIOS_DISABLECARD )
cpu5wdt_stop ( ) ;
break ;
2008-07-18 15:41:17 +04:00
case WDIOC_KEEPALIVE :
cpu5wdt_reset ( ) ;
break ;
2008-05-19 17:05:24 +04:00
default :
return - ENOTTY ;
2005-04-17 02:20:36 +04:00
}
return 0 ;
}
2008-05-19 17:05:24 +04:00
static ssize_t cpu5wdt_write ( struct file * file , const char __user * buf ,
size_t count , loff_t * ppos )
2005-04-17 02:20:36 +04:00
{
2008-05-19 17:05:24 +04:00
if ( ! count )
2005-04-17 02:20:36 +04:00
return - EIO ;
cpu5wdt_reset ( ) ;
return count ;
}
2006-07-03 11:24:21 +04:00
static const struct file_operations cpu5wdt_fops = {
2005-04-17 02:20:36 +04:00
. owner = THIS_MODULE ,
. llseek = no_llseek ,
2008-05-19 17:05:24 +04:00
. unlocked_ioctl = cpu5wdt_ioctl ,
2005-04-17 02:20:36 +04:00
. open = cpu5wdt_open ,
. write = cpu5wdt_write ,
. release = cpu5wdt_release ,
} ;
static struct miscdevice cpu5wdt_misc = {
. minor = WATCHDOG_MINOR ,
. name = " watchdog " ,
. fops = & cpu5wdt_fops ,
} ;
/* init/exit function */
static int __devinit cpu5wdt_init ( void )
{
unsigned int val ;
int err ;
2008-05-19 17:05:24 +04:00
if ( verbose )
printk ( KERN_DEBUG PFX
" port=0x%x, verbose=%i \n " , port , verbose ) ;
2005-04-17 02:20:36 +04:00
2008-05-19 17:05:24 +04:00
init_completion ( & cpu5wdt_device . stop ) ;
spin_lock_init ( & cpu5wdt_lock ) ;
cpu5wdt_device . queue = 0 ;
setup_timer ( & cpu5wdt_device . timer , cpu5wdt_trigger , 0 ) ;
cpu5wdt_device . default_ticks = ticks ;
if ( ! request_region ( port , CPU5WDT_EXTENT , PFX ) ) {
2005-04-17 02:20:36 +04:00
printk ( KERN_ERR PFX " request_region failed \n " ) ;
err = - EBUSY ;
goto no_port ;
}
/* watchdog reboot? */
val = inb ( port + CPU5WDT_STATUS_REG ) ;
val = ( val > > 2 ) & 1 ;
2008-05-19 17:05:24 +04:00
if ( ! val )
2005-04-17 02:20:36 +04:00
printk ( KERN_INFO PFX " sorry, was my fault \n " ) ;
2008-05-19 17:05:24 +04:00
err = misc_register ( & cpu5wdt_misc ) ;
if ( err < 0 ) {
printk ( KERN_ERR PFX " misc_register failed \n " ) ;
goto no_misc ;
}
2005-04-17 02:20:36 +04:00
printk ( KERN_INFO PFX " init success \n " ) ;
return 0 ;
no_misc :
2007-03-24 15:58:12 +03:00
release_region ( port , CPU5WDT_EXTENT ) ;
no_port :
2005-04-17 02:20:36 +04:00
return err ;
}
static int __devinit cpu5wdt_init_module ( void )
{
return cpu5wdt_init ( ) ;
}
static void __devexit cpu5wdt_exit ( void )
{
2008-05-19 17:05:24 +04:00
if ( cpu5wdt_device . queue ) {
2005-04-17 02:20:36 +04:00
cpu5wdt_device . queue = 0 ;
2006-01-10 02:59:26 +03:00
wait_for_completion ( & cpu5wdt_device . stop ) ;
2005-04-17 02:20:36 +04:00
}
misc_deregister ( & cpu5wdt_misc ) ;
release_region ( port , CPU5WDT_EXTENT ) ;
}
static void __devexit cpu5wdt_exit_module ( void )
{
cpu5wdt_exit ( ) ;
}
/* module entry points */
module_init ( cpu5wdt_init_module ) ;
module_exit ( cpu5wdt_exit_module ) ;
MODULE_AUTHOR ( " Heiko Ronsdorf <hero@ihg.uni-duisburg.de> " ) ;
MODULE_DESCRIPTION ( " sma cpu5 watchdog driver " ) ;
MODULE_SUPPORTED_DEVICE ( " sma cpu5 watchdog " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS_MISCDEV ( WATCHDOG_MINOR ) ;
module_param ( port , int , 0 ) ;
MODULE_PARM_DESC ( port , " base address of watchdog card, default is 0x91 " ) ;
module_param ( verbose , int , 0 ) ;
MODULE_PARM_DESC ( verbose , " be verbose, default is 0 (no) " ) ;
module_param ( ticks , int , 0 ) ;
MODULE_PARM_DESC ( ticks , " count down ticks, default is 10000 " ) ;