Merge "[pvmfw/rialto] Disable full LTO in vmbase" into main
diff --git a/TEST_MAPPING b/TEST_MAPPING
index db0b43a..6d585a6 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -18,6 +18,9 @@
       "name": "MicrodroidTestApp"
     },
     {
+      "name": "MicrodroidTestAppNoInternetPerm"
+    },
+    {
       "name": "MicrodroidTestAppNoPerm"
     },
     {
diff --git a/apex/Android.bp b/apex/Android.bp
index 43819dc..17b1f9e 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -157,6 +157,7 @@
         "microdroid.json",
         "microdroid_kernel",
         "com.android.virt.init.rc",
+        "android_bootloader_crosvm_aarch64",
     ],
     host_required: [
         "vm_shell",
diff --git a/compos/tests/java/android/compos/test/ComposTestCase.java b/compos/tests/java/android/compos/test/ComposTestCase.java
index bd011fa..b31f4f3 100644
--- a/compos/tests/java/android/compos/test/ComposTestCase.java
+++ b/compos/tests/java/android/compos/test/ComposTestCase.java
@@ -24,11 +24,13 @@
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 
 import android.platform.test.annotations.RootPermissionTest;
 
 import com.android.microdroid.test.host.CommandRunner;
 import com.android.microdroid.test.host.MicrodroidHostTestCaseBase;
+import com.android.tradefed.device.TestDevice;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.result.FileInputStreamSource;
 import com.android.tradefed.result.LogDataType;
@@ -85,6 +87,8 @@
         assumeDeviceIsCapable(getDevice());
         // Test takes too long to run on Cuttlefish (b/292824951).
         assumeFalse("Skipping test on Cuttlefish", isCuttlefish());
+        // CompOS requires a protected VM
+        assumeTrue(((TestDevice) getDevice()).supportsMicrodroid(/*protectedVm*/ true));
 
         String value = getDevice().getProperty(SYSTEM_SERVER_COMPILER_FILTER_PROP_NAME);
         if (value == null) {
diff --git a/docs/custom_vm.md b/docs/custom_vm.md
index d52aa95..840acc3 100644
--- a/docs/custom_vm.md
+++ b/docs/custom_vm.md
@@ -190,6 +190,10 @@
             "writable": true
         }
     ],
+    "gpu": {
+        "backend": "virglrenderer",
+        "context_types": ["virgl2"]
+    },
     "params": "root=/dev/vda3 rootwait noinitrd ro enforcing=0 cros_debug cros_secure",
     "protected": false,
     "cpu_topology": "match_host",
diff --git a/java/framework/api/test-current.txt b/java/framework/api/test-current.txt
index d20d543..30dd7d9 100644
--- a/java/framework/api/test-current.txt
+++ b/java/framework/api/test-current.txt
@@ -11,12 +11,14 @@
     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") @NonNull public String getOs();
     method @Nullable public String getPayloadConfigPath();
+    method public boolean isNetworkSupported();
     method public boolean isVmConsoleInputSupported();
     field @FlaggedApi("com.android.system.virtualmachine.flags.avf_v_test_apis") public static final String MICRODROID = "microdroid";
   }
 
   public static final class VirtualMachineConfig.Builder {
     method @FlaggedApi("com.android.system.virtualmachine.flags.avf_v_test_apis") @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder addExtraApk(@NonNull String);
+    method @NonNull @RequiresPermission(allOf={android.system.virtualmachine.VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION, android.Manifest.permission.INTERNET}) public android.system.virtualmachine.VirtualMachineConfig.Builder setNetworkSupported(boolean);
     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 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("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);
@@ -31,6 +33,7 @@
     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_LLPVM_CHANGES = "com.android.kvm.LLPVM_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 public static final String FEATURE_NETWORK = "com.android.kvm.NETWORK";
     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/java/framework/src/android/system/virtualmachine/VirtualMachineConfig.java b/java/framework/src/android/system/virtualmachine/VirtualMachineConfig.java
index 4d3bf2d..8b444fc 100644
--- a/java/framework/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/java/framework/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -22,6 +22,7 @@
 
 import static java.util.Objects.requireNonNull;
 
+import android.Manifest;
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
@@ -77,9 +78,11 @@
     private static final String TAG = "VirtualMachineConfig";
 
     private static String[] EMPTY_STRING_ARRAY = {};
+    private static final String U_BOOT_PREBUILT_PATH = "/apex/com.android.virt/etc/u-boot.bin";
 
     // These define the schema of the config file persisted on disk.
-    private static final int VERSION = 8;
+    // Please bump up the version number when adding a new key.
+    private static final int VERSION = 9;
     private static final String KEY_VERSION = "version";
     private static final String KEY_PACKAGENAME = "packageName";
     private static final String KEY_APKPATH = "apkPath";
@@ -98,6 +101,8 @@
     private static final String KEY_VENDOR_DISK_IMAGE_PATH = "vendorDiskImagePath";
     private static final String KEY_OS = "os";
     private static final String KEY_EXTRA_APKS = "extraApks";
+    private static final String KEY_NETWORK_SUPPORTED = "networkSupported";
+    private static final String KEY_SHOULD_BOOST_UCLAMP = "shouldBoostUclamp";
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
@@ -205,6 +210,11 @@
     /** OS name of the VM using payload binaries. */
     @NonNull @OsName private final String mOs;
 
+    /** Whether to run the VM with supporting network feature or not. */
+    private final boolean mNetworkSupported;
+
+    private final boolean mShouldBoostUclamp;
+
     @Retention(RetentionPolicy.SOURCE)
     @StringDef(
             prefix = "MICRODROID",
@@ -239,7 +249,9 @@
             boolean vmConsoleInputSupported,
             boolean connectVmConsole,
             @Nullable File vendorDiskImage,
-            @NonNull @OsName String os) {
+            @NonNull @OsName String os,
+            boolean networkSupported,
+            boolean shouldBoostUclamp) {
         // This is only called from Builder.build(); the builder handles parameter validation.
         mPackageName = packageName;
         mApkPath = apkPath;
@@ -262,6 +274,8 @@
         mConnectVmConsole = connectVmConsole;
         mVendorDiskImage = vendorDiskImage;
         mOs = os;
+        mNetworkSupported = networkSupported;
+        mShouldBoostUclamp = shouldBoostUclamp;
     }
 
     /** Loads a config from a file. */
@@ -362,6 +376,9 @@
             }
         }
 
+        builder.setNetworkSupported(b.getBoolean(KEY_NETWORK_SUPPORTED));
+
+        builder.setShouldBoostUclamp(b.getBoolean(KEY_SHOULD_BOOST_UCLAMP));
         return builder.build();
     }
 
@@ -412,6 +429,8 @@
             String[] extraApks = mExtraApks.toArray(new String[0]);
             b.putStringArray(KEY_EXTRA_APKS, extraApks);
         }
+        b.putBoolean(KEY_NETWORK_SUPPORTED, mNetworkSupported);
+        b.putBoolean(KEY_SHOULD_BOOST_UCLAMP, mShouldBoostUclamp);
         b.writeToStream(output);
     }
 
@@ -588,6 +607,16 @@
     }
 
     /**
+     * Returns whether the network feature is supported to the VM or not.
+     *
+     * @hide
+     */
+    @TestApi
+    public boolean isNetworkSupported() {
+        return mNetworkSupported;
+    }
+
+    /**
      * Tests if this config is compatible with other config. Being compatible means that the configs
      * can be interchangeably used for the same virtual machine; they do not change the VM identity
      * or secrets. Such changes include varying the number of CPUs or the size of the RAM. Changes
@@ -653,6 +682,11 @@
                 Optional.ofNullable(customImageConfig.getBootloaderPath())
                         .map((path) -> openOrNull(new File(path), MODE_READ_ONLY))
                         .orElse(null);
+
+        if (config.kernel == null && config.bootloader == null) {
+            config.bootloader = openOrNull(new File(U_BOOT_PREBUILT_PATH), MODE_READ_ONLY);
+        }
+
         config.params =
                 Optional.ofNullable(customImageConfig.getParams())
                         .map((params) -> TextUtils.join(" ", params))
@@ -677,11 +711,16 @@
                 Optional.ofNullable(customImageConfig.getDisplayConfig())
                         .map(dc -> dc.toParcelable())
                         .orElse(null);
+        config.gpuConfig =
+                Optional.ofNullable(customImageConfig.getGpuConfig())
+                        .map(dc -> dc.toParcelable())
+                        .orElse(null);
         config.protectedVm = this.mProtectedVm;
         config.memoryMib = bytesToMebiBytes(mMemoryBytes);
         config.cpuTopology = (byte) this.mCpuTopology;
         config.consoleInputDevice = mConsoleInputDevice;
         config.devices = EMPTY_STRING_ARRAY;
+        config.networkSupported = this.mNetworkSupported;
         config.platformVersion = "~1.0";
         return config;
     }
@@ -733,20 +772,27 @@
                 vsConfig.cpuTopology = android.system.virtualizationservice.CpuTopology.ONE_CPU;
                 break;
         }
-        if (mVendorDiskImage != null) {
+
+        if (mVendorDiskImage != null || mNetworkSupported) {
             VirtualMachineAppConfig.CustomConfig customConfig =
                     new VirtualMachineAppConfig.CustomConfig();
             customConfig.devices = EMPTY_STRING_ARRAY;
-            try {
-                customConfig.vendorImage =
-                        ParcelFileDescriptor.open(mVendorDiskImage, MODE_READ_ONLY);
-            } catch (FileNotFoundException e) {
-                throw new VirtualMachineException(
-                        "Failed to open vendor disk image " + mVendorDiskImage.getAbsolutePath(),
-                        e);
+            if (mVendorDiskImage != null) {
+                try {
+                    customConfig.vendorImage =
+                            ParcelFileDescriptor.open(mVendorDiskImage, MODE_READ_ONLY);
+                } catch (FileNotFoundException e) {
+                    throw new VirtualMachineException(
+                            "Failed to open vendor disk image "
+                                    + mVendorDiskImage.getAbsolutePath(),
+                            e);
+                }
             }
+            customConfig.networkSupported = mNetworkSupported;
             vsConfig.customConfig = customConfig;
         }
+
+        vsConfig.boostUclamp = mShouldBoostUclamp;
         return vsConfig;
     }
 
@@ -826,6 +872,8 @@
         private boolean mConnectVmConsole = false;
         @Nullable private File mVendorDiskImage;
         @NonNull @OsName private String mOs = DEFAULT_OS;
+        private boolean mNetworkSupported;
+        private boolean mShouldBoostUclamp = false;
 
         /**
          * Creates a builder for the given context.
@@ -902,6 +950,10 @@
                         "debug level must be FULL to connect to the console");
             }
 
+            if (mNetworkSupported && mProtectedVm) {
+                throw new IllegalStateException("network is not supported on pVM");
+            }
+
             return new VirtualMachineConfig(
                     packageName,
                     apkPath,
@@ -919,7 +971,9 @@
                     mVmConsoleInputSupported,
                     mConnectVmConsole,
                     mVendorDiskImage,
-                    mOs);
+                    mOs,
+                    mNetworkSupported,
+                    mShouldBoostUclamp);
         }
 
         /**
@@ -1224,5 +1278,28 @@
             mOs = requireNonNull(os, "os must not be null");
             return this;
         }
+
+        /**
+         * Sets whether to support network feature to VM. Default is {@code false}.
+         *
+         * @hide
+         */
+        @TestApi
+        @RequiresPermission(
+                allOf = {
+                    VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION,
+                    Manifest.permission.INTERNET
+                })
+        @NonNull
+        public Builder setNetworkSupported(boolean networkSupported) {
+            mNetworkSupported = networkSupported;
+            return this;
+        }
+
+        /** @hide */
+        public Builder setShouldBoostUclamp(boolean shouldBoostUclamp) {
+            mShouldBoostUclamp = shouldBoostUclamp;
+            return this;
+        }
     }
 }
diff --git a/java/framework/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java b/java/framework/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java
index 2fcad20..c0ff11a 100644
--- a/java/framework/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java
+++ b/java/framework/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java
@@ -36,6 +36,7 @@
     private static final String KEY_TOUCH = "touch";
     private static final String KEY_KEYBOARD = "keyboard";
     private static final String KEY_MOUSE = "mouse";
+    private static final String KEY_GPU = "gpu";
 
     @Nullable private final String name;
     @Nullable private final String kernelPath;
@@ -47,6 +48,7 @@
     private final boolean touch;
     private final boolean keyboard;
     private final boolean mouse;
+    @Nullable private final GpuConfig gpuConfig;
 
     @Nullable
     public Disk[] getDisks() {
@@ -101,7 +103,8 @@
             DisplayConfig displayConfig,
             boolean touch,
             boolean keyboard,
-            boolean mouse) {
+            boolean mouse,
+            GpuConfig gpuConfig) {
         this.name = name;
         this.kernelPath = kernelPath;
         this.initrdPath = initrdPath;
@@ -112,6 +115,7 @@
         this.touch = touch;
         this.keyboard = keyboard;
         this.mouse = mouse;
+        this.gpuConfig = gpuConfig;
     }
 
     static VirtualMachineCustomImageConfig from(PersistableBundle customImageConfigBundle) {
@@ -142,6 +146,7 @@
         builder.useTouch(customImageConfigBundle.getBoolean(KEY_TOUCH));
         builder.useKeyboard(customImageConfigBundle.getBoolean(KEY_KEYBOARD));
         builder.useMouse(customImageConfigBundle.getBoolean(KEY_MOUSE));
+        builder.setGpuConfig(GpuConfig.from(customImageConfigBundle.getPersistableBundle(KEY_GPU)));
         return builder.build();
     }
 
@@ -173,6 +178,9 @@
         pb.putBoolean(KEY_TOUCH, touch);
         pb.putBoolean(KEY_KEYBOARD, keyboard);
         pb.putBoolean(KEY_MOUSE, mouse);
+        pb.putPersistableBundle(
+                KEY_GPU,
+                Optional.ofNullable(gpuConfig).map(gc -> gc.toPersistableBundle()).orElse(null));
         return pb;
     }
 
@@ -181,6 +189,11 @@
         return displayConfig;
     }
 
+    @Nullable
+    public GpuConfig getGpuConfig() {
+        return gpuConfig;
+    }
+
     /** @hide */
     public static final class Disk {
         private final boolean writable;
@@ -224,6 +237,7 @@
         private boolean touch;
         private boolean keyboard;
         private boolean mouse;
+        private GpuConfig gpuConfig;
 
         /** @hide */
         public Builder() {}
@@ -271,6 +285,12 @@
         }
 
         /** @hide */
+        public Builder setGpuConfig(GpuConfig gpuConfig) {
+            this.gpuConfig = gpuConfig;
+            return this;
+        }
+
+        /** @hide */
         public Builder useTouch(boolean touch) {
             this.touch = touch;
             return this;
@@ -300,7 +320,8 @@
                     displayConfig,
                     touch,
                     keyboard,
-                    mouse);
+                    mouse,
+                    gpuConfig);
         }
     }
 
@@ -437,4 +458,223 @@
             }
         }
     }
+
+    /** @hide */
+    public static final class GpuConfig {
+        private static final String KEY_BACKEND = "backend";
+        private static final String KEY_CONTEXT_TYPES = "context_types";
+        private static final String KEY_PCI_ADDRESS = "pci_address";
+        private static final String KEY_RENDERER_FEATURES = "renderer_features";
+        private static final String KEY_RENDERER_USE_EGL = "renderer_use_egl";
+        private static final String KEY_RENDERER_USE_GLES = "renderer_use_gles";
+        private static final String KEY_RENDERER_USE_GLX = "renderer_use_glx";
+        private static final String KEY_RENDERER_USE_SURFACELESS = "renderer_use_surfaceless";
+        private static final String KEY_RENDERER_USE_VULKAN = "renderer_use_vulkan";
+
+        private final String backend;
+        private final String[] contextTypes;
+        private final String pciAddress;
+        private final String rendererFeatures;
+        private final boolean rendererUseEgl;
+        private final boolean rendererUseGles;
+        private final boolean rendererUseGlx;
+        private final boolean rendererUseSurfaceless;
+        private final boolean rendererUseVulkan;
+
+        private GpuConfig(
+                String backend,
+                String[] contextTypes,
+                String pciAddress,
+                String rendererFeatures,
+                boolean rendererUseEgl,
+                boolean rendererUseGles,
+                boolean rendererUseGlx,
+                boolean rendererUseSurfaceless,
+                boolean rendererUseVulkan) {
+            this.backend = backend;
+            this.contextTypes = contextTypes;
+            this.pciAddress = pciAddress;
+            this.rendererFeatures = rendererFeatures;
+            this.rendererUseEgl = rendererUseEgl;
+            this.rendererUseGles = rendererUseGles;
+            this.rendererUseGlx = rendererUseGlx;
+            this.rendererUseSurfaceless = rendererUseSurfaceless;
+            this.rendererUseVulkan = rendererUseVulkan;
+        }
+
+        /** @hide */
+        public String getBackend() {
+            return backend;
+        }
+
+        /** @hide */
+        public String[] getContextTypes() {
+            return contextTypes;
+        }
+
+        /** @hide */
+        public String getPciAddress() {
+            return pciAddress;
+        }
+
+        /** @hide */
+        public String getRendererFeatures() {
+            return rendererFeatures;
+        }
+
+        /** @hide */
+        public boolean getRendererUseEgl() {
+            return rendererUseEgl;
+        }
+
+        /** @hide */
+        public boolean getRendererUseGles() {
+            return rendererUseGles;
+        }
+
+        /** @hide */
+        public boolean getRendererUseGlx() {
+            return rendererUseGlx;
+        }
+
+        /** @hide */
+        public boolean getRendererUseSurfaceless() {
+            return rendererUseSurfaceless;
+        }
+
+        /** @hide */
+        public boolean getRendererUseVulkan() {
+            return rendererUseVulkan;
+        }
+
+        android.system.virtualizationservice.GpuConfig toParcelable() {
+            android.system.virtualizationservice.GpuConfig parcelable =
+                    new android.system.virtualizationservice.GpuConfig();
+            parcelable.backend = this.backend;
+            parcelable.contextTypes = this.contextTypes;
+            parcelable.pciAddress = this.pciAddress;
+            parcelable.rendererFeatures = this.rendererFeatures;
+            parcelable.rendererUseEgl = this.rendererUseEgl;
+            parcelable.rendererUseGles = this.rendererUseGles;
+            parcelable.rendererUseGlx = this.rendererUseGlx;
+            parcelable.rendererUseSurfaceless = this.rendererUseSurfaceless;
+            parcelable.rendererUseVulkan = this.rendererUseVulkan;
+            return parcelable;
+        }
+
+        private static GpuConfig from(PersistableBundle pb) {
+            if (pb == null) {
+                return null;
+            }
+            Builder builder = new Builder();
+            builder.setBackend(pb.getString(KEY_BACKEND));
+            builder.setContextTypes(pb.getStringArray(KEY_CONTEXT_TYPES));
+            builder.setPciAddress(pb.getString(KEY_PCI_ADDRESS));
+            builder.setRendererFeatures(pb.getString(KEY_RENDERER_FEATURES));
+            builder.setRendererUseEgl(pb.getBoolean(KEY_RENDERER_USE_EGL));
+            builder.setRendererUseGles(pb.getBoolean(KEY_RENDERER_USE_GLES));
+            builder.setRendererUseGlx(pb.getBoolean(KEY_RENDERER_USE_GLX));
+            builder.setRendererUseSurfaceless(pb.getBoolean(KEY_RENDERER_USE_SURFACELESS));
+            builder.setRendererUseVulkan(pb.getBoolean(KEY_RENDERER_USE_VULKAN));
+            return builder.build();
+        }
+
+        private PersistableBundle toPersistableBundle() {
+            PersistableBundle pb = new PersistableBundle();
+            pb.putString(KEY_BACKEND, this.backend);
+            pb.putStringArray(KEY_CONTEXT_TYPES, this.contextTypes);
+            pb.putString(KEY_PCI_ADDRESS, this.pciAddress);
+            pb.putString(KEY_RENDERER_FEATURES, this.rendererFeatures);
+            pb.putBoolean(KEY_RENDERER_USE_EGL, this.rendererUseEgl);
+            pb.putBoolean(KEY_RENDERER_USE_GLES, this.rendererUseGles);
+            pb.putBoolean(KEY_RENDERER_USE_GLX, this.rendererUseGlx);
+            pb.putBoolean(KEY_RENDERER_USE_SURFACELESS, this.rendererUseSurfaceless);
+            pb.putBoolean(KEY_RENDERER_USE_VULKAN, this.rendererUseVulkan);
+            return pb;
+        }
+
+        /** @hide */
+        public static class Builder {
+            private String backend;
+            private String[] contextTypes;
+            private String pciAddress;
+            private String rendererFeatures;
+            private boolean rendererUseEgl = true;
+            private boolean rendererUseGles = true;
+            private boolean rendererUseGlx = false;
+            private boolean rendererUseSurfaceless = true;
+            private boolean rendererUseVulkan = false;
+
+            /** @hide */
+            public Builder() {}
+
+            /** @hide */
+            public Builder setBackend(String backend) {
+                this.backend = backend;
+                return this;
+            }
+
+            /** @hide */
+            public Builder setContextTypes(String[] contextTypes) {
+                this.contextTypes = contextTypes;
+                return this;
+            }
+
+            /** @hide */
+            public Builder setPciAddress(String pciAddress) {
+                this.pciAddress = pciAddress;
+                return this;
+            }
+
+            /** @hide */
+            public Builder setRendererFeatures(String rendererFeatures) {
+                this.rendererFeatures = rendererFeatures;
+                return this;
+            }
+
+            /** @hide */
+            public Builder setRendererUseEgl(Boolean rendererUseEgl) {
+                this.rendererUseEgl = rendererUseEgl;
+                return this;
+            }
+
+            /** @hide */
+            public Builder setRendererUseGles(Boolean rendererUseGles) {
+                this.rendererUseGles = rendererUseGles;
+                return this;
+            }
+
+            /** @hide */
+            public Builder setRendererUseGlx(Boolean rendererUseGlx) {
+                this.rendererUseGlx = rendererUseGlx;
+                return this;
+            }
+
+            /** @hide */
+            public Builder setRendererUseSurfaceless(Boolean rendererUseSurfaceless) {
+                this.rendererUseSurfaceless = rendererUseSurfaceless;
+                return this;
+            }
+
+            /** @hide */
+            public Builder setRendererUseVulkan(Boolean rendererUseVulkan) {
+                this.rendererUseVulkan = rendererUseVulkan;
+                return this;
+            }
+
+            /** @hide */
+            public GpuConfig build() {
+                return new GpuConfig(
+                        backend,
+                        contextTypes,
+                        pciAddress,
+                        rendererFeatures,
+                        rendererUseEgl,
+                        rendererUseGles,
+                        rendererUseGlx,
+                        rendererUseSurfaceless,
+                        rendererUseVulkan);
+            }
+        }
+    }
 }
diff --git a/java/framework/src/android/system/virtualmachine/VirtualMachineManager.java b/java/framework/src/android/system/virtualmachine/VirtualMachineManager.java
index 4a9e943..abb2c81 100644
--- a/java/framework/src/android/system/virtualmachine/VirtualMachineManager.java
+++ b/java/framework/src/android/system/virtualmachine/VirtualMachineManager.java
@@ -123,6 +123,7 @@
                 FEATURE_DICE_CHANGES,
                 FEATURE_LLPVM_CHANGES,
                 FEATURE_MULTI_TENANT,
+                FEATURE_NETWORK,
                 FEATURE_REMOTE_ATTESTATION,
                 FEATURE_VENDOR_MODULES,
             })
@@ -147,6 +148,13 @@
     public static final String FEATURE_MULTI_TENANT = IVirtualizationService.FEATURE_MULTI_TENANT;
 
     /**
+     * Feature to allow network features in VM.
+     *
+     * @hide
+     */
+    @TestApi public static final String FEATURE_NETWORK = IVirtualizationService.FEATURE_NETWORK;
+
+    /**
      * Feature to allow remote attestation in Microdroid.
      *
      * @hide
diff --git a/libs/avf_features/src/lib.rs b/libs/avf_features/src/lib.rs
index c0faab0..1ebe2a4 100644
--- a/libs/avf_features/src/lib.rs
+++ b/libs/avf_features/src/lib.rs
@@ -16,7 +16,7 @@
 
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
     IVirtualizationService::FEATURE_DICE_CHANGES, IVirtualizationService::FEATURE_LLPVM_CHANGES,
-    IVirtualizationService::FEATURE_MULTI_TENANT,
+    IVirtualizationService::FEATURE_MULTI_TENANT, IVirtualizationService::FEATURE_NETWORK,
     IVirtualizationService::FEATURE_REMOTE_ATTESTATION,
     IVirtualizationService::FEATURE_VENDOR_MODULES,
 };
@@ -28,6 +28,7 @@
         FEATURE_DICE_CHANGES => cfg!(dice_changes),
         FEATURE_LLPVM_CHANGES => cfg!(llpvm_changes),
         FEATURE_MULTI_TENANT => cfg!(multi_tenant),
+        FEATURE_NETWORK => cfg!(network),
         FEATURE_REMOTE_ATTESTATION => cfg!(remote_attestation),
         FEATURE_VENDOR_MODULES => cfg!(vendor_modules),
         _ => {
diff --git a/libs/hypervisor_props/src/lib.rs b/libs/hypervisor_props/src/lib.rs
index 14614fd..6665bc5 100644
--- a/libs/hypervisor_props/src/lib.rs
+++ b/libs/hypervisor_props/src/lib.rs
@@ -37,3 +37,8 @@
 pub fn version() -> Result<Option<String>> {
     Ok(hypervisorproperties::hypervisor_version()?)
 }
+
+/// Returns if the hypervisor is pKVM
+pub fn is_pkvm() -> Result<bool> {
+    Ok(version()?.unwrap_or_default().starts_with("kvm") && is_protected_vm_supported()?)
+}
diff --git a/microdroid_manager/src/vm_secret.rs b/microdroid_manager/src/vm_secret.rs
index ec40b45..c16a45e 100644
--- a/microdroid_manager/src/vm_secret.rs
+++ b/microdroid_manager/src/vm_secret.rs
@@ -20,7 +20,7 @@
 use secretkeeper_comm::data_types::request::Request;
 use binder::{Strong};
 use coset::{CoseKey, CborSerializable, CborOrdering};
-use dice_policy_builder::{CertIndex, ConstraintSpec, ConstraintType, policy_for_dice_chain, MissingAction, WILDCARD_FULL_ARRAY};
+use dice_policy_builder::{TargetEntry, ConstraintSpec, ConstraintType, policy_for_dice_chain, MissingAction, WILDCARD_FULL_ARRAY};
 use diced_open_dice::{DiceArtifacts, OwnedDiceArtifacts};
 use keystore2_crypto::ZVec;
 use openssl::hkdf::hkdf;
@@ -45,9 +45,10 @@
 const SUBCOMPONENT_DESCRIPTORS: i64 = -71002;
 const SUBCOMPONENT_SECURITY_VERSION: i64 = 2;
 const SUBCOMPONENT_AUTHORITY_HASH: i64 = 4;
-// Index of DiceChainEntry corresponding to Payload (relative to the end considering DICE Chain
-// as an array)
-const PAYLOAD_INDEX_FROM_END: usize = 0;
+// See dice_for_avf_guest.cddl for the `component_name` used by different boot stages in guest VM.
+const MICRODROID_PAYLOAD_COMPONENT_NAME: &str = "Microdroid payload";
+const GUEST_OS_COMPONENT_NAME: &str = "vm_entry";
+const INSTANCE_HASH_KEY: i64 = -71003;
 
 // Generated using hexdump -vn32 -e'14/1 "0x%02X, " 1 "\n"' /dev/urandom
 const SALT_ENCRYPTED_STORE: &[u8] = &[
@@ -173,25 +174,27 @@
 //     microdroid_manager/src/vm_config.cddl):
 //       - GreaterOrEqual on SECURITY_VERSION (Required)
 //       - ExactMatch on AUTHORITY_HASH (Required).
+//  5. ExactMatch on Instance Hash (Required) - This uniquely identifies one VM instance from
+//     another even if they are running the exact same images.
 fn sealing_policy(dice: &[u8]) -> Result<Vec<u8>, String> {
-    let constraint_spec = [
+    let constraint_spec = vec![
         ConstraintSpec::new(
             ConstraintType::ExactMatch,
             vec![AUTHORITY_HASH],
             MissingAction::Fail,
-            CertIndex::All,
+            TargetEntry::All,
         ),
         ConstraintSpec::new(
             ConstraintType::ExactMatch,
             vec![MODE],
             MissingAction::Fail,
-            CertIndex::All,
+            TargetEntry::All,
         ),
         ConstraintSpec::new(
             ConstraintType::GreaterOrEqual,
             vec![CONFIG_DESC, SECURITY_VERSION],
             MissingAction::Ignore,
-            CertIndex::All,
+            TargetEntry::All,
         ),
         ConstraintSpec::new(
             ConstraintType::GreaterOrEqual,
@@ -202,7 +205,7 @@
                 SUBCOMPONENT_SECURITY_VERSION,
             ],
             MissingAction::Fail,
-            CertIndex::FromEnd(PAYLOAD_INDEX_FROM_END),
+            TargetEntry::ByName(MICRODROID_PAYLOAD_COMPONENT_NAME.to_string()),
         ),
         ConstraintSpec::new(
             ConstraintType::ExactMatch,
@@ -213,11 +216,17 @@
                 SUBCOMPONENT_AUTHORITY_HASH,
             ],
             MissingAction::Fail,
-            CertIndex::FromEnd(PAYLOAD_INDEX_FROM_END),
+            TargetEntry::ByName(MICRODROID_PAYLOAD_COMPONENT_NAME.to_string()),
+        ),
+        ConstraintSpec::new(
+            ConstraintType::ExactMatch,
+            vec![CONFIG_DESC, INSTANCE_HASH_KEY],
+            MissingAction::Fail,
+            TargetEntry::ByName(GUEST_OS_COMPONENT_NAME.to_string()),
         ),
     ];
 
-    policy_for_dice_chain(dice, &constraint_spec)?
+    policy_for_dice_chain(dice, constraint_spec)?
         .to_vec()
         .map_err(|e| format!("DicePolicy construction failed {e:?}"))
 }
diff --git a/tests/benchmark/Android.bp b/tests/benchmark/Android.bp
index 413ffe4..5ede699 100644
--- a/tests/benchmark/Android.bp
+++ b/tests/benchmark/Android.bp
@@ -22,8 +22,10 @@
         "MicrodroidTestNativeLib",
         "libiovsock_host_jni",
     ],
-    jni_uses_platform_apis: true,
-    sdk_version: "test_current",
+    libs: [
+        "framework-virtualization.impl",
+    ],
+    platform_apis: true,
     use_embedded_native_libs: true,
     compile_multilib: "64",
     required: ["perf-setup"],
diff --git a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
index 9cc1b7b..d8b17f1 100644
--- a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
+++ b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
@@ -233,6 +233,7 @@
         for (int i = 0; i < trialCount; i++) {
             VirtualMachineConfig.Builder builder =
                     newVmConfigBuilderWithPayloadBinary("MicrodroidIdleNativeLib.so")
+                            .setShouldBoostUclamp(true)
                             .setMemoryBytes(256 * ONE_MEBI)
                             .setDebugLevel(DEBUG_LEVEL_NONE);
             if (fullDebug) {
diff --git a/tests/ferrochrome/Android.bp b/tests/ferrochrome/Android.bp
new file mode 100644
index 0000000..889f41e
--- /dev/null
+++ b/tests/ferrochrome/Android.bp
@@ -0,0 +1,13 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+sh_test_host {
+    name: "ferrochrome-tests",
+    src: "ferrochrome.sh",
+    test_options: {
+        unit_test: false,
+    },
+    per_testcase_directory: true,
+    data: ["assets/vm_config.json"],
+}
diff --git a/tests/ferrochrome/AndroidTest.xml b/tests/ferrochrome/AndroidTest.xml
new file mode 100644
index 0000000..3f902ea
--- /dev/null
+++ b/tests/ferrochrome/AndroidTest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Host driven tests for ferrochrome">
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+    <!-- 'adb root' to enable vmlauncher -->
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+        <option name="force-root" value="true"/>
+    </target_preparer>
+
+    <!-- Check presence of vmlauncher here, because we can't skip tests in shell test -->
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="throw-if-cmd-fail" value="true" />
+        <option name="run-command" value="pm list packages | grep vmlauncher" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.binary.ExecutableHostTest" >
+        <option name="binary" value="ferrochrome-tests" />
+        <option name="runtime-hint" value="10m" />
+        <option name="per-binary-timeout" value="20m" />
+    </test>
+</configuration>
+
diff --git a/tests/ferrochrome/assets/vm_config.json b/tests/ferrochrome/assets/vm_config.json
new file mode 100644
index 0000000..f8a3099
--- /dev/null
+++ b/tests/ferrochrome/assets/vm_config.json
@@ -0,0 +1,18 @@
+{
+    "name": "cros",
+    "kernel": "/data/local/tmp/ferrochrome/vmlinuz",
+    "disks": [
+        {
+            "image": "/data/local/tmp/ferrochrome/chromiumos_test_image.bin",
+            "partitions": [],
+            "writable": true
+        }
+    ],
+    "params": "root=/dev/vda3 rootwait noinitrd ro enforcing=0 cros_debug cros_secure",
+    "protected": false,
+    "cpu_topology": "match_host",
+    "platform_version": "~1.0",
+    "memory_mib" : 8096,
+    "console_input_device": "ttyS0"
+}
+
diff --git a/tests/ferrochrome/ferrochrome.sh b/tests/ferrochrome/ferrochrome.sh
new file mode 100755
index 0000000..4dde401
--- /dev/null
+++ b/tests/ferrochrome/ferrochrome.sh
@@ -0,0 +1,142 @@
+#!/bin/bash
+
+# Copyright 2024 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.
+
+## Booting tests for ferrochrome
+## Keep this file synced with docs/custom_vm.md
+
+set -e
+
+FECR_GS_URL="https://storage.googleapis.com/chromiumos-image-archive/ferrochrome-public"
+FECR_DEFAULT_VERSION="R127-15916.0.0"
+FECR_DEVICE_DIR="/data/local/tmp/ferrochrome"
+FECR_CONFIG_PATH="/data/local/tmp/vm_config.json"  # hardcoded at VmLauncherApp
+FECR_CONSOLE_LOG_PATH="/data/data/\${pkg_name}/files/console.log"
+FECR_BOOT_COMPLETED_LOG="Have fun and send patches!"
+FECR_BOOT_TIMEOUT="300" # 5 minutes (300 seconds)
+AOSP_PKG_NAME="com.android.virtualization.vmlauncher"
+SIGNED_PKG_NAME="com.google.android.virtualization.vmlauncher"
+
+fecr_clean_up() {
+  trap - INT
+
+  if [[ -d ${fecr_dir} && -z ${fecr_keep} ]]; then
+    rm -rf ${fecr_dir}
+  fi
+}
+
+print_usage() {
+  echo "ferochrome.sh: Launches ferrochrome image"
+  echo ""
+  echo "By default, this downloads ferrochrome image with version ${FECR_DEFAULT_VERSION},"
+  echo "launches, and waits for boot completed."
+  echo "When done, removes downloaded image."
+  echo ""
+  echo "Usage: ferrochrome.sh [options]"
+  echo ""
+  echo "Options"
+  echo "  --help or -h: This message"
+  echo "  --dir \${dir}: Use ferrochrome images at the dir instead of downloading"
+  echo "  --skip: Skipping downloading and/or pushing images"
+  echo "  --version \${version}: ferrochrome version to be downloaded"
+  echo "  --keep: Keep downloaded ferrochrome image"
+}
+
+
+fecr_version=""
+fecr_dir=""
+fecr_keep=""
+fecr_skip=""
+fecr_script_path=$(dirname ${0})
+
+# Parse parameters
+while (( "${#}" )); do
+  case "${1}" in
+    --version)
+      shift
+      fecr_version="${1}"
+      ;;
+    --dir)
+      shift
+      fecr_dir="${1}"
+      fecr_keep="true"
+      ;;
+    --keep)
+      fecr_keep="true"
+      ;;
+    --skip)
+      fecr_skip="true"
+      ;;
+    -h|--help)
+      print_usage
+      exit 0
+      ;;
+    *)
+      print_usage
+      exit 1
+      ;;
+  esac
+  shift
+done
+
+trap fecr_clean_up INT
+trap fecr_clean_up EXIT
+
+if [[ -z "${fecr_skip}" ]]; then
+  if [[ -z "${fecr_dir}" ]]; then
+    # Download fecr image archive, and extract necessary files
+    # DISCLAIMER: Image is too large (1.5G+ for compressed, 6.5G+ for uncompressed), so can't submit.
+    fecr_dir=$(mktemp -d)
+
+    echo "Downloading ferrochrome image to ${fecr_dir}"
+    fecr_version=${fecr_version:-${FECR_DEFAULT_VERSION}}
+    curl --output-dir ${fecr_dir} -O ${FECR_GS_URL}/${fecr_version}/image.zip
+  fi
+  if [[ ! -f "${fecr_dir}/chromiumos_test_image.bin" ]]; then
+    unzip ${fecr_dir}/image.zip chromiumos_test_image.bin boot_images/vmlinuz* -d ${fecr_dir} > /dev/null
+  fi
+
+  echo "Pushing ferrochrome image to ${FECR_DEVICE_DIR}"
+  adb shell mkdir -p ${FECR_DEVICE_DIR} > /dev/null || true
+  adb push ${fecr_dir}/chromiumos_test_image.bin ${FECR_DEVICE_DIR}
+  adb push ${fecr_dir}/boot_images/vmlinuz ${FECR_DEVICE_DIR}
+  adb push ${fecr_script_path}/assets/vm_config.json ${FECR_CONFIG_PATH}
+fi
+
+adb root > /dev/null
+adb shell pm list packages | grep ${AOSP_PKG_NAME} > /dev/null
+if [[ "${?}" == "0" ]]; then
+  pkg_name=${AOSP_PKG_NAME}
+else
+  pkg_name=${SIGNED_PKG_NAME}
+fi
+
+adb shell pm enable ${pkg_name}/${AOSP_PKG_NAME}.MainActivity > /dev/null
+adb shell pm grant ${pkg_name} android.permission.USE_CUSTOM_VIRTUAL_MACHINE > /dev/null
+adb shell pm clear ${pkg_name} > /dev/null
+
+echo "Starting ferrochrome"
+adb shell am start-activity ${pkg_name}/${AOSP_PKG_NAME}.MainActivity > /dev/null
+
+log_path="/data/data/${pkg_name}/files/console.log"
+fecr_start_time=${EPOCHSECONDS}
+
+while [[ $((EPOCHSECONDS - fecr_start_time)) -lt ${FECR_BOOT_TIMEOUT} ]]; do
+  adb shell grep -sF \""${FECR_BOOT_COMPLETED_LOG}"\" "${log_path}" && exit 0
+  sleep 10
+done
+
+echo "Ferrochrome failed to boot"
+exit 1
diff --git a/tests/testapk/AndroidManifestV5.xml b/tests/testapk/AndroidManifestV5.xml
index 7d97680..b869586 100644
--- a/tests/testapk/AndroidManifestV5.xml
+++ b/tests/testapk/AndroidManifestV5.xml
@@ -18,6 +18,7 @@
       android:versionCode="5">
     <uses-permission android:name="android.permission.MANAGE_VIRTUAL_MACHINE" />
     <uses-permission android:name="android.permission.USE_CUSTOM_VIRTUAL_MACHINE" />
+    <uses-permission android:name="android.permission.INTERNET" />
     <uses-sdk android:minSdkVersion="33" android:targetSdkVersion="33" />
     <uses-feature android:name="android.software.virtualization_framework" android:required="false" />
     <queries>
diff --git a/tests/testapk/AndroidManifestV6.xml b/tests/testapk/AndroidManifestV6.xml
index 19d5674..c55da85 100644
--- a/tests/testapk/AndroidManifestV6.xml
+++ b/tests/testapk/AndroidManifestV6.xml
@@ -18,6 +18,7 @@
       android:versionCode="6">
     <uses-permission android:name="android.permission.MANAGE_VIRTUAL_MACHINE" />
     <uses-permission android:name="android.permission.USE_CUSTOM_VIRTUAL_MACHINE" />
+    <uses-permission android:name="android.permission.INTERNET" />
     <uses-sdk android:minSdkVersion="33" android:targetSdkVersion="33" />
     <uses-feature android:name="android.software.virtualization_framework" android:required="false" />
     <queries>
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 12a46f7..4141903 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -28,8 +28,6 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 import static com.google.common.truth.TruthJUnit.assume;
-import com.android.virt.vm_attestation.testservice.IAttestationService.AttestationStatus;
-import com.android.virt.vm_attestation.testservice.IAttestationService.SigningResult;
 
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
@@ -70,8 +68,15 @@
 import com.android.microdroid.testservice.IAppCallback;
 import com.android.microdroid.testservice.ITestService;
 import com.android.microdroid.testservice.IVmCallback;
+import com.android.virt.vm_attestation.testservice.IAttestationService.AttestationStatus;
+import com.android.virt.vm_attestation.testservice.IAttestationService.SigningResult;
 import com.android.virt.vm_attestation.util.X509Utils;
 
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.model.Array;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.MajorType;
+
 import com.google.common.base.Strings;
 import com.google.common.truth.BooleanSubject;
 
@@ -113,17 +118,13 @@
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.stream.Stream;
 
-import co.nstant.in.cbor.CborDecoder;
-import co.nstant.in.cbor.model.Array;
-import co.nstant.in.cbor.model.DataItem;
-import co.nstant.in.cbor.model.MajorType;
-
 @RunWith(Parameterized.class)
 public class MicrodroidTests extends MicrodroidDeviceTestBase {
     private static final String TAG = "MicrodroidTests";
     private static final String TEST_APP_PACKAGE_NAME = "com.android.microdroid.test";
     private static final String VM_ATTESTATION_PAYLOAD_PATH = "libvm_attestation_test_payload.so";
     private static final String VM_ATTESTATION_MESSAGE = "Hello RKP from AVF!";
+    private static final int ENCRYPTED_STORAGE_BYTES = 4_000_000;
 
     @Rule public Timeout globalTimeout = Timeout.seconds(300);
 
@@ -571,8 +572,9 @@
         assertThat(minimal.isProtectedVm()).isEqualTo(isProtectedVm());
         assertThat(minimal.isEncryptedStorageEnabled()).isFalse();
         assertThat(minimal.getEncryptedStorageBytes()).isEqualTo(0);
-        assertThat(minimal.isVmOutputCaptured()).isEqualTo(false);
+        assertThat(minimal.isVmOutputCaptured()).isFalse();
         assertThat(minimal.getOs()).isEqualTo("microdroid");
+        assertThat(minimal.isNetworkSupported()).isFalse();
 
         // Maximal has everything that can be set to some non-default value. (And has different
         // values than minimal for the required fields.)
@@ -589,6 +591,9 @@
                         .setEncryptedStorageBytes(1_000_000)
                         .setVmOutputCaptured(true)
                         .setOs("microdroid_gki-android14-6.1");
+        if (!mProtectedVm) {
+            maximalBuilder.setNetworkSupported(true);
+        }
         VirtualMachineConfig maximal = maximalBuilder.build();
 
         assertThat(maximal.getApkPath()).isEqualTo("/apk/path");
@@ -603,8 +608,11 @@
         assertThat(maximal.isProtectedVm()).isEqualTo(isProtectedVm());
         assertThat(maximal.isEncryptedStorageEnabled()).isTrue();
         assertThat(maximal.getEncryptedStorageBytes()).isEqualTo(1_000_000);
-        assertThat(maximal.isVmOutputCaptured()).isEqualTo(true);
+        assertThat(maximal.isVmOutputCaptured()).isTrue();
         assertThat(maximal.getOs()).isEqualTo("microdroid_gki-android14-6.1");
+        if (!mProtectedVm) {
+            assertThat(maximal.isNetworkSupported()).isTrue();
+        }
 
         assertThat(minimal.isCompatibleWith(maximal)).isFalse();
         assertThat(minimal.isCompatibleWith(minimal)).isTrue();
@@ -659,6 +667,18 @@
                         .setVmConsoleInputSupported(true);
         e = assertThrows(IllegalStateException.class, () -> captureInputOnNonDebuggable.build());
         assertThat(e).hasMessageThat().contains("debug level must be FULL to use console input");
+
+        if (mProtectedVm) {
+            VirtualMachineConfig.Builder networkSupportedOnProtectedVm =
+                    newVmConfigBuilderWithPayloadBinary("binary.so")
+                            .setProtectedVm(mProtectedVm)
+                            .setNetworkSupported(true);
+            e =
+                    assertThrows(
+                            IllegalStateException.class,
+                            () -> networkSupportedOnProtectedVm.build());
+            assertThat(e).hasMessageThat().contains("network is not supported on pVM");
+        }
     }
 
     @Test
@@ -1583,7 +1603,7 @@
                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
                         .setDebugLevel(DEBUG_LEVEL_FULL);
         if (encryptedStoreEnabled) {
-            builder.setEncryptedStorageBytes(4_000_000);
+            builder.setEncryptedStorageBytes(ENCRYPTED_STORAGE_BYTES);
         }
         VirtualMachineConfig config = builder.build();
         String vmNameOrig = "test_vm_orig";
@@ -1637,7 +1657,7 @@
         VirtualMachineConfig config =
                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
                         .setMemoryBytes(minMemoryRequired())
-                        .setEncryptedStorageBytes(4_000_000)
+                        .setEncryptedStorageBytes(ENCRYPTED_STORAGE_BYTES)
                         .setDebugLevel(DEBUG_LEVEL_FULL)
                         .build();
         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
@@ -1664,7 +1684,7 @@
         VirtualMachineConfig config =
                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
                         .setMemoryBytes(minMemoryRequired())
-                        .setEncryptedStorageBytes(4_000_000)
+                        .setEncryptedStorageBytes(ENCRYPTED_STORAGE_BYTES)
                         .setDebugLevel(DEBUG_LEVEL_FULL)
                         .build();
 
@@ -1771,7 +1791,7 @@
         VirtualMachineConfig config =
                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
                         .setMemoryBytes(minMemoryRequired())
-                        .setEncryptedStorageBytes(4_000_000)
+                        .setEncryptedStorageBytes(ENCRYPTED_STORAGE_BYTES)
                         .setDebugLevel(DEBUG_LEVEL_FULL)
                         .build();
         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_a", config);
@@ -2263,7 +2283,7 @@
         VirtualMachineConfig vmConfig =
                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
                         .setDebugLevel(DEBUG_LEVEL_FULL)
-                        .setEncryptedStorageBytes(4_000_000)
+                        .setEncryptedStorageBytes(ENCRYPTED_STORAGE_BYTES)
                         .build();
         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_encstore_no_exec", vmConfig);
 
@@ -2300,6 +2320,49 @@
         }
     }
 
+    private VirtualMachineConfig buildVmConfigWithNetworkSupported() throws Exception {
+        return buildVmConfigWithNetworkSupported("MicrodroidTestNativeLib.so");
+    }
+
+    private VirtualMachineConfig buildVmConfigWithNetworkSupported(String binaryPath)
+            throws Exception {
+        assumeSupportedDevice();
+        assumeNonProtectedVM();
+        assumeFeatureEnabled(VirtualMachineManager.FEATURE_NETWORK);
+        VirtualMachineConfig config =
+                newVmConfigBuilderWithPayloadBinary(binaryPath)
+                        .setNetworkSupported(true)
+                        .setDebugLevel(DEBUG_LEVEL_FULL)
+                        .build();
+        grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
+        return config;
+    }
+
+    @Test
+    public void configuringNetworkSupportedRequiresCustomPermission() throws Exception {
+        VirtualMachineConfig config = buildVmConfigWithNetworkSupported();
+        revokePermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
+
+        VirtualMachine vm =
+                forceCreateNewVirtualMachine(
+                        "test_network_supported_req_custom_permission", config);
+        SecurityException e =
+                assertThrows(
+                        SecurityException.class, () -> runVmTestService(TAG, vm, (ts, tr) -> {}));
+        assertThat(e)
+                .hasMessageThat()
+                .contains("android.permission.USE_CUSTOM_VIRTUAL_MACHINE permission");
+    }
+
+    @Test
+    public void bootsWithNetworkSupported() throws Exception {
+        VirtualMachineConfig config = buildVmConfigWithNetworkSupported();
+
+        VirtualMachine vm =
+                forceCreateNewVirtualMachine("test_boot_with_network_supported", config);
+        runVmTestService(TAG, vm, (ts, tr) -> {}).assertNoException();
+    }
+
     private VirtualMachineConfig buildVmConfigWithVendor(File vendorDiskImage) throws Exception {
         return buildVmConfigWithVendor(vendorDiskImage, "MicrodroidTestNativeLib.so");
     }
diff --git a/tests/testapk_no_internet_perm/Android.bp b/tests/testapk_no_internet_perm/Android.bp
new file mode 100644
index 0000000..d23081f
--- /dev/null
+++ b/tests/testapk_no_internet_perm/Android.bp
@@ -0,0 +1,26 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "MicrodroidTestAppNoInternetPerm",
+    static_libs: [
+        "MicrodroidDeviceTestHelper",
+        "MicrodroidTestHelper",
+        "androidx.test.runner",
+        "androidx.test.ext.junit",
+        "com.android.microdroid.testservice-java",
+        "truth",
+        "compatibility-common-util-devicesidelib",
+    ],
+    jni_libs: [
+        "MicrodroidTestNativeLib",
+    ],
+    test_suites: [
+        "general-tests",
+        "cts",
+    ],
+    srcs: ["src/java/**/*.java"],
+    defaults: ["MicrodroidTestAppsDefaults"],
+    min_sdk_version: "34",
+}
diff --git a/tests/testapk_no_internet_perm/AndroidManifest.xml b/tests/testapk_no_internet_perm/AndroidManifest.xml
new file mode 100644
index 0000000..87b302a
--- /dev/null
+++ b/tests/testapk_no_internet_perm/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+      package="com.android.microdroid.test_no_internet_perm">
+    <uses-permission android:name="android.permission.MANAGE_VIRTUAL_MACHINE" />
+    <uses-permission android:name="android.permission.USE_CUSTOM_VIRTUAL_MACHINE" />
+    <uses-sdk android:minSdkVersion="34" android:targetSdkVersion="34" />
+    <uses-feature android:name="android.software.virtualization_framework" android:required="false" />
+    <application />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.microdroid.test_no_internet_perm"
+        android:label="No Internet Permission Microdroid Test" />
+</manifest>
diff --git a/tests/testapk_no_internet_perm/AndroidTest.xml b/tests/testapk_no_internet_perm/AndroidTest.xml
new file mode 100644
index 0000000..61f8b8c
--- /dev/null
+++ b/tests/testapk_no_internet_perm/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 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.
+-->
+<configuration description="Runs Microdroid Tests with no internet permission">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="security" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="test-file-name" value="MicrodroidTestAppNoInternetPerm.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.microdroid.test_no_internet_perm" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="shell-timeout" value="300000" />
+        <option name="test-timeout" value="300000" />
+    </test>
+</configuration>
diff --git a/tests/testapk_no_internet_perm/src/java/com/android/microdroid/test/MicrodroidTestAppNoInternetPerm.java b/tests/testapk_no_internet_perm/src/java/com/android/microdroid/test/MicrodroidTestAppNoInternetPerm.java
new file mode 100644
index 0000000..767f745
--- /dev/null
+++ b/tests/testapk_no_internet_perm/src/java/com/android/microdroid/test/MicrodroidTestAppNoInternetPerm.java
@@ -0,0 +1,73 @@
+/*
+ * 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.
+ */
+
+package com.android.microdroid.test;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.system.virtualmachine.VirtualMachine;
+import android.system.virtualmachine.VirtualMachineConfig;
+import android.system.virtualmachine.VirtualMachineManager;
+
+import com.android.microdroid.test.device.MicrodroidDeviceTestBase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/**
+ * Test that the android.permission.MANAGE_VIRTUAL_MACHINE is enforced and that an app cannot launch
+ * a VM without said permission.
+ */
+@RunWith(Parameterized.class)
+public class MicrodroidTestAppNoInternetPerm extends MicrodroidDeviceTestBase {
+    private static final String TAG = "MicrodroidTestAppNoInternetPerm";
+
+    @Parameterized.Parameters(name = "protectedVm={0}")
+    public static Object[] protectedVmConfigs() {
+        return new Object[] {false, true};
+    }
+
+    @Parameterized.Parameter public boolean mProtectedVm;
+
+    @Before
+    public void setup() {
+        prepareTestSetup(mProtectedVm, null);
+    }
+
+    @Test
+    public void configuringNetworkSupportedRequiresInternetPermission() throws Exception {
+        assumeSupportedDevice();
+        assumeNonProtectedVM();
+        assumeFeatureEnabled(VirtualMachineManager.FEATURE_NETWORK);
+
+        VirtualMachineConfig config =
+                newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
+                        .setNetworkSupported(true)
+                        .build();
+
+        VirtualMachine vm =
+                forceCreateNewVirtualMachine(
+                        "config_network_supported_req_internet_permission", config);
+        SecurityException e =
+                assertThrows(
+                        SecurityException.class, () -> runVmTestService(TAG, vm, (ts, tr) -> {}));
+        assertThat(e).hasMessageThat().contains("android.permission.INTERNET permission");
+    }
+}
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index 0055b3b..ac70509 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -17,7 +17,7 @@
 use crate::{get_calling_pid, get_calling_uid, get_this_pid};
 use crate::atom::{write_vm_booted_stats, write_vm_creation_stats};
 use crate::composite::make_composite_image;
-use crate::crosvm::{CrosvmConfig, DiskFile, DisplayConfig, InputDeviceOption, PayloadState, VmContext, VmInstance, VmState};
+use crate::crosvm::{CrosvmConfig, DiskFile, DisplayConfig, GpuConfig, InputDeviceOption, 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};
@@ -401,68 +401,9 @@
             check_gdb_allowed(config)?;
         }
 
-        // Currently, VirtMgr adds the host copy of reference DT & untrusted properties
-        // (e.g. 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 vendor_hashtree_digest = extract_vendor_hashtree_digest(config)
-            .context("Failed to extract vendor hashtree digest")
-            .or_service_specific_exception(-1)?;
-
-        let trusted_props = if let Some(ref vendor_hashtree_digest) = vendor_hashtree_digest {
-            info!(
-                "Passing vendor hashtree digest to pvmfw. This will be rejected if it doesn't \
-                match the trusted digest in the pvmfw config, causing the VM to fail to start."
-            );
-            vec![(
-                cstr!("vendor_hashtree_descriptor_root_digest"),
-                vendor_hashtree_digest.as_slice(),
-            )]
-        } else {
-            vec![]
-        };
-
-        let instance_id;
-        let mut untrusted_props = Vec::with_capacity(2);
-        if cfg!(llpvm_changes) {
-            instance_id = extract_instance_id(config);
-            untrusted_props.push((cstr!("instance-id"), &instance_id[..]));
-            let want_updatable = extract_want_updatable(config);
-            if want_updatable && is_secretkeeper_supported() {
-                // Let guest know that it can defer rollback protection to Secretkeeper by setting
-                // an empty property in untrusted node in DT. This enables Updatable VMs.
-                untrusted_props.push((cstr!("defer-rollback-protection"), &[]))
-            }
-        }
-
-        let device_tree_overlay =
-            if host_ref_dt.is_some() || !untrusted_props.is_empty() || !trusted_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,
-                    &trusted_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 device_tree_overlay = maybe_create_device_tree_overlay(config, &temporary_directory)?;
 
         let debug_config = DebugConfig::new(config);
-
         let ramdump = if !uses_gki_kernel(config) && debug_config.is_ramdump_needed() {
             Some(prepare_ramdump_file(&temporary_directory)?)
         } else {
@@ -594,6 +535,16 @@
         } else {
             None
         };
+        let gpu_config = if cfg!(paravirtualized_devices) {
+            config
+                .gpuConfig
+                .as_ref()
+                .map(GpuConfig::new)
+                .transpose()
+                .or_binder_exception(ExceptionCode::ILLEGAL_ARGUMENT)?
+        } else {
+            None
+        };
 
         let input_device_options = if cfg!(paravirtualized_devices) {
             config
@@ -658,6 +609,8 @@
             tap,
             virtio_snd_backend,
             console_input_device: config.consoleInputDevice.clone(),
+            boost_uclamp: config.boostUclamp,
+            gpu_config,
         };
         let instance = Arc::new(
             VmInstance::new(
@@ -732,6 +685,67 @@
     Err(anyhow!("No hashtree digest is extracted from microdroid vendor image"))
 }
 
+fn maybe_create_device_tree_overlay(
+    config: &VirtualMachineConfig,
+    temporary_directory: &Path,
+) -> binder::Result<Option<File>> {
+    // Currently, VirtMgr adds the host copy of reference DT & untrusted properties
+    // (e.g. 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 vendor_hashtree_digest = extract_vendor_hashtree_digest(config)
+        .context("Failed to extract vendor hashtree digest")
+        .or_service_specific_exception(-1)?;
+
+    let trusted_props = if let Some(ref vendor_hashtree_digest) = vendor_hashtree_digest {
+        info!(
+            "Passing vendor hashtree digest to pvmfw. This will be rejected if it doesn't \
+                match the trusted digest in the pvmfw config, causing the VM to fail to start."
+        );
+        vec![(cstr!("vendor_hashtree_descriptor_root_digest"), vendor_hashtree_digest.as_slice())]
+    } else {
+        vec![]
+    };
+
+    let instance_id;
+    let mut untrusted_props = Vec::with_capacity(2);
+    if cfg!(llpvm_changes) {
+        instance_id = extract_instance_id(config);
+        untrusted_props.push((cstr!("instance-id"), &instance_id[..]));
+        let want_updatable = extract_want_updatable(config);
+        if want_updatable && is_secretkeeper_supported() {
+            // Let guest know that it can defer rollback protection to Secretkeeper by setting
+            // an empty property in untrusted node in DT. This enables Updatable VMs.
+            untrusted_props.push((cstr!("defer-rollback-protection"), &[]))
+        }
+    }
+
+    let device_tree_overlay = if host_ref_dt.is_some()
+        || !untrusted_props.is_empty()
+        || !trusted_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, &trusted_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
+    };
+    Ok(device_tree_overlay)
+}
+
 fn write_zero_filler(zero_filler_path: &Path) -> Result<()> {
     let file = OpenOptions::new()
         .create_new(true)
@@ -958,6 +972,7 @@
     vm_config.protectedVm = config.protectedVm;
     vm_config.cpuTopology = config.cpuTopology;
     vm_config.hugePages = config.hugePages || vm_payload_config.hugepages;
+    vm_config.boostUclamp = config.boostUclamp;
 
     // Microdroid takes additional init ramdisk & (optionally) storage image
     add_microdroid_system_images(config, instance_file, storage_image, os_name, &mut vm_config)?;
diff --git a/virtualizationmanager/src/crosvm.rs b/virtualizationmanager/src/crosvm.rs
index 6408b84..3722d4d 100644
--- a/virtualizationmanager/src/crosvm.rs
+++ b/virtualizationmanager/src/crosvm.rs
@@ -14,10 +14,11 @@
 
 //! Functions for running instances of `crosvm`.
 
-use crate::aidl::{remove_temporary_files, Cid, VirtualMachineCallbacks};
+use crate::aidl::{remove_temporary_files, Cid, GLOBAL_SERVICE, VirtualMachineCallbacks};
 use crate::atom::{get_num_cpus, write_vm_exited_stats_sync};
 use crate::debug_config::DebugConfig;
 use anyhow::{anyhow, bail, Context, Error, Result};
+use binder::ParcelFileDescriptor;
 use command_fds::CommandFdExt;
 use lazy_static::lazy_static;
 use libc::{sysconf, _SC_CLK_TCK};
@@ -34,7 +35,7 @@
 use std::io::{self, Read};
 use std::mem;
 use std::num::{NonZeroU16, NonZeroU32};
-use std::os::unix::io::{AsRawFd, RawFd};
+use std::os::unix::io::{AsRawFd, OwnedFd, RawFd};
 use std::os::unix::process::ExitStatusExt;
 use std::path::{Path, PathBuf};
 use std::process::{Command, ExitStatus};
@@ -46,6 +47,7 @@
     MemoryTrimLevel::MemoryTrimLevel,
     VirtualMachineAppConfig::DebugLevel::DebugLevel,
     DisplayConfig::DisplayConfig as DisplayConfigParcelable,
+    GpuConfig::GpuConfig as GpuConfigParcelable,
 };
 use android_system_virtualizationservice_internal::aidl::android::system::virtualizationservice_internal::IGlobalVmContext::IGlobalVmContext;
 use android_system_virtualizationservice_internal::aidl::android::system::virtualizationservice_internal::IBoundDevice::IBoundDevice;
@@ -131,6 +133,8 @@
     pub tap: Option<File>,
     pub virtio_snd_backend: Option<String>,
     pub console_input_device: Option<String>,
+    pub boost_uclamp: bool,
+    pub gpu_config: Option<GpuConfig>,
 }
 
 #[derive(Debug)]
@@ -153,6 +157,37 @@
     }
 }
 
+#[derive(Debug)]
+pub struct GpuConfig {
+    pub backend: Option<String>,
+    pub context_types: Option<Vec<String>>,
+    pub pci_address: Option<String>,
+    pub renderer_features: Option<String>,
+    pub renderer_use_egl: Option<bool>,
+    pub renderer_use_gles: Option<bool>,
+    pub renderer_use_glx: Option<bool>,
+    pub renderer_use_surfaceless: Option<bool>,
+    pub renderer_use_vulkan: Option<bool>,
+}
+
+impl GpuConfig {
+    pub fn new(raw_config: &GpuConfigParcelable) -> Result<GpuConfig> {
+        Ok(GpuConfig {
+            backend: raw_config.backend.clone(),
+            context_types: raw_config.contextTypes.clone().map(|context_types| {
+                context_types.iter().filter_map(|context_type| context_type.clone()).collect()
+            }),
+            pci_address: raw_config.pciAddress.clone(),
+            renderer_features: raw_config.rendererFeatures.clone(),
+            renderer_use_egl: Some(raw_config.rendererUseEgl),
+            renderer_use_gles: Some(raw_config.rendererUseGles),
+            renderer_use_glx: Some(raw_config.rendererUseGlx),
+            renderer_use_surfaceless: Some(raw_config.rendererUseSurfaceless),
+            renderer_use_vulkan: Some(raw_config.rendererUseVulkan),
+        })
+    }
+}
+
 fn try_into_non_zero_u32(value: i32) -> Result<NonZeroU32> {
     let u32_value = value.try_into()?;
     NonZeroU32::new(u32_value).ok_or(anyhow!("value should be greater than 0"))
@@ -241,6 +276,8 @@
             let detect_hangup = config.detect_hangup;
             let (failure_pipe_read, failure_pipe_write) = create_pipe()?;
             let vfio_devices = config.vfio_devices.clone();
+            let tap =
+                if let Some(tap_file) = &config.tap { Some(tap_file.try_clone()?) } else { None };
 
             // If this fails and returns an error, `self` will be left in the `Failed` state.
             let child =
@@ -255,7 +292,7 @@
             let child_clone = child.clone();
             let instance_clone = instance.clone();
             let monitor_vm_exit_thread = Some(thread::spawn(move || {
-                instance_clone.monitor_vm_exit(child_clone, failure_pipe_read, vfio_devices);
+                instance_clone.monitor_vm_exit(child_clone, failure_pipe_read, vfio_devices, tap);
             }));
 
             if detect_hangup {
@@ -397,6 +434,7 @@
         child: Arc<SharedChild>,
         mut failure_pipe_read: File,
         vfio_devices: Vec<VfioDevice>,
+        tap: Option<File>,
     ) {
         let result = child.wait();
         match &result {
@@ -456,6 +494,14 @@
             error!("Error removing temporary files from {:?}: {}", self.temporary_directory, e);
         });
 
+        if let Some(tap_file) = tap {
+            GLOBAL_SERVICE
+                .deleteTapInterface(&ParcelFileDescriptor::new(OwnedFd::from(tap_file)))
+                .unwrap_or_else(|e| {
+                    error!("Error deleting TAP interface: {e:?}");
+                });
+        }
+
         drop(vfio_devices); // Cleanup devices.
     }
 
@@ -842,6 +888,8 @@
         command.arg("--no-balloon");
     }
 
+    let mut memory_mib = config.memory_mib;
+
     if config.protected {
         match system_properties::read(SYSPROP_CUSTOM_PVMFW_PATH)? {
             Some(pvmfw_path) if !pvmfw_path.is_empty() => {
@@ -857,6 +905,12 @@
         let swiotlb_size_mib = 2 * virtio_pci_device_count as u32;
         command.arg("--swiotlb").arg(swiotlb_size_mib.to_string());
 
+        // b/346770542 for consistent "usable" memory across protected and non-protected VMs under
+        // pKVM.
+        if hypervisor_props::is_pkvm()? {
+            memory_mib = memory_mib.map(|m| m.saturating_add(swiotlb_size_mib));
+        }
+
         // Workaround to keep crash_dump from trying to read protected guest memory.
         // Context in b/238324526.
         command.arg("--unmap-guest-memory-on-fork");
@@ -878,7 +932,7 @@
         command.arg("--params").arg("console=hvc0");
     }
 
-    if let Some(memory_mib) = config.memory_mib {
+    if let Some(memory_mib) = memory_mib {
         command.arg("--mem").arg(memory_mib.to_string());
     }
 
@@ -988,12 +1042,48 @@
     }
 
     if cfg!(paravirtualized_devices) {
+        if let Some(gpu_config) = &config.gpu_config {
+            let mut gpu_args = Vec::new();
+            if let Some(backend) = &gpu_config.backend {
+                gpu_args.push(format!("backend={}", backend));
+            }
+            if let Some(context_types) = &gpu_config.context_types {
+                gpu_args.push(format!("context-types={}", context_types.join(":")));
+            }
+            if let Some(pci_address) = &gpu_config.pci_address {
+                gpu_args.push(format!("pci-address={}", pci_address));
+            }
+            if let Some(renderer_features) = &gpu_config.renderer_features {
+                gpu_args.push(format!("renderer-features={}", renderer_features));
+            }
+            if gpu_config.renderer_use_egl.unwrap_or(false) {
+                gpu_args.push("egl=true".to_string());
+            }
+            if gpu_config.renderer_use_gles.unwrap_or(false) {
+                gpu_args.push("gles=true".to_string());
+            }
+            if gpu_config.renderer_use_glx.unwrap_or(false) {
+                gpu_args.push("glx=true".to_string());
+            }
+            if gpu_config.renderer_use_surfaceless.unwrap_or(false) {
+                gpu_args.push("surfaceless=true".to_string());
+            }
+            if gpu_config.renderer_use_vulkan.unwrap_or(false) {
+                gpu_args.push("vulkan=true".to_string());
+            }
+            command.arg(format!("--gpu={}", gpu_args.join(",")));
+        }
         if let Some(display_config) = &config.display_config {
-            command.arg("--gpu")
-            // TODO(b/331708504): support backend config as well
-            .arg("backend=virglrenderer,context-types=virgl2,egl=true,surfaceless=true,glx=false,gles=true")
-            .arg(format!("--gpu-display=mode=windowed[{},{}],dpi=[{},{}],refresh-rate={}", display_config.width, display_config.height, display_config.horizontal_dpi, display_config.vertical_dpi, display_config.refresh_rate))
-            .arg(format!("--android-display-service={}", config.name));
+            command
+                .arg(format!(
+                    "--gpu-display=mode=windowed[{},{}],dpi=[{},{}],refresh-rate={}",
+                    display_config.width,
+                    display_config.height,
+                    display_config.horizontal_dpi,
+                    display_config.vertical_dpi,
+                    display_config.refresh_rate
+                ))
+                .arg(format!("--android-display-service={}", config.name));
         }
     }
 
@@ -1042,6 +1132,10 @@
         command.arg("--hugepages");
     }
 
+    if config.boost_uclamp {
+        command.arg("--boost-uclamp");
+    }
+
     append_platform_devices(&mut command, &mut preserved_fds, &config)?;
 
     debug!("Preserving FDs {:?}", preserved_fds);
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/GpuConfig.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/GpuConfig.aidl
new file mode 100644
index 0000000..1cd4dc6
--- /dev/null
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/GpuConfig.aidl
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+package android.system.virtualizationservice;
+
+parcelable GpuConfig {
+    @nullable String backend;
+    @nullable String[] contextTypes;
+    @nullable String pciAddress;
+    @nullable String rendererFeatures;
+    boolean rendererUseEgl = false;
+    boolean rendererUseGles = false;
+    boolean rendererUseGlx = false;
+    boolean rendererUseSurfaceless = false;
+    boolean rendererUseVulkan = false;
+}
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
index f8b5087..234d8d0 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
@@ -25,6 +25,7 @@
     const String FEATURE_DICE_CHANGES = "com.android.kvm.DICE_CHANGES";
     const String FEATURE_LLPVM_CHANGES = "com.android.kvm.LLPVM_CHANGES";
     const String FEATURE_MULTI_TENANT = "com.android.kvm.MULTI_TENANT";
+    const String FEATURE_NETWORK = "com.android.kvm.NETWORK";
     const String FEATURE_REMOTE_ATTESTATION = "com.android.kvm.REMOTE_ATTESTATION";
     const String FEATURE_VENDOR_MODULES = "com.android.kvm.VENDOR_MODULES";
 
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl
index a3f4b0f..ee39d75 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl
@@ -139,4 +139,7 @@
      *  https://docs.kernel.org/admin-guide/mm/transhuge.html
      */
     boolean hugePages;
+
+    /** Enable boost UClamp for less variance during testing/benchmarking */
+    boolean boostUclamp;
 }
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
index c927c9b..69664b4 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
@@ -18,6 +18,7 @@
 import android.system.virtualizationservice.CpuTopology;
 import android.system.virtualizationservice.DiskImage;
 import android.system.virtualizationservice.DisplayConfig;
+import android.system.virtualizationservice.GpuConfig;
 import android.system.virtualizationservice.InputDevice;
 
 /** Raw configuration for running a VM. */
@@ -91,4 +92,9 @@
 
     /** The serial device for VM console input. */
     @nullable @utf8InCpp String consoleInputDevice;
+
+    /** Enable boost UClamp for less variance during testing/benchmarking */
+    boolean boostUclamp;
+
+    @nullable GpuConfig gpuConfig;
 }
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
index 4e6879d..0da7755 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
@@ -128,4 +128,10 @@
      * @return file descriptor of the TAP network interface.
      */
     ParcelFileDescriptor createTapInterface(String ifaceNameSuffix);
+
+    /**
+     * Delete TAP network interface created for a VM.
+     * @param file descriptor of the TAP network interface.
+     */
+    void deleteTapInterface(in ParcelFileDescriptor tapFd);
 }
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVmnic.aidl b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVmnic.aidl
index 66739da..e3cc73a 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVmnic.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVmnic.aidl
@@ -22,4 +22,10 @@
      * @return file descriptor of the TAP network interface.
      */
     ParcelFileDescriptor createTapInterface(String ifaceNameSuffix);
+
+    /**
+     * Delete TAP network interface created for a VM.
+     * @param file descriptor of the TAP network interface.
+     */
+    void deleteTapInterface(in ParcelFileDescriptor tapFd);
 }
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 5592f14..ae8d1da 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -454,7 +454,7 @@
             .context("Failed to allocate instance_id")
             .or_service_specific_exception(-1)?;
         let uid = get_calling_uid();
-        info!("Allocated a VM's instance_id: {:?}, for uid: {:?}", hex::encode(id), uid);
+        info!("Allocated a VM's instance_id: {:?}..., for uid: {:?}", &hex::encode(id)[..8], uid);
         let state = &mut *self.state.lock().unwrap();
         if let Some(sk_state) = &mut state.sk_state {
             let user_id = multiuser_get_user_id(uid);
@@ -510,6 +510,7 @@
     }
 
     fn createTapInterface(&self, iface_name_suffix: &str) -> binder::Result<ParcelFileDescriptor> {
+        check_internet_permission()?;
         check_use_custom_virtual_machine()?;
         if !cfg!(network) {
             return Err(Status::new_exception_str(
@@ -520,6 +521,19 @@
         }
         NETWORK_SERVICE.createTapInterface(iface_name_suffix)
     }
+
+    fn deleteTapInterface(&self, tap_fd: &ParcelFileDescriptor) -> binder::Result<()> {
+        check_internet_permission()?;
+        check_use_custom_virtual_machine()?;
+        if !cfg!(network) {
+            return Err(Status::new_exception_str(
+                ExceptionCode::UNSUPPORTED_OPERATION,
+                Some("deleteTapInterface is not supported with the network feature disabled"),
+            ))
+            .with_log();
+        }
+        NETWORK_SERVICE.deleteTapInterface(tap_fd)
+    }
 }
 
 impl IVirtualizationMaintenance for VirtualizationServiceInternal {
@@ -911,6 +925,12 @@
     check_permission("android.permission.USE_CUSTOM_VIRTUAL_MACHINE")
 }
 
+/// Check whether the caller of the current Binder method is allowed to create socket and
+/// establish connection between the VM and the Internet.
+fn check_internet_permission() -> binder::Result<()> {
+    check_permission("android.permission.INTERNET")
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
diff --git a/virtualizationservice/vmnic/src/aidl.rs b/virtualizationservice/vmnic/src/aidl.rs
index a206c25..69c37b8 100644
--- a/virtualizationservice/vmnic/src/aidl.rs
+++ b/virtualizationservice/vmnic/src/aidl.rs
@@ -22,19 +22,19 @@
 use nix::{ioctl_write_int_bad, ioctl_write_ptr_bad};
 use nix::sys::ioctl::ioctl_num_type;
 use nix::sys::socket::{socket, AddressFamily, SockFlag, SockType};
-use std::ffi::CString;
+use std::ffi::{CStr, CString};
 use std::fs::File;
 use std::os::fd::{AsRawFd, RawFd};
 use std::slice::from_raw_parts;
 
+const TUNGETIFF: ioctl_num_type = 0x800454d2u32 as c_int;
 const TUNSETIFF: ioctl_num_type = 0x400454ca;
 const TUNSETPERSIST: ioctl_num_type = 0x400454cb;
-const SIOCGIFFLAGS: ioctl_num_type = 0x00008913;
 const SIOCSIFFLAGS: ioctl_num_type = 0x00008914;
 
+ioctl_write_ptr_bad!(ioctl_tungetiff, TUNGETIFF, ifreq);
 ioctl_write_ptr_bad!(ioctl_tunsetiff, TUNSETIFF, ifreq);
 ioctl_write_int_bad!(ioctl_tunsetpersist, TUNSETPERSIST);
-ioctl_write_ptr_bad!(ioctl_siocgifflags, SIOCGIFFLAGS, ifreq);
 ioctl_write_ptr_bad!(ioctl_siocsifflags, SIOCSIFFLAGS, ifreq);
 
 fn validate_ifname(ifname: &[c_char]) -> Result<()> {
@@ -44,32 +44,38 @@
     Ok(())
 }
 
-fn create_tap_interface(fd: RawFd, ifname: &[c_char]) -> Result<()> {
+fn create_tap_interface(fd: RawFd, sockfd: c_int, ifname: &[c_char]) -> Result<()> {
     // SAFETY: All-zero is a valid value for the ifreq type.
     let mut ifr: ifreq = unsafe { std::mem::zeroed() };
     ifr.ifr_ifru.ifru_flags = (IFF_TAP | IFF_NO_PI | IFF_VNET_HDR) as c_short;
     ifr.ifr_name[..ifname.len()].copy_from_slice(ifname);
-    // SAFETY: `ioctl` is copied into the kernel. It modifies the state in the kernel, not the
-    // state of this process in any way.
+    // SAFETY: It modifies the state in the kernel, not the state of this process in any way.
     unsafe { ioctl_tunsetiff(fd, &ifr) }.context("Failed to ioctl TUNSETIFF")?;
-    // SAFETY: `ioctl` is copied into the kernel. It modifies the state in the kernel, not the
-    // state of this process in any way.
+    // SAFETY: It modifies the state in the kernel, not the state of this process in any way.
     unsafe { ioctl_tunsetpersist(fd, 1) }.context("Failed to ioctl TUNSETPERSIST")?;
+    // SAFETY: ifr_ifru holds ifru_flags in its union field.
+    unsafe { ifr.ifr_ifru.ifru_flags |= IFF_UP as c_short };
+    // SAFETY: It modifies the state in the kernel, not the state of this process in any way.
+    unsafe { ioctl_siocsifflags(sockfd, &ifr) }.context("Failed to ioctl SIOCSIFFLAGS")?;
     Ok(())
 }
 
-fn bring_up_interface(sockfd: c_int, ifname: &[c_char]) -> Result<()> {
+fn get_tap_ifreq(fd: RawFd) -> Result<ifreq> {
     // SAFETY: All-zero is a valid value for the ifreq type.
-    let mut ifr: ifreq = unsafe { std::mem::zeroed() };
-    ifr.ifr_name[..ifname.len()].copy_from_slice(ifname);
-    // SAFETY: `ioctl` is copied into the kernel. It modifies the state in the kernel, not the
-    // state of this process in any way.
-    unsafe { ioctl_siocgifflags(sockfd, &ifr) }.context("Failed to ioctl SIOCGIFFLAGS")?;
-    // SAFETY: After calling SIOCGIFFLAGS, ifr_ifru holds ifru_flags in its union field.
-    unsafe { ifr.ifr_ifru.ifru_flags |= IFF_UP as c_short };
-    // SAFETY: `ioctl` is copied into the kernel. It modifies the state in the kernel, not the
-    // state of this process in any way.
-    unsafe { ioctl_siocsifflags(sockfd, &ifr) }.context("Failed to ioctl SIOCGIFFLAGS")?;
+    let ifr: ifreq = unsafe { std::mem::zeroed() };
+    // SAFETY: Returned `ifr` of given file descriptor is set from TUNSETIFF ioctl while executing
+    // create_tap_interface(fd, sockfd, ifname). So the variable `ifr` should be safe.
+    unsafe { ioctl_tungetiff(fd, &ifr) }.context("Failed to ioctl TUNGETIFF")?;
+    Ok(ifr)
+}
+
+fn delete_tap_interface(fd: RawFd, sockfd: c_int, ifr: &mut ifreq) -> Result<()> {
+    // SAFETY: After calling TUNGETIFF, ifr_ifru holds ifru_flags in its union field.
+    unsafe { ifr.ifr_ifru.ifru_flags &= !IFF_UP as c_short };
+    // SAFETY: It modifies the state in the kernel, not the state of this process in any way.
+    unsafe { ioctl_siocsifflags(sockfd, ifr) }.context("Failed to ioctl SIOCSIFFLAGS")?;
+    // SAFETY: It modifies the state in the kernel, not the state of this process in any way.
+    unsafe { ioctl_tunsetpersist(fd, 0) }.context("Failed to ioctl TUNSETPERSIST")?;
     Ok(())
 }
 
@@ -102,18 +108,34 @@
         let tunfd = File::open("/dev/tun")
             .context("Failed to open /dev/tun")
             .or_service_specific_exception(-1)?;
-        create_tap_interface(tunfd.as_raw_fd(), ifname_bytes)
-            .context(format!("Failed to create TAP interface: {ifname:#?}"))
-            .or_service_specific_exception(-1)?;
-
         let sock = socket(AddressFamily::Inet, SockType::Datagram, SockFlag::empty(), None)
             .context("Failed to create socket")
             .or_service_specific_exception(-1)?;
-        bring_up_interface(sock.as_raw_fd(), ifname_bytes)
-            .context(format!("Failed to bring up TAP interface: {ifname:#?}"))
+        create_tap_interface(tunfd.as_raw_fd(), sock.as_raw_fd(), ifname_bytes)
+            .context(format!("Failed to create TAP interface: {ifname:#?}"))
             .or_service_specific_exception(-1)?;
 
         info!("Created TAP network interface: {ifname:#?}");
         Ok(ParcelFileDescriptor::new(tunfd))
     }
+
+    fn deleteTapInterface(&self, tapfd: &ParcelFileDescriptor) -> binder::Result<()> {
+        let tap = tapfd.as_raw_fd();
+        let mut tap_ifreq = get_tap_ifreq(tap)
+            .context("Failed to get ifreq of TAP interface")
+            .or_service_specific_exception(-1)?;
+        // SAFETY: tap_ifreq.ifr_name is null-terminated within IFNAMSIZ, validated when creating
+        // TAP interface.
+        let ifname = unsafe { CStr::from_ptr(tap_ifreq.ifr_name.as_ptr()) };
+
+        let sock = socket(AddressFamily::Inet, SockType::Datagram, SockFlag::empty(), None)
+            .context("Failed to create socket")
+            .or_service_specific_exception(-1)?;
+        delete_tap_interface(tap, sock.as_raw_fd(), &mut tap_ifreq)
+            .context(format!("Failed to create TAP interface: {ifname:#?}"))
+            .or_service_specific_exception(-1)?;
+
+        info!("Deleted TAP network interface: {ifname:#?}");
+        Ok(())
+    }
 }
diff --git a/vm/src/main.rs b/vm/src/main.rs
index 390a60d..a250c35 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -65,6 +65,10 @@
     #[cfg(network)]
     #[arg(short, long)]
     network_supported: bool,
+
+    /// Boost uclamp to stablise results for benchmarks.
+    #[arg(short, long)]
+    boost_uclamp: bool,
 }
 
 impl CommonConfig {
diff --git a/vm/src/run.rs b/vm/src/run.rs
index 0c9fbb6..cb15802 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -179,6 +179,7 @@
         customConfig: Some(custom_config),
         osName: os_name,
         hugePages: config.common.hugepages,
+        boostUclamp: config.common.boost_uclamp,
     });
     run(
         service.as_ref(),
@@ -260,6 +261,7 @@
     }
     vm_config.cpuTopology = config.common.cpu_topology;
     vm_config.hugePages = config.common.hugepages;
+    vm_config.boostUclamp = config.common.boost_uclamp;
     run(
         get_service()?.as_ref(),
         &VirtualMachineConfig::RawConfig(vm_config),
diff --git a/vmbase/src/uart.rs b/vmbase/src/uart.rs
index 09d747f..e35555d 100644
--- a/vmbase/src/uart.rs
+++ b/vmbase/src/uart.rs
@@ -16,7 +16,6 @@
 //! provided by crosvm, and won't work with real hardware.
 
 use core::fmt::{self, Write};
-use core::ptr::write_volatile;
 
 /// Minimal driver for an 8250 UART. This only implements enough to work with the emulated 8250
 /// provided by crosvm, and won't work with real hardware.
@@ -41,7 +40,11 @@
         // SAFETY: We know that the base address points to the control registers of a UART device
         // which is appropriately mapped.
         unsafe {
-            write_volatile(self.base_address, byte);
+            core::arch::asm!(
+                "strb {value:w}, [{ptr}]",
+                value = in(reg) byte,
+                ptr = in(reg) self.base_address,
+            );
         }
     }
 }
diff --git a/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java b/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
index 5355313..33e4755 100644
--- a/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
+++ b/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
@@ -32,6 +32,7 @@
 import android.system.virtualmachine.VirtualMachineConfig;
 import android.system.virtualmachine.VirtualMachineCustomImageConfig;
 import android.system.virtualmachine.VirtualMachineCustomImageConfig.DisplayConfig;
+import android.system.virtualmachine.VirtualMachineCustomImageConfig.GpuConfig;
 import android.system.virtualmachine.VirtualMachineException;
 import android.system.virtualmachine.VirtualMachineManager;
 import android.util.DisplayMetrics;
@@ -63,6 +64,7 @@
 import java.nio.ByteOrder;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -125,6 +127,47 @@
             if (json.has("console_input_device")) {
                 configBuilder.setConsoleInputDevice(json.getString("console_input_device"));
             }
+            if (json.has("gpu")) {
+                JSONObject gpuJson = json.getJSONObject("gpu");
+
+                GpuConfig.Builder gpuConfigBuilder = new GpuConfig.Builder();
+
+                if (gpuJson.has("backend")) {
+                    gpuConfigBuilder.setBackend(gpuJson.getString("backend"));
+                }
+                if (gpuJson.has("context_types")) {
+                    ArrayList<String> contextTypes = new ArrayList<String>();
+                    JSONArray contextTypesJson = gpuJson.getJSONArray("context_types");
+                    for (int i = 0; i < contextTypesJson.length(); i++) {
+                        contextTypes.add(contextTypesJson.getString(i));
+                    }
+                    gpuConfigBuilder.setContextTypes(contextTypes.toArray(new String[0]));
+                }
+                if (gpuJson.has("pci_address")) {
+                    gpuConfigBuilder.setPciAddress(gpuJson.getString("pci_address"));
+                }
+                if (gpuJson.has("renderer_features")) {
+                    gpuConfigBuilder.setRendererFeatures(gpuJson.getString("renderer_features"));
+                }
+                if (gpuJson.has("renderer_use_egl")) {
+                    gpuConfigBuilder.setRendererUseEgl(gpuJson.getBoolean("renderer_use_egl"));
+                }
+                if (gpuJson.has("renderer_use_gles")) {
+                    gpuConfigBuilder.setRendererUseGles(gpuJson.getBoolean("renderer_use_gles"));
+                }
+                if (gpuJson.has("renderer_use_glx")) {
+                    gpuConfigBuilder.setRendererUseGlx(gpuJson.getBoolean("renderer_use_glx"));
+                }
+                if (gpuJson.has("renderer_use_surfaceless")) {
+                    gpuConfigBuilder.setRendererUseSurfaceless(
+                            gpuJson.getBoolean("renderer_use_surfaceless"));
+                }
+                if (gpuJson.has("renderer_use_vulkan")) {
+                    gpuConfigBuilder.setRendererUseVulkan(
+                            gpuJson.getBoolean("renderer_use_vulkan"));
+                }
+                customImageConfigBuilder.setGpuConfig(gpuConfigBuilder.build());
+            }
 
             configBuilder.setMemoryBytes(8L * 1024 * 1024 * 1024 /* 8 GB */);
             WindowMetrics windowMetrics = getWindowManager().getCurrentWindowMetrics();