歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

Linux Platform驅動模型詳述

我在Linux字符設備驅動框架一文中簡單介紹了Linux字符設備編程模型,在那個模型中,只要應用程序open()了相應的設備文件,就可以使用ioctl通過驅動程序來控制我們的硬件,這種模型直觀,但是從軟件設計的角度看,卻是一種十分糟糕的方式,它有一個致命的問題,就是設備信息和驅動代碼冗余在一起,一旦硬件信息發生改變甚至設備已經不在了,就必須要修改驅動源碼,非常的麻煩,為了解決這種驅動代碼和設備信息耦合的問題,Linux提出了platform bus(平台總線)的概念,即使用虛擬總線將設備信息和驅動程序進行分離,設備樹的提出就是進一步深化這種思想,將設備信息進行更好的整理。平台總線會維護兩條鏈表,分別管理設備和驅動,當一個設備被注冊到總線上的時候,總線會根據其名字搜索對應的驅動,如果找到就將設備信息導入驅動程序並執行驅動;當一個驅動被注冊到平台總線的時候,總線也會搜索設備。總之,平台總線負責將設備信息和驅動代碼匹配,這樣就可以做到驅動和設備信息的分離。

在設備樹出現之前,設備信息只能使用C語言的方式進行編寫,在3.0之後,設備信息就開始同時支持兩種編寫方式——設備樹、C語言,如果用設備樹,手動將設備信息寫到設備樹中之後,內核就可以自動從設備樹中提取相應的設備信息並將其封裝成相應的platform_device對象,i2c_device對象並注冊到相應的總線中,如果使用設備樹,我們就不需要對設備信息再進行編碼。如果使用C語言,顯然,我們需要將使用內核提供的結構將設備信息進行手動封裝,這種封裝又分為兩種形式,一種是使用平台文件(靜態),將整個板子的所有設備都寫在一個文件中並編譯進內核。另一種是使用模塊(動態),將我們需要的設備信息編譯成模塊在insmod進內核。對於ARM平台,使用設備樹封裝設備信息是將來的趨勢,但是由於歷史原因,當下的內核中這三種方式並存。封裝好後再創建相應的xxx_device實例最後注冊到總線中。針對平台總線的設備信息,我在Linux設備樹語法詳解一文中已經討論了設備樹的寫法,所以,本文主要討論4個問題:

  1. 如何使用C語言封裝設備信息?
  2. 設備樹的設備信息和C語言的設備信息如何轉換?
  3. 如何將C語言設備信息封裝到platform_device結構中?
  4. 如何將封裝好的platform_device結構注冊到平台總線中?

何為資源?

所謂的設備信息,主要分為兩種:硬件信息、軟件信息,硬件信息主要包括xxx控制器在xxx地址上,xxx設備占用了xxx中斷號,即地址資源中斷資源等。內核提供了struct resource來對這些資源進行封裝。軟件信息的種類就比較多樣,比如網卡設備中的MAC地址等等,這些信息需要我們以私有數據的形式封裝的設備對象(內核使用面向對象的思想編寫,一個設備的設備信息是一個對象,即一個結構體實例,一個設備的驅動方法也是一個對象)中,這部分信息就需要我們自定義結構進行封裝。

struct resource那點事

這個結構用來描述一個地址資源或中斷資源,除了這個結構,內核還提供了一些宏來幫助我們快速的創建一個resource對象。

//include/linux/ioport.h
 18 struct resource {    
 19         resource_size_t start;
 20         resource_size_t end;
 21         const char *name;
 22         unsigned long flags;
 23         unsigned long desc;
 24         struct resource *parent, *sibling, *child;
 25 };

struct resource
--19--> start表示資源開始的位置,如果是IO地址資源,就是起始物理地址,如果是中斷資源,就是中斷號;
--20--> end表示資源結束的位置,如果是IO地址地址,就是映射的最後一個物理地址,如果是中斷資源,就不用填;
--21--> name就是這個資源的名字。
--22--> flags表示資源類型,提取函數在尋找資源的時候會對比自己傳入的參數和這個成員,理論上只要和可以隨便寫,但是合格的工程師應該使用內核提供的宏,這些宏也在"ioport.h"中進行了定義,比如IORESOURCE_MEM表示這個資源是地址資源,IORESOURCE_IRQ表示這個資源是中斷資源...。

//include/linux/ioport.h
 33 #define IORESOURCE_BITS         0x000000ff      /* Bus-specific bits */
 35 #define IORESOURCE_TYPE_BITS    0x00001f00      /* Resource type */
 36 #define IORESOURCE_IO           0x00000100      /* PCI/ISA I/O ports */
 37 #define IORESOURCE_MEM          0x00000200
 38 #define IORESOURCE_REG          0x00000300      /* Register offsets */
 39 #define IORESOURCE_IRQ          0x00000400          
 40 #define IORESOURCE_DMA          0x00000800
 41 #define IORESOURCE_BUS          0x00001000
 ...
147 #define DEFINE_RES_IO(_start, _size)   
152 #define DEFINE_RES_MEM(_start, _size)   
157 #define DEFINE_RES_IRQ(_irq)  
162 #define DEFINE_RES_DMA(_dma)   

有了這幾個屬性,就可以完整的描述一個資源,但如果每個資源都需要單獨管理而不是組成某種數據結構,顯然是一種非常愚蠢的做法,所以內核的resource結構還提供了三個指針:parent,sibling,child(24),分別用來表示資源的父資源,兄弟資源,子資源,這樣內核就可以使用樹結構來高效的管理大量的系統資源,linux內核有兩種樹結構:iomem_resource,ioport_resource,進行板級開發的時候,通常將主板上的ROM資源放入iomem_resource樹的一個節點,而將系統固有的I/O資源掛到ioport_resource樹上。

下面是一個小例子,分別用兩種寫法表示了地址資源和中斷資源,強烈推薦使用DEFINE_RES_XXX的版本。

//IO地址資源,自己填充resource結構體+flags宏
struct resource res= {
    .start = 0x10000000,
    .end     = 0x20000000-1,
    .flags = IORESOURCE_MEM
};
//IO地址資源,使用內核提供的定義宏
struct resource res = DEFINE_RES_MEM(0x20000000, 1024);
//中斷資源,自己填充resource結構體+flags宏
struct resource res = { 
        .start  = 10,
        .flags  = IORESOURCE_IRQ,
};
//中斷資源,使用內核提供的定義宏
struct resource res = DEFINE_RES_IRQ(11);

下面是一個資源數組的實例,多個資源的時候就寫成數組,這裡我同時使用了上面兩種寫法。

struct resource res[] = {
    [0] = {
        .start  = 0x10000000,
        .end    = 0x20000000-1,
        .flags  = IORESOURCE_MEM
    },
    [1] = DEFINE_RES_MEM(0x20000000, 1024),
    [2] = {
        .start  = 10,   //中斷號
        .flags  = IORESOURCE_IRQ|IRQF_TRIGGER_RISING //include/linux/interrupt.h
    },
    [3] = DEFINE_RES_IRQ(11),   
};

resource VS dts

至此,我們已經討論了使用設備樹和resource結構兩種方式寫設備信息,顯然,這兩種方式最終是殊途同歸的,這裡我們簡單的討論一個二者之間的轉換問題。下圖是我在Linux設備樹語法詳解一文中用到的dm9000網卡的節點

將它的地址資源寫成resource就是這個樣子,清晰起見,這裡也是兩種寫法:

struct resource res[] = {
    [0] = {
        .start = 0x05000000,
        .end = 0x05000000+0x2-1,
        .flags = IORESOURCE_MEM,
    },
    [1] = DEFINE_RES_MEM(0x05000004,2),
};

platform_device對象

這個對象就是我們最終要注冊到平台總線上的設備信息對象,對設備信息進行編碼,其實就是創建一個platform_device對象,可以看出,platform_device和其他設備一樣,都是device的子類

//include/linux/platform_device.h
 22 struct platform_device {                                    
 23         const char      *name;
 24         int             id;
 25         bool            id_auto;
 26         struct device   dev;
 27         u32             num_resources;
 28         struct resource *resource;
 29 
 30         const struct platform_device_id *id_entry;
 31 
 32         /* MFD cell pointer */
 33         struct mfd_cell *mfd_cell;
 34 
 35         /* arch specific additions */
 36         struct pdev_archdata    archdata;
 37 };

在這個對象中,我們主要關心以下幾個成員

struct platform_device
--23-->name就是設備的名字,注意, 模塊名(lsmod)!=設備名(/proc/devices)!=設備文件名(/dev),這個名字就是驅動方法和設備信息匹配的橋梁
--24-->表示這個platform_device對象表征了幾個設備,當多個設備有共用資源的時候(MFD),裡面填充相應的設備數量,如果只是一個,填-1
--26-->父類對象(include/linux/device.h +722),我們通常關心裡面的platform_datarelease,前者是用來存儲私有設備信息的,後者是供當這個設備的最後引用被刪除時被內核回調,注意和rmmod沒關系。
--27-->資源的數量,即resource數組中元素的個數,我們用ARRAY_SIZE()宏來確定數組的大小(include/linux/kernel.h +54 #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]) )
--28-->資源指針,如果是多個資源就是struct resource[]數組名,

static struct platform_device demoobj = {
    //2. init obj
    .name   = "demo0",
    .id = 0,
    .dev    = {
        .platform_data = &priv,
        .release = dev_release,
    },
    .num_resources  = ARRAY_SIZE(res),
    .resource   = res,
};

設備對象的注冊與注銷

准備好了platform_device對象,接下來就可以將其注冊進內核,顯然內核已經為我們准備好了相關的函數

/**
 *注冊:把指定設備添加到內核中平台總線的設備列表,等待匹配,匹配成功則回調驅動中probe;
 */
int platform_device_register(struct platform_device *);
/**
 *注銷:把指定設備從設備列表中刪除,如果驅動已匹配則回調驅動方法和設備信息中的release;
 */
void platform_device_unregister(struct platform_device *);

通常,我們會將platform_device_register寫在模塊加載的函數中,將platform_device_unregister寫在模塊卸載函數中。

更多詳情見請繼續閱讀下一頁的精彩內容: http://www.linuxidc.com/Linux/2017-02/140229p2.htm

Copyright © Linux教程網 All Rights Reserved