【轉載】Linux驅動之platform匯流排詳解
原文地址:https://www.jb51.net/article/231922.htm
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 都是用這個結構體來定義的。該結構體如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
/* 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 核心賦值好了,其結構體成員對應的函式也已經在核心裡面寫好。
定義如下:
1 2 3 4 5 6 7 8 9 |
/* 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 就是我們前面所說的做驅動和裝置匹配的函式,該函式定義如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
/* 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 來表示,該結構體內容為:
1 2 3 4 5 6 7 8 9 10 11 12 |
/* 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 是一個結構體,內容如下:
1 2 3 4 |
struct platform_device_id {
char name[PLATFORM_NAME_SIZE];
kernel_ulong_t driver_data;
};
|
platform_driver 中 driver 是一個驅動基類,相當於驅動具有的最基礎的屬性,在不同匯流排下具有的屬性則存放在 platform_driver 結構體下。
驅動基類結構體 device_driver 內容為:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
/* 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 型別,該型別結構體如下:
1 2 3 4 5 6 7 8 9 |
/* 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 驅動,函式大致流程如下:
1 2 3 4 5 6 7 8 9 10 11 |
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 這一步我們用省略號代替了,現在來做一下分析:
1 2 3 4 5 6 7 8 9 10 |
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 結構體來表示,該結構體定義如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
/* 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 函式,分析一下這個函式:
1 2 3 4 5 |
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 匹配。
現在我們來具體看一下裝置樹條件下的匹配過程:
1 2 3 4 5 6 |
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匯流排內容請搜尋指令碼之家以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援指令碼之家!