#![no_std]
use core::ffi::CStr;
#[cfg(test)]
mod tests;
mod error;
pub mod node;
mod parsing;
pub mod standard_nodes;
#[cfg(feature = "pretty-printing")]
mod pretty_print;
pub use error::*;
use node::MemoryReservation;
use parsing::{BigEndianU32, FdtData};
use standard_nodes::{Aliases, Chosen, Cpu, Memory, Root};
#[derive(Clone, Copy)]
pub struct Fdt<'a> {
data: &'a [u8],
header: FdtHeader,
}
impl core::fmt::Debug for Fdt<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
#[cfg(feature = "pretty-printing")]
pretty_print::print_node(f, self.root()?.node, 0)?;
#[cfg(not(feature = "pretty-printing"))]
f.debug_struct("Fdt").finish_non_exhaustive()?;
Ok(())
}
}
#[derive(Debug, Clone, Copy)]
#[repr(C)]
struct FdtHeader {
magic: BigEndianU32,
totalsize: BigEndianU32,
off_dt_struct: BigEndianU32,
off_dt_strings: BigEndianU32,
off_mem_rsvmap: BigEndianU32,
version: BigEndianU32,
last_comp_version: BigEndianU32,
boot_cpuid_phys: BigEndianU32,
size_dt_strings: BigEndianU32,
size_dt_struct: BigEndianU32,
}
impl FdtHeader {
fn valid_magic(&self) -> bool {
self.magic.get() == 0xd00dfeed
}
fn struct_range(&self) -> core::ops::Range<usize> {
let start = self.off_dt_struct.get() as usize;
let end = start + self.size_dt_struct.get() as usize;
start..end
}
fn strings_range(&self) -> core::ops::Range<usize> {
let start = self.off_dt_strings.get() as usize;
let end = start + self.size_dt_strings.get() as usize;
start..end
}
fn from_bytes(bytes: &mut FdtData<'_>) -> Option<Self> {
Some(Self {
magic: bytes.u32()?,
totalsize: bytes.u32()?,
off_dt_struct: bytes.u32()?,
off_dt_strings: bytes.u32()?,
off_mem_rsvmap: bytes.u32()?,
version: bytes.u32()?,
last_comp_version: bytes.u32()?,
boot_cpuid_phys: bytes.u32()?,
size_dt_strings: bytes.u32()?,
size_dt_struct: bytes.u32()?,
})
}
}
impl<'a> Fdt<'a> {
pub fn new(data: &'a [u8]) -> Result<Self> {
let mut stream = FdtData::new(data);
let header = FdtHeader::from_bytes(&mut stream).ok_or(Error::BufferTooSmall)?;
if !header.valid_magic() {
Err(Error::BadMagic)
} else if data.len() < header.totalsize.get() as usize {
Err(Error::BufferTooSmall)
} else {
Ok(Self { data, header })
}
}
pub unsafe fn from_ptr(ptr: *const u8) -> Result<Self> {
if ptr.is_null() {
return Err(Error::BadPtr);
}
let tmp_header = core::slice::from_raw_parts(ptr, core::mem::size_of::<FdtHeader>());
let real_size =
FdtHeader::from_bytes(&mut FdtData::new(tmp_header)).unwrap().totalsize.get() as usize;
Self::new(core::slice::from_raw_parts(ptr, real_size))
}
pub fn raw_data(&self) -> &'a [u8] {
self.data
}
pub fn aliases(&self) -> Option<Aliases<'_, 'a>> {
Some(Aliases {
node: node::find_node(&mut FdtData::new(self.structs_block()), "/aliases", self, None)?,
header: self,
})
}
pub fn chosen(&self) -> Result<Chosen<'_, 'a>> {
node::find_node(&mut FdtData::new(self.structs_block()), "/chosen", self, None)
.map(|node| Chosen { node })
.ok_or(Error::MissingChosen)
}
pub fn cpus(&self) -> impl Iterator<Item = Cpu<'_, 'a>> {
let mut cpus = self.find_node("/cpus").map(|parent| {
parent.children().filter_map(move |c| {
if c.name.split('@').next().unwrap() == "cpu" {
Some(Cpu { parent, node: c })
} else {
None
}
})
});
core::iter::from_fn(move || match cpus.as_mut() {
Some(cpu) => cpu.next(),
None => None,
})
}
pub fn memory(&self) -> Result<Memory<'_, 'a>> {
Ok(Memory { node: self.find_node("/memory").ok_or(Error::MissingMemory)? })
}
pub fn memory_reservations(&self) -> impl Iterator<Item = MemoryReservation> + 'a {
let mut stream = FdtData::new(&self.data[self.header.off_mem_rsvmap.get() as usize..]);
let mut done = false;
core::iter::from_fn(move || {
if stream.is_empty() || done {
return None;
}
let res = MemoryReservation::from_bytes(&mut stream)?;
if res.address() as usize == 0 && res.size() == 0 {
done = true;
return None;
}
Some(res)
})
}
pub fn root(&self) -> Result<Root<'_, 'a>> {
Ok(Root { node: self.find_node("/").ok_or(Error::MissingRoot)? })
}
pub fn find_node(&self, path: &str) -> Option<node::FdtNode<'_, 'a>> {
let node = node::find_node(&mut FdtData::new(self.structs_block()), path, self, None);
node.or_else(|| self.aliases()?.resolve_node(path))
}
pub fn find_compatible(&self, with: &[&str]) -> Option<node::FdtNode<'_, 'a>> {
self.all_nodes().find(|n| {
n.compatible().and_then(|compats| compats.all().find(|c| with.contains(c))).is_some()
})
}
pub fn find_phandle(&self, phandle: u32) -> Option<node::FdtNode<'_, 'a>> {
self.all_nodes().find(|n| {
n.properties()
.find(|p| p.name == "phandle")
.and_then(|p| Some(BigEndianU32::from_bytes(p.value)?.get() == phandle))
.unwrap_or(false)
})
}
pub fn find_all_nodes(&self, path: &'a str) -> impl Iterator<Item = node::FdtNode<'_, 'a>> {
let mut done = false;
let only_root = path == "/";
let valid_path = path.chars().fold(0, |acc, c| acc + if c == '/' { 1 } else { 0 }) >= 1;
let mut path_split = path.rsplitn(2, '/');
let child_name = path_split.next().unwrap();
let parent = match path_split.next() {
Some("") => self.root().ok().map(|root| root.node),
Some(s) => node::find_node(&mut FdtData::new(self.structs_block()), s, self, None),
None => None,
};
let (parent, bad_parent) = match parent {
Some(parent) => (parent, false),
None => (self.find_node("/").unwrap(), true),
};
let mut child_iter = parent.children();
core::iter::from_fn(move || {
if done || !valid_path || bad_parent {
return None;
}
if only_root {
done = true;
return self.find_node("/");
}
let mut ret = None;
#[allow(clippy::while_let_on_iterator)]
while let Some(child) = child_iter.next() {
if child.name.split('@').next()? == child_name {
ret = Some(child);
break;
}
}
ret
})
}
pub fn all_nodes(&self) -> impl Iterator<Item = node::FdtNode<'_, 'a>> {
node::all_nodes(self)
}
pub fn strings(&self) -> impl Iterator<Item = &'a str> {
let mut blocks = core::str::from_utf8(self.strings_block())
.ok()
.map(|s| s.trim_end_matches('\0').split('\0'));
core::iter::from_fn(move || match blocks.as_mut() {
Some(block) => block.next(),
None => None,
})
}
pub fn total_size(&self) -> usize {
self.header.totalsize.get() as usize
}
fn cstr_at_offset(&self, offset: usize) -> &'a CStr {
CStr::from_bytes_until_nul(self.strings_block().get(offset..).unwrap_or_default())
.unwrap_or_default()
}
fn str_at_offset(&self, offset: usize) -> &'a str {
self.cstr_at_offset(offset).to_str().unwrap_or_default()
}
fn strings_block(&self) -> &'a [u8] {
self.data.get(self.header.strings_range()).unwrap_or_default()
}
fn structs_block(&self) -> &'a [u8] {
self.data.get(self.header.struct_range()).unwrap_or_default()
}
}