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, Iommu, IommuType, ResourceEntry};
6use anyhow::{Context, anyhow, bail};
7use fdf_fidl::DriverChannel;
8use fidl_fuchsia_driver_metadata_common as fdr;
9use fidl_next_fuchsia_driver_framework as fdf_framework;
10use fidl_next_fuchsia_hardware_platform_bus as fpbus;
11use fidl_next_fuchsia_hardware_power as fpower;
12use futures::future::try_join_all;
13use phf;
14use std::collections::{HashMap, HashSet};
15use zx;
16
17pub const BIND_PROTOCOL_DEVICE: u32 = 85; // 0x55
18pub const BIND_PLATFORM_DEV_VID_GENERIC: u32 = 0;
19pub const BIND_PLATFORM_DEV_DID_DEVICETREE: u32 = 50; // 0x32
20
21pub fn property_int(val: u32) -> fdf_framework::NodePropertyValue {
22    fdf_framework::NodePropertyValue::IntValue(val)
23}
24
25pub fn property_string(val: &str) -> fdf_framework::NodePropertyValue {
26    fdf_framework::NodePropertyValue::StringValue(val.to_string())
27}
28
29pub fn property_bool(val: bool) -> fdf_framework::NodePropertyValue {
30    fdf_framework::NodePropertyValue::BoolValue(val)
31}
32
33fn map_interrupt_mode(mode: Option<&str>) -> fpbus::ZirconInterruptMode {
34    match mode {
35        Some("EdgeLow") => fpbus::ZirconInterruptMode::EdgeLow,
36        Some("EdgeHigh") => fpbus::ZirconInterruptMode::EdgeHigh,
37        Some("LevelLow") => fpbus::ZirconInterruptMode::LevelLow,
38        Some("LevelHigh") => fpbus::ZirconInterruptMode::LevelHigh,
39        Some("EdgeBoth") => fpbus::ZirconInterruptMode::EdgeBoth,
40        _ => fpbus::ZirconInterruptMode::Default,
41    }
42}
43
44pub type DriverSpecificMetadata =
45    phf::Map<&'static str, &'static [(&'static str, fn() -> anyhow::Result<Vec<u8>>)]>;
46
47pub type DriverSpecificPowerConfigs =
48    phf::Map<&'static str, fn() -> anyhow::Result<Vec<fpower::PowerElementConfiguration>>>;
49
50pub fn make_accept_bind_rule(
51    key: &str,
52    value: fdf_framework::NodePropertyValue,
53) -> fdf_framework::BindRule2 {
54    fdf_framework::BindRule2 {
55        key: key.to_string(),
56        condition: fdf_framework::Condition::Accept,
57        values: vec![value],
58    }
59}
60
61pub fn make_property2(
62    key: &str,
63    value: fdf_framework::NodePropertyValue,
64) -> fdf_framework::NodeProperty2 {
65    fdf_framework::NodeProperty2 { key: key.to_string(), value }
66}
67
68pub fn add_rule_and_property(
69    bind_rules: &mut Vec<fdf_framework::BindRule2>,
70    properties: &mut Vec<fdf_framework::NodeProperty2>,
71    key: &str,
72    value: fdf_framework::NodePropertyValue,
73) {
74    bind_rules.push(make_accept_bind_rule(key, value.clone()));
75    properties.push(make_property2(key, value));
76}
77
78#[derive(Clone, Copy, Debug)]
79pub enum ValueSource {
80    ConstraintKey(&'static str),
81    ResourceName,
82    ResourceNode,
83    Integer(u32),
84    ProviderId,
85}
86
87#[derive(Clone, Copy, Debug)]
88pub enum RuleValueType {
89    Integer,
90    String,
91    Bool,
92}
93
94#[derive(Clone, Copy, Debug)]
95pub enum Destination {
96    BindRules,
97    Properties,
98    Both,
99}
100
101#[derive(Clone, Copy, Debug)]
102pub struct PropertyRule {
103    pub bind_key: &'static str,
104    pub sources: &'static [ValueSource],
105    pub value_type: RuleValueType,
106    pub destination: Destination,
107}
108
109#[derive(Clone, Copy, Debug)]
110pub enum TransportType {
111    None,
112    Zircon,
113    Driver,
114}
115
116#[derive(Clone, Copy, Debug)]
117pub struct ServiceBindConfig {
118    pub transport: TransportType,
119    pub rules: &'static [PropertyRule],
120    pub parent_key_sources: &'static [ValueSource],
121    pub bind_id: bool,
122}
123
124pub const DEFAULT_SERVICE_BIND_CONFIG: ServiceBindConfig = ServiceBindConfig {
125    transport: TransportType::Zircon,
126    rules: &[],
127    parent_key_sources: &[ValueSource::ResourceName],
128    bind_id: false,
129};
130
131pub const DEFAULT_ID_SERVICE_BIND_CONFIG: ServiceBindConfig =
132    ServiceBindConfig { bind_id: true, ..DEFAULT_SERVICE_BIND_CONFIG };
133
134pub struct DmlParserConfig {
135    pub service_configs: phf::Map<&'static str, ServiceBindConfig>,
136}
137
138pub static STANDARD_SERVICE_CONFIGS: phf::Map<&'static str, ServiceBindConfig> = phf::phf_map! {
139    "fuchsia.clock.Init" => ServiceBindConfig {
140        transport: TransportType::None,
141        rules: &[PropertyRule {
142            bind_key: "fuchsia.BIND_INIT_STEP",
143            sources: &[ValueSource::Integer(0x494B4C43)],
144            value_type: RuleValueType::Integer,
145            destination: Destination::Both,
146        }],
147        ..DEFAULT_SERVICE_BIND_CONFIG
148    },
149    "fuchsia.pwm.Init" => ServiceBindConfig {
150        transport: TransportType::None,
151        rules: &[PropertyRule {
152            bind_key: "fuchsia.BIND_INIT_STEP",
153            sources: &[ValueSource::Integer(0x004D5750)],
154            value_type: RuleValueType::Integer,
155            destination: Destination::Both,
156        }],
157        ..DEFAULT_SERVICE_BIND_CONFIG
158    },
159    "fuchsia.gpio.Init" => ServiceBindConfig {
160        transport: TransportType::None,
161        rules: &[PropertyRule {
162            bind_key: "fuchsia.BIND_INIT_STEP",
163            sources: &[ValueSource::Integer(0x4F495047)],
164            value_type: RuleValueType::Integer,
165            destination: Destination::Both,
166        }],
167        ..DEFAULT_SERVICE_BIND_CONFIG
168    },
169    "fuchsia.hardware.gpu.mali.Service" => ServiceBindConfig {
170        transport: TransportType::Driver,
171        ..DEFAULT_SERVICE_BIND_CONFIG
172    },
173    "fuchsia.hardware.platform.device.Service" => ServiceBindConfig {
174        rules: &[
175            PropertyRule {
176                bind_key: "fuchsia.BIND_PROTOCOL",
177                sources: &[ValueSource::Integer(BIND_PROTOCOL_DEVICE)],
178                value_type: RuleValueType::Integer,
179                destination: Destination::Both,
180            },
181            PropertyRule {
182                bind_key: "fuchsia.BIND_PLATFORM_DEV_VID",
183                sources: &[ValueSource::ConstraintKey("vid")],
184                value_type: RuleValueType::Integer,
185                destination: Destination::Both,
186            },
187            PropertyRule {
188                bind_key: "fuchsia.BIND_PLATFORM_DEV_DID",
189                sources: &[ValueSource::ConstraintKey("did")],
190                value_type: RuleValueType::Integer,
191                destination: Destination::Both,
192            },
193        ],
194        ..DEFAULT_SERVICE_BIND_CONFIG
195    },
196    "fuchsia.hardware.platform.bus.Service" => ServiceBindConfig {
197        transport: TransportType::Driver,
198        rules: &[
199            PropertyRule {
200                bind_key: "fuchsia.BIND_PLATFORM_DEV_VID",
201                sources: &[ValueSource::ConstraintKey("vid")],
202                value_type: RuleValueType::Integer,
203                destination: Destination::Both,
204            },
205            PropertyRule {
206                bind_key: "fuchsia.BIND_PLATFORM_DEV_PID",
207                sources: &[ValueSource::ConstraintKey("pid")],
208                value_type: RuleValueType::Integer,
209                destination: Destination::Both,
210            },
211            PropertyRule {
212                bind_key: "fuchsia.BIND_PLATFORM_DEV_DID",
213                sources: &[ValueSource::ConstraintKey("did")],
214                value_type: RuleValueType::Integer,
215                destination: Destination::Both,
216            },
217        ],
218        ..DEFAULT_SERVICE_BIND_CONFIG
219    },
220    "fuchsia.hardware.pci.Service" => ServiceBindConfig {
221        rules: &[
222            PropertyRule {
223                bind_key: "fuchsia.BIND_PCI_VID",
224                sources: &[ValueSource::ConstraintKey("vid")],
225                value_type: RuleValueType::Integer,
226                destination: Destination::Both,
227            },
228            PropertyRule {
229                bind_key: "fuchsia.BIND_PCI_DID",
230                sources: &[ValueSource::ConstraintKey("did")],
231                value_type: RuleValueType::Integer,
232                destination: Destination::Both,
233            },
234            PropertyRule {
235                bind_key: "fuchsia.BIND_PCI_TOPO",
236                sources: &[ValueSource::ConstraintKey("topo")],
237                value_type: RuleValueType::Integer,
238                destination: Destination::BindRules,
239            },
240        ],
241        ..DEFAULT_SERVICE_BIND_CONFIG
242    },
243};
244
245pub const DEFAULT_DML_PARSER_CONFIG: DmlParserConfig =
246    DmlParserConfig { service_configs: phf::phf_map! {} };
247
248impl Default for DmlParserConfig {
249    fn default() -> Self {
250        DEFAULT_DML_PARSER_CONFIG
251    }
252}
253
254fn resolve_value(
255    _provider: &str,
256    provider_id: u32,
257    source: &ValueSource,
258    res: &ResourceEntry,
259    constraint: &fdr::Dictionary,
260) -> Option<ResolvedValue> {
261    match source {
262        ValueSource::ConstraintKey(key) => {
263            if let Some(i) = crate::get_int64(constraint, key) {
264                Some(ResolvedValue::Integer(i as u32))
265            } else if let Some(s) = crate::get_string(constraint, key) {
266                Some(ResolvedValue::String(s))
267            } else if let Some(b) = crate::get_bool(constraint, key) {
268                Some(ResolvedValue::Bool(b))
269            } else {
270                None
271            }
272        }
273        ValueSource::ResourceName => res.name.clone().map(ResolvedValue::String),
274        ValueSource::ResourceNode => res.node.clone().map(ResolvedValue::String),
275        ValueSource::Integer(i) => Some(ResolvedValue::Integer(*i)),
276        ValueSource::ProviderId => Some(ResolvedValue::Integer(provider_id)),
277    }
278}
279
280enum ResolvedValue {
281    Integer(u32),
282    String(String),
283    Bool(bool),
284}
285
286fn apply_rule(
287    provider: &str,
288    provider_id: u32,
289    rule: &PropertyRule,
290    res: &ResourceEntry,
291    constraint: &fdr::Dictionary,
292    bind_rules: &mut Vec<fdf_framework::BindRule2>,
293    properties: &mut Vec<fdf_framework::NodeProperty2>,
294) -> anyhow::Result<()> {
295    let resolved = rule
296        .sources
297        .iter()
298        .find_map(|source| resolve_value(provider, provider_id, source, res, constraint));
299
300    let Some(val) = resolved else {
301        return Ok(());
302    };
303
304    let property_value = match (val, rule.value_type) {
305        (ResolvedValue::Integer(i), RuleValueType::Integer) => property_int(i),
306        (ResolvedValue::String(s), RuleValueType::String) => property_string(&s),
307        (ResolvedValue::Bool(b), RuleValueType::Bool) => property_bool(b),
308        (ResolvedValue::String(s), RuleValueType::Integer) => {
309            let parsed = if s.starts_with("0x") || s.starts_with("0X") {
310                u32::from_str_radix(&s[2..], 16)
311            } else {
312                s.parse::<u32>()
313            };
314            match parsed {
315                Ok(i) => property_int(i),
316                Err(e) => {
317                    bail!("Failed to parse string {} as integer: {}", s, e);
318                }
319            }
320        }
321        (ResolvedValue::String(s), RuleValueType::Bool) => match s.parse::<bool>() {
322            Ok(b) => property_bool(b),
323            Err(e) => {
324                bail!("Failed to parse string {} as bool: {}", s, e);
325            }
326        },
327        _ => {
328            bail!("Type mismatch in rule resolution");
329        }
330    };
331
332    match rule.destination {
333        Destination::BindRules => {
334            bind_rules.push(make_accept_bind_rule(rule.bind_key, property_value));
335        }
336        Destination::Properties => {
337            properties.push(make_property2(rule.bind_key, property_value));
338        }
339        Destination::Both => {
340            add_rule_and_property(bind_rules, properties, rule.bind_key, property_value);
341        }
342    }
343
344    Ok(())
345}
346
347pub fn generate_parent_spec_generic(
348    provider: &str,
349    provider_id: u32,
350    service_name: &str,
351    res: &ResourceEntry,
352    config: &DmlParserConfig,
353) -> anyhow::Result<Option<(fdf_framework::ParentSpec2, String)>> {
354    let mut bind_rules = Vec::new();
355    let mut properties = Vec::new();
356
357    let service_config = config
358        .service_configs
359        .get(service_name)
360        .or_else(|| STANDARD_SERVICE_CONFIGS.get(service_name))
361        .unwrap_or(&DEFAULT_ID_SERVICE_BIND_CONFIG);
362
363    match service_config.transport {
364        TransportType::Zircon => {
365            add_rule_and_property(
366                &mut bind_rules,
367                &mut properties,
368                "fuchsia.Service",
369                property_string(service_name),
370            );
371            properties.push(make_property2(
372                service_name,
373                property_string(&format!("{service_name}.ZirconTransport")),
374            ));
375        }
376        TransportType::Driver => {
377            add_rule_and_property(
378                &mut bind_rules,
379                &mut properties,
380                "fuchsia.Service",
381                property_string(service_name),
382            );
383            properties.push(make_property2(
384                service_name,
385                property_string(&format!("{service_name}.DriverTransport")),
386            ));
387        }
388        TransportType::None => {}
389    }
390
391    let constraint =
392        res.constraint.as_ref().ok_or_else(|| anyhow!("constraint is missing in ResourceEntry"))?;
393
394    for rule in service_config.rules {
395        apply_rule(provider, provider_id, rule, res, constraint, &mut bind_rules, &mut properties)?;
396    }
397
398    if !properties.iter().any(|p| p.key == "fuchsia.NAME") {
399        let name_opt = res.name.clone().or_else(|| crate::get_string(constraint, "name"));
400        if let Some(name) = name_opt {
401            properties.push(make_property2("fuchsia.NAME", property_string(&name)));
402        }
403    }
404
405    if service_config.bind_id
406        && !bind_rules.iter().any(|r| r.key == "fuchsia.ID")
407        && let Some(id) = crate::get_uint32(constraint, "id")
408    {
409        bind_rules.push(make_accept_bind_rule("fuchsia.ID", property_int(id)));
410    }
411
412    let resolved_key = service_config.parent_key_sources.iter().find_map(|source| {
413        match resolve_value(provider, provider_id, source, res, constraint) {
414            Some(ResolvedValue::String(k)) => Some(k),
415            _ => None,
416        }
417    });
418
419    let key = match resolved_key {
420        Some(k) => k,
421        None => {
422            bail!(
423                "Failed to resolve parent key for service {}. If this service should be ignored, add it to the config.",
424                service_name
425            );
426        }
427    };
428
429    let parent = fdf_framework::ParentSpec2 { bind_rules, properties };
430    Ok(Some((parent, key)))
431}
432
433fn get_aggregate_id(agg: &AggregateEntry, devices: &[Device], fallback_id: u32) -> u32 {
434    let device = devices.iter().find(|d| d.name.as_deref() == agg.provider.as_deref());
435    let id = device.and_then(|d| d.id).unwrap_or(fallback_id);
436    log::debug!(
437        "get_aggregate_id: provider={:?}, device_found={}, device_id={:?}, final_id={}, fallback={}",
438        agg.provider,
439        device.is_some(),
440        device.and_then(|d| d.id),
441        id,
442        fallback_id
443    );
444    id
445}
446
447pub async fn publish_dml_devices(
448    pbus: &fidl_next::Client<fpbus::PlatformBus, DriverChannel>,
449    composite_manager: &fidl_next::Client<fdf_framework::CompositeNodeManager, zx::Channel>,
450    config: &BoardConfig,
451    parser_config: &DmlParserConfig,
452    driver_metadata: Option<&DriverSpecificMetadata>,
453    driver_power_configs: Option<&DriverSpecificPowerConfigs>,
454    enabled_nodes: &[String],
455) -> anyhow::Result<()> {
456    let mut provider_metadata = HashMap::<String, Vec<fpbus::Metadata>>::new();
457
458    register_iommus(pbus, config.iommus.as_deref()).await.context("Failed to register IOMMUs")?;
459
460    let devices = config
461        .devices
462        .as_ref()
463        .ok_or_else(|| anyhow!("devices field is missing in BoardConfig"))?;
464
465    let disabled_devices: HashSet<&str> = devices
466        .iter()
467        .filter(|d| crate::is_device_disabled(d, enabled_nodes))
468        .filter_map(|d| d.name.as_deref())
469        .collect();
470
471    // 1. Generate driver specific metadata for devices in config
472    if let Some(drv_meta) = driver_metadata {
473        for dev in devices {
474            let name = dev.name.as_deref().unwrap_or("");
475            if disabled_devices.contains(name) {
476                continue;
477            }
478            if let Some(generators) = drv_meta.get(name) {
479                for (metadata_id, gen_fn) in *generators {
480                    let data = gen_fn()
481                        .with_context(|| format!("Failed to generate metadata for {}", name))?;
482                    provider_metadata.entry(name.to_string()).or_default().push(fpbus::Metadata {
483                        id: Some(metadata_id.to_string()),
484                        data: Some(data),
485                        ..Default::default()
486                    });
487                }
488            }
489        }
490    }
491
492    for (idx, dev) in devices.iter().enumerate() {
493        let dev_name = dev.name.as_deref().unwrap_or("");
494        if disabled_devices.contains(dev_name) {
495            continue;
496        }
497        if dev.disabled.unwrap_or(false) {
498            log::info!("Publishing disabled DML device '{}' due to runtime override", dev_name);
499        }
500        let instance_id = idx as u32 + 1;
501        let mut node = fpbus::Node {
502            name: dev.name.clone(),
503            vid: Some(0),
504            pid: Some(0),
505            did: Some(0),
506            instance_id: Some(instance_id),
507            driver_host: dev.driver_host.clone(),
508            ..Default::default()
509        };
510        node.interrupt_controller_id = dev.interrupt_controller_id;
511
512        if let Some(compatible) = &dev.compatible {
513            node.properties = Some(vec![fdf_framework::NodeProperty2 {
514                key: "fuchsia.COMPATIBLE".to_string(),
515                value: fdf_framework::NodePropertyValue::StringValue(compatible.clone()),
516            }]);
517            node.did = Some(BIND_PLATFORM_DEV_DID_DEVICETREE);
518            node.vid = Some(BIND_PLATFORM_DEV_VID_GENERIC);
519        }
520
521        let mut mmio_list = Vec::new();
522        let mut irq_list = Vec::new();
523        let mut bti_list = Vec::new();
524        let mut smc_list = Vec::new();
525        let mut boot_metadata_list = Vec::new();
526
527        if let Some(pdev_dict) = crate::pdev_constraints(config, dev.name.as_deref().unwrap_or(""))
528        {
529            for mmio in crate::mmio_list(pdev_dict) {
530                mmio_list.push(fpbus::Mmio {
531                    base: Some(mmio.base),
532                    length: Some(mmio.length),
533                    name: mmio.name,
534                    ..Default::default()
535                });
536            }
537            for irq in crate::irq_list(pdev_dict) {
538                let irq_spec = match irq.controller {
539                    Some(controller_id) => fpbus::IrqSpec::UserspaceIrq(fpbus::UserspaceIrq {
540                        irq: irq.number,
541                        controller_id,
542                    }),
543                    None => fpbus::IrqSpec::Irq(irq.number),
544                };
545                irq_list.push(fpbus::Irq {
546                    irq: Some(irq_spec),
547                    mode: Some(map_interrupt_mode(irq.mode.as_deref())),
548                    name: irq.name.clone(),
549                    wake_vector: irq.wake_vector,
550                    ..Default::default()
551                });
552            }
553            for bti in crate::bti_list(pdev_dict)? {
554                bti_list.push(fpbus::Bti {
555                    iommu_id: Some(bti.iommu_id),
556                    bti_id: Some(bti.id),
557                    name: bti.name,
558                    ..Default::default()
559                });
560            }
561            for smc in crate::smc_list(pdev_dict) {
562                smc_list.push(fpbus::Smc {
563                    service_call_num_base: Some(smc.service_call_num_base),
564                    count: Some(smc.count),
565                    exclusive: Some(smc.exclusive),
566                    name: smc.name,
567                    ..Default::default()
568                });
569            }
570            for bm in crate::boot_metadata_list(pdev_dict) {
571                boot_metadata_list.push(fpbus::BootMetadata {
572                    zbi_type: Some(bm.zbi_type),
573                    zbi_extra: bm.zbi_extra.or(Some(0)),
574                    ..Default::default()
575                });
576            }
577        }
578
579        if !mmio_list.is_empty() {
580            node.mmio = Some(mmio_list);
581        }
582        if !irq_list.is_empty() {
583            node.irq = Some(irq_list);
584        }
585        if !bti_list.is_empty() {
586            node.bti = Some(bti_list);
587        }
588        if !smc_list.is_empty() {
589            node.smc = Some(smc_list);
590        }
591        if !boot_metadata_list.is_empty() {
592            node.boot_metadata = Some(boot_metadata_list);
593        }
594
595        let mut metadata_list = Vec::new();
596
597        // Handle static metadata from config
598        if let Some(metadata) = &dev.metadata {
599            for meta in metadata {
600                let data =
601                    meta.data.clone().ok_or_else(|| anyhow!("Static metadata missing data"))?;
602                metadata_list.push(fpbus::Metadata {
603                    id: meta.id.clone(),
604                    data: Some(data),
605                    ..Default::default()
606                });
607            }
608        }
609
610        if let Some(meta) = provider_metadata.get(dev_name) {
611            metadata_list.extend(meta.clone());
612        }
613
614        if !metadata_list.is_empty() {
615            node.metadata = Some(metadata_list);
616        }
617
618        if let Some(gen_fn) = driver_power_configs.and_then(|configs| configs.get(dev_name)) {
619            let power_config = gen_fn()
620                .with_context(|| format!("Failed to generate power config for {}", dev_name))?;
621            if !power_config.is_empty() {
622                node.power_config = Some(power_config);
623            }
624        }
625
626        let mut resource_parents = Vec::new();
627        let mut generated_keys = HashSet::new();
628        if let Some(aggregates) = &config.aggregates {
629            for (agg_idx, agg) in aggregates.iter().enumerate() {
630                if agg.provider.as_deref().is_some_and(|p| disabled_devices.contains(p)) {
631                    continue;
632                }
633                if let Some(resources) = &agg.resources {
634                    for res in resources {
635                        if res.node.as_deref() == dev.name.as_deref() {
636                            let parent_and_key = if (agg.service.as_deref()
637                                == Some("fuchsia.hardware.platform.device.Service")
638                                || agg.service.as_deref()
639                                    == Some("fuchsia.hardware.interrupt.ControllerRegistryService"))
640                                && dev.compatible.is_some()
641                            {
642                                None
643                            } else {
644                                generate_parent_spec_generic(
645                                    agg.provider.as_deref().unwrap_or(""),
646                                    get_aggregate_id(
647                                        agg,
648                                        config.devices.as_deref().unwrap_or(&[]),
649                                        agg_idx as u32,
650                                    ),
651                                    agg.service.as_deref().unwrap_or(""),
652                                    res,
653                                    parser_config,
654                                )?
655                            };
656                            if let Some((parent, key)) = parent_and_key {
657                                if generated_keys.insert(key) {
658                                    if matches!(
659                                        agg.provider.as_deref(),
660                                        Some("parent") | Some("pdev")
661                                    ) {
662                                        resource_parents.insert(0, parent);
663                                    } else {
664                                        resource_parents.push(parent);
665                                    }
666                                }
667                            }
668                        }
669                    }
670                }
671            }
672        }
673
674        if resource_parents.is_empty() && dev.driver_host.is_some() {
675            node.driver_host = dev.driver_host.clone();
676        }
677
678        let mut spec = fdf_framework::CompositeNodeSpec {
679            name: dev.name.clone(),
680            driver_host: dev.driver_host.clone(),
681            ..Default::default()
682        };
683
684        let mut parents2 = Vec::new();
685
686        // 1. Generate pdev parent if compatible is present
687        if let Some(compatible) = &dev.compatible {
688            let pdev_parent = fdf_framework::ParentSpec2 {
689                bind_rules: vec![
690                    make_accept_bind_rule(
691                        "fuchsia.BIND_PROTOCOL",
692                        property_int(BIND_PROTOCOL_DEVICE),
693                    ),
694                    make_accept_bind_rule(
695                        "fuchsia.BIND_PLATFORM_DEV_VID",
696                        property_int(BIND_PLATFORM_DEV_VID_GENERIC),
697                    ),
698                    make_accept_bind_rule(
699                        "fuchsia.BIND_PLATFORM_DEV_DID",
700                        property_int(BIND_PLATFORM_DEV_DID_DEVICETREE),
701                    ),
702                    make_accept_bind_rule(
703                        "fuchsia.BIND_PLATFORM_DEV_INSTANCE_ID",
704                        property_int(instance_id),
705                    ),
706                    make_accept_bind_rule("fuchsia.COMPATIBLE", property_string(compatible)),
707                ],
708                properties: vec![
709                    // TODO(https://fxbug.dev/555962083): Restore `fuchsia.NAME = "pdev"` once all
710                    // composite drivers are migrated from `primary parent "devicetree"` to `"pdev"`
711                    // (`driver-index` rejects `ParentSpec2.properties` when `fuchsia.NAME` does not
712                    // equal the `.bind` primary parent symbol name).
713                    make_property2("fuchsia.BIND_PROTOCOL", property_int(BIND_PROTOCOL_DEVICE)),
714                    make_property2(
715                        "fuchsia.BIND_PLATFORM_DEV_VID",
716                        property_int(BIND_PLATFORM_DEV_VID_GENERIC),
717                    ),
718                    make_property2(
719                        "fuchsia.BIND_PLATFORM_DEV_DID",
720                        property_int(BIND_PLATFORM_DEV_DID_DEVICETREE),
721                    ),
722                    make_property2(
723                        "fuchsia.BIND_PLATFORM_DEV_INSTANCE_ID",
724                        property_int(instance_id),
725                    ),
726                    make_property2("fuchsia.COMPATIBLE", property_string(compatible)),
727                    make_property2(
728                        "fuchsia.devicetree.FIRST_COMPATIBLE",
729                        property_string(compatible),
730                    ),
731                    make_property2(
732                        "fuchsia.Service",
733                        property_string("fuchsia.hardware.platform.device.Service"),
734                    ),
735                ],
736            };
737            parents2.push(pdev_parent);
738        }
739
740        parents2.extend(resource_parents);
741
742        if !parents2.is_empty() {
743            spec.parents2 = Some(parents2);
744
745            log::debug!(
746                "DML-CONFIG: Adding composite spec: name={:?}, parents2={:?}",
747                spec.name,
748                spec.parents2
749            );
750            composite_manager
751                .add_spec_with(spec)
752                .await
753                .context("AddSpec request failed")?
754                .map_err(|e| anyhow!("AddSpec failed: {e:?}"))?;
755        }
756
757        if dev.compatible.is_some() {
758            log::debug!("DML-CONFIG: Adding node: {}", dev_name);
759            pbus.node_add(node)
760                .await
761                .context("NodeAdd request failed")?
762                .context("NodeAdd failed")?;
763        }
764    }
765
766    Ok(())
767}
768
769/// Registers `iommus` with the `pbus`.
770async fn register_iommus(
771    pbus: &fidl_next::Client<fpbus::PlatformBus, DriverChannel>,
772    iommus: Option<&[Iommu]>,
773) -> anyhow::Result<()> {
774    let Some(iommus) = iommus else {
775        return Ok(());
776    };
777
778    let futures = iommus
779        .iter()
780        .map(|iommu| {
781            let iommu_name = iommu.name.as_deref().unwrap_or("unnamed");
782            let iommu_id = iommu.id.with_context(|| format!("IOMMU {iommu_name:?} missing id"))?;
783            let fpbus_iommu = match &iommu.iommu_type {
784                Some(IommuType::ArmSmmu(arm_smmu)) => {
785                    fpbus::Iommu::ArmSmmu(fpbus::ArmSmmu { base_address: arm_smmu.base_address })
786                }
787                Some(IommuType::StubIommu(_)) | None => fpbus::Iommu::StubIommu(()),
788                _ => {
789                    bail!("Unsupported IOMMU type for IOMMU {iommu_name:?}");
790                }
791            };
792            let future = async move {
793                log::info!("Registering IOMMU '{iommu_name}' (id: {iommu_id})");
794                pbus.register_iommu(iommu_id, &fpbus_iommu)
795                    .await
796                    .with_context(|| {
797                        format!(
798                            "Failed to send RegisterIommu FIDL request for IOMMU {iommu_name:?}"
799                        )
800                    })?
801                    .with_context(|| format!("Failed to register IOMMU {iommu_name:?}"))?;
802                Ok::<(), anyhow::Error>(())
803            };
804            Ok(future)
805        })
806        .collect::<Result<Vec<_>, _>>()?;
807    try_join_all(futures).await?;
808    Ok(())
809}
810
811#[cfg(test)]
812mod tests {
813    use super::*;
814
815    #[test]
816    fn test_pci_service_parent_spec() {
817        let res = ResourceEntry {
818            name: Some("pci".to_string()),
819            node: Some("mali-gpu".to_string()),
820            constraint: Some(fdr::Dictionary {
821                entries: Some(vec![
822                    fdr::DictionaryEntry {
823                        key: "vid".to_string(),
824                        value: fdr::DictionaryValue::Int64(0x13b5),
825                    },
826                    fdr::DictionaryEntry {
827                        key: "did".to_string(),
828                        value: fdr::DictionaryValue::Int64(0x7212),
829                    },
830                    fdr::DictionaryEntry {
831                        key: "topo".to_string(),
832                        value: fdr::DictionaryValue::Int64(256),
833                    },
834                ]),
835                ..Default::default()
836            }),
837            ..Default::default()
838        };
839
840        let (parent, key) = generate_parent_spec_generic(
841            "pcie-c500000",
842            0,
843            "fuchsia.hardware.pci.Service",
844            &res,
845            &DEFAULT_DML_PARSER_CONFIG,
846        )
847        .expect("should succeed")
848        .expect("should return parent spec");
849
850        assert_eq!(key, "pci");
851        assert!(parent.bind_rules.iter().any(|r| r.key == "fuchsia.BIND_PCI_VID"
852            && r.values == vec![fdf_framework::NodePropertyValue::IntValue(0x13b5)]));
853        assert!(parent.bind_rules.iter().any(|r| r.key == "fuchsia.BIND_PCI_DID"
854            && r.values == vec![fdf_framework::NodePropertyValue::IntValue(0x7212)]));
855        assert!(parent.bind_rules.iter().any(|r| r.key == "fuchsia.BIND_PCI_TOPO"
856            && r.values == vec![fdf_framework::NodePropertyValue::IntValue(256)]));
857        assert!(parent.properties.iter().any(|p| p.key == "fuchsia.BIND_PCI_VID"
858            && p.value == fdf_framework::NodePropertyValue::IntValue(0x13b5)));
859        assert!(parent.properties.iter().any(|p| p.key == "fuchsia.BIND_PCI_DID"
860            && p.value == fdf_framework::NodePropertyValue::IntValue(0x7212)));
861        assert!(!parent.properties.iter().any(|p| p.key == "fuchsia.BIND_PCI_TOPO"));
862    }
863}