目录
1.4 最终misc_register注册watchdog_miscdev
2.6 watchdog_ping/分布式(有兴趣自主了解)
3. 使用标准的内核框架wdt driver时,需要特别注意以下两点
0 简介
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 sunxi_wdt_probe
sunxi_wdt.c文件
static const struct of_device_id sunxi_wdt_dt_ids[] = {
{ .compatible = "allwinner,sun4i-wdt", .data = &sun4i_wdt_reg },
{ .compatible = "allwinner,sun6i-wdt", .data = &sun6i_wdt_reg },
{ /* sentinel */ }
};
static struct platform_driver sunxi_wdt_driver = {
.probe = sunxi_wdt_probe,
.remove = sunxi_wdt_remove,
.shutdown = sunxi_wdt_shutdown,
.driver = {
.name = DRV_NAME,
.of_match_table = sunxi_wdt_dt_ids,
},
};
设备树中比较compatible,比较通过后调用sunxi_wdt_probe函数。
static int sunxi_wdt_probe(struct platform_device *pdev)
{
struct sunxi_wdt_dev *sunxi_wdt;
const struct of_device_id *device;
struct resource *res;
int err;
sunxi_wdt = devm_kzalloc(&pdev->dev, sizeof(*sunxi_wdt), GFP_KERNEL);
if (!sunxi_wdt)
return -EINVAL;
platform_set_drvdata(pdev, sunxi_wdt);
device = of_match_device(sunxi_wdt_dt_ids, &pdev->dev);
if (!device)
return -ENODEV;
sunxi_wdt->wdt_regs = device->data;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
sunxi_wdt->wdt_base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(sunxi_wdt->wdt_base))
return PTR_ERR(sunxi_wdt->wdt_base);
sunxi_wdt->wdt_dev.info = &sunxi_wdt_info;
sunxi_wdt->wdt_dev.ops = &sunxi_wdt_ops; //设置操作函数
sunxi_wdt->wdt_dev.timeout = WDT_MAX_TIMEOUT;
sunxi_wdt->wdt_dev.max_timeout = WDT_MAX_TIMEOUT;
sunxi_wdt->wdt_dev.min_timeout = WDT_MIN_TIMEOUT;
sunxi_wdt->wdt_dev.parent = &pdev->dev;
watchdog_init_timeout(&sunxi_wdt->wdt_dev, timeout, &pdev->dev);
watchdog_set_nowayout(&sunxi_wdt->wdt_dev, nowayout);
watchdog_set_drvdata(&sunxi_wdt->wdt_dev, sunxi_wdt);
/* sunxi_wdt_stop(&sunxi_wdt->wdt_dev); */
err = readl(sunxi_wdt->wdt_base + sunxi_wdt->wdt_regs->wdt_mode);
if (err == 0x5b) {
sunxi_wdt_ping(&sunxi_wdt->wdt_dev);
}
err = watchdog_register_device(&sunxi_wdt->wdt_dev);
if (unlikely(err))
return err;
reboot_wdt_reg = sunxi_wdt->wdt_regs;
reboot_wdt_base = sunxi_wdt->wdt_base;
arm_pm_restart = sunxi_wdt_restart;
dev_info(&pdev->dev, "Watchdog enabled (timeout=%d sec, nowayout=%d)",
sunxi_wdt->wdt_dev.timeout, nowayout);
return 0;
}
static const struct watchdog_ops sunxi_wdt_ops = {
.owner = THIS_MODULE,
.start = sunxi_wdt_start,
.stop = sunxi_wdt_stop,
.ping = sunxi_wdt_ping,
.set_timeout = sunxi_wdt_set_timeout,
};
前面一些关于硬件、IO等的设置,这里不做介绍,注意wdt的fops就行。我们主要讲watchdog_register_device函数。
1.2 watchdog_register_device
该函数watchdog_core.c在这里面,如下:
int watchdog_register_device(struct watchdog_device *wdd)
{
int ret, id, devno;
if (wdd == NULL || wdd->info == NULL || wdd->ops == NULL)
return -EINVAL;
/* Mandatory operations need to be supported */
if (wdd->ops->start == NULL || wdd->ops->stop == NULL)
return -EINVAL;
watchdog_check_min_max_timeout(wdd);
/*
* Note: now that all watchdog_device data has been verified, we
* will not check this anymore in other functions. If data gets
* corrupted in a later stage then we expect a kernel panic!
*/
mutex_init(&wdd->lock);
id = ida_simple_get(&watchdog_ida, 0, MAX_DOGS, GFP_KERNEL);
if (id < 0)
return id;
wdd->id = id;
ret = watchdog_dev_register(wdd);
if (ret) {
ida_simple_remove(&watchdog_ida, id);
if (!(id == 0 && ret == -EBUSY))
return ret;
/* Retry in case a legacy watchdog module exists */
id = ida_simple_get(&watchdog_ida, 1, MAX_DOGS, GFP_KERNEL);
if (id < 0)
return id;
wdd->id = id;
ret = watchdog_dev_register(wdd);
if (ret) {
ida_simple_remove(&watchdog_ida, id);
return ret;
}
}
devno = wdd->cdev.dev;
wdd->dev = device_create(watchdog_class, wdd->parent, devno,
NULL, "watchdog%d", wdd->id);
if (IS_ERR(wdd->dev)) {
watchdog_dev_unregister(wdd);
ida_simple_remove(&watchdog_ida, id);
ret = PTR_ERR(wdd->dev);
return ret;
}
return 0;
}
EXPORT_SYMBOL_GPL(watchdog_register_device);
发现调用watchdog_dev_register。 其他都是些初始化配置。
1.4 最终misc_register注册watchdog_miscdev
int watchdog_dev_register(struct watchdog_device *watchdog)
{
int err, devno;
if (watchdog->id == 0) {
old_wdd = watchdog;
watchdog_miscdev.parent = watchdog->parent;
err = misc_register(&watchdog_miscdev);
if (err != 0) {
pr_err("%s: cannot register miscdev on minor=%d (err=%d).\n",
watchdog->info->identity, WATCHDOG_MINOR, err);
if (err == -EBUSY)
pr_err("%s: a legacy watchdog module is probably present.\n",
watchdog->info->identity);
old_wdd = NULL;
return err;
}
}
/* Fill in the data structures */
devno = MKDEV(MAJOR(watchdog_devt), watchdog->id);
cdev_init(&watchdog->cdev, &watchdog_fops);
watchdog->cdev.owner = watchdog->ops->owner;
/* Add the device */
err = cdev_add(&watchdog->cdev, devno, 1);
if (err) {
pr_err("watchdog%d unable to add device %d:%d\n",
watchdog->id, MAJOR(watchdog_devt), watchdog->id);
if (watchdog->id == 0) {
misc_deregister(&watchdog_miscdev);
old_wdd = NULL;
}
}
return err;
}
可通过watchdog_miscdev找到
发现最终注册了一个混杂设备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)
{
int err = -EBUSY;
struct watchdog_device *wdd;
/* Get the corresponding watchdog device */
if (imajor(inode) == MISC_MAJOR)
wdd = old_wdd;
else
wdd = container_of(inode->i_cdev, struct watchdog_device, cdev);
/* the watchdog is single open! */
if (test_and_set_bit(WDOG_DEV_OPEN, &wdd->status))
return -EBUSY;
/*
* If the /dev/watchdog device is open, we don't want the module
* to be unloaded.
*/
if (!try_module_get(wdd->ops->owner))
goto out;
err = watchdog_start(wdd);
if (err < 0)
goto out_mod;
file->private_data = wdd;
if (wdd->ops->ref)
wdd->ops->ref(wdd);
/* dev/watchdog is a virtual (and thus non-seekable) filesystem */
return nonseekable_open(inode, file);
out_mod:
module_put(wdd->ops->owner);
out:
clear_bit(WDOG_DEV_OPEN, &wdd->status);
return err;
}
当打开open("/dev/watchdog")时,最终调用watchdog_open,可以看出最终调用了watchdog_start。
static int watchdog_start(struct watchdog_device *wddev)
{
int err = 0;
mutex_lock(&wddev->lock);
if (test_bit(WDOG_UNREGISTERED, &wddev->status)) {
err = -ENODEV;
goto out_start;
}
if (watchdog_active(wddev))
goto out_start;
err = wddev->ops->start(wddev);
if (err == 0)
set_bit(WDOG_ACTIVE, &wddev->status);
out_start:
mutex_unlock(&wddev->lock);
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_device *wdd = file->private_data;
size_t i;
char c;
if (len == 0)
return 0;
/*
* Note: just in case someone wrote the magic character
* five months ago...
*/
clear_bit(WDOG_ALLOW_RELEASE, &wdd->status);
/* scan to see whether or not we got the magic character */
for (i = 0; i != len; i++) {
if (get_user(c, data + i))
return -EFAULT;
if (c == 'V')
set_bit(WDOG_ALLOW_RELEASE, &wdd->status);
}
/* someone wrote to us, so we send the watchdog a keepalive ping */
watchdog_ping(wdd);
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_device *wdd = file->private_data;
int err = -EBUSY;
/*
* We only stop the watchdog if we received the magic character
* or if WDIOF_MAGICCLOSE is not set. If nowayout was set then
* watchdog_stop will fail.
*/
if (test_and_clear_bit(WDOG_ALLOW_RELEASE, &wdd->status) ||
!(wdd->info->options & WDIOF_MAGICCLOSE))
err = watchdog_stop(wdd);
/* If the watchdog was not stopped, send a keepalive ping */
if (err < 0) {
mutex_lock(&wdd->lock);
if (!test_bit(WDOG_UNREGISTERED, &wdd->status))
dev_crit(wdd->dev, "watchdog did not stop!\n");
mutex_unlock(&wdd->lock);
watchdog_ping(wdd);
}
/* Allow the owner module to be unloaded again */
module_put(wdd->ops->owner);
/* make sure that /dev/watchdog can be re-opened */
clear_bit(WDOG_DEV_OPEN, &wdd->status);
/* Note wdd may be gone after this, do not use after this! */
if (wdd->ops->unref)
wdd->ops->unref(wdd);
return 0;
}
从代码上看,sunxi的并没有在release里和上面一样用“V”操作。不过被禁止则同样不能被关掉。
2.4 watchdog_stop
static int watchdog_stop(struct watchdog_device *wddev)
{
int err = 0;
mutex_lock(&wddev->lock);
if (test_bit(WDOG_UNREGISTERED, &wddev->status)) {
err = -ENODEV;
goto out_stop;
}
if (!watchdog_active(wddev))
goto out_stop;
if (test_bit(WDOG_NO_WAY_OUT, &wddev->status)) {
dev_info(wddev->dev, "nowayout prevents watchdog being stopped!\n");
err = -EBUSY;
goto out_stop;
}
err = wddev->ops->stop(wddev);
if (err == 0)
clear_bit(WDOG_ACTIVE, &wddev->status);
out_stop:
mutex_unlock(&wddev->lock);
return err;
}
WDOG_NO_WAY_OUT,这里有个特殊的一点是上层调用进这个接口想关闭wdt即终止计数功能时,这里会判断status是否被设置为WDOG_NO_WAY_OUT状态,如果设置了,则直接返回不去调用实际driver中的stop函数。也就是说当NOWAYOUT被配置后,无论上层是close wdt节点还是调用统一层stop接口,wdt都是不会关掉的,会一直计数下去,如果不持续喂狗就会reset。
2.5 watchdog_set_timeout
static int watchdog_set_timeout(struct watchdog_device *wddev,
unsigned int timeout)
{
int err;
if ((wddev->ops->set_timeout == NULL) ||
!(wddev->info->options & WDIOF_SETTIMEOUT))
return -EOPNOTSUPP;
if (watchdog_timeout_invalid(wddev, timeout))
return -EINVAL;
mutex_lock(&wddev->lock);
if (test_bit(WDOG_UNREGISTERED, &wddev->status)) {
err = -ENODEV;
goto out_timeout;
}
err = wddev->ops->set_timeout(wddev, timeout);
out_timeout:
mutex_unlock(&wddev->lock);
return err;
}
设置超时时间,一般正常喂狗会下发一个时间,如果上层想主动重启,只需设置时间为0,当然实际的底层driver需要对时间做判断,当时间为0时,就重启系统。
2.6 watchdog_ping/分布式(有兴趣自主了解)
static int watchdog_ping(struct watchdog_device *wddev)
{
int err = 0;
mutex_lock(&wddev->lock);
if (test_bit(WDOG_UNREGISTERED, &wddev->status)) {
err = -ENODEV;
goto out_ping;
}
if (!watchdog_active(wddev))
goto out_ping;
if (wddev->ops->ping)
err = wddev->ops->ping(wddev); /* ping the watchdog */
else
err = wddev->ops->start(wddev); /* restart watchdog */
out_ping:
mutex_unlock(&wddev->lock);
return err;
}
2.7 watchdog_ioctl
static long watchdog_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct watchdog_device *wdd = file->private_data;
void __user *argp = (void __user *)arg;
int __user *p = argp;
unsigned int val;
int err;
err = watchdog_ioctl_op(wdd, cmd, arg);
if (err != -ENOIOCTLCMD)
return err;
switch (cmd) {
case WDIOC_GETSUPPORT:
return copy_to_user(argp, wdd->info,
sizeof(struct watchdog_info)) ? -EFAULT : 0;
case WDIOC_GETSTATUS:
err = watchdog_get_status(wdd, &val);
if (err == -ENODEV)
return err;
return put_user(val, p);
case WDIOC_GETBOOTSTATUS:
return put_user(wdd->bootstatus, p);
case WDIOC_SETOPTIONS:
if (get_user(val, p))
return -EFAULT;
if (val & WDIOS_DISABLECARD) {
err = watchdog_stop(wdd);
if (err < 0)
return err;
}
if (val & WDIOS_ENABLECARD) {
err = watchdog_start(wdd);
if (err < 0)
return err;
}
return 0;
case WDIOC_KEEPALIVE:
if (!(wdd->info->options & WDIOF_KEEPALIVEPING))
return -EOPNOTSUPP;
watchdog_ping(wdd);
return 0;
case WDIOC_SETTIMEOUT:
if (get_user(val, p))
return -EFAULT;
err = watchdog_set_timeout(wdd, val);
if (err < 0)
return err;
/* If the watchdog is active then we send a keepalive ping
* to make sure that the watchdog keep's running (and if
* possible that it takes the new timeout) */
watchdog_ping(wdd);
/* Fall */
case WDIOC_GETTIMEOUT:
/* timeout == 0 means that we don't know the timeout */
if (wdd->timeout == 0)
return -EOPNOTSUPP;
return put_user(wdd->timeout, p);
case WDIOC_GETTIMELEFT:
err = watchdog_get_timeleft(wdd, &val);
if (err)
return err;
return put_user(val, p);
default:
return -ENOTTY;
}
}
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)
/* Use the following function to set the nowayout feature */
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接口。
4. 应用层代码(app demo)
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <pthread.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/time.h>
#include <unistd.h>
#include <time.h>
#include <getopt.h>
#include <sys/signal.h>
//watchdog
#define WATCHDOG_IOCTL_BASE 'W'
struct watchdog_info {
unsigned int options; /* Options the card/driver supports */
unsigned int firmware_version; /* Firmware version of the card */
char identity[32]; /* Identity of the board */
};
#define WDIOC_GETSUPPORT _IOR(WATCHDOG_IOCTL_BASE, 0, struct watchdog_info)
#define WDIOC_GETSTATUS _IOR(WATCHDOG_IOCTL_BASE, 1, int)
#define WDIOC_GETBOOTSTATUS _IOR(WATCHDOG_IOCTL_BASE, 2, int)
#define WDIOC_GETTEMP _IOR(WATCHDOG_IOCTL_BASE, 3, int)
#define WDIOC_SETOPTIONS _IOR(WATCHDOG_IOCTL_BASE, 4, int)
#define WDIOC_KEEPALIVE _IOR(WATCHDOG_IOCTL_BASE, 5, int)
#define WDIOC_SETTIMEOUT _IOWR(WATCHDOG_IOCTL_BASE, 6, int)
#define WDIOC_GETTIMEOUT _IOR(WATCHDOG_IOCTL_BASE, 7, int)
#define WDIOC_SETPRETIMEOUT _IOWR(WATCHDOG_IOCTL_BASE, 8, int)
#define WDIOC_GETPRETIMEOUT _IOR(WATCHDOG_IOCTL_BASE, 9, int)
#define WDIOC_GETTIMELEFT _IOR(WATCHDOG_IOCTL_BASE, 10, int)
#define WDIOF_OVERHEAT 0x0001 /* Reset due to CPU overheat */
#define WDIOF_FANFAULT 0x0002 /* Fan failed */
#define WDIOF_EXTERN1 0x0004 /* External relay 1 */
#define WDIOF_EXTERN2 0x0008 /* External relay 2 */
#define WDIOF_POWERUNDER 0x0010 /* Power bad/power fault */
#define WDIOF_CARDRESET 0x0020 /* Card previously reset the CPU */
#define WDIOF_POWEROVER 0x0040 /* Power over voltage */
#define WDIOF_SETTIMEOUT 0x0080 /* Set timeout (in seconds) */
#define WDIOF_MAGICCLOSE 0x0100 /* Supports magic close char */
#define WDIOF_PRETIMEOUT 0x0200 /* Pretimeout (in seconds), get/set */
#define WDIOF_KEEPALIVEPING 0x8000 /* Keep alive ping reply */
#define WDIOS_DISABLECARD 0x0001 /* Turn off the watchdog timer */
#define WDIOS_ENABLECARD 0x0002 /* Turn on the watchdog timer */
#define WDIOS_TEMPPANIC 0x0004 /* Kernel panic on temperature trip */
int wdt_fd;
int time_out = 5;
#define DEFAULT_PING_RATE 1
void stop_signal()
{
int val = 0 , ret = 0 ;
val = WDIOS_DISABLECARD ;
ret = ioctl(wdt_fd, WDIOC_SETOPTIONS, &val) ;
if (ret < 0)
printf("ioctl WDIOC_GETSUPPORT failed with %d.\n", ret);
printf("===watchdow will be closed===\n") ;
close(wdt_fd) ;
exit(0);
}
int main(int argc, char *argv[])
{
int ret;
static int count = 0;
struct watchdog_info wdt_info;
unsigned int ping_rate = DEFAULT_PING_RATE;
signal(SIGINT, stop_signal) ;
wdt_fd = open("/dev/watchdog0", O_RDWR);
if(wdt_fd < 0)
{
printf("open /dev/watchdog0 failed.\n");
}
/* get watchdog infomation struct */
ret = ioctl(wdt_fd, WDIOC_GETSUPPORT, &wdt_info);
if (ret < 0)
printf("ioctl WDIOC_GETSUPPORT failed.\n");
else
{
printf("options = 0x%x,id = %s\n", wdt_info.options, wdt_info.identity);
}
ioctl(wdt_fd, WDIOC_SETTIMEOUT, &time_out);
if (ret < 0)
printf("ioctl WDIOC_SETTIMEOUT failed.\n");
while(1)
{
if(count > 10)
{
printf("unfood watchdog, count = %d \n",count++);
}
else
{
ioctl(wdt_fd,WDIOC_KEEPALIVE,NULL);
printf("food watchdog, count = %d \n",count++);
}
sleep(DEFAULT_PING_RATE);
}
close(wdt_fd);
return 0;
}