本次来开发自己的watchdog驱动程序.watchdog是系统的硬件守护者,在Linux里面的守护进程则是软件守护者,两者维护的对象有一些差异,这里先暂不做讨论,直接说看门狗的驱动:
在三星平台片上集成了看门狗外设,这种外设只有4个寄存器,别看寄存器比较少,但是它使用的频率是很高的,从结构上看,可以把它看作一个定时器,当然,看门狗也可以作为定时器使用,我们在驱动代码里面已经写了一个开关,开关为0时,作为看门狗复位使用,开关为1时,作为定时器使用,因为,片上已经集成了很多专门的定时器,所以,一般没必要用看门狗去做定时器用,所以,我们写的这个驱动里面没有实现ioctl,在ioctl方法里实际上可以设置看门狗模式,超时时间等等,暂时没有写.我们来看一下三星官方提供的datasheet:
从上图可以看出,一共有4个寄存器,这4个寄存器的作用可以参考下图:
结合图和datasheet,可以知道寄存器的作用如下:
WTCON: 看门狗配置寄存器
WTDAT: 看门狗数据寄存器
WTCNT: 看门狗计数寄存器
WTCLRINT:看门狗清中断寄存器
我们这个驱动主要是实现的是看门狗复位,没有用作定时器,所以WTCLRINT不需要用到,所以没有配置,它的作用就是在配置为中断模式时,随便往这个寄存器写一个不超过0xffffffff的值,就可以清除中断,我们看看官方怎们说的:
"你能使用WTCLRINT寄存器清除中断, 中断服务函数有责任在中断服务完成之后清除中断,方法是写任意值到这个寄存器,不允许读取这个寄存器"(虽然说是任意值,但不要超过0xffffffff,否则会寄存器溢出,因为它是32bit寄存器)
顾名思义喽,如果配制成中断模式,在中断服务函数里执行完后,要往此寄存器写一个值,以清除中断.
下面,我们来看一下其它几个寄存器都是干嘛的:
WTCON配置寄存器只使用了低16位,第0为配置重启是否有效,第2位配置中断是否有效,第[4:3]这两位配置二级时钟分频,第5位是看门狗使能位,第[15:8]这8位是看门狗时钟预分频位,可以配置为0~255,因为配置中不允许是0,所以,实际设计当中是[15:8] + 1,也就是说,配置的数再加1,比如寄存器配置的是255,那么芯片会自动加1,也就是256.从而可以避免是0这种情况发生.
然后接下来是WTDAT和WTCNT寄存器:
它们的作用是WTDAT存储重载数值,WTCNT则是存储计数器值,这个值会递减,当递减到0,正常情况下,则WTDAT的值会重载入WTCNT,从上图可以看出,它们只用到了寄存器的低16位,也就是说其最大值是65535(0xffff).
好了了解了寄存器之后,我们就直接上代码,代码里关键地方有写注释,所以设计思路就不讲了,直接看代码(参考s3c2410_wdt.c):
#include <linux/init.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/gpio.h> #include <linux/platform_device.h> #include <linux/types.h> #include <linux/timer.h> #include <linux/miscdevice.h> #include <linux/watchdog.h> #include <linux/interrupt.h> #include <linux/clk.h> #include <linux/uaccess.h> #include <linux/io.h> #include <linux/cpufreq.h> #include <linux/err.h> #include <linux/of.h> #include <mach/map.h> #include <plat/regs-watchdog.h> #undef S3C_VA_WATCHDOG #define S3C_VA_WATCHDOG (0) #ifdef CONFIG_CPU_FREQ #undef CONFIG_CPU_FREQ #endif #define CONFIG_S3C2410_WATCHDOG_ATBOOT (0) #define CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME (15) struct wdt_object{ bool nowayout; // 是否允许看门狗被关闭 int tmr_margin; // 默认喂狗时间 int tmr_atboot; // 系统启动时是否启动看门狗 int soft_noboot; // 看门狗工作模式,0:复位 1,中断 unsigned int wdt_count; void __iomem *wdt_base; struct device *wdt_dev; struct resource *wdt_mem; struct resource *wdt_irq; struct clk *wdt_clock; }; struct wdt_object *wdt_drv; static DEFINE_SPINLOCK(wdt_lock); void __exynos4_wdt_stop(void) { unsigned long wtcon; wtcon = readl(wdt_drv->wdt_base + S3C2410_WTCON); wtcon &= ~(S3C2410_WTCON_ENABLE | S3C2410_WTCON_RSTEN); writel(wtcon, wdt_drv->wdt_base + S3C2410_WTCON); } int exynos4_wdt_start(struct watchdog_device *wdd) { unsigned long wtcon; spin_lock(&wdt_lock); __exynos4_wdt_stop(); wtcon = readl(wdt_drv->wdt_base + S3C2410_WTCON); wtcon |= S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128; // 设置看门狗工作模式, 0,复位, 1,中断 if(wdt_drv->soft_noboot){ wtcon |= S3C2410_WTCON_INTEN; wtcon &= ~S3C2410_WTCON_RSTEN; } else { wtcon &= ~S3C2410_WTCON_INTEN; wtcon |= S3C2410_WTCON_RSTEN; } // 把配置后的参数回写寄存器 writel(wdt_drv->wdt_count, wdt_drv->wdt_base + S3C2410_WTDAT); writel(wdt_drv->wdt_count, wdt_drv->wdt_base + S3C2410_WTCNT); writel(wtcon, wdt_drv->wdt_base + S3C2410_WTCON); spin_unlock(&wdt_lock); return 0; } int exynos4_wdt_stop(struct watchdog_device *wdd) { spin_lock(&wdt_lock); __exynos4_wdt_stop(); spin_unlock(&wdt_lock); return 0; } int exynos4_wdt_keeplive(struct watchdog_device *wdd) { spin_lock(&wdt_lock); writel(wdt_drv->wdt_count, wdt_drv->wdt_base + S3C2410_WTCNT); spin_unlock(&wdt_lock); return 0; } int exynos4_wdt_set_heartbeat(struct watchdog_device *wdd, unsigned int timeout) { unsigned long freq = clk_get_rate(wdt_drv->wdt_clock); // 获得watchdog时钟频率 unsigned long wtcon = 0; // 配置WTCON值的临时变量 unsigned int count; // WTCNT的计数值 unsigned int divisor = 1; // WTCON[15:8]的预分频系数 if(timeout < 1) return -EINVAL; freq /= 128; // 默认使用128位分频 count = timeout * freq; // 总计数值 = 秒数 * 每秒的时钟滴答数 // WTCNT是一个16位寄存器,最大值为65535(C中有0的缘故使用65535),也就是0xffff if(count >= 0x10000){ // divisor使用的是WTCON的高8位,所以最大值是255,由于可以配置0,所以设计之初就自动加1,防止是0,所以最大值是255 + 1 for(divisor = 1; divisor <= 0x100; divisor++){ if((count / divisor) < 0x10000) break; } // 经过上述分频(预分频和二级分频)之后,如果值仍然大于等于0x10000则返回出错 if((count / divisor) >= 0x10000){ printk("timeout %d too big\n", timeout); return -EINVAL; } } count /= divisor; // 分频后,最终计数值 wdt_drv->wdt_count = count; // 配置WTCON wtcon = readl(wdt_drv->wdt_base + S3C2410_WTCON); wtcon &= ~S3C2410_WTCON_PRESCALE_MASK; wtcon |= S3C2410_WTCON_PRESCALE(divisor - 1); // 回写寄存器 writel(count, wdt_drv->wdt_base + S3C2410_WTDAT); writel(wtcon, wdt_drv->wdt_base + S3C2410_WTCON); // 最终的设置超时值 wdd->timeout = (count * divisor) / freq; return 0; } #define OPTIONS (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE) static const struct watchdog_info exynos4_wdt_ident = { .options = OPTIONS, .firmware_version = 0, .identity = "S3C2410 Watchdog" }; static struct watchdog_ops exynos4_wdt_ops = { .owner = THIS_MODULE, .start = exynos4_wdt_start, .stop = exynos4_wdt_stop, .ping = exynos4_wdt_keeplive, .set_timeout = exynos4_wdt_set_heartbeat, }; struct watchdog_device exynos4_wdd = { .info = &exynos4_wdt_ident, .ops = &exynos4_wdt_ops, }; irqreturn_t wdt_irq_handler(int inqno, void *dev_id) { // 我个人感觉这里除了喂狗之外,还需要往WTCLRINT中写一个不大于0xffffffff的值,以清除中断,没有测试,有机会再试试 exynos4_wdt_keeplive(&exynos4_wdd); return IRQ_HANDLED; } static inline int exynos4_wdt_is_running(void) { return readl(wdt_drv->wdt_base + S3C2410_WTCON) & S3C2410_WTCON_ENABLE; } #ifdef CONFIG_CPU_FREQ /* 当CPU频率发生改变后,就会通知所以注册CPU频率通知链的所有模块,并执行绑定的相应函数,此处正是看门狗相关的执行函数 */ static int exynos4_wdt_cpufreq_transition(struct notifier_block *nb, unsigned long val, void *data) { int ret; if(!exynos4_wdt_is_running()) goto done; if(CPUFREQ_PRECHANGE == val){ exynos4_wdt_keeplive(&exynos4_wdd); } else if(CPUFREQ_POSTCHANGE == val){ exynos4_wdt_stop(&exynos4_wdd); // 动态调整WTCON的分频系数和WTDAT的数值 ret = exynos4_wdt_set_heartbeat(&exynos4_wdd, exynos4_wdd.timeout); if(ret >= 0) exynos4_wdt_start(&exynos4_wdd); else return ret; } done: return 0; } static struct notifier_block exynos4_wdt_cpufreq_transition_nb = { .notifier_call = exynos4_wdt_cpufreq_transition, }; static inline int exynos4_wdt_cpufreq_register(void) { return cpufreq_register_notifier(&exynos4_wdt_cpufreq_transition_nb, CPUFREQ_TRANSITION_NOTIFIER); } static inline void exynos4_wdt_cpufreq_deregister(void) { cpufreq_unregister_notifier(&exynos4_wdt_cpufreq_transition_nb, CPUFREQ_TRANSITION_NOTIFIER); } #else static inline int exynos4_wdt_cpufreq_register(void) { return 0; } static inline void exynos4_wdt_cpufreq_deregister(void) { } #endif int exynos4_wdt_probe(struct platform_device *pdev) { int started = 0, ret = -1, size = 0; printk("--------%s--------\n", __func__); // 1,申请设备对象 wdt_drv = kzalloc(sizeof(struct wdt_object), GFP_KERNEL); if(NULL == wdt_drv){ printk("kzalloc failed!\n"); return -ENOMEM; } // 2,预置看门狗状态 wdt_drv->nowayout = WATCHDOG_NOWAYOUT; // 不允许看门狗关闭 wdt_drv->tmr_margin = CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME; // 设置看门狗复位时间15s wdt_drv->tmr_atboot = CONFIG_S3C2410_WATCHDOG_ATBOOT; // 系统启动时同时启动看门狗 // 3,记录设备dev wdt_drv->wdt_dev = &pdev->dev; // 4,获取看门狗内存资源 wdt_drv->wdt_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); if(NULL == wdt_drv->wdt_mem){ printk("platform get memory failed !\n"); goto err1; } // 5,获取看门狗中断资源 wdt_drv->wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); if(NULL == wdt_drv->wdt_irq){ printk("platform get irq failed !\n"); goto err2; } // 6,获取内存资源长度,并申请一块IO内存,对应看门狗的3个寄存器 size = resource_size(wdt_drv->wdt_mem); if(!request_mem_region(wdt_drv->wdt_mem->start, size, pdev->name)){ printk("request mem failed !\n"); ret = -EBUSY; goto err2; } // 7,将设备内存映射到虚拟空间 wdt_drv->wdt_base = ioremap(wdt_drv->wdt_mem->start, size); if(NULL == wdt_drv->wdt_base){ printk("ioremap failed !\n"); ret = -EINVAL; goto err3; } // 8,获取watchdog时钟频率 wdt_drv->wdt_clock = clk_get(&pdev->dev, "watchdog"); if(IS_ERR(wdt_drv->wdt_clock)){ printk("clk get failed !\n"); ret = PTR_ERR(wdt_drv->wdt_clock); goto err4; } // 9,使有效watchdog时钟 clk_enable(wdt_drv->wdt_clock); // 10,注册CPU频率通知链 ret = exynos4_wdt_cpufreq_register(); if(ret < 0){ printk("cpu frequency register failed !\n"); goto err5; } // 11,设置看门狗复位时间tmr_margin,如果设置时间不合法,返回非0,并重置为默认时间 if(exynos4_wdt_set_heartbeat(&exynos4_wdd, wdt_drv->tmr_margin)){ started = exynos4_wdt_set_heartbeat(&exynos4_wdd, CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME); if(0 == started) printk("wdt used default time !\n"); else printk("default time is fault, cannot start\n"); } // 12,申请中断,并注册中断处理函数 ret = request_irq(wdt_drv->wdt_irq->start, wdt_irq_handler, 0, pdev->name, pdev); if(ret != 0){ printk("request irq failed !\n"); goto err6; } // 13,设置nowayout进status watchdog_set_nowayout(&exynos4_wdd, wdt_drv->nowayout); // 14,注册看门狗驱动 ret = watchdog_register_device(&exynos4_wdd); if(ret){ printk("watchdog register failed !\n"); goto err7; } // 15,判断是否开机就启动watchdog,并做出相应动作 if(wdt_drv->tmr_atboot && 0 == started){ printk("starting watchdog timer...\n"); exynos4_wdt_start(&exynos4_wdd); } else if(!wdt_drv->tmr_atboot){ exynos4_wdt_stop(&exynos4_wdd); } return 0; err7: free_irq(wdt_drv->wdt_irq->start, pdev); err6: exynos4_wdt_cpufreq_deregister(); err5: clk_disable(wdt_drv->wdt_clock); clk_put(wdt_drv->wdt_clock); wdt_drv->wdt_clock = NULL; err4: iounmap(wdt_drv->wdt_base); err3: release_mem_region(wdt_drv->wdt_mem->start, size); err2: wdt_drv->wdt_irq = NULL; wdt_drv->wdt_mem = NULL; err1: kfree(wdt_drv); return ret; } int exynos4_wdt_remove(struct platform_device *pdev) { watchdog_unregister_device(&exynos4_wdd); free_irq(wdt_drv->wdt_irq->start, pdev); exynos4_wdt_cpufreq_deregister(); clk_disable(wdt_drv->wdt_clock); clk_put(wdt_drv->wdt_clock); wdt_drv->wdt_clock = NULL; iounmap(wdt_drv->wdt_base); release_mem_region(wdt_drv->wdt_mem->start, resource_size(wdt_drv->wdt_mem)); wdt_drv->wdt_irq = NULL; wdt_drv->wdt_mem = NULL; kfree(wdt_drv); return 0; } void exynos4_wdt_shutdown(struct platform_device *pdev) { exynos4_wdt_stop(&exynos4_wdd); } #if defined CONFIG_PM static unsigned long wtcon_save; static unsigned long wtdat_save; int exynos4_wdt_suspend(struct platform_device *pdev, pm_message_t state) { wtcon_save = readl(wdt_drv->wdt_base + S3C2410_WTCON); wtdat_save = readl(wdt_drv->wdt_base + S3C2410_WTDAT); exynos4_wdt_stop(&exynos4_wdd); return 0; } int exynos4_wdt_resume(struct platform_device *pdev) { writel(wtcon_save, wdt_drv->wdt_base + S3C2410_WTCON); writel(wtdat_save, wdt_drv->wdt_base + S3C2410_WTDAT); writel(wtdat_save, wdt_drv->wdt_base + S3C2410_WTCNT); return 0; } #else #define exynos4_wdt_suspend NULL #define exynos4_wdt_resume NULL #endif static const struct of_device_id exynos4_wdt_match[] = { {.compatible = "samsung,s3c2410-wdt"}, {}, }; struct platform_driver exynos4_wdt_drv = { .driver = { .owner = THIS_MODULE, .name = "s3c2410-wdt", .of_match_table = of_match_ptr(exynos4_wdt_match), }, .probe = exynos4_wdt_probe, .remove = exynos4_wdt_remove, .shutdown=exynos4_wdt_shutdown, .suspend= exynos4_wdt_suspend, .resume = exynos4_wdt_resume, }; static void __exit exynos4_wdt_exit(void) { platform_driver_unregister(&exynos4_wdt_drv); } static int __init exynos4_wdt_init(void) { return platform_driver_register(&exynos4_wdt_drv); } module_init(exynos4_wdt_init); module_exit(exynos4_wdt_exit); MODULE_LICENSE("GPL");
驱动程序使用了平台总线,平台数据定义在devs.c里面,可以自己去看一下,然后,再来一个测试代码:
#include <stdio.h> #include <unistd.h> #include <linux/watchdog.h> #include <fcntl.h> int main(void) { int fd, val, ret; fd = open("/dev/watchdog", O_RDWR); if(fd < 0){ printf("open device fail\n"); return -1; } while(1){ ret = write(fd, &val, sizeof(val)); if(ret < 0){ perror("watchdog write wrong\n"); return -1; } printf("nihao 0419 watchdog test!\n"); sleep(1); } if(close(fd) < 0){ perror("close failed"); return -1; } return 0; }
本次,我们直接让此驱动和内核一块加载,所以我们需要做以下事情:
1).首先把驱动文件放在\drivers\watchdog\目录下,然后修改此目录下的Kconfig和Makefile文件,如下:
// Kconfig里增加如下代码 config EXYNOS4_WATCHDOG tristate "EXYNOS4 Watchdog" depends on HAVE_S3C2410_WATCHDOG select WATCHDOG_CORE help customer watchdog demo test
// Makefile里增加如下代码 obj-$(CONFIG_EXYNOS4_WATCHDOG) += exynos4_wdt.o
然后,我们配置一下menuconfig,改成如下:
如上,把S3C2410_Watchdog去掉,把EXYNOS4_Watchdog选上,重新编译内核,然后开发板加载新内核,我们可以看到如下信息:
对,我们在驱动程序里有让probe方法打印函数名,说明开机加载成功,然后,用我们的测试程序进行测试,因为我们在看门狗里配置的是15s,测试程序里是每秒都写一次看门狗,所以,不会重启系统,但是这时,我们按下Ctrl + C,程序停止了,然后等15s之后,系统就会重启了.