sbc_epx_c3.c (4939B)
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 29static int epx_c3_alive; 30 31#define WATCHDOG_TIMEOUT 1 /* 1 sec default timeout */ 32 33static bool nowayout = WATCHDOG_NOWAYOUT; 34module_param(nowayout, bool, 0); 35MODULE_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 41static void epx_c3_start(void) 42{ 43 outb(1, EPXC3_WATCHDOG_CTL_REG); 44} 45 46static void epx_c3_stop(void) 47{ 48 49 outb(0, EPXC3_WATCHDOG_CTL_REG); 50 51 pr_info("Stopped watchdog timer\n"); 52} 53 54static 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 */ 62static 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 80static 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 92static 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 101static 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 145static 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 154static 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 164static struct miscdevice epx_c3_miscdev = { 165 .minor = WATCHDOG_MINOR, 166 .name = "watchdog", 167 .fops = &epx_c3_fops, 168}; 169 170static struct notifier_block epx_c3_notifier = { 171 .notifier_call = epx_c3_notify_sys, 172}; 173 174static 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 199out: 200 release_region(EPXC3_WATCHDOG_CTL_REG, 2); 201 return ret; 202} 203 204static 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 211module_init(watchdog_init); 212module_exit(watchdog_exit); 213 214MODULE_AUTHOR("Calin A. Culianu <calin@ajvar.org>"); 215MODULE_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!"); 219MODULE_LICENSE("GPL");