当确定需要设备驱动程序时,它们会被加载到驱动程序宿主进程中。驱动程序是否加载取决于绑定程序,该程序描述了驱动程序可以绑定到哪些设备。绑定程序使用一种小型特定于网域的语言定义,该语言会被编译为与驱动程序一起分发的字节码。
Intel 以太网驱动程序的绑定程序示例:
fuchsia.device.protocol == fuchsia.pci.protocol.PCI_DEVICE;
fuchsia.pci.vendor == fuchsia.pci.vendor.INTEL;
accept fuchsia.pci.device {
0x100E, // Qemu
0x15A3, // Broadwell
0x1570, // Skylake
0x1533, // I210 standalone
0x15b7, // Skull Canyon NUC
0x15b8, // I219
0x15d8, // Kaby Lake NUC
}
绑定编译器会获取绑定程序,并输出一个定义了宏 ZIRCON_DRIVER 的 C 头文件。ZIRCON_DRIVER 宏包含必要的编译器指令,可将绑定程序放入 ELF NOTE 部分,从而允许设备协调器检查该程序,而无需将驱动程序完全加载到其进程中。
ZIRCON_DRIVER 的第二个参数是 zx_driver_ops_t 结构指针(由 lib/ddk/driver.h 定义,用于定义 init、bind、create 和 release 方法)。
当驱动程序加载到驱动程序宿主进程中时,会调用 init(),并允许进行任何全局初始化。通常不需要。如果实现了 init() 方法但该方法失败,则驱动程序加载也会失败。
调用 bind() 以向驱动程序提供要绑定的设备。设备已与驱动程序发布的绑定规则相匹配。如果 bind() 方法成功,驱动程序必须创建一个新设备,并将其添加为传递给 bind() 方法的设备的子设备。如需了解详情,请参阅“设备生命周期”。
create() 针对平台/系统总线驱动程序或代理驱动程序调用。对于绝大多数驱动程序,此方法不是必需的。
release() 在驱动程序卸载之前调用,此时驱动程序在 bind() 和其他位置可能创建的所有设备都已被销毁。目前,此方法永远不会被调用。驱动程序一旦加载,就会在驱动程序宿主进程的整个生命周期内保持加载状态。
设备生命周期
在驱动程序宿主进程中,设备以 zx_device_t 结构的树形式存在,这些结构对驱动程序是不透明的。这些对象是使用 device_add() 创建的,驱动程序会为该对象提供 zx_protocol_device_t 结构。此结构中的函数指针所定义的方法是“设备操作”。各种结构和函数在 device.h 中定义
device_add() 函数会创建一个新设备,并将其添加为所提供父设备的子设备。该父设备必须是传递给设备驱动程序的 bind() 方法的设备,或者是同一设备驱动程序创建的另一设备。
device_add() 的一个副作用是,新创建的设备将被添加到由设备协调器维护的全局设备文件系统中。如果设备未实现 init() 钩子,则可立即访问设备。
init() 钩子在 device_add() 之后调用。对于必须执行扩展初始化或探测的驱动程序,这非常有用,因为这些驱动程序不希望在成功之前公开其设备(如果失败,则会悄悄移除这些设备)。驱动程序应在完成初始化后调用 device_init_reply()。此回复不一定需要从 init() 钩子中调用。设备将保持不显示状态,并且保证在此时间点之前不会被移除。
设备是引用计数的。当驱动程序使用 device_add() 创建设备时,以及当远程进程通过设备文件系统打开设备时,会获取引用。
从调用 device_init_reply() 或在未实现 init() 钩子的情况下调用 device_add() 的那一刻起,驱动程序主机可能会调用其他设备操作。
当在设备上调用 device_async_remove() 时,系统会安排移除该设备及其后代。
移除设备包括四个部分:运行设备的 unbind() 钩子、从设备文件系统中移除设备、舍弃 device_add() 获取的引用以及运行设备的 release() 钩子。
调用 unbind() 方法时,会向驱动程序发出信号,表明它应开始关闭设备,并在完成解除绑定后调用 device_unbind_reply()。解除绑定也充当 FIDL 交易的硬屏障。当调用 Unbind 时,FDF 不允许创建任何新的 FIDL 交易或连接。如果驱动程序处理 FIDL 消息,则负责在解绑钩子中关闭或回复任何未完成的交易。
这是一个可选的钩子。如果未实现,则视为立即调用了 device_unbind_reply()。当调用 device_unbind_reply 时,所有 FIDL 连接都将终止。
仅在创建驱动程序完成取消绑定、相应设备的所有打开实例都已关闭,以及相应设备的所有子级都已取消绑定并释放后,才会调用 release() 方法。这是驱动程序销毁或释放与设备关联的任何资源的最后机会。在 release() 返回后,无法再参考相应设备的 zx_device_t。在此之后,调用从父设备获取的协议的任何设备方法或协议方法都是非法的,很可能会导致崩溃。
拆解序列示例
为了说明 unbind() 和 release() 在拆解过程中的工作方式,下面举例说明了 USB WLAN 驱动程序通常会如何处理。简而言之,unbind() 和 release() 调用序列是自下而上的。
请注意,这只是一个示例。这可能与实际 WLAN 驱动程序所做的事情不完全一致。
假设 WLAN 设备作为 USB 设备插入,并且已在 USB 设备下创建 PHY 接口。除了 PHY 接口之外,还在 PHY 接口下创建了 2 个 MAC 接口。
+------------+
| USB Device | .unbind()
+------------+ .release()
|
+------------+
| WLAN PHY | .unbind()
+------------+ .release()
| |
+------------+ +------------+
| WLAN MAC 0 | | WLAN MAC 1 | .unbind()
+------------+ +------------+ .release()
现在,我们拔下此 USB WLAN 设备。
USB XHCI 检测到移除操作并调用
device_async_remove(usb_device)。系统将对其所有叶后代(wlan_mac_0、wlan_mac_1)调用 unbind()。
wlan_mac_unbind(void* ctx) {
// Cancel any outstanding requests.
...
device_unbind_reply(iface_mac_X);
}
当设备的解除绑定完成时,系统会调用其 release() 方法。
调用 WLAN MAC 0 和 1 的
release()。
wlan_mac_release(void* ctx) {
// Release sources allocated at creation.
...
// Delete the object here.
...
}
- 当 WLAN PHY 的子级被移除后,会调用 WLAN PHY 的
unbind()。一旦完成取消绑定,它就会调用device_unbind_reply()。
wlan_phy_unbind(void* ctx) {
// Stop interrupt and cancel any outstanding work.
...
device_unbind_reply(wlan_phy);
}
- 一旦其取消绑定例程完成,就会调用 WLAN PHY 的
release()。
wlan_phy_release(void* ctx) {
// Release sources allocated at creation.
...
// Delete the object here.
...
}
- 这将导致调用 USB 设备的
unbind()。 一旦完成取消绑定,它就会调用device_unbind_reply()。
usb_device_unbind(void* ctx) {
// Stop interrupt cancel any outstanding work.
...
device_unbind_reply(usb_dev);
}
- 最后,系统会调用 USB 设备的
release()。