编写最小 DFv2 驱动程序

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

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

具体步骤如下:

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

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

创建驱动程序头文件

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

  1. 为驱动程序(例如 skeleton_driver.h)创建新的头文件 (.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. 为驱动程序(例如 skeleton_driver.cc)创建新的源文件 (.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. 添加以下代码行以导入驱动程序 build 规则:

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

    fuchsia_cc_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. meta 目录中,为驱动程序(例如 skeleton_driver.bind)创建一个新的绑定规则文件 (.bind)。

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

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

    (来源:skeleton_driver.bind

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

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

    driver_bind_rules("bind") {
      rules = "meta/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. meta 目录中创建一个新的 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 文件中,添加以下行以导入组件 build 规则:

    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::infofdf::errorfdf::warnfdf::debugfdf::trace)。这些宏使用 std::format 样式的字符串格式。

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

  1. 包含以下标头:

    #include <lib/driver/logging/cpp/logger.h>
    
  2. 使用 fdf:: 宏打印日志,例如:

    fdf::info("Starting SimpleDriver");
    fdf::error("Failed to add child: {}", status);
    

BUILD.gn 中,确保您依赖于日志记录库: gn deps = [ "//sdk/lib/driver/logging/cpp", ]

添加子节点

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;
};

为了实现这一点,在启动期间,驱动程序框架通过 DriverBase 向 DFv2 驱动程序提供绑定节点的 Node 协议的客户端。驱动程序可以随时访问其节点客户端,以便在其上创建子节点。不过,直接使用此 FIDL 库需要进行设置,包括创建 FIDL 通道对和构建 NodeAddArgs 表。因此,DriverBase 类提供了一组辅助函数,可让您更轻松地添加子节点。(如需查看这些辅助函数,请查看 driver_base.h 文件。)

DFv2 驱动程序可以添加两种类型的节点:非自有自有。无主节点与有主节点之间的主要区别在于它们是否参与驱动程序匹配流程

驱动程序框架会尝试查找与未归属节点的属性匹配的驱动程序,以便将驱动程序绑定到该节点。驱动程序与节点匹配并绑定后,绑定的驱动程序将成为节点的所有者。 另一方面,自有节点不参与匹配,因为创建该节点的驱动程序已经是所有者。

DriverBase 辅助函数

驱动程序当前绑定到的节点的客户端存储在 DriverBase 对象中。这样一来,驱动程序就可以使用 DriverBase 类的 AddChild()AddOwnedChild() 函数向此节点添加子节点。

不过,如需使用这些 DriverBase 辅助函数,节点不得已移出驱动程序。如果节点被移出,或者您的目标节点不是驱动程序当前绑定的节点(例如,对于孙子节点),您需要改用 add_child.h 文件中提供的命名空间方法。这些方法与 DriverBase 辅助函数相同,但它们可用于将子项添加到 DriverBase 对象范围之外的节点,方法是提供正确的父节点客户端作为目标。

最后,这些辅助函数会负责记录发生的错误,因此驱动程序无需进行日志记录。

创建未归属的节点

如需创建无主节点,驱动程序可以使用 DriverBase::AddChild() 辅助函数。这些函数允许在无主节点上设置属性,驱动程序框架使用这些属性来查找匹配的驱动程序。两者的返回结果都是 NodeController 协议的客户端,可以由驱动程序保留,也可以安全地舍弃。

以下示例代码在驱动程序的绑定节点下创建了一个无主节点:

// Add a child node.
auto properties = std::vector{fdf::MakeProperty2(bind_fuchsia_test::TEST_CHILD, "simple")};
zx::result child_result = AddChild(child_name, properties, compat_server_.CreateOffers2());
if (child_result.is_error()) {
  return child_result.take_error();
}

child_controller_.Bind(std::move(child_result.value()));

(来源:simple_driver.cc

创建自有节点

如需创建自有节点,驱动程序可以使用 DriverBase::AddOwnedChild() 辅助函数。这些函数不提供 properties 实参,因为自有节点不参与驱动程序匹配。两者的返回结果都是一个 OwnedChildNode 对象,其中包含 NodeController 的客户端端点(可以安全地舍弃)和 Node 协议的客户端端点(不能安全地舍弃)。只要驱动程序希望拥有的节点保持存在,就必须持有 Node 客户端。舍弃此客户端将导致驱动程序框架移除相应节点。

以下示例代码会创建一个自有节点:

// Add an owned child node.
zx::result owned_child_result = AddOwnedChild("owned_child");
if (owned_child_result.is_error()) {
  fdf::error("Failed to add owned child: {}", owned_child_result);
  return owned_child_result.take_error();
}
owned_child_ = std::move(owned_child_result.value());

(来源:simple_driver.cc

清理驱动程序

如果 DFv2 驱动程序需要在停止之前执行拆解操作(例如停止线程),则需要替换并实现额外的 DriverBase 方法:PrepareStop()Stop()

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

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

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

void SimpleDriver::Stop() {
  fdf::info("Stopping SimpleDriver");
}

添加兼容设备服务器

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