watchdog
看门狗,又叫watchdog timer,是一个定时器电路,一般有一个输入,叫喂狗或踢狗;一个输出到MCU 的 RST 端,MCU 正常工作的时候,每隔一段时间输出一个信号到喂狗端,给 WDT 清零,如果超过规定的时间不喂狗(一般在程序跑飞时),WDT 定时超过,就会给出一个复位信号到 MCU,使 MCU 复位。防止 MCU 死机。
整体思路
- 内核模块初始化watchdog控制寄存器并使能watchdog
- 用户看门狗进程定时踢狗
一、用户空间代码分析
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <sys/time.h> #include <signal.h> #include <errno.h> #include <stdarg.h> static void die(const char *msg, ...) { va_list ap; va_start(ap, msg); fprintf(stderr, "%s: ERROR: ", "watchdog"); vfprintf(stderr, msg, ap); va_end(ap); exit(1); } static void watchdog_write_pidfile(void) { char pidfile[80]; char pidbuf[16]; int fd; int ret; snprintf(pidfile, sizeof(pidfile), "/var/run/%s.pid", "watchdog"); fd = open(pidfile, O_RDONLY); if (fd < 0) { if (errno != ENOENT) { die("watchdog_write_pidfile: opening pidfile %s for read: %s\n", pidfile, strerror(errno)); } /* ENOENT is good: the pidfile doesn\'t exist */ } else { /* the pidfile exists: read it and check whether the named pid is still around */ int pid; char *end; ret = read(fd, pidbuf, sizeof(pidbuf)); if (ret < 0) { die("watchdog_write_pidfile: read of pre-existing %s failed: %s\n", pidfile, strerror(errno)); } pid = strtol(pidbuf, &end, 10); if (*end != \'\0\' && *end != \'\n\') { die("watchdog_write_pidfile: couldn\'t parse \"%s\" as a pid (from file %s); " "aborting\n", pidbuf, pidfile); } ret = kill(pid, 0); /* try sending signal 0 to the pid to check it exists */ if (ret == 0) { die("watchdog_write_pidfile: %s contains pid %d which is still running; aborting\n", pidfile, pid); } /* pid doesn\'t exist, looks like we can proceed */ close(fd); } /* re-open pidfile for write, possibly creating it */ fd = open(pidfile, O_WRONLY|O_CREAT|O_TRUNC, 0644); if (fd < 0) die("watchdog_write_pidfile: open %s for write/create: %s\n", pidfile, strerror(errno)); snprintf(pidbuf, sizeof(pidbuf), "%d\n", getpid()); ret = write(fd, pidbuf, strlen(pidbuf)); if (ret < 0) { die("watchdog_write_pidfile: writing my PID to lockfile %s: %s\n", pidfile, strerror(errno)); } close(fd); } void watchdog_func() { FILE *file; file = fopen("/proc/watchdog_kick","w+"); if(file) { fputs("111", file); fclose(file); } } int main(int argc, char **argv) { pid_t pid; char tmpBuff[30] = {0}; int res = 0; int fd; int interval; int sec,micro_sec; sigset_t sigset; if(argc >= 2) interval = atoi(argv[1]); else interval = 500; if(interval >= 10000){ printf("watchdog interval too long,should not more than 10s\n"); interval = 1000; } sec = interval/1000; micro_sec = (interval % 1000) * 1000; watchdog_write_pidfile(); /* unblock sigalarm and sigterm signal */ sigaddset(&sigset,SIGALRM); if(sigprocmask(SIG_UNBLOCK,&sigset,NULL) < 0) printf("sigprocmask error\n"); // Register watchdog_func to SIGALRM signal(SIGALRM, watchdog_func); struct itimerval tick; memset(&tick, 0, sizeof(tick)); //printf("interval:%d.\n",interval); // Timeout to run function first time tick.it_value.tv_sec = sec; // sec tick.it_value.tv_usec = micro_sec; // micro sec. // Interval time to run function tick.it_interval.tv_sec = sec; tick.it_interval.tv_usec = micro_sec; pid = getpid(); snprintf(tmpBuff,30,"renice -19 %d",pid); system(tmpBuff); //stop watchdog first system("echo 1 > /proc/watchdog_kick"); system("echo enable 0 interval 0 > /proc/watchdog_cmd"); // resume watchdog #ifdef CONFIG_RTL_8197F system("echo enable 1 interval 32 > /proc/watchdog_cmd"); #else system("echo enable 1 interval 10 > /proc/watchdog_cmd"); #endif system("echo 1 > /proc/watchdog_kick"); res = setitimer(ITIMER_REAL, &tick, NULL); if (res) { printf("Set watchdog timer failed!!/n"); return -1; } while(1) { pause(); } return 0; }
用户进程主要做了两件事:
1. 用户进程设置watchdog intervel
echo enable 1 interval 32 > /proc/watchdog_cmd
2. 定时器定时踢狗
void watchdog_func() { FILE *file; file = fopen("/proc/watchdog_kick","w+"); if(file) { fputs("111", file); fclose(file); } }
二、内核部分
1. 看门狗定时器控制寄存器初始化
void bsp_enable_watchdog( void ) { bBspWatchdog = 1; *(volatile unsigned long *)(0xb800311C)=0x00240000; // 2^24 } void __init plat_time_init(void) // mips-ori { printk(COLOR_RED"[%s:%d] [watchdog] platform timer init\n"COLOR_CLEAR, __FUNCTION__, __LINE__); {/* 省略部分代码 */} #ifdef CONFIG_RTL_WTDOG /* 配置时钟分频寄存器 */ REG32(BSP_CDBR)=(BSP_DIVISOR) << BSP_DIVF_OFFSET; printk(COLOR_RED"[%s:%d] [watchdog] BSP enable watchdog, BSP_CDBR=0x%x\n"COLOR_CLEAR, \ __FUNCTION__, __LINE__, ((BSP_DIVISOR) << BSP_DIVF_OFFSET)); /* 使能watchdog */ bsp_enable_watchdog(); wtdog_cdbr = (REG32(BSP_CDBR) >> 16) & 0xffff; printk(COLOR_RED"[%s:%d] [watchdog] watchdog cdbr=%u\n"COLOR_CLEAR, __FUNCTION__, __LINE__, wtdog_cdbr); #endif /* CONFIG_RTL_WTDOG */ {/* 省略部分代码 */} }
看门狗控制寄存器初始化为:0x00240000,表示 OVSEL 的高两位是 10, OVSEL 的低两位是 01, OVSEL=1001
2. 创建proc节点(watchdog_cmd 和 watchdog_kick)
为应用层配置watchdog提供两个接口,watchdog_cmd用来设置interval,watchdog_kick用来踢狗
int __init bsp_watchdog_proc_init(void) { proc_create_data("watchdog_reboot", 0, &proc_root, &watchdog_reboot_proc_fops, NULL); #ifdef CONFIG_RTL_USERSPACE_WTDOG proc_create_data("watchdog_cmd", 0, &proc_root, &watchdog_cmd_proc_fops, NULL); proc_create_data("watchdog_kick", 0, &proc_root, &watchdog_kick_proc_fops, NULL); #endif return 0; }
2.1 watchdog_cmd的write接口
应用层向该节点写入watchdog interval的配置,该接口去配置watchdog寄存器的OVSEL位
如:echo enable 1 interval 32 > /proc/watchdog_cmd
static ssize_t watchdog_cmd_single_write(struct file * file, const char __user * userbuf, size_t count, loff_t * off) { char flag[64]; int enable,interval; extern void bsp_enable_watchdog(void); extern void bsp_disable_watchdog(void); if (count < 2) return -EFAULT; if (userbuf && !copy_from_user(&flag, userbuf, 63)) { int i; unsigned int wtdog_intervel,wtdog_intervel0 = 0,wtdog_cdbr,wtdog_maxtime; sscanf(flag,"enable %d interval %d",&enable,&interval); if (enable == 0) { /* disable watchdog */ bsp_disable_watchdog(); } else if (enable == 1) { if (watchdog_default_flag == 0) { watchdog_default_flag = 1; watchdog_default_val = interval; } else { if(interval < watchdog_default_val){ printk("\t\nwatchdog timeout time should not less than default val,default=%d\n",watchdog_default_val); return -1; } } wtdog_cdbr = (REG32(BSP_CDBR) >> 16) & 0xffff; i = sizeof(wtdog_tbl)/sizeof(WTDOG_REGTBL_T) - 1; /* interval计算方法 */ wtdog_maxtime = wtdog_tbl[i].wtdog_val/(LXBUS_CLOCK/wtdog_cdbr); if (interval > wtdog_maxtime) { printk("\t\n watchdog max intervale time is %d,please check the set value\n", wtdog_maxtime); return -1; } for(i = 0; i < sizeof(wtdog_tbl)/sizeof(WTDOG_REGTBL_T); i++) { wtdog_intervel = wtdog_tbl[i].wtdog_val/(LXBUS_CLOCK/wtdog_cdbr); printk(COLOR_GREEN"[%s:%d] [watchdog] Supported interval=%ds\n"COLOR_CLEAR, \ __FUNCTION__, __LINE__, wtdog_intervel); if (interval >= wtdog_intervel0 && interval <= wtdog_intervel) { goto END; } wtdog_intervel0 = wtdog_intervel; } END: printk(COLOR_GREEN"[%s:%d] [watchdog] Do watchdog control register set (index=%d).\n"COLOR_CLEAR, \ __FUNCTION__, __LINE__, i); printk(COLOR_GREEN"[%s:%d] [watchdog] oversel_l = 0x%x, oversel_h = 0x%x.\n"COLOR_CLEAR, \ __FUNCTION__, __LINE__, wtdog_tbl[i].oversel_l, wtdog_tbl[i].oversel_h); /* 设置wathdog寄存器 */ REG32(BSP_WDTCNR) = ( wtdog_tbl[i].oversel_l << 21) | ( wtdog_tbl[i].oversel_h << 17); } return count; } return -EFAULT; }
2.2 watchdog_kick的write接口
清空watchdog计数器(喂狗)
static ssize_t watchdog_kick_single_write(struct file * file, const char __user * userbuf, size_t count, loff_t * off) { char flag[20]; if (count < 2) return -EFAULT; #ifdef CONFIG_RTL_WTDOG { /*If kernel fault. reboot whole system so softwatch dog can not kick even*/ extern int is_fault; if(is_fault) return count; } #endif if (userbuf && !copy_from_user(&flag, userbuf, 1)) { if(flag[0] == \'1\'){ watchdog_kick_state = RTL_WATCHDOG_KICK; /* kick watchdog here */ *(volatile unsigned long *)(0xB800311c) |= 1 << 23; }else { watchdog_kick_state = 0; } return count; } return -EFAULT; }