1use 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; pub const BIND_PLATFORM_DEV_VID_GENERIC: u32 = 0;
19pub const BIND_PLATFORM_DEV_DID_DEVICETREE: u32 = 50; pub 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 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 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 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 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
769async 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}