绑定规则教程

本指南介绍了如何为驱动程序编写绑定规则,以便驱动程序绑定到其所需的设备。本文介绍了如何查找节点属性,然后使用绑定语言为其编写绑定规则。

本指南假定您熟悉 Driver Binding

节点属性的当前状态

目前,节点属性是在绑定库和 C++ 头文件中定义的。过去,节点属性是基于整数的键值对,以 C++ 结构体形式描述。所有属性都在 C++ 头文件中定义,绑定规则是驱动程序源代码的一部分。

不过,绑定系统最近经过了改版,现在绑定规则使用绑定语言在单独的文件中定义,并且节点属性可以支持具有布尔值、字符串值、整数值或枚举值的基于字符串的键。

目前,我们正在进行迁移,将所有驱动程序从旧绑定系统迁移到新绑定系统。C++ 头文件中的节点属性正在绑定库中重新定义。例如,所有设备协议 ID 绑定值都硬编码在 protodefs.h 中。现在,每个设备协议都在其自己的绑定库中定义,其中包含协议 ID 的定义以及与该协议关联的其他节点属性。绑定库全部位于 src/devices/bind 中。

在迁移完成之前,需要同时支持旧绑定系统和新绑定系统。

节点属性的未来状态

绑定迁移完成后,我们可以停止支持基于旧整数的节点属性,并移除 C++ 定义,例如 protodefs.hbinding_priv.h。所有属性都将在绑定库中定义,并且键将完全基于字符串。

您可以更新现有房源,使其能够利用新系统的功能。例如,BIND_COMPOSITE 属性是一个仅针对复合设备设置的标志。不过,由于旧系统仅支持整数值,因此该属性值以整数而非布尔值的形式表示。移除旧的绑定系统后,属性值可以更改为布尔值。

还可以更改 VID 的表示方式。我们可以使用 VID 名称,而不是为 VID 分配唯一的整数。例如,Intel VID 目前由整数值 0x8 表示:

library fuchsia.intel.platform;

extend uint fuchsia.BIND_PLATFORM_DEV_VID {
  INTEL = 0x8,
};

借助新的绑定系统,VID 可以由字符串值甚至枚举表示。

查找节点属性

使用 ffx driver list-devices

命令 ffx driver list-devices -v 会以以下格式输出树中每个设备的属性:

Name     : _TZ_
Moniker  : root.sys.platform.pt.acpi._TZ_
Driver   : None
5 Properties
[ 1/  5] : Key fuchsia.BIND_PROTOCOL Value 0x1F
[ 2/  5] : Key fuchsia.BIND_PCI_VID Value 0x1AF4
[ 3/  5] : Key fuchsia.BIND_PCI_DID Value 0x1052
[ 4/  5] : Key fuchsia.COMPOSITE_BIND Value 1
[ 5/  5] : Key "fuchsia.acpi.HID" Value "GFSH0005"

驱动程序源代码中的驱动程序属性

添加子设备时,驱动程序可以提供绑定规则匹配的属性。因此,您可以通过驱动程序源代码找到要绑定的属性。

在 DFv1 中,源代码中的节点属性由“属性”和“字符串属性”表示。属性包含基于整数的键和值。不过,字符串属性包含基于字符串的键和值,这些值可以是整数、布尔值、字符串或枚举。

以下代码段展示了驱动程序如何添加具有 BIND_PROTOCOL 属性和“ENABLE_TEST”字符串属性的设备。

device_add_args_t args = {};
args.version = DEVICE_ADD_ARGS_VERSION;
args.name = "parent";
args.ops = &dev_ops;

zx_device_str_prop_t str_props[] = {
      zx_device_str_prop_t{.key = "ENABLE_TEST",
                           .property_value = str_prop_bool_val(true)}
};
args.str_props = str_props;
args.str_prop_count = std::size(str_props);

device_add(parent, &args, &dev);

在 DFv2 中,节点属性由 fuchsia.driver.framework FIDL 库中的 NodeProperty 结构体表示:

auto properties = fidl::VectorView<fdf::wire::NodeProperty>(arena, 2);
properties[0] = fdf::MakeProperty(arena, BIND_PROTOCOL, ZX_PROTOCOL_PCI);
properties[1] = fdf::MakeProperty(arena, "ENABLED_TEST", true);

auto args = fdf::wire::NodeAddArgs::Builder(arena)
                  .name(arena, "sample-child")
                  .properties(properties)
                  .Build();

在绑定库中定义属性

在从旧绑定系统的驱动程序迁移到新驱动程序的过程中,我们会在新系统上重新定义绑定库中的旧属性。这些绑定库位于 src/devices/bind 目录中。

任何新属性都应在绑定库中定义。

旧绑定系统中的属性

大多数旧节点属性键和值都在 binding_priv.hprotodefs.h 中定义。

binding_priv.h 包含硬编码的属性键。每个属性键都会分配一个唯一的整数值。在新的绑定系统中,这些键使用 fuchsia.BIND_ 前缀重新定义。例如,BIND_PROTOCOL 会变为 fuchsia.BIND_PROTOCOLBIND_COMPOSITE 会变为 fuchsia.BIND_COMPOSITE

protodefs.h 包含设备协议的硬编码 ID 值。

设备协议绑定库

protodefs.h 中的每个设备协议都应有自己的绑定库,其中包含其协议 ID 和相关属性。一个示例是 fuchsia.i2c 绑定库,它不仅定义了协议 ID 值,还定义了其他 i2c 相关属性:

library fuchsia.i2c;

extend uint fuchsia.BIND_PROTOCOL {
  DEVICE = 24,
  IMPL = 25,
};

extend uint fuchsia.BIND_I2C_CLASS {
  HID = 0x01,
};

extend uint fuchsia.BIND_I2C_ADDRESS {
  BACKLIGHT = 0x2C,
  ETH = 0x18,
  FOCALTECH_TOUCH = 0x38,
  AMBIENTLIGHT = 0x39,
};

协议 ID 值与 protodefs.h 中定义的值一致:

DDK_PROTOCOL_DEF(I2C,                     24,   "i2c", 0)
DDK_PROTOCOL_DEF(I2C_IMPL ,               25,   "i2c-impl", 0)

IMPL 值表示实现并提供 i2c 协议的驱动程序。DEVICE 表示使用 IMPL 协议并提供 DEVICE 协议的核心驱动程序。

编写绑定规则

本部分介绍了如何针对驱动程序绑定的当前状态编写绑定规则。对于仅绑定到上述属性集的绑定规则,您需要编写:

using fuchsia.acpi;
using fuchsia.pci;

fuchsia.BIND_PROTOCOL == fuchsia.pci.BIND_PROTOCOL.DEVICE;
fuchsia.BIND_PCI_VID == fuchsia.pci.BIND_PCI_VID.VIRTIO;
fuchsia.BIND_PCI_DID == fuchsia.pci.BIND_PCI_DID.VIRTIO_DEV_TYPE_INPUT;
fuchsia.BIND_COMPOSITE == 1;
fuchsia.acpi.HID == "GFSH0005";

媒体资源键

整数键名称在 binding_priv.h 中定义,并以“fuchsia”为前缀。例如,dm dump 中的键 0x03 被确定为 fuchsia.BIND_COMPOSITE,因为 binding_priv.h 包含以下内容:

#define BIND_FLAGS 0x0000          // value of the flags register
#define BIND_PROTOCOL 0x0001       // primary protocol of the device
#define BIND_AUTOBIND 0x0002       // if this is an automated bind/load
#define BIND_COMPOSITE 0x003       // Whether this is a composite device

房产价值

由于这些属性与 PCI 相关联,因此匹配的值位于 fuchsia.pci.bind 库中。通过将 0x001af4fuchsia.BIND_PCI_VID 中的值进行匹配来找到 fuchsia.pci.BIND_PCI_VID.VIRTIO

extend uint fuchsia.BIND_PCI_VID {
  TEST = 0x0eff,
  AMD = 0x1002,
  REALTEK = 0x10ec,
  NVIDIA = 0x10de,
  GOOGLE = 0x1ae0,
  VIRTIO = 0x1af4,
  BROADCOM = 0x14e4,
  ATHEROS = 0x168c,
  INTEL = 0x8086,
};

如果绑定库中缺少某个值,您需要在新的或现有的绑定库中添加缺少的定义。或者,您也可以直接为该值编写绑定规则:

fuchsia.BIND_PCI_VID == 0x1af4;

不过,最好在绑定库中定义一个值。

复合绑定规则

复合绑定规则也使用相同的流程。对于要为其编写绑定规则的每个节点,您可以打印节点属性并为其编写绑定规则。

假设您要编写包含以下节点的复合绑定规则:一个节点绑定到上述示例,另一个节点绑定到以下内容:

Name     : I2C2
Moniker  : root.sys.platform.pt.acpi.I2C2
Driver   : None
5 Properties
[ 1/  5] : Key fuchsia.BIND_ACPI_ID           Value 0x000034
[ 2/  5] : Key fuchsia.BIND_PCI_TOPO          Value 0x0000aa
[ 3/  5] : Key fuchsia.BIND_ACPI_BUS_TYPE     Value 0x000001
[ 4/  5] : Key "fuchsia.hardware.acpi.Device" Value true
[ 5/  5] : Key fuchsia.BIND_PROTOCOL          Value 0x00001e

然后,您可以编写以下内容:

using fuchsia.acpi;
using fuchsia.pci;

primary parent "pci_sample" {
  fuchsia.BIND_PROTOCOL == fuchsia.pci.BIND_PROTOCOL.DEVICE;
  fuchsia.BIND_PCI_VID == fuchsia.pci.BIND_PCI_VID.VIRTIO;
  fuchsia.BIND_PCI_DID == fuchsia.pci.BIND_PCI_DID.VIRTIO_DEV_TYPE_INPUT;
  fuchsia.BIND_COMPOSITE == 1;
  fuchsia.acpi.HID == "GFSH0005";
}

parent "acpi" {
  fuchsia.driver.framework.dfv2 == true;
  fuchsia.BIND_ACPI_ID == 0x000034;
  fuchsia.BIND_PCI_TOPO == 0x0000aa;
  fuchsia.BIND_ACPI_BUS_TYPE == 0x000001;
}

可选节点

可选节点是指设备组中不必存在的节点,以便与复合驱动程序匹配。仅在使用设备组时支持此功能,在将拓扑中的节点与复合驱动程序匹配的普通旧版复合中,不会匹配可选节点。

在绑定规则中,将 optional 放在 parent 关键字之前会将节点标记为可选。按照惯例,所有可选节点都应写在常规附加节点之后。

可选节点用于编写更通用的复合驱动程序规则。例如,HID 按钮驱动程序支持 9 种不同的按钮类型(调高音量、摄像头静音等),但并非所有硬件设备都具有所有这些按钮。对于可选节点,绑定规则可以编写为与平台无关,这样,主板驱动程序就可以创建具有 HID 按钮驱动程序支持的任意一组按钮的复合设备。

绑定规则示例

分支绑定规则

绑定语言支持通过 if 语句进行分支,但存在一些限制。

if 语句必须有 else 块,并且是终端语句。此限制通过明确执行分支来提高可读性。由于 if 语句后不能跟任何语句,因此很容易跟踪绑定规则的路径。

if fuchsia.hardware.tee.Service == fuchsia.hardware.tee.Service.ZirconTransport {
  fuchsia.BIND_PLATFORM_DEV_VID == fuchsia.platform.BIND_PLATFORM_DEV_VID.GENERIC;
} else {
  fuchsia.BIND_PLATFORM_DEV_VID == fuchsia.platform.BIND_PLATFORM_DEV_VID.QEMU;
}

只要嵌套的 if 语句符合限制条件,就可以使用。例如:

if fuchsia.driver.framework.dfv2 == true {
  if fuchsia.acpi.hid == "PNP0303" {
    true;
  } else {
    fuchsia.acpi.first_cid == "PNP0303";
  }
} else {
  fuchsia.BIND_PROTOCOL == fuchsia.acpi.BIND_PROTOCOL.DEVICE;
}

排除多个媒体资源值

如需拒绝某个键的多个属性值,您可以将多个不等于条件语句链接在一起。例如,如果您需要拒绝多个 fuchsia.BIND_PCI_DID 值,可以执行以下操作:

fuchsia.BIND_PCI_DID != 0x191b;
fuchsia.BIND_PCI_DID != 0x1912;
fuchsia.BIND_PCI_DID != 0x191d;
fuchsia.BIND_PCI_DID != 0x1902;
fuchsia.BIND_PCI_DID != 0x1916;
fuchsia.BIND_PCI_DID != 0x191e;