diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..8392d15 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake \ No newline at end of file diff --git a/.gitignore b/.gitignore index b354aec..17adb98 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ -Cargo.lock -target/ \ No newline at end of file +/.direnv/ +/Cargo.lock +/target/ diff --git a/Cargo.toml b/Cargo.toml index 4db41e4..f2acc9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "fdt" version = "0.2.0-alpha1" authors = ["Wesley Norris "] -edition = "2018" +edition = "2021" repository = "https://github.com/repnop/fdt" @@ -15,5 +15,6 @@ readme = "README.md" [features] pretty-printing = [] +linux-dt-bindings = [] [dependencies] diff --git a/examples/basic_info.rs b/examples/basic_info.rs index 2bfc5e4..0e742b4 100644 --- a/examples/basic_info.rs +++ b/examples/basic_info.rs @@ -1,35 +1,37 @@ -static MY_FDT: &[u8] = include_bytes!("../dtb/test.dtb"); +// static MY_FDT: &[u8] = include_bytes!("../dtb/test.dtb"); -fn main() { - let fdt = fdt::Fdt::new(MY_FDT).unwrap(); +// fn main() { +// let fdt = fdt::Fdt::new(MY_FDT).unwrap(); - println!("This is a devicetree representation of a {}", fdt.root().model()); - println!("...which is compatible with at least: {}", fdt.root().compatible().first()); - println!("...and has {} CPU(s)", fdt.cpus().count()); - println!( - "...and has at least one memory location at: {:#X}\n", - fdt.memory().regions().next().unwrap().starting_address as usize - ); +// println!("This is a devicetree representation of a {}", fdt.root().model()); +// println!("...which is compatible with at least: {}", fdt.root().compatible().first()); +// println!("...and has {} CPU(s)", fdt.cpus().count()); +// println!( +// "...and has at least one memory location at: {:#X}\n", +// fdt.memory().regions().next().unwrap().starting_address as usize +// ); - let chosen = fdt.chosen(); - if let Some(bootargs) = chosen.bootargs() { - println!("The bootargs are: {:?}", bootargs); - } +// let chosen = fdt.chosen(); +// if let Some(bootargs) = chosen.bootargs() { +// println!("The bootargs are: {:?}", bootargs); +// } - if let Some(stdout) = chosen.stdout() { - println!( - "It would write stdout to: {} with params: {:?}", - stdout.node().name, - stdout.params() - ); - } +// if let Some(stdout) = chosen.stdout() { +// println!( +// "It would write stdout to: {} with params: {:?}", +// stdout.node().name, +// stdout.params() +// ); +// } - let soc = fdt.find_node("/soc"); - println!("Does it have a `/soc` node? {}", if soc.is_some() { "yes" } else { "no" }); - if let Some(soc) = soc { - println!("...and it has the following children:"); - for child in soc.children() { - println!(" {}", child.name); - } - } -} +// let soc = fdt.find_node("/soc"); +// println!("Does it have a `/soc` node? {}", if soc.is_some() { "yes" } else { "no" }); +// if let Some(soc) = soc { +// println!("...and it has the following children:"); +// for child in soc.children() { +// println!(" {}", child.name); +// } +// } +// } + +fn main() {} diff --git a/examples/tree_print.rs b/examples/tree_print.rs index 194562c..6e81cc3 100644 --- a/examples/tree_print.rs +++ b/examples/tree_print.rs @@ -1,16 +1,16 @@ -use fdt::node::FdtNode; +use fdt::helpers::UnalignedInfallibleNode; static MY_FDT: &[u8] = include_bytes!("../dtb/test.dtb"); fn main() { - let fdt = fdt::Fdt::new(MY_FDT).unwrap(); + let fdt = fdt::Fdt::new_unaligned(MY_FDT).unwrap(); print_node(fdt.find_node("/").unwrap(), 0); } -fn print_node(node: FdtNode<'_, '_>, n_spaces: usize) { +fn print_node(node: UnalignedInfallibleNode<'_>, n_spaces: usize) { (0..n_spaces).for_each(|_| print!(" ")); - println!("{}/", node.name); + println!("{}/", node.name()); for child in node.children() { print_node(child, n_spaces + 4); diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..6dfe80a --- /dev/null +++ b/flake.lock @@ -0,0 +1,82 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1722062969, + "narHash": "sha256-QOS0ykELUmPbrrUGmegAUlpmUFznDQeR4q7rFhl8eQg=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "b73c2221a46c13557b1b3be9c2070cc42cf01eb3", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1722133294, + "narHash": "sha256-XKSVN+lmjVEFPjMa5Ui0VTay2Uvqa74h0MQT0HU1pqw=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "9803f6e04ca37a2c072783e8297d2080f8d0e739", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..a543443 --- /dev/null +++ b/flake.nix @@ -0,0 +1,35 @@ +{ + description = "fdt development flake"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + rust-overlay = { + url = "github:oxalica/rust-overlay"; + inputs = { + nixpkgs.follows = "nixpkgs"; + flake-utils.follows = "flake-utils"; + }; + }; + }; + + outputs = { nixpkgs, flake-utils, rust-overlay, ... }: + flake-utils.lib.eachDefaultSystem(system: + let + overlays = [(import rust-overlay)]; + pkgs = import nixpkgs { + inherit system overlays; + }; + rust-bin = pkgs.rust-bin.stable.latest.default.override { + extensions = ["rust-src"]; + }; + in { + devShells.default = pkgs.mkShell { + buildInputs = [ + pkgs.nil + rust-bin + ]; + }; + } + ); +} \ No newline at end of file diff --git a/rustfmt.toml b/rustfmt.toml index 2a35f02..4da9651 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1 +1,2 @@ use_small_heuristics = "Max" +max_width = 120 diff --git a/src/cell_collector.rs b/src/cell_collector.rs new file mode 100644 index 0000000..78886e8 --- /dev/null +++ b/src/cell_collector.rs @@ -0,0 +1,259 @@ +use crate::FdtError; + +/// Error type indicating that the cell value that was attempted to be collected +/// was too large for the desired type. +#[derive(Debug, Clone, Copy)] +pub struct CollectCellsError; + +impl From for FdtError { + fn from(_: CollectCellsError) -> Self { + FdtError::CollectCellsError + } +} + +/// A type which performs the underlying collection of cell-sized values into +/// the desired underlying type. +pub trait BuildCellCollector: Default { + /// Output of the builder. Usually the same as the type implementing + /// [`CellCollector`]. + type Output; + + /// Push a new [`u32`] component of a cell-sized value. This can error + /// whenever the value would overflow or otherwise be undesirable. + fn push(&mut self, component: u32) -> Result<(), CollectCellsError>; + /// Finish collecting components and return the collected value. + fn finish(self) -> Self::Output; +} + +/// A type which can be "collected" into from devicetree cell-sized values. The +/// most common types to use for this purpose are [`u32`] and [`u64`] but other +/// types may implement this trait when it's useful, such as [PciAddress]. +/// +/// In the case that a cell value may not exist (such as the parent unit address +/// in a PCI `interrupt-map`), [`Option`] implements [`CellCollector`] for +/// any type `C: CellCollector`. +/// +/// For those who want the collection of these values to always succeed, +/// [`core::num::Wrapping`] implements [`CellCollector`] for numeric types +/// which fit the bounds. ([`u32`] and above, as it requires `From`) +/// +/// [PciAddress]: crate::properties::interrupts::pci::PciAddress +pub trait CellCollector: Default + Sized { + /// Underlying output type, this is usually the same as `Self`. + type Output; + /// Builder type used to collect the individual cell values into the desired type. + type Builder: BuildCellCollector; + + /// Maps the builder output to the desired underlying type. This is usually + /// a no-op, but may not always be, see the [`core::num::Wrapping`] impl. + fn map(builder_out: ::Output) -> Self::Output; +} + +/// Generic integer type collector. +pub struct BuildIntCollector { + value: Int, +} + +impl Default for BuildIntCollector { + fn default() -> Self { + Self { value: Default::default() } + } +} + +impl< + Int: Copy + + Default + + core::cmp::PartialEq + + core::ops::Shl + + core::ops::Shr + + core::ops::BitOr + + From, + > BuildCellCollector for BuildIntCollector +{ + type Output = Int; + + #[inline(always)] + fn push(&mut self, component: u32) -> Result<(), CollectCellsError> { + let shr = const { + match core::mem::size_of::().checked_sub(4) { + Some(value) => value as u32 * 8, + None => panic!("integer type too small"), + } + }; + + if self.value >> shr != Int::from(0u32) { + return Err(CollectCellsError); + } + + // HACK: shifting a `u32` by `32` bits at all, regardless of the value, + // panics, so for `u32`s, don't shift at all since the next call will + // fail above. + let shl = const { + match core::mem::size_of::() { + 0..=4 => 0, + _ => 32, + } + }; + + self.value = self.value.shl(shl).bitor(Int::from(component)); + + Ok(()) + } + + #[inline(always)] + fn finish(self) -> Self::Output { + self.value + } +} + +/// Wrapping collector, used for [`core::num::Wrapping`]. +pub struct BuildWrappingIntCollector { + value: Int, +} + +impl Default for BuildWrappingIntCollector { + fn default() -> Self { + Self { value: Default::default() } + } +} + +impl + core::ops::BitOr + From> + BuildCellCollector for BuildWrappingIntCollector +{ + type Output = Int; + + #[inline(always)] + fn push(&mut self, component: u32) -> Result<(), CollectCellsError> { + self.value = self.value.shl(32).bitor(Int::from(component)); + + Ok(()) + } + + #[inline(always)] + fn finish(self) -> Self::Output { + self.value + } +} + +impl CellCollector for u32 { + type Output = Self; + type Builder = BuildIntCollector; + + #[inline(always)] + fn map(builder_out: as BuildCellCollector>::Output) -> Self::Output { + builder_out + } +} + +impl CellCollector for u64 { + type Output = Self; + type Builder = BuildIntCollector; + + #[inline(always)] + fn map(builder_out: as BuildCellCollector>::Output) -> Self::Output { + builder_out + } +} + +impl CellCollector for u128 { + type Output = Self; + type Builder = BuildIntCollector; + + #[inline(always)] + fn map(builder_out: as BuildCellCollector>::Output) -> Self::Output { + builder_out + } +} + +impl CellCollector for usize { + type Output = Self; + type Builder = UsizeCollector; + + #[inline(always)] + fn map(builder_out: ::Output) -> Self::Output { + builder_out + } +} + +impl CellCollector for Option { + type Builder = BuildOptionalCellCollector; + type Output = Option; + + fn map(builder_out: ::Output) -> Self::Output { + builder_out.map(T::map) + } +} + +#[allow(missing_docs)] +#[derive(Default)] +pub struct UsizeCollector { + value: usize, +} + +impl BuildCellCollector for UsizeCollector { + type Output = usize; + + #[inline(always)] + fn push(&mut self, component: u32) -> Result<(), CollectCellsError> { + use core::ops::{BitOr, Shl}; + + let shr = const { (core::mem::size_of::() - 4) * 8 }; + + if self.value >> shr != 0 { + return Err(CollectCellsError); + } + + self.value = self.value.shl(32i32).bitor(component as usize); + + Ok(()) + } + + #[inline(always)] + fn finish(self) -> Self::Output { + self.value + } +} + +/// [`BuildCellCollector`] for [`Option`]. +pub struct BuildOptionalCellCollector { + builder: T::Builder, + used: bool, +} + +impl Default for BuildOptionalCellCollector { + fn default() -> Self { + Self { builder: Default::default(), used: false } + } +} + +impl BuildCellCollector for BuildOptionalCellCollector { + type Output = Option<::Output>; + + #[inline(always)] + fn push(&mut self, component: u32) -> Result<(), CollectCellsError> { + self.used = true; + self.builder.push(component)?; + + Ok(()) + } + + #[inline(always)] + fn finish(self) -> Self::Output { + match self.used { + true => Some(self.builder.finish()), + false => None, + } + } +} + +impl + core::ops::BitOr + From> + CellCollector for core::num::Wrapping +{ + type Output = Int; + type Builder = BuildWrappingIntCollector; + + #[inline(always)] + fn map(builder_out: ::Output) -> Self::Output { + builder_out + } +} diff --git a/src/helpers.rs b/src/helpers.rs new file mode 100644 index 0000000..1ae343b --- /dev/null +++ b/src/helpers.rs @@ -0,0 +1,25 @@ +use crate::{ + nodes::{root::Root, Node}, + parsing::{aligned::AlignedParser, unaligned::UnalignedParser, NoPanic, Panic, ParserWithMode}, +}; + +/// Parser mode tuple which indicates the parser will not panic and return [`Result`]s instead. +pub type FallibleParser<'a, P> = (

>::Parser, NoPanic); +/// A node using a fallible parser. +pub type FallibleNode<'a, P> = Node<'a, FallibleParser<'a, P>>; +/// Devicetree root which uses a fallible parser. +pub type FallibleRoot<'a, P> = Root<'a, FallibleParser<'a, P>>; + +/// Indicates the underlying data is aligned to 4 bytes and the parser will +/// produce [`Result`]s instead of panicking. +pub type AlignedFallibleNode<'a> = Node<'a, (AlignedParser<'a>, NoPanic)>; +/// Indicates the underlying data is byte aligned and the parser will +/// produce [`Result`]s instead of panicking. +pub type UnalignedFallibleNode<'a> = Node<'a, (UnalignedParser<'a>, NoPanic)>; + +/// Indicates the underlying data is aligned to 4 bytes and the parser will +/// panic if invalid devicetree data is encountered. +pub type AlignedInfallibleNode<'a> = Node<'a, (AlignedParser<'a>, Panic)>; +/// Indicates the underlying data is byte aligned and the parser will +/// panic if invalid devicetree data is encountered. +pub type UnalignedInfallibleNode<'a> = Node<'a, (UnalignedParser<'a>, Panic)>; diff --git a/src/lib.rs b/src/lib.rs index c650024..048ffb9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,77 +4,124 @@ //! # `fdt` //! -//! A pure-Rust `#![no_std]` crate for parsing Flattened Devicetrees, with the goal of having a -//! very ergonomic and idiomatic API. +//! A pure-Rust `#![no_std]` crate for parsing Flattened Devicetrees, with the +//! goal of having a very ergonomic and idiomatic API. //! -//! [![crates.io](https://img.shields.io/crates/v/fdt.svg)](https://crates.io/crates/fdt) [![Documentation](https://docs.rs/fdt/badge.svg)](https://docs.rs/fdt) ![Build](https://github.com/repnop/fdt/actions/workflows/test.yml/badge.svg?branch=master&event=push) +//! [![crates.io](https://img.shields.io/crates/v/fdt.svg)](https://crates.io/crates/fdt) +//! [![Documentation](https://docs.rs/fdt/badge.svg)](https://docs.rs/fdt) +//! ![Build](https://github.com/repnop/fdt/actions/workflows/test.yml/badge.svg?branch=master&event=push) //! //! ## License //! -//! This crate is licensed under the Mozilla Public License 2.0 (see the LICENSE file). +//! This crate is licensed under the Mozilla Public License 2.0 (see the LICENSE +//! file). //! //! ## Example //! -//! ```rust,no_run +//! ```rust //! static MY_FDT: &[u8] = include_bytes!("../dtb/test.dtb"); //! //! fn main() { -//! let fdt = fdt::Fdt::new(MY_FDT).unwrap(); +//! let fdt = fdt::Fdt::new_unaligned(MY_FDT).unwrap(); +//! let root = fdt.root(); //! -//! println!("This is a devicetree representation of a {}", fdt.root().model()); -//! println!("...which is compatible with at least: {}", fdt.root().compatible().first()); -//! println!("...and has {} CPU(s)", fdt.cpus().count()); +//! println!("This is a devicetree representation of a {}", root.model()); +//! println!("...which is compatible with at least: {}", root.compatible().first()); +//! println!("...and has {} CPU(s)", root.cpus().iter().count()); //! println!( //! "...and has at least one memory location at: {:#X}\n", -//! fdt.memory().regions().next().unwrap().starting_address as usize +//! root.memory().reg().iter::().next().unwrap().unwrap().address //! ); //! -//! let chosen = fdt.chosen(); +//! let chosen = root.chosen(); //! if let Some(bootargs) = chosen.bootargs() { //! println!("The bootargs are: {:?}", bootargs); //! } //! -//! if let Some(stdout) = chosen.stdout() { -//! println!("It would write stdout to: {}", stdout.node().name); +//! if let Some(stdout) = chosen.stdout_path() { +//! println!("It would write stdout to: {}", stdout.path()); //! } //! -//! let soc = fdt.find_node("/soc"); +//! let soc = root.find_node("/soc"); //! println!("Does it have a `/soc` node? {}", if soc.is_some() { "yes" } else { "no" }); //! if let Some(soc) = soc { //! println!("...and it has the following children:"); -//! for child in soc.children() { -//! println!(" {}", child.name); +//! for child in soc.children().iter() { +//! println!(" {}", child.name()); //! } //! } //! } //! ``` #![no_std] +#![warn(missing_docs)] + +#[cfg(test)] +extern crate std; #[cfg(test)] mod tests; -pub mod node; +/// Trait and types for working with `*-cells` values. +pub mod cell_collector; +/// Helper type aliases. +pub mod helpers; +/// Devicetree node abstractions. +pub mod nodes; mod parsing; -pub mod standard_nodes; - -#[cfg(feature = "pretty-printing")] mod pretty_print; - -use node::MemoryReservation; -use parsing::{BigEndianU32, CStr, FdtData}; -use standard_nodes::{Aliases, Chosen, Cpu, Memory, MemoryRange, MemoryRegion, Root}; +/// Devicetree property abstractions. +pub mod properties; +mod util; + +use helpers::FallibleParser; +use nodes::{ + root::{AllCompatibleIter, AllNodesIter, AllNodesWithNameIter, Root}, + Node, +}; +use parsing::{ + aligned::AlignedParser, unaligned::UnalignedParser, NoPanic, Panic, ParseError, Parser, ParserWithMode, + StringsBlock, StructsBlock, +}; +// use standard_nodes::{Aliases, Chosen, Cpu, Memory, MemoryRange, MemoryRegion, Root}; + +mod sealed { + pub trait Sealed {} +} /// Possible errors when attempting to create an `Fdt` -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy)] pub enum FdtError { /// The FDT had an invalid magic value BadMagic, /// The given pointer was null BadPtr, - /// The slice passed in was too small to fit the given total size of the FDT - /// structure - BufferTooSmall, + /// The provided slice is smaller than the required size given by the header + SliceTooSmall, + /// An error was encountered during parsing + ParseError(ParseError), + /// Attempted to resolve the `phandle` value for a node, but was unable to + /// locate it. + MissingPHandleNode(u32), + /// A parent node is required. + MissingParent, + /// A required node with the given name wasn't found. + MissingRequiredNode(&'static str), + /// A required property with the given name wasn't found. + MissingRequiredProperty(&'static str), + /// Property name contained invalid characters. + InvalidPropertyValue, + /// Node name contained invalid characters. + InvalidNodeName, + /// A `-cells` property value was unable to be collected into the specified + /// type. + CollectCellsError, +} + +impl From for FdtError { + fn from(value: ParseError) -> Self { + Self::ParseError(value) + } } impl core::fmt::Display for FdtError { @@ -82,8 +129,24 @@ impl core::fmt::Display for FdtError { match self { FdtError::BadMagic => write!(f, "bad FDT magic value"), FdtError::BadPtr => write!(f, "an invalid pointer was passed"), - FdtError::BufferTooSmall => { - write!(f, "the given buffer was too small to contain a FDT header") + FdtError::SliceTooSmall => write!(f, "provided slice is too small"), + FdtError::ParseError(e) => core::fmt::Display::fmt(e, f), + FdtError::MissingPHandleNode(value) => { + write!(f, "a node containing the `phandle` property value of `{value}` was not found") + } + FdtError::MissingParent => write!(f, "node parent is not present but needed to parse a property"), + FdtError::MissingRequiredNode(name) => { + write!(f, "FDT is missing a required node `{}`", name) + } + FdtError::MissingRequiredProperty(name) => { + write!(f, "FDT node is missing a required property `{}`", name) + } + FdtError::InvalidPropertyValue => write!(f, "FDT property value is invalid"), + FdtError::InvalidNodeName => { + write!(f, "FDT node contained invalid characters or did not match the expected format") + } + FdtError::CollectCellsError => { + write!(f, "overflow occurred while collecting `#-cells` size values into the desired type") } } } @@ -95,335 +158,288 @@ impl core::fmt::Display for FdtError { /// print any useful information, if you would like a best-effort tree print /// which looks similar to `dtc`'s output, enable the `pretty-printing` feature #[derive(Clone, Copy)] -pub struct Fdt<'a> { - data: &'a [u8], +pub struct Fdt<'a, P: ParserWithMode<'a>> { + structs: StructsBlock<'a, P::Granularity>, + strings: StringsBlock<'a>, header: FdtHeader, } -impl core::fmt::Debug for Fdt<'_> { +impl<'a, P: ParserWithMode<'a>> core::fmt::Debug for Fdt<'a, P> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("Fdt").finish_non_exhaustive() + } +} + +impl<'a, P: ParserWithMode<'a>> core::fmt::Display for Fdt<'a, P> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - #[cfg(feature = "pretty-printing")] - pretty_print::print_node(f, self.root().node, 0)?; + let mut parser: (P::Parser, NoPanic) = <_>::new(self.structs.0, self.strings, self.structs); - #[cfg(not(feature = "pretty-printing"))] - f.debug_struct("Fdt").finish_non_exhaustive()?; + let Ok(node) = parser.parse_root() else { + return Err(core::fmt::Error); + }; - Ok(()) + pretty_print::print_fdt(f, Root { node }) } } +/// FDT header. #[derive(Debug, Clone, Copy)] #[repr(C)] -struct FdtHeader { +pub struct FdtHeader { /// FDT header magic - magic: BigEndianU32, + pub magic: u32, /// Total size in bytes of the FDT structure - totalsize: BigEndianU32, + pub total_size: u32, /// Offset in bytes from the start of the header to the structure block - off_dt_struct: BigEndianU32, + pub structs_offset: u32, /// Offset in bytes from the start of the header to the strings block - off_dt_strings: BigEndianU32, + pub strings_offset: u32, /// Offset in bytes from the start of the header to the memory reservation /// block - off_mem_rsvmap: BigEndianU32, + pub memory_reserve_map_offset: u32, /// FDT version - version: BigEndianU32, + pub version: u32, /// Last compatible FDT version - last_comp_version: BigEndianU32, + pub last_compatible_version: u32, /// System boot CPU ID - boot_cpuid_phys: BigEndianU32, + pub boot_cpuid: u32, /// Length in bytes of the strings block - size_dt_strings: BigEndianU32, + pub strings_size: u32, /// Length in bytes of the struct block - size_dt_struct: BigEndianU32, + pub structs_size: u32, } impl FdtHeader { fn valid_magic(&self) -> bool { - self.magic.get() == 0xd00dfeed - } - - fn struct_range(&self) -> core::ops::Range { - let start = self.off_dt_struct.get() as usize; - let end = start + self.size_dt_struct.get() as usize; - - start..end - } - - fn strings_range(&self) -> core::ops::Range { - let start = self.off_dt_strings.get() as usize; - let end = start + self.size_dt_strings.get() as usize; - - start..end - } - - fn from_bytes(bytes: &mut FdtData<'_>) -> Option { - Some(Self { - magic: bytes.u32()?, - totalsize: bytes.u32()?, - off_dt_struct: bytes.u32()?, - off_dt_strings: bytes.u32()?, - off_mem_rsvmap: bytes.u32()?, - version: bytes.u32()?, - last_comp_version: bytes.u32()?, - boot_cpuid_phys: bytes.u32()?, - size_dt_strings: bytes.u32()?, - size_dt_struct: bytes.u32()?, - }) + self.magic == 0xd00dfeed } } -impl<'a> Fdt<'a> { +impl<'a> Fdt<'a, (UnalignedParser<'a>, Panic)> { /// Construct a new `Fdt` from a byte buffer - /// - /// Note: this function does ***not*** require that the data be 4-byte - /// aligned - pub fn new(data: &'a [u8]) -> Result { - let mut stream = FdtData::new(data); - let header = FdtHeader::from_bytes(&mut stream).ok_or(FdtError::BufferTooSmall)?; + pub fn new_unaligned(data: &'a [u8]) -> Result { + let mut parser = UnalignedParser::new(data, StringsBlock(&[]), StructsBlock(&[])); + let header = parser.parse_header()?; + + let strings_end = (header.strings_offset + header.strings_size) as usize; + let structs_end = (header.structs_offset + header.structs_size) as usize; + if data.len() < strings_end || data.len() < structs_end { + return Err(FdtError::SliceTooSmall); + } + + let strings = StringsBlock(&data[header.strings_offset as usize..][..header.strings_size as usize]); + let structs = StructsBlock(&data[header.structs_offset as usize..][..header.structs_size as usize]); if !header.valid_magic() { return Err(FdtError::BadMagic); - } else if data.len() < header.totalsize.get() as usize { - return Err(FdtError::BufferTooSmall); + } else if data.len() < header.total_size as usize { + return Err(FdtError::SliceTooSmall); } - Ok(Self { data, header }) + Ok(Self { header, structs, strings }) } /// # Safety /// This function performs a read to verify the magic value. If the pointer /// is invalid this can result in undefined behavior. - /// - /// Note: this function does ***not*** require that the data be 4-byte - /// aligned - pub unsafe fn from_ptr(ptr: *const u8) -> Result { + pub unsafe fn from_ptr_unaligned(ptr: *const u8) -> Result { if ptr.is_null() { return Err(FdtError::BadPtr); } let tmp_header = core::slice::from_raw_parts(ptr, core::mem::size_of::()); - let real_size = - FdtHeader::from_bytes(&mut FdtData::new(tmp_header)).unwrap().totalsize.get() as usize; + let real_size = usize::try_from( + UnalignedParser::new(tmp_header, StringsBlock(&[]), StructsBlock(&[])).parse_header()?.total_size, + ) + .map_err(|_| ParseError::NumericConversionError)?; - Self::new(core::slice::from_raw_parts(ptr, real_size)) + Self::new_unaligned(core::slice::from_raw_parts(ptr, real_size)) } +} - /// Return reference to raw data. This can be used to obtain the original pointer passed to - /// [Fdt::from_ptr]. - /// - /// # Example - /// ``` - /// # let fdt_ref: &[u8] = include_bytes!("../dtb/test.dtb"); - /// # let original_pointer = fdt_ref.as_ptr(); - /// let fdt = unsafe{fdt::Fdt::from_ptr(original_pointer)}.unwrap(); - /// assert_eq!(fdt.raw_data().as_ptr(), original_pointer); - /// ``` - pub fn raw_data(&self) -> &'a [u8] { - self.data - } +impl<'a> Fdt<'a, (AlignedParser<'a>, Panic)> { + /// Construct a new `Fdt` from a `u32`-aligned buffer + pub fn new(data: &'a [u32]) -> Result { + let mut parser = AlignedParser::new(data, StringsBlock(&[]), StructsBlock(&[])); + let header = parser.parse_header()?; - /// Return the `/aliases` node, if one exists - pub fn aliases(&self) -> Option> { - Some(Aliases { - node: node::find_node(&mut FdtData::new(self.structs_block()), "/aliases", self, None)?, - header: self, - }) - } + let strings_end = (header.strings_offset + header.strings_size) as usize / 4; + let structs_end = (header.structs_offset + header.structs_size) as usize / 4; + if data.len() < strings_end || data.len() < structs_end { + return Err(FdtError::SliceTooSmall); + } - /// Searches for the `/chosen` node, which is always available - pub fn chosen(&self) -> Chosen<'_, 'a> { - node::find_node(&mut FdtData::new(self.structs_block()), "/chosen", self, None) - .map(|node| Chosen { node }) - .expect("/chosen is required") - } + let strings_start = header.strings_offset as usize; + let strings_end = strings_start + header.strings_size as usize; + let strings = StringsBlock( + util::cast_slice(data) + .get(strings_start..strings_end) + .ok_or(FdtError::ParseError(ParseError::UnexpectedEndOfData))?, + ); - /// Return the `/cpus` node, which is always available - pub fn cpus(&self) -> impl Iterator> { - let parent = self.find_node("/cpus").expect("/cpus is a required node"); + let structs_start = header.structs_offset as usize / 4; + let structs_end = structs_start + (header.structs_size as usize / 4); + let structs = StructsBlock( + data.get(structs_start..structs_end).ok_or(FdtError::ParseError(ParseError::UnexpectedEndOfData))?, + ); - parent - .children() - .filter(|c| c.name.split('@').next().unwrap() == "cpu") - .map(move |cpu| Cpu { parent, node: cpu }) - } + if !header.valid_magic() { + return Err(FdtError::BadMagic); + } else if data.len() < (header.total_size / 4) as usize { + return Err(FdtError::ParseError(ParseError::UnexpectedEndOfData)); + } - /// Returns the memory node, which is always available - pub fn memory(&self) -> Memory<'_, 'a> { - Memory { node: self.find_node("/memory").expect("requires memory node") } + Ok(Self { header, strings, structs }) } - /// Returns an iterator over the memory reservations - pub fn memory_reservations(&self) -> impl Iterator + 'a { - let mut stream = FdtData::new(&self.data[self.header.off_mem_rsvmap.get() as usize..]); - let mut done = false; - - core::iter::from_fn(move || { - if stream.is_empty() || done { - return None; - } - - let res = MemoryReservation::from_bytes(&mut stream)?; + /// # Safety + /// This function performs a read to verify the magic value. If the pointer + /// is invalid this can result in undefined behavior. + pub unsafe fn from_ptr(ptr: *const u32) -> Result { + if ptr.is_null() { + return Err(FdtError::BadPtr); + } - if res.address() as usize == 0 && res.size() == 0 { - done = true; - return None; - } + let tmp_header = core::slice::from_raw_parts(ptr, core::mem::size_of::()); + let real_size = usize::try_from( + AlignedParser::new(tmp_header, StringsBlock(&[]), StructsBlock(&[])).parse_header()?.total_size, + ) + .map_err(|_| ParseError::NumericConversionError)?; - Some(res) - }) + Self::new(core::slice::from_raw_parts(ptr, real_size)) } +} - /// Return the root (`/`) node, which is always available - pub fn root(&self) -> Root<'_, 'a> { - Root { node: self.find_node("/").expect("/ is a required node") } +impl<'a> Fdt<'a, (UnalignedParser<'a>, NoPanic)> { + /// Construct a new `Fdt` from a byte buffer + pub fn new_unaligned_fallible(data: &'a [u8]) -> Result { + let Fdt { header, strings, structs } = Fdt::new_unaligned(data)?; + Ok(Self { header, strings, structs }) } - /// Returns the first node that matches the node path, if you want all that - /// match the path, use `find_all_nodes`. This will automatically attempt to - /// resolve aliases if `path` is not found. - /// - /// Node paths must begin with a leading `/` and are ASCII only. Passing in - /// an invalid node path or non-ASCII node name in the path will return - /// `None`, as they will not be found within the devicetree structure. - /// - /// Note: if the address of a node name is left out, the search will find - /// the first node that has a matching name, ignoring the address portion if - /// it exists. - pub fn find_node(&self, path: &str) -> Option> { - let node = node::find_node(&mut FdtData::new(self.structs_block()), path, self, None); - node.or_else(|| self.aliases()?.resolve_node(path)) + /// # Safety + /// This function performs a read to verify the magic value. If the pointer + /// is invalid this can result in undefined behavior. + pub unsafe fn from_ptr_unaligned_fallible(ptr: *const u8) -> Result { + let Fdt { header, strings, structs } = Fdt::from_ptr_unaligned(ptr)?; + Ok(Self { header, strings, structs }) } +} - /// Searches for a node which contains a `compatible` property and contains - /// one of the strings inside of `with` - pub fn find_compatible(&self, with: &[&str]) -> Option> { - self.all_nodes().find(|n| { - n.compatible().and_then(|compats| compats.all().find(|c| with.contains(c))).is_some() - }) +impl<'a> Fdt<'a, (AlignedParser<'a>, NoPanic)> { + /// Construct a new `Fdt` from a `u32`-aligned buffer which won't panic on invalid data + pub fn new_fallible(data: &'a [u32]) -> Result { + let Fdt { header, strings, structs } = Fdt::new(data)?; + Ok(Self { header, strings, structs }) } - /// Searches for the given `phandle` - pub fn find_phandle(&self, phandle: u32) -> Option> { - self.all_nodes().find(|n| { - n.properties() - .find(|p| p.name == "phandle") - .and_then(|p| Some(BigEndianU32::from_bytes(p.value)?.get() == phandle)) - .unwrap_or(false) - }) + /// # Safety + /// This function performs a read to verify the magic value. If the pointer + /// is invalid this can result in undefined behavior. + pub unsafe fn from_ptr_fallible(ptr: *const u32) -> Result { + let Fdt { header, strings, structs } = Fdt::from_ptr(ptr)?; + Ok(Self { header, strings, structs }) } +} - /// Returns an iterator over all of the available nodes with the given path. - /// This does **not** attempt to find any node with the same name as the - /// provided path, if you're looking to do that, [`Fdt::all_nodes`] will - /// allow you to iterate over each node's name and filter for the desired - /// node(s). - /// - /// For example: - /// ```rust - /// static MY_FDT: &[u8] = include_bytes!("../dtb/test.dtb"); - /// - /// let fdt = fdt::Fdt::new(MY_FDT).unwrap(); - /// - /// for node in fdt.find_all_nodes("/soc/virtio_mmio") { - /// println!("{}", node.name); - /// } - /// ``` - /// prints: - /// ```notrust - /// virtio_mmio@10008000 - /// virtio_mmio@10007000 - /// virtio_mmio@10006000 - /// virtio_mmio@10005000 - /// virtio_mmio@10004000 - /// virtio_mmio@10003000 - /// virtio_mmio@10002000 - /// virtio_mmio@10001000 - /// ``` - pub fn find_all_nodes(&self, path: &'a str) -> impl Iterator> { - let mut done = false; - let only_root = path == "/"; - let valid_path = path.chars().fold(0, |acc, c| acc + if c == '/' { 1 } else { 0 }) >= 1; - - let mut path_split = path.rsplitn(2, '/'); - let child_name = path_split.next().unwrap(); - let parent = match path_split.next() { - Some("") => Some(self.root().node), - Some(s) => node::find_node(&mut FdtData::new(self.structs_block()), s, self, None), - None => None, - }; +impl<'a, P: ParserWithMode<'a>> Fdt<'a, P> { + #[inline(always)] + fn fallible_root(&self) -> Result>, FdtError> { + let mut parser = FallibleParser::<'a, P>::new(self.structs.0, self.strings, self.structs); + Ok(Root { node: parser.parse_root()? }) + } - let (parent, bad_parent) = match parent { - Some(parent) => (parent, false), - None => (self.find_node("/").unwrap(), true), - }; + /// Return the root (`/`) node, which is always available + pub fn root(&self) -> P::Output> { + let mut parser = P::new(self.structs.0, self.strings, self.structs); + P::to_output(parser.parse_root().map(|node| Root { node: node.fallible() })) + } - let mut child_iter = parent.children(); + /// Returns an iterator over all of the strings inside of the strings block + pub fn strings(&self) -> impl Iterator { + let mut block = self.strings_block(); core::iter::from_fn(move || { - if done || !valid_path || bad_parent { + if block.is_empty() { return None; } - if only_root { - done = true; - return self.find_node("/"); - } - - let mut ret = None; + let cstr = core::ffi::CStr::from_bytes_until_nul(block).ok()?; - #[allow(clippy::while_let_on_iterator)] - while let Some(child) = child_iter.next() { - if child.name.split('@').next()? == child_name { - ret = Some(child); - break; - } - } + block = &block[cstr.to_bytes().len() + 1..]; - ret + cstr.to_str().ok() }) } - /// Returns an iterator over all of the nodes in the devicetree, depth-first - pub fn all_nodes(&self) -> impl Iterator> { - node::all_nodes(self) + /// Convenience wrapper around [`Root::find_all_nodes_with_name`]. Returns + /// an iterator that yields every node with the name that matches `name` in + /// depth-first order. + #[track_caller] + pub fn find_all_nodes_with_name<'b>(&self, name: &'b str) -> P::Output> { + P::to_output(self.fallible_root().and_then(|root| { + root.find_all_nodes_with_name(name).map(|i| AllNodesWithNameIter { iter: i.iter, name: i.name }) + })) } - /// Returns an iterator over all of the strings inside of the strings block - pub fn strings(&self) -> impl Iterator { - let mut block = self.strings_block(); - - core::iter::from_fn(move || { - if block.is_empty() { - return None; - } + /// Convenience wrapper around [`Root::find_node_by_name`]. Attempt to find + /// a node with the given name, returning the first node with a name that + /// matches `name` in depth-first order. + #[track_caller] + pub fn find_node_by_name(&self, name: &str) -> P::Output>> { + P::to_output(self.fallible_root().and_then(|root| Ok(root.find_node_by_name(name)?.map(|n| n.alt())))) + } - let cstr = CStr::new(block)?; + /// Convenience wrapper around [`Root::find_node`]. Attempt to find a node + /// with the given path (with an optional unit address, defaulting to the + /// first matching name if omitted). If you only have the node name but not + /// the path, use [`Root::find_node_by_name`] instead. + #[track_caller] + pub fn find_node(&self, path: &str) -> P::Output>> { + P::to_output(self.fallible_root().and_then(|root| Ok(root.find_node(path)?.map(|n| n.alt())))) + } - block = &block[cstr.len() + 1..]; + /// Convenience wrapper around [`Root::all_compatible`]. Returns an iterator over + /// every node within the devicetree which is compatible with at least one + /// of the compatible strings contained within `with`. + #[track_caller] + pub fn all_compatible<'b>(&self, with: &'b [&str]) -> P::Output> { + P::to_output( + self.fallible_root() + .and_then(|root| root.all_compatible(with).map(|i| AllCompatibleIter { iter: i.iter, with: i.with })), + ) + } - cstr.as_str() - }) + /// Convenience wrapper around [`Root::all_nodes`]. Returns an iterator over + /// each node in the tree, depth-first, along with its depth in the tree. + #[track_caller] + pub fn all_nodes(&self) -> P::Output> { + P::to_output(self.fallible_root().and_then(|root| { + root.all_nodes().map(|i| AllNodesIter { + parser: P::new(i.parser.data(), i.parser.strings(), i.parser.structs()), + parent_index: i.parent_index, + parents: i.parents, + }) + })) } /// Total size of the devicetree in bytes pub fn total_size(&self) -> usize { - self.header.totalsize.get() as usize - } - - fn cstr_at_offset(&self, offset: usize) -> CStr<'a> { - CStr::new(&self.strings_block()[offset..]).expect("no null terminating string on C str?") + self.header.total_size as usize } - fn str_at_offset(&self, offset: usize) -> &'a str { - self.cstr_at_offset(offset).as_str().expect("not utf-8 cstr") + /// Header describing this devicetree. + pub fn header(&self) -> &FdtHeader { + &self.header } - fn strings_block(&self) -> &'a [u8] { - &self.data[self.header.strings_range()] + /// Slice pointing to the raw strings block. + pub fn strings_block(&self) -> &'a [u8] { + self.strings.0 } - fn structs_block(&self) -> &'a [u8] { - &self.data[self.header.struct_range()] + /// Slice pointing to the raw structs block. + pub fn structs_block(&self) -> &'a [P::Granularity] { + self.structs.0 } } diff --git a/src/node.rs b/src/node.rs deleted file mode 100644 index 3e5e215..0000000 --- a/src/node.rs +++ /dev/null @@ -1,633 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public License, -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at https://mozilla.org/MPL/2.0/. - -use crate::{ - parsing::{BigEndianU32, BigEndianU64, CStr, FdtData}, - standard_nodes::{Compatible, MemoryRange, MemoryRegion}, - Fdt, -}; - -const FDT_BEGIN_NODE: u32 = 1; -const FDT_END_NODE: u32 = 2; -const FDT_PROP: u32 = 3; -pub(crate) const FDT_NOP: u32 = 4; -const FDT_END: u32 = 5; - -#[derive(Debug, Clone, Copy)] -#[repr(C)] -struct FdtProperty { - len: BigEndianU32, - name_offset: BigEndianU32, -} - -impl FdtProperty { - fn from_bytes(bytes: &mut FdtData<'_>) -> Option { - let len = bytes.u32()?; - let name_offset = bytes.u32()?; - - Some(Self { len, name_offset }) - } -} - -/// A devicetree node -#[derive(Debug, Clone, Copy)] -pub struct FdtNode<'b, 'a: 'b> { - pub name: &'a str, - pub(crate) header: &'b Fdt<'a>, - props: &'a [u8], - parent_props: Option<&'a [u8]>, -} - -#[cfg(feature = "pretty-printing")] -impl core::fmt::Display for FdtNode<'_, '_> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - crate::pretty_print::print_node(f, *self, 0)?; - Ok(()) - } -} - -impl<'b, 'a: 'b> FdtNode<'b, 'a> { - fn new( - name: &'a str, - header: &'b Fdt<'a>, - props: &'a [u8], - parent_props: Option<&'a [u8]>, - ) -> Self { - Self { name, header, props, parent_props } - } - - /// Returns an iterator over the available properties of the node - pub fn properties(self) -> impl Iterator> + 'b { - let mut stream = FdtData::new(self.props); - let mut done = false; - - core::iter::from_fn(move || { - if stream.is_empty() || done { - return None; - } - - while stream.peek_u32()?.get() == FDT_NOP { - stream.skip(4); - } - - if stream.peek_u32()?.get() == FDT_PROP { - Some(NodeProperty::parse(&mut stream, self.header)) - } else { - done = true; - None - } - }) - } - - /// Attempts to find the a property by its name - pub fn property(self, name: &str) -> Option> { - self.properties().find(|p| p.name == name) - } - - /// Returns an iterator over the children of the current node - pub fn children(self) -> impl Iterator> { - let mut stream = FdtData::new(self.props); - - while stream.peek_u32().unwrap().get() == FDT_NOP { - stream.skip(4); - } - - while stream.peek_u32().unwrap().get() == FDT_PROP { - NodeProperty::parse(&mut stream, self.header); - } - - let mut done = false; - - core::iter::from_fn(move || { - if stream.is_empty() || done { - return None; - } - - while stream.peek_u32()?.get() == FDT_NOP { - stream.skip(4); - } - - if stream.peek_u32()?.get() == FDT_BEGIN_NODE { - let origin = stream.remaining(); - let ret = { - stream.skip(4); - let unit_name = CStr::new(stream.remaining()).expect("unit name").as_str()?; - let full_name_len = unit_name.len() + 1; - stream.skip(full_name_len); - - if full_name_len % 4 != 0 { - stream.skip(4 - (full_name_len % 4)); - } - - Some(Self::new(unit_name, self.header, stream.remaining(), Some(self.props))) - }; - - stream = FdtData::new(origin); - - skip_current_node(&mut stream, self.header); - - ret - } else { - done = true; - None - } - }) - } - - /// `reg` property - /// - /// Important: this method assumes that the value(s) inside the `reg` - /// property represent CPU-addressable addresses that are able to fit within - /// the platform's pointer size (e.g. `#address-cells` and `#size-cells` are - /// less than or equal to 2 for a 64-bit platform). If this is not the case - /// or you're unsure of whether this applies to the node, it is recommended - /// to use the [`FdtNode::property`] method to extract the raw value slice - /// or use the provided [`FdtNode::raw_reg`] helper method to give you an - /// iterator over the address and size slices. One example of where this - /// would return `None` for a node is a `pci` child node which contains the - /// PCI address information in the `reg` property, of which the address has - /// an `#address-cells` value of 3. - pub fn reg(self) -> Option + 'a> { - let sizes = self.parent_cell_sizes(); - if sizes.address_cells > 2 || sizes.size_cells > 2 { - return None; - } - - let mut reg = None; - for prop in self.properties() { - if prop.name == "reg" { - let mut stream = FdtData::new(prop.value); - reg = Some(core::iter::from_fn(move || { - let starting_address = match sizes.address_cells { - 1 => stream.u32()?.get() as usize, - 2 => stream.u64()?.get() as usize, - _ => return None, - } as *const u8; - - let size = match sizes.size_cells { - 0 => None, - 1 => Some(stream.u32()?.get() as usize), - 2 => Some(stream.u64()?.get() as usize), - _ => return None, - }; - - Some(MemoryRegion { starting_address, size }) - })); - break; - } - } - - reg - } - - pub fn ranges(self) -> Option + 'a> { - let sizes = self.cell_sizes(); - let parent_sizes = self.parent_cell_sizes(); - - if sizes.address_cells > 3 || sizes.size_cells > 2 || parent_sizes.size_cells > 2 { - return None; - } - - let mut ranges = None; - for prop in self.properties() { - if prop.name == "ranges" { - let mut stream = FdtData::new(prop.value); - ranges = Some(core::iter::from_fn(move || { - let (child_bus_address_hi, child_bus_address) = match sizes.address_cells { - 1 => (0, stream.u32()?.get() as usize), - 2 => (0, stream.u64()?.get() as usize), - 3 => (stream.u32()?.get(), stream.u64()?.get() as usize), - _ => return None, - }; - - let parent_bus_address = match parent_sizes.address_cells { - 1 => stream.u32()?.get() as usize, - 2 => stream.u64()?.get() as usize, - _ => return None, - }; - - let size = match sizes.size_cells { - 1 => stream.u32()?.get() as usize, - 2 => stream.u64()?.get() as usize, - _ => return None, - }; - - Some(MemoryRange { - child_bus_address, - child_bus_address_hi, - parent_bus_address, - size, - }) - })); - break; - } - } - - ranges - } - - /// Convenience method that provides an iterator over the raw bytes for the - /// address and size values inside of the `reg` property - pub fn raw_reg(self) -> Option> + 'a> { - let sizes = self.parent_cell_sizes(); - - if let Some(prop) = self.property("reg") { - let mut stream = FdtData::new(prop.value); - return Some(core::iter::from_fn(move || { - Some(RawReg { - address: stream.take(sizes.address_cells * 4)?, - size: stream.take(sizes.size_cells * 4)?, - }) - })); - } - - None - } - - /// `compatible` property - pub fn compatible(self) -> Option> { - let mut s = None; - for prop in self.properties() { - if prop.name == "compatible" { - s = Some(Compatible { data: prop.value }); - } - } - - s - } - - /// Cell sizes for child nodes - pub fn cell_sizes(self) -> CellSizes { - let mut cell_sizes = CellSizes::default(); - - for property in self.properties() { - match property.name { - "#address-cells" => { - cell_sizes.address_cells = BigEndianU32::from_bytes(property.value) - .expect("not enough bytes for #address-cells value") - .get() as usize; - } - "#size-cells" => { - cell_sizes.size_cells = BigEndianU32::from_bytes(property.value) - .expect("not enough bytes for #size-cells value") - .get() as usize; - } - _ => {} - } - } - - cell_sizes - } - - /// Searches for the interrupt parent, if the node contains one - pub fn interrupt_parent(self) -> Option> { - self.properties() - .find(|p| p.name == "interrupt-parent") - .and_then(|p| self.header.find_phandle(BigEndianU32::from_bytes(p.value)?.get())) - } - - /// `#interrupt-cells` property - pub fn interrupt_cells(self) -> Option { - let mut interrupt_cells = None; - - if let Some(prop) = self.property("#interrupt-cells") { - interrupt_cells = BigEndianU32::from_bytes(prop.value).map(|n| n.get() as usize) - } - - interrupt_cells - } - - /// `interrupts` property - pub fn interrupts(self) -> Option + 'a> { - let sizes = self.parent_interrupt_cells()?; - - let mut interrupt = None; - for prop in self.properties() { - if prop.name == "interrupts" { - let mut stream = FdtData::new(prop.value); - interrupt = Some(core::iter::from_fn(move || { - let interrupt = match sizes { - 1 => stream.u32()?.get() as usize, - 2 => stream.u64()?.get() as usize, - _ => return None, - }; - - Some(interrupt) - })); - break; - } - } - - interrupt - } - - /// `interrupts-extended` property - pub fn interrupts_extended(self) -> Option + 'a> { - let sizes = self.interrupt_cells()?; - - let mut interrupt = None; - for prop in self.properties() { - if prop.name == "interrupts-extended" { - let mut stream = FdtData::new(prop.value); - interrupt = Some(core::iter::from_fn(move || { - let interrupt = match sizes { - 1 => stream.u32()?.get() as usize, - 2 => stream.u64()?.get() as usize, - _ => return None, - }; - - Some(interrupt) - })); - break; - } - } - - interrupt - } - - pub(crate) fn parent_cell_sizes(self) -> CellSizes { - let mut cell_sizes = CellSizes::default(); - - if let Some(parent) = self.parent_props { - let parent = - FdtNode { name: "", props: parent, header: self.header, parent_props: None }; - cell_sizes = parent.cell_sizes(); - } - - cell_sizes - } - - pub(crate) fn parent_interrupt_cells(self) -> Option { - let mut interrupt_cells = None; - let parent = self - .property("interrupt-parent") - .and_then(|p| self.header.find_phandle(BigEndianU32::from_bytes(p.value)?.get())) - .or_else(|| { - Some(FdtNode { - name: "", - props: self.parent_props?, - header: self.header, - parent_props: None, - }) - }); - - if let Some(size) = parent.and_then(|parent| parent.interrupt_cells()) { - interrupt_cells = Some(size); - } - - interrupt_cells - } -} - -/// The number of cells (big endian u32s) that addresses and sizes take -#[derive(Debug, Clone, Copy)] -pub struct CellSizes { - /// Size of values representing an address - pub address_cells: usize, - /// Size of values representing a size - pub size_cells: usize, -} - -impl Default for CellSizes { - fn default() -> Self { - CellSizes { address_cells: 2, size_cells: 1 } - } -} - -/// A raw `reg` property value set -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct RawReg<'a> { - /// Big-endian encoded bytes making up the address portion of the property. - /// Length will always be a multiple of 4 bytes. - pub address: &'a [u8], - /// Big-endian encoded bytes making up the size portion of the property. - /// Length will always be a multiple of 4 bytes. - pub size: &'a [u8], -} - -pub(crate) fn find_node<'b, 'a: 'b>( - stream: &mut FdtData<'a>, - name: &str, - header: &'b Fdt<'a>, - parent_props: Option<&'a [u8]>, -) -> Option> { - let mut parts = name.splitn(2, '/'); - let looking_for = parts.next()?; - - stream.skip_nops(); - - let curr_data = stream.remaining(); - - match stream.u32()?.get() { - FDT_BEGIN_NODE => {} - _ => return None, - } - - let unit_name = CStr::new(stream.remaining()).expect("unit name C str").as_str()?; - - let full_name_len = unit_name.len() + 1; - skip_4_aligned(stream, full_name_len); - - let looking_contains_addr = looking_for.contains('@'); - let addr_name_same = unit_name == looking_for; - let base_name_same = unit_name.split('@').next()? == looking_for; - - if (looking_contains_addr && !addr_name_same) || (!looking_contains_addr && !base_name_same) { - *stream = FdtData::new(curr_data); - skip_current_node(stream, header); - - return None; - } - - let next_part = match parts.next() { - None | Some("") => { - return Some(FdtNode::new(unit_name, header, stream.remaining(), parent_props)) - } - Some(part) => part, - }; - - stream.skip_nops(); - - let parent_props = Some(stream.remaining()); - - while stream.peek_u32()?.get() == FDT_PROP { - let _ = NodeProperty::parse(stream, header); - } - - while stream.peek_u32()?.get() == FDT_BEGIN_NODE { - if let Some(p) = find_node(stream, next_part, header, parent_props) { - return Some(p); - } - } - - stream.skip_nops(); - - if stream.u32()?.get() != FDT_END_NODE { - return None; - } - - None -} - -// FIXME: this probably needs refactored -pub(crate) fn all_nodes<'b, 'a: 'b>(header: &'b Fdt<'a>) -> impl Iterator> { - let mut stream = FdtData::new(header.structs_block()); - let mut done = false; - let mut parents: [&[u8]; 64] = [&[]; 64]; - let mut parent_index = 0; - - core::iter::from_fn(move || { - if stream.is_empty() || done { - return None; - } - - while stream.peek_u32()?.get() == FDT_END_NODE { - parent_index -= 1; - stream.skip(4); - } - - if stream.peek_u32()?.get() == FDT_END { - done = true; - return None; - } - - while stream.peek_u32()?.get() == FDT_NOP { - stream.skip(4); - } - - match stream.u32()?.get() { - FDT_BEGIN_NODE => {} - _ => return None, - } - - let unit_name = CStr::new(stream.remaining()).expect("unit name C str").as_str().unwrap(); - let full_name_len = unit_name.len() + 1; - skip_4_aligned(&mut stream, full_name_len); - - let curr_node = stream.remaining(); - - parent_index += 1; - parents[parent_index] = curr_node; - - while stream.peek_u32()?.get() == FDT_NOP { - stream.skip(4); - } - - while stream.peek_u32()?.get() == FDT_PROP { - NodeProperty::parse(&mut stream, header); - } - - Some(FdtNode { - name: if unit_name.is_empty() { "/" } else { unit_name }, - header, - parent_props: match parent_index { - 1 => None, - _ => Some(parents[parent_index - 1]), - }, - props: curr_node, - }) - }) -} - -pub(crate) fn skip_current_node<'a>(stream: &mut FdtData<'a>, header: &Fdt<'a>) { - assert_eq!(stream.u32().unwrap().get(), FDT_BEGIN_NODE, "bad node"); - - let unit_name = CStr::new(stream.remaining()).expect("unit_name C str").as_str().unwrap(); - let full_name_len = unit_name.len() + 1; - skip_4_aligned(stream, full_name_len); - - while stream.peek_u32().unwrap().get() == FDT_PROP { - NodeProperty::parse(stream, header); - } - - while stream.peek_u32().unwrap().get() == FDT_BEGIN_NODE { - skip_current_node(stream, header); - } - - stream.skip_nops(); - - assert_eq!(stream.u32().unwrap().get(), FDT_END_NODE, "bad node"); -} - -/// A node property -#[derive(Debug, Clone, Copy)] -pub struct NodeProperty<'a> { - /// Property name - pub name: &'a str, - /// Property value - pub value: &'a [u8], -} - -impl<'a> NodeProperty<'a> { - /// Attempt to parse the property value as a `usize` - pub fn as_usize(self) -> Option { - match self.value.len() { - 4 => BigEndianU32::from_bytes(self.value).map(|i| i.get() as usize), - 8 => BigEndianU64::from_bytes(self.value).map(|i| i.get() as usize), - _ => None, - } - } - - /// Attempt to parse the property value as a `&str` - pub fn as_str(self) -> Option<&'a str> { - core::str::from_utf8(self.value).map(|s| s.trim_end_matches('\0')).ok() - } - - /// Attempts to parse the property value as a list of [`&str`]. - pub fn iter_str(self) -> impl Iterator + 'a { - let mut s = self.as_str().map(|s| s.split('\0')); - - core::iter::from_fn(move || match s.as_mut() { - Some(s) => s.next(), - None => None, - }) - } - - fn parse(stream: &mut FdtData<'a>, header: &Fdt<'a>) -> Self { - match stream.u32().unwrap().get() { - FDT_PROP => {} - other => panic!("bad prop, tag: {}", other), - } - - let prop = FdtProperty::from_bytes(stream).expect("FDT property"); - let data_len = prop.len.get() as usize; - - let data = &stream.remaining()[..data_len]; - - skip_4_aligned(stream, data_len); - - NodeProperty { name: header.str_at_offset(prop.name_offset.get() as usize), value: data } - } -} - -/// A memory reservation -#[derive(Debug)] -#[repr(C)] -pub struct MemoryReservation { - pub(crate) address: BigEndianU64, - pub(crate) size: BigEndianU64, -} - -impl MemoryReservation { - /// Pointer representing the memory reservation address - pub fn address(&self) -> *const u8 { - self.address.get() as usize as *const u8 - } - - /// Size of the memory reservation - pub fn size(&self) -> usize { - self.size.get() as usize - } - - pub(crate) fn from_bytes(bytes: &mut FdtData<'_>) -> Option { - let address = bytes.u64()?; - let size = bytes.u64()?; - - Some(Self { address, size }) - } -} - -fn skip_4_aligned(stream: &mut FdtData<'_>, len: usize) { - stream.skip((len + 3) & !0x3); -} diff --git a/src/nodes.rs b/src/nodes.rs new file mode 100644 index 0000000..1d21fbf --- /dev/null +++ b/src/nodes.rs @@ -0,0 +1,668 @@ +/// `/aliases` node. +pub mod aliases; +/// Parameters chosen or specified by the system firmware at run time. +pub mod chosen; +/// Description of the CPUs available on the system. +pub mod cpus; +/// Memory region nodes and properties. +pub mod memory; +/// Root devicetree node type and helpers. +pub mod root; + +use crate::{ + helpers::FallibleNode, + parsing::{ + aligned::AlignedParser, BigEndianToken, NoPanic, Panic, PanicMode, ParseError, Parser, ParserWithMode, + StringsBlock, StructsBlock, + }, + properties::{ + ranges::Ranges, + reg::Reg, + values::{InvalidPropertyValue, PropertyValue}, + Property, + }, + FdtError, +}; +use root::Root; + +#[macro_export] +#[doc(hidden)] +macro_rules! tryblock { + ($errty:ty, $block:block) => {{ + (|| -> Result<_, $errty> { $block })() + }}; + ($block:block) => {{ + (|| -> Result<_, $crate::FdtError> { $block })() + }}; +} + +/// Trait for extracting a [`Node`] from a wrapper type. +pub trait AsNode<'a, P: ParserWithMode<'a>> { + #[allow(missing_docs)] + fn as_node(&self) -> Node<'a, P>; +} + +/// A node name that can searched with. +#[derive(Debug, Clone, Copy)] +pub enum SearchableNodeName<'a> { + /// Node name without the unit address + Base(&'a str), + /// Node name with the unit address + WithUnitAddress(NodeName<'a>), +} + +/// Convert from a type that can potentially represent a node name that is able +/// to be searched for during lookup operations. +/// +/// Currently, two type impls are defined on types other than +/// [`SearchableNodeName`]: +/// 1. [`NodeName`]: corresponds directly to a +/// [`SearchableNodeName::WithUnitAddress`]. +/// 2. [`&str`]: attempts to parse the `str` as `name@unit-address`, +/// corresponding to [`SearchableNodeName::WithUnitAddress`], or as just a +/// base node name with no specified unit address, which will resolve +/// to the first node with that base name found. +pub trait IntoSearchableNodeName<'a>: Sized + crate::sealed::Sealed { + #[allow(missing_docs)] + fn into_searchable_node_name(self) -> SearchableNodeName<'a>; +} + +impl crate::sealed::Sealed for SearchableNodeName<'_> {} +impl<'a> IntoSearchableNodeName<'a> for SearchableNodeName<'a> { + fn into_searchable_node_name(self) -> SearchableNodeName<'a> { + self + } +} + +impl crate::sealed::Sealed for NodeName<'_> {} +impl<'a> IntoSearchableNodeName<'a> for NodeName<'a> { + fn into_searchable_node_name(self) -> SearchableNodeName<'a> { + SearchableNodeName::WithUnitAddress(self) + } +} + +impl crate::sealed::Sealed for &'_ str {} +impl<'a> IntoSearchableNodeName<'a> for &'a str { + fn into_searchable_node_name(self) -> SearchableNodeName<'a> { + match self.rsplit_once('@') { + Some((base, unit_address)) => { + SearchableNodeName::WithUnitAddress(NodeName { name: base, unit_address: Some(unit_address) }) + } + None => SearchableNodeName::Base(self), + } + } +} + +/// A node name, split into its component parts. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct NodeName<'a> { + /// Node name. + pub name: &'a str, + /// Optional unit address specified after the `@`. + pub unit_address: Option<&'a str>, +} + +impl<'a> NodeName<'a> { + /// Create a new [`NodeName`] from its raw parts. + pub fn new(name: &'a str, unit_address: Option<&'a str>) -> Self { + Self { name, unit_address } + } +} + +impl core::fmt::Display for NodeName<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self.unit_address { + Some(ua) => write!(f, "{}@{}", self.name, ua), + None => write!(f, "{}", self.name), + } + } +} + +/// A generic devicetree node. +pub struct Node<'a, P: ParserWithMode<'a>> { + pub(crate) this: &'a RawNode<

>::Granularity>, + pub(crate) parent: Option<&'a RawNode<

>::Granularity>>, + pub(crate) strings: StringsBlock<'a>, + pub(crate) structs: StructsBlock<'a,

>::Granularity>, + pub(crate) _mode: core::marker::PhantomData<*mut P>, +} + +impl<'a, P: ParserWithMode<'a>> Node<'a, P> { + /// Change the type of this node's [`PanicMode`] to [`NoPanic`]. + #[inline(always)] + pub fn fallible(self) -> FallibleNode<'a, P> { + self.alt() + } + + /// Helper function for changing the [`PanicMode`] of this node. + #[inline(always)] + pub fn alt>(self) -> Node<'a, P2> { + Node { + this: self.this, + parent: self.parent, + strings: self.strings, + structs: self.structs, + _mode: core::marker::PhantomData, + } + } + + pub(crate) fn make_root>( + self, + ) -> Result, FdtError> { + let mut parser = <(P2, NoPanic)>::new(self.structs.0, self.strings, self.structs); + parser.parse_root().map(|node| Root { node }) + } + + /// The name of this node along with the optional unit address. + #[inline] + #[track_caller] + pub fn name(&self) ->

::Output> { + P::to_output( + P::new(&self.this.0, self.strings, self.structs) + .advance_cstr() + .and_then(|s| s.to_str().map_err(|_| FdtError::ParseError(ParseError::InvalidCStrValue))) + .map(|s| { + if s.is_empty() { + return NodeName { name: "/", unit_address: None }; + } + + let (name, unit_address) = s.split_once('@').unzip(); + NodeName { name: name.unwrap_or(s), unit_address } + }), + ) + } + + /// [Devicetree 3.8.1 General Properties of `/cpus/cpu*` + /// nodes](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#general-properties-of-cpus-cpu-nodes) + /// + /// **Required** + /// + /// The value of `reg` is a `` that defines a unique + /// CPU/thread id for the CPU/threads represented by the CPU node. + /// + /// If a CPU supports more than one thread (i.e. multiple streams of + /// execution) the `reg` property is an array with 1 element per thread. The + /// `#address-cells` on the `/cpus` node specifies how many cells each + /// element of the array takes. Software can determine the number of threads + /// by dividing the size of `reg` by the parent node’s `#address-cells`. + /// + /// If a CPU/thread can be the target of an external interrupt the `reg` + /// property value must be a unique CPU/thread id that is addressable by the + /// interrupt controller. + /// + /// If a CPU/thread cannot be the target of an external interrupt, then + /// `reg` must be unique and out of bounds of the range addressed by the + /// interrupt controller + /// + /// If a CPU/thread’s PIR (pending interrupt register) is modifiable, a + /// client program should modify PIR to match the `reg` property value. If + /// PIR cannot be modified and the PIR value is distinct from the interrupt + /// controller number space, the CPUs binding may define a binding-specific + /// representation of PIR values if desired. + #[inline(always)] + #[track_caller] + pub fn reg(&self) -> P::Output>> { + self.property::>() + } + + /// [Devicetree 2.3.8 + /// `ranges`](https://devicetree-specification.readthedocs.io/en/latest/chapter2-devicetree-basics.html#sect-standard-properties-ranges) + /// + /// Value type: `` or `` encoded as an arbitrary + /// number of `(child-bus-address, parent-bus-address, length)` triplets. + /// + /// Description: + /// + /// The ranges property provides a means of defining a mapping or + /// translation between the address space of the bus (the child address + /// space) and the address space of the bus node’s parent (the parent + /// address space). + /// + /// The format of the value of the ranges property is an arbitrary number of + /// triplets of `(child-bus-address, parent-bus-address, length)` + /// + /// * The `child-bus-address` is a physical address within the child bus’ + /// address space. The number of cells to represent the address is bus + /// dependent and can be determined from the `#address-cells` of this node + /// (the node in which the ranges property appears). + /// * The `parent-bus-address` is a physical address within the parent bus’ + /// address space. The number of cells to represent the parent address is + /// bus dependent and can be determined from the `#address-cells` property + /// of the node that defines the parent’s address space. + /// * The `length` specifies the size of the range in the child’s address + /// space. The number of cells to represent the size can be determined + /// from the `#size-cells` of this node (the node in which the ranges + /// property appears). + /// + /// If the property is defined with an `` value, it specifies that + /// the parent and child address space is identical, and no address + /// translation is required. + /// + /// If the property is not present in a bus node, it is assumed that no + /// mapping exists between children of the node and the parent address + /// space. + /// + /// Address Translation Example: + /// + /// ```notrust + /// soc { + /// compatible = "simple-bus"; + /// #address-cells = <1>; + /// #size-cells = <1>; + /// ranges = <0x0 0xe0000000 0x00100000>; + /// + /// serial@4600 { + /// device_type = "serial"; + /// compatible = "ns16550"; + /// reg = <0x4600 0x100>; + /// clock-frequency = <0>; + /// interrupts = <0xA 0x8>; + /// interrupt-parent = <&ipic>; + /// }; + /// }; + /// ``` + /// + /// The soc node specifies a ranges property of + /// + /// ```notrust + /// <0x0 0xe0000000 0x00100000>; + /// ``` + /// + /// This property value specifies that for a 1024 KB range of address space, + /// a child node addressed at physical `0x0` maps to a parent address of + /// physical `0xe0000000`. With this mapping, the serial device node can be + /// addressed by a load or store at address `0xe0004600`, an offset of + /// `0x4600` (specified in `reg`) plus the `0xe0000000` mapping specified in + /// ranges. + #[inline(always)] + #[track_caller] + pub fn ranges(&self) -> P::Output>> { + self.property() + } + + /// Returns [`NodeProperties`] which allows searching and iterating over + /// this node's properties. + #[inline] + #[track_caller] + pub fn properties(&self) -> P::Output> { + let mut parser = P::new(&self.this.0, self.strings, self.structs); + let res = parser.advance_cstr(); + + P::to_output(res.map(|_| NodeProperties { + data: parser.data(), + strings: self.strings, + structs: self.structs, + _mode: core::marker::PhantomData, + })) + } + + /// Attempt to find the property with the given name and extract the raw + /// name and value. + #[inline] + #[track_caller] + pub fn raw_property(&self, name: &str) -> P::Output>> { + P::to_output(tryblock!({ + let this = self.fallible(); + this.properties()?.find(name) + })) + } + + /// Attempt to find and extract the specified property represented by + /// `Prop`. + #[track_caller] + pub fn property>(&self) -> P::Output> { + P::to_output(crate::tryblock!({ Prop::parse(self.alt(), self.make_root()?) })) + } + + /// Attempt to find a child of the current [`Node`] with the given name. + /// + /// For more details on what constitutes a node name which can be + /// searchable, see [`IntoSearchableNodeName`]. + #[inline] + #[track_caller] + pub fn child(&self, name: N) -> P::Output>> + where + N: IntoSearchableNodeName<'a>, + { + P::to_output(crate::tryblock!({ self.fallible().children()?.find(name).map(|o| o.map(|n| n.alt())) })) + } + + /// Returns [`NodeChildren`] which allows searching and iterating over this + /// node's children. + #[inline] + #[track_caller] + pub fn children(&self) -> P::Output> { + P::to_output(tryblock!({ + let mut parser = P::new(&self.this.0, self.strings, self.structs); + parser.advance_cstr()?; + + loop { + match parser.peek_token() { + Ok(BigEndianToken::PROP) => parser.parse_raw_property()?, + Ok(BigEndianToken::BEGIN_NODE) => break, + Ok(_) | Err(FdtError::ParseError(ParseError::UnexpectedEndOfData)) => break, + Err(e) => return Err(e), + }; + } + + Ok(NodeChildren { + data: parser.data(), + parent: self.this, + strings: self.strings, + structs: self.structs, + _mode: core::marker::PhantomData, + }) + })) + } + + /// Attempt to retrieve the parent for this node. Note that this + #[inline] + pub fn parent(&self) -> Option { + self.parent.map(|parent| Self { + this: parent, + parent: None, + strings: self.strings, + structs: self.structs, + _mode: core::marker::PhantomData, + }) + } +} + +impl<'a, P: ParserWithMode<'a>> AsNode<'a, P> for Node<'a, P> { + fn as_node(&self) -> Node<'a, P> { + *self + } +} + +impl<'a, P: ParserWithMode<'a>> core::fmt::Debug for Node<'a, P> +where + P::Output>: core::fmt::Debug, +{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("Node").field("name", &self.name()).finish_non_exhaustive() + } +} + +impl<'a, P: ParserWithMode<'a>> Clone for Node<'a, P> { + fn clone(&self) -> Self { + *self + } +} + +impl<'a, P: ParserWithMode<'a>> Copy for Node<'a, P> {} + +/// Newtype around a slice of raw node data. +#[repr(transparent)] +pub struct RawNode([Granularity]); + +impl RawNode { + pub(crate) fn new(data: &[Granularity]) -> &Self { + // SAFETY: the representation of `Self` and `data` are the same + unsafe { core::mem::transmute(data) } + } + + pub(crate) fn as_slice(&self) -> &[Granularity] { + // SAFETY: the representation of `Self` and `data` are the same + unsafe { core::mem::transmute(self) } + } +} + +/// Allows for searching and iterating over all of the properties of a given +/// [`Node`]. +pub struct NodeProperties<'a, P: ParserWithMode<'a> = (AlignedParser<'a>, Panic)> { + data: &'a [

>::Granularity], + strings: StringsBlock<'a>, + structs: StructsBlock<'a,

>::Granularity>, + _mode: core::marker::PhantomData<*mut P>, +} + +impl<'a, P: ParserWithMode<'a>> NodeProperties<'a, P> { + pub(crate) fn alt>(self) -> NodeProperties<'a, P2> { + NodeProperties { + data: self.data, + strings: self.strings, + structs: self.structs, + _mode: core::marker::PhantomData, + } + } + + /// Create an iterator over the properties in the [`Node`]. + #[inline(always)] + pub fn iter(self) -> NodePropertiesIter<'a, P> { + NodePropertiesIter { properties: self.alt(), _mode: core::marker::PhantomData } + } + + #[track_caller] + pub(crate) fn advance(&mut self) -> P::Output>> { + let mut parser = P::new(self.data, self.strings, self.structs); + + match parser.peek_token() { + Ok(BigEndianToken::PROP) => {} + Ok(BigEndianToken::BEGIN_NODE) | Ok(BigEndianToken::END_NODE) => return P::to_output(Ok(None)), + Ok(_) => { + return P::to_output(Err(ParseError::UnexpectedToken.into())); + } + Err(FdtError::ParseError(ParseError::UnexpectedEndOfData)) => return P::to_output(Ok(None)), + Err(e) => return P::to_output(Err(e)), + } + + P::to_output(tryblock!({ + match parser.parse_raw_property() { + Ok((name_offset, data)) => { + self.data = parser.data(); + + Ok(Some(NodeProperty::new(self.strings.offset_at(name_offset)?, data))) + } + Err(FdtError::ParseError(ParseError::UnexpectedEndOfData)) => Ok(None), + Err(e) => return Err(e), + } + })) + } + + /// Attempt to find a property with the provided name. + #[inline] + #[track_caller] + pub fn find(&self, name: &str) -> P::Output>> { + let this: NodeProperties<'a, (P::Parser, NoPanic)> = NodeProperties { + data: self.data, + strings: self.strings, + structs: self.structs, + _mode: core::marker::PhantomData, + }; + + P::to_output( + this.iter() + .find_map(|p| match p { + Err(e) => Some(Err(e)), + Ok(p) => (p.name == name).then_some(Ok(p)), + }) + .transpose(), + ) + } +} + +impl<'a, P: ParserWithMode<'a>> Clone for NodeProperties<'a, P> { + fn clone(&self) -> Self { + *self + } +} + +impl<'a, P: ParserWithMode<'a>> Copy for NodeProperties<'a, P> {} + +impl<'a, P: ParserWithMode<'a>> IntoIterator for NodeProperties<'a, P> { + type IntoIter = NodePropertiesIter<'a, P>; + type Item = P::Output>; + + #[inline(always)] + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +/// See [`NodeProperties::iter`]. +#[derive(Clone)] +pub struct NodePropertiesIter<'a, P: ParserWithMode<'a> = (AlignedParser<'a>, Panic)> { + properties: NodeProperties<'a, (P::Parser, NoPanic)>, + _mode: core::marker::PhantomData<*mut P>, +} + +impl<'a, P: ParserWithMode<'a>> Iterator for NodePropertiesIter<'a, P> { + type Item = P::Output>; + + #[track_caller] + fn next(&mut self) -> Option { + // This is a manual impl of `map` because we need the panic location to + // be the caller if `P::to_output` panics + #[allow(clippy::manual_map)] + match self.properties.advance().transpose() { + Some(output) => Some(P::to_output(output)), + None => None, + } + } +} + +/// Generic node property. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct NodeProperty<'a> { + /// Property name. + pub name: &'a str, + /// Raw property value. + pub value: &'a [u8], +} + +impl<'a> NodeProperty<'a> { + /// Create a new [`NodeProperty`] from its name and raw value. + #[inline(always)] + pub fn new(name: &'a str, value: &'a [u8]) -> Self { + Self { name, value } + } + + /// Attempt to convert this property's value to the specified + /// [`PropertyValue`] type. + #[inline(always)] + pub fn as_value>(&self) -> Result { + V::parse(self.value) + } +} + +/// Allows for searching and iterating over the children of a given [`Node`]. +pub struct NodeChildren<'a, P: ParserWithMode<'a> = (AlignedParser<'a>, Panic)> { + data: &'a [

>::Granularity], + parent: &'a RawNode<

>::Granularity>, + strings: StringsBlock<'a>, + structs: StructsBlock<'a,

>::Granularity>, + _mode: core::marker::PhantomData<*mut P>, +} + +impl<'a, P: ParserWithMode<'a>> NodeChildren<'a, P> { + /// Create an iterator over the [`Node`]'s children. + #[inline(always)] + pub fn iter(&self) -> NodeChildrenIter<'a, P> { + NodeChildrenIter { + children: NodeChildren { + data: self.data, + parent: self.parent, + strings: self.strings, + structs: self.structs, + _mode: core::marker::PhantomData, + }, + } + } + + #[inline] + pub(crate) fn advance(&mut self) -> P::Output>> { + let mut parser = P::new(self.data, self.strings, self.structs); + + match parser.peek_token() { + Ok(BigEndianToken::BEGIN_NODE) => {} + Ok(BigEndianToken::END_NODE) => return P::to_output(Ok(None)), + Ok(_) => return P::to_output(Err(ParseError::UnexpectedToken.into())), + Err(FdtError::ParseError(ParseError::UnexpectedEndOfData)) => return P::to_output(Ok(None)), + Err(e) => return P::to_output(Err(e)), + } + + P::to_output(match parser.parse_node(Some(self.parent)) { + Ok(node) => { + self.data = parser.data(); + + Ok(Some(node)) + } + Err(FdtError::ParseError(ParseError::UnexpectedEndOfData)) => Ok(None), + Err(e) => Err(e), + }) + } + + /// Attempt to find the first child matching the provided name, see + /// [`IntoSearchableNodeName`] for more details. If the name lacks a unit + /// address, unit addresses on the children will be ignored when checking if + /// the name matches. + #[inline] + #[track_caller] + pub fn find<'n, N>(&self, name: N) -> P::Output>> + where + N: IntoSearchableNodeName<'n>, + { + let this: NodeChildren<(P::Parser, NoPanic)> = NodeChildren { + data: self.data, + parent: self.parent, + strings: self.strings, + structs: self.structs, + _mode: core::marker::PhantomData, + }; + + let name = name.into_searchable_node_name(); + P::to_output( + this.iter() + .find_map(|n| match n { + Err(e) => Some(Err(e)), + Ok(node) => match node.name() { + Err(e) => Some(Err(e)), + Ok(nn) => match name { + SearchableNodeName::Base(base) => (nn.name == base).then_some(Ok(node)), + SearchableNodeName::WithUnitAddress(snn) => (nn == snn).then_some(Ok(node)), + }, + }, + }) + .map(|n| n.map(Node::alt)) + .transpose(), + ) + } +} + +impl<'a, P: ParserWithMode<'a>> Clone for NodeChildren<'a, P> { + fn clone(&self) -> Self { + *self + } +} + +impl<'a, P: ParserWithMode<'a>> Copy for NodeChildren<'a, P> {} + +impl<'a, P: ParserWithMode<'a>> IntoIterator for NodeChildren<'a, P> { + type IntoIter = NodeChildrenIter<'a, P>; + type Item = P::Output>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +/// See [`NodeChildren::iter`]. +#[derive(Clone)] +pub struct NodeChildrenIter<'a, P: ParserWithMode<'a> = (AlignedParser<'a>, Panic)> { + children: NodeChildren<'a, (P::Parser, NoPanic)>, +} + +impl<'a, P: ParserWithMode<'a>> Iterator for NodeChildrenIter<'a, P> { + type Item = P::Output>; + + #[track_caller] + fn next(&mut self) -> Option { + // This is a manual impl of `map` because we need the panic location to + // be the caller if `P::to_output` panics + #[allow(clippy::manual_map)] + match self.children.advance().transpose() { + Some(output) => Some(P::to_output(output.map(Node::alt))), + None => None, + } + } +} diff --git a/src/nodes/aliases.rs b/src/nodes/aliases.rs new file mode 100644 index 0000000..ee98507 --- /dev/null +++ b/src/nodes/aliases.rs @@ -0,0 +1,93 @@ +use super::{AsNode, Node, NodePropertiesIter}; +use crate::{ + helpers::{FallibleNode, FallibleParser}, + parsing::{NoPanic, ParserWithMode}, +}; + +/// [Devicetree 3.3. `/aliases` +/// node](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#aliases-node) +/// +/// A devicetree may have an aliases node (`/aliases`) that defines one or more +/// alias properties. The alias node shall be at the root of the devicetree and +/// have the node name `/aliases`. +/// +/// Each property of the `/aliases` node defines an alias. The property name +/// specifies the alias name. The property value specifies the full path to a +/// node in the devicetree. For example, the property `serial0 = +/// "/simple-bus@fe000000/serial@llc500"` defines the alias `serial0`. +/// +/// An alias value is a device path and is encoded as a string. The value +/// represents the full path to a node, but the path does not need to refer to a +/// leaf node. +/// +/// A client program may use an alias property name to refer to a full device +/// path as all or part of its string value. A client program, when considering +/// a string as a device path, shall detect and use the alias. +/// +/// ### Example +/// +/// ```norust +/// aliases { +/// serial0 = "/simple-bus@fe000000/serial@llc500"; +/// ethernet0 = "/simple-bus@fe000000/ethernet@31c000"; +/// }; +/// ``` +/// +/// Given the alias `serial0`, a client program can look at the `/aliases` node +/// and determine the alias refers to the device path +/// `/simple-bus@fe000000/serial@llc500`. +#[derive(Debug, Clone, Copy)] +pub struct Aliases<'a, P: ParserWithMode<'a>> { + pub(crate) node: FallibleNode<'a, P>, +} + +impl<'a, P: ParserWithMode<'a>> Aliases<'a, P> { + /// Attempt to resolve an alias to a node name. + pub fn resolve_name(self, alias: &str) -> P::Output> { + P::to_output(crate::tryblock!({ + self.node.properties()?.find(alias)?.map(|p| p.as_value().map_err(Into::into)).transpose() + })) + } + + /// Attempt resolve an alias to the aliased-to node. + pub fn resolve(self, alias: &str) -> P::Output>> { + P::to_output(crate::tryblock!({ + let Some(path) = Aliases::<(_, NoPanic)> { node: self.node }.resolve_name(alias)? else { + return Ok(None); + }; + + self.node.make_root::()?.find_node(path).map(|r| r.map(|n| n.alt())) + })) + } + + /// Create an iterator over all of the available aliases + pub fn iter(self) -> P::Output> { + P::to_output(crate::tryblock!({ Ok(AllAliasesIter { properties: self.node.properties()?.iter() }) })) + } +} + +impl<'a, P: ParserWithMode<'a>> AsNode<'a, P> for Aliases<'a, P> { + fn as_node(&self) -> Node<'a, P> { + self.node.alt() + } +} + +#[allow(missing_docs)] +pub struct AllAliasesIter<'a, P: ParserWithMode<'a>> { + properties: NodePropertiesIter<'a, FallibleParser<'a, P>>, +} + +impl<'a, P> Iterator for AllAliasesIter<'a, P> +where + P: ParserWithMode<'a>, +{ + type Item = P::Output<(&'a str, &'a str)>; + #[track_caller] + fn next(&mut self) -> Option { + Some(P::to_output(match self.properties.next() { + Some(Ok(prop)) => crate::tryblock!({ Ok((prop.name, prop.as_value::<&'a str>()?)) }), + Some(Err(e)) => Err(e), + None => return None, + })) + } +} diff --git a/src/nodes/chosen.rs b/src/nodes/chosen.rs new file mode 100644 index 0000000..ab92845 --- /dev/null +++ b/src/nodes/chosen.rs @@ -0,0 +1,257 @@ +// This Source Code Form is subject to the terms of the Mozilla Public License, +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at https://mozilla.org/MPL/2.0/. + +use super::{FallibleNode, Node}; +use crate::{ + helpers::{FallibleParser, FallibleRoot}, + parsing::{aligned::AlignedParser, Panic, ParseError, ParserWithMode}, + FdtError, +}; + +/// [Devicetree 3.6. `/chosen` +/// Node](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#chosen-node) +/// +/// The `/chosen` node does not represent a real device in the system but +/// describes parameters chosen or specified by the system firmware at run time. +/// It shall be a child of the root node. +pub struct Chosen<'a, P: ParserWithMode<'a> = (AlignedParser<'a>, Panic)> { + pub(crate) node: FallibleNode<'a, P>, +} + +impl<'a, P: ParserWithMode<'a>> Chosen<'a, P> { + /// [Devicetree 3.6. `/chosen` + /// Node](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#chosen-node) + /// + /// A string that specifies the boot arguments for the client program. The + /// value could potentially be a null string if no boot arguments are + /// required. + #[track_caller] + pub fn bootargs(self) -> P::Output> { + P::to_output(crate::tryblock!({ + for prop in self.node.properties()?.into_iter().flatten() { + if prop.name == "bootargs" { + return Ok(Some( + core::str::from_utf8(&prop.value[..prop.value.len() - 1]) + .map_err(|_| FdtError::ParseError(ParseError::InvalidCStrValue))?, + )); + } + } + + Ok(None) + })) + } + + /// Like [`Chosen::stdout_path`] but also attempts to resolve the path (also + /// attempts to resolve the path to an alias if: the path does not look like + /// a devicetree path, or the path is not found), and returns the stdout + /// parameters along with the node, if it was successfully resolved. + /// + /// For more information on the `stdout-path` property, see + /// [`Chosen::stdout_path`]. + #[allow(clippy::type_complexity)] + #[track_caller] + pub fn stdout(self) -> P::Output>> { + P::to_output(crate::tryblock!({ + let this: Chosen<'a, FallibleParser<'a, P>> = Chosen { node: self.node }; + let Some(stdout) = this.stdout_path()? else { return Ok(None) }; + let root: FallibleRoot<'a, P> = this.node.make_root()?; + + let node = match stdout.path().contains('/') { + true => root.find_node(stdout.path())?, + false => None, + }; + + match node { + Some(node) => Ok(Some(Stdout { node: node.alt(), params: stdout.params() })), + None => { + let Some(aliases) = root.aliases()? else { return Ok(None) }; + match aliases.resolve(stdout.path())? { + Some(node) => Ok(Some(Stdout { node: node.alt(), params: stdout.params() })), + None => Ok(None), + } + } + } + })) + } + + /// [Devicetree 3.6. `/chosen` + /// Node](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#chosen-node) + /// + /// A string that specifies the full path to the node representing the + /// device to be used for boot console output. If the character ":" is + /// present in the value it terminates the path. The value may be an alias. + /// If the `stdin-path` property is not specified, `stdout-path` should be + /// assumed to define the input device. + #[track_caller] + pub fn stdout_path(self) -> P::Output>> { + P::to_output(crate::tryblock!({ + self.node + .properties()? + .into_iter() + .find_map(|n| match n { + Err(e) => Some(Err(e)), + Ok(property) => match property.name == "stdout-path" { + false => None, + true => Some(property.as_value::<&'a str>().map_err(Into::into).map(|s| { + let (path, params) = + s.split_once(':').map_or_else(|| (s, None), |(name, params)| (name, Some(params))); + StdInOutPath { path, params } + })), + }, + }) + .transpose() + })) + } + + /// Like [`Chosen::stdin_path`] but also attempts to resolve the path (also + /// attempts to resolve the path to an alias if: the path does not look like + /// a devicetree path, or the path is not found), and returns the stdin + /// parameters along with the node, if it was successfully resolved. + /// + /// For more information on the `stdin-path` property, see + /// [`Chosen::stdin_path`]. + #[allow(clippy::type_complexity)] + #[track_caller] + pub fn stdin(self) -> P::Output>> { + P::to_output(crate::tryblock!({ + let this: Chosen<'a, FallibleParser<'a, P>> = Chosen { node: self.node }; + let Some(stdin) = this.stdin_path()? else { return Ok(None) }; + let root: FallibleRoot<'a, P> = this.node.make_root()?; + + let node = match stdin.path().contains('/') { + true => root.find_node(stdin.path())?, + false => None, + }; + + match node { + Some(node) => Ok(Some(Stdin { node: node.alt(), params: stdin.params() })), + None => { + let Some(aliases) = root.aliases()? else { return Ok(None) }; + match aliases.resolve(stdin.path())? { + Some(node) => Ok(Some(Stdin { node: node.alt(), params: stdin.params() })), + None => Ok(None), + } + } + } + })) + } + + /// [Devicetree 3.6. `/chosen` + /// Node](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#chosen-node) + /// + /// A string that specifies the full path to the node representing the + /// device to be used for boot console input. If the character ":" is + /// present in the value it terminates the path. The value may be an alias. + #[track_caller] + pub fn stdin_path(self) -> P::Output>> { + P::to_output(crate::tryblock!({ + self.node + .properties()? + .into_iter() + .find_map(|n| match n { + Err(e) => Some(Err(e)), + Ok(property) => match property.name == "stdin-path" { + false => None, + true => Some(property.as_value::<&str>().map_err(Into::into).map(|s| { + let (path, params) = + s.split_once(':').map_or_else(|| (s, None), |(name, params)| (name, Some(params))); + StdInOutPath { path, params } + })), + }, + }) + .transpose() + })) + } +} + +impl<'a, P: ParserWithMode<'a>> Clone for Chosen<'a, P> { + fn clone(&self) -> Self { + *self + } +} + +impl<'a, P: ParserWithMode<'a>> Copy for Chosen<'a, P> {} + +/// See [`Chosen::stdin`]. +pub struct Stdin<'a, P: ParserWithMode<'a>> { + /// Node representing an stdin device. + pub node: Node<'a, P>, + /// Optional parameters following the node path. + pub params: Option<&'a str>, +} + +impl<'a, P: ParserWithMode<'a>> core::fmt::Debug for Stdin<'a, P> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let mut debug_struct = f.debug_struct("Stdin"); + let debug_struct = match self.node.fallible().name() { + Ok(name) => debug_struct.field("node", &name), + Err(e) => debug_struct.field("node", &Err::<(), _>(e)), + }; + + debug_struct.field("params", &self.params).finish() + } +} + +/// See [`Chosen::stdout`]. +pub struct Stdout<'a, P: ParserWithMode<'a>> { + /// Node representing an stdout device. + pub node: Node<'a, P>, + /// Optional parameters following the node path. + pub params: Option<&'a str>, +} + +impl<'a, P: ParserWithMode<'a>> core::fmt::Debug for Stdout<'a, P> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let mut debug_struct = f.debug_struct("Stdin"); + let debug_struct = match self.node.fallible().name() { + Ok(name) => debug_struct.field("node", &name), + Err(e) => debug_struct.field("node", &Err::<(), _>(e)), + }; + + debug_struct.field("params", &self.params).finish() + } +} + +/// Like [`Stdout`] and [`Stdin`] but does not contain the resolved node, only +/// its path and the optional parameters that may follow. +pub struct StdInOutPath<'a> { + path: &'a str, + params: Option<&'a str>, +} + +impl<'a> StdInOutPath<'a> { + /// Path to the node representing the stdin/stdout device. This node path + /// may be an alias, which can be resolved with [`Aliases::resolve`]. To be + /// used in conjunction with [`Root::find_node`]. + pub fn path(&self) -> &'a str { + self.path + } + + /// Optional parameters specified by the stdin/stdout property value. See + /// https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#chosen-node + /// + /// Example: + /// + /// ```dts + /// / { + /// chosen { + /// stdout-path = "/soc/uart@10000000:115200"; + /// stdin-path = "/soc/uart@10000000"; + /// } + /// } + /// ``` + /// + /// ```rust + /// # let fdt = fdt::Fdt::new_unaligned(include_bytes!("../../dtb/test.dtb")).unwrap(); + /// # let chosen = fdt.root().chosen(); + /// let stdout = chosen.stdout_path().unwrap(); + /// let stdin = chosen.stdin_path().unwrap(); + /// + /// assert_eq!((stdout.path(), stdout.params()), ("/soc/uart@10000000", Some("115200"))); + /// assert_eq!((stdin.path(), stdin.params()), ("/soc/uart@10000000", None)); + /// ``` + pub fn params(&self) -> Option<&'a str> { + self.params + } +} diff --git a/src/nodes/cpus.rs b/src/nodes/cpus.rs new file mode 100644 index 0000000..47b5fc6 --- /dev/null +++ b/src/nodes/cpus.rs @@ -0,0 +1,964 @@ +use super::{AsNode, FallibleNode, NodeChildrenIter}; +use crate::{ + cell_collector::{BuildCellCollector, CellCollector, CollectCellsError}, + parsing::{aligned::AlignedParser, NoPanic, Panic, ParserWithMode}, + properties::{ + cells::{AddressCells, CellSizes}, + values::StringList, + PHandle, + }, + FdtError, +}; + +/// [Devicetree 3.7. +/// `/cpus`](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#cpus-node-properties) +/// +/// A `/cpus` node is required for all devicetrees. It does not represent a real +/// device in the system, but acts as a container for child cpu nodes which +/// represent the systems CPUs. +pub struct Cpus<'a, P: ParserWithMode<'a> = (AlignedParser<'a>, Panic)> { + pub(crate) node: FallibleNode<'a, P>, +} + +impl<'a, P: ParserWithMode<'a>> Cpus<'a, P> { + /// Retrieve the `#address-cells` and `#size-cells` values from this node + #[track_caller] + pub fn cell_sizes(&self) -> P::Output { + P::to_output( + self.node.property().and_then(|p| p.ok_or(FdtError::MissingRequiredProperty("#address-cells/#size-cells"))), + ) + } + + /// Attempt to find a common `timebase-frequency` property inside of this + /// node, which will only exist if there is a common value between the child + /// `cpu` nodes. See [`Cpu::timebase_frequency`] for documentation about the + /// `timebase-frequency` property. + #[track_caller] + pub fn common_timebase_frequency(&self) -> P::Output> { + P::to_output(crate::tryblock!({ + match self.node.properties()?.find("timebase-frequency")? { + Some(prop) => match prop.value.len() { + 4 => Ok(Some(u64::from(prop.as_value::()?))), + 8 => Ok(Some(prop.as_value::()?)), + _ => Err(FdtError::InvalidPropertyValue), + }, + None => Ok(None), + } + })) + } + + /// Attempt to find a common `clock-frequency` property inside of this + /// node, which will only exist if there is a common value between the child + /// `cpu` nodes. See [`Cpu::clock_frequency`] for documentation about the + /// `clock-frequency` property. + #[track_caller] + pub fn common_clock_frequency(&self) -> P::Output> { + P::to_output(crate::tryblock!({ + match self.node.properties()?.find("clock-frequency")? { + Some(prop) => match prop.value.len() { + 4 => Ok(Some(u64::from(prop.as_value::()?))), + 8 => Ok(Some(prop.as_value::()?)), + _ => Err(FdtError::InvalidPropertyValue), + }, + None => Ok(None), + } + })) + } + + /// Returns the (optional) `cpu-map` child node, which describes the system + /// socket and CPU topology. See [`CpuTopology`] for more details. + pub fn topology(&self) -> P::Output>> { + P::to_output(crate::tryblock!({ + match self.node.children()?.find("cpu-map")? { + Some(node) => Ok(Some(CpuTopology { node })), + None => Ok(None), + } + })) + } + + /// Create an iterator over the children of this node, primarily composed of + /// [`Cpu`] nodes. + pub fn iter(&self) -> P::Output> { + P::to_output(crate::tryblock!({ + Ok(CpusIter { children: self.node.children()?.iter().filter(filter_cpus::

) }) + })) + } +} + +impl<'a, P: ParserWithMode<'a>> AsNode<'a, P> for Cpus<'a, P> { + fn as_node(&self) -> super::Node<'a, P> { + self.node.alt() + } +} + +fn filter_cpus<'a, P: ParserWithMode<'a>>(node: &Result, FdtError>) -> bool { + match node { + Ok(node) => matches!(node.name().map(|n| n.name), Ok("cpu")), + _ => true, + } +} + +#[allow(missing_docs)] +pub struct CpusIter<'a, P: ParserWithMode<'a> = (AlignedParser<'a>, Panic)> { + #[allow(clippy::type_complexity)] + children: core::iter::Filter< + NodeChildrenIter<'a, (P::Parser, NoPanic)>, + fn(&Result, FdtError>) -> bool, + >, +} + +impl<'a, P: ParserWithMode<'a>> Iterator for CpusIter<'a, P> { + type Item = P::Output>; + + #[track_caller] + fn next(&mut self) -> Option { + match self.children.next()? { + Ok(node) => Some(P::to_output(Ok(Cpu { node }))), + Err(e) => Some(P::to_output(Err(e))), + } + } +} + +/// [Devicetree 3.8. +/// `/cpus/cpu*`](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#cpus-cpu-node-properties) +/// +/// A `cpu` node represents a hardware execution block that is sufficiently +/// independent that it is capable of running an operating system without +/// interfering with other CPUs possibly running other operating systems. +/// +/// Hardware threads that share an MMU would generally be represented under one +/// `cpu` node. If other more complex CPU topographies are designed, the binding +/// for the CPU must describe the topography (e.g. threads that don’t share an +/// MMU). +/// +/// CPUs and threads are numbered through a unified number-space that should +/// match as closely as possible the interrupt controller’s numbering of +/// CPUs/threads. +/// +/// Properties that have identical values across `cpu` nodes may be placed in the +/// /cpus node instead. A client program must first examine a specific `cpu` node, +/// but if an expected property is not found then it should look at the parent +/// /cpus node. This results in a less verbose representation of properties +/// which are identical across all CPUs. +#[derive(Debug, Clone, Copy)] +pub struct Cpu<'a, P: ParserWithMode<'a> = (AlignedParser<'a>, Panic)> { + pub(crate) node: FallibleNode<'a, P>, +} + +impl<'a, P: ParserWithMode<'a>> Cpu<'a, P> { + /// [Devicetree 3.8.1 General Properties of `/cpus/cpu*` + /// nodes](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#general-properties-of-cpus-cpu-nodes) + /// + /// **Required** + /// + /// The value of `reg` is a `` that defines a unique + /// CPU/thread id for the CPU/threads represented by the CPU node. + /// + /// If a CPU supports more than one thread (i.e. multiple streams of + /// execution) the `reg` property is an array with 1 element per thread. The + /// `#address-cells` on the `/cpus` node specifies how many cells each + /// element of the array takes. Software can determine the number of threads + /// by dividing the size of `reg` by the parent node’s `#address-cells`. + /// + /// If a CPU/thread can be the target of an external interrupt the `reg` + /// property value must be a unique CPU/thread id that is addressable by the + /// interrupt controller. + /// + /// If a CPU/thread cannot be the target of an external interrupt, then + /// `reg` must be unique and out of bounds of the range addressed by the + /// interrupt controller + /// + /// If a CPU/thread’s PIR (pending interrupt register) is modifiable, a + /// client program should modify PIR to match the `reg` property value. If + /// PIR cannot be modified and the PIR value is distinct from the interrupt + /// controller number space, the CPUs binding may define a binding-specific + /// representation of PIR values if desired. + #[inline] + #[track_caller] + #[doc(alias = "ids")] + pub fn reg(self) -> P::Output> { + P::to_output(crate::tryblock!({ + let Some(reg) = self.node.properties()?.find("reg")? else { + return Err(FdtError::MissingRequiredProperty("reg")); + }; + + if reg.value.is_empty() { + return Err(FdtError::InvalidPropertyValue); + } + + let Some(address_cells) = self.node.parent().unwrap().property::()? else { + return Err(FdtError::MissingRequiredProperty("#address-cells")); + }; + + Ok(CpuIds { reg: reg.value, address_cells: address_cells.0, _collector: core::marker::PhantomData }) + })) + } + + /// [Devicetree 3.8.1 General Properties of `/cpus/cpu*` + /// nodes](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#general-properties-of-cpus-cpu-nodes) + /// + /// **Required** + /// + /// Specifies the current clock speed of the CPU in Hertz. The value is a + /// `` in one of two forms: + /// + /// * A 32-bit integer consisting of one `` specifying the frequency. + /// * A 64-bit integer represented as a `` specifying the frequency. + #[inline] + #[track_caller] + pub fn clock_frequency(self) -> P::Output { + P::to_output(crate::tryblock!({ + match self.node.properties()?.find("clock-frequency")? { + Some(prop) => match prop.value.len() { + 4 => Ok(u64::from(prop.as_value::()?)), + 8 => Ok(prop.as_value::()?), + _ => Err(FdtError::InvalidPropertyValue), + }, + None => { + let prop = self + .node + .parent() + .unwrap() + .properties()? + .find("clock-frequency")? + .ok_or(FdtError::MissingRequiredProperty("clock-frequency"))?; + + match prop.value.len() { + 4 => Ok(u64::from(prop.as_value::()?)), + 8 => Ok(prop.as_value::()?), + _ => Err(FdtError::InvalidPropertyValue), + } + } + } + })) + } + + /// [Devicetree 3.8.1 General Properties of `/cpus/cpu*` + /// nodes](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#general-properties-of-cpus-cpu-nodes) + /// + /// **Required** + /// + /// Specifies the current frequency at which the timebase and decrementer + /// registers are updated (in Hertz). The value is a `` + /// in one of two forms: + /// + /// * A 32-bit integer consisting of one `` specifying the frequency. + /// * A 64-bit integer represented as a ``. + #[inline] + #[track_caller] + pub fn timebase_frequency(self) -> P::Output { + P::to_output(crate::tryblock!({ + match self.node.properties()?.find("timebase-frequency")? { + Some(prop) => match prop.value.len() { + 4 => Ok(u64::from(prop.as_value::()?)), + 8 => Ok(prop.as_value::()?), + _ => Err(FdtError::InvalidPropertyValue), + }, + None => { + let prop = self + .node + .parent() + .unwrap() + .properties()? + .find("timebase-frequency")? + .ok_or(FdtError::MissingRequiredProperty("timebase-frequency"))?; + + match prop.value.len() { + 4 => Ok(u64::from(prop.as_value::()?)), + 8 => Ok(prop.as_value::()?), + _ => Err(FdtError::InvalidPropertyValue), + } + } + } + })) + } + + /// [Devicetree 3.8.1 General Properties of `/cpus/cpu*` + /// nodes](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#general-properties-of-cpus-cpu-nodes) + /// + /// A standard property describing the state of a CPU. This property shall + /// be present for nodes representing CPUs in a symmetric multiprocessing + /// (SMP) configuration. For a CPU node the meaning of the `"okay"`, + /// `"disabled"` and `"fail"` values are as follows: + /// + /// `"okay"`: The CPU is running. + /// + /// `"disabled"`: The CPU is in a quiescent state. + /// + /// `"fail"`: The CPU is not operational or does not exist. + /// + /// A quiescent CPU is in a state where it cannot interfere with the normal + /// operation of other CPUs, nor can its state be affected by the normal + /// operation of other running CPUs, except by an explicit method for + /// enabling or re-enabling the quiescent CPU (see the enable-method + /// property). + /// + /// In particular, a running CPU shall be able to issue broadcast TLB + /// invalidates without affecting a quiescent CPU. + /// + /// Examples: A quiescent CPU could be in a spin loop, held in reset, and + /// electrically isolated from the system bus or in another implementation + /// dependent state. + /// + /// A CPU with `"fail"` status does not affect the system in any way. The + /// status is assigned to nodes for which no corresponding CPU exists. + #[inline] + #[track_caller] + pub fn status(&self) -> P::Output>> { + P::to_output(crate::tryblock!({ + let Some(status) = self.node.properties()?.find("status")? else { + return Ok(None); + }; + + Ok(Some(CpuStatus(status.as_value()?))) + })) + } + + /// [Devicetree 3.8.1 General Properties of `/cpus/cpu*` + /// nodes](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#general-properties-of-cpus-cpu-nodes) + /// + /// Describes the method by which a CPU in a disabled state is enabled. This + /// property is required for CPUs with a status property with a value of + /// `"disabled"`. The value consists of one or more strings that define the + /// method to release this CPU. If a client program recognizes any of the + /// methods, it may use it. The value shall be one of the following: + /// + /// `"spin-table"`: The CPU is enabled with the spin table method defined in + /// the DTSpec. + /// + /// `"[vendor],[method]"`: Implementation dependent string that describes + /// the method by which a CPU is released from a `"disabled"` state. The + /// required format is: `"[vendor],[method]"`, where vendor is a string + /// describing the name of the manufacturer and method is a string + /// describing the vendor specific mechanism. + /// + /// Example: `"fsl,MPC8572DS"` + #[inline] + #[track_caller] + pub fn enable_method(&self) -> P::Output>> { + P::to_output(crate::tryblock!({ + let Some(status) = self.node.properties()?.find("enable-method")? else { + return Ok(None); + }; + + let s: &'a str = status.as_value()?; + + if s.is_empty() { + return Err(FdtError::InvalidPropertyValue); + } + + Ok(Some(CpuEnableMethods(s.into()))) + })) + } + + /// [Devicetree 3.8.1 General Properties of `/cpus/cpu*` + /// nodes](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#table-10) + /// + /// Specifies the CPU’s MMU type. + #[inline] + #[track_caller] + pub fn mmu_type(&self) -> P::Output> { + P::to_output(self.node.properties().and_then(|p| { + p.find("mmu-type").and_then(|p| match p { + Some(p) => Ok(Some(p.as_value()?)), + None => Ok(None), + }) + })) + } + + /// [Devicetree 3.8.2. TLB + /// Properties](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#tlb-properties) + /// + /// If present specifies that the TLB has a split configuration, with + /// separate TLBs for instructions and data. If absent, specifies that the + /// TLB has a unified configuration. Required for a CPU with a TLB in a + /// split configuration. + #[inline] + #[track_caller] + pub fn tlb_split(&self) -> P::Output { + P::to_output(self.node.properties().and_then(|p| p.find("tlb-split").map(|p| p.is_some()))) + } + + /// [Devicetree 3.8.2. TLB + /// Properties](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#tlb-properties) + /// + /// Specifies the number of entries in the TLB. Required for a CPU with a + /// unified TLB for instruction and data addresses. + #[inline] + #[track_caller] + pub fn tlb_size(&self) -> P::Output> { + P::to_output(self.node.properties().and_then(|p| { + p.find("tlb-size").and_then(|p| match p { + Some(p) => Ok(Some(p.as_value()?)), + None => Ok(None), + }) + })) + } + + /// [Devicetree 3.8.2. TLB + /// Properties](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#tlb-properties) + /// + /// Specifies the number of associativity sets in the TLB. Required for a + /// CPU with a unified TLB for instruction and data addresses. + #[inline] + #[track_caller] + pub fn tlb_sets(&self) -> P::Output> { + P::to_output(self.node.properties().and_then(|p| { + p.find("tlb-sets").and_then(|p| match p { + Some(p) => Ok(Some(p.as_value()?)), + None => Ok(None), + }) + })) + } + + /// [Devicetree 3.8.2. TLB + /// Properties](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#tlb-properties) + /// + /// Specifies the number of entries in the data TLB. Required for a CPU with + /// a split TLB configuration. + #[inline] + #[track_caller] + pub fn d_tlb_size(&self) -> P::Output> { + P::to_output(self.node.properties().and_then(|p| { + p.find("d-tlb-size").and_then(|p| match p { + Some(p) => Ok(Some(p.as_value()?)), + None => Ok(None), + }) + })) + } + + /// [Devicetree 3.8.2. TLB + /// Properties](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#tlb-properties) + /// + /// Specifies the number of associativity sets in the data TLB. Required for + /// a CPU with a split TLB configuration. + #[inline] + #[track_caller] + pub fn d_tlb_sets(&self) -> P::Output> { + P::to_output(self.node.properties().and_then(|p| { + p.find("d-tlb-sets").and_then(|p| match p { + Some(p) => Ok(Some(p.as_value()?)), + None => Ok(None), + }) + })) + } + + /// [Devicetree 3.8.2. TLB + /// Properties](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#tlb-properties) + /// + /// Specifies the number of entries in the instruction TLB. Required for a + /// CPU with a split TLB configuration. + #[inline] + #[track_caller] + pub fn i_tlb_size(&self) -> P::Output> { + P::to_output(self.node.properties().and_then(|p| { + p.find("i-tlb-size").and_then(|p| match p { + Some(p) => Ok(Some(p.as_value()?)), + None => Ok(None), + }) + })) + } + + /// [Devicetree 3.8.2. TLB + /// Properties](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#tlb-properties) + /// + /// Specifies the number of associativity sets in the instruction TLB. + /// Required for a CPU with a split TLB configuration. + #[inline] + #[track_caller] + pub fn i_tlb_sets(&self) -> P::Output> { + P::to_output(self.node.properties().and_then(|p| { + p.find("i-tlb-sets").and_then(|p| match p { + Some(p) => Ok(Some(p.as_value()?)), + None => Ok(None), + }) + })) + } +} + +impl<'a, P: ParserWithMode<'a>> AsNode<'a, P> for Cpu<'a, P> { + fn as_node(&self) -> super::Node<'a, P> { + self.node.alt() + } +} + +/// CPU status value. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(transparent)] +pub struct CpuStatus<'a>(&'a str); + +impl<'a> CpuStatus<'a> { + /// The CPU is running. + pub const OKAY: Self = Self("okay"); + /// The CPU is in a quiescent state. + pub const DISABLED: Self = Self("disabled"); + /// The CPU is not operational or does not exist. + pub const FAIL: Self = Self("fail"); + + /// Create a new [`CpuStatus`] which may not be one of the associated + /// constant values. + pub fn new(status: &'a str) -> Self { + Self(status) + } + + /// Whether the status is `"okay"`. + pub fn is_okay(self) -> bool { + self == Self::OKAY + } + + /// Whether the status is `"disabled"`. + pub fn is_disabled(self) -> bool { + self == Self::DISABLED + } + + /// Whether the status is `"failed"` + pub fn is_failed(self) -> bool { + self == Self::FAIL + } +} + +impl<'a> PartialEq for CpuStatus<'a> { + fn eq(&self, other: &str) -> bool { + self.0 == other + } +} + +/// Type representing one or more CPU enable methods. See +/// [`Cpu::enable_method`]. +#[derive(Debug, Clone)] +pub struct CpuEnableMethods<'a>(StringList<'a>); + +impl<'a> CpuEnableMethods<'a> { + /// Create an iterator over the enable methods. + pub fn iter(&self) -> CpuEnableMethodsIter<'a> { + CpuEnableMethodsIter(self.0.clone()) + } + + /// Return the first enable method contained in the list of enable methods. + pub fn first(&self) -> CpuEnableMethod<'a> { + self.iter().next().unwrap() + } +} + +impl<'a> IntoIterator for CpuEnableMethods<'a> { + type IntoIter = CpuEnableMethodsIter<'a>; + type Item = CpuEnableMethod<'a>; + + fn into_iter(self) -> Self::IntoIter { + CpuEnableMethodsIter(self.0) + } +} + +/// Iterator over the enable methods described by the `enable-method` property +/// on a CPU node. See [`Cpu::enable_method`]. +pub struct CpuEnableMethodsIter<'a>(StringList<'a>); + +impl<'a> Iterator for CpuEnableMethodsIter<'a> { + type Item = CpuEnableMethod<'a>; + + fn next(&mut self) -> Option { + match self.0.next()? { + "spin-table" => Some(CpuEnableMethod::SpinTable), + other => { + let (vendor, method) = other.split_once(',').unwrap_or((other, "")); + Some(CpuEnableMethod::VendorMethod { vendor, method }) + } + } + } +} + +/// An enable method contained by the [`Cpu::enable_method`] +pub enum CpuEnableMethod<'a> { + /// The CPU is enabled with the spin table method defined in the DTSpec. + SpinTable, + /// Implementation dependent string that describes the method by which a CPU + /// is released from a `"disabled"` state. + VendorMethod { + /// The manufacturer. + vendor: &'a str, + /// The vendor specific mechanism. + /// + /// NOTE: If the string value of this enable method does not match the + /// `"[vendor],[method]"` format defined by the devicetree spec, this + /// will be an empty string. + method: &'a str, + }, +} + +/// See [`Cpu::reg`] +pub struct CpuIds<'a, C: CellCollector> { + reg: &'a [u8], + address_cells: usize, + _collector: core::marker::PhantomData<*mut C>, +} + +impl<'a, C: CellCollector> CpuIds<'a, C> { + /// The first listed CPU ID, which will always exist + pub fn first(&self) -> Result { + self.iter().next().unwrap() + } + + /// Create an iterator over the CPU IDs described by a [`Cpu`] node. + pub fn iter(&self) -> CpuIdsIter<'a, C> { + CpuIdsIter { reg: self.reg, address_cells: self.address_cells, _collector: core::marker::PhantomData } + } +} + +impl Copy for CpuIds<'_, C> {} +impl Clone for CpuIds<'_, C> { + fn clone(&self) -> Self { + *self + } +} + +impl<'a, C: CellCollector> core::fmt::Debug for CpuIds<'a, C> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("CpuIds") + .field("reg", &self.reg) + .field("address_cells", &self.address_cells) + .finish_non_exhaustive() + } +} + +#[allow(missing_docs)] +pub struct CpuIdsIter<'a, C: CellCollector> { + reg: &'a [u8], + address_cells: usize, + _collector: core::marker::PhantomData<*mut C>, +} + +impl<'a, C: CellCollector> core::fmt::Debug for CpuIdsIter<'a, C> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("CpuIds") + .field("reg", &self.reg) + .field("address_cells", &self.address_cells) + .finish_non_exhaustive() + } +} + +impl Clone for CpuIdsIter<'_, C> { + fn clone(&self) -> Self { + Self { address_cells: self.address_cells, reg: self.reg, _collector: core::marker::PhantomData } + } +} + +impl<'a, C: CellCollector> Iterator for CpuIdsIter<'a, C> { + type Item = Result; + fn next(&mut self) -> Option { + let (this_cell, rest) = self.reg.split_at_checked(self.address_cells * 4)?; + self.reg = rest; + + let mut collector = ::Builder::default(); + + for cell in this_cell.chunks_exact(4) { + if let Err(e) = collector.push(u32::from_be_bytes(cell.try_into().unwrap())) { + return Some(Err(e)); + } + } + + Some(Ok(C::map(collector.finish()))) + } +} + +/// [Linux Kernel Devicetree Bindings - CPU topology binding +/// description](https://www.kernel.org/doc/Documentation/devicetree/bindings/cpu/cpu-topology.txt) +/// +/// In a SMP system, the hierarchy of CPUs is defined through three entities +/// that are used to describe the layout of physical CPUs in the system: +/// +/// - socket +/// - cluster +/// - core +/// - thread +/// +/// The bottom hierarchy level sits at core or thread level depending on whether +/// symmetric multi-threading (SMT) is supported or not. +/// +/// For instance in a system where CPUs support SMT, "cpu" nodes represent all +/// threads existing in the system and map to the hierarchy level "thread" +/// above. In systems where SMT is not supported "cpu" nodes represent all cores +/// present in the system and map to the hierarchy level "core" above. +/// +/// CPU topology bindings allow one to associate cpu nodes with hierarchical +/// groups corresponding to the system hierarchy; syntactically they are defined +/// as device tree nodes. +/// +/// Currently, only ARM/RISC-V intend to use this cpu topology binding but it +/// may be used for any other architecture as well. +/// +/// The cpu nodes, as per bindings defined in [4][4], represent the devices that +/// correspond to physical CPUs and are to be mapped to the hierarchy levels. +/// +/// A topology description containing phandles to cpu nodes that are not +/// compliant with bindings standardized in [4][4] is therefore considered invalid. +/// +/// [4]: https://www.devicetree.org/specifications/ +pub struct CpuTopology<'a, P: ParserWithMode<'a> = (AlignedParser<'a>, Panic)> { + node: FallibleNode<'a, P>, +} + +impl<'a, P: ParserWithMode<'a>> CpuTopology<'a, P> { + /// Returns an iterator over all top-level [`CpuSocket`] children. Sockets are + /// optional for single-socket systems, so this may not return any sockets. + /// If that is the case, iterate over the clusters instead. + pub fn sockets(&self) -> P::Output> { + P::to_output(crate::tryblock!({ + Ok(CpuSocketIter { children: self.node.children()?.iter().filter(filter_sockets::

) }) + })) + } + + /// Returns an iterator over all top-level [`CpuCluster`] children. Clusters may be + /// contained underneath socket nodes, so if the iterator is empty, iterate + /// over the sockets instead. + pub fn clusters(&self) -> P::Output> { + P::to_output(crate::tryblock!({ + Ok(CpuClusterIter { children: self.node.children()?.iter().filter(filter_clusters::

) }) + })) + } +} + +fn filter_sockets<'a, P: ParserWithMode<'a>>(node: &Result, FdtError>) -> bool { + match node { + Ok(node) => matches!(node.name().map(|n| n.name), Ok(n) if n.starts_with("socket")), + _ => true, + } +} + +#[allow(missing_docs)] +pub struct CpuSocketIter<'a, P: ParserWithMode<'a> = (AlignedParser<'a>, Panic)> { + #[allow(clippy::type_complexity)] + children: core::iter::Filter< + NodeChildrenIter<'a, (P::Parser, NoPanic)>, + fn(&Result, FdtError>) -> bool, + >, +} + +impl<'a, P: ParserWithMode<'a>> Iterator for CpuSocketIter<'a, P> { + type Item = P::Output>; + + fn next(&mut self) -> Option { + match self.children.next()? { + Ok(node) => Some(P::to_output(Ok(CpuSocket { node }))), + Err(e) => Some(P::to_output(Err(e))), + } + } +} + +fn filter_clusters<'a, P: ParserWithMode<'a>>(node: &Result, FdtError>) -> bool { + match node { + Ok(node) => matches!(node.name().map(|n| n.name), Ok(n) if n.starts_with("cluster")), + _ => true, + } +} + +#[allow(missing_docs)] +pub struct CpuClusterIter<'a, P: ParserWithMode<'a> = (AlignedParser<'a>, Panic)> { + #[allow(clippy::type_complexity)] + children: core::iter::Filter< + NodeChildrenIter<'a, (P::Parser, NoPanic)>, + fn(&Result, FdtError>) -> bool, + >, +} + +impl<'a, P: ParserWithMode<'a>> Iterator for CpuClusterIter<'a, P> { + type Item = P::Output>; + + #[track_caller] + fn next(&mut self) -> Option { + match self.children.next()? { + Ok(node) => Some(P::to_output(Ok(CpuCluster { node }))), + Err(e) => Some(P::to_output(Err(e))), + } + } +} + +/// A physical CPU socket. +pub struct CpuSocket<'a, P: ParserWithMode<'a> = (AlignedParser<'a>, Panic)> { + node: FallibleNode<'a, P>, +} + +impl<'a, P: ParserWithMode<'a>> CpuSocket<'a, P> { + /// Returns the socket number for this particular socket, e.g. the `0` in + /// `socket0`. + pub fn id(&self) -> P::Output { + P::to_output(crate::tryblock!({ + match self.node.name()?.name.trim_start_matches("socket").parse() { + Ok(id) => Ok(id), + Err(_) => Err(FdtError::InvalidNodeName), + } + })) + } + + /// Returns an iterator over the [`CpuCluster`]s contained by this socket. + pub fn clusters(&self) -> P::Output> { + P::to_output(crate::tryblock!({ + Ok(CpuClusterIter { children: self.node.children()?.iter().filter(filter_clusters::

) }) + })) + } +} + +/// A CPU cluster that is made up of either one or more clusters, or one or more [`CpuCore`]s. +pub struct CpuCluster<'a, P: ParserWithMode<'a> = (AlignedParser<'a>, Panic)> { + node: FallibleNode<'a, P>, +} + +impl<'a, P: ParserWithMode<'a>> CpuCluster<'a, P> { + /// Returns the cluster number for this particular cluster, e.g. the `0` in + /// `cluster0`. + pub fn id(&self) -> P::Output { + P::to_output(crate::tryblock!({ + match self.node.name()?.name.trim_start_matches("cluster").parse() { + Ok(id) => Ok(id), + Err(_) => Err(FdtError::InvalidNodeName), + } + })) + } + + /// Returns an iterator over the [`CpuCore`]s contained by this cluster. + pub fn cores(&self) -> P::Output> { + P::to_output(crate::tryblock!({ + Ok(CpuCoreIter { children: self.node.children()?.iter().filter(filter_cores::

) }) + })) + } +} + +fn filter_cores<'a, P: ParserWithMode<'a>>(node: &Result, FdtError>) -> bool { + match node { + Ok(node) => matches!(node.name().map(|n| n.name), Ok(n) if n.starts_with("core")), + _ => true, + } +} + +#[allow(missing_docs)] +pub struct CpuCoreIter<'a, P: ParserWithMode<'a> = (AlignedParser<'a>, Panic)> { + #[allow(clippy::type_complexity)] + children: core::iter::Filter< + NodeChildrenIter<'a, (P::Parser, NoPanic)>, + fn(&Result, FdtError>) -> bool, + >, +} + +impl<'a, P: ParserWithMode<'a>> Iterator for CpuCoreIter<'a, P> { + type Item = P::Output>; + + #[track_caller] + fn next(&mut self) -> Option { + match self.children.next()? { + Ok(node) => Some(P::to_output(Ok(CpuCore { node }))), + Err(e) => Some(P::to_output(Err(e))), + } + } +} + +/// A physical CPU core, which may be described by a `cpu` node or a set of +/// threads if symmetric multithreading (SMT) is enabled. +pub struct CpuCore<'a, P: ParserWithMode<'a> = (AlignedParser<'a>, Panic)> { + node: FallibleNode<'a, P>, +} + +impl<'a, P: ParserWithMode<'a>> CpuCore<'a, P> { + /// Returns the core number for this particular core, e.g. the `0` in + /// `core0`. + pub fn id(&self) -> P::Output { + P::to_output(crate::tryblock!({ + match self.node.name()?.name.trim_start_matches("core").parse() { + Ok(id) => Ok(id), + Err(_) => Err(FdtError::InvalidNodeName), + } + })) + } + + /// If this core is described by a single physical CPU core (that is, if SMT + /// is not enabled), return the `/cpus/cpu@N` node that represents this + /// code. See [`Cpu`] for more details. If this returns [`None`], the core is + /// represented by one or more [`CpuThread`]s. + pub fn cpu(&self) -> P::Output>> { + P::to_output(crate::tryblock!({ + let phandle = match self.node.properties()?.find("cpu")? { + Some(property) => PHandle::new(property.as_value::()?), + None => return Ok(None), + }; + + Ok(Some(Cpu { + node: self + .node + .make_root()? + .resolve_phandle(phandle)? + .ok_or(FdtError::MissingPHandleNode(phandle.as_u32()))?, + })) + })) + } + + /// Returns an iterator over all threads described by this CPU core. If this + /// iterator does not return any [`CpuThread`]s, SMT is not enabled and the + /// core is described by a single [`Cpu`] which can be retreived by + /// [`CpuCore::cpu`]. + pub fn threads(&self) -> P::Output> { + P::to_output(crate::tryblock!({ + Ok(CpuThreadIter { children: self.node.children()?.iter().filter(filter_threads::

) }) + })) + } +} + +fn filter_threads<'a, P: ParserWithMode<'a>>(node: &Result, FdtError>) -> bool { + match node { + Ok(node) => matches!(node.name().map(|n| n.name), Ok(n) if n.starts_with("thread")), + _ => true, + } +} + +#[allow(missing_docs)] +pub struct CpuThreadIter<'a, P: ParserWithMode<'a> = (AlignedParser<'a>, Panic)> { + #[allow(clippy::type_complexity)] + children: core::iter::Filter< + NodeChildrenIter<'a, (P::Parser, NoPanic)>, + fn(&Result, FdtError>) -> bool, + >, +} + +impl<'a, P: ParserWithMode<'a>> Iterator for CpuThreadIter<'a, P> { + type Item = P::Output>; + + #[track_caller] + fn next(&mut self) -> Option { + match self.children.next()? { + Ok(node) => Some(P::to_output(Ok(CpuThread { node }))), + Err(e) => Some(P::to_output(Err(e))), + } + } +} + +/// A logical CPU thread of execution. A single [`CpuCore`] may contain multiple +/// threads if symmetric multithreading is enabled. +pub struct CpuThread<'a, P: ParserWithMode<'a> = (AlignedParser<'a>, Panic)> { + node: FallibleNode<'a, P>, +} + +impl<'a, P: ParserWithMode<'a>> CpuThread<'a, P> { + /// Returns the thread number for this particular thread, e.g. the `0` in + /// `thread0`. + pub fn id(&self) -> P::Output { + P::to_output(crate::tryblock!({ + match self.node.name()?.name.trim_start_matches("socket").parse() { + Ok(id) => Ok(id), + Err(_) => Err(FdtError::InvalidNodeName), + } + })) + } + + /// Returns the [`Cpu`] that is represented by this thread. + pub fn cpu(&self) -> P::Output> { + P::to_output(crate::tryblock!({ + let phandle = match self.node.properties()?.find("cpu")? { + Some(property) => PHandle::new(property.as_value::()?), + None => return Err(FdtError::MissingRequiredProperty("cpu")), + }; + + self.node + .make_root()? + .resolve_phandle(phandle)? + .map(|node| Cpu { node }) + .ok_or(FdtError::MissingPHandleNode(phandle.as_u32())) + })) + } +} diff --git a/src/nodes/memory.rs b/src/nodes/memory.rs new file mode 100644 index 0000000..c36ced7 --- /dev/null +++ b/src/nodes/memory.rs @@ -0,0 +1,342 @@ +use crate::{ + cell_collector::{BuildCellCollector, CellCollector, CollectCellsError}, + parsing::{aligned::AlignedParser, NoPanic, Panic, ParserWithMode}, + properties::{ + cells::{CellSizes, SizeCells}, + reg::Reg, + Compatible, + }, + FdtError, +}; + +use super::{AsNode, FallibleNode, NodeChildrenIter, NodeName}; + +/// [Devicetree 3.4. `/memory` +/// node](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#memory-node) +/// +/// A memory device node is required for all devicetrees and describes the +/// physical memory layout for the system. If a system has multiple ranges of +/// memory, multiple memory nodes can be created, or the ranges can be specified +/// in the `reg` property of a single memory node. +/// +/// The unit-name component of the node name (see [Section +/// 2.2.1](https://devicetree-specification.readthedocs.io/en/latest/chapter2-devicetree-basics.html#sect-node-names)) +/// shall be memory. +/// +/// The client program may access memory not covered by any memory reservations +/// (see [Section +/// 5.3](https://devicetree-specification.readthedocs.io/en/latest/chapter5-flattened-format.html#sect-fdt-memory-reservation-block)) +/// using any storage attributes it chooses. However, before changing the +/// storage attributes used to access a real page, the client program is +/// responsible for performing actions required by the architecture and +/// implementation, possibly including flushing the real page from the caches. +/// The boot program is responsible for ensuring that, without taking any action +/// associated with a change in storage attributes, the client program can +/// safely access all memory (including memory covered by memory reservations) +/// as `WIMG = 0b001x`. That is: +/// +/// * not Write Through Required +/// * not Caching Inhibited +/// * Memory Coherence +/// * Required either not Guarded or Guarded +/// +/// If the VLE storage attribute is supported, with `VLE=0`. +#[derive(Debug, Clone, Copy)] +pub struct Memory<'a, P: ParserWithMode<'a> = (AlignedParser<'a>, Panic)> { + pub(crate) node: FallibleNode<'a, P>, +} + +impl<'a, P: ParserWithMode<'a>> Memory<'a, P> { + /// [Devicetree 3.4. `/memory` + /// node](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#memory-node) + /// + /// **Required** + /// + /// Consists of an arbitrary number of address and size pairs that specify + /// the physical address and size of the memory ranges. + #[track_caller] + pub fn reg(&self) -> P::Output> { + P::to_output(self.node.reg().and_then(|m| m.ok_or(FdtError::MissingRequiredNode("reg")))) + } + + /// [Devicetree 3.4. `/memory` + /// node](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#memory-node) + /// + /// **Optional** + /// + /// Specifies the address and size of the Initial Mapped Area + /// + /// A `prop-encoded-array` consisting of a triplet of (effective address, + /// physical address, size). The effective and physical address shall each + /// be 64-bit (`` value), and the size shall be 32-bits (`` + /// value). + #[track_caller] + pub fn initial_mapped_area(&self) -> P::Output> { + P::to_output(crate::tryblock!({ + match self.node.properties()?.find("initial-mapped-area")? { + Some(prop) => { + let value = prop.value; + if value.len() != (/* effective address */8 + /* physical address */ 8 + /* size */ 4) { + return Err(FdtError::InvalidPropertyValue); + } + + Ok(Some(MappedArea { + effective_address: u64::from_be_bytes(value[0..8].try_into().unwrap()), + physical_address: u64::from_be_bytes(value[8..16].try_into().unwrap()), + size: u32::from_be_bytes(value[16..20].try_into().unwrap()), + })) + } + None => Ok(None), + } + })) + } + + /// [Devicetree 3.4. `/memory` + /// node](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#memory-node) + /// + /// Specifies an explicit hint to the operating system that this memory may + /// potentially be removed later. + #[track_caller] + pub fn hotpluggable(&self) -> P::Output { + P::to_output(crate::tryblock!({ Ok(self.node.properties()?.find("hotpluggable")?.is_some()) })) + } +} + +impl<'a, P: ParserWithMode<'a>> AsNode<'a, P> for Memory<'a, P> { + fn as_node(&self) -> super::Node<'a, P> { + self.node.alt() + } +} + +/// Describes the initial mapped area of the `/memory` node. See +/// [`Memory::initial_mapped_area`]. +#[allow(missing_docs)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct MappedArea { + pub effective_address: u64, + pub physical_address: u64, + pub size: u32, +} + +/// [Devicetree 3.5. `/reserved-memory` +/// node](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#reserved-memory-node) +/// +/// Reserved memory is specified as a node under the `/reserved-memory` node. The +/// operating system shall exclude reserved memory from normal usage. One can +/// create child nodes describing particular reserved (excluded from normal use) +/// memory regions. Such memory regions are usually designed for the special +/// usage by various device drivers. +pub struct ReservedMemory<'a, P: ParserWithMode<'a> = (AlignedParser<'a>, Panic)> { + pub(crate) node: FallibleNode<'a, P>, +} + +impl<'a, P: ParserWithMode<'a>> ReservedMemory<'a, P> { + #[inline] + #[track_caller] + #[allow(missing_docs)] + pub fn cell_sizes(&self) -> P::Output { + P::to_output( + self.node + .property::() + .and_then(|c| c.ok_or(FdtError::MissingRequiredNode("#address-cells/#size-cells"))), + ) + } + + #[inline] + #[track_caller] + #[allow(missing_docs)] + pub fn children(&self) -> P::Output> { + P::to_output(crate::tryblock!({ Ok(ReservedMemoryChildrenIter { children: self.node.children()?.iter() }) })) + } +} + +impl<'a, P: ParserWithMode<'a>> AsNode<'a, P> for ReservedMemory<'a, P> { + fn as_node(&self) -> super::Node<'a, P> { + self.node.alt() + } +} + +#[allow(missing_docs)] +pub struct ReservedMemoryChildrenIter<'a, P: ParserWithMode<'a> = (AlignedParser<'a>, Panic)> { + children: NodeChildrenIter<'a, (P::Parser, NoPanic)>, +} + +impl<'a, P: ParserWithMode<'a>> Iterator for ReservedMemoryChildrenIter<'a, P> { + type Item = P::Output>; + + #[track_caller] + fn next(&mut self) -> Option { + match self.children.next()? { + Ok(node) => Some(P::to_output(Ok(ReservedMemoryChild { node }))), + Err(e) => Some(P::to_output(Err(e))), + } + } +} + +#[allow(missing_docs)] +pub struct ReservedMemoryChild<'a, P: ParserWithMode<'a> = (AlignedParser<'a>, Panic)> { + node: FallibleNode<'a, P>, +} + +impl<'a, P: ParserWithMode<'a>> ReservedMemoryChild<'a, P> { + #[allow(missing_docs)] + pub fn name(&self) -> P::Output> { + P::to_output(self.node.name()) + } + + /// [Devicetree 3.5.2. `/reserved-memory` child + /// nodes](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#table-5) + /// + /// **Optional** + /// + /// Consists of an arbitrary number of address and size pairs that specify + /// the physical address and size of the memory ranges. + pub fn reg(&self) -> P::Output>> { + P::to_output(self.node.reg()) + } + + /// [Devicetree 3.5.2. `/reserved-memory` child + /// nodes](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#table-5) + /// + /// **Optional** + /// + /// Size in bytes of memory to reserve for dynamically allocated regions. + /// Size of this property is based on parent node’s `#size-cells` property. + pub fn size(&self) -> P::Output>> { + P::to_output(crate::tryblock!({ + let Some(size) = self.node.properties()?.find("size")? else { + return Ok(None); + }; + + // Unwrap: nodes will always have parents because they are created + // from the `NodeChildrenIter` struct + let size_cells = self.node.parent().unwrap().property::()?.unwrap_or(SizeCells(1)); + + if size.value.len() % size_cells.0 != 0 { + return Err(FdtError::InvalidPropertyValue); + } + + let mut builder = ::Builder::default(); + + for component in size.value.chunks_exact(4) { + if builder.push(u32::from_be_bytes(component.try_into().unwrap())).is_err() { + return Ok(Some(Err(CollectCellsError))); + } + } + + Ok(Some(Ok(C::map(builder.finish())))) + })) + } + + /// [Devicetree 3.5.2. `/reserved-memory` child + /// nodes](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#table-5) + /// + /// **Optional** + /// + /// Address boundary for alignment of allocation. Size of this property is + /// based on parent node’s `#size-cells` property. + pub fn alignment(&self) -> P::Output>> { + P::to_output(crate::tryblock!({ + let Some(alignment) = self.node.properties()?.find("alignment")? else { + return Ok(None); + }; + + // Unwrap: nodes will always have parents because they are created + // from the `NodeChildrenIter` struct + let size_cells = self.node.parent().unwrap().property::()?.unwrap_or(SizeCells(1)); + + if alignment.value.len() % size_cells.0 != 0 { + return Err(FdtError::InvalidPropertyValue); + } + + let mut builder = ::Builder::default(); + + for component in alignment.value.chunks_exact(4) { + if builder.push(u32::from_be_bytes(component.try_into().unwrap())).is_err() { + return Ok(Some(Err(CollectCellsError))); + } + } + + Ok(Some(Ok(C::map(builder.finish())))) + })) + } + + /// [Devicetree 3.5.2. `/reserved-memory` child + /// nodes](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#table-5) + /// + /// **Optional** + /// + /// May contain the following strings: + /// + /// * `shared-dma-pool`: This indicates a region of memory meant to be used + /// as a shared pool of DMA buffers for a set of devices. It can be used by + /// an operating system to instantiate the necessary pool management + /// subsystem if necessary. + /// + /// * vendor specific string in the form `,[-]` + pub fn compatible(&self) -> P::Output>> { + P::to_output(self.node.property::>()) + } + + /// [Devicetree 3.5.2. `/reserved-memory` child + /// nodes](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#table-5) + /// + /// **Optional** + /// + /// If present, indicates the operating system must not create a virtual + /// mapping of the region as part of its standard mapping of system memory, + /// nor permit speculative access to it under any circumstances other than + /// under the control of the device driver using the region. + pub fn no_map(&self) -> P::Output { + P::to_output(self.node.properties().and_then(|p| p.find("no-map").map(|p| p.is_some()))) + } + + /// [Devicetree 3.5.2. `/reserved-memory` child + /// nodes](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#table-5) + /// + /// **Optional** + /// + /// The operating system can use the memory in this region with the + /// limitation that the device driver(s) owning the region need to be able + /// to reclaim it back. Typically that means that the operating system can + /// use that region to store volatile or cached data that can be otherwise + /// regenerated or migrated elsewhere. + pub fn reusable(&self) -> P::Output { + P::to_output(self.node.properties().and_then(|p| p.find("no-map").map(|p| p.is_some()))) + } + + /// [Devicetree 3.5.2. `/reserved-memory` child + /// nodes](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#table-5) + /// + /// If a `linux,cma-default` property is present, then Linux will use the + /// region for the default pool of the contiguous memory allocator. + #[cfg(feature = "linux-dt-bindings")] + pub fn cma_default(&self) -> P::Output { + P::to_output(self.node.properties().and_then(|p| p.find("no-map").map(|p| p.is_some()))) + } + + /// [Devicetree 3.5.2. `/reserved-memory` child + /// nodes](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#table-5) + /// + /// If a `linux,dma-default` property is present, then Linux will use the + /// region for the default pool of the consistent DMA allocator. + #[cfg(feature = "linux-dt-bindings")] + pub fn dma_default(&self) -> P::Output { + P::to_output(self.node.properties().and_then(|p| p.find("no-map").map(|p| p.is_some()))) + } +} + +impl<'a, P: ParserWithMode<'a>> AsNode<'a, P> for ReservedMemoryChild<'a, P> { + fn as_node(&self) -> super::Node<'a, P> { + self.node.alt() + } +} + +/// A memory region. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct MemoryRegion { + #[allow(missing_docs)] + pub starting_address: u64, + #[allow(missing_docs)] + pub size: Option, +} diff --git a/src/nodes/root.rs b/src/nodes/root.rs new file mode 100644 index 0000000..20ea7f9 --- /dev/null +++ b/src/nodes/root.rs @@ -0,0 +1,506 @@ +use super::{ + aliases::Aliases, + chosen::Chosen, + cpus::Cpus, + memory::{Memory, ReservedMemory}, + AsNode, IntoSearchableNodeName, Node, RawNode, SearchableNodeName, +}; +use crate::{ + helpers::{FallibleNode, FallibleRoot}, + parsing::{aligned::AlignedParser, BigEndianToken, NoPanic, Panic, ParseError, Parser, ParserWithMode}, + properties::{cells::CellSizes, Compatible, PHandle, Property}, + FdtError, +}; + +/// [Devicetree 3.2. Root +/// node](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#root-node) +/// +/// The devicetree has a single root node of which all other device nodes are +/// descendants. The full path to the root node is `/`. +pub struct Root<'a, P: ParserWithMode<'a> = (AlignedParser<'a>, Panic)> { + pub(crate) node: FallibleNode<'a, P>, +} + +impl<'a, P: ParserWithMode<'a>> Root<'a, P> { + /// [Devicetree 3.2. Root + /// node](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#root-node) + /// + /// **Required** + /// + /// Specifies the number of cells to represent the address and length + /// in the reg property in children of root. + #[track_caller] + pub fn cell_sizes(self) -> P::Output { + P::to_output(crate::tryblock!({ + self.node.property::()?.ok_or(FdtError::MissingRequiredProperty("#address-cells/#size-cells")) + })) + } + + /// [Devicetree 3.2. Root + /// node](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#root-node) + /// + /// **Required** + /// + /// Specifies a string that uniquely identifies the model of the system + /// board. The recommended format is "manufacturer,model-number". + #[track_caller] + pub fn model(self) -> P::Output<&'a str> { + P::to_output(crate::tryblock!({ + let node = self.node.fallible(); + node.properties()?.find("model").and_then(|p| { + p.ok_or(FdtError::MissingRequiredProperty("model"))?.as_value::<&'a str>().map_err(Into::into) + }) + })) + } + + /// [Devicetree 3.2. Root + /// node](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#root-node) + /// + /// **Required** + /// + /// Specifies a list of platform architectures with which this platform is + /// compatible. This property can be used by operating systems in selecting + /// platform specific code. The recommended form of the property value is: + /// `"manufacturer,model"` + /// + /// For example: `compatible = "fsl,mpc8572ds"` + #[track_caller] + pub fn compatible(&self) -> P::Output> { + P::to_output(crate::tryblock!({ + >::parse(self.node.fallible(), self.node.make_root()?)? + .ok_or(FdtError::MissingRequiredProperty("compatible")) + })) + } + + /// [Devicetree 3.2. Root + /// node](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#root-node) + /// + /// **Optional** + /// + /// Specifies a string representing the device’s serial number. + #[track_caller] + pub fn serial_number(&self) -> P::Output> { + P::to_output(crate::tryblock!({ + match self.node.fallible().properties()?.find("serial-number")? { + Some(prop) => Ok(Some(prop.as_value()?)), + None => Ok(None), + } + })) + } + + /// [Devicetree 3.2. Root + /// node](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#root-node) + /// + /// **Optional, but Recommended** + /// + /// Specifies a string that identifies the form-factor of the system. The + /// property value can be one of: + /// + /// * "desktop" + /// * "laptop" + /// * "convertible" + /// * "server" + /// * "tablet" + /// * "handset" + /// * "watch" + /// * "embedded" + #[track_caller] + pub fn chassis_type(&self) -> P::Output> { + P::to_output(crate::tryblock!({ + match self.node.fallible().properties()?.find("serial-number")? { + Some(prop) => Ok(Some(prop.as_value()?)), + None => Ok(None), + } + })) + } + + /// [Devicetree 3.3. `/aliases` + /// node](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#aliases-node) + /// + /// **Required**s + /// + /// A devicetree may have an aliases node (`/aliases`) that defines one or + /// more alias properties. The alias node shall be at the root of the + /// devicetree and have the node name `/aliases`. + /// + /// Each property of the `/aliases` node defines an alias. The property name + /// specifies the alias name. The property value specifies the full path to + /// a node in the devicetree. For example, the property `serial0 = + /// "/simple-bus@fe000000/serial@llc500"` defines the alias `serial0`. + #[track_caller] + pub fn aliases(&self) -> P::Output>> { + P::to_output(crate::tryblock!({ + let this: FallibleRoot<'a, P> = Root { node: self.node }; + match this.find_node("/aliases")? { + Some(node) => Ok(Some(Aliases { node })), + None => Ok(None), + } + })) + } + + /// [Devicetree 3.6. `/chosen` + /// Node](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#chosen-node) + /// + /// **Required** + /// + /// The `/chosen` node does not represent a real device in the system but + /// describes parameters chosen or specified by the system firmware at run + /// time. It shall be a child of the root node. + #[track_caller] + pub fn chosen(&self) -> P::Output> { + P::to_output(crate::tryblock!({ + let this: FallibleRoot<'a, P> = Root { node: self.node }; + match this.find_node("/chosen")? { + Some(node) => Ok(Chosen { node }), + None => Err(FdtError::MissingRequiredNode("/chosen")), + } + })) + } + + /// [Devicetree 3.7. + /// `/cpus`](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#cpus-node-properties) + /// + /// **Required** + /// + /// A `/cpus` node is required for all devicetrees. It does not represent a + /// real device in the system, but acts as a container for child cpu nodes + /// which represent the systems CPUs. + #[track_caller] + pub fn cpus(&self) -> P::Output> { + P::to_output(crate::tryblock!({ + let this: FallibleRoot<'a, P> = Root { node: self.node }; + match this.find_node("/cpus")? { + Some(node) => Ok(Cpus { node }), + None => Err(FdtError::MissingRequiredNode("/cpus")), + } + })) + } + + /// [Devicetree 3.4. `/memory` + /// node](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#memory-node) + /// + /// **Required** + /// + /// A memory device node is required for all devicetrees and describes the + /// physical memory layout for the system. If a system has multiple ranges + /// of memory, multiple memory nodes can be created, or the ranges can be + /// specified in the `reg` property of a single memory node. + #[track_caller] + pub fn memory(&self) -> P::Output> { + P::to_output(crate::tryblock!({ + let this: FallibleRoot<'a, P> = Root { node: self.node }; + match this.find_node("/memory")? { + Some(node) => Ok(Memory { node }), + None => Err(FdtError::MissingRequiredNode("/memory")), + } + })) + } + + /// [Devicetree 3.5. `/reserved-memory` + /// node](https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#reserved-memory-node) + /// + /// Reserved memory is specified as a node under the `/reserved-memory` + /// node. The operating system shall exclude reserved memory from normal + /// usage. One can create child nodes describing particular reserved + /// (excluded from normal use) memory regions. Such memory regions are + /// usually designed for the special usage by various device drivers. + #[track_caller] + pub fn reserved_memory(&self) -> P::Output> { + P::to_output(crate::tryblock!({ + let this: FallibleRoot<'a, P> = Root { node: self.node }; + match this.find_node("/reserved-memory")? { + Some(node) => Ok(ReservedMemory { node }), + None => Err(FdtError::MissingRequiredNode("/reserved-memory")), + } + })) + } + + /// Attempt to resolve a [`PHandle`] to the node containing a `phandle` + /// property with the value + #[track_caller] + pub fn resolve_phandle(&self, phandle: PHandle) -> P::Output>> { + P::to_output(crate::tryblock!({ + let this: FallibleRoot<'a, P> = Root { node: self.node.fallible() }; + for node in this.all_nodes()? { + let (_, node) = node?; + if node.property::()? == Some(phandle) { + return Ok(Some(node.alt())); + } + } + + Ok(None) + })) + } + + /// Returns an iterator that yields every node with the name that matches + /// `name` in depth-first order + #[track_caller] + pub fn find_all_nodes_with_name<'b>(self, name: &'b str) -> P::Output> { + P::to_output(crate::tryblock!({ + let this: FallibleRoot<'a, P> = Root { node: self.node }; + Ok(AllNodesWithNameIter { iter: this.all_nodes()?, name }) + })) + } + + /// Attempt to find a node with the given name, returning the first node + /// with a name that matches `name` in depth-first order + #[track_caller] + pub fn find_node_by_name(self, name: &str) -> P::Output>> { + P::to_output(crate::tryblock!({ + let this: FallibleRoot<'a, P> = Root { node: self.node }; + this.find_all_nodes_with_name(name)?.next().transpose().map(|n| n.map(|n| n.alt())) + })) + } + + /// Attempt to find a node with the given path (with an optional unit + /// address, defaulting to the first matching name if omitted). If you only + /// have the node name but not the path, use [`Root::find_node_by_name`] instead. + #[track_caller] + pub fn find_node(self, path: &str) -> P::Output>> { + if path == "/" { + return P::to_output(Ok(Some(self.node.alt()))); + } + + let fallible_self: FallibleRoot<'a, P> = Root { node: self.node.fallible() }; + + let mut current_depth = 1; + let mut all_nodes = match fallible_self.all_nodes() { + Ok(iter) => iter, + Err(e) => return P::to_output(Err(e)), + }; + + let mut found_node = None; + 'outer: for component in path.trim_start_matches('/').split('/') { + let component_name = IntoSearchableNodeName::into_searchable_node_name(component); + + loop { + let (depth, next_node) = match all_nodes.next() { + Some(Ok(next)) => next, + Some(Err(e)) => return P::to_output(Err(e)), + None => return P::to_output(Ok(None)), + }; + + if depth < current_depth { + return P::to_output(Ok(None)); + } + + let name = match next_node.name() { + Ok(name) => name, + Err(e) => return P::to_output(Err(e)), + }; + + let name_eq = match component_name { + SearchableNodeName::Base(cname) => cname == name.name, + SearchableNodeName::WithUnitAddress(cname) => cname == name, + }; + + if name_eq { + found_node = Some(next_node); + current_depth = depth; + continue 'outer; + } + } + } + + P::to_output(Ok(found_node.map(|n| n.alt::

()))) + } + + /// Returns an iterator over every node within the devicetree which is + /// compatible with at least one of the compatible strings contained within + /// `with` + #[track_caller] + pub fn all_compatible<'b>(self, with: &'b [&str]) -> P::Output> { + P::to_output(crate::tryblock!({ + let this: FallibleRoot<'a, P> = Root { node: self.node }; + let f: fn(_) -> _ = |node: Result<(usize, FallibleNode<'a, P>), FdtError>| match node + .and_then(|(_, n)| Ok((n, n.property::()?))) + { + Ok((n, compatible)) => Some(Ok((n, compatible?))), + Err(e) => Some(Err(e)), + }; + + let iter = this.all_nodes()?.filter_map(f); + + Ok(AllCompatibleIter { iter, with }) + })) + } + + /// Returns an iterator over each node in the tree, depth-first, along with + /// its depth in the tree + #[track_caller] + pub fn all_nodes(self) -> P::Output> { + let mut parser = P::new(self.node.this.as_slice(), self.node.strings, self.node.structs); + let res = crate::tryblock!({ + parser.advance_cstr()?; + + while parser.peek_token()? == BigEndianToken::PROP { + parser.parse_raw_property()?; + } + + Ok(()) + }); + + if let Err(e) = res { + return P::to_output(Err(e)); + } + + P::to_output(Ok(AllNodesIter { + parser, + parents: [ + self.node.this.as_slice(), + &[], + &[], + &[], + &[], + &[], + &[], + &[], + &[], + &[], + &[], + &[], + &[], + &[], + &[], + &[], + ], + parent_index: 0, + })) + } +} + +impl<'a, P: ParserWithMode<'a>> AsNode<'a, P> for Root<'a, P> { + fn as_node(&self) -> Node<'a, P> { + self.node.alt() + } +} + +impl<'a, P: ParserWithMode<'a>> Copy for Root<'a, P> {} +impl<'a, P: ParserWithMode<'a>> Clone for Root<'a, P> { + fn clone(&self) -> Self { + *self + } +} + +impl<'a, P: ParserWithMode<'a>> core::fmt::Debug for Root<'a, P> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("Root").finish_non_exhaustive() + } +} + +#[allow(missing_docs)] +pub struct AllNodesWithNameIter<'a, 'b, P: ParserWithMode<'a>> { + pub(crate) iter: AllNodesIter<'a, (P::Parser, NoPanic)>, + pub(crate) name: &'b str, +} + +impl<'a, 'b, P: ParserWithMode<'a>> Iterator for AllNodesWithNameIter<'a, 'b, P> { + type Item = P::Output>; + + #[track_caller] + fn next(&mut self) -> Option { + for next in self.iter.by_ref() { + match next.and_then(|(_, n)| Ok((n, n.name()?))) { + Ok((node, name)) => match name.name == self.name { + true => return Some(P::to_output(Ok(node.alt()))), + false => continue, + }, + Err(e) => return Some(P::to_output(Err(e))), + } + } + + None + } +} + +/// See [`Root::all_compatible`] +pub struct AllCompatibleIter<'a, 'b, P: ParserWithMode<'a>> { + #[allow(clippy::type_complexity)] + pub(crate) iter: core::iter::FilterMap< + AllNodesIter<'a, (P::Parser, NoPanic)>, + fn( + Result<(usize, FallibleNode<'a, P>), FdtError>, + ) -> Option, Compatible<'a>), FdtError>>, + >, + pub(crate) with: &'b [&'b str], +} + +impl<'a, 'b, P: ParserWithMode<'a>> Iterator for AllCompatibleIter<'a, 'b, P> { + type Item = P::Output>; + + #[track_caller] + fn next(&mut self) -> Option { + for next in self.iter.by_ref() { + match next { + Ok((node, compatible)) => match self.with.iter().copied().any(|c| compatible.compatible_with(c)) { + true => return Some(P::to_output(Ok(node.alt()))), + false => continue, + }, + Err(e) => return Some(P::to_output(Err(e))), + } + } + + None + } +} + +#[allow(missing_docs)] +pub struct AllNodesIter<'a, P: ParserWithMode<'a>> { + pub(crate) parser: P, + pub(crate) parents: [&'a [

>::Granularity]; 16], + pub(crate) parent_index: usize, +} + +impl<'a, P: ParserWithMode<'a>> Iterator for AllNodesIter<'a, P> { + type Item = P::Output<(usize, Node<'a, P>)>; + + #[track_caller] + fn next(&mut self) -> Option { + while let Ok(BigEndianToken::END_NODE) = self.parser.peek_token() { + let _ = self.parser.advance_token(); + self.parent_index = self.parent_index.saturating_sub(1); + } + + match self.parser.advance_token() { + Ok(BigEndianToken::BEGIN_NODE) => self.parent_index += 1, + Ok(BigEndianToken::END) | Err(FdtError::ParseError(ParseError::UnexpectedEndOfData)) => return None, + Ok(_) => return Some(P::to_output(Err(FdtError::ParseError(ParseError::UnexpectedToken)))), + Err(e) => return Some(P::to_output(Err(e))), + } + + let starting_data = self.parser.data(); + + match self.parents.get_mut(self.parent_index) { + Some(idx) => *idx = starting_data, + // FIXME: what makes sense for this to return? + None => return None, + } + + let node = Some(P::to_output(Ok(( + self.parent_index, + Node { + this: RawNode::new(starting_data), + parent: self.parents.get(self.parent_index.saturating_sub(1)).map(|parent| RawNode::new(parent)), + strings: self.parser.strings(), + structs: self.parser.structs(), + _mode: core::marker::PhantomData, + }, + )))); + + let res = crate::tryblock!({ + self.parser.advance_cstr()?; + + while self.parser.peek_token()? == BigEndianToken::PROP { + self.parser.parse_raw_property()?; + } + + Ok(()) + }); + + if let Err(e) = res { + return Some(P::to_output(Err(e))); + } + + node + } +} diff --git a/src/parsing.rs b/src/parsing.rs index 526886b..de5e508 100644 --- a/src/parsing.rs +++ b/src/parsing.rs @@ -2,108 +2,392 @@ // v. 2.0. If a copy of the MPL was not distributed with this file, You can // obtain one at https://mozilla.org/MPL/2.0/. -use core::convert::TryInto; -use core::ffi::CStr as FfiCStr; +pub mod aligned; +pub mod unaligned; -pub struct CStr<'a>(&'a FfiCStr); +use crate::{ + nodes::{Node, RawNode}, + FdtError, FdtHeader, +}; -impl<'a> CStr<'a> { - pub fn new(data: &'a [u8]) -> Option { - Some(Self(FfiCStr::from_bytes_until_nul(data).ok()?)) +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(transparent)] +pub struct BigEndianU32(u32); + +impl BigEndianU32 { + pub const fn from_ne(n: u32) -> Self { + Self(n.to_be()) + } + + pub const fn from_le(n: u32) -> Self { + Self(u32::from_le(n).to_be()) } - /// Does not include the null terminating byte - pub fn len(&self) -> usize { - self.0.to_bytes().len().saturating_sub(1) + pub const fn from_be(n: u32) -> Self { + Self(n) } - pub fn as_str(&self) -> Option<&'a str> { - self.0.to_str().ok() + pub const fn to_ne(self) -> u32 { + u32::from_be(self.0) + } + + pub const fn to_be(self) -> u32 { + self.0 } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[repr(transparent)] -pub struct BigEndianU32(u32); +pub struct BigEndianToken(pub(crate) BigEndianU32); -impl BigEndianU32 { - pub fn get(self) -> u32 { - self.0 +impl BigEndianToken { + pub const BEGIN_NODE: Self = Self(BigEndianU32::from_ne(1)); + pub const END_NODE: Self = Self(BigEndianU32::from_ne(2)); + pub const PROP: Self = Self(BigEndianU32::from_ne(3)); + pub const NOP: Self = Self(BigEndianU32::from_ne(4)); + pub const END: Self = Self(BigEndianU32::from_ne(9)); +} + +pub(crate) struct Stream<'a, T: Copy>(&'a [T]); + +impl<'a, T: Copy> Stream<'a, T> { + #[inline(always)] + pub(crate) fn new(data: &'a [T]) -> Self { + Self(data) } - pub(crate) fn from_bytes(bytes: &[u8]) -> Option { - Some(BigEndianU32(u32::from_be_bytes(bytes.get(..4)?.try_into().unwrap()))) + #[inline(always)] + pub(crate) fn advance(&mut self) -> Option { + let ret = *self.0.first()?; + self.0 = self.0.get(1..)?; + Some(ret) + } + + pub(crate) fn skip_many(&mut self, n: usize) { + self.0 = self.0.get(n..).unwrap_or_default(); + } +} + +impl<'a, T: Copy> Clone for Stream<'a, T> { + fn clone(&self) -> Self { + Self(self.0) } } #[derive(Debug, Clone, Copy)] -#[repr(transparent)] -pub struct BigEndianU64(u64); +pub enum ParseError { + NumericConversionError, + InvalidCStrValue, + InvalidPropertyValue, + InvalidTokenValue, + UnexpectedToken, + UnexpectedEndOfData, +} -impl BigEndianU64 { - pub fn get(&self) -> u64 { - self.0 +impl core::fmt::Display for ParseError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::InvalidCStrValue => write!(f, "cstr was either non-terminated or invalid ASCII"), + Self::InvalidPropertyValue => write!(f, "invalid property value"), + Self::InvalidTokenValue => { + write!(f, "encountered invalid FDT token value while parsing") + } + Self::NumericConversionError => { + write!(f, "u32 value too large for usize (this should only occur on 16-bit platforms)") + } + Self::UnexpectedEndOfData => { + write!(f, "encountered end of data while parsing but expected more") + } + Self::UnexpectedToken => { + write!(f, "encountered an unexpected FDT token value while parsing") + } + } } +} + +pub trait PanicMode: crate::sealed::Sealed { + type Output; + fn to_output(result: Result) -> Self::Output; +} - pub(crate) fn from_bytes(bytes: &[u8]) -> Option { - Some(BigEndianU64(u64::from_be_bytes(bytes.get(..8)?.try_into().unwrap()))) +#[derive(Clone, Copy, Default)] +pub struct NoPanic; + +impl crate::sealed::Sealed for NoPanic {} +impl PanicMode for NoPanic { + type Output = Result; + + #[inline(always)] + fn to_output(result: Result) -> Self::Output { + result } } -#[derive(Debug, Clone, Copy)] -pub struct FdtData<'a> { - bytes: &'a [u8], +#[derive(Clone, Copy, Default)] +pub struct Panic; + +impl crate::sealed::Sealed for Panic {} +impl PanicMode for Panic { + type Output = T; + + #[track_caller] + #[inline(always)] + fn to_output(result: Result) -> Self::Output { + result.unwrap() + } } -impl<'a> FdtData<'a> { - pub fn new(bytes: &'a [u8]) -> Self { - Self { bytes } +pub trait ParserWithMode<'a>: Parser<'a> + PanicMode + crate::sealed::Sealed { + type Parser: Parser<'a, Granularity = Self::Granularity>; + type Mode: PanicMode + Clone + Default; + + fn into_parts(self) -> (>::Parser, >::Mode); +} + +impl<'a, T: Parser<'a>, U: PanicMode> crate::sealed::Sealed for (T, U) {} + +impl<'a, T: Parser<'a>, U: PanicMode + Clone + Default> Parser<'a> for (T, U) { + type Granularity = T::Granularity; + + fn new( + data: &'a [Self::Granularity], + strings: StringsBlock<'a>, + structs: StructsBlock<'a, Self::Granularity>, + ) -> Self { + (T::new(data, strings, structs), U::default()) } - pub fn u32(&mut self) -> Option { - let ret = BigEndianU32::from_bytes(self.bytes)?; - self.skip(4); + fn data(&self) -> &'a [Self::Granularity] { + self.0.data() + } - Some(ret) + fn byte_data(&self) -> &'a [u8] { + self.0.byte_data() } - pub fn u64(&mut self) -> Option { - let ret = BigEndianU64::from_bytes(self.bytes)?; - self.skip(8); + fn strings(&self) -> StringsBlock<'a> { + self.0.strings() + } - Some(ret) + fn structs(&self) -> StructsBlock<'a, Self::Granularity> { + self.0.structs() + } + + fn advance_token(&mut self) -> Result { + self.0.advance_token() } - pub fn skip(&mut self, n_bytes: usize) { - self.bytes = self.bytes.get(n_bytes..).unwrap_or_default() + fn advance_u32(&mut self) -> Result { + self.0.advance_u32() } - pub fn remaining(&self) -> &'a [u8] { - self.bytes + fn advance_cstr(&mut self) -> Result<&'a core::ffi::CStr, FdtError> { + self.0.advance_cstr() } - pub fn peek_u32(&self) -> Option { - Self::new(self.remaining()).u32() + fn advance_aligned(&mut self, n: usize) { + self.0.advance_aligned(n) } +} + +impl<'a, P: Parser<'a>, U: PanicMode> PanicMode for (P, U) { + type Output = U::Output; + + #[track_caller] + fn to_output(result: Result) -> Self::Output { + U::to_output(result) + } +} + +impl<'a, T: Parser<'a>, U: PanicMode + Clone + Default + 'static> ParserWithMode<'a> for (T, U) { + type Mode = U; + type Parser = T; + + fn into_parts(self) -> (>::Parser, >::Mode) { + self + } +} + +pub trait Parser<'a>: crate::sealed::Sealed + Clone { + type Granularity: Copy + core::fmt::Debug; + + fn new( + data: &'a [Self::Granularity], + strings: StringsBlock<'a>, + structs: StructsBlock<'a, Self::Granularity>, + ) -> Self; + fn data(&self) -> &'a [Self::Granularity]; + fn byte_data(&self) -> &'a [u8]; + fn strings(&self) -> StringsBlock<'a>; + fn structs(&self) -> StructsBlock<'a, Self::Granularity>; + + fn advance_token(&mut self) -> Result; + fn peek_token(&mut self) -> Result { + self.clone().advance_token() + } + + fn advance_u32(&mut self) -> Result; + fn advance_cstr(&mut self) -> Result<&'a core::ffi::CStr, FdtError>; + fn advance_aligned(&mut self, n: usize); + + fn peek_u32(&self) -> Result { + self.clone().advance_u32() + } + + fn parse_header(&mut self) -> Result { + let magic = self.advance_u32()?.to_ne(); + let total_size = self.advance_u32()?.to_ne(); + let struct_offset = self.advance_u32()?.to_ne(); + let strings_offset = self.advance_u32()?.to_ne(); + let memory_reserve_map_offset = self.advance_u32()?.to_ne(); + let version = self.advance_u32()?.to_ne(); + let last_compatible_version = self.advance_u32()?.to_ne(); + let boot_cpuid = self.advance_u32()?.to_ne(); + let strings_size = self.advance_u32()?.to_ne(); + let structs_size = self.advance_u32()?.to_ne(); + + Ok(FdtHeader { + magic, + total_size, + structs_offset: struct_offset, + strings_offset, + memory_reserve_map_offset, + version, + last_compatible_version, + boot_cpuid, + strings_size, + structs_size, + }) + } + + fn parse_root(&mut self) -> Result, FdtError> + where + Self: ParserWithMode<'a>, + { + match self.advance_token()? { + BigEndianToken::BEGIN_NODE => {} + _ => return Err(FdtError::ParseError(ParseError::UnexpectedToken)), + } + + let starting_data = self.data(); + + let byte_data = self.byte_data(); + match byte_data.get(byte_data.len() - 4..).map(<[u8; 4]>::try_from) { + Some(Ok(data @ [_, _, _, _])) => match BigEndianToken(BigEndianU32(u32::from_ne_bytes(data))) { + BigEndianToken::END => {} + _ => return Err(FdtError::ParseError(ParseError::UnexpectedToken)), + }, + _ => return Err(FdtError::ParseError(ParseError::UnexpectedEndOfData)), + } + + let granularity_offset = const { + match core::mem::size_of::() { + 1 => 4, + 4 => 1, + _ => unreachable!(), + } + }; - pub fn is_empty(&self) -> bool { - self.remaining().is_empty() + Ok(Node { + this: RawNode::new(&starting_data[..starting_data.len() - granularity_offset]), + parent: None, + strings: self.strings(), + structs: self.structs(), + _mode: core::marker::PhantomData, + }) } - pub fn skip_nops(&mut self) { - while let Some(crate::node::FDT_NOP) = self.peek_u32().map(|n| n.get()) { - let _ = self.u32(); + fn parse_node(&mut self, parent: Option<&'a RawNode>) -> Result, FdtError> + where + Self: ParserWithMode<'a>, + { + match self.advance_token()? { + BigEndianToken::BEGIN_NODE => {} + _ => return Err(FdtError::ParseError(ParseError::UnexpectedToken)), + } + + let starting_data = self.data(); + let starting_len = starting_data.len(); + + self.advance_cstr()?; + + while self.peek_token()? == BigEndianToken::PROP { + self.parse_raw_property()?; + } + + let mut depth = 0; + loop { + let token = self.peek_token()?; + match token { + BigEndianToken::BEGIN_NODE => depth += 1, + BigEndianToken::END_NODE => match depth { + 0 => break, + _ => { + depth -= 1; + let _ = self.advance_token(); + continue; + } + }, + _ => return Err(FdtError::ParseError(ParseError::InvalidTokenValue)), + } + + let _ = self.advance_token(); + + self.advance_cstr()?; + + while self.peek_token()? == BigEndianToken::PROP { + self.parse_raw_property()?; + } + } + + let ending_len = self.data().len(); + + match self.advance_token()? { + BigEndianToken::END_NODE => Ok(Node { + this: RawNode::new( + starting_data.get(..starting_len - ending_len).ok_or(ParseError::UnexpectedEndOfData)?, + ), + parent, + strings: self.strings(), + structs: self.structs(), + _mode: core::marker::PhantomData, + }), + _ => Err(FdtError::ParseError(ParseError::UnexpectedToken)), } } - pub fn take(&mut self, bytes: usize) -> Option<&'a [u8]> { - if self.bytes.len() >= bytes { - let ret = &self.bytes[..bytes]; - self.skip(bytes); + fn parse_raw_property(&mut self) -> Result<(usize, &'a [u8]), FdtError> { + match self.advance_token()? { + BigEndianToken::PROP => { + // Properties are in the format: + let len = + usize::try_from(self.advance_u32()?.to_ne()).map_err(|_| ParseError::NumericConversionError)?; + let name_offset = + usize::try_from(self.advance_u32()?.to_ne()).map_err(|_| ParseError::NumericConversionError)?; + let data = self.byte_data().get(..len).ok_or(ParseError::UnexpectedEndOfData)?; + + self.advance_aligned(data.len()); - return Some(ret); + Ok((name_offset, data)) + } + _ => Err(FdtError::ParseError(ParseError::UnexpectedToken)), } + } +} - None +#[derive(Debug, Clone, Copy)] +#[repr(transparent)] +pub struct StringsBlock<'a>(pub(crate) &'a [u8]); + +impl<'a> StringsBlock<'a> { + pub fn offset_at(self, offset: usize) -> Result<&'a str, FdtError> { + core::ffi::CStr::from_bytes_until_nul(self.0.get(offset..).ok_or(ParseError::UnexpectedEndOfData)?) + .map_err(|_| ParseError::InvalidCStrValue)? + .to_str() + .map_err(|_| ParseError::InvalidCStrValue.into()) } } + +#[derive(Debug, Clone, Copy)] +#[repr(transparent)] +pub struct StructsBlock<'a, G>(pub(crate) &'a [G]); diff --git a/src/parsing/aligned.rs b/src/parsing/aligned.rs new file mode 100644 index 0000000..192daa4 --- /dev/null +++ b/src/parsing/aligned.rs @@ -0,0 +1,85 @@ +// This Source Code Form is subject to the terms of the Mozilla Public License, +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at https://mozilla.org/MPL/2.0/. + +use crate::FdtError; + +use super::{BigEndianToken, BigEndianU32, ParseError, Parser, Stream, StringsBlock, StructsBlock}; + +pub struct AlignedParser<'a> { + stream: Stream<'a, u32>, + strings: StringsBlock<'a>, + structs: StructsBlock<'a, u32>, +} + +impl Clone for AlignedParser<'_> { + fn clone(&self) -> Self { + Self { stream: self.stream.clone(), strings: self.strings, structs: self.structs } + } +} + +impl crate::sealed::Sealed for AlignedParser<'_> {} +impl<'a> Parser<'a> for AlignedParser<'a> { + type Granularity = u32; + + fn new( + data: &'a [Self::Granularity], + strings: StringsBlock<'a>, + structs: StructsBlock<'a, Self::Granularity>, + ) -> Self { + Self { stream: Stream::new(data), strings, structs } + } + + fn data(&self) -> &'a [Self::Granularity] { + self.stream.0 + } + + fn byte_data(&self) -> &'a [u8] { + // SAFETY: it is always valid to cast a `u32` to 4 `u8`s + unsafe { core::slice::from_raw_parts(self.stream.0.as_ptr().cast::(), self.stream.0.len() * 4) } + } + + fn strings(&self) -> super::StringsBlock<'a> { + self.strings + } + + fn structs(&self) -> StructsBlock<'a, Self::Granularity> { + self.structs + } + + fn advance_token(&mut self) -> Result { + loop { + match BigEndianToken(self.stream.advance().map(BigEndianU32).ok_or(ParseError::UnexpectedEndOfData)?) { + BigEndianToken::NOP => continue, + token @ BigEndianToken::BEGIN_NODE + | token @ BigEndianToken::END_NODE + | token @ BigEndianToken::PROP + | token @ BigEndianToken::END => break Ok(token), + _ => break Err(FdtError::ParseError(ParseError::InvalidTokenValue)), + } + } + } + + fn advance_u32(&mut self) -> Result { + self.stream.advance().map(BigEndianU32).ok_or(FdtError::ParseError(ParseError::UnexpectedEndOfData)) + } + + fn advance_cstr(&mut self) -> Result<&'a core::ffi::CStr, FdtError> { + // SAFETY: It is safe to reinterpret the stream data to a smaller integer size + let bytes = + unsafe { core::slice::from_raw_parts(self.stream.0.as_ptr().cast::(), self.stream.0.len() * 4) }; + let cstr = core::ffi::CStr::from_bytes_until_nul(bytes).map_err(|_| ParseError::InvalidCStrValue)?; + + // Round up to the next multiple of 4, if necessary + let skip = ((cstr.to_bytes_with_nul().len() + 3) & !3) / 4; + self.stream.skip_many(skip); + + Ok(cstr) + } + + fn advance_aligned(&mut self, n: usize) { + // Round up to the next multiple of 4, if necessary + let skip = ((n + 3) & !3) / 4; + self.stream.skip_many(skip); + } +} diff --git a/src/parsing/unaligned.rs b/src/parsing/unaligned.rs new file mode 100644 index 0000000..837268b --- /dev/null +++ b/src/parsing/unaligned.rs @@ -0,0 +1,107 @@ +// This Source Code Form is subject to the terms of the Mozilla Public License, +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at https://mozilla.org/MPL/2.0/. + +use crate::FdtError; + +use super::{BigEndianToken, BigEndianU32, ParseError, Parser, Stream, StringsBlock, StructsBlock}; + +pub struct UnalignedParser<'a> { + stream: Stream<'a, u8>, + strings: StringsBlock<'a>, + structs: StructsBlock<'a, u8>, +} + +impl Clone for UnalignedParser<'_> { + fn clone(&self) -> Self { + Self { stream: self.stream.clone(), strings: self.strings, structs: self.structs } + } +} + +impl crate::sealed::Sealed for UnalignedParser<'_> {} +impl<'a> Parser<'a> for UnalignedParser<'a> { + type Granularity = u8; + + fn new( + data: &'a [Self::Granularity], + strings: StringsBlock<'a>, + structs: StructsBlock<'a, Self::Granularity>, + ) -> Self { + Self { stream: Stream::new(data), strings, structs } + } + + fn data(&self) -> &'a [Self::Granularity] { + self.stream.0 + } + + fn byte_data(&self) -> &'a [u8] { + self.stream.0 + } + + fn strings(&self) -> super::StringsBlock<'a> { + self.strings + } + + fn structs(&self) -> StructsBlock<'a, Self::Granularity> { + self.structs + } + + fn advance_token(&mut self) -> Result { + loop { + match BigEndianToken(self.advance_u32()?) { + BigEndianToken::NOP => continue, + token @ BigEndianToken::BEGIN_NODE + | token @ BigEndianToken::END_NODE + | token @ BigEndianToken::PROP + | token @ BigEndianToken::END => break Ok(token), + _ => break Err(FdtError::ParseError(ParseError::InvalidTokenValue)), + } + } + } + + fn advance_u32(&mut self) -> Result { + if self.stream.0.len() < core::mem::size_of::() { + return Err(FdtError::ParseError(ParseError::UnexpectedEndOfData)); + } + + let data = self.stream.0; + self.stream.skip_many(4); + + // SAFETY: The buffer has at least 4 bytes available to read + Ok(BigEndianU32::from_be(unsafe { core::ptr::read_unaligned(data.as_ptr().cast::()) })) + } + + fn advance_cstr(&mut self) -> Result<&'a core::ffi::CStr, FdtError> { + let cstr = core::ffi::CStr::from_bytes_until_nul(self.stream.0).map_err(|_| ParseError::InvalidCStrValue)?; + + // Round up to the next multiple of 4, if necessary + let skip = (cstr.to_bytes_with_nul().len() + 3) & !3; + self.stream.skip_many(skip); + + Ok(cstr) + } + + fn advance_aligned(&mut self, n: usize) { + // Round up to the next multiple of 4, if necessary + let skip = (n + 3) & !3; + self.stream.skip_many(skip); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn advance_u32() { + let n = BigEndianU32::from_ne(0xF00DCAFE); + let mut parser = UnalignedParser::new( + unsafe { core::slice::from_raw_parts(&n as *const BigEndianU32 as *const u8, 4) }, + StringsBlock(&[]), + StructsBlock(&[]), + ); + let m = parser.advance_u32().unwrap(); + + assert_eq!(n, m); + } +} diff --git a/src/pretty_print.rs b/src/pretty_print.rs index 08725d7..8ac7662 100644 --- a/src/pretty_print.rs +++ b/src/pretty_print.rs @@ -2,96 +2,179 @@ // v. 2.0. If a copy of the MPL was not distributed with this file, You can // obtain one at https://mozilla.org/MPL/2.0/. -pub fn print_node( +use crate::{ + cell_collector::CollectCellsError, + nodes::{root::Root, Node, NodeName}, + parsing::{NoPanic, Parser}, + properties::values::{InvalidPropertyValue, U32List}, + FdtError, +}; + +#[derive(Debug)] +pub struct Error; + +impl From for Error { + #[track_caller] + fn from(_: FdtError) -> Self { + Error + } +} + +impl From for Error { + #[track_caller] + fn from(_: CollectCellsError) -> Self { + Error + } +} + +impl From for Error { + #[track_caller] + fn from(_: InvalidPropertyValue) -> Self { + Error + } +} + +impl From for Error { + fn from(_: core::fmt::Error) -> Self { + Error + } +} + +impl From for core::fmt::Error { + fn from(_: Error) -> Self { + core::fmt::Error + } +} + +pub fn print_fdt<'a, P: Parser<'a>>( f: &mut core::fmt::Formatter<'_>, - node: crate::node::FdtNode<'_, '_>, - n_spaces: usize, + root: Root<'a, (P, NoPanic)>, ) -> core::fmt::Result { - write!(f, "{:width$}", ' ', width = n_spaces)?; - writeln!(f, "{} {{", if node.name.is_empty() { "/" } else { node.name })?; - let mut were_props = false; - for prop in node.properties() { - were_props = true; + let res = crate::tryblock!(Error, { + let mut any_children = true; + let mut any_props; + let mut node_iter = root.all_nodes()?.peekable(); + let (mut n_braces, mut final_depth) = (0, 0); + writeln!(f, "/ {{")?; + any_props = print_properties(f, root.node, 0)?; + while let Some((depth, node)) = node_iter.next().transpose()? { + let next_depth = match node_iter.peek().cloned().transpose()? { + Some((next_depth, _)) => next_depth, + None => 0, + }; + let next_is_child = next_depth > depth; + any_children = true; + + if n_braces > 0 { + for _ in (0..n_braces).rev() { + final_depth -= 1; + writeln!(f, "{:width$}}};", ' ', width = (final_depth) * 4)?; + } + + n_braces = 0; + } + + if any_props { + writeln!(f)?; + } + + writeln!( + f, + "{:width$}{} {{", + ' ', + if node.name()?.name.is_empty() { NodeName { name: "/", unit_address: None } } else { node.name()? }, + width = depth * 4, + )?; + + any_props = print_properties(f, node, depth)?; + + if !any_props && !next_is_child { + writeln!(f)?; + } + + if next_depth <= depth { + writeln!(f, "{:width$}}};", ' ', width = depth * 4)?; + + if !any_props && !next_is_child { + writeln!(f)?; + } + } + + if depth > next_depth { + n_braces += depth - next_depth; + final_depth = depth; + } else { + n_braces = 0; + final_depth = 0; + } + } + + if any_children { + writeln!(f, " }};")?; + } + + write!(f, "}};")?; + + Ok(()) + }); + + Ok(res?) +} + +fn print_properties<'a, P: Parser<'a>>( + f: &mut core::fmt::Formatter<'_>, + node: Node<'a, (P, NoPanic)>, + depth: usize, +) -> Result { + let mut any_props = false; + for prop in node.properties()? { + any_props = true; + let prop = prop?; match prop.name { "reg" => { - write!(f, "{:width$}reg = <", ' ', width = n_spaces + 4)?; - for (i, reg) in node.reg().unwrap().enumerate() { + write!(f, "{:width$}reg = <", ' ', width = depth * 4 + 4)?; + for (i, reg) in node.reg()?.unwrap().iter::>().enumerate() { + let reg = reg?; if i > 0 { write!(f, " ")?; } - match reg.size { - Some(size) => { - write!(f, "{:#x} {:#x}", reg.starting_address as usize, size)? - } - None => write!(f, "{:#x}", reg.starting_address as usize)?, + match reg.len { + Some(size) => write!(f, "{:#04x} {:#04x}", reg.address as usize, size)?, + None => write!(f, "{:#04x}", reg.address as usize)?, } } - writeln!(f, ">")?; + writeln!(f, ">;")?; + } + "compatible" => { + writeln!(f, "{:width$}compatible = {:?};", ' ', prop.as_value::<&str>()?, width = depth * 4 + 4)? } - "compatible" => writeln!( - f, - "{:width$}compatible = {:?}", - ' ', - prop.as_str().unwrap(), - width = n_spaces + 4 - )?, name if name.contains("-cells") => { - writeln!( - f, - "{:width$}{} = <{:#x}>", - ' ', - name, - prop.as_usize().unwrap(), - width = n_spaces + 4 - )?; + writeln!(f, "{:width$}{} = <{:#04x}>;", ' ', name, prop.as_value::()?, width = depth * 4 + 4)?; } - _ => match prop.as_str() { - Some(value) if !value.is_empty() => { - writeln!(f, "{:width$}{} = {:?}", ' ', prop.name, value, width = n_spaces + 4)? - } + _ => match prop.as_value::<&str>() { + Ok("") => writeln!(f, "{:width$}{};", ' ', prop.name, width = depth * 4 + 4)?, + Ok(value) => writeln!(f, "{:width$}{} = {:?};", ' ', prop.name, value, width = depth * 4 + 4)?, _ => match prop.value.len() { - 4 | 8 => writeln!( - f, - "{:width$}{} = <{:#x}>", - ' ', - prop.name, - prop.as_usize().unwrap(), - width = n_spaces + 4 - )?, - _ => writeln!( - f, - "{:width$}{} = {:?}", - ' ', - prop.name, - prop.value, - width = n_spaces + 4 - )?, - }, - }, - } - } - - if node.children().next().is_some() && were_props { - writeln!(f)?; - } + 0 => writeln!(f, "{:width$}{};", ' ', prop.name, width = depth * 4 + 4)?, + _ => { + write!(f, "{:width$}{} = <", ' ', prop.name, width = depth * 4 + 4)?; - let mut first = true; - for child in node.children() { - if !first { - writeln!(f)?; - } + for (i, n) in prop.as_value::()?.iter().enumerate() { + if i != 0 { + write!(f, " ")?; + } - print_node(f, child, n_spaces + 4)?; - first = false; - } + write!(f, "{n:#04x}")?; + } - if n_spaces > 0 { - write!(f, "{:width$}", ' ', width = n_spaces)?; + writeln!(f, ">;")?; + } + }, + }, + } } - writeln!(f, "}};")?; - - Ok(()) + Ok(any_props) } diff --git a/src/properties.rs b/src/properties.rs new file mode 100644 index 0000000..4f7a199 --- /dev/null +++ b/src/properties.rs @@ -0,0 +1,316 @@ +// This Source Code Form is subject to the terms of the Mozilla Public License, +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at https://mozilla.org/MPL/2.0/. + +/// Types for various `*-cells` properties. +pub mod cells; +/// Types for working with interrupt properties. +pub mod interrupts; +/// Types for working with the `ranges` property. +pub mod ranges; +/// Type for working with the `reg` property. +pub mod reg; +/// Abstractions for various devicetree value types. +pub mod values; + +use crate::{ + helpers::{FallibleNode, FallibleRoot}, + parsing::{BigEndianU32, ParserWithMode}, + FdtError, +}; + +/// A property (or potentially a group of related properties, see +/// [`cells::CellSizes`]) that can be parsed from a [`crate::nodes::Node`] which +/// may also need additional information from the devicetree. +pub trait Property<'a, P: ParserWithMode<'a>>: Sized { + /// Attempt to parse out the property from the given node and a reference to + /// the root node of the devicetree. + fn parse(node: FallibleNode<'a, P>, root: FallibleRoot<'a, P>) -> Result, FdtError>; +} + +/// [Devicetree 2.3.1. +/// `compatible`](https://devicetree-specification.readthedocs.io/en/latest/chapter2-devicetree-basics.html#compatible) +/// +/// The `compatible` property value consists of one or more strings that define +/// the specific programming model for the device. This list of strings should +/// be used by a client program for device driver selection. The property value +/// consists of a concatenated list of null terminated strings, from most +/// specific to most general. They allow a device to express its compatibility +/// with a family of similar devices, potentially allowing a single device +/// driver to match against several devices. +/// +/// The recommended format is `"manufacturer,model"`, where `manufacturer` is a +/// string describing the name of the manufacturer (such as a stock ticker +/// symbol), and model specifies the model number. +/// +/// The compatible string should consist only of lowercase letters, digits and +/// dashes, and should start with a letter. A single comma is typically only +/// used following a vendor prefix. Underscores should not be used. +/// +/// Example: `compatible = "fsl,mpc8641", "ns16550";` +/// +/// In this example, an operating system would first try to locate a device +/// driver that supported `fsl,mpc8641`. If a driver was not found, it would +/// then try to locate a driver that supported the more general `ns16550` device +/// type. +#[derive(Debug, Clone, Copy)] +pub struct Compatible<'a> { + string: &'a str, +} + +impl<'a, P: ParserWithMode<'a>> Property<'a, P> for Compatible<'a> { + fn parse(node: FallibleNode<'a, P>, _: FallibleRoot<'a, P>) -> Result, FdtError> { + let property = node.properties()?.find("compatible")?; + + match property { + Some(prop) => Ok(Some(Self { string: prop.as_value()? })), + None => Ok(None), + } + } +} + +impl<'a> Compatible<'a> { + /// First compatible model. + pub fn first(self) -> &'a str { + self.string.split('\0').next().unwrap_or(self.string) + } + + /// Returns an iterator over all compatible models. + pub fn all(self) -> CompatibleIter<'a> { + CompatibleIter { iter: self.string.split('\0') } + } + + /// Returns whether the node is compatible with the given string value. + pub fn compatible_with(self, value: &str) -> bool { + self.all().any(|c| c == value) + } +} + +impl<'a> IntoIterator for Compatible<'a> { + type IntoIter = CompatibleIter<'a>; + type Item = &'a str; + + fn into_iter(self) -> Self::IntoIter { + self.all() + } +} + +/// An iterator over all of the strings contained within a [`Compatible`] +/// property. +pub struct CompatibleIter<'a> { + iter: core::str::Split<'a, char>, +} + +impl<'a> Iterator for CompatibleIter<'a> { + type Item = &'a str; + fn next(&mut self) -> Option { + self.iter.next() + } +} + +/// [Devicetree 2.3.2. +/// `model`](https://devicetree-specification.readthedocs.io/en/latest/chapter2-devicetree-basics.html#model) +/// +/// The model property value is a `` that specifies the manufacturer’s +/// model number of the device. +/// +/// The recommended format is: `"manufacturer,model"`, where `manufacturer` is a +/// string describing the name of the manufacturer (such as a stock ticker +/// symbol), and model specifies the model number. +/// +/// Example: `model = "fsl,MPC8349EMITX";` +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Model<'a>(&'a str); + +impl<'a, P: ParserWithMode<'a>> Property<'a, P> for Model<'a> { + fn parse(node: FallibleNode<'a, P>, _: FallibleRoot<'a, P>) -> Result, FdtError> { + match node.properties()?.find("model")? { + Some(model) => Ok(Some(Self(model.as_value()?))), + None => Ok(None), + } + } +} + +impl<'a> core::ops::Deref for Model<'a> { + type Target = str; + fn deref(&self) -> &Self::Target { + self.0 + } +} + +impl<'a> AsRef for Model<'a> { + fn as_ref(&self) -> &str { + self.0 + } +} + +impl<'a> core::cmp::PartialEq for Model<'a> { + fn eq(&self, other: &str) -> bool { + self.0.eq(other) + } +} + +impl<'a> core::cmp::PartialEq> for str { + fn eq(&self, other: &Model<'a>) -> bool { + self.eq(other.0) + } +} + +/// [Devicetree 2.3.3. +/// `phandle`](https://devicetree-specification.readthedocs.io/en/latest/chapter2-devicetree-basics.html#phandle) +/// +/// The `phandle` property specifies a numerical identifier for a node that is +/// unique within the devicetree. The `phandle` property value is used by other +/// nodes that need to refer to the node associated with the property. +/// +/// Example: +/// +/// ```dts +/// pic@10000000 { +/// phandle = <1>; +/// interrupt-controller; +/// reg = <0x10000000 0x100>; +/// }; +/// ``` +/// +/// A `phandle` value of `1` is defined. Another device node could reference the pic node with a `phandle` value of `1`: +/// +/// ```dts +/// another-device-node { +/// interrupt-parent = <1>; +/// }; +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct PHandle(BigEndianU32); + +impl PHandle { + /// Create a new [`PHandle`] using an existing handle ID. + /// + /// Note: this function will convert the handle ID to big endian, so it + /// should be in the platform's native endianness. + pub fn new(handle: u32) -> Self { + Self(BigEndianU32::from_ne(handle)) + } + + /// Return the [`PHandle`]'s value as a native-endianness [`u32`]. + pub fn as_u32(self) -> u32 { + self.0.to_ne() + } +} + +impl<'a, P: ParserWithMode<'a>> Property<'a, P> for PHandle { + fn parse(node: FallibleNode<'a, P>, _: FallibleRoot<'a, P>) -> Result, FdtError> { + let Some(phandle) = node.properties()?.find("phandle")? else { + return Ok(None); + }; + + Ok(Some(PHandle(phandle.as_value()?))) + } +} + +/// [Devicetree 2.3.4. +/// `status`](https://devicetree-specification.readthedocs.io/en/latest/chapter2-devicetree-basics.html#status) +/// +/// The `status` property indicates the operational status of a device. The lack +/// of a `status` property should be treated as if the property existed with the +/// value of `"okay"`. Valid values for the `status` property are: +/// +/// | Value | Description | +/// |--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +/// | `"okay"` | Indicates the device is operational. | +/// | `"disabled"` | Indicates that the device is not presently operational, but it might become operational in the future (for example, something is not plugged in, or switched off). Refer to the device binding for details on what disabled means for a given device. | +/// | `"reserved"` | Indicates that the device is operational, but should not be used. Typically this is used for devices that are controlled by another software component, such as platform firmware. | +/// | `"fail"` | Indicates that the device is not operational. A serious error was detected in the device, and it is unlikely to become operational without repair. | +/// | `"fail-sss"` | Indicates that the device is not operational. A serious error was detected in the device and it is unlikely to become operational without repair. The `sss` portion of the value is specific to the device and indicates the error condition detected. | +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Status<'a>(&'a str); + +impl<'a> Status<'a> { + #[allow(missing_docs)] + pub const OKAY: Self = Self("okay"); + #[allow(missing_docs)] + pub const DISABLED: Self = Self("disabled"); + #[allow(missing_docs)] + pub const RESERVED: Self = Self("reserved"); + #[allow(missing_docs)] + pub const FAIL: Self = Self("fail"); + + /// Returns true if the status is `"okay"` + #[inline] + pub fn is_okay(self) -> bool { + self == Self::OKAY + } + + /// Returns true if the status is `"disabled"` + #[inline] + pub fn is_disabled(self) -> bool { + self == Self::DISABLED + } + + /// Returns true if the status is `"reserved"` + #[inline] + pub fn is_reserved(self) -> bool { + self == Self::RESERVED + } + + /// Returns true if the status is `"fail"` or begins with `"fail-"` + #[inline] + pub fn is_failed(self) -> bool { + self == Self::FAIL || self.0.starts_with("fail-") + } + + /// Returns the `sss` portion of the `fail-sss` status, if the status is + /// failed and contains a status condition + pub fn failed_status_code(self) -> Option<&'a str> { + match self.0.starts_with("fail-") { + true => Some(self.0.trim_start_matches("fail-")), + false => None, + } + } +} + +impl<'a, P: ParserWithMode<'a>> Property<'a, P> for Status<'a> { + fn parse(node: FallibleNode<'a, P>, _: FallibleRoot<'a, P>) -> Result, FdtError> { + match node.properties()?.find("status")? { + Some(model) => Ok(Some(Self(model.as_value()?))), + None => Ok(None), + } + } +} + +impl<'a> core::ops::Deref for Status<'a> { + type Target = str; + fn deref(&self) -> &Self::Target { + self.0 + } +} + +impl<'a> core::cmp::PartialEq for Status<'a> { + fn eq(&self, other: &str) -> bool { + self.0.eq(other) + } +} + +impl<'a> core::cmp::PartialEq> for str { + fn eq(&self, other: &Status<'a>) -> bool { + self.eq(other.0) + } +} + +/// [Devicetree 2.3.10. +/// `dma-coherent`](https://devicetree-specification.readthedocs.io/en/latest/chapter2-devicetree-basics.html#dma-coherent) +/// +/// For architectures which are by default non-coherent for I/O, the +/// `dma-coherent` property is used to indicate a device is capable of coherent +/// DMA operations. Some architectures have coherent DMA by default and this +/// property is not applicable. +pub struct DmaCoherent; + +impl<'a, P: ParserWithMode<'a>> Property<'a, P> for DmaCoherent { + fn parse(node: FallibleNode<'a, P>, _: FallibleRoot<'a, P>) -> Result, FdtError> { + match node.properties()?.find("dma-coherent")? { + Some(_) => Ok(Some(Self)), + None => Ok(None), + } + } +} diff --git a/src/properties/cells.rs b/src/properties/cells.rs new file mode 100644 index 0000000..cb009bc --- /dev/null +++ b/src/properties/cells.rs @@ -0,0 +1,124 @@ +use super::Property; +use crate::{ + helpers::{FallibleNode, FallibleRoot}, + parsing::{unaligned::UnalignedParser, Parser, ParserWithMode, StringsBlock, StructsBlock}, + FdtError, +}; + +/// [Devicetree 2.3.5. `#address-cells` and +/// `#size-cells`](https://devicetree-specification.readthedocs.io/en/latest/chapter2-devicetree-basics.html#address-cells-and-size-cells) +/// +/// The `#address-cells` and `#size-cells` properties may be used in any device +/// node that has children in the devicetree hierarchy and describes how child +/// device nodes should be addressed. The `#address-cells` property defines the +/// number of `` cells used to encode the address field in a child node’s +/// reg property. The `#size-cells` property defines the number of `` cells +/// used to encode the size field in a child node’s reg property. +/// +/// The `#address-cells` and `#size-cells` properties are not inherited from +/// ancestors in the devicetree. They shall be explicitly defined. +/// +/// A DTSpec-compliant boot program shall supply `#address-cells` and +/// `#size-cells` on all nodes that have children. +/// +/// If missing, a client program should assume a default value of 2 for +/// `#address-cells`, and a value of 1 for `#size-cells`. +/// +/// Example: +/// +/// ```dts +/// soc { +/// #address-cells = <1>; +/// #size-cells = <1>; +/// +/// serial@4600 { +/// compatible = "ns16550"; +/// reg = <0x4600 0x100>; +/// clock-frequency = <0>; +/// interrupts = <0xA 0x8>; +/// interrupt-parent = <&ipic>; +/// }; +/// }; +/// ``` +/// +/// In this example, the `#address-cells` and `#size-cells` properties of the +/// soc node are both set to `1`. This setting specifies that one cell is +/// required to represent an address and one cell is required to represent the +/// size of nodes that are children of this node. +/// +/// The serial device reg property necessarily follows this specification set in +/// the parent (soc) node—the address is represented by a single cell +/// (`0x4600`), and the size is represented by a single cell (`0x100`). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct CellSizes { + #[allow(missing_docs)] + pub address_cells: usize, + #[allow(missing_docs)] + pub size_cells: usize, +} + +impl<'a, P: ParserWithMode<'a>> Property<'a, P> for CellSizes { + #[inline] + fn parse(node: FallibleNode<'a, P>, _: FallibleRoot<'a, P>) -> Result, FdtError> { + let (mut address_cells, mut size_cells) = (None, None); + + for property in node.properties()? { + let property = property?; + + let mut parser = UnalignedParser::new(property.value, StringsBlock(&[]), StructsBlock(&[])); + match property.name { + "#address-cells" => address_cells = Some(parser.advance_u32()?.to_ne() as usize), + "#size-cells" => size_cells = Some(parser.advance_u32()?.to_ne() as usize), + _ => {} + } + } + + Ok(address_cells.zip(size_cells).map(|(address_cells, size_cells)| CellSizes { address_cells, size_cells })) + } +} + +impl Default for CellSizes { + fn default() -> Self { + CellSizes { address_cells: 2, size_cells: 1 } + } +} + +/// Newtype representing the number of [`u32`]s that make up an address value. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct AddressCells(pub usize); + +impl<'a, P: ParserWithMode<'a>> Property<'a, P> for AddressCells { + #[inline] + fn parse(node: FallibleNode<'a, P>, _: FallibleRoot<'a, P>) -> Result, FdtError> { + match node.properties()?.find("#address-cells")? { + Some(value) => Ok(Some(Self(value.as_value()?))), + None => Ok(None), + } + } +} + +impl Default for AddressCells { + fn default() -> Self { + Self(2) + } +} + +/// Newtype representing the number of [`u32`]s that make up a size value. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct SizeCells(pub usize); + +impl<'a, P: ParserWithMode<'a>> Property<'a, P> for SizeCells { + #[inline] + fn parse(node: FallibleNode<'a, P>, _: FallibleRoot<'a, P>) -> Result, FdtError> { + match node.properties()?.find("#size-cells")? { + Some(value) => Ok(Some(Self(value.as_value()?))), + None => Ok(None), + } + } +} + +impl Default for SizeCells { + fn default() -> Self { + Self(1) + } +} diff --git a/src/properties/interrupts.rs b/src/properties/interrupts.rs new file mode 100644 index 0000000..80b65d7 --- /dev/null +++ b/src/properties/interrupts.rs @@ -0,0 +1,964 @@ +/// Types and helpers for the devicetree PCI bindings. +pub mod pci; + +use super::{cells::AddressCells, PHandle, Property}; +use crate::{ + cell_collector::{BuildCellCollector, CellCollector, CollectCellsError}, + helpers::{FallibleNode, FallibleRoot}, + nodes::{root::Root, Node}, + parsing::{aligned::AlignedParser, BigEndianU32, NoPanic, Panic, ParserWithMode}, + FdtError, +}; + +/// Enum representing the two possibilities for interrupt descriptions on a +/// devicetree node. See the documentation for each type for more information. +/// [`ExtendedInterrupts`] will take precedence if both properties exist. +pub enum Interrupts<'a, P: ParserWithMode<'a> = (AlignedParser<'a>, Panic)> { + /// The `interrupts` property. + Legacy(LegacyInterrupts<'a, P>), + /// The `interrupts-extended` property. + Extended(ExtendedInterrupts<'a, P>), +} + +impl<'a, P: ParserWithMode<'a>> Property<'a, P> for Interrupts<'a, P> { + fn parse(node: FallibleNode<'a, P>, root: FallibleRoot<'a, P>) -> Result, FdtError> { + match ExtendedInterrupts::parse(node, root)? { + Some(extended) => Ok(Some(Self::Extended(extended))), + None => match LegacyInterrupts::parse(node, root)? { + Some(legacy) => Ok(Some(Self::Legacy(legacy))), + None => Ok(None), + }, + } + } +} + +/// [Devicetree 2.4.1.1. +/// `interrupts`](https://devicetree-specification.readthedocs.io/en/latest/chapter2-devicetree-basics.html#interrupts) +/// +/// The `interrupts` property of a device node defines the interrupt or +/// interrupts that are generated by the device. The value of the `interrupts` +/// property consists of an arbitrary number of interrupt specifiers. The format +/// of an interrupt specifier is defined by the binding of the interrupt domain +/// root. +/// +/// `interrupts` is overridden by the `interrupts-extended` property and +/// normally only one or the other should be used. +pub struct LegacyInterrupts<'a, P: ParserWithMode<'a> = (AlignedParser<'a>, Panic)> { + interrupt_parent: InterruptParent<'a, P>, + interrupt_cells: InterruptCells, + encoded_array: &'a [u8], +} + +impl<'a, P: ParserWithMode<'a>> LegacyInterrupts<'a, P> { + #[allow(missing_docs)] + pub fn interrupt_parent(self) -> InterruptParent<'a, P> { + self.interrupt_parent + } + + #[allow(missing_docs)] + pub fn iter(self) -> LegacyInterruptsIter<'a, I> { + LegacyInterruptsIter { + interrupt_cells: self.interrupt_cells, + encoded_array: self.encoded_array, + _collector: core::marker::PhantomData, + } + } +} + +impl<'a, P: ParserWithMode<'a>> Property<'a, P> for LegacyInterrupts<'a, P> { + fn parse(node: FallibleNode<'a, P>, root: FallibleRoot<'a, P>) -> Result, FdtError> { + match node.properties()?.find("interrupts")? { + Some(interrupts) => { + let interrupt_parent = match InterruptParent::<(P::Parser, NoPanic)>::parse(node, root)? { + Some(p) => p, + None => return Err(FdtError::MissingRequiredProperty("interrupt-parent")), + }; + + let Some(interrupt_cells) = interrupt_parent.property::()? else { + return Err(FdtError::MissingRequiredProperty("interrupt-cells")); + }; + + if interrupts.value.len() % (interrupt_cells.as_byte_count()) != 0 { + return Err(FdtError::InvalidPropertyValue); + } + + Ok(Some(Self { + interrupt_parent: InterruptParent(interrupt_parent.0.alt()), + interrupt_cells, + encoded_array: interrupts.value, + })) + } + None => Ok(None), + } + } +} + +impl<'a, P: ParserWithMode<'a>> Copy for LegacyInterrupts<'a, P> {} +impl<'a, P: ParserWithMode<'a>> Clone for LegacyInterrupts<'a, P> { + fn clone(&self) -> Self { + *self + } +} + +#[allow(missing_docs)] +pub struct LegacyInterruptsIter<'a, I: CellCollector> { + interrupt_cells: InterruptCells, + encoded_array: &'a [u8], + _collector: core::marker::PhantomData<*mut I>, +} + +impl<'a, I: CellCollector> Iterator for LegacyInterruptsIter<'a, I> { + type Item = Result; + fn next(&mut self) -> Option { + let encoded_specifier = self.encoded_array.get(..self.interrupt_cells.as_byte_count())?; + let mut specifier_collector = ::Builder::default(); + + for encoded_specifier in encoded_specifier.chunks_exact(4) { + // TODO: replace this stuff with `array_chunks` when its stabilized + // + // These unwraps can't panic because `chunks_exact` guarantees that + // we'll always get slices of 4 bytes + if let Err(e) = specifier_collector.push(u32::from_be_bytes(encoded_specifier.try_into().unwrap())) { + return Some(Err(e)); + } + } + + self.encoded_array = self.encoded_array.get(self.interrupt_cells.as_byte_count()..)?; + Some(Ok(I::map(specifier_collector.finish()))) + } +} + +/// [Devicetree 2.4.1.3. +/// `interrupts-extended`](https://devicetree-specification.readthedocs.io/en/latest/chapter2-devicetree-basics.html#interrupts-extended) +/// +/// The `interrupts-extended` property lists the interrupt(s) generated by a +/// device. `interrupts-extended` should be used instead of interrupts when a +/// device is connected to multiple interrupt controllers as it encodes a parent +/// `phandle` with each interrupt specifier. +/// +/// Example: +/// +/// This example shows how a device with two interrupt outputs connected to two +/// separate interrupt controllers would describe the connection using an +/// `interrupts-extended` property. `pic` is an interrupt controller with an +/// `#interrupt-cells` specifier of 2, while `gic` is an interrupt controller +/// with an `#interrupts-cells` specifier of 1. +/// +/// `interrupts-extended = <&pic 0xA 8>, <&gic 0xda>;` +pub struct ExtendedInterrupts<'a, P: ParserWithMode<'a> = (AlignedParser<'a>, Panic)> { + root: Root<'a, P>, + encoded_array: &'a [u8], +} + +impl<'a, P: ParserWithMode<'a>> ExtendedInterrupts<'a, P> { + #[allow(missing_docs)] + pub fn iter(self) -> ExtendedInterruptsIter<'a, P> { + ExtendedInterruptsIter { root: self.root, encoded_array: self.encoded_array } + } +} + +impl<'a, P: ParserWithMode<'a>> Property<'a, P> for ExtendedInterrupts<'a, P> { + fn parse(node: FallibleNode<'a, P>, root: FallibleRoot<'a, P>) -> Result, FdtError> { + match node.properties()?.find("interrupts-extended")? { + Some(interrupts) => { + Ok(Some(Self { encoded_array: interrupts.value, root: Root { node: root.node.alt() } })) + } + + None => Ok(None), + } + } +} + +#[allow(missing_docs)] +pub struct ExtendedInterruptsIter<'a, P: ParserWithMode<'a> = (AlignedParser<'a>, Panic)> { + root: Root<'a, P>, + encoded_array: &'a [u8], +} + +impl<'a, P: ParserWithMode<'a>> Iterator for ExtendedInterruptsIter<'a, P> { + type Item = P::Output>; + + #[track_caller] + fn next(&mut self) -> Option { + let phandle = self + .encoded_array + .get(..4) + .map(|bytes| PHandle(BigEndianU32::from_be(u32::from_ne_bytes(bytes.try_into().unwrap()))))?; + self.encoded_array = self.encoded_array.get(4..)?; + + let res = crate::tryblock!({ + let root: FallibleRoot<'a, P> = Root { node: self.root.node.fallible() }; + let Some(interrupt_parent) = root.resolve_phandle(phandle)? else { + return Err(FdtError::MissingPHandleNode(phandle.0.to_ne())); + }; + + let Some(interrupt_cells) = interrupt_parent.property::()? else { + return Err(FdtError::MissingRequiredProperty("#interrupt-cells")); + }; + + let cells_length = interrupt_cells.as_byte_count(); + let encoded_array = match self.encoded_array.get(..cells_length) { + Some(bytes) => bytes, + None => return Ok(None), + }; + + self.encoded_array = match self.encoded_array.get(cells_length..) { + Some(bytes) => bytes, + None => return Ok(None), + }; + + Ok(Some(ExtendedInterrupt { + interrupt_parent: InterruptParent(interrupt_parent.alt()), + interrupt_cells, + encoded_array, + })) + }); + + // This is a manual impl of `map` because we need the panic location to + // be the caller if `P::to_output` panics + #[allow(clippy::manual_map)] + match res.transpose() { + Some(output) => Some(P::to_output(output)), + None => None, + } + } +} + +/// A single entry in an `interrupts-extended` property. +pub struct ExtendedInterrupt<'a, P: ParserWithMode<'a> = (AlignedParser<'a>, Panic)> { + interrupt_parent: InterruptParent<'a, P>, + interrupt_cells: InterruptCells, + encoded_array: &'a [u8], +} + +#[allow(missing_docs)] +impl<'a, P: ParserWithMode<'a>> ExtendedInterrupt<'a, P> { + pub fn interrupt_parent(self) -> InterruptParent<'a, P> { + self.interrupt_parent + } + + pub fn interrupt_cells(self) -> InterruptCells { + self.interrupt_cells + } + + pub fn interrupt_specifier(self) -> InterruptSpecifier<'a> { + InterruptSpecifier { interrupt_cells: self.interrupt_cells, encoded_array: self.encoded_array } + } +} + +/// An individual interrupt specifier from an [`ExtendedInterrupt`] value. +pub struct InterruptSpecifier<'a> { + interrupt_cells: InterruptCells, + encoded_array: &'a [u8], +} + +impl<'a> InterruptSpecifier<'a> { + /// Attempt to collect the specifier bytes into a specific type. + pub fn collect_to(self) -> Result<::Output, CollectCellsError> { + let mut collector = ::Builder::default(); + for chunk in self.encoded_array.chunks_exact(4) { + // UNWRAP: this unwrap cannot panic because `chunk` is guaranteed to + // be 4 bytes. + collector.push(u32::from_be_bytes(chunk.try_into().unwrap()))?; + } + + Ok(C::map(collector.finish())) + } + + /// Iterator over the raw [`u32`] components that comprise this interrupt specifier. + pub fn iter(self) -> InterruptSpecifierIter<'a> { + InterruptSpecifierIter { encoded_array: self.encoded_array } + } + + /// Iterator over `(u32, u32)` interrupt specifier pairs, if + /// `#interrupt-cells` value is `2`. + pub fn iter_pairs(self) -> Option> { + if self.interrupt_cells.0 != 2 { + return None; + } + + Some(InterruptSpecifierIterPairs { encoded_array: self.encoded_array }) + } + + /// Extract the single component that comprises the interrupt specifier, if + /// the `#interrupt-cells` value is `1`. + pub fn single(self) -> Option { + if self.interrupt_cells.0 != 1 { + return None; + } + + self.iter().next() + } + + /// Extract the two components that comprise the interrupt specifier, if the + /// `#interrupt-cells` value is `2`. + pub fn pair(self) -> Option<(u32, u32)> { + if self.interrupt_cells.0 != 2 { + return None; + } + + let mut iter = self.into_iter(); + Some((iter.next()?, iter.next()?)) + } +} + +impl<'a> IntoIterator for InterruptSpecifier<'a> { + type IntoIter = InterruptSpecifierIter<'a>; + type Item = u32; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +/// Iterator over individual components in an interrupt specifier +pub struct InterruptSpecifierIter<'a> { + encoded_array: &'a [u8], +} + +impl<'a> Iterator for InterruptSpecifierIter<'a> { + type Item = u32; + + fn next(&mut self) -> Option { + if self.encoded_array.is_empty() { + return None; + } + + let next = self.encoded_array.get(..4)?; + self.encoded_array = self.encoded_array.get(4..)?; + + // This panic can never fail since the slice length is guaranteed to be + // 4 bytes long + Some(u32::from_be_bytes(next.try_into().unwrap())) + } +} + +/// Iterator over pairs of `u32`s representing an interrupt specifier +pub struct InterruptSpecifierIterPairs<'a> { + encoded_array: &'a [u8], +} + +impl<'a> Iterator for InterruptSpecifierIterPairs<'a> { + type Item = (u32, u32); + + fn next(&mut self) -> Option { + if self.encoded_array.is_empty() { + return None; + } + + let (next, rest) = self.encoded_array.split_at_checked(8)?; + self.encoded_array = rest; + + let (first, second) = next.split_at(4); + + // This panic can never fail since the slice length is guaranteed to be + // 4 bytes long + Some((u32::from_be_bytes(first.try_into().unwrap()), u32::from_be_bytes(second.try_into().unwrap()))) + } +} + +/// [Devicetree 2.4.1.2. +/// `interrupt-parent`](https://devicetree-specification.readthedocs.io/en/latest/chapter2-devicetree-basics.html#interrupt-parent) +/// +/// Because the hierarchy of the nodes in the interrupt tree might not match the +/// devicetree, the `interrupt-parent` property is available to make the +/// definition of an interrupt parent explicit. The value is the `phandle` to +/// the interrupt parent. If this property is missing from a device, its +/// interrupt parent is assumed to be its devicetree parent. +pub struct InterruptParent<'a, P: ParserWithMode<'a> = (AlignedParser<'a>, Panic)>(Node<'a, P>); + +impl<'a, P: ParserWithMode<'a>> Copy for InterruptParent<'a, P> {} +impl<'a, P: ParserWithMode<'a>> Clone for InterruptParent<'a, P> { + fn clone(&self) -> Self { + *self + } +} + +impl<'a, P: ParserWithMode<'a>> core::ops::Deref for InterruptParent<'a, P> { + type Target = Node<'a, P>; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, P: ParserWithMode<'a>> core::ops::DerefMut for InterruptParent<'a, P> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl<'a, P: ParserWithMode<'a>> Property<'a, P> for InterruptParent<'a, P> { + fn parse(node: FallibleNode<'a, P>, root: FallibleRoot<'a, P>) -> Result, FdtError> { + match node.properties()?.find("interrupt-parent")? { + Some(phandle) => match root.resolve_phandle(PHandle(phandle.as_value()?))? { + Some(parent) => Ok(Some(Self(parent.alt()))), + None => Err(FdtError::MissingPHandleNode(phandle.as_value()?)), + }, + None => Ok(node.parent().map(|n| Self(n.alt()))), + } + } +} + +/// [Devicetree 2.4.2.1. +/// `#interrupt-cells`](https://devicetree-specification.readthedocs.io/en/latest/chapter2-devicetree-basics.html#interrupt-cells) +/// +/// The `#interrupt-cells` property defines the number of cells required to +/// encode an interrupt specifier for an interrupt domain. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct InterruptCells(pub usize); + +impl InterruptCells { + /// The number of interrupt cells times `size_of::()`. + pub fn as_byte_count(self) -> usize { + self.0 * core::mem::size_of::() + } +} + +impl<'a, P: ParserWithMode<'a>> Property<'a, P> for InterruptCells { + fn parse(node: FallibleNode<'a, P>, _: FallibleRoot<'a, P>) -> Result, FdtError> { + match node.properties()?.find("#interrupt-cells")? { + Some(ic) => Ok(Some(Self(ic.as_value()?))), + None => Ok(None), + } + } +} + +/// [Devicetree 2.4.3.2. +/// `interrupt-map-mask`](https://devicetree-specification.readthedocs.io/en/latest/chapter2-devicetree-basics.html#interrupt-map-mask) +/// +/// An `interrupt-map-mask` property is specified for a nexus node in the +/// interrupt tree. This property specifies a mask that is `AND`ed with the +/// incoming unit interrupt specifier being looked up in the table specified in +/// the `interrupt-map` property. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct InterruptMapMask { + address_mask: AddrMask::Output, + interrupt_specifier_mask: IntMask::Output, +} + +impl InterruptMapMask { + /// Mask the provided unit address and interrupt specifier with the value of + /// the mask. + pub fn mask( + &self, + address: ::Output, + interrupt_specifier: ::Output, + ) -> (::Output, ::Output) + where + ::Output: Clone + + core::ops::BitAnd<::Output, Output = ::Output>, + ::Output: + Clone + core::ops::BitAnd<::Output, Output = ::Output>, + { + (self.address_mask.clone() & address, self.interrupt_specifier_mask.clone() & interrupt_specifier) + } +} + +impl<'a, AddrMask: CellCollector, IntMask: CellCollector, P: ParserWithMode<'a>> Property<'a, P> + for InterruptMapMask +{ + fn parse(node: FallibleNode<'a, P>, _: FallibleRoot<'a, P>) -> Result, FdtError> { + let address_cells = + node.property::()?.ok_or(FdtError::MissingRequiredProperty("#address-cells"))?; + let interrupt_cells = + node.property::()?.ok_or(FdtError::MissingRequiredProperty("#interrupt-cells"))?; + match node.properties()?.find("interrupt-map-mask")? { + Some(prop) => { + if prop.value.len() % 4 != 0 { + return Err(FdtError::InvalidPropertyValue); + } + + let mut address_collector = AddrMask::Builder::default(); + let mut specifier_collector = IntMask::Builder::default(); + let mut cells = prop.value.chunks_exact(4); + + // TODO: replace this stuff with `array_chunks` when its stabilized + // + // These unwraps can't panic because `chunks_exact` guarantees that + // we'll always get slices of 4 bytes + for chunk in cells.by_ref().take(address_cells.0) { + address_collector.push(u32::from_be_bytes(chunk.try_into().unwrap()))?; + } + + for chunk in cells.take(interrupt_cells.0) { + specifier_collector.push(u32::from_be_bytes(chunk.try_into().unwrap()))?; + } + + Ok(Some(Self { + address_mask: AddrMask::map(address_collector.finish()), + interrupt_specifier_mask: IntMask::map(specifier_collector.finish()), + })) + } + None => Ok(None), + } + } +} + +/// [Devicetree 2.4.2.2. +/// `interrupt-controller`](https://devicetree-specification.readthedocs.io/en/latest/chapter2-devicetree-basics.html#interrupt-controller) +/// +/// The presence of an `interrupt-controller` property defines a node as an +/// interrupt controller node. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct InterruptController; + +impl<'a, P: ParserWithMode<'a>> Property<'a, P> for InterruptController { + fn parse(node: FallibleNode<'a, P>, _: FallibleRoot<'a, P>) -> Result, FdtError> { + match node.properties()?.find("interrupt-controller")? { + Some(_) => Ok(Some(Self)), + None => Ok(None), + } + } +} + +/// [Devicetree 2.4.3.1. +/// `interrupt-map`](https://devicetree-specification.readthedocs.io/en/latest/chapter2-devicetree-basics.html#interrupt-map) +/// +/// The `interrupt-map` property allows for mapping interrupt specifiers between +/// one interrupt domain to a parent domain. The description of each generic +/// parameter is as follows: +/// +/// `ChildUnitAddres`: +/// > The unit address of the child node being mapped. The number of 32-bit +/// > cells required to specify this is described by the `#address-cells` +/// > property of the bus node on which the child is located. +/// +/// `ChildInterruptSpecifier`: +/// > The interrupt specifier of the child node being mapped. The number of +/// > 32-bit cells required to specify this component is described by the +/// > `#interrupt-cells` property of this node—the nexus node containing the +/// > `interrupt-map` property. +/// +/// `ParentUnitAddress`: +/// > The unit address in the domain of the interrupt parent. The number of +/// > 32-bit cells required to specify this address is described by the +/// > `#address-cells` property of the node pointed to by the +/// > `interrupt-parent` field. +/// +/// `ParentInterruptSpecifier`: +/// > The interrupt specifier in the parent domain. The number of 32-bit +/// > cells required to specify this component is described by the +/// > `#interrupt-cells` property of the node pointed to by the +/// > `interrupt-parent` field. +pub struct InterruptMap< + 'a, + ChildUnitAddress: CellCollector, + ChildInterruptSpecifier: CellCollector = u32, + ParentUnitAddress: CellCollector = u64, + ParentInterruptSpecifier: CellCollector = u32, + P: ParserWithMode<'a> = (AlignedParser<'a>, Panic), +> { + address_cells: AddressCells, + interrupt_cells: InterruptCells, + node: FallibleNode<'a, P>, + encoded_map: &'a [u8], + _collectors: core::marker::PhantomData<*mut ( + ChildUnitAddress, + ChildInterruptSpecifier, + ParentUnitAddress, + ParentInterruptSpecifier, + )>, +} + +impl< + 'a, + P: ParserWithMode<'a>, + CAddr: CellCollector, + CInt: CellCollector, + PAddr: CellCollector, + PInt: CellCollector, + > InterruptMap<'a, CAddr, CInt, PAddr, PInt, P> +{ + /// Create an iterator over each individual [`InterruptMapEntry`]. + pub fn iter(self) -> InterruptMapIter<'a, CAddr, CInt, PAddr, PInt, P> { + InterruptMapIter { + address_cells: self.address_cells, + interrupt_cells: self.interrupt_cells, + node: self.node, + encoded_map: self.encoded_map, + _collectors: core::marker::PhantomData, + } + } + + /// Create an iterator over each individual [`InterruptMapEntry`] which uses the provided mask to modify the . + pub fn iter_masked( + self, + mask: InterruptMapMask, + ) -> MaskedInterruptMapIter<'a, CAddr, CInt, PAddr, PInt, P> + where + ::Output: + Clone + core::ops::BitAnd<::Output, Output = ::Output>, + ::Output: + Clone + core::ops::BitAnd<::Output, Output = ::Output>, + { + MaskedInterruptMapIter { + address_cells: self.address_cells, + interrupt_cells: self.interrupt_cells, + node: self.node, + encoded_map: self.encoded_map, + mask, + _collectors: core::marker::PhantomData, + } + } + + /// Attempt to find a specific child unit address with a specific child + /// interrupt specifier. + #[allow(clippy::type_complexity)] + pub fn find( + self, + client_unit_address: CAddr::Output, + child_interrupt_specifier: CInt::Output, + ) -> P::Output>> + where + CAddr::Output: PartialEq, + CInt::Output: PartialEq, + { + let this: InterruptMap<_, _, _, _, (P::Parser, NoPanic)> = InterruptMap { + address_cells: self.address_cells, + interrupt_cells: self.interrupt_cells, + node: self.node, + encoded_map: self.encoded_map, + _collectors: self._collectors, + }; + + P::to_output( + this.iter() + .find(|e| match e { + Err(_) => true, + Ok(entry) => { + entry.child_unit_address == client_unit_address + && entry.child_interrupt_specifier == child_interrupt_specifier + } + }) + .transpose() + .map(|e| { + e.map(|e| InterruptMapEntry::<_, _, _, _, P> { + child_unit_address: e.child_unit_address, + child_interrupt_specifier: e.child_interrupt_specifier, + interrupt_parent: e.interrupt_parent.alt(), + parent_unit_address: e.parent_unit_address, + parent_interrupt_specifier: e.parent_interrupt_specifier, + }) + }), + ) + } +} + +impl< + 'a, + P: ParserWithMode<'a>, + CAddr: CellCollector, + CInt: CellCollector, + PAddr: CellCollector, + PInt: CellCollector, + > Property<'a, P> for InterruptMap<'a, CAddr, CInt, PAddr, PInt, P> +{ + fn parse(node: FallibleNode<'a, P>, _: FallibleRoot<'a, P>) -> Result, FdtError> { + let Some(encoded_map) = node.properties()?.find("interrupt-map")? else { return Ok(None) }; + + let address_cells = + node.property::()?.ok_or(FdtError::MissingRequiredProperty("#address-cells"))?; + let interrupt_cells = + node.property::()?.ok_or(FdtError::MissingRequiredProperty("#interrupt-cells"))?; + + Ok(Some(InterruptMap { + address_cells, + interrupt_cells, + node: node.alt(), + encoded_map: encoded_map.value, + _collectors: core::marker::PhantomData, + })) + } +} + +#[allow(missing_docs)] +pub struct InterruptMapIter< + 'a, + CAddr: CellCollector, + CInt: CellCollector, + PAddr: CellCollector, + PInt: CellCollector, + P: ParserWithMode<'a> = (AlignedParser<'a>, Panic), +> { + address_cells: AddressCells, + interrupt_cells: InterruptCells, + node: FallibleNode<'a, P>, + encoded_map: &'a [u8], + _collectors: core::marker::PhantomData<*mut (CAddr, CInt, PAddr, PInt)>, +} + +impl< + 'a, + CAddr: CellCollector, + CInt: CellCollector, + PAddr: CellCollector, + PInt: CellCollector, + P: ParserWithMode<'a>, + > Iterator for InterruptMapIter<'a, CAddr, CInt, PAddr, PInt, P> +{ + type Item = P::Output>; + + #[track_caller] + fn next(&mut self) -> Option { + let res = crate::tryblock!({ + let child_addr_size = self.address_cells.0 * 4; + let child_intsp_size = self.interrupt_cells.as_byte_count(); + + let Some((child_address_iter, rest)) = self.encoded_map.split_at_checked(child_addr_size) else { + return Ok(None); + }; + let Some((child_specifier_iter, rest)) = rest.split_at_checked(child_intsp_size) else { + return Ok(None); + }; + let Some((interrupt_parent, rest)) = rest.split_at_checked(4) else { + return Ok(None); + }; + + let root = self.node.make_root::<(P::Parser, NoPanic)>()?; + let phandle = u32::from_ne_bytes(interrupt_parent.try_into().unwrap()); + let interrupt_parent = root + .resolve_phandle(PHandle(BigEndianU32::from_be(phandle)))? + .ok_or(FdtError::MissingPHandleNode(phandle.swap_bytes()))?; + + let parent_address_cells = interrupt_parent + .property::()? + .ok_or(FdtError::MissingRequiredProperty("#address-cells"))?; + let parent_interrupt_cells = interrupt_parent + .property::()? + .ok_or(FdtError::MissingRequiredProperty("#interrupt-cells"))?; + + let parent_addr_size = parent_address_cells.0 * 4; + let parent_intsp_size = parent_interrupt_cells.as_byte_count(); + + let Some((parent_address_iter, rest)) = rest.split_at_checked(parent_addr_size) else { + return Ok(None); + }; + + let Some((parent_specifier_iter, rest)) = rest.split_at_checked(parent_intsp_size) else { + return Ok(None); + }; + self.encoded_map = rest; + + let mut child_address_collector = CAddr::Builder::default(); + for chunk in child_address_iter.chunks_exact(4) { + child_address_collector.push(u32::from_be_bytes(chunk.try_into().unwrap()))?; + } + + let mut child_specifier_collector = CInt::Builder::default(); + for chunk in child_specifier_iter.chunks_exact(4) { + child_specifier_collector.push(u32::from_be_bytes(chunk.try_into().unwrap()))?; + } + + let mut parent_address_collector = PAddr::Builder::default(); + for chunk in parent_address_iter.chunks_exact(4) { + parent_address_collector.push(u32::from_be_bytes(chunk.try_into().unwrap()))?; + } + + let mut parent_specifier_collector = PInt::Builder::default(); + for chunk in parent_specifier_iter.chunks_exact(4) { + parent_specifier_collector.push(u32::from_be_bytes(chunk.try_into().unwrap()))?; + } + + Ok(Some(InterruptMapEntry { + interrupt_parent: interrupt_parent.alt(), + child_unit_address: CAddr::map(child_address_collector.finish()), + child_interrupt_specifier: CInt::map(child_specifier_collector.finish()), + parent_unit_address: PAddr::map(parent_address_collector.finish()), + parent_interrupt_specifier: PInt::map(parent_specifier_collector.finish()), + })) + }); + + #[allow(clippy::manual_map)] + match res.transpose() { + Some(output) => Some(P::to_output(output)), + None => None, + } + } +} + +#[allow(missing_docs)] +pub struct MaskedInterruptMapIter< + 'a, + CAddr: CellCollector, + CInt: CellCollector, + PAddr: CellCollector, + PInt: CellCollector, + P: ParserWithMode<'a> = (AlignedParser<'a>, Panic), +> where + ::Output: + Clone + core::ops::BitAnd<::Output, Output = ::Output>, + ::Output: + Clone + core::ops::BitAnd<::Output, Output = ::Output>, +{ + address_cells: AddressCells, + interrupt_cells: InterruptCells, + node: FallibleNode<'a, P>, + encoded_map: &'a [u8], + mask: InterruptMapMask, + _collectors: core::marker::PhantomData<*mut (CAddr, CInt, PAddr, PInt)>, +} + +impl< + 'a, + CAddr: CellCollector, + CInt: CellCollector, + PAddr: CellCollector, + PInt: CellCollector, + P: ParserWithMode<'a>, + > Iterator for MaskedInterruptMapIter<'a, CAddr, CInt, PAddr, PInt, P> +where + ::Output: + Clone + core::ops::BitAnd<::Output, Output = ::Output>, + ::Output: + Clone + core::ops::BitAnd<::Output, Output = ::Output>, +{ + type Item = P::Output>; + + #[track_caller] + fn next(&mut self) -> Option { + let res = crate::tryblock!({ + let child_addr_size = self.address_cells.0 * 4; + let child_intsp_size = self.interrupt_cells.as_byte_count(); + + let Some((child_address_iter, rest)) = self.encoded_map.split_at_checked(child_addr_size) else { + return Ok(None); + }; + let Some((child_specifier_iter, rest)) = rest.split_at_checked(child_intsp_size) else { + return Ok(None); + }; + let Some((interrupt_parent, rest)) = rest.split_at_checked(4) else { + return Ok(None); + }; + + let root = self.node.make_root::<(P::Parser, NoPanic)>()?; + let phandle = u32::from_ne_bytes(interrupt_parent.try_into().unwrap()); + let interrupt_parent = root + .resolve_phandle(PHandle(BigEndianU32::from_be(phandle)))? + .ok_or(FdtError::MissingPHandleNode(phandle.swap_bytes()))?; + + let parent_address_cells = interrupt_parent + .property::()? + .ok_or(FdtError::MissingRequiredProperty("#address-cells"))?; + let parent_interrupt_cells = interrupt_parent + .property::()? + .ok_or(FdtError::MissingRequiredProperty("#interrupt-cells"))?; + + let parent_addr_size = parent_address_cells.0 * 4; + let parent_intsp_size = parent_interrupt_cells.as_byte_count(); + + let Some((parent_address_iter, rest)) = rest.split_at_checked(parent_addr_size) else { + return Ok(None); + }; + + let Some((parent_specifier_iter, rest)) = rest.split_at_checked(parent_intsp_size) else { + return Ok(None); + }; + self.encoded_map = rest; + + let mut child_address_collector = CAddr::Builder::default(); + for chunk in child_address_iter.chunks_exact(4) { + child_address_collector.push(u32::from_be_bytes(chunk.try_into().unwrap()))?; + } + + let mut child_specifier_collector = CInt::Builder::default(); + for chunk in child_specifier_iter.chunks_exact(4) { + child_specifier_collector.push(u32::from_be_bytes(chunk.try_into().unwrap()))?; + } + + let mut parent_address_collector = PAddr::Builder::default(); + for chunk in parent_address_iter.chunks_exact(4) { + parent_address_collector.push(u32::from_be_bytes(chunk.try_into().unwrap()))?; + } + + let mut parent_specifier_collector = PInt::Builder::default(); + for chunk in parent_specifier_iter.chunks_exact(4) { + parent_specifier_collector.push(u32::from_be_bytes(chunk.try_into().unwrap()))?; + } + + let child_unit_address = CAddr::map(child_address_collector.finish()); + let child_interrupt_specifier = CInt::map(child_specifier_collector.finish()); + let (child_unit_address, child_interrupt_specifier) = + self.mask.mask(child_unit_address, child_interrupt_specifier); + + Ok(Some(InterruptMapEntry { + interrupt_parent: interrupt_parent.alt(), + child_unit_address, + child_interrupt_specifier, + parent_unit_address: PAddr::map(parent_address_collector.finish()), + parent_interrupt_specifier: PInt::map(parent_specifier_collector.finish()), + })) + }); + + #[allow(clippy::manual_map)] + match res.transpose() { + Some(output) => Some(P::to_output(output)), + None => None, + } + } +} + +/// [Devicetree 2.4.3.1. +/// `interrupt-map`](https://devicetree-specification.readthedocs.io/en/latest/chapter2-devicetree-basics.html#interrupt-map) +/// +/// An individual `interrupt-map` entry, including the resolved interrupt parent +/// [`PHandle`]. +pub struct InterruptMapEntry< + 'a, + CAddr: CellCollector, + CInt: CellCollector, + PAddr: CellCollector, + PInt: CellCollector, + P: ParserWithMode<'a>, +> { + /// Interrupt parent of this entry. + pub interrupt_parent: Node<'a, P>, + /// See [`InterruptMap`] for more information. + pub child_unit_address: CAddr::Output, + /// See [`InterruptMap`] for more information. + pub child_interrupt_specifier: CInt::Output, + /// See [`InterruptMap`] for more information. + pub parent_unit_address: PAddr::Output, + /// See [`InterruptMap`] for more information. + pub parent_interrupt_specifier: PInt::Output, +} + +impl< + 'a, + P: ParserWithMode<'a>, + CAddr: CellCollector, + CInt: CellCollector, + PAddr: CellCollector, + PInt: CellCollector, + > Clone for InterruptMapEntry<'a, CAddr, CInt, PAddr, PInt, P> +where + CAddr::Output: Clone, + CInt::Output: Clone, + PAddr::Output: Clone, + PInt::Output: Clone, +{ + fn clone(&self) -> Self { + Self { + child_unit_address: self.child_unit_address.clone(), + child_interrupt_specifier: self.child_interrupt_specifier.clone(), + interrupt_parent: self.interrupt_parent, + parent_unit_address: self.parent_unit_address.clone(), + parent_interrupt_specifier: self.parent_interrupt_specifier.clone(), + } + } +} + +impl< + 'a, + P: ParserWithMode<'a>, + CAddr: CellCollector, + CInt: CellCollector, + PAddr: CellCollector, + PInt: CellCollector, + > Copy for InterruptMapEntry<'a, CAddr, CInt, PAddr, PInt, P> +where + CAddr::Output: Copy, + CInt::Output: Copy, + PAddr::Output: Copy, + PInt::Output: Copy, +{ +} diff --git a/src/properties/interrupts/pci.rs b/src/properties/interrupts/pci.rs new file mode 100644 index 0000000..cb37a52 --- /dev/null +++ b/src/properties/interrupts/pci.rs @@ -0,0 +1,158 @@ +use crate::cell_collector::{BuildCellCollector, CellCollector, CollectCellsError}; + +/// [PCI Bus Binding to Open Firmware 2.2.1.1 Numerical Representation](https://www.openfirmware.info/data/docs/bus.pci.pdf) +/// +/// Numerical representation of a PCI address used within the `interrupt-map` property +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct PciAddress { + #[allow(missing_docs)] + pub hi: PciAddressHighBits, + #[allow(missing_docs)] + pub mid: u32, + #[allow(missing_docs)] + pub lo: u32, +} + +impl CellCollector for PciAddress { + type Builder = PciAddressCollector; + type Output = Self; + + fn map(builder_out: ::Output) -> Self::Output { + builder_out + } +} + +impl PartialEq<&'_ PciAddress> for PciAddress { + fn eq(&self, other: &&'_ Self) -> bool { + self.eq(*other) + } +} + +impl PartialEq for &'_ PciAddress { + fn eq(&self, other: &PciAddress) -> bool { + (*self).eq(other) + } +} + +/// `phys.hi cell: npt000ss bbbbbbbb dddddfff rrrrrrrr` +/// +/// where: +/// +/// `n` is 0 if the address is relocatable, 1 otherwise +/// +/// `p` is 1 if the addressable region is "prefetchable", 0 otherwise +/// +/// `t` is 1 if the address is aliased (for non-relocatable I/O), below 1 MB (for Memory), +/// +/// `or` below 64 KB (for relocatable I/O). +/// +/// `ss` is the space code, denoting the address space +/// +/// `bbbbbbbb` is the 8-bit Bus Number +/// +/// `ddddd` is the 5-bit Device Number +/// +/// `fff` is the 3-bit Function Number +/// +/// `rrrrrrrr` is the 8-bit Register Number +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct PciAddressHighBits(u32); + +#[allow(missing_docs)] +impl PciAddressHighBits { + #[inline(always)] + pub fn new(raw: u32) -> Self { + Self(raw) + } + + #[inline(always)] + pub fn register(self) -> u8 { + self.0 as u8 + } + + #[inline(always)] + pub fn function(self) -> u8 { + ((self.0 >> 8) & 0b111) as u8 + } + + #[inline(always)] + pub fn device(self) -> u8 { + ((self.0 >> 12) & 0b11111) as u8 + } + + #[inline(always)] + pub fn bus(self) -> u8 { + (self.0 >> 16) as u8 + } + + #[inline(always)] + pub fn address_space(self) -> PciAddressSpace { + const CONFIGURATION: u8 = const { PciAddressSpace::Configuration as u8 }; + const IO: u8 = const { PciAddressSpace::Io as u8 }; + const MEMORY32: u8 = const { PciAddressSpace::Memory32 as u8 }; + const MEMORY64: u8 = const { PciAddressSpace::Memory64 as u8 }; + + match ((self.0 >> 24) & 0b11) as u8 { + CONFIGURATION => PciAddressSpace::Configuration, + IO => PciAddressSpace::Io, + MEMORY32 => PciAddressSpace::Memory32, + MEMORY64 => PciAddressSpace::Memory64, + _ => unreachable!(), + } + } + + #[inline(always)] + pub fn prefetchable(self) -> bool { + (self.0 >> 30) & 0b1 == 0b1 + } + + #[inline(always)] + pub fn relocatable(self) -> bool { + (self.0 >> 31) & 0b1 == 0b0 + } +} + +impl core::ops::BitAnd for PciAddress { + type Output = Self; + fn bitand(self, rhs: Self) -> Self::Output { + Self { hi: PciAddressHighBits(self.hi.0 & rhs.hi.0), mid: self.mid & rhs.mid, lo: self.lo & rhs.lo } + } +} + +/// Type of PCI address space. +#[allow(missing_docs)] +#[repr(u8)] +pub enum PciAddressSpace { + Configuration = 0b00, + Io = 0b01, + Memory32 = 0b10, + Memory64 = 0b11, +} + +#[allow(missing_docs)] +#[derive(Default)] +pub struct PciAddressCollector { + address: PciAddress, + num_pushes: u32, +} + +impl BuildCellCollector for PciAddressCollector { + type Output = PciAddress; + + fn push(&mut self, component: u32) -> Result<(), CollectCellsError> { + match self.num_pushes { + 0 => self.address.hi = PciAddressHighBits(component), + 1 => self.address.mid = component, + 2 => self.address.lo = component, + _ => return Err(CollectCellsError), + } + + self.num_pushes += 1; + + Ok(()) + } + + fn finish(self) -> Self::Output { + self.address + } +} diff --git a/src/properties/ranges.rs b/src/properties/ranges.rs new file mode 100644 index 0000000..ae825e9 --- /dev/null +++ b/src/properties/ranges.rs @@ -0,0 +1,129 @@ +use crate::{ + cell_collector::{BuildCellCollector, CellCollector, CollectCellsError}, + helpers::{FallibleNode, FallibleRoot}, + parsing::ParserWithMode, + properties::{ + cells::{AddressCells, CellSizes}, + Property, + }, + FdtError, +}; + +/// See [`Node::ranges`]. +/// +/// [`Node::ranges`]: crate::nodes::Node::ranges +#[derive(Debug, Clone, Copy)] +pub struct Ranges<'a> { + parent_address_cells: AddressCells, + cell_sizes: CellSizes, + ranges: &'a [u8], +} + +impl<'a> Ranges<'a> { + /// Create an iterator over the entries in the property value and attempt to + /// collect the constituent parts into the specified [`CellCollector`]s. + pub fn iter(self) -> RangesIter<'a, CAddr, PAddr, Len> + where + CAddr: CellCollector, + PAddr: CellCollector, + Len: CellCollector, + { + RangesIter { + parent_address_cells: self.parent_address_cells, + cell_sizes: self.cell_sizes, + ranges: self.ranges, + _collectors: core::marker::PhantomData, + } + } +} + +impl<'a, P: ParserWithMode<'a>> Property<'a, P> for Ranges<'a> { + fn parse(node: FallibleNode<'a, P>, _: FallibleRoot<'a, P>) -> Result, FdtError> { + let Some(ranges) = node.properties()?.find("ranges")? else { + return Ok(None); + }; + + let parent_address_cells = + node.parent().ok_or(FdtError::MissingParent)?.property::()?.unwrap_or_default(); + let cell_sizes = node.property::()?.unwrap_or_default(); + + Ok(Some(Self { parent_address_cells, cell_sizes, ranges: ranges.value })) + } +} + +#[allow(missing_docs)] +pub struct RangesIter<'a, CAddr: CellCollector = u64, PAddr: CellCollector = u64, Len: CellCollector = u64> { + parent_address_cells: AddressCells, + cell_sizes: CellSizes, + ranges: &'a [u8], + _collectors: core::marker::PhantomData<*mut (CAddr, PAddr, Len)>, +} + +impl<'a, CAddr: CellCollector, PAddr: CellCollector, Len: CellCollector> Iterator + for RangesIter<'a, CAddr, PAddr, Len> +{ + type Item = Result, CollectCellsError>; + fn next(&mut self) -> Option { + let child_address_bytes = self.cell_sizes.address_cells * 4; + let parent_address_bytes = self.parent_address_cells.0 * 4; + let len_bytes = self.cell_sizes.size_cells * 4; + + let child_encoded_address = self.ranges.get(..child_address_bytes)?; + let parent_encoded_address = + self.ranges.get(child_address_bytes..child_address_bytes + parent_address_bytes)?; + let encoded_len = self + .ranges + .get(child_address_bytes + parent_address_bytes..child_address_bytes + parent_address_bytes + len_bytes)?; + + let mut child_address_collector = ::Builder::default(); + for encoded_address in child_encoded_address.chunks_exact(4) { + // TODO: replace this stuff with `array_chunks` when its stabilized + // + // These unwraps can't panic because `chunks_exact` guarantees that + // we'll always get slices of 4 bytes + if let Err(e) = child_address_collector.push(u32::from_be_bytes(encoded_address.try_into().unwrap())) { + return Some(Err(e)); + } + } + + let mut parent_address_collector = ::Builder::default(); + for encoded_address in parent_encoded_address.chunks_exact(4) { + // TODO: replace this stuff with `array_chunks` when its stabilized + // + // These unwraps can't panic because `chunks_exact` guarantees that + // we'll always get slices of 4 bytes + if let Err(e) = parent_address_collector.push(u32::from_be_bytes(encoded_address.try_into().unwrap())) { + return Some(Err(e)); + } + } + + let mut len_collector = ::Builder::default(); + for encoded_len in encoded_len.chunks_exact(4) { + // TODO: replace this stuff with `array_chunks` when its stabilized + // + // These unwraps can't panic because `chunks_exact` guarantees that + // we'll always get slices of 4 bytes + if let Err(e) = len_collector.push(u32::from_be_bytes(encoded_len.try_into().unwrap())) { + return Some(Err(e)); + } + } + + self.ranges = self.ranges.get(child_address_bytes + parent_address_bytes + len_bytes..)?; + Some(Ok(Range { + child_bus_address: CAddr::map(child_address_collector.finish()), + parent_bus_address: PAddr::map(parent_address_collector.finish()), + len: Len::map(len_collector.finish()), + })) + } +} + +/// A single range entry contained by a [`Ranges`] property. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Range { + #[allow(missing_docs)] + pub child_bus_address: CAddr, + #[allow(missing_docs)] + pub parent_bus_address: PAddr, + #[allow(missing_docs)] + pub len: Len, +} diff --git a/src/properties/reg.rs b/src/properties/reg.rs new file mode 100644 index 0000000..bf1edfd --- /dev/null +++ b/src/properties/reg.rs @@ -0,0 +1,200 @@ +use super::{cells::CellSizes, Property}; +use crate::{ + cell_collector::{BuildCellCollector, CellCollector, CollectCellsError}, + helpers::{FallibleNode, FallibleRoot}, + parsing::ParserWithMode, + FdtError, +}; + +/// A `reg` property value. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Reg<'a> { + cell_sizes: CellSizes, + encoded_array: &'a [u8], +} + +impl<'a> Reg<'a> { + /// Standard cell sizes for this [`Reg`]. + pub fn cell_sizes(self) -> CellSizes { + self.cell_sizes + } + + /// Returns an iterator over the raw property data for custom behavior. + pub fn iter_raw(self) -> RegRawIter<'a> { + RegRawIter { cell_sizes: self.cell_sizes, encoded_array: self.encoded_array } + } + + /// Iterate over the entries represented by this [`Reg`] and attempt to + /// collect them into the specified address and length types. + pub fn iter(self) -> RegIter<'a, Addr, Len> { + RegIter { + cell_sizes: self.cell_sizes, + encoded_array: self.encoded_array, + _collector: core::marker::PhantomData, + } + } +} + +impl<'a, P: ParserWithMode<'a>> Property<'a, P> for Reg<'a> { + fn parse(node: FallibleNode<'a, P>, _: FallibleRoot<'a, P>) -> Result, FdtError> { + let Some(prop) = node.raw_property("reg")? else { + return Ok(None); + }; + + let cell_sizes = match node.parent() { + Some(parent) => parent.property::()?.unwrap_or_default(), + None => CellSizes::default(), + }; + + let encoded_array = prop.value; + + if encoded_array.len() % (cell_sizes.address_cells * 4 + cell_sizes.size_cells * 4) != 0 { + return Err(FdtError::InvalidPropertyValue); + } + + Ok(Some(Self { cell_sizes, encoded_array })) + } +} + +/// An individual entry in a [`Reg`] property. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct RegEntry { + /// Starting address. + pub address: Addr, + /// Length of the region. + pub len: Len, +} + +#[allow(missing_docs)] +pub struct RegIter<'a, CAddr: CellCollector, Len: CellCollector> { + cell_sizes: CellSizes, + encoded_array: &'a [u8], + _collector: core::marker::PhantomData<*mut (CAddr, Len)>, +} + +impl<'a, CAddr: CellCollector, Len: CellCollector> Iterator for RegIter<'a, CAddr, Len> { + type Item = Result, CollectCellsError>; + fn next(&mut self) -> Option { + let address_bytes = self.cell_sizes.address_cells * 4; + let size_bytes = self.cell_sizes.size_cells * 4; + + let encoded_address = self.encoded_array.get(..address_bytes)?; + let encoded_len = self.encoded_array.get(address_bytes..address_bytes + size_bytes)?; + + let mut address_collector = ::Builder::default(); + for encoded_address in encoded_address.chunks_exact(4) { + // TODO: replace this stuff with `array_chunks` when its stabilized + // + // These unwraps can't panic because `chunks_exact` guarantees that + // we'll always get slices of 4 bytes + if let Err(e) = address_collector.push(u32::from_be_bytes(encoded_address.try_into().unwrap())) { + return Some(Err(e)); + } + } + + let mut len_collector = ::Builder::default(); + for encoded_len in encoded_len.chunks_exact(4) { + // TODO: replace this stuff with `array_chunks` when its stabilized + // + // These unwraps can't panic because `chunks_exact` guarantees that + // we'll always get slices of 4 bytes + if let Err(e) = len_collector.push(u32::from_be_bytes(encoded_len.try_into().unwrap())) { + return Some(Err(e)); + } + } + + self.encoded_array = self.encoded_array.get((address_bytes + size_bytes)..)?; + Some(Ok(RegEntry { address: CAddr::map(address_collector.finish()), len: Len::map(len_collector.finish()) })) + } +} + +#[allow(missing_docs)] +pub struct RegRawIter<'a> { + cell_sizes: CellSizes, + encoded_array: &'a [u8], +} + +impl<'a> Iterator for RegRawIter<'a> { + type Item = RawRegEntry<'a>; + fn next(&mut self) -> Option { + let address_bytes = self.cell_sizes.address_cells * 4; + let size_bytes = self.cell_sizes.size_cells * 4; + + let (addr_len, rest) = self.encoded_array.split_at_checked(address_bytes + size_bytes)?; + let (address, len) = addr_len.split_at(address_bytes); + + self.encoded_array = rest; + Some(RawRegEntry { address, len }) + } +} + +/// [`RegEntry`] except the address and length are represented by their raw +/// underlying bytes. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct RawRegEntry<'a> { + #[allow(missing_docs)] + pub address: &'a [u8], + #[allow(missing_docs)] + pub len: &'a [u8], +} + +/// [Devicetree 2.3.7. +/// `virtual-reg`](https://devicetree-specification.readthedocs.io/en/latest/chapter2-devicetree-basics.html#virtual-reg) +/// +/// The `virtual-reg` property specifies an effective address that maps to the +/// first physical address specified in the `reg` property of the device node. +/// This property enables boot programs to provide client programs with +/// virtual-to-physical mappings that have been set up. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct VirtualReg(u32); + +impl VirtualReg { + #[allow(missing_docs)] + pub fn into_u32(self) -> u32 { + self.0 + } +} + +impl From for u32 { + fn from(value: VirtualReg) -> Self { + value.0 + } +} + +impl<'a, P: ParserWithMode<'a>> Property<'a, P> for VirtualReg { + fn parse(node: FallibleNode<'a, P>, _: FallibleRoot<'a, P>) -> Result, FdtError> { + match node.properties()?.find("virtual-reg")? { + Some(vreg) => Ok(Some(Self(vreg.as_value()?))), + None => Ok(None), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn reg_raw_iter() { + let mut iter = RegRawIter { + cell_sizes: CellSizes { address_cells: 2, size_cells: 1 }, + encoded_array: &[0x55, 0x44, 0x33, 0x22, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD], + }; + + assert_eq!( + iter.next().unwrap(), + RawRegEntry { address: &[0x55, 0x44, 0x33, 0x22, 0x66, 0x77, 0x88, 0x99], len: &[0xAA, 0xBB, 0xCC, 0xDD] } + ); + } + + #[test] + fn reg_u64_iter() { + let mut iter = RegIter:: { + cell_sizes: CellSizes { address_cells: 2, size_cells: 1 }, + encoded_array: &[0x55, 0x44, 0x33, 0x22, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD], + _collector: core::marker::PhantomData, + }; + + assert_eq!(iter.next().unwrap().unwrap(), RegEntry { address: 0x5544332266778899, len: 0xAABBCCDD }); + } +} diff --git a/src/properties/values.rs b/src/properties/values.rs new file mode 100644 index 0000000..a068a82 --- /dev/null +++ b/src/properties/values.rs @@ -0,0 +1,146 @@ +use crate::{parsing::BigEndianU32, FdtError}; +use core::ffi::CStr; + +/// Error type indicating an invalid property value was encountered. +#[derive(Debug, Clone, Copy)] +pub struct InvalidPropertyValue; + +impl From for FdtError { + fn from(_: InvalidPropertyValue) -> Self { + FdtError::InvalidPropertyValue + } +} + +/// Helper trait for parsing property values. +pub trait PropertyValue<'a>: Sized { + /// Attempt to parse the raw property value bytes that represent the type + /// implementing this trait. + fn parse(value: &'a [u8]) -> Result; +} + +impl<'a> PropertyValue<'a> for u32 { + #[inline] + fn parse(value: &'a [u8]) -> Result { + match value { + [a, b, c, d] => Ok(u32::from_be_bytes([*a, *b, *c, *d])), + _ => Err(InvalidPropertyValue), + } + } +} + +impl<'a> PropertyValue<'a> for u64 { + #[inline] + fn parse(value: &'a [u8]) -> Result { + match value { + [a, b, c, d] => Ok(u64::from_be_bytes([0, 0, 0, 0, *a, *b, *c, *d])), + [a, b, c, d, e, f, g, h] => Ok(u64::from_be_bytes([*a, *b, *c, *d, *e, *f, *g, *h])), + _ => Err(InvalidPropertyValue), + } + } +} + +impl<'a> PropertyValue<'a> for usize { + #[inline] + fn parse(value: &'a [u8]) -> Result { + #[cfg(target_pointer_width = "32")] + let ret = match value { + [a, b, c, d] => Ok(usize::from_be_bytes([*a, *b, *c, *d])), + _ => Err(InvalidPropertyValue), + }; + + #[cfg(target_pointer_width = "64")] + let ret = match value { + [a, b, c, d] => Ok(usize::from_be_bytes([0, 0, 0, 0, *a, *b, *c, *d])), + [a, b, c, d, e, f, g, h] => Ok(usize::from_be_bytes([*a, *b, *c, *d, *e, *f, *g, *h])), + _ => Err(InvalidPropertyValue), + }; + + ret + } +} + +impl<'a> PropertyValue<'a> for BigEndianU32 { + #[inline] + fn parse(value: &'a [u8]) -> Result { + match value { + [a, b, c, d] => Ok(BigEndianU32::from_be(u32::from_ne_bytes([*a, *b, *c, *d]))), + _ => Err(InvalidPropertyValue), + } + } +} + +impl<'a> PropertyValue<'a> for &'a CStr { + #[inline] + fn parse(value: &'a [u8]) -> Result { + CStr::from_bytes_until_nul(value).map_err(|_| InvalidPropertyValue) + } +} + +impl<'a> PropertyValue<'a> for &'a str { + #[inline] + fn parse(value: &'a [u8]) -> Result { + core::str::from_utf8(value).map(|s| s.trim_end_matches('\0')).map_err(|_| InvalidPropertyValue) + } +} + +/// Property value represented by a list of [`u32`] values. +#[derive(Debug, Clone, Copy)] +pub struct U32List<'a>(&'a [u8]); + +impl<'a> U32List<'a> { + /// Returns an iterator over the individual [`u32`] components of the + /// property value. + pub fn iter(self) -> U32ListIter<'a> { + U32ListIter(self.0) + } +} + +impl<'a> PropertyValue<'a> for U32List<'a> { + fn parse(value: &'a [u8]) -> Result { + if !value.len().is_multiple_of(4) { + return Err(InvalidPropertyValue); + } + + Ok(Self(value)) + } +} + +#[allow(missing_docs)] +pub struct U32ListIter<'a>(&'a [u8]); + +impl<'a> Iterator for U32ListIter<'a> { + type Item = u32; + fn next(&mut self) -> Option { + let val = u32::from_be_bytes(self.0.get(..4)?.try_into().unwrap()); + self.0 = self.0.get(4..)?; + Some(val) + } +} + +/// Property value represented by a list of null-terminated string values. +#[derive(Debug, Clone)] +pub struct StringList<'a> { + strs: core::str::Split<'a, char>, +} + +impl<'a> PropertyValue<'a> for StringList<'a> { + #[inline] + fn parse(value: &'a [u8]) -> Result { + Ok(Self { strs: <&'a str as PropertyValue<'a>>::parse(value)?.split('\0') }) + } +} + +impl<'a> Iterator for StringList<'a> { + type Item = &'a str; + + #[inline(always)] + fn next(&mut self) -> Option { + self.strs.next() + } +} + +impl<'a> From<&'a str> for StringList<'a> { + fn from(value: &'a str) -> Self { + Self { strs: value.split('\0') } + } +} diff --git a/src/standard_nodes.rs b/src/standard_nodes.rs deleted file mode 100644 index d75416d..0000000 --- a/src/standard_nodes.rs +++ /dev/null @@ -1,349 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public License, -// v. 2.0. If a copy of the MPL was not distributed with this file, You can -// obtain one at https://mozilla.org/MPL/2.0/. - -use crate::{ - node::{CellSizes, FdtNode, NodeProperty}, - parsing::{BigEndianU32, BigEndianU64, CStr, FdtData}, - Fdt, -}; - -/// Represents the `/chosen` node with specific helper methods -#[derive(Debug, Clone, Copy)] -pub struct Chosen<'b, 'a: 'b> { - pub(crate) node: FdtNode<'b, 'a>, -} - -impl<'b, 'a: 'b> Chosen<'b, 'a> { - /// Contains the bootargs, if they exist - pub fn bootargs(self) -> Option<&'a str> { - self.node - .properties() - .find(|n| n.name == "bootargs") - .and_then(|n| core::str::from_utf8(&n.value[..n.value.len() - 1]).ok()) - } - - /// Searches for the node representing `stdout`, if the property exists, - /// attempting to resolve aliases if the node name doesn't exist as-is - pub fn stdout(self) -> Option> { - self.node - .properties() - .find(|n| n.name == "stdout-path") - .and_then(|n| core::str::from_utf8(&n.value[..n.value.len() - 1]).ok()) - .map(Self::split_stdinout_property) - .and_then(|(name, params)| { - self.node.header.find_node(name).map(|node| StdInOutPath::new(node, params)) - }) - } - - /// Searches for the node representing `stdout`, if the property exists, - /// attempting to resolve aliases if the node name doesn't exist as-is. If - /// no `stdin` property exists, but `stdout` is present, it will return the - /// node specified by the `stdout` property. - pub fn stdin(self) -> Option> { - self.node - .properties() - .find(|n| n.name == "stdin-path") - .and_then(|n| core::str::from_utf8(&n.value[..n.value.len() - 1]).ok()) - .map(Self::split_stdinout_property) - .and_then(|(name, params)| { - self.node.header.find_node(name).map(|node| StdInOutPath::new(node, params)) - }) - .or_else(|| self.stdout()) - } - - /// Splits a stdout-path or stdin-path property into its node path and optional parameters which are seperated by a colon ':'. - /// see https://devicetree-specification.readthedocs.io/en/latest/chapter3-devicenodes.html#chosen-node - /// example "/soc/uart@10000000" => ("/soc/uart@10000000", None) - /// example "/soc/uart@10000000:115200" => ("/soc/uart@10000000", Some("115200")) - /// example "/soc/uart@10000000:115200n8r" => ("/soc/uart@10000000", Some("115200n8r")) - fn split_stdinout_property(property: &str) -> (&str, Option<&str>) { - property - .split_once(':') - .map_or_else(|| (property, None), |(name, params)| (name, Some(params))) - } -} - -pub struct StdInOutPath<'b, 'a> { - pub(crate) node: FdtNode<'b, 'a>, - pub(crate) params: Option<&'a str>, -} - -impl<'b, 'a> StdInOutPath<'b, 'a> { - fn new(node: FdtNode<'b, 'a>, params: Option<&'a str>) -> Self { - Self { node, params } - } - - pub fn node(&self) -> FdtNode<'b, 'a> { - self.node - } - - pub fn params(&self) -> Option<&'a str> { - self.params - } -} - -/// Represents the root (`/`) node with specific helper methods -#[derive(Debug, Clone, Copy)] -pub struct Root<'b, 'a: 'b> { - pub(crate) node: FdtNode<'b, 'a>, -} - -impl<'b, 'a: 'b> Root<'b, 'a> { - /// Root node cell sizes - pub fn cell_sizes(self) -> CellSizes { - self.node.cell_sizes() - } - - /// `model` property - pub fn model(self) -> &'a str { - self.node - .properties() - .find(|p| p.name == "model") - .and_then(|p| core::str::from_utf8(p.value).map(|s| s.trim_end_matches('\0')).ok()) - .unwrap() - } - - /// `compatible` property - pub fn compatible(self) -> Compatible<'a> { - self.node.compatible().unwrap() - } - - /// Returns an iterator over all of the available properties - pub fn properties(self) -> impl Iterator> + 'b { - self.node.properties() - } - - /// Attempts to find the a property by its name - pub fn property(self, name: &str) -> Option> { - self.node.properties().find(|p| p.name == name) - } -} - -/// Represents the `/aliases` node with specific helper methods -#[derive(Debug, Clone, Copy)] -pub struct Aliases<'b, 'a: 'b> { - pub(crate) header: &'b Fdt<'a>, - pub(crate) node: FdtNode<'b, 'a>, -} - -impl<'b, 'a: 'b> Aliases<'b, 'a> { - /// Attempt to resolve an alias to a node name - pub fn resolve(self, alias: &str) -> Option<&'a str> { - self.node - .properties() - .find(|p| p.name == alias) - .and_then(|p| core::str::from_utf8(p.value).map(|s| s.trim_end_matches('\0')).ok()) - } - - /// Attempt to find the node specified by the given alias - pub fn resolve_node(self, alias: &str) -> Option> { - self.resolve(alias).and_then(|name| self.header.find_node(name)) - } - - /// Returns an iterator over all of the available aliases - pub fn all(self) -> impl Iterator + 'b { - self.node.properties().filter_map(|p| { - Some((p.name, core::str::from_utf8(p.value).map(|s| s.trim_end_matches('\0')).ok()?)) - }) - } -} - -/// Represents a `/cpus/cpu*` node with specific helper methods -#[derive(Debug, Clone, Copy)] -pub struct Cpu<'b, 'a: 'b> { - pub(crate) parent: FdtNode<'b, 'a>, - pub(crate) node: FdtNode<'b, 'a>, -} - -impl<'b, 'a: 'b> Cpu<'b, 'a> { - /// Return the IDs for the given CPU - pub fn ids(self) -> CpuIds<'a> { - let address_cells = self.node.parent_cell_sizes().address_cells; - - CpuIds { - reg: self - .node - .properties() - .find(|p| p.name == "reg") - .expect("reg is a required property of cpu nodes"), - address_cells, - } - } - - /// `clock-frequency` property - pub fn clock_frequency(self) -> usize { - self.node - .properties() - .find(|p| p.name == "clock-frequency") - .or_else(|| self.parent.property("clock-frequency")) - .map(|p| match p.value.len() { - 4 => BigEndianU32::from_bytes(p.value).unwrap().get() as usize, - 8 => BigEndianU64::from_bytes(p.value).unwrap().get() as usize, - _ => unreachable!(), - }) - .expect("clock-frequency is a required property of cpu nodes") - } - - /// `timebase-frequency` property - pub fn timebase_frequency(self) -> usize { - self.node - .properties() - .find(|p| p.name == "timebase-frequency") - .or_else(|| self.parent.property("timebase-frequency")) - .map(|p| match p.value.len() { - 4 => BigEndianU32::from_bytes(p.value).unwrap().get() as usize, - 8 => BigEndianU64::from_bytes(p.value).unwrap().get() as usize, - _ => unreachable!(), - }) - .expect("timebase-frequency is a required property of cpu nodes") - } - - /// Returns an iterator over all of the properties for the CPU node - pub fn properties(self) -> impl Iterator> + 'b { - self.node.properties() - } - - /// Attempts to find the a property by its name - pub fn property(self, name: &str) -> Option> { - self.node.properties().find(|p| p.name == name) - } -} - -/// Represents the value of the `reg` property of a `/cpus/cpu*` node which may -/// contain more than one CPU or thread ID -#[derive(Debug, Clone, Copy)] -pub struct CpuIds<'a> { - pub(crate) reg: NodeProperty<'a>, - pub(crate) address_cells: usize, -} - -impl<'a> CpuIds<'a> { - /// The first listed CPU ID, which will always exist - pub fn first(self) -> usize { - match self.address_cells { - 1 => BigEndianU32::from_bytes(self.reg.value).unwrap().get() as usize, - 2 => BigEndianU64::from_bytes(self.reg.value).unwrap().get() as usize, - n => panic!("address-cells of size {} is currently not supported", n), - } - } - - /// Returns an iterator over all of the listed CPU IDs - pub fn all(self) -> impl Iterator + 'a { - let mut vals = FdtData::new(self.reg.value); - core::iter::from_fn(move || match vals.remaining() { - [] => None, - _ => Some(match self.address_cells { - 1 => vals.u32()?.get() as usize, - 2 => vals.u64()?.get() as usize, - n => panic!("address-cells of size {} is currently not supported", n), - }), - }) - } -} - -/// Represents the `compatible` property of a node -#[derive(Clone, Copy)] -pub struct Compatible<'a> { - pub(crate) data: &'a [u8], -} - -impl<'a> Compatible<'a> { - /// First compatible string - pub fn first(self) -> &'a str { - CStr::new(self.data).expect("expected C str").as_str().unwrap() - } - - /// Returns an iterator over all available compatible strings - pub fn all(self) -> impl Iterator { - let mut data = self.data; - core::iter::from_fn(move || { - if data.is_empty() { - return None; - } - - match data.iter().position(|b| *b == b'\0') { - Some(idx) => { - let ret = Some(core::str::from_utf8(&data[..idx]).ok()?); - data = &data[idx + 1..]; - - ret - } - None => { - let ret = Some(core::str::from_utf8(data).ok()?); - data = &[]; - - ret - } - } - }) - } -} - -/// Represents the `/memory` node with specific helper methods -#[derive(Debug, Clone, Copy)] -pub struct Memory<'b, 'a: 'b> { - pub(crate) node: FdtNode<'b, 'a>, -} - -impl<'a> Memory<'_, 'a> { - /// Returns an iterator over all of the available memory regions - pub fn regions(&self) -> impl Iterator + 'a { - self.node.reg().unwrap() - } - - /// Returns the initial mapped area, if it exists - pub fn initial_mapped_area(&self) -> Option { - let mut mapped_area = None; - - if let Some(init_mapped_area) = self.node.property("initial_mapped_area") { - let mut stream = FdtData::new(init_mapped_area.value); - let effective_address = stream.u64().expect("effective address"); - let physical_address = stream.u64().expect("physical address"); - let size = stream.u32().expect("size"); - - mapped_area = Some(MappedArea { - effective_address: effective_address.get() as usize, - physical_address: physical_address.get() as usize, - size: size.get() as usize, - }); - } - - mapped_area - } -} - -/// An area described by the `initial-mapped-area` property of the `/memory` -/// node -#[derive(Debug, Clone, Copy, PartialEq)] -#[repr(C)] -pub struct MappedArea { - /// Effective address of the mapped area - pub effective_address: usize, - /// Physical address of the mapped area - pub physical_address: usize, - /// Size of the mapped area - pub size: usize, -} - -/// A memory region -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct MemoryRegion { - /// Starting address represented as a pointer - pub starting_address: *const u8, - /// Size of the memory region - pub size: Option, -} - -/// Range mapping child bus addresses to parent bus addresses -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct MemoryRange { - /// Starting address on child bus - pub child_bus_address: usize, - /// The high bits of the child bus' starting address, if present - pub child_bus_address_hi: u32, - /// Starting address on parent bus - pub parent_bus_address: usize, - /// Size of range - pub size: usize, -} diff --git a/src/tests.rs b/src/tests.rs index 06eeddc..6fdaaa0 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -4,74 +4,217 @@ extern crate std; -use crate::{node::RawReg, *}; +use crate::*; +use nodes::{AsNode, NodeName}; +use properties::{ + cells::CellSizes, + interrupts::{ + pci::{PciAddress, PciAddressHighBits}, + InterruptCells, InterruptMap, Interrupts, + }, + ranges::Range, + reg::{RawRegEntry, RegEntry}, + Compatible, +}; + +struct AlignArrayUp([u8; N]); + +impl AlignArrayUp { + const fn align_up(self) -> [u8; M] { + assert!(M > N); + assert!(M.is_multiple_of(4)); + + let mut copy: [u8; M] = [0u8; M]; + let mut i = 0; + + while i < N { + copy[i] = self.0[i]; + i += 1; + } + + copy + } +} + +#[repr(align(4))] +struct Align4([u8; N]); + +impl Align4 { + const fn new(a: [u8; N]) -> Self { + Self(a) + } -static TEST: &[u8] = include_bytes!("../dtb/test.dtb"); + fn as_slice(&self) -> &[u32] { + unsafe { core::slice::from_raw_parts(self.0.as_ptr().cast::(), self.0.len() / 4) } + } +} + +static TEST: Align4<3764> = Align4::new(AlignArrayUp(*include_bytes!("../dtb/test.dtb")).align_up::<3764>()); static ISSUE_3: &[u8] = include_bytes!("../dtb/issue-3.dtb"); static SIFIVE: &[u8] = include_bytes!("../dtb/sifive.dtb"); #[test] fn returns_fdt() { - assert!(Fdt::new(TEST).is_ok()); + assert!(Fdt::new(TEST.as_slice()).is_ok()); +} + +#[test] +fn root() { + let fdt = Fdt::new(TEST.as_slice()).unwrap(); + std::println!("{:?}", fdt.root()); +} + +#[test] +fn all_nodes() { + let fdt = Fdt::new(TEST.as_slice()).unwrap(); + assert_eq!( + fdt.root() + .all_nodes() + .map(|(depth, n)| std::format!("{depth} {}", n.name())) + .collect::>() + .join("\n"), + "1 chosen +1 memory@80000000 +1 cpus +2 cpu@0 +3 interrupt-controller +2 cpu-map +3 cluster0 +4 core0 +1 emptyproptest +1 soc +2 flash@20000000 +2 rtc@101000 +2 uart@10000000 +2 poweroff +2 reboot +2 test@100000 +2 pci@30000000 +2 virtio_mmio@10008000 +2 virtio_mmio@10007000 +2 virtio_mmio@10006000 +2 virtio_mmio@10005000 +2 virtio_mmio@10004000 +2 virtio_mmio@10003000 +2 virtio_mmio@10002000 +2 virtio_mmio@10001000 +2 plic@c000000 +2 clint@2000000" + ); } #[test] fn finds_root_node() { - let fdt = Fdt::new(TEST).unwrap(); - assert!(fdt.find_node("/").is_some(), "couldn't find root node"); + let fdt = Fdt::new(TEST.as_slice()).unwrap(); + assert!(fdt.root().find_node("/").is_some(), "couldn't find root node"); } #[test] fn finds_root_node_properties() { - let fdt = Fdt::new(TEST).unwrap(); - let prop = fdt - .find_node("/") - .unwrap() - .properties() - .any(|p| p.name == "compatible" && p.value == b"riscv-virtio\0"); + // infallible + let fdt = Fdt::new(TEST.as_slice()).unwrap(); + let prop = fdt.root().find_node("/").unwrap().properties().find("compatible").unwrap(); - assert!(prop); + assert_eq!(prop.value, b"riscv-virtio\0"); + + // fallible + let fdt = Fdt::new_fallible(TEST.as_slice()).unwrap(); + let prop = + fdt.root().unwrap().find_node("/").unwrap().unwrap().properties().unwrap().find("compatible").unwrap().unwrap(); + + assert_eq!(prop.value, b"riscv-virtio\0"); } #[test] fn finds_child_of_root_node() { - let fdt = Fdt::new(TEST).unwrap(); - assert!(fdt.find_node("/cpus").is_some(), "couldn't find cpus node"); + let fdt = Fdt::new(TEST.as_slice()).unwrap(); + let root = fdt.root(); + assert_eq!( + root.find_node("/cpus").unwrap().name(), + NodeName { name: "cpus", unit_address: None }, + "couldn't find cpus node" + ); + + assert_eq!( + root.find_node("/cpus/cpu@0/interrupt-controller").unwrap().name(), + NodeName { name: "interrupt-controller", unit_address: None }, + "couldn't find interrupt-controller node" + ); + + assert!(root.find_node("/cpus/cpu@1/interrupt-controller").is_none(), "couldn't find interrupt-controller node"); } #[test] -fn correct_flash_regions() { - let fdt = Fdt::new(TEST).unwrap(); - let regions = fdt.find_node("/soc/flash").unwrap().reg().unwrap().collect::>(); +fn finds_child_with_unit_address() { + let fdt = Fdt::new(TEST.as_slice()).unwrap(); + let root = fdt.root(); + assert_eq!( + root.find_node("/memory@80000000").unwrap().name(), + NodeName { name: "memory", unit_address: Some("80000000") }, + "couldn't find cpus node" + ); + assert!(root.find_node("/memory@80000001").is_none(), "didn't use unit address to filter!"); +} + +#[test] +fn properties() { + let fdt = Fdt::new(TEST.as_slice()).unwrap(); + let test = fdt.root().find_node("/soc/test").unwrap(); + + let props = test.properties().into_iter().map(|p| (p.name, p.value)).collect::>(); assert_eq!( - regions, + props, &[ - MemoryRegion { starting_address: 0x20000000 as *const u8, size: Some(0x2000000) }, - MemoryRegion { starting_address: 0x22000000 as *const u8, size: Some(0x2000000) } + ("phandle", &[0, 0, 0, 4][..]), + ("reg", &[0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0]), + ("compatible", b"sifive,test1\0sifive,test0\0syscon\0"), ] ); } +#[test] +fn correct_flash_regions() { + let fdt = Fdt::new(TEST.as_slice()).unwrap(); + let regions = fdt + .find_node("/soc/flash") + .unwrap() + .reg() + .unwrap() + .iter::() + .collect::, _>>() + .unwrap(); + + assert_eq!( + regions, + &[RegEntry { address: 0x20000000, len: 0x2000000 }, RegEntry { address: 0x22000000, len: 0x2000000 }] + ); +} + #[test] fn parses_populated_ranges() { - let fdt = Fdt::new(TEST).unwrap(); - let ranges = fdt.find_node("/soc/pci").unwrap().ranges().unwrap().collect::>(); + let fdt = Fdt::new(TEST.as_slice()).unwrap(); + let ranges = fdt + .find_node("/soc/pci") + .unwrap() + .ranges() + .unwrap() + .iter::() + .collect::, _>>() + .unwrap(); assert_eq!( ranges, &[ - MemoryRange { - child_bus_address: 0x0000_0000_0000_0000, - child_bus_address_hi: 0x0100_0000, + Range { + child_bus_address: PciAddress { hi: PciAddressHighBits::new(0x1000000), mid: 0, lo: 0 }, parent_bus_address: 0x3000000, - size: 0x10000, + len: 0x10000 }, - MemoryRange { - child_bus_address: 0x40000000, - child_bus_address_hi: 0x2000000, + Range { + child_bus_address: PciAddress { hi: PciAddressHighBits::new(0x2000000), mid: 0, lo: 0x40000000 }, parent_bus_address: 0x4000_0000, - size: 0x4000_0000, + len: 0x4000_0000 } ] ); @@ -79,209 +222,247 @@ fn parses_populated_ranges() { #[test] fn parses_empty_ranges() { - let fdt = Fdt::new(TEST).unwrap(); - let ranges = fdt.find_node("/soc").unwrap().ranges().unwrap().collect::>(); + let fdt = Fdt::new(TEST.as_slice()).unwrap(); + let ranges = fdt + .find_node("/soc") + .unwrap() + .ranges() + .unwrap() + .iter::() + .collect::, _>>() + .unwrap(); assert_eq!(ranges, &[]); } #[test] fn finds_with_addr() { - let fdt = Fdt::new(TEST).unwrap(); - assert_eq!(fdt.find_node("/soc/virtio_mmio@10004000").unwrap().name, "virtio_mmio@10004000"); + let fdt = Fdt::new(TEST.as_slice()).unwrap(); + assert_eq!( + fdt.find_node("/soc/virtio_mmio@10004000").unwrap().name(), + NodeName { name: "virtio_mmio", unit_address: Some("10004000") } + ); } #[test] fn compatibles() { - let fdt = Fdt::new(TEST).unwrap(); + let fdt = Fdt::new(TEST.as_slice()).unwrap(); let res = fdt .find_node("/soc/test") .unwrap() - .compatible() + .property::() .unwrap() - .all() + .into_iter() .all(|s| ["sifive,test1", "sifive,test0", "syscon"].contains(&s)); assert!(res); } #[test] -fn parent_cell_sizes() { - let fdt = Fdt::new(TEST).unwrap(); - let regions = fdt.find_node("/memory").unwrap().reg().unwrap().collect::>(); - - assert_eq!( - regions, - &[MemoryRegion { starting_address: 0x80000000 as *const u8, size: Some(0x20000000) }] - ); +fn cell_sizes() { + let fdt = Fdt::new(TEST.as_slice()).unwrap(); + + let cpu_cs = fdt.root().find_node("/cpus").unwrap().property::().unwrap(); + assert_eq!(cpu_cs, CellSizes { address_cells: 1, size_cells: 0 }); + + let soc_sc = fdt.root().find_node("/soc").unwrap().property::().unwrap(); + let test_cs = fdt.root().find_node("/soc/test").unwrap().property::(); + let pci_cs = fdt.root().find_node("/soc/pci").unwrap().property::().unwrap(); + assert_eq!(soc_sc, CellSizes { address_cells: 2, size_cells: 2 }); + assert_eq!(test_cs, None); + assert_ne!(pci_cs, soc_sc); } #[test] -fn no_properties() { - let fdt = Fdt::new(TEST).unwrap(); - let regions = fdt.find_node("/emptyproptest").unwrap(); - assert_eq!(regions.properties().count(), 0); +fn interrupt_map() { + let fdt = Fdt::new(TEST.as_slice()).unwrap(); + let root = fdt.root(); + + let entries: [(PciAddress, u64, Option, u64); 16] = [ + (PciAddress { hi: PciAddressHighBits::new(0), mid: 0, lo: 0 }, 1, None, 32), + (PciAddress { hi: PciAddressHighBits::new(0), mid: 0, lo: 0 }, 2, None, 33), + (PciAddress { hi: PciAddressHighBits::new(0), mid: 0, lo: 0 }, 3, None, 34), + (PciAddress { hi: PciAddressHighBits::new(0), mid: 0, lo: 0 }, 4, None, 35), + (PciAddress { hi: PciAddressHighBits::new(2048), mid: 0, lo: 0 }, 1, None, 33), + (PciAddress { hi: PciAddressHighBits::new(2048), mid: 0, lo: 0 }, 2, None, 34), + (PciAddress { hi: PciAddressHighBits::new(2048), mid: 0, lo: 0 }, 3, None, 35), + (PciAddress { hi: PciAddressHighBits::new(2048), mid: 0, lo: 0 }, 4, None, 32), + (PciAddress { hi: PciAddressHighBits::new(4096), mid: 0, lo: 0 }, 1, None, 34), + (PciAddress { hi: PciAddressHighBits::new(4096), mid: 0, lo: 0 }, 2, None, 35), + (PciAddress { hi: PciAddressHighBits::new(4096), mid: 0, lo: 0 }, 3, None, 32), + (PciAddress { hi: PciAddressHighBits::new(4096), mid: 0, lo: 0 }, 4, None, 33), + (PciAddress { hi: PciAddressHighBits::new(6144), mid: 0, lo: 0 }, 1, None, 35), + (PciAddress { hi: PciAddressHighBits::new(6144), mid: 0, lo: 0 }, 2, None, 32), + (PciAddress { hi: PciAddressHighBits::new(6144), mid: 0, lo: 0 }, 3, None, 33), + (PciAddress { hi: PciAddressHighBits::new(6144), mid: 0, lo: 0 }, 4, None, 34), + ]; + + for (entry, expected) in root + .find_node("/soc/pci") + .unwrap() + .property::, u64>>() + .unwrap() + .iter() + .zip(entries) + { + assert_eq!(entry.child_unit_address, expected.0); + assert_eq!(entry.child_interrupt_specifier, expected.1); + assert_eq!(entry.parent_unit_address, expected.2); + assert_eq!(entry.parent_interrupt_specifier, expected.3); + } } #[test] -fn finds_all_nodes() { - let fdt = Fdt::new(TEST).unwrap(); - - let mut all_nodes: std::vec::Vec<_> = fdt.all_nodes().map(|n| n.name).collect(); - all_nodes.sort_unstable(); - - assert_eq!( - all_nodes, - &[ - "/", - "chosen", - "clint@2000000", - "cluster0", - "core0", - "cpu-map", - "cpu@0", - "cpus", - "emptyproptest", - "flash@20000000", - "interrupt-controller", - "memory@80000000", - "pci@30000000", - "plic@c000000", - "poweroff", - "reboot", - "rtc@101000", - "soc", - "test@100000", - "uart@10000000", - "virtio_mmio@10001000", - "virtio_mmio@10002000", - "virtio_mmio@10003000", - "virtio_mmio@10004000", - "virtio_mmio@10005000", - "virtio_mmio@10006000", - "virtio_mmio@10007000", - "virtio_mmio@10008000" - ] - ) +fn no_properties() { + let fdt = Fdt::new(TEST.as_slice()).unwrap(); + let regions = fdt.find_node("/emptyproptest").unwrap(); + assert_eq!(regions.properties().into_iter().count(), 0); } #[test] fn required_nodes() { - let fdt = Fdt::new(TEST).unwrap(); - fdt.cpus().next().unwrap(); - fdt.memory(); - fdt.chosen(); + let fdt = Fdt::new(TEST.as_slice()).unwrap(); + let root = fdt.root(); + root.cpus().iter().next().unwrap(); + root.memory(); + root.chosen(); } #[test] fn doesnt_exist() { - let fdt = Fdt::new(TEST).unwrap(); + let fdt = Fdt::new(TEST.as_slice()).unwrap(); assert!(fdt.find_node("/this/doesnt/exist").is_none()); } #[test] fn raw_reg() { - let fdt = Fdt::new(TEST).unwrap(); - let regions = - fdt.find_node("/soc/flash").unwrap().raw_reg().unwrap().collect::>(); + let fdt = Fdt::new(TEST.as_slice()).unwrap(); + let regions = fdt.find_node("/soc/flash").unwrap().reg().unwrap().iter_raw().collect::>(); assert_eq!( regions, &[ - RawReg { address: &0x20000000u64.to_be_bytes(), size: &0x2000000u64.to_be_bytes() }, - RawReg { address: &0x22000000u64.to_be_bytes(), size: &0x2000000u64.to_be_bytes() } + RawRegEntry { address: &0x20000000u64.to_be_bytes(), len: &0x2000000u64.to_be_bytes() }, + RawRegEntry { address: &0x22000000u64.to_be_bytes(), len: &0x2000000u64.to_be_bytes() } ] ); } #[test] fn issue_3() { - let fdt = Fdt::new(ISSUE_3).unwrap(); - fdt.find_all_nodes("uart").for_each(|n| std::println!("{:?}", n)); + let fdt = Fdt::new_unaligned(ISSUE_3).unwrap(); + fdt.find_all_nodes_with_name("uart").for_each(|n| std::println!("{:?}", n)); } #[test] fn issue_4() { - let fdt = Fdt::new(ISSUE_3).unwrap(); + let fdt = Fdt::new_unaligned(ISSUE_3).unwrap(); fdt.all_nodes().for_each(|n| std::println!("{:?}", n)); } #[test] fn cpus() { - let fdt = Fdt::new(TEST).unwrap(); - for cpu in fdt.cpus() { - cpu.ids().all().for_each(|n| std::println!("{:?}", n)); + let fdt = Fdt::new(TEST.as_slice()).unwrap(); + for cpu in fdt.root().cpus().iter() { + std::println!( + "{:?}", + cpu.as_node() + .properties() + .iter() + .map(|p| std::format!("{}={:?}", p.name, p.value)) + .collect::>() + ); + cpu.reg::().iter().for_each(|n| std::println!("{:?}", n)); } } #[test] fn invalid_node() { - let fdt = Fdt::new(TEST).unwrap(); + let fdt = Fdt::new(TEST.as_slice()).unwrap(); assert!(fdt.find_node("this/is/an invalid node///////////").is_none()); } #[test] fn aliases() { - let fdt = Fdt::new(SIFIVE).unwrap(); - let aliases = fdt.aliases().unwrap(); - for (_, node_path) in aliases.all() { + let fdt = Fdt::new_unaligned(SIFIVE).unwrap(); + let aliases = fdt.root().aliases().unwrap(); + for (_, node_path) in aliases.iter() { assert!(fdt.find_node(node_path).is_some(), "path: {:?}", node_path); } } #[test] fn stdout() { - let fdt = Fdt::new(TEST).unwrap(); - let stdout = fdt.chosen().stdout().unwrap(); - assert!(stdout.node().name == "uart@10000000"); - assert!(stdout.params() == Some("115200")); + let fdt = Fdt::new(TEST.as_slice()).unwrap(); + let stdout = fdt.root().chosen().stdout().unwrap(); + assert!(stdout.node.name() == NodeName { name: "uart", unit_address: Some("10000000") }); + assert!(stdout.params == Some("115200")); } #[test] fn stdin() { - let fdt = Fdt::new(TEST).unwrap(); - let stdin = fdt.chosen().stdin().unwrap(); - assert!(stdin.node().name == "uart@10000000"); - assert!(stdin.params().is_none()); + let fdt = Fdt::new(TEST.as_slice()).unwrap(); + let stdin = fdt.root().chosen().stdin().unwrap(); + assert!(stdin.node.name() == NodeName { name: "uart", unit_address: Some("10000000") }); + assert!(stdin.params.is_none()); } #[test] fn node_property_str_value() { - let fdt = Fdt::new(TEST).unwrap(); + let fdt = Fdt::new(TEST.as_slice()).unwrap(); let cpu0 = fdt.find_node("/cpus/cpu@0").unwrap(); - assert_eq!(cpu0.property("riscv,isa").unwrap().as_str().unwrap(), "rv64imafdcsu"); + assert_eq!(cpu0.properties().find("riscv,isa").unwrap().as_value::<&str>().unwrap(), "rv64imafdcsu"); } #[test] fn model_value() { - let fdt = Fdt::new(TEST).unwrap(); + let fdt = Fdt::new(TEST.as_slice()).unwrap(); assert_eq!(fdt.root().model(), "riscv-virtio,qemu"); } #[test] fn memory_node() { - let fdt = Fdt::new(TEST).unwrap(); - assert_eq!(fdt.memory().regions().count(), 1); + let fdt = Fdt::new(TEST.as_slice()).unwrap(); + let root = fdt.root(); + assert_eq!(root.memory().reg().iter::().count(), 1); } #[test] fn interrupt_cells() { - let fdt = Fdt::new(TEST).unwrap(); + let fdt = Fdt::new(TEST.as_slice()).unwrap(); let uart = fdt.find_node("/soc/uart").unwrap(); - std::println!("{:?}", uart.parent_interrupt_cells()); - assert_eq!(uart.interrupts().unwrap().collect::>(), std::vec![0xA]); + std::println!("{:?}", uart.parent().unwrap().property::()); + let interrupts = match uart.property::().unwrap() { + Interrupts::Legacy(legacy) => legacy, + _ => unreachable!(), + }; + + assert_eq!(interrupts.iter::().collect::, _>>().unwrap(), &[0xA]); } #[test] -fn property_str_list() { - let fdt = Fdt::new(TEST).unwrap(); - let test = fdt.find_node("/soc/test").unwrap(); - let expected = ["sifive,test1", "sifive,test0", "syscon"]; - let compat = test.property("compatible").unwrap(); +fn cpu_map() { + let fdt = Fdt::new(TEST.as_slice()).unwrap(); + let topology = fdt.root().cpus().topology().unwrap(); - assert_eq!(compat.iter_str().count(), expected.len()); + assert_eq!(topology.sockets().count(), 0); - test.property("compatible").unwrap().iter_str().zip(expected).for_each(|(prop, exp)| { - assert_eq!(prop, exp); - }); + assert_eq!(topology.clusters().next().unwrap().id(), 0); + assert_eq!(topology.clusters().next().unwrap().cores().next().unwrap().id(), 0); + assert_eq!( + topology + .clusters() + .next() + .unwrap() + .cores() + .next() + .unwrap() + .cpu() + .unwrap() + .as_node() + .raw_property("riscv,isa") + .unwrap() + .value, + b"rv64imafdcsu\0" + ); } diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..97532fb --- /dev/null +++ b/src/util.rs @@ -0,0 +1,5 @@ +pub fn cast_slice(s: &[u32]) -> &[u8] { + // SAFETY: it is always valid to cast a `u32` slice to a slice of `u8`s as + // they have lower alignment requirements and there is no padding + unsafe { core::slice::from_raw_parts(s.as_ptr().cast(), core::mem::size_of_val(s)) } +}