將其他服務更新至 DFv2

本頁面提供相關指示、最佳做法和範例,說明如何將 DFv1 驅動程式中的各種服務 (除了 DDK 介面) 更新為 DFv2。

設定相容性裝置伺服器

如果 DFv1 驅動程式庫與其他尚未遷移至 DFv2 的 DFv1 驅動程式通訊,您就需要使用相容性墊片,讓現已是 DFv2 的驅動程式庫能夠與系統中的其他 DFv1 驅動程式通訊。如要進一步瞭解如何在 DFv2 驅動程式庫中設定及使用這個相容性墊片,請參閱「在 DFv2 驅動程式中設定相容性裝置伺服器」指南。

使用 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",
        },
    ],
    

DFv1 驅動程式庫轉換為 DFv2 驅動程式庫 (搭配相容性墊片)

如要讓其他 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) 檔案,也可能需要更新與現有 DFv2 驅動程式庫互動的其他驅動程式的元件資訊清單檔案。

請完成下列步驟:

  1. 請更新葉狀驅動程式 (也就是沒有子項驅動程式) 的元件資訊清單,並加入下列變更:

    • include 欄位中移除 //sdk/lib/driver/compat/compat.shard.cml
    • program.compat 欄位替換為 program.binary
  2. 更新執行下列工作內容的其他驅動程式的元件資訊清單:

    • 存取核心 args
    • 建立複合裝置。
    • 偵測重新啟動、關機或重新繫結呼叫。
    • 使用 Banjo 通訊協定與其他駕駛人通訊。
    • 存取或轉寄來自父項驅動程式庫的中繼資料。
    • 與 DFv1 驅動程式庫通訊,該驅動程式會繫結至由驅動程式新增的節點。

    針對這些驅動程式,請更新其元件資訊清單,並加入下列變更:

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

      use: [
          {
              protocol: [
                  "fuchsia.boot.Arguments",
                  "fuchsia.boot.Items",
                  "fuchsia.driver.framework.CompositeNodeManager",
                  "fuchsia.system.state.SystemStateTransition",
              ],
          },
          { 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 方法實作項目

如要進一步瞭解 devfs 設定程序,請參閱「在 DFv2 驅動程式中設定 devfs」指南。

使用調度器

調度器會從 FIDL 用戶端和伺服器之間的管道擷取資料。根據預設,這個管道的 FIDL 呼叫為非同步。

如要在 DFv2 中為驅動程式引入非同步功能,請參閱以下建議:

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

  • 請考慮使用多個調度器,原因包括但不限於:

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

    • 驅動程式想要執行封鎖作業 (因為它是舊版驅動程式庫或非 Fuchsia 驅動程式庫,且已移植至 Fuchsia),因此需要在封鎖期間處理更多工作。

  • 如果需要多個調度器,fdf::Dispatcher::Create() 方法可以為驅動程式建立新的調度器。不過,您必須在預設調度器上呼叫這個方法 (例如在 Start() 鉤子中呼叫),讓驅動程式代管程序瞭解屬於驅動程式的其他調度器。

  • 在 DFv2 中,您不需要手動關閉調度器。這些會在 PrepareStop()Stop() 呼叫之間關閉。

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

使用 DFv2 檢查

如要在 DFv2 中設定由驅動程式庫程式維護的檢查指標,您可以使用 fdf::DriverBase::inspector() 提供的 ComponentInspector

inspect::Node& root = inspector().root();

如果要使用自訂檢查器,請先呼叫 fdf::DriverBase::InitInspectorExactlyOnce(inspector),再存取 inspector() 方法。

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

DFv2 駕駛員的檢查畫面會顯示歸因於驅動程式庫 (因為這是「一般」元件)。不過,DFv2 驅動程式的別名不穩定,因此在針對驅動程式庫編寫隱私權選取器時,請使用萬用字元和名稱篩選器來參照特定驅動程式庫。例如:

bootstrap/*-drivers*:[name=sysmem]root

如要在偵錯期間存取驅動程式庫檢查功能,您可以使用所有一般工具,例如

ffx inspect show --name sysmem "bootstrap/*-drivers*:root"

ffx inspect show --component sysmem.cm

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

如果您的 DFv1 驅動程式庫會呼叫 DDK 程式庫中的 load_firmware() 函式,您就需要實作此函式的專屬版本,因為 DFv2 中沒有對等的函式。

這個函式應可輕鬆實作。您必須手動從路徑取得備援 VMO。如需範例,請參閱這個 Gerrit 異動

(選用) 使用由 FIDL 服務產品產生的節點屬性

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

舉例來說,在「父項驅動程式 (伺服器)」範例中,父項驅動程式庫會新增名為 "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 (Resource Acquisition Is Initialization) 包裝函式。

    • DriverRuntime:這個類別是 RAII 包裝函式,可用於受管理的驅動程式庫執行階段執行緒集區。

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

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

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

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

其他資源

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

本節提及的所有 Gerrit 變更

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

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