内核驱动 (五)看门狗

尽管在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;
	}
}

 

你可能感兴趣的:(内核驱动)