1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* drivers/char/watchdog/scx200_wdt.c 3 4 National Semiconductor SCx200 Watchdog support 5 6 Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com> 7 8 Some code taken from: 9 National Semiconductor PC87307/PC97307 (ala SC1200) WDT driver 10 (c) Copyright 2002 Zwane Mwaikambo <zwane@commfireservices.com> 11 12 13 The author(s) of this software shall not be held liable for damages 14 of any nature resulting due to the use of this software. This 15 software is provided AS-IS with no warranties. */ 16 17 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 18 19 #include <linux/module.h> 20 #include <linux/moduleparam.h> 21 #include <linux/init.h> 22 #include <linux/miscdevice.h> 23 #include <linux/watchdog.h> 24 #include <linux/notifier.h> 25 #include <linux/reboot.h> 26 #include <linux/fs.h> 27 #include <linux/ioport.h> 28 #include <linux/scx200.h> 29 #include <linux/uaccess.h> 30 #include <linux/io.h> 31 32 #define DEBUG 33 34 MODULE_AUTHOR("Christer Weinigel <wingel@nano-system.com>"); 35 MODULE_DESCRIPTION("NatSemi SCx200 Watchdog Driver"); 36 MODULE_LICENSE("GPL"); 37 38 static int margin = 60; /* in seconds */ 39 module_param(margin, int, 0); 40 MODULE_PARM_DESC(margin, "Watchdog margin in seconds"); 41 42 static bool nowayout = WATCHDOG_NOWAYOUT; 43 module_param(nowayout, bool, 0); 44 MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close"); 45 46 static u16 wdto_restart; 47 static char expect_close; 48 static unsigned long open_lock; 49 static DEFINE_SPINLOCK(scx_lock); 50 51 /* Bits of the WDCNFG register */ 52 #define W_ENABLE 0x00fa /* Enable watchdog */ 53 #define W_DISABLE 0x0000 /* Disable watchdog */ 54 55 /* The scaling factor for the timer, this depends on the value of W_ENABLE */ 56 #define W_SCALE (32768/1024) 57 58 static void scx200_wdt_ping(void) 59 { 60 spin_lock(&scx_lock); 61 outw(wdto_restart, scx200_cb_base + SCx200_WDT_WDTO); 62 spin_unlock(&scx_lock); 63 } 64 65 static void scx200_wdt_update_margin(void) 66 { 67 pr_info("timer margin %d seconds\n", margin); 68 wdto_restart = margin * W_SCALE; 69 } 70 71 static void scx200_wdt_enable(void) 72 { 73 pr_debug("enabling watchdog timer, wdto_restart = %d\n", wdto_restart); 74 75 spin_lock(&scx_lock); 76 outw(0, scx200_cb_base + SCx200_WDT_WDTO); 77 outb(SCx200_WDT_WDSTS_WDOVF, scx200_cb_base + SCx200_WDT_WDSTS); 78 outw(W_ENABLE, scx200_cb_base + SCx200_WDT_WDCNFG); 79 spin_unlock(&scx_lock); 80 81 scx200_wdt_ping(); 82 } 83 84 static void scx200_wdt_disable(void) 85 { 86 pr_debug("disabling watchdog timer\n"); 87 88 spin_lock(&scx_lock); 89 outw(0, scx200_cb_base + SCx200_WDT_WDTO); 90 outb(SCx200_WDT_WDSTS_WDOVF, scx200_cb_base + SCx200_WDT_WDSTS); 91 outw(W_DISABLE, scx200_cb_base + SCx200_WDT_WDCNFG); 92 spin_unlock(&scx_lock); 93 } 94 95 static int scx200_wdt_open(struct inode *inode, struct file *file) 96 { 97 /* only allow one at a time */ 98 if (test_and_set_bit(0, &open_lock)) 99 return -EBUSY; 100 scx200_wdt_enable(); 101 102 return stream_open(inode, file); 103 } 104 105 static int scx200_wdt_release(struct inode *inode, struct file *file) 106 { 107 if (expect_close != 42) 108 pr_warn("watchdog device closed unexpectedly, will not disable the watchdog timer\n"); 109 else if (!nowayout) 110 scx200_wdt_disable(); 111 expect_close = 0; 112 clear_bit(0, &open_lock); 113 114 return 0; 115 } 116 117 static int scx200_wdt_notify_sys(struct notifier_block *this, 118 unsigned long code, void *unused) 119 { 120 if (code == SYS_HALT || code == SYS_POWER_OFF) 121 if (!nowayout) 122 scx200_wdt_disable(); 123 124 return NOTIFY_DONE; 125 } 126 127 static struct notifier_block scx200_wdt_notifier = { 128 .notifier_call = scx200_wdt_notify_sys, 129 }; 130 131 static ssize_t scx200_wdt_write(struct file *file, const char __user *data, 132 size_t len, loff_t *ppos) 133 { 134 /* check for a magic close character */ 135 if (len) { 136 size_t i; 137 138 scx200_wdt_ping(); 139 140 expect_close = 0; 141 for (i = 0; i < len; ++i) { 142 char c; 143 if (get_user(c, data + i)) 144 return -EFAULT; 145 if (c == 'V') 146 expect_close = 42; 147 } 148 149 return len; 150 } 151 152 return 0; 153 } 154 155 static long scx200_wdt_ioctl(struct file *file, unsigned int cmd, 156 unsigned long arg) 157 { 158 void __user *argp = (void __user *)arg; 159 int __user *p = argp; 160 static const struct watchdog_info ident = { 161 .identity = "NatSemi SCx200 Watchdog", 162 .firmware_version = 1, 163 .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | 164 WDIOF_MAGICCLOSE, 165 }; 166 int new_margin; 167 168 switch (cmd) { 169 case WDIOC_GETSUPPORT: 170 if (copy_to_user(argp, &ident, sizeof(ident))) 171 return -EFAULT; 172 return 0; 173 case WDIOC_GETSTATUS: 174 case WDIOC_GETBOOTSTATUS: 175 if (put_user(0, p)) 176 return -EFAULT; 177 return 0; 178 case WDIOC_KEEPALIVE: 179 scx200_wdt_ping(); 180 return 0; 181 case WDIOC_SETTIMEOUT: 182 if (get_user(new_margin, p)) 183 return -EFAULT; 184 if (new_margin < 1) 185 return -EINVAL; 186 margin = new_margin; 187 scx200_wdt_update_margin(); 188 scx200_wdt_ping(); 189 /* Fall through */ 190 case WDIOC_GETTIMEOUT: 191 if (put_user(margin, p)) 192 return -EFAULT; 193 return 0; 194 default: 195 return -ENOTTY; 196 } 197 } 198 199 static const struct file_operations scx200_wdt_fops = { 200 .owner = THIS_MODULE, 201 .llseek = no_llseek, 202 .write = scx200_wdt_write, 203 .unlocked_ioctl = scx200_wdt_ioctl, 204 .compat_ioctl = compat_ptr_ioctl, 205 .open = scx200_wdt_open, 206 .release = scx200_wdt_release, 207 }; 208 209 static struct miscdevice scx200_wdt_miscdev = { 210 .minor = WATCHDOG_MINOR, 211 .name = "watchdog", 212 .fops = &scx200_wdt_fops, 213 }; 214 215 static int __init scx200_wdt_init(void) 216 { 217 int r; 218 219 pr_debug("NatSemi SCx200 Watchdog Driver\n"); 220 221 /* check that we have found the configuration block */ 222 if (!scx200_cb_present()) 223 return -ENODEV; 224 225 if (!request_region(scx200_cb_base + SCx200_WDT_OFFSET, 226 SCx200_WDT_SIZE, 227 "NatSemi SCx200 Watchdog")) { 228 pr_warn("watchdog I/O region busy\n"); 229 return -EBUSY; 230 } 231 232 scx200_wdt_update_margin(); 233 scx200_wdt_disable(); 234 235 r = register_reboot_notifier(&scx200_wdt_notifier); 236 if (r) { 237 pr_err("unable to register reboot notifier\n"); 238 release_region(scx200_cb_base + SCx200_WDT_OFFSET, 239 SCx200_WDT_SIZE); 240 return r; 241 } 242 243 r = misc_register(&scx200_wdt_miscdev); 244 if (r) { 245 unregister_reboot_notifier(&scx200_wdt_notifier); 246 release_region(scx200_cb_base + SCx200_WDT_OFFSET, 247 SCx200_WDT_SIZE); 248 return r; 249 } 250 251 return 0; 252 } 253 254 static void __exit scx200_wdt_cleanup(void) 255 { 256 misc_deregister(&scx200_wdt_miscdev); 257 unregister_reboot_notifier(&scx200_wdt_notifier); 258 release_region(scx200_cb_base + SCx200_WDT_OFFSET, 259 SCx200_WDT_SIZE); 260 } 261 262 module_init(scx200_wdt_init); 263 module_exit(scx200_wdt_cleanup); 264