#![no_std]
#![warn(missing_docs)]
#[cfg(test)]
extern crate std;
#[cfg(test)]
mod tests;
pub mod cell_collector;
pub mod helpers;
pub mod nodes;
pub mod parsing;
mod pretty_print;
pub mod properties;
mod util;
use helpers::FallibleParser;
use nodes::{
root::{AllCompatibleIter, AllNodesIter, AllNodesWithNameIter, Root},
Node,
};
use parsing::{
aligned::AlignedParser, unaligned::UnalignedParser, NoPanic, Panic, ParseError, Parser, ParserWithMode,
StringsBlock, StructsBlock,
};
mod sealed {
pub trait Sealed {}
}
#[derive(Debug, Clone, Copy)]
pub enum FdtError {
BadMagic,
BadPtr,
SliceTooSmall,
ParseError(ParseError),
MissingPHandleNode(u32),
MissingParent,
MissingRequiredNode(&'static str),
MissingRequiredProperty(&'static str),
InvalidPropertyValue,
InvalidNodeName,
CollectCellsError,
}
impl From<ParseError> for FdtError {
fn from(value: ParseError) -> Self {
Self::ParseError(value)
}
}
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::SliceTooSmall => write!(f, "provided slice is too small"),
FdtError::ParseError(e) => core::fmt::Display::fmt(e, f),
FdtError::MissingPHandleNode(value) => {
write!(f, "a node containing the `phandle` property value of `{value}` was not found")
}
FdtError::MissingParent => write!(f, "node parent is not present but needed to parse a property"),
FdtError::MissingRequiredNode(name) => {
write!(f, "FDT is missing a required node `{}`", name)
}
FdtError::MissingRequiredProperty(name) => {
write!(f, "FDT node is missing a required property `{}`", name)
}
FdtError::InvalidPropertyValue => write!(f, "FDT property value is invalid"),
FdtError::InvalidNodeName => {
write!(f, "FDT node contained invalid characters or did not match the expected format")
}
FdtError::CollectCellsError => {
write!(f, "overflow occurred while collecting `#<specifier>-cells` size values into the desired type")
}
}
}
}
#[derive(Clone, Copy)]
pub struct Fdt<'a, P: ParserWithMode<'a>> {
structs: StructsBlock<'a, P::Granularity>,
strings: StringsBlock<'a>,
header: FdtHeader,
}
impl<'a, P: ParserWithMode<'a>> core::fmt::Debug for Fdt<'a, P> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Fdt").finish_non_exhaustive()
}
}
impl<'a, P: ParserWithMode<'a>> core::fmt::Display for Fdt<'a, P> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let mut parser: (P::Parser, NoPanic) = <_>::new(self.structs.0, self.strings, self.structs);
let Ok(node) = parser.parse_root() else {
return Err(core::fmt::Error);
};
pretty_print::print_fdt(f, Root { node })
}
}
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct FdtHeader {
pub magic: u32,
pub total_size: u32,
pub structs_offset: u32,
pub strings_offset: u32,
pub memory_reserve_map_offset: u32,
pub version: u32,
pub last_compatible_version: u32,
pub boot_cpuid: u32,
pub strings_size: u32,
pub structs_size: u32,
}
impl FdtHeader {
fn valid_magic(&self) -> bool {
self.magic == 0xd00dfeed
}
}
impl<'a> Fdt<'a, (UnalignedParser<'a>, Panic)> {
pub fn new_unaligned(data: &'a [u8]) -> Result<Self, FdtError> {
let mut parser = UnalignedParser::new(data, StringsBlock(&[]), StructsBlock(&[]));
let header = parser.parse_header()?;
let strings_end = (header.strings_offset + header.strings_size) as usize;
let structs_end = (header.structs_offset + header.structs_size) as usize;
if data.len() < strings_end || data.len() < structs_end {
return Err(FdtError::SliceTooSmall);
}
let strings = StringsBlock(&data[header.strings_offset as usize..][..header.strings_size as usize]);
let structs = StructsBlock(&data[header.structs_offset as usize..][..header.structs_size as usize]);
if !header.valid_magic() {
return Err(FdtError::BadMagic);
} else if data.len() < header.total_size as usize {
return Err(FdtError::SliceTooSmall);
}
Ok(Self { header, structs, strings })
}
pub unsafe fn from_ptr_unaligned(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 = usize::try_from(
UnalignedParser::new(tmp_header, StringsBlock(&[]), StructsBlock(&[])).parse_header()?.total_size,
)
.map_err(|_| ParseError::NumericConversionError)?;
Self::new_unaligned(core::slice::from_raw_parts(ptr, real_size))
}
}
impl<'a> Fdt<'a, (AlignedParser<'a>, Panic)> {
pub fn new(data: &'a [u32]) -> Result<Self, FdtError> {
let mut parser = AlignedParser::new(data, StringsBlock(&[]), StructsBlock(&[]));
let header = parser.parse_header()?;
let strings_end = (header.strings_offset + header.strings_size) as usize / 4;
let structs_end = (header.structs_offset + header.structs_size) as usize / 4;
if data.len() < strings_end || data.len() < structs_end {
return Err(FdtError::SliceTooSmall);
}
let strings_start = header.strings_offset as usize;
let strings_end = strings_start + header.strings_size as usize;
let strings = StringsBlock(
util::cast_slice(data)
.get(strings_start..strings_end)
.ok_or(FdtError::ParseError(ParseError::UnexpectedEndOfData))?,
);
let structs_start = header.structs_offset as usize / 4;
let structs_end = structs_start + (header.structs_size as usize / 4);
let structs = StructsBlock(
data.get(structs_start..structs_end).ok_or(FdtError::ParseError(ParseError::UnexpectedEndOfData))?,
);
if !header.valid_magic() {
return Err(FdtError::BadMagic);
} else if data.len() < (header.total_size / 4) as usize {
return Err(FdtError::ParseError(ParseError::UnexpectedEndOfData));
}
Ok(Self { header, strings, structs })
}
pub unsafe fn from_ptr(ptr: *const u32) -> 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 = usize::try_from(
AlignedParser::new(tmp_header, StringsBlock(&[]), StructsBlock(&[])).parse_header()?.total_size,
)
.map_err(|_| ParseError::NumericConversionError)?;
Self::new(core::slice::from_raw_parts(ptr, real_size))
}
}
impl<'a> Fdt<'a, (UnalignedParser<'a>, NoPanic)> {
pub fn new_unaligned_fallible(data: &'a [u8]) -> Result<Self, FdtError> {
let Fdt { header, strings, structs } = Fdt::new_unaligned(data)?;
Ok(Self { header, strings, structs })
}
pub unsafe fn from_ptr_unaligned_fallible(ptr: *const u8) -> Result<Self, FdtError> {
let Fdt { header, strings, structs } = Fdt::from_ptr_unaligned(ptr)?;
Ok(Self { header, strings, structs })
}
}
impl<'a> Fdt<'a, (AlignedParser<'a>, NoPanic)> {
pub fn new_fallible(data: &'a [u32]) -> Result<Self, FdtError> {
let Fdt { header, strings, structs } = Fdt::new(data)?;
Ok(Self { header, strings, structs })
}
pub unsafe fn from_ptr_fallible(ptr: *const u32) -> Result<Self, FdtError> {
let Fdt { header, strings, structs } = Fdt::from_ptr(ptr)?;
Ok(Self { header, strings, structs })
}
}
impl<'a, P: ParserWithMode<'a>> Fdt<'a, P> {
#[inline(always)]
fn fallible_root(&self) -> Result<Root<'a, FallibleParser<'a, P>>, FdtError> {
let mut parser = FallibleParser::<'a, P>::new(self.structs.0, self.strings, self.structs);
Ok(Root { node: parser.parse_root()? })
}
pub fn root(&self) -> P::Output<Root<'a, P>> {
let mut parser = P::new(self.structs.0, self.strings, self.structs);
P::to_output(parser.parse_root().map(|node| Root { node: node.fallible() }))
}
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 = core::ffi::CStr::from_bytes_until_nul(block).ok()?;
block = &block[cstr.to_bytes().len() + 1..];
cstr.to_str().ok()
})
}
#[track_caller]
pub fn find_all_nodes_with_name<'b>(&self, name: &'b str) -> P::Output<AllNodesWithNameIter<'a, 'b, P>> {
P::to_output(self.fallible_root().and_then(|root| {
root.find_all_nodes_with_name(name).map(|i| AllNodesWithNameIter { iter: i.iter, name: i.name })
}))
}
#[track_caller]
pub fn find_node_by_name(&self, name: &str) -> P::Output<Option<Node<'a, P>>> {
P::to_output(self.fallible_root().and_then(|root| Ok(root.find_node_by_name(name)?.map(|n| n.alt()))))
}
#[track_caller]
pub fn find_node(&self, path: &str) -> P::Output<Option<Node<'a, P>>> {
P::to_output(self.fallible_root().and_then(|root| Ok(root.find_node(path)?.map(|n| n.alt()))))
}
#[track_caller]
pub fn all_compatible<'b>(&self, with: &'b [&str]) -> P::Output<AllCompatibleIter<'a, 'b, P>> {
P::to_output(
self.fallible_root()
.and_then(|root| root.all_compatible(with).map(|i| AllCompatibleIter { iter: i.iter, with: i.with })),
)
}
#[track_caller]
pub fn all_nodes(&self) -> P::Output<AllNodesIter<'a, P>> {
P::to_output(self.fallible_root().and_then(|root| {
root.all_nodes().map(|i| AllNodesIter {
parser: P::new(i.parser.data(), i.parser.strings(), i.parser.structs()),
parent_index: i.parent_index,
parents: i.parents,
})
}))
}
pub fn total_size(&self) -> usize {
self.header.total_size as usize
}
pub fn header(&self) -> &FdtHeader {
&self.header
}
pub fn strings_block(&self) -> &'a [u8] {
self.strings.0
}
pub fn structs_block(&self) -> &'a [P::Granularity] {
self.structs.0
}
}