// Copyright 2018 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 writes Flattened Devicetree blobs as defined here: //! use std::collections::BTreeMap; use std::convert::TryInto; use std::io; use indexmap::map::Entry; use indexmap::IndexMap; use remain::sorted; use thiserror::Error as ThisError; use crate::path::Path; use crate::propval::FromFdtPropval; use crate::propval::ToFdtPropval; pub(crate) const SIZE_U32: usize = std::mem::size_of::(); pub(crate) const SIZE_U64: usize = std::mem::size_of::(); #[sorted] #[derive(ThisError, Debug)] pub enum Error { #[error("Error applying device tree overlay: {}", .0)] ApplyOverlayError(String), #[error("Binary size must fit in 32 bits")] BinarySizeTooLarge, #[error("Duplicate node {}", .0)] DuplicateNode(String), #[error("I/O error dumping FDT to file code={} path={}", .0, .1.display())] FdtDumpIoError(io::Error, std::path::PathBuf), #[error("Error writing FDT to guest memory")] FdtGuestMemoryWriteError, #[error("I/O error code={0}")] FdtIoError(io::Error), #[error("Parse error reading FDT parameters: {}", .0)] FdtParseError(String), #[error("Error applying FDT tree filter: {}", .0)] FilterError(String), #[error("Invalid name string: {}", .0)] InvalidName(String), #[error("Invalid path: {}", .0)] InvalidPath(String), #[error("Invalid string value {}", .0)] InvalidString(String), #[error("Expected phandle value for IOMMU of type: {}, id: {:?}", .0, .1)] MissingIommuPhandle(String, Option), #[error("Property value is not valid")] PropertyValueInvalid, #[error("Property value size must fit in 32 bits")] PropertyValueTooLarge, #[error("Total size must fit in 32 bits")] TotalSizeTooLarge, } impl From for Error { fn from(value: io::Error) -> Self { Self::FdtIoError(value) } } pub type Result = std::result::Result; type Blob<'a> = &'a [u8]; const FDT_BEGIN_NODE: u32 = 0x00000001; const FDT_END_NODE: u32 = 0x00000002; const FDT_PROP: u32 = 0x00000003; const FDT_NOP: u32 = 0x00000004; const FDT_END: u32 = 0x00000009; // Consume and return `n` bytes from the beginning of a slice. fn consume<'a>(bytes: &mut &'a [u8], n: usize) -> Result<&'a [u8]> { let mid = n; if mid > bytes.len() { Err(Error::PropertyValueInvalid) } else { let (data_bytes, rest) = bytes.split_at(n); *(bytes) = rest; Ok(data_bytes) } } // Consume a u32 from a byte slice. #[inline] fn rdu32(data: &mut Blob) -> Result { Ok(u32::from_be_bytes( // Unwrap won't panic because the slice length is checked in consume(). consume(data, SIZE_U32)?.try_into().unwrap(), )) } // Consume a u64 from a byte slice. #[inline] fn rdu64(data: &mut Blob) -> Result { Ok(u64::from_be_bytes( // Unwrap won't panic because the slice length is checked in consume(). consume(data, SIZE_U64)?.try_into().unwrap(), )) } // Return the number of padding bytes required to align `size` to `alignment`. #[inline] fn align_pad_len(size: usize, alignment: usize) -> usize { (alignment - size % alignment) % alignment } // Pad a byte vector to given alignment. #[inline] fn align_data(data: &mut Vec, alignment: usize) { data.resize(align_pad_len(data.len(), alignment) + data.len(), 0u8); } // Construct a string from the start of a byte slice until the first null byte. pub(crate) fn c_str_to_string(input: Blob) -> Option { let size = input.iter().position(|&v| v == 0u8)?; String::from_utf8(input[..size].to_vec()).ok() } // Verify FDT property name. fn is_valid_prop_name(name: &str) -> bool { const ALLOWED_SPECIAL_CHARS: [u8; 7] = [b'.', b',', b'_', b'+', b'?', b'#', b'-']; name.bytes() .all(|c| c.is_ascii_alphanumeric() || ALLOWED_SPECIAL_CHARS.contains(&c)) } // Verify FDT node name. fn is_valid_node_name(name: &str) -> bool { const ALLOWED_SPECIAL_CHARS: [u8; 6] = [b'.', b',', b'_', b'+', b'-', b'@']; const ADDR_SEP: u8 = b'@'; // At most one `@` separating node-name and unit-address if name.bytes().filter(|&c| c == ADDR_SEP).count() > 1 { return false; } name.bytes() .all(|c| c.is_ascii_alphanumeric() || ALLOWED_SPECIAL_CHARS.contains(&c)) } // An implementation of FDT header. #[derive(Default, Debug)] struct FdtHeader { magic: u32, // magic word total_size: u32, // total size of DT block off_dt_struct: u32, // offset to structure off_dt_strings: u32, // offset to strings off_mem_rsvmap: u32, // offset to memory reserve map version: u32, // format version last_comp_version: u32, // last compatible version boot_cpuid_phys: u32, // Which physical CPU id we're booting on size_dt_strings: u32, // size of the strings block size_dt_struct: u32, // size of the structure block } impl FdtHeader { const MAGIC: u32 = 0xd00dfeed; const VERSION: u32 = 17; const LAST_COMP_VERSION: u32 = 16; const SIZE: usize = 10 * SIZE_U32; // Create a new FdtHeader instance. fn new( total_size: u32, off_dt_struct: u32, off_dt_strings: u32, off_mem_rsvmap: u32, boot_cpuid_phys: u32, size_dt_strings: u32, size_dt_struct: u32, ) -> Self { Self { magic: Self::MAGIC, total_size, off_dt_struct, off_dt_strings, off_mem_rsvmap, version: Self::VERSION, last_comp_version: Self::LAST_COMP_VERSION, boot_cpuid_phys, size_dt_strings, size_dt_struct, } } // Dump FDT header to a byte vector. fn write_blob(&self, buffer: &mut [u8]) -> Result<()> { assert_eq!(buffer.len(), Self::SIZE); for (chunk, val_u32) in buffer.chunks_exact_mut(SIZE_U32).zip(&[ self.magic, self.total_size, self.off_dt_struct, self.off_dt_strings, self.off_mem_rsvmap, self.version, self.last_comp_version, self.boot_cpuid_phys, self.size_dt_strings, self.size_dt_struct, ]) { chunk.copy_from_slice(&val_u32.to_be_bytes()); } Ok(()) } // Load FDT header from a byte slice. fn from_blob(mut input: Blob) -> Result { if input.len() < Self::SIZE { return Err(Error::FdtParseError("invalid binary size".into())); } let input = &mut input; let header = Self { magic: rdu32(input)?, total_size: rdu32(input)?, off_dt_struct: rdu32(input)?, off_dt_strings: rdu32(input)?, off_mem_rsvmap: rdu32(input)?, version: rdu32(input)?, last_comp_version: rdu32(input)?, boot_cpuid_phys: rdu32(input)?, size_dt_strings: rdu32(input)?, size_dt_struct: rdu32(input)?, }; if header.magic != Self::MAGIC { return Err(Error::FdtParseError("invalid header magic".into())); } if header.version < Self::VERSION { return Err(Error::FdtParseError("unsupported FDT version".into())); } if header.off_mem_rsvmap >= header.off_dt_strings || header.off_mem_rsvmap < FdtHeader::SIZE as u32 { return Err(Error::FdtParseError( "invalid reserved memory offset".into(), )); } let off_dt_struct_end = header .off_dt_struct .checked_add(header.size_dt_struct) .ok_or_else(|| Error::FdtParseError("struct end offset must fit in 32 bits".into()))?; if off_dt_struct_end > header.off_dt_strings { return Err(Error::FdtParseError("struct and strings overlap".into())); } let off_dt_strings_end = header .off_dt_strings .checked_add(header.size_dt_strings) .ok_or_else(|| Error::FdtParseError("strings end offset must fit in 32 bits".into()))?; if off_dt_strings_end > header.total_size { return Err(Error::FdtParseError("strings data past total size".into())); } Ok(header) } } // An implementation of FDT strings block (property names) #[derive(Default)] struct FdtStrings { strings: Vec, string_offsets: BTreeMap, } impl FdtStrings { // Load the strings block from a byte slice. fn from_blob(input: Blob) -> Result { if input.last().map_or(false, |i| *i != 0) { return Err(Error::FdtParseError( "strings block missing null terminator".into(), )); } let mut string_offsets = BTreeMap::new(); let mut offset = 0u32; for bytes in input.split(|&x| x == 0u8) { if bytes.is_empty() { break; } let string = String::from_utf8(bytes.to_vec()) .map_err(|_| Error::FdtParseError("invalid value in strings block".into()))?; string_offsets.insert(string, offset); offset += u32::try_from(bytes.len() + 1).map_err(|_| Error::BinarySizeTooLarge)?; } Ok(Self { strings: input.to_vec(), string_offsets, }) } // Find an existing instance of a string `s`, or add it to the strings block. // Returns the offset into the strings block. fn intern_string(&mut self, s: &str) -> u32 { if let Some(off) = self.string_offsets.get(s) { *off } else { let off = self.strings.len() as u32; self.strings.extend_from_slice(s.as_bytes()); self.strings.push(0u8); self.string_offsets.insert(s.to_owned(), off); off } } // Write the strings blob to a `Write` object. fn write_blob(&self, mut writer: impl io::Write) -> Result<()> { Ok(writer.write_all(&self.strings)?) } // Return the string at given offset or `None` if such a string doesn't exist. fn at_offset(&self, off: u32) -> Option { self.strings .get(off as usize..) .and_then(c_str_to_string) .filter(|s| !s.is_empty()) } } /// Flattened device tree node. /// /// This represents a single node from the FDT structure block. Every node may contain properties /// and other (child) nodes. #[derive(Debug, Clone)] pub struct FdtNode { /// Node name pub(crate) name: String, pub(crate) props: IndexMap>, pub(crate) subnodes: IndexMap, } impl FdtNode { // Create a new node with the given name, properties, and child nodes. Return an error if // node or property names do not satisfy devicetree naming criteria. pub(crate) fn new( name: String, props: IndexMap>, subnodes: IndexMap, ) -> Result { if !is_valid_node_name(&name) { return Err(Error::InvalidName(name)); } for pname in props.keys() { if !is_valid_prop_name(pname) { return Err(Error::InvalidName(pname.into())); } } Ok(Self { name, props, subnodes, }) } // Create an empty node with the given name. pub(crate) fn empty(name: impl Into) -> Result { FdtNode::new(name.into(), [].into(), [].into()) } fn read_token(input: &mut Blob) -> Result { loop { let value = rdu32(input)?; if value != FDT_NOP { return Ok(value); } } } // Parse binary content of an FDT node. fn parse_node(input: &mut Blob, strings: &FdtStrings) -> Result { // Node name let name = c_str_to_string(input) .ok_or_else(|| Error::FdtParseError("could not parse node name".into()))?; let name_nbytes = name.len() + 1; consume(input, name_nbytes + align_pad_len(name_nbytes, SIZE_U32))?; // Node properties and subnodes let mut props = IndexMap::new(); let mut subnodes = IndexMap::new(); let mut encountered_subnode = false; // Properties must appear before subnodes loop { match Self::read_token(input)? { FDT_BEGIN_NODE => { encountered_subnode = true; let subnode = Self::parse_node(input, strings)?; match subnodes.entry(subnode.name.clone()) { Entry::Vacant(e) => e.insert(subnode), Entry::Occupied(_) => return Err(Error::DuplicateNode(subnode.name)), }; } FDT_END_NODE => break, FDT_PROP => { if encountered_subnode { return Err(Error::FdtParseError( "unexpected prop token after subnode".into(), )); } let prop_len = rdu32(input)? as usize; let prop_name_offset = rdu32(input)?; let prop_blob = consume(input, prop_len + align_pad_len(prop_len, SIZE_U32))?; let prop_name = strings.at_offset(prop_name_offset).ok_or_else(|| { Error::FdtParseError(format!( "invalid property name at {prop_name_offset:#x}", )) })?; // Keep the original (non-aligned) size as property value props.insert(prop_name, prop_blob[..prop_len].to_vec()); } FDT_NOP => continue, FDT_END => return Err(Error::FdtParseError("unexpected END token".into())), t => return Err(Error::FdtParseError(format!("invalid FDT token {t}"))), } } FdtNode::new(name, props, subnodes) } // Load an `FdtNode` instance from a slice of bytes. fn from_blob(mut input: Blob, strings: &FdtStrings) -> Result { let input = &mut input; if Self::read_token(input)? != FDT_BEGIN_NODE { return Err(Error::FdtParseError("expected begin node token".into())); } let root = Self::parse_node(input, strings)?; if Self::read_token(input)? != FDT_END { Err(Error::FdtParseError("expected end node token".into())) } else { Ok(root) } } // Write binary contents of a node to a vector of bytes. fn write_blob(&self, writer: &mut impl io::Write, strings: &mut FdtStrings) -> Result<()> { // Token writer.write_all(&FDT_BEGIN_NODE.to_be_bytes())?; // Name writer.write_all(self.name.as_bytes())?; writer.write_all(&[0])?; // Node name terminator let pad_len = align_pad_len(self.name.len() + 1, SIZE_U32); writer.write_all(&vec![0; pad_len])?; // Properties for (propname, propblob) in self.props.iter() { // Prop token writer.write_all(&FDT_PROP.to_be_bytes())?; // Prop size writer.write_all(&(propblob.len() as u32).to_be_bytes())?; // Prop name offset writer.write_all(&strings.intern_string(propname).to_be_bytes())?; // Prop value writer.write_all(propblob)?; let pad_len = align_pad_len(propblob.len(), SIZE_U32); writer.write_all(&vec![0; pad_len])?; } // Subnodes for subnode in self.subnodes.values() { subnode.write_blob(writer, strings)?; } // Token writer.write_all(&FDT_END_NODE.to_be_bytes())?; Ok(()) } // Iterate over property names defined for this node. pub(crate) fn prop_names(&self) -> impl std::iter::Iterator { self.props.keys().map(|s| s.as_str()) } // Return true if a property with the given name exists. pub(crate) fn has_prop(&self, name: &str) -> bool { self.props.contains_key(name) } /// Read property value if it exists. /// /// # Arguments /// /// `name` - name of the property. pub fn get_prop(&self, name: &str) -> Option where T: FromFdtPropval, { T::from_propval(self.props.get(name)?.as_slice()) } // Read a phandle value (a `u32`) at some offset within a property value. // Returns `None` if a phandle value cannot be constructed. pub(crate) fn phandle_at_offset(&self, name: &str, offset: usize) -> Option { let data = self.props.get(name)?; data.get(offset..offset + SIZE_U32) .and_then(u32::from_propval) } // Overwrite a phandle value (a `u32`) at some offset within a property value. // Returns `Err` if the property doesn't exist, or if the property value is too short to // construct a `u32` at given offset. Does not change property value size. pub(crate) fn update_phandle_at_offset( &mut self, name: &str, offset: usize, phandle: u32, ) -> Result<()> { let propval = self .props .get_mut(name) .ok_or_else(|| Error::InvalidName(format!("property {name} does not exist")))?; if let Some(bytes) = propval.get_mut(offset..offset + SIZE_U32) { bytes.copy_from_slice(phandle.to_propval()?.as_slice()); Ok(()) } else { Err(Error::PropertyValueInvalid) } } /// Write a property. /// /// # Arguments /// /// `name` - name of the property; must be a valid property name according to DT spec. /// `val` - value of the property (raw byte array). pub fn set_prop(&mut self, name: &str, value: T) -> Result<()> where T: ToFdtPropval, { if !is_valid_prop_name(name) { return Err(Error::InvalidName(name.into())); } let bytes = value.to_propval()?; // FDT property byte size must fit into a u32. u32::try_from(bytes.len()).map_err(|_| Error::PropertyValueTooLarge)?; self.props.insert(name.into(), bytes); Ok(()) } /// Return a reference to an existing subnode with given name, or `None` if it doesn't exist. /// /// # Arguments /// /// `name` - name of the node. pub fn subnode(&self, name: &str) -> Option<&FdtNode> { self.subnodes.get(name) } /// Create a node if it doesn't already exist, and return a mutable reference to it. Return /// an error if the node name is not valid. /// /// # Arguments /// /// `name` - name of the node; must be a valid node name according to DT specification. pub fn subnode_mut(&mut self, name: &str) -> Result<&mut FdtNode> { if !self.subnodes.contains_key(name) { self.subnodes.insert(name.into(), FdtNode::empty(name)?); } Ok(self.subnodes.get_mut(name).unwrap()) } // Iterate subnode references. pub(crate) fn iter_subnodes(&self) -> impl std::iter::Iterator { self.subnodes.values() } // Iterate mutable subnode references. pub(crate) fn iter_subnodes_mut(&mut self) -> impl std::iter::Iterator { self.subnodes.values_mut() } } /// Interface for creating and manipulating a Flattened Devicetree (FDT) and emitting /// a Devicetree Blob (DTB). /// /// # Example /// /// ```rust /// use cros_fdt::Fdt; /// /// # fn main() -> cros_fdt::Result<()> { /// let mut fdt = Fdt::new(&[]); /// let root_node = fdt.root_mut(); /// root_node.set_prop("compatible", "linux,dummy-virt")?; /// root_node.set_prop("#address-cells", 0x2u32)?; /// root_node.set_prop("#size-cells", 0x2u32)?; /// let chosen_node = root_node.subnode_mut("chosen")?; /// chosen_node.set_prop("linux,pci-probe-only", 1u32)?; /// chosen_node.set_prop("bootargs", "panic=-1 console=hvc0 root=/dev/vda")?; /// let dtb = fdt.finish().unwrap(); /// # Ok(()) /// # } /// ``` pub struct Fdt { pub(crate) reserved_memory: Vec, pub(crate) root: FdtNode, strings: FdtStrings, boot_cpuid_phys: u32, } /// Reserved physical memory region. /// /// This represents an area of physical memory reserved by the firmware and unusable by the OS. /// For example, this could be used to preserve bootloader code or data used at runtime. #[derive(Clone, PartialEq, Debug)] pub struct FdtReserveEntry { /// Physical address of the beginning of the reserved region. pub address: u64, /// Size of the reserved region in bytes. pub size: u64, } // Last entry in the reserved memory section const RESVMEM_TERMINATOR: FdtReserveEntry = FdtReserveEntry::new(0, 0); impl FdtReserveEntry { /// Create a new FdtReserveEntry /// /// # Arguments /// /// `address` - start of reserved memory region. /// `size` - size of reserved memory region. pub const fn new(address: u64, size: u64) -> Self { Self { address, size } } // Load a reserved memory entry from a byte slice. fn from_blob(input: &mut Blob) -> Result { Ok(Self { address: rdu64(input)?, size: rdu64(input)?, }) } // Dump the entry as a vector of bytes. fn write_blob(&self, mut writer: impl io::Write) -> Result<()> { writer.write_all(&self.address.to_be_bytes())?; writer.write_all(&self.size.to_be_bytes())?; Ok(()) } } impl Fdt { /// Create a new flattened device tree instance with an initialized root node. /// /// # Arguments /// /// `mem_reservations` - reserved physical memory regions to list in the FDT header. pub fn new(mem_reservations: &[FdtReserveEntry]) -> Self { Self { reserved_memory: mem_reservations.to_vec(), root: FdtNode::empty("").unwrap(), strings: FdtStrings::default(), boot_cpuid_phys: 0u32, } } /// Set the `boot_cpuid_phys` field of the devicetree header. /// /// # Arguments /// /// `boot_cpuid_phys` - CPU ID pub fn set_boot_cpuid_phys(&mut self, boot_cpuid_phys: u32) { self.boot_cpuid_phys = boot_cpuid_phys; } // Parse the reserved memory block from a binary blob. fn parse_reserved_memory(mut input: Blob) -> Result> { let mut entries = vec![]; let input = &mut input; loop { let entry = FdtReserveEntry::from_blob(input)?; if entry == RESVMEM_TERMINATOR { break; } entries.push(entry); } Ok(entries) } // Write the reserved memory block to a buffer. fn write_reserved_memory(&self, mut writer: impl io::Write) -> Result<()> { for entry in &self.reserved_memory { entry.write_blob(&mut writer)?; } RESVMEM_TERMINATOR.write_blob(writer) } /// Load a flattened device tree from a byte slice. /// /// # Arguments /// /// `input` - byte slice from which to load the FDT. pub fn from_blob(input: Blob) -> Result { let header = input .get(..FdtHeader::SIZE) .ok_or_else(|| Error::FdtParseError("cannot extract header, input too small".into()))?; let header = FdtHeader::from_blob(header)?; if header.total_size as usize != input.len() { return Err(Error::FdtParseError("input size doesn't match".into())); } let reserved_mem_blob = &input[header.off_mem_rsvmap as usize..]; let nodes_blob = &input[header.off_dt_struct as usize ..(header.off_dt_struct + header.size_dt_struct) as usize]; let strings_blob = &input[header.off_dt_strings as usize ..(header.off_dt_strings + header.size_dt_strings) as usize]; let reserved_memory = Self::parse_reserved_memory(reserved_mem_blob)?; let strings = FdtStrings::from_blob(strings_blob)?; let root = FdtNode::from_blob(nodes_blob, &strings)?; Ok(Self { reserved_memory, root, strings, boot_cpuid_phys: header.boot_cpuid_phys, }) } // Write the structure block of the FDT fn write_struct(&mut self, mut writer: impl io::Write) -> Result<()> { self.root.write_blob(&mut writer, &mut self.strings)?; writer.write_all(&FDT_END.to_be_bytes())?; Ok(()) } /// Finish writing the Devicetree Blob (DTB). /// /// Returns the DTB as a vector of bytes. pub fn finish(&mut self) -> Result> { let mut result = vec![0u8; FdtHeader::SIZE]; align_data(&mut result, SIZE_U64); let off_mem_rsvmap = result.len(); self.write_reserved_memory(&mut result)?; align_data(&mut result, SIZE_U64); let off_dt_struct = result.len(); self.write_struct(&mut result)?; align_data(&mut result, SIZE_U32); let off_dt_strings = result.len(); self.strings.write_blob(&mut result)?; let total_size = u32::try_from(result.len()).map_err(|_| Error::TotalSizeTooLarge)?; let header = FdtHeader::new( total_size, off_dt_struct as u32, off_dt_strings as u32, off_mem_rsvmap as u32, self.boot_cpuid_phys, total_size - off_dt_strings as u32, // strings size off_dt_strings as u32 - off_dt_struct as u32, // struct size ); header.write_blob(&mut result[..FdtHeader::SIZE])?; Ok(result) } /// Return a mutable reference to the root node of the FDT. pub fn root_mut(&mut self) -> &mut FdtNode { &mut self.root } /// Return a reference to the node the path points to, or `None` if it doesn't exist. /// /// # Arguments /// /// `path` - device tree path of the target node. pub fn get_node>(&self, path: T) -> Option<&FdtNode> { let mut result_node = &self.root; let path: Path = path.try_into().ok()?; for node_name in path.iter() { result_node = result_node.subnodes.get(node_name)?; } Some(result_node) } /// Return a mutable reference to the node the path points to, or `None` if it /// doesn't exist. /// /// # Arguments /// /// `path` - device tree path of the target node. pub fn get_node_mut>(&mut self, path: T) -> Option<&mut FdtNode> { let mut result_node = &mut self.root; let path: Path = path.try_into().ok()?; for node_name in path.iter() { result_node = result_node.subnodes.get_mut(node_name)?; } Some(result_node) } /// Find a device tree path to the symbol exported by the FDT. The symbol must be a node label. /// /// # Arguments /// /// `symbol` - symbol to search for. pub fn symbol_to_path(&self, symbol: &str) -> Result { const SYMBOLS_NODE: &str = "__symbols__"; let Some(symbols_node) = self.root.subnode(SYMBOLS_NODE) else { return Err(Error::InvalidPath("no symbols in fdt".into())); }; symbols_node .get_prop::(symbol) .ok_or_else(|| Error::InvalidName(format!("filter symbol {symbol} does not exist")))? .parse() } } #[cfg(test)] mod tests { use super::*; const FDT_BLOB_HEADER_ONLY: [u8; 0x48] = [ 0xd0, 0x0d, 0xfe, 0xed, // 0000: magic (0xd00dfeed) 0x00, 0x00, 0x00, 0x48, // 0004: totalsize (0x48) 0x00, 0x00, 0x00, 0x38, // 0008: off_dt_struct (0x38) 0x00, 0x00, 0x00, 0x48, // 000C: off_dt_strings (0x48) 0x00, 0x00, 0x00, 0x28, // 0010: off_mem_rsvmap (0x28) 0x00, 0x00, 0x00, 0x11, // 0014: version (0x11 = 17) 0x00, 0x00, 0x00, 0x10, // 0018: last_comp_version (0x10 = 16) 0x00, 0x00, 0x00, 0x00, // 001C: boot_cpuid_phys (0) 0x00, 0x00, 0x00, 0x00, // 0020: size_dt_strings (0) 0x00, 0x00, 0x00, 0x10, // 0024: size_dt_struct (0x10) 0x00, 0x00, 0x00, 0x00, // 0028: rsvmap terminator (address = 0 high) 0x00, 0x00, 0x00, 0x00, // 002C: rsvmap terminator (address = 0 low) 0x00, 0x00, 0x00, 0x00, // 0030: rsvmap terminator (size = 0 high) 0x00, 0x00, 0x00, 0x00, // 0034: rsvmap terminator (size = 0 low) 0x00, 0x00, 0x00, 0x01, // 0038: FDT_BEGIN_NODE 0x00, 0x00, 0x00, 0x00, // 003C: node name ("") + padding 0x00, 0x00, 0x00, 0x02, // 0040: FDT_END_NODE 0x00, 0x00, 0x00, 0x09, // 0044: FDT_END ]; const FDT_BLOB_RSVMAP: [u8; 0x68] = [ 0xd0, 0x0d, 0xfe, 0xed, // 0000: magic (0xd00dfeed) 0x00, 0x00, 0x00, 0x68, // 0004: totalsize (0x68) 0x00, 0x00, 0x00, 0x58, // 0008: off_dt_struct (0x58) 0x00, 0x00, 0x00, 0x68, // 000C: off_dt_strings (0x68) 0x00, 0x00, 0x00, 0x28, // 0010: off_mem_rsvmap (0x28) 0x00, 0x00, 0x00, 0x11, // 0014: version (0x11 = 17) 0x00, 0x00, 0x00, 0x10, // 0018: last_comp_version (0x10 = 16) 0x00, 0x00, 0x00, 0x00, // 001C: boot_cpuid_phys (0) 0x00, 0x00, 0x00, 0x00, // 0020: size_dt_strings (0) 0x00, 0x00, 0x00, 0x10, // 0024: size_dt_struct (0x10) 0x12, 0x34, 0x56, 0x78, // 0028: rsvmap entry 0 address high 0xAA, 0xBB, 0xCC, 0xDD, // 002C: rsvmap entry 0 address low 0x00, 0x00, 0x00, 0x00, // 0030: rsvmap entry 0 size high 0x00, 0x00, 0x12, 0x34, // 0034: rsvmap entry 0 size low 0x10, 0x20, 0x30, 0x40, // 0038: rsvmap entry 1 address high 0x50, 0x60, 0x70, 0x80, // 003C: rsvmap entry 1 address low 0x00, 0x00, 0x00, 0x00, // 0040: rsvmap entry 1 size high 0x00, 0x00, 0x56, 0x78, // 0044: rsvmap entry 1 size low 0x00, 0x00, 0x00, 0x00, // 0048: rsvmap terminator (address = 0 high) 0x00, 0x00, 0x00, 0x00, // 004C: rsvmap terminator (address = 0 low) 0x00, 0x00, 0x00, 0x00, // 0050: rsvmap terminator (size = 0 high) 0x00, 0x00, 0x00, 0x00, // 0054: rsvmap terminator (size = 0 low) 0x00, 0x00, 0x00, 0x01, // 0058: FDT_BEGIN_NODE 0x00, 0x00, 0x00, 0x00, // 005C: node name ("") + padding 0x00, 0x00, 0x00, 0x02, // 0060: FDT_END_NODE 0x00, 0x00, 0x00, 0x09, // 0064: FDT_END ]; const FDT_BLOB_STRINGS: [u8; 0x26] = [ b'n', b'u', b'l', b'l', 0x00, b'u', b'3', b'2', 0x00, b'u', b'6', b'4', 0x00, b's', b't', b'r', 0x00, b's', b't', b'r', b'l', b's', b't', 0x00, b'a', b'r', b'r', b'u', b'3', b'2', 0x00, b'a', b'r', b'r', b'u', b'6', b'4', 0x00, ]; const EXPECTED_STRINGS: [&str; 7] = ["null", "u32", "u64", "str", "strlst", "arru32", "arru64"]; const FDT_BLOB_NODES_ROOT_ONLY: [u8; 0x90] = [ 0x00, 0x00, 0x00, 0x01, // FDT_BEGIN_NODE 0x00, 0x00, 0x00, 0x00, // node name ("") + padding 0x00, 0x00, 0x00, 0x03, // FDT_PROP (null) 0x00, 0x00, 0x00, 0x00, // prop len (0) 0x00, 0x00, 0x00, 0x00, // prop nameoff (0) 0x00, 0x00, 0x00, 0x03, // FDT_PROP (u32) 0x00, 0x00, 0x00, 0x04, // prop len (4) 0x00, 0x00, 0x00, 0x05, // prop nameoff (0x05) 0x12, 0x34, 0x56, 0x78, // prop u32 value (0x12345678) 0x00, 0x00, 0x00, 0x03, // FDT_PROP (u64) 0x00, 0x00, 0x00, 0x08, // prop len (8) 0x00, 0x00, 0x00, 0x09, // prop nameoff (0x09) 0x12, 0x34, 0x56, 0x78, // prop u64 value high (0x12345678) 0x87, 0x65, 0x43, 0x21, // prop u64 value low (0x87654321) 0x00, 0x00, 0x00, 0x03, // FDT_PROP (string) 0x00, 0x00, 0x00, 0x06, // prop len (6) 0x00, 0x00, 0x00, 0x0D, // prop nameoff (0x0D) b'h', b'e', b'l', b'l', // prop str value ("hello") + padding b'o', 0x00, 0x00, 0x00, // "o\0" + padding 0x00, 0x00, 0x00, 0x03, // FDT_PROP (string list) 0x00, 0x00, 0x00, 0x07, // prop len (7) 0x00, 0x00, 0x00, 0x11, // prop nameoff (0x11) b'h', b'i', 0x00, b'b', // prop value ("hi", "bye") b'y', b'e', 0x00, 0x00, // "ye\0" + padding 0x00, 0x00, 0x00, 0x03, // FDT_PROP (u32 array) 0x00, 0x00, 0x00, 0x08, // prop len (8) 0x00, 0x00, 0x00, 0x18, // prop nameoff (0x18) 0x12, 0x34, 0x56, 0x78, // prop value 0 0xAA, 0xBB, 0xCC, 0xDD, // prop value 1 0x00, 0x00, 0x00, 0x03, // FDT_PROP (u64 array) 0x00, 0x00, 0x00, 0x08, // prop len (8) 0x00, 0x00, 0x00, 0x1f, // prop nameoff (0x1F) 0x12, 0x34, 0x56, 0x78, // prop u64 value 0 high 0x87, 0x65, 0x43, 0x21, // prop u64 value 0 low 0x00, 0x00, 0x00, 0x02, // FDT_END_NODE 0x00, 0x00, 0x00, 0x09, // FDT_END ]; /* Node structure: / |- nested |- nested2 |- nested3 */ const FDT_BLOB_NESTED_NODES: [u8; 0x80] = [ 0x00, 0x00, 0x00, 0x01, // FDT_BEGIN_NODE 0x00, 0x00, 0x00, 0x00, // node name ("") + padding 0x00, 0x00, 0x00, 0x03, // FDT_PROP 0x00, 0x00, 0x00, 0x04, // prop len (4) 0x00, 0x00, 0x00, 0x00, // prop nameoff (0x00) 0x13, 0x57, 0x90, 0x24, // prop u32 value (0x13579024) 0x00, 0x00, 0x00, 0x01, // FDT_BEGIN_NODE b'n', b'e', b's', b't', // Node name ("nested") b'e', b'd', 0x00, 0x00, // "ed\0" + pad 0x00, 0x00, 0x00, 0x03, // FDT_PROP 0x00, 0x00, 0x00, 0x04, // prop len (4) 0x00, 0x00, 0x00, 0x05, // prop nameoff (0x05) 0x12, 0x12, 0x12, 0x12, // prop u32 value (0x12121212) 0x00, 0x00, 0x00, 0x03, // FDT_PROP 0x00, 0x00, 0x00, 0x04, // prop len (4) 0x00, 0x00, 0x00, 0x18, // prop nameoff (0x18) 0x13, 0x57, 0x90, 0x24, // prop u32 value (0x13579024) 0x00, 0x00, 0x00, 0x02, // FDT_END_NODE ("nested") 0x00, 0x00, 0x00, 0x01, // FDT_BEGIN_NODE b'n', b'e', b's', b't', // Node name ("nested2") b'e', b'd', b'2', 0x00, // "ed2\0" 0x00, 0x00, 0x00, 0x03, // FDT_PROP 0x00, 0x00, 0x00, 0x04, // prop len (0) 0x00, 0x00, 0x00, 0x05, // prop nameoff (0x05) 0x12, 0x12, 0x12, 0x12, // prop u32 value (0x12121212) 0x00, 0x00, 0x00, 0x01, // FDT_BEGIN_NODE b'n', b'e', b's', b't', // Node name ("nested3") b'e', b'd', b'3', 0x00, // "ed3\0" 0x00, 0x00, 0x00, 0x02, // FDT_END_NODE ("nested3") 0x00, 0x00, 0x00, 0x02, // FDT_END_NODE ("nested2") 0x00, 0x00, 0x00, 0x02, // FDT_END_NODE ("") 0x00, 0x00, 0x00, 0x09, // FDT_END ]; #[test] fn fdt_load_header() { let blob: &[u8] = &FDT_BLOB_HEADER_ONLY; let header = FdtHeader::from_blob(blob).unwrap(); assert_eq!(header.magic, FdtHeader::MAGIC); assert_eq!(header.total_size, 0x48); assert_eq!(header.off_dt_struct, 0x38); assert_eq!(header.off_dt_strings, 0x48); assert_eq!(header.off_mem_rsvmap, 0x28); assert_eq!(header.version, 17); assert_eq!(header.last_comp_version, 16); assert_eq!(header.boot_cpuid_phys, 0); assert_eq!(header.size_dt_strings, 0); assert_eq!(header.size_dt_struct, 0x10); } #[test] fn fdt_load_invalid_header() { // HEADER is valid const HEADER: [u8; 40] = [ 0xd0, 0x0d, 0xfe, 0xed, // 0000: magic (0xd00dfeed) 0x00, 0x00, 0x00, 0xda, // 0004: totalsize (0xda) 0x00, 0x00, 0x00, 0x58, // 0008: off_dt_struct (0x58) 0x00, 0x00, 0x00, 0xb2, // 000C: off_dt_strings (0xb2) 0x00, 0x00, 0x00, 0x28, // 0010: off_mem_rsvmap (0x28) 0x00, 0x00, 0x00, 0x11, // 0014: version (0x11 = 17) 0x00, 0x00, 0x00, 0x10, // 0018: last_comp_version (0x10 = 16) 0x00, 0x00, 0x00, 0x00, // 001C: boot_cpuid_phys (0) 0x00, 0x00, 0x00, 0x28, // 0020: size_dt_strings (0x28) 0x00, 0x00, 0x00, 0x5a, // 0024: size_dt_struct (0x5a) ]; FdtHeader::from_blob(&HEADER).unwrap(); // Header too small assert!(FdtHeader::from_blob(&HEADER[..FdtHeader::SIZE - 4]).is_err()); assert!(FdtHeader::from_blob(&[]).is_err()); let mut invalid_header = HEADER; invalid_header[0x00] = 0x00; // change magic to (0x000dfeed) FdtHeader::from_blob(&invalid_header).expect_err("invalid magic"); let mut invalid_header = HEADER; invalid_header[0x07] = 0x10; // make totalsize too small FdtHeader::from_blob(&invalid_header).expect_err("invalid totalsize"); let mut invalid_header = HEADER; invalid_header[0x0b] = 0x60; // increase off_dt_struct FdtHeader::from_blob(&invalid_header).expect_err("dt struct overlaps with strings"); let mut invalid_header = HEADER; invalid_header[0x27] = 0x5c; // increase size_dt_struct FdtHeader::from_blob(&invalid_header).expect_err("dt struct overlaps with strings"); let mut invalid_header = HEADER; invalid_header[0x13] = 0x20; // decrease off_mem_rsvmap FdtHeader::from_blob(&invalid_header).expect_err("reserved memory overlaps with header"); let mut invalid_header = HEADER; invalid_header[0x0f] = 0x50; // decrease off_dt_strings FdtHeader::from_blob(&invalid_header).expect_err("strings start before struct"); let mut invalid_header = HEADER; invalid_header[0x23] = 0x50; // increase size_dt_strings FdtHeader::from_blob(&invalid_header).expect_err("strings go past totalsize"); } #[test] fn fdt_load_resv_map() { let blob: &[u8] = &FDT_BLOB_RSVMAP; let fdt = Fdt::from_blob(blob).unwrap(); assert_eq!(fdt.reserved_memory.len(), 2); assert!( fdt.reserved_memory[0].address == 0x12345678AABBCCDD && fdt.reserved_memory[0].size == 0x1234 ); assert!( fdt.reserved_memory[1].address == 0x1020304050607080 && fdt.reserved_memory[1].size == 0x5678 ); } #[test] fn fdt_test_node_props() { let mut node = FdtNode::empty("mynode").unwrap(); node.set_prop("myprop", 1u32).unwrap(); assert_eq!(node.get_prop::("myprop").unwrap(), 1u32); node.set_prop("myprop", 0xabcdef9876543210u64).unwrap(); assert_eq!( node.get_prop::("myprop").unwrap(), 0xabcdef9876543210u64 ); node.set_prop("myprop", ()).unwrap(); assert_eq!(node.get_prop::>("myprop").unwrap(), []); node.set_prop("myprop", vec![1u8, 2u8, 3u8]).unwrap(); assert_eq!( node.get_prop::>("myprop").unwrap(), vec![1u8, 2u8, 3u8] ); node.set_prop("myprop", vec![1u32, 2u32, 3u32]).unwrap(); assert_eq!( node.get_prop::>("myprop").unwrap(), vec![1u32, 2u32, 3u32] ); node.set_prop("myprop", vec![1u64, 2u64, 3u64]).unwrap(); assert_eq!( node.get_prop::>("myprop").unwrap(), vec![1u64, 2u64, 3u64] ); node.set_prop("myprop", "myval".to_string()).unwrap(); assert_eq!( node.get_prop::("myprop").unwrap(), "myval".to_string() ); node.set_prop( "myprop", vec![ "myval1".to_string(), "myval2".to_string(), "myval3".to_string(), ], ) .unwrap(); assert_eq!( node.get_prop::>("myprop").unwrap(), vec![ "myval1".to_string(), "myval2".to_string(), "myval3".to_string() ] ); } #[test] fn fdt_simple_use() { let mut fdt = Fdt::new(&[]); let root_node = fdt.root_mut(); root_node .set_prop("compatible", "linux,dummy-virt") .unwrap(); root_node.set_prop("#address-cells", 0x2u32).unwrap(); root_node.set_prop("#size-cells", 0x2u32).unwrap(); let chosen_node = root_node.subnode_mut("chosen").unwrap(); chosen_node.set_prop("linux,pci-probe-only", 1u32).unwrap(); chosen_node .set_prop("bootargs", "panic=-1 console=hvc0 root=/dev/vda") .unwrap(); fdt.finish().unwrap(); } #[test] fn fdt_load_strings() { let blob = &FDT_BLOB_STRINGS[..]; let strings = FdtStrings::from_blob(blob).unwrap(); let mut offset = 0u32; for s in EXPECTED_STRINGS { assert_eq!(strings.at_offset(offset).unwrap(), s); offset += strings.at_offset(offset).unwrap().len() as u32 + 1; } } #[test] fn fdt_load_strings_intern() { let strings_blob = &FDT_BLOB_STRINGS[..]; let mut strings = FdtStrings::from_blob(strings_blob).unwrap(); assert_eq!(strings.intern_string("null"), 0); assert_eq!(strings.intern_string("strlst"), 17); assert_eq!(strings.intern_string("arru64"), 31); assert_eq!(strings.intern_string("abc"), 38); assert_eq!(strings.intern_string("def"), 42); assert_eq!(strings.intern_string("strlst"), 17); } #[test] fn fdt_load_props() { const PROP_SIZES: [(&str, usize); 7] = [ ("null", 0), ("u32", 4), ("u64", 8), ("str", 6), ("strlst", 7), ("arru32", 8), ("arru64", 8), ]; let blob: &[u8] = &FDT_BLOB_STRINGS[..]; let strings = FdtStrings::from_blob(blob).unwrap(); let blob: &[u8] = &FDT_BLOB_NODES_ROOT_ONLY[..]; let node = FdtNode::from_blob(blob, &strings).unwrap(); assert_eq!(node.name, ""); assert_eq!(node.subnodes.len(), 0); assert_eq!(node.props.len(), PROP_SIZES.len()); for (pname, s) in PROP_SIZES.into_iter() { assert_eq!(node.get_prop::>(pname).unwrap().len(), s); } } #[test] fn fdt_load_nodes_nested() { let strings_blob = &FDT_BLOB_STRINGS[..]; let strings = FdtStrings::from_blob(strings_blob).unwrap(); let blob: &[u8] = &FDT_BLOB_NESTED_NODES[..]; let root_node = FdtNode::from_blob(blob, &strings).unwrap(); // Check root node assert_eq!(root_node.name, ""); assert_eq!(root_node.subnodes.len(), 2); assert_eq!(root_node.props.len(), 1); // Check first nested node let nested_node = root_node.subnodes.get("nested").unwrap(); assert_eq!(nested_node.name, "nested"); assert_eq!(nested_node.subnodes.len(), 0); assert_eq!(nested_node.props.len(), 2); // Check second nested node let nested2_node = root_node.subnodes.get("nested2").unwrap(); assert_eq!(nested2_node.name, "nested2"); assert_eq!(nested2_node.subnodes.len(), 1); assert_eq!(nested2_node.props.len(), 1); // Check third nested node let nested3_node = nested2_node.subnodes.get("nested3").unwrap(); assert_eq!(nested3_node.name, "nested3"); assert_eq!(nested3_node.subnodes.len(), 0); assert_eq!(nested3_node.props.len(), 0); } #[test] fn fdt_get_node() { let fdt = Fdt::new(&[]); assert!(fdt.get_node("/").is_some()); assert!(fdt.get_node("/a").is_none()); } #[test] fn fdt_find_nested_node() { let mut fdt = Fdt::new(&[]); let node1 = fdt.root.subnode_mut("N1").unwrap(); node1.subnode_mut("N1-1").unwrap(); node1.subnode_mut("N1-2").unwrap(); let node2 = fdt.root.subnode_mut("N2").unwrap(); let node2_1 = node2.subnode_mut("N2-1").unwrap(); node2_1.subnode_mut("N2-1-1").unwrap(); assert!(fdt.get_node("/").is_some()); assert!(fdt.get_node("/N1").is_some()); assert!(fdt.get_node("/N2").is_some()); assert!(fdt.get_node("/N1/N1-1").is_some()); assert!(fdt.get_node("/N1/N1-2").is_some()); assert!(fdt.get_node("/N2/N2-1").is_some()); assert!(fdt.get_node("/N2/N2-1/N2-1-1").is_some()); assert!(fdt.get_node("/N2/N2-1/A").is_none()); } #[test] fn minimal() { let mut fdt = Fdt::new(&[]); assert_eq!( fdt.finish().unwrap(), [ 0xd0, 0x0d, 0xfe, 0xed, // 0000: magic (0xd00dfeed) 0x00, 0x00, 0x00, 0x48, // 0004: totalsize (0x48) 0x00, 0x00, 0x00, 0x38, // 0008: off_dt_struct (0x38) 0x00, 0x00, 0x00, 0x48, // 000C: off_dt_strings (0x48) 0x00, 0x00, 0x00, 0x28, // 0010: off_mem_rsvmap (0x28) 0x00, 0x00, 0x00, 0x11, // 0014: version (0x11 = 17) 0x00, 0x00, 0x00, 0x10, // 0018: last_comp_version (0x10 = 16) 0x00, 0x00, 0x00, 0x00, // 001C: boot_cpuid_phys (0) 0x00, 0x00, 0x00, 0x00, // 0020: size_dt_strings (0) 0x00, 0x00, 0x00, 0x10, // 0024: size_dt_struct (0x10) 0x00, 0x00, 0x00, 0x00, // 0028: rsvmap terminator (address = 0 high) 0x00, 0x00, 0x00, 0x00, // 002C: rsvmap terminator (address = 0 low) 0x00, 0x00, 0x00, 0x00, // 0030: rsvmap terminator (size = 0 high) 0x00, 0x00, 0x00, 0x00, // 0034: rsvmap terminator (size = 0 low) 0x00, 0x00, 0x00, 0x01, // 0038: FDT_BEGIN_NODE 0x00, 0x00, 0x00, 0x00, // 003C: node name ("") + padding 0x00, 0x00, 0x00, 0x02, // 0040: FDT_END_NODE 0x00, 0x00, 0x00, 0x09, // 0044: FDT_END ] ); } #[test] fn reservemap() { let mut fdt = Fdt::new(&[ FdtReserveEntry { address: 0x12345678AABBCCDD, size: 0x1234, }, FdtReserveEntry { address: 0x1020304050607080, size: 0x5678, }, ]); assert_eq!( fdt.finish().unwrap(), [ 0xd0, 0x0d, 0xfe, 0xed, // 0000: magic (0xd00dfeed) 0x00, 0x00, 0x00, 0x68, // 0004: totalsize (0x68) 0x00, 0x00, 0x00, 0x58, // 0008: off_dt_struct (0x58) 0x00, 0x00, 0x00, 0x68, // 000C: off_dt_strings (0x68) 0x00, 0x00, 0x00, 0x28, // 0010: off_mem_rsvmap (0x28) 0x00, 0x00, 0x00, 0x11, // 0014: version (0x11 = 17) 0x00, 0x00, 0x00, 0x10, // 0018: last_comp_version (0x10 = 16) 0x00, 0x00, 0x00, 0x00, // 001C: boot_cpuid_phys (0) 0x00, 0x00, 0x00, 0x00, // 0020: size_dt_strings (0) 0x00, 0x00, 0x00, 0x10, // 0024: size_dt_struct (0x10) 0x12, 0x34, 0x56, 0x78, // 0028: rsvmap entry 0 address high 0xAA, 0xBB, 0xCC, 0xDD, // 002C: rsvmap entry 0 address low 0x00, 0x00, 0x00, 0x00, // 0030: rsvmap entry 0 size high 0x00, 0x00, 0x12, 0x34, // 0034: rsvmap entry 0 size low 0x10, 0x20, 0x30, 0x40, // 0038: rsvmap entry 1 address high 0x50, 0x60, 0x70, 0x80, // 003C: rsvmap entry 1 address low 0x00, 0x00, 0x00, 0x00, // 0040: rsvmap entry 1 size high 0x00, 0x00, 0x56, 0x78, // 0044: rsvmap entry 1 size low 0x00, 0x00, 0x00, 0x00, // 0048: rsvmap terminator (address = 0 high) 0x00, 0x00, 0x00, 0x00, // 004C: rsvmap terminator (address = 0 low) 0x00, 0x00, 0x00, 0x00, // 0050: rsvmap terminator (size = 0 high) 0x00, 0x00, 0x00, 0x00, // 0054: rsvmap terminator (size = 0 low) 0x00, 0x00, 0x00, 0x01, // 0058: FDT_BEGIN_NODE 0x00, 0x00, 0x00, 0x00, // 005C: node name ("") + padding 0x00, 0x00, 0x00, 0x02, // 0060: FDT_END_NODE 0x00, 0x00, 0x00, 0x09, // 0064: FDT_END ] ); } #[test] fn prop_null() { let mut fdt = Fdt::new(&[]); let root_node = fdt.root_mut(); root_node.set_prop("null", ()).unwrap(); assert_eq!( fdt.finish().unwrap(), [ 0xd0, 0x0d, 0xfe, 0xed, // 0000: magic (0xd00dfeed) 0x00, 0x00, 0x00, 0x59, // 0004: totalsize (0x59) 0x00, 0x00, 0x00, 0x38, // 0008: off_dt_struct (0x38) 0x00, 0x00, 0x00, 0x54, // 000C: off_dt_strings (0x54) 0x00, 0x00, 0x00, 0x28, // 0010: off_mem_rsvmap (0x28) 0x00, 0x00, 0x00, 0x11, // 0014: version (0x11 = 17) 0x00, 0x00, 0x00, 0x10, // 0018: last_comp_version (0x10 = 16) 0x00, 0x00, 0x00, 0x00, // 001C: boot_cpuid_phys (0) 0x00, 0x00, 0x00, 0x05, // 0020: size_dt_strings (0x05) 0x00, 0x00, 0x00, 0x1c, // 0024: size_dt_struct (0x1C) 0x00, 0x00, 0x00, 0x00, // 0028: rsvmap terminator (address = 0 high) 0x00, 0x00, 0x00, 0x00, // 002C: rsvmap terminator (address = 0 low) 0x00, 0x00, 0x00, 0x00, // 0030: rsvmap terminator (size = 0 high) 0x00, 0x00, 0x00, 0x00, // 0034: rsvmap terminator (size = 0 low) 0x00, 0x00, 0x00, 0x01, // 0038: FDT_BEGIN_NODE 0x00, 0x00, 0x00, 0x00, // 003C: node name ("") + padding 0x00, 0x00, 0x00, 0x03, // 0040: FDT_PROP 0x00, 0x00, 0x00, 0x00, // 0044: prop len (0) 0x00, 0x00, 0x00, 0x00, // 0048: prop nameoff (0) 0x00, 0x00, 0x00, 0x02, // 004C: FDT_END_NODE 0x00, 0x00, 0x00, 0x09, // 0050: FDT_END b'n', b'u', b'l', b'l', 0x00, // 0054: strings block ] ); } #[test] fn prop_u32() { let mut fdt = Fdt::new(&[]); let root_node = fdt.root_mut(); root_node.set_prop("u32", 0x12345678u32).unwrap(); assert_eq!( fdt.finish().unwrap(), [ 0xd0, 0x0d, 0xfe, 0xed, // 0000: magic (0xd00dfeed) 0x00, 0x00, 0x00, 0x5c, // 0004: totalsize (0x5C) 0x00, 0x00, 0x00, 0x38, // 0008: off_dt_struct (0x38) 0x00, 0x00, 0x00, 0x58, // 000C: off_dt_strings (0x58) 0x00, 0x00, 0x00, 0x28, // 0010: off_mem_rsvmap (0x28) 0x00, 0x00, 0x00, 0x11, // 0014: version (0x11 = 17) 0x00, 0x00, 0x00, 0x10, // 0018: last_comp_version (0x10 = 16) 0x00, 0x00, 0x00, 0x00, // 001C: boot_cpuid_phys (0) 0x00, 0x00, 0x00, 0x04, // 0020: size_dt_strings (0x04) 0x00, 0x00, 0x00, 0x20, // 0024: size_dt_struct (0x20) 0x00, 0x00, 0x00, 0x00, // 0028: rsvmap terminator (address = 0 high) 0x00, 0x00, 0x00, 0x00, // 002C: rsvmap terminator (address = 0 low) 0x00, 0x00, 0x00, 0x00, // 0030: rsvmap terminator (size = 0 high) 0x00, 0x00, 0x00, 0x00, // 0034: rsvmap terminator (size = 0 low) 0x00, 0x00, 0x00, 0x01, // 0038: FDT_BEGIN_NODE 0x00, 0x00, 0x00, 0x00, // 003C: node name ("") + padding 0x00, 0x00, 0x00, 0x03, // 0040: FDT_PROP 0x00, 0x00, 0x00, 0x04, // 0044: prop len (4) 0x00, 0x00, 0x00, 0x00, // 0048: prop nameoff (0) 0x12, 0x34, 0x56, 0x78, // 004C: prop u32 value (0x12345678) 0x00, 0x00, 0x00, 0x02, // 0050: FDT_END_NODE 0x00, 0x00, 0x00, 0x09, // 0054: FDT_END b'u', b'3', b'2', 0x00, // 0058: strings block ] ); } #[test] fn all_props() { let mut fdt = Fdt::new(&[]); let root_node = fdt.root_mut(); root_node .set_prop("arru32", &[0x12345678u32, 0xAABBCCDDu32]) .unwrap(); root_node .set_prop("arru64", &[0x1234567887654321u64]) .unwrap(); root_node.set_prop("null", ()).unwrap(); root_node.set_prop("str", "hello").unwrap(); root_node.set_prop("strlst", &["hi", "bye"]).unwrap(); root_node.set_prop("u32", 0x12345678u32).unwrap(); root_node.set_prop("u64", 0x1234567887654321u64).unwrap(); assert_eq!( fdt.finish().unwrap(), [ 0xd0, 0x0d, 0xfe, 0xed, // 0000: magic (0xd00dfeed) 0x00, 0x00, 0x00, 0xee, // 0004: totalsize (0xEE) 0x00, 0x00, 0x00, 0x38, // 0008: off_dt_struct (0x38) 0x00, 0x00, 0x00, 0xc8, // 000C: off_dt_strings (0xC8) 0x00, 0x00, 0x00, 0x28, // 0010: off_mem_rsvmap (0x28) 0x00, 0x00, 0x00, 0x11, // 0014: version (0x11 = 17) 0x00, 0x00, 0x00, 0x10, // 0018: last_comp_version (0x10 = 16) 0x00, 0x00, 0x00, 0x00, // 001C: boot_cpuid_phys (0) 0x00, 0x00, 0x00, 0x26, // 0020: size_dt_strings (0x26) 0x00, 0x00, 0x00, 0x90, // 0024: size_dt_struct (0x90) 0x00, 0x00, 0x00, 0x00, // 0028: rsvmap terminator (address = 0 high) 0x00, 0x00, 0x00, 0x00, // 002C: rsvmap terminator (address = 0 low) 0x00, 0x00, 0x00, 0x00, // 0030: rsvmap terminator (size = 0 high) 0x00, 0x00, 0x00, 0x00, // 0034: rsvmap terminator (size = 0 low) 0x00, 0x00, 0x00, 0x01, // 0038: FDT_BEGIN_NODE 0x00, 0x00, 0x00, 0x00, // 003C: node name ("") + padding 0x00, 0x00, 0x00, 0x03, // 0040: FDT_PROP (u32 array) 0x00, 0x00, 0x00, 0x08, // 0044: prop len (8) 0x00, 0x00, 0x00, 0x00, // 0048: prop nameoff (0x00) 0x12, 0x34, 0x56, 0x78, // 004C: prop value 0 0xAA, 0xBB, 0xCC, 0xDD, // 0050: prop value 1 0x00, 0x00, 0x00, 0x03, // 0054: FDT_PROP (u64 array) 0x00, 0x00, 0x00, 0x08, // 0058: prop len (8) 0x00, 0x00, 0x00, 0x07, // 005C: prop nameoff (0x07) 0x12, 0x34, 0x56, 0x78, // 0060: prop u64 value 0 high 0x87, 0x65, 0x43, 0x21, // 0064: prop u64 value 0 low 0x00, 0x00, 0x00, 0x03, // 0068: FDT_PROP (null) 0x00, 0x00, 0x00, 0x00, // 006C: prop len (0) 0x00, 0x00, 0x00, 0x0E, // 0070: prop nameoff (0x0e) 0x00, 0x00, 0x00, 0x03, // 0074: FDT_PROP (string) 0x00, 0x00, 0x00, 0x06, // 0078: prop len (6) 0x00, 0x00, 0x00, 0x13, // 007C: prop nameoff (0x13) b'h', b'e', b'l', b'l', // 0080: prop str value ("hello") + padding b'o', 0x00, 0x00, 0x00, // 0084: "o\0" + padding 0x00, 0x00, 0x00, 0x03, // 0088: FDT_PROP (string list) 0x00, 0x00, 0x00, 0x07, // 008C: prop len (7) 0x00, 0x00, 0x00, 0x17, // 0090: prop nameoff (0x17) b'h', b'i', 0x00, b'b', // 0094: prop value ("hi", "bye") b'y', b'e', 0x00, 0x00, // 0098: "ye\0" + padding 0x00, 0x00, 0x00, 0x03, // 009C: FDT_PROP (u32) 0x00, 0x00, 0x00, 0x04, // 00A0: prop len (4) 0x00, 0x00, 0x00, 0x1E, // 00A4: prop nameoff (0x1E) 0x12, 0x34, 0x56, 0x78, // 00A8: prop u32 value (0x12345678) 0x00, 0x00, 0x00, 0x03, // 00AC: FDT_PROP (u64) 0x00, 0x00, 0x00, 0x08, // 00B0: prop len (8) 0x00, 0x00, 0x00, 0x22, // 00B4: prop nameoff (0x22) 0x12, 0x34, 0x56, 0x78, // 00B8: prop u64 value high (0x12345678) 0x87, 0x65, 0x43, 0x21, // 00BC: prop u64 value low (0x87654321) 0x00, 0x00, 0x00, 0x02, // 00C0: FDT_END_NODE 0x00, 0x00, 0x00, 0x09, // 00C4: FDT_END b'a', b'r', b'r', b'u', b'3', b'2', 0x00, // 00C8: strings + 0x00: "arru32" b'a', b'r', b'r', b'u', b'6', b'4', 0x00, // 00CF: strings + 0x07: "arru64" b'n', b'u', b'l', b'l', 0x00, // 00D6: strings + 0x0E: "null" b's', b't', b'r', 0x00, // 00DB: strings + 0x13: "str" b's', b't', b'r', b'l', b's', b't', 0x00, // 00DF: strings + 0x17: "strlst" b'u', b'3', b'2', 0x00, // 00E6: strings + 0x1E: "u32" b'u', b'6', b'4', 0x00, // 00EA: strings + 0x22: "u64" ] ); } #[test] fn node_order() { let expected: &[u8] = &[ 0xd0, 0x0d, 0xfe, 0xed, // 0000: magic (0xd00dfeed) 0x00, 0x00, 0x00, 0x9C, // 0004: totalsize (0x9C) 0x00, 0x00, 0x00, 0x38, // 0008: off_dt_struct (0x38) 0x00, 0x00, 0x00, 0x9C, // 000C: off_dt_strings (0x9C) 0x00, 0x00, 0x00, 0x28, // 0010: off_mem_rsvmap (0x28) 0x00, 0x00, 0x00, 0x11, // 0014: version (0x11 = 17) 0x00, 0x00, 0x00, 0x10, // 0018: last_comp_version (0x10 = 16) 0x00, 0x00, 0x00, 0x00, // 001C: boot_cpuid_phys (0) 0x00, 0x00, 0x00, 0x00, // 0020: size_dt_strings (0x00) 0x00, 0x00, 0x00, 0x64, // 0024: size_dt_struct (0x64) 0x00, 0x00, 0x00, 0x00, // 0028: rsvmap terminator (address = 0 high) 0x00, 0x00, 0x00, 0x00, // 002C: rsvmap terminator (address = 0 low) 0x00, 0x00, 0x00, 0x00, // 0030: rsvmap terminator (size = 0 high) 0x00, 0x00, 0x00, 0x00, // 0034: rsvmap terminator (size = 0 low) 0x00, 0x00, 0x00, 0x01, // 0038: FDT_BEGIN_NODE 0x00, 0x00, 0x00, 0x00, // 003C: node name ("") + padding 0x00, 0x00, 0x00, 0x01, // 0040: FDT_BEGIN_NODE b'B', 0x00, 0x00, 0x00, // 0044: node name ("B") + padding 0x00, 0x00, 0x00, 0x02, // 0048: FDT_END_NODE 0x00, 0x00, 0x00, 0x01, // 004C: FDT_BEGIN_NODE b'A', 0x00, 0x00, 0x00, // 0050: node name ("A") + padding 0x00, 0x00, 0x00, 0x02, // 0054: FDT_END_NODE 0x00, 0x00, 0x00, 0x01, // 0058: FDT_BEGIN_NODE b'C', 0x00, 0x00, 0x00, // 005C: node name ("C") + padding 0x00, 0x00, 0x00, 0x01, // 0060: FDT_BEGIN_NODE b'D', 0x00, 0x00, 0x00, // 0064: node name ("D") + padding 0x00, 0x00, 0x00, 0x02, // 0068: FDT_END_NODE 0x00, 0x00, 0x00, 0x01, // 006C: FDT_BEGIN_NODE b'E', 0x00, 0x00, 0x00, // 0070: node name ("E") + padding 0x00, 0x00, 0x00, 0x02, // 0074: FDT_END_NODE 0x00, 0x00, 0x00, 0x01, // 0078: FDT_BEGIN_NODE b'B', 0x00, 0x00, 0x00, // 007C: node name ("B") + padding 0x00, 0x00, 0x00, 0x02, // 0080: FDT_END_NODE 0x00, 0x00, 0x00, 0x01, // 0084: FDT_BEGIN_NODE b'F', 0x00, 0x00, 0x00, // 0088: node name ("F") + padding 0x00, 0x00, 0x00, 0x02, // 008C: FDT_END_NODE 0x00, 0x00, 0x00, 0x02, // 0090: FDT_END_NODE 0x00, 0x00, 0x00, 0x02, // 0094: FDT_END_NODE 0x00, 0x00, 0x00, 0x09, // 0098: FDT_END ]; let mut fdt = Fdt::new(&[]); let root = fdt.root_mut(); let root_subnode_names = ["B", "A", "C"]; let node_c_subnode_names = ["D", "E", "B", "F"]; for n in root_subnode_names { root.subnode_mut(n).unwrap(); } let node_c = root.subnode_mut("C").unwrap(); for n in node_c_subnode_names { node_c.subnode_mut(n).unwrap(); } assert!(root .iter_subnodes() .zip(root_subnode_names) .all(|(sn, n)| sn.name == n)); assert!(root .subnode("C") .unwrap() .iter_subnodes() .zip(node_c_subnode_names) .all(|(sn, n)| sn.name == n)); assert_eq!(fdt.finish().unwrap(), expected); } #[test] fn prop_order() { let expected: &[u8] = &[ 0xd0, 0x0d, 0xfe, 0xed, // 0000: magic (0xd00dfeed) 0x00, 0x00, 0x00, 0x98, // 0004: totalsize (0x98) 0x00, 0x00, 0x00, 0x38, // 0008: off_dt_struct (0x38) 0x00, 0x00, 0x00, 0x88, // 000C: off_dt_strings (0x88) 0x00, 0x00, 0x00, 0x28, // 0010: off_mem_rsvmap (0x28) 0x00, 0x00, 0x00, 0x11, // 0014: version (0x11 = 17) 0x00, 0x00, 0x00, 0x10, // 0018: last_comp_version (0x10 = 16) 0x00, 0x00, 0x00, 0x00, // 001C: boot_cpuid_phys (0) 0x00, 0x00, 0x00, 0x10, // 0020: size_dt_strings (0x10) 0x00, 0x00, 0x00, 0x50, // 0024: size_dt_struct (0x50) 0x00, 0x00, 0x00, 0x00, // 0028: rsvmap terminator (address = 0 high) 0x00, 0x00, 0x00, 0x00, // 002C: rsvmap terminator (address = 0 low) 0x00, 0x00, 0x00, 0x00, // 0030: rsvmap terminator (size = 0 high) 0x00, 0x00, 0x00, 0x00, // 0034: rsvmap terminator (size = 0 low) 0x00, 0x00, 0x00, 0x01, // 0038: FDT_BEGIN_NODE 0x00, 0x00, 0x00, 0x00, // 003C: node name ("") + padding 0x00, 0x00, 0x00, 0x03, // 0040: FDT_PROP (u32) 0x00, 0x00, 0x00, 0x04, // 0044: prop len (4) 0x00, 0x00, 0x00, 0x00, // 0048: prop nameoff (0x00) 0x76, 0x61, 0x6c, 0x00, // 004C: prop string value ("val") 0x00, 0x00, 0x00, 0x03, // 0050: FDT_PROP (u32) 0x00, 0x00, 0x00, 0x04, // 0054: prop len (4) 0x00, 0x00, 0x00, 0x04, // 0058: prop nameoff (0x04) 0x00, 0x00, 0x00, 0x02, // 005C: prop u32 high (0x2) 0x00, 0x00, 0x00, 0x03, // 0060: FDT_PROP (u32) 0x00, 0x00, 0x00, 0x04, // 0064: prop len (4) 0x00, 0x00, 0x00, 0x08, // 0068: prop nameoff (0x08) 0x00, 0x00, 0x00, 0x01, // 006C: prop u32 value (0x1) 0x00, 0x00, 0x00, 0x03, // 0070: FDT_PROP (u32) 0x00, 0x00, 0x00, 0x04, // 0074: prop len (4) 0x00, 0x00, 0x00, 0x0C, // 0078: prop nameoff (0x0B) 0x00, 0x00, 0x00, 0x03, // 007C: prop u32 value (0x3) 0x00, 0x00, 0x00, 0x02, // 0080: FDT_END_NODE 0x00, 0x00, 0x00, 0x09, // 0084: FDT_END b'g', b'h', b'i', 0x00, // 0088: strings + 0x00: "ghi" b'd', b'e', b'f', 0x00, // 008C: strings + 0x04: "def" b'a', b'b', b'c', 0x00, // 0090: strings + 0x08: "abc" b'b', b'c', b'd', 0x00, // 0094: strings + 0x0C: "bcd" ]; let mut fdt = Fdt::new(&[]); let root_node = fdt.root_mut(); root_node.set_prop("ghi", "val").unwrap(); root_node.set_prop("def", 2u32).unwrap(); root_node.set_prop("abc", 1u32).unwrap(); root_node.set_prop("bcd", 3u32).unwrap(); assert_eq!( root_node.prop_names().collect::>(), ["ghi", "def", "abc", "bcd"] ); assert_eq!(fdt.finish().unwrap(), expected); } #[test] fn nested_nodes() { let mut fdt = Fdt::new(&[]); let root_node = fdt.root_mut(); root_node.set_prop("abc", 0x13579024u32).unwrap(); let nested_node = root_node.subnode_mut("nested").unwrap(); nested_node.set_prop("def", 0x12121212u32).unwrap(); assert_eq!( fdt.finish().unwrap(), [ 0xd0, 0x0d, 0xfe, 0xed, // 0000: magic (0xd00dfeed) 0x00, 0x00, 0x00, 0x80, // 0004: totalsize (0x80) 0x00, 0x00, 0x00, 0x38, // 0008: off_dt_struct (0x38) 0x00, 0x00, 0x00, 0x78, // 000C: off_dt_strings (0x78) 0x00, 0x00, 0x00, 0x28, // 0010: off_mem_rsvmap (0x28) 0x00, 0x00, 0x00, 0x11, // 0014: version (0x11 = 17) 0x00, 0x00, 0x00, 0x10, // 0018: last_comp_version (0x10 = 16) 0x00, 0x00, 0x00, 0x00, // 001C: boot_cpuid_phys (0) 0x00, 0x00, 0x00, 0x08, // 0020: size_dt_strings (0x08) 0x00, 0x00, 0x00, 0x40, // 0024: size_dt_struct (0x40) 0x00, 0x00, 0x00, 0x00, // 0028: rsvmap terminator (address = 0 high) 0x00, 0x00, 0x00, 0x00, // 002C: rsvmap terminator (address = 0 low) 0x00, 0x00, 0x00, 0x00, // 0030: rsvmap terminator (size = 0 high) 0x00, 0x00, 0x00, 0x00, // 0034: rsvmap terminator (size = 0 low) 0x00, 0x00, 0x00, 0x01, // 0038: FDT_BEGIN_NODE 0x00, 0x00, 0x00, 0x00, // 003C: node name ("") + padding 0x00, 0x00, 0x00, 0x03, // 0040: FDT_PROP 0x00, 0x00, 0x00, 0x04, // 0044: prop len (4) 0x00, 0x00, 0x00, 0x00, // 0048: prop nameoff (0x00) 0x13, 0x57, 0x90, 0x24, // 004C: prop u32 value (0x13579024) 0x00, 0x00, 0x00, 0x01, // 0050: FDT_BEGIN_NODE b'n', b'e', b's', b't', // 0054: Node name ("nested") b'e', b'd', 0x00, 0x00, // 0058: "ed\0" + pad 0x00, 0x00, 0x00, 0x03, // 005C: FDT_PROP 0x00, 0x00, 0x00, 0x04, // 0060: prop len (4) 0x00, 0x00, 0x00, 0x04, // 0064: prop nameoff (0x04) 0x12, 0x12, 0x12, 0x12, // 0068: prop u32 value (0x12121212) 0x00, 0x00, 0x00, 0x02, // 006C: FDT_END_NODE ("nested") 0x00, 0x00, 0x00, 0x02, // 0070: FDT_END_NODE ("") 0x00, 0x00, 0x00, 0x09, // 0074: FDT_END b'a', b'b', b'c', 0x00, // 0078: strings + 0x00: "abc" b'd', b'e', b'f', 0x00, // 007C: strings + 0x04: "def" ] ); } #[test] fn prop_name_string_reuse() { let mut fdt = Fdt::new(&[]); let root_node = fdt.root_mut(); root_node.set_prop("abc", 0x13579024u32).unwrap(); let nested = root_node.subnode_mut("nested").unwrap(); nested.set_prop("abc", 0x12121212u32).unwrap(); // This should reuse the "abc" string. nested.set_prop("def", 0x12121212u32).unwrap(); assert_eq!( fdt.finish().unwrap(), [ 0xd0, 0x0d, 0xfe, 0xed, // 0000: magic (0xd00dfeed) 0x00, 0x00, 0x00, 0x90, // 0004: totalsize (0x90) 0x00, 0x00, 0x00, 0x38, // 0008: off_dt_struct (0x38) 0x00, 0x00, 0x00, 0x88, // 000C: off_dt_strings (0x88) 0x00, 0x00, 0x00, 0x28, // 0010: off_mem_rsvmap (0x28) 0x00, 0x00, 0x00, 0x11, // 0014: version (0x11 = 17) 0x00, 0x00, 0x00, 0x10, // 0018: last_comp_version (0x10 = 16) 0x00, 0x00, 0x00, 0x00, // 001C: boot_cpuid_phys (0) 0x00, 0x00, 0x00, 0x08, // 0020: size_dt_strings (0x08) 0x00, 0x00, 0x00, 0x50, // 0024: size_dt_struct (0x50) 0x00, 0x00, 0x00, 0x00, // 0028: rsvmap terminator (address = 0 high) 0x00, 0x00, 0x00, 0x00, // 002C: rsvmap terminator (address = 0 low) 0x00, 0x00, 0x00, 0x00, // 0030: rsvmap terminator (size = 0 high) 0x00, 0x00, 0x00, 0x00, // 0034: rsvmap terminator (size = 0 low) 0x00, 0x00, 0x00, 0x01, // 0038: FDT_BEGIN_NODE 0x00, 0x00, 0x00, 0x00, // 003C: node name ("") + padding 0x00, 0x00, 0x00, 0x03, // 0040: FDT_PROP 0x00, 0x00, 0x00, 0x04, // 0044: prop len (4) 0x00, 0x00, 0x00, 0x00, // 0048: prop nameoff (0x00) 0x13, 0x57, 0x90, 0x24, // 004C: prop u32 value (0x13579024) 0x00, 0x00, 0x00, 0x01, // 0050: FDT_BEGIN_NODE b'n', b'e', b's', b't', // 0054: Node name ("nested") b'e', b'd', 0x00, 0x00, // 0058: "ed\0" + pad 0x00, 0x00, 0x00, 0x03, // 005C: FDT_PROP 0x00, 0x00, 0x00, 0x04, // 0060: prop len (4) 0x00, 0x00, 0x00, 0x00, // 0064: prop nameoff (0x00 - reuse) 0x12, 0x12, 0x12, 0x12, // 0068: prop u32 value (0x12121212) 0x00, 0x00, 0x00, 0x03, // 006C: FDT_PROP 0x00, 0x00, 0x00, 0x04, // 0070: prop len (4) 0x00, 0x00, 0x00, 0x04, // 0074: prop nameoff (0x04) 0x12, 0x12, 0x12, 0x12, // 0078: prop u32 value (0x12121212) 0x00, 0x00, 0x00, 0x02, // 007C: FDT_END_NODE ("nested") 0x00, 0x00, 0x00, 0x02, // 0080: FDT_END_NODE ("") 0x00, 0x00, 0x00, 0x09, // 0084: FDT_END b'a', b'b', b'c', 0x00, // 0088: strings + 0x00: "abc" b'd', b'e', b'f', 0x00, // 008C: strings + 0x04: "def" ] ); } #[test] fn invalid_node_name_nul() { let mut fdt = Fdt::new(&[]); let root_node = fdt.root_mut(); root_node .subnode_mut("abc\0def") .expect_err("node name with embedded NUL"); } #[test] fn invalid_prop_name_nul() { let mut fdt = Fdt::new(&[]); let root_node = fdt.root_mut(); root_node .set_prop("abc\0def", 0u32) .expect_err("property name with embedded NUL"); } #[test] fn invalid_prop_string_value_nul() { let mut fdt = Fdt::new(&[]); let root_node = fdt.root_mut(); root_node .set_prop("mystr", "abc\0def") .expect_err("string property value with embedded NUL"); } #[test] fn invalid_prop_string_list_value_nul() { let mut fdt = Fdt::new(&[]); let root_node = fdt.root_mut(); let strs = ["test", "abc\0def"]; root_node .set_prop("mystr", &strs) .expect_err("stringlist property value with embedded NUL"); } }