mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-23 09:19:51 +00:00
![Calin A. Culianu](/assets/img/avatar_default.png)
This is a 2.6 patch that adds support for the watchdog timer built into the EPX-C3 single board computer manufactured by Winsystems, Inc. Driver details: This is for x86 only. This watchdog is pretty basic and simple. It is only configurable via jumpers on the SBC, and it only has either a 1.5s or 200s interval. The watchdog can either be auto-configured to start as soon as the machine powers up (bad idea for the 1.5s interval!) or it can be enabled and disabled by writing to io port 0x1ee. Petting the watchdog involves writing any value to io port 0x1ef. The only unfortunate thing about this watchdog (and it is not at all uncommmon in watchdogs that linux supports) is that it is not a PCI or ISA-PNP device and as such it isn't at all probeable. Either the watchdog exists as 2 bytes at 0x1ee, or it doesn't. Thus, using this driver on a machine that doesn't have that watchdog can potentially hang/crash the system, etc. So only use this driver if you in fact are on a Winsystems EPX-C3 SBC. Anyway this driver fits into the already-existing watchdog framework quite nicely and I already tested it on my EPX-C3 and it works like a charm. Signed-off-by: Calin A. Culianu <calin@ajvar.org> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
216 lines
4.9 KiB
C
216 lines
4.9 KiB
C
/*
|
|
* SBC EPX C3 0.1 A Hardware Watchdog Device for the Winsystems EPX-C3
|
|
* single board computer
|
|
*
|
|
* (c) Copyright 2006 Calin A. Culianu <calin@ajvar.org>, All Rights
|
|
* Reserved.
|
|
*
|
|
* 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.
|
|
*
|
|
* based on softdog.c by Alan Cox <alan@redhat.com>
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/moduleparam.h>
|
|
#include <linux/config.h>
|
|
#include <linux/types.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/watchdog.h>
|
|
#include <linux/notifier.h>
|
|
#include <linux/reboot.h>
|
|
#include <linux/init.h>
|
|
#include <asm/uaccess.h>
|
|
#include <asm/io.h>
|
|
|
|
#define PFX "epx_c3: "
|
|
static int epx_c3_alive;
|
|
|
|
#define WATCHDOG_TIMEOUT 1 /* 1 sec default timeout */
|
|
|
|
static int nowayout = WATCHDOG_NOWAYOUT;
|
|
module_param(nowayout, int, 0);
|
|
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
|
|
|
|
#define EPXC3_WATCHDOG_CTL_REG 0x1ee /* write 1 to enable, 0 to disable */
|
|
#define EPXC3_WATCHDOG_PET_REG 0x1ef /* write anything to pet once enabled */
|
|
|
|
static void epx_c3_start(void)
|
|
{
|
|
outb(1, EPXC3_WATCHDOG_CTL_REG);
|
|
}
|
|
|
|
static void epx_c3_stop(void)
|
|
{
|
|
|
|
outb(0, EPXC3_WATCHDOG_CTL_REG);
|
|
|
|
printk(KERN_INFO PFX "Stopped watchdog timer.\n");
|
|
}
|
|
|
|
static void epx_c3_pet(void)
|
|
{
|
|
outb(1, EPXC3_WATCHDOG_PET_REG);
|
|
}
|
|
|
|
/*
|
|
* Allow only one person to hold it open
|
|
*/
|
|
static int epx_c3_open(struct inode *inode, struct file *file)
|
|
{
|
|
if (epx_c3_alive)
|
|
return -EBUSY;
|
|
|
|
if (nowayout)
|
|
__module_get(THIS_MODULE);
|
|
|
|
/* Activate timer */
|
|
epx_c3_start();
|
|
epx_c3_pet();
|
|
|
|
epx_c3_alive = 1;
|
|
printk(KERN_INFO "Started watchdog timer.\n");
|
|
|
|
return nonseekable_open(inode, file);
|
|
}
|
|
|
|
static int epx_c3_release(struct inode *inode, struct file *file)
|
|
{
|
|
/* Shut off the timer.
|
|
* Lock it in if it's a module and we defined ...NOWAYOUT */
|
|
if (!nowayout)
|
|
epx_c3_stop(); /* Turn the WDT off */
|
|
|
|
epx_c3_alive = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t epx_c3_write(struct file *file, const char *data,
|
|
size_t len, loff_t *ppos)
|
|
{
|
|
/* Refresh the timer. */
|
|
if (len)
|
|
epx_c3_pet();
|
|
return len;
|
|
}
|
|
|
|
static int epx_c3_ioctl(struct inode *inode, struct file *file,
|
|
unsigned int cmd, unsigned long arg)
|
|
{
|
|
int options, retval = -EINVAL;
|
|
static struct watchdog_info ident = {
|
|
.options = WDIOF_KEEPALIVEPING |
|
|
WDIOF_MAGICCLOSE,
|
|
.firmware_version = 0,
|
|
.identity = "Winsystems EPX-C3 H/W Watchdog",
|
|
};
|
|
|
|
switch (cmd) {
|
|
case WDIOC_GETSUPPORT:
|
|
if (copy_to_user((struct watchdog_info *)arg,
|
|
&ident, sizeof(ident)))
|
|
return -EFAULT;
|
|
return 0;
|
|
case WDIOC_GETSTATUS:
|
|
case WDIOC_GETBOOTSTATUS:
|
|
return put_user(0,(int *)arg);
|
|
case WDIOC_KEEPALIVE:
|
|
epx_c3_pet();
|
|
return 0;
|
|
case WDIOC_GETTIMEOUT:
|
|
return put_user(WATCHDOG_TIMEOUT,(int *)arg);
|
|
case WDIOC_SETOPTIONS: {
|
|
if (get_user(options, (int *)arg))
|
|
return -EFAULT;
|
|
|
|
if (options & WDIOS_DISABLECARD) {
|
|
epx_c3_stop();
|
|
retval = 0;
|
|
}
|
|
|
|
if (options & WDIOS_ENABLECARD) {
|
|
epx_c3_start();
|
|
retval = 0;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
default:
|
|
return -ENOIOCTLCMD;
|
|
}
|
|
}
|
|
|
|
static int epx_c3_notify_sys(struct notifier_block *this, unsigned long code,
|
|
void *unused)
|
|
{
|
|
if (code == SYS_DOWN || code == SYS_HALT)
|
|
epx_c3_stop(); /* Turn the WDT off */
|
|
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
static struct file_operations epx_c3_fops = {
|
|
.owner = THIS_MODULE,
|
|
.llseek = no_llseek,
|
|
.write = epx_c3_write,
|
|
.ioctl = epx_c3_ioctl,
|
|
.open = epx_c3_open,
|
|
.release = epx_c3_release,
|
|
};
|
|
|
|
static struct miscdevice epx_c3_miscdev = {
|
|
.minor = WATCHDOG_MINOR,
|
|
.name = "watchdog",
|
|
.fops = &epx_c3_fops,
|
|
};
|
|
|
|
static struct notifier_block epx_c3_notifier = {
|
|
.notifier_call = epx_c3_notify_sys,
|
|
};
|
|
|
|
static const char banner[] __initdata =
|
|
KERN_INFO PFX "Hardware Watchdog Timer for Winsystems EPX-C3 SBC: 0.1\n";
|
|
|
|
static int __init watchdog_init(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = register_reboot_notifier(&epx_c3_notifier);
|
|
if (ret) {
|
|
printk(KERN_ERR PFX "cannot register reboot notifier "
|
|
"(err=%d)\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = misc_register(&epx_c3_miscdev);
|
|
if (ret) {
|
|
printk(KERN_ERR PFX "cannot register miscdev on minor=%d "
|
|
"(err=%d)\n", WATCHDOG_MINOR, ret);
|
|
unregister_reboot_notifier(&epx_c3_notifier);
|
|
return ret;
|
|
}
|
|
|
|
printk(banner);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void __exit watchdog_exit(void)
|
|
{
|
|
misc_deregister(&epx_c3_miscdev);
|
|
unregister_reboot_notifier(&epx_c3_notifier);
|
|
}
|
|
|
|
module_init(watchdog_init);
|
|
module_exit(watchdog_exit);
|
|
|
|
MODULE_AUTHOR("Calin A. Culianu <calin@ajvar.org>");
|
|
MODULE_DESCRIPTION("Hardware Watchdog Device for Winsystems EPX-C3 SBC. Note that there is no way to probe for this device -- so only use it if you are *sure* you are runnning on this specific SBC system from Winsystems! It writes to IO ports 0x1ee and 0x1ef!");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
|