linux/drivers/watchdog/xen_wdt.c
<<
>>
Prefs
   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                .owner  = THIS_MODULE,
 324                .name   = DRV_NAME,
 325        },
 326};
 327
 328static int __init xen_wdt_init_module(void)
 329{
 330        int err;
 331
 332        if (!xen_domain())
 333                return -ENODEV;
 334
 335        pr_info("Xen WatchDog Timer Driver v%s\n", DRV_VERSION);
 336
 337        err = platform_driver_register(&xen_wdt_driver);
 338        if (err)
 339                return err;
 340
 341        platform_device = platform_device_register_simple(DRV_NAME,
 342                                                                  -1, NULL, 0);
 343        if (IS_ERR(platform_device)) {
 344                err = PTR_ERR(platform_device);
 345                platform_driver_unregister(&xen_wdt_driver);
 346        }
 347
 348        return err;
 349}
 350
 351static void __exit xen_wdt_cleanup_module(void)
 352{
 353        platform_device_unregister(platform_device);
 354        platform_driver_unregister(&xen_wdt_driver);
 355        pr_info("module unloaded\n");
 356}
 357
 358module_init(xen_wdt_init_module);
 359module_exit(xen_wdt_cleanup_module);
 360
 361MODULE_AUTHOR("Jan Beulich <jbeulich@novell.com>");
 362MODULE_DESCRIPTION("Xen WatchDog Timer Driver");
 363MODULE_VERSION(DRV_VERSION);
 364MODULE_LICENSE("GPL");
 365