|
@@ -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);
|
|
|
}
|