diff mbox series

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

Message ID 20250409053452.3173447-2-george.cherian@marvell.com
State New
Headers show
Series Add reset_on_panic support for watchdog | expand

Commit Message

George Cherian April 9, 2025, 5:34 a.m. UTC
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@marvell.com>
---
 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 mbox series

Patch

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 */