驱动绑定

在 Fuchsia 系统中,驱动框架维护了在系统中的一个树形驱动和设备。在这个树形结构中,一个设备代表了操作系统中对某些硬件的访问。驱动程序发布和绑定设备。例如,一个 USB 驱动可以绑定一个 PCI 设备(它的父设备)和发布一个网络设备(它的子设备)。为了决定驱动程序可以绑定哪个设备,每一个驱动程序都有一个绑定规则,同时每一个设备都有一套属性。绑定规则定义了想要绑定的设备属性的匹配条件。

绑定规则和他们参考条件使用特定领域语言来定义。绑定编译器使用该语言并生成对应绑定规则的比特码。语言有两种源文件:规则和库。库被用作在驱动和绑定规则间的共享属性定义。编译器同样从绑定库中生成 FIDL 文件,让驱动可以在代码中查询设备属性。

注意:驱动绑定正在开发中,本文档仅描述当前状态。不是所有驱动都使用这套绑定规则,但是目前正在迁移,以便其全部转换。

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

现在,它生成了一个在驱动内包含的 C 头文件。这个头文件定义了宏:

  1. ZIRCON_DRIVER(Driver, Ops, VendorName, Version);
  • Driver是驱动的名字。
  • Ops是一个 zx_driver_ops,提供驱动操作钩子函数。
  • VendorName 是一个代表驱动供应商的字符串。
  • Version 是代表驱动版本的字符串。

需要了解更多细节,请参见the driver development documentation.

绑定规则

绑定规则定义了调用驱动bind()钩子函数的条件。绑定规则中的每一条声明都是一个设备属性的条件,这些条件必须为真,才能让驱动绑定。如果绑定规则结束运行并且所有的条件都为真,那么设备协调器将会调用驱动的bind()钩子函数。

绑定规则被认为是哪一个驱动应该绑定的明确条件表达。正因如此,条件表达式的顺序对于它的最终评价是不相关的。把绑定规则看作是一个布尔公式可能会对理解有帮助。

其中有4种类型的声明:

  • 条件声明 是相等(或不相等)表达式,其形式为 <key> == <value> (或 <key> != <value> )。
  • 接受声明是一组给定键值的允许接受值。
  • 条件声明提供简单的分支。
  • 真假声明可以被用作明确评估绑定规则。

示例

这个示例绑定规则可以在 //tools/bindc/examples/gizmo.bind中找到。

  1. using fuchsia.usb;
  2. // The device must be a USB device.
  3. fuchsia.BIND_PROTOCOL == fuchsia.usb.BIND_PROTOCOL.INTERFACE;
  4. if fuchsia.BIND_USB_VID == fuchsia.usb.BIND_USB_VID.INTEL {
  5. // If the device's vendor is Intel, the device class must be audio.
  6. fuchsia.BIND_USB_CLASS == fuchsia.usb.BIND_USB_CLASS.AUDIO;
  7. } else if fuchsia.BIND_USB_VID == fuchsia.usb.BIND_USB_VID.REALTEK {
  8. // If the device's vendor is Realtek, the device class must be one of the following values:
  9. accept fuchsia.BIND_USB_CLASS {
  10. fuchsia.usb.BIND_USB_CLASS.COMM,
  11. fuchsia.usb.BIND_USB_CLASS.VIDEO,
  12. }
  13. } else {
  14. // If the vendor is neither Intel or Realtek, do not bind.
  15. false;
  16. }

语言限制

在语言中有一些使用限制,这些限制是为了提供可读性,并确保绑定规则是驱动程序应该绑定条件的简单描述。

  • 不允许出现空块 空块是否意味着驱动程序将被绑定还是终止,这一点是不明确的。作者应该使用明确的true 或者 false 来声明。
  • if声明必须有else部分,并且是终端 这条限制通过明确的执行分支来提高可读性。由于没有声明可以跟在if声明后,所以很容易通过绑定规则追踪到路径。
  • 真和假的陈述必须是其范围内的唯一陈述 绑定规则不是必要的程序,评估的顺序也不重要。混合布尔型声明(尤其是 true)和其他条件可能导致描述情景不清晰。

语法

  1. rule = using-list , ( statement )+ ;
  2. using-list = ( using , ";" )* ;
  3. using = "using" , compound-identifier , ( "as" , IDENTIFIER ) ;
  4. statement = condition , ";" | accept | if-statement | true | false ;
  5. condition = compound-identifier , condition-op , value ;
  6. condition-op = "==" | "!=" ;
  7. accept = "accept" , compound-identifier , "{" ( value , "," )+ "}" ;
  8. if-statement = "if" , condition , "{" , ( statement )+ , "}" ,
  9. ( "else if" , "{" , ( statement )+ , "}" )* ,
  10. "else" , "{" , ( statement )+ , "}" ;
  11. true = "true" , ";" ;
  12. false = "flase" , ";" ;
  13. compound-identifier = IDENTIFIER ( "." , IDENTIFIER )* ;
  14. value = compound-identifier | STRING-LITERAL | NUMERIC-LITERAL | "true" | "false" ;

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

  1. accept
  2. as
  3. else
  4. false
  5. if
  6. true
  7. using

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

绑定编译器将忽略(视为空格)任意前缀为 //的句子,多行语句则用 /**/来定界。

构建目标

使用以下构建目标,来明确 Fuchsia 构建系统中的绑定规则:

  1. bind_rules("bind") {
  2. rules = <bind rules filename>
  3. output = <generated header filename>
  4. deps = [ <list of bind library targets> ]
  5. }

更多细节,参见//build/bind/bind.gni

测试

绑定编译器支持一个数据驱动的绑定规则单元测试框架,允许你在基于驱动隔离的前提下测试你的绑定规则。一个绑定规则的测试实例包含一个特定设备和一个期待结果,例如,绑定或者退出。测试实例以 JSON 规范文件的形式传递给绑定编译器,编译器通过运行debugger来执行每一条测试实例。

特定 JSON 必须是一系列的测试场景对象,每一个对象包含:

  • name,测试场景名字的字符串。
  • expected期待结果。必须是 “match”或者 “abort”
  • device 为一系列键值对描述设备属性。这和 debugger 中 device specifications相似。

示例

以下为一个测试用例示例,完整测试组在 //tools/bindc/examples/test.json 。当前用例检查绑定规则是否与一个具有所列属性的设备相匹配,例如,一个 Intel USB 音频设备。

  1. [
  2. {
  3. "name": "Intel",
  4. "expected": "match",
  5. "device": {
  6. "fuchsia.BIND_PROTOCOL": "fuchsia.usb.BIND_PROTOCOL.INTERFACE",
  7. "fuchsia.BIND_USB_VID": "fuchsia.usb.BIND_USB_VID.INTEL",
  8. "fuchsia.BIND_USB_CLASS": "fuchsia.usb.BIND_USB_CLASS.AUDIO"
  9. }
  10. }
  11. ]

构建

定义了一个构建目标如下

  1. bind_test("example_bind_test") {
  2. rules = <bind rules filename>
  3. tests = <test specification filename>
  4. deps = [ <list of bind library targets> ]
  5. }

另外,你可以很简单地添加一个 tests 变量在你已有的bind_rules中来生成测试目标。它的名字会在原来的目标名字加上_test组成。例如,下述会生成example_bind_test

  1. bind_rules("example_bind") {
  2. rules = "gizmo.bind"
  3. output = gizmo_bind.h
  4. tests = "tests.json"
  5. deps = [ "//src/devices/bind/fuchsia.usb" ]
  6. }

运行

如果你已经定义了你的测试构建目标,那么接下来你可以像往常一样用 fx test 运行这些测试。

  1. fx test example_bind_test

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

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

绑定库

一个绑定库定义了一组驱动将要分配给它的子驱动的属性。同样,绑定规则可以适用于绑定库。

命名空间

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

  1. library <vendor>.<library>;

每一个命名空间都必须以供应商作为开始,并且每一个供应商应当保证在它们自己的命名空间内没有冲突。尽管如此,语言中允许一个供应商来扩展另一个库。Google将使用 fuchsia 作为公共库。

库引入的任意值都是在命名空间内的。例如,下述库定义了一个新的 PCI 设备 ID 为GIZMO_VER_1

  1. library gizmotronics.gizmo;
  2. using fuchsia.pci as pci;
  3. extend uint pci.device_id {
  4. GIZMO_VER_1 = 0x4242,
  5. };

为了查询这个值,驱动程序作者应该使用完全合规的名称,如下所示:

  1. using fuchsia.pci as pci;
  2. using gizmotronics.gizmo;
  3. pci.device_id == gizmotronics.gizmo.device_id.GIZMO_VER_1

键值和数据

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

  1. <type> <name>;
  2. Or:
  3. <type> <name> {
  4. <value>,
  5. <value>,
  6. };

绑定库同样也可以扩展其他库的属性。

  1. extend <type> <name> {
  2. <value>,
  3. };

每一个键值都有一个类型,并且所有与该键对应的值必须是该类型的。语言支持的元类型:uintstring或者bool之一;和枚举(enum)。当定义键时,你应该更优先使用枚举,除非这个值是由外部来源提供,例如硬件提供。

当使用结构 <identifier> = <literal> 来定义原始值时,对于枚举来说,只有一个标识符是有必要的。使用相同字面定义多个原始值也是有效的。

语法

  1. library = library-header , using-list , declaration-list ;
  2. library-header = "library" , compound-identifier , ";" ;
  3. using-list = ( using , ";" )* ;
  4. using = "using" , compound-identifier , ( "as" , IDENTIFIER ) ;
  5. compound-identifier = IDENTIFIER ( "." , IDENTIFIER )* ;
  6. declaration-list = ( declaration , ";" )* ;
  7. declaration = primitive-declaration | enum-declaration ;
  8. primitive-declaration = ( "extend" ) , type , compound-identifier ,
  9. ( "{" primitive-value-list "}" ) ;
  10. type = "uint" | "string" | "bool";
  11. primitive-value-list = ( IDENTIFIER , "=" , literal , "," )* ;
  12. enum-declaration = ( "extend" ) , "enum" , compound-identifier ,
  13. ( "{" , enum-value-list , "}" ) ;
  14. enum-value-list = ( IDENTIFIER , "," )* ;
  15. literal = STRING-LITERAL | NUMERIC-LITERAL | "true" | "false" ;

标识符匹配正则表达式 [a-zA-Z]([a-zA-Z0-9_]*[a-zA-Z0-9])?,且不得与其他关键字相同,关键字列表如下:

  1. as
  2. bool
  3. enum
  4. extend
  5. library
  6. string
  7. uint
  8. using

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

绑定编译器将忽略(视为空格)任意前缀为 //的句子,多行语句则用 /**/来定界。

构建目标

使用以下构建目标,来明确 Fuchsia 构建系统中的绑定库:

  1. bind_library(<library name>) {
  2. source = <bind library filename>
  3. public_deps = [ <list of bind library targets> ]
  4. }

更多细节,请参考//build/bind/bind.gni