驱动程序绑定

如需了解如何编写绑定规则,请参阅绑定规则教程

在 Fuchsia 中,驱动程序框架在系统中维护了一个驱动程序和设备树。在此树中,设备表示对操作系统可用的一些硬件的访问。驱动程序会发布并绑定到设备。例如,USB 驱动程序可能会绑定到 PCI 设备(其父设备)并发布以太网设备(其子设备)。为了确定驱动程序可以绑定到哪些设备,每个驱动程序都有一条绑定规则,每个设备都有一组属性。绑定规则定义与其要绑定的设备属性匹配的条件。

绑定规则及其引用的条件由网域特定语言定义。绑定编译器会使用这种语言并为绑定规则生成字节码。该语言有两种源文件:规则和库。库用于在驱动程序和绑定规则之间共享属性定义。编译器还会从绑定库生成 FIDL 文件,以便驱动程序可在代码中引用设备属性。

关于迁移这一阶段的一点需要注意的是,不支持在绑定库中定义设备属性键(见下文)。可以扩展旧版驱动程序绑定系统 (lib/ddk/binding.h) 中的按键。这些键会硬编码到绑定编译器中,并可在 fuchsia 命名空间下使用。例如,PCI 供应商 ID 密钥为 fuchsia.BIND_PCI_VID。最终,硬编码的键将从此命名空间中移除,并且所有节点属性键都将在绑定库中进行定义。

编译器

编译器会获取一个库源列表和一个规则源。例如:

fx bindc compile \
  --include src/devices/bind/fuchsia.usb/fuchsia.usb.bind \
  --output tools/bindc/examples/gizmo.h \
  tools/bindc/examples/gizmo.bind

目前,它可以生成一个 C 头文件,该文件可能包含在驱动程序中。头文件定义一个宏:

ZIRCON_DRIVER(Driver, Ops, VendorName, Version);
  • Driver 是驱动程序的名称。
  • Ops 是一个 zx_driver_ops,它们是驱动程序操作钩子
  • VendorName 是一个字符串,表示驱动程序供应商的名称。
  • Version 是一个字符串,表示驱动程序的版本。

如需了解详情,请参阅驱动程序开发文档

绑定规则

绑定规则定义了调用驱动程序的 bind() 钩子的条件。绑定规则中的每个语句都是针对设备属性的条件,必须满足 true 才能使驱动程序绑定。如果绑定规则执行完毕且所有条件均为 true,则设备协调器将调用驱动程序的 bind() 钩子。

应将绑定规则视为驱动程序应绑定的条件的声明性表达式。因此,条件表达式的执行顺序与其最终评估无关。将绑定规则视为布尔公式可能会有所帮助。

有以下四种语句:

  • 条件语句<key> == <value>(或 <key> != <value>)形式的等式(或不等式)表达式。
  • Accept 语句是给定键的允许值列表。
  • If 语句提供简单的分支。
  • 判断正误可用于明确评估绑定规则。

示例

您可以在以下位置找到此绑定规则示例://tools/bindc/examples/gizmo.bind

using fuchsia.usb;

// The device must be a USB device.
fuchsia.BIND_PROTOCOL == fuchsia.usb.BIND_PROTOCOL.INTERFACE;

if fuchsia.BIND_USB_VID == fuchsia.usb.BIND_USB_VID.INTEL {
  // If the device's vendor is Intel, the device class must be audio.
  fuchsia.BIND_USB_CLASS == fuchsia.usb.BIND_USB_CLASS.AUDIO;
} else if fuchsia.BIND_USB_VID == fuchsia.usb.BIND_USB_VID.REALTEK {
  // If the device's vendor is Realtek, the device class must be one of the following values:
  accept fuchsia.BIND_USB_CLASS {
    fuchsia.usb.BIND_USB_CLASS.COMM,
    fuchsia.usb.BIND_USB_CLASS.VIDEO,
  }
} else {
  // If the vendor is neither Intel or Realtek, do not bind.
  false;
}

语言限制

对语言施加了一些限制,以提高可读性,并确保绑定规则是对驱动程序应绑定的条件的简单表示。

  • 不允许出现空块。 空块是意味着驱动程序将绑定还是中止,这尚不明确。作者应使用明确的 truefalse 语句。

  • If 语句必须具有 else 块,并且为终止状态。 此限制通过显式执行分支来提高可读性。由于 if 语句后面可能没有语句,因此很容易通过绑定规则跟踪路径。

  • 判断正误必须是其涵盖范围内的唯一陈述。 绑定规则不是命令式程序,评估顺序并不重要。将布尔值语句(尤其是 true)与其他条件混合可能会导致这种情况不明确。

语法

rule = using-list , ( statement )+ ;

using-list = ( using , ";" )* ;

using = "using" , compound-identifier , ( "as" , IDENTIFIER ) ;

statement = condition , ";" | accept | if-statement | true | false ;

condition = compound-identifier , condition-op , value ;

condition-op = "==" | "!=" ;

accept = "accept" , compound-identifier , "{" ( value , "," )+ "}" ;

if-statement = "if" , condition , "{" , ( statement )+ , "}" ,
                ( "else if" , "{" , ( statement )+ , "}" )* ,
                "else" , "{" , ( statement )+ , "}" ;

true = "true" , ";" ;

false = "false" , ";" ;

compound-identifier = IDENTIFIER ( "." , IDENTIFIER )* ;

value = compound-identifier | STRING-LITERAL | NUMERIC-LITERAL | "true" | "false" ;

标识符与正则表达式 [a-zA-Z]([a-zA-Z0-9_]*[a-zA-Z0-9])? 匹配,但不得与任何关键字匹配。关键字列表如下:

accept
as
else
false
if
true
using

字符串字面量匹配正则表达式 ”[^”]*”,数字字面量匹配正则表达式 [0-9]+0x[0-9A-F]+

绑定编译器会忽略任何以 // 为前缀的行以及以 /**/ 分隔的任何多行(视为空格)。

复合材料绑定

除了将驱动程序绑定到设备之外,Fuchsia 中的驱动程序还可以使用绑定规则基于节点创建复合设备。绑定规则遵循与非复合绑定相同的语言规范,但会分成包含名称和一组语句的节点。

绑定规则中只能有一个主节点。复合驱动程序将在与主节点相同的驱动程序主机中启动。

可在以下位置找到复合绑定规则文件示例://tools/bindc/examples/composite-gizmo.bind

composite gizmo_sysmem;

using fuchsia.platform;
using fuchsia.sysmem;
using fuchsia.tee;

primary node "sysmem" {
  fuchsia.BIND_PROTOCOL == fuchsia.sysmem.BIND_PROTOCOL.DEVICE;
}

node "tee" {
  if fuchsia.BIND_PROTOCOL == fuchsia.tee.BIND_PROTOCOL.DEVICE {
    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;
  }
}

复合绑定的语法为:

composite-bind-rules = [composite-device], using-list , ( node )+ ;

composite-device = “composite” , IDENTIFIER;

node = [ "primary" ], "node" , STRING-LITERAL , "{" , ( statement )+ , "}"

编译目标

如需在 Fuchsia 构建系统中声明绑定规则,请使用以下构建目标:

driver_bind_rules("bind") {
  rules = <bind rules filename>
  bind_output = <generated bind binary filename>
  deps = [ <list of bind library targets> ]
}

如需了解详情,请参阅 //build/bind/bind.gni

测试

绑定编译器支持针对绑定规则的数据驱动单元测试框架,该框架允许您独立于驱动程序之外测试绑定规则。绑定规则的测试用例由设备规范和预期结果(即绑定或中止)组成。测试用例以 JSON 规范文件的形式传递给绑定编译器,编译器通过运行调试程序来执行每个测试用例。

JSON 规范必须是一系列测试用例对象,其中每个对象均包含:

  • name:测试用例名称的字符串。
  • expected 预期结果。必须为 “match”“abort”
  • device:描述设备属性的字符串键值对列表。这与调试程序的设备规格类似(请参阅此示例)。

如果测试针对的是复合设备,则设备中的每个节点都可以有一个测试用例对象列表。单元测试的 JSON 规范将改为节点对象列表。每个节点对象都包含:

  • node:节点名称的字符串。它必须与绑定规则测试中的节点匹配。
  • tests:测试用例对象列表。

示例

这是一个示例测试用例,完整的测试集位于 //tools/bindc/examples/test.json。此示例检查绑定规则是否与具有所列属性的设备(即 Intel USB 音频设备)匹配。

[
  {
    "name": "Intel",
    "expected": "match",
    "device": {
      "fuchsia.BIND_PROTOCOL": "fuchsia.usb.BIND_PROTOCOL.INTERFACE",
      "fuchsia.BIND_USB_VID": "fuchsia.usb.BIND_USB_VID.INTEL",
      "fuchsia.BIND_USB_CLASS": "fuchsia.usb.BIND_USB_CLASS.AUDIO"
    }
  }
]

下面是一个包含测试用例的复合绑定节点的示例。整套测试位于 //tools/bindc/examples/composite-tests.json 中。每个测试用例会检查节点的绑定规则是否与具有所列属性的设备匹配。

[
    {
        "node": "sysmem",
        "tests": [
            {
                "name": "Match",
                "expected": "match",
                "device": {
                    "fuchsia.BIND_PROTOCOL": "fuchsia.sysmem.BIND_PROTOCOL.DEVICE"
                }
            },
            {
                "name": "Abort sysmem",
                "expected": "abort",
                "device": {
                    "fuchsia.BIND_PROTOCOL": "fuchsia.tee.BIND_PROTOCOL.DEVICE"
                }
            }
        ]
    }
]

Build

像这样定义测试 build 目标

bind_test("example_bind_test") {
  rules = <bind rules filename>
  tests = <test specification filename>
  deps = [ <list of bind library targets> ]
}

或者,您只需向现有的 bind_rules 添加一个 tests 参数即可生成测试目标。该名称将是原始定位条件的名称加上 _test。例如,以下代码会生成 example_bind_test

driver_bind_rules("example_bind") {
  rules = "gizmo.bind"
  bind_output = “gizmo.bindbc”
  tests = "tests.json"
  deps = [ "//src/devices/bind/fuchsia.usb" ]
}

运行

如果您已为测试定义了构建目标,则可以像往常一样使用 fx 测试来运行测试。

fx test example_bind_test

否则,您可以直接运行绑定工具。例如:

fx bindc test \
  tools/bindc/examples/gizmo.bind \
  --test-spec tools/bindc/examples/tests.json \
  --include src/devices/bind/fuchsia.usb/fuchsia.usb.bind

绑定库

绑定库定义一组属性,驱动程序可分配给其子项。此外,绑定规则可以引用绑定库。

命名空间

绑定库首先要定义其命名空间:

library <vendor>.<library>;

每个命名空间都必须以供应商开头,并且每个供应商都应确保自己的命名空间内没有冲突。不过,该语言允许一个供应商扩展另一个供应商的库。对于公开库,Google 会使用 fuchsia

库引入的任何值都带有命名空间。例如,以下库定义了一个新的 PCI 设备 ID GIZMO_VER_1

library gizmotronics.gizmo;

using fuchsia.pci as pci;

extend uint pci.device_id {
  GIZMO_VER_1 = 0x4242,
};

要引用此值,驱动程序作者应使用完全限定名称,如下所示。

using fuchsia.pci as pci;
using gizmotronics.gizmo;

pci.device_id == gizmotronics.gizmo.device_id.GIZMO_VER_1

键和值

设备属性定义与其他语言中的变量声明类似。

<type> <name>;
Or:
<type> <name> {
  <value>,
  <value>,
  …
};

绑定库还可以扩展其他库的属性。

extend <type> <name> {
  <value>,
  …
};

每个键都有一个类型,与该键对应的所有值都必须属于该类型。该语言支持基元类型:uintstringbool 之一以及枚举 (enum)。定义键时,除非值由外部来源(例如硬件)提供,否则您应首选枚举。

定义基元值时,请使用 <identifier> = <literal> 格式;对于枚举,只需使用标识符即可。可以使用同一字面量定义多个原始值。

语法

library = library-header , using-list , declaration-list ;

library-header = "library" , compound-identifier , ";" ;

using-list = ( using , ";" )* ;

using = "using" , compound-identifier , ( "as" , IDENTIFIER ) ;

compound-identifier = IDENTIFIER ( "." , IDENTIFIER )* ;

declaration-list = ( declaration , ";" )* ;

declaration = primitive-declaration | enum-declaration ;

primitive-declaration = ( "extend" ) , type , compound-identifier ,
                        ( "{" primitive-value-list "}" ) ;

type = "uint" | "string" | "bool";

primitive-value-list = ( IDENTIFIER , "=" , literal , "," )* ;

enum-declaration = ( "extend" ) , "enum" , compound-identifier ,
                   ( "{" , enum-value-list , "}" ) ;

enum-value-list = ( IDENTIFIER , "," )* ;

literal = STRING-LITERAL | NUMERIC-LITERAL | "true" | "false" ;

标识符与正则表达式 [a-zA-Z]([a-zA-Z0-9_]*[a-zA-Z0-9])? 匹配,但不得与任何关键字匹配。关键字列表如下:

as
bool
enum
extend
library
string
uint
using

字符串字面量匹配正则表达式 ”[^”]*”,数字字面量匹配正则表达式 [0-9]+0x[0-9A-F]+

绑定编译器会忽略任何以 // 为前缀的行以及以 /**/ 分隔的任何多行(视为空格)。

编译目标

如需在 Fuchsia 构建系统中声明绑定库,请使用以下构建目标:

bind_library(<library name>) {
  source = <bind library filename>
  public_deps = [ <list of bind library targets> ]
}

如需了解详情,请参阅 //build/bind/bind.gni