Merge "Fix st_blksize type for riscv64" into main
diff --git a/apkdmverity/Android.bp b/apkdmverity/Android.bp
index fae7e99..8429263 100644
--- a/apkdmverity/Android.bp
+++ b/apkdmverity/Android.bp
@@ -40,7 +40,7 @@
     name: "apkdmverity.test",
     defaults: [
         "apkdmverity.defaults",
-        "ignorabletest.defaults",
+        "rdroidtest.defaults",
     ],
     test_suites: ["general-tests"],
     compile_multilib: "first",
diff --git a/apkdmverity/src/main.rs b/apkdmverity/src/main.rs
index 55953a9..d9e9e2b 100644
--- a/apkdmverity/src/main.rs
+++ b/apkdmverity/src/main.rs
@@ -153,12 +153,12 @@
 }
 
 #[cfg(test)]
-ignorabletest::test_main!();
+rdroidtest::test_main!();
 
 #[cfg(test)]
 mod tests {
     use crate::*;
-    use ignorabletest::test;
+    use rdroidtest::test;
     use std::fs::{File, OpenOptions};
     use std::io::Write;
     use std::ops::Deref;
diff --git a/libs/devicemapper/Android.bp b/libs/devicemapper/Android.bp
index b7cdedc..29f2f5f 100644
--- a/libs/devicemapper/Android.bp
+++ b/libs/devicemapper/Android.bp
@@ -33,7 +33,7 @@
     name: "libdm_rust.test",
     defaults: [
         "libdm_rust.defaults",
-        "ignorabletest.defaults",
+        "rdroidtest.defaults",
     ],
     test_suites: ["general-tests"],
     rustlibs: [
diff --git a/libs/devicemapper/src/lib.rs b/libs/devicemapper/src/lib.rs
index 0170795..868ac5a 100644
--- a/libs/devicemapper/src/lib.rs
+++ b/libs/devicemapper/src/lib.rs
@@ -233,13 +233,13 @@
 }
 
 #[cfg(test)]
-ignorabletest::test_main!();
+rdroidtest::test_main!();
 
 #[cfg(test)]
 mod tests {
     use super::*;
     use crypt::{CipherType, DmCryptTargetBuilder};
-    use ignorabletest::test;
+    use rdroidtest::test;
     use rustutils::system_properties;
     use std::fs::{read, File, OpenOptions};
     use std::io::Write;
diff --git a/libs/hyp/src/error.rs b/libs/hyp/src/error.rs
index b8498ca..3fdad70 100644
--- a/libs/hyp/src/error.rs
+++ b/libs/hyp/src/error.rs
@@ -26,7 +26,7 @@
 #[derive(Debug, Clone)]
 pub enum Error {
     /// MMIO guard is not supported.
-    MmioGuardNotsupported,
+    MmioGuardNotSupported,
     /// Failed to invoke a certain KVM HVC function.
     KvmError(KvmError, u32),
     /// Failed to invoke GenieZone HVC function.
@@ -40,7 +40,7 @@
 impl fmt::Display for Error {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         match self {
-            Self::MmioGuardNotsupported => write!(f, "MMIO guard is not supported"),
+            Self::MmioGuardNotSupported => write!(f, "MMIO guard is not supported"),
             Self::KvmError(e, function_id) => {
                 write!(f, "Failed to invoke the HVC function with function ID {function_id}: {e}")
             }
diff --git a/libs/hyp/src/hypervisor/common.rs b/libs/hyp/src/hypervisor/common.rs
index 7c030a1..70fdd0a 100644
--- a/libs/hyp/src/hypervisor/common.rs
+++ b/libs/hyp/src/hypervisor/common.rs
@@ -14,7 +14,7 @@
 
 //! This module regroups some common traits shared by all the hypervisors.
 
-use crate::error::Result;
+use crate::error::{Error, Result};
 use crate::util::SIZE_4KB;
 
 /// Expected MMIO guard granule size, validated during MMIO guard initialization.
@@ -34,10 +34,9 @@
 }
 
 pub trait MmioGuardedHypervisor {
-    /// Initializes the hypervisor by enrolling a MMIO guard and checking the memory granule size.
-    /// By enrolling, all MMIO will be blocked unless allow-listed with `mmio_guard_map`.
-    /// Protected VMs are auto-enrolled.
-    fn init(&self) -> Result<()>;
+    /// Enrolls with the MMIO guard so that all MMIO will be blocked unless allow-listed with
+    /// `MmioGuardedHypervisor::map`.
+    fn enroll(&self) -> Result<()>;
 
     /// Maps a page containing the given memory address to the hypervisor MMIO guard.
     /// The page size corresponds to the MMIO guard granule size.
@@ -46,6 +45,18 @@
     /// Unmaps a page containing the given memory address from the hypervisor MMIO guard.
     /// The page size corresponds to the MMIO guard granule size.
     fn unmap(&self, addr: usize) -> Result<()>;
+
+    /// Returns the MMIO guard granule size in bytes.
+    fn granule(&self) -> Result<usize>;
+
+    // TODO(ptosi): Fully move granule validation to client code.
+    /// Validates the MMIO guard granule size.
+    fn validate_granule(&self) -> Result<()> {
+        match self.granule()? {
+            MMIO_GUARD_GRANULE_SIZE => Ok(()),
+            granule => Err(Error::UnsupportedMmioGuardGranule(granule)),
+        }
+    }
 }
 
 pub trait MemSharingHypervisor {
diff --git a/libs/hyp/src/hypervisor/geniezone.rs b/libs/hyp/src/hypervisor/geniezone.rs
index 24eb89e..ad18e17 100644
--- a/libs/hyp/src/hypervisor/geniezone.rs
+++ b/libs/hyp/src/hypervisor/geniezone.rs
@@ -14,9 +14,7 @@
 
 //! Wrappers around calls to the GenieZone hypervisor.
 
-use super::common::{
-    Hypervisor, MemSharingHypervisor, MmioGuardedHypervisor, MMIO_GUARD_GRANULE_SIZE,
-};
+use super::common::{Hypervisor, MemSharingHypervisor, MmioGuardedHypervisor};
 use crate::error::{Error, Result};
 use crate::util::page_address;
 use core::fmt::{self, Display, Formatter};
@@ -96,13 +94,15 @@
 }
 
 impl MmioGuardedHypervisor for GeniezoneHypervisor {
-    fn init(&self) -> Result<()> {
-        mmio_guard_enroll()?;
-        let mmio_granule = mmio_guard_granule()?;
-        if mmio_granule != MMIO_GUARD_GRANULE_SIZE {
-            return Err(Error::UnsupportedMmioGuardGranule(mmio_granule));
+    fn enroll(&self) -> Result<()> {
+        let args = [0u64; 17];
+        match success_or_error_64(hvc64(VENDOR_HYP_GZVM_MMIO_GUARD_ENROLL_FUNC_ID, args)[0]) {
+            Ok(()) => Ok(()),
+            Err(GeniezoneError::NotSupported) | Err(GeniezoneError::NotRequired) => {
+                Err(Error::MmioGuardNotSupported)
+            }
+            Err(e) => Err(Error::GeniezoneError(e, VENDOR_HYP_GZVM_MMIO_GUARD_ENROLL_FUNC_ID)),
         }
-        Ok(())
     }
 
     fn map(&self, addr: usize) -> Result<()> {
@@ -118,6 +118,12 @@
 
         checked_hvc64_expect_zero(VENDOR_HYP_GZVM_MMIO_GUARD_UNMAP_FUNC_ID, args)
     }
+
+    fn granule(&self) -> Result<usize> {
+        let args = [0u64; 17];
+        let granule = checked_hvc64(VENDOR_HYP_GZVM_MMIO_GUARD_INFO_FUNC_ID, args)?;
+        Ok(granule.try_into().unwrap())
+    }
 }
 
 impl MemSharingHypervisor for GeniezoneHypervisor {
@@ -142,23 +148,6 @@
     }
 }
 
-fn mmio_guard_granule() -> Result<usize> {
-    let args = [0u64; 17];
-
-    let granule = checked_hvc64(VENDOR_HYP_GZVM_MMIO_GUARD_INFO_FUNC_ID, args)?;
-    Ok(granule.try_into().unwrap())
-}
-
-fn mmio_guard_enroll() -> Result<()> {
-    let args = [0u64; 17];
-    match success_or_error_64(hvc64(VENDOR_HYP_GZVM_MMIO_GUARD_ENROLL_FUNC_ID, args)[0]) {
-        Ok(_) => Ok(()),
-        Err(GeniezoneError::NotSupported) => Err(Error::MmioGuardNotsupported),
-        Err(GeniezoneError::NotRequired) => Err(Error::MmioGuardNotsupported),
-        Err(e) => Err(Error::GeniezoneError(e, VENDOR_HYP_GZVM_MMIO_GUARD_ENROLL_FUNC_ID)),
-    }
-}
-
 fn checked_hvc64_expect_zero(function: u32, args: [u64; 17]) -> Result<()> {
     success_or_error_64(hvc64(function, args)[0]).map_err(|e| Error::GeniezoneError(e, function))
 }
diff --git a/libs/hyp/src/hypervisor/kvm.rs b/libs/hyp/src/hypervisor/kvm.rs
index a95b8de..5835346 100644
--- a/libs/hyp/src/hypervisor/kvm.rs
+++ b/libs/hyp/src/hypervisor/kvm.rs
@@ -14,9 +14,7 @@
 
 //! Wrappers around calls to the KVM hypervisor.
 
-use super::common::{
-    Hypervisor, MemSharingHypervisor, MmioGuardedHypervisor, MMIO_GUARD_GRANULE_SIZE,
-};
+use super::common::{Hypervisor, MemSharingHypervisor, MmioGuardedHypervisor};
 use crate::error::{Error, Result};
 use crate::util::page_address;
 use core::fmt::{self, Display, Formatter};
@@ -95,13 +93,13 @@
 }
 
 impl MmioGuardedHypervisor for ProtectedKvmHypervisor {
-    fn init(&self) -> Result<()> {
-        mmio_guard_enroll()?;
-        let mmio_granule = mmio_guard_granule()?;
-        if mmio_granule != MMIO_GUARD_GRANULE_SIZE {
-            return Err(Error::UnsupportedMmioGuardGranule(mmio_granule));
+    fn enroll(&self) -> Result<()> {
+        let args = [0u64; 17];
+        match success_or_error_64(hvc64(VENDOR_HYP_KVM_MMIO_GUARD_ENROLL_FUNC_ID, args)[0]) {
+            Ok(()) => Ok(()),
+            Err(KvmError::NotSupported) => Err(Error::MmioGuardNotSupported),
+            Err(e) => Err(Error::KvmError(e, VENDOR_HYP_KVM_MMIO_GUARD_ENROLL_FUNC_ID)),
         }
-        Ok(())
     }
 
     fn map(&self, addr: usize) -> Result<()> {
@@ -125,6 +123,12 @@
             Err(e) => Err(Error::KvmError(e, VENDOR_HYP_KVM_MMIO_GUARD_UNMAP_FUNC_ID)),
         }
     }
+
+    fn granule(&self) -> Result<usize> {
+        let args = [0u64; 17];
+        let granule = checked_hvc64(VENDOR_HYP_KVM_MMIO_GUARD_INFO_FUNC_ID, args)?;
+        Ok(granule.try_into().unwrap())
+    }
 }
 
 impl MemSharingHypervisor for ProtectedKvmHypervisor {
@@ -149,22 +153,6 @@
     }
 }
 
-fn mmio_guard_granule() -> Result<usize> {
-    let args = [0u64; 17];
-
-    let granule = checked_hvc64(VENDOR_HYP_KVM_MMIO_GUARD_INFO_FUNC_ID, args)?;
-    Ok(granule.try_into().unwrap())
-}
-
-fn mmio_guard_enroll() -> Result<()> {
-    let args = [0u64; 17];
-    match success_or_error_64(hvc64(VENDOR_HYP_KVM_MMIO_GUARD_ENROLL_FUNC_ID, args)[0]) {
-        Ok(_) => Ok(()),
-        Err(KvmError::NotSupported) => Err(Error::MmioGuardNotsupported),
-        Err(e) => Err(Error::KvmError(e, VENDOR_HYP_KVM_MMIO_GUARD_ENROLL_FUNC_ID)),
-    }
-}
-
 fn checked_hvc64_expect_zero(function: u32, args: [u64; 17]) -> Result<()> {
     success_or_error_64(hvc64(function, args)[0]).map_err(|e| Error::KvmError(e, function))
 }
diff --git a/libs/hyp/src/hypervisor/mod.rs b/libs/hyp/src/hypervisor/mod.rs
index bc9e406..309f967 100644
--- a/libs/hyp/src/hypervisor/mod.rs
+++ b/libs/hyp/src/hypervisor/mod.rs
@@ -60,8 +60,10 @@
             GeniezoneHypervisor::UUID => Ok(HypervisorBackend::Geniezone),
             GunyahHypervisor::UUID => Ok(HypervisorBackend::Gunyah),
             RegularKvmHypervisor::UUID => {
-                // Protected KVM has the same UUID so differentiate based on MEM_SHARE.
-                match ProtectedKvmHypervisor.as_mem_sharer().unwrap().granule() {
+                // Protected KVM has the same UUID as "regular" KVM so issue an HVC that is assumed
+                // to only be supported by pKVM: if it returns SUCCESS, deduce that this is pKVM
+                // and if it returns NOT_SUPPORTED assume that it is "regular" KVM.
+                match ProtectedKvmHypervisor.as_mmio_guard().unwrap().granule() {
                     Ok(_) => Ok(HypervisorBackend::ProtectedKvm),
                     Err(Error::KvmError(KvmError::NotSupported, _)) => {
                         Ok(HypervisorBackend::RegularKvm)
@@ -101,7 +103,7 @@
 }
 
 fn detect_hypervisor() -> HypervisorBackend {
-    query_vendor_hyp_call_uid().try_into().expect("Unknown hypervisor")
+    query_vendor_hyp_call_uid().try_into().expect("Failed to detect hypervisor")
 }
 
 /// Gets the hypervisor singleton.
diff --git a/libs/ignorabletest/Android.bp b/libs/ignorabletest/Android.bp
deleted file mode 100644
index 4ae89af..0000000
--- a/libs/ignorabletest/Android.bp
+++ /dev/null
@@ -1,36 +0,0 @@
-rust_library {
-    name: "libignorabletest",
-    host_supported: true,
-    crate_name: "ignorabletest",
-    cargo_env_compat: true,
-    cargo_pkg_version: "0.1.0",
-    srcs: ["src/lib.rs"],
-    edition: "2021",
-    rustlibs: [
-        "liblibtest_mimic",
-        "liblinkme",
-        "liblog_rust",
-        "liblogger",
-    ],
-    proc_macros: ["libpaste"],
-    apex_available: [
-        "//apex_available:platform",
-        "//apex_available:anyapex",
-    ],
-}
-
-rust_defaults {
-    name: "ignorabletest.defaults",
-    test_harness: false,
-    cfgs: ["test"],
-    rustlibs: [
-        "libignorabletest",
-        "liblinkme",
-    ],
-    // Without this flag we get linker errors saying to add it. See
-    // https://github.com/dtolnay/linkme/issues/49 and related issues.
-    ld_flags: [
-        "-z",
-        "nostart-stop-gc",
-    ],
-}
diff --git a/libs/ignorabletest/README.md b/libs/ignorabletest/README.md
deleted file mode 100644
index 77140bd..0000000
--- a/libs/ignorabletest/README.md
+++ /dev/null
@@ -1,66 +0,0 @@
-# ignorabletest
-
-This is a custom Rust test harness which allows tests to be ignored at runtime based on arbitrary
-criteria. The built-in Rust test harness only allows tests to be ignored at compile time, but this
-is often not enough on Android, where we want to ignore tests based on system properties or other
-characteristics of the device on which the test is being run, which are not known at build time.
-
-## Usage
-
-Unfortunately without the built-in support that rustc provides to the standard test harness, this
-one is slightly more cumbersome to use. Firstly, add it to the `rust_test` build rule in your
-`Android.bp` by adding the defaults provided:
-
-```soong
-rust_test {
-    name: "mycrate.test",
-    defaults: ["ignorabletest.defaults"],
-    // ...
-}
-```
-
-If you are testing a binary that has a `main` function, you'll need to remove it from the test
-build:
-
-```rust
-#[cfg(not(test))]
-fn main() {
-    // ...
-}
-```
-
-(If you're testing a library or anything else which doesn't have a `main` function, you don't need
-to worry about this.)
-
-Each test case should be marked with the `ignorabletest::test!` macro, rather than the standard
-`#[test]` attribute:
-
-```rust
-use ignorabletest::test;
-
-test!(one_plus_one);
-fn one_plus_one {
-    assert_eq!(1 + 1, 2);
-}
-```
-
-To ignore a test, you can add an `ignore_if` clause with a boolean expression:
-
-```rust
-use ignorabletest::test;
-
-test!(clap_hands, ignore_if: !feeling_happy());
-fn clap_hands {
-    assert!(HANDS.clap().is_ok());
-}
-```
-
-Somewhere in your main module, you need to use the `test_main` macro to generate an entry point for
-the test harness:
-
-```rust
-#[cfg(test)]
-ignorabletest::test_main!();
-```
-
-You can then run your tests as usual with `atest`.
diff --git a/libs/ignorabletest/src/lib.rs b/libs/ignorabletest/src/lib.rs
deleted file mode 100644
index c7243e6..0000000
--- a/libs/ignorabletest/src/lib.rs
+++ /dev/null
@@ -1,57 +0,0 @@
-//! Test harness which supports ignoring tests at runtime.
-
-pub mod runner;
-
-#[doc(hidden)]
-pub use libtest_mimic as _libtest_mimic;
-#[doc(hidden)]
-pub use linkme as _linkme;
-#[doc(hidden)]
-pub use paste as _paste;
-
-/// Macro to generate the main function for the test harness.
-#[macro_export]
-macro_rules! test_main {
-    () => {
-        #[cfg(test)]
-        fn main() {
-            ignorabletest::runner::main()
-        }
-    };
-}
-
-/// Macro to generate a wrapper function for a single test.
-///
-/// # Usage
-///
-/// ```
-/// test!(test_string_equality);
-/// fn test_string_equality() {
-///   assert_eq!("", "");
-/// }
-/// ```
-#[macro_export]
-macro_rules! test {
-    ($test_name:ident) => {
-        $crate::_paste::paste!(
-            #[$crate::_linkme::distributed_slice($crate::runner::IGNORABLETEST_TESTS)]
-            fn [< __test_ $test_name >]() -> $crate::_libtest_mimic::Trial {
-                $crate::_libtest_mimic::Trial::test(
-                    ::std::stringify!($test_name),
-                    move || ignorabletest::runner::run($test_name),
-                )
-            }
-        );
-    };
-    ($test_name:ident, ignore_if: $ignore_expr:expr) => {
-        $crate::_paste::paste!(
-            #[$crate::_linkme::distributed_slice($crate::runner::IGNORABLETEST_TESTS)]
-            fn [< __test_ $test_name >]() -> $crate::_libtest_mimic::Trial {
-                $crate::_libtest_mimic::Trial::test(
-                    ::std::stringify!($test_name),
-                    move || ignorabletest::runner::run($test_name),
-                ).with_ignored_flag($ignore_expr)
-            }
-        );
-    };
-}
diff --git a/libs/ignorabletest/src/runner.rs b/libs/ignorabletest/src/runner.rs
deleted file mode 100644
index fdac406..0000000
--- a/libs/ignorabletest/src/runner.rs
+++ /dev/null
@@ -1,29 +0,0 @@
-//! Test runner.
-
-use core::ops::{Deref, FnOnce};
-use libtest_mimic::{Arguments, Failed, Trial};
-use linkme::distributed_slice;
-use log::Level;
-use std::env;
-
-/// Command-line arguments to ignore, because they are not supported by libtest-mimic.
-const IGNORED_ARGS: [&str; 2] = ["-Zunstable-options", "--report-time"];
-
-/// The collection of all tests to run.
-#[doc(hidden)]
-#[distributed_slice]
-pub static IGNORABLETEST_TESTS: [fn() -> Trial] = [..];
-
-/// Runs all tests.
-pub fn main() {
-    logger::init(logger::Config::default().with_min_level(Level::Debug));
-    let args = Arguments::from_iter(env::args().filter(|arg| !IGNORED_ARGS.contains(&arg.deref())));
-    let tests = IGNORABLETEST_TESTS.iter().map(|test| test()).collect();
-    libtest_mimic::run(&args, tests).exit();
-}
-
-/// Runs the given test.
-pub fn run(test: impl FnOnce()) -> Result<(), Failed> {
-    test();
-    Ok(())
-}
diff --git a/microdroid/kdump/Android.bp b/microdroid/kdump/Android.bp
index cc681a7..b9a18fe 100644
--- a/microdroid/kdump/Android.bp
+++ b/microdroid/kdump/Android.bp
@@ -18,6 +18,9 @@
     static_executable: true,
     installable: false,
     compile_multilib: "64",
+    sanitize: {
+        hwaddress: false, // HWASAN setup fails when run as init process
+    },
 }
 
 android_filesystem {
diff --git a/pvmfw/platform.dts b/pvmfw/platform.dts
index 74439d9..cb8e30d 100644
--- a/pvmfw/platform.dts
+++ b/pvmfw/platform.dts
@@ -225,6 +225,8 @@
 			0x3000 0x0 0x0 1 &intc 0 0 GIC_SPI (IRQ_BASE + 5) IRQ_TYPE_LEVEL_HIGH
 			0x3800 0x0 0x0 1 &intc 0 0 GIC_SPI (IRQ_BASE + 6) IRQ_TYPE_LEVEL_HIGH
 			0x4000 0x0 0x0 1 &intc 0 0 GIC_SPI (IRQ_BASE + 7) IRQ_TYPE_LEVEL_HIGH
+			0x4800 0x0 0x0 1 &intc 0 0 GIC_SPI (IRQ_BASE + 8) IRQ_TYPE_LEVEL_HIGH
+			0x5000 0x0 0x0 1 &intc 0 0 GIC_SPI (IRQ_BASE + 9) IRQ_TYPE_LEVEL_HIGH
 		>;
 		interrupt-map-mask = <0xf800 0x0 0x0 0x7
 				      0xf800 0x0 0x0 0x7
@@ -233,6 +235,8 @@
 				      0xf800 0x0 0x0 0x7
 				      0xf800 0x0 0x0 0x7
 				      0xf800 0x0 0x0 0x7
+				      0xf800 0x0 0x0 0x7
+				      0xf800 0x0 0x0 0x7
 				      0xf800 0x0 0x0 0x7>;
 	};
 
diff --git a/pvmfw/src/fdt.rs b/pvmfw/src/fdt.rs
index 4e41331..244b192 100644
--- a/pvmfw/src/fdt.rs
+++ b/pvmfw/src/fdt.rs
@@ -209,7 +209,7 @@
 impl PciInfo {
     const IRQ_MASK_CELLS: usize = 4;
     const IRQ_MAP_CELLS: usize = 10;
-    const MAX_IRQS: usize = 8;
+    const MAX_IRQS: usize = 10;
 }
 
 type PciAddrRange = AddressRange<(u32, u64), u64, u64>;
@@ -248,14 +248,22 @@
     let range1 = ranges.next().ok_or(FdtError::NotFound)?;
 
     let irq_masks = node.getprop_cells(cstr!("interrupt-map-mask"))?.ok_or(FdtError::NotFound)?;
-    let irq_masks = CellChunkIterator::<{ PciInfo::IRQ_MASK_CELLS }>::new(irq_masks);
-    let irq_masks: ArrayVec<[PciIrqMask; PciInfo::MAX_IRQS]> =
-        irq_masks.take(PciInfo::MAX_IRQS).collect();
+    let mut chunks = CellChunkIterator::<{ PciInfo::IRQ_MASK_CELLS }>::new(irq_masks);
+    let irq_masks = (&mut chunks).take(PciInfo::MAX_IRQS).collect();
+
+    if chunks.next().is_some() {
+        warn!("Input DT has more than {} PCI entries!", PciInfo::MAX_IRQS);
+        return Err(FdtError::NoSpace);
+    }
 
     let irq_maps = node.getprop_cells(cstr!("interrupt-map"))?.ok_or(FdtError::NotFound)?;
-    let irq_maps = CellChunkIterator::<{ PciInfo::IRQ_MAP_CELLS }>::new(irq_maps);
-    let irq_maps: ArrayVec<[PciIrqMap; PciInfo::MAX_IRQS]> =
-        irq_maps.take(PciInfo::MAX_IRQS).collect();
+    let mut chunks = CellChunkIterator::<{ PciInfo::IRQ_MAP_CELLS }>::new(irq_maps);
+    let irq_maps = (&mut chunks).take(PciInfo::MAX_IRQS).collect();
+
+    if chunks.next().is_some() {
+        warn!("Input DT has more than {} PCI entries!", PciInfo::MAX_IRQS);
+        return Err(FdtError::NoSpace);
+    }
 
     Ok(PciInfo { ranges: [range0, range1], irq_masks, irq_maps })
 }
diff --git a/tests/benchmark/Android.bp b/tests/benchmark/Android.bp
index 9c512bf..90ba575 100644
--- a/tests/benchmark/Android.bp
+++ b/tests/benchmark/Android.bp
@@ -26,6 +26,7 @@
     sdk_version: "test_current",
     use_embedded_native_libs: true,
     compile_multilib: "64",
+    required: ["perf-setup"],
     host_required: ["MicrodroidTestPreparer"],
 }
 
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 aed28a8..625f26a 100644
--- a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
+++ b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
@@ -78,6 +78,7 @@
     private static final String TAG = "MicrodroidBenchmarks";
     private static final String METRIC_NAME_PREFIX = getMetricPrefix() + "microdroid/";
     private static final int IO_TEST_TRIAL_COUNT = 5;
+    private static final int TEST_TRIAL_COUNT = 5;
     private static final long ONE_MEBI = 1024 * 1024;
 
     @Rule public Timeout globalTimeout = Timeout.seconds(300);
@@ -767,4 +768,35 @@
         }
         reportMetrics(requestLatencies, "latency/vsock", "us");
     }
+
+    @Test
+    public void testVmKillTime() throws Exception {
+        VirtualMachineConfig config =
+                newVmConfigBuilder()
+                        .setPayloadConfigPath("assets/vm_config_io.json")
+                        .setDebugLevel(DEBUG_LEVEL_NONE)
+                        .build();
+        List<Double> vmKillTime = new ArrayList<>(TEST_TRIAL_COUNT);
+
+        for (int i = 0; i < TEST_TRIAL_COUNT; ++i) {
+            VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_kill_time" + i, config);
+            VmEventListener listener =
+                    new VmEventListener() {
+                        @Override
+                        public void onPayloadReady(VirtualMachine vm) {
+                            long start = System.nanoTime();
+                            try {
+                                vm.stop();
+                            } catch (Exception e) {
+                                Log.e(TAG, "Error in vm.stop():" + e);
+                                throw new RuntimeException(e);
+                            }
+                            vmKillTime.add((double) (System.nanoTime() - start) / NANO_TO_MICRO);
+                            super.onPayloadReady(vm);
+                        }
+                    };
+            listener.runToFinish(TAG, vm);
+        }
+        reportMetrics(vmKillTime, "vm_kill_time", "microsecond");
+    }
 }
diff --git a/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java b/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
index 8c6218c..f98d1d9 100644
--- a/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
+++ b/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
@@ -60,9 +60,6 @@
     // Files that define the "test" instance of CompOS
     private static final String COMPOS_TEST_ROOT = "/data/misc/apexdata/com.android.compos/test/";
 
-    private static final String SYSTEM_SERVER_COMPILER_FILTER_PROP_NAME =
-            "dalvik.vm.systemservercompilerfilter";
-
     private static final String BOOTLOADER_TIME_PROP_NAME = "ro.boot.boottime";
     private static final String BOOTLOADER_PREFIX = "bootloader-";
     private static final String BOOTLOADER_TIME = "bootloader_time";
@@ -74,7 +71,6 @@
     private static final int COMPILE_STAGED_APEX_RETRY_INTERVAL_MS = 10 * 1000;
     private static final int COMPILE_STAGED_APEX_TIMEOUT_SEC = 540;
     private static final int BOOT_COMPLETE_TIMEOUT_MS = 10 * 60 * 1000;
-    private static final double NANOS_IN_SEC = 1_000_000_000.0;
     private static final int ROUND_COUNT = 5;
     private static final int ROUND_IGNORE_STARTUP_TIME = 3;
     private static final String APK_NAME = "MicrodroidTestApp.apk";
@@ -262,32 +258,34 @@
                         .memoryMib(vm_mem_mb)
                         .cpuTopology("match_host")
                         .build(device);
-        microdroidDevice.waitForBootComplete(30000);
-        microdroidDevice.enableAdbRoot();
-
-        CommandRunner microdroid = new CommandRunner(microdroidDevice);
-
-        microdroid.run("mkdir -p /mnt/ramdisk && chmod 777 /mnt/ramdisk");
-        microdroid.run("mount -t tmpfs -o size=32G tmpfs /mnt/ramdisk");
-
-        // Allocate memory for the VM until it fails and make sure that we touch
-        // the allocated memory in the guest to be able to create stage2 fragmentation.
         try {
-            microdroid.tryRun(
-                    String.format(
-                            "cd /mnt/ramdisk && truncate -s %dM sprayMemory"
-                                    + " && dd if=/dev/zero of=sprayMemory bs=1MB count=%d",
-                            vm_mem_mb, vm_mem_mb));
-        } catch (Exception ex) {
-        }
+            microdroidDevice.waitForBootComplete(30000);
+            microdroidDevice.enableAdbRoot();
 
-        // Run the app during the VM run and collect cold startup time.
-        for (int i = 0; i < ROUND_COUNT; i++) {
-            AmStartupTimeCmdParser duringVmStartApp = getColdRunStartupTimes(android, pkgName);
-            metricColector.addStartupTimeMetricDuringVmRun(duringVmStartApp);
-        }
+            CommandRunner microdroid = new CommandRunner(microdroidDevice);
 
-        device.shutdownMicrodroid(microdroidDevice);
+            microdroid.run("mkdir -p /mnt/ramdisk && chmod 777 /mnt/ramdisk");
+            microdroid.run("mount -t tmpfs -o size=32G tmpfs /mnt/ramdisk");
+
+            // Allocate memory for the VM until it fails and make sure that we touch
+            // the allocated memory in the guest to be able to create stage2 fragmentation.
+            try {
+                microdroid.tryRun(
+                        String.format(
+                                "cd /mnt/ramdisk && truncate -s %dM sprayMemory"
+                                        + " && dd if=/dev/zero of=sprayMemory bs=1MB count=%d",
+                                vm_mem_mb, vm_mem_mb));
+            } catch (Exception expected) {
+            }
+
+            // Run the app during the VM run and collect cold startup time.
+            for (int i = 0; i < ROUND_COUNT; i++) {
+                AmStartupTimeCmdParser duringVmStartApp = getColdRunStartupTimes(android, pkgName);
+                metricColector.addStartupTimeMetricDuringVmRun(duringVmStartApp);
+            }
+        } finally {
+            device.shutdownMicrodroid(microdroidDevice);
+        }
 
         // Run the app after the VM run and collect cold startup time.
         for (int i = 0; i < ROUND_COUNT; i++) {
@@ -304,12 +302,12 @@
             String[] lines = startAppLog.split("[\r\n]+");
             mTotalTime = mWaitTime = 0;
 
-            for (int i = 0; i < lines.length; i++) {
-                if (lines[i].contains("TotalTime:")) {
-                    mTotalTime = Integer.parseInt(lines[i].replaceAll("\\D+", ""));
+            for (String line : lines) {
+                if (line.contains("TotalTime:")) {
+                    mTotalTime = Integer.parseInt(line.replaceAll("\\D+", ""));
                 }
-                if (lines[i].contains("WaitTime:")) {
-                    mWaitTime = Integer.parseInt(lines[i].replaceAll("\\D+", ""));
+                if (line.contains("WaitTime:")) {
+                    mWaitTime = Integer.parseInt(line.replaceAll("\\D+", ""));
                 }
             }
         }
@@ -365,9 +363,9 @@
         String content = android.runForResult("cat /proc/meminfo").getStdout().trim();
         String[] lines = content.split("[\r\n]+");
 
-        for (int i = 0; i < lines.length; i++) {
-            if (lines[i].contains("MemFree:")) {
-                freeMemory = Integer.parseInt(lines[i].replaceAll("\\D+", "")) / 1024;
+        for (String line : lines) {
+            if (line.contains("MemFree:")) {
+                freeMemory = Integer.parseInt(line.replaceAll("\\D+", "")) / 1024;
                 return freeMemory;
             }
         }
@@ -416,7 +414,7 @@
 
         CommandRunner android = new CommandRunner(getDevice());
         String result = android.run("dmesg");
-        Pattern pattern = Pattern.compile("\\[(.*)\\].*sys.boot_completed=1.*");
+        Pattern pattern = Pattern.compile("\\[(.*)].*sys.boot_completed=1.*");
         for (String line : result.split("[\r\n]+")) {
             Matcher matcher = pattern.matcher(line);
             if (matcher.find()) {
@@ -568,7 +566,7 @@
     private void compileStagedApex(int timeoutSec) throws Exception {
 
         long timeStart = System.currentTimeMillis();
-        long timeEnd = timeStart + timeoutSec * 1000;
+        long timeEnd = timeStart + timeoutSec * 1000L;
 
         while (true) {
 
@@ -599,7 +597,7 @@
     private void reInstallApex(int timeoutSec) throws Exception {
 
         long timeStart = System.currentTimeMillis();
-        long timeEnd = timeStart + timeoutSec * 1000;
+        long timeEnd = timeStart + timeoutSec * 1000L;
 
         while (true) {
 
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index deaf08a..092325e 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -42,7 +42,6 @@
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.device.TestDevice;
-import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestMetrics;
 import com.android.tradefed.util.CommandResult;
@@ -62,12 +61,8 @@
 import org.xml.sax.Attributes;
 import org.xml.sax.helpers.DefaultHandler;
 
-import java.io.BufferedReader;
 import java.io.ByteArrayInputStream;
 import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
 import java.io.PipedInputStream;
 import java.io.PipedOutputStream;
 import java.util.ArrayList;
@@ -75,7 +70,6 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
-import java.util.Optional;
 import java.util.concurrent.Callable;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Function;
@@ -95,15 +89,11 @@
 
     private static final int BOOT_COMPLETE_TIMEOUT = 30000; // 30 seconds
 
-    private static final Pattern sCIDPattern = Pattern.compile("with CID (\\d+)");
-
     private static class VmInfo {
         final Process mProcess;
-        final String mCid;
 
-        VmInfo(Process process, String cid) {
+        VmInfo(Process process) {
             mProcess = process;
-            mCid = cid;
         }
     }
 
@@ -376,44 +366,14 @@
 
         PipedInputStream pis = new PipedInputStream();
         Process process = RunUtil.getDefault().runCmdInBackground(args, new PipedOutputStream(pis));
-        return new VmInfo(process, extractCidFrom(pis));
-    }
-
-    private static Optional<String> tryExtractCidFrom(String str) {
-        Matcher matcher = sCIDPattern.matcher(str);
-        if (matcher.find()) {
-            return Optional.of(matcher.group(1));
-        }
-        return Optional.empty();
-    }
-
-    private static String extractCidFrom(InputStream input) throws IOException {
-        String cid = null;
-        String line;
-        try (BufferedReader out = new BufferedReader(new InputStreamReader(input))) {
-            while ((line = out.readLine()) != null) {
-                CLog.i("VM output: " + line);
-                Optional<String> result = tryExtractCidFrom(line);
-                if (result.isPresent()) {
-                    cid = result.get();
-                    break;
-                }
-            }
-        }
-        assertWithMessage("The output does not contain the expected pattern for CID.")
-                .that(cid)
-                .isNotNull();
-        return cid;
+        return new VmInfo(process);
     }
 
     @Test
     @CddTest(requirements = {"9.17/C-2-1", "9.17/C-2-2", "9.17/C-2-6"})
     public void protectedVmRunsPvmfw() throws Exception {
         // Arrange
-        boolean protectedVm = true;
-        assumeTrue(
-                "Skip if protected VMs are not supported",
-                getAndroidDevice().supportsMicrodroid(protectedVm));
+        assumeProtectedVmSupported();
         final String configPath = "assets/vm_config_apex.json";
 
         // Act
@@ -422,7 +382,7 @@
                         .debugLevel("full")
                         .memoryMib(minMemorySize())
                         .cpuTopology("match_host")
-                        .protectedVm(protectedVm)
+                        .protectedVm(true)
                         .build(getAndroidDevice());
 
         // Assert
@@ -441,16 +401,16 @@
     @CddTest(requirements = {"9.17/C-2-1", "9.17/C-2-2", "9.17/C-2-6"})
     public void protectedVmWithImageSignedWithDifferentKeyRunsPvmfw() throws Exception {
         // Arrange
-        boolean protectedVm = true;
-        assumeTrue(
-                "Skip if protected VMs are not supported",
-                getAndroidDevice().supportsMicrodroid(protectedVm));
+        assumeProtectedVmSupported();
         File key = findTestFile("test.com.android.virt.pem");
 
         // Act
         VmInfo vmInfo =
                 runMicrodroidWithResignedImages(
-                        key, /*keyOverrides=*/ Map.of(), protectedVm, /*updateBootconfigs=*/ true);
+                        key,
+                        /*keyOverrides=*/ Map.of(),
+                        /*isProtected=*/ true,
+                        /*updateBootconfigs=*/ true);
 
         // Assert
         vmInfo.mProcess.waitFor(5L, TimeUnit.SECONDS);
@@ -465,6 +425,7 @@
     @CddTest(requirements = {"9.17/C-2-2", "9.17/C-2-6"})
     public void testBootSucceedsWhenNonProtectedVmStartsWithImagesSignedWithDifferentKey()
             throws Exception {
+        assumeNonProtectedVmSupported();
         File key = findTestFile("test.com.android.virt.pem");
         Map<String, File> keyOverrides = Map.of();
         VmInfo vmInfo =
@@ -481,6 +442,8 @@
     @Test
     @CddTest(requirements = {"9.17/C-2-2", "9.17/C-2-6"})
     public void testBootFailsWhenVbMetaDigestDoesNotMatchBootconfig() throws Exception {
+        // protectedVmWithImageSignedWithDifferentKeyRunsPvmfw() is the protected case.
+        assumeNonProtectedVmSupported();
         // Sign everything with key1 except vbmeta
         File key = findTestFile("test.com.android.virt.pem");
         // To be able to stop it, it should be a daemon.
@@ -558,10 +521,22 @@
     }
 
     @Test
-    public void testTombstonesAreGeneratedUponUserspaceCrash() throws Exception {
+    public void testTombstonesAreGeneratedUponUserspaceCrashOnNonPvm() throws Exception {
+        assumeNonProtectedVmSupported();
+        testTombstonesAreGeneratedUponUserspaceCrash(false);
+    }
+
+    @Test
+    public void testTombstonesAreGeneratedUponUserspaceCrashOnPvm() throws Exception {
+        assumeProtectedVmSupported();
+        testTombstonesAreGeneratedUponUserspaceCrash(true);
+    }
+
+    private void testTombstonesAreGeneratedUponUserspaceCrash(boolean protectedVm)
+            throws Exception {
         assertThat(
                         isTombstoneGeneratedWithCmd(
-                                false,
+                                protectedVm,
                                 "assets/vm_config.json",
                                 "kill",
                                 "-SIGSEGV",
@@ -570,10 +545,24 @@
     }
 
     @Test
-    public void testTombstonesAreNotGeneratedIfNotExportedUponUserspaceCrash() throws Exception {
+    public void testTombstonesAreNotGeneratedIfNotExportedUponUserspaceCrashOnNonPvm()
+            throws Exception {
+        assumeNonProtectedVmSupported();
+        testTombstonesAreNotGeneratedIfNotExportedUponUserspaceCrash(false);
+    }
+
+    @Test
+    public void testTombstonesAreNotGeneratedIfNotExportedUponUserspaceCrashOnPvm()
+            throws Exception {
+        assumeProtectedVmSupported();
+        testTombstonesAreNotGeneratedIfNotExportedUponUserspaceCrash(true);
+    }
+
+    private void testTombstonesAreNotGeneratedIfNotExportedUponUserspaceCrash(boolean protectedVm)
+            throws Exception {
         assertThat(
                         isTombstoneGeneratedWithCmd(
-                                false,
+                                protectedVm,
                                 "assets/vm_config_no_tombstone.json",
                                 "kill",
                                 "-SIGSEGV",
@@ -597,19 +586,18 @@
 
     @Test
     public void testTombstonesAreGeneratedUponKernelCrashOnNonPvm() throws Exception {
-        testTombstonesAreGeneratedUponKernelCrash(false);
+        assumeNonProtectedVmSupported();
+        testTombstonesAreGeneratedUponKernelCrash(/* protectedVm=*/ false);
     }
 
     @Test
     public void testTombstonesAreGeneratedUponKernelCrashOnPvm() throws Exception {
-        assumeTrue(
-                "Protected VMs are not supported",
-                getAndroidDevice().supportsMicrodroid(/*protectedVm=*/ true));
-        testTombstonesAreGeneratedUponKernelCrash(true);
+        assumeProtectedVmSupported();
+        testTombstonesAreGeneratedUponKernelCrash(/* protectedVm=*/ true);
     }
 
-    private boolean isTombstoneGeneratedWithVmRunApp(boolean debuggable, String... additionalArgs)
-            throws Exception {
+    private boolean isTombstoneGeneratedWithVmRunApp(
+            boolean protectedVm, boolean debuggable, String... additionalArgs) throws Exception {
         // we can't use microdroid builder as it wants ADB connection (debuggable)
         CommandRunner android = new CommandRunner(getDevice());
         String testStartTime = android.runWithTimeout(1000, "date", "'+%Y-%m-%d %H:%M:%S.%N'");
@@ -630,44 +618,114 @@
                                 apkPath,
                                 idsigPath,
                                 instanceImgPath));
+        if (protectedVm) {
+            cmd.add("--protected");
+        }
         Collections.addAll(cmd, additionalArgs);
 
         android.run(cmd.toArray(new String[0]));
         return isTombstoneReceivedFromHostLogcat(testStartTime);
     }
 
-    private boolean isTombstoneGeneratedWithCrashPayload(boolean debuggable) throws Exception {
+    private boolean isTombstoneGeneratedWithCrashPayload(boolean protectedVm, boolean debuggable)
+            throws Exception {
         return isTombstoneGeneratedWithVmRunApp(
-                debuggable, "--payload-binary-name", "MicrodroidCrashNativeLib.so");
+                protectedVm, debuggable, "--payload-binary-name", "MicrodroidCrashNativeLib.so");
     }
 
     @Test
-    public void testTombstonesAreGeneratedWithCrashPayload() throws Exception {
-        assertThat(isTombstoneGeneratedWithCrashPayload(true /* debuggable */)).isTrue();
+    public void testTombstonesAreGeneratedWithCrashPayloadOnPvm() throws Exception {
+        assumeProtectedVmSupported();
+        assertThat(
+                        isTombstoneGeneratedWithCrashPayload(
+                                /*protectedVm=*/ true, /*debuggable=*/ true))
+                .isTrue();
     }
 
     @Test
-    public void testTombstonesAreNotGeneratedWithCrashPayloadWhenNonDebuggable() throws Exception {
-        assertThat(isTombstoneGeneratedWithCrashPayload(false /* debuggable */)).isFalse();
+    public void testTombstonesAreGeneratedWithCrashPayloadOnNonPvm() throws Exception {
+        assumeNonProtectedVmSupported();
+        assertThat(
+                        isTombstoneGeneratedWithCrashPayload(
+                                /*protectedVm=*/ false, /*debuggable=*/ true))
+                .isTrue();
     }
 
-    private boolean isTombstoneGeneratedWithCrashConfig(boolean debuggable) throws Exception {
+    @Test
+    public void testTombstonesAreNotGeneratedWithCrashPayloadWhenNonDebuggableOnPvm()
+            throws Exception {
+        assumeProtectedVmSupported();
+        assertThat(
+                        isTombstoneGeneratedWithCrashPayload(
+                                /*protectedVm=*/ true, /*debuggable=*/ false))
+                .isFalse();
+    }
+
+    @Test
+    public void testTombstonesAreNotGeneratedWithCrashPayloadWhenNonDebuggableOnNonPvm()
+            throws Exception {
+        assumeNonProtectedVmSupported();
+        assertThat(
+                        isTombstoneGeneratedWithCrashPayload(
+                                /*protectedVm=*/ false, /*debuggable=*/ false))
+                .isFalse();
+    }
+
+    private boolean isTombstoneGeneratedWithCrashConfig(boolean protectedVm, boolean debuggable)
+            throws Exception {
         return isTombstoneGeneratedWithVmRunApp(
-                debuggable, "--config-path", "assets/vm_config_crash.json");
+                protectedVm, debuggable, "--config-path", "assets/vm_config_crash.json");
     }
 
     @Test
-    public void testTombstonesAreGeneratedWithCrashConfig() throws Exception {
-        assertThat(isTombstoneGeneratedWithCrashConfig(true /* debuggable */)).isTrue();
+    public void testTombstonesAreGeneratedWithCrashConfigOnPvm() throws Exception {
+        assumeProtectedVmSupported();
+        assertThat(isTombstoneGeneratedWithCrashConfig(/*protectedVm=*/ true, /*debuggable=*/ true))
+                .isTrue();
     }
 
     @Test
-    public void testTombstonesAreNotGeneratedWithCrashConfigWhenNonDebuggable() throws Exception {
-        assertThat(isTombstoneGeneratedWithCrashConfig(false /* debuggable */)).isFalse();
+    public void testTombstonesAreGeneratedWithCrashConfigOnNonPvm() throws Exception {
+        assumeNonProtectedVmSupported();
+        assertThat(
+                        isTombstoneGeneratedWithCrashConfig(
+                                /*protectedVm=*/ false, /*debuggable=*/ true))
+                .isTrue();
     }
 
     @Test
-    public void testTelemetryPushedAtoms() throws Exception {
+    public void testTombstonesAreNotGeneratedWithCrashConfigWhenNonDebuggableOnPvm()
+            throws Exception {
+        assumeProtectedVmSupported();
+        assertThat(
+                        isTombstoneGeneratedWithCrashConfig(
+                                /*protectedVm=*/ true, /*debuggable=*/ false))
+                .isFalse();
+    }
+
+    @Test
+    public void testTombstonesAreNotGeneratedWithCrashConfigWhenNonDebuggableOnNonPvm()
+            throws Exception {
+        assumeNonProtectedVmSupported();
+        assertThat(
+                        isTombstoneGeneratedWithCrashConfig(
+                                /*protectedVm=*/ false, /*debuggable=*/ false))
+                .isFalse();
+    }
+
+    @Test
+    public void testTelemetryPushedAtomsOnNonPvm() throws Exception {
+        assumeNonProtectedVmSupported();
+        testTelemetryPushedAtoms(false);
+    }
+
+    @Test
+    public void testTelemetryPushedAtomsOnPvm() throws Exception {
+        assumeProtectedVmSupported();
+        testTelemetryPushedAtoms(true);
+    }
+
+    private void testTelemetryPushedAtoms(boolean protectedVm) throws Exception {
         // Reset statsd config and report before the test
         ConfigUtils.removeConfig(getDevice());
         ReportUtils.clearReports(getDevice());
@@ -688,6 +746,7 @@
                         .debugLevel("full")
                         .memoryMib(minMemorySize())
                         .cpuTopology("match_host")
+                        .protectedVm(protectedVm)
                         .build(device);
         microdroid.waitForBootComplete(BOOT_COMPLETE_TIMEOUT);
         device.shutdownMicrodroid(microdroid);
@@ -714,7 +773,7 @@
                 data.get(0).getAtom().getVmCreationRequested();
         assertThat(atomVmCreationRequested.getHypervisor())
                 .isEqualTo(AtomsProto.VmCreationRequested.Hypervisor.PKVM);
-        assertThat(atomVmCreationRequested.getIsProtected()).isFalse();
+        assertThat(atomVmCreationRequested.getIsProtected()).isEqualTo(protectedVm);
         assertThat(atomVmCreationRequested.getCreationSucceeded()).isTrue();
         assertThat(atomVmCreationRequested.getBinderExceptionCode()).isEqualTo(0);
         assertThat(atomVmCreationRequested.getVmIdentifier()).isEqualTo("VmRunApp");
@@ -743,7 +802,19 @@
 
     @Test
     @CddTest(requirements = {"9.17/C-1-1", "9.17/C-1-2", "9.17/C/1-3"})
-    public void testMicrodroidBoots() throws Exception {
+    public void testMicrodroidBootsOnPvm() throws Exception {
+        assumeProtectedVmSupported();
+        testMicrodroidBoots(true);
+    }
+
+    @Test
+    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-1-2", "9.17/C/1-3"})
+    public void testMicrodroidBootsOnNonPvm() throws Exception {
+        assumeNonProtectedVmSupported();
+        testMicrodroidBoots(false);
+    }
+
+    private void testMicrodroidBoots(boolean protectedVm) throws Exception {
         CommandRunner android = new CommandRunner(getDevice());
 
         final String configPath = "assets/vm_config.json"; // path inside the APK
@@ -752,6 +823,7 @@
                         .debugLevel("full")
                         .memoryMib(minMemorySize())
                         .cpuTopology("match_host")
+                        .protectedVm(protectedVm)
                         .build(getAndroidDevice());
         mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT);
         CommandRunner microdroid = new CommandRunner(mMicrodroidDevice);
@@ -810,13 +882,25 @@
     }
 
     @Test
-    public void testMicrodroidRamUsage() throws Exception {
+    public void testMicrodroidRamUsageOnPvm() throws Exception {
+        assumeProtectedVmSupported();
+        testMicrodroidRamUsage(true);
+    }
+
+    @Test
+    public void testMicrodroidRamUsageOnNonPvm() throws Exception {
+        assumeNonProtectedVmSupported();
+        testMicrodroidRamUsage(false);
+    }
+
+    private void testMicrodroidRamUsage(boolean protectedVm) throws Exception {
         final String configPath = "assets/vm_config.json";
         mMicrodroidDevice =
                 MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath)
                         .debugLevel("full")
                         .memoryMib(minMemorySize())
                         .cpuTopology("match_host")
+                        .protectedVm(protectedVm)
                         .build(getAndroidDevice());
         mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT);
         mMicrodroidDevice.enableAdbRoot();
@@ -990,6 +1074,18 @@
                         "android.permission.USE_CUSTOM_VIRTUAL_MACHINE");
     }
 
+    private void assumeProtectedVmSupported() throws Exception {
+        assumeTrue(
+                "Test skipped because protected VMs are not supported",
+                getAndroidDevice().supportsMicrodroid(true));
+    }
+
+    private void assumeNonProtectedVmSupported() throws Exception {
+        assumeTrue(
+                "Test skipped because non-protected VMs are not supported",
+                getAndroidDevice().supportsMicrodroid(false));
+    }
+
     private TestDevice getAndroidDevice() {
         TestDevice androidDevice = (TestDevice) getDevice();
         assertThat(androidDevice).isNotNull();
diff --git a/vm/src/main.rs b/vm/src/main.rs
index d7c2c4d..0c99acb 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -82,8 +82,8 @@
         #[clap(long)]
         log: Option<PathBuf>,
 
-        /// Debug level of the VM. Supported values: "none" (default), and "full".
-        #[clap(long, default_value = "none", value_parser = parse_debug_level)]
+        /// Debug level of the VM. Supported values: "full" (default), and "none".
+        #[clap(long, default_value = "full", value_parser = parse_debug_level)]
         debug: DebugLevel,
 
         /// Run VM in protected mode.
@@ -154,7 +154,7 @@
         #[clap(long)]
         log: Option<PathBuf>,
 
-        /// Debug level of the VM. Supported values: "none" (default), and "full".
+        /// Debug level of the VM. Supported values: "full" (default), and "none".
         #[clap(long, default_value = "full", value_parser = parse_debug_level)]
         debug: DebugLevel,
 
diff --git a/vmbase/Android.bp b/vmbase/Android.bp
index 46f4937..71b9e76 100644
--- a/vmbase/Android.bp
+++ b/vmbase/Android.bp
@@ -84,6 +84,7 @@
         "libspin_nostd",
         "libtinyvec_nostd",
         "libvirtio_drivers",
+        "libzerocopy_nostd",
         "libzeroize_nostd",
     ],
     whole_static_libs: [
diff --git a/vmbase/src/bionic.rs b/vmbase/src/bionic.rs
index 2ce0e83..29fa0ff 100644
--- a/vmbase/src/bionic.rs
+++ b/vmbase/src/bionic.rs
@@ -22,6 +22,7 @@
 use core::str;
 
 use crate::console;
+use crate::cstr;
 use crate::eprintln;
 use crate::read_sysreg;
 
@@ -79,6 +80,11 @@
     unsafe { ERRNO = value };
 }
 
+fn get_errno() -> c_int {
+    // SAFETY: vmbase is currently single-threaded.
+    unsafe { ERRNO }
+}
+
 /// Reports a fatal error detected by Bionic.
 ///
 /// # Safety
@@ -154,142 +160,168 @@
 
 #[no_mangle]
 extern "C" fn strerror(n: c_int) -> *mut c_char {
-    // Messages taken from errno(1).
-    let s = match n {
-        0 => "Success",
-        1 => "Operation not permitted",
-        2 => "No such file or directory",
-        3 => "No such process",
-        4 => "Interrupted system call",
-        5 => "Input/output error",
-        6 => "No such device or address",
-        7 => "Argument list too long",
-        8 => "Exec format error",
-        9 => "Bad file descriptor",
-        10 => "No child processes",
-        11 => "Resource temporarily unavailable",
-        12 => "Cannot allocate memory",
-        13 => "Permission denied",
-        14 => "Bad address",
-        15 => "Block device required",
-        16 => "Device or resource busy",
-        17 => "File exists",
-        18 => "Invalid cross-device link",
-        19 => "No such device",
-        20 => "Not a directory",
-        21 => "Is a directory",
-        22 => "Invalid argument",
-        23 => "Too many open files in system",
-        24 => "Too many open files",
-        25 => "Inappropriate ioctl for device",
-        26 => "Text file busy",
-        27 => "File too large",
-        28 => "No space left on device",
-        29 => "Illegal seek",
-        30 => "Read-only file system",
-        31 => "Too many links",
-        32 => "Broken pipe",
-        33 => "Numerical argument out of domain",
-        34 => "Numerical result out of range",
-        35 => "Resource deadlock avoided",
-        36 => "File name too long",
-        37 => "No locks available",
-        38 => "Function not implemented",
-        39 => "Directory not empty",
-        40 => "Too many levels of symbolic links",
-        42 => "No message of desired type",
-        43 => "Identifier removed",
-        44 => "Channel number out of range",
-        45 => "Level 2 not synchronized",
-        46 => "Level 3 halted",
-        47 => "Level 3 reset",
-        48 => "Link number out of range",
-        49 => "Protocol driver not attached",
-        50 => "No CSI structure available",
-        51 => "Level 2 halted",
-        52 => "Invalid exchange",
-        53 => "Invalid request descriptor",
-        54 => "Exchange full",
-        55 => "No anode",
-        56 => "Invalid request code",
-        57 => "Invalid slot",
-        59 => "Bad font file format",
-        60 => "Device not a stream",
-        61 => "No data available",
-        62 => "Timer expired",
-        63 => "Out of streams resources",
-        64 => "Machine is not on the network",
-        65 => "Package not installed",
-        66 => "Object is remote",
-        67 => "Link has been severed",
-        68 => "Advertise error",
-        69 => "Srmount error",
-        70 => "Communication error on send",
-        71 => "Protocol error",
-        72 => "Multihop attempted",
-        73 => "RFS specific error",
-        74 => "Bad message",
-        75 => "Value too large for defined data type",
-        76 => "Name not unique on network",
-        77 => "File descriptor in bad state",
-        78 => "Remote address changed",
-        79 => "Can not access a needed shared library",
-        80 => "Accessing a corrupted shared library",
-        81 => ".lib section in a.out corrupted",
-        82 => "Attempting to link in too many shared libraries",
-        83 => "Cannot exec a shared library directly",
-        84 => "Invalid or incomplete multibyte or wide character",
-        85 => "Interrupted system call should be restarted",
-        86 => "Streams pipe error",
-        87 => "Too many users",
-        88 => "Socket operation on non-socket",
-        89 => "Destination address required",
-        90 => "Message too long",
-        91 => "Protocol wrong type for socket",
-        92 => "Protocol not available",
-        93 => "Protocol not supported",
-        94 => "Socket type not supported",
-        95 => "Operation not supported",
-        96 => "Protocol family not supported",
-        97 => "Address family not supported by protocol",
-        98 => "Address already in use",
-        99 => "Cannot assign requested address",
-        100 => "Network is down",
-        101 => "Network is unreachable",
-        102 => "Network dropped connection on reset",
-        103 => "Software caused connection abort",
-        104 => "Connection reset by peer",
-        105 => "No buffer space available",
-        106 => "Transport endpoint is already connected",
-        107 => "Transport endpoint is not connected",
-        108 => "Cannot send after transport endpoint shutdown",
-        109 => "Too many references: cannot splice",
-        110 => "Connection timed out",
-        111 => "Connection refused",
-        112 => "Host is down",
-        113 => "No route to host",
-        114 => "Operation already in progress",
-        115 => "Operation now in progress",
-        116 => "Stale file handle",
-        117 => "Structure needs cleaning",
-        118 => "Not a XENIX named type file",
-        119 => "No XENIX semaphores available",
-        120 => "Is a named type file",
-        121 => "Remote I/O error",
-        122 => "Disk quota exceeded",
-        123 => "No medium found",
-        124 => "Wrong medium type",
-        125 => "Operation canceled",
-        126 => "Required key not available",
-        127 => "Key has expired",
-        128 => "Key has been revoked",
-        129 => "Key was rejected by service",
-        130 => "Owner died",
-        131 => "State not recoverable",
-        132 => "Operation not possible due to RF-kill",
-        133 => "Memory page has hardware error",
-        _ => "Unknown errno value",
+    cstr_error(n).as_ptr().cast_mut().cast()
+}
+
+#[no_mangle]
+extern "C" fn perror(s: *const c_char) {
+    let prefix = if s.is_null() {
+        None
+    } else {
+        // SAFETY: Just like libc, we need to assume that `s` is a valid NULL-terminated string.
+        let c_str = unsafe { CStr::from_ptr(s) };
+        // TODO(Rust 1.71): if c_str.is_empty() {
+        if c_str.to_bytes().is_empty() {
+            None
+        } else {
+            Some(c_str.to_str().unwrap())
+        }
     };
 
-    s.as_ptr().cast_mut().cast()
+    let error = cstr_error(get_errno()).to_str().unwrap();
+
+    if let Some(prefix) = prefix {
+        eprintln!("{prefix}: {error}");
+    } else {
+        eprintln!("{error}");
+    }
+}
+
+fn cstr_error(n: c_int) -> &'static CStr {
+    // Messages taken from errno(1).
+    match n {
+        0 => cstr!("Success"),
+        1 => cstr!("Operation not permitted"),
+        2 => cstr!("No such file or directory"),
+        3 => cstr!("No such process"),
+        4 => cstr!("Interrupted system call"),
+        5 => cstr!("Input/output error"),
+        6 => cstr!("No such device or address"),
+        7 => cstr!("Argument list too long"),
+        8 => cstr!("Exec format error"),
+        9 => cstr!("Bad file descriptor"),
+        10 => cstr!("No child processes"),
+        11 => cstr!("Resource temporarily unavailable"),
+        12 => cstr!("Cannot allocate memory"),
+        13 => cstr!("Permission denied"),
+        14 => cstr!("Bad address"),
+        15 => cstr!("Block device required"),
+        16 => cstr!("Device or resource busy"),
+        17 => cstr!("File exists"),
+        18 => cstr!("Invalid cross-device link"),
+        19 => cstr!("No such device"),
+        20 => cstr!("Not a directory"),
+        21 => cstr!("Is a directory"),
+        22 => cstr!("Invalid argument"),
+        23 => cstr!("Too many open files in system"),
+        24 => cstr!("Too many open files"),
+        25 => cstr!("Inappropriate ioctl for device"),
+        26 => cstr!("Text file busy"),
+        27 => cstr!("File too large"),
+        28 => cstr!("No space left on device"),
+        29 => cstr!("Illegal seek"),
+        30 => cstr!("Read-only file system"),
+        31 => cstr!("Too many links"),
+        32 => cstr!("Broken pipe"),
+        33 => cstr!("Numerical argument out of domain"),
+        34 => cstr!("Numerical result out of range"),
+        35 => cstr!("Resource deadlock avoided"),
+        36 => cstr!("File name too long"),
+        37 => cstr!("No locks available"),
+        38 => cstr!("Function not implemented"),
+        39 => cstr!("Directory not empty"),
+        40 => cstr!("Too many levels of symbolic links"),
+        42 => cstr!("No message of desired type"),
+        43 => cstr!("Identifier removed"),
+        44 => cstr!("Channel number out of range"),
+        45 => cstr!("Level 2 not synchronized"),
+        46 => cstr!("Level 3 halted"),
+        47 => cstr!("Level 3 reset"),
+        48 => cstr!("Link number out of range"),
+        49 => cstr!("Protocol driver not attached"),
+        50 => cstr!("No CSI structure available"),
+        51 => cstr!("Level 2 halted"),
+        52 => cstr!("Invalid exchange"),
+        53 => cstr!("Invalid request descriptor"),
+        54 => cstr!("Exchange full"),
+        55 => cstr!("No anode"),
+        56 => cstr!("Invalid request code"),
+        57 => cstr!("Invalid slot"),
+        59 => cstr!("Bad font file format"),
+        60 => cstr!("Device not a stream"),
+        61 => cstr!("No data available"),
+        62 => cstr!("Timer expired"),
+        63 => cstr!("Out of streams resources"),
+        64 => cstr!("Machine is not on the network"),
+        65 => cstr!("Package not installed"),
+        66 => cstr!("Object is remote"),
+        67 => cstr!("Link has been severed"),
+        68 => cstr!("Advertise error"),
+        69 => cstr!("Srmount error"),
+        70 => cstr!("Communication error on send"),
+        71 => cstr!("Protocol error"),
+        72 => cstr!("Multihop attempted"),
+        73 => cstr!("RFS specific error"),
+        74 => cstr!("Bad message"),
+        75 => cstr!("Value too large for defined data type"),
+        76 => cstr!("Name not unique on network"),
+        77 => cstr!("File descriptor in bad state"),
+        78 => cstr!("Remote address changed"),
+        79 => cstr!("Can not access a needed shared library"),
+        80 => cstr!("Accessing a corrupted shared library"),
+        81 => cstr!(".lib section in a.out corrupted"),
+        82 => cstr!("Attempting to link in too many shared libraries"),
+        83 => cstr!("Cannot exec a shared library directly"),
+        84 => cstr!("Invalid or incomplete multibyte or wide character"),
+        85 => cstr!("Interrupted system call should be restarted"),
+        86 => cstr!("Streams pipe error"),
+        87 => cstr!("Too many users"),
+        88 => cstr!("Socket operation on non-socket"),
+        89 => cstr!("Destination address required"),
+        90 => cstr!("Message too long"),
+        91 => cstr!("Protocol wrong type for socket"),
+        92 => cstr!("Protocol not available"),
+        93 => cstr!("Protocol not supported"),
+        94 => cstr!("Socket type not supported"),
+        95 => cstr!("Operation not supported"),
+        96 => cstr!("Protocol family not supported"),
+        97 => cstr!("Address family not supported by protocol"),
+        98 => cstr!("Address already in use"),
+        99 => cstr!("Cannot assign requested address"),
+        100 => cstr!("Network is down"),
+        101 => cstr!("Network is unreachable"),
+        102 => cstr!("Network dropped connection on reset"),
+        103 => cstr!("Software caused connection abort"),
+        104 => cstr!("Connection reset by peer"),
+        105 => cstr!("No buffer space available"),
+        106 => cstr!("Transport endpoint is already connected"),
+        107 => cstr!("Transport endpoint is not connected"),
+        108 => cstr!("Cannot send after transport endpoint shutdown"),
+        109 => cstr!("Too many references: cannot splice"),
+        110 => cstr!("Connection timed out"),
+        111 => cstr!("Connection refused"),
+        112 => cstr!("Host is down"),
+        113 => cstr!("No route to host"),
+        114 => cstr!("Operation already in progress"),
+        115 => cstr!("Operation now in progress"),
+        116 => cstr!("Stale file handle"),
+        117 => cstr!("Structure needs cleaning"),
+        118 => cstr!("Not a XENIX named type file"),
+        119 => cstr!("No XENIX semaphores available"),
+        120 => cstr!("Is a named type file"),
+        121 => cstr!("Remote I/O error"),
+        122 => cstr!("Disk quota exceeded"),
+        123 => cstr!("No medium found"),
+        124 => cstr!("Wrong medium type"),
+        125 => cstr!("Operation canceled"),
+        126 => cstr!("Required key not available"),
+        127 => cstr!("Key has expired"),
+        128 => cstr!("Key has been revoked"),
+        129 => cstr!("Key was rejected by service"),
+        130 => cstr!("Owner died"),
+        131 => cstr!("State not recoverable"),
+        132 => cstr!("Operation not possible due to RF-kill"),
+        133 => cstr!("Memory page has hardware error"),
+        _ => cstr!("Unknown errno value"),
+    }
 }
diff --git a/vmbase/src/entry.rs b/vmbase/src/entry.rs
index 24b5035..2ff66cc 100644
--- a/vmbase/src/entry.rs
+++ b/vmbase/src/entry.rs
@@ -26,7 +26,8 @@
     console::init();
 
     if let Some(mmio_guard) = get_mmio_guard() {
-        mmio_guard.init()?;
+        mmio_guard.enroll()?;
+        mmio_guard.validate_granule()?;
         mmio_guard.map(console::BASE_ADDRESS)?;
     }
 
diff --git a/vmbase/src/hvc.rs b/vmbase/src/hvc.rs
index ebd1625..1197143 100644
--- a/vmbase/src/hvc.rs
+++ b/vmbase/src/hvc.rs
@@ -37,7 +37,7 @@
     (version as u32 as i32).try_into()
 }
 
-pub type TrngRng64Entropy = (u64, u64, u64);
+pub type TrngRng64Entropy = [u64; 3];
 
 pub fn trng_rnd64(nbits: u64) -> trng::Result<TrngRng64Entropy> {
     let mut args = [0u64; 17];
@@ -46,7 +46,7 @@
     let regs = hvc64(ARM_SMCCC_TRNG_RND64, args);
     success_or_error_64::<Error>(regs[0])?;
 
-    Ok((regs[1], regs[2], regs[3]))
+    Ok([regs[1], regs[2], regs[3]])
 }
 
 pub fn trng_features(fid: u32) -> trng::Result<u64> {
diff --git a/vmbase/src/memory/mod.rs b/vmbase/src/memory/mod.rs
index 898aa10..2f72fc4 100644
--- a/vmbase/src/memory/mod.rs
+++ b/vmbase/src/memory/mod.rs
@@ -23,10 +23,12 @@
 pub use error::MemoryTrackerError;
 pub use page_table::PageTable;
 pub use shared::{
-    alloc_shared, dealloc_shared, handle_permission_fault, handle_translation_fault, MemoryRange,
-    MemoryTracker, MEMORY,
+    handle_permission_fault, handle_translation_fault, MemoryRange, MemoryTracker, MEMORY,
 };
 pub use util::{
-    flush, flushed_zeroize, min_dcache_line_size, page_4kb_of, phys_to_virt, virt_to_phys,
-    PAGE_SIZE, SIZE_128KB, SIZE_2MB, SIZE_4KB, SIZE_4MB, SIZE_64KB,
+    flush, flushed_zeroize, min_dcache_line_size, page_4kb_of, PAGE_SIZE, SIZE_128KB, SIZE_2MB,
+    SIZE_4KB, SIZE_4MB, SIZE_64KB,
 };
+
+pub(crate) use shared::{alloc_shared, dealloc_shared};
+pub(crate) use util::{phys_to_virt, virt_to_phys};
diff --git a/vmbase/src/memory/shared.rs b/vmbase/src/memory/shared.rs
index 173c0ec..064fb6d 100644
--- a/vmbase/src/memory/shared.rs
+++ b/vmbase/src/memory/shared.rs
@@ -27,6 +27,7 @@
 use alloc::vec::Vec;
 use buddy_system_allocator::{FrameAllocator, LockedFrameAllocator};
 use core::alloc::Layout;
+use core::cmp::max;
 use core::mem::size_of;
 use core::num::NonZeroUsize;
 use core::ops::Range;
@@ -343,7 +344,7 @@
 
 /// Allocates a memory range of at least the given size and alignment that is shared with the host.
 /// Returns a pointer to the buffer.
-pub fn alloc_shared(layout: Layout) -> hyp::Result<NonNull<u8>> {
+pub(crate) fn alloc_shared(layout: Layout) -> hyp::Result<NonNull<u8>> {
     assert_ne!(layout.size(), 0);
     let Some(buffer) = try_shared_alloc(layout) else {
         handle_alloc_error(layout);
@@ -359,7 +360,11 @@
     if let Some(buffer) = shared_pool.alloc_aligned(layout) {
         Some(NonNull::new(buffer as _).unwrap())
     } else if let Some(shared_memory) = SHARED_MEMORY.lock().as_mut() {
-        shared_memory.refill(&mut shared_pool, layout);
+        // Adjusts the layout size to the max of the next power of two and the alignment,
+        // as this is the actual size of the memory allocated in `alloc_aligned()`.
+        let size = max(layout.size().next_power_of_two(), layout.align());
+        let refill_layout = Layout::from_size_align(size, layout.align()).unwrap();
+        shared_memory.refill(&mut shared_pool, refill_layout);
         shared_pool.alloc_aligned(layout).map(|buffer| NonNull::new(buffer as _).unwrap())
     } else {
         None
@@ -374,7 +379,7 @@
 ///
 /// The memory must have been allocated by `alloc_shared` with the same layout, and not yet
 /// deallocated.
-pub unsafe fn dealloc_shared(vaddr: NonNull<u8>, layout: Layout) -> hyp::Result<()> {
+pub(crate) unsafe fn dealloc_shared(vaddr: NonNull<u8>, layout: Layout) -> hyp::Result<()> {
     SHARED_POOL.get().unwrap().lock().dealloc_aligned(vaddr.as_ptr() as usize, layout);
 
     trace!("Deallocated shared buffer at {vaddr:?} with {layout:?}");
diff --git a/vmbase/src/memory/util.rs b/vmbase/src/memory/util.rs
index 48d4c55..2b75414 100644
--- a/vmbase/src/memory/util.rs
+++ b/vmbase/src/memory/util.rs
@@ -88,7 +88,7 @@
 ///
 /// As we use identity mapping for everything, this is just a cast, but it's useful to use it to be
 /// explicit about where we are converting from virtual to physical address.
-pub fn virt_to_phys(vaddr: NonNull<u8>) -> usize {
+pub(crate) fn virt_to_phys(vaddr: NonNull<u8>) -> usize {
     vaddr.as_ptr() as _
 }
 
@@ -96,6 +96,6 @@
 /// physical address.
 ///
 /// Panics if `paddr` is 0.
-pub fn phys_to_virt(paddr: usize) -> NonNull<u8> {
+pub(crate) fn phys_to_virt(paddr: usize) -> NonNull<u8> {
     NonNull::new(paddr as _).unwrap()
 }
diff --git a/vmbase/src/rand.rs b/vmbase/src/rand.rs
index 6b8d7e0..2acc390 100644
--- a/vmbase/src/rand.rs
+++ b/vmbase/src/rand.rs
@@ -14,10 +14,13 @@
 
 //! Functions and drivers for obtaining true entropy.
 
-use crate::hvc::{self, TrngRng64Entropy};
+use crate::hvc;
 use core::fmt;
 use core::mem::size_of;
 use smccc::{self, Hvc};
+use zerocopy::AsBytes as _;
+
+type Entropy = [u8; size_of::<u64>() * 3];
 
 /// Error type for rand operations.
 pub enum Error {
@@ -95,46 +98,55 @@
 
 /// Fills a slice of bytes with true entropy.
 pub fn fill_with_entropy(s: &mut [u8]) -> Result<()> {
-    const MAX_BYTES_PER_CALL: usize = size_of::<TrngRng64Entropy>();
+    const MAX_BYTES_PER_CALL: usize = size_of::<Entropy>();
 
-    let (aligned, remainder) = s.split_at_mut(s.len() - s.len() % MAX_BYTES_PER_CALL);
-
-    for chunk in aligned.chunks_exact_mut(MAX_BYTES_PER_CALL) {
-        let (r, s, t) = repeat_trng_rnd(chunk.len())?;
-
-        let mut words = chunk.chunks_exact_mut(size_of::<u64>());
-        words.next().unwrap().clone_from_slice(&t.to_ne_bytes());
-        words.next().unwrap().clone_from_slice(&s.to_ne_bytes());
-        words.next().unwrap().clone_from_slice(&r.to_ne_bytes());
-    }
-
-    if !remainder.is_empty() {
-        let mut entropy = [0; MAX_BYTES_PER_CALL];
-        let (r, s, t) = repeat_trng_rnd(remainder.len())?;
-
-        let mut words = entropy.chunks_exact_mut(size_of::<u64>());
-        words.next().unwrap().clone_from_slice(&t.to_ne_bytes());
-        words.next().unwrap().clone_from_slice(&s.to_ne_bytes());
-        words.next().unwrap().clone_from_slice(&r.to_ne_bytes());
-
-        remainder.clone_from_slice(&entropy[..remainder.len()]);
+    for chunk in s.chunks_mut(MAX_BYTES_PER_CALL) {
+        let entropy = repeat_trng_rnd(chunk.len())?;
+        chunk.clone_from_slice(&entropy[..chunk.len()]);
     }
 
     Ok(())
 }
 
-fn repeat_trng_rnd(n_bytes: usize) -> Result<TrngRng64Entropy> {
-    let bits = usize::try_from(u8::BITS).unwrap();
-    let n_bits = (n_bytes * bits).try_into().unwrap();
+/// Returns an array where the first `n_bytes` bytes hold entropy.
+///
+/// The rest of the array should be ignored.
+fn repeat_trng_rnd(n_bytes: usize) -> Result<Entropy> {
     loop {
-        match hvc::trng_rnd64(n_bits) {
-            Ok(entropy) => return Ok(entropy),
-            Err(hvc::trng::Error::NoEntropy) => (),
-            Err(e) => return Err(e.into()),
+        if let Some(entropy) = rnd64(n_bytes)? {
+            return Ok(entropy);
         }
     }
 }
 
+/// Returns an array where the first `n_bytes` bytes hold entropy, if available.
+///
+/// The rest of the array should be ignored.
+fn rnd64(n_bytes: usize) -> Result<Option<Entropy>> {
+    let bits = usize::try_from(u8::BITS).unwrap();
+    let result = hvc::trng_rnd64((n_bytes * bits).try_into().unwrap());
+    let entropy = if matches!(result, Err(hvc::trng::Error::NoEntropy)) {
+        None
+    } else {
+        let r = result?;
+        // From the SMCCC TRNG:
+        //
+        //     A MAX_BITS-bits wide value (Entropy) is returned across X1 to X3.
+        //     The requested conditioned entropy is returned in Entropy[N-1:0].
+        //
+        //             X1     Entropy[191:128]
+        //             X2     Entropy[127:64]
+        //             X3     Entropy[63:0]
+        //
+        //     The bits in Entropy[MAX_BITS-1:N] are 0.
+        let reordered = [r[2].to_le(), r[1].to_le(), r[0].to_le()];
+
+        Some(reordered.as_bytes().try_into().unwrap())
+    };
+
+    Ok(entropy)
+}
+
 /// Generate an array of fixed-size initialized with true-random bytes.
 pub fn random_array<const N: usize>() -> Result<[u8; N]> {
     let mut arr = [0; N];
diff --git a/zipfuse/src/inode.rs b/zipfuse/src/inode.rs
index ea63422..ef48389 100644
--- a/zipfuse/src/inode.rs
+++ b/zipfuse/src/inode.rs
@@ -210,7 +210,7 @@
                     parent = found;
                     // Update the mode if this is a directory leaf.
                     if !is_file && is_leaf {
-                        let mut inode = table.get_mut(parent).unwrap();
+                        let inode = table.get_mut(parent).unwrap();
                         inode.mode = file.unix_mode().unwrap_or(DEFAULT_DIR_MODE);
                     }
                     continue;