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 node properties of the
node. In this guide, we’ll write bind rules for the i2c_temperature
sample
driver so that they match the node 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 node 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 node properties
You can identify the node 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.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.platform.DRIVER_FRAMEWORK_VERSION" Value 0x000002
The output above shows that the i2c-child
node has the following node properties:
- Property key
fuchsia.hardware.i2c.Service
with an enum value offuchsia.hardware.i2c.Service.ZirconTransport
. - Property key
fuchsia.BIND_I2C_ADDRESS
with an integer value of0xFF
.
Look up the node properties in the driver source code
When adding a child node, drivers can provide node properties to the node. Reviewing the source code of the driver that creates your target node as a child node helps you identify the node 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 node 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] = fdf::MakeProperty(arena, bind_fuchsia_hardware_i2c::SERVICE,
bind_fuchsia_hardware_i2c::SERVICE_ZIRCONTRANSPORT);
properties[1] = fdf::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 offuchsia.hardware.i2c.Service.ZirconTransport
. - Property key
fuchsia.BIND_I2C_ADDRESS
with an integer value of0xFF
.
2. Write the bind rules
Once you know the node 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 node properties:
- Property key
fuchsia.hardware.i2c
with an enum value offuchsia.hardware.i2c.Service.ZirconTransport
. - Property key
fuchsia.BIND_I2C_ADDRESS
with an integer value of0xFF
.
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 node 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 node 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] = fdf::MakeProperty(arena, bind_fuchsia_hardware_i2c::SERVICE,
bind_fuchsia_hardware_i2c::SERVICE_ZIRCONTRANSPORT);
properties[1] = fdf::MakeProperty(arena, 0x0A02 /* BIND_I2C_ADDRESS */, 0xff);
The prefix fuchsia_hardware_i2c
implies that this node 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:
fuchsia_cc_driver(
name = "i2c_controller",
srcs = [
"i2c_controller.cc",
"i2c_controller.h",
"i2c_server.cc",
"i2c_server.h",
],
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_component_cpp",
],
)
Appendices
NodeProperty and NodeAddArgs structs
Node 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 node 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;
};