mod node;
mod property;
use core::ffi::CStr;
use core::fmt::{self, Debug, Display, Formatter};
use core::mem::offset_of;
use core::ptr;
use zerocopy::byteorder::big_endian;
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned};
pub use self::node::FdtNode;
pub use self::property::FdtProperty;
use crate::Node;
use crate::error::{FdtErrorKind, FdtParseError};
use crate::memreserve::MemoryReservation;
const FDT_VERSION: u32 = 17;
pub(crate) const FDT_TAGSIZE: usize = size_of::<u32>();
pub(crate) const FDT_MAGIC: u32 = 0xd00d_feed;
pub(crate) const FDT_BEGIN_NODE: u32 = 0x1;
pub(crate) const FDT_END_NODE: u32 = 0x2;
pub(crate) const FDT_END: u32 = 0x9;
pub(crate) const FDT_PROP: u32 = 0x3;
pub(crate) const FDT_NOP: u32 = 0x4;
#[repr(C, packed)]
#[derive(Debug, Copy, Clone, FromBytes, IntoBytes, Unaligned, Immutable, KnownLayout)]
pub(crate) struct FdtHeader {
pub(crate) magic: big_endian::U32,
pub(crate) totalsize: big_endian::U32,
pub(crate) off_dt_struct: big_endian::U32,
pub(crate) off_dt_strings: big_endian::U32,
pub(crate) off_mem_rsvmap: big_endian::U32,
pub(crate) version: big_endian::U32,
pub(crate) last_comp_version: big_endian::U32,
pub(crate) boot_cpuid_phys: big_endian::U32,
pub(crate) size_dt_strings: big_endian::U32,
pub(crate) size_dt_struct: big_endian::U32,
}
impl FdtHeader {
pub(crate) fn magic(&self) -> u32 {
self.magic.get()
}
pub(crate) fn totalsize(&self) -> u32 {
self.totalsize.get()
}
pub(crate) fn off_dt_struct(&self) -> u32 {
self.off_dt_struct.get()
}
pub(crate) fn off_dt_strings(&self) -> u32 {
self.off_dt_strings.get()
}
pub(crate) fn off_mem_rsvmap(&self) -> u32 {
self.off_mem_rsvmap.get()
}
pub(crate) fn version(&self) -> u32 {
self.version.get()
}
pub(crate) fn last_comp_version(&self) -> u32 {
self.last_comp_version.get()
}
pub(crate) fn boot_cpuid_phys(&self) -> u32 {
self.boot_cpuid_phys.get()
}
pub(crate) fn size_dt_strings(&self) -> u32 {
self.size_dt_strings.get()
}
pub(crate) fn size_dt_struct(&self) -> u32 {
self.size_dt_struct.get()
}
}
#[derive(Clone, Copy)]
pub struct Fdt<'a> {
pub(crate) data: &'a [u8],
}
impl Debug for Fdt<'_> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(
f,
"Fdt {{ data: {} bytes at {:?} }}",
self.data.len(),
self.data.as_ptr()
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum FdtToken {
BeginNode,
EndNode,
Prop,
Nop,
End,
}
impl TryFrom<u32> for FdtToken {
type Error = u32;
fn try_from(value: u32) -> Result<Self, Self::Error> {
match value {
FDT_BEGIN_NODE => Ok(FdtToken::BeginNode),
FDT_END_NODE => Ok(FdtToken::EndNode),
FDT_PROP => Ok(FdtToken::Prop),
FDT_NOP => Ok(FdtToken::Nop),
FDT_END => Ok(FdtToken::End),
_ => Err(value),
}
}
}
impl<'a> Fdt<'a> {
pub fn new(data: &'a [u8]) -> Result<Self, FdtParseError> {
let fdt = Self::new_unchecked(data);
fdt.validate()?;
Ok(fdt)
}
#[must_use]
pub fn new_unchecked(data: &'a [u8]) -> Self {
Self { data }
}
#[expect(
unsafe_code,
reason = "Having a methods that reads a Device Tree from a raw pointer is useful for \
embedded applications, where the binary only gets a pointer to DT from the firmware or \
a bootloader. The user must ensure it trusts the data."
)]
#[must_use]
pub unsafe fn from_raw_unchecked(data: *const u8) -> Self {
let header = unsafe { ptr::read_unaligned(data.cast::<FdtHeader>()) };
let size = header.totalsize();
let slice = unsafe { core::slice::from_raw_parts(data, size as usize) };
Self::new_unchecked(slice)
}
#[expect(
unsafe_code,
reason = "Having a methods that reads a Device Tree from a raw pointer is useful for \
embedded applications, where the binary only gets a pointer to DT from the firmware or \
a bootloader. The user must ensure it trusts the data."
)]
pub unsafe fn from_raw(data: *const u8) -> Result<Self, FdtParseError> {
let fdt = unsafe { Self::from_raw_unchecked(data) };
fdt.validate()?;
Ok(fdt)
}
fn validate(self) -> Result<(), FdtParseError> {
if self.data.len() < size_of::<FdtHeader>() {
return Err(FdtParseError::new(FdtErrorKind::InvalidLength, 0));
}
let header = self.header();
if header.magic() != FDT_MAGIC {
return Err(FdtParseError::new(
FdtErrorKind::InvalidMagic,
offset_of!(FdtHeader, magic),
));
}
if !(header.last_comp_version()..=header.version()).contains(&FDT_VERSION) {
return Err(FdtParseError::new(
FdtErrorKind::UnsupportedVersion(header.version()),
offset_of!(FdtHeader, version),
));
}
if header.totalsize() as usize != self.data.len() {
return Err(FdtParseError::new(
FdtErrorKind::InvalidLength,
offset_of!(FdtHeader, totalsize),
));
}
self.validate_header()?;
self.validate_mem_reservations()?;
let offset = header.off_dt_struct() as usize;
if self.read_token(offset)? != FdtToken::BeginNode {
return Err(FdtParseError::new(
FdtErrorKind::BadToken(FDT_BEGIN_NODE),
offset,
));
}
let end_offset = self.traverse_node(offset, true)?;
if self.read_token(end_offset)? != FdtToken::End {
return Err(FdtParseError::new(
FdtErrorKind::BadToken(FDT_END),
end_offset,
));
}
Ok(())
}
fn validate_mem_reservations(self) -> Result<(), FdtParseError> {
let header = self.header();
let mut offset = header.off_mem_rsvmap() as usize;
loop {
if offset >= header.off_dt_struct() as usize {
return Err(FdtParseError::new(
FdtErrorKind::MemReserveNotTerminated,
offset,
));
}
let (reservation, _) = MemoryReservation::ref_from_prefix(&self.data[offset..])
.map_err(|_| FdtParseError::new(FdtErrorKind::MemReserveInvalid, offset))?;
offset += size_of::<MemoryReservation>();
if *reservation == MemoryReservation::TERMINATOR {
break;
}
}
Ok(())
}
fn traverse_node(self, offset: usize, check_strings: bool) -> Result<usize, FdtParseError> {
let mut offset = offset;
let mut depth = 0;
loop {
let token = self.read_token(offset)?;
match token {
FdtToken::BeginNode => {
depth += 1;
offset += FDT_TAGSIZE;
offset = self.find_string_end(offset)?;
offset = Self::align_tag_offset(offset);
}
FdtToken::EndNode => {
if depth == 0 {
return Err(FdtParseError::new(
FdtErrorKind::BadToken(FDT_END_NODE),
offset,
));
}
depth -= 1;
offset += FDT_TAGSIZE;
if depth == 0 {
return Ok(offset);
}
}
FdtToken::Prop => {
offset += FDT_TAGSIZE;
offset = self.next_property_offset(offset, check_strings)?;
}
FdtToken::Nop => offset += FDT_TAGSIZE,
FdtToken::End => {
return Err(FdtParseError::new(FdtErrorKind::BadToken(FDT_END), offset));
}
}
}
}
fn validate_header(self) -> Result<(), FdtParseError> {
let header = self.header();
let data = &self.data;
let off_mem_rsvmap = header.off_mem_rsvmap() as usize;
let off_dt_struct = header.off_dt_struct() as usize;
let off_dt_strings = header.off_dt_strings() as usize;
if off_mem_rsvmap > off_dt_struct {
return Err(FdtParseError::new(
FdtErrorKind::InvalidHeader("dt_struct not after memrsvmap"),
offset_of!(FdtHeader, off_mem_rsvmap),
));
}
if off_dt_struct > data.len() {
return Err(FdtParseError::new(
FdtErrorKind::InvalidHeader("struct offset out of bounds"),
offset_of!(FdtHeader, off_dt_struct),
));
}
if off_dt_strings > data.len() {
return Err(FdtParseError::new(
FdtErrorKind::InvalidHeader("strings offset out of bounds"),
offset_of!(FdtHeader, off_dt_strings),
));
}
let size_dt_struct = header.size_dt_struct() as usize;
let size_dt_strings = header.size_dt_strings() as usize;
if off_dt_struct.saturating_add(size_dt_struct) > data.len() {
return Err(FdtParseError::new(
FdtErrorKind::InvalidHeader("struct block overflows"),
offset_of!(FdtHeader, size_dt_struct),
));
}
if off_dt_strings.saturating_add(size_dt_strings) > data.len() {
return Err(FdtParseError::new(
FdtErrorKind::InvalidHeader("strings block overflows"),
offset_of!(FdtHeader, size_dt_strings),
));
}
if off_dt_struct.saturating_add(size_dt_struct) > off_dt_strings {
return Err(FdtParseError::new(
FdtErrorKind::InvalidHeader("strings block not after struct block"),
offset_of!(FdtHeader, off_dt_strings),
));
}
Ok(())
}
pub(crate) fn header(self) -> &'a FdtHeader {
let (header, _remaining_bytes) = FdtHeader::ref_from_prefix(self.data)
.expect("new() checks if the slice is at least as big as the header");
header
}
#[must_use]
pub fn data(self) -> &'a [u8] {
self.data
}
#[must_use]
pub fn version(self) -> u32 {
self.header().version()
}
#[must_use]
pub fn last_comp_version(self) -> u32 {
self.header().last_comp_version()
}
#[must_use]
pub fn boot_cpuid_phys(self) -> u32 {
self.header().boot_cpuid_phys()
}
pub fn memory_reservations(self) -> impl Iterator<Item = MemoryReservation> + 'a {
let mut offset = self.header().off_mem_rsvmap() as usize;
core::iter::from_fn(move || {
let (reservation, _) = MemoryReservation::ref_from_prefix(&self.data[offset..])
.expect("Fdt should be valid");
offset += size_of::<MemoryReservation>();
if *reservation == MemoryReservation::TERMINATOR {
return None;
}
Some(*reservation)
})
}
#[must_use]
pub fn root(self) -> FdtNode<'a> {
let offset = self.header().off_dt_struct() as usize;
FdtNode::new(self, offset)
}
#[must_use]
pub fn find_node(self, path: &str) -> Option<FdtNode<'a>> {
if !path.starts_with('/') {
return None;
}
let mut current_node = self.root();
if path == "/" {
return Some(current_node);
}
for component in path.split('/').filter(|s| !s.is_empty()) {
match current_node.child(component) {
Some(node) => current_node = node,
None => return None,
}
}
Some(current_node)
}
pub(crate) fn read_token(self, offset: usize) -> Result<FdtToken, FdtParseError> {
let val = big_endian::U32::ref_from_prefix(&self.data[offset..])
.map(|(val, _)| val.get())
.map_err(|_e| FdtParseError::new(FdtErrorKind::InvalidLength, offset))?;
FdtToken::try_from(val).map_err(|t| FdtParseError::new(FdtErrorKind::BadToken(t), offset))
}
pub(crate) fn string(self, string_block_offset: usize) -> Result<&'a str, FdtParseError> {
let header = self.header();
let str_block_start = header.off_dt_strings() as usize;
let str_block_size = header.size_dt_strings() as usize;
let str_block_end = str_block_start + str_block_size;
let str_start = str_block_start + string_block_offset;
if str_start >= str_block_end {
return Err(FdtParseError::new(FdtErrorKind::InvalidLength, str_start));
}
self.string_at_offset(str_start, Some(str_block_end))
}
pub(crate) fn string_at_offset(
self,
offset: usize,
end: Option<usize>,
) -> Result<&'a str, FdtParseError> {
let slice = match end {
Some(end) => self.data.get(offset..end),
None => self.data.get(offset..),
};
let slice = slice.ok_or(FdtParseError::new(FdtErrorKind::InvalidOffset, offset))?;
match CStr::from_bytes_until_nul(slice).map(|val| val.to_str()) {
Ok(Ok(val)) => Ok(val),
_ => Err(FdtParseError::new(FdtErrorKind::InvalidString, offset)),
}
}
pub(crate) fn find_string_end(self, start: usize) -> Result<usize, FdtParseError> {
let mut offset = start;
loop {
match self.data.get(offset) {
Some(0) => return Ok(offset + 1),
Some(_) => {}
None => return Err(FdtParseError::new(FdtErrorKind::InvalidString, start)),
}
offset += 1;
}
}
pub(crate) fn next_sibling_offset(self, offset: usize) -> Result<usize, FdtParseError> {
self.traverse_node(offset, false)
}
pub(crate) fn next_property_offset(
self,
offset: usize,
check_name: bool,
) -> Result<usize, FdtParseError> {
let len = big_endian::U32::ref_from_prefix(&self.data[offset..])
.map(|(val, _)| val.get())
.map_err(|_e| FdtParseError::new(FdtErrorKind::InvalidLength, offset))?
as usize;
let nameoff = big_endian::U32::ref_from_prefix(&self.data[offset + FDT_TAGSIZE..])
.map(|(val, _)| val.get())
.map_err(|_e| FdtParseError::new(FdtErrorKind::InvalidLength, offset))?
as usize;
if check_name {
self.string(nameoff)?;
}
let prop_offset = offset + 2 * FDT_TAGSIZE;
let end_offset = prop_offset + len;
if end_offset > self.data.len() {
return Err(FdtParseError::new(FdtErrorKind::InvalidLength, prop_offset));
}
Ok(Self::align_tag_offset(end_offset))
}
pub(crate) fn align_tag_offset(offset: usize) -> usize {
offset.next_multiple_of(FDT_TAGSIZE)
}
}
impl Display for Fdt<'_> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
writeln!(f, "/dts-v1/;")?;
for reservation in self.memory_reservations() {
writeln!(
f,
"/memreserve/ {:#x} {:#x};",
reservation.address(),
reservation.size()
)?;
}
writeln!(f)?;
let root = self.root();
root.fmt_recursive(f, 0)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::FdtErrorKind;
const FDT_HEADER_OK: &[u8] = &[
0xd0, 0x0d, 0xfe, 0xed, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x09, ];
#[test]
fn header_is_parsed_correctly() {
let fdt = Fdt::new(FDT_HEADER_OK).unwrap();
let header = fdt.header();
assert_eq!(header.totalsize(), 72);
assert_eq!(header.off_dt_struct(), 56);
assert_eq!(header.off_dt_strings(), 72);
assert_eq!(header.off_mem_rsvmap(), 40);
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(), 16);
}
#[test]
fn invalid_magic() {
let mut header = FDT_HEADER_OK.to_vec();
header[0] = 0x00;
let result = Fdt::new(&header);
assert!(matches!(result, Err(e) if matches!(e.kind, FdtErrorKind::InvalidMagic)));
}
#[test]
fn invalid_length() {
let header = &FDT_HEADER_OK[..10];
let result = Fdt::new(header);
assert!(matches!(result, Err(e) if matches!(e.kind, FdtErrorKind::InvalidLength)));
}
#[test]
fn unsupported_version() {
let mut header = FDT_HEADER_OK.to_vec();
header[23] = 0x10;
let result = Fdt::new(&header);
assert!(matches!(result, Err(e) if matches!(e.kind, FdtErrorKind::UnsupportedVersion(16))));
}
}