[arm驅動]Platform總線原理

PlatForm設備驅動:

一、platform總線、設備與驅動

1.一個現實的Linux設備和驅動通常都需要掛接在一種總線上,對於本身依附於PCI、USB、I2C、SPI等的設備而言,這自然不是問題,

但是在嵌入式系統裏面,SoC系統中集成的獨立的外設控制器、掛接在SoC內存空間的外設等確不依附於此類總線。

基於這一背景,Linux發明了一種虛擬的總線,稱爲platform總線,相應的設備稱爲platform_device,而驅動成爲 platform_driver。


2.注意,所謂的platform_device並不是與字符設備、塊設備和網絡設備並列的概念,而是Linux系統提供的一種附加手段,

例如,在 S3C6410處理器中,把內部集成的I2C、RTC、SPI、LCD、看門狗等控制器都歸納爲platform_device,而它們本身就是字符設備。


3.基於Platform總線的驅動開發流程如下:

(1)定義初始化platform bus

(2)定義各種platform devices

(3)註冊各種platform devices

(4)定義相關platform driver

(5)註冊相關platform driver

(6)操作相關設備


4.平臺相關結構

//platform_device結構體

struct platform_device {

const char * name;/* 設備名 */

   u32 id;//設備id,用於給插入給該總線並且具有相同name的設備編號,如果只有一個設備的話填-1。

   struct device dev;//結構體中內嵌的device結構體。

   u32 num_resources;/* 設備所使用各類資源數量 */

 struct resource * resource;/*//定義平臺設備的資源*/

};


//平臺資源結構

struct resource {

   resource_size_t start;//定義資源的起始地址

   resource_size_t end;//定義資源的結束地址

const char *name;//定義資源的名稱

   unsigned long flags;//定義資源的類型,比如MEM,IO,IRQ,DMA類型

   struct resource *parent,*sibling,*child;

};


//設備的驅動:platform_driver這個結構體中包含probe()、remove()、shutdown()、suspend()resume()函數,通常也需要由驅動實現。

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 pm_ext_ops *pm;

   struct device_driver driver;

};


//系統中爲platform總線定義了一個bus_type的實例platform_bus_type,

struct bus_type platform_bus_type ={

.name = “platform”,

.dev_attrs = platform_dev_attrs,

.match = platform_match,

.uevent = platform_uevent,

.pm = PLATFORM_PM_OPS_PTR,

};

EXPORT_SYMBOL_GPL(platform_bus_type);


//這裏要重點關注其match()成員函數,正是此成員表明了platform_device和platform_driver之間如何匹配。

static int platform_match(struct device *dev, struct device_driver *drv)

{

   struct platform_device *pdev;


   pdev = container_of(dev, struct platform_device, dev);

   return (strncmp(pdev->name, drv->name, BUS_ID_SIZE)== 0);

}

//匹配platform_device和platform_driver主要看二者的name字段是否相同。

//對platform_device的定義通常在BSP的板文件中實現,在板文件中,將platform_device歸納爲一個數組,最終通過platform_add_devices()函數統一註冊。

//platform_add_devices()函數可以將平臺設備添加到系統中,這個函數的 原型爲:

int platform_add_devices(struct platform_device **devs,int num);

//該函數的第一個參數爲平臺設備數組的指針,第二個參數爲平臺設備的數量,它內部調用了platform_device_register()函 數用於註冊單個的平臺設備。


1. platform bus總線先被kenrel註冊。

2. 系統初始化過程中調用platform_add_devices或者platform_device_register,將平臺設備(platform devices)註冊到平臺總線中(platform bus)

3. 平臺驅動(platform driver)與平臺設備(platform device)的關聯是在platform_driver_register或者driver_register中實現,一般這個函數在驅動的初始化過程調用。

通過這三步,就將平臺總線,設備,驅動關聯起來。


二.Platform初始化

系統啓動時初始化時創建了platform_bus總線設備和platform_bus_type總線,platform總線是在內核初始化的時候就註冊進了內核。

內核初始化函數kernel_init()中調用了do_basic_setup() ,該函數中調用driver_init(),該函數中調用platform_bus_init(),我們看看platform_bus_init()函數:

int __init platform_bus_init(void)

{

interror;

      early_platform_cleanup();//清除platform設備鏈表

//該函數把設備名爲platform 的設備platform_bus註冊到系統中,其他的platform的設備都會以它爲parent。它在sysfs中目錄下./sys/devices/platform。

//platform_bus總線也是設備,所以也要進行設備的註冊

//struct device platform_bus ={

//.init_name ="platform",

//};

error= device_register(&platform_bus);//將平臺bus作爲一個設備註冊,出現在sys文件系統的device目錄

if(error)

             return error;

//接着bus_register(&platform_bus_type)註冊了platform_bus_type總線.

/*

      struct bus_type platform_bus_type ={

.name = “platform”,

.dev_attrs = platform_dev_attrs,

.match = platform_match,

.uevent = platform_uevent,

.pm = PLATFORM_PM_OPS_PTR,

};

*/

//默認platform_bus_type中沒有定義probe函數。

error= bus_register(&platform_bus_type);//註冊平臺類型的bus,將出現sys文件系統在bus目錄下,創建一個platform的目錄,以及相關屬性文件

if(error)

             device_unregister(&platform_bus);

      return error;

}


//總線類型match函數是在設備匹配驅動時調用,uevent函數在產生事件時調用。

//platform_match函數在當屬於platform的設備或者驅動註冊到內核時就會調用,完成設備與驅動的匹配工作。

static int platform_match(struct device *dev, struct device_driver *drv)

{

      struct platform_device *pdev = to_platform_device(dev);

      struct platform_driver *pdrv = to_platform_driver(drv);

/* match against the id table first */

if(pdrv->id_table)

             return platform_match_id(pdrv->id_table, pdev)!=NULL;

/* fall-back to driver name match */

      return (strcmp(pdev->name, drv->name)== 0);//比較設備和驅動的名稱是否一樣


}


static const struct platform_device_id *platform_match_id(struct platform_device_id *id,struct platform_device *pdev)

{

while(id->name[0]){

if(strcmp(pdev->name, id->name)== 0){

                    pdev->id_entry = id;

                    return id;

}

             id++;

}

      return NULL;


}


//不難看出,如果pdrv的id_table數組中包含了pdev->name,或者drv->name和pdev->name名字相同,都會認爲是匹配成功。

//id_table數組是爲了應對那些對應設備和驅動的drv->name和pdev->name名字不同的情況。


//再看看platform_uevent()函數:platform_uevent 熱插拔操作函數

static int platform_uevent(struct device *dev, struct kobj_uevent_env *env)

{

      struct platform_device *pdev = to_platform_device(dev);

      add_uevent_var(env,"MODALIAS=%s%s", PLATFORM_MODULE_PREFIX,(pdev->id_entry)? pdev->id_entry->name : pdev->name);

      return 0;

}


//添加了MODALIAS環境變量,我們回顧一下:platform_bus. parent->kobj->kset->uevent_ops爲device_uevent_ops,bus_uevent_ops的定義如下:

static struct kset_uevent_ops device_uevent_ops ={

.filter= dev_uevent_filter,

.name = dev_uevent_name,

.uevent = dev_uevent,

};

//當調用device_add()時會調用kobject_uevent(&dev->kobj, KOBJ_ADD)產生一個事件,這個函數中會調用相應的kset_uevent_ops的uevent函數,


三.Platform設備的註冊

我們在設備模型的分析中知道了把設備添加到系統要調用device_initialize()和platform_device_add(pdev)函數。


Platform設備的註冊分兩種方式:

1.對於platform設備的初註冊,內核源碼提供了platform_device_add()函數,輸入參數platform_device可以是靜態的全局設備,它是進行一系列的操作後調用device_add()將設備註冊到相應的總線(platform總線)上,

內核代碼中platform設備的其他註冊函數都是基於這個函數,如platform_device_register()、platform_device_register_simple()、platform_device_register_data()等。


2.另外一種機制就是動態申請platform_device_alloc()一個platform_device設備,然後通過platform_device_add_resources及platform_device_add_data等添加相關資源和屬性。


無論哪一種platform_device,最終都將通過platform_device_add這冊到platform總線上。

區別在於第二步:其實platform_device_add()包括device_add(),不過要先註冊resources,然後將設備掛接到特定的platform總線。


3.第一種平臺設備註冊方式

//platform_device是靜態的全局設備,即platform_device結構的成員已經初始化完成

//直接將平臺設備註冊到platform總線上

/*platform_device_register和device_register的區別:

(1).主要是有沒有resource的區別,前者的結構體包含後面,並且增加了struct resource結構體成員,後者沒有。

       platform_device_register在device_register的基礎上增加了struct resource部分的註冊。

       由此。可以看出,platform_device---paltform_driver_register機制與device-driver的主要區別就在於resource。

       前者適合於具有獨立資源設備的描述,後者則不是。

(2).其實linux的各種其他驅動機制的基礎都是device_driver。只不過是增加了部分功能,適合於不同的應用場合.

*/

int platform_device_register(struct platform_device *pdev)

{

   device_initialize(&pdev->dev);//初始化platform_device內嵌的device

   return platform_device_add(pdev);//把它註冊到platform_bus_type上

}


int platform_device_add(struct platform_device *pdev)

{

int i, ret = 0;

if(!pdev)

             return -EINVAL;

if(!pdev->dev.parent)

           pdev->dev.parent =&platform_bus;//設置父節點,即platform_bus作爲總線設備的父節點,其餘的platform設備都是它的子設備


//platform_bus是一個設備,platform_bus_type纔是真正的總線    

       pdev->dev.bus =&platform_bus_type;//設置platform總線,指定bus類型爲platform_bus_type


//設置pdev->dev內嵌的kobj的name字段,將platform下的名字傳到內部device,最終會傳到kobj

if(pdev->id !=-1)

             dev_set_name(&pdev->dev,"%s.%d", pdev->name, pdev->id);

else

             dev_set_name(&pdev->dev,"%s", pdev->name);



//初始化資源並將資源分配給它,每個資源的它的parent不存在則根據flags域設置parent,flags爲IORESOURCE_MEM,

//則所表示的資源爲I/O映射內存,flags爲IORESOURCE_IO,則所表示的資源爲I/O端口。

for(i = 0; i < pdev->num_resources; i++){

           struct resource *p,*r =&pdev->resource[i];

if(r->name ==NULL)//資源名稱爲NULL則把設備名稱設置給它

                   r->name = dev_name(&pdev->dev);

           p = r->parent;//取得資源的父節點,資源在內核中也是層次安排的

if(!p){

if(resource_type(r)== IORESOURCE_MEM)//如果父節點爲NULL,並且資源類型爲IORESOURCE_MEM,則把父節點設置爲iomem_resource

                      p =&iomem_resource;

elseif(resource_type(r)== IORESOURCE_IO)//否則如果類型爲IORESOURCE_IO,則把父節點設置爲ioport_resource

                    p =&ioport_resource;

}


//從父節點申請資源,也就是出現在父節點目錄層次下

if(p && insert_resource(p, r)){

              printk(KERN_ERR "%s: failed to claim resource %d\n",dev_name(&pdev->dev), i);ret =-EBUSY;

              goto failed;

}

}


       pr_debug("Registering platform device '%s'. Parent at %s\n",dev_name(&pdev->dev), dev_name(pdev->dev.parent));

//device_creat() 創建一個設備並註冊到內核驅動架構...

//device_add() 註冊一個設備到內核,少了一個創建設備..

       ret = device_add(&pdev->dev);//就在這裏把設備註冊到總線設備上,標準設備註冊,即在sys文件系統中添加目錄和各種屬性文件

if(ret == 0)

             return ret;


       failed:

while(--i >= 0){

             struct resource *r =&pdev->resource[i];

             unsigned long type = resource_type(r);

if(type == IORESOURCE_MEM || type == IORESOURCE_IO)

                    release_resource(r);

}

       return ret;


}


4.第二種平臺設備註冊方式

//先分配一個platform_device結構,對其進行資源等的初始化

//之後再對其進行註冊,再調用platform_device_register()函數

struct platform_device * platform_device_alloc(const char *name,int id)

{

   struct platform_object *pa;

/*

   struct platform_object {

      struct platform_device pdev;

      char name[1];

};

*/

   pa = kzalloc(sizeof(struct platform_object)+ strlen(name), GFP_KERNEL);//該函數首先爲platform設備分配內存空間

if(pa){

       strcpy(pa->name, name);

       pa->pdev.name = pa->name;//初始化platform_device設備的名稱

       pa->pdev.id = id;//初始化platform_device設備的id

       device_initialize(&pa->pdev.dev);//初始化platform_device內嵌的device

       pa->pdev.dev.release = platform_device_release;

}

   return pa ?&pa->pdev :NULL;

}


//一個更好的方法是,通過下面的函數platform_device_register_simple()動態創建一個設備,並把這個設備註冊到系統中:

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);

}


//該函數就是調用了platform_device_alloc()和platform_device_add()函數來創建的註冊platform device,函數也根據res參數分配資源,看看platform_device_add_resources()函數:

int platform_device_add_resources(struct platform_device *pdev,struct resource *res, unsigned int num)

{

      struct resource *r;

      r = kmalloc(sizeof(struct resource)* num, GFP_KERNEL);//爲資源分配內存空間

if(r){

             memcpy(r, res, sizeof(struct resource)* num);

             pdev->resource = r;//並拷貝參數res中的內容,鏈接到device並設置其num_resources

             pdev-> num_resources = num;

}

      return r ? 0 :-ENOMEM;

}



四.Platform設備驅動的註冊

我們在設備驅動模型的分析中已經知道驅動在註冊要調用driver_register()

platform driver的註冊函數platform_driver_register()同樣也是進行其它的一些初始化後調用driver_register()將驅動註冊到platform_bus_type總線上.


int platform_driver_register(struct platform_driver *drv)

{

      drv->driver.bus =&platform_bus_type;//它將要註冊到的總線

/*設置成platform_bus_type這個很重要,因爲driver和device是通過bus聯繫在一起的,

           具體在本例中是通過 platform_bus_type中註冊的回調例程和屬性來是實現的,

           driver與device的匹配就是通過 platform_bus_type註冊的回調例程platform_match ()來完成的。

*/

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;

      return driver_register(&drv->driver);//註冊驅動

}


//然後設定了platform_driver內嵌的driver的probe、remove、shutdown函數。

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);//調用platform_driver的probe()函數,這個函數一般由用戶自己實現

//例如下邊結構,回調的是serial8250_probe()函數

/*

               static struct platform_driver serial8250_isa_driver ={

.probe        = serial8250_probe,

.remove        = __devexit_p(serial8250_remove),

.suspend    = serial8250_suspend,

.resume= serial8250_resume,

.driver        ={

.name    ="serial8250",

.owner    = THIS_MODULE,

},

};

*/

}


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);


}


//總結:

1.從這三個函數的代碼可以看到,又找到了相應的platform_driver和platform_device,然後調用platform_driver的probe、remove、shutdown函數。這是一種高明的做法:

在不針對某個驅動具體的probe、remove、shutdown指向的函數,而通過上三個過度函數來找到platform_driver,然後調用probe、remove、shutdown接口。

如果設備和驅動都註冊了,就可以通過bus ->match、bus->probe或driver->probe進行設備驅動匹配了。

2.驅動註冊的時候platform_driver_register()->driver_register()->bus_add_driver()->driver_attach()->bus_for_each_dev()

對每個掛在虛擬的platform bus的設備作__driver_attach()->driver_probe_device()->drv->bus->match()==platform_match()->比較strncmp(pdev->name, drv->name, BUS_ID_SIZE)

如果相符就調用platform_drv_probe()->driver->probe(),如果probe成功則綁定該設備到該驅動。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章