Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at v6.15 301 lines 6.7 kB view raw
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 .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 down 223 */ 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 watchdog 258 * is already claimed by the system timer, so we 259 * 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");