Google celebrates Hispanic Heritage Month. See how.

Create a new bind library for a driver

This guide walks through the tasks related to creating a new bind library in the Fuchsia SDK development environment (which uses the Bazel build system).

In Fuchsia driver development, bind libraries make the following tasks easy to coordinate:

The Fuchsia SDK includes a set of bind libraries you can use to write bind rules for drivers. However, sometimes using only the existing bind libraries may not be sufficient to compose precise build rules for your driver. On such occasions, you may need to create a new bind library to define a custom set of binding properties for the driver and its target node.

Development scenario in this guide

When a new bind library becomes available, two groups with two distinct responsibilities may be interested in using the new bind library: one that manages the parent driver and the other that works on the child driver.

alt_text

Diagram 1. A node topology that displays the parent-child relationship between two drivers.

Diagram 1 shows a node topology with two drivers: parent-driver and child-driver. Now, imagine a scenario where a group of platform developers has written a driver (parent-driver) for a built-in ACPI device in a Fuchsia system, and a group of device developers is writing a driver (child-driver) for a new ACPI device, whose target node is bindlib-child.

In this scenario, the following events take place:

  1. Create a new bind library - A new bind library is created to define binding properties for the new ACPI device.

  2. This new custom bind library is then shared between the two groups:

Create a new bind library

Create a new bind library that enables developers to precisely identify certain target devices in a Fuchsia system.

To create a new bind library, the steps are:

  1. Write a new bind library.
  2. Create a build file for the new bind library.

1. Write a new bind library

Write a bind library that defines a new set of custom binding properties.

A new bind library may look as shown below (see testlibrary.bind in the lib directory of the bind library sample):

library fuchsia.examples.gizmo.bind;

// Include properties from other libraries
using fuchsia.acpi;

// Define new properties and values for this library
string ModelName;

enum GizmoType {
  MEM_64K,
  MEM_128K,
  MEM_256K,
};

// Extend values defined in other libraries
extend uint fuchsia.BIND_PCI_VID {
  GIZMOTRONICS = 0x314159,
};

The name of this new bind library is defined at the top in the line library fuchsia.examples.gizmo.bind;. Also notice that the line using fuchsia.acpi; establishes a dependency to thefuchsia.acpi bind library, which is included in the Fuchsia SDK. This setup reflects a realistic development scenario which often requires using the two types of bind libraries: an existing library and a custom library.

2. Create a build file for the new bind library

Create a build file (that is, BUILD.bazel) for the new bind library so that developers can auto-generate library artifacts.

The build targets below define a new C++ bind library named fuchsia.example.gizmo.bind in the sample bind library’s BUILD.bazel file:

# This is a bind library that we manually define.
fuchsia_bind_library(
    name = "fuchsia.examples.gizmo.bind",
    srcs = [
        "testlibrary.bind",
    ],
    deps = [
        "@fuchsia_sdk//bind/fuchsia.acpi",  # An SDK bind library.
    ],
)

# We have to create the C++ library for it manually as well.
fuchsia_bind_cc_library(
    name = "fuchsia.examples.gizmo.bind_cc",
    library = ":fuchsia.examples.gizmo.bind",
    # Has to have the C++ libraries of all the deps of the bind library.
    deps = [
        "@fuchsia_sdk//bind/fuchsia.acpi:fuchsia.acpi_cc",  # An SDK bind library's C++ lib.
    ],
)

One of the library artifacts generated by building the fuchsia.example.gizmo.bind library is the following C++ header file (which, by the way, is not included in the sample):

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// WARNING: This file is machine generated by bindc.

#ifndef BIND_FUCHSIA_EXAMPLES_GIZMO_BIND_BINDLIB_
#define BIND_FUCHSIA_EXAMPLES_GIZMO_BIND_BINDLIB_

#include <string>

#include <bind/fuchsia/acpi/cpp/bind.h>

namespace bind_fuchsia_examples_gizmo_bind {

static const std::string MODELNAME = "fuchsia.examples.gizmo.bind.ModelName";

static const std::string GIZMOTYPE = "fuchsia.examples.gizmo.bind.GizmoType";
static const std::string GIZMOTYPE_MEM_64K = "fuchsia.examples.gizmo.bind.GizmoType.MEM_64K";
static const std::string GIZMOTYPE_MEM_128K = "fuchsia.examples.gizmo.bind.GizmoType.MEM_128K";
static const std::string GIZMOTYPE_MEM_256K = "fuchsia.examples.gizmo.bind.GizmoType.MEM_256K";

static constexpr uint32_t BIND_PCI_VID_GIZMOTRONICS = 3227993;

}  // namespace bind_fuchsia_examples_gizmo_bind

#endif  // BIND_FUCHSIA_EXAMPLES_GIZMO_BIND_BINDLIB_

Developers can now use the binding properties in this C++ header file for the following tasks:

Update the parent driver’s child node

In Fuchsia, some drivers create child nodes that enable other drivers to bind to. Drivers that create child nodes are referred to as parent drivers. With a new bind library, the main task is to update the parent driver’s child node so that it receives binding properties from the new bind library.

To update the parent driver’s child node, the steps are:

  1. Add the new bind library in the build file.
  2. Assign binding properties to the child node.

1. Add the new bind library in the build file

Update the build file of the parent driver to include a dependency to the new bind library.

The parent driver’s BUILD.bazel file below shows that the cc_binary target has a dependency to the custom bind library fuchsia.example.gizmo.bind_cc:

cc_binary(
    name = "parent_driver",
    srcs = [
        "parent-driver.cc",
        "parent-driver.h",
    ],
    linkshared = True,
    deps = [
        # This is a C++ lib from our manually created bind library.
        "//src/bind_library/lib:fuchsia.examples.gizmo.bind_cc",
        # This is a C++ lib from our manually created FIDL based bind library.
        "//src/bind_library/lib:fuchsia.examples.gizmo_bindlib_cc",
        "//src/bind_library/lib:fuchsia.examples.gizmo_cc",
        # This is a C++ lib from an SDK FIDL based bind library.
        "@fuchsia_sdk//fidl/fuchsia.device.fs:fuchsia.device.fs_bindlib_cc",
        "@fuchsia_sdk//pkg/driver2-llcpp",
        "@fuchsia_sdk//pkg/sys_component_llcpp",
    ],
)

Notice that the dependency is prefixed with //src/bind_library/lib (instead of @fuchsia_sdk//), which indicates that it is from a local directory in the sample.

2. Assign binding properties to the child node

Update the source code of the parent driver so that certain binding properties from the new bind library are passed down to the child node.

With the auto-generated C++ header file from the new bind library included in parent-driver.cc, this driver can now read binding properties from the new bind library:

#include <bind/fuchsia/device/fs/cpp/bind.h>
#include <bind/fuchsia/examples/gizmo/bind/cpp/bind.h>
#include <bind/fuchsia/examples/gizmo/cpp/bind.h>

...

properties[0] = fdf::wire::NodeProperty::Builder(arena)
                    .key(fdf::wire::NodePropertyKey::WithIntValue(1 /* BIND_PROTOCOL */))
                    .value(fdf::wire::NodePropertyValue::WithIntValue(
                        bind_fuchsia_acpi::BIND_PROTOCOL_DEVICE))
                    .Build();
properties[1] = fdf::wire::NodeProperty::Builder(arena)
                    .key(fdf::wire::NodePropertyKey::WithStringValue(
                        arena, bind_fuchsia_acpi::HID))
                    .value(fdf::wire::NodePropertyValue::WithStringValue(arena, "GOOG"))
                    .Build();
properties[2] = fdf::wire::NodeProperty::Builder(arena)
                    .key(fdf::wire::NodePropertyKey::WithStringValue(
                        arena, bind_fuchsia_examples_gizmo_bind::MODELNAME))
                    .value(fdf::wire::NodePropertyValue::WithStringValue(arena, "GIZMO3000"))
                    .Build();
properties[3] = fdf::wire::NodeProperty::Builder(arena)
                    .key(fdf::wire::NodePropertyKey::WithStringValue(
                        arena, bind_fuchsia_examples_gizmo_bind::GIZMOTYPE))
                    .value(fdf::wire::NodePropertyValue::WithEnumValue(
                        arena, bind_fuchsia_examples_gizmo_bind::GIZMOTYPE_MEM_64K))
                    .Build();

Notice that some string variables from the bind_fuchsia_examples_gizmo_bind namespace (see the auto-generated C++ header file) are used to initialize the child node's binding properties.

Update the child driver’s bind rules

A driver that binds to a child node of an existing driver is referred to as a child driver. With a new bind library, the main task is to update the child driver’s bind rules to make use of binding properties from the new bind library. The underlying goal is to write more precise bind rules for the driver so that it is guaranteed to match the target node in a Fuchsia system.

To update the child driver’s bind rules, the steps are:

  1. Add the new bind library in the build file.
  2. Write bind rules using new binding properties.

1. Add the new bind library in the build file

Update the build file of the child driver to include a dependency to the new bind library

The child driver’s BUILD.bazel file below shows that the fuchsia_driver_bytecode_bind_rules target has a dependency to the custom bind library fuchsia.example.gizmo.bind:

fuchsia_driver_bytecode_bind_rules(
    name = "bind_bytecode",
    output = "child-driver.bindbc",
    rules = "child-driver.bind",
    deps = [
        # This bind library is one we created manually.
        "//src/bind_library/lib:fuchsia.examples.gizmo.bind",
        # This bind library is from a FIDL library that we created manually.
        "//src/bind_library/lib:fuchsia.examples.gizmo_bindlib",
        # This bind library is from an SDK FIDL library.
        "@fuchsia_sdk//fidl/fuchsia.device.fs:fuchsia.device.fs_bindlib",
    ],
)

Notice that the dependency is prefixed with //src/bind_library/lib (instead of @fuchsia_sdk//), which indicates that it is from a local directory in the sample.

2. Write bind rules using new binding properties

Write (or update) the child driver’s bind rules to use binding properties from the new bind library.

The child driver’s bind rules (child-driver.bind) show that the ModelName and GizmoType properties from the custom bind library fuchsia.example.gizmo.bind are used in the rules to narrow down the driver's target device:

using fuchsia.acpi;
using fuchsia.examples.gizmo.bind;

...

fuchsia.BIND_PROTOCOL == fuchsia.acpi.BIND_PROTOCOL.DEVICE;
fuchsia.acpi.hid == "GOOG";
fuchsia.examples.gizmo.bind.ModelName == "GIZMO3000";
fuchsia.examples.gizmo.bind.GizmoType == fuchsia.examples.gizmo.bind.GizmoType.MEM_64K;

For more information on bind rules, see Write bind rules for a driver.

Appendices

Differences in bind library code generation between the source and SDK

Most concepts and samples in the Bind library code generation tutorial apply to the Fuchsia SDK development environment.

However, the differences are as follows:

  • Rust targets cannot be generated in the SDK.
  • The C++ library target cc_library is :{target_name}_cc (instead of :{target_name}_cpp) in the SDK.
  • Targets for the C++ library fuchsia_bind_cc_library need to be added in BUILD.bazel manually if the bind library is not included in the SDK.