4.3.14. 看门狗驱动调试指南

4.3.14.1. Watchdog 概述

看门狗(Watchdog Timer 或者 Watchdog)是用于监控计算机系统健康状态的一种硬件或软件机制。其主要目的是确保系统在异常情况下(例如挂起或崩溃)能够自动恢复,防止系统长时间失去响应,确保系统的可靠性。看门狗机制通常包括一个定时器,当系统没有在规定时间内发送“心跳”信号时,它会自动重启系统或执行其他恢复操作。

在芯片中,看门狗是 APB(Advanced Peripheral Bus)上的一个从设备。

4.3.14.2. Watchdog 的特点

芯片中 Watchdog 具有以下特点:

  1. APB3接口支持:兼容 APB3 接口。

  2. 倒计时超时指示:计数器从预设值倒计时到 0,表示发生超时。

  3. 系统复位触发:如果在第二次超时发生前中断未被清除,则会触发系统复位。

  4. 可编程超时范围:提供可编程的超时周期范围,可以在配置期间选择硬编码该值,以减少寄存器的需求。

  5. 双可编程超时周期:可选的双可编程超时周期,适用于首次启动等待时间与后续启动等待时间不同的情况,可以选择硬编码这些值。

  6. 复位脉冲长度设置:支持可编程和硬编码的复位脉冲长度。

  7. 防止意外重启:防止 DW_apb_wdt 计数器的意外重启。

  8. 防止意外禁用:防止 DW_apb_wdt 的意外禁用。

  9. 暂停模式支持:可选支持暂停模式,通过使用外部暂停使能信号实现。

  10. 测试模式信号:测试模式信号用于减少功能测试所需的时间。

4.3.14.3. 功能描述

Watchdog 典型应用

watchdog 是一种用于监控系统运行状态的机制,它能够在系统出现故障或卡死时进行自动重启。Linux系统中,watchdog 的典型的应用场景是确保系统在发生死锁、卡死或其他无法恢复的故障时能够自动重启,从而恢复正常工作。

下图描述的是 watchdog 使系统从死锁状态复位的典型应用场景:

watchdog_typical_applications

具体说明如下:

  1. 正常情况下:

    • 系统运行正常,watchdog 被定期喂狗。

    • 任务完成,没有死锁,watchdog 保持计时并不会触发重启。

  2. 死锁发生后:

    • 模块 A 和模块 B 发生死锁,系统进入无响应状态。

    • 由于死锁,系统无法完成周期性任务,因此也无法及时喂狗。

    • watchdog 超时,触发重启。

  3. 系统重启后:

    • 系统重新启动,模块 A 和 B 被初始化,死锁被消除。

    • watchdog 重新开始计时,继续监控系统状态。

Watchdog 功能原理

看门狗本质是一个定时器,一般有一个输入,称之为喂狗(kicking the dog/service the dog),SOC 正常工作的时候,每隔一段时间输出一个信号到看门狗,重置看门狗计时器。如果超过规定的时间不喂狗(一般在程序跑飞时),看门狗计时器会倒计时到 0,触发超时事件,这样看门狗就会给 SOC 输出一个复位信号,使系统重启。看门狗的作用就是防止程序跑飞导致系统崩溃。

Watchdog 的功能原理图如下所示:

watchdog_function_diagram_zh_CN

Watchdog 工作方式

Watchdog 的主要任务是监控系统的运行状态,防止系统因软件故障、死锁或其他异常情况导致无法自我恢复。它通过定时计数、超时检测和触发复位来实现这一功能。

Watchdog 的常见工作模式有如下几种:

  • 常规模式:软件定期重置 Watchdog 计时器。如果在规定的时间内没有“喂狗”信号,Watchdog 会触发复位。

  • 调试模式:Watchdog 在调试过程中可以暂停,避免调试时发生意外复位。通常需要外部信号或特殊控制指令来启动调试模式。

  • 复位保护模式:Watchdog 在遇到故障时会触发系统复位,但该复位过程会受到保护,防止因不必要的操作导致系统频繁重启。

Watchdog 基本工作流程

Watchdog 工作的核心机制是“计时器超时”和“复位”:

  • 初始化和配置:在系统启动时,Watchdog 被初始化并配置。系统设置超时时间(即 Watchdog 计时器的倒计时周期),通常由软件设置,也可以通过硬编码来实现。此时 Watchdog 开始计时。

  • “喂狗”机制:正常情况下,系统中的软件程序会定期给 Watchdog 发送信号,称为“喂狗”操作。这通常是通过重置 Watchdog 的计时器实现的。例如,软件每隔一定时间向 Watchdog 写入特定值,重置计时器的倒计时过程,确保 Watchdog 不会超时。

  • 超时检测:如果软件程序长时间没有进行“喂狗”操作(例如,程序发生死锁、崩溃或被挂起),Watchdog 计时器会倒计时到达设定的超时时间(通常为几秒钟至几分钟)。

  • 超时后的处理:当计时器倒计时到 0 时,Watchdog 会认为系统出现异常,自动触发预设的响应行为,通常是复位操作,重启系统或硬件模块,恢复系统到正常状态。

上述机制可以表示为下图:

Watchdog_work_flow

Watchdog 的工作方式细节

  1. 正常运行模式 在正常运行时,Watchdog 会定期接收到来自软件的“喂狗”信号。每次软件触发“喂狗”时,Watchdog 的计时器会被重置,重新开始计时。如果计时器周期内 Watchdog 没有收到“喂狗”信号,它会检测到超时并触发复位,重新启动系统。

  2. 超时处理 超时条件:如果 Watchdog 在设定的超时时间内未收到“喂狗”信号,说明系统可能出现了故障(例如程序死锁、死循环等)。 超时后复位:Watchdog 会触发系统复位。通常这是硬件级的复位操作,重启整个系统或部分硬件模块(如 CPU、外围设备等),清除当前的错误状态,确保系统能够继续运行。

  3. 配置和可编程性 超时时间(周期):Watchdog 的超时时间通常是可配置的。开发者可以根据需要设定适当的超时时间,短周期适合对实时性要求高的系统,长周期则适合对响应时间不那么敏感的系统。 双重超时段:一些 Watchdog 支持配置不同的超时段,如启动阶段与正常运行阶段的超时时间不同。启动阶段通常需要更长时间的等待,而在正常运行中,超时周期可以较短。 硬编码配置:为了减少对寄存器的频繁访问,一些 Watchdog 可以将配置值硬编码在硬件中,降低系统的复杂性和资源消耗。

  4. 防止误操作 防止禁用 Watchdog:为防止软件错误或恶意代码禁用 Watchdog,某些 Watchdog 系统提供“保护机制”,比如要求特定的安全序列才能禁用 Watchdog,或者在某些情况下(如调试模式)强制启用 Watchdog。 防止重启:一些 Watchdog 实现包括保护措施,以防止系统在未经授权或不必要的情况下反复重启。例如,在硬件设计中,可以使用额外的“复位保护”机制,以防止频繁复位。

  5. 外部控制和暂停 外部暂停信号:某些 Watchdog 允许外部信号暂停 Watchdog 的计时器,这对于调试或在某些特殊情况下暂停 Watchdog 非常有用。外部信号可以控制 Watchdog 是否继续计时,避免在调试过程中误触发复位。 暂停模式:在特定情况下,Watchdog 可以被配置为进入暂停模式,此时 Watchdog 不会触发复位,直到系统重新恢复正常工作。

  6. 测试模式 功能测试:在开发和验证过程中,Watchdog 通常会提供测试模式,用于模拟超时和复位行为。这有助于开发人员验证 Watchdog 是否能在预期条件下正常工作。

4.3.14.4. Watchdog 驱动代码说明

Watchdog 相关代码路径

drivers/watchdog/dw_wdt.c	# Watchdog 驱动代码源文件
include/linux/watchdog.h	# Watchdog 驱动代码头文件

Watchdog DTS配置

芯片中 Watchdog 控制器的设备树定义位于 BSP 源码包的 kernel文件夹下 的arch/arm64/boot/dts/hobot/x5.dtsi文件内:

a55_apb1 {
    ……
    watchdog: watchdog@34250000 {
        compatible = "snps,dw-wdt";
        status = "okay";
        reg = <0x34250000 0x10000>;
        clocks = <&hpsclks X5_WDT_PCLK>;
        interrupt-parent = <&gic>;
        interrupts = <GIC_SPI 71 IRQ_TYPE_LEVEL_HIGH>;
        timeout-sec = <10>;
        clk-rate-div = <2>;
        resets = <&socrst SOC_WDT_APB_RESET>;
    };

可以看出,watchdog 是挂载在芯片上 a55_apb1 总线上的一个设备。下面详细解释该设备树中各个字段的含义:

  1. watchdog

    • watchdog: watchdog@34250000

      • watchdog:是该设备的标签(label),可用于引用。

      • watchdog@34250000:是该设备的地址,在设备树中,这个表示设备的基地址。34250000 是该设备在系统内存中的物理地址。

  2. compatible

    • compatible = "snps,dw-wdt";

    • 这是一个设备兼容性描述符,用于指示驱动程序应当匹配哪些硬件。此处的 "snps,dw-wdt" 表示该设备是 Synopsys™ 提供的 dw-wdt(即设计 WARE Watchdog Timer),告诉操作系统应使用与之兼容的驱动程序来管理该硬件设备。

  3. status

    • status = "okay";

    • 该字段表示设备的状态。"okay" 表示该设备是启用的,可以被内核使用。如果该字段为 "disabled",则表示该设备在内核中被禁用。

  4. reg

    • reg = <0x34250000 0x10000>;

    • 该字段指定设备的寄存器范围。0x34250000 是设备的基地址,0x10000 表示该设备的地址范围(寄存器大小为 0x10000 字节)。这告诉内核如何映射硬件寄存器。

  5. clocks

    • clocks = <&hpsclks X5_WDT_PCLK>;

    • 这个字段指定该设备所依赖的时钟源。&hpsclks 是一个指向时钟控制器节点的引用,X5_WDT_PCLK 是一个具体的时钟源。内核会根据此信息来配置设备的时钟。

  6. interrupt-parent

    • interrupt-parent = <&gic>;

    • 该字段指定设备的中断控制器。在此例中,&gic 表示设备使用的中断控制器是 GIC(通用中断控制器)。这允许操作系统正确地配置设备中断。

  7. interrupts

    • interrupts = <GIC_SPI 71 IRQ_TYPE_LEVEL_HIGH>;

    • 该字段指定设备的中断信息。GIC_SPI 表示一个共享外部中断,71 是该设备的中断号,IRQ_TYPE_LEVEL_HIGH 表示该中断是一个高电平触发的中断。

  8. timeout-sec

    • timeout-sec = <10>;

    • 该字段指定看门狗定时器的超时时间(单位:秒)。在这个例子中,10 表示定时器的超时时间为 10 秒。如果系统在此时间内没有响应(例如没有重置看门狗定时器),则看门狗将触发系统重启。

  9. clk-rate-div

    • clk-rate-div = <2>;

    • 这个字段表示时钟频率的分频系数。在某些硬件平台中,定时器时钟是基于一个更高频率的时钟信号,分频系数决定了时钟信号的实际频率。这里的 2 表示时钟频率将被除以 2。

  10. resets

    • resets = <&socrst SOC_WDT_APB_RESET>;

    • 该字段指定与设备相关的复位控制信号。在此例中,&socrst 是复位控制器的节点引用,SOC_WDT_APB_RESET 是该设备的复位信号。这用于控制设备的复位操作。

备注: x5.dtsi 中的节点主要声明 SoC 共有特性,和具体电路板无关,一般情况下不用修改。

芯片的 Watchdog 控制器默认使能,如果需要再特定硬件上关闭 Watchdog 设备,请添加以下节点到对应的 dts 文件内:

&watchdog {
    status = "disabled";
};

Watchdog 内核配置

Watchdog 设备默认被使能,但是通过 HORIZON_WATCHDOG_ENABLE 来控制内核部分上电是否自动启动 Watchdog的计时器。

默认在 debug 版本中,Watchdog 设备会被注册,但是计时器不使能,可以在用户空间通过内核标准的 Watchdog 字符设备进行调试和验证:

/* arch/arm64/configs/hobot_x5_soc_defconfig */
CONFIG_WATCHDOG=y
CONFIG_DW_WATCHDOG=y

默认在 perf 版本中,Watchdog 设备会被注册,并且计时器将被使能:

/* arch/arm64/configs/hobot_x5_soc_perf_defconfig */
...
CONFIG_WATCHDOG=y
CONFIG_DW_WATCHDOG=y
CONFIG_HORIZON_WATCHDOG_ENABLE=y
...

CONFIG_HORIZON_WATCHDOG_ENABLE 配置会影响代码中 Watchdog 定时器是否启动,相关代码在 kernel/drivers/watchdog/dw_wdt.c 中:

static int dw_wdt_drv_probe(struct platform_device *pdev)
{
    ……
#ifdef CONFIG_HORIZON_WATCHDOG_ENABLE
    dw_wdt->wdt_disable = 0;
#else
    dw_wdt->wdt_disable = 1;
#endif

4.3.14.5. Watchdog 功能使用

Watchdog 寄存器说明

源码中使用宏定义描述了 watchdog 驱动所使用的的寄存器:

#define WDOG_CONTROL_REG_OFFSET		    0x00
#define WDOG_CONTROL_REG_WDT_EN_MASK	    0x01
#define WDOG_CONTROL_REG_RESP_MODE_MASK	    0x02
#define WDOG_TIMEOUT_RANGE_REG_OFFSET	    0x04
#define WDOG_TIMEOUT_RANGE_TOPINIT_SHIFT    4
#define WDOG_CURRENT_COUNT_REG_OFFSET	    0x08
#define WDOG_COUNTER_RESTART_REG_OFFSET     0x0c
#define WDOG_COUNTER_RESTART_KICK_VALUE	    0x76
#define WDOG_INTERRUPT_STATUS_REG_OFFSET    0x10
#define WDOG_INTERRUPT_CLEAR_REG_OFFSET     0x14
#define WDOG_COMP_PARAMS_5_REG_OFFSET       0xe4
#define WDOG_COMP_PARAMS_4_REG_OFFSET       0xe8
#define WDOG_COMP_PARAMS_3_REG_OFFSET       0xec
#define WDOG_COMP_PARAMS_2_REG_OFFSET       0xf0
#define WDOG_COMP_PARAMS_1_REG_OFFSET       0xf4
#define WDOG_COMP_PARAMS_1_USE_FIX_TOP      BIT(6)
#define WDOG_COMP_VERSION_REG_OFFSET        0xf8
#define WDOG_COMP_TYPE_REG_OFFSET           0xfc

/* There are sixteen TOPs (timeout periods) that can be set in the watchdog. */
#define DW_WDT_NUM_TOPS		16
#define DW_WDT_FIX_TOP(_idx)	(1U << (16 + _idx))

#define DW_WDT_DEFAULT_SECONDS	30

下面是这些宏定义的具体功能:

宏定义名称 描述
WDOG_CONTROL_REG_OFFSET 0x00 控制寄存器的偏移量。
WDOG_CONTROL_REG_WDT_EN_MASK 0x01 用于启用 watchdog 的掩码。
WDOG_CONTROL_REG_RESP_MODE_MASK 0x02 用于设置 watchdog 响应模式的掩码。
WDOG_TIMEOUT_RANGE_REG_OFFSET 0x04 超时范围寄存器的偏移量。
WDOG_TIMEOUT_RANGE_TOPINIT_SHIFT 4 超时范围寄存器中 TOP 初始化的位移量。
WDOG_CURRENT_COUNT_REG_OFFSET 0x08 当前计数寄存器的偏移量。
WDOG_COUNTER_RESTART_REG_OFFSET 0x0c 计数器重启寄存器的偏移量。
WDOG_COUNTER_RESTART_KICK_VALUE 0x76 重启计数器时写入的值。
WDOG_INTERRUPT_STATUS_REG_OFFSET 0x10 中断状态寄存器的偏移量。
WDOG_INTERRUPT_CLEAR_REG_OFFSET 0x14 中断清除寄存器的偏移量。
WDOG_COMP_PARAMS_5_REG_OFFSET 0xe4 组件参数寄存器5的偏移量。
WDOG_COMP_PARAMS_4_REG_OFFSET 0xe8 组件参数寄存器4的偏移量。
WDOG_COMP_PARAMS_3_REG_OFFSET 0xec 组件参数寄存器3的偏移量。
WDOG_COMP_PARAMS_2_REG_OFFSET 0xf0 组件参数寄存器2的偏移量。
WDOG_COMP_PARAMS_1_REG_OFFSET 0xf4 组件参数寄存器1的偏移量。
WDOG_COMP_PARAMS_1_USE_FIX_TOP BIT(6) 组件参数寄存器1中的位6,指示是否使用固定的 TOP 功能。
WDOG_COMP_VERSION_REG_OFFSET 0xf8 组件版本寄存器的偏移量。
WDOG_COMP_TYPE_REG_OFFSET 0xfc 组件类型寄存器的偏移量。
DW_WDT_NUM_TOPS 16 watchdog 支持的超时周期(TOPs)的数量。
DW_WDT_FIX_TOP(_idx) 1U << (16 + _idx) 用于生成固定 TOP 值的宏。
DW_WDT_DEFAULT_SECONDS 30 watchdog 的默认超时时间(秒)。

Watchdog 驱动关键结构体说明

1) dw_wdt_rmod

enum dw_wdt_rmod {
    DW_WDT_RMOD_RESET = 1,
    DW_WDT_RMOD_IRQ = 2
};

这个 dw_wdt_rmod 枚举类型,用于表示 Watchdog 设备(dw_wdt)的不同响应模式,它的成员说明如下:

  • DW_WDT_RMOD_RESET:值为1,表示当 watchdog 超时时,设备将触发系统重置(reset)。

  • DW_WDT_RMOD_IRQ:值为2,表示当 watchdog 超时时,设备将生成一个中断(IRQ)而不是重置系统。

使用场景:

  • 系统重置:在某些情况下,如果系统出现严重错误或无法恢复的状态,可以通过设置 DW_WDT_RMOD_RESET 模式使 watchdog 在超时时重置系统,以尝试恢复系统的正常运行。

  • 中断生成:在其他情况下,如果需要对超时事件进行更细粒度的控制或处理,可以通过设置 DW_WDT_RMOD_IRQ 模式使 watchdog 在超时时生成一个中断,然后由系统软件处理这个中断,执行相应的错误处理或日志记录操作。

2) dw_wdt_timeout

struct dw_wdt_timeout {
    u32 top_val;
    unsigned int sec;
    unsigned int msec;
};

dw_wdt_timeout 结构体,用于表示 Watchdog 设备(dw_wdt)的超时周期参数,其成员说明如下:

  • top_val:这是一个无符号32位整数,用于存储与超时周期相关的一个值,这个值通常与硬件寄存器中的设置有关。在 Watchdog 设备中,top_val 代表特定的超时周期值(TOPs),这些值用于配置 watchdog 的超时行为。

  • sec:这是一个无符号整数,表示超时周期的秒数部分。

  • msec:这也是一个无符号整数,表示超时周期的毫秒数部分。

3)dw_wdt

struct dw_wdt {
    void __iomem		*regs;
    struct clk		*clk;
    struct clk		*pclk;
    unsigned long		rate;
    enum dw_wdt_rmod	rmod;
    struct dw_wdt_timeout	timeouts[DW_WDT_NUM_TOPS];
    struct watchdog_device	wdd;
    struct reset_control	*rst;
    /* Save/restore */
    u32			control;
    u32			timeout;
#if IS_ENABLED(CONFIG_ARCH_HOBOT_X5)
    u32			clk_rate_div;
    u32			wdt_disable;
#endif

#ifdef CONFIG_DEBUG_FS
    struct dentry		*dbgfs_dir;
#endif
};

dw_wdt 结构体用例描述 Watchdog 设备,其成员说明如下:

  • regs:指向设备寄存器的指针,用于直接访问硬件寄存器。

  • clkpclk:分别表示 watchdog 定时器和 APB 总线的时钟,用于获取时钟频率并控制时钟的启用和禁用。

  • rate:表示 watchdog 定时器的时钟频率。

  • rmod:表示 watchdog 的响应模式,可以是重置(DW_WDT_RMOD_RESET)或中断(DW_WDT_RMOD_IRQ)。

  • timeouts:一个数组,存储了不同超时周期的值,允许 watchdog 有多个超时选项。

  • wdd:一个结构体,用于与 Linux 内核的 watchdog 框架进行交互。

  • rst:一个指针,指向 reset control 结构体,用于执行 watchdog 相关的复位操作。

  • controltimeout:用于保存 watchdog 控制寄存器和超时周期的当前值,以便在需要时恢复。

  • clk_rate_divwdt_disable:特定于 HOBOT_X5 架构的成员,用于时钟频率分频和禁用 watchdog。

  • dbgfs_dir:当配置了 CONFIG_DEBUG_FS 时,用于创建 debugfs 文件系统的目录项,便于调试。

这个结构体是 watchdog 驱动程序的核心数据结构,它包含了所有必要的信息来管理硬件 watchdog。驱动程序的初始化、配置、操作和清理函数都会使用这个结构体。

Watchdog 驱动接口函数

下面是源码中 watchdog 驱动相关的接口函数说明:

  1. dw_wdt_is_enabled(struct dw_wdt *dw_wdt)

    • 参数dw_wdt - 指向dw_wdt结构体的指针,代表watchdog设备的实例。

    • 功能:检查watchdog设备是否已经启用。

  2. dw_wdt_update_mode(struct dw_wdt *dw_wdt, enum dw_wdt_rmod rmod)

    • 参数

      • dw_wdt - 指向dw_wdt结构体的指针。

      • rmod - 新的响应模式,可以是DW_WDT_RMOD_RESETDW_WDT_RMOD_IRQ

    • 功能:更新watchdog设备的响应模式。

  3. dw_wdt_find_best_top(struct dw_wdt *dw_wdt, unsigned int timeout, u32 *top_val)

    • 参数

      • dw_wdt - 指向dw_wdt结构体的指针。

      • timeout - 请求的超时时间(秒)。

      • top_val - 指向存储最佳TOP值的变量的指针。

    • 功能:寻找最适合的超时周期(TOP)值。

  4. dw_wdt_get_min_timeout(struct dw_wdt *dw_wdt)

    • 参数dw_wdt - 指向dw_wdt结构体的指针。

    • 功能:获取watchdog设备的最小超时时间。

  5. dw_wdt_get_max_timeout_ms(struct dw_wdt *dw_wdt)

    • 参数dw_wdt - 指向dw_wdt结构体的指针。

    • 功能:获取watchdog设备的最大超时时间(毫秒)。

  6. dw_wdt_get_timeout(struct dw_wdt *dw_wdt)

    • 参数dw_wdt - 指向dw_wdt结构体的指针。

    • 功能:获取当前watchdog设备的超时时间。

  7. dw_wdt_ping(struct watchdog_device *wdd)

    • 参数wdd - 指向watchdog_device结构体的指针。

    • 功能:刷新watchdog设备的计时器。

  8. dw_wdt_set_timeout(struct watchdog_device *wdd, unsigned int top_s)

    • 参数

      • wdd - 指向watchdog_device结构体的指针。

      • top_s - 请求的超时时间(秒)。

    • 功能:设置watchdog设备的超时时间。

  9. dw_wdt_set_pretimeout(struct watchdog_device *wdd, unsigned int req)

    • 参数

      • wdd - 指向watchdog_device结构体的指针。

      • req - 请求的预超时时间。

    • 功能:设置预超时功能。

  10. dw_wdt_arm_system_reset(struct dw_wdt *dw_wdt)

    • 参数dw_wdt - 指向dw_wdt结构体的指针。

    • 功能:配置并启用watchdog设备,使其在超时后触发系统重置。

  11. dw_wdt_start(struct watchdog_device *wdd)

    • 参数wdd - 指向watchdog_device结构体的指针。

    • 功能:启动watchdog设备。

  12. dw_wdt_stop(struct watchdog_device *wdd)

    • 参数wdd - 指向watchdog_device结构体的指针。

    • 功能:停止watchdog设备。

  13. dw_wdt_restart(struct watchdog_device *wdd, unsigned long action, void *data)

    • 参数

      • wdd - 指向watchdog_device结构体的指针。

      • action - 重启动作。

      • data - 重启数据。

    • 功能:重启watchdog设备。

  14. dw_wdt_get_timeleft(struct watchdog_device *wdd)

    • 参数wdd - 指向watchdog_device结构体的指针。

    • 功能:获取watchdog设备剩余的时间。

  15. dw_wdt_irq(int irq, void *devid)

    • 参数

      • irq - 中断号。

      • devid - 设备实例指针。

    • 功能:watchdog设备的中断处理函数。

  16. dw_wdt_suspend(struct device *dev)

    • 参数dev - 指向device结构体的指针。

    • 功能:挂起watchdog设备。

  17. dw_wdt_resume(struct device *dev)

    • 参数dev - 指向device结构体的指针。

    • 功能:恢复watchdog设备。

  18. dw_wdt_handle_tops(struct dw_wdt *dw_wdt, const u32 *tops)

    • 参数

      • dw_wdt - 指向dw_wdt结构体的指针。

      • tops - 指向TOPs数组的指针。

    • 功能:处理和排序超时周期(TOPs)数组。

  19. dw_wdt_init_timeouts(struct dw_wdt *dw_wdt, struct device *dev)

    • 参数

      • dw_wdt - 指向dw_wdt结构体的指针。

      • dev - 指向device结构体的指针。

    • 功能:初始化watchdog设备的超时周期。

  20. dw_wdt_dbgfs_init(struct dw_wdt *dw_wdt)

    • 参数dw_wdt - 指向dw_wdt结构体的指针。

    • 功能:初始化debugfs接口。

  21. dw_wdt_dbgfs_clear(struct dw_wdt *dw_wdt)

    • 参数dw_wdt - 指向dw_wdt结构体的指针。

    • 功能:清除debugfs接口。

  22. dw_wdt_drv_probe(struct platform_device *pdev)

    • 参数pdev - 指向platform_device结构体的指针。

    • 功能:初始化平台设备驱动。

  23. dw_wdt_drv_remove(struct platform_device *pdev)

    • 参数pdev - 指向platform_device结构体的指针。

    • 功能:卸载平台设备驱动。

Watchdog 测试

注意,在进行 watchdog 测试之前,需要将看门狗计时器功能开启,即 CONFIG_HORIZON_WATCHDOG_ENABLE 需要使能,然后重新编译内核镜像,烧录新的内核镜像后才能进行测试。

# 重新配置 kernel config 文件,使能 CONFIG_HORIZON_WATCHDOG_ENABLE
./bd.sh boot menuconfig
# 重新编译内核镜像
./bd.sh boot

watchdog debugfs

在 watchdog 函数接口中可以看到几个和 debugfs 相关的函数,这些函数原型如下:

static const struct debugfs_reg32 dw_wdt_dbgfs_regs[] = {
    DW_WDT_DBGFS_REG("cr", WDOG_CONTROL_REG_OFFSET),
    DW_WDT_DBGFS_REG("torr", WDOG_TIMEOUT_RANGE_REG_OFFSET),
    DW_WDT_DBGFS_REG("ccvr", WDOG_CURRENT_COUNT_REG_OFFSET),
    DW_WDT_DBGFS_REG("crr", WDOG_COUNTER_RESTART_REG_OFFSET),
    DW_WDT_DBGFS_REG("stat", WDOG_INTERRUPT_STATUS_REG_OFFSET),
    DW_WDT_DBGFS_REG("param5", WDOG_COMP_PARAMS_5_REG_OFFSET),
    DW_WDT_DBGFS_REG("param4", WDOG_COMP_PARAMS_4_REG_OFFSET),
    DW_WDT_DBGFS_REG("param3", WDOG_COMP_PARAMS_3_REG_OFFSET),
    DW_WDT_DBGFS_REG("param2", WDOG_COMP_PARAMS_2_REG_OFFSET),
    DW_WDT_DBGFS_REG("param1", WDOG_COMP_PARAMS_1_REG_OFFSET),
    DW_WDT_DBGFS_REG("version", WDOG_COMP_VERSION_REG_OFFSET),
    DW_WDT_DBGFS_REG("type", WDOG_COMP_TYPE_REG_OFFSET)
};

static void dw_wdt_dbgfs_init(struct dw_wdt *dw_wdt)
{
    struct device *dev = dw_wdt->wdd.parent;
    struct debugfs_regset32 *regset;

    regset = devm_kzalloc(dev, sizeof(*regset), GFP_KERNEL);
    if (!regset)
        return;

    regset->regs = dw_wdt_dbgfs_regs;
    regset->nregs = ARRAY_SIZE(dw_wdt_dbgfs_regs);
    regset->base = dw_wdt->regs;

    dw_wdt->dbgfs_dir = debugfs_create_dir(dev_name(dev), NULL);

    debugfs_create_regset32("registers", 0444, dw_wdt->dbgfs_dir, regset);
}

static void dw_wdt_dbgfs_clear(struct dw_wdt *dw_wdt)
{
    debugfs_remove_recursive(dw_wdt->dbgfs_dir);
}

这组函数的功能就是在 linux 系统的 debugfs 中创建一个名为 registers 的只读节点(0444 权限),这样就可以通过 debugfs 接口方便地查看设备的寄存器(dw_wdt_dbgfs_regs 数组中包括的寄存器)状态,帮助调试和分析设备的工作情况。

调试日志如下:

root@buildroot:/sys/kernel/debug/34250000.watchdog# cat registers
cr = 0x00000019
torr = 0x0000000e
ccvr = 0x29a5f5d9
crr = 0x00000000
stat = 0x00000000
param5 = 0x00000000
param4 = 0x00000000
param3 = 0x00000000
param2 = 0x0000ffff
param1 = 0x10001a50
version = 0x3131332a
type = 0x44570120

watchdog 测例

下面提供一个简单的 watchdog 测例:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>  // UNIX Standard Function Definitions
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>  // file control definition
#include <termios.h>    // PPSIX terminal control definition
#include <errno.h>  // Error number definition
#include <pthread.h>
#include <linux/watchdog.h>
#include <string.h>
#include <sys/ioctl.h>

int watchdogfd;
int feeddog = 1;

void* feeddogthread()
{
    int feeddogvalue;
    int returnval;

    feeddogvalue = 65535;

    while (feeddog) {
        printf("feed dog\n");
        returnval = write(watchdogfd, &feeddogvalue, sizeof(int));
        sleep(5);  // Every 5s seconds, the value of the watchdog counter register will be reloaded
    }
}

void watchdog_demo_help() {
    printf("Usage: watchdog_program [option]\n");
    printf("Options:\n");
    printf("  h      Show this help message\n");
    printf("  g      Get the currently set watchdog timeout period\n");
    printf("  e      Exit the watchdog test program\n");
    printf("  t      Get the remaining time before the watchdog triggers timeout\n");
    printf("  r      Restart the watchdog\n");
    // Add more options here if necessary
}

int main()
{
    pthread_t watchdogThd;
    //int watchdogfd;
    int returnval;
    char readline[32], *p;

    // open watchdog device
    if ((watchdogfd = open("/dev/watchdog", O_RDWR|O_NONBLOCK)) < 0) {
        printf("cannot open the watchdog device\n");
        exit(0);
    }

    int timeout = 10;  // Warning! The value will be parsed as a hexadecimal number in ioctl.
    int timeleft;
    ioctl(watchdogfd, WDIOC_SETTIMEOUT, &timeout);
    printf("The watchdog timeout was set to %d seconds\n", timeout);

    // Creating a dog feeding thread
    returnval = pthread_create(&watchdogThd, NULL, feeddogthread, NULL);
    if (returnval < 0)
        printf("cannot create feeddog thread\n");

    while (1) {
        printf("Command (e quit): ");
        memset(readline, '\0', sizeof(readline));
        fgets(readline, sizeof(readline), stdin);

        /* Remove the first null character of a string */
        p = readline;
        while(*p == ' ' || *p == '\t')
                p++;

        switch(*p) {
        case 'h':
            watchdog_demo_help();  // Call help function to display options
            break;
        case 'g':
            ioctl(watchdogfd, WDIOC_GETTIMEOUT, &timeout);
            printf("The timeout was is %d seconds\n", timeout);
            break;
        case 'e':
            printf("Close watchdog an exit safety!\n");
            //write(watchdogfd, "V", 1);
            int disable_dog = WDIOS_DISABLECARD;
            ioctl(watchdogfd, WDIOC_SETOPTIONS, &disable_dog);
            close(watchdogfd);
            return 0;
        case 's':
            printf("stop feed dog\n");
            feeddog = 0;
            break;
        case 't':
            ioctl(watchdogfd, WDIOC_GETTIMELEFT, &timeleft);
            printf("The timeout was is %d seconds\n", timeleft);
            break;
        case 'r':
            printf("we don't close watchdog. The machine will reboot in a few seconds!\n");
            printf("wait......\n");
            break;
        default:
            printf("get error char: %c,it's an invalid option. Type 'h' for help.\n", *p);
        }
    }

    return 0;
}

这段代码的功能是通过操作看门狗设备 /dev/watchdog 来实现看门狗的管理和交互,并实现通过命令行控制看门狗超时、重启、喂狗等操作。具体功能如下:

  1. 打开看门狗设备

    • 通过 open("/dev/watchdog", O_RDWR | O_NONBLOCK) 打开 /dev/watchdog 设备,允许读写操作,并以非阻塞模式打开。

  2. 设置看门狗超时时间

    • 使用 ioctl(watchdogfd, WDIOC_SETTIMEOUT, &timeout) 设置看门狗的超时时间。默认设置为 10 秒。超时后,如果没有喂狗,系统会触发重启。

  3. 喂狗线程

    • 创建一个名为 feeddogthread 的线程,这个线程每 5 秒钟向看门狗设备写入一个值(65535),用于”喂狗”,防止看门狗超时触发系统重启。

  4. 用户交互命令

    • 主线程通过一个简单的命令行界面等待用户输入。用户可以输入以下命令来控制看门狗:

      • h:显示帮助信息,列出可用的命令。

      • g:获取当前设置的看门狗超时时间。

      • e:退出程序,并安全关闭看门狗设备。

      • s:停止喂狗(通过设置 feeddog 为 0,停止线程的循环)。

      • t:获取当前剩余的看门狗超时时间。

      • r:提示系统将重启,但实际上并没有关闭看门狗,只是输出提示信息。

  5. 线程管理

    • pthread_create 创建了一个独立的线程用于喂狗操作。主线程则持续等待并处理用户输入。

  6. 关闭看门狗

    • 当用户选择退出 (e),程序通过 ioctl(watchdogfd, WDIOC_SETOPTIONS, &disable_dog) 禁用看门狗,并关闭设备文件。

测例编译: 注意,需要使用 sdk 的编译工具链编译。

/opt/arm-gnu-toolchain-11.3.rel1-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-gcc watchdog_demo.c -o watchdog_demo

将生成的 watchdog_demo 传到单板上。

测例日志:

root@buildroot:/userdata# chmod +x watchdog_demo
root@buildroot:/userdata# ./watchdog_demo
The watchdog timeout was set to 10 seconds
Command (e quit): g
The timeout was is 10 seconds
Command (e quit): t
The timeout was is 7 seconds
Command (e quit): t
The timeout was is 6 seconds
Command (e quit): t
The timeout was is 5 seconds
Command (e quit): feed dog # 喂狗
t
The timeout was is 8 seconds # 看门狗时间重置了,再次从 10 开始倒计时
Command (e quit): t
The timeout was is 7 seconds

模拟看门狗触发复位日志:

# 停止喂狗,倒计时10秒没有喂狗会导致系统复位
Command (e quit): s
stop feed dog

# 等待重启后,查看复位日志
root@buildroot:~# cd /userdata/log/
root@buildroot:/userdata/log# cat reset_reason.txt
……
# 最后一行就是上一次系统重启的原因
1970-01-01-00-00-03: WATCHDOG   normal          LNX6.1.83_PL5.1_V1.0.14_20241212-1614   0068

可以在 reset_reason.txt 中看到 WATCHDOG 的记录,这就说明测例模拟看门狗触发复位成功。

4.3.14.6. 常见问题

在使用 Watchdog 过程中,可能会遇到一些问题,以下是几个常见的情况和问题解决建议:

  1. Watchdog 未能触发复位

    • 问题描述:系统没有在 Watchdog 超时后进行复位,可能会导致系统依然停留在死锁或异常状态。

    • 可能原因

      • Watchdog 驱动未正确加载或配置:驱动可能未启动或配置不当。

      • 硬件故障:Watchdog 的硬件可能存在问题,导致它无法触发复位。

      • 内核配置问题:内核未启用相关的 Watchdog 驱动或模块。

    • 解决方法

      • 检查 Watchdog 驱动是否已正确加载,查看 dmesg 日志确认是否有启动错误。

      • 确认硬件连接正常,特别是与 Watchdog 相关的电路和芯片。

      • 确认内核配置文件(/boot/config-$(uname -r))中是否启用了 Watchdog 驱动模块。

  2. Watchdog 无法定期喂狗

    • 问题描述:系统未能按预期定期喂狗,导致 Watchdog 触发复位。

    • 可能原因

      • 定时任务或应用程序未能及时喂狗:应用程序或内核模块没有按时喂狗,可能是由于性能瓶颈、延迟或死锁。

      • Watchdog 计时器配置不当:配置的超时时间过短,系统可能无法及时完成喂狗操作。

    • 解决方法

      • 确保系统中定时喂狗的任务(如 crontab 或内核定时器)正常运行,避免任务阻塞或长时间占用 CPU。

      • 增加 Watchdog 的超时时间,避免它触发复位。

      • 使用工具(如 watchdog 命令行工具)手动检查 Watchdog 状态。

  3. Watchdog 导致系统频繁重启

    • 问题描述:系统被 Watchdog 多次重启,可能是由于 Watchdog 提前触发了复位。

    • 可能原因

      • Watchdog 超时时间过短:超时时间过短,系统还未完成正常工作就会触发复位。

      • 系统资源消耗过大:系统负载过高,导致无法及时喂狗或完成其他重要任务。

    • 解决方法

      • 调整 Watchdog 的超时时间,使其适应系统的响应时间。

      • 优化系统性能,减少过载情况,尤其是 CPU 或内存使用过高的情况。

      • 使用工具监控系统性能,查找资源消耗瓶颈。

  4. Watchdog 监控的硬件或设备失效

    • 问题描述:Watchdog 硬件本身出现故障,导致无法正确监控系统。

    • 可能原因

      • 硬件损坏:Watchdog 芯片或其相关电路损坏。

      • 驱动不兼容:使用的 Watchdog 驱动与硬件不兼容。

    • 解决方法

      • 检查硬件连接和电源,确保 Watchdog 芯片和相关电路正常工作。

      • 更新或替换驱动程序,确保其与硬件兼容。

      • 使用其他工具(如硬件诊断工具)测试 Watchdog 硬件是否正常。

  5. Watchdog 被错误地禁用或关闭

    • 问题描述:Watchdog 驱动被误禁用,导致无法发挥作用。

    • 可能原因

      • 用户或程序手动禁用 Watchdog:可能是某些程序或配置禁用了 Watchdog 驱动。

      • 内核模块未加载:Watchdog 驱动未被加载到内核中。

    • 解决方法

      • 检查 /etc/watchdog.conf 文件或其他配置文件,确保没有禁用 Watchdog。

      • 确认 Watchdog 驱动已在内核中加载并正常运行。

      • 检查 dmesg 日志,查看是否有与 Watchdog 相关的禁用或错误信息。

  6. Watchdog 复位时未能正常重启系统

    • 问题描述:Watchdog 触发复位后,系统未能如预期般重启,可能停在某个状态。

    • 可能原因

      • 内核问题:系统可能未能正确处理复位请求,导致无法完成复位。

      • 硬件问题:复位信号可能未被硬件正确接收或处理。

    • 解决方法

      • 检查内核日志,确认复位过程是否成功。

      • 确认 Watchdog 复位信号与硬件是否正确连接,确保系统可以正常重启。

      • 确保内核支持硬件复位功能。

  7. Watchdog 与系统其他监控工具冲突

    • 问题描述:系统上使用了多个监控工具,如 systemd 的守护进程、monit 等,它们可能与 Watchdog 冲突。

    • 可能原因

      • 多重监控:多个监控工具可能会尝试独立地重启系统或管理 Watchdog,从而造成冲突。

    • 解决方法

      • 确保只使用一个监控工具来管理 Watchdog,避免冲突。

      • 配置合适的优先级,确保系统监控的一致性。