Merge "Use release_avf_microdroid_kernel_version to pick Microdroid kernel" into main
diff --git a/Android.bp b/Android.bp
index 717c902..6e7c3c8 100644
--- a/Android.bp
+++ b/Android.bp
@@ -89,3 +89,19 @@
     tools: ["dtc"],
     cmd: "FILES=($(in)) && $(location dtc) -I dts -O dtb $${FILES[-1]} -o $(out)",
 }
+
+// This is a temporary workaround until b/309090563 is implemented.
+aconfig_declarations {
+    name: "avf_aconfig_flags",
+    package: "com.android.system.virtualmachine.flags",
+    srcs: [
+        "avf_flags.aconfig",
+    ],
+}
+
+java_aconfig_library {
+    name: "avf_aconfig_flags_java",
+    aconfig_declarations: "avf_aconfig_flags",
+    sdk_version: "module_current",
+    apex_available: ["com.android.virt"],
+}
diff --git a/README.md b/README.md
index 3935f93..210f70e 100644
--- a/README.md
+++ b/README.md
@@ -29,3 +29,4 @@
 * [Building and running a demo app in C++](demo_native/README.md)
 * [Debugging](docs/debug)
 * [Using custom VM](docs/custom_vm.md)
+* [Device assignment](docs/device_assignment.md)
\ No newline at end of file
diff --git a/apex/Android.bp b/apex/Android.bp
index 7cc0414..cc59b16 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -41,10 +41,12 @@
         "release_avf_enable_llpvm_changes",
         "release_avf_enable_remote_attestation",
         "release_avf_enable_vendor_modules",
+        "release_avf_enable_virt_cpufreq",
     ],
     properties: [
         "androidManifest",
         "arch",
+        "canned_fs_config",
         "prebuilts",
         "systemserverclasspath_fragments",
         "vintf_fragments",
@@ -68,7 +70,6 @@
     ],
 
     file_contexts: ":com.android.virt-file_contexts",
-    canned_fs_config: "canned_fs_config",
 
     bootclasspath_fragments: [
         "com.android.virt-bootclasspath-fragment",
@@ -90,6 +91,12 @@
                 "com.android.virt-systemserver-fragment",
             ],
         },
+        release_avf_enable_virt_cpufreq: {
+            canned_fs_config: "canned_fs_config_sys_nice",
+            conditions_default: {
+                canned_fs_config: "canned_fs_config",
+            },
+        },
     },
 }
 
diff --git a/apex/canned_fs_config_sys_nice b/apex/canned_fs_config_sys_nice
new file mode 100644
index 0000000..5b12eb5
--- /dev/null
+++ b/apex/canned_fs_config_sys_nice
@@ -0,0 +1,2 @@
+/bin/virtualizationservice 0 2000 0755 capabilities=0x1000001  # CAP_CHOWN, CAP_SYS_RESOURCE
+/bin/crosvm 0 3013 0755 capabilities=0x800000  # SYS_NICE
diff --git a/avf_flags.aconfig b/avf_flags.aconfig
new file mode 100644
index 0000000..8abb9ee
--- /dev/null
+++ b/avf_flags.aconfig
@@ -0,0 +1,10 @@
+package: "com.android.system.virtualmachine.flags"
+
+flag {
+  name: "avf_v_test_apis"
+  namespace: "virtualization"
+  description: "Only purpose of this flag is to be used in @FlaggedApi in our V test apis"
+  bug: "325441024"
+  is_fixed_read_only: true
+}
+
diff --git a/docs/device_assignment.md b/docs/device_assignment.md
new file mode 100644
index 0000000..4c5b477
--- /dev/null
+++ b/docs/device_assignment.md
@@ -0,0 +1,239 @@
+# Getting started with device assignment
+
+Device assignment allows a VM to have direct access to HW without host/hyp
+intervention. AVF uses `vfio-platform` for device assignment, and host kernel
+support is required.
+
+This document explains how to setup and launch VM with device assignments.
+
+## VM device assignment DTBO (a.k.a. VM DTBO)
+
+For device assignment, a VM device assignment DTBO (a.k.a. VM DTBO) is required.
+VM DTBO is a device tree overlay which describes all assignable devices
+information. Information includes physical reg, IOMMU, device properties, and
+dependencies.
+
+VM DTBO allows to pass extra properties of assignable platform
+devices to the VM (which can't be discovered from the HW) while keeping the VMM
+device-agnostic.
+
+When the host boots, the bootloader provides VM DTBO to both Android and pvmfw.
+
+When a VM boots, the VMM selectively applies the DTBO based from provided
+labels, describing the assigned devices.
+
+## Prepare VM DTBO
+
+VM DTBO should be included in the dtbo partition. It should be in its own
+entry, and not together with any host OS's. See [DTB/DTBO Paritions] for
+partition format.
+
+[DTB/DTBO Paritions]: https://source.android.com/docs/core/architecture/dto/partitions
+
+### Write VM DTS for VM DTBO
+
+DTBO is compiled from device tree source (DTS) with `dtc` tool. [DTBO syntax]
+explains basic syntax of DTS.
+
+[DTBO syntax]: https://source.android.com/docs/core/architecture/dto/syntax
+
+Here are details and requirements:
+
+#### Describe assignable devices
+
+VM DTBO should describe assignable devices and their labels.
+
+* VM DTBO should have assignable devices in the `&{/}`, so it can be
+  overlaid onto VM DT. Assignable devices should be backed by physical device.
+  * We only support overlaying onto root node (i.e. `&{/}`) to prevent
+    unexpected modification of VM DT.
+* VM DTBO should have labels for assignable devices, so AVF can recognize
+  assignable device list. Labels should point to valid 'overlayable' nodes.
+  * Overlayable node is a node that would be applied to the base device tree
+    when DTBO is applied.
+
+#### Describe physical devices and physical IOMMUs
+
+VM DTBO should describe a `/host` node which describes physical devices and
+physical IOMMUs. The `/host` node only describes information for verification of
+assigned devices, and wouldn't be applied to VM DT. Here are details:
+
+* Physical IOMMU nodes
+  * IOMMU nodes must have a phandle to be referenced by a physical device node.
+  * IOMMU nodes must have `<android,pvmfw,token>` property. The property
+    describes the IOMMU token. An IOMMU token is a hypervisor-specific `<u64>`
+    which uniquely identifies a physical IOMMU. IOMMU token must be constant
+    across the VM boot for provisioning by pvmfw remains valid. The token must
+    be kept up-to-date across hypervisor updates.
+  * IOMMU nodes should be multi-master IOMMUs. (i.e. `#iommu-cells = <1>`)
+    * Other `#iommu-cells` values aren't supported for now.
+    * See: [Device tree binding for IOMMUs][IOMMU]
+* Physical device nodes
+  * Physical device nodes must have a `<android,pvmfw,target>` property that
+    references an overlayable node. The overlayable node contains the properties
+    that would be included in VM DT.
+  * Physical device nodes must have `<reg>` property to provide physical
+    regions.
+  * Physical device nodes can optionally contain `<iommus>` property. The
+    property is a prop-encoded-array and contains a number of
+    (iommu phandle, SID) pairs.
+    * IOMMU can be shared among devices, but should use distinct SIDs. Sharing
+      the same IOMMU-SID pair among multiple devices isn't supported for now.
+
+[IOMMU]: https://www.kernel.org/doc/Documentation/devicetree/bindings/iommu/iommu.txt
+
+#### Describe dependencies
+
+VM DTBO may have dependencies via phandle references. When a device node is
+assigned, dependencies of the node are also applied to VM DT.
+
+When dependencies are applied, siblings or children nodes of dependencies are
+ignored unless explicitly referenced.
+
+#### VM DTBO example
+
+Here's a simple example device tree source with four assignable devices nodes.
+
+```dts
+/dts-v1/;
+/plugin/;
+
+/ {
+    // host node describes physical devices and IOMMUs, and wouldn't be applied to VM DT
+    host {
+        #address-cells = <0x2>;
+        #size-cells = <0x1>;
+        rng {
+            reg = <0x0 0x12f00000 0x1000>;
+            iommus = <&iommu0 0x3>;
+            android,pvmfw,target = <&rng>;
+        };
+        light {
+            reg = <0x0 0x00f00000 0x1000>, <0x0 0x00f10000 0x1000>;
+            iommus = <&iommu1 0x4>, <&iommu2 0x5>;
+            android,pvmfw,target = <&light>;
+        };
+        led {
+            reg = <0x0 0x12000000 0x1000>;
+            iommus = <&iommu1 0x3>;
+            android,pvmfw,target = <&led>;
+        };
+        bus0 {
+            #address-cells = <0x1>;
+            #size-cells = <0x1>;
+            backlight {
+                reg = <0x300 0x100>;
+                android,pvmfw,target = <&backlight>;
+            };
+        };
+        iommu0: iommu0 {
+            #iommu-cells = <0x1>;
+            android,pvmfw,token = <0x0 0x12e40000>;
+        };
+        iommu1: iommu1 {
+            #iommu-cells = <0x1>;
+            android,pvmfw,token = <0x0 0x40000>;
+        };
+        iommu2: iommu2 {
+            #iommu-cells = <0x1>;
+            android,pvmfw,token = <0x0 0x50000>;
+        };
+    };
+};
+
+// Beginning of the assignable devices. Assigned devices would be applied to VM DT
+&{/} {  // We only allows to overlay to root node
+    rng: rng {
+        compatible = "android,rng";
+        android,rng,ignore-gctrl-reset;
+    };
+    light: light {
+        compatible = "android,light";
+        version = <0x1 0x2>;
+    };
+    led: led {
+        compatible = "android,led";
+        prop = <0x555>;
+    };
+    bus0 {
+        backlight: backlight {
+            compatible = "android,backlight";
+            android,backlight,ignore-gctrl-reset;
+        };
+    };
+};
+```
+
+If you compile the above with `dtc -@`, then you'll get `__symbols__` for free.
+The generated `__symbols__` indicates that there are four assignable devices.
+
+```dts
+    // generated __symbols__. AVF will ignore non-overlayable nodes.
+    __symbols__ {
+        iommu0 = "/host/iommu0";
+        iommu1 = "/host/iommu1";
+        iommu2 = "/host/iommu2";
+        rng = "/fragment@rng/__overlay__/rng";
+        light = "/fragment@sensor/__overlay__/light";
+        led = "/fragment@led/__overlay__/led";
+        backlight = "/fragment@backlight/__overlay__/bus0/backlight";
+    };
+```
+
+## Prepare AVF assignable devices XML
+
+AVF requires assignable device information to unbind from the host device driver
+and bind to VFIO driver. The information should be provided in an XML file at
+`/vendor/etc/avf/assignable_devices.xml`.
+
+Here's example.
+
+```xml
+<devices>
+    <device>
+        <kind>sensor</kind>
+        <dtbo_label>light</dtbo_label>
+        <sysfs_path>/sys/bus/platform/devices/16d00000.light</sysfs_path>
+    </device>
+</devices>
+```
+
+* `<kind>`: Device kind. Currently only used for debugging purposes and not used
+  for device assignment.
+* `<dtbo_label>`: Label in the VM DTBO (i.e. symbols in `__symbols__`). Must be
+  unique.
+* `<sysfs_path>`: Sysfs path of the device in host, used to bind to the VFIO
+  driver. Must be unique in the XML.
+
+## Boot with VM DTBO
+
+Bootloader should provide VM DTBO to both Android and pvmfw.
+
+### Provide VM DTBO index in dtbo.img
+
+Bootloader should provide the VM DTBO index with sysprop
+`ro.boot.hypervisor.vm_dtbo_idx.`. DTBO index represents DTBO location in
+dtbo.img.
+
+### Provide VM DTBO in the pvmfw config
+
+For protected VM, bootloader must provide VM DTBO to the pvmfw. pvmfw sanitizes
+incoming device tree with the VM DTBO.
+
+For more detail about providing VM DTBO in pvmfw,
+see: [pvmfw/README.md](../pvmfw/README.md#configuration-data-format)
+
+
+## Launch VM with device assignment
+
+We don't support client API yet in Android V, but you can use CLI to test device
+assignment. Note that host kernel support is required.
+
+Specify `--devices ${sysfs_path}` when booting VM. The parameter can be repeated
+multiple times for specifying multiple devices.
+
+Here's an example:
+
+```sh
+adb shell /apex/com.android.virt/bin/vm run-microdroid --devices /sys/bus/platform/devices/16d00000.light
+```
\ No newline at end of file
diff --git a/javalib/Android.bp b/javalib/Android.bp
index e3cb2e3..a7c531e 100644
--- a/javalib/Android.bp
+++ b/javalib/Android.bp
@@ -33,6 +33,7 @@
     srcs: ["src/**/*.java"],
     static_libs: [
         "android.system.virtualizationservice-java",
+        "avf_aconfig_flags_java",
         // For android.sysprop.HypervisorProperties
         "PlatformProperties",
     ],
diff --git a/javalib/api/test-current.txt b/javalib/api/test-current.txt
index 3ea50e2..0a988d8 100644
--- a/javalib/api/test-current.txt
+++ b/javalib/api/test-current.txt
@@ -2,33 +2,33 @@
 package android.system.virtualmachine {
 
   public class VirtualMachine implements java.lang.AutoCloseable {
-    method @FlaggedApi("RELEASE_AVF_ENABLE_REMOTE_ATTESTATION") @RequiresPermission(android.system.virtualmachine.VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION) public void enableTestAttestation() throws android.system.virtualmachine.VirtualMachineException;
+    method @FlaggedApi("com.android.system.virtualmachine.flags.avf_v_test_apis") @RequiresPermission(android.system.virtualmachine.VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION) public void enableTestAttestation() throws android.system.virtualmachine.VirtualMachineException;
     method @NonNull @WorkerThread public java.io.OutputStream getConsoleInput() throws android.system.virtualmachine.VirtualMachineException;
     method @NonNull public java.io.File getRootDir();
   }
 
   public final class VirtualMachineConfig {
-    method @FlaggedApi("RELEASE_AVF_ENABLE_MULTI_TENANT_MICRODROID_VM") @NonNull public java.util.List<java.lang.String> getExtraApks();
-    method @FlaggedApi("RELEASE_AVF_ENABLE_VENDOR_MODULES") @Nullable public String getOs();
+    method @FlaggedApi("com.android.system.virtualmachine.flags.avf_v_test_apis") @NonNull public java.util.List<java.lang.String> getExtraApks();
+    method @FlaggedApi("com.android.system.virtualmachine.flags.avf_v_test_apis") @Nullable public String getOs();
     method @Nullable public String getPayloadConfigPath();
     method public boolean isVmConsoleInputSupported();
   }
 
   public static final class VirtualMachineConfig.Builder {
-    method @FlaggedApi("RELEASE_AVF_ENABLE_MULTI_TENANT_MICRODROID_VM") @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder addExtraApk(@NonNull String);
-    method @FlaggedApi("RELEASE_AVF_ENABLE_VENDOR_MODULES") @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setOs(@NonNull String);
+    method @FlaggedApi("com.android.system.virtualmachine.flags.avf_v_test_apis") @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder addExtraApk(@NonNull String);
+    method @FlaggedApi("com.android.system.virtualmachine.flags.avf_v_test_apis") @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setOs(@NonNull String);
     method @NonNull @RequiresPermission(android.system.virtualmachine.VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION) public android.system.virtualmachine.VirtualMachineConfig.Builder setPayloadConfigPath(@NonNull String);
-    method @FlaggedApi("RELEASE_AVF_ENABLE_VENDOR_MODULES") @NonNull @RequiresPermission(android.system.virtualmachine.VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION) public android.system.virtualmachine.VirtualMachineConfig.Builder setVendorDiskImage(@NonNull java.io.File);
+    method @FlaggedApi("com.android.system.virtualmachine.flags.avf_v_test_apis") @NonNull @RequiresPermission(android.system.virtualmachine.VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION) public android.system.virtualmachine.VirtualMachineConfig.Builder setVendorDiskImage(@NonNull java.io.File);
     method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setVmConsoleInputSupported(boolean);
   }
 
   public class VirtualMachineManager {
-    method @FlaggedApi("RELEASE_AVF_ENABLE_VENDOR_MODULES") @NonNull public java.util.List<java.lang.String> getSupportedOSList() throws android.system.virtualmachine.VirtualMachineException;
-    method @RequiresPermission(android.system.virtualmachine.VirtualMachine.MANAGE_VIRTUAL_MACHINE_PERMISSION) public boolean isFeatureEnabled(String) throws android.system.virtualmachine.VirtualMachineException;
-    field public static final String FEATURE_DICE_CHANGES = "com.android.kvm.DICE_CHANGES";
-    field public static final String FEATURE_MULTI_TENANT = "com.android.kvm.MULTI_TENANT";
-    field public static final String FEATURE_REMOTE_ATTESTATION = "com.android.kvm.REMOTE_ATTESTATION";
-    field public static final String FEATURE_VENDOR_MODULES = "com.android.kvm.VENDOR_MODULES";
+    method @FlaggedApi("com.android.system.virtualmachine.flags.avf_v_test_apis") @NonNull public java.util.List<java.lang.String> getSupportedOSList() throws android.system.virtualmachine.VirtualMachineException;
+    method @FlaggedApi("com.android.system.virtualmachine.flags.avf_v_test_apis") @RequiresPermission(android.system.virtualmachine.VirtualMachine.MANAGE_VIRTUAL_MACHINE_PERMISSION) public boolean isFeatureEnabled(String) throws android.system.virtualmachine.VirtualMachineException;
+    field @FlaggedApi("com.android.system.virtualmachine.flags.avf_v_test_apis") public static final String FEATURE_DICE_CHANGES = "com.android.kvm.DICE_CHANGES";
+    field @FlaggedApi("com.android.system.virtualmachine.flags.avf_v_test_apis") public static final String FEATURE_MULTI_TENANT = "com.android.kvm.MULTI_TENANT";
+    field @FlaggedApi("com.android.system.virtualmachine.flags.avf_v_test_apis") public static final String FEATURE_REMOTE_ATTESTATION = "com.android.kvm.REMOTE_ATTESTATION";
+    field @FlaggedApi("com.android.system.virtualmachine.flags.avf_v_test_apis") public static final String FEATURE_VENDOR_MODULES = "com.android.kvm.VENDOR_MODULES";
   }
 
 }
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index b4ba00b..6b03cfe 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -76,6 +76,7 @@
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.system.virtualmachine.flags.Flags;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -1215,7 +1216,7 @@
      */
     @TestApi
     @RequiresPermission(USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION)
-    @FlaggedApi("RELEASE_AVF_ENABLE_REMOTE_ATTESTATION")
+    @FlaggedApi(Flags.FLAG_AVF_V_TEST_APIS)
     public void enableTestAttestation() throws VirtualMachineException {
         try {
             mVirtualizationService.getBinder().enableTestAttestation();
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
index 9688789..19e663f 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -40,6 +40,8 @@
 import android.system.virtualizationservice.VirtualMachinePayloadConfig;
 import android.util.Log;
 
+import com.android.system.virtualmachine.flags.Flags;
+
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
@@ -376,7 +378,7 @@
      * @hide
      */
     @TestApi
-    @FlaggedApi("RELEASE_AVF_ENABLE_MULTI_TENANT_MICRODROID_VM")
+    @FlaggedApi(Flags.FLAG_AVF_V_TEST_APIS)
     @NonNull
     public List<String> getExtraApks() {
         return mExtraApks;
@@ -502,7 +504,7 @@
      * @hide
      */
     @TestApi
-    @FlaggedApi("RELEASE_AVF_ENABLE_VENDOR_MODULES")
+    @FlaggedApi(Flags.FLAG_AVF_V_TEST_APIS)
     @Nullable
     public String getOs() {
         return mOs;
@@ -792,7 +794,7 @@
          * @hide
          */
         @TestApi
-        @FlaggedApi("RELEASE_AVF_ENABLE_MULTI_TENANT_MICRODROID_VM")
+        @FlaggedApi(Flags.FLAG_AVF_V_TEST_APIS)
         @NonNull
         public Builder addExtraApk(@NonNull String packageName) {
             mExtraApks.add(requireNonNull(packageName, "extra APK package name must not be null"));
@@ -1003,8 +1005,8 @@
          * @hide
          */
         @TestApi
+        @FlaggedApi(Flags.FLAG_AVF_V_TEST_APIS)
         @RequiresPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION)
-        @FlaggedApi("RELEASE_AVF_ENABLE_VENDOR_MODULES")
         @NonNull
         public Builder setVendorDiskImage(@NonNull File vendorDiskImage) {
             mVendorDiskImage =
@@ -1020,7 +1022,7 @@
          * @hide
          */
         @TestApi
-        @FlaggedApi("RELEASE_AVF_ENABLE_VENDOR_MODULES")
+        @FlaggedApi(Flags.FLAG_AVF_V_TEST_APIS)
         @NonNull
         public Builder setOs(@NonNull String os) {
             mOs = requireNonNull(os, "os must not be null");
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
index 1a4b53a..f263b32 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
@@ -36,6 +36,7 @@
 import android.util.ArrayMap;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.system.virtualmachine.flags.Flags;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -130,6 +131,7 @@
      * @hide
      */
     @TestApi
+    @FlaggedApi(Flags.FLAG_AVF_V_TEST_APIS)
     public static final String FEATURE_DICE_CHANGES = IVirtualizationService.FEATURE_DICE_CHANGES;
 
     /**
@@ -138,6 +140,7 @@
      * @hide
      */
     @TestApi
+    @FlaggedApi(Flags.FLAG_AVF_V_TEST_APIS)
     public static final String FEATURE_MULTI_TENANT = IVirtualizationService.FEATURE_MULTI_TENANT;
 
     /**
@@ -146,6 +149,7 @@
      * @hide
      */
     @TestApi
+    @FlaggedApi(Flags.FLAG_AVF_V_TEST_APIS)
     public static final String FEATURE_REMOTE_ATTESTATION =
             IVirtualizationService.FEATURE_REMOTE_ATTESTATION;
 
@@ -155,6 +159,7 @@
      * @hide
      */
     @TestApi
+    @FlaggedApi(Flags.FLAG_AVF_V_TEST_APIS)
     public static final String FEATURE_VENDOR_MODULES =
             IVirtualizationService.FEATURE_VENDOR_MODULES;
 
@@ -347,7 +352,7 @@
      * @hide
      */
     @TestApi
-    @FlaggedApi("RELEASE_AVF_ENABLE_VENDOR_MODULES")
+    @FlaggedApi(Flags.FLAG_AVF_V_TEST_APIS)
     @NonNull
     public List<String> getSupportedOSList() throws VirtualMachineException {
         synchronized (sCreateLock) {
@@ -366,6 +371,7 @@
      * @hide
      */
     @TestApi
+    @FlaggedApi(Flags.FLAG_AVF_V_TEST_APIS)
     @RequiresPermission(VirtualMachine.MANAGE_VIRTUAL_MACHINE_PERMISSION)
     public boolean isFeatureEnabled(@Features String featureName) throws VirtualMachineException {
         synchronized (sCreateLock) {
diff --git a/libs/libfdt/src/lib.rs b/libs/libfdt/src/lib.rs
index ab3c83f..d1ab24e 100644
--- a/libs/libfdt/src/lib.rs
+++ b/libs/libfdt/src/lib.rs
@@ -459,7 +459,7 @@
     }
 
     /// Returns the first subnode of this
-    pub fn first_subnode(&'a mut self) -> Result<Option<Self>> {
+    pub fn first_subnode(self) -> Result<Option<Self>> {
         let offset = self.fdt.first_subnode(self.offset)?;
 
         Ok(offset.map(|offset| Self { fdt: self.fdt, offset }))
diff --git a/libs/libfdt/tests/api_test.rs b/libs/libfdt/tests/api_test.rs
index cafbf97..ddc4538 100644
--- a/libs/libfdt/tests/api_test.rs
+++ b/libs/libfdt/tests/api_test.rs
@@ -378,7 +378,7 @@
     let mut data = fs::read(TEST_TREE_PHANDLE_PATH).unwrap();
     let fdt = Fdt::from_mut_slice(&mut data).unwrap();
 
-    let mut root = fdt.root_mut().unwrap();
+    let root = fdt.root_mut().unwrap();
     let mut subnode_iter = root.first_subnode().unwrap();
 
     while let Some(subnode) = subnode_iter {
diff --git a/pvmfw/Android.bp b/pvmfw/Android.bp
index 9a2b3ef..6a6d199 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -110,6 +110,7 @@
         "libciborium",
         "libdiced_open_dice_nostd",
         "libpvmfw_avb_nostd",
+        "libzerocopy_nostd",
     ],
 }
 
diff --git a/pvmfw/README.md b/pvmfw/README.md
index d7884fb..2758a5d 100644
--- a/pvmfw/README.md
+++ b/pvmfw/README.md
@@ -195,7 +195,8 @@
 
 In version 1.1, a third blob is added.
 
-- entry 2 may point to a [DTBO] that describes VM DTBO for device assignment.
+- entry 2 may point to a [DTBO] that describes VM DA DTBO for
+  [device assignment][device_assignment].
   pvmfw will provision assigned devices with the VM DTBO.
 
 In version 1.2, a fourth blob is added.
@@ -225,6 +226,7 @@
 [header]: src/config.rs
 [DTBO]: https://android.googlesource.com/platform/external/dtc/+/refs/heads/main/Documentation/dt-object-internal.txt
 [debug_policy]: ../docs/debug/README.md#debug-policy
+[device_assignment]: ../docs/device_assignment.md
 [secretkeeper_key]: https://android.googlesource.com/platform/system/secretkeeper/+/refs/heads/main/README.md#secretkeeper-public-key
 [vendor_hashtree_digest]: ../microdroid/README.md#verification-of-vendor-image
 
diff --git a/pvmfw/platform.dts b/pvmfw/platform.dts
index 9abc123..cea1c33 100644
--- a/pvmfw/platform.dts
+++ b/pvmfw/platform.dts
@@ -57,96 +57,497 @@
 			compatible = "arm,arm-v8";
 			enable-method = "psci";
 			reg = <0>;
+			operating-points-v2 = <&opp_table0>;
+			opp_table0: opp-table-0 {
+				compatible = "operating-points-v2";
+
+				opp1 { opp-hz = <PLACEHOLDER2>; };
+				opp2 { opp-hz = <PLACEHOLDER2>; };
+				opp3 { opp-hz = <PLACEHOLDER2>; };
+				opp4 { opp-hz = <PLACEHOLDER2>; };
+				opp5 { opp-hz = <PLACEHOLDER2>; };
+				opp6 { opp-hz = <PLACEHOLDER2>; };
+				opp7 { opp-hz = <PLACEHOLDER2>; };
+				opp8 { opp-hz = <PLACEHOLDER2>; };
+				opp9 { opp-hz = <PLACEHOLDER2>; };
+				opp10 { opp-hz = <PLACEHOLDER2>; };
+				opp11 { opp-hz = <PLACEHOLDER2>; };
+				opp12 { opp-hz = <PLACEHOLDER2>; };
+				opp13 { opp-hz = <PLACEHOLDER2>; };
+				opp14 { opp-hz = <PLACEHOLDER2>; };
+				opp15 { opp-hz = <PLACEHOLDER2>; };
+				opp16 { opp-hz = <PLACEHOLDER2>; };
+				opp17 { opp-hz = <PLACEHOLDER2>; };
+				opp18 { opp-hz = <PLACEHOLDER2>; };
+				opp19 { opp-hz = <PLACEHOLDER2>; };
+				opp20 { opp-hz = <PLACEHOLDER2>; };
+			};
 		};
 		cpu@1 {
 			device_type = "cpu";
 			compatible = "arm,arm-v8";
 			enable-method = "psci";
 			reg = <1>;
+			operating-points-v2 = <&opp_table1>;
+			opp_table1: opp-table-1 {
+				compatible = "operating-points-v2";
+
+				opp1 { opp-hz = <PLACEHOLDER2>; };
+				opp2 { opp-hz = <PLACEHOLDER2>; };
+				opp3 { opp-hz = <PLACEHOLDER2>; };
+				opp4 { opp-hz = <PLACEHOLDER2>; };
+				opp5 { opp-hz = <PLACEHOLDER2>; };
+				opp6 { opp-hz = <PLACEHOLDER2>; };
+				opp7 { opp-hz = <PLACEHOLDER2>; };
+				opp8 { opp-hz = <PLACEHOLDER2>; };
+				opp9 { opp-hz = <PLACEHOLDER2>; };
+				opp10 { opp-hz = <PLACEHOLDER2>; };
+				opp11 { opp-hz = <PLACEHOLDER2>; };
+				opp12 { opp-hz = <PLACEHOLDER2>; };
+				opp13 { opp-hz = <PLACEHOLDER2>; };
+				opp14 { opp-hz = <PLACEHOLDER2>; };
+				opp15 { opp-hz = <PLACEHOLDER2>; };
+				opp16 { opp-hz = <PLACEHOLDER2>; };
+				opp17 { opp-hz = <PLACEHOLDER2>; };
+				opp18 { opp-hz = <PLACEHOLDER2>; };
+				opp19 { opp-hz = <PLACEHOLDER2>; };
+				opp20 { opp-hz = <PLACEHOLDER2>; };
+			};
 		};
 		cpu@2 {
 			device_type = "cpu";
 			compatible = "arm,arm-v8";
 			enable-method = "psci";
 			reg = <2>;
+			operating-points-v2 = <&opp_table2>;
+			opp_table2: opp-table-2 {
+				compatible = "operating-points-v2";
+
+				opp1 { opp-hz = <PLACEHOLDER2>; };
+				opp2 { opp-hz = <PLACEHOLDER2>; };
+				opp3 { opp-hz = <PLACEHOLDER2>; };
+				opp4 { opp-hz = <PLACEHOLDER2>; };
+				opp5 { opp-hz = <PLACEHOLDER2>; };
+				opp6 { opp-hz = <PLACEHOLDER2>; };
+				opp7 { opp-hz = <PLACEHOLDER2>; };
+				opp8 { opp-hz = <PLACEHOLDER2>; };
+				opp9 { opp-hz = <PLACEHOLDER2>; };
+				opp10 { opp-hz = <PLACEHOLDER2>; };
+				opp11 { opp-hz = <PLACEHOLDER2>; };
+				opp12 { opp-hz = <PLACEHOLDER2>; };
+				opp13 { opp-hz = <PLACEHOLDER2>; };
+				opp14 { opp-hz = <PLACEHOLDER2>; };
+				opp15 { opp-hz = <PLACEHOLDER2>; };
+				opp16 { opp-hz = <PLACEHOLDER2>; };
+				opp17 { opp-hz = <PLACEHOLDER2>; };
+				opp18 { opp-hz = <PLACEHOLDER2>; };
+				opp19 { opp-hz = <PLACEHOLDER2>; };
+				opp20 { opp-hz = <PLACEHOLDER2>; };
+			};
 		};
 		cpu@3 {
 			device_type = "cpu";
 			compatible = "arm,arm-v8";
 			enable-method = "psci";
 			reg = <3>;
+			operating-points-v2 = <&opp_table3>;
+			opp_table3: opp-table-3 {
+				compatible = "operating-points-v2";
+
+				opp1 { opp-hz = <PLACEHOLDER2>; };
+				opp2 { opp-hz = <PLACEHOLDER2>; };
+				opp3 { opp-hz = <PLACEHOLDER2>; };
+				opp4 { opp-hz = <PLACEHOLDER2>; };
+				opp5 { opp-hz = <PLACEHOLDER2>; };
+				opp6 { opp-hz = <PLACEHOLDER2>; };
+				opp7 { opp-hz = <PLACEHOLDER2>; };
+				opp8 { opp-hz = <PLACEHOLDER2>; };
+				opp9 { opp-hz = <PLACEHOLDER2>; };
+				opp10 { opp-hz = <PLACEHOLDER2>; };
+				opp11 { opp-hz = <PLACEHOLDER2>; };
+				opp12 { opp-hz = <PLACEHOLDER2>; };
+				opp13 { opp-hz = <PLACEHOLDER2>; };
+				opp14 { opp-hz = <PLACEHOLDER2>; };
+				opp15 { opp-hz = <PLACEHOLDER2>; };
+				opp16 { opp-hz = <PLACEHOLDER2>; };
+				opp17 { opp-hz = <PLACEHOLDER2>; };
+				opp18 { opp-hz = <PLACEHOLDER2>; };
+				opp19 { opp-hz = <PLACEHOLDER2>; };
+				opp20 { opp-hz = <PLACEHOLDER2>; };
+			};
 		};
 		cpu@4 {
 			device_type = "cpu";
 			compatible = "arm,arm-v8";
 			enable-method = "psci";
 			reg = <4>;
+			operating-points-v2 = <&opp_table4>;
+			opp_table4: opp-table-4 {
+				compatible = "operating-points-v2";
+
+				opp1 { opp-hz = <PLACEHOLDER2>; };
+				opp2 { opp-hz = <PLACEHOLDER2>; };
+				opp3 { opp-hz = <PLACEHOLDER2>; };
+				opp4 { opp-hz = <PLACEHOLDER2>; };
+				opp5 { opp-hz = <PLACEHOLDER2>; };
+				opp6 { opp-hz = <PLACEHOLDER2>; };
+				opp7 { opp-hz = <PLACEHOLDER2>; };
+				opp8 { opp-hz = <PLACEHOLDER2>; };
+				opp9 { opp-hz = <PLACEHOLDER2>; };
+				opp10 { opp-hz = <PLACEHOLDER2>; };
+				opp11 { opp-hz = <PLACEHOLDER2>; };
+				opp12 { opp-hz = <PLACEHOLDER2>; };
+				opp13 { opp-hz = <PLACEHOLDER2>; };
+				opp14 { opp-hz = <PLACEHOLDER2>; };
+				opp15 { opp-hz = <PLACEHOLDER2>; };
+				opp16 { opp-hz = <PLACEHOLDER2>; };
+				opp17 { opp-hz = <PLACEHOLDER2>; };
+				opp18 { opp-hz = <PLACEHOLDER2>; };
+				opp19 { opp-hz = <PLACEHOLDER2>; };
+				opp20 { opp-hz = <PLACEHOLDER2>; };
+			};
 		};
 		cpu@5 {
 			device_type = "cpu";
 			compatible = "arm,arm-v8";
 			enable-method = "psci";
 			reg = <5>;
+			operating-points-v2 = <&opp_table5>;
+			opp_table5: opp-table-5 {
+				compatible = "operating-points-v2";
+
+				opp1 { opp-hz = <PLACEHOLDER2>; };
+				opp2 { opp-hz = <PLACEHOLDER2>; };
+				opp3 { opp-hz = <PLACEHOLDER2>; };
+				opp4 { opp-hz = <PLACEHOLDER2>; };
+				opp5 { opp-hz = <PLACEHOLDER2>; };
+				opp6 { opp-hz = <PLACEHOLDER2>; };
+				opp7 { opp-hz = <PLACEHOLDER2>; };
+				opp8 { opp-hz = <PLACEHOLDER2>; };
+				opp9 { opp-hz = <PLACEHOLDER2>; };
+				opp10 { opp-hz = <PLACEHOLDER2>; };
+				opp11 { opp-hz = <PLACEHOLDER2>; };
+				opp12 { opp-hz = <PLACEHOLDER2>; };
+				opp13 { opp-hz = <PLACEHOLDER2>; };
+				opp14 { opp-hz = <PLACEHOLDER2>; };
+				opp15 { opp-hz = <PLACEHOLDER2>; };
+				opp16 { opp-hz = <PLACEHOLDER2>; };
+				opp17 { opp-hz = <PLACEHOLDER2>; };
+				opp18 { opp-hz = <PLACEHOLDER2>; };
+				opp19 { opp-hz = <PLACEHOLDER2>; };
+				opp20 { opp-hz = <PLACEHOLDER2>; };
+			};
 		};
 		cpu@6 {
 			device_type = "cpu";
 			compatible = "arm,arm-v8";
 			enable-method = "psci";
 			reg = <6>;
+			operating-points-v2 = <&opp_table6>;
+			opp_table6: opp-table-6 {
+				compatible = "operating-points-v2";
+
+				opp1 { opp-hz = <PLACEHOLDER2>; };
+				opp2 { opp-hz = <PLACEHOLDER2>; };
+				opp3 { opp-hz = <PLACEHOLDER2>; };
+				opp4 { opp-hz = <PLACEHOLDER2>; };
+				opp5 { opp-hz = <PLACEHOLDER2>; };
+				opp6 { opp-hz = <PLACEHOLDER2>; };
+				opp7 { opp-hz = <PLACEHOLDER2>; };
+				opp8 { opp-hz = <PLACEHOLDER2>; };
+				opp9 { opp-hz = <PLACEHOLDER2>; };
+				opp10 { opp-hz = <PLACEHOLDER2>; };
+				opp11 { opp-hz = <PLACEHOLDER2>; };
+				opp12 { opp-hz = <PLACEHOLDER2>; };
+				opp13 { opp-hz = <PLACEHOLDER2>; };
+				opp14 { opp-hz = <PLACEHOLDER2>; };
+				opp15 { opp-hz = <PLACEHOLDER2>; };
+				opp16 { opp-hz = <PLACEHOLDER2>; };
+				opp17 { opp-hz = <PLACEHOLDER2>; };
+				opp18 { opp-hz = <PLACEHOLDER2>; };
+				opp19 { opp-hz = <PLACEHOLDER2>; };
+				opp20 { opp-hz = <PLACEHOLDER2>; };
+			};
 		};
 		cpu@7 {
 			device_type = "cpu";
 			compatible = "arm,arm-v8";
 			enable-method = "psci";
 			reg = <7>;
+			operating-points-v2 = <&opp_table7>;
+			opp_table7: opp-table-7 {
+				compatible = "operating-points-v2";
+
+				opp1 { opp-hz = <PLACEHOLDER2>; };
+				opp2 { opp-hz = <PLACEHOLDER2>; };
+				opp3 { opp-hz = <PLACEHOLDER2>; };
+				opp4 { opp-hz = <PLACEHOLDER2>; };
+				opp5 { opp-hz = <PLACEHOLDER2>; };
+				opp6 { opp-hz = <PLACEHOLDER2>; };
+				opp7 { opp-hz = <PLACEHOLDER2>; };
+				opp8 { opp-hz = <PLACEHOLDER2>; };
+				opp9 { opp-hz = <PLACEHOLDER2>; };
+				opp10 { opp-hz = <PLACEHOLDER2>; };
+				opp11 { opp-hz = <PLACEHOLDER2>; };
+				opp12 { opp-hz = <PLACEHOLDER2>; };
+				opp13 { opp-hz = <PLACEHOLDER2>; };
+				opp14 { opp-hz = <PLACEHOLDER2>; };
+				opp15 { opp-hz = <PLACEHOLDER2>; };
+				opp16 { opp-hz = <PLACEHOLDER2>; };
+				opp17 { opp-hz = <PLACEHOLDER2>; };
+				opp18 { opp-hz = <PLACEHOLDER2>; };
+				opp19 { opp-hz = <PLACEHOLDER2>; };
+				opp20 { opp-hz = <PLACEHOLDER2>; };
+			};
 		};
 		cpu@8 {
 			device_type = "cpu";
 			compatible = "arm,arm-v8";
 			enable-method = "psci";
 			reg = <8>;
+
+			operating-points-v2 = <&opp_table8>;
+			opp_table8: opp-table-8 {
+				compatible = "operating-points-v2";
+
+				opp1 { opp-hz = <PLACEHOLDER2>; };
+				opp2 { opp-hz = <PLACEHOLDER2>; };
+				opp3 { opp-hz = <PLACEHOLDER2>; };
+				opp4 { opp-hz = <PLACEHOLDER2>; };
+				opp5 { opp-hz = <PLACEHOLDER2>; };
+				opp6 { opp-hz = <PLACEHOLDER2>; };
+				opp7 { opp-hz = <PLACEHOLDER2>; };
+				opp8 { opp-hz = <PLACEHOLDER2>; };
+				opp9 { opp-hz = <PLACEHOLDER2>; };
+				opp10 { opp-hz = <PLACEHOLDER2>; };
+				opp11 { opp-hz = <PLACEHOLDER2>; };
+				opp12 { opp-hz = <PLACEHOLDER2>; };
+				opp13 { opp-hz = <PLACEHOLDER2>; };
+				opp14 { opp-hz = <PLACEHOLDER2>; };
+				opp15 { opp-hz = <PLACEHOLDER2>; };
+				opp16 { opp-hz = <PLACEHOLDER2>; };
+				opp17 { opp-hz = <PLACEHOLDER2>; };
+				opp18 { opp-hz = <PLACEHOLDER2>; };
+				opp19 { opp-hz = <PLACEHOLDER2>; };
+				opp20 { opp-hz = <PLACEHOLDER2>; };
+			};
 		};
 		cpu@9 {
 			device_type = "cpu";
 			compatible = "arm,arm-v8";
 			enable-method = "psci";
 			reg = <9>;
+			operating-points-v2 = <&opp_table9>;
+			opp_table9: opp-table-9 {
+				compatible = "operating-points-v2";
+
+				opp1 { opp-hz = <PLACEHOLDER2>; };
+				opp2 { opp-hz = <PLACEHOLDER2>; };
+				opp3 { opp-hz = <PLACEHOLDER2>; };
+				opp4 { opp-hz = <PLACEHOLDER2>; };
+				opp5 { opp-hz = <PLACEHOLDER2>; };
+				opp6 { opp-hz = <PLACEHOLDER2>; };
+				opp7 { opp-hz = <PLACEHOLDER2>; };
+				opp8 { opp-hz = <PLACEHOLDER2>; };
+				opp9 { opp-hz = <PLACEHOLDER2>; };
+				opp10 { opp-hz = <PLACEHOLDER2>; };
+				opp11 { opp-hz = <PLACEHOLDER2>; };
+				opp12 { opp-hz = <PLACEHOLDER2>; };
+				opp13 { opp-hz = <PLACEHOLDER2>; };
+				opp14 { opp-hz = <PLACEHOLDER2>; };
+				opp15 { opp-hz = <PLACEHOLDER2>; };
+				opp16 { opp-hz = <PLACEHOLDER2>; };
+				opp17 { opp-hz = <PLACEHOLDER2>; };
+				opp18 { opp-hz = <PLACEHOLDER2>; };
+				opp19 { opp-hz = <PLACEHOLDER2>; };
+				opp20 { opp-hz = <PLACEHOLDER2>; };
+			};
 		};
 		cpu@10 {
 			device_type = "cpu";
 			compatible = "arm,arm-v8";
 			enable-method = "psci";
 			reg = <10>;
+			operating-points-v2 = <&opp_table10>;
+			opp_table10: opp-table-10 {
+				compatible = "operating-points-v2";
+
+				opp1 { opp-hz = <PLACEHOLDER2>; };
+				opp2 { opp-hz = <PLACEHOLDER2>; };
+				opp3 { opp-hz = <PLACEHOLDER2>; };
+				opp4 { opp-hz = <PLACEHOLDER2>; };
+				opp5 { opp-hz = <PLACEHOLDER2>; };
+				opp6 { opp-hz = <PLACEHOLDER2>; };
+				opp7 { opp-hz = <PLACEHOLDER2>; };
+				opp8 { opp-hz = <PLACEHOLDER2>; };
+				opp9 { opp-hz = <PLACEHOLDER2>; };
+				opp10 { opp-hz = <PLACEHOLDER2>; };
+				opp11 { opp-hz = <PLACEHOLDER2>; };
+				opp12 { opp-hz = <PLACEHOLDER2>; };
+				opp13 { opp-hz = <PLACEHOLDER2>; };
+				opp14 { opp-hz = <PLACEHOLDER2>; };
+				opp15 { opp-hz = <PLACEHOLDER2>; };
+				opp16 { opp-hz = <PLACEHOLDER2>; };
+				opp17 { opp-hz = <PLACEHOLDER2>; };
+				opp18 { opp-hz = <PLACEHOLDER2>; };
+				opp19 { opp-hz = <PLACEHOLDER2>; };
+				opp20 { opp-hz = <PLACEHOLDER2>; };
+			};
 		};
 		cpu@11 {
 			device_type = "cpu";
 			compatible = "arm,arm-v8";
 			enable-method = "psci";
 			reg = <11>;
+			operating-points-v2 = <&opp_table11>;
+			opp_table11: opp-table-11 {
+				compatible = "operating-points-v2";
+
+				opp1 { opp-hz = <PLACEHOLDER2>; };
+				opp2 { opp-hz = <PLACEHOLDER2>; };
+				opp3 { opp-hz = <PLACEHOLDER2>; };
+				opp4 { opp-hz = <PLACEHOLDER2>; };
+				opp5 { opp-hz = <PLACEHOLDER2>; };
+				opp6 { opp-hz = <PLACEHOLDER2>; };
+				opp7 { opp-hz = <PLACEHOLDER2>; };
+				opp8 { opp-hz = <PLACEHOLDER2>; };
+				opp9 { opp-hz = <PLACEHOLDER2>; };
+				opp10 { opp-hz = <PLACEHOLDER2>; };
+				opp11 { opp-hz = <PLACEHOLDER2>; };
+				opp12 { opp-hz = <PLACEHOLDER2>; };
+				opp13 { opp-hz = <PLACEHOLDER2>; };
+				opp14 { opp-hz = <PLACEHOLDER2>; };
+				opp15 { opp-hz = <PLACEHOLDER2>; };
+				opp16 { opp-hz = <PLACEHOLDER2>; };
+				opp17 { opp-hz = <PLACEHOLDER2>; };
+				opp18 { opp-hz = <PLACEHOLDER2>; };
+				opp19 { opp-hz = <PLACEHOLDER2>; };
+				opp20 { opp-hz = <PLACEHOLDER2>; };
+			};
 		};
 		cpu@12 {
 			device_type = "cpu";
 			compatible = "arm,arm-v8";
 			enable-method = "psci";
 			reg = <12>;
+			operating-points-v2 = <&opp_table12>;
+			opp_table12: opp-table-12 {
+				compatible = "operating-points-v2";
+
+				opp1 { opp-hz = <PLACEHOLDER2>; };
+				opp2 { opp-hz = <PLACEHOLDER2>; };
+				opp3 { opp-hz = <PLACEHOLDER2>; };
+				opp4 { opp-hz = <PLACEHOLDER2>; };
+				opp5 { opp-hz = <PLACEHOLDER2>; };
+				opp6 { opp-hz = <PLACEHOLDER2>; };
+				opp7 { opp-hz = <PLACEHOLDER2>; };
+				opp8 { opp-hz = <PLACEHOLDER2>; };
+				opp9 { opp-hz = <PLACEHOLDER2>; };
+				opp10 { opp-hz = <PLACEHOLDER2>; };
+				opp11 { opp-hz = <PLACEHOLDER2>; };
+				opp12 { opp-hz = <PLACEHOLDER2>; };
+				opp13 { opp-hz = <PLACEHOLDER2>; };
+				opp14 { opp-hz = <PLACEHOLDER2>; };
+				opp15 { opp-hz = <PLACEHOLDER2>; };
+				opp16 { opp-hz = <PLACEHOLDER2>; };
+				opp17 { opp-hz = <PLACEHOLDER2>; };
+				opp18 { opp-hz = <PLACEHOLDER2>; };
+				opp19 { opp-hz = <PLACEHOLDER2>; };
+				opp20 { opp-hz = <PLACEHOLDER2>; };
+			};
 		};
 		cpu@13 {
 			device_type = "cpu";
 			compatible = "arm,arm-v8";
 			enable-method = "psci";
 			reg = <13>;
+			operating-points-v2 = <&opp_table13>;
+			opp_table13: opp-table-13 {
+				compatible = "operating-points-v2";
+
+				opp1 { opp-hz = <PLACEHOLDER2>; };
+				opp2 { opp-hz = <PLACEHOLDER2>; };
+				opp3 { opp-hz = <PLACEHOLDER2>; };
+				opp4 { opp-hz = <PLACEHOLDER2>; };
+				opp5 { opp-hz = <PLACEHOLDER2>; };
+				opp6 { opp-hz = <PLACEHOLDER2>; };
+				opp7 { opp-hz = <PLACEHOLDER2>; };
+				opp8 { opp-hz = <PLACEHOLDER2>; };
+				opp9 { opp-hz = <PLACEHOLDER2>; };
+				opp10 { opp-hz = <PLACEHOLDER2>; };
+				opp11 { opp-hz = <PLACEHOLDER2>; };
+				opp12 { opp-hz = <PLACEHOLDER2>; };
+				opp13 { opp-hz = <PLACEHOLDER2>; };
+				opp14 { opp-hz = <PLACEHOLDER2>; };
+				opp15 { opp-hz = <PLACEHOLDER2>; };
+				opp16 { opp-hz = <PLACEHOLDER2>; };
+				opp17 { opp-hz = <PLACEHOLDER2>; };
+				opp18 { opp-hz = <PLACEHOLDER2>; };
+				opp19 { opp-hz = <PLACEHOLDER2>; };
+				opp20 { opp-hz = <PLACEHOLDER2>; };
+			};
 		};
 		cpu@14 {
 			device_type = "cpu";
 			compatible = "arm,arm-v8";
 			enable-method = "psci";
 			reg = <14>;
+			operating-points-v2 = <&opp_table14>;
+			opp_table14: opp-table-14 {
+				compatible = "operating-points-v2";
+
+				opp1 { opp-hz = <PLACEHOLDER2>; };
+				opp2 { opp-hz = <PLACEHOLDER2>; };
+				opp3 { opp-hz = <PLACEHOLDER2>; };
+				opp4 { opp-hz = <PLACEHOLDER2>; };
+				opp5 { opp-hz = <PLACEHOLDER2>; };
+				opp6 { opp-hz = <PLACEHOLDER2>; };
+				opp7 { opp-hz = <PLACEHOLDER2>; };
+				opp8 { opp-hz = <PLACEHOLDER2>; };
+				opp9 { opp-hz = <PLACEHOLDER2>; };
+				opp10 { opp-hz = <PLACEHOLDER2>; };
+				opp11 { opp-hz = <PLACEHOLDER2>; };
+				opp12 { opp-hz = <PLACEHOLDER2>; };
+				opp13 { opp-hz = <PLACEHOLDER2>; };
+				opp14 { opp-hz = <PLACEHOLDER2>; };
+				opp15 { opp-hz = <PLACEHOLDER2>; };
+				opp16 { opp-hz = <PLACEHOLDER2>; };
+				opp17 { opp-hz = <PLACEHOLDER2>; };
+				opp18 { opp-hz = <PLACEHOLDER2>; };
+				opp19 { opp-hz = <PLACEHOLDER2>; };
+				opp20 { opp-hz = <PLACEHOLDER2>; };
+			};
 		};
 		cpu@15 {
 			device_type = "cpu";
 			compatible = "arm,arm-v8";
 			enable-method = "psci";
 			reg = <15>;
+			operating-points-v2 = <&opp_table15>;
+			opp_table15: opp-table-15 {
+				compatible = "operating-points-v2";
+
+				opp1 { opp-hz = <PLACEHOLDER2>; };
+				opp2 { opp-hz = <PLACEHOLDER2>; };
+				opp3 { opp-hz = <PLACEHOLDER2>; };
+				opp4 { opp-hz = <PLACEHOLDER2>; };
+				opp5 { opp-hz = <PLACEHOLDER2>; };
+				opp6 { opp-hz = <PLACEHOLDER2>; };
+				opp7 { opp-hz = <PLACEHOLDER2>; };
+				opp8 { opp-hz = <PLACEHOLDER2>; };
+				opp9 { opp-hz = <PLACEHOLDER2>; };
+				opp10 { opp-hz = <PLACEHOLDER2>; };
+				opp11 { opp-hz = <PLACEHOLDER2>; };
+				opp12 { opp-hz = <PLACEHOLDER2>; };
+				opp13 { opp-hz = <PLACEHOLDER2>; };
+				opp14 { opp-hz = <PLACEHOLDER2>; };
+				opp15 { opp-hz = <PLACEHOLDER2>; };
+				opp16 { opp-hz = <PLACEHOLDER2>; };
+				opp17 { opp-hz = <PLACEHOLDER2>; };
+				opp18 { opp-hz = <PLACEHOLDER2>; };
+				opp19 { opp-hz = <PLACEHOLDER2>; };
+				opp20 { opp-hz = <PLACEHOLDER2>; };
+			};
 		};
 	};
 
@@ -321,4 +722,9 @@
 		id = <PLACEHOLDER>;
 		#iommu-cells = <1>;
 	};
+
+	cpufreq {
+		compatible = "virtual,android-v-only-cpufreq";
+		reg = <0x0 0x1040000 PLACEHOLDER2>;
+	};
 };
diff --git a/pvmfw/src/dice.rs b/pvmfw/src/dice.rs
index 99bf589..540fd03 100644
--- a/pvmfw/src/dice.rs
+++ b/pvmfw/src/dice.rs
@@ -21,6 +21,7 @@
     Hash, InputValues, HIDDEN_SIZE,
 };
 use pvmfw_avb::{Capability, DebugLevel, Digest, VerifiedBootData};
+use zerocopy::AsBytes;
 
 fn to_dice_mode(debug_level: DebugLevel) -> DiceMode {
     match debug_level {
@@ -72,12 +73,30 @@
             Config::Descriptor(config),
             self.auth_hash,
             self.mode,
-            *salt,
+            self.make_hidden(salt)?,
         );
         let _ = bcc_handover_main_flow(current_bcc_handover, &dice_inputs, next_bcc)?;
         Ok(())
     }
 
+    fn make_hidden(&self, salt: &[u8; HIDDEN_SIZE]) -> diced_open_dice::Result<[u8; HIDDEN_SIZE]> {
+        // We want to make sure we get a different sealing CDI for:
+        // - VMs with different salt values
+        // - An RKP VM and any other VM (regardless of salt)
+        // The hidden input for DICE affects the sealing CDI (but the values in the config
+        // descriptor do not).
+        // Since the hidden input has to be a fixed size, create it as a hash of the values we
+        // want included.
+        #[derive(AsBytes)]
+        #[repr(C, packed)]
+        struct HiddenInput {
+            rkp_vm_marker: bool,
+            salt: [u8; HIDDEN_SIZE],
+        }
+
+        hash(HiddenInput { rkp_vm_marker: self.rkp_vm_marker, salt: *salt }.as_bytes())
+    }
+
     fn generate_config_descriptor<'a>(
         &self,
         config_descriptor_buffer: &'a mut [u8],
diff --git a/pvmfw/src/fdt.rs b/pvmfw/src/fdt.rs
index ac52be9..f20451a 100644
--- a/pvmfw/src/fdt.rs
+++ b/pvmfw/src/fdt.rs
@@ -35,6 +35,7 @@
 use libfdt::CellIterator;
 use libfdt::Fdt;
 use libfdt::FdtError;
+use libfdt::FdtNode;
 use libfdt::FdtNodeMut;
 use log::debug;
 use log::error;
@@ -54,12 +55,17 @@
 pub enum FdtValidationError {
     /// Invalid CPU count.
     InvalidCpuCount(usize),
+    /// Invalid VCpufreq Range.
+    InvalidVcpufreq(u64, u64),
 }
 
 impl fmt::Display for FdtValidationError {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         match self {
             Self::InvalidCpuCount(num_cpus) => write!(f, "Invalid CPU count: {num_cpus}"),
+            Self::InvalidVcpufreq(addr, size) => {
+                write!(f, "Invalid vcpufreq region: ({addr:#x}, {size:#x})")
+            }
         }
     }
 }
@@ -172,15 +178,41 @@
         .setprop_inplace(cstr!("reg"), [addr.to_be(), size.to_be()].as_bytes())
 }
 
+//TODO: Need to add info for cpu capacity
 #[derive(Debug, Default)]
-struct CpuInfo {}
+struct CpuInfo {
+    opptable_info: Option<ArrayVec<[u64; CpuInfo::MAX_OPPTABLES]>>,
+}
+
+impl CpuInfo {
+    const MAX_OPPTABLES: usize = 20;
+}
+
+fn read_opp_info_from(
+    opp_node: FdtNode,
+) -> libfdt::Result<ArrayVec<[u64; CpuInfo::MAX_OPPTABLES]>> {
+    let mut table = ArrayVec::new();
+    for subnode in opp_node.subnodes()? {
+        let prop = subnode.getprop_u64(cstr!("opp-hz"))?.ok_or(FdtError::NotFound)?;
+        table.push(prop);
+    }
+
+    Ok(table)
+}
 
 fn read_cpu_info_from(fdt: &Fdt) -> libfdt::Result<ArrayVec<[CpuInfo; DeviceTreeInfo::MAX_CPUS]>> {
     let mut cpus = ArrayVec::new();
-
     let mut cpu_nodes = fdt.compatible_nodes(cstr!("arm,arm-v8"))?;
-    for _cpu in cpu_nodes.by_ref().take(cpus.capacity()) {
-        let info = CpuInfo {};
+    for cpu in cpu_nodes.by_ref().take(cpus.capacity()) {
+        let opp_phandle = cpu.getprop_u32(cstr!("operating-points-v2"))?;
+        let opptable_info = if let Some(phandle) = opp_phandle {
+            let phandle = phandle.try_into()?;
+            let node = fdt.node_with_phandle(phandle)?.ok_or(FdtError::NotFound)?;
+            Some(read_opp_info_from(node)?)
+        } else {
+            None
+        };
+        let info = CpuInfo { opptable_info };
         cpus.push(info);
     }
     if cpu_nodes.next().is_some() {
@@ -198,12 +230,87 @@
     Ok(())
 }
 
+fn read_vcpufreq_info(fdt: &Fdt) -> libfdt::Result<Option<VcpufreqInfo>> {
+    let mut nodes = fdt.compatible_nodes(cstr!("virtual,android-v-only-cpufreq"))?;
+    let Some(node) = nodes.next() else {
+        return Ok(None);
+    };
+
+    if nodes.next().is_some() {
+        warn!("DT has more than 1 cpufreq node: discarding extra nodes.");
+    }
+
+    let mut regs = node.reg()?.ok_or(FdtError::NotFound)?;
+    let reg = regs.next().ok_or(FdtError::NotFound)?;
+    let size = reg.size.ok_or(FdtError::NotFound)?;
+
+    Ok(Some(VcpufreqInfo { addr: reg.addr, size }))
+}
+
+fn validate_vcpufreq_info(
+    vcpufreq_info: &VcpufreqInfo,
+    cpus: &[CpuInfo],
+) -> Result<(), FdtValidationError> {
+    const VCPUFREQ_BASE_ADDR: u64 = 0x1040000;
+    const VCPUFREQ_SIZE_PER_CPU: u64 = 0x8;
+
+    let base = vcpufreq_info.addr;
+    let size = vcpufreq_info.size;
+    let expected_size = VCPUFREQ_SIZE_PER_CPU * cpus.len() as u64;
+
+    if (base, size) != (VCPUFREQ_BASE_ADDR, expected_size) {
+        return Err(FdtValidationError::InvalidVcpufreq(base, size));
+    }
+
+    Ok(())
+}
+
+fn patch_opptable(
+    node: FdtNodeMut,
+    opptable: Option<ArrayVec<[u64; CpuInfo::MAX_OPPTABLES]>>,
+) -> libfdt::Result<()> {
+    let oppcompat = cstr!("operating-points-v2");
+    let next = node.next_compatible(oppcompat)?.ok_or(FdtError::NoSpace)?;
+
+    let Some(opptable) = opptable else {
+        return next.nop();
+    };
+
+    let mut next_subnode = next.first_subnode()?;
+
+    for entry in opptable {
+        let mut subnode = next_subnode.ok_or(FdtError::NoSpace)?;
+        subnode.setprop_inplace(cstr!("opp-hz"), &entry.to_be_bytes())?;
+        next_subnode = subnode.next_subnode()?;
+    }
+
+    while let Some(current) = next_subnode {
+        next_subnode = current.delete_and_next_subnode()?;
+    }
+
+    Ok(())
+}
+
+// TODO(ptosi): Rework FdtNodeMut and replace this function.
+fn get_nth_compatible<'a>(
+    fdt: &'a mut Fdt,
+    n: usize,
+    compat: &CStr,
+) -> libfdt::Result<Option<FdtNodeMut<'a>>> {
+    let mut node = fdt.root_mut()?.next_compatible(compat)?;
+    for _ in 0..n {
+        node = node.ok_or(FdtError::NoSpace)?.next_compatible(compat)?;
+    }
+    Ok(node)
+}
+
 fn patch_cpus(fdt: &mut Fdt, cpus: &[CpuInfo]) -> libfdt::Result<()> {
     const COMPAT: &CStr = cstr!("arm,arm-v8");
-    let mut next = fdt.root_mut()?.next_compatible(COMPAT)?;
-    for _cpu in cpus {
-        next = next.ok_or(FdtError::NoSpace)?.next_compatible(COMPAT)?;
+    for (idx, cpu) in cpus.iter().enumerate() {
+        let cur = get_nth_compatible(fdt, idx, COMPAT)?.ok_or(FdtError::NoSpace)?;
+        patch_opptable(cur, cpu.opptable_info)?;
     }
+    let mut next = get_nth_compatible(fdt, cpus.len(), COMPAT)?;
     while let Some(current) = next {
         next = current.delete_and_next_compatible(COMPAT)?;
     }
@@ -628,6 +735,21 @@
 }
 
 #[derive(Debug)]
+struct VcpufreqInfo {
+    addr: u64,
+    size: u64,
+}
+
+fn patch_vcpufreq(fdt: &mut Fdt, vcpufreq_info: &Option<VcpufreqInfo>) -> libfdt::Result<()> {
+    let mut node = fdt.node_mut(cstr!("/cpufreq"))?.unwrap();
+    if let Some(info) = vcpufreq_info {
+        node.setprop_addrrange_inplace(cstr!("reg"), info.addr, info.size)
+    } else {
+        node.nop()
+    }
+}
+
+#[derive(Debug)]
 pub struct DeviceTreeInfo {
     pub kernel_range: Option<Range<usize>>,
     pub initrd_range: Option<Range<usize>>,
@@ -639,6 +761,7 @@
     pub swiotlb_info: SwiotlbInfo,
     device_assignment: Option<DeviceAssignmentInfo>,
     vm_ref_dt_props_info: BTreeMap<CString, Vec<u8>>,
+    vcpufreq_info: Option<VcpufreqInfo>,
 }
 
 impl DeviceTreeInfo {
@@ -751,6 +874,17 @@
         RebootReason::InvalidFdt
     })?;
 
+    let vcpufreq_info = read_vcpufreq_info(fdt).map_err(|e| {
+        error!("Failed to read vcpufreq info from DT: {e}");
+        RebootReason::InvalidFdt
+    })?;
+    if let Some(ref info) = vcpufreq_info {
+        validate_vcpufreq_info(info, &cpus).map_err(|e| {
+            error!("Failed to validate vcpufreq info from DT: {e}");
+            RebootReason::InvalidFdt
+        })?;
+    }
+
     let pci_info = read_pci_info_from(fdt).map_err(|e| {
         error!("Failed to read pci info from DT: {e}");
         RebootReason::InvalidFdt
@@ -801,6 +935,7 @@
         swiotlb_info,
         device_assignment,
         vm_ref_dt_props_info,
+        vcpufreq_info,
     })
 }
 
@@ -825,6 +960,10 @@
         error!("Failed to patch cpus to DT: {e}");
         RebootReason::InvalidFdt
     })?;
+    patch_vcpufreq(fdt, &info.vcpufreq_info).map_err(|e| {
+        error!("Failed to patch vcpufreq info to DT: {e}");
+        RebootReason::InvalidFdt
+    })?;
     patch_pci_info(fdt, &info.pci_info).map_err(|e| {
         error!("Failed to patch pci info to DT: {e}");
         RebootReason::InvalidFdt
diff --git a/service_vm/test_apk/src/native/main.rs b/service_vm/test_apk/src/native/main.rs
index 26b8062..d5d599d 100644
--- a/service_vm/test_apk/src/native/main.rs
+++ b/service_vm/test_apk/src/native/main.rs
@@ -45,7 +45,7 @@
     android_logger::init_once(
         android_logger::Config::default()
             .with_tag("service_vm_client")
-            .with_min_level(log::Level::Debug),
+            .with_max_level(log::LevelFilter::Debug),
     );
     // Redirect panic messages to logcat.
     panic::set_hook(Box::new(|panic_info| {
diff --git a/virtualizationmanager/fsfdt/src/lib.rs b/virtualizationmanager/fsfdt/src/lib.rs
index 549df04..84e50c1 100644
--- a/virtualizationmanager/fsfdt/src/lib.rs
+++ b/virtualizationmanager/fsfdt/src/lib.rs
@@ -26,8 +26,8 @@
     /// Creates a Fdt from /proc/device-tree style directory by wrapping a mutable slice
     fn from_fs(fs_path: &Path, fdt_buffer: &'a mut [u8]) -> Result<&'a mut Self>;
 
-    /// Appends a FDT from /proc/device-tree style directory at the given node path
-    fn append(&mut self, fdt_node_path: &CStr, fs_path: &Path) -> Result<()>;
+    /// Overlay an FDT from /proc/device-tree style directory at the given node path
+    fn overlay_onto(&mut self, fdt_node_path: &CStr, fs_path: &Path) -> Result<()>;
 }
 
 impl<'a> FsFdt<'a> for Fdt {
@@ -35,12 +35,12 @@
         let fdt = Fdt::create_empty_tree(fdt_buffer)
             .map_err(|e| anyhow!("Failed to create FDT, {e:?}"))?;
 
-        fdt.append(&CString::new("").unwrap(), fs_path)?;
+        fdt.overlay_onto(&CString::new("").unwrap(), fs_path)?;
 
         Ok(fdt)
     }
 
-    fn append(&mut self, fdt_node_path: &CStr, fs_path: &Path) -> Result<()> {
+    fn overlay_onto(&mut self, fdt_node_path: &CStr, fs_path: &Path) -> Result<()> {
         // Recursively traverse fs_path with DFS algorithm.
         let mut stack = vec![fs_path.to_path_buf()];
         while let Some(dir_path) = stack.pop() {
@@ -56,7 +56,7 @@
             let mut node = self
                 .node_mut(&fdt_path)
                 .map_err(|e| anyhow!("Failed to write FDT, {e:?}"))?
-                .ok_or_else(|| anyhow!("Failed to find {fdt_node_path:?} in FDT"))?;
+                .ok_or_else(|| anyhow!("Failed to find {fdt_path:?} in FDT"))?;
 
             let mut subnode_names = vec![];
             let entries =
@@ -90,9 +90,21 @@
             // FDT library may omit address in node name when comparing their name, so sort to add
             // node without address first.
             subnode_names.sort();
-            let subnode_names_c_str: Vec<_> = subnode_names.iter().map(|x| x.as_c_str()).collect();
-            node.add_subnodes(&subnode_names_c_str)
-                .map_err(|e| anyhow!("Failed to add node, {e:?}"))?;
+            let subnode_names: Vec<_> = subnode_names
+                .iter()
+                .filter_map(|name| {
+                    // Filter out subnode names which are already present in the target parent node!
+                    let name = name.as_c_str();
+                    let is_present_res = node.as_node().subnode(name);
+                    match is_present_res {
+                        Ok(Some(_)) => None,
+                        Ok(None) => Some(Ok(name)),
+                        Err(e) => Some(Err(e)),
+                    }
+                })
+                .collect::<Result<_, _>>()
+                .map_err(|e| anyhow!("Failed to filter subnodes, {e:?}"))?;
+            node.add_subnodes(&subnode_names).map_err(|e| anyhow!("Failed to add node, {e:?}"))?;
         }
 
         Ok(())
@@ -149,5 +161,8 @@
         let actual = dts_from_dtb(file.path());
 
         assert_eq!(&expected, &actual);
+        // Again append fdt from TEST_FS_FDT_ROOT_PATH at root & ensure it succeeds when some
+        // subnode are already present.
+        fdt.overlay_onto(&CString::new("/").unwrap(), fs_path).unwrap();
     }
 }
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index 62df47d..a2194cc 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -19,9 +19,9 @@
 use crate::composite::make_composite_image;
 use crate::crosvm::{CrosvmConfig, DiskFile, PayloadState, VmContext, VmInstance, VmState};
 use crate::debug_config::DebugConfig;
+use crate::dt_overlay::{create_device_tree_overlay, VM_DT_OVERLAY_MAX_SIZE, VM_DT_OVERLAY_PATH};
 use crate::payload::{add_microdroid_payload_images, add_microdroid_system_images, add_microdroid_vendor_image};
 use crate::selinux::{getfilecon, SeContext};
-use crate::reference_dt;
 use android_os_permissions_aidl::aidl::android::os::IPermissionController;
 use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::{
     Certificate::Certificate,
@@ -69,6 +69,7 @@
     Status, StatusCode, Strong,
     IntoBinderResult,
 };
+use cstr::cstr;
 use disk::QcowFile;
 use glob::glob;
 use lazy_static::lazy_static;
@@ -80,6 +81,7 @@
 use semver::VersionReq;
 use std::collections::HashSet;
 use std::convert::TryInto;
+use std::fs;
 use std::ffi::CStr;
 use std::fs::{canonicalize, read_dir, remove_file, File, OpenOptions};
 use std::io::{BufRead, BufReader, Error, ErrorKind, Seek, SeekFrom, Write};
@@ -119,6 +121,8 @@
 /// crosvm requires all partitions to be a multiple of 4KiB.
 const PARTITION_GRANULARITY_BYTES: u64 = 4096;
 
+const VM_REFERENCE_DT_ON_HOST_PATH: &str = "/proc/device-tree/avf/reference";
+
 lazy_static! {
     pub static ref GLOBAL_SERVICE: Strong<dyn IVirtualizationServiceInternal> =
         wait_for_interface(BINDER_SERVICE_IDENTIFIER)
@@ -382,12 +386,36 @@
             check_gdb_allowed(config)?;
         }
 
-        let reference_dt = reference_dt::parse_reference_dt(&temporary_directory)
-            .context("Failed to create VM reference DT")
-            .or_service_specific_exception(-1)?;
-        if reference_dt.is_none() {
-            warn!("VM reference DT doesn't exist");
-        }
+        // Currently, VirtMgr adds the host copy of reference DT & an untrusted prop (instance-id)
+        let host_ref_dt = Path::new(VM_REFERENCE_DT_ON_HOST_PATH);
+        let host_ref_dt = if host_ref_dt.exists()
+            && read_dir(host_ref_dt).or_service_specific_exception(-1)?.next().is_some()
+        {
+            Some(host_ref_dt)
+        } else {
+            warn!("VM reference DT doesn't exist in host DT");
+            None
+        };
+
+        let untrusted_props = if cfg!(llpvm_changes) {
+            // TODO(b/291213394): Replace this with a per-VM instance Id.
+            let instance_id = b"sixtyfourbyteslonghardcoded_indeed_sixtyfourbyteslonghardcoded_h";
+            vec![(cstr!("instance-id"), &instance_id[..])]
+        } else {
+            vec![]
+        };
+
+        let device_tree_overlay = if host_ref_dt.is_some() || !untrusted_props.is_empty() {
+            let dt_output = temporary_directory.join(VM_DT_OVERLAY_PATH);
+            let mut data = [0_u8; VM_DT_OVERLAY_MAX_SIZE];
+            let fdt = create_device_tree_overlay(&mut data, host_ref_dt, &untrusted_props)
+                .map_err(|e| anyhow!("Failed to create DT overlay, {e:?}"))
+                .or_service_specific_exception(-1)?;
+            fs::write(&dt_output, fdt.as_slice()).or_service_specific_exception(-1)?;
+            Some(File::open(dt_output).or_service_specific_exception(-1)?)
+        } else {
+            None
+        };
 
         let debug_level = match config {
             VirtualMachineConfig::AppConfig(config) => config.debugLevel,
@@ -537,7 +565,7 @@
             gdb_port,
             vfio_devices,
             dtbo,
-            reference_dt,
+            device_tree_overlay,
         };
         let instance = Arc::new(
             VmInstance::new(
diff --git a/virtualizationmanager/src/crosvm.rs b/virtualizationmanager/src/crosvm.rs
index 84c60bd..2c23441 100644
--- a/virtualizationmanager/src/crosvm.rs
+++ b/virtualizationmanager/src/crosvm.rs
@@ -118,7 +118,7 @@
     pub gdb_port: Option<NonZeroU16>,
     pub vfio_devices: Vec<VfioDevice>,
     pub dtbo: Option<File>,
-    pub reference_dt: Option<File>,
+    pub device_tree_overlay: Option<File>,
 }
 
 /// A disk image to pass to crosvm for a VM.
@@ -896,10 +896,8 @@
         .arg("--socket")
         .arg(add_preserved_fd(&mut preserved_fds, &control_server_socket.as_raw_descriptor()));
 
-    if let Some(reference_dt) = &config.reference_dt {
-        command
-            .arg("--device-tree-overlay")
-            .arg(add_preserved_fd(&mut preserved_fds, reference_dt));
+    if let Some(dt_overlay) = &config.device_tree_overlay {
+        command.arg("--device-tree-overlay").arg(add_preserved_fd(&mut preserved_fds, dt_overlay));
     }
 
     append_platform_devices(&mut command, &mut preserved_fds, &config)?;
diff --git a/virtualizationmanager/src/dt_overlay.rs b/virtualizationmanager/src/dt_overlay.rs
new file mode 100644
index 0000000..83f7734
--- /dev/null
+++ b/virtualizationmanager/src/dt_overlay.rs
@@ -0,0 +1,117 @@
+// Copyright 2024, 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 module support creating AFV related overlays, that can then be appended to DT by VM.
+
+use anyhow::{anyhow, Result};
+use cstr::cstr;
+use fsfdt::FsFdt;
+use libfdt::Fdt;
+use std::ffi::CStr;
+use std::path::Path;
+
+pub(crate) const AVF_NODE_NAME: &CStr = cstr!("avf");
+pub(crate) const UNTRUSTED_NODE_NAME: &CStr = cstr!("untrusted");
+pub(crate) const VM_DT_OVERLAY_PATH: &str = "vm_dt_overlay.dtbo";
+pub(crate) const VM_DT_OVERLAY_MAX_SIZE: usize = 2000;
+
+/// Create a Device tree overlay containing the provided proc style device tree & properties!
+/// # Arguments
+/// * `dt_path` - (Optional) Path to (proc style) device tree to be included in the overlay.
+/// * `untrusted_props` - Include a property in /avf/untrusted node. This node is used to specify
+///   host provided properties such as `instance-id`.
+///
+/// Example: with `create_device_tree_overlay(_, _, [("instance-id", _),])`
+/// ```
+///   {
+///     fragment@0 {
+///         target-path = "/";
+///         __overlay__ {
+///             avf {
+///                 untrusted { instance-id = [0x01 0x23 .. ] }
+///               }
+///             };
+///         };
+///     };
+/// };
+/// ```
+pub(crate) fn create_device_tree_overlay<'a>(
+    buffer: &'a mut [u8],
+    dt_path: Option<&'a Path>,
+    untrusted_props: &[(&'a CStr, &'a [u8])],
+) -> Result<&'a mut Fdt> {
+    if dt_path.is_none() && untrusted_props.is_empty() {
+        return Err(anyhow!("Expected at least one device tree addition"));
+    }
+
+    let fdt =
+        Fdt::create_empty_tree(buffer).map_err(|e| anyhow!("Failed to create empty Fdt: {e:?}"))?;
+    let mut root = fdt.root_mut().map_err(|e| anyhow!("Failed to get root: {e:?}"))?;
+    let mut node =
+        root.add_subnode(cstr!("fragment@0")).map_err(|e| anyhow!("Failed to fragment: {e:?}"))?;
+    node.setprop(cstr!("target-path"), b"/\0")
+        .map_err(|e| anyhow!("Failed to set target-path: {e:?}"))?;
+    let mut node = node
+        .add_subnode(cstr!("__overlay__"))
+        .map_err(|e| anyhow!("Failed to __overlay__ node: {e:?}"))?;
+
+    if !untrusted_props.is_empty() {
+        let mut node = node
+            .add_subnode(AVF_NODE_NAME)
+            .map_err(|e| anyhow!("Failed to add avf node: {e:?}"))?;
+        let mut node = node
+            .add_subnode(UNTRUSTED_NODE_NAME)
+            .map_err(|e| anyhow!("Failed to add /avf/untrusted node: {e:?}"))?;
+        for (name, value) in untrusted_props {
+            node.setprop(name, value).map_err(|e| anyhow!("Failed to set property: {e:?}"))?;
+        }
+    }
+
+    if let Some(path) = dt_path {
+        fdt.overlay_onto(cstr!("/fragment@0/__overlay__"), path)?;
+    }
+    fdt.pack().map_err(|e| anyhow!("Failed to pack DT overlay, {e:?}"))?;
+
+    Ok(fdt)
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn empty_overlays_not_allowed() {
+        let mut buffer = vec![0_u8; VM_DT_OVERLAY_MAX_SIZE];
+        let res = create_device_tree_overlay(&mut buffer, None, &[]);
+        assert!(res.is_err());
+    }
+
+    #[test]
+    fn untrusted_prop_test() {
+        let mut buffer = vec![0_u8; VM_DT_OVERLAY_MAX_SIZE];
+        let prop_name = cstr!("XOXO");
+        let prop_val_input = b"OXOX";
+        let fdt =
+            create_device_tree_overlay(&mut buffer, None, &[(prop_name, prop_val_input)]).unwrap();
+
+        let prop_value_dt = fdt
+            .node(cstr!("/fragment@0/__overlay__/avf/untrusted"))
+            .unwrap()
+            .expect("/avf/untrusted node doesn't exist")
+            .getprop(prop_name)
+            .unwrap()
+            .expect("Prop not found!");
+        assert_eq!(prop_value_dt, prop_val_input, "Unexpected property value");
+    }
+}
diff --git a/virtualizationmanager/src/main.rs b/virtualizationmanager/src/main.rs
index 2e542c3..b2a734a 100644
--- a/virtualizationmanager/src/main.rs
+++ b/virtualizationmanager/src/main.rs
@@ -19,8 +19,8 @@
 mod composite;
 mod crosvm;
 mod debug_config;
+mod dt_overlay;
 mod payload;
-mod reference_dt;
 mod selinux;
 
 use crate::aidl::{GLOBAL_SERVICE, VirtualizationService};
diff --git a/virtualizationmanager/src/reference_dt.rs b/virtualizationmanager/src/reference_dt.rs
deleted file mode 100644
index 797ee3c..0000000
--- a/virtualizationmanager/src/reference_dt.rs
+++ /dev/null
@@ -1,93 +0,0 @@
-// Copyright 2024, 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.
-
-//! Functions for VM reference DT
-
-use anyhow::{anyhow, Result};
-use cstr::cstr;
-use fsfdt::FsFdt;
-use libfdt::Fdt;
-use std::fs;
-use std::fs::File;
-use std::path::Path;
-
-const VM_REFERENCE_DT_ON_HOST_PATH: &str = "/proc/device-tree/avf/reference";
-const VM_REFERENCE_DT_NAME: &str = "vm_reference_dt.dtbo";
-const VM_REFERENCE_DT_MAX_SIZE: usize = 2000;
-
-// Parses to VM reference if exists.
-// TODO(b/318431695): Allow to parse from custom VM reference DT
-pub(crate) fn parse_reference_dt(out_dir: &Path) -> Result<Option<File>> {
-    parse_reference_dt_internal(
-        Path::new(VM_REFERENCE_DT_ON_HOST_PATH),
-        &out_dir.join(VM_REFERENCE_DT_NAME),
-    )
-}
-
-fn parse_reference_dt_internal(dir_path: &Path, fdt_path: &Path) -> Result<Option<File>> {
-    if !dir_path.exists() || fs::read_dir(dir_path)?.next().is_none() {
-        return Ok(None);
-    }
-
-    let mut data = vec![0_u8; VM_REFERENCE_DT_MAX_SIZE];
-
-    let fdt = Fdt::create_empty_tree(&mut data)
-        .map_err(|e| anyhow!("Failed to create an empty DT, {e:?}"))?;
-    let mut root = fdt.root_mut().map_err(|e| anyhow!("Failed to find the DT root, {e:?}"))?;
-    let mut fragment = root
-        .add_subnode(cstr!("fragment@0"))
-        .map_err(|e| anyhow!("Failed to create the fragment@0, {e:?}"))?;
-    fragment
-        .setprop(cstr!("target-path"), b"/\0")
-        .map_err(|e| anyhow!("Failed to set target-path, {e:?}"))?;
-    fragment
-        .add_subnode(cstr!("__overlay__"))
-        .map_err(|e| anyhow!("Failed to create the __overlay__, {e:?}"))?;
-
-    fdt.append(cstr!("/fragment@0/__overlay__"), dir_path)?;
-
-    fdt.pack().map_err(|e| anyhow!("Failed to pack VM reference DT, {e:?}"))?;
-    fs::write(fdt_path, fdt.as_slice())?;
-
-    Ok(Some(File::open(fdt_path)?))
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn test_parse_reference_dt_from_empty_dir() {
-        let empty_dir = tempfile::TempDir::new().unwrap();
-        let test_dir = tempfile::TempDir::new().unwrap();
-
-        let empty_dir_path = empty_dir.path();
-        let fdt_path = test_dir.path().join("test.dtb");
-
-        let fdt_file = parse_reference_dt_internal(empty_dir_path, &fdt_path).unwrap();
-
-        assert!(fdt_file.is_none());
-    }
-
-    #[test]
-    fn test_parse_reference_dt_from_empty_reference() {
-        let fdt_file = parse_reference_dt_internal(
-            Path::new("/this/path/would/not/exists"),
-            Path::new("test.dtb"),
-        )
-        .unwrap();
-
-        assert!(fdt_file.is_none());
-    }
-}