diff --git a/src/fdt/mod.rs b/src/fdt/mod.rs index ebe14de..a0d71f5 100644 --- a/src/fdt/mod.rs +++ b/src/fdt/mod.rs @@ -20,7 +20,7 @@ mod node; mod property; use core::ffi::CStr; use core::mem::offset_of; -use core::ptr; +use core::{fmt, ptr}; pub use node::FdtNode; pub use property::FdtProperty; @@ -491,6 +491,15 @@ impl<'a> Fdt<'a> { } } +impl fmt::Display for Fdt<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "/dts-v1/;")?; + writeln!(f)?; + let root = self.root().map_err(|_| fmt::Error)?; + root.fmt_recursive(f, 0) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/fdt/node.rs b/src/fdt/node.rs index 2e77309..30d252f 100644 --- a/src/fdt/node.rs +++ b/src/fdt/node.rs @@ -8,6 +8,8 @@ //! A read-only API for inspecting a device tree node. +use core::fmt; + use super::{FDT_TAGSIZE, Fdt, FdtToken}; use crate::error::FdtError; use crate::fdt::property::{FdtPropIter, FdtProperty}; @@ -149,6 +151,43 @@ impl<'a> FdtNode<'a> { offset: self.offset, } } + + pub(crate) fn fmt_recursive(&self, f: &mut fmt::Formatter<'_>, indent: usize) -> fmt::Result { + let name = self.name().map_err(|_| fmt::Error)?; + if name.is_empty() { + writeln!(f, "{:indent$}/ {{", "", indent = indent)?; + } else { + writeln!(f, "{:indent$}{} {{", "", name, indent = indent)?; + } + + let mut has_properties = false; + for prop in self.properties() { + has_properties = true; + match prop { + Ok(prop) => prop.fmt(f, indent + 4)?, + Err(_e) => { + writeln!(f, "")?; + } + } + } + + let mut first_child = true; + for child in self.children() { + if !first_child || has_properties { + writeln!(f)?; + } + + first_child = false; + match child { + Ok(child) => child.fmt_recursive(f, indent + 4)?, + Err(_e) => { + writeln!(f, "")?; + } + } + } + + writeln!(f, "{:indent$}}};", "", indent = indent) + } } /// An iterator over the children of a device tree node. diff --git a/src/fdt/property.rs b/src/fdt/property.rs index 5477e29..2de5a82 100644 --- a/src/fdt/property.rs +++ b/src/fdt/property.rs @@ -9,6 +9,7 @@ //! A read-only API for inspecting a device tree property. use core::ffi::CStr; +use core::fmt; use zerocopy::{FromBytes, big_endian}; @@ -125,6 +126,59 @@ impl<'a> FdtProperty<'a> { pub fn as_str_list(&self) -> impl Iterator { FdtStringListIterator { value: self.value } } + + pub(crate) fn fmt(&self, f: &mut fmt::Formatter<'_>, indent: usize) -> fmt::Result { + write!(f, "{:indent$}{}", "", self.name, indent = indent)?; + + if self.value.is_empty() { + writeln!(f, ";")?; + return Ok(()); + } + + let is_printable = self + .value + .iter() + .all(|&ch| ch.is_ascii_graphic() || ch == b' ' || ch == 0); + let has_empty = self.value.windows(2).any(|window| window == [0, 0]); + if is_printable && self.value.ends_with(&[0]) && !has_empty { + let mut strings = self.as_str_list(); + if let Some(first) = strings.next() { + write!(f, " = \"{first}\"")?; + for s in strings { + write!(f, ", \"{s}\"")?; + } + writeln!(f, ";")?; + return Ok(()); + } + } + + if self.value.len().is_multiple_of(4) { + write!(f, " = <")?; + for (i, chunk) in self.value.chunks_exact(4).enumerate() { + if i > 0 { + write!(f, " ")?; + } + let val = u32::from_be_bytes( + chunk + .try_into() + .expect("u32::from_be_bytes() should always succeed with 4 bytes"), + ); + write!(f, "0x{val:02x}")?; + } + writeln!(f, ">;")?; + } else { + write!(f, " = [")?; + for (i, byte) in self.value.iter().enumerate() { + if i > 0 { + write!(f, " ")?; + } + write!(f, "{byte:02x}")?; + } + writeln!(f, "];")?; + } + + Ok(()) + } } /// An iterator over the properties of a device tree node. diff --git a/tests/dtb/test_pretty_print.dtb b/tests/dtb/test_pretty_print.dtb new file mode 100644 index 0000000..ea91a93 Binary files /dev/null and b/tests/dtb/test_pretty_print.dtb differ diff --git a/tests/dts/test.dts b/tests/dts/test.dts index ec393ee..b212005 100644 --- a/tests/dts/test.dts +++ b/tests/dts/test.dts @@ -2,5 +2,5 @@ / { prop1 = "test"; - prop2 = <0x01020304>; + prop2 = <0x1020304>; }; diff --git a/tests/dts/test_children_nested.dts b/tests/dts/test_children_nested.dts index 83a1b31..f64c567 100644 --- a/tests/dts/test_children_nested.dts +++ b/tests/dts/test_children_nested.dts @@ -10,6 +10,5 @@ }; child3 { - prop3 = <0x02>; }; }; diff --git a/tests/dts/test_pretty_print.dts b/tests/dts/test_pretty_print.dts new file mode 100644 index 0000000..87eb86b --- /dev/null +++ b/tests/dts/test_pretty_print.dts @@ -0,0 +1,27 @@ +/dts-v1/; + +/ { + #address-cells = <0x01>; + #size-cells = <0x01>; + compatible = "acme,coyote"; + model = "ACME Coyote"; + phandle = <0x01>; + str-list = "first", "second"; + empty-prop; + byte-array = [01 23 45 67 89 ab cd]; + + cpus { + #address-cells = <0x01>; + #size-cells = <0x00>; + + cpu@0 { + compatible = "arm,cortex-a9"; + reg = <0x00>; + }; + }; + + memory@80000000 { + device_type = "memory"; + reg = <0x80000000 0x20000000>; + }; +}; diff --git a/tests/dts/test_props.dts b/tests/dts/test_props.dts index 779a31b..2c0e363 100644 --- a/tests/dts/test_props.dts +++ b/tests/dts/test_props.dts @@ -1,8 +1,8 @@ /dts-v1/; / { - #address-cells = <2>; - #size-cells = <2>; + #address-cells = <0x02>; + #size-cells = <0x02>; test-props { u32-prop = <0x12345678>; diff --git a/tests/dts/test_traversal.dts b/tests/dts/test_traversal.dts index 40ddd00..0f52af2 100644 --- a/tests/dts/test_traversal.dts +++ b/tests/dts/test_traversal.dts @@ -4,9 +4,11 @@ a { b { c { - prop = <1234>; + prop = <0x4d2>; }; }; }; - d {}; + + d { + }; }; diff --git a/tests/fdt.rs b/tests/fdt.rs index 66e06a0..8cb5de4 100644 --- a/tests/fdt.rs +++ b/tests/fdt.rs @@ -140,3 +140,35 @@ fn find_node_by_path() { assert!(fdt.find_node("/x").is_none()); assert!(fdt.find_node("").is_none()); } + +#[macro_export] +macro_rules! load_dtb_dts_pair { + ($name:expr) => { + ( + include_bytes!(concat!("dtb/", $name, ".dtb")).as_slice(), + include_str!(concat!("dts/", $name, ".dts")), + $name, + ) + }; +} + +const ALL_DT_FILES: &[(&[u8], &str, &str)] = &[ + load_dtb_dts_pair!("test_children_nested"), + load_dtb_dts_pair!("test_children"), + load_dtb_dts_pair!("test_pretty_print"), + load_dtb_dts_pair!("test_props"), + load_dtb_dts_pair!("test_traversal"), + load_dtb_dts_pair!("test"), +]; + +#[test] +fn pretty_print() { + for (dtb, expected_dts, name) in ALL_DT_FILES { + let fdt = Fdt::new(dtb).unwrap(); + let s = fdt.to_string(); + let expected = expected_dts + // account for Windows newlines, if needed + .replace("\r\n", "\n"); + assert_eq!(s, expected, "Mismatch for {name}"); + } +}