1use crate::{AggregateEntry, BoardConfig, Device, ResourceEntry};
6use anyhow::{Context, anyhow, bail};
7use fdf_fidl::DriverChannel;
8use fidl_fuchsia_driver_metadata as fdr;
9use fidl_next_fuchsia_driver_framework as fdf_framework;
10use fidl_next_fuchsia_hardware_platform_bus as fpbus;
11use phf;
12use std::collections::{HashMap, HashSet};
13use zx;
14
15pub const BIND_PROTOCOL_DEVICE: u32 = 85; pub const BIND_PLATFORM_DEV_VID_GENERIC: u32 = 0;
17pub const BIND_PLATFORM_DEV_DID_DEVICETREE: u32 = 50; pub fn property_int(val: u32) -> fdf_framework::NodePropertyValue {
20 fdf_framework::NodePropertyValue::IntValue(val)
21}
22
23pub fn property_string(val: &str) -> fdf_framework::NodePropertyValue {
24 fdf_framework::NodePropertyValue::StringValue(val.to_string())
25}
26
27pub fn property_bool(val: bool) -> fdf_framework::NodePropertyValue {
28 fdf_framework::NodePropertyValue::BoolValue(val)
29}
30
31fn map_interrupt_mode(mode: Option<&str>) -> fpbus::ZirconInterruptMode {
32 match mode {
33 Some("EdgeLow") => fpbus::ZirconInterruptMode::EdgeLow,
34 Some("EdgeHigh") => fpbus::ZirconInterruptMode::EdgeHigh,
35 Some("LevelLow") => fpbus::ZirconInterruptMode::LevelLow,
36 Some("LevelHigh") => fpbus::ZirconInterruptMode::LevelHigh,
37 Some("EdgeBoth") => fpbus::ZirconInterruptMode::EdgeBoth,
38 _ => fpbus::ZirconInterruptMode::Default,
39 }
40}
41
42pub type DriverSpecificMetadata =
43 phf::Map<&'static str, &'static [(&'static str, fn() -> anyhow::Result<Vec<u8>>)]>;
44
45pub fn make_accept_bind_rule(
46 key: &str,
47 value: fdf_framework::NodePropertyValue,
48) -> fdf_framework::BindRule2 {
49 fdf_framework::BindRule2 {
50 key: key.to_string(),
51 condition: fdf_framework::Condition::Accept,
52 values: vec![value],
53 }
54}
55
56pub fn make_property2(
57 key: &str,
58 value: fdf_framework::NodePropertyValue,
59) -> fdf_framework::NodeProperty2 {
60 fdf_framework::NodeProperty2 { key: key.to_string(), value }
61}
62
63pub fn add_rule_and_property(
64 bind_rules: &mut Vec<fdf_framework::BindRule2>,
65 properties: &mut Vec<fdf_framework::NodeProperty2>,
66 key: &str,
67 value: fdf_framework::NodePropertyValue,
68) {
69 bind_rules.push(make_accept_bind_rule(key, value.clone()));
70 properties.push(make_property2(key, value));
71}
72
73#[derive(Clone, Copy, Debug)]
74pub enum ValueSource {
75 ConstraintKey(&'static str),
76 ResourceName,
77 ResourceNode,
78 Template(&'static str),
79 Integer(u32),
80 ProviderId,
81}
82
83#[derive(Clone, Copy, Debug)]
84pub enum RuleValueType {
85 Integer,
86 String,
87 Bool,
88}
89
90#[derive(Clone, Copy, Debug)]
91pub enum Destination {
92 BindRules,
93 Properties,
94 Both,
95}
96
97#[derive(Clone, Copy, Debug)]
98pub struct PropertyRule {
99 pub bind_key: &'static str,
100 pub sources: &'static [ValueSource],
101 pub value_type: RuleValueType,
102 pub destination: Destination,
103 pub required: bool,
104}
105
106#[derive(Clone, Copy, Debug)]
107pub enum TransportType {
108 None,
109 Zircon,
110 Driver,
111}
112
113#[derive(Clone, Copy, Debug)]
114pub struct ServiceBindConfig {
115 pub transport: TransportType,
116 pub rules: &'static [PropertyRule],
117 pub parent_key_sources: &'static [ValueSource],
118}
119
120pub const DEFAULT_SERVICE_BIND_CONFIG: ServiceBindConfig = ServiceBindConfig {
121 transport: TransportType::Zircon,
122 rules: &[],
123 parent_key_sources: &[ValueSource::ResourceName],
124};
125
126pub struct DmlParserConfig {
127 pub service_configs: phf::Map<&'static str, ServiceBindConfig>,
128}
129
130fn resolve_template(
131 provider: &str,
132 provider_id: u32,
133 template: &str,
134 res: &ResourceEntry,
135 constraint: &fdr::Dictionary,
136) -> Option<String> {
137 let mut result = String::new();
138 let mut start = 0;
139 while let Some(open) = template[start..].find('{') {
140 let open_idx = start + open;
141 result.push_str(&template[start..open_idx]);
142 if let Some(close) = template[open_idx..].find('}') {
143 let close_idx = open_idx + close;
144 let key = &template[open_idx + 1..close_idx];
145 let value = match key {
146 "res.name" => res.name.clone()?,
147 "res.node" => res.node.clone()?,
148 "provider" => provider.to_string(),
149 "provider_id" => provider_id.to_string(),
150 k => {
151 if let Some(s) = crate::get_string(constraint, k) {
152 s
153 } else if let Some(i) = crate::get_int64(constraint, k) {
154 i.to_string()
155 } else if let Some(b) = crate::get_bool(constraint, k) {
156 b.to_string()
157 } else {
158 return None;
159 }
160 }
161 };
162 result.push_str(&value);
163 start = close_idx + 1;
164 } else {
165 return None;
166 }
167 }
168 result.push_str(&template[start..]);
169 Some(result)
170}
171
172fn resolve_value(
173 provider: &str,
174 provider_id: u32,
175 source: &ValueSource,
176 res: &ResourceEntry,
177 constraint: &fdr::Dictionary,
178) -> Option<ResolvedValue> {
179 match source {
180 ValueSource::ConstraintKey(key) => {
181 if let Some(i) = crate::get_int64(constraint, key) {
182 Some(ResolvedValue::Integer(i as u32))
183 } else if let Some(s) = crate::get_string(constraint, key) {
184 Some(ResolvedValue::String(s))
185 } else if let Some(b) = crate::get_bool(constraint, key) {
186 Some(ResolvedValue::Bool(b))
187 } else {
188 None
189 }
190 }
191 ValueSource::ResourceName => res.name.clone().map(ResolvedValue::String),
192 ValueSource::ResourceNode => res.node.clone().map(ResolvedValue::String),
193 ValueSource::Template(template) => {
194 resolve_template(provider, provider_id, template, res, constraint)
195 .map(ResolvedValue::String)
196 }
197 ValueSource::Integer(i) => Some(ResolvedValue::Integer(*i)),
198 ValueSource::ProviderId => Some(ResolvedValue::Integer(provider_id)),
199 }
200}
201
202enum ResolvedValue {
203 Integer(u32),
204 String(String),
205 Bool(bool),
206}
207
208fn apply_rule(
209 provider: &str,
210 provider_id: u32,
211 rule: &PropertyRule,
212 res: &ResourceEntry,
213 constraint: &fdr::Dictionary,
214 bind_rules: &mut Vec<fdf_framework::BindRule2>,
215 properties: &mut Vec<fdf_framework::NodeProperty2>,
216) -> anyhow::Result<()> {
217 let resolved = rule
218 .sources
219 .iter()
220 .find_map(|source| resolve_value(provider, provider_id, source, res, constraint));
221
222 let val = match resolved {
223 Some(v) => v,
224 None => {
225 if rule.required {
226 bail!("Failed to resolve required property {}", rule.bind_key);
227 } else {
228 return Ok(());
229 }
230 }
231 };
232
233 let property_value = match (val, rule.value_type) {
234 (ResolvedValue::Integer(i), RuleValueType::Integer) => property_int(i),
235 (ResolvedValue::String(s), RuleValueType::String) => property_string(&s),
236 (ResolvedValue::Bool(b), RuleValueType::Bool) => property_bool(b),
237 (ResolvedValue::String(s), RuleValueType::Integer) => {
238 let parsed = if s.starts_with("0x") || s.starts_with("0X") {
239 u32::from_str_radix(&s[2..], 16)
240 } else {
241 s.parse::<u32>()
242 };
243 match parsed {
244 Ok(i) => property_int(i),
245 Err(e) => {
246 bail!("Failed to parse string {} as integer: {}", s, e);
247 }
248 }
249 }
250 (ResolvedValue::String(s), RuleValueType::Bool) => match s.parse::<bool>() {
251 Ok(b) => property_bool(b),
252 Err(e) => {
253 bail!("Failed to parse string {} as bool: {}", s, e);
254 }
255 },
256 _ => {
257 bail!("Type mismatch in rule resolution");
258 }
259 };
260
261 match rule.destination {
262 Destination::BindRules => {
263 bind_rules.push(make_accept_bind_rule(rule.bind_key, property_value));
264 }
265 Destination::Properties => {
266 properties.push(make_property2(rule.bind_key, property_value));
267 }
268 Destination::Both => {
269 add_rule_and_property(bind_rules, properties, rule.bind_key, property_value);
270 }
271 }
272
273 Ok(())
274}
275
276pub fn generate_parent_spec_generic(
277 provider: &str,
278 provider_id: u32,
279 service_name: &str,
280 res: &ResourceEntry,
281 config: &DmlParserConfig,
282) -> anyhow::Result<Option<(fdf_framework::ParentSpec2, String)>> {
283 let mut bind_rules = Vec::new();
284 let mut properties = Vec::new();
285
286 let service_config =
287 config.service_configs.get(service_name).unwrap_or(&DEFAULT_SERVICE_BIND_CONFIG);
288
289 match service_config.transport {
290 TransportType::Zircon => {
291 let transport_value = format!("{}.ZirconTransport", service_name);
292 add_rule_and_property(
293 &mut bind_rules,
294 &mut properties,
295 service_name,
296 property_string(&transport_value),
297 );
298 }
299 TransportType::Driver => {
300 let transport_value = format!("{}.DriverTransport", service_name);
301 add_rule_and_property(
302 &mut bind_rules,
303 &mut properties,
304 service_name,
305 property_string(&transport_value),
306 );
307 }
308 TransportType::None => {}
309 }
310
311 let constraint =
312 res.constraint.as_ref().ok_or_else(|| anyhow!("constraint is missing in ResourceEntry"))?;
313
314 for rule in service_config.rules {
315 apply_rule(provider, provider_id, rule, res, constraint, &mut bind_rules, &mut properties)?;
316 }
317
318 let resolved_key = service_config.parent_key_sources.iter().find_map(|source| {
319 match resolve_value(provider, provider_id, source, res, constraint) {
320 Some(ResolvedValue::String(k)) => Some(k),
321 _ => None,
322 }
323 });
324
325 let key = match resolved_key {
326 Some(k) => k,
327 None => {
328 bail!(
329 "Failed to resolve parent key for service {}. If this service should be ignored, add it to the config.",
330 service_name
331 );
332 }
333 };
334
335 let parent = fdf_framework::ParentSpec2 { bind_rules, properties };
336 Ok(Some((parent, key)))
337}
338
339fn get_aggregate_id(agg: &AggregateEntry, devices: &[Device], fallback_id: u32) -> u32 {
340 let device = devices.iter().find(|d| d.name.as_deref() == agg.provider.as_deref());
341 let id = device.and_then(|d| d.id).unwrap_or(fallback_id);
342 log::info!(
343 "get_aggregate_id: provider={:?}, device_found={}, device_id={:?}, final_id={}, fallback={}",
344 agg.provider,
345 device.is_some(),
346 device.and_then(|d| d.id),
347 id,
348 fallback_id
349 );
350 id
351}
352
353pub async fn publish_dml_devices(
354 pbus: &fidl_next::Client<fpbus::PlatformBus, DriverChannel>,
355 composite_manager: &fidl_next::Client<fdf_framework::CompositeNodeManager, zx::Channel>,
356 config: &BoardConfig,
357 parser_config: &DmlParserConfig,
358 driver_metadata: Option<&DriverSpecificMetadata>,
359) -> anyhow::Result<()> {
360 let mut provider_metadata = HashMap::<String, Vec<fpbus::Metadata>>::new();
361
362 if let (Some(drv_meta), Some(devices)) = (driver_metadata, &config.devices) {
364 for dev in devices {
365 let name = dev.name.as_deref().unwrap_or("");
366 if let Some(generators) = drv_meta.get(name) {
367 for (metadata_id, gen_fn) in *generators {
368 let data = gen_fn()
369 .with_context(|| format!("Failed to generate metadata for {}", name))?;
370 provider_metadata.entry(name.to_string()).or_default().push(fpbus::Metadata {
371 id: Some(metadata_id.to_string()),
372 data: Some(data),
373 ..Default::default()
374 });
375 }
376 }
377 }
378 }
379
380 let devices = config
381 .devices
382 .as_ref()
383 .ok_or_else(|| anyhow!("devices field is missing in BoardConfig"))?;
384 for (idx, dev) in devices.iter().enumerate() {
385 let instance_id = idx as u32 + 1;
386 let mut node = fpbus::Node {
387 name: dev.name.clone(),
388 vid: Some(0),
389 pid: Some(0),
390 did: Some(0),
391 instance_id: Some(instance_id),
392 driver_host: dev.url.clone(),
393 ..Default::default()
394 };
395
396 if let Some(compatible) = &dev.compatible {
397 node.properties = Some(vec![fdf_framework::NodeProperty2 {
398 key: "fuchsia.devicetree.FIRST_COMPATIBLE".to_string(),
399 value: fdf_framework::NodePropertyValue::StringValue(compatible.clone()),
400 }]);
401 node.did = Some(BIND_PLATFORM_DEV_DID_DEVICETREE);
402 node.vid = Some(BIND_PLATFORM_DEV_VID_GENERIC);
403 }
404
405 let mut mmio_list = Vec::new();
406 let mut irq_list = Vec::new();
407 let mut bti_list = Vec::new();
408 let mut smc_list = Vec::new();
409 let mut boot_metadata_list = Vec::new();
410
411 if let Some(pdev_dict) = crate::pdev_constraints(config, dev.name.as_deref().unwrap_or(""))
412 {
413 for mmio in crate::mmio_list(pdev_dict) {
414 mmio_list.push(fpbus::Mmio {
415 base: Some(mmio.base),
416 length: Some(mmio.length),
417 name: mmio.name.clone(),
418 ..Default::default()
419 });
420 }
421 for irq in crate::irq_list(pdev_dict) {
422 irq_list.push(fpbus::Irq {
423 irq: Some(fpbus::IrqSpec::Irq(irq.number)),
424 mode: Some(map_interrupt_mode(irq.mode.as_deref())),
425 name: irq.name.clone(),
426 ..Default::default()
427 });
428 }
429 for bti in crate::bti_list(pdev_dict) {
430 bti_list.push(fpbus::Bti {
431 iommu_id: Some(0),
432 bti_id: Some(bti.id),
433 ..Default::default()
434 });
435 }
436 for smc in crate::smc_list(pdev_dict) {
437 smc_list.push(fpbus::Smc {
438 service_call_num_base: Some(smc.service_call_num_base),
439 count: Some(smc.count),
440 exclusive: Some(smc.exclusive),
441 name: smc.name.clone(),
442 ..Default::default()
443 });
444 }
445 for bm in crate::boot_metadata_list(pdev_dict) {
446 boot_metadata_list.push(fpbus::BootMetadata {
447 zbi_type: Some(bm.zbi_type),
448 zbi_extra: bm.zbi_extra.or(Some(0)),
449 ..Default::default()
450 });
451 }
452 }
453
454 if !mmio_list.is_empty() {
455 node.mmio = Some(mmio_list);
456 }
457 if !irq_list.is_empty() {
458 node.irq = Some(irq_list);
459 }
460 if !bti_list.is_empty() {
461 node.bti = Some(bti_list);
462 }
463 if !smc_list.is_empty() {
464 node.smc = Some(smc_list);
465 }
466 if !boot_metadata_list.is_empty() {
467 node.boot_metadata = Some(boot_metadata_list);
468 }
469
470 let mut metadata_list = Vec::new();
471
472 if let Some(metadata) = &dev.metadata {
474 for meta in metadata {
475 let data =
476 meta.data.clone().ok_or_else(|| anyhow!("Static metadata missing data"))?;
477 metadata_list.push(fpbus::Metadata {
478 id: meta.id.clone(),
479 data: Some(data),
480 ..Default::default()
481 });
482 }
483 }
484
485 let dev_name = dev.name.as_deref().unwrap_or("");
486 if let Some(meta) = provider_metadata.get(dev_name) {
487 metadata_list.extend(meta.clone());
488 }
489
490 if !metadata_list.is_empty() {
491 node.metadata = Some(metadata_list);
492 }
493
494 let mut resource_parents = Vec::new();
495 let mut generated_keys = HashSet::new();
496 if let Some(aggregates) = &config.aggregates {
497 for (agg_idx, agg) in aggregates.iter().enumerate() {
498 if let Some(resources) = &agg.resources {
499 for res in resources {
500 if res.node.as_deref() == dev.name.as_deref() {
501 let parent_and_key = if agg.service.as_deref()
502 == Some("fuchsia.hardware.platform.device.Service")
503 && dev.compatible.is_some()
504 {
505 None
506 } else {
507 generate_parent_spec_generic(
508 agg.provider.as_deref().unwrap_or(""),
509 get_aggregate_id(
510 agg,
511 config.devices.as_deref().unwrap_or(&[]),
512 agg_idx as u32,
513 ),
514 agg.service.as_deref().unwrap_or(""),
515 res,
516 parser_config,
517 )?
518 };
519 if let Some((parent, key)) = parent_and_key {
520 if generated_keys.insert(key) {
521 resource_parents.push(parent);
522 }
523 }
524 }
525 }
526 }
527 }
528 }
529
530 let mut spec =
531 fdf_framework::CompositeNodeSpec { name: dev.name.clone(), ..Default::default() };
532
533 let mut parents2 = Vec::new();
534
535 if let Some(compatible) = &dev.compatible {
537 let pdev_parent = fdf_framework::ParentSpec2 {
538 bind_rules: vec![
539 make_accept_bind_rule(
540 "fuchsia.BIND_PROTOCOL",
541 property_int(BIND_PROTOCOL_DEVICE),
542 ),
543 make_accept_bind_rule(
544 "fuchsia.BIND_PLATFORM_DEV_VID",
545 property_int(BIND_PLATFORM_DEV_VID_GENERIC),
546 ),
547 make_accept_bind_rule(
548 "fuchsia.BIND_PLATFORM_DEV_DID",
549 property_int(BIND_PLATFORM_DEV_DID_DEVICETREE),
550 ),
551 make_accept_bind_rule(
552 "fuchsia.BIND_PLATFORM_DEV_INSTANCE_ID",
553 property_int(instance_id),
554 ),
555 make_accept_bind_rule(
556 "fuchsia.devicetree.FIRST_COMPATIBLE",
557 property_string(compatible),
558 ),
559 ],
560 properties: vec![
561 make_property2("fuchsia.BIND_PROTOCOL", property_int(BIND_PROTOCOL_DEVICE)),
562 make_property2(
563 "fuchsia.BIND_PLATFORM_DEV_VID",
564 property_int(BIND_PLATFORM_DEV_VID_GENERIC),
565 ),
566 make_property2(
567 "fuchsia.BIND_PLATFORM_DEV_DID",
568 property_int(BIND_PLATFORM_DEV_DID_DEVICETREE),
569 ),
570 make_property2(
571 "fuchsia.BIND_PLATFORM_DEV_INSTANCE_ID",
572 property_int(instance_id),
573 ),
574 make_property2(
575 "fuchsia.devicetree.FIRST_COMPATIBLE",
576 property_string(compatible),
577 ),
578 make_property2(
579 "fuchsia.hardware.platform.device.Service",
580 property_string("fuchsia.hardware.platform.device.Service.ZirconTransport"),
581 ),
582 ],
583 };
584 parents2.push(pdev_parent);
585 }
586
587 parents2.extend(resource_parents);
588
589 if parents2.is_empty() {
590 bail!(
591 "Device {} has no parents (no compatible string and no resource parents)",
592 dev_name
593 );
594 }
595
596 spec.parents2 = Some(parents2);
597
598 log::info!(
599 "DML-CONFIG: Adding composite spec: name={:?}, parents2={:?}",
600 spec.name,
601 spec.parents2
602 );
603 composite_manager
604 .add_spec_with(spec)
605 .await
606 .context("AddSpec request failed")?
607 .map_err(|e| anyhow!("AddSpec failed: {:?}", e))?;
608
609 if dev.compatible.is_some() {
610 log::info!("DML-CONFIG: Adding node: {}", dev_name);
611 pbus.node_add(node)
612 .await
613 .context("NodeAdd request failed")?
614 .context("NodeAdd failed")?;
615 }
616 }
617
618 Ok(())
619}