#![no_std]
pub mod node;
mod parsing;
pub mod standard_nodes;
use node::FdtNode;
use parsing::{CStr, FdtData};
use standard_nodes::{Chosen, Cpu, Memory, Root};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FdtError {
BadMagic,
BufferTooSmall,
}
impl core::fmt::Display for FdtError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::BadMagic => f.write_str("bad FDT magic"),
Self::BufferTooSmall => f.write_str("buffer too small"),
}
}
}
#[derive(Debug, Clone, Copy)]
struct FdtHeader {
totalsize: u32,
off_dt_struct: u32,
off_dt_strings: u32,
off_mem_rsvmap: u32,
#[allow(dead_code)]
version: u32,
#[allow(dead_code)]
boot_cpuid_phys: u32,
size_dt_strings: u32,
size_dt_struct: u32,
}
impl FdtHeader {
#[moa_sec_macros::init]
fn from_bytes(bytes: &mut FdtData<'_>) -> Option<Self> {
let magic = bytes.u32()?;
if magic != 0xd00d_feed {
return None;
}
Some(Self {
totalsize: bytes.u32()?,
off_dt_struct: bytes.u32()?,
off_dt_strings: bytes.u32()?,
off_mem_rsvmap: bytes.u32()?,
version: bytes.u32()?,
boot_cpuid_phys: {
let _last_comp_version = bytes.u32()?;
bytes.u32()?
},
size_dt_strings: bytes.u32()?,
size_dt_struct: bytes.u32()?,
})
}
#[inline(always)]
fn struct_range(&self) -> core::ops::Range<usize> {
let start = self.off_dt_struct as usize;
start..start + self.size_dt_struct as usize
}
#[inline(always)]
fn strings_range(&self) -> core::ops::Range<usize> {
let start = self.off_dt_strings as usize;
start..start + self.size_dt_strings as usize
}
}
#[derive(Clone, Copy)]
pub struct Fdt<'a> {
data: &'a [u8],
header: FdtHeader,
}
impl<'a> Fdt<'a> {
#[moa_sec_macros::init]
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::BadMagic)?;
if data.len() < header.totalsize as usize {
return Err(FdtError::BufferTooSmall);
}
Ok(Self { data, header })
}
#[inline(always)]
pub fn total_size(&self) -> usize {
self.header.totalsize as usize
}
#[inline(always)]
pub fn root(&self) -> Root<'a> {
Root { node: self.find_node("/").expect("/ is required") }
}
#[inline(always)]
pub fn chosen(&self) -> Chosen<'a> {
Chosen {
node: node::find_node(
&mut FdtData::new(self.structs_block()),
"/chosen",
self.strings_block(),
None,
)
.expect("/chosen is required"),
}
}
#[inline(always)]
pub fn cpus(&self) -> impl Iterator<Item = Cpu<'a>> {
let parent = self.find_node("/cpus").expect("/cpus is required");
parent
.children()
.filter(|c| c.name.split('@').next() == Some("cpu"))
.map(move |cpu| Cpu { parent, node: cpu })
}
#[inline(always)]
pub fn memory(&self) -> Memory<'a> {
Memory { structs: self.structs_block(), strings: self.strings_block() }
}
#[inline(always)]
pub fn find_node(&self, path: &str) -> Option<FdtNode<'a>> {
node::find_node(&mut FdtData::new(self.structs_block()), path, self.strings_block(), None)
}
#[inline(always)]
pub fn find_compatible(&self, with: &str) -> Option<FdtNode<'a>> {
self.all_nodes().find(|n| n.compatible().is_some_and(|c| c.contains(with)))
}
#[inline(always)]
pub fn find_compatible_any(&self, with: &[&str]) -> Option<FdtNode<'a>> {
self.all_nodes().find(|n| {
n.compatible().is_some_and(|compats| compats.all().any(|c| with.contains(&c)))
})
}
#[inline(always)]
pub fn all_nodes(&self) -> impl Iterator<Item = FdtNode<'a>> {
node::all_nodes(self.structs_block(), self.strings_block())
}
#[inline(always)]
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()
})
}
#[inline(always)]
pub fn raw_data(&self) -> &'a [u8] {
self.data
}
#[inline(always)]
pub fn memreserve_offset(&self) -> usize {
self.header.off_mem_rsvmap as usize
}
#[inline(always)]
pub fn memreserve_entries(&self) -> MemreserveIter<'a> {
let off = self.header.off_mem_rsvmap as usize;
MemreserveIter { stream: FdtData::new(self.data.get(off..).unwrap_or(&[])) }
}
#[inline(always)]
pub fn initrd(&self) -> Option<(usize, usize)> {
let chosen = self.find_node("/chosen")?;
let start = chosen.property("linux,initrd-start")?.as_usize()?;
let end = chosen.property("linux,initrd-end")?.as_usize()?;
if end > start { Some((start, end - start)) } else { None }
}
#[inline(always)]
pub fn reserved_memory_node(&self) -> Option<FdtNode<'a>> {
self.find_node("/reserved-memory")
}
#[inline(always)]
fn structs_block(&self) -> &'a [u8] {
self.data.get(self.header.struct_range()).unwrap_or(&[])
}
#[inline(always)]
fn strings_block(&self) -> &'a [u8] {
self.data.get(self.header.strings_range()).unwrap_or(&[])
}
}
pub struct MemreserveIter<'a> {
stream: FdtData<'a>,
}
impl Iterator for MemreserveIter<'_> {
type Item = node::MemoryRegion;
#[moa_sec_macros::init]
#[allow(clippy::cast_possible_truncation)]
fn next(&mut self) -> Option<Self::Item> {
let addr = self.stream.u64()? as usize;
let size = self.stream.u64()? as usize;
if addr == 0 && size == 0 {
return None;
}
Some(node::MemoryRegion { address: addr, size: Some(size) })
}
}