1. 程式人生 > >【linux】驅動-6-匯流排-裝置-驅動

【linux】驅動-6-匯流排-裝置-驅動

[toc] --- ## 前言 ## 6. 匯流排-裝置-驅動 **匯流排-裝置-驅動** 又稱為 **裝置驅動模型**。 ### 6.1 概念 * ![](https://img2020.cnblogs.com/blog/2085252/202103/2085252-20210330122623246-1771831714.png) **匯流排(bus)**:負責管理掛載對應匯流排的裝置以及驅動; **裝置(device)**:掛載在某個匯流排的物理裝置; **驅動(driver)**:與特定裝置相關的軟體,負責初始化該裝置以及提供一些操作該裝置的操作方式; **類(class)**:對於具有相同功能的裝置,歸結到一種類別,進行分類管理; ### 6.2 工作原理 *以下只說 匯流排-裝置-驅動 模式下的操作* **匯流排**: * 匯流排管理著兩個連結串列:**裝置連結串列** 和 **驅動連結串列**。 * 當我們向核心註冊一個**驅動**時,便插入到匯流排的**驅動連結串列**。 * 當我們向核心註冊一個**裝置**時,便插入到匯流排的**裝置連結串列**。 * 在插入的同時,匯流排會執行一個 **bus_type** 結構體中的 **match** 方法對新插入的 **裝置/驅動** 進行匹配。(*例如以名字的方式匹配。方式有很多總,下面再詳細分析。*) * 匹配成功後,會呼叫 驅動 device_driver 結構體中的 **probe** 方法。(***通常在 probe 中獲取裝置資源。具體有開發人員決定。***) * 在移除裝置或驅動時,會呼叫 **device_driver** 結構體中的 **remove** 方法。 ### 6.3 匯流排 #### 6.3.1 匯流排介紹 **匯流排**: * 匯流排是連線處理器和裝置之間的橋樑 * 代表著同類裝置需要共同遵循的工作時序。 **匯流排驅動**: * 負責實現匯流排行為,管理兩個連結串列。 **匯流排結構體**: ```c struct bus_type { const char *name; 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); int (*suspend)(struct device *dev, pm_message_t state); int (*resume)(struct device *dev); const struct dev_pm_ops *pm; struct subsys_private *p; }; ``` * **name**:指定匯流排的名稱,當新註冊一種匯流排型別時,會在 **/sys/bus** 目錄建立一個新的目錄,目錄名就是該引數的值; * **bus_groups、dev_groups、drv_groups**:分別表示 匯流排、裝置、驅動的屬性。 * 通常會在對應的 **/sys** 目錄下在以檔案的形式存在,對於驅動而言,在目錄 **/sys/bus//driver/** 存放了驅動的預設屬性;裝置則在目錄 **/sys/bus//devices/** 中。這些檔案一般是可讀寫的,使用者可以通過讀寫操作來獲取和設定這些 **attribute** 的值。 * **match**:當向匯流排註冊一個新的裝置或者是新的驅動時,會呼叫該回調函式。該裝置主要負責匹配工作。 * **uevent**:總線上的裝置發生新增、移除或者其它動作時,就會呼叫該函式,來通知驅動做出相應的對策。 * **probe**:當匯流排將裝置以及驅動相匹配之後,執行該回調函式,最終會呼叫驅動提供的**probe** 函式。 * **remove**:當裝置從匯流排移除時,呼叫該回調函式。 * **suspend、resume**:電源管理的相關函式,當匯流排進入睡眠模式時,會呼叫suspend回撥函式;而resume回撥函式則是在喚醒匯流排的狀態下執行。 * **pm**:電源管理的結構體,存放了一系列跟匯流排電源管理有關的函式,與 **device_driver** 結構體中的 **pm_ops** 有關。 * **p**:該結構體用於存放特定的私有資料,其成員 **klist_devices** 和 **klist_drivers** 記錄了掛載在該匯流排的裝置和驅動。 #### 6.3.2 註冊匯流排 在實際的驅動開發中,Linux 已經為我們編寫好了大部分的匯流排驅動。 但是核心也提供了註冊匯流排的 API。 **bus_register()**: * **bus_register()** 函式用於註冊匯流排。 * 核心原始碼路徑:**核心原始碼/drivers/base/bus.c**。 * 函式原型:**`int bus_register(struct bus_type *bus);`**。 * **bus**:bus_type 型別的結構體指標。 * 返回值: * 成功:0; * 失敗:負數。 **bus_unregister()**: * **bus_unregister()** 用於登出匯流排。 * 核心原始碼路徑:**核心原始碼/drivers/base/bus.c**。 * 函式原型:**`int bus_unregister(struct bus_type *bus);`**。 * **bus**:bus_type 型別的結構體指標。 * 返回值:無。 當我們成功註冊匯流排時,會在 **/sys/bus/** 目錄下建立一個新目錄,目錄名為我們新註冊的匯流排名。 ### 6.4 裝置 #### 6.4.1 裝置介紹 在 **/sys/devices** 目錄記錄了系統中所有的裝置。 **/sys** 下的所有裝置檔案和 **/sys/dev** 下的所有裝置節點都是連結檔案,實際上都指向了對應的裝置檔案。 **device** 結構體: ```c struct device { const char *init_name; struct device *parent; struct bus_type *bus; struct device_driver *driver; void *platform_data; void *driver_data; struct device_node *of_node; dev_t devt; struct class *class; void (*release)(struct device *dev); const struct attribute_group **groups; /* optional groups */ struct device_private *p; }; ``` * 核心原始碼路徑:**核心原始碼/include/linux/device.h**。 * **init_name**:指定該裝置的名稱,匯流排匹配時,一般會根據比較名字來進行配對。 * **parent**:表示該裝置的父物件,舊版本的裝置之間沒有任何聯絡,引入 Linux 裝置驅動模組後,裝置之間呈現樹狀結構,便於管理各種裝置。 * **bus**:歸屬與哪個匯流排。當我們註冊裝置時,核心便會將該設備註冊到對應的匯流排。(*相愛*) * **of_node**:存放裝置樹中匹配的裝置節點。當核心使能裝置樹,匯流排負責將驅動的 **of_match_table** 以及裝置樹的 **compatible** 屬性進行比較之後,將匹配的節點儲存到該變數。 * **platform_data**:特定裝置的私有資料,通常定義在板級檔案中。 * **driver_data**:驅動層可以通過 **dev_set/get_drvdata** 函式來獲取該成員變數。 * **class**:指向該裝置對應類。 * **dev**:裝置號。**dev_t** 型別。 * **release**:回撥函式。當裝置被登出時,該函式被呼叫。 * **group**:指向 **struct attribute_group** 型別指標指定該裝置屬性。 #### 6.4.2 設備註冊、登出 在前面的字元裝置驅動編寫中,我們使用到了 **device_create()** 函式和 **device_destroy()** 函式來建立和刪除裝置。 現在介紹向匯流排註冊和登出裝置。 **向匯流排註冊裝置**: * 函式原型:**`int device_register(struct device *dev);`**。 * 核心原始碼路徑:**核心原始碼/driver/base/core.c**。 * **dev**:**struct device** 結構體型別指標。 * 返回: * 成功:0; * 失敗:負數。 **向匯流排登出裝置**: * 函式原型:**`int device_unregister(struct device *dev);`**。 * 核心原始碼路徑:**核心原始碼/driver/base/core.c**。 * **dev**:**struct device** 結構體型別指標。 * 返回:無。 ### 6.5 驅動 #### 6.5.1 驅動介紹 **driver** 結構體: ```c struct device_driver { const char *name; struct bus_type *bus; struct module *owner; const char *mod_name; /* used for built-in modules */ bool suppress_bind_attrs; /* disables bind/unbind via sysfs */ 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); const struct attribute_group **groups; struct driver_private *p; }; ``` * 核心原始碼路徑:**核心原始碼/include/linux/device.h** * **name**:指定驅動名稱,匯流排進行匹配時,利用該成員與裝置名進行比較。 * **bus**:歸屬與哪個匯流排。核心需要保證在驅動執行之前,對應的匯流排能夠正常工作。 * **suppress_bind_attrs**:布林量,用於指定是否通過 **sysfs** 匯出 **bind** 與 **unbind**檔案,**bind** 與 **unbind** 檔案是驅動用於繫結/解綁關聯的裝置。 * **owner**:表示該驅動的擁有者,一般設定為 **THIS_MODULE**。 * **of_match_table**:指定該驅動支援的裝置型別。當核心使能裝置樹時,會利用該成員與裝置樹中的 **compatible** 屬性進行比較。 * **remove**:當裝置從作業系統中拔出或者是系統重啟時,會呼叫該回調函式。 * **probe**:當驅動以及裝置匹配後,會執行該回調函式,對裝置進行初始化。通常的程式碼,都是以main函式開始執行的,但是在核心的驅動程式碼,都是從 **probe** 函式開始的。 * **group**:指向 **struct attribute_group** 型別的指標,指定該驅動的屬性。 #### 6.4.2 驅動註冊、登出 **向匯流排註冊驅動**: * 函式原型:**`int driver_register(struct device_driver *drv);`**。 * 核心原始碼路徑:**核心原始碼/include/linux/device.h**。 * **drv**:**struct device_driver** 結構體型別指標。 * 返回: * 成功:0; * 失敗:負數。 **向匯流排登出驅動**: * 函式原型:**`int driver_unregister(struct device_driver *drv);`**。 * 核心原始碼路徑:**核心原始碼/include/linux/device.h**。 * **drv**:**struct device_driver** 結構體型別指標。 * 返回:無。 ### 6.5 便解圖文 **資料結構該系統**: * ![](https://img2020.cnblogs.com/blog/2085252/202103/2085252-20210330122634602-1819788896.png) **註冊流程圖**: * ![](https://img2020.cnblogs.com/blog/2085252/202103/2085252-20210330122640834-1962564302.png) * 系統啟動之後會呼叫buses_init函式建立/sys/bus檔案目錄,這部分系統在開機時已經幫我們準備好了, 接下去就是通過匯流排註冊函式bus_register進行匯流排註冊,註冊完匯流排後在匯流排的目錄下生成devices資料夾和drivers資料夾, 最後分別通過device_register以及driver_register函式註冊相對應的裝置和驅動。 ### 6.6 attribute屬性檔案 #### 6.6.1 attribute 介紹 **attribute** 結構體: ```c struct attribute { const char *name; umode_t mode; }; ``` * 核心原始碼路徑:**核心原始碼/include/linux/sysfs.h** * **name**:指定檔案的檔名。 * **mode**:指定檔案的許可權。 **bus_type、device、device_driver** 結構體中都包含了一種資料型別 **struct attribute_group**,它是多個 **attribute** 檔案的集合, 利用它進行初始化,可以避免一個個註冊 **attribute**。 **struct attribute_group** 結構體: ```c struct attribute_group { const char *name; umode_t (*is_visible)(struct kobject *, struct attribute *, int); struct attribute **attrs; struct bin_attribute **bin_attrs; }; ``` * 核心原始碼路徑:**核心原始碼/include/linux/sysfs.h** #### 6.6.2 裝置屬性檔案 Linux 提供註冊和登出裝置屬性檔案的 API。我們可以通過這些 API 直接在使用者層進行查詢和修改。 **struct device_attribute** 結構體: ```c struct device_attribute { struct attribute attr; ssize_t (*show)(struct device *dev, struct device_attribute *attr, char *buf); ssize_t (*store)(struct device *dev, struct device_attribute *attr, const char *buf, size_t count); }; #define DEVICE_ATTR(_name, _mode, _show, _store) \ struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store) extern int device_create_file(struct device *device, const struct device_attribute *entry); extern void device_remove_file(struct device *dev, const struct device_attribute *attr); ``` * 核心原始碼路徑:**核心原始碼/include/linux/device.h**。 * **DEVICE_ATTR** 巨集:用於定義一個device_attribute型別的變數。 * 引數 **_name,_mode,_show,_store**,分別代表了檔名, 檔案許可權,**show** 回撥函式,**store** 回撥函式。 * **show** 回撥函式以及 **store** 回撥函式分別對應著使用者層的 **cat** 和 **echo** 命令,當我們使用 **cat** 命令,來獲取 **/sys** 目錄下某個檔案時,最終會執行 **show** 回撥函式;使用 **echo** 命令,則會執行 **store** 回撥函式。 引數 **\_mode** 的值,可以使用**S_IRUSR、S_IWUSR、S_IXUSR** 等巨集定義,更多選項可以檢視讀寫檔案章節關於檔案許可權的內容。 * **device_create_file**:該函式用於建立檔案。 * **device**:表示裝置。也是歸屬匯流排下的 device 目錄下的目錄名稱。 * **entry**:自定義的 **device_attribute** 型別變數。 * **device_remove_file**:該函式用於刪除檔案。當登出驅動時,對應目錄以及檔案都被移除。 * **device**:表示裝置。也是歸屬匯流排下的 device 目錄下的目錄名稱。 * **entry**:自定義的 **device_attribute** 型別變數。 #### 6.6.3 驅動屬性檔案 Linux 提供註冊和登出驅動屬性檔案的 API。我們可以通過這些 API 直接在使用者層進行查詢和修改。 **struct driver_attribute** 結構體: ```c struct driver_attribute { struct attribute attr; ssize_t (*show)(struct device_driver *driver, char *buf); ssize_t (*store)(struct device_driver *driver, const char *buf, size_t count);}; #define DRIVER_ATTR_RW(_name) \ struct driver_attribute driver_attr_##_name = __ATTR_RW(_name) #define DRIVER_ATTR_RO(_name) \ struct driver_attribute driver_attr_##_name = __ATTR_RO(_name) #define DRIVER_ATTR_WO(_name) \ struct driver_attribute driver_attr_##_name = __ATTR_WO(_name) extern int __must_check driver_create_file(struct device_driver *driver, const struct driver_attribute *attr); extern void driver_remove_file(struct device_driver *driver, const struct driver_attribute *attr); ``` * 核心原始碼路徑:**核心原始碼/include/linux/device.h**。 * **DRIVER_ATTR_RW、DRIVER_ATTR_RO 以及 DRIVER_ATTR_WO** 巨集:用於定義一個driver_attribute型別的變數。 * 帶有 **driver_attr_** 的字首,區別在於檔案許可權不同; * **RW** 字尾表示檔案可讀寫; * **RO** 字尾表示檔案僅可讀; * **WO** 字尾表示檔案僅可寫。 * **DRIVER_ATTR** 型別的巨集定義沒有引數來設定 **show** 和 **store** 回撥函式,在寫驅動程式碼時,只需要提供 **xxx_store** 以及 **xxx_show** 這兩個函式, 並確保兩個函式的 **xxx** 和 **DRIVER_ATTR** 型別的巨集定義中名字是一致的即可。 * **driver_create_file** 和 **driver_remove_file** 函式用於建立和移除檔案,使用**driver_create_file** 函式, 會在 **/sys/bus//drivers//** 目錄下建立檔案。 #### 6.6.4 匯流排屬性檔案 **struct bus_attribute** 結構體: ```c struct bus_attribute { struct attribute attr; ssize_t (*show)(struct bus_type *bus, char *buf); ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count); }; #define BUS_ATTR(_name, _mode, _show, _store) \ struct bus_attribute bus_attr_##_name = __ATTR(_name, _mode, _show, _store) extern int __must_check bus_create_file(struct bus_type *, struct bus_attribute *); extern void bus_remove_file(struct bus_type *, struct bus_attribute *); ``` * 核心原始碼路徑:**核心原始碼/include/linux/device.h**。 * **BUS_ATTR** 巨集定義用於定義一個 **bus_attribute** 變數; * 使用 **bus_create_file** 函式,會在 **/sys/bus/** 下建立對應的檔案。 * **bus_remove_file** 則用於移除該檔案。 ### 6.7 匹配規則 ** 下章筆記就是記錄平臺裝置。本次匹配規則就參考 **平臺裝置驅動** 原始碼。 #### 6.7.1 最先比較 最先比較 **platform_device.driver_override** 和 **platform_driver.driver.name**。 可以設定 **platform_device** 的 **driver_override**,強制選擇某個 **platform_driver**。 #### 6.7.2 其次比較 其次比較 **platform_device.name** 和 **platform_driver.id_table[i].name**。 **platform_driver.id_table** 是 **platform_device_id** 指標,表示該 **drv** 支援若干個 **device**,它裡面列出了各個 **device** 的 **{.name, .driver_data}**,其中的 **name** 表示該 **drv** 支援的裝置的名字,**driver_data**是些提供給該 **device** 的私有資料。 #### 6.7.3 最後比較 最後比較 **platform_device.name** 和 **platform_driver.driver.name**。 由於 **platform_driver.id_table** 可能為空,所以,接下來就可以使用 **platform_driver.driver.name** 來匹配。 #### 6.7.4 函式呼叫關係 ```c platform_device_register platform_device_add device_add bus_add_device // 放入連結串列 bus_probe_device // probe 列舉裝置,即找到匹配的(dev, drv) device_initial_probe __device_attach bus_for_each_drv(...,__device_attach_driver,...) __device_attach_driver driver_match_device(drv, dev) // 是否匹配 driver_probe_device // 呼叫 drv 的 probe platform_driver_register __platform_driver_register driver_register bus_add_driver // 放入連結串列 driver_attach(drv) bus_for_each_dev(drv->bus, NULL, drv, __driver_attach); __driver_attach driver_match_device(drv, dev) // 是否匹配 driver_probe_device // 呼叫 drv 的 probe ``` ### 6.8 實現裝置模型的簡要步驟 * 匯流排、裝置、驅動都基於驅動模型上實現,方便插入。 模組三步驟: 1. 入口函式; 2. 出口函式; 3. 協議。 #### 6.8.1 匯流排 1. 實現匯流排結構體內容; 2. 填充匯流排結構體; 3. 實現屬性檔案內容,包括設定屬性檔案結構體; 4. 註冊匯流排;(*模組入口函式*) 5. 建立屬性檔案; 6. 移除屬性檔案;(*模組出口函式*) 7. 登出匯流排。 #### 6.8.2 裝置 1. 宣告外部匯流排變數; 2. 實現裝置結構體內容; 3. 填充裝置結構體; 4. 實現屬性檔案內容,包括設定屬性檔案結構體; 5. 註冊裝置;(*模組入口函式*) 6. 建立屬性檔案; 7. 移除屬性檔案;(*模組出口函式*) 8. 登出裝置。 #### 6.8.3 驅動 1. 宣告外部匯流排變數; 2. 實現驅動結構體內容; 3. 填充驅動結構體; 4. 實現屬性檔案內容,包括設定屬性檔案結構體; 5. 註冊驅動;(*模組入口函式*) 6. 建立屬性檔案; 7. 移除屬性檔案;(*模組出口函式*) 8. 登出驅動。 ## 參考 * [李柱明部落格](https://www.cnblogs.com/lizhuming/p/1459623