hi3518c 看门狗驱动程序示例:
/*
function :
本模块支持三个加载参数: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);