1use 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
22pub struct DtbOverlay {
24 pub file: File,
26 pub symbol_allowlist: Option<BTreeSet<String>>,
28}
29
30#[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#[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#[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 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 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 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
190pub 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 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
240pub 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
283pub 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}