• 基于S3C2440的嵌入式Linux驱动——看门狗(watchdog)驱动解读


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

    目标平台: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 ";
    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 ", __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 ");
    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 ");
    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 ");
    36. ret = -EINVAL;
    37. goto err_req;
    38. }
    39.  
    40. DBG("probe: mapped wdt_base=%p ", 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 ");
    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) ", 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 ");
    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 ",
    78. CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);
    79. else
    80. dev_info(dev, "default timer value is out of range, cannot start ");
    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) ",
    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 ");
    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 ",
    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 ",
    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 ", timeout);
    30. return -EINVAL;
    31. }
    32. }
    33.  
    34. tmr_margin = timeout; /*保存timeout*/
    35.  
    36. DBG("%s: timeout=%d, divisor=%d, count=%d (%08x) ",
    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 ",
    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 ");
    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 ");
    13. return -1;
    14. }
    15.  
    16. while(1){
    17. ret = write(fd, &val, sizeof(val));
    18. if(ret < 0){
    19. perror("watchdog write wrong ");
    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 添加测试程序

  • 相关阅读:
    HDU-4027-Can you answer these queries?
    Python的多协程(三种简单生成多协程方法)
    关于django 如何实现简单api的restful 接口
    flask 框架服务原理
    DVWA渗透测试环境搭建
    装饰器 python 你也可以叫语法糖
    websocket python实现原理
    robotframe 自定义开发库
    mysql linux 安装卸载
    python+jenkins 构建节点环境编译器配置问题
  • 原文地址:https://www.cnblogs.com/wanghuaijun/p/9461671.html
Copyright © 2020-2023  润新知