设计说明
源码说明
源代码位于:
drivers/watchdog/artinchip_wdt.c
模块架构
Linux 提供了一个 Watchdog 子系统 (简称 Watchdog Core),使得在用户空间可以通过 /dev/watchdogX 来访问 Watchdog 控制器。为了更方便查看硬件状态和参数设置,本驱动另外扩展了几个 sysfs 节点。
整个软件框架可以简单抽象为下图:

针对 Watchdog 控制器的几个特色功能:
-
- 多通道
-
将每个通道注册为一个 watchdog 设备,在/dev/目录下面生成多个 watchdog 设备节点。每一个 Watchdog 设备节点都提供标准的 Watchdog ioctl 接口。
-
- 超时中断
-
在 Watchdog 超时之前可以产生一些中断信号,让软件有机会做一些预处理。
对应到 Core 的 pretimeout 参数,可以支持对外注册 pretimeout 回调的机制。
-
- 清零窗口
-
Watchdog Core 中没有对应的参数,所以提供一个 int 类型的 DTS 字段“clr_thd”,让用户态可以设置此门限,需要注意这个值是 4 个 Watchdog 通道共用的。默认是 0,表示随时可以 clean 计数。详见 Watchdog 自定义参数。
-
- 调试模式的计数状态
-
当 CPU 进入 Jtag 的 debug 状态时,Watchdog 计数可以选择是否暂停,默认暂停。
类似的,也通过一个 bool 类型的 DTS 字段 “dbg_continue” 提供给用户态去设置。详见 Watchdog 自定义参数。
关键流程设计
初始化流程
int devm_watchdog_register_device(struct device *dev, struct watchdog_device *);
static const struct watchdog_ops aic_wdt_ops = { .owner = THIS_MODULE, .start = aic_wdt_start, .stop = aic_wdt_stop, .ping = aic_wdt_ping, .set_timeout = aic_wdt_set_timeout, .set_pretimeout = aic_wdt_set_pretimeout, .restart = aic_wdt_restart, };
数据结构设计
- aic_wdt_dev:记录 Watchdog 控制器的配置信息。其中包含四个 Watchdog
设备:
struct aic_wdt_dev { struct watchdog_device wdt_dev[WDT_CHAN_NUM]; void __iomem *base; struct attribute_group attrs; struct clk *clk; struct reset_control *rst; u32 wdt_no; struct aic_wdt wdt[WDT_CHAN_NUM]; bool dbg_continue; u32 clr_thd; };
- aic_wdt:记录每一个 Watchdog
通道的配置信息。
struct aic_wdt { u32 clr_thd; u32 irq_thd; u32 rst_thd; };
接口设计
以下接口是 Watchdog 子系统需要的标准接口。
外部接口
struct watchdog_info { __u32 options; /* Options the card/driver supports */ __u32 firmware_version; /* Firmware version of the card */ __u8 identity[32]; /* Identity of the board */ }; #define WDIOC_GETSUPPORT _IOR(WATCHDOG_IOCTL_BASE, 0, struct watchdog_info) #define WDIOC_GETSTATUS _IOR(WATCHDOG_IOCTL_BASE, 1, int) #define WDIOC_GETBOOTSTATUS _IOR(WATCHDOG_IOCTL_BASE, 2, int) #define WDIOC_GETTEMP _IOR(WATCHDOG_IOCTL_BASE, 3, int) #define WDIOC_SETOPTIONS _IOR(WATCHDOG_IOCTL_BASE, 4, int) #define WDIOC_KEEPALIVE _IOR(WATCHDOG_IOCTL_BASE, 5, int) #define WDIOC_SETTIMEOUT _IOWR(WATCHDOG_IOCTL_BASE, 6, int) #define WDIOC_GETTIMEOUT _IOR(WATCHDOG_IOCTL_BASE, 7, int) #define WDIOC_SETPRETIMEOUT _IOWR(WATCHDOG_IOCTL_BASE, 8, int) #define WDIOC_GETPRETIMEOUT _IOR(WATCHDOG_IOCTL_BASE, 9, int) #define WDIOC_GETTIMELEFT _IOR(WATCHDOG_IOCTL_BASE, 10, int)
Demo 就是调用的这些接口 Watchdog 的访问。
内部接口
函数原型 | static int aic_wdt_start(struct watchdog_device *wdt_dev) |
---|---|
功能说明 | 使能一个 Watchdog 通道 (device) |
参数定义 | wdt_dev - 指向 Watchdog 设备的指针 |
返回值 | 0,成功 |
注意事项 | 如果当前通道的 Watchdog 已经是使能状态,将执行 ping 操作 (喂狗)。 |
函数原型 | static int aic_wdt_stop(struct watchdog_device *wdt_dev) |
---|---|
功能说明 | 关闭一个 Watchdog 通道 (device) |
参数定义 | wdt_dev - 指向 Watchdog 设备的指针 |
返回值 | 0,成功 |
注意事项 | - |
函数原型 | static int aic_wdt_ping(struct watchdog_device *wdt_dev) |
---|---|
功能说明 | 清零指定的 Watchdog 通道计数器,相当于“喂狗”操作 |
参数定义 | wdt_dev - 指向 Watchdog 设备的指针 |
返回值 | 0,成功 |
注意事项 | 需要先调用 aic_wdt_start(),再调用此接口 |
函数原型 | static int aic_wdt_set_timeout(struct watchdog_device *wdt_dev, unsigned int timeout) |
---|---|
功能说明 | 给指定的 Watchdog 设备设置一个超时 |
参数定义 | wdt_dev - 指向 Watchdog 设备的指针 timetout - 超时的数值,单位:秒 |
返回值 | 0,成功 |
注意事项 | 1. 在 Watchdog 初始化时配置的最大、最小 timeout 参数,Watchdog Core 会去做校验,如果超出范围,将采用上一次有效的 timeout 参数值。2. clr_thd 会和 timeout 一起设置到 Watchdog 控制器。 |
函数原型 | static int aic_wdt_set_pretimeout(struct watchdog_device *wdt_dev,unsigned int pretimeout) |
---|---|
功能说明 | 给指定的 Watchdog 设备设置一个预超时 |
参数定义 | wdt_dev - 指向 Watchdog 设备的指针 pretimetout - 预超时的数值,单位:秒 |
返回值 | 0,成功 |
注意事项 | pretimeout 必须要小于该 Watchdog 通道的 timeout 参数,这个有效性检查会在 Core 中去做 (所以合理的 ioctl 操作是先设置 timeout、再设置 pretimeout),如果 pretimeout 无效将返回出错。 |
函数原型 | static int aic_wdt_restart(struct watchdog_device *wdt_dev,unsigned long action, void *data) |
---|---|
功能说明 | 用于重启整个系统 (方法是设置 timeout 是 0,Watchdog 会立即触发超时重启) |
参数定义 |
|
返回值 | 0,成功 |
注意事项 | - |
#define SYS_DOWN 0x0001 /* Notify of system down */ #define SYS_RESTART SYS_DOWN #define SYS_HALT 0x0002 /* Notify of system halt */ #define SYS_POWER_OFF 0x0003 /* Notify of system power off */
Demo
#include <artinchip/sample_base.h> #include <sys/time.h> #include <linux/watchdog.h> /* Global macro and variables */ #define WDT_CHAN_NUM 4 #define WDT_MAX_TIMEOUT (60 * 60) #define WDT_MIN_TIMEOUT 1 #define WDT_DEV_PATH "/dev/watchdog" static const char sopts[] = "ic:s:gp:Gku"; static const struct option lopts[] = { {"info", no_argument, NULL, 'd'}, {"channel", required_argument, NULL, 'c'}, {"set-timeout", required_argument, NULL, 's'}, {"get-timeout", no_argument, NULL, 'g'}, {"set-pretimeout", required_argument, NULL, 'p'}, {"get-pretimeout", no_argument, NULL, 'G'}, {"keepalive", no_argument, NULL, 'k'}, {"usage", no_argument, NULL, 'u'}, {0, 0, 0, 0} }; /* Functions */ int usage(char *program) { printf("Compile time: %s %s\n", __DATE__, __TIME__); printf("Usage: %s [options]\n", program); printf("\t -i, --info\t\tPrint the status and infomation\n"); printf("\t -s, --set-timeout\tSet a timeout, in second\n"); printf("\t -g, --get-timeout\tGet the current timeout, in second\n"); printf("\t -p, --set-pretimeout\tSet a pretimeout, in second\n"); printf("\t -G, --get-pretimeout\tGet the current pretimeout, in second\n"); printf("\t -k, --keepalive\tKeepalive the watchdog\n"); printf("\t -u, --usage \n"); printf("\n"); printf("Example: %s -c 0 -s 12\n", program); printf("Example: %s -c 1 -s 100 -p 90\n\n", program); return 0; } /* Open a device file to be needed. */ int wdt_open(int chan) { s32 fd = -1; char filename[16] = {0}; sprintf(filename, "%s%d", WDT_DEV_PATH, chan); fd = open(filename, O_RDWR); if (fd < 0) ERR("Failed to open %s errno: %d[%s]\n", filename, errno, strerror(errno)); return fd; } int wdt_enable(int fd, int enable) { int ret = 0; int cmd = enable ? WDIOS_ENABLECARD : WDIOS_DISABLECARD; ret = ioctl(fd, WDIOC_SETOPTIONS, &cmd); if (ret < 0) ERR("Failed to %s wdt %d[%s]\n", enable ? "enable" : "disable", errno, strerror(errno)); return ret; } int wdt_info(int chan) { int ret = 0, devfd = -1; int status = 0; struct watchdog_info info = {0}; devfd = wdt_open(chan); if (devfd < 0) return -1; ret = ioctl(devfd, WDIOC_GETSUPPORT, &info); if (ret < 0) { ERR("Failed to get support %d[%s]\n", errno, strerror(errno)); goto err; } printf("In %s watchdog V%d, options %#x\n", info.identity, info.firmware_version, info.options); ret = ioctl(devfd, WDIOC_GETSTATUS, &status); if (ret < 0) { ERR("Failed to get status %d[%s]\n", errno, strerror(errno)); goto err; } printf("Status: %d\n", status); ret = ioctl(devfd, WDIOC_GETBOOTSTATUS, &status); if (ret < 0) { ERR("Failed to get bootstatus %d[%s]\n", errno, strerror(errno)); goto err; } printf("Boot status: %d\n", status); err: wdt_enable(devfd, 0); close(devfd); return ret; } int wdt_set_timeout(int chan, int timeout, int pretimeout) { int ret = 0, devfd = -1; devfd = wdt_open(chan); if (devfd < 0) return -1; DBG("Set chan%d timeout %d, pretimeout %d\n", chan, timeout, pretimeout); ret = ioctl(devfd, WDIOC_SETTIMEOUT, &timeout); if (ret < 0) ERR("Failed to set timeout %d[%s]\n", errno, strerror(errno)); if (pretimeout) { ret = ioctl(devfd, WDIOC_SETPRETIMEOUT, &pretimeout); if (ret < 0) ERR("Failed to set pretimeout %d[%s]\n", errno, strerror(errno)); } wdt_enable(devfd, 0); close(devfd); return ret; } int wdt_get_timeout(int chan) { int ret = 0, devfd = -1; int timeout; devfd = wdt_open(chan); if (devfd < 0) return -1; ret = ioctl(devfd, WDIOC_GETTIMEOUT, &timeout); if (ret < 0) ERR("Failed to get timeout %d[%s]\n", errno, strerror(errno)); else DBG("Get chan%d timeout %d\n", chan, timeout); wdt_enable(devfd, 0); close(devfd); return ret; } int wdt_set_pretimeout(int chan, int pretimeout) { int ret = 0, devfd = -1; devfd = wdt_open(chan); if (devfd < 0) return -1; DBG("Set chan%d pretimeout %d\n", chan, pretimeout); ret = ioctl(devfd, WDIOC_SETPRETIMEOUT, &pretimeout); if (ret < 0) ERR("Failed to set pretimeout %d[%s]\n", errno, strerror(errno)); wdt_enable(devfd, 0); close(devfd); return ret; } int wdt_get_pretimeout(int chan) { int ret = 0, devfd = -1; int pretimeout; devfd = wdt_open(chan); if (devfd < 0) return -1; ret = ioctl(devfd, WDIOC_GETPRETIMEOUT, &pretimeout); if (ret < 0) ERR("Failed to get pretimeout %d[%s]\n", errno, strerror(errno)); else DBG("Get chan%d pretimeout %d\n", chan, pretimeout); wdt_enable(devfd, 0); close(devfd); return ret; } int wdt_keepalive(int chan) { int ret = 0, devfd = -1; devfd = wdt_open(chan); if (devfd < 0) return -1; ret = ioctl(devfd, WDIOC_KEEPALIVE, NULL); if (ret < 0) ERR("Failed to keepalive %d[%s]\n", errno, strerror(errno)); else DBG("keepalive chan%d\n", chan); wdt_enable(devfd, 0); close(devfd); return ret; } int main(int argc, char **argv) { int c, chan = 0; int timeout = 0, pretimeout = 0; while ((c = getopt_long(argc, argv, sopts, lopts, NULL)) != -1) { switch (c) { case 'c': chan = str2int(optarg); if ((chan < 0) || (chan ≥ WDT_CHAN_NUM)) { ERR("Invalid channel No.%s\n", optarg); return -1; } DBG("You select the channel %d\n", chan); continue; case 's': timeout = str2int(optarg); continue; case 'g': return wdt_get_timeout(chan); case 'p': pretimeout = str2int(optarg); continue; case 'G': return wdt_get_pretimeout(chan); case 'i': return wdt_info(chan); case 'k': return wdt_keepalive(chan); case 'u': default: return usage(argv[0]); } } return wdt_set_timeout(chan, timeout, pretimeout); }