Connect and serve Banjo protocols in a DFv2 driver

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

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.
  2. Implement the Banjo protocol.
  3. Serve the Banjo protocol.

1. Set up the Banjo protocol

To set up the Misc Banjo protocol in a DFv2 driver, do the following:

  1. 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",
        "//examples/drivers/transport/banjo:fuchsia.examples.gizmo_banjo_cpp",
         ...
      ]
    }
    
  2. 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)

  3. 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,
        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:

  1. Set up the compat device server.
  2. Provide Banjo services to descendant DFv1 drivers.

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.
  2. Use the Banjo protocol.

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:

  1. 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
            colocate: 'true',
        },
        use: [
            { service: 'fuchsia.driver.compat.Service' },
        ],
    }
    

    (Source: child-driver.cml)

  2. 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 = [
        "//examples/drivers/transport/banjo:fuchsia.examples.gizmo_banjo_cpp",
        "//sdk/lib/driver/component/cpp:cpp",
        "//src/devices/lib/driver:driver_runtime",
      ]
    }
    

    (Source: BUILD.gn)

  3. 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)

  4. 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",
        "//sdk/lib/driver/compat/cpp",
        "//sdk/lib/driver/component/cpp:cpp",
        "//src/devices/lib/driver:driver_runtime",
      ]
    }
    

    (Source: BUILD.gn)

  5. 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)

  6. 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)