驱动程序清单语言 (DML) 参考

本文档全面介绍了 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" 相匹配。