mini2440 watchdog支持

内核版本: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秒后重启。

你可能感兴趣的:(mini2440 watchdog支持)