ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Linux Auxiliary Bus 详解:从内核源码剖析辅助设备总线的设计与实战

Linux Auxiliary Bus 详解:从内核源码剖析辅助设备总线的设计与实战 Linux Auxiliary Bus 详解从内核源码剖析辅助设备总线的设计与实战【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux导读Auxiliary Bus辅助总线是 Linux 内核 driver core 提供的一种轻量级虚拟总线用于将一个复杂物理设备如 PCI/ACPI 设备的功能按子域拆分注册为多个子设备auxiliary_device并由独立的内核模块以 auxiliary_driver 形式绑定驱动。本文以当前仓库中 Documentation/driver-api/auxiliary_bus.rst 为骨架结合其内核实现 drivers/base/auxiliary.c、头文件 include/linux/auxiliary_bus.h 与真实使用案例Sound Open Firmware 的 client 设备完整讲解辅助总线的设计动机、设备/驱动注册流程、内存生命周期模型、匹配机制与实战代码模板。读完本文你将能够独立把一个功能繁杂的核心驱动拆分重构为总线 子设备 子驱动的模块化架构。一、为什么需要 Auxiliary Bus核心设备功能拆分的困境在内核中某些子系统面临一个共性问题核心设备PCI/ACPI/其他的功能过于复杂无法由一个巨无霸驱动的单一设备模型来管理。典型场景包括单一 IP 承载多个功能实体例如音频子系统一块 IP 同时处理 HDMI、Soundwire、本地麦克风/扬声器等多个实体多设备实现公共功能交集例如网卡NIC与 RDMA 共存的设备一个 PCI 网卡既是网络设备又具备 RDMA 能力向其他子系统导出接口例如 SIOV Physical Function 向外部导出 Virtual Function 管理能力。drivers/base/auxiliary.c的DOC: PURPOSE明确指出把核心功能拆分为代表功能子域的子设备可以借助标准 Linux 设备-驱动模型实现**隔离compartmentalize、分层layer与分发distribute**领域专属的关注点。以音频为例DSP 固件拓扑可以任意划分核心功能甚至包含测试/调试钩子从而使音频核心设备保持最小化专注于硬件相关的控制与通信。Auxiliary Bus 的关键设计哲学体现在以下三点每个 auxiliary_device 代表父设备功能的一部分通用行为可以通过将其封装进领域专属结构、配合.ops回调来扩展和特化总线上的设备之间不共享任何结构与父设备的通信通道由具体领域自行定义ops 不是向子模块导出父模块公共基础设施的机制导出基础设施应使用EXPORT_SYMBOL_NS()。何时使用 Auxiliary Bus而非平台总线或 MFDDOC: USAGE给出了严格的使用判定条件当一个驱动与一个或多个共享公共头文件的内核模块需要一种机制来连接并访问由 auxiliary_device 注册驱动分配共享对象时就应当使用辅助总线。注册方与驱动方可以来自同一子系统也可以来自多个子系统。判定要点无物理总线依赖拆分出的子设备不依赖物理总线、寄存器访问或 regmap 支持不能挂在平台总线platform bus上这些拆分设备不是受 DT/ACPI 控制的物理设备不能用 MFDMFD 依赖各功能设备是物理设备而 auxiliary_device 是纯软件构造的逻辑设备。一个典型范例PCI 网络设备具备 RDMA 能力PCI 驱动为网卡上的每个 Physical Function 分配并注册一个 auxiliary_deviceRDMA 子系统注册的 auxiliary_driver 负责认领这些子设备从而把父设备/驱动发布的 data/ops 传递给 RDMA 驱动。另一类场景是 PCI 设备的 Sub Function 拆分每个 Sub Function 创建一个 auxiliary_device由 PCI Sub Function 驱动绑定再创建自己的 class 设备这类辅助设备通常被封装进带有 Sub Function 编号、资源等附加属性的结构体这些属性可被 systemd/udev 使用因此必须在驱动绑定 auxiliary_device之前初始化完毕。二、核心数据结构auxiliary_device 与 auxiliary_device_idstruct auxiliary_device定义于 include/linux/auxiliary_bus.h#L145-L155struct auxiliary_device { struct device dev; const char *name; u32 id; struct { struct xarray irqs; struct mutex lock; /* Synchronize irq sysfs creation */ bool irq_dir_exists; } sysfs; void *registration_data_rust; };各字段含义字段说明dev内嵌的标准 device 结构其release与parent字段必须由注册方填充name匹配名供 auxiliary 驱动匹配使用id唯一标识符用于区分同名导出的多个设备sysfs.irqsxarray保存设备使用的 IRQ 索引sysfs.lock同步 irq sysfs 创建的互斥锁sysfs.irq_dir_exists标记irqs目录是否已存在registration_data_rust注册父驱动持有的私有数据在设备注册于 driver core 期间有效命名与匹配规则name与注册驱动的KBUILD_MODNAME组合成match_name用于驱动绑定match_name再与id组合成注册到总线子系统的唯一设备名。例如驱动模块名为foo_mod.ko、子设备名为foo_dev则match_name foo_mod.foo_dev注册设备名 foo_mod.foo_dev.idid 为数字若两个具有相同 match_name如foo_mod.foo_dev的 auxiliary_device 同时注册则它们的id必须唯一否则device_add会失败并报错。struct auxiliary_device_id定义于 include/linux/device-id/auxiliary.h#define AUXILIARY_NAME_SIZE 40 #define AUXILIARY_MODULE_PREFIX auxiliary: struct auxiliary_device_id { char name[AUXILIARY_NAME_SIZE]; kernel_ulong_t driver_data; };AUXILIARY_MODULE_PREFIX即auxiliary:用于 uevent 的MODALIAS变量生成驱动模块自动加载依赖于此driver_data可由驱动用于携带每个设备匹配项的自定义数据。三、注册一个 Auxiliary Device三步流程与两步注销DOC: DEVICE_LIFESPAN强调了一个与平台总线截然不同的核心差异注册驱动是分配 auxiliary_device 内存并注册到总线的主体它全权负责设备对象的内存管理。设备内存的释放发生在注册驱动定义的release()回调中。注册三步流程第一步定义/分配 struct auxiliary_device并填充关键字段#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;三个强制约束dev.release或dev.type.release必须是非 NULL 指针否则注册失败。release()中必须释放与该 auxiliary device 关联的全部资源——设备一旦挂上总线父驱动无法得知其他代码是否仍持有引用dev.parent应被设置通常指向注册驱动的设备name必须是 auxiliary 驱动能识别的名字id保证同名设备唯一。第二步调用auxiliary_device_init()// Step 2: if (auxiliary_device_init(my_aux_dev)) goto fail;实现见 drivers/base/auxiliary.c#L275-L293该函数检查dev-parent与name是否为 NULL任一为空返回-EINVAL然后执行device_initialize()并初始化sysfs.lock。此步骤之后任何错误路径都必须调用auxiliary_device_uninit()若auxiliary_device_init()本身返回错误则device_initialize未执行调用方需直接在错误路径释放已分配的内存。第三步调用auxiliary_device_add()// Step 3: if (auxiliary_device_add(my_aux_dev)) { auxiliary_device_uninit(my_aux_dev); goto fail; }auxiliary_device_add是宏自动把调用方的KBUILD_MODNAME作为modname传入真正的实现__auxiliary_device_add()。见 drivers/base/auxiliary.c#L315-L336int __auxiliary_device_add(struct auxiliary_device *auxdev, const char *modname) { struct device *dev auxdev-dev; int ret; if (!modname) { dev_err(dev, auxiliary device modname is NULL\n); return -EINVAL; } ret dev_set_name(dev, %s.%s.%d, modname, auxdev-name, auxdev-id); ... ret device_add(dev); ... }可以看到设备名正是%s.%s.%dmodname.name.id格式device_add()成功后设备即出现在总线上并触发驱动匹配。注销两步流程注销与注册镜像对称先调auxiliary_device_delete()再调auxiliary_device_uninit()auxiliary_device_delete(my_dev-my_aux_dev); auxiliary_device_uninit(my_dev-my_aux_dev);这两个辅助函数是内联实现include/linux/auxiliary_bus.h#L243-L252auxiliary_device_delete()调用device_del()auxiliary_device_uninit()销毁sysfs.lock并调用put_device()——正是这个put_device()会在引用计数归零时触发release()回调完成内存释放。四、内存模型与生命周期管理规范DOC: DEVICE_LIFESPAN还规定了共享对象shared object的存活规则父对象在共享头文件中定义内含 auxiliary_device并持有指向共享对象的指针父对象与共享对象都由注册驱动分配。这种布局允许 auxiliary_driver 的注册模块在 probe 回调中通过container_of()从 auxiliary_device 指针回溯到父对象进而访问共享对象共享对象的内存存活期必须 ≥ auxiliary_device 的内存存活期。auxiliary_driver 只有在 auxiliary_device 仍注册于总线期间才能认为共享对象有效共享对象在设备生命周期之外是释放还是保留由注册驱动决定注册驱动必须在其自身driver.remove()完成之前注销全部 auxiliary device。推荐方式是用devm_add_action_or_reset()在父设备上注册一个注销 auxiliary device 的动作最后任何作用于 auxiliary device 的操作在注册驱动注销该设备之后必须仍然可用哪怕只是返回错误以避免悬垂调用。内核为此也提供了托管创建辅助见 drivers/base/auxiliary.c#L477-L497 的__devm_auxiliary_device_create()它调用auxiliary_device_create()创建设备再用devm_add_action_or_reset()挂上auxiliary_device_destroy动作父设备移除时自动完成注销省去了手动管理remove()顺序的麻烦。宏接口为#define devm_auxiliary_device_create(dev, devname, platform_data) \ __devm_auxiliary_device_create(dev, KBUILD_MODNAME, devname, \ platform_data, 0)此外 drivers/base/auxiliary.c#L503-L507 提供dev_is_auxiliary()通过比较dev-bus auxiliary_bus_type判断一个 device 是否为 auxiliary 设备常用于通用代码中对设备类型做分流。五、编写 Auxiliary Driverprobe/remove 与模块注册struct auxiliary_driver定义于 include/linux/auxiliary_bus.h#L193-L202struct 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; };Auxiliary 驱动遵循标准驱动模型惯例枚举由核心driver core完成驱动只需提供probe()/remove()并支持标准约定的电源管理suspend/resume与shutdown通知。注册方式为auxiliary_driver_register()宏自动填充THIS_MODULE与KBUILD_MODNAME其实现__auxiliary_driver_register()drivers/base/auxiliary.c#L350-L375会用WARN_ON校验probe与id_table非空用kasprintf生成驱动名%s.%smodname.name未设 name 时仅 modname填充driver.owner、driver.bus auxiliary_bus_type后调用driver_register()。驱动侧的标准写法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 };对于无需在模块 init/exit 做特殊处理的驱动可以直接使用module_auxiliary_driver()宏include/linux/auxiliary_bus.h#L292-L293它展开为module_driver(__auxiliary_driver, auxiliary_driver_register, auxiliary_driver_unregister)取代module_init()/module_exit()每个模块只能使用一次module_auxiliary_driver(my_drv);底层匹配与绑定机制总线类型定义于 drivers/base/auxiliary.c#L249-L256其.match、.uevent、.probe、.remove、.shutdown回调全部由内核实现匹配auxiliary_match_id取设备名dev_name()用strrchr定位最后一个.以该点之前的字符串为前缀与 id_table 中的name逐项比对长度相等且前缀相同即命中。这也是为什么驱动侧 id_table 写foo_mod.foo_dev而设备实际名是foo_mod.foo_dev.0——匹配只比较最后一个.之前的部分uevent生成MODALIASauxiliary:前缀供 udev/modprobe 自动加载对应驱动模块总线 probe先dev_pm_domain_attach()尝试挂接电源域再调用驱动的probe(auxdev, id)并把匹配到的 id 一并传给驱动remove/shutdown分别调用驱动的remove()/shutdown()回调若存在。总线本身由 drivers/base/init.c#L39 中的auxiliary_bus_init()在驱动核心初始化阶段注册并受 drivers/base/Kconfig 中的config AUXILIARY_BUS控制。六、完整实战模板父设备封装 自定义 opsDOC: EXAMPLE给出了一个完整的端到端示例。父设备先把 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成员name与父模块KBUILD_MODNAME组成 match_name 用于匹配。每当 auxiliary_driver 注册时根据 match_name 触发匹配设备的probe()。驱动侧可通过封装来扩展核心设备功能——在 auxiliary_driver 外层叠加领域专属 opsstruct 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, };文档同时对这种自定义 ops 方案给出了重要警告该做法难以在没有每设备全局锁的情况下保证正确性无法防止调用 ops 期间 auxiliary_drv 被卸载且缺乏模块依赖容易引发父模块与子设备模块之间的加载/卸载竞态。最稳妥的方式是直接用EXPORT_SYMBOL*()导出这些 ops依托既有模块基础设施保证其有效性与依赖链正确。七、内核真实案例Sound Open FirmwareSOFClient 设备辅助总线最典型的生产级应用之一是 Sound Open Firmware 子系统的 client 设备机制实现于 sound/soc/sof/sof-client.c。SOF 核心设备把调试与扩展功能拆分成ipc_flood、msg_injector、kernel_injector等 client 设备每个 client 就是一个 auxiliary_device。设备侧父驱动sof_client_dev_register()完整展示了前文的三步流程sound/soc/sof/sof-client.c#L225-L280先填充auxdev-name、auxdev-dev.parent、auxdev-dev.release、auxdev-id再依次调用auxiliary_device_init()与auxiliary_device_add()失败路径分别对应直接释放与auxiliary_device_uninit()触发 release 释放两种处理与文档规范完全一致。其release()回调sof_client_auxdev_release()sound/soc/sof/sof-client.c#L64-L72负责释放platform_data与封装结构内存。驱动侧client 模块以 IPC 消息注入器 sound/soc/sof/sof-client-ipc-msg-injector.c 为例其匹配表与模块注册正是标准模板的直接应用static const struct auxiliary_device_id sof_msg_inject_client_id_table[] { { .name snd_sof.msg_injector }, ... }; MODULE_DEVICE_TABLE(auxiliary, sof_msg_inject_client_id_table); ... .probe sof_msg_inject_probe, .remove sof_msg_inject_remove, ... module_auxiliary_driver(sof_msg_inject_client_drv);注意 id_table 中的snd_sof.msg_injector正是设备名snd_sof.msg_injector.0去掉最后一个.后缀后的前缀印证了上文只匹配最后一个.之前的部分的实现细节。此外 sound/soc/sof/sof-client.c#L395-L439 的sof_suspend_clients()/sof_resume_clients()通过to_auxiliary_drv()拿到已绑定驱动的suspend/resume回调并遍历调用展示了父驱动如何在运行时管理子设备驱动的电源状态。八、总结与最佳实践清单选型需要把无物理设备形态、不依赖 DT/ACPI 的功能子域拆给独立模块驱动时选择 Auxiliary Bus不要用它模拟平台设备或 MFD 场景。命名match_name 模块名.设备名注册名 模块名.设备名.id同名设备必须用唯一id区分。内存auxiliary_device 内存由注册驱动分配、在release()中释放共享对象存活期必须 ≥ 设备存活期在driver.remove()完成前注销全部子设备推荐devm_add_action_or_reset或devm_auxiliary_device_create。错误路径auxiliary_device_init()失败直接释放内存其成功后失败必须走auxiliary_device_uninit()。扩展机制优先用EXPORT_SYMBOL_NS()导出共享基础设施自定义 ops 需注意并发卸载风险与模块依赖链。辅助能力dev_is_auxiliary()判断设备类型module_auxiliary_driver()简化驱动模块样板代码auxiliary_device_sysfs_irq_add/remove()管理 IRQ 的 sysfs 呈现。深入阅读入口总线实现 drivers/base/auxiliary.c、API 头文件 include/linux/auxiliary_bus.h、匹配结构 include/linux/device-id/auxiliary.h、驱动核心注册点 drivers/base/init.c以及生产级用例 sound/soc/sof/sof-client.c 与 sound/soc/sof/sof-client-ipc-msg-injector.c。【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表