驱动绑定
在 Fuchsia 系统中,驱动框架维护了在系统中的一个树形驱动和设备。在这个树形结构中,一个设备代表了操作系统中对某些硬件的访问。驱动程序发布和绑定设备。例如,一个 USB 驱动可以绑定一个 PCI 设备(它的父设备)和发布一个网络设备(它的子设备)。为了决定驱动程序可以绑定哪个设备,每一个驱动程序都有一个绑定规则,同时每一个设备都有一套属性。绑定规则定义了想要绑定的设备属性的匹配条件。
绑定规则和他们参考条件使用特定领域语言来定义。绑定编译器使用该语言并生成对应绑定规则的比特码。语言有两种源文件:规则和库。库被用作在驱动和绑定规则间的共享属性定义。编译器同样从绑定库中生成 FIDL 文件,让驱动可以在代码中查询设备属性。
注意:驱动绑定正在开发中,本文档仅描述当前状态。不是所有驱动都使用这套绑定规则,但是目前正在迁移,以便其全部转换。
关于这个阶段的迁移,需要注意的一点是,不支持在绑定库中定义设备属性键(见下文)。相反,旧驱动绑定系统(lib/ddk/binding.h)中的键是可以扩展的。
这些键值在绑定编译器中是硬编码,在 fuchsia 命名空间下可用。例如, PCI 供应商 ID 键是fuchsia.BIND_PCI_VID。最终,这些硬编码键值将会从这个命名空间中移除,所有绑定属性键值都将在绑定库中定义。
现在,它生成了一个在驱动内包含的 C 头文件。这个头文件定义了宏:
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中找到。
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;}
语言限制
在语言中有一些使用限制,这些限制是为了提供可读性,并确保绑定规则是驱动程序应该绑定条件的简单描述。
- 不允许出现空块
空块是否意味着驱动程序将被绑定还是终止,这一点是不明确的。作者应该使用明确的
true或者false来声明。 - 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 = "flase" , ";" ;compound-identifier = IDENTIFIER ( "." , IDENTIFIER )* ;value = compound-identifier | STRING-LITERAL | NUMERIC-LITERAL | "true" | "false" ;
标识符匹配正则表达式 [a-zA-Z]([a-zA-Z0-9_]*[a-zA-Z0-9])? ,且不得与其他关键字匹配。关键词列表如下:
acceptaselsefalseiftrueusing
字符串字面上匹配正则表达式”[^”]*”,并且数字上匹配正则表达式 [0-9]+ 或者0x[0-9A-F]+。
绑定编译器将忽略(视为空格)任意前缀为 //的句子,多行语句则用 /* 和*/来定界。
构建目标
使用以下构建目标,来明确 Fuchsia 构建系统中的绑定规则:
bind_rules("bind") {rules = <bind rules filename>output = <generated header filename>deps = [ <list of bind library targets> ]}
更多细节,参见//build/bind/bind.gni。
测试
绑定编译器支持一个数据驱动的绑定规则单元测试框架,允许你在基于驱动隔离的前提下测试你的绑定规则。一个绑定规则的测试实例包含一个特定设备和一个期待结果,例如,绑定或者退出。测试实例以 JSON 规范文件的形式传递给绑定编译器,编译器通过运行debugger来执行每一条测试实例。
特定 JSON 必须是一系列的测试场景对象,每一个对象包含:
name,测试场景名字的字符串。expected期待结果。必须是“match”或者“abort”。device为一系列键值对描述设备属性。这和 debugger 中 device specifications相似。
示例
以下为一个测试用例示例,完整测试组在 //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"}}]
构建
定义了一个构建目标如下
bind_test("example_bind_test") {rules = <bind rules filename>tests = <test specification filename>deps = [ <list of bind library targets> ]}
另外,你可以很简单地添加一个 tests 变量在你已有的bind_rules中来生成测试目标。它的名字会在原来的目标名字加上_test组成。例如,下述会生成example_bind_test。
bind_rules("example_bind") {rules = "gizmo.bind"output = “gizmo_bind.h”tests = "tests.json"deps = [ "//src/devices/bind/fuchsia.usb" ]}
运行
如果你已经定义了你的测试构建目标,那么接下来你可以像往常一样用 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>,…};
每一个键值都有一个类型,并且所有与该键对应的值必须是该类型的。语言支持的元类型:uint,string或者bool之一;和枚举(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])?,且不得与其他关键字相同,关键字列表如下:
asboolenumextendlibrarystringuintusing
字符串字面上匹配正则表达式”[^”]*”,并且数字上匹配正则表达式 [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。
