pvmfw: Handle dependent nodes in VM DTBO
Test: atest libpvmfw.device_assignment.test
Bug: 317830919
Change-Id: I961519f5d89c043e3085853dda61046e0ad90848
diff --git a/pvmfw/Android.bp b/pvmfw/Android.bp
index cce0e73..4ee02c1 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -74,16 +74,19 @@
srcs: ["src/device_assignment.rs"],
defaults: ["libpvmfw.test.defaults"],
rustlibs: [
+ "libdts",
"libhyp",
"liblibfdt",
"liblog_rust",
"libpvmfw_fdt_template",
+ "libzerocopy",
],
data: [
":test_pvmfw_devices_vm_dtbo",
":test_pvmfw_devices_vm_dtbo_without_symbols",
":test_pvmfw_devices_vm_dtbo_with_duplicated_iommus",
":test_pvmfw_devices_overlapping_pvmfw",
+ ":test_pvmfw_devices_vm_dtbo_with_dependencies",
":test_pvmfw_devices_with_rng",
":test_pvmfw_devices_with_multiple_devices_iommus",
":test_pvmfw_devices_with_iommu_sharing",
@@ -92,7 +95,13 @@
":test_pvmfw_devices_without_iommus",
":test_pvmfw_devices_with_duplicated_pviommus",
":test_pvmfw_devices_with_multiple_reg_iommus",
+ ":test_pvmfw_devices_with_dependency",
+ ":test_pvmfw_devices_with_dependency_loop",
+ ":test_pvmfw_devices_with_multiple_dependencies",
+ ":test_pvmfw_devices_expected_dt",
],
+ data_bins: ["dtc_static"],
+ compile_multilib: "first",
// To use libpvmfw_fdt_template for testing
enabled: false,
target: {
@@ -136,6 +145,14 @@
out: ["test_pvmfw_devices_vm_dtbo_with_duplicated_iommus.dtbo"],
}
+genrule {
+ name: "test_pvmfw_devices_vm_dtbo_with_dependencies",
+ tools: ["dtc"],
+ cmd: "$(location dtc) -@ -I dts -O dtb $(in) -o $(out)",
+ srcs: ["testdata/test_pvmfw_devices_vm_dtbo_with_dependencies.dts"],
+ out: ["test_pvmfw_devices_vm_dtbo_with_dependencies.dtbo"],
+}
+
genrule_defaults {
name: "test_device_assignment_dts_to_dtb",
defaults: ["dts_to_dtb"],
@@ -205,6 +222,53 @@
out: ["test_pvmfw_devices_with_multiple_reg_iommus.dtb"],
}
+genrule {
+ name: "test_pvmfw_devices_with_dependency",
+ defaults: ["test_device_assignment_dts_to_dtb"],
+ srcs: ["testdata/test_pvmfw_devices_with_dependency.dts"],
+ out: ["test_pvmfw_devices_with_dependency.dtb"],
+}
+
+genrule {
+ name: "test_pvmfw_devices_with_multiple_dependencies",
+ defaults: ["test_device_assignment_dts_to_dtb"],
+ srcs: ["testdata/test_pvmfw_devices_with_multiple_dependencies.dts"],
+ out: ["test_pvmfw_devices_with_multiple_dependencies.dtb"],
+}
+
+genrule {
+ name: "test_pvmfw_devices_with_dependency_loop",
+ defaults: ["test_device_assignment_dts_to_dtb"],
+ srcs: ["testdata/test_pvmfw_devices_with_dependency_loop.dts"],
+ out: ["test_pvmfw_devices_with_dependency_loop.dtb"],
+}
+
+// We can't use genrule because preprocessed platform DT is built with cc_object.
+// cc_genrule doesn't support default, so we'll build all expected DTs in
+// a single build rule.
+cc_genrule {
+ name: "test_pvmfw_devices_expected_dt",
+ srcs: [
+ ":pvmfw_platform.dts.preprocessed",
+ "testdata/expected_dt_with_dependency.dts",
+ "testdata/expected_dt_with_multiple_dependencies.dts",
+ "testdata/expected_dt_with_dependency_loop.dts",
+ ],
+ out: [
+ "expected_dt_with_dependency.dtb",
+ "expected_dt_with_multiple_dependencies.dtb",
+ "expected_dt_with_dependency_loop.dtb",
+ ],
+ tools: ["dtc"],
+ cmd: "FILES=($(in));" +
+ "cp $${FILES[0]} $(genDir)/platform_preprocessed.dts;" +
+ "for DTS in $${FILES[@]:1}; do" +
+ " DTB=$$(basename -s .dts $${DTS}).dtb;" +
+ " $(location dtc) -@ -i $(genDir) -I dts -O dtb $${DTS} -o $(genDir)/$${DTB};" +
+ "done",
+ visibility: ["//visibility:private"],
+}
+
cc_binary {
name: "pvmfw",
defaults: ["vmbase_elf_defaults"],
diff --git a/pvmfw/platform.dts b/pvmfw/platform.dts
index 275a1c9..8074188 100644
--- a/pvmfw/platform.dts
+++ b/pvmfw/platform.dts
@@ -706,6 +706,14 @@
timeout-sec = <8>;
};
+ cpufreq {
+ compatible = "virtual,android-v-only-cpufreq";
+ reg = <0x0 0x1040000 PLACEHOLDER2>;
+ };
+
+ // Keep pvIOMMUs at the last for making test happy.
+ // Otherwise, phandle of other nodes are changed when unused pvIOMMU nodes
+ // are removed, so hardcoded phandles in test data would mismatch.
pviommu_0: pviommu0 {
compatible = "pkvm,pviommu";
id = <PLACEHOLDER>;
@@ -766,8 +774,5 @@
#iommu-cells = <1>;
};
- cpufreq {
- compatible = "virtual,android-v-only-cpufreq";
- reg = <0x0 0x1040000 PLACEHOLDER2>;
- };
+ // Do not add new node below
};
diff --git a/pvmfw/src/device_assignment.rs b/pvmfw/src/device_assignment.rs
index c3ccf96..885cd22 100644
--- a/pvmfw/src/device_assignment.rs
+++ b/pvmfw/src/device_assignment.rs
@@ -29,8 +29,10 @@
use core::mem;
use core::ops::Range;
use hyp::DeviceAssigningHypervisor;
-use libfdt::{Fdt, FdtError, FdtNode, Phandle, Reg};
+use libfdt::{Fdt, FdtError, FdtNode, FdtNodeMut, Phandle, Reg};
use log::error;
+use zerocopy::byteorder::big_endian::U32;
+use zerocopy::FromBytes as _;
// TODO(b/308694211): Use cstr! from vmbase instead.
macro_rules! cstr {
@@ -214,6 +216,72 @@
}
}
+#[derive(Debug, Eq, PartialEq)]
+enum DeviceTreeChildrenMask {
+ Partial(Vec<DeviceTreeMask>),
+ All,
+}
+
+#[derive(Eq, PartialEq)]
+struct DeviceTreeMask {
+ name_bytes: Vec<u8>,
+ children: DeviceTreeChildrenMask,
+}
+
+impl fmt::Debug for DeviceTreeMask {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let name_bytes = [self.name_bytes.as_slice(), b"\0"].concat();
+
+ f.debug_struct("DeviceTreeMask")
+ .field("name", &CStr::from_bytes_with_nul(&name_bytes).unwrap())
+ .field("children", &self.children)
+ .finish()
+ }
+}
+
+impl DeviceTreeMask {
+ fn new() -> Self {
+ Self { name_bytes: b"/".to_vec(), children: DeviceTreeChildrenMask::Partial(Vec::new()) }
+ }
+
+ fn mask_internal(&mut self, path: &DtPathTokens, leaf_mask: DeviceTreeChildrenMask) -> bool {
+ let mut iter = self;
+ let mut newly_masked = false;
+ 'next_token: for path_token in &path.tokens {
+ let DeviceTreeChildrenMask::Partial(ref mut children) = &mut iter.children else {
+ return false;
+ };
+
+ // Note: Can't use iterator for 'get or insert'. (a.k.a. polonius Rust)
+ #[allow(clippy::needless_range_loop)]
+ for i in 0..children.len() {
+ if children[i].name_bytes.as_slice() == *path_token {
+ iter = &mut children[i];
+ newly_masked = false;
+ continue 'next_token;
+ }
+ }
+ let child = Self {
+ name_bytes: path_token.to_vec(),
+ children: DeviceTreeChildrenMask::Partial(Vec::new()),
+ };
+ children.push(child);
+ newly_masked = true;
+ iter = children.last_mut().unwrap()
+ }
+ iter.children = leaf_mask;
+ newly_masked
+ }
+
+ fn mask(&mut self, path: &DtPathTokens) -> bool {
+ self.mask_internal(path, DeviceTreeChildrenMask::Partial(Vec::new()))
+ }
+
+ fn mask_all(&mut self, path: &DtPathTokens) {
+ self.mask_internal(path, DeviceTreeChildrenMask::All);
+ }
+}
+
/// Represents VM DTBO
#[repr(transparent)]
pub struct VmDtbo(Fdt);
@@ -347,6 +415,114 @@
}
Ok(Some(node))
}
+
+ fn collect_overlayable_nodes_with_phandle(&self) -> Result<BTreeMap<Phandle, DtPathTokens>> {
+ let mut paths = BTreeMap::new();
+ let mut path: DtPathTokens = Default::default();
+ let root = self.as_ref().root();
+ for (node, depth) in root.descendants() {
+ path.tokens.truncate(depth - 1);
+ path.tokens.push(node.name()?.to_bytes());
+ if !path.is_overlayable_node() {
+ continue;
+ }
+ if let Some(phandle) = node.get_phandle()? {
+ paths.insert(phandle, path.clone());
+ }
+ }
+ Ok(paths)
+ }
+
+ fn collect_phandle_references_from_overlayable_nodes(
+ &self,
+ ) -> Result<BTreeMap<DtPathTokens, Vec<Phandle>>> {
+ const CELL_SIZE: usize = core::mem::size_of::<u32>();
+
+ let vm_dtbo = self.as_ref();
+
+ let mut phandle_map = BTreeMap::new();
+ let Some(local_fixups) = vm_dtbo.node(cstr!("/__local_fixups__"))? else {
+ return Ok(phandle_map);
+ };
+
+ let mut path: DtPathTokens = Default::default();
+ for (fixup_node, depth) in local_fixups.descendants() {
+ let node_name = fixup_node.name()?;
+ path.tokens.truncate(depth - 1);
+ path.tokens.push(node_name.to_bytes());
+ if path.tokens.len() != depth {
+ return Err(DeviceAssignmentError::Internal);
+ }
+ if !path.is_overlayable_node() {
+ continue;
+ }
+ let target_node = self.node(&path)?.ok_or(DeviceAssignmentError::InvalidDtbo)?;
+
+ let mut phandles = vec![];
+ for fixup_prop in fixup_node.properties()? {
+ let target_prop = target_node
+ .getprop(fixup_prop.name()?)
+ .or(Err(DeviceAssignmentError::InvalidDtbo))?
+ .ok_or(DeviceAssignmentError::InvalidDtbo)?;
+ let fixup_prop_values = fixup_prop.value()?;
+ if fixup_prop_values.is_empty() || fixup_prop_values.len() % CELL_SIZE != 0 {
+ return Err(DeviceAssignmentError::InvalidDtbo);
+ }
+
+ for fixup_prop_cell in fixup_prop_values.chunks(CELL_SIZE) {
+ let phandle_offset: usize = u32::from_be_bytes(
+ fixup_prop_cell.try_into().or(Err(DeviceAssignmentError::InvalidDtbo))?,
+ )
+ .try_into()
+ .or(Err(DeviceAssignmentError::InvalidDtbo))?;
+ if phandle_offset % CELL_SIZE != 0 {
+ return Err(DeviceAssignmentError::InvalidDtbo);
+ }
+ let phandle_value = target_prop
+ .get(phandle_offset..phandle_offset + CELL_SIZE)
+ .ok_or(DeviceAssignmentError::InvalidDtbo)?;
+ let phandle: Phandle = U32::ref_from(phandle_value)
+ .unwrap()
+ .get()
+ .try_into()
+ .or(Err(DeviceAssignmentError::InvalidDtbo))?;
+
+ phandles.push(phandle);
+ }
+ }
+ if !phandles.is_empty() {
+ phandle_map.insert(path.clone(), phandles);
+ }
+ }
+
+ Ok(phandle_map)
+ }
+
+ fn build_mask(&self, assigned_devices: Vec<DtPathTokens>) -> Result<DeviceTreeMask> {
+ if assigned_devices.is_empty() {
+ return Err(DeviceAssignmentError::Internal);
+ }
+
+ let dependencies = self.collect_phandle_references_from_overlayable_nodes()?;
+ let paths = self.collect_overlayable_nodes_with_phandle()?;
+
+ let mut mask = DeviceTreeMask::new();
+ let mut stack = assigned_devices;
+ while let Some(path) = stack.pop() {
+ if !mask.mask(&path) {
+ continue;
+ }
+ let Some(dst_phandles) = dependencies.get(&path) else {
+ continue;
+ };
+ for dst_phandle in dst_phandles {
+ let dst_path = paths.get(dst_phandle).ok_or(DeviceAssignmentError::Internal)?;
+ stack.push(dst_path.clone());
+ }
+ }
+
+ Ok(mask)
+ }
}
fn filter_dangling_symbols(fdt: &mut Fdt) -> Result<()> {
@@ -381,6 +557,38 @@
}
}
+// Filter any node that isn't masked by DeviceTreeMask.
+fn filter_with_mask(anchor: FdtNodeMut, mask: &DeviceTreeMask) -> Result<()> {
+ let mut stack = vec![mask];
+ let mut iter = anchor.next_node(0)?;
+ while let Some((node, depth)) = iter {
+ stack.truncate(depth);
+ let parent_mask = stack.last().unwrap();
+ let DeviceTreeChildrenMask::Partial(parent_mask_children) = &parent_mask.children else {
+ // Shouldn't happen. We only step-in if parent has DeviceTreeChildrenMask::Partial.
+ return Err(DeviceAssignmentError::Internal);
+ };
+
+ let name = node.as_node().name()?.to_bytes();
+ let mask = parent_mask_children.iter().find(|child_mask| child_mask.name_bytes == name);
+ if let Some(masked) = mask {
+ if let DeviceTreeChildrenMask::Partial(_) = &masked.children {
+ // This node is partially masked. Stepping-in.
+ stack.push(masked);
+ iter = node.next_node(depth)?;
+ } else {
+ // This node is fully masked. Stepping-out.
+ iter = node.next_node_skip_subnodes(depth)?;
+ }
+ } else {
+ // This node isn't masked.
+ iter = node.delete_and_next_node(depth)?;
+ }
+ }
+
+ Ok(())
+}
+
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
struct PvIommu {
// ID from pvIOMMU node
@@ -689,11 +897,11 @@
}
}
-#[derive(Debug, Default, Eq, PartialEq)]
+#[derive(Debug, Eq, PartialEq)]
pub struct DeviceAssignmentInfo {
pviommus: BTreeSet<PvIommu>,
assigned_devices: Vec<AssignedDeviceInfo>,
- filtered_dtbo_paths: Vec<CString>,
+ vm_dtbo_mask: DeviceTreeMask,
}
impl DeviceAssignmentInfo {
@@ -751,7 +959,7 @@
let physical_devices = vm_dtbo.parse_physical_devices()?;
let mut assigned_devices = vec![];
- let mut filtered_dtbo_paths = vec![];
+ let mut assigned_device_paths = vec![];
for symbol_prop in symbols_node.properties()? {
let symbol_prop_value = symbol_prop.value()?;
let dtbo_node_path = CStr::from_bytes_with_nul(symbol_prop_value)
@@ -770,8 +978,7 @@
)?;
if let Some(assigned_device) = assigned_device {
assigned_devices.push(assigned_device);
- } else {
- filtered_dtbo_paths.push(dtbo_node_path.to_cstring());
+ assigned_device_paths.push(dtbo_node_path);
}
}
if assigned_devices.is_empty() {
@@ -780,32 +987,29 @@
Self::validate_pviommu_topology(&assigned_devices)?;
- // Clean up any nodes that wouldn't be overlaid but may contain reference to filtered nodes.
- // Otherwise, `fdt_apply_overlay()` would fail because of missing phandle reference.
- // TODO(b/277993056): Also filter other unused nodes/props in __local_fixups__
- filtered_dtbo_paths.push(CString::new("/__local_fixups__/host").unwrap());
+ let mut vm_dtbo_mask = vm_dtbo.build_mask(assigned_device_paths)?;
+ vm_dtbo_mask.mask_all(&DtPathTokens::new(cstr!("/__local_fixups__"))?);
+ vm_dtbo_mask.mask_all(&DtPathTokens::new(cstr!("/__symbols__"))?);
// Note: Any node without __overlay__ will be ignored by fdt_apply_overlay,
// so doesn't need to be filtered.
- Ok(Some(Self { pviommus: unique_pviommus, assigned_devices, filtered_dtbo_paths }))
+ Ok(Some(Self { pviommus: unique_pviommus, assigned_devices, vm_dtbo_mask }))
}
/// Filters VM DTBO to only contain necessary information for booting pVM
- /// In detail, this will remove followings by setting nop node / nop property.
- /// - Removes unassigned devices
- // TODO(b/277993056): remove unused dependencies in VM DTBO.
- // TODO(b/277993056): remove supernodes' properties.
- // TODO(b/277993056): remove unused alises.
pub fn filter(&self, vm_dtbo: &mut VmDtbo) -> Result<()> {
let vm_dtbo = vm_dtbo.as_mut();
- // Filters unused node in assigned devices
- for filtered_dtbo_path in &self.filtered_dtbo_paths {
- let node = vm_dtbo.node_mut(filtered_dtbo_path).unwrap().unwrap();
- node.nop()?;
+ // Filter unused references in /__local_fixups__
+ if let Some(local_fixups) = vm_dtbo.node_mut(cstr!("/__local_fixups__"))? {
+ filter_with_mask(local_fixups, &self.vm_dtbo_mask)?;
}
+ // Filter unused nodes in rest of tree
+ let root = vm_dtbo.root_mut();
+ filter_with_mask(root, &self.vm_dtbo_mask)?;
+
filter_dangling_symbols(vm_dtbo)
}
@@ -860,13 +1064,17 @@
mod tests {
use super::*;
use alloc::collections::{BTreeMap, BTreeSet};
+ use dts::Dts;
use std::fs;
+ use std::path::Path;
const VM_DTBO_FILE_PATH: &str = "test_pvmfw_devices_vm_dtbo.dtbo";
const VM_DTBO_WITHOUT_SYMBOLS_FILE_PATH: &str =
"test_pvmfw_devices_vm_dtbo_without_symbols.dtbo";
const VM_DTBO_WITH_DUPLICATED_IOMMUS_FILE_PATH: &str =
"test_pvmfw_devices_vm_dtbo_with_duplicated_iommus.dtbo";
+ const VM_DTBO_WITH_DEPENDENCIES_FILE_PATH: &str =
+ "test_pvmfw_devices_vm_dtbo_with_dependencies.dtbo";
const FDT_WITHOUT_IOMMUS_FILE_PATH: &str = "test_pvmfw_devices_without_iommus.dtb";
const FDT_WITHOUT_DEVICE_FILE_PATH: &str = "test_pvmfw_devices_without_device.dtb";
const FDT_FILE_PATH: &str = "test_pvmfw_devices_with_rng.dtb";
@@ -879,6 +1087,16 @@
"test_pvmfw_devices_with_duplicated_pviommus.dtb";
const FDT_WITH_MULTIPLE_REG_IOMMU_FILE_PATH: &str =
"test_pvmfw_devices_with_multiple_reg_iommus.dtb";
+ const FDT_WITH_DEPENDENCY_FILE_PATH: &str = "test_pvmfw_devices_with_dependency.dtb";
+ const FDT_WITH_MULTIPLE_DEPENDENCIES_FILE_PATH: &str =
+ "test_pvmfw_devices_with_multiple_dependencies.dtb";
+ const FDT_WITH_DEPENDENCY_LOOP_FILE_PATH: &str = "test_pvmfw_devices_with_dependency_loop.dtb";
+
+ const EXPECTED_FDT_WITH_DEPENDENCY_FILE_PATH: &str = "expected_dt_with_dependency.dtb";
+ const EXPECTED_FDT_WITH_MULTIPLE_DEPENDENCIES_FILE_PATH: &str =
+ "expected_dt_with_multiple_dependencies.dtb";
+ const EXPECTED_FDT_WITH_DEPENDENCY_LOOP_FILE_PATH: &str =
+ "expected_dt_with_dependency_loop.dtb";
#[derive(Debug, Default)]
struct MockHypervisor {
@@ -1449,4 +1667,97 @@
let compatible = platform_dt.root().next_compatible(cstr!("pkvm,pviommu"));
assert_eq!(Ok(None), compatible);
}
+
+ #[test]
+ fn device_info_dependency() {
+ let mut fdt_data = fs::read(FDT_WITH_DEPENDENCY_FILE_PATH).unwrap();
+ let mut vm_dtbo_data = fs::read(VM_DTBO_WITH_DEPENDENCIES_FILE_PATH).unwrap();
+ let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
+ let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
+ let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
+ platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
+ let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
+ platform_dt.unpack().unwrap();
+
+ let hypervisor = MockHypervisor {
+ mmio_tokens: [((0xFF000, 0x1), 0xF000)].into(),
+ iommu_tokens: Default::default(),
+ };
+
+ let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
+ device_info.filter(vm_dtbo).unwrap();
+
+ // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
+ unsafe {
+ platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
+ }
+ device_info.patch(platform_dt).unwrap();
+
+ let expected = Dts::from_dtb(Path::new(EXPECTED_FDT_WITH_DEPENDENCY_FILE_PATH)).unwrap();
+ let platform_dt = Dts::from_fdt(platform_dt).unwrap();
+
+ assert_eq!(expected, platform_dt);
+ }
+
+ #[test]
+ fn device_info_multiple_dependencies() {
+ let mut fdt_data = fs::read(FDT_WITH_MULTIPLE_DEPENDENCIES_FILE_PATH).unwrap();
+ let mut vm_dtbo_data = fs::read(VM_DTBO_WITH_DEPENDENCIES_FILE_PATH).unwrap();
+ let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
+ let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
+ let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
+ platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
+ let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
+ platform_dt.unpack().unwrap();
+
+ let hypervisor = MockHypervisor {
+ mmio_tokens: [((0xFF000, 0x1), 0xF000), ((0xFF100, 0x1), 0xF100)].into(),
+ iommu_tokens: Default::default(),
+ };
+ let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
+ device_info.filter(vm_dtbo).unwrap();
+
+ // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
+ unsafe {
+ platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
+ }
+ device_info.patch(platform_dt).unwrap();
+
+ let expected =
+ Dts::from_dtb(Path::new(EXPECTED_FDT_WITH_MULTIPLE_DEPENDENCIES_FILE_PATH)).unwrap();
+ let platform_dt = Dts::from_fdt(platform_dt).unwrap();
+
+ assert_eq!(expected, platform_dt);
+ }
+
+ #[test]
+ fn device_info_dependency_loop() {
+ let mut fdt_data = fs::read(FDT_WITH_DEPENDENCY_LOOP_FILE_PATH).unwrap();
+ let mut vm_dtbo_data = fs::read(VM_DTBO_WITH_DEPENDENCIES_FILE_PATH).unwrap();
+ let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
+ let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
+ let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
+ platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
+ let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
+ platform_dt.unpack().unwrap();
+
+ let hypervisor = MockHypervisor {
+ mmio_tokens: [((0xFF200, 0x1), 0xF200)].into(),
+ iommu_tokens: Default::default(),
+ };
+ let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap().unwrap();
+ device_info.filter(vm_dtbo).unwrap();
+
+ // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
+ unsafe {
+ platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
+ }
+ device_info.patch(platform_dt).unwrap();
+
+ let expected =
+ Dts::from_dtb(Path::new(EXPECTED_FDT_WITH_DEPENDENCY_LOOP_FILE_PATH)).unwrap();
+ let platform_dt = Dts::from_fdt(platform_dt).unwrap();
+
+ assert_eq!(expected, platform_dt);
+ }
}
diff --git a/pvmfw/testdata/expected_dt_with_dependency.dts b/pvmfw/testdata/expected_dt_with_dependency.dts
new file mode 100644
index 0000000..7e0ad20
--- /dev/null
+++ b/pvmfw/testdata/expected_dt_with_dependency.dts
@@ -0,0 +1,47 @@
+/dts-v1/;
+
+/include/ "platform_preprocessed.dts"
+
+// Note: This uses manually written __symbols__ so we don't
+
+/ {
+ node_a: node_a {
+ phandle = <0x2E>;
+ val = <0x6>;
+ dep = <&node_a_dep &common>;
+ reg = <0x0 0xFF000 0x0 0x1>;
+ interrupts = <0x0 0xF 0x4>;
+ iommus;
+ };
+
+ node_a_dep: node_a_dep {
+ phandle = <0x31>;
+ val = <0xFF>;
+ dep = <&node_aa_nested_dep>;
+ };
+
+ node_aa {
+ should_be_preserved = <0xFF>;
+
+ node_aa_nested_dep: node_aa_nested_dep {
+ phandle = <0x33>;
+ tag = <0x9>;
+ };
+ };
+
+ common: common {
+ phandle = <0x32>;
+ id = <0x9>;
+ };
+
+ /delete-node/ pviommu0;
+ /delete-node/ pviommu1;
+ /delete-node/ pviommu2;
+ /delete-node/ pviommu3;
+ /delete-node/ pviommu4;
+ /delete-node/ pviommu5;
+ /delete-node/ pviommu6;
+ /delete-node/ pviommu7;
+ /delete-node/ pviommu8;
+ /delete-node/ pviommu9;
+};
diff --git a/pvmfw/testdata/expected_dt_with_dependency_loop.dts b/pvmfw/testdata/expected_dt_with_dependency_loop.dts
new file mode 100644
index 0000000..61031ab
--- /dev/null
+++ b/pvmfw/testdata/expected_dt_with_dependency_loop.dts
@@ -0,0 +1,29 @@
+/dts-v1/;
+
+/include/ "platform_preprocessed.dts"
+
+/ {
+ node_c: node_c {
+ phandle = <0x30>;
+ loop_dep = <&node_c_loop>;
+ reg = <0x0 0xFF200 0x0 0x1>;
+ interrupts = <0x0 0xF 0x4>;
+ iommus;
+ };
+
+ node_c_loop: node_c_loop {
+ phandle = <0x36>;
+ loop_dep = <&node_c>;
+ };
+
+ /delete-node/ pviommu0;
+ /delete-node/ pviommu1;
+ /delete-node/ pviommu2;
+ /delete-node/ pviommu3;
+ /delete-node/ pviommu4;
+ /delete-node/ pviommu5;
+ /delete-node/ pviommu6;
+ /delete-node/ pviommu7;
+ /delete-node/ pviommu8;
+ /delete-node/ pviommu9;
+};
diff --git a/pvmfw/testdata/expected_dt_with_multiple_dependencies.dts b/pvmfw/testdata/expected_dt_with_multiple_dependencies.dts
new file mode 100644
index 0000000..dc8c357
--- /dev/null
+++ b/pvmfw/testdata/expected_dt_with_multiple_dependencies.dts
@@ -0,0 +1,70 @@
+/dts-v1/;
+
+// Note: We can't use label syntax here.
+// Implementation applies overlay after removing /__symbols__,
+// so using label syntax here wouldn't match with the actual reasult.
+
+/include/ "platform_preprocessed.dts"
+
+/ {
+ node_a: node_a {
+ phandle = <0x2E>;
+ val = <0x6>;
+ dep = <&node_a_dep &common>;
+ reg = <0x0 0xFF000 0x0 0x1>;
+ interrupts = <0x0 0xF 0x4>;
+ iommus;
+ };
+
+ node_a_dep: node_a_dep {
+ phandle = <0x31>;
+ val = <0xFF>;
+ dep = <&node_aa_nested_dep>;
+ };
+
+ node_aa {
+ should_be_preserved = <0xFF>;
+
+ node_aa_nested_dep: node_aa_nested_dep {
+ phandle = <0x33>;
+ tag = <0x9>;
+ };
+ };
+
+ node_b: node_b {
+ phandle = <0x2F>;
+ tag = <0x33>;
+ version = <0x1 0x2>;
+ dep = <&node_b_dep1 &node_b_dep2>;
+ reg = <0x00 0xFF100 0x00 0x01>;
+ interrupts = <0x00 0x0F 0x04>;
+ iommus;
+ };
+
+ node_b_dep1: node_b_dep1 {
+ phandle = <0x34>;
+ placeholder;
+ };
+
+ node_b_dep2: node_b_dep2 {
+ phandle = <0x35>;
+ placeholder;
+ dep = <&common>;
+ };
+
+ common: common {
+ phandle = <0x32>;
+ id = <0x9>;
+ };
+
+ /delete-node/ pviommu0;
+ /delete-node/ pviommu1;
+ /delete-node/ pviommu2;
+ /delete-node/ pviommu3;
+ /delete-node/ pviommu4;
+ /delete-node/ pviommu5;
+ /delete-node/ pviommu6;
+ /delete-node/ pviommu7;
+ /delete-node/ pviommu8;
+ /delete-node/ pviommu9;
+};
diff --git a/pvmfw/testdata/test_pvmfw_devices_vm_dtbo_with_dependencies.dts b/pvmfw/testdata/test_pvmfw_devices_vm_dtbo_with_dependencies.dts
new file mode 100644
index 0000000..21075e7
--- /dev/null
+++ b/pvmfw/testdata/test_pvmfw_devices_vm_dtbo_with_dependencies.dts
@@ -0,0 +1,77 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+ host {
+ #address-cells = <0x2>;
+ #size-cells = <0x1>;
+ node_a {
+ reg = <0x0 0xF000 0x1>;
+ android,pvmfw,target = <&node_a>;
+ };
+ node_b {
+ reg = <0x0 0xF100 0x1>;
+ android,pvmfw,target = <&node_b>;
+ };
+ node_c {
+ reg = <0x0 0xF200 0x1>;
+ android,pvmfw,target = <&node_c>;
+ };
+ };
+};
+
+&{/} {
+ node_a: node_a {
+ val = <0x6>;
+ dep = <&node_a_dep &common>;
+ };
+
+ node_a_dep: node_a_dep {
+ val = <0xFF>;
+ dep = <&node_aa_nested_dep>;
+
+ node_a_internal {
+ val;
+ };
+ };
+
+ node_aa {
+ should_be_preserved = <0xFF>;
+ node_aa_nested_dep: node_aa_nested_dep {
+ tag = <0x9>;
+ };
+ };
+};
+
+&{/} {
+ node_b: node_b {
+ tag = <0x33>;
+ version = <0x1 0x2>;
+ dep = <&node_b_dep1 &node_b_dep2>;
+ };
+
+ node_b_dep1: node_b_dep1 {
+ placeholder;
+ };
+
+ node_b_dep2: node_b_dep2 {
+ placeholder;
+ dep = <&common>;
+ };
+};
+
+&{/} {
+ node_c: node_c {
+ loop_dep = <&node_c_loop>;
+ };
+
+ node_c_loop: node_c_loop {
+ loop_dep = <&node_c>;
+ };
+};
+
+&{/} {
+ common: common {
+ id = <0x9>;
+ };
+};
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_dependency.dts b/pvmfw/testdata/test_pvmfw_devices_with_dependency.dts
new file mode 100644
index 0000000..b1cf6c7
--- /dev/null
+++ b/pvmfw/testdata/test_pvmfw_devices_with_dependency.dts
@@ -0,0 +1,36 @@
+/dts-v1/;
+
+/include/ "test_crosvm_dt_base.dtsi"
+
+/ {
+ node_a: node_a {
+ reg = <0x0 0xFF000 0x0 0x1>;
+ interrupts = <0x0 0xF 0x4>;
+ val = <0x6>;
+ dep = <&node_a_dep &common>;
+
+ node_a_internal {
+ parent = <&node_a>;
+ };
+ };
+
+ node_a_dep: node_a_dep {
+ val = <0xFF>;
+ dep = <&node_aa_nested_dep>;
+
+ node_a_dep_internal {
+ val;
+ };
+ };
+
+ node_aa {
+ should_be_preserved = <0xFF>;
+ node_aa_nested_dep: node_aa_nested_dep {
+ tag = <0x9>;
+ };
+ };
+
+ common: common {
+ id = <0x9>;
+ };
+};
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_dependency_loop.dts b/pvmfw/testdata/test_pvmfw_devices_with_dependency_loop.dts
new file mode 100644
index 0000000..9a62cb5
--- /dev/null
+++ b/pvmfw/testdata/test_pvmfw_devices_with_dependency_loop.dts
@@ -0,0 +1,15 @@
+/dts-v1/;
+
+/include/ "test_crosvm_dt_base.dtsi"
+
+/ {
+ node_c: node_c {
+ reg = <0x0 0xFF200 0x0 0x1>;
+ interrupts = <0x0 0xF 0x4>;
+ loop_dep = <&node_c_loop>;
+ };
+
+ node_c_loop: node_c_loop {
+ loop_dep = <&node_c>;
+ };
+};
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_multiple_dependencies.dts b/pvmfw/testdata/test_pvmfw_devices_with_multiple_dependencies.dts
new file mode 100644
index 0000000..573bdcf
--- /dev/null
+++ b/pvmfw/testdata/test_pvmfw_devices_with_multiple_dependencies.dts
@@ -0,0 +1,50 @@
+/dts-v1/;
+
+/include/ "test_crosvm_dt_base.dtsi"
+
+/ {
+ node_a: node_a {
+ reg = <0x0 0xFF000 0x0 0x1>;
+ interrupts = <0x0 0xF 0x4>;
+ val = <0x6>;
+ dep = <&node_a_dep &common>;
+ };
+
+ node_a_dep: node_a_dep {
+ val = <0xFF>;
+ dep = <&node_nested_dep>;
+
+ node_a_internal {
+ val;
+ };
+ };
+
+ node_aa {
+ should_be_preserved = <0xFF>;
+ node_nested_dep: node_aa_nested_dep {
+ tag = <0x9>;
+ };
+ };
+
+ node_b: node_b {
+ reg = <0x0 0xFF100 0x0 0x1>;
+ interrupts = <0x0 0xF 0x4>;
+ tag = <0x33>;
+ version = <0x1 0x2>;
+ phandle = <0x5>;
+ dep = <&node_b_dep1 &node_b_dep2>;
+ };
+
+ node_b_dep1: node_b_dep1 {
+ placeholder;
+ };
+
+ node_b_dep2: node_b_dep2 {
+ placeholder;
+ dep = <&common>;
+ };
+
+ common: common {
+ id = <0x9>;
+ };
+};