將驅動程式庫介面更新至 DFv2

本頁提供更新 DFv1 驅動程式庫以開始使用 DFv2 介面的說明、最佳做法和範例。

將依附元件從 DDK 更新為 DFv2

DFv1 驅動程式會使用 DDK 程式庫 (//src/lib/ddk)。如果是 DFv2 驅動程式,您可以放心移除此 DDK 程式庫目錄中的所有套件依附元件,並替換為下列新的程式庫,其中包含大部分的 DFv2 驅動程式的重要公用程式:

//sdk/lib/driver/component/cpp:cpp

在標頭檔案中,針對新 DFv2 驅動程式庫介面加入下列程式庫:

#include <lib/driver/component/cpp/driver_base.h>

將依附元件從 DDK 更新為 DFv2 後,您必須完成下一節「將驅動程式庫介面從 DFv1 更新為 DFv2」一節,才能編譯驅動程式庫。

將驅動程式庫介面從 DFv1 更新至 DFv2

DFv2 提供名為 DriverBase 的虛擬類別,可納入驅動程式庫的一般處理常式。針對 DFv2 驅動程式,建議您在新的驅動程式庫類別中沿用 DriverBase,讓介面變得更加簡單。

舉例來說,假設您的 DFv1 驅動程式庫中有以下類別:

class MyExampleDriver;

using MyExampleDeviceType = ddk::Device<MyExampleDriver, ddk::Initializable,
   ddk::Unbindable>;

class MyExampleDriver : public MyExampleDeviceType {
 public:
  void DdkInit(ddk::InitTxn txn);
  void DdkUnbind(ddk::UnbindTxn txn);
  void DdkRelease();
}

如果您將介面更新為沿用 DriverBase,新類別看起來會像這樣:

class MyExampleDriver : public fdf::DriverBase {
  public:
    Driver(fdf::DriverStartArgs start_args,
        fdf::UnownedSynchronizedDispatcher driver_dispatcher);

    zx::result<> Start() override;
    void Stop() override;
}

除了啟動和停止驅動程式庫 (如上述範例所示) 以外,DriverBase 類別也提供物件,讓驅動程式庫能與其他元件 (和驅動程式) 通訊。舉例來說,您的驅動程式庫現在可以呼叫 DriverBase 類別的 outgoing() 方法,擷取傳出目錄,而不必在 DFv1 中宣告及建立自己的傳出目錄 (如「從 Banjo to FIDL」階段):

class DriverBase {
   ...
   // Used to access the outgoing directory that the driver is serving. Can be used to add both
   // zircon and driver transport outgoing services.
   std::shared_ptr<OutgoingDirectory>& outgoing() { return outgoing_; }

   ...
}

(資料來源:driver_base.h)

DFv2 中還有另一個實用類別是 Node 類別。以下範例顯示會連線至 Node 伺服器,並使用 AddChild() 函式新增子節點的 DFv2 驅動程式庫程式碼:

zx::result<> MyExampleDriver::Start() {
  fidl::WireSyncClient<fuchsia_driver_framework::Node> node(std::move(node()));
  auto args = fuchsia_driver_framework::wire::NodeAddArgs::Builder(arena)
                    .name(arena, “example_node”)
                    .Build();

  auto controller_endpoints = fidl::Endpoints<fuchsia_driver_framework::NodeController>::Create();

  auto result = node_->AddChild(args, std::move(controller_endpoints.server), {});
  if (!result.ok()) {
    FDF_LOG(ERROR, "Failed to add child: %s", result.status_string());
    return zx::error(result.status());
  }
  return zx::ok();
}

傳遞至 AddChild() 函式的 NodeController 端點 (在上述範例中為 controller_endpoints) 可用於控管子節點。舉例來說,這個端點可用於移除節點拓撲中的子節點、要求驅動程式庫架構將節點繫結至特定驅動程式庫,或是在子項節點繫結時接收回呼。以下範例顯示可在關閉期間移除子項節點的 DFv2 驅動程式庫程式碼:

void MyExampleDriver::Stop() {
  // controller_endpoints defined in the previous example.
  fidl::WireSyncClient<fuchsia_driver_framework::NodeController>
       node_controller(controller_endpoints.client);

  auto status = node_controller->Remove();
  if (!status.ok()) {
    FDF_LOG(ERROR, "Could not remove child: %s", status.status_string());
  }
}

此外,在 DFv2 中,OnBind() 事件是在 NodeController 通訊協定中定義,而從伺服器端的子節點叫用該事件。DriverBase::PrepareStop() 函式有機會在呼叫 DriverBase::Stop() 之前執行取消初始化。

下表列出 DFv1 和 DFv2 之間的常見驅動程式庫和裝置介面對應:

DFv1 DFv2
zx_driver_ops::bind() DriverBase::Start()
zx_driver_ops::init() DriverBase::Start()
zx_driver_ops::release() DriverBase::Stop()
device_add()

DdkAdd()
Node::AddChild()
device_add_composite()

DdkAddComposite()
:依據規格新增複合式節點。請參閱複合式節點
device_add_composite_node_spec()

DdkAddCompositeNodeSpec()
CompositeNodeManager::AddSpec()
zx_protocol_device::init()

DdkInit()
:在 DFv2 中,驅動程式庫會負責其新增的節點 (裝置) 生命週期。
zx_protocol_device::unbind()

DdkUnbind()
:在 DFv2 中,驅動程式庫會負責其新增的節點 (裝置) 生命週期。
zx_protocol_device::release()

DdkRelease()
NodeController::Remove()
zx_protocol_device::get_protocol()

device_get_protocol()
:這些方法以 DFv2 中的 Banjo 通訊協定為基礎,所有通訊皆位於 FIDL 中。
zx_protocol_device::service_connect()

device_service_connect()

DdkServiceConnect()
:這是駕駛人彼此建立 FIDL 連線的傳統方法。詳情請參閱「使用 DFv2 服務探索」。
Device_connect_runtime_protocol()

DdkConnectRuntimeProtocol()
:這些是 DFv1 中新增的服務和通訊協定探索方法。詳情請參閱「使用 DFv2 服務探索」。

使用 DFv2 記錄器

DFv2 中的新記錄機制並非使用 zxlogf() (DFv2 中已淘汰),而是取決於 fdf::Logger 物件,該物件會在驅動程式啟動時透過 DriverStartArgs 從驅動程式庫主機傳遞。

fdf::DriverBase 類別會納入 fdf::Logger,且驅動程式庫可透過呼叫 logger() 方法取得參照 (請參閱此 wlantap-driver 驅動程式庫範例)。透過這項參考資料,您可以使用 logger.logf() 函式或這些巨集列印記錄,例如:

FDF_LOG(INFO, "Example log message here");

更新巨集

除了更新介面之外,您也必須變更填入驅動程式庫介面函式的巨集:

  • 從:

    ZIRCON_DRIVER()
    
  • 到:

    FUCHSIA_DRIVER_EXPORT()
    

設定 compat 裝置伺服器

如果您的 DFv1 驅動程式庫與尚未遷移至 DFv2 的其他 DFv1 驅動程式通訊,您必須使用相容性填充碼,使目前的 DFv2 驅動程式庫能夠與其他 DFv1 驅動程式進行通訊。如要進一步瞭解如何在 DFv2 驅動程式庫中設定及使用此相容性輔助程式,請參閱「在 DFv2 驅動程式設定 compat 裝置伺服器」指南。

使用 DFv2 服務探索

進行驅動程式庫遷移時,您可能會遇到以下三種情況,也就是兩個驅動程式建立 FIDL 連線 (採用 child driver -> parent driver 格式) 的情境:

  • 情境 1:DFv2 驅動程式庫 -> DFv2 驅動程式庫
  • 情境 2:DFv1 驅動程式庫 -> DFv2 驅動程式庫
  • 情境 3:DFv2 驅動程式庫 -> DFv1 驅動程式庫

情境 1 是 DFv2 驅動程式的標準案例 (此範例說明新的 DFv2 語法)。如要在此情境中更新驅動程式,請參閱下方的「DFv2 驅動程式庫至 DFv2 驅動程式」一節。

情境 2 和 3 比較複雜,因為 DFv1 驅動程式會包裝在 DFv2 環境中的相容性填充碼中。但差異如下:

  • 情境 2 中,這個「Gerrit 變更」顯示的方法會從 DFv2 父項公開服務至 DFv1 子項。

  • 情境 3 中,驅動程式庫會連線至父項驅動程式庫的相容性輔助碼提供的 fuchsia_driver_compat::Service::Device 通訊協定,而驅動程式庫會透過這個通訊協定呼叫 ConnectFidl() 方法來連線至實際通訊協定 (範例請見此 Gerrit 變更)。

如要在情境 2 或 3 下更新驅動程式,請參閱下方的「DFv1 驅動程式庫至 DFv2 驅動程式庫 (具有相容性輔助鍵)」一節。

DFv2 驅動程式庫至 DFv2 驅動程式庫

如果想讓其他 DFv2 驅動程式能夠找到您的驅動程式服務,請按照下列步驟操作:

  1. 更新驅動程式的 .fidl 檔案。

    DFv2 中的通訊協定探索需要為驅動程式庫的通訊協定新增 service 欄位,例如:

    library fuchsia.example;
    
    @discoverable
    @transport("Driver")
    protocol MyProtocol {
        MyMethod() -> (struct {
            ...
        });
    };
    
    service Service {
        my_protocol client_end:MyProtocol;
    };
    
  2. 更新子項驅動程式庫。

    DFv2 驅動程式可以採用與 FIDL 服務相同的方式連線至通訊協定,例如:

    incoming()->Connect<fuchsia_example::Service::MyProtocol>();
    

    此外,您也需要更新元件資訊清單 (.cml) 檔案,才能使用驅動程式庫執行階段服務,例如:

    use: [
        { service: "fuchsia.example.Service" },
    ]
    
  3. 更新父項驅動程式庫。

    您的父項驅動程式庫必須使用 fdf::DriverBaseoutgoing() 函式來取得 fdf::OutgoingDirectory 物件。請注意,您必須使用服務而非通訊協定。如果您的驅動程式庫並未使用 fdf::DriverBase,則必須自行建立並提供 fdf::OutgoingDirectory

    接著,您必須將執行階段服務新增至傳出目錄。以下範例為繼承自 fdf::DriverBase 類別的驅動程式庫:

    zx::status<> Start() override {
      auto protocol = [this](
          fdf::ServerEnd<fuchsia_example::MyProtocol> server_end) mutable {
        // bindings_ is a class field with type fdf::ServerBindingGroup<fuchsia_example::MyProtocol>
        bindings_.AddBinding(
          dispatcher()->get(), std::move(server_end), this, fidl::kIgnoreBindingClosure);
      };
    
      fuchsia_example::Service::InstanceHandler handler(
           {.my_protocol = std::move(protocol)});
    
      auto status =
            outgoing()->AddService<fuchsia_wlan_phyimpl::Service>(std::move(handler));
      if (status.is_error()) {
        return status.take_error();
      }
    
      return zx::ok();
    }
    

    更新子節點的 NodeAddArgs 以加入執行階段服務優惠,例如:

    auto offers =
        std::vector{fdf::MakeOffer2<fuchsia_example::Service>(arena, name)};
    
    fidl::WireSyncClient<fuchsia_driver_framework::Node> node(std::move(node()));
      auto args = fuchsia_driver_framework::wire::NodeAddArgs::Builder(arena)
                        .name(arena, “example_node”)
                        .offers2(offers)
                        .Build();
    
      zx::result controller_endpoints =
           fidl::CreateEndpoints<fuchsia_driver_framework::NodeController>();
      ZX_ASSERT(controller_endpoints.is_ok());
    
      auto result = node_->AddChild(
          args, std::move(controller_endpoints->server), {});
    

    同樣地,請更新父項驅動程式庫程式的元件資訊清單 (.cml) 檔案,以提供執行階段服務,例如:

    capabilities: [
        { service: "fuchsia.example.Service" },
    ],
    
    expose: [
        {
            service: "fuchsia.example.Service",
            from: "self",
        },
    ],
    

套用至 DFv2 驅動程式庫的 DFv1 驅動程式庫 (含相容性填充碼)

如果想讓其他 DFv1 驅動程式能夠找到您的 DFv2 驅動程式庫服務,請執行下列操作:

  1. 更新 DFv1 驅動程式。

    您必須按照上述「DFv2 驅動程式庫至 DFv2 驅動程式」一節所述的方式更新 DFv1 驅動程式的元件資訊清單 (.cml) 檔案,例如:

    • 子項驅動程式庫:

      {
          include: [
              "//sdk/lib/driver_compat/compat.shard.cml",
              "inspect/client.shard.cml",
              "syslog/client.shard.cml",
          ],
          program: {
              runner: "driver",
              compat: "driver/child-driver-name.so",
              bind: "meta/bind/child-driver-name.bindbc",
              colocate: "true",
          },
          use: [
              { service: "fuchsia.example.Service" },
          ],
      }
      
    • 父項驅動程式庫:

      {
          include: [
              "//sdk/lib/driver_compat/compat.shard.cml",
              "inspect/client.shard.cml",
              "syslog/client.shard.cml",
          ],
          program: {
              runner: "driver",
              compat: "driver/parent-driver-name.so",
              bind: "meta/bind/parent-driver-name.bindbc",
          },
          capabilities: [
              { service: "fuchsia.example.Service" },
          ],
          expose: [
              {
                  service: "fuchsia.example.Service",
                  from: "self",
              },
          ],
      }
      
  2. 更新 DFv2 驅動程式庫。

    以下範例顯示將 DFv2 父項服務公開給 DFv1 子項的方法:

      fit::result<fdf::NodeError> AddChild() {
        fidl::Arena arena;
    
        auto offer = fdf::MakeOffer2<ft::Service>(kChildName);
    
        // Set the properties of the node that a driver will bind to.
        auto property =
            fdf::MakeProperty(1 /*BIND_PROTOCOL */, bind_fuchsia_test::BIND_PROTOCOL_COMPAT_CHILD);
    
        auto args = fdf::NodeAddArgs{
          {
            .name = std::string(kChildName),
            .properties = std::vector{std::move(property)},
            .offers2 = std::vector{std::move(offer)},
          }
        };
    
        // Create endpoints of the `NodeController` for the node.
        auto endpoints = fidl::CreateEndpoints<fdf::NodeController>();
        if (endpoints.is_error()) {
          return fit::error(fdf::NodeError::kInternal);
        }
    
        auto add_result = node_.sync()->AddChild(fidl::ToWire(arena, std::move(args)),
                                                 std::move(endpoints->server), {});
    

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

更新其他驅動程式的元件資訊清單

如要完成將 DFv1 驅動程式庫遷移至 DFv2 的程序,您不僅需要更新目標驅動程式庫的元件資訊清單 (.cml) 檔案,也需要更新其他與 Now-DFv2 驅動程式庫互動的其他驅動程式的元件資訊清單檔案。

請完成下列步驟:

  1. 請按照下列步驟更新分葉驅動程式 (不含子項驅動程式) 的元件資訊清單:

    • include 欄位移除 //sdk/lib/driver/compat/compat.shard.cml
    • program.compat 欄位替換為 program.binary
  2. 針對執行以下任務的其他驅動程式,更新元件資訊清單:

    • 存取核心 args
    • 建立複合型裝置。
    • 偵測重新啟動、關閉或重新繫結通話。
    • 使用 Banjo 通訊協定與其他駕駛人交談。
    • 存取父項驅動程式庫的中繼資料,或是轉寄中繼資料。
    • 連線至會繫結至驅動程式新增節點的 DFv1 驅動程式庫。

    針對這些驅動程式,請根據以下變更更新其元件資訊清單:

    • compat.shard.cml 中的部分 use 功能複製到元件資訊清單,例如:

      use: [
          {
              protocol: [
                  "fuchsia.boot.Arguments",
                  "fuchsia.boot.Items",
                  "fuchsia.device.manager.SystemStateTransition",
                  "fuchsia.driver.framework.CompositeNodeManager",
              ],
          },
          { service: "fuchsia.driver.compat.Service" },
      ],
      
    • program.runner 欄位設為 driver,例如:

      program: {
          runner: "driver",
          binary: "driver/compat.so",
      },
      

公開 DFv2 驅動程式庫中的 devfs 節點

如要從 DFv2 驅動程式庫公開 devfs 節點,您必須將 device_args 成員新增至 NodeAddArgs。特別是,它需要指定類別名稱,並實作連接器,透過使用 Connector 程式庫來簡化,例如:

zx::result connector = devfs_connector_.Bind(dispatcher());
if (connector.is_error()) {
  return connector.take_error();
}

auto devfs =
    fuchsia_driver_framework::wire::DevfsAddArgs::Builder(arena).connector(
        std::move(connector.value()));

auto args = fuchsia_driver_framework::wire::NodeAddArgs::Builder(arena)
                    .name(arena, name)
                    .devfs_args(devfs.Build())
                    .Build();

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

詳情請參閱 DFv2 驅動程式庫程式碼研究室中的「驅動程式庫」。此外,也請參閱程式碼研究室所述 ExportToDevfs 方法的此實作

使用調度工具

調度工具會從 FIDL 用戶端和伺服器組合之間的管道擷取資料。根據預設,此管道中的 FIDL 呼叫為非同步性質。

如要瞭解如何為 DFv2 中的驅動程式導入同步處理程序,請參閱下列建議:

  • fdf::Dispatcher::GetCurrent() 方法會提供執行驅動程式庫的預設調度工具 (請參閱 aml-ethernet 驅動程式庫範例)。建議您盡可能單獨使用這個預設調度工具。

  • 考慮使用多個調度工具的原因如下 (但不限於):

    • 驅動程式需要平行處理才能提高效能。

    • 驅動程式想要執行阻斷作業 (因為其不是舊版驅動程式庫或攜碼至 Fuchsia 的非 Fuchsia 驅動程式庫),且需要在遭到封鎖的情況下處理更多工作。

  • 如果需要多個調度工具,fdf::Dispatcher::Create() 方法可以為駕駛建立新的調度工具。然而,您必須在預設調度工具中呼叫這個方法 (例如在 Start() 掛鉤中呼叫此方法),這樣驅動程式代管程序才會知道其他驅動程式屬於您的驅動程式庫。

  • 在 DFv2 中,您無須手動關閉調度工具。這些屬性將在 PrepareStop()Stop() 呼叫之間關閉。

如要進一步瞭解如何遷移驅動程式庫以使用多個調度工具,請參閱「更新 DFv1 驅動程式庫以使用非預設調度器」一節 (請參閱「從 Banjo 遷移至 FIDL」詞組)。

使用 DFv2 檢查功能

如要在 DFv2 中設定驅動程式庫維護的「檢查」inspect指標,您必須建立 inspect::ComponentInspector 物件,例如:

component_inspector_ =
    std::make_unique<inspect::ComponentInspector>(out, dispatcher, *inspector_);

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

建立 inspect::ComponentInspector 物件需要下列三個輸入項目:

  • Context().outgoing()->component() 呼叫中的 component::OutgoingDirectory 物件

  • 調度器

  • 原始 inspect::Inspector 物件

不過,DFv2 檢查不需要將 inspect::Inspector 的 VMO 傳遞至驅動程式庫架構。

(選用) 實作自己的 load_firmware 方法

如果 DFv1 驅動程式庫會呼叫 DDK 程式庫中的 load_firmware() 函式,您就必須實作自己的函式版本,因為 DFv2 中未提供對等函式。

這個函式應該很簡單的實作。您必須手動從路徑取得備份 VMO。如需範例,請參閱這項變更器變更

(選用) 使用從 FIDL 服務方案產生的節點屬性

DFv2 節點包含從父項 FIDL 服務優惠產生的節點屬性。

舉例來說,在父項驅動程式 (The Server) 範例中,父項驅動程式庫會新增名為 "parent" 的節點,以及 fidl.examples.EchoService 的服務優惠。在 DFv2 中,與這個節點繫結的驅動程式庫可以有該 FIDL 服務節點屬性的繫結規則,例如:

using fidl.examples.echo;

fidl.examples.echo.Echo == fidl.examples.echo.Echo.ZirconTransport;

詳情請參閱 FIDL 教學課程頁面的「產生的繫結程式庫」一節。

將單元測試更新為 DFv2

mock_ddk 程式庫 (用於測試驅動程式庫和裝置生命週期的單元測試) 僅適用於 DFv1。新的 DFv2 測試架構 (請參閱這項 Gerrit 變更) 可讓您透過 TestEnvironment 類別為 DFv2 驅動程式提供模擬的 FIDL 伺服器。

以下程式庫可用於單元測試 DFv2 驅動程式:

  • //sdk/lib/driver/testing/cpp

    • TestNode - 此類別會實作 fuchsia_driver_framework::Node 通訊協定,並可以提供給驅動程式庫以建立子節點。測試也會使用這個類別,存取驅動程式庫已建立的子節點。

    • TestEnvironment - OutgoingDirectory 物件的包裝函式,這個包裝函式會做為受測驅動程式庫傳入命名空間的幕後 VFS (虛擬檔案系統)。

    • DriverUnderTest - 此類別是受測試驅動程式庫的 RAII (資源擷取是初始化) 包裝函式。

    • DriverRuntime:此類別是代管驅動程式庫執行階段執行緒集區的 RAII 包裝函式。

  • //sdk/lib/driver/testing/cpp/driver_runtime.h

    • TestSynchronizedDispatcher – 此類別是驅動程式庫調度器上的 RAII 包裝函式。

下列程式庫可能有助於編寫驅動程式庫單元測試:

最後,下列單元測試範例涵蓋不同的設定和測試案例:

其他資源

以下是一些 DFv2 驅動程式的範例:

本節提及的所有Gerrit 變更

本節提及的所有原始碼檔案

本節提及的所有說明文件頁面