尽管在linux系统中,对于S3C2440开发板来说,默认是已经配置了看门狗定时器,如:
Device Drivers ---> [*] Watchdog Timer Support ---> [*] Disable watchdog shutdown on close (NEW) //如果选中, 用户一旦 open 看门狗设备,用户就没法关闭看门狗 <*> S3C2410 Watchdog |
但看门狗定时器是没有打开的,所以我们会在启动系统的时候,看到如下信息提示:
下面就具体分析一下看门狗定时器。
在Mach-Louis210.c文件中已经添加了看门狗定时器这个平台设备:
static struct platform_device *Louis210_devices[]__initdata = {
……
&s3c_device_wdt,
……
};
而这个平台设备的具体定义是在arch/arm/plat-samsung目录下的Devs.c文件内给出的:
static struct resource s3c_wdt_resource[] = {
[0] = DEFINE_RES_MEM(S3C_PA_WDT, SZ_1K),
[1] = DEFINE_RES_IRQ(IRQ_WDT),
};
struct platform_device s3c_device_wdt = {
.name = "s5pv210-wdt",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_wdt_resource),
.resource = s3c_wdt_resource,
};
该平台设备所对应的平台驱动定义在drivers/watchdog目录下的S3c2410_wdt.c文件内:
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 = "s5pv210-wdt",
.of_match_table = of_match_ptr(s3c2410_wdt_match),
},
};
在S3c2410_wdt.c文件内的开始处,定义了几个很重要的变量:
#define CONFIG_S3C2410_WATCHDOG_ATBOOT (1)//(0)
#define CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME (15) //定时器会溢出时间,系统会复位
#define CONFIG_S3C2410_WATCHDOG_INTEN (0) //add by Louis,与CONFIG_S3C2410_WATCHDOG_ATBOOT冲突
static bool nowayout = WATCHDOG_NOWAYOUT; //是否允许关闭看门狗定时器,1允许,0不允许
static int tmr_margin; //喂狗的最长时间间隔,上电默认时间间隔为 CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME
static int tmr_atboot = CONFIG_S3C2410_WATCHDOG_ATBOOT; //表示系统启动后是否使能看门狗,1表示使能,0表示关闭
static int soft_noboot = CONFIG_S3C2410_WATCHDOG_INTEN; //表示是否把看门狗当作一般的定时器来用,是否发出中断信号
static int debug;
在S3c2410_wdt.c文件内,还定义了一个重要的结构:
static struct watchdog_device s3c2410_wdd = {
.info = &s3c2410_wdt_ident,
.ops = &s3c2410wdt_ops,
.timeout = CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME,
};
其中.ops指向的是s3c2410wdt_ops结构:
static struct watchdog_ops s3c2410wdt_ops = {
.owner = THIS_MODULE,
.start = s3c2410wdt_start, 开启看门狗
.stop = s3c2410wdt_stop, 关闭看门狗
.ping = s3c2410wdt_keepalive, 用于喂狗
.set_timeout = s3c2410wdt_set_heartbeat, 设置看门狗定时时间间隔,即喂狗时间
};
下面就重点介绍一下s3c2410wdt_probe函数:
static int s3c2410wdt_probe(struct platform_device *pdev)
{
struct device *dev;
unsigned int wtcon;
int started = 0;
int ret;
DBG("%s: probe=%p\n", __func__, pdev);
dev = &pdev->dev;
wdt_dev = &pdev->dev;
/* 获取看门狗平台设备所使用的内存映射空间 */
wdt_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (wdt_mem == NULL) {
dev_err(dev, "no memory resource specified\n");
return -ENOENT;
}
/* 获取看门狗平台设备所使用的中断号 */
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;
}
/* get the memory region for the watchdog timer */
/* 获取内存基地址 */
wdt_base = devm_ioremap_resource(dev, wdt_mem);
if (IS_ERR(wdt_base)) {
ret = PTR_ERR(wdt_base);
goto err;
}
DBG("probe: mapped wdt_base=%p\n", wdt_base);
/* 从平台时钟队列中获取看门狗的时钟 */
wdt_clock = devm_clk_get(dev, "watchdog");
if (IS_ERR(wdt_clock)) {
dev_err(dev, "failed to find watchdog clock source\n");
ret = PTR_ERR(wdt_clock);
goto err;
}
/* 使能看门狗时钟 */
clk_prepare_enable(wdt_clock);
/* 注册CPU频率 */
ret = s3c2410wdt_cpufreq_register();
if (ret < 0) {
pr_err("failed to register cpufreq\n");
goto err_clk;
}
/* see if we can actually set the requested timer margin, and if
* not, try the default value */
watchdog_init_timeout(&s3c2410_wdd, tmr_margin, &pdev->dev);
/* 设置看门狗定时器的溢出时间间隔 */
if (s3c2410wdt_set_heartbeat(&s3c2410_wdd, s3c2410_wdd.timeout)) {
started = s3c2410wdt_set_heartbeat(&s3c2410_wdd,
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 = devm_request_irq(dev, wdt_irq->start, s3c2410wdt_irq, 0,
pdev->name, pdev);
if (ret != 0) {
dev_err(dev, "failed to install irq (%d)\n", ret);
goto err_cpufreq;
}
/* 由nowayout的值设置看门狗的属性 */
watchdog_set_nowayout(&s3c2410_wdd, nowayout);
/* 注册看门狗设备 */
ret = watchdog_register_device(&s3c2410_wdd);
if (ret) {
dev_err(dev, "cannot register watchdog (%d)\n", ret);
goto err_cpufreq;
}
/* 由tmr_atboot的值来决定是否开启看门狗定时器 */
if (tmr_atboot && started == 0) {
dev_info(dev, "starting watchdog timer\n");
s3c2410wdt_start(&s3c2410_wdd);
} 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(&s3c2410_wdd);
}
/* 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) ? "en" : "dis", //看门狗是否允许发生复位信号
(wtcon & S3C2410_WTCON_INTEN) ? "en" : "dis"); //看门狗是否允许发出中断信号(soft_noboot决定是否打开)
return 0;
err_cpufreq:
s3c2410wdt_cpufreq_deregister();
err_clk:
clk_disable_unprepare(wdt_clock);
wdt_clock = NULL;
err:
wdt_irq = NULL;
wdt_mem = NULL;
return ret;
}
注册函数 watchdog_register_device()
int watchdog_register_device(struct watchdog_device *wdd)
{
int ret, id, devno;
if (wdd == NULL || wdd->info == NULL || wdd->ops == NULL)
return -EINVAL;
/* Mandatory operations need to be supported */
if (wdd->ops->start == NULL || wdd->ops->stop == NULL)
return -EINVAL;
watchdog_check_min_max_timeout(wdd);
/*
* Note: now that all watchdog_device data has been verified, we
* will not check this anymore in other functions. If data gets
* corrupted in a later stage then we expect a kernel panic!
*/
mutex_init(&wdd->lock);
id = ida_simple_get(&watchdog_ida, 0, MAX_DOGS, GFP_KERNEL);
if (id < 0)
return id;
wdd->id = id;
/* 函数内注册混杂设备,生成/dev/watchdog文件 */
ret = watchdog_dev_register(wdd);
if (ret) {
ida_simple_remove(&watchdog_ida, id);
if (!(id == 0 && ret == -EBUSY))
return ret;
/* Retry in case a legacy watchdog module exists */
id = ida_simple_get(&watchdog_ida, 1, MAX_DOGS, GFP_KERNEL);
if (id < 0)
return id;
wdd->id = id;
ret = watchdog_dev_register(wdd);
if (ret) {
ida_simple_remove(&watchdog_ida, id);
return ret;
}
}
devno = wdd->cdev.dev;
/* 生成/dev/watchdog0文件 */
wdd->dev = device_create(watchdog_class, wdd->parent, devno,
NULL, "watchdog%d", wdd->id);
if (IS_ERR(wdd->dev)) {
watchdog_dev_unregister(wdd);
ida_simple_remove(&watchdog_ida, id);
ret = PTR_ERR(wdd->dev);
return ret;
}
return 0;
}
从上面的介绍中可以看出,s3c2410wdt_probe函数中最重要的部分是调用watchdog_register_device函数用以注册看门狗。watchdog_register_device函数在drivers/watchdog目录下的Watchdog_core.c文件内。而watchdog_register_device函数最重要的作用是调用watchdog_dev_register函数,它在drivers/watchdog目录下的Watchdog_dev.c文件内,即把看门狗设备注册成一个混杂设备。
看门狗定时器的杂项设备结构为:
static struct miscdevice watchdog_miscdev = {
.minor = WATCHDOG_MINOR,
.name = "watchdog",
.fops = &watchdog_fops,
};
watchdog_fops结构为:
static const struct file_operations watchdog_fops = {
.owner = THIS_MODULE,
.write = watchdog_write,
.unlocked_ioctl = watchdog_ioctl,
.open = watchdog_open,
.release = watchdog_release,
};
在打开看门狗的函数watchdog_open内,调用了watchdog_start函数,而在watchdog_start函数中最终是调用的S3c2410_wdt.c文件中的s3c2410wdt_start函数来实现看门狗的开启。在写看门狗函数函数watchdog_write内,调用了watchdog_ping函数,而在watchdog_ping函数中最终是调用的S3c2410_wdt.c文件中的s3c2410wdt_keepalive函数来实现喂狗。在控制看门狗函数watchdog_ioctl内,实现了对看门狗的不同操作,如写看门狗、获得看门狗的状态、设置看门狗的定时时间等。
下面我们就开启看门狗的功能,并写一段应用程序来实现喂狗以防止系统复位。
开启看门狗,只需要在S3c2410_wdt.c文件内把CONFIG_S3C2410_WATCHDOG_ATBOOT改为1即可,
开启看门狗定时器中断,只需要在S3c2410_wdt.c文件内把CONFIG_S3C2410_WATCHDOG_INTEN改为1即可:
重新编译Linux后,在启动系统的过程中,则会打印下列信息:
这时,如果我们不实现喂狗功能的话,系统就会不断的复位重启。
下面就是实现喂狗的应用程序:
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
int fd;
fd = open("/dev/watchdog",O_RDONLY);
if(fd < 0){
perror("/dev/watchdog");
return -1;
}
for(;;){
ioctl(fd, WDIOC_KEEPALIVE);
sleep(5);
}
close(fd);
return 0;
}
我们把上面程序编译成wdt文件,然后把它复制到根文件系统的bin目录下,再修改根文件系统的etc/init.d/rcS文件,在该文件的最后添加wdt &一句,即实现了在后台喂狗的功能。下面是rcS文件的内容:
#!/bin/sh
#This is the first script called by init process
mount -a
mkdir /dev/pts
mount -t devpts devpts /dev/pts
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s
wdt &
#/usr/sbin/vsftpd &
/usr/local/sbin/sshd
#/usr/local/libexec/sftp-server
/usr/sbin/telnetd &
/usr/sbin/vsftpd &
#cat /root/test/test.img > /dev/fb0
最后再重新编译根文件系统并烧写到开发板上。此时系统虽然开启了看门狗功能,但由于后台不断在喂狗,所以系统仍然可以正常运行。
s3c2410wdt_start()
static int s3c2410wdt_start(struct watchdog_device *wdd)
{
unsigned long wtcon;
spin_lock(&wdt_lock); //避免不多线程同时访问临界资源
__s3c2410wdt_stop(); //先停止看门狗便于设置
wtcon = readl(wdt_base + S3C2410_WTCON);
wtcon |= S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128;
/* 第5位允许看门狗工作 第3、4位设置为11,使用4相分频*/
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); //自旋锁解锁
return 0;
}
s3c2410wdt_set_heartbeat()
static int s3c2410wdt_set_heartbeat(struct watchdog_device *wdd, unsigned timeout)
{
unsigned long freq = clk_get_rate(wdt_clock); //得到看门狗的时钟频率PCLK
unsigned int count; //将填入WTCNT的计数值
unsigned int divisor = 1; //要填入WTCON[15:8]的预分频系数
unsigned long wtcon; //暂存WTCON的值
if (timeout < 1) //看门狗的复位时间不能小于1s
return -EINVAL;
freq /= 128; //看门狗默认使用128的4相分频
count = timeout * freq; //计数值 = 秒*频率(每秒时钟滴答)
DBG("%s: count=%d, timeout=%d, freq=%lu\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) { //最终填入的计数值不能大于WTCNT的范围,
//WTCNT是一个16位寄存器,其最大为0x10000
for (divisor = 1; divisor <= 0x100; divisor++) {//从1到256,寻找一个合适的预分频系数
if ((count / divisor) < 0x10000) //找到则退出
break;
}
if ((count / divisor) >= 0x10000) {//经过预分频和4相分频的计数值仍大于0x10000,
//则复位时间太长,看门狗不支持
dev_err(wdt_dev, "timeout %d too big\n", timeout);
return -EINVAL;
}
}
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; //将WTCNT的高8位清高
wtcon |= S3C2410_WTCON_PRESCALE(divisor-1); //填入预分频系数
writel(count, wdt_base + S3C2410_WTDAT);
writel(wtcon, wdt_base + S3C2410_WTCON);
wdd->timeout = (count * divisor) / freq;
return 0;
}
watchdog_ioctl()
static long watchdog_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct watchdog_device *wdd = file->private_data;
void __user *argp = (void __user *)arg;
int __user *p = argp;
unsigned int val;
int err;
err = watchdog_ioctl_op(wdd, cmd, arg);
if (err != -ENOIOCTLCMD)
return err;
switch (cmd) {
case WDIOC_GETSUPPORT: //获取看门狗设备信息,在watchdog_info结构体中
return copy_to_user(argp, wdd->info,
sizeof(struct watchdog_info)) ? -EFAULT : 0;
case WDIOC_GETSTATUS: //和下面一个,获取看门狗状态,一般返回0
err = watchdog_get_status(wdd, &val);
if (err == -ENODEV)
return err;
return put_user(val, p);
case WDIOC_GETBOOTSTATUS:
return put_user(wdd->bootstatus, p);
case WDIOC_SETOPTIONS:
if (get_user(val, p))
return -EFAULT;
if (val & WDIOS_DISABLECARD) {
err = watchdog_stop(wdd);
if (err < 0)
return err;
}
if (val & WDIOS_ENABLECARD) {
err = watchdog_start(wdd);
if (err < 0)
return err;
}
return 0;
case WDIOC_KEEPALIVE: //对看门狗进行喂狗操作
if (!(wdd->info->options & WDIOF_KEEPALIVEPING))
return -EOPNOTSUPP;
watchdog_ping(wdd);
return 0;
case WDIOC_SETTIMEOUT: //用来设置看门狗的新超时时间,并返回旧超时时间,
//使用get_user()函数从用户空间获得超时时间,并使用watchdog_set_timeout
//函数设置新的超时时间,通过put_user()函数返回旧的超时时间
if (get_user(val, p))
return -EFAULT;
err = watchdog_set_timeout(wdd, val);
if (err < 0)
return err;
/* If the watchdog is active then we send a keepalive ping
* to make sure that the watchdog keep's running (and if
* possible that it takes the new timeout) */
watchdog_ping(wdd);
/* Fall */
case WDIOC_GETTIMEOUT: //获取当前的超时时间
/* timeout == 0 means that we don't know the timeout */
if (wdd->timeout == 0)
return -EOPNOTSUPP;
return put_user(wdd->timeout, p);
case WDIOC_GETTIMELEFT:
err = watchdog_get_timeleft(wdd, &val);
if (err)
return err;
return put_user(val, p);
default:
return -ENOTTY;
}
}