#![no_std]
#[cfg(test)]
mod tests;
pub mod node;
mod parsing;
pub mod standard_nodes;
#[cfg(feature = "pretty-printing")]
mod pretty_print;
use node::MemoryReservation;
use parsing::{BigEndianU32, CStr, FdtData};
use standard_nodes::{Aliases, Chosen, Cpu, Memory, MemoryRegion, Root};
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum FdtError {
BadMagic,
BadPtr,
BufferTooSmall,
}
impl core::fmt::Display for FdtError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
FdtError::BadMagic => write!(f, "bad FDT magic value"),
FdtError::BadPtr => write!(f, "an invalid pointer was passed"),
FdtError::BufferTooSmall => {
write!(f, "the given buffer was too small to contain a FDT header")
}
}
}
}
#[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, FdtError> {
let mut stream = FdtData::new(data);
let header = FdtHeader::from_bytes(&mut stream).ok_or(FdtError::BufferTooSmall)?;
if !header.valid_magic() {
return Err(FdtError::BadMagic);
} else if data.len() < header.totalsize.get() as usize {
return Err(FdtError::BufferTooSmall);
}
Ok(Self { data, header })
}
pub unsafe fn from_ptr(ptr: *const u8) -> Result<Self, FdtError> {
if ptr.is_null() {
return Err(FdtError::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 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) -> Chosen<'_, 'a> {
node::find_node(&mut FdtData::new(self.structs_block()), "/chosen", self, None)
.map(|node| Chosen { node })
.expect("/chosen is required")
}
pub fn cpus(&self) -> impl Iterator<Item = Cpu<'_, 'a>> {
let parent = self.find_node("/cpus").expect("/cpus is a required node");
parent
.children()
.filter(|c| c.name.split('@').next().unwrap() == "cpu")
.map(move |cpu| Cpu { parent, node: cpu })
}
pub fn memory(&self) -> Memory<'_, 'a> {
Memory { node: self.find_node("/memory").expect("requires memory node") }
}
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) -> Root<'_, 'a> {
Root { node: self.find_node("/").expect("/ is a required node") }
}
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("") => Some(self.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 block = self.strings_block();
core::iter::from_fn(move || {
if block.is_empty() {
return None;
}
let cstr = CStr::new(block)?;
block = &block[cstr.len() + 1..];
cstr.as_str()
})
}
pub fn total_size(&self) -> usize {
self.header.totalsize.get() as usize
}
fn cstr_at_offset(&self, offset: usize) -> CStr<'a> {
CStr::new(&self.strings_block()[offset..]).expect("no null terminating string on C str?")
}
fn str_at_offset(&self, offset: usize) -> &'a str {
self.cstr_at_offset(offset).as_str().expect("not utf-8 cstr")
}
fn strings_block(&self) -> &'a [u8] {
&self.data[self.header.strings_range()]
}
fn structs_block(&self) -> &'a [u8] {
&self.data[self.header.struct_range()]
}
}