Rust 驱动程序评分准则

编写基本驱动程序

代码结构应遵循 fx create driver goldens 模板。

Meta 目录

每个驱动程序目录都必须有一个 meta 子目录,其中包含以下文件:

  • 一个 bind 文件,用于定义驱动程序的 bind 规则
  • 组件清单

驱动程序源代码

Rust 驱动程序源代码应位于名为 lib.rs 的文件中的 src 子目录中。Rust 源代码应使用 fdf_component 库来定义驱动程序。

驱动程序必须定义为实现 fdf_component::Driver 特征的结构。实现的 start 方法接收一个 DriverContext 结构体,其中包含连接到驱动程序并为其提供协议和服务和日志所需的结构。此外,驱动程序代码必须使用 driver_register!() 宏向驱动程序框架注册驱动程序。

组件清单

组件清单不应将主调度程序声明为 allow_sync_calls,因为 Rust 驱动程序必须是异步的。

初始化驱动程序

所有初始化逻辑都应放在 start 方法中,并且必须将 DriverContext 中的 Node 句柄存储到 Driver 结构体中。初始化逻辑包含:

  • 获取(使用 DriverContext::take_node)并存储驱动程序的 Node 对象,直到关闭。无论是有意还是无意,舍弃 Node 对象都会导致驱动程序关闭。在大多数情况下,此变量将不会使用,但仍应存储在 Driver 对象中(作为 _node 以消除有关其未使用的警告)。这样可在司机关闭时正确丢弃 Node
  • 提取并配置所有驱动程序资源。
  • 建立服务连接。
  • 将驱动程序自己的服务添加到传出目录。
  • 添加子节点(在资源设置和提供自有服务后执行)。
  • 从隔离区中释放 BTI。

关闭驱动程序

在大多数情况下,Driver stop() 实现为空。除非驱动程序的功能明确需要进行清理(例如执行正常硬件关机或取消固定 DMA),否则请勿向其中添加更多内容。

如果驱动程序控制着何时应关闭,则应将 Node 对象存储为可丢弃的对象,例如在 Option<Node> 中,或者在必要时存储在互斥锁后面。舍弃 Node 对象将启动驱动程序关闭。

build 文件

所有 Rust 驱动程序都必须使用 GN 进行构建流程,并且必须包含以下目标:

  • fuchsia_driver_bind_bytecode
  • fuchsia_rust_driver
  • fuchsia_driver_component

司机沟通

驱动程序通过 FIDL 服务与其父驱动程序通信。

提供服务

使用 examples/drivers/transport/driver/rust_next/parentexamples/drivers/transport/zircon/rust_next/parent/ 作为使用 rust_next 绑定的指南,Fuchsia 建议将此绑定用于常规 FIDL 传输服务,并要求将此绑定用于驱动程序传输服务(因为旧绑定不支持此绑定)。

提供 FIDL 服务的驱动程序必须将 FIDL 服务实现为特征。在初始化期间,它需要在传出目录中添加 ServiceOffer,然后使用 serve_outgoing() 函数将该目录提供给 DriverContext。在目录提供服务后,使用 fuchsia_async Scope 库生成一个任务,以运行 ServiceFs 事件循环。

CML 文件必须在功能中指定服务,并从 self 中公开该服务。

使用服务

使用 examples/drivers/transport/driver/rust_next/childexamples/drivers/transport/zircon/rust_next/child/ 作为使用 rust_next 绑定的指南,Fuchsia 建议将这些绑定用于常规 FIDL 传输服务,并要求将这些绑定用于驱动程序传输服务(因为旧绑定不支持这些绑定)。

如需使用某项服务,驱动程序应将其纳入 bind 规则,并在 CML 的 uses 部分中指定该服务。连接到服务时,请使用服务功能,而不是协议。

添加孩子

添加子节点的主要原因是,驾驶员需要为另一位驾驶员提供服务或资源。避免无故添加子级。 除非必要,否则应在 start() 函数中添加子节点作为初始化逻辑的一部分。

应使用 Rust 封装容器 Node::add_child 添加子节点。所有子节点都应为无主节点,除非用于支持 devfs(Rust 驱动程序不支持此功能)。因此,不应有自有子项。

日志记录

使用标准 Rust 日志记录 API 记录所有日志。在记录 FIDL 错误等失败情况时,请使用 warningerror 日志级别,遵循 Fuchsia 日志记录指南

Zircon 资源

使用 Zircon 内核绑定来处理 VmoInterrupt 等资源。

中断

避免将中断存储为原始 zx::Handle 对象,因为这需要在等待操作期间使用 unsafe 代码。正确的做法是,使用 Zircon 中断绑定 (sdk/rust/zx/src/interrupt.rs) 将设备结构体定义为 K: zx::InterruptKind(例如 Device<K>)的泛型,并使用它来封装和存储 zx::Interrupt<K> 对象中的句柄。

使用 fasync::OnInterrupt 创建中断流,并在异步任务中处理这些中断,而不是使用 std::thread::spawn 生成专用线程并在 irq.wait() 上阻塞。

DMA

必须使用 VmoMapping::map() (sdk/lib/driver/mmio/rust/) 将 MMIO 句柄映射到 MmioRegion 对象中。访问 MMIO 寄存器时,驱动程序绝不能对原始整数执行手动按位运算(例如 val |= 1 << 5;)。而应改用 mmio::register!mmio::register_block!

时钟

时钟通过 fuchsia.hardware.clock FIDL 服务进行控制。驱动程序需要在其依赖的所有时钟上调用 Enable(),并在不再需要时钟信号时随后调用 Disable()。驱动程序不得在未先启用时钟的情况下调用 Disable()

异步代码

请勿使用 .detach() 分离任务。请改为在驱动程序的 start 方法中初始化 fuchsia_async::Scope,并使用它来生成并发工作。驱动程序必须保留对 Scope 的所有权。

测试

验证 build 目标是否包含必要的驱动程序测试。

单元测试

您可以在常规单元测试中尽可能多地测试驱动程序的代码。如需在包含驱动程序启动和关闭的情况下对某项内容进行“单元测试”,您可以使用 fdf_component::testing::TestHarness 来启动驱动程序并与之互动。

如果您的驱动程序声明的 output_name 为 my_driver,则驱动程序的 GN 目标为 my_driver_test。如需查看示例,请参阅 //examples/drivers 中任何 Rust 示例驱动程序的 GN 规则。

集成测试

使用 DriverTestRealm 库编写集成测试,就像使用 C++ 驱动程序一样。

常规测试建议

测试使用 MmioRegion 的驱动程序时:

  • 首选实际 VMO:对于简单的使用情形,请使用实际 VMO 支持内存区域,不要模拟 read/write 方法。
  • 高级用法:对于复杂场景,模拟 read/write 是合理的,请考虑使用 MockMemoryOps 或类似方法。
  • 使用 VMO 注入:在测试中,创建 zx::Vmo,使用 VmoMapping::map() 对其进行映射,并将生成的 MmioRegion 传递给驱动程序。