ARM-Linux驱动--Watch Dog Timer(看门狗)驱动分析

 

1、看门狗驱动的原理

下图是看门狗驱动的原理图

ARM-Linux驱动--Watch Dog Timer(看门狗)驱动分析_第1张图片

可以看出,PCLK是系统时钟,经过8位的预分频,然后再被分频(16、32、64、128)然后产生计数脉冲,进行计数,当计数器WTCNT加到0或减到0,然后产生中断,或引起系统复位。所以要隔一段时间,重置WTCNT的值,防止WTCNT减到0,称之“喂狗”。

2、驱动分析

下面是自己的驱动分析,如有理解错误,请指正

注,为了尽量是驱动容易理解,这个驱动暂时将有关电源管理的功能删除了,等理解透彻再完善

view plain copy to clipboard print ?
  1. #include <linux/module.h>   
  2. #include <linux/moduleparam.h>   
  3. #include <linux/types.h>   
  4. #include <linux/timer.h>   
  5. #include <linux/miscdevice.h>   
  6. #include <linux/watchdog.h>   
  7. #include <linux/fs.h>   
  8. #include <linux/init.h>   
  9. #include <linux/platform_device.h>   
  10. #include <linux/interrupt.h>   
  11. #include <linux/clk.h>   
  12. #include <linux/uaccess.h>   
  13. #include <linux/io.h>   
  14.   
  15. #include <mach/regs-gpio.h>   
  16.   
  17. #include <mach/map.h>   
  18.   
  19. #undef S3C_VA_WATCHDOG   
  20. #define S3C_VA_WATCHDOG (0)   
  21.   
  22. #include <asm/plat-s3c/regs-watchdog.h>   
  23.   
  24. #define PFX "s3c2410-wdt: "   
  25.   
  26. #define CONFIG_S3C2410_WATCHDOG_ATBOOT      (0)   
  27. #define CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME    (15)   
  28.   
  29. static int nowayout = WATCHDOG_NOWAYOUT;  
  30. static int tmr_margin   = CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME;  
  31. static int tmr_atboot   = CONFIG_S3C2410_WATCHDOG_ATBOOT;  
  32. static int soft_noboot = 1; //设置默认为执行中断   
  33. static int debug;  
  34. static int count;//用于计数,控制LED灯的亮灭   
  35.   
  36. module_param(tmr_margin,  int, 0);  
  37. module_param(tmr_atboot,  int, 0);  
  38. module_param(nowayout,    int, 0);  
  39. module_param(soft_noboot, int, 0);  
  40. module_param(debug,   int, 0);  
  41.   
  42. MODULE_PARM_DESC(tmr_margin, "Watchdog tmr_margin in seconds. default="  
  43.         __MODULE_STRING(CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME) ")");  
  44. MODULE_PARM_DESC(tmr_atboot,  
  45.         "Watchdog is started at boot time if set to 1, default="  
  46.             __MODULE_STRING(CONFIG_S3C2410_WATCHDOG_ATBOOT));  
  47. MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="  
  48.             __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");  
  49. MODULE_PARM_DESC(soft_noboot, "Watchdog action, set to 1 to ignore reboots, 0 to \  
  50.                                             reboot (default depends on ONLY_TESTING)");  
  51. MODULE_PARM_DESC(debug, "Watchdog debug, set to >1 for debug, (default 0)");  
  52.   
  53.   
  54. typedef enum close_state   
  55. {  
  56.     CLOSE_STATE_NOT,  
  57.     CLOSE_STATE_ALLOW = 0x4021  
  58. } close_state_t;  
  59.   
  60. static unsigned long open_lock;  
  61. static struct device    *wdt_dev;   /* platform device attached to */  
  62. static struct resource  *wdt_mem;//用来保存IO端口占用的内存资源   
  63. static struct resource  *wdt_irq;//保存wdt中断号   
  64. static struct clk   *wdt_clock;//保存从平台获取的watchdog时钟   
  65. static void __iomem *wdt_base;//经过ioremap后的内存基地址   
  66. static unsigned int  wdt_count;//保存向WTCNT写的计数值   
  67. static close_state_t     allow_close;  
  68. static DEFINE_SPINLOCK(wdt_lock);//定义一个自旋锁,用于资源的互斥访问   
  69.   
  70. /* watchdog control routines */  
  71.   
  72. #define DBG(msg...) do { \   
  73.     if (debug) \  
  74.         printk(KERN_INFO msg); \  
  75.     } while (0)  
  76.   
  77. /* functions */  
  78.   
  79. //喂狗函数,实际上是将wdt_count写入WTCNT寄存器   
  80. static void s3c2410wdt_keepalive(void)  
  81. {  
  82.     spin_lock(&wdt_lock);//给资源上锁   
  83.     writel(wdt_count, wdt_base + S3C2410_WTCNT);//写WTCNT寄存器   
  84.     spin_unlock(&wdt_lock);//解锁资源,下同   
  85. }  
  86.   
  87. static void __s3c2410wdt_stop(void)  
  88. {  
  89.     unsigned long wtcon;  
  90.   
  91.     wtcon = readl(wdt_base + S3C2410_WTCON);  
  92.     wtcon &= ~(S3C2410_WTCON_ENABLE | S3C2410_WTCON_RSTEN);  
  93.     writel(wtcon, wdt_base + S3C2410_WTCON);//设备看门狗使能无效、复位功能无效   
  94. }  
  95. //停止watchdog计时   
  96. static void s3c2410wdt_stop(void)  
  97. {  
  98.     spin_lock(&wdt_lock);  
  99.     __s3c2410wdt_stop();  
  100.     spin_unlock(&wdt_lock);  
  101. }  
  102.   
  103. //启动watchdog计时   
  104. static void s3c2410wdt_start(void)  
  105. {  
  106.     unsigned long wtcon;  
  107.   
  108.     spin_lock(&wdt_lock);  
  109.   
  110.     __s3c2410wdt_stop();  
  111.   
  112.     wtcon = readl(wdt_base + S3C2410_WTCON);  
  113.     wtcon |= S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128;//看门狗定时器使能、时钟除数因子128   
  114.   
  115.     if (soft_noboot) //使用参数soft_noboot来选择看门狗到时是重启还是执行中断函数   
  116.     {  
  117.         wtcon |= S3C2410_WTCON_INTEN;//中断使能   
  118.         wtcon &= ~S3C2410_WTCON_RSTEN;//复位功能无效   
  119.     }   
  120.     else   
  121.     {  
  122.         wtcon &= ~S3C2410_WTCON_INTEN;//中断不使能   
  123.         wtcon |= S3C2410_WTCON_RSTEN;//复位功能有效   
  124.     }  
  125.   
  126.     writel(wdt_count, wdt_base + S3C2410_WTDAT);//将wdt_count写入WTDAT   
  127.     writel(wdt_count, wdt_base + S3C2410_WTCNT);//将wdt_count写入WTCNT   
  128.     writel(wtcon, wdt_base + S3C2410_WTCON);//设置WTCON   
  129.     spin_unlock(&wdt_lock);  
  130. }  
  131.   
  132. //设置WatchDog周期timeout   
  133. static int s3c2410wdt_set_heartbeat(int timeout)  
  134. {  
  135.     unsigned int freq = clk_get_rate(wdt_clock);  
  136.     unsigned int count;  
  137.     unsigned int divisor = 1;  
  138.     unsigned long wtcon;  
  139.   
  140.     if (timeout < 1)  
  141.         return -EINVAL;  
  142.   
  143.     freq /= 128;  
  144.     count = timeout * freq;  
  145.   
  146.     /* if the count is bigger than the watchdog register, 
  147.        then work out what we need to do (and if) we can 
  148.        actually make this value 
  149.     */  
  150. //如果计时的时间过大,则适当加大预分频值   
  151.     if (count >= 0x10000)   
  152.     {  
  153.         for (divisor = 1; divisor <= 0x100; divisor++)   
  154.         {  
  155.             if ((count / divisor) < 0x10000)  
  156.                 break;  
  157.         }  
  158. //若预分频最大仍不能满足计时周期,则报错   
  159.         if ((count / divisor) >= 0x10000)   
  160.         {  
  161.             dev_err(wdt_dev, "timeout %d too big\n", timeout);  
  162.             return -EINVAL;  
  163.         }  
  164.     }  
  165.   
  166.     tmr_margin = timeout;  
  167.   
  168.     count /= divisor;  
  169.     wdt_count = count;  
  170.   
  171.     /* update the pre-scaler */  
  172.     wtcon = readl(wdt_base + S3C2410_WTCON);  
  173.     wtcon &= ~S3C2410_WTCON_PRESCALE_MASK;  
  174.     wtcon |= S3C2410_WTCON_PRESCALE(divisor-1);  
  175.   
  176.     writel(count, wdt_base + S3C2410_WTDAT);//是指WTDAT寄存器   
  177.     writel(wtcon, wdt_base + S3C2410_WTCON);//设置预分频值   
  178.   
  179.     return 0;  
  180. }  
  181.   
  182. /* 
  183.  *  /dev/watchdog handling 
  184.  */  
  185.   
  186. static int s3c2410wdt_open(struct inode *inode, struct file *file)  
  187. {  
  188.     if (test_and_set_bit(0, &open_lock))//测试并设置open_lock第0位为1   
  189.         return -EBUSY;  
  190.     if (nowayout)  
  191.         __module_get(THIS_MODULE);  
  192.           
  193.     allow_close = CLOSE_STATE_NOT;  
  194.   
  195.     /* start the timer */  
  196.     s3c2410wdt_start();//启动watchdog   
  197.     return nonseekable_open(inode, file);  
  198. }  
  199.   
  200. static int s3c2410wdt_release(struct inode *inode, struct file *file)  
  201. {  
  202.     /* 
  203.      *  Shut off the timer. 
  204.      *  Lock it in if it's a module and we set nowayout 
  205.      */  
  206.   
  207.     if (allow_close == CLOSE_STATE_ALLOW)  
  208.         s3c2410wdt_stop();  
  209.     else   
  210.     {  
  211.         dev_err(wdt_dev, "Unexpected close, not stopping watchdog\n");  
  212.         s3c2410wdt_keepalive();  
  213.     }  
  214.     allow_close = CLOSE_STATE_NOT;  
  215.     clear_bit(0, &open_lock);//清楚open_lock第0位   
  216.     return 0;  
  217. }  
  218.   
  219. static ssize_t s3c2410wdt_write(struct file *file, const char __user *data,  
  220.                 size_t len, loff_t *ppos)  
  221. {  
  222.     /* 
  223.      *  Refresh the timer. 
  224.      */  
  225.     if (len)   
  226.     {  
  227.         if (!nowayout)   
  228.         {  
  229.             size_t i;  
  230.             /* In case it was set long ago */  
  231.             allow_close = CLOSE_STATE_NOT;  
  232.   
  233.             for (i = 0; i != len; i++)   
  234.             {  
  235.                 char c;  
  236.                 if (get_user(c, data + i))//从用户空间copy数据   
  237.                     return -EFAULT;  
  238.                 if (c == 'V')//当输入V时,关闭watchdog   
  239.                     allow_close = CLOSE_STATE_ALLOW;  
  240.             }  
  241.         }  
  242.         //注意:这里要手动喂狗一次?!   
  243.         s3c2410wdt_keepalive();//由于将allow_close设置成CLOSE_STATE_ALLOW后,当release时无法再次喂狗   
  244.     }  
  245.     return len;  
  246. }  
  247.   
  248. #define OPTIONS WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE   
  249.   
  250. static const struct watchdog_info s3c2410_wdt_ident =   
  251. {  
  252.     .options          =     OPTIONS,  
  253.     .firmware_version = 0,  
  254.     .identity         = "S3C2410 Watchdog",  
  255. };  
  256.   
  257. //IO控制接口函数   
  258. static long s3c2410wdt_ioctl(struct file *file, unsigned int cmd,  
  259.                             unsigned long arg)  
  260. {  
  261.     void __user *argp = (void __user *)arg;  
  262.     int __user *p = argp;  
  263.     int new_margin;  
  264.   
  265.     switch (cmd) {  
  266.     case WDIOC_GETSUPPORT:  
  267.         return copy_to_user(argp, &s3c2410_wdt_ident,  
  268.             sizeof(s3c2410_wdt_ident)) ? -EFAULT : 0;  
  269.     case WDIOC_GETSTATUS:  
  270.     case WDIOC_GETBOOTSTATUS:  
  271.         return put_user(0, p);  
  272.     case WDIOC_KEEPALIVE:  
  273.         s3c2410wdt_keepalive();  
  274.         return 0;  
  275.     case WDIOC_SETTIMEOUT:  
  276.         if (get_user(new_margin, p))  
  277.             return -EFAULT;  
  278.         if (s3c2410wdt_set_heartbeat(new_margin))  
  279.             return -EINVAL;  
  280.         s3c2410wdt_keepalive();  
  281.         return put_user(tmr_margin, p);  
  282.     case WDIOC_GETTIMEOUT:  
  283.         return put_user(tmr_margin, p);  
  284.     default:  
  285.         return -ENOTTY;  
  286.     }  
  287. }  
  288.   
  289. /* kernel interface */  
  290.   
  291. //文件操作结构体   
  292. static const struct file_operations s3c2410wdt_fops =   
  293. {  
  294.     .owner      = THIS_MODULE,  
  295.     .llseek     = no_llseek,//屏蔽seek操作   
  296.     .write      = s3c2410wdt_write,//写方法   
  297.     .unlocked_ioctl = s3c2410wdt_ioctl,//控制方法   
  298.     .open       = s3c2410wdt_open,//代开设备方法   
  299.     .release    = s3c2410wdt_release,//关闭设备方法   
  300. };  
  301.   
  302. static struct miscdevice s3c2410wdt_miscdev =   
  303. {  
  304.     .minor      = WATCHDOG_MINOR,  
  305.     .name       = "watchdog",  
  306.     .fops       = &s3c2410wdt_fops,  
  307. };  
  308.   
  309. /* interrupt handler code */  
  310.   
  311. static irqreturn_t s3c2410wdt_irq(int irqno, void *param)  
  312. {  
  313.     //dev_info(wdt_dev, "watchdog timer expired (irq)\n");   
  314.     s3c2410_gpio_setpin(S3C2410_GPB10,(count++)%2);  
  315.     s3c2410wdt_keepalive();  
  316.     return IRQ_HANDLED;  
  317. }  
  318. /* device interface */  
  319.   
  320. //注册设备时执行   
  321. static int s3c2410wdt_probe(struct platform_device *pdev)  
  322. {  
  323.     struct resource *res;  
  324.     struct device *dev;  
  325.     unsigned int wtcon;  
  326.     int started = 0;  
  327.     int ret;  
  328.     int size;  
  329.   
  330.     dev = &pdev->dev;  
  331.     wdt_dev = &pdev->dev;  
  332.   
  333.     /* get the memory region for the watchdog timer */  
  334. //获取IO端口资源,这里 IORESOURCE_MEM和平台设备中资源的定义一致   
  335.     res = platform_get_resource(pdev, IORESOURCE_MEM, 0);  
  336.     if (res == NULL) {  
  337.         dev_err(dev, "no memory resource specified\n");  
  338.         return -ENOENT;  
  339.     }  
  340.   
  341.     size = (res->end - res->start) + 1;  
  342. //申请内存空间   
  343.     wdt_mem = request_mem_region(res->start, size, pdev->name);  
  344.     if (wdt_mem == NULL) {  
  345.         dev_err(dev, "failed to get memory region\n");  
  346.         ret = -ENOENT;  
  347.         goto err_req;  
  348.     }  
  349. //地址映射,wdt_base为基地址   
  350.     wdt_base = ioremap(res->start, size);  
  351.     if (wdt_base == NULL) {  
  352.         dev_err(dev, "failed to ioremap() region\n");  
  353.         ret = -EINVAL;  
  354.         goto err_req;  
  355.     }  
  356.   
  357.     DBG("probe: mapped wdt_base=%p\n", wdt_base);  
  358. //从平台设备中获取中断号   
  359.     wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);  
  360.     if (wdt_irq == NULL) {  
  361.         dev_err(dev, "no irq resource specified\n");  
  362.         ret = -ENOENT;  
  363.         goto err_map;  
  364.     }  
  365. //注册中断,中断处理函数s3c2410wdt_irq()   
  366.     ret = request_irq(wdt_irq->start, s3c2410wdt_irq, 0, pdev->name, pdev);  
  367.     if (ret != 0) {  
  368.         dev_err(dev, "failed to install irq (%d)\n", ret);  
  369.         goto err_map;  
  370.     }  
  371. //获取系统时钟   
  372.     wdt_clock = clk_get(&pdev->dev, "watchdog");  
  373.     if (IS_ERR(wdt_clock)) {  
  374.         dev_err(dev, "failed to find watchdog clock source\n");  
  375.         ret = PTR_ERR(wdt_clock);  
  376.         goto err_irq;  
  377.     }  
  378. //使能时钟   
  379.     clk_enable(wdt_clock);  
  380.   
  381.     /* see if we can actually set the requested timer margin, and if 
  382.      * not, try the default value */  
  383.   
  384.     if (s3c2410wdt_set_heartbeat(tmr_margin))   
  385.     {//这里第一次设置计时周期为tmr_margin,如果设置不成功,则重新设置为默认值   
  386.         started = s3c2410wdt_set_heartbeat(  
  387.                     CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);  
  388.   
  389.         if (started == 0)  
  390.             dev_info(dev,  
  391.                "tmr_margin value out of range, default %d used\n",  
  392.                    CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);  
  393.         else  
  394.             dev_info(dev, "default timer value is out of range, cannot start\n");  
  395.     }  
  396. //注册设备   
  397.     ret = misc_register(&s3c2410wdt_miscdev);  
  398.     if (ret)   
  399.     {  
  400.         dev_err(dev, "cannot register miscdev on minor=%d (%d)\n",  
  401.             WATCHDOG_MINOR, ret);  
  402.         goto err_clk;  
  403.     }  
  404.   
  405.     if (tmr_atboot && started == 0)   
  406.     {  
  407.         dev_info(dev, "starting watchdog timer\n");  
  408.         s3c2410wdt_start();//启动看门狗   
  409.     }   
  410.     else if (!tmr_atboot)   
  411.     {  
  412.         /* if we're not enabling the watchdog, then ensure it is 
  413.          * disabled if it has been left running from the bootloader 
  414.          * or other source */  
  415.         s3c2410wdt_stop();  
  416.     }  
  417.   
  418.     /* print out a statement of readiness */  
  419.   
  420.     wtcon = readl(wdt_base + S3C2410_WTCON);  
  421.   
  422.     dev_info(dev, "watchdog %sactive, reset %sabled, irq %sabled\n",  
  423.          (wtcon & S3C2410_WTCON_ENABLE) ?  "" : "in",  
  424.          (wtcon & S3C2410_WTCON_RSTEN) ? "" : "dis",  
  425.          (wtcon & S3C2410_WTCON_INTEN) ? "" : "en");  
  426. //设置GPB10端口为输出端口,用于点亮LED10   
  427.     s3c2410_gpio_cfgpin(S3C2410_GPB10,S3C2410_GPB10_OUTP);  
  428.     return 0;  
  429.   
  430. //出错跳转表,异常处理   
  431.   
  432.  err_clk:  
  433.     clk_disable(wdt_clock);  
  434.     clk_put(wdt_clock);  
  435.   
  436.  err_irq:  
  437.     free_irq(wdt_irq->start, pdev);  
  438.   
  439.  err_map:  
  440.     iounmap(wdt_base);  
  441.   
  442.  err_req:  
  443.     release_resource(wdt_mem);  
  444.     kfree(wdt_mem);  
  445.   
  446.     return ret;  
  447. }  
  448.   
  449. //设备移除函数,释放资源和映射   
  450. static int s3c2410wdt_remove(struct platform_device *dev)  
  451. {  
  452.     release_resource(wdt_mem);  
  453.     kfree(wdt_mem);  
  454.     wdt_mem = NULL;  
  455.   
  456.     free_irq(wdt_irq->start, dev);  
  457.     wdt_irq = NULL;  
  458.   
  459.     clk_disable(wdt_clock);  
  460.     clk_put(wdt_clock);  
  461.     wdt_clock = NULL;  
  462.   
  463.     iounmap(wdt_base);  
  464.     misc_deregister(&s3c2410wdt_miscdev);  
  465.     return 0;  
  466. }  
  467.   
  468. //关闭看门狗   
  469. static void s3c2410wdt_shutdown(struct platform_device *dev)  
  470. {  
  471.     s3c2410wdt_stop();  
  472. }  
  473.   
  474. //定义平台设备驱动   
  475. static struct platform_driver s3c2410wdt_driver = {  
  476.     .probe      = s3c2410wdt_probe,  
  477.     .remove     = s3c2410wdt_remove,  
  478.     .shutdown   = s3c2410wdt_shutdown,  
  479.     .driver     = {  
  480.         .owner  = THIS_MODULE,  
  481.         .name   = "s3c2410-wdt",//该名称参照内核源码中该资源的名称,两者必须一致   
  482.     },  
  483. };  
  484.   
  485.   
  486. static char banner[] __initdata =  
  487.     KERN_INFO "S3C2410 Watchdog Timer, (c) 2004 Simtec Electronics\n";  
  488.   
  489. //驱动安装时执行   
  490. static int __init watchdog_init(void)  
  491. {  
  492.     printk(banner);  
  493.     return platform_driver_register(&s3c2410wdt_driver);//注册设备   
  494. }  
  495.   
  496. //驱动移除是执行   
  497. static void __exit watchdog_exit(void)  
  498. {  
  499.     platform_driver_unregister(&s3c2410wdt_driver);//移除设备   
  500. }  
  501.   
  502. module_init(watchdog_init);  
  503. module_exit(watchdog_exit);  
  504.   
  505. MODULE_AUTHOR("Yan Ming");  
  506. MODULE_DESCRIPTION("S3C2410 Watchdog Device Driver");  
  507. MODULE_LICENSE("GPL");  
  508. MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);  
设置默认不是重启机器,而是执行中断函数,当不喂狗,计数器减到0,点亮LED,然后喂狗,重新计数。


你可能感兴趣的:(ARM-Linux驱动--Watch Dog Timer(看门狗)驱动分析)