本文将介绍看门狗驱动的实现。

目标平台:TQ2440

CPU:s3c2440

内核版本:2.6.30

1. 看门狗概述

看门狗其实就是一个定时器,当该定时器溢出前必须对看门狗进行"喂狗“,如果不这样做,定时器溢出后则将复位CPU。

因此,看门狗通常用于对处于异常状态的CPU进行复位。

具体的概念请自行百度。

2. S3C2440看门狗

s3c2440的看门狗的原理框图如下:

可以看出,看门狗定时器的频率由PCLK提供,其预分频器最大取值为255+1;另外,通过MUX,可以进一步降低频率。

定时器采用递减模式,一旦到0,则可以触发看门狗中断以及RESET复位信号。

看门狗定时器的频率的计算公式如下:

3. 看门狗驱动

看门狗驱动代码位于: linux/drivers/char/watchdog/s3c2410_wdt.c

3.1 模块注册以及probe函数

  1. static struct platform_driver s3c2410wdt_driver = {
  2. .probe = s3c2410wdt_probe,
  3. .remove = s3c2410wdt_remove,
  4. .shutdown = s3c2410wdt_shutdown,
  5. .suspend = s3c2410wdt_suspend,
  6. .resume = s3c2410wdt_resume,
  7. .driver = {
  8. .owner = THIS_MODULE,
  9. .name = "s3c2410-wdt",
  10. },
  11. };
  12.  
  13. static char banner[] __initdata =
  14. KERN_INFO "S3C2410 Watchdog Timer, (c) 2004 Simtec Electronics\n";
  15.  
  16. static int __init watchdog_init(void){printk(banner);return platform_driver_register(&s3c2410wdt_driver);}
  17.  
  18. module_init(watchdog_init)

模块的注册函数很简单,直接调用了 platform的驱动注册函数platform_driver_register。

该函数在注册时会调用驱动的probe方法,也即s3c2410wdt_probe函数。

我们来看下这个函数:

  1. static int s3c2410wdt_probe(struct platform_device *pdev)
  2. {
  3. struct resource *res;
  4. struct device *dev;
  5. unsigned int wtcon;
  6. int started = 0;
  7. int ret;
  8. int size;
  9.  
  10. DBG("%s: probe=%p\n", __func__, pdev);
  11.  
  12. dev = &pdev->dev;
  13. wdt_dev = &pdev->dev;
  14.  
  15. /* get the memory region for the watchdog timer */
  16. /*获取平台资源,寄存器地址范围*/
  17. res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
  18. if (res == NULL) {
  19. dev_err(dev, "no memory resource specified\n");
  20. return -ENOENT;
  21. }
  22.  
  23. /*内存申请*/
  24. size = (res->end - res->start) + 1;
  25. wdt_mem = request_mem_region(res->start, size, pdev->name);
  26. if (wdt_mem == NULL) {
  27. dev_err(dev, "failed to get memory region\n");
  28. ret = -ENOENT;
  29. goto err_req;
  30. }
  31.  
  32. /*内存映射*/
  33. wdt_base = ioremap(res->start, size);
  34. if (wdt_base == NULL) {
  35. dev_err(dev, "failed to ioremap() region\n");
  36. ret = -EINVAL;
  37. goto err_req;
  38. }
  39.  
  40. DBG("probe: mapped wdt_base=%p\n", wdt_base);
  41.  
  42. /*获取平台资源,看门狗定时器中断号*/
  43. wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
  44. if (wdt_irq == NULL) {
  45. dev_err(dev, "no irq resource specified\n");
  46. ret = -ENOENT;
  47. goto err_map;
  48. }
  49.  
  50. /*注册看门狗定时器中断*/
  51. ret = request_irq(wdt_irq->start, s3c2410wdt_irq, 0, pdev->name, pdev);
  52. if (ret != 0) {
  53. dev_err(dev, "failed to install irq (%d)\n", ret);
  54. goto err_map;
  55. }
  56. /*获取看门狗模块的时钟*/
  57. wdt_clock = clk_get(&pdev->dev, "watchdog");
  58. if (IS_ERR(wdt_clock)) {
  59. dev_err(dev, "failed to find watchdog clock source\n");
  60. ret = PTR_ERR(wdt_clock);
  61. goto err_irq;
  62. }
  63.  
  64. /*使能该时钟*/
  65. clk_enable(wdt_clock);
  66.  
  67. /* see if we can actually set the requested timer margin, and if
  68. * not, try the default value */
  69.  
  70. /*设置定时器模块的时钟频率*/
  71. if (s3c2410wdt_set_heartbeat(tmr_margin)) {
  72. started = s3c2410wdt_set_heartbeat(
  73. CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);
  74.  
  75. if (started == 0)
  76. dev_info(dev,
  77. "tmr_margin value out of range, default %d used\n",
  78. CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);
  79. else
  80. dev_info(dev, "default timer value is out of range, cannot start\n");
  81. }
  82.  
  83. /*注册混杂设备,设备名watchdog,次设备号130*/
  84. ret = misc_register(&s3c2410wdt_miscdev);
  85. if (ret) {
  86. dev_err(dev, "cannot register miscdev on minor=%d (%d)\n",
  87. WATCHDOG_MINOR, ret);
  88. goto err_clk;
  89. }
  90.  
  91. /*
  92. *如果需要在看门狗模块加载时启动看门狗则
  93. *调用s3c2410wdt_start,否则调用s3c2410wdt_stop
  94. */
  95.  
  96. if (tmr_atboot && started == 0) {
  97. dev_info(dev, "starting watchdog timer\n");
  98. s3c2410wdt_start();
  99. } else if (!tmr_atboot) {
  100. /* if we're not enabling the watchdog, then ensure it is
  101. * disabled if it has been left running from the bootloader
  102. * or other source */
  103.  
  104. s3c2410wdt_stop();
  105. }
  106.  
  107. /* print out a statement of readiness */
  108. /*读取控制寄存器,打印目前看门狗的状态*/
  109. wtcon = readl(wdt_base + S3C2410_WTCON);
  110.  
  111. dev_info(dev, "watchdog %sactive, reset %sabled, irq %sabled\n",
  112. (wtcon & S3C2410_WTCON_ENABLE) ? "" : "in",
  113. (wtcon & S3C2410_WTCON_RSTEN) ? "" : "dis",
  114. (wtcon & S3C2410_WTCON_INTEN) ? "" : "en");
  115.  
  116. return 0;
  117.  
  118. err_clk:
  119. clk_disable(wdt_clock);
  120. clk_put(wdt_clock);
  121.  
  122. err_irq:
  123. free_irq(wdt_irq->start, pdev);
  124.  
  125. err_map:
  126. iounmap(wdt_base);
  127.  
  128. err_req:
  129. release_resource(wdt_mem);
  130. kfree(wdt_mem);
  131.  
  132. return ret;
  133. }

该函数的前面几步和其他驱动的类似:获取平台资源后进行相应的注册,并使能时钟。接着,将调用s3c2410wdt_set_heartbeat函数来设置看门狗的工作频率。

然后,注册一个混杂设备,为看门狗注册相应的API到内核中。最后,判断是否需要启动看门狗并调用相应的函数。

上面是probe函数大致的执行过程。随后我们看下其中被调用的s3c2410wdt_set_heartbeat函数,该函数将设置看门狗的工作频率。

PS:probe函数的执行依赖于平台设备,而看门狗平台设备的添加和注册在linux/arch/arm/plat-s3c24xx/devs.c和 linux/arch/arm/mach-s3c2410/mach-smdk2410.c中已经完成,因此对于看门狗驱动无需进行移植。

3.2 s3c2410wdt_set_heartbeat

  1. static int s3c2410wdt_set_heartbeat(int timeout) /*timeout 超时时间,单位秒*/
  2. {
  3. unsigned int freq = clk_get_rate(wdt_clock);
  4. unsigned int count;
  5. unsigned int divisor = 1;
  6. unsigned long wtcon;
  7.  
  8. if (timeout < 1)
  9. return -EINVAL;
  10.  
  11. freq /= 128; /*时钟源为PCLK/128,不使用16 32 和64*/
  12. count = timeout * freq; /*得出计数器值*/
  13.  
  14. DBG("%s: count=%d, timeout=%d, freq=%d\n",
  15. __func__, count, timeout, freq);
  16.  
  17. /* if the count is bigger than the watchdog register,
  18. then work out what we need to do (and if) we can
  19. actually make this value
  20. */
  21. /*计数器最大值为0xFFFF,如果大于则要计算Prescaler value*/
  22. if (count >= 0x10000) {
  23. for (divisor = 1; divisor <= 0x100; divisor++) { /*Prescaler value最大为0xff*/
  24. if ((count / divisor) < 0x10000)
  25. break;
  26. }
  27. /*找不到合适的Prescaler value,报错返回*/
  28. if ((count / divisor) >= 0x10000) {
  29. dev_err(wdt_dev, "timeout %d too big\n", timeout);
  30. return -EINVAL;
  31. }
  32. }
  33.  
  34. tmr_margin = timeout; /*保存timeout*/
  35.  
  36. DBG("%s: timeout=%d, divisor=%d, count=%d (%08x)\n",
  37. __func__, timeout, divisor, count, count/divisor);
  38.  
  39. count /= divisor; /*根据Prescaler value计算出新的计数器值*/
  40. wdt_count = count; /*保存计数器值*/
  41.  
  42. /* update the pre-scaler */
  43. /*
  44. *设置预分频计数器和数据寄存器
  45. * NOTE:此时并未使能看门狗定时器
  46. */
  47. wtcon = readl(wdt_base + S3C2410_WTCON);
  48. wtcon &= ~S3C2410_WTCON_PRESCALE_MASK;
  49. wtcon |= S3C2410_WTCON_PRESCALE(divisor-1);
  50.  
  51. writel(count, wdt_base + S3C2410_WTDAT);
  52. writel(wtcon, wdt_base + S3C2410_WTCON);
  53.  
  54. return 0;
  55. }

形参timeout为定时时间,单位为秒。

这里唯一需要注意的是freq/= 128这一步。在第2节我们看到,通过MUX,可选择的分频系数为16,32,64和128,但是在这里驱动直接使用了128来计算系数。

在下一节我们将会看到,驱动为什么在这里只使用了128这个分频系数。

当该函数调用结束时,Prescalervalue 和计数器值都将计算完成,并写入寄存器。

3.3 定时器的启动、停止和保活

3.3.1 停止

定时器的停止由 s3c2410wdt_stop函数完成。

  1. static void __s3c2410wdt_stop(void)
  2. {
  3. unsigned long wtcon;
  4.  
  5. wtcon = readl(wdt_base + S3C2410_WTCON);
  6. /*禁止看门狗,禁止RESET*/
  7. wtcon &= ~(S3C2410_WTCON_ENABLE | S3C2410_WTCON_RSTEN);
  8. writel(wtcon, wdt_base + S3C2410_WTCON);
  9. }
  10.  
  11. static void s3c2410wdt_stop(void)
  12. {
  13. spin_lock(&wdt_lock);
  14. __s3c2410wdt_stop();
  15. spin_unlock(&wdt_lock);
  16. }

3.3.2 启动

定时器的启动由s3c2410wdt_start函数完成。

  1. static void s3c2410wdt_start(void)
  2. {
  3. unsigned long wtcon;
  4.  
  5. spin_lock(&wdt_lock);
  6.  
  7. __s3c2410wdt_stop(); ./*先禁止看门狗*/
  8.  
  9. wtcon = readl(wdt_base + S3C2410_WTCON); /*读取控制寄存器*/
  10. /*启动定时器,设置分频系数为128*/
  11. wtcon |= S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128;
  12.  
  13. if (soft_noboot) { /*判断许是否需要RESET*/
  14. wtcon |= S3C2410_WTCON_INTEN; /*使能看门狗中断*/
  15. wtcon &= ~S3C2410_WTCON_RSTEN; /*取消RESET*/
  16. } else { /*复位*/
  17. wtcon &= ~S3C2410_WTCON_INTEN; /*禁止看门狗中断*/
  18. wtcon |= S3C2410_WTCON_RSTEN; /*设置RESET*/
  19. }
  20.  
  21. DBG("%s: wdt_count=0x%08x, wtcon=%08lx\n",
  22. __func__, wdt_count, wtcon);
  23.  
  24. writel(wdt_count, wdt_base + S3C2410_WTDAT);
  25. writel(wdt_count, wdt_base + S3C2410_WTCNT);
  26. /*写入控制器,此时将启动看门狗定时器*/
  27. writel(wtcon, wdt_base + S3C2410_WTCON);
  28. spin_unlock(&wdt_lock);
  29. }

在这里我们到了一个宏S3C2410_WTCON_DIV128,这里设置了分频系数为128。而s3c2410wdt_start函数的调用肯定在s3c2410wdt_set_heartbeat之后,这也就是为什么在3.2节中使用了freq/= 128这一步。

3.3.3 保活

定时器的保活由s3c2410wdt_keepalive函数完成。

  1. static void s3c2410wdt_keepalive(void)
  2. {
  3. spin_lock(&wdt_lock);
  4. writel(wdt_count, wdt_base + S3C2410_WTCNT); /*重置计数器值*/
  5. spin_unlock(&wdt_lock);
  6. }

最后需要说明的是,从3.1节probe函数的执行来看,由于tmr_atboot变量的初值为0,因此看门狗定时器是没有工作的。

 

3.4 看门狗驱动API

看门狗驱动提供的API如下:

  1. static const struct file_operations s3c2410wdt_fops = {
  2. .owner = THIS_MODULE,
  3. .llseek = no_llseek,
  4. .write = s3c2410wdt_write,
  5. .unlocked_ioctl = s3c2410wdt_ioctl,
  6. .open = s3c2410wdt_open,
  7. .release = s3c2410wdt_release,
  8. };

我们可以看到驱动提供了4个API,同时,驱动并不支持llseek方法。

3.4.1 open方法

  1. static int s3c2410wdt_open(struct inode *inode, struct file *file)
  2. {
  3. if (test_and_set_bit(0, &open_lock))/*看门狗设备文件只能open一次*/
  4. return -EBUSY;
  5.  
  6. if (nowayout)
  7. __module_get(THIS_MODULE); /*增加模块引用计数*/
  8.  
  9. allow_close = CLOSE_STATE_NOT; /*设置标志位,不允许关闭看门狗*/
  10.  
  11. /* start the timer */
  12. s3c2410wdt_start(); /*启动定时器*/
  13. return nonseekable_open(inode, file); /*告知内核不支持llseek操作*/
  14. }

这里需要注意的是,设备文件/dev/watchdog 只能被open一次,大于一次的open都将返回-EBUSY。

3.4.2 release方法

  1. static int s3c2410wdt_release(struct inode *inode, struct file *file)
  2. {
  3. /*
  4. * Shut off the timer.
  5. * Lock it in if it's a module and we set nowayout
  6. */
  7. /*状态是允许关闭看门狗,则停止看门狗,否则保活*/
  8. if (allow_close == CLOSE_STATE_ALLOW)
  9. s3c2410wdt_stop();
  10. else {
  11. dev_err(wdt_dev, "Unexpected close, not stopping watchdog\n");
  12. s3c2410wdt_keepalive();
  13. }
  14. allow_close = CLOSE_STATE_NOT; /*设置标志位,不允许关闭看门狗*/
  15. clear_bit(0, &open_lock);
  16. return 0;
  17. }

3.4.3 wirte方法

  1. static ssize_t s3c2410wdt_write(struct file *file, const char __user *data,
  2. size_t len, loff_t *ppos)
  3. {
  4. /*
  5. * Refresh the timer.
  6. */
  7. if (len) {
  8. /*nowayout 为真,不允许看门狗停止,使其保活*/
  9. if (!nowayout) {
  10. size_t i;
  11.  
  12. /* In case it was set long ago */
  13. allow_close = CLOSE_STATE_NOT;/*设置标志位,不允许关闭看门狗*/
  14.  
  15. for (i = 0; i != len; i++) {
  16. char c;
  17.  
  18. if (get_user(c, data + i)) /*从用户空间获取一个字节的数据*/
  19. return -EFAULT;
  20. /*读取到字符V,设置标志位,允许关闭看门狗*/
  21. if (c == 'V')
  22. allow_close = CLOSE_STATE_ALLOW;
  23. }
  24. }
  25. s3c2410wdt_keepalive(); /*保活*/
  26. }
  27. return len;
  28. }

只要写入数据的长度不为0,都会调用s3c2410wdt_keepalive函数来重置定时器。

3.4.4 unlocked_ioctl方法

  1. static long s3c2410wdt_ioctl(struct file *file, unsigned int cmd,
  2. unsigned long arg)
  3. {
  4. void __user *argp = (void __user *)arg;
  5. int __user *p = argp;
  6. int new_margin;
  7.  
  8. switch (cmd) {
  9. case WDIOC_GETSUPPORT:
  10. return copy_to_user(argp, &s3c2410_wdt_ident,
  11. sizeof(s3c2410_wdt_ident)) ? -EFAULT : 0;
  12. case WDIOC_GETSTATUS:
  13. case WDIOC_GETBOOTSTATUS:
  14. return put_user(0, p);
  15. case WDIOC_KEEPALIVE:
  16. s3c2410wdt_keepalive();
  17. return 0;
  18. case WDIOC_SETTIMEOUT:
  19. if (get_user(new_margin, p))
  20. return -EFAULT;
  21. if (s3c2410wdt_set_heartbeat(new_margin))
  22. return -EINVAL;
  23. s3c2410wdt_keepalive();
  24. return put_user(tmr_margin, p);
  25. case WDIOC_GETTIMEOUT:
  26. return put_user(tmr_margin, p);
  27. default:
  28. return -ENOTTY;
  29. }
  30. }

4. 测试程序

  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <linux/watchdog.h>
  4. #include <fcntl.h>
  5.  
  6. int main(void)
  7. {
  8. int fd, val, ret;
  9.  
  10. fd = open("/dev/watchdog", O_RDWR);
  11. if(fd < 0){
  12. printf("open device fail\n");
  13. return -1;
  14. }
  15.  
  16. while(1){
  17. ret = write(fd, &val, sizeof(val));
  18. if(ret < 0){
  19. perror("watchdog write wrong\n");
  20. return -1;
  21. }
  22. sleep(5);
  23. }
  24.  
  25. return 0;
  26. }

该测试程序每隔5秒重置看门狗定时器,而驱动默认的超时时间是15秒。

可以将5秒替换为16秒,你会发现系统自动重启了。

5. 结束语

本文主要对基于S3C2440的看门狗驱动作出了分析。该驱动只是一个简单的字符设备,比较简单。其中,用于计算预分频系数的s3c2410wdt_set_heartbeat函数比较关键,读者可以好好琢磨下该系数是如何计算出来的。

Thank you for your time。

2013.1.30 添加测试程序

基于S3C2440的嵌入式Linux驱动——看门狗(watchdog)驱动解读的更多相关文章

  1. 【分享】iTOP-iMX6UL开发板驱动看门狗 watchdog 以及 Linux-c 测试例程

    iTOP-iMX6UL开发板看门狗测试例程,iTOP-iMX6UL 开发板的看门狗驱动默认已经配置,可以直接使用测试例程. 版本 V1.1:1.格式修改:2.例程修改完善,其中增加喂狗代码.1 看门狗 ...

  2. Linux 软件看门狗 watchdog 喂狗

    Linux 自带了一个 watchdog 的实现,用于监视系统的运行,包括一个内核 watchdog module 和一个用户空间的 watchdog程序.内核 watchdog 模块通过 /dev/ ...

  3. CH02基于ZYNQ的嵌入式LINUX移植

    CH02基于ZYNQ的嵌入式LINUX移植 1.1概述 实验环境: Windows 10 专业版 Vmware workstation 14.1.1 Ubuntu 16.04.3 Xilinx SDx ...

  4. 7、基于嵌入式Linux的视频采集系统---UVC驱动模型介绍

    UVC 即 usb video class.USB协议中,除了通用的软硬件电气接口规范等,还包含了各种各样的Class协议,用来为不同的功能定义各自的标准接口和具体的总线上的数据交互格式和内容.这些C ...

  5. 基于ARM9和嵌入式Linux系统的多功能综合通信控制系统的框架

    基于ARM9硬件平台和嵌入式Linux系统的多功能综合通信控制系统的框架设计及各模块的功能.系统采用符合POSIX.1标准的C语言编写,实现了对下位机传送数据帧的采集.分析和存储,并能根据上位机的配置 ...

  6. 基于bootsplash的嵌入式linux启动画面定制

    来源: ChinaUnix博客 作者: ChinaUnix博客 发布时间:2007-01-01 16:29:00 摘 要:在基于linux的嵌入式仿真平台研发中,利用开源工具bootsplash能够定 ...

  7. 基于live555开发嵌入式linux系统的rtsp直播服务

    最近要搞一个直播服务,车机本身是个前后双路的Dvr,前路1080P 25fps,后路720P 50fps,现在要连接手机app预览实时画面,且支持前后摄像头画面切换. 如果要做直播,这个分辨率和帧率是 ...

  8. linux设备驱动归纳总结(十一):写个简单的看门狗驱动【转】

    本文转载自:http://blog.chinaunix.net/uid-25014876-id-112879.html linux设备驱动归纳总结(十一):写个简单的看门狗驱动 xxxxxxxxxxx ...

  9. 【Linux开发】linux设备驱动归纳总结(十一):写个简单的看门狗驱动

    linux设备驱动归纳总结(十一):写个简单的看门狗驱动 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...

随机推荐

  1. ArduinoYun教程之通过网络为Arduino Yun编程

    ArduinoYun教程之通过网络为Arduino Yun编程 Arduino Yun的软件部分 通过第一章的介绍后读者就明白了Arduino Yun除了是一个类似其他Arduino的单片机之外,它的 ...

  2. StringBuilder的实现与技巧ZZ

      在上一篇进一步了解String 中,发现了string的不便之处,而string的替代解决方案就是StringBuilder的使用..它的使用也很简单System.Text.StringBuild ...

  3. leetcode 两数之和 python

      两数之和     给定一个整数数组和一个目标值,找出数组中和为目标值的两个数. 你可以假设每个输入只对应一种答案,且同样的元素不能被重复利用. 示例: 给定 nums = [2, 7, 11, 1 ...

  4. poj 3071 概率dp

    转自:cxlove 题目:有2^n个队,相邻的两两打淘汰赛,,求最后哪个队夺冠的概率最大 dp[i][j]表示第i轮的时候,第j去支队伍赢的概率. 那么dp[i][j]的前提就是i-1轮的时候,j是赢 ...

  5. spring boot 集成 druid

    写在前面 因为在用到spring boot + mybatis的项目时候,经常发生访问接口卡,服务器项目用了几天就很卡的甚至不能访问的情况,而我们的项目和数据库都是好了,考虑到可能是数据库连接的问题, ...

  6. windows servier2008+virtualenv下部署Flask (IIS+wfastcgi)

    由于业务只有一台windows server2008的服务器,一般的nginx+uwsgi的部署方式不行,以下记录部署过程,本文参考自:https://www.cnblogs.com/xiaolecn ...

  7. HDU 3979 Monster (贪心排序)

    Monster Time Limit: 10000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Su ...

  8. UIScrollView 遇到的小坑

    在做一个 UIScrollView  展示的时候 ,须要计算 contentSize 的高度,于是 我遍历了一下 UIScrollView 全部的子view的高度累加 然后得出 高度 .奇怪的是 发现 ...

  9. Booting LPC-Link2, Updating LPCXpresso firmware

    Booting LPC-Link2 The recommended way to use LPC-Link2 with the LPCXpresso IDE is to boot and soft l ...

  10. External Input Counter and External interrupt

    External Input Counter and External interrupt : count the input signal from the button. So what is t ...