diff options
-rw-r--r-- | Documentation/watchdog/watchdog-kernel-api.txt | 22 | ||||
-rw-r--r-- | drivers/watchdog/watchdog_dev.c | 52 | ||||
-rw-r--r-- | include/linux/watchdog.h | 10 |
3 files changed, 64 insertions, 20 deletions
diff --git a/Documentation/watchdog/watchdog-kernel-api.txt b/Documentation/watchdog/watchdog-kernel-api.txt index 15a02595ade1..954134a5c4a4 100644 --- a/Documentation/watchdog/watchdog-kernel-api.txt +++ b/Documentation/watchdog/watchdog-kernel-api.txt @@ -137,10 +137,10 @@ are: * stop: with this routine the watchdog timer device is being stopped. The routine needs a pointer to the watchdog timer device structure as a parameter. It returns zero on success or a negative errno code for failure. - Some watchdog timer hardware can only be started and not be stopped. The - driver supporting this hardware needs to make sure that a start and stop - routine is being provided. This can be done by using a timer in the driver - that regularly sends a keepalive ping to the watchdog timer hardware. + Some watchdog timer hardware can only be started and not be stopped. + If a watchdog can not be stopped, the watchdog driver must set the + WDOG_HW_RUNNING flag in its stop function to inform the watchdog core that + the watchdog is still running. Not all watchdog timer hardware supports the same functionality. That's why all other routines/operations are optional. They only need to be provided if @@ -189,11 +189,19 @@ The 'ref' and 'unref' operations are no longer used and deprecated. The status bits should (preferably) be set with the set_bit and clear_bit alike bit-operations. The status bits that are defined are: * WDOG_ACTIVE: this status bit indicates whether or not a watchdog timer device - is active or not. When the watchdog is active after booting, then you should - set this status bit (Note: when you register the watchdog timer device with - this bit set, then opening /dev/watchdog will skip the start operation) + is active or not from user perspective. User space is expected to send + heartbeat requests to the driver while this flag is set. * WDOG_NO_WAY_OUT: this bit stores the nowayout setting for the watchdog. If this bit is set then the watchdog timer will not be able to stop. +* WDOG_HW_RUNNING: Set by the watchdog driver if the hardware watchdog is + running. The bit must be set if the watchdog timer hardware can not be + stopped. The bit may also be set if the watchdog timer is running after + booting, before the watchdog device is opened. If set, the watchdog + infrastructure will send keepalives to the watchdog hardware while + WDOG_ACTIVE is not set. + Note: when you register the watchdog timer device with this bit set, + then opening /dev/watchdog will skip the start operation but send a keepalive + request instead. To set the WDOG_NO_WAY_OUT status bit (before registering your watchdog timer device) you can either: diff --git a/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c index e668a9e8b648..5d3a9fa4856e 100644 --- a/drivers/watchdog/watchdog_dev.c +++ b/drivers/watchdog/watchdog_dev.c @@ -92,7 +92,8 @@ static inline bool watchdog_need_worker(struct watchdog_device *wdd) * requests. * - Userspace requests a longer timeout than the hardware can handle. */ - return watchdog_active(wdd) && hm && t > hm; + return hm && ((watchdog_active(wdd) && t > hm) || + (t && !watchdog_active(wdd) && watchdog_hw_running(wdd))); } static long watchdog_next_keepalive(struct watchdog_device *wdd) @@ -108,6 +109,9 @@ static long watchdog_next_keepalive(struct watchdog_device *wdd) hw_heartbeat_ms = min(timeout_ms, wdd->max_hw_heartbeat_ms); keepalive_interval = msecs_to_jiffies(hw_heartbeat_ms / 2); + if (!watchdog_active(wdd)) + return keepalive_interval; + /* * To ensure that the watchdog times out wdd->timeout seconds * after the most recent ping from userspace, the last @@ -161,7 +165,7 @@ static int watchdog_ping(struct watchdog_device *wdd) { struct watchdog_core_data *wd_data = wdd->wd_data; - if (!watchdog_active(wdd)) + if (!watchdog_active(wdd) && !watchdog_hw_running(wdd)) return 0; wd_data->last_keepalive = jiffies; @@ -178,7 +182,7 @@ static void watchdog_ping_work(struct work_struct *work) mutex_lock(&wd_data->lock); wdd = wd_data->wdd; - if (wdd && watchdog_active(wdd)) + if (wdd && (watchdog_active(wdd) || watchdog_hw_running(wdd))) __watchdog_ping(wdd); mutex_unlock(&wd_data->lock); } @@ -204,7 +208,10 @@ static int watchdog_start(struct watchdog_device *wdd) return 0; started_at = jiffies; - err = wdd->ops->start(wdd); + if (watchdog_hw_running(wdd) && wdd->ops->ping) + err = wdd->ops->ping(wdd); + else + err = wdd->ops->start(wdd); if (err == 0) { set_bit(WDOG_ACTIVE, &wdd->status); wd_data->last_keepalive = started_at; @@ -228,8 +235,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; + int err = 0; if (!watchdog_active(wdd)) return 0; @@ -243,7 +249,7 @@ static int watchdog_stop(struct watchdog_device *wdd) err = wdd->ops->stop(wdd); if (err == 0) { clear_bit(WDOG_ACTIVE, &wdd->status); - cancel_delayed_work(&wd_data->work); + watchdog_update_worker(wdd); } return err; @@ -641,7 +647,7 @@ static int watchdog_open(struct inode *inode, struct file *file) * If the /dev/watchdog device is open, we don't want the module * to be unloaded. */ - if (!try_module_get(wdd->ops->owner)) { + if (!watchdog_hw_running(wdd) && !try_module_get(wdd->ops->owner)) { err = -EBUSY; goto out_clear; } @@ -652,7 +658,8 @@ static int watchdog_open(struct inode *inode, struct file *file) file->private_data = wd_data; - kref_get(&wd_data->kref); + if (!watchdog_hw_running(wdd)) + kref_get(&wd_data->kref); /* dev/watchdog is a virtual (and thus non-seekable) filesystem */ return nonseekable_open(inode, file); @@ -713,15 +720,22 @@ static int watchdog_release(struct inode *inode, struct file *file) } cancel_delayed_work_sync(&wd_data->work); + watchdog_update_worker(wdd); /* make sure that /dev/watchdog can be re-opened */ clear_bit(_WDOG_DEV_OPEN, &wd_data->status); done: mutex_unlock(&wd_data->lock); - /* Allow the owner module to be unloaded again */ - module_put(wd_data->cdev.owner); - kref_put(&wd_data->kref, watchdog_core_data_release); + /* + * Allow the owner module to be unloaded again unless the watchdog + * is still running. If the watchdog is still running, it can not + * be stopped, and its driver must not be unloaded. + */ + if (!watchdog_hw_running(wdd)) { + module_put(wdd->ops->owner); + kref_put(&wd_data->kref, watchdog_core_data_release); + } return 0; } @@ -798,8 +812,20 @@ static int watchdog_cdev_register(struct watchdog_device *wdd, dev_t devno) old_wd_data = NULL; kref_put(&wd_data->kref, watchdog_core_data_release); } + return err; } - return err; + + /* + * If the watchdog is running, prevent its driver from being unloaded, + * and schedule an immediate ping. + */ + if (watchdog_hw_running(wdd)) { + __module_get(wdd->ops->owner); + kref_get(&wd_data->kref); + queue_delayed_work(watchdog_wq, &wd_data->work, 0); + } + + return 0; } /* diff --git a/include/linux/watchdog.h b/include/linux/watchdog.h index 8e82daecb7d3..e2f45549b243 100644 --- a/include/linux/watchdog.h +++ b/include/linux/watchdog.h @@ -105,6 +105,7 @@ struct watchdog_device { #define WDOG_ACTIVE 0 /* Is the watchdog running/active */ #define WDOG_NO_WAY_OUT 1 /* Is 'nowayout' feature set ? */ #define WDOG_STOP_ON_REBOOT 2 /* Should be stopped on reboot */ +#define WDOG_HW_RUNNING 3 /* True if HW watchdog running */ struct list_head deferred; }; @@ -117,6 +118,15 @@ static inline bool watchdog_active(struct watchdog_device *wdd) return test_bit(WDOG_ACTIVE, &wdd->status); } +/* + * Use the following function to check whether or not the hardware watchdog + * is running + */ +static inline bool watchdog_hw_running(struct watchdog_device *wdd) +{ + return test_bit(WDOG_HW_RUNNING, &wdd->status); +} + /* Use the following function to set the nowayout feature */ static inline void watchdog_set_nowayout(struct watchdog_device *wdd, bool nowayout) { |