编写基本驱动程序
代码结构应遵循 fx create driver goldens
模板。
元目录
每个驱动程序目录都必须有一个 meta 子目录,其中包含以下文件:
- 一个
bind文件,用于定义驱动程序的bind规则 - 一个组件清单
驱动程序源代码
Rust 驱动程序源代码应位于 src 子目录中,文件名为 lib.rs。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 方法
中,并且必须将 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
建议所有 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 资源
对于 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! 宏
检查来自驱动程序外部的长度、偏移量、索引和 ID 的范围,然后再将它们写入寄存器、用作 DMA 传输大小,或用作 VMO 索引,并确认客户端已注册客户端 ID(VMO 注册)。mmio::register! 设置器会以静默方式截断超出范围的值。
原子 (Ordering::Release) 对 CPU 之间的内存访问进行排序,而不是对设备的访问进行排序,并且 MmioRegion 存储不包含屏障:在告知设备有关 DMA 描述符的存储之前,明确对 DMA 描述符写入进行排序。
当传输完成、失败或取消时,以及在关停时,仅在设备停止后调用 Pmt::unpin();如果您无法确认这一点,请将其保持固定。请勿在 Drop 中取消固定,因为 无法知道设备是否已
停止。丢弃 zx::Pmt 而不取消固定会隔离页面,直到 zx_bti_release_quarantine()。
时钟
时钟通过 fuchsia.hardware.clock FIDL 服务进行控制。
驱动程序需要在其依赖的所有时钟上调用 Enable(),然后在不再需要时钟信号时调用 Disable()。
驱动程序不得在未先启用时钟的情况下调用 Disable()。
异步代码
请勿使用 .detach() 分离任务。请改为在驱动程序的 start 方法
中初始化 a
fuchsia_async::Scope,并使用它来生成并发工作。驱动程序必须保留 Scope 的所有权。
优先使用 fuchsia_sync crate 中的 Mutex 和 RwLock,而不是 std::sync。
测试
验证构建目标是否包含必要的驱动程序测试。
单元测试
您可以并且应该在正常的单元测试中测试驱动程序代码的尽可能多的部分。如需在包含驱动程序启动和关停的情况下 “单元测试”某些内容,您可以使用 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传递给驱动程序。