本指南介绍了如何在 DFv2 驱动程序中设置和使用兼容设备服务器,以便与 DFv1 驱动程序通信。
在迁移过程中,兼容设备服务器可帮助 DFv2 驱动程序保持与 DFv1 驱动程序的兼容性。兼容设备服务器提供 fuchsia_driver_compat::Device 接口(请参阅 compat.fidl)。此接口允许 DFv2 驱动程序向后代 DFv1 驱动程序提供其资源。
兼容设备服务器的主要功能包括:
资源共享:将资源从 DFv2 驱动程序无缝提供给后代 DFv1 驱动程序。
Banjo 服务:向 DFv1 驱动程序提供在 DFv2 驱动程序中实现的 Banjo 协议。
元数据处理:转发、添加和解析 DFv1 元数据,以实现 DFv2 和 DFv1 驱动程序之间的通信。
本指南提供了分步说明和示例,可帮助您完成以下任务:
设置兼容设备服务器:在 DFv2 驱动程序中设置兼容设备服务器,包括初始化(同步或异步)并将其提供给子节点。
向后代 DFv1 驱动程序提供 Banjo 服务:配置 Banjo 协议并与后代 DFv1 驱动程序共享。
转发、添加和解析 DFv1 元数据:添加、转发和检索元数据,确保 DFv2 和 DFv1 驱动程序之间实现无缝信息交换。
设置兼容设备服务器
本部分基于 DFv2 简单驱动程序示例提供了兼容设备服务器设置说明。
具体步骤如下:
1. 将兼容设备服务器指定为依赖项
如需在 DFv2 驱动程序中将兼容设备服务器指定为依赖项,请执行以下操作:
将以下依赖项添加到
BUILD.gn中的fuchsia_driver目标:"//sdk/lib/driver/compat/cpp",在驱动程序的源文件中添加
device_server.h头文件:#include <lib/driver/compat/cpp/device_server.h>在驱动程序的组件清单 (
.cml) 中添加driver_component/driver.shard.cml分片,例如:include: [ "driver_component/driver.shard.cml", "inspect/client.shard.cml", "syslog/client.shard.cml", ],(来源:
simple_driver.cml)此分片定义了
fuchsia.driver.compat.Service的use、capabilities和expose字段。
通过此设置,您的 DFv2 驱动程序现在可以为后代节点创建 DeviceServer 对象。每个 DeviceServer 对象都必须只有一个目标节点。
2. 同步或异步初始化兼容设备服务器
同步或异步初始化 DeviceServer 对象。默认情况下,建议使用同步初始化而不是异步初始化。
不过,如果您符合以下任一条件,不妨考虑使用异步初始化:
不允许在当前调度程序上进行同步或阻塞调用。
您的 DFv1 驱动程序已针对异步代码进行了结构化,并且您需要通过异步行为来提高性能。
同步初始化
对于同步初始化,请执行以下操作:
向类添加
SyncInitializedDeviceServer对象,例如:class SimpleDriver : public fdf::DriverBase2 { ... compat::SyncInitializedDeviceServer compat_server_; ... }在驱动程序实现中,调用此
DeviceServer对象的Initialize()函数:zx::result<> Initialize(const std::shared_ptr<fdf::Namespace>& incoming, const std::shared_ptr<fdf::OutgoingDirectory>& outgoing, const std::optional<std::string>& node_name, std::string_view child_node_name, const ForwardMetadata& forward_metadata = ForwardMetadata::None(), std::optional<DeviceServer::BanjoConfig> banjo_config = std::nullopt, const std::optional<std::string>& child_additional_path = std::nullopt);如需了解此函数的参数,请参阅下文:
incoming的值可以在Start期间通过获取和转换来自DriverContext的传入命名空间来传递。可以通过调用DriverBase2的outgoing()访问器来传递outgoing的值。可以通过调用DriverContext的node_name()访问器来传递node_name的值。child_node_name是相应DeviceServer对象的子目标节点的名称。不过,如果 DFv2 驱动程序节点与目标子节点之间存在任何中间节点,您需要将
child_additional_path设置为节点之间以/分隔的拓扑路径。例如,如果目标子节点之前有node-a和node-b,则child_additional_path的值需要为node-a/node-b/。forward_metadata包含有关要从父节点转发的元数据的信息。(如需了解详情,请参阅转发元数据。)banjo_config包含将 Banjo 协议提供给目标子节点的信息。不过,如果设备服务器未提供任何协议,您可以将该形参设置为std::nullopt。(如需了解详情,请参阅向后代 DFv1 驱动程序提供 Banjo 服务。)
以下示例初始化了一个同步兼容设备服务器对象:
// Initialize our compat server. { zx::result<> result = compat_server_.Initialize( incoming(), outgoing(), node_name(), child_name); if (result.is_error()) { return result.take_error(); } }
异步初始化
对于异步初始化,请执行以下操作:
向类添加
AsyncInitializedDeviceServer对象,例如:class SimpleDriver : public fdf::DriverBase2 { ... compat::AsyncInitializedDeviceServer compat_server_; ... }在驱动程序实现中,调用此
DeviceServer对象的Begin()函数:void Begin(const std::shared_ptr<fdf::Namespace>& incoming, const std::shared_ptr<fdf::OutgoingDirectory>& outgoing, const std::optional<std::string>& node_name, std::string_view child_node_name, fit::callback<void(zx::result<>)> callback, const ForwardMetadata& forward_metadata = ForwardMetadata::None(), std::optional<DeviceServer::BanjoConfig> banjo_config = std::nullopt, const std::optional<std::string>& child_additional_path = std::nullopt);这些参数与上面的同步
Initialize()函数几乎完全相同,只是callback字段除外。当
DeviceServer对象完成初始化并可供访问时,系统会调用该回调函数。建议您使用此回调函数添加子节点。其他参数值的填充方式与同步初始化相同。以下示例初始化了一个异步兼容设备服务器对象:
void SimpleDriver::OnDeviceServerInitialized(zx::result<> device_server_init_result) { // Add the child nodes here async_completer_.value()(zx::ok()); async_completer_.reset(); } void SimpleDriver::Start(fdf::DriverContext context, fdf::StartCompleter completer) { auto incoming = std::shared_ptr<fdf::Namespace>(context.take_incoming()); async_completer_.emplace(std::move(completer)); device_server_.Begin(incoming, outgoing(), context.node_name(), child_name, fit::bind_member<&SimpleDriver::OnDeviceServerInitialized>(this), compat::ForwardMetadata::Some({DEVICE_METADATA_PRIVATE})); }
3. 向目标子节点提供兼容设备服务器
兼容设备服务器初始化后,将其提供的服务传递给目标子节点。
不过,如果目标节点不是直接子节点,您需要将 offer 传递给后代链中的下一个最接近的子节点。例如,节点拓扑显示 A -> B -> C -> D,如果节点 A 是当前节点,而节点 D 是目标节点,那么您需要将优惠传递给节点 B,后者是链中最接近的子节点。
使用 DeviceServer 对象的 CreateOffers2() 函数设置 NodeAddArgs 结构体中的 offers2 字段,例如:
auto properties = std::vector{fdf::MakeProperty(BIND_PROTOCOL, ZX_PROTOCOL_SERIAL_IMPL_ASYNC)};
zx::result child_result = AddChild(child_name, properties, compat_server_.CreateOffers2());
if (child_result.is_error()) {
return child_result.take_error();
}
如果有任何其他优惠,请先将其添加到 DeviceServer 对象的 offers 中,然后再设置 NodeAddArgs 结构体中的 offers2 字段,例如:
auto offers = compat_server_.CreateOffers2();
offers.push_back(fdf::MakeOffer2<fuchsia_hardware_serialimpl::Service>(child_name));
auto properties = std::vector{fdf::MakeProperty(BIND_PROTOCOL, ZX_PROTOCOL_SERIAL_IMPL_ASYNC)};
zx::result child_result = AddChild(child_name, properties, std::move(offers));
if (child_result.is_error()) {
return child_result.take_error();
}
如果您的兼容设备服务器是同步初始化的,则需要在调用 SyncInitializedDeviceServer::Initialize() 函数后执行此操作。否则,您需要在传递给 AsyncInitializedDeviceServer::Begin() 调用的回调函数中执行此操作。
向后代 DFv1 驱动程序提供 Banjo 服务
如果您的 DFv2 驱动程序实现了 Banjo 协议,并希望将其提供给目标子节点,则需要将该协议添加到兼容设备服务器。
假设您的驱动程序实现了 Misc Banjo 协议,例如:
class ParentBanjoTransportDriver : public fdf::DriverBase2,
public ddk::MiscProtocol<ParentBanjoTransportDriver> {
...
(本部分中的示例基于 Banjo 传输示例。)
如需将 Misc Banjo 协议添加到兼容设备服务器,请执行以下操作:
在驱动程序的源文件中添加以下头文件:
#include <lib/driver/compat/cpp/banjo_server.h>为 Banjo 协议创建
compat::BanjoServer对象:compat::BanjoServer banjo_server_{ZX_PROTOCOL_<NAME>, this, &<name>_protocol_ops_};在上面的模板中,将
NAME替换为大写形式的协议名称,并将name替换为小写形式的协议名称。因此,对于MiscBanjo 协议,该对象如下所示:class ParentBanjoTransportDriver : public fdf::DriverBase2, public ddk::MiscProtocol<ParentBanjoTransportDriver> { ... private: compat::BanjoServer banjo_server_{ZX_PROTOCOL_MISC, this, &misc_protocol_ops_}; ... }创建
BanjoConfig对象,并将协议回调设置为 Banjo 服务器的回调,例如:compat::DeviceServer::BanjoConfig banjo_config; banjo_config.callbacks[ZX_PROTOCOL_MISC] = banjo_server_.callback();此设置会将 Banjo 配置信息从
BanjoServer对象传递到兼容设备服务器。初始化兼容设备服务器时,将
Initialize()函数的banjo_config字段设置为BanjoConfig对象,例如:// Initialize our compat server. { zx::result<> result = compat_server_.Initialize( incoming(), outgoing(), node_name(), child_name, ForwardMetadata::None(), std::move(banjo_config)); if (result.is_error()) { return result.take_error(); } }
如需了解向后代 DFv1 驱动程序提供此 Banjo 协议的其余步骤,请参阅在 DFv2 驱动程序中提供 Banjo 协议指南。
转发、添加和解析 DFv1 元数据
许多现有的 DFv1 驱动程序使用元数据在父级和子级之间传递信息。在 DFv2 驱动程序中,您可以使用兼容设备服务器执行以下操作:
添加和发送元数据
元数据通过驱动程序的兼容设备服务器传递给子节点。为了添加和发送元数据,驱动程序需要创建一个兼容设备服务器并调用其 AddMetadata() 函数:
zx_status_t AddMetadata(MetadataKey type, const void* data, size_t size);
(来源:device_server.h)
以下示例使用 AddMetadata() 函数添加元数据:
const uint64_t metadata = 0xAABBCCDDEEFF0011;
zx_status_t status = compat_device_server.AddMetadata(
DEVICE_METADATA_PRIVATE, &metadata, sizeof(metadata));
不过,如果元数据是 FIDL 类型,您需要先通过 fidl::Persist() 调用将其持久化,然后将其添加到 AddMetadata() 函数,例如:
fuchsia_hardware_i2c_businfo::wire::I2CChannel local_channel(channel);
fit::result metadata = fidl::Persist(local_channel);
if (!metadata.is_ok()) {
fdf::error("Failed to fidl-encode channel: {}", metadata.error_value().FormatDescription());
return zx::error(metadata.error_value().status());
}
compat_server->AddMetadata(DEVICE_METADATA_PRIVATE, metadata.value().data(),
metadata.value().size());
转发元数据
如果您的 DFv2 驱动程序从父节点接收元数据,并且需要将部分或全部元数据传递给其子节点,您可以使用兼容设备服务器转发元数据。
如需转发元数据,请在初始化驱动程序的 DeviceServer 对象时设置 forward_metadata 参数:
如果您想转发所有元数据,请将该参数设置为
ForwardMetadata::All(),例如:// In Start(fdf::DriverContext context, fdf::StartCompleter completer): // auto incoming = std::shared_ptr<fdf::Namespace>(context.take_incoming()); // zx::result<> result = compat_server_.Initialize( incoming, outgoing(), context.node_name(), child_name, compat::ForwardMetadata::All());如果您只想转发部分元数据,请创建一个包含
ForwardMetadata Some(std::unordered_set<MetadataKey> filter)的ForwardMetadata对象,并将此对象传递给相应参数。以下示例仅转发具有
DEVICE_METADATA_GPT_INFO键的元数据:// In Start(fdf::DriverContext context, fdf::StartCompleter completer): // auto incoming = std::shared_ptr<fdf::Namespace>(context.take_incoming()); // zx::result<> result = compat_server_.Initialize( incoming, outgoing(), context.node_name(), child_name, compat::ForwardMetadata::Some({DEVICE_METADATA_GPT_INFO}));如果您不想转发元数据,请将该参数设置为
ForwardMetadata::None(),例如:// In Start(fdf::DriverContext context, fdf::StartCompleter completer): // auto incoming = std::shared_ptr<fdf::Namespace>(context.take_incoming()); // zx::result<> result = compat_server_.Initialize( incoming, outgoing(), context.node_name(), child_name, compat::ForwardMetadata::None());
检索元数据
兼容设备服务器的元数据库 (metadata.h) 提供了一些辅助函数,用于从驱动程序检索元数据。
如需使用此元数据库,请执行以下操作:
在
BUILD.gn中向fuchsia_driver目标添加以下依赖项:"//sdk/lib/driver/compat/cpp",在驱动程序的源文件中添加以下头文件:
#include <lib/driver/compat/cpp/metadata.h>如需从驱动程序的兼容设备服务器检索元数据,请使用
compat::GetMetadata<T>()方法,并将T替换为元数据类型。以下示例检索了具有
DEVICE_METADATA_VREG键和fuchsia_hardware_vreg::wire::Metadata类型的元数据:fidl::Arena arena; zx::result<fuchsia_hardware_vreg::wire::Metadata> metadata = compat::GetMetadata<fuchsia_hardware_vreg::wire::Metadata>( incoming, arena, DEVICE_METADATA_VREG); if (metadata.is_error()) { fdf::error("Failed to get metadata: {}", metadata); return completer(metadata.take_error()); }如需查看元数据键和类型的完整列表,请参阅此文件。
(可选)如果驱动程序是复合的,您需要传递父级的名称,以指定元数据来自哪个父节点,例如:
zx::result<fuchsia_hardware_vreg::wire::Metadata> metadata = compat::GetMetadata<fuchsia_hardware_vreg::wire::Metadata>( incoming, arena, DEVICE_METADATA_VREG, "pdev");
不过,如果元数据类型是动态大小的数组,请使用 compat::GetMetadataArray<T>() 并将 T 替换为数组的类型。
假设我们需要检索 DEVICE_METADATA_AML_VOLTAGE_TABLE 的元数据,这是一个 aml_voltage_table_t 结构体的数组:
#define DEVICE_METADATA_AML_VOLTAGE_TABLE (0x41565400 | (DEVICE_METADATA_PRIVATE)) // AVTp
然后,您需要将 T 替换为 aml_voltage_table_t 并检索元数据,如下所示:
zx::result<std::vector<aml_voltage_table_t>> voltage_table =
compat::GetMetadataArray<aml_voltage_table_t>(
incoming, DEVICE_METADATA_AML_VOLTAGE_TABLE);
if (voltage_table.is_error()) {
fdf::error("Failed to get voltage table metadata: {}", voltage_table);
return zx::error(voltage_table.take_error());
}
同样,如果驱动程序是复合驱动程序,请传递父驱动程序的名称以指定元数据来自哪个父节点,例如:
zx::result<std::vector<aml_voltage_table_t>> voltage_table =
compat::GetMetadataArray<aml_voltage_table_t>(
incoming, DEVICE_METADATA_AML_VOLTAGE_TABLE, "pdev");