blob: 86a3f9b8eb0f1db37bcd50d003548840d246b03f [file] [view]
# Driver Manifest Language (DML) Reference
This document provides a comprehensive reference for the **Driver Manifest Language (DML)** in
Fuchsia's [Driver Framework v2 (DFv2)][dfv2].
---
## 1. Overview
In Driver Framework v2 (DFv2), drivers are components executed by the driver runner.
Historically, authoring a DFv2 driver required maintaining multiple disconnected configuration files:
* A **Component Manifest (`.cml`)**: Declaring capabilities, used services, runner configuration, and binary locations.
* A **Bind Rules File (`.bind`)**: Declaring driver matching rules, hardware identifiers, and composite parent specifications.
* **Board Configurations (`.fidl`)**: Board drivers required board topology manifests, capability route mappings, and static metadata payloads.
* **Metadata Deserialization Boilerplate**: Manual deserialization of structured FIDL dictionaries or raw metadata bytes.
Maintaining these files separately introduced duplication: service names, parent node names, and
hardware protocol requirements were repeated across both `.cml` and `.bind` files, creating a risk
of drift and configuration bugs.
**DML (Driver Manifest Language)** solves this by providing a single, unified declarative JSON5 manifest
(`.dml`). The DML compiler (`dmlc`) processes `.dml` files and automatically produces:
1. **Component Manifests (`.cml`)**: Cleaned and validated component manifests ready for compilation with `cmc`.
2. **Bind Rules (`.bind`)**: Bind program source code compiled with `bindc` into driver bytecode (`.bindbc`).
3. **FIDL Board Configurations**: Board topology binaries (`BoardConfig`) for platform bus drivers.
4. **C++ and Rust Metadata Parsers**: Type-safe parser libraries automatically generated from embedded JSON Schemas.
The official schema for DML manifests is checked in at
[`//src/devices/tools/dmlc/dml.schema.json`](https://cs.opensource.google/fuchsia/fuchsia/+/main:src/devices/tools/dmlc/dml.schema.json).
---
## 2. Driver Manifest vs Board Manifest
DML supports two categories of manifests:
| Feature | Driver Manifest (`compile-driver`) | Board Manifest (`compile-board`) |
| :--- | :--- | :--- |
| **Target** | Standalone or composite leaf/intermediate device drivers | System board drivers (e.g. Vim3, QEMU, Astro) |
| **Outputs** | `.cml`, `.bind`, optional C++/Rust metadata parsers | `.cml`, `.bind`, `.fidl` board configuration |
| **Top-Level Sections** | `name`, `program`, `use`, `capabilities`, `expose`, `include`, `config` | `name`, `program`, `children`, `offer`, `metadata_mappings`, `use`, `include`, `capabilities`, `expose` |
| **Binding Style** | Non-composite (`program.bind`) or composite (`use[].bind`) | Platform bus device binding (`program.bind`) |
| **Hardware Nodes** | Consumes parent nodes | Declares child nodes and capability routes (`offer`) |
---
## 3. Syntax Reference
### 3.1 Top-Level Properties
```json5
{
// 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 The `program` Section
The `program` section defines driver execution parameters and bind rules for standalone (non-composite) drivers.
| Property | Type | Description |
| :--- | :--- | :--- |
| `runner` | string | Component runner name. Defaults to `"driver"`. |
| `binary` | string | Path to driver shared library (e.g. `"driver/sample.so"`). Defaults to `"driver/<name>.so"`. |
| `compat` | string | Path to DFv1 compatibility driver shared library (e.g. `"driver/sample_compat.so"`). |
| `colocate` | string or boolean | Whether to colocate the driver in its parent driver host (`"true"` or `true`). |
| `default_dispatcher_opts` | array of string | Options for the driver's default dispatcher (e.g. `["allow_sync_calls"]`). |
| `bind` / `requirements` | object | Structured bind block for standalone drivers (see [Hardware Bus Blocks](#4-hardware-bus-blocks)). Note: string bind paths are not allowed in DML. |
| `driver_name` | string | Driver name override (primarily used in board manifests). |
#### Example: Standalone Driver `program` Block
```json5
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 The `use` Section
The `use` section serves a dual purpose:
1. **Component Capability Routing**: Passing standard CML capabilities (`service`, `protocol`, `directory`).
2. **Composite Parent Node Specification**: Defining the parent nodes that a composite driver binds to.
| Property | Type | Description |
| :--- | :--- | :--- |
| `service` | string | FIDL service name (e.g. `"fuchsia.hardware.gpio.Service"`). Emits `fuchsia.Service == "<service>";` in parent bind rules. |
| `protocol` | string | FIDL protocol name. |
| `banjo` | string | Banjo protocol identifier (e.g. `"fuchsia.gpio.BIND_PROTOCOL.DEVICE"`). Emits `fuchsia.BIND_PROTOCOL == <banjo>;` in bind rules and is excluded from CML. |
| `name` / `instance_name` | string | Name of the parent node in the composite device specification. |
| `primary` | boolean | Set to `true` on exactly one `use` entry to designate it as the primary parent node. |
| `availability` | string | `"required"` (default) or `"optional"`. Generates `optional parent "<name>"` in bind rules. |
| `transport` | string | Service transport: `"Zircon"`, `"Driver"`, or `"Banjo"`. |
| `generate_bind_rule` | boolean | Defaults to `true`. If set to `false`, the capability is consumed via CML without generating a composite parent bind node. |
| `bind` / `requirements` | object | Inline bind rules for this parent node (see [Hardware Bus Blocks](#4-hardware-bus-blocks)). |
#### Special Handling for Initialization Steps
Services representing platform init steps (such as `fuchsia.gpio.Init` and `fuchsia.pwm.Init`) are automatically detected:
* They generate the appropriate bind rule (e.g. `fuchsia.BIND_INIT_STEP == fuchsia.gpio.BIND_INIT_STEP.GPIO;`).
* They are automatically filtered out of runtime CML `use` entries because init steps are transient bind milestones rather than connectable runtime FIDL services.
---
### 3.4 The `capabilities` and `expose` Sections
#### Capabilities
Declares capabilities provided by the driver. In addition to standard CML capability types, DML supports **schema-driven metadata definitions**:
```json5
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" ]
}
}
}
]
```
When `dmlc compile-driver` is executed with `--h-output`, `--cc-output`, or `--rs-output`, it generates complete C++ and Rust parser code that parses and validates incoming metadata against the schema.
#### Expose
Standard DFv2 capability exposure:
```json5
expose: [
{
service: "fuchsia.hardware.buttons.Service",
from: "self"
}
]
```
---
### 3.5 Board Manifest Top-Level Sections
Board manifests (compiled with `dmlc compile-board`) configure the platform bus and topology:
#### `children`
Declares devices published to the platform bus:
```json5
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`
Routes capabilities between parent controllers and child drivers with hardware constraints:
```json5
offer: [
{
service: "fuchsia.hardware.gpio.Service",
name: "power",
from: "#gpio-controller-ff634400",
to: "#gpio-buttons",
constraints: {
pin: 92,
name: "power"
}
}
]
```
##### Userspace Interrupt Controllers
When a platform device consumes an interrupt managed by a userspace interrupt controller driver (rather than the kernel interrupt controller directly), two `offer` entries are required:
1. Route `fuchsia.hardware.interrupt.ControllerRegistryService` from `"parent"` to the interrupt controller node (`name: "pdev"`). `dmlc` automatically allocates a unique platform bus `interrupt_controller_id` for that controller node.
2. Reference the controller node in the consumer's `fuchsia.hardware.platform.device.Service` `interrupts` constraint via `controller: "#<controller-node>"`.
```json5
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`
Aggregates child device constraints into unified FIDL metadata:
```json5
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. Hardware Bus Blocks
DML provides structured blocks for major hardware interconnects and discovery protocols.
### 4.1 Platform Bus & Devicetree
Platform devices match on Vendor ID (VID), Product ID (PID), Device ID (DID), or Devicetree `compatible` strings:
```json5
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"
}
```
Generated bind rules:
```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 Bus
PCI devices match on PCI vendor, device, class, subclass, interface, revision, or topology:
Note: Legacy flat PCI fields (`pci_class`, `pci_subclass`, `pci_interface`) are rejected by `dmlc`. Always use the structured `pci: { ... }` block.
```json5
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"
}
}
```
Generated bind rules:
```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 Bus
USB interfaces and devices match on USB vendor, product, class, subclass, protocol, and interface number:
```json5
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"
}
}
```
Generated bind rules:
```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 Bus
ACPI devices match on Hardware ID (`hid`), Compatible ID (`first_cid`), or ACPI Bus Type:
```json5
bind: {
acpi: {
hid: "PNP0C0A",
first_cid: "PNP0C0B",
bus_type: "fuchsia.acpi.BIND_ACPI_BUS_TYPE.PCI"
}
}
```
Generated bind rules:
```bind
fuchsia.acpi.HID == "PNP0C0A";
fuchsia.acpi.FIRST_CID == "PNP0C0B";
fuchsia.BIND_ACPI_BUS_TYPE == fuchsia.acpi.BIND_ACPI_BUS_TYPE.PCI;
```
---
## 5. Composite Binding Rules & Parent Matching
Composite drivers require binding to multiple parent devices (e.g. a platform device, plus GPIO pins, plus I2C buses).
### 5.1 Primary Parent Node
Designate the primary parent with `primary: true` in its `use` entry:
```json5
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"
}
]
```
Generated bind rules:
```bind
composite sample_composite;
using fuchsia;
primary parent "pdev" {
fuchsia.COMPATIBLE == "sample,buttons";
}
optional parent "mic-mute" {
fuchsia.Service == "fuchsia.hardware.gpio.Service";
}
```
Note: If a driver is a composite driver, specifying bind rules under `program.bind` is forbidden. Place all bind rules in the corresponding `use` entries instead.
### 5.2 Parent Node Matching (`match_name: true`)
When a driver connects to multiple parents of the same service type (e.g. multiple GPIO pins or ADCs), specify `match_name: true` inside the parent's `bind` block. `dmlc` will automatically emit a rule matching the parent's topological node name (`fuchsia.NAME`):
```json5
use: [
{
service: "fuchsia.hardware.gpio.Service",
name: "volume-up",
bind: {
match_name: true
}
}
]
```
Generated bind rules:
```bind
parent "volume-up" {
fuchsia.Service == "fuchsia.hardware.gpio.Service";
fuchsia.NAME == "volume-up";
}
```
### 5.3 Parent Grouping
If multiple `use` entries reference the same parent `name` (for example, one for a Banjo interface and another for a FIDL service or init step), `dmlc` automatically groups them into a single parent specification:
```json5
use: [
{
service: "fuchsia.gpio.Init",
name: "gpio-init"
},
{
service: "fuchsia.hardware.gpio.Service",
name: "gpio-init",
availability: "optional"
}
]
```
Generated bind rules:
```bind
parent "gpio-init" {
fuchsia.BIND_INIT_STEP == fuchsia.gpio.BIND_INIT_STEP.GPIO;
fuchsia.Service == "fuchsia.hardware.gpio.Service";
}
```
---
## 6. Conditional Branching and Array Acceptance
### 6.1 Array Acceptance (`accept`)
To match against any one of multiple acceptable values, pass an array instead of a single scalar. `dmlc` will generate an `accept <property> { ... }` block in bind rules.
```json5
bind: {
pci: {
vid: "fuchsia.pci.BIND_PCI_VID.INTEL",
did: [ "0x1234", "0x5678" ]
}
}
```
Generated bind rules:
```bind
fuchsia.BIND_PCI_VID == fuchsia.pci.BIND_PCI_VID.INTEL;
accept fuchsia.BIND_PCI_DID {
0x1234,
0x5678,
}
```
Works for `compat`, `vid`, `pid`, `did`, PCI properties, USB properties, ACPI `hid`, and custom rules.
### 6.2 Conditional Branching (`one_of`)
When matching logic requires branching across different buses or device generations, use `one_of`. `dmlc` inspects each branch's trigger conditions and compiles them into `if ... else if ... else ...` statements.
```json5
bind: {
one_of: [
{
pci: {
vid: "fuchsia.pci.BIND_PCI_VID.INTEL",
did: "0x1234"
}
},
{
pci: {
class: "0x02",
subclass: "0x00"
}
}
]
}
```
Generated bind rules:
```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;
}
```
Supported trigger properties in `one_of` include:
* `node_name` / `fuchsia.NAME`
* `acpi.hid` and `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`
* Custom `rules` keys (e.g. `fuchsia.BIND_AUTOBIND`)
### 6.3 Inequality Constraints
To require that a property does *not* equal a value, specify `{ "neq": <value> }` in `rules`:
```json5
bind: {
rules: {
"fuchsia.BIND_COMPOSITE": { neq: 1 }
}
}
```
Generated bind rule:
```bind
fuchsia.BIND_COMPOSITE != 1;
```
---
## 7. Real-World Before / After Examples
### Example 1: Standalone USB Mass Storage Driver
#### Before: Separate Files
`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;
```
#### After: Unified `meta/usb_mass_storage.dml`
```json5
{
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"
}
]
}
```
---
### Example 2: Composite Buttons Driver
#### Before: Separate Files
`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;
}
optional parent "volume-up" {
fuchsia.Service == "fuchsia.hardware.gpio.Service";
fuchsia.NAME == "volume-up";
}
```
#### After: Unified `meta/buttons.dml`
```json5
{
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"
}
]
}
```
Notice that:
* `pdev` is explicitly the primary parent.
* `fuchsia.gpio.Init` is converted into an init step bind rule and automatically filtered out of the generated CML.
* `volume-up` with `match_name: true` automatically matches `fuchsia.NAME == "volume-up"`.
---
[dfv2]: /docs/development/drivers/dfv2-overview.md