scx200_wdt.c (6315B)
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 34MODULE_AUTHOR("Christer Weinigel <wingel@nano-system.com>"); 35MODULE_DESCRIPTION("NatSemi SCx200 Watchdog Driver"); 36MODULE_LICENSE("GPL"); 37 38static int margin = 60; /* in seconds */ 39module_param(margin, int, 0); 40MODULE_PARM_DESC(margin, "Watchdog margin in seconds"); 41 42static bool nowayout = WATCHDOG_NOWAYOUT; 43module_param(nowayout, bool, 0); 44MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close"); 45 46static u16 wdto_restart; 47static char expect_close; 48static unsigned long open_lock; 49static 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 58static 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 65static void scx200_wdt_update_margin(void) 66{ 67 pr_info("timer margin %d seconds\n", margin); 68 wdto_restart = margin * W_SCALE; 69} 70 71static 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 84static 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 95static 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 105static 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 117static 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 127static struct notifier_block scx200_wdt_notifier = { 128 .notifier_call = scx200_wdt_notify_sys, 129}; 130 131static 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 155static 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 fallthrough; 190 case WDIOC_GETTIMEOUT: 191 if (put_user(margin, p)) 192 return -EFAULT; 193 return 0; 194 default: 195 return -ENOTTY; 196 } 197} 198 199static 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 209static struct miscdevice scx200_wdt_miscdev = { 210 .minor = WATCHDOG_MINOR, 211 .name = "watchdog", 212 .fops = &scx200_wdt_fops, 213}; 214 215static 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 254static 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 262module_init(scx200_wdt_init); 263module_exit(scx200_wdt_cleanup);