Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion src/fdt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@

use crate::error::{FdtError, FdtErrorKind};
mod node;
mod property;
use core::ffi::CStr;
use core::mem::offset_of;
use core::ptr;

pub use node::FdtNode;
pub use property::FdtProperty;
use zerocopy::byteorder::big_endian;
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned};

Expand Down Expand Up @@ -388,7 +390,22 @@ impl<'a> Fdt<'a> {
FdtToken::try_from(val).map_err(|t| FdtError::new(FdtErrorKind::BadToken(t), offset))
}

/// Return a NUL-terminated string from a given offset.
/// Returns a string from the string block.
pub(crate) fn string(&self, string_block_offset: usize) -> Result<&'a str, FdtError> {
let header = self.header();
let str_block_start = header.off_dt_strings() as usize;
let str_block_size = header.size_dt_strings() as usize;
let str_block_end = str_block_start + str_block_size;
let str_start = str_block_start + string_block_offset;

if str_start >= str_block_end {
return Err(FdtError::new(FdtErrorKind::InvalidLength, str_start));
}

self.string_at_offset(str_start, Some(str_block_end))
}

/// Returns a NUL-terminated string from a given offset.
pub(crate) fn string_at_offset(
&self,
offset: usize,
Expand Down
52 changes: 52 additions & 0 deletions src/fdt/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

use super::{FDT_TAGSIZE, Fdt, FdtToken};
use crate::error::FdtError;
use crate::fdt::property::{FdtPropIter, FdtProperty};

/// A node in a flattened device tree.
#[derive(Debug, Clone, Copy)]
Expand Down Expand Up @@ -46,6 +47,57 @@ impl<'a> FdtNode<'a> {
self.fdt.string_at_offset(name_offset, None)
}

/// Returns a property by its name.
///
/// # Performance
///
/// This method iterates through all properties of the node.
///
/// # Examples
///
/// ```
/// # use dtoolkit::fdt::Fdt;
/// # let dtb = include_bytes!("../../tests/dtb/test_props.dtb");
/// let fdt = Fdt::new(dtb).unwrap();
/// let node = fdt.find_node("/test-props").unwrap().unwrap();
/// let prop = node.property("u32-prop").unwrap().unwrap();
/// assert_eq!(prop.name(), "u32-prop");
/// ```
///
/// # Errors
///
/// Returns an error if a property's name or value cannot be read.
pub fn property(&self, name: &str) -> Result<Option<FdtProperty<'a>>, FdtError> {
for property in self.properties() {
let property = property?;
if property.name() == name {
return Ok(Some(property));
}
}
Ok(None)
}

/// Returns an iterator over the properties of this node.
///
/// # Examples
///
/// ```
/// # use dtoolkit::fdt::Fdt;
/// # let dtb = include_bytes!("../../tests/dtb/test_props.dtb");
/// let fdt = Fdt::new(dtb).unwrap();
/// let node = fdt.find_node("/test-props").unwrap().unwrap();
/// let mut props = node.properties();
/// assert_eq!(props.next().unwrap().unwrap().name(), "u32-prop");
/// assert_eq!(props.next().unwrap().unwrap().name(), "u64-prop");
/// assert_eq!(props.next().unwrap().unwrap().name(), "str-prop");
/// ```
pub fn properties(&self) -> impl Iterator<Item = Result<FdtProperty<'a>, FdtError>> + use<'a> {
FdtPropIter::Start {
fdt: self.fdt,
offset: self.offset,
}
}

/// Returns a child node by its name.
///
/// # Performance
Expand Down
230 changes: 230 additions & 0 deletions src/fdt/property.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! A read-only API for inspecting a device tree property.

use core::ffi::CStr;

use zerocopy::{FromBytes, big_endian};

use super::{FDT_TAGSIZE, Fdt, FdtToken};
use crate::error::{FdtError, FdtErrorKind};

/// A property of a device tree node.
#[derive(Debug, PartialEq)]
pub struct FdtProperty<'a> {
name: &'a str,
value: &'a [u8],
value_offset: usize,
}

impl<'a> FdtProperty<'a> {
/// Returns the name of this property.
#[must_use]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this (and value) must_use? It would be pointless to call it and ignore the return value, sure, but it's not like you're failing to check for an error condition or something like that.

Copy link
Collaborator Author

@m4tx m4tx Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, the usage of #[must_use] used to be very inconsistent in the Rust ecosystem and has been a topic of discussion for quite a long time. The reference, however, doesn't specify when exactly should the attribute be used.

Nowadays, it seems like the Rust standard library follows something in the lines of "if the function has no side-effects, it should warn when the result is discarded". A while ago over 800 attributes have been added, and you can see that methods such as str::len, or f32::is_nan (or honestly, many many others) do have the attribute. Hence I'm following this to be consistent with the standard library.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, fair enough.

pub fn name(&self) -> &'a str {
self.name
}

/// Returns the value of this property.
#[must_use]
pub fn value(&self) -> &'a [u8] {
self.value
}

/// Returns the value of this property as a `u32`.
///
/// # Errors
///
/// Returns an [`FdtErrorKind::InvalidLength`] if the property's value is
/// not 4 bytes long.
///
/// # Examples
///
/// ```
/// # use dtoolkit::fdt::Fdt;
/// # let dtb = include_bytes!("../../tests/dtb/test_props.dtb");
/// let fdt = Fdt::new(dtb).unwrap();
/// let node = fdt.find_node("/test-props").unwrap().unwrap();
/// let prop = node.property("u32-prop").unwrap().unwrap();
/// assert_eq!(prop.as_u32().unwrap(), 0x12345678);
/// ```
pub fn as_u32(&self) -> Result<u32, FdtError> {
big_endian::U32::ref_from_bytes(self.value)
.map(|val| val.get())
.map_err(|_e| FdtError::new(FdtErrorKind::InvalidLength, self.value_offset))
}

/// Returns the value of this property as a `u64`.
///
/// # Errors
///
/// Returns an [`FdtErrorKind::InvalidLength`] if the property's value is
/// not 8 bytes long.
///
/// # Examples
///
/// ```
/// # use dtoolkit::fdt::Fdt;
/// # let dtb = include_bytes!("../../tests/dtb/test_props.dtb");
/// let fdt = Fdt::new(dtb).unwrap();
/// let node = fdt.find_node("/test-props").unwrap().unwrap();
/// let prop = node.property("u64-prop").unwrap().unwrap();
/// assert_eq!(prop.as_u64().unwrap(), 0x1122334455667788);
/// ```
pub fn as_u64(&self) -> Result<u64, FdtError> {
big_endian::U64::ref_from_bytes(self.value)
.map(|val| val.get())
.map_err(|_e| FdtError::new(FdtErrorKind::InvalidLength, self.value_offset))
}

/// Returns the value of this property as a string.
///
/// # Errors
///
/// Returns an [`FdtErrorKind::InvalidString`] if the property's value is
/// not a null-terminated string or contains invalid UTF-8.
///
/// # Examples
///
/// ```
/// # use dtoolkit::fdt::Fdt;
/// # let dtb = include_bytes!("../../tests/dtb/test_props.dtb");
/// let fdt = Fdt::new(dtb).unwrap();
/// let node = fdt.find_node("/test-props").unwrap().unwrap();
/// let prop = node.property("str-prop").unwrap().unwrap();
/// assert_eq!(prop.as_str().unwrap(), "hello world");
/// ```
pub fn as_str(&self) -> Result<&'a str, FdtError> {
let cstr = CStr::from_bytes_with_nul(self.value)
.map_err(|_| FdtError::new(FdtErrorKind::InvalidString, self.value_offset))?;
cstr.to_str()
.map_err(|_| FdtError::new(FdtErrorKind::InvalidString, self.value_offset))
}

/// Returns an iterator over the strings in this property.
///
/// # Examples
///
/// ```
/// # use dtoolkit::fdt::Fdt;
/// # let dtb = include_bytes!("../../tests/dtb/test_props.dtb");
/// let fdt = Fdt::new(dtb).unwrap();
/// let node = fdt.find_node("/test-props").unwrap().unwrap();
/// let prop = node.property("str-list-prop").unwrap().unwrap();
/// let mut str_list = prop.as_str_list();
/// assert_eq!(str_list.next(), Some("first"));
/// assert_eq!(str_list.next(), Some("second"));
/// assert_eq!(str_list.next(), Some("third"));
/// assert_eq!(str_list.next(), None);
/// ```
pub fn as_str_list(&self) -> impl Iterator<Item = &'a str> {
FdtStringListIterator { value: self.value }
}
}

/// An iterator over the properties of a device tree node.
pub(crate) enum FdtPropIter<'a> {
Start { fdt: &'a Fdt<'a>, offset: usize },
Running { fdt: &'a Fdt<'a>, offset: usize },
Error,
}

impl<'a> Iterator for FdtPropIter<'a> {
type Item = Result<FdtProperty<'a>, FdtError>;

fn next(&mut self) -> Option<Self::Item> {
match self {
Self::Start { fdt, offset } => {
let mut offset = *offset;
offset += FDT_TAGSIZE; // Skip FDT_BEGIN_NODE
offset = match fdt.find_string_end(offset) {
Ok(offset) => offset,
Err(e) => {
*self = Self::Error;
return Some(Err(e));
}
};
offset = Fdt::align_tag_offset(offset);
*self = Self::Running { fdt, offset };
self.next()
}
Self::Running { fdt, offset } => match Self::try_next(fdt, offset) {
Some(Ok(val)) => Some(Ok(val)),
Some(Err(e)) => {
*self = Self::Error;
Some(Err(e))
}
None => None,
},
Self::Error => None,
}
}
}

impl<'a> FdtPropIter<'a> {
fn try_next(fdt: &'a Fdt<'a>, offset: &mut usize) -> Option<Result<FdtProperty<'a>, FdtError>> {
loop {
let token = match fdt.read_token(*offset) {
Ok(token) => token,
Err(e) => return Some(Err(e)),
};
match token {
FdtToken::Prop => {
let len = match big_endian::U32::ref_from_prefix(
&fdt.data[*offset + FDT_TAGSIZE..],
) {
Ok((val, _)) => val.get() as usize,
Err(_) => {
return Some(Err(FdtError::new(FdtErrorKind::InvalidLength, *offset)));
}
};
let nameoff = match big_endian::U32::ref_from_prefix(
&fdt.data[*offset + 2 * FDT_TAGSIZE..],
) {
Ok((val, _)) => val.get() as usize,
Err(_) => {
return Some(Err(FdtError::new(FdtErrorKind::InvalidLength, *offset)));
}
};
let prop_offset = *offset + 3 * FDT_TAGSIZE;
*offset = Fdt::align_tag_offset(prop_offset + len);
let name = match fdt.string(nameoff) {
Ok(name) => name,
Err(e) => return Some(Err(e)),
};
let value = fdt.data.get(prop_offset..prop_offset + len)?;
return Some(Ok(FdtProperty {
name,
value,
value_offset: prop_offset,
}));
}
FdtToken::Nop => *offset += FDT_TAGSIZE,
_ => return None,
}
}
}
}

struct FdtStringListIterator<'a> {
value: &'a [u8],
}

impl<'a> Iterator for FdtStringListIterator<'a> {
type Item = &'a str;

fn next(&mut self) -> Option<Self::Item> {
if self.value.is_empty() {
return None;
}
let cstr = CStr::from_bytes_until_nul(self.value).ok()?;
let s = cstr.to_str().ok()?;
self.value = &self.value[s.len() + 1..];
Some(s)
}
}
Binary file added tests/dtb/test_children_nested.dtb
Binary file not shown.
Binary file added tests/dtb/test_props.dtb
Binary file not shown.
15 changes: 15 additions & 0 deletions tests/dts/test_children_nested.dts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/dts-v1/;

/ {
child1 {
prop1 = <0x02>;

child2 {
prop2 = <0x02>;
};
};

child3 {
prop3 = <0x02>;
};
};
13 changes: 13 additions & 0 deletions tests/dts/test_props.dts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/dts-v1/;

/ {
#address-cells = <2>;
#size-cells = <2>;

test-props {
u32-prop = <0x12345678>;
u64-prop = <0x11223344 0x55667788>;
str-prop = "hello world";
str-list-prop = "first", "second", "third";
};
};
Loading