复合节点

概览

本指南介绍了如何使用复合节点规范将复合节点添加到驱动程序框架。该文档假定您熟悉以下内容:

创建复合节点

复合节点是具有多个父级的节点。如需创建复合节点,您需要:

  • 在驱动程序中定义复合节点规范
  • 创建具有与规范匹配的绑定规则的复合驱动程序

定义规范时,流程如下:

  1. 驱动程序管理器会请求驱动程序索引查找与规范匹配的复合驱动程序
  2. 找到匹配的复合驱动程序后,驱动程序管理器会在拓扑中查找与每个父规范匹配的节点。每个匹配的节点都将成为复合节点的父节点。
  3. 所有父级规范都匹配后,驱动程序管理器会创建一个复合节点,并将该节点作为父级,然后将复合驱动程序绑定到该节点。主节点和节点名称由复合驱动程序提供。

composite-node-spec-bind-diagram

定义复合节点规范

复合节点规范是一组父级规范,用于定义将成为复合节点父级的节点。每个父级规范都包含以下内容:

  • 绑定规则 - 用于将父级规范与节点进行匹配的绑定规则
  • 属性 - 父级规范中用于与复合驱动程序的绑定规则进行匹配的属性。它们遵循与节点属性相同的格式。

绑定规则

绑定规则用于查找节点并将其与父级规范进行匹配。系统会根据绑定规则评估节点属性,如果匹配,则节点会成为复合元件的父级。

父级规范的绑定规则由可接受和被拒绝的属性值列表组成。为了与绑定规则匹配,节点属性必须包含所有接受的节点属性值,而不是任何被拒绝的属性值。

例如,如果父级规范包含绑定规则:

  • 接受 fuchsia.BIND_PROTOCOL 值 15 和 17
  • 拒绝 fuchsia.BIND_PLATFORM_DEV_VID 值“Intel”

然后,如果设备 fuchsia.BIND_PROTOCOL 属性的值为 15 或 17,但不包含 fuchsia.BIND_PLATFORM_DEV_VID 属性的“Intel”值,则设备绑定到节点。

确定绑定规则

确定绑定规则的过程与绑定语言中的绑定规则相同。如需确定绑定规则,您首先需要查找要绑定到的节点的属性。

您可以使用命令 ffx driver list-devices -v 输出节点拓扑中每个节点的属性:

Name     : i2c-1-56
Topo Path: sys/platform/i2c-0/aml-i2c/i2c/i2c-1-56
Driver   : fuchsia-boot:///#driver/i2c.so
Flags    : MUST_ISOLATE | BOUND
Proto    : ZX_PROTOCOL_I2C (24)
3 Properties
[ 1/  3] : Key fuchsia.BIND_I2C_BUS_ID        Value 0x000001
[ 2/  3] : Key fuchsia.BIND_I2C_ADDRESS       Value 0x000038
[ 3/  3] : Key "fuchsia.hardware.i2c.Service" Value "fuchsia.hardware.i2c.Service.ZirconTransport"

从转储中,节点属性如下:

  • fuchsia.I2C_BUS_ID = 0x01
  • fuchsia.I2C_ADDRESS = 0x38
  • fuchsia.hardware.i2c.Service = fuchsia.hardware.i2c.Service.ZirconTransport

可以通过绑定库(例如 src/devices/bind 中的绑定库)搜索属性值。在此示例中,由于该节点是 I2C 节点,因此属性值位于 fuchsia.i2c.bind 中。

fuchsia.i2c.bind

extend uint fuchsia.BIND_I2C_BUS_ID {
  I2C_A0_0 = 0,
  I2C_2 = 1,
  I2C_3 = 2,
};

extend uint fuchsia.BIND_I2C_ADDRESS {
  BACKLIGHT = 0x2C,
  ETH = 0x18,
  FOCALTECH_TOUCH = 0x38,
  AMBIENTLIGHT = 0x39,
  AUDIO_CODEC = 0x48,
  GOODIX_TOUCH = 0x5d,
  TI_INA231_MLB = 0x49,
  TI_INA231_SPEAKERS = 0x40,
  TI_INA231_MLB_PROTO = 0x46,
};

我们还会从 FIDL 库生成绑定库,fuchsia.hardware.i2c.Service 及其值 fuchsia.hardware.i2c.Service.ZirconTransport 的条目便来自于此。如需了解详情,请参阅 generated-bind-libraries

这样,我们就可以将节点属性重新映射为:

  • fuchsia.hardware.i2c.Service = fuchsia.hardware.i2c.Service.ZirconTransport
  • fuchsia.BIND_I2C_BUS_ID = fuchsia.i2c.BIND_I2C_BUS_ID.I2C_2
  • fuchsia.BIND_I2C_ADDRESS = fuchsia.i2c.BIND_I2C_ADDRESS.FOCALTECH_TOUCH

您可以在驱动程序源代码中通过其生成的库访问绑定库值。如需了解详情,请参阅绑定库 codegen 教程

我们可以定义以下绑定规则来匹配这些属性:

accept fuchsia.hardware.i2c.Service { fuchsia.hardware.i2c.Service.ZirconTransport }
accept BIND_I2C_BUS_ID { fuchsia.i2c.BIND_I2C_BUS_ID.I2C_2 }
accept BIND_I2C_ADDRESS { fuchsia.i2c.BIND_I2C_ADDRESS.FOCALTECH_TOUCH }

在驱动程序框架 v1 (DFv1) 中编写代码

在 DFv1 中,复合节点规范是使用 DDK 编写的。用于编写绑定规则的函数位于 composite-node-spec.h 中。使用 DDK 库和绑定库 codegen 值,我们可以编写以下代码:

const ddk::BindRule kI2cBindRules[] = {
    ddk::MakeAcceptBindRule(bind_fuchsia_hardware_i2c::SERVICE,
                            bind_fuchsia_hardware_i2c::SERVICE_ZIRCONTRANSPORT),
    ddk::MakeAcceptBindRule(bind_fuchsia::I2C_BUS_ID,
                            bind_fuchsia_i2c::BIND_I2C_BUS_ID_I2C_2),
    ddk::MakeAcceptBindRule(bind_fuchsia::I2C_ADDRESS,
                            bind_fuchsia_focaltech_platform::BIND_I2C_ADDRESS_TOUCH),
};

在驱动程序框架 v2 (DFv2) 中编写

在 DFv2 中,系统会在 fuchsia.driver.framework FIDL 库中为 composite_node_spec.fidl 编写复合节点规范。sdk/lib/driver/component/cpp 中的 composite_node_spec.h 库可用于简化绑定规则的定义。

使用该库和绑定库 codegen 值,我们可以编写以下代码:

auto i2c_bind_rules = std::vector {
    MakeAcceptBindRule(bind_fuchsia_hardware_i2c::SERVICE,
                       bind_fuchsia_hardware_i2c::SERVICE_ZIRCONTRANSPORT),
    MakeAcceptBindRule(bind_fuchsia::I2C_BUS_ID,
                       bind_fuchsia_i2c::BIND_I2C_BUS_ID_I2C_2),
    MakeAcceptBindRule(bind_fuchsia::I2C_ADDRESS,
                       bind_fuchsia_focaltech_platform::BIND_I2C_ADDRESS_TOUCH),
};

属性

这些属性是键值对,用于将父级规范与复合驱动程序的绑定规则进行匹配。它们与节点属性是一回事,因此它们遵循相同的格式。属性键可以基于整数或基于字符串,而属性值可以是整数、布尔值、字符串或枚举类型。

在驱动程序框架 v1 (DFv1) 中写入

在 DFv1 中,复合节点规范使用 DDK 编写,而用于编写绑定规则的函数位于 composite-node-spec.h 中。使用 DDK 库和绑定库 codegen 值,我们可以编写以下代码:

const device_bind_prop_t kI2cProperties[] = {
    ddk::MakeProperty(bind_fuchsia_hardware_i2c::SERVICE,
                      bind_fuchsia_hardware_i2c::SERVICE_ZIRCONTRANSPORT),
    ddk::MakeProperty(bind_fuchsia::I2C_ADDRESS,
                      bind_fuchsia_focaltech_platform::BIND_I2C_ADDRESS_TOUCH),
};

在驱动程序框架 v2 (DFv2) 中编写

在 DFv2 中,系统会在 fuchsia.driver.framework FIDL 库中为 composite_node_spec.fidl 编写复合节点规范。//sdk/lib/driver/component/cpp 中的 node_add_args.h 库可用于简化绑定规则的定义。

auto i2c_properties[] = std::vector {
    ddk::MakeProperty(bind_fuchsia::I2C_ADDRESS,
                      bind_fuchsia_focaltech_platform::BIND_I2C_ADDRESS_TOUCH),
};

添加复合节点规范

创建复合节点规范涉及定义并将一组父级规范添加到驱动程序管理器。

平台总线复合元件

则可以通过 platform_bus.fidl API 添加复合节点规范。这适用于 DFv1 和 DFv2。

/// Adds a composite node specification to the bus. This will add a platform device specified
/// by |node| and insert a node into the composite node specification that matches the device.
AddCompositeNodeSpec(struct {
    node Node;
    spec fuchsia.driver.framework.CompositeNodeSpec;
}) -> () error zx.Status;

平台总线 API 使用 composite_node_spec.fidl 中定义的相同 CompositeNodeSpec 结构体。如需了解相关说明,请参阅使用 FIDL 定义复合节点规范

例如,假设我们定义了以下复合节点规范:

auto bind_rules = std::vector{
    driver::MakeAcceptBindRule(bind_fuchsia_hardware_i2c::SERVICE,
        bind_fuchsia_hardware_i2c::SERVICE_ZIRCONTRANSPORT),
    driver::MakeAcceptBindRule(bind_fuchsia::I2C_ADDRESS,
        bind_fuchsia_i2c::BIND_I2C_ADDRESS_BACKLIGHT),
};

auto properties = std::vector{
    driver::MakeProperty(bind_fuchsia_hardware_i2c::SERVICE,
        bind_fuchsia_hardware_i2c::SERVICE_ZIRCONTRANSPORT),
    driver::MakeProperty(bind_fuchsia::I2C_ADDRESS,
        bind_fuchsia_i2c::BIND_I2C_ADDRESS_BACKLIGHT),
};

auto spec = std::vector{
    fuchsia_driver_framework::ParentSpecification{
        .bind_rules = bind_rules,
        .properties = properties,
    },
};

定义复合节点规范后,板级驱动程序可以通过 PlatformBus FIDL 协议连接到平台总线,并使用客户端来调用 AddCompositeNodeSpec()

AddCompositeNodeSpec() 调用会将根据节点字段中的数据创建的平台设备的父级规范插入到指定的复合节点规范中,然后将修改后的复合节点规范添加到驱动程序框架中。然后,它会创建并添加平台设备。

fpbus::Node dev;
dev.name() = "backlight";
dev.vid() = PDEV_VID_TI;  // 0x10
dev.pid() = PDEV_PID_TI_LP8556; // 0x01
dev.did() = PDEV_DID_TI_BACKLIGHT;  // 0x01

auto endpoints =
    fdf::CreateEndpoints<fuchsia_hardware_platform_bus::PlatformBus>();
if (endpoints.is_error()) {
    return endpoints.error_value();
}

fdf::WireSyncClient<fuchsia_hardware_platform_bus::PlatformBus> pbus =
    endpoints->client;
auto result = pbus.buffer(arena)->AddCompositeNodeSpec(
fidl::ToWire(fidl_arena, dev),
fidl::ToWire(fidl_arena, spec), false);

if (!result.ok()) {
    zxlogf(ERROR, "AddCompositeNodeSpec request failed: %s",
               result.FormatDescription().data());
    return result.status();
}

调用 AddCompositeNodeSpec() 后,系统会将以下复合节点规范添加到驱动程序框架中:

Name      : backlight
Driver    : fuchsia-boot:///#meta/ti-lp8556.cm
Nodes     : 2
Node 0    : None
  3 Bind Rules
  [ 1/ 3] : Accept "fuchsia.BIND_PLATFORM_DEV_VID" { 0x000010 }
  [ 2/ 3] : Accept "fuchsia.BIND_PLATFORM_DEV_PID" { 0x000001 }
  [ 2/ 3] : Accept "fuchsia.BIND_PLATFORM_DEV_DID" { 0x000001 }
  3 Properties
  [ 1/ 3] : Key "fuchsia.BIND_PLATFORM_DEV_VID"   Value 0x000010
  [ 2/ 3] : Key "fuchsia.BIND_PLATFORM_DEV_PID"   Value 0x000001
  [ 3/ 3] : Key "fuchsia.BIND_PLATFORM_DEV_DID"   Value 0x000001
Node 1    : None
  2 Bind Rules
  [ 1/ 2] : Accept "fuchsia.hardware.i2c.Service" { "fuchsia.hardware.i2c.Service.ZirconTransport" }
  [ 2/ 2] : Accept "fuchsia.BIND_I2C_ADDRESS"     { 0x00002C }
  2 Properties
  [ 1/ 2] : Key "fuchsia.hardware.i2c.Service" Value "fuchsia.hardware.i2c.Service.ZirconTransport"
  [ 2/ 2] : Key "fuchsia.BIND_I2C_ADDRESS"     Value 0x00002C
}

第一个父级规范由 AddCompositeSpec() 插入,并与平台设备匹配,后者包含 fpbus::Node dev 中提供的 VID、PID 和 DID 中的绑定规则和属性。其余父级规范来自传入的复合节点规范。

驱动程序框架 v1 (DFv1)

在 DFv1 中,驱动程序可以通过 DdkAddCompositeNodeSpec() 函数通过 DDK 库添加复合节点规范。

驱动程序必须先在 spec.h 库中定义 CompositeNodeSpec。使用上述绑定规则和属性,我们可以定义具有 I2C 父级规范的 CompositeNodeSpec

const ddk::BindRule kI2cBindRules[] = {
    ddk::MakeAcceptBindRule(bind_fuchsia_hardware_i2c::SERVICE,
                            bind_fuchsia_hardware_i2c::SERVICE_ZIRCONTRANSPORT),
    ddk::MakeAcceptBindRule(bind_fuchsia::I2C_BUS_ID,
                            bind_fuchsia_i2c::BIND_I2C_BUS_ID_I2C_2),
    ddk::MakeAcceptBindRule(bind_fuchsia::I2C_ADDRESS,
                            bind_fuchsia_focaltech_platform::BIND_I2C_ADDRESS_TOUCH),
};

const device_bind_prop_t kI2cProperties[] = {
    ddk::MakeProperty(bind_fuchsia_hardware_i2c::SERVICE,
                      bind_fuchsia_hardware_i2c::SERVICE_ZIRCONTRANSPORT),
    ddk::MakeProperty(bind_fuchsia::I2C_ADDRESS,
                      bind_fuchsia_focaltech_platform::BIND_I2C_ADDRESS_TOUCH),
};

auto spec = ddk::CompositeNodeSpec(kI2cBindRules, kI2cProperties);

您可以通过 AddParentSpec() 添加任何其他节点。例如,如果我们想为 GPIO 解释引脚添加父级规范,可以编写以下代码:

const ddk::BindRule kGpioInterruptRules[] = {
    ddk::MakeAcceptBindRule(bind_fuchsia::PROTOCOL,
                            bind_fuchsia_gpio::BIND_PROTOCOL_DEVICE),
    ddk::MakeAcceptBindRule(bind_fuchsia::GPIO_PIN,
                bind_fuchsia_amlogic_platform_s905d2::GPIOZ_PIN_ID_PIN_4),
};

const device_bind_prop_t kGpioInterruptProperties[] = {
    ddk::MakeProperty(bind_fuchsia::PROTOCOL,
                      bind_fuchsia_gpio::BIND_PROTOCOL_DEVICE),
    ddk::MakeProperty(bind_fuchsia_gpio::FUNCTION,
                      bind_fuchsia_gpio::FUNCTION_TOUCH_INTERRUPT)};

desc.AddParentSpec(kGpioInterruptRules, kGpioInterruptProperties);

元数据可以通过 set_metadata() 函数传递给复合节点规范的复合项。

CompositeNodeSpec 准备就绪后,您可以使用 DdkAddCompositeNodeSpec() 添加它:

auto status = DdkAddCompositeNodeSpec("ft3x27_touch", spec);

由于 CompositeNodeSpec 遵循构建器模式,因此可以简化为:

auto status =
     DdkAddCompositeNodeSpec("ft3x27_touch",
          ddk::CompositeNodeSpec(kFocaltechI2cRules, kFocaltechI2cProperties)
              .AddParentSpec(kGpioInterruptRules, kGpioInterruptProperties)
              .set_metadata(metadata);

驱动程序框架 v2 (DFv2)

在 DFv2 中,我们使用 fuchsia.driver.framework FIDL API 中的 CompositeNodeManager 添加复合节点规范。

@discoverable
protocol CompositeNodeManager {
    /// Add the given spec to the driver manager.
    AddSpec(CompositeNodeSpec) -> () error CompositeNodeSpecError;
};

使用 FIDL 定义复合节点规范

CompositeNodeSpec 结构体在 composite_node_spec.fidl 中定义。您可以使用 sdk/lib/driver/component/cpp 库中的 spec.hnode_add_args.h 函数为父级规范定义绑定规则和属性。

使用该库,我们可以定义一个复合节点规范,其中包含 I2C 节点和 gpio-interrupt 节点的父级规范:

auto i2c_bind_rules = std::vector {
    MakeAcceptBindRule(bind_fuchsia_hardware_i2c::SERVICE,
                       bind_fuchsia_hardware_i2c::SERVICE_ZIRCONTRANSPORT),
    MakeAcceptBindRule(bind_fuchsia::I2C_BUS_ID,
                       bind_fuchsia_i2c::BIND_I2C_BUS_ID_I2C_2),
    MakeAcceptBindRule(bind_fuchsia::I2C_ADDRESS,
                       bind_fuchsia_focaltech_platform::BIND_I2C_ADDRESS_TOUCH),
};

auto i2c_properties[] = std::vector {
    ddk::MakeProperty(bind_fuchsia::I2C_ADDRESS,
                      bind_fuchsia_focaltech_platform::BIND_I2C_ADDRESS_TOUCH),
};


auto gpio_interrupt_bind_rules = std::vector {
    MakeAcceptBindRule(bind_fuchsia::BIND_PROTOCOL,
                       bind_fuchsia_gpio::BIND_PROTOCOL_DEVICE),
    MakeAcceptBindRule(bind_fuchsia::GPIO_PIN,
                       bind_fuchsia_amlogic_platform_s905d2::GPIOZ_PIN_ID_PIN_4),
};

auto gpio_interrupt_properties[] = std::vector {
    ddk::MakeProperty(bind_fuchsia::BIND_PROTOCOL,
                      bind_fuchsia_gpio::FUNCTION_TOUCH_INTERRUPT),
};

auto nodes = std::vector{
      fdf::ParentSpec{
          .bind_rules = i2c_bind_rules,
          .properties = i2c_properties,
      },
      fdf::ParentSpec{
          .bind_rules = gpio_interrupt_bind_rules,
          .properties = gpio_interrupt_properties,
      },
  };

auto spec = fdf::CompositeNodeSpec {.name = "fo", .nodes = nodes};

添加复合节点规范

如需将复合节点规范添加到 CompositeNodeManager,您需要连接到该服务:

auto client = incoming()->Connect<fdf::CompositeNodeManager>();

if (client.is_error()) {
  FDF_LOG(ERROR, "Failed to connect to CompositeNodeManager: %s",
      zx_status_get_string(client.error_value()));
  return client.take_error();
}

fidl::SharedClient<fdf::CompositeNodeManager> composite_node_manager;
composite_node_manager.Bind(std::move(client.value()), dispatcher());

然后调用 API:

composite_node_manager->AddSpec(std::move(spec))
    .Then([this](
        fidl::Result<fdf::CompositeNodeManager::AddSpec>& create_result) {
            if (create_result.is_error()) {
              FDF_LOG(ERROR, "AddSpec failed: %s",
                  create_result.error_value().FormatDescription().c_str());
              return;
            }
            FDF_LOG(INFO, "Succeeded adding spec");
        });

定义复合驱动程序绑定规则

复合驱动程序是仅绑定到复合节点的驱动程序。驱动程序通过其绑定规则被定义为此类驱动程序。如需了解详情,请参阅复合绑定规则

匹配流程

匹配过程是通过将复合驱动程序的绑定规则应用于父级规范的属性来完成的。如果满足以下条件,则匹配成功:

  • 所有父级规范都必须与复合绑定规则中的节点匹配
  • 所有非可选复合绑定规则节点都必须与父级规范相匹配。

匹配不能含糊不清的字词:

  • 每个父级规范只能与一个复合绑定规则节点对应
  • 每个复合绑定规则节点最多只能与一个父级规范匹配。可选绑定规则可以与零个父级规范匹配。
  • 节点不需要按顺序匹配
  • 如果出现不确定情况,系统会输出一则警告消息。

composite-node-spec-bind-diagram

编写绑定规则

基于上述示例,假设我们希望绑定到父级规范中具有以下属性的复合节点规范:

i2c parent specification properties {
     fuchsia.hardware.i2c.Service: fuchsia.hardware.i2c.Service.ZirconTransport,
     fuchsia.BIND_I2C_ADDRESS: fuchsia.focaltech.platform.BIND_I2C_ADDRESS_TOUCH,
}

gpio-interrupt parent specification properties {
     fuchsia.BIND_PROTOCOL: fuchsia.gpio.BIND_PROTOCOL_DEVICE,
     fuchsia.gpio.FUNCTION: fuchsia.gpio.FUNCTION.TOUCH_INTERRUPT,
}

我们可以编写复合绑定规则,使其与父级规范相匹配:

composite focaltech_touch;

using fuchsia.gpio;
using fuchsia.hardware.i2c;
using fuchsia.i2c;

primary node "i2c" {
  fuchsia.hardware.i2c.Service == fuchsia.hardware.i2c.Service.ZirconTransport;
  fuchsia.BIND_I2C_ADDRESS == fuchsia.i2c.BIND_I2C_ADDRESS.FOCALTECH_TOUCH;
}

node "gpio-int" {
  fuchsia.BIND_PROTOCOL == fuchsia.gpio.BIND_PROTOCOL.DEVICE;
  fuchsia.gpio.FUNCTION == fuchsia.gpio.FUNCTION.TOUCH_INTERRUPT;
}

调试

如需验证复合节点是否已成功创建并尝试绑定复合驱动程序,您可以在日志中查找类似以下的语句:

Binding driver fuchsia-boot:///#meta/focaltech.cm

如需验证复合节点规范是否已成功添加并与复合驱动程序匹配,请运行以下命令:

ffx driver list-composite-node-specs -v

这将输出类似于以下内容的内容:

Name      : ft3x27_touch
Driver    : fuchsia-boot:///#meta/focaltech.cm
Nodes     : 2
Node 0    : "i2c" (Primary)
  3 Bind Rules
  [ 1/ 3] : Accept "fuchsia.hardware.i2c.Service" { "fuchsia.hardware.i2c.Service.ZirconTransport" }
  [ 2/ 3] : Accept "fuchsia.BIND_I2C_BUS_ID" { 0x000001 }
  [ 3/ 3] : Accept "fuchsia.BIND_I2C_ADDRESS" { 0x000038 }
  2 Properties
  [ 1/ 2] : Key "fuchsia.hardware.i2c.Service" Value "fuchsia.hardware.i2c.Service.ZirconTransport"
  [ 2/ 2] : Key "fuchsia.BIND_I2C_ADDRESS"     Value 0x000038
Node 1    : "gpio-int"
  2 Bind Rules
  [ 1/ 2] : Accept "fuchsia.BIND_PROTOCOL" { 0x000014 }
  [ 2/ 2] : Accept "fuchsia.BIND_GPIO_PIN" { 0x000004 }
  2 Properties
  [ 1/ 2] : Key "fuchsia.BIND_PROTOCOL"        Value 0x000014
  [ 2/ 2] : Key "fuchsia.gpio.FUNCTION"        Value "fuchsia.gpio.FUNCTION.TOUCH_INTERRUPT"

如果复合节点规范没有匹配的复合驱动程序,则输出将更像以下内容:

Name      : focaltech_touch
Driver    : None
Nodes     : 2
Node 0    : None
  3 Bind Rules
  [ 1/ 3] : Accept "fuchsia.hardware.i2c.Service" { "fuchsia.hardware.i2c.Service.ZirconTransport" }
  [ 2/ 3] : Accept "fuchsia.BIND_I2C_BUS_ID" { 0x000001 }
  [ 3/ 3] : Accept "fuchsia.BIND_I2C_ADDRESS" { 0x000038 }
  1 Properties
  [ 1/ 2] : Key "fuchsia.hardware.i2c.Service" Value "fuchsia.hardware.i2c.Service.ZirconTransport"
  [ 2/ 2] : Key "fuchsia.BIND_I2C_ADDRESS"     Value 0x000038
Node 1    : None
  2 Bind Rules
  [ 1/ 2] : Accept "fuchsia.BIND_PROTOCOL" { 0x000014 }
  [ 2/ 2] : Accept "fuchsia.BIND_GPIO_PIN" { 0x000004 }
  2 Properties
  [ 1/ 2] : Key "fuchsia.BIND_PROTOCOL"        Value 0x000014
  [ 2/ 2] : Key "fuchsia.gpio.FUNCTION"        Value "fuchsia.gpio.FUNCTION.TOUCH_INTERRUPT