Linux驅動模型分析之platform

Linux驅動模型分析之platform

概述

  1. 一個現實的Linux設備和驅動通常需要掛接在一種總線上,對於本身依附於PCI、USB、I2C、SPI等設備而言,這自然不是問題。但是在嵌入式系統裏面,SOC系統中集成的獨立的外設控制器,掛接在SOC內存空間的外設卻不需要依附於此類總線。基於這一背景,Linux發明了一種虛擬總線,就是platform總線,相應設備稱爲platform_device,而驅動稱爲platform_driver。也就是說platform的設備是外設或者其控制器,就像EC外設一樣,被申請爲platform結構。

  2. 模型
    platform模型關心總線,設備和驅動3個實體,總線將設備和驅動綁定。在系統每註冊一個設備的時候,會尋找與之匹配的驅動,相反的,系統每註冊一個驅動的時候,會尋找與之匹配的設備,而匹配由總線完成。

platform總線

數據結構

  • 設備結構
struct device platform_bus =
{     
     .init_name = "platform",
};

platform也是一種設備,所以初始化一個device結構,設備名稱platform,因爲沒有指定父設備,所以註冊後將會在/sys/device/下出現一個platform目錄。

  • 總線結構
struct bus_type platform_bus_type =
{
     .name = "platform",                    
     .dev_attrs = platform_dev_attrs,     //設備屬性
     .match =platform_match,               //match函數,完成設備和驅動的匹配工作
     .uevent =platform_uevent,               //熱插拔操作
     .pm = &platform_dev_pm_ops,
};

註冊:

在系統中platform對應的文件是drivers/base/platform.c,他不是作爲一個模塊註冊到內核的,關鍵的註冊總線的函數由系統初始化部分,對應/init/main.c中的do_basic_setup函數間接調用。
- platform總線註冊函數:__init platform_bus_init()

int __init platform_bus_init(void)
{
     int err;
     early_platform_cleanup();
     err = device_register(&platform_bus);//總線也是設備
     if(err)
          return err;
     err = bus_register(&platform_bus_type);//註冊platform_bus_type總線到內核
     if(err)
          device_unregister(&platform_bus);
     return err;
}

這個函數向內核註冊了一種總線,首先由driver_init函數調用,driver_init函數由do_basic_setup函數調用,do_basic_setup函數由kernel_init調用。
所以platform總線在內核初始化的時候就註冊進了內核。

platform設備

數據結構

platform_device結構
struct platform_device
{
     const char *name;     //設備名
     int id;                         //設備編號
     struct device dev;
     u32 num_resource;     //設備使用資源數目
     struct resources *resource;     //設備使用資源
     const struct platform_device_id *id_entry;
     struct pdev_archdata archdata;
};

其中主要包含了device的屬性。

主要成員:
- const char *name;
匹配用的name。

  • int id;
    設備id,用於給要插入該總線的具有相同name的設備編號,如果只有一個設備編號-1.

  • u32 num_resource;
    資源數

  • struct resource *resource;
    存放資源的數組

resource結構
struct resource
{
     resource_size_t start;     //資源起始地址
     resource_size_t end;     // 資源結束地址 
     const char *name;
     unsigned long flags;     //資源類型
     struct resource *parent, *sibling, *child;
};

該結構體主要是資源分配。

主要成員:
- resource_size_t start;
資源的起始地址,含義隨着flags變化

  • resource_size_t end;
    資源的結束地址,含義隨着flags變化

  • const char *name;
    匹配用的name,要和platform_device和platform_driver中的name一致。

  • unsigned long flags;
    資源類型標誌位。可以爲:IORESOURCE_IO,IORESOURCE_MEM,IORESOURCE_IRQ,IORESOURCE_DMA
    如當flags爲IORESOURCE_MEM時,start和end分別表示該platform_device佔據的內存的起始地址和結束地址;
    當flags爲IORESOURCE_IRQ時,start和end分別表示該platform_device使用的中斷號的起始值和結束值;如果只有一箇中斷號,開始和結束值相同。

註冊

設備註冊函數:platform_device_register
int platform_device_register(struct platform_device *pdev)
{     
     device_initialize(&pdev->dev);
     return platform_device_add(pdev);
}

這個函數首先初始化了platform_device的device結構,然後調用platform_device_add,這個是註冊函數的關鍵,下面分析:

int platform_device_add(struct platform_device *pdev)
{
     inti, ret = 0;

     if(!pdev)
          {
               return -EINVALL;    
          }
     if(!pdev -> dev.parent)
          pdev -> dev.parent = &platform_bus;     //總線的設備結構:platform設備的父設備一般都是platform_bus

     pdev -> dev.bus = &platform_bus_type;     //總線的總線結構:掛到總線上

     if(pdev -> id != -1)
          dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id);     //設置設備名字
     else
          dev_set_name(&pdev->dev, "%s", pdev->name);

     for(i = 0; i < pdev->num_resources; i++)          //遍歷設備所佔資源
          {
               struct resource *p, *r = &pdev->resource[i];

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

               p = r->parent;     //設備父資源
               if(!p)
                    {
                         if(resource_type(r) == IORESOURCE_MEM)          //如果父資源沒有定義,根據資源的類型
                              p = &iomem_resource;                                   //分別賦予iomem_resource;  和ioport_resource; 
                         else if(resource_type(r) == IORESOURCE_IO)
                              p = &ioport_resource;
                    }

               if(p && insert_resource(p, r))          //調用insert_resource 插入資源
                    {
                         printk(KERN_ERR "%s:failed to claim resource %d\n", dev_name(&pdev->dev), 1);
                         ret = -EBUSY;
                         goto failed;
                    }
          }

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

     ret = device_add(&pdev->dev);     //註冊到設備模型中

     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;    

}

platform驅動

數據結構

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 (*resume)(struct platform_device *);
     struct device_driver driver;
     const struct platform_device_id *id_table;
};

其中包含了probe()、remove()、shutdown()、suspend()、resume()等函數,這些函數需要驅動來實現。

主要成員:
- int (probe)(struct platform_device );
這是一個匹配函數,通過比較platform_driver和platform_device的name來進行匹配。還有就是會在這個函數中初始化一些和該驅動有關的初始化。

  • struct device_driver driver;
    在這個結構體中,主要實現const char *name和struct module *owner這兩個成員。
    其中name就是用來匹配設備和驅動的。必須與device的name一致。
    owner表明該模塊的擁有者,一般都是THIS_MODULE;

  • const struct platform_device_id *id_table;
    這個不需要必然實現的,但是這個實現是另外一種匹配設備和驅動的方法。通過id_table來實現匹配。但是最終還是通過名字來對應匹配,只是匹配的名字被列入一張表中,platform_device中的name和這個表中的每一個值進行比較,直到找到相同的那一個。主要是爲了實現一個驅動對應多個設備的情況。設備之間的區別通過id_table.driver_data成員進行區分,從而在連接設備和驅動的時候可以針對設備區別,對加載的驅動程序進行調整。

註冊

驅動註冊函數:platform_driver_register
int platform_driver_register(struct platform_driver *drv)
{
     drv->driver.bus = &platform_bus_type;
     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_bus_type總線,將platform_driver結構中定義的probe,remove,shutdown賦值給device_driver結構中對應的成員,以供Linux設備模型核心調用。然後調用driver_register 將設備驅動註冊到Linux設備模型核心中。

總線,設備,驅動的整合

使用函數:platform_match
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);


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

     return (strcmp(pdev->name, drv->name) == 0);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章