This guide walks through the tasks of serving Banjo protocols in a DFv2 driver and connecting to its Banjo server from a DFv1 child driver.
Banjo protocols, primarily used in DFv1 drivers, are defined in FIDL library
annotated with the @transport("Banjo")
and @banjo_layout("ddk-protocol")
lines, for example:
/// The protocol provides access to functions of the driver.
@transport("Banjo")
@banjo_layout("ddk-protocol")
closed protocol Misc {
/// Returns a unique identifier for this device.
strict GetHardwareId() -> (struct {
status zx.Status;
response uint32;
});
/// Returns the current device firmware version
strict GetFirmwareVersion() -> (struct {
status zx.Status;
major uint32;
minor uint32;
});
};
(Source: gizmo.test.fidl
)
To enable a DFv2 driver to use Banjo protocols, see the tasks below:
Serve a Banjo protocol from a DFv2 driver: Implement and serve Banjo protocols in a DFv2 driver, which sets up the driver to communicate with child drivers using Banjo protocols.
Connect to a Banjo server from a DFv2 driver: Connect to a parent driver's Banjo server from a DFv2 child driver and start using Banjo protocols.
Serve a Banjo protocol from a DFv2 driver
This section walks through implementing a Banjo protocol in a DFv2 driver
and serving the protocol to child drivers. This walkthrough is based
on the Banjo Transport example, which implements
the Misc
Banjo protocol in the gizmo.test
FIDL library.
The steps are:
1. Set up the Banjo protocol
To set up the Misc
Banjo protocol in a DFv2 driver, do the following:
In the
BUILD.gn
file, add the Banjo library as a dependency in thefuchsia_driver
target, for example:fuchsia_cc_driver("parent_driver") { output_name = "banjo_transport_parent" sources = [ "parent-driver.cc" ] deps = [ "//examples/drivers/bind_library:gizmo.example_cpp", "//examples/drivers/transport/banjo:fuchsia.examples.gizmo_banjo_cpp", ... ] }
In the driver's C++ header file, include the Banjo library's C++ header, for example:
#include <fuchsia/examples/gizmo/cpp/banjo.h> ... namespace banjo_transport { ...
(Source:
parent-driver.h
)To inherit from the Banjo protocol bindings, update the driver class using the following format:
ddk::<PROTOCOL_NAME>Protocol<<YOUR_DRIVER_CLASS>>
Replace
PROTOCOL_NAME
with the name of the Banjo protocol andYOUR_DRIVER_CLASS
is the class of your driver, both in camel case, for example:class ParentBanjoTransportDriver : public fdf::DriverBase, public ddk::MiscProtocol<ParentBanjoTransportDriver> { ... };
(Source:
parent-driver.h
)
2. Implement the Banjo protocol
In the driver class, define and implement each function in the Banjo protocol.
For instance, the example below shows a Banjo protocol named ProtocolName
:
@transport("Banjo")
@banjo_layout("ddk-protocol")
closed protocol ProtocolName {
/// Returns a unique identifier for this device.
strict FunctionName() -> (struct {
status zx.Status;
response_1 response_1_type;
response_2 response_2_type;
});
};
For this ProtocolName
Banjo protocol, the C++ binding for the
FunctionName
function looks like below:
zx_status_t ProtocolNameFunctionName(response_1_type* response_1, response_2_type* response_2);
And you can find the C++ bindings of the existing Banjo protocols in the following path of the Fuchsia source checkout:
<OUT_DIRECTORY>/fidling/gen/<PATH_TO_THE_FIDL_LIBRARY>/banjo
For instance, if your out
directory is out/default
and the FIDL
library is located in the examples/drivers/transport
directory,
then the C++ bindings are located in the following directory:
out/default/fidling/gen/examples/drivers/transport/banjo
See the following implementation in the Banjo Transport example:
The
Misc
protocol contains the functions below:/// Returns a unique identifier for this device. strict GetHardwareId() -> (struct { status zx.Status; response uint32; }); /// Returns the current device firmware version strict GetFirmwareVersion() -> (struct { status zx.Status; major uint32; minor uint32; });
The
ParentBanjoTransportDriver
class defines these functions as below:class ParentBanjoTransportDriver : public fdf::DriverBase, public ddk::MiscProtocol<ParentBanjoTransportDriver> { public: ... // MiscProtocol implementation. zx_status_t MiscGetHardwareId(uint32_t* out_response); zx_status_t MiscGetFirmwareVersion(uint32_t* out_major, uint32_t* out_minor); ... };
(Source:
parent-driver.h
)The functions are implemented as below:
zx_status_t ParentBanjoTransportDriver::MiscGetHardwareId(uint32_t* out_response) { *out_response = 0x1234ABCD; return ZX_OK; } zx_status_t ParentBanjoTransportDriver::MiscGetFirmwareVersion(uint32_t* out_major, uint32_t* out_minor) { *out_major = 0x0; *out_minor = 0x1; return ZX_OK; }
(Source:
parent-driver.cc
)
3. Serve the Banjo protocol
Once the Banjo protocol is implemented in a DFv2 driver, you need to serve the protocol to a DFv1 child node using a compat device server configured with Banjo.
To do so, complete the following tasks in the Set up the compat device server in a DFv2 driver guide:
Connect to a Banjo server from a DFv2 driver
This section uses the Banjo transport example to walk through the task of connecting a DFv2 child driver to a parent driver that serves a Banjo protocol.
The steps are:
1. Connect the child driver to the parent driver
To be able to connect a child driver to a parent driver for using Banjo
protocols, the child must be co-located in the same driver host as the parent
and both drivers need to use the compat banjo_client
library.
To connect the child driver to the parent driver, do the following:
In the child driver's component manifest (
.cml
), set thecolocate
field totrue
, for example:{ include: [ 'syslog/client.shard.cml' ], program: { runner: 'driver', binary: 'driver/banjo_transport_child.so', bind: 'meta/bind/child-driver.bindbc', // Run in the same driver host as the parent driver colocate: 'true', }, use: [ { service: 'fuchsia.driver.compat.Service' }, ], }
(Source:
child-driver.cml
)In the child driver's
BUILD.gn
file, add the Banjo library as a dependency in thefuchsia_driver
target, for example:fuchsia_cc_driver("child_driver") { output_name = "banjo_transport_child" sources = [ "child-driver.cc" ] deps = [ "//examples/drivers/transport/banjo:fuchsia.examples.gizmo_banjo_cpp", "//sdk/lib/driver/component/cpp:cpp", "//src/devices/lib/driver:driver_runtime", ] }
(Source:
BUILD.gn
)In the child driver's C++ header file, include the Banjo library's C++ header, for example:
#include <fuchsia/examples/gizmo/cpp/banjo.h> ... namespace banjo_transport { ...
(Source:
child-driver.h
)In the child driver's
BUILD.gn
file, add the compatbanjo_client
library as a dependency in thefuchsia_driver
target, for example:fuchsia_cc_driver("child_driver") { output_name = "banjo_transport_child" sources = [ "child-driver.cc" ] deps = [ "//examples/drivers/transport/banjo:fuchsia.examples.gizmo_banjo_cpp", "//sdk/lib/driver/compat/cpp", "//sdk/lib/driver/component/cpp:cpp", "//src/devices/lib/driver:driver_runtime", ] }
(Source:
BUILD.gn
)In the child driver's C++ source file, include the compat
banjo_client
library's C++ header, for example:#include "examples/drivers/transport/banjo/v2/child-driver.h" #include <lib/driver/compat/cpp/compat.h> #include <lib/driver/component/cpp/driver_export.h> #include <lib/driver/logging/cpp/structured_logger.h> namespace banjo_transport { zx::result<> ChildBanjoTransportDriver::Start() { ...
(Source:
child-driver.cc
)In the child driver's C++ source file, set up a Banjo client with the
compat::ConnectBanjo()
function, for example:zx::result<Client> ConnectBanjo(const std::shared_ptr<fdf::Namespace>& incoming, std::string_view parent_name = "default") {
In the Banjo Transport example, the child driver does the following to connect to the
Misc
protocol:zx::result<ddk::MiscProtocolClient> client = compat::ConnectBanjo<ddk::MiscProtocolClient>(incoming()); if (client.is_error()) { FDF_SLOG(ERROR, "Failed to connect client", KV("status", client.status_string())); return client.take_error(); }
(Source:
child-driver.cc
)
2. Use the Banjo protocol
In the child driver, use the protocol client to invoke the Banjo functions.
For instance, the example below shows a Banjo protocol named
ProtocolName
:
@transport("Banjo")
@banjo_layout("ddk-protocol")
closed protocol ProtocolName {
/// Returns a unique identifier for this device.
strict FunctionName() -> (struct {
status zx.Status;
response_1 response_1_type;
response_2 response_2_type;
});
};
For this ProtocolName
Banjo protocol, the C++ binding for the
FunctionName
function looks like below:
zx_status_t ProtocolNameFunctionName(response_1_type* response_1, response_2_type* response_2);
In the Banjo Transport example,
the GetHardwareId()
function is defined as below:
/// Returns a unique identifier for this device.
strict GetHardwareId() -> (struct {
status zx.Status;
response uint32;
});
(Source: gizmo.test.fidl
)
With the Banjo client stored in the client_
object
and the hardware_id_
variable defined as
a uint32_t
, you can call the GetHardwareId()
function
in the following way:
zx_status_t status = client_.GetHardwareId(&hardware_id_);
if (status != ZX_OK) {
return status;
}
(Source: child-driver.cc
)