build_embed_linux_system

Watchdog设备管理框架

WDT又被称为看门狗,是用于监视系统运行的模块。其主要功能用于当系统跑飞或者死机时, 无法进行喂狗时, 产生某些动作重启设备。目前主流的看门狗有内部的系统看门狗,以及外部的硬件看门狗;虽然功能和配置可能不同,但其控制原理都类似。

  1. 配置看门狗触发时间,使能看门狗(部分硬件看门狗可能默认开启或由引脚状态控制开启)。
  2. 定时去执行喂狗动作,内部看门狗一般为特定寄存器写入时序;外部看门狗可能是通过通讯接口或者引脚产生时序。
  3. 系统异常等原因,导致无法喂狗,超时后则触发系统复位。

看门狗是芯片硬件的功能,可以实现报警和复位功能。对于WDT框架,只是提供应用层到硬件的接口,主要实现看门狗使能,周期性喂狗,以及停止等功能。

本节目录如下所示。

wdt_interface

在嵌入式Linux系统中,看门狗模块作为字符设备被访问,通过ioctl去操作。看门狗包含的代码实现如下。

对于watchdog的结构如下所示。

image

可以看到,watchdog是芯片内部模块,由platform总线统一管理。其中设备由解析设备树创建,驱动则包含两部分。

  1. 用于注册到watchdog到内核的接口,主要在watchdog_core和watchdog_dev中实现。
  2. 访问物理模块寄存器的实现,这部分主要基于regmap mmio模块访问内部模块的寄存器。

看门狗访问的接口如下所示。

//向系统中注册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设备注册时,会进行赋值或者处理,并不需要每个都理解,下面驱动中会对涉及选项进行说明,其它部分可以参考上面的结构体说明去理解。

wdt_hardware

看门狗作为内部模块,更重要的部分是模块功能和状态寄存器,用于实现对看门狗模块的管理。这里以I.MX6ULL为例,可以作为参考。

image

可以看到,看门狗寄存器范围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)
};

其中中断线号说明如下所示。

image

wdt_driver

看门狗驱动的基本原理是通过定时器来监控系统的运行状态。如果系统在规定的时间内没有“喂狗”(即重置定时器),看门狗定时器就会溢出,触发系统重启。在Linux内核中,看门狗驱动通常作为一个平台设备驱动来实现。另外wdog作为内部模块,也使用platform总线统一管理,具体步骤如下所示。

  1. 匹配设备树节点,加载驱动的接口
  2. 获取设备树硬件资源,实现操作看门狗硬件的接口
  3. 向内核注册看门狗设备,提供应用层访问时操作的底层接口

具体流程如下所示。

//匹配设备树中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*

查看,具体如下。

image

上述代码为访问内部的wdt设备,具体代码参考地址: kernel_wdt.c

wdt_application

对于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

next_chapter

返回目录

直接开始下一节说明: 随机数驱动管理框架