編寫最小的 DFv2 驅動程式庫

本指南將逐步說明如何建立最小的 DFv2 驅動程式庫。

本指南是根據最低限度的架構驅動程式所提供,其提供了在 Fuchsia 系統中建構、載入及註冊新的 DFv2 驅動程式庫所需的最低實作項目。

步驟如下:

  1. 建立驅動程式庫標頭檔案
  2. 建立驅動程式庫來源檔案
  3. 新增驅動程式庫匯出巨集
  4. 建立建構檔案
  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)

建立建構檔案

如要建立驅動程式庫的建構檔案,請執行下列步驟:

  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 目錄中建立新的元件資訊清單檔案 (.cml),例如 skeleton_driver.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 巨集之外,您也可以使用 Fauchsia 的結構化記錄器程式庫 (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 驅動程式設定 compat 裝置伺服器」指南。