1. 程式人生 > >基於S3C2440的嵌入式Linux驅動——看門狗(watchdog)驅動解讀

基於S3C2440的嵌入式Linux驅動——看門狗(watchdog)驅動解讀

csdn note 動作 strong 申請 信號 error started 讀者

本文將介紹看門狗驅動的實現。

目標平臺: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. static char banner[] __initdata =
  13. KERN_INFO "S3C2410 Watchdog Timer, (c) 2004 Simtec Electronics\n";
  14. static int __init watchdog_init(void){printk(banner);return platform_driver_register(&s3c2410wdt_driver);}
  15. 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. DBG("%s: probe=%p\n", __func__, pdev);
  10. dev = &pdev->dev;
  11. wdt_dev = &pdev->dev;
  12. /* get the memory region for the watchdog timer */
  13. /*獲取平臺資源,寄存器地址範圍*/
  14. res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
  15. if (res == NULL) {
  16. dev_err(dev, "no memory resource specified\n");
  17. return -ENOENT;
  18. }
  19. /*內存申請*/
  20. size = (res->end - res->start) + 1;
  21. wdt_mem = request_mem_region(res->start, size, pdev->name);
  22. if (wdt_mem == NULL) {
  23. dev_err(dev, "failed to get memory region\n");
  24. ret = -ENOENT;
  25. goto err_req;
  26. }
  27. /*內存映射*/
  28. wdt_base = ioremap(res->start, size);
  29. if (wdt_base == NULL) {
  30. dev_err(dev, "failed to ioremap() region\n");
  31. ret = -EINVAL;
  32. goto err_req;
  33. }
  34. DBG("probe: mapped wdt_base=%p\n", wdt_base);
  35. /*獲取平臺資源,看門狗定時器中斷號*/
  36. wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
  37. if (wdt_irq == NULL) {
  38. dev_err(dev, "no irq resource specified\n");
  39. ret = -ENOENT;
  40. goto err_map;
  41. }
  42. /*註冊看門狗定時器中斷*/
  43. ret = request_irq(wdt_irq->start, s3c2410wdt_irq, 0, pdev->name, pdev);
  44. if (ret != 0) {
  45. dev_err(dev, "failed to install irq (%d)\n", ret);
  46. goto err_map;
  47. }
  48. /*獲取看門狗模塊的時鐘*/
  49. wdt_clock = clk_get(&pdev->dev, "watchdog");
  50. if (IS_ERR(wdt_clock)) {
  51. dev_err(dev, "failed to find watchdog clock source\n");
  52. ret = PTR_ERR(wdt_clock);
  53. goto err_irq;
  54. }
  55. /*使能該時鐘*/
  56. clk_enable(wdt_clock);
  57. /* see if we can actually set the requested timer margin, and if
  58. * not, try the default value */
  59. /*設置定時器模塊的時鐘頻率*/
  60. if (s3c2410wdt_set_heartbeat(tmr_margin)) {
  61. started = s3c2410wdt_set_heartbeat(
  62. CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);
  63. if (started == 0)
  64. dev_info(dev,
  65. "tmr_margin value out of range, default %d used\n",
  66. CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);
  67. else
  68. dev_info(dev, "default timer value is out of range, cannot start\n");
  69. }
  70. /*註冊混雜設備,設備名watchdog,次設備號130*/
  71. ret = misc_register(&s3c2410wdt_miscdev);
  72. if (ret) {
  73. dev_err(dev, "cannot register miscdev on minor=%d (%d)\n",
  74. WATCHDOG_MINOR, ret);
  75. goto err_clk;
  76. }
  77. /*
  78. *如果需要在看門狗模塊加載時啟動看門狗則
  79. *調用s3c2410wdt_start,否則調用s3c2410wdt_stop
  80. */
  81. if (tmr_atboot && started == 0) {
  82. dev_info(dev, "starting watchdog timer\n");
  83. s3c2410wdt_start();
  84. } else if (!tmr_atboot) {
  85. /* if we‘re not enabling the watchdog, then ensure it is
  86. * disabled if it has been left running from the bootloader
  87. * or other source */
  88. s3c2410wdt_stop();
  89. }
  90. /* print out a statement of readiness */
  91. /*讀取控制寄存器,打印目前看門狗的狀態*/
  92. wtcon = readl(wdt_base + S3C2410_WTCON);
  93. dev_info(dev, "watchdog %sactive, reset %sabled, irq %sabled\n",
  94. (wtcon & S3C2410_WTCON_ENABLE) ? "" : "in",
  95. (wtcon & S3C2410_WTCON_RSTEN) ? "" : "dis",
  96. (wtcon & S3C2410_WTCON_INTEN) ? "" : "en");
  97. return 0;
  98. err_clk:
  99. clk_disable(wdt_clock);
  100. clk_put(wdt_clock);
  101. err_irq:
  102. free_irq(wdt_irq->start, pdev);
  103. err_map:
  104. iounmap(wdt_base);
  105. err_req:
  106. release_resource(wdt_mem);
  107. kfree(wdt_mem);
  108. return ret;
  109. }

該函數的前面幾步和其他驅動的類似:獲取平臺資源後進行相應的註冊,並使能時鐘。接著,將調用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. if (timeout < 1)
  8. return -EINVAL;
  9. freq /= 128; /*時鐘源為PCLK/128,不使用16 32 和64*/
  10. count = timeout * freq; /*得出計數器值*/
  11. DBG("%s: count=%d, timeout=%d, freq=%d\n",
  12. __func__, count, timeout, freq);
  13. /* if the count is bigger than the watchdog register,
  14. then work out what we need to do (and if) we can
  15. actually make this value
  16. */
  17. /*計數器最大值為0xFFFF,如果大於則要計算Prescaler value*/
  18. if (count >= 0x10000) {
  19. for (divisor = 1; divisor <= 0x100; divisor++) { /*Prescaler value最大為0xff*/
  20. if ((count / divisor) < 0x10000)
  21. break;
  22. }
  23. /*找不到合適的Prescaler value,報錯返回*/
  24. if ((count / divisor) >= 0x10000) {
  25. dev_err(wdt_dev, "timeout %d too big\n", timeout);
  26. return -EINVAL;
  27. }
  28. }
  29. tmr_margin = timeout; /*保存timeout*/
  30. DBG("%s: timeout=%d, divisor=%d, count=%d (%08x)\n",
  31. __func__, timeout, divisor, count, count/divisor);
  32. count /= divisor; /*根據Prescaler value計算出新的計數器值*/
  33. wdt_count = count; /*保存計數器值*/
  34. /* update the pre-scaler */
  35. /*
  36. *設置預分頻計數器和數據寄存器
  37. * NOTE:此時並未使能看門狗定時器
  38. */
  39. wtcon = readl(wdt_base + S3C2410_WTCON);
  40. wtcon &= ~S3C2410_WTCON_PRESCALE_MASK;
  41. wtcon |= S3C2410_WTCON_PRESCALE(divisor-1);
  42. writel(count, wdt_base + S3C2410_WTDAT);
  43. writel(wtcon, wdt_base + S3C2410_WTCON);
  44. return 0;
  45. }

形參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. wtcon = readl(wdt_base + S3C2410_WTCON);
  5. /*禁止看門狗,禁止RESET*/
  6. wtcon &= ~(S3C2410_WTCON_ENABLE | S3C2410_WTCON_RSTEN);
  7. writel(wtcon, wdt_base + S3C2410_WTCON);
  8. }
  9. static void s3c2410wdt_stop(void)
  10. {
  11. spin_lock(&wdt_lock);
  12. __s3c2410wdt_stop();
  13. spin_unlock(&wdt_lock);
  14. }

3.3.2 啟動

定時器的啟動由s3c2410wdt_start函數完成。

  1. static void s3c2410wdt_start(void)
  2. {
  3. unsigned long wtcon;
  4. spin_lock(&wdt_lock);
  5. __s3c2410wdt_stop(); ./*先禁止看門狗*/
  6. wtcon = readl(wdt_base + S3C2410_WTCON); /*讀取控制寄存器*/
  7. /*啟動定時器,設置分頻系數為128*/
  8. wtcon |= S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128;
  9. if (soft_noboot) { /*判斷許是否需要RESET*/
  10. wtcon |= S3C2410_WTCON_INTEN; /*使能看門狗中斷*/
  11. wtcon &= ~S3C2410_WTCON_RSTEN; /*取消RESET*/
  12. } else { /*復位*/
  13. wtcon &= ~S3C2410_WTCON_INTEN; /*禁止看門狗中斷*/
  14. wtcon |= S3C2410_WTCON_RSTEN; /*設置RESET*/
  15. }
  16. DBG("%s: wdt_count=0x%08x, wtcon=%08lx\n",
  17. __func__, wdt_count, wtcon);
  18. writel(wdt_count, wdt_base + S3C2410_WTDAT);
  19. writel(wdt_count, wdt_base + S3C2410_WTCNT);
  20. /*寫入控制器,此時將啟動看門狗定時器*/
  21. writel(wtcon, wdt_base + S3C2410_WTCON);
  22. spin_unlock(&wdt_lock);
  23. }

在這裏我們到了一個宏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. if (nowayout)
  6. __module_get(THIS_MODULE); /*增加模塊引用計數*/
  7. allow_close = CLOSE_STATE_NOT; /*設置標誌位,不允許關閉看門狗*/
  8. /* start the timer */
  9. s3c2410wdt_start(); /*啟動定時器*/
  10. return nonseekable_open(inode, file); /*告知內核不支持llseek操作*/
  11. }


這裏需要註意的是,設備文件/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. /* In case it was set long ago */
  12. allow_close = CLOSE_STATE_NOT;/*設置標誌位,不允許關閉看門狗*/
  13. for (i = 0; i != len; i++) {
  14. char c;
  15. if (get_user(c, data + i)) /*從用戶空間獲取一個字節的數據*/
  16. return -EFAULT;
  17. /*讀取到字符V,設置標誌位,允許關閉看門狗*/
  18. if (c == ‘V‘)
  19. allow_close = CLOSE_STATE_ALLOW;
  20. }
  21. }
  22. s3c2410wdt_keepalive(); /*保活*/
  23. }
  24. return len;
  25. }

只要寫入數據的長度不為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. switch (cmd) {
  8. case WDIOC_GETSUPPORT:
  9. return copy_to_user(argp, &s3c2410_wdt_ident,
  10. sizeof(s3c2410_wdt_ident)) ? -EFAULT : 0;
  11. case WDIOC_GETSTATUS:
  12. case WDIOC_GETBOOTSTATUS:
  13. return put_user(0, p);
  14. case WDIOC_KEEPALIVE:
  15. s3c2410wdt_keepalive();
  16. return 0;
  17. case WDIOC_SETTIMEOUT:
  18. if (get_user(new_margin, p))
  19. return -EFAULT;
  20. if (s3c2410wdt_set_heartbeat(new_margin))
  21. return -EINVAL;
  22. s3c2410wdt_keepalive();
  23. return put_user(tmr_margin, p);
  24. case WDIOC_GETTIMEOUT:
  25. return put_user(tmr_margin, p);
  26. default:
  27. return -ENOTTY;
  28. }
  29. }

4. 測試程序

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


該測試程序每隔5秒重置看門狗定時器,而驅動默認的超時時間是15秒。

可以將5秒替換為16秒,你會發現系統自動重啟了。

5. 結束語

本文主要對基於S3C2440的看門狗驅動作出了分析。該驅動只是一個簡單的字符設備,比較簡單。其中,用於計算預分頻系數的s3c2410wdt_set_heartbeat函數比較關鍵,讀者可以好好琢磨下該系數是如何計算出來的。

Thank you for your time。

2013.1.30 添加測試程序

基於S3C2440的嵌入式Linux驅動——看門狗(watchdog)驅動解讀