在 DFv2 驱动程序中设置 compat 设备服务器

本指南介绍了如何在 DFv2 驱动程序中设置和使用兼容型设备服务器,以便与 DFv1 驱动程序进行通信。

兼容型设备服务器有助于 DFv2 驱动程序在迁移过程中保持与 DFv1 驱动程序的兼容性。兼容型设备服务器提供 fuchsia_driver_compat::Device 接口(请参阅 compat.fidl)。此接口允许 DFv2 驱动程序向后代 DFv1 驱动程序提供其资源。

兼容型设备服务器的主要功能包括:

  • 资源共享:将 DFv2 驱动程序中的资源无缝提供给后代 DFv1 驱动程序。

  • Banjo 服务:向 DFv1 驱动程序提供您的 DFv2 驱动程序中实现的 Banjo 协议。

  • 元数据处理:转发、添加和解析 DFv2 和 DFv1 驱动程序之间通信的 DFv1 元数据。

本指南提供了分步说明和示例,可帮助您完成以下任务:

设置 compat 设备服务器

本部分基于 DFv2 简单驱动程序示例提供了兼容型设备服务器设置说明。

相关步骤如下:

  1. 将兼容型设备服务器指定为依赖项
  2. 同步或异步初始化兼容型设备服务器
  3. 向目标子节点提供兼容型设备服务器

1. 将兼容型设备服务器指定为依赖项

如需将兼容型设备服务器指定为 DFv2 驱动程序中的依赖项,请执行以下操作:

  1. 将以下依赖项添加到 BUILD.gn 中的 fuchsia_driver 目标:

    "//sdk/lib/driver/compat/cpp",
    
  2. 在驱动程序的源文件中添加 device_server.h 头文件:

    #include <lib/driver/compat/cpp/device_server.h>
    
  3. 在驱动程序的组件清单 (.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.Serviceusecapabilitiesexpose 字段。

通过此设置,您的 DFv2 驱动程序现在可以为后代节点创建 DeviceServer 对象。每个 DeviceServer 对象只能有一个目标节点。

2. 同步或异步初始化兼容型设备服务器

同步或异步初始化 DeviceServer 对象。默认情况下,我们建议使用同步而非异步初始化。

但是,如果您符合以下条件之一,可以考虑使用异步初始化

  • 不允许对当前调度程序进行同步或阻塞调用。

  • 您的 DFv1 驱动程序已经针对异步代码进行了结构化设计,并且您需要通过异步行为提升性能。

同步初始化

对于同步初始化,请执行以下操作:

  1. SyncInitializedDeviceServer 对象添加到类中,例如:

    class SimpleDriver : public fdf::DriverBase {
     ...
        compat::SyncInitializedDeviceServer compat_server_;
     ...
    }
    
  2. 在驱动程序实现中,调用此 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);
    

    有关此函数的参数,请参阅下文:

    • 您可以通过调用同名的 DriverBase 访问器来传递 incomingoutgoingnode_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();
        }
      }
    

异步初始化

对于异步初始化,请执行以下操作:

  1. AsyncInitializedDeviceServer 对象添加到类中,例如:

    class SimpleDriver : public fdf::DriverBase {
     ...
        compat::AsyncInitializedDeviceServer compat_server_;
     ...
    }
    
  2. 在驱动程序实现中,调用此 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);
    

    除了 callback 字段以外,这些参数与上面的同步 Initialize() 函数几乎完全相同。

    DeviceServer 对象完成初始化并准备好可供访问时,系统会调用回调函数。建议您使用此回调函数来添加子节点。其他参数值的填充方式与同步初始化相同。

    以下示例会初始化异步 compat 设备服务器对象:

    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::StartCompleter completer) {
       async_completer_.emplace(std::move(completer));
       device_server_.Begin(incoming(), outgoing(), node_name(), child_name,
       fit::bind_member<&MyDriver::OnDeviceServerInitialized>(this),
       compat::ForwardMetadata::Some({DEVICE_METADATA_MAC_ADDRESS}));
    }
    

3. 向目标子节点提供兼容型设备服务器

初始化兼容设备服务器后,将其优惠传递给目标子节点。

但是,如果目标节点不是直接子节点,您需要将优惠传递给后代链中下一个最近的子节点。例如,节点拓扑显示 A -> B -> C -> D,如果节点 A 是当前节点,节点 D 是目标节点,则您需要将优惠传递给节点 B(节点是链中最近的子节点)。

使用 DeviceServer 对象的 CreateOffers2() 函数在 NodeAddArgs 结构体中设置 offers2 字段,例如:

fuchsia_driver_framework::NodeAddArgs args{
    {
        .name = std::string(child_name),
        .properties = {
            {
                fdf::MakeProperty(BIND_PROTOCOL, ZX_PROTOCOL_SERIAL_IMPL_ASYNC),
            }
        },
        .offers2 = compat_server_.CreateOffers2(),
    },
};

fidl::Arena arena;
node_client_
    ->AddChild(fidl::ToWire(arena, std::move(args)),
                std::move(node_controller->server));

如果有任何其他优惠,请先将其添加到 DeviceServer 对象的优惠中,然后再在 NodeAddArgs 结构体中设置 offers2 字段,例如:

auto offers = device_server_.CreateOffers2();
offers.push_back(fdf::MakeOffer2<fuchsia_hardware_serialimpl::Service>(child_name));

fuchsia_driver_framework::NodeAddArgs args{
    {
        .name = std::string(child_name),
        .properties = {
            {
                fdf::MakeProperty(BIND_PROTOCOL, ZX_PROTOCOL_SERIAL_IMPL_ASYNC),
            }
        },
        .offers2 = std::move(offers),
    },
};

fidl::Arena arena;
node_client_
    ->AddChild(fidl::ToWire(arena, std::move(args)),
                std::move(node_controller->server));

如果您的兼容型设备服务器已同步初始化,则您需要在调用 SyncInitializedDeviceServer::Initialize() 函数后执行此操作。否则,您需要在传递给 AsyncInitializedDeviceServer::Begin() 调用的回调函数中执行此操作。

为后代 DFv1 驱动程序提供 Banjo 服务

如果您的 DFv2 驱动程序实现了 Banjo 协议,并且希望将其提供给目标子节点,则需要将该协议添加到兼容型设备服务器。

假设您的驱动程序实现了 Misc Banjo 协议,例如:

class ParentBanjoTransportDriver : public fdf::DriverBase,
                                   public ddk::MiscProtocol<ParentBanjoTransportDriver> {
...

(本部分中的示例基于 Banjo 传输示例。)

如需将 Misc Banjo 协议添加到兼容型设备服务器,请执行以下操作:

  1. 在驱动程序的源文件中添加以下头文件:

    #include <lib/driver/compat/cpp/banjo_server.h>
    
  2. 为 Banjo 协议创建一个 compat::BanjoServer 对象:

    compat::BanjoServer banjo_server_{ZX_PROTOCOL_<NAME>, this, &<name>_protocol_ops_};
    

    在上述模板中,将 NAME 替换为协议名称(采用大写形式),将 name 替换为小写形式。因此,对于 Misc Banjo 协议,该对象如下所示:

    class ParentBanjoTransportDriver : public fdf::DriverBase,
                                       public ddk::MiscProtocol<ParentBanjoTransportDriver> {
     ...
    
     private:
      compat::BanjoServer banjo_server_{ZX_PROTOCOL_MISC, this, &misc_protocol_ops_};
     ...
    }
    
  3. 创建一个 BanjoConfig 对象,并将协议回调设置为 Banjo 服务器的回调,例如:

    compat::DeviceServer::BanjoConfig banjo_config;
    banjo_config.callbacks[ZX_PROTOCOL_MISC] = banjo_server_.callback();
    

    此设置会将 Banjo 配置信息从 BanjoServer 对象传递到兼容型设备服务器。

  4. 初始化 compat 设备服务器时,请将 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 驱动程序中,您可以使用 compat 设备服务器执行以下操作:

添加并发送元数据

元数据通过驱动程序的兼容型设备服务器传递给子节点。如需添加和发送元数据,驱动程序需要创建一个兼容型设备服务器,并调用其 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 类型,则需要先将其添加至 AddMetadata() 函数,然后再通过 fidl::Persist() 调用使其保留下来,例如:

fuchsia_hardware_i2c_businfo::wire::I2CChannel local_channel(channel);
fit::result metadata = fidl::Persist(local_channel);
if (!metadata.is_ok()) {
  FDF_LOG(ERROR, "Failed to fidl-encode channel: %s",
          metadata.error_value().FormatDescription().data());
  return zx::error(metadata.error_value().status());
}
compat_server->AddMetadata(DEVICE_METADATA_I2C_DEVICE, metadata.value().data(),
                           metadata.value().size());

转发元数据

如果您的 DFv2 驱动程序从父节点接收元数据,并需要将部分或全部元数据传递给其子节点,则您可以使用兼容型设备服务器转发元数据。

如需转发元数据,请在初始化驱动程序的 DeviceServer 对象时设置 forward_metadata 参数:

  • 如果要转发所有元数据,请将该参数设置为 ForwardMetadata::All(),例如:

    zx::result<> result = compat_server_.Initialize(
           incoming(), outgoing(), node_name(), child_name,
           compat::ForwardMetadata::All());
    
  • 如果您只想转发部分元数据,请使用 ForwardMetadata Some(std::unordered_set<MetadataKey> filter) 创建一个 ForwardMetadata 对象,并将此对象传递给参数。

    以下示例仅使用 DEVICE_METADATA_ADC 键转发元数据:

    zx::result<> result = compat_server_.Initialize(
           incoming(), outgoing(), node_name(), child_name,
           compat::ForwardMetadata::Some({DEVICE_METADATA_ADC}));
    
  • 如果您不想转发元数据,请将该参数设置为 ForwardMetadata::None(),例如:

    zx::result<> result = compat_server_.Initialize(
           incoming(), outgoing(), node_name(), child_name,
                                  compat::ForwardMetadata::None());
    

检索元数据

兼容型设备服务器的元数据库 (metadata.h) 提供了用于从驱动程序检索元数据的辅助函数。

如需使用此元数据库,请执行以下操作:

  1. 将以下依赖项添加到 BUILD.gn 中的 fuchsia_driver 目标:

    "//sdk/lib/driver/compat/cpp",
    
  2. 在驱动程序的源文件中添加以下头文件:

    #include <lib/driver/compat/cpp/metadata.h>
    
  3. 如需从驱动程序的兼容型设备服务器检索元数据,请使用 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_LOG(ERROR, "Failed to get metadata  %s", metadata.status_string());
      return completer(metadata.take_error());
    }
    

    如需查看元数据键和类型的完整列表,请参阅此文件

  4. (可选)如果驱动程序是复合驱动程序,您需要传递父节点的名称,以指定元数据来自哪个父节点,例如:

    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_GPIO_PINS 的元数据,该元数据是 gpio_pin_t 结构体的数组:

// type: array of gpio_pin_t
#define DEVICE_METADATA_GPIO_PINS 0x4F495047  // GPIO

然后,您需要将 T 替换为 gpio_pin_t,并检索元数据,如下所示:

zx::result<std::vector<gpio_pin_t>>  gpio_pins =
     compat::GetMetadataArray<gpio_pin_t>(incoming(), DEVICE_METADATA_GPIO_PINS);
 if (gpio_pins.is_error()) {
   FDF_LOG(ERROR, "%s: Failed to get gpio pin metadata");
   return zx::error(gpio_pins.take_error());
 }

同样,如果驱动程序是复合驱动程序,则传递父节点的名称以指定元数据来自哪个父节点,例如:

zx::result<std::vector<gpio_pin_t>> gpio_pins =
     compat::GetMetadataArray<gpio_pin_t>(incoming(), DEVICE_METADATA_GPIO_PINS, "pdev");