编写最小 DFv2 驱动程序

本指南介绍了创建最小 DFv2 驱动程序所涉及的步骤。

本指南中的说明基于最小框架驱动程序,该驱动程序提供了在 Fuchsia 系统中构建、加载和注册新 DFv2 驱动程序所需的最低实现。

相关步骤如下:

  1. 创建驱动程序头文件
  2. 创建驱动程序源文件
  3. 添加驱动程序导出宏
  4. 创建 build 文件
  5. 编写绑定规则
  6. 创建驱动程序组件

如需了解更多与 DFv2 相关的功能,请参阅其他任务

创建驱动程序头文件

要为 DFv2 驱动程序创建头文件,请执行以下操作:

  1. 为驱动程序创建新的头文件 (.h)(例如 skeleton_driver.h)。

  2. 在头文件中添加以下接口:

    #include <lib/driver/component/cpp/driver_base.h>
    
  3. DriverBase 类添加一个接口,例如:

    #include <lib/driver/component/cpp/driver_base.h>
    
    namespace skeleton {
    
    class SkeletonDriver : public fdf::DriverBase {
     public:
      SkeletonDriver(fdf::DriverStartArgs start_args, fdf::UnownedSynchronizedDispatcher driver_dispatcher);
    
      // Called by the driver framework to initialize the driver instance.
      zx::result<> SkeletonDriver::Start() override;
    };
    
    }  // namespace skeleton
    

    (来源:skeleton_driver.h

创建驱动程序源文件

如需实现 DriverBase 类的基本方法,请执行以下操作:

  1. 为驱动程序创建新的源文件 (.cc)(例如 skeleton_driver.cc)。

  2. 包含为驱动程序创建的头文件,例如:

    #include "skeleton_driver.h"
    
  3. 实现类的基本方法,例如:

    #include "skeleton_driver.h"
    
    namespace skeleton {
    
    SkeletonDriver::SkeletonDriver(fdf::DriverStartArgs start_args,
                               fdf::UnownedSynchronizedDispatcher driver_dispatcher)
        : DriverBase("skeleton_driver", std::move(start_args),
              std::move(driver_dispatcher)) {
    }
    
    zx::result<> SkeletonDriver::Start() {
      return zx::ok();
    }
    
    }  // namespace skeleton
    

    (来源:skeleton_driver.cc

    此驱动程序构造函数需要将驱动程序名称(例如 "skeleton_driver")、start_argsdriver_dispatcher 传递给 DriverBase 类。

添加驱动程序导出宏

如需添加驱动程序导出宏,请执行以下操作:

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

    #include <lib/driver/component/cpp/driver_export.h>
    
  2. 在驱动程序源文件的底部添加以下宏(用于导出驱动程序类):

    FUCHSIA_DRIVER_EXPORT(skeleton::SkeletonDriver);
    

    例如:

    #include <lib/driver/component/cpp/driver_base.h>
    #include <lib/driver/component/cpp/driver_export.h>
    
    #include "skeleton_driver.h"
    
    namespace skeleton {
    
    SkeletonDriver::SkeletonDriver(fdf::DriverStartArgs start_args,
                               fdf::UnownedSynchronizedDispatcher driver_dispatcher)
        : DriverBase("skeleton_driver", std::move(start_args),
              std::move(driver_dispatcher)) {
    }
    
    zx::result<> SkeletonDriver::Start() {
      return zx::ok();
    }
    
    }  // namespace skeleton
    
    FUCHSIA_DRIVER_EXPORT(skeleton::SkeletonDriver);
    

    (来源:skeleton_driver.cc

创建 build 文件

如需为驱动程序创建 build 文件,请执行以下操作:

  1. 创建新的 BUILD.gn 文件。
  2. 添加以下行以导入驱动程序构建规则:

    import("//build/drivers.gni")
    
  3. 为驱动程序添加目标,例如:

    fuchsia_driver("driver") {
      output_name = "skeleton_driver"
      sources = [ "skeleton_driver.cc" ]
      deps = [
        "//sdk/lib/driver/component/cpp",
        "//src/devices/lib/driver:driver_runtime",
      ]
    }
    

    (来源:BUILD.gn

    output_name 字段在所有驱动程序中必须是唯一的。

编写绑定规则

如需为驱动程序编写绑定规则,请执行以下操作:

  1. 为驱动程序创建新的绑定规则文件 (.bind)(例如 skeleton_driver.bind)。

  2. 添加基本绑定规则,例如:

    using gizmo.example;
    gizmo.example.TEST_NODE_ID == "skeleton_driver";
    

    (来源:skeleton_driver.bind

  3. BUILD.gn 文件中,添加以下行以导入绑定构建规则:

    import("//build/bind/bind.gni")
    
  4. BUILD.gn 文件中,为驱动程序的绑定规则添加目标,例如:

    driver_bind_rules("bind") {
      rules = "skeleton.bind"
      bind_output = "skeleton_driver.bindbc"
      deps = [ "//examples/drivers/bind_library:gizmo.example" ]
    }
    

    (来源:BUILD.gn

    bind_output 字段在所有驱动程序中必须是唯一的。

创建驱动程序组件

如需为驱动程序创建 Fuchsia 组件,请执行以下操作:

  1. meta 目录(例如 skeleton_driver.cml)中创建新的组件清单文件 (.cml)。

  2. 包含以下组成部分分片:

    {
        include: [
            "inspect/client.shard.cml",
            "syslog/client.shard.cml",
        ],
    }
    
  3. 请使用以下格式添加驱动程序的 program 信息:

    {
        program: {
            runner: "driver",
            binary: "driver/<OUTPUT_NAME>.so",
            bind: "meta/bind/<BIND_OUTPUT>",
        },
    }
    

    binary 字段必须与 BUILD.gn 文件的 fuchsia_driver 目标中的 output_name 字段匹配,bind 字段必须与 driver_bind_rules 目标中的 bind_output 匹配,例如:

    {
        include: [
            "inspect/client.shard.cml",
            "syslog/client.shard.cml",
        ],
        program: {
            runner: "driver",
            binary: "driver/skeleton_driver.so",
            bind: "meta/bind/skeleton.bindbc",
        },
    }
    

    (来源:skeleton_driver.cml

  4. 创建新的 JSON 文件以提供组件的信息(例如 component-info.json)。

  5. 以 JSON 格式添加驱动程序组件的信息,例如:

    {
        "short_description": "Driver Framework example for a skeleton DFv2 driver",
        "manufacturer": "",
        "families": [],
        "models": [],
        "areas": [
            "DriverFramework"
        ]
    }
    

    (来源:component-info.json

  6. BUILD.gn 文件中,添加下面这行代码以导入组件构建规则:

    import("//build/components.gni")
    
  7. BUILD.gn 文件中,为驱动程序组件添加目标,例如:

    fuchsia_driver_component("component") {
      component_name = "skeleton"
      manifest = "meta/skeleton.cml"
      deps = [
         ":bind",
         ":driver"
      ]
      info = "component-info.json"
    }
    

    (来源:BUILD.gn

    有关这些字段的规则,请参阅下文:

    • manifest 字段设置为驱动程序的 .cml 文件的位置。
    • info 字段设置为驱动程序组件信息 JSON 文件的位置。
    • 设置 deps 数组以包含 BUILD.gn 文件中的 fuchsia_driverdriver_bind_rules 目标。

现在,您可以在 Fuchsia 系统中构建、加载和注册此 DFv2 驱动程序

其他任务

本部分提供了可添加到最低 DFv2 驱动程序的其他功能:

添加日志

默认情况下,如需从 DFv2 驱动程序输出日志,请使用 FDF_LOG 宏,例如:

FDF_LOG(INFO, "Starting SimpleDriver")

除了使用 FDF_LOG 宏之外,您还可以使用 Fuchsia 的结构化日志记录器库 (structured_logger.h) 输出日志,该库使用 FDF_SLOG 宏。

如需使用 DFv2 驱动程序中的结构化日志,请执行以下操作:

  1. 包含以下标题:

    #include <lib/driver/logging/cpp/structured_logger.h>
    
  2. 使用 FDF_SLOG 宏可以输出日志,例如:

    FDF_SLOG(ERROR, "Failed to add child", KV("status", result.status_string()));
    

添加子节点

DFv2 驱动程序可以在 fuchsia.driver.framework FIDL 库中使用以下 Node 协议添加子节点:

open protocol Node {
    flexible AddChild(resource struct {
        args NodeAddArgs;
        controller server_end:NodeController;
        node server_end:<Node, optional>;
    }) -> () error NodeError;
};

驱动程序可以使用 DriverStartArgs 对象中的 node() 值或 DriverBase 类的 node() 函数来连接到 Node 协议。

除了使用 Node 协议之外,您还需要创建 NodeController 端点。AddChild() 方法需要 fuchsia.driver.framework NodeController 协议的服务器端。另一方面,驱动程序负责存储并保存协议的客户端。如果此客户端已取消分配,则驱动程序框架将移除该子节点。

如需使用 Node 协议添加子节点,请执行以下操作:

  1. 在您的 DFv2 驱动程序中,创建一个采用以下参数的 NodeAddArgs 对象:

    type NodeAddArgs = resource table {
        /// Name of the node.
        1: name NodeName;
        2: offers vector<fuchsia.component.decl.Offer>:MAX_OFFER_COUNT;
        3: symbols vector<NodeSymbol>:MAX_SYMBOL_COUNT;
        4: properties NodePropertyVector;
        5: devfs_args DevfsAddArgs;
    };
    

    (来源:topology.fidl

    • name 是子节点的名称。
    • offers 是父节点为子节点提供的功能。您可以使用 node_adds_args 库中的 MakeOffer() 函数创建这些模块。
    • symbols 是要提供给驱动程序的函数。对于 DFv2 驱动程序,可以忽略此参数。
    • properties 是子节点属性,用于确定将哪个驱动程序绑定到该子节点(如需了解详情,请参阅绑定规则教程)。您可以使用 node_adds_args 库中的 MakeProperty() 函数创建节点属性。
    • 如果子节点需要访问 devfs,则 devfs_args 是必需的。

    以下示例创建了一个 NodeAddArgs 对象:

    fidl::Arena arena;
    auto properties = std::vector{fdf::MakeProperty(arena, bind_fuchsia_test::TEST_CHILD, "simple")};
      auto args = fuchsia_driver_framework::wire::NodeAddArgs::Builder(arena)
                      .name(arena, "simple_child")
                      .properties(arena, std::move(properties))
                      .Build();
    
  2. 更新驱动程序的 DriverBase 类,以添加 FIDL 客户端对象。

    下面的示例使用 fidl::WireSyncClient 对象:

    class SimpleDriver : public fdf::DriverBase {
    <...>
    private:
    <...>
      fidl::WireSyncClient<fuchsia_driver_framework::NodeController> child_controller_;
    };
    

    此设置允许您将客户端存储在驱动程序中。

  3. 使用 fidl::CreateEndpoints 函数创建服务器端点和客户端端点,例如:

    zx::result controller_endpoints =
        fidl::CreateEndpoints<fuchsia_driver_framework::NodeController>();
    ZX_ASSERT_MSG(controller_endpoints.is_ok(), "Failed to create endpoints: %s",
                  controller_endpoints.status_string());
    
  4. 将客户端绑定到 FIDL 客户端对象,例如:

    child_controller_.Bind(std::move(endpoints->client));
    

    您现在可以使用 Node 对象连接到 Node 服务器,

  5. 连接到 Node 服务器并调用 AddChild() 方法,例如:

     fidl::WireResult result =
          fidl::WireCall(node())->AddChild(args, std::move(controller_endpoints->server), {});
    

    将所有步骤放在一起,如以下示例所示:

    void SimpleDriver::Start(fdf::StartCompleter completer) {
      fidl::Arena arena;
      auto properties = std::vector{fdf::MakeProperty(arena, bind_fuchsia_test::TEST_CHILD, "skeleton")};
      auto args = fuchsia_driver_framework::wire::NodeAddArgs::Builder(arena)
                      .name(arena, "skeleton_child")
                      .properties(arena, std::move(properties))
                      .Build();
    
      auto controller_endpoints = fidl::Endpoints<fuchsia_driver_framework::NodeController>::Create();
      controller_.Bind(std::move(controller_endpoints.client));
    
      fidl::WireResult result =
          fidl::WireCall(node())->AddChild(args, std::move(controller_endpoints.server), {});
      if (!result.ok()) {
        FDF_LOG(ERROR, "Failed to add child %s", result.status_string());
        return completer(result.status());
      }
    
      completer(zx::ok());
    }
    

清理驱动程序

如果 DFv2 驱动程序需要在停止前执行拆解(例如停止线程),那么您需要替换并实现其他 DriverBase 方法:PrepareStop()Stop()

系统会在关闭驱动程序的 fdf 调度程序并取消分配驱动程序之前,调用 PrepareStop() 函数。因此,驱动程序需要实现 PrepareStop()if,它才能在驱动程序的调度程序关闭之前执行某些操作,例如:

void SimpleDriver::PrepareStop(fdf::PrepareStopCompleter completer) {
 // Teardown threads
  FDF_LOG(INFO, "Preparing to stop SimpleDriver");
  completer(zx::ok());
}

系统会在属于此驱动程序的所有调度程序关闭后调用 Stop() 函数,例如:

void SimpleDriver::Stop() {
  FDF_LOG(INFO, "Stopping SimpleDriver");
}

添加兼容型设备服务器

如果您的 DFv2 驱动程序具有尚未迁移到 DFv2 的后代 DFv1 驱动程序,则您需要使用兼容性填充码使 DFv2 驱动程序能够与系统中的其他 DFv1 驱动程序进行通信。如需了解详情,请参阅在 DFv2 驱动程序中设置兼容型设备服务器指南。