2019-05-27 09:55:01 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
2005-04-17 02:20:36 +04:00
/*
* MixCom Watchdog : A Simple Hardware Watchdog Device
* Based on Softdog driver by Alan Cox and PC Watchdog driver by Ken Hollis
*
* Author : Gergely Madarasz < gorgo @ itc . hu >
*
* Copyright ( c ) 1999 ITConsult - Pro Co . < info @ itc . hu >
*
* Version 0.1 ( 99 / 04 / 15 ) :
* - first version
*
* Version 0.2 ( 99 / 06 / 16 ) :
* - added kernel timer watchdog ping after close
* since the hardware does not support watchdog shutdown
*
* Version 0.3 ( 99 / 06 / 21 ) :
* - added WDIOC_GETSTATUS and WDIOC_GETSUPPORT ioctl calls
*
* Version 0.3 .1 ( 99 / 06 / 22 ) :
* - allow module removal while internal timer is active ,
* print warning about probable reset
*
* Version 0.4 ( 99 / 11 / 15 ) :
* - support for one more type board
*
* Version 0.5 ( 2001 / 12 / 14 ) Matt Domsch < Matt_Domsch @ dell . com >
2008-05-19 17:07:04 +04:00
* - added nowayout module option to override
* CONFIG_WATCHDOG_NOWAYOUT
2007-06-03 19:22:32 +04:00
*
* Version 0.6 ( 2002 / 04 / 12 ) : Rob Radez < rob @ osinvestor . com >
* - make mixcomwd_opened unsigned ,
* removed lock_kernel / unlock_kernel from mixcomwd_release ,
* modified ioctl a bit to conform to API
2005-04-17 02:20:36 +04:00
*/
2012-02-16 03:06:19 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2007-06-03 19:22:32 +04:00
# define VERSION "0.6"
2007-06-04 22:35:41 +04:00
# define WATCHDOG_NAME "mixcomwd"
2005-04-17 02:20:36 +04:00
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/types.h>
# include <linux/miscdevice.h>
# include <linux/ioport.h>
# include <linux/watchdog.h>
# include <linux/fs.h>
# include <linux/reboot.h>
# include <linux/init.h>
2005-10-31 02:03:48 +03:00
# include <linux/jiffies.h>
# include <linux/timer.h>
2008-05-19 17:07:04 +04:00
# include <linux/uaccess.h>
# include <linux/io.h>
2005-04-17 02:20:36 +04:00
2007-06-03 19:39:25 +04:00
/*
* We have two types of cards that can be probed :
* 1 ) The Mixcom cards : these cards can be found at addresses
* 0x180 , 0x280 , 0x380 with an additional offset of 0xc10 .
* ( Or 0xd90 , 0xe90 , 0xf90 ) .
* 2 ) The FlashCOM cards : these cards can be set up at
* 0x300 - > 0x378 , in 0x8 jumps with an offset of 0x04 .
* ( Or 0x304 - > 0x37c in 0x8 jumps ) .
* Each card has it ' s own ID .
*/
2005-04-17 02:20:36 +04:00
# define MIXCOM_ID 0x11
# define FLASHCOM_ID 0x18
2007-06-04 00:46:05 +04:00
static struct {
int ioport ;
int id ;
2012-11-19 22:24:05 +04:00
} mixcomwd_io_info [ ] = {
2007-06-04 00:46:05 +04:00
/* The Mixcom cards */
{ 0x0d90 , MIXCOM_ID } ,
{ 0x0e90 , MIXCOM_ID } ,
{ 0x0f90 , MIXCOM_ID } ,
/* The FlashCOM cards */
{ 0x0304 , FLASHCOM_ID } ,
{ 0x030c , FLASHCOM_ID } ,
{ 0x0314 , FLASHCOM_ID } ,
{ 0x031c , FLASHCOM_ID } ,
{ 0x0324 , FLASHCOM_ID } ,
{ 0x032c , FLASHCOM_ID } ,
{ 0x0334 , FLASHCOM_ID } ,
{ 0x033c , FLASHCOM_ID } ,
{ 0x0344 , FLASHCOM_ID } ,
{ 0x034c , FLASHCOM_ID } ,
{ 0x0354 , FLASHCOM_ID } ,
{ 0x035c , FLASHCOM_ID } ,
{ 0x0364 , FLASHCOM_ID } ,
{ 0x036c , FLASHCOM_ID } ,
{ 0x0374 , FLASHCOM_ID } ,
{ 0x037c , FLASHCOM_ID } ,
/* The end of the list */
{ 0x0000 , 0 } ,
} ;
2005-04-17 02:20:36 +04:00
2017-08-28 21:28:21 +03:00
static void mixcomwd_timerfun ( struct timer_list * unused ) ;
2007-02-08 20:39:36 +03:00
2005-04-17 02:20:36 +04:00
static unsigned long mixcomwd_opened ; /* long req'd for setbit --RR */
static int watchdog_port ;
static int mixcomwd_timer_alive ;
2017-10-05 02:27:04 +03:00
static DEFINE_TIMER ( mixcomwd_timer , mixcomwd_timerfun ) ;
2005-04-17 02:20:36 +04:00
static char expect_close ;
2012-03-05 19:51:11 +04:00
static bool nowayout = WATCHDOG_NOWAYOUT ;
module_param ( nowayout , bool , 0 ) ;
2008-05-19 17:07:04 +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
static void mixcomwd_ping ( void )
{
2008-05-19 17:07:04 +04:00
outb_p ( 55 , watchdog_port ) ;
2005-04-17 02:20:36 +04:00
return ;
}
2017-08-28 21:28:21 +03:00
static void mixcomwd_timerfun ( struct timer_list * unused )
2005-04-17 02:20:36 +04:00
{
mixcomwd_ping ( ) ;
2007-02-08 20:39:36 +03:00
mod_timer ( & mixcomwd_timer , jiffies + 5 * HZ ) ;
2005-04-17 02:20:36 +04:00
}
/*
* Allow only one person to hold it open
*/
static int mixcomwd_open ( struct inode * inode , struct file * file )
{
2008-05-19 17:07:04 +04:00
if ( test_and_set_bit ( 0 , & mixcomwd_opened ) )
2005-04-17 02:20:36 +04:00
return - EBUSY ;
2008-05-19 17:07:04 +04:00
2005-04-17 02:20:36 +04:00
mixcomwd_ping ( ) ;
2008-05-19 17:07:04 +04:00
if ( nowayout )
2005-04-17 02:20:36 +04:00
/*
* fops_get ( ) code via open ( ) has already done
* a try_module_get ( ) so it is safe to do the
* __module_get ( ) .
*/
__module_get ( THIS_MODULE ) ;
2008-05-19 17:07:04 +04:00
else {
if ( mixcomwd_timer_alive ) {
2005-04-17 02:20:36 +04:00
del_timer ( & mixcomwd_timer ) ;
2008-05-19 17:07:04 +04:00
mixcomwd_timer_alive = 0 ;
2005-04-17 02:20:36 +04:00
}
}
2019-03-26 23:51:19 +03:00
return stream_open ( inode , file ) ;
2005-04-17 02:20:36 +04:00
}
static int mixcomwd_release ( struct inode * inode , struct file * file )
{
if ( expect_close = = 42 ) {
2008-05-19 17:07:04 +04:00
if ( mixcomwd_timer_alive ) {
2012-02-16 03:06:19 +04:00
pr_err ( " release called while internal timer alive \n " ) ;
2005-04-17 02:20:36 +04:00
return - EBUSY ;
}
2008-05-19 17:07:04 +04:00
mixcomwd_timer_alive = 1 ;
2007-02-08 20:39:36 +03:00
mod_timer ( & mixcomwd_timer , jiffies + 5 * HZ ) ;
2008-05-19 17:07:04 +04:00
} else
2012-02-16 03:06:19 +04:00
pr_crit ( " WDT device closed unexpectedly. WDT will not stop! \n " ) ;
2005-04-17 02:20:36 +04:00
2008-05-19 17:07:04 +04:00
clear_bit ( 0 , & mixcomwd_opened ) ;
expect_close = 0 ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
2008-05-19 17:07:04 +04:00
static ssize_t mixcomwd_write ( struct file * file , const char __user * data ,
size_t len , loff_t * ppos )
2005-04-17 02:20:36 +04:00
{
2008-05-19 17:07:04 +04:00
if ( len ) {
2005-04-17 02:20:36 +04:00
if ( ! nowayout ) {
size_t i ;
/* In case it was set long ago */
expect_close = 0 ;
for ( i = 0 ; i ! = len ; i + + ) {
char c ;
if ( get_user ( c , data + i ) )
return - EFAULT ;
if ( c = = ' V ' )
expect_close = 42 ;
}
}
mixcomwd_ping ( ) ;
}
return len ;
}
2008-05-19 17:07:04 +04:00
static long mixcomwd_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 ;
int status ;
2009-12-26 21:55:22 +03:00
static const struct watchdog_info ident = {
2005-04-17 02:20:36 +04:00
. options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE ,
. firmware_version = 1 ,
. identity = " MixCOM watchdog " ,
} ;
2008-05-19 17:07:04 +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 ;
break ;
2008-05-19 17:07:04 +04:00
case WDIOC_GETSTATUS :
status = mixcomwd_opened ;
if ( ! nowayout )
status | = mixcomwd_timer_alive ;
return put_user ( status , p ) ;
case WDIOC_GETBOOTSTATUS :
return put_user ( 0 , p ) ;
case WDIOC_KEEPALIVE :
mixcomwd_ping ( ) ;
break ;
default :
return - ENOTTY ;
2005-04-17 02:20:36 +04:00
}
return 0 ;
}
2007-06-04 23:13:28 +04:00
static const struct file_operations mixcomwd_fops = {
2005-04-17 02:20:36 +04:00
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. write = mixcomwd_write ,
2008-05-19 17:07:04 +04:00
. unlocked_ioctl = mixcomwd_ioctl ,
2019-06-03 15:23:09 +03:00
. compat_ioctl = compat_ptr_ioctl ,
2005-04-17 02:20:36 +04:00
. open = mixcomwd_open ,
. release = mixcomwd_release ,
} ;
2007-06-04 23:13:28 +04:00
static struct miscdevice mixcomwd_miscdev = {
2005-04-17 02:20:36 +04:00
. minor = WATCHDOG_MINOR ,
. name = " watchdog " ,
. fops = & mixcomwd_fops ,
} ;
2007-06-03 23:01:38 +04:00
static int __init checkcard ( int port , int card_id )
2005-04-17 02:20:36 +04:00
{
int id ;
2008-05-19 17:07:04 +04:00
if ( ! request_region ( port , 1 , " MixCOM watchdog " ) )
2005-04-17 02:20:36 +04:00
return 0 ;
2008-05-19 17:07:04 +04:00
id = inb_p ( port ) ;
if ( card_id = = MIXCOM_ID )
2007-06-03 23:01:38 +04:00
id & = 0x3f ;
2008-05-19 17:07:04 +04:00
if ( id ! = card_id ) {
2005-04-17 02:20:36 +04:00
release_region ( port , 1 ) ;
return 0 ;
}
2007-06-03 23:01:38 +04:00
return 1 ;
}
2005-04-17 02:20:36 +04:00
static int __init mixcomwd_init ( void )
{
2008-05-19 17:07:04 +04:00
int i , ret , found = 0 ;
2005-04-17 02:20:36 +04:00
2007-06-04 00:46:05 +04:00
for ( i = 0 ; ! found & & mixcomwd_io_info [ i ] . ioport ! = 0 ; i + + ) {
if ( checkcard ( mixcomwd_io_info [ i ] . ioport ,
mixcomwd_io_info [ i ] . id ) ) {
2005-04-17 02:20:36 +04:00
found = 1 ;
2007-06-04 00:46:05 +04:00
watchdog_port = mixcomwd_io_info [ i ] . ioport ;
2005-04-17 02:20:36 +04:00
}
}
if ( ! found ) {
2012-02-16 03:06:19 +04:00
pr_err ( " No card detected, or port not available \n " ) ;
2005-04-17 02:20:36 +04:00
return - ENODEV ;
}
ret = misc_register ( & mixcomwd_miscdev ) ;
2008-05-19 17:07:04 +04:00
if ( ret ) {
2012-02-16 03:06:19 +04:00
pr_err ( " cannot register miscdev on minor=%d (err=%d) \n " ,
WATCHDOG_MINOR , ret ) ;
2007-06-04 22:35:41 +04:00
goto error_misc_register_watchdog ;
2005-04-17 02:20:36 +04:00
}
2012-02-16 03:06:19 +04:00
pr_info ( " MixCOM watchdog driver v%s, watchdog port at 0x%3x \n " ,
VERSION , watchdog_port ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
2007-06-04 22:35:41 +04:00
error_misc_register_watchdog :
release_region ( watchdog_port , 1 ) ;
watchdog_port = 0x0000 ;
return ret ;
2005-04-17 02:20:36 +04:00
}
static void __exit mixcomwd_exit ( void )
{
if ( ! nowayout ) {
2008-05-19 17:07:04 +04:00
if ( mixcomwd_timer_alive ) {
2012-02-16 03:06:19 +04:00
pr_warn ( " I quit now, hardware will probably reboot! \n " ) ;
2007-02-08 20:39:36 +03:00
del_timer_sync ( & mixcomwd_timer ) ;
2008-05-19 17:07:04 +04:00
mixcomwd_timer_alive = 0 ;
2005-04-17 02:20:36 +04:00
}
}
misc_deregister ( & mixcomwd_miscdev ) ;
2008-05-19 17:07:04 +04:00
release_region ( watchdog_port , 1 ) ;
2005-04-17 02:20:36 +04:00
}
module_init ( mixcomwd_init ) ;
module_exit ( mixcomwd_exit ) ;
MODULE_AUTHOR ( " Gergely Madarasz <gorgo@itc.hu> " ) ;
MODULE_DESCRIPTION ( " MixCom Watchdog driver " ) ;
2007-06-04 22:35:41 +04:00
MODULE_VERSION ( VERSION ) ;
2005-04-17 02:20:36 +04:00
MODULE_LICENSE ( " GPL " ) ;