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 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};
221
222pub const DEFAULT_DML_PARSER_CONFIG: DmlParserConfig =
223    DmlParserConfig { service_configs: phf::phf_map! {} };
224
225impl Default for DmlParserConfig {
226    fn default() -> Self {
227        DEFAULT_DML_PARSER_CONFIG
228    }
229}
230
231fn resolve_value(
232    _provider: &str,
233    provider_id: u32,
234    source: &ValueSource,
235    res: &ResourceEntry,
236    constraint: &fdr::Dictionary,
237) -> Option<ResolvedValue> {
238    match source {
239        ValueSource::ConstraintKey(key) => {
240            if let Some(i) = crate::get_int64(constraint, key) {
241                Some(ResolvedValue::Integer(i as u32))
242            } else if let Some(s) = crate::get_string(constraint, key) {
243                Some(ResolvedValue::String(s))
244            } else if let Some(b) = crate::get_bool(constraint, key) {
245                Some(ResolvedValue::Bool(b))
246            } else {
247                None
248            }
249        }
250        ValueSource::ResourceName => res.name.clone().map(ResolvedValue::String),
251        ValueSource::ResourceNode => res.node.clone().map(ResolvedValue::String),
252        ValueSource::Integer(i) => Some(ResolvedValue::Integer(*i)),
253        ValueSource::ProviderId => Some(ResolvedValue::Integer(provider_id)),
254    }
255}
256
257enum ResolvedValue {
258    Integer(u32),
259    String(String),
260    Bool(bool),
261}
262
263fn apply_rule(
264    provider: &str,
265    provider_id: u32,
266    rule: &PropertyRule,
267    res: &ResourceEntry,
268    constraint: &fdr::Dictionary,
269    bind_rules: &mut Vec<fdf_framework::BindRule2>,
270    properties: &mut Vec<fdf_framework::NodeProperty2>,
271) -> anyhow::Result<()> {
272    let resolved = rule
273        .sources
274        .iter()
275        .find_map(|source| resolve_value(provider, provider_id, source, res, constraint));
276
277    let Some(val) = resolved else {
278        return Ok(());
279    };
280
281    let property_value = match (val, rule.value_type) {
282        (ResolvedValue::Integer(i), RuleValueType::Integer) => property_int(i),
283        (ResolvedValue::String(s), RuleValueType::String) => property_string(&s),
284        (ResolvedValue::Bool(b), RuleValueType::Bool) => property_bool(b),
285        (ResolvedValue::String(s), RuleValueType::Integer) => {
286            let parsed = if s.starts_with("0x") || s.starts_with("0X") {
287                u32::from_str_radix(&s[2..], 16)
288            } else {
289                s.parse::<u32>()
290            };
291            match parsed {
292                Ok(i) => property_int(i),
293                Err(e) => {
294                    bail!("Failed to parse string {} as integer: {}", s, e);
295                }
296            }
297        }
298        (ResolvedValue::String(s), RuleValueType::Bool) => match s.parse::<bool>() {
299            Ok(b) => property_bool(b),
300            Err(e) => {
301                bail!("Failed to parse string {} as bool: {}", s, e);
302            }
303        },
304        _ => {
305            bail!("Type mismatch in rule resolution");
306        }
307    };
308
309    match rule.destination {
310        Destination::BindRules => {
311            bind_rules.push(make_accept_bind_rule(rule.bind_key, property_value));
312        }
313        Destination::Properties => {
314            properties.push(make_property2(rule.bind_key, property_value));
315        }
316        Destination::Both => {
317            add_rule_and_property(bind_rules, properties, rule.bind_key, property_value);
318        }
319    }
320
321    Ok(())
322}
323
324pub fn generate_parent_spec_generic(
325    provider: &str,
326    provider_id: u32,
327    service_name: &str,
328    res: &ResourceEntry,
329    config: &DmlParserConfig,
330) -> anyhow::Result<Option<(fdf_framework::ParentSpec2, String)>> {
331    let mut bind_rules = Vec::new();
332    let mut properties = Vec::new();
333
334    let service_config = config
335        .service_configs
336        .get(service_name)
337        .or_else(|| STANDARD_SERVICE_CONFIGS.get(service_name))
338        .unwrap_or(&DEFAULT_ID_SERVICE_BIND_CONFIG);
339
340    match service_config.transport {
341        TransportType::Zircon => {
342            add_rule_and_property(
343                &mut bind_rules,
344                &mut properties,
345                "fuchsia.Service",
346                property_string(service_name),
347            );
348            properties.push(make_property2(
349                service_name,
350                property_string(&format!("{service_name}.ZirconTransport")),
351            ));
352        }
353        TransportType::Driver => {
354            add_rule_and_property(
355                &mut bind_rules,
356                &mut properties,
357                "fuchsia.Service",
358                property_string(service_name),
359            );
360            properties.push(make_property2(
361                service_name,
362                property_string(&format!("{service_name}.DriverTransport")),
363            ));
364        }
365        TransportType::None => {}
366    }
367
368    let constraint =
369        res.constraint.as_ref().ok_or_else(|| anyhow!("constraint is missing in ResourceEntry"))?;
370
371    for rule in service_config.rules {
372        apply_rule(provider, provider_id, rule, res, constraint, &mut bind_rules, &mut properties)?;
373    }
374
375    if !properties.iter().any(|p| p.key == "fuchsia.NAME") {
376        let name_opt = res.name.clone().or_else(|| crate::get_string(constraint, "name"));
377        if let Some(name) = name_opt {
378            properties.push(make_property2("fuchsia.NAME", property_string(&name)));
379        }
380    }
381
382    if service_config.bind_id
383        && !bind_rules.iter().any(|r| r.key == "fuchsia.ID")
384        && let Some(id) = crate::get_uint32(constraint, "id")
385    {
386        bind_rules.push(make_accept_bind_rule("fuchsia.ID", property_int(id)));
387    }
388
389    let resolved_key = service_config.parent_key_sources.iter().find_map(|source| {
390        match resolve_value(provider, provider_id, source, res, constraint) {
391            Some(ResolvedValue::String(k)) => Some(k),
392            _ => None,
393        }
394    });
395
396    let key = match resolved_key {
397        Some(k) => k,
398        None => {
399            bail!(
400                "Failed to resolve parent key for service {}. If this service should be ignored, add it to the config.",
401                service_name
402            );
403        }
404    };
405
406    let parent = fdf_framework::ParentSpec2 { bind_rules, properties };
407    Ok(Some((parent, key)))
408}
409
410fn get_aggregate_id(agg: &AggregateEntry, devices: &[Device], fallback_id: u32) -> u32 {
411    let device = devices.iter().find(|d| d.name.as_deref() == agg.provider.as_deref());
412    let id = device.and_then(|d| d.id).unwrap_or(fallback_id);
413    log::debug!(
414        "get_aggregate_id: provider={:?}, device_found={}, device_id={:?}, final_id={}, fallback={}",
415        agg.provider,
416        device.is_some(),
417        device.and_then(|d| d.id),
418        id,
419        fallback_id
420    );
421    id
422}
423
424pub async fn publish_dml_devices(
425    pbus: &fidl_next::Client<fpbus::PlatformBus, DriverChannel>,
426    composite_manager: &fidl_next::Client<fdf_framework::CompositeNodeManager, zx::Channel>,
427    config: &BoardConfig,
428    parser_config: &DmlParserConfig,
429    driver_metadata: Option<&DriverSpecificMetadata>,
430    driver_power_configs: Option<&DriverSpecificPowerConfigs>,
431    enabled_nodes: &[String],
432) -> anyhow::Result<()> {
433    let mut provider_metadata = HashMap::<String, Vec<fpbus::Metadata>>::new();
434
435    register_iommus(pbus, config.iommus.as_deref()).await.context("Failed to register IOMMUs")?;
436
437    let devices = config
438        .devices
439        .as_ref()
440        .ok_or_else(|| anyhow!("devices field is missing in BoardConfig"))?;
441
442    let disabled_devices: HashSet<&str> = devices
443        .iter()
444        .filter(|d| crate::is_device_disabled(d, enabled_nodes))
445        .filter_map(|d| d.name.as_deref())
446        .collect();
447
448    // 1. Generate driver specific metadata for devices in config
449    if let Some(drv_meta) = driver_metadata {
450        for dev in devices {
451            let name = dev.name.as_deref().unwrap_or("");
452            if disabled_devices.contains(name) {
453                continue;
454            }
455            if let Some(generators) = drv_meta.get(name) {
456                for (metadata_id, gen_fn) in *generators {
457                    let data = gen_fn()
458                        .with_context(|| format!("Failed to generate metadata for {}", name))?;
459                    provider_metadata.entry(name.to_string()).or_default().push(fpbus::Metadata {
460                        id: Some(metadata_id.to_string()),
461                        data: Some(data),
462                        ..Default::default()
463                    });
464                }
465            }
466        }
467    }
468
469    for (idx, dev) in devices.iter().enumerate() {
470        let dev_name = dev.name.as_deref().unwrap_or("");
471        if disabled_devices.contains(dev_name) {
472            continue;
473        }
474        if dev.disabled.unwrap_or(false) {
475            log::info!("Publishing disabled DML device '{}' due to runtime override", dev_name);
476        }
477        let instance_id = idx as u32 + 1;
478        let mut node = fpbus::Node {
479            name: dev.name.clone(),
480            vid: Some(0),
481            pid: Some(0),
482            did: Some(0),
483            instance_id: Some(instance_id),
484            driver_host: dev.driver_host.clone(),
485            ..Default::default()
486        };
487        node.interrupt_controller_id = dev.interrupt_controller_id;
488
489        if let Some(compatible) = &dev.compatible {
490            node.properties = Some(vec![fdf_framework::NodeProperty2 {
491                key: "fuchsia.COMPATIBLE".to_string(),
492                value: fdf_framework::NodePropertyValue::StringValue(compatible.clone()),
493            }]);
494            node.did = Some(BIND_PLATFORM_DEV_DID_DEVICETREE);
495            node.vid = Some(BIND_PLATFORM_DEV_VID_GENERIC);
496        }
497
498        let mut mmio_list = Vec::new();
499        let mut irq_list = Vec::new();
500        let mut bti_list = Vec::new();
501        let mut smc_list = Vec::new();
502        let mut boot_metadata_list = Vec::new();
503
504        if let Some(pdev_dict) = crate::pdev_constraints(config, dev.name.as_deref().unwrap_or(""))
505        {
506            for mmio in crate::mmio_list(pdev_dict) {
507                mmio_list.push(fpbus::Mmio {
508                    base: Some(mmio.base),
509                    length: Some(mmio.length),
510                    name: mmio.name,
511                    ..Default::default()
512                });
513            }
514            for irq in crate::irq_list(pdev_dict) {
515                let irq_spec = match irq.controller {
516                    Some(controller_id) => fpbus::IrqSpec::UserspaceIrq(fpbus::UserspaceIrq {
517                        irq: irq.number,
518                        controller_id,
519                    }),
520                    None => fpbus::IrqSpec::Irq(irq.number),
521                };
522                irq_list.push(fpbus::Irq {
523                    irq: Some(irq_spec),
524                    mode: Some(map_interrupt_mode(irq.mode.as_deref())),
525                    name: irq.name.clone(),
526                    wake_vector: irq.wake_vector,
527                    ..Default::default()
528                });
529            }
530            for bti in crate::bti_list(pdev_dict)? {
531                bti_list.push(fpbus::Bti {
532                    iommu_id: Some(bti.iommu_id),
533                    bti_id: Some(bti.id),
534                    name: bti.name,
535                    ..Default::default()
536                });
537            }
538            for smc in crate::smc_list(pdev_dict) {
539                smc_list.push(fpbus::Smc {
540                    service_call_num_base: Some(smc.service_call_num_base),
541                    count: Some(smc.count),
542                    exclusive: Some(smc.exclusive),
543                    name: smc.name,
544                    ..Default::default()
545                });
546            }
547            for bm in crate::boot_metadata_list(pdev_dict) {
548                boot_metadata_list.push(fpbus::BootMetadata {
549                    zbi_type: Some(bm.zbi_type),
550                    zbi_extra: bm.zbi_extra.or(Some(0)),
551                    ..Default::default()
552                });
553            }
554        }
555
556        if !mmio_list.is_empty() {
557            node.mmio = Some(mmio_list);
558        }
559        if !irq_list.is_empty() {
560            node.irq = Some(irq_list);
561        }
562        if !bti_list.is_empty() {
563            node.bti = Some(bti_list);
564        }
565        if !smc_list.is_empty() {
566            node.smc = Some(smc_list);
567        }
568        if !boot_metadata_list.is_empty() {
569            node.boot_metadata = Some(boot_metadata_list);
570        }
571
572        let mut metadata_list = Vec::new();
573
574        // Handle static metadata from config
575        if let Some(metadata) = &dev.metadata {
576            for meta in metadata {
577                let data =
578                    meta.data.clone().ok_or_else(|| anyhow!("Static metadata missing data"))?;
579                metadata_list.push(fpbus::Metadata {
580                    id: meta.id.clone(),
581                    data: Some(data),
582                    ..Default::default()
583                });
584            }
585        }
586
587        if let Some(meta) = provider_metadata.get(dev_name) {
588            metadata_list.extend(meta.clone());
589        }
590
591        if !metadata_list.is_empty() {
592            node.metadata = Some(metadata_list);
593        }
594
595        if let Some(gen_fn) = driver_power_configs.and_then(|configs| configs.get(dev_name)) {
596            let power_config = gen_fn()
597                .with_context(|| format!("Failed to generate power config for {}", dev_name))?;
598            if !power_config.is_empty() {
599                node.power_config = Some(power_config);
600            }
601        }
602
603        let mut resource_parents = Vec::new();
604        let mut generated_keys = HashSet::new();
605        if let Some(aggregates) = &config.aggregates {
606            for (agg_idx, agg) in aggregates.iter().enumerate() {
607                if agg.provider.as_deref().is_some_and(|p| disabled_devices.contains(p)) {
608                    continue;
609                }
610                if let Some(resources) = &agg.resources {
611                    for res in resources {
612                        if res.node.as_deref() == dev.name.as_deref() {
613                            let parent_and_key = if (agg.service.as_deref()
614                                == Some("fuchsia.hardware.platform.device.Service")
615                                || agg.service.as_deref()
616                                    == Some("fuchsia.hardware.interrupt.ControllerRegistryService"))
617                                && dev.compatible.is_some()
618                            {
619                                None
620                            } else {
621                                generate_parent_spec_generic(
622                                    agg.provider.as_deref().unwrap_or(""),
623                                    get_aggregate_id(
624                                        agg,
625                                        config.devices.as_deref().unwrap_or(&[]),
626                                        agg_idx as u32,
627                                    ),
628                                    agg.service.as_deref().unwrap_or(""),
629                                    res,
630                                    parser_config,
631                                )?
632                            };
633                            if let Some((parent, key)) = parent_and_key {
634                                if generated_keys.insert(key) {
635                                    if matches!(
636                                        agg.provider.as_deref(),
637                                        Some("parent") | Some("pdev")
638                                    ) {
639                                        resource_parents.insert(0, parent);
640                                    } else {
641                                        resource_parents.push(parent);
642                                    }
643                                }
644                            }
645                        }
646                    }
647                }
648            }
649        }
650
651        if resource_parents.is_empty() && dev.driver_host.is_some() {
652            node.driver_host = dev.driver_host.clone();
653        }
654
655        let mut spec = fdf_framework::CompositeNodeSpec {
656            name: dev.name.clone(),
657            driver_host: dev.driver_host.clone(),
658            ..Default::default()
659        };
660
661        let mut parents2 = Vec::new();
662
663        // 1. Generate pdev parent if compatible is present
664        if let Some(compatible) = &dev.compatible {
665            let pdev_parent = fdf_framework::ParentSpec2 {
666                bind_rules: vec![
667                    make_accept_bind_rule(
668                        "fuchsia.BIND_PROTOCOL",
669                        property_int(BIND_PROTOCOL_DEVICE),
670                    ),
671                    make_accept_bind_rule(
672                        "fuchsia.BIND_PLATFORM_DEV_VID",
673                        property_int(BIND_PLATFORM_DEV_VID_GENERIC),
674                    ),
675                    make_accept_bind_rule(
676                        "fuchsia.BIND_PLATFORM_DEV_DID",
677                        property_int(BIND_PLATFORM_DEV_DID_DEVICETREE),
678                    ),
679                    make_accept_bind_rule(
680                        "fuchsia.BIND_PLATFORM_DEV_INSTANCE_ID",
681                        property_int(instance_id),
682                    ),
683                    make_accept_bind_rule("fuchsia.COMPATIBLE", property_string(compatible)),
684                ],
685                properties: vec![
686                    // TODO(https://fxbug.dev/555962083): Restore `fuchsia.NAME = "pdev"` once all
687                    // composite drivers are migrated from `primary parent "devicetree"` to `"pdev"`
688                    // (`driver-index` rejects `ParentSpec2.properties` when `fuchsia.NAME` does not
689                    // equal the `.bind` primary parent symbol name).
690                    make_property2("fuchsia.BIND_PROTOCOL", property_int(BIND_PROTOCOL_DEVICE)),
691                    make_property2(
692                        "fuchsia.BIND_PLATFORM_DEV_VID",
693                        property_int(BIND_PLATFORM_DEV_VID_GENERIC),
694                    ),
695                    make_property2(
696                        "fuchsia.BIND_PLATFORM_DEV_DID",
697                        property_int(BIND_PLATFORM_DEV_DID_DEVICETREE),
698                    ),
699                    make_property2(
700                        "fuchsia.BIND_PLATFORM_DEV_INSTANCE_ID",
701                        property_int(instance_id),
702                    ),
703                    make_property2("fuchsia.COMPATIBLE", property_string(compatible)),
704                    make_property2(
705                        "fuchsia.devicetree.FIRST_COMPATIBLE",
706                        property_string(compatible),
707                    ),
708                    make_property2(
709                        "fuchsia.Service",
710                        property_string("fuchsia.hardware.platform.device.Service"),
711                    ),
712                ],
713            };
714            parents2.push(pdev_parent);
715        }
716
717        parents2.extend(resource_parents);
718
719        if !parents2.is_empty() {
720            spec.parents2 = Some(parents2);
721
722            log::debug!(
723                "DML-CONFIG: Adding composite spec: name={:?}, parents2={:?}",
724                spec.name,
725                spec.parents2
726            );
727            composite_manager
728                .add_spec_with(spec)
729                .await
730                .context("AddSpec request failed")?
731                .map_err(|e| anyhow!("AddSpec failed: {e:?}"))?;
732        }
733
734        if dev.compatible.is_some() {
735            log::debug!("DML-CONFIG: Adding node: {}", dev_name);
736            pbus.node_add(node)
737                .await
738                .context("NodeAdd request failed")?
739                .map_err(|e| e.err().unwrap_or(zx::Status::INTERNAL))
740                .context("NodeAdd failed")?;
741        }
742    }
743
744    Ok(())
745}
746
747/// Registers `iommus` with the `pbus`.
748async fn register_iommus(
749    pbus: &fidl_next::Client<fpbus::PlatformBus, DriverChannel>,
750    iommus: Option<&[Iommu]>,
751) -> anyhow::Result<()> {
752    let Some(iommus) = iommus else {
753        return Ok(());
754    };
755
756    let futures = iommus
757        .iter()
758        .map(|iommu| {
759            let iommu_name = iommu.name.as_deref().unwrap_or("unnamed");
760            let iommu_id = iommu.id.with_context(|| format!("IOMMU {iommu_name:?} missing id"))?;
761            let fpbus_iommu = match &iommu.iommu_type {
762                Some(IommuType::ArmSmmu(arm_smmu)) => {
763                    fpbus::Iommu::ArmSmmu(fpbus::ArmSmmu { base_address: arm_smmu.base_address })
764                }
765                Some(IommuType::StubIommu(_)) | None => fpbus::Iommu::StubIommu(()),
766                _ => {
767                    bail!("Unsupported IOMMU type for IOMMU {iommu_name:?}");
768                }
769            };
770            let future = async move {
771                log::info!("Registering IOMMU '{iommu_name}' (id: {iommu_id})");
772                pbus.register_iommu(iommu_id, &fpbus_iommu)
773                    .await
774                    .with_context(|| {
775                        format!(
776                            "Failed to send RegisterIommu FIDL request for IOMMU {iommu_name:?}"
777                        )
778                    })?
779                    .map_err(|e| e.err().unwrap_or(zx::Status::INTERNAL))
780                    .with_context(|| format!("Failed to register IOMMU {iommu_name:?}"))?;
781                Ok::<(), anyhow::Error>(())
782            };
783            Ok(future)
784        })
785        .collect::<Result<Vec<_>, _>>()?;
786    try_join_all(futures).await?;
787    Ok(())
788}