pvmfw: Introduce CpuInfo for DT parsing

Refactor the code to add support for per-vCPU DT information. The struct
is currently empty and will be used by future CLs.

Similarly to the way UARTs are handled, discard extra CPU nodes with a
warning instead of aborting the boot.

Check at build time that gic_patched_size() won't panic when unwrapped.

Test: m pvmfw
Change-Id: I5f985c6afca9fc909d12f61857ab12ca9eee0ecf
diff --git a/pvmfw/src/fdt.rs b/pvmfw/src/fdt.rs
index d2aad61..e0927c8 100644
--- a/pvmfw/src/fdt.rs
+++ b/pvmfw/src/fdt.rs
@@ -41,6 +41,7 @@
 use log::error;
 use log::info;
 use log::warn;
+use static_assertions::const_assert;
 use tinyvec::ArrayVec;
 use vmbase::fdt::SwiotlbInfo;
 use vmbase::layout::{crosvm::MEM_START, MAX_VIRT_ADDR};
@@ -172,33 +173,40 @@
         .setprop_inplace(cstr!("reg"), [addr.to_be(), size.to_be()].as_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())
-}
+#[derive(Debug, Default)]
+struct CpuInfo {}
 
-/// Validate number of CPUs
-fn validate_num_cpus(num_cpus: usize) -> Result<(), FdtValidationError> {
-    if num_cpus == 0 || DeviceTreeInfo::gic_patched_size(num_cpus).is_none() {
-        Err(FdtValidationError::InvalidCpuCount(num_cpus))
-    } else {
-        Ok(())
+fn read_cpu_info_from(fdt: &Fdt) -> libfdt::Result<ArrayVec<[CpuInfo; DeviceTreeInfo::MAX_CPUS]>> {
+    let mut cpus = ArrayVec::new();
+
+    let mut cpu_nodes = fdt.compatible_nodes(cstr!("arm,arm-v8"))?;
+    for _cpu in cpu_nodes.by_ref().take(cpus.capacity()) {
+        let info = CpuInfo {};
+        cpus.push(info);
     }
+    if cpu_nodes.next().is_some() {
+        warn!("DT has more than {} CPU nodes: discarding extra nodes.", cpus.capacity());
+    }
+
+    Ok(cpus)
 }
 
-/// 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);
-        };
+fn validate_cpu_info(cpus: &[CpuInfo]) -> Result<(), FdtValidationError> {
+    if cpus.is_empty() {
+        return Err(FdtValidationError::InvalidCpuCount(0));
+    }
+
+    Ok(())
+}
+
+fn patch_cpus(fdt: &mut Fdt, cpus: &[CpuInfo]) -> libfdt::Result<()> {
+    const COMPAT: &CStr = cstr!("arm,arm-v8");
+    let mut next = fdt.root_mut()?.next_compatible(COMPAT)?;
+    for _cpu in cpus {
+        next = next.ok_or(FdtError::NoSpace)?.next_compatible(COMPAT)?;
     }
     while let Some(current) = next {
-        next = current.delete_and_next_compatible(cpu)?;
+        next = current.delete_and_next_compatible(COMPAT)?;
     }
     Ok(())
 }
@@ -582,7 +590,8 @@
     let mut range1 = ranges.next().ok_or(FdtError::NotFound)?;
 
     let addr = range0.addr;
-    // `validate_num_cpus()` checked that this wouldn't panic
+    // `read_cpu_info_from()` guarantees that we have at most MAX_CPUS.
+    const_assert!(DeviceTreeInfo::gic_patched_size(DeviceTreeInfo::MAX_CPUS).is_some());
     let size = u64::try_from(DeviceTreeInfo::gic_patched_size(num_cpus).unwrap()).unwrap();
 
     // range1 is just below range0
@@ -628,7 +637,7 @@
     pub initrd_range: Option<Range<usize>>,
     pub memory_range: Range<usize>,
     bootargs: Option<CString>,
-    num_cpus: usize,
+    cpus: ArrayVec<[CpuInfo; DeviceTreeInfo::MAX_CPUS]>,
     pci_info: PciInfo,
     serial_info: SerialInfo,
     pub swiotlb_info: SwiotlbInfo,
@@ -637,7 +646,9 @@
 }
 
 impl DeviceTreeInfo {
-    fn gic_patched_size(num_cpus: usize) -> Option<usize> {
+    const MAX_CPUS: usize = 16;
+
+    const fn gic_patched_size(num_cpus: usize) -> Option<usize> {
         const GIC_REDIST_SIZE_PER_CPU: usize = 32 * SIZE_4KB;
 
         GIC_REDIST_SIZE_PER_CPU.checked_mul(num_cpus)
@@ -733,12 +744,12 @@
         RebootReason::InvalidFdt
     })?;
 
-    let num_cpus = read_num_cpus_from(fdt).map_err(|e| {
-        error!("Failed to read num cpus from DT: {e}");
+    let cpus = read_cpu_info_from(fdt).map_err(|e| {
+        error!("Failed to read CPU info from DT: {e}");
         RebootReason::InvalidFdt
     })?;
-    validate_num_cpus(num_cpus).map_err(|e| {
-        error!("Failed to validate num cpus from DT: {e}");
+    validate_cpu_info(&cpus).map_err(|e| {
+        error!("Failed to validate CPU info from DT: {e}");
         RebootReason::InvalidFdt
     })?;
 
@@ -786,7 +797,7 @@
         initrd_range,
         memory_range,
         bootargs,
-        num_cpus,
+        cpus,
         pci_info,
         serial_info,
         swiotlb_info,
@@ -812,7 +823,7 @@
             RebootReason::InvalidFdt
         })?;
     }
-    patch_num_cpus(fdt, info.num_cpus).map_err(|e| {
+    patch_cpus(fdt, &info.cpus).map_err(|e| {
         error!("Failed to patch cpus to DT: {e}");
         RebootReason::InvalidFdt
     })?;
@@ -828,11 +839,11 @@
         error!("Failed to patch swiotlb info to DT: {e}");
         RebootReason::InvalidFdt
     })?;
-    patch_gic(fdt, info.num_cpus).map_err(|e| {
+    patch_gic(fdt, info.cpus.len()).map_err(|e| {
         error!("Failed to patch gic info to DT: {e}");
         RebootReason::InvalidFdt
     })?;
-    patch_timer(fdt, info.num_cpus).map_err(|e| {
+    patch_timer(fdt, info.cpus.len()).map_err(|e| {
         error!("Failed to patch timer info to DT: {e}");
         RebootReason::InvalidFdt
     })?;