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
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }

Expand Down
71 changes: 32 additions & 39 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
74 changes: 38 additions & 36 deletions src/fdt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<u32>();
Expand Down Expand Up @@ -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<Self, FdtError> {
pub fn new(data: &'a [u8]) -> Result<Self, FdtParseError> {
if data.len() < size_of::<FdtHeader>() {
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),
));
Expand Down Expand Up @@ -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<Self, FdtError> {
pub unsafe fn from_raw(data: *const u8) -> Result<Self, FdtParseError> {
// 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`
Expand All @@ -238,27 +240,27 @@ 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;

let off_mem_rsvmap = header.off_mem_rsvmap() as usize;
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),
));
Expand All @@ -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),
));
Expand Down Expand Up @@ -322,18 +324,18 @@ impl<'a> Fdt<'a> {
/// Returns an iterator over the memory reservation block.
pub fn memory_reservations(
&self,
) -> impl Iterator<Item = Result<MemoryReservation, FdtError>> + '_ {
) -> impl Iterator<Item = Result<MemoryReservation, FdtParseError>> + '_ {
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)),
Expand Down Expand Up @@ -364,11 +366,11 @@ impl<'a> Fdt<'a> {
/// let root = fdt.root().unwrap();
/// assert_eq!(root.name().unwrap(), "");
/// ```
pub fn root(&self) -> Result<FdtNode<'_>, FdtError> {
pub fn root(&self) -> Result<FdtNode<'_>, 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,
));
Expand Down Expand Up @@ -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<Option<FdtNode<'_>>, FdtError> {
pub fn find_node(&self, path: &str) -> Result<Option<FdtNode<'_>>, FdtParseError> {
if !path.starts_with('/') {
return Ok(None);
}
Expand All @@ -439,23 +441,23 @@ impl<'a> Fdt<'a> {
Ok(Some(current_node))
}

pub(crate) fn read_token(&self, offset: usize) -> Result<FdtToken, FdtError> {
pub(crate) fn read_token(&self, offset: usize) -> Result<FdtToken, FdtParseError> {
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;
let str_block_end = str_block_start + str_block_size;
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))
Expand All @@ -466,32 +468,32 @@ impl<'a> Fdt<'a> {
&self,
offset: usize,
end: Option<usize>,
) -> 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<usize, FdtError> {
pub(crate) fn find_string_end(&self, start: usize) -> Result<usize, FdtParseError> {
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<usize, FdtError> {
pub(crate) fn next_sibling_offset(&self, mut offset: usize) -> Result<usize, FdtParseError> {
offset += FDT_TAGSIZE; // Skip FDT_BEGIN_NODE

// Skip node name
Expand Down Expand Up @@ -530,10 +532,10 @@ impl<'a> Fdt<'a> {
Ok(offset)
}

pub(crate) fn next_property_offset(&self, mut offset: usize) -> Result<usize, FdtError> {
pub(crate) fn next_property_offset(&self, mut offset: usize) -> Result<usize, FdtParseError> {
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
Expand Down
Loading