123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415 |
- // 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/.
- //! # `fdt`
- //!
- //! A pure-Rust `#![no_std]` crate for parsing Flattened Devicetrees, with the goal of having a
- //! very ergonomic and idiomatic API.
- //!
- //! [](https://crates.io/crates/fdt) [](https://docs.rs/fdt) 
- //!
- //! ## License
- //!
- //! This crate is licensed under the Mozilla Public License 2.0 (see the LICENSE file).
- //!
- //! ## Example
- //!
- //! ```rust,no_run
- //! static MY_FDT: &[u8] = include_bytes!("../dtb/test.dtb");
- //!
- //! 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
- //! );
- //!
- //! 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: {}", stdout.node().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);
- //! }
- //! }
- //! }
- //! ```
- #![no_std]
- #[cfg(test)]
- mod tests;
- pub mod node;
- 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};
- /// Possible errors when attempting to create an `Fdt`
- #[derive(Debug, Clone, Copy, PartialEq)]
- 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,
- }
- impl core::fmt::Display for FdtError {
- fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
- 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")
- }
- }
- }
- }
- /// A flattened devicetree located somewhere in memory
- ///
- /// Note on `Debug` impl: by default the `Debug` impl of this struct will not
- /// 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],
- header: FdtHeader,
- }
- impl core::fmt::Debug for Fdt<'_> {
- fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
- #[cfg(feature = "pretty-printing")]
- pretty_print::print_node(f, self.root().node, 0)?;
- #[cfg(not(feature = "pretty-printing"))]
- f.debug_struct("Fdt").finish_non_exhaustive()?;
- Ok(())
- }
- }
- #[derive(Debug, Clone, Copy)]
- #[repr(C)]
- struct FdtHeader {
- /// FDT header magic
- magic: BigEndianU32,
- /// Total size in bytes of the FDT structure
- totalsize: BigEndianU32,
- /// Offset in bytes from the start of the header to the structure block
- off_dt_struct: BigEndianU32,
- /// Offset in bytes from the start of the header to the strings block
- off_dt_strings: BigEndianU32,
- /// Offset in bytes from the start of the header to the memory reservation
- /// block
- off_mem_rsvmap: BigEndianU32,
- /// FDT version
- version: BigEndianU32,
- /// Last compatible FDT version
- last_comp_version: BigEndianU32,
- /// System boot CPU ID
- boot_cpuid_phys: BigEndianU32,
- /// Length in bytes of the strings block
- size_dt_strings: BigEndianU32,
- /// Length in bytes of the struct block
- size_dt_struct: BigEndianU32,
- }
- impl FdtHeader {
- fn valid_magic(&self) -> bool {
- self.magic.get() == 0xd00dfeed
- }
- fn struct_range(&self) -> core::ops::Range<usize> {
- 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<usize> {
- 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<Self> {
- 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()?,
- })
- }
- }
- impl<'a> Fdt<'a> {
- /// 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<Self, FdtError> {
- let mut stream = FdtData::new(data);
- let header = FdtHeader::from_bytes(&mut stream).ok_or(FdtError::BufferTooSmall)?;
- if !header.valid_magic() {
- return Err(FdtError::BadMagic);
- } else if data.len() < header.totalsize.get() as usize {
- return Err(FdtError::BufferTooSmall);
- }
- Ok(Self { data, header })
- }
- /// # 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<Self, FdtError> {
- if ptr.is_null() {
- return Err(FdtError::BadPtr);
- }
- let tmp_header = core::slice::from_raw_parts(ptr, core::mem::size_of::<FdtHeader>());
- let real_size =
- FdtHeader::from_bytes(&mut FdtData::new(tmp_header)).unwrap().totalsize.get() as usize;
- Self::new(core::slice::from_raw_parts(ptr, real_size))
- }
- /// Return the `/aliases` node, if one exists
- pub fn aliases(&self) -> Option<Aliases<'_, 'a>> {
- Some(Aliases {
- node: node::find_node(&mut FdtData::new(self.structs_block()), "/aliases", self, None)?,
- header: self,
- })
- }
- /// 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")
- }
- /// Return the `/cpus` node, which is always available
- pub fn cpus(&self) -> impl Iterator<Item = Cpu<'_, 'a>> {
- let parent = self.find_node("/cpus").expect("/cpus is a required node");
- parent
- .children()
- .filter(|c| c.name.split('@').next().unwrap() == "cpu")
- .map(move |cpu| Cpu { parent, node: cpu })
- }
- /// Returns the memory node, which is always available
- pub fn memory(&self) -> Memory<'_, 'a> {
- Memory { node: self.find_node("/memory").expect("requires memory node") }
- }
- /// Returns an iterator over the memory reservations
- pub fn memory_reservations(&self) -> impl Iterator<Item = MemoryReservation> + '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)?;
- if res.address() as usize == 0 && res.size() == 0 {
- done = true;
- return None;
- }
- Some(res)
- })
- }
- /// Return the root (`/`) node, which is always available
- pub fn root(&self) -> Root<'_, 'a> {
- Root { node: self.find_node("/").expect("/ is a required node") }
- }
- /// 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<node::FdtNode<'_, 'a>> {
- let node = node::find_node(&mut FdtData::new(self.structs_block()), path, self, None);
- node.or_else(|| self.aliases()?.resolve_node(path))
- }
- /// 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<node::FdtNode<'_, 'a>> {
- self.all_nodes().find(|n| {
- n.compatible().and_then(|compats| compats.all().find(|c| with.contains(c))).is_some()
- })
- }
- /// Searches for the given `phandle`
- pub fn find_phandle(&self, phandle: u32) -> Option<node::FdtNode<'_, 'a>> {
- 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)
- })
- }
- /// 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<Item = node::FdtNode<'_, 'a>> {
- 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,
- };
- let (parent, bad_parent) = match parent {
- Some(parent) => (parent, false),
- None => (self.find_node("/").unwrap(), true),
- };
- let mut child_iter = parent.children();
- core::iter::from_fn(move || {
- if done || !valid_path || bad_parent {
- return None;
- }
- if only_root {
- done = true;
- return self.find_node("/");
- }
- let mut ret = None;
- #[allow(clippy::while_let_on_iterator)]
- while let Some(child) = child_iter.next() {
- if child.name.split('@').next()? == child_name {
- ret = Some(child);
- break;
- }
- }
- ret
- })
- }
- /// Returns an iterator over all of the nodes in the devicetree, depth-first
- pub fn all_nodes(&self) -> impl Iterator<Item = node::FdtNode<'_, 'a>> {
- node::all_nodes(self)
- }
- /// Returns an iterator over all of the strings inside of the strings block
- pub fn strings(&self) -> impl Iterator<Item = &'a str> {
- let mut block = self.strings_block();
- core::iter::from_fn(move || {
- if block.is_empty() {
- return None;
- }
- let cstr = CStr::new(block)?;
- block = &block[cstr.len() + 1..];
- cstr.as_str()
- })
- }
- /// 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?")
- }
- fn str_at_offset(&self, offset: usize) -> &'a str {
- self.cstr_at_offset(offset).as_str().expect("not utf-8 cstr")
- }
- fn strings_block(&self) -> &'a [u8] {
- &self.data[self.header.strings_range()]
- }
- fn structs_block(&self) -> &'a [u8] {
- &self.data[self.header.struct_range()]
- }
- }
|