[導讀] Linux裝置林林總總,嵌入式開發一個繞不開的話題就是裝置驅動開發,在做具體裝置驅動開發之前,有必要對Linux設驅動模型有一個相對清晰的認識,將會幫助驅動開發,明白具體驅動介面操作符相應都做些什麼。
個人對於驅動模型的理解概括起來就是一句話:利用物件導向程式設計思想,實現裝置分層管理軟體體系結構。
注:程式碼分析基於linux-5.4.31
為啥要驅動模型
隨著系統結構演化越來越複雜,Linux核心對裝置描述衍生出一般性的抽象描述,形成一個分層體系結構,從而引入了裝置驅動模型。這樣描述還是不夠讓人理解,來看一下這些需求就好理解些:
- Linux核心可以在各種體系結構和硬體平臺上執行,因此需要最大限度地提高程式碼在平臺之間的可重用性。
- 分層實現也實現了軟體工程的高內聚-低耦合的設計思想。低耦合體現在對外提供統一的抽象訪問介面,高內聚將相關度緊密的集中抽象實現。
- Linux核心驅動程式模型是先前在核心中使用的所有不同驅動程式模型的統一。 它旨在通過將一組資料和操作整合到全域性可訪問的資料結構中,來擴充套件基於基礎匯流排來橋接裝置驅動程式。
傳統的驅動模型為它們所控制的裝置實現了某種類似於樹的結構(有時只是一個列表)。不同型別的匯流排之間沒有任何一致性。
驅動模型抽象了啥
當前驅動程式模型為描述匯流排和匯流排下可能出現的裝置提供了一個通用的、統一的模型。統一匯流排模型包括一組所有匯流排都具有的公共屬性和一組公共回撥,如匯流排探測期間的裝置發現、匯流排關閉、匯流排電源管理等。
通用的裝置和橋接介面反映了現代計算機的目標:即執行無縫裝置“即插即用”,電源管理和熱插拔的能力。 特別是,英特爾和微軟規定的模型(即ACPI)可確保與x86相容的系統上幾乎任何匯流排上的幾乎所有裝置都可以在此正規化下工作。 當然,雖然大多數匯流排都支援其中大多數操作,但並不是每條匯流排都能夠支援所有此類操作。
那麼哪些通用需求被抽象出來了呢?
-
電源系統和系統關機,對於電源管理與系統關機對於裝置相關的操作進行抽象實現。關機為什麼要被抽象出來管理,比如裝置操作正在進行此時系統收到關機指令,那麼在裝置模型層就會遍歷系統裝置硬體,確保系統正確關機。
-
使用者空間訪問:sysfs虛擬檔案系統實現與裝置模型對外的訪問抽象,這也是為什麼說Linux 裝置也是檔案的由來。實際從軟體架構層面看,這其實是一個軟體橋接模組,抽象出統一使用者訪問介面,橋接了裝置驅動。
-
熱插拔管理:熱插拔管理機制定義統一的抽象介面操作符kset_hotplug_ops,不同裝置利用操作符實現差異化。
-
裝置型別:裝置分類機制,從高層級抽象描述裝置型別,具體可以在sysfs下面體現。
使用者空間訪問
由於具有系統中所有裝置的完整分層檢視,因此將完整的分層檢視匯出到使用者空間變得相對容易。 這是通過實現名為sysfs虛擬檔案系統來完成的。
sysfs的自動掛載通常是通過/etc/fstab檔案中的以下條目來完成的:
none /sys sysfs defaults 0 0
對於Debian系統而言,可能在/lib/init/fstab採用下面的形式掛載:
none /sys sysfs nodev,noexec,nosuid 0 0
當然也可以採用手動方式掛載:
# mount -t sysfs sysfs /sys
當將裝置插入樹中時,都會為其建立一個目錄。該目錄可以填充在發現的每個層(全域性層,匯流排層或裝置層)中。
全域性層當前建立兩個檔案-'name'和'power'。 前者報告裝置名稱。 後者報告裝置的當前電源狀態。 它還將用於設定當前電源狀態。
匯流排層為探測匯流排時發現的裝置建立檔案。 例如,PCI層當前為每個PCI裝置建立“ irq”和“resource”檔案。
特定於裝置的驅動程式也可以在其目錄中匯出檔案,以暴露特定於裝置的資料或可用介面。
驅動模型實現
先來梳理一下內部幾個主要與驅動模型相關的資料結構:
./include/linux/Device.h 定義裝置驅動主要資料結構
- bus_type:抽象描述匯流排型別,如USB/PCI/I2C/MMC等
- device_driver:實現具體連線在匯流排上的裝置驅動。
- device:描述連線在匯流排上的裝置
./include/linux/Kobject.h中定義了隱藏在後臺的類似於基類的資料結構:
- kset:可以認為是kobject的頂層容器類。每個kset內部都包含了自己的kobject.
- kobject:在 sysfs 中出現的每個物件都對應一個 kobject, 它和核心互動來建立它的可見表述,每一個 kobject 對應 檔案系統 /sys 裡的一個 目錄,目錄的名字就是結構體中的 name
bus_type
bus_type用以驅動匯流排,具體的驅動USB/I2C/PCI/MMC等:
- 註冊匯流排,利用bus_register註冊匯流排,bus_unregister刪除匯流排。如下例子,每種匯流排須定義一個bus_type物件,並利用bus_register註冊匯流排,或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);
/*註冊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;
/*錯誤時刪除匯流排*/
bus_unregister(&i2c_bus_type);
return retval;
}
- 註冊介面卡驅動程式(USB控制器,I2C介面卡等),以檢測連線的裝置,並提供與裝置的通訊機制
- 圖中的match函式介面用於將驅動程式與裝置進行匹配。match回撥的目的是使匯流排有機會通過比較驅動程式支援的裝置ID與特定裝置的裝置ID來確定特定驅動程式是否支援特定裝置,而不會犧牲特定於匯流排的功能或型別安全性 。當向匯流排註冊驅動程式時,將遍歷匯流排的裝置列表,併為每個沒有與之關聯的驅動程式的裝置呼叫match回撥。
- 提供API函式以實現介面卡驅動以及裝置驅動。
- 同時dev_pm_ops *pm實現對於匯流排的功耗管理介面抽象。對於特定匯流排實現這個操作符對應的函式。
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 操作符提供匯流排相關的IOMMU抽象。
- 裝置驅動註冊到匯流排上時,將在sysfs管理匯流排/裝置/裝置驅動的層次關係,以PCI為例:
/*在匯流排上註冊的驅動程式會在匯流排的驅動程式目錄中獲得一個目錄*/
/sys/bus/pci/
|-- devices
`-- drivers
|-- Intel ICH
|-- Intel ICH Joystick
|-- agpgart
`-- e100
/*在該型別的匯流排上發現的每個裝置都會在匯流排的裝置目錄中獲得到物理層次結構中該裝置目錄的符號連結*/
/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/裝置屬性dev_groups/驅動屬性drv_groups。
device
-
作用:抽象描述具體的裝置
-
裝置註冊:發現裝置的匯流排驅動程式使用下面的函式來向核心註冊裝置
int device_register(struct device * dev);
- 利用device_unregister()從匯流排上刪除裝置
device_driver
- 作用:抽象描述連線在匯流排上的具體裝置的驅動
- 驅動註冊,通過下面的函式將裝置驅動程式註冊
int driver_register(struct device_driver *drv);
- 使用它使用以下命令從驅動程式目錄中新增和刪除屬性
int driver_create_file(struct device_driver *, const struct driver_attribute *);
void driver_remove_file(struct device_driver *, const struct driver_attribute *);
class
- 作用:抽象裝置的高層檢視,描述的是裝置的集合。抽象了同型別的裝置的底層實現細節。比如所有的網路介面都位於/sys/class/net下
- struct subsys_private *p描述類連結串列
kobject/kset
- kobject類似於物件導向中的核心基類,核心利用它將各個物件連線起來組成分層的機構體系,其parent指標將形成一個樹狀分層結構。
- kset內部包含了kobject。重心在描述物件的聚集於集合。這也是set一詞的含義。每一個kset新增到系統中,都將在sysfs中建立一個目錄
- kobject/kset一起實現了sysfs虛擬檔案系統中裝置/匯流排/裝置驅動樹狀分層結構的最關鍵的底層實現由來。
總體上而言:
通過上面一些關鍵資料結構關係分析,匯流排裝置驅動模型最終目的是實現如下這樣一個分層驅動模型。
文章出自微信公眾號:嵌入式客棧,更多內容,請關注本人公眾號,嚴禁商業使用,違法必究