Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

watchdog: Add hrtimer-based pretimeout feature

This adds the option to use a hrtimer to generate a watchdog pretimeout
event for hardware watchdogs that do not natively support watchdog
pretimeouts.

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. However because a software timer is used, it may not
be able to fire in every circumstance.

If the watchdog does support a pretimeout natively, that functionality
will be used instead of the hrtimer.

The general design of this feaure was inspired by the software watchdog,
specifically its own pretimeout implementation. However the software
watchdog and this feature are completely independent. They can be used
together; with or without CONFIG_SOFT_WATCHDOG_PRETIMEOUT enabled.

The main advantage of using the hrtimer pretimeout with a hardware
watchdog, compared to running the software watchdog with a hardware
watchdog, is that if the hardware watchdog driver is unable to ping the
watchdog (e.g. due to a bus or communication error), then the hrtimer
pretimeout would still fire whereas the software watchdog would not.

Signed-off-by: Curtis Klein <curtis.klein@hpe.com>
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
Link: https://lore.kernel.org/r/1612383090-27110-1-git-send-email-curtis.klein@hpe.com
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Wim Van Sebroeck <wim@linux-watchdog.org>

authored by

Curtis Klein and committed by
Wim Van Sebroeck
7b7d2fdc e1138cef

+121 -32
+8
drivers/watchdog/Kconfig
··· 73 73 Say Y here if you want to enable watchdog device status read through 74 74 sysfs attributes. 75 75 76 + config WATCHDOG_HRTIMER_PRETIMEOUT 77 + bool "Enable watchdog hrtimer-based pretimeouts" 78 + help 79 + Enable this if you want to use a hrtimer timer based pretimeout for 80 + watchdogs that do not natively support pretimeout support. Be aware 81 + that because this pretimeout functionality uses hrtimers, it may not 82 + be able to fire before the actual watchdog fires in some situations. 83 + 76 84 comment "Watchdog Pretimeout Governors" 77 85 78 86 config WATCHDOG_PRETIMEOUT_GOV
+1
drivers/watchdog/Makefile
··· 9 9 watchdog-objs += watchdog_core.o watchdog_dev.o 10 10 11 11 watchdog-$(CONFIG_WATCHDOG_PRETIMEOUT_GOV) += watchdog_pretimeout.o 12 + watchdog-$(CONFIG_WATCHDOG_HRTIMER_PRETIMEOUT) += watchdog_hrtimer_pretimeout.o 12 13 13 14 obj-$(CONFIG_WATCHDOG_PRETIMEOUT_GOV_NOOP) += pretimeout_noop.o 14 15 obj-$(CONFIG_WATCHDOG_PRETIMEOUT_GOV_PANIC) += pretimeout_panic.o
+48
drivers/watchdog/watchdog_core.h
··· 7 7 * 8 8 * (c) Copyright 2008-2011 Wim Van Sebroeck <wim@iguana.be>. 9 9 * 10 + * (c) Copyright 2021 Hewlett Packard Enterprise Development LP. 11 + * 10 12 * This source code is part of the generic code that can be used 11 13 * by all the watchdog timer drivers. 12 14 * ··· 24 22 * This material is provided "AS-IS" and at no charge. 25 23 */ 26 24 25 + #include <linux/hrtimer.h> 26 + #include <linux/kthread.h> 27 + 27 28 #define MAX_DOGS 32 /* Maximum number of watchdog devices */ 29 + 30 + /* 31 + * struct watchdog_core_data - watchdog core internal data 32 + * @dev: The watchdog's internal device 33 + * @cdev: The watchdog's Character device. 34 + * @wdd: Pointer to watchdog device. 35 + * @lock: Lock for watchdog core. 36 + * @status: Watchdog core internal status bits. 37 + */ 38 + struct watchdog_core_data { 39 + struct device dev; 40 + struct cdev cdev; 41 + struct watchdog_device *wdd; 42 + struct mutex lock; 43 + ktime_t last_keepalive; 44 + ktime_t last_hw_keepalive; 45 + ktime_t open_deadline; 46 + struct hrtimer timer; 47 + struct kthread_work work; 48 + #if IS_ENABLED(CONFIG_WATCHDOG_HRTIMER_PRETIMEOUT) 49 + struct hrtimer pretimeout_timer; 50 + #endif 51 + unsigned long status; /* Internal status bits */ 52 + #define _WDOG_DEV_OPEN 0 /* Opened ? */ 53 + #define _WDOG_ALLOW_RELEASE 1 /* Did we receive the magic char ? */ 54 + #define _WDOG_KEEPALIVE 2 /* Did we receive a keepalive ? */ 55 + }; 28 56 29 57 /* 30 58 * Functions/procedures to be called by the core ··· 63 31 extern void watchdog_dev_unregister(struct watchdog_device *); 64 32 extern int __init watchdog_dev_init(void); 65 33 extern void __exit watchdog_dev_exit(void); 34 + 35 + static inline bool watchdog_have_pretimeout(struct watchdog_device *wdd) 36 + { 37 + return wdd->info->options & WDIOF_PRETIMEOUT || 38 + IS_ENABLED(CONFIG_WATCHDOG_HRTIMER_PRETIMEOUT); 39 + } 40 + 41 + #if IS_ENABLED(CONFIG_WATCHDOG_HRTIMER_PRETIMEOUT) 42 + void watchdog_hrtimer_pretimeout_init(struct watchdog_device *wdd); 43 + void watchdog_hrtimer_pretimeout_start(struct watchdog_device *wdd); 44 + void watchdog_hrtimer_pretimeout_stop(struct watchdog_device *wdd); 45 + #else 46 + static inline void watchdog_hrtimer_pretimeout_init(struct watchdog_device *wdd) {} 47 + static inline void watchdog_hrtimer_pretimeout_start(struct watchdog_device *wdd) {} 48 + static inline void watchdog_hrtimer_pretimeout_stop(struct watchdog_device *wdd) {} 49 + #endif
+17 -30
drivers/watchdog/watchdog_dev.c
··· 7 7 * 8 8 * (c) Copyright 2008-2011 Wim Van Sebroeck <wim@iguana.be>. 9 9 * 10 + * (c) Copyright 2021 Hewlett Packard Enterprise Development LP. 10 11 * 11 12 * This source code is part of the generic code that can be used 12 13 * by all the watchdog timer drivers. ··· 46 45 47 46 #include "watchdog_core.h" 48 47 #include "watchdog_pretimeout.h" 49 - 50 - /* 51 - * struct watchdog_core_data - watchdog core internal data 52 - * @dev: The watchdog's internal device 53 - * @cdev: The watchdog's Character device. 54 - * @wdd: Pointer to watchdog device. 55 - * @lock: Lock for watchdog core. 56 - * @status: Watchdog core internal status bits. 57 - */ 58 - struct watchdog_core_data { 59 - struct device dev; 60 - struct cdev cdev; 61 - struct watchdog_device *wdd; 62 - struct mutex lock; 63 - ktime_t last_keepalive; 64 - ktime_t last_hw_keepalive; 65 - ktime_t open_deadline; 66 - struct hrtimer timer; 67 - struct kthread_work work; 68 - unsigned long status; /* Internal status bits */ 69 - #define _WDOG_DEV_OPEN 0 /* Opened ? */ 70 - #define _WDOG_ALLOW_RELEASE 1 /* Did we receive the magic char ? */ 71 - #define _WDOG_KEEPALIVE 2 /* Did we receive a keepalive ? */ 72 - }; 73 48 74 49 /* the dev_t structure to store the dynamically allocated watchdog devices */ 75 50 static dev_t watchdog_devt; ··· 162 185 else 163 186 err = wdd->ops->start(wdd); /* restart watchdog */ 164 187 188 + if (err == 0) 189 + watchdog_hrtimer_pretimeout_start(wdd); 190 + 165 191 watchdog_update_worker(wdd); 166 192 167 193 return err; ··· 255 275 started_at = ktime_get(); 256 276 if (watchdog_hw_running(wdd) && wdd->ops->ping) { 257 277 err = __watchdog_ping(wdd); 258 - if (err == 0) 278 + if (err == 0) { 259 279 set_bit(WDOG_ACTIVE, &wdd->status); 280 + watchdog_hrtimer_pretimeout_start(wdd); 281 + } 260 282 } else { 261 283 err = wdd->ops->start(wdd); 262 284 if (err == 0) { ··· 266 284 wd_data->last_keepalive = started_at; 267 285 wd_data->last_hw_keepalive = started_at; 268 286 watchdog_update_worker(wdd); 287 + watchdog_hrtimer_pretimeout_start(wdd); 269 288 } 270 289 } 271 290 ··· 308 325 if (err == 0) { 309 326 clear_bit(WDOG_ACTIVE, &wdd->status); 310 327 watchdog_update_worker(wdd); 328 + watchdog_hrtimer_pretimeout_stop(wdd); 311 329 } 312 330 313 331 return err; ··· 344 360 345 361 if (test_and_clear_bit(_WDOG_KEEPALIVE, &wd_data->status)) 346 362 status |= WDIOF_KEEPALIVEPING; 363 + 364 + if (IS_ENABLED(CONFIG_WATCHDOG_HRTIMER_PRETIMEOUT)) 365 + status |= WDIOF_PRETIMEOUT; 347 366 348 367 return status; 349 368 } ··· 395 408 { 396 409 int err = 0; 397 410 398 - if (!(wdd->info->options & WDIOF_PRETIMEOUT)) 411 + if (!watchdog_have_pretimeout(wdd)) 399 412 return -EOPNOTSUPP; 400 413 401 414 if (watchdog_pretimeout_invalid(wdd, timeout)) ··· 600 613 601 614 if (attr == &dev_attr_timeleft.attr && !wdd->ops->get_timeleft) 602 615 mode = 0; 603 - else if (attr == &dev_attr_pretimeout.attr && 604 - !(wdd->info->options & WDIOF_PRETIMEOUT)) 616 + else if (attr == &dev_attr_pretimeout.attr && !watchdog_have_pretimeout(wdd)) 605 617 mode = 0; 606 618 else if ((attr == &dev_attr_pretimeout_governor.attr || 607 619 attr == &dev_attr_pretimeout_available_governors.attr) && 608 - (!(wdd->info->options & WDIOF_PRETIMEOUT) || 609 - !IS_ENABLED(CONFIG_WATCHDOG_PRETIMEOUT_GOV))) 620 + (!watchdog_have_pretimeout(wdd) || !IS_ENABLED(CONFIG_WATCHDOG_PRETIMEOUT_GOV))) 610 621 mode = 0; 611 622 612 623 return mode; ··· 1015 1030 kthread_init_work(&wd_data->work, watchdog_ping_work); 1016 1031 hrtimer_init(&wd_data->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_HARD); 1017 1032 wd_data->timer.function = watchdog_timer_expired; 1033 + watchdog_hrtimer_pretimeout_init(wdd); 1018 1034 1019 1035 if (wdd->id == 0) { 1020 1036 old_wd_data = wd_data; ··· 1103 1117 1104 1118 hrtimer_cancel(&wd_data->timer); 1105 1119 kthread_cancel_work_sync(&wd_data->work); 1120 + watchdog_hrtimer_pretimeout_stop(wdd); 1106 1121 1107 1122 put_device(&wd_data->dev); 1108 1123 }
+44
drivers/watchdog/watchdog_hrtimer_pretimeout.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * (c) Copyright 2021 Hewlett Packard Enterprise Development LP. 4 + */ 5 + 6 + #include <linux/hrtimer.h> 7 + #include <linux/watchdog.h> 8 + 9 + #include "watchdog_core.h" 10 + #include "watchdog_pretimeout.h" 11 + 12 + static enum hrtimer_restart watchdog_hrtimer_pretimeout(struct hrtimer *timer) 13 + { 14 + struct watchdog_core_data *wd_data; 15 + 16 + wd_data = container_of(timer, struct watchdog_core_data, pretimeout_timer); 17 + 18 + watchdog_notify_pretimeout(wd_data->wdd); 19 + return HRTIMER_NORESTART; 20 + } 21 + 22 + void watchdog_hrtimer_pretimeout_init(struct watchdog_device *wdd) 23 + { 24 + struct watchdog_core_data *wd_data = wdd->wd_data; 25 + 26 + hrtimer_init(&wd_data->pretimeout_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); 27 + wd_data->pretimeout_timer.function = watchdog_hrtimer_pretimeout; 28 + } 29 + 30 + void watchdog_hrtimer_pretimeout_start(struct watchdog_device *wdd) 31 + { 32 + if (!(wdd->info->options & WDIOF_PRETIMEOUT) && 33 + !watchdog_pretimeout_invalid(wdd, wdd->pretimeout)) 34 + hrtimer_start(&wdd->wd_data->pretimeout_timer, 35 + ktime_set(wdd->timeout - wdd->pretimeout, 0), 36 + HRTIMER_MODE_REL); 37 + else 38 + hrtimer_cancel(&wdd->wd_data->pretimeout_timer); 39 + } 40 + 41 + void watchdog_hrtimer_pretimeout_stop(struct watchdog_device *wdd) 42 + { 43 + hrtimer_cancel(&wdd->wd_data->pretimeout_timer); 44 + }
+3 -2
drivers/watchdog/watchdog_pretimeout.c
··· 9 9 #include <linux/string.h> 10 10 #include <linux/watchdog.h> 11 11 12 + #include "watchdog_core.h" 12 13 #include "watchdog_pretimeout.h" 13 14 14 15 /* Default watchdog pretimeout governor */ ··· 178 177 { 179 178 struct watchdog_pretimeout *p; 180 179 181 - if (!(wdd->info->options & WDIOF_PRETIMEOUT)) 180 + if (!watchdog_have_pretimeout(wdd)) 182 181 return 0; 183 182 184 183 p = kzalloc(sizeof(*p), GFP_KERNEL); ··· 198 197 { 199 198 struct watchdog_pretimeout *p, *t; 200 199 201 - if (!(wdd->info->options & WDIOF_PRETIMEOUT)) 200 + if (!watchdog_have_pretimeout(wdd)) 202 201 return; 203 202 204 203 spin_lock_irq(&pretimeout_lock);