内核版本: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,
/* ... */
};
2. 内核配置
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",
},
};
在watchdog_init函数里面完成平台驱动的注册:
static int __init watchdog_init(void)
{
/* ... */
return platform_driver_register(&s3c2410wdt_driver);
}
注销在watchdog_exit函数里面:
static void __exit watchdog_exit(void)
{
platform_driver_unregister(&s3c2410wdt_driver);
}
3.2 probe函数
首先是获取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);
然后是获取中断号资源信息并注册了一个中断处理函数,中断处理函数为s3c2410wdt_irq,代码如下:
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;
}
获取watchdog的时钟源并启用它:
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;
}
根据tmr_atboot变量判断是否启动watchdog,如果为0,表示在执行probe函数时不启动watchdog,交由应用程序完成。如果为1,表示启动watchdog。
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();
}
3.3 应用程序接口部分
杂项设备定义如下:
/* 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,
};
3.3.1 open函数
/*
* /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值设置为初始值。3.3.3 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;
}
}
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秒后重启。