设备驱动生命周期

当需要时,设备驱动被加载到驱动主机进程中。而决定它们是否加载取决于绑定程序,它是一个设备驱动绑定关系的描述。绑定程序使用一个小范围定义语言来定义,然后编译成比特码分发到驱动中。

下述为一个网络以太网驱动的绑定程序示例:

  1. fuchsia.device.protocol == fuchsia.pci.protocol.PCI_DEVICE;
  2. fuchsia.pci.vendor == fuchsia.pci.vendor.INTEL;
  3. accept fuchsia.pci.device {
  4. 0x100E, // Qemu
  5. 0x15A3, // Broadwell
  6. 0x1570, // Skylake
  7. 0x1533, // I210 standalone
  8. 0x15b7, // Skull Canyon NUC
  9. 0x15b8, // I219
  10. 0x15d8, // Kaby Lake NUC
  11. }

绑定编译器获取到绑定程序后以 C 头文件定义宏ZIRCON_DRIVER的形式输出。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 结构体树形式存在,并对驱动不可见。使用驱动提供 zx_protocol_device_t结构体,调用device_add()方法创建设备。该方法在这个结构体中以”device ops“函数指针的形式定义。更多结构体和函数定义参见device.h

device_add() 函数创建了一个新的设备,并作为一个子设备添加到提供的父设备中。父设备必须是要么通过bind() 传递到设备驱动的设备,要么是另一个被同样设备驱动创建的设备。

device_add()的副作用,则是新创建的设备将被添加到由设备协调程序维护的全局设备文件系统中。如果这个设备没有实现init() 钩函数,那么设备将在通过 devfs ,在打开节点时而被立即访问。

init()钩函数在 device_add()之后被调用。对驱动来说,必须经过扩展初始化或者检查,并且想在设置成功(如果失败则悄悄移除)之前对外部不可见是非常有用的。驱动在它们完成初始化时需要调用device_init_reply()。这个回复不一定需要从init()钩函数中调用。设备将保持不可见,并保证在这之前不会被移除。

设备是参考计数的。当驱动使用device_add()创建设备时,和设备被远端进程通过设备文件系统打开时,就会获得一个引用。

当调用device_init_reply(), 或者没有实现init()钩函数而调用device_add() 的那一刻起,其他设备操作就可以被驱动主机调用了。

device_async_remove()在设备上被调用时,就会规划移除该设备及其子设备。

移除设备包含4个步骤:运行设备unbind()钩函数,从设备文件系统移除设备,移除device_add()获取的引用和运行设备release()钩函数。

unbind()方法被调用时,这标志着驱动开始关闭设备,然后在完成解绑时调用device_unbind_reply()

解绑也作为 FIDL 传输的一个硬屏障。

当解绑被调用后, FDF 将不再允许创建任何新的 FIDL 传输或者连接。如果驱动处理 FIDL 消息,则需要负责关闭或回复在 unbind 函数中的任何未完成的工作。

这是一个可选的钩函数。如果没有实现它的话,将被视为 device_unbind_reply()立即调用。当device_unbind_reply 被调用时,所有 FIDL 连接都将被中断。

因为可能存在在unbind() 被调用时,子设备正在进程内运行,所以父设备有可能(已经完全解绑)可以代表它的子设备继续接收设备方法调用或者协议方法调用。建议在完成解绑之前,父设备就应该安排这些方法返回错误,这样,在子设备移除完成之前,来自子设备的调用就不会启动更多的任务或导致意外的交互。

release()方法仅在创建驱动完成解绑后调用,所有设备的打开实例都被关闭,并且所有子设备已经解绑和销毁。这是驱动销毁或者释放设备相关资源的最后机会。在release()返回后,引用该设备的zx_device_t 结构体是无效的。调用任意的设备方法或从父设备获取协议方法传递指针都是非法的,这很可能导致崩溃。

销毁顺序示例

为了解释说明unbind()release()在销毁过程中是怎样工作的,下述是一个在 USB WLAN 驱动中处理的示例。简而言之,unbind()的调用顺序是自上而下的, release() 的顺序则是自底向上的。

注意,这仅仅是一个示例。这与实际 WLAN 驱动怎样工作可能不一致。

假设一个 WLAN 设备作为 USB 设备插入,然后在 USB 设备下已经创建一个 PHY 接口。除了 PHY 接口,2个 MAC 接口也会在 PHY 接口下被创建。

  1. +------------+
  2. | USB Device | .unbind()
  3. +------------+ .release()
  4. |
  5. +------------+
  6. | WLAN PHY | .unbind()
  7. +------------+ .release()
  8. | |
  9. +------------+ +------------+
  10. | WLAN MAC 0 | | WLAN MAC 1 | .unbind()
  11. +------------+ +------------+ .release()

现在,我们要拔出这个 USB WLAN 设备

  • USB XHCI 监测到设备移除并调用device_async_remove(usb_device)

  • 这将导致USB 设备中unbind()函数将被调用。 一旦完成解绑,它会调用device_unbind_reply()

  1. usb_device_unbind(void* ctx) {
  2. // Stop interrupt or anything to prevent incoming requests.
  3. ...
  4. device_unbind_reply(usb_dev);
  5. }
  • 当 USB 设备完成解绑后, WLAN PHY unbind()函数被调用。 一旦完成解绑,它会调用device_unbind_reply()

    1. wlan_phy_unbind(void* ctx) {
    2. // Stop interrupt or anything to prevent incoming requests.
    3. ...
    4. device_unbind_reply(wlan_phy);
    5. }

    <!—-

  • When wlan_phy completes unbinding, unbind() will be called on all of its children (wlan_mac_0, wlan_mac_1).

—->

  • 当 wlan_phy 完成解绑后,unbind() 将在它所有的子设备(wlan_mac_0, wlan_mac_1)上被调用。
  1. wlan_mac_unbind(void* ctx) {
  2. // Stop accepting new requests, and notify clients that this device is offline (often just
  3. // by returning an ZX_ERR_IO_NOT_PRESENT to any requests that happen after unbind).
  4. ...
  5. device_unbind_reply(iface_mac_X);
  6. }
  • 一旦所有设备的客户端都完成移除后,设备就没有子设备了, 它的 refcount 将被清零,并且release()方法被调用。

  • WLAN MAC 0和1的release()被调用。

  1. wlan_mac_release(void* ctx) {
  2. // Release sources allocated at creation.
  3. ...
  4. // Delete the object here.
  5. ...
  6. }
  • wlan_phy 没有打开的连接,但是依然有子设备( wlan_mac_0 和 wlan_mac_1 )。 一旦它们都被释放后,它的 refcount 最终被清零,并且 release() 方法被调用。
  1. wlan_phy_release(void* ctx) {
  2. // Release sources allocated at creation.
  3. ...
  4. // Delete the object here.
  5. ...
  6. }
  • 一旦 USB 设备现在已经没有子设备或者打开的连接,它的 release() 接口将被调用。