sbc7240_wdt.c (6674B)
1// SPDX-License-Identifier: GPL-2.0 2/* 3 * NANO7240 SBC Watchdog device driver 4 * 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 ": " fmt 11 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 0x443 28#define SBC7240_DISABLE_PORT 0x043 29#define SBC7240_SET_TIMEOUT_PORT SBC7240_ENABLE_PORT 30#define SBC7240_MAGIC_CHAR 'V' 31 32#define SBC7240_TIMEOUT 30 33#define SBC7240_MAX_TIMEOUT 255 34static 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 0 45#define SBC7240_ENABLED_STATUS_BIT 1 46#define SBC7240_EXPECT_CLOSE_STATUS_BIT 2 47static unsigned long wdt_status; 48 49/* 50 * Utility routines 51 */ 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 handling 93 */ 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 .llseek = no_llseek, 209 .write = fop_write, 210 .open = fop_open, 211 .release = fop_close, 212 .unlocked_ioctl = fop_ioctl, 213 .compat_ioctl = compat_ptr_ioctl, 214}; 215 216static struct miscdevice wdt_miscdev = { 217 .minor = WATCHDOG_MINOR, 218 .name = "watchdog", 219 .fops = &wdt_fops, 220}; 221 222/* 223 * Notifier for system down 224 */ 225 226static int wdt_notify_sys(struct notifier_block *this, unsigned long code, 227 void *unused) 228{ 229 if (code == SYS_DOWN || code == SYS_HALT) 230 wdt_disable(); 231 return NOTIFY_DONE; 232} 233 234static struct notifier_block wdt_notifier = { 235 .notifier_call = wdt_notify_sys, 236}; 237 238static void __exit sbc7240_wdt_unload(void) 239{ 240 pr_info("Removing watchdog\n"); 241 misc_deregister(&wdt_miscdev); 242 243 unregister_reboot_notifier(&wdt_notifier); 244 release_region(SBC7240_ENABLE_PORT, 1); 245} 246 247static int __init sbc7240_wdt_init(void) 248{ 249 int rc = -EBUSY; 250 251 if (!request_region(SBC7240_ENABLE_PORT, 1, "SBC7240 WDT")) { 252 pr_err("I/O address 0x%04x already in use\n", 253 SBC7240_ENABLE_PORT); 254 rc = -EIO; 255 goto err_out; 256 } 257 258 /* The IO port 0x043 used to disable the watchdog 259 * is already claimed by the system timer, so we 260 * can't request_region() it ...*/ 261 262 if (timeout < 1 || timeout > SBC7240_MAX_TIMEOUT) { 263 timeout = SBC7240_TIMEOUT; 264 pr_info("timeout value must be 1<=x<=%d, using %d\n", 265 SBC7240_MAX_TIMEOUT, timeout); 266 } 267 wdt_set_timeout(timeout); 268 wdt_disable(); 269 270 rc = register_reboot_notifier(&wdt_notifier); 271 if (rc) { 272 pr_err("cannot register reboot notifier (err=%d)\n", rc); 273 goto err_out_region; 274 } 275 276 rc = misc_register(&wdt_miscdev); 277 if (rc) { 278 pr_err("cannot register miscdev on minor=%d (err=%d)\n", 279 wdt_miscdev.minor, rc); 280 goto err_out_reboot_notifier; 281 } 282 283 pr_info("Watchdog driver for SBC7240 initialised (nowayout=%d)\n", 284 nowayout); 285 286 return 0; 287 288err_out_reboot_notifier: 289 unregister_reboot_notifier(&wdt_notifier); 290err_out_region: 291 release_region(SBC7240_ENABLE_PORT, 1); 292err_out: 293 return rc; 294} 295 296module_init(sbc7240_wdt_init); 297module_exit(sbc7240_wdt_unload); 298 299MODULE_AUTHOR("Gilles Gigan"); 300MODULE_DESCRIPTION("Watchdog device driver for single board" 301 " computers EPIC Nano 7240 from iEi"); 302MODULE_LICENSE("GPL");