Merge changes I2145a7ed,Iacd188cb,Ib672b23b am: 8770dbcfa3

Original change: https://android-review.googlesource.com/c/platform/packages/modules/Virtualization/+/2499577

Change-Id: I8f7f59eb8136818da6127d5b3a447ab218eee4e6
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/libs/libfdt/src/iterators.rs b/libs/libfdt/src/iterators.rs
index a7ea0ee..05fdb4a 100644
--- a/libs/libfdt/src/iterators.rs
+++ b/libs/libfdt/src/iterators.rs
@@ -85,6 +85,31 @@
     }
 }
 
+// Converts two cells into bytes of the same size
+fn two_cells_to_bytes(cells: [u32; 2]) -> [u8; 2 * size_of::<u32>()] {
+    // SAFETY: the size of the two arrays are the same
+    unsafe { core::mem::transmute::<[u32; 2], [u8; 2 * size_of::<u32>()]>(cells) }
+}
+
+impl Reg<u64> {
+    const NUM_CELLS: usize = 2;
+    /// Converts addr and (optional) size to the format that is consumable by libfdt.
+    pub fn to_cells(
+        &self,
+    ) -> ([u8; Self::NUM_CELLS * size_of::<u32>()], Option<[u8; Self::NUM_CELLS * size_of::<u32>()]>)
+    {
+        let addr =
+            two_cells_to_bytes([((self.addr >> 32) as u32).to_be(), (self.addr as u32).to_be()]);
+        let size = if self.size.is_some() {
+            let size = self.size.unwrap();
+            Some(two_cells_to_bytes([((size >> 32) as u32).to_be(), (size as u32).to_be()]))
+        } else {
+            None
+        };
+        (addr, size)
+    }
+}
+
 /// Iterator over the address ranges defined by the /memory/ node.
 #[derive(Debug)]
 pub struct MemRegIterator<'a> {
@@ -122,7 +147,7 @@
 }
 
 /// An address range from the 'ranges' property of a DT node.
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, Default)]
 pub struct AddressRange<A, P, S> {
     /// The physical address of the range within the child bus's address space.
     pub addr: A,
@@ -202,3 +227,25 @@
         })
     }
 }
+
+impl AddressRange<(u32, u64), u64, u64> {
+    const SIZE_CELLS: usize = 7;
+    /// Converts to the format that is consumable by libfdt
+    pub fn to_cells(&self) -> [u8; Self::SIZE_CELLS * size_of::<u32>()] {
+        let buf = [
+            self.addr.0.to_be(),
+            ((self.addr.1 >> 32) as u32).to_be(),
+            (self.addr.1 as u32).to_be(),
+            ((self.parent_addr >> 32) as u32).to_be(),
+            (self.parent_addr as u32).to_be(),
+            ((self.size >> 32) as u32).to_be(),
+            (self.size as u32).to_be(),
+        ];
+        // SAFETY: the size of the two arrays are the same
+        unsafe {
+            core::mem::transmute::<[u32; Self::SIZE_CELLS], [u8; Self::SIZE_CELLS * size_of::<u32>()]>(
+                buf,
+            )
+        }
+    }
+}
diff --git a/libs/libfdt/src/lib.rs b/libs/libfdt/src/lib.rs
index 1d295eb..a1a740b 100644
--- a/libs/libfdt/src/lib.rs
+++ b/libs/libfdt/src/lib.rs
@@ -196,6 +196,10 @@
 }
 
 impl<'a> FdtNode<'a> {
+    /// Create immutable node from a mutable node at the same offset
+    pub fn from_mut(other: &'a FdtNodeMut) -> Self {
+        FdtNode { fdt: other.fdt, offset: other.offset }
+    }
     /// Find parent node.
     pub fn parent(&self) -> Result<Self> {
         // SAFETY - Accesses (read-only) are constrained to the DT totalsize.
@@ -285,13 +289,31 @@
 
     /// Retrieve the value of a given property.
     pub fn getprop(&self, name: &CStr) -> Result<Option<&'a [u8]>> {
+        if let Some((prop, len)) = Self::getprop_internal(self.fdt, self.offset, name)? {
+            let offset = (prop as usize)
+                .checked_sub(self.fdt.as_ptr() as usize)
+                .ok_or(FdtError::Internal)?;
+
+            Ok(Some(self.fdt.buffer.get(offset..(offset + len)).ok_or(FdtError::Internal)?))
+        } else {
+            Ok(None) // property was not found
+        }
+    }
+
+    /// Return the pointer and size of the property named `name`, in a node at offset `offset`, in
+    /// a device tree `fdt`. The pointer is guaranteed to be non-null, in which case error returns.
+    fn getprop_internal(
+        fdt: &'a Fdt,
+        offset: c_int,
+        name: &CStr,
+    ) -> Result<Option<(*const c_void, usize)>> {
         let mut len: i32 = 0;
         // SAFETY - Accesses are constrained to the DT totalsize (validated by ctor) and the
         // function respects the passed number of characters.
         let prop = unsafe {
             libfdt_bindgen::fdt_getprop_namelen(
-                self.fdt.as_ptr(),
-                self.offset,
+                fdt.as_ptr(),
+                offset,
                 name.as_ptr(),
                 // *_namelen functions don't include the trailing nul terminator in 'len'.
                 name.to_bytes().len().try_into().map_err(|_| FdtError::BadPath)?,
@@ -308,11 +330,7 @@
             // We expected an error code in len but still received a valid value?!
             return Err(FdtError::Internal);
         }
-
-        let offset =
-            (prop as usize).checked_sub(self.fdt.as_ptr() as usize).ok_or(FdtError::Internal)?;
-
-        Ok(Some(self.fdt.buffer.get(offset..(offset + len)).ok_or(FdtError::Internal)?))
+        Ok(Some((prop.cast::<c_void>(), len)))
     }
 
     /// Get reference to the containing device tree.
@@ -405,6 +423,23 @@
         fdt_err_expect_zero(ret)
     }
 
+    /// Replace the value of the given property with the given value, and ensure that the given
+    /// value has the same length as the current value length
+    pub fn setprop_inplace(&mut self, name: &CStr, value: &[u8]) -> Result<()> {
+        // SAFETY - fdt size is not altered
+        let ret = unsafe {
+            libfdt_bindgen::fdt_setprop_inplace(
+                self.fdt.as_mut_ptr(),
+                self.offset,
+                name.as_ptr(),
+                value.as_ptr().cast::<c_void>(),
+                value.len().try_into().map_err(|_| FdtError::BadValue)?,
+            )
+        };
+
+        fdt_err_expect_zero(ret)
+    }
+
     /// Create or change a flag-like empty property.
     pub fn setprop_empty(&mut self, name: &CStr) -> Result<()> {
         self.setprop(name, &[])
@@ -423,6 +458,31 @@
         fdt_err_expect_zero(ret)
     }
 
+    /// Reduce the size of the given property to new_size
+    pub fn trimprop(&mut self, name: &CStr, new_size: usize) -> Result<()> {
+        let (prop, len) =
+            FdtNode::getprop_internal(self.fdt, self.offset, name)?.ok_or(FdtError::NotFound)?;
+        if len == new_size {
+            return Ok(());
+        }
+        if new_size > len {
+            return Err(FdtError::NoSpace);
+        }
+
+        // SAFETY - new_size is smaller than the old size
+        let ret = unsafe {
+            libfdt_bindgen::fdt_setprop(
+                self.fdt.as_mut_ptr(),
+                self.offset,
+                name.as_ptr(),
+                prop.cast::<c_void>(),
+                new_size.try_into().map_err(|_| FdtError::BadValue)?,
+            )
+        };
+
+        fdt_err_expect_zero(ret)
+    }
+
     /// Get reference to the containing device tree.
     pub fn fdt(&mut self) -> &mut Fdt {
         self.fdt
@@ -444,6 +504,51 @@
 
         Ok(FdtNode { fdt: &*self.fdt, offset: fdt_err(ret)? })
     }
+
+    /// Return the compatible node of the given name that is next to 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(),
+            )
+        };
+
+        Ok(fdt_err_or_option(ret)?.map(|offset| Self { fdt: self.fdt, offset }))
+    }
+
+    /// Replace this node and its subtree with nop tags, effectively removing it from the tree, and
+    /// then return the next compatible node of the given name.
+    // Side note: without this, filterint out excessive compatible nodes from the DT is impossible.
+    // The reason is that libfdt ensures that the node from where the search for the next
+    // compatible node is started is always a valid one -- except for the special case of offset =
+    // -1 which is to find the first compatible node. So, we can't delete a node and then find the
+    // next compatible node from it.
+    //
+    // We can't do in the opposite direction either. If we call next_compatible to find the next
+    // node, and delete the current node, the Rust borrow checker kicks in. The next node has a
+    // 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)?;
+
+        // SAFETY - fdt_nop_node alter only the bytes in the blob which contain the node and its
+        // properties and subnodes, and will not alter or move any other part of the tree.
+        let ret = unsafe { libfdt_bindgen::fdt_nop_node(self.fdt.as_mut_ptr(), self.offset) };
+        fdt_err_expect_zero(ret)?;
+
+        Ok(next_offset.map(|offset| Self { fdt: self.fdt, offset }))
+    }
 }
 
 /// Iterator over nodes sharing a same compatible string.
diff --git a/pvmfw/platform.dts b/pvmfw/platform.dts
index 127f69a..a7b1de7 100644
--- a/pvmfw/platform.dts
+++ b/pvmfw/platform.dts
@@ -8,6 +8,8 @@
 #define PLACEHOLDER2	PLACEHOLDER PLACEHOLDER
 #define PLACEHOLDER4	PLACEHOLDER2 PLACEHOLDER2
 
+#define IRQ_BASE 4
+
 /dts-v1/;
 
 / {
@@ -214,13 +216,14 @@
 		bus-range = <0x00 0x00>;
 		reg = <0x00 0x10000 0x00 0x1000000>;
 		interrupt-map = <
-			0x0800 0x0 0x0 1 &intc 0 0 GIC_SPI 3 IRQ_TYPE_LEVEL_HIGH
-			0x1000 0x0 0x0 1 &intc 0 0 GIC_SPI 4 IRQ_TYPE_LEVEL_HIGH
-			0x1800 0x0 0x0 1 &intc 0 0 GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH
-			0x2000 0x0 0x0 1 &intc 0 0 GIC_SPI 6 IRQ_TYPE_LEVEL_HIGH
-			0x2800 0x0 0x0 1 &intc 0 0 GIC_SPI 7 IRQ_TYPE_LEVEL_HIGH
-			0x3000 0x0 0x0 1 &intc 0 0 GIC_SPI 8 IRQ_TYPE_LEVEL_HIGH
-			0x3800 0x0 0x0 1 &intc 0 0 GIC_SPI 9 IRQ_TYPE_LEVEL_HIGH
+			0x0800 0x0 0x0 1 &intc 0 0 GIC_SPI (IRQ_BASE + 0) IRQ_TYPE_LEVEL_HIGH
+			0x1000 0x0 0x0 1 &intc 0 0 GIC_SPI (IRQ_BASE + 1) IRQ_TYPE_LEVEL_HIGH
+			0x1800 0x0 0x0 1 &intc 0 0 GIC_SPI (IRQ_BASE + 2) IRQ_TYPE_LEVEL_HIGH
+			0x2000 0x0 0x0 1 &intc 0 0 GIC_SPI (IRQ_BASE + 3) IRQ_TYPE_LEVEL_HIGH
+			0x2800 0x0 0x0 1 &intc 0 0 GIC_SPI (IRQ_BASE + 4) IRQ_TYPE_LEVEL_HIGH
+			0x3000 0x0 0x0 1 &intc 0 0 GIC_SPI (IRQ_BASE + 5) IRQ_TYPE_LEVEL_HIGH
+			0x3800 0x0 0x0 1 &intc 0 0 GIC_SPI (IRQ_BASE + 6) IRQ_TYPE_LEVEL_HIGH
+			0x4000 0x0 0x0 1 &intc 0 0 GIC_SPI (IRQ_BASE + 7) IRQ_TYPE_LEVEL_HIGH
 		>;
 		interrupt-map-mask = <0xf800 0x0 0x0 0x7
 				      0xf800 0x0 0x0 0x7
@@ -228,6 +231,7 @@
 				      0xf800 0x0 0x0 0x7
 				      0xf800 0x0 0x0 0x7
 				      0xf800 0x0 0x0 0x7
+				      0xf800 0x0 0x0 0x7
 				      0xf800 0x0 0x0 0x7>;
 	};
 
diff --git a/pvmfw/src/entry.rs b/pvmfw/src/entry.rs
index 89f2637..8219882 100644
--- a/pvmfw/src/entry.rs
+++ b/pvmfw/src/entry.rs
@@ -109,38 +109,17 @@
             RebootReason::InvalidFdt
         })?;
 
-        fdt::sanitize_device_tree(fdt)?;
+        let info = fdt::sanitize_device_tree(fdt)?;
         debug!("Fdt passed validation!");
 
-        let memory_range = fdt
-            .memory()
-            .map_err(|e| {
-                error!("Failed to get /memory from the DT: {e}");
-                RebootReason::InvalidFdt
-            })?
-            .ok_or_else(|| {
-                error!("Node /memory was found empty");
-                RebootReason::InvalidFdt
-            })?
-            .next()
-            .ok_or_else(|| {
-                error!("Failed to read the memory size from the FDT");
-                RebootReason::InternalError
-            })?;
-
+        let memory_range = info.memory_range;
         debug!("Resizing MemoryTracker to range {memory_range:#x?}");
-
         memory.shrink(&memory_range).map_err(|_| {
             error!("Failed to use memory range value from DT: {memory_range:#x?}");
             RebootReason::InvalidFdt
         })?;
 
-        let kernel_range = fdt::kernel_range(fdt).map_err(|e| {
-            error!("Error while attempting to read the kernel range from the DT: {e}");
-            RebootReason::InvalidFdt
-        })?;
-
-        let kernel_range = if let Some(r) = kernel_range {
+        let kernel_range = if let Some(r) = info.kernel_range {
             memory.alloc_range(&r).map_err(|e| {
                 error!("Failed to obtain the kernel range with DT range: {e}");
                 RebootReason::InternalError
@@ -166,12 +145,7 @@
         let kernel =
             unsafe { slice::from_raw_parts(kernel_range.start as *const u8, kernel_range.len()) };
 
-        let ramdisk_range = fdt::initrd_range(fdt).map_err(|e| {
-            error!("An error occurred while locating the ramdisk in the device tree: {e}");
-            RebootReason::InternalError
-        })?;
-
-        let ramdisk = if let Some(r) = ramdisk_range {
+        let ramdisk = if let Some(r) = info.initrd_range {
             debug!("Located ramdisk at {r:?}");
             let r = memory.alloc_range(&r).map_err(|e| {
                 error!("Failed to obtain the initrd range: {e}");
diff --git a/pvmfw/src/fdt.rs b/pvmfw/src/fdt.rs
index a794b42..d014be9 100644
--- a/pvmfw/src/fdt.rs
+++ b/pvmfw/src/fdt.rs
@@ -15,10 +15,12 @@
 //! High-level FDT functions.
 
 use crate::cstr;
+use crate::helpers::flatten;
 use crate::helpers::GUEST_PAGE_SIZE;
+use crate::helpers::SIZE_4KB;
 use crate::RebootReason;
 use core::ffi::CStr;
-use core::num::NonZeroUsize;
+use core::mem::size_of;
 use core::ops::Range;
 use fdtpci::PciMemoryFlags;
 use fdtpci::PciRangeType;
@@ -26,12 +28,14 @@
 use libfdt::CellIterator;
 use libfdt::Fdt;
 use libfdt::FdtError;
+use libfdt::FdtNode;
 use log::debug;
 use log::error;
 use tinyvec::ArrayVec;
 
-/// Extract from /config the address range containing the pre-loaded kernel.
-pub fn kernel_range(fdt: &libfdt::Fdt) -> libfdt::Result<Option<Range<usize>>> {
+/// Extract from /config the address range containing the pre-loaded kernel. Absence of /config is
+/// not an error.
+fn read_kernel_range_from(fdt: &Fdt) -> libfdt::Result<Option<Range<usize>>> {
     let addr = cstr!("kernel-address");
     let size = cstr!("kernel-size");
 
@@ -47,8 +51,9 @@
     Ok(None)
 }
 
-/// Extract from /chosen the address range containing the pre-loaded ramdisk.
-pub fn initrd_range(fdt: &libfdt::Fdt) -> libfdt::Result<Option<Range<usize>>> {
+/// Extract from /chosen the address range containing the pre-loaded ramdisk. Absence is not an
+/// error as there can be initrd-less VM.
+fn read_initrd_range_from(fdt: &Fdt) -> libfdt::Result<Option<Range<usize>>> {
     let start = cstr!("linux,initrd-start");
     let end = cstr!("linux,initrd-end");
 
@@ -61,145 +66,101 @@
     Ok(None)
 }
 
-/// Read and validate the size and base address of memory, and returns the size
-fn parse_memory_node(fdt: &libfdt::Fdt) -> Result<NonZeroUsize, RebootReason> {
-    let memory_range = fdt
-        .memory()
-        // Actually, these checks are unnecessary because we read /memory node in entry.rs
-        // where the exactly same checks are done. We are repeating the same check just for
-        // extra safety (in case when the code structure changes in the future).
-        .map_err(|e| {
-            error!("Failed to get /memory from the DT: {e}");
-            RebootReason::InvalidFdt
-        })?
-        .ok_or_else(|| {
-            error!("Node /memory was found empty");
-            RebootReason::InvalidFdt
-        })?
-        .next()
-        .ok_or_else(|| {
-            error!("Failed to read memory range from the DT");
-            RebootReason::InvalidFdt
-        })?;
+fn patch_initrd_range(fdt: &mut Fdt, initrd_range: &Range<usize>) -> libfdt::Result<()> {
+    let start = u32::try_from(initrd_range.start).unwrap();
+    let end = u32::try_from(initrd_range.end).unwrap();
 
-    let base = memory_range.start;
+    let mut node = fdt.chosen_mut()?.ok_or(FdtError::NotFound)?;
+    node.setprop(cstr!("linux,initrd-start"), &start.to_be_bytes())?;
+    node.setprop(cstr!("linux,initrd-end"), &end.to_be_bytes())?;
+    Ok(())
+}
+
+/// Read the first range in /memory node in DT
+fn read_memory_range_from(fdt: &Fdt) -> libfdt::Result<Range<usize>> {
+    fdt.memory()?.ok_or(FdtError::NotFound)?.next().ok_or(FdtError::NotFound)
+}
+
+/// Check if memory range is ok
+fn validate_memory_range(range: &Range<usize>) -> Result<(), RebootReason> {
+    let base = range.start;
     if base as u64 != DeviceTreeInfo::RAM_BASE_ADDR {
         error!("Memory base address {:#x} is not {:#x}", base, DeviceTreeInfo::RAM_BASE_ADDR);
         return Err(RebootReason::InvalidFdt);
     }
 
-    let size = memory_range.len(); // end is exclusive
+    let size = range.len();
     if size % GUEST_PAGE_SIZE != 0 {
         error!("Memory size {:#x} is not a multiple of page size {:#x}", size, GUEST_PAGE_SIZE);
         return Err(RebootReason::InvalidFdt);
     }
-    // In the u-boot implementation, we checked if base + size > u64::MAX, but we don't need that
-    // because memory() function uses checked_add when constructing the Range object. If an
-    // overflow happened, we should have gotten None from the next() call above and would have
-    // bailed already.
 
-    NonZeroUsize::new(size).ok_or_else(|| {
-        error!("Memory size can't be 0");
-        RebootReason::InvalidFdt
-    })
+    if size == 0 {
+        error!("Memory size is 0");
+        return Err(RebootReason::InvalidFdt);
+    }
+    Ok(())
 }
 
-/// Read the number of CPUs
-fn parse_cpu_nodes(fdt: &libfdt::Fdt) -> Result<NonZeroUsize, RebootReason> {
-    let num = fdt
-        .compatible_nodes(cstr!("arm,arm-v8"))
-        .map_err(|e| {
-            error!("Failed to read compatible nodes \"arm,arm-v8\" from DT: {e}");
-            RebootReason::InvalidFdt
-        })?
-        .count();
-    NonZeroUsize::new(num).ok_or_else(|| {
+fn patch_memory_range(fdt: &mut Fdt, memory_range: &Range<usize>) -> libfdt::Result<()> {
+    let size = memory_range.len() as u64;
+    fdt.node_mut(cstr!("/memory"))?.ok_or(FdtError::NotFound)?.setprop_inplace(
+        cstr!("reg"),
+        flatten(&[DeviceTreeInfo::RAM_BASE_ADDR.to_be_bytes(), size.to_be_bytes()]),
+    )
+}
+
+/// Read the number of CPUs from DT
+fn read_num_cpus_from(fdt: &Fdt) -> libfdt::Result<usize> {
+    Ok(fdt.compatible_nodes(cstr!("arm,arm-v8"))?.count())
+}
+
+/// Validate number of CPUs
+fn validate_num_cpus(num_cpus: usize) -> Result<(), RebootReason> {
+    if num_cpus == 0 {
         error!("Number of CPU can't be 0");
-        RebootReason::InvalidFdt
-    })
+        return Err(RebootReason::InvalidFdt);
+    }
+    if DeviceTreeInfo::GIC_REDIST_SIZE_PER_CPU.checked_mul(num_cpus.try_into().unwrap()).is_none() {
+        error!("Too many CPUs for gic: {}", num_cpus);
+        return Err(RebootReason::InvalidFdt);
+    }
+    Ok(())
+}
+
+/// Patch DT by keeping `num_cpus` number of arm,arm-v8 compatible nodes, and pruning the rest.
+fn patch_num_cpus(fdt: &mut Fdt, num_cpus: usize) -> libfdt::Result<()> {
+    let cpu = cstr!("arm,arm-v8");
+    let mut next = fdt.root_mut()?.next_compatible(cpu)?;
+    for _ in 0..num_cpus {
+        next = if let Some(current) = next {
+            current.next_compatible(cpu)?
+        } else {
+            return Err(FdtError::NoSpace);
+        };
+    }
+    while let Some(current) = next {
+        next = current.delete_and_next_compatible(cpu)?;
+    }
+    Ok(())
 }
 
 #[derive(Debug)]
-#[allow(dead_code)] // TODO: remove this
 struct PciInfo {
-    ranges: [Range<u64>; 2],
-    num_irq: usize,
+    ranges: [PciAddrRange; 2],
+    irq_masks: ArrayVec<[PciIrqMask; PciInfo::MAX_IRQS]>,
+    irq_maps: ArrayVec<[PciIrqMap; PciInfo::MAX_IRQS]>,
 }
 
-/// Read and validate PCI node
-fn parse_pci_nodes(fdt: &libfdt::Fdt) -> Result<PciInfo, RebootReason> {
-    let node = fdt
-        .compatible_nodes(cstr!("pci-host-cam-generic"))
-        .map_err(|e| {
-            error!("Failed to read compatible node \"pci-host-cam-generic\" from DT: {e}");
-            RebootReason::InvalidFdt
-        })?
-        .next()
-        .ok_or_else(|| {
-            // pvmfw requires at least one pci device (virtio-blk) for the instance disk. So,
-            // let's fail early.
-            error!("Compatible node \"pci-host-cam-generic\" doesn't exist");
-            RebootReason::InvalidFdt
-        })?;
-
-    let mut iter = node
-        .ranges::<(u32, u64), u64, u64>()
-        .map_err(|e| {
-            error!("Failed to read ranges from PCI node: {e}");
-            RebootReason::InvalidFdt
-        })?
-        .ok_or_else(|| {
-            error!("PCI node missing ranges property");
-            RebootReason::InvalidFdt
-        })?;
-
-    let range0 = iter.next().ok_or_else(|| {
-        error!("First range missing in PCI node");
-        RebootReason::InvalidFdt
-    })?;
-    let range0 = get_and_validate_pci_range(&range0)?;
-
-    let range1 = iter.next().ok_or_else(|| {
-        error!("Second range missing in PCI node");
-        RebootReason::InvalidFdt
-    })?;
-    let range1 = get_and_validate_pci_range(&range1)?;
-
-    let num_irq = count_and_validate_pci_irq_masks(&node)?;
-
-    validate_pci_irq_maps(&node)?;
-
-    Ok(PciInfo { ranges: [range0, range1], num_irq })
+impl PciInfo {
+    const IRQ_MASK_CELLS: usize = 4;
+    const IRQ_MAP_CELLS: usize = 10;
+    const MAX_IRQS: usize = 8;
 }
 
-fn get_and_validate_pci_range(
-    range: &AddressRange<(u32, u64), u64, u64>,
-) -> Result<Range<u64>, RebootReason> {
-    let mem_flags = PciMemoryFlags(range.addr.0);
-    let range_type = mem_flags.range_type();
-    let prefetchable = mem_flags.prefetchable();
-    let bus_addr = range.addr.1;
-    let cpu_addr = range.parent_addr;
-    let size = range.size;
-    if range_type != PciRangeType::Memory64 {
-        error!("Invalid range type {:?} for bus address {:#x} in PCI node", range_type, bus_addr);
-        return Err(RebootReason::InvalidFdt);
-    }
-    if prefetchable {
-        error!("PCI bus address {:#x} in PCI node is prefetchable", bus_addr);
-        return Err(RebootReason::InvalidFdt);
-    }
-    // Enforce ID bus-to-cpu mappings, as used by crosvm.
-    if bus_addr != cpu_addr {
-        error!("PCI bus address: {:#x} is different from CPU address: {:#x}", bus_addr, cpu_addr);
-        return Err(RebootReason::InvalidFdt);
-    }
-    let bus_end = bus_addr.checked_add(size).ok_or_else(|| {
-        error!("PCI address range size {:#x} too big", size);
-        RebootReason::InvalidFdt
-    })?;
-    Ok(bus_addr..bus_end)
-}
+type PciAddrRange = AddressRange<(u32, u64), u64, u64>;
+type PciIrqMask = [u32; PciInfo::IRQ_MASK_CELLS];
+type PciIrqMap = [u32; PciInfo::IRQ_MAP_CELLS];
 
 /// Iterator that takes N cells as a chunk
 struct CellChunkIterator<'a, const N: usize> {
@@ -223,39 +184,86 @@
     }
 }
 
-fn count_and_validate_pci_irq_masks(pci_node: &libfdt::FdtNode) -> Result<usize, RebootReason> {
-    const IRQ_MASK_CELLS: usize = 4;
+/// Read pci host controller ranges, irq maps, and irq map masks from DT
+fn read_pci_info_from(fdt: &Fdt) -> libfdt::Result<PciInfo> {
+    let node =
+        fdt.compatible_nodes(cstr!("pci-host-cam-generic"))?.next().ok_or(FdtError::NotFound)?;
+
+    let mut ranges = node.ranges::<(u32, u64), u64, u64>()?.ok_or(FdtError::NotFound)?;
+    let range0 = ranges.next().ok_or(FdtError::NotFound)?;
+    let range1 = ranges.next().ok_or(FdtError::NotFound)?;
+
+    let irq_masks = node.getprop_cells(cstr!("interrupt-map-mask"))?.ok_or(FdtError::NotFound)?;
+    let irq_masks = CellChunkIterator::<{ PciInfo::IRQ_MASK_CELLS }>::new(irq_masks);
+    let irq_masks: ArrayVec<[PciIrqMask; PciInfo::MAX_IRQS]> =
+        irq_masks.take(PciInfo::MAX_IRQS).collect();
+
+    let irq_maps = node.getprop_cells(cstr!("interrupt-map"))?.ok_or(FdtError::NotFound)?;
+    let irq_maps = CellChunkIterator::<{ PciInfo::IRQ_MAP_CELLS }>::new(irq_maps);
+    let irq_maps: ArrayVec<[PciIrqMap; PciInfo::MAX_IRQS]> =
+        irq_maps.take(PciInfo::MAX_IRQS).collect();
+
+    Ok(PciInfo { ranges: [range0, range1], irq_masks, irq_maps })
+}
+
+fn validate_pci_info(pci_info: &PciInfo) -> Result<(), RebootReason> {
+    for range in pci_info.ranges.iter() {
+        validate_pci_addr_range(range)?;
+    }
+    for irq_mask in pci_info.irq_masks.iter() {
+        validate_pci_irq_mask(irq_mask)?;
+    }
+    for (idx, irq_map) in pci_info.irq_maps.iter().enumerate() {
+        validate_pci_irq_map(irq_map, idx)?;
+    }
+    Ok(())
+}
+
+fn validate_pci_addr_range(range: &PciAddrRange) -> Result<(), RebootReason> {
+    let mem_flags = PciMemoryFlags(range.addr.0);
+    let range_type = mem_flags.range_type();
+    let prefetchable = mem_flags.prefetchable();
+    let bus_addr = range.addr.1;
+    let cpu_addr = range.parent_addr;
+    let size = range.size;
+
+    if range_type != PciRangeType::Memory64 {
+        error!("Invalid range type {:?} for bus address {:#x} in PCI node", range_type, bus_addr);
+        return Err(RebootReason::InvalidFdt);
+    }
+    if prefetchable {
+        error!("PCI bus address {:#x} in PCI node is prefetchable", bus_addr);
+        return Err(RebootReason::InvalidFdt);
+    }
+    // Enforce ID bus-to-cpu mappings, as used by crosvm.
+    if bus_addr != cpu_addr {
+        error!("PCI bus address: {:#x} is different from CPU address: {:#x}", bus_addr, cpu_addr);
+        return Err(RebootReason::InvalidFdt);
+    }
+
+    if bus_addr.checked_add(size).is_none() {
+        error!("PCI address range size {:#x} too big", size);
+        return Err(RebootReason::InvalidFdt);
+    }
+
+    Ok(())
+}
+
+fn validate_pci_irq_mask(irq_mask: &PciIrqMask) -> Result<(), RebootReason> {
     const IRQ_MASK_ADDR_HI: u32 = 0xf800;
     const IRQ_MASK_ADDR_ME: u32 = 0x0;
     const IRQ_MASK_ADDR_LO: u32 = 0x0;
     const IRQ_MASK_ANY_IRQ: u32 = 0x7;
-    const EXPECTED: [u32; IRQ_MASK_CELLS] =
+    const EXPECTED: PciIrqMask =
         [IRQ_MASK_ADDR_HI, IRQ_MASK_ADDR_ME, IRQ_MASK_ADDR_LO, IRQ_MASK_ANY_IRQ];
-
-    let mut irq_count: usize = 0;
-    for irq_mask in CellChunkIterator::<IRQ_MASK_CELLS>::new(
-        pci_node
-            .getprop_cells(cstr!("interrupt-map-mask"))
-            .map_err(|e| {
-                error!("Failed to read interrupt-map-mask property: {e}");
-                RebootReason::InvalidFdt
-            })?
-            .ok_or_else(|| {
-                error!("PCI node missing interrupt-map-mask property");
-                RebootReason::InvalidFdt
-            })?,
-    ) {
-        if irq_mask != EXPECTED {
-            error!("invalid irq mask {:?}", irq_mask);
-            return Err(RebootReason::InvalidFdt);
-        }
-        irq_count += 1;
+    if *irq_mask != EXPECTED {
+        error!("Invalid PCI irq mask {:#?}", irq_mask);
+        return Err(RebootReason::InvalidFdt);
     }
-    Ok(irq_count)
+    Ok(())
 }
 
-fn validate_pci_irq_maps(pci_node: &libfdt::FdtNode) -> Result<(), RebootReason> {
-    const IRQ_MAP_CELLS: usize = 10;
+fn validate_pci_irq_map(irq_map: &PciIrqMap, idx: usize) -> Result<(), RebootReason> {
     const PCI_DEVICE_IDX: usize = 11;
     const PCI_IRQ_ADDR_ME: u32 = 0;
     const PCI_IRQ_ADDR_LO: u32 = 0;
@@ -264,163 +272,139 @@
     const GIC_SPI: u32 = 0;
     const IRQ_TYPE_LEVEL_HIGH: u32 = 4;
 
-    let mut phys_hi: u32 = 0;
-    let mut irq_nr = AARCH64_IRQ_BASE;
+    let pci_addr = (irq_map[0], irq_map[1], irq_map[2]);
+    let pci_irq_number = irq_map[3];
+    let _controller_phandle = irq_map[4]; // skipped.
+    let gic_addr = (irq_map[5], irq_map[6]); // address-cells is <2> for GIC
+                                             // interrupt-cells is <3> for GIC
+    let gic_peripheral_interrupt_type = irq_map[7];
+    let gic_irq_number = irq_map[8];
+    let gic_irq_type = irq_map[9];
 
-    for irq_map in CellChunkIterator::<IRQ_MAP_CELLS>::new(
-        pci_node
-            .getprop_cells(cstr!("interrupt-map"))
-            .map_err(|e| {
-                error!("Failed to read interrupt-map property: {e}");
-                RebootReason::InvalidFdt
-            })?
-            .ok_or_else(|| {
-                error!("PCI node missing interrupt-map property");
-                RebootReason::InvalidFdt
-            })?,
-    ) {
-        phys_hi += 0x1 << PCI_DEVICE_IDX;
+    let phys_hi: u32 = (0x1 << PCI_DEVICE_IDX) * (idx + 1) as u32;
+    let expected_pci_addr = (phys_hi, PCI_IRQ_ADDR_ME, PCI_IRQ_ADDR_LO);
 
-        let pci_addr = (irq_map[0], irq_map[1], irq_map[2]);
-        let pci_irq_number = irq_map[3];
-        let _controller_phandle = irq_map[4]; // skipped.
-        let gic_addr = (irq_map[5], irq_map[6]); // address-cells is <2> for GIC
-                                                 // interrupt-cells is <3> for GIC
-        let gic_peripheral_interrupt_type = irq_map[7];
-        let gic_irq_number = irq_map[8];
-        let gic_irq_type = irq_map[9];
+    if pci_addr != expected_pci_addr {
+        error!("PCI device address {:#x} {:#x} {:#x} in interrupt-map is different from expected address \
+               {:#x} {:#x} {:#x}",
+               pci_addr.0, pci_addr.1, pci_addr.2, expected_pci_addr.0, expected_pci_addr.1, expected_pci_addr.2);
+        return Err(RebootReason::InvalidFdt);
+    }
 
-        let expected_pci_addr = (phys_hi, PCI_IRQ_ADDR_ME, PCI_IRQ_ADDR_LO);
+    if pci_irq_number != PCI_IRQ_INTC {
+        error!(
+            "PCI INT# {:#x} in interrupt-map is different from expected value {:#x}",
+            pci_irq_number, PCI_IRQ_INTC
+        );
+        return Err(RebootReason::InvalidFdt);
+    }
 
-        if pci_addr != expected_pci_addr {
-            error!("PCI device address {:#x} {:#x} {:#x} in interrupt-map is different from expected address \
-                   {:#x} {:#x} {:#x}",
-                   pci_addr.0, pci_addr.1, pci_addr.2, expected_pci_addr.0, expected_pci_addr.1, expected_pci_addr.2);
-            return Err(RebootReason::InvalidFdt);
-        }
-        if pci_irq_number != PCI_IRQ_INTC {
-            error!(
-                "PCI INT# {:#x} in interrupt-map is different from expected value {:#x}",
-                pci_irq_number, PCI_IRQ_INTC
-            );
-            return Err(RebootReason::InvalidFdt);
-        }
-        if gic_addr != (0, 0) {
-            error!(
-                "GIC address {:#x} {:#x} in interrupt-map is different from expected address \
-                   {:#x} {:#x}",
-                gic_addr.0, gic_addr.1, 0, 0
-            );
-            return Err(RebootReason::InvalidFdt);
-        }
-        if gic_peripheral_interrupt_type != GIC_SPI {
-            error!("GIC peripheral interrupt type {:#x} in interrupt-map is different from expected value \
-                   {:#x}", gic_peripheral_interrupt_type, GIC_SPI);
-            return Err(RebootReason::InvalidFdt);
-        }
-        if gic_irq_number != irq_nr {
-            error!(
-                "GIC irq number {:#x} in interrupt-map is unexpected. Expected {:#x}",
-                gic_irq_number, irq_nr
-            );
-            return Err(RebootReason::InvalidFdt);
-        }
-        irq_nr += 1; // move to next irq
-        if gic_irq_type != IRQ_TYPE_LEVEL_HIGH {
-            error!(
-                "IRQ type in {:#x} is invalid. Must be LEVEL_HIGH {:#x}",
-                gic_irq_type, IRQ_TYPE_LEVEL_HIGH
-            );
-            return Err(RebootReason::InvalidFdt);
+    if gic_addr != (0, 0) {
+        error!(
+            "GIC address {:#x} {:#x} in interrupt-map is different from expected address \
+               {:#x} {:#x}",
+            gic_addr.0, gic_addr.1, 0, 0
+        );
+        return Err(RebootReason::InvalidFdt);
+    }
+
+    if gic_peripheral_interrupt_type != GIC_SPI {
+        error!("GIC peripheral interrupt type {:#x} in interrupt-map is different from expected value \
+               {:#x}", gic_peripheral_interrupt_type, GIC_SPI);
+        return Err(RebootReason::InvalidFdt);
+    }
+
+    let irq_nr: u32 = AARCH64_IRQ_BASE + (idx as u32);
+    if gic_irq_number != irq_nr {
+        error!(
+            "GIC irq number {:#x} in interrupt-map is unexpected. Expected {:#x}",
+            gic_irq_number, irq_nr
+        );
+        return Err(RebootReason::InvalidFdt);
+    }
+
+    if gic_irq_type != IRQ_TYPE_LEVEL_HIGH {
+        error!(
+            "IRQ type in {:#x} is invalid. Must be LEVEL_HIGH {:#x}",
+            gic_irq_type, IRQ_TYPE_LEVEL_HIGH
+        );
+        return Err(RebootReason::InvalidFdt);
+    }
+    Ok(())
+}
+
+fn patch_pci_info(fdt: &mut Fdt, pci_info: &PciInfo) -> libfdt::Result<()> {
+    let mut node = fdt
+        .root_mut()?
+        .next_compatible(cstr!("pci-host-cam-generic"))?
+        .ok_or(FdtError::NotFound)?;
+
+    let irq_masks_size = pci_info.irq_masks.len() * size_of::<PciIrqMask>();
+    node.trimprop(cstr!("interrupt-map-mask"), irq_masks_size)?;
+
+    let irq_maps_size = pci_info.irq_maps.len() * size_of::<PciIrqMap>();
+    node.trimprop(cstr!("interrupt-map"), irq_maps_size)?;
+
+    node.setprop_inplace(
+        cstr!("ranges"),
+        flatten(&[pci_info.ranges[0].to_cells(), pci_info.ranges[1].to_cells()]),
+    )
+}
+
+#[derive(Default, Debug)]
+struct SerialInfo {
+    addrs: ArrayVec<[u64; Self::MAX_SERIALS]>,
+}
+
+impl SerialInfo {
+    const MAX_SERIALS: usize = 4;
+}
+
+fn read_serial_info_from(fdt: &Fdt) -> libfdt::Result<SerialInfo> {
+    let mut addrs: ArrayVec<[u64; SerialInfo::MAX_SERIALS]> = Default::default();
+    for node in fdt.compatible_nodes(cstr!("ns16550a"))?.take(SerialInfo::MAX_SERIALS) {
+        let reg = node.reg()?.ok_or(FdtError::NotFound)?.next().ok_or(FdtError::NotFound)?;
+        addrs.push(reg.addr);
+    }
+    Ok(SerialInfo { addrs })
+}
+
+/// Patch the DT by deleting the ns16550a compatible nodes whose address are unknown
+fn patch_serial_info(fdt: &mut Fdt, serial_info: &SerialInfo) -> libfdt::Result<()> {
+    let name = cstr!("ns16550a");
+    let mut next = fdt.root_mut()?.next_compatible(name);
+    while let Some(current) = next? {
+        let reg = FdtNode::from_mut(&current)
+            .reg()?
+            .ok_or(FdtError::NotFound)?
+            .next()
+            .ok_or(FdtError::NotFound)?;
+        next = if !serial_info.addrs.contains(&reg.addr) {
+            current.delete_and_next_compatible(name)
+        } else {
+            current.next_compatible(name)
         }
     }
     Ok(())
 }
 
-#[derive(Default, Debug)]
-#[allow(dead_code)] // TODO: remove this
-pub struct SerialInfo {
-    addrs: ArrayVec<[u64; Self::SERIAL_MAX_COUNT]>,
-}
-
-impl SerialInfo {
-    const SERIAL_MAX_COUNT: usize = 4;
-}
-
-fn parse_serial_nodes(fdt: &libfdt::Fdt) -> Result<SerialInfo, RebootReason> {
-    let mut ret: SerialInfo = Default::default();
-    for (i, node) in fdt
-        .compatible_nodes(cstr!("ns16550a"))
-        .map_err(|e| {
-            error!("Failed to read compatible nodes \"ns16550a\" from DT: {e}");
-            RebootReason::InvalidFdt
-        })?
-        .enumerate()
-    {
-        if i >= ret.addrs.capacity() {
-            error!("Too many serials: {i}");
-            return Err(RebootReason::InvalidFdt);
-        }
-        let reg = node
-            .reg()
-            .map_err(|e| {
-                error!("Failed to read reg property from \"ns16550a\" node: {e}");
-                RebootReason::InvalidFdt
-            })?
-            .ok_or_else(|| {
-                error!("No reg property in \"ns16550a\" node");
-                RebootReason::InvalidFdt
-            })?
-            .next()
-            .ok_or_else(|| {
-                error!("No value in reg property of \"ns16550a\" node");
-                RebootReason::InvalidFdt
-            })?;
-        ret.addrs.push(reg.addr);
-    }
-    Ok(ret)
-}
-
 #[derive(Debug)]
-#[allow(dead_code)] // TODO: remove this
-pub struct SwiotlbInfo {
+struct SwiotlbInfo {
     size: u64,
     align: u64,
 }
 
-fn parse_swiotlb_nodes(fdt: &libfdt::Fdt) -> Result<SwiotlbInfo, RebootReason> {
-    let node = fdt
-        .compatible_nodes(cstr!("restricted-dma-pool"))
-        .map_err(|e| {
-            error!("Failed to read compatible nodes \"restricted-dma-pool\" from DT: {e}");
-            RebootReason::InvalidFdt
-        })?
-        .next()
-        .ok_or_else(|| {
-            error!("No compatible node \"restricted-dma-pool\" in DT");
-            RebootReason::InvalidFdt
-        })?;
-    let size = node
-        .getprop_u64(cstr!("size"))
-        .map_err(|e| {
-            error!("Failed to read \"size\" property of \"restricted-dma-pool\": {e}");
-            RebootReason::InvalidFdt
-        })?
-        .ok_or_else(|| {
-            error!("No \"size\" property in \"restricted-dma-pool\"");
-            RebootReason::InvalidFdt
-        })?;
+fn read_swiotlb_info_from(fdt: &Fdt) -> libfdt::Result<SwiotlbInfo> {
+    let node =
+        fdt.compatible_nodes(cstr!("restricted-dma-pool"))?.next().ok_or(FdtError::NotFound)?;
+    let size = node.getprop_u64(cstr!("size"))?.ok_or(FdtError::NotFound)?;
+    let align = node.getprop_u64(cstr!("alignment"))?.ok_or(FdtError::NotFound)?;
+    Ok(SwiotlbInfo { size, align })
+}
 
-    let align = node
-        .getprop_u64(cstr!("alignment"))
-        .map_err(|e| {
-            error!("Failed to read \"alignment\" property of \"restricted-dma-pool\": {e}");
-            RebootReason::InvalidFdt
-        })?
-        .ok_or_else(|| {
-            error!("No \"alignment\" property in \"restricted-dma-pool\"");
-            RebootReason::InvalidFdt
-        })?;
+fn validate_swiotlb_info(swiotlb_info: &SwiotlbInfo) -> Result<(), RebootReason> {
+    let size = swiotlb_info.size;
+    let align = swiotlb_info.align;
 
     if size == 0 || (size % GUEST_PAGE_SIZE as u64) != 0 {
         error!("Invalid swiotlb size {:#x}", size);
@@ -431,15 +415,82 @@
         error!("Invalid swiotlb alignment {:#x}", align);
         return Err(RebootReason::InvalidFdt);
     }
+    Ok(())
+}
 
-    Ok(SwiotlbInfo { size, align })
+fn patch_swiotlb_info(fdt: &mut Fdt, swiotlb_info: &SwiotlbInfo) -> libfdt::Result<()> {
+    let mut node =
+        fdt.root_mut()?.next_compatible(cstr!("restricted-dma-pool"))?.ok_or(FdtError::NotFound)?;
+    node.setprop_inplace(cstr!("size"), &swiotlb_info.size.to_be_bytes())?;
+    node.setprop_inplace(cstr!("alignment"), &swiotlb_info.align.to_be_bytes())?;
+    Ok(())
+}
+
+fn patch_gic(fdt: &mut Fdt, num_cpus: usize) -> libfdt::Result<()> {
+    let node = fdt.compatible_nodes(cstr!("arm,gic-v3"))?.next().ok_or(FdtError::NotFound)?;
+    let mut ranges = node.reg()?.ok_or(FdtError::NotFound)?;
+    let range0 = ranges.next().ok_or(FdtError::NotFound)?;
+    let mut range1 = ranges.next().ok_or(FdtError::NotFound)?;
+
+    let addr = range0.addr;
+    // SAFETY - doesn't overflow. checked in validate_num_cpus
+    let size: u64 =
+        DeviceTreeInfo::GIC_REDIST_SIZE_PER_CPU.checked_mul(num_cpus.try_into().unwrap()).unwrap();
+
+    // range1 is just below range0
+    range1.addr = addr - size;
+    range1.size = Some(size);
+
+    let range0 = range0.to_cells();
+    let range1 = range1.to_cells();
+    let value = [
+        range0.0,          // addr
+        range0.1.unwrap(), //size
+        range1.0,          // addr
+        range1.1.unwrap(), //size
+    ];
+
+    let mut node =
+        fdt.root_mut()?.next_compatible(cstr!("arm,gic-v3"))?.ok_or(FdtError::NotFound)?;
+    node.setprop_inplace(cstr!("reg"), flatten(&value))
+}
+
+fn patch_timer(fdt: &mut Fdt, num_cpus: usize) -> libfdt::Result<()> {
+    const NUM_INTERRUPTS: usize = 4;
+    const CELLS_PER_INTERRUPT: usize = 3;
+    let node = fdt.compatible_nodes(cstr!("arm,armv8-timer"))?.next().ok_or(FdtError::NotFound)?;
+    let interrupts = node.getprop_cells(cstr!("interrupts"))?.ok_or(FdtError::NotFound)?;
+    let mut value: ArrayVec<[u32; NUM_INTERRUPTS * CELLS_PER_INTERRUPT]> =
+        interrupts.take(NUM_INTERRUPTS * CELLS_PER_INTERRUPT).collect();
+
+    let num_cpus: u32 = num_cpus.try_into().unwrap();
+    let cpu_mask: u32 = (((0x1 << num_cpus) - 1) & 0xff) << 8;
+    for v in value.iter_mut().skip(2).step_by(CELLS_PER_INTERRUPT) {
+        *v |= cpu_mask;
+    }
+    for v in value.iter_mut() {
+        *v = v.to_be();
+    }
+
+    // SAFETY - array size is the same
+    let value = unsafe {
+        core::mem::transmute::<
+            [u32; NUM_INTERRUPTS * CELLS_PER_INTERRUPT],
+            [u8; NUM_INTERRUPTS * CELLS_PER_INTERRUPT * size_of::<u32>()],
+        >(value.into_inner())
+    };
+
+    let mut node =
+        fdt.root_mut()?.next_compatible(cstr!("arm,armv8-timer"))?.ok_or(FdtError::NotFound)?;
+    node.setprop_inplace(cstr!("interrupts"), value.as_slice())
 }
 
 #[derive(Debug)]
-#[allow(dead_code)] // TODO: remove this
-struct DeviceTreeInfo {
-    memory_size: NonZeroUsize,
-    num_cpu: NonZeroUsize,
+pub struct DeviceTreeInfo {
+    pub kernel_range: Option<Range<usize>>,
+    pub initrd_range: Option<Range<usize>>,
+    pub memory_range: Range<usize>,
+    num_cpus: usize,
     pci_info: PciInfo,
     serial_info: SerialInfo,
     swiotlb_info: SwiotlbInfo,
@@ -447,27 +498,107 @@
 
 impl DeviceTreeInfo {
     const RAM_BASE_ADDR: u64 = 0x8000_0000;
+    const GIC_REDIST_SIZE_PER_CPU: u64 = (32 * SIZE_4KB) as u64;
 }
 
-pub fn sanitize_device_tree(fdt: &mut libfdt::Fdt) -> Result<(), RebootReason> {
+pub fn sanitize_device_tree(fdt: &mut Fdt) -> Result<DeviceTreeInfo, RebootReason> {
     let info = parse_device_tree(fdt)?;
     debug!("Device tree info: {:?}", info);
 
     // TODO: replace fdt with the template DT
-    // TODO: patch the replaced fdt using info
-    Ok(())
+    patch_device_tree(fdt, &info)?;
+    Ok(info)
 }
 
 fn parse_device_tree(fdt: &libfdt::Fdt) -> Result<DeviceTreeInfo, RebootReason> {
+    let kernel_range = read_kernel_range_from(fdt).map_err(|e| {
+        error!("Failed to read kernel range from DT: {e}");
+        RebootReason::InvalidFdt
+    })?;
+
+    let initrd_range = read_initrd_range_from(fdt).map_err(|e| {
+        error!("Failed to read initrd range from DT: {e}");
+        RebootReason::InvalidFdt
+    })?;
+
+    let memory_range = read_memory_range_from(fdt).map_err(|e| {
+        error!("Failed to read memory range from DT: {e}");
+        RebootReason::InvalidFdt
+    })?;
+    validate_memory_range(&memory_range)?;
+
+    let num_cpus = read_num_cpus_from(fdt).map_err(|e| {
+        error!("Failed to read num cpus from DT: {e}");
+        RebootReason::InvalidFdt
+    })?;
+    validate_num_cpus(num_cpus)?;
+
+    let pci_info = read_pci_info_from(fdt).map_err(|e| {
+        error!("Failed to read pci info from DT: {e}");
+        RebootReason::InvalidFdt
+    })?;
+    validate_pci_info(&pci_info)?;
+
+    let serial_info = read_serial_info_from(fdt).map_err(|e| {
+        error!("Failed to read serial info from DT: {e}");
+        RebootReason::InvalidFdt
+    })?;
+
+    let swiotlb_info = read_swiotlb_info_from(fdt).map_err(|e| {
+        error!("Failed to read swiotlb info from DT: {e}");
+        RebootReason::InvalidFdt
+    })?;
+    validate_swiotlb_info(&swiotlb_info)?;
+
     Ok(DeviceTreeInfo {
-        memory_size: parse_memory_node(fdt)?,
-        num_cpu: parse_cpu_nodes(fdt)?,
-        pci_info: parse_pci_nodes(fdt)?,
-        serial_info: parse_serial_nodes(fdt)?,
-        swiotlb_info: parse_swiotlb_nodes(fdt)?,
+        kernel_range,
+        initrd_range,
+        memory_range,
+        num_cpus,
+        pci_info,
+        serial_info,
+        swiotlb_info,
     })
 }
 
+fn patch_device_tree(fdt: &mut Fdt, info: &DeviceTreeInfo) -> Result<(), RebootReason> {
+    if let Some(initrd_range) = &info.initrd_range {
+        patch_initrd_range(fdt, initrd_range).map_err(|e| {
+            error!("Failed to patch initrd range to DT: {e}");
+            RebootReason::InvalidFdt
+        })?;
+    }
+    patch_memory_range(fdt, &info.memory_range).map_err(|e| {
+        error!("Failed to patch memory range to DT: {e}");
+        RebootReason::InvalidFdt
+    })?;
+    patch_num_cpus(fdt, info.num_cpus).map_err(|e| {
+        error!("Failed to patch cpus to DT: {e}");
+        RebootReason::InvalidFdt
+    })?;
+    patch_pci_info(fdt, &info.pci_info).map_err(|e| {
+        error!("Failed to patch pci info to DT: {e}");
+        RebootReason::InvalidFdt
+    })?;
+    patch_serial_info(fdt, &info.serial_info).map_err(|e| {
+        error!("Failed to patch serial info to DT: {e}");
+        RebootReason::InvalidFdt
+    })?;
+    patch_swiotlb_info(fdt, &info.swiotlb_info).map_err(|e| {
+        error!("Failed to patch swiotlb info to DT: {e}");
+        RebootReason::InvalidFdt
+    })?;
+    patch_gic(fdt, info.num_cpus).map_err(|e| {
+        error!("Failed to patch gic info to DT: {e}");
+        RebootReason::InvalidFdt
+    })?;
+    patch_timer(fdt, info.num_cpus).map_err(|e| {
+        error!("Failed to patch timer info to DT: {e}");
+        RebootReason::InvalidFdt
+    })?;
+    Ok(())
+}
+
 /// Modifies the input DT according to the fields of the configuration.
 pub fn modify_for_next_stage(
     fdt: &mut Fdt,
diff --git a/pvmfw/src/helpers.rs b/pvmfw/src/helpers.rs
index fddd8c3..4df9386 100644
--- a/pvmfw/src/helpers.rs
+++ b/pvmfw/src/helpers.rs
@@ -114,6 +114,15 @@
     flush(reg)
 }
 
+/// Flatten [[T; N]] into &[T]
+/// TODO: use slice::flatten when it graduates from experimental
+pub fn flatten<T, const N: usize>(original: &[[T; N]]) -> &[T] {
+    // SAFETY: no overflow because original (whose size is len()*N) is already in memory
+    let len = original.len() * N;
+    // SAFETY: [T] has the same layout as [T;N]
+    unsafe { core::slice::from_raw_parts(original.as_ptr().cast(), len) }
+}
+
 /// Create &CStr out of &str literal
 #[macro_export]
 macro_rules! cstr {