在这里插入图片描述

[toc]

drivers/watchdog Watchdog子系统(Watchdog Subsystem) 确保系统在软件故障时自动重启

历史与背景

这项技术是为了解决什么特定问题而诞生的?

Watchdog(看门狗)子系统的诞生是为了解决一个在计算系统中,尤其是高可靠性系统中,至关重要的问题:如何从致命的软件故障中自动恢复

软件系统可能会因为各种原因(如内核死锁、驱动程序中的无限循环、用户空间关键进程假死)而完全“卡死”(Hang),导致系统停止响应。在这种状态下,系统无法执行任何有效任务,也无法被正常地远程管理。对于无人值守的嵌入式设备或需要高可用性的服务器而言,这种状态是不可接受的。

Watchdog技术通过一个简单的“死人开关”(Dead Man’s Switch)机制来解决这个问题:

  1. 它提供一个硬件或软件定时器,一旦启动,就会开始倒计时。
  2. 系统中的监控软件(通常是一个用户空间的守护进程)必须周期性地“喂狗”(Feed the dog)或“踢狗”(Kick the dog),即重置这个定时器。
  3. 如果监控软件因为系统卡死而未能按时重置定时器,定时器就会超时。
  4. 超时后,Watchdog硬件会触发一个强制的、不可屏蔽的系统复位(Reset),使系统重启到一个已知的、干净的状态。

Watchdog框架的出现,则是为了将内核中五花八门的Watchdog硬件驱动统一起来,为用户空间提供一个标准的、可移植的交互接口。

它的发展经历了哪些重要的里程碑或版本迭代?

  • 早期的独立驱动:在统一框架出现之前,许多Watchdog硬件的驱动都是独立实现的,各有各的/proc接口或私有的ioctl命令,导致用户空间的监控程序难以做到通用。
  • Watchdog API v1的建立:这是最重要的里程碑。内核引入了一个标准的字符设备接口——/dev/watchdog,并定义了一套通用的ioctl命令(如WDIOC_KEEPALIVE, WDIOC_SETTIMEOUT)。这使得任何用户空间的守护进程(如watchdogd)都可以通过这个标准接口与任何遵循该框架的Watchdog驱动进行交互,实现了硬件的解耦。
  • “Pre-timeout”支持的引入:为了在硬重启之前提供更精细的故障诊断机会,框架增加了“预超时”功能。一些高级的Watchdog硬件可以在最终复位前的几秒钟,先触发一个不可屏蔽中断(NMI)或普通中断。内核可以捕获这个中断,执行一些紧急操作,例如打印内核调试信息(kdump)或通知高可用性软件准备故障转移。
  • “nowayout”特性的标准化:这是一个关键的安全特性。一旦通过模块参数或编译时选项开启,它会阻止任何程序(甚至是root)在Watchdog启动后将其关闭。这可以防止一个设计不佳的应用程序在退出时意外地关闭了Watchdog,从而使系统失去保护。

目前该技术的社区活跃度和主流应用情况如何?

Watchdog子系统是Linux内核中一个非常基础、稳定且对可靠性至关重要的部分。它被广泛应用于:

  • 嵌入式系统:工业控制器、路由器、物联网设备、汽车电子等所有需要无人值守且高可靠运行的场景。
  • 服务器:在高可用性(High-Availability)集群中,Watchdog是实现STONITH(Shoot The Other Node In The Head)/ fencing机制的关键,用于确保一个故障节点被可靠地重启,防止“脑裂”问题。
  • 通用系统:现代的系统管理器(如systemd)也集成了Watchdog功能,可以监控关键服务,并在服务卡死时自动重启系统。

核心原理与设计

它的核心工作原理是什么?

Watchdog框架是一个典型的三层模型,连接了硬件、内核和用户空间。

  1. 硬件层(Watchdog Timer, WDT)
    • 这是一个物理硬件定时器(或由内核模拟的软件定时器softdog)。
    • 它的核心是一个倒数计数器。
    • 当计数器减到0时,它会通过一个硬件信号线强制复位整个SoC或主板。
  2. 内核层(Watchdog Core and Driver)
    • Watchdog核心 (watchdog_core.c):实现了/dev/watchdog字符设备,并处理来自用户空间的open, write, ioctl等文件操作。
    • Watchdog硬件驱动:这是特定于硬件的驱动程序。它实现了一组标准的回调函数struct watchdog_ops,包括:
      • .start(): 启动硬件定时器。
      • .stop(): 停止硬件定时器。
      • .ping().keepalive(): 重置硬件定时器(“喂狗”)。
      • .set_timeout(): 设置超时时间。
    • 核心层的作用就是将用户空间的标准请求(如一次write操作)转换为对具体硬件驱动.ping()回调函数的调用。
  3. 用户空间层(Daemon)
    • 这是一个守护进程,例如watchdog包中的watchdogd,或者systemd
    • 它在启动时open("/dev/watchdog")。一旦打开成功,Watchdog硬件通常就会被驱动start()
    • 该进程进入一个主循环,周期性地(例如每隔几秒)向/dev/watchdog的文件描述符执行一次write操作或WDIOC_KEEPALIVE ioctl调用,从而触发内核去“喂狗”。
    • 这个守护进程通常还会执行一些系统健康检查,例如检查系统负载、内存使用情况、特定进程是否存在等。如果检查发现系统处于不健康状态,它可以故意停止“喂狗”,从而主动触发Watchdog重启系统。

它的主要优势体现在哪些方面?

  • 高可靠性:提供了一种从软件完全卡死状态下恢复的最终手段。
  • 解耦与标准化:统一的/dev/watchdog接口将用户空间监控程序与底层硬件驱动完全解耦。
  • 驱动开发简化:驱动开发者只需实现一小组定义良好的硬件操作回调,即可将他们的设备接入整个框架。

它存在哪些已知的劣势、局限性或在特定场景下的不适用性?

  • 无法解决硬件问题:Watchdog只能应对软件故障,无法修复硬件损坏。
  • 可能导致不必要的重启:如果系统负载过高,导致用户空间守护进程被调度器延迟,没能及时“喂狗”,就可能触发一次不必要的重启。因此,超时时间的设定需要仔细权衡。
  • 开发风险(nowayout:在开发阶段,如果nowayout被启用,而用户空间的守护进程因为配置错误等原因未能正常启动,系统将会陷入无限的重启循环(Boot Loop),给调试带来困难。
  • 诊断信息有限:一次简单的Watchdog重启本身不会告诉你系统为何卡死。需要配合pre-timeoutkdump等机制才能在重启前捕获到有用的调试信息。

使用场景

在哪些具体的业务或技术场景下,它是首选解决方案?

Watchdog是任何无法接受长时间服务中断或需要自主恢复的系统的标准解决方案。

  • 远程无人值守设备:部署在野外的气象站、通信基站、太空中的卫星等。
  • 高可用性集群:作为 fencing 设备,防止集群中的节点因通信中断而错误地认为其他节点已死,从而同时去抢占共享资源,导致数据损坏。
  • 生命支持或安全关键系统:在医疗设备或工业控制系统中,一个可预测的快速重启通常比一个未知的、持续的故障状态更安全。

是否有不推荐使用该技术的场景?为什么?

  • 典型的交互式桌面:对于普通桌面用户,一个意外的、无预警的重启可能会导致大量未保存的工作丢失,其体验通常比一个暂时卡死的应用程序更差。用户通常倾向于手动去杀死问题进程。
  • 需要长时间保持状态的非容错计算:例如,一个正在进行数天之久的科学计算任务,如果其软件没有设计检查点(Checkpoint)机制,一次Watchdog重启将意味着所有计算成果的丢失。在这种场景下,可能需要禁用Watchdog,但这本身也带来了系统可能永久卡死的风险。

对比分析

请将其 与 其他相似技术 进行详细对比。

Watchdog是一种独特的、 अंतिम的系统级恢复机制。其对比对象通常是其他层面的高可用或故障检测技术。

特性 Hardware Watchdog High-Availability (HA) Software Kernel Panic/Oops
检测对象 整个系统的无响应(软件完全停止“喂狗”)。 服务、应用程序或网络节点的无响应 内核自身检测到的非法操作(如访问无效内存)。
恢复粒度 整个系统(硬重启)。 单个服务(重启服务)或单个节点(触发fencing,可能也用到watchdog)。 内核(通常会导致系统停机或重启)。
触发方式 被动超时。因为“喂狗”动作没有发生。 主动探测。通过心跳检测、服务端口检查等主动发现问题。 主动触发。当CPU执行到错误指令时,由内核的异常处理代码主动触发。
解决的问题 系统完全死锁/假死,内核和应用都无法自救。 应用程序崩溃、服务无响应,但操作系统本身通常还活着。 内核代码遇到了无法恢复的内部错误
  • 关系:它们是互补而非竞争的关系。一个完整的高可用系统会同时使用这几种技术:
    • Kernel Oops/Panic处理内核自身的致命错误。
    • HA Software监控并恢复应用程序级别的故障。
    • 当HA软件发现某个节点彻底失联,或者它自己所在的节点完全卡死时,Watchdog作为最后的保障,会强制重启该节点,让其恢复到一个干净的状态,从而可以重新加入集群。

drivers/watchdog/watchdog_core.c

Watchdog核心初始化与延迟注册机制

本代码片段是Linux内核看门狗(Watchdog)子系统核心的初始化部分。其核心功能是建立看门狗框架的基础设施,并实现一个“延迟注册”机制。这个机制用于解决一个时序问题:某些看门狗硬件的驱动程序可能在内核启动的很早阶段就被探测(probe),甚至早于看门狗核心子系统自身的初始化。延迟注册确保了这些“早到”的驱动不会因核心未就绪而注册失败,而是被安全地放入一个队列中,待核心初始化完毕后再统一进行正式注册。

实现原理分析

此代码的实现巧妙地利用了内核的初始化调用顺序和同步机制来保证系统的健壮性。

  1. 两阶段初始化:

    • watchdog_dev_init(): 此函数(在此代码段中未显示其定义)是第一阶段。它负责创建所有具体看门狗设备都依赖的公共资源,例如注册/sys/class/watchdog设备类,以及通过alloc_chrdev_region申请用于/dev/watchdogX的主/次设备号池。
    • watchdog_deferred_registration(): 这是第二阶段。它处理那些在第一阶段完成前就已尝试注册的设备。
  2. 延迟注册机制:

    • 内核中存在一个全局链表wtd_deferred_reg_list和一个布尔标志wtd_deferred_reg_done(初始为false)。
    • 当一个具体的看门狗驱动调用watchdog_register_device()(未在此处显示)时,该函数会首先检查wtd_deferred_reg_done标志。
    • 如果标志为false,意味着看门狗核心尚未就绪。此时,注册函数不会报错,而是将该看门狗设备结构体(struct watchdog_device)添加到一个临时的延迟注册链表wtd_deferred_reg_list中。
    • 如果标志为true,则直接进行正常的设备注册流程。
  3. 同步与执行:

    • subsys_initcall_sync(watchdog_init): 这个宏是关键。subsys_initcall确保了watchdog_init在大多数设备驱动probe之前执行。后缀_sync则提供了一个更强的保证:它确保watchdog_init函数执行完毕后,内核才会继续下一阶段的初始化调用。
    • 当内核执行到watchdog_init时,它首先调用watchdog_dev_init完成基础设施的创建。
    • 随后,watchdog_deferred_registration被调用。它锁住互斥体,将wtd_deferred_reg_done标志设置为true,然后遍历wtd_deferred_reg_list链表,将所有等待的设备逐一取出并调用__watchdog_register_device进行正式注册。

通过这个机制,无论具体的看门狗驱动何时被探测,其注册请求都能被正确处理,从而避免了因初始化顺序依赖而导致的启动问题。

代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// watchdog_deferred_registration: 执行延迟注册的处理函数。
// 作用是将在核心初始化完成前就尝试注册的看门狗设备进行正式注册。
static int __init watchdog_deferred_registration(void)
{
// 加锁,以保护延迟注册列表和完成标志。
mutex_lock(&wtd_deferred_reg_mutex);
// 设置完成标志为true,此后新的注册请求将直接执行,不再进入延迟列表。
wtd_deferred_reg_done = true;
// 循环处理延迟注册列表,直到列表为空。
while (!list_empty(&wtd_deferred_reg_list)) {
struct watchdog_device *wdd;

// 获取列表中的第一个待处理的看门狗设备。
wdd = list_first_entry(&wtd_deferred_reg_list,
struct watchdog_device, deferred);
// 将该设备从延迟列表中移除。
list_del(&wdd->deferred);
// 调用内部的、真正的设备注册函数来完成注册。
__watchdog_register_device(wdd);
}
// 解锁。
mutex_unlock(&wtd_deferred_reg_mutex);
return 0;
}

// watchdog_init: 看门狗子系统的核心初始化函数。
static int __init watchdog_init(void)
{
int err;

// 首先,初始化看门狗的设备基础设施(如字符设备号、sysfs类等)。
err = watchdog_dev_init();
if (err < 0)
return err;

// 然后,处理所有在上述初始化完成前就已经被探测到的、等待注册的看门狗设备。
watchdog_deferred_registration();
return 0;
}

// watchdog_exit: 看门狗子系统的退出/清理函数。
static void __exit watchdog_exit(void)
{
// 以相反的顺序清理资源:首先清理设备基础设施。
watchdog_dev_exit();
// 然后销毁用于分配看门狗ID的IDA(ID Allocator)。
ida_destroy(&watchdog_ida);
}

// 将watchdog_init注册为同步的子系统初始化调用。
// _sync确保此函数执行完毕后,其他初始化才会继续,这对于延迟注册机制至关重要。
subsys_initcall_sync(watchdog_init);
// 将watchdog_exit注册为模块退出调用。
module_exit(watchdog_exit);

// 标准的模块作者、描述和许可证信息。
MODULE_AUTHOR("Alan Cox <alan@lxorguk.ukuu.org.uk>");
MODULE_AUTHOR("Wim Van Sebroeck <wim@iguana.be>");
MODULE_DESCRIPTION("WatchDog Timer Driver Core");
MODULE_LICENSE("GPL");

watchdog 注册/注销主路径:ID 分配、设备节点注册、重启/PM 通知挂接(watchdog_register_device / devm_watchdog_register_device)

单核、无MMU、ARMv7-M


___watchdog_register_device:核心注册流程(参数校验→分配 id→注册字符设备→按策略挂 notifier)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
static void watchdog_check_min_max_timeout(struct watchdog_device *wdd)
{
/*
* Check that we have valid min and max timeout values, if
* not reset them both to 0 (=not used or unknown)
*/
if (!wdd->max_hw_heartbeat_ms && wdd->min_timeout > wdd->max_timeout) {
pr_info("Invalid min and max timeout values, resetting to 0!\n");
wdd->min_timeout = 0;
wdd->max_timeout = 0;
}
}

/**
* watchdog_init_timeout() - initialize the timeout field
* @wdd: watchdog device
* @timeout_parm: timeout module parameter
* @dev: Device that stores the timeout-sec property
*
* Initialize the timeout field of the watchdog_device struct with either the
* timeout module parameter (if it is valid value) or the timeout-sec property
* (only if it is a valid value and the timeout_parm is out of bounds).
* If none of them are valid then we keep the old value (which should normally
* be the default timeout value). Note that for the module parameter, '0' means
* 'use default' while it is an invalid value for the timeout-sec property.
* It should simply be dropped if you want to use the default value then.
*
* A zero is returned on success or -EINVAL if all provided values are out of
* bounds.
*/
int watchdog_init_timeout(struct watchdog_device *wdd,
unsigned int timeout_parm, struct device *dev)
{
const char *dev_str = wdd->parent ? dev_name(wdd->parent) :
(const char *)wdd->info->identity;
unsigned int t = 0;
int ret = 0;

watchdog_check_min_max_timeout(wdd);

/* check the driver supplied value (likely a module parameter) first */
if (timeout_parm) {
if (!watchdog_timeout_invalid(wdd, timeout_parm)) {
wdd->timeout = timeout_parm;
return 0;
}
pr_err("%s: driver supplied timeout (%u) out of range\n",
dev_str, timeout_parm);
ret = -EINVAL;
}

/* try to get the timeout_sec property */
if (dev && device_property_read_u32(dev, "timeout-sec", &t) == 0) {
if (t && !watchdog_timeout_invalid(wdd, t)) {
wdd->timeout = t;
return 0;
}
pr_err("%s: DT supplied timeout (%u) out of range\n", dev_str, t);
ret = -EINVAL;
}

if (ret < 0 && wdd->timeout)
pr_warn("%s: falling back to default timeout (%u)\n", dev_str,
wdd->timeout);

return ret;
}
EXPORT_SYMBOL_GPL(watchdog_init_timeout);
static int ___watchdog_register_device(struct watchdog_device *wdd)
{
int ret, id = -1;

/** @brief 关键入参校验:没有 info/ops 就无法对外声明能力或执行硬件操作。 */
if (wdd == NULL || wdd->info == NULL || wdd->ops == NULL)
return -EINVAL;

/**
* @brief 强制要求的硬件操作能力:
* - 必须支持 start;
* - 如果不支持 stop,则必须提供 max_hw_heartbeat_ms(用于声明“无法停止但具备硬件最大心跳”能力边界),
* 否则框架无法形成一致的策略(例如 nowayout/停表行为)。
*/
if (!wdd->ops->start || (!wdd->ops->stop && !wdd->max_hw_heartbeat_ms))
return -EINVAL;

/** @brief 统一修正/核验 min/max timeout(避免用户传入越界导致后续计算溢出或语义不一致)。 */
watchdog_check_min_max_timeout(wdd);

/**
* @brief 优先使用 DT alias 固定 watchdog id(使 /dev/watchdogX 的编号稳定)。
* - 有 parent 且有 of_node 时尝试从 "watchdog" 别名获取固定序号;
* - 若成功,则用 ida_alloc_range 在同一固定 id 上分配(避免动态变化)。
*/
if (wdd->parent) {
ret = of_alias_get_id(wdd->parent->of_node, "watchdog");
if (ret >= 0)
id = ida_alloc_range(&watchdog_ida, ret, ret,
GFP_KERNEL);
}

/**
* @brief 若没有 alias 或固定分配失败,则从 [0..MAX_DOGS-1] 里分配一个可用 id。
* ida_* 维护全局唯一 id 池;在单核上仍需其内部锁语义来对抗抢占/中断/并发注册路径。
*/
if (id < 0)
id = ida_alloc_max(&watchdog_ida, MAX_DOGS - 1, GFP_KERNEL);

if (id < 0)
return id;
wdd->id = id;

/**
* @brief 真正向 watchdog 子系统/字符设备层注册(通常决定 /dev/watchdogX 的创建与可访问性)。
*/
ret = watchdog_dev_register(wdd);
if (ret) {
ida_free(&watchdog_ida, id);

/**
* @brief 特殊兼容:id==0 且返回 -EBUSY 时重试。
* 典型语义:历史“legacy watchdog”可能占用了主设备号/默认节点,
* 这里改用 id>=1 规避冲突(让新框架设备仍可注册)。
*/
if (!(id == 0 && ret == -EBUSY))
return ret;

/* Retry in case a legacy watchdog module exists */
id = ida_alloc_range(&watchdog_ida, 1, MAX_DOGS - 1,
GFP_KERNEL);
if (id < 0)
return id;
wdd->id = id;

ret = watchdog_dev_register(wdd);
if (ret) {
ida_free(&watchdog_ida, id);
return ret;
}
}

/**
* @brief stop_on_reboot 为模块参数(全局策略开关):
* - != -1 表示强制覆盖每个 watchdog 的 WDOG_STOP_ON_REBOOT 状态位;
* - 该位决定是否在系统 reboot 路径里尝试调用 ops->stop。
*/
if (stop_on_reboot != -1) {
if (stop_on_reboot)
set_bit(WDOG_STOP_ON_REBOOT, &wdd->status);
else
clear_bit(WDOG_STOP_ON_REBOOT, &wdd->status);
}

/**
* @brief 如启用“reboot 时 stop”,则注册 reboot notifier:
* - 若硬件/驱动不支持 stop,仅警告;
* - 否则在 reboot 通知链触发时执行 watchdog_reboot_notifier(通常会 stop 或做策略动作)。
*/
if (test_bit(WDOG_STOP_ON_REBOOT, &wdd->status)) {
if (!wdd->ops->stop)
pr_warn("watchdog%d: stop_on_reboot not supported\n", wdd->id);
else {
wdd->reboot_nb.notifier_call = watchdog_reboot_notifier;

ret = register_reboot_notifier(&wdd->reboot_nb);
if (ret) {
pr_err("watchdog%d: Cannot register reboot notifier (%d)\n",
wdd->id, ret);
watchdog_dev_unregister(wdd);
ida_free(&watchdog_ida, id);
return ret;
}
}
}

/**
* @brief 若驱动提供 restart(硬件复位能力),则挂 restart handler:
* - register_restart_handler 参与“重启优先级仲裁”;
* - 注册失败仅警告,不回滚 watchdog 注册(复位能力可选)。
*/
if (wdd->ops->restart) {
wdd->restart_nb.notifier_call = watchdog_restart_notifier;

ret = register_restart_handler(&wdd->restart_nb);
if (ret)
pr_warn("watchdog%d: Cannot register restart handler (%d)\n",
wdd->id, ret);
}

/**
* @brief 若设置“suspend 时不 ping”,则挂 PM notifier:
* - 用于系统进入/退出低功耗时调整喂狗策略,避免 suspend 期间错误喂狗或超时策略失配。
*/
if (test_bit(WDOG_NO_PING_ON_SUSPEND, &wdd->status)) {
wdd->pm_nb.notifier_call = watchdog_pm_notifier;

ret = register_pm_notifier(&wdd->pm_nb);
if (ret)
pr_warn("watchdog%d: Cannot register pm handler (%d)\n",
wdd->id, ret);
}

return 0;
}

__watchdog_register_device:注册失败时统一打印可识别的设备名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static int __watchdog_register_device(struct watchdog_device *wdd)
{
const char *dev_str;
int ret;

ret = ___watchdog_register_device(wdd);
if (ret) {
/** @brief 优先用 parent 的设备名,否则用 identity(便于定位是哪一个 watchdog)。 */
dev_str = wdd->parent ? dev_name(wdd->parent) :
(const char *)wdd->info->identity;
pr_err("%s: failed to register watchdog device (err = %d)\n",
dev_str, ret);
}

return ret;
}

watchdog_register_device:处理“延迟注册”窗口(避免在子系统未就绪阶段注册失败)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int watchdog_register_device(struct watchdog_device *wdd)
{
int ret = 0;

/**
* @brief 用互斥锁保护“延迟注册队列/状态位”:
* - wtd_deferred_reg_done 为真:watchdog 子系统已完成关键初始化,可立即注册;
* - 否则把 wdd 放入延迟队列,待稍后统一注册。
*
* 在单核系统里互斥锁仍必要:可能存在抢占/中断线程化/多上下文路径进入。
*/
mutex_lock(&wtd_deferred_reg_mutex);
if (wtd_deferred_reg_done)
ret = __watchdog_register_device(wdd);
else
watchdog_deferred_registration_add(wdd);
mutex_unlock(&wtd_deferred_reg_mutex);

return ret;
}
EXPORT_SYMBOL_GPL(watchdog_register_device);

__watchdog_unregister_device / watchdog_unregister_device:对称释放(notifier→字符设备→id 池)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
static void __watchdog_unregister_device(struct watchdog_device *wdd)
{
if (wdd == NULL)
return;

/** @brief 若注册过 restart handler,需要先从重启处理链移除。 */
if (wdd->ops->restart)
unregister_restart_handler(&wdd->restart_nb);

/** @brief 若挂了 reboot notifier,也需对称注销。 */
if (test_bit(WDOG_STOP_ON_REBOOT, &wdd->status))
unregister_reboot_notifier(&wdd->reboot_nb);

/** @brief 注销字符设备/内部对象,并归还 watchdog id。 */
watchdog_dev_unregister(wdd);
ida_free(&watchdog_ida, wdd->id);
}

void watchdog_unregister_device(struct watchdog_device *wdd)
{
mutex_lock(&wtd_deferred_reg_mutex);
if (wtd_deferred_reg_done)
__watchdog_unregister_device(wdd);
else
watchdog_deferred_registration_del(wdd);
mutex_unlock(&wtd_deferred_reg_mutex);
}
EXPORT_SYMBOL_GPL(watchdog_unregister_device);

devm_watchdog_register_device:devres 托管注册(驱动 detach 时自动注销)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
static void devm_watchdog_unregister_device(struct device *dev, void *res)
{
/** @brief devres 回收回调:把注册过的 watchdog 对称注销。 */
watchdog_unregister_device(*(struct watchdog_device **)res);
}

int devm_watchdog_register_device(struct device *dev,
struct watchdog_device *wdd)
{
struct watchdog_device **rcwdd;
int ret;

/**
* @brief 分配一块 devres 资源记录区,用来保存 wdd 指针;
* 后续设备释放时会触发 devm_watchdog_unregister_device。
*/
rcwdd = devres_alloc(devm_watchdog_unregister_device, sizeof(*rcwdd),
GFP_KERNEL);
if (!rcwdd)
return -ENOMEM;

ret = watchdog_register_device(wdd);
if (!ret) {
*rcwdd = wdd;
devres_add(dev, rcwdd); /**< 注册成功:把“自动回收动作”挂到设备资源链 */
} else {
devres_free(rcwdd); /**< 注册失败:释放 devres 记录块,避免泄漏 */
}

return ret;
}
EXPORT_SYMBOL_GPL(devm_watchdog_register_device);

Watchdog 通知链回调:系统关机/重启/电源管理阶段的策略钩子(watchdog_reboot_notifier / watchdog_restart_notifier / watchdog_pm_notifier)

这些函数都是 notifier 回调:watchdog core 在注册阶段把它们挂到 reboot notifier / restart handler / PM notifier 链上,使得系统生命周期事件发生时能够调用到对应 wdd->ops,以实现“停表”“硬件复位”“挂起/恢复期间的策略调整”等行为。


watchdog_reboot_notifier:在特定 reboot 事件中尝试 stop(仅当硬件正在运行)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
static int watchdog_reboot_notifier(struct notifier_block *nb,
unsigned long code, void *data)
{
struct watchdog_device *wdd;

/** @brief 通过 notifier_block 指针反推出其所属的 watchdog_device 对象。 */
wdd = container_of(nb, struct watchdog_device, reboot_nb);

/**
* @brief 仅在关机/停机/断电这类终止态事件时尝试 stop。
* - SYS_DOWN:常见的 reboot/shutdown 路径(语义上系统将退出运行态)
* - SYS_HALT:halt
* - SYS_POWER_OFF:poweroff
*/
if (code == SYS_DOWN || code == SYS_HALT || code == SYS_POWER_OFF) {
/**
* @brief 只有当 watchdog 被认为“硬件正在跑”才会尝试 stop。
* 目的:避免对未运行的硬件执行 stop 造成多余副作用(例如寄存器访问、时钟打开等)。
*/
if (watchdog_hw_running(wdd)) {
int ret;

/**
* @brief 调用驱动 stop:这是 stop_on_reboot 策略真正落到硬件的入口。
* stop 失败会让 notifier 返回 NOTIFY_BAD,意味着该阶段出现不可接受的失败。
*/
ret = wdd->ops->stop(wdd);

/** @brief 记录一次 stop 行为到 trace,便于调试重启/关机阶段的看门狗状态。 */
trace_watchdog_stop(wdd, ret);

if (ret)
return NOTIFY_BAD;
}
}

return NOTIFY_DONE;
}

watchdog_restart_notifier:把系统“重启动作”委托给 watchdog 的 restart(如果提供)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static int watchdog_restart_notifier(struct notifier_block *nb,
unsigned long action, void *data)
{
/** @brief 从 restart_nb 找回所属的 watchdog_device。 */
struct watchdog_device *wdd = container_of(nb, struct watchdog_device,
restart_nb);

int ret;

/**
* @brief 调用驱动的 restart:用于实现“由 watchdog 触发的系统复位”或“平台级重启”。
* action/data 来自 restart handler 框架,用于描述重启类型、原因或附带参数。
*/
ret = wdd->ops->restart(wdd, action, data);
if (ret)
return NOTIFY_BAD;

return NOTIFY_DONE;
}

要点:

  • 只有 wdd->ops->restart 存在时,注册阶段才会把该 notifier 挂到 restart handler 链。
  • 失败返回 NOTIFY_BAD,意味着“该 restart handler 未能完成其责任”。

watchdog_pm_notifier:在 suspend/hibernate 相关阶段挂起与恢复 watchdog 设备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
static int watchdog_pm_notifier(struct notifier_block *nb, unsigned long mode,
void *data)
{
struct watchdog_device *wdd;
int ret = 0;

/** @brief 从 pm_nb 找回所属 watchdog_device。 */
wdd = container_of(nb, struct watchdog_device, pm_nb);

switch (mode) {
/**
* @brief 进入低功耗准备阶段:让 watchdog core 有机会暂停/调整喂狗策略。
* 常见目的:若配置了 “NO_PING_ON_SUSPEND”,则在 suspend 期间不再做内核定时 ping,
* 以及可能调整硬件/软件状态以避免 suspend 中误触发。
*/
case PM_HIBERNATION_PREPARE:
case PM_RESTORE_PREPARE:
case PM_SUSPEND_PREPARE:
ret = watchdog_dev_suspend(wdd);
break;

/**
* @brief 从低功耗返回后:恢复 watchdog core 的喂狗策略、pretimout 机制等。
*/
case PM_POST_HIBERNATION:
case PM_POST_RESTORE:
case PM_POST_SUSPEND:
ret = watchdog_dev_resume(wdd);
break;
}

if (ret)
return NOTIFY_BAD;

return NOTIFY_DONE;
}

drivers/watchdog/watchdog_dev.c

Watchdog核心设备初始化:创建内核工作线程、设备类与设备号池

本代码片段的功能是初始化Linux Watchdog核心子系统中与设备模型和字符设备接口相关的部分。它在内核启动的早期阶段执行,为后续具体的硬件看门狗驱动程序(hardware watchdog drivers)注册和运行搭建必要的基础软件设施。这包括创建一个高优先级的内核工作线程、注册一个 “watchdog” 设备类,以及预留一块用于看门狗设备的字符设备号区域。

实现原理分析

此函数的实现集成了内核线程、设备模型和字符设备子系统的功能,以构建一个健壮的框架。

  1. 创建内核工作线程 (kthread_run_worker):

    • 函数首先创建一个名为 “watchdogd” 的内核工作线程(kworker)。这个线程专门用于异步处理看门狗相关的任务,例如处理预超时(pre-timeout)通知。将这些任务放在一个专用的工作线程中,可以避免阻塞调用者上下文,并允许以统一的方式处理来自不同硬件看门狗驱动的事件。
  2. 设置实时调度策略 (sched_set_fifo):

    • 这是一个至关重要的步骤。函数将 “watchdogd” 线程的调度策略设置为SCHED_FIFO(先进先出)。这是一种实时调度策略,赋予了该线程非常高的运行优先级。此举确保了当有看门狗相关的紧急任务(如响应预超时中断)需要执行时,”watchdogd” 线程能够立即抢占大多数其他普通内核线程或用户进程,从而保证了看门狗子系统对时间敏感事件的及时响应,这是维持系统稳定性的关键。
  3. 注册设备类 (class_register):

    • 通过调用class_register并传入watchdog_class(在别处定义),函数在sysfs中创建了/sys/class/watchdog/目录。这个目录充当了所有看门狗设备的逻辑容器。当一个具体的硬件看门狗驱动(如stm32-iwdg)注册其设备时,它会把自己归入这个类,从而在用户空间表现为/sys/class/watchdog/watchdog0这样的符号链接,提供了一个标准化的接口。
  4. 分配字符设备号 (alloc_chrdev_region):

    • 看门狗设备通过一个字符设备节点(如/dev/watchdog/dev/watchdog0)向用户空间提供主接口。alloc_chrdev_region函数向内核动态申请并预留了一段连续的字符设备号(主/次设备号对),最多可支持MAX_DOGS个看门狗设备。这为后续创建设备节点提供了必要的“门牌号”。
  5. 错误处理:

    • 函数包含了完善的错误处理逻辑。如果在初始化过程中的任何一步失败,它都会以相反的顺序撤销所有已经成功的操作(例如,如果分配设备号失败,它会注销设备类并销毁工作线程),以确保系统不会处于部分初始化或资源泄漏的状态。

代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// watchdog_dev_init: 初始化看门狗核心的设备部分。
// 描述:
// 为看门狗设备分配一段字符设备节点区域。
// 返回值: 成功则为0,否则为错误码。
int __init watchdog_dev_init(void)
{
int err;

// 创建一个名为 "watchdogd" 的内核工作线程。
// 0 表示工作线程可以在任何CPU上运行。
watchdog_kworker = kthread_run_worker(0, "watchdogd");
if (IS_ERR(watchdog_kworker)) {
pr_err("Failed to create watchdog kworker\n");
return PTR_ERR(watchdog_kworker);
}
// 将工作线程的调度策略设置为SCHED_FIFO(实时先进先出),赋予其高优先级。
sched_set_fifo(watchdog_kworker->task);

// 注册 "watchdog" 设备类,这将在sysfs中创建 /sys/class/watchdog/ 目录。
err = class_register(&watchdog_class);
if (err < 0) {
pr_err("couldn't register class\n");
goto err_register; // 如果失败,跳转到错误处理。
}

// 动态申请一段字符设备号区域,用于watchdog设备。
// &watchdog_devt: 用于存储分配到的起始设备号。
// 0: 起始次设备号。
// MAX_DOGS: 希望分配的设备数量。
// "watchdog": 与该区域关联的名称。
err = alloc_chrdev_region(&watchdog_devt, 0, MAX_DOGS, "watchdog");
if (err < 0) {
pr_err("watchdog: unable to allocate char dev region\n");
goto err_alloc; // 如果失败,跳转到错误处理。
}

return 0; // 初始化成功。

err_alloc:
// 错误处理路径:注销已经注册的设备类。
class_unregister(&watchdog_class);
err_register:
// 错误处理路径:销毁已经创建的内核工作线程。
kthread_destroy_worker(watchdog_kworker);
return err;
}

watchdog_start / watchdog_stop / watchdog_get_status / watchdog_set_timeout / watchdog_set_pretimeout / watchdog_get_timeleft:看门狗核心层封装(启动/停止/状态/超时/预超时/剩余时间)

平台关注:单核、无 MMU、ARMv7-M(STM32H750)

  • 这些函数属于 watchdog 核心层对底层驱动 ops 的封装,关键语义是:调用者已持有 wd_data->lock,因此在单核场景下主要用于防止抢占/中断/并发路径status 与时间戳等共享状态的破坏(不是为多核并行而设计的差异逻辑)。
  • set_bit()/clear_bit()/test_bit() 等位操作在单核下仍然有意义:它们提供对共享状态位的原子修改语义,并与内核通用并发模型保持一致;在无 MMU 平台上,其内存访问语义不改变,但错误写入的影响范围更“全局”,因此更依赖锁与位操作保证一致性。
  • 若你在 STM32H750 上对接的是片上独立看门狗(IWDG/WWDG),通常 ops->start/stop/ping/set_timeout/get_timeleft 由具体硬件驱动实现;这里的核心层负责策略位、时间戳与 pretimeout 机制的编排

watchdog_start:启动看门狗或在“硬件已在跑”时仅进行 keepalive 同步

作用与实现要点

  • 如果软件视角已 active,则直接返回。

  • 启动前设置 _WDOG_KEEPALIVE,用于后续 watchdog_get_status() 报告 “曾发生 keepalive”。

  • 分两条路径:

    1. 硬件已运行且支持 ping:不重复 start,仅 ping 同步,并标记 ACTIVE,启动 pretimeout 计时器。
    2. 正常 start:调用 ops->start(),成功后设置 ACTIVE/HW_RUNNING,并初始化 last_keepalive/last_hw_keepalive,更新 worker 与 pretimeout。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
* @brief 启动 watchdog 的核心层封装。
*
* @param wdd watchdog 设备对象;其 wd_data 指向核心层私有数据。
* @return 成功返回 0;失败返回负 errno。
*
* @note 调用者必须已持有 wd_data->lock,用于序列化状态位与时间戳更新。
*/
static int watchdog_start(struct watchdog_device *wdd)
{
struct watchdog_core_data *wd_data = wdd->wd_data; /**< 核心层数据:包含状态位与 keepalive 时间戳等。 */
ktime_t started_at; /**< 记录“启动/同步时刻”,用于后续 keepalive 逻辑。 */
int err; /**< 底层驱动返回的错误码。 */

if (watchdog_active(wdd)) /** 已经处于 ACTIVE 则无需重复启动。 */
return 0;

set_bit(_WDOG_KEEPALIVE, &wd_data->status); /** 标记“发生过 keepalive 请求”,供 get_status 报告使用。 */

started_at = ktime_get(); /** 获取当前单调时间,用于初始化 keepalive 时间戳。 */

if (watchdog_hw_running(wdd) && wdd->ops->ping) { /** 若硬件已在运行:优先用 ping 同步而不是重新 start。 */
err = __watchdog_ping(wdd); /** 触发一次硬件喂狗/同步动作(封装层可能含额外检查)。 */
if (err == 0) {
set_bit(WDOG_ACTIVE, &wdd->status); /** 标记软件视角 ACTIVE。 */
watchdog_hrtimer_pretimeout_start(wdd); /** 启动 pretimeout 机制(若配置启用)。 */
}
} else {
err = wdd->ops->start(wdd); /** 走底层驱动 start:真正开启硬件倒计时。 */
trace_watchdog_start(wdd, err); /** 追踪点:用于观测 start 的结果。 */
if (err == 0) {
set_bit(WDOG_ACTIVE, &wdd->status); /** 软件视角 ACTIVE。 */
set_bit(WDOG_HW_RUNNING, &wdd->status); /** 硬件视角 RUNNING:后续路径会据此选择策略。 */
wd_data->last_keepalive = started_at; /** 记录最近一次 keepalive(逻辑层时间)。 */
wd_data->last_hw_keepalive = started_at; /** 记录最近一次硬件 keepalive(用于区分策略/观测)。 */
watchdog_update_worker(wdd); /** 更新后台 worker 的调度策略(例如自动喂狗周期)。 */
watchdog_hrtimer_pretimeout_start(wdd); /** 启动 pretimeout 机制。 */
}
}

return err;
}

watchdog_stop:停止看门狗(受 nowayout 约束)

作用与实现要点

  • 若未 active,直接返回。
  • 若设置了 WDOG_NO_WAY_OUT(nowayout),禁止停止并返回 -EBUSY
  • 若底层实现了 ops->stop:先清除 WDOG_HW_RUNNING 再调用 stop;否则维持 WDOG_HW_RUNNING(表示硬件无法停止或不支持停止语义)。
  • 成功停止后清除 WDOG_ACTIVE,并更新 worker 与停止 pretimeout。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* @brief 停止 watchdog 的核心层封装。
*
* @param wdd watchdog 设备对象。
* @return 成功返回 0;失败返回负 errno。
*
* @note 调用者必须已持有 wd_data->lock。
*/
static int watchdog_stop(struct watchdog_device *wdd)
{
int err = 0; /**< stop 路径的错误码,默认成功。 */

if (!watchdog_active(wdd)) /** 未 ACTIVE 则无需停止。 */
return 0;

if (test_bit(WDOG_NO_WAY_OUT, &wdd->status)) { /** nowayout:策略上禁止停止看门狗。 */
pr_info("watchdog%d: nowayout prevents watchdog being stopped!\n",
wdd->id);
return -EBUSY;
}

if (wdd->ops->stop) { /** 底层支持 stop:允许关闭硬件倒计时。 */
clear_bit(WDOG_HW_RUNNING, &wdd->status); /** 先更新状态位:停止意图已发生。 */
err = wdd->ops->stop(wdd); /** 调用底层 stop。 */
trace_watchdog_stop(wdd, err); /** 追踪点:观测 stop 结果。 */
} else {
set_bit(WDOG_HW_RUNNING, &wdd->status); /** 不支持 stop:视为硬件仍然运行(或不可被软件停止)。 */
}

if (err == 0) {
clear_bit(WDOG_ACTIVE, &wdd->status); /** 仅在 stop 成功时清除 ACTIVE。 */
watchdog_update_worker(wdd); /** 让 worker 策略与 ACTIVE 状态一致(通常停止自动喂狗)。 */
watchdog_hrtimer_pretimeout_stop(wdd); /** 停止 pretimeout 计时器,避免无意义回调。 */
}

return err;
}

watchdog_get_status:读取并组合状态标志(含“魔术关闭”“keepalive ping”“pretimeout”)

作用与实现要点

  • 优先使用 ops->status(),否则从 bootstatus 中抽取一组标准位。

  • 根据核心层私有状态位补充:

    • _WDOG_ALLOW_RELEASEWDIOF_MAGICCLOSE
    • _WDOG_KEEPALIVE(test_and_clear)→ WDIOF_KEEPALIVEPING
  • 若启用 hrtimer pretimeout 配置,则补充 WDIOF_PRETIMEOUT

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* @brief 获取 watchdog 的状态位组合(核心层封装)。
*
* @param wdd watchdog 设备对象。
* @return 组合后的 WDIOF_* 状态位。
*
* @note 调用者必须已持有 wd_data->lock。
*/
static unsigned int watchdog_get_status(struct watchdog_device *wdd)
{
struct watchdog_core_data *wd_data = wdd->wd_data; /**< 核心层状态数据,包含辅助状态位。 */
unsigned int status; /**< 返回给用户态/上层的 WDIOF_* 位集合。 */

if (wdd->ops->status)
status = wdd->ops->status(wdd); /** 底层提供实时状态则直接取用。 */
else
status = wdd->bootstatus & (WDIOF_CARDRESET |
WDIOF_OVERHEAT |
WDIOF_FANFAULT |
WDIOF_EXTERN1 |
WDIOF_EXTERN2 |
WDIOF_POWERUNDER |
WDIOF_POWEROVER); /** 否则仅从 bootstatus 抽取一组标准原因位。 */

if (test_bit(_WDOG_ALLOW_RELEASE, &wd_data->status))
status |= WDIOF_MAGICCLOSE; /** 允许“魔术关闭”语义:释放设备文件可触发特定关闭行为。 */

if (test_and_clear_bit(_WDOG_KEEPALIVE, &wd_data->status))
status |= WDIOF_KEEPALIVEPING; /** 报告一次“发生过 keepalive”,并清除此一次性标志位。 */

if (IS_ENABLED(CONFIG_WATCHDOG_HRTIMER_PRETIMEOUT))
status |= WDIOF_PRETIMEOUT; /** 编译期启用 pretimeout 支持,则对外宣告具备该能力。 */

return status;
}

watchdog_set_timeout:设置主超时时间(并在必要时调整/禁用 pretimeout)

作用与实现要点

  • 若设备不支持 WDIOF_SETTIMEOUT,返回 -EOPNOTSUPP
  • 参数合法性由 watchdog_timeout_invalid() 判定,非法返回 -EINVAL
  • 若底层实现 set_timeout:调用之并记录 trace;否则仅更新 wdd->timeout
  • 当新的 timeout 小于等于当前 pretimeout 时,核心层会将 pretimeout 置 0,以避免 pretimeout 逻辑落在无意义区间。
  • 最后调用 watchdog_update_worker() 使后台策略与新超时一致。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* @brief 设置 watchdog 主超时时间(单位:秒)。
*
* @param wdd watchdog 设备对象。
* @param timeout 目标超时秒数。
* @return 成功返回 0;失败返回负 errno。
*
* @note 调用者必须已持有 wd_data->lock。
*/
static int watchdog_set_timeout(struct watchdog_device *wdd,
unsigned int timeout)
{
int err = 0; /**< set_timeout 路径错误码,默认成功。 */

if (!(wdd->info->options & WDIOF_SETTIMEOUT))
return -EOPNOTSUPP; /** 设备信息声明不支持设置超时。 */

if (watchdog_timeout_invalid(wdd, timeout))
return -EINVAL; /** 超时参数不在允许范围内。 */

if (wdd->ops->set_timeout) {
err = wdd->ops->set_timeout(wdd, timeout); /** 底层实现了 set_timeout,则以硬件为准进行配置。 */
trace_watchdog_set_timeout(wdd, timeout, err); /** 追踪点:观测配置结果。 */
} else {
wdd->timeout = timeout; /** 无底层实现时仅更新软件字段(依赖其他路径使用它)。 */
if (wdd->pretimeout >= wdd->timeout)
wdd->pretimeout = 0; /** pretimeout 不得大于等于 timeout,否则语义冲突,直接禁用。 */
}

watchdog_update_worker(wdd); /** 更新 worker:让自动喂狗/调度周期匹配新 timeout。 */

return err;
}

watchdog_set_pretimeout:设置预超时(pretimeout)

作用与实现要点

  • 若设备不具备 pretimeout 能力,返回 -EOPNOTSUPP
  • 参数合法性由 watchdog_pretimeout_invalid() 判定,非法返回 -EINVAL
  • 若底层实现且声明 WDIOF_PRETIMEOUT:调用底层配置;否则仅更新软件字段 wdd->pretimeout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* @brief 设置 watchdog 预超时(单位:秒)。
*
* @param wdd watchdog 设备对象。
* @param timeout 预超时秒数。
* @return 成功返回 0;失败返回负 errno。
*/
static int watchdog_set_pretimeout(struct watchdog_device *wdd,
unsigned int timeout)
{
int err = 0; /**< 返回错误码,默认成功。 */

if (!watchdog_have_pretimeout(wdd))
return -EOPNOTSUPP; /** 设备不具备 pretimeout 能力。 */

if (watchdog_pretimeout_invalid(wdd, timeout))
return -EINVAL; /** 参数非法。 */

if (wdd->ops->set_pretimeout && (wdd->info->options & WDIOF_PRETIMEOUT))
err = wdd->ops->set_pretimeout(wdd, timeout); /** 由底层硬件驱动完成配置。 */
else
wdd->pretimeout = timeout; /** 无底层实现则仅更新软件字段,供核心层策略使用。 */

return err;
}

watchdog_get_timeleft:读取剩余时间(离复位还有多久)

作用与实现要点

  • 若底层未实现 get_timeleft,返回 -EOPNOTSUPP
  • 否则通过 ops->get_timeleft() 获取剩余秒数写回 *timeleft,并返回 0。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* @brief 获取 watchdog 剩余时间(单位:秒)。
*
* @param wdd watchdog 设备对象。
* @param timeleft 输出参数:剩余秒数。
* @return 成功返回 0;失败返回负 errno。
*
* @note 调用者必须已持有 wd_data->lock。
*/
static int watchdog_get_timeleft(struct watchdog_device *wdd,
unsigned int *timeleft)
{
*timeleft = 0; /**< 先置零,避免失败路径留下未定义值。 */

if (!wdd->ops->get_timeleft)
return -EOPNOTSUPP; /** 底层不支持读取剩余时间。 */

*timeleft = wdd->ops->get_timeleft(wdd); /** 底层提供剩余时间读数。 */

return 0;
}

watchdog_ioctl_op watchdog_write watchdog_ioctl watchdog_open watchdog_core_data_release watchdog_release 看门狗核心层封装


watchdog_ioctl_op 调用驱动私有 ioctl 或回退兜底

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* @brief 调用看门狗驱动的 ioctl 回调(若已实现)。
* @param wdd 看门狗设备对象。
* @param cmd ioctl 命令。
* @param arg ioctl 参数(通常为用户态指针地址的整数表示)。
* @return 成功返回 0;若驱动未实现 ioctl 返回 -ENOIOCTLCMD;否则返回驱动 ioctl 的错误码。
*
* @note 调用方必须持有 wd_data->lock,以保证 wdd 及其 ops 的并发与生命周期安全。
*/
static int watchdog_ioctl_op(struct watchdog_device *wdd, unsigned int cmd,
unsigned long arg)
{
if (!wdd->ops->ioctl)
return -ENOIOCTLCMD; /* 能力门控:驱动未提供 ioctl,交由上层通用分支处理 */

return wdd->ops->ioctl(wdd, cmd, arg); /* 分支策略:驱动优先扩展 */
}

watchdog_write 写入即喂狗并解析一次性 magic 字符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* @brief watchdog 写操作:定义为一次 keepalive,同时解析 magic close 字符 'V'。
* @param file VFS 文件对象。
* @param data 用户态数据指针。
* @param len 写入长度。
* @param ppos 文件偏移指针。
* @return 成功返回 len;失败返回负错误码。
*
* @note 写入数据包含 'V' 时,设置一次性允许释放标志,影响下一次 close 的 stop 决策。
*/
static ssize_t watchdog_write(struct file *file, const char __user *data,
size_t len, loff_t *ppos)
{
struct watchdog_core_data *wd_data = file->private_data;
struct watchdog_device *wdd;
int err;
size_t i;
char c;

if (len == 0)
return 0;

clear_bit(_WDOG_ALLOW_RELEASE, &wd_data->status); /* 一次性标志位:每次写入先清除,避免历史残留 */

for (i = 0; i != len; i++) {
if (get_user(c, data + i))
return -EFAULT; /* 参数合法性:用户指针不可读 */
if (c == 'V')
set_bit(_WDOG_ALLOW_RELEASE, &wd_data->status); /* 一次性标志位:允许下一次 close 尝试 stop */
}

err = -ENODEV;
mutex_lock(&wd_data->lock);
wdd = wd_data->wdd;
if (wdd)
err = watchdog_ping(wdd); /* 关键动作:写入即喂狗 */
mutex_unlock(&wd_data->lock);

if (err < 0)
return err;

return len;
}

watchdog_ioctl 驱动 ioctl 优先 通用 ioctl 兜底

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
/**
* @brief 处理 watchdog 设备 ioctl:优先转发驱动私有 ioctl,未实现则走通用 API。
* @param file 文件对象。
* @param cmd ioctl 命令。
* @param arg ioctl 参数(用户态传入)。
* @return 成功返回 0;失败返回负错误码。
*
* @note 内部持有 wd_data->lock,保证 wdd 操作的并发安全。
*/
static long watchdog_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct watchdog_core_data *wd_data = file->private_data;
void __user *argp = (void __user *)arg;
struct watchdog_device *wdd;
int __user *p = argp;
unsigned int val;
int err;

mutex_lock(&wd_data->lock);

wdd = wd_data->wdd;
if (!wdd) {
err = -ENODEV; /* 参数合法性:设备不存在 */
goto out_ioctl;
}

err = watchdog_ioctl_op(wdd, cmd, arg); /* 分支策略:驱动私有 ioctl 优先 */
if (err != -ENOIOCTLCMD)
goto out_ioctl;

switch (cmd) {
case WDIOC_GETSUPPORT:
err = copy_to_user(argp, wdd->info,
sizeof(struct watchdog_info)) ? -EFAULT : 0; /* 参数合法性:用户态写回 */
break;
case WDIOC_GETSTATUS:
val = watchdog_get_status(wdd);
err = put_user(val, p); /* 用户态写回:状态 */
break;
case WDIOC_GETBOOTSTATUS:
err = put_user(wdd->bootstatus, p); /* 用户态写回:bootstatus */
break;
case WDIOC_SETOPTIONS:
if (get_user(val, p)) { /* 参数合法性:读取用户选项失败 */
err = -EFAULT;
break;
}
if (val & WDIOS_DISABLECARD) {
err = watchdog_stop(wdd); /* 分支策略:按位选择 stop */
if (err < 0)
break;
}
if (val & WDIOS_ENABLECARD)
err = watchdog_start(wdd); /* 分支策略:按位选择 start */
break;
case WDIOC_KEEPALIVE:
if (!(wdd->info->options & WDIOF_KEEPALIVEPING)) { /* 能力门控:不支持 keepalive */
err = -EOPNOTSUPP;
break;
}
err = watchdog_ping(wdd); /* 关键动作:喂狗 */
break;
case WDIOC_SETTIMEOUT:
if (get_user(val, p)) { /* 参数合法性:读取 timeout 失败 */
err = -EFAULT;
break;
}
err = watchdog_set_timeout(wdd, val);
if (err < 0)
break;
err = watchdog_ping(wdd); /* 分支策略:设置后立刻喂狗 */
if (err < 0)
break;
fallthrough;
case WDIOC_GETTIMEOUT:
if (wdd->timeout == 0) { /* 能力门控:timeout 未知/不支持 */
err = -EOPNOTSUPP;
break;
}
err = put_user(wdd->timeout, p); /* 用户态写回:timeout */
break;
case WDIOC_GETTIMELEFT:
err = watchdog_get_timeleft(wdd, &val);
if (err < 0)
break;
err = put_user(val, p); /* 用户态写回:剩余时间 */
break;
case WDIOC_SETPRETIMEOUT:
if (get_user(val, p)) { /* 参数合法性:读取 pretimeout 失败 */
err = -EFAULT;
break;
}
err = watchdog_set_pretimeout(wdd, val);
break;
case WDIOC_GETPRETIMEOUT:
err = put_user(wdd->pretimeout, p); /* 用户态写回:pretimeout */
break;
default:
err = -ENOTTY;
break;
}

out_ioctl:
mutex_unlock(&wd_data->lock);
return err;
}

watchdog_open 单开保护并启动看门狗与引用计数管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
* @brief 打开 /dev/watchdog*:单开保护,并在需要时启动看门狗与增加引用。
* @param inode 设备 inode。
* @param file 文件对象。
* @return 成功返回 0;失败返回负错误码。
*
* @note 通过 _WDOG_DEV_OPEN 实现 single open;依据硬件是否已运行决定 try_module_get/get_device。
*/
static int watchdog_open(struct inode *inode, struct file *file)
{
struct watchdog_core_data *wd_data;
struct watchdog_device *wdd;
bool hw_running;
int err;

if (imajor(inode) == MISC_MAJOR)
wd_data = old_wd_data;
else
wd_data = container_of(inode->i_cdev, struct watchdog_core_data,
cdev);

if (test_and_set_bit(_WDOG_DEV_OPEN, &wd_data->status)) /* 状态位:单开 */
return -EBUSY;

wdd = wd_data->wdd;

hw_running = watchdog_hw_running(wdd);
if (!hw_running && !try_module_get(wdd->ops->owner)) { /* 分支策略:仅硬件未运行时防卸载 */
err = -EBUSY;
goto out_clear;
}

err = watchdog_start(wdd);
if (err < 0)
goto out_mod;

file->private_data = wd_data;

if (!hw_running)
get_device(&wd_data->dev); /* 生命周期:首次启动增加设备引用 */

wd_data->open_deadline = KTIME_MAX; /* 时间基准:设置为无穷 */

return stream_open(inode, file);

out_mod:
module_put(wd_data->wdd->ops->owner); /* 回滚:释放模块引用 */
out_clear:
clear_bit(_WDOG_DEV_OPEN, &wd_data->status); /* 状态位:失败路径清理 */
return err;
}

watchdog_core_data_release 释放 core_data 设备对象内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @brief watchdog_core_data 的 device release 回调:释放 wd_data。
* @param dev 设备对象指针。
*
* @note 设备引用计数归零后触发,用于最终资源回收。
*/
static void watchdog_core_data_release(struct device *dev)
{
struct watchdog_core_data *wd_data;

wd_data = container_of(dev, struct watchdog_core_data, dev);

kfree(wd_data);
}

watchdog_release 关闭设备并按 magic close 能力决定 stop 或继续运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
* @brief 释放 /dev/watchdog:依据一次性标志位与能力配置决定是否停止看门狗。
* @param inode 设备 inode。
* @param file 文件对象。
* @return 恒返回 0。
*
* @note stop 失败会打印严重日志并立即喂狗;随后刷新后台 worker 状态。
*/
static int watchdog_release(struct inode *inode, struct file *file)
{
struct watchdog_core_data *wd_data = file->private_data;
struct watchdog_device *wdd;
int err = -EBUSY;
bool running;

mutex_lock(&wd_data->lock);

wdd = wd_data->wdd;
if (!wdd)
goto done;

if (!watchdog_active(wdd))
err = 0;
else if (test_and_clear_bit(_WDOG_ALLOW_RELEASE, &wd_data->status) || /* 一次性标志位:读取并清除 */
!(wdd->info->options & WDIOF_MAGICCLOSE)) /* 能力门控:未启用 magicclose 则允许尝试 stop */
err = watchdog_stop(wdd);

if (err < 0) {
pr_crit("watchdog%d: watchdog did not stop!\n", wdd->id);
watchdog_ping(wdd); /* 分支策略:无法停则强制喂狗 */
}

watchdog_update_worker(wdd); /* 后台机制:同步 worker 状态 */

clear_bit(_WDOG_DEV_OPEN, &wd_data->status); /* 状态位:释放单开 */

done:
running = wdd && watchdog_hw_running(wdd);
mutex_unlock(&wd_data->lock);

if (!running) {
module_put(wd_data->cdev.owner);
put_device(&wd_data->dev);
}
return 0;
}

Watchdog 字符设备注册与 legacy /dev/watchdog 兼容:watchdog_cdev_register / watchdog_cdev_unregister / watchdog_dev_register / watchdog_dev_unregister

这段代码的核心是:把一个 struct watchdog_device 绑定到 Linux 设备模型与字符设备节点,并且id==0 的 watchdog 额外维护历史兼容的 miscdevice 节点 /dev/watchdog。同时还初始化了内核喂狗工作(kthread work)与高精度定时器(hrtimer),用于“内核在用户态启动前/或用户态失效时的保底喂狗策略”和 pretimeout 相关机制。


watchdog_cdev_register:为一个 watchdog 设备建立 cdev + class 设备节点,并在 id==0 时建立 legacy miscdevice

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
static struct miscdevice watchdog_miscdev = {
.minor = WATCHDOG_MINOR,
.name = "watchdog",
.fops = &watchdog_fops,
};

static const struct class watchdog_class = {
.name = "watchdog",
.dev_groups = wdt_groups,
};

/**
* @brief 注册 watchdog 的字符设备与 sysfs/class 设备,并在 id==0 时注册 legacy 的 misc /dev/watchdog。
*
* 关键点:
* - 分配并挂接 watchdog_core_data(wd_data),它是 watchdog core 运行期状态与并发保护的核心载体;
* - 为每个 wdd 建立一个独立的 cdev:主设备号为 watchdog_devt 的 MAJOR,次设备号为 wdd->id;
* - 对 wdd->id == 0,额外注册 miscdevice:该 miscdevice 固定使用 WATCHDOG_MINOR,对应历史 /dev/watchdog;
* - 初始化 kthread_work 与 hrtimer,用于内核侧定时 ping 与 pretimeout 支持;
* - 若检测到硬件已经在跑(例如 bootloader 启动),会对模块与设备引用计数加固,避免驱动被卸载并可选立即安排一次 ping。
*/
static int watchdog_cdev_register(struct watchdog_device *wdd)
{
struct watchdog_core_data *wd_data;
int err;

/** @brief wd_data 保存 cdev、device、锁、hrtimer、kthread_work 等“core 层状态”。 */
wd_data = kzalloc(sizeof(struct watchdog_core_data), GFP_KERNEL);
if (!wd_data)
return -ENOMEM;

/** @brief 串行化 open/ioctl/ping/stop 等路径对 wd_data 的访问(单核仍可能被抢占/线程化中断打断)。 */
mutex_init(&wd_data->lock);

/** @brief 建立双向关联:核心状态指向 wdd;wdd 也回指核心状态。 */
wd_data->wdd = wdd;
wdd->wd_data = wd_data;

/**
* @brief watchdog_kworker 是 watchdog core 统一的内核工作线程上下文;
* 若不存在,说明 core 的异步执行基础设施未就绪,注册没有意义。
*/
if (IS_ERR_OR_NULL(watchdog_kworker)) {
kfree(wd_data);
return -ENODEV;
}

/** @brief 初始化设备模型对象:后续会进入 class、生成 sysfs 节点并绑定到 cdev。 */
device_initialize(&wd_data->dev);

/** @brief 设备号:主设备号由 watchdog_devt 决定,次设备号使用 wdd->id。 */
wd_data->dev.devt = MKDEV(MAJOR(watchdog_devt), wdd->id);
wd_data->dev.class = &watchdog_class;
wd_data->dev.parent = wdd->parent;
wd_data->dev.groups = wdd->groups;

/** @brief release 回调用于设备引用计数归零时释放 wd_data(避免内存泄漏/悬挂)。 */
wd_data->dev.release = watchdog_core_data_release;

/** @brief 将 wdd 作为 drvdata 挂到 device 上,便于从 device 反查 watchdog_device。 */
dev_set_drvdata(&wd_data->dev, wdd);

/** @brief 设备名 watchdog%d 决定了 /sys/class/watchdog/watchdogX 等命名。 */
err = dev_set_name(&wd_data->dev, "watchdog%d", wdd->id);
if (err) {
put_device(&wd_data->dev);
return err;
}

/** @brief 内核喂狗工作:在 watchdog_kworker 上下文中执行 watchdog_ping_work。 */
kthread_init_work(&wd_data->work, watchdog_ping_work);

/**
* @brief hrtimer:用单调时钟驱动定时动作(例如定期 ping、pretimout 相关时序)。
* HRTIMER_MODE_REL_HARD 代表回调可能在硬中断/高优先级上下文触发,要求回调路径避免睡眠。
*/
hrtimer_setup(&wd_data->timer, watchdog_timer_expired, CLOCK_MONOTONIC,
HRTIMER_MODE_REL_HARD);

/** @brief 初始化 pretimeout 的 hrtimer/机制(是否启用由 wdd->info/ops 与配置共同决定)。 */
watchdog_hrtimer_pretimeout_init(wdd);

/**
* @brief legacy /dev/watchdog 兼容:
* - 只有 id==0 的 watchdog 负责提供固定节点 /dev/watchdog;
* - old_wd_data 作为 legacy 节点访问时对应的“当前 0 号 watchdog”的全局指针。
*/
if (wdd->id == 0) {
old_wd_data = wd_data;
watchdog_miscdev.parent = wdd->parent;

/** @brief misc_register 失败常见原因是 -EBUSY:已有 legacy watchdog 模块占用了同一 misc minor。 */
err = misc_register(&watchdog_miscdev);
if (err != 0) {
pr_err("%s: cannot register miscdev on minor=%d (err=%d).\n",
wdd->info->identity, WATCHDOG_MINOR, err);
if (err == -EBUSY)
pr_err("%s: a legacy watchdog module is probably present.\n",
wdd->info->identity);
old_wd_data = NULL;
put_device(&wd_data->dev);
return err;
}
}

/** @brief 初始化并绑定 cdev:watchdog_fops 负责 open/ioctl/write 等用户态接口语义。 */
cdev_init(&wd_data->cdev, &watchdog_fops);
wd_data->cdev.owner = wdd->ops->owner;

/** @brief 将 cdev 与 device 一并加入系统:完成后 /dev/watchdogX 通常可见。 */
err = cdev_device_add(&wd_data->cdev, &wd_data->dev);
if (err) {
pr_err("watchdog%d unable to add device %d:%d\n",
wdd->id, MAJOR(watchdog_devt), wdd->id);
if (wdd->id == 0) {
misc_deregister(&watchdog_miscdev);
old_wd_data = NULL;
}
put_device(&wd_data->dev);
return err;
}

/**
* @brief last_hw_keepalive 用于估计“上一次硬件心跳时间”:
* 设置为“略早于现在”可避免边界条件下被当作从未喂狗。
*/
wd_data->last_hw_keepalive = ktime_sub(ktime_get(), 1);

/** @brief open_deadline 影响 open() 后需要在何时前完成首次 ping/配置,防止用户态打开后长期不动作。 */
watchdog_set_open_deadline(wd_data);

/**
* @brief 若硬件已处于运行状态:
* - 通过 __module_get 防止驱动模块被卸载(否则用户态仍在喂狗但代码消失会导致灾难性后果);
* - get_device 增加 device 引用,保证 wd_data 生命周期覆盖内核喂狗逻辑;
* - handle_boot_enabled 允许内核在用户态接管前立即启动定时 ping。
*/
if (watchdog_hw_running(wdd)) {
__module_get(wdd->ops->owner);
get_device(&wd_data->dev);
if (handle_boot_enabled)
hrtimer_start(&wd_data->timer, 0,
HRTIMER_MODE_REL_HARD);
else
pr_info("watchdog%d running and kernel based pre-userspace handler disabled\n",
wdd->id);
}

return 0;
}

watchdog_cdev_unregister:删除 cdev + legacy miscdevice,并安全停止内核侧异步机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* @brief 注销 watchdog 的字符设备与设备模型对象,并在必要时停止 watchdog 与相关定时/工作项。
*
* 关键点:
* - 先删除 cdev/device,再处理 legacy misc;
* - 若配置 WDOG_STOP_ON_UNREGISTER 且 watchdog 仍 active,则尝试 stop(语义上是“驱动卸载时尽量停表”);
* - 停止 pretimeout 相关机制;
* - 通过锁把 wd_data->wdd 与 wdd->wd_data 置空,避免并发路径继续访问已注销对象;
* - cancel hrtimer 与 kthread_work,防止释放后仍回调。
*/
static void watchdog_cdev_unregister(struct watchdog_device *wdd)
{
struct watchdog_core_data *wd_data = wdd->wd_data;

cdev_device_del(&wd_data->cdev, &wd_data->dev);

if (wdd->id == 0) {
misc_deregister(&watchdog_miscdev);
old_wd_data = NULL;
}

if (watchdog_active(wdd) &&
test_bit(WDOG_STOP_ON_UNREGISTER, &wdd->status)) {
/** @brief 依赖驱动是否实现 stop;stop 失败通常由 watchdog core 统一处理错误路径。 */
watchdog_stop(wdd);
}

/** @brief 停止 pretimeout 的 hrtimer/机制,避免注销后仍产生 pretimeout 通知。 */
watchdog_hrtimer_pretimeout_stop(wdd);

/** @brief 置空关联以阻断后续访问;锁用于与 open/ioctl/ping 等路径互斥。 */
mutex_lock(&wd_data->lock);
wd_data->wdd = NULL;
wdd->wd_data = NULL;
mutex_unlock(&wd_data->lock);

/** @brief 确保定时器与异步工作彻底停止,避免 use-after-free。 */
hrtimer_cancel(&wd_data->timer);
kthread_cancel_work_sync(&wd_data->work);

/** @brief 释放 device 引用,最终会触发 watchdog_core_data_release 回收 wd_data。 */
put_device(&wd_data->dev);
}

watchdog_dev_register / watchdog_dev_unregister:把“字符设备注册”与“pretimout 注册”组合成对外 API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* @brief 注册 watchdog:先建立 /dev/watchdogX(以及可能的 /dev/watchdog),再注册 pretimeout 相关能力。
*
* 说明:
* - pretimeout 注册失败时,需要回滚字符设备注册,保证对外可见性与内部能力一致。
*/
int watchdog_dev_register(struct watchdog_device *wdd)
{
int ret;

ret = watchdog_cdev_register(wdd);
if (ret)
return ret;

ret = watchdog_register_pretimeout(wdd);
if (ret)
watchdog_cdev_unregister(wdd);

return ret;
}

/**
* @brief 注销 watchdog:先撤销 pretimeout,再撤销字符设备(顺序与注册相反以保持依赖关系正确释放)。
*/
void watchdog_dev_unregister(struct watchdog_device *wdd)
{
watchdog_unregister_pretimeout(wdd);
watchdog_cdev_unregister(wdd);
}

Watchdog 内核喂狗调度:watchdog_need_worker / watchdog_next_keepalive / __watchdog_ping / watchdog_ping_work 等

这组函数实现了 watchdog core 的一个关键能力:当用户态设置的超时(wdd->timeout)超过硬件可支持的最大超时(wdd->max_hw_heartbeat_ms)时,内核通过 hrtimer+kthread 周期性“代喂狗”,从而在用户态视角上维持更长的逻辑超时;另外,当硬件 watchdog 已经在运行但用户态尚未打开设备节点时,内核也会在 open_deadline 之前“保底喂狗”,避免启动阶段被硬件复位。


watchdog_need_worker:判断是否需要内核 worker 代喂狗

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* @brief 判断是否需要启动内核喂狗 worker(hrtimer 驱动的 kthread_work)。
* @param wdd watchdog 设备对象。
* @return true 需要 worker;false 不需要。
*
* 触发 worker 的两类典型场景:
* 1) 用户态已激活 watchdog,且用户态要求的超时 t 大于硬件最大超时 hm;
* 2) 用户态未激活(尚未 open/enable),但硬件已在运行(例如 bootloader 遗留),且仍处于 open_deadline 窗口内需要保底喂狗。
*/
static inline bool watchdog_need_worker(struct watchdog_device *wdd)
{
/**< 硬件最大超时能力(毫秒)。为 0 表示驱动未声明该能力。 */
unsigned int hm = wdd->max_hw_heartbeat_ms;
/**< 用户态期望的逻辑超时(毫秒)。 */
unsigned int t = wdd->timeout * 1000;

return (hm && watchdog_active(wdd) && t > hm) ||
(t && !watchdog_active(wdd) && watchdog_hw_running(wdd));
}

watchdog_next_keepalive:计算下一次“内核代喂狗”的相对时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/**
* @brief 计算下一次应当发起硬件喂狗的等待时间(相对 now 的 ktime_t)。
* @param wdd watchdog 设备对象。
* @return 相对当前时间的延迟;用于 hrtimer_start(..., REL)。
*
* 关键思想(非平凡点):
* - “虚拟超时”以用户态最后一次 ping(last_keepalive)为起点,目标是 wdd->timeout;
* - 若硬件最大超时小于虚拟超时,则内核必须保证在“虚拟超时到期前 hw_heartbeat_ms”之内至少喂一次;
* - 常规情况下按 hw_heartbeat_ms/2 周期喂狗以留出裕量;
* - 但若已经接近“最后允许喂狗点”,则需要提前(返回更小的 latest_heartbeat)。
*/
static ktime_t watchdog_next_keepalive(struct watchdog_device *wdd)
{
struct watchdog_core_data *wd_data = wdd->wd_data;
unsigned int timeout_ms = wdd->timeout * 1000;
ktime_t keepalive_interval;
ktime_t last_heartbeat, latest_heartbeat;
ktime_t virt_timeout;
unsigned int hw_heartbeat_ms;

/**
* @brief virt_timeout 是“逻辑到期时刻”:
* - active:以用户态最后一次 ping 为基准,推进 timeout;
* - 非 active:以 open_deadline 为基准(启动阶段保底窗口)。
*/
if (watchdog_active(wdd))
virt_timeout = ktime_add(wd_data->last_keepalive,
ms_to_ktime(timeout_ms));
else
virt_timeout = wd_data->open_deadline;

/**
* @brief 选择实际硬件喂狗周期上限:
* - timeout_ms:用户态逻辑需求;
* - max_hw_heartbeat_ms:硬件上限;
* 取两者的“非零最小值”,避免 0 参与导致周期错误。
*/
hw_heartbeat_ms = min_not_zero(timeout_ms, wdd->max_hw_heartbeat_ms);

/**< 常规策略:在硬件超时的一半处喂狗,提高抗抖动余量。 */
keepalive_interval = ms_to_ktime(hw_heartbeat_ms / 2);

/**
* @brief last_heartbeat 表示“最晚必须喂狗的绝对时刻”:
* 若超过该时刻仍不喂狗,则硬件超时将先于 virt_timeout 触发复位。
*/
last_heartbeat = ktime_sub(virt_timeout, ms_to_ktime(hw_heartbeat_ms));

/**< latest_heartbeat:从 now 到 last_heartbeat 的剩余时间(可能小于常规周期)。 */
latest_heartbeat = ktime_sub(last_heartbeat, ktime_get());

if (ktime_before(latest_heartbeat, keepalive_interval))
return latest_heartbeat;

return keepalive_interval;
}

watchdog_update_worker:根据 need_worker 决定启动/取消 hrtimer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* @brief 根据当前状态更新内核喂狗 hrtimer。
* @param wdd watchdog 设备对象。
*
* 注意:这里使用 HRTIMER_MODE_REL_HARD,意味着定时器到期回调路径通常要求“不可睡眠”。
* 该实现通过“hrtimer 触发 -> kthread_work 执行 __watchdog_ping”将可能睡眠的硬件操作放到线程上下文。
*/
static inline void watchdog_update_worker(struct watchdog_device *wdd)
{
struct watchdog_core_data *wd_data = wdd->wd_data;

if (watchdog_need_worker(wdd)) {
ktime_t t = watchdog_next_keepalive(wdd);

if (t > 0)
hrtimer_start(&wd_data->timer, t,
HRTIMER_MODE_REL_HARD);
} else {
hrtimer_cancel(&wd_data->timer);
}
}

__watchdog_ping:真正执行一次“硬件喂狗/重启 watchdog”的核心路径(带最小间隔节流)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
* @brief 执行一次硬件喂狗:优先调用 ops->ping,否则用 ops->start 作为“重启/刷新”替代。
* @param wdd watchdog 设备对象。
* @return 0 成功;负值 errno 失败。
*
* 非平凡点:
* - min_hw_heartbeat_ms 约束“喂狗最小间隔”,避免过频访问硬件(或违反硬件窗口/节拍约束);
* - 若未到最早喂狗时刻,则不直接喂狗,而是把 hrtimer 重新安排到 earliest_keepalive;
* - 成功后启动 pretimeout 机制,并重算下一次 worker 喂狗计划。
*/
static int __watchdog_ping(struct watchdog_device *wdd)
{
struct watchdog_core_data *wd_data = wdd->wd_data;
ktime_t earliest_keepalive, now;
int err;

/**< earliest_keepalive:上次硬件喂狗 + 最小喂狗间隔。 */
earliest_keepalive = ktime_add(wd_data->last_hw_keepalive,
ms_to_ktime(wdd->min_hw_heartbeat_ms));
now = ktime_get();

if (ktime_after(earliest_keepalive, now)) {
/**< 未到允许喂狗的最早时刻:把定时器推迟到 earliest_keepalive。 */
hrtimer_start(&wd_data->timer,
ktime_sub(earliest_keepalive, now),
HRTIMER_MODE_REL_HARD);
return 0;
}

wd_data->last_hw_keepalive = now;

if (wdd->ops->ping) {
err = wdd->ops->ping(wdd);
trace_watchdog_ping(wdd, err);
} else {
/**< 部分硬件没有独立 ping,只能通过 start 重新装载/启动来达到“刷新计数器”的效果。 */
err = wdd->ops->start(wdd);
trace_watchdog_start(wdd, err);
}

if (err == 0)
watchdog_hrtimer_pretimeout_start(wdd);

watchdog_update_worker(wdd);

return err;
}

watchdog_ping:对外层包装(要求 caller 已持有锁,且只在硬件运行时才触发)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* @brief watchdog core 的“喂狗入口”封装:更新状态位与 last_keepalive,再进入 __watchdog_ping。
* @param wdd watchdog 设备对象。
* @return 0 成功;负值 errno 失败。
*
* 关键点:
* - 若硬件未运行则直接返回(避免无意义硬件访问);
* - last_keepalive 用于定义用户态视角的“逻辑超时”起点(virt_timeout 的基准)。
*/
static int watchdog_ping(struct watchdog_device *wdd)
{
struct watchdog_core_data *wd_data = wdd->wd_data;

if (!watchdog_hw_running(wdd))
return 0;

set_bit(_WDOG_KEEPALIVE, &wd_data->status);

wd_data->last_keepalive = ktime_get();
return __watchdog_ping(wdd);
}

watchdog_worker_should_ping / watchdog_ping_work:worker 线程上下文里决定是否执行 __watchdog_ping

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
* @brief 判断 worker 是否应当执行一次喂狗。
* @param wd_data watchdog core 私有数据。
* @return true 应喂狗;false 不应喂狗。
*
* 逻辑要点:
* - active:用户态已接管并启用,则 worker 允许代喂(尤其是 t > hm 的场景);
* - 非 active:仅在硬件仍在跑且未超过 open_deadline 的窗口内代喂。
*/
static bool watchdog_worker_should_ping(struct watchdog_core_data *wd_data)
{
struct watchdog_device *wdd = wd_data->wdd;

if (!wdd)
return false;

if (watchdog_active(wdd))
return true;

return watchdog_hw_running(wdd) && !watchdog_past_open_deadline(wd_data);
}

/**
* @brief kthread_work 的执行函数:在可睡眠线程上下文中调用 __watchdog_ping。
* @param work kthread_work 对象。
*
* 互斥意义(单核同样必要):
* - 用户态 ioctl/write/ping 可能与 worker 并发(抢占/线程调度导致交错);
* - 通过 wd_data->lock 串行化,避免 last_keepalive/last_hw_keepalive 与硬件访问次序被破坏。
*/
static void watchdog_ping_work(struct kthread_work *work)
{
struct watchdog_core_data *wd_data;

wd_data = container_of(work, struct watchdog_core_data, work);

mutex_lock(&wd_data->lock);
if (watchdog_worker_should_ping(wd_data))
__watchdog_ping(wd_data->wdd);
mutex_unlock(&wd_data->lock);
}

static enum hrtimer_restart watchdog_timer_expired(struct hrtimer *timer)
{
struct watchdog_core_data *wd_data;

wd_data = container_of(timer, struct watchdog_core_data, timer);

kthread_queue_work(watchdog_kworker, &wd_data->work);
return HRTIMER_NORESTART;
}

drivers/watchdog/stm32_iwdg.c

STM32 IWDG:超时参数换算与启动/喂狗/预超时中断(单核、无 MMU、ARMv7-M)

stm32_iwdg_start:将“秒级 timeout / pretimeout”换算为 IWDG 的 PR/RLR/EWCR,并启动看门狗

作用与原理

  • IWDG 的计数时钟来自 LSI,频率为 wdt->rate(Hz)。计数分频由 PR(2 的幂分频)决定,计数上限由 RLR(12bit)决定。
  • 驱动根据 timeout 反推一个足够大的分频 presc,使得 RLR <= 0xFFF,并将 presc 向上取整到 2 的幂,以符合硬件 PR 的编码方式。
  • pretimout(早期唤醒)通过 EWCR 编程:在距离最终复位还剩 pretimout 的时刻产生中断,因此 EWCR 对应的是“剩余时间 = tout - pretimeout”。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
static int stm32_iwdg_start(struct watchdog_device *wdd)
{
struct stm32_iwdg *wdt = watchdog_get_drvdata(wdd);
u32 tout, ptot, presc, iwdg_rlr, iwdg_ewcr, iwdg_pr, iwdg_sr;
int ret;

/* 预超时未设置时,默认取总超时的 3/4(便于在真正复位前留出处理窗口) */
if (!wdd->pretimeout)
wdd->pretimeout = 3 * wdd->timeout / 4;

/* tout:最终采用的总超时(秒),限制在驱动给出的硬件能力范围内 */
tout = clamp_t(unsigned int, wdd->timeout,
wdd->min_timeout, wdd->max_hw_heartbeat_ms / 1000);

/* ptot:距离复位还剩的时间窗口(秒),即 tout - pretimeout;同样做范围约束 */
ptot = clamp_t(unsigned int, tout - wdd->pretimeout,
wdd->min_timeout, tout);

/*
* presc:希望选择的“分频系数”(不是 PR 寄存器值)
* 推导:计数周期 T = presc / rate;RLR 最大为 0xFFF(计数值是 RLR+1)
* 为满足 tout 秒: (RLR+1) * presc / rate >= tout
* 取最小 presc: presc >= tout * rate / (RLR_MAX+1)
*/
presc = DIV_ROUND_UP(tout * wdt->rate, RLR_MAX + 1);

/* IWDG 分频只能是 2^k,并且最小从 2^PR_SHIFT 开始;因此 presc 取上整到 2 的幂 */
presc = roundup_pow_of_two(presc);

/* PR 编码:presc = 2^(PR_SHIFT + PR);当 presc <= 2^PR_SHIFT 时 PR=0 */
iwdg_pr = presc <= 1 << PR_SHIFT ? 0 : ilog2(presc) - PR_SHIFT;

/* RLR:按最终 presc 反算计数装载值(计数值为 RLR+1) */
iwdg_rlr = ((tout * wdt->rate) / presc) - 1;

/* EWCR:早期唤醒点对应的剩余时间 ptot(即 tout - pretimeout) */
iwdg_ewcr = ((ptot * wdt->rate) / presc) - 1;

/* 允许写 PR/RLR/EWCR(IWDG 采用钥匙序列保护寄存器写入) */
reg_write(wdt->regs, IWDG_KR, KR_KEY_EWA);

/* 写入分频与重装载值;EWCR 仅在支持 early wakeup 的型号上启用,并置 EWIE 使能中断 */
reg_write(wdt->regs, IWDG_PR, iwdg_pr);
reg_write(wdt->regs, IWDG_RLR, iwdg_rlr);
if (wdt->data->has_early_wakeup)
reg_write(wdt->regs, IWDG_EWCR, iwdg_ewcr | EWCR_EWIE);

/* 使能 IWDG:一旦开启,典型 STM32 IWDG 无法被软件完全停止(取决于 SoC/option bytes) */
reg_write(wdt->regs, IWDG_KR, KR_KEY_ENABLE);

/* 等待硬件将 PR/RLR 同步到内部(SR 的 PVU/RVU 清零表示更新完成) */
ret = readl_relaxed_poll_timeout(wdt->regs + IWDG_SR, iwdg_sr,
!(iwdg_sr & (SR_PVU | SR_RVU)),
SLEEP_US, TIMEOUT_US);
if (ret) {
dev_err(wdd->parent, "Fail to set prescaler, reload regs\n");
return ret;
}

/* 喂狗:写入 RELOAD key 将计数器重装载,确保从确定的起点开始计时 */
reg_write(wdt->regs, IWDG_KR, KR_KEY_RELOAD);

return 0;
}

stm32_iwdg_ping:喂狗(重装载计数器)

要点

  • 仅写 KR_KEY_RELOAD,不会更改 PR/RLR。
  • 在单核 ARMv7-M 上,这个写寄存器操作对外设是确定性的;是否需要临界区由上层 watchdog 框架与调用上下文决定,这里不额外加锁。
1
2
3
4
5
6
7
static int stm32_iwdg_ping(struct watchdog_device *wdd)
{
struct stm32_iwdg *wdt = watchdog_get_drvdata(wdd);

reg_write(wdt->regs, IWDG_KR, KR_KEY_RELOAD);
return 0;
}

stm32_iwdg_set_timeout / stm32_iwdg_set_pretimeout:修改参数并在已运行时重新编程

要点

  • 如果 watchdog 已经处于 active,则立即调用 stm32_iwdg_start() 重新计算并写寄存器。
  • 由于 IWDG 配置写入存在 PVU/RVU 同步窗口,这里复用 start() 的轮询等待逻辑,保证更新完成后再继续。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static int stm32_iwdg_set_timeout(struct watchdog_device *wdd,
unsigned int timeout)
{
wdd->timeout = timeout;

if (watchdog_active(wdd))
return stm32_iwdg_start(wdd);

return 0;
}

static int stm32_iwdg_set_pretimeout(struct watchdog_device *wdd,
unsigned int pretimeout)
{
wdd->pretimeout = pretimeout;

if (watchdog_active(wdd))
return stm32_iwdg_start(wdd);

return 0;
}

stm32_iwdg_isr:早期唤醒中断处理(EWCR 置位确认 + 通知框架)

作用与原理

  • 该中断不是复位中断,而是“距离复位还剩 pretimout”时的提前通知。
  • 驱动先写 EWCR 的 EWIC 做确认/清除(具体语义由硬件定义),再调用 watchdog_notify_pretimeout() 让上层执行预超时回调(例如用户态守护进程记录日志、尝试恢复等)。
1
2
3
4
5
6
7
8
9
10
11
12
13
static irqreturn_t stm32_iwdg_isr(int irq, void *wdog_arg)
{
struct watchdog_device *wdd = wdog_arg;
struct stm32_iwdg *wdt = watchdog_get_drvdata(wdd);
u32 reg;

reg = reg_read(wdt->regs, IWDG_EWCR);
reg |= EWCR_EWIC; /* 硬件要求:置位确认 early interrupt */
reg_write(wdt->regs, IWDG_EWCR, reg);

watchdog_notify_pretimeout(wdd);
return IRQ_HANDLED;
}

stm32_iwdg_clk_init / stm32_iwdg_irq_init / stm32_iwdg_probe:时钟域建立、早期唤醒中断接入与 watchdog 框架注册


stm32_iwdg_clk_init:获取并使能 LSI(可选 PCLK),确定计数基准频率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/**
* @brief 初始化 IWDG 相关时钟并获取 LSI 频率。
*
* 关注点:
* - IWDG 计数时钟使用 LSI;wdt->rate 直接决定后续 timeout→PR/RLR 的换算精度。
* - 部分 SoC 需要额外开启外设总线时钟 pclk(仅用于寄存器访问时钟门控,不改变 IWDG 计数基准)。
* - devm_add_action_or_reset 用于确保 probe 失败或卸载时,时钟能被对称关闭。
*/
static int stm32_iwdg_clk_init(struct platform_device *pdev,
struct stm32_iwdg *wdt)
{
struct device *dev = &pdev->dev;
u32 ret;

wdt->clk_lsi = devm_clk_get(dev, "lsi");
if (IS_ERR(wdt->clk_lsi))
return dev_err_probe(dev, PTR_ERR(wdt->clk_lsi),
"Unable to get lsi clock\n");

if (wdt->data->has_pclk) {
wdt->clk_pclk = devm_clk_get(dev, "pclk");
if (IS_ERR(wdt->clk_pclk))
return dev_err_probe(dev, PTR_ERR(wdt->clk_pclk),
"Unable to get pclk clock\n");

ret = clk_prepare_enable(wdt->clk_pclk); /**< 使能寄存器访问时钟门控 */
if (ret) {
dev_err(dev, "Unable to prepare pclk clock\n");
return ret;
}

ret = devm_add_action_or_reset(dev,
stm32_clk_disable_unprepare,
wdt->clk_pclk); /**< 失败/卸载时自动关闭 */
if (ret)
return ret;
}

ret = clk_prepare_enable(wdt->clk_lsi); /**< IWDG 计数基准:LSI */
if (ret) {
dev_err(dev, "Unable to prepare lsi clock\n");
return ret;
}

ret = devm_add_action_or_reset(dev, stm32_clk_disable_unprepare,
wdt->clk_lsi); /**< 失败/卸载时自动关闭 */
if (ret)
return ret;

wdt->rate = clk_get_rate(wdt->clk_lsi); /**< Hz:后续超时换算核心参数 */

return 0;
}

stm32_iwdg_irq_init:早期唤醒中断(pretimout)接入与可唤醒配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
* @brief 配置 IWDG early wakeup IRQ(若 SoC 支持且 DT 提供 IRQ)。
*
* 关注点:
* - 该 IRQ 仅在 has_early_wakeup=true 且 platform 提供 IRQ 时启用。
* - wakeup-source:把该 IRQ 注册为系统唤醒源,便于低功耗场景从 WFI/STOP 状态唤醒。
* - 启用后会把 wdd->info 切换为支持 WDIOF_PRETIMEOUT 的版本。
*/
static int stm32_iwdg_irq_init(struct platform_device *pdev,
struct stm32_iwdg *wdt)
{
struct device_node *np = pdev->dev.of_node;
struct watchdog_device *wdd = &wdt->wdd;
struct device *dev = &pdev->dev;
int irq, ret;

if (!wdt->data->has_early_wakeup)
return 0;

irq = platform_get_irq_optional(pdev, 0);
if (irq <= 0)
return 0;

if (of_property_read_bool(np, "wakeup-source")) {
ret = devm_device_init_wakeup(dev); /**< 初始化设备唤醒能力状态 */
if (ret)
return ret;

ret = dev_pm_set_wake_irq(dev, irq); /**< 绑定“唤醒 IRQ”到 PM 框架 */
if (ret)
return ret;
}

ret = devm_request_irq(dev, irq, stm32_iwdg_isr, 0,
dev_name(dev), wdd); /**< ISR 内部调用 watchdog_notify_pretimeout */
if (ret)
return ret;

wdd->info = &stm32_iwdg_preinfo; /**< 声明支持 pretimeout */
return 0;
}

stm32_iwdg_probe:资源映射、能力边界计算、框架注册与“boot 已开启”确定化处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
/**
* @brief 平台驱动 probe:把 IWDG 接入 Linux watchdog 框架。
*
* 关注点:
* - min_timeout/max_hw_heartbeat_ms 的计算依赖硬件 RLR/PR 上限与 LSI 频率。
* - 某些启动链会在进入内核前已打开 IWDG;由于硬件通常无法可靠回读配置,驱动选择“强制写入确定参数”。
*/
static int stm32_iwdg_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct watchdog_device *wdd;
struct stm32_iwdg *wdt;
int ret;

wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
if (!wdt)
return -ENOMEM;

wdt->data = of_device_get_match_data(&pdev->dev);
if (!wdt->data)
return -ENODEV;

wdt->regs = devm_platform_ioremap_resource(pdev, 0); /**< IWDG 寄存器基址映射(MMIO) */
if (IS_ERR(wdt->regs))
return PTR_ERR(wdt->regs);

ret = stm32_iwdg_clk_init(pdev, wdt); /**< 建立 wdt->rate(LSI Hz) */
if (ret)
return ret;

wdd = &wdt->wdd;
wdd->parent = dev;
wdd->info = &stm32_iwdg_info;
wdd->ops = &stm32_iwdg_ops;
wdd->timeout = DEFAULT_TIMEOUT;

/**
* min_timeout:
* - IWDG 最小推荐 RLR 为 RLR_MIN(硬件约束/建议)
* - 最小分频为 PR_MIN(对应 PR=0 的最小 2^PR_SHIFT)
* - 因此最短超时约为 (RLR_MIN+1)*PR_MIN / rate
*/
wdd->min_timeout = DIV_ROUND_UP((RLR_MIN + 1) * PR_MIN, wdt->rate);

/**
* max_hw_heartbeat_ms:
* - RLR 最大为 12bit:RLR_MAX
* - prescaler 上限由 SoC 能力给出:wdt->data->max_prescaler
* - 最大超时约为 (RLR_MAX+1)*max_prescaler / rate
*/
wdd->max_hw_heartbeat_ms = ((RLR_MAX + 1) * wdt->data->max_prescaler *
1000) / wdt->rate;

ret = stm32_iwdg_irq_init(pdev, wdt); /**< 可能把 wdd->info 切换为支持 pretimeout */
if (ret)
return ret;

watchdog_set_drvdata(wdd, wdt);
watchdog_set_nowayout(wdd, WATCHDOG_NOWAYOUT); /**< nowayout 策略由 Kconfig/平台策略决定 */
watchdog_init_timeout(wdd, 0, dev); /**< 从 DT/模块参数初始化 timeout(若提供) */

/**
* 关键点:若启动加载器已使能 IWDG,内核侧无法可靠读取“当前 PR/RLR”:
* - IWDG 常见限制是“开启后不可停止/不可回读完整配置”
* - 因此这里选择立即按驱动计算结果重编程并喂狗,避免用户态守护进程接管前发生非预期复位
*/
if (IS_ENABLED(CONFIG_WATCHDOG_HANDLE_BOOT_ENABLED)) {
ret = stm32_iwdg_start(wdd);
if (ret)
return ret;

set_bit(WDOG_HW_RUNNING, &wdd->status); /**< 告知框架:硬件已在运行 */
}

ret = devm_watchdog_register_device(dev, wdd); /**< 向 watchdog core 注册 */
if (ret)
return ret;

platform_set_drvdata(pdev, wdt);

return 0;
}

stm32_iwdg_info / stm32_iwdg_preinfo / stm32_iwdg_ops / stm32_iwdg_of_match:对 watchdog core 暴露能力、操作集与 DT 匹配


stm32_iwdg_info:普通模式下对用户态/框架声明的能力集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @brief 向 watchdog core 声明该设备支持的基础能力(无 pretimeout)。
*
* 关注点:
* - WDIOF_SETTIMEOUT:允许修改超时时间(对应 .set_timeout)。
* - WDIOF_MAGICCLOSE:支持 magic close 语义(由 watchdog core/用户态协议处理)。
* - WDIOF_KEEPALIVEPING:支持 keepalive(对应 .ping)。
*/
static const struct watchdog_info stm32_iwdg_info = {
.options = WDIOF_SETTIMEOUT |
WDIOF_MAGICCLOSE |
WDIOF_KEEPALIVEPING,
.identity = "STM32 Independent Watchdog",
};

stm32_iwdg_preinfo:支持 pretimeout(早期唤醒中断)时的能力集合

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* @brief 当平台支持 early wakeup 且成功申请 IRQ 后,wdd->info 会被替换为该版本。
*
* 关注点:
* - WDIOF_PRETIMEOUT:对用户态声明“可配置 pretimeout”,并允许框架走 pretimeout 通路。
*/
static const struct watchdog_info stm32_iwdg_preinfo = {
.options = WDIOF_SETTIMEOUT |
WDIOF_MAGICCLOSE |
WDIOF_KEEPALIVEPING |
WDIOF_PRETIMEOUT,
.identity = "STM32 Independent Watchdog",
};

stm32_iwdg_ops:watchdog core 调度到驱动的操作入口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* @brief watchdog 操作集:watchdog core 通过这些回调驱动硬件。
*
* 关注点:
* - .start:把 timeout/pretimout 换算为 PR/RLR/EWCR,并启动 IWDG。
* - .ping:喂狗(重装载计数器)。
* - .set_timeout / .set_pretimeout:修改参数,若已经运行则重编程。
*/
static const struct watchdog_ops stm32_iwdg_ops = {
.owner = THIS_MODULE,
.start = stm32_iwdg_start,
.ping = stm32_iwdg_ping,
.set_timeout = stm32_iwdg_set_timeout,
.set_pretimeout = stm32_iwdg_set_pretimeout,
};

stm32_iwdg_of_match:DT compatible → SoC 差异数据(pclk/early_wakeup/presc 上限)

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* @brief DT 匹配表:不同兼容项绑定不同 SoC 能力参数。
*
* 关注点:
* - st,stm32-iwdg:无 pclk、无 early wakeup、max_prescaler=256(对应较小分频能力)。
* - st,stm32mp1-iwdg:有 pclk、支持 early wakeup、max_prescaler=1024。
*/
static const struct of_device_id stm32_iwdg_of_match[] = {
{ .compatible = "st,stm32-iwdg", .data = &stm32_iwdg_data },
{ .compatible = "st,stm32mp1-iwdg", .data = &stm32mp1_iwdg_data },
{ /* end node */ }
};
MODULE_DEVICE_TABLE(of, stm32_iwdg_of_match);

stm32_iwdg_driver:平台驱动注册入口(probe 绑定、DT 匹配表挂载)

1
2
3
4
5
6
7
8
9
10
11
/**
* @brief platform_driver 注册:由内核平台总线根据 DT 匹配调用 probe。
*/
static struct platform_driver stm32_iwdg_driver = {
.probe = stm32_iwdg_probe,
.driver = {
.name = "iwdg",
.of_match_table = stm32_iwdg_of_match,
},
};
module_platform_driver(stm32_iwdg_driver);