aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/watchdog/watchdog_dev.c
diff options
context:
space:
mode:
authorGuenter Roeck <linux@roeck-us.net>2016-02-28 13:12:15 -0800
committerWim Van Sebroeck <wim@iguana.be>2016-03-16 21:11:14 +0100
commit664a39236e718f9f03fa73fc01006da9ced04efc (patch)
tree116bfdc2660493a14cc08769d23236c1070b533a /drivers/watchdog/watchdog_dev.c
parentwatchdog: Make set_timeout function optional (diff)
downloadlinux-dev-664a39236e718f9f03fa73fc01006da9ced04efc.tar.xz
linux-dev-664a39236e718f9f03fa73fc01006da9ced04efc.zip
watchdog: Introduce hardware maximum heartbeat in watchdog core
Introduce an optional hardware maximum heartbeat in the watchdog core. The hardware maximum heartbeat can be lower than the maximum timeout. Drivers can set the maximum hardware heartbeat value in the watchdog data structure. If the configured timeout exceeds the maximum hardware heartbeat, the watchdog core enables a timer function to assist sending keepalive requests to the watchdog driver. Signed-off-by: Guenter Roeck <linux@roeck-us.net> Signed-off-by: Wim Van Sebroeck <wim@iguana.be>
Diffstat (limited to 'drivers/watchdog/watchdog_dev.c')
-rw-r--r--drivers/watchdog/watchdog_dev.c129
1 files changed, 121 insertions, 8 deletions
diff --git a/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c
index b5e700186ae0..e668a9e8b648 100644
--- a/drivers/watchdog/watchdog_dev.c
+++ b/drivers/watchdog/watchdog_dev.c
@@ -36,6 +36,7 @@
#include <linux/errno.h> /* For the -ENODEV/... values */
#include <linux/fs.h> /* For file operations */
#include <linux/init.h> /* For __init/__exit/... */
+#include <linux/jiffies.h> /* For timeout functions */
#include <linux/kernel.h> /* For printk/panic/... */
#include <linux/kref.h> /* For data references */
#include <linux/miscdevice.h> /* For handling misc devices */
@@ -44,6 +45,7 @@
#include <linux/slab.h> /* For memory functions */
#include <linux/types.h> /* For standard types (like size_t) */
#include <linux/watchdog.h> /* For watchdog specific items */
+#include <linux/workqueue.h> /* For workqueue */
#include <linux/uaccess.h> /* For copy_to_user/put_user/... */
#include "watchdog_core.h"
@@ -61,6 +63,8 @@ struct watchdog_core_data {
struct cdev cdev;
struct watchdog_device *wdd;
struct mutex lock;
+ unsigned long last_keepalive;
+ struct delayed_work work;
unsigned long status; /* Internal status bits */
#define _WDOG_DEV_OPEN 0 /* Opened ? */
#define _WDOG_ALLOW_RELEASE 1 /* Did we receive the magic char ? */
@@ -71,6 +75,76 @@ static dev_t watchdog_devt;
/* Reference to watchdog device behind /dev/watchdog */
static struct watchdog_core_data *old_wd_data;
+static struct workqueue_struct *watchdog_wq;
+
+static inline bool watchdog_need_worker(struct watchdog_device *wdd)
+{
+ /* All variables in milli-seconds */
+ unsigned int hm = wdd->max_hw_heartbeat_ms;
+ unsigned int t = wdd->timeout * 1000;
+
+ /*
+ * A worker to generate heartbeat requests is needed if all of the
+ * following conditions are true.
+ * - Userspace activated the watchdog.
+ * - The driver provided a value for the maximum hardware timeout, and
+ * thus is aware that the framework supports generating heartbeat
+ * requests.
+ * - Userspace requests a longer timeout than the hardware can handle.
+ */
+ return watchdog_active(wdd) && hm && t > hm;
+}
+
+static long watchdog_next_keepalive(struct watchdog_device *wdd)
+{
+ struct watchdog_core_data *wd_data = wdd->wd_data;
+ unsigned int timeout_ms = wdd->timeout * 1000;
+ unsigned long keepalive_interval;
+ unsigned long last_heartbeat;
+ unsigned long virt_timeout;
+ unsigned int hw_heartbeat_ms;
+
+ virt_timeout = wd_data->last_keepalive + msecs_to_jiffies(timeout_ms);
+ hw_heartbeat_ms = min(timeout_ms, wdd->max_hw_heartbeat_ms);
+ keepalive_interval = msecs_to_jiffies(hw_heartbeat_ms / 2);
+
+ /*
+ * To ensure that the watchdog times out wdd->timeout seconds
+ * after the most recent ping from userspace, the last
+ * worker ping has to come in hw_heartbeat_ms before this timeout.
+ */
+ last_heartbeat = virt_timeout - msecs_to_jiffies(hw_heartbeat_ms);
+ return min_t(long, last_heartbeat - jiffies, keepalive_interval);
+}
+
+static inline void watchdog_update_worker(struct watchdog_device *wdd)
+{
+ struct watchdog_core_data *wd_data = wdd->wd_data;
+
+ if (watchdog_need_worker(wdd)) {
+ long t = watchdog_next_keepalive(wdd);
+
+ if (t > 0)
+ mod_delayed_work(watchdog_wq, &wd_data->work, t);
+ } else {
+ cancel_delayed_work(&wd_data->work);
+ }
+}
+
+static int __watchdog_ping(struct watchdog_device *wdd)
+{
+ int err;
+
+ if (wdd->ops->ping)
+ err = wdd->ops->ping(wdd); /* ping the watchdog */
+ else
+ err = wdd->ops->start(wdd); /* restart watchdog */
+
+ watchdog_update_worker(wdd);
+
+ return err;
+}
+
/*
* watchdog_ping: ping the watchdog.
* @wdd: the watchdog device to ping
@@ -85,17 +159,28 @@ static struct watchdog_core_data *old_wd_data;
static int watchdog_ping(struct watchdog_device *wdd)
{
- int err;
+ struct watchdog_core_data *wd_data = wdd->wd_data;
if (!watchdog_active(wdd))
return 0;
- if (wdd->ops->ping)
- err = wdd->ops->ping(wdd); /* ping the watchdog */
- else
- err = wdd->ops->start(wdd); /* restart watchdog */
+ wd_data->last_keepalive = jiffies;
+ return __watchdog_ping(wdd);
+}
- return err;
+static void watchdog_ping_work(struct work_struct *work)
+{
+ struct watchdog_core_data *wd_data;
+ struct watchdog_device *wdd;
+
+ wd_data = container_of(to_delayed_work(work), struct watchdog_core_data,
+ work);
+
+ mutex_lock(&wd_data->lock);
+ wdd = wd_data->wdd;
+ if (wdd && watchdog_active(wdd))
+ __watchdog_ping(wdd);
+ mutex_unlock(&wd_data->lock);
}
/*
@@ -111,14 +196,20 @@ static int watchdog_ping(struct watchdog_device *wdd)
static int watchdog_start(struct watchdog_device *wdd)
{
+ struct watchdog_core_data *wd_data = wdd->wd_data;
+ unsigned long started_at;
int err;
if (watchdog_active(wdd))
return 0;
+ started_at = jiffies;
err = wdd->ops->start(wdd);
- if (err == 0)
+ if (err == 0) {
set_bit(WDOG_ACTIVE, &wdd->status);
+ wd_data->last_keepalive = started_at;
+ watchdog_update_worker(wdd);
+ }
return err;
}
@@ -137,6 +228,7 @@ static int watchdog_start(struct watchdog_device *wdd)
static int watchdog_stop(struct watchdog_device *wdd)
{
+ struct watchdog_core_data *wd_data = wdd->wd_data;
int err;
if (!watchdog_active(wdd))
@@ -149,8 +241,10 @@ static int watchdog_stop(struct watchdog_device *wdd)
}
err = wdd->ops->stop(wdd);
- if (err == 0)
+ if (err == 0) {
clear_bit(WDOG_ACTIVE, &wdd->status);
+ cancel_delayed_work(&wd_data->work);
+ }
return err;
}
@@ -196,6 +290,8 @@ static int watchdog_set_timeout(struct watchdog_device *wdd,
else
wdd->timeout = timeout;
+ watchdog_update_worker(wdd);
+
return err;
}
@@ -616,6 +712,8 @@ static int watchdog_release(struct inode *inode, struct file *file)
watchdog_ping(wdd);
}
+ cancel_delayed_work_sync(&wd_data->work);
+
/* make sure that /dev/watchdog can be re-opened */
clear_bit(_WDOG_DEV_OPEN, &wd_data->status);
@@ -665,6 +763,11 @@ static int watchdog_cdev_register(struct watchdog_device *wdd, dev_t devno)
wd_data->wdd = wdd;
wdd->wd_data = wd_data;
+ if (!watchdog_wq)
+ return -ENODEV;
+
+ INIT_DELAYED_WORK(&wd_data->work, watchdog_ping_work);
+
if (wdd->id == 0) {
old_wd_data = wd_data;
watchdog_miscdev.parent = wdd->parent;
@@ -722,6 +825,8 @@ static void watchdog_cdev_unregister(struct watchdog_device *wdd)
wdd->wd_data = NULL;
mutex_unlock(&wd_data->lock);
+ cancel_delayed_work_sync(&wd_data->work);
+
kref_put(&wd_data->kref, watchdog_core_data_release);
}
@@ -787,6 +892,13 @@ int __init watchdog_dev_init(void)
{
int err;
+ watchdog_wq = alloc_workqueue("watchdogd",
+ WQ_HIGHPRI | WQ_MEM_RECLAIM, 0);
+ if (!watchdog_wq) {
+ pr_err("Failed to create watchdog workqueue\n");
+ return -ENOMEM;
+ }
+
err = class_register(&watchdog_class);
if (err < 0) {
pr_err("couldn't register class\n");
@@ -813,4 +925,5 @@ void __exit watchdog_dev_exit(void)
{
unregister_chrdev_region(watchdog_devt, MAX_DOGS);
class_unregister(&watchdog_class);
+ destroy_workqueue(watchdog_wq);
}