libfdt: Extend Rust wrapper with write functions

Add a subset of the libfdt "read-write" function to the Rust wrapper.

Bug: 255521657
Test: atest vmbase_example.integration_test
Change-Id: Id73e823d7f522431c8f9dd23799841317a9ce38a
diff --git a/libs/libfdt/src/lib.rs b/libs/libfdt/src/lib.rs
index 01f7b36..c38381b 100644
--- a/libs/libfdt/src/lib.rs
+++ b/libs/libfdt/src/lib.rs
@@ -384,6 +384,69 @@
     }
 }
 
+/// Mutable FDT node.
+pub struct FdtNodeMut<'a> {
+    fdt: &'a mut Fdt,
+    offset: c_int,
+}
+
+impl<'a> FdtNodeMut<'a> {
+    /// Append a property name-value (possibly empty) pair to the given node.
+    pub fn appendprop<T: AsRef<[u8]>>(&mut self, name: &CStr, value: &T) -> Result<()> {
+        // SAFETY - Accesses are constrained to the DT totalsize (validated by ctor).
+        let ret = unsafe {
+            libfdt_bindgen::fdt_appendprop(
+                self.fdt.as_mut_ptr(),
+                self.offset,
+                name.as_ptr(),
+                value.as_ref().as_ptr().cast::<c_void>(),
+                value.as_ref().len().try_into().map_err(|_| FdtError::BadValue)?,
+            )
+        };
+
+        fdt_err_expect_zero(ret)
+    }
+
+    /// Append a (address, size) pair property to the given node.
+    pub fn appendprop_addrrange(&mut self, name: &CStr, addr: u64, size: u64) -> Result<()> {
+        // SAFETY - Accesses are constrained to the DT totalsize (validated by ctor).
+        let ret = unsafe {
+            libfdt_bindgen::fdt_appendprop_addrrange(
+                self.fdt.as_mut_ptr(),
+                self.parent()?.offset,
+                self.offset,
+                name.as_ptr(),
+                addr,
+                size,
+            )
+        };
+
+        fdt_err_expect_zero(ret)
+    }
+
+    /// Get reference to the containing device tree.
+    pub fn fdt(&mut self) -> &mut Fdt {
+        self.fdt
+    }
+
+    /// Add 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> {
+        // SAFETY - Accesses are constrained to the DT totalsize (validated by ctor).
+        let ret = unsafe {
+            libfdt_bindgen::fdt_add_subnode(self.fdt.as_mut_ptr(), self.offset, name.as_ptr())
+        };
+
+        Ok(Self { fdt: self.fdt, offset: fdt_err(ret)? })
+    }
+
+    fn parent(&'a self) -> Result<FdtNode<'a>> {
+        // SAFETY - Accesses (read-only) are constrained to the DT totalsize.
+        let ret = unsafe { libfdt_bindgen::fdt_parent_offset(self.fdt.as_ptr(), self.offset) };
+
+        Ok(FdtNode { fdt: &*self.fdt, offset: fdt_err(ret)? })
+    }
+}
+
 /// Iterator over nodes sharing a same compatible string.
 pub struct CompatibleIterator<'a> {
     node: FdtNode<'a>,
@@ -411,7 +474,7 @@
     }
 }
 
-/// Wrapper around low-level read-only libfdt functions.
+/// Wrapper around low-level libfdt functions.
 #[repr(transparent)]
 pub struct Fdt {
     bytes: [u8],
@@ -428,6 +491,16 @@
         Ok(fdt)
     }
 
+    /// Wraps a mutable slice containing a Flattened Device Tree.
+    ///
+    /// Fails if the FDT does not pass validation.
+    pub fn from_mut_slice(fdt: &mut [u8]) -> Result<&mut Self> {
+        // SAFETY - The FDT will be validated before it is returned.
+        let fdt = unsafe { Self::unchecked_from_mut_slice(fdt) };
+        fdt.check_full()?;
+        Ok(fdt)
+    }
+
     /// Wraps a slice containing a Flattened Device Tree.
     ///
     /// # Safety
@@ -437,6 +510,39 @@
         mem::transmute::<&[u8], &Self>(fdt)
     }
 
+    /// Wraps a mutable slice containing a Flattened Device Tree.
+    ///
+    /// # Safety
+    ///
+    /// The returned FDT might be invalid, only use on slices containing a valid DT.
+    pub unsafe fn unchecked_from_mut_slice(fdt: &mut [u8]) -> &mut Self {
+        mem::transmute::<&mut [u8], &mut Self>(fdt)
+    }
+
+    /// Make the whole slice containing the DT available to libfdt.
+    pub fn unpack(&mut self) -> Result<()> {
+        // SAFETY - "Opens" the DT in-place (supported use-case) by updating its header and
+        // internal structures to make use of the whole self.fdt slice but performs no accesses
+        // outside of it and leaves the DT in a state that will be detected by other functions.
+        let ret = unsafe {
+            libfdt_bindgen::fdt_open_into(
+                self.as_ptr(),
+                self.as_mut_ptr(),
+                self.capacity().try_into().map_err(|_| FdtError::Internal)?,
+            )
+        };
+        fdt_err_expect_zero(ret)
+    }
+
+    /// Pack the DT to take a minimum amount of memory.
+    ///
+    /// Doesn't shrink the underlying memory slice.
+    pub fn pack(&mut self) -> Result<()> {
+        // SAFETY - "Closes" the DT in-place by updating its header and relocating its structs.
+        let ret = unsafe { libfdt_bindgen::fdt_pack(self.as_mut_ptr()) };
+        fdt_err_expect_zero(ret)
+    }
+
     /// Return an iterator of memory banks specified the "/memory" node.
     ///
     /// NOTE: This does not support individual "/memory@XXXX" banks.
@@ -473,6 +579,17 @@
         CompatibleIterator::new(self, compatible)
     }
 
+    /// Get the mutable root node of the tree.
+    pub fn root_mut(&mut self) -> Result<FdtNodeMut> {
+        self.node_mut(CStr::from_bytes_with_nul(b"/\0").unwrap())?.ok_or(FdtError::Internal)
+    }
+
+    /// Find a mutable tree node by its full path.
+    pub fn node_mut(&mut self, path: &CStr) -> Result<FdtNodeMut> {
+        let offset = self.path_offset(path)?;
+        Ok(FdtNodeMut { fdt: self, offset })
+    }
+
     fn path_offset(&self, path: &CStr) -> Result<c_int> {
         let len = path.to_bytes().len().try_into().map_err(|_| FdtError::BadPath)?;
         // SAFETY - Accesses are constrained to the DT totalsize (validated by ctor) and the
@@ -499,4 +616,12 @@
     fn as_ptr(&self) -> *const c_void {
         self as *const _ as *const c_void
     }
+
+    fn as_mut_ptr(&mut self) -> *mut c_void {
+        self as *mut _ as *mut c_void
+    }
+
+    fn capacity(&self) -> usize {
+        self.bytes.len()
+    }
 }
diff --git a/vmbase/example/idmap.S b/vmbase/example/idmap.S
index 7fc5d5e..71a6ade 100644
--- a/vmbase/example/idmap.S
+++ b/vmbase/example/idmap.S
@@ -44,7 +44,7 @@
 	.fill		509, 8, 0x0			// 509 GiB of remaining VA space
 
 	/* level 2 */
-0:	.quad		.L_BLOCK_RO  | 0x80000000	// DT provided by VMM
+0:	.quad		.L_BLOCK_MEM | 0x80000000	// DT provided by VMM
 	.quad		.L_BLOCK_MEM_XIP | 0x80200000	// 2 MiB of DRAM containing image
 	.quad		.L_BLOCK_MEM | 0x80400000	// 2 MiB of writable DRAM
 	.fill		509, 8, 0x0
diff --git a/vmbase/example/src/main.rs b/vmbase/example/src/main.rs
index dcff6e1..b305559 100644
--- a/vmbase/example/src/main.rs
+++ b/vmbase/example/src/main.rs
@@ -162,6 +162,30 @@
         info!("node compatible with '{}' at {reg:?}", compatible.to_str().unwrap());
     }
 
+    let writer = Fdt::from_mut_slice(fdt).unwrap();
+    writer.unpack().unwrap();
+    info!("FDT successfully unpacked.");
+
+    let path = CStr::from_bytes_with_nul(b"/memory\0").unwrap();
+    let mut node = writer.node_mut(path).unwrap();
+    let name = CStr::from_bytes_with_nul(b"child\0").unwrap();
+    let mut child = node.add_subnode(name).unwrap();
+    info!("Created subnode '{}/{}'.", path.to_str().unwrap(), name.to_str().unwrap());
+
+    let name = CStr::from_bytes_with_nul(b"str-property\0").unwrap();
+    child.appendprop(name, b"property-value\0").unwrap();
+    info!("Appended property '{}'.", name.to_str().unwrap());
+
+    let name = CStr::from_bytes_with_nul(b"pair-property\0").unwrap();
+    let addr = 0x0123_4567u64;
+    let size = 0x89ab_cdefu64;
+    child.appendprop_addrrange(name, addr, size).unwrap();
+    info!("Appended property '{}'.", name.to_str().unwrap());
+
+    let writer = child.fdt();
+    writer.pack().unwrap();
+    info!("FDT successfully packed.");
+
     info!("FDT checks done.");
 }