輔助匯流排

在某些子系統中,核心裝置(PCI/ACPI/其他)的功能過於複雜,無法由一個單一驅動程式(例如 Sound Open Firmware)管理;多個裝置可能實現共同的功能交集(例如 NICs + RDMA);或者一個驅動程式可能希望匯出一個介面供另一個子系統驅動(例如 SIOV 物理功能匯出虛擬功能管理)。將功能拆分為代表功能子域的子裝置,使得透過 Linux 裝置驅動程式模型,可以對特定領域的問題進行分割槽、分層和分發。

這種要求的一個例子是音訊子系統,其中一個 IP 處理多個實體,如 HDMI、Soundwire、本地裝置(如麥克風/揚聲器等)。核心功能的拆分可以是任意的,也可以由 DSP 韌體拓撲定義,幷包含用於測試/除錯的鉤子。這使得音訊核心裝置可以最小化,並專注於硬體特定的控制和通訊。

每個 auxiliary_device 代表其父功能的一部分。透過將 auxiliary_device 封裝在其他特定領域結構中並使用 .ops 回撥,可以根據需要擴充套件和專業化通用行為。輔助總線上的裝置不共享任何結構,並且與父裝置之間的通訊通道是特定於領域的。

請注意,ops 旨在作為增強輔助裝置類別內例項行為的方式,而不是從父級匯出通用基礎設施的機制。考慮使用 EXPORT_SYMBOL_NS() 將基礎設施從父模組傳遞到輔助模組。

何時使用輔助匯流排

當一個驅動程式和一個或多個與該驅動程式共享公共標頭檔案的核心模組,需要一種機制來連線並提供對由 auxiliary_device 註冊驅動程式分配的共享物件的訪問時,應使用輔助匯流排。 auxiliary_device(s) 的註冊驅動程式和註冊 auxiliary_driver(s) 的核心模組可以來自同一個子系統,也可以來自多個子系統。

這裡的重點是一個通用的介面,它將子系統定製排除在匯流排基礎設施之外。

一個例子是具有 RDMA 功能的 PCI 網絡卡,它匯出一個子裝置,由 RDMA 子系統中的 auxiliary_driver 驅動。PCI 驅動程式為 NIC 上的每個物理功能分配並註冊一個 auxiliary_device。RDMA 驅動程式註冊一個 auxiliary_driver 來認領這些 auxiliary_device。這會將父 PCI 裝置/驅動程式釋出的資料/操作傳遞給 RDMA auxiliary_driver。

另一個用例是將 PCI 裝置拆分為多個子功能。對於每個子功能,都會建立一個 auxiliary_device。PCI 子功能驅動程式繫結到此類裝置,並建立自己的一個或多個類裝置。PCI 子功能輔助裝置可能包含在一個結構中,該結構具有額外的屬性,例如使用者定義的子功能編號以及可選屬性,例如資源和指向父裝置的連結。這些屬性可由 systemd/udev 使用;因此,在驅動程式繫結到 auxiliary_device 之前應進行初始化。

利用輔助匯流排的一個關鍵要求是,它不依賴於物理匯流排、裝置、暫存器訪問或 regmap 支援。這些從核心裝置中分離出來的獨立裝置不能存在於平臺總線上,因為它們不是由 DT/ACPI 控制的物理裝置。同樣的論點也適用於在這種情況下不使用 MFD,因為 MFD 依賴於單個功能裝置是物理裝置。

輔助裝置的建立

struct auxiliary_device

輔助裝置物件。

定義:

struct auxiliary_device {
    struct device dev;
    const char *name;
    u32 id;
    struct {
        struct xarray irqs;
        struct mutex lock;
        bool irq_dir_exists;
    } sysfs;
};

成員

dev

裝置,裝置結構的 release 和 parent 欄位必須填寫

name

輔助裝置驅動程式找到的匹配名稱,

id

如果匯出多個同名裝置,則為唯一識別符號,

sysfs

嵌入式結構,包含所有 sysfs 相關欄位,

sysfs.irqs

irqs xarray 包含裝置使用的中斷索引,

sysfs.lock

同步 irq sysfs 建立,

sysfs.irq_dir_exists

“irqs”目錄是否存在,

描述

一個 auxiliary_device 代表其父裝置功能的一部分。它被賦予一個名稱,該名稱與註冊驅動程式的 KBUILD_MODNAME 結合,建立一個用於驅動程式繫結的 match_name,以及一個與 match_name 結合提供唯一名稱以在匯流排子系統註冊的 id。例如,一個註冊輔助裝置的驅動程式名為 'foo_mod.ko',子裝置名為 'foo_dev'。因此,匹配名稱為 'foo_mod.foo_dev'。

註冊 auxiliary_device 是一個三步過程。

首先,需要為每個所需的子裝置定義或分配一個 'struct auxiliary_device'。此結構的 name、id、dev.release 和 dev.parent 欄位必須按如下方式填寫。

'name' 欄位應給定一個輔助驅動程式識別的名稱。如果兩個具有相同 match_name(例如 "foo_mod.foo_dev")的 auxiliary_device 註冊到匯流排,它們必須具有唯一的 id 值(例如 "x" 和 "y"),以便註冊裝置名稱為 "foo_mod.foo_dev.x" 和 "foo_mod.foo_dev.y"。如果 match_name + id 不唯一,則 device_add 失敗並生成錯誤訊息。

auxiliary_device.dev.type.release 或 auxiliary_device.dev.release 必須填充一個非 NULL 指標才能成功註冊 auxiliary_device。此 release 呼叫是必須釋放與輔助裝置相關聯的資源的地方。因為一旦裝置放置在總線上,父驅動程式無法知道其他程式碼可能對此資料有多少引用。

應設定 auxiliary_device.dev.parent。通常設定為註冊驅動程式的裝置。

其次,呼叫 auxiliary_device_init(),它會檢查 auxiliary_device 結構的幾個方面並執行 device_initialize()。此步驟完成後,任何錯誤狀態在其解決方案路徑中都必須呼叫 auxiliary_device_uninit()。

註冊 auxiliary_device 的第三步也是最後一步是呼叫 auxiliary_device_add(),它設定裝置的名稱並將裝置新增到匯流排。

#define MY_DEVICE_NAME "foo_dev"

...

struct auxiliary_device *my_aux_dev = my_aux_dev_alloc(xxx);

// Step 1:
my_aux_dev->name = MY_DEVICE_NAME;
my_aux_dev->id = my_unique_id_alloc(xxx);
my_aux_dev->dev.release = my_aux_dev_release;
my_aux_dev->dev.parent = my_dev;

// Step 2:
if (auxiliary_device_init(my_aux_dev))
        goto fail;

// Step 3:
if (auxiliary_device_add(my_aux_dev)) {
        auxiliary_device_uninit(my_aux_dev);
        goto fail;
}

...

登出 auxiliary_device 是一個兩步過程,與註冊過程相對應。首先呼叫 auxiliary_device_delete(),然後呼叫 auxiliary_device_uninit()。

auxiliary_device_delete(my_dev->my_aux_dev);
auxiliary_device_uninit(my_dev->my_aux_dev);
int auxiliary_device_init(struct auxiliary_device *auxdev)

檢查並初始化輔助裝置

引數

struct auxiliary_device *auxdev

輔助裝置結構

描述

這是註冊 auxiliary_device 的三步過程中的第二步。

當此函式返回錯誤程式碼時,device_initialize 將**不會**被執行,並且呼叫者將負責直接在錯誤路徑中釋放為 auxiliary_device 分配的任何記憶體。

成功時返回 0。成功時,device_initialize 已被執行。在此點之後,任何錯誤展開都將需要包含對 auxiliary_device_uninit() 的呼叫。在這種初始化後錯誤場景中,將觸發對裝置的 .release 回撥的呼叫,並且所有記憶體清理預計都在那裡處理。

int __auxiliary_device_add(struct auxiliary_device *auxdev, const char *modname)

新增一個輔助匯流排裝置

引數

struct auxiliary_device *auxdev

要新增到匯流排的輔助匯流排裝置

const char *modname

父裝置驅動模組的名稱

描述

這是註冊 auxiliary_device 的三步過程中的第三步。

此函式必須在成功呼叫 auxiliary_device_init() 之後呼叫,後者將執行 device_initialize。這意味著如果此函式返回錯誤程式碼,則必須執行 auxiliary_device_uninit() 呼叫,以便觸發 .release 回撥以釋放與 auxiliary_device 相關聯的記憶體。

期望使用者呼叫 "auxiliary_device_add" 宏,以便呼叫者的 KBUILD_MODNAME 自動插入 modname 引數。只有當用戶需要自定義名稱時,才會直接呼叫此版本。

輔助裝置的記憶體模型和生命週期

註冊驅動程式是為 auxiliary_device 分配記憶體並將其註冊到輔助總線上的實體。重要的是要注意,與平臺匯流排不同,註冊驅動程式完全負責管理用於裝置物件的記憶體。

明確地說,auxiliary_device 的記憶體是在註冊驅動程式定義的 release() 回撥中釋放的。註冊驅動程式在完成裝置操作後,應該只調用 auxiliary_device_delete(),然後呼叫 auxiliary_device_uninit()。如果其他程式碼釋放了對裝置的引用,並且何時釋放,release() 函式將自動被呼叫。

共享標頭檔案中定義的父物件包含 auxiliary_device。它還包含指向共享物件(也定義在共享標頭檔案中)的指標。父物件和共享物件都由註冊驅動程式分配。這種佈局允許 auxiliary_driver 的註冊模組執行 container_of() 呼叫,從傳遞給 auxiliary_driver 的 probe 函式的 auxiliary_device 指標向上追溯到父物件,然後訪問共享物件。

共享物件的記憶體生命週期必須等於或大於 auxiliary_device 的記憶體生命週期。輔助驅動程式只應認為共享物件在 auxiliary_device 仍在輔助總線上註冊時才有效。註冊驅動程式負責管理(例如,釋放或保持可用)共享物件的記憶體,使其超出 auxiliary_device 的生命週期。

註冊驅動程式必須在其自己的 driver.remove() 完成之前登出所有輔助裝置。確保這一點的一個簡單方法是使用 devm_add_action_or_reset() 呼叫註冊一個針對父裝置的函式,該函式登出輔助裝置物件。

最後,在註冊驅動程式登出輔助裝置後,任何操作輔助裝置的功能都必須繼續執行(即使只是返回錯誤)。

輔助驅動程式

struct auxiliary_driver

輔助匯流排驅動程式的定義

定義:

struct auxiliary_driver {
    int (*probe)(struct auxiliary_device *auxdev, const struct auxiliary_device_id *id);
    void (*remove)(struct auxiliary_device *auxdev);
    void (*shutdown)(struct auxiliary_device *auxdev);
    int (*suspend)(struct auxiliary_device *auxdev, pm_message_t state);
    int (*resume)(struct auxiliary_device *auxdev);
    const char *name;
    struct device_driver driver;
    const struct auxiliary_device_id *id_table;
};

成員

probe

當匹配裝置新增到匯流排時呼叫。

remove

當裝置從匯流排移除時呼叫。

shutdown

在關機時呼叫以使裝置靜止。

suspend

呼叫此函式使裝置進入睡眠模式。通常是進入某種電源狀態。

resume

呼叫此函式使裝置從睡眠模式喚醒。

name

驅動名稱。

driver

核心驅動結構。

id_table

此驅動程式應在總線上匹配的裝置表。

描述

輔助驅動程式遵循標準驅動程式模型約定,其中發現/列舉由核心處理,驅動程式提供 probe() 和 remove() 方法。它們使用標準約定支援電源管理和關機通知。

輔助驅動程式透過呼叫 auxiliary_driver_register() 向匯流排註冊。id_table 包含驅動程式可以繫結的輔助裝置的 match_name。

static const struct auxiliary_device_id my_auxiliary_id_table[] = {
        { .name = "foo_mod.foo_dev" },
        {},
};

MODULE_DEVICE_TABLE(auxiliary, my_auxiliary_id_table);

struct auxiliary_driver my_drv = {
        .name = "myauxiliarydrv",
        .id_table = my_auxiliary_id_table,
        .probe = my_drv_probe,
        .remove = my_drv_remove
};
module_auxiliary_driver

module_auxiliary_driver (__auxiliary_driver)

註冊輔助驅動程式的輔助宏

引數

__auxiliary_driver

輔助驅動程式結構

描述

用於在模組初始化/退出時不執行任何特殊操作的輔助驅動程式的輔助宏。這消除了大量樣板程式碼。每個模組只能使用此宏一次,並且呼叫它會替換 module_init()module_exit()

module_auxiliary_driver(my_drv);
int __auxiliary_driver_register(struct auxiliary_driver *auxdrv, struct module *owner, const char *modname)

註冊輔助匯流排裝置的驅動程式

引數

struct auxiliary_driver *auxdrv

輔助驅動程式結構

struct module *owner

所有者模組/驅動程式

const char *modname

父驅動程式的 KBUILD_MODNAME

描述

期望使用者呼叫 "auxiliary_driver_register" 宏,以便呼叫者的 KBUILD_MODNAME 自動插入 modname 引數。只有當用戶需要自定義名稱時,才會直接呼叫此版本。

void auxiliary_driver_unregister(struct auxiliary_driver *auxdrv)

登出驅動程式

引數

struct auxiliary_driver *auxdrv

輔助驅動程式結構

使用示例

輔助裝置由需要將其功能分解為更小片段的子系統級核心裝置建立和註冊。擴充套件 auxiliary_device 範圍的一種方法是將其封裝在父裝置定義的特定領域結構中。此結構包含 auxiliary_device 和建立與父裝置連線所需的任何相關共享資料/回撥。

一個例子是

 struct foo {
      struct auxiliary_device auxdev;
      void (*connect)(struct auxiliary_device *auxdev);
      void (*disconnect)(struct auxiliary_device *auxdev);
      void *data;
};

然後,父裝置透過呼叫 auxiliary_device_init(),然後呼叫 auxiliary_device_add(),並傳入指向上述結構中 auxdev 成員的指標來註冊 auxiliary_device。父裝置為 auxiliary_device 提供一個名稱,該名稱與父裝置的 KBUILD_MODNAME 結合,建立一個用於與驅動程式匹配和繫結的 match_name。

每當註冊 auxiliary_driver 時,基於 match_name,將為匹配裝置呼叫 auxiliary_driver 的 probe()。auxiliary_driver 也可以封裝在自定義驅動程式中,透過新增額外的特定領域操作來擴充套件核心裝置的功能,如下所示:

struct my_ops {
        void (*send)(struct auxiliary_device *auxdev);
        void (*receive)(struct auxiliary_device *auxdev);
};


struct my_driver {
        struct auxiliary_driver auxiliary_drv;
        const struct my_ops ops;
};

此類用法的一個例子是

const struct auxiliary_device_id my_auxiliary_id_table[] = {
        { .name = "foo_mod.foo_dev" },
        { },
};

const struct my_ops my_custom_ops = {
        .send = my_tx,
        .receive = my_rx,
};

const struct my_driver my_drv = {
        .auxiliary_drv = {
                .name = "myauxiliarydrv",
                .id_table = my_auxiliary_id_table,
                .probe = my_probe,
                .remove = my_remove,
                .shutdown = my_shutdown,
        },
        .ops = my_custom_ops,
};

請注意,這種自定義操作方法是有效的,但很難正確實現,因為需要每個裝置全域性鎖來防止在呼叫這些操作期間輔助驅動程式被移除。此外,此實現缺乏適當的模組依賴性,這會導致輔助父模組和裝置模組之間載入/解除安裝的競爭條件。

提供這些操作的最簡單可靠的方法是使用 EXPORT_SYMBOL*() 匯出它們,並依賴現有的模組基礎設施來確保有效性和正確的依賴鏈。