[toc]
drivers/watchdog Watchdog子系统(Watchdog Subsystem) 确保系统在软件故障时自动重启 历史与背景 这项技术是为了解决什么特定问题而诞生的? Watchdog(看门狗)子系统的诞生是为了解决一个在计算系统中,尤其是高可靠性系统中,至关重要的问题:如何从致命的软件故障中自动恢复 。
软件系统可能会因为各种原因(如内核死锁、驱动程序中的无限循环、用户空间关键进程假死)而完全“卡死”(Hang),导致系统停止响应。在这种状态下,系统无法执行任何有效任务,也无法被正常地远程管理。对于无人值守的嵌入式设备或需要高可用性的服务器而言,这种状态是不可接受的。
Watchdog技术通过一个简单的“死人开关”(Dead Man’s Switch)机制来解决这个问题:
它提供一个硬件或软件定时器,一旦启动,就会开始倒计时。
系统中的监控软件(通常是一个用户空间的守护进程)必须周期性地“喂狗”(Feed the dog)或“踢狗”(Kick the dog),即重置这个定时器。
如果监控软件因为系统卡死而未能按时重置定时器,定时器就会超时。
超时后,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框架是一个典型的三层模型,连接了硬件、内核和用户空间。
硬件层(Watchdog Timer, WDT)
这是一个物理硬件定时器(或由内核模拟的软件定时器softdog)。
它的核心是一个倒数计数器。
当计数器减到0时,它会通过一个硬件信号线强制复位整个SoC或主板。
内核层(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()回调函数的调用。
用户空间层(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-timeout和kdump等机制才能在重启前捕获到有用的调试信息。
使用场景 在哪些具体的业务或技术场景下,它是首选解决方案? 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),甚至早于看门狗核心子系统自身的初始化。延迟注册确保了这些“早到”的驱动不会因核心未就绪而注册失败,而是被安全地放入一个队列中,待核心初始化完毕后再统一进行正式注册。
实现原理分析 此代码的实现巧妙地利用了内核的初始化调用顺序和同步机制来保证系统的健壮性。
两阶段初始化 :
watchdog_dev_init(): 此函数(在此代码段中未显示其定义)是第一阶段。它负责创建所有具体看门狗设备都依赖的公共资源,例如注册/sys/class/watchdog设备类,以及通过alloc_chrdev_region申请用于/dev/watchdogX的主/次设备号池。
watchdog_deferred_registration(): 这是第二阶段。它处理那些在第一阶段完成前就已尝试注册的设备。
延迟注册机制 :
内核中存在一个全局链表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,则直接进行正常的设备注册流程。
同步与执行 :
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 static int __init watchdog_deferred_registration (void ) { mutex_lock(&wtd_deferred_reg_mutex); 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 ; } static int __init watchdog_init (void ) { int err; err = watchdog_dev_init(); if (err < 0 ) return err; watchdog_deferred_registration(); return 0 ; } static void __exit watchdog_exit (void ) { watchdog_dev_exit(); ida_destroy(&watchdog_ida); } subsys_initcall_sync(watchdog_init); 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) { 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 ; } } 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); 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; } 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 ; if (wdd == NULL || wdd->info == NULL || wdd->ops == NULL ) return -EINVAL; if (!wdd->ops->start || (!wdd->ops->stop && !wdd->max_hw_heartbeat_ms)) return -EINVAL; watchdog_check_min_max_timeout(wdd); 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); } if (id < 0 ) id = ida_alloc_max(&watchdog_ida, 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); if (!(id == 0 && ret == -EBUSY)) return ret; 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; } } 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); } 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; } } } 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); } 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) { 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 ; 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 ; if (wdd->ops->restart) unregister_restart_handler(&wdd->restart_nb); if (test_bit(WDOG_STOP_ON_REBOOT, &wdd->status)) unregister_reboot_notifier(&wdd->reboot_nb); 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) { 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; 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); } 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 ; wdd = container_of(nb, struct watchdog_device, reboot_nb); if (code == SYS_DOWN || code == SYS_HALT || code == SYS_POWER_OFF) { if (watchdog_hw_running(wdd)) { int ret; ret = wdd->ops->stop(wdd); 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) { struct watchdog_device *wdd = container_of(nb, struct watchdog_device, restart_nb); int ret; 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 ; wdd = container_of(nb, struct watchdog_device, pm_nb); switch (mode) { case PM_HIBERNATION_PREPARE: case PM_RESTORE_PREPARE: case PM_SUSPEND_PREPARE: ret = watchdog_dev_suspend(wdd); break ; 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” 设备类,以及预留一块用于看门狗设备的字符设备号区域。
实现原理分析 此函数的实现集成了内核线程、设备模型和字符设备子系统的功能,以构建一个健壮的框架。
创建内核工作线程 (kthread_run_worker) :
函数首先创建一个名为 “watchdogd” 的内核工作线程(kworker)。这个线程专门用于异步处理看门狗相关的任务,例如处理预超时(pre-timeout)通知。将这些任务放在一个专用的工作线程中,可以避免阻塞调用者上下文,并允许以统一的方式处理来自不同硬件看门狗驱动的事件。
设置实时调度策略 (sched_set_fifo) :
这是一个至关重要的步骤。函数将 “watchdogd” 线程的调度策略设置为SCHED_FIFO(先进先出)。这是一种实时调度策略,赋予了该线程非常高的运行优先级。此举确保了当有看门狗相关的紧急任务(如响应预超时中断)需要执行时,”watchdogd” 线程能够立即抢占大多数其他普通内核线程或用户进程,从而保证了看门狗子系统对时间敏感事件的及时响应,这是维持系统稳定性的关键。
注册设备类 (class_register) :
通过调用class_register并传入watchdog_class(在别处定义),函数在sysfs中创建了/sys/class/watchdog/目录。这个目录充当了所有看门狗设备的逻辑容器。当一个具体的硬件看门狗驱动(如stm32-iwdg)注册其设备时,它会把自己归入这个类,从而在用户空间表现为/sys/class/watchdog/watchdog0这样的符号链接,提供了一个标准化的接口。
分配字符设备号 (alloc_chrdev_region) :
看门狗设备通过一个字符设备节点(如/dev/watchdog或/dev/watchdog0)向用户空间提供主接口。alloc_chrdev_region函数向内核动态申请并预留了一段连续的字符设备号(主/次设备号对),最多可支持MAX_DOGS个看门狗设备。这为后续创建设备节点提供了必要的“门牌号”。
错误处理 :
函数包含了完善的错误处理逻辑。如果在初始化过程中的任何一步失败,它都会以相反的顺序撤销所有已经成功的操作(例如,如果分配设备号失败,它会注销设备类并销毁工作线程),以确保系统不会处于部分初始化或资源泄漏的状态。
代码分析 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 int __init watchdog_dev_init (void ) { int err; 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_set_fifo(watchdog_kworker->task); err = class_register(&watchdog_class); if (err < 0 ) { pr_err("couldn't register class\n" ); goto err_register; } 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 同步 作用与实现要点
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 static int watchdog_start (struct watchdog_device *wdd) { struct watchdog_core_data *wd_data = wdd->wd_data; ktime_t started_at; int err; if (watchdog_active(wdd)) return 0 ; set_bit(_WDOG_KEEPALIVE, &wd_data->status); started_at = ktime_get(); if (watchdog_hw_running(wdd) && wdd->ops->ping) { err = __watchdog_ping(wdd); if (err == 0 ) { set_bit(WDOG_ACTIVE, &wdd->status); watchdog_hrtimer_pretimeout_start(wdd); } } else { err = wdd->ops->start(wdd); trace_watchdog_start(wdd, err); if (err == 0 ) { set_bit(WDOG_ACTIVE, &wdd->status); set_bit(WDOG_HW_RUNNING, &wdd->status); wd_data->last_keepalive = started_at; wd_data->last_hw_keepalive = started_at; watchdog_update_worker(wdd); watchdog_hrtimer_pretimeout_start(wdd); } } 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 static int watchdog_stop (struct watchdog_device *wdd) { int err = 0 ; if (!watchdog_active(wdd)) return 0 ; if (test_bit(WDOG_NO_WAY_OUT, &wdd->status)) { pr_info("watchdog%d: nowayout prevents watchdog being stopped!\n" , wdd->id); return -EBUSY; } if (wdd->ops->stop) { clear_bit(WDOG_HW_RUNNING, &wdd->status); err = wdd->ops->stop(wdd); trace_watchdog_stop(wdd, err); } else { set_bit(WDOG_HW_RUNNING, &wdd->status); } if (err == 0 ) { clear_bit(WDOG_ACTIVE, &wdd->status); watchdog_update_worker(wdd); watchdog_hrtimer_pretimeout_stop(wdd); } return err; }
watchdog_get_status:读取并组合状态标志(含“魔术关闭”“keepalive ping”“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 static unsigned int watchdog_get_status (struct watchdog_device *wdd) { struct watchdog_core_data *wd_data = wdd->wd_data; unsigned int status; 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); 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; if (IS_ENABLED(CONFIG_WATCHDOG_HRTIMER_PRETIMEOUT)) status |= WDIOF_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 static int watchdog_set_timeout (struct watchdog_device *wdd, unsigned int timeout) { int err = 0 ; 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); trace_watchdog_set_timeout(wdd, timeout, err); } else { wdd->timeout = timeout; if (wdd->pretimeout >= wdd->timeout) wdd->pretimeout = 0 ; } watchdog_update_worker(wdd); 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 static int watchdog_set_pretimeout (struct watchdog_device *wdd, unsigned int timeout) { int err = 0 ; if (!watchdog_have_pretimeout(wdd)) return -EOPNOTSUPP; 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 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 static int watchdog_ioctl_op (struct watchdog_device *wdd, unsigned int cmd, unsigned long arg) { if (!wdd->ops->ioctl) return -ENOIOCTLCMD; 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 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); } 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 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); 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); break ; case WDIOC_SETOPTIONS: if (get_user(val, p)) { err = -EFAULT; break ; } if (val & WDIOS_DISABLECARD) { err = watchdog_stop(wdd); if (err < 0 ) break ; } if (val & WDIOS_ENABLECARD) err = watchdog_start(wdd); break ; case WDIOC_KEEPALIVE: if (!(wdd->info->options & WDIOF_KEEPALIVEPING)) { err = -EOPNOTSUPP; break ; } err = watchdog_ping(wdd); break ; case WDIOC_SETTIMEOUT: if (get_user(val, p)) { 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 ) { err = -EOPNOTSUPP; break ; } err = put_user(wdd->timeout, p); 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)) { err = -EFAULT; break ; } err = watchdog_set_pretimeout(wdd, val); break ; case WDIOC_GETPRETIMEOUT: err = put_user(wdd->pretimeout, p); 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 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 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 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)) 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); 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, }; static int watchdog_cdev_register (struct watchdog_device *wdd) { struct watchdog_core_data *wd_data ; int err; wd_data = kzalloc(sizeof (struct watchdog_core_data), GFP_KERNEL); if (!wd_data) return -ENOMEM; mutex_init(&wd_data->lock); wd_data->wdd = wdd; wdd->wd_data = wd_data; if (IS_ERR_OR_NULL(watchdog_kworker)) { kfree(wd_data); return -ENODEV; } device_initialize(&wd_data->dev); 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; wd_data->dev.release = watchdog_core_data_release; dev_set_drvdata(&wd_data->dev, wdd); err = dev_set_name(&wd_data->dev, "watchdog%d" , wdd->id); if (err) { put_device(&wd_data->dev); return err; } kthread_init_work(&wd_data->work, watchdog_ping_work); hrtimer_setup(&wd_data->timer, watchdog_timer_expired, CLOCK_MONOTONIC, HRTIMER_MODE_REL_HARD); watchdog_hrtimer_pretimeout_init(wdd); if (wdd->id == 0 ) { old_wd_data = wd_data; watchdog_miscdev.parent = wdd->parent; 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; } } cdev_init(&wd_data->cdev, &watchdog_fops); wd_data->cdev.owner = wdd->ops->owner; 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; } wd_data->last_hw_keepalive = ktime_sub(ktime_get(), 1 ); watchdog_set_open_deadline(wd_data); 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 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)) { watchdog_stop(wdd); } watchdog_hrtimer_pretimeout_stop(wdd); mutex_lock(&wd_data->lock); wd_data->wdd = NULL ; wdd->wd_data = NULL ; mutex_unlock(&wd_data->lock); hrtimer_cancel(&wd_data->timer); kthread_cancel_work_sync(&wd_data->work); 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 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; } 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 static inline bool watchdog_need_worker (struct watchdog_device *wdd) { 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 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; if (watchdog_active(wdd)) virt_timeout = ktime_add(wd_data->last_keepalive, ms_to_ktime(timeout_ms)); else virt_timeout = wd_data->open_deadline; hw_heartbeat_ms = min_not_zero(timeout_ms, wdd->max_hw_heartbeat_ms); keepalive_interval = ms_to_ktime(hw_heartbeat_ms / 2 ); last_heartbeat = ktime_sub(virt_timeout, ms_to_ktime(hw_heartbeat_ms)); 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 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 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 = ktime_add(wd_data->last_hw_keepalive, ms_to_ktime(wdd->min_hw_heartbeat_ms)); now = ktime_get(); if (ktime_after(earliest_keepalive, now)) { 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 { 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 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 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); } 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; if (!wdd->pretimeout) wdd->pretimeout = 3 * wdd->timeout / 4 ; tout = clamp_t (unsigned int , wdd->timeout, wdd->min_timeout, wdd->max_hw_heartbeat_ms / 1000 ); ptot = clamp_t (unsigned int , tout - wdd->pretimeout, wdd->min_timeout, tout); presc = DIV_ROUND_UP(tout * wdt->rate, RLR_MAX + 1 ); presc = roundup_pow_of_two(presc); iwdg_pr = presc <= 1 << PR_SHIFT ? 0 : ilog2(presc) - PR_SHIFT; iwdg_rlr = ((tout * wdt->rate) / presc) - 1 ; iwdg_ewcr = ((ptot * wdt->rate) / presc) - 1 ; reg_write(wdt->regs, IWDG_KR, KR_KEY_EWA); 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); reg_write(wdt->regs, IWDG_KR, KR_KEY_ENABLE); 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; } 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; 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 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); 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); 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 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); if (ret) return ret; } ret = devm_request_irq(dev, irq, stm32_iwdg_isr, 0 , dev_name(dev), wdd); if (ret) return ret; wdd->info = &stm32_iwdg_preinfo; 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 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 ); if (IS_ERR(wdt->regs)) return PTR_ERR(wdt->regs); ret = stm32_iwdg_clk_init(pdev, wdt); if (ret) return ret; wdd = &wdt->wdd; wdd->parent = dev; wdd->info = &stm32_iwdg_info; wdd->ops = &stm32_iwdg_ops; wdd->timeout = DEFAULT_TIMEOUT; wdd->min_timeout = DIV_ROUND_UP((RLR_MIN + 1 ) * PR_MIN, wdt->rate); wdd->max_hw_heartbeat_ms = ((RLR_MAX + 1 ) * wdt->data->max_prescaler * 1000 ) / wdt->rate; ret = stm32_iwdg_irq_init(pdev, wdt); if (ret) return ret; watchdog_set_drvdata(wdd, wdt); watchdog_set_nowayout(wdd, WATCHDOG_NOWAYOUT); watchdog_init_timeout(wdd, 0 , dev); 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); 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 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 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 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 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 }, { } }; MODULE_DEVICE_TABLE(of, stm32_iwdg_of_match);
stm32_iwdg_driver:平台驱动注册入口(probe 绑定、DT 匹配表挂载) 1 2 3 4 5 6 7 8 9 10 11 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);