标签:red 嵌入式linux 字节 app .sh specified 系统 starting comment
本文将介绍看门狗驱动的实现。
目标平台:TQ2440
CPU:s3c2440
内核版本:2.6.30
1. 看门狗概述
看门狗其实就是一个定时器,当该定时器溢出前必须对看门狗进行"喂狗“,如果不这样做,定时器溢出后则将复位CPU。
因此,看门狗通常用于对处于异常状态的CPU进行复位。
具体的概念请自行百度。
2. S3C2440看门狗
s3c2440的看门狗的原理框图如下:
可以看出,看门狗定时器的频率由PCLK提供,其预分频器最大取值为255+1;另外,通过MUX,可以进一步降低频率。
定时器采用递减模式,一旦到0,则可以触发看门狗中断以及RESET复位信号。
看门狗定时器的频率的计算公式如下:
3. 看门狗驱动
看门狗驱动代码位于: linux/drivers/char/watchdog/s3c2410_wdt.c
3.1 模块注册以及probe函数
-
static struct platform_driver s3c2410wdt_driver = {
-
.probe = s3c2410wdt_probe,
-
.remove = s3c2410wdt_remove,
-
.shutdown = s3c2410wdt_shutdown,
-
.suspend = s3c2410wdt_suspend,
-
.resume = s3c2410wdt_resume,
-
-
-
-
-
-
-
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);}
-
-
module_init(watchdog_init)
模块的注册函数很简单,直接调用了 platform的驱动注册函数platform_driver_register。
该函数在注册时会调用驱动的probe方法,也即s3c2410wdt_probe函数。
我们来看下这个函数:
-
static int s3c2410wdt_probe(struct platform_device *pdev)
-
-
-
-
-
-
-
-
-
DBG("%s: probe=%p\n", __func__, pdev);
-
-
-
-
-
-
-
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
-
-
dev_err(dev, "no memory resource specified\n");
-
-
-
-
-
size = (res->end - res->start) + 1;
-
wdt_mem = request_mem_region(res->start, size, pdev->name);
-
-
dev_err(dev, "failed to get memory region\n");
-
-
-
-
-
-
wdt_base = ioremap(res->start, size);
-
-
dev_err(dev, "failed to ioremap() region\n");
-
-
-
-
-
DBG("probe: mapped wdt_base=%p\n", wdt_base);
-
-
-
wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
-
-
dev_err(dev, "no irq resource specified\n");
-
-
-
-
-
-
ret = request_irq(wdt_irq->start, s3c2410wdt_irq, 0, pdev->name, pdev);
-
-
dev_err(dev, "failed to install irq (%d)\n", ret);
-
-
-
-
wdt_clock = clk_get(&pdev->dev, "watchdog");
-
-
dev_err(dev, "failed to find watchdog clock source\n");
-
ret = PTR_ERR(wdt_clock);
-
-
-
-
-
-
-
-
-
-
-
if (s3c2410wdt_set_heartbeat(tmr_margin)) {
-
started = s3c2410wdt_set_heartbeat(
-
CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);
-
-
-
-
"tmr_margin value out of range, default %d used\n",
-
CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);
-
-
dev_info(dev, "default timer value is out of range, cannot start\n");
-
-
-
-
ret = misc_register(&s3c2410wdt_miscdev);
-
-
dev_err(dev, "cannot register miscdev on minor=%d (%d)\n",
-
-
-
-
-
-
-
-
-
-
if (tmr_atboot && started == 0) {
-
dev_info(dev, "starting watchdog timer\n");
-
-
} else if (!tmr_atboot) {
-
-
-
-
-
-
-
-
-
-
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");
-
-
-
-
-
-
-
-
-
free_irq(wdt_irq->start, pdev);
-
-
-
-
-
-
release_resource(wdt_mem);
-
-
-
-
该函数的前面几步和其他驱动的类似:获取平台资源后进行相应的注册,并使能时钟。接着,将调用s3c2410wdt_set_heartbeat函数来设置看门狗的工作频率。
然后,注册一个混杂设备,为看门狗注册相应的API到内核中。最后,判断是否需要启动看门狗并调用相应的函数。
上面是probe函数大致的执行过程。随后我们看下其中被调用的s3c2410wdt_set_heartbeat函数,该函数将设置看门狗的工作频率。
PS:probe函数的执行依赖于平台设备,而看门狗平台设备的添加和注册在linux/arch/arm/plat-s3c24xx/devs.c和 linux/arch/arm/mach-s3c2410/mach-smdk2410.c中已经完成,因此对于看门狗驱动无需进行移植。
3.2 s3c2410wdt_set_heartbeat
-
static int s3c2410wdt_set_heartbeat(int timeout)
-
-
unsigned int freq = clk_get_rate(wdt_clock);
-
-
unsigned int divisor = 1;
-
-
-
-
-
-
-
-
-
DBG("%s: count=%d, timeout=%d, freq=%d\n",
-
__func__, count, timeout, freq);
-
-
-
-
-
-
-
-
for (divisor = 1; divisor <= 0x100; divisor++) {
-
if ((count / divisor) < 0x10000)
-
-
-
-
if ((count / divisor) >= 0x10000) {
-
dev_err(wdt_dev, "timeout %d too big\n", timeout);
-
-
-
-
-
-
-
DBG("%s: timeout=%d, divisor=%d, count=%d (%08x)\n",
-
__func__, timeout, divisor, count, count/divisor);
-
-
-
-
-
-
-
-
-
-
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);
-
-
-
形参timeout为定时时间,单位为秒。
这里唯一需要注意的是freq/= 128这一步。在第2节我们看到,通过MUX,可选择的分频系数为16,32,64和128,但是在这里驱动直接使用了128来计算系数。
在下一节我们将会看到,驱动为什么在这里只使用了128这个分频系数。
当该函数调用结束时,Prescalervalue 和计数器值都将计算完成,并写入寄存器。
3.3 定时器的启动、停止和保活
3.3.1 停止
定时器的停止由 s3c2410wdt_stop函数完成。
-
static void __s3c2410wdt_stop(void)
-
-
-
-
wtcon = readl(wdt_base + S3C2410_WTCON);
-
-
wtcon &= ~(S3C2410_WTCON_ENABLE | S3C2410_WTCON_RSTEN);
-
writel(wtcon, wdt_base + S3C2410_WTCON);
-
-
-
static void s3c2410wdt_stop(void)
-
-
-
-
-
3.3.2 启动
定时器的启动由s3c2410wdt_start函数完成。
-
static void s3c2410wdt_start(void)
-
-
-
-
-
-
-
-
wtcon = readl(wdt_base + S3C2410_WTCON);
-
-
wtcon |= S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128;
-
-
-
wtcon |= S3C2410_WTCON_INTEN;
-
wtcon &= ~S3C2410_WTCON_RSTEN;
-
-
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);
-
-
在这里我们到了一个宏S3C2410_WTCON_DIV128,这里设置了分频系数为128。而s3c2410wdt_start函数的调用肯定在s3c2410wdt_set_heartbeat之后,这也就是为什么在3.2节中使用了freq/= 128这一步。
3.3.3 保活
定时器的保活由s3c2410wdt_keepalive函数完成。
-
static void s3c2410wdt_keepalive(void)
-
-
-
writel(wdt_count, wdt_base + S3C2410_WTCNT);
-
-
最后需要说明的是,从3.1节probe函数的执行来看,由于tmr_atboot变量的初值为0,因此看门狗定时器是没有工作的。
3.4 看门狗驱动API
看门狗驱动提供的API如下:
-
static const struct file_operations s3c2410wdt_fops = {
-
-
-
.write = s3c2410wdt_write,
-
.unlocked_ioctl = s3c2410wdt_ioctl,
-
-
.release = s3c2410wdt_release,
-
我们可以看到驱动提供了4个API,同时,驱动并不支持llseek方法。
3.4.1 open方法
-
static int s3c2410wdt_open(struct inode *inode, struct file *file)
-
-
if (test_and_set_bit(0, &open_lock))
-
-
-
-
__module_get(THIS_MODULE);
-
-
allow_close = CLOSE_STATE_NOT;
-
-
-
-
return nonseekable_open(inode, file);
-
这里需要注意的是,设备文件/dev/watchdog 只能被open一次,大于一次的open都将返回-EBUSY。
3.4.2 release方法
-
static int s3c2410wdt_release(struct inode *inode, struct file *file)
-
-
-
-
-
-
-
if (allow_close == CLOSE_STATE_ALLOW)
-
-
-
dev_err(wdt_dev, "Unexpected close, not stopping watchdog\n");
-
-
-
allow_close = CLOSE_STATE_NOT;
-
clear_bit(0, &open_lock);
-
-
3.4.3 wirte方法
-
static ssize_t s3c2410wdt_write(struct file *file, const char __user *data,
-
size_t len, loff_t *ppos)
-
-
-
-
-
-
-
-
-
-
-
allow_close = CLOSE_STATE_NOT;
-
-
for (i = 0; i != len; i++) {
-
-
-
if (get_user(c, data + i))
-
-
-
-
allow_close = CLOSE_STATE_ALLOW;
-
-
-
-
-
-
只要写入数据的长度不为0,都会调用s3c2410wdt_keepalive函数来重置定时器。
3.4.4 unlocked_ioctl方法
-
static long s3c2410wdt_ioctl(struct file *file, unsigned int cmd,
-
-
-
void __user *argp = (void __user *)arg;
-
-
-
-
-
-
return copy_to_user(argp, &s3c2410_wdt_ident,
-
sizeof(s3c2410_wdt_ident)) ? -EFAULT : 0;
-
-
case WDIOC_GETBOOTSTATUS:
-
-
-
-
-
-
if (get_user(new_margin, p))
-
-
if (s3c2410wdt_set_heartbeat(new_margin))
-
-
-
return put_user(tmr_margin, p);
-
-
return put_user(tmr_margin, p);
-
-
-
-
4. 测试程序
-
-
-
#include <linux/watchdog.h>
-
-
-
-
-
-
-
fd = open("/dev/watchdog", O_RDWR);
-
-
printf("open device fail\n");
-
-
-
-
-
ret = write(fd, &val, sizeof(val));
-
-
perror("watchdog write wrong\n");
-
-
-
-
-
-
-
该测试程序每隔5秒重置看门狗定时器,而驱动默认的超时时间是15秒。
可以将5秒替换为16秒,你会发现系统自动重启了。
5. 结束语
本文主要对基于S3C2440的看门狗驱动作出了分析。该驱动只是一个简单的字符设备,比较简单。其中,用于计算预分频系数的s3c2410wdt_set_heartbeat函数比较关键,读者可以好好琢磨下该系数是如何计算出来的。
Thank you for your time。
2013.1.30 添加测试程序
基于S3C2440的嵌入式Linux驱动——看门狗(watchdog)驱动解读
标签:red 嵌入式linux 字节 app .sh specified 系统 starting comment
原文地址:https://www.cnblogs.com/wanghuaijun/p/9461671.html