本文档全面介绍了 Fuchsia 驱动程序框架 v2 (DFv2) 中的驱动程序清单语言 (DML)。
1. 概览
在驱动程序框架 v2 (DFv2) 中,驱动程序是由驱动程序运行程序执行的组件。过去,编写 DFv2 驱动程序需要维护多个不相关的配置文件:组件清单 (.cml):声明功能、使用的服务、runner 配置和二进制文件位置。
* 绑定规则文件 (.bind):声明驱动程序匹配规则、硬件标识符和复合父规范。
* 主板配置 (.fidl):主板驱动程序需要主板拓扑清单、功能路由映射和静态元数据载荷。
* 元数据反序列化样板:手动反序列化结构化 FIDL 字典或原始元数据字节。
单独维护这些文件会导致重复:服务名称、父节点名称和硬件协议要求在 .cml 和 .bind 文件中重复出现,从而导致漂移和配置 bug 的风险。
DML(驱动程序清单语言)通过提供单个统一的声明性 JSON5 清单 (.dml) 来解决此问题。DML 编译器 (dmlc) 会处理 .dml 文件并自动生成:
1. 组件清单 (.cml):已清理并验证的组件清单,可使用 cmc 进行编译。
2. 绑定规则 (.bind):将使用 bindc 编译的程序源代码绑定到驱动程序字节码 (.bindbc)。
3. FIDL 板配置:平台总线驱动程序的板拓扑二进制文件 (BoardConfig)。4. C++ 和 Rust 元数据解析器:从嵌入式 JSON 架构自动生成的类型安全解析器库。
DML 清单的官方架构已签入 //src/devices/tools/dmlc/dml.schema.json。
2. 驱动程序清单与主板清单
DML 支持两类清单:
| 功能 | 驱动程序清单 (compile-driver) |
主板清单 (compile-board) |
|---|---|---|
| 目标 | 独立或复合叶/中间设备驱动程序 | 系统主板驱动程序(例如 Vim3、QEMU、Astro) |
| 输出 | .cml、.bind、可选的 C++/Rust 元数据解析器 |
.cml、.bind、.fidl 板配置 |
| 顶级部分 | name,program,use,capabilities,expose,include,config |
name、program、children、offer、metadata_mappings、use、include、capabilities、expose |
| 绑定样式 | 非复合 (program.bind) 或复合 (use[].bind) |
平台总线设备绑定 (program.bind) |
| 硬件节点 | 消耗父节点 | 声明子节点和功能路由 (offer) |
3. 语法参考
3.1 顶层属性
{
// Name of the driver or board component (required).
name: "sample_driver",
// Optional composite name override. Defaults to 'name' with '-' replaced by '_'.
composite_name: "custom_composite_name",
// Shards and manifests to include.
include: [
"//sdk/lib/driver/compat/compat.shard.cml",
"subsystem.shard.dml"
],
// Execution parameters and standalone bind rules.
program: { ... },
// Capabilities consumed, and parent node definitions for composite drivers.
use: [ ... ],
// Capabilities provided by this driver, including metadata schemas.
capabilities: [ ... ],
// Capabilities exposed to the framework or parent component.
expose: [ ... ],
// Optional structured component configuration schema.
config: { ... },
// Board manifest only: child device node definitions.
children: [ ... ],
// Board manifest only: capability offers and constraint routing.
offer: [ ... ],
// Board manifest only: rules for aggregating metadata across children.
metadata_mappings: [ ... ]
}
3.2 program 部分
program 部分定义了独立(非复合)驱动程序的驱动程序执行参数和绑定规则。
| 属性 | 类型 | 说明 |
|---|---|---|
runner |
string | 组件运行程序名称。默认为 "driver"。 |
binary |
string | 驱动程序共享库的路径(例如 "driver/sample.so")。默认值为 "driver/<name>.so"。 |
compat |
string | DFv1 兼容性驱动程序共享库的路径(例如 "driver/sample_compat.so")。 |
colocate |
字符串或布尔值 | 是否将驱动程序与其父驱动程序主机("true" 或 true)并置。 |
default_dispatcher_opts |
字符串数组 | 驱动程序的默认调度程序的选项(例如 ["allow_sync_calls"])。 |
bind/requirements |
object | 用于独立驱动程序的结构化绑定块(请参阅硬件总线块)。注意:DML 中不允许使用字符串绑定路径。 |
driver_name |
string | 驱动程序名称替换(主要用于主板清单)。 |
示例:独立驱动程序 program 块
program: {
binary: "driver/usb_mass_storage.so",
colocate: "true",
default_dispatcher_opts: [ "allow_sync_calls" ],
bind: {
protocol: "fuchsia.usb.BIND_PROTOCOL.INTERFACE",
usb: {
class: "fuchsia.usb.BIND_USB_CLASS.MASS_STORAGE",
subclass: "fuchsia.usb.massstorage.BIND_USB_SUBCLASS.SCSI",
protocol: "fuchsia.usb.massstorage.BIND_USB_PROTOCOL.BULK_ONLY"
}
}
}
3.3 use 部分
use 部分具有双重用途:
1. 组件功能路由:传递标准 CML 功能(service、protocol、directory)。
2. 复合父节点规范:定义复合驱动程序绑定的父节点。
| 属性 | 类型 | 说明 |
|---|---|---|
service |
string | FIDL 服务名称(例如 "fuchsia.hardware.gpio.Service")。在父级绑定规则中发出 fuchsia.Service == "<service>";。 |
protocol |
string | FIDL 协议名称。 |
banjo |
string | Banjo 协议标识符(例如 "fuchsia.gpio.BIND_PROTOCOL.DEVICE")。在绑定规则中发出 fuchsia.BIND_PROTOCOL == <banjo>;,并从 CML 中排除。 |
name/instance_name |
string | 复合设备规范中父节点的名称。 |
primary |
布尔值 | 在恰好一个 use 条目中设置为 true,以将其指定为主要父节点。 |
availability |
string | "required"(默认)或 "optional"。在绑定规则中生成 optional parent "<name>"。 |
transport |
string | 服务传输:"Zircon"、"Driver" 或 "Banjo"。 |
generate_bind_rule |
布尔值 | 默认为 true。如果设置为 false,则通过 CML 使用相应功能,而无需生成复合父绑定节点。 |
bind/requirements |
object | 相应父节点的内嵌绑定规则(请参阅硬件总线块)。 |
初始化步骤的特殊处理
表示平台初始化步骤(例如 fuchsia.gpio.Init 和 fuchsia.pwm.Init)的服务会自动检测到:
* 它们会生成相应的绑定规则(例如 fuchsia.BIND_INIT_STEP == fuchsia.gpio.BIND_INIT_STEP.GPIO;)。
* 它们会自动从运行时 CML use 条目中过滤掉,因为初始化步骤是临时绑定里程碑,而不是可连接的运行时 FIDL 服务。
3.4 capabilities 和 expose 部分
功能
声明驱动程序提供的功能。除了标准 CML 功能类型之外,DML 还支持基于架构的元数据定义:
capabilities: [
{
service: "fuchsia.hardware.buttons.Service"
},
{
metadata: {
id: "fuchsia.hardware.buttons.Metadata",
schema: {
title: "ButtonsMetadata",
type: "object",
definitions: {
ButtonItem: {
type: "object",
properties: {
type: { type: "integer", fuchsia_type: "uint8" },
gpio: { type: "integer", fuchsia_type: "uint32" }
},
required: [ "type", "gpio" ]
}
},
properties: {
buttons: {
type: "array",
items: {
"$ref": "#/definitions/ButtonItem"
}
}
},
required: [ "buttons" ]
}
}
}
]
当 dmlc compile-driver 与 --h-output、--cc-output 或 --rs-output 一起执行时,它会生成完整的 C++ 和 Rust 解析器代码,用于根据架构解析和验证传入的元数据。
泄露
标准 DFv2 功能公开:
expose: [
{
service: "fuchsia.hardware.buttons.Service",
from: "self"
}
]
3.5 板级清单顶层部分
主板清单(使用 dmlc compile-board 编译)用于配置平台总线和拓扑:
children
声明发布到平台总线的设备:
children: [
{
name: "adc-buttons",
url: "fuchsia-pkg://fuchsia.com/adc-buttons#meta/adc-buttons.cm",
compatible: "fuchsia,adc-buttons",
metadata: [
{
id: "fuchsia.hardware.adc.Metadata",
data: [ 1, 0, 0, 0 ]
}
]
}
]
offer
在具有硬件限制的父控制器和子驱动程序之间路由功能:
offer: [
{
service: "fuchsia.hardware.gpio.Service",
name: "power",
from: "#gpio-controller-ff634400",
to: "#gpio-buttons",
constraints: {
pin: 92,
name: "power"
}
}
]
用户空间中断控制器
当平台设备使用由用户空间中断控制器驱动程序(而非内核中断控制器直接)管理的中断时,需要两个 offer 条目:从 "parent" 到中断控制器节点 (name: "pdev") 的路由 fuchsia.hardware.interrupt.ControllerRegistryService。dmlc 会自动为该控制器节点分配唯一的平台总线 interrupt_controller_id。
2. 通过 controller: "#<controller-node>" 引用使用方 fuchsia.hardware.platform.device.Service interrupts 约束中的控制器节点。
offer: [
{
name: "pdev",
service: "fuchsia.hardware.interrupt.ControllerRegistryService",
from: "parent",
to: "#gpio-controller-ff634400",
},
{
name: "pdev",
service: "fuchsia.hardware.platform.device.Service",
from: "parent",
to: "#touchscreen",
constraints: {
interrupts: [
{
name: "touch-irq",
number: 14,
mode: "EdgeLow",
controller: "#gpio-controller-ff634400",
},
],
},
},
]
metadata_mappings
将子设备限制聚合到统一的 FIDL 元数据中:
metadata_mappings: [
{
metadata_id: "fuchsia.hardware.pinimpl.Metadata",
aggregations: [
{
service: "fuchsia.hardware.gpio.Service",
field: "pins"
},
{
service: "fuchsia.hardware.pin.PinStatesService",
field: "device_pin_states",
use_node_name: true
}
]
}
]
4. 硬件总线块
DML 为主要硬件互连和发现协议提供了结构化块。
4.1 平台总线和设备树
平台设备通过供应商 ID (VID)、产品 ID (PID)、设备 ID (DID) 或设备树 compatible 字符串进行匹配:
bind: {
vid: "fuchsia.khadas.platform.BIND_PLATFORM_DEV_VID.KHADAS",
pid: "fuchsia.khadas.platform.BIND_PLATFORM_DEV_PID.VIM3",
did: "fuchsia.platform.BIND_PLATFORM_DEV_DID.GPIO",
compat: "fuchsia,gpio-buttons"
}
生成的绑定规则:
bind
fuchsia.BIND_PLATFORM_DEV_VID == fuchsia.khadas.platform.BIND_PLATFORM_DEV_VID.KHADAS;
fuchsia.BIND_PLATFORM_DEV_PID == fuchsia.khadas.platform.BIND_PLATFORM_DEV_PID.VIM3;
fuchsia.BIND_PLATFORM_DEV_DID == fuchsia.platform.BIND_PLATFORM_DEV_DID.GPIO;
fuchsia.COMPATIBLE == "fuchsia,gpio-buttons";
4.2 PCI 总线
PCI 设备匹配 PCI 供应商、设备、类、子类、接口、修订版本或拓扑:
bind: {
service: "fuchsia.hardware.pci.Service",
pci: {
vid: "fuchsia.pci.BIND_PCI_VID.INTEL",
did: "0x1234",
class: "fuchsia.pci.BIND_PCI_CLASS.GENERIC_SYSTEM_PERIPHERAL",
subclass: "0x05",
interface: "0x01",
revision: "0x04",
topo: "0x05"
}
}
生成的绑定规则:
bind
fuchsia.Service == "fuchsia.hardware.pci.Service";
fuchsia.BIND_PCI_VID == fuchsia.pci.BIND_PCI_VID.INTEL;
fuchsia.BIND_PCI_DID == 0x1234;
fuchsia.BIND_PCI_CLASS == fuchsia.pci.BIND_PCI_CLASS.GENERIC_SYSTEM_PERIPHERAL;
fuchsia.BIND_PCI_SUBCLASS == 0x05;
fuchsia.BIND_PCI_INTERFACE == 0x01;
fuchsia.BIND_PCI_REVISION == 0x04;
fuchsia.BIND_PCI_TOPO == 0x05;
4.3 USB 总线
USB 接口和设备在 USB 供应商、产品、类、子类、协议和接口编号方面匹配:
bind: {
usb: {
vid: "fuchsia.usb.BIND_USB_VID.GOOGLE",
pid: "0x1234",
class: "fuchsia.usb.BIND_USB_CLASS.MASS_STORAGE",
subclass: "0x02",
protocol: 0,
interface_number: 1,
bind_protocol: "fuchsia.usb.BIND_PROTOCOL.INTERFACE"
}
}
生成的绑定规则:
bind
fuchsia.BIND_PROTOCOL == fuchsia.usb.BIND_PROTOCOL.INTERFACE;
fuchsia.BIND_USB_VID == fuchsia.usb.BIND_USB_VID.GOOGLE;
fuchsia.BIND_USB_PID == 0x1234;
fuchsia.BIND_USB_CLASS == fuchsia.usb.BIND_USB_CLASS.MASS_STORAGE;
fuchsia.BIND_USB_SUBCLASS == 0x02;
fuchsia.BIND_USB_PROTOCOL == 0;
fuchsia.BIND_USB_INTERFACE_NUMBER == 1;
4.4 ACPI 总线
ACPI 设备通过硬件 ID (hid)、兼容 ID (first_cid) 或 ACPI 总线类型进行匹配:
bind: {
acpi: {
hid: "PNP0C0A",
first_cid: "PNP0C0B",
bus_type: "fuchsia.acpi.BIND_ACPI_BUS_TYPE.PCI"
}
}
生成的绑定规则:
bind
fuchsia.acpi.HID == "PNP0C0A";
fuchsia.acpi.FIRST_CID == "PNP0C0B";
fuchsia.BIND_ACPI_BUS_TYPE == fuchsia.acpi.BIND_ACPI_BUS_TYPE.PCI;
5. 复合绑定规则和父级匹配
复合驱动程序需要绑定到多个父设备(例如平台设备、GPIO 引脚和 I2C 总线)。
5.1 主要父节点
在主要父项的 use 条目中使用 primary: true 指定该父项:
use: [
{
service: "fuchsia.hardware.platform.device.Service",
name: "pdev",
primary: true,
bind: {
compat: "sample,buttons"
}
},
{
service: "fuchsia.hardware.gpio.Service",
name: "mic-mute",
availability: "optional"
}
]
生成的绑定规则: ```bind composite sample_composite;
使用 Fuchsia;
主要父级“pdev”{ fuchsia.COMPATIBLE == "sample,buttons"; }
可选父级“mic-mute”{ fuchsia.Service == "fuchsia.hardware.gpio.Service"; } ```
5.2 父节点匹配 (match_name: true)
当驱动程序连接到多个相同服务类型的父级(例如多个 GPIO 针脚或 ADC)时,请在父级的 bind 块内指定 match_name: true。dmlc 将自动发出与父级的拓扑节点名称 (fuchsia.NAME) 匹配的规则:
use: [
{
service: "fuchsia.hardware.gpio.Service",
name: "volume-up",
bind: {
match_name: true
}
}
]
生成的绑定规则:
bind
parent "volume-up" {
fuchsia.Service == "fuchsia.hardware.gpio.Service";
fuchsia.NAME == "volume-up";
}
5.3 父级分组
如果多个 use 条目引用了同一父级 name(例如,一个用于 Banjo 接口,另一个用于 FIDL 服务或 init 步骤),dmlc 会自动将它们分组到单个父级规范中:
use: [
{
service: "fuchsia.gpio.Init",
name: "gpio-init"
},
{
service: "fuchsia.hardware.gpio.Service",
name: "gpio-init",
availability: "optional"
}
]
生成的绑定规则:
bind
parent "gpio-init" {
fuchsia.BIND_INIT_STEP == fuchsia.gpio.BIND_INIT_STEP.GPIO;
fuchsia.Service == "fuchsia.hardware.gpio.Service";
}
6. 条件分支和数组接受
6.1 数组接受(accept)
如需与多个可接受的值中的任意一个值进行匹配,请传递数组,而不是单个标量。dmlc 将在绑定规则中生成 accept <property> { ... } 块。
bind: {
pci: {
vid: "fuchsia.pci.BIND_PCI_VID.INTEL",
did: [ "0x1234", "0x5678" ]
}
}
生成的绑定规则:
bind
fuchsia.BIND_PCI_VID == fuchsia.pci.BIND_PCI_VID.INTEL;
accept fuchsia.BIND_PCI_DID {
0x1234,
0x5678,
}
适用于 compat、vid、pid、did、PCI 属性、USB 属性、ACPI hid 和自定义规则。
6.2 条件分支 (one_of)
如果匹配逻辑需要在不同总线或设备代际之间进行分支,请使用 one_of。dmlc 会检查每个分支的触发条件,并将其编译为 if ... else if ... else ... 语句。
bind: {
one_of: [
{
pci: {
vid: "fuchsia.pci.BIND_PCI_VID.INTEL",
did: "0x1234"
}
},
{
pci: {
class: "0x02",
subclass: "0x00"
}
}
]
}
生成的绑定规则:
bind
if fuchsia.BIND_PCI_VID == fuchsia.pci.BIND_PCI_VID.INTEL {
fuchsia.BIND_PCI_DID == 0x1234;
} else if fuchsia.BIND_PCI_CLASS == 0x02 {
fuchsia.BIND_PCI_SUBCLASS == 0x00;
} else {
false;
}
one_of 中支持的触发属性包括:
* node_name / fuchsia.NAME
* acpi.hid 和 acpi.bus_type
* compat (fuchsia.COMPATIBLE)
* protocol / banjo (fuchsia.BIND_PROTOCOL)
* service (fuchsia.Service)
* vid、pid、did
* pci.vid、pci.did、pci.class
* usb.vid、usb.pid、usb.class
* 自定义 rules 键(例如 fuchsia.BIND_AUTOBIND)
6.3 不等式约束
如需要求属性不等于某个值,请在 rules 中指定 { "neq": <value> }:
bind: {
rules: {
"fuchsia.BIND_COMPOSITE": { neq: 1 }
}
}
生成的绑定规则:
bind
fuchsia.BIND_COMPOSITE != 1;
7. 真实世界中的前后对比示例
示例 1:独立 USB 大容量存储设备驱动程序
之前:单独的文件
meta/usb_mass_storage.cml:
json5
{
include: [
"inspect/client.shard.cml",
"syslog/client.shard.cml",
],
program: {
runner: "driver",
binary: "driver/usb_mass_storage.so",
bind: "meta/bind/usb_mass_storage.bindbc",
colocate: "true",
default_dispatcher_opts: [ "allow_sync_calls" ],
},
capabilities: [
{ service: "fuchsia.hardware.block.volume.Service" }
],
expose: [
{
service: "fuchsia.hardware.block.volume.Service",
from: "self"
}
]
}
meta/usb_mass_storage.bind:
```bind
using fuchsia.usb;
using fuchsia.usb.massstorage;
fuchsia.BIND_PROTOCOL == fuchsia.usb.BIND_PROTOCOL.INTERFACE; fuchsia.BIND_USB_CLASS == fuchsia.usb.BIND_USB_CLASS.MASS_STORAGE; fuchsia.BIND_USB_SUBCLASS == fuchsia.usb.massstorage.BIND_USB_SUBCLASS.SCSI; fuchsia.BIND_USB_PROTOCOL == fuchsia.usb.massstorage.BIND_USB_PROTOCOL.BULK_ONLY; ```
之后:统一 meta/usb_mass_storage.dml
{
name: "usb-mass-storage",
program: {
colocate: "true",
default_dispatcher_opts: [ "allow_sync_calls" ],
bind: {
protocol: "fuchsia.usb.BIND_PROTOCOL.INTERFACE",
usb: {
class: "fuchsia.usb.BIND_USB_CLASS.MASS_STORAGE",
subclass: "fuchsia.usb.massstorage.BIND_USB_SUBCLASS.SCSI",
protocol: "fuchsia.usb.massstorage.BIND_USB_PROTOCOL.BULK_ONLY"
}
}
},
capabilities: [
{ service: "fuchsia.hardware.block.volume.Service" }
],
expose: [
{
service: "fuchsia.hardware.block.volume.Service",
from: "self"
}
]
}
示例 2:复合按钮驱动程序
之前:单独的文件
meta/buttons.cml:
json5
{
include: [
"inspect/client.shard.cml",
"syslog/client.shard.cml",
],
program: {
runner: "driver",
binary: "driver/buttons.so",
bind: "meta/bind/buttons.bindbc",
},
use: [
{ service: "fuchsia.hardware.platform.device.Service" },
{ service: "fuchsia.hardware.gpio.Service" },
],
capabilities: [
{ service: "fuchsia.input.report.Service" }
],
expose: [
{
service: "fuchsia.input.report.Service",
from: "self"
}
]
}
meta/buttons.bind:
```bind
composite buttons;
using fuchsia; using fuchsia.gpio;
primary parent "pdev" { fuchsia.COMPATIBLE == "fuchsia,gpio-buttons"; }
optional parent "gpio-init" { fuchsia.BIND_INIT_STEP == fuchsia.gpio.BIND_INIT_STEP.GPIO; }
可选的父“volume-up”{ fuchsia.Service == "fuchsia.hardware.gpio.Service"; fuchsia.NAME == "volume-up"; } ```
之后:统一 meta/buttons.dml
{
name: "buttons",
use: [
{
service: "fuchsia.hardware.platform.device.Service",
name: "pdev",
primary: true,
bind: {
compat: "fuchsia,gpio-buttons"
}
},
{
service: "fuchsia.gpio.Init",
name: "gpio-init",
availability: "optional"
},
{
service: "fuchsia.hardware.gpio.Service",
name: "volume-up",
availability: "optional",
bind: {
match_name: true
}
}
],
capabilities: [
{ service: "fuchsia.input.report.Service" }
],
expose: [
{
service: "fuchsia.input.report.Service",
from: "self"
}
]
}
请注意:* pdev 明确是主要父级。
* fuchsia.gpio.Init 会转换为初始步骤绑定规则,并自动从生成的 CML 中过滤掉。
* volume-up 带有 match_name: true 的广告会自动与 fuchsia.NAME == "volume-up" 相匹配。