驱动程序绑定

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

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

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

关于迁移的此阶段,需要注意的一点是,不支持在绑定库中定义设备属性键(见下文)。相反,旧驱动程序绑定系统 (lib/ddk/binding.h) 中的键可供扩展。这些键已硬编码到 bind 编译器中,可在 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 是驱动程序的名称。
  • Opszx_driver_ops,即驱动程序操作钩子
  • VendorName 是一个字符串,表示驱动程序供应商的名称。
  • Version 是一个字符串,表示驱动程序的版本。

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

绑定规则

绑定规则用于定义调用驱动程序的 bind() 钩子的条件。绑定规则中的每个语句都是设备属性的条件,只有当这些条件为 true 时,驱动程序才能绑定。如果绑定规则执行完毕且所有条件均为 true,设备协调器将调用驱动程序的 bind() 钩子。

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

语句分为四种:

  • 条件语句<key> == <value>(或 <key> != <value>)形式的等式(或不等式)表达式。
  • 接受语句是指定键的允许值列表。
  • if 语句提供简单的分支。
  • true 和 false 语句可用于明确评估绑定规则。

示例

您可以在 //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 和 false 语句必须是其作用域中的唯一语句。绑定规则不是命令式程序,评估顺序并不重要。将布尔值语句(尤其是 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_pci;

using fuchsia.pci;
using fuchsia.platform;
using fuchsia.tee;

primary parent "pci" {
  fuchsia.BIND_PROTOCOL == fuchsia.pci.BIND_PROTOCOL.DEVICE;
}

parent "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 , ( parent )+ ;

composite-device = “composite” , IDENTIFIER;

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

编译目标

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

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": "pci",
        "tests": [
            {
                "name": "Match",
                "expected": "match",
                "device": {
                    "fuchsia.BIND_PROTOCOL": "fuchsia.pci.BIND_PROTOCOL.DEVICE"
                }
            },
            {
                "name": "Abort pci",
                "expected": "abort",
                "device": {
                    "fuchsia.BIND_PROTOCOL": "fuchsia.tee.BIND_PROTOCOL.DEVICE"
                }
            }
        ]
    }
]

构建

按如下方式定义测试 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 = "meta/gizmo.bind"
  bind_output = “gizmo.bindbc”
  tests = "meta/tests.json"
  deps = [ "//src/devices/bind/fuchsia.usb" ]
}

运行

如果您已为测试定义了 build 目标,则可以使用 fx test 照常运行测试。

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