本次来开发自己的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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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
#include
#include
#include
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之后,系统就会重启了.