Skip to contentSkip to conversation

watchdog: Extend kernel API and add early_timeout_sec feature

PATCH Superseded Timo Kokkonen 2 participants 14 messages Apr 10, 2015 – Apr 14, 2015

Subsystems (3)

v5 of 8 Superseded Latest is v8 →

lore
Share & tools
lore
#0 lore raw

v5 cover watchdog: Extend kernel API and add early_timeout_sec feature

The watchdog kernel API is quite limited. It has support for providing generic device handling, but it doesn't really know anything about the watchdog hardware or its constraints. The watchdog drivers come with a lot of diversity and their own set of quirks and constraints. Some of their limitations are not nice for the user space, so the drivers work around them with all sorts of ad hoc implementations.

One common pattern is to use kernel timers or work queues to allow longer timeout parameters than the actual hardware supports. To solve this problem, this patch set extends the kernel watchdog API with a few parameters that let the core know more about the watchdog HW and take care about the timeout extending.

The patch set also implements "early_timeout_sec" feature that is very common on many production systems where early kernel or user space crashes must lead to a device reset. Traditional watchdog handling does not allow this as the watchdog is stopped (fully or emulating stopped state with kernel timers) before user space opens it for the first time.

The changes are designed to be taken in use one driver at time. If the driver does not set the new parameters and call watchdog_init_params(), the watchdog behavior is exactly the same as before.

In principle this new API makes it possible for the user space to see every watchdog hardware to behave the same, at least in terms of watchdog timeouts. Once the API is in, it should be easier to move even more common behavior out of the driver code to the watchdog core and make the drivers simpler.

I'm not anticipating this patch set to go in as is, this is merely to help bringing up the discussion as there is a working patch set to look at. There are propably some corner cases that I didn't get right yet. Also, we could even remove the maximum timeout completely as the HW no longer limits the watchdog timeouts. This propably needs some thinking still and your commenting.

Please review and give feedback.

Patch revision history:

-v5: Re-think the approach to be fully generic. The early_timeout_sec
handling is no longer in the driver but in the watchdog core. As a result the core needed to gain knowledge about the watchdog hardware. Appropriate handling is added in the core. The side effect for this is that drivers using the new extensions can be simplified a lot and different kinds of watchdog hardware can be made to behave the same for the user space.

-v4: Binding documentation is now separated completely from the driver
patch. The documentation no longer makes any assumptions about how the actual implementation is made, it just describes the actual behavior the driver should implement in order to satisfy the requirement.

- v3: Rename the property to "early-timeout-sec" and use it as a timeout value that stops the timer in the atmel driver after the timeout expires. A watchdog.txt is also introduced for documenting the common watchdog properties, including now this one and "timeout-sec" property.

- v2: Rename the property to "enable-early-reset" as the behavior itself is not atmel specific. This way other drivers are free to implement same behavior with the same property name.

- v1: Propose property name "atmle,no-early-timer" for disabling the timer that keeps the atmel watchdog running until user space opens the device.

Timo Kokkonen (4):
watchdog: Extend kernel API to know about HW limitations
watchdog: Allow watchdog to reset device at early boot
devicetree: Document generic watchdog properties
watchdog: at91sam9_wdt: Convert to use new watchdog core extensions

 .../devicetree/bindings/watchdog/watchdog.txt      |  20 ++++
 drivers/watchdog/at91sam9_wdt.c                    |  58 +++--------
 drivers/watchdog/watchdog_core.c                   | 108 ++++++++++++++++++++-
 drivers/watchdog/watchdog_dev.c                    |  12 +++
 include/linux/watchdog.h                           |  24 +++++
 5 files changed, 173 insertions(+), 49 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/watchdog/watchdog.txt
Signature · 1 line
2.1.0
#1 lore raw

PATCH v5 1/4 watchdog: Extend kernel API to know about HW limitations

There is a great deal of diversity in the watchdog hardware found on different devices. Differen hardware have different contstraints on them, many of the constraints that are excessively difficult for the user space to satisfy.

One such constraint is the length of the timeout value, which in many cases can be just a few seconds. Drivers are creating ad hoc solutions with timers and workqueues to extend the timeout in order to give user space more time between updates. Looking at the drivers it is clear that this has resulted to a lot of duplicate code.

Add an extension to the watchdog kernel API that allows the driver to describe tis HW constraints to the watchdog code. A kernel worker in the core is then used to extend the watchdog timeout on behalf of the user space. This allows the drivers to be simplified as core takes over the timer extending.

---
 drivers/watchdog/watchdog_core.c | 87 ++++++++++++++++++++++++++++++++++++++--
 drivers/watchdog/watchdog_dev.c  | 12 ++++++
 include/linux/watchdog.h         | 23 +++++++++++
 3 files changed, 119 insertions(+), 3 deletions(-)
drivers/watchdog/watchdog_core.c +84 -3
index cec9b55..8e7d08d 100644
@@ -99,6 +99,75 @@ int watchdog_init_timeout(struct watchdog_device *wdd,
 EXPORT_SYMBOL_GPL(watchdog_init_timeout);
 
 /**
+ * watchdog_init_timeout() - initialize generic watchdog parameters
+ * @wdd: Watchdog device to operate
+ * @dev: Device that stores the device tree properties
+ *
+ * Initialize the generic timeout parameters. The driver needs to set
+ * hw_features bitmask from @wdd prior calling this function in order
+ * to ensure the core knows how to handle the HW.
+ *
+ * A zero is returned on success and -EINVAL for failure.
+ */
+int watchdog_init_params(struct watchdog_device *wdd, struct device *dev)
+{
+	int ret = 0;
+
+	ret = watchdog_init_timeout(wdd, wdd->timeout, dev);
+	if (ret < 0)
+		return ret;
+
+	/*
+	 * Max HW timeout needs to be set so that core knows when to
+	 * use a kernel worker to support longer watchdog timeouts
+	 */
+	if (!wdd->hw_max_timeout)
+		return -EINVAL;
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(watchdog_init_params);
+
+static void watchdog_worker(struct work_struct *work)
+{
+	struct watchdog_device *wdd = container_of(to_delayed_work(work),
+						struct watchdog_device, work);
+
+	if (time_after(jiffies, wdd->expires)) {
+		pr_crit("I will reset your machine !\n");
+		return;
+	}
+
+	if ((!watchdog_active(wdd) && !watchdog_is_stoppable(wdd)) ||
+		(watchdog_active(wdd) &&
+			wdd->hw_max_timeout < wdd->timeout * HZ)) {
+		if (wdd->ops->ping)
+			wdd->ops->ping(wdd);
+		else
+			wdd->ops->start(wdd);
+
+		schedule_delayed_work(&wdd->work,  wdd->hw_heartbeat);
+	}
+}
+
+static int prepare_watchdog(struct watchdog_device *wdd)
+{
+	/* Stop the watchdog now before user space opens the device */
+	if (wdd->hw_features & WDOG_HW_IS_STOPPABLE &&
+		wdd->hw_features & WDOG_HW_RUNNING_AT_BOOT) {
+		wdd->ops->stop(wdd);
+
+	} else if (!(wdd->hw_features & WDOG_HW_IS_STOPPABLE)) {
+		/*
+		 * Can't stop it, use a kernel timer to tick
+		 * it until it's open by user space
+		 */
+		schedule_delayed_work(&wdd->work, wdd->hw_heartbeat);
+	}
+	return 0;
+}
+
+/**
  * watchdog_register_device() - register a watchdog device
  * @wdd: watchdog device
  *
@@ -156,13 +225,24 @@ int watchdog_register_device(struct watchdog_device *wdd)
 	wdd->dev = device_create(watchdog_class, wdd->parent, devno,
 					NULL, "watchdog%d", wdd->id);
 	if (IS_ERR(wdd->dev)) {
-		watchdog_dev_unregister(wdd);
-		ida_simple_remove(&watchdog_ida, id);
 		ret = PTR_ERR(wdd->dev);
-		return ret;
+		goto dev_create_fail;
+	}
+
+	INIT_DELAYED_WORK(&wdd->work, watchdog_worker);
+
+	if (wdd->hw_max_timeout) {
+		ret = prepare_watchdog(wdd);
+		if (ret)
+			goto dev_create_fail;
 	}
 
 	return 0;
+
+dev_create_fail:
+	watchdog_dev_unregister(wdd);
+	ida_simple_remove(&watchdog_ida, id);
+	return ret;
 }
 EXPORT_SYMBOL_GPL(watchdog_register_device);
 
@@ -181,6 +261,7 @@ void watchdog_unregister_device(struct watchdog_device *wdd)
 	if (wdd == NULL)
 		return;
 
+	cancel_delayed_work_sync(&wdd->work);
 	devno = wdd->cdev.dev;
 	ret = watchdog_dev_unregister(wdd);
 	if (ret)
drivers/watchdog/watchdog_dev.c +12 -0
index 6aaefba..99eb363 100644
@@ -78,6 +78,12 @@ static int watchdog_ping(struct watchdog_device *wddev)
 	else
 		err = wddev->ops->start(wddev); /* restart watchdog */
 
+	if (wddev->hw_max_timeout &&
+		wddev->timeout * HZ > wddev->hw_max_timeout) {
+		wddev->expires = jiffies + wddev->timeout * HZ;
+		schedule_delayed_work(&wddev->work, wddev->hw_heartbeat);
+	}
+
 out_ping:
 	mutex_unlock(&wddev->lock);
 	return err;
@@ -110,6 +116,12 @@ static int watchdog_start(struct watchdog_device *wddev)
 	if (err == 0)
 		set_bit(WDOG_ACTIVE, &wddev->status);
 
+	if (wddev->hw_max_timeout &&
+		wddev->timeout * HZ > wddev->hw_max_timeout) {
+		wddev->expires = jiffies + wddev->timeout * HZ;
+		schedule_delayed_work(&wddev->work, wddev->hw_heartbeat);
+	}
+
 out_start:
 	mutex_unlock(&wddev->lock);
 	return err;
include/linux/watchdog.h +23 -0
index 395b70e..c16c921 100644
@@ -12,6 +12,7 @@
 #include <linux/bitops.h>
 #include <linux/device.h>
 #include <linux/cdev.h>
+#include <linux/workqueue.h>
 #include <uapi/linux/watchdog.h>
 
 struct watchdog_ops;
@@ -62,9 +63,13 @@ struct watchdog_ops {
  * @timeout:	The watchdog devices timeout value.
  * @min_timeout:The watchdog devices minimum timeout value.
  * @max_timeout:The watchdog devices maximum timeout value.
+ * @hw_max_timeout:The watchdog hardware maximum timeout value.
+ * @hw_margin:	Time interval between timer pings.
  * @driver-data:Pointer to the drivers private data.
  * @lock:	Lock for watchdog core internal use only.
+ * @work:	Worker used to provide longer timeouts than hardware supports.
  * @status:	Field that contains the devices internal status bits.
+ * @hw_features:Feature bits describing how the watchdog HW works.
  *
  * The watchdog_device structure contains all information about a
  * watchdog timer device.
@@ -86,8 +91,12 @@ struct watchdog_device {
 	unsigned int timeout;
 	unsigned int min_timeout;
 	unsigned int max_timeout;
+	unsigned int hw_max_timeout; /* in jiffies */
+	unsigned int hw_heartbeat; /* in jiffies */
+	unsigned long int expires; /* for soft timer */
 	void *driver_data;
 	struct mutex lock;
+	struct delayed_work work;
 	unsigned long status;
 /* Bit numbers for status flags */
 #define WDOG_ACTIVE		0	/* Is the watchdog running/active */
@@ -95,6 +104,14 @@ struct watchdog_device {
 #define WDOG_ALLOW_RELEASE	2	/* Did we receive the magic char ? */
 #define WDOG_NO_WAY_OUT		3	/* Is 'nowayout' feature set ? */
 #define WDOG_UNREGISTERED	4	/* Has the device been unregistered */
+
+/* Bits describing features supported by the HW */
+	unsigned long hw_features;
+
+/* Can the watchdog be stopped and started */
+#define WDOG_HW_IS_STOPPABLE		BIT(0)
+/* Is the watchdog already running when the driver starts up */
+#define WDOG_HW_RUNNING_AT_BOOT		BIT(1)
 };
 
 #define WATCHDOG_NOWAYOUT		IS_BUILTIN(CONFIG_WATCHDOG_NOWAYOUT)
@@ -120,6 +137,11 @@ static inline bool watchdog_timeout_invalid(struct watchdog_device *wdd, unsigne
 		(t < wdd->min_timeout || t > wdd->max_timeout));
 }
 
+static inline bool watchdog_is_stoppable(struct watchdog_device *wdd)
+{
+	return wdd->hw_features & WDOG_HW_IS_STOPPABLE;
+}
+
 /* Use the following functions to manipulate watchdog driver specific data */
 static inline void watchdog_set_drvdata(struct watchdog_device *wdd, void *data)
 {
@@ -134,6 +156,7 @@ static inline void *watchdog_get_drvdata(struct watchdog_device *wdd)
 /* drivers/watchdog/watchdog_core.c */
 extern int watchdog_init_timeout(struct watchdog_device *wdd,
 				  unsigned int timeout_parm, struct device *dev);
+int watchdog_init_params(struct watchdog_device *wdd, struct device *dev);
 extern int watchdog_register_device(struct watchdog_device *);
 extern void watchdog_unregister_device(struct watchdog_device *);
 
Signature · 1 line
2.1.0
#2 lore raw

PATCH v5 1/4 watchdog: Extend kernel API to know about HW limitations

Hi Timo,

There is trivial typo.

Quoted 29 lines · Timo Kokkonen

There is a great deal of diversity in the watchdog hardware found on different devices. Differen hardware have different contstraints on them, many of the constraints that are excessively difficult for the user space to satisfy.

One such constraint is the length of the timeout value, which in many cases can be just a few seconds. Drivers are creating ad hoc solutions with timers and workqueues to extend the timeout in order to give user space more time between updates. Looking at the drivers it is clear that this has resulted to a lot of duplicate code.

Add an extension to the watchdog kernel API that allows the driver to describe tis HW constraints to the watchdog code. A kernel worker in the core is then used to extend the watchdog timeout on behalf of the user space. This allows the drivers to be simplified as core takes over the timer extending.

---
  drivers/watchdog/watchdog_core.c | 87 ++++++++++++++++++++++++++++++++++++++--
  drivers/watchdog/watchdog_dev.c  | 12 ++++++
  include/linux/watchdog.h         | 23 +++++++++++
  3 files changed, 119 insertions(+), 3 deletions(-)
drivers/watchdog/watchdog_core.c +0 -0
index cec9b55..8e7d08d 100644

drivers/watchdog/watchdog_core.c int watchdog_init_timeout(struct watchdog_device *wdd, · line 102

Quoted 4 lines
drivers/watchdog/watchdog_core.c +1 -0
@@ -99,6 +99,75 @@ int watchdog_init_timeout(struct watchdog_device *wdd,
  EXPORT_SYMBOL_GPL(watchdog_init_timeout);
 
  /**
+ * watchdog_init_timeout() - initialize generic watchdog parameters

s/watchdog_init_timeout/watchdog_init_params/ ?

Quoted 150 lines
drivers/watchdog/watchdog_core.c +83 -3
+ * @wdd: Watchdog device to operate
+ * @dev: Device that stores the device tree properties
+ *
+ * Initialize the generic timeout parameters. The driver needs to set
+ * hw_features bitmask from @wdd prior calling this function in order
+ * to ensure the core knows how to handle the HW.
+ *
+ * A zero is returned on success and -EINVAL for failure.
+ */
+int watchdog_init_params(struct watchdog_device *wdd, struct device *dev)
+{
+	int ret = 0;
+
+	ret = watchdog_init_timeout(wdd, wdd->timeout, dev);
+	if (ret < 0)
+		return ret;
+
+	/*
+	 * Max HW timeout needs to be set so that core knows when to
+	 * use a kernel worker to support longer watchdog timeouts
+	 */
+	if (!wdd->hw_max_timeout)
+		return -EINVAL;
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(watchdog_init_params);
+
+static void watchdog_worker(struct work_struct *work)
+{
+	struct watchdog_device *wdd = container_of(to_delayed_work(work),
+						struct watchdog_device, work);
+
+	if (time_after(jiffies, wdd->expires)) {
+		pr_crit("I will reset your machine !\n");
+		return;
+	}
+
+	if ((!watchdog_active(wdd) && !watchdog_is_stoppable(wdd)) ||
+		(watchdog_active(wdd) &&
+			wdd->hw_max_timeout < wdd->timeout * HZ)) {
+		if (wdd->ops->ping)
+			wdd->ops->ping(wdd);
+		else
+			wdd->ops->start(wdd);
+
+		schedule_delayed_work(&wdd->work,  wdd->hw_heartbeat);
+	}
+}
+
+static int prepare_watchdog(struct watchdog_device *wdd)
+{
+	/* Stop the watchdog now before user space opens the device */
+	if (wdd->hw_features & WDOG_HW_IS_STOPPABLE &&
+		wdd->hw_features & WDOG_HW_RUNNING_AT_BOOT) {
+		wdd->ops->stop(wdd);
+
+	} else if (!(wdd->hw_features & WDOG_HW_IS_STOPPABLE)) {
+		/*
+		 * Can't stop it, use a kernel timer to tick
+		 * it until it's open by user space
+		 */
+		schedule_delayed_work(&wdd->work, wdd->hw_heartbeat);
+	}
+	return 0;
+}
+
+/**
   * watchdog_register_device() - register a watchdog device
   * @wdd: watchdog device
   *
@@ -156,13 +225,24 @@ int watchdog_register_device(struct watchdog_device *wdd)
  	wdd->dev = device_create(watchdog_class, wdd->parent, devno,
  					NULL, "watchdog%d", wdd->id);
  	if (IS_ERR(wdd->dev)) {
-		watchdog_dev_unregister(wdd);
-		ida_simple_remove(&watchdog_ida, id);
  		ret = PTR_ERR(wdd->dev);
-		return ret;
+		goto dev_create_fail;
+	}
+
+	INIT_DELAYED_WORK(&wdd->work, watchdog_worker);
+
+	if (wdd->hw_max_timeout) {
+		ret = prepare_watchdog(wdd);
+		if (ret)
+			goto dev_create_fail;
  	}
 
  	return 0;
+
+dev_create_fail:
+	watchdog_dev_unregister(wdd);
+	ida_simple_remove(&watchdog_ida, id);
+	return ret;
  }
  EXPORT_SYMBOL_GPL(watchdog_register_device);
 
@@ -181,6 +261,7 @@ void watchdog_unregister_device(struct watchdog_device *wdd)
  	if (wdd == NULL)
  		return;
 
+	cancel_delayed_work_sync(&wdd->work);
  	devno = wdd->cdev.dev;
  	ret = watchdog_dev_unregister(wdd);
  	if (ret)
drivers/watchdog/watchdog_dev.c +12 -0
index 6aaefba..99eb363 100644
@@ -78,6 +78,12 @@ static int watchdog_ping(struct watchdog_device *wddev)
  	else
  		err = wddev->ops->start(wddev); /* restart watchdog */
 
+	if (wddev->hw_max_timeout &&
+		wddev->timeout * HZ > wddev->hw_max_timeout) {
+		wddev->expires = jiffies + wddev->timeout * HZ;
+		schedule_delayed_work(&wddev->work, wddev->hw_heartbeat);
+	}
+
  out_ping:
  	mutex_unlock(&wddev->lock);
  	return err;
@@ -110,6 +116,12 @@ static int watchdog_start(struct watchdog_device *wddev)
  	if (err == 0)
  		set_bit(WDOG_ACTIVE, &wddev->status);
 
+	if (wddev->hw_max_timeout &&
+		wddev->timeout * HZ > wddev->hw_max_timeout) {
+		wddev->expires = jiffies + wddev->timeout * HZ;
+		schedule_delayed_work(&wddev->work, wddev->hw_heartbeat);
+	}
+
  out_start:
  	mutex_unlock(&wddev->lock);
  	return err;
include/linux/watchdog.h +1 -0
index 395b70e..c16c921 100644
@@ -12,6 +12,7 @@
  #include <linux/bitops.h>
  #include <linux/device.h>
  #include <linux/cdev.h>
+#include <linux/workqueue.h>
  #include <uapi/linux/watchdog.h>
 
  struct watchdog_ops;

include/linux/watchdog.h struct watchdog_ops { · line 67

Quoted 5 lines
include/linux/watchdog.h +2 -0
@@ -62,9 +63,13 @@ struct watchdog_ops {
   * @timeout:	The watchdog devices timeout value.
   * @min_timeout:The watchdog devices minimum timeout value.
   * @max_timeout:The watchdog devices maximum timeout value.
+ * @hw_max_timeout:The watchdog hardware maximum timeout value.
+ * @hw_margin:	Time interval between timer pings.

s/hw_margin/hw_heartbeat/ ?

Quoted 49 lines
include/linux/watchdog.h +19 -0
   * @driver-data:Pointer to the drivers private data.
   * @lock:	Lock for watchdog core internal use only.
+ * @work:	Worker used to provide longer timeouts than hardware supports.
   * @status:	Field that contains the devices internal status bits.
+ * @hw_features:Feature bits describing how the watchdog HW works.
   *
   * The watchdog_device structure contains all information about a
   * watchdog timer device.
@@ -86,8 +91,12 @@ struct watchdog_device {
  	unsigned int timeout;
  	unsigned int min_timeout;
  	unsigned int max_timeout;
+	unsigned int hw_max_timeout; /* in jiffies */
+	unsigned int hw_heartbeat; /* in jiffies */
+	unsigned long int expires; /* for soft timer */
  	void *driver_data;
  	struct mutex lock;
+	struct delayed_work work;
  	unsigned long status;
  /* Bit numbers for status flags */
  #define WDOG_ACTIVE		0	/* Is the watchdog running/active */
@@ -95,6 +104,14 @@ struct watchdog_device {
  #define WDOG_ALLOW_RELEASE	2	/* Did we receive the magic char ? */
  #define WDOG_NO_WAY_OUT		3	/* Is 'nowayout' feature set ? */
  #define WDOG_UNREGISTERED	4	/* Has the device been unregistered */
+
+/* Bits describing features supported by the HW */
+	unsigned long hw_features;
+
+/* Can the watchdog be stopped and started */
+#define WDOG_HW_IS_STOPPABLE		BIT(0)
+/* Is the watchdog already running when the driver starts up */
+#define WDOG_HW_RUNNING_AT_BOOT		BIT(1)
  };
 
  #define WATCHDOG_NOWAYOUT		IS_BUILTIN(CONFIG_WATCHDOG_NOWAYOUT)
@@ -120,6 +137,11 @@ static inline bool watchdog_timeout_invalid(struct watchdog_device *wdd, unsigne
  		(t < wdd->min_timeout || t > wdd->max_timeout));
  }
 
+static inline bool watchdog_is_stoppable(struct watchdog_device *wdd)
+{
+	return wdd->hw_features & WDOG_HW_IS_STOPPABLE;
+}
+
  /* Use the following functions to manipulate watchdog driver specific data */
  static inline void watchdog_set_drvdata(struct watchdog_device *wdd, void *data)
  {

include/linux/watchdog.h static inline void *watchdog_get_drvdata(struct watchdog_device *wdd) · line 162

Quoted 7 lines
include/linux/watchdog.h +1 -0
@@ -134,6 +156,7 @@ static inline void *watchdog_get_drvdata(struct watchdog_device *wdd)
  /* drivers/watchdog/watchdog_core.c */
  extern int watchdog_init_timeout(struct watchdog_device *wdd,
  				  unsigned int timeout_parm, struct device *dev);
+int watchdog_init_params(struct watchdog_device *wdd, struct device *dev);
  extern int watchdog_register_device(struct watchdog_device *);
  extern void watchdog_unregister_device(struct watchdog_device *);
 

Best Regards,
Wenyou Yang

#3 lore raw

PATCH v5 1/4 watchdog: Extend kernel API to know about HW limitations

In reply to Timo Kokkonen

Hi Timo,

If the /dev/watchdog device file doesn't open by the user space application, the system will reboot by the watchdog.

I think it is missing update of the wdd->expires value. added it as following.

Quoted 70 lines · Timo Kokkonen

There is a great deal of diversity in the watchdog hardware found on different devices. Differen hardware have different contstraints on them, many of the constraints that are excessively difficult for the user space to satisfy.

One such constraint is the length of the timeout value, which in many cases can be just a few seconds. Drivers are creating ad hoc solutions with timers and workqueues to extend the timeout in order to give user space more time between updates. Looking at the drivers it is clear that this has resulted to a lot of duplicate code.

Add an extension to the watchdog kernel API that allows the driver to describe tis HW constraints to the watchdog code. A kernel worker in the core is then used to extend the watchdog timeout on behalf of the user space. This allows the drivers to be simplified as core takes over the timer extending.

---
  drivers/watchdog/watchdog_core.c | 87 ++++++++++++++++++++++++++++++++++++++--
  drivers/watchdog/watchdog_dev.c  | 12 ++++++
  include/linux/watchdog.h         | 23 +++++++++++
  3 files changed, 119 insertions(+), 3 deletions(-)
drivers/watchdog/watchdog_core.c +38 -0
index cec9b55..8e7d08d 100644
@@ -99,6 +99,75 @@ int watchdog_init_timeout(struct watchdog_device *wdd,
  EXPORT_SYMBOL_GPL(watchdog_init_timeout);
 
  /**
+ * watchdog_init_timeout() - initialize generic watchdog parameters
+ * @wdd: Watchdog device to operate
+ * @dev: Device that stores the device tree properties
+ *
+ * Initialize the generic timeout parameters. The driver needs to set
+ * hw_features bitmask from @wdd prior calling this function in order
+ * to ensure the core knows how to handle the HW.
+ *
+ * A zero is returned on success and -EINVAL for failure.
+ */
+int watchdog_init_params(struct watchdog_device *wdd, struct device *dev)
+{
+	int ret = 0;
+
+	ret = watchdog_init_timeout(wdd, wdd->timeout, dev);
+	if (ret < 0)
+		return ret;
+
+	/*
+	 * Max HW timeout needs to be set so that core knows when to
+	 * use a kernel worker to support longer watchdog timeouts
+	 */
+	if (!wdd->hw_max_timeout)
+		return -EINVAL;
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(watchdog_init_params);
+
+static void watchdog_worker(struct work_struct *work)
+{
+	struct watchdog_device *wdd = container_of(to_delayed_work(work),
+						struct watchdog_device, work);
+
+	if (time_after(jiffies, wdd->expires)) {
+		pr_crit("I will reset your machine !\n");
+		return;
+	}

drivers/watchdog/watchdog_core.c int watchdog_init_timeout(struct watchdog_device *wdd, · line 149

Quoted 10 lines
drivers/watchdog/watchdog_core.c +10 -0
+
+	if ((!watchdog_active(wdd) && !watchdog_is_stoppable(wdd)) ||
+		(watchdog_active(wdd) &&
+			wdd->hw_max_timeout < wdd->timeout * HZ)) {
+		if (wdd->ops->ping)
+			wdd->ops->ping(wdd);
+		else
+			wdd->ops->start(wdd);
+
+		schedule_delayed_work(&wdd->work,  wdd->hw_heartbeat);
    I think you missed the update of wdd->expires, as following.
                                    wdd->expires = jiffies + 

wdd->timeout * HZ;

Quoted 7 lines
drivers/watchdog/watchdog_core.c +7 -0
+	}
+}
+
+static int prepare_watchdog(struct watchdog_device *wdd)
+{
+	/* Stop the watchdog now before user space opens the device */
+	if (wdd->hw_features & WDOG_HW_IS_STOPPABLE &&

drivers/watchdog/watchdog_core.c int watchdog_init_timeout(struct watchdog_device *wdd, · line 166

Quoted 10 lines
drivers/watchdog/watchdog_core.c +10 -0
+		wdd->hw_features & WDOG_HW_RUNNING_AT_BOOT) {
+		wdd->ops->stop(wdd);
+
+	} else if (!(wdd->hw_features & WDOG_HW_IS_STOPPABLE)) {
+		/*
+		 * Can't stop it, use a kernel timer to tick
+		 * it until it's open by user space
+		 */
+		schedule_delayed_work(&wdd->work, wdd->hw_heartbeat);
+	}
      ditto
                     wdd->expires = jiffies + wdd->timeout * HZ;
Quoted 140 lines
drivers/watchdog/watchdog_core.c +19 -3
+	return 0;
+}
+
+/**
   * watchdog_register_device() - register a watchdog device
   * @wdd: watchdog device
   *
@@ -156,13 +225,24 @@ int watchdog_register_device(struct watchdog_device *wdd)
  	wdd->dev = device_create(watchdog_class, wdd->parent, devno,
  					NULL, "watchdog%d", wdd->id);
  	if (IS_ERR(wdd->dev)) {
-		watchdog_dev_unregister(wdd);
-		ida_simple_remove(&watchdog_ida, id);
  		ret = PTR_ERR(wdd->dev);
-		return ret;
+		goto dev_create_fail;
+	}
+
+	INIT_DELAYED_WORK(&wdd->work, watchdog_worker);
+
+	if (wdd->hw_max_timeout) {
+		ret = prepare_watchdog(wdd);
+		if (ret)
+			goto dev_create_fail;
  	}
 
  	return 0;
+
+dev_create_fail:
+	watchdog_dev_unregister(wdd);
+	ida_simple_remove(&watchdog_ida, id);
+	return ret;
  }
  EXPORT_SYMBOL_GPL(watchdog_register_device);
 
@@ -181,6 +261,7 @@ void watchdog_unregister_device(struct watchdog_device *wdd)
  	if (wdd == NULL)
  		return;
 
+	cancel_delayed_work_sync(&wdd->work);
  	devno = wdd->cdev.dev;
  	ret = watchdog_dev_unregister(wdd);
  	if (ret)
drivers/watchdog/watchdog_dev.c +12 -0
index 6aaefba..99eb363 100644
@@ -78,6 +78,12 @@ static int watchdog_ping(struct watchdog_device *wddev)
  	else
  		err = wddev->ops->start(wddev); /* restart watchdog */
 
+	if (wddev->hw_max_timeout &&
+		wddev->timeout * HZ > wddev->hw_max_timeout) {
+		wddev->expires = jiffies + wddev->timeout * HZ;
+		schedule_delayed_work(&wddev->work, wddev->hw_heartbeat);
+	}
+
  out_ping:
  	mutex_unlock(&wddev->lock);
  	return err;
@@ -110,6 +116,12 @@ static int watchdog_start(struct watchdog_device *wddev)
  	if (err == 0)
  		set_bit(WDOG_ACTIVE, &wddev->status);
 
+	if (wddev->hw_max_timeout &&
+		wddev->timeout * HZ > wddev->hw_max_timeout) {
+		wddev->expires = jiffies + wddev->timeout * HZ;
+		schedule_delayed_work(&wddev->work, wddev->hw_heartbeat);
+	}
+
  out_start:
  	mutex_unlock(&wddev->lock);
  	return err;
include/linux/watchdog.h +22 -0
index 395b70e..c16c921 100644
@@ -12,6 +12,7 @@
  #include <linux/bitops.h>
  #include <linux/device.h>
  #include <linux/cdev.h>
+#include <linux/workqueue.h>
  #include <uapi/linux/watchdog.h>
 
  struct watchdog_ops;
@@ -62,9 +63,13 @@ struct watchdog_ops {
   * @timeout:	The watchdog devices timeout value.
   * @min_timeout:The watchdog devices minimum timeout value.
   * @max_timeout:The watchdog devices maximum timeout value.
+ * @hw_max_timeout:The watchdog hardware maximum timeout value.
+ * @hw_margin:	Time interval between timer pings.
   * @driver-data:Pointer to the drivers private data.
   * @lock:	Lock for watchdog core internal use only.
+ * @work:	Worker used to provide longer timeouts than hardware supports.
   * @status:	Field that contains the devices internal status bits.
+ * @hw_features:Feature bits describing how the watchdog HW works.
   *
   * The watchdog_device structure contains all information about a
   * watchdog timer device.
@@ -86,8 +91,12 @@ struct watchdog_device {
  	unsigned int timeout;
  	unsigned int min_timeout;
  	unsigned int max_timeout;
+	unsigned int hw_max_timeout; /* in jiffies */
+	unsigned int hw_heartbeat; /* in jiffies */
+	unsigned long int expires; /* for soft timer */
  	void *driver_data;
  	struct mutex lock;
+	struct delayed_work work;
  	unsigned long status;
  /* Bit numbers for status flags */
  #define WDOG_ACTIVE		0	/* Is the watchdog running/active */
@@ -95,6 +104,14 @@ struct watchdog_device {
  #define WDOG_ALLOW_RELEASE	2	/* Did we receive the magic char ? */
  #define WDOG_NO_WAY_OUT		3	/* Is 'nowayout' feature set ? */
  #define WDOG_UNREGISTERED	4	/* Has the device been unregistered */
+
+/* Bits describing features supported by the HW */
+	unsigned long hw_features;
+
+/* Can the watchdog be stopped and started */
+#define WDOG_HW_IS_STOPPABLE		BIT(0)
+/* Is the watchdog already running when the driver starts up */
+#define WDOG_HW_RUNNING_AT_BOOT		BIT(1)
  };
 
  #define WATCHDOG_NOWAYOUT		IS_BUILTIN(CONFIG_WATCHDOG_NOWAYOUT)
@@ -120,6 +137,11 @@ static inline bool watchdog_timeout_invalid(struct watchdog_device *wdd, unsigne
  		(t < wdd->min_timeout || t > wdd->max_timeout));
  }
 
+static inline bool watchdog_is_stoppable(struct watchdog_device *wdd)
+{
+	return wdd->hw_features & WDOG_HW_IS_STOPPABLE;
+}
+
  /* Use the following functions to manipulate watchdog driver specific data */
  static inline void watchdog_set_drvdata(struct watchdog_device *wdd, void *data)
  {

include/linux/watchdog.h static inline void *watchdog_get_drvdata(struct watchdog_device *wdd) · line 162

Quoted 7 lines
include/linux/watchdog.h +1 -0
@@ -134,6 +156,7 @@ static inline void *watchdog_get_drvdata(struct watchdog_device *wdd)
  /* drivers/watchdog/watchdog_core.c */
  extern int watchdog_init_timeout(struct watchdog_device *wdd,
  				  unsigned int timeout_parm, struct device *dev);
+int watchdog_init_params(struct watchdog_device *wdd, struct device *dev);
  extern int watchdog_register_device(struct watchdog_device *);
  extern void watchdog_unregister_device(struct watchdog_device *);
 

Best Regards,
Wenyou Yang

#4 lore raw

PATCH v5 1/4 watchdog: Extend kernel API to know about HW limitations

Quoted 5 lines · Wenyou

Hi Timo,

If the /dev/watchdog device file doesn't open by the user space application, the system will reboot by the watchdog.

Thank you for pointing out the bug. When the struct watchdog_device is initialized, the expires variable is zero. Jiffies however is negative on boot up, so time_after() comparison with zero returns false and the worker keeps the watchdog alive for some minutes, until jiffies become positive. Then it will stop updating the HW as the expires value has become expired.

Quoted 2 lines

I think it is missing update of the wdd->expires value. added it as following.

Actually, the expires value stores the user space expiration value. It should only be updated when user space pings the watchdog. If we increment the expires value in the worker thread, the watchdog will never expire even if user space stops pinging it and keep it open.

So the proper fix is to have it like this (in watchdog_worker function):

	if (time_after(jiffies, wdd->expires) && watchdog_active(wdd)) {
		pr_crit("I will reset your machine !\n");
		return;
	}

Also we need this change to re-start the worker when watchdog is properly stopped (in watchdog_dev.c):

@@ -157,9 +159,15 @@ static int watchdog_stop(struct watchdog_device *wddev)

                 goto out_stop;
         }
-       err = wddev->ops->stop(wddev);
-       if (err == 0)
+       if (!wddev->hw_max_timeout || watchdog_is_stoppable(wddev)) {

+ err = wddev->ops->stop(wddev);
+ if (err == 0)
+ clear_bit(WDOG_ACTIVE, &wddev->status);
+ } else {
+ /* Unstoppable watchdogs need the worker to keep them
alive */

                 clear_bit(WDOG_ACTIVE, &wddev->status);

+ schedule_delayed_work(&wddev->work, wddev->hw_heartbeat);
+ }

  out_stop:
         mutex_unlock(&wddev->lock);

After these two modifications the watchdog is being pinged indefinitely until the user space opens the device and the worker is re-started if watchdog daemon exits after writing the magic character. I will update these change to the next version of the patch.

Thanks again!

-Timo

Quoted 259 lines · Timo Kokkonen
Quoted 72 lines

There is a great deal of diversity in the watchdog hardware found on different devices. Differen hardware have different contstraints on them, many of the constraints that are excessively difficult for the user space to satisfy.

One such constraint is the length of the timeout value, which in many cases can be just a few seconds. Drivers are creating ad hoc solutions with timers and workqueues to extend the timeout in order to give user space more time between updates. Looking at the drivers it is clear that this has resulted to a lot of duplicate code.

Add an extension to the watchdog kernel API that allows the driver to describe tis HW constraints to the watchdog code. A kernel worker in the core is then used to extend the watchdog timeout on behalf of the user space. This allows the drivers to be simplified as core takes over the timer extending.

---
  drivers/watchdog/watchdog_core.c | 87
++++++++++++++++++++++++++++++++++++++--
  drivers/watchdog/watchdog_dev.c  | 12 ++++++
  include/linux/watchdog.h         | 23 +++++++++++
  3 files changed, 119 insertions(+), 3 deletions(-)
drivers/watchdog/watchdog_core.c +38 -0
index cec9b55..8e7d08d 100644
@@ -99,6 +99,75 @@ int watchdog_init_timeout(struct watchdog_device *wdd,
  EXPORT_SYMBOL_GPL(watchdog_init_timeout);
  /**
+ * watchdog_init_timeout() - initialize generic watchdog parameters
+ * @wdd: Watchdog device to operate
+ * @dev: Device that stores the device tree properties
+ *
+ * Initialize the generic timeout parameters. The driver needs to set
+ * hw_features bitmask from @wdd prior calling this function in order
+ * to ensure the core knows how to handle the HW.
+ *
+ * A zero is returned on success and -EINVAL for failure.
+ */
+int watchdog_init_params(struct watchdog_device *wdd, struct device
 *dev)
+{
+    int ret = 0;
+
+    ret = watchdog_init_timeout(wdd, wdd->timeout, dev);
+    if (ret < 0)
+        return ret;
+
+    /*
+     * Max HW timeout needs to be set so that core knows when to
+     * use a kernel worker to support longer watchdog timeouts
+     */
+    if (!wdd->hw_max_timeout)
+        return -EINVAL;
+
+    return ret;
+}
+EXPORT_SYMBOL_GPL(watchdog_init_params);
+
+static void watchdog_worker(struct work_struct *work)
+{
+    struct watchdog_device *wdd = container_of(to_delayed_work(work),
+                        struct watchdog_device, work);
+
+    if (time_after(jiffies, wdd->expires)) {
+        pr_crit("I will reset your machine !\n");
+        return;
+    }

drivers/watchdog/watchdog_core.c int watchdog_init_timeout(struct watchdog_device *wdd, · line 149

Quoted 10 lines
drivers/watchdog/watchdog_core.c +10 -0
+
+    if ((!watchdog_active(wdd) && !watchdog_is_stoppable(wdd)) ||
+        (watchdog_active(wdd) &&
+            wdd->hw_max_timeout < wdd->timeout * HZ)) {
+        if (wdd->ops->ping)
+            wdd->ops->ping(wdd);
+        else
+            wdd->ops->start(wdd);
+
+        schedule_delayed_work(&wdd->work,  wdd->hw_heartbeat);
    I think you missed the update of wdd->expires, as following.
                                    wdd->expires = jiffies +

wdd->timeout * HZ;

Quoted 7 lines
drivers/watchdog/watchdog_core.c +7 -0
+    }
+}
+
+static int prepare_watchdog(struct watchdog_device *wdd)
+{
+    /* Stop the watchdog now before user space opens the device */
+    if (wdd->hw_features & WDOG_HW_IS_STOPPABLE &&

drivers/watchdog/watchdog_core.c int watchdog_init_timeout(struct watchdog_device *wdd, · line 166

Quoted 10 lines
drivers/watchdog/watchdog_core.c +10 -0
+        wdd->hw_features & WDOG_HW_RUNNING_AT_BOOT) {
+        wdd->ops->stop(wdd);
+
+    } else if (!(wdd->hw_features & WDOG_HW_IS_STOPPABLE)) {
+        /*
+         * Can't stop it, use a kernel timer to tick
+         * it until it's open by user space
+         */
+        schedule_delayed_work(&wdd->work, wdd->hw_heartbeat);
+    }
      ditto
                     wdd->expires = jiffies + wdd->timeout * HZ;
Quoted 150 lines
drivers/watchdog/watchdog_core.c +19 -3
+    return 0;
+}
+
+/**
   * watchdog_register_device() - register a watchdog device
   * @wdd: watchdog device
   *
@@ -156,13 +225,24 @@ int watchdog_register_device(struct
 watchdog_device *wdd)
      wdd->dev = device_create(watchdog_class, wdd->parent, devno,
                      NULL, "watchdog%d", wdd->id);
      if (IS_ERR(wdd->dev)) {
-        watchdog_dev_unregister(wdd);
-        ida_simple_remove(&watchdog_ida, id);
          ret = PTR_ERR(wdd->dev);
-        return ret;
+        goto dev_create_fail;
+    }
+
+    INIT_DELAYED_WORK(&wdd->work, watchdog_worker);
+
+    if (wdd->hw_max_timeout) {
+        ret = prepare_watchdog(wdd);
+        if (ret)
+            goto dev_create_fail;
      }
      return 0;
+
+dev_create_fail:
+    watchdog_dev_unregister(wdd);
+    ida_simple_remove(&watchdog_ida, id);
+    return ret;
  }
  EXPORT_SYMBOL_GPL(watchdog_register_device);
@@ -181,6 +261,7 @@ void watchdog_unregister_device(struct
 watchdog_device *wdd)
      if (wdd == NULL)
          return;
+    cancel_delayed_work_sync(&wdd->work);
      devno = wdd->cdev.dev;
      ret = watchdog_dev_unregister(wdd);
      if (ret)
drivers/watchdog/watchdog_dev.c +12 -0
index 6aaefba..99eb363 100644
@@ -78,6 +78,12 @@ static int watchdog_ping(struct watchdog_device
 *wddev)
      else
          err = wddev->ops->start(wddev); /* restart watchdog */
+    if (wddev->hw_max_timeout &&
+        wddev->timeout * HZ > wddev->hw_max_timeout) {
+        wddev->expires = jiffies + wddev->timeout * HZ;
+        schedule_delayed_work(&wddev->work, wddev->hw_heartbeat);
+    }
+
  out_ping:
      mutex_unlock(&wddev->lock);
      return err;
@@ -110,6 +116,12 @@ static int watchdog_start(struct watchdog_device
 *wddev)
      if (err == 0)
          set_bit(WDOG_ACTIVE, &wddev->status);
+    if (wddev->hw_max_timeout &&
+        wddev->timeout * HZ > wddev->hw_max_timeout) {
+        wddev->expires = jiffies + wddev->timeout * HZ;
+        schedule_delayed_work(&wddev->work, wddev->hw_heartbeat);
+    }
+
  out_start:
      mutex_unlock(&wddev->lock);
      return err;
include/linux/watchdog.h +4 -0
index 395b70e..c16c921 100644
@@ -12,6 +12,7 @@
  #include <linux/bitops.h>
  #include <linux/device.h>
  #include <linux/cdev.h>
+#include <linux/workqueue.h>
  #include <uapi/linux/watchdog.h>
  struct watchdog_ops;
@@ -62,9 +63,13 @@ struct watchdog_ops {
   * @timeout:    The watchdog devices timeout value.
   * @min_timeout:The watchdog devices minimum timeout value.
   * @max_timeout:The watchdog devices maximum timeout value.
+ * @hw_max_timeout:The watchdog hardware maximum timeout value.
+ * @hw_margin:    Time interval between timer pings.
   * @driver-data:Pointer to the drivers private data.
   * @lock:    Lock for watchdog core internal use only.
+ * @work:    Worker used to provide longer timeouts than hardware

supports.
* @status: Field that contains the devices internal status bits.
+ * @hw_features:Feature bits describing how the watchdog HW works. *
* The watchdog_device structure contains all information about a
* watchdog timer device.

include/linux/watchdog.h +4 -0
@@ -86,8 +91,12 @@ struct watchdog_device {
      unsigned int timeout;
      unsigned int min_timeout;
      unsigned int max_timeout;
+    unsigned int hw_max_timeout; /* in jiffies */
+    unsigned int hw_heartbeat; /* in jiffies */
+    unsigned long int expires; /* for soft timer */
      void *driver_data;
      struct mutex lock;
+    struct delayed_work work;
      unsigned long status;
  /* Bit numbers for status flags */
  #define WDOG_ACTIVE        0    /* Is the watchdog running/active */
@@ -95,6 +104,14 @@ struct watchdog_device {
  #define WDOG_ALLOW_RELEASE    2    /* Did we receive the magic char

? */
#define WDOG_NO_WAY_OUT 3 /* Is 'nowayout' feature set ? */
#define WDOG_UNREGISTERED 4 /* Has the device been
unregistered */

include/linux/watchdog.h +8 -0
+
+/* Bits describing features supported by the HW */
+    unsigned long hw_features;
+
+/* Can the watchdog be stopped and started */
+#define WDOG_HW_IS_STOPPABLE        BIT(0)
+/* Is the watchdog already running when the driver starts up */
+#define WDOG_HW_RUNNING_AT_BOOT        BIT(1)
  };
  #define WATCHDOG_NOWAYOUT        IS_BUILTIN(CONFIG_WATCHDOG_NOWAYOUT)
@@ -120,6 +137,11 @@ static inline bool

watchdog_timeout_invalid(struct watchdog_device *wdd, unsigne

          (t < wdd->min_timeout || t > wdd->max_timeout));

}

include/linux/watchdog.h +5 -0
+static inline bool watchdog_is_stoppable(struct watchdog_device *wdd)
+{
+    return wdd->hw_features & WDOG_HW_IS_STOPPABLE;
+}
+
  /* Use the following functions to manipulate watchdog driver

specific data */
static inline void watchdog_set_drvdata(struct watchdog_device *wdd,
void *data)
{

include/linux/watchdog.h +1 -0
@@ -134,6 +156,7 @@ static inline void *watchdog_get_drvdata(struct
 watchdog_device *wdd)
  /* drivers/watchdog/watchdog_core.c */
  extern int watchdog_init_timeout(struct watchdog_device *wdd,
                    unsigned int timeout_parm, struct device *dev);
+int watchdog_init_params(struct watchdog_device *wdd, struct device
 *dev);
  extern int watchdog_register_device(struct watchdog_device *);
Quoted 1 line
  extern void watchdog_unregister_device(struct watchdog_device *);

Best Regards,
Wenyou Yang

#5 lore raw

PATCH v5 1/4 watchdog: Extend kernel API to know about HW limitations

Quoted 22 lines · Timo Kokkonen
Quoted 5 lines · Wenyou

Hi Timo,

If the /dev/watchdog device file doesn't open by the user space application, the system will reboot by the watchdog.

Thank you for pointing out the bug. When the struct watchdog_device is initialized, the expires variable is zero. Jiffies however is negative on boot up, so time_after() comparison with zero returns false and the worker keeps the watchdog alive for some minutes, until jiffies become positive. Then it will stop updating the HW as the expires value has become expired.

Quoted 2 lines

I think it is missing update of the wdd->expires value. added it as following.

Actually, the expires value stores the user space expiration value. It should only be updated when user space pings the watchdog. If we increment the expires value in the worker thread, the watchdog will never expire even if user space stops pinging it and keep it open.

Quoted 6 lines

So the proper fix is to have it like this (in watchdog_worker function):

    if (time_after(jiffies, wdd->expires) && watchdog_active(wdd)) {
        pr_crit("I will reset your machine !\n");
        return;
    }

I have a little doubt, if the watchdog is not opened by the user space, the watchdog will not reset the system in any case.

it doesn't work in the same way as before in the at91sam9_wdt.c.

Quoted 19 lines

Also we need this change to re-start the worker when watchdog is properly stopped (in watchdog_dev.c):

(quoted diff) +6 -2
@@ -157,9 +159,15 @@ static int watchdog_stop(struct watchdog_device 
 *wddev)
                goto out_stop;
        }
 
-       err = wddev->ops->stop(wddev);
-       if (err == 0)
+       if (!wddev->hw_max_timeout || watchdog_is_stoppable(wddev)) {
+               err = wddev->ops->stop(wddev);
+               if (err == 0)
+                       clear_bit(WDOG_ACTIVE, &wddev->status);
+       } else {
+               /* Unstoppable watchdogs need the worker to keep them 

alive */

                clear_bit(WDOG_ACTIVE, &wddev->status);

Review

Quoted 2 lines
(quoted diff) +2 -0
+               schedule_delayed_work(&wddev->work, wddev->hw_heartbeat);
+       }

Tested it, there is a issue.
After open the watchdog device file, and feed the watchdog, then close the watchdog device file, the system reset by the watchdog. It didn't reach here, the watchdog_worker didn't run.

Quoted 5 lines

out_stop:

        mutex_unlock(&wddev->lock);
Quoted 270 lines

After these two modifications the watchdog is being pinged indefinitely until the user space opens the device and the worker is re-started if watchdog daemon exits after writing the magic character. I will update these change to the next version of the patch.

Thanks again!

-Timo

Quoted 260 lines · Timo Kokkonen
Quoted 73 lines

There is a great deal of diversity in the watchdog hardware found on different devices. Differen hardware have different contstraints on them, many of the constraints that are excessively difficult for the user space to satisfy.

One such constraint is the length of the timeout value, which in many cases can be just a few seconds. Drivers are creating ad hoc solutions with timers and workqueues to extend the timeout in order to give user space more time between updates. Looking at the drivers it is clear that this has resulted to a lot of duplicate code.

Add an extension to the watchdog kernel API that allows the driver to describe tis HW constraints to the watchdog code. A kernel worker in the core is then used to extend the watchdog timeout on behalf of the user space. This allows the drivers to be simplified as core takes over the timer extending.

---
  drivers/watchdog/watchdog_core.c | 87
++++++++++++++++++++++++++++++++++++++--
  drivers/watchdog/watchdog_dev.c  | 12 ++++++
  include/linux/watchdog.h         | 23 +++++++++++
  3 files changed, 119 insertions(+), 3 deletions(-)
drivers/watchdog/watchdog_core.c +38 -0
index cec9b55..8e7d08d 100644
@@ -99,6 +99,75 @@ int watchdog_init_timeout(struct watchdog_device 
 *wdd,
  EXPORT_SYMBOL_GPL(watchdog_init_timeout);
  /**
+ * watchdog_init_timeout() - initialize generic watchdog parameters
+ * @wdd: Watchdog device to operate
+ * @dev: Device that stores the device tree properties
+ *
+ * Initialize the generic timeout parameters. The driver needs to set
+ * hw_features bitmask from @wdd prior calling this function in order
+ * to ensure the core knows how to handle the HW.
+ *
+ * A zero is returned on success and -EINVAL for failure.
+ */
+int watchdog_init_params(struct watchdog_device *wdd, struct device
 *dev)
+{
+    int ret = 0;
+
+    ret = watchdog_init_timeout(wdd, wdd->timeout, dev);
+    if (ret < 0)
+        return ret;
+
+    /*
+     * Max HW timeout needs to be set so that core knows when to
+     * use a kernel worker to support longer watchdog timeouts
+     */
+    if (!wdd->hw_max_timeout)
+        return -EINVAL;
+
+    return ret;
+}
+EXPORT_SYMBOL_GPL(watchdog_init_params);
+
+static void watchdog_worker(struct work_struct *work)
+{
+    struct watchdog_device *wdd = container_of(to_delayed_work(work),
+                        struct watchdog_device, work);
+
+    if (time_after(jiffies, wdd->expires)) {
+        pr_crit("I will reset your machine !\n");
+        return;
+    }

drivers/watchdog/watchdog_core.c int watchdog_init_timeout(struct watchdog_device · line 150

Quoted 10 lines
drivers/watchdog/watchdog_core.c +10 -0
+
+    if ((!watchdog_active(wdd) && !watchdog_is_stoppable(wdd)) ||
+        (watchdog_active(wdd) &&
+            wdd->hw_max_timeout < wdd->timeout * HZ)) {
+        if (wdd->ops->ping)
+            wdd->ops->ping(wdd);
+        else
+            wdd->ops->start(wdd);
+
+        schedule_delayed_work(&wdd->work, wdd->hw_heartbeat);
    I think you missed the update of wdd->expires, as following.
                                    wdd->expires = jiffies +

wdd->timeout * HZ;

Quoted 7 lines
drivers/watchdog/watchdog_core.c +7 -0
+    }
+}
+
+static int prepare_watchdog(struct watchdog_device *wdd)
+{
+    /* Stop the watchdog now before user space opens the device */
+    if (wdd->hw_features & WDOG_HW_IS_STOPPABLE &&

drivers/watchdog/watchdog_core.c int watchdog_init_timeout(struct watchdog_device *wdd, · line 166

Quoted 10 lines
drivers/watchdog/watchdog_core.c +10 -0
+        wdd->hw_features & WDOG_HW_RUNNING_AT_BOOT) {
+        wdd->ops->stop(wdd);
+
+    } else if (!(wdd->hw_features & WDOG_HW_IS_STOPPABLE)) {
+        /*
+         * Can't stop it, use a kernel timer to tick
+         * it until it's open by user space
+         */
+        schedule_delayed_work(&wdd->work, wdd->hw_heartbeat);
+    }
      ditto
                     wdd->expires = jiffies + wdd->timeout * HZ;
Quoted 150 lines
drivers/watchdog/watchdog_core.c +4 -0
+    return 0;
+}
+
+/**
   * watchdog_register_device() - register a watchdog device
   * @wdd: watchdog device
   *
drivers/watchdog/watchdog_core.c +15 -3
@@ -156,13 +225,24 @@ int watchdog_register_device(struct
 watchdog_device *wdd)
      wdd->dev = device_create(watchdog_class, wdd->parent, devno,
                      NULL, "watchdog%d", wdd->id);
      if (IS_ERR(wdd->dev)) {
-        watchdog_dev_unregister(wdd);
-        ida_simple_remove(&watchdog_ida, id);
          ret = PTR_ERR(wdd->dev);
-        return ret;
+        goto dev_create_fail;
+    }
+
+    INIT_DELAYED_WORK(&wdd->work, watchdog_worker);
+
+    if (wdd->hw_max_timeout) {
+        ret = prepare_watchdog(wdd);
+        if (ret)
+            goto dev_create_fail;
      }
      return 0;
+
+dev_create_fail:
+    watchdog_dev_unregister(wdd);
+    ida_simple_remove(&watchdog_ida, id);
+    return ret;
  }
  EXPORT_SYMBOL_GPL(watchdog_register_device);
@@ -181,6 +261,7 @@ void watchdog_unregister_device(struct
 watchdog_device *wdd)
      if (wdd == NULL)
          return;
+    cancel_delayed_work_sync(&wdd->work);
      devno = wdd->cdev.dev;
      ret = watchdog_dev_unregister(wdd);
      if (ret)
drivers/watchdog/watchdog_dev.c +12 -0
index 6aaefba..99eb363 100644
@@ -78,6 +78,12 @@ static int watchdog_ping(struct watchdog_device
 *wddev)
      else
          err = wddev->ops->start(wddev); /* restart watchdog */
+    if (wddev->hw_max_timeout &&
+        wddev->timeout * HZ > wddev->hw_max_timeout) {
+        wddev->expires = jiffies + wddev->timeout * HZ;
+        schedule_delayed_work(&wddev->work, wddev->hw_heartbeat);
+    }
+
  out_ping:
      mutex_unlock(&wddev->lock);
      return err;
@@ -110,6 +116,12 @@ static int watchdog_start(struct watchdog_device
 *wddev)
      if (err == 0)
          set_bit(WDOG_ACTIVE, &wddev->status);
+    if (wddev->hw_max_timeout &&
+        wddev->timeout * HZ > wddev->hw_max_timeout) {
+        wddev->expires = jiffies + wddev->timeout * HZ;
+        schedule_delayed_work(&wddev->work, wddev->hw_heartbeat);
+    }
+
  out_start:
      mutex_unlock(&wddev->lock);
      return err;
include/linux/watchdog.h +4 -0
index 395b70e..c16c921 100644
@@ -12,6 +12,7 @@
  #include <linux/bitops.h>
  #include <linux/device.h>
  #include <linux/cdev.h>
+#include <linux/workqueue.h>
  #include <uapi/linux/watchdog.h>
  struct watchdog_ops;
@@ -62,9 +63,13 @@ struct watchdog_ops {
   * @timeout:    The watchdog devices timeout value.
   * @min_timeout:The watchdog devices minimum timeout value.
   * @max_timeout:The watchdog devices maximum timeout value.
+ * @hw_max_timeout:The watchdog hardware maximum timeout value.
+ * @hw_margin:    Time interval between timer pings.
   * @driver-data:Pointer to the drivers private data.
   * @lock:    Lock for watchdog core internal use only.
+ * @work:    Worker used to provide longer timeouts than hardware

supports.
* @status: Field that contains the devices internal status bits.
+ * @hw_features:Feature bits describing how the watchdog HW works. *
* The watchdog_device structure contains all information about a
* watchdog timer device.

include/linux/watchdog.h +4 -0
@@ -86,8 +91,12 @@ struct watchdog_device {
      unsigned int timeout;
      unsigned int min_timeout;
      unsigned int max_timeout;
+    unsigned int hw_max_timeout; /* in jiffies */
+    unsigned int hw_heartbeat; /* in jiffies */
+    unsigned long int expires; /* for soft timer */
      void *driver_data;
      struct mutex lock;
+    struct delayed_work work;
      unsigned long status;
  /* Bit numbers for status flags */
  #define WDOG_ACTIVE        0    /* Is the watchdog running/active */
@@ -95,6 +104,14 @@ struct watchdog_device {
  #define WDOG_ALLOW_RELEASE    2    /* Did we receive the magic char

? */
#define WDOG_NO_WAY_OUT 3 /* Is 'nowayout' feature set ? */
#define WDOG_UNREGISTERED 4 /* Has the device been
unregistered */

include/linux/watchdog.h +8 -0
+
+/* Bits describing features supported by the HW */
+    unsigned long hw_features;
+
+/* Can the watchdog be stopped and started */
+#define WDOG_HW_IS_STOPPABLE        BIT(0)
+/* Is the watchdog already running when the driver starts up */
+#define WDOG_HW_RUNNING_AT_BOOT        BIT(1)
  };
  #define WATCHDOG_NOWAYOUT IS_BUILTIN(CONFIG_WATCHDOG_NOWAYOUT)
@@ -120,6 +137,11 @@ static inline bool

watchdog_timeout_invalid(struct watchdog_device *wdd, unsigne

          (t < wdd->min_timeout || t > wdd->max_timeout));

}

include/linux/watchdog.h +5 -0
+static inline bool watchdog_is_stoppable(struct watchdog_device *wdd)
+{
+    return wdd->hw_features & WDOG_HW_IS_STOPPABLE;
+}
+
  /* Use the following functions to manipulate watchdog driver

specific data */
static inline void watchdog_set_drvdata(struct watchdog_device *wdd,
void *data)
{

include/linux/watchdog.h +1 -0
@@ -134,6 +156,7 @@ static inline void *watchdog_get_drvdata(struct
 watchdog_device *wdd)
  /* drivers/watchdog/watchdog_core.c */
  extern int watchdog_init_timeout(struct watchdog_device *wdd,
                    unsigned int timeout_parm, struct device *dev);
+int watchdog_init_params(struct watchdog_device *wdd, struct device
 *dev);
  extern int watchdog_register_device(struct watchdog_device *);
Quoted 1 line
  extern void watchdog_unregister_device(struct watchdog_device *);

Best Regards,
Wenyou Yang

Best Regards,
Wenyou Yang

#6 lore raw

PATCH v5 1/4 watchdog: Extend kernel API to know about HW limitations

Quoted 27 lines · Wenyou
Quoted 24 lines · Timo Kokkonen
Quoted 5 lines · Wenyou

Hi Timo,

If the /dev/watchdog device file doesn't open by the user space application, the system will reboot by the watchdog.

Thank you for pointing out the bug. When the struct watchdog_device is initialized, the expires variable is zero. Jiffies however is negative on boot up, so time_after() comparison with zero returns false and the worker keeps the watchdog alive for some minutes, until jiffies become positive. Then it will stop updating the HW as the expires value has become expired.

Quoted 2 lines

I think it is missing update of the wdd->expires value. added it as following.

Actually, the expires value stores the user space expiration value. It should only be updated when user space pings the watchdog. If we increment the expires value in the worker thread, the watchdog will never expire even if user space stops pinging it and keep it open.

So the proper fix is to have it like this (in watchdog_worker function):

Quoted 8 lines
Quoted 4 lines
    if (time_after(jiffies, wdd->expires) && watchdog_active(wdd)) {
        pr_crit("I will reset your machine !\n");
        return;
    }

I have a little doubt, if the watchdog is not opened by the user space, the watchdog will not reset the system in any case.

it doesn't work in the same way as before in the at91sam9_wdt.c.

That's why the timer was originally in at91sam9_wdt, it kept on pinging the watchdog HW until user space opened it. That's why I started working on the early-timeout-sec properlty in the first place. And that is the same pattern that repeats all over the watchdog drivers.

Quoted 9 lines
Quoted 9 lines

Also we need this change to re-start the worker when watchdog is properly stopped (in watchdog_dev.c):

(quoted diff) +0 -0
@@ -157,9 +159,15 @@ static int watchdog_stop(struct watchdog_device
 *wddev)
                goto out_stop;
        }
 
Quoted 17 lines

Review

Quoted 12 lines
(quoted diff) +6 -2
-       err = wddev->ops->stop(wddev);
-       if (err == 0)
+       if (!wddev->hw_max_timeout || watchdog_is_stoppable(wddev)) {
+               err = wddev->ops->stop(wddev);
+               if (err == 0)
+                       clear_bit(WDOG_ACTIVE, &wddev->status);
+       } else {
+               /* Unstoppable watchdogs need the worker to keep them

alive */

                clear_bit(WDOG_ACTIVE, &wddev->status);
(quoted diff) +2 -0
+               schedule_delayed_work(&wddev->work, wddev->hw_heartbeat);
+       }

Tested it, there is a issue.
After open the watchdog device file, and feed the watchdog, then close the watchdog device file, the system reset by the watchdog. It didn't reach here, the watchdog_worker didn't run.

If you just close the device, watchdog is not stopped. That is to prevent watchdog daemon crash from hanging the system. You need to feed the magic char 'V' in order to stop the watchdog, which will trigger the start of the worker. Have you tried that?

I noticed some other issues with the patches. I'll send a new one with all fixes combined shortly..

Thanks for your feedback.

-Timo

Quoted 278 lines
Quoted 5 lines

out_stop:

        mutex_unlock(&wddev->lock);
Quoted 270 lines

After these two modifications the watchdog is being pinged indefinitely until the user space opens the device and the worker is re-started if watchdog daemon exits after writing the magic character. I will update these change to the next version of the patch.

Thanks again!

-Timo

Quoted 260 lines · Timo Kokkonen
Quoted 73 lines

There is a great deal of diversity in the watchdog hardware found on different devices. Differen hardware have different contstraints on them, many of the constraints that are excessively difficult for the user space to satisfy.

One such constraint is the length of the timeout value, which in many cases can be just a few seconds. Drivers are creating ad hoc solutions with timers and workqueues to extend the timeout in order to give user space more time between updates. Looking at the drivers it is clear that this has resulted to a lot of duplicate code.

Add an extension to the watchdog kernel API that allows the driver to describe tis HW constraints to the watchdog code. A kernel worker in the core is then used to extend the watchdog timeout on behalf of the user space. This allows the drivers to be simplified as core takes over the timer extending.

---
  drivers/watchdog/watchdog_core.c | 87
++++++++++++++++++++++++++++++++++++++--
  drivers/watchdog/watchdog_dev.c  | 12 ++++++
  include/linux/watchdog.h         | 23 +++++++++++
  3 files changed, 119 insertions(+), 3 deletions(-)
drivers/watchdog/watchdog_core.c +38 -0
index cec9b55..8e7d08d 100644
@@ -99,6 +99,75 @@ int watchdog_init_timeout(struct watchdog_device
 *wdd,
  EXPORT_SYMBOL_GPL(watchdog_init_timeout);
  /**
+ * watchdog_init_timeout() - initialize generic watchdog parameters
+ * @wdd: Watchdog device to operate
+ * @dev: Device that stores the device tree properties
+ *
+ * Initialize the generic timeout parameters. The driver needs to set
+ * hw_features bitmask from @wdd prior calling this function in order
+ * to ensure the core knows how to handle the HW.
+ *
+ * A zero is returned on success and -EINVAL for failure.
+ */
+int watchdog_init_params(struct watchdog_device *wdd, struct device
 *dev)
+{
+    int ret = 0;
+
+    ret = watchdog_init_timeout(wdd, wdd->timeout, dev);
+    if (ret < 0)
+        return ret;
+
+    /*
+     * Max HW timeout needs to be set so that core knows when to
+     * use a kernel worker to support longer watchdog timeouts
+     */
+    if (!wdd->hw_max_timeout)
+        return -EINVAL;
+
+    return ret;
+}
+EXPORT_SYMBOL_GPL(watchdog_init_params);
+
+static void watchdog_worker(struct work_struct *work)
+{
+    struct watchdog_device *wdd = container_of(to_delayed_work(work),
+                        struct watchdog_device, work);
+
+    if (time_after(jiffies, wdd->expires)) {
+        pr_crit("I will reset your machine !\n");
+        return;
+    }

drivers/watchdog/watchdog_core.c int watchdog_init_timeout(struct watchdog_device · line 150

Quoted 10 lines
drivers/watchdog/watchdog_core.c +10 -0
+
+    if ((!watchdog_active(wdd) && !watchdog_is_stoppable(wdd)) ||
+        (watchdog_active(wdd) &&
+            wdd->hw_max_timeout < wdd->timeout * HZ)) {
+        if (wdd->ops->ping)
+            wdd->ops->ping(wdd);
+        else
+            wdd->ops->start(wdd);
+
+        schedule_delayed_work(&wdd->work, wdd->hw_heartbeat);
    I think you missed the update of wdd->expires, as following.
                                    wdd->expires = jiffies +

wdd->timeout * HZ;

Quoted 7 lines
drivers/watchdog/watchdog_core.c +7 -0
+    }
+}
+
+static int prepare_watchdog(struct watchdog_device *wdd)
+{
+    /* Stop the watchdog now before user space opens the device */
+    if (wdd->hw_features & WDOG_HW_IS_STOPPABLE &&

drivers/watchdog/watchdog_core.c int watchdog_init_timeout(struct watchdog_device *wdd, · line 166

Quoted 10 lines
drivers/watchdog/watchdog_core.c +10 -0
+        wdd->hw_features & WDOG_HW_RUNNING_AT_BOOT) {
+        wdd->ops->stop(wdd);
+
+    } else if (!(wdd->hw_features & WDOG_HW_IS_STOPPABLE)) {
+        /*
+         * Can't stop it, use a kernel timer to tick
+         * it until it's open by user space
+         */
+        schedule_delayed_work(&wdd->work, wdd->hw_heartbeat);
+    }
      ditto
                     wdd->expires = jiffies + wdd->timeout * HZ;
Quoted 150 lines
drivers/watchdog/watchdog_core.c +4 -0
+    return 0;
+}
+
+/**
   * watchdog_register_device() - register a watchdog device
   * @wdd: watchdog device
   *
drivers/watchdog/watchdog_core.c +15 -3
@@ -156,13 +225,24 @@ int watchdog_register_device(struct
 watchdog_device *wdd)
      wdd->dev = device_create(watchdog_class, wdd->parent, devno,
                      NULL, "watchdog%d", wdd->id);
      if (IS_ERR(wdd->dev)) {
-        watchdog_dev_unregister(wdd);
-        ida_simple_remove(&watchdog_ida, id);
          ret = PTR_ERR(wdd->dev);
-        return ret;
+        goto dev_create_fail;
+    }
+
+    INIT_DELAYED_WORK(&wdd->work, watchdog_worker);
+
+    if (wdd->hw_max_timeout) {
+        ret = prepare_watchdog(wdd);
+        if (ret)
+            goto dev_create_fail;
      }
      return 0;
+
+dev_create_fail:
+    watchdog_dev_unregister(wdd);
+    ida_simple_remove(&watchdog_ida, id);
+    return ret;
  }
  EXPORT_SYMBOL_GPL(watchdog_register_device);
@@ -181,6 +261,7 @@ void watchdog_unregister_device(struct
 watchdog_device *wdd)
      if (wdd == NULL)
          return;
+    cancel_delayed_work_sync(&wdd->work);
      devno = wdd->cdev.dev;
      ret = watchdog_dev_unregister(wdd);
      if (ret)
drivers/watchdog/watchdog_dev.c +12 -0
index 6aaefba..99eb363 100644
@@ -78,6 +78,12 @@ static int watchdog_ping(struct watchdog_device
 *wddev)
      else
          err = wddev->ops->start(wddev); /* restart watchdog */
+    if (wddev->hw_max_timeout &&
+        wddev->timeout * HZ > wddev->hw_max_timeout) {
+        wddev->expires = jiffies + wddev->timeout * HZ;
+        schedule_delayed_work(&wddev->work, wddev->hw_heartbeat);
+    }
+
  out_ping:
      mutex_unlock(&wddev->lock);
      return err;
@@ -110,6 +116,12 @@ static int watchdog_start(struct watchdog_device
 *wddev)
      if (err == 0)
          set_bit(WDOG_ACTIVE, &wddev->status);
+    if (wddev->hw_max_timeout &&
+        wddev->timeout * HZ > wddev->hw_max_timeout) {
+        wddev->expires = jiffies + wddev->timeout * HZ;
+        schedule_delayed_work(&wddev->work, wddev->hw_heartbeat);
+    }
+
  out_start:
      mutex_unlock(&wddev->lock);
      return err;
include/linux/watchdog.h +4 -0
index 395b70e..c16c921 100644
@@ -12,6 +12,7 @@
  #include <linux/bitops.h>
  #include <linux/device.h>
  #include <linux/cdev.h>
+#include <linux/workqueue.h>
  #include <uapi/linux/watchdog.h>
  struct watchdog_ops;
@@ -62,9 +63,13 @@ struct watchdog_ops {
   * @timeout:    The watchdog devices timeout value.
   * @min_timeout:The watchdog devices minimum timeout value.
   * @max_timeout:The watchdog devices maximum timeout value.
+ * @hw_max_timeout:The watchdog hardware maximum timeout value.
+ * @hw_margin:    Time interval between timer pings.
   * @driver-data:Pointer to the drivers private data.
   * @lock:    Lock for watchdog core internal use only.
+ * @work:    Worker used to provide longer timeouts than hardware

supports.
* @status: Field that contains the devices internal status bits.
+ * @hw_features:Feature bits describing how the watchdog HW works. *
* The watchdog_device structure contains all information about a
* watchdog timer device.

include/linux/watchdog.h +4 -0
@@ -86,8 +91,12 @@ struct watchdog_device {
      unsigned int timeout;
      unsigned int min_timeout;
      unsigned int max_timeout;
+    unsigned int hw_max_timeout; /* in jiffies */
+    unsigned int hw_heartbeat; /* in jiffies */
+    unsigned long int expires; /* for soft timer */
      void *driver_data;
      struct mutex lock;
+    struct delayed_work work;
      unsigned long status;
  /* Bit numbers for status flags */
  #define WDOG_ACTIVE        0    /* Is the watchdog running/active */
@@ -95,6 +104,14 @@ struct watchdog_device {
  #define WDOG_ALLOW_RELEASE    2    /* Did we receive the magic char

? */
#define WDOG_NO_WAY_OUT 3 /* Is 'nowayout' feature set ? */
#define WDOG_UNREGISTERED 4 /* Has the device been
unregistered */

include/linux/watchdog.h +8 -0
+
+/* Bits describing features supported by the HW */
+    unsigned long hw_features;
+
+/* Can the watchdog be stopped and started */
+#define WDOG_HW_IS_STOPPABLE        BIT(0)
+/* Is the watchdog already running when the driver starts up */
+#define WDOG_HW_RUNNING_AT_BOOT        BIT(1)
  };
  #define WATCHDOG_NOWAYOUT IS_BUILTIN(CONFIG_WATCHDOG_NOWAYOUT)
@@ -120,6 +137,11 @@ static inline bool

watchdog_timeout_invalid(struct watchdog_device *wdd, unsigne

          (t < wdd->min_timeout || t > wdd->max_timeout));

}

include/linux/watchdog.h +5 -0
+static inline bool watchdog_is_stoppable(struct watchdog_device *wdd)
+{
+    return wdd->hw_features & WDOG_HW_IS_STOPPABLE;
+}
+
  /* Use the following functions to manipulate watchdog driver

specific data */
static inline void watchdog_set_drvdata(struct watchdog_device *wdd,
void *data)
{

include/linux/watchdog.h +1 -0
@@ -134,6 +156,7 @@ static inline void *watchdog_get_drvdata(struct
 watchdog_device *wdd)
  /* drivers/watchdog/watchdog_core.c */
  extern int watchdog_init_timeout(struct watchdog_device *wdd,
                    unsigned int timeout_parm, struct device *dev);
+int watchdog_init_params(struct watchdog_device *wdd, struct device
 *dev);
  extern int watchdog_register_device(struct watchdog_device *);
Quoted 1 line
  extern void watchdog_unregister_device(struct watchdog_device *);

Best Regards,
Wenyou Yang

Best Regards,
Wenyou Yang

#7 lore raw

PATCH v5 1/4 watchdog: Extend kernel API to know about HW limitations

Quoted 80 lines

-----Original Message-----
From: Timo Kokkonen [mailto:timo.kokkonen at offcode.fi]
Sent: 2015?4?14? 13:32
To: Yang, Wenyou; linux-arm-kernel at lists.infradead.org; linux- watchdog at vger.kernel.org; boris.brezillon at free-electrons.com; Ferre, Nicolas; alexandre.belloni at free-electrons.com
Subject: Re: [PATCHv5 1/4] watchdog: Extend kernel API to know about HW limitations

Quoted 27 lines · Wenyou
Quoted 24 lines · Timo Kokkonen
Quoted 5 lines · Wenyou

Hi Timo,

If the /dev/watchdog device file doesn't open by the user space application, the system will reboot by the watchdog.

Thank you for pointing out the bug. When the struct watchdog_device is initialized, the expires variable is zero. Jiffies however is negative on boot up, so time_after() comparison with zero returns false and the worker keeps the watchdog alive for some minutes, until jiffies become positive. Then it will stop updating the HW as the expires value has become expired.

Quoted 2 lines

I think it is missing update of the wdd->expires value. added it as following.

Actually, the expires value stores the user space expiration value. It should only be updated when user space pings the watchdog. If we increment the expires value in the worker thread, the watchdog will never expire even if user space stops pinging it and keep it open.

So the proper fix is to have it like this (in watchdog_worker function):

Quoted 8 lines
Quoted 4 lines
    if (time_after(jiffies, wdd->expires) && watchdog_active(wdd)) {
        pr_crit("I will reset your machine !\n");
        return;
    }

I have a little doubt, if the watchdog is not opened by the user space, the watchdog will not reset the system in any case.

it doesn't work in the same way as before in the at91sam9_wdt.c.

That's why the timer was originally in at91sam9_wdt, it kept on pinging the watchdog HW until user space opened it. That's why I started working on the early-timeout-sec properlty in the first place. And that is the same pattern that repeats all over the watchdog drivers.

Quoted 9 lines
Quoted 9 lines

Also we need this change to re-start the worker when watchdog is properly stopped (in watchdog_dev.c):

(quoted diff) +0 -0
@@ -157,9 +159,15 @@ static int watchdog_stop(struct watchdog_device
 *wddev)
                goto out_stop;
        }
 
Quoted 17 lines

Review

Quoted 12 lines
(quoted diff) +6 -2
-       err = wddev->ops->stop(wddev);
-       if (err == 0)
+       if (!wddev->hw_max_timeout || watchdog_is_stoppable(wddev)) {
+               err = wddev->ops->stop(wddev);
+               if (err == 0)
+                       clear_bit(WDOG_ACTIVE, &wddev->status);
+       } else {
+               /* Unstoppable watchdogs need the worker to keep them

alive */

                clear_bit(WDOG_ACTIVE, &wddev->status);
(quoted diff) +2 -0
+               schedule_delayed_work(&wddev->work, wddev->hw_heartbeat);
+       }

Tested it, there is a issue.
After open the watchdog device file, and feed the watchdog, then close the watchdog device file, the system reset by the watchdog. It didn't reach here, the watchdog_worker didn't run.

Quoted 4 lines

If you just close the device, watchdog is not stopped. That is to prevent watchdog daemon crash from hanging the system. You need to feed the magic char 'V' in order to stop the watchdog, which will trigger the start of the worker. Have you tried that?

Yes, if feed the magic char "V", then close the watchdog device file, it works fine.

Quoted 250 lines

I noticed some other issues with the patches. I'll send a new one with all fixes combined shortly..

Thanks for your feedback.

-Timo

Quoted 242 lines
Quoted 242 lines

out_stop:

        mutex_unlock(&wddev->lock);

After these two modifications the watchdog is being pinged indefinitely until the user space opens the device and the worker is re-started if watchdog daemon exits after writing the magic character. I will update these change to the next version of the patch.

Thanks again!

-Timo

Quoted 228 lines · Timo Kokkonen
Quoted 73 lines

There is a great deal of diversity in the watchdog hardware found on different devices. Differen hardware have different contstraints on them, many of the constraints that are excessively difficult for the user space to satisfy.

One such constraint is the length of the timeout value, which in many cases can be just a few seconds. Drivers are creating ad hoc solutions with timers and workqueues to extend the timeout in order to give user space more time between updates. Looking at the drivers it is clear that this has resulted to a lot of duplicate code.

Add an extension to the watchdog kernel API that allows the driver to describe tis HW constraints to the watchdog code. A kernel worker in the core is then used to extend the watchdog timeout on behalf of the user space. This allows the drivers to be simplified as core takes over the timer extending.

---
  drivers/watchdog/watchdog_core.c | 87
++++++++++++++++++++++++++++++++++++++--
  drivers/watchdog/watchdog_dev.c  | 12 ++++++
  include/linux/watchdog.h         | 23 +++++++++++
  3 files changed, 119 insertions(+), 3 deletions(-)
drivers/watchdog/watchdog_core.c +38 -0
index cec9b55..8e7d08d 100644
@@ -99,6 +99,75 @@ int watchdog_init_timeout(struct watchdog_device
 *wdd,
  EXPORT_SYMBOL_GPL(watchdog_init_timeout);
  /**
+ * watchdog_init_timeout() - initialize generic watchdog parameters
+ * @wdd: Watchdog device to operate
+ * @dev: Device that stores the device tree properties
+ *
+ * Initialize the generic timeout parameters. The driver needs to set
+ * hw_features bitmask from @wdd prior calling this function in order
+ * to ensure the core knows how to handle the HW.
+ *
+ * A zero is returned on success and -EINVAL for failure.
+ */
+int watchdog_init_params(struct watchdog_device *wdd, struct device
 *dev)
+{
+    int ret = 0;
+
+    ret = watchdog_init_timeout(wdd, wdd->timeout, dev);
+    if (ret < 0)
+        return ret;
+
+    /*
+     * Max HW timeout needs to be set so that core knows when to
+     * use a kernel worker to support longer watchdog timeouts
+     */
+    if (!wdd->hw_max_timeout)
+        return -EINVAL;
+
+    return ret;
+}
+EXPORT_SYMBOL_GPL(watchdog_init_params);
+
+static void watchdog_worker(struct work_struct *work)
+{
+    struct watchdog_device *wdd = container_of(to_delayed_work(work),
+                        struct watchdog_device, work);
+
+    if (time_after(jiffies, wdd->expires)) {
+        pr_crit("I will reset your machine !\n");
+        return;
+    }

drivers/watchdog/watchdog_core.c int watchdog_init_timeout(struct watchdog_device · line 150

Quoted 10 lines
drivers/watchdog/watchdog_core.c +10 -0
+
+    if ((!watchdog_active(wdd) && !watchdog_is_stoppable(wdd)) ||
+        (watchdog_active(wdd) &&
+            wdd->hw_max_timeout < wdd->timeout * HZ)) {
+        if (wdd->ops->ping)
+            wdd->ops->ping(wdd);
+        else
+            wdd->ops->start(wdd);
+
+        schedule_delayed_work(&wdd->work, wdd->hw_heartbeat);
    I think you missed the update of wdd->expires, as following.
                                    wdd->expires = jiffies +

wdd->timeout * HZ;

Quoted 7 lines
drivers/watchdog/watchdog_core.c +7 -0
+    }
+}
+
+static int prepare_watchdog(struct watchdog_device *wdd)
+{
+    /* Stop the watchdog now before user space opens the device */
+    if (wdd->hw_features & WDOG_HW_IS_STOPPABLE &&

drivers/watchdog/watchdog_core.c int watchdog_init_timeout(struct watchdog_device *wdd, · line 166

Quoted 10 lines
drivers/watchdog/watchdog_core.c +10 -0
+        wdd->hw_features & WDOG_HW_RUNNING_AT_BOOT) {
+        wdd->ops->stop(wdd);
+
+    } else if (!(wdd->hw_features & WDOG_HW_IS_STOPPABLE)) {
+        /*
+         * Can't stop it, use a kernel timer to tick
+         * it until it's open by user space
+         */
+        schedule_delayed_work(&wdd->work, wdd->hw_heartbeat);
+    }
      ditto
                     wdd->expires = jiffies + wdd->timeout * HZ;
Quoted 121 lines
drivers/watchdog/watchdog_core.c +4 -0
+    return 0;
+}
+
+/**
   * watchdog_register_device() - register a watchdog device
   * @wdd: watchdog device
   *
drivers/watchdog/watchdog_core.c +15 -3
@@ -156,13 +225,24 @@ int watchdog_register_device(struct
 watchdog_device *wdd)
      wdd->dev = device_create(watchdog_class, wdd->parent, devno,
                      NULL, "watchdog%d", wdd->id);
      if (IS_ERR(wdd->dev)) {
-        watchdog_dev_unregister(wdd);
-        ida_simple_remove(&watchdog_ida, id);
          ret = PTR_ERR(wdd->dev);
-        return ret;
+        goto dev_create_fail;
+    }
+
+    INIT_DELAYED_WORK(&wdd->work, watchdog_worker);
+
+    if (wdd->hw_max_timeout) {
+        ret = prepare_watchdog(wdd);
+        if (ret)
+            goto dev_create_fail;
      }
      return 0;
+
+dev_create_fail:
+    watchdog_dev_unregister(wdd);
+    ida_simple_remove(&watchdog_ida, id);
+    return ret;
  }
  EXPORT_SYMBOL_GPL(watchdog_register_device);
@@ -181,6 +261,7 @@ void watchdog_unregister_device(struct
 watchdog_device *wdd)
      if (wdd == NULL)
          return;
+    cancel_delayed_work_sync(&wdd->work);
      devno = wdd->cdev.dev;
      ret = watchdog_dev_unregister(wdd);
      if (ret)
drivers/watchdog/watchdog_dev.c +12 -0
index 6aaefba..99eb363 100644
@@ -78,6 +78,12 @@ static int watchdog_ping(struct watchdog_device
 *wddev)
      else
          err = wddev->ops->start(wddev); /* restart watchdog */
+    if (wddev->hw_max_timeout &&
+        wddev->timeout * HZ > wddev->hw_max_timeout) {
+        wddev->expires = jiffies + wddev->timeout * HZ;
+        schedule_delayed_work(&wddev->work, wddev->hw_heartbeat);
+    }
+
  out_ping:
      mutex_unlock(&wddev->lock);
      return err;
@@ -110,6 +116,12 @@ static int watchdog_start(struct watchdog_device
 *wddev)
      if (err == 0)
          set_bit(WDOG_ACTIVE, &wddev->status);
+    if (wddev->hw_max_timeout &&
+        wddev->timeout * HZ > wddev->hw_max_timeout) {
+        wddev->expires = jiffies + wddev->timeout * HZ;
+        schedule_delayed_work(&wddev->work, wddev->hw_heartbeat);
+    }
+
  out_start:
      mutex_unlock(&wddev->lock);
      return err;
include/linux/watchdog.h +4 -0
index 395b70e..c16c921 100644
@@ -12,6 +12,7 @@
  #include <linux/bitops.h>
  #include <linux/device.h>
  #include <linux/cdev.h>
+#include <linux/workqueue.h>
  #include <uapi/linux/watchdog.h>
  struct watchdog_ops;
@@ -62,9 +63,13 @@ struct watchdog_ops {
   * @timeout:    The watchdog devices timeout value.
   * @min_timeout:The watchdog devices minimum timeout value.
   * @max_timeout:The watchdog devices maximum timeout value.
+ * @hw_max_timeout:The watchdog hardware maximum timeout value.
+ * @hw_margin:    Time interval between timer pings.
   * @driver-data:Pointer to the drivers private data.
   * @lock:    Lock for watchdog core internal use only.
+ * @work:    Worker used to provide longer timeouts than hardware

supports.
* @status: Field that contains the devices internal status bits.
+ * @hw_features:Feature bits describing how the watchdog HW works. *
* The watchdog_device structure contains all information about a
* watchdog timer device.

include/linux/watchdog.h +4 -0
@@ -86,8 +91,12 @@ struct watchdog_device {
      unsigned int timeout;
      unsigned int min_timeout;
      unsigned int max_timeout;
+    unsigned int hw_max_timeout; /* in jiffies */
+    unsigned int hw_heartbeat; /* in jiffies */
+    unsigned long int expires; /* for soft timer */
      void *driver_data;
      struct mutex lock;
+    struct delayed_work work;
      unsigned long status;
  /* Bit numbers for status flags */
  #define WDOG_ACTIVE        0    /* Is the watchdog running/active */
@@ -95,6 +104,14 @@ struct watchdog_device {
  #define WDOG_ALLOW_RELEASE    2    /* Did we receive the magic char

? */
#define WDOG_NO_WAY_OUT 3 /* Is 'nowayout' feature set ? */
#define WDOG_UNREGISTERED 4 /* Has the device been
unregistered */

include/linux/watchdog.h +3 -0
+
+/* Bits describing features supported by the HW */
+    unsigned long hw_features;
Quoted 37 lines
Quoted 7 lines
Quoted 7 lines
Quoted 7 lines

include/linux/watchdog.h struct watchdog_device {

Quoted 7 lines
include/linux/watchdog.h +5 -0
+
+/* Can the watchdog be stopped and started */
+#define WDOG_HW_IS_STOPPABLE        BIT(0)
+/* Is the watchdog already running when the driver starts up */
+#define WDOG_HW_RUNNING_AT_BOOT        BIT(1)
  };
  #define WATCHDOG_NOWAYOUT

IS_BUILTIN(CONFIG_WATCHDOG_NOWAYOUT)

Quoted 29 lines
Quoted 17 lines
Quoted 17 lines
Quoted 17 lines
include/linux/watchdog.h +0 -0
@@ -120,6 +137,11 @@ static inline bool

watchdog_timeout_invalid(struct watchdog_device *wdd, unsigne

          (t < wdd->min_timeout || t > wdd->max_timeout));

}

include/linux/watchdog.h +5 -0
+static inline bool watchdog_is_stoppable(struct watchdog_device *wdd)
+{
+    return wdd->hw_features & WDOG_HW_IS_STOPPABLE;
+}
+
  /* Use the following functions to manipulate watchdog driver

specific data */
static inline void watchdog_set_drvdata(struct watchdog_device *wdd,
void *data)
{

include/linux/watchdog.h +0 -0
@@ -134,6 +156,7 @@ static inline void *watchdog_get_drvdata(struct
 watchdog_device *wdd)
  /* drivers/watchdog/watchdog_core.c */
Quoted 9 lines
Quoted 8 lines
Quoted 6 lines
include/linux/watchdog.h +1 -0
  extern int watchdog_init_timeout(struct watchdog_device *wdd,
                    unsigned int timeout_parm, struct device *dev);
+int watchdog_init_params(struct watchdog_device *wdd, struct device
 *dev);
  extern int watchdog_register_device(struct watchdog_device *);
  extern void watchdog_unregister_device(struct watchdog_device *);

Best Regards,
Wenyou Yang

Best Regards,
Wenyou Yang

Best Regards,
Wenyou Yang

#8 lore raw

PATCH v5 2/4 watchdog: Allow watchdog to reset device at early boot

In reply to Timo Kokkonen

Historically the watchdogs have always been stopped before user space opens and takes over the device. This is not good on many production systems where any crash, in kernel or user space, must always result in a device reset.

Add a new early_timeout_sec parameter to the watchdog that gives user space certain amount of time to set up itself and take over the watchdog. Until this timeout has been reached the watchdog core takes care of petting the watchdog HW. If there is any crash in kernel or user space, reboot is guaranteed as watchdog hardware is never stopped.

There is also mode of supplying zero seconds for the early_timeout_sec parameter. In this mode the worker is not scheduled, so the watchdog timer is not touched nor is the HW petted until user space takes over it.

---
 drivers/watchdog/watchdog_core.c | 39 ++++++++++++++++++++++++++++++---------
 include/linux/watchdog.h         |  1 +
 2 files changed, 31 insertions(+), 9 deletions(-)
drivers/watchdog/watchdog_core.c +30 -9
index 8e7d08d..d6bedf7 100644
@@ -111,12 +111,18 @@ EXPORT_SYMBOL_GPL(watchdog_init_timeout);
  */
 int watchdog_init_params(struct watchdog_device *wdd, struct device *dev)
 {
+	unsigned int t = 0;
 	int ret = 0;
 
 	ret = watchdog_init_timeout(wdd, wdd->timeout, dev);
 	if (ret < 0)
 		return ret;
 
+	if (!of_property_read_u32(dev->of_node, "early-timeout-sec", &t))
+		wdd->early_timeout_sec = t;
+	else
+		wdd->early_timeout_sec = -1;
+
 	/*
 	 * Max HW timeout needs to be set so that core knows when to
 	 * use a kernel worker to support longer watchdog timeouts
@@ -139,6 +145,7 @@ static void watchdog_worker(struct work_struct *work)
 	}
 
 	if ((!watchdog_active(wdd) && !watchdog_is_stoppable(wdd)) ||
+		(!watchdog_active(wdd) && wdd->early_timeout_sec >= 0) ||
 		(watchdog_active(wdd) &&
 			wdd->hw_max_timeout < wdd->timeout * HZ)) {
 		if (wdd->ops->ping)
@@ -152,17 +159,31 @@ static void watchdog_worker(struct work_struct *work)
 
 static int prepare_watchdog(struct watchdog_device *wdd)
 {
-	/* Stop the watchdog now before user space opens the device */
-	if (wdd->hw_features & WDOG_HW_IS_STOPPABLE &&
-		wdd->hw_features & WDOG_HW_RUNNING_AT_BOOT) {
-		wdd->ops->stop(wdd);
-
-	} else if (!(wdd->hw_features & WDOG_HW_IS_STOPPABLE)) {
+	if (wdd->early_timeout_sec >= 0) {
 		/*
-		 * Can't stop it, use a kernel timer to tick
-		 * it until it's open by user space
+		 * early timeout, if set, ensures that watchdog will
+		 * reset the device unless user space opens the
+		 * watchdog device within the given interval.
 		 */
-		schedule_delayed_work(&wdd->work, wdd->hw_heartbeat);
+		if (!(wdd->hw_features & WDOG_HW_RUNNING_AT_BOOT))
+			wdd->ops->start(wdd);
+
+		if (wdd->early_timeout_sec > 0) {
+			schedule_delayed_work(&wdd->work,  wdd->hw_heartbeat);
+		}
+	} else {
+		/* Stop the watchdog now before user space opens the device */
+		if (wdd->hw_features & WDOG_HW_IS_STOPPABLE &&
+			wdd->hw_features & WDOG_HW_RUNNING_AT_BOOT) {
+			wdd->ops->stop(wdd);
+
+		} else if (!(wdd->hw_features & WDOG_HW_IS_STOPPABLE)) {
+			/*
+			 * Can't stop it, use a kernel timer to tick
+			 * it until it's open by user space
+			 */
+			schedule_delayed_work(&wdd->work, wdd->hw_heartbeat);
+		}
 	}
 	return 0;
 }
include/linux/watchdog.h +1 -0
index c16c921..6f868b6 100644
@@ -94,6 +94,7 @@ struct watchdog_device {
 	unsigned int hw_max_timeout; /* in jiffies */
 	unsigned int hw_heartbeat; /* in jiffies */
 	unsigned long int expires; /* for soft timer */
+	unsigned int early_timeout_sec;
 	void *driver_data;
 	struct mutex lock;
 	struct delayed_work work;
Signature · 1 line
2.1.0
#9 lore raw

PATCH v5 2/4 watchdog: Allow watchdog to reset device at early boot

Quoted 69 lines · Timo Kokkonen

Historically the watchdogs have always been stopped before user space opens and takes over the device. This is not good on many production systems where any crash, in kernel or user space, must always result in a device reset.

Add a new early_timeout_sec parameter to the watchdog that gives user space certain amount of time to set up itself and take over the watchdog. Until this timeout has been reached the watchdog core takes care of petting the watchdog HW. If there is any crash in kernel or user space, reboot is guaranteed as watchdog hardware is never stopped.

There is also mode of supplying zero seconds for the early_timeout_sec parameter. In this mode the worker is not scheduled, so the watchdog timer is not touched nor is the HW petted until user space takes over it.

---
  drivers/watchdog/watchdog_core.c | 39 ++++++++++++++++++++++++++++++---------
  include/linux/watchdog.h         |  1 +
  2 files changed, 31 insertions(+), 9 deletions(-)
drivers/watchdog/watchdog_core.c +9 -8
index 8e7d08d..d6bedf7 100644
@@ -111,12 +111,18 @@ EXPORT_SYMBOL_GPL(watchdog_init_timeout);
   */
  int watchdog_init_params(struct watchdog_device *wdd, struct device *dev)
  {
+	unsigned int t = 0;
  	int ret = 0;
 
  	ret = watchdog_init_timeout(wdd, wdd->timeout, dev);
  	if (ret < 0)
  		return ret;
 
+	if (!of_property_read_u32(dev->of_node, "early-timeout-sec", &t))
+		wdd->early_timeout_sec = t;
+	else
+		wdd->early_timeout_sec = -1;
+
  	/*
  	 * Max HW timeout needs to be set so that core knows when to
  	 * use a kernel worker to support longer watchdog timeouts
@@ -139,6 +145,7 @@ static void watchdog_worker(struct work_struct *work)
  	}
 
  	if ((!watchdog_active(wdd) && !watchdog_is_stoppable(wdd)) ||
+		(!watchdog_active(wdd) && wdd->early_timeout_sec >= 0) ||
  		(watchdog_active(wdd) &&
  			wdd->hw_max_timeout < wdd->timeout * HZ)) {
  		if (wdd->ops->ping)
@@ -152,17 +159,31 @@ static void watchdog_worker(struct work_struct *work)
 
  static int prepare_watchdog(struct watchdog_device *wdd)
  {
-	/* Stop the watchdog now before user space opens the device */
-	if (wdd->hw_features & WDOG_HW_IS_STOPPABLE &&
-		wdd->hw_features & WDOG_HW_RUNNING_AT_BOOT) {
-		wdd->ops->stop(wdd);
-
-	} else if (!(wdd->hw_features & WDOG_HW_IS_STOPPABLE)) {
+	if (wdd->early_timeout_sec >= 0) {
  		/*
-		 * Can't stop it, use a kernel timer to tick
-		 * it until it's open by user space
+		 * early timeout, if set, ensures that watchdog will

drivers/watchdog/watchdog_core.c static void watchdog_worker(struct work_struct *work) · line 173

Quoted 10 lines
drivers/watchdog/watchdog_core.c +8 -1
+		 * reset the device unless user space opens the
+		 * watchdog device within the given interval.
  		 */
-		schedule_delayed_work(&wdd->work, wdd->hw_heartbeat);
+		if (!(wdd->hw_features & WDOG_HW_RUNNING_AT_BOOT))
+			wdd->ops->start(wdd);
+
+		if (wdd->early_timeout_sec > 0) {
+			schedule_delayed_work(&wdd->work,  wdd->hw_heartbeat);
+		}

This bracket is unnecessary.

Quoted 21 lines
drivers/watchdog/watchdog_core.c +13 -0
+	} else {
+		/* Stop the watchdog now before user space opens the device */
+		if (wdd->hw_features & WDOG_HW_IS_STOPPABLE &&
+			wdd->hw_features & WDOG_HW_RUNNING_AT_BOOT) {
+			wdd->ops->stop(wdd);
+
+		} else if (!(wdd->hw_features & WDOG_HW_IS_STOPPABLE)) {
+			/*
+			 * Can't stop it, use a kernel timer to tick
+			 * it until it's open by user space
+			 */
+			schedule_delayed_work(&wdd->work, wdd->hw_heartbeat);
+		}
  	}
  	return 0;
  }
include/linux/watchdog.h +0 -0
index c16c921..6f868b6 100644

include/linux/watchdog.h struct watchdog_device { · line 100

Quoted 7 lines
include/linux/watchdog.h +1 -0
@@ -94,6 +94,7 @@ struct watchdog_device {
  	unsigned int hw_max_timeout; /* in jiffies */
  	unsigned int hw_heartbeat; /* in jiffies */
  	unsigned long int expires; /* for soft timer */
+	unsigned int early_timeout_sec;
  	void *driver_data;
  	struct mutex lock;
  	struct delayed_work work;

Best Regards,
Wenyou Yang

#10 lore raw

PATCH v5 3/4 devicetree: Document generic watchdog properties

In reply to Timo Kokkonen

There is no documentation for the watchdog properties that are common among most of the watchdog drivers. Add document where these generic properties can be described and told how they should be used in drivers.

---
 .../devicetree/bindings/watchdog/watchdog.txt        | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/watchdog/watchdog.txt
Documentation/devicetree/bindings/watchdog/watchdog.txt new file +20 -0
new file mode 100644
index 0000000..3781406
@@ -0,0 +1,20 @@
+These properties are common among most watchdog drivers. Any driver
+that requires the functionality listed below should implement them
+using these definitions.
+
+Optional properties:
+- timeout-sec: Contains the watchdog timeout in seconds.
+- early-timeout-sec: If present, specify the timeout in seconds for
+  how long it can take for the watchdog daemon to take over the
+  watchdog device. If driver supports this property it must ensure the
+  watchdog hardware is running during this period and a watchdog reset
+  must occur if user space fails to open the device in time. If left
+  zero, the driver only needs to guarantee the watchdog is not
+  stopped or is started during driver init.
+
+Example:
+
+watchdog {
+	 timeout-sec = <60>;
+	 early-timeout-sec = <120>;
+};
Signature · 1 line
2.1.0
#11 lore raw

PATCH v5 4/4 watchdog: at91sam9_wdt: Convert to use new watchdog core extensions

In reply to Timo Kokkonen

Fill in the new watchdog core parameters and call watchdog_init_params() to let core know we are ready to support the new core API extensions. This allows the ad hoc timer code to be removed from the driver as the watchdog core takes care of petting the driver as needed.

---
 drivers/watchdog/at91sam9_wdt.c | 58 +++++++++--------------------------------
 1 file changed, 12 insertions(+), 46 deletions(-)
drivers/watchdog/at91sam9_wdt.c +12 -46
index 1443b3c..a30dadb 100644
@@ -29,7 +29,6 @@
 #include <linux/types.h>
 #include <linux/watchdog.h>
 #include <linux/jiffies.h>
-#include <linux/timer.h>
 #include <linux/bitops.h>
 #include <linux/uaccess.h>
 #include <linux/of.h>
@@ -83,11 +82,8 @@ MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
 struct at91wdt {
 	struct watchdog_device wdd;
 	void __iomem *base;
-	unsigned long next_heartbeat;	/* the next_heartbeat for the timer */
-	struct timer_list timer;	/* The timer that pings the watchdog */
 	u32 mr;
 	u32 mr_mask;
-	unsigned long heartbeat;	/* WDT heartbeat in jiffies */
 	bool nowayout;
 	unsigned int irq;
 };
@@ -115,26 +111,11 @@ static inline void at91_wdt_reset(struct at91wdt *wdt)
 	wdt_write(wdt, AT91_WDT_CR, AT91_WDT_KEY | AT91_WDT_WDRSTT);
 }
 
-/*
- * Timer tick
- */
-static void at91_ping(unsigned long data)
-{
-	struct at91wdt *wdt = (struct at91wdt *)data;
-	if (time_before(jiffies, wdt->next_heartbeat) ||
-	    !watchdog_active(&wdt->wdd)) {
-		at91_wdt_reset(wdt);
-		mod_timer(&wdt->timer, jiffies + wdt->heartbeat);
-	} else {
-		pr_crit("I will reset your machine !\n");
-	}
-}
-
 static int at91_wdt_start(struct watchdog_device *wdd)
 {
 	struct at91wdt *wdt = to_wdt(wdd);
-	/* calculate when the next userspace timeout will be */
-	wdt->next_heartbeat = jiffies + wdd->timeout * HZ;
+
+	at91_wdt_reset(wdt);
 	return 0;
 }
 
@@ -146,7 +127,7 @@ static int at91_wdt_stop(struct watchdog_device *wdd)
 
 static int at91_wdt_set_timeout(struct watchdog_device *wdd, unsigned int new_timeout)
 {
-	wdd->timeout = new_timeout;
+	/* The watchdog hardware can't be reconfigured */
 	return at91_wdt_start(wdd);
 }
 
@@ -196,11 +177,11 @@ static int at91_wdt_init(struct platform_device *pdev, struct at91wdt *wdt)
 	 * it at the min_heartbeat period.
 	 */
 	if ((max_heartbeat / 4) >= min_heartbeat)
-		wdt->heartbeat = max_heartbeat / 4;
+		wdt->wdd.hw_heartbeat = max_heartbeat / 4;
 	else if ((max_heartbeat / 2) >= min_heartbeat)
-		wdt->heartbeat = max_heartbeat / 2;
+		wdt->wdd.hw_heartbeat = max_heartbeat / 2;
 	else
-		wdt->heartbeat = min_heartbeat;
+		wdt->wdd.hw_heartbeat = min_heartbeat;
 
 	if (max_heartbeat < min_heartbeat + 4)
 		dev_warn(dev,
@@ -220,32 +201,15 @@ static int at91_wdt_init(struct platform_device *pdev, struct at91wdt *wdt)
 			 "watchdog already configured differently (mr = %x expecting %x)\n",
 			 tmp & wdt->mr_mask, wdt->mr & wdt->mr_mask);
 
-	setup_timer(&wdt->timer, at91_ping, (unsigned long)wdt);
+	wdt->wdd.hw_features = WDOG_HW_RUNNING_AT_BOOT;
+	watchdog_init_params(&wdt->wdd, dev);
 
-	/*
-	 * Use min_heartbeat the first time to avoid spurious watchdog reset:
-	 * we don't know for how long the watchdog counter is running, and
-	 *  - resetting it right now might trigger a watchdog fault reset
-	 *  - waiting for heartbeat time might lead to a watchdog timeout
-	 *    reset
-	 */
-	mod_timer(&wdt->timer, jiffies + min_heartbeat);
-
-	/* Try to set timeout from device tree first */
-	if (watchdog_init_timeout(&wdt->wdd, 0, dev))
-		watchdog_init_timeout(&wdt->wdd, heartbeat, dev);
 	watchdog_set_nowayout(&wdt->wdd, wdt->nowayout);
 	err = watchdog_register_device(&wdt->wdd);
 	if (err)
-		goto out_stop_timer;
-
-	wdt->next_heartbeat = jiffies + wdt->wdd.timeout * HZ;
+		return err;
 
 	return 0;
-
-out_stop_timer:
-	del_timer(&wdt->timer);
-	return err;
 }
 
 /* ......................................................................... */
@@ -287,6 +251,8 @@ static int of_at91wdt_init(struct device_node *np, struct at91wdt *wdt)
 		}
 	}
 
+	wdt->wdd.hw_max_timeout = max * HZ;
+
 	min = secs_to_ticks(min);
 	max = secs_to_ticks(max);
 
@@ -346,6 +312,7 @@ static int __init at91wdt_probe(struct platform_device *pdev)
 	wdt->wdd.timeout = WDT_HEARTBEAT;
 	wdt->wdd.min_timeout = 1;
 	wdt->wdd.max_timeout = 0xFFFF;
+	wdt->wdd.hw_max_timeout = 15 * HZ;
 
 	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 	wdt->base = devm_ioremap_resource(&pdev->dev, r);
@@ -376,7 +343,6 @@ static int __exit at91wdt_remove(struct platform_device *pdev)
 	watchdog_unregister_device(&wdt->wdd);
 
 	pr_warn("I quit now, hardware will probably reboot!\n");
-	del_timer(&wdt->timer);
 
 	return 0;
 }
Signature · 1 line
2.1.0
#12 lore raw

v5 cover watchdog: Extend kernel API and add early_timeout_sec feature

In reply to Timo Kokkonen
Quoted 31 lines · Timo Kokkonen

The watchdog kernel API is quite limited. It has support for providing generic device handling, but it doesn't really know anything about the watchdog hardware or its constraints. The watchdog drivers come with a lot of diversity and their own set of quirks and constraints. Some of their limitations are not nice for the user space, so the drivers work around them with all sorts of ad hoc implementations.

One common pattern is to use kernel timers or work queues to allow longer timeout parameters than the actual hardware supports. To solve this problem, this patch set extends the kernel watchdog API with a few parameters that let the core know more about the watchdog HW and take care about the timeout extending.

The patch set also implements "early_timeout_sec" feature that is very common on many production systems where early kernel or user space crashes must lead to a device reset. Traditional watchdog handling does not allow this as the watchdog is stopped (fully or emulating stopped state with kernel timers) before user space opens it for the first time.

The changes are designed to be taken in use one driver at time. If the driver does not set the new parameters and call watchdog_init_params(), the watchdog behavior is exactly the same as before.

In principle this new API makes it possible for the user space to see every watchdog hardware to behave the same, at least in terms of watchdog timeouts. Once the API is in, it should be easier to move even more common behavior out of the driver code to the watchdog core and make the drivers simpler.

Quoted 8 lines

I'm not anticipating this patch set to go in as is, this is merely to help bringing up the discussion as there is a working patch set to look at. There are propably some corner cases that I didn't get right yet. Also, we could even remove the maximum timeout completely as the HW no longer limits the watchdog timeouts. This propably needs some thinking still and your commenting.

Please review and give feedback.

Tested on the SAMA5D4EK.

Tested-by Wenyou Yang <[email protected]>

Quoted 46 lines

Patch revision history:

(quoted diff) +0 -5
-v5: Re-think the approach to be fully generic. The early_timeout_sec
   handling is no longer in the driver but in the watchdog core. As a
   result the core needed to gain knowledge about the watchdog
   hardware. Appropriate handling is added in the core. The side effect
   for this is that drivers using the new extensions can be simplified
   a lot and different kinds of watchdog hardware can be made to
   behave the same for the user space.
 
-v4: Binding documentation is now separated completely from the driver
   patch. The documentation no longer makes any assumptions about how
   the actual implementation is made, it just describes the actual
   behavior the driver should implement in order to satisfy the
   requirement.
 
- v3: Rename the property to "early-timeout-sec" and use it as a
   timeout value that stops the timer in the atmel driver after the
   timeout expires. A watchdog.txt is also introduced for documenting
   the common watchdog properties, including now this one and
   "timeout-sec" property.
 
- v2: Rename the property to "enable-early-reset" as the behavior
   itself is not atmel specific. This way other drivers are free to
   implement same behavior with the same property name.
 
- v1: Propose property name "atmle,no-early-timer" for disabling the
   timer that keeps the atmel watchdog running until user space opens
   the device.

Timo Kokkonen (4):
watchdog: Extend kernel API to know about HW limitations
watchdog: Allow watchdog to reset device at early boot
devicetree: Document generic watchdog properties
watchdog: at91sam9_wdt: Convert to use new watchdog core extensions

  .../devicetree/bindings/watchdog/watchdog.txt      |  20 ++++
  drivers/watchdog/at91sam9_wdt.c                    |  58 +++--------
  drivers/watchdog/watchdog_core.c                   | 108 ++++++++++++++++++++-
  drivers/watchdog/watchdog_dev.c                    |  12 +++
  include/linux/watchdog.h                           |  24 +++++
  5 files changed, 173 insertions(+), 49 deletions(-)

create mode 100644 Documentation/devicetree/bindings/watchdog/watchdog.txt

#13 lore raw

v5 cover watchdog: Extend kernel API and add early_timeout_sec feature

Quoted 41 lines · Wenyou
Quoted 38 lines · Timo Kokkonen

The watchdog kernel API is quite limited. It has support for providing generic device handling, but it doesn't really know anything about the watchdog hardware or its constraints. The watchdog drivers come with a lot of diversity and their own set of quirks and constraints. Some of their limitations are not nice for the user space, so the drivers work around them with all sorts of ad hoc implementations.

One common pattern is to use kernel timers or work queues to allow longer timeout parameters than the actual hardware supports. To solve this problem, this patch set extends the kernel watchdog API with a few parameters that let the core know more about the watchdog HW and take care about the timeout extending.

The patch set also implements "early_timeout_sec" feature that is very common on many production systems where early kernel or user space crashes must lead to a device reset. Traditional watchdog handling does not allow this as the watchdog is stopped (fully or emulating stopped state with kernel timers) before user space opens it for the first time.

The changes are designed to be taken in use one driver at time. If the driver does not set the new parameters and call watchdog_init_params(), the watchdog behavior is exactly the same as before.

In principle this new API makes it possible for the user space to see every watchdog hardware to behave the same, at least in terms of watchdog timeouts. Once the API is in, it should be easier to move even more common behavior out of the driver code to the watchdog core and make the drivers simpler.

I'm not anticipating this patch set to go in as is, this is merely to help bringing up the discussion as there is a working patch set to look at. There are propably some corner cases that I didn't get right yet. Also, we could even remove the maximum timeout completely as the HW no longer limits the watchdog timeouts. This propably needs some thinking still and your commenting.

Quoted 4 lines
Quoted 1 line

Please review and give feedback.

Tested on the SAMA5D4EK.

Tested-by Wenyou Yang <[email protected]>

Thank you, the typos you pointed will be fixed on the next version.

-Timo

Quoted 49 lines
Quoted 48 lines

Patch revision history:

(quoted diff) +0 -5
-v5: Re-think the approach to be fully generic. The early_timeout_sec
   handling is no longer in the driver but in the watchdog core. As a
   result the core needed to gain knowledge about the watchdog
   hardware. Appropriate handling is added in the core. The side effect
   for this is that drivers using the new extensions can be simplified
   a lot and different kinds of watchdog hardware can be made to
   behave the same for the user space.
 
-v4: Binding documentation is now separated completely from the driver
   patch. The documentation no longer makes any assumptions about how
   the actual implementation is made, it just describes the actual
   behavior the driver should implement in order to satisfy the
   requirement.
 
- v3: Rename the property to "early-timeout-sec" and use it as a
   timeout value that stops the timer in the atmel driver after the
   timeout expires. A watchdog.txt is also introduced for documenting
   the common watchdog properties, including now this one and
   "timeout-sec" property.
 
- v2: Rename the property to "enable-early-reset" as the behavior
   itself is not atmel specific. This way other drivers are free to
   implement same behavior with the same property name.
 
- v1: Propose property name "atmle,no-early-timer" for disabling the
   timer that keeps the atmel watchdog running until user space opens
   the device.

Timo Kokkonen (4):
watchdog: Extend kernel API to know about HW limitations
watchdog: Allow watchdog to reset device at early boot
devicetree: Document generic watchdog properties
watchdog: at91sam9_wdt: Convert to use new watchdog core extensions

  .../devicetree/bindings/watchdog/watchdog.txt      |  20 ++++
  drivers/watchdog/at91sam9_wdt.c                    |  58 +++--------
  drivers/watchdog/watchdog_core.c                   | 108
++++++++++++++++++++-
  drivers/watchdog/watchdog_dev.c                    |  12 +++
  include/linux/watchdog.h                           |  24 +++++
  5 files changed, 173 insertions(+), 49 deletions(-)

create mode 100644
Documentation/devicetree/bindings/watchdog/watchdog.txt