// Copyright 2023 The ChromiumOS Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. //! This module applies binary flattened device tree overlays. use std::collections::BTreeMap; use std::collections::HashSet; use std::collections::VecDeque; use crate::fdt::Error; use crate::fdt::Fdt; use crate::fdt::FdtNode; use crate::fdt::FdtReserveEntry; use crate::fdt::Result; use crate::path::parse_path_with_prop; use crate::path::Path; use crate::path::PhandlePin; use crate::path::PATH_SEP; const PHANDLE_PROP: &str = "phandle"; const LINUX_PHANDLE_PROP: &str = "linux,phandle"; const TARGET_PATH_PROP: &str = "target-path"; const TARGET_PROP: &str = "target"; const LOCAL_FIXUPS_NODE: &str = "__local_fixups__"; const OVERLAY_NODE: &str = "__overlay__"; const SYMBOLS_NODE: &str = "__symbols__"; const FIXUPS_NODE: &str = "__fixups__"; const ROOT_NODE: &str = "/"; // Ensure filtered symbols exist and contain a valid path. They will be the starting points // for the filtering algorithm. fn prepare_filtered_symbols>( start_symbols: impl std::iter::IntoIterator, fdt: &Fdt, ) -> Result<(HashSet, Vec)> { let symbols = HashSet::from_iter(start_symbols.into_iter().map(|s| s.as_ref().to_owned())); let mut paths = vec![]; for symbol in &symbols { paths.push( fdt.symbol_to_path(symbol) .map_err(|e| Error::FilterError(format!("{e}")))?, ); } Ok((symbols, paths)) } // Look for references (phandle values) defined by `fixup_node` in properties of `tree_node`. fn collect_phandle_refs_from_props(fixup_node: &FdtNode, tree_node: &FdtNode) -> Result> { let mut phandles = vec![]; for propname in fixup_node.prop_names() { for phandle_offset in fixup_node.get_prop::>(propname).unwrap() { phandles.push( tree_node .phandle_at_offset(propname, phandle_offset as usize) .ok_or(Error::PropertyValueInvalid)?, ); } } Ok(phandles) } // Traverse all nodes along given node path, and collect phandle reference values from properties. fn collect_all_references_by_path( path: &Path, root: &FdtNode, local_fixups_node: &FdtNode, ) -> Result> { // Follow node names inside the local fixups node and in the tree root. let mut tree_node = root; let mut fixup_node = local_fixups_node; let mut phandle_refs = HashSet::::new(); // Follow node names along path for node_name in path.iter() { tree_node = tree_node .subnode(node_name) .ok_or_else(|| Error::InvalidPath(format!("cannot find subnode {}", node_name)))?; if let Some(n) = fixup_node.subnode(node_name) { fixup_node = n } else { return Ok(phandle_refs); // No references left to collect in this subtree. } // Look for references (phandle values) in properties along path; add them to set. phandle_refs.extend(collect_phandle_refs_from_props(fixup_node, tree_node)?); } Ok(phandle_refs) } // Collect locations of all phandles in the FDT. fn get_all_phandles(fdt: &Fdt) -> BTreeMap { let mut phandles = BTreeMap::new(); let mut nodes = VecDeque::<(&FdtNode, Path)>::new(); nodes.push_back((&fdt.root, ROOT_NODE.parse().unwrap())); while let Some((node, path)) = nodes.pop_front() { for subnode in node.iter_subnodes() { nodes.push_back((subnode, path.push(&subnode.name).unwrap())); } if let Some(phandle) = get_node_phandle(node) { phandles.insert(phandle, path); } } phandles } // Minimize paths - if the vector contains two paths where one is the // parent of the other, only include the parent path, and drop the child path. fn minimize_paths(paths: &mut Vec) { paths.sort(); paths.dedup_by(|a, b| a.is_child_of(b)); } // Collect paths of all nodes that nodes in `start_paths` depend on. Path A depends on // path B if any node along the path A references the node path B points to. fn collect_all_filtered_paths(mut start_paths: Vec, fdt: &Fdt) -> Result> { if start_paths.is_empty() { return Ok(vec![]); } minimize_paths(&mut start_paths); let Some(local_fixups_node) = fdt.root.subnode(LOCAL_FIXUPS_NODE) else { return Ok(start_paths); // No fixups node -> no other references }; let all_phandles = get_all_phandles(fdt); // All FDT phandles, mapped to their paths let mut result_paths = HashSet::::with_capacity(start_paths.len()); let mut pending_paths: VecDeque<_> = start_paths.iter().collect(); // Paths to visit while let Some(path) = pending_paths.pop_front() { if result_paths.contains(path) { continue; // Already seen this path } // Collect all phandles that this path references let phandles = collect_all_references_by_path(path, &fdt.root, local_fixups_node)?; // Map the phandles to other locations for ph in phandles { pending_paths.push_back(all_phandles.get(&ph).ok_or(Error::PropertyValueInvalid)?); } // This path should remain in the final overlay. result_paths.insert(path.to_owned()); } let mut result_paths = result_paths.into_iter().collect(); minimize_paths(&mut result_paths); Ok(result_paths) } // Drop nodes which are not covered by the filtered paths. fn do_overlay_filter(filtered_paths: Vec, overlay: &mut Fdt) { if filtered_paths.is_empty() { return; } let mut new_root = FdtNode::empty("").unwrap(); for path in filtered_paths { let mut src_node = &overlay.root; let mut tgt_node = &mut new_root; for node_name in path.iter() { src_node = src_node .subnode(node_name) .expect("filtered paths reference valid nodes"); tgt_node = tgt_node .subnode_mut(node_name) .expect("filtered paths reference valid nodes"); tgt_node.props.clone_from(&src_node.props); } } overlay.root = new_root; } // Read 'phandle' or 'linux,phandle' property of a node. fn get_node_phandle(node: &FdtNode) -> Option { node.get_prop(PHANDLE_PROP) .or_else(|| node.get_prop(LINUX_PHANDLE_PROP)) } // Return the largest phandle value in a node tree. fn get_max_phandle(root_node: &FdtNode) -> u32 { let mut max_phandle = 0u32; let mut nodes_to_visit = VecDeque::new(); nodes_to_visit.push_back(root_node); while let Some(node) = nodes_to_visit.pop_front() { max_phandle = max_phandle.max(get_node_phandle(node).unwrap_or(0u32)); nodes_to_visit.extend(node.iter_subnodes()); } max_phandle } // Add the given delta to the phandle property of the node. fn offset_phandle_prop(node: &mut FdtNode, propname: &str, delta: u32) -> Result<()> { let mut val: u32 = node.get_prop(propname).ok_or_else(|| { Error::ApplyOverlayError(format!( "cannot offset {}:{} - invalid value", node.name, propname )) })?; val = val .checked_add(delta) .ok_or_else(|| Error::ApplyOverlayError("cannot offset phandle - value overflow".into()))?; node.set_prop(propname, val) .expect("phandle property name is valid"); Ok(()) } // Add the given delta to phandle properties of all nodes in the FDT. fn offset_phandle_values(fdt: &mut Fdt, delta: u32) -> Result<()> { let mut stack = VecDeque::new(); stack.push_back(&mut fdt.root); while let Some(node) = stack.pop_front() { if node.has_prop(PHANDLE_PROP) { offset_phandle_prop(node, PHANDLE_PROP, delta)?; } if node.has_prop(LINUX_PHANDLE_PROP) { offset_phandle_prop(node, LINUX_PHANDLE_PROP, delta)?; } stack.extend(node.iter_subnodes_mut()); } Ok(()) } // Returns a vector of paths which contain a local phandle value (reference) fn collect_local_fixup_paths(fdt: &Fdt) -> Result>> { let mut local_phandles = BTreeMap::>::new(); let Some(local_fixups_node) = fdt.root.subnode(LOCAL_FIXUPS_NODE) else { return Ok(local_phandles); }; let mut stack = VecDeque::<(Path, &FdtNode)>::new(); stack.push_back((ROOT_NODE.parse().unwrap(), local_fixups_node)); // Collect local phandle properties to fixup from __local_fixups__ while let Some((path, node)) = stack.pop_front() { // Every property in __local_fixups__ contains a vector of offsets (u32) // where the phandles are located for propname in node.prop_names() { let offsets = node.get_prop::>(propname).ok_or_else(|| { Error::ApplyOverlayError(format!( "fixup node {} contains invalid offset array", node.name )) })?; // Add phandle pins if !local_phandles.contains_key(&path) { local_phandles.insert(path.clone(), vec![]); } let pins = local_phandles.get_mut(&path).unwrap(); pins.extend(offsets.into_iter().map(|o| PhandlePin(propname.into(), o))); } // Traverse into this node's children for child in node.iter_subnodes() { stack.push_back((path.push(&child.name)?, child)); } } Ok(local_phandles) } fn update_local_phandle_propvals( fdt: &mut Fdt, paths: BTreeMap>, delta: u32, ) -> Result<()> { // Update phandles in collected locations for (path, pins) in paths { let node = fdt .get_node_mut(path) .ok_or_else(|| Error::ApplyOverlayError("cannot find node for fixup".into()))?; for pin in pins { let phandle_val = node .phandle_at_offset(&pin.0, pin.1 as usize) .ok_or_else(|| Error::ApplyOverlayError(format!("missing property {}", &pin.0)))?; node.update_phandle_at_offset(&pin.0, pin.1 as usize, phandle_val + delta)?; } } Ok(()) } fn update_local_refs(fdt: &mut Fdt, delta: u32) -> Result<()> { let phandle_locations = collect_local_fixup_paths(fdt)?; update_local_phandle_propvals(fdt, phandle_locations, delta) } // Given a DT symbol (label), find the path and phandle value of the node the symbol refers to. fn get_symbol_path_and_phandle(symbol: &str, fdt: &Fdt) -> Option<(String, u32)> { let symbols_node = fdt.root.subnode(SYMBOLS_NODE)?; let symbol = symbols_node.get_prop::(symbol)?; let target_node = fdt.get_node(symbol.as_str())?; Some((symbol, get_node_phandle(target_node)?)) } // For each symbol defined in base and referenced in overlay, set its references in overlay to // correct phandle values. fn apply_external_fixups(base: &Fdt, overlay: &mut Fdt) -> Result<()> { let Some(fixups_node) = overlay.root.subnode(FIXUPS_NODE) else { return Ok(()); // No references to base nodes }; // Collect locations in overlay where external nodes are referenced let mut paths_to_update = BTreeMap::<(String, u32), Vec>::new(); for fixup_symbol in fixups_node.prop_names() { // Find phandle value and path of a labeled node in base DT let path_and_phandle = get_symbol_path_and_phandle(fixup_symbol, base).ok_or_else(|| { Error::ApplyOverlayError(format!("cannot find symbol {fixup_symbol} in base fdt")) })?; // Get target paths of this symbol in overlay let target_paths: Vec = fixups_node.get_prop(fixup_symbol).ok_or_else(|| { Error::ApplyOverlayError(format!( "cannot parse target paths for fixup {fixup_symbol}" )) })?; paths_to_update.insert(path_and_phandle, target_paths); } // Update locations in overlay where external nodes are referenced for ((base_path, phandle), paths) in paths_to_update { for path in paths { let (path, pin) = parse_path_with_prop(&path)?; // Update phandle reference in target to new value let target_node = overlay .get_node_mut(path) .ok_or_else(|| Error::ApplyOverlayError("invalid fixup target path".into()))?; target_node.update_phandle_at_offset(&pin.0, pin.1 as usize, phandle)?; // If the property that is being updated here is actually a `target` property of // an overlay fragment, also add the `target-path` property to the fragment, containing // the full path to the target node in base FDT. // This covers the case where the target of an overlay fragment is a phandle reference // (of a node in base overlay), instead of absolute path in base. if pin.0 == TARGET_PROP && target_node.iter_subnodes().any(|n| n.name == OVERLAY_NODE) { target_node.set_prop(TARGET_PATH_PROP, base_path.as_str())?; } } } Ok(()) } // Copy properties from overlay node to base node, then add subnodes and overlay them as well. fn overlay_node_pair(base_node: &mut FdtNode, overlay_node: &FdtNode) -> Result<()> { base_node.props.extend(overlay_node.props.clone()); for overlay_subnode in overlay_node.iter_subnodes() { overlay_node_pair( base_node.subnode_mut(&overlay_subnode.name)?, overlay_subnode, )?; } Ok(()) } // Verify and apply an overlay fragment node to the base FDT. fn overlay_fragment(fragment_node: &FdtNode, base: &mut Fdt) -> Result<()> { // Fragment must have an '__overlay__' subnode and `target-path` property. let Some(overlay_node) = fragment_node.subnode(OVERLAY_NODE) else { return Ok(()); // Skip invalid fragments. }; let Some(target_path) = fragment_node.get_prop::(TARGET_PATH_PROP) else { return Ok(()); // Skip invalid fragments. }; // Apply overlay fragment to target node in base FDT. let target_node = base.get_node_mut(target_path.as_str()).ok_or_else(|| { Error::ApplyOverlayError(format!( "cannot find node in base FDT for target-path {target_path}", )) })?; overlay_node_pair(target_node, overlay_node) } // Parse the location of the symbol (property value), extract fragment name and the // rest of the path after `__overlay__`, (expected structure: // "/fragment@X/__overlay__/path/to/subnode"). fn extract_fragment_and_subpath(path: &Path) -> Result<(&str, String)> { let mut path_iter = path.iter(); let fragment_name = path_iter .next() .ok_or_else(|| Error::ApplyOverlayError(format!("symbol path {path} too short")))?; path_iter.next(); // Skip "__overlay__" node let rest = path_iter.collect::>(); if rest.is_empty() { Err(Error::ApplyOverlayError(format!( "symbol path {path} too short" ))) } else { Ok((fragment_name, rest.join(PATH_SEP))) } } fn update_base_symbols( base: &mut Fdt, overlay: &Fdt, filtered_symbols: HashSet, ) -> Result<()> { let Some(overlay_symbols_node) = overlay.root.subnode(SYMBOLS_NODE) else { return Ok(()); // If there are no symbols in the overlay, just skip it. }; let base_symbols_node = base.root.subnode_mut(SYMBOLS_NODE).unwrap(); for symbol in overlay_symbols_node.prop_names() { if !filtered_symbols.is_empty() && !filtered_symbols.contains(symbol) { continue; // Skip this symbol, it is not in the set of symbols we want. } let symbol_target: Path = overlay_symbols_node .get_prop::(symbol) .unwrap() .parse()?; // Parse location let (fragment_name, rest) = extract_fragment_and_subpath(&symbol_target)?; // Find the overlay fragment let fragment_node = overlay.root.subnode(fragment_name).ok_or_else(|| { Error::ApplyOverlayError(format!("invalid symbol path {symbol_target}")) })?; // Construct the new symbol path from `target-path` property value and the remainder of // the symbol location. Eg, for target-path = "/node", and overlay symbol path // "/fragment@X/__overlay__/path/to/subnode", the result is "/node/path/to/subnode". let new_path: String = fragment_node .get_prop::(TARGET_PATH_PROP) .unwrap_or_default() .parse::()? .push(&rest)? .into(); // Update base with new symbol path. `symbol` is a valid property name. base_symbols_node.set_prop(symbol, new_path).unwrap(); } Ok(()) } // Merge new reserved memory entries from overlay into base. fn merge_resvmem(base: &mut Vec, new_entries: Vec) { base.extend(new_entries); base.sort_by_key(|a| std::cmp::Reverse(a.address)); if let Some(mut entry) = base.pop() { let mut result = Vec::new(); while let Some(next_entry) = base.pop() { if next_entry.address <= entry.address + entry.size { entry.size = (entry.address + entry.size).max(next_entry.address + next_entry.size) - entry.address; } else { result.push(entry); entry = next_entry; } } result.push(entry); base.extend(result); } } /// Apply an overlay to the base FDT. /// /// # Arguments /// /// `base` - base FDT that will be updated with new nodes and properties. /// `overlay` - overlay FDT that will be applied to the base. Must contain symbols and fixups nodes. /// `filtered_symbols` - A slice of node labels (symbols) listing nodes which will be applied to the /// base. Values must correspond to the properties of overlay `__symbols__` node. If empty, the /// entire overlay is applied to base. pub fn apply_overlay>( base: &mut Fdt, mut overlay: Fdt, filter_symbols: impl std::iter::IntoIterator, ) -> Result<()> { // Analyze filtered symbols and find paths they point to. let (filter_symbols, filter_paths) = prepare_filtered_symbols(filter_symbols, &overlay)?; // Analyze the overlay tree and extract paths that have to be applied to base. let filtered_paths = collect_all_filtered_paths(filter_paths, &overlay)?; // Offset phandle property values in overlay nodes let max_phandle = get_max_phandle(&base.root); offset_phandle_values(&mut overlay, max_phandle)?; // Offset local phandle references in overlay properties update_local_refs(&mut overlay, max_phandle)?; // Apply phandle values for external references apply_external_fixups(base, &mut overlay)?; // Copy filtered overlay __symbols__ to base update_base_symbols(base, &overlay, filter_symbols)?; // Remove unneeded nodes do_overlay_filter(filtered_paths, &mut overlay); // Merge nodes from overlay into base for fragment_node in overlay.root.iter_subnodes() { overlay_fragment(fragment_node, base)?; } // Merge reserved regions merge_resvmem(&mut base.reserved_memory, overlay.reserved_memory); Ok(()) } #[cfg(test)] mod tests { use super::*; fn load_fdt(mut reader: impl std::io::Read) -> Result { let mut buffer = Vec::new(); reader.read_to_end(&mut buffer).map_err(Error::FdtIoError)?; Fdt::from_blob(&buffer[..]) } #[test] fn fdt_merge_resvmem() { let mut base = vec![ FdtReserveEntry::new(1000, 100), FdtReserveEntry::new(2000, 500), FdtReserveEntry::new(3000, 1000), ]; let new_entries = vec![ FdtReserveEntry::new(1010, 20), FdtReserveEntry::new(1050, 1000), FdtReserveEntry::new(2700, 500), ]; merge_resvmem(&mut base, new_entries); assert_eq!( base, vec![ FdtReserveEntry::new(1000, 1500), FdtReserveEntry::new(2700, 1300), ] ); } #[test] fn fdt_find_phandle_single() { let mut root = FdtNode::empty("").unwrap(); root.set_prop("a", 1u32).unwrap(); root.set_prop("b", 2u32).unwrap(); root.set_prop("phandle", 3u32).unwrap(); assert_eq!(get_node_phandle(&root), Some(3)); } #[test] fn fdt_find_phandle_none() { let mut root = FdtNode::empty("").unwrap(); root.set_prop("a", 1u32).unwrap(); root.set_prop("b", 2u32).unwrap(); assert_eq!(get_node_phandle(&root), None); } #[test] fn fdt_find_phandle_deprecated() { let mut root = FdtNode::empty("").unwrap(); root.set_prop("a", 1u32).unwrap(); root.set_prop("linux,phandle", 2u32).unwrap(); assert_eq!(get_node_phandle(&root), Some(2)); } #[test] fn fdt_find_max_phandle() { let mut root = FdtNode::empty("").unwrap(); root.set_prop("phandle", 2u32).unwrap(); let node_a = root.subnode_mut("a").unwrap(); node_a.set_prop("linux,phandle", 4u32).unwrap(); let node_b = root.subnode_mut("b").unwrap(); node_b.set_prop("phandle", 0xAu32).unwrap(); node_b.set_prop("linux,phandle", 0xAAu32).unwrap(); let node_c = node_b.subnode_mut("c").unwrap(); node_c.set_prop("linux,phandle", 0x10u32).unwrap(); node_c.set_prop("not-phandle", 0x11u32).unwrap(); let node_d = node_b.subnode_mut("d").unwrap(); node_d.set_prop("not-phandle", 0x20u32).unwrap(); node_b.subnode_mut("").unwrap(); assert_eq!(get_max_phandle(&root), 0x10); } #[test] fn fdt_offset_phandles() { let mut fdt = Fdt::new(&[]); fdt.root.set_prop("a", 1u32).unwrap(); fdt.root.set_prop("b", 2u32).unwrap(); fdt.root.set_prop("phandle", 3u32).unwrap(); let node_a = fdt.root.subnode_mut("a").unwrap(); node_a.set_prop("linux,phandle", 0x10u32).unwrap(); fdt.root.subnode_mut("b").unwrap(); offset_phandle_values(&mut fdt, 100).unwrap(); for (prop, exp_val) in fdt.root.prop_names().zip([1u32, 2, 103].into_iter()) { assert_eq!(fdt.root.get_prop::(prop).unwrap(), exp_val); } let node = fdt.get_node("/a").unwrap(); assert_eq!(node.get_prop::(LINUX_PHANDLE_PROP).unwrap(), 116); let node = fdt.get_node("/b").unwrap(); assert!(node.prop_names().next().is_none()); } #[test] fn fdt_collect_local_references() { let mut fdt = Fdt::new(&[]); let fixups_node = fdt.root.subnode_mut(LOCAL_FIXUPS_NODE).unwrap(); fixups_node.set_prop("p1", vec![0u32, 4u32]).unwrap(); let fixups_subnode = fixups_node.subnode_mut("subnode1").unwrap(); fixups_subnode.set_prop("p2", vec![8u32]).unwrap(); let fixups_subnode = fixups_node.subnode_mut("subnode2").unwrap(); fixups_subnode.set_prop("p1", vec![16u32, 24u32]).unwrap(); let paths = collect_local_fixup_paths(&fdt).unwrap(); assert_eq!(paths.len(), 3); let expected_paths: BTreeMap> = BTreeMap::from([ ( ROOT_NODE.parse().unwrap(), vec![PhandlePin("p1".into(), 0), PhandlePin("p1".into(), 4)], ), ( "/subnode1".parse().unwrap(), vec![PhandlePin("p2".into(), 8)], ), ( "/subnode2".parse().unwrap(), vec![PhandlePin("p1".into(), 16), PhandlePin("p1".into(), 24)], ), ]); for (key, value) in expected_paths { assert!(value.eq(paths.get(&key).unwrap())); } } fn make_fragment0() -> FdtNode { let mut fragment_node = FdtNode::empty("fragment@0").unwrap(); fragment_node.set_prop("target-path", ROOT_NODE).unwrap(); let overlay_node = fragment_node.subnode_mut(OVERLAY_NODE).unwrap(); overlay_node.set_prop("root-prop1", 1u32).unwrap(); overlay_node .set_prop("root-prop2", vec![1u32, 2u32, 3u32]) .unwrap(); let overlay_child_node = overlay_node.subnode_mut("child1").unwrap(); overlay_child_node.set_prop("prop1", 10u32).unwrap(); overlay_child_node .set_prop("prop2", vec![10u32, 20u32, 30u32]) .unwrap(); fragment_node } fn make_fragment1() -> FdtNode { let mut fragment_node = FdtNode::empty("fragment@1").unwrap(); fragment_node.set_prop("target-path", ROOT_NODE).unwrap(); let overlay_node = fragment_node.subnode_mut(OVERLAY_NODE).unwrap(); overlay_node.set_prop("root-prop1", "abc").unwrap(); overlay_node.set_prop("root-prop3", 100u64).unwrap(); let overlay_child_node = overlay_node.subnode_mut("child1").unwrap(); overlay_child_node.set_prop("prop1", 0u32).unwrap(); let _ = overlay_node.subnode_mut("child2").unwrap(); fragment_node } #[test] fn fdt_test_overlay_nodes() { let mut base = Fdt::new(&[]); let fragment_node = make_fragment0(); overlay_fragment(&fragment_node, &mut base).unwrap(); assert_eq!(base.root.get_prop::("root-prop1").unwrap(), 1u32); assert_eq!( base.root.get_prop::>("root-prop2").unwrap(), vec![1u32, 2u32, 3u32] ); let child_node = base.get_node("/child1").unwrap(); assert_eq!(child_node.get_prop::("prop1").unwrap(), 10u32); assert_eq!( child_node.get_prop::>("prop2").unwrap(), vec![10u32, 20u32, 30u32] ); let fragment_node = make_fragment1(); overlay_fragment(&fragment_node, &mut base).unwrap(); assert_eq!( base.root.get_prop::>("root-prop1").unwrap(), vec![b'a', b'b', b'c', 0u8] ); assert_eq!(base.root.get_prop::("root-prop3").unwrap(), 100u64); let child_node = base.get_node("/child1").unwrap(); assert_eq!(child_node.get_prop::("prop1").unwrap(), 0u32); let child_node = base.get_node("/child2").unwrap(); assert!(child_node.prop_names().next().is_none()); } #[test] fn fdt_overlay_symbols() { let mut base = Fdt::new(&[]); let symbols = base.root.subnode_mut(SYMBOLS_NODE).unwrap(); symbols.set_prop("n1", "/path/to/node1").unwrap(); symbols.set_prop("n2", "/path/to/node2").unwrap(); let mut overlay = Fdt::new(&[]); let symbols = overlay.root.subnode_mut(SYMBOLS_NODE).unwrap(); symbols .set_prop("n1", "/fragment@0/__overlay__/node1") .unwrap(); symbols .set_prop("n3", "/fragment@0/__overlay__/path/to/node3") .unwrap(); let fragment = overlay.root.subnode_mut("fragment@0").unwrap(); fragment.set_prop("target-path", ROOT_NODE).unwrap(); update_base_symbols(&mut base, &overlay, [].into()).unwrap(); let symbols = base.root.subnode_mut(SYMBOLS_NODE).unwrap(); assert_eq!(symbols.get_prop::("n1").unwrap(), "/node1"); assert_eq!(symbols.get_prop::("n2").unwrap(), "/path/to/node2"); assert_eq!(symbols.get_prop::("n3").unwrap(), "/path/to/node3"); } #[test] fn fdt_overlay_filtered_symbols() { let mut base = Fdt::new(&[]); let symbols = base.root.subnode_mut(SYMBOLS_NODE).unwrap(); symbols.set_prop("n1", "/path/to/node1").unwrap(); symbols.set_prop("n2", "/path/to/node2").unwrap(); let mut overlay = Fdt::new(&[]); let symbols = overlay.root.subnode_mut(SYMBOLS_NODE).unwrap(); symbols .set_prop("n1", "/fragment@0/__overlay__/node1") .unwrap(); symbols .set_prop("n3", "/fragment@0/__overlay__/path/to/node3") .unwrap(); symbols .set_prop("not-this", "/fragment@0/__overlay__/path/to/not-this") .unwrap(); symbols .set_prop( "not-this-either", "/fragment@0/__overlay__/path/to/not-this-either", ) .unwrap(); let fragment = overlay.root.subnode_mut("fragment@0").unwrap(); fragment.set_prop("target-path", ROOT_NODE).unwrap(); update_base_symbols( &mut base, &overlay, ["n1".to_string(), "n3".to_string()].into(), ) .unwrap(); let symbols = base.root.subnode(SYMBOLS_NODE).unwrap(); assert_eq!(symbols.get_prop::("n1").unwrap(), "/node1"); assert_eq!(symbols.get_prop::("n2").unwrap(), "/path/to/node2"); assert_eq!(symbols.get_prop::("n3").unwrap(), "/path/to/node3"); assert!(symbols.get_prop::("not-this").is_none()); assert!(symbols.get_prop::("not-this-either").is_none()); update_base_symbols(&mut base, &overlay, [].into()).unwrap(); let symbols = base.root.subnode(SYMBOLS_NODE).unwrap(); assert_eq!(symbols.get_prop::("n1").unwrap(), "/node1"); assert_eq!(symbols.get_prop::("n2").unwrap(), "/path/to/node2"); assert_eq!(symbols.get_prop::("n3").unwrap(), "/path/to/node3"); assert_eq!( symbols.get_prop::("not-this").unwrap(), "/path/to/not-this" ); assert_eq!( symbols.get_prop::("not-this-either").unwrap(), "/path/to/not-this-either" ); } fn make_fdt_with_local_refs(references: &[(&str, u32)]) -> Result { /* Returns this structure: / node1 (phandle=1) node1-1 (phandle=2) node1-1-1 (phandle=3) node1-1-2 (phandle=4) node1-2 (phandle=5) node1-2-1 (phandle=6) node2 (phandle=7) node2-1 (phandle=8) node2-2 (phandle=9) node2-3 (phandle=10) node2-3-1 (phandle=11) node3 (phandle=12) node3-1 (phandle=13) __local_fixups__ __symbols__ */ let mut fdt = Fdt::new(&[]); let root = fdt.root_mut(); let node1 = root.subnode_mut("node1")?; node1.set_prop(PHANDLE_PROP, 1u32)?; let node11 = node1.subnode_mut("node1-1")?; node11.set_prop(PHANDLE_PROP, 2u32)?; let node111 = node11.subnode_mut("node1-1-1")?; node111.set_prop(PHANDLE_PROP, 3u32)?; let node112 = node11.subnode_mut("node1-1-2")?; node112.set_prop(PHANDLE_PROP, 4u32)?; let node12 = node1.subnode_mut("node1-2")?; node12.set_prop(PHANDLE_PROP, 5u32)?; let node121 = node12.subnode_mut("node1-2-1")?; node121.set_prop(PHANDLE_PROP, 6u32)?; let node2 = root.subnode_mut("node2")?; node2.set_prop(PHANDLE_PROP, 7u32)?; let node21 = node2.subnode_mut("node2-1")?; node21.set_prop(PHANDLE_PROP, 8u32)?; let node22 = node2.subnode_mut("node2-2")?; node22.set_prop(PHANDLE_PROP, 9u32)?; let node23 = node2.subnode_mut("node2-3")?; node23.set_prop(PHANDLE_PROP, 10u32)?; let node231 = node23.subnode_mut("node2-3-1")?; node231.set_prop(PHANDLE_PROP, 11u32)?; let node3 = root.subnode_mut("node3")?; node3.set_prop(PHANDLE_PROP, 12u32)?; let node31 = node3.subnode_mut("node3-1")?; node31.set_prop(PHANDLE_PROP, 13u32)?; let symbols = root.subnode_mut(SYMBOLS_NODE)?; symbols.set_prop("node1", "/node1")?; symbols.set_prop("node1-1", "/node1/node1-1")?; symbols.set_prop("node1-1-2", "/node1/node1-1/node1-1-2")?; symbols.set_prop("node2", "/node2")?; symbols.set_prop("node2-3-1", "/node2/node2-3/node2-3-1")?; for (loc, phandle_val) in references { let (path, pin) = parse_path_with_prop(loc)?; // Write reference value in the tree sutrcture let mut node = fdt .get_node_mut(path.clone()) .ok_or_else(|| Error::InvalidPath(path.to_string()))?; node.set_prop(&pin.0, *phandle_val)?; // Write reference path to local fixups node node = fdt.root_mut().subnode_mut(LOCAL_FIXUPS_NODE)?; for nname in path.iter() { node = node.subnode_mut(nname)?; } node.set_prop(&pin.0, 0u32)?; } Ok(fdt) } #[test] fn fdt_collect_filter_roots() { let fdt = make_fdt_with_local_refs(&[]).unwrap(); let (symbols, paths) = prepare_filtered_symbols::<&str>([], &fdt).unwrap(); assert!(symbols.is_empty()); assert!(paths.is_empty()); let (symbols, paths) = prepare_filtered_symbols(["node1"], &fdt).unwrap(); assert_eq!(symbols.len(), 1); assert_eq!(paths.len(), 1); assert!(symbols.contains("node1")); assert!(paths.contains(&"/node1".parse().unwrap())); let (symbols, paths) = prepare_filtered_symbols(["node1", "node1-1", "node1"], &fdt).unwrap(); assert_eq!(symbols.len(), 2); assert!(symbols.contains("node1") && symbols.contains("node1-1")); assert!( paths.contains(&"/node1".parse().unwrap()) && paths.contains(&"/node1/node1-1".parse().unwrap()) ); prepare_filtered_symbols(["node1", "node1-1", "node1", "nosuchnode"], &fdt) .expect_err("no symbol"); prepare_filtered_symbols(["node1-1-1"], &fdt).expect_err("no symbol"); prepare_filtered_symbols(["node1"], &Fdt::new(&[])).expect_err("no symbols node"); } #[test] fn fdt_collect_filtered_paths() { // /node1/node1-2/node1-2-1:prop:0 => /node2/node2-3/node2-3-1 (phandle=11) // /node1:prop:0 => /node3 (phandle=12) let fdt = make_fdt_with_local_refs(&[ ("/node1/node1-2/node1-2-1:prop:0", 11), ("/node1:prop:0", 12), ]) .unwrap(); let (_, paths) = prepare_filtered_symbols(["node1"], &fdt).unwrap(); let filtered = collect_all_filtered_paths(paths, &fdt).unwrap(); // This is referenced by the symbol that was given. assert!(filtered.contains(&"/node1".parse().unwrap())); // This is referenced by the phandle value stored in the property. assert!(filtered.contains(&"/node3".parse().unwrap())); // References that appeart in the subtree of the filtered node are not included. assert!(!filtered.contains(&"/node2/node2-3/node2-3-1".parse().unwrap())); } #[test] fn fdt_collect_filtered_paths_circular() { // /node1:prop:0 => /node2/node2-3/node2-3-1 (phandle=11) // /node2/node2-3:prop:0 => /node1/node1-1 (phandle=2) let fdt = make_fdt_with_local_refs(&[("/node1:prop:0", 11), ("/node2/node2-3:prop:0", 2)]) .unwrap(); let (_, paths) = prepare_filtered_symbols(["node1-1"], &fdt).unwrap(); let filtered = collect_all_filtered_paths(paths, &fdt).unwrap(); // This is referenced by the symbol that was given. assert!(filtered.contains(&"/node1/node1-1".parse().unwrap())); // This is referenced by a parent node of the given symbol. assert!(filtered.contains(&"/node2/node2-3/node2-3-1".parse().unwrap())); // Above two paths cover all references assert_eq!(filtered.len(), 2); } #[test] fn fdt_collect_filtered_paths_dangling() { // /node1:prop:0 => /node2/node2-3/node2-3-1 (phandle=11) // /node2/node2-3:prop:0 => dangling phandle=200 let fdt = make_fdt_with_local_refs(&[("/node1:prop:0", 11), ("/node2/node2-3:prop:0", 200)]) .unwrap(); let (_, paths) = prepare_filtered_symbols(["node1"], &fdt).unwrap(); collect_all_filtered_paths(paths, &fdt).expect_err("dangling phandle"); } #[test] fn fdt_collect_filtered_paths_minimal() { // /node1:prop:0 => /node3/node3-1 (phandle=13) // /node1/node1-1:prop:0 => /node1/node1-1/node1-1-2 (phandle=4) // /node1/node1-1/node1-1-2:prop:0 => /node1 (phandle=1) // /node3/node3-1:prop:0 => /node3 (phandle=12) let fdt = make_fdt_with_local_refs(&[ ("/node1:prop:0", 13), ("/node1/node1-1:prop:0", 4), ("/node1/node1-1/node1-1-2:prop:0", 1), ("/node3/node3-1:prop:0", 12), ]) .unwrap(); let (_, paths) = prepare_filtered_symbols(["node1"], &fdt).unwrap(); let filtered = collect_all_filtered_paths(paths, &fdt).unwrap(); assert!(filtered.contains(&"/node1".parse().unwrap())); assert!(filtered.contains(&"/node3".parse().unwrap())); // Above two paths cover all references assert_eq!(filtered.len(), 2); } fn count_nodes(root: &FdtNode) -> usize { let mut count = 1; for s in root.iter_subnodes() { count += count_nodes(s); } count } #[test] fn fdt_do_filter_simple() { let l1 = "/node1"; let l2 = "/node2"; let l3 = "/node3"; let fdt = &mut make_fdt_with_local_refs(&[]).unwrap(); do_overlay_filter([].into(), fdt); assert!(fdt.get_node(l1).is_some()); assert!(fdt.get_node(l2).is_some()); assert!(fdt.get_node(l3).is_some()); do_overlay_filter([l1.try_into().unwrap(), l2.try_into().unwrap()].into(), fdt); assert!(fdt.get_node(l1).is_some()); assert!(fdt.get_node(l2).is_some()); assert!(fdt.get_node(l3).is_none()); } #[test] fn fdt_do_filter_subnodes() { let l1: Path = "/node1/node1-1".parse().unwrap(); let fdt = &mut make_fdt_with_local_refs(&[]).unwrap(); do_overlay_filter([l1.clone()].into(), fdt); assert!(fdt.get_node(l1).is_some()); assert_eq!(count_nodes(&fdt.root), 3); } #[test] fn fdt_do_filter_deep() { let l1: Path = "/node1/node1-1/node1-1-1".parse().unwrap(); let l2: Path = "/node2/node2-2".parse().unwrap(); let l3: Path = "/node2/node2-3/node2-3-1".parse().unwrap(); let fdt = &mut make_fdt_with_local_refs(&[]).unwrap(); do_overlay_filter([l1.clone(), l2.clone(), l3.clone()].into(), fdt); assert!(fdt.get_node(l1).is_some()); assert!(fdt.get_node(l2).is_some()); assert!(fdt.get_node(l3).is_some()); assert_eq!(count_nodes(&fdt.root), 8); } #[test] fn fdt_offset_local_references() { let file = include_bytes!("../test-files/local_refs.dtb").as_slice(); let mut fdt = load_fdt(file).unwrap(); let node = fdt.get_node("/fragment@0/__overlay__/node1").unwrap(); assert_eq!(node.get_prop::("p2").unwrap(), 0x01); assert_eq!(node.get_prop::("p3").unwrap(), 0xaa); let node = fdt.get_node("/fragment@0/__overlay__/node1/node2").unwrap(); assert_eq!(node.get_prop::("p1").unwrap(), 0xaa); assert_eq!(node.get_prop::("p2").unwrap(), 0x02); assert_eq!(node.get_prop::("p3").unwrap(), 0x03); let node = fdt.get_node("/fragment@0/__overlay__/node1/node3").unwrap(); assert_eq!(node.get_prop::("p1").unwrap(), 0x01); update_local_refs(&mut fdt, 5).unwrap(); let node = fdt.get_node("/fragment@0/__overlay__/node1").unwrap(); assert_eq!(node.get_prop::("p2").unwrap(), 0x06); assert_eq!(node.get_prop::("p3").unwrap(), 0xaa); let node = fdt.get_node("/fragment@0/__overlay__/node1/node2").unwrap(); assert_eq!(node.get_prop::("p1").unwrap(), 0xaa); assert_eq!(node.get_prop::("p2").unwrap(), 0x07); assert_eq!(node.get_prop::("p3").unwrap(), 0x08); let node = fdt.get_node("/fragment@0/__overlay__/node1/node3").unwrap(); assert_eq!(node.get_prop::("p1").unwrap(), 0x06); } #[test] fn fdt_collect_symbols() { let base = load_fdt(include_bytes!("../test-files/external_refs_base.dtb").as_slice()).unwrap(); let mut overlay = load_fdt(include_bytes!("../test-files/external_refs_overlay.dtb").as_slice()).unwrap(); let paths = [ "/fragment@0/__overlay__/node1:p2:0", "/fragment@0/__overlay__/node1/node2:p3:4", "/fragment@0/__overlay__/node1/node3:p1:0", ]; for p in paths.iter() { let (path, pin) = parse_path_with_prop(p).unwrap(); let node = overlay.get_node(path).unwrap(); let ref_val = node.phandle_at_offset(&pin.0, pin.1 as usize).unwrap(); assert_eq!(ref_val, 0xffffffff); } apply_external_fixups(&base, &mut overlay).unwrap(); for (p, exp_val) in paths.iter().zip([1u32, 2u32, 2u32].into_iter()) { let (path, pin) = parse_path_with_prop(p).unwrap(); let node = overlay.get_node(path).unwrap(); let ref_val = node.phandle_at_offset(&pin.0, pin.1 as usize).unwrap(); assert_eq!(ref_val, exp_val); } } #[test] fn fdt_apply_overlay_complete() { let mut base = load_fdt(include_bytes!("../test-files/base.dtb").as_slice()).unwrap(); assert_eq!(count_nodes(&base.root), 7); let overlay = load_fdt(include_bytes!("../test-files/overlay.dtb").as_slice()).unwrap(); apply_overlay(&mut base, overlay, ["mydev"]).unwrap(); assert!(base.get_node("/mydev@8000000").is_some()); assert!(base.get_node("/mydev@8000000/devnode1").is_none()); assert!(base.get_node("/mydev@8001000").is_none()); assert_eq!(count_nodes(&base.root), 8); let overlay = load_fdt(include_bytes!("../test-files/overlay.dtb").as_slice()).unwrap(); apply_overlay(&mut base, overlay, ["mydev"]).unwrap(); assert!(base.get_node("/mydev@8000000").is_some()); assert!(base.get_node("/mydev@8001000").is_none()); assert_eq!(count_nodes(&base.root), 8); let overlay = load_fdt(include_bytes!("../test-files/overlay.dtb").as_slice()).unwrap(); apply_overlay(&mut base, overlay, ["mydev2"]).unwrap(); assert!(base.get_node("/mydev@8000000").is_some()); assert!(base.get_node("/mydev@8001000").is_some()); assert!(base.get_node("/mydev@8000000/devnode1").is_none()); assert!(base.get_node("/mydev@8001000/devnode1").is_none()); assert_eq!(count_nodes(&base.root), 9); } #[test] fn fdt_overlay_filter_with_dependencies() { let mut base = Fdt::new(&[]); let overlay = load_fdt(include_bytes!("../test-files/overlay_deps.dtb").as_slice()).unwrap(); apply_overlay(&mut base, overlay, ["dev2"]).unwrap(); assert_eq!(count_nodes(&base.root), 6); let n = base.get_node("/n0-1").unwrap(); assert_eq!(n.get_prop::("prop1"), Some(1)); assert!(base.get_node("/no-1/n2").is_none()); let n = base.get_node("/n0-1/n1").unwrap(); assert_eq!(n.get_prop::("prop1"), Some(2)); let n = base.get_node("/n0-2").unwrap(); assert_eq!(n.get_prop::("prop1"), Some(4)); assert!(base.get_node("/n0-2/n2").is_none()); let n = base.get_node("/n0-2/n1").unwrap(); assert_eq!(n.get_prop::("prop1"), Some(5)); } #[test] fn fdt_overlay_skips_children() { let mut base = load_fdt(include_bytes!("../test-files/external_refs_base.dtb").as_slice()).unwrap(); let overlay = load_fdt(include_bytes!("../test-files/external_refs_overlay.dtb").as_slice()).unwrap(); apply_overlay(&mut base, overlay, ["n1"]).unwrap(); assert_eq!(count_nodes(&base.root), 6); assert!(base.get_node("/node1").is_some()); assert!(base.get_node("/node1/node2").is_none()); assert!(base.get_node("/node1/node3").is_none()); } }