看门狗,是一个确保嵌入式平台稳定性的必要措施,虽然正常操作不应触发看门狗,但在一些特殊场景,比如ESD等情况导致的系统跑飞,就需要看门狗做复位了。
源码分析
源码路径
components\drivers\watchdog\watchdog.c
注册入口
#ifdef RT_USING_DEVICE_OPS const static struct rt_device_ops wdt_ops = { rt_watchdog_init, rt_watchdog_open, rt_watchdog_close, RT_NULL, RT_NULL, rt_watchdog_control, }; #endif rt_err_t rt_hw_watchdog_register(struct rt_watchdog_device *wtd, const char *name, rt_uint32_t flag, void *data) { struct rt_device *device; RT_ASSERT(wtd != RT_NULL); device = &(wtd->parent); device->type = RT_Device_Class_WDT; device->rx_indicate = RT_NULL; device->tx_complete = RT_NULL; #ifdef RT_USING_DEVICE_OPS device->ops = &wdt_ops; #else device->init = rt_watchdog_init; device->open = rt_watchdog_open; device->close = rt_watchdog_close; device->read = RT_NULL; device->write = RT_NULL; device->control = rt_watchdog_control; #endif device->user_data = data; return rt_device_register(device, name, flag); }
查看了这么多设备驱动,其实我们会发现,注册接口其实都差不多,基本上就设置设备类型,指定device的操作函数表(不同类型设备实现方法不一),之后注册设备。唯一可能有差异的是部分设备,框架层存在一些操作,需要初始化一些框架层维护的数据。看门狗设备也不例外符合这么个规律。
初始化入口
static rt_err_t rt_watchdog_init(struct rt_device *dev) { rt_watchdog_t *wtd; RT_ASSERT(dev != RT_NULL); wtd = (rt_watchdog_t *)dev; if (wtd->ops->init) { return (wtd->ops->init(wtd)); } return (-RT_ENOSYS); }
从初始化入口看,基本上看门狗框架本身不维护任何信息,所有信息的维护都是交由驱动自己维护了。
打开入口
static rt_err_t rt_watchdog_open(struct rt_device *dev, rt_uint16_t oflag) { return (RT_EOK); }
看起来这个入口并没有实现任何功能,而实际上,这个函数所实现的功能已通过control接口实现,而不知为何open函数未调用control实现的接口。
关闭入口
static rt_err_t rt_watchdog_close(struct rt_device *dev) { rt_watchdog_t *wtd; RT_ASSERT(dev != RT_NULL); wtd = (rt_watchdog_t *)dev; if (wtd->ops->control(wtd, RT_DEVICE_CTRL_WDT_STOP, RT_NULL) != RT_EOK) { rt_kprintf(" This watchdog can not be stoped\n"); return (-RT_ERROR); } return (RT_EOK); }
关闭入口需要驱动实现control中的RT_DEVICE_CTRL_WDT_STOP,具体功能是停止看门狗功能。
控制入口
static rt_err_t rt_watchdog_control(struct rt_device *dev, int cmd, void *args) { rt_watchdog_t *wtd; RT_ASSERT(dev != RT_NULL); wtd = (rt_watchdog_t *)dev; return (wtd->ops->control(wtd, cmd, args)); }
控制入口的实现,算是现在看的这么多驱动之中,实现最为迷惑的部分了,从实现上看,看不出任何对驱动层的实现要求,但实际上,还是有要求的,这些要求需要跳转到看门狗的头文件中查看。
#define RT_DEVICE_CTRL_WDT_GET_TIMEOUT (RT_DEVICE_CTRL_BASE(WDT) + 1) /* 获取秒级的超时时间 */ #define RT_DEVICE_CTRL_WDT_SET_TIMEOUT (RT_DEVICE_CTRL_BASE(WDT) + 2) /* 设置秒级的超时时间 */ #define RT_DEVICE_CTRL_WDT_GET_TIMELEFT (RT_DEVICE_CTRL_BASE(WDT) + 3) /* 获取秒级的剩余时间(至重启) */ #define RT_DEVICE_CTRL_WDT_KEEPALIVE (RT_DEVICE_CTRL_BASE(WDT) + 4) /* 刷新看门狗计时 */ #define RT_DEVICE_CTRL_WDT_START (RT_DEVICE_CTRL_BASE(WDT) + 5) /* 启动看门狗 */ #define RT_DEVICE_CTRL_WDT_STOP (RT_DEVICE_CTRL_BASE(WDT) + 6) /* 关闭看门狗 */
这些变量就是control接口的参数cmd,也就是说,驱动需要实现这些入口。
总结
至此,我们基本上可以梳理出驱动层的看门狗实现框架。即:
#include <board.h> #ifdef RT_USING_WDT //#define DRV_DEBUG #define LOG_TAG "drv.wdt" #include <drv_log.h> struct wdt_obj { rt_watchdog_t watchdog; // TODO: wdt inside param }; static rt_err_t wdt_init(rt_watchdog_t *wdt) { return RT_EOK; } static rt_err_t wdt_control(rt_watchdog_t *wdt, int cmd, void *arg) { switch (cmd) { case RT_DEVICE_CTRL_WDT_KEEPALIVE: // TODO: 执行喂狗动作 break; /* set watchdog timeout */ case RT_DEVICE_CTRL_WDT_SET_TIMEOUT: // TODO:设置看门狗超时时间 break; case RT_DEVICE_CTRL_WDT_GET_TIMEOUT: // TODO:获取设置的超时时间 break; case RT_DEVICE_CTRL_WDT_GET_TIMELEFT: // TODO:获取看门狗的剩余计时 break; case RT_DEVICE_CTRL_WDT_START: // TODO:启动看门狗 break; case RT_DEVICE_CTRL_WDT_STOP: // TODO:关闭看门狗 break; default: LOG_W("This command is not supported."); return -RT_ERROR; } return RT_EOK; } static struct rt_watchdog_ops ops = { .init = wdt_init, .control = wdt_control, }; int rt_wdt_init(void) { struct wdt_obj *wdt; wdt = (struct wdt_obj *)rt_malloc(sizeof(struct wdt_obj)); if(wdt == RT_NULL) { LOG_E("wdt device malloc failed."); return -RT_ERROR; } wdt.watchdog.ops.ops = &ops; if (rt_hw_watchdog_register(&wdt.watchdog, "wdt", RT_DEVICE_FLAG_DEACTIVATE, RT_NULL) != RT_EOK) { LOG_E("wdt device register failed."); return -RT_ERROR; } LOG_D("wdt device register success."); return RT_EOK; } INIT_BOARD_EXPORT(rt_wdt_init); #endif /* RT_USING_WDT */