編寫最小的 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)。meta

  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 = "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 欄位必須與output_name BUILD.gn 檔案的 fuchsia_driver 目標,以及 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 檔案,在 meta 目錄中提供元件資訊 (例如 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 的結構化 Logger 程式庫 (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;
};

為方便這項操作,驅動程式架構會在啟動期間,透過 DriverBase 將已繫結節點的 Node 通訊協定用戶端提供給 DFv2 驅動程式。驅動程式隨時可以存取其節點用戶端,在節點上建立子節點。不過,直接使用這個 FIDL 程式庫需要設定,包括建立 FIDL 管道組合和建構 NodeAddArgs 表格。因此,DriverBase 類別提供一組輔助函式,方便您新增子節點。(如要查看這些輔助程式,請查看 driver_base.h 檔案)。

DFv2 驅動程式庫可新增的節點類型有兩種:「非擁有」和「擁有」。 非擁有節點和自有節點之間的主要差異在於 決定是否要參與駕駛人比對程序

驅動程式庫程式架構會嘗試找出與 非自有節點,以便將驅動程式庫繫結至節點。一旦成功找到驅動程式庫成功 繫結至節點,繫結的驅動程式庫就會成為節點的擁有者。 另一方面,已擁有的節點不會參與比對,因為建立節點的駕駛人已是擁有者。

DriverBase 輔助函式

驅動程式目前繫結節點的用戶端儲存在 DriverBase 物件。這可讓驅動程式使用 DriverBase 類別的 AddChild()AddOwnedChild() 函式,將子節點新增至此節點。

不過,如要使用這些 DriverBase 輔助函式,節點不得移出驅動程式。如果節點被移出,或您的目標節點不是 驅動程式庫目前繫結的節點 (亦即大子項節點); 您需要使用 add_child.h 檔案。這些方法與 DriverBase 輔助函式,但可用來將子項新增至節點 而不只是 DriverBase 物件所能觸及的地方,而是提供正確的父項 做為目標

最後,如果發生記錄錯誤,這些輔助函式會負責處理,因此驅動程式不需要記錄。

建立非擁有的節點

如要建立無擁有者的節點,驅動程式可以使用 DriverBase::AddChild() 輔助函式。這些函式有兩種:一種可讓您提供 DevfsAddArgs 和其他不需要。這些函式可讓您在未擁有的節點上設定屬性,驅動程式架構會使用這些屬性來尋找相符的驅動程式。這兩者的傳回結果都是 NodeController 通訊協定的用戶端端點,可由驅動程式保留或安全地捨棄。

以下程式碼範例會在驅動程式的繫結節點下建立無擁有權的節點:

// Add a child node.
auto properties = std::vector{fdf::MakeProperty(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() 輔助程式 函式。又有兩種類型:一種允許 DevfsAddArgs,另一種類型允許 但它不會顯示這些函式不提供屬性引數,因為 節點不會參與驅動程式庫比對。兩者的傳回結果都是 OwnedChildNode 物件,其中包含 NodeController 的用戶端端點 (可安全捨棄),以及 Node 通訊協定的用戶端端點 (不安全,請勿捨棄)。駕駛必須在 Node 用戶端保持連線狀態, 希望擁有的節點留在系統中捨棄這個用戶端會導致驅動程式庫 移除節點

下列程式碼範例會使用 devfs 引數建立自有節點:

fuchsia_driver_framework::DevfsAddArgs devfs_args{{.connector = std::move(connector.value())}};
zx::result owned_child = AddOwnedChild("retriever", devfs_args);
if (owned_child.is_error()) {
  return owned_child.error_value();
}

child_node_.emplace(std::move(owned_child.value()));

(資料來源:retriever-driver.cc)

清理驅動程式

如果 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 裝置伺服器 指南。