Watchdog驱动开发实例分析

看门狗(Watchdog)主要是实现系统自动复位的功能,他是利用芯片内部的定时器,定时输出连接到电路的复位端,程序在一定时间范围内对定时器清零(俗称“喂狗”),所以程序在正常工作时,定时器总是不能溢出,也就不能产生复位信号;如果程序出现错误,不在定时周期内复位看门狗,那么定时器就会溢出而产生复位信号使系统复位。

Watchdog模块提供了三个寄存器来对Watchdog进行操作,他们分别是:定时器控制寄存器WTCON、定时器数据寄存器 WTDAT和定时器计数寄存器WTCNT。

在对定时器数据寄存器WTDAT进行操作时必须在定时器控制寄存器WTCON使能之前写入一个计数目标值,当Watchdog使能开启后,WTDAT中的值会自动被加载到计数寄存器WTCNT中,然后Watchdog从CPU内部的时钟分频和时钟除数因子得到一个工作周期,当每个周期结束时计数寄存器WTCNT中的值会1,直到递减为0时,如果还不重新往WTCNT中写入新的计数目标值(即“喂狗”),则 Watchdog就产生复位信号使系统复位。

1.初始化和卸载

/*Watchdog平台驱动结构体,平台驱动结构体定义在platform_device.h中*/
static struct platform_driver watchdog_driver =
{
    . probe = watchdog_probe,    //Watchdog探测函数
    . remove = __devexit_p( watchdog_remove) , //Watchdog移除函数
    . shutdown = watchdog_shutdown,  //Watchdog关闭函数
    . suspend = watchdog_suspend,   //Watchdog挂起函数
    . resume = watchdog_resume,    //Watchdog恢复函数
    . driver     =
    {
        . name   = "s3c2410-wdt" ,
        . owner  = THIS_MODULE,
    } ,
} ;
static int __init watchdog_init( void )
{
    /*将Watchdog注册成平台设备驱动*/
    return platform_driver_register( & watchdog_driver) ;
}
static void __exit watchdog_exit( void )
{
    /*注销Watchdog平台设备驱动*/
    platform_driver_unregister( & watchdog_driver) ;
}

module_init( watchdog_init) ;
module_exit( watchdog_exit) ;

module_param( tmr_margin, int , 0) ;
module_param( tmr_atboot, int , 0) ;
module_param( nowayout,    int , 0) ;
module_param( soft_noboot, int , 0) ;

MODULE_LICENSE( "GPL" ) ;
MODULE_AUTHOR( "Huang Gang" ) ;
MODULE_DESCRIPTION( "My2440 Watchdog Driver" ) ;

2.探测函数watchdog_probe

/*定义了一个用来保存watchdog的I/O端口占用的IO空间和经过虚拟映射后的内存地址*/
static struct resource * wdt_mem;
static void __iomem * wdt_base;
/*保存watchdog中断号,NO_IRQ宏定义在irq.h中*/
static int wdt_irqno = NO_IRQ;
/*保存从平台时钟队列中获取watchdog的时钟*/
static struct clk * wdt_clock;

# define CONFIG_WATCHDOG_ATBOOT          ( 0)
# define CONFIG_WATCHDOG_DEFAULT_TIME    ( 15)
static int tmr_atboot = CONFIG_WATCHDOG_ATBOOT;
static int tmr_margin = CONFIG_WATCHDOG_DEFAULT_TIME;
static int soft_noboot;
static unsigned int      wdt_count; /*用于保存经计算后得到的计数寄存器WTCNT的计数值*/

/*申明并初始化一个自旋锁wdt_pie_lock,对Watchdog资源进行互斥访问*/
static DEFINE_SPINLOCK( wdt_pie_lock) ;
static int __devinit watchdog_probe( struct platform_device * pdev)
{
    int ret;
    int started = 0;
    struct resource * res; /*定义一个资源,用来保存获取的watchdog的I/O资源*/

     /*在系统定义的watchdog平台设备中获取watchdog中断号
     platform_get_irq定义在platform_device.h中*/
    wdt_irqno = platform_get_irq( pdev, 0) ;
    if ( wdt_irqno < 0)
    {
         /*获取watchdog中断号不成功错误处理,dev_err定义在device.h中*/
        dev_err( & pdev- > dev, "no irq for watchdog/n" ) ;
        return - ENOENT;
    }

     /*申请Watchdog中断服务,这里使用的是快速中断:IRQF_DISABLED。中断服务程序为:wdt_irq,将Watchdog平台设备pdev做参数传递过去了*/
    ret = request_irq( wdt_irqno, wdt_irq, IRQF_DISABLED, pdev- > name, pdev) ;
    if ( ret)
    {
        /*错误处理*/
        dev_err( dev, "IRQ%d error %d/n" , wdt_irqno, ret) ;
        return ret;
    }

    /*获取watchdog平台设备所使用的IO端口资源,注意这个IORESOURCE_MEM标志和watchdog平台设备定义中的一致*/
    res = platform_get_resource( pdev, IORESOURCE_MEM, 0) ;
    if ( res = = NULL )
    {
        /*错误处理*/
        dev_err( & pdev- > dev, "failed to get memory region resource/n" ) ;
        return - ENOENT;
    }

     /*从平台时钟队列中获取watchdog的时钟,这里为什么要取得这个时钟,因为看门狗定时器的工作周期是由这个时钟和时钟除数因子得到的。注意这里的watchdog参数要与系统中定义的时钟名称一致才能获取得到,也就是说,系统必须先定义得有watchdog。系统的一些时钟定义在arch/arm/plat-s3c24xx/s3c2410-clock.c中*/
    wdt_clock = clk_get( & pdev- > dev, "watchdog" ) ;
    if ( IS_ERR( wdt_clock) )
    {
        /*错误处理*/
        dev_err( & pdev- > dev, "failed to find watchdog clock source/n" ) ;
        return PTR_ERR( wdt_clock) ;
    }

    /*时钟获取后要使能后才可以使用,clk_enable定义在arch/arm/plat-s3c/clock.c中*/
    clk_enable( wdt_clock) ;

     /*申请watchdog的IO端口资源所占用的IO空间,request_mem_region定义在ioport.h中*/
    wdt_mem = request_mem_region( res- > start, res- > end - res- > start + 1, pdev- > name) ;
    if ( wdt_mem = = NULL )
    {
        /*错误处理*/
        dev_err( & pdev- > dev, "failed to reserve memory region/n" ) ;
        ret = - ENOENT;
        goto err_noclk;
    }

     /*将watchdog的IO端口占用的这段I/O空间映射到内存的虚拟地址,ioremap定义在io.h中。注意:IO空间要映射后才能使用,以后对虚拟地址的操作就是对IO空间的操作,*/
    wdt_base = ioremap( res- > start, res- > end - res- > start + 1) ;
    if ( wdt_base = = NULL )
    {
        /*错误处理*/
        dev_err( & pdev- > dev, "failed ioremap()/n" ) ;
        ret = - EINVAL;
        goto err_noreq;
    }

    /*至此,已经将watchdog的资源都准备好了*/

     /计算并设置看门狗定时器时钟周期值,wdt_set_heartbeat定义在下面。初始化看门狗定时器*/
    if ( wdt_set_heartbeat( pdev, tmr_margin) )
    {
        /*这里调用两次的意思是看能不能设置成期望的值,如果不能就设置默认的值*/
        started = wdt_set_heartbeat( pdev, CONFIG_WATCHDOG_DEFAULT_TIME) ;

        /*打印设置的值信息*/
        if ( started = = 0)
            dev_info( & pdev- > dev, "tmr_margin value out of range, default %d used/n" , CONFIG_WATCHDOG_DEFAULT_TIME) ;
        else
            dev_info( & pdev- > dev, "default timer value is out of range, cannot start/n" ) ;
    }

     /*device_init_wakeup该函数定义在pm_wakeup.h中,定义如下:
    static inline void device_init_wakeup(struct device *dev, int val){
        dev->power.can_wakeup = dev->power.should_wakeup = !!val;}
    显然这个函数是让驱动支持电源管理的,这里只要知道,can_wakeup为1时表明这个设备可以被唤醒,设备驱动为了支持Linux中的电源管理,有责任调用device_init_wakeup()来初始化can_wakeup,而should_wakeup则是在设备的电源状态发生变化的时候被device_may_wakeup()用来测试,测试它该不该变化,因此can_wakeup表明的是一种能力,而should_wakeup表明的是有了这种能力以后去不去做某件事。*/
    device_init_wakeup( & pdev- > dev, 1) ;

     /*把看门狗设备又注册成为misc设备,misc_register定义在miscdevice.h中*/
    ret = misc_register( & wdt_miscdev) ;
    if ( ret)
    {
        /*错误处理*/
        dev_err( & pdev- > dev, "cannot register miscdev on minor=%d (%d)/n" , WATCHDOG_MINOR, ret) ;
        goto err_nomap;
    }

    /*函数wdt_start_or_stop定义在下面*/
    if ( tmr_atboot & & started = = 0)
    {
        wdt_start_or_stop( 1) ; /*参数1表示启动看门狗定时器*/
    }
    else if ( ! tmr_atboot)
    {
        wdt_start_or_stop( 0) ; /*参数0表示停止看门狗定时器*/
    }

    return 0;
 
//以下是上面错误处理的跳转点
err_noclk:
    clk_disable( wdt_clock) ;
    clk_put( wdt_clock) ;
err_noreq:
    release_resource( wdt_mem) ;
    kfree( wdt_mem) ;
err_nomap:
    iounmap( wdt_base) ;
    
    return ret;
}

/*看门狗定时器中断服务程序*/
static irqreturn_t wdt_irq( int irq, void * argv)
{
    /*主要要做的事情是在看门狗定时器计数寄存器值递减到0之前重新写入新值(即:“喂狗”)*/
    wdt_keepalive( ) ;

    return IRQ_HANDLED;
}

/*看门狗定时器“喂狗”*/
static void wdt_keepalive( void )
{
    spin_lock( & wdt_lock) ; /*获取自旋锁保护临界区资源*/

    writel( wdt_count, wdt_base + S3C2410_WTCNT) ; /*往计数寄存器WTCNT重新写入计数值*/

    spin_unlock( & wdt_lock) ; /*释放自旋锁,即解锁*/
}

/*计算并设置看门狗定时器时钟周期值并初始化看门狗定时器*/
static int wdt_set_heartbeat( struct platform_device * pdev, int 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;

    if ( count > = 0x10000)
    {     
        for ( divisor = 1; divisor < = 0x100; divisor+ + )
        {
            if ( ( count / divisor) < 0x10000)
                break ;
        }

        if ( ( count / divisor) > = 0x10000)
        {
            dev_err( & pdev- > dev, "timeout %d too big/n" , timeout) ;
            return - EINVAL;
        }
    }

    tmr_margin = timeout;

    count / = divisor;
    wdt_count = count ;

    wtcon = readl( wdt_base + S3C2410_WTCON) ; /* wtcon=1000000000100001 这是控制寄存器的默认值*/
    wtcon & = ~ S3C2410_WTCON_PRESCALE_MASK; /* wtcon=1000000000100001 & ~1111111100000000 = 0000000000100001 */
     /*S3C2410_WTCON_PRESCALE宏是将divisor-1的值向左位移8位,也就是说该值的右8位都为0,则计算如下:
     wtcon=0000000000100001 | (xxxxxxxx)00000000 = (xxxxxxxx)00100001*/
    wtcon | = S3C2410_WTCON_PRESCALE( divisor- 1) ;

    /*设置看门狗定时器数据寄存器WTDAT的值,然后WTDAT的值会自动加载到WTCNT中*/
    writel( count , wdt_base + S3C2410_WTDAT) ;

     /*根据数据手册和上面计算的wtcon值可得,下面是设置看门狗定时控制寄存器WTCON为:
     看门狗定时器输出使能有效、一个保留位默认0、中断使能无效、时钟除数因子为16、
     看门狗定时器使能有效、两个保留位默认00、预定标器值为xxxxxxxx的内容。*/
    writel( wtcon, wdt_base + S3C2410_WTCON) ;

    return 0;
}

/*根据标志flag的值来启动或者停止看门狗定时器,1表示启动,0表示停止*/
static void wdt_start_or_stop( int flag)
{
    unsigned long wtcon;

    spin_lock( & wdt_pie_lock) ; /*获取自旋锁保护临界区资源*/

    /*停止看门狗定时器*/
    wtcon = readl( wdt_base + S3C2410_WTCON) ;
    wtcon & = ~ ( S3C2410_WTCON_ENABLE | S3C2410_WTCON_RSTEN) ;
    writel( wtcon, wdt_base + S3C2410_WTCON) ;

    if ( ! flag)
    {
        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;
        }

        writel( wdt_count, wdt_base + S3C2410_WTDAT) ;
        writel( wdt_count, wdt_base + S3C2410_WTCNT) ;
        writel( wtcon, wdt_base + S3C2410_WTCON) ;
    }

    spin_unlock( & wdt_pie_lock) ; /*释放自旋锁,即解锁*/
}

3.实现misc设备中对设备文件的操作

/*申明并初始化一个信号量open_clock,对Watchdog资源进行互斥访问*/
static DECLARE_MUTEX( open_clock) ;

/*用来表示Linux内核配置选项中配不配置CONFIG_WATCHDOG_NOWAYOUT项,WATCHDOG_NOWAYOUT定义在watchdog.h中*/
static int nowayout    = WATCHDOG_NOWAYOUT;
typedef enum close_state
{
    CLOSE_STATE_NOT,
    CLOSE_STATE_ALLOW = 0x4021
} close_state_t;
static close_state_t allow_close; /*用于记录看门狗定时器的当前的操作状态*/

/*misc设备结构体实现*/
static struct miscdevice wdt_miscdev = {
    . minor        = WATCHDOG_MINOR, /*WATCHDOG_MINOR次设备号定义在miscdevice.h中为130*/
    . name        = "watchdog" ,      /*设备名称*/
    . fops        = & wdt_fops,      /*实现字符设备的相关操作*/
} ;

/*字符设备的相关操作实现*/
static const struct file_operations wdt_fops = {
    . owner        = THIS_MODULE,
    . open         = wdt_open,
    . release    = wdt_release,
    . write         = wdt_write,
    . ioctl        = wdt_ioctl,
    . llseek        = no_llseek, /*定义为不可定位,即屏蔽seek操作,no_llseek定义在fs.h中*/
} ;

/*看门狗设备驱动的打开接口函数*/
static int wdt_open( struct inode * inode, struct file * file )
{
    /*试着获取信号量(即:加锁),如果获取不成功,说明其他进程此时占用了,就返回忙*/
    if ( down_trylock( & open_clock) )
    {
        return - EBUSY;
    }

    if ( nowayout)
    {
        /*如果内核配置了CONFIG_WATCHDOG_NOWAYOUT项,则使模块使用计数加1*/
        __module_get( THIS_MODULE) ;
    }

    /*开始记录看门狗定时器的当前操作状态为:无状态*/
    allow_close = CLOSE_STATE_NOT;

    /*启动看门狗定时器*/
    wdt_start_or_stop( 1) ;

    /*表示返回的这个设备文件是不可以被seek操作的,nonseekable_open定义在fs.h中*/
    return nonseekable_open( inode, file ) ;
}

/*看门狗设备驱动的关闭接口函数*/
static int wdt_release( struct inode * inode, struct file * file )
{
    /*如果判断到当前操作状态是可以关闭看门狗定时器时就关闭,否则就是“喂狗”状态*/
    if ( allow_close = = CLOSE_STATE_ALLOW)
    {
        wdt_start_or_stop( 0) ; /*关闭*/
    }
    else
    {
        wdt_keepalive( ) ; /*“喂狗”*/
    }

    /*恢复看门狗定时器的当前操作状态为:无状态*/
    allow_close = CLOSE_STATE_NOT;

    /*释放获取的信号量(即:解锁),与wdt_open中加锁相对应*/
    up( & open_lock) ;

    return 0;
}

/*看门狗设备驱动的写数据接口函数*/
static ssize_t wdt_write( struct file * file , const char __user * buff, size_t len, loff_t * ppos)
{
    if ( len) /*判断有数据写入*/
    {
        if ( ! nowayout) /*如果没有配置内核CONFIG_WATCHDOG_NOWAYOUT选项*/
        {
            size_t i;
            /*设看门狗定时器的当前操作状态为:无状态*/
            allow_close = CLOSE_STATE_NOT;
            for ( i = 0; i ! = len; i+ + )
            {
                char c;
                if ( get_user( c, data + i) )
                    return - EFAULT;
                if ( c = = 'V' ) /*判断写入的数据是"V"时,则设看门狗定时器的当前操作状态为关闭*/
                    allow_close = CLOSE_STATE_ALLOW;
            }
        }
         /*上面的意思是想要看门狗定时器可以被关闭,则内核不要配置CONFIG_WATCHDOG_NOWAYOUT选项,
         对于下面这里还要“喂狗”一次,我刚开始觉得不需要,因为在看门狗定时器中断里面不断的在“喂狗”。后来想想,这里还必须要“喂狗”一次,因为当上面我们判断到写入的数据是"V"时,看门狗定时器的当前操作状态马上被设置为关闭,再当驱动去调用看门狗设备驱动的关闭接口函数时,看门狗定时器中断被禁止,无法再实现“喂狗”,所以这里要手动“喂狗”一次,否则定时器溢出系统被复位*/
        wdt_keepalive( ) ;
    }

    return len;
}

/*用于支持看门狗IO控制中获取看门狗信息的命令WDIOC_GETSUPPORT,
  下面的宏和看门狗信息结构体定义在watchdog.h中*/
# define OPTIONS WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE
static const struct watchdog_info wdt_ident =
{
    . options = OPTIONS,
    . firmware_version =     0,
    . identity =     "S3C2440 Watchdog" ,
} ;

/*看门狗设备驱动的IO控制接口函数*/
static long wdt_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, & wdt_ident, sizeof ( wdt_ident) ) ? - EFAULT : 0;

        case WDIOC_GETSTATUS:

        case WDIOC_GETBOOTSTATUS: /*获取看门够状态*/
            return put_user( 0, p) ;

        case WDIOC_KEEPALIVE: /*喂狗命令*/
            wdt_keepalive( ) ;
            return 0;

        case WDIOC_SETTIMEOUT: /*设置定时器溢出时间值命令(时间单位为秒)*/
            if ( get_user( new_margin, p) ) /*获取时间值*/
                return - EFAULT;
            if ( wdt_set_heartbeat( new_margin) ) /*设置到计数寄存器WTCNT中*/
                return - EINVAL;

            wdt_keepalive( ) ; /*喂狗*/
            return put_user( tmr_margin, p) ;

        case WDIOC_GETTIMEOUT: /*读取定时器默认溢出时间值命令(时间单位为秒)*/
            return put_user( tmr_margin, p) ;

        default :
            return - ENOTTY;
    }
}

4.设备移除、挂起和恢复接口函数

/*Watchdog平台驱动的设备移除接口函数的实现*/
static int __devexit wdt_remove( struct platform_device * dev)
{
    /*释放获取的Watchdog平台设备的IO资源*/
    release_resource( wdt_mem) ;
    kfree( wdt_mem) ;
    wdt_mem = NULL ;

    /*同watchdog_probe中中断的申请相对应,在那里申请中断,这里就释放中断*/
    free_irq( wdt_irqno, dev) ;
    wdt_irq = NULL ;

    /*释放获取的Watchdog平台设备的时钟*/
    clk_disable( wdt_clock) ;
    clk_put( wdt_clock) ;
    wdt_clock = NULL ;

    /*释放Watchdog设备虚拟地址映射空间*/
    iounmap( wdt_base) ;

    /*注销misc设备*/
    misc_deregister( & wdt_miscdev) ;

    return 0;
}

/*Watchdog平台驱动的设备关闭接口函数的实现*/
static void wdt_shutdown( struct platform_device * dev)
{
    /*停止看门狗定时器*/
    wdt_start_or_stop( 0) ;
}

/*对Watchdog平台设备驱动电源管理的支持。CONFIG_PM这个宏定义在内核中,
  当配置内核时选上电源管理,则Watchdog平台驱动的设备挂起和恢复功能均有效,
  这时候你应该明白了在第②步中为什么要有device_init_wakeup(&pdev->dev, 1)这句吧!!*/
# ifdef CONFIG_PM

/*定义两个变量来分别保存挂起时的WTCON和WTDAT值,到恢复的时候使用*/
static unsigned long wtcon_save;
static unsigned long wtdat_save;

/*Watchdog平台驱动的设备挂起接口函数的实现*/
static int wdt_suspend( struct platform_device * dev, pm_message_t state)
{
    /*保存挂起时的WTCON和WTDAT值*/
    wtcon_save = readl( wdt_base + S3C2410_WTCON) ;
    wtdat_save = readl( wdt_base + S3C2410_WTDAT) ;

    /*停止看门狗定时器*/
    wdt_start_or_stop( 0) ;

    return 0;
}

/*Watchdog平台驱动的设备恢复接口函数的实现*/
static int wdt_resume( struct platform_device * dev)
{
    /*恢复挂起时的WTCON和WTDAT值,注意这个顺序*/
    writel( wtdat_save, wdt_base + S3C2410_WTDAT) ;
    writel( wtdat_save, wdt_base + S3C2410_WTCNT) ;
    writel( wtcon_save, wdt_base + S3C2410_WTCON) ;

    return 0;
}

# else /*配置内核时没选上电源管理,Watchdog平台驱动的设备挂起和恢复功能均无效,这两个函数也就无需实现了*/
# define wdt_suspend NULL
# define wdt_resume NULL
# endif

具体参考大佬的例程,这位大佬真的好厉害!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值