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. {{"<strong>"}}@transport("Banjo"){{"</strong>"}} {{"<strong>"}}@banjo_layout("ddk-protocol"){{"</strong>"}} 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.
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:
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 the fuchsia_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", {{"<strong>"}}"//examples/drivers/transport/banjo:fuchsia.examples.gizmo_banjo_cpp",{{"</strong>"}} ... ] }
In the driver‘s C++ header file, include the Banjo library’s C++ header, for example:
{{"<strong>"}}#include <fuchsia/examples/gizmo/cpp/banjo.h>{{"</strong>"}} ... 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 and YOUR_DRIVER_CLASS
is the class of your driver, both in camel case, for example:
class ParentBanjoTransportDriver : public fdf::DriverBase, {{"<strong>"}}public ddk::MiscProtocol<ParentBanjoTransportDriver>{{"</strong>"}} { ... };
(Source: parent-driver.h
)
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
)
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:
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:
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 the colocate
field to true
, 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 {{"<strong>"}}colocate: 'true',{{"</strong>"}} }, 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 the fuchsia_driver
target, for example:
fuchsia_cc_driver("child_driver") { output_name = "banjo_transport_child" sources = [ "child-driver.cc" ] deps = [ {{"<strong>"}}"//examples/drivers/transport/banjo:fuchsia.examples.gizmo_banjo_cpp",{{"</strong>"}} "//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:
{{"<strong>"}}#include <fuchsia/examples/gizmo/cpp/banjo.h>{{"</strong>"}} ... namespace banjo_transport { ...
(Source: child-driver.h
)
In the child driver's BUILD.gn
file, add the compat banjo_client
library as a dependency in the fuchsia_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", {{"<strong>"}}"//sdk/lib/driver/compat/cpp",{{"</strong>"}} "//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" {{"<strong>"}}#include <lib/driver/compat/cpp/compat.h>{{"</strong>"}} #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
)
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
)