diff --git a/src/error.rs b/src/error.rs index 4651973..e179956 100644 --- a/src/error.rs +++ b/src/error.rs @@ -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. diff --git a/src/fdt/property.rs b/src/fdt/property.rs index 3cc774a..260c594 100644 --- a/src/fdt/property.rs +++ b/src/fdt/property.rs @@ -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)] @@ -127,6 +127,23 @@ impl<'a> FdtProperty<'a> { FdtStringListIterator { value: self.value } } + pub(crate) fn as_prop_encoded_array( + &self, + chunk_cells: usize, + ) -> Result + use<'a>, FdtError> { + let chunk_bytes = chunk_cells * size_of::(); + 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)?; diff --git a/src/standard.rs b/src/standard.rs index 8f93384..2fefa97 100644 --- a/src/standard.rs +++ b/src/standard.rs @@ -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; @@ -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 { + 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, 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 diff --git a/src/standard/memory.rs b/src/standard/memory.rs new file mode 100644 index 0000000..4b83d58 --- /dev/null +++ b/src/standard/memory.rs @@ -0,0 +1,103 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , 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, 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 + 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 { + 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(), + } + } +} diff --git a/tests/dtb/test_pretty_print.dtb b/tests/dtb/test_pretty_print.dtb index ea91a93..31ac05c 100644 Binary files a/tests/dtb/test_pretty_print.dtb and b/tests/dtb/test_pretty_print.dtb differ diff --git a/tests/dts/test_pretty_print.dts b/tests/dts/test_pretty_print.dts index 87eb86b..e60ad04 100644 --- a/tests/dts/test_pretty_print.dts +++ b/tests/dts/test_pretty_print.dts @@ -23,5 +23,7 @@ memory@80000000 { device_type = "memory"; reg = <0x80000000 0x20000000>; + initial-mapped-area = <0x00 0x1234 0x00 0x4321 0x1000>; + hotpluggable; }; }; diff --git a/tests/fdt.rs b/tests/fdt.rs index 785de25..a381de6 100644 --- a/tests/fdt.rs +++ b/tests/fdt.rs @@ -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() { @@ -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![InitialMappedArea { + effective_address: 0x1234, + physical_address: 0x4321, + size: 0x1000, + }] + ); +} + #[macro_export] macro_rules! load_dtb_dts_pair { ($name:expr) => {