设备驱动程序生命周期

确定需要设备驱动程序时,会将其加载到驱动程序主机进程中。确定它们是否加载的取决于绑定程序,该程序说明了驱动程序可以绑定到什么设备。绑定程序使用小域特定语言进行定义,该语言被编译为随驱动程序分发的字节码。

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()。对于绝大多数驱动程序,此方法不是必需的。

在卸载驱动程序之前,系统会在驱动程序在 bind() 和其他位置创建的所有设备均被销毁之后调用 release()。目前,此方法从未调用过。驱动程序一经加载,便会在驱动程序主机进程的生命周期内保持加载状态。

设备生命周期

在驱动程序主机进程中,设备以 zx_device_t 结构树的形式存在,这些结构对驱动程序来说是不透明的。这些映像是使用 device_add() 创建的,驱动程序会提供 zx_protocol_device_t 结构。此结构中的函数指针定义的方法是“设备操作”。device.h 中定义了各种结构和函数

device_add() 函数会创建新设备,并将其作为子级添加到提供的父级设备。该父设备必须是传入设备驱动程序的 bind() 方法的设备,或者由同一设备驱动程序创建的其他设备。

device_add() 的附带效应是,新创建的设备将添加到由设备协调器维护的全局设备文件系统中。如果设备未实现 init() 钩子,可立即通过在 devfs 中打开其节点来访问设备。

device_add() 之后调用 init() 钩子。对于需要执行扩展初始化或探测,并且不想在成功之前以可见方式发布其设备(并在失败时静默移除)的驱动程序,此功能非常有用。完成初始化后,驱动程序应调用 device_init_reply()。此回复不一定需要从 init() 钩子调用。该设备将保持不可见状态,并且在此之前不会将其移除。

设备会计入参考计数。当驱动程序使用 device_add() 创建设备时,以及当远程进程通过设备文件系统打开设备时,系统会获取引用。

从调用 device_init_reply() 或在未实现 init() 钩子的情况下调用 device_add() 的那一刻起,驱动程序主机可能会调用其他设备操作。

在设备上调用 device_async_remove() 时,此操作会安排移除设备及其后代。

移除设备的流程包括四部分:运行设备的 unbind() 钩子、从设备文件系统中移除设备、丢弃通过 device_add() 获取的引用,以及运行设备的 release() 钩子。

调用 unbind() 方法时,这会告知驱动程序应该开始关闭设备,并在完成取消绑定后调用 device_unbind_reply()。Unbind 还可以充当 FIDL 交易的硬屏障。调用 Unbind 时,FDF 不允许创建任何新的 FIDL 事务或连接。如果处理 FIDL 消息,驱动程序应负责关闭或回复解除绑定钩子中的任何待处理交易。这是一个可选的钩子。如果未实现,系统会将其视为立即调用 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 接口之外,我们还在 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)

  • 这会调用 USB 设备的 unbind()。完成解除绑定后,它会调用 device_unbind_reply()

    usb_device_unbind(void* ctx) {
        // Stop interrupt or anything to prevent incoming requests.
        ...

        device_unbind_reply(usb_dev);
    }
  • 当 USB 设备完成解除绑定后,系统会调用 WLAN PHY 的 unbind()。完成解除绑定后,它会调用 device_unbind_reply()
    wlan_phy_unbind(void* ctx) {
        // Stop interrupt or anything to prevent incoming requests.
        ...

        device_unbind_reply(wlan_phy);
    }
  • 当 wlan_phy 完成解除绑定后,将对其所有子级(wlan_mac_0、wlan_mac_1)调用 unbind()。
    wlan_mac_unbind(void* ctx) {
        // Stop accepting new requests, and notify clients that this device is offline (often just
        // by returning a ZX_ERR_IO_NOT_PRESENT to any requests that happen after unbind).
        ...

        device_unbind_reply(iface_mac_X);
    }
  • 一旦设备的所有客户端都被移除,并且该设备没有子节点,其引用计数将变为 0,并且系统将调用其 release() 方法。

  • 调用 Wi-Fi MAC 0 和 1 的 release()

    wlan_mac_release(void* ctx) {
        // Release sources allocated at creation.
        ...

        // Delete the object here.
        ...
    }
  • wlan_phy 没有打开的连接,但仍有子设备(wlan_mac_0 和 wlan_mac_1)。 当这两者被释放后,其引用计数最终会降至零,并且系统会调用其 release() 方法。
    wlan_phy_release(void* ctx) {
        // Release sources allocated at creation.
        ...

        // Delete the object here.
        ...
    }
  • 当 USB 设备现在没有子设备或打开的连接时,系统会调用其 release()