Merge "sign_virt_apex: --signing_args for avbtool's extra args"
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 6338c82..7be81a8 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -7,7 +7,7 @@
       "name": "ComposHostTestCases"
     },
     {
-      "name": "VirtualizationTestCases"
+      "name": "VirtualizationTestCases.64"
     },
     {
       "name": "MicrodroidTestApp"
diff --git a/authfs/tests/Android.bp b/authfs/tests/Android.bp
index ca403e8..1b5cf09 100644
--- a/authfs/tests/Android.bp
+++ b/authfs/tests/Android.bp
@@ -15,9 +15,10 @@
     ],
     test_suites: ["general-tests"],
     data_device_bins: ["open_then_run"],
+    per_testcase_directory: true,
     data: [
         ":authfs_test_files",
-        ":MicrodroidTestApp.signed",
+        ":MicrodroidTestApp",
     ],
 }
 
diff --git a/compos/apk/assets/vm_config.json b/compos/apk/assets/vm_config.json
index 260332e..b93c3f7 100644
--- a/compos/apk/assets/vm_config.json
+++ b/compos/apk/assets/vm_config.json
@@ -5,10 +5,7 @@
   },
   "task": {
     "type": "executable",
-    "command": "/apex/com.android.compos/bin/compsvc",
-    "args": [
-      "--log_to_stderr"
-    ]
+    "command": "/apex/com.android.compos/bin/compsvc"
   },
   "extra_apks": [
     {
diff --git a/compos/apk/assets/vm_config_staged.json b/compos/apk/assets/vm_config_staged.json
index 1affa79..83fa6eb 100644
--- a/compos/apk/assets/vm_config_staged.json
+++ b/compos/apk/assets/vm_config_staged.json
@@ -5,10 +5,7 @@
   },
   "task": {
     "type": "executable",
-    "command": "/apex/com.android.compos/bin/compsvc",
-    "args": [
-      "--log_to_stderr"
-    ]
+    "command": "/apex/com.android.compos/bin/compsvc"
   },
   "prefer_staged": true,
   "extra_apks": [
diff --git a/compos/common/compos_client.rs b/compos/common/compos_client.rs
index 72d2b76..69f095a 100644
--- a/compos/common/compos_client.rs
+++ b/compos/common/compos_client.rs
@@ -200,21 +200,20 @@
 
 fn want_protected_vm() -> Result<bool> {
     let have_protected_vm =
-        system_properties::read_bool("ro.boot.hypervisor.protected_vm.supported", false)
-            .unwrap_or(false);
+        system_properties::read_bool("ro.boot.hypervisor.protected_vm.supported", false)?;
     if have_protected_vm {
         info!("Starting protected VM");
         return Ok(true);
     }
 
-    let build_type = system_properties::read("ro.build.type")?;
+    let build_type = system_properties::read("ro.build.type")?.context("ro.build.type not set")?;
     let is_debug_build = matches!(build_type.as_str(), "userdebug" | "eng");
     if !is_debug_build {
         bail!("Protected VM not supported, unable to start VM");
     }
 
     let have_unprotected_vm =
-        system_properties::read_bool("ro.boot.hypervisor.vm.supported", false).unwrap_or(false);
+        system_properties::read_bool("ro.boot.hypervisor.vm.supported", false)?;
     if have_unprotected_vm {
         warn!("Protected VM not supported, falling back to unprotected on {} build", build_type);
         return Ok(false);
diff --git a/compos/common/timeouts.rs b/compos/common/timeouts.rs
index c86ae34..e6cc430 100644
--- a/compos/common/timeouts.rs
+++ b/compos/common/timeouts.rs
@@ -35,8 +35,11 @@
 /// Whether the current platform requires extra time for operations inside a VM.
 pub fn need_extra_time() -> Result<bool> {
     // Nested virtualization is slow. Check if we are running on vsoc as a proxy for this.
-    let value = system_properties::read("ro.build.product")?;
-    Ok(value == "vsoc_x86_64" || value == "vsoc_x86")
+    if let Some(value) = system_properties::read("ro.build.product")? {
+        Ok(value == "vsoc_x86_64" || value == "vsoc_x86")
+    } else {
+        Ok(false)
+    }
 }
 
 /// Return the timeouts that are appropriate on the current platform.
diff --git a/compos/composd/src/instance_manager.rs b/compos/composd/src/instance_manager.rs
index 2f15cb5..9761a3e 100644
--- a/compos/composd/src/instance_manager.rs
+++ b/compos/composd/src/instance_manager.rs
@@ -83,15 +83,15 @@
 }
 
 fn new_vm_parameters() -> Result<VmParameters> {
-    let cpus = match system_properties::read(DEX2OAT_THREADS_PROP_NAME) {
-        Ok(s) => Some(NonZeroU32::from_str(&s)?),
-        Err(_) => {
+    let cpus = match system_properties::read(DEX2OAT_THREADS_PROP_NAME)? {
+        Some(s) => Some(NonZeroU32::from_str(&s)?),
+        None => {
             // dex2oat uses all CPUs by default. To match the behavior, give the VM all CPUs by
             // default.
             NonZeroU32::new(num_cpus::get() as u32)
         }
     };
-    let cpu_set = system_properties::read(DEX2OAT_CPU_SET_PROP_NAME).ok();
+    let cpu_set = system_properties::read(DEX2OAT_CPU_SET_PROP_NAME)?;
     Ok(VmParameters { cpus, cpu_set, ..Default::default() })
 }
 
diff --git a/compos/composd/src/odrefresh_task.rs b/compos/composd/src/odrefresh_task.rs
index 47ff590..d1d0e28 100644
--- a/compos/composd/src/odrefresh_task.rs
+++ b/compos/composd/src/odrefresh_task.rs
@@ -27,7 +27,7 @@
     CompilationMode::CompilationMode, ICompOsService,
 };
 use compos_common::odrefresh::ExitCode;
-use log::{error, warn};
+use log::{error, info, warn};
 use rustutils::system_properties;
 use std::fs::{remove_dir_all, File, OpenOptions};
 use std::os::unix::fs::OpenOptionsExt;
@@ -96,7 +96,10 @@
             // We don't do the callback if cancel has already happened.
             if let Some(task) = task {
                 let result = match exit_code {
-                    Ok(ExitCode::CompilationSuccess) => task.callback.onSuccess(),
+                    Ok(ExitCode::CompilationSuccess) => {
+                        info!("CompilationSuccess");
+                        task.callback.onSuccess()
+                    }
                     Ok(exit_code) => {
                         error!("Unexpected odrefresh result: {:?}", exit_code);
                         task.callback.onFailure()
@@ -142,9 +145,9 @@
     };
     let fd_server_raii = fd_server_config.into_fd_server()?;
 
-    let zygote_arch = system_properties::read("ro.zygote")?;
+    let zygote_arch = system_properties::read("ro.zygote")?.context("ro.zygote not set")?;
     let system_server_compiler_filter =
-        system_properties::read("dalvik.vm.systemservercompilerfilter").unwrap_or_default();
+        system_properties::read("dalvik.vm.systemservercompilerfilter")?.unwrap_or_default();
     let exit_code = service.odrefresh(
         compilation_mode,
         system_dir.as_raw_fd(),
diff --git a/compos/src/compsvc_main.rs b/compos/src/compsvc_main.rs
index ebb5514..16e3031 100644
--- a/compos/src/compsvc_main.rs
+++ b/compos/src/compsvc_main.rs
@@ -51,20 +51,13 @@
 }
 
 fn try_main() -> Result<()> {
-    let args = clap::App::new("compsvc")
-        .arg(clap::Arg::with_name("log_to_stderr").long("log_to_stderr"))
-        .get_matches();
-    if args.is_present("log_to_stderr") {
-        env_logger::builder().filter_level(log::LevelFilter::Debug).init();
-    } else {
-        android_logger::init_once(
-            android_logger::Config::default().with_tag("compsvc").with_min_level(log::Level::Debug),
-        );
-        // Redirect panic messages to logcat.
-        panic::set_hook(Box::new(|panic_info| {
-            log::error!("{}", panic_info);
-        }));
-    }
+    android_logger::init_once(
+        android_logger::Config::default().with_tag("compsvc").with_min_level(log::Level::Debug),
+    );
+    // Redirect panic messages to logcat.
+    panic::set_hook(Box::new(|panic_info| {
+        error!("{}", panic_info);
+    }));
 
     let service = compsvc::new_binder()?.as_binder();
     let vm_service = get_vm_service()?;
diff --git a/compos/tests/Android.bp b/compos/tests/Android.bp
index d380059..c178ddd 100644
--- a/compos/tests/Android.bp
+++ b/compos/tests/Android.bp
@@ -13,5 +13,7 @@
     static_libs: [
         "VirtualizationTestHelper",
     ],
-    test_suites: ["general-tests"],
+    test_suites: [
+        "general-tests",
+    ],
 }
diff --git a/docs/getting_started/index.md b/docs/getting_started/index.md
index 6d3b208..ac5d38b 100644
--- a/docs/getting_started/index.md
+++ b/docs/getting_started/index.md
@@ -36,7 +36,7 @@
 all can run via `atest`.
 
 ```shell
-atest VirtualizationTestCases
+atest VirtualizationTestCases.64
 atest MicrodroidHostTestCases
 atest MicrodroidTestApp
 ```
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
index 35d600a..3a2d581 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -24,6 +24,7 @@
 import android.content.pm.Signature; // This actually is certificate!
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
+import android.sysprop.HypervisorProperties;
 import android.system.virtualizationservice.VirtualMachineAppConfig;
 
 import java.io.File;
@@ -330,6 +331,16 @@
                         + " is invalid");
             }
 
+            if (mProtectedVm
+                    && !HypervisorProperties.hypervisor_protected_vm_supported().orElse(false)) {
+                throw new UnsupportedOperationException(
+                        "Protected VMs are not supported on this device.");
+            }
+            if (!mProtectedVm && !HypervisorProperties.hypervisor_vm_supported().orElse(false)) {
+                throw new UnsupportedOperationException(
+                        "Unprotected VMs are not supported on this device.");
+            }
+
             return new VirtualMachineConfig(
                     apkPath, certs, mPayloadConfigPath, mDebugLevel, mProtectedVm, mMemoryMib,
                     mNumCpus, mCpuAffinity);
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index 7384cef..a93a801 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -292,18 +292,24 @@
     avb_private_key: ":microdroid_sign_key",
 }
 
-android_filesystem {
-    name: "microdroid_vendor_ramdisk-5.10",
+prebuilt_kernel_modules {
+    name: "microdroid_kernel_modules",
     arch: {
         arm64: {
-            deps: ["virt_device_prebuilts_kernel_modules-5.10-arm64"],
+            srcs: [":virt_device_prebuilts_kernel_modules_microdroid-5.10-arm64"],
         },
         x86_64: {
-            deps: ["virt_device_prebuilts_kernel_modules-5.10-x86_64"],
+            srcs: [":virt_device_prebuilts_kernel_modules_microdroid-5.10-x86_64"],
         },
     },
+    kernel_version: "5.10",
+}
+
+android_filesystem {
+    name: "microdroid_vendor_ramdisk-5.10",
     deps: [
         "microdroid_fstab",
+        "microdroid_kernel_modules",
     ],
     base_dir: "first_stage_ramdisk",
     type: "compressed_cpio",
diff --git a/microdroid_manager/src/instance.rs b/microdroid_manager/src/instance.rs
index f3bbf16..4d65421 100644
--- a/microdroid_manager/src/instance.rs
+++ b/microdroid_manager/src/instance.rs
@@ -33,6 +33,8 @@
 //! The payload of a partition is encrypted/signed by a key that is unique to the loader and to the
 //! VM as well. Failing to decrypt/authenticate a partition by a loader stops the boot process.
 
+use crate::ioutil;
+
 use android_security_dice::aidl::android::security::dice::IDiceNode::IDiceNode;
 use anyhow::{anyhow, bail, Context, Result};
 use binder::wait_for_interface;
@@ -180,7 +182,7 @@
 
         // Persist the encrypted payload data
         self.file.write_all(&data)?;
-        self.file.flush()?;
+        ioutil::blkflsbuf(&mut self.file)?;
 
         Ok(())
     }
diff --git a/microdroid_manager/src/ioutil.rs b/microdroid_manager/src/ioutil.rs
index 8ab2413..8ac3712 100644
--- a/microdroid_manager/src/ioutil.rs
+++ b/microdroid_manager/src/ioutil.rs
@@ -14,11 +14,13 @@
 
 //! IO utilities
 
-use anyhow::{anyhow, Result};
+use anyhow::{anyhow, bail, Result};
 use log::debug;
 use std::fmt::Debug;
 use std::fs::File;
 use std::io;
+use std::os::unix::fs::FileTypeExt;
+use std::os::unix::io::AsRawFd;
 use std::path::Path;
 use std::thread;
 use std::time::{Duration, Instant};
@@ -45,6 +47,20 @@
     }
 }
 
+// From include/uapi/linux/fs.h
+const BLK: u8 = 0x12;
+const BLKFLSBUF: u8 = 97;
+nix::ioctl_none!(_blkflsbuf, BLK, BLKFLSBUF);
+
+pub fn blkflsbuf(f: &mut File) -> Result<()> {
+    if !f.metadata()?.file_type().is_block_device() {
+        bail!("{:?} is not a block device", f.as_raw_fd());
+    }
+    // SAFETY: The file is kept open until the end of this function.
+    unsafe { _blkflsbuf(f.as_raw_fd()) }?;
+    Ok(())
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
diff --git a/tests/Android.bp b/tests/Android.bp
index cf720f1..74d58f5 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -19,11 +19,15 @@
 kernel_version = "5.10"
 
 kernel_stem = "kernel_prebuilts-" + kernel_version
-kernel_modules_stem = "virt_device_prebuilts_kernel_modules-" + kernel_version
 
 cc_test {
-    name: "VirtualizationTestCases",
-    test_suites: ["general-tests"],
+    // ".64" suffix is to work around cts-unit-test which is demanding that all
+    // executables in CTS should have both 32 and 64 ABIs.
+    name: "VirtualizationTestCases.64",
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
     srcs: [
         "common.cc",
         "vsock_test.cc",
@@ -80,15 +84,8 @@
 
 android_filesystem {
     name: "virt_test_initramfs",
-    arch: {
-        arm64: {
-            deps: [kernel_modules_stem + "-arm64"],
-        },
-        x86_64: {
-            deps: [kernel_modules_stem + "-x86_64"],
-        },
-    },
     deps: [
+        "microdroid_kernel_modules",
         "virt_test_guest_init",
         "virt_test_vsock_guest",
     ],
diff --git a/tests/AndroidTest.xml b/tests/AndroidTest.xml
index 5e7faf9..68e9c1b 100644
--- a/tests/AndroidTest.xml
+++ b/tests/AndroidTest.xml
@@ -15,11 +15,16 @@
 -->
 
 <configuration description="Config for Virtualization tests">
+    <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" />
     <!-- Push test binaries to the device. -->
     <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
         <option name="cleanup" value="true" />
         <option name="abort-on-push-failure" value="true" />
-        <option name="push-file" key="VirtualizationTestCases" value="/data/local/tmp/virt-test/VirtualizationTestCases" />
+        <option name="push-file" key="VirtualizationTestCases.64" value="/data/local/tmp/virt-test/VirtualizationTestCases.64" />
         <option name="push-file" key="virt_test_kernel"        value="/data/local/tmp/virt-test/kernel" />
         <option name="push-file" key="virt_test_initramfs.img" value="/data/local/tmp/virt-test/initramfs" />
     </target_preparer>
@@ -30,7 +35,7 @@
 
     <test class="com.android.tradefed.testtype.GTest" >
         <option name="native-test-device-path" value="/data/local/tmp/virt-test" />
-        <option name="module-name" value="VirtualizationTestCases" />
+        <option name="module-name" value="VirtualizationTestCases.64" />
         <!-- test-timeout unit is ms, value = 2 minutes -->
         <option name="native-test-timeout" value="120000" />
     </test>
diff --git a/tests/hostside/Android.bp b/tests/hostside/Android.bp
index d1318b5..bc8a4a5 100644
--- a/tests/hostside/Android.bp
+++ b/tests/hostside/Android.bp
@@ -5,12 +5,16 @@
 java_test_host {
     name: "MicrodroidHostTestCases",
     srcs: ["java/**/*.java"],
-    test_suites: ["general-tests"],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
     libs: [
         "tradefed",
     ],
     static_libs: [
         "VirtualizationTestHelper",
     ],
-    data: [":MicrodroidTestApp.signed"],
+    per_testcase_directory: true,
+    data: [":MicrodroidTestApp"],
 }
diff --git a/tests/hostside/AndroidTest.xml b/tests/hostside/AndroidTest.xml
index e8aced6..79428ce 100644
--- a/tests/hostside/AndroidTest.xml
+++ b/tests/hostside/AndroidTest.xml
@@ -14,6 +14,11 @@
      limitations under the License.
 -->
 <configuration description="Tests for microdroid">
+    <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" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="MicrodroidHostTestCases.jar" />
     </test>
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index 7dca024..0699e3d 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -4,7 +4,10 @@
 
 android_test {
     name: "MicrodroidTestApp",
-    test_suites: ["general-tests"],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
     srcs: ["src/java/**/*.java"],
     static_libs: [
         "androidx.test.runner",
@@ -16,6 +19,8 @@
     jni_libs: ["MicrodroidTestNativeLib"],
     platform_apis: true,
     use_embedded_native_libs: true,
+    // We only support 64-bit ABI, but CTS demands all APKs to be multi-ABI.
+    compile_multilib: "both",
 }
 
 // TODO(jiyong): make this a binary, not a shared library
@@ -42,20 +47,3 @@
     name: "MicrodroidTestNativeLibSub",
     srcs: ["src/native/testlib.cpp"],
 }
-
-genrule {
-    name: "MicrodroidTestApp.signed",
-    out: [
-        "MicrodroidTestApp.apk",
-        "MicrodroidTestApp.apk.idsig",
-    ],
-    srcs: [":MicrodroidTestApp"],
-    tools: ["apksigner"],
-    tool_files: ["test.keystore"],
-    cmd: "$(location apksigner) sign " +
-        "--ks $(location test.keystore) " +
-        "--ks-pass=pass:testkey --key-pass=pass:testkey " +
-        "--in $(in) " +
-        "--out $(genDir)/MicrodroidTestApp.apk",
-    // $(genDir)/MicrodroidTestApp.apk.idsig is generated implicitly
-}
diff --git a/tests/testapk/AndroidTest.xml b/tests/testapk/AndroidTest.xml
index c7097db..e8bb1aa 100644
--- a/tests/testapk/AndroidTest.xml
+++ b/tests/testapk/AndroidTest.xml
@@ -14,7 +14,12 @@
      limitations under the License.
 -->
 <configuration description="Runs sample instrumentation test.">
-    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+    <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="MicrodroidTestApp.apk" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
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 803bdc6..061e8cf 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -30,6 +30,7 @@
 import android.os.Build;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
+import android.os.SystemProperties;
 import android.system.virtualmachine.VirtualMachine;
 import android.system.virtualmachine.VirtualMachineCallback;
 import android.system.virtualmachine.VirtualMachineConfig;
@@ -62,6 +63,8 @@
 public class MicrodroidTests {
     @Rule public Timeout globalTimeout = Timeout.seconds(300);
 
+    private static final String KERNEL_VERSION = SystemProperties.get("ro.kernel.version");
+
     private static class Inner {
         public Context mContext;
         public VirtualMachineManager mVmm;
@@ -140,6 +143,11 @@
 
     @Test
     public void connectToVmService() throws VirtualMachineException, InterruptedException {
+        assume()
+            .withMessage("SKip on 5.4 kernel. b/218303240")
+            .that(KERNEL_VERSION)
+            .isNotEqualTo("5.4");
+
         VirtualMachineConfig.Builder builder =
                 new VirtualMachineConfig.Builder(mInner.mContext,
                         "assets/vm_config_extra_apk.json");
@@ -218,6 +226,11 @@
             .that(android.os.Build.DEVICE)
             .isNotEqualTo("vsoc_x86_64");
 
+        assume()
+            .withMessage("SKip on 5.4 kernel. b/218303240")
+            .that(KERNEL_VERSION)
+            .isNotEqualTo("5.4");
+
         VirtualMachineConfig.Builder builder =
                 new VirtualMachineConfig.Builder(mInner.mContext, "assets/vm_config.json");
         VirtualMachineConfig normalConfig = builder.debugLevel(DebugLevel.NONE).build();
@@ -226,11 +239,6 @@
                 new VmEventListener() {
                     @Override
                     public void onPayloadReady(VirtualMachine vm) {
-                        // TODO(b/208639280): remove this sleep. For now, we need to wait for a few
-                        // seconds so that crosvm can actually persist instance.img.
-                        try {
-                            Thread.sleep(30 * 1000);
-                        } catch (InterruptedException e) { }
                         forceStop(vm);
                     }
                 };
@@ -291,11 +299,6 @@
                         } catch (Exception e) {
                             fail("Exception while connecting to service: " + e.toString());
                         }
-                        // TODO(b/208639280): remove this sleep. For now, we need to wait for a few
-                        // seconds so that crosvm can actually persist instance.img.
-                        try {
-                            Thread.sleep(30 * 1000);
-                        } catch (InterruptedException e) { }
                         forceStop(vm);
                     }
                 };
@@ -311,6 +314,11 @@
             .that(android.os.Build.DEVICE)
             .isNotEqualTo("vsoc_x86_64");
 
+        assume()
+            .withMessage("SKip on 5.4 kernel. b/218303240")
+            .that(KERNEL_VERSION)
+            .isNotEqualTo("5.4");
+
         byte[] vm_a_secret = launchVmAndGetSecret("test_vm_a");
         byte[] vm_b_secret = launchVmAndGetSecret("test_vm_b");
         assertThat(vm_a_secret).isNotNull();
@@ -326,6 +334,11 @@
             .that(android.os.Build.DEVICE)
             .isNotEqualTo("vsoc_x86_64");
 
+        assume()
+            .withMessage("SKip on 5.4 kernel. b/218303240")
+            .that(KERNEL_VERSION)
+            .isNotEqualTo("5.4");
+
         byte[] vm_secret_first_boot = launchVmAndGetSecret("test_vm");
         byte[] vm_secret_second_boot = launchVmAndGetSecret("test_vm");
         assertThat(vm_secret_first_boot).isNotNull();
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index ebb01b3..7e0c634 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -868,7 +868,7 @@
 /// a system property so that restart of virtualizationservice doesn't reuse CID while the host
 /// Android is up.
 fn next_cid() -> Result<Cid> {
-    let next = if let Ok(val) = system_properties::read(SYSPROP_LAST_CID) {
+    let next = if let Some(val) = system_properties::read(SYSPROP_LAST_CID)? {
         if let Ok(num) = val.parse::<u32>() {
             num.checked_add(1).ok_or_else(|| anyhow!("run out of CID"))?
         } else {
diff --git a/virtualizationservice/src/crosvm.rs b/virtualizationservice/src/crosvm.rs
index 2c50fed..df7a7d2 100644
--- a/virtualizationservice/src/crosvm.rs
+++ b/virtualizationservice/src/crosvm.rs
@@ -272,6 +272,13 @@
 
     if config.protected {
         command.arg("--protected-vm");
+
+        // 3 virtio-console devices + vsock = 4.
+        let virtio_pci_device_count = 4 + config.disks.len();
+        // crosvm virtio queue has 256 entries, so 2 MiB per device (2 pages per entry) should be
+        // enough.
+        let swiotlb_size_mib = 2 * virtio_pci_device_count;
+        command.arg("--swiotlb").arg(swiotlb_size_mib.to_string());
     }
 
     if let Some(memory_mib) = config.memory_mib {
diff --git a/vm/Android.bp b/vm/Android.bp
index 2d22562..d1d53d0 100644
--- a/vm/Android.bp
+++ b/vm/Android.bp
@@ -15,6 +15,7 @@
         "liblibc",
         "liblog_rust",
         "libmicrodroid_payload_config",
+        "librustutils",
         "libserde_json",
         "libserde",
         "libstructopt",
diff --git a/vm/src/main.rs b/vm/src/main.rs
index 25f9bfb..2cbae3e 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -26,7 +26,8 @@
 use anyhow::{Context, Error};
 use create_partition::command_create_partition;
 use run::{command_run, command_run_app};
-use std::path::PathBuf;
+use rustutils::system_properties;
+use std::path::{Path, PathBuf};
 use structopt::clap::AppSettings;
 use structopt::StructOpt;
 
@@ -126,6 +127,8 @@
     },
     /// List running virtual machines
     List,
+    /// Print information about virtual machine support
+    Info,
     /// Create a new empty partition to be used as a writable partition for a VM
     CreatePartition {
         /// Path at which to create the image file
@@ -212,6 +215,7 @@
         }
         Opt::Stop { cid } => command_stop(service, cid),
         Opt::List => command_list(service),
+        Opt::Info => command_info(),
         Opt::CreatePartition { path, size, partition_type } => {
             command_create_partition(service, &path, size, partition_type)
         }
@@ -233,3 +237,31 @@
     println!("Running VMs: {:#?}", vms);
     Ok(())
 }
+
+/// Print information about supported VM types.
+fn command_info() -> Result<(), Error> {
+    let unprotected_vm_supported =
+        system_properties::read_bool("ro.boot.hypervisor.vm.supported", false)?;
+    let protected_vm_supported =
+        system_properties::read_bool("ro.boot.hypervisor.protected_vm.supported", false)?;
+    match (unprotected_vm_supported, protected_vm_supported) {
+        (false, false) => println!("VMs are not supported."),
+        (false, true) => println!("Only protected VMs are supported."),
+        (true, false) => println!("Only unprotected VMs are supported."),
+        (true, true) => println!("Both protected and unprotected VMs are supported."),
+    }
+
+    if let Some(version) = system_properties::read("ro.boot.hypervisor.version")? {
+        println!("Hypervisor version: {}", version);
+    } else {
+        println!("Hypervisor version not set.");
+    }
+
+    if Path::new("/dev/kvm").exists() {
+        println!("/dev/kvm exists.");
+    } else {
+        println!("/dev/kvm does not exist.");
+    }
+
+    Ok(())
+}