Refactor DT validation routine

For each category CAT of information that can be extracted from DT,
following routinese are created:

* read_CAT_from(&Fdt) -> libfdt::Result<CAT>: for reading the info
  from DT. At this point, pvmfw-specific validation beyond that is done
by libfdt is NOT performed. Note the result type.

* validate_CAT(&CAT) -> Result<(), RebootReason>: validates the
  extracted information

Also, memory.rs is modified so that parsing the DT is done in one place.

Bug: 249054080
Test: TH
Change-Id: Ib672b23b3fb176b9d06e87ee909be9192ab21664
diff --git a/libs/libfdt/src/iterators.rs b/libs/libfdt/src/iterators.rs
index a7ea0ee..f2afe1a 100644
--- a/libs/libfdt/src/iterators.rs
+++ b/libs/libfdt/src/iterators.rs
@@ -122,7 +122,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,
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..c4a3ae1 100644
--- a/pvmfw/src/fdt.rs
+++ b/pvmfw/src/fdt.rs
@@ -18,7 +18,6 @@
 use crate::helpers::GUEST_PAGE_SIZE;
 use crate::RebootReason;
 use core::ffi::CStr;
-use core::num::NonZeroUsize;
 use core::ops::Range;
 use fdtpci::PciMemoryFlags;
 use fdtpci::PciRangeType;
@@ -30,8 +29,9 @@
 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 +47,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 +62,63 @@
     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
-        })?;
+/// 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)
+}
 
-    let base = memory_range.start;
+/// 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(|| {
+/// 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);
+    }
+    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 +142,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 +230,104 @@
     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(())
 }
 
 #[derive(Default, Debug)]
 #[allow(dead_code)] // TODO: remove this
-pub struct SerialInfo {
-    addrs: ArrayVec<[u64; Self::SERIAL_MAX_COUNT]>,
+struct SerialInfo {
+    addrs: ArrayVec<[u64; Self::MAX_SERIALS]>,
 }
 
 impl SerialInfo {
-    const SERIAL_MAX_COUNT: usize = 4;
+    const MAX_SERIALS: 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);
+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(ret)
+    Ok(SerialInfo { addrs })
 }
 
 #[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 +338,16 @@
         error!("Invalid swiotlb alignment {:#x}", align);
         return Err(RebootReason::InvalidFdt);
     }
-
-    Ok(SwiotlbInfo { size, align })
+    Ok(())
 }
 
 #[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,
@@ -449,22 +357,63 @@
     const RAM_BASE_ADDR: u64 = 0x8000_0000;
 }
 
-pub fn sanitize_device_tree(fdt: &mut libfdt::Fdt) -> Result<(), RebootReason> {
+pub fn sanitize_device_tree(fdt: &mut libfdt::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(())
+    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,
     })
 }