Skip to content

Commit 2728d73

Browse files
authored
feat: add more types and methods for standard nodes (#25)
* Add methods to get standard /reserved-memory subnodes and properties. * Add methods to get standard /chosen node and properties. * Add more methods for standard properties on CPU nodes.
1 parent fc55931 commit 2728d73

8 files changed

Lines changed: 276 additions & 3 deletions

File tree

src/lib.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,19 @@ pub trait Property<'a>: Sized {
258258
.map_err(|_| PropertyError::InvalidLength)
259259
}
260260

261+
/// Returns the value of this property as a slide of 32-bit cells.
262+
///
263+
/// # Errors
264+
///
265+
/// Returns an error if the value of the property isn't a multiple of 4
266+
/// bytes long.
267+
fn as_cells(&self) -> Result<Cells<'a>, PropertyError> {
268+
Ok(Cells(
269+
<[big_endian::U32]>::ref_from_bytes(self.value())
270+
.map_err(|_| PropertyError::InvalidLength)?,
271+
))
272+
}
273+
261274
/// Returns the value of this property as a string.
262275
///
263276
/// # Errors

src/standard.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,16 @@
88

99
//! Standard nodes and properties.
1010
11+
mod chosen;
1112
mod cpus;
1213
mod memory;
1314
mod ranges;
1415
mod reg;
1516
mod status;
1617

18+
pub use self::chosen::Chosen;
1719
pub use self::cpus::{Cpu, Cpus};
18-
pub use self::memory::{InitialMappedArea, Memory};
20+
pub use self::memory::{InitialMappedArea, Memory, ReservedMemory};
1921
pub use self::ranges::Range;
2022
pub use self::reg::Reg;
2123
pub use self::status::Status;

src/standard/chosen.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Copyright 2026 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+
use core::fmt::{self, Display, Formatter};
10+
use core::ops::Deref;
11+
12+
use crate::error::PropertyError;
13+
use crate::fdt::{Fdt, FdtNode};
14+
use crate::{Node, Property};
15+
16+
impl<'a> Fdt<'a> {
17+
/// Returns the `/chosen` node, if it exists.
18+
#[must_use]
19+
pub fn chosen(self) -> Option<Chosen<FdtNode<'a>>> {
20+
let node = self.find_node("/chosen")?;
21+
Some(Chosen { node })
22+
}
23+
}
24+
25+
/// Typed wrapper for a `/chosen` node.
26+
#[derive(Clone, Copy, Debug)]
27+
pub struct Chosen<N> {
28+
node: N,
29+
}
30+
31+
impl<N> Deref for Chosen<N> {
32+
type Target = N;
33+
34+
fn deref(&self) -> &Self::Target {
35+
&self.node
36+
}
37+
}
38+
39+
impl<N: Display> Display for Chosen<N> {
40+
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
41+
self.node.fmt(f)
42+
}
43+
}
44+
45+
impl<'a, N: Node<'a>> Chosen<N> {
46+
/// Returns the value of the standard `bootargs` property.
47+
///
48+
/// # Errors
49+
///
50+
/// Returns an [`PropertyError::InvalidString`] if the property's value is
51+
/// not a null-terminated string or contains invalid UTF-8.
52+
pub fn bootargs(&self) -> Result<Option<&'a str>, PropertyError> {
53+
self.node
54+
.property("bootargs")
55+
.map(|value| value.as_str())
56+
.transpose()
57+
}
58+
59+
/// Returns the value of the standard `stdout-path` property.
60+
///
61+
/// # Errors
62+
///
63+
/// Returns an [`PropertyError::InvalidString`] if the property's value is
64+
/// not a null-terminated string or contains invalid UTF-8.
65+
pub fn stdout_path(&self) -> Result<Option<&'a str>, PropertyError> {
66+
self.node
67+
.property("stdout-path")
68+
.map(|value| value.as_str())
69+
.transpose()
70+
}
71+
72+
/// Returns the value of the standard `stdin-path` property.
73+
///
74+
/// # Errors
75+
///
76+
/// Returns an [`PropertyError::InvalidString`] if the property's value is
77+
/// not a null-terminated string or contains invalid UTF-8.
78+
pub fn stdin_path(&self) -> Result<Option<&'a str>, PropertyError> {
79+
self.node
80+
.property("stdin-path")
81+
.map(|value| value.as_str())
82+
.transpose()
83+
}
84+
}

src/standard/cpus.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
use core::fmt::{self, Display, Formatter};
1010
use core::ops::Deref;
1111

12-
use crate::error::StandardError;
12+
use crate::error::{PropertyError, StandardError};
1313
use crate::fdt::{Fdt, FdtNode};
14-
use crate::{Cells, Node};
14+
use crate::{Cells, Node, Property};
1515

1616
impl<'a> Fdt<'a> {
1717
/// Returns the `/cpus` node.
@@ -82,6 +82,27 @@ impl<N: Display> Display for Cpu<N> {
8282
}
8383
}
8484

85+
impl<'a, N: Node<'a>> Cpu<N> {
86+
/// Returns the value of the standard `enable-method` property if it is
87+
/// present.
88+
pub fn enable_method(&self) -> Option<impl Iterator<Item = &'a str>> {
89+
Some(self.node.property("enable-method")?.as_str_list())
90+
}
91+
92+
/// Returns the value of the standard `cpu-release-addr` property if it is
93+
/// present.
94+
///
95+
/// # Errors
96+
///
97+
/// Returns an error if the value of the property isn't 8 bytes long.
98+
pub fn cpu_release_addr(&self) -> Result<Option<u64>, PropertyError> {
99+
self.node
100+
.property("cpu-release-addr")
101+
.map(|value| value.as_u64())
102+
.transpose()
103+
}
104+
}
105+
85106
impl<'a> Cpu<FdtNode<'a>> {
86107
/// Returns an iterator over the IDs of the CPU, from the standard `reg`
87108
/// property.

src/standard/memory.rs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use core::ops::Deref;
1111

1212
use crate::error::{PropertyError, StandardError};
1313
use crate::fdt::{Fdt, FdtNode};
14+
use crate::standard::Reg;
1415
use crate::{Cells, Node, Property};
1516

1617
impl<'a> Fdt<'a> {
@@ -27,6 +28,16 @@ impl<'a> Fdt<'a> {
2728
.ok_or(StandardError::MemoryMissing)?;
2829
Ok(Memory { node })
2930
}
31+
32+
/// Returns the `/reserved-memory/*` nodes, if any.
33+
#[must_use]
34+
pub fn reserved_memory(self) -> Option<impl Iterator<Item = ReservedMemory<FdtNode<'a>>>> {
35+
Some(
36+
self.find_node("/reserved-memory")?
37+
.children()
38+
.map(|node| ReservedMemory { node }),
39+
)
40+
}
3041
}
3142

3243
/// Typed wrapper for a `/memory` node.
@@ -107,3 +118,92 @@ impl InitialMappedArea {
107118
}
108119
}
109120
}
121+
122+
/// Typed wrapper for a `/reserved-memory/*` node.
123+
#[derive(Clone, Copy, Debug)]
124+
pub struct ReservedMemory<N> {
125+
node: N,
126+
}
127+
128+
impl<N> Deref for ReservedMemory<N> {
129+
type Target = N;
130+
131+
fn deref(&self) -> &Self::Target {
132+
&self.node
133+
}
134+
}
135+
136+
impl<N: Display> Display for ReservedMemory<N> {
137+
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
138+
self.node.fmt(f)
139+
}
140+
}
141+
142+
impl<'a, N: Node<'a>> ReservedMemory<N> {
143+
/// Returns the value of the standard `size` property of the reserved memory
144+
/// node, if it is present.
145+
///
146+
/// # Errors
147+
///
148+
/// Returns an error if the value of the property isn't a multiple of 4
149+
/// bytes long.
150+
pub fn size(&self) -> Result<Option<Cells<'a>>, PropertyError> {
151+
self.node
152+
.property("size")
153+
.map(|value| value.as_cells())
154+
.transpose()
155+
}
156+
157+
/// Returns the value of the standard `alignment` property of the reserved
158+
/// memory node, if it is present.
159+
///
160+
/// # Errors
161+
///
162+
/// Returns an error if the value of the property isn't a multiple of 4
163+
/// bytes long.
164+
pub fn alignment(&self) -> Result<Option<Cells<'a>>, PropertyError> {
165+
self.node
166+
.property("alignment")
167+
.map(|value| value.as_cells())
168+
.transpose()
169+
}
170+
171+
/// Returns whether the standard `no-map` property is present.
172+
pub fn no_map(&self) -> bool {
173+
self.node.property("no-map").is_some()
174+
}
175+
176+
/// Returns whether the standard `no-map-fixup` property is present.
177+
pub fn no_map_fixup(&self) -> bool {
178+
self.node.property("no-map-fixup").is_some()
179+
}
180+
181+
/// Returns whether the standard `reusable` property is present.
182+
pub fn reusable(&self) -> bool {
183+
self.node.property("reusable").is_some()
184+
}
185+
}
186+
187+
impl<'a> ReservedMemory<FdtNode<'a>> {
188+
/// Returns the value of the standard `alloc-ranges` property.
189+
///
190+
/// # Errors
191+
///
192+
/// Returns an error if the size of the value isn't a multiple of the
193+
/// expected number of address and size cells.
194+
pub fn alloc_ranges(
195+
&self,
196+
) -> Result<Option<impl Iterator<Item = Reg<'a>> + use<'a>>, StandardError> {
197+
let address_cells = self.node.parent_address_space.address_cells as usize;
198+
let size_cells = self.node.parent_address_space.size_cells as usize;
199+
if let Some(property) = self.property("alloc_ranges") {
200+
Ok(Some(
201+
property
202+
.as_prop_encoded_array([address_cells, size_cells])?
203+
.map(Reg::from_cells),
204+
))
205+
} else {
206+
Ok(None)
207+
}
208+
}
209+
}

tests/dtb/test_pretty_print.dtb

203 Bytes
Binary file not shown.

tests/dts/test_pretty_print.dts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,20 @@
2626
initial-mapped-area = <0x00 0x1234 0x00 0x4321 0x1000>;
2727
hotpluggable;
2828
};
29+
30+
reserved-memory {
31+
#address-cells = <0x01>;
32+
#size-cells = <0x01>;
33+
ranges;
34+
35+
shared {
36+
reusable;
37+
size = <0x4000000>;
38+
alignment = <0x2000>;
39+
};
40+
41+
foo@78000000 {
42+
reg = <0x78000000 0x800000>;
43+
};
44+
};
2945
};

tests/fdt.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,43 @@ fn memory() {
258258
);
259259
}
260260

261+
#[test]
262+
fn reserved_memory() {
263+
let dtb = include_bytes!("dtb/test_pretty_print.dtb");
264+
let fdt = Fdt::new(dtb).unwrap();
265+
266+
let reserved = fdt.reserved_memory().unwrap().collect::<Vec<_>>();
267+
268+
assert!(reserved[0].reg().unwrap().is_none());
269+
assert_eq!(
270+
reserved[0]
271+
.size()
272+
.unwrap()
273+
.unwrap()
274+
.to_int::<u32>()
275+
.unwrap(),
276+
0x400_0000
277+
);
278+
assert_eq!(
279+
reserved[0]
280+
.alignment()
281+
.unwrap()
282+
.unwrap()
283+
.to_int::<u32>()
284+
.unwrap(),
285+
0x2000
286+
);
287+
assert!(reserved[0].reusable());
288+
289+
assert!(reserved[1].size().unwrap().is_none());
290+
assert!(reserved[1].alignment().unwrap().is_none());
291+
assert!(!reserved[1].reusable());
292+
let reg = reserved[1].reg().unwrap().unwrap().collect::<Vec<_>>();
293+
assert_eq!(reg.len(), 1);
294+
assert_eq!(reg[0].address::<u32>().unwrap(), 0x7800_0000);
295+
assert_eq!(reg[0].size::<u32>().unwrap(), 0x80_0000);
296+
}
297+
261298
#[macro_export]
262299
macro_rules! load_dtb_dts_pair {
263300
($name:expr) => {

0 commit comments

Comments
 (0)