看門狗是當(dāng)CPU進(jìn)入錯誤狀態(tài)后,無法恢復(fù)的情況下,使計算機(jī)重新啟動 由于計算機(jī)在工作時不可避免的受到各種各樣的因素干擾,即使再優(yōu)秀的計算機(jī)程序也可能因?yàn)檫@種干擾使計算機(jī)進(jìn)入一個死循環(huán),更嚴(yán)重的就是導(dǎo)致死機(jī)。 有兩種辦法來處理這種情況: 一是:采用人工復(fù)位的方法 二是:依賴于某種硬件來執(zhí)行這個復(fù)位工作。這種硬件通常叫做看門狗(Watch Dog,WD) 看門狗,就像一只狗一樣,在那看著們,計算機(jī)中通常用定時器來處理這種周期性的動作 看門狗實(shí)際上是一個定時器,其硬件內(nèi)部維護(hù)了一個定時器,每當(dāng)時鐘信號到來時,計數(shù)寄存器減1。如果減到0,則系統(tǒng)重啟(就像狗一樣,看你不認(rèn)識就咬你,可不管你是誰)。 如果在減到0之前,系統(tǒng)又設(shè)置計數(shù)寄存器一個較大的值,那么系統(tǒng)永遠(yuǎn)不會重啟。系統(tǒng)的這種設(shè)置能力表示系統(tǒng)一直處于一種正常運(yùn)行狀態(tài)。反之,如果計算機(jī)系統(tǒng)崩潰,那么就無法重新設(shè)置計數(shù)寄存器的值。當(dāng)計數(shù)寄存器為0,系統(tǒng)重啟 看門狗的工作原來很簡單,處理器內(nèi)部一般都集成了一個看門狗硬件。其提供了三個寄存器 看門狗控制寄存器(WTCON) 看門狗數(shù)據(jù)寄存器(WTDAT) 看門狗計數(shù)寄存器(WTCNT) 
結(jié)合上圖可知,看門狗從一個PCLK頻率到產(chǎn)生一個RESET復(fù)位信號的過程如下: 1,處理器向看門狗提供一個PCLK時鐘信號。其通過一個8位預(yù)分頻器(8-bit Prescaler)使頻率降低 2,8位預(yù)分頻器由控制寄存器WTCON的第8~15位決定。分頻后的頻率就相當(dāng)于PCLK除以(WTCON[15:8]+1). 3,然后再通過一個4相分頻器,分成4種大小的頻率。這4種頻率系數(shù)分別是16,32,64,128.看門狗可以通過寄存器的3,4位決定使用哪種頻率 4,當(dāng)選擇的時鐘頻率到達(dá)計數(shù)器(Down Counter)時,會按照工作頻率將WTCNT減1.當(dāng)達(dá)到0時,就會產(chǎn)生一個中斷信號或者復(fù)位信號 5,如果控制寄存器WTCOON的第二位為1,則發(fā)出一個中斷信號;如果控制寄存器WTCON第0位為1,則輸出一個復(fù)位信號,使系統(tǒng)重新啟動 看門狗驅(qū)動涉及兩種設(shè)備模型,分別是平臺設(shè)備和混雜設(shè)備 平臺設(shè)備模型: 從Linux2.6起引入了一套新的驅(qū)動管理和注冊模型,即平臺設(shè)備platform_device和平臺驅(qū)動platform_driver.Linux中大部分的設(shè)備驅(qū)動,都可以使用這套機(jī)制,設(shè)備用platform_device表示,驅(qū)動用platform_driver表示 平臺設(shè)備模型與傳統(tǒng)的device和driver模型相比,一個十分明顯的優(yōu)勢在于平臺設(shè)備模型將設(shè)備本身的資源注冊進(jìn)內(nèi)核,由內(nèi)核統(tǒng)一管理。這樣提高了驅(qū)動和資源管理的獨(dú)立性,并且擁有較好的可移植性和安全性。通過平臺設(shè)備模型開發(fā)底層驅(qū)動的大致流程為下圖: 
平臺設(shè)備是指處理器上集成的額外功能的附加設(shè)備,如Watch Dog,IIC,IIS,RTC,ADC等設(shè)備。這些額外功能設(shè)備是為了節(jié)約硬件成本、減少產(chǎn)品功耗、縮小產(chǎn)品形狀而集成到處理器內(nèi)部的。需要注意的是,平臺設(shè)備并不是與字符設(shè)備、塊設(shè)備和網(wǎng)絡(luò)設(shè)備并列的概念,而是一種平行的概念,其從另一個角度對設(shè)備進(jìn)行了概括。如果從內(nèi)核開發(fā)者的角度來看,平臺設(shè)備的引入,是為了更容易開發(fā)字符設(shè)備、塊設(shè)備和網(wǎng)絡(luò)設(shè)備驅(qū)動 平臺設(shè)備結(jié)構(gòu)體(platform_device) struct platform_device { const char *name; //平臺設(shè)備的名字,與驅(qū)動的名字對應(yīng) int id; //與驅(qū)動綁定有關(guān),一般為-1 struct device dev; //設(shè)備結(jié)構(gòu)體說明platform_device派生于device u32 num_resources; //設(shè)備使用的資源數(shù)量 struct resource *resource; //指向資源的數(shù)組,數(shù)量由num_resources指定 }; 看門狗的平臺設(shè)備為: struct platform_device s3c_device_wdt = { .name = "s3c2410-wdt", .id = -1, .num_resources = ARRAY_SIZE(s3c_wdt_resource), .resource = s3c_wdt_resource, }; 為了統(tǒng)一管理平臺設(shè)備的資源,在platform_device機(jī)構(gòu)體中定義了平臺設(shè)備所使用的資源。 看門狗的資源如下: static struct resource s3c_wdt_resource[] = { [0] = { // I/O資源指向看門狗的寄存器 .start = S3C24XX_PA_WATCHDOG, //看門狗I/O內(nèi)存開始位置,被定義為WTCON的地址0x53000000 .end = S3C24XX_PA_WATCHDOG + S3C24XX_SZ_WATCHDOG - 1, //1M的地址空間 .flags = IORESOURCE_MEM, //I/O內(nèi)存資源 }, [1] = { //IRQ資源 .start = IRQ_WDT, //看門狗的開始中斷號,被定義為80 .end = IRQ_WDT, //看門狗的結(jié)束中斷號 .flags = IORESOURCE_IRQ, //中斷的IRQ資源 }
}; struct resource { resource_size_t start; //資源的開始地址,resource_size是32位或者64位的無符號整數(shù) resource_size_t end; //資源的結(jié)束地址 const char *name; //資源名 unsigned long flags; //資源的類型 (IORESOURCE_IO,IORESOURCE_MEM,IORESOURCE_IRQ,IORESOURCE_DMA等) struct resource *parent,*sibling,*child; //用于構(gòu)建資源的樹形結(jié)構(gòu) }; 通過platfrom_add_devices()函數(shù)可以將一組設(shè)備添加到系統(tǒng)中,其主要完成以下兩個功能: 1,分配平臺設(shè)備所使用的資源,并將這些資源掛接到資源樹中 2,初始化device設(shè)備,并將設(shè)備注冊到系統(tǒng)中 第一個參數(shù)是平臺設(shè)備數(shù)組的指針,第2個參數(shù)是平臺設(shè)備的數(shù)量 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()函數(shù)可以獲得平臺設(shè)備的resource資源: 第一個參數(shù)dev是平臺設(shè)備的指針,第2個參數(shù)type是資源的類型,這些類型可以是(IORESOURCE_IO,IORESOURCE_MEM,IORESOURCE_IRQ,IORESOURCE_DMA等),第3個參數(shù)num是同種資源的索引。例如一個平臺設(shè)備有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; } 平臺設(shè)備驅(qū)動 : 每一個平臺設(shè)備都對應(yīng)一個平臺設(shè)備驅(qū)動,這個驅(qū)動用來對平臺設(shè)備進(jìn)行探測、移除、關(guān)閉和電源管理等操作。 struct platform_driver { int (*probe)(struct platform_device *); //探測函數(shù) int (*remove)(struct platform_device *); //移除函數(shù) void (*shutdown)(struct platform_device *); //關(guān)閉設(shè)備時調(diào)用該函數(shù) int (*suspend)(struct platform_device *, pm_message_t state); //掛起函數(shù) int (*suspend_late)(struct platform_device *, pm_message_t state); //掛起之后調(diào)用的函數(shù) int (*resume_early)(struct platform_device *); //恢復(fù)正常狀態(tài)之前調(diào)用的函數(shù) int (*resume)(struct platform_device *); //恢復(fù)正常狀態(tài)的函數(shù) struct device_driver driver; //設(shè)備驅(qū)動核心結(jié)構(gòu) }; 看門狗的平臺設(shè)備驅(qū)動: 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", }, }; 一般來說,在內(nèi)核啟動時,會注冊平臺設(shè)備和平臺設(shè)備驅(qū)動程序。內(nèi)核將在適當(dāng)?shù)臅r候,將平臺設(shè)備和平臺驅(qū)動連接起來。連接的方法,是用系統(tǒng)中的所有平臺設(shè)備和所有已經(jīng)注冊的平臺驅(qū)動進(jìn)行匹配。下面是源代碼: 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); } 該函數(shù)將由內(nèi)核自己調(diào)用,當(dāng)設(shè)備找到對應(yīng)的驅(qū)動時,會觸發(fā)probe函數(shù)。所以,probe函數(shù)一般是驅(qū)動程序加載成功后的第一個調(diào)用函數(shù),在該函數(shù)中可以申請設(shè)備所需要的資源 driver的綁定是通過driver core自動完成的,完成driver和device的匹配后以后會自動執(zhí)行probe()函數(shù),如果函數(shù)執(zhí)行成功,則driver和device就綁定在一起了,drvier和device匹配的方法有3種: >> 當(dāng)一個設(shè)備注冊的時候,他會在總線上尋找匹配的driver,platform device一般在系統(tǒng)啟動很早的時候就注冊了 >> 當(dāng)一個驅(qū)動注冊[platform_driver_register()]的時候,他會遍歷所有總線上的設(shè)備來尋找匹配,在啟動的過程驅(qū)動的注冊一般比較晚,或者在模塊載入的時候 >> 當(dāng)一個驅(qū)動注冊[platform_driver_probe()]的時候, 功能上和使用platform_driver_register()是一樣的,唯一的區(qū)別是它不能被以后其他的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); } 需要將平臺設(shè)備驅(qū)動注冊到系統(tǒng)中才能使用,內(nèi)核提供了platform_driver_register()函數(shù)實(shí)現(xiàn)這個功能: int platform_driver_register(struct platform_driver *drv) { drv->driver.bus = &platform_bus_type; //平臺總線類型 //如果定義了probe函數(shù),該函數(shù)將覆蓋driver中定義的函數(shù)(即覆蓋父函數(shù)) if (drv->probe) drv->driver.probe = platform_drv_probe; //默認(rèn)探測函數(shù) if (drv->remove) drv->driver.remove = platform_drv_remove; //默認(rèn)移除函數(shù) if (drv->shutdown) drv->driver.shutdown = platform_drv_shutdown; //默認(rèn)關(guān)閉函數(shù) if (drv->suspend) drv->driver.suspend = platform_drv_suspend; //默認(rèn)掛起函數(shù) if (drv->resume) drv->driver.resume = platform_drv_resume; //默認(rèn)恢復(fù)函數(shù) return driver_register(&drv->driver); //將驅(qū)動注冊到系統(tǒng)中 } 在平臺設(shè)備platform_driver和其父結(jié)構(gòu)driver中相同的方法。如果平臺設(shè)備驅(qū)動中定義probe()方法,那么內(nèi)核將會調(diào)用平臺設(shè)備驅(qū)動中的方法;如果平臺設(shè)備驅(qū)動中沒有定義probe()方法,那么將調(diào)用driver中對應(yīng)方法。platform_driver_register()函數(shù)用來完成這種功能,并注冊設(shè)備驅(qū)動到內(nèi)核中。platform_driver注冊到內(nèi)核后,內(nèi)核調(diào)用驅(qū)動的關(guān)系如下圖: 
模塊卸載時需要注銷函數(shù) : void platform_driver_unregister(struct platform_driver *drv) { driver_unregister(&drv->driver); } 可以使用platform_device_alloc動態(tài)地創(chuàng)建一個設(shè)備: 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; } 一個更好的方法是,通過下面的函數(shù)動態(tài)創(chuàng)建一個設(shè)備,并把這個設(shè)備注冊到系統(tǒng)中: 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; } 根據(jù)參數(shù)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實(shí)際上是cpu總線可以直接尋址的設(shè)備和驅(qū)動,他們掛載在一個虛擬的總線platform_bus_type上,是一種bus- specific設(shè)備和驅(qū)動。與其他bus-specific驅(qū)動比如pci是一樣的。他們都是將device和device_driver加了一個 warpper產(chǎn)生,仔細(xì)看看platform_device就可以看到它必然包含一個device dev,而platform_driver也一樣,它必然包含一個device_driver driver。 所有的設(shè)備通過bus_id 掛在總線上,多個device可以共用一個driver,但是一個device不可以對應(yīng)多個driver。驅(qū)動去注冊時候會根據(jù)設(shè)備名尋找設(shè)備,沒有設(shè) 備會注冊失敗,注冊的過程會通過probe來進(jìn)行相應(yīng)資源的申請,以及硬件的初始化,如果probe執(zhí)行成功,則device和driver的綁定就成功 了。設(shè)備注冊的時候同樣會在總線上尋找相應(yīng)的驅(qū)動,如果找到他也會試圖綁定,綁定的過程同樣是執(zhí)行probe (參考http://www.cnblogs.com/alfredzzj/archive/2012/07/02/2573699.html) 混雜設(shè)備: 混雜設(shè)備并沒有一個明確的定義。由于設(shè)備號比較緊張,所以一些不相關(guān)的設(shè)備可以使用同一主設(shè)備號。主設(shè)備號通常是10.由于這個原因,一些設(shè)備也可以叫做混雜設(shè)備。 struct miscdevice { int minor; //次設(shè)備號 const char *name; //混雜設(shè)備名字 const struct file_operations *fops; //設(shè)備的操作函數(shù),與字符設(shè)備相同 struct list_head list; //連向下一個混雜設(shè)備的鏈表 struct device *parent; //指向父設(shè)備 struct device *this_device; 指向當(dāng)前設(shè)備結(jié)構(gòu)體 }; 看門狗的混雜設(shè)備定義: static struct miscdevice s3c2410wdt_miscdev = { .minor = WATCHDOG_MINOR, //次設(shè)備號,定義為130 .name = "watchdog", //混雜設(shè)備名字 .fops = &s3c2410wdt_fops, //混雜設(shè)備操作指針 }; 混雜設(shè)備的注冊很簡單,misc_register(),傳遞一個混雜設(shè)備的指針 int misc_register(struct miscdevice *misc); 該函數(shù)內(nèi)部檢測次設(shè)備號是否合法,如果次設(shè)備號被占用,則返回設(shè)備忙狀態(tài)。如果miscdevice的成員minor為255,則嘗試動態(tài)申請一個次設(shè)備號。當(dāng)次設(shè)備號可用時,函數(shù)會將混雜設(shè)備注冊到內(nèi)核設(shè)備模型中 相反,注銷函數(shù): int misc_deregister(struct misc_miscdevice *misc); 看門狗設(shè)備驅(qū)動程序: 主要變量: static int nowayout = WATCHDOG_NOWAYOUT; 表示絕不允許看門狗關(guān)閉,為1表示不允許關(guān)閉,為0表示允許關(guān)閉,當(dāng)不允許關(guān)閉調(diào)用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; //表示默認(rèn)的看門狗喂狗時間我15秒 static int tmr_atboot = CONFIG_S3C2410_WATCHDOG_ATBOOT; //表示系統(tǒng)啟動時就使能看門狗。為1表示使能,為0表示關(guān)閉 static int soft_noboot; //看門狗工作方式,為1表示看門狗作為定時器使用,不發(fā)送復(fù)位信號,為0表示發(fā)送復(fù)位信號 static int debug; //是否使用調(diào)試模式來調(diào)試代碼。該模式中會打印調(diào)試信息 另一個重要的枚舉值close_state來標(biāo)識看門狗是否允許關(guān)閉 typedef enum close_state { CLOSE_STATE_NOT, //不允許關(guān)閉看門狗 CLOSE_STATE_ALLOW = 0x4021 //允許關(guān)閉看門狗 } close_state_t; 看門狗的加載和卸載函數(shù): 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); } 當(dāng)調(diào)用platform_driver_register()函數(shù)注冊驅(qū)動后,會觸發(fā)設(shè)備和驅(qū)動的匹配函數(shù)platform_match().匹配成功,則會調(diào)用平臺設(shè)備驅(qū)動中的probe()函數(shù),看門狗驅(qū)動中對應(yīng)的函數(shù)是s3c2410wdt_probe():代碼: static int s3c2410wdt_probe(struct platform_device *pdev) { struct resource *res; //資源指針 struct device *dev; //設(shè)備結(jié)構(gòu)體指針 unsigned int wtcon; //用于暫時存放WTCON寄存器的數(shù)據(jù) int started = 0; int ret; int size;
DBG("%s: probe=%p\n", __func__, pdev);
dev = &pdev->dev; //平臺設(shè)備中取出設(shè)備device wdt_dev = &pdev->dev;
/* get the memory region for the watchdog timer */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); //獲得看門狗的內(nèi)存資源 if (res == NULL) { //失敗退出 dev_err(dev, "no memory resource specified\n"); return -ENOENT; }
size = (res->end - res->start) + 1; //內(nèi)存資源所占的字節(jié)數(shù) wdt_mem = request_mem_region(res->start, size, pdev->name); //申請一塊I/O內(nèi)存,對應(yīng)看門狗的3個寄存器 if (wdt_mem == NULL) { //申請設(shè)備退出 dev_err(dev, "failed to get memory region\n"); ret = -ENOENT; goto err_req; }
wdt_base = ioremap(res->start, size); //將設(shè)備內(nèi)存映射到虛擬地址空間,這樣可以使用函數(shù)訪問 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); //輸出映射基地址,調(diào)試時用 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); //申請中斷,并注冊中斷處理函數(shù)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)) { //設(shè)置看門狗復(fù)位時間tmr_margin,如果時間值不合法,返回非0,重新設(shè)置默認(rèn)復(fù)位時間 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); //注冊混雜設(shè)備 if (ret) { //注冊失敗退出 dev_err(dev, "cannot register miscdev on minor=%d (%d)\n", WATCHDOG_MINOR, ret); goto err_clk; }
if (tmr_atboot && started == 0) { //開機(jī)時就立即啟動看門狗定時器 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", //看門狗是否允許發(fā)生復(fù)位信號 (wtcon & S3C2410_WTCON_INTEN) ? "" : "en"); //看門狗是否允許發(fā)出中斷信號
return 0;
err_clk: //注冊混雜設(shè)備失敗 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內(nèi)存失敗 release_resource(wdt_mem); kfree(wdt_mem);
return ret; } 設(shè)置看門狗復(fù)位時間函數(shù):s3c2410wdt_set_heartbeat() 該函數(shù)的參數(shù)接受看門狗復(fù)位時間,默認(rèn)值是15秒。該函數(shù)主要完成如下幾個功能: 1,使用clk_set_rate()函數(shù)獲得看門狗的時鐘頻率PCLK 2,判斷復(fù)位時間timeout是否超過計數(shù)寄存器WTCNT能表示的最大值,該寄存器的最大值為65536 3,設(shè)置第一個分頻器的分頻系數(shù) 4,設(shè)置數(shù)據(jù)寄存器WTDAT 源代碼: static int s3c2410wdt_set_heartbeat(int timeout) { unsigned int freq = clk_get_rate(wdt_clock); //得到看門狗的時鐘頻率PCLK unsigned int count; //將填入WTCNT的計數(shù)值 unsigned int divisor = 1; //要填入WTCON[15:8]的預(yù)分頻系數(shù) unsigned long wtcon; //暫存WTCON的值
if (timeout < 1) //看門狗的復(fù)位時間不能小于1秒 return -EINVAL;
freq /= 128; //看門狗默認(rèn)使用128的四相分頻 count = timeout * freq; //計數(shù)值 = 秒 X 頻率(每秒時鐘滴答)
DBG("%s: count=%d, timeout=%d, freq=%d\n", __func__, count, timeout, freq); //打印相關(guān)的信息用于調(diào)試
/* 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) { //最終填入的計數(shù)值不能大于WTCNT的范圍,WTCNT是一個16位寄存器,其最大值為0x10000 for (divisor = 1; divisor <= 0x100; divisor++) { //從1到256,尋找一個合適的預(yù)分頻系數(shù) if ((count / divisor) < 0x10000) break; //找到則退出 }
if ((count / divisor) >= 0x10000) { //經(jīng)過預(yù)分頻和四相分頻的計數(shù)值仍大于0x10000,則復(fù)位時間太長,看門狗不支持 dev_err(wdt_dev, "timeout %d too big\n", timeout); return -EINVAL; } }
tmr_margin = timeout; //合法的復(fù)位時間
DBG("%s: timeout=%d, divisor=%d, count=%d (%08x)\n", __func__, timeout, divisor, count, count/divisor); //打印相關(guān)的調(diào)試信息
count /= divisor; //分頻后最終的計數(shù)值 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); //填入預(yù)分頻系數(shù)
writel(count, wdt_base + S3C2410_WTDAT); //將計數(shù)值寫到數(shù)據(jù)寄存器WTDAT中 writel(wtcon, wdt_base + S3C2410_WTCON); //設(shè)置控制寄存器WTCON
return 0; } 看門狗開始函數(shù)s3c2410wdt_start(): 當(dāng)所有的工作完成后,并且允許看門狗隨機(jī)啟動(tmp_atboot = 1),則會調(diào)用s3c2410wdt_start()函數(shù)使看門狗開始工作 : static void s3c2410wdt_start(void) { unsigned long wtcon; //暫存WTCNT
spin_lock(&wdt_lock); //避免不多線程同時訪問臨界資源
__s3c2410wdt_stop(); //先停止看門狗便于設(shè)置
wtcon = readl(wdt_base + S3C2410_WTCON); //讀取WTCON的值 wtcon |= S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128; //通過設(shè)置WTCON的第5位允許看門狗工作,并將第3,4位設(shè)置為11,使用四相分頻
if (soft_noboot) { //看門狗作為定時器使用 wtcon |= S3C2410_WTCON_INTEN; //使能中斷 wtcon &= ~S3C2410_WTCON_RSTEN; //不允許發(fā)送復(fù)位信號 } else { //看門狗作為復(fù)位器使用 wtcon &= ~S3C2410_WTCON_INTEN; //禁止發(fā)出中斷 wtcon |= S3C2410_WTCON_RSTEN; // 允許發(fā)出復(fù)位信號 }
DBG("%s: wdt_count=0x%08x, wtcon=%08lx\n", __func__, wdt_count, wtcon); //打印相關(guān)調(diào)試信息用于調(diào)試 writel(wdt_count, wdt_base + S3C2410_WTDAT); //重新寫數(shù)據(jù)寄存器的值 writel(wdt_count, wdt_base + S3C2410_WTCNT); //重新寫計數(shù)寄存器的值 writel(wtcon, wdt_base + S3C2410_WTCON); //寫控制寄存器的值 spin_unlock(&wdt_lock); //自旋鎖解鎖 }
看門狗停止函數(shù):s3c2410wdt_stop(): 當(dāng)所有的工作準(zhǔn)備完成后,如果不允許看門狗立即啟動(tmp_atboot = 0),則會調(diào)用s3c2410wdt_stop()函數(shù)使看門狗停止工作: 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); //設(shè)置WTCON,使看門狗不工作,并且不發(fā)出復(fù)位信號 writel(wtcon, wdt_base + S3C2410_WTCON); //寫控制寄存器的值 } 看門狗驅(qū)動程序移除函數(shù):s3c2410wdt_remove(): s3c2440看門狗驅(qū)動程序的移除函數(shù)完成與探測函數(shù)相反的功能。包括釋放I/O內(nèi)存資源、釋放IRQ資源、禁止看門狗時鐘源和注銷混雜設(shè)備: static int s3c2410wdt_remove(struct platform_device *dev) { release_resource(wdt_mem); //釋放資源resource kfree(wdt_mem); //釋放I/O內(nèi)存 wdt_mem = NULL;
free_irq(wdt_irq->start, dev); //釋放中斷號 wdt_irq = NULL;
clk_disable(wdt_clock); //禁止時鐘 clk_put(wdt_clock); //減少時鐘引用計數(shù) wdt_clock = NULL;
iounmap(wdt_base); //關(guān)閉內(nèi)存映射 misc_deregister(&s3c2410wdt_miscdev); //注銷混雜設(shè)備 return 0; } 當(dāng)看門狗關(guān)閉時,內(nèi)核會自動調(diào)用s3c2410wdt_shutdown()函數(shù)先停止看門狗設(shè)備: static void s3c2410wdt_shutdown(struct platform_device *dev) { s3c2410wdt_stop(); } 當(dāng)需要暫??撮T狗時,可以調(diào)用s3c2410wdt_suspend()函數(shù)。該函數(shù)保存看門狗的寄存器,并設(shè)置看門狗為停止?fàn)顟B(tài)。該函數(shù)一般由電源管理子模塊調(diào)用,用來節(jié)省電源: #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. */ //保存看門狗當(dāng)前狀態(tài),就是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; } 與掛起相反的函數(shù)是恢復(fù)函數(shù)s3c2410wdt_resume().該函數(shù)恢復(fù)看門狗寄存器的值。如果掛起之前為停止?fàn)顟B(tài),則恢復(fù)后看門狗為停止?fàn)顟B(tài);如果掛起前為啟動狀態(tài),則恢復(fù)后也為啟動: 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; } 混雜設(shè)備是一種特殊的字符設(shè)備,所以混雜設(shè)備的操作方法和字符設(shè)備的操作方法基本一樣。 看門狗驅(qū)動中,混雜設(shè)備定義: 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, }; 當(dāng)用戶程序調(diào)用open()函數(shù)時,內(nèi)核會最終調(diào)用s3c2410wdt_open()函數(shù): 1,調(diào)用test_and_set_bit()函數(shù)測試open_lock的第0位。如果open_lock的第0位為0,則表示test_and_set_bit()函數(shù)返回0,表示設(shè)備沒有被另外的程序打開。如果為1,則表示設(shè)備已經(jīng)被打開,返回忙EBUSY狀態(tài) 2,nowayout不為0,表示看門狗絕不允許關(guān)閉,則增加看門狗模塊引用計數(shù) 3,將是否運(yùn)行關(guān)閉變量allow_close()設(shè)為CLOSE_STATE_NOT,表示不允許關(guān)閉設(shè)備 4,使用s3c2410wdt_start()函數(shù)打開設(shè)備 5,使用nonseekable_open()函數(shù)設(shè)置設(shè)備文件file不允許seek操作,即是不允許對設(shè)備進(jìn)行定位 static int s3c2410wdt_open(struct inode *inode, struct file *file) { if (test_and_set_bit(0, &open_lock)) //只允許打開一次 return -EBUSY;
if (nowayout) 不允許關(guān)閉設(shè)備 __module_get(THIS_MODULE); 增加引用計數(shù)
allow_close = CLOSE_STATE_NOT; //設(shè)為不允許關(guān)閉
/* start the timer */ s3c2410wdt_start(); 開始運(yùn)行看門狗設(shè)備 return nonseekable_open(inode, file); 不允許調(diào)用seek() } 為了使看門狗設(shè)備在調(diào)用close()函數(shù)關(guān)閉后,能夠使用open()方法重新打開,驅(qū)動程序需要定義s3c2410wdt_release()函數(shù)。應(yīng)該在s3c2410wdt_release()函數(shù)中清除open_lock的第0位,是設(shè)備能夠被open()函數(shù)打開。如果看門狗允許關(guān)閉,則應(yīng)該調(diào)用s3c2410wdt_stop()函數(shù)關(guān)閉看門狗。如果不允許關(guān)閉設(shè)備,則調(diào)用s3c2410wdt_keepalive()函數(shù),使看門狗為活動狀態(tài): 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) //看門狗為允許狀態(tài) 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位設(shè)為0,是原子操作 return 0; } static void s3c2410wdt_keepalive(void) //相當(dāng)于一個喂狗功能 { spin_lock(&wdt_lock); writel(wdt_count, wdt_base + S3C2410_WTCNT); //重寫計數(shù)寄存器WTCNT spin_unlock(&wdt_lock); }
混雜設(shè)備s3c2410wdt_miscdev的file_operations中沒有實(shí)現(xiàn)read()函數(shù),因?yàn)楹苌傩枰獜目撮T狗的寄存器中獲取數(shù)據(jù),但是實(shí)現(xiàn)了寫函數(shù)s3c2410wdt_write()。該函數(shù)主要用來設(shè)置allow_close變量為允許關(guān)閉狀態(tài)。如果想看門狗設(shè)備中寫入V,那么就允許關(guān)閉設(shè)備: static ssize_t s3c2410wdt_write(struct file *file, const char __user *data, size_t len, loff_t *ppos) { /* * Refresh the timer. */ if (len) { //有數(shù)據(jù)寫入len不為0 if (!nowayout) { //允許關(guān)閉 size_t i;
/* In case it was set long ago */ allow_close = CLOSE_STATE_NOT; //允許關(guān)閉狀態(tài)
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()函數(shù)接受一些系統(tǒng)命令,用來設(shè)置看門狗內(nèi)部狀態(tài): 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: //獲得看門狗設(shè)備信息,這些信息包含在一個watchdog_info結(jié)構(gòu)體中 return copy_to_user(argp, &s3c2410_wdt_ident, sizeof(s3c2410_wdt_ident)) ? -EFAULT : 0; case WDIOC_GETSTATUS: //和下面的一個,這兩個表示獲得看門狗狀態(tài),一般將0返回給用戶 case WDIOC_GETBOOTSTATUS: return put_user(0, p); case WDIOC_KEEPALIVE: //對看門狗進(jìn)行喂狗操作 s3c2410wdt_keepalive(); return 0; case WDIOC_SETTIMEOUT: //用來設(shè)置看門狗的新超時時間,并返回舊超時時間。使用get_user()函數(shù)從用戶空間獲得超時時間,并使用s3c2410wdt_set_heartbeat()函數(shù)設(shè)置新的超時時間。通過put_user()函數(shù)返回舊的超時時間 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: //用來獲取當(dāng)前的超時時間 return put_user(tmr_margin, p); default: return -ENOTTY; } } 看門狗的內(nèi)部存儲單元為一組寄存器,這些寄存器是WTCON,WTDAT,WTCNT。這些寄存器不需要像文件一樣對位置進(jìn)行尋址,所以不需要對llseek()函數(shù)進(jìn)行具體實(shí)現(xiàn)。ESPIPE表示該設(shè)備不允許尋址: loff_t no_seek(struct file * file, loff_t offset, int origin) { return -ESPIPE; } 當(dāng)看門狗設(shè)備作為定時器使用時,發(fā)出中斷信號,而不是復(fù)位信號。該中斷在探測函數(shù)s3c2410wdt_probe()中通過調(diào)用request_irq()函數(shù)向系統(tǒng)做了申請。中斷處理函數(shù)的只要功能是喂狗操作,使看門狗重新開始計數(shù): static irqreturn_t s3c2410wdt_irq(int irqno, void *param) { dev_info(wdt_dev, "watchdog timer expired (irq)\n");
s3c2410wdt_keepalive(); return IRQ_HANDLED; }
|