fdt/
lib.rs

1// This Source Code Form is subject to the terms of the Mozilla Public License,
2// v. 2.0. If a copy of the MPL was not distributed with this file, You can
3// obtain one at https://mozilla.org/MPL/2.0/.
4
5//! # `fdt`
6//!
7//! A pure-Rust `#![no_std]` crate for parsing Flattened Devicetrees, with the goal of having a
8//! very ergonomic and idiomatic API.
9//!
10//! [![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)
11//!
12//! ## License
13//!
14//! This crate is licensed under the Mozilla Public License 2.0 (see the LICENSE file).
15//!
16//! ## Example
17//!
18//! ```rust,no_run
19//! static MY_FDT: &[u8] = include_bytes!("../dtb/test.dtb");
20//!
21//! fn main() {
22//!     let fdt = fdt::Fdt::new(MY_FDT).unwrap();
23//!
24//!     println!("This is a devicetree representation of a {}", fdt.root().model());
25//!     println!("...which is compatible with at least: {}", fdt.root().compatible().first());
26//!     println!("...and has {} CPU(s)", fdt.cpus().count());
27//!     println!(
28//!         "...and has at least one memory location at: {:#X}\n",
29//!         fdt.memory().regions().next().unwrap().starting_address as usize
30//!     );
31//!
32//!     let chosen = fdt.chosen();
33//!     if let Some(bootargs) = chosen.bootargs() {
34//!         println!("The bootargs are: {:?}", bootargs);
35//!     }
36//!
37//!     if let Some(stdout) = chosen.stdout() {
38//!         println!("It would write stdout to: {}", stdout.name);
39//!     }
40//!
41//!     let soc = fdt.find_node("/soc");
42//!     println!("Does it have a `/soc` node? {}", if soc.is_some() { "yes" } else { "no" });
43//!     if let Some(soc) = soc {
44//!         println!("...and it has the following children:");
45//!         for child in soc.children() {
46//!             println!("    {}", child.name);
47//!         }
48//!     }
49//! }
50//! ```
51
52#![no_std]
53
54#[cfg(test)]
55mod tests;
56
57pub mod node;
58mod parsing;
59pub mod standard_nodes;
60
61#[cfg(feature = "pretty-printing")]
62mod pretty_print;
63
64use node::MemoryReservation;
65use parsing::{BigEndianU32, CStr, FdtData};
66use standard_nodes::{Aliases, Chosen, Cpu, Memory, MemoryRegion, Root};
67
68/// Possible errors when attempting to create an `Fdt`
69#[derive(Debug, Clone, Copy, PartialEq)]
70pub enum FdtError {
71    /// The FDT had an invalid magic value
72    BadMagic,
73    /// The given pointer was null
74    BadPtr,
75    /// The slice passed in was too small to fit the given total size of the FDT
76    /// structure
77    BufferTooSmall,
78}
79
80impl core::fmt::Display for FdtError {
81    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
82        match self {
83            FdtError::BadMagic => write!(f, "bad FDT magic value"),
84            FdtError::BadPtr => write!(f, "an invalid pointer was passed"),
85            FdtError::BufferTooSmall => {
86                write!(f, "the given buffer was too small to contain a FDT header")
87            }
88        }
89    }
90}
91
92/// A flattened devicetree located somewhere in memory
93///
94/// Note on `Debug` impl: by default the `Debug` impl of this struct will not
95/// print any useful information, if you would like a best-effort tree print
96/// which looks similar to `dtc`'s output, enable the `pretty-printing` feature
97#[derive(Clone, Copy)]
98pub struct Fdt<'a> {
99    data: &'a [u8],
100    header: FdtHeader,
101}
102
103impl core::fmt::Debug for Fdt<'_> {
104    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
105        #[cfg(feature = "pretty-printing")]
106        pretty_print::print_node(f, self.root().node, 0)?;
107
108        #[cfg(not(feature = "pretty-printing"))]
109        f.debug_struct("Fdt").finish_non_exhaustive()?;
110
111        Ok(())
112    }
113}
114
115#[derive(Debug, Clone, Copy)]
116#[repr(C)]
117struct FdtHeader {
118    /// FDT header magic
119    magic: BigEndianU32,
120    /// Total size in bytes of the FDT structure
121    totalsize: BigEndianU32,
122    /// Offset in bytes from the start of the header to the structure block
123    off_dt_struct: BigEndianU32,
124    /// Offset in bytes from the start of the header to the strings block
125    off_dt_strings: BigEndianU32,
126    /// Offset in bytes from the start of the header to the memory reservation
127    /// block
128    off_mem_rsvmap: BigEndianU32,
129    /// FDT version
130    version: BigEndianU32,
131    /// Last compatible FDT version
132    last_comp_version: BigEndianU32,
133    /// System boot CPU ID
134    boot_cpuid_phys: BigEndianU32,
135    /// Length in bytes of the strings block
136    size_dt_strings: BigEndianU32,
137    /// Length in bytes of the struct block
138    size_dt_struct: BigEndianU32,
139}
140
141impl FdtHeader {
142    fn valid_magic(&self) -> bool {
143        self.magic.get() == 0xd00dfeed
144    }
145
146    fn struct_range(&self) -> core::ops::Range<usize> {
147        let start = self.off_dt_struct.get() as usize;
148        let end = start + self.size_dt_struct.get() as usize;
149
150        start..end
151    }
152
153    fn strings_range(&self) -> core::ops::Range<usize> {
154        let start = self.off_dt_strings.get() as usize;
155        let end = start + self.size_dt_strings.get() as usize;
156
157        start..end
158    }
159
160    fn from_bytes(bytes: &mut FdtData<'_>) -> Option<Self> {
161        Some(Self {
162            magic: bytes.u32()?,
163            totalsize: bytes.u32()?,
164            off_dt_struct: bytes.u32()?,
165            off_dt_strings: bytes.u32()?,
166            off_mem_rsvmap: bytes.u32()?,
167            version: bytes.u32()?,
168            last_comp_version: bytes.u32()?,
169            boot_cpuid_phys: bytes.u32()?,
170            size_dt_strings: bytes.u32()?,
171            size_dt_struct: bytes.u32()?,
172        })
173    }
174}
175
176impl<'a> Fdt<'a> {
177    /// Construct a new `Fdt` from a byte buffer
178    ///
179    /// Note: this function does ***not*** require that the data be 4-byte
180    /// aligned
181    pub fn new(data: &'a [u8]) -> Result<Self, FdtError> {
182        let mut stream = FdtData::new(data);
183        let header = FdtHeader::from_bytes(&mut stream).ok_or(FdtError::BufferTooSmall)?;
184
185        if !header.valid_magic() {
186            return Err(FdtError::BadMagic);
187        } else if data.len() < header.totalsize.get() as usize {
188            return Err(FdtError::BufferTooSmall);
189        }
190
191        Ok(Self { data, header })
192    }
193
194    /// # Safety
195    /// This function performs a read to verify the magic value. If the pointer
196    /// is invalid this can result in undefined behavior.
197    ///
198    /// Note: this function does ***not*** require that the data be 4-byte
199    /// aligned
200    pub unsafe fn from_ptr(ptr: *const u8) -> Result<Self, FdtError> {
201        if ptr.is_null() {
202            return Err(FdtError::BadPtr);
203        }
204
205        let tmp_header = core::slice::from_raw_parts(ptr, core::mem::size_of::<FdtHeader>());
206        let real_size =
207            FdtHeader::from_bytes(&mut FdtData::new(tmp_header)).unwrap().totalsize.get() as usize;
208
209        Self::new(core::slice::from_raw_parts(ptr, real_size))
210    }
211
212    /// Return the `/aliases` node, if one exists
213    pub fn aliases(&self) -> Option<Aliases<'_, 'a>> {
214        Some(Aliases {
215            node: node::find_node(&mut FdtData::new(self.structs_block()), "/aliases", self, None)?,
216            header: self,
217        })
218    }
219
220    /// Searches for the `/chosen` node, which is always available
221    pub fn chosen(&self) -> Chosen<'_, 'a> {
222        node::find_node(&mut FdtData::new(self.structs_block()), "/chosen", self, None)
223            .map(|node| Chosen { node })
224            .expect("/chosen is required")
225    }
226
227    /// Return the `/cpus` node, which is always available
228    pub fn cpus(&self) -> impl Iterator<Item = Cpu<'_, 'a>> {
229        let parent = self.find_node("/cpus").expect("/cpus is a required node");
230
231        parent
232            .children()
233            .filter(|c| c.name.split('@').next().unwrap() == "cpu")
234            .map(move |cpu| Cpu { parent, node: cpu })
235    }
236
237    /// Returns the memory node, which is always available
238    pub fn memory(&self) -> Memory<'_, 'a> {
239        Memory { node: self.find_node("/memory").expect("requires memory node") }
240    }
241
242    /// Returns an iterator over the memory reservations
243    pub fn memory_reservations(&self) -> impl Iterator<Item = MemoryReservation> + 'a {
244        let mut stream = FdtData::new(&self.data[self.header.off_mem_rsvmap.get() as usize..]);
245        let mut done = false;
246
247        core::iter::from_fn(move || {
248            if stream.is_empty() || done {
249                return None;
250            }
251
252            let res = MemoryReservation::from_bytes(&mut stream)?;
253
254            if res.address() as usize == 0 && res.size() == 0 {
255                done = true;
256                return None;
257            }
258
259            Some(res)
260        })
261    }
262
263    /// Return the root (`/`) node, which is always available
264    pub fn root(&self) -> Root<'_, 'a> {
265        Root { node: self.find_node("/").expect("/ is a required node") }
266    }
267
268    /// Returns the first node that matches the node path, if you want all that
269    /// match the path, use `find_all_nodes`. This will automatically attempt to
270    /// resolve aliases if `path` is not found.
271    ///
272    /// Node paths must begin with a leading `/` and are ASCII only. Passing in
273    /// an invalid node path or non-ASCII node name in the path will return
274    /// `None`, as they will not be found within the devicetree structure.
275    ///
276    /// Note: if the address of a node name is left out, the search will find
277    /// the first node that has a matching name, ignoring the address portion if
278    /// it exists.
279    pub fn find_node(&self, path: &str) -> Option<node::FdtNode<'_, 'a>> {
280        let node = node::find_node(&mut FdtData::new(self.structs_block()), path, self, None);
281        node.or_else(|| self.aliases()?.resolve_node(path))
282    }
283
284    /// Searches for a node which contains a `compatible` property and contains
285    /// one of the strings inside of `with`
286    pub fn find_compatible(&self, with: &[&str]) -> Option<node::FdtNode<'_, 'a>> {
287        self.all_nodes().find(|n| {
288            n.compatible().and_then(|compats| compats.all().find(|c| with.contains(c))).is_some()
289        })
290    }
291
292    /// Searches for the given `phandle`
293    pub fn find_phandle(&self, phandle: u32) -> Option<node::FdtNode<'_, 'a>> {
294        self.all_nodes().find(|n| {
295            n.properties()
296                .find(|p| p.name == "phandle")
297                .and_then(|p| Some(BigEndianU32::from_bytes(p.value)?.get() == phandle))
298                .unwrap_or(false)
299        })
300    }
301
302    /// Returns an iterator over all of the available nodes with the given path.
303    /// This does **not** attempt to find any node with the same name as the
304    /// provided path, if you're looking to do that, [`Fdt::all_nodes`] will
305    /// allow you to iterate over each node's name and filter for the desired
306    /// node(s).
307    ///
308    /// For example:
309    /// ```rust
310    /// static MY_FDT: &[u8] = include_bytes!("../dtb/test.dtb");
311    ///
312    /// let fdt = fdt::Fdt::new(MY_FDT).unwrap();
313    ///
314    /// for node in fdt.find_all_nodes("/soc/virtio_mmio") {
315    ///     println!("{}", node.name);
316    /// }
317    /// ```
318    /// prints:
319    /// ```notrust
320    /// virtio_mmio@10008000
321    /// virtio_mmio@10007000
322    /// virtio_mmio@10006000
323    /// virtio_mmio@10005000
324    /// virtio_mmio@10004000
325    /// virtio_mmio@10003000
326    /// virtio_mmio@10002000
327    /// virtio_mmio@10001000
328    /// ```
329    pub fn find_all_nodes(&self, path: &'a str) -> impl Iterator<Item = node::FdtNode<'_, 'a>> {
330        let mut done = false;
331        let only_root = path == "/";
332        let valid_path = path.chars().fold(0, |acc, c| acc + if c == '/' { 1 } else { 0 }) >= 1;
333
334        let mut path_split = path.rsplitn(2, '/');
335        let child_name = path_split.next().unwrap();
336        let parent = match path_split.next() {
337            Some("") => Some(self.root().node),
338            Some(s) => node::find_node(&mut FdtData::new(self.structs_block()), s, self, None),
339            None => None,
340        };
341
342        let (parent, bad_parent) = match parent {
343            Some(parent) => (parent, false),
344            None => (self.find_node("/").unwrap(), true),
345        };
346
347        let mut child_iter = parent.children();
348
349        core::iter::from_fn(move || {
350            if done || !valid_path || bad_parent {
351                return None;
352            }
353
354            if only_root {
355                done = true;
356                return self.find_node("/");
357            }
358
359            let mut ret = None;
360
361            #[allow(clippy::while_let_on_iterator)]
362            while let Some(child) = child_iter.next() {
363                if child.name.split('@').next()? == child_name {
364                    ret = Some(child);
365                    break;
366                }
367            }
368
369            ret
370        })
371    }
372
373    /// Returns an iterator over all of the nodes in the devicetree, depth-first
374    pub fn all_nodes(&self) -> impl Iterator<Item = node::FdtNode<'_, 'a>> {
375        node::all_nodes(self)
376    }
377
378    /// Returns an iterator over all of the strings inside of the strings block
379    pub fn strings(&self) -> impl Iterator<Item = &'a str> {
380        let mut block = self.strings_block();
381
382        core::iter::from_fn(move || {
383            if block.is_empty() {
384                return None;
385            }
386
387            let cstr = CStr::new(block)?;
388
389            block = &block[cstr.len() + 1..];
390
391            cstr.as_str()
392        })
393    }
394
395    /// Total size of the devicetree in bytes
396    pub fn total_size(&self) -> usize {
397        self.header.totalsize.get() as usize
398    }
399
400    fn cstr_at_offset(&self, offset: usize) -> CStr<'a> {
401        CStr::new(&self.strings_block()[offset..]).expect("no null terminating string on C str?")
402    }
403
404    fn str_at_offset(&self, offset: usize) -> &'a str {
405        self.cstr_at_offset(offset).as_str().expect("not utf-8 cstr")
406    }
407
408    fn strings_block(&self) -> &'a [u8] {
409        &self.data[self.header.strings_range()]
410    }
411
412    fn structs_block(&self) -> &'a [u8] {
413        &self.data[self.header.struct_range()]
414    }
415}