Fuchsia 驱动程序开发 (DFv1)

Fuchsia 驱动程序是在驱动程序主机中动态加载的共享库 进程。驱动程序的加载过程由 驱动程序管理器。请参阅 设备型号 详细了解驱动程序主机、驱动程序管理器以及驱动程序和设备 生命周期

目录结构

驱动程序可以在源代码树的 driver 子目录下找到 部分指定的 源代码布局文档。大多数人 Fuchsia 驱动程序位于 //src/devices/ 下。它们会按组 进行扩缩。驱动程序协议在 ddk/include/lib/ddk/protodefs.h.对于 一个 USB 以太网驱动程序 //src/connectivity/ethernet/drivers/ 而不是//src/devices/usb/drivers/ 实现了以太网协议不过,实现 USB 堆栈的驱动程序 位于 //src/devices/usb/drivers/ 中,因为它们 实现 USB 协议。

在驱动程序的 BUILD.gn 中,应该有一个 fuchsia_driver_component 目标。 为了让驾驶员显示在 /boot/driver 下,应包含它 如位于相关板文件中的 board_bootfs_labels 列表下, //boards.要让它显示在 /system/driver 中,它应该 添加到带有 driver_package 构建目标的系统软件包中, 然后被 //boards 下的相关板级文件引用。驱动程序管理器 首先在 /boot/driver 中查找可加载驱动程序,然后在 /system/driver/ 中查找可加载的驱动程序。

创建新的驱动程序

您可以使用 创建工具。只需运行以下命令:

fx create driver --path <PATH> --lang cpp

这将创建包含空驱动程序的目录 <PATH>,其中 <PATH> 的最后一部分是驱动程序名称和 GN 目标名称。在此之后 命令,则需要执行以下步骤:

  1. 在该文件中添加 fuchsia_driver_componentdriver_package 构建目标 将司机加入系统的正确位置。
  2. 对于打包的驱动程序,应将 driver_package 构建目标添加到 将 //boards//vendor/<foo>/boards 中的相关开发板文件复制到 xx_package_labels GN 参数。
  3. 对于启动驱动程序,应添加 fuchsia_driver_component 构建目标 到 //boards//vendor/<foo>/boards 中的相关开发板文件, board_bootfs_labels GN 参数。
  4. <PATH>:tests build 目标中添加 tests build 目标以获取 包含在 CQ 中的测试。
  5. meta/<NAME>.bind 中添加适当的绑定规则。
  6. meta/<NAME>-info.json 中添加驾驶员信息。该文件必须包含 short_descriptionareas至少与以下列出的其中一个区域匹配: //build/drivers/areas.txt
  7. 为驱动程序编写功能。

声明驱动程序

驱动程序至少应包含驱动程序声明,并实现 bind() 驱动程序操作。

驱动程序管理器成功加载驱动程序并将其绑定到设备 为设备寻找匹配的驱动程序。驱动程序声明设备 通过绑定规则兼容,该规则应放在 .bind 文件中 旁边。绑定编译器会编译这些规则,并创建 驱动程序声明宏,该宏将这些规则包含在 C 头文件中。通过 以下绑定规则声明了 AHCI 驱动程序

using fuchsia.pci;
using fuchsia.pci.massstorage;

fuchsia.BIND_PROTOCOL == fuchsia.pci.BIND_PROTOCOL.DEVICE;
fuchsia.BIND_PCI_CLASS == fuchsia.pci.BIND_PCI_CLASS.MASS_STORAGE;
fuchsia.BIND_PCI_SUBCLASS == fuchsia.pci.massstorage.BIND_PCI_SUBCLASS_SATA;
fuchsia.BIND_PCI_INTERFACE == 0x01;
fuchsia.BIND_COMPOSITE == 1;

这些绑定规则规定,驱动程序通过 BIND_PROTOCOL 绑定到设备 属性,与 pci 命名空间中的 DEVICE 和给定 PCI 匹配 类/子类/接口。pci 命名空间是从 fucnsia.pci 导入的 。如需了解详情,请参阅绑定 文档

要生成包含这些绑定规则的驱动程序声明宏, 是相应的 bind_rules 构建目标。这应该会声明依赖项 这对应于“使用”语句。

driver_bind_rules("bind") {
    rules = "meta/ahci.bind"
    bind_output = "ahci.bindbc"
    deps = [
        "//src/devices/bind/fuchsia.pci",
        "//src/devices/bind/fuchsia.pci.massstorage",
    ]
}

驱动程序现在可以包含生成的标头,并使用 后续宏。"zircon" 是供应商 ID,"0.1" 是驱动程序版本。

#include <lib/ddk/binding_driver.h>
...
ZIRCON_DRIVER(ahci, ahci_driver_ops, "zircon", "0.1");

PCI 驱动程序会发布匹配的 具有以下属性的设备:

zx_device_str_prop_t pci_device_props[] = {
    ddk::MakeStrProperty(bind_fuchsia::PCI_VID,
        static_cast<uint32_t>(device.info.vendor_id)),
    ddk::MakeStrProperty(bind_fuchsia::PCI_DID,
        static_cast<uint32_t>(device.info.device_id)),
    ddk::MakeStrProperty(bind_fuchsia::PCI_CLASS,
        static_cast<uint32_t>(device.info.base_class)),
    ddk::MakeStrProperty(bind_fuchsia::PCI_SUBCLASS,
        static_cast<uint32_t>(device.info.sub_class)),
    ddk::MakeStrProperty(bind_fuchsia::PCI_INTERFACE,
        static_cast<uint32_t>(device.info.program_interface)),
    ddk::MakeStrProperty(bind_fuchsia::PCI_REVISION,
        static_cast<uint32_t>(device.info.revision_id)),
    ddk::MakeStrProperty(bind_fuchsia::PCI_TOPO, pci_bind_topo),
};

目前,绑定变量和宏是在 lib/ddk/binding.h.在不久的将来, 所有节点属性都将由绑定库定义,例如 fuchsia.pci 库。如果您要引入新的设备类别,则可能需要 为绑定标头以及 bind 库

节点属性是 32 位值。如果您的变量值要求大于 一个 32 位值,将它们拆分为多个 32 位变量。以 ACPI 为例 HID 值,长度为 8 个字符(64 位)。它分为 BIND_ACPI_HID_0_3BIND_ACPI_HID_4_7。完成迁移后 库之后,您就可以使用其他数据类型,例如字符串、 更大的数字和布尔值。

您可以在 bind_rules 构建规则中指定 disable_autobind = true,以 停用自动绑定行为。在这种情况下,可以将驱动程序绑定到 设备使用 fuchsia.device.Controller/Bind FIDL 调用。

驱动程序绑定

当驱动程序与设备匹配时,系统会调用驱动程序的 bind() 函数。一般 驱动程序将初始化设备所需的任何数据结构 初始化硬件。它不应执行任何耗时的 因为它是从驱动程序主机的 RPC 线程,并且在此期间它将无法处理其他请求。 相反,它应该生成一个新线程来执行冗长的任务。

驱动程序不应对硬件状态做任何假设, bind()。它可能需要重置硬件或以其他方式确保采用 状态。因为系统会重新生成驱动程序,从驾驶员崩溃中恢复 主机,则在调用 bind() 时,硬件可能处于未知状态。

bind() 通常会有四种结果:

  1. 驱动程序确定设备受支持,无需采取任何操作 因此使用 C 或 C 版本发布具有 device_add() 的新设备 ddk::Device::DdkAdd()DDKTL C++ 封装容器库,并返回 `ZX_OK.

  2. 驱动程序确定,即使绑定规则匹配,设备 (可能是因为检查了硬件版本位或其他原因) 返回一个错误。

  3. 驱动程序需要在设备准备就绪之前进行进一步初始化,或者 它确定自己能支持它,因此发布了一个隐形设备, 实现 init() 钩子,以及 启动一个线程以继续工作,同时将 ZX_OK 返回到 bind()。 该线程最终会调用 device_init_reply() 或 C 语言 ddk::InitTxn::Reply()(在 DDKTL C++ 封装容器库。在 收到回复。状态表明 ZX_OK 是否能够 已成功初始化设备,并且该设备应处于可见状态;或者 错误,指示应移除设备。

  4. 驱动程序表示具有 0 个子项的总线或控制器, 动态地出现或消失。在本示例中,它应该发布 然后动态发布 子项(下游驱动程序将绑定到的子项),表示 这辆公共汽车。示例:AHCI/SATA、USB 等

在设备被添加并被系统设为可见后,该设备就可供 以及通过兼容的驱动程序进行绑定。

Banjo 协议

驱动程序用于为设备提供一组设备操作和可选的协议操作。 设备操作实现设备生命周期方法和外部接口, 由其他用户空间应用和服务调用的设备。 协议操作实现设备的进程内协议,由 将其他驱动程序加载到同一驱动程序主机中。

您可以在 device_add_args_t 中为设备传递一组协议操作。如果 设备支持多种协议,请实现 get_protocol() 设备操作。答 设备只能有一个协议 ID。协议 ID 对应类 设备发布在 devfs 下。

驱动程序操作

驱动程序通常通过处理来自子级驱动程序的客户端请求来运行 或其他进程满足这些要求 与硬件连接(例如,通过 MMIO)或与其父项通信 设备(例如,将 USB 事务加入队列)。

来自驱动程序主机外部进程的外部客户端请求由 儿童驾驶员通常在同一进程中,由班卓琴扮演 与设备类对应的协议。驾驶员之间的请求应 使用班卓琴协议而不是设备操作。

设备可通过调用 device_get_protocol()

设备中断

设备中断通过中断对象来实现,中断对象是一种 内核对象。驱动程序从 通过设备协议方法指定父设备返回的句柄将绑定到 相应设备中断(如父驱动程序所定义)。对于 例如,PCI 协议会为 PCI 子级实现 map_interrupt()。答 驱动程序应生成一个线程来等待中断句柄。

内核将根据中断情况自动遮盖和取消遮盖中断, 具体取决于中断是边缘触发还是 。对于音频级触发的硬件中断, zx_interrupt_wait()将进行遮盖 在返回之前中断该中断,并在再次调用时取消屏蔽中断 。对于边缘触发的中断,中断保持未屏蔽状态。

中断线程不应执行任何长时间运行的任务。对于 执行冗长的任务,请使用工作线程。

您可以使用以下代码发出中断句柄 zx_interrupt_trigger()已开启 槽 ZX_INTERRUPT_SLOT_USERzx_interrupt_wait() 返回。这是 需要在驱动程序清理期间关闭中断线程。

FIDL 消息

非驱动程序进程

FIDL 语言。每台设备 实现零个或多个 FIDL 协议, 客户端。驱动程序可通过 message() 钩子。只有非驱动程序组件才能访问这些内容 DevFs 的方法。

其他进程中的驱动程序

如果驱动程序需要在单独的进程中与驱动程序通信, 它必须改为托管传出目录,类似于 组件,这些组件应托管子驱动程序将访问的所有 FIDL 协议 。

协议操作与 FIDL 消息

协议操作定义设备的进程内 API。FIDL 消息定义 用于进程外通信的 API。如果函数是 同一进程中的其他驱动程序会调用这些函数。司机应拨打 协议操作,以利用这些函数。

隔离设备

使用 DEVICE_ADD_MUST_ISOLATE 添加的设备将生成新的驱动程序主机。 设备必须具有用于托管 FIDL 的相应外发目录 协议绑定到设备的驱动程序将加载到新的 驱动程序主机,并提供了将 父级驱动程序提供的传出目录。

驾驶员权利

虽然驱动程序在用户空间进程中运行,但它们对 权限。不允许驱动程序访问文件系统 包括 devfs这意味着驱动程序无法与任意设备交互。如果 您的驱动程序需要执行此操作,不妨考虑改为编写服务组件。对于 虚拟控制台由 virtcon 组件。

特权操作,例如 zx_vmo_create_contiguous()zx_interrupt_create 需要 根资源句柄。除了 系统驱动程序(x86 系统上的 ACPIplatform)。设备应 请求其父级为其执行此类操作。请与 父驱动程序的协议未处理此用例的情况。

同样,不允许驱动程序请求任意 MMIO 范围、中断 或 GPIOPCI 和平台等总线驱动程序只会返回资源 与子设备相关联。

高级主题和提示

初始化需要很长时间

如果您的设备初始化时间很长,该怎么办?我们讨论 null_bind() 函数,我们表示,如果返回成功, 驱动程序管理器,告知该驱动程序现在已与设备关联。我们不能支出 在绑定函数中会耗费大量时间;我们需要先完成 然后发布它,就大功告成了

但是,您的设备可能需要执行冗长的初始化操作, 以:

  • 枚举硬件点
  • 加载固件
  • 协商协议

依此类推,这可能需要很长时间

您可以将设备发布为“隐形”方法是实现设备 init() 钩子。在通过 device_add() 添加设备后,系统会运行 init() 钩子, 可用于安全访问设备状态和生成工作线程。 该设备将保持不可见状态,并保证在 系统会调用 device_init_reply(),此操作可通过任何线程完成。这符合 绑定函数的要求,但任何人都无法使用您的设备 (因为还没有人知道它,因为它还没有显示。)现在,您的设备 可以在后台线程上执行长操作。

当您的设备准备好处理客户端请求时,请调用 device_init_reply() 这会使其出现在路径名空间中。

节能

您的设备有两条宣传信息,suspend()resume()可供使用 以支持省电或其他资源节约功能

两者都接受设备上下文指针和 flag 参数,但 flag 参数 只在挂起情况下使用。

标志 含义
DEVICE_SUSPEND_FLAG_REBOOT 驱动程序应自行关闭,以便为重新启动或关闭机器做好准备
DEVICE_SUSPEND_FLAG_REBOOT_BOOTLOADER ?
DEVICE_SUSPEND_FLAG_REBOOT_RECOVERY ?
DEVICE_SUSPEND_FLAG_POWEROFF 驾驶员应自行关闭,准备关闭电源
DEVICE_SUSPEND_FLAG_MEXEC 驱动程序应自行关闭以准备软重启
DEVICE_SUSPEND_FLAG_SUSPEND_RAM 驱动程序应进行适当安排,以便可以从 RAM 重新启动

参考文档:支持函数

本部分列出了我们为驱动程序提供的支持函数。

访问器函数

作为第一个参数传递给驱动程序协议的上下文块 函数是一种不透明的数据结构。这意味着,要访问 您需要调用一个存取器函数:

函数 用途
device_get_name() 检索设备名称

管理功能

以下函数用于管理设备:

函数 用途
device_add() 将设备添加到家长账号
device_async_remove() 安排移除设备及其所有子项