323 lines · c
1// SPDX-License-Identifier: GPL-2.0+2/*3 * ICP Wafer 5823 Single Board Computer WDT driver4 * http://www.icpamerica.com/wafer_5823.php5 * May also work on other similar models6 *7 * (c) Copyright 2002 Justin Cormack <justin@street-vision.com>8 *9 * Release 0.0210 *11 * Based on advantechwdt.c which is based on wdt.c.12 * Original copyright messages:13 *14 * (c) Copyright 1996-1997 Alan Cox <alan@lxorguk.ukuu.org.uk>,15 * All Rights Reserved.16 *17 * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide18 * warranty for any of this software. This material is provided19 * "AS-IS" and at no charge.20 *21 * (c) Copyright 1995 Alan Cox <alan@lxorguk.ukuu.org.uk>22 *23 */24 25#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt26 27#include <linux/module.h>28#include <linux/moduleparam.h>29#include <linux/miscdevice.h>30#include <linux/watchdog.h>31#include <linux/fs.h>32#include <linux/ioport.h>33#include <linux/notifier.h>34#include <linux/reboot.h>35#include <linux/init.h>36#include <linux/spinlock.h>37#include <linux/io.h>38#include <linux/uaccess.h>39 40#define WATCHDOG_NAME "Wafer 5823 WDT"41#define PFX WATCHDOG_NAME ": "42#define WD_TIMO 60 /* 60 sec default timeout */43 44static unsigned long wafwdt_is_open;45static char expect_close;46static DEFINE_SPINLOCK(wafwdt_lock);47 48/*49 * You must set these - there is no sane way to probe for this board.50 *51 * To enable, write the timeout value in seconds (1 to 255) to I/O52 * port WDT_START, then read the port to start the watchdog. To pat53 * the dog, read port WDT_STOP to stop the timer, then read WDT_START54 * to restart it again.55 */56 57static int wdt_stop = 0x843;58static int wdt_start = 0x443;59 60static int timeout = WD_TIMO; /* in seconds */61module_param(timeout, int, 0);62MODULE_PARM_DESC(timeout,63 "Watchdog timeout in seconds. 1 <= timeout <= 255, default="64 __MODULE_STRING(WD_TIMO) ".");65 66static bool nowayout = WATCHDOG_NOWAYOUT;67module_param(nowayout, bool, 0);68MODULE_PARM_DESC(nowayout,69 "Watchdog cannot be stopped once started (default="70 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");71 72static void wafwdt_ping(void)73{74 /* pat watchdog */75 spin_lock(&wafwdt_lock);76 inb_p(wdt_stop);77 inb_p(wdt_start);78 spin_unlock(&wafwdt_lock);79}80 81static void wafwdt_start(void)82{83 /* start up watchdog */84 outb_p(timeout, wdt_start);85 inb_p(wdt_start);86}87 88static void wafwdt_stop(void)89{90 /* stop watchdog */91 inb_p(wdt_stop);92}93 94static ssize_t wafwdt_write(struct file *file, const char __user *buf,95 size_t count, loff_t *ppos)96{97 /* See if we got the magic character 'V' and reload the timer */98 if (count) {99 if (!nowayout) {100 size_t i;101 102 /* In case it was set long ago */103 expect_close = 0;104 105 /* scan to see whether or not we got the magic106 character */107 for (i = 0; i != count; i++) {108 char c;109 if (get_user(c, buf + i))110 return -EFAULT;111 if (c == 'V')112 expect_close = 42;113 }114 }115 /* Well, anyhow someone wrote to us, we should116 return that favour */117 wafwdt_ping();118 }119 return count;120}121 122static long wafwdt_ioctl(struct file *file, unsigned int cmd,123 unsigned long arg)124{125 int new_timeout;126 void __user *argp = (void __user *)arg;127 int __user *p = argp;128 static const struct watchdog_info ident = {129 .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT |130 WDIOF_MAGICCLOSE,131 .firmware_version = 1,132 .identity = "Wafer 5823 WDT",133 };134 135 switch (cmd) {136 case WDIOC_GETSUPPORT:137 if (copy_to_user(argp, &ident, sizeof(ident)))138 return -EFAULT;139 break;140 141 case WDIOC_GETSTATUS:142 case WDIOC_GETBOOTSTATUS:143 return put_user(0, p);144 145 case WDIOC_SETOPTIONS:146 {147 int options, retval = -EINVAL;148 149 if (get_user(options, p))150 return -EFAULT;151 152 if (options & WDIOS_DISABLECARD) {153 wafwdt_stop();154 retval = 0;155 }156 157 if (options & WDIOS_ENABLECARD) {158 wafwdt_start();159 retval = 0;160 }161 162 return retval;163 }164 165 case WDIOC_KEEPALIVE:166 wafwdt_ping();167 break;168 169 case WDIOC_SETTIMEOUT:170 if (get_user(new_timeout, p))171 return -EFAULT;172 if ((new_timeout < 1) || (new_timeout > 255))173 return -EINVAL;174 timeout = new_timeout;175 wafwdt_stop();176 wafwdt_start();177 fallthrough;178 case WDIOC_GETTIMEOUT:179 return put_user(timeout, p);180 181 default:182 return -ENOTTY;183 }184 return 0;185}186 187static int wafwdt_open(struct inode *inode, struct file *file)188{189 if (test_and_set_bit(0, &wafwdt_is_open))190 return -EBUSY;191 192 /*193 * Activate194 */195 wafwdt_start();196 return stream_open(inode, file);197}198 199static int wafwdt_close(struct inode *inode, struct file *file)200{201 if (expect_close == 42)202 wafwdt_stop();203 else {204 pr_crit("WDT device closed unexpectedly. WDT will not stop!\n");205 wafwdt_ping();206 }207 clear_bit(0, &wafwdt_is_open);208 expect_close = 0;209 return 0;210}211 212/*213 * Notifier for system down214 */215 216static int wafwdt_notify_sys(struct notifier_block *this, unsigned long code,217 void *unused)218{219 if (code == SYS_DOWN || code == SYS_HALT)220 wafwdt_stop();221 return NOTIFY_DONE;222}223 224/*225 * Kernel Interfaces226 */227 228static const struct file_operations wafwdt_fops = {229 .owner = THIS_MODULE,230 .write = wafwdt_write,231 .unlocked_ioctl = wafwdt_ioctl,232 .compat_ioctl = compat_ptr_ioctl,233 .open = wafwdt_open,234 .release = wafwdt_close,235};236 237static struct miscdevice wafwdt_miscdev = {238 .minor = WATCHDOG_MINOR,239 .name = "watchdog",240 .fops = &wafwdt_fops,241};242 243/*244 * The WDT needs to learn about soft shutdowns in order to245 * turn the timebomb registers off.246 */247 248static struct notifier_block wafwdt_notifier = {249 .notifier_call = wafwdt_notify_sys,250};251 252static int __init wafwdt_init(void)253{254 int ret;255 256 pr_info("WDT driver for Wafer 5823 single board computer initialising\n");257 258 if (timeout < 1 || timeout > 255) {259 timeout = WD_TIMO;260 pr_info("timeout value must be 1 <= x <= 255, using %d\n",261 timeout);262 }263 264 if (wdt_stop != wdt_start) {265 if (!request_region(wdt_stop, 1, "Wafer 5823 WDT")) {266 pr_err("I/O address 0x%04x already in use\n", wdt_stop);267 ret = -EIO;268 goto error;269 }270 }271 272 if (!request_region(wdt_start, 1, "Wafer 5823 WDT")) {273 pr_err("I/O address 0x%04x already in use\n", wdt_start);274 ret = -EIO;275 goto error2;276 }277 278 ret = register_reboot_notifier(&wafwdt_notifier);279 if (ret != 0) {280 pr_err("cannot register reboot notifier (err=%d)\n", ret);281 goto error3;282 }283 284 ret = misc_register(&wafwdt_miscdev);285 if (ret != 0) {286 pr_err("cannot register miscdev on minor=%d (err=%d)\n",287 WATCHDOG_MINOR, ret);288 goto error4;289 }290 291 pr_info("initialized. timeout=%d sec (nowayout=%d)\n",292 timeout, nowayout);293 294 return ret;295error4:296 unregister_reboot_notifier(&wafwdt_notifier);297error3:298 release_region(wdt_start, 1);299error2:300 if (wdt_stop != wdt_start)301 release_region(wdt_stop, 1);302error:303 return ret;304}305 306static void __exit wafwdt_exit(void)307{308 misc_deregister(&wafwdt_miscdev);309 unregister_reboot_notifier(&wafwdt_notifier);310 if (wdt_stop != wdt_start)311 release_region(wdt_stop, 1);312 release_region(wdt_start, 1);313}314 315module_init(wafwdt_init);316module_exit(wafwdt_exit);317 318MODULE_AUTHOR("Justin Cormack");319MODULE_DESCRIPTION("ICP Wafer 5823 Single Board Computer WDT driver");320MODULE_LICENSE("GPL");321 322/* end of wafer5823wdt.c */323