Merge "virtmgr: Pass VM DTBO path to crosvm if devices assigned" into main
diff --git a/TEST_MAPPING b/TEST_MAPPING
index a9193d7..6983fde 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -34,6 +34,9 @@
     },
     {
       "name": "libapkzip.test"
+    },
+    {
+      "name": "libsecretkeeper_comm.test"
     }
   ],
   "avf-postsubmit": [
diff --git a/apex/Android.bp b/apex/Android.bp
index e04dbd2..a889d08 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -73,11 +73,13 @@
     config_namespace: "ANDROID",
     bool_variables: [
         "release_avf_enable_device_assignment",
+        "release_avf_enable_remote_attestation",
         "release_avf_enable_vendor_modules",
     ],
     properties: [
         "arch",
         "prebuilts",
+        "vintf_fragments",
     ],
 }
 
@@ -112,7 +114,6 @@
         "vm",
     ],
     prebuilts: [
-        "com.android.virt.init.rc",
         "features_com.android.virt.xml",
         "microdroid_initrd_debuggable",
         "microdroid_initrd_normal",
@@ -122,7 +123,6 @@
     ],
     host_required: [
         "vm_shell",
-        "prepare_device_vfio",
     ],
     apps: [
         "EmptyPayloadApp",
@@ -149,6 +149,15 @@
                 "microdroid_gki.json",
             ],
         },
+        release_avf_enable_remote_attestation: {
+            prebuilts: ["com.android.virt.init_attestation_enabled.rc"],
+            vintf_fragments: [
+                "virtualizationservice.xml",
+            ],
+            conditions_default: {
+                prebuilts: ["com.android.virt.init.rc"],
+            },
+        },
     },
 }
 
@@ -172,7 +181,14 @@
 prebuilt_etc {
     name: "com.android.virt.init.rc",
     src: "virtualizationservice.rc",
-    filename: "init.rc",
+    filename: "virtualizationservice.rc",
+    installable: false,
+}
+
+prebuilt_etc {
+    name: "com.android.virt.init_attestation_enabled.rc",
+    src: "virtualizationservice_attestation_enabled.rc",
+    filename: "virtualizationservice.rc",
     installable: false,
 }
 
@@ -183,12 +199,6 @@
     installable: false,
 }
 
-sh_binary_host {
-    name: "prepare_device_vfio",
-    src: "prepare_device_vfio.sh",
-    filename: "prepare_device_vfio.sh",
-}
-
 // Virt apex needs a custom signer for its payload
 python_binary_host {
     name: "sign_virt_apex",
diff --git a/apex/prepare_device_vfio.sh b/apex/prepare_device_vfio.sh
deleted file mode 100755
index de2d502..0000000
--- a/apex/prepare_device_vfio.sh
+++ /dev/null
@@ -1,176 +0,0 @@
-#!/bin/bash
-
-# Copyright 2023 Google Inc. All rights reserved.
-#
-# 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.
-
-# prepare_device_vfio.sh: prepares a device for VFIO assignment by binding a VFIO driver to it
-
-adb="${ADB:="adb"}" # ADB command to use
-vfio_dir="/dev/vfio"
-platform_bus="/sys/bus/platform"
-vfio_reset_required="/sys/module/vfio_platform/parameters/reset_required"
-vfio_noiommu_param="/sys/module/vfio/parameters/enable_unsafe_noiommu_mode"
-vfio_unsafe_interrupts_param="/sys/module/vfio_iommu_type1/parameters/allow_unsafe_interrupts"
-
-function print_help() {
-    echo "prepare_device_vfio.sh prepares a device for VFIO assignment"
-    echo ""
-    echo " Usage:"
-    echo "    $0 DEVICE_NAME"
-    echo "      Prepare device DEVICE_NAME for VFIO assignment."
-    echo ""
-    echo "    help - prints this help message"
-}
-
-function cmd() {
-    $adb shell $@
-}
-
-function tcmd() {
-    trap "echo \"Error: adb shell command '$@' failed\" ; exit 1" ERR
-    $adb shell $@
-}
-
-function ensure_root() {
-    # Check user id
-    if [ $(cmd "id -u") != 0 ]; then
-        read -p "Must run as root; restart ADBD? [y/n] " answer
-        case $answer in
-            [Yy]* )
-                $adb root && $adb wait-for-device && sleep 3 || exit 1
-                ;;
-            * )
-                exit 1
-        esac
-    fi
-}
-
-function check_vfio() {
-    cmd "[ -c $vfio_dir/vfio ]"
-    if [ $? -ne 0 ]; then
-        echo "cannot find $vfio_dir/vfio"
-        exit 1
-    fi
-
-    cmd "[ -d $platform_bus/drivers/vfio-platform ]"
-    if [ $? -ne 0 ]; then
-        echo "VFIO-platform is not supported"
-        exit 1
-    fi
-}
-
-function check_device() {
-    cmd "[ -d $device_sys ]"
-    if [ $? -ne 0 ]; then
-        echo "no device $device ($device_sys)"
-        exit 1
-    fi
-}
-
-function get_device_iommu_group() {
-    local group=$(cmd "basename \$(readlink \"$device_sys/iommu_group\")")
-    if [ $? -eq 0 ]; then
-        echo $group
-    else
-        echo ""
-    fi
-}
-
-function misc_setup() {
-    # VFIO NOIOMMU check
-    if [ -z "$group" ]; then
-        echo "$device_sys does not have an IOMMU group - setting $vfio_noiommu_param"
-        tcmd "echo y > \"$vfio_noiommu_param\""
-    fi
-
-    # Disable SELinux to allow virtualizationmanager and crosvm to access sysfs
-    echo "[*WARN*] setenforce=0: SELinux is disabled"
-    tcmd "setenforce 0"
-
-    # Samsung IOMMU does not report interrupt remapping support, so enable unsafe uinterrupts
-    if [ -n "$group" ]; then
-        local iommu_drv=$(cmd "basename \$(readlink \"$device_sys/iommu/device/driver\")")
-        if [ "$iommu_drv" = "samsung-sysmmu-v9" ]; then
-            tcmd "echo y > \"$vfio_unsafe_interrupts_param\""
-        fi
-    fi
-}
-
-function bind_vfio_driver() {
-    # Check if non-VFIO driver is currently bound, ie unbinding is needed
-    cmd "[ -e \"$device_driver\" ] && \
-        [ ! \$(basename \$(readlink \"$device_driver\")) = \"vfio-platform\" ]"
-            if [ $? -eq 0 ]; then
-                # Unbind current driver
-                tcmd "echo \"$device\" > \"$device_driver/unbind\""
-            fi
-
-    # Bind to VFIO driver
-    cmd "[ ! -e \"$device_driver\" ]"
-    if [ $? -eq 0 ]; then
-        # Bind vfio-platform driver
-        tcmd "echo \"vfio-platform\" > \"$device_sys/driver_override\""
-        tcmd "echo \"$device\" > \"$platform_bus/drivers_probe\""
-        sleep 2
-    fi
-}
-
-function verify_vfio_driver() {
-    # Verify new VFIO file structure
-    group=$(get_device_iommu_group)
-    if [ -z "$group" ]; then
-        echo "cannot setup VFIO-NOIOMMU for $device_sys"
-        exit 1
-    fi
-
-    cmd "[ ! -c \"$vfio_dir/$group\" ] || \
-        [ ! -e \"$device_driver\" ] || \
-        [ ! \$(basename \$(readlink \"$device_driver\")) = \"vfio-platform\" ]"
-    if [ $? -eq 0 ]; then
-        echo "could not bind $device to VFIO platform driver"
-
-        if [ $(cmd "cat $vfio_reset_required") = Y ]; then
-            echo "VFIO device reset handler must be registered. Either unset $vfio_reset_required, \
-or register a reset handler for $device_sys"
-        fi
-        exit 1
-    fi
-}
-
-function prepare_device() {
-    device="$1"
-    device_sys="/sys/bus/platform/devices/$device"
-    device_driver="$device_sys/driver"
-
-    ensure_root
-    check_vfio
-    check_device
-    group=$(get_device_iommu_group)
-    misc_setup
-
-    bind_vfio_driver
-    verify_vfio_driver
-
-    echo "Device: $device_sys"
-    echo "IOMMU group: $group"
-    echo "VFIO group file: $vfio_dir/$group"
-    echo "Ready!"
-}
-
-cmd=$1
-
-case $cmd in
-    ""|help) print_help ;;
-    *) prepare_device "$cmd" $@ ;;
-esac
diff --git a/apex/virtualizationservice.xml b/apex/virtualizationservice.xml
index 0ce1e10..60f466f 100644
--- a/apex/virtualizationservice.xml
+++ b/apex/virtualizationservice.xml
@@ -1,6 +1,6 @@
 <manifest version="1.0" type="framework">
     <hal format="aidl">
-        <name>android.system.virtualization</name>
+        <name>android.hardware.security.keymint</name>
         <version>3</version>
         <fqname>IRemotelyProvisionedComponent/avf</fqname>
     </hal>
diff --git a/apex/virtualizationservice_attestation_enabled.rc b/apex/virtualizationservice_attestation_enabled.rc
new file mode 100644
index 0000000..8eaccae
--- /dev/null
+++ b/apex/virtualizationservice_attestation_enabled.rc
@@ -0,0 +1,22 @@
+# Copyright (C) 2021 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 virtualizationservice /apex/com.android.virt/bin/virtualizationservice
+    class main
+    user system
+    group system
+    interface aidl android.system.virtualizationservice
+    interface aidl android.hardware.security.keymint.IRemotelyProvisionedComponent/avf
+    disabled
+    oneshot
diff --git a/libs/bssl/Android.bp b/libs/bssl/Android.bp
index 0a2f334..ff45af9 100644
--- a/libs/bssl/Android.bp
+++ b/libs/bssl/Android.bp
@@ -23,6 +23,7 @@
     rustlibs: [
         "libbssl_avf_error_nostd",
         "libbssl_ffi_nostd",
+        "libciborium_nostd",
         "libcoset_nostd",
         "liblog_rust_nostd",
         "libzeroize_nostd",
@@ -44,5 +45,6 @@
     defaults: ["libbssl_avf_test_defaults"],
     rustlibs: [
         "libbssl_avf_nostd",
+        "libcoset_nostd",
     ],
 }
diff --git a/libs/bssl/error/src/code.rs b/libs/bssl/error/src/code.rs
index 9b661e9..a318a07 100644
--- a/libs/bssl/error/src/code.rs
+++ b/libs/bssl/error/src/code.rs
@@ -25,6 +25,8 @@
     NoError,
     Global(GlobalError),
     Cipher(CipherError),
+    Ec(EcError),
+    Ecdsa(EcdsaError),
     Unknown(BsslReasonCode, BsslLibraryCode),
 }
 
@@ -102,3 +104,86 @@
         write!(f, "An error occurred in a Cipher function: {self:?}")
     }
 }
+
+/// Errors occurred in the EC functions.
+///
+/// The values are from:
+/// boringssl/src/include/openssl/ec.h
+#[allow(missing_docs)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub enum EcError {
+    BufferTooSmall,
+    CoordinatesOutOfRange,
+    D2IEcpkparametersFailure,
+    EcGroupNewByNameFailure,
+    Group2PkparametersFailure,
+    I2DEcpkparametersFailure,
+    IncompatibleObjects,
+    InvalidCompressedPoint,
+    InvalidCompressionBit,
+    InvalidEncoding,
+    InvalidField,
+    InvalidForm,
+    InvalidGroupOrder,
+    InvalidPrivateKey,
+    MissingParameters,
+    MissingPrivateKey,
+    NonNamedCurve,
+    NotInitialized,
+    Pkparameters2GroupFailure,
+    PointAtInfinity,
+    PointIsNotOnCurve,
+    SlotFull,
+    UndefinedGenerator,
+    UnknownGroup,
+    UnknownOrder,
+    WrongOrder,
+    BignumOutOfRange,
+    WrongCurveParameters,
+    DecodeError,
+    EncodeError,
+    GroupMismatch,
+    InvalidCofactor,
+    PublicKeyValidationFailed,
+    InvalidScalar,
+}
+
+impl From<EcError> for ReasonCode {
+    fn from(e: EcError) -> ReasonCode {
+        ReasonCode::Ec(e)
+    }
+}
+
+impl fmt::Display for EcError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "An error occurred in an EC function: {self:?}")
+    }
+}
+
+/// Errors occurred in the ECDSA functions.
+///
+/// The values are from:
+/// boringssl/src/include/openssl/ecdsa.h
+#[allow(missing_docs)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub enum EcdsaError {
+    BadSignature,
+    MissingParameters,
+    NeedNewSetupValues,
+    NotImplemented,
+    RandomNumberGenerationFailed,
+    EncodeError,
+    TooManyIterations,
+}
+
+impl From<EcdsaError> for ReasonCode {
+    fn from(e: EcdsaError) -> ReasonCode {
+        ReasonCode::Ecdsa(e)
+    }
+}
+
+impl fmt::Display for EcdsaError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "An error occurred in an ECDSA function: {self:?}")
+    }
+}
diff --git a/libs/bssl/error/src/lib.rs b/libs/bssl/error/src/lib.rs
index 88929af..89865d4 100644
--- a/libs/bssl/error/src/lib.rs
+++ b/libs/bssl/error/src/lib.rs
@@ -21,7 +21,7 @@
 use core::{fmt, result};
 use serde::{Deserialize, Serialize};
 
-pub use crate::code::{CipherError, GlobalError, ReasonCode};
+pub use crate::code::{CipherError, EcError, EcdsaError, GlobalError, ReasonCode};
 
 /// libbssl_avf result type.
 pub type Result<T> = result::Result<T, Error>;
@@ -34,6 +34,12 @@
 
     /// An unexpected internal error occurred.
     InternalError,
+
+    /// Failed to decode the COSE_Key.
+    CoseKeyDecodingFailed,
+
+    /// Unimplemented operation.
+    Unimplemented,
 }
 
 impl fmt::Display for Error {
@@ -43,6 +49,8 @@
                 write!(f, "Failed to invoke the BoringSSL API: {api_name:?}. Reason: {reason}")
             }
             Self::InternalError => write!(f, "An unexpected internal error occurred"),
+            Self::CoseKeyDecodingFailed => write!(f, "Failed to decode the COSE_Key"),
+            Self::Unimplemented => write!(f, "Unimplemented operation"),
         }
     }
 }
@@ -53,6 +61,7 @@
 #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
 pub enum ApiName {
     BN_new,
+    BN_bin2bn,
     BN_bn2bin_padded,
     CBB_flush,
     CBB_len,
@@ -64,11 +73,16 @@
     EC_KEY_marshal_private_key,
     EC_KEY_parse_private_key,
     EC_KEY_new_by_curve_name,
+    EC_KEY_set_public_key_affine_coordinates,
     EC_POINT_get_affine_coordinates,
+    ECDSA_sign,
+    ECDSA_size,
+    ECDSA_verify,
     EVP_AEAD_CTX_new,
     EVP_AEAD_CTX_open,
     EVP_AEAD_CTX_seal,
     HKDF,
     HMAC,
     RAND_bytes,
+    SHA256,
 }
diff --git a/libs/bssl/src/ec_key.rs b/libs/bssl/src/ec_key.rs
index 4c1ba5c..7e677c4 100644
--- a/libs/bssl/src/ec_key.rs
+++ b/libs/bssl/src/ec_key.rs
@@ -18,19 +18,28 @@
 use crate::cbb::CbbFixed;
 use crate::cbs::Cbs;
 use crate::util::{check_int_result, to_call_failed_error};
+use alloc::vec;
 use alloc::vec::Vec;
 use bssl_avf_error::{ApiName, Error, Result};
 use bssl_ffi::{
-    BN_bn2bin_padded, BN_clear_free, BN_new, CBB_flush, CBB_len, EC_GROUP_new_by_curve_name,
-    EC_KEY_check_key, EC_KEY_free, EC_KEY_generate_key, EC_KEY_get0_group, EC_KEY_get0_public_key,
-    EC_KEY_marshal_private_key, EC_KEY_new_by_curve_name, EC_KEY_parse_private_key,
+    BN_bin2bn, BN_bn2bin_padded, BN_clear_free, BN_new, CBB_flush, CBB_len, ECDSA_sign, ECDSA_size,
+    ECDSA_verify, EC_GROUP_new_by_curve_name, EC_KEY_check_key, EC_KEY_free, EC_KEY_generate_key,
+    EC_KEY_get0_group, EC_KEY_get0_public_key, EC_KEY_marshal_private_key,
+    EC_KEY_new_by_curve_name, EC_KEY_parse_private_key, EC_KEY_set_public_key_affine_coordinates,
     EC_POINT_get_affine_coordinates, NID_X9_62_prime256v1, BIGNUM, EC_GROUP, EC_KEY, EC_POINT,
 };
+use ciborium::Value;
 use core::ptr::{self, NonNull};
 use core::result;
-use coset::{iana, CoseKey, CoseKeyBuilder};
+use coset::{
+    iana::{self, EnumI64},
+    CborSerializable, CoseKey, CoseKeyBuilder, Label,
+};
+use log::error;
 use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
 
+const ES256_ALGO: iana::Algorithm = iana::Algorithm::ES256;
+const P256_CURVE: iana::EllipticCurve = iana::EllipticCurve::P_256;
 const P256_AFFINE_COORDINATE_SIZE: usize = 32;
 
 type Coordinate = [u8; P256_AFFINE_COORDINATE_SIZE];
@@ -53,10 +62,46 @@
         let ec_key = unsafe {
             EC_KEY_new_by_curve_name(NID_X9_62_prime256v1) // EC P-256 CURVE Nid
         };
-        let mut ec_key = NonNull::new(ec_key)
+        NonNull::new(ec_key)
             .map(Self)
-            .ok_or(to_call_failed_error(ApiName::EC_KEY_new_by_curve_name))?;
-        ec_key.generate_key()?;
+            .ok_or(to_call_failed_error(ApiName::EC_KEY_new_by_curve_name))
+    }
+
+    /// Constructs an `EcKey` instance from the provided COSE_Key encoded public key slice.
+    pub fn from_cose_public_key(cose_key: &[u8]) -> Result<Self> {
+        let cose_key = CoseKey::from_slice(cose_key).map_err(|e| {
+            error!("Failed to deserialize COSE_Key: {e:?}");
+            Error::CoseKeyDecodingFailed
+        })?;
+        if cose_key.alg != Some(coset::Algorithm::Assigned(ES256_ALGO)) {
+            error!(
+                "Only ES256 algorithm is supported. Algo type in the COSE Key: {:?}",
+                cose_key.alg
+            );
+            return Err(Error::Unimplemented);
+        }
+        let crv = get_label_value(&cose_key, Label::Int(iana::Ec2KeyParameter::Crv.to_i64()))?;
+        if &Value::from(P256_CURVE.to_i64()) != crv {
+            error!("Only EC P-256 curve is supported. Curve type in the COSE Key: {crv:?}");
+            return Err(Error::Unimplemented);
+        }
+
+        let x = get_label_value_as_bytes(&cose_key, Label::Int(iana::Ec2KeyParameter::X.to_i64()))?;
+        let y = get_label_value_as_bytes(&cose_key, Label::Int(iana::Ec2KeyParameter::Y.to_i64()))?;
+
+        check_p256_affine_coordinate_size(x)?;
+        check_p256_affine_coordinate_size(y)?;
+
+        let x = BigNum::from_slice(x)?;
+        let y = BigNum::from_slice(y)?;
+
+        let ec_key = EcKey::new_p256()?;
+        // SAFETY: All the parameters are checked non-null and initialized.
+        // The function only reads the coordinates x and y within their bounds.
+        let ret = unsafe {
+            EC_KEY_set_public_key_affine_coordinates(ec_key.0.as_ptr(), x.as_ref(), y.as_ref())
+        };
+        check_int_result(ret, ApiName::EC_KEY_set_public_key_affine_coordinates)?;
         Ok(ec_key)
     }
 
@@ -70,9 +115,73 @@
         check_int_result(ret, ApiName::EC_KEY_check_key)
     }
 
+    /// Verifies the DER-encoded ECDSA `signature` of the `digest` with the current `EcKey`.
+    ///
+    /// Returns Ok(()) if the verification succeeds, otherwise an error will be returned.
+    pub fn ecdsa_verify(&self, signature: &[u8], digest: &[u8]) -> Result<()> {
+        // The `type` argument should be 0 as required in the BoringSSL spec.
+        const TYPE: i32 = 0;
+
+        // SAFETY: This function only reads the given data within its bounds.
+        // The `EC_KEY` passed to this function has been initialized and checked non-null.
+        let ret = unsafe {
+            ECDSA_verify(
+                TYPE,
+                digest.as_ptr(),
+                digest.len(),
+                signature.as_ptr(),
+                signature.len(),
+                self.0.as_ptr(),
+            )
+        };
+        check_int_result(ret, ApiName::ECDSA_verify)
+    }
+
+    /// Signs the `digest` with the current `EcKey` using ECDSA.
+    ///
+    /// Returns the DER-encoded ECDSA signature.
+    pub fn ecdsa_sign(&self, digest: &[u8]) -> Result<Vec<u8>> {
+        // The `type` argument should be 0 as required in the BoringSSL spec.
+        const TYPE: i32 = 0;
+
+        let mut signature = vec![0u8; self.ecdsa_size()?];
+        let mut signature_len = 0;
+        // SAFETY: This function only reads the given data within its bounds.
+        // The `EC_KEY` passed to this function has been initialized and checked non-null.
+        let ret = unsafe {
+            ECDSA_sign(
+                TYPE,
+                digest.as_ptr(),
+                digest.len(),
+                signature.as_mut_ptr(),
+                &mut signature_len,
+                self.0.as_ptr(),
+            )
+        };
+        check_int_result(ret, ApiName::ECDSA_sign)?;
+        if signature.len() < (signature_len as usize) {
+            Err(to_call_failed_error(ApiName::ECDSA_sign))
+        } else {
+            signature.truncate(signature_len as usize);
+            Ok(signature)
+        }
+    }
+
+    /// Returns the maximum size of an ECDSA signature using the current `EcKey`.
+    fn ecdsa_size(&self) -> Result<usize> {
+        // SAFETY: This function only reads the `EC_KEY` that has been initialized
+        // and checked non-null when this instance is created.
+        let size = unsafe { ECDSA_size(self.0.as_ptr()) };
+        if size == 0 {
+            Err(to_call_failed_error(ApiName::ECDSA_size))
+        } else {
+            Ok(size)
+        }
+    }
+
     /// Generates a random, private key, calculates the corresponding public key and stores both
     /// in the `EC_KEY`.
-    fn generate_key(&mut self) -> Result<()> {
+    pub fn generate_key(&mut self) -> Result<()> {
         // SAFETY: The non-null pointer is created with `EC_KEY_new_by_curve_name` and should
         // point to a valid `EC_KEY`.
         // The randomness is provided by `getentropy()` in `vmbase`.
@@ -82,12 +191,10 @@
 
     /// Returns the `CoseKey` for the public key.
     pub fn cose_public_key(&self) -> Result<CoseKey> {
-        const ALGO: iana::Algorithm = iana::Algorithm::ES256;
-        const CURVE: iana::EllipticCurve = iana::EllipticCurve::P_256;
-
         let (x, y) = self.public_key_coordinates()?;
-        let key =
-            CoseKeyBuilder::new_ec2_pub_key(CURVE, x.to_vec(), y.to_vec()).algorithm(ALGO).build();
+        let key = CoseKeyBuilder::new_ec2_pub_key(P256_CURVE, x.to_vec(), y.to_vec())
+            .algorithm(ES256_ALGO)
+            .build();
         Ok(key)
     }
 
@@ -183,6 +290,30 @@
     }
 }
 
+fn get_label_value_as_bytes(key: &CoseKey, label: Label) -> Result<&[u8]> {
+    Ok(get_label_value(key, label)?.as_bytes().ok_or_else(|| {
+        error!("Value not a bstr.");
+        Error::CoseKeyDecodingFailed
+    })?)
+}
+
+fn get_label_value(key: &CoseKey, label: Label) -> Result<&Value> {
+    Ok(&key.params.iter().find(|(k, _)| k == &label).ok_or(Error::CoseKeyDecodingFailed)?.1)
+}
+
+fn check_p256_affine_coordinate_size(coordinate: &[u8]) -> Result<()> {
+    if P256_AFFINE_COORDINATE_SIZE == coordinate.len() {
+        Ok(())
+    } else {
+        error!(
+            "The size of the affine coordinate '{}' does not match the expected size '{}'",
+            coordinate.len(),
+            P256_AFFINE_COORDINATE_SIZE
+        );
+        Err(Error::CoseKeyDecodingFailed)
+    }
+}
+
 /// A u8 vector that is zeroed when dropped.
 #[derive(Zeroize, ZeroizeOnDrop)]
 pub struct ZVec(Vec<u8>);
@@ -210,6 +341,13 @@
 }
 
 impl BigNum {
+    fn from_slice(x: &[u8]) -> Result<Self> {
+        // SAFETY: The function reads `x` within its bounds, and the returned
+        // pointer is checked below.
+        let bn = unsafe { BN_bin2bn(x.as_ptr(), x.len(), ptr::null_mut()) };
+        NonNull::new(bn).map(Self).ok_or(to_call_failed_error(ApiName::BN_bin2bn))
+    }
+
     fn new() -> Result<Self> {
         // SAFETY: The returned pointer is checked below.
         let bn = unsafe { BN_new() };
@@ -221,6 +359,14 @@
     }
 }
 
+impl AsRef<BIGNUM> for BigNum {
+    fn as_ref(&self) -> &BIGNUM {
+        // SAFETY: The pointer is valid and points to an initialized instance of `BIGNUM`
+        // when the instance was created.
+        unsafe { self.0.as_ref() }
+    }
+}
+
 /// Converts the `BigNum` to a big-endian integer. The integer is padded with leading zeros up to
 /// size `N`. The conversion fails if `N` is smaller thanthe size of the integer.
 impl<const N: usize> TryFrom<BigNum> for [u8; N] {
diff --git a/libs/bssl/src/err.rs b/libs/bssl/src/err.rs
index 1ee40c9..7040441 100644
--- a/libs/bssl/src/err.rs
+++ b/libs/bssl/src/err.rs
@@ -14,7 +14,7 @@
 
 //! Wrappers of the error handling functions in BoringSSL err.h.
 
-use bssl_avf_error::{CipherError, GlobalError, ReasonCode};
+use bssl_avf_error::{CipherError, EcError, EcdsaError, GlobalError, ReasonCode};
 use bssl_ffi::{self, ERR_get_error, ERR_GET_LIB_RUST, ERR_GET_REASON_RUST};
 
 const NO_ERROR_REASON_CODE: i32 = 0;
@@ -75,6 +75,8 @@
 fn map_library_reason_code(reason: i32, lib: i32) -> Option<ReasonCode> {
     u32::try_from(lib).ok().and_then(|x| match x {
         bssl_ffi::ERR_LIB_CIPHER => map_cipher_reason_code(reason).map(ReasonCode::Cipher),
+        bssl_ffi::ERR_LIB_EC => map_ec_reason_code(reason).map(ReasonCode::Ec),
+        bssl_ffi::ERR_LIB_ECDSA => map_ecdsa_reason_code(reason).map(ReasonCode::Ecdsa),
         _ => None,
     })
 }
@@ -110,3 +112,60 @@
     };
     Some(error)
 }
+
+fn map_ec_reason_code(reason: i32) -> Option<EcError> {
+    let error = match reason {
+        bssl_ffi::EC_R_BUFFER_TOO_SMALL => EcError::BufferTooSmall,
+        bssl_ffi::EC_R_COORDINATES_OUT_OF_RANGE => EcError::CoordinatesOutOfRange,
+        bssl_ffi::EC_R_D2I_ECPKPARAMETERS_FAILURE => EcError::D2IEcpkparametersFailure,
+        bssl_ffi::EC_R_EC_GROUP_NEW_BY_NAME_FAILURE => EcError::EcGroupNewByNameFailure,
+        bssl_ffi::EC_R_GROUP2PKPARAMETERS_FAILURE => EcError::Group2PkparametersFailure,
+        bssl_ffi::EC_R_I2D_ECPKPARAMETERS_FAILURE => EcError::I2DEcpkparametersFailure,
+        bssl_ffi::EC_R_INCOMPATIBLE_OBJECTS => EcError::IncompatibleObjects,
+        bssl_ffi::EC_R_INVALID_COMPRESSED_POINT => EcError::InvalidCompressedPoint,
+        bssl_ffi::EC_R_INVALID_COMPRESSION_BIT => EcError::InvalidCompressionBit,
+        bssl_ffi::EC_R_INVALID_ENCODING => EcError::InvalidEncoding,
+        bssl_ffi::EC_R_INVALID_FIELD => EcError::InvalidField,
+        bssl_ffi::EC_R_INVALID_FORM => EcError::InvalidForm,
+        bssl_ffi::EC_R_INVALID_GROUP_ORDER => EcError::InvalidGroupOrder,
+        bssl_ffi::EC_R_INVALID_PRIVATE_KEY => EcError::InvalidPrivateKey,
+        bssl_ffi::EC_R_MISSING_PARAMETERS => EcError::MissingParameters,
+        bssl_ffi::EC_R_MISSING_PRIVATE_KEY => EcError::MissingPrivateKey,
+        bssl_ffi::EC_R_NON_NAMED_CURVE => EcError::NonNamedCurve,
+        bssl_ffi::EC_R_NOT_INITIALIZED => EcError::NotInitialized,
+        bssl_ffi::EC_R_PKPARAMETERS2GROUP_FAILURE => EcError::Pkparameters2GroupFailure,
+        bssl_ffi::EC_R_POINT_AT_INFINITY => EcError::PointAtInfinity,
+        bssl_ffi::EC_R_POINT_IS_NOT_ON_CURVE => EcError::PointIsNotOnCurve,
+        bssl_ffi::EC_R_SLOT_FULL => EcError::SlotFull,
+        bssl_ffi::EC_R_UNDEFINED_GENERATOR => EcError::UndefinedGenerator,
+        bssl_ffi::EC_R_UNKNOWN_GROUP => EcError::UnknownGroup,
+        bssl_ffi::EC_R_UNKNOWN_ORDER => EcError::UnknownOrder,
+        bssl_ffi::EC_R_WRONG_ORDER => EcError::WrongOrder,
+        bssl_ffi::EC_R_BIGNUM_OUT_OF_RANGE => EcError::BignumOutOfRange,
+        bssl_ffi::EC_R_WRONG_CURVE_PARAMETERS => EcError::WrongCurveParameters,
+        bssl_ffi::EC_R_DECODE_ERROR => EcError::DecodeError,
+        bssl_ffi::EC_R_ENCODE_ERROR => EcError::EncodeError,
+        bssl_ffi::EC_R_GROUP_MISMATCH => EcError::GroupMismatch,
+        bssl_ffi::EC_R_INVALID_COFACTOR => EcError::InvalidCofactor,
+        bssl_ffi::EC_R_PUBLIC_KEY_VALIDATION_FAILED => EcError::PublicKeyValidationFailed,
+        bssl_ffi::EC_R_INVALID_SCALAR => EcError::InvalidScalar,
+        _ => return None,
+    };
+    Some(error)
+}
+
+fn map_ecdsa_reason_code(reason: i32) -> Option<EcdsaError> {
+    let error = match reason {
+        bssl_ffi::ECDSA_R_BAD_SIGNATURE => EcdsaError::BadSignature,
+        bssl_ffi::ECDSA_R_MISSING_PARAMETERS => EcdsaError::MissingParameters,
+        bssl_ffi::ECDSA_R_NEED_NEW_SETUP_VALUES => EcdsaError::NeedNewSetupValues,
+        bssl_ffi::ECDSA_R_NOT_IMPLEMENTED => EcdsaError::NotImplemented,
+        bssl_ffi::ECDSA_R_RANDOM_NUMBER_GENERATION_FAILED => {
+            EcdsaError::RandomNumberGenerationFailed
+        }
+        bssl_ffi::ECDSA_R_ENCODE_ERROR => EcdsaError::EncodeError,
+        bssl_ffi::ECDSA_R_TOO_MANY_ITERATIONS => EcdsaError::TooManyIterations,
+        _ => return None,
+    };
+    Some(error)
+}
diff --git a/libs/bssl/src/hmac.rs b/libs/bssl/src/hmac.rs
index ddbbe4a..1b3a403 100644
--- a/libs/bssl/src/hmac.rs
+++ b/libs/bssl/src/hmac.rs
@@ -15,15 +15,14 @@
 //! Wrappers of the HMAC functions in BoringSSL hmac.h.
 
 use crate::digest::Digester;
+use crate::sha::SHA256_DIGEST_LENGTH;
 use crate::util::to_call_failed_error;
 use bssl_avf_error::{ApiName, Result};
-use bssl_ffi::{HMAC, SHA256_DIGEST_LENGTH};
-
-const SHA256_LEN: usize = SHA256_DIGEST_LENGTH as usize;
+use bssl_ffi::HMAC;
 
 /// Computes the HMAC using SHA-256 for the given `data` with the given `key`.
-pub fn hmac_sha256(key: &[u8], data: &[u8]) -> Result<[u8; SHA256_LEN]> {
-    hmac::<SHA256_LEN>(key, data, Digester::sha256())
+pub fn hmac_sha256(key: &[u8], data: &[u8]) -> Result<[u8; SHA256_DIGEST_LENGTH]> {
+    hmac::<SHA256_DIGEST_LENGTH>(key, data, Digester::sha256())
 }
 
 /// Computes the HMAC for the given `data` with the given `key` and `digester`.
diff --git a/libs/bssl/src/lib.rs b/libs/bssl/src/lib.rs
index de81368..8e3abcf 100644
--- a/libs/bssl/src/lib.rs
+++ b/libs/bssl/src/lib.rs
@@ -27,9 +27,10 @@
 mod hkdf;
 mod hmac;
 mod rand;
+mod sha;
 mod util;
 
-pub use bssl_avf_error::{ApiName, CipherError, Error, ReasonCode, Result};
+pub use bssl_avf_error::{ApiName, CipherError, EcError, EcdsaError, Error, ReasonCode, Result};
 
 pub use aead::{Aead, AeadContext, AES_GCM_NONCE_LENGTH};
 pub use cbb::CbbFixed;
@@ -39,3 +40,4 @@
 pub use hkdf::hkdf;
 pub use hmac::hmac_sha256;
 pub use rand::rand_bytes;
+pub use sha::sha256;
diff --git a/libs/bssl/src/sha.rs b/libs/bssl/src/sha.rs
new file mode 100644
index 0000000..6c65d7f
--- /dev/null
+++ b/libs/bssl/src/sha.rs
@@ -0,0 +1,35 @@
+// 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.
+
+//! Wrappers of the SHA functions in BoringSSL sha.h.
+
+use crate::util::to_call_failed_error;
+use bssl_avf_error::{ApiName, Result};
+use bssl_ffi::SHA256;
+
+/// The length of a SHA256 digest.
+pub(crate) const SHA256_DIGEST_LENGTH: usize = bssl_ffi::SHA256_DIGEST_LENGTH as usize;
+
+/// Computes the SHA256 digest of the provided `data``.
+pub fn sha256(data: &[u8]) -> Result<[u8; SHA256_DIGEST_LENGTH]> {
+    let mut out = [0u8; SHA256_DIGEST_LENGTH];
+    // SAFETY: This function reads `data` and writes to `out` within its bounds.
+    // `out` has `SHA256_DIGEST_LENGTH` bytes of space for write.
+    let ret = unsafe { SHA256(data.as_ptr(), data.len(), out.as_mut_ptr()) };
+    if ret.is_null() {
+        Err(to_call_failed_error(ApiName::SHA256))
+    } else {
+        Ok(out)
+    }
+}
diff --git a/libs/bssl/tests/eckey_test.rs b/libs/bssl/tests/eckey_test.rs
index a013fba..3dd243c 100644
--- a/libs/bssl/tests/eckey_test.rs
+++ b/libs/bssl/tests/eckey_test.rs
@@ -12,14 +12,70 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use bssl_avf::{EcKey, Result};
+use bssl_avf::{sha256, ApiName, EcKey, EcdsaError, Error, Result};
+use coset::CborSerializable;
+
+const MESSAGE1: &[u8] = b"test message 1";
+const MESSAGE2: &[u8] = b"test message 2";
 
 #[test]
 fn ec_private_key_serialization() -> Result<()> {
-    let ec_key = EcKey::new_p256()?;
+    let mut ec_key = EcKey::new_p256()?;
+    ec_key.generate_key()?;
     let der_encoded_ec_private_key = ec_key.ec_private_key()?;
     let deserialized_ec_key = EcKey::from_ec_private_key(der_encoded_ec_private_key.as_slice())?;
 
     assert_eq!(ec_key.cose_public_key()?, deserialized_ec_key.cose_public_key()?);
     Ok(())
 }
+
+#[test]
+fn cose_public_key_serialization() -> Result<()> {
+    let mut ec_key = EcKey::new_p256()?;
+    ec_key.generate_key()?;
+    let cose_key = ec_key.cose_public_key()?;
+    let cose_key_data = cose_key.clone().to_vec().unwrap();
+    let deserialized_ec_key = EcKey::from_cose_public_key(&cose_key_data)?;
+
+    assert_eq!(cose_key, deserialized_ec_key.cose_public_key()?);
+    Ok(())
+}
+
+#[test]
+fn ecdsa_p256_signing_and_verification_succeed() -> Result<()> {
+    let mut ec_key = EcKey::new_p256()?;
+    ec_key.generate_key()?;
+    let digest = sha256(MESSAGE1)?;
+
+    let signature = ec_key.ecdsa_sign(&digest)?;
+    ec_key.ecdsa_verify(&signature, &digest)
+}
+
+#[test]
+fn verifying_ecdsa_p256_signed_with_a_different_key_fails() -> Result<()> {
+    let mut ec_key1 = EcKey::new_p256()?;
+    ec_key1.generate_key()?;
+    let digest = sha256(MESSAGE1)?;
+    let signature = ec_key1.ecdsa_sign(&digest)?;
+
+    let mut ec_key2 = EcKey::new_p256()?;
+    ec_key2.generate_key()?;
+    let err = ec_key2.ecdsa_verify(&signature, &digest).unwrap_err();
+    let expected_err = Error::CallFailed(ApiName::ECDSA_verify, EcdsaError::BadSignature.into());
+    assert_eq!(expected_err, err);
+    Ok(())
+}
+
+#[test]
+fn verifying_ecdsa_p256_signed_with_a_different_message_fails() -> Result<()> {
+    let mut ec_key = EcKey::new_p256()?;
+    ec_key.generate_key()?;
+    let digest1 = sha256(MESSAGE1)?;
+    let signature = ec_key.ecdsa_sign(&digest1)?;
+    let digest2 = sha256(MESSAGE2)?;
+
+    let err = ec_key.ecdsa_verify(&signature, &digest2).unwrap_err();
+    let expected_err = Error::CallFailed(ApiName::ECDSA_verify, EcdsaError::BadSignature.into());
+    assert_eq!(expected_err, err);
+    Ok(())
+}
diff --git a/libs/hyp/src/hypervisor.rs b/libs/hyp/src/hypervisor.rs
index 309f967..3d42ccb 100644
--- a/libs/hyp/src/hypervisor.rs
+++ b/libs/hyp/src/hypervisor.rs
@@ -34,6 +34,8 @@
 use smccc::hvc64;
 use uuid::Uuid;
 
+use self::common::DeviceAssigningHypervisor;
+
 enum HypervisorBackend {
     RegularKvm,
     Gunyah,
@@ -122,3 +124,8 @@
 pub fn get_mem_sharer() -> Option<&'static dyn MemSharingHypervisor> {
     get_hypervisor().as_mem_sharer()
 }
+
+/// Gets the device assigning hypervisor singleton, if any.
+pub fn get_device_assigner() -> Option<&'static dyn DeviceAssigningHypervisor> {
+    get_hypervisor().as_device_assigner()
+}
diff --git a/libs/hyp/src/hypervisor/common.rs b/libs/hyp/src/hypervisor/common.rs
index 70fdd0a..2ea18f3 100644
--- a/libs/hyp/src/hypervisor/common.rs
+++ b/libs/hyp/src/hypervisor/common.rs
@@ -31,6 +31,11 @@
     fn as_mem_sharer(&self) -> Option<&dyn MemSharingHypervisor> {
         None
     }
+
+    /// Returns the hypervisor's device assigning implementation, if any.
+    fn as_device_assigner(&self) -> Option<&dyn DeviceAssigningHypervisor> {
+        None
+    }
 }
 
 pub trait MmioGuardedHypervisor {
@@ -73,3 +78,11 @@
     /// Returns the memory protection granule size in bytes.
     fn granule(&self) -> Result<usize>;
 }
+
+pub trait DeviceAssigningHypervisor {
+    /// Returns MMIO token.
+    fn get_phys_mmio_token(&self, base_ipa: u64, size: u64) -> Result<u64>;
+
+    /// Returns DMA token as a tuple of (phys_iommu_id, phys_sid).
+    fn get_phys_iommu_token(&self, pviommu_id: u64, vsid: u64) -> Result<(u64, u64)>;
+}
diff --git a/libs/hyp/src/hypervisor/kvm.rs b/libs/hyp/src/hypervisor/kvm.rs
index 5835346..720318e 100644
--- a/libs/hyp/src/hypervisor/kvm.rs
+++ b/libs/hyp/src/hypervisor/kvm.rs
@@ -14,7 +14,9 @@
 
 //! Wrappers around calls to the KVM hypervisor.
 
-use super::common::{Hypervisor, MemSharingHypervisor, MmioGuardedHypervisor};
+use super::common::{
+    DeviceAssigningHypervisor, Hypervisor, MemSharingHypervisor, MmioGuardedHypervisor,
+};
 use crate::error::{Error, Result};
 use crate::util::page_address;
 use core::fmt::{self, Display, Formatter};
@@ -70,6 +72,9 @@
 const VENDOR_HYP_KVM_MMIO_GUARD_MAP_FUNC_ID: u32 = 0xc6000007;
 const VENDOR_HYP_KVM_MMIO_GUARD_UNMAP_FUNC_ID: u32 = 0xc6000008;
 
+const VENDOR_HYP_KVM_DEV_REQ_MMIO_FUNC_ID: u32 = 0xc6000012;
+const VENDOR_HYP_KVM_DEV_REQ_DMA_FUNC_ID: u32 = 0xc6000013;
+
 pub(super) struct RegularKvmHypervisor;
 
 impl RegularKvmHypervisor {
@@ -90,6 +95,10 @@
     fn as_mem_sharer(&self) -> Option<&dyn MemSharingHypervisor> {
         Some(self)
     }
+
+    fn as_device_assigner(&self) -> Option<&dyn DeviceAssigningHypervisor> {
+        Some(self)
+    }
 }
 
 impl MmioGuardedHypervisor for ProtectedKvmHypervisor {
@@ -153,6 +162,26 @@
     }
 }
 
+impl DeviceAssigningHypervisor for ProtectedKvmHypervisor {
+    fn get_phys_mmio_token(&self, base_ipa: u64, size: u64) -> Result<u64> {
+        let mut args = [0u64; 17];
+        args[0] = base_ipa;
+        args[1] = size;
+
+        let ret = checked_hvc64_expect_results(VENDOR_HYP_KVM_DEV_REQ_MMIO_FUNC_ID, args)?;
+        Ok(ret[0])
+    }
+
+    fn get_phys_iommu_token(&self, pviommu_id: u64, vsid: u64) -> Result<(u64, u64)> {
+        let mut args = [0u64; 17];
+        args[0] = pviommu_id;
+        args[1] = vsid;
+
+        let ret = checked_hvc64_expect_results(VENDOR_HYP_KVM_DEV_REQ_DMA_FUNC_ID, args)?;
+        Ok((ret[0], ret[1]))
+    }
+}
+
 fn checked_hvc64_expect_zero(function: u32, args: [u64; 17]) -> Result<()> {
     success_or_error_64(hvc64(function, args)[0]).map_err(|e| Error::KvmError(e, function))
 }
@@ -160,3 +189,9 @@
 fn checked_hvc64(function: u32, args: [u64; 17]) -> Result<u64> {
     positive_or_error_64(hvc64(function, args)[0]).map_err(|e| Error::KvmError(e, function))
 }
+
+fn checked_hvc64_expect_results(function: u32, args: [u64; 17]) -> Result<[u64; 17]> {
+    let [ret, results @ ..] = hvc64(function, args);
+    success_or_error_64(ret).map_err(|e| Error::KvmError(e, function))?;
+    Ok(results)
+}
diff --git a/libs/hyp/src/lib.rs b/libs/hyp/src/lib.rs
index 486a181..505aade 100644
--- a/libs/hyp/src/lib.rs
+++ b/libs/hyp/src/lib.rs
@@ -21,6 +21,8 @@
 mod util;
 
 pub use error::{Error, Result};
-pub use hypervisor::{get_mem_sharer, get_mmio_guard, KvmError, MMIO_GUARD_GRANULE_SIZE};
+pub use hypervisor::{
+    get_device_assigner, get_mem_sharer, get_mmio_guard, KvmError, MMIO_GUARD_GRANULE_SIZE,
+};
 
 use hypervisor::GeniezoneError;
diff --git a/libs/libfdt/Android.bp b/libs/libfdt/Android.bp
index 5920d5d..ba9e971 100644
--- a/libs/libfdt/Android.bp
+++ b/libs/libfdt/Android.bp
@@ -69,22 +69,34 @@
 
 genrule {
     name: "fdt_test_tree_one_memory_range_dtb",
-    defaults: ["dts_to_dtb"],
-    srcs: ["tests/data/test_tree_one_memory_range.dts"],
+    tools: ["dtc"],
+    srcs: [
+        "tests/data/test_tree_one_memory_range.dts",
+        "tests/data/test_tree_no_memory_node.dts",
+    ],
+    cmd: "$(location dtc) -I dts -O dtb $(location tests/data/test_tree_one_memory_range.dts) -o $(out)",
     out: ["data/test_tree_one_memory_range.dtb"],
 }
 
 genrule {
     name: "fdt_test_tree_multiple_memory_ranges_dtb",
-    defaults: ["dts_to_dtb"],
-    srcs: ["tests/data/test_tree_multiple_memory_ranges.dts"],
+    tools: ["dtc"],
+    srcs: [
+        "tests/data/test_tree_multiple_memory_ranges.dts",
+        "tests/data/test_tree_no_memory_node.dts",
+    ],
+    cmd: "$(location dtc) -I dts -O dtb $(location tests/data/test_tree_multiple_memory_ranges.dts) -o $(out)",
     out: ["data/test_tree_multiple_memory_ranges.dtb"],
 }
 
 genrule {
     name: "fdt_test_tree_empty_memory_range_dtb",
-    defaults: ["dts_to_dtb"],
-    srcs: ["tests/data/test_tree_empty_memory_range.dts"],
+    tools: ["dtc"],
+    srcs: [
+        "tests/data/test_tree_empty_memory_range.dts",
+        "tests/data/test_tree_no_memory_node.dts",
+    ],
+    cmd: "$(location dtc) -I dts -O dtb $(location tests/data/test_tree_empty_memory_range.dts) -o $(out)",
     out: ["data/test_tree_empty_memory_range.dtb"],
 }
 
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index 23eadcf..c93cb4c 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -283,12 +283,14 @@
     ],
     soong_config_variables: {
         release_avf_enable_vendor_modules: {
-            default_group: [
-                {
-                    name: "vendor_a",
-                    filesystem: ":microdroid_vendor",
-                },
-            ],
+            conditions_default: {
+                default_group: [
+                    {
+                        name: "vendor_a",
+                        filesystem: ":microdroid_vendor",
+                    },
+                ],
+            },
         },
     },
 }
diff --git a/pvmfw/Android.bp b/pvmfw/Android.bp
index b6f3940..103619f 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -82,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"],
@@ -101,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/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/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/secretkeeper/comm/Android.bp b/secretkeeper/comm/Android.bp
new file mode 100644
index 0000000..cb3e713
--- /dev/null
+++ b/secretkeeper/comm/Android.bp
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+    name: "libsecretkeeper_comm.defaults",
+    crate_name: "secretkeeper_comm",
+    defaults: ["avf_build_flags_rust"],
+    edition: "2021",
+    lints: "android",
+    rustlibs: [
+        "libciborium",
+        "libcoset",
+    ],
+    proc_macros: ["libenumn"],
+    vendor_available: true,
+}
+
+rust_library {
+    name: "libsecretkeeper_comm_nostd",
+    defaults: ["libsecretkeeper_comm.defaults"],
+    srcs: ["src/lib.rs"],
+}
+
+rust_test {
+    name: "libsecretkeeper_comm.test",
+    defaults: [
+        "libsecretkeeper_comm.defaults",
+        "rdroidtest.defaults",
+    ],
+    srcs: ["tests/*.rs"],
+    test_suites: ["general-tests"],
+    rustlibs: [
+        "libsecretkeeper_comm_nostd",
+    ],
+}
diff --git a/secretkeeper/comm/src/cbor_convert.rs b/secretkeeper/comm/src/cbor_convert.rs
new file mode 100644
index 0000000..ab6ca3f
--- /dev/null
+++ b/secretkeeper/comm/src/cbor_convert.rs
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+
+//! Implements various useful CBOR conversion method.
+
+use crate::data_types::error::Error;
+use alloc::vec::Vec;
+use ciborium::Value;
+
+/// Decodes the provided binary CBOR-encoded value and returns a
+/// [`ciborium::Value`] struct wrapped in Result.
+pub fn value_from_bytes(mut bytes: &[u8]) -> Result<Value, Error> {
+    let value = ciborium::de::from_reader(&mut bytes).map_err(|_| Error::ConversionError)?;
+    // Ciborium tries to read one Value, but doesn't care if there is trailing data after it. We do
+    if !bytes.is_empty() {
+        return Err(Error::ConversionError);
+    }
+    Ok(value)
+}
+
+/// Encodes a [`ciborium::Value`] into bytes.
+pub fn value_to_bytes(value: &Value) -> Result<Vec<u8>, Error> {
+    let mut bytes: Vec<u8> = Vec::new();
+    ciborium::ser::into_writer(&value, &mut bytes).map_err(|_| Error::UnexpectedError)?;
+    Ok(bytes)
+}
+
+// Useful to convert [`ciborium::Value`] to integer, we return largest integer range for
+// convenience, callers should downcast into appropriate type.
+pub fn value_to_integer(value: &Value) -> Result<i128, Error> {
+    let num = value.as_integer().ok_or(Error::ConversionError)?.into();
+    Ok(num)
+}
diff --git a/secretkeeper/comm/src/data_types/error.rs b/secretkeeper/comm/src/data_types/error.rs
new file mode 100644
index 0000000..6a5e24f
--- /dev/null
+++ b/secretkeeper/comm/src/data_types/error.rs
@@ -0,0 +1,94 @@
+/*
+ * 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.
+ */
+
+//! Error-like data structures. See `ResponsePacketError` in the CDDL
+
+// derive(N) generates a method that is missing a docstring.
+#![allow(missing_docs)]
+
+use crate::cbor_convert::value_to_integer;
+use crate::data_types::response::Response;
+use alloc::boxed::Box;
+use alloc::vec::Vec;
+use ciborium::Value;
+use enumn::N;
+
+/// 'Error code' corresponding to successful response.
+pub const ERROR_OK: u16 = 0; // All real errors must have non-zero error_codes
+
+/// Errors from Secretkeeper API. Keep in sync with `ErrorCode` defined for Secretkeeper HAL
+/// at SecretManagement.cddl
+#[derive(Clone, Copy, Debug, Eq, N, PartialEq)]
+pub enum SecretkeeperError {
+    // This is the Error code used if no other error codes explains the issue.
+    UnexpectedServerError = 1,
+    // Indicates the Request was malformed & hence couldn't be served.
+    RequestMalformed = 2,
+    // TODO(b/291228655): Add other errors such as DicePolicyError.
+}
+
+// [`SecretkeeperError`] is a valid [`Response`] type.
+// For more information see `ErrorCode` in SecretManagement.cddl alongside ISecretkeeper.aidl
+impl Response for SecretkeeperError {
+    fn new(response_cbor: Vec<Value>) -> Result<Box<Self>, Error> {
+        // TODO(b/291228655): This method currently discards the second value in response_cbor,
+        // which contains additional human-readable context in error. Include it!
+        if response_cbor.is_empty() || response_cbor.len() > 2 {
+            return Err(Error::ResponseMalformed);
+        }
+        let error_code: u16 = value_to_integer(&response_cbor[0])?.try_into()?;
+        SecretkeeperError::n(error_code)
+            .map_or_else(|| Err(Error::ResponseMalformed), |sk_err| Ok(Box::new(sk_err)))
+    }
+
+    fn error_code(&self) -> u16 {
+        *self as u16
+    }
+}
+
+/// Errors thrown internally by the library.
+#[derive(Debug, PartialEq)]
+pub enum Error {
+    /// Request was malformed.
+    RequestMalformed,
+    /// Response received from the server was malformed.
+    ResponseMalformed,
+    /// An error happened when serializing to/from a [`Value`].
+    CborValueError,
+    /// An error happened while casting a type to different type,
+    /// including one [`Value`] type to another.
+    ConversionError,
+    /// These are unexpected errors, which should never really happen.
+    UnexpectedError,
+}
+
+impl From<ciborium::value::Error> for Error {
+    fn from(_e: ciborium::value::Error) -> Self {
+        Self::CborValueError
+    }
+}
+
+impl From<ciborium::Value> for Error {
+    fn from(_e: ciborium::Value) -> Self {
+        Self::ConversionError
+    }
+}
+
+impl From<core::num::TryFromIntError> for Error {
+    fn from(_e: core::num::TryFromIntError) -> Self {
+        Self::ConversionError
+    }
+}
diff --git a/secretkeeper/comm/src/data_types/mod.rs b/secretkeeper/comm/src/data_types/mod.rs
new file mode 100644
index 0000000..096777f
--- /dev/null
+++ b/secretkeeper/comm/src/data_types/mod.rs
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+//! Implements the data structures specified by SecretManagement.cddl in Secretkeeper HAL.
+//!  Data structures specified by SecretManagement.cddl in Secretkeeper HAL.
+//!  Note this library must stay in sync with:
+//!      platform/hardware/interfaces/security/\
+//!      secretkeeper/aidl/android/hardware/security/secretkeeper/SecretManagement.cddl
+
+pub mod error;
+pub mod packet;
+pub mod request;
+pub mod request_response_impl;
+pub mod response;
diff --git a/secretkeeper/comm/src/data_types/packet.rs b/secretkeeper/comm/src/data_types/packet.rs
new file mode 100644
index 0000000..7a1e575
--- /dev/null
+++ b/secretkeeper/comm/src/data_types/packet.rs
@@ -0,0 +1,118 @@
+/*
+ * 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.
+ */
+
+//! Defines the packet structures passed between functional layer & the layer below.
+
+pub use ciborium::Value;
+
+use crate::cbor_convert::{value_from_bytes, value_to_bytes, value_to_integer};
+use crate::data_types::error::Error;
+use crate::data_types::error::ERROR_OK;
+use crate::data_types::request_response_impl::Opcode;
+use alloc::vec::Vec;
+
+/// Encapsulate Request-like data that functional layer operates on. All structures
+/// that implements `data_types::request::Request` can be serialized to [`ResponsePacket`].
+/// Similarly all [`RequestPacket`] can be deserialized to concrete Requests.
+/// Keep in sync with HAL spec (in particular RequestPacket):
+///     security/secretkeeper/aidl/android/hardware/security/secretkeeper/SecretManagement.cddl
+#[derive(Clone, Debug, PartialEq)]
+pub struct RequestPacket(Vec<Value>);
+
+impl RequestPacket {
+    /// Construct a [`RequestPacket`] from array of `ciborium::Value`
+    pub fn from(request_cbor: Vec<Value>) -> Self {
+        Self(request_cbor)
+    }
+
+    /// Get the containing CBOR. This can be used for getting concrete response objects.
+    /// Keep in sync with [`crate::data_types::request::Request::serialize_to_packet()`]
+    pub fn into_inner(self) -> Vec<Value> {
+        self.0
+    }
+
+    /// Extract [`Opcode`] corresponding to this packet. As defined in by the spec, this is
+    /// the first value in the CBOR array.
+    pub fn opcode(&self) -> Result<Opcode, Error> {
+        if self.0.is_empty() {
+            return Err(Error::RequestMalformed);
+        }
+        let num: u16 = value_to_integer(&self.0[0])?.try_into()?;
+
+        Opcode::n(num).ok_or(Error::RequestMalformed)
+    }
+
+    /// Serialize the [`ResponsePacket`] to bytes
+    pub fn into_bytes(self) -> Result<Vec<u8>, Error> {
+        value_to_bytes(&Value::Array(self.0))
+    }
+
+    /// Deserialize the bytes into [`ResponsePacket`]
+    pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
+        Ok(RequestPacket(value_from_bytes(bytes)?.into_array()?))
+    }
+}
+
+/// Encapsulate Response like data that the functional layer operates on. All structures
+/// that implements `data_types::response::Response` can be serialized to [`ResponsePacket`].
+/// Similarly all [`ResponsePacket`] can be deserialized to concrete Response.
+#[derive(Clone, Debug, PartialEq)]
+pub struct ResponsePacket(Vec<Value>);
+
+impl ResponsePacket {
+    /// Construct a [`ResponsePacket`] from array of `ciborium::Value`
+    pub fn from(response_cbor: Vec<Value>) -> Self {
+        Self(response_cbor)
+    }
+
+    /// Get raw content. This can be used for getting concrete response objects.
+    /// Keep in sync with `crate::data_types::response::Response::serialize_to_packet`
+    pub fn into_inner(self) -> Vec<Value> {
+        self.0
+    }
+
+    /// A [`ResponsePacket`] encapsulates different types of responses, find which one!
+    pub fn response_type(&self) -> Result<ResponseType, Error> {
+        if self.0.is_empty() {
+            return Err(Error::ResponseMalformed);
+        }
+        let error_code: u16 = value_to_integer(&self.0[0])?.try_into()?;
+        if error_code == ERROR_OK {
+            Ok(ResponseType::Success)
+        } else {
+            Ok(ResponseType::Error)
+        }
+    }
+
+    /// Serialize the [`ResponsePacket`] to bytes
+    pub fn into_bytes(self) -> Result<Vec<u8>, Error> {
+        value_to_bytes(&Value::Array(self.0))
+    }
+
+    /// Deserialize the bytes into [`ResponsePacket`]
+    pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
+        Ok(ResponsePacket(value_from_bytes(bytes)?.into_array()?))
+    }
+}
+
+/// Responses can be different type - `Success`-like or `Error`-like.
+#[derive(Debug, Eq, PartialEq)]
+pub enum ResponseType {
+    /// Indicates successful operation. See `ResponsePacketSuccess` in SecretManagement.cddl
+    Success,
+    /// Indicate failed operation. See `ResponsePacketError` in SecretManagement.cddl
+    Error,
+}
diff --git a/secretkeeper/comm/src/data_types/request.rs b/secretkeeper/comm/src/data_types/request.rs
new file mode 100644
index 0000000..0d54bcd
--- /dev/null
+++ b/secretkeeper/comm/src/data_types/request.rs
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+
+//! Defines the shared behaviour of all request like data structures.
+
+use crate::data_types::error::Error;
+use crate::data_types::packet::RequestPacket;
+use crate::data_types::request_response_impl::Opcode;
+use alloc::boxed::Box;
+use alloc::vec::Vec;
+use ciborium::Value;
+
+/// Collection of methods defined for Secretkeeper's request-like data structures,
+/// e.g. `GetVersionRequestPacket` in the HAL spec.
+///
+/// Keep in sync with SecretManagement.cddl, in particular `RequestPacket` type.
+pub trait Request {
+    /// [`Opcode`] of the request: Each Request type is associated with an opcode. See `Opcode` in
+    /// SecretManagement.cddl.
+    const OPCODE: Opcode;
+
+    /// Constructor of the [`Request`] object. Implementation of this constructor should check
+    /// the args' type adheres to the HAL spec.
+    ///
+    /// # Arguments
+    /// * `args` - The vector of arguments associated with this request. Each argument is a
+    ///   `ciborium::Value` type. See `Params` in `RequestPacket` in SecretManagement.cddl
+    fn new(args: Vec<Value>) -> Result<Box<Self>, Error>;
+
+    /// Get the 'arguments' of this request.
+    fn args(&self) -> Vec<Value>;
+
+    /// Serialize the request to a [`RequestPacket`], which, as per SecretManagement.cddl is:
+    /// ```
+    ///      RequestPacket<Opcode, Params> = [
+    ///         Opcode,
+    ///         Params
+    ///      ]
+    /// ```
+    fn serialize_to_packet(&self) -> RequestPacket {
+        let mut res = self.args();
+        res.insert(0, Value::from(Self::OPCODE as u16));
+        RequestPacket::from(res)
+    }
+
+    /// Construct the [`Request`] struct from given [`RequestPacket`].
+    fn deserialize_from_packet(packet: RequestPacket) -> Result<Box<Self>, Error> {
+        let mut req = packet.into_inner();
+        if req.get(0) != Some(&Value::from(Self::OPCODE as u16)) {
+            return Err(Error::RequestMalformed);
+        }
+        req.remove(0);
+        Self::new(req)
+    }
+}
diff --git a/secretkeeper/comm/src/data_types/request_response_impl.rs b/secretkeeper/comm/src/data_types/request_response_impl.rs
new file mode 100644
index 0000000..a7d29cc
--- /dev/null
+++ b/secretkeeper/comm/src/data_types/request_response_impl.rs
@@ -0,0 +1,97 @@
+/*
+ * 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.
+ */
+
+//! Implementation of request & response like data structures.
+
+// derive(N) generates a method that is missing a docstring.
+#![allow(missing_docs)]
+
+use crate::cbor_convert::value_to_integer;
+use crate::data_types::error::Error;
+use crate::data_types::error::ERROR_OK;
+use crate::data_types::request::Request;
+use crate::data_types::response::Response;
+use alloc::boxed::Box;
+use alloc::vec;
+use alloc::vec::Vec;
+use ciborium::Value;
+use enumn::N;
+
+/// Set of all possible `Opcode` supported by SecretManagement API of the HAL.
+/// See `Opcode` in SecretManagement.cddl
+#[derive(Clone, Copy, Debug, N, PartialEq)]
+#[non_exhaustive]
+pub enum Opcode {
+    /// Get version of the SecretManagement API.
+    GetVersion = 1,
+    /// Store a secret
+    StoreSecret = 2,
+    /// Get the secret
+    GetSecret = 3,
+}
+
+/// Corresponds to `GetVersionRequestPacket` defined in SecretManagement.cddl
+#[derive(Debug, Eq, PartialEq)]
+pub struct GetVersionRequest;
+
+impl Request for GetVersionRequest {
+    const OPCODE: Opcode = Opcode::GetVersion;
+
+    fn new(args: Vec<Value>) -> Result<Box<Self>, Error> {
+        if !args.is_empty() {
+            return Err(Error::RequestMalformed);
+        }
+        Ok(Box::new(Self))
+    }
+
+    fn args(&self) -> Vec<Value> {
+        Vec::new()
+    }
+}
+
+/// Success response corresponding to `GetVersionResponsePacket`.
+#[derive(Debug, Eq, PartialEq)]
+pub struct GetVersionResponse {
+    /// Version of SecretManagement API
+    version: u64,
+}
+
+impl GetVersionResponse {
+    pub fn new(version: u64) -> Self {
+        Self { version }
+    }
+    pub fn version(&self) -> u64 {
+        self.version
+    }
+}
+
+impl Response for GetVersionResponse {
+    fn new(res: Vec<Value>) -> Result<Box<Self>, Error> {
+        if res.len() != 2 {
+            return Err(Error::ResponseMalformed);
+        }
+        let error_code: u16 = value_to_integer(&res[0])?.try_into()?;
+        if error_code != ERROR_OK {
+            return Err(Error::ResponseMalformed);
+        }
+        let version: u64 = value_to_integer(&res[1])?.try_into()?;
+        Ok(Box::new(Self::new(version)))
+    }
+
+    fn result(&self) -> Vec<Value> {
+        vec![self.version.into()]
+    }
+}
diff --git a/secretkeeper/comm/src/data_types/response.rs b/secretkeeper/comm/src/data_types/response.rs
new file mode 100644
index 0000000..e975ebc
--- /dev/null
+++ b/secretkeeper/comm/src/data_types/response.rs
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+
+//! Defines the shared behaviour of all response like data structures.
+
+use crate::data_types::error::{Error, ERROR_OK};
+use crate::data_types::packet::ResponsePacket;
+use alloc::boxed::Box;
+use alloc::vec::Vec;
+use ciborium::Value;
+
+/// Shared behaviour of all Secretkeeper's response-like data structures,
+/// e.g. `GetVersionResponsePacket`. Note - A valid [`Response`] can be error as well, like
+/// `SecretkeeperError::RequestMalformed`.
+///
+/// Keep in sync with SecretManagement.cddl, in particular `ResponsePacket` type.
+pub trait Response {
+    /// Constructor of the Response object.
+    /// # Arguments
+    /// * `response_cbor`: A vector of `[ciborium::Value]` such that:
+    /// ```
+    ///     For success-like responses:
+    ///         ResponsePacketSuccess = [
+    ///             0,                          ; Indicates successful Response
+    ///             result : Result
+    ///         ]
+    ///     For error responses:
+    ///         ResponsePacketError = [
+    ///             error_code: ErrorCode,      ; Indicate the error
+    ///             error_message: tstr         ; Additional human-readable context
+    ///         ]
+    /// ```
+    /// See ResponsePacket<Result> in SecretManagement.cddl alongside ISecretkeeper.aidl
+    fn new(response_cbor: Vec<Value>) -> Result<Box<Self>, Error>;
+
+    /// The result in the `Response`. By default this is empty, but [`Response`] structures like
+    /// `GetVersionResponse` must overwrite these to return the expected non-empty result.
+    fn result(&self) -> Vec<Value> {
+        Vec::new()
+    }
+
+    /// Error code corresponding to the response. The default value is 0 but that will work only
+    /// for successful responses. Error-like response structures must overwrite this method.
+    fn error_code(&self) -> u16 {
+        ERROR_OK // Indicates success
+    }
+
+    /// Serialize the response to a [`ResponsePacket`].
+    fn serialize_to_packet(&self) -> ResponsePacket {
+        let mut res = self.result();
+        res.insert(0, Value::from(self.error_code()));
+        ResponsePacket::from(res)
+    }
+
+    /// Construct the response struct from given [`ResponsePacket`].
+    fn deserialize_from_packet(packet: ResponsePacket) -> Result<Box<Self>, Error> {
+        let res = packet.into_inner();
+        // Empty response packet is not allowed, all responses in Secretkeeper HAL at least
+        // have `error_code` or '0'; so throw an error!
+        if res.is_empty() {
+            return Err(Error::ResponseMalformed);
+        }
+        Self::new(res)
+    }
+}
diff --git a/secretkeeper/comm/src/lib.rs b/secretkeeper/comm/src/lib.rs
new file mode 100644
index 0000000..9a10ac0
--- /dev/null
+++ b/secretkeeper/comm/src/lib.rs
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+//! This library exposes data structures and methods that can be used by Secretkeeper HAL & client
+//! implementation. This is compatible with Secretkeeper HAL specification.
+
+#![no_std]
+extern crate alloc;
+
+mod cbor_convert;
+pub mod data_types;
diff --git a/secretkeeper/comm/tests/data_types.rs b/secretkeeper/comm/tests/data_types.rs
new file mode 100644
index 0000000..68964fd
--- /dev/null
+++ b/secretkeeper/comm/tests/data_types.rs
@@ -0,0 +1,132 @@
+/*
+ * 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.
+ */
+
+//! Unit tests for testing serialization & deserialization of exported data_types.
+
+use ciborium::Value;
+use secretkeeper_comm::data_types::error::{Error, SecretkeeperError, ERROR_OK};
+use secretkeeper_comm::data_types::packet::{RequestPacket, ResponsePacket, ResponseType};
+use secretkeeper_comm::data_types::request::Request;
+use secretkeeper_comm::data_types::request_response_impl::Opcode;
+use secretkeeper_comm::data_types::request_response_impl::{GetVersionRequest, GetVersionResponse};
+use secretkeeper_comm::data_types::response::Response;
+
+#[cfg(test)]
+rdroidtest::test_main!();
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use rdroidtest::test;
+
+    test!(request_serialization_deserialization);
+    fn request_serialization_deserialization() {
+        let req = GetVersionRequest {};
+        let packet = req.serialize_to_packet();
+        assert_eq!(packet.opcode().unwrap(), Opcode::GetVersion);
+        assert_eq!(
+            RequestPacket::from_bytes(&packet.clone().into_bytes().unwrap()).unwrap(),
+            packet
+        );
+        let req_deserialized = *GetVersionRequest::deserialize_from_packet(packet).unwrap();
+        assert_eq!(req, req_deserialized);
+    }
+
+    test!(success_response_serialization_deserialization);
+    fn success_response_serialization_deserialization() {
+        let response = GetVersionResponse::new(1);
+        let packet = response.serialize_to_packet();
+        assert_eq!(packet.response_type().unwrap(), ResponseType::Success);
+        assert_eq!(
+            ResponsePacket::from_bytes(&packet.clone().into_bytes().unwrap()).unwrap(),
+            packet
+        );
+        let response_deserialized = *GetVersionResponse::deserialize_from_packet(packet).unwrap();
+        assert_eq!(response, response_deserialized);
+    }
+
+    test!(error_response_serialization_deserialization);
+    fn error_response_serialization_deserialization() {
+        let response = SecretkeeperError::RequestMalformed;
+        let packet = response.serialize_to_packet();
+        assert_eq!(packet.response_type().unwrap(), ResponseType::Error);
+        assert_eq!(
+            ResponsePacket::from_bytes(&packet.clone().into_bytes().unwrap()).unwrap(),
+            packet
+        );
+        let response_deserialized = *SecretkeeperError::deserialize_from_packet(packet).unwrap();
+        assert_eq!(response, response_deserialized);
+    }
+
+    test!(request_creation);
+    fn request_creation() {
+        let req: GetVersionRequest = *Request::new(vec![]).unwrap();
+        assert_eq!(req, GetVersionRequest {});
+    }
+
+    test!(response_creation);
+    fn response_creation() {
+        let res: GetVersionResponse =
+            *Response::new(vec![Value::from(ERROR_OK), Value::from(5)]).unwrap();
+        assert_eq!(res.version(), 5);
+    }
+
+    test!(invalid_get_version_request_creation);
+    fn invalid_get_version_request_creation() {
+        // A request with non-zero arg is considered invalid.
+        assert_eq!(
+            <GetVersionRequest as Request>::new(vec![Value::Null]).unwrap_err(),
+            Error::RequestMalformed
+        );
+    }
+
+    test!(invalid_get_version_response_creation);
+    fn invalid_get_version_response_creation() {
+        // A response with non-zero error_code is an invalid success response.
+        assert_eq!(
+            <GetVersionResponse as Response>::new(vec![
+                Value::from(SecretkeeperError::RequestMalformed as u16),
+                Value::from(5)
+            ])
+            .unwrap_err(),
+            Error::ResponseMalformed
+        );
+
+        // A response with incorrect size of array is invalid.
+        assert_eq!(
+            <GetVersionResponse as Response>::new(vec![
+                Value::from(ERROR_OK),
+                Value::from(5),
+                Value::from(7)
+            ])
+            .unwrap_err(),
+            Error::ResponseMalformed
+        );
+
+        // A response with incorrect type is invalid.
+        <GetVersionResponse as Response>::new(vec![Value::from(ERROR_OK), Value::from("a tstr")])
+            .unwrap_err();
+    }
+
+    test!(invalid_error_response_creation);
+    fn invalid_error_response_creation() {
+        // A response with ERROR_OK(0) as the error_code is an invalid error response.
+        assert_eq!(
+            <SecretkeeperError as Response>::new(vec![Value::from(ERROR_OK)]).unwrap_err(),
+            Error::ResponseMalformed
+        );
+    }
+}
diff --git a/secretkeeper/dice_policy/src/lib.rs b/secretkeeper/dice_policy/src/lib.rs
index 2e91305..076ba3b 100644
--- a/secretkeeper/dice_policy/src/lib.rs
+++ b/secretkeeper/dice_policy/src/lib.rs
@@ -213,10 +213,10 @@
         ConstraintType::GreaterOrEqual => {
             let value_in_node = value_in_node
                 .as_integer()
-                .ok_or(anyhow!("Mismatch type: expected a cbor integer"))?;
+                .ok_or(anyhow!("Mismatch type: expected a CBOR integer"))?;
             let value_min = value_in_constraint
                 .as_integer()
-                .ok_or(anyhow!("Mismatch type: expected a cbor integer"))?;
+                .ok_or(anyhow!("Mismatch type: expected a CBOR integer"))?;
             ensure!(value_in_node >= value_min);
         }
     };
@@ -260,9 +260,9 @@
         Value::Bytes(b) => value_from_bytes(b)?
             .into_map()
             .map(Cow::Owned)
-            .map_err(|e| anyhow!("Expected a cbor map: {:?}", e)),
+            .map_err(|e| anyhow!("Expected a CBOR map: {:?}", e)),
         Value::Map(map) => Ok(Cow::Borrowed(map)),
-        _ => bail!("/Expected a cbor map {:?}", cbor_map),
+        _ => bail!("Expected a CBOR map {:?}", cbor_map),
     }
 }
 
diff --git a/service_vm/requests/src/client_vm.rs b/service_vm/requests/src/client_vm.rs
index 74c26d3..612605f 100644
--- a/service_vm/requests/src/client_vm.rs
+++ b/service_vm/requests/src/client_vm.rs
@@ -17,22 +17,39 @@
 
 use crate::keyblob::decrypt_private_key;
 use alloc::vec::Vec;
-use bssl_avf::EcKey;
+use bssl_avf::{sha256, EcKey};
 use core::result;
 use coset::{CborSerializable, CoseSign};
 use diced_open_dice::DiceArtifacts;
 use log::error;
-use service_vm_comm::{ClientVmAttestationParams, Csr, RequestProcessingError};
+use service_vm_comm::{ClientVmAttestationParams, Csr, CsrPayload, RequestProcessingError};
 
 type Result<T> = result::Result<T, RequestProcessingError>;
 
+const ATTESTATION_KEY_SIGNATURE_INDEX: usize = 1;
+
 pub(super) fn request_attestation(
     params: ClientVmAttestationParams,
     dice_artifacts: &dyn DiceArtifacts,
 ) -> Result<Vec<u8>> {
     let csr = Csr::from_cbor_slice(&params.csr)?;
-    let _cose_sign = CoseSign::from_slice(&csr.signed_csr_payload)?;
-    // TODO(b/309440321): Verify the signatures in the `_cose_sign`.
+    let cose_sign = CoseSign::from_slice(&csr.signed_csr_payload)?;
+    let csr_payload = cose_sign.payload.as_ref().ok_or_else(|| {
+        error!("No CsrPayload found in the CSR");
+        RequestProcessingError::InternalError
+    })?;
+    let csr_payload = CsrPayload::from_cbor_slice(csr_payload)?;
+
+    // AAD is empty as defined in service_vm/comm/client_vm_csr.cddl.
+    let aad = &[];
+
+    // TODO(b/310931749): Verify the first signature with CDI_Leaf_Pub of
+    // the DICE chain in `cose_sign`.
+
+    let ec_public_key = EcKey::from_cose_public_key(&csr_payload.public_key)?;
+    cose_sign.verify_signature(ATTESTATION_KEY_SIGNATURE_INDEX, aad, |signature, message| {
+        ecdsa_verify(&ec_public_key, signature, message)
+    })?;
 
     // TODO(b/278717513): Compare client VM's DICE chain in the `csr` up to pvmfw
     // cert with RKP VM's DICE chain.
@@ -49,3 +66,9 @@
     // `_private_key`.
     Err(RequestProcessingError::OperationUnimplemented)
 }
+
+fn ecdsa_verify(key: &EcKey, signature: &[u8], message: &[u8]) -> bssl_avf::Result<()> {
+    // The message was signed with ECDSA with curve P-256 and SHA-256 at the signature generation.
+    let digest = sha256(message)?;
+    key.ecdsa_verify(signature, &digest)
+}
diff --git a/service_vm/requests/src/rkp.rs b/service_vm/requests/src/rkp.rs
index c2c13b3..9901a92 100644
--- a/service_vm/requests/src/rkp.rs
+++ b/service_vm/requests/src/rkp.rs
@@ -44,7 +44,8 @@
     dice_artifacts: &dyn DiceArtifacts,
 ) -> Result<EcdsaP256KeyPair> {
     let hmac_key = derive_hmac_key(dice_artifacts)?;
-    let ec_key = EcKey::new_p256()?;
+    let mut ec_key = EcKey::new_p256()?;
+    ec_key.generate_key()?;
 
     let maced_public_key = build_maced_public_key(ec_key.cose_public_key()?, hmac_key.as_ref())?;
     let key_blob =
@@ -75,10 +76,13 @@
         public_keys.push(public_key.to_cbor_value()?);
     }
     // Builds `CsrPayload`.
+    // TODO(b/299256925): The device information is currently empty as we do not
+    // have sufficient details to include.
+    let device_info = Value::Map(Vec::new());
     let csr_payload = cbor!([
         Value::Integer(CSR_PAYLOAD_SCHEMA_V3.into()),
         Value::Text(String::from(CERTIFICATE_TYPE)),
-        // TODO(b/299256925): Add device info in CBOR format here.
+        device_info,
         Value::Array(public_keys),
     ])?;
     let csr_payload = cbor_util::serialize(&csr_payload)?;
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index 001dfeb..886ca81 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -450,7 +450,9 @@
                         key, keyOverrides, /* isProtected= */ false, /* updateBootconfigs= */ true);
         assertThatEventually(
                 100000,
-                () -> getDevice().pullFileContents(LOG_PATH),
+                () ->
+                        getDevice().pullFileContents(CONSOLE_PATH)
+                                + getDevice().pullFileContents(LOG_PATH),
                 containsString("boot completed, time to run payload"));
 
         vmInfo.mProcess.destroy();
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index c6291e4..b06eea6 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -1293,7 +1293,7 @@
 
         assertThat(payloadStarted.getNow(false)).isTrue();
         assertThat(exitCodeFuture.getNow(0)).isNotEqualTo(0);
-        assertThat(listener.getLogOutput()).contains(reason);
+        assertThat(listener.getConsoleOutput() + listener.getLogOutput()).contains(reason);
     }
 
     @Test
@@ -1698,7 +1698,7 @@
                         .command(
                                 "logcat",
                                 "-e",
-                                "virtualizationmanager::aidl: Log.*executing main task",
+                                "virtualizationmanager::aidl: (Console|Log).*executing main task",
                                 "-t",
                                 time)
                         .start();
diff --git a/virtualizationservice/Android.bp b/virtualizationservice/Android.bp
index bef7dd0..5cf2a39 100644
--- a/virtualizationservice/Android.bp
+++ b/virtualizationservice/Android.bp
@@ -31,6 +31,7 @@
         "libanyhow",
         "libavflog",
         "libbinder_rs",
+        "libhypervisor_props",
         "liblibc",
         "liblog_rust",
         "libnix",
diff --git a/virtualizationservice/src/main.rs b/virtualizationservice/src/main.rs
index d80ddd4..ea073bf 100644
--- a/virtualizationservice/src/main.rs
+++ b/virtualizationservice/src/main.rs
@@ -34,7 +34,7 @@
 
 const LOG_TAG: &str = "VirtualizationService";
 pub(crate) const REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME: &str =
-    "android.system.virtualization.IRemotelyProvisionedComponent/avf";
+    "android.hardware.security.keymint.IRemotelyProvisionedComponent/avf";
 
 fn get_calling_pid() -> pid_t {
     ThreadState::get_calling_pid()
@@ -69,10 +69,17 @@
     register_lazy_service(BINDER_SERVICE_IDENTIFIER, service.as_binder()).unwrap();
     info!("Registered Binder service {}.", BINDER_SERVICE_IDENTIFIER);
 
-    // The IRemotelyProvisionedComponent service is only supposed to be triggered by rkpd for
-    // RKP VM attestation.
-    let _remote_provisioning_service = remote_provisioning::new_binder();
-    // TODO(b/274881098): Register the RKP service when the implementation is ready.
+    if cfg!(remote_attestation) {
+        // The IRemotelyProvisionedComponent service is only supposed to be triggered by rkpd for
+        // RKP VM attestation.
+        let remote_provisioning_service = remote_provisioning::new_binder();
+        register_lazy_service(
+            REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME,
+            remote_provisioning_service.as_binder(),
+        )
+        .unwrap();
+        info!("Registered Binder service {}.", REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME);
+    }
 
     ProcessState::join_thread_pool();
 }
diff --git a/virtualizationservice/src/remote_provisioning.rs b/virtualizationservice/src/remote_provisioning.rs
index a9a07a5..40f54db 100644
--- a/virtualizationservice/src/remote_provisioning.rs
+++ b/virtualizationservice/src/remote_provisioning.rs
@@ -27,7 +27,11 @@
 };
 use anyhow::Context;
 use avflog::LogResult;
-use binder::{BinderFeatures, Interface, IntoBinderResult, Result as BinderResult, Status, Strong};
+use binder::{
+    BinderFeatures, ExceptionCode, Interface, IntoBinderResult, Result as BinderResult, Status,
+    Strong,
+};
+use hypervisor_props::is_protected_vm_supported;
 use service_vm_comm::{RequestProcessingError, Response};
 
 /// Constructs a binder object that implements `IRemotelyProvisionedComponent`.
@@ -45,11 +49,13 @@
 #[allow(non_snake_case)]
 impl IRemotelyProvisionedComponent for AvfRemotelyProvisionedComponent {
     fn getHardwareInfo(&self) -> BinderResult<RpcHardwareInfo> {
+        check_protected_vm_is_supported()?;
+
         Ok(RpcHardwareInfo {
             versionNumber: 3,
             rpcAuthorName: String::from("Android Virtualization Framework"),
             supportedEekCurve: CURVE_NONE,
-            uniqueId: Some(String::from("Android Virtualization Framework 1")),
+            uniqueId: Some(String::from("AVF Remote Provisioning 1")),
             supportedNumKeysInCsr: MIN_SUPPORTED_NUM_KEYS_IN_CSR,
         })
     }
@@ -59,6 +65,8 @@
         testMode: bool,
         macedPublicKey: &mut MacedPublicKey,
     ) -> BinderResult<Vec<u8>> {
+        check_protected_vm_is_supported()?;
+
         if testMode {
             return Err(Status::new_service_specific_error_str(
                 STATUS_REMOVED,
@@ -101,6 +109,8 @@
         keysToSign: &[MacedPublicKey],
         challenge: &[u8],
     ) -> BinderResult<Vec<u8>> {
+        check_protected_vm_is_supported()?;
+
         const MAX_CHALLENGE_SIZE: usize = 64;
         if challenge.len() > MAX_CHALLENGE_SIZE {
             let message = format!(
@@ -123,6 +133,18 @@
     }
 }
 
+fn check_protected_vm_is_supported() -> BinderResult<()> {
+    if is_protected_vm_supported().unwrap_or(false) {
+        Ok(())
+    } else {
+        Err(Status::new_exception_str(
+            ExceptionCode::UNSUPPORTED_OPERATION,
+            Some("Protected VM support is missing for this operation"),
+        ))
+        .with_log()
+    }
+}
+
 fn to_service_specific_error(response: Response) -> Status {
     match response {
         Response::Err(e) => match e {