WDT又被称为看门狗,是用于监视系统运行的模块。其主要功能用于当系统跑飞或者死机时, 无法进行喂狗时, 产生某些动作重启设备。目前主流的看门狗有内部的系统看门狗,以及外部的硬件看门狗;虽然功能和配置可能不同,但其控制原理都类似。
看门狗是芯片硬件的功能,可以实现报警和复位功能。对于WDT框架,只是提供应用层到硬件的接口,主要实现看门狗使能,周期性喂狗,以及停止等功能。
本节目录如下所示。
在嵌入式Linux系统中,看门狗模块作为字符设备被访问,通过ioctl去操作。看门狗包含的代码实现如下。
对于watchdog的结构如下所示。
可以看到,watchdog是芯片内部模块,由platform总线统一管理。其中设备由解析设备树创建,驱动则包含两部分。
看门狗访问的接口如下所示。
//向系统中注册watchdog设备(支持驱动卸载时移除)
int devm_watchdog_register_device(struct device *dev, struct watchdog_device *wdd)
//向系统中注册watchdog设备
int watchdog_register_device(struct watchdog_device *wdd)
//从系统中移除注册的watchdog设备
void watchdog_unregister_device(struct watchdog_device *wdd)
可以看到,其中最重要的结构就是”struct watchdog_device”,解析如下所示。
//watchdog_device结构
struct watchdog_device {
int id; // watchdog的id,在注册时申请
struct device *parent; // watchdog所属的总线设备
const struct attribute_group **groups; // 创建看门狗设备时需要创建的sysfs属性组列表。
const struct watchdog_info *info; // watchdog配置信息结构
const struct watchdog_ops *ops; // watchdog需要执行的函数
const struct watchdog_governor *gov; // watchdog pretimeout调控器的指针
unsigned int bootstatus; // 启动后的watchdog状态
unsigned int timeout; // 当前设置的看门狗超时时间,单位秒
unsigned int pretimeout; // 当前设置的看门狗超时前中断触发时间,单位秒
unsigned int min_timeout; // 允许设置的看门狗最小超时时间,单位秒
unsigned int max_timeout; // 允许设置的看门狗最大超时时间,单位秒
unsigned int min_hw_heartbeat_ms; // 硬件限制最小心跳间隔时间,单位ms
unsigned int max_hw_heartbeat_ms; // 硬件限制最大心跳间隔时间,单位ms
struct notifier_block reboot_nb; // 在重启时停止看门狗的通知块
struct notifier_block restart_nb; // 用于注册重启功能的通知块
struct notifier_block pm_nb; // 用于pm管理的通知块
void *driver_data; // 驱动私有数据
struct watchdog_core_data *wd_data; // 指向watchdog核心内部数据的指针
unsigned long status; // 包含设备内部状态位的字段
/* Bit numbers for status flags */
#define WDOG_ACTIVE 0 /* Is the watchdog running/active */
#define WDOG_NO_WAY_OUT 1 /* Is 'nowayout' feature set ? */
#define WDOG_STOP_ON_REBOOT 2 /* Should be stopped on reboot */
#define WDOG_HW_RUNNING 3 /* True if HW watchdog running */
#define WDOG_STOP_ON_UNREGISTER 4 /* Should be stopped on unregister */
#define WDOG_NO_PING_ON_SUSPEND 5 /* Ping worker should be stopped on suspend */
struct list_head deferred; // wtd_deferred_reg_list的入口,用来注册和初始化看门狗
};
//Documentation/watchdog/watchdog-api.rst
//watchdog_info
struct watchdog_info {
__u32 options; // 驱动程序支持的选项
__u32 firmware_version; // 固件版本号
__u8 identity[32]; // 标识符,字符串
};
//options支持的选项
/*
application:
struct watchdog_info ident;
ioctl(fd, WDIOC_GETSUPPORT, &ident);
下面定义何种类型的看门狗状态可以被返回查看
*/
#define WDIOF_OVERHEAT 0x0001 /* Reset due to CPU overheat */
#define WDIOF_FANFAULT 0x0002 /* Fan failed */
#define WDIOF_EXTERN1 0x0004 /* External relay 1 */
#define WDIOF_EXTERN2 0x0008 /* External relay 2 */
#define WDIOF_POWERUNDER 0x0010 /* Power bad/power fault */
#define WDIOF_CARDRESET 0x0020 /* Card previously reset the CPU */
#define WDIOF_POWEROVER 0x0040 /* Power over voltage */
#define WDIOF_SETTIMEOUT 0x0080 /* Set timeout (in seconds) */
#define WDIOF_MAGICCLOSE 0x0100 /* Supports magic close char */
#define WDIOF_PRETIMEOUT 0x0200 /* Pretimeout (in seconds), get/set */
#define WDIOF_ALARMONLY 0x0400 /* Watchdog triggers a management or
other external alarm not a reboot */
#define WDIOF_KEEPALIVEPING 0x8000 /* Keep alive ping reply */
//watchdog_ops
struct watchdog_ops {
struct module *owner; // 驱动的拥有者,THIS_MODULE
/* mandatory operations */
int (*start)(struct watchdog_device *); // 看门狗启动时调用函数
/* optional operations */
int (*stop)(struct watchdog_device *); // 看门狗结束时调用函数
int (*ping)(struct watchdog_device *); // 看门狗发送持久连接ping函数
unsigned int (*status)(struct watchdog_device *); // 看门狗显示设备状态函数
int (*set_timeout)(struct watchdog_device *, unsigned int); // 看门狗设置超时时间函数
int (*set_pretimeout)(struct watchdog_device *, unsigned int); // 看门狗设置预超时时间函数
unsigned int (*get_timeleft)(struct watchdog_device *); // 看门狗获取重置前剩余时间的函数
int (*restart)(struct watchdog_device *, unsigned long, void *); // 看门狗重启设备的函数
long (*ioctl)(struct watchdog_device *, unsigned int, unsigned long); // 看门狗处理额外ioctl调用的函数
};
//watchdog_governor
struct watchdog_governor {
const char name[WATCHDOG_GOV_NAME_MAXLEN]; // 调控器名称
void (*pretimeout)(struct watchdog_device *wdd); // pretimout函数
};
可以看到,对于看门狗来说,配置选项还是十分复杂的。不过这里面的选项一部分需要开发者注册前定义,另一部分是watchdog设备注册时,会进行赋值或者处理,并不需要每个都理解,下面驱动中会对涉及选项进行说明,其它部分可以参考上面的结构体说明去理解。
看门狗作为内部模块,更重要的部分是模块功能和状态寄存器,用于实现对看门狗模块的管理。这里以I.MX6ULL为例,可以作为参考。
可以看到,看门狗寄存器范围0x020BC000~0x021E4008之间,包含了看门狗的控制寄存器,状态寄存器,以及中断寄存器等,对应设备树如下。
wdog1: watchdog@20bc000 {
compatible = "fsl,imx6ul-wdt", "fsl,imx21-wdt"; //标签,用于驱动匹配
reg = <0x020bc000 0x4000>; //寄存器列表,通过regmap访问
interrupts = <GIC_SPI 80 IRQ_TYPE_LEVEL_HIGH>; //watchdog的中断控制器,中断线号和中断触发条件
clocks = <&clks IMX6UL_CLK_WDOG1>; //管理模块的时钟使能,一般用于模块的电源管理(shutdown, suspend, resume)
};
其中中断线号说明如下所示。
看门狗驱动的基本原理是通过定时器来监控系统的运行状态。如果系统在规定的时间内没有“喂狗”(即重置定时器),看门狗定时器就会溢出,触发系统重启。在Linux内核中,看门狗驱动通常作为一个平台设备驱动来实现。另外wdog作为内部模块,也使用platform总线统一管理,具体步骤如下所示。
具体流程如下所示。
//匹配设备树中compatible字段的数组
static const struct of_device_id wdt_of_match[] = {
{ .compatible = "rmk,usr-wdt", },
{ /* Sentinel */ }
};
MODULE_DEVICE_TABLE(of, wdt_of_match);
static struct platform_driver platform_driver = {
.shutdown = wdt_shutdown,
.driver = {
.name = DRIVER_NAME,
.of_match_table = wdt_of_match,
},
.probe = wdt_probe,
.remove = wdt_remove,
};
static int __init wdt_module_init(void)
{
platform_driver_register(&platform_driver); //注册platform驱动,执行probe函数
return 0;
}
static void __exit wdt_module_exit(void)
{
platform_driver_unregister(&platform_driver); //移除platform驱动,执行remove函数
}
//注册模块初始化和卸载函数
module_init(wdt_module_init);
module_exit(wdt_module_exit);
MODULE_AUTHOR("wzdxf");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("platform driver for wdt");
MODULE_ALIAS("led_data");
static int wdt_probe(struct platform_device *pdev)
{
int ret = 0;
struct kernel_wdt_data *wdata;
struct watchdog_device *wdog;
// 申请内存,用于保存wdt设备的信息
wdata = devm_kzalloc(&pdev->dev, sizeof(struct kernel_wdt_data), GFP_KERNEL);
if (!wdata) {
dev_err(&pdev->dev, "wdata malloc failed!\r\n");
return -ENOMEM;
}
wdata->pdev = pdev;
platform_set_drvdata(pdev, wdata);
// 获取gpio描述符,用于操作wdt硬件
wdata->wdt_desc = devm_gpiod_get(&pdev->dev, "wdt", GPIOD_OUT_LOW);
if (!wdata->wdt_desc)
{
dev_err(&pdev->dev, "devm_gpiod_get error!\n");
return -EIO;
}
wdata->status = 0;
gpiod_direction_output(wdata->wdt_desc, wdata->status);
// 初始化wdog结构体,用于注册到watchdog内核
wdog = &wdata->wdog;
wdog->info = &wdt_info;
wdog->ops = &wdt_ops;
wdog->min_timeout = 1;
wdog->timeout = CONFIG_WDT_DEFAULT_TIME;
wdog->max_hw_heartbeat_ms = 1000*CONFIG_WDT_MAX_TIME;
wdog->parent = &pdev->dev;
wdog->bootstatus = 0;
watchdog_set_drvdata(wdog, wdata);
ret = devm_watchdog_register_device(&pdev->dev, wdog);
if (ret != 0)
{
dev_err(&pdev->dev, "devm_watchdog_register_device error, ret:%d!\n", ret);
return ret;
}
dev_info(&pdev->dev, "wdt device register success!\n");
return ret;
}
static int wdt_remove(struct platform_device *pdev)
{
dev_info(&pdev->dev, "driver release!\n");
return 0;
}
// wdog喂狗操作
static int wdt_ping(struct watchdog_device *wdog)
{
struct kernel_wdt_data *wdata = watchdog_get_drvdata(wdog);
if (wdata->status == 0) {
wdata->status = 1;
} else {
wdata->status = 0;
}
gpiod_set_value(wdata->wdt_desc, wdata->status);
dev_info(&wdata->pdev->dev, "wdt gpio reserved:%d", wdata->status);
return 0;
}
// wdog启动操作
static int wdt_start(struct watchdog_device *wdog)
{
struct kernel_wdt_data *wdata = watchdog_get_drvdata(wdog);
set_bit(WDOG_HW_RUNNING, &wdog->status);
dev_info(&wdata->pdev->dev, "wdt start, status:%ld!", wdog->status);
return wdt_ping(wdog);
}
// wdog停止操作
static int wdt_stop(struct watchdog_device *wdog)
{
struct kernel_wdt_data *wdata = watchdog_get_drvdata(wdog);
wdata->status = 0;
clear_bit(WDOG_HW_RUNNING, &wdog->status);
gpiod_set_value(wdata->wdt_desc, wdata->status);
dev_info(&wdata->pdev->dev, "wdt stop!");
return 0;
}
// wdt设置超时时间操作
static int wdt_set_timeout(struct watchdog_device *wdog,
unsigned int new_timeout)
{
unsigned int actual;
struct kernel_wdt_data *wdata = watchdog_get_drvdata(wdog);
actual = min(new_timeout, CONFIG_WDT_MAX_TIME);
wdog->timeout = actual;
dev_info(&wdata->pdev->dev, "wdt timeout:%d", wdog->timeout);
return 0;
}
static const struct watchdog_ops wdt_ops = {
.owner = THIS_MODULE,
.start = wdt_start,
.ping = wdt_ping,
.stop = wdt_stop,
.set_timeout = wdt_set_timeout,
.set_pretimeout = wdt_set_pretimeout,
.restart = imx2_wdt_restart,
};
当驱动加载成功后,此时会在/dev/下创建相应节点,通过应用层文件接口即可操作,此时可通过
ls /dev/watchdog*
查看,具体如下。
上述代码为访问内部的wdt设备,具体代码参考地址: kernel_wdt.c
对于watchdog的应用层访问,和常规的字符设备访问一致,使用open,ioctl,close等接口即可,实现如下。
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/input.h>
#include <linux/watchdog.h>
#include <stdlib.h>
#include <string.h>
static unsigned int flag = 0;
#define WATCHDOG_DEVICE "/dev/watchdog1"
int main(int argc, char *argv[])
{
int wdt_fd;
int timeout = 5;
int ioctl_alive = 0;
int ops;
if (argc > 1) {
ioctl_alive = atoi(argv[1]);
}
printf("ioctl_alive:%d\n", ioctl_alive);
// open会直接开启对应的watchdog
wdt_fd = open(WATCHDOG_DEVICE, O_RDWR | O_NONBLOCK);
if (wdt_fd >= 0)
{
ioctl(wdt_fd, WDIOC_SETTIMEOUT, &timeout);
while (!flag)
{
if (ioctl_alive == 0) {
// 执行ping操作,喂狗
ioctl(wdt_fd, WDIOC_KEEPALIVE, NULL);
}
sleep(1);
}
// 关闭watchdog设备(需要驱动支持)
ops = WDIOS_DISABLECARD;
ioctl(wdt_fd, WDIOC_SETOPTIONS, &ops);
close(wdt_fd);
}
return 0;
}
应用层通过ioctl来管理和操作watchdog硬件,这里关键的命令说明如下。
//获取看门狗设备支持的功能和信息,返回watchdog_info
#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)
//定义用于通过IOCTL接口发送保持活动信号给看门狗设备,防止看门狗设备复位。
#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)
关于wdt的应用代码可参考:wdt_test.c。
直接开始下一节说明: 随机数驱动管理框架