linux watchdog

1.  定义

定时器看门狗IWDG: 独立于系统之外,因为有独立时钟,所以不受系统影响的系统故障探测器,主要用于监视硬件错误;

窗口看门狗WWDG:系统内部的故障探测器,时钟与系统相同。如果系统时钟不走了,这个狗也就失去了作用了,主要用于监视软件错误。

这里只对定时器看门狗分析,不对窗口看门狗分析!!!

2. 流程图

linux watchdog_第1张图片

3. platform_device

platform设备注册:

static struct resource nuc970_wdt_resource[] = {
        [0] = {
                .start = NUC970_PA_WDT,
                .end   = NUC970_PA_WDT + NUC970_SZ_WDT - 1,
                .flags = IORESOURCE_MEM,
        },
        [1] = {
                .start = IRQ_WDT,
                .end   = IRQ_WDT,
                .flags = IORESOURCE_IRQ,
        }
};

struct platform_device nuc970_device_wdt = {
        .name		  = "nuc970-wdt",
        .id		  = -1,
        .num_resources	  = ARRAY_SIZE(nuc970_wdt_resource),
        .resource	  = nuc970_wdt_resource,
};
platform_device_register(nuc970_device_wdt)

platform设备注册主要是将看门狗设备的寄存器资源注册到platform总线上。

4. platform_driver

4.1 看门狗涉及的结构体

看门狗信息:

struct watchdog_info {
	__u32 options;		/* Options the card/driver supports */
	__u32 firmware_version;	/* Firmware version of the card */
	__u8  identity[32];	/* Identity of the board */
};

对看门狗寄存器操作的句柄:

struct watchdog_ops {
	struct module *owner;
	/* mandatory operations */
	int (*start)(struct watchdog_device *); //启动看门狗
	int (*stop)(struct watchdog_device *); //停止看门狗
	/* optional operations */
	int (*ping)(struct watchdog_device *); //测试看门狗
	unsigned int (*status)(struct watchdog_device *); //看门狗状态
	int (*set_timeout)(struct watchdog_device *, unsigned int); //设置看门狗超时时间
	unsigned int (*get_timeleft)(struct watchdog_device *); //???
	void (*ref)(struct watchdog_device *);
	void (*unref)(struct watchdog_device *);
	long (*ioctl)(struct watchdog_device *, unsigned int, unsigned long); //看门狗操作
};

看门狗设备:

struct watchdog_device {
	int id;
	struct cdev cdev;
	struct device *dev;
	struct device *parent;
	const struct watchdog_info *info; //指向看门狗信息
	const struct watchdog_ops *ops; //指向看门狗操作句柄
	unsigned int bootstatus;
	unsigned int timeout; //看门狗喂狗的超时时间
	unsigned int min_timeout; //最小超时时间
	unsigned int max_timeout; //最大超时事件
	void *driver_data;
	struct mutex lock;
	unsigned long status; //看门狗的执行状态
/* Bit numbers for status flags */
#define WDOG_ACTIVE		0	/* Is the watchdog running/active */
#define WDOG_DEV_OPEN		1	/* Opened via /dev/watchdog ? */
#define WDOG_ALLOW_RELEASE	2	/* Did we receive the magic char ? */
#define WDOG_NO_WAY_OUT		3	/* Is 'nowayout' feature set ? */
#define WDOG_UNREGISTERED	4	/* Has the device been unregistered */
};

上述结构体定义:

static const struct watchdog_info nuc970wdt_info = {
	.identity	= "nuc970 watchdog",
	.options	= WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
};

static struct watchdog_ops nuc970wdt_ops = {
	.owner = THIS_MODULE,
	.start = nuc970wdt_start,
	.stop = nuc970wdt_stop,
	.ping = nuc970wdt_ping,
	.set_timeout = nuc970wdt_set_timeout,
};

static struct watchdog_device nuc970_wdd = {
	.status = WATCHDOG_NOWAYOUT_INIT_STATUS,
	.info = &nuc970wdt_info,
	.ops = &nuc970wdt_ops,
};

其中看门狗启动:

static int nuc970wdt_start(struct watchdog_device *wdd)
{
	unsigned int val = 0;

	val |= (WTRE | WTE | WTR);

	if(wdd->timeout < 2) {
		val |= 0x5 << 8;
	} else if (wdd->timeout < 8) {
		val |= 0x6 << 8;
	} else {
		val |= 0x7 << 8;
	}
	Unlock_RegWriteProtect();
	__raw_writel(val, REG_WDT_CR);
	Lock_RegWriteProtect();
	return 0;
}

看门狗停止:

static int nuc970wdt_stop(struct watchdog_device *wdd)
{
	Unlock_RegWriteProtect();
	__raw_writel(0, REG_WDT_CR);
	Lock_RegWriteProtect();
	return 0;
}

看门狗超时时间设置:

static int nuc970wdt_set_timeout(struct watchdog_device *wdd, unsigned int timeout)
{
	unsigned int val;

	Unlock_RegWriteProtect();
	val = __raw_readl(REG_WDT_CR);
	val &= ~WTIS;
	if(timeout < 2) {
		val |= 0x5 << 8;
	} else if (timeout < 8) {
		val |= 0x6 << 8;
	} else {
		val |= 0x7 << 8;
	}

	__raw_writel(val, REG_WDT_CR);
	Lock_RegWriteProtect();
	return 0;
}

这里多说一句,关于看门狗的超时时间,必须要结合datasheet才能计算超时时间!!!

喂狗:

static int nuc970wdt_ping(struct watchdog_device *wdd)
{
	unsigned int val;
	
	Unlock_RegWriteProtect();
	val = __raw_readl(REG_WDT_CR);
	val |= WTR;
	__raw_writel(val, REG_WDT_CR);
	Lock_RegWriteProtect();
	return 0;
}

4.2 平台驱动的注册

static struct platform_driver nuc970wdt_driver = {
	.probe		= nuc970wdt_probe,
	.remove		= nuc970wdt_remove,
	.shutdown	= nuc970wdt_shutdown,
	.suspend	= nuc970wdt_suspend,
	.resume		= nuc970wdt_resume,
	.driver		= {
		.name	= "nuc970-wdt",
		.owner	= THIS_MODULE,
	},
};
module_platform_driver(nuc970wdt_driver);

这里将看门狗驱动注册到platform总线上,通过驱动名称“nuc970-wdt”与对应的平台设备匹配,成功将调用nuc970wdt_probe探测函数。

4.3 nuc970wdt_probe()

static int nuc970wdt_probe(struct platform_device *pdev)
{
	int ret = 0;
	struct clk *clkmux, *clklxt;

	nuc970_wdt = devm_kzalloc(&pdev->dev, sizeof(struct nuc970_wdt), GFP_KERNEL);
	if (!nuc970_wdt)
		return -ENOMEM;

	nuc970_wdt->pdev = pdev;

	nuc970_wdt->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (nuc970_wdt->res == NULL) {
		dev_err(&pdev->dev, "no memory resource specified\n");
		return -ENOENT;
	}

	if (!devm_request_mem_region(&pdev->dev, nuc970_wdt->res->start,
				resource_size(nuc970_wdt->res), pdev->name)) {
		dev_err(&pdev->dev, "failed to get memory region\n");
		return -ENOENT;
	}

	//因看门狗驱动有4个时钟源可供选择,关于时钟架构分析详见另外一篇博客
	clkmux = clk_get(NULL, "wdt_eclk_mux"); 
	if (IS_ERR(clkmux)) {
		dev_err(&pdev->dev, "failed to get watchdog clock mux\n");
		ret = PTR_ERR(clkmux);
		return ret;
	}

	//这里选择时钟源为32K的外部时钟
	clklxt = clk_get(NULL, "xin32k");
	if (IS_ERR(clklxt)) {
		dev_err(&pdev->dev, "failed to get 32k clk\n");
		ret = PTR_ERR(clklxt);
		return ret;
	}

	//设置时钟源
	clk_set_parent(clkmux, clklxt);

	nuc970_wdt->clk = clk_get(NULL, "wdt");
	if (IS_ERR(nuc970_wdt->clk)) {
		dev_err(&pdev->dev, "failed to get watchdog clock\n");
		ret = PTR_ERR(nuc970_wdt->clk);
		return ret;
	}

	//启动看门狗时钟源
	clk_prepare(nuc970_wdt->clk);
	clk_enable(nuc970_wdt->clk);


	//获取看门狗使能
	nuc970_wdt->eclk = clk_get(NULL, "wdt_eclk"); //enable
	if (IS_ERR(nuc970_wdt->eclk)) {
		dev_err(&pdev->dev, "failed to get watchdog eclock\n");
		ret = PTR_ERR(nuc970_wdt->eclk);
		return ret;
	}

	//使能看门狗
	clk_prepare(nuc970_wdt->eclk);
	clk_enable(nuc970_wdt->eclk);

	nuc970_wdd.timeout = 2;		// default time out = 2 sec (2.03)
	nuc970_wdd.min_timeout = 1;	// min time out = 1 sec (0.53)
	nuc970_wdd.max_timeout = 9;	// max time out = 9 sec (8.03)

	//nuc970_wdd结构体见下面
	watchdog_init_timeout(&nuc970_wdd, heartbeat, &pdev->dev);
	watchdog_set_nowayout(&nuc970_wdd, nowayout);

	//看门狗注册设备
	ret = watchdog_register_device(&nuc970_wdd);
	if (ret) {
		dev_err(&pdev->dev, "err register watchdog device\n");
		clk_disable(nuc970_wdt->clk);
		clk_put(nuc970_wdt->clk);
		return ret;
	}

	return 0;
}

watchdog_register_device()函数将当前看门狗驱动注册到看门狗核心core层。

4.4 看门狗核心层注册

	//看门狗注册设备
	ret = watchdog_register_device(&nuc970_wdd);
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!
	 */

	//从watchdog_ida中获取一个空id 
	mutex_init(&wdd->lock);
	id = ida_simple_get(&watchdog_ida, 0, MAX_DOGS, GFP_KERNEL);
	if (id < 0)
		return id;
	wdd->id = id;

	//看门狗设备注册,主要包括字符设备的初始化cdev_init、添加cdev_add
	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()看门狗设备注册

4.5 看门狗设备注册

//看门狗设备注册,主要包括字符设备的初始化cdev_init、添加cdev_add
ret = watchdog_dev_register(wdd); 
int watchdog_dev_register(struct watchdog_device *watchdog)
{
	int err, devno;

	//如果watchdog->id==0就注册为杂设备
	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_fops结构体,这里贴出源码:

static const struct file_operations watchdog_fops = {
	.owner		= THIS_MODULE,
	.write		= watchdog_write, //对接应用层的write
	.unlocked_ioctl	= watchdog_ioctl, //对接应用层的ioctl
	.open		= watchdog_open, //对接应用层的open
	.release	= watchdog_release,
};

通过对watchdog_open、watchdog_write、watchdog_ioctl这几个函数分析,发现他们是供应用层系统调用的API,即当我们在应用层操作如下步骤时:

1> 打开看门狗设备时,经过文件系统,最终会调用这里的watchdog_open函数;

2> 向看门狗设备写操作时,经过文件系统,最终会调用这里的watchdog_write函数;

3> 向看门狗设备ioctl操作时,经过文件系统,最终会调用这里的watchdog_ioctl函数;

关于这里的三点,对内部实现感兴趣的可以去看源码,路径:linux-3.10.x\drivers\watchdog\watchdog_dev.c

5. 应用程序

#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 

int main(void)
{

        int ii = 10, j;
        int fd = open("/dev/watchdog", O_RDWR);
        if (fd == -1) {
                perror("watchdog");
                exit(EXIT_FAILURE);
        }

        printf("Open watchdog ok\n");

	// Ping WDT for 10 times, then let system reset
        while (1) {
        	printf("ii = %d\n", ii);
                if(ii-- > 0)
                        ioctl(fd, WDIOC_KEEPALIVE, 0);
                sleep(1);
        }
        close(fd);
        return 0;
}

6. 总结

       看门狗驱动比较简单,相比之前分析的usb驱动,简直是“小巫见大巫”,不过“麻雀虽小五脏俱全”,看门狗驱动包括了所有驱动共有的注册流程,platform平台设备和驱动的匹配,调用探测函数,完成设备的注册,其中也包括设备模型的注册...就到这里,今天过节,要准备做饭咯!!!


你可能感兴趣的:(nuc972)