Registering Linux Watchdog Devices-Free Linux Device Drivers Course

PREV_LECNEXT_LEC

Registering Linux Watchdog Devices
A complete, original watchdog_register_device() and probe walkthrough for a free Linux device drivers course
18+ min read
Kernel 6.x
Full demo driver

In the last lecture of this free embedded systems course we decoded a watchdog’s capability flags and callback table from user space. Now we flip to the driver side: how does a driver actually hand a fully populated watchdog_device to the kernel and get it live under /dev/watchdog? This lecture walks through watchdog_register_device(), its managed sibling devm_watchdog_register_device(), restart-handler priority, and closes with a complete, original platform driver you can build and load yourself.

watchdog_register_device devm_watchdog_register_device restart priority free linux device drivers course platform driver probe

What You Will Learn

  • The exact sequencing a probe function must follow before registering a watchdog
  • Managed vs unmanaged registration and when to prefer each
  • How restart-handler priority is chosen and what the three common values mean
  • How to embed struct watchdog_device inside a per-driver structure, the idiomatic way
  • Build, load, and exercise a complete original platform-driver watchdog end to end

Prerequisites

  • The previous lecture on watchdog_info flags and watchdog_ops callbacks
  • Basic platform driver / device tree familiarity (see our platform device drivers lecture series)
  • A kernel build environment for loadable modules (kernel headers installed, matching running kernel)

The Two Registration Functions

The watchdog core exposes a matched pair of functions for bringing a device online and taking it back down:

int  watchdog_register_device(struct watchdog_device *wdd);
void watchdog_unregister_device(struct watchdog_device *wdd);

watchdog_register_device() returns 0 on success or a negative errno on failure — always check it. watchdog_unregister_device() is its exact mirror, and you must call it yourself in your driver’s remove() path if you used the unmanaged form.

Most new drivers should instead reach for the managed variant:

int devm_watchdog_register_device(struct device *dev,
                                   struct watchdog_device *wdd);

This ties the watchdog’s lifetime to the underlying struct device. When the device is detached — driver unbind, module removal, or a hot-unplug on a removable bus — the kernel automatically calls the unregister path for you. You get a smaller, less error-prone remove() function as a direct result.

Managed vs Unmanaged Registration
Unmanaged: probe() -> watchdog_register_device(wdd) remove() -> watchdog_unregister_device(wdd) [you must remember this] Managed (devm_): probe() -> devm_watchdog_register_device(dev, wdd) remove() -> (often not needed for the watchdog part at all)

Restart Priority: Why It Matters

If your watchdog_ops.restart callback is set, the registration call automatically registers it as a system restart handler — the kernel-wide mechanism used when something calls emergency_restart() or similar. Multiple restart handlers can coexist on one system (a PMIC, an SoC-internal reset controller, and your watchdog might all offer one), so the kernel needs a priority to pick between them via watchdog_set_restart_priority(), called before registration.

PriorityMeaningWhen to choose it
0Lowest — last resortYour watchdog’s restart path is a rough fallback, not the preferred mechanism, and better handlers usually exist.
128DefaultNo other handler is expected on this board, or the watchdog reset is sufficient and reliable enough to be the default choice.
255Highest — preempts everythingYou know this restart path is the most reliable one available and must win over any other registered handler.

Note: Priority must be set before calling either registration function — the registration call is what reads it and wires the handler in.

Structuring the Driver: Embed, Don’t Allocate Separately

Good practice is to embed struct watchdog_device inside a larger, per-driver structure that also holds your hardware-specific state — clocks, register maps, GPIOs, whatever your chip needs. This avoids a second allocation and keeps everything reachable from one pointer via container_of() or, more commonly today, watchdog_get_drvdata()/watchdog_set_drvdata().

Embedding Pattern
struct ep_wdt_device +——————————-+ | struct watchdog_device wdog | | void __iomem *regs | | struct clk *clk | | bool ep_armed | +——————————-+

Hands-On: A Complete Original Watchdog Platform Driver

Let’s build ep_swwdt from scratch — a self-contained, simulated-register software watchdog platform driver. It uses an in-memory “register” to model real MMIO behaviour (so the concepts transfer directly to real hardware) without depending on any specific SoC, and it demonstrates every piece covered above: embedding, managed registration, restart priority, and the ops table.

// ep_swwdt.c — an original, self-contained watchdog platform driver demo
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/watchdog.h>
#include <linux/hrtimer.h>
#include <linux/reboot.h>

#define EP_WDT_DEFAULT_TIMEOUT   30   /* seconds */
#define EP_WDT_MIN_TIMEOUT        1
#define EP_WDT_MAX_TIMEOUT      120

struct ep_wdt_device {
    struct watchdog_device wdog;
    struct hrtimer         hr_timer;
    bool                    ep_armed;
};

static struct ep_wdt_device *ep_from_wdog(struct watchdog_device *wdd)
{
    return watchdog_get_drvdata(wdd);
}

static enum hrtimer_restart ep_wdt_expired(struct hrtimer *timer)
{
    struct ep_wdt_device *ep =
        container_of(timer, struct ep_wdt_device, hr_timer);

    dev_crit(ep->wdog.parent,
             "ep_swwdt: timeout expired, forcing emergency restart\n");
    emergency_restart();
    return HRTIMER_NORESTART;
}

static int ep_wdt_start(struct watchdog_device *wdd)
{
    struct ep_wdt_device *ep = ep_from_wdog(wdd);

    hrtimer_start(&ep->hr_timer,
                  ktime_set(wdd->timeout, 0), HRTIMER_MODE_REL);
    ep->ep_armed = true;
    dev_info(wdd->parent, "ep_swwdt: armed, timeout=%u s\n", wdd->timeout);
    return 0;
}

static int ep_wdt_stop(struct watchdog_device *wdd)
{
    struct ep_wdt_device *ep = ep_from_wdog(wdd);

    hrtimer_cancel(&ep->hr_timer);
    ep->ep_armed = false;
    dev_info(wdd->parent, "ep_swwdt: disarmed\n");
    return 0;
}

static int ep_wdt_ping(struct watchdog_device *wdd)
{
    struct ep_wdt_device *ep = ep_from_wdog(wdd);

    hrtimer_start(&ep->hr_timer,
                  ktime_set(wdd->timeout, 0), HRTIMER_MODE_REL);
    return 0;
}

static int ep_wdt_set_timeout(struct watchdog_device *wdd, unsigned int t)
{
    wdd->timeout = t;
    if (ep_from_wdog(wdd)->ep_armed)
        return ep_wdt_ping(wdd);
    return 0;
}

static int ep_wdt_restart(struct watchdog_device *wdd,
                           unsigned long action, void *data)
{
    dev_crit(wdd->parent, "ep_swwdt: restart handler invoked\n");
    emergency_restart();
    return 0;
}

static const struct watchdog_ops ep_wdt_ops = {
    .owner       = THIS_MODULE,
    .start       = ep_wdt_start,
    .stop        = ep_wdt_stop,
    .ping        = ep_wdt_ping,
    .set_timeout = ep_wdt_set_timeout,
    .restart     = ep_wdt_restart,
};

static const struct watchdog_info ep_wdt_info = {
    .identity = "ep_swwdt",
    .options  = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
};

static int ep_wdt_probe(struct platform_device *pdev)
{
    struct ep_wdt_device *ep;
    int ret;

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

    hrtimer_init(&ep->hr_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
    ep->hr_timer.function = ep_wdt_expired;

    ep->wdog.info        = &ep_wdt_info;
    ep->wdog.ops         = &ep_wdt_ops;
    ep->wdog.min_timeout = EP_WDT_MIN_TIMEOUT;
    ep->wdog.max_timeout = EP_WDT_MAX_TIMEOUT;
    ep->wdog.timeout     = EP_WDT_DEFAULT_TIMEOUT;
    ep->wdog.parent      = &pdev->dev;

    watchdog_set_drvdata(&ep->wdog, ep);
    watchdog_set_restart_priority(&ep->wdog, 128);
    watchdog_stop_on_reboot(&ep->wdog);

    ret = devm_watchdog_register_device(&pdev->dev, &ep->wdog);
    if (ret) {
        dev_err(&pdev->dev, "ep_swwdt: registration failed: %d\n", ret);
        return ret;
    }

    platform_set_drvdata(pdev, ep);
    dev_info(&pdev->dev, "ep_swwdt: registered, default timeout %u s\n",
             ep->wdog.timeout);
    return 0;
}

static struct platform_driver ep_wdt_driver = {
    .driver = {
        .name = "ep_swwdt",
    },
    .probe = ep_wdt_probe,
};

static struct platform_device *ep_wdt_pdev;

static int __init ep_wdt_init(void)
{
    int ret;

    ret = platform_driver_register(&ep_wdt_driver);
    if (ret)
        return ret;

    ep_wdt_pdev = platform_device_register_simple("ep_swwdt", -1, NULL, 0);
    if (IS_ERR(ep_wdt_pdev)) {
        platform_driver_unregister(&ep_wdt_driver);
        return PTR_ERR(ep_wdt_pdev);
    }
    return 0;
}

static void __exit ep_wdt_exit(void)
{
    platform_device_unregister(ep_wdt_pdev);
    platform_driver_unregister(&ep_wdt_driver);
}

module_init(ep_wdt_init);
module_exit(ep_wdt_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("EmbeddedPathashala original demo watchdog platform driver");

Notice we did not reuse the book’s imx2_wdt naming or any vendor-specific register layout — ep_swwdt is a clean, self-registering platform device so you can load and test it on any machine without device tree changes.

Build and Load

$ make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
$ sudo insmod ep_swwdt.ko
$ dmesg | tail -n 3

Expected dmesg output:

[ 1234.001122] ep_swwdt ep_swwdt.0: watchdog: watchdog0: watchdog did not stop!
[ 1234.001210] ep_swwdt ep_swwdt.0: ep_swwdt: registered, default timeout 30 s

(The generic “watchdog did not stop” line only appears if a previous load left it armed — a clean first load will not print it.)

$ sudo ep_wdt_info /dev/watchdog0
Identity        : ep_swwdt
Firmware version: 0
Capabilities    :
  [x] MAGICCLOSE ('V' disarms on close)
  [x] KEEPALIVEPING (ioctl-based ping supported)
  [x] SETTIMEOUT (timeout is configurable)
Current timeout : 30 seconds

We reuse the ep_wdt_info tool from the previous lecture here on purpose — this is exactly the “prove the contract holds” step: everything ep_swwdt advertises in watchdog_ops shows up correctly on the user-space side.

$ sudo sh -c 'echo 1 > /sys/class/watchdog/watchdog0/state 2>/dev/null; true'
$ echo b | sudo tee /dev/watchdog0 >/dev/null   # simple keep-alive write path
$ sudo rmmod ep_swwdt
$ dmesg | tail -n 1
[ 1300.552013] ep_swwdt ep_swwdt.0: ep_swwdt: disarmed

Because we used devm_watchdog_register_device(), unregistration on module removal happened automatically — our driver never had to implement a remove() callback for the watchdog itself.

Real-World Use Cases

  • Industrial gateways use this exact registration pattern with a real MMIO-backed timer instead of an hrtimer, wrapping vendor register access behind the same start/stop/ping shape.
  • Automotive and safety-rated boards frequently set restart priority to 255 for the watchdog specifically because it is the most trustworthy last-resort reset path when the rest of the SoC may be wedged.
  • Multi-PMIC systems often register several restart handlers; priority 0 is a common choice for a watchdog acting purely as a backstop behind a dedicated PMIC reset line.

Common Mistakes and Troubleshooting

  • Forgetting watchdog_set_restart_priority() before registering: the restart handler still gets wired in at default priority, which may not be what you intended.
  • Mixing managed and unmanaged calls: calling watchdog_unregister_device() explicitly on a device registered with the devm_ variant leads to a double-unregister.
  • Not checking the return value of registration: a failed registration silently leaves /dev/watchdogN missing; always log and propagate the error from probe().
  • Populating .info/.ops after registering: always finish populating every field of watchdog_device before calling either registration function — the core may read them immediately.

Best Practices

  • Default to the managed devm_watchdog_register_device() unless you have a specific reason to control unregistration ordering yourself.
  • Call watchdog_stop_on_reboot() (as shown above) so a clean reboot doesn’t leave the watchdog armed and racing the shutdown sequence.
  • Pick restart priority deliberately and document why, especially on boards with more than one restart handler.
  • Keep hardware-specific state in the same embedding struct as watchdog_device — it keeps probe/remove logic in one place.

Performance and Security Considerations

Registration itself happens once at probe time and has no runtime performance impact. The ping/start path, however, is timing-sensitive on hardware watchdogs with tight tolerances — keep it free of sleeping calls or anything that could be delayed by scheduling. On the security side, remember that whichever restart handler wins at the highest priority effectively controls how the system responds to emergency_restart() calls; don’t grant that priority to a path you can’t fully trust.

Summary and Key Takeaways

  • watchdog_register_device()/watchdog_unregister_device() are the unmanaged pair; devm_watchdog_register_device() ties the lifecycle to the device automatically.
  • A restart callback auto-registers as a system restart handler; priority (0/128/255) decides who wins among multiple handlers.
  • Embed watchdog_device in a per-driver struct and reach it via watchdog_get_drvdata().
  • Our complete ep_swwdt driver demonstrates the whole flow end to end, verified with the ep_wdt_info tool from the previous lecture.

Conclusion

Between this lecture and the previous one, you now have the full picture for a working Linux watchdog driver: describe your capabilities honestly, back every advertised flag with a real callback, and register cleanly with a deliberate restart priority. That’s everything this free Linux device drivers course set out to cover for the watchdog subsystem — try adapting ep_swwdt to talk to a real MMIO-backed timer on your own board as practice.

FAQ

Should new drivers use watchdog_register_device() or the devm_ variant?

Prefer devm_watchdog_register_device() in almost all cases — it automatically unregisters on device detach and removes the need for manual cleanup in most drivers.

What happens if I don’t call watchdog_set_restart_priority()?

Your restart callback, if provided, is still registered as a restart handler, just at whatever default priority the core applies rather than a value you chose deliberately.

Can more than one restart handler be active on the same system?

Yes, multiple handlers can coexist; the kernel picks among them using each handler’s registered priority when a restart is triggered.

Why embed watchdog_device inside a larger struct instead of allocating it separately?

It avoids an extra allocation and keeps all driver-specific state reachable from a single pointer via watchdog_get_drvdata(), matching how real-world drivers like imx2_wdt are structured.

Does devm_watchdog_register_device() call remove() automatically?

No, it doesn’t add a remove() callback; it registers a devres cleanup action so the underlying watchdog_unregister_device() call happens automatically when the device is torn down.

What does watchdog_stop_on_reboot() do?

It arranges for the watchdog to be disarmed automatically during a clean system reboot, preventing it from firing while the machine is legitimately shutting down.

Is the restart callback required?

No, it’s entirely optional. If omitted, the watchdog simply won’t participate in the system restart-handler mechanism at all.

Keep Going With EmbeddedPathashala

This lecture is part of our free Linux kernel development course, free Linux device drivers course, and free embedded systems course — all completely free.

Browse the Course Index Previous: Watchdog Flags and Ops

PREV_LECNEXT_LEC

Leave a Reply

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