Skip to content

Commit 4be464c

Browse files
committed
Add support for reading nodes in FDT
1 parent 6d01dad commit 4be464c

7 files changed

Lines changed: 465 additions & 2 deletions

File tree

src/fdt/mod.rs

Lines changed: 202 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,25 @@
1515
//!
1616
//! [Flattened Device Tree (FDT)]: https://devicetree-specification.readthedocs.io/en/latest/chapter5-flattened-format.html
1717
18+
use crate::error::{FdtError, FdtErrorKind};
19+
mod node;
20+
use core::ffi::CStr;
1821
use core::mem::offset_of;
1922
use core::ptr;
2023

24+
pub use node::FdtNode;
2125
use zerocopy::byteorder::big_endian;
2226
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned};
2327

24-
use crate::error::{FdtError, FdtErrorKind};
25-
2628
/// Version of the FDT specification supported by this library.
2729
const FDT_VERSION: u32 = 17;
30+
pub(crate) const FDT_TAGSIZE: usize = size_of::<u32>();
2831
pub(crate) const FDT_MAGIC: u32 = 0xd00d_feed;
32+
pub(crate) const FDT_BEGIN_NODE: u32 = 0x1;
33+
pub(crate) const FDT_END_NODE: u32 = 0x2;
34+
pub(crate) const FDT_END: u32 = 0x9;
35+
pub(crate) const FDT_PROP: u32 = 0x3;
36+
pub(crate) const FDT_NOP: u32 = 0x4;
2937

3038
#[repr(C, packed)]
3139
#[derive(Debug, Copy, Clone, FromBytes, IntoBytes, Unaligned, Immutable, KnownLayout)]
@@ -100,6 +108,31 @@ pub struct Fdt<'a> {
100108
pub(crate) data: &'a [u8],
101109
}
102110

111+
/// A token in the device tree structure.
112+
#[derive(Debug, PartialEq, Eq)]
113+
pub(crate) enum FdtToken {
114+
BeginNode,
115+
EndNode,
116+
Prop,
117+
Nop,
118+
End,
119+
}
120+
121+
impl TryFrom<u32> for FdtToken {
122+
type Error = u32;
123+
124+
fn try_from(value: u32) -> Result<Self, Self::Error> {
125+
match value {
126+
FDT_BEGIN_NODE => Ok(FdtToken::BeginNode),
127+
FDT_END_NODE => Ok(FdtToken::EndNode),
128+
FDT_PROP => Ok(FdtToken::Prop),
129+
FDT_NOP => Ok(FdtToken::Nop),
130+
FDT_END => Ok(FdtToken::End),
131+
_ => Err(value),
132+
}
133+
}
134+
}
135+
103136
impl<'a> Fdt<'a> {
104137
/// Creates a new `Fdt` from the given byte slice.
105138
///
@@ -276,6 +309,173 @@ impl<'a> Fdt<'a> {
276309
pub fn boot_cpuid_phys(&self) -> u32 {
277310
self.header().boot_cpuid_phys()
278311
}
312+
313+
/// Returns the root node of the device tree.
314+
///
315+
/// # Errors
316+
///
317+
/// Returns an [`FdtErrorKind::InvalidLength`] if the FDT structure is
318+
/// truncated or an [`FdtErrorKind::BadToken`] if the first token is not
319+
/// `FDT_BEGIN_NODE`.
320+
///
321+
/// # Examples
322+
///
323+
/// ```
324+
/// # use dtoolkit::fdt::Fdt;
325+
/// # let dtb = include_bytes!("../../tests/dtb/test.dtb");
326+
/// let fdt = Fdt::new(dtb).unwrap();
327+
/// let root = fdt.root().unwrap();
328+
/// assert_eq!(root.name().unwrap(), "");
329+
/// ```
330+
pub fn root(&self) -> Result<FdtNode<'_>, FdtError> {
331+
let offset = self.header().off_dt_struct() as usize;
332+
let token = self.read_token(offset)?;
333+
if token != FdtToken::BeginNode {
334+
return Err(FdtError::new(
335+
FdtErrorKind::BadToken(FDT_BEGIN_NODE),
336+
offset,
337+
));
338+
}
339+
Ok(FdtNode { fdt: self, offset })
340+
}
341+
342+
/// Finds a node by its path.
343+
///
344+
/// # Performance
345+
///
346+
/// This method traverses the device tree and its performance is linear in
347+
/// the number of nodes in the path. If you need to call this often,
348+
/// consider using
349+
/// [`DeviceTree::from_fdt`](crate::model::DeviceTree::from_fdt)
350+
/// first. [`DeviceTree`](crate::model::DeviceTree) stores the nodes in a
351+
/// hash map for constant-time lookup.
352+
///
353+
/// # Examples
354+
///
355+
/// ```
356+
/// # use dtoolkit::fdt::Fdt;
357+
/// # let dtb = include_bytes!("../../tests/dtb/test_traversal.dtb");
358+
/// let fdt = Fdt::new(dtb).unwrap();
359+
/// let node = fdt.find_node("/a/b/c").unwrap().unwrap();
360+
/// assert_eq!(node.name().unwrap(), "c");
361+
/// ```
362+
#[must_use]
363+
pub fn find_node(&self, path: &str) -> Option<Result<FdtNode<'_>, FdtError>> {
364+
if !path.starts_with('/') {
365+
return None;
366+
}
367+
let mut current_node = match self.root() {
368+
Ok(node) => node,
369+
Err(e) => return Some(Err(e)),
370+
};
371+
if path == "/" {
372+
return Some(Ok(current_node));
373+
}
374+
for component in path.split('/').filter(|s| !s.is_empty()) {
375+
match current_node.children().find(|child| {
376+
child
377+
.as_ref()
378+
.is_ok_and(|c| c.name().is_ok_and(|n| n == component))
379+
}) {
380+
Some(Ok(node)) => current_node = node,
381+
Some(Err(e)) => return Some(Err(e)),
382+
None => return None,
383+
}
384+
}
385+
Some(Ok(current_node))
386+
}
387+
388+
pub(crate) fn read_token(&self, offset: usize) -> Result<FdtToken, FdtError> {
389+
let val = big_endian::U32::ref_from_prefix(&self.data[offset..])
390+
.map(|(val, _)| val.get())
391+
.map_err(|_e| FdtError::new(FdtErrorKind::InvalidLength, offset))?;
392+
FdtToken::try_from(val).map_err(|t| FdtError::new(FdtErrorKind::BadToken(t), offset))
393+
}
394+
395+
/// Return a NUL-terminated string from a given offset.
396+
pub(crate) fn string_at_offset(
397+
&self,
398+
offset: usize,
399+
end: Option<usize>,
400+
) -> Result<&'a str, FdtError> {
401+
let slice = match end {
402+
Some(end) => self.data.get(offset..end),
403+
None => self.data.get(offset..),
404+
};
405+
let slice = slice.ok_or(FdtError::new(FdtErrorKind::InvalidOffset, offset))?;
406+
407+
match CStr::from_bytes_until_nul(slice).map(|val| val.to_str()) {
408+
Ok(Ok(val)) => Ok(val),
409+
_ => Err(FdtError::new(FdtErrorKind::InvalidString, offset)),
410+
}
411+
}
412+
413+
pub(crate) fn find_string_end(&self, start: usize) -> Result<usize, FdtError> {
414+
let mut offset = start;
415+
loop {
416+
match self.data.get(offset) {
417+
Some(0) => return Ok(offset + 1),
418+
Some(_) => {}
419+
None => return Err(FdtError::new(FdtErrorKind::InvalidString, start)),
420+
}
421+
offset += 1;
422+
}
423+
}
424+
425+
pub(crate) fn next_sibling_offset(&self, mut offset: usize) -> Result<usize, FdtError> {
426+
offset += FDT_TAGSIZE; // Skip FDT_BEGIN_NODE
427+
428+
// Skip node name
429+
offset = self.find_string_end(offset)?;
430+
offset = Self::align_tag_offset(offset);
431+
432+
// Skip properties
433+
loop {
434+
let token = self.read_token(offset)?;
435+
match token {
436+
FdtToken::Prop => {
437+
offset += FDT_TAGSIZE; // skip FDT_PROP
438+
offset = self.next_property_offset(offset)?;
439+
}
440+
FdtToken::Nop => offset += FDT_TAGSIZE,
441+
_ => break,
442+
}
443+
}
444+
445+
// Skip child nodes
446+
loop {
447+
let token = self.read_token(offset)?;
448+
match token {
449+
FdtToken::BeginNode => {
450+
offset = self.next_sibling_offset(offset)?;
451+
}
452+
FdtToken::EndNode => {
453+
offset += FDT_TAGSIZE;
454+
break;
455+
}
456+
FdtToken::Nop => offset += FDT_TAGSIZE,
457+
_ => {}
458+
}
459+
}
460+
461+
Ok(offset)
462+
}
463+
464+
pub(crate) fn next_property_offset(&self, mut offset: usize) -> Result<usize, FdtError> {
465+
let len = big_endian::U32::ref_from_prefix(&self.data[offset..])
466+
.map(|(val, _)| val.get())
467+
.map_err(|_e| FdtError::new(FdtErrorKind::InvalidLength, offset))?
468+
as usize;
469+
offset += FDT_TAGSIZE; // skip value length
470+
offset += FDT_TAGSIZE; // skip name offset
471+
offset += len; // skip property value
472+
473+
Ok(Self::align_tag_offset(offset))
474+
}
475+
476+
pub(crate) fn align_tag_offset(offset: usize) -> usize {
477+
offset.next_multiple_of(FDT_TAGSIZE)
478+
}
279479
}
280480

281481
#[cfg(test)]

0 commit comments

Comments
 (0)