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
14 changes: 14 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,20 @@ pub enum FdtError {
/// The `status` property of a node had an invalid value.
#[error("Invalid status value")]
InvalidStatus,
/// The required `/memory` node wasn't found.
#[error("/memory node missing")]
MemoryMissing,
/// The size of a prop-encoded-array property wasn't a multiple of the
/// expected element size.
#[error(
"prop-encoded-array property was {size} bytes, but should have been a multiple of {chunk} cells"
)]
PropEncodedArraySizeMismatch {
/// The size in bytes of the prop-encoded-array property.
size: usize,
/// The number of 4 byte cells expected in each element of the array.
chunk: usize,
},
}

/// An error that can occur when parsing a device tree.
Expand Down
19 changes: 18 additions & 1 deletion src/fdt/property.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use core::fmt;
use zerocopy::{FromBytes, big_endian};

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

/// A property of a device tree node.
#[derive(Debug, PartialEq)]
Expand Down Expand Up @@ -127,6 +127,23 @@ impl<'a> FdtProperty<'a> {
FdtStringListIterator { value: self.value }
}

pub(crate) fn as_prop_encoded_array(
&self,
chunk_cells: usize,
) -> Result<impl Iterator<Item = &'a [big_endian::U32]> + use<'a>, FdtError> {
let chunk_bytes = chunk_cells * size_of::<u32>();
if !self.value.len().is_multiple_of(chunk_bytes) {
return Err(FdtError::PropEncodedArraySizeMismatch {
size: self.value.len(),
chunk: chunk_cells,
});
}
Ok(self.value.chunks_exact(chunk_bytes).map(|chunk| {
<[big_endian::U32]>::ref_from_bytes(chunk)
.expect("chunk should be a multiple of 4 bytes because of chunks_exact")
}))
}

pub(crate) fn fmt(&self, f: &mut fmt::Formatter<'_>, indent: usize) -> fmt::Result {
write!(f, "{:indent$}{}", "", self.name, indent = indent)?;

Expand Down
35 changes: 33 additions & 2 deletions src/standard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@

//! Standard nodes and properties.

mod memory;
mod status;

pub use status::Status;

pub use self::memory::{InitialMappedArea, Memory};
pub use self::status::Status;
use crate::error::{FdtError, FdtParseError};
use crate::fdt::FdtNode;

Expand All @@ -32,6 +33,36 @@ impl<'a> FdtNode<'a> {
.map(|property| property.as_str_list()))
}

/// Returns whether this node has a `compatible` properties containing the
/// given string.
///
/// # Errors
///
/// Returns an error if a property's name or value cannot be read.
pub fn is_compatible(&self, compatible_filter: &str) -> Result<bool, FdtParseError> {
Ok(if let Some(mut compatible) = self.compatible()? {
compatible.any(|c| c == compatible_filter)
} else {
false
})
}

/// Finds all child nodes with a `compatible` property containing the given
/// string.
pub fn find_compatible<'f>(
&self,
compatible_filter: &'f str,
) -> impl Iterator<Item = Result<FdtNode<'a>, FdtParseError>> + use<'a, 'f> {
self.children().filter_map(|child| match child {
Ok(child) => match child.is_compatible(compatible_filter) {
Ok(true) => Some(Ok(child)),
Ok(false) => None,
Err(e) => Some(Err(e)),
},
Err(e) => Some(Err(e)),
})
}

/// Returns the value of the standard `model` property.
///
/// # Errors
Expand Down
103 changes: 103 additions & 0 deletions src/standard/memory.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// 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.

use core::ops::Deref;

use zerocopy::big_endian;

use crate::error::FdtError;
use crate::fdt::{Fdt, FdtNode};

impl Fdt<'_> {
/// Returns the /memory node.
///
/// This should always be included in a valid device tree.
///
/// # Errors
///
/// Returns a parse error if there was a problem reading the FDT structure
/// to find the node, or `FdtError::MemoryMissing` if the memory node is
/// missing.
pub fn memory(&self) -> Result<Memory<'_>, FdtError> {
let node = self.find_node("/memory")?.ok_or(FdtError::MemoryMissing)?;
Ok(Memory { node })
}
}

/// Typed wrapper for a "/memory" node.
#[derive(Clone, Copy, Debug)]
pub struct Memory<'a> {
node: FdtNode<'a>,
}

impl<'a> Deref for Memory<'a> {
type Target = FdtNode<'a>;

fn deref(&self) -> &Self::Target {
&self.node
}
}

impl<'a> Memory<'a> {
/// Returns the value of the standard `initial-mapped-area` property of the
/// memory node.
///
/// # Errors
///
/// Returns an error if a property's name or value cannot be read, or the
/// size of the value isn't a multiple of 5 cells.
pub fn initial_mapped_area(
&self,
) -> Result<Option<impl Iterator<Item = InitialMappedArea> + use<'a>>, FdtError> {
Ok(
if let Some(property) = self.node.property("initial-mapped-area")? {
Some(property.as_prop_encoded_array(5)?.map(|chunk| {
InitialMappedArea::from_cells(
#[expect(clippy::missing_panics_doc)]
chunk
.try_into()
.expect("as_prop_encoded_array should return chunks of the size that InitialMappedArea::from_cells expects"),
)
}))
} else {
None
},
)
}

/// Returns the value of the standard `hotpluggable` property of the memory
/// node.
///
/// # Errors
///
/// Returns an error if a property's name or value cannot be read.
pub fn hotpluggable(&self) -> Result<bool, FdtError> {
Ok(self.node.property("hotpluggable")?.is_some())
}
}

/// The value of an `initial-mapped-area` property.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct InitialMappedArea {
/// The effective address.
pub effective_address: u64,
/// The physical address.
pub physical_address: u64,
/// The size of the area.
pub size: u32,
}

impl InitialMappedArea {
fn from_cells([ea_high, ea_low, pa_high, pa_low, size]: [big_endian::U32; 5]) -> Self {
Self {
effective_address: u64::from(ea_high.get()) << 32 | u64::from(ea_low.get()),
physical_address: u64::from(pa_high.get()) << 32 | u64::from(pa_low.get()),
size: size.get(),
}
}
}
Binary file modified tests/dtb/test_pretty_print.dtb
Binary file not shown.
2 changes: 2 additions & 0 deletions tests/dts/test_pretty_print.dts
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,7 @@
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>;
initial-mapped-area = <0x00 0x1234 0x00 0x4321 0x1000>;
hotpluggable;
};
};
23 changes: 22 additions & 1 deletion tests/fdt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
use dtoolkit::fdt::Fdt;
#[cfg(feature = "write")]
use dtoolkit::model::DeviceTree;
use dtoolkit::standard::Status;
use dtoolkit::standard::{InitialMappedArea, Status};

#[test]
fn read_child_nodes() {
Expand Down Expand Up @@ -185,6 +185,27 @@ fn find_node_by_path() {
assert!(fdt.find_node("").unwrap().is_none());
}

#[test]
fn memory() {
let dtb = include_bytes!("dtb/test_pretty_print.dtb");
let fdt = Fdt::new(dtb).unwrap();

let memory = fdt.memory().unwrap();
assert!(memory.hotpluggable().unwrap());
assert_eq!(
memory
.initial_mapped_area()
.unwrap()
.unwrap()
.collect::<Vec<_>>(),
vec![InitialMappedArea {
effective_address: 0x1234,
physical_address: 0x4321,
size: 0x1000,
}]
);
}

#[macro_export]
macro_rules! load_dtb_dts_pair {
($name:expr) => {
Expand Down