Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ keywords = ["device-tree", "fdt", "dtb", "dts", "parser", "no_std"]
default = []
alloc = []
std = ["alloc"]
write = ["alloc", "dep:indexmap", "dep:twox-hash"]

[dependencies]
indexmap = { version = "2", optional = true, default-features = false }
twox-hash = { version = "2", optional = true, features = ["xxhash64"], default-features = false }
zerocopy = { version = "0.8.28", features = ["derive"] }

[lints.rust]
Expand Down
6 changes: 5 additions & 1 deletion src/fdt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,11 @@ impl<'a> Fdt<'a> {
/// # Performance
///
/// This method traverses the device tree and its performance is linear in
/// the number of nodes in the path.
/// the number of nodes in the path. If you need to call this often,
/// consider using
/// [`DeviceTree::from_fdt`](crate::model::DeviceTree::from_fdt)
/// first. [`DeviceTree`](crate::model::DeviceTree) stores the nodes in a
/// hash map for constant-time lookup.
///
/// # Examples
///
Expand Down
6 changes: 5 additions & 1 deletion src/fdt/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,11 @@ impl<'a> FdtNode<'a> {
/// # Performance
///
/// This method's performance is linear in the number of children of this
/// node because it iterates through the children.
/// node because it iterates through the children. If you need to call this
/// often, consider converting to a
/// [`DeviceTreeNode`](crate::model::DeviceTreeNode) first. Child lookup
/// on a [`DeviceTreeNode`](crate::model::DeviceTreeNode) is a
/// constant-time operation.
///
/// # Errors
///
Expand Down
5 changes: 5 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@
#![warn(missing_docs, rustdoc::missing_crate_level_docs)]
#![cfg_attr(docsrs, feature(doc_cfg))]

#[cfg(feature = "alloc")]
extern crate alloc;

pub mod error;
pub mod fdt;
pub mod memreserve;
#[cfg(feature = "write")]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks enabling the alloc feature without write has no meaningful effect, why not just have a single feature?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought we might eventually have some functionality that will require the alloc crate, but won't be used for the intermediate DT representation (which requires its own set of additional libraries). For instance, in-place FDT modification might optionally require alloc crate to support extending the FDT size to relocate nodes when adding new data - but this is indeed too forward-looking and we can just re-introduce the feature flag then.

pub mod model;
132 changes: 132 additions & 0 deletions src/model/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! A read-write, in-memory representation of a device tree.
//!
//! This module provides the [`DeviceTree`], [`DeviceTreeNode`], and
//! [`DeviceTreeProperty`] structs, which can be used to create or modify a
//! device tree in memory. The [`DeviceTree`] can then be serialized to a
//! flattened device tree blob.
use alloc::vec::Vec;

use crate::error::FdtError;
use crate::fdt::Fdt;
use crate::memreserve::MemoryReservation;
mod node;
mod property;
pub use node::{DeviceTreeNode, DeviceTreeNodeBuilder};
pub use property::DeviceTreeProperty;

/// A mutable, in-memory representation of a device tree.
///
/// This struct provides a high-level API for creating and modifying a device
/// tree. It can be created from scratch or by parsing an existing FDT blob.
///
/// # Examples
///
/// ```
/// # use dtoolkit::model::{DeviceTree, DeviceTreeNode};
/// let root = DeviceTreeNode::new("/");
/// let mut tree = DeviceTree::new(root);
/// tree.root_mut().add_child(DeviceTreeNode::new("child"));
/// let child = tree.find_node_mut("/child").unwrap();
/// ```
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DeviceTree {
pub(self) root: DeviceTreeNode,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't pub(self) the same as omitting pub entirely?

Copy link
Collaborator Author

@m4tx m4tx Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, for some reason I thought that this makes the property visible in the child modules (which isn't even utilized in this PR, but at some point was in the next one), but indeed I was wrong. I'll just make this pub and remove the getter/setter methods (and add #[non_exhaustive] to the struct to avoid API breakages when we add new stuff here).

/// The memory reservations for this device tree.
pub memory_reservations: Vec<MemoryReservation>,
}

impl DeviceTree {
/// Creates a new `DeviceTree` with the given root node.
///
/// # Examples
///
/// ```
/// # use dtoolkit::model::{DeviceTree, DeviceTreeNode};
/// let root = DeviceTreeNode::new("/");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if someone passes a node with a different name as the root? If there's no reason to do that, why not just construct the appropriate DeviceTreeNode within this new method?

Copy link
Collaborator Author

@m4tx m4tx Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, the device tree spec says:

The devicetree has a single root node of which all other device nodes are descendants. The full path to the root node is /.

I'll change this to create an appropriate root node here then. This isn't ideal, because the user can still do tree.root = DeviceTreeNode::new("foobar"), but I guess that's fine.

/// let tree = DeviceTree::new(root);
/// ```
#[must_use]
pub fn new(root: DeviceTreeNode) -> Self {
Self {
root,
memory_reservations: Vec::new(),
}
}

/// Creates a new `DeviceTree` from a `Fdt`.
///
/// # Examples
///
/// ```
/// # use dtoolkit::{fdt::Fdt, model::DeviceTree};
/// # let dtb = include_bytes!("../../tests/dtb/test.dtb");
/// let fdt = Fdt::new(dtb).unwrap();
/// let tree = DeviceTree::from_fdt(&fdt).unwrap();
/// ```
///
/// # Errors
///
/// Returns an error if the root node of the `Fdt` cannot be parsed.
pub fn from_fdt(fdt: &Fdt<'_>) -> Result<Self, FdtError> {
let root = DeviceTreeNode::try_from(fdt.root()?)?;
let memory_reservations: Result<Vec<_>, _> = fdt.memory_reservations().collect();
Ok(DeviceTree {
root,
memory_reservations: memory_reservations?,
})
}

/// Returns a reference to the root node of the device tree.
#[must_use]
pub fn root(&self) -> &DeviceTreeNode {
&self.root
}

/// Returns a mutable reference to the root node of the device tree.
pub fn root_mut(&mut self) -> &mut DeviceTreeNode {
&mut self.root
}

/// Finds a node by its path and returns a mutable reference to it.
///
/// # Performance
///
/// This method traverses the device tree, but since child lookup is a
/// constant-time operation, performance is linear in the number of path
/// segments.
///
/// # Examples
///
/// ```
/// # use dtoolkit::model::{DeviceTree, DeviceTreeNode};
/// let mut tree = DeviceTree::new(DeviceTreeNode::new("/"));
/// tree.root_mut().add_child(DeviceTreeNode::new("child"));
/// let child = tree.find_node_mut("/child").unwrap();
/// assert_eq!(child.name(), "child");
/// ```
pub fn find_node_mut(&mut self, path: &str) -> Option<&mut DeviceTreeNode> {
if !path.starts_with('/') {
return None;
}
let mut current_node = &mut self.root;
if path == "/" {
return Some(current_node);
}
for component in path.split('/').filter(|s| !s.is_empty()) {
match current_node.child_mut(component) {
Some(node) => current_node = node,
None => return None,
}
}
Some(current_node)
}
}
Loading