本页面提供了有关将 DFv1 驱动程序中的各种服务(DDK 接口除外)更新为 DFv2 的说明、最佳实践和示例。
设置兼容设备服务器
如果您的 DFv1 驱动程序与尚未迁移到 DFv2 的其他 DFv1 驱动程序通信,则需要使用兼容性 shim,以便您的现已是 DFv2 的驱动程序能够与系统中的其他 DFv1 驱动程序通信。如需详细了解如何在 DFv2 驱动程序中设置和 使用此兼容性 shim,请参阅 在 DFv2 驱动程序中设置兼容设备服务器 指南。
使用 DFv2 服务发现
在进行驱动程序迁移时,您可能会遇到以下三种情况中的一种或多种
,在这些情况下,两个驱动程序会建立 FIDL
连接(格式为 child driver -> parent driver):
- 场景 1:DFv2 驱动程序 -> DFv2 驱动程序
- 场景 2:DFv1 驱动程序 -> DFv2 驱动程序
- 场景 3:DFv2 驱动程序 -> DFv1 驱动程序
场景 1 是 DFv2 驱动程序的标准情况(此 示例展示了新的 DFv2 语法)。如需在此场景下更新 驱动程序,请参阅下方的 DFv2 驱动程序到 DFv2 驱动程序部分。
场景 2 和 3 较为复杂,因为 DFv1 驱动程序在 DFv2 世界中封装在兼容性 shim 中。不过,二者之间的区别在于:
在 **场景 2** 中,此 **Gerrit 更改** 展示了一种将服务从 DFv2 父级公开给 DFv1 子级的方法。
在场景 3 中,驱动程序连接到父级驱动程序的兼容性 shim 提供的
fuchsia_driver_compat::Service::Device协议,并且驱动程序通过此协议调用ConnectFidl()方法以连接到实际 协议(如需查看示例,请参阅此 Gerrit 更改)。
如需在场景 2 或 3 下更新驱动程序,请参阅下方的 DFv1 驱动程序到 DFv2 驱动程序(使用兼容性 shim) 部分。
DFv2 驱动程序到 DFv2 驱动程序
如需让其他 DFv2 驱动程序发现驱动程序的服务,请执行以下操作:
更新驱动程序的
.fidl文件。在 DFv2 中进行协议发现需要为驱动程序的协议添加
service字段,例如:library fuchsia.example; @discoverable @transport("Driver") protocol MyProtocol { MyMethod() -> (struct { ... }); }; service Service { my_protocol client_end:MyProtocol; };更新子级驱动程序。
DFv2 驱动程序可以像 FIDL 服务一样连接到协议,例如:
incoming()->Connect<fuchsia_example::Service::MyProtocol>();您还需要更新组件清单 (
.cml) 文件以使用驱动程序运行时服务,例如:use: [ { service: "fuchsia.example.Service" }, ]更新父级驱动程序。
父级驱动程序需要使用
fdf::DriverBase的outgoing()函数来获取fdf::OutgoingDirectory对象。请注意,您必须使用服务而不是协议。 如果您的驱动程序未使用fdf::DriverBase,则必须自行创建并提供fdf::OutgoingDirectory。然后,您需要将运行时服务添加到传出目录。 以下示例是一个继承自
fdf::DriverBase类的驱动程序:zx::status<> Start() override { auto protocol = [this]( fdf::ServerEnd<fuchsia_example::MyProtocol> server_end) mutable { // bindings_ is a class field with type fdf::ServerBindingGroup<fuchsia_example::MyProtocol> bindings_.AddBinding( dispatcher()->get(), std::move(server_end), this, fidl::kIgnoreBindingClosure); }; fuchsia_example::Service::InstanceHandler handler( {.my_protocol = std::move(protocol)}); auto status = outgoing()->AddService<fuchsia_wlan_phyimpl::Service>(std::move(handler)); if (status.is_error()) { return status.take_error(); } return zx::ok(); }更新子级节点的
NodeAddArgs以包含运行时服务的 offer ,例如:auto offers = std::vector{fdf::MakeOffer2<fuchsia_example::Service>(arena, name)}; fidl::WireSyncClient<fuchsia_driver_framework::Node> node(std::move(node())); auto args = fuchsia_driver_framework::wire::NodeAddArgs::Builder(arena) .name(arena, "example_node") .offers2(offers) .Build(); zx::result controller_endpoints = fidl::CreateEndpoints<fuchsia_driver_framework::NodeController>(); ZX_ASSERT(controller_endpoints.is_ok()); auto result = node_->AddChild( args, std::move(controller_endpoints->server), {});同样,更新父级驱动程序的组件清单 (
.cml) 文件以提供运行时服务,例如:capabilities: [ { service: "fuchsia.example.Service" }, ], expose: [ { service: "fuchsia.example.Service", from: "self", }, ],
DFv1 驱动程序到 DFv2 驱动程序(使用兼容性 shim)
如需让其他 DFv1 驱动程序发现 DFv2 驱动程序的服务,请执行以下操作:
更新 DFv1 驱动程序。
您需要以与上述DFv2 驱动程序到 DFv2 驱动程序部分中提到的相同方式更新 DFv1 驱动程序的组件清单 (
.cml) 文件,例如:子级驱动程序:
{ include: [ "//sdk/lib/driver_compat/compat.shard.cml", "inspect/client.shard.cml", "syslog/client.shard.cml", ], program: { runner: "driver", compat: "driver/child-driver-name.so", bind: "meta/bind/child-driver-name.bindbc", colocate: "true", }, use: [ { service: "fuchsia.example.Service" }, ], }父级驱动程序:
{ include: [ "//sdk/lib/driver_compat/compat.shard.cml", "inspect/client.shard.cml", "syslog/client.shard.cml", ], program: { runner: "driver", compat: "driver/parent-driver-name.so", bind: "meta/bind/parent-driver-name.bindbc", }, capabilities: [ { service: "fuchsia.example.Service" }, ], expose: [ { service: "fuchsia.example.Service", from: "self", }, ], }
更新 DFv2 驱动程序。
以下示例展示了一种将服务从 DFv2 父级公开给 DFv1 子级的方法:
fit::result<fdf::NodeError> AddChild() { fidl::Arena arena; auto offer = fdf::MakeOffer2<ft::Service>(kChildName); // Set the properties of the node that a driver will bind to. auto property = fdf::MakeProperty(1 /*BIND_PROTOCOL */, bind_fuchsia_test::BIND_PROTOCOL_COMPAT_CHILD); auto args = fdf::NodeAddArgs{ { .name = std::string(kChildName), .properties = std::vector{std::move(property)}, .offers2 = std::vector{std::move(offer)}, } }; // Create endpoints of the `NodeController` for the node. auto endpoints = fidl::CreateEndpoints<fdf::NodeController>(); if (endpoints.is_error()) { return fit::error(fdf::NodeError::kInternal); } auto add_result = node_.sync()->AddChild(fidl::ToWire(arena, std::move(args)), std::move(endpoints->server), {});(来源:
root-driver.cc)
更新其他驱动程序的组件清单
如需完成将 DFv1 驱动程序迁移到 DFv2,您不仅需要更新目标驱动程序的组件清单 (.cml) 文件,还可能需要更新与现已是 DFv2 的驱动程序交互的其他一些驱动程序的组件清单文件。
请执行以下操作:
使用以下更改更新叶驱动程序(即没有子级驱动程序)的组件清单:
- 从
include字段中移除//sdk/lib/driver/compat/compat.shard.cml。 - 将
program.compat字段替换为program.binary。
- 从
更新执行以下任务的其他驱动程序的组件清单:
- 访问内核
args。 - 创建复合设备。
- 检测重启、关机或重新绑定调用。
- 使用 Banjo 协议与其他驱动程序通信。
- 从父级驱动程序访问元数据或转发元数据。
- 与绑定到驱动程序添加的节点的 DFv1 驱动程序通信。
对于这些驱动程序,请使用以下更改更新其组件清单:
将
use功能从compat.shard.cml复制到组件清单, 例如:use: [ { protocol: [ "fuchsia.boot.Items", "fuchsia.driver.framework.CompositeNodeManager", "fuchsia.system.state.SystemStateTransition", ], }, { service: "fuchsia.driver.compat.Service" }, ],将
program.runner字段设置为driver,例如:program: { runner: "driver", binary: "driver/compat.so", },
- 访问内核
使用调度程序
调度程序从 FIDL 客户端-服务器对之间的通道提取数据。默认情况下,此通道中的 FIDL 调用是异步的。
如需在 DFv2 中向驱动程序引入异步,请参阅以下建议:
fdf::Dispatcher::GetCurrent()方法可为您提供驱动程序正在运行的默认 调度程序(请参阅此aml-ethernet驱动程序示例)。如果可能,建议单独使用此默认调度程序。考虑使用多个调度程序,原因如下(但不限于):
驱动程序需要并行处理以提高性能。
驱动程序想要执行阻塞操作(因为它要么是旧版驱动程序,要么是正在移植到 Fuchsia 的非 Fuchsia 驱动程序),并且需要在被阻塞时处理更多工作。
如果需要多个调度程序,
fdf::Dispatcher::Create()方法可以为您的驱动程序创建新的调度程序。不过,您必须在默认调度程序上调用此方法(例如,在Start()hook 中调用),以便驱动程序主机了解属于您的驱动程序的其他调度程序。在 DFv2 中,您无需手动关闭调度程序。它们将在
PrepareStop()和Stop()调用之间关闭。
如需详细了解如何迁移驱动程序以使用多个调度程序, 请参阅 更新 DFv1 驱动程序以使用非默认调度程序 部分(位于 从 Banjo 迁移到 FIDL 短语中)。
使用 DFv2 检查
如需在 DFv2 中设置驱动程序维护的 inspect 指标,
您可以使用 ComponentInspector 提供的 fdf::DriverBase::inspector():
inspect::Node& root = inspector().root();
如果应使用自定义检查器,请在访问 inspector() 方法之前调用 fdf::DriverBase::InitInspectorExactlyOnce(inspector)。
DFv2 检查不需要将 inspect::Inspector 的 VMO 传递给驱动程序框架。
DFv2 驱动程序的检查将显示归因于驱动程序(因为它是一个“正常”组件)。 不过,DFv2 驱动程序的别名不稳定,因此在针对驱动程序编写隐私选择器时,您应使用通配符和名称过滤器来引用特定驱动程序。例如,
bootstrap/*-drivers*:[name=sysmem]root
如需在调试期间访问驱动程序的检查,您可以使用所有常规工具,例如
ffx inspect show --name sysmem "bootstrap/*-drivers*:root"
或
ffx inspect show sysmem.cm
(可选)实现自己的 load_firmware 方法
如果您的 DFv1 驱动程序调用 DDK 库中的 load_firmware()
函数,则需要实现自己的此函数版本
,因为 DFv2 中没有等效函数。
此函数预计实现起来很简单。您需要手动从路径获取支持 VMO。如需查看示例,请参阅此 Gerrit 更改。
(可选)使用从 FIDL 服务 offer 生成的节点属性
DFv2 节点包含从其父级的 FIDL 服务 offer 生成的节点属性。
例如,在 父级驱动程序(服务器)
示例中,父级驱动程序添加了一个名为 "parent" 的节点,其中包含 fidl.examples.echo.EchoService
的服务 offer。在 DFv2 中,绑定到此节点的驱动程序可以具有该 FIDL 服务节点属性的绑定规则,例如:
fuchsia.Service == "fidl.examples.echo.EchoService";
将单元测试更新为 DFv2
The mock_ddk 库(用于在单元测试中测试
驱动程序和设备生命周期)特定于 DFv1。新的 DFv2 测试
框架(请参阅此 Gerrit 更改)通过 TestEnvironment
类向 DFv2 驱动程序提供模拟的 FIDL 服务器。
以下库可用于对 DFv2 驱动程序进行单元测试:
-
TestNode- 此类实现了fuchsia_driver_framework::Node协议,该协议可以提供给驱动程序以创建子级节点。测试也会使用此类来访问驱动程序已创建的子级节点。TestEnvironment-OutgoingDirectory对象的封装容器,用作受测驱动程序的传入 命名空间的支持 VFS(虚拟文件系统)。DriverUnderTest- 此类是受测驱动程序的 RAII (资源获取即初始化)封装容器 。DriverRuntime- 此类是受管驱动程序 运行时线程池的 RAII 封装容器。
//sdk/lib/driver/testing/cpp/driver_runtime.hTestSynchronizedDispatcher- 此类是 驱动程序调度程序的 RAII 封装容器。
以下库可能有助于编写驱动程序单元测试:
//src/devices/bus/testing/fake-pdev/fake-pdev.h- 此 帮助程序库实现了pdevFIDL 协议的虚假版本。
最后,以下示例单元测试涵盖了不同的配置和测试用例:
//sdk/lib/driver/component/cpp/tests/driver_base_test.cc- 此文件包含驱动程序 测试可以具有的不同线程模型的示例。//sdk/lib/driver/component/cpp/tests/driver_fidl_test.cc- 此文件演示了如何使用驱动程序传输和 Zircon 传输的传入和传出 FIDL 服务。
其他资源
一些 DFv2 驱动程序示例:
本部分中提到的所有 Gerrit 更改 :
- [iwlwifi] iwlwifi 驱动程序的 Dfv2 迁移
- [compat-runtime-test] 迁移掉 DeviceServer 的使用
- [msd-arm-mali] 添加 DFv2 版本
- [sdk][driver][testing] 添加测试库
本部分中提到的所有源代码文件 :
//examples/drivers/transport/zircon/v2/parent-driver.cc//sdk/fidl/fuchsia.driver.framework/topology.fidl//sdk/lib/driver/component/cpp/driver_base.h//sdk/lib/driver/component/cpp/tests/driver_base_test.cc//sdk/lib/driver/component/cpp/tests/driver_fidl_test.cc//sdk/lib/driver/compat/cpp/banjo_server.h//sdk/lib/driver/compat/cpp/banjo_client.h//sdk/lib/driver/compat/cpp/device_server.h//sdk/lib/driver/testing/cpp/driver_runtime.h//src/connectivity/wlan/testing/wlantap-driver/wlantap-driver.cc//src/devices/bus/testing/fake-pdev/fake-pdev.h//src/devices/tests/v2/compat-runtime/root-driver.cc//src/lib/ddk/include/lib/ddk/device.h//src/lib/ddk/include/lib/ddk/driver.h
本部分中提到的所有文档页面 :
- 驱动程序和节点
- 驱动程序通信
- 驱动程序和节点
- 驱动程序调度程序和线程
- 驱动程序
- 创建复合节点
- 公开驱动程序功能
- Fuchsia 组件检查概览
- 模拟 DDK 迁移
- 拆解序列示例 (来自设备驱动程序生命周期)
- 父级驱动程序(服务器) (来自 FIDL 教程)