Merge "Separate AVFHostTestCases from avf-postsubmit"
diff --git a/compos/common/lib.rs b/compos/common/lib.rs
index 8d49ff0..1f937c9 100644
--- a/compos/common/lib.rs
+++ b/compos/common/lib.rs
@@ -53,9 +53,6 @@
 /// /system_ext available in CompOS.
 pub const IDSIG_MANIFEST_EXT_APK_FILE: &str = "idsig_manifest_ext_apk";
 
-/// Number of CPUs to run dex2oat (actually the entire compos VM) with
-pub const DEX2OAT_THREADS_PROP_NAME: &str = "dalvik.vm.boot-dex2oat-threads";
-
 /// The Android path of fs-verity build manifest APK for /system.
 pub const BUILD_MANIFEST_APK_PATH: &str = "/system/etc/security/fsverity/BuildManifest.apk";
 
diff --git a/compos/composd/src/instance_manager.rs b/compos/composd/src/instance_manager.rs
index c4791e9..0a6c3d6 100644
--- a/compos/composd/src/instance_manager.rs
+++ b/compos/composd/src/instance_manager.rs
@@ -22,10 +22,8 @@
 use anyhow::{bail, Result};
 use binder::Strong;
 use compos_common::compos_client::VmParameters;
-use compos_common::{CURRENT_INSTANCE_DIR, DEX2OAT_THREADS_PROP_NAME, TEST_INSTANCE_DIR};
-use rustutils::system_properties;
+use compos_common::{CURRENT_INSTANCE_DIR, TEST_INSTANCE_DIR};
 use std::num::NonZeroU32;
-use std::str::FromStr;
 use std::sync::{Arc, Mutex, Weak};
 use virtualizationservice::IVirtualizationService::IVirtualizationService;
 
@@ -79,14 +77,10 @@
 }
 
 fn new_vm_parameters() -> Result<VmParameters> {
-    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)
-        }
-    };
+    // By default, dex2oat starts as many threads as there are CPUs. This can be overridden with
+    // a system property. Start the VM with all CPUs and assume the guest will start a suitable
+    // number of dex2oat threads.
+    let cpus = NonZeroU32::new(num_cpus::get() as u32);
     let task_profiles = vec!["SCHED_SP_COMPUTE".to_string()];
     Ok(VmParameters { cpus, task_profiles, memory_mib: Some(VM_MEMORY_MIB), ..Default::default() })
 }
diff --git a/compos/verify/verify.rs b/compos/verify/verify.rs
index 71d8bcc..528719f 100644
--- a/compos/verify/verify.rs
+++ b/compos/verify/verify.rs
@@ -33,6 +33,7 @@
 use log::error;
 use std::fs::File;
 use std::io::Read;
+use std::num::NonZeroU32;
 use std::panic;
 use std::path::Path;
 
@@ -114,7 +115,11 @@
         &idsig,
         &idsig_manifest_apk,
         &idsig_manifest_ext_apk,
-        &VmParameters { debug_mode: args.debug, ..Default::default() },
+        &VmParameters {
+            cpus: Some(NonZeroU32::new(1).unwrap()), // This VM runs very little work at boot
+            debug_mode: args.debug,
+            ..Default::default()
+        },
     )?;
 
     let service = vm_instance.connect_service()?;
diff --git a/libs/dice/src/bcc.rs b/libs/dice/src/bcc.rs
index 18b5083..a7ef882 100644
--- a/libs/dice/src/bcc.rs
+++ b/libs/dice/src/bcc.rs
@@ -16,21 +16,15 @@
 
 //! Wrapper around dice/android/bcc.h.
 
-use core::ffi::CStr;
 use core::mem;
 use core::ptr;
 
-use open_dice_bcc_bindgen::BccConfigValues;
-use open_dice_bcc_bindgen::BccFormatConfigDescriptor;
 use open_dice_bcc_bindgen::BccHandoverMainFlow;
 use open_dice_bcc_bindgen::BccHandoverParse;
-use open_dice_bcc_bindgen::BCC_INPUT_COMPONENT_NAME;
-use open_dice_bcc_bindgen::BCC_INPUT_COMPONENT_VERSION;
-use open_dice_bcc_bindgen::BCC_INPUT_RESETTABLE;
 
-use crate::check_call;
+use crate::check_result;
 use crate::Cdi;
-use crate::Error;
+use crate::DiceError;
 use crate::InputValues;
 use crate::Result;
 
@@ -56,7 +50,7 @@
 
         // SAFETY - The buffer is only read and never stored and the returned pointers should all
         // point within the address range of the buffer or be NULL.
-        check_call(unsafe {
+        check_result(unsafe {
             BccHandoverParse(
                 buffer.as_ptr(),
                 buffer.len(),
@@ -68,20 +62,20 @@
         })?;
 
         let cdi_attest = {
-            let i = index_from_ptr(buffer, cdi_attest).ok_or(Error::PlatformError)?;
-            let s = buffer.get(i..(i + mem::size_of::<Cdi>())).ok_or(Error::PlatformError)?;
-            s.try_into().map_err(|_| Error::PlatformError)?
+            let i = index_from_ptr(buffer, cdi_attest).ok_or(DiceError::PlatformError)?;
+            let s = buffer.get(i..(i + mem::size_of::<Cdi>())).ok_or(DiceError::PlatformError)?;
+            s.try_into().map_err(|_| DiceError::PlatformError)?
         };
         let cdi_seal = {
-            let i = index_from_ptr(buffer, cdi_seal).ok_or(Error::PlatformError)?;
-            let s = buffer.get(i..(i + mem::size_of::<Cdi>())).ok_or(Error::PlatformError)?;
-            s.try_into().map_err(|_| Error::PlatformError)?
+            let i = index_from_ptr(buffer, cdi_seal).ok_or(DiceError::PlatformError)?;
+            let s = buffer.get(i..(i + mem::size_of::<Cdi>())).ok_or(DiceError::PlatformError)?;
+            s.try_into().map_err(|_| DiceError::PlatformError)?
         };
         let bcc = if bcc.is_null() {
             None
         } else {
-            let i = index_from_ptr(buffer, bcc).ok_or(Error::PlatformError)?;
-            Some(buffer.get(i..(i + bcc_size)).ok_or(Error::PlatformError)?)
+            let i = index_from_ptr(buffer, bcc).ok_or(DiceError::PlatformError)?;
+            Some(buffer.get(i..(i + bcc_size)).ok_or(DiceError::PlatformError)?)
         };
 
         Ok(Self { buffer, cdi_attest, cdi_seal, bcc })
@@ -93,7 +87,7 @@
         let mut size: usize = 0;
         // SAFETY - The function only reads `self.buffer`, writes to `buffer` within its bounds,
         // reads `input_values` as a constant input and doesn't store any pointer.
-        check_call(unsafe {
+        check_result(unsafe {
             BccHandoverMainFlow(
                 context,
                 self.buffer.as_ptr(),
@@ -109,57 +103,6 @@
     }
 }
 
-/// Formats a configuration descriptor following the BCC's specification.
-///
-/// ```
-/// BccConfigDescriptor = {
-///   ? -70002 : tstr,     ; Component name
-///   ? -70003 : int,      ; Component version
-///   ? -70004 : null,     ; Resettable
-/// }
-/// ```
-pub fn format_config_descriptor(
-    buffer: &mut [u8],
-    name: Option<&CStr>,
-    version: Option<u64>,
-    resettable: bool,
-) -> Result<usize> {
-    let mut inputs = 0;
-
-    if name.is_some() {
-        inputs |= BCC_INPUT_COMPONENT_NAME;
-    }
-
-    if version.is_some() {
-        inputs |= BCC_INPUT_COMPONENT_VERSION;
-    }
-
-    if resettable {
-        inputs |= BCC_INPUT_RESETTABLE;
-    }
-
-    let values = BccConfigValues {
-        inputs,
-        component_name: name.map_or(ptr::null(), |p| p.as_ptr()),
-        component_version: version.unwrap_or(0),
-    };
-
-    let mut buffer_size = 0;
-
-    // SAFETY - The function writes to the buffer, within the given bounds, and only reads the
-    // input values. It writes its result to buffer_size.
-    check_call(unsafe {
-        BccFormatConfigDescriptor(
-            &values as *const _,
-            buffer.len(),
-            buffer.as_mut_ptr(),
-            &mut buffer_size as *mut _,
-        )
-    })?;
-
-    Ok(buffer_size)
-}
-
 fn index_from_ptr(slice: &[u8], pointer: *const u8) -> Option<usize> {
     if slice.as_ptr_range().contains(&pointer) {
         (pointer as usize).checked_sub(slice.as_ptr() as usize)
diff --git a/libs/dice/src/lib.rs b/libs/dice/src/lib.rs
index f0ac2ae..58575eb 100644
--- a/libs/dice/src/lib.rs
+++ b/libs/dice/src/lib.rs
@@ -18,62 +18,15 @@
 
 #![no_std]
 
-use core::fmt;
-use core::result;
-
-pub use diced_open_dice::{Config, Hash, InputValues, HASH_SIZE};
-pub use open_dice_cbor_bindgen::DiceMode;
+pub use diced_open_dice::{
+    bcc_format_config_descriptor, check_result, Cdi, Config, DiceError, DiceMode, Hash,
+    InputValues, Result, CDI_SIZE, HASH_SIZE, HIDDEN_SIZE,
+};
 
 use open_dice_cbor_bindgen::DiceHash;
-use open_dice_cbor_bindgen::DiceResult;
-use open_dice_cbor_bindgen::DiceResult_kDiceResultBufferTooSmall as DICE_RESULT_BUFFER_TOO_SMALL;
-use open_dice_cbor_bindgen::DiceResult_kDiceResultInvalidInput as DICE_RESULT_INVALID_INPUT;
-use open_dice_cbor_bindgen::DiceResult_kDiceResultOk as DICE_RESULT_OK;
-use open_dice_cbor_bindgen::DiceResult_kDiceResultPlatformError as DICE_RESULT_PLATFORM_ERROR;
 
 pub mod bcc;
 
-const CDI_SIZE: usize = open_dice_cbor_bindgen::DICE_CDI_SIZE as usize;
-
-/// Array type of CDIs.
-pub type Cdi = [u8; CDI_SIZE];
-
-/// Error type used by DICE.
-pub enum Error {
-    /// Provided input was invalid.
-    InvalidInput,
-    /// Provided buffer was too small.
-    BufferTooSmall,
-    /// Unexpected platform error.
-    PlatformError,
-    /// Unexpected return value.
-    Unknown(DiceResult),
-}
-
-impl fmt::Debug for Error {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            Error::InvalidInput => write!(f, "invalid input"),
-            Error::BufferTooSmall => write!(f, "buffer too small"),
-            Error::PlatformError => write!(f, "platform error"),
-            Error::Unknown(n) => write!(f, "unknown error: {}", n),
-        }
-    }
-}
-
-/// Result of DICE functions.
-pub type Result<T> = result::Result<T, Error>;
-
-fn check_call(ret: DiceResult) -> Result<()> {
-    match ret {
-        DICE_RESULT_OK => Ok(()),
-        DICE_RESULT_INVALID_INPUT => Err(Error::InvalidInput),
-        DICE_RESULT_BUFFER_TOO_SMALL => Err(Error::BufferTooSmall),
-        DICE_RESULT_PLATFORM_ERROR => Err(Error::PlatformError),
-        n => Err(Error::Unknown(n)),
-    }
-}
-
 fn ctx() -> *mut core::ffi::c_void {
     core::ptr::null_mut()
 }
@@ -82,6 +35,6 @@
 pub fn hash(bytes: &[u8]) -> Result<Hash> {
     let mut output: Hash = [0; HASH_SIZE];
     // SAFETY - DiceHash takes a sized input buffer and writes to a constant-sized output buffer.
-    check_call(unsafe { DiceHash(ctx(), bytes.as_ptr(), bytes.len(), output.as_mut_ptr()) })?;
+    check_result(unsafe { DiceHash(ctx(), bytes.as_ptr(), bytes.len(), output.as_mut_ptr()) })?;
     Ok(output)
 }
diff --git a/microdroid_manager/src/dice.rs b/microdroid_manager/src/dice.rs
index 739c944..a7288b6 100644
--- a/microdroid_manager/src/dice.rs
+++ b/microdroid_manager/src/dice.rs
@@ -17,7 +17,7 @@
 use anyhow::{bail, Context, Error, Result};
 use byteorder::{NativeEndian, ReadBytesExt};
 use diced_open_dice_cbor::{
-    Config, ContextImpl, DiceMode, Hash, Hidden, InputValuesOwned, OpenDiceCborContext, CDI_SIZE,
+    Config, ContextImpl, DiceMode, Hash, Hidden, InputValues, OpenDiceCborContext, CDI_SIZE,
 };
 use keystore2_crypto::ZVec;
 use libc::{c_void, mmap, munmap, MAP_FAILED, MAP_PRIVATE, PROT_READ};
@@ -142,11 +142,10 @@
         debug: bool,
         hidden: Hidden,
     ) -> Result<DiceContext> {
-        let input_values = InputValuesOwned::new(
+        let input_values = InputValues::new(
             code_hash,
             Config::Descriptor(config_desc),
             authority_hash,
-            None,
             if debug { DiceMode::kDiceModeDebug } else { DiceMode::kDiceModeNormal },
             hidden,
         );
diff --git a/pvmfw/src/dice.rs b/pvmfw/src/dice.rs
index 4e1e60a..c5241c4 100644
--- a/pvmfw/src/dice.rs
+++ b/pvmfw/src/dice.rs
@@ -16,12 +16,13 @@
 
 use core::ffi::CStr;
 use core::mem::size_of;
-use dice::bcc::format_config_descriptor;
 use dice::bcc::Handover;
+use dice::bcc_format_config_descriptor;
 use dice::hash;
 use dice::Config;
 use dice::DiceMode;
 use dice::InputValues;
+use dice::HIDDEN_SIZE;
 use pvmfw_avb::{DebugLevel, Digest, VerifiedBootData};
 
 fn to_dice_mode(debug_level: DebugLevel) -> DiceMode {
@@ -52,22 +53,20 @@
     let mode = to_dice_mode(verified_boot_data.debug_level);
     let component_name = CStr::from_bytes_with_nul(b"vm_entry\0").unwrap();
     let mut config_descriptor_buffer = [0; 128];
-    let config_descriptor_size = format_config_descriptor(
-        &mut config_descriptor_buffer,
+    let config_descriptor_size = bcc_format_config_descriptor(
         Some(component_name),
         None,  // component_version
         false, // resettable
+        &mut config_descriptor_buffer,
     )?;
     let config = &config_descriptor_buffer[..config_descriptor_size];
 
     let input_values = InputValues::new(
-        &code_hash,
-        None, // code_descriptor
+        code_hash,
         Config::Descriptor(config),
-        &auth_hash,
-        None, // authority_descriptor
+        auth_hash,
         mode,
-        None, // TODO(b/249723852): Get salt from instance.img (virtio-blk) and/or TRNG.
+        [0u8; HIDDEN_SIZE], // TODO(b/249723852): Get salt from instance.img (virtio-blk) and/or TRNG.
     );
 
     bcc.main_flow(&input_values, next_bcc)
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 f17000a..db87126 100644
--- a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
+++ b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
@@ -50,8 +50,11 @@
 import java.io.IOException;
 import java.nio.file.Files;
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.OptionalLong;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Function;
 
@@ -136,102 +139,87 @@
         mInstrumentation.sendStatus(0, bundle);
     }
 
-    @Test
-    public void testMicrodroidBootTime()
+    private static class BootTimeStats {
+        private final Map<BootTimeMetric, List<Double>> mData = new HashMap<>();
+
+        public BootTimeStats(int trialCount) {
+            for (BootTimeMetric metric : BootTimeMetric.values()) {
+                mData.put(metric, new ArrayList<>(trialCount));
+            }
+        }
+
+        public void collect(BootResult result) {
+            for (BootTimeMetric metric : BootTimeMetric.values()) {
+                OptionalLong value = result.getBootTimeMetricNanoTime(metric);
+                if (value.isPresent()) {
+                    mData.get(metric).add(value.getAsLong() / NANO_TO_MILLI);
+                }
+            }
+        }
+
+        public List<Double> get(BootTimeMetric metric) {
+            return Collections.unmodifiableList(mData.get(metric));
+        }
+    }
+
+    private BootTimeStats runBootTimeTest(
+            String name,
+            Function<VirtualMachineConfig.Builder, VirtualMachineConfig.Builder> fnConfig)
             throws VirtualMachineException, InterruptedException, IOException {
         assume().withMessage("Skip on CF; too slow").that(isCuttlefish()).isFalse();
 
         final int trialCount = 10;
 
-        List<Double> bootTimeMetrics = new ArrayList<>();
+        BootTimeStats stats = new BootTimeStats(trialCount);
         for (int i = 0; i < trialCount; i++) {
-            VirtualMachineConfig normalConfig =
+            VirtualMachineConfig.Builder builder =
                     newVmConfigBuilder()
                             .setPayloadBinaryName("MicrodroidIdleNativeLib.so")
-                            .setDebugLevel(DEBUG_LEVEL_NONE)
                             .setMemoryBytes(256 * ONE_MEBI)
-                            .build();
-            forceCreateNewVirtualMachine("test_vm_boot_time", normalConfig);
+                            .setDebugLevel(DEBUG_LEVEL_NONE);
+            VirtualMachineConfig config = fnConfig.apply(builder).build();
+            forceCreateNewVirtualMachine(name, config);
 
-            BootResult result = tryBootVm(TAG, "test_vm_boot_time");
+            BootResult result = tryBootVm(TAG, name);
             assertThat(result.payloadStarted).isTrue();
-
-            bootTimeMetrics.add(result.endToEndNanoTime / NANO_TO_MILLI);
+            stats.collect(result);
         }
+        return stats;
+    }
 
-        reportMetrics(bootTimeMetrics, "boot_time", "ms");
+    @Test
+    public void testMicrodroidBootTime()
+            throws VirtualMachineException, InterruptedException, IOException {
+        BootTimeStats stats = runBootTimeTest("test_vm_boot_time", (builder) -> builder);
+        reportMetrics(stats.get(BootTimeMetric.TOTAL), "boot_time", "ms");
     }
 
     @Test
     public void testMicrodroidMulticoreBootTime()
             throws VirtualMachineException, InterruptedException, IOException {
-        assume().withMessage("Skip on CF; too slow").that(isCuttlefish()).isFalse();
-
-        final int trialCount = 10;
-        final int[] trialNumCpus = {2, 4, 8};
-
-        for (int numCpus : trialNumCpus) {
-            List<Double> bootTimeMetrics = new ArrayList<>();
-            for (int i = 0; i < trialCount; i++) {
-                VirtualMachineConfig normalConfig =
-                        newVmConfigBuilder()
-                                .setPayloadBinaryName("MicrodroidIdleNativeLib.so")
-                                .setDebugLevel(DEBUG_LEVEL_NONE)
-                                .setMemoryBytes(256 * ONE_MEBI)
-                                .setNumCpus(numCpus)
-                                .build();
-                forceCreateNewVirtualMachine("test_vm_boot_time_multicore", normalConfig);
-
-                BootResult result = tryBootVm(TAG, "test_vm_boot_time_multicore");
-                assertThat(result.payloadStarted).isTrue();
-
-                bootTimeMetrics.add(result.endToEndNanoTime / NANO_TO_MILLI);
-            }
-
+        for (int numCpus : new int[] {2, 4, 8}) {
+            BootTimeStats stats =
+                    runBootTimeTest(
+                            "test_vm_boot_time_multicore",
+                            (builder) -> builder.setNumCpus(numCpus));
             String metricName = "boot_time_" + numCpus + "cpus";
-            reportMetrics(bootTimeMetrics, metricName, "ms");
+            reportMetrics(stats.get(BootTimeMetric.TOTAL), metricName, "ms");
         }
     }
 
     @Test
     public void testMicrodroidDebugBootTime()
             throws VirtualMachineException, InterruptedException, IOException {
-        assume().withMessage("Skip on CF; too slow").that(isCuttlefish()).isFalse();
-
-        final int trialCount = 10;
-
-        List<Double> vmStartingTimeMetrics = new ArrayList<>();
-        List<Double> bootTimeMetrics = new ArrayList<>();
-        List<Double> bootloaderTimeMetrics = new ArrayList<>();
-        List<Double> kernelBootTimeMetrics = new ArrayList<>();
-        List<Double> userspaceBootTimeMetrics = new ArrayList<>();
-
-        for (int i = 0; i < trialCount; i++) {
-            // To grab boot events from log, set debug mode to FULL
-            VirtualMachineConfig normalConfig =
-                    newVmConfigBuilder()
-                            .setPayloadBinaryName("MicrodroidIdleNativeLib.so")
-                            .setDebugLevel(DEBUG_LEVEL_FULL)
-                            .setVmOutputCaptured(true)
-                            .setMemoryBytes(256 * ONE_MEBI)
-                            .build();
-            forceCreateNewVirtualMachine("test_vm_boot_time_debug", normalConfig);
-
-            BootResult result = tryBootVm(TAG, "test_vm_boot_time_debug");
-            assertThat(result.payloadStarted).isTrue();
-
-            vmStartingTimeMetrics.add(result.getVMStartingElapsedNanoTime() / NANO_TO_MILLI);
-            bootTimeMetrics.add(result.endToEndNanoTime / NANO_TO_MILLI);
-            bootloaderTimeMetrics.add(result.getBootloaderElapsedNanoTime() / NANO_TO_MILLI);
-            kernelBootTimeMetrics.add(result.getKernelElapsedNanoTime() / NANO_TO_MILLI);
-            userspaceBootTimeMetrics.add(result.getUserspaceElapsedNanoTime() / NANO_TO_MILLI);
-        }
-
-        reportMetrics(vmStartingTimeMetrics, "vm_starting_time", "ms");
-        reportMetrics(bootTimeMetrics, "boot_time", "ms");
-        reportMetrics(bootloaderTimeMetrics, "bootloader_time", "ms");
-        reportMetrics(kernelBootTimeMetrics, "kernel_boot_time", "ms");
-        reportMetrics(userspaceBootTimeMetrics, "userspace_boot_time", "ms");
+        BootTimeStats stats =
+                runBootTimeTest(
+                        "test_vm_boot_time_debug",
+                        (builder) ->
+                                builder.setDebugLevel(DEBUG_LEVEL_FULL).setVmOutputCaptured(true));
+        reportMetrics(stats.get(BootTimeMetric.TOTAL), "boot_time", "ms");
+        reportMetrics(stats.get(BootTimeMetric.VM_START), "vm_starting_time", "ms");
+        reportMetrics(stats.get(BootTimeMetric.BOOTLOADER), "bootloader_time", "ms");
+        reportMetrics(stats.get(BootTimeMetric.KERNEL), "kernel_boot_time", "ms");
+        reportMetrics(stats.get(BootTimeMetric.USERSPACE), "userspace_boot_time", "ms");
     }
 
     @Test
diff --git a/tests/helper/src/java/com/android/microdroid/test/common/DeviceProperties.java b/tests/helper/src/java/com/android/microdroid/test/common/DeviceProperties.java
index 94f7e99..ba82c38 100644
--- a/tests/helper/src/java/com/android/microdroid/test/common/DeviceProperties.java
+++ b/tests/helper/src/java/com/android/microdroid/test/common/DeviceProperties.java
@@ -26,9 +26,11 @@
     }
 
     private static final String KEY_VENDOR_DEVICE = "ro.product.vendor.device";
+    private static final String KEY_BUILD_TYPE = "ro.build.type";
     private static final String KEY_METRICS_TAG = "debug.hypervisor.metrics_tag";
 
     private static final String CUTTLEFISH_DEVICE_PREFIX = "vsoc_";
+    private static final String USER_BUILD_TYPE = "user";
 
     private final PropertyGetter mPropertyGetter;
 
@@ -49,6 +51,13 @@
         return vendorDeviceName != null && vendorDeviceName.startsWith(CUTTLEFISH_DEVICE_PREFIX);
     }
 
+    /**
+     * @return whether the device is user build.
+     */
+    public boolean isUserBuild() {
+        return USER_BUILD_TYPE.equals(getProperty(KEY_BUILD_TYPE));
+    }
+
     public String getMetricsTag() {
         return getProperty(KEY_METRICS_TAG);
     }
diff --git a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
index f1da43a..419b250 100644
--- a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
+++ b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
@@ -148,8 +148,9 @@
         private OptionalLong mPayloadStartedNanoTime = OptionalLong.empty();
         private StringBuilder mConsoleOutput = new StringBuilder();
         private StringBuilder mLogOutput = new StringBuilder();
+        private boolean mProcessedBootTimeMetrics = false;
 
-        private void processBootEvents(String log) {
+        private void processBootTimeMetrics(String log) {
             if (!mVcpuStartedNanoTime.isPresent()) {
                 mVcpuStartedNanoTime = OptionalLong.of(System.nanoTime());
             }
@@ -165,12 +166,13 @@
             }
         }
 
-        private void logVmOutputAndMonitorBootEvents(
+        private void logVmOutputAndMonitorBootTimeMetrics(
                 String tag,
                 InputStream vmOutputStream,
                 String name,
                 StringBuilder result,
                 boolean monitorEvents) {
+            mProcessedBootTimeMetrics = monitorEvents;
             new Thread(
                             () -> {
                                 try {
@@ -180,7 +182,7 @@
                                     String line;
                                     while ((line = reader.readLine()) != null
                                             && !Thread.interrupted()) {
-                                        if (monitorEvents) processBootEvents(line);
+                                        if (monitorEvents) processBootTimeMetrics(line);
                                         Log.i(tag, name + ": " + line);
                                         result.append(line + "\n");
                                     }
@@ -191,15 +193,15 @@
                     .start();
         }
 
-        private void logVmOutputAndMonitorBootEvents(
+        private void logVmOutputAndMonitorBootTimeMetrics(
                 String tag, InputStream vmOutputStream, String name, StringBuilder result) {
-            logVmOutputAndMonitorBootEvents(tag, vmOutputStream, name, result, true);
+            logVmOutputAndMonitorBootTimeMetrics(tag, vmOutputStream, name, result, true);
         }
 
         /** Copy output from the VM to logcat. This is helpful when things go wrong. */
         protected void logVmOutput(
                 String tag, InputStream vmOutputStream, String name, StringBuilder result) {
-            logVmOutputAndMonitorBootEvents(tag, vmOutputStream, name, result, false);
+            logVmOutputAndMonitorBootTimeMetrics(tag, vmOutputStream, name, result, false);
         }
 
         public void runToFinish(String logTag, VirtualMachine vm)
@@ -207,7 +209,7 @@
             vm.setCallback(mExecutorService, this);
             vm.run();
             if (vm.getConfig().isVmOutputCaptured()) {
-                logVmOutputAndMonitorBootEvents(
+                logVmOutputAndMonitorBootTimeMetrics(
                         logTag, vm.getConsoleOutput(), "Console", mConsoleOutput);
                 logVmOutput(logTag, vm.getLogOutput(), "Log", mLogOutput);
             }
@@ -238,6 +240,10 @@
             return mLogOutput.toString();
         }
 
+        public boolean hasProcessedBootTimeMetrics() {
+            return mProcessedBootTimeMetrics;
+        }
+
         protected void forceStop(VirtualMachine vm) {
             try {
                 vm.stop();
@@ -266,12 +272,21 @@
         }
     }
 
+    public enum BootTimeMetric {
+        TOTAL,
+        VM_START,
+        BOOTLOADER,
+        KERNEL,
+        USERSPACE,
+    }
+
     public static class BootResult {
         public final boolean payloadStarted;
         public final int deathReason;
         public final long apiCallNanoTime;
         public final long endToEndNanoTime;
 
+        public final boolean processedBootTimeMetrics;
         public final OptionalLong vcpuStartedNanoTime;
         public final OptionalLong kernelStartedNanoTime;
         public final OptionalLong initStartedNanoTime;
@@ -285,6 +300,7 @@
                 int deathReason,
                 long apiCallNanoTime,
                 long endToEndNanoTime,
+                boolean processedBootTimeMetrics,
                 OptionalLong vcpuStartedNanoTime,
                 OptionalLong kernelStartedNanoTime,
                 OptionalLong initStartedNanoTime,
@@ -295,6 +311,7 @@
             this.payloadStarted = payloadStarted;
             this.deathReason = deathReason;
             this.endToEndNanoTime = endToEndNanoTime;
+            this.processedBootTimeMetrics = processedBootTimeMetrics;
             this.vcpuStartedNanoTime = vcpuStartedNanoTime;
             this.kernelStartedNanoTime = kernelStartedNanoTime;
             this.initStartedNanoTime = initStartedNanoTime;
@@ -336,6 +353,31 @@
         public long getUserspaceElapsedNanoTime() {
             return getPayloadStartedNanoTime() - getInitStartedNanoTime();
         }
+
+        public boolean hasProcessedBootTimeMetrics() {
+            return processedBootTimeMetrics;
+        }
+
+        public OptionalLong getBootTimeMetricNanoTime(BootTimeMetric metric) {
+            if (metric == BootTimeMetric.TOTAL) {
+                return OptionalLong.of(endToEndNanoTime);
+            }
+
+            if (processedBootTimeMetrics) {
+                switch (metric) {
+                    case VM_START:
+                        return OptionalLong.of(getVMStartingElapsedNanoTime());
+                    case BOOTLOADER:
+                        return OptionalLong.of(getBootloaderElapsedNanoTime());
+                    case KERNEL:
+                        return OptionalLong.of(getKernelElapsedNanoTime());
+                    case USERSPACE:
+                        return OptionalLong.of(getUserspaceElapsedNanoTime());
+                }
+            }
+
+            return OptionalLong.empty();
+        }
     }
 
     public BootResult tryBootVm(String logTag, String vmName)
@@ -366,6 +408,7 @@
                 deathReason.getNow(VmEventListener.STOP_REASON_INFRASTRUCTURE_ERROR),
                 apiCallNanoTime,
                 endTime.getNow(apiCallNanoTime) - apiCallNanoTime,
+                listener.hasProcessedBootTimeMetrics(),
                 listener.getVcpuStartedNanoTime(),
                 listener.getKernelStartedNanoTime(),
                 listener.getInitStartedNanoTime(),
diff --git a/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java b/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
index 8d328bc..1766835 100644
--- a/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
+++ b/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
@@ -110,6 +110,10 @@
         }
     }
 
+    public boolean isUserBuild() {
+        return DeviceProperties.create(getDevice()::getProperty).isUserBuild();
+    }
+
     protected boolean isCuttlefish() {
         return DeviceProperties.create(getDevice()::getProperty).isCuttlefish();
     }
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index 0623ff2..a890770 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -55,6 +55,7 @@
 import org.json.JSONObject;
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestName;
@@ -583,8 +584,10 @@
     }
 
     @Test
+    @Ignore("b/243630590: Temporal workaround until lab devices has flashed new DPM")
     public void testTombstonesAreGeneratedUponKernelCrash() throws Exception {
         assumeFalse("Cuttlefish is not supported", isCuttlefish());
+        assumeFalse("Skipping test because ramdump is disabled on user build", isUserBuild());
         assertThat(
                         isTombstoneGenerated(
                                 "assets/vm_config_crash.json",
diff --git a/virtualizationmanager/src/crosvm.rs b/virtualizationmanager/src/crosvm.rs
index 8f88daf..19d862a 100644
--- a/virtualizationmanager/src/crosvm.rs
+++ b/virtualizationmanager/src/crosvm.rs
@@ -663,6 +663,24 @@
     }
 }
 
+fn should_configure_ramdump(protected: bool) -> bool {
+    if protected {
+        // Protected VM needs ramdump configuration here.
+        // pvmfw will disable ramdump if unnecessary.
+        true
+    } else {
+        // For unprotected VM, ramdump should be handled here.
+        // ramdump wouldn't be enabled if ramdump is explicitly set to <1>.
+        if let Ok(mut file) = File::open("/proc/device-tree/avf/guest/common/ramdump") {
+            let mut ramdump: [u8; 4] = Default::default();
+            file.read_exact(&mut ramdump).map_err(|_| false).unwrap();
+            // DT spec uses big endian although Android is always little endian.
+            return u32::from_be_bytes(ramdump) == 1;
+        }
+        false
+    }
+}
+
 /// Starts an instance of `crosvm` to manage a new VM.
 fn run_vm(
     config: CrosvmConfig,
@@ -779,7 +797,10 @@
     debug!("Preserving FDs {:?}", preserved_fds);
     command.preserved_fds(preserved_fds);
 
-    command.arg("--params").arg("crashkernel=17M");
+    if should_configure_ramdump(config.protected) {
+        command.arg("--params").arg("crashkernel=17M");
+    }
+
     print_crosvm_args(&command);
 
     let result = SharedChild::spawn(&mut command)?;