4.3.14. 看门狗驱动调试指南
4.3.14.1. Watchdog 概述
看门狗(Watchdog Timer 或者 Watchdog)是用于监控计算机系统健康状态的一种硬件或软件机制。其主要目的是确保系统在异常情况下(例如挂起或崩溃)能够自动恢复,防止系统长时间失去响应,确保系统的可靠性。看门狗机制通常包括一个定时器,当系统没有在规定时间内发送“心跳”信号时,它会自动重启系统或执行其他恢复操作。
在芯片中,看门狗是 APB(Advanced Peripheral Bus)上的一个从设备。
4.3.14.2. Watchdog 的特点
芯片中 Watchdog 具有以下特点:
APB3接口支持:兼容 APB3 接口。
倒计时超时指示:计数器从预设值倒计时到 0,表示发生超时。
系统复位触发:如果在第二次超时发生前中断未被清除,则会触发系统复位。
可编程超时范围:提供可编程的超时周期范围,可以在配置期间选择硬编码该值,以减少寄存器的需求。
双可编程超时周期:可选的双可编程超时周期,适用于首次启动等待时间与后续启动等待时间不同的情况,可以选择硬编码这些值。
复位脉冲长度设置:支持可编程和硬编码的复位脉冲长度。
防止意外重启:防止 DW_apb_wdt 计数器的意外重启。
防止意外禁用:防止 DW_apb_wdt 的意外禁用。
暂停模式支持:可选支持暂停模式,通过使用外部暂停使能信号实现。
测试模式信号:测试模式信号用于减少功能测试所需的时间。
4.3.14.3. 功能描述
Watchdog 典型应用
watchdog 是一种用于监控系统运行状态的机制,它能够在系统出现故障或卡死时进行自动重启。Linux系统中,watchdog 的典型的应用场景是确保系统在发生死锁、卡死或其他无法恢复的故障时能够自动重启,从而恢复正常工作。
下图描述的是 watchdog 使系统从死锁状态复位的典型应用场景:

具体说明如下:
正常情况下:
系统运行正常,watchdog 被定期喂狗。
任务完成,没有死锁,watchdog 保持计时并不会触发重启。
死锁发生后:
模块 A 和模块 B 发生死锁,系统进入无响应状态。
由于死锁,系统无法完成周期性任务,因此也无法及时喂狗。
watchdog 超时,触发重启。
系统重启后:
系统重新启动,模块 A 和 B 被初始化,死锁被消除。
watchdog 重新开始计时,继续监控系统状态。
Watchdog 功能原理
看门狗本质是一个定时器,一般有一个输入,称之为喂狗(kicking the dog/service the dog),SOC 正常工作的时候,每隔一段时间输出一个信号到看门狗,重置看门狗计时器。如果超过规定的时间不喂狗(一般在程序跑飞时),看门狗计时器会倒计时到 0,触发超时事件,这样看门狗就会给 SOC 输出一个复位信号,使系统重启。看门狗的作用就是防止程序跑飞导致系统崩溃。
Watchdog 的功能原理图如下所示:

Watchdog 工作方式
Watchdog 的主要任务是监控系统的运行状态,防止系统因软件故障、死锁或其他异常情况导致无法自我恢复。它通过定时计数、超时检测和触发复位来实现这一功能。
Watchdog 的常见工作模式有如下几种:
常规模式:软件定期重置 Watchdog 计时器。如果在规定的时间内没有“喂狗”信号,Watchdog 会触发复位。
调试模式:Watchdog 在调试过程中可以暂停,避免调试时发生意外复位。通常需要外部信号或特殊控制指令来启动调试模式。
复位保护模式:Watchdog 在遇到故障时会触发系统复位,但该复位过程会受到保护,防止因不必要的操作导致系统频繁重启。
Watchdog 基本工作流程
Watchdog 工作的核心机制是“计时器超时”和“复位”:
初始化和配置:在系统启动时,Watchdog 被初始化并配置。系统设置超时时间(即 Watchdog 计时器的倒计时周期),通常由软件设置,也可以通过硬编码来实现。此时 Watchdog 开始计时。
“喂狗”机制:正常情况下,系统中的软件程序会定期给 Watchdog 发送信号,称为“喂狗”操作。这通常是通过重置 Watchdog 的计时器实现的。例如,软件每隔一定时间向 Watchdog 写入特定值,重置计时器的倒计时过程,确保 Watchdog 不会超时。
超时检测:如果软件程序长时间没有进行“喂狗”操作(例如,程序发生死锁、崩溃或被挂起),Watchdog 计时器会倒计时到达设定的超时时间(通常为几秒钟至几分钟)。
超时后的处理:当计时器倒计时到 0 时,Watchdog 会认为系统出现异常,自动触发预设的响应行为,通常是复位操作,重启系统或硬件模块,恢复系统到正常状态。
上述机制可以表示为下图:

Watchdog 的工作方式细节
正常运行模式 在正常运行时,Watchdog 会定期接收到来自软件的“喂狗”信号。每次软件触发“喂狗”时,Watchdog 的计时器会被重置,重新开始计时。如果计时器周期内 Watchdog 没有收到“喂狗”信号,它会检测到超时并触发复位,重新启动系统。
超时处理 超时条件:如果 Watchdog 在设定的超时时间内未收到“喂狗”信号,说明系统可能出现了故障(例如程序死锁、死循环等)。 超时后复位:Watchdog 会触发系统复位。通常这是硬件级的复位操作,重启整个系统或部分硬件模块(如 CPU、外围设备等),清除当前的错误状态,确保系统能够继续运行。
配置和可编程性 超时时间(周期):Watchdog 的超时时间通常是可配置的。开发者可以根据需要设定适当的超时时间,短周期适合对实时性要求高的系统,长周期则适合对响应时间不那么敏感的系统。 双重超时段:一些 Watchdog 支持配置不同的超时段,如启动阶段与正常运行阶段的超时时间不同。启动阶段通常需要更长时间的等待,而在正常运行中,超时周期可以较短。 硬编码配置:为了减少对寄存器的频繁访问,一些 Watchdog 可以将配置值硬编码在硬件中,降低系统的复杂性和资源消耗。
防止误操作 防止禁用 Watchdog:为防止软件错误或恶意代码禁用 Watchdog,某些 Watchdog 系统提供“保护机制”,比如要求特定的安全序列才能禁用 Watchdog,或者在某些情况下(如调试模式)强制启用 Watchdog。 防止重启:一些 Watchdog 实现包括保护措施,以防止系统在未经授权或不必要的情况下反复重启。例如,在硬件设计中,可以使用额外的“复位保护”机制,以防止频繁复位。
外部控制和暂停 外部暂停信号:某些 Watchdog 允许外部信号暂停 Watchdog 的计时器,这对于调试或在某些特殊情况下暂停 Watchdog 非常有用。外部信号可以控制 Watchdog 是否继续计时,避免在调试过程中误触发复位。 暂停模式:在特定情况下,Watchdog 可以被配置为进入暂停模式,此时 Watchdog 不会触发复位,直到系统重新恢复正常工作。
测试模式 功能测试:在开发和验证过程中,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 总线上的一个设备。下面详细解释该设备树中各个字段的含义:
watchdog:
watchdog: watchdog@34250000:watchdog:是该设备的标签(label),可用于引用。watchdog@34250000:是该设备的地址,在设备树中,这个表示设备的基地址。34250000是该设备在系统内存中的物理地址。
compatible:
compatible = "snps,dw-wdt";这是一个设备兼容性描述符,用于指示驱动程序应当匹配哪些硬件。此处的
"snps,dw-wdt"表示该设备是 Synopsys™ 提供的dw-wdt(即设计 WARE Watchdog Timer),告诉操作系统应使用与之兼容的驱动程序来管理该硬件设备。
status:
status = "okay";该字段表示设备的状态。
"okay"表示该设备是启用的,可以被内核使用。如果该字段为"disabled",则表示该设备在内核中被禁用。
reg:
reg = <0x34250000 0x10000>;该字段指定设备的寄存器范围。
0x34250000是设备的基地址,0x10000表示该设备的地址范围(寄存器大小为 0x10000 字节)。这告诉内核如何映射硬件寄存器。
clocks:
clocks = <&hpsclks X5_WDT_PCLK>;这个字段指定该设备所依赖的时钟源。
&hpsclks是一个指向时钟控制器节点的引用,X5_WDT_PCLK是一个具体的时钟源。内核会根据此信息来配置设备的时钟。
interrupt-parent:
interrupt-parent = <&gic>;该字段指定设备的中断控制器。在此例中,
&gic表示设备使用的中断控制器是 GIC(通用中断控制器)。这允许操作系统正确地配置设备中断。
interrupts:
interrupts = <GIC_SPI 71 IRQ_TYPE_LEVEL_HIGH>;该字段指定设备的中断信息。
GIC_SPI表示一个共享外部中断,71是该设备的中断号,IRQ_TYPE_LEVEL_HIGH表示该中断是一个高电平触发的中断。
timeout-sec:
timeout-sec = <10>;该字段指定看门狗定时器的超时时间(单位:秒)。在这个例子中,
10表示定时器的超时时间为 10 秒。如果系统在此时间内没有响应(例如没有重置看门狗定时器),则看门狗将触发系统重启。
clk-rate-div:
clk-rate-div = <2>;这个字段表示时钟频率的分频系数。在某些硬件平台中,定时器时钟是基于一个更高频率的时钟信号,分频系数决定了时钟信号的实际频率。这里的
2表示时钟频率将被除以 2。
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:指向设备寄存器的指针,用于直接访问硬件寄存器。clk和pclk:分别表示 watchdog 定时器和 APB 总线的时钟,用于获取时钟频率并控制时钟的启用和禁用。rate:表示 watchdog 定时器的时钟频率。rmod:表示 watchdog 的响应模式,可以是重置(DW_WDT_RMOD_RESET)或中断(DW_WDT_RMOD_IRQ)。timeouts:一个数组,存储了不同超时周期的值,允许 watchdog 有多个超时选项。wdd:一个结构体,用于与 Linux 内核的 watchdog 框架进行交互。rst:一个指针,指向 reset control 结构体,用于执行 watchdog 相关的复位操作。control和timeout:用于保存 watchdog 控制寄存器和超时周期的当前值,以便在需要时恢复。clk_rate_div和wdt_disable:特定于 HOBOT_X5 架构的成员,用于时钟频率分频和禁用 watchdog。dbgfs_dir:当配置了 CONFIG_DEBUG_FS 时,用于创建 debugfs 文件系统的目录项,便于调试。
这个结构体是 watchdog 驱动程序的核心数据结构,它包含了所有必要的信息来管理硬件 watchdog。驱动程序的初始化、配置、操作和清理函数都会使用这个结构体。
Watchdog 驱动接口函数
下面是源码中 watchdog 驱动相关的接口函数说明:
dw_wdt_is_enabled(struct dw_wdt *dw_wdt):参数:
dw_wdt- 指向dw_wdt结构体的指针,代表watchdog设备的实例。功能:检查watchdog设备是否已经启用。
dw_wdt_update_mode(struct dw_wdt *dw_wdt, enum dw_wdt_rmod rmod):参数:
dw_wdt- 指向dw_wdt结构体的指针。rmod- 新的响应模式,可以是DW_WDT_RMOD_RESET或DW_WDT_RMOD_IRQ。
功能:更新watchdog设备的响应模式。
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)值。
dw_wdt_get_min_timeout(struct dw_wdt *dw_wdt):参数:
dw_wdt- 指向dw_wdt结构体的指针。功能:获取watchdog设备的最小超时时间。
dw_wdt_get_max_timeout_ms(struct dw_wdt *dw_wdt):参数:
dw_wdt- 指向dw_wdt结构体的指针。功能:获取watchdog设备的最大超时时间(毫秒)。
dw_wdt_get_timeout(struct dw_wdt *dw_wdt):参数:
dw_wdt- 指向dw_wdt结构体的指针。功能:获取当前watchdog设备的超时时间。
dw_wdt_ping(struct watchdog_device *wdd):参数:
wdd- 指向watchdog_device结构体的指针。功能:刷新watchdog设备的计时器。
dw_wdt_set_timeout(struct watchdog_device *wdd, unsigned int top_s):参数:
wdd- 指向watchdog_device结构体的指针。top_s- 请求的超时时间(秒)。
功能:设置watchdog设备的超时时间。
dw_wdt_set_pretimeout(struct watchdog_device *wdd, unsigned int req):参数:
wdd- 指向watchdog_device结构体的指针。req- 请求的预超时时间。
功能:设置预超时功能。
dw_wdt_arm_system_reset(struct dw_wdt *dw_wdt):参数:
dw_wdt- 指向dw_wdt结构体的指针。功能:配置并启用watchdog设备,使其在超时后触发系统重置。
dw_wdt_start(struct watchdog_device *wdd):参数:
wdd- 指向watchdog_device结构体的指针。功能:启动watchdog设备。
dw_wdt_stop(struct watchdog_device *wdd):参数:
wdd- 指向watchdog_device结构体的指针。功能:停止watchdog设备。
dw_wdt_restart(struct watchdog_device *wdd, unsigned long action, void *data):参数:
wdd- 指向watchdog_device结构体的指针。action- 重启动作。data- 重启数据。
功能:重启watchdog设备。
dw_wdt_get_timeleft(struct watchdog_device *wdd):参数:
wdd- 指向watchdog_device结构体的指针。功能:获取watchdog设备剩余的时间。
dw_wdt_irq(int irq, void *devid):参数:
irq- 中断号。devid- 设备实例指针。
功能:watchdog设备的中断处理函数。
dw_wdt_suspend(struct device *dev):参数:
dev- 指向device结构体的指针。功能:挂起watchdog设备。
dw_wdt_resume(struct device *dev):参数:
dev- 指向device结构体的指针。功能:恢复watchdog设备。
dw_wdt_handle_tops(struct dw_wdt *dw_wdt, const u32 *tops):参数:
dw_wdt- 指向dw_wdt结构体的指针。tops- 指向TOPs数组的指针。
功能:处理和排序超时周期(TOPs)数组。
dw_wdt_init_timeouts(struct dw_wdt *dw_wdt, struct device *dev):参数:
dw_wdt- 指向dw_wdt结构体的指针。dev- 指向device结构体的指针。
功能:初始化watchdog设备的超时周期。
dw_wdt_dbgfs_init(struct dw_wdt *dw_wdt):参数:
dw_wdt- 指向dw_wdt结构体的指针。功能:初始化debugfs接口。
dw_wdt_dbgfs_clear(struct dw_wdt *dw_wdt):参数:
dw_wdt- 指向dw_wdt结构体的指针。功能:清除debugfs接口。
dw_wdt_drv_probe(struct platform_device *pdev):参数:
pdev- 指向platform_device结构体的指针。功能:初始化平台设备驱动。
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 来实现看门狗的管理和交互,并实现通过命令行控制看门狗超时、重启、喂狗等操作。具体功能如下:
打开看门狗设备:
通过
open("/dev/watchdog", O_RDWR | O_NONBLOCK)打开/dev/watchdog设备,允许读写操作,并以非阻塞模式打开。
设置看门狗超时时间:
使用
ioctl(watchdogfd, WDIOC_SETTIMEOUT, &timeout)设置看门狗的超时时间。默认设置为10秒。超时后,如果没有喂狗,系统会触发重启。
喂狗线程:
创建一个名为
feeddogthread的线程,这个线程每 5 秒钟向看门狗设备写入一个值(65535),用于”喂狗”,防止看门狗超时触发系统重启。
用户交互命令:
主线程通过一个简单的命令行界面等待用户输入。用户可以输入以下命令来控制看门狗:
h:显示帮助信息,列出可用的命令。g:获取当前设置的看门狗超时时间。e:退出程序,并安全关闭看门狗设备。s:停止喂狗(通过设置feeddog为 0,停止线程的循环)。t:获取当前剩余的看门狗超时时间。r:提示系统将重启,但实际上并没有关闭看门狗,只是输出提示信息。
线程管理:
pthread_create创建了一个独立的线程用于喂狗操作。主线程则持续等待并处理用户输入。
关闭看门狗:
当用户选择退出 (
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 过程中,可能会遇到一些问题,以下是几个常见的情况和问题解决建议:
Watchdog 未能触发复位
问题描述:系统没有在 Watchdog 超时后进行复位,可能会导致系统依然停留在死锁或异常状态。
可能原因:
Watchdog 驱动未正确加载或配置:驱动可能未启动或配置不当。
硬件故障:Watchdog 的硬件可能存在问题,导致它无法触发复位。
内核配置问题:内核未启用相关的 Watchdog 驱动或模块。
解决方法:
检查 Watchdog 驱动是否已正确加载,查看
dmesg日志确认是否有启动错误。确认硬件连接正常,特别是与 Watchdog 相关的电路和芯片。
确认内核配置文件(
/boot/config-$(uname -r))中是否启用了 Watchdog 驱动模块。
Watchdog 无法定期喂狗
问题描述:系统未能按预期定期喂狗,导致 Watchdog 触发复位。
可能原因:
定时任务或应用程序未能及时喂狗:应用程序或内核模块没有按时喂狗,可能是由于性能瓶颈、延迟或死锁。
Watchdog 计时器配置不当:配置的超时时间过短,系统可能无法及时完成喂狗操作。
解决方法:
确保系统中定时喂狗的任务(如 crontab 或内核定时器)正常运行,避免任务阻塞或长时间占用 CPU。
增加 Watchdog 的超时时间,避免它触发复位。
使用工具(如
watchdog命令行工具)手动检查 Watchdog 状态。
Watchdog 导致系统频繁重启
问题描述:系统被 Watchdog 多次重启,可能是由于 Watchdog 提前触发了复位。
可能原因:
Watchdog 超时时间过短:超时时间过短,系统还未完成正常工作就会触发复位。
系统资源消耗过大:系统负载过高,导致无法及时喂狗或完成其他重要任务。
解决方法:
调整 Watchdog 的超时时间,使其适应系统的响应时间。
优化系统性能,减少过载情况,尤其是 CPU 或内存使用过高的情况。
使用工具监控系统性能,查找资源消耗瓶颈。
Watchdog 监控的硬件或设备失效
问题描述:Watchdog 硬件本身出现故障,导致无法正确监控系统。
可能原因:
硬件损坏:Watchdog 芯片或其相关电路损坏。
驱动不兼容:使用的 Watchdog 驱动与硬件不兼容。
解决方法:
检查硬件连接和电源,确保 Watchdog 芯片和相关电路正常工作。
更新或替换驱动程序,确保其与硬件兼容。
使用其他工具(如硬件诊断工具)测试 Watchdog 硬件是否正常。
Watchdog 被错误地禁用或关闭
问题描述:Watchdog 驱动被误禁用,导致无法发挥作用。
可能原因:
用户或程序手动禁用 Watchdog:可能是某些程序或配置禁用了 Watchdog 驱动。
内核模块未加载:Watchdog 驱动未被加载到内核中。
解决方法:
检查
/etc/watchdog.conf文件或其他配置文件,确保没有禁用 Watchdog。确认 Watchdog 驱动已在内核中加载并正常运行。
检查
dmesg日志,查看是否有与 Watchdog 相关的禁用或错误信息。
Watchdog 复位时未能正常重启系统
问题描述:Watchdog 触发复位后,系统未能如预期般重启,可能停在某个状态。
可能原因:
内核问题:系统可能未能正确处理复位请求,导致无法完成复位。
硬件问题:复位信号可能未被硬件正确接收或处理。
解决方法:
检查内核日志,确认复位过程是否成功。
确认 Watchdog 复位信号与硬件是否正确连接,确保系统可以正常重启。
确保内核支持硬件复位功能。
Watchdog 与系统其他监控工具冲突
问题描述:系统上使用了多个监控工具,如
systemd的守护进程、monit等,它们可能与 Watchdog 冲突。可能原因:
多重监控:多个监控工具可能会尝试独立地重启系统或管理 Watchdog,从而造成冲突。
解决方法:
确保只使用一个监控工具来管理 Watchdog,避免冲突。
配置合适的优先级,确保系统监控的一致性。