Linux WDT 软件分析
linux WDT(看门狗)
看门狗子系统
- 为什么要实现看门狗子系统?
因为要实现linux底层对WDT 不同硬件的统一控制
在driver部分中最终回去调用watchdog_dev
wdt 代码解析
- 结构体使用场景和Attention
structxxxwdd_data;// 先定义数据本体wdd=&wdd_data;// 把wdd_data的地址赋值给指针wdd// 场景1:函数内临时使用小结构体 → 用变量voidset_short_config(){structxxxwdd;// 栈上变量,用完就销毁wdd.id=1;wdd.timeout=1000;// 临时使用,无需传递 → 直接用变量更简单}// 场景2:函数间传递配置 → 用指针(避免拷贝)voidset_hardware_config(structxxx*wdd){if(wdd==NULL){// 必加空指针检查!防止踩地址return;}wdd->id=2;wdd->timeout=2000;}// 场景3:动态分配(内核中)→ 用指针structxxx*create_hardware_instance(){// 堆内存分配(内核用kmalloc,用户态用malloc)structxxx*wdd=kmalloc(sizeof(structxxx),GFP_KERNEL);if(wdd==NULL){// 检查分配是否成功returnNULL;}// 初始化指针指向的结构体wdd->id=3;wdd->timeout=3000;returnwdd;// 返回指针,数据不会随函数结束销毁}//attention1.野指针解引用structxxx*wdd;wdd->id=1;// 错误!指针没指向任何有效内存,直接踩地址崩溃//修复:先赋值地址,再加空指针检查//attention2.返回栈变量的指针structxxx*get_wdd(){structxxxwdd;// 栈变量,函数结束就销毁return&wdd;// 返回野指针,后续访问必出问题}//修复:用 kmalloc/malloc 分配堆内存,或让调用方传入指针//attention3.混淆 . 和 ->structxxxwdd;wdd->id=1;// 错误!变量用.,指针用->structxxx*wdd_ptr=&wdd;wdd_ptr.id=1;// 错误!指针用->- 在设备树 .dtsi 文件中定义硬件的地址
其中 <0x0 … 对应的地址信息>
- 进行匹配probe函数
其中的dw_wdt_of_match结构体中的 "snp,dw-wdt"和.dtsi下的匹配
//define the watchdog infostructwatchdog_info{...}//函数操作集合 原厂工程师实现structwatchdog_ops{...}//看门狗管理者 重启系统的函数structwatchdog_governor{...//重启系统之前调用void[*pretimeout]{strcut watchdog_device*wdd}}//实现的对 wdd的watchdog_device封装 作为硬件的启动驱动structwd_wdt{...strcut watchdog_device wdd}mem=platform_get_resource(pdev,IORESOURCE_MEM,0);//获取 ./dtsi 中的设备地址信息其中的看门狗信息可由 watchdog_info结构体进行获取赋值 ,代码如下:
- 获取看门狗信息
wdd->info=&dw_wdt_ident;//设置最小时间wdd->min_timeout=1//设置最大时间wdd->max_hw_heartbeat_ms=dw_wdt_top_in_seconds(dw_wdt,DW_WHT_MAX_TOP)*1000;wdd->parent=dev设置看门狗参数
- 看门狗不停止
//需要设置禁止停止时候WDOG_NO_WAY_OUT该参数的设置需要在linux mkconfig中打开config watchdog nowayoff 选项
- 看门狗优先级
//设置优先级watchdog_set_restart_priority()在设置不同 优先级0 ,128, 256 后系统内核会决定用哪个函数来进行重启。
//设置下列函数后watchdog_set_drvdate(wdd,dw_wdt);//可以用下列函数获取结构体watchdog_get_drvdate(wdd,dw_wdt);看门狗核心代码
- 看门狗初始化代码
staticint__initwatchdog_init(void){init err;err=watchdog_dev_init();if(err<0)return0;watchdog_deferred_registration();return0;}- id 号重分配机制
staticint__initwatchdog_init(void){init err;err=watchdog_dev_init();if(err<0)return0;watchdog_deferred_registration();return0;}- 在获取到wdd id 后可以进行调用ida_simple_get(&watchdog_ida, 0, MAX_DOGS, GFP_KERNEL);
- 初始值为 id为-1 在 id<1 时会进行有一次的id分配
代码如下所示
调用的注册函数后注册判断
获取成功后,进行wdd->id = id 的赋值,再进行注册wdd
如果失败 再获取一次 再注册
看门狗停止
- 判断系统重启时候的看门狗状态
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 not register reboot notifier(%d)\n",wdd->id,ret);watchdog_dev_unregister(wdd);ida_simple_remove(&watchdog_ida,id);returnret}}}if(wdd->ops->start){wdd->restart_nb->restart_handler(&wdd->restart_nb);ret=register_restart_handler(&wdd->restart_nb);if(ret)pr_warn("watchdog%d: Cannot register restart handler (%d)\n",wdd->id,ret);}以上代码是进行对系统重启时候的stop函数存在判断
如果有则注册回调函数 watchdog_reboot_notifier 在启动时进行调用回调函数
如果有(wdd-> ops->start) 则注册,在重置系统时候进行回调函数register_restart_handler
初始化看门狗
- 进行看门狗设dev_init
采用kthread_create_worker(0, ""watchdogd); 创建一个看门狗内核线程工作线程
/** * watchdog_dev_init - init dev part of watchdog core * * Allocate a range of chardev nodes to use for watchdog devices. * * Return: 0 if successful, error otherwise. */int__initwatchdog_dev_init(void){interr;watchdog_kworker=kthread_run_worker(0,"watchdogd");if(IS_ERR(watchdog_kworker)){pr_err("Failed to create watchdog kworker\n");returnPTR_ERR(watchdog_kworker);}sched_set_fifo(watchdog_kworker->task);err=class_register(&watchdog_class);if(err<0){pr_err("couldn't register class\n");gotoerr_register;}err=alloc_chrdev_region(&watchdog_devt,0,MAX_DOGS,"watchdog");if(err<0){pr_err("watchdog: unable to allocate char dev region\n");gotoerr_alloc;}return0;err_alloc:class_unregister(&watchdog_class);err_register:kthread_destroy_worker(watchdog_kworker);returnerr;}在watchdog_ping_work 函数中进行喂狗
杂项设备和字符设备
>
杂项设备的注册流程
- 定义struct miscdevice结构体,指定设备名(如 “watchdog”)、次设备号、file_operations(操作函数集);
- 设置父设备等关联信息(watchdog_miscdev.parent = wdd->parent);
- 调用misc_register(&watchdog_miscdev)完成注册,内核自动创建/dev/watchdog设备文件
字符设备的注册流程(传统方式) 要实现一个字符设备驱动,需要手动完成:
申请主设备号(静态指定或动态申请); 定义struct file_operations结构体(实现 open/read/write/ioctl 等操作函数); 调用cdev_init()初始化字符设备结构体; 调用cdev_add()将设备添加到内核; 创建/dev下的设备文件(手动 mknod 或自动创建设备节点)。
存在多个看门狗
存在普通看门狗 (Non-secure WDT (WDT_NS))由linux内核控制
而 安全看门狗 (SecureWDT (WDT_S) )由安全模块控制的
- old 设备看门狗和新的兼通问题
if(wdd->id==0){old_wd_data=wd_data;watchdog_miscdev.parent=wdd->parent;err=misc_register(&watchdog_miscdev);misc_register 注册看门狗 misc 设备到内核
- misc_register():内核提供的 misc 设备注册函数,成功返回 0,失败返回负的错误码(如 - EBUSY 表示设备已被注册)。
- 注册成功后:
内核会在/dev/目录下创建看门狗设备文件(通常是/dev/watchdog);
用户空间程序可通过 open/read/write/ioctl 等系统调用操作看门狗(比如喂狗、设置超时时间);
该设备会关联到看门狗驱动的操作函数集(如watchdog_miscdev.fops指向的 file_operations)。
- watchdog_register_pretimeout 中会确定看门狗是否支持超时预处理的功能,若没有则返回0
