diff --git a/Cargo.toml b/Cargo.toml index fa87a03..fa07ad9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ write = ["dep:indexmap", "dep:twox-hash"] [dependencies] indexmap = { version = "2", optional = true, default-features = false } +thiserror = { version = "2", default-features = false } twox-hash = { version = "2", optional = true, features = ["xxhash64"], default-features = false } zerocopy = { version = "0.8.28", features = ["derive"] } diff --git a/src/error.rs b/src/error.rs index cd07dde..4651973 100644 --- a/src/error.rs +++ b/src/error.rs @@ -8,78 +8,71 @@ //! Error types for the `dtoolkit` crate. -use core::fmt; +use core::fmt::{self, Display, Formatter}; + +use thiserror::Error; + +/// An error that can occur when parsing or accessing a device tree. +#[derive(Clone, Debug, Eq, Error, PartialEq)] +pub enum FdtError { + /// There was an error parsing the device tree. + #[error("{0}")] + Parse(#[from] FdtParseError), + /// The `status` property of a node had an invalid value. + #[error("Invalid status value")] + InvalidStatus, +} /// An error that can occur when parsing a device tree. -#[derive(Debug)] +#[derive(Clone, Debug, Eq, Error, PartialEq)] #[non_exhaustive] -pub struct FdtError { +pub struct FdtParseError { offset: usize, /// The type of the error that has occurred. pub kind: FdtErrorKind, } -impl FdtError { +impl FdtParseError { pub(crate) fn new(kind: FdtErrorKind, offset: usize) -> Self { Self { offset, kind } } } +impl Display for FdtParseError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{} at offset {}", self.kind, self.offset) + } +} /// The kind of an error that can occur when parsing a device tree. -#[derive(Debug)] +#[derive(Clone, Debug, Eq, Error, PartialEq)] #[non_exhaustive] pub enum FdtErrorKind { /// The magic number of the device tree is invalid. + #[error("Invalid FDT magic number")] InvalidMagic, /// The Device Tree version is not supported by this library. + #[error("FDT version {0} is not supported")] UnsupportedVersion(u32), /// The length of the device tree is invalid. + #[error("Invalid FDT length")] InvalidLength, /// The header failed validation. + #[error("FDT header has failed validation: {0}")] InvalidHeader(&'static str), /// An invalid token was encountered. + #[error("Bad FDT token: {0:#x}")] BadToken(u32), /// A read from data at invalid offset was attempted. + #[error("Invalid offset in FDT")] InvalidOffset, /// An invalid string was encountered. + #[error("Invalid string in FDT")] InvalidString, /// Memory reservation block has not been terminated with a null entry. + #[error("Memory reservation block was not terminated with a null entry")] MemReserveNotTerminated, /// Memory reservation block has an entry that is unaligned or has invalid /// size. + #[error("Memory reservation block has an entry that is unaligned or has invalid size")] MemReserveInvalid, } - -impl fmt::Display for FdtError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{} at offset {}", self.kind, self.offset) - } -} - -impl fmt::Display for FdtErrorKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - FdtErrorKind::InvalidMagic => write!(f, "invalid FDT magic number"), - FdtErrorKind::UnsupportedVersion(version) => { - write!(f, "the FDT version {version} is not supported") - } - FdtErrorKind::InvalidLength => write!(f, "invalid FDT length"), - FdtErrorKind::InvalidHeader(msg) => { - write!(f, "FDT header has failed validation: {msg}") - } - FdtErrorKind::BadToken(token) => write!(f, "bad FDT token: 0x{token:x}"), - FdtErrorKind::InvalidOffset => write!(f, "invalid offset in FDT"), - FdtErrorKind::InvalidString => write!(f, "invalid string in FDT"), - FdtErrorKind::MemReserveNotTerminated => write!( - f, - "memory reservation block not terminated with a null entry" - ), - FdtErrorKind::MemReserveInvalid => write!( - f, - "memory reservation block has an entry that is unaligned or has invalid size" - ), - } - } -} - -impl core::error::Error for FdtError {} diff --git a/src/fdt/mod.rs b/src/fdt/mod.rs index abe0add..e4c4d62 100644 --- a/src/fdt/mod.rs +++ b/src/fdt/mod.rs @@ -15,19 +15,21 @@ //! //! [Flattened Device Tree (FDT)]: https://devicetree-specification.readthedocs.io/en/latest/chapter5-flattened-format.html -use crate::error::{FdtError, FdtErrorKind}; -use crate::memreserve::MemoryReservation; mod node; mod property; + use core::ffi::CStr; use core::mem::offset_of; use core::{fmt, ptr}; -pub use node::FdtNode; -pub use property::FdtProperty; use zerocopy::byteorder::big_endian; use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned}; +pub use self::node::FdtNode; +pub use self::property::FdtProperty; +use crate::error::{FdtErrorKind, FdtParseError}; +use crate::memreserve::MemoryReservation; + /// Version of the FDT specification supported by this library. const FDT_VERSION: u32 = 17; pub(crate) const FDT_TAGSIZE: usize = size_of::(); @@ -161,29 +163,29 @@ impl<'a> Fdt<'a> { /// # let dtb = include_bytes!("../../tests/dtb/test.dtb"); /// let fdt = Fdt::new(dtb).unwrap(); /// ``` - pub fn new(data: &'a [u8]) -> Result { + pub fn new(data: &'a [u8]) -> Result { if data.len() < size_of::() { - return Err(FdtError::new(FdtErrorKind::InvalidLength, 0)); + return Err(FdtParseError::new(FdtErrorKind::InvalidLength, 0)); } let fdt = Fdt { data }; let header = fdt.header(); if header.magic() != FDT_MAGIC { - return Err(FdtError::new( + return Err(FdtParseError::new( FdtErrorKind::InvalidMagic, offset_of!(FdtHeader, magic), )); } if !(header.last_comp_version()..=header.version()).contains(&FDT_VERSION) { - return Err(FdtError::new( + return Err(FdtParseError::new( FdtErrorKind::UnsupportedVersion(header.version()), offset_of!(FdtHeader, version), )); } if header.totalsize() as usize != data.len() { - return Err(FdtError::new( + return Err(FdtParseError::new( FdtErrorKind::InvalidLength, offset_of!(FdtHeader, totalsize), )); @@ -222,7 +224,7 @@ impl<'a> Fdt<'a> { embedded applications, where the binary only gets a pointer to DT from the firmware or \ a bootloader. The user must ensure it trusts the data." )] - pub unsafe fn from_raw(data: *const u8) -> Result { + pub unsafe fn from_raw(data: *const u8) -> Result { // SAFETY: The caller guarantees that `data` is a valid pointer to a Flattened // Device Tree (FDT) blob. We are reading an `FdtHeader` from this // pointer, which is a `#[repr(C, packed)]` struct. The `totalsize` @@ -238,7 +240,7 @@ impl<'a> Fdt<'a> { Fdt::new(slice) } - fn validate_header(&self) -> Result<(), FdtError> { + fn validate_header(&self) -> Result<(), FdtParseError> { let header = self.header(); let data = &self.data; @@ -246,19 +248,19 @@ impl<'a> Fdt<'a> { let off_dt_struct = header.off_dt_struct() as usize; let off_dt_strings = header.off_dt_strings() as usize; if off_mem_rsvmap > off_dt_struct { - return Err(FdtError::new( + return Err(FdtParseError::new( FdtErrorKind::InvalidHeader("dt_struct not after memrsvmap"), offset_of!(FdtHeader, off_mem_rsvmap), )); } if off_dt_struct > data.len() { - return Err(FdtError::new( + return Err(FdtParseError::new( FdtErrorKind::InvalidHeader("struct offset out of bounds"), offset_of!(FdtHeader, off_dt_struct), )); } if off_dt_strings > data.len() { - return Err(FdtError::new( + return Err(FdtParseError::new( FdtErrorKind::InvalidHeader("strings offset out of bounds"), offset_of!(FdtHeader, off_dt_strings), )); @@ -267,19 +269,19 @@ impl<'a> Fdt<'a> { let size_dt_struct = header.size_dt_struct() as usize; let size_dt_strings = header.size_dt_strings() as usize; if off_dt_struct.saturating_add(size_dt_struct) > data.len() { - return Err(FdtError::new( + return Err(FdtParseError::new( FdtErrorKind::InvalidHeader("struct block overflows"), offset_of!(FdtHeader, size_dt_struct), )); } if off_dt_strings.saturating_add(size_dt_strings) > data.len() { - return Err(FdtError::new( + return Err(FdtParseError::new( FdtErrorKind::InvalidHeader("strings block overflows"), offset_of!(FdtHeader, size_dt_strings), )); } if off_dt_struct.saturating_add(size_dt_struct) > off_dt_strings { - return Err(FdtError::new( + return Err(FdtParseError::new( FdtErrorKind::InvalidHeader("strings block not after struct block"), offset_of!(FdtHeader, off_dt_strings), )); @@ -322,18 +324,18 @@ impl<'a> Fdt<'a> { /// Returns an iterator over the memory reservation block. pub fn memory_reservations( &self, - ) -> impl Iterator> + '_ { + ) -> impl Iterator> + '_ { let mut offset = self.header().off_mem_rsvmap() as usize; core::iter::from_fn(move || { if offset >= self.header().off_dt_struct() as usize { - return Some(Err(FdtError::new( + return Some(Err(FdtParseError::new( FdtErrorKind::MemReserveNotTerminated, offset, ))); } let reservation = match MemoryReservation::ref_from_prefix(&self.data[offset..]) - .map_err(|_| FdtError::new(FdtErrorKind::MemReserveInvalid, offset)) + .map_err(|_| FdtParseError::new(FdtErrorKind::MemReserveInvalid, offset)) { Ok((reservation, _)) => *reservation, Err(e) => return Some(Err(e)), @@ -364,11 +366,11 @@ impl<'a> Fdt<'a> { /// let root = fdt.root().unwrap(); /// assert_eq!(root.name().unwrap(), ""); /// ``` - pub fn root(&self) -> Result, FdtError> { + pub fn root(&self) -> Result, FdtParseError> { let offset = self.header().off_dt_struct() as usize; let token = self.read_token(offset)?; if token != FdtToken::BeginNode { - return Err(FdtError::new( + return Err(FdtParseError::new( FdtErrorKind::BadToken(FDT_BEGIN_NODE), offset, )); @@ -422,7 +424,7 @@ impl<'a> Fdt<'a> { /// let node = fdt.find_node("/child2@42").unwrap().unwrap(); /// assert_eq!(node.name().unwrap(), "child2@42"); /// ``` - pub fn find_node(&self, path: &str) -> Result>, FdtError> { + pub fn find_node(&self, path: &str) -> Result>, FdtParseError> { if !path.starts_with('/') { return Ok(None); } @@ -439,15 +441,15 @@ impl<'a> Fdt<'a> { Ok(Some(current_node)) } - pub(crate) fn read_token(&self, offset: usize) -> Result { + pub(crate) fn read_token(&self, offset: usize) -> Result { let val = big_endian::U32::ref_from_prefix(&self.data[offset..]) .map(|(val, _)| val.get()) - .map_err(|_e| FdtError::new(FdtErrorKind::InvalidLength, offset))?; - FdtToken::try_from(val).map_err(|t| FdtError::new(FdtErrorKind::BadToken(t), offset)) + .map_err(|_e| FdtParseError::new(FdtErrorKind::InvalidLength, offset))?; + FdtToken::try_from(val).map_err(|t| FdtParseError::new(FdtErrorKind::BadToken(t), offset)) } /// Returns a string from the string block. - pub(crate) fn string(&self, string_block_offset: usize) -> Result<&'a str, FdtError> { + pub(crate) fn string(&self, string_block_offset: usize) -> Result<&'a str, FdtParseError> { let header = self.header(); let str_block_start = header.off_dt_strings() as usize; let str_block_size = header.size_dt_strings() as usize; @@ -455,7 +457,7 @@ impl<'a> Fdt<'a> { let str_start = str_block_start + string_block_offset; if str_start >= str_block_end { - return Err(FdtError::new(FdtErrorKind::InvalidLength, str_start)); + return Err(FdtParseError::new(FdtErrorKind::InvalidLength, str_start)); } self.string_at_offset(str_start, Some(str_block_end)) @@ -466,32 +468,32 @@ impl<'a> Fdt<'a> { &self, offset: usize, end: Option, - ) -> Result<&'a str, FdtError> { + ) -> Result<&'a str, FdtParseError> { let slice = match end { Some(end) => self.data.get(offset..end), None => self.data.get(offset..), }; - let slice = slice.ok_or(FdtError::new(FdtErrorKind::InvalidOffset, offset))?; + let slice = slice.ok_or(FdtParseError::new(FdtErrorKind::InvalidOffset, offset))?; match CStr::from_bytes_until_nul(slice).map(|val| val.to_str()) { Ok(Ok(val)) => Ok(val), - _ => Err(FdtError::new(FdtErrorKind::InvalidString, offset)), + _ => Err(FdtParseError::new(FdtErrorKind::InvalidString, offset)), } } - pub(crate) fn find_string_end(&self, start: usize) -> Result { + pub(crate) fn find_string_end(&self, start: usize) -> Result { let mut offset = start; loop { match self.data.get(offset) { Some(0) => return Ok(offset + 1), Some(_) => {} - None => return Err(FdtError::new(FdtErrorKind::InvalidString, start)), + None => return Err(FdtParseError::new(FdtErrorKind::InvalidString, start)), } offset += 1; } } - pub(crate) fn next_sibling_offset(&self, mut offset: usize) -> Result { + pub(crate) fn next_sibling_offset(&self, mut offset: usize) -> Result { offset += FDT_TAGSIZE; // Skip FDT_BEGIN_NODE // Skip node name @@ -530,10 +532,10 @@ impl<'a> Fdt<'a> { Ok(offset) } - pub(crate) fn next_property_offset(&self, mut offset: usize) -> Result { + pub(crate) fn next_property_offset(&self, mut offset: usize) -> Result { let len = big_endian::U32::ref_from_prefix(&self.data[offset..]) .map(|(val, _)| val.get()) - .map_err(|_e| FdtError::new(FdtErrorKind::InvalidLength, offset))? + .map_err(|_e| FdtParseError::new(FdtErrorKind::InvalidLength, offset))? as usize; offset += FDT_TAGSIZE; // skip value length offset += FDT_TAGSIZE; // skip name offset diff --git a/src/fdt/node.rs b/src/fdt/node.rs index 382c9f3..92f5761 100644 --- a/src/fdt/node.rs +++ b/src/fdt/node.rs @@ -11,7 +11,7 @@ use core::fmt; use super::{FDT_TAGSIZE, Fdt, FdtToken}; -use crate::error::FdtError; +use crate::error::FdtParseError; use crate::fdt::property::{FdtPropIter, FdtProperty}; /// A node in a flattened device tree. @@ -42,7 +42,7 @@ impl<'a> FdtNode<'a> { /// let child = root.child("child1").unwrap().unwrap(); /// assert_eq!(child.name().unwrap(), "child1"); /// ``` - pub fn name(&self) -> Result<&'a str, FdtError> { + pub fn name(&self) -> Result<&'a str, FdtParseError> { let name_offset = self.offset + FDT_TAGSIZE; self.fdt.string_at_offset(name_offset, None) } @@ -56,7 +56,7 @@ impl<'a> FdtNode<'a> { /// if the name offset is invalid or an /// [`FdtErrorKind::InvalidString`](crate::error::FdtErrorKind::InvalidString) if the string at the offset is not null-terminated /// or contains invalid UTF-8. - pub fn name_without_address(&self) -> Result<&'a str, FdtError> { + pub fn name_without_address(&self) -> Result<&'a str, FdtParseError> { let name = self.name()?; if let Some((name, _)) = name.split_once('@') { Ok(name) @@ -85,7 +85,7 @@ impl<'a> FdtNode<'a> { /// # Errors /// /// Returns an error if a property's name or value cannot be read. - pub fn property(&self, name: &str) -> Result>, FdtError> { + pub fn property(&self, name: &str) -> Result>, FdtParseError> { for property in self.properties() { let property = property?; if property.name() == name { @@ -109,7 +109,9 @@ impl<'a> FdtNode<'a> { /// assert_eq!(props.next().unwrap().unwrap().name(), "u64-prop"); /// assert_eq!(props.next().unwrap().unwrap().name(), "str-prop"); /// ``` - pub fn properties(&self) -> impl Iterator, FdtError>> + use<'a> { + pub fn properties( + &self, + ) -> impl Iterator, FdtParseError>> + use<'a> { FdtPropIter::Start { fdt: self.fdt, offset: self.offset, @@ -161,7 +163,7 @@ impl<'a> FdtNode<'a> { /// let child = root.child("child2@42").unwrap().unwrap(); /// assert_eq!(child.name().unwrap(), "child2@42"); /// ``` - pub fn child(&self, name: &str) -> Result>, FdtError> { + pub fn child(&self, name: &str) -> Result>, FdtParseError> { let include_address = name.contains('@'); for child in self.children() { let child = child?; @@ -193,7 +195,7 @@ impl<'a> FdtNode<'a> { /// ); /// assert!(children.next().is_none()); /// ``` - pub fn children(&self) -> impl Iterator, FdtError>> + use<'a> { + pub fn children(&self) -> impl Iterator, FdtParseError>> + use<'a> { FdtChildIter::Start { fdt: self.fdt, offset: self.offset, @@ -246,7 +248,7 @@ enum FdtChildIter<'a> { } impl<'a> Iterator for FdtChildIter<'a> { - type Item = Result, FdtError>; + type Item = Result, FdtParseError>; fn next(&mut self) -> Option { match self { @@ -278,7 +280,10 @@ impl<'a> Iterator for FdtChildIter<'a> { } impl<'a> FdtChildIter<'a> { - fn try_next(fdt: &'a Fdt<'a>, offset: &mut usize) -> Option, FdtError>> { + fn try_next( + fdt: &'a Fdt<'a>, + offset: &mut usize, + ) -> Option, FdtParseError>> { loop { let token = match fdt.read_token(*offset) { Ok(token) => token, diff --git a/src/fdt/property.rs b/src/fdt/property.rs index 2de5a82..3cc774a 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::{FdtError, FdtErrorKind}; +use crate::error::{FdtErrorKind, FdtParseError}; /// A property of a device tree node. #[derive(Debug, PartialEq)] @@ -54,10 +54,10 @@ impl<'a> FdtProperty<'a> { /// let prop = node.property("u32-prop").unwrap().unwrap(); /// assert_eq!(prop.as_u32().unwrap(), 0x12345678); /// ``` - pub fn as_u32(&self) -> Result { + pub fn as_u32(&self) -> Result { big_endian::U32::ref_from_bytes(self.value) .map(|val| val.get()) - .map_err(|_e| FdtError::new(FdtErrorKind::InvalidLength, self.value_offset)) + .map_err(|_e| FdtParseError::new(FdtErrorKind::InvalidLength, self.value_offset)) } /// Returns the value of this property as a `u64`. @@ -77,10 +77,10 @@ impl<'a> FdtProperty<'a> { /// let prop = node.property("u64-prop").unwrap().unwrap(); /// assert_eq!(prop.as_u64().unwrap(), 0x1122334455667788); /// ``` - pub fn as_u64(&self) -> Result { + pub fn as_u64(&self) -> Result { big_endian::U64::ref_from_bytes(self.value) .map(|val| val.get()) - .map_err(|_e| FdtError::new(FdtErrorKind::InvalidLength, self.value_offset)) + .map_err(|_e| FdtParseError::new(FdtErrorKind::InvalidLength, self.value_offset)) } /// Returns the value of this property as a string. @@ -100,11 +100,11 @@ impl<'a> FdtProperty<'a> { /// let prop = node.property("str-prop").unwrap().unwrap(); /// assert_eq!(prop.as_str().unwrap(), "hello world"); /// ``` - pub fn as_str(&self) -> Result<&'a str, FdtError> { + pub fn as_str(&self) -> Result<&'a str, FdtParseError> { let cstr = CStr::from_bytes_with_nul(self.value) - .map_err(|_| FdtError::new(FdtErrorKind::InvalidString, self.value_offset))?; + .map_err(|_| FdtParseError::new(FdtErrorKind::InvalidString, self.value_offset))?; cstr.to_str() - .map_err(|_| FdtError::new(FdtErrorKind::InvalidString, self.value_offset)) + .map_err(|_| FdtParseError::new(FdtErrorKind::InvalidString, self.value_offset)) } /// Returns an iterator over the strings in this property. @@ -123,7 +123,7 @@ impl<'a> FdtProperty<'a> { /// assert_eq!(str_list.next(), Some("third")); /// assert_eq!(str_list.next(), None); /// ``` - pub fn as_str_list(&self) -> impl Iterator { + pub fn as_str_list(&self) -> impl Iterator + use<'a> { FdtStringListIterator { value: self.value } } @@ -189,7 +189,7 @@ pub(crate) enum FdtPropIter<'a> { } impl<'a> Iterator for FdtPropIter<'a> { - type Item = Result, FdtError>; + type Item = Result, FdtParseError>; fn next(&mut self) -> Option { match self { @@ -221,7 +221,10 @@ impl<'a> Iterator for FdtPropIter<'a> { } impl<'a> FdtPropIter<'a> { - fn try_next(fdt: &'a Fdt<'a>, offset: &mut usize) -> Option, FdtError>> { + fn try_next( + fdt: &'a Fdt<'a>, + offset: &mut usize, + ) -> Option, FdtParseError>> { loop { let token = match fdt.read_token(*offset) { Ok(token) => token, @@ -234,7 +237,10 @@ impl<'a> FdtPropIter<'a> { ) { Ok((val, _)) => val.get() as usize, Err(_) => { - return Some(Err(FdtError::new(FdtErrorKind::InvalidLength, *offset))); + return Some(Err(FdtParseError::new( + FdtErrorKind::InvalidLength, + *offset, + ))); } }; let nameoff = match big_endian::U32::ref_from_prefix( @@ -242,7 +248,10 @@ impl<'a> FdtPropIter<'a> { ) { Ok((val, _)) => val.get() as usize, Err(_) => { - return Some(Err(FdtError::new(FdtErrorKind::InvalidLength, *offset))); + return Some(Err(FdtParseError::new( + FdtErrorKind::InvalidLength, + *offset, + ))); } }; let prop_offset = *offset + 3 * FDT_TAGSIZE; diff --git a/src/lib.rs b/src/lib.rs index 8e732cf..0547867 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,3 +85,4 @@ pub mod fdt; pub mod memreserve; #[cfg(feature = "write")] pub mod model; +pub mod standard; diff --git a/src/model/mod.rs b/src/model/mod.rs index 3ad2d13..97e8e42 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -16,7 +16,7 @@ use alloc::vec::Vec; use core::fmt::Display; -use crate::error::FdtError; +use crate::error::FdtParseError; use crate::fdt::Fdt; use crate::memreserve::MemoryReservation; mod node; @@ -78,7 +78,7 @@ impl DeviceTree { /// # Errors /// /// Returns an error if the root node of the `Fdt` cannot be parsed. - pub fn from_fdt(fdt: &Fdt<'_>) -> Result { + pub fn from_fdt(fdt: &Fdt<'_>) -> Result { let root = DeviceTreeNode::try_from(fdt.root()?)?; let memory_reservations: Result, _> = fdt.memory_reservations().collect(); Ok(DeviceTree { diff --git a/src/model/node.rs b/src/model/node.rs index af5824e..c5d08e2 100644 --- a/src/model/node.rs +++ b/src/model/node.rs @@ -14,7 +14,7 @@ use indexmap::IndexMap; use twox_hash::xxhash64; use super::property::DeviceTreeProperty; -use crate::error::FdtError; +use crate::error::FdtParseError; use crate::fdt::FdtNode; /// A mutable, in-memory representation of a device tree node. @@ -258,7 +258,7 @@ impl DeviceTreeNode { } impl<'a> TryFrom> for DeviceTreeNode { - type Error = FdtError; + type Error = FdtParseError; fn try_from(node: FdtNode<'a>) -> Result { let name = node.name()?.to_string(); diff --git a/src/model/property.rs b/src/model/property.rs index 93dc1cb..6aab40d 100644 --- a/src/model/property.rs +++ b/src/model/property.rs @@ -10,7 +10,7 @@ use alloc::string::{String, ToString}; use alloc::vec::Vec; use core::{fmt, str}; -use crate::error::FdtError; +use crate::error::FdtParseError; use crate::fdt::FdtProperty; /// An error that can occur when parsing a property. @@ -126,7 +126,7 @@ impl DeviceTreeProperty { } impl<'a> TryFrom> for DeviceTreeProperty { - type Error = FdtError; + type Error = FdtParseError; fn try_from(prop: FdtProperty<'a>) -> Result { let name = prop.name().to_string(); diff --git a/src/standard.rs b/src/standard.rs new file mode 100644 index 0000000..8f93384 --- /dev/null +++ b/src/standard.rs @@ -0,0 +1,129 @@ +// 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. + +//! Standard nodes and properties. + +mod status; + +pub use status::Status; + +use crate::error::{FdtError, FdtParseError}; +use crate::fdt::FdtNode; + +const DEFAULT_ADDRESS_CELLS: u32 = 2; +const DEFAULT_SIZE_CELLS: u32 = 1; + +impl<'a> FdtNode<'a> { + /// Returns the value of the standard `compatible` property. + /// + /// # Errors + /// + /// Returns an error if a property's name or value cannot be read. + pub fn compatible( + &self, + ) -> Result + use<'a>>, FdtParseError> { + Ok(self + .property("compatible")? + .map(|property| property.as_str_list())) + } + + /// Returns the value of the standard `model` property. + /// + /// # Errors + /// + /// Returns an error if a property's name or value cannot be read, or the + /// value isn't a valid UTF-8 string. + pub fn model(&self) -> Result, FdtParseError> { + Ok(if let Some(model) = self.property("model")? { + Some(model.as_str()?) + } else { + None + }) + } + + /// Returns the value of the standard `phandle` property. + /// + /// # Errors + /// + /// Returns an error if a property's name or value cannot be read, or the + /// value isn't a valid u32. + pub fn phandle(&self) -> Result, FdtParseError> { + Ok(if let Some(property) = self.property("phandle")? { + Some(property.as_u32()?) + } else { + None + }) + } + + /// Returns the value of the standard `status` property. + /// + /// If there is no `status` property then `okay` is assumed. + /// + /// # Errors + /// + /// Returns an error if a property's name or value cannot be read, or the + /// value isn't a valid status. + pub fn status(&self) -> Result { + Ok(if let Some(status) = self.property("status")? { + status.as_str()?.parse()? + } else { + Status::Okay + }) + } + + /// Returns the value of the standard `#address-cells` property. + /// + /// # Errors + /// + /// Returns an error if a property's name or value cannot be read, or the + /// value isn't a valid u32. + pub fn address_cells(&self) -> Result { + Ok(if let Some(property) = self.property("#address-cells")? { + property.as_u32()? + } else { + DEFAULT_ADDRESS_CELLS + }) + } + + /// Returns the value of the standard `#size-cells` property. + /// + /// # Errors + /// + /// Returns an error if a property's name or value cannot be read, or the + /// value isn't a valid u32. + pub fn size_cells(&self) -> Result { + Ok(if let Some(model) = self.property("#size-cells")? { + model.as_u32()? + } else { + DEFAULT_SIZE_CELLS + }) + } + + /// Returns the value of the standard `virtual-reg` property. + /// + /// # Errors + /// + /// Returns an error if a property's name or value cannot be read, or the + /// value isn't a valid u32. + pub fn virtual_reg(&self) -> Result, FdtParseError> { + Ok(if let Some(property) = self.property("virtual-reg")? { + Some(property.as_u32()?) + } else { + None + }) + } + + /// Returns whether the standard `dma-coherent` property is present. + /// + /// # Errors + /// + /// Returns an error if a property can't be read. + pub fn dma_coherent(&self) -> Result { + Ok(self.property("dma-coherent")?.is_some()) + } +} diff --git a/src/standard/status.rs b/src/standard/status.rs new file mode 100644 index 0000000..cc7ab6d --- /dev/null +++ b/src/standard/status.rs @@ -0,0 +1,62 @@ +// 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::fmt::{self, Display, Formatter}; +use core::str::FromStr; + +use crate::error::FdtError; + +/// The value of a `status` property. +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub enum Status { + /// The device is operational. + #[default] + Okay, + /// The device is not currently operational, but might become so. + Disabled, + /// The device is operational but shouln't be used. + Reserved, + /// The device is not operational. + Fail, + /// The device is not operational, with some device-specific error + /// condition. + FailSss, +} + +impl Status { + fn as_str(self) -> &'static str { + match self { + Status::Okay => "okay", + Status::Disabled => "disadbled", + Status::Reserved => "reserved", + Status::Fail => "fail", + Status::FailSss => "fail-sss", + } + } +} + +impl Display for Status { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +impl FromStr for Status { + type Err = FdtError; + + fn from_str(s: &str) -> Result { + match s { + "okay" => Ok(Self::Okay), + "disadbled" => Ok(Self::Disabled), + "reserved" => Ok(Self::Reserved), + "fail" => Ok(Self::Fail), + "fail-sss" => Ok(Self::FailSss), + _ => Err(FdtError::InvalidStatus), + } + } +} diff --git a/tests/dtb/test_props.dtb b/tests/dtb/test_props.dtb index 7eb1afd..098e606 100644 Binary files a/tests/dtb/test_props.dtb and b/tests/dtb/test_props.dtb differ diff --git a/tests/dts/test_props.dts b/tests/dts/test_props.dts index 2c0e363..5728573 100644 --- a/tests/dts/test_props.dts +++ b/tests/dts/test_props.dts @@ -10,4 +10,15 @@ str-prop = "hello world"; str-list-prop = "first", "second", "third"; }; + + standard-props { + #address-cells = <0x08>; + #size-cells = <0x04>; + compatible = "abc,def", "some,other"; + status = "fail"; + model = "Some Model"; + phandle = <0x1234>; + virtual-reg = <0xabcd>; + dma-coherent; + }; }; diff --git a/tests/fdt.rs b/tests/fdt.rs index 68ee5b3..785de25 100644 --- a/tests/fdt.rs +++ b/tests/fdt.rs @@ -9,6 +9,7 @@ use dtoolkit::fdt::Fdt; #[cfg(feature = "write")] use dtoolkit::model::DeviceTree; +use dtoolkit::standard::Status; #[test] fn read_child_nodes() { @@ -80,6 +81,42 @@ fn get_property_by_name() { assert!(node.property("non-existent-prop").unwrap().is_none()); } +#[test] +fn standard_properties() { + let dtb = include_bytes!("dtb/test_props.dtb"); + let fdt = Fdt::new(dtb).unwrap(); + let root = fdt.root().unwrap(); + let test_props_node = root.child("test-props").unwrap().unwrap(); + let standard_props_node = root.child("standard-props").unwrap().unwrap(); + + // Default values. + assert_eq!(test_props_node.address_cells().unwrap(), 2); + assert_eq!(test_props_node.size_cells().unwrap(), 1); + assert_eq!(test_props_node.status().unwrap(), Status::Okay); + assert_eq!(test_props_node.model().unwrap(), None); + assert!(!test_props_node.dma_coherent().unwrap()); + assert_eq!(test_props_node.phandle().unwrap(), None); + assert_eq!(test_props_node.virtual_reg().unwrap(), None); + assert!(test_props_node.compatible().unwrap().is_none()); + + // Explicit values. + assert_eq!(standard_props_node.address_cells().unwrap(), 8); + assert_eq!(standard_props_node.size_cells().unwrap(), 4); + assert_eq!(standard_props_node.status().unwrap(), Status::Fail); + assert_eq!(standard_props_node.model().unwrap(), Some("Some Model")); + assert!(standard_props_node.dma_coherent().unwrap()); + assert_eq!(standard_props_node.phandle().unwrap(), Some(0x1234)); + assert_eq!(standard_props_node.virtual_reg().unwrap(), Some(0xabcd)); + assert_eq!( + standard_props_node + .compatible() + .unwrap() + .unwrap() + .collect::>(), + vec!["abc,def", "some,other"] + ); +} + #[test] fn get_child_by_name() { let dtb = include_bytes!("dtb/test_children.dtb");