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 a DFv1 child driver. 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_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 DFv1 driver

This section uses the Banjo transport example to walk through the task of connecting a DFv1 child driver to a DFv2 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 driver's BUILD.gn file, add the Banjo library as a dependency in the fuchsia_driver target, for example:

    fuchsia_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 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 driver's BUILD.gn file, add the compat banjo_client library as a dependency in the fuchsia_driver target, for example:

    fuchsia_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 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 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)