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
5#[cfg(any(target_os = "android", target_os = "linux"))]
6use std::collections::BTreeMap;
7use std::fs::File;
8use std::io::Read;
9
10use cros_fdt::apply_overlay;
11use cros_fdt::Error;
12use cros_fdt::Fdt;
13#[cfg(any(target_os = "android", target_os = "linux"))]
14use cros_fdt::Path;
15use cros_fdt::Result;
16#[cfg(any(target_os = "android", target_os = "linux"))]
17use devices::IommuDevType;
18use vm_memory::GuestAddress;
19use vm_memory::GuestMemory;
20use vm_memory::MemoryRegionInformation;
21use vm_memory::MemoryRegionPurpose;
22
23#[cfg(any(target_os = "android", target_os = "linux"))]
24use crate::sys::linux::PlatformBusResources;
25
26/// Device tree overlay file
27pub struct DtbOverlay {
28    /// Device tree overlay file to apply
29    pub file: File,
30    /// Whether to filter out nodes that do not belong to assigned VFIO devices.
31    #[cfg(any(target_os = "android", target_os = "linux"))]
32    pub do_filter: bool,
33}
34
35/// Apply multiple device tree overlays to the base FDT.
36#[cfg(not(any(target_os = "android", target_os = "linux")))]
37pub fn apply_device_tree_overlays(fdt: &mut Fdt, overlays: Vec<DtbOverlay>) -> Result<()> {
38    for mut 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        apply_overlay::<&str>(fdt, overlay, [])?;
45    }
46    Ok(())
47}
48
49#[cfg(any(target_os = "android", target_os = "linux"))]
50fn get_iommu_phandle(
51    iommu_type: IommuDevType,
52    id: Option<u32>,
53    phandles: &BTreeMap<&str, u32>,
54) -> Result<u32> {
55    match iommu_type {
56        IommuDevType::NoIommu | IommuDevType::VirtioIommu | IommuDevType::CoIommu => None,
57        IommuDevType::PkvmPviommu => {
58            if let Some(id) = id {
59                phandles.get(format!("pviommu{id}").as_str()).copied()
60            } else {
61                None
62            }
63        }
64    }
65    .ok_or_else(|| Error::MissingIommuPhandle(format!("{iommu_type:?}"), id))
66}
67
68#[cfg(any(target_os = "android", target_os = "linux"))]
69fn get_power_domain_phandle(offset: usize, phandles: &BTreeMap<&str, u32>) -> Result<u32> {
70    phandles
71        .get(format!("dev_pd{offset}").as_str())
72        .copied()
73        .ok_or(Error::MissingPowerDomain(offset))
74}
75
76// Find the device node at given path and update its `reg` and `interrupts` properties using
77// its platform resources.
78#[cfg(any(target_os = "android", target_os = "linux"))]
79fn update_device_nodes(
80    node_path: Path,
81    fdt: &mut Fdt,
82    resources: &PlatformBusResources,
83    phandles: &BTreeMap<&str, u32>,
84    power_domain_count: &mut usize,
85) -> Result<()> {
86    const GIC_FDT_IRQ_TYPE_SPI: u32 = 0;
87
88    let node = fdt.get_node_mut(node_path).ok_or_else(|| {
89        Error::InvalidPath(format!(
90            "cannot find FDT node for dt-symbol {}",
91            &resources.dt_symbol
92        ))
93    })?;
94    let reg_val: Vec<u64> = resources
95        .regions
96        .iter()
97        .flat_map(|(a, s)| [*a, *s].into_iter())
98        .collect();
99    let irq_val: Vec<u32> = resources
100        .irqs
101        .iter()
102        .flat_map(|(n, f)| [GIC_FDT_IRQ_TYPE_SPI, *n, *f].into_iter())
103        .collect();
104    if !reg_val.is_empty() {
105        node.set_prop("reg", reg_val)?;
106    }
107    if !irq_val.is_empty() {
108        node.set_prop("interrupts", irq_val)?;
109    }
110
111    if !resources.iommus.is_empty() {
112        let mut iommus_val = Vec::new();
113        for (t, id, vsids) in &resources.iommus {
114            let phandle = get_iommu_phandle(*t, *id, phandles)?;
115            for vsid in vsids {
116                iommus_val.push(phandle);
117                iommus_val.push(*vsid);
118            }
119        }
120        node.set_prop("iommus", iommus_val)?;
121    }
122
123    if resources.requires_power_domain {
124        let phandle = get_power_domain_phandle(*power_domain_count, phandles)?;
125        node.set_prop("power-domains", phandle)?;
126        *power_domain_count += 1;
127    }
128
129    Ok(())
130}
131
132/// Apply multiple device tree overlays to the base FDT.
133///
134/// # Arguments
135///
136/// * `fdt` - The base FDT
137/// * `overlays` - A vector of overlay files to apply
138/// * `devices` - A vector of device resource descriptors to amend the overlay nodes with
139#[cfg(any(target_os = "android", target_os = "linux"))]
140pub fn apply_device_tree_overlays(
141    fdt: &mut Fdt,
142    overlays: Vec<DtbOverlay>,
143    mut devices: Vec<PlatformBusResources>,
144    phandles: &BTreeMap<&str, u32>,
145) -> Result<()> {
146    for mut dtbo in overlays {
147        let mut buffer = Vec::new();
148        dtbo.file
149            .read_to_end(&mut buffer)
150            .map_err(Error::FdtIoError)?;
151        let mut overlay = Fdt::from_blob(buffer.as_slice())?;
152
153        // Find device node paths corresponding to the resources.
154        let mut node_paths = vec![];
155        let devs_in_overlay;
156        (devs_in_overlay, devices) = devices.into_iter().partition(|r| {
157            if let Ok(path) = overlay.symbol_to_path(&r.dt_symbol) {
158                node_paths.push(path);
159                true
160            } else {
161                false
162            }
163        });
164
165        let mut power_domain_count = 0;
166        // Update device nodes found in this overlay, and then apply the overlay.
167        for (path, res) in node_paths.into_iter().zip(&devs_in_overlay) {
168            update_device_nodes(path, &mut overlay, res, phandles, &mut power_domain_count)?;
169        }
170
171        // Unfiltered DTBOs applied as whole.
172        if !dtbo.do_filter {
173            apply_overlay::<&str>(fdt, overlay, [])?;
174        } else if !devs_in_overlay.is_empty() {
175            apply_overlay(fdt, overlay, devs_in_overlay.iter().map(|r| &r.dt_symbol))?;
176        }
177    }
178
179    if devices.is_empty() {
180        Ok(())
181    } else {
182        Err(Error::ApplyOverlayError(format!(
183            "labels {:#?} not found in overlay files",
184            devices.iter().map(|r| &r.dt_symbol).collect::<Vec<_>>()
185        )))
186    }
187}
188
189/// Create a "/memory" node describing all guest memory regions.
190pub fn create_memory_node(fdt: &mut Fdt, guest_mem: &GuestMemory) -> Result<()> {
191    let mut mem_reg_prop = Vec::new();
192    let mut previous_memory_region_end = None;
193    let mut regions: Vec<MemoryRegionInformation> = guest_mem
194        .regions()
195        .filter(|region| match region.options.purpose {
196            MemoryRegionPurpose::Bios => false,
197            MemoryRegionPurpose::GuestMemoryRegion => true,
198            MemoryRegionPurpose::ProtectedFirmwareRegion => true,
199            MemoryRegionPurpose::ReservedMemory => true,
200            #[cfg(target_arch = "aarch64")]
201            MemoryRegionPurpose::StaticSwiotlbRegion => true,
202        })
203        .collect();
204    regions.sort_by(|a, b| a.guest_addr.cmp(&b.guest_addr));
205    for region in regions {
206        // Merge with the previous region if possible.
207        if let Some(previous_end) = previous_memory_region_end {
208            if region.guest_addr == previous_end {
209                *mem_reg_prop.last_mut().unwrap() += region.size as u64;
210                previous_memory_region_end =
211                    Some(previous_end.checked_add(region.size as u64).unwrap());
212                continue;
213            }
214            assert!(region.guest_addr > previous_end, "Memory regions overlap");
215        }
216
217        mem_reg_prop.push(region.guest_addr.offset());
218        mem_reg_prop.push(region.size as u64);
219        previous_memory_region_end =
220            Some(region.guest_addr.checked_add(region.size as u64).unwrap());
221    }
222
223    let memory_node = fdt.root_mut().subnode_mut("memory")?;
224    memory_node.set_prop("device_type", "memory")?;
225    memory_node.set_prop("reg", mem_reg_prop)?;
226    Ok(())
227}
228
229pub struct ReservedMemoryRegion<'a> {
230    pub name: &'a str,
231    pub address: Option<GuestAddress>,
232    pub size: u64,
233    pub phandle: Option<u32>,
234    pub compatible: Option<&'a str>,
235    pub alignment: Option<u64>,
236    pub no_map: bool,
237}
238
239/// Create a "/reserved-memory" node with child nodes for `reserved_regions`.
240pub fn create_reserved_memory_node(
241    fdt: &mut Fdt,
242    reserved_regions: &[ReservedMemoryRegion],
243) -> Result<()> {
244    if reserved_regions.is_empty() {
245        return Ok(());
246    }
247
248    let resv_memory_node = fdt.root_mut().subnode_mut("reserved-memory")?;
249    resv_memory_node.set_prop("#address-cells", 0x2u32)?;
250    resv_memory_node.set_prop("#size-cells", 0x2u32)?;
251    resv_memory_node.set_prop("ranges", ())?;
252
253    for region in reserved_regions {
254        let child_node = if let Some(resv_addr) = region.address {
255            let node =
256                resv_memory_node.subnode_mut(&format!("{}@{:x}", region.name, resv_addr.0))?;
257            node.set_prop("reg", &[resv_addr.0, region.size])?;
258            node
259        } else {
260            let node = resv_memory_node.subnode_mut(region.name)?;
261            node.set_prop("size", region.size)?;
262            node
263        };
264
265        if let Some(phandle) = region.phandle {
266            child_node.set_prop("phandle", phandle)?;
267        }
268        if let Some(compatible) = region.compatible {
269            child_node.set_prop("compatible", compatible)?;
270        }
271        if let Some(alignment) = region.alignment {
272            child_node.set_prop("alignment", alignment)?;
273        }
274        if region.no_map {
275            child_node.set_prop("no-map", ())?;
276        }
277    }
278
279    Ok(())
280}
281
282/// Collect a list of `ReservedMemoryRegion`s for any `MemoryRegionPurpose::ReservedMemory` regions
283/// in `GuestMemory`.
284pub fn reserved_memory_regions_from_guest_mem(
285    guest_mem: &GuestMemory,
286) -> Vec<ReservedMemoryRegion> {
287    guest_mem
288        .regions()
289        .filter(|region| region.options.purpose == MemoryRegionPurpose::ReservedMemory)
290        .map(|region| ReservedMemoryRegion {
291            address: Some(region.guest_addr),
292            size: region.size.try_into().unwrap(),
293            name: "reserved",
294            phandle: None,
295            compatible: None,
296            alignment: None,
297            no_map: true,
298        })
299        .collect()
300}