看门狗

转载--看门狗驱动  

2011-10-11 21:17:28|  分类:linux驱动源码 |  标签:|字号 订阅

#include <linux/module.h>   
#include <linux/moduleparam.h>   
#include <linux/types.h>   
#include <linux/timer.h>   
#include <linux/miscdevice.h>   
#include <linux/watchdog.h>   
#include <linux/fs.h>   
#include <linux/init.h>   
#include <linux/platform_device.h>   
#include <linux/interrupt.h>   
#include <linux/clk.h>   
#include <linux/uaccess.h>   
#include <linux/io.h>   
  
#include <mach/map.h>   
  
#undef S3C_VA_WATCHDOG   
#define S3C_VA_WATCHDOG (0)   
  
#include <plat/regs-watchdog.h>   
  
#define PFX "s3c2410-wdt: "   
  
#define CONFIG_S3C2410_WATCHDOG_ATBOOT      (0)   
#define CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME    (15)   
  
static int nowayout = WATCHDOG_NOWAYOUT;  
static int tmr_margin   = CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME;  
static int tmr_atboot   = CONFIG_S3C2410_WATCHDOG_ATBOOT;  
static int soft_noboot;  
static int debug;  
  
module_param(tmr_margin,  int, 0);  
module_param(tmr_atboot,  int, 0);  
module_param(nowayout,    int, 0);  
module_param(soft_noboot, int, 0);  
module_param(debug,   int, 0);  
  
static unsigned long open_lock;  
static struct device    *wdt_dev;   /* platform device attached to */  
static struct resource  *wdt_mem;  
static struct resource  *wdt_irq;  
static struct clk   *wdt_clock;  
static void __iomem *wdt_base;  
static unsigned int  wdt_count;  
static char      expect_close;  
static DEFINE_SPINLOCK(wdt_lock);  
  
/* watchdog control routines */  
  
#define DBG(msg...) do { \   
    if (debug) \  
        printk(KERN_INFO msg); \  
    } while (0)  
  
/* functions */  
  
static void s3c2410wdt_keepalive(void) //喂狗   
{  
    spin_lock(&wdt_lock);  
    writel(wdt_count, wdt_base + S3C2410_WTCNT);  
    spin_unlock(&wdt_lock);  
}  
  
static void __s3c2410wdt_stop(void)  
{  
    unsigned long wtcon;  
  
    wtcon = readl(wdt_base + S3C2410_WTCON);  
    wtcon &= ~(S3C2410_WTCON_ENABLE | S3C2410_WTCON_RSTEN);  
    writel(wtcon, wdt_base + S3C2410_WTCON);  
}  
  
static void s3c2410wdt_stop(void)   //停止看门狗   
{  
    spin_lock(&wdt_lock);  
    __s3c2410wdt_stop();  
    spin_unlock(&wdt_lock);  
}  
  
static void s3c2410wdt_start(void)  //启动看门狗   
{  
    unsigned long wtcon;  
  
    spin_lock(&wdt_lock);  
  
    __s3c2410wdt_stop();  
  
    wtcon = readl(wdt_base + S3C2410_WTCON);  
    wtcon |= S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128;  
  
    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);  
}  
  
static int s3c2410wdt_set_heartbeat(int timeout)   //根据timeout时间设置看门狗定时器初始值   
{  
    unsigned int freq = clk_get_rate(wdt_clock);  
    unsigned int count;  
    unsigned int divisor = 1;  
    unsigned long wtcon;  
  
    if (timeout < 1)  
        return -EINVAL;  
  
    freq /= 128;  
    count = timeout * freq;  
  
    DBG("%s: count=%d, timeout=%d, freq=%d\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) {  
        for (divisor = 1; divisor <= 0x100; divisor++) {  
            if ((count / divisor) < 0x10000)  
                break;  
        }  
  
        if ((count / divisor) >= 0x10000) {  
            dev_err(wdt_dev, "timeout %d too big\n", timeout);  
            return -EINVAL;  
        }  
    }  
  
    tmr_margin = timeout;  
  
    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;  
    wtcon |= S3C2410_WTCON_PRESCALE(divisor-1);  
  
    writel(count, wdt_base + S3C2410_WTDAT);  
    writel(wtcon, wdt_base + S3C2410_WTCON);  
  
    return 0;  
}  
  
/* 
 *  /dev/watchdog handling 
 */  
  
static int s3c2410wdt_open(struct inode *inode, struct file *file)  
{  
    if (test_and_set_bit(0, &open_lock))  
        return -EBUSY;  
  
    if (nowayout)     /*nowayout是内涵配置选项,如果配置则看门狗不能被关闭*/  
        __module_get(THIS_MODULE);/*如果内核配置了CONFIG_WATCHDOG_NOWAYOUT项,则使模块使用计数加1*/  
  
    expect_close = 0;  
  
    /* start the timer */  
    s3c2410wdt_start();  
    return nonseekable_open(inode, file);/*表示返回的这个设备文件是不可以被seek操作的,nonseekable_open定义在fs.h中*/  
}  
  
static int s3c2410wdt_release(struct inode *inode, struct file *file)  
{  
    /* 
     *  Shut off the timer. 
     *  Lock it in if it's a module and we set nowayout 
     */  
  
    if (expect_close == 42)  
        s3c2410wdt_stop();  
    else {  
        dev_err(wdt_dev, "Unexpected close, not stopping watchdog\n");  
        s3c2410wdt_keepalive();  
    }  
    expect_close = 0;  
    clear_bit(0, &open_lock);  
    return 0;  
}  
  
static ssize_t s3c2410wdt_write(struct file *file, const char __user *data,  
                size_t len, loff_t *ppos)  
{  
    /* 
     *  Refresh the timer. 
     */  
    if (len) {  
        if (!nowayout) { /*如果没有配置内核CONFIG_WATCHDOG_NOWAYOUT选项,WATCHDOG_NOWAYOUT宏位于include/linux/watchdog.h*/  
            size_t i;  
  
            /* In case it was set long ago */  
            expect_close = 0;  
  
            for (i = 0; i != len; i++) {  
                char c;  
  
                if (get_user(c, data + i))  
                    return -EFAULT;  
                if (c == 'V')  
                    expect_close = 42;  
            }  
        }  
/*上面的意思是想要看门狗定时器可以被关闭,则内核不要配置CONFIG_WATCHDOG_NOWAYOUT选项,对于下面这里还要“喂狗”一次,我刚开始觉得不需要,因为在看门狗定时器中断里面不断的在“喂狗”。后来想想,这里还必须要“喂狗”一次,因为当上面我们判断到写入的数据是"V"时,看门狗定时器的当前操作状态马上被设置为关闭,再当驱动去调用看门狗设备驱动的关闭接口函数时,看门狗定时器中断被禁止,无法再实现“喂狗”,所以这里要手动“喂狗”一次,否则定时器溢出系统被复位*/  
  
        s3c2410wdt_keepalive();  
    }  
    return len;  
}  
  
#define OPTIONS (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE)   
  
/*用于支持看门狗IO控制中获取看门狗信息的命令WDIOC_GETSUPPORT,下面的宏和看门狗信息结构体定义在include/linux/watchdog.h中*/  
static const struct watchdog_info s3c2410_wdt_ident = {  
    .options          =     OPTIONS,  
    .firmware_version = 0,  
    .identity         = "S3C2410 Watchdog",  
};  
  
  
static long s3c2410wdt_ioctl(struct file *file, unsigned int cmd,  
                            unsigned long arg)  
{  
    void __user *argp = (void __user *)arg;  
    int __user *p = argp;  
    int new_margin;  
  
    /*以下对看门狗定时器IO控制的命令定义在watchdog.h中*/  
    switch (cmd) {  
    case WDIOC_GETSUPPORT:/*获取看门狗的支持信息,wdt_ident定义在上面*/  
        return copy_to_user(argp, &s3c2410_wdt_ident,sizeof(s3c2410_wdt_ident)) ? -EFAULT : 0;  
    case WDIOC_GETSTATUS:  
    case WDIOC_GETBOOTSTATUS:  
        return put_user(0, p);  
    case WDIOC_KEEPALIVE:  
        s3c2410wdt_keepalive();  
        return 0;  
    case WDIOC_SETTIMEOUT:  
        if (get_user(new_margin, p))  
            return -EFAULT;  
        if (s3c2410wdt_set_heartbeat(new_margin))  
            return -EINVAL;  
        s3c2410wdt_keepalive();  
        return put_user(tmr_margin, p);  
    case WDIOC_GETTIMEOUT:  
        return put_user(tmr_margin, p);  
    default:  
        return -ENOTTY;  
    }  
}  
  
/* kernel interface */  
  
static const struct file_operations s3c2410wdt_fops = {  
    .owner      = THIS_MODULE,  
    .llseek     = no_llseek,  /*定义为不可定位,即屏蔽seek操作,no_llseek定义在fs.h中*/  
    .write      = s3c2410wdt_write,  
    .unlocked_ioctl = s3c2410wdt_ioctl,  
    .open       = s3c2410wdt_open,  
    .release    = s3c2410wdt_release,  
};  
  
static struct miscdevice s3c2410wdt_miscdev = {  
    .minor      = WATCHDOG_MINOR,  //位于include/linux/miscdevic.h中   
    .name       = "watchdog",  
    .fops       = &s3c2410wdt_fops,  
};  
  
/* interrupt handler code */  
  
static irqreturn_t s3c2410wdt_irq(int irqno, void *param)  
{  
    dev_info(wdt_dev, "watchdog timer expired (irq)\n");  
  
    s3c2410wdt_keepalive();  
    return IRQ_HANDLED;  
}  
/* device interface */  
  
static int __devinit s3c2410wdt_probe(struct platform_device *pdev)  
{  
    struct resource *res;  /*定义一个资源,用来保存获取的watchdog的IO资源*/  
    struct device *dev;  
    unsigned int wtcon;  
    int started = 0;  
    int ret;  
    int size;  
  
    DBG("%s: probe=%p\n", __func__, pdev);  
  
    dev = &pdev->dev;  
    wdt_dev = &pdev->dev;  
  
    /* get the memory region for the watchdog timer */  
  
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);  
    if (res == NULL) {  
        /*dev_err定义在device.h中,在platform_device.h中已经引用,所以这里就不需再引用*/  
        dev_err(dev, "no memory resource specified\n");  
        return -ENOENT;  
    }  
  
    size = (res->end - res->start) + 1;  
       /*申请watchdog的IO端口资源所占用的IO内存,request_mem_region定义在ioport.h中*/  
    wdt_mem = request_mem_region(res->start, size, pdev->name);  
    if (wdt_mem == NULL) {  
        dev_err(dev, "failed to get memory region\n");  
        ret = -ENOENT;  
        goto err_req;  
    }  
  
    wdt_base = ioremap(res->start, size);  
    if (wdt_base == NULL) {  
        dev_err(dev, "failed to ioremap() region\n");  
        ret = -EINVAL;  
        goto err_req;  
    }  
  
    DBG("probe: mapped wdt_base=%p\n", wdt_base);  
  
    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_map;  
    }  
  
    ret = request_irq(wdt_irq->start, s3c2410wdt_irq, 0, pdev->name, pdev);  
    if (ret != 0) {  
        dev_err(dev, "failed to install irq (%d)\n", ret);  
        goto err_map;  
    }  
    /*定义在arch/arm/plat-s3c24xx/s3c2410-clock.c中*/  
    wdt_clock = clk_get(&pdev->dev, "watchdog");  
    if (IS_ERR(wdt_clock)) {  
        dev_err(dev, "failed to find watchdog clock source\n");  
        ret = PTR_ERR(wdt_clock);  
        goto err_irq;  
    }  
  
    clk_enable(wdt_clock);  
  
    /* see if we can actually set the requested timer margin, and if 
     * not, try the default value */  
  
    if (s3c2410wdt_set_heartbeat(tmr_margin)) {  
        started = s3c2410wdt_set_heartbeat(  
                    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 = misc_register(&s3c2410wdt_miscdev);  
    if (ret) {  
        dev_err(dev, "cannot register miscdev on minor=%d (%d)\n",  
            WATCHDOG_MINOR, ret);  
        goto err_clk;  
    }  
  
    if (tmr_atboot && started == 0) {  
        dev_info(dev, "starting watchdog timer\n");  
        s3c2410wdt_start();  
    } 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();  
    }  
  
    /* 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) ? "" : "dis",  
         (wtcon & S3C2410_WTCON_INTEN) ? "" : "en");  
  
    return 0;  
  
 err_clk:  
    clk_disable(wdt_clock);  
    clk_put(wdt_clock);  
  
 err_irq:  
    free_irq(wdt_irq->start, pdev);  
  
 err_map:  
    iounmap(wdt_base);  
  
 err_req:  
    release_resource(wdt_mem);  
    kfree(wdt_mem);  
  
    return ret;  
}  
  
static int __devexit s3c2410wdt_remove(struct platform_device *dev)  
{  
    release_resource(wdt_mem);  
    kfree(wdt_mem);  
    wdt_mem = NULL;  
  
    free_irq(wdt_irq->start, dev);  
    wdt_irq = NULL;  
  
    clk_disable(wdt_clock);  
    clk_put(wdt_clock);  
    wdt_clock = NULL;  
  
    iounmap(wdt_base);  
    misc_deregister(&s3c2410wdt_miscdev);  
    return 0;  
}  
  
static void s3c2410wdt_shutdown(struct platform_device *dev)  
{  
    s3c2410wdt_stop();  
}  
  
#ifdef CONFIG_PM   
/*对Watchdog平台设备驱动电源管理的支持。CONFIG_PM这个宏定义在内核中,当配置内核时选上电源管理,则Watchdog平台驱动的设备挂起和恢复功能均有效*/  
  
static unsigned long wtcon_save;  
static unsigned long wtdat_save;  
  
static int s3c2410wdt_suspend(struct platform_device *dev, pm_message_t state)  
{  
    /* Save watchdog state, and turn it off. */  
    wtcon_save = readl(wdt_base + S3C2410_WTCON);  
    wtdat_save = readl(wdt_base + S3C2410_WTDAT);  
  
    /* Note that WTCNT doesn't need to be saved. */  
    s3c2410wdt_stop();  
  
    return 0;  
}  
  
static int s3c2410wdt_resume(struct platform_device *dev)  
{  
    /* Restore watchdog state. */  
  
    writel(wtdat_save, wdt_base + S3C2410_WTDAT);  
    writel(wtdat_save, wdt_base + S3C2410_WTCNT); /* Reset count */  
    writel(wtcon_save, wdt_base + S3C2410_WTCON);  
  
    printk(KERN_INFO PFX "watchdog %sabled\n",  
           (wtcon_save & S3C2410_WTCON_ENABLE) ? "en" : "dis");  
  
    return 0;  
}  
  
#else   
#define s3c2410wdt_suspend NULL   
#define s3c2410wdt_resume  NULL   
#endif /* CONFIG_PM */   
  
/*注意:这是使用了一个__devexit_p,还有前面使用了__devinit和__devexit,我们还是先来讲讲这个:在Linux内核中,使用了大量不同的宏来标记具有不同作用的函数和数据结构,这些宏在include/linux/init.h头文件中定义,编译器通过这些宏可以把代码优化放到合适的内存位置,以减少内存占用和提高内核效率。__devinit、__devexit就是这些宏之一,在probe()和remove()函数中应该使用__devinit和__devexit宏。又当remove()函数使用了__devexit宏时,则在驱动结构体中一定要使用__devexit_p宏来引用remove(),所以下面就用__devexit_p来引用rtc_remove*/  
  
static struct platform_driver s3c2410wdt_driver = {  
    .probe      = s3c2410wdt_probe,  
    .remove     = __devexit_p(s3c2410wdt_remove),  
    .shutdown   = s3c2410wdt_shutdown,  
    .suspend    = s3c2410wdt_suspend,  
    .resume     = s3c2410wdt_resume,  
    .driver     = {  
        .owner  = THIS_MODULE,  
        .name   = "s3c2410-wdt",   
              /*注意这里的名称一定要和系统中定义平台设备的名称一致,这样才能把平台设备与该平台设备的驱动关联起来*/  
    },  
};  
  
  
static char banner[] __initdata =  
    KERN_INFO "S3C2410 Watchdog Timer, (c) 2004 Simtec Electronics\n";  
  
static int __init watchdog_init(void)  
{  
    printk(banner);  
    return platform_driver_register(&s3c2410wdt_driver);  
}  
  
static void __exit watchdog_exit(void)  
{  
    platform_driver_unregister(&s3c2410wdt_driver);  
}  
  
module_init(watchdog_init);  
module_exit(watchdog_exit);  
  
MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>, "  
          "Dimitry Andric <dimitry.andric@tomtom.com>");  
MODULE_DESCRIPTION("S3C2410 Watchdog Device Driver");  
MODULE_LICENSE("GPL");  
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);  
MODULE_ALIAS("platform:s3c2410-wdt"); 

 

配置内核,默认情况下已经配置

Devide drivers --->

       <*> Watchdog Timer Support --->

              <*> s3c2410 wathcdog


测试:

      我们可以使用echo向/dev/watchdog设备随便写入一些数据即可开启看门狗,比如:echo 0 > /dev/watchdog如果等待15秒钟,系统自动重启。

      由驱动程序知道,只要写入“V”就可以停止看门狗,echo –n V > /dev/watchdog,-n是去掉回车。

 

下面是一个图形化各个主要函数之间层次关系,更清晰的了解驱动程序。

module_init(&s3c24xx_i2c_driver)

       |__ platform_driver_register(&s3c24xx_i2c_driver)

                     |__ s3c24xx_i2c_probe

                     |        |__ platform_get_resource   

                     |        |__ request_irq

                     |        |__ misc_register(&s3c2410wdt_miscdev)

                     |               |__ &s3c2410wdt_fops

                     |                      |__ s3c2410wdt_open

                     |                      |__ s3c2410wdt_release

                     |                      |__ s3c2410wdt_write

                     |                      |__ s3c2410wdt_ioctl

                     |                      |__ no_llseek

                     |__ s3c2410wdt_remove

                     |__ s3c2410wdt_shutdown

                     |__ s3c2410wdt_suspend

                     |__ s3c2410wdt_resume

module_exit(watchdog_exit)

       |__ platform_driver_unregister(&s3c2410wdt_driver)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值