【linux】驅動-6-總線-設備-驅動


前言

6. 總線-設備-驅動

總線-設備-驅動 又稱爲 設備驅動模型html

6.1 概念

總線(bus):負責管理掛載對應總線的設備以及驅動;
設備(device):掛載在某個總線的物理設備;
驅動(driver):與特定設備相關的軟件,負責初始化該設備以及提供一些操做該設備的操做方式;
類(class):對於具備相同功能的設備,歸結到一種類別,進行分類管理;node

6.2 工做原理

如下只說 總線-設備-驅動 模式下的操做
總線linux

  • 總線管理着兩個鏈表:設備鏈表驅動鏈表
  • 當咱們向內核註冊一個驅動時,便插入到總線的驅動鏈表
  • 當咱們向內核註冊一個設備時,便插入到總線的設備鏈表
  • 在插入的同時,總線會執行一個 bus_type 結構體中的 match 方法對新插入的 設備/驅動 進行匹配。(例如以名字的方式匹配。方式有不少總,下面再詳細分析。
  • 匹配成功後,會調用 驅動 device_driver 結構體中的 probe 方法。(一般在 probe 中獲取設備資源。具體有開發人員決定。
  • 在移除設備或驅動時,會調用 device_driver 結構體中的 remove 方法。

6.3 總線

6.3.1 總線介紹

總線數據結構

  • 總線是鏈接處理器和設備之間的橋樑
  • 表明着同類設備須要共同遵循的工做時序。

總線驅動ide

  • 負責實現總線行爲,管理兩個鏈表。

總線結構體函數

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。ui

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/ 目錄下建立一個新目錄,目錄名爲咱們新註冊的總線名。code

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. 註銷驅動。

參考

相關文章
相關標籤/搜索