Skip to content

Commit bdf1e1d

Browse files
authored
feat: add support for reading properties in FDT (#4)
1 parent a02f0ce commit bdf1e1d

8 files changed

Lines changed: 405 additions & 1 deletion

File tree

src/fdt/mod.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@
1717
1818
use crate::error::{FdtError, FdtErrorKind};
1919
mod node;
20+
mod property;
2021
use core::ffi::CStr;
2122
use core::mem::offset_of;
2223
use core::ptr;
2324

2425
pub use node::FdtNode;
26+
pub use property::FdtProperty;
2527
use zerocopy::byteorder::big_endian;
2628
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned};
2729

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

391-
/// Return a NUL-terminated string from a given offset.
393+
/// Returns a string from the string block.
394+
pub(crate) fn string(&self, string_block_offset: usize) -> Result<&'a str, FdtError> {
395+
let header = self.header();
396+
let str_block_start = header.off_dt_strings() as usize;
397+
let str_block_size = header.size_dt_strings() as usize;
398+
let str_block_end = str_block_start + str_block_size;
399+
let str_start = str_block_start + string_block_offset;
400+
401+
if str_start >= str_block_end {
402+
return Err(FdtError::new(FdtErrorKind::InvalidLength, str_start));
403+
}
404+
405+
self.string_at_offset(str_start, Some(str_block_end))
406+
}
407+
408+
/// Returns a NUL-terminated string from a given offset.
392409
pub(crate) fn string_at_offset(
393410
&self,
394411
offset: usize,

src/fdt/node.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
1111
use super::{FDT_TAGSIZE, Fdt, FdtToken};
1212
use crate::error::FdtError;
13+
use crate::fdt::property::{FdtPropIter, FdtProperty};
1314

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

50+
/// Returns a property by its name.
51+
///
52+
/// # Performance
53+
///
54+
/// This method iterates through all properties of the node.
55+
///
56+
/// # Examples
57+
///
58+
/// ```
59+
/// # use dtoolkit::fdt::Fdt;
60+
/// # let dtb = include_bytes!("../../tests/dtb/test_props.dtb");
61+
/// let fdt = Fdt::new(dtb).unwrap();
62+
/// let node = fdt.find_node("/test-props").unwrap().unwrap();
63+
/// let prop = node.property("u32-prop").unwrap().unwrap();
64+
/// assert_eq!(prop.name(), "u32-prop");
65+
/// ```
66+
///
67+
/// # Errors
68+
///
69+
/// Returns an error if a property's name or value cannot be read.
70+
pub fn property(&self, name: &str) -> Result<Option<FdtProperty<'a>>, FdtError> {
71+
for property in self.properties() {
72+
let property = property?;
73+
if property.name() == name {
74+
return Ok(Some(property));
75+
}
76+
}
77+
Ok(None)
78+
}
79+
80+
/// Returns an iterator over the properties of this node.
81+
///
82+
/// # Examples
83+
///
84+
/// ```
85+
/// # use dtoolkit::fdt::Fdt;
86+
/// # let dtb = include_bytes!("../../tests/dtb/test_props.dtb");
87+
/// let fdt = Fdt::new(dtb).unwrap();
88+
/// let node = fdt.find_node("/test-props").unwrap().unwrap();
89+
/// let mut props = node.properties();
90+
/// assert_eq!(props.next().unwrap().unwrap().name(), "u32-prop");
91+
/// assert_eq!(props.next().unwrap().unwrap().name(), "u64-prop");
92+
/// assert_eq!(props.next().unwrap().unwrap().name(), "str-prop");
93+
/// ```
94+
pub fn properties(&self) -> impl Iterator<Item = Result<FdtProperty<'a>, FdtError>> + use<'a> {
95+
FdtPropIter::Start {
96+
fdt: self.fdt,
97+
offset: self.offset,
98+
}
99+
}
100+
49101
/// Returns a child node by its name.
50102
///
51103
/// # Performance

src/fdt/property.rs

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4+
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5+
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6+
// option. This file may not be copied, modified, or distributed
7+
// except according to those terms.
8+
9+
//! A read-only API for inspecting a device tree property.
10+
11+
use core::ffi::CStr;
12+
13+
use zerocopy::{FromBytes, big_endian};
14+
15+
use super::{FDT_TAGSIZE, Fdt, FdtToken};
16+
use crate::error::{FdtError, FdtErrorKind};
17+
18+
/// A property of a device tree node.
19+
#[derive(Debug, PartialEq)]
20+
pub struct FdtProperty<'a> {
21+
name: &'a str,
22+
value: &'a [u8],
23+
value_offset: usize,
24+
}
25+
26+
impl<'a> FdtProperty<'a> {
27+
/// Returns the name of this property.
28+
#[must_use]
29+
pub fn name(&self) -> &'a str {
30+
self.name
31+
}
32+
33+
/// Returns the value of this property.
34+
#[must_use]
35+
pub fn value(&self) -> &'a [u8] {
36+
self.value
37+
}
38+
39+
/// Returns the value of this property as a `u32`.
40+
///
41+
/// # Errors
42+
///
43+
/// Returns an [`FdtErrorKind::InvalidLength`] if the property's value is
44+
/// not 4 bytes long.
45+
///
46+
/// # Examples
47+
///
48+
/// ```
49+
/// # use dtoolkit::fdt::Fdt;
50+
/// # let dtb = include_bytes!("../../tests/dtb/test_props.dtb");
51+
/// let fdt = Fdt::new(dtb).unwrap();
52+
/// let node = fdt.find_node("/test-props").unwrap().unwrap();
53+
/// let prop = node.property("u32-prop").unwrap().unwrap();
54+
/// assert_eq!(prop.as_u32().unwrap(), 0x12345678);
55+
/// ```
56+
pub fn as_u32(&self) -> Result<u32, FdtError> {
57+
big_endian::U32::ref_from_bytes(self.value)
58+
.map(|val| val.get())
59+
.map_err(|_e| FdtError::new(FdtErrorKind::InvalidLength, self.value_offset))
60+
}
61+
62+
/// Returns the value of this property as a `u64`.
63+
///
64+
/// # Errors
65+
///
66+
/// Returns an [`FdtErrorKind::InvalidLength`] if the property's value is
67+
/// not 8 bytes long.
68+
///
69+
/// # Examples
70+
///
71+
/// ```
72+
/// # use dtoolkit::fdt::Fdt;
73+
/// # let dtb = include_bytes!("../../tests/dtb/test_props.dtb");
74+
/// let fdt = Fdt::new(dtb).unwrap();
75+
/// let node = fdt.find_node("/test-props").unwrap().unwrap();
76+
/// let prop = node.property("u64-prop").unwrap().unwrap();
77+
/// assert_eq!(prop.as_u64().unwrap(), 0x1122334455667788);
78+
/// ```
79+
pub fn as_u64(&self) -> Result<u64, FdtError> {
80+
big_endian::U64::ref_from_bytes(self.value)
81+
.map(|val| val.get())
82+
.map_err(|_e| FdtError::new(FdtErrorKind::InvalidLength, self.value_offset))
83+
}
84+
85+
/// Returns the value of this property as a string.
86+
///
87+
/// # Errors
88+
///
89+
/// Returns an [`FdtErrorKind::InvalidString`] if the property's value is
90+
/// not a null-terminated string or contains invalid UTF-8.
91+
///
92+
/// # Examples
93+
///
94+
/// ```
95+
/// # use dtoolkit::fdt::Fdt;
96+
/// # let dtb = include_bytes!("../../tests/dtb/test_props.dtb");
97+
/// let fdt = Fdt::new(dtb).unwrap();
98+
/// let node = fdt.find_node("/test-props").unwrap().unwrap();
99+
/// let prop = node.property("str-prop").unwrap().unwrap();
100+
/// assert_eq!(prop.as_str().unwrap(), "hello world");
101+
/// ```
102+
pub fn as_str(&self) -> Result<&'a str, FdtError> {
103+
let cstr = CStr::from_bytes_with_nul(self.value)
104+
.map_err(|_| FdtError::new(FdtErrorKind::InvalidString, self.value_offset))?;
105+
cstr.to_str()
106+
.map_err(|_| FdtError::new(FdtErrorKind::InvalidString, self.value_offset))
107+
}
108+
109+
/// Returns an iterator over the strings in this property.
110+
///
111+
/// # Examples
112+
///
113+
/// ```
114+
/// # use dtoolkit::fdt::Fdt;
115+
/// # let dtb = include_bytes!("../../tests/dtb/test_props.dtb");
116+
/// let fdt = Fdt::new(dtb).unwrap();
117+
/// let node = fdt.find_node("/test-props").unwrap().unwrap();
118+
/// let prop = node.property("str-list-prop").unwrap().unwrap();
119+
/// let mut str_list = prop.as_str_list();
120+
/// assert_eq!(str_list.next(), Some("first"));
121+
/// assert_eq!(str_list.next(), Some("second"));
122+
/// assert_eq!(str_list.next(), Some("third"));
123+
/// assert_eq!(str_list.next(), None);
124+
/// ```
125+
pub fn as_str_list(&self) -> impl Iterator<Item = &'a str> {
126+
FdtStringListIterator { value: self.value }
127+
}
128+
}
129+
130+
/// An iterator over the properties of a device tree node.
131+
pub(crate) enum FdtPropIter<'a> {
132+
Start { fdt: &'a Fdt<'a>, offset: usize },
133+
Running { fdt: &'a Fdt<'a>, offset: usize },
134+
Error,
135+
}
136+
137+
impl<'a> Iterator for FdtPropIter<'a> {
138+
type Item = Result<FdtProperty<'a>, FdtError>;
139+
140+
fn next(&mut self) -> Option<Self::Item> {
141+
match self {
142+
Self::Start { fdt, offset } => {
143+
let mut offset = *offset;
144+
offset += FDT_TAGSIZE; // Skip FDT_BEGIN_NODE
145+
offset = match fdt.find_string_end(offset) {
146+
Ok(offset) => offset,
147+
Err(e) => {
148+
*self = Self::Error;
149+
return Some(Err(e));
150+
}
151+
};
152+
offset = Fdt::align_tag_offset(offset);
153+
*self = Self::Running { fdt, offset };
154+
self.next()
155+
}
156+
Self::Running { fdt, offset } => match Self::try_next(fdt, offset) {
157+
Some(Ok(val)) => Some(Ok(val)),
158+
Some(Err(e)) => {
159+
*self = Self::Error;
160+
Some(Err(e))
161+
}
162+
None => None,
163+
},
164+
Self::Error => None,
165+
}
166+
}
167+
}
168+
169+
impl<'a> FdtPropIter<'a> {
170+
fn try_next(fdt: &'a Fdt<'a>, offset: &mut usize) -> Option<Result<FdtProperty<'a>, FdtError>> {
171+
loop {
172+
let token = match fdt.read_token(*offset) {
173+
Ok(token) => token,
174+
Err(e) => return Some(Err(e)),
175+
};
176+
match token {
177+
FdtToken::Prop => {
178+
let len = match big_endian::U32::ref_from_prefix(
179+
&fdt.data[*offset + FDT_TAGSIZE..],
180+
) {
181+
Ok((val, _)) => val.get() as usize,
182+
Err(_) => {
183+
return Some(Err(FdtError::new(FdtErrorKind::InvalidLength, *offset)));
184+
}
185+
};
186+
let nameoff = match big_endian::U32::ref_from_prefix(
187+
&fdt.data[*offset + 2 * FDT_TAGSIZE..],
188+
) {
189+
Ok((val, _)) => val.get() as usize,
190+
Err(_) => {
191+
return Some(Err(FdtError::new(FdtErrorKind::InvalidLength, *offset)));
192+
}
193+
};
194+
let prop_offset = *offset + 3 * FDT_TAGSIZE;
195+
*offset = Fdt::align_tag_offset(prop_offset + len);
196+
let name = match fdt.string(nameoff) {
197+
Ok(name) => name,
198+
Err(e) => return Some(Err(e)),
199+
};
200+
let value = fdt.data.get(prop_offset..prop_offset + len)?;
201+
return Some(Ok(FdtProperty {
202+
name,
203+
value,
204+
value_offset: prop_offset,
205+
}));
206+
}
207+
FdtToken::Nop => *offset += FDT_TAGSIZE,
208+
_ => return None,
209+
}
210+
}
211+
}
212+
}
213+
214+
struct FdtStringListIterator<'a> {
215+
value: &'a [u8],
216+
}
217+
218+
impl<'a> Iterator for FdtStringListIterator<'a> {
219+
type Item = &'a str;
220+
221+
fn next(&mut self) -> Option<Self::Item> {
222+
if self.value.is_empty() {
223+
return None;
224+
}
225+
let cstr = CStr::from_bytes_until_nul(self.value).ok()?;
226+
let s = cstr.to_str().ok()?;
227+
self.value = &self.value[s.len() + 1..];
228+
Some(s)
229+
}
230+
}

tests/dtb/test_children_nested.dtb

164 Bytes
Binary file not shown.

tests/dtb/test_props.dtb

284 Bytes
Binary file not shown.

tests/dts/test_children_nested.dts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/dts-v1/;
2+
3+
/ {
4+
child1 {
5+
prop1 = <0x02>;
6+
7+
child2 {
8+
prop2 = <0x02>;
9+
};
10+
};
11+
12+
child3 {
13+
prop3 = <0x02>;
14+
};
15+
};

tests/dts/test_props.dts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/dts-v1/;
2+
3+
/ {
4+
#address-cells = <2>;
5+
#size-cells = <2>;
6+
7+
test-props {
8+
u32-prop = <0x12345678>;
9+
u64-prop = <0x11223344 0x55667788>;
10+
str-prop = "hello world";
11+
str-list-prop = "first", "second", "third";
12+
};
13+
};

0 commit comments

Comments
 (0)