内核版本:linux-2.6.32.2 实验平台:mini2440
1. 平台设备定义:
s3c2440的watchdog平台设备定义如下(plat-s3c24xx/devs.c):
/* Watchdog */ static struct resource s3c_wdt_resource[] = { [0] = { .start = S3C24XX_PA_WATCHDOG, .end = S3C24XX_PA_WATCHDOG + S3C24XX_SZ_WATCHDOG - 1, .flags = IORESOURCE_MEM, }, [1] = { .start = IRQ_WDT, .end = IRQ_WDT, .flags = IORESOURCE_IRQ, } }; struct platform_device s3c_device_wdt = { .name = "s3c2410-wdt", .id = -1, .num_resources = ARRAY_SIZE(s3c_wdt_resource), .resource = s3c_wdt_resource, }; EXPORT_SYMBOL(s3c_device_wdt);现在我们将watchdog平台设备添加到mini2440_devices里面:
static struct platform_device *mini2440_devices[] __initdata = { /* ... */ &s3c_device_wdt, /* ... */ };
Device Drivers ---> [*] Watchdog Timer Support ---> <*> S3C2410 Watchdog
3. s3c2440 watchdog驱动分析
3.1 watchdog 平台驱动注册
平台驱动定义如下:
static struct platform_driver s3c2410wdt_driver = { .probe = s3c2410wdt_probe, .remove = __devexit_p(s3c2410wdt_remove), .shutdown = s3c2410wdt_shutdown, .suspend = s3c2410wdt_suspend, .resume = s3c2410wdt_resume, .driver = { .owner = THIS_MODULE, .name = "s3c2410-wdt", }, };
static int __init watchdog_init(void) { /* ... */ return platform_driver_register(&s3c2410wdt_driver); }
static void __exit watchdog_exit(void) { platform_driver_unregister(&s3c2410wdt_driver); }
首先是获取watchdog平台设备的IO地址资源,然后将这个物理地址映射成虚拟地址,这个虚拟地址的基地址保存在变量wdt_base中,代码如下:
/* 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);watchdog的时钟源是PCLK,此时假设它是50MHz。
接下来是设置watchdog的时钟:
/* 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"); }watchdog的时钟源为PCLK,经预分频和128分频得到watchdog的时钟频率,而这个值是根据tmr_margin计算得来的,tmr_margin即超时时间,单位为秒,默认为15秒。
注册一个杂项设备,为应用程序提供接口:
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(); }
杂项设备定义如下:
/* kernel interface */ static const struct file_operations s3c2410wdt_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .write = s3c2410wdt_write, .unlocked_ioctl = s3c2410wdt_ioctl, .open = s3c2410wdt_open, .release = s3c2410wdt_release, }; static struct miscdevice s3c2410wdt_miscdev = { .minor = WATCHDOG_MINOR, .name = "watchdog", .fops = &s3c2410wdt_fops, };
/* * /dev/watchdog handling */ 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); expect_close = 0; /* start the timer */ s3c2410wdt_start(); return nonseekable_open(inode, file); }open函数用于启动watchdog。
s3c2410wdt_start代码如下:
static void s3c2410wdt_start(void) { unsigned long wtcon; spin_lock(&wdt_lock); __s3c2410wdt_stop(); wtcon = readl(wdt_base + S3C2410_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); }首先调用 __s3c2410wdt_stop()函数停止watchdog,soft_noboot变量表示watchdog超时后是否交由软件来处理,如果为soft_noboot不为0,那么将使能中断和禁止watchdog超时产生复位信号,如果为0那么将禁止中断和使能watchdog超时产生复位信号。最后设置watchdog的WTDAT、WTCNT寄存器,使能watchdog定时器。
3.3.2 write函数
static ssize_t s3c2410wdt_write(struct file *file, const char __user *data, size_t len, loff_t *ppos) { /* * Refresh the timer. */ if (len) { if (!nowayout) { size_t i; /* In case it was set long ago */ expect_close = 0; for (i = 0; i != len; i++) { char c; if (get_user(c, data + i)) return -EFAULT; if (c == 'V') expect_close = 42; } } s3c2410wdt_keepalive(); } return len; }nowayout表示应用程序在调用close系统调用时是否停止watchdog定时器,为0表示允许停止,否则不允许停止。最后调用s3c2410wdt_keepalive函数,俗称喂狗,代码如下:
static void s3c2410wdt_keepalive(void) { spin_lock(&wdt_lock); writel(wdt_count, wdt_base + S3C2410_WTCNT); spin_unlock(&wdt_lock); }s3c2410wdt_keepalive函数即将WTCNT值设置为初始值。
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; } }ioctl有以下几个主要的命令:
OC_KEEPALIVE: 调用s3c2410wdt_keepalive函数进行喂狗操作。 WDIOC_SETTIMEOUT: 设置新的超时时间,并将老的超时时间(tmr_margin)返回。 WDIOC_GETTIMEOUT: 返回超时时间值。
3.3.4 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 (expect_close == 42) s3c2410wdt_stop(); else { dev_err(wdt_dev, "Unexpected close, not stopping watchdog\n"); s3c2410wdt_keepalive(); } expect_close = 0; clear_bit(0, &open_lock); return 0; }根据expect_close值决定是否停止watchdog定时器。
3.4 中断处理函数
/* interrupt handler code */ static irqreturn_t s3c2410wdt_irq(int irqno, void *param) { dev_info(wdt_dev, "watchdog timer expired (irq)\n"); s3c2410wdt_keepalive(); return IRQ_HANDLED; }中断处理函数执行的喂狗操作。
4. watchdog实验
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main(void) { int fd; fd = open("/dev/watchdog", O_WRONLY); if (fd < 0) return -1; while (1); return 0; }在测试程序中,调用open函数之后将会启动watchdog,而由于没有执行任何的喂狗操作,系统将在15秒后重启。为了程序的正常运行,必须在15秒呢执行喂狗操作,例如:
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> #include <unistd.h> int main(void) { int fd; char *food = "food"; fd = open("/dev/watchdog", O_WRONLY); if (fd < 0) return -1; while (1) { write(fd, food, strlen(food)); sleep(5); } return 0; }上面代码5秒钟执行一次喂狗操作,这样系统就不会在15秒后重启。