285 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * sma cpu5 watchdog driver4 *5 * Copyright (C) 2003 Heiko Ronsdorf <hero@ihg.uni-duisburg.de>6 */7 8#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt9 10#include <linux/module.h>11#include <linux/moduleparam.h>12#include <linux/types.h>13#include <linux/errno.h>14#include <linux/miscdevice.h>15#include <linux/fs.h>16#include <linux/ioport.h>17#include <linux/timer.h>18#include <linux/completion.h>19#include <linux/jiffies.h>20#include <linux/io.h>21#include <linux/uaccess.h>22#include <linux/watchdog.h>23 24/* adjustable parameters */25 26static int verbose;27static int port = 0x91;28static int ticks = 10000;29static DEFINE_SPINLOCK(cpu5wdt_lock);30 31#define PFX "cpu5wdt: "32 33#define CPU5WDT_EXTENT 0x0A34 35#define CPU5WDT_STATUS_REG 0x0036#define CPU5WDT_TIME_A_REG 0x0237#define CPU5WDT_TIME_B_REG 0x0338#define CPU5WDT_MODE_REG 0x0439#define CPU5WDT_TRIGGER_REG 0x0740#define CPU5WDT_ENABLE_REG 0x0841#define CPU5WDT_RESET_REG 0x0942 43#define CPU5WDT_INTERVAL (HZ/10+1)44 45/* some device data */46 47static struct {48 struct completion stop;49 int running;50 struct timer_list timer;51 int queue;52 int default_ticks;53 unsigned long inuse;54} cpu5wdt_device;55 56/* generic helper functions */57 58static void cpu5wdt_trigger(struct timer_list *unused)59{60 if (verbose > 2)61 pr_debug("trigger at %i ticks\n", ticks);62 63 if (cpu5wdt_device.running)64 ticks--;65 66 spin_lock(&cpu5wdt_lock);67 /* keep watchdog alive */68 outb(1, port + CPU5WDT_TRIGGER_REG);69 70 /* requeue?? */71 if (cpu5wdt_device.queue && ticks)72 mod_timer(&cpu5wdt_device.timer, jiffies + CPU5WDT_INTERVAL);73 else {74 /* ticks doesn't matter anyway */75 complete(&cpu5wdt_device.stop);76 }77 spin_unlock(&cpu5wdt_lock);78 79}80 81static void cpu5wdt_reset(void)82{83 ticks = cpu5wdt_device.default_ticks;84 85 if (verbose)86 pr_debug("reset (%i ticks)\n", (int) ticks);87 88}89 90static void cpu5wdt_start(void)91{92 unsigned long flags;93 94 spin_lock_irqsave(&cpu5wdt_lock, flags);95 if (!cpu5wdt_device.queue) {96 cpu5wdt_device.queue = 1;97 outb(0, port + CPU5WDT_TIME_A_REG);98 outb(0, port + CPU5WDT_TIME_B_REG);99 outb(1, port + CPU5WDT_MODE_REG);100 outb(0, port + CPU5WDT_RESET_REG);101 outb(0, port + CPU5WDT_ENABLE_REG);102 mod_timer(&cpu5wdt_device.timer, jiffies + CPU5WDT_INTERVAL);103 }104 /* if process dies, counter is not decremented */105 cpu5wdt_device.running++;106 spin_unlock_irqrestore(&cpu5wdt_lock, flags);107}108 109static int cpu5wdt_stop(void)110{111 unsigned long flags;112 113 spin_lock_irqsave(&cpu5wdt_lock, flags);114 if (cpu5wdt_device.running)115 cpu5wdt_device.running = 0;116 ticks = cpu5wdt_device.default_ticks;117 spin_unlock_irqrestore(&cpu5wdt_lock, flags);118 if (verbose)119 pr_crit("stop not possible\n");120 return -EIO;121}122 123/* filesystem operations */124 125static int cpu5wdt_open(struct inode *inode, struct file *file)126{127 if (test_and_set_bit(0, &cpu5wdt_device.inuse))128 return -EBUSY;129 return stream_open(inode, file);130}131 132static int cpu5wdt_release(struct inode *inode, struct file *file)133{134 clear_bit(0, &cpu5wdt_device.inuse);135 return 0;136}137 138static long cpu5wdt_ioctl(struct file *file, unsigned int cmd,139 unsigned long arg)140{141 void __user *argp = (void __user *)arg;142 int __user *p = argp;143 unsigned int value;144 static const struct watchdog_info ident = {145 .options = WDIOF_CARDRESET,146 .identity = "CPU5 WDT",147 };148 149 switch (cmd) {150 case WDIOC_GETSUPPORT:151 if (copy_to_user(argp, &ident, sizeof(ident)))152 return -EFAULT;153 break;154 case WDIOC_GETSTATUS:155 value = inb(port + CPU5WDT_STATUS_REG);156 value = (value >> 2) & 1;157 return put_user(value, p);158 case WDIOC_GETBOOTSTATUS:159 return put_user(0, p);160 case WDIOC_SETOPTIONS:161 if (get_user(value, p))162 return -EFAULT;163 if (value & WDIOS_ENABLECARD)164 cpu5wdt_start();165 if (value & WDIOS_DISABLECARD)166 cpu5wdt_stop();167 break;168 case WDIOC_KEEPALIVE:169 cpu5wdt_reset();170 break;171 default:172 return -ENOTTY;173 }174 return 0;175}176 177static ssize_t cpu5wdt_write(struct file *file, const char __user *buf,178 size_t count, loff_t *ppos)179{180 if (!count)181 return -EIO;182 cpu5wdt_reset();183 return count;184}185 186static const struct file_operations cpu5wdt_fops = {187 .owner = THIS_MODULE,188 .unlocked_ioctl = cpu5wdt_ioctl,189 .compat_ioctl = compat_ptr_ioctl,190 .open = cpu5wdt_open,191 .write = cpu5wdt_write,192 .release = cpu5wdt_release,193};194 195static struct miscdevice cpu5wdt_misc = {196 .minor = WATCHDOG_MINOR,197 .name = "watchdog",198 .fops = &cpu5wdt_fops,199};200 201/* init/exit function */202 203static int cpu5wdt_init(void)204{205 unsigned int val;206 int err;207 208 if (verbose)209 pr_debug("port=0x%x, verbose=%i\n", port, verbose);210 211 init_completion(&cpu5wdt_device.stop);212 cpu5wdt_device.queue = 0;213 timer_setup(&cpu5wdt_device.timer, cpu5wdt_trigger, 0);214 cpu5wdt_device.default_ticks = ticks;215 216 if (!request_region(port, CPU5WDT_EXTENT, PFX)) {217 pr_err("request_region failed\n");218 err = -EBUSY;219 goto no_port;220 }221 222 /* watchdog reboot? */223 val = inb(port + CPU5WDT_STATUS_REG);224 val = (val >> 2) & 1;225 if (!val)226 pr_info("sorry, was my fault\n");227 228 err = misc_register(&cpu5wdt_misc);229 if (err < 0) {230 pr_err("misc_register failed\n");231 goto no_misc;232 }233 234 235 pr_info("init success\n");236 return 0;237 238no_misc:239 release_region(port, CPU5WDT_EXTENT);240no_port:241 return err;242}243 244static int cpu5wdt_init_module(void)245{246 return cpu5wdt_init();247}248 249static void cpu5wdt_exit(void)250{251 if (cpu5wdt_device.queue) {252 cpu5wdt_device.queue = 0;253 wait_for_completion(&cpu5wdt_device.stop);254 timer_shutdown_sync(&cpu5wdt_device.timer);255 }256 257 misc_deregister(&cpu5wdt_misc);258 259 release_region(port, CPU5WDT_EXTENT);260 261}262 263static void cpu5wdt_exit_module(void)264{265 cpu5wdt_exit();266}267 268/* module entry points */269 270module_init(cpu5wdt_init_module);271module_exit(cpu5wdt_exit_module);272 273MODULE_AUTHOR("Heiko Ronsdorf <hero@ihg.uni-duisburg.de>");274MODULE_DESCRIPTION("sma cpu5 watchdog driver");275MODULE_LICENSE("GPL");276 277module_param_hw(port, int, ioport, 0);278MODULE_PARM_DESC(port, "base address of watchdog card, default is 0x91");279 280module_param(verbose, int, 0);281MODULE_PARM_DESC(verbose, "be verbose, default is 0 (no)");282 283module_param(ticks, int, 0);284MODULE_PARM_DESC(ticks, "count down ticks, default is 10000");285