驱动绑定
在 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])?
,且不得与其他关键字匹配。关键词列表如下:
accept
as
else
false
if
true
using
字符串字面上匹配正则表达式”[^”]*”
,并且数字上匹配正则表达式 [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])?
,且不得与其他关键字相同,关键字列表如下:
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。