Skip to main content

dml_config/
parser.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::{AggregateEntry, BoardConfig, Device, ResourceEntry};
6use anyhow::{Context, anyhow, bail};
7use fdf_fidl::DriverChannel;
8use fidl_fuchsia_driver_metadata as fdr;
9use fidl_next_fuchsia_driver_framework as fdf_framework;
10use fidl_next_fuchsia_hardware_platform_bus as fpbus;
11use phf;
12use std::collections::{HashMap, HashSet};
13use zx;
14
15pub const BIND_PROTOCOL_DEVICE: u32 = 85; // 0x55
16pub const BIND_PLATFORM_DEV_VID_GENERIC: u32 = 0;
17pub const BIND_PLATFORM_DEV_DID_DEVICETREE: u32 = 50; // 0x32
18
19pub fn property_int(val: u32) -> fdf_framework::NodePropertyValue {
20    fdf_framework::NodePropertyValue::IntValue(val)
21}
22
23pub fn property_string(val: &str) -> fdf_framework::NodePropertyValue {
24    fdf_framework::NodePropertyValue::StringValue(val.to_string())
25}
26
27pub fn property_bool(val: bool) -> fdf_framework::NodePropertyValue {
28    fdf_framework::NodePropertyValue::BoolValue(val)
29}
30
31fn map_interrupt_mode(mode: Option<&str>) -> fpbus::ZirconInterruptMode {
32    match mode {
33        Some("EdgeLow") => fpbus::ZirconInterruptMode::EdgeLow,
34        Some("EdgeHigh") => fpbus::ZirconInterruptMode::EdgeHigh,
35        Some("LevelLow") => fpbus::ZirconInterruptMode::LevelLow,
36        Some("LevelHigh") => fpbus::ZirconInterruptMode::LevelHigh,
37        Some("EdgeBoth") => fpbus::ZirconInterruptMode::EdgeBoth,
38        _ => fpbus::ZirconInterruptMode::Default,
39    }
40}
41
42pub type DriverSpecificMetadata =
43    phf::Map<&'static str, &'static [(&'static str, fn() -> anyhow::Result<Vec<u8>>)]>;
44
45pub fn make_accept_bind_rule(
46    key: &str,
47    value: fdf_framework::NodePropertyValue,
48) -> fdf_framework::BindRule2 {
49    fdf_framework::BindRule2 {
50        key: key.to_string(),
51        condition: fdf_framework::Condition::Accept,
52        values: vec![value],
53    }
54}
55
56pub fn make_property2(
57    key: &str,
58    value: fdf_framework::NodePropertyValue,
59) -> fdf_framework::NodeProperty2 {
60    fdf_framework::NodeProperty2 { key: key.to_string(), value }
61}
62
63pub fn add_rule_and_property(
64    bind_rules: &mut Vec<fdf_framework::BindRule2>,
65    properties: &mut Vec<fdf_framework::NodeProperty2>,
66    key: &str,
67    value: fdf_framework::NodePropertyValue,
68) {
69    bind_rules.push(make_accept_bind_rule(key, value.clone()));
70    properties.push(make_property2(key, value));
71}
72
73#[derive(Clone, Copy, Debug)]
74pub enum ValueSource {
75    ConstraintKey(&'static str),
76    ResourceName,
77    ResourceNode,
78    Template(&'static str),
79    Integer(u32),
80    ProviderId,
81}
82
83#[derive(Clone, Copy, Debug)]
84pub enum RuleValueType {
85    Integer,
86    String,
87    Bool,
88}
89
90#[derive(Clone, Copy, Debug)]
91pub enum Destination {
92    BindRules,
93    Properties,
94    Both,
95}
96
97#[derive(Clone, Copy, Debug)]
98pub struct PropertyRule {
99    pub bind_key: &'static str,
100    pub sources: &'static [ValueSource],
101    pub value_type: RuleValueType,
102    pub destination: Destination,
103    pub required: bool,
104}
105
106#[derive(Clone, Copy, Debug)]
107pub enum TransportType {
108    None,
109    Zircon,
110    Driver,
111}
112
113#[derive(Clone, Copy, Debug)]
114pub struct ServiceBindConfig {
115    pub transport: TransportType,
116    pub rules: &'static [PropertyRule],
117    pub parent_key_sources: &'static [ValueSource],
118}
119
120pub const DEFAULT_SERVICE_BIND_CONFIG: ServiceBindConfig = ServiceBindConfig {
121    transport: TransportType::Zircon,
122    rules: &[],
123    parent_key_sources: &[ValueSource::ResourceName],
124};
125
126pub struct DmlParserConfig {
127    pub service_configs: phf::Map<&'static str, ServiceBindConfig>,
128}
129
130fn resolve_template(
131    provider: &str,
132    provider_id: u32,
133    template: &str,
134    res: &ResourceEntry,
135    constraint: &fdr::Dictionary,
136) -> Option<String> {
137    let mut result = String::new();
138    let mut start = 0;
139    while let Some(open) = template[start..].find('{') {
140        let open_idx = start + open;
141        result.push_str(&template[start..open_idx]);
142        if let Some(close) = template[open_idx..].find('}') {
143            let close_idx = open_idx + close;
144            let key = &template[open_idx + 1..close_idx];
145            let value = match key {
146                "res.name" => res.name.clone()?,
147                "res.node" => res.node.clone()?,
148                "provider" => provider.to_string(),
149                "provider_id" => provider_id.to_string(),
150                k => {
151                    if let Some(s) = crate::get_string(constraint, k) {
152                        s
153                    } else if let Some(i) = crate::get_int64(constraint, k) {
154                        i.to_string()
155                    } else if let Some(b) = crate::get_bool(constraint, k) {
156                        b.to_string()
157                    } else {
158                        return None;
159                    }
160                }
161            };
162            result.push_str(&value);
163            start = close_idx + 1;
164        } else {
165            return None;
166        }
167    }
168    result.push_str(&template[start..]);
169    Some(result)
170}
171
172fn resolve_value(
173    provider: &str,
174    provider_id: u32,
175    source: &ValueSource,
176    res: &ResourceEntry,
177    constraint: &fdr::Dictionary,
178) -> Option<ResolvedValue> {
179    match source {
180        ValueSource::ConstraintKey(key) => {
181            if let Some(i) = crate::get_int64(constraint, key) {
182                Some(ResolvedValue::Integer(i as u32))
183            } else if let Some(s) = crate::get_string(constraint, key) {
184                Some(ResolvedValue::String(s))
185            } else if let Some(b) = crate::get_bool(constraint, key) {
186                Some(ResolvedValue::Bool(b))
187            } else {
188                None
189            }
190        }
191        ValueSource::ResourceName => res.name.clone().map(ResolvedValue::String),
192        ValueSource::ResourceNode => res.node.clone().map(ResolvedValue::String),
193        ValueSource::Template(template) => {
194            resolve_template(provider, provider_id, template, res, constraint)
195                .map(ResolvedValue::String)
196        }
197        ValueSource::Integer(i) => Some(ResolvedValue::Integer(*i)),
198        ValueSource::ProviderId => Some(ResolvedValue::Integer(provider_id)),
199    }
200}
201
202enum ResolvedValue {
203    Integer(u32),
204    String(String),
205    Bool(bool),
206}
207
208fn apply_rule(
209    provider: &str,
210    provider_id: u32,
211    rule: &PropertyRule,
212    res: &ResourceEntry,
213    constraint: &fdr::Dictionary,
214    bind_rules: &mut Vec<fdf_framework::BindRule2>,
215    properties: &mut Vec<fdf_framework::NodeProperty2>,
216) -> anyhow::Result<()> {
217    let resolved = rule
218        .sources
219        .iter()
220        .find_map(|source| resolve_value(provider, provider_id, source, res, constraint));
221
222    let val = match resolved {
223        Some(v) => v,
224        None => {
225            if rule.required {
226                bail!("Failed to resolve required property {}", rule.bind_key);
227            } else {
228                return Ok(());
229            }
230        }
231    };
232
233    let property_value = match (val, rule.value_type) {
234        (ResolvedValue::Integer(i), RuleValueType::Integer) => property_int(i),
235        (ResolvedValue::String(s), RuleValueType::String) => property_string(&s),
236        (ResolvedValue::Bool(b), RuleValueType::Bool) => property_bool(b),
237        (ResolvedValue::String(s), RuleValueType::Integer) => {
238            let parsed = if s.starts_with("0x") || s.starts_with("0X") {
239                u32::from_str_radix(&s[2..], 16)
240            } else {
241                s.parse::<u32>()
242            };
243            match parsed {
244                Ok(i) => property_int(i),
245                Err(e) => {
246                    bail!("Failed to parse string {} as integer: {}", s, e);
247                }
248            }
249        }
250        (ResolvedValue::String(s), RuleValueType::Bool) => match s.parse::<bool>() {
251            Ok(b) => property_bool(b),
252            Err(e) => {
253                bail!("Failed to parse string {} as bool: {}", s, e);
254            }
255        },
256        _ => {
257            bail!("Type mismatch in rule resolution");
258        }
259    };
260
261    match rule.destination {
262        Destination::BindRules => {
263            bind_rules.push(make_accept_bind_rule(rule.bind_key, property_value));
264        }
265        Destination::Properties => {
266            properties.push(make_property2(rule.bind_key, property_value));
267        }
268        Destination::Both => {
269            add_rule_and_property(bind_rules, properties, rule.bind_key, property_value);
270        }
271    }
272
273    Ok(())
274}
275
276pub fn generate_parent_spec_generic(
277    provider: &str,
278    provider_id: u32,
279    service_name: &str,
280    res: &ResourceEntry,
281    config: &DmlParserConfig,
282) -> anyhow::Result<Option<(fdf_framework::ParentSpec2, String)>> {
283    let mut bind_rules = Vec::new();
284    let mut properties = Vec::new();
285
286    let service_config =
287        config.service_configs.get(service_name).unwrap_or(&DEFAULT_SERVICE_BIND_CONFIG);
288
289    match service_config.transport {
290        TransportType::Zircon => {
291            let transport_value = format!("{}.ZirconTransport", service_name);
292            add_rule_and_property(
293                &mut bind_rules,
294                &mut properties,
295                service_name,
296                property_string(&transport_value),
297            );
298        }
299        TransportType::Driver => {
300            let transport_value = format!("{}.DriverTransport", service_name);
301            add_rule_and_property(
302                &mut bind_rules,
303                &mut properties,
304                service_name,
305                property_string(&transport_value),
306            );
307        }
308        TransportType::None => {}
309    }
310
311    let constraint =
312        res.constraint.as_ref().ok_or_else(|| anyhow!("constraint is missing in ResourceEntry"))?;
313
314    for rule in service_config.rules {
315        apply_rule(provider, provider_id, rule, res, constraint, &mut bind_rules, &mut properties)?;
316    }
317
318    let resolved_key = service_config.parent_key_sources.iter().find_map(|source| {
319        match resolve_value(provider, provider_id, source, res, constraint) {
320            Some(ResolvedValue::String(k)) => Some(k),
321            _ => None,
322        }
323    });
324
325    let key = match resolved_key {
326        Some(k) => k,
327        None => {
328            bail!(
329                "Failed to resolve parent key for service {}. If this service should be ignored, add it to the config.",
330                service_name
331            );
332        }
333    };
334
335    let parent = fdf_framework::ParentSpec2 { bind_rules, properties };
336    Ok(Some((parent, key)))
337}
338
339fn get_aggregate_id(agg: &AggregateEntry, devices: &[Device], fallback_id: u32) -> u32 {
340    let device = devices.iter().find(|d| d.name.as_deref() == agg.provider.as_deref());
341    let id = device.and_then(|d| d.id).unwrap_or(fallback_id);
342    log::info!(
343        "get_aggregate_id: provider={:?}, device_found={}, device_id={:?}, final_id={}, fallback={}",
344        agg.provider,
345        device.is_some(),
346        device.and_then(|d| d.id),
347        id,
348        fallback_id
349    );
350    id
351}
352
353pub async fn publish_dml_devices(
354    pbus: &fidl_next::Client<fpbus::PlatformBus, DriverChannel>,
355    composite_manager: &fidl_next::Client<fdf_framework::CompositeNodeManager, zx::Channel>,
356    config: &BoardConfig,
357    parser_config: &DmlParserConfig,
358    driver_metadata: Option<&DriverSpecificMetadata>,
359) -> anyhow::Result<()> {
360    let mut provider_metadata = HashMap::<String, Vec<fpbus::Metadata>>::new();
361
362    // 1. Generate driver specific metadata for devices in config
363    if let (Some(drv_meta), Some(devices)) = (driver_metadata, &config.devices) {
364        for dev in devices {
365            let name = dev.name.as_deref().unwrap_or("");
366            if let Some(generators) = drv_meta.get(name) {
367                for (metadata_id, gen_fn) in *generators {
368                    let data = gen_fn()
369                        .with_context(|| format!("Failed to generate metadata for {}", name))?;
370                    provider_metadata.entry(name.to_string()).or_default().push(fpbus::Metadata {
371                        id: Some(metadata_id.to_string()),
372                        data: Some(data),
373                        ..Default::default()
374                    });
375                }
376            }
377        }
378    }
379
380    let devices = config
381        .devices
382        .as_ref()
383        .ok_or_else(|| anyhow!("devices field is missing in BoardConfig"))?;
384    for (idx, dev) in devices.iter().enumerate() {
385        let instance_id = idx as u32 + 1;
386        let mut node = fpbus::Node {
387            name: dev.name.clone(),
388            vid: Some(0),
389            pid: Some(0),
390            did: Some(0),
391            instance_id: Some(instance_id),
392            driver_host: dev.url.clone(),
393            ..Default::default()
394        };
395
396        if let Some(compatible) = &dev.compatible {
397            node.properties = Some(vec![fdf_framework::NodeProperty2 {
398                key: "fuchsia.devicetree.FIRST_COMPATIBLE".to_string(),
399                value: fdf_framework::NodePropertyValue::StringValue(compatible.clone()),
400            }]);
401            node.did = Some(BIND_PLATFORM_DEV_DID_DEVICETREE);
402            node.vid = Some(BIND_PLATFORM_DEV_VID_GENERIC);
403        }
404
405        let mut mmio_list = Vec::new();
406        let mut irq_list = Vec::new();
407        let mut bti_list = Vec::new();
408        let mut smc_list = Vec::new();
409        let mut boot_metadata_list = Vec::new();
410
411        if let Some(pdev_dict) = crate::pdev_constraints(config, dev.name.as_deref().unwrap_or(""))
412        {
413            for mmio in crate::mmio_list(pdev_dict) {
414                mmio_list.push(fpbus::Mmio {
415                    base: Some(mmio.base),
416                    length: Some(mmio.length),
417                    name: mmio.name.clone(),
418                    ..Default::default()
419                });
420            }
421            for irq in crate::irq_list(pdev_dict) {
422                irq_list.push(fpbus::Irq {
423                    irq: Some(fpbus::IrqSpec::Irq(irq.number)),
424                    mode: Some(map_interrupt_mode(irq.mode.as_deref())),
425                    name: irq.name.clone(),
426                    wake_vector: irq.wake_vector,
427                    ..Default::default()
428                });
429            }
430            for bti in crate::bti_list(pdev_dict) {
431                bti_list.push(fpbus::Bti {
432                    iommu_id: Some(0),
433                    bti_id: Some(bti.id),
434                    ..Default::default()
435                });
436            }
437            for smc in crate::smc_list(pdev_dict) {
438                smc_list.push(fpbus::Smc {
439                    service_call_num_base: Some(smc.service_call_num_base),
440                    count: Some(smc.count),
441                    exclusive: Some(smc.exclusive),
442                    name: smc.name.clone(),
443                    ..Default::default()
444                });
445            }
446            for bm in crate::boot_metadata_list(pdev_dict) {
447                boot_metadata_list.push(fpbus::BootMetadata {
448                    zbi_type: Some(bm.zbi_type),
449                    zbi_extra: bm.zbi_extra.or(Some(0)),
450                    ..Default::default()
451                });
452            }
453        }
454
455        if !mmio_list.is_empty() {
456            node.mmio = Some(mmio_list);
457        }
458        if !irq_list.is_empty() {
459            node.irq = Some(irq_list);
460        }
461        if !bti_list.is_empty() {
462            node.bti = Some(bti_list);
463        }
464        if !smc_list.is_empty() {
465            node.smc = Some(smc_list);
466        }
467        if !boot_metadata_list.is_empty() {
468            node.boot_metadata = Some(boot_metadata_list);
469        }
470
471        let mut metadata_list = Vec::new();
472
473        // Handle static metadata from config
474        if let Some(metadata) = &dev.metadata {
475            for meta in metadata {
476                let data =
477                    meta.data.clone().ok_or_else(|| anyhow!("Static metadata missing data"))?;
478                metadata_list.push(fpbus::Metadata {
479                    id: meta.id.clone(),
480                    data: Some(data),
481                    ..Default::default()
482                });
483            }
484        }
485
486        let dev_name = dev.name.as_deref().unwrap_or("");
487        if let Some(meta) = provider_metadata.get(dev_name) {
488            metadata_list.extend(meta.clone());
489        }
490
491        if !metadata_list.is_empty() {
492            node.metadata = Some(metadata_list);
493        }
494
495        let mut resource_parents = Vec::new();
496        let mut generated_keys = HashSet::new();
497        if let Some(aggregates) = &config.aggregates {
498            for (agg_idx, agg) in aggregates.iter().enumerate() {
499                if let Some(resources) = &agg.resources {
500                    for res in resources {
501                        if res.node.as_deref() == dev.name.as_deref() {
502                            let parent_and_key = if agg.service.as_deref()
503                                == Some("fuchsia.hardware.platform.device.Service")
504                                && dev.compatible.is_some()
505                            {
506                                None
507                            } else {
508                                generate_parent_spec_generic(
509                                    agg.provider.as_deref().unwrap_or(""),
510                                    get_aggregate_id(
511                                        agg,
512                                        config.devices.as_deref().unwrap_or(&[]),
513                                        agg_idx as u32,
514                                    ),
515                                    agg.service.as_deref().unwrap_or(""),
516                                    res,
517                                    parser_config,
518                                )?
519                            };
520                            if let Some((parent, key)) = parent_and_key {
521                                if generated_keys.insert(key) {
522                                    resource_parents.push(parent);
523                                }
524                            }
525                        }
526                    }
527                }
528            }
529        }
530
531        let mut spec =
532            fdf_framework::CompositeNodeSpec { name: dev.name.clone(), ..Default::default() };
533
534        let mut parents2 = Vec::new();
535
536        // 1. Generate pdev parent if compatible is present
537        if let Some(compatible) = &dev.compatible {
538            let pdev_parent = fdf_framework::ParentSpec2 {
539                bind_rules: vec![
540                    make_accept_bind_rule(
541                        "fuchsia.BIND_PROTOCOL",
542                        property_int(BIND_PROTOCOL_DEVICE),
543                    ),
544                    make_accept_bind_rule(
545                        "fuchsia.BIND_PLATFORM_DEV_VID",
546                        property_int(BIND_PLATFORM_DEV_VID_GENERIC),
547                    ),
548                    make_accept_bind_rule(
549                        "fuchsia.BIND_PLATFORM_DEV_DID",
550                        property_int(BIND_PLATFORM_DEV_DID_DEVICETREE),
551                    ),
552                    make_accept_bind_rule(
553                        "fuchsia.BIND_PLATFORM_DEV_INSTANCE_ID",
554                        property_int(instance_id),
555                    ),
556                    make_accept_bind_rule(
557                        "fuchsia.devicetree.FIRST_COMPATIBLE",
558                        property_string(compatible),
559                    ),
560                ],
561                properties: vec![
562                    make_property2("fuchsia.BIND_PROTOCOL", property_int(BIND_PROTOCOL_DEVICE)),
563                    make_property2(
564                        "fuchsia.BIND_PLATFORM_DEV_VID",
565                        property_int(BIND_PLATFORM_DEV_VID_GENERIC),
566                    ),
567                    make_property2(
568                        "fuchsia.BIND_PLATFORM_DEV_DID",
569                        property_int(BIND_PLATFORM_DEV_DID_DEVICETREE),
570                    ),
571                    make_property2(
572                        "fuchsia.BIND_PLATFORM_DEV_INSTANCE_ID",
573                        property_int(instance_id),
574                    ),
575                    make_property2(
576                        "fuchsia.devicetree.FIRST_COMPATIBLE",
577                        property_string(compatible),
578                    ),
579                    make_property2(
580                        "fuchsia.hardware.platform.device.Service",
581                        property_string("fuchsia.hardware.platform.device.Service.ZirconTransport"),
582                    ),
583                ],
584            };
585            parents2.push(pdev_parent);
586        }
587
588        parents2.extend(resource_parents);
589
590        if parents2.is_empty() {
591            bail!(
592                "Device {} has no parents (no compatible string and no resource parents)",
593                dev_name
594            );
595        }
596
597        spec.parents2 = Some(parents2);
598
599        log::info!(
600            "DML-CONFIG: Adding composite spec: name={:?}, parents2={:?}",
601            spec.name,
602            spec.parents2
603        );
604        composite_manager
605            .add_spec_with(spec)
606            .await
607            .context("AddSpec request failed")?
608            .map_err(|e| anyhow!("AddSpec failed: {:?}", e))?;
609
610        if dev.compatible.is_some() {
611            log::info!("DML-CONFIG: Adding node: {}", dev_name);
612            pbus.node_add(node)
613                .await
614                .context("NodeAdd request failed")?
615                .context("NodeAdd failed")?;
616        }
617    }
618
619    Ok(())
620}