學Linux驅動: 應該先了解驅動模型

[導讀] Linux設備林林總總,嵌入式開發一個繞不開的話題就是設備驅動開發,在作具體設備驅動開發以前,有必要對Linux設驅動模型有一個相對清晰的認識,將會幫助驅動開發,明白具體驅動接口操做符相應都作些什麼。node

我的對於驅動模型的理解歸納起來就是一句話:利用面向對象編程思想,實現設備分層管理軟件體系結構linux

注:代碼分析基於linux-5.4.31c++

爲啥要驅動模型

隨着系統結構演化愈來愈複雜,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虛擬文件系統中設備/總線/設備驅動樹狀分層結構的最關鍵的底層實現由來。

整體上而言:

經過上面一些關鍵數據結構關係分析,總線設備驅動模型最終目的是實現以下這樣一個分層驅動模型。

文章出自微信公衆號:嵌入式客棧,更多內容,請關注本人公衆號,嚴禁商業使用,違法必究

相關文章
相關標籤/搜索