看门狗驱动程序

大多数设备中都有看门狗硬件,所以驱动开发人员需要去实现这种设备的驱动。看门狗的用途是当CPU进入错误状态后,无法恢复的情况下,使计算机重新启动。本章将对看门狗的原理和驱动程序进行详细分析。

看门狗
由于计算机在工作时不可避免的要受到各种各样因素的干扰,即使再优秀的计算机程序也可能因为这种干扰使计算机进入一个死循环,更严重的就是导致死机。有两种方法来处理这种情况,一是采用人工复位的方法,二是依赖某种硬件来执行这个复位工作。这种硬件通常叫做看门狗(Watch Dog,WD)。
看门狗工作原理
S3C2440处理器内部集成了一个看门狗硬件。其提供了3个寄存器对看门狗进行操作。这3个寄存器分别是WTCON(看门狗控制寄存器)、WTDAT(看门狗数据寄存器)、WTCNT(看门狗计数寄存器)。
看门狗硬件结构
看门狗驱动中涉及到两种设备模型,分别是平台设备和混杂设备。
平台设备模型
从Linux 2.6起引入了一套新的驱动管理和注册模型:平台设备platform_device和平台驱动platform_driver。Linux中大部分的设备驱动,都可以使用这套机制,设备用platform_device表示,驱动用platform_driver进行表示。
1.平台设备
在Linux设备驱动中,有一种设备叫做平台设备。平台设备是指处理器上集成的额外功能的附加设备,如Watch Dog、IIC、IIS、RTC、ADC等设备。这些格外功能设备是为了节约硬件成本、减少产品功耗、缩小产品形状而集成到处理器内部的。需要注意的是,平台设备并不是与字符设备、块设备和网络设备并列的概念,而是一种平行的概念,其从另一个角度对设备进行了概括。如果从内核开发者的角度来看,平台设备的引入是为了更容易的开发字符设备、块设备和网络设备驱动。

2.平台设备驱动
每一个平台设备都对应一个平台设备驱动,这个驱动用来对平台设备进行探测、移除、关闭、电源管理等操作。平台设备用驱动platform_device结构体来描述。

static struct platform_driver s3c2410wdt_driver = {
	.probe		= s3c2410wdt_probe,     //看门狗探测函数
	.remove		= s3c2410wdt_remove,    //看门狗移除函数
	.shutdown	= s3c2410wdt_shutdown,  //看门狗关闭函数
	.suspend	= s3c2410wdt_suspend,   //看门狗挂起函数
	.resume		= s3c2410wdt_resume,    //看门狗恢复函数
	.driver		= {
		.owner	= THIS_MODULE,
		.name	= "s3c2410-wdt",  //设备的名字,与平台设备中的名字对应
	},
};

3.平台设备驱动的注册和注销
注册函数platform_driver_register()
注销函数platform_driver_unregister()

4.混杂设备
混杂设备并没有一个明确的定义。由于设备号比较紧张,所以一些不相关的设备可以使用同一个主设备,不同的次设备号。主设备号通常是10。由于这个原因,一些设备也可以叫做混杂设备。

static struct miscdevice s3c2410wdt_miscdev = {
	.minor		= WATCHDOG_MINOR,  //次设备号,定义为130
	.name		= "watchdog",         //混杂设备的名字
	.fops		= &s3c2410wdt_fops,   //混杂设备的操作指针
};

5.混杂设备的注册和注销
驱动程序中需要对混杂设备进行注册和注销,内核提供了misc_register ()和misc_deregister()两个函数。

看门狗设备驱动程序分析
Linux 2.6内核中已经实现了S3C2440处理器的看门狗驱动。由于S3C2440与S3C2410的看门狗硬件没有变化,所以内核沿用了S3C2410的看门狗驱动。
1.看门狗驱动的一些变量定义

static int nowayout	= WATCHDOG_NOWAYOUT;
static int tmr_margin	= CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME;
static int tmr_atboot	= CONFIG_S3C2410_WATCHDOG_ATBOOT;
static int soft_noboot;
static int debug;

2.看门狗模块的加载和卸载函数
看门狗模块的加载函数watchdog_init()中调用platform_driver_register()函数来注册平台设备驱动。

static char banner[] __initdata =
	KERN_INFO "S3C2410 Watchdog Timer, (c) 2004 Simtec Electronics\n";

static int __init watchdog_init(void)
{
	printk(banner);  //打印看门狗信息
	return platform_driver_register(&s3c2410wdt_driver);
}

卸载函数watchdog_exit()调用platform_driver_unregister()函数注销平台设备驱动,回收驱动所占用的系统资源。

static void __exit watchdog_exit(void)
{
	platform_driver_unregister(&s3c2410wdt_driver);
}

3.看门狗驱动探测函数
当调用platform_driver_register()函数注册驱动之后,会触发平台设备和驱动的匹配函数platform_match()。匹配成功,则会调用平台设备驱动中的probe()函数。

static int s3c2410wdt_probe(struct platform_device *pdev)
{
	struct resource *res;
	struct device *dev;
	unsigned int wtcon;
	int started = 0;
	int ret;
	int size;

	DBG("%s: probe=%p\n", __func__, pdev);

	dev = &pdev->dev;
	wdt_dev = &pdev->dev;

	/* get the memory region for the watchdog timer */

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

	size = (res->end - res->start) + 1;
	wdt_mem = request_mem_region(res->start, size, pdev->name);
	if (wdt_mem == NULL) {
		dev_err(dev, "failed to get memory region\n");
		ret = -ENOENT;
		goto err_req;
	}

	wdt_base = ioremap(res->start, size);
	if (wdt_base == NULL) {
		dev_err(dev, "failed to ioremap() region\n");
		ret = -EINVAL;
		goto err_req;
	}

	DBG("probe: mapped wdt_base=%p\n", wdt_base);

	wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
	if (wdt_irq == NULL) {
		dev_err(dev, "no irq resource specified\n");
		ret = -ENOENT;
		goto err_map;
	}

	ret = request_irq(wdt_irq->start, s3c2410wdt_irq, 0, pdev->name, pdev);
	if (ret != 0) {
		dev_err(dev, "failed to install irq (%d)\n", ret);
		goto err_map;
	}

	wdt_clock = clk_get(&pdev->dev, "watchdog");
	if (IS_ERR(wdt_clock)) {
		dev_err(dev, "failed to find watchdog clock source\n");
		ret = PTR_ERR(wdt_clock);
		goto err_irq;
	}

	clk_enable(wdt_clock);

	/* see if we can actually set the requested timer margin, and if
	 * not, try the default value */

	if (s3c2410wdt_set_heartbeat(tmr_margin)) {
		started = s3c2410wdt_set_heartbeat(
					CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);

		if (started == 0)
			dev_info(dev,
			   "tmr_margin value out of range, default %d used\n",
			       CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);
		else
			dev_info(dev, "default timer value is out of range, cannot start\n");
	}

	ret = misc_register(&s3c2410wdt_miscdev);
	if (ret) {
		dev_err(dev, "cannot register miscdev on minor=%d (%d)\n",
			WATCHDOG_MINOR, ret);
		goto err_clk;
	}

	if (tmr_atboot && started == 0) {
		dev_info(dev, "starting watchdog timer\n");
		s3c2410wdt_start();
	} else if (!tmr_atboot) {
		/* if we're not enabling the watchdog, then ensure it is
		 * disabled if it has been left running from the bootloader
		 * or other source */

		s3c2410wdt_stop();
	}

	/* print out a statement of readiness */

	wtcon = readl(wdt_base + S3C2410_WTCON);

	dev_info(dev, "watchdog %sactive, reset %sabled, irq %sabled\n",
		 (wtcon & S3C2410_WTCON_ENABLE) ?  "" : "in",
		 (wtcon & S3C2410_WTCON_RSTEN) ? "" : "dis",
		 (wtcon & S3C2410_WTCON_INTEN) ? "" : "en");

	return 0;

 err_clk:
	clk_disable(wdt_clock);
	clk_put(wdt_clock);

 err_irq:
	free_irq(wdt_irq->start, pdev);

 err_map:
	iounmap(wdt_base);

 err_req:
	release_resource(wdt_mem);
	kfree(wdt_mem);

	return ret;
}

4.设置看门狗复位时间函数s3c2410wdt_set_heartbeat()
在探测函数s3c2410wdt_probe()中的大多数函数,在前面的章节都有说明。这里重点讲解一下s3c2410wdt_set_heartbeat()函数,该函数的参数接收看门狗复位时间,默认是15秒。该函数先后完成如下几个功能:
(1)使用clk_get_rate()函数获得看门狗的时钟频率PCLK。
(2)判断复位时间timeout是否超过计数寄存器WTCNT能表示的最大值,该寄存器的最大值为65536。
(3)设置第一个分频器的分频系数。
(4)设置数据寄存器WTDAT。

static int s3c2410wdt_set_heartbeat(int timeout)
{
	unsigned int freq = clk_get_rate(wdt_clock);
	unsigned int count;
	unsigned int divisor = 1;
	unsigned long wtcon;

	if (timeout < 1)
		return -EINVAL;

	freq /= 128;
	count = timeout * freq;

	DBG("%s: count=%d, timeout=%d, freq=%d\n",
	    __func__, count, timeout, freq);

	/* if the count is bigger than the watchdog register,
	   then work out what we need to do (and if) we can
	   actually make this value
	*/

	if (count >= 0x10000) {
		for (divisor = 1; divisor <= 0x100; divisor++) {
			if ((count / divisor) < 0x10000)
				break;
		}

		if ((count / divisor) >= 0x10000) {
			dev_err(wdt_dev, "timeout %d too big\n", timeout);
			return -EINVAL;
		}
	}

	tmr_margin = timeout;

	DBG("%s: timeout=%d, divisor=%d, count=%d (%08x)\n",
	    __func__, timeout, divisor, count, count/divisor);

	count /= divisor;
	wdt_count = count;

	/* update the pre-scaler */
	wtcon = readl(wdt_base + S3C2410_WTCON);
	wtcon &= ~S3C2410_WTCON_PRESCALE_MASK;
	wtcon |= S3C2410_WTCON_PRESCALE(divisor-1);

	writel(count, wdt_base + S3C2410_WTDAT);
	writel(wtcon, wdt_base + S3C2410_WTCON);

	return 0;  //成功返回0
}

5.看门狗的开始函数s3c2410wdt_start()和停止函数s3c2410wdt_stop()
为了控制看门狗的开始和停止,驱动中提供了开始和停止函数。
(1)开始函数s3c2410wdt_start()

static void s3c2410wdt_start(void)
{
	unsigned long wtcon;  //暂时存储WTCNT的值

	spin_lock(&wdt_lock);  //避免不同线程同时访问临界资源

	__s3c2410wdt_stop();  //先停止看门狗便于设置

	wtcon = readl(wdt_base + S3C2410_WTCON);  //读出WTCON的值
	wtcon |= S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128;

	if (soft_noboot) {
		wtcon |= S3C2410_WTCON_INTEN;
		wtcon &= ~S3C2410_WTCON_RSTEN;
	} else {
		wtcon &= ~S3C2410_WTCON_INTEN;
		wtcon |= S3C2410_WTCON_RSTEN;
	}

	DBG("%s: wdt_count=0x%08x, wtcon=%08lx\n",
	    __func__, wdt_count, wtcon);

	writel(wdt_count, wdt_base + S3C2410_WTDAT);
	writel(wdt_count, wdt_base + S3C2410_WTCNT);
	writel(wtcon, wdt_base + S3C2410_WTCON);
	spin_unlock(&wdt_lock);
}

(2)停止函数s3c2410wdt_stop()
在探测函数s3c2410wdt_probe()中,当所有工作都准备完成后,如果不允许看门狗立即启动,会调用s3c2410wdt_stop()函数使看门狗停止工作。

static void s3c2410wdt_stop(void)
{
	spin_lock(&wdt_lock);  //加自旋锁
	__s3c2410wdt_stop();
	spin_unlock(&wdt_lock);  //解除自旋锁
}

在s3c2410wdt_stop()中使用了spin_lock()函数对wdt_lock进行加锁,并调用__s3c2410wdt_stop()函数完成实际的看门狗停止工作。

static void __s3c2410wdt_stop(void)
{
	unsigned long wtcon;

	wtcon = readl(wdt_base + S3C2410_WTCON);
	wtcon &= ~(S3C2410_WTCON_ENABLE | S3C2410_WTCON_RSTEN);
	writel(wtcon, wdt_base + S3C2410_WTCON);
}

6.看门狗驱动移除函数s3c2410wdt_remove()
S3C2440肯门狗驱动的移除函数完成与探测函数相反的功能。其包括释放I/O内存资源、释放IRQ资源、禁止看门狗时钟源和注销混杂设备。

static int s3c2410wdt_remove(struct platform_device *dev)
{
	release_resource(wdt_mem);  //释放资源resource
	kfree(wdt_mem);            //释放I/O内存
	wdt_mem = NULL;           

	free_irq(wdt_irq->start, dev);   //释放中断号
	wdt_irq = NULL;

	clk_disable(wdt_clock);        //禁止时钟
	clk_put(wdt_clock);           //减少时钟引用计数
	wdt_clock = NULL;

	iounmap(wdt_base);          //关闭内存映射
	misc_deregister(&s3c2410wdt_miscdev);  //注销混杂设备
	return 0;
}

7.平台设备驱动s3c2410wdt_driver中的其他重要函数
平台设备驱动s3c2410wdt_driver中的s3c2410wdt_probe ()和s3c2410wdt_remove()函数都已经说明,剩下另外几个重要的函数需要说明。
(1)关闭函数s3c2410wdt_shutdown()
当看门狗被关闭时,内核会自动调用s3c2410wdt_shutdown()函数先停止看门狗设备。

static void s3c2410wdt_shutdown(struct platform_device *dev)
{
	s3c2410wdt_stop();  //停止看门狗
}

(2)挂起函数s3c2410wdt_suspend()
当需要暂停看门狗时,可以调用s3c2410wdt_suspend()函数。该函数保存看门狗的寄存器,并设置看门狗为停止状态。该函数一般由电源管理子模块调用,用来节省电源。

static int s3c2410wdt_suspend(struct platform_device *dev, pm_message_t state)
{
	/* Save watchdog state, and turn it off. */
	wtcon_save = readl(wdt_base + S3C2410_WTCON);
	wtdat_save = readl(wdt_base + S3C2410_WTDAT);

	/* Note that WTCNT doesn't need to be saved. */
	s3c2410wdt_stop();

	return 0;
}

(3)恢复函数s3c2410wdt_resume()
该函数恢复看门狗寄存器的值。若挂起前为停止状态,则恢复后看门狗为停止状态;若挂起前为启动状态,则恢复后看门狗为启动。

static int s3c2410wdt_resume(struct platform_device *dev)
{
	/* Restore watchdog state. */

	writel(wtdat_save, wdt_base + S3C2410_WTDAT);
	writel(wtdat_save, wdt_base + S3C2410_WTCNT); /* Reset count */
	writel(wtcon_save, wdt_base + S3C2410_WTCON);

	printk(KERN_INFO PFX "watchdog %sabled\n",
	       (wtcon_save & S3C2410_WTCON_ENABLE) ? "en" : "dis");

	return 0;
}

8.混杂设备的file_operations中的函数
(1)打开函数s3c2410wdt_open()

static int s3c2410wdt_open(struct inode *inode, struct file *file)
{
	if (test_and_set_bit(0, &open_lock))  //只允许打开一次
		return -EBUSY;

	if (nowayout)  //不允许关闭设备
		__module_get(THIS_MODULE);  //增加模块引用计数

	allow_close = CLOSE_STATE_NOT;  //设为不允许关闭

	/* start the timer */
	s3c2410wdt_start();  //开始运行看门狗设备
	return nonseekable_open(inode, file);  //不允许调用seek()
}

(2)释放函数s3c2410wdt_release()

static int s3c2410wdt_release(struct inode *inode, struct file *file)
{
	/*
	 *	Shut off the timer.
	 * 	Lock it in if it's a module and we set nowayout
	 */

	if (allow_close == CLOSE_STATE_ALLOW)  //看门狗为允许状态
		s3c2410wdt_stop();  //关闭看门狗
	else {
		dev_err(wdt_dev, "Unexpected close, not stopping watchdog\n");
		s3c2410wdt_keepalive();
	}
	allow_close = CLOSE_STATE_NOT;  //设为不允许关闭
	clear_bit(0, &open_lock);  //将open_lock的第【0】位设置为0,是原子操作
	return 0;
}
s3c2410wdt_release()中调用了s3c2410wdt_keepalive()函数,该函数重新装载了WTCNT寄存器,相当于一个喂狗的功能
static void s3c2410wdt_keepalive(void)
{
	spin_lock(&wdt_lock);  //获得自旋锁
	writel(wdt_count, wdt_base + S3C2410_WTCNT);  //重写计数寄存器WTCNT
	spin_unlock(&wdt_lock);  //释放自旋锁
}

(3)写函数s3c2410wdt_write()

static ssize_t s3c2410wdt_write(struct file *file, const char __user *data,
				size_t len, loff_t *ppos)
{
	/*
	 *	Refresh the timer.
	 */
	if (len) {  //有数据写入len不为零
		if (!nowayout) {  //允许关闭
			size_t i;

			/* In case it was set long ago */
			allow_close = CLOSE_STATE_NOT;  //允许关闭状态

			for (i = 0; i != len; i++) {
				char c;

				if (get_user(c, data + i))
					return -EFAULT;
				if (c == 'V')
					allow_close = CLOSE_STATE_ALLOW;
			}
		}
		s3c2410wdt_keepalive();
	}
	return len;
}

(4)I/O控制函数s3c2410wdt_ioctl()

static long s3c2410wdt_ioctl(struct file *file,	unsigned int cmd,
							unsigned long arg)
{
	void __user *argp = (void __user *)arg;
	int __user *p = argp;
	int new_margin;

	switch (cmd) {
	case WDIOC_GETSUPPORT:  //获得看门狗设备的信息
		return copy_to_user(argp, &s3c2410_wdt_ident,
			sizeof(s3c2410_wdt_ident)) ? -EFAULT : 0;
	case WDIOC_GETSTATUS:  //获得看门狗的状态
	case WDIOC_GETBOOTSTATUS:  
		return put_user(0, p);
	case WDIOC_KEEPALIVE:  //对看门狗进行喂狗操作
		s3c2410wdt_keepalive();
		return 0;
	case WDIOC_SETTIMEOUT:  //设置新的超时时间
		if (get_user(new_margin, p))
			return -EFAULT;
		if (s3c2410wdt_set_heartbeat(new_margin))
			return -EINVAL;
		s3c2410wdt_keepalive();  //喂狗操作
		return put_user(tmr_margin, p);
	case WDIOC_GETTIMEOUT:  //获得看门狗设备的当前超时时间
		return put_user(tmr_margin, p);
	default:
		return -ENOTTY;
	}
}

9.看门狗中断处理函数s3c2410wdt_irq()
当看门狗设备作为定时器使用时,发出中断信号,而不发出复位信号。该中断在探测函数s3c2410wdt_probe()中通过调用request_irq()函数向系统做了申请。中断处理函数的主要功能是喂狗操作,使看门狗重新开始计数。

static irqreturn_t s3c2410wdt_irq(int irqno, void *param)
{
	dev_info(wdt_dev, "watchdog timer expired (irq)\n");  //调试信息

	s3c2410wdt_keepalive();  //看门狗喂狗操作
	return IRQ_HANDLED;
}
Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐