2009-07-21 14:13:29 +04:00
/*
* Watchdog driver for SBC - FITPC2 board
*
* Author : Denis Turischev < denis @ compulab . co . il >
*
* Adapted from the IXP2000 watchdog driver by Deepak Saxena .
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed " as is " without any
* warranty of any kind , whether express or implied .
*/
# define pr_fmt(fmt) KBUILD_MODNAME " WATCHDOG: " fmt
# include <linux/module.h>
# include <linux/types.h>
# include <linux/miscdevice.h>
# include <linux/watchdog.h>
# include <linux/ioport.h>
# include <linux/delay.h>
# include <linux/fs.h>
# include <linux/init.h>
# include <linux/moduleparam.h>
# include <linux/dmi.h>
# include <linux/io.h>
# include <linux/uaccess.h>
2012-03-05 19:51:11 +04:00
static bool nowayout = WATCHDOG_NOWAYOUT ;
2009-07-21 14:13:29 +04:00
static unsigned int margin = 60 ; /* (secs) Default is 1 minute */
static unsigned long wdt_status ;
2010-04-22 20:54:20 +04:00
static DEFINE_MUTEX ( wdt_lock ) ;
2009-07-21 14:13:29 +04:00
# define WDT_IN_USE 0
# define WDT_OK_TO_CLOSE 1
# define COMMAND_PORT 0x4c
# define DATA_PORT 0x48
# define IFACE_ON_COMMAND 1
# define REBOOT_COMMAND 2
2011-02-23 23:04:38 +03:00
# define WATCHDOG_NAME "SBC-FITPC2 Watchdog"
2009-07-21 14:13:29 +04:00
static void wdt_send_data ( unsigned char command , unsigned char data )
{
outb ( data , DATA_PORT ) ;
2010-01-21 17:10:07 +03:00
msleep ( 200 ) ;
2010-04-22 20:50:03 +04:00
outb ( command , COMMAND_PORT ) ;
msleep ( 100 ) ;
2009-07-21 14:13:29 +04:00
}
static void wdt_enable ( void )
{
2010-04-22 20:54:20 +04:00
mutex_lock ( & wdt_lock ) ;
2009-07-21 14:13:29 +04:00
wdt_send_data ( IFACE_ON_COMMAND , 1 ) ;
wdt_send_data ( REBOOT_COMMAND , margin ) ;
2010-04-22 20:54:20 +04:00
mutex_unlock ( & wdt_lock ) ;
2009-07-21 14:13:29 +04:00
}
static void wdt_disable ( void )
{
2010-04-22 20:54:20 +04:00
mutex_lock ( & wdt_lock ) ;
2009-07-21 14:13:29 +04:00
wdt_send_data ( IFACE_ON_COMMAND , 0 ) ;
wdt_send_data ( REBOOT_COMMAND , 0 ) ;
2010-04-22 20:54:20 +04:00
mutex_unlock ( & wdt_lock ) ;
2009-07-21 14:13:29 +04:00
}
static int fitpc2_wdt_open ( struct inode * inode , struct file * file )
{
if ( test_and_set_bit ( WDT_IN_USE , & wdt_status ) )
return - EBUSY ;
clear_bit ( WDT_OK_TO_CLOSE , & wdt_status ) ;
wdt_enable ( ) ;
2019-03-26 23:51:19 +03:00
return stream_open ( inode , file ) ;
2009-07-21 14:13:29 +04:00
}
2020-10-25 15:45:18 +03:00
static ssize_t fitpc2_wdt_write ( struct file * file , const char __user * data ,
2009-07-21 14:13:29 +04:00
size_t len , loff_t * ppos )
{
size_t i ;
if ( ! len )
return 0 ;
if ( nowayout ) {
len = 0 ;
goto out ;
}
clear_bit ( WDT_OK_TO_CLOSE , & wdt_status ) ;
for ( i = 0 ; i ! = len ; i + + ) {
char c ;
if ( get_user ( c , data + i ) )
return - EFAULT ;
if ( c = = ' V ' )
set_bit ( WDT_OK_TO_CLOSE , & wdt_status ) ;
}
out :
wdt_enable ( ) ;
return len ;
}
2009-12-26 21:55:22 +03:00
static const struct watchdog_info ident = {
2009-07-21 14:13:29 +04:00
. options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT |
WDIOF_KEEPALIVEPING ,
. identity = WATCHDOG_NAME ,
} ;
static long fitpc2_wdt_ioctl ( struct file * file , unsigned int cmd ,
unsigned long arg )
{
int ret = - ENOTTY ;
int time ;
switch ( cmd ) {
case WDIOC_GETSUPPORT :
2020-10-25 15:45:18 +03:00
ret = copy_to_user ( ( struct watchdog_info __user * ) arg , & ident ,
2009-07-21 14:13:29 +04:00
sizeof ( ident ) ) ? - EFAULT : 0 ;
break ;
case WDIOC_GETSTATUS :
2020-10-25 15:45:18 +03:00
ret = put_user ( 0 , ( int __user * ) arg ) ;
2009-07-21 14:13:29 +04:00
break ;
case WDIOC_GETBOOTSTATUS :
2020-10-25 15:45:18 +03:00
ret = put_user ( 0 , ( int __user * ) arg ) ;
2009-07-21 14:13:29 +04:00
break ;
case WDIOC_KEEPALIVE :
wdt_enable ( ) ;
ret = 0 ;
break ;
case WDIOC_SETTIMEOUT :
2020-10-25 15:45:18 +03:00
ret = get_user ( time , ( int __user * ) arg ) ;
2009-07-21 14:13:29 +04:00
if ( ret )
break ;
if ( time < 31 | | time > 255 ) {
ret = - EINVAL ;
break ;
}
margin = time ;
wdt_enable ( ) ;
2020-07-07 20:11:21 +03:00
fallthrough ;
2009-07-21 14:13:29 +04:00
case WDIOC_GETTIMEOUT :
2020-10-25 15:45:18 +03:00
ret = put_user ( margin , ( int __user * ) arg ) ;
2009-07-21 14:13:29 +04:00
break ;
}
return ret ;
}
static int fitpc2_wdt_release ( struct inode * inode , struct file * file )
{
if ( test_bit ( WDT_OK_TO_CLOSE , & wdt_status ) ) {
wdt_disable ( ) ;
pr_info ( " Device disabled \n " ) ;
} else {
2012-02-16 03:06:19 +04:00
pr_warn ( " Device closed unexpectedly - timer will not stop \n " ) ;
2009-07-21 14:13:29 +04:00
wdt_enable ( ) ;
}
clear_bit ( WDT_IN_USE , & wdt_status ) ;
clear_bit ( WDT_OK_TO_CLOSE , & wdt_status ) ;
return 0 ;
}
static const struct file_operations fitpc2_wdt_fops = {
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. write = fitpc2_wdt_write ,
. unlocked_ioctl = fitpc2_wdt_ioctl ,
2019-06-03 15:23:09 +03:00
. compat_ioctl = compat_ptr_ioctl ,
2009-07-21 14:13:29 +04:00
. open = fitpc2_wdt_open ,
. release = fitpc2_wdt_release ,
} ;
static struct miscdevice fitpc2_wdt_miscdev = {
. minor = WATCHDOG_MINOR ,
. name = " watchdog " ,
. fops = & fitpc2_wdt_fops ,
} ;
static int __init fitpc2_wdt_init ( void )
{
int err ;
2011-02-28 12:16:29 +03:00
const char * brd_name ;
2009-07-21 14:13:29 +04:00
2011-02-28 12:16:29 +03:00
brd_name = dmi_get_system_info ( DMI_BOARD_NAME ) ;
if ( ! brd_name | | ! strstr ( brd_name , " SBC-FITPC2 " ) )
2009-07-21 14:13:29 +04:00
return - ENODEV ;
2010-01-21 17:10:07 +03:00
2011-02-28 12:16:29 +03:00
pr_info ( " %s found \n " , brd_name ) ;
2009-07-21 14:13:29 +04:00
if ( ! request_region ( COMMAND_PORT , 1 , WATCHDOG_NAME ) ) {
pr_err ( " I/O address 0x%04x already in use \n " , COMMAND_PORT ) ;
return - EIO ;
}
if ( ! request_region ( DATA_PORT , 1 , WATCHDOG_NAME ) ) {
pr_err ( " I/O address 0x%04x already in use \n " , DATA_PORT ) ;
err = - EIO ;
goto err_data_port ;
}
if ( margin < 31 | | margin > 255 ) {
2012-02-16 03:06:19 +04:00
pr_err ( " margin must be in range 31 - 255 seconds, you tried to set %d \n " ,
margin ) ;
2009-07-21 14:13:29 +04:00
err = - EINVAL ;
goto err_margin ;
}
err = misc_register ( & fitpc2_wdt_miscdev ) ;
2009-11-05 14:32:40 +03:00
if ( err ) {
2009-07-21 14:13:29 +04:00
pr_err ( " cannot register miscdev on minor=%d (err=%d) \n " ,
2012-02-16 03:06:19 +04:00
WATCHDOG_MINOR , err ) ;
2009-07-21 14:13:29 +04:00
goto err_margin ;
}
return 0 ;
err_margin :
release_region ( DATA_PORT , 1 ) ;
err_data_port :
release_region ( COMMAND_PORT , 1 ) ;
return err ;
}
static void __exit fitpc2_wdt_exit ( void )
{
misc_deregister ( & fitpc2_wdt_miscdev ) ;
release_region ( DATA_PORT , 1 ) ;
release_region ( COMMAND_PORT , 1 ) ;
}
module_init ( fitpc2_wdt_init ) ;
module_exit ( fitpc2_wdt_exit ) ;
MODULE_AUTHOR ( " Denis Turischev <denis@compulab.co.il> " ) ;
MODULE_DESCRIPTION ( " SBC-FITPC2 Watchdog " ) ;
module_param ( margin , int , 0 ) ;
MODULE_PARM_DESC ( margin , " Watchdog margin in seconds (default 60s) " ) ;
2012-03-05 19:51:11 +04:00
module_param ( nowayout , bool , 0 ) ;
2009-07-21 14:13:29 +04:00
MODULE_PARM_DESC ( nowayout , " Watchdog cannot be stopped once started " ) ;
MODULE_LICENSE ( " GPL " ) ;