|
@@ -1,17 +1,23 @@
|
|
/*
|
|
/*
|
|
* Watchdog implementation based on z/VM Watchdog Timer API
|
|
* Watchdog implementation based on z/VM Watchdog Timer API
|
|
*
|
|
*
|
|
|
|
+ * Copyright IBM Corp. 2004,2009
|
|
|
|
+ *
|
|
* The user space watchdog daemon can use this driver as
|
|
* The user space watchdog daemon can use this driver as
|
|
* /dev/vmwatchdog to have z/VM execute the specified CP
|
|
* /dev/vmwatchdog to have z/VM execute the specified CP
|
|
* command when the timeout expires. The default command is
|
|
* command when the timeout expires. The default command is
|
|
* "IPL", which which cause an immediate reboot.
|
|
* "IPL", which which cause an immediate reboot.
|
|
*/
|
|
*/
|
|
|
|
+#define KMSG_COMPONENT "vmwatchdog"
|
|
|
|
+#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
|
|
|
|
+
|
|
#include <linux/init.h>
|
|
#include <linux/init.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/module.h>
|
|
#include <linux/module.h>
|
|
#include <linux/moduleparam.h>
|
|
#include <linux/moduleparam.h>
|
|
|
|
+#include <linux/suspend.h>
|
|
#include <linux/watchdog.h>
|
|
#include <linux/watchdog.h>
|
|
#include <linux/smp_lock.h>
|
|
#include <linux/smp_lock.h>
|
|
|
|
|
|
@@ -43,6 +49,9 @@ static unsigned int vmwdt_interval = 60;
|
|
static unsigned long vmwdt_is_open;
|
|
static unsigned long vmwdt_is_open;
|
|
static int vmwdt_expect_close;
|
|
static int vmwdt_expect_close;
|
|
|
|
|
|
|
|
+#define VMWDT_OPEN 0 /* devnode is open or suspend in progress */
|
|
|
|
+#define VMWDT_RUNNING 1 /* The watchdog is armed */
|
|
|
|
+
|
|
enum vmwdt_func {
|
|
enum vmwdt_func {
|
|
/* function codes */
|
|
/* function codes */
|
|
wdt_init = 0,
|
|
wdt_init = 0,
|
|
@@ -92,6 +101,7 @@ static int vmwdt_keepalive(void)
|
|
EBC_TOUPPER(ebc_cmd, MAX_CMDLEN);
|
|
EBC_TOUPPER(ebc_cmd, MAX_CMDLEN);
|
|
|
|
|
|
func = vmwdt_conceal ? (wdt_init | wdt_conceal) : wdt_init;
|
|
func = vmwdt_conceal ? (wdt_init | wdt_conceal) : wdt_init;
|
|
|
|
+ set_bit(VMWDT_RUNNING, &vmwdt_is_open);
|
|
ret = __diag288(func, vmwdt_interval, ebc_cmd, len);
|
|
ret = __diag288(func, vmwdt_interval, ebc_cmd, len);
|
|
WARN_ON(ret != 0);
|
|
WARN_ON(ret != 0);
|
|
kfree(ebc_cmd);
|
|
kfree(ebc_cmd);
|
|
@@ -102,6 +112,7 @@ static int vmwdt_disable(void)
|
|
{
|
|
{
|
|
int ret = __diag288(wdt_cancel, 0, "", 0);
|
|
int ret = __diag288(wdt_cancel, 0, "", 0);
|
|
WARN_ON(ret != 0);
|
|
WARN_ON(ret != 0);
|
|
|
|
+ clear_bit(VMWDT_RUNNING, &vmwdt_is_open);
|
|
return ret;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -123,13 +134,13 @@ static int vmwdt_open(struct inode *i, struct file *f)
|
|
{
|
|
{
|
|
int ret;
|
|
int ret;
|
|
lock_kernel();
|
|
lock_kernel();
|
|
- if (test_and_set_bit(0, &vmwdt_is_open)) {
|
|
|
|
|
|
+ if (test_and_set_bit(VMWDT_OPEN, &vmwdt_is_open)) {
|
|
unlock_kernel();
|
|
unlock_kernel();
|
|
return -EBUSY;
|
|
return -EBUSY;
|
|
}
|
|
}
|
|
ret = vmwdt_keepalive();
|
|
ret = vmwdt_keepalive();
|
|
if (ret)
|
|
if (ret)
|
|
- clear_bit(0, &vmwdt_is_open);
|
|
|
|
|
|
+ clear_bit(VMWDT_OPEN, &vmwdt_is_open);
|
|
unlock_kernel();
|
|
unlock_kernel();
|
|
return ret ? ret : nonseekable_open(i, f);
|
|
return ret ? ret : nonseekable_open(i, f);
|
|
}
|
|
}
|
|
@@ -139,7 +150,7 @@ static int vmwdt_close(struct inode *i, struct file *f)
|
|
if (vmwdt_expect_close == 42)
|
|
if (vmwdt_expect_close == 42)
|
|
vmwdt_disable();
|
|
vmwdt_disable();
|
|
vmwdt_expect_close = 0;
|
|
vmwdt_expect_close = 0;
|
|
- clear_bit(0, &vmwdt_is_open);
|
|
|
|
|
|
+ clear_bit(VMWDT_OPEN, &vmwdt_is_open);
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -223,6 +234,57 @@ static ssize_t vmwdt_write(struct file *f, const char __user *buf,
|
|
return count;
|
|
return count;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static int vmwdt_resume(void)
|
|
|
|
+{
|
|
|
|
+ clear_bit(VMWDT_OPEN, &vmwdt_is_open);
|
|
|
|
+ return NOTIFY_DONE;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * It makes no sense to go into suspend while the watchdog is running.
|
|
|
|
+ * Depending on the memory size, the watchdog might trigger, while we
|
|
|
|
+ * are still saving the memory.
|
|
|
|
+ * We reuse the open flag to ensure that suspend and watchdog open are
|
|
|
|
+ * exclusive operations
|
|
|
|
+ */
|
|
|
|
+static int vmwdt_suspend(void)
|
|
|
|
+{
|
|
|
|
+ if (test_and_set_bit(VMWDT_OPEN, &vmwdt_is_open)) {
|
|
|
|
+ pr_err("The watchdog is in use. "
|
|
|
|
+ "This prevents hibernation or suspend.\n");
|
|
|
|
+ return NOTIFY_BAD;
|
|
|
|
+ }
|
|
|
|
+ if (test_bit(VMWDT_RUNNING, &vmwdt_is_open)) {
|
|
|
|
+ clear_bit(VMWDT_OPEN, &vmwdt_is_open);
|
|
|
|
+ pr_err("The watchdog is running. "
|
|
|
|
+ "This prevents hibernation or suspend.\n");
|
|
|
|
+ return NOTIFY_BAD;
|
|
|
|
+ }
|
|
|
|
+ return NOTIFY_DONE;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * This function is called for suspend and resume.
|
|
|
|
+ */
|
|
|
|
+static int vmwdt_power_event(struct notifier_block *this, unsigned long event,
|
|
|
|
+ void *ptr)
|
|
|
|
+{
|
|
|
|
+ switch (event) {
|
|
|
|
+ case PM_POST_HIBERNATION:
|
|
|
|
+ case PM_POST_SUSPEND:
|
|
|
|
+ return vmwdt_resume();
|
|
|
|
+ case PM_HIBERNATION_PREPARE:
|
|
|
|
+ case PM_SUSPEND_PREPARE:
|
|
|
|
+ return vmwdt_suspend();
|
|
|
|
+ default:
|
|
|
|
+ return NOTIFY_DONE;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static struct notifier_block vmwdt_power_notifier = {
|
|
|
|
+ .notifier_call = vmwdt_power_event,
|
|
|
|
+};
|
|
|
|
+
|
|
static const struct file_operations vmwdt_fops = {
|
|
static const struct file_operations vmwdt_fops = {
|
|
.open = &vmwdt_open,
|
|
.open = &vmwdt_open,
|
|
.release = &vmwdt_close,
|
|
.release = &vmwdt_close,
|
|
@@ -244,12 +306,21 @@ static int __init vmwdt_init(void)
|
|
ret = vmwdt_probe();
|
|
ret = vmwdt_probe();
|
|
if (ret)
|
|
if (ret)
|
|
return ret;
|
|
return ret;
|
|
- return misc_register(&vmwdt_dev);
|
|
|
|
|
|
+ ret = register_pm_notifier(&vmwdt_power_notifier);
|
|
|
|
+ if (ret)
|
|
|
|
+ return ret;
|
|
|
|
+ ret = misc_register(&vmwdt_dev);
|
|
|
|
+ if (ret) {
|
|
|
|
+ unregister_pm_notifier(&vmwdt_power_notifier);
|
|
|
|
+ return ret;
|
|
|
|
+ }
|
|
|
|
+ return 0;
|
|
}
|
|
}
|
|
module_init(vmwdt_init);
|
|
module_init(vmwdt_init);
|
|
|
|
|
|
static void __exit vmwdt_exit(void)
|
|
static void __exit vmwdt_exit(void)
|
|
{
|
|
{
|
|
- WARN_ON(misc_deregister(&vmwdt_dev) != 0);
|
|
|
|
|
|
+ unregister_pm_notifier(&vmwdt_power_notifier);
|
|
|
|
+ misc_deregister(&vmwdt_dev);
|
|
}
|
|
}
|
|
module_exit(vmwdt_exit);
|
|
module_exit(vmwdt_exit);
|