pvmfw: Add irq node to the vcpu stall detector device tree

Specify the interrupt node in the device tree to let the guest
kernel register the PPI for the stall detector.

Bug: 272284118
Test: m pvmfw
Change-Id: Iff5c4bed0036c2fdc810e95ebfdc6c7e1d051271
Signed-off-by: Sebastian Ene <sebastianene@google.com>
diff --git a/pvmfw/src/fdt.rs b/pvmfw/src/fdt.rs
index 84dc14d..11cd5e3 100644
--- a/pvmfw/src/fdt.rs
+++ b/pvmfw/src/fdt.rs
@@ -759,6 +759,83 @@
     Ok(SerialInfo { addrs })
 }
 
+#[derive(Default, Debug, PartialEq)]
+struct WdtInfo {
+    addr: u64,
+    size: u64,
+    irq: [u32; WdtInfo::IRQ_CELLS],
+}
+
+impl WdtInfo {
+    const IRQ_CELLS: usize = 3;
+    const IRQ_NR: u32 = 0xf;
+    const ADDR: u64 = 0x3000;
+    const SIZE: u64 = 0x1000;
+    const GIC_PPI: u32 = 1;
+    const IRQ_TYPE_EDGE_RISING: u32 = 1;
+    const GIC_FDT_IRQ_PPI_CPU_SHIFT: u32 = 8;
+    const GIC_FDT_IRQ_PPI_CPU_MASK: u32 = 0xff << Self::GIC_FDT_IRQ_PPI_CPU_SHIFT;
+
+    const fn get_expected(num_cpus: usize) -> Self {
+        Self {
+            addr: Self::ADDR,
+            size: Self::SIZE,
+            irq: [
+                Self::GIC_PPI,
+                Self::IRQ_NR,
+                ((((1 << num_cpus) - 1) << Self::GIC_FDT_IRQ_PPI_CPU_SHIFT)
+                    & Self::GIC_FDT_IRQ_PPI_CPU_MASK)
+                    | Self::IRQ_TYPE_EDGE_RISING,
+            ],
+        }
+    }
+}
+
+fn read_wdt_info_from(fdt: &Fdt) -> libfdt::Result<WdtInfo> {
+    let mut node_iter = fdt.compatible_nodes(cstr!("qemu,vcpu-stall-detector"))?;
+    let node = node_iter.next().ok_or(FdtError::NotFound)?;
+    let mut ranges = node.reg()?.ok_or(FdtError::NotFound)?;
+
+    let reg = ranges.next().ok_or(FdtError::NotFound)?;
+    let size = reg.size.ok_or(FdtError::NotFound)?;
+    if ranges.next().is_some() {
+        warn!("Discarding extra vmwdt <reg> entries.");
+    }
+
+    let interrupts = node.getprop_cells(cstr!("interrupts"))?.ok_or(FdtError::NotFound)?;
+    let mut chunks = CellChunkIterator::<{ WdtInfo::IRQ_CELLS }>::new(interrupts);
+    let irq = chunks.next().ok_or(FdtError::NotFound)?;
+
+    if chunks.next().is_some() {
+        warn!("Discarding extra vmwdt <interrupts> entries.");
+    }
+
+    Ok(WdtInfo { addr: reg.addr, size, irq })
+}
+
+fn validate_wdt_info(wdt: &WdtInfo, num_cpus: usize) -> Result<(), RebootReason> {
+    if *wdt != WdtInfo::get_expected(num_cpus) {
+        error!("Invalid watchdog timer: {wdt:?}");
+        return Err(RebootReason::InvalidFdt);
+    }
+
+    Ok(())
+}
+
+fn patch_wdt_info(fdt: &mut Fdt, num_cpus: usize) -> libfdt::Result<()> {
+    let mut interrupts = WdtInfo::get_expected(num_cpus).irq;
+    for v in interrupts.iter_mut() {
+        *v = v.to_be();
+    }
+
+    let mut node = fdt
+        .root_mut()
+        .next_compatible(cstr!("qemu,vcpu-stall-detector"))?
+        .ok_or(libfdt::FdtError::NotFound)?;
+    node.setprop_inplace(cstr!("interrupts"), interrupts.as_bytes())?;
+    Ok(())
+}
+
 /// 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");
@@ -863,6 +940,7 @@
 
     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;
     }
@@ -1053,6 +1131,12 @@
     })?;
     validate_pci_info(&pci_info, &memory_range)?;
 
+    let wdt_info = read_wdt_info_from(fdt).map_err(|e| {
+        error!("Failed to read vCPU stall detector info from DT: {e}");
+        RebootReason::InvalidFdt
+    })?;
+    validate_wdt_info(&wdt_info, cpus.len())?;
+
     let serial_info = read_serial_info_from(fdt).map_err(|e| {
         error!("Failed to read serial info from DT: {e}");
         RebootReason::InvalidFdt
@@ -1141,6 +1225,10 @@
         error!("Failed to patch pci info to DT: {e}");
         RebootReason::InvalidFdt
     })?;
+    patch_wdt_info(fdt, info.cpus.len()).map_err(|e| {
+        error!("Failed to patch wdt 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