blob: 944e5025ad688a81d5696996dfdc184c20e84744 [file] [log] [blame]
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// 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:
//! <https://devicetree-specification.readthedocs.io/en/stable/flattened-format.html>
use std::collections::BTreeMap;
use std::convert::TryInto;
use std::ffi::CString;
use std::io;
use std::mem::size_of;
use thiserror::Error as ThisError;
#[derive(ThisError, Debug)]
pub enum Error {
#[error("Properties may not be added after a node has been ended")]
PropertyAfterEndNode,
#[error("Property value size must fit in 32 bits")]
PropertyValueTooLarge,
#[error("Total size must fit in 32 bits")]
TotalSizeTooLarge,
#[error("Strings cannot contain NUL")]
InvalidString,
#[error("Attempted to end a node that was not the most recent")]
OutOfOrderEndNode,
#[error("Attempted to call finish without ending all nodes")]
UnclosedNode,
#[error("Error writing FDT to guest memory")]
FdtGuestMemoryWriteError,
#[error("Parse error reading FDT parameters")]
FdtFileParseError,
#[error("I/O error reading FDT parameters code={0}")]
FdtIoError(io::Error),
}
pub type Result<T> = std::result::Result<T, Error>;
const FDT_HEADER_SIZE: usize = 40;
const FDT_VERSION: u32 = 17;
const FDT_LAST_COMP_VERSION: u32 = 16;
const FDT_MAGIC: u32 = 0xd00dfeed;
const FDT_BEGIN_NODE: u32 = 0x00000001;
const FDT_END_NODE: u32 = 0x00000002;
const FDT_PROP: u32 = 0x00000003;
const FDT_END: u32 = 0x00000009;
/// Interface for writing a Flattened Devicetree (FDT) and emitting a Devicetree Blob (DTB).
///
/// # Example
///
/// ```rust
/// use arch::fdt::FdtWriter;
///
/// # fn main() -> arch::fdt::Result<()> {
/// let mut fdt = FdtWriter::new(&[]);
/// let root_node = fdt.begin_node("")?;
/// fdt.property_string("compatible", "linux,dummy-virt")?;
/// fdt.property_u32("#address-cells", 0x2)?;
/// fdt.property_u32("#size-cells", 0x2)?;
/// let chosen_node = fdt.begin_node("chosen")?;
/// fdt.property_u32("linux,pci-probe-only", 1)?;
/// fdt.property_string("bootargs", "panic=-1 console=hvc0 root=/dev/vda")?;
/// fdt.end_node(chosen_node)?;
/// fdt.end_node(root_node)?;
/// let dtb = fdt.finish(0x1000)?;
/// # Ok(())
/// # }
/// ```
pub struct FdtWriter {
data: Vec<u8>,
off_mem_rsvmap: u32,
off_dt_struct: u32,
strings: Vec<u8>,
string_offsets: BTreeMap<CString, u32>,
node_depth: usize,
node_ended: bool,
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.
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,
}
/// Handle to an open node created by `FdtWriter::begin_node`.
///
/// This must be passed back to `FdtWriter::end_node` to close the nodes.
/// Nodes must be closed in reverse order as they were opened, matching the nesting structure
/// of the devicetree.
#[derive(Debug)]
pub struct FdtWriterNode {
depth: usize,
}
impl FdtWriter {
/// Create a new Flattened Devicetree writer instance.
///
/// # Arguments
///
/// `mem_reservations` - reserved physical memory regions to list in the FDT header.
pub fn new(mem_reservations: &[FdtReserveEntry]) -> Self {
let data = vec![0u8; FDT_HEADER_SIZE]; // Reserve space for header.
let mut fdt = FdtWriter {
data,
off_mem_rsvmap: 0,
off_dt_struct: 0,
strings: Vec::new(),
string_offsets: BTreeMap::new(),
node_depth: 0,
node_ended: false,
boot_cpuid_phys: 0,
};
fdt.align(8);
fdt.off_mem_rsvmap = fdt.data.len() as u32;
fdt.write_mem_rsvmap(mem_reservations);
fdt.align(4);
fdt.off_dt_struct = fdt.data.len() as u32;
fdt
}
fn write_mem_rsvmap(&mut self, mem_reservations: &[FdtReserveEntry]) {
for rsv in mem_reservations {
self.append_u64(rsv.address);
self.append_u64(rsv.size);
}
self.append_u64(0);
self.append_u64(0);
}
/// Set the `boot_cpuid_phys` field of the devicetree header.
pub fn set_boot_cpuid_phys(&mut self, boot_cpuid_phys: u32) {
self.boot_cpuid_phys = boot_cpuid_phys;
}
// Append `num_bytes` padding bytes (0x00).
fn pad(&mut self, num_bytes: usize) {
self.data.extend(std::iter::repeat(0).take(num_bytes));
}
// Append padding bytes (0x00) until the length of data is a multiple of `alignment`.
fn align(&mut self, alignment: usize) {
let offset = self.data.len() % alignment;
if offset != 0 {
self.pad(alignment - offset);
}
}
// Rewrite the value of a big-endian u32 within data.
fn update_u32(&mut self, offset: usize, val: u32) {
let data_slice = &mut self.data[offset..offset + 4];
data_slice.copy_from_slice(&val.to_be_bytes());
}
fn append_u32(&mut self, val: u32) {
self.data.extend_from_slice(&val.to_be_bytes());
}
fn append_u64(&mut self, val: u64) {
self.data.extend_from_slice(&val.to_be_bytes());
}
/// Open a new FDT node.
///
/// The node must be closed using `end_node`.
///
/// # Arguments
///
/// `name` - name of the node; must not contain any NUL bytes.
pub fn begin_node(&mut self, name: &str) -> Result<FdtWriterNode> {
let name_cstr = CString::new(name).map_err(|_| Error::InvalidString)?;
self.append_u32(FDT_BEGIN_NODE);
self.data.extend(name_cstr.to_bytes_with_nul());
self.align(4);
self.node_depth += 1;
self.node_ended = false;
Ok(FdtWriterNode {
depth: self.node_depth,
})
}
/// Close a node previously opened with `begin_node`.
pub fn end_node(&mut self, node: FdtWriterNode) -> Result<()> {
if node.depth != self.node_depth {
return Err(Error::OutOfOrderEndNode);
}
self.append_u32(FDT_END_NODE);
self.node_depth -= 1;
self.node_ended = true;
Ok(())
}
// 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: CString) -> 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.to_bytes_with_nul());
self.string_offsets.insert(s, off);
off
}
}
/// Write a property.
///
/// # Arguments
///
/// `name` - name of the property; must not contain any NUL bytes.
/// `val` - value of the property (raw byte array).
pub fn property(&mut self, name: &str, val: &[u8]) -> Result<()> {
if self.node_ended {
return Err(Error::PropertyAfterEndNode);
}
let name_cstr = CString::new(name).map_err(|_| Error::InvalidString)?;
let len = val
.len()
.try_into()
.map_err(|_| Error::PropertyValueTooLarge)?;
let nameoff = self.intern_string(name_cstr);
self.append_u32(FDT_PROP);
self.append_u32(len);
self.append_u32(nameoff);
self.data.extend_from_slice(val);
self.align(4);
Ok(())
}
/// Write an empty property.
pub fn property_null(&mut self, name: &str) -> Result<()> {
self.property(name, &[])
}
/// Write a string property.
pub fn property_string(&mut self, name: &str, val: &str) -> Result<()> {
let cstr_value = CString::new(val).map_err(|_| Error::InvalidString)?;
self.property(name, cstr_value.to_bytes_with_nul())
}
/// Write a stringlist property.
pub fn property_string_list(&mut self, name: &str, values: Vec<String>) -> Result<()> {
let mut bytes = Vec::new();
for s in values {
let cstr = CString::new(s).map_err(|_| Error::InvalidString)?;
bytes.extend_from_slice(&cstr.to_bytes_with_nul());
}
self.property(name, &bytes)
}
/// Write a 32-bit unsigned integer property.
pub fn property_u32(&mut self, name: &str, val: u32) -> Result<()> {
self.property(name, &val.to_be_bytes())
}
/// Write a 64-bit unsigned integer property.
pub fn property_u64(&mut self, name: &str, val: u64) -> Result<()> {
self.property(name, &val.to_be_bytes())
}
/// Write a property containing an array of 32-bit unsigned integers.
pub fn property_array_u32(&mut self, name: &str, cells: &[u32]) -> Result<()> {
let mut arr = Vec::with_capacity(cells.len() * size_of::<u32>());
for &c in cells {
arr.extend(&c.to_be_bytes());
}
self.property(name, &arr)
}
/// Write a property containing an array of 64-bit unsigned integers.
pub fn property_array_u64(&mut self, name: &str, cells: &[u64]) -> Result<()> {
let mut arr = Vec::with_capacity(cells.len() * size_of::<u64>());
for &c in cells {
arr.extend(&c.to_be_bytes());
}
self.property(name, &arr)
}
/// Finish writing the Devicetree Blob (DTB).
///
/// Returns the DTB as a vector of bytes, consuming the `FdtWriter`.
/// The DTB is always padded up to `max_size` with zeroes, so the returned
/// value will either be exactly `max_size` bytes long, or an error will
/// be returned if the DTB does not fit in `max_size` bytes.
///
/// # Arguments
///
/// `max_size` - Maximum size of the finished DTB in bytes.
pub fn finish(mut self, max_size: usize) -> Result<Vec<u8>> {
if self.node_depth > 0 {
return Err(Error::UnclosedNode);
}
self.append_u32(FDT_END);
let size_dt_struct = self.data.len() as u32 - self.off_dt_struct;
let totalsize = self.data.len() + self.strings.len();
let totalsize = totalsize.try_into().map_err(|_| Error::TotalSizeTooLarge)?;
let off_dt_strings = self
.data
.len()
.try_into()
.map_err(|_| Error::TotalSizeTooLarge)?;
let size_dt_strings = self
.strings
.len()
.try_into()
.map_err(|_| Error::TotalSizeTooLarge)?;
// Finalize the header.
self.update_u32(0, FDT_MAGIC);
self.update_u32(1 * 4, totalsize);
self.update_u32(2 * 4, self.off_dt_struct);
self.update_u32(3 * 4, off_dt_strings);
self.update_u32(4 * 4, self.off_mem_rsvmap);
self.update_u32(5 * 4, FDT_VERSION);
self.update_u32(6 * 4, FDT_LAST_COMP_VERSION);
self.update_u32(7 * 4, self.boot_cpuid_phys);
self.update_u32(8 * 4, size_dt_strings);
self.update_u32(9 * 4, size_dt_struct);
// Add the strings block.
self.data.append(&mut self.strings);
if self.data.len() > max_size {
Err(Error::TotalSizeTooLarge)
} else {
// Fill remaining data up to `max_size` with zeroes.
self.pad(max_size - self.data.len());
Ok(self.data)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn minimal() {
let mut fdt = FdtWriter::new(&[]);
let root_node = fdt.begin_node("").unwrap();
fdt.end_node(root_node).unwrap();
assert_eq!(
fdt.finish(0x48).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 = FdtWriter::new(&[
FdtReserveEntry {
address: 0x12345678AABBCCDD,
size: 0x1234,
},
FdtReserveEntry {
address: 0x1020304050607080,
size: 0x5678,
},
]);
let root_node = fdt.begin_node("").unwrap();
fdt.end_node(root_node).unwrap();
assert_eq!(
fdt.finish(0x68).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 = FdtWriter::new(&[]);
let root_node = fdt.begin_node("").unwrap();
fdt.property_null("null").unwrap();
fdt.end_node(root_node).unwrap();
assert_eq!(
fdt.finish(0x59).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 = FdtWriter::new(&[]);
let root_node = fdt.begin_node("").unwrap();
fdt.property_u32("u32", 0x12345678).unwrap();
fdt.end_node(root_node).unwrap();
assert_eq!(
fdt.finish(0x5C).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 = FdtWriter::new(&[]);
let root_node = fdt.begin_node("").unwrap();
fdt.property_null("null").unwrap();
fdt.property_u32("u32", 0x12345678).unwrap();
fdt.property_u64("u64", 0x1234567887654321).unwrap();
fdt.property_string("str", "hello").unwrap();
fdt.property_string_list("strlst", vec!["hi".into(), "bye".into()])
.unwrap();
fdt.property_array_u32("arru32", &[0x12345678, 0xAABBCCDD])
.unwrap();
fdt.property_array_u64("arru64", &[0x1234567887654321])
.unwrap();
fdt.end_node(root_node).unwrap();
assert_eq!(
fdt.finish(0xEE).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 (null)
0x00, 0x00, 0x00, 0x00, // 0044: prop len (0)
0x00, 0x00, 0x00, 0x00, // 0048: prop nameoff (0)
0x00, 0x00, 0x00, 0x03, // 004C: FDT_PROP (u32)
0x00, 0x00, 0x00, 0x04, // 0050: prop len (4)
0x00, 0x00, 0x00, 0x05, // 0054: prop nameoff (0x05)
0x12, 0x34, 0x56, 0x78, // 0058: prop u32 value (0x12345678)
0x00, 0x00, 0x00, 0x03, // 005C: FDT_PROP (u64)
0x00, 0x00, 0x00, 0x08, // 0060: prop len (8)
0x00, 0x00, 0x00, 0x09, // 0064: prop nameoff (0x09)
0x12, 0x34, 0x56, 0x78, // 0068: prop u64 value high (0x12345678)
0x87, 0x65, 0x43, 0x21, // 006C: prop u64 value low (0x87654321)
0x00, 0x00, 0x00, 0x03, // 0070: FDT_PROP (string)
0x00, 0x00, 0x00, 0x06, // 0074: prop len (6)
0x00, 0x00, 0x00, 0x0D, // 0078: prop nameoff (0x0D)
b'h', b'e', b'l', b'l', // 007C: prop str value ("hello") + padding
b'o', 0x00, 0x00, 0x00, // 0080: "o\0" + padding
0x00, 0x00, 0x00, 0x03, // 0084: FDT_PROP (string list)
0x00, 0x00, 0x00, 0x07, // 0088: prop len (7)
0x00, 0x00, 0x00, 0x11, // 008C: prop nameoff (0x11)
b'h', b'i', 0x00, b'b', // 0090: prop value ("hi", "bye")
b'y', b'e', 0x00, 0x00, // 0094: "ye\0" + padding
0x00, 0x00, 0x00, 0x03, // 0098: FDT_PROP (u32 array)
0x00, 0x00, 0x00, 0x08, // 009C: prop len (8)
0x00, 0x00, 0x00, 0x18, // 00A0: prop nameoff (0x18)
0x12, 0x34, 0x56, 0x78, // 00A4: prop value 0
0xAA, 0xBB, 0xCC, 0xDD, // 00A8: prop value 1
0x00, 0x00, 0x00, 0x03, // 00AC: FDT_PROP (u64 array)
0x00, 0x00, 0x00, 0x08, // 00B0: prop len (8)
0x00, 0x00, 0x00, 0x1f, // 00B4: prop nameoff (0x1F)
0x12, 0x34, 0x56, 0x78, // 00B8: prop u64 value 0 high
0x87, 0x65, 0x43, 0x21, // 00BC: prop u64 value 0 low
0x00, 0x00, 0x00, 0x02, // 00C0: FDT_END_NODE
0x00, 0x00, 0x00, 0x09, // 00C4: FDT_END
b'n', b'u', b'l', b'l', 0x00, // 00C8: strings + 0x00: "null""
b'u', b'3', b'2', 0x00, // 00CD: strings + 0x05: "u32"
b'u', b'6', b'4', 0x00, // 00D1: strings + 0x09: "u64"
b's', b't', b'r', 0x00, // 00D5: strings + 0x0D: "str"
b's', b't', b'r', b'l', b's', b't', 0x00, // 00D9: strings + 0x11: "strlst"
b'a', b'r', b'r', b'u', b'3', b'2', 0x00, // 00E0: strings + 0x18: "arru32"
b'a', b'r', b'r', b'u', b'6', b'4', 0x00, // 00E7: strings + 0x1F: "arru64"
]
);
}
#[test]
fn nested_nodes() {
let mut fdt = FdtWriter::new(&[]);
let root_node = fdt.begin_node("").unwrap();
fdt.property_u32("abc", 0x13579024).unwrap();
let nested_node = fdt.begin_node("nested").unwrap();
fdt.property_u32("def", 0x12121212).unwrap();
fdt.end_node(nested_node).unwrap();
fdt.end_node(root_node).unwrap();
assert_eq!(
fdt.finish(0x80).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 = FdtWriter::new(&[]);
let root_node = fdt.begin_node("").unwrap();
fdt.property_u32("abc", 0x13579024).unwrap();
let nested_node = fdt.begin_node("nested").unwrap();
fdt.property_u32("def", 0x12121212).unwrap();
fdt.property_u32("abc", 0x12121212).unwrap(); // This should reuse the "abc" string.
fdt.end_node(nested_node).unwrap();
fdt.end_node(root_node).unwrap();
assert_eq!(
fdt.finish(0x90).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, 0x04, // 0064: prop nameoff (0x04)
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, 0x00, // 0074: prop nameoff (0x00 - reuse)
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 = FdtWriter::new(&[]);
fdt.begin_node("abc\0def")
.expect_err("node name with embedded NUL");
}
#[test]
fn invalid_prop_name_nul() {
let mut fdt = FdtWriter::new(&[]);
fdt.property_u32("abc\0def", 0)
.expect_err("property name with embedded NUL");
}
#[test]
fn invalid_prop_string_value_nul() {
let mut fdt = FdtWriter::new(&[]);
fdt.property_string("mystr", "abc\0def")
.expect_err("string property value with embedded NUL");
}
#[test]
fn invalid_prop_string_list_value_nul() {
let mut fdt = FdtWriter::new(&[]);
let strs = vec!["test".into(), "abc\0def".into()];
fdt.property_string_list("mystr", strs)
.expect_err("stringlist property value with embedded NUL");
}
#[test]
fn invalid_prop_after_end_node() {
let mut fdt = FdtWriter::new(&[]);
let _root_node = fdt.begin_node("").unwrap();
fdt.property_u32("ok_prop", 1234).unwrap();
let nested_node = fdt.begin_node("mynode").unwrap();
fdt.property_u32("ok_nested_prop", 5678).unwrap();
fdt.end_node(nested_node).unwrap();
fdt.property_u32("bad_prop_after_end_node", 1357)
.expect_err("property after end_node");
}
#[test]
fn invalid_end_node_out_of_order() {
let mut fdt = FdtWriter::new(&[]);
let root_node = fdt.begin_node("").unwrap();
fdt.property_u32("ok_prop", 1234).unwrap();
let _nested_node = fdt.begin_node("mynode").unwrap();
fdt.end_node(root_node)
.expect_err("end node while nested node is open");
}
#[test]
fn invalid_finish_while_node_open() {
let mut fdt = FdtWriter::new(&[]);
let _root_node = fdt.begin_node("").unwrap();
fdt.property_u32("ok_prop", 1234).unwrap();
let _nested_node = fdt.begin_node("mynode").unwrap();
fdt.property_u32("ok_nested_prop", 5678).unwrap();
fdt.finish(0x100)
.expect_err("finish without ending all nodes");
}
}