pika_wdt.c (6965B)
1// SPDX-License-Identifier: GPL-2.0-only 2/* 3 * PIKA FPGA based Watchdog Timer 4 * 5 * Copyright (c) 2008 PIKA Technologies 6 * Sean MacLennan <smaclennan@pikatech.com> 7 */ 8 9#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 10 11#include <linux/init.h> 12#include <linux/errno.h> 13#include <linux/module.h> 14#include <linux/moduleparam.h> 15#include <linux/types.h> 16#include <linux/kernel.h> 17#include <linux/fs.h> 18#include <linux/miscdevice.h> 19#include <linux/watchdog.h> 20#include <linux/reboot.h> 21#include <linux/jiffies.h> 22#include <linux/timer.h> 23#include <linux/bitops.h> 24#include <linux/uaccess.h> 25#include <linux/io.h> 26#include <linux/of_address.h> 27#include <linux/of_platform.h> 28 29#define DRV_NAME "PIKA-WDT" 30 31/* Hardware timeout in seconds */ 32#define WDT_HW_TIMEOUT 2 33 34/* Timer heartbeat (500ms) */ 35#define WDT_TIMEOUT (HZ/2) 36 37/* User land timeout */ 38#define WDT_HEARTBEAT 15 39static int heartbeat = WDT_HEARTBEAT; 40module_param(heartbeat, int, 0); 41MODULE_PARM_DESC(heartbeat, "Watchdog heartbeats in seconds. " 42 "(default = " __MODULE_STRING(WDT_HEARTBEAT) ")"); 43 44static bool nowayout = WATCHDOG_NOWAYOUT; 45module_param(nowayout, bool, 0); 46MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started " 47 "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 48 49static struct { 50 void __iomem *fpga; 51 unsigned long next_heartbeat; /* the next_heartbeat for the timer */ 52 unsigned long open; 53 char expect_close; 54 int bootstatus; 55 struct timer_list timer; /* The timer that pings the watchdog */ 56} pikawdt_private; 57 58static struct watchdog_info ident __ro_after_init = { 59 .identity = DRV_NAME, 60 .options = WDIOF_CARDRESET | 61 WDIOF_SETTIMEOUT | 62 WDIOF_KEEPALIVEPING | 63 WDIOF_MAGICCLOSE, 64}; 65 66/* 67 * Reload the watchdog timer. (ie, pat the watchdog) 68 */ 69static inline void pikawdt_reset(void) 70{ 71 /* -- FPGA: Reset Control Register (32bit R/W) (Offset: 0x14) -- 72 * Bit 7, WTCHDG_EN: When set to 1, the watchdog timer is enabled. 73 * Once enabled, it cannot be disabled. The watchdog can be 74 * kicked by performing any write access to the reset 75 * control register (this register). 76 * Bit 8-11, WTCHDG_TIMEOUT_SEC: Sets the watchdog timeout value in 77 * seconds. Valid ranges are 1 to 15 seconds. The value can 78 * be modified dynamically. 79 */ 80 unsigned reset = in_be32(pikawdt_private.fpga + 0x14); 81 /* enable with max timeout - 15 seconds */ 82 reset |= (1 << 7) + (WDT_HW_TIMEOUT << 8); 83 out_be32(pikawdt_private.fpga + 0x14, reset); 84} 85 86/* 87 * Timer tick 88 */ 89static void pikawdt_ping(struct timer_list *unused) 90{ 91 if (time_before(jiffies, pikawdt_private.next_heartbeat) || 92 (!nowayout && !pikawdt_private.open)) { 93 pikawdt_reset(); 94 mod_timer(&pikawdt_private.timer, jiffies + WDT_TIMEOUT); 95 } else 96 pr_crit("I will reset your machine !\n"); 97} 98 99 100static void pikawdt_keepalive(void) 101{ 102 pikawdt_private.next_heartbeat = jiffies + heartbeat * HZ; 103} 104 105static void pikawdt_start(void) 106{ 107 pikawdt_keepalive(); 108 mod_timer(&pikawdt_private.timer, jiffies + WDT_TIMEOUT); 109} 110 111/* 112 * Watchdog device is opened, and watchdog starts running. 113 */ 114static int pikawdt_open(struct inode *inode, struct file *file) 115{ 116 /* /dev/watchdog can only be opened once */ 117 if (test_and_set_bit(0, &pikawdt_private.open)) 118 return -EBUSY; 119 120 pikawdt_start(); 121 122 return stream_open(inode, file); 123} 124 125/* 126 * Close the watchdog device. 127 */ 128static int pikawdt_release(struct inode *inode, struct file *file) 129{ 130 /* stop internal ping */ 131 if (!pikawdt_private.expect_close) 132 del_timer(&pikawdt_private.timer); 133 134 clear_bit(0, &pikawdt_private.open); 135 pikawdt_private.expect_close = 0; 136 return 0; 137} 138 139/* 140 * Pat the watchdog whenever device is written to. 141 */ 142static ssize_t pikawdt_write(struct file *file, const char __user *data, 143 size_t len, loff_t *ppos) 144{ 145 if (!len) 146 return 0; 147 148 /* Scan for magic character */ 149 if (!nowayout) { 150 size_t i; 151 152 pikawdt_private.expect_close = 0; 153 154 for (i = 0; i < len; i++) { 155 char c; 156 if (get_user(c, data + i)) 157 return -EFAULT; 158 if (c == 'V') { 159 pikawdt_private.expect_close = 42; 160 break; 161 } 162 } 163 } 164 165 pikawdt_keepalive(); 166 167 return len; 168} 169 170/* 171 * Handle commands from user-space. 172 */ 173static long pikawdt_ioctl(struct file *file, 174 unsigned int cmd, unsigned long arg) 175{ 176 void __user *argp = (void __user *)arg; 177 int __user *p = argp; 178 int new_value; 179 180 switch (cmd) { 181 case WDIOC_GETSUPPORT: 182 return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; 183 184 case WDIOC_GETSTATUS: 185 return put_user(0, p); 186 187 case WDIOC_GETBOOTSTATUS: 188 return put_user(pikawdt_private.bootstatus, p); 189 190 case WDIOC_KEEPALIVE: 191 pikawdt_keepalive(); 192 return 0; 193 194 case WDIOC_SETTIMEOUT: 195 if (get_user(new_value, p)) 196 return -EFAULT; 197 198 heartbeat = new_value; 199 pikawdt_keepalive(); 200 201 return put_user(new_value, p); /* return current value */ 202 203 case WDIOC_GETTIMEOUT: 204 return put_user(heartbeat, p); 205 } 206 return -ENOTTY; 207} 208 209 210static const struct file_operations pikawdt_fops = { 211 .owner = THIS_MODULE, 212 .llseek = no_llseek, 213 .open = pikawdt_open, 214 .release = pikawdt_release, 215 .write = pikawdt_write, 216 .unlocked_ioctl = pikawdt_ioctl, 217 .compat_ioctl = compat_ptr_ioctl, 218}; 219 220static struct miscdevice pikawdt_miscdev = { 221 .minor = WATCHDOG_MINOR, 222 .name = "watchdog", 223 .fops = &pikawdt_fops, 224}; 225 226static int __init pikawdt_init(void) 227{ 228 struct device_node *np; 229 void __iomem *fpga; 230 u32 post1; 231 int ret; 232 233 np = of_find_compatible_node(NULL, NULL, "pika,fpga"); 234 if (np == NULL) { 235 pr_err("Unable to find fpga\n"); 236 return -ENOENT; 237 } 238 239 pikawdt_private.fpga = of_iomap(np, 0); 240 of_node_put(np); 241 if (pikawdt_private.fpga == NULL) { 242 pr_err("Unable to map fpga\n"); 243 return -ENOMEM; 244 } 245 246 ident.firmware_version = in_be32(pikawdt_private.fpga + 0x1c) & 0xffff; 247 248 /* POST information is in the sd area. */ 249 np = of_find_compatible_node(NULL, NULL, "pika,fpga-sd"); 250 if (np == NULL) { 251 pr_err("Unable to find fpga-sd\n"); 252 ret = -ENOENT; 253 goto out; 254 } 255 256 fpga = of_iomap(np, 0); 257 of_node_put(np); 258 if (fpga == NULL) { 259 pr_err("Unable to map fpga-sd\n"); 260 ret = -ENOMEM; 261 goto out; 262 } 263 264 /* -- FPGA: POST Test Results Register 1 (32bit R/W) (Offset: 0x4040) -- 265 * Bit 31, WDOG: Set to 1 when the last reset was caused by a watchdog 266 * timeout. 267 */ 268 post1 = in_be32(fpga + 0x40); 269 if (post1 & 0x80000000) 270 pikawdt_private.bootstatus = WDIOF_CARDRESET; 271 272 iounmap(fpga); 273 274 timer_setup(&pikawdt_private.timer, pikawdt_ping, 0); 275 276 ret = misc_register(&pikawdt_miscdev); 277 if (ret) { 278 pr_err("Unable to register miscdev\n"); 279 goto out; 280 } 281 282 pr_info("initialized. heartbeat=%d sec (nowayout=%d)\n", 283 heartbeat, nowayout); 284 return 0; 285 286out: 287 iounmap(pikawdt_private.fpga); 288 return ret; 289} 290 291static void __exit pikawdt_exit(void) 292{ 293 misc_deregister(&pikawdt_miscdev); 294 295 iounmap(pikawdt_private.fpga); 296} 297 298module_init(pikawdt_init); 299module_exit(pikawdt_exit); 300 301MODULE_AUTHOR("Sean MacLennan <smaclennan@pikatech.com>"); 302MODULE_DESCRIPTION("PIKA FPGA based Watchdog Timer"); 303MODULE_LICENSE("GPL");