S3C3440看門狗驅動程序

S3C3440看門狗驅動程序 http://www.cnblogs.com/lfsblack/archive/2012/09/13/2684079.html

看門狗是當CPU進入錯誤狀態後,沒法恢復的狀況下,使計算機從新啓動html

因爲計算機在工做時不可避免的受到各類各樣的因素干擾,即便再優秀的計算機程序也可能由於這種干擾使計算機進入一個死循環,更嚴重的就是致使死機。node

有兩種辦法來處理這種狀況:數組

一是:採用人工復位的方法安全

二是:依賴於某種硬件來執行這個復位工做。這種硬件一般叫作看門狗(Watch Dog,WD)網絡

看門狗,就像一隻狗同樣,在那看着們,計算機中一般用定時器來處理這種週期性的動做多線程

看門狗其實是一個定時器,其硬件內部維護了一個定時器,每當時鐘信號到來時,計數寄存器減1。若是減到0,則系統重啓(就像狗同樣,看你不認識就咬你,可無論你是誰)。app

若是在減到0以前,系統又設置計數寄存器一個較大的值,那麼系統永遠不會重啓。系統的這種設置能力表示系統一直處於一種正常運行狀態。反之,若是計算機系統崩潰,那麼就沒法從新設置計數寄存器的值。當計數寄存器爲0,系統重啓ide

 

看門狗的工做原來很簡單,處理器內部通常都集成了一個看門狗硬件。其提供了三個寄存器函數

看門狗控制寄存器(WTCON)post

看門狗數據寄存器(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;
}

 

 

 

 

這一節東西仍是比較多的。。。。

當五百年的光陰只是一個騙局虛無時間中的人物又爲什麼而苦,爲什麼而喜! 我要這天,再遮不住我眼要這地,再埋不了我心要這衆生,都明白我意要那諸佛,都煙消雲散
相關文章
相關標籤/搜索