本指南介绍了如何添加从某个驱动程序导出的 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_;
};
子驱动程序(客户端)
账号绑定
首先要讨论的是子驱动程序如何绑定。它可能会因任何数量的节点属性而进行绑定,但如果您只想根据父级导出的 FIDL 协议进行绑定,则需要 build 从 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(cpp17::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 协议和服务以及父项用于提供每个规则的传输方法创建绑定规则。
绑定库
这些绑定库中有三种可能的传输方法:Banjo
、ZirconTransport
和 DriverTransport
。目前可以放心地假定该值为 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_name
和 target_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 = "child_driver.bind"
bind_output = "child_driver.bindbc"
deps = [ ":my_fidl_target_bindlib" ]
}
子级驱动程序.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" ]
}
Rust
rustc_binary("bindlib_usage_rust") {
edition = "2021"
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;
Rust
fn main() {
let _test_protocol_key: &str = bind_fuchsia_gizmo_protocol::TESTINGPROTOCOL;
let _test_protocol_value: &str = bind_fuchsia_gizmo_protocol::TESTINGPROTOCOL_ZIRCONTRANSPORT;
}