嵌入式 hi3518c看门狗驱动程序相关示例

/* 
本模块支持三个加载参数:default_margin、nodeamon与nowayout。
三者的含义分别为:
 (1)default_margin:指定看门狗产生中断的时间间隔,以second为单位,模块默认的值为60。
    该值必须大于或等于0,设为0则采用默认值。
 (2)nodeamon:指定在加载模块的时候,是否建立一个守护进程来进行喂狗。为0则产生守护进程,非0值则不产生;
    默认的值为0。不管是否建立专门喂狗的守护进程,一旦在由用户创建的程序中打开了看门狗对应的设备文件,
    则在用户程序关闭设备文件之前必须由用户程序完成喂狗动作,否则系统在两次时间间隔中断产生之后会复位。
 (3)nowayout:设置在用户打开看门狗设备文件并在相关程序退出之后,是否允许模块被卸载。
    非0值则不允许卸载,这时一旦在由用户创建的程序中打开了看门狗对应的设备文件之后,
    如果没有用户程序完成喂狗操作,则系统在一定时间之后会进行复位;为0则允许卸载,
    并且这时如果不再有用户程序打开着看门狗的设备文件,则在开启专门喂狗的守护进程的情况下,
    由守护进程完成喂狗的动作。它默认的值为0,可通过调用write向设备文件写入带‘V’的字符串来设为1;
    如果写入的字符串不带有‘V’,则为0。
*/


#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/notifier.h>
#include <linux/reboot.h>
#include <linux/init.h>
#include <asm/uaccess.h>
#include <asm/io.h>
//#include <asm/arch/hardware.h>
#include <linux/kthread.h>
//#include <asm/arch/clock.h>
#include <linux/string.h>
#include <linux/sched.h>

#define OSDRV_MODULE_VERSION_STRING "HISI_WDT-MDC030001 @Hi3518v100"

#define HISILICON_SCTL_BASE 0x20050000
/* define watchdog IO */
#define HIWDT_BASE      0x20040000
#define HIWDT_REG(x)    (HIWDT_BASE + (x))

#define HIWDT_LOAD      0x000
#define HIWDT_VALUE     0x004
#define HIWDT_CTRL      0x008
#define HIWDT_INTCLR    0x00C
#define HIWDT_RIS       0x010
#define HIWDT_MIS       0x014
#define HIWDT_LOCK      0xC00

#define HIWDT_UNLOCK_VAL    0x1ACCE551

void __iomem *reg_ctl_base_va;
void __iomem *reg_wdt_base_va;
#define IO_WDT_ADDRESS(x) (reg_wdt_base_va + ((x)-(HIWDT_BASE)))

#define hiwdt_readl(x)      readl(IO_WDT_ADDRESS(HIWDT_REG(x)))
#define hiwdt_writel(v,x)   writel(v, IO_WDT_ADDRESS(HIWDT_REG(x)))

/* debug */
#define HIDOG_PFX "HiDog: "
#define hidog_dbg(params...) printk(KERN_INFO HIDOG_PFX params)

/* module param */
#define HIDOG_TIMER_MARGIN    60
static int default_margin = HIDOG_TIMER_MARGIN;    /* in seconds */
#define HIDOG_TIMER_DEMULTIPLY  9
module_param(default_margin, int, 0);
MODULE_PARM_DESC(default_margin, "Watchdog default_margin in seconds. (0<default_margin<80, default=" __MODULE_STRING(HIDOG_TIMER_MARGIN) ")");

static int nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, int, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");

static int nodeamon = 0;
module_param(nodeamon, int, 0);
MODULE_PARM_DESC(nodeamon, "By default, a kernel deamon feed watchdog when idle, set 'nodeamon=1' to disable this. (default=0)");

/* watchdog info */
static struct watchdog_info ident = {
    .options =  WDIOF_SETTIMEOUT |
                WDIOF_KEEPALIVEPING |
                WDIOF_MAGICCLOSE,
    .firmware_version = 0,
    .identity = "Hisilicon Watchdog",
};

/* local var */
static DEFINE_SPINLOCK(hidog_lock);
static int cur_margin = HIDOG_TIMER_MARGIN;
//static pid_t pid_hidog_deamon = -1;
struct task_struct *task_hidog_deamon = NULL;


#define HIDOG_EXIT    0
#define HIDOG_SELFCLR    1
#define HIDOG_EXTCLR    2
volatile static unsigned int hidog_state = 0;

static unsigned long driver_open = 0, orphan_timer = 0;
static int options = WDIOS_ENABLECARD;

#ifndef MHZ
#define MHZ (1000*1000)
#endif

static unsigned long rate = 3*MHZ;

static inline void hidog_set_timeout(unsigned int nr)
{
    unsigned long cnt = (~0x0)/rate;        /* max cnt */
    unsigned long flags; 

    spin_lock_irqsave(&hidog_lock, flags); 

    if( nr==0 || nr>cnt)
        cnt=~0x0; 
    else
        cnt = nr*rate;
    /* unlock watchdog registers */ 
    hiwdt_writel(HIWDT_UNLOCK_VAL, HIWDT_LOCK); 
    hiwdt_writel(cnt, HIWDT_LOAD); 
    hiwdt_writel(cnt, HIWDT_VALUE); 
    /* lock watchdog registers */ 
    hiwdt_writel(0, HIWDT_LOCK); 
    spin_unlock_irqrestore(&hidog_lock, flags); 
};

static inline void hidog_feed(void)
{
    unsigned long flags; 

    spin_lock_irqsave(&hidog_lock, flags); 
    /* unlock watchdog registers */ 
    hiwdt_writel(HIWDT_UNLOCK_VAL, HIWDT_LOCK); 
    /* clear watchdog */ 
    hiwdt_writel(0x00, HIWDT_INTCLR); 
    /* lock watchdog registers */ 
    hiwdt_writel(0, HIWDT_LOCK); 
    spin_unlock_irqrestore(&hidog_lock, flags); 
};

static int hidog_set_heartbeat(int t)
{
    int ret = 0;
    unsigned int cnt = (~0x0)/rate; 

    if( t==0 ) {
        printk(KERN_ERR HIDOG_PFX "set heartbeat to 0, heartbeat will not be changed.\n");
        t = cur_margin;
        ret = 1;
    } else if( t>cnt ) {
        printk(KERN_ERR HIDOG_PFX "set heartbeat range error, t = %d\n", t);
        printk(KERN_ERR HIDOG_PFX "force heartbeat to %d\n", cnt);
        t = cnt;
        ret = -1;
    }

    cur_margin = t;

    hidog_set_timeout(t);
    hidog_feed();

    if(NULL != task_hidog_deamon)
        wake_up_process(task_hidog_deamon);    

    return ret;
}

static int hidog_keepalive(void)
{
    hidog_feed();
    return 0;
}

static inline void hidog_start(void)
{
    unsigned long flags;
    unsigned long t;
    
    spin_lock_irqsave(&hidog_lock, flags);
    /* unlock watchdog registers */
    hiwdt_writel(HIWDT_UNLOCK_VAL, HIWDT_LOCK);
    hiwdt_writel(0x00, HIWDT_CTRL);
    hiwdt_writel(0x00, HIWDT_INTCLR);
    hiwdt_writel(0x03, HIWDT_CTRL);
    /* lock watchdog registers */
    hiwdt_writel(0, HIWDT_LOCK);
    /* enable watchdog clock --- set the frequency to 3MHz */
    t = readl(reg_ctl_base_va);
    writel(t & ~0x00800000, reg_ctl_base_va);
    spin_unlock_irqrestore(&hidog_lock, flags);
    
    options = WDIOS_ENABLECARD;
}

static inline void hidog_stop(void)
{
    unsigned long flags;

    spin_lock_irqsave(&hidog_lock, flags);

    /* unlock watchdog registers */
    hiwdt_writel(HIWDT_UNLOCK_VAL, HIWDT_LOCK);

    /* stop watchdog timer */
    hiwdt_writel(0x00, HIWDT_CTRL);
    hiwdt_writel(0x00, HIWDT_INTCLR);

    /* lock watchdog registers */
    hiwdt_writel(0, HIWDT_LOCK);

    spin_unlock_irqrestore(&hidog_lock, flags);

    hidog_set_timeout(0);

    options = WDIOS_DISABLECARD;
}

static int hidog_open(struct inode *inode, struct file *file)
{
    int ret = 0;

    if (test_and_set_bit(0, &driver_open))
        return -EBUSY;
    if (!test_and_clear_bit(0, &orphan_timer))
        __module_get(THIS_MODULE);
    /*
     *    Activate timer
     */
    hidog_keepalive();
    ret = nonseekable_open(inode, file);
    if(ret == 0)
        hidog_state = HIDOG_EXTCLR;

    return ret;
}

static int hidog_release(struct inode *inode, struct file *file)
{
    /*
     *    Shut off the timer.
     *     Lock it in if it's a module and we set nowayout
     */
    if (nowayout == 0) {
        //cur_margin = default_margin;
        hidog_state = HIDOG_SELFCLR;
        hidog_set_heartbeat(cur_margin);
        module_put(THIS_MODULE);
    } else {
        printk(KERN_CRIT HIDOG_PFX "Unexpected close, not stopping watchdog!\n");
        set_bit(0, &orphan_timer);
        hidog_keepalive();
    }
    clear_bit(0, &driver_open);

    if(options == WDIOS_DISABLECARD)
        printk(KERN_INFO HIDOG_PFX "Watchdog is disabled!\n");

    return 0;
}

static ssize_t hidog_write(struct file *file, const char __user *data, size_t len, loff_t *ppos)
{
    /*
     *      *    Refresh the timer.
     *           */
    if(len) {        
        size_t i;

        nowayout = 0;

        for (i = 0; i != len; i++) {
            char c;

            if (get_user(c, data + i))
                return -EFAULT;
            if (c == 'V')           
                nowayout = 1;
        }
        hidog_keepalive();
    }

    return len;
}

//static int hidog_ioctl(struct inode *inode, struct file *file,
//    unsigned int cmd, unsigned long arg)
static int hidog_ioctl(struct file *file,
    unsigned int cmd, unsigned long arg)
{
    void __user *argp = (void __user *)arg;
    int __user *p = argp;
    int new_margin;
    int new_options;

    switch (cmd) {
        case WDIOC_GETSUPPORT:
            return copy_to_user(argp, &ident,
                sizeof(ident)) ? -EFAULT : 0;
        case WDIOC_GETSTATUS:
        case WDIOC_GETBOOTSTATUS:
            return put_user(options, p);

        case WDIOC_KEEPALIVE:
            hidog_keepalive();
            return 0;

        case WDIOC_SETTIMEOUT:
            if (get_user(new_margin, p))
                return -EFAULT;
            if (hidog_set_heartbeat(new_margin))
                return -EINVAL;
            hidog_keepalive();
            return 0;

        case WDIOC_GETTIMEOUT:
            return put_user(cur_margin, p);

        case WDIOC_SETOPTIONS:
            if(nowayout)
                return -WDIOS_UNKNOWN;

            if (get_user(new_options, p))
                return -EFAULT;
            if(new_options & WDIOS_ENABLECARD) {
                hidog_start();
                hidog_set_heartbeat(cur_margin);
                return 0;
            } else if (new_options & WDIOS_DISABLECARD) {
                hidog_stop();
                return 0;
            } else
                return -WDIOS_UNKNOWN;

        default:
            return -ENOIOCTLCMD;
    }

}

/*
 *    Notifier for system down
 */

static int hidog_notifier_sys(struct notifier_block *this, unsigned long code,
    void *unused)
{
    if(code==SYS_DOWN || code==SYS_HALT) {
        /* Turn the WDT off */
        hidog_stop();
    }
    return NOTIFY_DONE;
}

/*
 *    Kernel Interfaces
 */

static struct file_operations hidog_fops = {
    .owner        = THIS_MODULE,
    .llseek        = no_llseek,
    .write        = hidog_write,
//    .ioctl        = hidog_ioctl,
    .unlocked_ioctl = hidog_ioctl,
    .open        = hidog_open,
    .release    = hidog_release,
};

static struct miscdevice hidog_miscdev = {
    .minor        = WATCHDOG_MINOR,
    .name        = "watchdog",
    .fops        = &hidog_fops,
};

static struct notifier_block hidog_notifier = {
    .notifier_call    = hidog_notifier_sys,
};

static char banner[] __initdata = KERN_INFO "Hisilicon Watchdog Timer: 0.01 initialized. default_margin=%d sec (nowayout= %d, nodeamon= %d)\n";

static int hidog_deamon(void *data)
{
    struct sched_param param = { .sched_priority = 99 };

    sched_setscheduler(current, SCHED_FIFO, ¶m);
    current->flags |= PF_NOFREEZE;

    set_current_state(TASK_INTERRUPTIBLE);

    while(hidog_state != HIDOG_EXIT)
    {
        switch(hidog_state) {
        case HIDOG_SELFCLR:
            if(options & WDIOS_ENABLECARD)
                hidog_feed();
            break;
        case HIDOG_EXTCLR:
            break;
        default:
            break;
        }
        /* sleep */
        /* when self feed dog, only use the default margin */
        schedule_timeout_interruptible(msecs_to_jiffies(default_margin*1000/2)+1);
    }

    set_current_state(TASK_RUNNING);

    return 0;
}

static int __init hidog_init(void)
{
    hidog_start();

    hidog_state = HIDOG_SELFCLR;
    if(!nodeamon) {
        #if 0
        struct task_struct *p_dog;
        p_dog = kthread_create(hidog_deamon, NULL, "hidog");
        if(IS_ERR(p_dog) <0) {
            printk(KERN_ERR HIDOG_PFX "create hidog_deamon failed!\n");
            return -1;        
        pid_hidog_deamon = p_dog->pid;        
        #endif

        struct task_struct *p_dog;
		p_dog = kthread_create(hidog_deamon, NULL, "hidog");
		if(IS_ERR(p_dog) <0) {
			printk(KERN_ERR HIDOG_PFX "create hidog_deamon failed!\n");
			return -1;
		}
		wake_up_process(p_dog);
		task_hidog_deamon = p_dog;
    }
    //printk(KERN_INFO OSDRV_MODULE_VERSION_STRING);
    return 0;
}

static int __init watchdog_init(void)
{
    int ret = 0;

    reg_wdt_base_va = ioremap_nocache((unsigned long)HIWDT_BASE, (unsigned long)0x10000);
    if (NULL == reg_wdt_base_va)
    {
        printk(KERN_ERR HIDOG_PFX "function %s line %u failed\n", 
            __FUNCTION__, __LINE__);
        return -1;
    }
    reg_ctl_base_va = ioremap_nocache((unsigned long)HISILICON_SCTL_BASE, 4);
    if (NULL == reg_ctl_base_va)
    {
        printk(KERN_ERR HIDOG_PFX "function %s line %u failed\n", 
            __FUNCTION__, __LINE__);
        iounmap(reg_wdt_base_va);
        return -1;
    }
    
    cur_margin = default_margin;

    /* Check that the default_margin value is within it's range ; if not reset to the default */
    if(hidog_set_heartbeat(default_margin)) {
        default_margin = HIDOG_TIMER_MARGIN;
        hidog_set_heartbeat(HIDOG_TIMER_MARGIN);
        printk(KERN_WARNING HIDOG_PFX "default_margin value must be 0<default_margin<%lu, using %d\n",
            ~0x0/rate, HIDOG_TIMER_MARGIN);
    }

    ret = register_reboot_notifier(&hidog_notifier);
    if(ret) {
        printk(KERN_ERR HIDOG_PFX "cannot register reboot notifier (err=%d)\n", ret);
        goto watchdog_init_errA;
    }

    ret = misc_register(&hidog_miscdev);
    if(ret) {
        printk (KERN_ERR HIDOG_PFX "cannot register miscdev on minor=%d (err=%d)\n",
            WATCHDOG_MINOR, ret);
        goto watchdog_init_errB;
    }

    printk(banner, default_margin, nowayout, nodeamon);

    ret = hidog_init();
    if(ret) {
        goto watchdog_init_errC;
    }
    //printk(KERN_INFO OSDRV_MODULE_VERSION_STRING "\n");
    return ret;

watchdog_init_errC:
    misc_deregister(&hidog_miscdev);
watchdog_init_errB:
    unregister_reboot_notifier(&hidog_notifier);
watchdog_init_errA:
    return ret;
}

static void __exit hidog_exit(void)
{
    hidog_set_timeout(0);
    hidog_stop();
#if 0
    for(hidog_state=HIDOG_EXIT; !nodeamon; ) {
        #if 0
		struct task_struct *p_dog = find_task_by_vpid(pid_hidog_deamon);    
        if(p_dog == NULL)
            break;
        #endif
        
        struct task_struct *p_dog = task_hidog_deamon;
		if(p_dog == NULL)
			break;
		wake_up_process(p_dog);
		yield();
    }
#endif
    hidog_state=HIDOG_EXIT;
    if(!nodeamon) {
        struct task_struct *p_dog = task_hidog_deamon;
        if(p_dog == NULL)
            return;
        wake_up_process(p_dog);
        kthread_stop(p_dog);
        yield();
    }
    
    task_hidog_deamon = NULL;
}

static void __exit watchdog_exit(void)
{    
    misc_deregister(&hidog_miscdev);
    unregister_reboot_notifier(&hidog_notifier);

    hidog_exit();
    iounmap(reg_ctl_base_va);
    iounmap(reg_wdt_base_va);
}

module_init(watchdog_init);
module_exit(watchdog_exit);

MODULE_AUTHOR("Liu Jiandong");
MODULE_DESCRIPTION("Hisilicon Watchdog Device Driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
MODULE_VERSION("HI_VERSION=" OSDRV_MODULE_VERSION_STRING);


你可能感兴趣的:(嵌入式 hi3518c看门狗驱动程序相关示例)