[PATCH v5 1/2] drivers: watchdog: Introduce watchdog reset timeout on panic

From: George Cherian
Date: Wed Apr 09 2025 - 01:35:56 EST


In a kernel panic situation followed by loading of kdump kernel, it can
so happen that watchdog reset can happen while booting the kdump kernel.
Add a provision to configure/update the watchdog timeout in case of
panic.
This is acheived by:
1. Provide a sysfs entry to configure the timeout (reset_on_panic) in
case of kernel panic.
2. reset_on_panic takes time in seconds. If set to 0 then the watchdog
is disarmed completely.
3. Register a panic notifer and update the watchdog timeout in case of
panic.
4. Introduce a new flag to watchdog drivers to set whether ops are
atmoic.
The above feature cannot be supported to watchdog drivers which sleeps
during set_timeout or stop.

Signed-off-by: George Cherian <george.cherian@xxxxxxxxxxx>
---
drivers/watchdog/watchdog_core.c | 33 ++++++++++++++++++++++++++++++++
drivers/watchdog/watchdog_dev.c | 28 +++++++++++++++++++++++++++
include/linux/watchdog.h | 3 +++
include/uapi/linux/watchdog.h | 1 +
4 files changed, 65 insertions(+)

diff --git a/drivers/watchdog/watchdog_core.c b/drivers/watchdog/watchdog_core.c
index 6152dba4b52c..7ec46dfe9888 100644
--- a/drivers/watchdog/watchdog_core.c
+++ b/drivers/watchdog/watchdog_core.c
@@ -35,6 +35,7 @@
#include <linux/err.h> /* For IS_ERR macros */
#include <linux/of.h> /* For of_alias_get_id */
#include <linux/property.h> /* For device_property_read_u32 */
+#include <linux/panic_notifier.h> /* For panic handler */
#include <linux/suspend.h>

#include "watchdog_core.h" /* For watchdog_dev_register/... */
@@ -155,6 +156,28 @@ int watchdog_init_timeout(struct watchdog_device *wdd,
}
EXPORT_SYMBOL_GPL(watchdog_init_timeout);

+static int watchdog_panic_notify(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ int ret;
+ struct watchdog_device *wdd;
+
+ wdd = container_of(nb, struct watchdog_device, panic_nb);
+ if (watchdog_active(wdd)) {
+ if (!wdd->reset_on_panic) {
+ ret = wdd->ops->stop(wdd);
+ if (ret)
+ return NOTIFY_BAD;
+ } else {
+ ret = wdd->ops->set_timeout(wdd, wdd->reset_on_panic);
+ if (ret)
+ return NOTIFY_BAD;
+ }
+ }
+
+ return NOTIFY_DONE;
+}
+
static int watchdog_reboot_notifier(struct notifier_block *nb,
unsigned long code, void *data)
{
@@ -334,6 +357,13 @@ static int ___watchdog_register_device(struct watchdog_device *wdd)
wdd->id, ret);
}

+ if (wdd->info->options & WDIOF_OPS_ATOMIC) {
+ wdd->panic_nb.notifier_call = watchdog_panic_notify;
+ atomic_notifier_chain_register(&panic_notifier_list,
+ &wdd->panic_nb);
+ set_bit(WDOG_RESET_ON_PANIC, &wdd->status);
+ }
+
return 0;
}

@@ -390,6 +420,9 @@ static void __watchdog_unregister_device(struct watchdog_device *wdd)
if (test_bit(WDOG_STOP_ON_REBOOT, &wdd->status))
unregister_reboot_notifier(&wdd->reboot_nb);

+ if (test_bit(WDOG_RESET_ON_PANIC, &wdd->status))
+ atomic_notifier_chain_unregister(&panic_notifier_list,
+ &wdd->panic_nb);
watchdog_dev_unregister(wdd);
ida_free(&watchdog_ida, wdd->id);
}
diff --git a/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c
index 8369fd94fc1a..89cc9b681992 100644
--- a/drivers/watchdog/watchdog_dev.c
+++ b/drivers/watchdog/watchdog_dev.c
@@ -617,6 +617,33 @@ static ssize_t pretimeout_governor_store(struct device *dev,
}
static DEVICE_ATTR_RW(pretimeout_governor);

+static ssize_t reset_on_panic_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct watchdog_device *wdd = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%u\n", wdd->reset_on_panic);
+}
+
+static ssize_t reset_on_panic_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct watchdog_device *wdd = dev_get_drvdata(dev);
+ unsigned int value;
+ int ret;
+
+ ret = kstrtouint(buf, 0, &value);
+ if (ret)
+ return ret;
+
+ wdd->reset_on_panic = value;
+
+ return count;
+}
+static DEVICE_ATTR_RW(reset_on_panic);
+
static umode_t wdt_is_visible(struct kobject *kobj, struct attribute *attr,
int n)
{
@@ -648,6 +675,7 @@ static struct attribute *wdt_attrs[] = {
&dev_attr_bootstatus.attr,
&dev_attr_status.attr,
&dev_attr_nowayout.attr,
+ &dev_attr_reset_on_panic.attr,
&dev_attr_pretimeout_governor.attr,
&dev_attr_pretimeout_available_governors.attr,
NULL,
diff --git a/include/linux/watchdog.h b/include/linux/watchdog.h
index 99660197a36c..8783edf93bab 100644
--- a/include/linux/watchdog.h
+++ b/include/linux/watchdog.h
@@ -105,9 +105,11 @@ struct watchdog_device {
unsigned int max_timeout;
unsigned int min_hw_heartbeat_ms;
unsigned int max_hw_heartbeat_ms;
+ unsigned int reset_on_panic;
struct notifier_block reboot_nb;
struct notifier_block restart_nb;
struct notifier_block pm_nb;
+ struct notifier_block panic_nb;
void *driver_data;
struct watchdog_core_data *wd_data;
unsigned long status;
@@ -118,6 +120,7 @@ struct watchdog_device {
#define WDOG_HW_RUNNING 3 /* True if HW watchdog running */
#define WDOG_STOP_ON_UNREGISTER 4 /* Should be stopped on unregister */
#define WDOG_NO_PING_ON_SUSPEND 5 /* Ping worker should be stopped on suspend */
+#define WDOG_RESET_ON_PANIC 6 /* Reset the watchdog on panic for loading kdump kernels */
struct list_head deferred;
};

diff --git a/include/uapi/linux/watchdog.h b/include/uapi/linux/watchdog.h
index b15cde5c9054..4e19cc653a72 100644
--- a/include/uapi/linux/watchdog.h
+++ b/include/uapi/linux/watchdog.h
@@ -48,6 +48,7 @@ struct watchdog_info {
#define WDIOF_PRETIMEOUT 0x0200 /* Pretimeout (in seconds), get/set */
#define WDIOF_ALARMONLY 0x0400 /* Watchdog triggers a management or
other external alarm not a reboot */
+#define WDIOF_OPS_ATOMIC 0x0800 /* Indicate whether watchdog ops will sleep or not */
#define WDIOF_KEEPALIVEPING 0x8000 /* Keep alive ping reply */

#define WDIOS_DISABLECARD 0x0001 /* Turn off the watchdog timer */
--
2.34.1