Merge "libfdt: Introduce Libfdt traits & Move node funcs" into main
diff --git a/libs/libfdt/src/lib.rs b/libs/libfdt/src/lib.rs
index 72cd3e9..ce2a9f6 100644
--- a/libs/libfdt/src/lib.rs
+++ b/libs/libfdt/src/lib.rs
@@ -29,11 +29,12 @@
 
 use core::ffi::{c_int, c_void, CStr};
 use core::ops::Range;
-use core::ptr;
 use cstr::cstr;
 use result::{fdt_err, fdt_err_expect_zero, fdt_err_or_option};
 use zerocopy::AsBytes as _;
 
+use crate::libfdt::{Libfdt, LibfdtMut};
+
 /// Value of a #address-cells property.
 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
 enum AddrCells {
@@ -164,25 +165,14 @@
 impl<'a> FdtNode<'a> {
     /// Returns parent node.
     pub fn parent(&self) -> Result<Self> {
-        // SAFETY: Accesses (read-only) are constrained to the DT totalsize.
-        let ret = unsafe { libfdt_bindgen::fdt_parent_offset(self.fdt.as_ptr(), self.offset) };
-        let offset = fdt_err(ret)?;
+        let offset = self.fdt.parent_offset(self.offset)?;
 
         Ok(Self { fdt: self.fdt, offset })
     }
 
     /// Returns supernode with depth. Note that root is at depth 0.
     pub fn supernode_at_depth(&self, depth: usize) -> Result<Self> {
-        // SAFETY: Accesses (read-only) are constrained to the DT totalsize.
-        let ret = unsafe {
-            libfdt_bindgen::fdt_supernode_atdepth_offset(
-                self.fdt.as_ptr(),
-                self.offset,
-                depth.try_into().unwrap(),
-                ptr::null_mut(),
-            )
-        };
-        let offset = fdt_err(ret)?;
+        let offset = self.fdt.supernode_atdepth_offset(self.offset, depth)?;
 
         Ok(Self { fdt: self.fdt, offset })
     }
@@ -321,15 +311,7 @@
 
     /// Returns the compatible node of the given name that is next after this node.
     pub fn next_compatible(self, compatible: &CStr) -> Result<Option<Self>> {
-        // SAFETY: Accesses (read-only) are constrained to the DT totalsize.
-        let ret = unsafe {
-            libfdt_bindgen::fdt_node_offset_by_compatible(
-                self.fdt.as_ptr(),
-                self.offset,
-                compatible.as_ptr(),
-            )
-        };
-        let offset = fdt_err_or_option(ret)?;
+        let offset = self.fdt.node_offset_by_compatible(self.offset, compatible)?;
 
         Ok(offset.map(|offset| Self { fdt: self.fdt, offset }))
     }
@@ -340,17 +322,11 @@
     }
 
     fn address_cells(&self) -> Result<AddrCells> {
-        // SAFETY: Accesses are constrained to the DT totalsize (validated by ctor).
-        let ret = unsafe { libfdt_bindgen::fdt_address_cells(self.fdt.as_ptr(), self.offset) };
-
-        usize::try_from(ret).map_err(|_| FdtError::Internal)?.try_into()
+        self.fdt.address_cells(self.offset)?.try_into()
     }
 
     fn size_cells(&self) -> Result<SizeCells> {
-        // SAFETY: Accesses are constrained to the DT totalsize (validated by ctor).
-        let ret = unsafe { libfdt_bindgen::fdt_size_cells(self.fdt.as_ptr(), self.offset) };
-
-        usize::try_from(ret).map_err(|_| FdtError::Internal)?.try_into()
+        self.fdt.size_cells(self.offset)?.try_into()
     }
 
     /// Returns an iterator of subnodes
@@ -359,17 +335,13 @@
     }
 
     fn first_subnode(&self) -> Result<Option<Self>> {
-        // SAFETY: Accesses (read-only) are constrained to the DT totalsize.
-        let ret = unsafe { libfdt_bindgen::fdt_first_subnode(self.fdt.as_ptr(), self.offset) };
-        let offset = fdt_err_or_option(ret)?;
+        let offset = self.fdt.first_subnode(self.offset)?;
 
         Ok(offset.map(|offset| Self { fdt: self.fdt, offset }))
     }
 
     fn next_subnode(&self) -> Result<Option<Self>> {
-        // SAFETY: Accesses (read-only) are constrained to the DT totalsize.
-        let ret = unsafe { libfdt_bindgen::fdt_next_subnode(self.fdt.as_ptr(), self.offset) };
-        let offset = fdt_err_or_option(ret)?;
+        let offset = self.fdt.next_subnode(self.offset)?;
 
         Ok(offset.map(|offset| Self { fdt: self.fdt, offset }))
     }
@@ -380,15 +352,7 @@
     }
 
     fn next_node(&self, depth: usize) -> Result<Option<(Self, usize)>> {
-        let mut next_depth: c_int = depth.try_into().unwrap();
-        // SAFETY: Accesses (read-only) are constrained to the DT totalsize.
-        let ret = unsafe {
-            libfdt_bindgen::fdt_next_node(self.fdt.as_ptr(), self.offset, &mut next_depth)
-        };
-        let Ok(depth) = usize::try_from(next_depth) else {
-            return Ok(None);
-        };
-        if let Some(offset) = fdt_err_or_option(ret)? {
+        if let Some((offset, depth)) = self.fdt.next_node(self.offset, depth)? {
             Ok(Some((Self { fdt: self.fdt, offset }, depth)))
         } else {
             Ok(None)
@@ -427,31 +391,17 @@
     /// Returns the subnode of the given name. The name doesn't need to be nul-terminated.
     pub fn subnode(&self, name: &CStr) -> Result<Option<Self>> {
         let name = name.to_bytes();
-        let offset = self.subnode_offset(name)?;
+        let offset = self.fdt.subnode_offset_namelen(self.offset, name)?;
 
         Ok(offset.map(|offset| Self { fdt: self.fdt, offset }))
     }
 
     /// Returns the subnode of the given name bytes
     pub fn subnode_with_name_bytes(&self, name: &[u8]) -> Result<Option<Self>> {
-        let offset = self.subnode_offset(name)?;
+        let offset = self.fdt.subnode_offset_namelen(self.offset, name)?;
 
         Ok(offset.map(|offset| Self { fdt: self.fdt, offset }))
     }
-
-    fn subnode_offset(&self, name: &[u8]) -> Result<Option<c_int>> {
-        let namelen = name.len().try_into().unwrap();
-        // SAFETY: Accesses are constrained to the DT totalsize (validated by ctor).
-        let ret = unsafe {
-            libfdt_bindgen::fdt_subnode_offset_namelen(
-                self.fdt.as_ptr(),
-                self.offset,
-                name.as_ptr().cast::<_>(),
-                namelen,
-            )
-        };
-        fdt_err_or_option(ret)
-    }
 }
 
 impl<'a> PartialEq for FdtNode<'a> {
@@ -652,7 +602,7 @@
     /// Adds new subnodes to the given node.
     pub fn add_subnodes(&mut self, names: &[&CStr]) -> Result<()> {
         for name in names {
-            self.add_subnode_offset(name.to_bytes())?;
+            self.fdt.add_subnode_namelen(self.offset, name.to_bytes())?;
         }
         Ok(())
     }
@@ -660,7 +610,7 @@
     /// Adds a new subnode to the given node and return it as a FdtNodeMut on success.
     pub fn add_subnode(&'a mut self, name: &CStr) -> Result<Self> {
         let name = name.to_bytes();
-        let offset = self.add_subnode_offset(name)?;
+        let offset = self.fdt.add_subnode_namelen(self.offset, name)?;
 
         Ok(Self { fdt: self.fdt, offset })
     }
@@ -669,80 +619,46 @@
     /// on success.
     pub fn add_subnode_with_namelen(&'a mut self, name: &CStr, namelen: usize) -> Result<Self> {
         let name = &name.to_bytes()[..namelen];
-        let offset = self.add_subnode_offset(name)?;
+        let offset = self.fdt.add_subnode_namelen(self.offset, name)?;
 
         Ok(Self { fdt: self.fdt, offset })
     }
 
-    fn add_subnode_offset(&mut self, name: &[u8]) -> Result<c_int> {
-        let namelen = name.len().try_into().unwrap();
-        // SAFETY: Accesses are constrained to the DT totalsize (validated by ctor).
-        let ret = unsafe {
-            libfdt_bindgen::fdt_add_subnode_namelen(
-                self.fdt.as_mut_ptr(),
-                self.offset,
-                name.as_ptr().cast::<_>(),
-                namelen,
-            )
-        };
-        fdt_err(ret)
-    }
-
     /// Returns the first subnode of this
     pub fn first_subnode(&'a mut self) -> Result<Option<Self>> {
-        // SAFETY: Accesses (read-only) are constrained to the DT totalsize.
-        let ret = unsafe { libfdt_bindgen::fdt_first_subnode(self.fdt.as_ptr(), self.offset) };
-        let offset = fdt_err_or_option(ret)?;
+        let offset = self.fdt.first_subnode(self.offset)?;
 
         Ok(offset.map(|offset| Self { fdt: self.fdt, offset }))
     }
 
     /// Returns the next subnode that shares the same parent with this
     pub fn next_subnode(self) -> Result<Option<Self>> {
-        // SAFETY: Accesses (read-only) are constrained to the DT totalsize.
-        let ret = unsafe { libfdt_bindgen::fdt_next_subnode(self.fdt.as_ptr(), self.offset) };
-        let offset = fdt_err_or_option(ret)?;
+        let offset = self.fdt.next_subnode(self.offset)?;
 
         Ok(offset.map(|offset| Self { fdt: self.fdt, offset }))
     }
 
     /// Deletes the current node and returns the next subnode
     pub fn delete_and_next_subnode(self) -> Result<Option<Self>> {
-        // SAFETY: Accesses (read-only) are constrained to the DT totalsize.
-        let ret = unsafe { libfdt_bindgen::fdt_next_subnode(self.fdt.as_ptr(), self.offset) };
-
-        let next_offset = fdt_err_or_option(ret)?;
+        let next_offset = self.fdt.next_subnode(self.offset)?;
 
         self.delete_and_next(next_offset)
     }
 
-    fn next_node_offset(&self, depth: usize) -> Result<Option<(c_int, usize)>> {
-        let mut next_depth: c_int = depth.try_into().or(Err(FdtError::BadValue))?;
-        // SAFETY: Accesses (read-only) are constrained to the DT totalsize.
-        let ret = unsafe {
-            libfdt_bindgen::fdt_next_node(self.fdt.as_ptr(), self.offset, &mut next_depth)
-        };
-        let Ok(next_depth) = usize::try_from(next_depth) else {
-            return Ok(None);
-        };
-        Ok(fdt_err_or_option(ret)?.map(|offset| (offset, next_depth)))
-    }
-
     /// Returns the next node
     pub fn next_node(self, depth: usize) -> Result<Option<(Self, usize)>> {
-        let next = self.next_node_offset(depth)?;
+        let next = self.fdt.next_node(self.offset, depth)?;
 
         Ok(next.map(|(offset, depth)| (Self { fdt: self.fdt, offset }, depth)))
     }
 
     fn next_node_skip_subnodes(&mut self, depth: usize) -> Result<Option<(c_int, usize)>> {
-        let mut iter = self.next_node_offset(depth)?;
+        let mut iter = self.fdt.next_node(self.offset, depth)?;
         while let Some((descendant_offset, descendant_depth)) = iter {
             if descendant_depth <= depth {
                 return Ok(Some((descendant_offset, descendant_depth)));
             }
-            let descendant = FdtNodeMut { fdt: self.fdt, offset: descendant_offset };
-            iter = descendant.next_node_offset(descendant_depth)?;
+            iter = self.fdt.next_node(descendant_offset, descendant_depth)?;
         }
 
         Ok(None)
@@ -765,15 +681,7 @@
 
     /// Returns the compatible node of the given name that is next after this node.
     pub fn next_compatible(self, compatible: &CStr) -> Result<Option<Self>> {
-        // SAFETY: Accesses (read-only) are constrained to the DT totalsize.
-        let ret = unsafe {
-            libfdt_bindgen::fdt_node_offset_by_compatible(
-                self.fdt.as_ptr(),
-                self.offset,
-                compatible.as_ptr(),
-            )
-        };
-        let offset = fdt_err_or_option(ret)?;
+        let offset = self.fdt.node_offset_by_compatible(self.offset, compatible)?;
 
         Ok(offset.map(|offset| Self { fdt: self.fdt, offset }))
     }
@@ -791,46 +699,24 @@
     // mutable reference to DT, so we can't use current node (which also has a mutable reference to
     // DT).
     pub fn delete_and_next_compatible(self, compatible: &CStr) -> Result<Option<Self>> {
-        // SAFETY: Accesses (read-only) are constrained to the DT totalsize.
-        let ret = unsafe {
-            libfdt_bindgen::fdt_node_offset_by_compatible(
-                self.fdt.as_ptr(),
-                self.offset,
-                compatible.as_ptr(),
-            )
-        };
-        let next_offset = fdt_err_or_option(ret)?;
+        let next_offset = self.fdt.node_offset_by_compatible(self.offset, compatible)?;
 
         self.delete_and_next(next_offset)
     }
 
-    fn delete_and_next(mut self, next_offset: Option<c_int>) -> Result<Option<Self>> {
+    fn delete_and_next(self, next_offset: Option<c_int>) -> Result<Option<Self>> {
         if Some(self.offset) == next_offset {
             return Err(FdtError::Internal);
         }
 
-        // SAFETY: nop_self() only touches bytes of the self and its properties and subnodes, and
-        // doesn't alter any other blob in the tree. self.fdt and next_offset would remain valid.
-        unsafe { self.nop_self()? };
+        self.fdt.nop_node(self.offset)?;
 
         Ok(next_offset.map(|offset| Self { fdt: self.fdt, offset }))
     }
 
     /// Deletes this node effectively from DT, by setting it with FDT_NOP
-    pub fn nop(mut self) -> Result<()> {
-        // SAFETY: This consumes self, so invalid node wouldn't be used any further
-        unsafe { self.nop_self() }
-    }
-
-    /// Deletes this node effectively from DT, by setting it with FDT_NOP.
-    /// This only changes bytes of the node and its properties and subnodes, and doesn't alter or
-    /// move any other part of the tree.
-    /// SAFETY: This node is no longer valid.
-    unsafe fn nop_self(&mut self) -> Result<()> {
-        // SAFETY: Accesses are constrained to the DT totalsize (validated by ctor).
-        let ret = unsafe { libfdt_bindgen::fdt_nop_node(self.fdt.as_mut_ptr(), self.offset) };
-
-        fdt_err_expect_zero(ret)
+    pub fn nop(self) -> Result<()> {
+        self.fdt.nop_node(self.offset)
     }
 }
 
@@ -841,6 +727,22 @@
     buffer: [u8],
 }
 
+// SAFETY: Fdt calls check_full() before safely returning a &Self, making it impossible for trait
+// methods to be called on invalid device trees.
+unsafe impl Libfdt for Fdt {
+    fn as_fdt_slice(&self) -> &[u8] {
+        &self.buffer[..self.totalsize()]
+    }
+}
+
+// SAFETY: Fdt calls check_full() before safely returning a &Self, making it impossible for trait
+// methods to be called on invalid device trees.
+unsafe impl LibfdtMut for Fdt {
+    fn as_fdt_slice_mut(&mut self) -> &mut [u8] {
+        &mut self.buffer
+    }
+}
+
 impl Fdt {
     /// Wraps a slice containing a Flattened Device Tree.
     ///
@@ -994,7 +896,7 @@
 
     /// Returns a tree node by its full path.
     pub fn node(&self, path: &CStr) -> Result<Option<FdtNode>> {
-        let offset = self.path_offset(path.to_bytes())?;
+        let offset = self.path_offset_namelen(path.to_bytes())?;
 
         Ok(offset.map(|offset| FdtNode { fdt: self, offset }))
     }
@@ -1039,26 +941,14 @@
 
     /// Returns a mutable tree node by its full path.
     pub fn node_mut(&mut self, path: &CStr) -> Result<Option<FdtNodeMut>> {
-        let offset = self.path_offset(path.to_bytes())?;
+        let offset = self.path_offset_namelen(path.to_bytes())?;
 
         Ok(offset.map(|offset| FdtNodeMut { fdt: self, offset }))
     }
 
     /// Returns the device tree as a slice (may be smaller than the containing buffer).
     pub fn as_slice(&self) -> &[u8] {
-        &self.buffer[..self.totalsize()]
-    }
-
-    fn path_offset(&self, path: &[u8]) -> Result<Option<c_int>> {
-        let len = path.len().try_into().map_err(|_| FdtError::BadPath)?;
-        // SAFETY: Accesses are constrained to the DT totalsize (validated by ctor) and the
-        // function respects the passed number of characters.
-        let ret = unsafe {
-            // *_namelen functions don't include the trailing nul terminator in 'len'.
-            libfdt_bindgen::fdt_path_offset_namelen(self.as_ptr(), path.as_ptr().cast::<_>(), len)
-        };
-
-        fdt_err_or_option(ret)
+        self.as_fdt_slice()
     }
 
     fn get_from_ptr(&self, ptr: *const c_void, len: usize) -> Result<&[u8]> {
diff --git a/libs/libfdt/src/libfdt.rs b/libs/libfdt/src/libfdt.rs
index bd9ae1e..7e3b65a 100644
--- a/libs/libfdt/src/libfdt.rs
+++ b/libs/libfdt/src/libfdt.rs
@@ -13,8 +13,15 @@
 // limitations under the License.
 
 //! Low-level libfdt_bindgen wrapper, easy to integrate safely in higher-level APIs.
+//!
+//! These traits decouple the safe libfdt C function calls from the representation of those
+//! user-friendly higher-level types, allowing the trait to be shared between different ones,
+//! adapted to their use-cases (e.g. alloc-based userspace or statically allocated no_std).
 
-use crate::{fdt_err_expect_zero, Result};
+use core::ffi::{c_int, CStr};
+use core::ptr;
+
+use crate::{fdt_err, fdt_err_expect_zero, fdt_err_or_option, FdtError, Result};
 
 // Function names are the C function names without the `fdt_` prefix.
 
@@ -43,3 +50,170 @@
 
     fdt_err_expect_zero(ret)
 }
+
+/// Wrapper for the read-only libfdt.h functions.
+///
+/// # Safety
+///
+/// Implementors must ensure that at any point where a method of this trait is called, the
+/// underlying type returns the bytes of a valid device tree (as validated by `check_full`)
+/// through its `.as_fdt_slice` method.
+pub(crate) unsafe trait Libfdt {
+    /// Provides an immutable slice containing the device tree.
+    ///
+    /// The implementation must ensure that the size of the returned slice and
+    /// `fdt_header::totalsize` match.
+    fn as_fdt_slice(&self) -> &[u8];
+
+    /// Safe wrapper around `fdt_path_offset_namelen()` (C function).
+    fn path_offset_namelen(&self, path: &[u8]) -> Result<Option<c_int>> {
+        let fdt = self.as_fdt_slice().as_ptr().cast();
+        // *_namelen functions don't include the trailing nul terminator in 'len'.
+        let len = path.len().try_into().map_err(|_| FdtError::BadPath)?;
+        let path = path.as_ptr().cast();
+        // SAFETY: Accesses are constrained to the DT totalsize (validated by ctor) and the
+        // function respects the passed number of characters.
+        let ret = unsafe { libfdt_bindgen::fdt_path_offset_namelen(fdt, path, len) };
+
+        fdt_err_or_option(ret)
+    }
+
+    /// Safe wrapper around `fdt_node_offset_by_compatible()` (C function).
+    fn node_offset_by_compatible(&self, prev: c_int, compatible: &CStr) -> Result<Option<c_int>> {
+        let fdt = self.as_fdt_slice().as_ptr().cast();
+        let compatible = compatible.as_ptr();
+        // SAFETY: Accesses (read-only) are constrained to the DT totalsize.
+        let ret = unsafe { libfdt_bindgen::fdt_node_offset_by_compatible(fdt, prev, compatible) };
+
+        fdt_err_or_option(ret)
+    }
+
+    /// Safe wrapper around `fdt_next_node()` (C function).
+    fn next_node(&self, node: c_int, depth: usize) -> Result<Option<(c_int, usize)>> {
+        let fdt = self.as_fdt_slice().as_ptr().cast();
+        let mut depth = depth.try_into().unwrap();
+        // SAFETY: Accesses (read-only) are constrained to the DT totalsize.
+        let ret = unsafe { libfdt_bindgen::fdt_next_node(fdt, node, &mut depth) };
+
+        match fdt_err_or_option(ret)? {
+            Some(offset) if depth >= 0 => {
+                let depth = depth.try_into().unwrap();
+                Ok(Some((offset, depth)))
+            }
+            _ => Ok(None),
+        }
+    }
+
+    /// Safe wrapper around `fdt_parent_offset()` (C function).
+    ///
+    /// Note that this function returns a `Err` when called on a root.
+    fn parent_offset(&self, node: c_int) -> Result<c_int> {
+        let fdt = self.as_fdt_slice().as_ptr().cast();
+        // SAFETY: Accesses (read-only) are constrained to the DT totalsize.
+        let ret = unsafe { libfdt_bindgen::fdt_parent_offset(fdt, node) };
+
+        fdt_err(ret)
+    }
+
+    /// Safe wrapper around `fdt_supernode_atdepth_offset()` (C function).
+    ///
+    /// Note that this function returns a `Err` when called on a node at a depth shallower than
+    /// the provided `depth`.
+    fn supernode_atdepth_offset(&self, node: c_int, depth: usize) -> Result<c_int> {
+        let fdt = self.as_fdt_slice().as_ptr().cast();
+        let depth = depth.try_into().unwrap();
+        let nodedepth = ptr::null_mut();
+        let ret =
+            // SAFETY: Accesses (read-only) are constrained to the DT totalsize.
+            unsafe { libfdt_bindgen::fdt_supernode_atdepth_offset(fdt, node, depth, nodedepth) };
+
+        fdt_err(ret)
+    }
+
+    /// Safe wrapper around `fdt_subnode_offset_namelen()` (C function).
+    fn subnode_offset_namelen(&self, parent: c_int, name: &[u8]) -> Result<Option<c_int>> {
+        let fdt = self.as_fdt_slice().as_ptr().cast();
+        let namelen = name.len().try_into().unwrap();
+        let name = name.as_ptr().cast();
+        // SAFETY: Accesses are constrained to the DT totalsize (validated by ctor).
+        let ret = unsafe { libfdt_bindgen::fdt_subnode_offset_namelen(fdt, parent, name, namelen) };
+
+        fdt_err_or_option(ret)
+    }
+    /// Safe wrapper around `fdt_first_subnode()` (C function).
+    fn first_subnode(&self, node: c_int) -> Result<Option<c_int>> {
+        let fdt = self.as_fdt_slice().as_ptr().cast();
+        // SAFETY: Accesses (read-only) are constrained to the DT totalsize.
+        let ret = unsafe { libfdt_bindgen::fdt_first_subnode(fdt, node) };
+
+        fdt_err_or_option(ret)
+    }
+
+    /// Safe wrapper around `fdt_next_subnode()` (C function).
+    fn next_subnode(&self, node: c_int) -> Result<Option<c_int>> {
+        let fdt = self.as_fdt_slice().as_ptr().cast();
+        // SAFETY: Accesses (read-only) are constrained to the DT totalsize.
+        let ret = unsafe { libfdt_bindgen::fdt_next_subnode(fdt, node) };
+
+        fdt_err_or_option(ret)
+    }
+
+    /// Safe wrapper around `fdt_address_cells()` (C function).
+    fn address_cells(&self, node: c_int) -> Result<usize> {
+        let fdt = self.as_fdt_slice().as_ptr().cast();
+        // SAFETY: Accesses are constrained to the DT totalsize (validated by ctor).
+        let ret = unsafe { libfdt_bindgen::fdt_address_cells(fdt, node) };
+
+        Ok(fdt_err(ret)?.try_into().unwrap())
+    }
+
+    /// Safe wrapper around `fdt_size_cells()` (C function).
+    fn size_cells(&self, node: c_int) -> Result<usize> {
+        let fdt = self.as_fdt_slice().as_ptr().cast();
+        // SAFETY: Accesses are constrained to the DT totalsize (validated by ctor).
+        let ret = unsafe { libfdt_bindgen::fdt_size_cells(fdt, node) };
+
+        Ok(fdt_err(ret)?.try_into().unwrap())
+    }
+}
+
+/// Wrapper for the read-write libfdt.h functions.
+///
+/// # Safety
+///
+/// Implementors must ensure that at any point where a method of this trait is called, the
+/// underlying type returns the bytes of a valid device tree (as validated by `check_full`)
+/// through its `.as_fdt_slice_mut` method.
+///
+/// Some methods may make previously returned values such as node or string offsets or phandles
+/// invalid by modifying the device tree (e.g. by inserting or removing new nodes or properties).
+/// As most methods take or return such values, instead of marking them all as unsafe, this trait
+/// is marked as unsafe as implementors must ensure that methods that modify the validity of those
+/// values are never called while the values are still in use.
+pub(crate) unsafe trait LibfdtMut {
+    /// Provides a mutable pointer to a buffer containing the device tree.
+    ///
+    /// The implementation must ensure that the size of the returned slice is at least
+    /// `fdt_header::totalsize`, to allow for device tree growth.
+    fn as_fdt_slice_mut(&mut self) -> &mut [u8];
+
+    /// Safe wrapper around `fdt_nop_node()` (C function).
+    fn nop_node(&mut self, node: c_int) -> Result<()> {
+        let fdt = self.as_fdt_slice_mut().as_mut_ptr().cast();
+        // SAFETY: Accesses are constrained to the DT totalsize (validated by ctor).
+        let ret = unsafe { libfdt_bindgen::fdt_nop_node(fdt, node) };
+
+        fdt_err_expect_zero(ret)
+    }
+
+    /// Safe wrapper around `fdt_add_subnode_namelen()` (C function).
+    fn add_subnode_namelen(&mut self, node: c_int, name: &[u8]) -> Result<c_int> {
+        let fdt = self.as_fdt_slice_mut().as_mut_ptr().cast();
+        let namelen = name.len().try_into().unwrap();
+        let name = name.as_ptr().cast();
+        // SAFETY: Accesses are constrained to the DT totalsize (validated by ctor).
+        let ret = unsafe { libfdt_bindgen::fdt_add_subnode_namelen(fdt, node, name, namelen) };
+
+        fdt_err(ret)
+    }
+}