辅助总线

在某些子系统中,核心设备(PCI/ACPI/其他)的功能过于复杂,无法由单个驱动程序管理(例如,Sound Open Firmware),多个设备可能实现功能的公共交集(例如,NIC + RDMA),或者驱动程序可能想要导出一个接口供另一个子系统驱动(例如,SIOV 物理功能导出虚拟功能管理)。将功能拆分为表示功能子域的子设备,使得可以通过 Linux 设备驱动程序模型来划分、分层和分配特定领域的关注点。

这种需求的一个例子是音频子系统,其中单个 IP 处理多个实体,例如 HDMI、Soundwire、本地设备(如麦克风/扬声器等)。核心功能的拆分可以是任意的,也可以由 DSP 固件拓扑定义,并包含用于测试/调试的钩子。这允许音频核心设备尽可能小,并专注于特定于硬件的控制和通信。

每个 auxiliary_device 代表其父功能的一部分。可以通过将 auxiliary_device 封装在其他特定领域的结构中以及使用 .ops 回调来根据需要扩展和专门化通用行为。辅助总线上的设备不共享任何结构,并且与父级的通信通道的使用是特定于领域的。

请注意,ops 旨在作为一种增强辅助设备类中实例行为的方式,而不是从父级导出通用基础结构的机制。考虑使用 EXPORT_SYMBOL_NS() 将基础结构从父模块传递到辅助模块。

何时应使用辅助总线

当驱动程序和一个或多个与驱动程序共享公共头文件的内核模块需要一种机制来连接并提供对 auxiliary_device 注册驱动程序分配的共享对象的访问时,应使用辅助总线。auxiliary_device 的注册驱动程序和注册 auxiliary_drivers 的内核模块可以来自同一子系统,也可以来自多个子系统。

这里的重点是保持子系统定制在总线基础设施之外的通用接口。

一个例子是具有 RDMA 功能的 PCI 网络设备,并导出一个子设备,由 RDMA 子系统中的辅助驱动程序驱动。PCI 驱动程序为 NIC 上的每个物理功能分配并注册一个 auxiliary_device。RDMA 驱动程序注册一个 auxiliary_driver,该驱动程序声明这些 auxiliary_device 中的每一个。这会将父 PCI 设备/驱动程序发布的数据/操作传递给 RDMA 辅助驱动程序。

另一个用例是将 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 包含设备使用的 irq 索引,

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 的 auxiliary_device(例如 “foo_mod.foo_dev”)注册到总线上,则它们必须具有唯一的 ID 值(例如 “x” 和 “y”),以便注册的设备名称为 “foo_mod.foo_dev.x” 和 “foo_mod.foo_dev.y”。如果 match_name + id 不是唯一的,则 device_add 将失败并生成错误消息。

必须使用非 NULL 指针填充 auxiliary_device.dev.type.release 或 auxiliary_device.dev.release 才能成功注册 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)

检查 auxiliary_device 并初始化

参数

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” 宏,以便自动为 modname 参数插入调用者的 KBUILD_MODNAME。只有当用户需要自定义名称时,才会直接调用此版本。

辅助设备内存模型和生命周期

注册驱动程序是为 auxiliary_device 分配内存并将其注册到辅助总线上的实体。重要的是要注意,与平台总线相反,注册驱动程序完全负责管理用于设备对象的内存。

明确地说,auxiliary_device 的内存在注册驱动程序定义的 release() 回调中释放。注册驱动程序仅应在完成设备操作时才调用 auxiliary_device_delete(),然后调用 auxiliary_device_uninit()。如果其他代码释放对设备的引用,则会自动调用 release() 函数。

在共享头文件中定义的父对象包含 auxiliary_device。它还包含指向共享对象(也在共享头文件中定义)的指针。父对象和共享对象都由注册驱动程序分配。这种布局允许辅助驱动程序的注册模块执行 container_of() 调用,以从指向 auxiliary_device 的指针(在调用辅助驱动程序的 probe 函数期间传递)向上到父对象,然后访问共享对象。

共享对象的内存的生命周期必须等于或大于 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_names,驱动程序可以与之绑定。

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

auxiliary_driver 结构

struct module *owner

所属模块/驱动程序

const char *modname

父驱动程序的 KBUILD_MODNAME

描述

期望用户调用 “auxiliary_driver_register” 宏,以便自动为 modname 参数插入调用者的 KBUILD_MODNAME。只有当用户需要自定义名称时,才会直接调用此版本。

void auxiliary_driver_unregister(struct auxiliary_driver *auxdrv)

注销驱动程序

参数

struct auxiliary_driver *auxdrv

auxiliary_driver 结构

用法示例

辅助设备由子系统级别的核心设备创建和注册,该设备需要将其功能分解为更小的片段。扩展 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。

每当注册辅助驱动程序时,基于 match_name,都会为匹配的设备调用辅助驱动程序的 probe()。辅助驱动程序也可以封装在自定义驱动程序中,这些驱动程序通过添加其他特定于域的操作来使核心设备的功能可扩展,如下所示

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,
};