302 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * NANO7240 SBC Watchdog device driver4 *5 * Based on w83877f.c by Scott Jennings,6 *7 * (c) Copyright 2007 Gilles GIGAN <gilles.gigan@jcu.edu.au>8 */9 10#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt11 12#include <linux/fs.h>13#include <linux/init.h>14#include <linux/ioport.h>15#include <linux/jiffies.h>16#include <linux/module.h>17#include <linux/moduleparam.h>18#include <linux/miscdevice.h>19#include <linux/notifier.h>20#include <linux/reboot.h>21#include <linux/types.h>22#include <linux/watchdog.h>23#include <linux/io.h>24#include <linux/uaccess.h>25#include <linux/atomic.h>26 27#define SBC7240_ENABLE_PORT 0x44328#define SBC7240_DISABLE_PORT 0x04329#define SBC7240_SET_TIMEOUT_PORT SBC7240_ENABLE_PORT30#define SBC7240_MAGIC_CHAR 'V'31 32#define SBC7240_TIMEOUT 3033#define SBC7240_MAX_TIMEOUT 25534static int timeout = SBC7240_TIMEOUT; /* in seconds */35module_param(timeout, int, 0);36MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (1<=timeout<="37 __MODULE_STRING(SBC7240_MAX_TIMEOUT) ", default="38 __MODULE_STRING(SBC7240_TIMEOUT) ")");39 40static bool nowayout = WATCHDOG_NOWAYOUT;41module_param(nowayout, bool, 0);42MODULE_PARM_DESC(nowayout, "Disable watchdog when closing device file");43 44#define SBC7240_OPEN_STATUS_BIT 045#define SBC7240_ENABLED_STATUS_BIT 146#define SBC7240_EXPECT_CLOSE_STATUS_BIT 247static unsigned long wdt_status;48 49/*50 * Utility routines51 */52 53static void wdt_disable(void)54{55 /* disable the watchdog */56 if (test_and_clear_bit(SBC7240_ENABLED_STATUS_BIT, &wdt_status)) {57 inb_p(SBC7240_DISABLE_PORT);58 pr_info("Watchdog timer is now disabled\n");59 }60}61 62static void wdt_enable(void)63{64 /* enable the watchdog */65 if (!test_and_set_bit(SBC7240_ENABLED_STATUS_BIT, &wdt_status)) {66 inb_p(SBC7240_ENABLE_PORT);67 pr_info("Watchdog timer is now enabled\n");68 }69}70 71static int wdt_set_timeout(int t)72{73 if (t < 1 || t > SBC7240_MAX_TIMEOUT) {74 pr_err("timeout value must be 1<=x<=%d\n", SBC7240_MAX_TIMEOUT);75 return -1;76 }77 /* set the timeout */78 outb_p((unsigned)t, SBC7240_SET_TIMEOUT_PORT);79 timeout = t;80 pr_info("timeout set to %d seconds\n", t);81 return 0;82}83 84/* Whack the dog */85static inline void wdt_keepalive(void)86{87 if (test_bit(SBC7240_ENABLED_STATUS_BIT, &wdt_status))88 inb_p(SBC7240_ENABLE_PORT);89}90 91/*92 * /dev/watchdog handling93 */94static ssize_t fop_write(struct file *file, const char __user *buf,95 size_t count, loff_t *ppos)96{97 size_t i;98 char c;99 100 if (count) {101 if (!nowayout) {102 clear_bit(SBC7240_EXPECT_CLOSE_STATUS_BIT,103 &wdt_status);104 105 /* is there a magic char ? */106 for (i = 0; i != count; i++) {107 if (get_user(c, buf + i))108 return -EFAULT;109 if (c == SBC7240_MAGIC_CHAR) {110 set_bit(SBC7240_EXPECT_CLOSE_STATUS_BIT,111 &wdt_status);112 break;113 }114 }115 }116 117 wdt_keepalive();118 }119 120 return count;121}122 123static int fop_open(struct inode *inode, struct file *file)124{125 if (test_and_set_bit(SBC7240_OPEN_STATUS_BIT, &wdt_status))126 return -EBUSY;127 128 wdt_enable();129 130 return stream_open(inode, file);131}132 133static int fop_close(struct inode *inode, struct file *file)134{135 if (test_and_clear_bit(SBC7240_EXPECT_CLOSE_STATUS_BIT, &wdt_status)136 || !nowayout) {137 wdt_disable();138 } else {139 pr_crit("Unexpected close, not stopping watchdog!\n");140 wdt_keepalive();141 }142 143 clear_bit(SBC7240_OPEN_STATUS_BIT, &wdt_status);144 return 0;145}146 147static const struct watchdog_info ident = {148 .options = WDIOF_KEEPALIVEPING|149 WDIOF_SETTIMEOUT|150 WDIOF_MAGICCLOSE,151 .firmware_version = 1,152 .identity = "SBC7240",153};154 155 156static long fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg)157{158 switch (cmd) {159 case WDIOC_GETSUPPORT:160 return copy_to_user((void __user *)arg, &ident, sizeof(ident))161 ? -EFAULT : 0;162 case WDIOC_GETSTATUS:163 case WDIOC_GETBOOTSTATUS:164 return put_user(0, (int __user *)arg);165 case WDIOC_SETOPTIONS:166 {167 int options;168 int retval = -EINVAL;169 170 if (get_user(options, (int __user *)arg))171 return -EFAULT;172 173 if (options & WDIOS_DISABLECARD) {174 wdt_disable();175 retval = 0;176 }177 178 if (options & WDIOS_ENABLECARD) {179 wdt_enable();180 retval = 0;181 }182 183 return retval;184 }185 case WDIOC_KEEPALIVE:186 wdt_keepalive();187 return 0;188 case WDIOC_SETTIMEOUT:189 {190 int new_timeout;191 192 if (get_user(new_timeout, (int __user *)arg))193 return -EFAULT;194 195 if (wdt_set_timeout(new_timeout))196 return -EINVAL;197 }198 fallthrough;199 case WDIOC_GETTIMEOUT:200 return put_user(timeout, (int __user *)arg);201 default:202 return -ENOTTY;203 }204}205 206static const struct file_operations wdt_fops = {207 .owner = THIS_MODULE,208 .write = fop_write,209 .open = fop_open,210 .release = fop_close,211 .unlocked_ioctl = fop_ioctl,212 .compat_ioctl = compat_ptr_ioctl,213};214 215static struct miscdevice wdt_miscdev = {216 .minor = WATCHDOG_MINOR,217 .name = "watchdog",218 .fops = &wdt_fops,219};220 221/*222 * Notifier for system down223 */224 225static int wdt_notify_sys(struct notifier_block *this, unsigned long code,226 void *unused)227{228 if (code == SYS_DOWN || code == SYS_HALT)229 wdt_disable();230 return NOTIFY_DONE;231}232 233static struct notifier_block wdt_notifier = {234 .notifier_call = wdt_notify_sys,235};236 237static void __exit sbc7240_wdt_unload(void)238{239 pr_info("Removing watchdog\n");240 misc_deregister(&wdt_miscdev);241 242 unregister_reboot_notifier(&wdt_notifier);243 release_region(SBC7240_ENABLE_PORT, 1);244}245 246static int __init sbc7240_wdt_init(void)247{248 int rc = -EBUSY;249 250 if (!request_region(SBC7240_ENABLE_PORT, 1, "SBC7240 WDT")) {251 pr_err("I/O address 0x%04x already in use\n",252 SBC7240_ENABLE_PORT);253 rc = -EIO;254 goto err_out;255 }256 257 /* The IO port 0x043 used to disable the watchdog258 * is already claimed by the system timer, so we259 * can't request_region() it ...*/260 261 if (timeout < 1 || timeout > SBC7240_MAX_TIMEOUT) {262 timeout = SBC7240_TIMEOUT;263 pr_info("timeout value must be 1<=x<=%d, using %d\n",264 SBC7240_MAX_TIMEOUT, timeout);265 }266 wdt_set_timeout(timeout);267 wdt_disable();268 269 rc = register_reboot_notifier(&wdt_notifier);270 if (rc) {271 pr_err("cannot register reboot notifier (err=%d)\n", rc);272 goto err_out_region;273 }274 275 rc = misc_register(&wdt_miscdev);276 if (rc) {277 pr_err("cannot register miscdev on minor=%d (err=%d)\n",278 wdt_miscdev.minor, rc);279 goto err_out_reboot_notifier;280 }281 282 pr_info("Watchdog driver for SBC7240 initialised (nowayout=%d)\n",283 nowayout);284 285 return 0;286 287err_out_reboot_notifier:288 unregister_reboot_notifier(&wdt_notifier);289err_out_region:290 release_region(SBC7240_ENABLE_PORT, 1);291err_out:292 return rc;293}294 295module_init(sbc7240_wdt_init);296module_exit(sbc7240_wdt_unload);297 298MODULE_AUTHOR("Gilles Gigan");299MODULE_DESCRIPTION("Watchdog device driver for single board"300 " computers EPIC Nano 7240 from iEi");301MODULE_LICENSE("GPL");302