@@ -73,6 +73,14 @@ config WATCHDOG_SYSFS
Say Y here if you want to enable watchdog device status read through
sysfs attributes.
+config WATCHDOG_SOFTWARE_PRETIMEOUT
+ bool "Enable software timer based pretimeout support"
+ help
+ Enable this if you want to use a software timer based pretimeout for
+ watchdogs that do not have pretimeout support. Be aware that because
+ this pretimeout is purely software based, it may not be able to fire
+ before the actual watchdog fires in some situations.
+
comment "Watchdog Pretimeout Governors"
config WATCHDOG_PRETIMEOUT_GOV
@@ -7,6 +7,8 @@
*
* (c) Copyright 2008-2011 Wim Van Sebroeck <wim@iguana.be>.
*
+ * (c) Copyright 2020 Hewlett Packard Enterprise Development LP.
+ *
*
* This source code is part of the generic code that can be used
* by all the watchdog timer drivers.
@@ -64,6 +66,9 @@ struct watchdog_core_data {
ktime_t open_deadline;
struct hrtimer timer;
struct kthread_work work;
+#ifdef CONFIG_WATCHDOG_SOFTWARE_PRETIMEOUT
+ struct hrtimer pretimeout_timer;
+#endif
unsigned long status; /* Internal status bits */
#define _WDOG_DEV_OPEN 0 /* Opened ? */
#define _WDOG_ALLOW_RELEASE 1 /* Did we receive the magic char ? */
@@ -185,6 +190,17 @@ static int __watchdog_ping(struct watchdog_device *wdd)
else
err = wdd->ops->start(wdd); /* restart watchdog */
+#ifdef CONFIG_WATCHDOG_SOFTWARE_PRETIMEOUT
+ if (!(wdd->info->options & WDIOF_PRETIMEOUT) && wdd->pretimeout) {
+ if (err == 0)
+ hrtimer_start(&wd_data->pretimeout_timer,
+ ktime_set(wdd->timeout - wdd->pretimeout, 0),
+ HRTIMER_MODE_REL);
+ } else {
+ hrtimer_cancel(&wd_data->pretimeout_timer);
+ }
+#endif
+
watchdog_update_worker(wdd);
return err;
@@ -250,6 +266,19 @@ static enum hrtimer_restart watchdog_timer_expired(struct hrtimer *timer)
return HRTIMER_NORESTART;
}
+
+#ifdef CONFIG_WATCHDOG_SOFTWARE_PRETIMEOUT
+static enum hrtimer_restart watchdog_software_pretimeout(struct hrtimer *timer)
+{
+ struct watchdog_core_data *wd_data;
+
+ wd_data = container_of(timer, struct watchdog_core_data, pretimeout_timer);
+
+ watchdog_notify_pretimeout(wd_data->wdd);
+ return HRTIMER_NORESTART;
+}
+#endif
+
/*
* watchdog_start: wrapper to start the watchdog.
* @wdd: the watchdog device to start
@@ -287,6 +316,12 @@ static int watchdog_start(struct watchdog_device *wdd)
}
}
+#ifdef CONFIG_WATCHDOG_SOFTWARE_PRETIMEOUT
+ if (!(wdd->info->options & WDIOF_PRETIMEOUT) && wdd->pretimeout)
+ hrtimer_start(&wd_data->pretimeout_timer,
+ ktime_set(wdd->timeout - wdd->pretimeout, 0),
+ HRTIMER_MODE_REL);
+#endif
return err;
}
@@ -325,6 +360,10 @@ static int watchdog_stop(struct watchdog_device *wdd)
if (err == 0) {
clear_bit(WDOG_ACTIVE, &wdd->status);
watchdog_update_worker(wdd);
+
+#ifdef CONFIG_WATCHDOG_SOFTWARE_PRETIMEOUT
+ hrtimer_cancel(&wdd->wd_data->pretimeout_timer);
+#endif
}
return err;
@@ -361,6 +400,9 @@ static unsigned int watchdog_get_status(struct watchdog_device *wdd)
if (test_and_clear_bit(_WDOG_KEEPALIVE, &wd_data->status))
status |= WDIOF_KEEPALIVEPING;
+ if (IS_ENABLED(CONFIG_WATCHDOG_SOFTWARE_PRETIMEOUT))
+ status |= WDIOF_PRETIMEOUT;
+
return status;
}
@@ -408,7 +450,8 @@ static int watchdog_set_pretimeout(struct watchdog_device *wdd,
{
int err = 0;
- if (!(wdd->info->options & WDIOF_PRETIMEOUT))
+ if (!(wdd->info->options & WDIOF_PRETIMEOUT) &&
+ !IS_ENABLED(CONFIG_WATCHDOG_SOFTWARE_PRETIMEOUT))
return -EOPNOTSUPP;
if (watchdog_pretimeout_invalid(wdd, timeout))
@@ -595,12 +638,14 @@ static umode_t wdt_is_visible(struct kobject *kobj, struct attribute *attr,
if (attr == &dev_attr_timeleft.attr && !wdd->ops->get_timeleft)
mode = 0;
else if (attr == &dev_attr_pretimeout.attr &&
- !(wdd->info->options & WDIOF_PRETIMEOUT))
+ !(wdd->info->options & WDIOF_PRETIMEOUT) &&
+ !IS_ENABLED(CONFIG_WATCHDOG_SOFTWARE_PRETIMEOUT))
mode = 0;
else if ((attr == &dev_attr_pretimeout_governor.attr ||
attr == &dev_attr_pretimeout_available_governors.attr) &&
- (!(wdd->info->options & WDIOF_PRETIMEOUT) ||
- !IS_ENABLED(CONFIG_WATCHDOG_PRETIMEOUT_GOV)))
+ ((!(wdd->info->options & WDIOF_PRETIMEOUT) &&
+ !IS_ENABLED(CONFIG_WATCHDOG_SOFTWARE_PRETIMEOUT)) ||
+ !IS_ENABLED(CONFIG_WATCHDOG_PRETIMEOUT_GOV)))
mode = 0;
return mode;
@@ -1010,6 +1055,11 @@ static int watchdog_cdev_register(struct watchdog_device *wdd)
hrtimer_init(&wd_data->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_HARD);
wd_data->timer.function = watchdog_timer_expired;
+#ifdef CONFIG_WATCHDOG_SOFTWARE_PRETIMEOUT
+ hrtimer_init(&wd_data->pretimeout_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ wd_data->pretimeout_timer.function = watchdog_software_pretimeout;
+#endif
+
if (wdd->id == 0) {
old_wd_data = wd_data;
watchdog_miscdev.parent = wdd->parent;
@@ -1097,6 +1147,10 @@ static void watchdog_cdev_unregister(struct watchdog_device *wdd)
hrtimer_cancel(&wd_data->timer);
kthread_cancel_work_sync(&wd_data->work);
+#ifdef CONFIG_WATCHDOG_SOFTWARE_PRETIMEOUT
+ hrtimer_cancel(&wd_data->pretimeout_timer);
+#endif
+
put_device(&wd_data->dev);
}
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2015-2016 Mentor Graphics
+ * Copyright (C) 2020 Hewlett Packard Enterprise Development LP.
*/
#include <linux/list.h>
@@ -177,7 +178,8 @@ int watchdog_register_pretimeout(struct watchdog_device *wdd)
{
struct watchdog_pretimeout *p;
- if (!(wdd->info->options & WDIOF_PRETIMEOUT))
+ if (!(wdd->info->options & WDIOF_PRETIMEOUT) &&
+ !IS_ENABLED(CONFIG_WATCHDOG_SOFTWARE_PRETIMEOUT))
return 0;
p = kzalloc(sizeof(*p), GFP_KERNEL);
@@ -197,7 +199,8 @@ void watchdog_unregister_pretimeout(struct watchdog_device *wdd)
{
struct watchdog_pretimeout *p, *t;
- if (!(wdd->info->options & WDIOF_PRETIMEOUT))
+ if (!(wdd->info->options & WDIOF_PRETIMEOUT) &&
+ !IS_ENABLED(CONFIG_WATCHDOG_SOFTWARE_PRETIMEOUT))
return;
spin_lock_irq(&pretimeout_lock);
This adds the option to use a software timer to generate a watchdog pretimeout event for hardware watchdogs that do not support the pretimeout feature. With this enabled, all watchdogs will appear to have pretimeout support in userspace. If no pretimeout value is set, there will be no change in the watchdog's behavior. If a pretimeout value is set for a specific watchdog that does not have built-in pretimeout support, a timer will be started that should fire at the specified time before the watchdog timeout would occur. When the watchdog is successfully pinged, the timer will be restarted. If the timer is allowed to fire it will generate a pretimeout event. If the watchdog does support a pretimeout natively, that functionality will be used instead of the software timer. Signed-off-by: Curtis Klein <curtis.klein@hpe.com> --- drivers/watchdog/Kconfig | 8 +++++ drivers/watchdog/watchdog_dev.c | 62 +++++++++++++++++++++++++++++++--- drivers/watchdog/watchdog_pretimeout.c | 7 ++-- 3 files changed, 71 insertions(+), 6 deletions(-)