linux platform 驅動模型分析

linux platform 驅動模型分析
一. 概述
    platform設備和驅動與linux設備模型密切相關。platform在linux設備模型中,其實就是一種虛擬總線沒有對應的硬件結構。它的主要作用就是管理系統的外設資源,比如io內存,中斷信號線。現在大多數處理器芯片都是soc,如s3c2440,它包括處理器內核(arm920t)和系統的外設(lcd接口,nandflash接口等)。linux在引入了platform機制之後,內核假設所有的這些外設都掛載在platform虛擬總線上,以便進行統一管理。
二. platform 總線
   1. 在系統中platform對應的文件drivers/base/platform.c,它不是作爲一個模塊註冊到內核的,關鍵的註冊總線的函數由系統初始化部分,對應/init/main.c中的do_basic_setup函數間接調用。這裏可以看出platform非常重要,要在系統其他驅動加載之前註冊。下面分析platform總線註冊函數
  1. int __init platform_bus_init(void)  
  2. {  
  3.     int error;  
  4.     early_platform_cleanup();  
  5.     error = device_register(&platform_bus);  
  6.         //總線也是設備,所以也要進行設備的註冊  
  7.     if (error)  
  8.         return error;  
  9.     error =  bus_register(&platform_bus_type);  
  10.         //註冊platform_bus_type總線到內核  
  11.     if (error)  
  12.         device_unregister(&platform_bus);  
  13.     return error;  
  14. }  
    這個函數向內核註冊了一種總線。他首先由/drivers/base/init.c中的driver_init函數調用,driver_init函數由/init/main.c中的do_basic_setup函數調用,do_basic_setup這個函數由kernel_init調用,所以platform總線是在內核初始化的時候就註冊進了內核。
   2. platform_bus_type 總線結構與設備結構
    (1) platform總線 設備結構
  1. struct device platform_bus = {  
  2.     .init_name  = "platform",  
  3. };  
    platform總線也是一種設備,這裏初始化一個device結構,設備名稱platform,因爲沒有指定父設備,所以註冊後將會在/sys/device/下出現platform目錄。
    (2) platform總線 總線結構
  1. struct bus_type platform_bus_type = {  
  2.     .name       = "platform",  
  3.     .dev_attrs  = platform_dev_attrs,   
  4.     .match      = platform_match,  
  5.     .uevent     = platform_uevent,  
  6.     .pm     = &platform_dev_pm_ops,  
  7. };  
    platform_dev_attrs    設備屬性
    platform_match        match函數,這個函數在當屬於platform的設備或者驅動註冊到內核時就會調用,完成設備與驅動的匹配工作。
    platform_uevent       熱插拔操作函數
三. platform 設備
   1. platform_device 結構
  1. struct platform_device {  
  2.     const char  * name;  
  3.     int     id;  
  4.     struct device   dev;  
  5.     u32     num_resources;  
  6.     struct resource * resource;  
  7.     struct platform_device_id   *id_entry;  
  8.     /* arch specific additions */  
  9.     struct pdev_archdata    archdata;  
  10. };  
    (1)platform_device結構體中有一個struct resource結構,是設備佔用系統的資源,定義在ioport.h中,如下
  1. struct resource {  
  2.     resource_size_t start;  
  3.     resource_size_t end;  
  4.     const char *name;  
  5.     unsigned long flags;  
  6.     struct resource *parent, *sibling, *child;  
  7. };  
    (2) num_resources 佔用系統資源的數目,一般設備都佔用兩種資源,io內存和中斷信號線。這個爲兩種資源的總和。
   2. 設備註冊函數 platform_device_register
  1. int platform_device_register(struct platform_device *pdev)  
  2. {  
  3.     device_initialize(&pdev->dev);  
  4.     return platform_device_add(pdev);  
  5. }  
    這個函數首先初始化了platform_device的device結構,然後調用platform_device_add,這個是註冊函數的關鍵,下面分析platform_device_add:
  1. int platform_device_add(struct platform_device *pdev)  
  2. {  
  3.     int i, ret = 0;  
  4.   
  5.     if (!pdev)  
  6.         return -EINVAL;  
  7.   
  8.     if (!pdev->dev.parent)  
  9.         pdev->dev.parent = &platform_bus;  
  10.         //可以看出,platform設備的父設備一般都是platform_bus,所以註冊後的platform設備都出現在/sys/devices/platform_bus下  
  11.     pdev->dev.bus = &platform_bus_type;  
  12.         //掛到platform總線上  
  13.     if (pdev->id != -1)  
  14.         dev_set_name(&pdev->dev, "%s.%d", pdev->name,  pdev->id);  
  15.     else  
  16.         dev_set_name(&pdev->dev, "%s", pdev->name);  
  17.         //設置設備名字,這個名字與/sys/devices/platform_bus下的名字對應  
  18.     for (i = 0; i < pdev->num_resources; i++) { //下面操作設備所佔用的系統資源  
  19.         struct resource *p, *r = &pdev->resource[i];  
  20.   
  21.         if (r->name == NULL)  
  22.             r->name = dev_name(&pdev->dev);  
  23.   
  24.         p = r->parent;  
  25.         if (!p) {  
  26.             if (resource_type(r) == IORESOURCE_MEM)  
  27.                 p = &iomem_resource;  
  28.             else if (resource_type(r) == IORESOURCE_IO)  
  29.                 p = &ioport_resource;  
  30.         }  
  31.   
  32.         if (p && insert_resource(p, r)) {  
  33.             printk(KERN_ERR  
  34.                    "%s: failed to claim resource %d\n",  
  35.                    dev_name(&pdev->dev), i);  
  36.             ret = -EBUSY;  
  37.             goto failed;  
  38.         }  
  39.     }  
  40.        //上面主要是遍歷設備所佔用的資源,找到對應的父資源,如果沒有定義,那麼根據資源的類型,分別賦予iomem_resource和ioport_resource,然後調用insert_resource插入資源。  
  41.        //這樣系統的資源就形成了一個樹形的數據結構,便於系統的管理  
  42.     pr_debug("Registering platform device '%s'. Parent at %s\n",  
  43.          dev_name(&pdev->dev), dev_name(pdev->dev.parent));  
  44.   
  45.     ret = device_add(&pdev->dev);  
  46.         //註冊到設備模型中  
  47.     if (ret == 0)  
  48.         return ret;  
  49.  failed:  
  50.     while (--i >= 0) {  
  51.         struct resource *r = &pdev->resource[i];  
  52.         unsigned long type = resource_type(r);  
  53.         if (type == IORESOURCE_MEM || type == IORESOURCE_IO)  
  54.             release_resource(r);  
  55.     }  
  56.     return ret;  
  57. }  
   3. mini2440內核註冊platform設備過程
    因爲一種soc確定之後,其外設模塊就已經確定了,所以註冊platform設備就由板級初始化代碼來完成,在mini2440中是mach-mini2440.c的mini2440_machine_init函數中調用platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices))來完成註冊。這個函數完成mini2440的所有platform設備的註冊:
    (1) platform_add_devices函數是platform_device_register的簡單封裝,它向內核註冊一組platform設備
    (2) mini2440_devices是一個platform_device指針數組,定義如下:
  1. static struct platform_device *mini2440_devices[] __initdata = {  
  2.     &s3c_device_usb,  
  3.     &s3c_device_rtc,  
  4.     &s3c_device_lcd,  
  5.     &s3c_device_wdt,  
  6.     &s3c_device_i2c0,  
  7.     &s3c_device_iis,  
  8.     &mini2440_device_eth,  
  9.     &s3c24xx_uda134x,  
  10.     &s3c_device_nand,  
  11.     &s3c_device_sdi,  
  12.     &s3c_device_usbgadget,  
  13. };  
    這個就是mini2440的所有外設資源了,每個外設的具體定義在/arch/arm/plat-s3c24xx/devs.c,下面以s3c_device_lcd爲例說明,其他的類似。s3c_device_lcd在devs.c中它定義爲:
  1. struct platform_device s3c_device_lcd = {  
  2.     .name         = "s3c2410-lcd",  
  3.     .id       = -1,  
  4.     .num_resources    = ARRAY_SIZE(s3c_lcd_resource),  
  5.     .resource     = s3c_lcd_resource,  
  6.     .dev              = {  
  7.         .dma_mask       = &s3c_device_lcd_dmamask,  
  8.         .coherent_dma_mask  = 0xffffffffUL  
  9.     }  
  10. };  
    可以看出,它佔用的資源s3c_lcd_resource,定義如下:
  1. static struct resource s3c_lcd_resource[] = {  
  2.     [0] = {  
  3.         .start = S3C24XX_PA_LCD,  
  4.         .end   = S3C24XX_PA_LCD + S3C24XX_SZ_LCD - 1,  
  5.         .flags = IORESOURCE_MEM,  
  6.     },  
  7.     [1] = {  
  8.         .start = IRQ_LCD,  
  9.         .end   = IRQ_LCD,  
  10.         .flags = IORESOURCE_IRQ,  
  11.     }  
  12. };  
   這是一個數組,有兩個元素,說明lcd佔用了系統兩個資源,一個資源類型是IORESOURCE_MEM代表io內存,起使地址S3C24XX_PA_LCD,這個是LCDCON1寄存器的地址。另外一個資源是中斷信號線。
四. platform設備驅動
   如果要將所寫的驅動程序註冊成platform驅動,那麼所做的工作就是初始化一個platform_driver,然後調用platform_driver_register進行註冊。
   1. 基本數據機構platform_driver
  1. struct platform_driver {  
  2.     int (*probe)(struct platform_device *);  
  3.     int (*remove)(struct platform_device *);  
  4.     void (*shutdown)(struct platform_device *);  
  5.     int (*suspend)(struct platform_device *, pm_message_t state);  
  6.     int (*resume)(struct platform_device *);  
  7.     struct device_driver driver;  
  8.     struct platform_device_id *id_table;  
  9. };  
    這是platform驅動基本的數據結構,在驅動程序中我們要做的就是聲明一個這樣的結構並初始化。下面是lcd驅動程序對它的初始化:
  1. static struct platform_driver s3c2412fb_driver = {  
  2.     .probe      = s3c2412fb_probe,  
  3.     .remove     = s3c2410fb_remove,  
  4.     .suspend    = s3c2410fb_suspend,  
  5.     .resume     = s3c2410fb_resume,  
  6.     .driver     = {  
  7.         .name   = "s3c2412-lcd",  
  8.         .owner  = THIS_MODULE,  
  9.     },  
  10. };  
    上面幾個函數是我們要實現的,它將賦值給device_driver中的相關成員,probe函數是用來查詢特定設備是夠真正存在的函數。當設備從系統刪除的時候調用remove函數。
   2. 註冊函數platform_driver_register
  1. int platform_driver_register(struct platform_driver *drv)  
  2. {  
  3.     drv->driver.bus = &platform_bus_type;  
  4.     if (drv->probe)  
  5.         drv->driver.probe = platform_drv_probe;  
  6.     if (drv->remove)  
  7.         drv->driver.remove = platform_drv_remove;    
  8.     if (drv->shutdown)  
  9.         drv->driver.shutdown = platform_drv_shutdown;  
  10.     return driver_register(&drv->driver);  
  11. }  
    這個函數首先使驅動屬於platform_bus_type總線,將platform_driver結構中的定義的probe,remove,shutdown賦值給device_driver結構中的相應成員,以供linux設備模型核心調用,然後調用driver_regster將設備驅動註冊到linux設備模型核心中。
五. 各環節的整合
    前面提到mini2440板級初始化程序將它所有的platform設備註冊到了linux設備模型核心中,在/sys/devices/platform目錄中都有相應的目錄表示。platform驅動則是由各個驅動程序模塊分別註冊到系統中的。但是他們是如何聯繫起來的呢,這就跟linux設備模型核心有關係了。在ldd3中的linux設備模型的各環節的整合中有詳細的論述。這裏簡要說明一下platform實現的方法。每當註冊一個platform驅動的時候就會調用driver_register,這個函數的調用會遍歷設備驅動所屬總線上的所有設備,並對每個設備調用總線的match函數。platform驅動是屬於platform_bus_type總線,所以調用platform_match函數。這個函數實現如下:
  1. static int platform_match(struct device *dev, struct device_driver *drv)  
  2. {  
  3.     struct platform_device *pdev = to_platform_device(dev);  
  4.     struct platform_driver *pdrv = to_platform_driver(drv);  
  5.   
  6.     /* match against the id table first */  
  7.     if (pdrv->id_table)  
  8.         return platform_match_id(pdrv->id_table, pdev) != NULL;  
  9.     /* fall-back to driver name match */  
  10.     return (strcmp(pdev->name, drv->name) == 0);  
  11. }  
    這個函數將device結構轉換爲platform_devcie結構,將device_driver結構轉換爲platform_driver結構,並調用platform_match_id對設備與驅動相關信息進行比較。如果沒有比較成功會返回0,以便進行下一個設備的比較,如果比較成功就會返回1,並且將device結構中的driver指針指向這個驅動。然後調用device_driver中的probe函數,在lcd驅動中就是s3c2412fb_probe。這個函數是我們要編寫的函數。這個函數檢測驅動的狀態,並且測試能否真正驅動設備,並且做一些初始化工作。

發佈了0 篇原創文章 · 獲贊 2 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章