Linux驅動之platform總線詳解

1、platform 總線簡介

1.1、Linux 驅動的分離和分層思想

1.1.1、Linux 驅動的分離

        先講 Linux 驅動的分離,Linux 操作系統支持在各類 CPU 上運行,因為每一種 CPU 對設備的驅動不一樣,這樣就造成瞭 Linux 內核中積累瞭大量代碼,並且這些代碼關於同一設備的描述大致相同,這就使得內核代碼很冗餘。以 CPU 通過 I2C 控制 MPU6050 為例:

        從圖可以看出每一種平臺下都有一套主機驅動和一套設備驅動,因為每個平臺的 I2C 控制器不同,所以這個主機驅動得每個平臺配一個自己的,但大傢所用的 MPU6050 是一樣的,所以完全可以就共用一套設備驅動代碼。完善後框架如下:

        當然,這隻是對於 I2C 下的 MPU6050 這個設備,實際情況下,I2C 下肯定會掛載很多設備,根據這個思路,我們可以得到框架為:

         而在實際開發中,I2C 主機驅動半導體廠傢會編寫好,設備驅動也由設備廠傢編寫好,我們隻需要提供設備信息即可,如設備接到那個 I2C 接口上,I2C 速度為多少。這樣就相當於把設備信息從設備驅動中剝離出來,而設備驅動也會用標準方法去獲取設備信息(如從設備樹中獲取設備信息)。這樣就相當於驅動隻負責驅動,設備(信息)隻負責設備,想辦法將兩者進行匹配即可,來做這個匹配工作的就是總線,這就構成瞭 Linux 中的 總線-驅動-設備 模型。結構圖如下:

1.2、platform 平臺驅動模型

        上面我們講做設備驅動的分離,得到 總線-驅動-設備 模型,這個總線就是我平常所說的 I2C、SPI、USB 等總線。但問題是有些設備是不需要通過某一跟總線的,這是就引入瞭 platform 總線。

        這裡需要註意的是,platform 總線是區別於 USB、SPI、I2C 這些總線的虛擬總線。說它虛擬是因為 SoC 與一些外設如 LED、定時器、蜂鳴器是通過內存的尋址空間來進行尋址的,所以 CPU 與這些設備通信壓根就不需要總線,那麼硬件上也就沒有這樣一個總線。但內核有對這些設備做統一管理的需求,所以就對這些直接通過內存尋址的設備虛擬瞭一條 platform 總線,所有直接通過內存尋址的設備都映射到這條虛擬總線上。

        platform 總線的優點:

        1、通過 platform 總線,可以遍歷所有掛載在 platform 總線上的設備;

        2、實現設備和驅動的分離,通過 platform 總線,設備和驅動是分開註冊的,因為有 probe 函數,可以隨時檢測與設備匹配的驅動,匹配成功就會把這個驅動向內核註冊;

        3、一個驅動可供同類的幾個設備使用,這個功能的實現是因為驅動註冊過程中有一個遍歷設備的操作。      

2、platform 框架

2.1、platform 總線

        Linux 內核用 bus_type 結構體來表示總線,我們所用的 I2C、SPI、USB 都是用這個結構體來定義的。該結構體如下:

/* include/linux/device.h */
 
struct bus_type {
    const char *name;                                  /* 總線名字 */
    const char *dev_name; 
    struct device *dev_root;
    struct device_attribute *dev_attrs;
    const struct attribute_group **bus_groups;         /* 總線屬性 */
    const struct attribute_group **dev_groups;         /* 設備屬性 */
    const struct attribute_group **drv_groups;         /* 驅動屬性 */
 
    int (*match)(struct device *dev, struct device_driver *drv);      /* 設備驅動匹配函數 */
    int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
    int (*probe)(struct device *dev);
    int (*remove)(struct device *dev);
    void (*shutdown)(struct device *dev);
 
    int (*online)(struct device *dev);
    int (*offline)(struct device *dev);
    int (*suspend)(struct device *dev, pm_message_t state);
    int (*resume)(struct device *dev);
    const struct dev_pm_ops *pm;
    const struct iommu_ops *iommu_ops;
    struct subsys_private *p;
    struct lock_class_key lock_key;
};

         platform 總線是 bus_type 類型的常量,之所以說它是常量是因為這個變量已經被 Linux 內核賦值好瞭,其結構體成員對應的函數也已經在內核裡面寫好。

定義如下:

/* drivers/base/platform.c */
 
struct bus_type platform_bus_type = {
    .name = "platform",
    .dev_groups = platform_dev_groups,
    .match = platform_match,       /* 匹配函數 */
    .uevent = platform_uevent,
    .pm = &platform_dev_pm_ops,
};

        platform_bus_type 中的 platform_match 就是我們前面所說的做驅動和設備匹配的函數,該函數定義如下:

/* drivers/base/platform.c */
 
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);
 
    /*When driver_override is set,only bind to the matching driver*/
    if (pdev->driver_override)
        return !strcmp(pdev->driver_override, drv->name);
 
    /* 設備樹OF類型匹配
       驅動基類的 of_match_table 裡的 compatible 匹配表與設備樹
       每一個設備節點的 compatible 屬性作比較,有相同就表示匹配成功 */
    if (of_driver_match_device(dev, drv))
        return 1;
 
    /* ACPI 匹配 */
    if (acpi_driver_match_device(dev, drv))
        return 1;
 
    /* id_table 匹配
       platform 驅動裡的 id_table 數組會保存很多 id 信息  */
    if (pdrv->id_table)
        return platform_match_id(pdrv->id_table, pdev) != NULL;
 
    /* name 匹配
       直接粗暴比較platform 的驅動和設備裡面的 name 信息 */
    return (strcmp(pdev->name, drv->name) == 0);
}

        這個匹配函數什麼時候用,在哪裡用,我們不妨先留一個懸念。

2.2、platform 驅動

2.2.1、platform 驅動定義

        platform 驅動用結構體 platform_driver 來表示,該結構體內容為:

/* include/linux/platform_device.h */
 
struct platform_driver {
    int (*probe)(struct platform_device *);    /* platform驅動和platform設備匹配後會執行這個probe函數 */
    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;  /* id_table表 */
    bool prevent_deferred_probe;
};

         platform_driver 中 const struct platform_device_id *id_table 是 id_table 表,在 platform 總線匹配驅動和設備時 id_table 表匹配法時使用的,這個 id_table 表其實是一個數組,裡面的每個元素類型都為 platform_device_id,platform_device_id 是一個結構體,內容如下:

struct platform_device_id {
    char name[PLATFORM_NAME_SIZE];
    kernel_ulong_t driver_data;
};

        platform_driver 中 driver 是一個驅動基類,相當於驅動具有的最基礎的屬性,在不同總線下具有的屬性則存放在 platform_driver 結構體下。

        驅動基類結構體 device_driver 內容為:

/* include/linux/device.h */
 
struct device_driver {
    const char *name;                               /* platform 總線來匹配設備與驅動的第四種方        
                                                       法就是直接粗暴匹配兩者的 name 字段 */
    struct bus_type *bus;
    struct module *owner;
    const char *mod_name; 
    bool suppress_bind_attrs; 
    const struct of_device_id *of_match_table;      /* 采用設備樹時驅動使用的的匹配表 */
    const struct acpi_device_id *acpi_match_table;
    int (*probe) (struct device *dev);
    int (*remove) (struct device *dev);
    void (*shutdown) (struct device *dev);
    int (*suspend) (struct device *dev, pm_message_t state);
    int (*resume) (struct device *dev);
    const struct attribute_group **groups;
    const struct dev_pm_ops *pm;
    struct driver_private *p;
};

         driver 中 of_match_table 也是一個匹配表,這個匹配表是 platform 總線給驅動和設備做匹配時使用設備樹匹配時用的,也是一個數組,數組元素都為 of_device_id 類型,該類型結構體如下:

/* include/linux/mod_devicetable.h */
 
struct of_device_id {
    char name[32];
    char type[32];
    char compatible[128];   /* 使用設備樹匹配時就是把設備節點的 compatible 屬性值和 of_match_table 中
                               每個項目的這個 compatible 作比較,如果有相等的就表示設備和驅動匹配成功 */
    const void *data;
};

2.2.2、platform 驅動註冊

        用 platform_driver 結構體定義好 platform 驅動後,用 platform_driver_register 函數向 Linux 內核註冊 platform 驅動,函數大致流程如下:

platform_driver_register (drv)
    -> __platform_driver_register
        -> drv->driver.probe = platform_drv_probe;       /* 把 platform_drv_probe 這個函數賦給
                                                            platform 驅動裡的驅動基類 drier 的 probe 函數 */
        -> driver_registe (&drv->driver)                 /* 向 Linux 內核註冊驅動基類 driver  */
            -> ...... 
                -> drv->driver->probe                    /* 最終執行驅動基類 driver 的 probe 函數,
                                                            其實就是上面給的 platform_drv_probe 函數 */
                    -> platform_drv_probe
                        -> drv->probe                    /* platform_drv_probe 函數又會執行                 
                                                            platform 驅動 drv 的 probe 函數 */

        上面的分析中從 driver_register (&drv->driver) 到 drv->driver->probe 這一步我們用省略號代替瞭,現在來做一下分析:

driver_register(&drv->driver)
    -> bus_add_driver                             /* 向總線添加驅動 */
        -> driver_attach 
            -> bus_for_each_dev                   /* 查找總線下每一個設備,即遍歷操作 */ 
                -> __driver_attach                /* 每個設備都調用此函數 */
                    -> driver_match_device        /* 檢查是否匹配 */
                        -> 調用bus下的match匹配函數
                    -> driver_probe_device        /* 匹配成功後執行此函數 */
                        -> really_probe 
                            -> drv->probe         /* 執行drv下的probe函數 */

        根據 driver_register 函數流程,我們就知道瞭總線的 match 匹配函數會在這裡遍歷使用,這就回答瞭我們之前留下的一個問題:總線 match 函數在哪裡用,一旦匹配成功就會進入到驅動的 probe 函數。 

        根據 platform_driver_register 函數流程,我們可以得出一個結論:向 Linux 內核註冊 platform driver 過程裡面會有一個遍歷驅動和設備匹配的過程,匹配成功後最終會執行 platform driver 的 probe 函數,過程中 的驅動基類 driver 的 probe 函數和 platform_drv_probe 函數都是達到這個目的的中轉函數而已。

        值得註意的是,最終會執行的 platform driver 的 probe 函數是由我們來寫的,所以主動權又回到我們手裡。

2.3、platform 設備

2.3.1、platform 設備定義

        如果我們用的 Linux 版本支持設備樹,那就在設備樹中去描述設備,如果不支持設備樹,就要定義好 platform 設備。這裡我們需要考慮的一個點是,總線下的匹配函數 match 在做匹配時是先設備樹匹配,然後 id_table 表匹配,然後才是 name 字段匹配。支持設備樹時,直接在設備樹節點裡面改設備信息,內核啟動時會自動遍歷設備樹節點,匹配成功就會自動生成一個 platform_device,給下一步來使用。不是設備樹的話,這個 platform_device 就是由開發者來寫。

        這裡我們先不用設備樹,自己來定義 platform 設備。platform 設備用 platform_device 結構體來表示,該結構體定義如下:

/* include/linux/platform_device.h */
 
struct platform_device {
    const char *name;                   /* 設備名,得和對應的 platform 驅動的 name 一樣,
                                           否則設備就無法匹配到對應驅動 */
    int id; 
    bool id_auto;
    struct device dev;
    u32 num_resources; 
    struct resource *resource;
    const struct platform_device_id *id_entry;
    char *driver_override; /* Driver name to force a match */
    /* MFD cell pointer */
    struct mfd_cell *mfd_cell;
    /* arch specific additions */
    struct pdev_archdata archdata;
};

2.4、platform 匹配過程

        platform 總線對驅動和設備的匹配過程其實上面零零碎碎也已經講的差不多瞭,現在我們匯總起來在過一遍。

        前面也說過,總線下的驅動和設備的匹配是通過總線下的 match 函數來實現的,不同的總線對應的 match 函數肯定不一樣,這個我們不用管,內核都會寫好。我們所用的 platform 總線對應的 match 函數是 platform_match 函數,分析一下這個函數:

platform_match
    -> of_driver_match_device          /* 設備樹匹配 */
    -> acpi_driver_match_device        /* ACPI 匹配 */
    -> platform_match_id               /* platform_driver->id_table 匹配 */
    -> strcmp(pdev->name, drv->name)   /* name 匹配 */

         通過對上面匹配函數的一個簡單分析,我們知道匹配函數做匹配的順序是先匹配設備樹,然後匹配 id_table 表,然後才是暴力匹配 name 字段。對於支持設備樹的 Linux 版本,我們一上來做設備樹匹配就完事。不支持設備樹時,我們就得定義 platform 設備,再用 id_tabale 表或 name 匹配,一般情況下都是選用 name 匹配。

        現在我們來具體看一下設備樹條件下的匹配過程:

of_driver_match_device     /* of函數一般是用於設備樹,這也算給瞭我們提示 */
    -> of_match_device (drv->of_match_table, dev)    
        -> of_match_node  
            -> __of_match_node
                -> __of_device_is_compatible
                    -> __of_find_property(device, "compatible", NULL)   /* 取出compatible屬性值 */

        看上面的分析我們就知道瞭這個匹配過程最終是驅動基類的 of_match_table 裡的 compatible 去設備樹節點裡面的 compatible 屬性作比較。這個就是把設備樹與 platform 總線串起來的一個機理,從而實現瞭在設備樹對應節點裡面寫設備信息,驅動另外單獨寫的目的,也就是我們前面講的驅動分離。

 3、總結

        在具體的開發過程中我們並不需要真的去寫一個 platform 總線模型,內核中都已經給我們定義好瞭。我們對 platform 總線模型的分析主要是搞清楚如何將驅動和設備匹配的,即當我們插入設備是如何找到對應驅動或插入驅動如何找到對應設備的,並最終調用 probe 函數。其實不管是先有驅動後有設備、還是先有設備後有驅動,最終匹配成功後第一件事都是執行驅動的 probe 函數,所以我們盡可放心的忽略中間曲折的情感糾葛,直接把註意力放在最終的 probe 函數。

到此這篇關於Linux驅動之platform總線詳解的文章就介紹到這瞭,更多相關Linux驅動platform總線內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: