arch/
fdt.rs

1// Copyright 2023 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use std::collections::BTreeMap;
6use std::collections::BTreeSet;
7use std::fs::File;
8use std::io::Read;
9
10use cros_fdt::apply_overlay;
11use cros_fdt::Error;
12use cros_fdt::Fdt;
13use cros_fdt::FdtNode;
14use cros_fdt::Result;
15use devices::IommuDevType;
16use devices::PlatformBusResources;
17use vm_memory::GuestAddress;
18use vm_memory::GuestMemory;
19use vm_memory::MemoryRegionInformation;
20use vm_memory::MemoryRegionPurpose;
21
22/// Device tree overlay file
23pub struct DtbOverlay {
24    /// Device tree overlay file to apply
25    pub file: File,
26    /// Labels of nodes to include in the final device tree.
27    pub symbol_allowlist: Option<BTreeSet<String>>,
28}
29
30/// Apply multiple device tree overlays to the base FDT.
31#[cfg(not(any(target_os = "android", target_os = "linux")))]
32pub fn apply_device_tree_overlays(
33    fdt: &mut Fdt,
34    overlays: &[DtbOverlay],
35    _devices: &[PlatformBusResources],
36    _phandles: &BTreeMap<&str, u32>,
37) -> Result<()> {
38    for dtbo in overlays {
39        let mut buffer = Vec::new();
40        (&dtbo.file)
41            .read_to_end(&mut buffer)
42            .map_err(Error::FdtIoError)?;
43        let overlay = Fdt::from_blob(buffer.as_slice())?;
44        if let Some(allowlist) = &dtbo.symbol_allowlist {
45            if !allowlist.is_empty() {
46                apply_overlay(fdt, overlay, allowlist)?;
47            }
48        } else {
49            apply_overlay::<&str>(fdt, overlay, [])?;
50        }
51    }
52    Ok(())
53}
54
55#[cfg_attr(not(any(target_os = "android", target_os = "linux")), allow(unused))]
56fn get_iommu_phandle(
57    iommu_type: IommuDevType,
58    id: Option<u32>,
59    phandles: &BTreeMap<&str, u32>,
60) -> Result<u32> {
61    match iommu_type {
62        IommuDevType::NoIommu | IommuDevType::VirtioIommu | IommuDevType::CoIommu => None,
63        IommuDevType::PkvmPviommu => {
64            if let Some(id) = id {
65                phandles.get(format!("pviommu{id}").as_str()).copied()
66            } else {
67                None
68            }
69        }
70    }
71    .ok_or_else(|| Error::MissingIommuPhandle(format!("{iommu_type:?}"), id))
72}
73
74#[cfg_attr(not(any(target_os = "android", target_os = "linux")), allow(unused))]
75fn get_power_domain_phandle(offset: usize, phandles: &BTreeMap<&str, u32>) -> Result<u32> {
76    phandles
77        .get(format!("dev_pd{offset}").as_str())
78        .copied()
79        .ok_or(Error::MissingPowerDomain(offset))
80}
81
82// Find the device node at given path and update its `reg` and `interrupts` properties using
83// its platform resources.
84#[cfg_attr(not(any(target_os = "android", target_os = "linux")), allow(unused))]
85fn patch_fdt_node(
86    node: &mut FdtNode,
87    resources: &PlatformBusResources,
88    phandles: &BTreeMap<&str, u32>,
89    power_domain_count: &mut usize,
90) -> Result<()> {
91    const GIC_FDT_IRQ_TYPE_SPI: u32 = 0;
92    let reg_val: Vec<u64> = resources
93        .regions
94        .iter()
95        .flat_map(|(a, s)| [*a, *s].into_iter())
96        .collect();
97    let irq_val: Vec<u32> = resources
98        .irqs
99        .iter()
100        .flat_map(|(n, f)| [GIC_FDT_IRQ_TYPE_SPI, *n, *f].into_iter())
101        .collect();
102    if !reg_val.is_empty() {
103        node.set_prop("reg", reg_val)?;
104    }
105    if !irq_val.is_empty() {
106        node.set_prop("interrupts", irq_val)?;
107    }
108
109    if !resources.iommus.is_empty() {
110        let mut iommus_val = Vec::new();
111        for (t, id, vsids) in &resources.iommus {
112            let phandle = get_iommu_phandle(*t, *id, phandles)?;
113            for vsid in vsids {
114                iommus_val.push(phandle);
115                iommus_val.push(*vsid);
116            }
117        }
118        node.set_prop("iommus", iommus_val)?;
119    }
120
121    if resources.requires_power_domain {
122        let phandle = get_power_domain_phandle(*power_domain_count, phandles)?;
123        node.set_prop("power-domains", phandle)?;
124        *power_domain_count += 1;
125    }
126
127    Ok(())
128}
129
130/// Apply multiple device tree overlays to the base FDT.
131#[cfg(any(target_os = "android", target_os = "linux"))]
132pub fn apply_device_tree_overlays(
133    fdt: &mut Fdt,
134    overlays: &[DtbOverlay],
135    devices: &[PlatformBusResources],
136    phandles: &BTreeMap<&str, u32>,
137) -> Result<()> {
138    let mut power_domain_count = 0;
139    let mut devices: Vec<&_> = devices.iter().collect();
140    for dtbo in overlays {
141        let mut buffer = Vec::new();
142        (&dtbo.file)
143            .read_to_end(&mut buffer)
144            .map_err(Error::FdtIoError)?;
145        let mut overlay = Fdt::from_blob(buffer.as_slice())?;
146
147        // Find device node paths corresponding to the resources.
148        let mut node_paths = vec![];
149        let devs_in_overlay;
150        (devs_in_overlay, devices) = devices.into_iter().partition(|r| {
151            if let Ok(path) = overlay.symbol_to_path(&r.dt_symbol) {
152                node_paths.push(path);
153                true
154            } else {
155                false
156            }
157        });
158
159        // Update device nodes found in this overlay, and then apply the overlay.
160        for (path, res) in node_paths.into_iter().zip(&devs_in_overlay) {
161            let node = overlay.get_node_mut(path).ok_or_else(|| {
162                Error::InvalidPath(format!(
163                    "cannot find FDT node for dt-symbol {}",
164                    &res.dt_symbol
165                ))
166            })?;
167            patch_fdt_node(node, res, phandles, &mut power_domain_count)?;
168        }
169
170        // Apply overlay, optionally filtered by label allowlist.
171        if let Some(allowlist) = &dtbo.symbol_allowlist {
172            if !allowlist.is_empty() {
173                apply_overlay(fdt, overlay, allowlist)?;
174            }
175        } else {
176            apply_overlay::<&str>(fdt, overlay, [])?;
177        }
178    }
179
180    if devices.is_empty() {
181        Ok(())
182    } else {
183        Err(Error::ApplyOverlayError(format!(
184            "labels {:#?} not found in overlay files",
185            devices.iter().map(|r| &r.dt_symbol).collect::<Vec<_>>()
186        )))
187    }
188}
189
190/// Create a "/memory" node describing all guest memory regions.
191pub fn create_memory_node(fdt: &mut Fdt, guest_mem: &GuestMemory) -> Result<()> {
192    let mut mem_reg_prop = Vec::new();
193    let mut previous_memory_region_end = None;
194    let mut regions: Vec<MemoryRegionInformation> = guest_mem
195        .regions()
196        .filter(|region| match region.options.purpose {
197            MemoryRegionPurpose::Bios => false,
198            MemoryRegionPurpose::GuestMemoryRegion => true,
199            MemoryRegionPurpose::ProtectedFirmwareRegion => true,
200            MemoryRegionPurpose::ReservedMemory => true,
201            #[cfg(target_arch = "aarch64")]
202            MemoryRegionPurpose::StaticSwiotlbRegion => true,
203        })
204        .collect();
205    regions.sort_by(|a, b| a.guest_addr.cmp(&b.guest_addr));
206    for region in regions {
207        // Merge with the previous region if possible.
208        if let Some(previous_end) = previous_memory_region_end {
209            if region.guest_addr == previous_end {
210                *mem_reg_prop.last_mut().unwrap() += region.size as u64;
211                previous_memory_region_end =
212                    Some(previous_end.checked_add(region.size as u64).unwrap());
213                continue;
214            }
215            assert!(region.guest_addr > previous_end, "Memory regions overlap");
216        }
217
218        mem_reg_prop.push(region.guest_addr.offset());
219        mem_reg_prop.push(region.size as u64);
220        previous_memory_region_end =
221            Some(region.guest_addr.checked_add(region.size as u64).unwrap());
222    }
223
224    let memory_node = fdt.root_mut().subnode_mut("memory")?;
225    memory_node.set_prop("device_type", "memory")?;
226    memory_node.set_prop("reg", mem_reg_prop)?;
227    Ok(())
228}
229
230pub struct ReservedMemoryRegion<'a> {
231    pub name: &'a str,
232    pub address: Option<GuestAddress>,
233    pub size: u64,
234    pub phandle: Option<u32>,
235    pub compatible: Option<&'a str>,
236    pub alignment: Option<u64>,
237    pub no_map: bool,
238}
239
240/// Create a "/reserved-memory" node with child nodes for `reserved_regions`.
241pub fn create_reserved_memory_node(
242    fdt: &mut Fdt,
243    reserved_regions: &[ReservedMemoryRegion],
244) -> Result<()> {
245    if reserved_regions.is_empty() {
246        return Ok(());
247    }
248
249    let resv_memory_node = fdt.root_mut().subnode_mut("reserved-memory")?;
250    resv_memory_node.set_prop("#address-cells", 0x2u32)?;
251    resv_memory_node.set_prop("#size-cells", 0x2u32)?;
252    resv_memory_node.set_prop("ranges", ())?;
253
254    for region in reserved_regions {
255        let child_node = if let Some(resv_addr) = region.address {
256            let node =
257                resv_memory_node.subnode_mut(&format!("{}@{:x}", region.name, resv_addr.0))?;
258            node.set_prop("reg", &[resv_addr.0, region.size])?;
259            node
260        } else {
261            let node = resv_memory_node.subnode_mut(region.name)?;
262            node.set_prop("size", region.size)?;
263            node
264        };
265
266        if let Some(phandle) = region.phandle {
267            child_node.set_prop("phandle", phandle)?;
268        }
269        if let Some(compatible) = region.compatible {
270            child_node.set_prop("compatible", compatible)?;
271        }
272        if let Some(alignment) = region.alignment {
273            child_node.set_prop("alignment", alignment)?;
274        }
275        if region.no_map {
276            child_node.set_prop("no-map", ())?;
277        }
278    }
279
280    Ok(())
281}
282
283/// Collect a list of `ReservedMemoryRegion`s for any `MemoryRegionPurpose::ReservedMemory` regions
284/// in `GuestMemory`.
285pub fn reserved_memory_regions_from_guest_mem(
286    guest_mem: &GuestMemory,
287) -> Vec<ReservedMemoryRegion> {
288    guest_mem
289        .regions()
290        .filter(|region| region.options.purpose == MemoryRegionPurpose::ReservedMemory)
291        .map(|region| ReservedMemoryRegion {
292            address: Some(region.guest_addr),
293            size: region.size.try_into().unwrap(),
294            name: "reserved",
295            phandle: None,
296            compatible: None,
297            alignment: None,
298            no_map: true,
299        })
300        .collect()
301}
302
303#[cfg(test)]
304mod tests {
305    use super::*;
306
307    #[test]
308    fn test_patch_fdt_node() {
309        let mut fdt = Fdt::new(&[]);
310        let root = fdt.root_mut();
311
312        let resources = PlatformBusResources {
313            dt_symbol: "test".to_string(),
314            regions: vec![(0x1000, 0x100)],
315            irqs: vec![(5, 1)],
316            iommus: vec![(IommuDevType::PkvmPviommu, Some(1), vec![10])],
317            requires_power_domain: true,
318        };
319
320        let mut phandles = BTreeMap::new();
321        phandles.insert("dev_pd0", 42);
322        phandles.insert("pviommu1", 43);
323
324        let mut power_domain_count = 0;
325
326        patch_fdt_node(root, &resources, &phandles, &mut power_domain_count).unwrap();
327
328        assert_eq!(root.get_prop("reg"), Some(vec![0x1000u64, 0x100]));
329        assert_eq!(root.get_prop("interrupts"), Some(vec![0u32, 5, 1]));
330        assert_eq!(root.get_prop("power-domains"), Some(vec![42u32]));
331        assert_eq!(root.get_prop("iommus"), Some(vec![43u32, 10]));
332        assert_eq!(power_domain_count, 1);
333    }
334
335    #[test]
336    fn test_patch_fdt_node_power_domain_offsets() {
337        let mut fdt = Fdt::new(&[]);
338        let root = fdt.root_mut();
339
340        let resources = PlatformBusResources {
341            dt_symbol: "test".to_string(),
342            regions: vec![],
343            irqs: vec![],
344            iommus: vec![],
345            requires_power_domain: true,
346        };
347
348        let mut phandles = BTreeMap::new();
349        phandles.insert("dev_pd1", 42);
350
351        let mut power_domain_count = 1;
352
353        patch_fdt_node(root, &resources, &phandles, &mut power_domain_count).unwrap();
354
355        assert_eq!(root.get_prop("power-domains"), Some(vec![42u32]));
356        assert_eq!(power_domain_count, 2);
357    }
358
359    #[test]
360    fn test_patch_fdt_node_multiple_properties() {
361        let mut fdt = Fdt::new(&[]);
362        let root = fdt.root_mut();
363
364        let resources = PlatformBusResources {
365            dt_symbol: "test".to_string(),
366            regions: vec![(0x1000, 0x100), (0x2000, 0x200)],
367            irqs: vec![(5, 1), (6, 2)],
368            iommus: vec![
369                (IommuDevType::PkvmPviommu, Some(1), vec![10]),
370                (IommuDevType::PkvmPviommu, Some(2), vec![20]),
371            ],
372            requires_power_domain: false,
373        };
374
375        let mut phandles = BTreeMap::new();
376        phandles.insert("pviommu1", 42);
377        phandles.insert("pviommu2", 43);
378
379        let mut power_domain_count = 0;
380
381        patch_fdt_node(root, &resources, &phandles, &mut power_domain_count).unwrap();
382
383        assert_eq!(
384            root.get_prop("reg"),
385            Some(vec![0x1000u64, 0x100, 0x2000, 0x200])
386        );
387        assert_eq!(root.get_prop("interrupts"), Some(vec![0u32, 5, 1, 0, 6, 2]));
388        assert_eq!(root.get_prop("iommus"), Some(vec![42u32, 10, 43, 20]));
389    }
390
391    #[test]
392    fn test_patch_fdt_node_non_root() {
393        let mut fdt = Fdt::new(&[]);
394        let root = fdt.root_mut();
395        let subnode = root.subnode_mut("subnode").unwrap();
396
397        let resources = PlatformBusResources {
398            dt_symbol: "test".to_string(),
399            regions: vec![(0x1000, 0x100)],
400            irqs: vec![],
401            iommus: vec![],
402            requires_power_domain: false,
403        };
404
405        let phandles = BTreeMap::new();
406        let mut power_domain_count = 0;
407
408        patch_fdt_node(subnode, &resources, &phandles, &mut power_domain_count).unwrap();
409
410        assert_eq!(subnode.get_prop("reg"), Some(vec![0x1000u64, 0x100]));
411    }
412
413    #[test]
414    fn test_patch_fdt_node_non_pkvm_iommu() {
415        let mut fdt = Fdt::new(&[]);
416        let root = fdt.root_mut();
417
418        let resources = PlatformBusResources {
419            dt_symbol: "test".to_string(),
420            regions: vec![],
421            irqs: vec![],
422            iommus: vec![(IommuDevType::VirtioIommu, Some(1), vec![10])],
423            requires_power_domain: false,
424        };
425
426        let phandles = BTreeMap::new();
427        let mut power_domain_count = 0;
428
429        let result = patch_fdt_node(root, &resources, &phandles, &mut power_domain_count);
430        assert!(matches!(
431            result,
432            Err(Error::MissingIommuPhandle(msg, id)) if msg == "VirtioIommu" && id == Some(1)
433        ));
434    }
435
436    #[test]
437    fn test_patch_fdt_node_missing_phandle() {
438        let mut fdt = Fdt::new(&[]);
439        let root = fdt.root_mut();
440
441        let resources = PlatformBusResources {
442            dt_symbol: "test".to_string(),
443            regions: vec![],
444            irqs: vec![],
445            iommus: vec![(IommuDevType::PkvmPviommu, Some(1), vec![10])],
446            requires_power_domain: false,
447        };
448
449        let phandles = BTreeMap::new();
450        let mut power_domain_count = 0;
451
452        let result = patch_fdt_node(root, &resources, &phandles, &mut power_domain_count);
453        assert!(matches!(
454            result,
455            Err(Error::MissingIommuPhandle(msg, id)) if msg == "PkvmPviommu" && id == Some(1)
456        ));
457    }
458}