Write bind rules for a driver

This guide walks through the steps for writing bind rules using the i2c_temperature sample driver.

For a driver to bind to a node (which represents a hardware or virtual device), the driver’s bind rules must match the bind properties of the node. In this guide, we’ll write bind rules for the i2c_temperature sample driver so that they match the bind properties of the i2c-child node.

The i2c_controller driver creates a child node named i2c-child for testing the i2c_temperature sample driver. We can use this i2c_controller driver to identify the bind properties of the i2c-child node and write the matching bind rules for i2c_temperature.

Before you begin, writing bind rules requires familiarity with the concepts in Driver binding.

The steps are:

  1. Identify the bind properties.
  2. Write the bind rules.
  3. Add a Bazel build target for the bind rules.

1. Identify the bind properties

You can identify the bind properties of your target node in one of the following ways:

Use the ffx driver list-devices command

To print the properties of every node in the Fuchsia system, run the following command:

ffx driver list-devices -v

This command prints the properties of a node in the following format:

Name     : i2c-child
Moniker  : root.sys.platform.pt.acpi.acpi-FWCF.i2c-child
Driver   : None
3 Properties
[ 1/  3] : Key "fuchsia.hardware.i2c.Service"  Value Enum(fuchsia.hardware.i2c.Service.ZirconTransport)
[ 2/  3] : Key fuchsia.BIND_I2C_ADDRESS        Value 0x0000ff
[ 3/  3] : Key "fuchsia.driver.framework.dfv2" Value true

The output above shows that the i2c-child node has the following bind properties:

  • Property key fuchsia.hardware.i2c.Service with an enum value of fuchsia.hardware.i2c.Service.ZirconTransport.
  • Property key fuchsia.BIND_I2C_ADDRESS with an integer value of 0xFF.

Look up the bind properties in the driver source code

When adding a child node, drivers can provide bind properties to the node. Reviewing the source code of the driver that creates your target node as a child node helps you identify the bind properties to include in your bind rules.

The i2c_controller driver creates a child node named i2c-child to which the i2c_temperature sample driver binds. Examine the source code of the i2c_controller driver to identify which bind properties are passed to this child node:

// Set the properties of the node for drivers to target.
auto properties = fidl::VectorView<fuchsia_driver_framework::wire::NodeProperty>(arena, 2);
properties[0] = driver::MakeEnumProperty(arena, bind_fuchsia_hardware_i2c::SERVICE,
                                         bind_fuchsia_hardware_i2c::SERVICE_ZIRCONTRANSPORT);
properties[1] = driver::MakeProperty(arena, 0x0A02 /* BIND_I2C_ADDRESS */, 0xff);

This code shows that the i2c-child node is created with the following bind properties:

  • Property key fuchsia.hardware.i2c.Service with an enum value of fuchsia.hardware.i2c.Service.ZirconTransport.
  • Property key fuchsia.BIND_I2C_ADDRESS with an integer value of 0xFF.

2. Write the bind rules

Once you know the bind properties you want to match, you can use the bind language to write the bind rules for your driver.

In the previous section, we’ve identified that the i2c-child node has the following bind properties:

  • Property key fuchsia.hardware.i2c with an enum value of fuchsia.hardware.i2c.Service.ZirconTransport.
  • Property key fuchsia.BIND_I2C_ADDRESS with an integer value of 0xFF.

To match these properties, the i2c_temperature driver declares the following bind rules:

using fuchsia.hardware.i2c;

fuchsia.hardware.i2c.Service == fuchsia.hardware.i2c.Service.ZirconTransport;
fuchsia.BIND_I2C_ADDRESS == 0xFF;

Integer-based bind property keys that start with BIND_ (defined in binding_priv.h in the Fuchsia source tree) are old property keys currently hardcoded in the bind compiler. See the following definition for BIND_I2C_ADDRESS from binding_priv.h:

#define BIND_I2C_ADDRESS 0x0A02

When these keys are used in bind rules, they are prefixed with fuchsia..

3. Add a Bazel build target for the bind rules

Once you have written the bind rules for your driver, you need to update the BUILD.bazel file to add a build target for the bind rules bytecode using the fuchsia_driver_bytecode_bind_rules() template:

fuchsia_driver_bind_bytecode(
    name = "bind_bytecode",
    output = "i2c_temperature.bindbc",
    rules = "i2c_temperature.bind",
    deps = [
        "@fuchsia_sdk//fidl/fuchsia.hardware.i2c:fuchsia.hardware.i2c_bindlib",
    ],
)

For each library used in your bind rules, add the library as a dependency to the build target. For example, the i2c_temperature sample driver's bind rules use the fuchsia.hardware.i2c library, so the build target includes the bind library as a build dependency.

To determine which bind libraries are used in the bind rules, you can examine the driver source code. In the bind properties of the i2c-child node, the first property key fuchsia.hardware.i2c.Service is from a generated bind library from the FIDL protocol:

// Set the properties of the node for drivers to target.
auto properties = fidl::VectorView<fuchsia_driver_framework::wire::NodeProperty>(arena, 2);
properties[0] = driver::MakeEnumProperty(arena, bind_fuchsia_hardware_i2c::SERVICE,
                                         bind_fuchsia_hardware_i2c::SERVICE_ZIRCONTRANSPORT);
properties[1] = driver::MakeProperty(arena, 0x0A02 /* BIND_I2C_ADDRESS */, 0xff);

The prefix fuchsia_hardware_i2c implies that this bind property’s key and value are defined in the following header:

#include <bind/fuchsia/hardware/i2c/cpp/bind.h>

These bind libraries will have corresponding dependencies in the driver's build rules. See the following fuchsia.hardware.i2c dependency in the i2c_controller binary target:

cc_binary(
    name = "i2c_controller",
    srcs = [
        "i2c_controller.cc",
        "i2c_controller.h",
        "i2c_server.cc",
        "i2c_server.h",
    ],
    linkshared = True,
    deps = [
        "//src/i2c_temperature/lib",
        "@fuchsia_sdk//fidl/fuchsia.hardware.i2c:fuchsia.hardware.i2c_bindlib_cc",
        "@fuchsia_sdk//fidl/fuchsia.hardware.i2c:fuchsia.hardware.i2c_llcpp_cc",
        "@fuchsia_sdk//pkg/driver_compat",
        "@fuchsia_sdk//pkg/driver_component_cpp",
    ],
)

Appendices

NodeProperty and NodeAddArgs structs

Bind properties are represented by the NodeProperty struct in the fuchsia.driver.framework FIDL library:

/// Definition of a property for a node. A property is commonly used to match a
/// node to a driver for driver binding.
type NodeProperty = table {
    /// Key for the property.
    1: key NodePropertyKey;

    /// Value for the property.
    2: value NodePropertyValue;
};

Then the bind properties are passed to a child node using the NodeAddArgs struct:

/// Arguments for adding a node.
type NodeAddArgs = table {
    /// Name of the node.
    1: name string:MAX_NODE_NAME_LENGTH;

    /// Capabilities to offer to the driver that is bound to this node.
    2: offers vector<fuchsia.component.decl.Offer>:MAX_OFFER_COUNT;

    /// Functions to provide to the driver that is bound to this node.
    3: symbols vector<NodeSymbol>:MAX_SYMBOL_COUNT;

    /// Properties of the node.
    4: properties vector<NodeProperty>:MAX_PROPERTY_COUNT;
};