目录
0 简介
1. 设备的注册
1.1 dw_wdt_drv_probe
1.2 watchdog_register_device
1.3 __watchdog_register_device
1.4 最终misc_register注册watchdog_miscdev
2. watchdog_miscdev设备分析
2.1 watchdog_open
2.2 watchdog_write
2.3 watchdog_release
2.4 watchdog_stop
2.5 watchdog_set_timeout
2.6 watchdog_ping
2.7 watchdog_ioctl
3. 使用标准的内核框架wdt driver时,需要特别注意以下两点
3.1 NOWAYOUT(无路可逃)的使用
3.2 magic close特性
4. 应用层代码(app demo)
0 简介
版本:Linux 4.14
用到的文件:
kernelwatchdog.c
driverswatchdogdw_wdt.c
driverswatchdogwatchdog_dev.c
driverswatchdogwatchdog_core.c
wdt的驱动挺特别的,linux内核中也对它做了一个封装并归纳处理总结出了一个框架,分为以下三层:统一driver层(watchdog_dev),核心层(watchdog_core),具体的设备层(本文以dw wdt为例)。
在写wdt的时候会发现,和其他driver不同的是,不需要我们在自己的driver中去创建节点,我们只需要实现ops结构体成员即可,然后去调用wdt核心层的api注册ops即可。
其实,创建节点的工作在wdt的统一driver层已经实现了,这是因wdt设备在各个soc上是一个高度统一的设备,可以被高度抽象出来,查看代码watchdog_dev.c中就可以看到,这是一个标准的字符设备驱动,该文件中创建”dev/watchdog”节点,同时向上层提供了ioctl接口。
driver中的各个接口调用都是以函数指针的方式去调用,而这些函数指针在我们每个soc自己的巨头的wdt driver层去初始化然后注册。
1. 设备的注册
1.1 dw_wdt_drv_probe
driverswatchdogdw_wdt.c
#ifdef CONFIG_OF
static const struct of_device_id dw_wdt_of_match[] = {
{ .compatible = "snps,dw-wdt", },
{ }
};
MODULE_DEVICE_TABLE(of, dw_wdt_of_match);
#endif
static struct platform_driver dw_wdt_driver = {
.probe = dw_wdt_drv_probe,
.remove = dw_wdt_drv_remove,
.driver = {
.name = "dw_wdt",
.of_match_table = of_match_ptr(dw_wdt_of_match),
.pm = &dw_wdt_pm_ops,
},
};
设备树中比较compatible = "snps,dw-wdt",比较通过后调用dw_wdt_drv_probe函数。
static int dw_wdt_drv_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct watchdog_device *wdd;
struct dw_wdt *dw_wdt;
struct resource *mem;
int ret;
dw_wdt = devm_kzalloc(dev, sizeof(*dw_wdt), GFP_KERNEL);
if (!dw_wdt)
return -ENOMEM;
wdd->info = &dw_wdt_ident;
wdd->ops = &dw_wdt_ops; //设置操作函数
........
watchdog_set_nowayout(wdd, nowayout);
........
platform_set_drvdata(pdev, dw_wdt);
watchdog_set_restart_priority(wdd, 128);
ret = watchdog_register_device(wdd);
if (ret)
goto out_disable_clk;
return 0;
out_disable_clk:
clk_disable_unprepare(dw_wdt->clk);
return ret;
}
static const struct watchdog_ops dw_wdt_ops = {
.owner = THIS_MODULE,
.start = dw_wdt_start,
.stop = dw_wdt_stop,
.ping = dw_wdt_ping,
.set_timeout = dw_wdt_set_timeout,
.get_timeleft = dw_wdt_get_timeleft,
.restart = dw_wdt_restart,
};
前面一些关于硬件、IO等的设置,这里不做介绍,注意wdt的fops就行。我们主要讲watchdog_register_device函数。
1.2 watchdog_register_device
该函数driverswatchdogwatchdog_core.c在这里面,如下:
int watchdog_register_device(struct watchdog_device *wdd)
{
int ret;
mutex_lock(&wtd_deferred_reg_mutex);
if (wtd_deferred_reg_done)
ret = __watchdog_register_device(wdd);
else
ret = watchdog_deferred_registration_add(wdd);
mutex_unlock(&wtd_deferred_reg_mutex);
return ret;
}
wtd_deferred_reg_done全局变量已经定义了,所以走__watchdog_register_device分支。
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;
}
subsys_initcall_sync(watchdog_init);
1.3 __watchdog_register_device
static int __watchdog_register_device(struct watchdog_device *wdd)
{
int ret, id = -1;
.......
ret = watchdog_dev_register(wdd);
....
return 0;
}
其他不管,发现调用watchdog_dev_register。
1.4 最终misc_register注册watchdog_miscdev
static int watchdog_cdev_register(struct watchdog_device *wdd)
{
struct watchdog_core_data *wd_data;
int err;
......
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;
kfree(wd_data);
return err;
}
}
.......
return 0;
}
发现最终注册了一个混杂设备watchdog_miscdev,其定义如下。
static const struct file_operations watchdog_fops = {
.owner = THIS_MODULE,
.write = watchdog_write,
.unlocked_ioctl = watchdog_ioctl,
.open = watchdog_open,
.release = watchdog_release,
};
static struct miscdevice watchdog_miscdev = {
.minor = WATCHDOG_MINOR,
.name = "watchdog",
.fops = &watchdog_fops,
};
2. watchdog_miscdev设备分析
2.1 watchdog_open
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;
......
err = watchdog_start(wdd);
if (err < 0)
goto out_mod;
......
}
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;
......
err = watchdog_start(wdd);
if (err < 0)
goto out_mod;
......
}
当打开open("/dev/watchdog")时,最终调用watchdog_open,可以看出最终调用了watchdog_start。
static int watchdog_start(struct watchdog_device *wdd)
{
struct watchdog_core_data *wd_data = wdd->wd_data;
unsigned long started_at;
int err;
.....
set_bit(_WDOG_KEEPALIVE, &wd_data->status);
started_at = jiffies;
if (watchdog_hw_running(wdd) && wdd->ops->ping)
err = wdd->ops->ping(wdd);
else
err = wdd->ops->start(wdd);
.....
return err;
}
查看watchdog_start源码可知,最终调用了wdt fops中的start函数。
2.2 watchdog_write
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;
}
这里的写有一个特殊操作,如果向wdt节点写“V”字符时,则会置位标志为_WDOG_ALLOW_RELEASE,即允许release,当上层去close节点的时候会callback到这一层的release接口,release会去判断_WDOG_ALLOW_RELEASE,如果不被置位则不执行watchdog_stop,即虽然上层关掉了fd,但是底层实际没有执行stop的操作。如果被置位,就执行watchdog_stop,按照上面的分析,watchdog_stop又会判断NOWAYOUT是否被置位。即想要关闭wdt,首先需要disable _WDOG_ALLOW_RELEASE,即去掉该选项。即在close节点前向节点写“V”,然后再clsoe,这样才可以正真调用到具体driver的stop接口
2.3 watchdog_release
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;
......
if (!test_bit(WDOG_ACTIVE, &wdd->status))
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);
......
return 0;
}
release 接口,上层做close的时候会调用到。release会去判断_WDOG_ALLOW_RELEASE,而这个标志在write函数中会被置位,(向节点写“V”操作)。如果不被置位则不执行watchdog_stop即虽然上层关掉了fd,但是底层实际没有执行stop的操作。如果被置位,就执行watchdog_stop,按照上面的分析,watchdog_stop又会判断NOWAYOUT是否被置位。即想要关闭wdt,首先需要disable _WDOG_ALLOW_RELEASE,即去掉该选项。然后在close节点前向节点写“V”,然后再clsoe,这样才可以正真调用到具体driver的stop接口。
2.4 watchdog_stop
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);
} else {
set_bit(WDOG_HW_RUNNING, &wdd->status);
}
if (err == 0) {
clear_bit(WDOG_ACTIVE, &wdd->status);
watchdog_update_worker(wdd);
}
return err;
}
WDOG_NO_WAY_OUT,这里有个特殊的一点是上层调用进这个接口想关闭wdt即终止计数功能时,这里会判断status是否被设置为WDOG_NO_WAY_OUT状态,如果设置了,则直接返回不去调用实际driver中的stop函数。也就是说当NOWAYOUT被配置后,无论上层是close wdt节点还是调用统一层stop接口,wdt都是不会关掉的,会一直计数下去,如果不持续喂狗就会reset。
static bool nowayout = WATCHDOG_NOWAYOUT;
#define WATCHDOG_NOWAYOUT IS_BUILTIN(CONFIG_WATCHDOG_NOWAYOUT)
static inline void watchdog_set_nowayout(struct watchdog_device *wdd, bool nowayout)
{
if (nowayout)
set_bit(WDOG_NO_WAY_OUT, &wdd->status);
}
由上可以看出如果配置了CONFIG_WATCHDOG_NOWAYOUT项,则watchdog_set_nowayout在dw_wdt_drv_probe就会被执行,WDOG_NO_WAY_OUT状态就会被置起来,当上层通过ioctl调用了wdt同一层的stop接口时,则直接返回。
2.5 watchdog_set_timeout
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);
} else {
wdd->timeout = timeout;
if (wdd->pretimeout >= wdd->timeout)
wdd->pretimeout = 0;
}
watchdog_update_worker(wdd);
return err;
}
设置超时时间,一般正常喂狗会下发一个时间,如果上层想主动重启,只需设置时间为0,当然实际的底层driver需要对时间做判断,当时间为0时,就重启系统。
2.6 watchdog_ping
static int watchdog_ping(struct watchdog_device *wdd)
{
struct watchdog_core_data *wd_data = wdd->wd_data;
if (!watchdog_active(wdd) && !watchdog_hw_running(wdd))
return 0;
set_bit(_WDOG_KEEPALIVE, &wd_data->status);
wd_data->last_keepalive = jiffies;
return __watchdog_ping(wdd);
}
static int __watchdog_ping(struct watchdog_device *wdd)
{
struct watchdog_core_data *wd_data = wdd->wd_data;
unsigned long earliest_keepalive = wd_data->last_hw_keepalive +
msecs_to_jiffies(wdd->min_hw_heartbeat_ms);
int err;
if (time_is_after_jiffies(earliest_keepalive)) {
mod_delayed_work(watchdog_wq, &wd_data->work,
earliest_keepalive - jiffies);
return 0;
}
wd_data->last_hw_keepalive = jiffies;
if (wdd->ops->ping)
err = wdd->ops->ping(wdd);
else
err = wdd->ops->start(wdd);
watchdog_update_worker(wdd);
return err;
}
2.7 watchdog_ioctl
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;
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;
}
3. 使用标准的内核框架wdt driver时,需要特别注意以下两点
3.1 NOWAYOUT(无路可逃)的使用
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;
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;
}
3. 使用标准的内核框架wdt driver时,需要特别注意以下两点
3.1 NOWAYOUT(无路可逃)的使用
有的时候我们希望看门狗不被停止,即上层的任何关狗的动作都不予支持,此时就可以使用NOWAYOUT功能,接配置内核的标准配置项:CONFIG_WATCHDOG_NOWAYOUT
使能该项后,要为我们的driver所用需要在具体的wdt driver中对该配置进行支持,即调用watchdog_set_nowayout()去设置WDOG_NO_WAY_OUT:
static bool nowayout = WATCHDOG_NOWAYOUT;
#define WATCHDOG_NOWAYOUT IS_BUILTIN(CONFIG_WATCHDOG_NOWAYOUT)
static inline void watchdog_set_nowayout(struct watchdog_device *wdd, bool nowayout)
{
if (nowayout)
set_bit(WDOG_NO_WAY_OUT, &wdd->status);
}
配置后会去执行set_bit(WDOG_NO_WAY_OUT, &wdd->status);置位WDOG_NO_WAY_OUT。在上层调用统一driver层IOCTL去调用watchdog_stop()时,watchdog_stop会去判断是否WDOG_NO_WAY_OUT被置位,如果设置了,则直接返回不去调用实际driver中的stop函数
这样就可以屏蔽所有关wdt的动作,这个操作仍然在统一设备层,具体的wdt driver不用管这个
3.2 magic close特性
有的时候我们想停掉狗,不想然他reset,如果系统使用了标准的wdt框架,则需要magic close的支持。
magic close即向wdt节点写字符“V”向wdt节点写“V”字符时,被调用到的接口watchdog_write()会置位标志为_WDOG_ALLOW_RELEASE。当上层close节点时,会调用标准的wdt统一driver层的release接口,release会去判断_WDOG_ALLOW_RELEASE,如果不被置位则不执行watchdog_stop即虽然上层关掉了fd,但是底层实际没有执行stop的操作。如果被置位,就执行watchdog_stop,按照上面的分析,watchdog_stop又会判断NOWAYOUT是否被置位。
所以想要关闭wdt rest功能结束其计数,首先需要disable _WDOG_ALLOW_RELEASE,即去掉该选项。然后在close节点前向节点写“V”,然后再clsoe,这样才可以正真调用到具体driver的stop接口。



