将其他服务更新到 DFv2

本页介绍了将 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 驱动程序能够发现您的驱动程序服务,请执行以下操作:

  1. 更新驱动程序的 .fidl 文件。

    DFv2 中的协议发现需要为驱动程序的协议添加 service 字段,例如:

    library fuchsia.example;
    
    @discoverable
    @transport("Driver")
    protocol MyProtocol {
        MyMethod() -> (struct {
            ...
        });
    };
    
    service Service {
        my_protocol client_end:MyProtocol;
    };
    
  2. 更新子驱动程序。

    DFv2 驱动程序可以与协议连接,方法与 FIDL 服务相同,例如:

    incoming()->Connect<fuchsia_example::Service::MyProtocol>();
    

    您还需要更新组件清单 (.cml) 文件,才能使用驱动程序运行时服务,例如:

    use: [
        { service: "fuchsia.example.Service" },
    ]
    
  3. 更新父级驱动程序。

    父级驱动程序需要使用 fdf::DriverBaseoutgoing() 函数来获取 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 驱动程序(使用兼容性 shim)

如需让其他 DFv1 驱动程序能够发现您的 DFv2 驱动程序的服务,请执行以下操作:

  1. 更新了 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",
              },
          ],
      }
      
  2. 更新 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 驱动程序交互的某些其他驱动程序的组件清单文件。

请执行以下操作:

  1. 使用以下更改更新叶驱动程序(即没有子驱动程序)的组件清单:

    • include 字段中移除了 //sdk/lib/driver/compat/compat.shard.cml
    • program.compat 字段替换为 program.binary
  2. 更新执行以下任务的其他驱动程序的组件清单:

    • 访问内核 args
    • 创建复合设备。
    • 检测重新启动、关闭或重新绑定调用。
    • 使用 Banjo 协议与其他驾驶员交谈。
    • 访问父级驱动程序中的元数据或将其转发。
    • 与绑定到您的驱动程序添加的节点的 DFv1 驱动程序进行通信。

    对于这些驱动程序,请使用以下更改更新其组件清单:

    • 将一些 use 功能从 compat.shard.cml 复制到组件清单,例如:

      use: [
          {
              protocol: [
                  "fuchsia.boot.Arguments",
                  "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",
      },
      

从 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 方法的此实现

如需详细了解 devfs 设置流程,请参阅在 DFv2 驱动程序中设置 devfs 指南。

使用调度程序

调度程序会从 FIDL 客户端-服务器对之间的通道中提取数据。默认情况下,此通道中的 FIDL 调用是异步的。

如需在 DFv2 中向驱动程序引入异步功能,请参阅以下建议:

  • fdf::Dispatcher::GetCurrent() 方法可为您提供驱动程序正在运行的默认调度程序(请参阅此 aml-ethernet 驱动程序示例)。建议您尽可能仅使用此默认调度程序。

  • 考虑使用多个调度程序的原因包括(但不限于):

    • 为了提高性能,驱动程序需要并行处理。

    • 驱动程序想要执行阻塞操作(因为它是旧版驱动程序或正在移植到 Fuchsia 的非 Fuchsia 驱动程序),并且需要在阻塞期间处理更多工作。

  • 如果需要多个调度程序,fdf::Dispatcher::Create() 方法可以为您的驱动程序创建新的调度程序。不过,您必须对默认调度程序调用此方法(例如,在 Start() 钩子中调用此方法),以便驱动程序主机知道属于驱动程序的其他调度程序。

  • 在 DFv2 中,您无需手动关闭调度程序。它们将在 PrepareStop()Stop() 调用之间关闭。

如需详细了解如何迁移驱动程序以使用多个调度程序,请参阅更新 DFv1 驱动程序以使用非默认调度程序部分(在从 Banjo 迁移到 FIDL 中)。

使用 DFv2 检查

如需在 DFv2 中设置由驱动程序维护的检查指标,您可以使用 fdf::DriverBase::inspector() 提供的 ComponentInspector

inspect::Node& root = inspector().root();

如果应使用自定义 Inspector,请先调用 fdf::DriverBase::InitInspectorExactlyOnce(inspector),然后再访问 inspector() 方法。

DFv2 检查不需要将 inspect::Inspector 的 VMO 传递给驱动程序框架。

DFv2 驱动程序的“检查”将显示为归因于驱动程序(因为它是“正常”组件)。 不过,DFv2 驱动程序的标识名不稳定,因此在针对驱动程序编写隐私权选择器时,您应使用通配符和名称过滤器来引用特定驱动程序。例如,

bootstrap/*-drivers*:[name=sysmem]root

如需在调试期间访问驱动程序的“检查”功能,您可以使用所有常规工具,例如

ffx inspect show --name sysmem "bootstrap/*-drivers*:root"

ffx inspect show --component sysmem.cm

(可选)实现您自己的 load_firmware 方法

如果您的 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 类将模拟的 FIDL 服务器提供给 DFv2 驱动程序。

以下库可用于对 DFv2 驱动程序进行单元测试:

  • //sdk/lib/driver/testing/cpp

    • TestNode - 此类实现了 fuchsia_driver_framework::Node 协议,可提供给驱动程序以创建子节点。测试还会使用此类来访问驱动程序创建的子节点。

    • TestEnvironment - OutgoingDirectory 对象的封装容器,用作受测驱动程序的传入命名空间的后备 VFS(虚拟文件系统)。

    • DriverUnderTest - 此类是被测驱动程序的 RAII(Resource Acquisition Is Initialization)封装容器。

    • DriverRuntime - 此类是托管驱动程序运行时线程池上的 RAII 封装容器。

  • //sdk/lib/driver/testing/cpp/driver_runtime.h

    • TestSynchronizedDispatcher - 此类是驱动程序调度程序的 RAII 封装容器。

以下库可能有助于编写驱动程序单元测试:

最后,以下单元测试示例涵盖了不同的配置和测试用例:

其他资源

一些 DFv2 驱动程序示例:

本部分中提及的所有 Gerrit 更改

本部分中提及的所有源代码文件

本部分中提及的所有文档页面