1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * SBC EPX C3 0.1 A Hardware Watchdog Device for the Winsystems EPX-C3 4 * single board computer 5 * 6 * (c) Copyright 2006 Calin A. Culianu <calin@ajvar.org>, All Rights 7 * Reserved. 8 * 9 * based on softdog.c by Alan Cox <alan@lxorguk.ukuu.org.uk> 10 */ 11 12 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 13 14 #include <linux/module.h> 15 #include <linux/moduleparam.h> 16 #include <linux/types.h> 17 #include <linux/kernel.h> 18 #include <linux/fs.h> 19 #include <linux/mm.h> 20 #include <linux/miscdevice.h> 21 #include <linux/watchdog.h> 22 #include <linux/notifier.h> 23 #include <linux/reboot.h> 24 #include <linux/init.h> 25 #include <linux/ioport.h> 26 #include <linux/uaccess.h> 27 #include <linux/io.h> 28 29 static int epx_c3_alive; 30 31 #define WATCHDOG_TIMEOUT 1 /* 1 sec default timeout */ 32 33 static bool nowayout = WATCHDOG_NOWAYOUT; 34 module_param(nowayout, bool, 0); 35 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" 36 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 37 38 #define EPXC3_WATCHDOG_CTL_REG 0x1ee /* write 1 to enable, 0 to disable */ 39 #define EPXC3_WATCHDOG_PET_REG 0x1ef /* write anything to pet once enabled */ 40 41 static void epx_c3_start(void) 42 { 43 outb(1, EPXC3_WATCHDOG_CTL_REG); 44 } 45 46 static void epx_c3_stop(void) 47 { 48 49 outb(0, EPXC3_WATCHDOG_CTL_REG); 50 51 pr_info("Stopped watchdog timer\n"); 52 } 53 54 static void epx_c3_pet(void) 55 { 56 outb(1, EPXC3_WATCHDOG_PET_REG); 57 } 58 59 /* 60 * Allow only one person to hold it open 61 */ 62 static int epx_c3_open(struct inode *inode, struct file *file) 63 { 64 if (epx_c3_alive) 65 return -EBUSY; 66 67 if (nowayout) 68 __module_get(THIS_MODULE); 69 70 /* Activate timer */ 71 epx_c3_start(); 72 epx_c3_pet(); 73 74 epx_c3_alive = 1; 75 pr_info("Started watchdog timer\n"); 76 77 return stream_open(inode, file); 78 } 79 80 static int epx_c3_release(struct inode *inode, struct file *file) 81 { 82 /* Shut off the timer. 83 * Lock it in if it's a module and we defined ...NOWAYOUT */ 84 if (!nowayout) 85 epx_c3_stop(); /* Turn the WDT off */ 86 87 epx_c3_alive = 0; 88 89 return 0; 90 } 91 92 static ssize_t epx_c3_write(struct file *file, const char __user *data, 93 size_t len, loff_t *ppos) 94 { 95 /* Refresh the timer. */ 96 if (len) 97 epx_c3_pet(); 98 return len; 99 } 100 101 static long epx_c3_ioctl(struct file *file, unsigned int cmd, 102 unsigned long arg) 103 { 104 int options, retval = -EINVAL; 105 int __user *argp = (void __user *)arg; 106 static const struct watchdog_info ident = { 107 .options = WDIOF_KEEPALIVEPING, 108 .firmware_version = 0, 109 .identity = "Winsystems EPX-C3 H/W Watchdog", 110 }; 111 112 switch (cmd) { 113 case WDIOC_GETSUPPORT: 114 if (copy_to_user(argp, &ident, sizeof(ident))) 115 return -EFAULT; 116 return 0; 117 case WDIOC_GETSTATUS: 118 case WDIOC_GETBOOTSTATUS: 119 return put_user(0, argp); 120 case WDIOC_SETOPTIONS: 121 if (get_user(options, argp)) 122 return -EFAULT; 123 124 if (options & WDIOS_DISABLECARD) { 125 epx_c3_stop(); 126 retval = 0; 127 } 128 129 if (options & WDIOS_ENABLECARD) { 130 epx_c3_start(); 131 retval = 0; 132 } 133 134 return retval; 135 case WDIOC_KEEPALIVE: 136 epx_c3_pet(); 137 return 0; 138 case WDIOC_GETTIMEOUT: 139 return put_user(WATCHDOG_TIMEOUT, argp); 140 default: 141 return -ENOTTY; 142 } 143 } 144 145 static int epx_c3_notify_sys(struct notifier_block *this, unsigned long code, 146 void *unused) 147 { 148 if (code == SYS_DOWN || code == SYS_HALT) 149 epx_c3_stop(); /* Turn the WDT off */ 150 151 return NOTIFY_DONE; 152 } 153 154 static const struct file_operations epx_c3_fops = { 155 .owner = THIS_MODULE, 156 .llseek = no_llseek, 157 .write = epx_c3_write, 158 .unlocked_ioctl = epx_c3_ioctl, 159 .compat_ioctl = compat_ptr_ioctl, 160 .open = epx_c3_open, 161 .release = epx_c3_release, 162 }; 163 164 static struct miscdevice epx_c3_miscdev = { 165 .minor = WATCHDOG_MINOR, 166 .name = "watchdog", 167 .fops = &epx_c3_fops, 168 }; 169 170 static struct notifier_block epx_c3_notifier = { 171 .notifier_call = epx_c3_notify_sys, 172 }; 173 174 static int __init watchdog_init(void) 175 { 176 int ret; 177 178 if (!request_region(EPXC3_WATCHDOG_CTL_REG, 2, "epxc3_watchdog")) 179 return -EBUSY; 180 181 ret = register_reboot_notifier(&epx_c3_notifier); 182 if (ret) { 183 pr_err("cannot register reboot notifier (err=%d)\n", ret); 184 goto out; 185 } 186 187 ret = misc_register(&epx_c3_miscdev); 188 if (ret) { 189 pr_err("cannot register miscdev on minor=%d (err=%d)\n", 190 WATCHDOG_MINOR, ret); 191 unregister_reboot_notifier(&epx_c3_notifier); 192 goto out; 193 } 194 195 pr_info("Hardware Watchdog Timer for Winsystems EPX-C3 SBC: 0.1\n"); 196 197 return 0; 198 199 out: 200 release_region(EPXC3_WATCHDOG_CTL_REG, 2); 201 return ret; 202 } 203 204 static void __exit watchdog_exit(void) 205 { 206 misc_deregister(&epx_c3_miscdev); 207 unregister_reboot_notifier(&epx_c3_notifier); 208 release_region(EPXC3_WATCHDOG_CTL_REG, 2); 209 } 210 211 module_init(watchdog_init); 212 module_exit(watchdog_exit); 213 214 MODULE_AUTHOR("Calin A. Culianu <calin@ajvar.org>"); 215 MODULE_DESCRIPTION("Hardware Watchdog Device for Winsystems EPX-C3 SBC. " 216 "Note that there is no way to probe for this device -- " 217 "so only use it if you are *sure* you are running on this specific " 218 "SBC system from Winsystems! It writes to IO ports 0x1ee and 0x1ef!"); 219 MODULE_LICENSE("GPL"); 220