Watchdog Governors and GPIO Watchdogs-Free Linux Device Drivers Course

PREV_LEC | NEXT_LEC

Watchdog Governors and GPIO Watchdogs
A hands-on lecture in EmbeddedPathashala’s free Linux kernel development course
Chapter 13 · Lecture 4
Watchdog Subsystem
Kernel 6.x

In the previous lectures of this free linux device drivers course we built a complete platform watchdog driver, registered it with devm_watchdog_register_device(), and wired up a restart handler. A production watchdog driver rarely stops there. Two more pieces show up constantly in real SoC BSPs: a way to warn software just before the watchdog actually fires (the pretimeout mechanism, handled by a linux watchdog governor), and a way to build a watchdog entirely out of a GPIO line when the SoC has no dedicated watchdog hardware, or when an external supervisory chip is preferred for safety reasons. This lecture, part of our free embedded linux course, covers both in depth with original, from-scratch code.

Keywords covered in this lecture

watchdog_notify_pretimeout linux watchdog governor struct watchdog_governor CONFIG_WATCHDOG_PRETIMEOUT_GOV gpio-wdt device tree pretimeout IRQ handler

What You Will Learn

  • What a pretimeout is and why it exists
  • How a linux watchdog governor plugs into the pretimeout path
  • The noop and panic governor drivers shipped in-tree
  • Writing an IRQ handler that calls watchdog_notify_pretimeout()
  • How GPIO-based watchdogs work end to end
  • Writing a gpio-wdt device tree binding correctly
  • Common mistakes when configuring pretimeouts and GPIO watchdogs

Prerequisites

This lecture assumes you’ve been following this free linux kernel development course through the earlier watchdog lectures. You should be comfortable with:

  • struct watchdog_device and struct watchdog_ops
  • watchdog_register_device() / devm_watchdog_register_device()
  • Basic device tree node and property syntax
  • Threaded IRQ handlers (request_threaded_irq)

Why a Watchdog Needs a Pretimeout

A plain watchdog is binary: either user space pings it in time, or the SoC resets. That’s fine for recovering from a hang, but it throws away diagnostic information — by the time the reset happens, whatever state explained why the system hung is gone. A pretimeout closes that gap. Some watchdog hardware can raise an interrupt a configurable interval before the real timeout expires. That interrupt is the system’s last chance to do something useful: dump a stack trace, flush a log to persistent storage, or deliberately panic so a kernel crash dump captures the failure.

The kernel doesn’t hard-code what “something useful” means. That decision is delegated to a pluggable policy driver — a linux watchdog governor — the same architectural pattern the kernel already uses for CPUFreq governors and thermal governors. A governor is identified by a short unique name, and the currently active governor can be swapped through sysfs without reloading the watchdog driver itself.

The Watchdog Governor Framework

Governor support is gated behind CONFIG_WATCHDOG_PRETIMEOUT_GOV. With it enabled, the kernel ships two governor drivers out of the box:

Governor nameSource fileBehavior on pretimeout
noopdrivers/watchdog/pretimeout_noop.cDoes nothing beyond logging — useful as a safe default
panicdrivers/watchdog/pretimeout_noop.c (panic variant)Forces an immediate kernel panic so a crash dump is captured

One of the two can be selected as the system-wide default via CONFIG_WATCHDOG_PRETIMEOUT_DEFAULT_GOV_NOOP or CONFIG_WATCHDOG_PRETIMEOUT_DEFAULT_GOV_PANIC. Every governor implements the same small interface:

struct watchdog_governor {
    const char name[WATCHDOG_GOV_NAME_MAXLEN];
    void (*pretimeout)(struct watchdog_device *wdd);
};

The kernel keeps a global registry of governors by name. When a driver’s pretimeout IRQ fires, it doesn’t call a governor directly — it calls a single generic entry point:

void watchdog_notify_pretimeout(struct watchdog_device *wdd);

Internally, this looks up whichever governor is currently attached to that watchdog_device and invokes its .pretimeout callback. Your driver’s only job is (1) advertise pretimeout support and (2) call this function from the right place — the actual policy decision is entirely the governor’s concern.

Pretimeout Notification Flow
Hardware pretimeout IRQ fires | v Driver’s threaded IRQ handler | v watchdog_notify_pretimeout(wdd) | v Kernel looks up active governor by name | v governor->pretimeout(wdd) runs (noop: log only | panic: crash the kernel)

Advertising Pretimeout Support

A watchdog driver tells the framework it supports pretimeouts through the .options field of its watchdog_info structure, using the WDIOF_PRETIMEOUT flag alongside the WDIOF_SETTIMEOUT flag we covered earlier in this course. Many real drivers keep two separate watchdog_info structures and pick one at probe time depending on whether a pretimeout-capable IRQ line was actually found in the device tree — there’s no point advertising a capability the hardware instance in front of you doesn’t have.

Original Demo: An hrtimer-Based Pretimeout Driver

Real pretimeout IRQs come from hardware, which makes them awkward to demo without a board. To keep this runnable on any machine, ep_pretimeout_wdt simulates a pretimeout using a second, shorter hrtimer that fires before the main watchdog timeout — the notification path to the governor framework is identical to what a real IRQ handler would do.

// ep_pretimeout_wdt.c
#include <linux/module.h>
#include <linux/watchdog.h>
#include <linux/hrtimer.h>
#include <linux/platform_device.h>

struct ep_pt_wdt {
    struct watchdog_device wdd;
    struct hrtimer pretimeout_timer;
};

static enum hrtimer_restart ep_pt_fire(struct hrtimer *t)
{
    struct ep_pt_wdt *pt = container_of(t, struct ep_pt_wdt,
                                         pretimeout_timer);

    dev_warn(pt->wdd.parent, "ep_pretimeout_wdt: pretimeout reached\n");
    watchdog_notify_pretimeout(&pt->wdd);

    return HRTIMER_NORESTART;
}

static int ep_pt_start(struct watchdog_device *wdd)
{
    struct ep_pt_wdt *pt = watchdog_get_drvdata(wdd);
    ktime_t pretime = ms_to_ktime((wdd->timeout - wdd->pretimeout) * 1000);

    hrtimer_start(&pt->pretimeout_timer, pretime, HRTIMER_MODE_REL);
    return 0;
}

static int ep_pt_stop(struct watchdog_device *wdd)
{
    struct ep_pt_wdt *pt = watchdog_get_drvdata(wdd);

    hrtimer_cancel(&pt->pretimeout_timer);
    return 0;
}

static const struct watchdog_ops ep_pt_ops = {
    .owner = THIS_MODULE,
    .start = ep_pt_start,
    .stop  = ep_pt_stop,
};

static const struct watchdog_info ep_pt_info = {
    .options  = WDIOF_SETTIMEOUT | WDIOF_PRETIMEOUT | WDIOF_KEEPALIVEPING,
    .identity = "ep_pretimeout_wdt",
};

static int ep_pt_probe(struct platform_device *pdev)
{
    struct ep_pt_wdt *pt;

    pt = devm_kzalloc(&pdev->dev, sizeof(*pt), GFP_KERNEL);
    if (!pt)
        return -ENOMEM;

    hrtimer_init(&pt->pretimeout_timer, CLOCK_MONOTONIC,
                 HRTIMER_MODE_REL);
    pt->pretimeout_timer.function = ep_pt_fire;

    pt->wdd.info      = &ep_pt_info;
    pt->wdd.ops       = &ep_pt_ops;
    pt->wdd.timeout   = 20;
    pt->wdd.pretimeout = 5;
    pt->wdd.parent    = &pdev->dev;

    watchdog_set_drvdata(&pt->wdd, pt);
    watchdog_set_nowayout(&pt->wdd, 0);

    return devm_watchdog_register_device(&pdev->dev, &pt->wdd);
}

static struct platform_driver ep_pt_driver = {
    .driver = { .name = "ep_pretimeout_wdt" },
    .probe  = ep_pt_probe,
};

static struct platform_device *ep_pt_pdev;

static int __init ep_pt_init(void)
{
    int ret = platform_driver_register(&ep_pt_driver);

    if (ret)
        return ret;

    ep_pt_pdev = platform_device_register_simple("ep_pretimeout_wdt",
                                                   -1, NULL, 0);
    return PTR_ERR_OR_ZERO(ep_pt_pdev);
}

static void __exit ep_pt_exit(void)
{
    platform_device_unregister(ep_pt_pdev);
    platform_driver_unregister(&ep_pt_driver);
}

module_init(ep_pt_init);
module_exit(ep_pt_exit);
MODULE_LICENSE("GPL");

Build, load, start the watchdog, and watch dmesg:

$ make
$ sudo insmod ep_pretimeout_wdt.ko
$ echo panic | sudo tee /sys/class/watchdog/watchdog0/pretimeout_governor
$ sudo watchdog-simple-ping /dev/watchdog0 &

$ dmesg -w
[  102.441002] ep_pretimeout_wdt: pretimeout reached
[  102.441210] Kernel panic - not syncing: watchdog pretimeout governor: panic

Switching the pretimeout_governor sysfs file to noop instead of panic makes the same driver only log the warning and keep running — exactly the “changed on the fly, most often through sysfs” behavior the governor framework was built for.

GPIO-Based Watchdogs

Not every SoC has an internal watchdog worth using. Some internal watchdog blocks draw more standby power than the rest of the design can spare; other times a board simply wants a watchdog that lives entirely outside the SoC, so a firmware bug in the SoC itself can never disable it. The common solution is an external supervisory IC that resets the board unless it’s “pinged” by toggling one GPIO line on a schedule.

The kernel exposes this through CONFIG_GPIO_WATCHDOG and the drivers/watchdog/gpio_wdt.c driver. Instead of talking to the GPIO directly from user space — which the kernel doesn’t want you doing for something safety-critical — this driver gives the same standard /dev/watchdog interface as any other watchdog, and translates writes into GPIO toggles underneath. There is no platform-data registration path for it; it is device-tree-only.

Two toggle strategies are supported, selected by the hw_algo device tree property:

hw_algo valueHow the watchdog is pingedIdle-line behavior
toggleLine flips 1 → 0 → 1 on every pingWatchdog disarms itself if the GPIO floats or sits on a tri-state buffer
levelA steady high or low level is assertedExternal chip watches for level changes on a timer of its own

Original Demo: A GPIO Watchdog Device Tree Binding

This example targets a fictional carrier board built around an external supervisory chip on GPIO bank 2, line 14, wired to reset the SoC on a missed ping:

/ {
    ep_gpio_watchdog: watchdog {
        compatible = "linux,wdt-gpio";
        gpios = <&gpio2 14 GPIO_ACTIVE_LOW>;
        hw_algo = "toggle";
        hw_margin_ms = <1200>;
        always-running;
    };
};

hw_margin_ms sets how often the external chip expects a ping before it decides the SoC has hung — keep it comfortably shorter than the external chip’s own datasheet timeout, never equal to it. The optional always-running property tells the driver to keep pinging the GPIO on its own internal timer even before /dev/watchdog is opened, then hand control to user space once it is — useful for boards where the external chip must never see a gap, even during early boot before your watchdog daemon has started.

Confirm the binding came up correctly:

$ dmesg | grep -i gpio_wdt
[    3.881204] gpio_wdt gpio_wdt: gpio pings the watchdog, hw_algo=toggle
$ cat /sys/class/watchdog/watchdog1/bootstatus
0
$ echo test > /dev/watchdog1

Common Mistakes and Troubleshooting

  • Setting pretimeout >= timeout, leaving zero margin for the governor to act
  • Choosing the panic governor on a board with no configured kdump / crash-log target
  • Forgetting WDIOF_PRETIMEOUT in watchdog_info while still calling watchdog_notify_pretimeout()
  • Setting hw_margin_ms equal to the external chip’s datasheet timeout instead of safely under it
  • Using hw_algo = “toggle” on a GPIO that can’t be safely left as input at boot
  • Talking to the supervisory GPIO from a user-space script instead of going through gpio_wdt.c

Best Practices

  • Default new boards to the noop governor; opt into panic only once crash dumps are verified working
  • Keep the pretimeout window large enough for your handler’s actual work — log flush, dump, etc.
  • Prefer devm_watchdog_register_device() so pretimeout timers are torn down automatically on driver removal
  • For GPIO watchdogs, always set always-running on boards where an early-boot ping gap is unacceptable
  • Document the external chip’s own timeout margin next to your hw_margin_ms in the device tree

Security consideration: a panic-governor pretimeout is itself a potential denial-of-service vector if an attacker can artificially trigger it repeatedly — rate-limit or authenticate any user-space path that can influence pretimeout timing on a production system.

Interview Questions

  • What problem does a pretimeout solve that a plain watchdog timeout cannot?
  • Which two governor drivers ship in-tree, and what does each one do?
  • What kernel function does a driver call from its pretimeout IRQ handler?
  • How can a running system change its active governor at runtime?
  • What’s the difference between hw_algo = “toggle” and hw_algo = “level” for gpio-wdt?
  • Why is the GPIO watchdog device-tree-only, with no platform-data path?

Summary and Key Takeaways

  • A pretimeout gives software a last chance to react before a watchdog reset actually happens
  • A linux watchdog governor is a pluggable policy driver, selected by name, swappable via sysfs
  • watchdog_notify_pretimeout() is the single entry point every driver calls; the governor decides what happens next
  • GPIO-based watchdogs let an external supervisory chip cover for SoCs with no usable internal watchdog
  • gpio_wdt.c is configured entirely through device tree, using compatible, gpios, hw_algo, and hw_margin_ms

Together, pretimeout governors and GPIO watchdogs round out the “advanced configuration” side of the watchdog subsystem in this free linux kernel development course. The next lecture moves to the consumer side: the standard /dev/watchdog user-space interface and how a watchdog daemon actually uses it.

Frequently Asked Questions

What is a watchdog pretimeout in the Linux kernel?

It’s an earlier interrupt, fired a configurable interval before the real watchdog timeout, giving software a chance to log diagnostics or force a controlled crash dump before the actual reset occurs.

What does a linux watchdog governor actually do?

It decides the system’s response to a pretimeout notification. The in-tree noop governor just logs it; the panic governor forces an immediate kernel panic so a crash dump is captured.

How do I change the active watchdog governor at runtime?

Write the governor’s name to the device’s pretimeout_governor sysfs attribute, e.g. echo panic > /sys/class/watchdog/watchdog0/pretimeout_governor.

What kernel config option enables governor support?

CONFIG_WATCHDOG_PRETIMEOUT_GOV, with CONFIG_WATCHDOG_PRETIMEOUT_DEFAULT_GOV_NOOP or _GOV_PANIC choosing the system-wide default.

Why would a board use a GPIO-based watchdog instead of an SoC-internal one?

Common reasons include lower standby power on the external chip, or wanting a watchdog that a firmware bug inside the SoC itself can never disable.

What is the difference between hw_algo “toggle” and “level” in gpio-wdt?

“toggle” pings by flipping the GPIO 1-0-1 and disarms if the line floats; “level” pings by asserting a steady high or low signal that the external chip monitors.

Can I ping a GPIO watchdog directly from a user-space script?

You shouldn’t — go through the standard /dev/watchdog interface backed by gpio_wdt.c instead; it integrates properly with kernel frameworks and the standard watchdog ioctls.

What does the always-running device tree property do for a GPIO watchdog?

It makes the driver keep pinging the GPIO on its own internal timer even before /dev/watchdog is opened, so the external chip never sees a gap during early boot.

Continue This Free Linux Kernel Development Course

Next up: the standard /dev/watchdog user-space interface and building a minimal watchdog daemon.

Next Lecture Browse the Full Course

PREV_LEC | NEXT_LEC

Leave a Reply

Your email address will not be published. Required fields are marked *