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 = "/";
fn prepare_filtered_symbols<T: AsRef<str>>(
start_symbols: impl std::iter::IntoIterator<Item = T>,
fdt: &Fdt,
) -> Result<(HashSet<String>, Vec<Path>)> {
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))
}
fn collect_phandle_refs_from_props(fixup_node: &FdtNode, tree_node: &FdtNode) -> Result<Vec<u32>> {
let mut phandles = vec![];
for propname in fixup_node.prop_names() {
for phandle_offset in fixup_node.get_prop::<Vec<u32>>(propname).unwrap() {
phandles.push(
tree_node
.phandle_at_offset(propname, phandle_offset as usize)
.ok_or(Error::PropertyValueInvalid)?,
);
}
}
Ok(phandles)
}
fn collect_all_references_by_path(
path: &Path,
root: &FdtNode,
local_fixups_node: &FdtNode,
) -> Result<HashSet<u32>> {
let mut tree_node = root;
let mut fixup_node = local_fixups_node;
let mut phandle_refs = HashSet::<u32>::new();
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); }
phandle_refs.extend(collect_phandle_refs_from_props(fixup_node, tree_node)?);
}
Ok(phandle_refs)
}
fn get_all_phandles(fdt: &Fdt) -> BTreeMap<u32, Path> {
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
}
fn minimize_paths(paths: &mut Vec<Path>) {
paths.sort();
paths.dedup_by(|a, b| a.is_child_of(b));
}
fn collect_all_filtered_paths(mut start_paths: Vec<Path>, fdt: &Fdt) -> Result<Vec<Path>> {
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); };
let all_phandles = get_all_phandles(fdt); let mut result_paths = HashSet::<Path>::with_capacity(start_paths.len());
let mut pending_paths: VecDeque<_> = start_paths.iter().collect(); while let Some(path) = pending_paths.pop_front() {
if result_paths.contains(path) {
continue; }
let phandles = collect_all_references_by_path(path, &fdt.root, local_fixups_node)?;
for ph in phandles {
pending_paths.push_back(all_phandles.get(&ph).ok_or(Error::PropertyValueInvalid)?);
}
result_paths.insert(path.to_owned());
}
let mut result_paths = result_paths.into_iter().collect();
minimize_paths(&mut result_paths);
Ok(result_paths)
}
fn do_overlay_filter(filtered_paths: Vec<Path>, 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;
}
fn get_node_phandle(node: &FdtNode) -> Option<u32> {
node.get_prop(PHANDLE_PROP)
.or_else(|| node.get_prop(LINUX_PHANDLE_PROP))
}
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
}
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(())
}
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(())
}
fn collect_local_fixup_paths(fdt: &Fdt) -> Result<BTreeMap<Path, Vec<PhandlePin>>> {
let mut local_phandles = BTreeMap::<Path, Vec<PhandlePin>>::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));
while let Some((path, node)) = stack.pop_front() {
for propname in node.prop_names() {
let offsets = node.get_prop::<Vec<u32>>(propname).ok_or_else(|| {
Error::ApplyOverlayError(format!(
"fixup node {} contains invalid offset array",
node.name
))
})?;
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)));
}
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<Path, Vec<PhandlePin>>,
delta: u32,
) -> Result<()> {
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)
}
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::<String>(symbol)?;
let target_node = fdt.get_node(symbol.as_str())?;
Some((symbol, get_node_phandle(target_node)?))
}
fn apply_external_fixups(base: &Fdt, overlay: &mut Fdt) -> Result<()> {
let Some(fixups_node) = overlay.root.subnode(FIXUPS_NODE) else {
return Ok(()); };
let mut paths_to_update = BTreeMap::<(String, u32), Vec<String>>::new();
for fixup_symbol in fixups_node.prop_names() {
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"))
})?;
let target_paths: Vec<String> = 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);
}
for ((base_path, phandle), paths) in paths_to_update {
for path in paths {
let (path, pin) = parse_path_with_prop(&path)?;
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 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(())
}
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(())
}
fn overlay_fragment(fragment_node: &FdtNode, base: &mut Fdt) -> Result<()> {
let Some(overlay_node) = fragment_node.subnode(OVERLAY_NODE) else {
return Ok(()); };
let Some(target_path) = fragment_node.get_prop::<String>(TARGET_PATH_PROP) else {
return Ok(()); };
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)
}
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(); let rest = path_iter.collect::<Vec<_>>();
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<String>,
) -> Result<()> {
let Some(overlay_symbols_node) = overlay.root.subnode(SYMBOLS_NODE) else {
return Ok(()); };
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; }
let symbol_target: Path = overlay_symbols_node
.get_prop::<String>(symbol)
.unwrap()
.parse()?;
let (fragment_name, rest) = extract_fragment_and_subpath(&symbol_target)?;
let fragment_node = overlay.root.subnode(fragment_name).ok_or_else(|| {
Error::ApplyOverlayError(format!("invalid symbol path {symbol_target}"))
})?;
let new_path: String = fragment_node
.get_prop::<String>(TARGET_PATH_PROP)
.unwrap_or_default()
.parse::<Path>()?
.push(&rest)?
.into();
base_symbols_node.set_prop(symbol, new_path).unwrap();
}
Ok(())
}
fn merge_resvmem(base: &mut Vec<FdtReserveEntry>, new_entries: Vec<FdtReserveEntry>) {
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);
}
}
pub fn apply_overlay<T: AsRef<str>>(
base: &mut Fdt,
mut overlay: Fdt,
filter_symbols: impl std::iter::IntoIterator<Item = T>,
) -> Result<()> {
let (filter_symbols, filter_paths) = prepare_filtered_symbols(filter_symbols, &overlay)?;
let filtered_paths = collect_all_filtered_paths(filter_paths, &overlay)?;
let max_phandle = get_max_phandle(&base.root);
offset_phandle_values(&mut overlay, max_phandle)?;
update_local_refs(&mut overlay, max_phandle)?;
apply_external_fixups(base, &mut overlay)?;
update_base_symbols(base, &overlay, filter_symbols)?;
do_overlay_filter(filtered_paths, &mut overlay);
for fragment_node in overlay.root.iter_subnodes() {
overlay_fragment(fragment_node, base)?;
}
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<Fdt> {
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::<u32>(prop).unwrap(), exp_val);
}
let node = fdt.get_node("/a").unwrap();
assert_eq!(node.get_prop::<u32>(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<Path, Vec<PhandlePin>> = 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::<u32>("root-prop1").unwrap(), 1u32);
assert_eq!(
base.root.get_prop::<Vec<u32>>("root-prop2").unwrap(),
vec![1u32, 2u32, 3u32]
);
let child_node = base.get_node("/child1").unwrap();
assert_eq!(child_node.get_prop::<u32>("prop1").unwrap(), 10u32);
assert_eq!(
child_node.get_prop::<Vec<u32>>("prop2").unwrap(),
vec![10u32, 20u32, 30u32]
);
let fragment_node = make_fragment1();
overlay_fragment(&fragment_node, &mut base).unwrap();
assert_eq!(
base.root.get_prop::<Vec<u8>>("root-prop1").unwrap(),
vec![b'a', b'b', b'c', 0u8]
);
assert_eq!(base.root.get_prop::<u64>("root-prop3").unwrap(), 100u64);
let child_node = base.get_node("/child1").unwrap();
assert_eq!(child_node.get_prop::<u32>("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::<String>("n1").unwrap(), "/node1");
assert_eq!(symbols.get_prop::<String>("n2").unwrap(), "/path/to/node2");
assert_eq!(symbols.get_prop::<String>("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::<String>("n1").unwrap(), "/node1");
assert_eq!(symbols.get_prop::<String>("n2").unwrap(), "/path/to/node2");
assert_eq!(symbols.get_prop::<String>("n3").unwrap(), "/path/to/node3");
assert!(symbols.get_prop::<String>("not-this").is_none());
assert!(symbols.get_prop::<String>("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::<String>("n1").unwrap(), "/node1");
assert_eq!(symbols.get_prop::<String>("n2").unwrap(), "/path/to/node2");
assert_eq!(symbols.get_prop::<String>("n3").unwrap(), "/path/to/node3");
assert_eq!(
symbols.get_prop::<String>("not-this").unwrap(),
"/path/to/not-this"
);
assert_eq!(
symbols.get_prop::<String>("not-this-either").unwrap(),
"/path/to/not-this-either"
);
}
fn make_fdt_with_local_refs(references: &[(&str, u32)]) -> Result<Fdt> {
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)?;
let mut node = fdt
.get_node_mut(path.clone())
.ok_or_else(|| Error::InvalidPath(path.to_string()))?;
node.set_prop(&pin.0, *phandle_val)?;
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() {
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();
assert!(filtered.contains(&"/node1".parse().unwrap()));
assert!(filtered.contains(&"/node3".parse().unwrap()));
assert!(!filtered.contains(&"/node2/node2-3/node2-3-1".parse().unwrap()));
}
#[test]
fn fdt_collect_filtered_paths_circular() {
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();
assert!(filtered.contains(&"/node1/node1-1".parse().unwrap()));
assert!(filtered.contains(&"/node2/node2-3/node2-3-1".parse().unwrap()));
assert_eq!(filtered.len(), 2);
}
#[test]
fn fdt_collect_filtered_paths_dangling() {
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() {
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()));
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::<u32>("p2").unwrap(), 0x01);
assert_eq!(node.get_prop::<u32>("p3").unwrap(), 0xaa);
let node = fdt.get_node("/fragment@0/__overlay__/node1/node2").unwrap();
assert_eq!(node.get_prop::<u32>("p1").unwrap(), 0xaa);
assert_eq!(node.get_prop::<u32>("p2").unwrap(), 0x02);
assert_eq!(node.get_prop::<u32>("p3").unwrap(), 0x03);
let node = fdt.get_node("/fragment@0/__overlay__/node1/node3").unwrap();
assert_eq!(node.get_prop::<u32>("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::<u32>("p2").unwrap(), 0x06);
assert_eq!(node.get_prop::<u32>("p3").unwrap(), 0xaa);
let node = fdt.get_node("/fragment@0/__overlay__/node1/node2").unwrap();
assert_eq!(node.get_prop::<u32>("p1").unwrap(), 0xaa);
assert_eq!(node.get_prop::<u32>("p2").unwrap(), 0x07);
assert_eq!(node.get_prop::<u32>("p3").unwrap(), 0x08);
let node = fdt.get_node("/fragment@0/__overlay__/node1/node3").unwrap();
assert_eq!(node.get_prop::<u32>("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::<u32>("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::<u32>("prop1"), Some(2));
let n = base.get_node("/n0-2").unwrap();
assert_eq!(n.get_prop::<u32>("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::<u32>("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());
}
}