Linux Watchdog Start Stop Ping-Free Linux Device Drivers Training Online

PREV_LEC | NEXT_LEC

Linux Watchdog Start Stop Ping

Controlling the /dev/watchdog character device from user space: opening, stopping safely with magic close, and sending keep-alive pings

Chapter 13 · Lecture 5
Watchdog Device Drivers
Free Linux Kernel Course

Every watchdog driver we built earlier in this free linux kernel development course exposes itself to user space as a character device, usually /dev/watchdog. Kernel-side registration is only half the story — a real embedded product needs a user-space daemon or application that actually talks to that device correctly, or the watchdog becomes a liability instead of a safety net. This lecture is entirely about that user-space contract: how opening the device arms it, why closing the file is not enough to disarm it, and how to keep the system alive with keep-alive pings. If you are building a free embedded linux course style watchdog client, this is the lecture that gets it right.

watchdog userspace dev watchdog WDIOC_KEEPALIVE magic close free linux device drivers course free embedded systems course

What You Will Learn

Opening /dev/watchdog and what it actually arms Handling open() failures cleanly Why close() alone can reboot your board The magic close character and CONFIG_WATCHDOG_NOWAYOUT Two ways to send a keep-alive ping Building a small original watchdog control tool

Prerequisites

Comfortable with the watchdog_device / watchdog_ops model from earlier lectures Basic POSIX file I/O: open(), write(), close(), ioctl() A working ep_swwdt-style software watchdog loaded as /dev/watchdog0

Opening the Device Arms It

Unlike most character devices, opening /dev/watchdog is not a passive act. The moment the open call succeeds, the kernel’s watchdog core treats the countdown as active, and if nobody services it before the timeout elapses, the platform’s restart handler fires. This is deliberate: a watchdog that has to be explicitly “started” after open is a watchdog that can be forgotten. Your application should open the device as early as possible in its boot sequence, right after any critical initialization, and never delay it waiting on non-essential subsystems.

A well-behaved opener always checks the return value and distinguishes the failure modes, since each one calls for a different corrective action rather than a generic error message:

Open Failure Decision Path
open(“/dev/watchdog”)
|
+– success –> watchdog is now counting down
|
+– ENOENT –> no watchdog driver bound / module not loaded
|
+– EACCES –> insufficient permission, needs root or device group
|
+– EBUSY –> another process already holds the device open
int wdfd = open("/dev/watchdog0", O_WRONLY);

if (wdfd == -1) {
    switch (errno) {
    case ENOENT:
        fprintf(stderr, "ep_wdt_ctl: no watchdog bound at this node\n");
        break;
    case EACCES:
        fprintf(stderr, "ep_wdt_ctl: run as root or add udev rule\n");
        break;
    case EBUSY:
        fprintf(stderr, "ep_wdt_ctl: device already held open elsewhere\n");
        break;
    default:
        fprintf(stderr, "ep_wdt_ctl: open failed: %s\n", strerror(errno));
    }
    exit(EXIT_FAILURE);
}

Why close() Does Not Stop the Watchdog

This trips up almost every developer the first time they touch a watchdog device: calling close(fd) on its own does not disarm the countdown. The driver has no way to know whether your process is exiting cleanly or has crashed, and a crashed supervisor is exactly the situation a watchdog exists to catch. So the default assumption on close is “the supervisor is gone, keep counting” — and on most drivers that means a reset is still coming.

To stop the watchdog on purpose, you first write a single magic byte — the ASCII character V — into the device file before closing it. This is called the magic close feature. Writing V tells the driver “the next close is intentional, disarm on it.” Any other character written to the device is treated as an ordinary keep-alive ping instead, which is why the kernel documentation is explicit that you should never write a stray V as part of a longer status string — doing so would accidentally disarm the watchdog.

const char magic = 'V';
ssize_t n = write(wdfd, &magic, 1);

if (n != 1) {
    fprintf(stderr, "ep_wdt_ctl: magic close write failed: %s\n",
            strerror(errno));
} else {
    close(wdfd);
    printf("ep_wdt_ctl: watchdog disarmed cleanly\n");
}

There is one important exception. If the driver was built or configured with CONFIG_WATCHDOG_NOWAYOUT, magic close is disabled entirely at the kernel level — once armed, the watchdog cannot be stopped by any user-space action for the lifetime of the boot. This is a common production hardening choice: it guarantees that a compromised or buggy supervisor process can never silently disable the safety net. If you rely on this option, your driver must still advertise the WDIOF_MAGICCLOSE flag correctly so tooling can detect the behaviour, even though the write itself becomes a no-op.

Sending Keep-Alive Pings

Between opening and (optionally) closing, your supervisor’s real job is to prove liveness on a schedule. The watchdog framework gives you two equivalent ways to do this, and most production daemons use one or the other consistently rather than mixing them.

Method 1: Write Any Character

A plain write() of one byte to the device — any byte other than V — is defined as a keep-alive ping. This is the simplest possible mechanism and works even from a shell script.

const char ping = '1';
write(wdfd, &ping, 1);

Method 2: WDIOC_KEEPALIVE ioctl

The alternative is an explicit ioctl call, which some teams prefer because it cannot be confused with a data write and reads clearly in code review. The argument is ignored by the kernel, so passing 0 is conventional.

if (ioctl(wdfd, WDIOC_KEEPALIVE, 0) == -1) {
    fprintf(stderr, "ep_wdt_ctl: keepalive ioctl failed: %s\n",
            strerror(errno));
}

Either mechanism only works if the driver advertised the WDIOF_KEEPALIVEPING capability flag — you will learn how to query that flag safely in the next lecture. As a rule of thumb, feed the watchdog at roughly half its configured timeout: a 30-second timeout should see a ping at least every 15 seconds, giving one full missed cycle of margin before the deadline is at real risk.

Building ep_wdt_ctl: A Minimal Watchdog Supervisor

Below is a small original command-line tool that ties the lecture together: it opens the device, pings it in a loop at half the requested interval, and disarms it cleanly with magic close on SIGINT.

// ep_wdt_ctl.c — minimal watchdog supervisor demo
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <linux/watchdog.h>

static int wdfd = -1;
static volatile sig_atomic_t stop_requested;

static void on_sigint(int sig) { (void)sig; stop_requested = 1; }

int main(int argc, char *argv[])
{
    const char *node = (argc > 1) ? argv[1] : "/dev/watchdog0";
    int ping_interval_s = (argc > 2) ? atoi(argv[2]) : 15;

    signal(SIGINT, on_sigint);

    wdfd = open(node, O_WRONLY);
    if (wdfd == -1) {
        fprintf(stderr, "ep_wdt_ctl: open(%s) failed: %s\n",
                node, strerror(errno));
        return EXIT_FAILURE;
    }
    printf("ep_wdt_ctl: armed on %s, pinging every %ds\n",
           node, ping_interval_s);

    while (!stop_requested) {
        if (ioctl(wdfd, WDIOC_KEEPALIVE, 0) == -1)
            fprintf(stderr, "ep_wdt_ctl: ping failed: %s\n", strerror(errno));
        else
            printf("ep_wdt_ctl: keep-alive sent\n");
        sleep(ping_interval_s);
    }

    const char magic = 'V';
    write(wdfd, &magic, 1);
    close(wdfd);
    printf("ep_wdt_ctl: disarmed and exiting cleanly\n");
    return EXIT_SUCCESS;
}
Build and Run
$ gcc -o ep_wdt_ctl ep_wdt_ctl.c
$ sudo ./ep_wdt_ctl /dev/watchdog0 5
ep_wdt_ctl: armed on /dev/watchdog0, pinging every 5s
ep_wdt_ctl: keep-alive sent
ep_wdt_ctl: keep-alive sent
^C
ep_wdt_ctl: disarmed and exiting cleanly

Common Mistakes and Troubleshooting

Assuming close() disarms the watchdog

Always send the magic V write before closing, or the next timeout will still reset the board.

Writing ‘V’ inside a longer status string

Any buffer written to the device that happens to contain a V byte can trigger an unintended disarm — keep ping payloads and magic-close writes strictly separate.

Pinging too close to the deadline

A supervisor that pings only just before timeout leaves no margin for scheduling jitter under load. Feed at roughly half the timeout, not at 90% of it.

Expecting magic close to work under NOWAYOUT

If the driver was built with CONFIG_WATCHDOG_NOWAYOUT, the magic write is silently ineffective — design your supervisor to run for the entire uptime of the system in that configuration.

Best Practices

Open the watchdog as early as possible in boot Run the ping loop from the most reliable process in the system, not a script prone to hangs Log every ping failure — a failing ping is itself an early warning sign Prefer WDIOC_KEEPALIVE over a raw write for clarity in production code Decide up front whether NOWAYOUT is desired for your product’s safety posture

Summary and Key Takeaways

Opening /dev/watchdog arms the countdown immediately, and closing the file alone does not stop it — you must write the magic V character first, unless CONFIG_WATCHDOG_NOWAYOUT makes the watchdog permanently unstoppable. Keeping the system alive is a matter of pinging on a schedule, either with a plain write or the clearer WDIOC_KEEPALIVE ioctl, at roughly half the configured timeout. With this lifecycle understood, the next lecture moves on to querying watchdog capabilities and controlling the timeout and pretimeout values themselves.

Frequently Asked Questions

Does opening /dev/watchdog start the timer immediately?

Yes. The kernel’s watchdog core begins counting down as soon as the open() call succeeds, so the ping loop should start right after.

What happens if I just call close() without writing ‘V’ first?

On most drivers the watchdog keeps counting after close, and if nothing else feeds it, the system resets once the timeout elapses.

Can I write any character to send a keep-alive ping?

Any byte works except ‘V’, which is reserved as the magic close character and will attempt to disarm the watchdog instead.

Why would a driver refuse to be stopped at all?

If it was built with CONFIG_WATCHDOG_NOWAYOUT, the magic close feature is disabled at the kernel level for the entire boot session.

Is WDIOC_KEEPALIVE better than a raw write?

Functionally they are equivalent when the driver advertises WDIOF_KEEPALIVEPING; the ioctl is preferred mainly for code clarity.

How often should I feed the watchdog?

A common rule of thumb is half the configured timeout, giving one full missed cycle of margin before a real risk of reset.

What does EBUSY on open mean for a watchdog device?

It usually means another process already holds the device open — only one supervisor should own the watchdog file descriptor at a time.

Continue the Free Linux Kernel Development Course

Next: querying watchdog capabilities and controlling timeout/pretimeout values.

Next Lecture Back to Course Index

PREV_LEC | NEXT_LEC

Leave a Reply

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