Merge "pvmfw: Clarify docs about BCC generation in ABL"
diff --git a/authfs/Android.bp b/authfs/Android.bp
index 154a1d6..2532026 100644
--- a/authfs/Android.bp
+++ b/authfs/Android.bp
@@ -23,7 +23,7 @@
         "liblog_rust",
         "libnix",
         "libopenssl",
-        "libprotobuf",
+        "libprotobuf_deprecated",
         "librpcbinder_rs",
         "libthiserror",
     ],
diff --git a/compos/Android.bp b/compos/Android.bp
index 2f6be98..c120b0f 100644
--- a/compos/Android.bp
+++ b/compos/Android.bp
@@ -18,7 +18,7 @@
         "libminijail_rust",
         "libnix",
         "libodsign_proto_rust",
-        "libprotobuf",
+        "libprotobuf_deprecated",
         "libregex",
         "librpcbinder_rs",
         "librustutils",
diff --git a/compos/common/compos_client.rs b/compos/common/compos_client.rs
index 96c8147..bf4c678 100644
--- a/compos/common/compos_client.rs
+++ b/compos/common/compos_client.rs
@@ -19,7 +19,7 @@
 use crate::timeouts::TIMEOUTS;
 use crate::{
     get_vm_config_path, BUILD_MANIFEST_APK_PATH, BUILD_MANIFEST_SYSTEM_EXT_APK_PATH,
-    COMPOS_APEX_ROOT, COMPOS_DATA_ROOT, COMPOS_VSOCK_PORT,
+    COMPOS_APEX_ROOT, COMPOS_VSOCK_PORT,
 };
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
     CpuTopology::CpuTopology,
@@ -80,7 +80,6 @@
         let instance_fd = ParcelFileDescriptor::new(instance_image);
 
         let apex_dir = Path::new(COMPOS_APEX_ROOT);
-        let data_dir = Path::new(COMPOS_DATA_ROOT);
 
         let config_apk = locate_config_apk(apex_dir)?;
         let apk_fd = File::open(config_apk).context("Failed to open config APK file")?;
@@ -110,18 +109,6 @@
 
         let debug_level = if parameters.debug_mode { DebugLevel::FULL } else { DebugLevel::NONE };
 
-        let (console_fd, log_fd) = if debug_level == DebugLevel::NONE {
-            (None, None)
-        } else {
-            // Console output and the system log output from the VM are redirected to file.
-            let console_fd = File::create(data_dir.join("vm_console.log"))
-                .context("Failed to create console log file")?;
-            let log_fd = File::create(data_dir.join("vm.log"))
-                .context("Failed to create system log file")?;
-            info!("Running in debug level {:?}", debug_level);
-            (Some(console_fd), Some(log_fd))
-        };
-
         let cpu_topology = match parameters.cpu_topology {
             VmCpuTopology::OneCpu => CpuTopology::ONE_CPU,
             VmCpuTopology::MatchHost => CpuTopology::MATCH_HOST,
@@ -143,6 +130,8 @@
             gdbPort: 0, // Don't start gdb-server
         });
 
+        // Let logs go to logcat.
+        let (console_fd, log_fd) = (None, None);
         let callback = Box::new(Callback {});
         let instance = VmInstance::create(service, &config, console_fd, log_fd, Some(callback))
             .context("Failed to create VM")?;
diff --git a/compos/composd/Android.bp b/compos/composd/Android.bp
index b0294dd..f66de32 100644
--- a/compos/composd/Android.bp
+++ b/compos/composd/Android.bp
@@ -22,7 +22,7 @@
         "liblibc",
         "liblog_rust",
         "libodsign_proto_rust",
-        "libprotobuf",
+        "libprotobuf_deprecated",
         "librustutils",
         "libshared_child",
         "libvmclient",
diff --git a/compos/tests/java/android/compos/test/ComposTestCase.java b/compos/tests/java/android/compos/test/ComposTestCase.java
index 4e3d0a8..eb2e072 100644
--- a/compos/tests/java/android/compos/test/ComposTestCase.java
+++ b/compos/tests/java/android/compos/test/ComposTestCase.java
@@ -27,7 +27,6 @@
 
 import com.android.microdroid.test.host.CommandRunner;
 import com.android.microdroid.test.host.MicrodroidHostTestCaseBase;
-import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.result.FileInputStreamSource;
 import com.android.tradefed.result.LogDataType;
@@ -81,7 +80,7 @@
 
     @Before
     public void setUp() throws Exception {
-        testIfDeviceIsCapable(getDevice());
+        assumeDeviceIsCapable(getDevice());
 
         String value = getDevice().getProperty(SYSTEM_SERVER_COMPILER_FILTER_PROP_NAME);
         if (value == null) {
@@ -95,8 +94,6 @@
     public void tearDown() throws Exception {
         killVmAndReconnectAdb();
 
-        archiveVmLogsThenDelete("teardown");
-
         CommandRunner android = new CommandRunner(getDevice());
 
         // Clear up any CompOS instance files we created
@@ -113,19 +110,6 @@
         }
     }
 
-    private void archiveVmLogsThenDelete(String suffix) throws DeviceNotAvailableException {
-        archiveLogThenDelete(
-                mTestLogs,
-                getDevice(),
-                COMPOS_APEXDATA_DIR + "/vm_console.log",
-                "vm_console.log-" + suffix + "-" + mTestName.getMethodName());
-        archiveLogThenDelete(
-                mTestLogs,
-                getDevice(),
-                COMPOS_APEXDATA_DIR + "/vm.log",
-                "vm.log-" + suffix + "-" + mTestName.getMethodName());
-    }
-
     @Test
     public void testOdrefreshSpeed() throws Exception {
         getDevice().setProperty(SYSTEM_SERVER_COMPILER_FILTER_PROP_NAME, "speed");
@@ -170,10 +154,6 @@
         }
         killVmAndReconnectAdb();
 
-        // These logs are potentially useful, capture them before they are overwritten by
-        // compos_verify.
-        archiveVmLogsThenDelete("compile");
-
         // Expect the BCC extracted from the BCC to be well-formed.
         assertVmBccIsValid();
 
diff --git a/demo/README.md b/demo/README.md
index c5c87d8..fa4e38a 100644
--- a/demo/README.md
+++ b/demo/README.md
@@ -8,13 +8,18 @@
 
 ## Installing
 
+You can install the app like this:
 ```
-adb install -t out/dist/MicrodroidDemoApp.apk
-adb shell pm grant com.android.microdroid.demo android.permission.MANAGE_VIRTUAL_MACHINE
+adb install -t -g out/dist/MicrodroidDemoApp.apk
 ```
 
-Don't run the app before granting the permission. Or you will have to uninstall
-the app, and then re-install it.
+(-t allows it to be installed even though it is marked as a test app, -g grants
+the necessary permission.)
+
+You can also explicitly grant or revoke the permission, e.g.
+```
+adb shell pm grant com.android.microdroid.demo android.permission.MANAGE_VIRTUAL_MACHINE
+```
 
 ## Running
 
diff --git a/encryptedstore/src/main.rs b/encryptedstore/src/main.rs
index 7a41f13..86fa6da 100644
--- a/encryptedstore/src/main.rs
+++ b/encryptedstore/src/main.rs
@@ -125,9 +125,13 @@
 
 fn format_ext4(device: &Path) -> Result<()> {
     let mkfs_options = [
-        "-j",               // Create appropriate sized journal
-        "-O metadata_csum", // Metadata checksum for filesystem integrity
-        "-b 4096",          // block size in the filesystem
+        "-j", // Create appropriate sized journal
+        /* metadata_csum: enabled for filesystem integrity
+         * extents: Not enabling extents reduces the coverage of metadata checksumming.
+         * 64bit: larger fields afforded by this feature enable full-strength checksumming.
+         */
+        "-O metadata_csum, extents, 64bit",
+        "-b 4096", // block size in the filesystem
     ];
     let mut cmd = Command::new(MK2FS_BIN);
     let status = cmd
diff --git a/libs/devicemapper/src/lib.rs b/libs/devicemapper/src/lib.rs
index 4cf4e99..fec0114 100644
--- a/libs/devicemapper/src/lib.rs
+++ b/libs/devicemapper/src/lib.rs
@@ -227,8 +227,8 @@
     let context = Context::new(0);
     let now = SystemTime::now().duration_since(UNIX_EPOCH)?;
     let ts = Timestamp::from_unix(context, now.as_secs(), now.subsec_nanos());
-    let uuid = Uuid::new_v1(ts, node_id)?;
-    Ok(String::from(uuid.to_hyphenated().encode_lower(&mut Uuid::encode_buffer())))
+    let uuid = Uuid::new_v1(ts, node_id.try_into()?);
+    Ok(String::from(uuid.hyphenated().encode_lower(&mut Uuid::encode_buffer())))
 }
 
 #[cfg(test)]
diff --git a/microdroid/payload/metadata/Android.bp b/microdroid/payload/metadata/Android.bp
index cd182fc..e3138e8 100644
--- a/microdroid/payload/metadata/Android.bp
+++ b/microdroid/payload/metadata/Android.bp
@@ -12,7 +12,7 @@
     rustlibs: [
         "libanyhow",
         "libmicrodroid_metadata_proto_rust",
-        "libprotobuf",
+        "libprotobuf_deprecated",
     ],
     apex_available: [
         "com.android.virt",
diff --git a/pvmfw/src/exceptions.rs b/pvmfw/src/exceptions.rs
index 03fc220..42f4c3b 100644
--- a/pvmfw/src/exceptions.rs
+++ b/pvmfw/src/exceptions.rs
@@ -14,22 +14,21 @@
 
 //! Exception handlers.
 
-use crate::helpers::page_4kb_of;
-use core::arch::asm;
+use crate::{helpers::page_4kb_of, read_sysreg};
 use vmbase::console;
 use vmbase::{console::emergency_write_str, eprintln, power::reboot};
 
-const ESR_32BIT_EXT_DABT: u64 = 0x96000010;
+const ESR_32BIT_EXT_DABT: usize = 0x96000010;
 const UART_PAGE: usize = page_4kb_of(console::BASE_ADDRESS);
 
 #[no_mangle]
 extern "C" fn sync_exception_current(_elr: u64, _spsr: u64) {
-    let esr = read_esr();
-    let far = read_far();
+    let esr = read_sysreg!("esr_el1");
+    let far = read_sysreg!("far_el1");
     // Don't print to the UART if we're handling the exception it could raise.
-    if esr != ESR_32BIT_EXT_DABT || page_4kb_of(far as usize) != UART_PAGE {
+    if esr != ESR_32BIT_EXT_DABT || page_4kb_of(far) != UART_PAGE {
         emergency_write_str("sync_exception_current\n");
-        print_esr(esr);
+        eprintln!("esr={esr:#08x}");
     }
     reboot();
 }
@@ -48,17 +47,17 @@
 
 #[no_mangle]
 extern "C" fn serr_current(_elr: u64, _spsr: u64) {
-    let esr = read_esr();
+    let esr = read_sysreg!("esr_el1");
     emergency_write_str("serr_current\n");
-    print_esr(esr);
+    eprintln!("esr={esr:#08x}");
     reboot();
 }
 
 #[no_mangle]
 extern "C" fn sync_lower(_elr: u64, _spsr: u64) {
-    let esr = read_esr();
+    let esr = read_sysreg!("esr_el1");
     emergency_write_str("sync_lower\n");
-    print_esr(esr);
+    eprintln!("esr={esr:#08x}");
     reboot();
 }
 
@@ -76,31 +75,8 @@
 
 #[no_mangle]
 extern "C" fn serr_lower(_elr: u64, _spsr: u64) {
-    let esr = read_esr();
+    let esr = read_sysreg!("esr_el1");
     emergency_write_str("serr_lower\n");
-    print_esr(esr);
+    eprintln!("esr={esr:#08x}");
     reboot();
 }
-
-#[inline]
-fn read_esr() -> u64 {
-    let mut esr: u64;
-    unsafe {
-        asm!("mrs {esr}, esr_el1", esr = out(reg) esr);
-    }
-    esr
-}
-
-#[inline]
-fn print_esr(esr: u64) {
-    eprintln!("esr={:#08x}", esr);
-}
-
-#[inline]
-fn read_far() -> u64 {
-    let mut far: u64;
-    unsafe {
-        asm!("mrs {far}, far_el1", far = out(reg) far);
-    }
-    far
-}
diff --git a/pvmfw/src/helpers.rs b/pvmfw/src/helpers.rs
index 4df9386..6310826 100644
--- a/pvmfw/src/helpers.rs
+++ b/pvmfw/src/helpers.rs
@@ -22,6 +22,39 @@
 
 pub const GUEST_PAGE_SIZE: usize = SIZE_4KB;
 
+/// Read a value from a system register.
+#[macro_export]
+macro_rules! read_sysreg {
+    ($sysreg:literal) => {{
+        let mut r: usize;
+        // Safe because it reads a system register and does not affect Rust.
+        unsafe {
+            core::arch::asm!(
+                concat!("mrs {}, ", $sysreg),
+                out(reg) r,
+                options(nomem, nostack, preserves_flags),
+            )
+        }
+        r
+    }};
+}
+
+/// Write a value to a system register.
+#[macro_export]
+macro_rules! write_sysreg {
+    ($sysreg:literal, $val:expr) => {{
+        let value: usize = $val;
+        // Safe because it writes a system register and does not affect Rust.
+        unsafe {
+            core::arch::asm!(
+                concat!("msr ", $sysreg, ", {}"),
+                in(reg) value,
+                options(nomem, nostack, preserves_flags),
+            )
+        }
+    }};
+}
+
 /// Computes the largest multiple of the provided alignment smaller or equal to the address.
 ///
 /// Note: the result is undefined if alignment isn't a power of two.
@@ -78,9 +111,7 @@
 fn min_dcache_line_size() -> usize {
     const DMINLINE_SHIFT: usize = 16;
     const DMINLINE_MASK: usize = 0xf;
-    let ctr_el0: usize;
-
-    unsafe { asm!("mrs {x}, ctr_el0", x = out(reg) ctr_el0) }
+    let ctr_el0 = read_sysreg!("ctr_el0");
 
     // DminLine: log2 of the number of words in the smallest cache line of all the data caches.
     let dminline = (ctr_el0 >> DMINLINE_SHIFT) & DMINLINE_MASK;
@@ -97,7 +128,13 @@
 
     for line in (start..end).step_by(line_size) {
         // SAFETY - Clearing cache lines shouldn't have Rust-visible side effects.
-        unsafe { asm!("dc cvau, {x}", x = in(reg) line) }
+        unsafe {
+            asm!(
+                "dc cvau, {x}",
+                x = in(reg) line,
+                options(nomem, nostack, preserves_flags),
+            )
+        }
     }
 }
 
diff --git a/pvmfw/src/instance.rs b/pvmfw/src/instance.rs
index fbf2040..a974543 100644
--- a/pvmfw/src/instance.rs
+++ b/pvmfw/src/instance.rs
@@ -258,11 +258,11 @@
 
 impl EntryHeader {
     fn new(uuid: Uuid, payload_size: usize) -> Self {
-        Self { uuid: uuid.as_u128(), payload_size: u64::try_from(payload_size).unwrap().to_le() }
+        Self { uuid: uuid.to_u128_le(), payload_size: u64::try_from(payload_size).unwrap().to_le() }
     }
 
     fn uuid(&self) -> Uuid {
-        Uuid::from_u128(self.uuid)
+        Uuid::from_u128_le(self.uuid)
     }
 
     fn payload_size(&self) -> usize {
diff --git a/pvmfw/src/main.rs b/pvmfw/src/main.rs
index 577ad6e..00ff61f 100644
--- a/pvmfw/src/main.rs
+++ b/pvmfw/src/main.rs
@@ -16,7 +16,6 @@
 
 #![no_main]
 #![no_std]
-#![feature(default_alloc_error_handler)]
 
 extern crate alloc;
 
diff --git a/rialto/src/main.rs b/rialto/src/main.rs
index 3b730f4..59ee0b6 100644
--- a/rialto/src/main.rs
+++ b/rialto/src/main.rs
@@ -16,7 +16,6 @@
 
 #![no_main]
 #![no_std]
-#![feature(default_alloc_error_handler)]
 
 mod exceptions;
 
diff --git a/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java b/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
index 9c8714f..8c6218c 100644
--- a/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
+++ b/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
@@ -85,9 +85,14 @@
 
     private boolean mNeedTearDown = false;
 
+    private boolean mNeedToRestartPkvmStatus = false;
+
     @Before
     public void setUp() throws Exception {
-        testIfDeviceIsCapable(getDevice());
+        mNeedTearDown = false;
+        mNeedToRestartPkvmStatus = false;
+
+        assumeDeviceIsCapable(getDevice());
         mNeedTearDown = true;
 
         getDevice().installPackage(findTestFile(APK_NAME), /* reinstall */ false);
@@ -99,12 +104,12 @@
     public void tearDown() throws Exception {
         if (!mNeedTearDown) {
             // If we skipped setUp, we don't need to undo it, and that avoids potential exceptions
-            // incompatible hardware. (Note that tests can change what testIfDeviceIsCapable()
+            // incompatible hardware. (Note that tests can change what assumeDeviceIsCapable()
             // sees, so we can't rely on that - b/268688303.)
             return;
         }
-        // Set PKVM enable and reboot to prevent previous staged session.
-        if (!isCuttlefish()) {
+        // Restore PKVM status and reboot to prevent previous staged session, if switched.
+        if (mNeedToRestartPkvmStatus) {
             setPKVMStatusWithRebootToBootloader(true);
             rebootFromBootloaderAndWaitBootCompleted();
         }
@@ -422,7 +427,7 @@
     }
 
     private void enableDisablePKVMTestHelper(boolean isEnable) throws Exception {
-        skipIfPKVMStatusSwitchNotSupported();
+        assumePKVMStatusSwitchSupported();
 
         List<Double> bootDmesgTime = new ArrayList<>(ROUND_COUNT);
         Map<String, List<Double>> bootloaderTime = new HashMap<>();
@@ -478,9 +483,15 @@
         reportMetric(bootDmesgTime, "dmesg_boot_time_" + suffix, "s");
     }
 
-    private void skipIfPKVMStatusSwitchNotSupported() throws Exception {
+    private void assumePKVMStatusSwitchSupported() throws Exception {
         assumeFalse("Skip on CF; can't reboot to bootloader", isCuttlefish());
 
+        // This is an overkill. The intention is to exclude remote_device_proxy, which uses
+        // different serial for fastboot. But there's no good way to distinguish from regular IP
+        // transport. This is currently not a problem until someone really needs to run the test
+        // over regular IP transport.
+        assumeFalse("Skip over IP (overkill for remote_device_proxy)", getDevice().isAdbTcp());
+
         if (!getDevice().isStateBootloaderOrFastbootd()) {
             getDevice().rebootIntoBootloader();
         }
@@ -513,6 +524,7 @@
     }
 
     private void setPKVMStatusWithRebootToBootloader(boolean isEnable) throws Exception {
+        mNeedToRestartPkvmStatus = true;
 
         if (!getDevice().isStateBootloaderOrFastbootd()) {
             getDevice().rebootIntoBootloader();
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 a7f7906..4807c0f 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
@@ -97,7 +97,7 @@
                 DeviceProperties.create(getDevice()::getProperty).getMetricsTag());
     }
 
-    public static void testIfDeviceIsCapable(ITestDevice androidDevice) throws Exception {
+    public static void assumeDeviceIsCapable(ITestDevice androidDevice) throws Exception {
         assumeTrue("Need an actual TestDevice", androidDevice instanceof TestDevice);
         TestDevice testDevice = (TestDevice) androidDevice;
         assumeTrue(
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index 687756e..2027fcd 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -24,7 +24,6 @@
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
@@ -82,6 +81,7 @@
 import java.util.function.Function;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class MicrodroidHostTests extends MicrodroidHostTestCaseBase {
@@ -682,19 +682,24 @@
         microdroid.waitForBootComplete(BOOT_COMPLETE_TIMEOUT);
         device.shutdownMicrodroid(microdroid);
 
+        // Try to collect atoms for 60000 milliseconds.
         List<StatsLog.EventMetricData> data = new ArrayList<>();
-        assertThatEventually(
-                10000,
-                () -> {
-                    data.addAll(ReportUtils.getEventMetricDataList(getDevice()));
-                    return data.size();
-                },
-                is(3)
-        );
+        long start = System.currentTimeMillis();
+        while ((System.currentTimeMillis() - start < 60000) && data.size() < 3) {
+            data.addAll(ReportUtils.getEventMetricDataList(getDevice()));
+            Thread.sleep(500);
+        }
+        assertThat(
+                        data.stream()
+                                .map(x -> x.getAtom().getPushedCase().getNumber())
+                                .collect(Collectors.toList()))
+                .containsExactly(
+                        AtomsProto.Atom.VM_CREATION_REQUESTED_FIELD_NUMBER,
+                        AtomsProto.Atom.VM_BOOTED_FIELD_NUMBER,
+                        AtomsProto.Atom.VM_EXITED_FIELD_NUMBER)
+                .inOrder();
 
         // Check VmCreationRequested atom
-        assertThat(data.get(0).getAtom().getPushedCase().getNumber()).isEqualTo(
-                AtomsProto.Atom.VM_CREATION_REQUESTED_FIELD_NUMBER);
         AtomsProto.VmCreationRequested atomVmCreationRequested =
                 data.get(0).getAtom().getVmCreationRequested();
         assertThat(atomVmCreationRequested.getHypervisor())
@@ -711,14 +716,10 @@
                 .isEqualTo("com.android.art:com.android.compos:com.android.sdkext");
 
         // Check VmBooted atom
-        assertThat(data.get(1).getAtom().getPushedCase().getNumber())
-                .isEqualTo(AtomsProto.Atom.VM_BOOTED_FIELD_NUMBER);
         AtomsProto.VmBooted atomVmBooted = data.get(1).getAtom().getVmBooted();
         assertThat(atomVmBooted.getVmIdentifier()).isEqualTo("VmRunApp");
 
         // Check VmExited atom
-        assertThat(data.get(2).getAtom().getPushedCase().getNumber())
-                .isEqualTo(AtomsProto.Atom.VM_EXITED_FIELD_NUMBER);
         AtomsProto.VmExited atomVmExited = data.get(2).getAtom().getVmExited();
         assertThat(atomVmExited.getVmIdentifier()).isEqualTo("VmRunApp");
         assertThat(atomVmExited.getDeathReason()).isEqualTo(AtomsProto.VmExited.DeathReason.KILLED);
@@ -970,7 +971,7 @@
 
     @Before
     public void setUp() throws Exception {
-        testIfDeviceIsCapable(getDevice());
+        assumeDeviceIsCapable(getDevice());
         mMetricPrefix = getMetricPrefix() + "microdroid/";
         mMicrodroidDevice = null;
 
diff --git a/tests/hostside/java/com/android/microdroid/test/PvmfwDebugPolicyHostTests.java b/tests/hostside/java/com/android/microdroid/test/PvmfwDebugPolicyHostTests.java
index 18aa273..22131f1 100644
--- a/tests/hostside/java/com/android/microdroid/test/PvmfwDebugPolicyHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/PvmfwDebugPolicyHostTests.java
@@ -112,7 +112,7 @@
                 getTestInformation().getDependencyFile(BCC_FILE_NAME, /* targetFirst= */ false);
 
         // Check device capability
-        testIfDeviceIsCapable(mAndroidDevice);
+        assumeDeviceIsCapable(mAndroidDevice);
         assumeTrue(
                 "Protected VMs are not supported",
                 mAndroidDevice.supportsMicrodroid(/*protectedVm=*/ true));
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 b7dbcd8..7044ae7 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -66,7 +66,6 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.function.ThrowingRunnable;
@@ -1183,7 +1182,6 @@
     }
 
     @Test
-    @Ignore("b/249723852")
     @CddTest(requirements = {
             "9.17/C-1-1",
             "9.17/C-2-7"
diff --git a/vmbase/example/src/main.rs b/vmbase/example/src/main.rs
index 3b0e9db..9ec2dc4 100644
--- a/vmbase/example/src/main.rs
+++ b/vmbase/example/src/main.rs
@@ -16,7 +16,6 @@
 
 #![no_main]
 #![no_std]
-#![feature(default_alloc_error_handler)]
 
 mod exceptions;
 mod layout;
diff --git a/vmbase/example/src/pci.rs b/vmbase/example/src/pci.rs
index c0a2d2b..117cbc8 100644
--- a/vmbase/example/src/pci.rs
+++ b/vmbase/example/src/pci.rs
@@ -20,7 +20,7 @@
 use fdtpci::PciInfo;
 use log::{debug, info};
 use virtio_drivers::{
-    device::blk::VirtIOBlk,
+    device::{blk::VirtIOBlk, console::VirtIOConsole},
     transport::{
         pci::{bus::PciRoot, virtio_device_type, PciTransport},
         DeviceType, Transport,
@@ -53,29 +53,41 @@
         }
     }
 
-    assert_eq!(checked_virtio_device_count, 1);
+    assert_eq!(checked_virtio_device_count, 4);
 }
 
 /// Checks the given VirtIO device, if we know how to.
 ///
 /// Returns true if the device was checked, or false if it was ignored.
 fn check_virtio_device(transport: impl Transport, device_type: DeviceType) -> bool {
-    if device_type == DeviceType::Block {
-        let mut blk = VirtIOBlk::<HalImpl, _>::new(transport).expect("failed to create blk driver");
-        info!("Found {} KiB block device.", blk.capacity() * SECTOR_SIZE_BYTES as u64 / 1024);
-        assert_eq!(blk.capacity(), EXPECTED_SECTOR_COUNT as u64);
-        let mut data = [0; SECTOR_SIZE_BYTES * EXPECTED_SECTOR_COUNT];
-        for i in 0..EXPECTED_SECTOR_COUNT {
-            blk.read_block(i, &mut data[i * SECTOR_SIZE_BYTES..(i + 1) * SECTOR_SIZE_BYTES])
-                .expect("Failed to read block device.");
+    match device_type {
+        DeviceType::Block => {
+            let mut blk =
+                VirtIOBlk::<HalImpl, _>::new(transport).expect("failed to create blk driver");
+            info!("Found {} KiB block device.", blk.capacity() * SECTOR_SIZE_BYTES as u64 / 1024);
+            assert_eq!(blk.capacity(), EXPECTED_SECTOR_COUNT as u64);
+            let mut data = [0; SECTOR_SIZE_BYTES * EXPECTED_SECTOR_COUNT];
+            for i in 0..EXPECTED_SECTOR_COUNT {
+                blk.read_block(i, &mut data[i * SECTOR_SIZE_BYTES..(i + 1) * SECTOR_SIZE_BYTES])
+                    .expect("Failed to read block device.");
+            }
+            for (i, chunk) in data.chunks(size_of::<u32>()).enumerate() {
+                assert_eq!(chunk, &(i as u32).to_le_bytes());
+            }
+            info!("Read expected data from block device.");
+            true
         }
-        for (i, chunk) in data.chunks(size_of::<u32>()).enumerate() {
-            assert_eq!(chunk, &(i as u32).to_le_bytes());
+        DeviceType::Console => {
+            let mut console = VirtIOConsole::<HalImpl, _>::new(transport)
+                .expect("Failed to create VirtIO console driver");
+            info!("Found console device: {:?}", console.info());
+            for &c in b"Hello VirtIO console\n" {
+                console.send(c).expect("Failed to send character to VirtIO console device");
+            }
+            info!("Wrote to VirtIO console.");
+            true
         }
-        info!("Read expected data from block device.");
-        true
-    } else {
-        false
+        _ => false,
     }
 }
 
diff --git a/vmbase/example/tests/test.rs b/vmbase/example/tests/test.rs
index 930e137..8f0eaa5 100644
--- a/vmbase/example/tests/test.rs
+++ b/vmbase/example/tests/test.rs
@@ -25,7 +25,7 @@
 use log::info;
 use std::{
     fs::File,
-    io::{self, BufRead, BufReader, Write},
+    io::{self, BufRead, BufReader, Read, Write},
     os::unix::io::FromRawFd,
     panic, thread,
 };
@@ -90,8 +90,8 @@
         gdbPort: 0, // no gdb
     });
     let console = android_log_fd()?;
-    let log = android_log_fd()?;
-    let vm = VmInstance::create(service.as_ref(), &config, Some(console), Some(log), None)
+    let (mut log_reader, log_writer) = pipe()?;
+    let vm = VmInstance::create(service.as_ref(), &config, Some(console), Some(log_writer), None)
         .context("Failed to create VM")?;
     vm.start().context("Failed to start VM")?;
     info!("Started example VM.");
@@ -100,15 +100,17 @@
     let death_reason = vm.wait_for_death();
     assert_eq!(death_reason, DeathReason::Shutdown);
 
+    // Check that the expected string was written to the log VirtIO console device.
+    let expected = "Hello VirtIO console\n";
+    let mut log_output = String::new();
+    assert_eq!(log_reader.read_to_string(&mut log_output)?, expected.len());
+    assert_eq!(log_output, expected);
+
     Ok(())
 }
 
 fn android_log_fd() -> io::Result<File> {
-    let (reader_fd, writer_fd) = nix::unistd::pipe()?;
-
-    // SAFETY: These are new FDs with no previous owner.
-    let reader = unsafe { File::from_raw_fd(reader_fd) };
-    let writer = unsafe { File::from_raw_fd(writer_fd) };
+    let (reader, writer) = pipe()?;
 
     thread::spawn(|| {
         for line in BufReader::new(reader).lines() {
@@ -117,3 +119,13 @@
     });
     Ok(writer)
 }
+
+fn pipe() -> io::Result<(File, File)> {
+    let (reader_fd, writer_fd) = nix::unistd::pipe()?;
+
+    // SAFETY: These are new FDs with no previous owner.
+    let reader = unsafe { File::from_raw_fd(reader_fd) };
+    let writer = unsafe { File::from_raw_fd(writer_fd) };
+
+    Ok((reader, writer))
+}