本页面提供了有关如何更新 DFv1 驱动程序以开始使用 DFv2 接口的说明、最佳实践和示例。
将依赖项从 DDK 更新为 DFv2
DFv1 驱动程序使用 DDK 库 (//src/lib/ddk
)。对于 DFv2 驱动程序,您可以安全地移除此 DDK 库目录下的所有软件包依赖项,并将其替换为以下新库,其中包含 DFv2 驱动程序的大多数基本实用程序:
//sdk/lib/driver/component/cpp:cpp
在头文件中,为新 DFv2 驱动程序接口添加以下库:
#include <lib/driver/component/cpp/driver_base.h>
将依赖项从 DDK 更新为 DFv2 后,驱动程序将无法编译,直到您完成下一部分中的将驱动程序接口从 DFv1 更新为 DFv2 部分。
将驱动程序接口从 DFv1 更新为 DFv2
DFv2 提供了一个名为 DriverBase
的虚拟类,该类封装了驱动程序的常规例程。对于 DFv2 驱动程序,建议在新驱动程序类中继承 DriverBase
,以大幅简化接口。
例如,假设您的 DFv1 驱动程序中有以下类:
class MyExampleDriver;
using MyExampleDeviceType = ddk::Device<MyExampleDriver, ddk::Initializable,
ddk::Unbindable>;
class MyExampleDriver : public MyExampleDeviceType {
public:
void DdkInit(ddk::InitTxn txn);
void DdkUnbind(ddk::UnbindTxn txn);
void DdkRelease();
}
如果您更新该接口以从 DriverBase
继承,新类可能如下所示:
class MyExampleDriver : public fdf::DriverBase {
public:
Driver(fdf::DriverStartArgs start_args,
fdf::UnownedSynchronizedDispatcher driver_dispatcher);
zx::result<> Start() override;
void Stop() override;
}
除了启动和停止驱动程序之外(如上例所示),DriverBase
类还提供使驱动程序能够与其他组件(和驱动程序)进行通信的对象。例如,您的驱动程序现在可以调用 DriverBase
类的 outgoing()
方法来检索传出目录,而不是在 DFv1 中声明和创建您自己的传出目录(如从 Banjo 迁移到 FIDL 阶段):
class DriverBase {
...
// Used to access the outgoing directory that the driver is serving. Can be used to add both
// zircon and driver transport outgoing services.
std::shared_ptr<OutgoingDirectory>& outgoing() { return outgoing_; }
...
}
(来源:driver_base.h
)
DFv2 中另一个实用的类是 Node
类。以下示例展示了连接到 Node
服务器并使用 AddChild()
函数添加子节点的 DFv2 驱动程序代码:
zx::result<> MyExampleDriver::Start() {
fidl::WireSyncClient<fuchsia_driver_framework::Node> node(std::move(node()));
auto args = fuchsia_driver_framework::wire::NodeAddArgs::Builder(arena)
.name(arena, “example_node”)
.Build();
auto controller_endpoints = fidl::Endpoints<fuchsia_driver_framework::NodeController>::Create();
auto result = node_->AddChild(args, std::move(controller_endpoints.server), {});
if (!result.ok()) {
FDF_LOG(ERROR, "Failed to add child: %s", result.status_string());
return zx::error(result.status());
}
return zx::ok();
}
传递给 AddChild()
函数的 NodeController
端点(上例中的 controller_endpoints
)可用于控制子节点。例如,此端点可用于从节点拓扑中移除子节点、请求驱动程序框架将节点绑定到特定驱动程序,或在子节点绑定时接收回调。以下示例展示了用于在关闭过程中移除子节点的 DFv2 驱动程序代码:
void MyExampleDriver::Stop() {
// controller_endpoints defined in the previous example.
fidl::WireSyncClient<fuchsia_driver_framework::NodeController>
node_controller(controller_endpoints.client);
auto status = node_controller->Remove();
if (!status.ok()) {
FDF_LOG(ERROR, "Could not remove child: %s", status.status_string());
}
}
此外,在 DFv2 中,OnBind()
事件是在 NodeController
协议中定义的,该协议由服务器端的子节点调用。DriverBase::PrepareStop()
函数让您有机会在调用 DriverBase::Stop()
之前执行去初始化操作。
下表显示了 DFv1 和 DFv2 之间通用驱动程序和设备接口的对应关系:
DFv1 | DFv2 |
---|---|
zx_driver_ops::bind() |
DriverBase::Start() |
zx_driver_ops::init() |
DriverBase::Start() |
zx_driver_ops::release() |
DriverBase::Stop() |
device_add() DdkAdd() |
Node::AddChild() |
device_add_composite() DdkAddComposite() |
无。使用规范添加复合节点。请参阅复合节点。 |
device_add_composite_node_spec() DdkAddCompositeNodeSpec() |
CompositeNodeManager::AddSpec() |
zx_protocol_device::init() DdkInit() |
无。在 DFv2 中,驱动程序负责控制其添加的节点(设备)的生命周期。 |
zx_protocol_device::unbind() DdkUnbind() |
无。在 DFv2 中,驱动程序负责控制其添加的节点(设备)的生命周期。 |
zx_protocol_device::release() DdkRelease() |
NodeController::Remove() |
zx_protocol_device::get_protocol() device_get_protocol() |
无。这些方法基于 Banjo 协议。在 DFv2 中,所有通信均采用 FIDL。 |
zx_protocol_device::service_connect() device_service_connect() DdkServiceConnect() |
无。这是驱动程序之间建立 FIDL 连接的老式方法。如需了解详情,请参阅使用 DFv2 服务发现。 |
Device_connect_runtime_protocol() DdkConnectRuntimeProtocol() |
无。这些是 DFv1 中用于发现服务和协议的新方法。如需了解详情,请参阅使用 DFv2 服务发现。 |
使用 DFv2 日志记录器
DFv2 中的新日志记录机制依赖于 fdf::Logger
对象,该对象是在启动驱动程序时通过 DriverStartArgs
从驱动程序主机传递的,而不是使用 zxlogf()
(DFv2 中已废弃)。
fdf::DriverBase
类封装了 fdf::Logger
,而驱动程序可以通过调用 logger()
方法获取其引用(请参阅此 wlantap-driver
驱动程序示例)。借助此参考文档,您可以使用 logger.logf()
函数或使用这些宏输出日志,例如:
FDF_LOG(INFO, "Example log message here");
更新宏
除了更新接口之外,您还需要更改用于填充驱动程序接口函数的宏:
From:
ZIRCON_DRIVER()
改后:
FUCHSIA_DRIVER_EXPORT()
设置 compat 设备服务器
如果您的 DFv1 驱动程序与尚未迁移到 DFv2 的其他 DFv1 驱动程序进行通信,则您需要使用兼容性 shim 才能让现在的 DFv2 驱动程序与系统中的其他 DFv1 驱动程序进行通信。如需详细了解如何在 DFv2 驱动程序中设置和使用此兼容性填充码,请参阅在 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 环境中的兼容性填充码中。但是,区别如下:
在场景 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
以包含运行时服务的优惠,例如: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 驱动程序(具有兼容性填充码)
如需让其他 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 驱动程序。
对于这些驱动程序,请通过以下更改更新其组件清单:
将
compat.shard.cml
中的部分use
功能复制到组件清单中,例如:use: [ { protocol: [ "fuchsia.boot.Arguments", "fuchsia.boot.Items", "fuchsia.device.manager.SystemStateTransition", "fuchsia.driver.framework.CompositeNodeManager", ], }, { service: "fuchsia.driver.compat.Service" }, ],
将
program.runner
字段设置为driver
,例如:program: { runner: "driver", binary: "driver/compat.so", },
- 访问内核
公开 DFv2 驱动程序中的 devfs 节点
如需公开 DFv2 驱动程序中的 devfs
节点,您需要将 device_args
成员添加到 NodeAddArgs
。具体而言,它需要指定类名称并实现连接器,您可以使用 Connector
库简化该过程,例如:
zx::result connector = devfs_connector_.Bind(dispatcher());
if (connector.is_error()) {
return connector.take_error();
}
auto devfs =
fuchsia_driver_framework::wire::DevfsAddArgs::Builder(arena).connector(
std::move(connector.value()));
auto args = fuchsia_driver_framework::wire::NodeAddArgs::Builder(arena)
.name(arena, name)
.devfs_args(devfs.Build())
.Build();
(来源:parent-driver.cc
)
如需了解详情,请参阅 DFv2 驱动程序 Codelab 中的公开驱动程序功能。另请参阅 Codelab 中提到的 ExportToDevfs
方法的实现。
使用调度程序
调度程序从 FIDL 客户端与服务器对之间的通道提取数据。默认情况下,此通道中的 FIDL 调用是异步的。
如需在 DFv2 中为驱动程序引入异步,请参阅以下建议:
fdf::Dispatcher::GetCurrent()
方法可为您提供运行驱动程序的默认调度程序(请参阅此aml-ethernet
驱动程序示例)。如果可能,建议单独使用此默认调度程序。出于以下原因(但不限于):
驱动程序需要并行处理才能提升性能。
驱动程序想要执行阻塞操作(因为它是旧版驱动程序或要移植到 Fuchsia 的非 Fuchsia 驱动程序),并且需要在阻塞状态下处理更多工作。
如果需要多个调度程序,
fdf::Dispatcher::Create()
方法可以为驱动程序创建新的调度程序。不过,您必须在默认调度程序上调用此方法(例如,在Start()
钩子内调用),以便驱动程序主机知道属于您的驱动程序的其他调度程序。在 DFv2 中,您无需手动关闭调度程序。它们将在
PrepareStop()
和Stop()
调用之间关停。
如需详细了解如何迁移驱动程序以使用多个调度程序,请参阅更新 DFv1 驱动程序以使用非默认调度程序部分(在从 Banjo 迁移到 FIDL 短语)。
使用 DFv2 检查
如需在 DFv2 中设置驱动程序维护的inspect指标,您需要创建一个 inspect::ComponentInspector
对象,例如:
component_inspector_ =
std::make_unique<inspect::ComponentInspector>(out, dispatcher, *inspector_);
(来源:driver-inspector.cc
)
创建 inspect::ComponentInspector
对象需要以下三个输入项:
来自
Context().outgoing()->component()
调用的component::OutgoingDirectory
对象调度程序
原始
inspect::Inspector
对象
但是,DFv2 检查不需要将 inspect::Inspector
的 VMO 传递给驱动程序框架。
(可选)实现您自己的 load_ramdisk 方法
如果您的 DFv1 驱动程序调用 DDK 库中的 load_firmware()
函数,则您需要实现您自己的此函数版本,因为 DFv2 中未提供等效函数。
该函数应该很容易实现。您需要从该路径手动获取后备 VMO。如需查看示例,请参阅此 Gerrit 更改。
(可选)使用根据 FIDL 服务方案生成的节点属性
DFv2 节点包含其父级通过 FIDL 服务生成的节点属性。
例如,在父驱动程序(服务器)示例中,父驱动程序添加一个名为 "parent"
的节点,其中包含 fidl.examples.EchoService
服务优惠。在 DFv2 中,绑定到此节点的驱动程序可针对该 FIDL 服务节点属性设置绑定规则,例如:
using fidl.examples.echo;
fidl.examples.echo.Echo == fidl.examples.echo.Echo.ZirconTransport;
如需了解详情,请参阅 FIDL 教程页面的生成的绑定库部分。
将单元测试更新为 DFv2
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.h
TestSynchronizedDispatcher
- 此类是驱动程序调度程序的 RAII 封装容器。
以下库可能有助于编写驱动程序单元测试:
//src/devices/bus/testing/fake-pdev/fake-pdev.h
- 此辅助库实现了pdev
FIDL 协议的虚构版本。
最后,以下示例单元测试涵盖不同的配置和测试用例:
//sdk/lib/driver/component/cpp/tests/driver_base_test.cc
- 此文件包含驱动程序测试可以采用的不同线程模型的示例。//sdk/lib/driver/component/cpp/tests/driver_fidl_test.cc
- 此文件演示了如何针对驱动程序传输和 Zircon 传输以及devfs
使用传入和传出 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
//third_party/iwlwifi/platform/driver-inspector.cc
本部分中提及的所有文档页面:
- 班卓琴
- 驱动程序和节点
- 驾驶员通信
- 驱动程序和节点
- 驱动程序调度程序和线程
- 驱动程序
- 复合节点
- 公开驱动程序功能
- Fuchsia 组件检查概览
- 模拟 DDK 迁移
- 拆解序列示例(来自设备驱动程序生命周期)
- 父级驱动程序(服务器)(来自 FIDL 教程)
- 生成的绑定库(来源:FIDL 教程)