1/* 2 * Xen Watchdog Driver 3 * 4 * (c) Copyright 2010 Novell, Inc. 5 * 6 * This program is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU General Public License 8 * as published by the Free Software Foundation; either version 9 * 2 of the License, or (at your option) any later version. 10 */ 11 12#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 13 14#define DRV_NAME "wdt" 15#define DRV_VERSION "0.01" 16 17#include <linux/bug.h> 18#include <linux/errno.h> 19#include <linux/fs.h> 20#include <linux/hrtimer.h> 21#include <linux/kernel.h> 22#include <linux/ktime.h> 23#include <linux/init.h> 24#include <linux/miscdevice.h> 25#include <linux/module.h> 26#include <linux/moduleparam.h> 27#include <linux/platform_device.h> 28#include <linux/spinlock.h> 29#include <linux/uaccess.h> 30#include <linux/watchdog.h> 31#include <xen/xen.h> 32#include <asm/xen/hypercall.h> 33#include <xen/interface/sched.h> 34 35static struct platform_device *platform_device; 36static DEFINE_SPINLOCK(wdt_lock); 37static struct sched_watchdog wdt; 38static __kernel_time_t wdt_expires; 39static bool is_active, expect_release; 40 41#define WATCHDOG_TIMEOUT 60 /* in seconds */ 42static unsigned int timeout = WATCHDOG_TIMEOUT; 43module_param(timeout, uint, S_IRUGO); 44MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds " 45 "(default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); 46 47static bool nowayout = WATCHDOG_NOWAYOUT; 48module_param(nowayout, bool, S_IRUGO); 49MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started " 50 "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 51 52static inline __kernel_time_t set_timeout(void) 53{ 54 wdt.timeout = timeout; 55 return ktime_to_timespec(ktime_get()).tv_sec + timeout; 56} 57 58static int xen_wdt_start(void) 59{ 60 __kernel_time_t expires; 61 int err; 62 63 spin_lock(&wdt_lock); 64 65 expires = set_timeout(); 66 if (!wdt.id) 67 err = HYPERVISOR_sched_op(SCHEDOP_watchdog, &wdt); 68 else 69 err = -EBUSY; 70 if (err > 0) { 71 wdt.id = err; 72 wdt_expires = expires; 73 err = 0; 74 } else 75 BUG_ON(!err); 76 77 spin_unlock(&wdt_lock); 78 79 return err; 80} 81 82static int xen_wdt_stop(void) 83{ 84 int err = 0; 85 86 spin_lock(&wdt_lock); 87 88 wdt.timeout = 0; 89 if (wdt.id) 90 err = HYPERVISOR_sched_op(SCHEDOP_watchdog, &wdt); 91 if (!err) 92 wdt.id = 0; 93 94 spin_unlock(&wdt_lock); 95 96 return err; 97} 98 99static int xen_wdt_kick(void) 100{ 101 __kernel_time_t expires; 102 int err; 103 104 spin_lock(&wdt_lock); 105 106 expires = set_timeout(); 107 if (wdt.id) 108 err = HYPERVISOR_sched_op(SCHEDOP_watchdog, &wdt); 109 else 110 err = -ENXIO; 111 if (!err) 112 wdt_expires = expires; 113 114 spin_unlock(&wdt_lock); 115 116 return err; 117} 118 119static int xen_wdt_open(struct inode *inode, struct file *file) 120{ 121 int err; 122 123 /* /dev/watchdog can only be opened once */ 124 if (xchg(&is_active, true)) 125 return -EBUSY; 126 127 err = xen_wdt_start(); 128 if (err == -EBUSY) 129 err = xen_wdt_kick(); 130 return err ?: nonseekable_open(inode, file); 131} 132 133static int xen_wdt_release(struct inode *inode, struct file *file) 134{ 135 int err = 0; 136 137 if (expect_release) 138 err = xen_wdt_stop(); 139 else { 140 pr_crit("unexpected close, not stopping watchdog!\n"); 141 xen_wdt_kick(); 142 } 143 is_active = err; 144 expect_release = false; 145 return err; 146} 147 148static ssize_t xen_wdt_write(struct file *file, const char __user *data, 149 size_t len, loff_t *ppos) 150{ 151 /* See if we got the magic character 'V' and reload the timer */ 152 if (len) { 153 if (!nowayout) { 154 size_t i; 155 156 /* in case it was set long ago */ 157 expect_release = false; 158 159 /* scan to see whether or not we got the magic 160 character */ 161 for (i = 0; i != len; i++) { 162 char c; 163 if (get_user(c, data + i)) 164 return -EFAULT; 165 if (c == 'V') 166 expect_release = true; 167 } 168 } 169 170 /* someone wrote to us, we should reload the timer */ 171 xen_wdt_kick(); 172 } 173 return len; 174} 175 176static long xen_wdt_ioctl(struct file *file, unsigned int cmd, 177 unsigned long arg) 178{ 179 int new_options, retval = -EINVAL; 180 int new_timeout; 181 int __user *argp = (void __user *)arg; 182 static const struct watchdog_info ident = { 183 .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, 184 .firmware_version = 0, 185 .identity = DRV_NAME, 186 }; 187 188 switch (cmd) { 189 case WDIOC_GETSUPPORT: 190 return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; 191 192 case WDIOC_GETSTATUS: 193 case WDIOC_GETBOOTSTATUS: 194 return put_user(0, argp); 195 196 case WDIOC_SETOPTIONS: 197 if (get_user(new_options, argp)) 198 return -EFAULT; 199 200 if (new_options & WDIOS_DISABLECARD) 201 retval = xen_wdt_stop(); 202 if (new_options & WDIOS_ENABLECARD) { 203 retval = xen_wdt_start(); 204 if (retval == -EBUSY) 205 retval = xen_wdt_kick(); 206 } 207 return retval; 208 209 case WDIOC_KEEPALIVE: 210 xen_wdt_kick(); 211 return 0; 212 213 case WDIOC_SETTIMEOUT: 214 if (get_user(new_timeout, argp)) 215 return -EFAULT; 216 if (!new_timeout) 217 return -EINVAL; 218 timeout = new_timeout; 219 xen_wdt_kick(); 220 /* fall through */ 221 case WDIOC_GETTIMEOUT: 222 return put_user(timeout, argp); 223 224 case WDIOC_GETTIMELEFT: 225 retval = wdt_expires - ktime_to_timespec(ktime_get()).tv_sec; 226 return put_user(retval, argp); 227 } 228 229 return -ENOTTY; 230} 231 232static const struct file_operations xen_wdt_fops = { 233 .owner = THIS_MODULE, 234 .llseek = no_llseek, 235 .write = xen_wdt_write, 236 .unlocked_ioctl = xen_wdt_ioctl, 237 .open = xen_wdt_open, 238 .release = xen_wdt_release, 239}; 240 241static struct miscdevice xen_wdt_miscdev = { 242 .minor = WATCHDOG_MINOR, 243 .name = "watchdog", 244 .fops = &xen_wdt_fops, 245}; 246 247static int xen_wdt_probe(struct platform_device *dev) 248{ 249 struct sched_watchdog wd = { .id = ~0 }; 250 int ret = HYPERVISOR_sched_op(SCHEDOP_watchdog, &wd); 251 252 switch (ret) { 253 case -EINVAL: 254 if (!timeout) { 255 timeout = WATCHDOG_TIMEOUT; 256 pr_info("timeout value invalid, using %d\n", timeout); 257 } 258 259 ret = misc_register(&xen_wdt_miscdev); 260 if (ret) { 261 pr_err("cannot register miscdev on minor=%d (%d)\n", 262 WATCHDOG_MINOR, ret); 263 break; 264 } 265 266 pr_info("initialized (timeout=%ds, nowayout=%d)\n", 267 timeout, nowayout); 268 break; 269 270 case -ENOSYS: 271 pr_info("not supported\n"); 272 ret = -ENODEV; 273 break; 274 275 default: 276 pr_info("bogus return value %d\n", ret); 277 break; 278 } 279 280 return ret; 281} 282 283static int xen_wdt_remove(struct platform_device *dev) 284{ 285 /* Stop the timer before we leave */ 286 if (!nowayout) 287 xen_wdt_stop(); 288 289 misc_deregister(&xen_wdt_miscdev); 290 291 return 0; 292} 293 294static void xen_wdt_shutdown(struct platform_device *dev) 295{ 296 xen_wdt_stop(); 297} 298 299static int xen_wdt_suspend(struct platform_device *dev, pm_message_t state) 300{ 301 typeof(wdt.id) id = wdt.id; 302 int rc = xen_wdt_stop(); 303 304 wdt.id = id; 305 return rc; 306} 307 308static int xen_wdt_resume(struct platform_device *dev) 309{ 310 if (!wdt.id) 311 return 0; 312 wdt.id = 0; 313 return xen_wdt_start(); 314} 315 316static struct platform_driver xen_wdt_driver = { 317 .probe = xen_wdt_probe, 318 .remove = xen_wdt_remove, 319 .shutdown = xen_wdt_shutdown, 320 .suspend = xen_wdt_suspend, 321 .resume = xen_wdt_resume, 322 .driver = { 323 .name = DRV_NAME, 324 }, 325}; 326 327static int __init xen_wdt_init_module(void) 328{ 329 int err; 330 331 if (!xen_domain()) 332 return -ENODEV; 333 334 pr_info("Xen WatchDog Timer Driver v%s\n", DRV_VERSION); 335 336 err = platform_driver_register(&xen_wdt_driver); 337 if (err) 338 return err; 339 340 platform_device = platform_device_register_simple(DRV_NAME, 341 -1, NULL, 0); 342 if (IS_ERR(platform_device)) { 343 err = PTR_ERR(platform_device); 344 platform_driver_unregister(&xen_wdt_driver); 345 } 346 347 return err; 348} 349 350static void __exit xen_wdt_cleanup_module(void) 351{ 352 platform_device_unregister(platform_device); 353 platform_driver_unregister(&xen_wdt_driver); 354 pr_info("module unloaded\n"); 355} 356 357module_init(xen_wdt_init_module); 358module_exit(xen_wdt_cleanup_module); 359 360MODULE_AUTHOR("Jan Beulich <jbeulich@novell.com>"); 361MODULE_DESCRIPTION("Xen WatchDog Timer Driver"); 362MODULE_VERSION(DRV_VERSION); 363MODULE_LICENSE("GPL"); 364