From 41b93d58224f3853fc7e321cda7bf6d8a126fadf Mon Sep 17 00:00:00 2001 From: Andrew Walbran Date: Fri, 12 Dec 2025 17:35:09 +0000 Subject: [PATCH 1/2] Add type and getters for memory node. --- src/error.rs | 14 +++++ src/fdt/property.rs | 22 ++++++- src/standard.rs | 5 +- src/standard/memory.rs | 104 ++++++++++++++++++++++++++++++++ tests/dtb/test_pretty_print.dtb | Bin 499 -> 576 bytes tests/dts/test_pretty_print.dts | 2 + tests/fdt.rs | 23 ++++++- 7 files changed, 166 insertions(+), 4 deletions(-) create mode 100644 src/standard/memory.rs 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..cf5ce3c 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,26 @@ 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, + }); + } + // `ref_from_bytes` shouldn't panic because each chunk will always be a multiple + // of 4 bytes because of `chunks_exact`. + #[allow(clippy::unwrap_used)] + Ok(self + .value + .chunks_exact(chunk_bytes) + .map(|chunk| <[big_endian::U32]>::ref_from_bytes(chunk).unwrap())) + } + 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..a0df607 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; diff --git a/src/standard/memory.rs b/src/standard/memory.rs new file mode 100644 index 0000000..a530797 --- /dev/null +++ b/src/standard/memory.rs @@ -0,0 +1,104 @@ +// 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. + #[allow(clippy::missing_panics_doc)] + pub fn initial_mapped_area( + &self, + ) -> Result + use<'a>>, FdtError> { + Ok( + if let Some(property) = self.node.property("initial-mapped-area")? { + // try_into can't return an error, because we passed a chunk size matching what + // `InitialMappedArea::from_cells` expects. + #[allow(clippy::unwrap_used)] + Some( + property + .as_prop_encoded_array(5)? + .map(|chunk| InitialMappedArea::from_cells(chunk.try_into().unwrap())), + ) + } 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, PartialEq)] +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 ea91a936f0e1f9609d168f35d0ec32605401d32f..31ac05c1c70328a5322b12830f4fa35e617e8787 100644 GIT binary patch delta 129 zcmey&e1Jvc0`I@K3=B*T3=9kw3=E8WfV2h>3j(nK5Q9JqP`qWMMm(b?Gms|&#K|CG z1_mJ$2<@y0RSc2`f%3^MjQNV0d6^}di8;Eti3J6zDY}V8sfi33`6UH8rRnL3Nja$u E04d}cJ^%m! delta 53 ycmX@W@|juV0`I@K3=E8)85kHWfb;|)tpUV>Kr8^nAdn0ckJzXY&p3HAV-5g8W(rvV 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) => { From 28adad541105b303bfbf4711cace28758729ad07 Mon Sep 17 00:00:00 2001 From: Andrew Walbran Date: Wed, 17 Dec 2025 13:53:04 +0000 Subject: [PATCH 2/2] Fixes from code review. --- src/fdt/property.rs | 11 ++++------- src/standard/memory.rs | 19 +++++++++---------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/fdt/property.rs b/src/fdt/property.rs index cf5ce3c..260c594 100644 --- a/src/fdt/property.rs +++ b/src/fdt/property.rs @@ -138,13 +138,10 @@ impl<'a> FdtProperty<'a> { chunk: chunk_cells, }); } - // `ref_from_bytes` shouldn't panic because each chunk will always be a multiple - // of 4 bytes because of `chunks_exact`. - #[allow(clippy::unwrap_used)] - Ok(self - .value - .chunks_exact(chunk_bytes) - .map(|chunk| <[big_endian::U32]>::ref_from_bytes(chunk).unwrap())) + 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 { diff --git a/src/standard/memory.rs b/src/standard/memory.rs index a530797..4b83d58 100644 --- a/src/standard/memory.rs +++ b/src/standard/memory.rs @@ -51,20 +51,19 @@ impl<'a> Memory<'a> { /// /// 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. - #[allow(clippy::missing_panics_doc)] pub fn initial_mapped_area( &self, ) -> Result + use<'a>>, FdtError> { Ok( if let Some(property) = self.node.property("initial-mapped-area")? { - // try_into can't return an error, because we passed a chunk size matching what - // `InitialMappedArea::from_cells` expects. - #[allow(clippy::unwrap_used)] - Some( - property - .as_prop_encoded_array(5)? - .map(|chunk| InitialMappedArea::from_cells(chunk.try_into().unwrap())), - ) + 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 }, @@ -83,7 +82,7 @@ impl<'a> Memory<'a> { } /// The value of an `initial-mapped-area` property. -#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct InitialMappedArea { /// The effective address. pub effective_address: u64,