# 看门狗驱动调试指南

## Watchdog 概述

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

在芯片中，看门狗是 APB（Advanced Peripheral Bus）上的一个从设备。

## Watchdog 的特点

芯片中 Watchdog 具有以下特点：

1. **APB3接口支持**：兼容 APB3 接口。
2. **倒计时超时指示**：计数器从预设值倒计时到 0，表示发生超时。
3. **系统复位触发**：如果在第二次超时发生前中断未被清除，则会触发系统复位。
4. **可编程超时范围**：提供可编程的超时周期范围，可以在配置期间选择硬编码该值，以减少寄存器的需求。
5. **双可编程超时周期**：可选的双可编程超时周期，适用于首次启动等待时间与后续启动等待时间不同的情况，可以选择硬编码这些值。
6. **复位脉冲长度设置**：支持可编程和硬编码的复位脉冲长度。
7. **防止意外重启**：防止 DW_apb_wdt 计数器的意外重启。
8. **防止意外禁用**：防止 DW_apb_wdt 的意外禁用。
9. **暂停模式支持**：可选支持暂停模式，通过使用外部暂停使能信号实现。
10. **测试模式信号**：测试模式信号用于减少功能测试所需的时间。

## 功能描述

### Watchdog 典型应用

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

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

![watchdog_typical_applications](_static/_images/WatchDog_Driver_Debug_Guide_zh_CN/watchdog_typical_applications_zh_CN.png)

**具体说明如下：**

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](_static/_images/WatchDog_Driver_Debug_Guide_zh_CN/watchdog_function_diagram_zh_CN.png)

### Watchdog 工作方式

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

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

- 常规模式：软件定期重置 Watchdog 计时器。如果在规定的时间内没有“喂狗”信号，Watchdog 会触发复位。
- 调试模式：Watchdog 在调试过程中可以暂停，避免调试时发生意外复位。通常需要外部信号或特殊控制指令来启动调试模式。
- 复位保护模式：Watchdog 在遇到故障时会触发系统复位，但该复位过程会受到保护，防止因不必要的操作导致系统频繁重启。

#### Watchdog 基本工作流程

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

- 初始化和配置：在系统启动时，Watchdog 被初始化并配置。系统设置超时时间（即 Watchdog 计时器的倒计时周期），通常由软件设置，也可以通过硬编码来实现。此时 Watchdog 开始计时。
- “喂狗”机制：正常情况下，系统中的软件程序会定期给 Watchdog 发送信号，称为“喂狗”操作。这通常是通过重置 Watchdog 的计时器实现的。例如，软件每隔一定时间向 Watchdog 写入特定值，重置计时器的倒计时过程，确保 Watchdog 不会超时。
- 超时检测：如果软件程序长时间没有进行“喂狗”操作（例如，程序发生死锁、崩溃或被挂起），Watchdog 计时器会倒计时到达设定的超时时间（通常为几秒钟至几分钟）。
- 超时后的处理：当计时器倒计时到 0 时，Watchdog 会认为系统出现异常，自动触发预设的响应行为，通常是复位操作，重启系统或硬件模块，恢复系统到正常状态。

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

![Watchdog_work_flow](_static/_images/WatchDog_Driver_Debug_Guide_zh_CN/Watchdog_work_flow_zh_CN.png)

#### 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 是否能在预期条件下正常工作。

## Watchdog 驱动代码说明

### Watchdog 相关代码路径

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

### Watchdog DTS配置

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

```Shell
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` 是该设备的复位信号。这用于控制设备的复位操作。

<font color=red>备注：</font>
x5.dtsi 中的节点主要声明 SoC 共有特性，和具体电路板无关，一般情况下不用修改。

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

```shell
&watchdog {
    status = "disabled";
};
```

### Watchdog 内核配置

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

默认在 debug 版本中，Watchdog 设备会被注册，但是计时器不使能，可以在用户空间通过内核标准的 [Watchdog 字符设备](https://www.kernel.org/doc/html/v6.1/watchdog/watchdog-api.html#the-ioctl-api)进行调试和验证:

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

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

```shell
/* 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` 中：

```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
```

## Watchdog 功能使用

### Watchdog 寄存器说明

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

```c
#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**

```c
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**

```c
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**

```c
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 驱动相关的接口函数说明：

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_RESET`或`DW_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` 需要使能，然后重新编译内核镜像，烧录新的内核镜像后才能进行测试。

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

#### watchdog debugfs

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

```c
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` 数组中包括的寄存器）状态，帮助调试和分析设备的工作情况。

调试日志如下：

```shell
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 测例：

```c
#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 的编译工具链编译。

```shell
/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` 传到单板上。

**测例日志：**

```shell
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
```

**模拟看门狗触发复位日志：**

```shell
# 停止喂狗，倒计时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` 的记录，这就说明测例模拟看门狗触发复位成功。

## 常见问题

在使用 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，避免冲突。
     - 配置合适的优先级，确保系统监控的一致性。
