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

李柱明發表於2021-03-30


前言

6. 匯流排-裝置-驅動

匯流排-裝置-驅動 又稱為 裝置驅動模型

6.1 概念

匯流排(bus):負責管理掛載對應匯流排的裝置以及驅動;
裝置(device):掛載在某個匯流排的物理裝置;
驅動(driver):與特定裝置相關的軟體,負責初始化該裝置以及提供一些操作該裝置的操作方式;
類(class):對於具有相同功能的裝置,歸結到一種類別,進行分類管理;

6.2 工作原理

以下只說 匯流排-裝置-驅動 模式下的操作
匯流排

  • 匯流排管理著兩個連結串列:裝置連結串列驅動連結串列
  • 當我們向核心註冊一個驅動時,便插入到匯流排的驅動連結串列
  • 當我們向核心註冊一個裝置時,便插入到匯流排的裝置連結串列
  • 在插入的同時,匯流排會執行一個 bus_type 結構體中的 match 方法對新插入的 裝置/驅動 進行匹配。(例如以名字的方式匹配。方式有很多總,下面再詳細分析。
  • 匹配成功後,會呼叫 驅動 device_driver 結構體中的 probe 方法。(通常在 probe 中獲取裝置資源。具體有開發人員決定。
  • 在移除裝置或驅動時,會呼叫 device_driver 結構體中的 remove 方法。

6.3 匯流排

6.3.1 匯流排介紹

匯流排

  • 匯流排是連線處理器和裝置之間的橋樑
  • 代表著同類裝置需要共同遵循的工作時序。

匯流排驅動

  • 負責實現匯流排行為,管理兩個連結串列。

匯流排結構體

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_devicesklist_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 結構體:

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
    • devstruct device 結構體型別指標。
    • 返回:
      • 成功:0;
      • 失敗:負數。

向匯流排登出裝置

  • 函式原型:int device_unregister(struct device *dev);
    • 核心原始碼路徑:核心原始碼/driver/base/core.c
    • devstruct device 結構體型別指標。
    • 返回:無。

6.5 驅動

6.5.1 驅動介紹

driver 結構體:


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 匯出 bindunbind檔案,bindunbind 檔案是驅動用於繫結/解綁關聯的裝置。
  • 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
    • drvstruct device_driver 結構體型別指標。
    • 返回:
      • 成功:0;
      • 失敗:負數。

向匯流排登出驅動

  • 函式原型:int driver_unregister(struct device_driver *drv);
    • 核心原始碼路徑:核心原始碼/include/linux/device.h
    • drvstruct device_driver 結構體型別指標。
    • 返回:無。

6.5 便解圖文

資料結構該系統

註冊流程圖

  • 系統啟動之後會呼叫buses_init函式建立/sys/bus檔案目錄,這部分系統在開機時已經幫我們準備好了, 接下去就是通過匯流排註冊函式bus_register進行匯流排註冊,註冊完匯流排後在匯流排的目錄下生成devices資料夾和drivers資料夾, 最後分別通過device_register以及driver_register函式註冊相對應的裝置和驅動。

6.6 attribute屬性檔案

6.6.1 attribute 介紹

attribute 結構體:

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 結構體:

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 結構體:

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 回撥函式分別對應著使用者層的 catecho 命令,當我們使用 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 結構體:

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 型別的巨集定義沒有引數來設定 showstore 回撥函式,在寫驅動程式碼時,只需要提供 xxx_store 以及 xxx_show 這兩個函式, 並確保兩個函式的 xxxDRIVER_ATTR 型別的巨集定義中名字是一致的即可。
  • driver_create_filedriver_remove_file 函式用於建立和移除檔案,使用driver_create_file 函式, 會在 /sys/bus//drivers// 目錄下建立檔案。

6.6.4 匯流排屬性檔案

struct bus_attribute 結構體:

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_overrideplatform_driver.driver.name

可以設定 platform_devicedriver_override,強制選擇某個 platform_driver

6.7.2 其次比較

其次比較 platform_device.nameplatform_driver.id_table[i].name

platform_driver.id_tableplatform_device_id 指標,表示該 drv 支援若干個 device,它裡面列出了各個 device{.name, .driver_data},其中的 name 表示該 drv 支援的裝置的名字,driver_data是些提供給該 device 的私有資料。

6.7.3 最後比較

最後比較 platform_device.nameplatform_driver.driver.name

由於 platform_driver.id_table 可能為空,所以,接下來就可以使用 platform_driver.driver.name 來匹配。

6.7.4 函式呼叫關係

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. 登出匯流排驅動。
  • 注意:匯流排驅動只是一個框架,類似於模組框架,填空,然後核心幫你操作。具體的裝置檔案、裝置號、裝置節點這些還是和以前一樣自己實現。

參考

相關文章