辅助总线¶
在某些子系统中,核心设备(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*() 导出它们,并依赖现有的模块基础设施来确保有效性和正确的依赖链。