學(xué)Linux驅(qū)動(dòng):應(yīng)先了解總線驅(qū)動(dòng)模型
個(gè)人對(duì)于驅(qū)動(dòng)模型的理解概括起來(lái)就是一句話:利用面向?qū)ο缶幊趟枷?,?shí)現(xiàn)設(shè)備分層管理軟件體系結(jié)構(gòu)。
注:代碼分析基于linux-5.4.31
為啥要驅(qū)動(dòng)模型
隨著系統(tǒng)結(jié)構(gòu)演化越來(lái)越復(fù)雜,Linux內(nèi)核對(duì)設(shè)備描述衍生出一般性的抽象描述,形成一個(gè)分層體系結(jié)構(gòu),從而引入了設(shè)備驅(qū)動(dòng)模型。這樣描述還是不夠讓人理解,來(lái)看一下這些需求就好理解些:
-
Linux內(nèi)核可以在各種體系結(jié)構(gòu)和硬件平臺(tái)上運(yùn)行,因此需要最大限度地提高代碼在平臺(tái)之間的可重用性。 -
分層實(shí)現(xiàn)也實(shí)現(xiàn)了軟件工程的高內(nèi)聚-低耦合的設(shè)計(jì)思想。低耦合體現(xiàn)在對(duì)外提供統(tǒng)一的抽象訪問(wèn)接口,高內(nèi)聚將相關(guān)度緊密的集中抽象實(shí)現(xiàn)。 -
Linux內(nèi)核驅(qū)動(dòng)程序模型是先前在內(nèi)核中使用的所有不同驅(qū)動(dòng)程序模型的統(tǒng)一。它旨在通過(guò)將一組數(shù)據(jù)和操作整合到全局可訪問(wèn)的數(shù)據(jù)結(jié)構(gòu)中,來(lái)擴(kuò)展基于基礎(chǔ)總線來(lái)橋接設(shè)備驅(qū)動(dòng)程序。
傳統(tǒng)的驅(qū)動(dòng)模型為它們所控制的設(shè)備實(shí)現(xiàn)了某種類似于樹的結(jié)構(gòu)(有時(shí)只是一個(gè)列表)。不同類型的總線之間沒有任何一致性。
驅(qū)動(dòng)模型抽象了啥
當(dāng)前驅(qū)動(dòng)程序模型為描述總線和總線下可能出現(xiàn)的設(shè)備提供了一個(gè)通用的、統(tǒng)一的模型。統(tǒng)一總線模型包括一組所有總線都具有的公共屬性和一組公共回調(diào),如總線探測(cè)期間的設(shè)備發(fā)現(xiàn)、總線關(guān)閉、總線電源管理等。
通用的設(shè)備和橋接接口反映了現(xiàn)代計(jì)算機(jī)的目標(biāo):即執(zhí)行無(wú)縫設(shè)備“即插即用”,電源管理和熱插拔的能力。特別是,英特爾和微軟規(guī)定的模型(即ACPI)可確保與x86兼容的系統(tǒng)上幾乎任何總線上的幾乎所有設(shè)備都可以在此范式下工作。當(dāng)然,雖然大多數(shù)總線都支持其中大多數(shù)操作,但并不是每條總線都能夠支持所有此類操作。
那么哪些通用需求被抽象出來(lái)了呢?
-
電源系統(tǒng)和系統(tǒng)關(guān)機(jī),對(duì)于電源管理與系統(tǒng)關(guān)機(jī)對(duì)于設(shè)備相關(guān)的操作進(jìn)行抽象實(shí)現(xiàn)。關(guān)機(jī)為什么要被抽象出來(lái)管理,比如設(shè)備操作正在進(jìn)行此時(shí)系統(tǒng)收到關(guān)機(jī)指令,那么在設(shè)備模型層就會(huì)遍歷系統(tǒng)設(shè)備硬件,確保系統(tǒng)正確關(guān)機(jī)。
-
用戶空間訪問(wèn):sysfs虛擬文件系統(tǒng)實(shí)現(xiàn)與設(shè)備模型對(duì)外的訪問(wèn)抽象,這也是為什么說(shuō)Linux 設(shè)備也是文件的由來(lái)。實(shí)際從軟件架構(gòu)層面看,這其實(shí)是一個(gè)軟件橋接模塊,抽象出統(tǒng)一用戶訪問(wèn)接口,橋接了設(shè)備驅(qū)動(dòng)。
-
熱插拔管理:熱插拔管理機(jī)制定義統(tǒng)一的抽象接口操作符kset_hotplug_ops,不同設(shè)備利用操作符實(shí)現(xiàn)差異化。
-
設(shè)備類型:設(shè)備分類機(jī)制,從高層級(jí)抽象描述設(shè)備類型,具體可以在sysfs下面體現(xiàn)。
用戶空間訪問(wèn)
由于具有系統(tǒng)中所有設(shè)備的完整分層視圖,因此將完整的分層視圖導(dǎo)出到用戶空間變得相對(duì)容易。這是通過(guò)實(shí)現(xiàn)名為sysfs虛擬文件系統(tǒng)來(lái)完成的。
sysfs的自動(dòng)掛載通常是通過(guò)/etc/fstab文件中的以下條目來(lái)完成的:
none /sys sysfs defaults 0 0
對(duì)于Debian系統(tǒng)而言,可能在/lib/init/fstab采用下面的形式掛載:
none /sys sysfs nodev,noexec,nosuid 0 0
當(dāng)然也可以采用手動(dòng)方式掛載:
# mount -t sysfs sysfs /sys
當(dāng)將設(shè)備插入樹中時(shí),都會(huì)為其創(chuàng)建一個(gè)目錄。該目錄可以填充在發(fā)現(xiàn)的每個(gè)層(全局層,總線層或設(shè)備層)中。
全局層當(dāng)前創(chuàng)建兩個(gè)文件-'name'和'power'。前者報(bào)告設(shè)備名稱。后者報(bào)告設(shè)備的當(dāng)前電源狀態(tài)。它還將用于設(shè)置當(dāng)前電源狀態(tài)。
總線層為探測(cè)總線時(shí)發(fā)現(xiàn)的設(shè)備創(chuàng)建文件。例如,PCI層當(dāng)前為每個(gè)PCI設(shè)備創(chuàng)建“ irq”和“resource”文件。
特定于設(shè)備的驅(qū)動(dòng)程序也可以在其目錄中導(dǎo)出文件,以暴露特定于設(shè)備的數(shù)據(jù)或可用接口。
驅(qū)動(dòng)模型實(shí)現(xiàn)
先來(lái)梳理一下內(nèi)部幾個(gè)主要與驅(qū)動(dòng)模型相關(guān)的數(shù)據(jù)結(jié)構(gòu):
./include/linux/Device.h 定義設(shè)備驅(qū)動(dòng)主要數(shù)據(jù)結(jié)構(gòu)
-
bus_type:抽象描述總線類型,如USB/PCI/I2C/MMC等 -
device_driver:實(shí)現(xiàn)具體連接在總線上的設(shè)備驅(qū)動(dòng)。 -
device:描述連接在總線上的設(shè)備
./include/linux/Kobject.h中定義了隱藏在后臺(tái)的類似于基類的數(shù)據(jù)結(jié)構(gòu):
-
kset:可以認(rèn)為是kobject的頂層容器類。每個(gè)kset內(nèi)部都包含了自己的kobject. -
kobject:在 sysfs 中出現(xiàn)的每個(gè)對(duì)象都對(duì)應(yīng)一個(gè) kobject, 它和內(nèi)核交互來(lái)創(chuàng)建它的可見表述,每一個(gè) kobject 對(duì)應(yīng) 文件系統(tǒng) /sys 里的一個(gè) 目錄,目錄的名字就是結(jié)構(gòu)體中的 name
bus_type
bus_type用以驅(qū)動(dòng)總線,具體的驅(qū)動(dòng)USB/I2C/PCI/MMC等:
-
注冊(cè)總線,利用bus_register注冊(cè)總線,bus_unregister刪除總線。如下例子,每種總線須定義一個(gè)bus_type對(duì)象,并利用bus_register注冊(cè)總線,或bus_unregister刪除總線。
/*i2c-core-base.c*/
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
};
EXPORT_SYMBOL_GPL(i2c_bus_type);
static int __init i2c_init(void)
{
int retval;
retval = of_alias_get_highest_id("i2c");
down_write(&__i2c_board_lock);
if (retval >= __i2c_first_dynamic_bus_num)
__i2c_first_dynamic_bus_num = retval + 1;
up_write(&__i2c_board_lock);
/*注冊(cè)I2C總線*/
retval = bus_register(&i2c_bus_type);
if (retval)
return retval;
is_registered = true;
#ifdef CONFIG_I2C_COMPAT
i2c_adapter_compat_class = class_compat_register("i2c-adapter");
if (!i2c_adapter_compat_class) {
retval = -ENOMEM;
goto bus_err;
}
#endif
retval = i2c_add_driver(&dummy_driver);
if (retval)
goto class_err;
if (IS_ENABLED(CONFIG_OF_DYNAMIC))
WARN_ON(of_reconfig_notifier_register(&i2c_of_notifier));
if (IS_ENABLED(CONFIG_ACPI))
WARN_ON(acpi_reconfig_notifier_register(&i2c_acpi_notifier));
return 0;
class_err:
#ifdef CONFIG_I2C_COMPAT
class_compat_unregister(i2c_adapter_compat_class);
bus_err:
#endif
is_registered = false;
/*錯(cuò)誤時(shí)刪除總線*/
bus_unregister(&i2c_bus_type);
return retval;
}
-
注冊(cè)適配器驅(qū)動(dòng)程序(USB控制器,I2C適配器等),以檢測(cè)連接的設(shè)備,并提供與設(shè)備的通信機(jī)制 -
圖中的match函數(shù)接口用于將驅(qū)動(dòng)程序與設(shè)備進(jìn)行匹配。match回調(diào)的目的是使總線有機(jī)會(huì)通過(guò)比較驅(qū)動(dòng)程序支持的設(shè)備ID與特定設(shè)備的設(shè)備ID來(lái)確定特定驅(qū)動(dòng)程序是否支持特定設(shè)備,而不會(huì)犧牲特定于總線的功能或類型安全性 。當(dāng)向總線注冊(cè)驅(qū)動(dòng)程序時(shí),將遍歷總線的設(shè)備列表,并為每個(gè)沒有與之關(guān)聯(lián)的驅(qū)動(dòng)程序的設(shè)備調(diào)用match回調(diào)。 -
提供API函數(shù)以實(shí)現(xiàn)適配器驅(qū)動(dòng)以及設(shè)備驅(qū)動(dòng)。 -
同時(shí)dev_pm_ops *pm實(shí)現(xiàn)對(duì)于總線的功耗管理接口抽象。對(duì)于特定總線實(shí)現(xiàn)這個(gè)操作符對(duì)應(yīng)的函數(shù)。
struct dev_pm_ops {
int (*prepare)(struct device *dev);
void (*complete)(struct device *dev);
int (*suspend)(struct device *dev);
int (*resume)(struct device *dev);
int (*freeze)(struct device *dev);
int (*thaw)(struct device *dev);
int (*poweroff)(struct device *dev);
int (*restore)(struct device *dev);
int (*suspend_late)(struct device *dev);
int (*resume_early)(struct device *dev);
int (*freeze_late)(struct device *dev);
int (*thaw_early)(struct device *dev);
int (*poweroff_late)(struct device *dev);
int (*restore_early)(struct device *dev);
int (*suspend_noirq)(struct device *dev);
int (*resume_noirq)(struct device *dev);
int (*freeze_noirq)(struct device *dev);
int (*thaw_noirq)(struct device *dev);
int (*poweroff_noirq)(struct device *dev);
int (*restore_noirq)(struct device *dev);
int (*runtime_suspend)(struct device *dev);
int (*runtime_resume)(struct device *dev);
int (*runtime_idle)(struct device *dev);
};
-
iommu_ops 操作符提供總線相關(guān)的IOMMU抽象。 -
設(shè)備驅(qū)動(dòng)注冊(cè)到總線上時(shí),將在sysfs管理總線/設(shè)備/設(shè)備驅(qū)動(dòng)的層次關(guān)系,以PCI為例:
/*在總線上注冊(cè)的驅(qū)動(dòng)程序會(huì)在總線的驅(qū)動(dòng)程序目錄中獲得一個(gè)目錄*/
/sys/bus/pci/
|-- devices
`-- drivers
|-- Intel ICH
|-- Intel ICH Joystick
|-- agpgart
`-- e100
/*在該類型的總線上發(fā)現(xiàn)的每個(gè)設(shè)備都會(huì)在總線的設(shè)備目錄中獲得到物理層次結(jié)構(gòu)中該設(shè)備目錄的符號(hào)鏈接*/
/sys/bus/pci/
|-- devices
| |-- 00:00.0 -> ../../../root/pci0/00:00.0
| |-- 00:01.0 -> ../../../root/pci0/00:01.0
| `-- 00:02.0 -> ../../../root/pci0/00:02.0
`-- drivers
-
總線屬性:bus_groups/設(shè)備屬性dev_groups/驅(qū)動(dòng)屬性drv_groups。
device
-
作用:抽象描述具體的設(shè)備
-
設(shè)備注冊(cè):發(fā)現(xiàn)設(shè)備的總線驅(qū)動(dòng)程序使用下面的函數(shù)來(lái)向內(nèi)核注冊(cè)設(shè)備
int device_register(struct device * dev);
-
利用device_unregister()從總線上刪除設(shè)備
device_driver
-
作用:抽象描述連接在總線上的具體設(shè)備的驅(qū)動(dòng) -
驅(qū)動(dòng)注冊(cè),通過(guò)下面的函數(shù)將設(shè)備驅(qū)動(dòng)程序注冊(cè)
int driver_register(struct device_driver *drv);
-
使用它使用以下命令從驅(qū)動(dòng)程序目錄中添加和刪除屬性
int driver_create_file(struct device_driver *, const struct driver_attribute *);
void driver_remove_file(struct device_driver *, const struct driver_attribute *);
class
-
作用:抽象設(shè)備的高層視圖,描述的是設(shè)備的集合。抽象了同類型的設(shè)備的底層實(shí)現(xiàn)細(xì)節(jié)。比如所有的網(wǎng)絡(luò)接口都位于/sys/class/net下 -
struct subsys_private *p描述類鏈表
kobject/kset
-
kobject類似于面向?qū)ο笾械膬?nèi)核基類,內(nèi)核利用它將各個(gè)對(duì)象連接起來(lái)組成分層的機(jī)構(gòu)體系,其parent指針將形成一個(gè)樹狀分層結(jié)構(gòu)。 -
kset內(nèi)部包含了kobject。重心在描述對(duì)象的聚集于集合。這也是set一詞的含義。每一個(gè)kset添加到系統(tǒng)中,都將在sysfs中創(chuàng)建一個(gè)目錄 -
kobject/kset一起實(shí)現(xiàn)了sysfs虛擬文件系統(tǒng)中設(shè)備/總線/設(shè)備驅(qū)動(dòng)樹狀分層結(jié)構(gòu)的最關(guān)鍵的底層實(shí)現(xiàn)由來(lái)。
總體上而言:
通過(guò)上面一些關(guān)鍵數(shù)據(jù)結(jié)構(gòu)關(guān)系分析,總線設(shè)備驅(qū)動(dòng)模型最終目的是實(shí)現(xiàn)如下這樣一個(gè)分層驅(qū)動(dòng)模型。
—END—
本文授權(quán)轉(zhuǎn)載自公眾號(hào)“嵌入式客棧”,作者逸珺
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問(wèn)題,請(qǐng)聯(lián)系我們,謝謝!