FIDL 教學課程

本指南說明如何從驅動程式庫匯出 FIDL 通訊協定,並在另一個驅動程式庫中使用。本指南假設您熟悉下列概念:

FIDL 通訊協定定義

下列程式碼片段會使用這個 FIDL 通訊協定:

library fidl.examples.echo;

const MAX_STRING_LENGTH uint64 = 32;

// The discoverable annotation is required, otherwise the protocol bindings
// will not have a name string generated.
@discoverable
protocol Echo {
    /// Returns the input.
    EchoString(struct {
        value string:<MAX_STRING_LENGTH, optional>;
    }) -> (struct {
        response string:<MAX_STRING_LENGTH, optional>;
    });
};

service EchoService {
    echo client_end:Echo;
};

父項驅動程式 (伺服器)

我們在此約略說明如何編寫實作通訊協定的父項驅動程式庫,該通訊協定會呼叫至該驅動程式。雖然未顯示,但我們假設這個類別使用 DDKTL。

// This class implement the fuchsia.examples.echo/Echo FIDL protocol using the
// new C++ FIDL bindings
class Device : public fidl::WireServer<fidl_examples_echo::Echo> {

  // This is the main entry point for the driver.
  static zx_status_t Bind(void* ctx, zx_device_t* parent) {
    // When creating the device, we initialize it with a dispatcher provided by
    // the driver framework. This dispatcher is allows us to schedule
    // asynchronous work on the same thread as other drivers. You may opt to
    // create your own dispatcher which is serviced on a thread you spawn if you
    // desire instead.
    auto* dispatcher = fdf::Dispatcher::GetCurrent()->async_dispatcher();
    auto device = std::make_unique<Device>(parent, dispatcher);

    // We add the FIDL protocol we wish to export to our child to our outgoing
    // directory. When a connection is attempted we will bind the server end of
    // the channel pair to our server implementation.
    zx::result = device->outgoing_.AddService<fidl_examples_echo::EchoService>(
        fidl_examples_echo::EchoService::InstanceHandler({
            .echo = device->bindings_.CreateHandler(device.get(), dispatcher,
                                                    fidl::kIgnoreBindingClosure),
            }));

    // Utilizing the server end of the endpoint pair we created above, we bind
    // it to our outgoing directory.
    result = device->outgoing_.Serve(std::move(endpoints->server));
    if (result.is_error()) {
      zxlogf(ERROR, "Failed to service the outgoing directory");
      return result.status_value();
    }

    // We declare our outgoing protocols here. These will be utilize to
    // help the framework populate node properties which can be used for
    // binding.
    std::array offers = {
        fidl_examples_echo::Service::Name,
    };

    status = device->DdkAdd(ddk::DeviceAddArgs("parent")
                                // The device must be spawned in a separate
                                // driver host.
                                .set_flags(DEVICE_ADD_MUST_ISOLATE)
                                .set_fidl_service_offers(offers)
                                // The client side of outgoing directory is
                                // provided to the framework. This will be
                                // forwarded to the new driver host that spawns to
                                // allow the child driver which binds the ability
                                // to connect to our outgoing FIDL protocols.
                                .set_outgoing_dir(endpoints->client.TakeChannel()));
    if (status == ZX_OK) {
      [[maybe_unused]] auto ptr = device.release();
    } else {
      zxlogf(ERROR, "Failed to add device");
    }

    return status;
  }

 private:
  // This is the implementation of the only method our FIDL protocol requires.
  void EchoString(EchoStringRequestView request, EchoStringCompleter::Sync& completer) override {
    completer.Reply(request->value);
  }

  // This is a helper class which we use to serve the outgoing directory.
  component::OutgoingDirectory outgoing_;
  // This ensures that the fidl connections don't outlive the device object.
  fidl::ServerBindingGroup<fidl_examples_echo::Echo> bindings_;
};

Child Driver (The Client)

裝訂

首先要討論的重要事項是,孩子驅動程式庫如何繫好安全帶。節點屬性數量不限,但如果您只想根據父項匯出的 FIDL 通訊協定進行繫結,則需要建構作業從 FIDL 程式庫自動產生的繫結程式庫 (詳情請參閱「產生的繫結程式庫」)。

您會在驅動程式庫的繫結規則中依附及使用這個繫結程式庫:

using fidl.examples.echo;

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

ZirconTransport 是父項驅動程式庫用來將 Echo FIDL 通訊協定提供給子項的傳輸方法。

您可以視需要新增其他繫結限制。請注意,只有在父項驅動程式庫於新增裝置時宣告 FIDL 通訊協定提供項目,我們才會新增此處所述的屬性。

客戶代碼

以下程式碼片段位於已成功繫結至上述父項驅動程式庫的子項驅動程式庫中。

zx_status_t CallEcho() {
  // The following method allows us to connect to the protocol we desire. This
  // works by providing the server end of our endpoint pair to the framework. It
  // will push this channel through the outgoing directory to our parent driver
  // which will then bind it to its server implementation. We do  not need to
  // name the protocol because the method is templated on the  channel type and
  // it is able to automatically derive the name from the type.
  zx::result client_end = DdkConnectFidlProtocol<fidl_examples_echo::EchoService::Echo>();
  if (client_end.is_error()) {
    zxlogf(ERROR, "Failed to connect fidl protocol: %s", client_end.status_string());
    return client_end.status_value();
  }

  // We turn the client side of the endpoint pair into a synchronous client.
  fidl::WireSyncClient client{std::move(client_end.value())};

  // We can now utilize our client to make calls!
  constexpr std::string_view kInput = "Test String";

  auto result = client->EchoString(fidl::StringView::FromExternal(std::string_view(kInput)));
  if (!result.ok()) {
    zxlogf(ERROR, "Failed to call EchoString");
    return result.status();
  }
  if (result->response.get() != kInput) {
    zxlogf(ERROR, "Unexpected response: Actual: \"%.*s\", Expected: \"%.*s\"",
           static_cast<int>(result->response.size()), result->response.data(),
           static_cast<int>(kInput.size()), kInput.data());
    return ZX_ERR_INTERNAL;
  }

  return ZX_OK;
}

產生的繫結程式庫

所有 FIDL 程式庫都會自動產生繫結程式庫。這有助於驅動程式庫作者根據上層提供的 FIDL 通訊協定和服務,以及上層用來提供各項服務的傳輸方法,建立繫結規則。

繫結程式庫

這些繫結程式庫中可能有三種傳輸方法:BanjoZirconTransportDriverTransport。目前可以安全地假設值為 ZirconTransport (只是透過 Zircon 管道的 FIDL),或 DriverTransport (適用於共置驅動程式的程序內通訊堆疊)。繫結程式庫包含通訊協定和這些傳輸方法的常數。

FIDL 程式庫中定義的每項服務和可探索的通訊協定,都會在繫結程式庫中取得列舉,而列舉的值是三種傳輸方法。

以下範例顯示 FIDL 程式庫包含單一可探索通訊協定的情況:

protocol.fidl

library fuchsia.gizmo.protocol;

@discoverable
closed protocol TestingProtocol {
    strict Get();
};

產生的程式庫

// WARNING: This file is machine generated by bindc.
library fuchsia.gizmo.protocol;

enum TestingProtocol {
  Banjo,
  ZirconTransport,
  DriverTransport,
};

建構目標

這些產生的繫結程式庫會以 FIDL 程式庫的 library_nametarget_name 為基礎。繫結程式庫的目標名稱為 {fidl_target_name}_bindlib,且其 library_name 與 FIDL 相同。

舉例來說,如果 FIDL 程式庫目標是 //sdk/fidl/fidl.examples.echo:myecholibrary,則自動產生的繫結程式庫目標為 //sdk/fidl/fidl.examples.echo:myecholibrary_bindlib

在實務上,大多數 FIDL 程式庫的 target_name 與所在資料夾相同,通常也是程式庫名稱。舉例來說,如果 FIDL 程式庫是 //sdk/fidl/fidl.examples.echo,自動產生的繫結程式庫目標就是 //sdk/fidl/fidl.examples.echo:fidl.examples.echo_bindlib

產生的程式碼目標

這些產生的繫結程式庫與使用者編寫的繫結程式庫完全相同。如要詳細瞭解如何為使用者編寫的繫結程式庫生成程式碼,請參閱繫結程式庫程式碼生成教學課程

範例

讓我們採用上述的 FIDL 程式庫,並在範例中使用。

FIDL (BUILD.gn)

fidl("my_fidl_target") {  # The target_name
  name = "fuchsia.gizmo.protocol"  # The library_name (optional, defaults to
                                   # target_name)
  sources = [ "protocol.fidl" ]
}

現在我們有了產生的繫結程式庫,目標名稱為 :my_fidl_target_bindlib,程式庫名稱為 fuchsia.gizmo.protocol。系統先前已顯示繫結程式庫的產生來源。我們可以使用這項資訊,為子項驅動程式庫建立繫結規則。

子項繫結規則 (BUILD.gn)

driver_bind_rules("bind") {
  rules = "meta/child_driver.bind"
  bind_output = "child_driver.bindbc"
  deps = [ ":my_fidl_target_bindlib" ]
}

child-driver.bind


using fuchsia.gizmo.protocol;

fuchsia.gizmo.protocol.TestingProtocol == fuchsia.gizmo.protocol.TestingProtocol.ZirconTransport;

驅動程式庫建立子項節點時,系統會自動為 fuchsia_driver_framework::NodeAddArgs 表格中的每個 offers 指派屬性。因此,父項驅動程式不需要手動指定這項屬性。

舉例來說,如果節點 (稱為 fuchsia_hardware_gizmo::Service) 提供以驅動程式庫程式為基礎的傳輸服務能力,系統會新增屬性,並將鍵設為 fuchsia.hardware.gizmo.Service,值設為 fuchsia.hardware.gizmo.Service.DriverTransport。這些值會與子項驅動程式庫在繫結規則中使用的相應產生繫結程式庫變數相符。

建立複合節點規格時,這類產生的程式碼仍很有用,通常會發生在板載驅動程式庫中。如果規格要根據這些方案與節點相符,就必須手動填寫規格的屬性,並提供方案資訊。

我們可以透過自動生成的程式碼目標,從複合節點規格建立程式碼存取這個繫結程式庫的常數。

複合節點規格建立工具 (BUILD.gn)

C++

source_set("bindlib_usage_cpp") {
  sources = [ "bindlib_usage.cc" ]
  deps = [ ":my_fidl_target_bindlib_cpp" ]
}

荒漠油廠

rustc_binary("bindlib_usage_rust") {
  edition = "2024"
  source_root = "bindlib_usage.rs"
  sources = [ "bindlib_usage.rs" ]
  deps = [ ":my_fidl_target_bindlib_rust" ]
}

複合節點規格建立者程式碼

C++

#include <bind/fuchsia/gizmo/protocol/cpp/bind.h>

std::string test_protocol_key = bind_fuchsia_gizmo_protocol::TESTINGPROTOCOL;
std::string test_protocol_value = bind_fuchsia_gizmo_protocol::TESTINGPROTOCOL_ZIRCONTRANSPORT;

荒漠油廠


fn main() {
    let _test_protocol_key: &str = bind_fuchsia_gizmo_protocol::TESTINGPROTOCOL;
    let _test_protocol_value: &str = bind_fuchsia_gizmo_protocol::TESTINGPROTOCOL_ZIRCONTRANSPORT;
}