pvmfw: Do iommus validation for pkvm-pviommu only

There are VMs not using pkvm-pviommu. For example, some Qualcomm
VMs. In that case, there will be devices using iommus. So the device
node will be having iommus=< >, defined. But it's physical device under
/host node in VM dtbo will not have iommus property. This patch decides
to validate only if the trustable physical node under /host has any iommu.
Otherwise ignore iommus from crosvm and skip validation.

Test: m libpvmfw.device_assignment.test pvmfw
Bug: 380074580
Bug: 385802423
Change-Id: I9ad5d1da9561ec0ae19b0cc6d69d02de2ddffd1f
Signed-off-by: Sreenad Menon <quic_sreemeno@quicinc.com>
diff --git a/guest/pvmfw/src/device_assignment.rs b/guest/pvmfw/src/device_assignment.rs
index fb485fe..efdf4c4 100644
--- a/guest/pvmfw/src/device_assignment.rs
+++ b/guest/pvmfw/src/device_assignment.rs
@@ -373,10 +373,12 @@
         // see: DeviceAssignmentInfo::validate_all_regs().
         let mut all_iommus = BTreeSet::new();
         for physical_device in physical_devices.values() {
-            for iommu in &physical_device.iommus {
-                if !all_iommus.insert(iommu) {
-                    error!("Unsupported phys IOMMU duplication found, <iommus> = {iommu:?}");
-                    return Err(DeviceAssignmentError::UnsupportedIommusDuplication);
+            if let Some(iommus) = &physical_device.iommus {
+                for iommu in iommus {
+                    if !all_iommus.insert(iommu) {
+                        error!("Unsupported phys IOMMU duplication found, <iommus> = {iommu:?}");
+                        return Err(DeviceAssignmentError::UnsupportedIommusDuplication);
+                    }
                 }
             }
         }
@@ -692,17 +694,17 @@
 struct PhysicalDeviceInfo {
     target: Phandle,
     reg: Vec<DeviceReg>,
-    iommus: Vec<(PhysIommu, Sid)>,
+    iommus: Option<Vec<(PhysIommu, Sid)>>,
 }
 
 impl PhysicalDeviceInfo {
     fn parse_iommus(
         node: &FdtNode,
         phys_iommus: &BTreeMap<Phandle, PhysIommu>,
-    ) -> Result<Vec<(PhysIommu, Sid)>> {
+    ) -> Result<Option<Vec<(PhysIommu, Sid)>>> {
         let mut iommus = vec![];
         let Some(mut cells) = node.getprop_cells(c"iommus")? else {
-            return Ok(iommus);
+            return Ok(None);
         };
         while let Some(cell) = cells.next() {
             // Parse pIOMMU ID
@@ -717,7 +719,7 @@
 
             iommus.push((*iommu, Sid::from(cell)));
         }
-        Ok(iommus)
+        Ok(Some(iommus))
     }
 
     fn parse(node: &FdtNode, phys_iommus: &BTreeMap<Phandle, PhysIommu>) -> Result<Option<Self>> {
@@ -742,7 +744,7 @@
     // <interrupts> property from the crosvm DT
     interrupts: Vec<u8>,
     // Parsed <iommus> property from the crosvm DT. Tuple of PvIommu and vSID.
-    iommus: Vec<(PvIommu, Vsid)>,
+    iommus: Option<Vec<(PvIommu, Vsid)>>,
 }
 
 impl AssignedDeviceInfo {
@@ -895,8 +897,16 @@
 
         let interrupts = Self::parse_interrupts(&node)?;
 
-        let iommus = Self::parse_iommus(&node, pviommus)?;
-        Self::validate_iommus(&iommus, &physical_device.iommus, hypervisor)?;
+        // Ignore <iommus> if no pvIOMMUs are expected based on the VM DTBO, possibly
+        // because physical IOMMUs are being assigned directly.
+        let iommus = if let Some(iommus) = &physical_device.iommus {
+            let parsed_iommus = Self::parse_iommus(&node, pviommus)?;
+            Self::validate_iommus(&parsed_iommus, iommus, hypervisor)?;
+            Some(parsed_iommus)
+        } else {
+            // TODO: Detect misconfigured iommus in input DT.
+            None
+        };
 
         Ok(Some(Self { node_path, reg, interrupts, iommus }))
     }
@@ -905,13 +915,16 @@
         let mut dst = fdt.node_mut(&self.node_path)?.unwrap();
         dst.setprop(c"reg", &to_be_bytes(&self.reg))?;
         dst.setprop(c"interrupts", &self.interrupts)?;
-        let mut iommus = Vec::with_capacity(8 * self.iommus.len());
-        for (pviommu, vsid) in &self.iommus {
-            let phandle = pviommu_phandles.get(pviommu).unwrap();
-            iommus.extend_from_slice(&u32::from(*phandle).to_be_bytes());
-            iommus.extend_from_slice(&vsid.0.to_be_bytes());
+
+        if let Some(iommus) = &self.iommus {
+            let mut iommus_vec = Vec::with_capacity(8 * iommus.len());
+            for (pviommu, vsid) in iommus {
+                let phandle = pviommu_phandles.get(pviommu).unwrap();
+                iommus_vec.extend_from_slice(&u32::from(*phandle).to_be_bytes());
+                iommus_vec.extend_from_slice(&vsid.0.to_be_bytes());
+            }
+            dst.setprop(c"iommus", &iommus_vec)?;
         }
-        dst.setprop(c"iommus", &iommus)?;
 
         Ok(())
     }
@@ -946,10 +959,12 @@
     fn validate_pviommu_topology(assigned_devices: &[AssignedDeviceInfo]) -> Result<()> {
         let mut all_iommus = BTreeSet::new();
         for assigned_device in assigned_devices {
-            for iommu in &assigned_device.iommus {
-                if !all_iommus.insert(iommu) {
-                    error!("Unsupported pvIOMMU duplication found, <iommus> = {iommu:?}");
-                    return Err(DeviceAssignmentError::UnsupportedPvIommusDuplication);
+            if let Some(iommus) = &assigned_device.iommus {
+                for iommu in iommus {
+                    if !all_iommus.insert(iommu) {
+                        error!("Unsupported pvIOMMU duplication found, <iommus> = {iommu:?}");
+                        return Err(DeviceAssignmentError::UnsupportedPvIommusDuplication);
+                    }
                 }
             }
         }
@@ -1282,6 +1297,8 @@
 
     // TODO(ptosi): Add tests with varying HYP_GRANULE values.
 
+    // TODO(ptosi): Add tests with iommus.is_none()
+
     #[test]
     fn device_info_new_without_symbols() {
         let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
@@ -1329,7 +1346,7 @@
             node_path: CString::new("/bus0/backlight").unwrap(),
             reg: vec![[0x9, 0xFF].into()],
             interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
-            iommus: vec![],
+            iommus: Some(vec![]),
         }];
 
         assert_eq!(device_info.assigned_devices, expected);
@@ -1354,7 +1371,7 @@
             node_path: CString::new("/rng").unwrap(),
             reg: vec![[0x9, 0xFF].into()],
             interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
-            iommus: vec![(PvIommu { id: 0x4 }, Vsid(0xFF0))],
+            iommus: Some(vec![(PvIommu { id: 0x4 }, Vsid(0xFF0))]),
         }];
 
         assert_eq!(device_info.assigned_devices, expected);