设备驱动程序生命周期

确定设备驱动程序后,将其加载到驱动程序主机进程中 所需的资源。绑定程序决定了它们是否加载 是对驱动程序可绑定到什么设备的描述。绑定程序 使用一种针对特定领域的小型语言定义的,这种语言会被编译成字节码, 都随驱动程序一起分发。

以下是来自 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
}

绑定编译器会使用绑定程序输出一个 C 头文件, 定义了一个宏 ZIRCON_DRIVERZIRCON_DRIVER 宏包含 将绑定程序放入 ELF 备注所需的编译器指令 部分,以便设备协调器无需对其进行检查 将驱动程序完全加载到其进程中。

ZIRCON_DRIVER 的第二个参数是 zx_driver_ops_t 结构指针 (由 lib/ddk/driver.h 定义, 定义了 init、bind、create 和 release 方法。

init() 会在驱动程序加载到驱动程序主机进程时调用,并允许 任何全局初始化。通常不需要。如果 init() 方法是 但之后失败,驱动程序加载将会失败。

调用 bind(),为驱动程序提供要绑定的设备。该设备属于 与驱动程序发布的绑定规则匹配。如果 bind() 方法执行成功, 驱动程序必须创建新设备并将其添加为传入设备的子设备 传递给 bind() 方法。如需了解详情,请参阅“设备生命周期”。

针对平台/系统总线驱动程序或代理驱动程序调用 create()。对于 绝大多数驱动程序,则不需要使用此方法。

在驱动程序卸载前,在其可能装载的所有设备之后调用 release()bind() 和其他位置创建的文件已被销毁。目前,此方法 never 调用过。驱动程序在加载后会在驱动程序主机的生命周期内保持加载 过程。

设备生命周期

在驱动程序主机进程中,设备以 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()device_add() 调用之时起 未实现的 init() 钩子时,可由 驱动程序主机。

在设备上调用 device_async_remove() 时,系统会安排移除 设备及其后代节点的名称。

设备的移除过程包括四个部分:运行设备的 unbind() 钩子、 从设备文件系统中移除设备,从而丢弃获取的参考 由 device_add() 运行并运行设备的 release() 钩子。

调用 unbind() 方法时,这会告知驱动程序应启动的驱动程序 关闭设备,并在完成解除绑定后调用 device_unbind_reply()。 解除绑定也是 FIDL 交易的硬屏障。 FDF 将不允许任何新的 FIDL 交易或连接 调用“Unbind”时创建的任何关联由驾驶员负责 关闭或回复其 取消绑定钩子(如果它们处理 FIDL 消息)。 这是一个可选的钩子。如果未实现,则被视为 device_unbind_reply() 调用该方法。调用 device_unbind_reply 时, 所有 FIDL 连接都将终止。

由于子设备的 unbind() 方法 则父级设备(已完成 解除绑定)可能会继续接收设备方法调用或协议方法 来代表该子发布商进行调用。建议在完成解除绑定之前 父级设备应安排这些方法返回错误,以便 儿童在完成移除之前发出的调用不会启动更多 工作或引发意外交互。

只有在完成驱动程序创建操作后,系统才会调用 release() 方法 解除绑定,该设备的所有打开的实例都已关闭, 并且该设备的所有子节点均已解除绑定并释放。这个 是驱动程序销毁或释放任何相关资源的最后机会 与设备互动参考该设备的zx_device_t无效 在 release() 返回后返回。调用任何设备方法或协议方法 超过此时限后,通过家长设备获取的协议是非法的, 可能会导致崩溃。

拆解序列示例

为了说明 unbind()release() 在拆解过程中的工作原理, 下例说明了 USB WLAN 驱动程序通常会如何处理它。简而言之, unbind() 调用序列自上而下,而 release() 序列自下而上。

请注意,这只是一个示例。这可能与真正的 WLAN 驱动程序并不完全一致 具体表现

假设 WLAN 设备作为 USB 设备插入,并且已配置 PHY 接口 在 USB 设备下创建除 PHY 接口外,还有 2 个 MAC 接口 已在 PHY 接口下创建。

            +------------+
            | 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 完成解绑定后,系统会对其所有子级调用 unbind() (wlan_mac_0、wlan_mac_1)。
    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);
    }
  • 移除设备的所有客户端并且该设备不再有子设备后 其 refcount 将达到零,并且将调用其 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)。 在这两者都被释放后,其 refcount 最终将变为零,并且其 release() 方法。
    wlan_phy_release(void* ctx) {
        // Release sources allocated at creation.
        ...

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