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

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

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

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

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

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

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

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

设置兼容型设备服务器

本部分提供了基于兼容型设备服务器的设置说明, (请参阅 DFv2 Simple 驱动程序示例)。

具体步骤如下:

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

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

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

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

    "//sdk/lib/driver/compat/cpp",
    
  2. 请在驾驶员处添加 device_server.h 标头 源文件:

    #include <lib/driver/compat/cpp/device_server.h>
    
  3. driver_component/driver.shard.cml 分片包含在驱动程序的 组件清单 (.cml),例如:

     include: [
         "driver_component/driver.shard.cml",
         "inspect/client.shard.cml",
         "syslog/client.shard.cml",
     ],
    

    (来源:simple_driver.cml

    此分片定义了以下各项的 usecapabilitiesexpose 字段: fuchsia.driver.compat.Service

通过此设置,您的 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);
    

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

    • incomingoutgoingnode_name 的值可通过 使用相同名称调用 DriverBase 的访问器(请参阅下面的示例)。

    • 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);
    

    这些参数与 同步 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::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() 函数设置 offers2 NodeAddArgs 结构体中的字段,例如:

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 对象的 在 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::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();
    

    此设置会从 BanjoServer 对象。

  4. 初始化兼容型设备服务器时,将 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();
        }
    }
    

关于向后代提供此 Banjo 协议的其余步骤 DFv1 驱动程序,请参阅 在 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_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_PRIVATE, metadata.value().data(),
                           metadata.value().size());

转发元数据

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

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

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

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

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

    zx::result<> result = compat_server_.Initialize(
           incoming(), outgoing(), node_name(), child_name,
           compat::ForwardMetadata::Some({DEVICE_METADATA_GPT_INFO}));
    
  • 如果您不想转发元数据,请将该参数设置为 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");