2016年(23)
分类: 嵌入式
2016-06-28 15:33:01
看门狗是当CPU进入错误状态后,无法恢复的情况下,使计算机重新启动
由于计算机在工作时不可避免的受到各种各样的因素干扰,即使再优秀的计算机程序也可能因为这种干扰使计算机进入一个死循环,更严重的就是导致死机。
有两种办法来处理这种情况:
一是:采用人工复位的方法
二是:依赖于某种硬件来执行这个复位工作。这种硬件通常叫做看门狗(Watch Dog,WD)
看门狗,就像一只狗一样,在那看着们,计算机中通常用定时器来处理这种周期性的动作
看门狗实际上是一个定时器,其硬件内部维护了一个定时器,每当时钟信号到来时,计数寄存器减1。如果减到0,则系统重启(就像狗一样,看你不认识就咬你,可不管你是谁)。
如果在减到0之前,系统又设置计数寄存器一个较大的值,那么系统永远不会重启。系统的这种设置能力表示系统一直处于一种正常运行状态。反之,如果计算机系统崩溃,那么就无法重新设置计数寄存器的值。当计数寄存器为0,系统重启
看门狗的工作原来很简单,处理器内部一般都集成了一个看门狗硬件。其提供了三个寄存器
看门狗控制寄存器(WTCON)
看门狗数据寄存器(WTDAT)
看门狗计数寄存器(WTCNT)
结合上图可知,看门狗从一个PCLK频率到产生一个RESET复位信号的过程如下:
1,处理器向看门狗提供一个PCLK时钟信号。其通过一个8位预分频器(8-bit Prescaler)使频率降低
2,8位预分频器由控制寄存器WTCON的第8~15位决定。分频后的频率就相当于PCLK除以(WTCON[15:8]+1).
3,然后再通过一个4相分频器,分成4种大小的频率。这4种频率系数分别是16,32,64,128.看门狗可以通过寄存器的3,4位决定使用哪种频率
4,当选择的时钟频率到达计数器(Down Counter)时,会按照工作频率将WTCNT减1.当达到0时,就会产生一个中断信号或者复位信号
5,如果控制寄存器WTCOON的第二位为1,则发出一个中断信号;如果控制寄存器WTCON第0位为1,则输出一个复位信号,使系统重新启动
看门狗驱动涉及两种设备模型,分别是平台设备和混杂设备
平台设备模型:
从Linux2.6起引入了一套新的驱动管理和注册模型,即平台设备platform_device和平台驱动platform_driver.Linux中大部分的设备驱动,都可以使用这套机制,设备用platform_device表示,驱动用platform_driver表示
平台设备模型与传统的device和driver模型相比,一个十分明显的优势在于平台设备模型将设备本身的资源注册进内核,由内核统一管理。这样提高了驱动和资源管理的独立性,并且拥有较好的可移植性和安全性。通过平台设备模型开发底层驱动的大致流程为下图:
平台设备是指处理器上集成的额外功能的附加设备,如Watch Dog,IIC,IIS,RTC,ADC等设备。这些额外功能设备是为了节约硬件成本、减少产品功耗、缩小产品形状而集成到处理器内部的。需要注意的是,平台设备并不是与字符设备、块设备和网络设备并列的概念,而是一种平行的概念,其从另一个角度对设备进行了概括。如果从内核开发者的角度来看,平台设备的引入,是为了更容易开发字符设备、块设备和网络设备驱动
平台设备结构体(platform_device)
struct platform_device
{
const char *name; //平台设备的名字,与驱动的名字对应
int id; //与驱动绑定有关,一般为-1
struct device dev; //设备结构体说明platform_device派生于device
u32 num_resources; //设备使用的资源数量
struct resource *resource; //指向资源的数组,数量由num_resources指定
};
看门狗的平台设备为:
struct platform_device s3c_device_wdt = {
.name = "s3c2410-wdt",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_wdt_resource),
.resource = s3c_wdt_resource,
};
为了统一管理平台设备的资源,在platform_device机构体中定义了平台设备所使用的资源。
看门狗的资源如下:
static struct resource s3c_wdt_resource[] = {
[0] = { // I/O资源指向看门狗的寄存器
.start = S3C24XX_PA_WATCHDOG, //看门狗I/O内存开始位置,被定义为WTCON的地址0x53000000
.end = S3C24XX_PA_WATCHDOG + S3C24XX_SZ_WATCHDOG - 1, //1M的地址空间
.flags = IORESOURCE_MEM, //I/O内存资源
},
[1] = { //IRQ资源
.start = IRQ_WDT, //看门狗的开始中断号,被定义为80
.end = IRQ_WDT, //看门狗的结束中断号
.flags = IORESOURCE_IRQ, //中断的IRQ资源
}
};
struct resource
{
resource_size_t start; //资源的开始地址,resource_size是32位或者64位的无符号整数
resource_size_t end; //资源的结束地址
const char *name; //资源名
unsigned long flags; //资源的类型 (IORESOURCE_IO,IORESOURCE_MEM,IORESOURCE_IRQ,IORESOURCE_DMA等)
struct resource *parent,*sibling,*child; //用于构建资源的树形结构
};
通过platfrom_add_devices()函数可以将一组设备添加到系统中,其主要完成以下两个功能:
1,分配平台设备所使用的资源,并将这些资源挂接到资源树中
2,初始化device设备,并将设备注册到系统中
第一个参数是平台设备数组的指针,第2个参数是平台设备的数量
int platform_add_devices(struct platform_device **devs, int num)
{
int i, ret = 0;
for (i = 0; i < num; i++) {
ret = platform_device_register(devs[i]);
if (ret) {
while (--i >= 0)
platform_device_unregister(devs[i]);
break;
}
}
return ret;
}
通过platform_get_resource()函数可以获得平台设备的resource资源:
第一个参数dev是平台设备的指针,第2个参数type是资源的类型,这些类型可以是(IORESOURCE_IO,IORESOURCE_MEM,IORESOURCE_IRQ,IORESOURCE_DMA等),第3个参数num是同种资源的索引。例如一个平台设备有3哥IORESOURCE_MEM资源,如果要获得第2个资源,那么需要使num等于1
struct resource *platform_get_resource(struct platform_device *dev,unsigned int type, unsigned int num)
{
int i;
for (i = 0; i < dev->num_resources; i++) {
struct resource *r = &dev->resource[i];
if (type == resource_type(r) && num-- == 0)
return r;
}
return NULL;
}
平台设备驱动 :
每一个平台设备都对应一个平台设备驱动,这个驱动用来对平台设备进行探测、移除、关闭和电源管理等操作。
struct platform_driver {
int (*probe)(struct platform_device *); //探测函数
int (*remove)(struct platform_device *); //移除函数
void (*shutdown)(struct platform_device *); //关闭设备时调用该函数
int (*suspend)(struct platform_device *, pm_message_t state); //挂起函数
int (*suspend_late)(struct platform_device *, pm_message_t state); //挂起之后调用的函数
int (*resume_early)(struct platform_device *); //恢复正常状态之前调用的函数
int (*resume)(struct platform_device *); //恢复正常状态的函数
struct device_driver driver; //设备驱动核心结构
};
看门狗的平台设备驱动:
static struct platform_driver s3c2410wdt_driver = {
.probe = s3c2410wdt_probe,
.remove = s3c2410wdt_remove,
.shutdown = s3c2410wdt_shutdown,
.suspend = s3c2410wdt_suspend,
.resume = s3c2410wdt_resume,
.driver = {
.owner = THIS_MODULE,
.name = "s3c2410-wdt",
},
};
一般来说,在内核启动时,会注册平台设备和平台设备驱动程序。内核将在适当的时候,将平台设备和平台驱动连接起来。连接的方法,是用系统中的所有平台设备和所有已经注册的平台驱动进行匹配。下面是源代码:
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev;
pdev = container_of(dev, struct platform_device, dev);
return (strcmp(pdev->name, drv->name) == 0);
}
该函数将由内核自己调用,当设备找到对应的驱动时,会触发probe函数。所以,probe函数一般是驱动程序加载成功后的第一个调用函数,在该函数中可以申请设备所需要的资源
driver的绑定是通过driver core自动完成的,完成driver和device的匹配后以后会自动执行probe()函数,如果函数执行成功,则driver和device就绑定在一起了,drvier和device匹配的方法有3种:
>> 当一个设备注册的时候,他会在总线上寻找匹配的driver,platform device一般在系统启动很早的时候就注册了
>> 当一个驱动注册[platform_driver_register()]的时候,他会遍历所有总线上的设备来寻找匹配,在启动的过程驱动的注册一般比较晚,或者在模块载入的时候
>> 当一个驱动注册[platform_driver_probe()]的时候, 功能上和使用platform_driver_register()是一样的,唯一的区别是它不能被以后其他的device probe了,也就是说这个driver只能和一个device绑定。
int __init_or_module platform_driver_probe(struct platform_driver *drv,
int (*probe)(struct platform_device *))
{
int retval, code;
/* temporary section violation during probe() */
drv->probe = probe;
retval = code = platform_driver_register(drv);
/* Fixup that section violation, being paranoid about code scanning
* the list of drivers in order to probe new devices. Check to see
* if the probe was successful, and make sure any forced probes of
* new devices fail.
*/
spin_lock(&platform_bus_type.p->klist_drivers.k_lock);
drv->probe = NULL;
if (code == 0 && list_empty(&drv->driver.p->klist_devices.k_list))
retval = -ENODEV;
drv->driver.probe = platform_drv_probe_fail;
spin_unlock(&platform_bus_type.p->klist_drivers.k_lock);
if (code != retval)
platform_driver_unregister(drv);
return retval;
}
static int platform_drv_probe(struct device *_dev)
{
struct platform_driver *drv = to_platform_driver(_dev->driver);
struct platform_device *dev = to_platform_device(_dev);
return drv->probe(dev);
}
static int platform_drv_probe_fail(struct device *_dev)
{
return -ENXIO;
}
static int platform_drv_remove(struct device *_dev)
{
struct platform_driver *drv = to_platform_driver(_dev->driver);
struct platform_device *dev = to_platform_device(_dev);
return drv->remove(dev);
}
static void platform_drv_shutdown(struct device *_dev)
{
struct platform_driver *drv = to_platform_driver(_dev->driver);
struct platform_device *dev = to_platform_device(_dev);
drv->shutdown(dev);
}
static int platform_drv_suspend(struct device *_dev, pm_message_t state)
{
struct platform_driver *drv = to_platform_driver(_dev->driver);
struct platform_device *dev = to_platform_device(_dev);
return drv->suspend(dev, state);
}
static int platform_drv_resume(struct device *_dev)
{
struct platform_driver *drv = to_platform_driver(_dev->driver);
struct platform_device *dev = to_platform_device(_dev);
return drv->resume(dev);
}
需要将平台设备驱动注册到系统中才能使用,内核提供了platform_driver_register()函数实现这个功能:
int platform_driver_register(struct platform_driver *drv)
{
drv->driver.bus = &platform_bus_type; //平台总线类型
//如果定义了probe函数,该函数将覆盖driver中定义的函数(即覆盖父函数)
if (drv->probe)
drv->driver.probe = platform_drv_probe; //默认探测函数
if (drv->remove)
drv->driver.remove = platform_drv_remove; //默认移除函数
if (drv->shutdown)
drv->driver.shutdown = platform_drv_shutdown; //默认关闭函数
if (drv->suspend)
drv->driver.suspend = platform_drv_suspend; //默认挂起函数
if (drv->resume)
drv->driver.resume = platform_drv_resume; //默认恢复函数
return driver_register(&drv->driver); //将驱动注册到系统中
}
在平台设备platform_driver和其父结构driver中相同的方法。如果平台设备驱动中定义probe()方法,那么内核将会调用平台设备驱动中的方法;如果平台设备驱动中没有定义probe()方法,那么将调用driver中对应方法。platform_driver_register()函数用来完成这种功能,并注册设备驱动到内核中。platform_driver注册到内核后,内核调用驱动的关系如下图:
模块卸载时需要注销函数 :
void platform_driver_unregister(struct platform_driver *drv)
{
driver_unregister(&drv->driver);
}
可以使用platform_device_alloc动态地创建一个设备:
struct platform_device *platform_device_alloc(const char *name, int id)
{
struct platform_object *pa;
pa = kzalloc(sizeof(struct platform_object) + strlen(name), GFP_KERNEL);
if (pa) {
strcpy(pa->name, name);
pa->pdev.name = pa->name;
pa->pdev.id = id;
device_initialize(&pa->pdev.dev);
pa->pdev.dev.release = platform_device_release;
}
return pa ? &pa->pdev : NULL;
}
一个更好的方法是,通过下面的函数动态创建一个设备,并把这个设备注册到系统中:
struct platform_device *platform_device_register_simple(const char *name,
int id,
struct resource *res,
unsigned int num)
{
struct platform_device *pdev;
int retval;
pdev = platform_device_alloc(name, id);
if (!pdev) {
retval = -ENOMEM;
goto error;
}
if (num) {
retval = platform_device_add_resources(pdev, res, num);
if (retval)
goto error;
}
retval = platform_device_add(pdev);
if (retval)
goto error;
return pdev;
error:
platform_device_put(pdev);
return ERR_PTR(retval);
}
获取资源中的中断号:
int platform_get_irq(struct platform_device *dev, unsigned int num)
{
struct resource *r = platform_get_resource(dev, IORESOURCE_IRQ, num);
return r ? r->start : -ENXIO;
}
根据参数name所指定的名称,来获取资源中的中断号:
int platform_get_irq_byname(struct platform_device *dev, char *name)
{
struct resource *r = platform_get_resource_byname(dev, IORESOURCE_IRQ,
name);
return r ? r->start : -ENXIO;
}
Platform device 和 Platform driver实际上是cpu总线可以直接寻址的设备和驱动,他们挂载在一个虚拟的总线platform_bus_type上,是一种bus- specific设备和驱动。与其他bus-specific驱动比如pci是一样的。他们都是将device和device_driver加了一个 warpper产生,仔细看看platform_device就可以看到它必然包含一个device dev,而platform_driver也一样,它必然包含一个device_driver driver。
所有的设备通过bus_id 挂在总线上,多个device可以共用一个driver,但是一个device不可以对应多个driver。驱动去注册时候会根据设备名寻找设备,没有设 备会注册失败,注册的过程会通过probe来进行相应资源的申请,以及硬件的初始化,如果probe执行成功,则device和driver的绑定就成功 了。设备注册的时候同样会在总线上寻找相应的驱动,如果找到他也会试图绑定,绑定的过程同样是执行probe (参考http://www.cnblogs.com/alfredzzj/archive/2012/07/02/2573699.html)
混杂设备:
混杂设备并没有一个明确的定义。由于设备号比较紧张,所以一些不相关的设备可以使用同一主设备号。主设备号通常是10.由于这个原因,一些设备也可以叫做混杂设备。
struct miscdevice
{
int minor; //次设备号
const char *name; //混杂设备名字
const struct file_operations *fops; //设备的操作函数,与字符设备相同
struct list_head list; //连向下一个混杂设备的链表
struct device *parent; //指向父设备
struct device *this_device; 指向当前设备结构体
};
看门狗的混杂设备定义:
static struct miscdevice s3c2410wdt_miscdev = {
.minor = WATCHDOG_MINOR, //次设备号,定义为130
.name = "watchdog", //混杂设备名字
.fops = &s3c2410wdt_fops, //混杂设备操作指针
};
混杂设备的注册很简单,misc_register(),传递一个混杂设备的指针
int misc_register(struct miscdevice *misc);
该函数内部检测次设备号是否合法,如果次设备号被占用,则返回设备忙状态。如果miscdevice的成员minor为255,则尝试动态申请一个次设备号。当次设备号可用时,函数会将混杂设备注册到内核设备模型中
相反,注销函数:
int misc_deregister(struct misc_miscdevice *misc);
看门狗设备驱动程序:
主要变量:
static int nowayout = WATCHDOG_NOWAYOUT;
表示绝不允许看门狗关闭,为1表示不允许关闭,为0表示允许关闭,当不允许关闭调用close()是没用的.WATCHDOG_NOWAYOUT的取值由配置选项
CONFIG_WATCHDOG_NOWAYOUT决定:
#ifdef CONFIG_WATCHDOG_NOWAYOUT
#define WATCHDOG_NOWAYOUT 1
#else
#define WATCHDOG_NOWAYOUT 0
#endif
static int tmr_margin = CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME; //表示默认的看门狗喂狗时间我15秒
static int tmr_atboot = CONFIG_S3C2410_WATCHDOG_ATBOOT; //表示系统启动时就使能看门狗。为1表示使能,为0表示关闭
static int soft_noboot; //看门狗工作方式,为1表示看门狗作为定时器使用,不发送复位信号,为0表示发送复位信号
static int debug; //是否使用调试模式来调试代码。该模式中会打印调试信息
另一个重要的枚举值close_state来标识看门狗是否允许关闭
typedef enum close_state {
CLOSE_STATE_NOT, //不允许关闭看门狗
CLOSE_STATE_ALLOW = 0x4021 //允许关闭看门狗
} close_state_t;
看门狗的加载和卸载函数:
static char banner[] __initdata =
KERN_INFO "S3C2410 Watchdog Timer, (c) 2004 Simtec Electronics\n";
static int __init watchdog_init(void)
{
printk(banner);
return platform_driver_register(&s3c2410wdt_driver);
}
static void __exit watchdog_exit(void)
{
platform_driver_unregister(&s3c2410wdt_driver);
}
当调用platform_driver_register()函数注册驱动后,会触发设备和驱动的匹配函数platform_match().匹配成功,则会调用平台设备驱动中的probe()函数,看门狗驱动中对应的函数是s3c2410wdt_probe():代码:
static int s3c2410wdt_probe(struct platform_device *pdev)
{
struct resource *res; //资源指针
struct device *dev; //设备结构体指针
unsigned int wtcon; //用于暂时存放WTCON寄存器的数据
int started = 0;
int ret;
int size;
DBG("%s: probe=%p\n", __func__, pdev);
dev = &pdev->dev; //平台设备中取出设备device
wdt_dev = &pdev->dev;
/* get the memory region for the watchdog timer */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); //获得看门狗的内存资源
if (res == NULL) { //失败退出
dev_err(dev, "no memory resource specified\n");
return -ENOENT;
}
size = (res->end - res->start) + 1; //内存资源所占的字节数
wdt_mem = request_mem_region(res->start, size, pdev->name); //申请一块I/O内存,对应看门狗的3个寄存器
if (wdt_mem == NULL) { //申请设备退出
dev_err(dev, "failed to get memory region\n");
ret = -ENOENT;
goto err_req;
}
wdt_base = ioremap(res->start, size); //将设备内存映射到虚拟地址空间,这样可以使用函数访问
if (wdt_base == NULL) { //映射失败退出
dev_err(dev, "failed to ioremap() region\n");
ret = -EINVAL;
goto err_req;
}
DBG("probe: mapped wdt_base=%p\n", wdt_base); //输出映射基地址,调试时用
wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); //获得看门狗可以申请的中断号
if (wdt_irq == NULL) { //获取中断号失败,退出
dev_err(dev, "no irq resource specified\n");
ret = -ENOENT;
goto err_map;
}
ret = request_irq(wdt_irq->start, s3c2410wdt_irq, 0, pdev->name, pdev); //申请中断,并注册中断处理函数s3c2410wdt_irq()
if (ret != 0) { //申请失败退出
dev_err(dev, "failed to install irq (%d)\n", ret);
goto err_map;
}
wdt_clock = clk_get(&pdev->dev, "watchdog"); //得到看门狗时钟源
if (IS_ERR(wdt_clock)) {
dev_err(dev, "failed to find watchdog clock source\n");
ret = PTR_ERR(wdt_clock);
goto err_irq;
}
clk_enable(wdt_clock); //使能看门狗时钟
/* see if we can actually set the requested timer margin, and if
* not, try the default value */
if (s3c2410wdt_set_heartbeat(tmr_margin)) { //设置看门狗复位时间tmr_margin,如果时间值不合法,返回非0,重新设置默认复位时间
started = s3c2410wdt_set_heartbeat(
CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);
if (started == 0)
dev_info(dev,
"tmr_margin value out of range, default %d used\n",
CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);
else
dev_info(dev, "default timer value is out of range, cannot start\n");
}
ret = misc_register(&s3c2410wdt_miscdev); //注册混杂设备
if (ret) { //注册失败退出
dev_err(dev, "cannot register miscdev on minor=%d (%d)\n",
WATCHDOG_MINOR, ret);
goto err_clk;
}
if (tmr_atboot && started == 0) { //开机时就立即启动看门狗定时器
dev_info(dev, "starting watchdog timer\n");
s3c2410wdt_start(); //启动看门狗
} else if (!tmr_atboot) {
/* if we're not enabling the watchdog, then ensure it is
* disabled if it has been left running from the bootloader
* or other source */
s3c2410wdt_stop(); //停止看门狗
}
/* print out a statement of readiness */
wtcon = readl(wdt_base + S3C2410_WTCON); //读出控制寄存器的值
dev_info(dev, "watchdog %sactive, reset %sabled, irq %sabled\n",
(wtcon & S3C2410_WTCON_ENABLE) ? "" : "in", //看门狗是否启动
(wtcon & S3C2410_WTCON_RSTEN) ? "" : "dis", //看门狗是否允许发生复位信号
(wtcon & S3C2410_WTCON_INTEN) ? "" : "en"); //看门狗是否允许发出中断信号
return 0;
err_clk: //注册混杂设备失败
clk_disable(wdt_clock);
clk_put(wdt_clock);
err_irq: //得到时钟失败
free_irq(wdt_irq->start, pdev);
err_map: //获取中断失败
iounmap(wdt_base);
err_req: //申请I/O内存失败
release_resource(wdt_mem);
kfree(wdt_mem);
return ret;
}
设置看门狗复位时间函数:s3c2410wdt_set_heartbeat()
该函数的参数接受看门狗复位时间,默认值是15秒。该函数主要完成如下几个功能:
1,使用clk_set_rate()函数获得看门狗的时钟频率PCLK
2,判断复位时间timeout是否超过计数寄存器WTCNT能表示的最大值,该寄存器的最大值为65536
3,设置第一个分频器的分频系数
4,设置数据寄存器WTDAT
源代码:
static int s3c2410wdt_set_heartbeat(int timeout)
{
unsigned int freq = clk_get_rate(wdt_clock); //得到看门狗的时钟频率PCLK
unsigned int count; //将填入WTCNT的计数值
unsigned int divisor = 1; //要填入WTCON[15:8]的预分频系数
unsigned long wtcon; //暂存WTCON的值
if (timeout < 1) //看门狗的复位时间不能小于1秒
return -EINVAL;
freq /= 128; //看门狗默认使用128的四相分频
count = timeout * freq; //计数值 = 秒 X 频率(每秒时钟滴答)
DBG("%s: count=%d, timeout=%d, freq=%d\n",
__func__, count, timeout, freq); //打印相关的信息用于调试
/* if the count is bigger than the watchdog register,
then work out what we need to do (and if) we can
actually make this value
*/
if (count >= 0x10000) { //最终填入的计数值不能大于WTCNT的范围,WTCNT是一个16位寄存器,其最大值为0x10000
for (divisor = 1; divisor <= 0x100; divisor++) { //从1到256,寻找一个合适的预分频系数
if ((count / divisor) < 0x10000)
break; //找到则退出
}
if ((count / divisor) >= 0x10000) { //经过预分频和四相分频的计数值仍大于0x10000,则复位时间太长,看门狗不支持
dev_err(wdt_dev, "timeout %d too big\n", timeout);
return -EINVAL;
}
}
tmr_margin = timeout; //合法的复位时间
DBG("%s: timeout=%d, divisor=%d, count=%d (%08x)\n",
__func__, timeout, divisor, count, count/divisor); //打印相关的调试信息
count /= divisor; //分频后最终的计数值
wdt_count = count;
/* update the pre-scaler */
wtcon = readl(wdt_base + S3C2410_WTCON); //读WTCNT的值
wtcon &= ~S3C2410_WTCON_PRESCALE_MASK; //将WTCNT的高8位清零
wtcon |= S3C2410_WTCON_PRESCALE(divisor-1); //填入预分频系数
writel(count, wdt_base + S3C2410_WTDAT); //将计数值写到数据寄存器WTDAT中
writel(wtcon, wdt_base + S3C2410_WTCON); //设置控制寄存器WTCON
return 0;
}
看门狗开始函数s3c2410wdt_start():
当所有的工作完成后,并且允许看门狗随机启动(tmp_atboot = 1),则会调用s3c2410wdt_start()函数使看门狗开始工作 :
static void s3c2410wdt_start(void)
{
unsigned long wtcon; //暂存WTCNT
spin_lock(&wdt_lock); //避免不多线程同时访问临界资源
__s3c2410wdt_stop(); //先停止看门狗便于设置
wtcon = readl(wdt_base + S3C2410_WTCON); //读取WTCON的值
wtcon |= S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128; //通过设置WTCON的第5位允许看门狗工作,并将第3,4位设置为11,使用四相分频
if (soft_noboot) { //看门狗作为定时器使用
wtcon |= S3C2410_WTCON_INTEN; //使能中断
wtcon &= ~S3C2410_WTCON_RSTEN; //不允许发送复位信号
} else { //看门狗作为复位器使用
wtcon &= ~S3C2410_WTCON_INTEN; //禁止发出中断
wtcon |= S3C2410_WTCON_RSTEN; // 允许发出复位信号
}
DBG("%s: wdt_count=0x%08x, wtcon=%08lx\n",
__func__, wdt_count, wtcon); //打印相关调试信息用于调试
writel(wdt_count, wdt_base + S3C2410_WTDAT); //重新写数据寄存器的值
writel(wdt_count, wdt_base + S3C2410_WTCNT); //重新写计数寄存器的值
writel(wtcon, wdt_base + S3C2410_WTCON); //写控制寄存器的值
spin_unlock(&wdt_lock); //自旋锁解锁
}
看门狗停止函数:s3c2410wdt_stop():
当所有的工作准备完成后,如果不允许看门狗立即启动(tmp_atboot = 0),则会调用s3c2410wdt_stop()函数使看门狗停止工作:
static void s3c2410wdt_stop(void)
{
spin_lock(&wdt_lock);
__s3c2410wdt_stop();
spin_unlock(&wdt_lock);
}
static void __s3c2410wdt_stop(void)
{
unsigned long wtcon; //暂存WTCNT的值
wtcon = readl(wdt_base + S3C2410_WTCON); //读取WTCON值
wtcon &= ~(S3C2410_WTCON_ENABLE | S3C2410_WTCON_RSTEN); //设置WTCON,使看门狗不工作,并且不发出复位信号
writel(wtcon, wdt_base + S3C2410_WTCON); //写控制寄存器的值
}
看门狗驱动程序移除函数:s3c2410wdt_remove():
s3c2440看门狗驱动程序的移除函数完成与探测函数相反的功能。包括释放I/O内存资源、释放IRQ资源、禁止看门狗时钟源和注销混杂设备:
static int s3c2410wdt_remove(struct platform_device *dev)
{
release_resource(wdt_mem); //释放资源resource
kfree(wdt_mem); //释放I/O内存
wdt_mem = NULL;
free_irq(wdt_irq->start, dev); //释放中断号
wdt_irq = NULL;
clk_disable(wdt_clock); //禁止时钟
clk_put(wdt_clock); //减少时钟引用计数
wdt_clock = NULL;
iounmap(wdt_base); //关闭内存映射
misc_deregister(&s3c2410wdt_miscdev); //注销混杂设备
return 0;
}
当看门狗关闭时,内核会自动调用s3c2410wdt_shutdown()函数先停止看门狗设备:
static void s3c2410wdt_shutdown(struct platform_device *dev)
{
s3c2410wdt_stop();
}
当需要暂停看门狗时,可以调用s3c2410wdt_suspend()函数。该函数保存看门狗的寄存器,并设置看门狗为停止状态。该函数一般由电源管理子模块调用,用来节省电源:
#ifdef CONFIG_PM
static unsigned long wtcon_save;
static unsigned long wtdat_save;
static int s3c2410wdt_suspend(struct platform_device *dev, pm_message_t state)
{
/* Save watchdog state, and turn it off. */ //保存看门狗当前状态,就是WTDAT和WTCON。不需要保存WTCNT
wtcon_save = readl(wdt_base + S3C2410_WTCON);
wtdat_save = readl(wdt_base + S3C2410_WTDAT);
/* Note that WTCNT doesn't need to be saved. */
s3c2410wdt_stop();
return 0;
}
与挂起相反的函数是恢复函数s3c2410wdt_resume().该函数恢复看门狗寄存器的值。如果挂起之前为停止状态,则恢复后看门狗为停止状态;如果挂起前为启动状态,则恢复后也为启动:
static int s3c2410wdt_resume(struct platform_device *dev)
{
/* Restore watchdog state. */
writel(wtdat_save, wdt_base + S3C2410_WTDAT);
writel(wtdat_save, wdt_base + S3C2410_WTCNT); /* Reset count */
writel(wtcon_save, wdt_base + S3C2410_WTCON);
printk(KERN_INFO PFX "watchdog %sabled\n",
(wtcon_save & S3C2410_WTCON_ENABLE) ? "en" : "dis");
return 0;
}
混杂设备是一种特殊的字符设备,所以混杂设备的操作方法和字符设备的操作方法基本一样。
看门狗驱动中,混杂设备定义:
static struct miscdevice s3c2410wdt_miscdev = {
.minor = WATCHDOG_MINOR,
.name = "watchdog",
.fops = &s3c2410wdt_fops,
};
static const struct file_operations s3c2410wdt_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = s3c2410wdt_write,
.unlocked_ioctl = s3c2410wdt_ioctl,
.open = s3c2410wdt_open,
.release = s3c2410wdt_release,
};
当用户程序调用open()函数时,内核会最终调用s3c2410wdt_open()函数:
1,调用test_and_set_bit()函数测试open_lock的第0位。如果open_lock的第0位为0,则表示test_and_set_bit()函数返回0,表示设备没有被另外的程序打开。如果为1,则表示设备已经被打开,返回忙EBUSY状态
2,nowayout不为0,表示看门狗绝不允许关闭,则增加看门狗模块引用计数
3,将是否运行关闭变量allow_close()设为CLOSE_STATE_NOT,表示不允许关闭设备
4,使用s3c2410wdt_start()函数打开设备
5,使用nonseekable_open()函数设置设备文件file不允许seek操作,即是不允许对设备进行定位
static int s3c2410wdt_open(struct inode *inode, struct file *file)
{
if (test_and_set_bit(0, &open_lock)) //只允许打开一次
return -EBUSY;
if (nowayout) 不允许关闭设备
__module_get(THIS_MODULE); 增加引用计数
allow_close = CLOSE_STATE_NOT; //设为不允许关闭
/* start the timer */
s3c2410wdt_start(); 开始运行看门狗设备
return nonseekable_open(inode, file); 不允许调用seek()
}
为了使看门狗设备在调用close()函数关闭后,能够使用open()方法重新打开,驱动程序需要定义s3c2410wdt_release()函数。应该在s3c2410wdt_release()函数中清除open_lock的第0位,是设备能够被open()函数打开。如果看门狗允许关闭,则应该调用s3c2410wdt_stop()函数关闭看门狗。如果不允许关闭设备,则调用s3c2410wdt_keepalive()函数,使看门狗为活动状态:
static int s3c2410wdt_release(struct inode *inode, struct file *file)
{
/*
* Shut off the timer.
* Lock it in if it's a module and we set nowayout
*/
if (allow_close == CLOSE_STATE_ALLOW) //看门狗为允许状态
s3c2410wdt_stop();
else {
dev_err(wdt_dev, "Unexpected close, not stopping watchdog\n");
s3c2410wdt_keepalive();
}
allow_close = CLOSE_STATE_NOT;
clear_bit(0, &open_lock); //将open_lock的第0位设为0,是原子操作
return 0;
}
static void s3c2410wdt_keepalive(void) //相当于一个喂狗功能
{
spin_lock(&wdt_lock);
writel(wdt_count, wdt_base + S3C2410_WTCNT); //重写计数寄存器WTCNT
spin_unlock(&wdt_lock);
}
混杂设备s3c2410wdt_miscdev的file_operations中没有实现read()函数,因为很少需要从看门狗的寄存器中获取数据,但是实现了写函数s3c2410wdt_write()。该函数主要用来设置allow_close变量为允许关闭状态。如果想看门狗设备中写入V,那么就允许关闭设备:
static ssize_t s3c2410wdt_write(struct file *file, const char __user *data,
size_t len, loff_t *ppos)
{
/*
* Refresh the timer.
*/
if (len) { //有数据写入len不为0
if (!nowayout) { //允许关闭
size_t i;
/* In case it was set long ago */
allow_close = CLOSE_STATE_NOT; //允许关闭状态
for (i = 0; i != len; i++) {
char c;
if (get_user(c, data + i))
return -EFAULT;
if (c == 'V')
allow_close = CLOSE_STATE_ALLOW;
}
}
s3c2410wdt_keepalive();
}
return len;
}
s3c2410wdt_ioctl()函数接受一些系统命令,用来设置看门狗内部状态:
static long s3c2410wdt_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
void __user *argp = (void __user *)arg;
int __user *p = argp;
int new_margin;
switch (cmd) {
case WDIOC_GETSUPPORT: //获得看门狗设备信息,这些信息包含在一个watchdog_info结构体中
return copy_to_user(argp, &s3c2410_wdt_ident,
sizeof(s3c2410_wdt_ident)) ? -EFAULT : 0;
case WDIOC_GETSTATUS: //和下面的一个,这两个表示获得看门狗状态,一般将0返回给用户
case WDIOC_GETBOOTSTATUS:
return put_user(0, p);
case WDIOC_KEEPALIVE: //对看门狗进行喂狗操作
s3c2410wdt_keepalive();
return 0;
case WDIOC_SETTIMEOUT: //用来设置看门狗的新超时时间,并返回旧超时时间。使用get_user()函数从用户空间获得超时时间,并使用s3c2410wdt_set_heartbeat()函数设置新的超时时间。通过put_user()函数返回旧的超时时间
if (get_user(new_margin, p))
return -EFAULT;
if (s3c2410wdt_set_heartbeat(new_margin))
return -EINVAL;
s3c2410wdt_keepalive();
return put_user(tmr_margin, p);
case WDIOC_GETTIMEOUT: //用来获取当前的超时时间
return put_user(tmr_margin, p);
default:
return -ENOTTY;
}
}
看门狗的内部存储单元为一组寄存器,这些寄存器是WTCON,WTDAT,WTCNT。这些寄存器不需要像文件一样对位置进行寻址,所以不需要对llseek()函数进行具体实现。ESPIPE表示该设备不允许寻址:
loff_t no_seek(struct file * file, loff_t offset, int origin)
{
return -ESPIPE;
}
当看门狗设备作为定时器使用时,发出中断信号,而不是复位信号。该中断在探测函数s3c2410wdt_probe()中通过调用request_irq()函数向系统做了申请。中断处理函数的只要功能是喂狗操作,使看门狗重新开始计数:
static irqreturn_t s3c2410wdt_irq(int irqno, void *param)
{
dev_info(wdt_dev, "watchdog timer expired (irq)\n");
s3c2410wdt_keepalive();
return IRQ_HANDLED;
}