編寫最小的 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_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 檔案中加入以下行,匯入繫結建構規則:

    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. 建立新的 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 的結構化記錄器程式庫 (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() 輔助函式。這些函式可讓您在未擁有的節點上設定屬性,驅動程式庫架構會使用這些屬性來尋找相符的驅動程式庫。這兩者的傳回結果都是 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() 輔助函式。這些函式不會提供屬性引數,因為擁有的節點不會參與驅動程式庫比對。兩者的傳回結果都是 OwnedChildNode 物件,其中包含 NodeController 的用戶端端點 (可安全捨棄),以及 Node 通訊協定的用戶端端點 (不安全,請勿捨棄)。只要驅動程式希望擁有的節點持續存在,就必須保留 Node 用戶端。刪除這個用戶端會導致驅動程式庫程式架構移除節點。

下列程式碼範例會建立擁有的節點:

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 驅動程式中設定相容裝置伺服器」指南。