编写基本驱动程序
代码结构应遵循 fx create driver goldens
模板。
元目录
每个驱动程序目录都必须有一个 meta 子目录,其中包含以下文件:
- 一个
bind文件,用于定义驱动程序的bind规则 - 一个组件清单
驱动程序源代码
Rust 驱动程序源代码应位于 src 子目录中,文件名为 lib.rs。Rust 源代码应使用 fdf_component 库
来定义驱动程序。
驱动程序必须定义为实现
fdf_component::Driver 特征的结构体。实现的
start 方法会接收一个 DriverContext 结构体
,其中包含连接到驱动程序并为其提供协议和服务以及日志所需的结构体
。此外,驱动程序代码必须使用 driver_register!() 宏向驱动程序框架注册驱动程序。
组件清单
组件清单不应将主调度程序声明为 allow_sync_calls,因为 Rust 驱动程序必须是异步的。
初始化驱动程序
所有初始化逻辑都应放置在 start 方法
中,并且必须将 Node 句柄从 DriverContext 存储到
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
驱动程序通信
驱动程序通过 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 驱动程序不支持 devfs。
因此,不应有拥有的子项。
日志记录
对所有日志使用标准 Rust 日志记录 API。记录失败(例如 FIDL 错误)时,请使用 warning 或 error
日志级别,遵循
Fuchsia 日志记录指南。
Zircon 资源
对于 Vmo 和 Interrupt 等资源,请使用 Zircon 内核绑定。
中断
避免将中断存储为原始 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
MMIO 句柄必须使用
VmoMapping::map() (sdk/lib/driver/mmio/rust/) 映射到 MmioRegion 对象中。
访问 MMIO 寄存器时,驱动程序绝不能对原始整数执行手动按位运算(例如
val |= 1 << 5;)。相反,它们应使用 mmio::register! 和 mmio::register_block! 宏
时钟
时钟通过 fuchsia.hardware.clock FIDL 服务进行控制。
驱动程序需要对其依赖的所有时钟调用 Enable(),然后在不再需要时钟信号时调用 Disable()。
驱动程序不得在未先启用时钟的情况下调用 Disable()。
异步代码
请勿使用 .detach() 分离任务。请改为在驱动程序的 start 方法
中初始化 a
fuchsia_async::Scope,并使用它来生成并发工作。驱动程序必须保留 Scope 的所有权。
测试
验证 build 目标是否包含必要的驱动程序测试。
单元测试
您可以并且应该在正常的单元测试中测试驱动程序代码的尽可能多的部分。如需在包含驱动程序启动和关闭的同时 “单元测试”某些内容,您可以使用 fdf_component::testing::TestHarness启动驱动程序 并与其互动。
如果您的驱动程序声明的 output_name 为 my_driver,则驱动程序的 GN
目标将为 my_driver_test。如需查看示例,请参阅 //examples/drivers 中任何 Rust 示例驱动程序的 GN 规则。
集成测试
使用 DriverTestRealm 库 编写集成测试,就像 使用 C++ 驱动程序一样。
一般测试建议
测试使用 MmioRegion 的驱动程序时:
- 避免手动模拟:请勿模拟
read/write方法,而是使用真实的 VMO 来支持内存区域。 - 使用 VMO 注入:在测试中,创建
zx::Vmo,使用VmoMapping::map()映射它,并将生成的MmioRegion传递给驱动程序。