看门狗,是一个确保嵌入式平台稳定性的必要措施,虽然正常操作不应触发看门狗,但在一些特殊场景,比如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 */
我要赚赏金
