-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
While the hypervisor change adding SCHEDOP_watchdog support included a daemon to make use of the new functionality, having a kernel driver for /dev/watchdog so that user space code doesn't need to distinguish non-Xen and Xen seems to be preferable. Signed-off-by: Jan Beulich <jbeulich@novell.com> Cc: Jeremy Fitzhardinge <jeremy@goop.org> Signed-off-by: Wim Van Sebroeck <wim@iguana.be>
- Loading branch information
Jan Beulich
authored and
Wim Van Sebroeck
committed
Mar 15, 2011
1 parent
57539c1
commit 066d6c7
Showing
4 changed files
with
406 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,359 @@ | ||
/* | ||
* Xen Watchdog Driver | ||
* | ||
* (c) Copyright 2010 Novell, Inc. | ||
* | ||
* This program is free software; you can redistribute it and/or | ||
* modify it under the terms of the GNU General Public License | ||
* as published by the Free Software Foundation; either version | ||
* 2 of the License, or (at your option) any later version. | ||
*/ | ||
|
||
#define DRV_NAME "wdt" | ||
#define DRV_VERSION "0.01" | ||
#define PFX DRV_NAME ": " | ||
|
||
#include <linux/bug.h> | ||
#include <linux/errno.h> | ||
#include <linux/fs.h> | ||
#include <linux/hrtimer.h> | ||
#include <linux/kernel.h> | ||
#include <linux/ktime.h> | ||
#include <linux/init.h> | ||
#include <linux/miscdevice.h> | ||
#include <linux/module.h> | ||
#include <linux/moduleparam.h> | ||
#include <linux/platform_device.h> | ||
#include <linux/spinlock.h> | ||
#include <linux/uaccess.h> | ||
#include <linux/watchdog.h> | ||
#include <xen/xen.h> | ||
#include <asm/xen/hypercall.h> | ||
#include <xen/interface/sched.h> | ||
|
||
static struct platform_device *platform_device; | ||
static DEFINE_SPINLOCK(wdt_lock); | ||
static struct sched_watchdog wdt; | ||
static __kernel_time_t wdt_expires; | ||
static bool is_active, expect_release; | ||
|
||
#define WATCHDOG_TIMEOUT 60 /* in seconds */ | ||
static unsigned int timeout = WATCHDOG_TIMEOUT; | ||
module_param(timeout, uint, S_IRUGO); | ||
MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds " | ||
"(default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); | ||
|
||
static bool nowayout = WATCHDOG_NOWAYOUT; | ||
module_param(nowayout, bool, S_IRUGO); | ||
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started " | ||
"(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
|
||
static inline __kernel_time_t set_timeout(void) | ||
{ | ||
wdt.timeout = timeout; | ||
return ktime_to_timespec(ktime_get()).tv_sec + timeout; | ||
} | ||
|
||
static int xen_wdt_start(void) | ||
{ | ||
__kernel_time_t expires; | ||
int err; | ||
|
||
spin_lock(&wdt_lock); | ||
|
||
expires = set_timeout(); | ||
if (!wdt.id) | ||
err = HYPERVISOR_sched_op(SCHEDOP_watchdog, &wdt); | ||
else | ||
err = -EBUSY; | ||
if (err > 0) { | ||
wdt.id = err; | ||
wdt_expires = expires; | ||
err = 0; | ||
} else | ||
BUG_ON(!err); | ||
|
||
spin_unlock(&wdt_lock); | ||
|
||
return err; | ||
} | ||
|
||
static int xen_wdt_stop(void) | ||
{ | ||
int err = 0; | ||
|
||
spin_lock(&wdt_lock); | ||
|
||
wdt.timeout = 0; | ||
if (wdt.id) | ||
err = HYPERVISOR_sched_op(SCHEDOP_watchdog, &wdt); | ||
if (!err) | ||
wdt.id = 0; | ||
|
||
spin_unlock(&wdt_lock); | ||
|
||
return err; | ||
} | ||
|
||
static int xen_wdt_kick(void) | ||
{ | ||
__kernel_time_t expires; | ||
int err; | ||
|
||
spin_lock(&wdt_lock); | ||
|
||
expires = set_timeout(); | ||
if (wdt.id) | ||
err = HYPERVISOR_sched_op(SCHEDOP_watchdog, &wdt); | ||
else | ||
err = -ENXIO; | ||
if (!err) | ||
wdt_expires = expires; | ||
|
||
spin_unlock(&wdt_lock); | ||
|
||
return err; | ||
} | ||
|
||
static int xen_wdt_open(struct inode *inode, struct file *file) | ||
{ | ||
int err; | ||
|
||
/* /dev/watchdog can only be opened once */ | ||
if (xchg(&is_active, true)) | ||
return -EBUSY; | ||
|
||
err = xen_wdt_start(); | ||
if (err == -EBUSY) | ||
err = xen_wdt_kick(); | ||
return err ?: nonseekable_open(inode, file); | ||
} | ||
|
||
static int xen_wdt_release(struct inode *inode, struct file *file) | ||
{ | ||
if (expect_release) | ||
xen_wdt_stop(); | ||
else { | ||
printk(KERN_CRIT PFX | ||
"unexpected close, not stopping watchdog!\n"); | ||
xen_wdt_kick(); | ||
} | ||
is_active = false; | ||
expect_release = false; | ||
return 0; | ||
} | ||
|
||
static ssize_t xen_wdt_write(struct file *file, const char __user *data, | ||
size_t len, loff_t *ppos) | ||
{ | ||
/* See if we got the magic character 'V' and reload the timer */ | ||
if (len) { | ||
if (!nowayout) { | ||
size_t i; | ||
|
||
/* in case it was set long ago */ | ||
expect_release = false; | ||
|
||
/* scan to see whether or not we got the magic | ||
character */ | ||
for (i = 0; i != len; i++) { | ||
char c; | ||
if (get_user(c, data + i)) | ||
return -EFAULT; | ||
if (c == 'V') | ||
expect_release = true; | ||
} | ||
} | ||
|
||
/* someone wrote to us, we should reload the timer */ | ||
xen_wdt_kick(); | ||
} | ||
return len; | ||
} | ||
|
||
static long xen_wdt_ioctl(struct file *file, unsigned int cmd, | ||
unsigned long arg) | ||
{ | ||
int new_options, retval = -EINVAL; | ||
int new_timeout; | ||
int __user *argp = (void __user *)arg; | ||
static const struct watchdog_info ident = { | ||
.options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, | ||
.firmware_version = 0, | ||
.identity = DRV_NAME, | ||
}; | ||
|
||
switch (cmd) { | ||
case WDIOC_GETSUPPORT: | ||
return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; | ||
|
||
case WDIOC_GETSTATUS: | ||
case WDIOC_GETBOOTSTATUS: | ||
return put_user(0, argp); | ||
|
||
case WDIOC_SETOPTIONS: | ||
if (get_user(new_options, argp)) | ||
return -EFAULT; | ||
|
||
if (new_options & WDIOS_DISABLECARD) | ||
retval = xen_wdt_stop(); | ||
if (new_options & WDIOS_ENABLECARD) { | ||
retval = xen_wdt_start(); | ||
if (retval == -EBUSY) | ||
retval = xen_wdt_kick(); | ||
} | ||
return retval; | ||
|
||
case WDIOC_KEEPALIVE: | ||
xen_wdt_kick(); | ||
return 0; | ||
|
||
case WDIOC_SETTIMEOUT: | ||
if (get_user(new_timeout, argp)) | ||
return -EFAULT; | ||
if (!new_timeout) | ||
return -EINVAL; | ||
timeout = new_timeout; | ||
xen_wdt_kick(); | ||
/* fall through */ | ||
case WDIOC_GETTIMEOUT: | ||
return put_user(timeout, argp); | ||
|
||
case WDIOC_GETTIMELEFT: | ||
retval = wdt_expires - ktime_to_timespec(ktime_get()).tv_sec; | ||
return put_user(retval, argp); | ||
} | ||
|
||
return -ENOTTY; | ||
} | ||
|
||
static const struct file_operations xen_wdt_fops = { | ||
.owner = THIS_MODULE, | ||
.llseek = no_llseek, | ||
.write = xen_wdt_write, | ||
.unlocked_ioctl = xen_wdt_ioctl, | ||
.open = xen_wdt_open, | ||
.release = xen_wdt_release, | ||
}; | ||
|
||
static struct miscdevice xen_wdt_miscdev = { | ||
.minor = WATCHDOG_MINOR, | ||
.name = "watchdog", | ||
.fops = &xen_wdt_fops, | ||
}; | ||
|
||
static int __devinit xen_wdt_probe(struct platform_device *dev) | ||
{ | ||
struct sched_watchdog wd = { .id = ~0 }; | ||
int ret = HYPERVISOR_sched_op(SCHEDOP_watchdog, &wd); | ||
|
||
switch (ret) { | ||
case -EINVAL: | ||
if (!timeout) { | ||
timeout = WATCHDOG_TIMEOUT; | ||
printk(KERN_INFO PFX | ||
"timeout value invalid, using %d\n", timeout); | ||
} | ||
|
||
ret = misc_register(&xen_wdt_miscdev); | ||
if (ret) { | ||
printk(KERN_ERR PFX | ||
"cannot register miscdev on minor=%d (%d)\n", | ||
WATCHDOG_MINOR, ret); | ||
break; | ||
} | ||
|
||
printk(KERN_INFO PFX | ||
"initialized (timeout=%ds, nowayout=%d)\n", | ||
timeout, nowayout); | ||
break; | ||
|
||
case -ENOSYS: | ||
printk(KERN_INFO PFX "not supported\n"); | ||
ret = -ENODEV; | ||
break; | ||
|
||
default: | ||
printk(KERN_INFO PFX "bogus return value %d\n", ret); | ||
break; | ||
} | ||
|
||
return ret; | ||
} | ||
|
||
static int __devexit xen_wdt_remove(struct platform_device *dev) | ||
{ | ||
/* Stop the timer before we leave */ | ||
if (!nowayout) | ||
xen_wdt_stop(); | ||
|
||
misc_deregister(&xen_wdt_miscdev); | ||
|
||
return 0; | ||
} | ||
|
||
static void xen_wdt_shutdown(struct platform_device *dev) | ||
{ | ||
xen_wdt_stop(); | ||
} | ||
|
||
static int xen_wdt_suspend(struct platform_device *dev, pm_message_t state) | ||
{ | ||
return xen_wdt_stop(); | ||
} | ||
|
||
static int xen_wdt_resume(struct platform_device *dev) | ||
{ | ||
return xen_wdt_start(); | ||
} | ||
|
||
static struct platform_driver xen_wdt_driver = { | ||
.probe = xen_wdt_probe, | ||
.remove = __devexit_p(xen_wdt_remove), | ||
.shutdown = xen_wdt_shutdown, | ||
.suspend = xen_wdt_suspend, | ||
.resume = xen_wdt_resume, | ||
.driver = { | ||
.owner = THIS_MODULE, | ||
.name = DRV_NAME, | ||
}, | ||
}; | ||
|
||
static int __init xen_wdt_init_module(void) | ||
{ | ||
int err; | ||
|
||
if (!xen_domain()) | ||
return -ENODEV; | ||
|
||
printk(KERN_INFO PFX "Xen WatchDog Timer Driver v%s\n", DRV_VERSION); | ||
|
||
err = platform_driver_register(&xen_wdt_driver); | ||
if (err) | ||
return err; | ||
|
||
platform_device = platform_device_register_simple(DRV_NAME, | ||
-1, NULL, 0); | ||
if (IS_ERR(platform_device)) { | ||
err = PTR_ERR(platform_device); | ||
platform_driver_unregister(&xen_wdt_driver); | ||
} | ||
|
||
return err; | ||
} | ||
|
||
static void __exit xen_wdt_cleanup_module(void) | ||
{ | ||
platform_device_unregister(platform_device); | ||
platform_driver_unregister(&xen_wdt_driver); | ||
printk(KERN_INFO PFX "module unloaded\n"); | ||
} | ||
|
||
module_init(xen_wdt_init_module); | ||
module_exit(xen_wdt_cleanup_module); | ||
|
||
MODULE_AUTHOR("Jan Beulich <jbeulich@novell.com>"); | ||
MODULE_DESCRIPTION("Xen WatchDog Timer Driver"); | ||
MODULE_VERSION(DRV_VERSION); | ||
MODULE_LICENSE("GPL"); | ||
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); |
Oops, something went wrong.