fdt 0.2.0-alpha2

A pure-Rust `#![no_std]` crate for parsing Flattened Devicetrees
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
// 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.
//!
//! [![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).
//!
//! ## Example
//!
//! ```rust
//! static MY_FDT: &[u8] = include_bytes!("../dtb/test.dtb");
//!
//! fn main() {
//!     let fdt = fdt::Fdt::new_unaligned(MY_FDT).unwrap();
//!     let root = fdt.root();
//!
//!     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",
//!         root.memory().reg().iter::<u64, u64>().next().unwrap().unwrap().address
//!     );
//!
//!     let chosen = root.chosen();
//!     if let Some(bootargs) = chosen.bootargs() {
//!         println!("The bootargs are: {:?}", bootargs);
//!     }
//!
//!     if let Some(stdout) = chosen.stdout_path() {
//!         println!("It would write stdout to: {}", stdout.path());
//!     }
//!
//!     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().iter() {
//!             println!("    {}", child.name());
//!         }
//!     }
//! }
//! ```

#![no_std]
#![warn(missing_docs)]

#[cfg(test)]
extern crate std;

#[cfg(test)]
mod tests;

/// Trait and types for working with `*-cells` values.
pub mod cell_collector;
/// Helper type aliases.
pub mod helpers;
/// Devicetree node abstractions.
pub mod nodes;
/// Traits, types, and helpers for parsing flattened devicetrees. The helper types are not meant
/// to be used by end users but need to be public. If you have a need to create
/// a custom parser or other behavior, please open an issue.
pub mod parsing;
mod pretty_print;
/// 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)]
pub enum FdtError {
    /// The flattened devicetree had an invalid magic value
    BadMagic,
    /// The given pointer was null
    BadPtr,
    /// 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<ParseError> for FdtError {
    fn from(value: ParseError) -> Self {
        Self::ParseError(value)
    }
}

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::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 `#<specifier>-cells` size values into the desired type")
            }
        }
    }
}

/// 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, P: ParserWithMode<'a>> {
    structs: StructsBlock<'a, P::Granularity>,
    strings: StringsBlock<'a>,
    header: FdtHeader,
}

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 {
        let mut parser: (P::Parser, NoPanic) = <_>::new(self.structs.0, self.strings, self.structs);

        let Ok(node) = parser.parse_root() else {
            return Err(core::fmt::Error);
        };

        pretty_print::print_fdt(f, Root { node })
    }
}

/// FDT header.
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct FdtHeader {
    /// Flattened devicetree header magic
    pub magic: u32,
    /// Total size in bytes of the flattened devicetree structure
    pub total_size: u32,
    /// Offset in bytes from the start of the header to the structure block
    pub structs_offset: u32,
    /// Offset in bytes from the start of the header to the strings block
    pub strings_offset: u32,
    /// Offset in bytes from the start of the header to the memory reservation
    /// block
    pub memory_reserve_map_offset: u32,
    /// Flattened devicetree version
    pub version: u32,
    /// Last compatible flattened devicetree version
    pub last_compatible_version: u32,
    /// System boot CPU ID
    pub boot_cpuid: u32,
    /// Length in bytes of the strings block
    pub strings_size: u32,
    /// Length in bytes of the struct block
    pub structs_size: u32,
}

impl FdtHeader {
    fn valid_magic(&self) -> bool {
        self.magic == 0xd00dfeed
    }
}

impl<'a> Fdt<'a, (UnalignedParser<'a>, Panic)> {
    /// Construct a new `Fdt` from a byte buffer
    pub fn new_unaligned(data: &'a [u8]) -> Result<Self, FdtError> {
        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.total_size as usize {
            return Err(FdtError::SliceTooSmall);
        }

        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.
    pub unsafe fn from_ptr_unaligned(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 = usize::try_from(
            UnalignedParser::new(tmp_header, StringsBlock(&[]), StructsBlock(&[])).parse_header()?.total_size,
        )
        .map_err(|_| ParseError::NumericConversionError)?;

        Self::new_unaligned(core::slice::from_raw_parts(ptr, real_size))
    }
}

impl<'a> Fdt<'a, (AlignedParser<'a>, Panic)> {
    /// Construct a new `Fdt` from a `u32`-aligned buffer
    pub fn new(data: &'a [u32]) -> Result<Self, FdtError> {
        let mut parser = AlignedParser::new(data, StringsBlock(&[]), StructsBlock(&[]));
        let header = parser.parse_header()?;

        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);
        }

        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))?,
        );

        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))?,
        );

        if !header.valid_magic() {
            return Err(FdtError::BadMagic);
        } else if data.len() < (header.total_size / 4) as usize {
            return Err(FdtError::ParseError(ParseError::UnexpectedEndOfData));
        }

        Ok(Self { header, strings, structs })
    }

    /// # 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<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 = usize::try_from(
            AlignedParser::new(tmp_header, StringsBlock(&[]), StructsBlock(&[])).parse_header()?.total_size,
        )
        .map_err(|_| ParseError::NumericConversionError)?;

        Self::new(core::slice::from_raw_parts(ptr, real_size))
    }
}

impl<'a> Fdt<'a, (UnalignedParser<'a>, NoPanic)> {
    /// Construct a new `Fdt` from a byte buffer
    pub fn new_unaligned_fallible(data: &'a [u8]) -> Result<Self, FdtError> {
        let Fdt { header, strings, structs } = Fdt::new_unaligned(data)?;
        Ok(Self { header, strings, structs })
    }

    /// # 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<Self, FdtError> {
        let Fdt { header, strings, structs } = Fdt::from_ptr_unaligned(ptr)?;
        Ok(Self { header, strings, structs })
    }
}

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<Self, FdtError> {
        let Fdt { header, strings, structs } = Fdt::new(data)?;
        Ok(Self { header, strings, structs })
    }

    /// # 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<Self, FdtError> {
        let Fdt { header, strings, structs } = Fdt::from_ptr(ptr)?;
        Ok(Self { header, strings, structs })
    }
}

impl<'a, P: ParserWithMode<'a>> Fdt<'a, P> {
    #[inline(always)]
    fn fallible_root(&self) -> Result<Root<'a, FallibleParser<'a, P>>, FdtError> {
        let mut parser = FallibleParser::<'a, P>::new(self.structs.0, self.strings, self.structs);
        Ok(Root { node: parser.parse_root()? })
    }

    /// Return the root (`/`) node, which is always available
    pub fn root(&self) -> P::Output<Root<'a, P>> {
        let mut parser = P::new(self.structs.0, self.strings, self.structs);
        P::to_output(parser.parse_root().map(|node| Root { node: node.fallible() }))
    }

    /// 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 = core::ffi::CStr::from_bytes_until_nul(block).ok()?;

            block = &block[cstr.to_bytes().len() + 1..];

            cstr.to_str().ok()
        })
    }

    /// 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<AllNodesWithNameIter<'a, 'b, P>> {
        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 })
        }))
    }

    /// 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<Option<Node<'a, P>>> {
        P::to_output(self.fallible_root().and_then(|root| Ok(root.find_node_by_name(name)?.map(|n| n.alt()))))
    }

    /// 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<Option<Node<'a, P>>> {
        P::to_output(self.fallible_root().and_then(|root| Ok(root.find_node(path)?.map(|n| n.alt()))))
    }

    /// 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<AllCompatibleIter<'a, 'b, P>> {
        P::to_output(
            self.fallible_root()
                .and_then(|root| root.all_compatible(with).map(|i| AllCompatibleIter { iter: i.iter, with: i.with })),
        )
    }

    /// 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<AllNodesIter<'a, P>> {
        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.total_size as usize
    }

    /// Header describing this devicetree.
    pub fn header(&self) -> &FdtHeader {
        &self.header
    }

    /// Slice pointing to the raw strings block.
    pub fn strings_block(&self) -> &'a [u8] {
        self.strings.0
    }

    /// Slice pointing to the raw structs block.
    pub fn structs_block(&self) -> &'a [P::Granularity] {
        self.structs.0
    }
}