编写基本驱动程序
代码结构应遵循 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_bytecodefuchsia_rust_driverfuchsia_driver_component
司机沟通
驱动程序通过 FIDL 服务与其父驱动程序通信。
提供服务
使用 examples/drivers/transport/driver/rust_next/parent 和 examples/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/child 和 examples/drivers/transport/zircon/rust_next/child/ 作为使用 rust_next 绑定的指南,Fuchsia 建议将这些绑定用于常规 FIDL 传输服务,并要求将这些绑定用于驱动程序传输服务(因为旧绑定不支持这些绑定)。
如需使用某项服务,驱动程序应将其纳入 bind 规则,并在 CML 的 uses 部分中指定该服务。连接到服务时,请使用服务功能,而不是协议。
添加孩子
添加子节点的主要原因是,驾驶员需要为另一位驾驶员提供服务或资源。避免无故添加子级。
除非必要,否则应在 start() 函数中添加子节点作为初始化逻辑的一部分。
应使用 Rust 封装容器 Node::add_child 添加子节点。所有子节点都应为无主节点,除非用于支持 devfs(Rust 驱动程序不支持此功能)。因此,不应有自有子项。
日志记录
使用标准 Rust 日志记录 API 记录所有日志。在记录 FIDL 错误等失败情况时,请使用 warning 或 error 日志级别,遵循 Fuchsia 日志记录指南。
Zircon 资源
使用 Zircon 内核绑定来处理 Vmo 和 Interrupt 等资源。
中断
避免将中断存储为原始 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传递给驱动程序。