Merge "Only add vendor_a to microdroid_super if vendor modules flag is disabled" into main
diff --git a/Android.bp b/Android.bp
index 550a6be..9c17c7f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -22,6 +22,7 @@
     module_type: "rust_defaults",
     config_namespace: "ANDROID",
     bool_variables: [
+        "release_avf_enable_device_assignment",
         "release_avf_enable_dice_changes",
         "release_avf_enable_llpvm_changes",
         "release_avf_enable_multi_tenant_microdroid_vm",
@@ -36,6 +37,9 @@
 avf_flag_aware_rust_defaults {
     name: "avf_build_flags_rust",
     soong_config_variables: {
+        release_avf_enable_device_assignment: {
+            cfgs: ["device_assignment"],
+        },
         release_avf_enable_dice_changes: {
             cfgs: ["dice_changes"],
         },
diff --git a/apex/Android.bp b/apex/Android.bp
index b7fd67e..e04dbd2 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -71,8 +71,14 @@
     name: "avf_flag_aware_apex_defaults",
     module_type: "apex_defaults",
     config_namespace: "ANDROID",
-    bool_variables: ["release_avf_enable_vendor_modules"],
-    properties: ["prebuilts"],
+    bool_variables: [
+        "release_avf_enable_device_assignment",
+        "release_avf_enable_vendor_modules",
+    ],
+    properties: [
+        "arch",
+        "prebuilts",
+    ],
 }
 
 avf_flag_aware_apex_defaults {
@@ -87,7 +93,6 @@
         arm64: {
             binaries: [
                 "crosvm",
-                "vfio_handler",
                 "virtmgr",
                 "virtualizationservice",
             ],
@@ -96,7 +101,6 @@
         x86_64: {
             binaries: [
                 "crosvm",
-                "vfio_handler",
                 "virtmgr",
                 "virtualizationservice",
             ],
@@ -124,11 +128,25 @@
         "EmptyPayloadApp",
     ],
     soong_config_variables: {
+        release_avf_enable_device_assignment: {
+            prebuilts: [
+                "com.android.virt.vfio_handler.rc",
+            ],
+            arch: {
+                arm64: {
+                    binaries: ["vfio_handler"],
+                },
+                x86_64: {
+                    binaries: ["vfio_handler"],
+                },
+            },
+        },
         release_avf_enable_vendor_modules: {
             prebuilts: [
                 "microdroid_gki_initrd_debuggable",
                 "microdroid_gki_initrd_normal",
                 "microdroid_gki_kernel",
+                "microdroid_gki.json",
             ],
         },
     },
@@ -158,6 +176,13 @@
     installable: false,
 }
 
+prebuilt_etc {
+    name: "com.android.virt.vfio_handler.rc",
+    src: "vfio_handler.rc",
+    filename: "vfio_handler.rc",
+    installable: false,
+}
+
 sh_binary_host {
     name: "prepare_device_vfio",
     src: "prepare_device_vfio.sh",
diff --git a/apex/vfio_handler.rc b/apex/vfio_handler.rc
new file mode 100644
index 0000000..419acef
--- /dev/null
+++ b/apex/vfio_handler.rc
@@ -0,0 +1,20 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+service vfio_handler /apex/com.android.virt/bin/vfio_handler
+    user root
+    group system
+    interface aidl android.system.virtualizationservice_internal.IVfioHandler
+    disabled
+    oneshot
diff --git a/apex/virtualizationservice.rc b/apex/virtualizationservice.rc
index 8283594..02b2081 100644
--- a/apex/virtualizationservice.rc
+++ b/apex/virtualizationservice.rc
@@ -19,10 +19,3 @@
     interface aidl android.system.virtualizationservice
     disabled
     oneshot
-
-service vfio_handler /apex/com.android.virt/bin/vfio_handler
-    user root
-    group system
-    interface aidl android.system.virtualizationservice_internal.IVfioHandler
-    disabled
-    oneshot
diff --git a/libs/cstr/Android.bp b/libs/cstr/Android.bp
new file mode 100644
index 0000000..4ea87df
--- /dev/null
+++ b/libs/cstr/Android.bp
@@ -0,0 +1,36 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_library_rlib {
+    name: "libcstr",
+    crate_name: "cstr",
+    defaults: ["avf_build_flags_rust"],
+    srcs: ["src/lib.rs"],
+    edition: "2021",
+    host_supported: true,
+    prefer_rlib: true,
+    target: {
+        android: {
+            no_stdlibs: true,
+            stdlibs: [
+                "libcompiler_builtins.rust_sysroot",
+                "libcore.rust_sysroot",
+            ],
+        },
+    },
+    apex_available: [
+        "//apex_available:platform",
+        "//apex_available:anyapex",
+    ],
+}
+
+rust_test {
+    name: "libcstr.tests",
+    crate_name: "libcstr_test",
+    defaults: ["avf_build_flags_rust"],
+    srcs: ["src/lib.rs"],
+    test_suites: ["general-tests"],
+    prefer_rlib: true,
+    rustlibs: ["libcstr"],
+}
diff --git a/libs/cstr/src/lib.rs b/libs/cstr/src/lib.rs
new file mode 100644
index 0000000..ddf20fc
--- /dev/null
+++ b/libs/cstr/src/lib.rs
@@ -0,0 +1,50 @@
+// Copyright 2023, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Provide a safe const-compatible no_std macro for readable &'static CStr.
+
+#![no_std]
+
+/// Create &CStr out of &str literal
+#[macro_export]
+macro_rules! cstr {
+    ($str:literal) => {{
+        const S: &str = concat!($str, "\0");
+        const C: &::core::ffi::CStr = match ::core::ffi::CStr::from_bytes_with_nul(S.as_bytes()) {
+            Ok(v) => v,
+            Err(_) => panic!("string contains interior NUL"),
+        };
+        C
+    }};
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use std::ffi::CString;
+
+    #[test]
+    fn valid_input_string() {
+        let expected = CString::new("aaa").unwrap();
+        assert_eq!(cstr!("aaa"), expected.as_c_str());
+    }
+
+    #[test]
+    fn valid_empty_string() {
+        let expected = CString::new("").unwrap();
+        assert_eq!(cstr!(""), expected.as_c_str());
+    }
+
+    // As cstr!() panics at compile time, tests covering invalid inputs fail to compile!
+}
diff --git a/libs/libfdt/Android.bp b/libs/libfdt/Android.bp
index b889ee5..5920d5d 100644
--- a/libs/libfdt/Android.bp
+++ b/libs/libfdt/Android.bp
@@ -37,6 +37,7 @@
         "libcore.rust_sysroot",
     ],
     rustlibs: [
+        "libcstr",
         "liblibfdt_bindgen",
         "libzerocopy_nostd",
     ],
@@ -61,6 +62,7 @@
     ],
     prefer_rlib: true,
     rustlibs: [
+        "libcstr",
         "liblibfdt",
     ],
 }
diff --git a/libs/libfdt/src/lib.rs b/libs/libfdt/src/lib.rs
index b369390..b513649 100644
--- a/libs/libfdt/src/lib.rs
+++ b/libs/libfdt/src/lib.rs
@@ -31,15 +31,9 @@
 use core::ops::Range;
 use core::ptr;
 use core::result;
+use cstr::cstr;
 use zerocopy::AsBytes as _;
 
-// TODO(b/308694211): Use cstr!() from vmbase
-macro_rules! cstr {
-    ($str:literal) => {{
-        core::ffi::CStr::from_bytes_with_nul(concat!($str, "\0").as_bytes()).unwrap()
-    }};
-}
-
 /// Error type corresponding to libfdt error codes.
 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
 pub enum FdtError {
@@ -526,7 +520,7 @@
 
 /// Phandle of a FDT node
 #[repr(transparent)]
-#[derive(Debug, Copy, Clone, PartialEq)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
 pub struct Phandle(u32);
 
 impl Phandle {
diff --git a/libs/libfdt/tests/api_test.rs b/libs/libfdt/tests/api_test.rs
index d76b1a4..63cbdee 100644
--- a/libs/libfdt/tests/api_test.rs
+++ b/libs/libfdt/tests/api_test.rs
@@ -17,23 +17,12 @@
 //! Integration tests of the library libfdt.
 
 use core::ffi::CStr;
+use cstr::cstr;
 use libfdt::{Fdt, FdtError, FdtNodeMut, Phandle};
 use std::ffi::CString;
 use std::fs;
 use std::ops::Range;
 
-// TODO(b/308694211): Use cstr!() from vmbase
-macro_rules! cstr {
-    ($str:literal) => {{
-        const S: &str = concat!($str, "\0");
-        const C: &::core::ffi::CStr = match ::core::ffi::CStr::from_bytes_with_nul(S.as_bytes()) {
-            Ok(v) => v,
-            Err(_) => panic!("string contains interior NUL"),
-        };
-        C
-    }};
-}
-
 const TEST_TREE_WITH_ONE_MEMORY_RANGE_PATH: &str = "data/test_tree_one_memory_range.dtb";
 const TEST_TREE_WITH_MULTIPLE_MEMORY_RANGES_PATH: &str =
     "data/test_tree_multiple_memory_ranges.dtb";
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index f134eef..c93cb4c 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -418,6 +418,11 @@
 }
 
 prebuilt_etc {
+    name: "microdroid_gki.json",
+    src: "microdroid_gki.json",
+}
+
+prebuilt_etc {
     name: "microdroid_manifest",
     src: "microdroid_manifest.xml",
     filename: "manifest.xml",
diff --git a/microdroid/microdroid_gki.json b/microdroid/microdroid_gki.json
new file mode 100644
index 0000000..d7ba53e
--- /dev/null
+++ b/microdroid/microdroid_gki.json
@@ -0,0 +1,20 @@
+{
+  "kernel": "/apex/com.android.virt/etc/fs/microdroid_gki_kernel",
+  "disks": [
+    {
+      "partitions": [
+        {
+          "label": "vbmeta_a",
+          "path": "/apex/com.android.virt/etc/fs/microdroid_vbmeta.img"
+        },
+        {
+          "label": "super",
+          "path": "/apex/com.android.virt/etc/fs/microdroid_super.img"
+        }
+      ],
+      "writable": false
+    }
+  ],
+  "memory_mib": 256,
+  "platform_version": "~1.0"
+}
diff --git a/pvmfw/Android.bp b/pvmfw/Android.bp
index b7b5900..103619f 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -16,6 +16,7 @@
         "libbssl_ffi_nostd",
         "libciborium_nostd",
         "libciborium_io_nostd",
+        "libcstr",
         "libdiced_open_dice_nostd",
         "libfdtpci",
         "libhyp",
@@ -55,6 +56,7 @@
         unit_test: true,
     },
     rustlibs: [
+        "libcstr",
         "libzeroize",
     ],
 }
@@ -80,6 +82,34 @@
     out: ["test_pvmfw_devices_with_rng.dtb"],
 }
 
+genrule {
+    name: "test_pvmfw_devices_with_rng_iommu",
+    defaults: ["dts_to_dtb"],
+    srcs: ["testdata/test_pvmfw_devices_with_rng_iommu.dts"],
+    out: ["test_pvmfw_devices_with_rng_iommu.dtb"],
+}
+
+genrule {
+    name: "test_pvmfw_devices_with_multiple_devices_iommus",
+    defaults: ["dts_to_dtb"],
+    srcs: ["testdata/test_pvmfw_devices_with_multiple_devices_iommus.dts"],
+    out: ["test_pvmfw_devices_with_multiple_devices_iommus.dtb"],
+}
+
+genrule {
+    name: "test_pvmfw_devices_with_iommu_sharing",
+    defaults: ["dts_to_dtb"],
+    srcs: ["testdata/test_pvmfw_devices_with_iommu_sharing.dts"],
+    out: ["test_pvmfw_devices_with_iommu_sharing.dtb"],
+}
+
+genrule {
+    name: "test_pvmfw_devices_with_iommu_id_conflict",
+    defaults: ["dts_to_dtb"],
+    srcs: ["testdata/test_pvmfw_devices_with_iommu_id_conflict.dts"],
+    out: ["test_pvmfw_devices_with_iommu_id_conflict.dtb"],
+}
+
 rust_test {
     name: "libpvmfw.device_assignment.test",
     srcs: ["src/device_assignment.rs"],
@@ -90,6 +120,7 @@
     },
     prefer_rlib: true,
     rustlibs: [
+        "libcstr",
         "liblibfdt",
         "liblog_rust",
         "libpvmfw_fdt_template",
@@ -98,6 +129,10 @@
         ":test_pvmfw_devices_vm_dtbo",
         ":test_pvmfw_devices_vm_dtbo_without_symbols",
         ":test_pvmfw_devices_with_rng",
+        ":test_pvmfw_devices_with_rng_iommu",
+        ":test_pvmfw_devices_with_multiple_devices_iommus",
+        ":test_pvmfw_devices_with_iommu_sharing",
+        ":test_pvmfw_devices_with_iommu_id_conflict",
     ],
     // To use libpvmfw_fdt_template for testing
     enabled: false,
diff --git a/pvmfw/src/bootargs.rs b/pvmfw/src/bootargs.rs
index a089a67..aacd8e0 100644
--- a/pvmfw/src/bootargs.rs
+++ b/pvmfw/src/bootargs.rs
@@ -108,19 +108,7 @@
 #[cfg(test)]
 mod tests {
     use super::*;
-
-    // TODO(b/308694211): Use cstr!() from vmbase
-    macro_rules! cstr {
-        ($str:literal) => {{
-            const S: &str = concat!($str, "\0");
-            const C: &::core::ffi::CStr = match ::core::ffi::CStr::from_bytes_with_nul(S.as_bytes())
-            {
-                Ok(v) => v,
-                Err(_) => panic!("string contains interior NUL"),
-            };
-            C
-        }};
-    }
+    use cstr::cstr;
 
     fn check(raw: &CStr, expected: Result<&[(&str, Option<&str>)], ()>) {
         let actual = BootArgsIterator::new(raw);
diff --git a/pvmfw/src/crypto.rs b/pvmfw/src/crypto.rs
index 2b3d921..8f31553 100644
--- a/pvmfw/src/crypto.rs
+++ b/pvmfw/src/crypto.rs
@@ -33,7 +33,7 @@
 use bssl_ffi::EVP_aead_aes_256_gcm_randnonce;
 use bssl_ffi::EVP_AEAD;
 use bssl_ffi::EVP_AEAD_CTX;
-use vmbase::cstr;
+use cstr::cstr;
 
 #[derive(Debug)]
 pub struct Error {
diff --git a/pvmfw/src/device_assignment.rs b/pvmfw/src/device_assignment.rs
index a92b418..3f84a8d 100644
--- a/pvmfw/src/device_assignment.rs
+++ b/pvmfw/src/device_assignment.rs
@@ -19,6 +19,7 @@
 #[cfg(test)]
 extern crate alloc;
 
+use alloc::collections::{BTreeMap, BTreeSet};
 use alloc::ffi::CString;
 use alloc::fmt;
 use alloc::vec;
@@ -26,7 +27,7 @@
 use core::ffi::CStr;
 use core::iter::Iterator;
 use core::mem;
-use libfdt::{Fdt, FdtError, FdtNode};
+use libfdt::{Fdt, FdtError, FdtNode, Phandle};
 
 // TODO(b/308694211): Use cstr! from vmbase instead.
 macro_rules! cstr {
@@ -52,8 +53,16 @@
     InvalidSymbols,
     /// Invalid <interrupts>
     InvalidInterrupts,
+    /// Invalid <iommus>
+    InvalidIommus,
+    /// Too many pvIOMMU
+    TooManyPvIommu,
+    /// Duplicated pvIOMMU IDs exist
+    DuplicatedPvIommuIds,
     /// Unsupported overlay target syntax. Only supports <target-path> with full path.
     UnsupportedOverlayTarget,
+    /// Internal error
+    Internal,
     /// Unexpected error from libfdt
     UnexpectedFdtError(FdtError),
 }
@@ -73,9 +82,18 @@
                 "Invalid property in /__symbols__. Must point to valid assignable device node."
             ),
             Self::InvalidInterrupts => write!(f, "Invalid <interrupts>"),
+            Self::InvalidIommus => write!(f, "Invalid <iommus>"),
+            Self::TooManyPvIommu => write!(
+                f,
+                "Too many pvIOMMU node. Insufficient pre-populated pvIOMMUs in platform DT"
+            ),
+            Self::DuplicatedPvIommuIds => {
+                write!(f, "Duplicated pvIOMMU IDs exist. IDs must unique")
+            }
             Self::UnsupportedOverlayTarget => {
                 write!(f, "Unsupported overlay target. Only supports 'target-path = \"/\"'")
             }
+            Self::Internal => write!(f, "Internal error"),
             Self::UnexpectedFdtError(e) => write!(f, "Unexpected Error from libfdt: {e}"),
         }
     }
@@ -169,6 +187,19 @@
     }
 }
 
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
+struct PvIommu {
+    // ID from pvIOMMU node
+    id: u32,
+}
+
+impl PvIommu {
+    fn parse(node: &FdtNode) -> Result<Self> {
+        let id = node.getprop_u32(cstr!("id"))?.ok_or(DeviceAssignmentError::InvalidIommus)?;
+        Ok(Self { id })
+    }
+}
+
 /// Assigned device information parsed from crosvm DT.
 /// Keeps everything in the owned data because underlying FDT will be reused for platform DT.
 #[derive(Debug, Eq, PartialEq)]
@@ -181,6 +212,8 @@
     reg: Vec<u8>,
     // <interrupts> property from the crosvm DT
     interrupts: Vec<u8>,
+    // Parsed <iommus> property from the crosvm DT.
+    iommus: Vec<PvIommu>,
 }
 
 impl AssignedDeviceInfo {
@@ -199,41 +232,91 @@
         Ok(node.getprop(cstr!("interrupts")).unwrap().unwrap().into())
     }
 
-    // TODO(b/277993056): Read and validate iommu
-    fn parse(fdt: &Fdt, vm_dtbo: &VmDtbo, dtbo_node_path: &CStr) -> Result<Option<Self>> {
+    // TODO(b/277993056): Also validate /__local_fixups__ to ensure that <iommus> has phandle.
+    // TODO(b/277993056): Also keep vSID.
+    // TODO(b/277993056): Validate #iommu-cells values.
+    fn parse_iommus(node: &FdtNode, pviommus: &BTreeMap<Phandle, PvIommu>) -> Result<Vec<PvIommu>> {
+        let mut iommus = vec![];
+        let Some(cells) = node.getprop_cells(cstr!("iommus"))? else {
+            return Ok(iommus);
+        };
+        for cell in cells {
+            let phandle = Phandle::try_from(cell).or(Err(DeviceAssignmentError::InvalidIommus))?;
+            let pviommu = pviommus.get(&phandle).ok_or(DeviceAssignmentError::InvalidIommus)?;
+            iommus.push(*pviommu)
+        }
+        Ok(iommus)
+    }
+
+    fn parse(
+        fdt: &Fdt,
+        vm_dtbo: &VmDtbo,
+        dtbo_node_path: &CStr,
+        pviommus: &BTreeMap<Phandle, PvIommu>,
+    ) -> Result<Option<Self>> {
         let node_path = vm_dtbo.locate_overlay_target_path(dtbo_node_path)?;
 
         let Some(node) = fdt.node(&node_path)? else { return Ok(None) };
 
         // TODO(b/277993056): Validate reg with HVC, and keep reg with FdtNode::reg()
         let reg = node.getprop(cstr!("reg")).unwrap().unwrap();
-
         let interrupts = Self::parse_interrupts(&node)?;
-
+        let iommus = Self::parse_iommus(&node, pviommus)?;
         Ok(Some(Self {
             node_path,
             dtbo_node_path: dtbo_node_path.into(),
             reg: reg.to_vec(),
-            interrupts: interrupts.to_vec(),
+            interrupts,
+            iommus,
         }))
     }
 
-    fn patch(&self, fdt: &mut Fdt) -> Result<()> {
+    fn patch(&self, fdt: &mut Fdt, pviommu_phandles: &BTreeMap<PvIommu, Phandle>) -> Result<()> {
         let mut dst = fdt.node_mut(&self.node_path)?.unwrap();
         dst.setprop(cstr!("reg"), &self.reg)?;
         dst.setprop(cstr!("interrupts"), &self.interrupts)?;
-        // TODO(b/277993056): Read and patch iommu
+
+        let iommus: Vec<u8> = self
+            .iommus
+            .iter()
+            .flat_map(|pviommu| {
+                let phandle = pviommu_phandles.get(pviommu).unwrap();
+                u32::from(*phandle).to_be_bytes()
+            })
+            .collect();
+        dst.setprop(cstr!("iommus"), &iommus)?;
+
         Ok(())
     }
 }
 
 #[derive(Debug, Default, Eq, PartialEq)]
 pub struct DeviceAssignmentInfo {
+    pviommus: BTreeSet<PvIommu>,
     assigned_devices: Vec<AssignedDeviceInfo>,
     filtered_dtbo_paths: Vec<CString>,
 }
 
 impl DeviceAssignmentInfo {
+    const PVIOMMU_COMPATIBLE: &CStr = cstr!("pkvm,pviommu");
+
+    /// Parses pvIOMMUs in fdt
+    // Note: This will validate pvIOMMU ids' uniqueness, even when unassigned.
+    fn parse_pviommus(fdt: &Fdt) -> Result<BTreeMap<Phandle, PvIommu>> {
+        // TODO(b/277993056): Validated `<#iommu-cells>`.
+        let mut pviommus = BTreeMap::new();
+        for compatible in fdt.compatible_nodes(Self::PVIOMMU_COMPATIBLE)? {
+            let Some(phandle) = compatible.get_phandle()? else {
+                continue; // Skips unreachable pvIOMMU node
+            };
+            let pviommu = PvIommu::parse(&compatible)?;
+            if pviommus.insert(phandle, pviommu).is_some() {
+                return Err(FdtError::BadPhandle.into());
+            }
+        }
+        Ok(pviommus)
+    }
+
     /// Parses fdt and vm_dtbo, and creates new DeviceAssignmentInfo
     // TODO(b/277993056): Parse __local_fixups__
     // TODO(b/277993056): Parse __fixups__
@@ -244,25 +327,32 @@
             return Ok(None);
         };
 
+        let pviommus = Self::parse_pviommus(fdt)?;
+        let unique_pviommus: BTreeSet<_> = pviommus.values().cloned().collect();
+        if pviommus.len() != unique_pviommus.len() {
+            return Err(DeviceAssignmentError::DuplicatedPvIommuIds);
+        }
+
         let mut assigned_devices = vec![];
         let mut filtered_dtbo_paths = vec![];
         for symbol_prop in symbols_node.properties()? {
             let symbol_prop_value = symbol_prop.value()?;
             let dtbo_node_path = CStr::from_bytes_with_nul(symbol_prop_value)
                 .or(Err(DeviceAssignmentError::InvalidSymbols))?;
-            let assigned_device = AssignedDeviceInfo::parse(fdt, vm_dtbo, dtbo_node_path)?;
+            let assigned_device =
+                AssignedDeviceInfo::parse(fdt, vm_dtbo, dtbo_node_path, &pviommus)?;
             if let Some(assigned_device) = assigned_device {
                 assigned_devices.push(assigned_device);
             } else {
                 filtered_dtbo_paths.push(dtbo_node_path.into());
             }
         }
-        filtered_dtbo_paths.push(CString::new("/__symbols__").unwrap());
-
         if assigned_devices.is_empty() {
             return Ok(None);
         }
-        Ok(Some(Self { assigned_devices, filtered_dtbo_paths }))
+        filtered_dtbo_paths.push(CString::new("/__symbols__").unwrap());
+
+        Ok(Some(Self { pviommus: unique_pviommus, assigned_devices, filtered_dtbo_paths }))
     }
 
     /// Filters VM DTBO to only contain necessary information for booting pVM
@@ -290,16 +380,46 @@
         for assigned_device in &self.assigned_devices {
             let mut node = vm_dtbo.node_mut(&assigned_device.dtbo_node_path).unwrap().unwrap();
             for prop in FILTERED_VM_DTBO_PROP {
-                node.nop_property(prop)?;
+                match node.nop_property(prop) {
+                    Err(FdtError::NotFound) => Ok(()), // allows not exists
+                    other => other,
+                }?;
             }
         }
+
         Ok(())
     }
 
-    pub fn patch(&self, fdt: &mut Fdt) -> Result<()> {
-        for device in &self.assigned_devices {
-            device.patch(fdt)?
+    fn patch_pviommus(&self, fdt: &mut Fdt) -> Result<BTreeMap<PvIommu, Phandle>> {
+        let mut compatible = fdt.root_mut()?.next_compatible(Self::PVIOMMU_COMPATIBLE)?;
+        let mut pviommu_phandles = BTreeMap::new();
+
+        for pviommu in &self.pviommus {
+            let mut node = compatible.ok_or(DeviceAssignmentError::TooManyPvIommu)?;
+            let phandle = node.as_node().get_phandle()?.ok_or(DeviceAssignmentError::Internal)?;
+            node.setprop_inplace(cstr!("id"), &pviommu.id.to_be_bytes())?;
+            if pviommu_phandles.insert(*pviommu, phandle).is_some() {
+                return Err(DeviceAssignmentError::Internal);
+            }
+            compatible = node.next_compatible(Self::PVIOMMU_COMPATIBLE)?;
         }
+
+        // Filters pre-populated but unassigned pvIOMMUs.
+        while let Some(filtered_pviommu) = compatible {
+            compatible = filtered_pviommu.delete_and_next_compatible(Self::PVIOMMU_COMPATIBLE)?;
+        }
+
+        Ok(pviommu_phandles)
+    }
+
+    pub fn patch(&self, fdt: &mut Fdt) -> Result<()> {
+        let pviommu_phandles = self.patch_pviommus(fdt)?;
+
+        // Patches assigned devices
+        for device in &self.assigned_devices {
+            device.patch(fdt, &pviommu_phandles)?;
+        }
+
         Ok(())
     }
 }
@@ -307,12 +427,68 @@
 #[cfg(test)]
 mod tests {
     use super::*;
+    use alloc::collections::BTreeSet;
     use std::fs;
 
     const VM_DTBO_FILE_PATH: &str = "test_pvmfw_devices_vm_dtbo.dtbo";
     const VM_DTBO_WITHOUT_SYMBOLS_FILE_PATH: &str =
         "test_pvmfw_devices_vm_dtbo_without_symbols.dtbo";
     const FDT_FILE_PATH: &str = "test_pvmfw_devices_with_rng.dtb";
+    const FDT_WITH_IOMMU_FILE_PATH: &str = "test_pvmfw_devices_with_rng_iommu.dtb";
+    const FDT_WITH_MULTIPLE_DEVICES_IOMMUS_FILE_PATH: &str =
+        "test_pvmfw_devices_with_multiple_devices_iommus.dtb";
+    const FDT_WITH_IOMMU_SHARING: &str = "test_pvmfw_devices_with_iommu_sharing.dtb";
+    const FDT_WITH_IOMMU_ID_CONFLICT: &str = "test_pvmfw_devices_with_iommu_id_conflict.dtb";
+
+    #[derive(Debug, Eq, PartialEq)]
+    struct AssignedDeviceNode {
+        path: CString,
+        reg: Vec<u8>,
+        interrupts: Vec<u8>,
+        iommus: Vec<u32>, // pvIOMMU ids
+    }
+
+    impl AssignedDeviceNode {
+        fn parse(fdt: &Fdt, path: &CStr) -> Result<Self> {
+            let Some(node) = fdt.node(path)? else {
+                return Err(FdtError::NotFound.into());
+            };
+
+            // TODO(b/277993056): Replace DeviceAssignmentError::Internal
+            let reg = node.getprop(cstr!("reg"))?.ok_or(DeviceAssignmentError::Internal)?;
+            let interrupts = node
+                .getprop(cstr!("interrupts"))?
+                .ok_or(DeviceAssignmentError::InvalidInterrupts)?;
+            let mut iommus = vec![];
+            if let Some(cells) = node.getprop_cells(cstr!("iommus"))? {
+                for cell in cells {
+                    let phandle = Phandle::try_from(cell)?;
+                    let pviommu = fdt
+                        .node_with_phandle(phandle)?
+                        .ok_or(DeviceAssignmentError::InvalidIommus)?;
+                    let compatible = pviommu.getprop_str(cstr!("compatible"));
+                    if compatible != Ok(Some(cstr!("pkvm,pviommu"))) {
+                        return Err(DeviceAssignmentError::InvalidIommus);
+                    }
+                    let id = pviommu
+                        .getprop_u32(cstr!("id"))?
+                        .ok_or(DeviceAssignmentError::InvalidIommus)?;
+                    iommus.push(id);
+                }
+            }
+            Ok(Self { path: path.into(), reg: reg.into(), interrupts: interrupts.into(), iommus })
+        }
+    }
+
+    fn collect_pviommus(fdt: &Fdt) -> Result<Vec<u32>> {
+        let mut pviommus = BTreeSet::new();
+        for pviommu in fdt.compatible_nodes(cstr!("pkvm,pviommu"))? {
+            if let Ok(Some(id)) = pviommu.getprop_u32(cstr!("id")) {
+                pviommus.insert(id);
+            }
+        }
+        Ok(pviommus.iter().cloned().collect())
+    }
 
     fn into_fdt_prop(native_bytes: Vec<u32>) -> Vec<u8> {
         let mut v = Vec::with_capacity(native_bytes.len() * 4);
@@ -347,16 +523,18 @@
             dtbo_node_path: cstr!("/fragment@rng/__overlay__/rng").into(),
             reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
             interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
+            iommus: vec![],
         }];
 
         assert_eq!(device_info.assigned_devices, expected);
     }
 
+    // TODO(b/311655051): Test with real once instead of empty FDT.
     #[test]
-    fn device_info_new_without_assigned_devices() {
-        let mut fdt_data: Vec<u8> = pvmfw_fdt_template::RAW.into();
+    fn device_info_new_with_empty_device_tree() {
+        let mut fdt_data = vec![0; pvmfw_fdt_template::RAW.len()];
         let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
-        let fdt = Fdt::from_mut_slice(fdt_data.as_mut_slice()).unwrap();
+        let fdt = Fdt::create_empty_tree(&mut fdt_data).unwrap();
         let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
 
         let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap();
@@ -403,11 +581,13 @@
         }
         device_info.patch(platform_dt).unwrap();
 
+        // Note: Intentionally not using AssignedDeviceNode for matching all props.
         type FdtResult<T> = libfdt::Result<T>;
         let expected: Vec<(FdtResult<&CStr>, FdtResult<Vec<u8>>)> = vec![
             (Ok(cstr!("android,rng,ignore-gctrl-reset")), Ok(Vec::new())),
             (Ok(cstr!("compatible")), Ok(Vec::from(*b"android,rng\0"))),
             (Ok(cstr!("interrupts")), Ok(into_fdt_prop(vec![0x0, 0xF, 0x4]))),
+            (Ok(cstr!("iommus")), Ok(Vec::new())),
             (Ok(cstr!("reg")), Ok(into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]))),
         ];
 
@@ -425,4 +605,137 @@
 
         assert_eq!(properties, expected);
     }
+
+    #[test]
+    fn device_info_overlay_iommu() {
+        let mut fdt_data = fs::read(FDT_WITH_IOMMU_FILE_PATH).unwrap();
+        let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
+        let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
+        let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
+        let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
+        platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
+        let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
+        platform_dt.unpack().unwrap();
+
+        let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap().unwrap();
+        device_info.filter(vm_dtbo).unwrap();
+
+        // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
+        unsafe {
+            platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
+        }
+        device_info.patch(platform_dt).unwrap();
+
+        let expected = AssignedDeviceNode {
+            path: CString::new("/rng").unwrap(),
+            reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
+            interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
+            iommus: vec![0x4],
+        };
+
+        let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
+        assert_eq!(node, Ok(expected));
+
+        let pviommus = collect_pviommus(platform_dt);
+        assert_eq!(pviommus, Ok(vec![0x4]));
+    }
+
+    #[test]
+    fn device_info_multiple_devices_iommus() {
+        let mut fdt_data = fs::read(FDT_WITH_MULTIPLE_DEVICES_IOMMUS_FILE_PATH).unwrap();
+        let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
+        let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
+        let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
+        let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
+        platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
+        let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
+        platform_dt.unpack().unwrap();
+
+        let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap().unwrap();
+        device_info.filter(vm_dtbo).unwrap();
+
+        // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
+        unsafe {
+            platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
+        }
+        device_info.patch(platform_dt).unwrap();
+
+        let expected_devices = [
+            AssignedDeviceNode {
+                path: CString::new("/rng").unwrap(),
+                reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
+                interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
+                iommus: vec![0x4, 0x9],
+            },
+            AssignedDeviceNode {
+                path: CString::new("/light").unwrap(),
+                reg: into_fdt_prop(vec![0x100, 0x9]),
+                interrupts: into_fdt_prop(vec![0x0, 0xF, 0x5]),
+                iommus: vec![0x40, 0x50, 0x60],
+            },
+        ];
+
+        for expected in expected_devices {
+            let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
+            assert_eq!(node, Ok(expected));
+        }
+        let pviommus = collect_pviommus(platform_dt);
+        assert_eq!(pviommus, Ok(vec![0x4, 0x9, 0x40, 0x50, 0x60]));
+    }
+
+    #[test]
+    fn device_info_iommu_sharing() {
+        let mut fdt_data = fs::read(FDT_WITH_IOMMU_SHARING).unwrap();
+        let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
+        let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
+        let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
+        let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
+        platform_dt_data.resize(pvmfw_fdt_template::RAW.len() * 2, 0);
+        let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
+        platform_dt.unpack().unwrap();
+
+        let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo).unwrap().unwrap();
+        device_info.filter(vm_dtbo).unwrap();
+
+        // SAFETY: Damaged VM DTBO wouldn't be used after this unsafe block.
+        unsafe {
+            platform_dt.apply_overlay(vm_dtbo.as_mut()).unwrap();
+        }
+        device_info.patch(platform_dt).unwrap();
+
+        let expected_devices = [
+            AssignedDeviceNode {
+                path: CString::new("/rng").unwrap(),
+                reg: into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]),
+                interrupts: into_fdt_prop(vec![0x0, 0xF, 0x4]),
+                iommus: vec![0x4, 0x9],
+            },
+            AssignedDeviceNode {
+                path: CString::new("/light").unwrap(),
+                reg: into_fdt_prop(vec![0x100, 0x9]),
+                interrupts: into_fdt_prop(vec![0x0, 0xF, 0x5]),
+                iommus: vec![0x9, 0x40],
+            },
+        ];
+
+        for expected in expected_devices {
+            let node = AssignedDeviceNode::parse(platform_dt, &expected.path);
+            assert_eq!(node, Ok(expected));
+        }
+
+        let pviommus = collect_pviommus(platform_dt);
+        assert_eq!(pviommus, Ok(vec![0x4, 0x9, 0x40]));
+    }
+
+    #[test]
+    fn device_info_iommu_id_conflict() {
+        let mut fdt_data = fs::read(FDT_WITH_IOMMU_ID_CONFLICT).unwrap();
+        let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
+        let fdt = Fdt::from_mut_slice(&mut fdt_data).unwrap();
+        let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
+
+        let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo);
+
+        assert_eq!(device_info, Err(DeviceAssignmentError::DuplicatedPvIommuIds));
+    }
 }
diff --git a/pvmfw/src/dice.rs b/pvmfw/src/dice.rs
index cc31f34..112c24c 100644
--- a/pvmfw/src/dice.rs
+++ b/pvmfw/src/dice.rs
@@ -17,12 +17,12 @@
 use core::ffi::c_void;
 use core::mem::size_of;
 use core::slice;
+use cstr::cstr;
 use diced_open_dice::{
     bcc_format_config_descriptor, bcc_handover_main_flow, hash, Config, DiceConfigValues, DiceMode,
     Hash, InputValues, HIDDEN_SIZE,
 };
 use pvmfw_avb::{DebugLevel, Digest, VerifiedBootData};
-use vmbase::cstr;
 use vmbase::memory::flushed_zeroize;
 
 fn to_dice_mode(debug_level: DebugLevel) -> DiceMode {
diff --git a/pvmfw/src/fdt.rs b/pvmfw/src/fdt.rs
index 7655614..4fe2c34 100644
--- a/pvmfw/src/fdt.rs
+++ b/pvmfw/src/fdt.rs
@@ -28,6 +28,7 @@
 use core::fmt;
 use core::mem::size_of;
 use core::ops::Range;
+use cstr::cstr;
 use fdtpci::PciMemoryFlags;
 use fdtpci::PciRangeType;
 use libfdt::AddressRange;
@@ -41,7 +42,6 @@
 use log::info;
 use log::warn;
 use tinyvec::ArrayVec;
-use vmbase::cstr;
 use vmbase::fdt::SwiotlbInfo;
 use vmbase::layout::{crosvm::MEM_START, MAX_VIRT_ADDR};
 use vmbase::memory::SIZE_4KB;
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_iommu_id_conflict.dts b/pvmfw/testdata/test_pvmfw_devices_with_iommu_id_conflict.dts
new file mode 100644
index 0000000..f0a7162
--- /dev/null
+++ b/pvmfw/testdata/test_pvmfw_devices_with_iommu_id_conflict.dts
@@ -0,0 +1,84 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+	chosen {
+		stdout-path = "/uart@3f8";
+		linux,pci-probe-only = <1>;
+	};
+
+	memory {
+		device_type = "memory";
+		reg = <0x00 0x80000000 0xFFFFFFFF>;
+	};
+
+	reserved-memory {
+		#address-cells = <2>;
+		#size-cells = <2>;
+		ranges;
+		swiotlb: restricted_dma_reserved {
+			compatible = "restricted-dma-pool";
+			reg = <0xFFFFFFFF>;
+			size = <0xFFFFFFFF>;
+			alignment = <0xFFFFFFFF>;
+		};
+
+		dice {
+			compatible = "google,open-dice";
+			no-map;
+			reg = <0xFFFFFFFF>;
+		};
+	};
+
+	cpus {
+		#address-cells = <1>;
+		#size-cells = <0>;
+		cpu@0 {
+			device_type = "cpu";
+		};
+		cpu@1 {
+			device_type = "cpu";
+		    reg = <0x00 0x80000000 0xFFFFFFFF>;
+		};
+    };
+
+    rng@90000000 {
+        compatible = "android,rng";
+        reg = <0x0 0x9 0x0 0xFF>;
+        interrupts = <0x0 0xF 0x4>;
+        google,eh,ignore-gctrl-reset;
+        status = "okay";
+        iommus = <&pviommu_0>, <&pviommu_1>;
+    };
+
+    pviommu_0: pviommu0 {
+        compatible = "pkvm,pviommu";
+        id = <0x4>;
+        #iommu-cells = <0>;
+    };
+
+    pviommu_1: pviommu1 {
+        compatible = "pkvm,pviommu";
+        id = <0x9>;
+        #iommu-cells = <0>;
+    };
+
+    light@70000000 {
+        compatible = "android,light";
+        reg = <0x100 0x9>;
+        interrupts = <0x0 0xF 0x5>;
+        iommus = <&pviommu_0>, <&pviommu_a>, <&pviommu_b>;
+    };
+
+    pviommu_a: pviommua {
+        compatible = "pkvm,pviommu";
+        id = <0x40>;
+        #iommu-cells = <0>;
+    };
+
+    pviommu_b: pviommub {
+        compatible = "pkvm,pviommu";
+        id = <0x9>;
+        #iommu-cells = <0>;
+    };
+};
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_iommu_sharing.dts b/pvmfw/testdata/test_pvmfw_devices_with_iommu_sharing.dts
new file mode 100644
index 0000000..d6952fa
--- /dev/null
+++ b/pvmfw/testdata/test_pvmfw_devices_with_iommu_sharing.dts
@@ -0,0 +1,78 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+	chosen {
+		stdout-path = "/uart@3f8";
+		linux,pci-probe-only = <1>;
+	};
+
+	memory {
+		device_type = "memory";
+		reg = <0x00 0x80000000 0xFFFFFFFF>;
+	};
+
+	reserved-memory {
+		#address-cells = <2>;
+		#size-cells = <2>;
+		ranges;
+		swiotlb: restricted_dma_reserved {
+			compatible = "restricted-dma-pool";
+			reg = <0xFFFFFFFF>;
+			size = <0xFFFFFFFF>;
+			alignment = <0xFFFFFFFF>;
+		};
+
+		dice {
+			compatible = "google,open-dice";
+			no-map;
+			reg = <0xFFFFFFFF>;
+		};
+	};
+
+	cpus {
+		#address-cells = <1>;
+		#size-cells = <0>;
+		cpu@0 {
+			device_type = "cpu";
+		};
+		cpu@1 {
+			device_type = "cpu";
+		    reg = <0x00 0x80000000 0xFFFFFFFF>;
+		};
+    };
+
+    rng@90000000 {
+        compatible = "android,rng";
+        reg = <0x0 0x9 0x0 0xFF>;
+        interrupts = <0x0 0xF 0x4>;
+        google,eh,ignore-gctrl-reset;
+        status = "okay";
+        iommus = <&pviommu_0>, <&pviommu_1>;
+    };
+
+    light@70000000 {
+        compatible = "android,light";
+        reg = <0x100 0x9>;
+        interrupts = <0x0 0xF 0x5>;
+        iommus = <&pviommu_1>, <&pviommu_a>;
+    };
+
+    pviommu_0: pviommu0 {
+        compatible = "pkvm,pviommu";
+        id = <0x4>;
+        #iommu-cells = <0>;
+    };
+
+    pviommu_1: pviommu1 {
+        compatible = "pkvm,pviommu";
+        id = <0x9>;
+        #iommu-cells = <0>;
+    };
+
+    pviommu_a: pviommua {
+        compatible = "pkvm,pviommu";
+        id = <0x40>;
+        #iommu-cells = <0>;
+    };
+};
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_multiple_devices_iommus.dts b/pvmfw/testdata/test_pvmfw_devices_with_multiple_devices_iommus.dts
new file mode 100644
index 0000000..2609c45
--- /dev/null
+++ b/pvmfw/testdata/test_pvmfw_devices_with_multiple_devices_iommus.dts
@@ -0,0 +1,90 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+	chosen {
+		stdout-path = "/uart@3f8";
+		linux,pci-probe-only = <1>;
+	};
+
+	memory {
+		device_type = "memory";
+		reg = <0x00 0x80000000 0xFFFFFFFF>;
+	};
+
+	reserved-memory {
+		#address-cells = <2>;
+		#size-cells = <2>;
+		ranges;
+		swiotlb: restricted_dma_reserved {
+			compatible = "restricted-dma-pool";
+			reg = <0xFFFFFFFF>;
+			size = <0xFFFFFFFF>;
+			alignment = <0xFFFFFFFF>;
+		};
+
+		dice {
+			compatible = "google,open-dice";
+			no-map;
+			reg = <0xFFFFFFFF>;
+		};
+	};
+
+	cpus {
+		#address-cells = <1>;
+		#size-cells = <0>;
+		cpu@0 {
+			device_type = "cpu";
+		};
+		cpu@1 {
+			device_type = "cpu";
+		    reg = <0x00 0x80000000 0xFFFFFFFF>;
+		};
+    };
+
+    rng@90000000 {
+        compatible = "android,rng";
+        reg = <0x0 0x9 0x0 0xFF>;
+        interrupts = <0x0 0xF 0x4>;
+        google,eh,ignore-gctrl-reset;
+        status = "okay";
+        iommus = <&pviommu_0>, <&pviommu_1>;
+    };
+
+    pviommu_0: pviommu0 {
+        compatible = "pkvm,pviommu";
+        id = <0x4>;
+        #iommu-cells = <0>;
+    };
+
+    pviommu_1: pviommu1 {
+        compatible = "pkvm,pviommu";
+        id = <0x9>;
+        #iommu-cells = <0>;
+    };
+
+    light@70000000 {
+        compatible = "android,light";
+        reg = <0x100 0x9>;
+        interrupts = <0x0 0xF 0x5>;
+        iommus = <&pviommu_a>, <&pviommu_b>, <&pviommu_c>;
+    };
+
+    pviommu_a: pviommua {
+        compatible = "pkvm,pviommu";
+        id = <0x40>;
+        #iommu-cells = <0>;
+    };
+
+    pviommu_b: pviommub {
+        compatible = "pkvm,pviommu";
+        id = <0x50>;
+        #iommu-cells = <0>;
+    };
+
+    pviommu_c: pviommuc {
+        compatible = "pkvm,pviommu";
+        id = <0x60>;
+        #iommu-cells = <0>;
+    };
+};
diff --git a/pvmfw/testdata/test_pvmfw_devices_with_rng_iommu.dts b/pvmfw/testdata/test_pvmfw_devices_with_rng_iommu.dts
new file mode 100644
index 0000000..6a5068c
--- /dev/null
+++ b/pvmfw/testdata/test_pvmfw_devices_with_rng_iommu.dts
@@ -0,0 +1,59 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+	chosen {
+		stdout-path = "/uart@3f8";
+		linux,pci-probe-only = <1>;
+	};
+
+	memory {
+		device_type = "memory";
+		reg = <0x00 0x80000000 0xFFFFFFFF>;
+	};
+
+	reserved-memory {
+		#address-cells = <2>;
+		#size-cells = <2>;
+		ranges;
+		swiotlb: restricted_dma_reserved {
+			compatible = "restricted-dma-pool";
+			reg = <0xFFFFFFFF>;
+			size = <0xFFFFFFFF>;
+			alignment = <0xFFFFFFFF>;
+		};
+
+		dice {
+			compatible = "google,open-dice";
+			no-map;
+			reg = <0xFFFFFFFF>;
+		};
+	};
+
+	cpus {
+		#address-cells = <1>;
+		#size-cells = <0>;
+		cpu@0 {
+			device_type = "cpu";
+		};
+		cpu@1 {
+			device_type = "cpu";
+		    reg = <0x00 0x80000000 0xFFFFFFFF>;
+		};
+    };
+
+    rng@90000000 {
+        compatible = "android,rng";
+        reg = <0x0 0x9 0x0 0xFF>;
+        interrupts = <0x0 0xF 0x4>;
+        google,eh,ignore-gctrl-reset;
+        status = "okay";
+        iommus = <&pviommu_0>;
+    };
+
+    pviommu_0: pviommu0 {
+        compatible = "pkvm,pviommu";
+        id = <0x4>;
+        #iommu-cells = <0>;
+    };
+};
diff --git a/rialto/Android.bp b/rialto/Android.bp
index 28c261e..728c1eb 100644
--- a/rialto/Android.bp
+++ b/rialto/Android.bp
@@ -13,6 +13,7 @@
         "libbssl_ffi_nostd",
         "libciborium_io_nostd",
         "libciborium_nostd",
+        "libcstr",
         "libdiced_open_dice_nostd",
         "libdiced_sample_inputs_nostd",
         "libhyp",
diff --git a/rialto/src/fdt.rs b/rialto/src/fdt.rs
index 8bb40c3..09cdd36 100644
--- a/rialto/src/fdt.rs
+++ b/rialto/src/fdt.rs
@@ -15,8 +15,8 @@
 //! High-level FDT functions.
 
 use core::ops::Range;
+use cstr::cstr;
 use libfdt::{Fdt, FdtError};
-use vmbase::cstr;
 
 /// Reads the DICE data range from the given `fdt`.
 pub fn read_dice_range_from(fdt: &Fdt) -> libfdt::Result<Range<usize>> {
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index c6a30aa..6ae3bbd 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -101,6 +101,8 @@
 
 const MICRODROID_OS_NAME: &str = "microdroid";
 
+const MICRODROID_GKI_OS_NAME: &str = "microdroid_gki";
+
 const UNFORMATTED_STORAGE_MAGIC: &str = "UNFORMATTED-STORAGE";
 
 /// Roughly estimated sufficient size for storing vendor public key into DTBO.
@@ -694,6 +696,16 @@
     }
 }
 
+fn is_valid_os(os_name: &str) -> bool {
+    if os_name == MICRODROID_OS_NAME {
+        return true;
+    }
+    if cfg!(vendor_modules) && os_name == MICRODROID_GKI_OS_NAME {
+        return true;
+    }
+    false
+}
+
 fn load_app_config(
     config: &VirtualMachineAppConfig,
     debug_config: &DebugConfig,
@@ -717,9 +729,9 @@
         Payload::PayloadConfig(payload_config) => create_vm_payload_config(payload_config)?,
     };
 
-    // For now, the only supported OS is Microdroid
+    // For now, the only supported OS is Microdroid and Microdroid GKI
     let os_name = vm_payload_config.os.name.as_str();
-    if os_name != MICRODROID_OS_NAME {
+    if !is_valid_os(os_name) {
         bail!("Unknown OS \"{}\"", os_name);
     }
 
@@ -753,7 +765,7 @@
     vm_config.cpuTopology = config.cpuTopology;
 
     // Microdroid takes additional init ramdisk & (optionally) storage image
-    add_microdroid_system_images(config, instance_file, storage_image, &mut vm_config)?;
+    add_microdroid_system_images(config, instance_file, storage_image, os_name, &mut vm_config)?;
 
     // Include Microdroid payload disk (contains apks, idsigs) in vm config
     add_microdroid_payload_images(
@@ -788,8 +800,9 @@
     }
 
     let task = Task { type_: TaskType::MicrodroidLauncher, command: payload_binary_name.clone() };
+    let name = payload_config.osName.clone();
     Ok(VmPayloadConfig {
-        os: OsConfig { name: MICRODROID_OS_NAME.to_owned() },
+        os: OsConfig { name },
         task: Some(task),
         apexes: vec![],
         extra_apks: vec![],
@@ -871,7 +884,7 @@
 /// Check that a file SELinux label is acceptable.
 ///
 /// We only want to allow code in a VM to be sourced from places that apps, and the
-/// system, do not have write access to.
+/// system or vendor, do not have write access to.
 ///
 /// Note that sepolicy must also grant read access for these types to both virtualization
 /// service and crosvm.
@@ -885,6 +898,7 @@
         | "staging_data_file" // updated/staged APEX images
         | "system_file" // immutable dm-verity protected partition
         | "virtualizationservice_data_file" // files created by VS / VirtMgr
+        | "vendor_microdroid_file" // immutable dm-verity protected partition (/vendor/etc/avf/microdroid/.*)
          => Ok(()),
         _ => bail!("Label {} is not allowed", context),
     }
@@ -1195,10 +1209,24 @@
     Ok(())
 }
 
+fn check_no_devices(config: &VirtualMachineConfig) -> binder::Result<()> {
+    let VirtualMachineConfig::AppConfig(config) = config else { return Ok(()) };
+    if let Some(custom_config) = &config.customConfig {
+        if !custom_config.devices.is_empty() {
+            return Err(anyhow!("device assignment feature is disabled"))
+                .or_binder_exception(ExceptionCode::UNSUPPORTED_OPERATION);
+        }
+    }
+    Ok(())
+}
+
 fn check_config_features(config: &VirtualMachineConfig) -> binder::Result<()> {
     if !cfg!(vendor_modules) {
         check_no_vendor_modules(config)?;
     }
+    if !cfg!(device_assignment) {
+        check_no_devices(config)?;
+    }
     Ok(())
 }
 
@@ -1332,7 +1360,7 @@
     }
 
     fn requestAttestation(&self, csr: &[u8]) -> binder::Result<Vec<Certificate>> {
-        GLOBAL_SERVICE.requestAttestation(csr)
+        GLOBAL_SERVICE.requestAttestation(csr, get_calling_uid() as i32)
     }
 }
 
diff --git a/virtualizationmanager/src/payload.rs b/virtualizationmanager/src/payload.rs
index 3bfad33..c19c103 100644
--- a/virtualizationmanager/src/payload.rs
+++ b/virtualizationmanager/src/payload.rs
@@ -425,6 +425,7 @@
     config: &VirtualMachineAppConfig,
     instance_file: File,
     storage_image: Option<File>,
+    os_name: &str,
     vm_config: &mut VirtualMachineRawConfig,
 ) -> Result<()> {
     let debug_suffix = match config.debugLevel {
@@ -432,7 +433,7 @@
         DebugLevel::FULL => "debuggable",
         _ => return Err(anyhow!("unsupported debug level: {:?}", config.debugLevel)),
     };
-    let initrd = format!("/apex/com.android.virt/etc/microdroid_initrd_{}.img", debug_suffix);
+    let initrd = format!("/apex/com.android.virt/etc/{os_name}_initrd_{debug_suffix}.img");
     vm_config.initrd = Some(open_parcel_file(Path::new(&initrd), false)?);
 
     let mut writable_partitions = vec![Partition {
diff --git a/virtualizationservice/Android.bp b/virtualizationservice/Android.bp
index c00445d..bef7dd0 100644
--- a/virtualizationservice/Android.bp
+++ b/virtualizationservice/Android.bp
@@ -34,6 +34,7 @@
         "liblibc",
         "liblog_rust",
         "libnix",
+        "librkpd_client",
         "librustutils",
         "libvmclient",
         "libstatslog_virtualization_rust",
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachinePayloadConfig.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachinePayloadConfig.aidl
index 55c2f5d..f3f54f3 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachinePayloadConfig.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachinePayloadConfig.aidl
@@ -23,4 +23,9 @@
      * function invoked.
      */
     @utf8InCpp String payloadBinaryName;
+
+    /**
+     * Name of the OS to run the payload. Currently "microdroid" and "microdroid_gki" is supported.
+     */
+    @utf8InCpp String osName = "microdroid";
 }
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
index 2592135..c384a6f 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
@@ -60,10 +60,14 @@
      * Requests a certificate chain for the provided certificate signing request (CSR).
      *
      * @param csr The certificate signing request.
+     * @param requesterUid The UID of the app that requests remote attestation. The client VM to be
+     *                     attested is owned by this app.
+     *                     The uniqueness of the UID ensures that no two VMs owned by different apps
+     *                     are able to correlate keys.
      * @return A sequence of DER-encoded X.509 certificates that make up the attestation
      *         key's certificate chain. The attestation key is provided in the CSR.
      */
-    Certificate[] requestAttestation(in byte[] csr);
+    Certificate[] requestAttestation(in byte[] csr, int requesterUid);
 
     /**
      * Get a list of assignable devices.
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 2be2b19..938225e 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -14,7 +14,7 @@
 
 //! Implementation of the AIDL interface of the VirtualizationService.
 
-use crate::{get_calling_pid, get_calling_uid};
+use crate::{get_calling_pid, get_calling_uid, REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME};
 use crate::atom::{forward_vm_booted_atom, forward_vm_creation_atom, forward_vm_exited_atom};
 use crate::rkpvm::request_attestation;
 use android_os_permissions_aidl::aidl::android::os::IPermissionController;
@@ -39,6 +39,7 @@
 use binder::{self, wait_for_interface, BinderFeatures, ExceptionCode, Interface, LazyServiceGuard, Status, Strong, IntoBinderResult};
 use libc::VMADDR_CID_HOST;
 use log::{error, info, warn};
+use rkpd_client::get_rkpd_attestation_key;
 use rustutils::system_properties;
 use serde::Deserialize;
 use std::collections::{HashMap, HashSet};
@@ -159,14 +160,31 @@
         Ok(cids)
     }
 
-    fn requestAttestation(&self, csr: &[u8]) -> binder::Result<Vec<Certificate>> {
+    fn requestAttestation(
+        &self,
+        csr: &[u8],
+        requester_uid: i32,
+    ) -> binder::Result<Vec<Certificate>> {
         check_manage_access()?;
         info!("Received csr. Requestting attestation...");
         if cfg!(remote_attestation) {
-            request_attestation(csr)
+            let attestation_key = get_rkpd_attestation_key(
+                REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME,
+                requester_uid as u32,
+            )
+            .context("Failed to retrieve the remotely provisioned keys")
+            .with_log()
+            .or_service_specific_exception(-1)?;
+            let certificate = request_attestation(csr, &attestation_key.keyBlob)
                 .context("Failed to request attestation")
                 .with_log()
-                .or_service_specific_exception(-1)
+                .or_service_specific_exception(-1)?;
+            // TODO(b/309780089): Parse the remotely provisioned certificate chain into
+            // individual certificates.
+            let mut certificate_chain =
+                vec![Certificate { encodedCertificate: attestation_key.encodedCertChain }];
+            certificate_chain.push(Certificate { encodedCertificate: certificate });
+            Ok(certificate_chain)
         } else {
             Err(Status::new_exception_str(
                 ExceptionCode::UNSUPPORTED_OPERATION,
diff --git a/virtualizationservice/src/main.rs b/virtualizationservice/src/main.rs
index fd668bc..d80ddd4 100644
--- a/virtualizationservice/src/main.rs
+++ b/virtualizationservice/src/main.rs
@@ -33,7 +33,7 @@
 use std::path::Path;
 
 const LOG_TAG: &str = "VirtualizationService";
-const _REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME: &str =
+pub(crate) const REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME: &str =
     "android.system.virtualization.IRemotelyProvisionedComponent/avf";
 
 fn get_calling_pid() -> pid_t {
diff --git a/virtualizationservice/src/rkpvm.rs b/virtualizationservice/src/rkpvm.rs
index 8f1de6b..5087120 100644
--- a/virtualizationservice/src/rkpvm.rs
+++ b/virtualizationservice/src/rkpvm.rs
@@ -17,23 +17,25 @@
 //! serves as a trusted platform to attest a client VM.
 
 use android_hardware_security_rkp::aidl::android::hardware::security::keymint::MacedPublicKey::MacedPublicKey;
-use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::Certificate::Certificate;
 use anyhow::{bail, Context, Result};
-use service_vm_comm::{GenerateCertificateRequestParams, Request, Response};
+use service_vm_comm::{
+    ClientVmAttestationParams, GenerateCertificateRequestParams, Request, Response,
+};
 use service_vm_manager::ServiceVm;
 
-pub(crate) fn request_attestation(csr: &[u8]) -> Result<Vec<Certificate>> {
+pub(crate) fn request_attestation(
+    csr: &[u8],
+    remotely_provisioned_keyblob: &[u8],
+) -> Result<Vec<u8>> {
     let mut vm = ServiceVm::start()?;
 
-    // TODO(b/271275206): Send the correct request type with client VM's
-    // information to be attested.
-    let request = Request::Reverse(csr.to_vec());
+    let params = ClientVmAttestationParams {
+        csr: csr.to_vec(),
+        remotely_provisioned_key_blob: remotely_provisioned_keyblob.to_vec(),
+    };
+    let request = Request::RequestClientVmAttestation(params);
     match vm.process_request(request).context("Failed to process request")? {
-        // TODO(b/271275206): Adjust the response type.
-        Response::Reverse(cert) => {
-            let cert = Certificate { encodedCertificate: cert };
-            Ok(vec![cert])
-        }
+        Response::RequestClientVmAttestation(cert) => Ok(cert),
         _ => bail!("Incorrect response type"),
     }
 }
diff --git a/vm/src/main.rs b/vm/src/main.rs
index 0af9791..87278bc 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -97,33 +97,24 @@
     #[arg(long)]
     storage_size: Option<u64>,
 
-    /// Path to custom kernel image to use when booting Microdroid.
-    #[cfg(vendor_modules)]
-    #[arg(long)]
-    kernel: Option<PathBuf>,
-
     /// Path to disk image containing vendor-specific modules.
     #[cfg(vendor_modules)]
     #[arg(long)]
     vendor: Option<PathBuf>,
 
     /// SysFS nodes of devices to assign to VM
+    #[cfg(device_assignment)]
     #[arg(long)]
     devices: Vec<PathBuf>,
+
+    /// If set, use GKI instead of microdroid kernel
+    #[cfg(vendor_modules)]
+    #[arg(long)]
+    gki: bool,
 }
 
 impl MicrodroidConfig {
     #[cfg(vendor_modules)]
-    fn kernel(&self) -> &Option<PathBuf> {
-        &self.kernel
-    }
-
-    #[cfg(not(vendor_modules))]
-    fn kernel(&self) -> Option<PathBuf> {
-        None
-    }
-
-    #[cfg(vendor_modules)]
     fn vendor(&self) -> &Option<PathBuf> {
         &self.vendor
     }
@@ -132,6 +123,26 @@
     fn vendor(&self) -> Option<PathBuf> {
         None
     }
+
+    #[cfg(vendor_modules)]
+    fn gki(&self) -> bool {
+        self.gki
+    }
+
+    #[cfg(not(vendor_modules))]
+    fn gki(&self) -> bool {
+        false
+    }
+
+    #[cfg(device_assignment)]
+    fn devices(&self) -> &Vec<PathBuf> {
+        &self.devices
+    }
+
+    #[cfg(not(device_assignment))]
+    fn devices(&self) -> Vec<PathBuf> {
+        Vec::new()
+    }
 }
 
 #[derive(Args)]
diff --git a/vm/src/run.rs b/vm/src/run.rs
index 1ba9dec..44ba9af 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -98,9 +98,6 @@
         None
     };
 
-    let kernel =
-        config.microdroid.kernel().as_ref().map(|p| open_parcel_file(p, false)).transpose()?;
-
     let vendor =
         config.microdroid.vendor().as_ref().map(|p| open_parcel_file(p, false)).transpose()?;
 
@@ -114,8 +111,11 @@
         }
         Payload::ConfigPath(config_path)
     } else if let Some(payload_binary_name) = config.payload_binary_name {
+        let os_name =
+            if config.microdroid.gki() { "microdroid_gki" } else { "microdroid" }.to_owned();
         Payload::PayloadConfig(VirtualMachinePayloadConfig {
             payloadBinaryName: payload_binary_name,
+            osName: os_name,
         })
     } else {
         bail!("Either --config-path or --payload-binary-name must be defined")
@@ -124,13 +124,13 @@
     let payload_config_str = format!("{:?}!{:?}", config.apk, payload);
 
     let custom_config = CustomConfig {
-        customKernelImage: kernel,
+        customKernelImage: None,
         gdbPort: config.debug.gdb.map(u16::from).unwrap_or(0) as i32, // 0 means no gdb
         taskProfiles: config.common.task_profiles,
         vendorImage: vendor,
         devices: config
             .microdroid
-            .devices
+            .devices()
             .iter()
             .map(|x| {
                 x.to_str().map(String::from).ok_or(anyhow!("Failed to convert {x:?} to String"))
diff --git a/vmbase/Android.bp b/vmbase/Android.bp
index b2b1549..e682773 100644
--- a/vmbase/Android.bp
+++ b/vmbase/Android.bp
@@ -76,6 +76,7 @@
     rustlibs: [
         "libaarch64_paging",
         "libbuddy_system_allocator",
+        "libcstr",
         "libfdtpci",
         "libhyp",
         "liblibfdt",
diff --git a/vmbase/example/Android.bp b/vmbase/example/Android.bp
index ae1a593..fe9de44 100644
--- a/vmbase/example/Android.bp
+++ b/vmbase/example/Android.bp
@@ -9,6 +9,7 @@
     srcs: ["src/main.rs"],
     rustlibs: [
         "libaarch64_paging",
+        "libcstr",
         "libdiced_open_dice_nostd",
         "libfdtpci",
         "liblibfdt",
diff --git a/vmbase/example/src/main.rs b/vmbase/example/src/main.rs
index ebd981c..6f513ee 100644
--- a/vmbase/example/src/main.rs
+++ b/vmbase/example/src/main.rs
@@ -28,11 +28,12 @@
 use aarch64_paging::paging::MemoryRegion;
 use aarch64_paging::MapError;
 use alloc::{vec, vec::Vec};
+use cstr::cstr;
 use fdtpci::PciInfo;
 use libfdt::Fdt;
 use log::{debug, error, info, trace, warn, LevelFilter};
 use vmbase::{
-    bionic, configure_heap, cstr,
+    bionic, configure_heap,
     layout::{dtb_range, rodata_range, scratch_range, text_range},
     linker, logger, main,
     memory::{PageTable, SIZE_64KB},
diff --git a/vmbase/src/bionic.rs b/vmbase/src/bionic.rs
index f8db1fe..a049616 100644
--- a/vmbase/src/bionic.rs
+++ b/vmbase/src/bionic.rs
@@ -22,11 +22,12 @@
 use core::str;
 
 use crate::console;
-use crate::cstr;
 use crate::eprintln;
 use crate::rand::fill_with_entropy;
 use crate::read_sysreg;
 
+use cstr::cstr;
+
 const EOF: c_int = -1;
 const EIO: c_int = 5;
 
diff --git a/vmbase/src/fdt.rs b/vmbase/src/fdt.rs
index 537ca03..4101f7e 100644
--- a/vmbase/src/fdt.rs
+++ b/vmbase/src/fdt.rs
@@ -14,8 +14,8 @@
 
 //! High-level FDT functions.
 
-use crate::cstr;
 use core::ops::Range;
+use cstr::cstr;
 use libfdt::{self, Fdt, FdtError};
 
 /// Represents information about a SWIOTLB buffer.
diff --git a/vmbase/src/util.rs b/vmbase/src/util.rs
index 25586bc..8c230a1 100644
--- a/vmbase/src/util.rs
+++ b/vmbase/src/util.rs
@@ -16,19 +16,6 @@
 
 use core::ops::Range;
 
-/// Create &CStr out of &str literal
-#[macro_export]
-macro_rules! cstr {
-    ($str:literal) => {{
-        const S: &str = concat!($str, "\0");
-        const C: &::core::ffi::CStr = match ::core::ffi::CStr::from_bytes_with_nul(S.as_bytes()) {
-            Ok(v) => v,
-            Err(_) => panic!("string contains interior NUL"),
-        };
-        C
-    }};
-}
-
 /// 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] {