diff options
Diffstat (limited to 'drivers/watchdog')
-rw-r--r-- | drivers/watchdog/watchdog_dev.c | 129 |
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); } |