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),
82
83 ResourceName,
85
86 ResourceNode,
88
89 Integer(u32),
91
92 ProviderId,
94}
95
96#[derive(Clone, Copy, Debug)]
97pub enum RuleValueType {
98 Integer,
99 String,
100 Bool,
101}
102
103#[derive(Clone, Copy, Debug)]
104pub enum Destination {
105 BindRules,
107
108 Properties,
110
111 Both,
113}
114
115#[derive(Clone, Copy, Debug)]
117pub struct PropertyRule {
118 pub bind_key: &'static str,
120
121 pub sources: &'static [ValueSource],
126
127 pub value_type: RuleValueType,
129
130 pub destination: Destination,
132}
133
134#[derive(Clone, Copy, Debug)]
135pub enum TransportType {
136 None,
137 Zircon,
138 Driver,
139}
140
141#[derive(Clone, Copy, Debug)]
143pub struct ServiceBindConfig {
144 pub transport: TransportType,
146
147 pub rules: &'static [PropertyRule],
149
150 pub parent_key_sources: &'static [ValueSource],
155
156 pub bind_id: bool,
159}
160
161pub const DEFAULT_SERVICE_BIND_CONFIG: ServiceBindConfig = ServiceBindConfig {
162 transport: TransportType::Zircon,
163 rules: &[],
164 parent_key_sources: &[ValueSource::ResourceName],
165 bind_id: false,
166};
167
168pub const DEFAULT_ID_SERVICE_BIND_CONFIG: ServiceBindConfig =
169 ServiceBindConfig { bind_id: true, ..DEFAULT_SERVICE_BIND_CONFIG };
170
171pub struct DmlParserConfig {
172 pub service_configs: phf::Map<&'static str, ServiceBindConfig>,
173}
174
175pub static STANDARD_SERVICE_CONFIGS: phf::Map<&'static str, ServiceBindConfig> = phf::phf_map! {
178 "fuchsia.clock.Init" => ServiceBindConfig {
179 transport: TransportType::None,
180 rules: &[PropertyRule {
181 bind_key: "fuchsia.BIND_INIT_STEP",
182 sources: &[ValueSource::Integer(0x494B4C43)],
183 value_type: RuleValueType::Integer,
184 destination: Destination::Both,
185 }],
186 ..DEFAULT_SERVICE_BIND_CONFIG
187 },
188 "fuchsia.pwm.Init" => ServiceBindConfig {
189 transport: TransportType::None,
190 rules: &[PropertyRule {
191 bind_key: "fuchsia.BIND_INIT_STEP",
192 sources: &[ValueSource::Integer(0x004D5750)],
193 value_type: RuleValueType::Integer,
194 destination: Destination::Both,
195 }],
196 ..DEFAULT_SERVICE_BIND_CONFIG
197 },
198 "fuchsia.gpio.Init" => ServiceBindConfig {
199 transport: TransportType::None,
200 rules: &[PropertyRule {
201 bind_key: "fuchsia.BIND_INIT_STEP",
202 sources: &[ValueSource::Integer(0x4F495047)],
203 value_type: RuleValueType::Integer,
204 destination: Destination::Both,
205 }],
206 ..DEFAULT_SERVICE_BIND_CONFIG
207 },
208 "fuchsia.hardware.gpu.mali.Service" => ServiceBindConfig {
209 transport: TransportType::Driver,
210 ..DEFAULT_SERVICE_BIND_CONFIG
211 },
212 "fuchsia.hardware.platform.device.Service" => ServiceBindConfig {
213 rules: &[
214 PropertyRule {
215 bind_key: "fuchsia.BIND_PROTOCOL",
216 sources: &[ValueSource::Integer(BIND_PROTOCOL_DEVICE)],
217 value_type: RuleValueType::Integer,
218 destination: Destination::Both,
219 },
220 PropertyRule {
221 bind_key: "fuchsia.BIND_PLATFORM_DEV_VID",
222 sources: &[ValueSource::ConstraintKey("vid")],
223 value_type: RuleValueType::Integer,
224 destination: Destination::Both,
225 },
226 PropertyRule {
227 bind_key: "fuchsia.BIND_PLATFORM_DEV_DID",
228 sources: &[ValueSource::ConstraintKey("did")],
229 value_type: RuleValueType::Integer,
230 destination: Destination::Both,
231 },
232 ],
233 ..DEFAULT_SERVICE_BIND_CONFIG
234 },
235 "fuchsia.hardware.platform.bus.Service" => ServiceBindConfig {
236 transport: TransportType::Driver,
237 rules: &[
238 PropertyRule {
239 bind_key: "fuchsia.BIND_PLATFORM_DEV_VID",
240 sources: &[ValueSource::ConstraintKey("vid")],
241 value_type: RuleValueType::Integer,
242 destination: Destination::Both,
243 },
244 PropertyRule {
245 bind_key: "fuchsia.BIND_PLATFORM_DEV_PID",
246 sources: &[ValueSource::ConstraintKey("pid")],
247 value_type: RuleValueType::Integer,
248 destination: Destination::Both,
249 },
250 PropertyRule {
251 bind_key: "fuchsia.BIND_PLATFORM_DEV_DID",
252 sources: &[ValueSource::ConstraintKey("did")],
253 value_type: RuleValueType::Integer,
254 destination: Destination::Both,
255 },
256 ],
257 ..DEFAULT_SERVICE_BIND_CONFIG
258 },
259 "fuchsia.hardware.pci.Service" => ServiceBindConfig {
260 rules: &[
261 PropertyRule {
262 bind_key: "fuchsia.BIND_PCI_VID",
263 sources: &[ValueSource::ConstraintKey("vid")],
264 value_type: RuleValueType::Integer,
265 destination: Destination::Both,
266 },
267 PropertyRule {
268 bind_key: "fuchsia.BIND_PCI_DID",
269 sources: &[ValueSource::ConstraintKey("did")],
270 value_type: RuleValueType::Integer,
271 destination: Destination::Both,
272 },
273 PropertyRule {
274 bind_key: "fuchsia.BIND_PCI_TOPO",
275 sources: &[ValueSource::ConstraintKey("topo")],
276 value_type: RuleValueType::Integer,
277 destination: Destination::BindRules,
278 },
279 ],
280 ..DEFAULT_SERVICE_BIND_CONFIG
281 },
282};
283
284pub const DEFAULT_DML_PARSER_CONFIG: DmlParserConfig =
285 DmlParserConfig { service_configs: phf::phf_map! {} };
286
287impl Default for DmlParserConfig {
288 fn default() -> Self {
289 DEFAULT_DML_PARSER_CONFIG
290 }
291}
292
293fn resolve_value(
294 provider_id: Option<u32>,
295 source: &ValueSource,
296 res: &ResourceEntry,
297 constraint: &fdr::Dictionary,
298) -> Option<ResolvedValue> {
299 match source {
300 ValueSource::ConstraintKey(key) => {
301 if let Some(i) = crate::get_int64(constraint, key) {
302 Some(ResolvedValue::Integer(i as u32))
303 } else if let Some(s) = crate::get_string(constraint, key) {
304 Some(ResolvedValue::String(s))
305 } else if let Some(b) = crate::get_bool(constraint, key) {
306 Some(ResolvedValue::Bool(b))
307 } else {
308 None
309 }
310 }
311 ValueSource::ResourceName => res.name.clone().map(ResolvedValue::String),
312 ValueSource::ResourceNode => res.node.clone().map(ResolvedValue::String),
313 ValueSource::Integer(i) => Some(ResolvedValue::Integer(*i)),
314 ValueSource::ProviderId => provider_id.map(ResolvedValue::Integer),
315 }
316}
317
318enum ResolvedValue {
319 Integer(u32),
320 String(String),
321 Bool(bool),
322}
323
324fn apply_rule(
325 provider_id: Option<u32>,
326 rule: &PropertyRule,
327 res: &ResourceEntry,
328 constraint: &fdr::Dictionary,
329 bind_rules: &mut Vec<fdf_framework::BindRule2>,
330 properties: &mut Vec<fdf_framework::NodeProperty2>,
331) -> anyhow::Result<()> {
332 let resolved =
333 rule.sources.iter().find_map(|source| resolve_value(provider_id, source, res, constraint));
334
335 let Some(val) = resolved else {
336 return Ok(());
337 };
338
339 let property_value = match (val, rule.value_type) {
340 (ResolvedValue::Integer(i), RuleValueType::Integer) => property_int(i),
341 (ResolvedValue::String(s), RuleValueType::String) => property_string(&s),
342 (ResolvedValue::Bool(b), RuleValueType::Bool) => property_bool(b),
343 (ResolvedValue::String(s), RuleValueType::Integer) => {
344 let parsed = if s.starts_with("0x") || s.starts_with("0X") {
345 u32::from_str_radix(&s[2..], 16)
346 } else {
347 s.parse::<u32>()
348 };
349 match parsed {
350 Ok(i) => property_int(i),
351 Err(e) => {
352 bail!("Failed to parse string {} as integer: {}", s, e);
353 }
354 }
355 }
356 (ResolvedValue::String(s), RuleValueType::Bool) => match s.parse::<bool>() {
357 Ok(b) => property_bool(b),
358 Err(e) => {
359 bail!("Failed to parse string {} as bool: {}", s, e);
360 }
361 },
362 _ => {
363 bail!("Type mismatch in rule resolution");
364 }
365 };
366
367 match rule.destination {
368 Destination::BindRules => {
369 bind_rules.push(make_accept_bind_rule(rule.bind_key, property_value));
370 }
371 Destination::Properties => {
372 properties.push(make_property2(rule.bind_key, property_value));
373 }
374 Destination::Both => {
375 add_rule_and_property(bind_rules, properties, rule.bind_key, property_value);
376 }
377 }
378
379 Ok(())
380}
381
382pub fn generate_parent_spec_generic(
383 agg: &AggregateEntry,
384 res: &ResourceEntry,
385 provider_id: Option<u32>,
386 parser_config: &DmlParserConfig,
387) -> anyhow::Result<Option<(fdf_framework::ParentSpec2, String)>> {
388 let mut bind_rules = Vec::new();
389 let mut properties = Vec::new();
390
391 let service_name = agg.service.as_deref().unwrap_or("");
392 let service_config = parser_config
393 .service_configs
394 .get(service_name)
395 .or_else(|| STANDARD_SERVICE_CONFIGS.get(service_name))
396 .unwrap_or(&DEFAULT_ID_SERVICE_BIND_CONFIG);
397
398 match service_config.transport {
399 TransportType::Zircon => {
400 add_rule_and_property(
401 &mut bind_rules,
402 &mut properties,
403 "fuchsia.Service",
404 property_string(service_name),
405 );
406 properties.push(make_property2(
407 service_name,
408 property_string(&format!("{service_name}.ZirconTransport")),
409 ));
410 }
411 TransportType::Driver => {
412 add_rule_and_property(
413 &mut bind_rules,
414 &mut properties,
415 "fuchsia.Service",
416 property_string(service_name),
417 );
418 properties.push(make_property2(
419 service_name,
420 property_string(&format!("{service_name}.DriverTransport")),
421 ));
422 }
423 TransportType::None => {}
424 }
425
426 let constraint =
427 res.constraint.as_ref().ok_or_else(|| anyhow!("constraint is missing in ResourceEntry"))?;
428
429 for rule in service_config.rules {
430 apply_rule(provider_id, rule, res, constraint, &mut bind_rules, &mut properties)?;
431 }
432
433 if !properties.iter().any(|p| p.key == "fuchsia.NAME") {
434 let name_opt = res.name.clone().or_else(|| crate::get_string(constraint, "name"));
435 if let Some(name) = name_opt {
436 properties.push(make_property2("fuchsia.NAME", property_string(&name)));
437 }
438 }
439
440 if service_config.bind_id
441 && !bind_rules.iter().any(|r| r.key == "fuchsia.ID")
442 && let Some(id) = crate::get_uint32(constraint, "id")
443 {
444 bind_rules.push(make_accept_bind_rule("fuchsia.ID", property_int(id)));
445 }
446
447 let resolved_key = service_config.parent_key_sources.iter().find_map(|source| {
448 match resolve_value(provider_id, source, res, constraint) {
449 Some(ResolvedValue::String(k)) => Some(k),
450 _ => None,
451 }
452 });
453
454 let key = resolved_key.with_context(|| {
455 format!(
456 "Failed to resolve parent key for service {}. If this service should be ignored, add it to the config.",
457 service_name
458 )
459 })?;
460
461 let parent = fdf_framework::ParentSpec2 { bind_rules, properties };
462 Ok(Some((parent, key)))
463}
464
465pub fn get_provider_id(agg: &AggregateEntry, devices: &[Device]) -> Option<u32> {
469 let device = devices.iter().find(|d| d.name.as_deref() == agg.provider.as_deref());
470 let id = device.and_then(|d| d.id);
471 log::debug!(
472 "get_provider_id: provider={:?}, device_found={}, device_id={:?}",
473 agg.provider,
474 device.is_some(),
475 id,
476 );
477 id
478}
479
480pub async fn publish_dml_devices(
481 pbus: &fidl_next::Client<fpbus::PlatformBus, DriverChannel>,
482 composite_manager: &fidl_next::Client<fdf_framework::CompositeNodeManager, zx::Channel>,
483 config: &BoardConfig,
484 parser_config: &DmlParserConfig,
485 driver_metadata: Option<&DriverSpecificMetadata>,
486 driver_power_configs: Option<&DriverSpecificPowerConfigs>,
487 enabled_nodes: &[String],
488) -> anyhow::Result<()> {
489 let mut provider_metadata = HashMap::<String, Vec<fpbus::Metadata>>::new();
490
491 register_iommus(pbus, config.iommus.as_deref()).await.context("Failed to register IOMMUs")?;
492
493 let devices = config
494 .devices
495 .as_ref()
496 .ok_or_else(|| anyhow!("devices field is missing in BoardConfig"))?;
497
498 let disabled_devices: HashSet<&str> = devices
499 .iter()
500 .filter(|d| crate::is_device_disabled(d, enabled_nodes))
501 .filter_map(|d| d.name.as_deref())
502 .collect();
503
504 if let Some(drv_meta) = driver_metadata {
506 for dev in devices {
507 let name = dev.name.as_deref().unwrap_or("");
508 if disabled_devices.contains(name) {
509 continue;
510 }
511 if let Some(generators) = drv_meta.get(name) {
512 for (metadata_id, gen_fn) in *generators {
513 let data = gen_fn()
514 .with_context(|| format!("Failed to generate metadata for {}", name))?;
515 provider_metadata.entry(name.to_string()).or_default().push(fpbus::Metadata {
516 id: Some(metadata_id.to_string()),
517 data: Some(data),
518 ..Default::default()
519 });
520 }
521 }
522 }
523 }
524
525 for (idx, dev) in devices.iter().enumerate() {
526 let dev_name = dev.name.as_deref().unwrap_or("");
527 if disabled_devices.contains(dev_name) {
528 continue;
529 }
530 if dev.disabled.unwrap_or(false) {
531 log::info!("Publishing disabled DML device '{}' due to runtime override", dev_name);
532 }
533 let instance_id = idx as u32 + 1;
534 let mut node = fpbus::Node {
535 name: dev.name.clone(),
536 vid: Some(0),
537 pid: Some(0),
538 did: Some(0),
539 instance_id: Some(instance_id),
540 driver_host: dev.driver_host.clone(),
541 ..Default::default()
542 };
543 node.interrupt_controller_id = dev.interrupt_controller_id;
544
545 if let Some(compatible) = &dev.compatible {
546 node.properties = Some(vec![fdf_framework::NodeProperty2 {
547 key: "fuchsia.COMPATIBLE".to_string(),
548 value: fdf_framework::NodePropertyValue::StringValue(compatible.clone()),
549 }]);
550 node.did = Some(BIND_PLATFORM_DEV_DID_DEVICETREE);
551 node.vid = Some(BIND_PLATFORM_DEV_VID_GENERIC);
552 }
553
554 let mut mmio_list = Vec::new();
555 let mut irq_list = Vec::new();
556 let mut irq_controllers = Vec::new();
557 let mut bti_list = Vec::new();
558 let mut smc_list = Vec::new();
559 let mut boot_metadata_list = Vec::new();
560
561 if let Some(pdev_dict) = crate::pdev_constraints(config, dev.name.as_deref().unwrap_or(""))
562 {
563 for mmio in crate::mmio_list(pdev_dict) {
564 mmio_list.push(fpbus::Mmio {
565 base: Some(mmio.base),
566 length: Some(mmio.length),
567 name: mmio.name,
568 ..Default::default()
569 });
570 }
571 for irq in crate::irq_list(pdev_dict) {
572 let irq_spec = match irq.controller {
573 Some(controller_id) => {
574 if !irq_controllers.contains(&controller_id) {
575 irq_controllers.push(controller_id);
576 }
577 fpbus::IrqSpec::UserspaceIrq(fpbus::UserspaceIrq {
578 irq: irq.number,
579 controller_id,
580 })
581 }
582 None => fpbus::IrqSpec::Irq(irq.number),
583 };
584 irq_list.push(fpbus::Irq {
585 irq: Some(irq_spec),
586 mode: Some(map_interrupt_mode(irq.mode.as_deref())),
587 name: irq.name.clone(),
588 wake_vector: irq.wake_vector,
589 ..Default::default()
590 });
591 }
592 for bti in crate::bti_list(pdev_dict)? {
593 bti_list.push(fpbus::Bti {
594 iommu_id: Some(bti.iommu_id),
595 bti_id: Some(bti.id),
596 name: bti.name,
597 ..Default::default()
598 });
599 }
600 for smc in crate::smc_list(pdev_dict) {
601 smc_list.push(fpbus::Smc {
602 service_call_num_base: Some(smc.service_call_num_base),
603 count: Some(smc.count),
604 exclusive: Some(smc.exclusive),
605 name: smc.name,
606 ..Default::default()
607 });
608 }
609 for bm in crate::boot_metadata_list(pdev_dict) {
610 boot_metadata_list.push(fpbus::BootMetadata {
611 zbi_type: Some(bm.zbi_type),
612 zbi_extra: bm.zbi_extra.or(Some(0)),
613 ..Default::default()
614 });
615 }
616 }
617
618 if !mmio_list.is_empty() {
619 node.mmio = Some(mmio_list);
620 }
621 if !irq_list.is_empty() {
622 node.irq = Some(irq_list);
623 }
624 if !bti_list.is_empty() {
625 node.bti = Some(bti_list);
626 }
627 if !smc_list.is_empty() {
628 node.smc = Some(smc_list);
629 }
630 if !boot_metadata_list.is_empty() {
631 node.boot_metadata = Some(boot_metadata_list);
632 }
633
634 let mut metadata_list = Vec::new();
635
636 if let Some(metadata) = &dev.metadata {
638 for meta in metadata {
639 let data =
640 meta.data.clone().ok_or_else(|| anyhow!("Static metadata missing data"))?;
641 metadata_list.push(fpbus::Metadata {
642 id: meta.id.clone(),
643 data: Some(data),
644 ..Default::default()
645 });
646 }
647 }
648
649 if let Some(meta) = provider_metadata.get(dev_name) {
650 metadata_list.extend(meta.clone());
651 }
652
653 if !metadata_list.is_empty() {
654 node.metadata = Some(metadata_list);
655 }
656
657 if let Some(gen_fn) = driver_power_configs.and_then(|configs| configs.get(dev_name)) {
658 let power_config = gen_fn()
659 .with_context(|| format!("Failed to generate power config for {}", dev_name))?;
660 if !power_config.is_empty() {
661 node.power_config = Some(power_config);
662 }
663 }
664
665 let mut resource_parents = Vec::new();
666 let mut generated_keys = HashSet::new();
667 for agg in config.aggregates.iter().flatten() {
668 if agg.provider.as_deref().is_some_and(|p| disabled_devices.contains(p)) {
669 continue;
670 }
671 if dev.compatible.is_some()
672 && matches!(
673 agg.service.as_deref(),
674 Some("fuchsia.hardware.platform.device.Service")
675 | Some("fuchsia.hardware.interrupt.ControllerRegistryService")
676 )
677 {
678 continue;
679 }
680 let provider_id = get_provider_id(agg, devices);
681 for res in agg
682 .resources
683 .iter()
684 .flatten()
685 .filter(|res| res.node.as_deref() == dev.name.as_deref())
686 {
687 if let Some((parent, key)) =
688 generate_parent_spec_generic(agg, res, provider_id, parser_config)?
689 {
690 if generated_keys.insert(key) {
691 if matches!(agg.provider.as_deref(), Some("parent") | Some("pdev")) {
692 resource_parents.insert(0, parent);
693 } else {
694 resource_parents.push(parent);
695 }
696 }
697 }
698 }
699 }
700
701 for (parent, key) in generate_interrupt_controller_parents(&irq_controllers) {
702 if generated_keys.insert(key) {
703 resource_parents.push(parent);
704 }
705 }
706
707 if resource_parents.is_empty() && dev.driver_host.is_some() {
708 node.driver_host = dev.driver_host.clone();
709 }
710
711 let mut spec = fdf_framework::CompositeNodeSpec {
712 name: dev.name.clone(),
713 driver_host: dev.driver_host.clone(),
714 ..Default::default()
715 };
716
717 let mut parents2 = Vec::new();
718
719 if let Some(compatible) = &dev.compatible {
721 let pdev_parent = fdf_framework::ParentSpec2 {
722 bind_rules: vec![
723 make_accept_bind_rule(
724 "fuchsia.BIND_PROTOCOL",
725 property_int(BIND_PROTOCOL_DEVICE),
726 ),
727 make_accept_bind_rule(
728 "fuchsia.BIND_PLATFORM_DEV_VID",
729 property_int(BIND_PLATFORM_DEV_VID_GENERIC),
730 ),
731 make_accept_bind_rule(
732 "fuchsia.BIND_PLATFORM_DEV_DID",
733 property_int(BIND_PLATFORM_DEV_DID_DEVICETREE),
734 ),
735 make_accept_bind_rule(
736 "fuchsia.BIND_PLATFORM_DEV_INSTANCE_ID",
737 property_int(instance_id),
738 ),
739 make_accept_bind_rule("fuchsia.COMPATIBLE", property_string(compatible)),
740 ],
741 properties: vec![
742 make_property2("fuchsia.BIND_PROTOCOL", property_int(BIND_PROTOCOL_DEVICE)),
747 make_property2(
748 "fuchsia.BIND_PLATFORM_DEV_VID",
749 property_int(BIND_PLATFORM_DEV_VID_GENERIC),
750 ),
751 make_property2(
752 "fuchsia.BIND_PLATFORM_DEV_DID",
753 property_int(BIND_PLATFORM_DEV_DID_DEVICETREE),
754 ),
755 make_property2(
756 "fuchsia.BIND_PLATFORM_DEV_INSTANCE_ID",
757 property_int(instance_id),
758 ),
759 make_property2("fuchsia.COMPATIBLE", property_string(compatible)),
760 make_property2(
761 "fuchsia.devicetree.FIRST_COMPATIBLE",
762 property_string(compatible),
763 ),
764 make_property2(
765 "fuchsia.Service",
766 property_string("fuchsia.hardware.platform.device.Service"),
767 ),
768 ],
769 };
770 parents2.push(pdev_parent);
771 }
772
773 parents2.extend(resource_parents);
774
775 if !parents2.is_empty() {
776 spec.parents2 = Some(parents2);
777
778 log::debug!(
779 "DML-CONFIG: Adding composite spec: name={:?}, parents2={:?}",
780 spec.name,
781 spec.parents2
782 );
783 composite_manager
784 .add_spec_with(spec)
785 .await
786 .context("AddSpec request failed")?
787 .map_err(|e| anyhow!("AddSpec failed: {e:?}"))?;
788 }
789
790 if dev.compatible.is_some() {
791 log::debug!("DML-CONFIG: Adding node: {}", dev_name);
792 pbus.node_add(node)
793 .await
794 .context("NodeAdd request failed")?
795 .context("NodeAdd failed")?;
796 }
797 }
798
799 Ok(())
800}
801
802fn generate_interrupt_controller_parents(
805 irq_controllers: &[u32],
806) -> Vec<(fdf_framework::ParentSpec2, String)> {
807 irq_controllers
808 .iter()
809 .enumerate()
810 .map(|(idx, &controller_id)| {
811 let key = format!("interrupt-controller-{idx}");
812 let parent = fdf_framework::ParentSpec2 {
813 bind_rules: vec![
814 make_accept_bind_rule("fuchsia.NAME", property_string("interrupt-controller")),
815 make_accept_bind_rule("fuchsia.ID", property_int(controller_id)),
816 ],
817 properties: vec![make_property2("fuchsia.NAME", property_string(&key))],
818 };
819 (parent, key)
820 })
821 .collect()
822}
823
824async fn register_iommus(
826 pbus: &fidl_next::Client<fpbus::PlatformBus, DriverChannel>,
827 iommus: Option<&[Iommu]>,
828) -> anyhow::Result<()> {
829 let Some(iommus) = iommus else {
830 return Ok(());
831 };
832
833 let futures = iommus
834 .iter()
835 .map(|iommu| {
836 let iommu_name = iommu.name.as_deref().unwrap_or("unnamed");
837 let iommu_id = iommu.id.with_context(|| format!("IOMMU {iommu_name:?} missing id"))?;
838 let fpbus_iommu = match &iommu.iommu_type {
839 Some(IommuType::ArmSmmu(arm_smmu)) => {
840 fpbus::Iommu::ArmSmmu(fpbus::ArmSmmu { base_address: arm_smmu.base_address })
841 }
842 Some(IommuType::StubIommu(_)) | None => fpbus::Iommu::StubIommu(()),
843 _ => {
844 bail!("Unsupported IOMMU type for IOMMU {iommu_name:?}");
845 }
846 };
847 let future = async move {
848 log::info!("Registering IOMMU '{iommu_name}' (id: {iommu_id})");
849 pbus.register_iommu(iommu_id, &fpbus_iommu)
850 .await
851 .with_context(|| {
852 format!(
853 "Failed to send RegisterIommu FIDL request for IOMMU {iommu_name:?}"
854 )
855 })?
856 .with_context(|| format!("Failed to register IOMMU {iommu_name:?}"))?;
857 Ok::<(), anyhow::Error>(())
858 };
859 Ok(future)
860 })
861 .collect::<Result<Vec<_>, _>>()?;
862 try_join_all(futures).await?;
863 Ok(())
864}
865
866#[cfg(test)]
867mod tests {
868 use super::*;
869
870 #[test]
871 fn test_pci_service_parent_spec() {
872 let res = ResourceEntry {
873 name: Some("pci".to_string()),
874 node: Some("mali-gpu".to_string()),
875 constraint: Some(fdr::Dictionary {
876 entries: Some(vec![
877 fdr::DictionaryEntry {
878 key: "vid".to_string(),
879 value: fdr::DictionaryValue::Int64(0x13b5),
880 },
881 fdr::DictionaryEntry {
882 key: "did".to_string(),
883 value: fdr::DictionaryValue::Int64(0x7212),
884 },
885 fdr::DictionaryEntry {
886 key: "topo".to_string(),
887 value: fdr::DictionaryValue::Int64(256),
888 },
889 ]),
890 ..Default::default()
891 }),
892 ..Default::default()
893 };
894
895 let agg = AggregateEntry {
896 provider: Some("pcie-c500000".to_string()),
897 service: Some("fuchsia.hardware.pci.Service".to_string()),
898 resources: Some(vec![res.clone()]),
899 ..Default::default()
900 };
901
902 let (parent, key) =
903 generate_parent_spec_generic(&agg, &res, None, &DEFAULT_DML_PARSER_CONFIG)
904 .expect("should succeed")
905 .expect("should return parent spec");
906
907 assert_eq!(key, "pci");
908 assert!(parent.bind_rules.iter().any(|r| r.key == "fuchsia.BIND_PCI_VID"
909 && r.values == vec![fdf_framework::NodePropertyValue::IntValue(0x13b5)]));
910 assert!(parent.bind_rules.iter().any(|r| r.key == "fuchsia.BIND_PCI_DID"
911 && r.values == vec![fdf_framework::NodePropertyValue::IntValue(0x7212)]));
912 assert!(parent.bind_rules.iter().any(|r| r.key == "fuchsia.BIND_PCI_TOPO"
913 && r.values == vec![fdf_framework::NodePropertyValue::IntValue(256)]));
914 assert!(parent.properties.iter().any(|p| p.key == "fuchsia.BIND_PCI_VID"
915 && p.value == fdf_framework::NodePropertyValue::IntValue(0x13b5)));
916 assert!(parent.properties.iter().any(|p| p.key == "fuchsia.BIND_PCI_DID"
917 && p.value == fdf_framework::NodePropertyValue::IntValue(0x7212)));
918 assert!(!parent.properties.iter().any(|p| p.key == "fuchsia.BIND_PCI_TOPO"));
919 }
920
921 #[test]
922 fn test_interrupt_controller_parents_dedup_and_order() {
923 let pdev_dict = fdr::Dictionary {
924 entries: Some(vec![
925 fdr::DictionaryEntry {
926 key: "interrupts.0.number".to_string(),
927 value: fdr::DictionaryValue::Int64(574),
928 },
929 fdr::DictionaryEntry {
930 key: "interrupts.1.number".to_string(),
931 value: fdr::DictionaryValue::Int64(17),
932 },
933 fdr::DictionaryEntry {
934 key: "interrupts.1.controller".to_string(),
935 value: fdr::DictionaryValue::Int64(290),
936 },
937 fdr::DictionaryEntry {
938 key: "interrupts.2.number".to_string(),
939 value: fdr::DictionaryValue::Int64(18),
940 },
941 fdr::DictionaryEntry {
942 key: "interrupts.2.controller".to_string(),
943 value: fdr::DictionaryValue::Int64(290),
944 },
945 fdr::DictionaryEntry {
946 key: "interrupts.3.number".to_string(),
947 value: fdr::DictionaryValue::Int64(4),
948 },
949 fdr::DictionaryEntry {
950 key: "interrupts.3.controller".to_string(),
951 value: fdr::DictionaryValue::Int64(289),
952 },
953 ]),
954 ..Default::default()
955 };
956
957 let mut irq_controllers = Vec::new();
958 for irq in crate::irq_list(&pdev_dict) {
959 if let Some(controller_id) = irq.controller {
960 if !irq_controllers.contains(&controller_id) {
961 irq_controllers.push(controller_id);
962 }
963 }
964 }
965 assert_eq!(irq_controllers, vec![290, 289]);
966
967 let parents = generate_interrupt_controller_parents(&irq_controllers);
968 assert_eq!(parents.len(), 2);
969
970 let (parent0, key0) = &parents[0];
971 assert_eq!(key0, "interrupt-controller-0");
972 assert_eq!(
973 parent0.bind_rules,
974 vec![
975 make_accept_bind_rule("fuchsia.NAME", property_string("interrupt-controller")),
976 make_accept_bind_rule("fuchsia.ID", property_int(290)),
977 ]
978 );
979 assert_eq!(
980 parent0.properties,
981 vec![make_property2("fuchsia.NAME", property_string("interrupt-controller-0"))]
982 );
983
984 let (parent1, key1) = &parents[1];
985 assert_eq!(key1, "interrupt-controller-1");
986 assert_eq!(
987 parent1.bind_rules,
988 vec![
989 make_accept_bind_rule("fuchsia.NAME", property_string("interrupt-controller")),
990 make_accept_bind_rule("fuchsia.ID", property_int(289)),
991 ]
992 );
993 assert_eq!(
994 parent1.properties,
995 vec![make_property2("fuchsia.NAME", property_string("interrupt-controller-1"))]
996 );
997 }
998}