Skip to main content

dml_config/
lib.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 anyhow::Context;
6use fidl_fuchsia_board_dml_config as fbdc;
7use fidl_fuchsia_driver_metadata as fdr;
8
9// Re-export FIDL types for convenience
10pub use fbdc::{
11    AggregateEntry, ArmSmmu, BoardConfig, Device, Iommu, IommuType, ResourceEntry, StaticMetadata,
12    StubIommu,
13};
14
15#[derive(Debug, Clone, Default)]
16pub struct Mmio {
17    pub name: Option<String>,
18    pub base: u64,
19    pub length: u64,
20}
21
22#[derive(Debug, Clone, Default)]
23pub struct Irq {
24    pub name: Option<String>,
25    pub number: u32,
26    pub mode: Option<String>,
27    pub wake_vector: Option<bool>,
28    pub controller: Option<u32>,
29}
30
31#[derive(Debug, Clone, Default)]
32pub struct Bti {
33    pub id: u32,
34    pub name: Option<String>,
35    pub iommu_id: u32,
36}
37
38#[derive(Debug, Clone, Default)]
39pub struct Smc {
40    pub service_call_num_base: u32,
41    pub count: u32,
42    pub exclusive: bool,
43    pub name: Option<String>,
44}
45
46#[derive(Debug, Clone, Default)]
47pub struct BootMetadata {
48    pub zbi_type: u32,
49    pub zbi_extra: Option<u32>,
50}
51
52// Dictionary Lookup Helpers
53pub fn find_value<'a>(dict: &'a fdr::Dictionary, key: &str) -> Option<&'a fdr::DictionaryValue> {
54    dict.entries.as_ref()?.iter().find(|e| e.key == key).map(|e| &e.value)
55}
56
57pub fn get_int64(dict: &fdr::Dictionary, key: &str) -> Option<i64> {
58    match find_value(dict, key)? {
59        fdr::DictionaryValue::Int64(i) => Some(*i),
60        _ => None,
61    }
62}
63
64pub fn get_uint32(dict: &fdr::Dictionary, key: &str) -> Option<u32> {
65    get_int64(dict, key).and_then(|i| u32::try_from(i).ok())
66}
67
68pub fn get_uint64(dict: &fdr::Dictionary, key: &str) -> Option<u64> {
69    get_int64(dict, key).map(|i| i as u64)
70}
71
72pub fn get_string(dict: &fdr::Dictionary, key: &str) -> Option<String> {
73    match find_value(dict, key)? {
74        fdr::DictionaryValue::Str(s) => Some(s.clone()),
75        _ => None,
76    }
77}
78
79pub fn get_bool(dict: &fdr::Dictionary, key: &str) -> Option<bool> {
80    match find_value(dict, key)? {
81        fdr::DictionaryValue::Boolean(b) => Some(*b),
82        _ => None,
83    }
84}
85
86// Resource Parsing Helpers
87pub fn mmio_list(dict: &fdr::Dictionary) -> Vec<Mmio> {
88    let mut list = Vec::new();
89    for i in 0.. {
90        let prefix = format!("mmio.{}", i);
91        let base = get_uint64(dict, &format!("{}.base", prefix))
92            .or_else(|| get_uint64(dict, &format!("{}.address", prefix)));
93        if let Some(base) = base {
94            let length = get_uint64(dict, &format!("{}.length", prefix))
95                .or_else(|| get_uint64(dict, &format!("{}.size", prefix)))
96                .unwrap_or(0);
97            let name = get_string(dict, &format!("{}.name", prefix));
98            list.push(Mmio { name, base, length });
99        } else {
100            break;
101        }
102    }
103    list
104}
105
106pub fn irq_list(dict: &fdr::Dictionary) -> Vec<Irq> {
107    let mut list = Vec::new();
108    for i in 0.. {
109        let prefix = format!("interrupts.{}", i);
110        if let Some(number) = get_uint32(dict, &format!("{}.number", prefix)) {
111            let name = get_string(dict, &format!("{}.name", prefix));
112            let mode = get_string(dict, &format!("{}.mode", prefix));
113            let wake_vector = get_bool(dict, &format!("{}.wake_vector", prefix));
114            let controller = get_uint32(dict, &format!("{}.controller", prefix));
115            list.push(Irq { name, number, mode, wake_vector, controller });
116        } else {
117            break;
118        }
119    }
120    list
121}
122
123pub fn bti_list(dict: &fdr::Dictionary) -> anyhow::Result<Vec<Bti>> {
124    let mut list = Vec::new();
125    let entries = dict.entries.as_deref().unwrap_or(&[]);
126    for i in 0.. {
127        let prefix = format!("btis.{i}.");
128        if !entries.iter().any(|e| e.key.starts_with(&prefix)) {
129            break;
130        }
131
132        let id = get_uint32(dict, &format!("btis.{i}.id"))
133            .with_context(|| format!("BTI at index {i} is missing required \"id\""))?;
134        let iommu_id = get_uint32(dict, &format!("btis.{i}.iommu_id"))
135            .with_context(|| format!("BTI with ID {id} at index {i} is missing \"iommu_id\""))?;
136        let name = get_string(dict, &format!("btis.{i}.name"));
137        list.push(Bti { id, name, iommu_id });
138    }
139    Ok(list)
140}
141
142pub fn smc_list(dict: &fdr::Dictionary) -> Vec<Smc> {
143    let mut list = Vec::new();
144    for i in 0.. {
145        let prefix = format!("smcs.{}", i);
146        if let Some(service_call_num_base) =
147            get_uint32(dict, &format!("{}.service_call_num_base", prefix))
148        {
149            let count = get_uint32(dict, &format!("{}.count", prefix)).unwrap_or(0);
150            let exclusive = get_bool(dict, &format!("{}.exclusive", prefix)).unwrap_or(false);
151            let name = get_string(dict, &format!("{}.name", prefix));
152            list.push(Smc { service_call_num_base, count, exclusive, name });
153        } else {
154            break;
155        }
156    }
157    list
158}
159
160pub fn boot_metadata_list(dict: &fdr::Dictionary) -> Vec<BootMetadata> {
161    let mut list = Vec::new();
162    for i in 0.. {
163        let prefix = format!("boot_metadata.{}", i);
164        if let Some(zbi_type) = get_uint32(dict, &format!("{}.zbi_type", prefix)) {
165            let zbi_extra = get_uint32(dict, &format!("{}.zbi_extra", prefix));
166            list.push(BootMetadata { zbi_type, zbi_extra });
167        } else {
168            break;
169        }
170    }
171    list
172}
173
174pub fn pdev_constraints<'a>(
175    config: &'a BoardConfig,
176    node_name: &str,
177) -> Option<&'a fdr::Dictionary> {
178    config
179        .aggregates
180        .as_ref()?
181        .iter()
182        .find(|agg| {
183            agg.provider.as_deref() == Some("pdev")
184                && agg.service.as_deref() == Some("fuchsia.hardware.platform.device.Service")
185        })
186        .and_then(|agg| {
187            agg.resources
188                .as_ref()?
189                .iter()
190                .find(|res| res.node.as_deref() == Some(node_name))
191                .and_then(|res| res.constraint.as_ref())
192        })
193}
194
195pub fn is_node_force_enabled(dev_name: &str, enabled_nodes: &[String]) -> bool {
196    enabled_nodes.iter().any(|n| {
197        n == dev_name
198            || n.trim_start_matches('/').replace('@', "-") == dev_name
199            || n.rsplit('/').next().unwrap_or(n).replace('@', "-") == dev_name
200    })
201}
202
203pub fn is_device_disabled(dev: &Device, enabled_nodes: &[String]) -> bool {
204    if !dev.disabled.unwrap_or(false) {
205        return false;
206    }
207    let name = dev.name.as_deref().unwrap_or("");
208    !is_node_force_enabled(name, enabled_nodes)
209}
210
211#[cfg(target_os = "fuchsia")]
212pub use board_structured_config::Config as StructuredConfig;
213
214#[cfg(target_os = "fuchsia")]
215pub mod parser;
216
217#[cfg(test)]
218mod tests {
219    use super::*;
220
221    #[test]
222    fn test_is_device_disabled_and_runtime_override() {
223        let dev_enabled = Device { name: Some("dev_a".to_string()), ..Default::default() };
224        let dev_explicit_enabled =
225            Device { name: Some("dev_b".to_string()), disabled: Some(false), ..Default::default() };
226        let dev_disabled = Device {
227            name: Some("pcie-c500000".to_string()),
228            disabled: Some(true),
229            ..Default::default()
230        };
231
232        assert!(!is_device_disabled(&dev_enabled, &[]));
233        assert!(!is_device_disabled(&dev_explicit_enabled, &[]));
234        assert!(is_device_disabled(&dev_disabled, &[]));
235
236        // Runtime override by exact DML node name
237        assert!(!is_device_disabled(&dev_disabled, &["pcie-c500000".to_string()]));
238
239        // Runtime override by devicetree path format (e.g. "/pcie@c500000")
240        assert!(!is_device_disabled(&dev_disabled, &["/pcie@c500000".to_string()]));
241
242        // Unrelated enabled_nodes entry leaves it disabled
243        assert!(is_device_disabled(&dev_disabled, &["other-node".to_string()]));
244    }
245
246    #[test]
247    fn test_bti_list() {
248        let dict = fdr::Dictionary {
249            entries: Some(vec![
250                fdr::DictionaryEntry {
251                    key: "btis.0.id".to_string(),
252                    value: fdr::DictionaryValue::Int64(1),
253                },
254                fdr::DictionaryEntry {
255                    key: "btis.0.iommu_id".to_string(),
256                    value: fdr::DictionaryValue::Int64(10),
257                },
258                fdr::DictionaryEntry {
259                    key: "btis.0.name".to_string(),
260                    value: fdr::DictionaryValue::Str("dma_bti".to_string()),
261                },
262                fdr::DictionaryEntry {
263                    key: "btis.1.id".to_string(),
264                    value: fdr::DictionaryValue::Int64(2),
265                },
266                fdr::DictionaryEntry {
267                    key: "btis.1.iommu_id".to_string(),
268                    value: fdr::DictionaryValue::Int64(0),
269                },
270            ]),
271            ..Default::default()
272        };
273
274        let btis = bti_list(&dict).unwrap();
275        assert_eq!(btis.len(), 2);
276        assert_eq!(btis[0].id, 1);
277        assert_eq!(btis[0].name.as_deref(), Some("dma_bti"));
278        assert_eq!(btis[0].iommu_id, 10);
279        assert_eq!(btis[1].id, 2);
280        assert_eq!(btis[1].name, None);
281        assert_eq!(btis[1].iommu_id, 0);
282
283        let dict_missing_iommu = fdr::Dictionary {
284            entries: Some(vec![fdr::DictionaryEntry {
285                key: "btis.0.id".to_string(),
286                value: fdr::DictionaryValue::Int64(1),
287            }]),
288            ..Default::default()
289        };
290        assert!(bti_list(&dict_missing_iommu).is_err());
291
292        let dict_missing_id = fdr::Dictionary {
293            entries: Some(vec![
294                fdr::DictionaryEntry {
295                    key: "btis.0.name".to_string(),
296                    value: fdr::DictionaryValue::Str("dma_bti".to_string()),
297                },
298                fdr::DictionaryEntry {
299                    key: "btis.0.iommu_id".to_string(),
300                    value: fdr::DictionaryValue::Int64(10),
301                },
302            ]),
303            ..Default::default()
304        };
305        assert!(bti_list(&dict_missing_id).is_err());
306    }
307}