diff options
| -rw-r--r-- | Documentation/watchdog/watchdog-kernel-api.txt | 19 | ||||
| -rw-r--r-- | drivers/watchdog/watchdog_dev.c | 129 | ||||
| -rw-r--r-- | include/linux/watchdog.h | 28 | 
3 files changed, 158 insertions, 18 deletions
| diff --git a/Documentation/watchdog/watchdog-kernel-api.txt b/Documentation/watchdog/watchdog-kernel-api.txt index dd8f912c0576..15a02595ade1 100644 --- a/Documentation/watchdog/watchdog-kernel-api.txt +++ b/Documentation/watchdog/watchdog-kernel-api.txt @@ -52,6 +52,7 @@ struct watchdog_device {  	unsigned int timeout;  	unsigned int min_timeout;  	unsigned int max_timeout; +	unsigned int max_hw_heartbeat_ms;  	struct notifier_block reboot_nb;  	struct notifier_block restart_nb;  	void *driver_data; @@ -73,8 +74,18 @@ It contains following fields:    additional information about the watchdog timer itself. (Like it's unique name)  * ops: a pointer to the list of watchdog operations that the watchdog supports.  * timeout: the watchdog timer's timeout value (in seconds). +  This is the time after which the system will reboot if user space does +  not send a heartbeat request if WDOG_ACTIVE is set.  * min_timeout: the watchdog timer's minimum timeout value (in seconds). -* max_timeout: the watchdog timer's maximum timeout value (in seconds). +  If set, the minimum configurable value for 'timeout'. +* max_timeout: the watchdog timer's maximum timeout value (in seconds), +  as seen from userspace. If set, the maximum configurable value for +  'timeout'. Not used if max_hw_heartbeat_ms is non-zero. +* max_hw_heartbeat_ms: Maximum hardware heartbeat, in milli-seconds. +  If set, the infrastructure will send heartbeats to the watchdog driver +  if 'timeout' is larger than max_hw_heartbeat_ms, unless WDOG_ACTIVE +  is set and userspace failed to send a heartbeat for at least 'timeout' +  seconds.  * reboot_nb: notifier block that is registered for reboot notifications, for    internal use only. If the driver calls watchdog_stop_on_reboot, watchdog core    will stop the watchdog on such notifications. @@ -153,7 +164,11 @@ they are supported. These optional routines/operations are:    and -EIO for "could not write value to the watchdog". On success this    routine should set the timeout value of the watchdog_device to the    achieved timeout value (which may be different from the requested one -  because the watchdog does not necessarily has a 1 second resolution). +  because the watchdog does not necessarily have a 1 second resolution). +  Drivers implementing max_hw_heartbeat_ms set the hardware watchdog heartbeat +  to the minimum of timeout and max_hw_heartbeat_ms. Those drivers set the +  timeout value of the watchdog_device either to the requested timeout value +  (if it is larger than max_hw_heartbeat_ms), or to the achieved timeout value.    (Note: the WDIOF_SETTIMEOUT needs to be set in the options field of the    watchdog's info structure).    If the watchdog driver does not have to perform any action but setting the 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);  } diff --git a/include/linux/watchdog.h b/include/linux/watchdog.h index 0b565f2ad242..8e82daecb7d3 100644 --- a/include/linux/watchdog.h +++ b/include/linux/watchdog.h @@ -10,8 +10,9 @@  #include <linux/bitops.h> -#include <linux/device.h>  #include <linux/cdev.h> +#include <linux/device.h> +#include <linux/kernel.h>  #include <linux/notifier.h>  #include <uapi/linux/watchdog.h> @@ -61,14 +62,19 @@ struct watchdog_ops {   * @bootstatus:	Status of the watchdog device at boot.   * @timeout:	The watchdog devices timeout value (in seconds).   * @min_timeout:The watchdog devices minimum timeout value (in seconds). - * @max_timeout:The watchdog devices maximum timeout value (in seconds). + * @max_timeout:The watchdog devices maximum timeout value (in seconds) + *		as configurable from user space. Only relevant if + *		max_hw_heartbeat_ms is not provided. + * @max_hw_heartbeat_ms: + *		Hardware limit for maximum timeout, in milli-seconds. + *		Replaces max_timeout if specified.   * @reboot_nb:	The notifier block to stop watchdog on reboot.   * @restart_nb:	The notifier block to register a restart function.   * @driver_data:Pointer to the drivers private data.   * @wd_data:	Pointer to watchdog core internal data.   * @status:	Field that contains the devices internal status bits. - * @deferred: entry in wtd_deferred_reg_list which is used to - *			   register early initialized watchdogs. + * @deferred:	Entry in wtd_deferred_reg_list which is used to + *		register early initialized watchdogs.   *   * The watchdog_device structure contains all information about a   * watchdog timer device. @@ -89,6 +95,7 @@ struct watchdog_device {  	unsigned int timeout;  	unsigned int min_timeout;  	unsigned int max_timeout; +	unsigned int max_hw_heartbeat_ms;  	struct notifier_block reboot_nb;  	struct notifier_block restart_nb;  	void *driver_data; @@ -128,13 +135,18 @@ static inline bool watchdog_timeout_invalid(struct watchdog_device *wdd, unsigne  {  	/*  	 * The timeout is invalid if +	 * - the requested value is larger than UINT_MAX / 1000 +	 *   (since internal calculations are done in milli-seconds), +	 * or  	 * - the requested value is smaller than the configured minimum timeout,  	 * or -	 * - a maximum timeout is configured, and the requested value is larger -	 *   than the maximum timeout. +	 * - a maximum hardware timeout is not configured, a maximum timeout +	 *   is configured, and the requested value is larger than the +	 *   configured maximum timeout.  	 */ -	return t < wdd->min_timeout || -		(wdd->max_timeout && t > wdd->max_timeout); +	return t > UINT_MAX / 1000 || t < wdd->min_timeout || +		(!wdd->max_hw_heartbeat_ms && wdd->max_timeout && +		 t > wdd->max_timeout);  }  /* Use the following functions to manipulate watchdog driver specific data */ |