Linux驅(qū)動(dòng)之platform總線詳解
1、platform 總線簡(jiǎn)介
1.1、Linux 驅(qū)動(dòng)的分離和分層思想
1.1.1、Linux 驅(qū)動(dòng)的分離
? ? ? ? 先講 Linux 驅(qū)動(dòng)的分離,Linux 操作系統(tǒng)支持在各類 CPU 上運(yùn)行,因?yàn)槊恳环N CPU 對(duì)設(shè)備的驅(qū)動(dòng)不一樣,這樣就造成了 Linux 內(nèi)核中積累了大量代碼,并且這些代碼關(guān)于同一設(shè)備的描述大致相同,這就使得內(nèi)核代碼很冗余。以 CPU 通過(guò) I2C 控制 MPU6050 為例:

? ? ? ? 從圖可以看出每一種平臺(tái)下都有一套主機(jī)驅(qū)動(dòng)和一套設(shè)備驅(qū)動(dòng),因?yàn)槊總€(gè)平臺(tái)的 I2C 控制器不同,所以這個(gè)主機(jī)驅(qū)動(dòng)得每個(gè)平臺(tái)配一個(gè)自己的,但大家所用的 MPU6050 是一樣的,所以完全可以就共用一套設(shè)備驅(qū)動(dòng)代碼。完善后框架如下:

????????當(dāng)然,這只是對(duì)于 I2C 下的 MPU6050 這個(gè)設(shè)備,實(shí)際情況下,I2C 下肯定會(huì)掛載很多設(shè)備,根據(jù)這個(gè)思路,我們可以得到框架為:

? ? ? ? ?而在實(shí)際開(kāi)發(fā)中,I2C 主機(jī)驅(qū)動(dòng)半導(dǎo)體廠家會(huì)編寫好,設(shè)備驅(qū)動(dòng)也由設(shè)備廠家編寫好,我們只需要提供設(shè)備信息即可,如設(shè)備接到那個(gè) I2C 接口上,I2C 速度為多少。這樣就相當(dāng)于把設(shè)備信息從設(shè)備驅(qū)動(dòng)中剝離出來(lái),而設(shè)備驅(qū)動(dòng)也會(huì)用標(biāo)準(zhǔn)方法去獲取設(shè)備信息(如從設(shè)備樹(shù)中獲取設(shè)備信息)。這樣就相當(dāng)于驅(qū)動(dòng)只負(fù)責(zé)驅(qū)動(dòng),設(shè)備(信息)只負(fù)責(zé)設(shè)備,想辦法將兩者進(jìn)行匹配即可,來(lái)做這個(gè)匹配工作的就是總線,這就構(gòu)成了 Linux 中的 總線-驅(qū)動(dòng)-設(shè)備 模型。結(jié)構(gòu)圖如下:

1.2、platform 平臺(tái)驅(qū)動(dòng)模型
? ? ? ? 上面我們講做設(shè)備驅(qū)動(dòng)的分離,得到 總線-驅(qū)動(dòng)-設(shè)備 模型,這個(gè)總線就是我平常所說(shuō)的 I2C、SPI、USB 等總線。但問(wèn)題是有些設(shè)備是不需要通過(guò)某一跟總線的,這是就引入了 platform 總線。
????????這里需要注意的是,platform 總線是區(qū)別于 USB、SPI、I2C 這些總線的虛擬總線。說(shuō)它虛擬是因?yàn)?SoC 與一些外設(shè)如 LED、定時(shí)器、蜂鳴器是通過(guò)內(nèi)存的尋址空間來(lái)進(jìn)行尋址的,所以 CPU 與這些設(shè)備通信壓根就不需要總線,那么硬件上也就沒(méi)有這樣一個(gè)總線。但內(nèi)核有對(duì)這些設(shè)備做統(tǒng)一管理的需求,所以就對(duì)這些直接通過(guò)內(nèi)存尋址的設(shè)備虛擬了一條 platform 總線,所有直接通過(guò)內(nèi)存尋址的設(shè)備都映射到這條虛擬總線上。
? ? ? ? platform 總線的優(yōu)點(diǎn):
? ? ? ? 1、通過(guò) platform 總線,可以遍歷所有掛載在?platform 總線上的設(shè)備;
? ? ? ? 2、實(shí)現(xiàn)設(shè)備和驅(qū)動(dòng)的分離,通過(guò) platform 總線,設(shè)備和驅(qū)動(dòng)是分開(kāi)注冊(cè)的,因?yàn)橛?probe 函數(shù),可以隨時(shí)檢測(cè)與設(shè)備匹配的驅(qū)動(dòng),匹配成功就會(huì)把這個(gè)驅(qū)動(dòng)向內(nèi)核注冊(cè);
? ? ? ? 3、一個(gè)驅(qū)動(dòng)可供同類的幾個(gè)設(shè)備使用,這個(gè)功能的實(shí)現(xiàn)是因?yàn)轵?qū)動(dòng)注冊(cè)過(guò)程中有一個(gè)遍歷設(shè)備的操作。? ? ??
2、platform 框架
2.1、platform 總線
? ? ? ? Linux 內(nèi)核用 bus_type 結(jié)構(gòu)體來(lái)表示總線,我們所用的 I2C、SPI、USB 都是用這個(gè)結(jié)構(gòu)體來(lái)定義的。該結(jié)構(gòu)體如下:
/* 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; /* 設(shè)備屬性 */
const struct attribute_group **drv_groups; /* 驅(qū)動(dòng)屬性 */
int (*match)(struct device *dev, struct device_driver *drv); /* 設(shè)備驅(qū)動(dòng)匹配函數(shù) */
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 類型的常量,之所以說(shuō)它是常量是因?yàn)檫@個(gè)變量已經(jīng)被 Linux 內(nèi)核賦值好了,其結(jié)構(gòu)體成員對(duì)應(yīng)的函數(shù)也已經(jīng)在內(nèi)核里面寫好。
定義如下:
/* drivers/base/platform.c */
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match, /* 匹配函數(shù) */
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
? ? ? ? platform_bus_type 中的 platform_match 就是我們前面所說(shuō)的做驅(qū)動(dòng)和設(shè)備匹配的函數(shù),該函數(shù)定義如下:
/* 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);
/* 設(shè)備樹(shù)OF類型匹配
驅(qū)動(dòng)基類的 of_match_table 里的 compatible 匹配表與設(shè)備樹(shù)
每一個(gè)設(shè)備節(jié)點(diǎn)的 compatible 屬性作比較,有相同就表示匹配成功 */
if (of_driver_match_device(dev, drv))
return 1;
/* ACPI 匹配 */
if (acpi_driver_match_device(dev, drv))
return 1;
/* id_table 匹配
platform 驅(qū)動(dòng)里的 id_table 數(shù)組會(huì)保存很多 id 信息 */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* name 匹配
直接粗暴比較platform 的驅(qū)動(dòng)和設(shè)備里面的 name 信息 */
return (strcmp(pdev->name, drv->name) == 0);
}
? ? ? ? 這個(gè)匹配函數(shù)什么時(shí)候用,在哪里用,我們不妨先留一個(gè)懸念。
2.2、platform 驅(qū)動(dòng)
2.2.1、platform 驅(qū)動(dòng)定義
? ? ? ? platform 驅(qū)動(dòng)用結(jié)構(gòu)體 platform_driver 來(lái)表示,該結(jié)構(gòu)體內(nèi)容為:
/* include/linux/platform_device.h */
struct platform_driver {
int (*probe)(struct platform_device *); /* platform驅(qū)動(dòng)和platform設(shè)備匹配后會(huì)執(zhí)行這個(gè)probe函數(shù) */
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; /* 驅(qū)動(dòng)基類 */
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 總線匹配驅(qū)動(dòng)和設(shè)備時(shí)?id_table 表匹配法時(shí)使用的,這個(gè)?id_table 表其實(shí)是一個(gè)數(shù)組,里面的每個(gè)元素類型都為?platform_device_id,platform_device_id 是一個(gè)結(jié)構(gòu)體,內(nèi)容如下:
struct platform_device_id {
char name[PLATFORM_NAME_SIZE];
kernel_ulong_t driver_data;
};
? ? ? ? platform_driver 中?driver 是一個(gè)驅(qū)動(dòng)基類,相當(dāng)于驅(qū)動(dòng)具有的最基礎(chǔ)的屬性,在不同總線下具有的屬性則存放在?platform_driver 結(jié)構(gòu)體下。
? ? ? ? 驅(qū)動(dòng)基類結(jié)構(gòu)體?device_driver 內(nèi)容為:
/* include/linux/device.h */
struct device_driver {
const char *name; /* platform 總線來(lái)匹配設(shè)備與驅(qū)動(dòng)的第四種方
法就是直接粗暴匹配兩者的 name 字段 */
struct bus_type *bus;
struct module *owner;
const char *mod_name;
bool suppress_bind_attrs;
const struct of_device_id *of_match_table; /* 采用設(shè)備樹(shù)時(shí)驅(qū)動(dòng)使用的的匹配表 */
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 也是一個(gè)匹配表,這個(gè)匹配表是 platform 總線給驅(qū)動(dòng)和設(shè)備做匹配時(shí)使用設(shè)備樹(shù)匹配時(shí)用的,也是一個(gè)數(shù)組,數(shù)組元素都為?of_device_id 類型,該類型結(jié)構(gòu)體如下:
/* include/linux/mod_devicetable.h */
struct of_device_id {
char name[32];
char type[32];
char compatible[128]; /* 使用設(shè)備樹(shù)匹配時(shí)就是把設(shè)備節(jié)點(diǎn)的 compatible 屬性值和 of_match_table 中
每個(gè)項(xiàng)目的這個(gè) compatible 作比較,如果有相等的就表示設(shè)備和驅(qū)動(dòng)匹配成功 */
const void *data;
};
2.2.2、platform 驅(qū)動(dòng)注冊(cè)
? ? ? ? 用?platform_driver 結(jié)構(gòu)體定義好 platform 驅(qū)動(dòng)后,用 platform_driver_register 函數(shù)向 Linux 內(nèi)核注冊(cè) platform 驅(qū)動(dòng),函數(shù)大致流程如下:
platform_driver_register (drv)
-> __platform_driver_register
-> drv->driver.probe = platform_drv_probe; /* 把 platform_drv_probe 這個(gè)函數(shù)賦給
platform 驅(qū)動(dòng)里的驅(qū)動(dòng)基類 drier 的 probe 函數(shù) */
-> driver_registe (&drv->driver) /* 向 Linux 內(nèi)核注冊(cè)驅(qū)動(dòng)基類 driver */
-> ......
-> drv->driver->probe /* 最終執(zhí)行驅(qū)動(dòng)基類 driver 的 probe 函數(shù),
其實(shí)就是上面給的 platform_drv_probe 函數(shù) */
-> platform_drv_probe
-> drv->probe /* platform_drv_probe 函數(shù)又會(huì)執(zhí)行
platform 驅(qū)動(dòng) drv 的 probe 函數(shù) */
? ? ? ? 上面的分析中從 driver_register (&drv->driver) 到 drv->driver->probe 這一步我們用省略號(hào)代替了,現(xiàn)在來(lái)做一下分析:
driver_register(&drv->driver)
-> bus_add_driver /* 向總線添加驅(qū)動(dòng) */
-> driver_attach
-> bus_for_each_dev /* 查找總線下每一個(gè)設(shè)備,即遍歷操作 */
-> __driver_attach /* 每個(gè)設(shè)備都調(diào)用此函數(shù) */
-> driver_match_device /* 檢查是否匹配 */
-> 調(diào)用bus下的match匹配函數(shù)
-> driver_probe_device /* 匹配成功后執(zhí)行此函數(shù) */
-> really_probe
-> drv->probe /* 執(zhí)行drv下的probe函數(shù) */
? ? ? ? 根據(jù)?driver_register 函數(shù)流程,我們就知道了總線的 match 匹配函數(shù)會(huì)在這里遍歷使用,這就回答了我們之前留下的一個(gè)問(wèn)題:總線 match 函數(shù)在哪里用,一旦匹配成功就會(huì)進(jìn)入到驅(qū)動(dòng)的 probe 函數(shù)。?
????????根據(jù) platform_driver_register 函數(shù)流程,我們可以得出一個(gè)結(jié)論:向 Linux 內(nèi)核注冊(cè) platform driver 過(guò)程里面會(huì)有一個(gè)遍歷驅(qū)動(dòng)和設(shè)備匹配的過(guò)程,匹配成功后最終會(huì)執(zhí)行 platform driver 的 probe 函數(shù),過(guò)程中 的驅(qū)動(dòng)基類 driver 的 probe 函數(shù)和 platform_drv_probe 函數(shù)都是達(dá)到這個(gè)目的的中轉(zhuǎn)函數(shù)而已。
? ? ? ? 值得注意的是,最終會(huì)執(zhí)行的 platform driver 的 probe 函數(shù)是由我們來(lái)寫的,所以主動(dòng)權(quán)又回到我們手里。
2.3、platform 設(shè)備
2.3.1、platform 設(shè)備定義
? ? ? ? 如果我們用的 Linux 版本支持設(shè)備樹(shù),那就在設(shè)備樹(shù)中去描述設(shè)備,如果不支持設(shè)備樹(shù),就要定義好 platform 設(shè)備。這里我們需要考慮的一個(gè)點(diǎn)是,總線下的匹配函數(shù) match 在做匹配時(shí)是先設(shè)備樹(shù)匹配,然后 id_table 表匹配,然后才是 name 字段匹配。支持設(shè)備樹(shù)時(shí),直接在設(shè)備樹(shù)節(jié)點(diǎn)里面改設(shè)備信息,內(nèi)核啟動(dòng)時(shí)會(huì)自動(dòng)遍歷設(shè)備樹(shù)節(jié)點(diǎn),匹配成功就會(huì)自動(dòng)生成一個(gè) platform_device,給下一步來(lái)使用。不是設(shè)備樹(shù)的話,這個(gè) platform_device 就是由開(kāi)發(fā)者來(lái)寫。
????????這里我們先不用設(shè)備樹(shù),自己來(lái)定義 platform 設(shè)備。platform 設(shè)備用 platform_device 結(jié)構(gòu)體來(lái)表示,該結(jié)構(gòu)體定義如下:
/* include/linux/platform_device.h */
struct platform_device {
const char *name; /* 設(shè)備名,得和對(duì)應(yīng)的 platform 驅(qū)動(dòng)的 name 一樣,
否則設(shè)備就無(wú)法匹配到對(duì)應(yīng)驅(qū)動(dòng) */
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 匹配過(guò)程
? ? ? ? platform 總線對(duì)驅(qū)動(dòng)和設(shè)備的匹配過(guò)程其實(shí)上面零零碎碎也已經(jīng)講的差不多了,現(xiàn)在我們匯總起來(lái)在過(guò)一遍。
? ? ? ? 前面也說(shuō)過(guò),總線下的驅(qū)動(dòng)和設(shè)備的匹配是通過(guò)總線下的 match 函數(shù)來(lái)實(shí)現(xiàn)的,不同的總線對(duì)應(yīng)的 match 函數(shù)肯定不一樣,這個(gè)我們不用管,內(nèi)核都會(huì)寫好。我們所用的 platform 總線對(duì)應(yīng)的 match 函數(shù)是 platform_match 函數(shù),分析一下這個(gè)函數(shù):
platform_match
-> of_driver_match_device /* 設(shè)備樹(shù)匹配 */
-> acpi_driver_match_device /* ACPI 匹配 */
-> platform_match_id /* platform_driver->id_table 匹配 */
-> strcmp(pdev->name, drv->name) /* name 匹配 */
? ? ? ? ?通過(guò)對(duì)上面匹配函數(shù)的一個(gè)簡(jiǎn)單分析,我們知道匹配函數(shù)做匹配的順序是先匹配設(shè)備樹(shù),然后匹配 id_table 表,然后才是暴力匹配 name 字段。對(duì)于支持設(shè)備樹(shù)的 Linux 版本,我們一上來(lái)做設(shè)備樹(shù)匹配就完事。不支持設(shè)備樹(shù)時(shí),我們就得定義 platform 設(shè)備,再用 id_tabale 表或 name 匹配,一般情況下都是選用 name 匹配。
? ? ? ? 現(xiàn)在我們來(lái)具體看一下設(shè)備樹(shù)條件下的匹配過(guò)程:
of_driver_match_device /* of函數(shù)一般是用于設(shè)備樹(shù),這也算給了我們提示 */
-> 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屬性值 */
? ? ? ? 看上面的分析我們就知道了這個(gè)匹配過(guò)程最終是驅(qū)動(dòng)基類的 of_match_table 里的 compatible 去設(shè)備樹(shù)節(jié)點(diǎn)里面的?compatible 屬性作比較。這個(gè)就是把設(shè)備樹(shù)與 platform 總線串起來(lái)的一個(gè)機(jī)理,從而實(shí)現(xiàn)了在設(shè)備樹(shù)對(duì)應(yīng)節(jié)點(diǎn)里面寫設(shè)備信息,驅(qū)動(dòng)另外單獨(dú)寫的目的,也就是我們前面講的驅(qū)動(dòng)分離。
?3、總結(jié)
? ? ? ? 在具體的開(kāi)發(fā)過(guò)程中我們并不需要真的去寫一個(gè) platform 總線模型,內(nèi)核中都已經(jīng)給我們定義好了。我們對(duì) platform 總線模型的分析主要是搞清楚如何將驅(qū)動(dòng)和設(shè)備匹配的,即當(dāng)我們插入設(shè)備是如何找到對(duì)應(yīng)驅(qū)動(dòng)或插入驅(qū)動(dòng)如何找到對(duì)應(yīng)設(shè)備的,并最終調(diào)用 probe 函數(shù)。其實(shí)不管是先有驅(qū)動(dòng)后有設(shè)備、還是先有設(shè)備后有驅(qū)動(dòng),最終匹配成功后第一件事都是執(zhí)行驅(qū)動(dòng)的 probe 函數(shù),所以我們盡可放心的忽略中間曲折的情感糾葛,直接把注意力放在最終的 probe 函數(shù)。
到此這篇關(guān)于Linux驅(qū)動(dòng)之platform總線詳解的文章就介紹到這了,更多相關(guān)Linux驅(qū)動(dòng)platform總線內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
linux下php-fpm開(kāi)啟關(guān)閉使用方法
自php5.3.3開(kāi)始,php源碼中包含了php-fpm,不需要單獨(dú)通過(guò)補(bǔ)丁的方式安裝php-fpm,在源碼安裝的時(shí)候直接 configure 中增加參數(shù) –enable-fpm即可,使用方法如下2014-03-03
解決CentOS7虛擬機(jī)無(wú)法上網(wǎng)并設(shè)置CentOS7虛擬機(jī)使用靜態(tài)IP上網(wǎng)
這篇文章主要介紹了解決CentOS7虛擬機(jī)無(wú)法上網(wǎng)并設(shè)置CentOS7虛擬機(jī)使用靜態(tài)IP上網(wǎng),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02
在Linux服務(wù)器上部署War項(xiàng)目教程
文章講述了如何將War包上傳到Linux服務(wù)器上的步驟,包括使用FTP或SFTP上傳,確認(rèn)并安裝Java運(yùn)行環(huán)境和Web服務(wù)器(如ApacheTomcat或Nginx),將War包復(fù)制到相應(yīng)的目錄,并重啟服務(wù)以確保部署成功,最后,通過(guò)瀏覽器訪問(wèn)部署的應(yīng)用2025-02-02
apache2.2 支持.net 3.5的設(shè)置方法
一直在為asp.net程序的打包發(fā)布頭疼,甚至想過(guò)把程序裝好放到vware里!但為什么一直沒(méi)有想到apache這位大哥呢?!以至今天才google apache+asp.net。2009-10-10
Apache httpd 安裝module mod_expires、mod_deflate的方法
Apache httpd 安裝module mod_expires、mod_deflate的方法,需要的朋友可以參考下。2011-11-11

