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                    ..Default::default()
427                });
428            }
429            for bti in crate::bti_list(pdev_dict) {
430                bti_list.push(fpbus::Bti {
431                    iommu_id: Some(0),
432                    bti_id: Some(bti.id),
433                    ..Default::default()
434                });
435            }
436            for smc in crate::smc_list(pdev_dict) {
437                smc_list.push(fpbus::Smc {
438                    service_call_num_base: Some(smc.service_call_num_base),
439                    count: Some(smc.count),
440                    exclusive: Some(smc.exclusive),
441                    name: smc.name.clone(),
442                    ..Default::default()
443                });
444            }
445            for bm in crate::boot_metadata_list(pdev_dict) {
446                boot_metadata_list.push(fpbus::BootMetadata {
447                    zbi_type: Some(bm.zbi_type),
448                    zbi_extra: bm.zbi_extra.or(Some(0)),
449                    ..Default::default()
450                });
451            }
452        }
453
454        if !mmio_list.is_empty() {
455            node.mmio = Some(mmio_list);
456        }
457        if !irq_list.is_empty() {
458            node.irq = Some(irq_list);
459        }
460        if !bti_list.is_empty() {
461            node.bti = Some(bti_list);
462        }
463        if !smc_list.is_empty() {
464            node.smc = Some(smc_list);
465        }
466        if !boot_metadata_list.is_empty() {
467            node.boot_metadata = Some(boot_metadata_list);
468        }
469
470        let mut metadata_list = Vec::new();
471
472        // Handle static metadata from config
473        if let Some(metadata) = &dev.metadata {
474            for meta in metadata {
475                let data =
476                    meta.data.clone().ok_or_else(|| anyhow!("Static metadata missing data"))?;
477                metadata_list.push(fpbus::Metadata {
478                    id: meta.id.clone(),
479                    data: Some(data),
480                    ..Default::default()
481                });
482            }
483        }
484
485        let dev_name = dev.name.as_deref().unwrap_or("");
486        if let Some(meta) = provider_metadata.get(dev_name) {
487            metadata_list.extend(meta.clone());
488        }
489
490        if !metadata_list.is_empty() {
491            node.metadata = Some(metadata_list);
492        }
493
494        let mut resource_parents = Vec::new();
495        let mut generated_keys = HashSet::new();
496        if let Some(aggregates) = &config.aggregates {
497            for (agg_idx, agg) in aggregates.iter().enumerate() {
498                if let Some(resources) = &agg.resources {
499                    for res in resources {
500                        if res.node.as_deref() == dev.name.as_deref() {
501                            let parent_and_key = if agg.service.as_deref()
502                                == Some("fuchsia.hardware.platform.device.Service")
503                                && dev.compatible.is_some()
504                            {
505                                None
506                            } else {
507                                generate_parent_spec_generic(
508                                    agg.provider.as_deref().unwrap_or(""),
509                                    get_aggregate_id(
510                                        agg,
511                                        config.devices.as_deref().unwrap_or(&[]),
512                                        agg_idx as u32,
513                                    ),
514                                    agg.service.as_deref().unwrap_or(""),
515                                    res,
516                                    parser_config,
517                                )?
518                            };
519                            if let Some((parent, key)) = parent_and_key {
520                                if generated_keys.insert(key) {
521                                    resource_parents.push(parent);
522                                }
523                            }
524                        }
525                    }
526                }
527            }
528        }
529
530        let mut spec =
531            fdf_framework::CompositeNodeSpec { name: dev.name.clone(), ..Default::default() };
532
533        let mut parents2 = Vec::new();
534
535        // 1. Generate pdev parent if compatible is present
536        if let Some(compatible) = &dev.compatible {
537            let pdev_parent = fdf_framework::ParentSpec2 {
538                bind_rules: vec![
539                    make_accept_bind_rule(
540                        "fuchsia.BIND_PROTOCOL",
541                        property_int(BIND_PROTOCOL_DEVICE),
542                    ),
543                    make_accept_bind_rule(
544                        "fuchsia.BIND_PLATFORM_DEV_VID",
545                        property_int(BIND_PLATFORM_DEV_VID_GENERIC),
546                    ),
547                    make_accept_bind_rule(
548                        "fuchsia.BIND_PLATFORM_DEV_DID",
549                        property_int(BIND_PLATFORM_DEV_DID_DEVICETREE),
550                    ),
551                    make_accept_bind_rule(
552                        "fuchsia.BIND_PLATFORM_DEV_INSTANCE_ID",
553                        property_int(instance_id),
554                    ),
555                    make_accept_bind_rule(
556                        "fuchsia.devicetree.FIRST_COMPATIBLE",
557                        property_string(compatible),
558                    ),
559                ],
560                properties: vec![
561                    make_property2("fuchsia.BIND_PROTOCOL", property_int(BIND_PROTOCOL_DEVICE)),
562                    make_property2(
563                        "fuchsia.BIND_PLATFORM_DEV_VID",
564                        property_int(BIND_PLATFORM_DEV_VID_GENERIC),
565                    ),
566                    make_property2(
567                        "fuchsia.BIND_PLATFORM_DEV_DID",
568                        property_int(BIND_PLATFORM_DEV_DID_DEVICETREE),
569                    ),
570                    make_property2(
571                        "fuchsia.BIND_PLATFORM_DEV_INSTANCE_ID",
572                        property_int(instance_id),
573                    ),
574                    make_property2(
575                        "fuchsia.devicetree.FIRST_COMPATIBLE",
576                        property_string(compatible),
577                    ),
578                    make_property2(
579                        "fuchsia.hardware.platform.device.Service",
580                        property_string("fuchsia.hardware.platform.device.Service.ZirconTransport"),
581                    ),
582                ],
583            };
584            parents2.push(pdev_parent);
585        }
586
587        parents2.extend(resource_parents);
588
589        if parents2.is_empty() {
590            bail!(
591                "Device {} has no parents (no compatible string and no resource parents)",
592                dev_name
593            );
594        }
595
596        spec.parents2 = Some(parents2);
597
598        log::info!(
599            "DML-CONFIG: Adding composite spec: name={:?}, parents2={:?}",
600            spec.name,
601            spec.parents2
602        );
603        composite_manager
604            .add_spec_with(spec)
605            .await
606            .context("AddSpec request failed")?
607            .map_err(|e| anyhow!("AddSpec failed: {:?}", e))?;
608
609        if dev.compatible.is_some() {
610            log::info!("DML-CONFIG: Adding node: {}", dev_name);
611            pbus.node_add(node)
612                .await
613                .context("NodeAdd request failed")?
614                .context("NodeAdd failed")?;
615        }
616    }
617
618    Ok(())
619}