编写基本驱动程序
代码结构应遵循 fx create driver goldens 模板。
Meta 目录
每个驱动程序目录都必须有一个 meta 子目录,其中包含以下文件:
- 一个
bind文件,用于定义驱动程序的bind规则 - 组件清单
驱动程序源代码
Rust 驱动程序源代码应位于名为 lib.rs 的文件中的 src 子目录中。Rust 源代码应使用 fdf_component 库来定义驱动程序。
模块结构应使用扁平文件模块着陆(例如 src/foo.rs),而不是文件夹中的 mod.rs(例如 src/foo/mod.rs)。子模块应放置在同级文件夹中(例如 src/foo/bar.rs)。
驱动程序必须定义为实现 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 对象将启动驱动程序关闭。
构建文件
所有 Rust 驱动程序都必须使用 GN 进行构建流程,并且必须包含以下目标:
fuchsia_driver_bind_bytecodefuchsia_rust_driverfuchsia_driver_component
建议所有 Rust 驱动程序目标通过在其 GN 定义中包含以下配置来强制执行严格的 lint:
configs += [
"//build/config/rust/lints:clippy_warn_all",
]
司机沟通
驱动程序通过 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 的所有权。
优先选择 fuchsia_sync 箱中的 Mutex 和 RwLock,而不是 std::sync。
测试
验证 build 目标是否包含必要的驱动程序测试。
单元测试
您可以在常规单元测试中测试驱动程序的大部分代码,并且应该这样做。如需在包含驱动程序启动和关闭的情况下对某项内容进行“单元测试”,您可以使用 fdf_component::testing::TestHarness 来启动驱动程序并与之互动。
如果您的驱动程序声明具有 my_driver 的 output_name,则驱动程序的 GN 目标将为 my_driver_test。如需查看示例,请参阅 //examples/drivers 中任何 Rust 示例驱动程序的 GN 规则。
集成测试
使用 DriverTestRealm 库编写集成测试,就像使用 C++ 驱动程序一样。
一般测试建议
测试使用 MmioRegion 的驱动程序时:
- 首选实际 VMO:对于简单的使用情形,请使用实际 VMO 支持内存区域,不要模拟
read/write方法。 - 高级用法:对于复杂场景,模拟
read/write是合理的,请考虑使用MockMemoryOps或类似方法。 - 使用 VMO 注入:在测试中,创建
zx::Vmo,使用VmoMapping::map()映射它,并将生成的MmioRegion传递给驱动程序。