关注了就能看到更多这么棒的文章哦~
Managing multifunction devices with the auxiliary bus
December 17, 2020
This article was contributed by Marta Rybczyńska
DeepL assisted translation
https://lwn.net/Articles/840416/
设备驱动程序通常都是属于某一个内核子系统中的,一般不会属于两个子系统。然而,有时开发人员需要处理意外情况。例如,我们有一个网络接口卡(NIC),它就同时具有以太网和 RDMA 功能。这里只有一个硬件,但是会有两个驱动程序来实现两个不同功能。这些驱动程序需要在各自的子系统中工作,但它们也必须共享对同一硬件的访问。目前的内核中没有标准的方法来将这些驱动程序合并起来,所以开发者发明了一些临时的方法来处理它们之间的交互。最近,Dave Ertman 发布了一组 patch set,介绍了一种新的总线类型,称为 "auxiliary bus",希望解决这个问题。
Complex devices
Linux 已经包含了许多驱动程序用来支持同时具有多种功能的设备(multi-function device)。支持方法之一就是 Multi-Function Devices(MFD)子系统。它将多个独立的设备 "粘合" 在一起,成为一个硬件模块,大家共享一些资源。MFD 允许直接访问设备的寄存器,也允许使用一个普通总线(common bus)。在使用 common bus 的情况下,它可以让我们很方便地在 Inter-Integrated Circuit(I2C)或 Serial Peripheral Interface(SPI)总线上安全地管理多路访问。由于 MFD 子设备都是互相独立的,所以 MFD 驱动程序不会共享公共状态。
Ertman 想支持的这些设备都不能简单使用 MFD 方式。使用 auxiliary bus 的设备会把某个硬件设备的一部分功能提供出来。因为不是针对每种功能都暴露完全不同的一批寄存器,因此它们不能使用 devicetrees 描述,也不能通过 ACPI 来进行识别。它们的驱动程序需要共享访问硬件。那些跟所有子功能(如电源管理)都有关的 event 需要各个驱动程序都能正确处理。这些设备通常是运行 firmware 的一些专用处理器,并且通过消息传递方式来与 host 系统(以及 Linux 驱动程序)进行通信。具体有哪些功能可用,事先都可能并不知道,因此必须在运行起来后进行识别(discovery)。
auxiliary bus 这组 patch 中的文档里面介绍了一些例子。比如 Sound Open Firmware (SOF)驱动程序 所管理的这个硬件设备中可能会提供 HDMI 输出、麦克风、扬声器、测试和调试 hook 等接口。还有同时实现了 Ethernet 和 RDMA 的网卡(NIC)会首先需要一个能支持公共部分功能的驱动程序,然后针对特定的以太网和 RDMA 功能的驱动程序就在此基础上来实现相应的功能。
目前的内核中没有一种通用的方法来描述这种设备的多个驱动程序之间的依赖关系。解决这个问题的方法可以是通过某种方式来将辅助驱动(secondary driver)附加(attach)到主驱动上,这正是 auxiliary bus 所实现的。
Auxiliary devices and drivers
patch set 中引入了两个主要概念:"auxiliary device"和 "auxiliary driver"。它们分别对应了主驱动(main driver)和辅助驱动(secondary driver)。主驱动维护设备的状态,分配和管理所有的共享数据。在关闭设备时,它还会 unregister 所有的辅助驱动。辅助驱动程序则用来处理与它们所针对的特定子系统的交互。
每个主驱动可以为辅助驱动暴露一些功能(devices)。每个功能只能属于一个辅助驱动程序。
主驱动会创建辅助设备,这是由 struct auxiliary_device 表示的:
struct auxiliary_device {
struct device dev;
const char *name;
u32 id;
};
name 和 id 的组合必须是唯一的,设备的完整名称是 module name 和这两个字段的组合,用点(.)来连接。这样就会得到一个类似 modname.device_name.id 的名字。
开发者将这个结构填入到主驱动的 device structure 中,还需要指明主驱动和辅助驱动之间的通信所需的所有共享数据。还可以加一些 callback 函数。
初始化主驱动的流程包含两个步骤。第一步是调用 auxiliary_device_init():
int auxiliary_device_init(struct auxiliary_device *auxdev);
这个函数会验证参数,必要时返回一个错误代码,在这种情况下,设备的初始化就应该被中止。如果这个调用成功了,第二步就是对初始化后的设备来调用 macro auxiliary_device_add(),这就是在设置设备名称并注册设备本身。
unregistration 的过程也有两步,包括对 auxiliary_device_uninit()的调用(auxiliary_device_init()成功的话就需要)和 auxiliary_device_delete()。这些函数的原型如下:
void auxiliary_device_uninit(struct auxiliary_device *auxdev);
void auxiliary_device_delete(struct auxiliary_device *auxdev);
这个两步操作是根据早期版本 patch set 所收到的 review 意见而实现的。因为这样做就允许驱动程序在 auxiliary_device_init()和 auxiliary_device_add()之间对自己的数据进行 allocation,也可以在失败的情况下正确 free 数据。
辅助设备将连接到主驱动,由 struct auxiliary_driver 表示:
struct auxiliary_driver {
int (*probe)(struct auxiliary_device *auxdev,
const struct auxiliary_device_id *id);
int (*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;
};
这个结构里包含一些回调函数来管理此设备的生命周期,而 id_table 则包含了此 driver 能 bind 的所有 device 的名称。所有的回调函数收到的都是指向父设备的 auxiliary_device 的指针,这样就可以访问共享数据了。
辅助设备是通过 auxiliary_driver_register()来设置的:
int auxiliary_driver_register(struct auxiliary_driver *auxdrv);
这个函数会要求 probe() callback 和 id_table 这两项都设置好了。当调用成功时,它会调用所有匹配的设备的 probe() callback。辅助设备可以使用 container_of()和 auxiliary_device 结构来访问公共数据。
当 unregister 一个驱动程序时,开发者应该调用 auxiliary_driver_unregister():
void auxiliary_driver_unregister(struct auxiliary_driver *auxdrv)。
First users
在实现 auxiliary bus 的同时,Ertman 发布了对 SOF 驱动的改动。修改后的驱动利用 auxiliary bus 实现了一个测试用的驱动和一个 probe 驱动,允许创建一个新的虚拟音频设备并接入 pipeline,允许在任意节点来进行监听。
另一个使用场景是在网络子系统中;Leon Romanovsky 发布了 mlx5 驱动程序改为使用 auxiliary bus 的版本。更新后的驱动程序为单个物理设备创建了网络、VDPA 以及 RDMA 的驱动程序。进行这些改动之后可以删除掉特定驱动里的许多代码。Parav Pandit 后续还使用这个功能来实现了 device sub-function。
这组 patch-set 已经到了第四个版本,此前更早的版本里面名字还是 ancillary bus 或者 virtual bus。auxiliary bus 这组 patch set 的开发花费了不少时间,而且也开始有其他一些工作在依赖它的了。这给推进 upstream 的流程造成了相当大的压力,这也导致了列表上的一些催促。为了能推动它,Dan Williams 重新发布了这组 patch,并表示 "在我和其他几个利益相关者看来,它很不错"。经过 Greg Kroah-Hartman 的 review 后,auxiliary bus 的代码被合并到了 5.11 内核版本的 mainline 中。
全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。
欢迎分享、转载及基于现有协议再创作~
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~