If you’re new with embedded Linux, one of interesting thing that you can start to learn is how to use the watchdog.
Watchdog, like its name, is a kind of peripheral that will boot the system if it doesn’t being ‘fed’ at certain time (In our daily terms: it will bite your system, unless you kick it). Therefore, it will prevent your system from hanging. It’s a nice feature to have, specially if there’s nobody around to press the ‘reset’ button for you =)
Watchdog Driver
To use watchdog peripheral in Linux, you will need:
- Watchdog driver: Most of board suppliers will provide you for free (ask their support if you need to). This article is using EA3131 Linux BSP’s provided watchdog driver.
- Watchdog device file: Device file is a special kind of file to mark the device node in your root filesystem (so you can access the peripheral like accessing file..’everything is a file’ in UNIX system). Normally it is called ‘/dev/watchdog’
If you don’t have watchdog device file in your target root filesystem, you can recreate it using ‘mknod’
# mknod /dev/watchdog c 10 130
Of course, the ‘mknod’ tool must be present in your root filesystem first before you can execute this. To run the watchdog driver in your system
# insmod your_watchdog_driver.ko
Starting – Stopping Watchdog
The watchdog is automatically started once you open ‘/dev/watchdog’. To stop the watchdog, you will need to:
- Write character ‘V’ into ‘/dev/watchdog’ to prevent stopping the watchdog accidentally
- Close the ‘/dev/watchdog’ file
An exception on stopping the watchdog by closing the file is when ‘CONFIG_WATCHDOG_NOWAYOUT’ is enabled in your kernel configuration. When this option is enabled, the watchdog cannot be stopped at all. Hence, you will need to feed / kick it all the time or it will reset the system
‘Kicking’ Watchdog
To kick or to feed the watchdog you can do it in two ways:
- Write any character into ‘/dev/watchdog’. You can write any character into /dev/watchdog, but, my suggestion, don’t write ‘V’ character (See the ‘Starting-Stopping Watchdog’ point above)
- Use IOCTL to insert ‘WDIOC_KEEPALIVE’ value.
Other Things To Do with Watchdog
If you’re bored with the standard start-kick-stop things, you can also try out other watchdog features:
- Set the watchdog timeout. Use IOCTL with WDIOC_SETTIMEOUT
- Get the current watchdog timeout. Use IOCTL with WDIOC_GETTIMEOUT
- Check if the last boot is caused by watchdog or it is power-on-reset. Use IOCTL with WDIOC_GETBOOTSTATUS.
If you are interested to know more about using watchdog in Linux, read the watchdog documentation in Linux source code (Linux-x.y.z/Documentation/watchdog/watchdog-api.txt). Here’s some demo code to test the Linux watchdog:
/*
* Linux watchdog demo for LPC313x
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <getopt.h>
#include <string.h>
#include <errno.h>
#include <linux/watchdog.h>
#define WATCHDOGDEV "/dev/watchdog"
static const char *const short_options = "hd:i:";
static const struct option long_options[] = {
{"help", 0, NULL, 'h'},
{"dev", 1, NULL, 'd'},
{"interval", 1, NULL, 'i'},
{NULL, 0, NULL, 0},
};
static void print_usage(FILE * stream, char *app_name, int exit_code)
{
fprintf(stream, "Usage: %s [options]\n", app_name);
fprintf(stream,
" -h --help Display this usage information.\n"
" -d --dev <device_file> Use <device_file> as watchdog device file.\n"
" The default device file is '/dev/watchdog'\n"
" -i --interval <interval> Change the watchdog interval time\n");
exit(exit_code);
}
int main(int argc, char **argv)
{
int fd; /* File handler for watchdog */
int interval; /* Watchdog timeout interval (in secs) */
int bootstatus; /* Wathdog last boot status */
char *dev; /* Watchdog default device file */
int next_option; /* getopt iteration var */
char kick_watchdog; /* kick_watchdog options */
/* Init variables */
dev = WATCHDOGDEV;
interval = 0;
kick_watchdog = 0;
/* Parse options if any */
do {
next_option = getopt_long(argc, argv, short_options,
long_options, NULL);
switch (next_option) {
case 'h':
print_usage(stdout, argv[0], EXIT_SUCCESS);
case 'd':
dev = optarg;
break;
case 'i':
interval = atoi(optarg);
break;
case '?': /* Invalid options */
print_usage(stderr, argv[0], EXIT_FAILURE);
case -1: /* Done with options */
break;
default: /* Unexpected stuffs */
abort();
}
} while (next_option != -1);
/* Once the watchdog device file is open, the watchdog will be activated by
the driver */
fd = open(dev, O_RDWR);
if (-1 == fd) {
fprintf(stderr, "Error: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
/* If user wants to change the watchdog interval */
if (interval != 0) {
fprintf(stdout, "Set watchdog interval to %d\n", interval);
if (ioctl(fd, WDIOC_SETTIMEOUT, &interval) != 0) {
fprintf(stderr,
"Error: Set watchdog interval failed\n");
exit(EXIT_FAILURE);
}
}
/* Display current watchdog interval */
if (ioctl(fd, WDIOC_GETTIMEOUT, &interval) == 0) {
fprintf(stdout, "Current watchdog interval is %d\n", interval);
} else {
fprintf(stderr, "Error: Cannot read watchdog interval\n");
exit(EXIT_FAILURE);
}
/* Check if last boot is caused by watchdog */
if (ioctl(fd, WDIOC_GETBOOTSTATUS, &bootstatus) == 0) {
fprintf(stdout, "Last boot is caused by : %s\n",
(bootstatus != 0) ? "Watchdog" : "Power-On-Reset");
} else {
fprintf(stderr, "Error: Cannot read watchdog status\n");
exit(EXIT_FAILURE);
}
/* There are two ways to kick the watchdog:
- by writing any dummy value into watchdog device file, or
- by using IOCTL WDIOC_KEEPALIVE
*/
fprintf(stdout,
"Use:\n"
" <w> to kick through writing over device file\n"
" <i> to kick through IOCTL\n" " <x> to exit the program\n");
do {
kick_watchdog = getchar();
switch (kick_watchdog) {
case 'w':
write(fd, "w", 1);
fprintf(stdout,
"Kick watchdog through writing over device file\n");
break;
case 'i':
ioctl(fd, WDIOC_KEEPALIVE, NULL);
fprintf(stdout, "Kick watchdog through IOCTL\n");
break;
case 'x':
fprintf(stdout, "Goodbye !\n");
break;
default:
fprintf(stdout, "Unknown command\n");
break;
}
} while (kick_watchdog != 'x');
/* The 'V' value needs to be written into watchdog device file to indicate
that we intend to close/stop the watchdog. Otherwise, debug message
'Watchdog timer closed unexpectedly' will be printed
*/
write(fd, "V", 1);
/* Closing the watchdog device will deactivate the watchdog. */
close(fd);
}
Note1: The watchdog driver may not implement all of IOCTL that is used in this demo code
Note2: The demo is tested on LPC313x (on EA3131 board). See LPCLinux forum for the details.
Note3: Assuming you’re using CodeSourcery’s GNU/Linux, the command to compile it for ARM926EJ-S is
$ arm-none-linux-gnueabi-gcc -mcpu=arm926ej-s watchdog_demo.c -o watchdog_demo
Great post, very helpful indeed.
I noticed you don’t handle the CR or LF in your getchar() keyboard input loop, this results in some mildly confusing output. Just something like:
case 0xA:
case 0xD:
break;
to your switch.
Comment by owen — December 15, 2011 @ 8:00 am