Merge "Remove DEBUG_LEVEL_APP_ONLY"
diff --git a/javalib/api/test-current.txt b/javalib/api/test-current.txt
index 42ad060..8b7ec11 100644
--- a/javalib/api/test-current.txt
+++ b/javalib/api/test-current.txt
@@ -1,6 +1,10 @@
 // Signature format: 2.0
 package android.system.virtualmachine {
 
+  public class VirtualMachine implements java.lang.AutoCloseable {
+    method @NonNull public java.io.File getRootDir();
+  }
+
   public final class VirtualMachineConfig {
     method @Nullable public String getPayloadConfigPath();
   }
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index 0305650..b8be703 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -49,6 +49,7 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.content.ComponentCallbacks2;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -984,6 +985,18 @@
     }
 
     /**
+     * Returns the root directory where all files related to this {@link VirtualMachine} (e.g.
+     * {@code instance.img}, {@code apk.idsig}, etc) are stored.
+     *
+     * @hide
+     */
+    @TestApi
+    @NonNull
+    public File getRootDir() {
+        return mVmRootPath;
+    }
+
+    /**
      * Captures the current state of the VM in a {@link VirtualMachineDescriptor} instance. The VM
      * needs to be stopped to avoid inconsistency in its state representation.
      *
diff --git a/libs/dice/src/bcc.rs b/libs/dice/src/bcc.rs
new file mode 100644
index 0000000..849dfa0
--- /dev/null
+++ b/libs/dice/src/bcc.rs
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! Wrapper around dice/android/bcc.h.
+
+use core::mem;
+use core::ptr;
+
+use open_dice_bcc_bindgen::BccHandoverParse;
+
+use crate::check_call;
+use crate::Cdi;
+use crate::Error;
+use crate::Result;
+
+/// Boot Chain Certificate handover format combining the BCC and CDIs in a single CBOR object.
+#[derive(Clone, Debug)]
+pub struct Handover<'a> {
+    /// Attestation CDI.
+    pub cdi_attest: &'a Cdi,
+    /// Sealing CDI.
+    pub cdi_seal: &'a Cdi,
+    /// Boot Chain Certificate (optional).
+    pub bcc: Option<&'a [u8]>,
+}
+
+impl<'a> Handover<'a> {
+    /// Validates and extracts the fields of a BCC handover buffer.
+    pub fn new(buffer: &'a [u8]) -> Result<Self> {
+        let mut cdi_attest: *const u8 = ptr::null();
+        let mut cdi_seal: *const u8 = ptr::null();
+        let mut bcc: *const u8 = ptr::null();
+        let mut bcc_size: usize = 0;
+
+        // SAFETY - The buffer is only read and never stored and the returned pointers should all
+        // point within the address range of the buffer or be NULL.
+        check_call(unsafe {
+            BccHandoverParse(
+                buffer.as_ptr(),
+                buffer.len(),
+                &mut cdi_attest as *mut *const u8,
+                &mut cdi_seal as *mut *const u8,
+                &mut bcc as *mut *const u8,
+                &mut bcc_size as *mut usize,
+            )
+        })?;
+
+        let cdi_attest = {
+            let i = index_from_ptr(buffer, cdi_attest).ok_or(Error::PlatformError)?;
+            let s = buffer.get(i..(i + mem::size_of::<Cdi>())).ok_or(Error::PlatformError)?;
+            s.try_into().map_err(|_| Error::PlatformError)?
+        };
+        let cdi_seal = {
+            let i = index_from_ptr(buffer, cdi_seal).ok_or(Error::PlatformError)?;
+            let s = buffer.get(i..(i + mem::size_of::<Cdi>())).ok_or(Error::PlatformError)?;
+            s.try_into().map_err(|_| Error::PlatformError)?
+        };
+        let bcc = if bcc.is_null() {
+            None
+        } else {
+            let i = index_from_ptr(buffer, bcc).ok_or(Error::PlatformError)?;
+            Some(buffer.get(i..(i + bcc_size)).ok_or(Error::PlatformError)?)
+        };
+
+        Ok(Self { cdi_attest, cdi_seal, bcc })
+    }
+}
+
+fn index_from_ptr(slice: &[u8], pointer: *const u8) -> Option<usize> {
+    if slice.as_ptr_range().contains(&pointer) {
+        (pointer as usize).checked_sub(slice.as_ptr() as usize)
+    } else {
+        None
+    }
+}
diff --git a/libs/dice/src/lib.rs b/libs/dice/src/lib.rs
index 9e39436..43d167f 100644
--- a/libs/dice/src/lib.rs
+++ b/libs/dice/src/lib.rs
@@ -18,16 +18,23 @@
 
 #![no_std]
 
-use core::fmt::{self, Debug};
-use open_dice_cbor_bindgen::{
-    DiceHash, DiceResult, DiceResult_kDiceResultBufferTooSmall as DICE_RESULT_BUFFER_TOO_SMALL,
-    DiceResult_kDiceResultInvalidInput as DICE_RESULT_INVALID_INPUT,
-    DiceResult_kDiceResultOk as DICE_RESULT_OK,
-    DiceResult_kDiceResultPlatformError as DICE_RESULT_PLATFORM_ERROR,
-};
+use core::fmt;
+use core::result;
 
+use open_dice_cbor_bindgen::DiceHash;
+use open_dice_cbor_bindgen::DiceResult;
+use open_dice_cbor_bindgen::DiceResult_kDiceResultBufferTooSmall as DICE_RESULT_BUFFER_TOO_SMALL;
+use open_dice_cbor_bindgen::DiceResult_kDiceResultInvalidInput as DICE_RESULT_INVALID_INPUT;
+use open_dice_cbor_bindgen::DiceResult_kDiceResultOk as DICE_RESULT_OK;
+use open_dice_cbor_bindgen::DiceResult_kDiceResultPlatformError as DICE_RESULT_PLATFORM_ERROR;
+
+pub mod bcc;
+
+const CDI_SIZE: usize = open_dice_cbor_bindgen::DICE_CDI_SIZE as usize;
 const HASH_SIZE: usize = open_dice_cbor_bindgen::DICE_HASH_SIZE as usize;
 
+/// Array type of CDIs.
+pub type Cdi = [u8; CDI_SIZE];
 /// Array type of hashes used by DICE.
 pub type Hash = [u8; HASH_SIZE];
 
@@ -43,7 +50,7 @@
     Unknown(DiceResult),
 }
 
-impl Debug for Error {
+impl fmt::Debug for Error {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         match self {
             Error::InvalidInput => write!(f, "invalid input"),
@@ -54,7 +61,10 @@
     }
 }
 
-fn check_call(ret: DiceResult) -> Result<(), Error> {
+/// Result of DICE functions.
+pub type Result<T> = result::Result<T, Error>;
+
+fn check_call(ret: DiceResult) -> Result<()> {
     match ret {
         DICE_RESULT_OK => Ok(()),
         DICE_RESULT_INVALID_INPUT => Err(Error::InvalidInput),
@@ -69,7 +79,7 @@
 }
 
 /// Hash the provided input using DICE's default hash function.
-pub fn hash(bytes: &[u8]) -> Result<Hash, Error> {
+pub fn hash(bytes: &[u8]) -> Result<Hash> {
     let mut output: Hash = [0; HASH_SIZE];
     // SAFETY - DiceHash takes a sized input buffer and writes to a constant-sized output buffer.
     check_call(unsafe { DiceHash(ctx(), bytes.as_ptr(), bytes.len(), output.as_mut_ptr()) })?;
diff --git a/pvmfw/Android.bp b/pvmfw/Android.bp
index 0da24c7..6a01713 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -15,6 +15,7 @@
         "libaarch64_paging",
         "libavb_nostd",
         "libbuddy_system_allocator",
+        "libdice_nostd",
         "liblibfdt",
         "liblog_rust_nostd",
         "libpvmfw_embedded_key",
diff --git a/pvmfw/src/entry.rs b/pvmfw/src/entry.rs
index efbb179..bffc140 100644
--- a/pvmfw/src/entry.rs
+++ b/pvmfw/src/entry.rs
@@ -24,6 +24,7 @@
 use core::arch::asm;
 use core::num::NonZeroUsize;
 use core::slice;
+use dice::bcc::Handover;
 use log::debug;
 use log::error;
 use log::info;
@@ -32,7 +33,7 @@
 use vmbase::{console, layout, logger, main, power::reboot};
 
 #[derive(Debug, Clone)]
-pub(crate) enum RebootReason {
+pub enum RebootReason {
     /// A malformed BCC was received.
     InvalidBcc,
     /// An invalid configuration was appended to pvmfw.
@@ -228,8 +229,9 @@
         RebootReason::InvalidConfig
     })?;
 
-    let bcc = appended.get_bcc_mut().ok_or_else(|| {
-        error!("Invalid BCC");
+    let bcc_slice = appended.get_bcc_mut();
+    let bcc = Handover::new(bcc_slice).map_err(|e| {
+        error!("Invalid BCC Handover: {e:?}");
         RebootReason::InvalidBcc
     })?;
 
@@ -243,10 +245,15 @@
     let slices = MemorySlices::new(fdt, payload, payload_size, &mut memory)?;
 
     // This wrapper allows main() to be blissfully ignorant of platform details.
-    crate::main(slices.fdt, slices.kernel, slices.ramdisk, bcc)?;
+    crate::main(slices.fdt, slices.kernel, slices.ramdisk, &bcc, &mut memory)?;
 
     // TODO: Overwrite BCC before jumping to payload to avoid leaking our sealing key.
 
+    info!("Expecting a bug making MMIO_GUARD_UNMAP return NOT_SUPPORTED on success");
+    memory.mmio_unmap_all().map_err(|e| {
+        error!("Failed to unshare MMIO ranges: {e}");
+        RebootReason::InternalError
+    })?;
     mmio_guard::unmap(console::BASE_ADDRESS).map_err(|e| {
         error!("Failed to unshare the UART: {e}");
         RebootReason::InternalError
@@ -361,12 +368,10 @@
         }
     }
 
-    fn get_bcc_mut(&mut self) -> Option<&mut [u8]> {
-        let bcc = match self {
+    fn get_bcc_mut(&mut self) -> &mut [u8] {
+        match self {
             Self::LegacyBcc(ref mut bcc) => bcc,
             Self::Config(ref mut cfg) => cfg.get_bcc_mut(),
-        };
-        // TODO(b/256148034): return None if BccHandoverParse(bcc) != kDiceResultOk.
-        Some(bcc)
+        }
     }
 }
diff --git a/pvmfw/src/main.rs b/pvmfw/src/main.rs
index d453e26..07cbd0c 100644
--- a/pvmfw/src/main.rs
+++ b/pvmfw/src/main.rs
@@ -29,18 +29,26 @@
 mod memory;
 mod mmio_guard;
 mod mmu;
+mod pci;
 mod smccc;
 
-use crate::entry::RebootReason;
+use crate::{
+    entry::RebootReason,
+    memory::MemoryTracker,
+    pci::{map_cam, pci_node},
+};
 use avb::PUBLIC_KEY;
 use avb_nostd::verify_image;
-use log::{debug, error, info};
+use dice::bcc;
+use libfdt::Fdt;
+use log::{debug, error, info, trace};
 
 fn main(
-    fdt: &libfdt::Fdt,
+    fdt: &Fdt,
     signed_kernel: &[u8],
     ramdisk: Option<&[u8]>,
-    bcc: &[u8],
+    bcc: &bcc::Handover,
+    memory: &mut MemoryTracker,
 ) -> Result<(), RebootReason> {
     info!("pVM firmware");
     debug!("FDT: {:?}", fdt as *const libfdt::Fdt);
@@ -50,7 +58,12 @@
     } else {
         debug!("Ramdisk: None");
     }
-    debug!("BCC: {:?} ({:#x} bytes)", bcc.as_ptr(), bcc.len());
+    trace!("BCC: {bcc:x?}");
+
+    // Set up PCI bus for VirtIO devices.
+    let pci_node = pci_node(fdt)?;
+    map_cam(&pci_node, memory)?;
+
     verify_image(signed_kernel, PUBLIC_KEY).map_err(|e| {
         error!("Failed to verify the payload: {e}");
         RebootReason::PayloadVerificationError
diff --git a/pvmfw/src/memory.rs b/pvmfw/src/memory.rs
index ca0b886..ca1024d 100644
--- a/pvmfw/src/memory.rs
+++ b/pvmfw/src/memory.rs
@@ -14,7 +14,8 @@
 
 //! Low-level allocation and tracking of main memory.
 
-use crate::helpers;
+use crate::helpers::{self, page_4kb_of, SIZE_4KB};
+use crate::mmio_guard;
 use crate::mmu;
 use core::cmp::max;
 use core::cmp::min;
@@ -43,8 +44,7 @@
 impl MemoryRegion {
     /// True if the instance overlaps with the passed range.
     pub fn overlaps(&self, range: &MemoryRange) -> bool {
-        let our: &MemoryRange = self.as_ref();
-        max(our.start, range.start) < min(our.end, range.end)
+        overlaps(&self.range, range)
     }
 
     /// True if the instance is fully contained within the passed range.
@@ -60,11 +60,17 @@
     }
 }
 
+/// Returns true if one range overlaps with the other at all.
+fn overlaps<T: Copy + Ord>(a: &Range<T>, b: &Range<T>) -> bool {
+    max(a.start, b.start) < min(a.end, b.end)
+}
+
 /// Tracks non-overlapping slices of main memory.
 pub struct MemoryTracker {
-    regions: ArrayVec<[MemoryRegion; MemoryTracker::CAPACITY]>,
     total: MemoryRange,
     page_table: mmu::PageTable,
+    regions: ArrayVec<[MemoryRegion; MemoryTracker::CAPACITY]>,
+    mmio_regions: ArrayVec<[MemoryRange; MemoryTracker::MMIO_CAPACITY]>,
 }
 
 /// Errors for MemoryTracker operations.
@@ -84,6 +90,8 @@
     Overlaps,
     /// Region couldn't be mapped.
     FailedToMap,
+    /// Error from an MMIO guard call.
+    MmioGuard(mmio_guard::Error),
 }
 
 impl fmt::Display for MemoryTrackerError {
@@ -96,14 +104,22 @@
             Self::OutOfRange => write!(f, "Region is out of the tracked memory address space"),
             Self::Overlaps => write!(f, "New region overlaps with tracked regions"),
             Self::FailedToMap => write!(f, "Failed to map the new region"),
+            Self::MmioGuard(e) => e.fmt(f),
         }
     }
 }
 
+impl From<mmio_guard::Error> for MemoryTrackerError {
+    fn from(e: mmio_guard::Error) -> Self {
+        Self::MmioGuard(e)
+    }
+}
+
 type Result<T> = result::Result<T, MemoryTrackerError>;
 
 impl MemoryTracker {
     const CAPACITY: usize = 5;
+    const MMIO_CAPACITY: usize = 5;
     /// Base of the system's contiguous "main" memory.
     const BASE: usize = 0x8000_0000;
     /// First address that can't be translated by a level 1 TTBR0_EL1.
@@ -111,7 +127,12 @@
 
     /// Create a new instance from an active page table, covering the maximum RAM size.
     pub fn new(page_table: mmu::PageTable) -> Self {
-        Self { total: Self::BASE..Self::MAX_ADDR, page_table, regions: ArrayVec::new() }
+        Self {
+            total: Self::BASE..Self::MAX_ADDR,
+            page_table,
+            regions: ArrayVec::new(),
+            mmio_regions: ArrayVec::new(),
+        }
     }
 
     /// Resize the total RAM size.
@@ -134,20 +155,24 @@
 
     /// Allocate the address range for a const slice; returns None if failed.
     pub fn alloc_range(&mut self, range: &MemoryRange) -> Result<MemoryRange> {
+        let region = MemoryRegion { range: range.clone(), mem_type: MemoryType::ReadOnly };
+        self.check(&region)?;
         self.page_table.map_rodata(range).map_err(|e| {
             error!("Error during range allocation: {e}");
             MemoryTrackerError::FailedToMap
         })?;
-        self.add(MemoryRegion { range: range.clone(), mem_type: MemoryType::ReadOnly })
+        self.add(region)
     }
 
     /// Allocate the address range for a mutable slice; returns None if failed.
     pub fn alloc_range_mut(&mut self, range: &MemoryRange) -> Result<MemoryRange> {
+        let region = MemoryRegion { range: range.clone(), mem_type: MemoryType::ReadWrite };
+        self.check(&region)?;
         self.page_table.map_data(range).map_err(|e| {
             error!("Error during mutable range allocation: {e}");
             MemoryTrackerError::FailedToMap
         })?;
-        self.add(MemoryRegion { range: range.clone(), mem_type: MemoryType::ReadWrite })
+        self.add(region)
     }
 
     /// Allocate the address range for a const slice; returns None if failed.
@@ -160,24 +185,77 @@
         self.alloc_range_mut(&(base..(base + size.get())))
     }
 
-    fn add(&mut self, region: MemoryRegion) -> Result<MemoryRange> {
+    /// Checks that the given range of addresses is within the MMIO region, and then maps it
+    /// appropriately.
+    pub fn map_mmio_range(&mut self, range: MemoryRange) -> Result<()> {
+        // MMIO space is below the main memory region.
+        if range.end > self.total.start {
+            return Err(MemoryTrackerError::OutOfRange);
+        }
+        if self.mmio_regions.iter().any(|r| overlaps(r, &range)) {
+            return Err(MemoryTrackerError::Overlaps);
+        }
+        if self.mmio_regions.len() == self.mmio_regions.capacity() {
+            return Err(MemoryTrackerError::Full);
+        }
+
+        self.page_table.map_device(&range).map_err(|e| {
+            error!("Error during MMIO device mapping: {e}");
+            MemoryTrackerError::FailedToMap
+        })?;
+
+        for page_base in page_iterator(&range) {
+            mmio_guard::map(page_base)?;
+        }
+
+        if self.mmio_regions.try_push(range).is_some() {
+            return Err(MemoryTrackerError::Full);
+        }
+
+        Ok(())
+    }
+
+    /// Checks that the given region is within the range of the `MemoryTracker` and doesn't overlap
+    /// with any other previously allocated regions, and that the regions ArrayVec has capacity to
+    /// add it.
+    fn check(&self, region: &MemoryRegion) -> Result<()> {
         if !region.is_within(&self.total) {
             return Err(MemoryTrackerError::OutOfRange);
         }
-        if self.regions.iter().any(|r| r.overlaps(region.as_ref())) {
+        if self.regions.iter().any(|r| r.overlaps(&region.range)) {
             return Err(MemoryTrackerError::Overlaps);
         }
+        if self.regions.len() == self.regions.capacity() {
+            return Err(MemoryTrackerError::Full);
+        }
+        Ok(())
+    }
+
+    fn add(&mut self, region: MemoryRegion) -> Result<MemoryRange> {
         if self.regions.try_push(region).is_some() {
             return Err(MemoryTrackerError::Full);
         }
 
         Ok(self.regions.last().unwrap().as_ref().clone())
     }
+
+    /// Unmaps all tracked MMIO regions from the MMIO guard.
+    ///
+    /// Note that they are not unmapped from the page table.
+    pub fn mmio_unmap_all(&self) -> Result<()> {
+        for region in &self.mmio_regions {
+            for page_base in page_iterator(region) {
+                mmio_guard::unmap(page_base)?;
+            }
+        }
+
+        Ok(())
+    }
 }
 
 impl Drop for MemoryTracker {
     fn drop(&mut self) {
-        for region in self.regions.iter() {
+        for region in &self.regions {
             match region.mem_type {
                 MemoryType::ReadWrite => {
                     // TODO: Use page table's dirty bit to only flush pages that were touched.
@@ -188,3 +266,8 @@
         }
     }
 }
+
+/// Returns an iterator which yields the base address of each 4 KiB page within the given range.
+fn page_iterator(range: &MemoryRange) -> impl Iterator<Item = usize> {
+    (page_4kb_of(range.start)..range.end).step_by(SIZE_4KB)
+}
diff --git a/pvmfw/src/mmio_guard.rs b/pvmfw/src/mmio_guard.rs
index eb6c1fa..28f928f 100644
--- a/pvmfw/src/mmio_guard.rs
+++ b/pvmfw/src/mmio_guard.rs
@@ -105,7 +105,6 @@
     args[0] = ipa;
 
     // TODO(b/251426790): pKVM currently returns NOT_SUPPORTED for SUCCESS.
-    info!("Expecting a bug making MMIO_GUARD_UNMAP return NOT_SUPPORTED on success");
     match smccc::checked_hvc64_expect_zero(VENDOR_HYP_KVM_MMIO_GUARD_UNMAP_FUNC_ID, args) {
         Err(smccc::Error::NotSupported) | Ok(_) => Ok(()),
         x => x,
diff --git a/pvmfw/src/pci.rs b/pvmfw/src/pci.rs
new file mode 100644
index 0000000..7baabed
--- /dev/null
+++ b/pvmfw/src/pci.rs
@@ -0,0 +1,74 @@
+// Copyright 2022, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Functions to scan the PCI bus for VirtIO device and allocate BARs.
+
+use crate::{entry::RebootReason, memory::MemoryTracker};
+use core::ffi::CStr;
+use libfdt::{Fdt, FdtNode};
+use log::{debug, error};
+
+/// PCI MMIO configuration region size.
+const PCI_CFG_SIZE: usize = 0x100_0000;
+
+/// Finds an FDT node with compatible=pci-host-cam-generic.
+pub fn pci_node(fdt: &Fdt) -> Result<FdtNode, RebootReason> {
+    fdt.compatible_nodes(CStr::from_bytes_with_nul(b"pci-host-cam-generic\0").unwrap())
+        .map_err(|e| {
+            error!("Failed to find PCI bus in FDT: {}", e);
+            RebootReason::InvalidFdt
+        })?
+        .next()
+        .ok_or(RebootReason::InvalidFdt)
+}
+
+pub fn map_cam(pci_node: &FdtNode, memory: &mut MemoryTracker) -> Result<(), RebootReason> {
+    // Parse reg property to find CAM.
+    let pci_reg = pci_node
+        .reg()
+        .map_err(|e| {
+            error!("Error getting reg property from PCI node: {}", e);
+            RebootReason::InvalidFdt
+        })?
+        .ok_or_else(|| {
+            error!("PCI node missing reg property.");
+            RebootReason::InvalidFdt
+        })?
+        .next()
+        .ok_or_else(|| {
+            error!("Empty reg property on PCI node.");
+            RebootReason::InvalidFdt
+        })?;
+    let cam_addr = pci_reg.addr as usize;
+    let cam_size = pci_reg.size.ok_or_else(|| {
+        error!("PCI reg property missing size.");
+        RebootReason::InvalidFdt
+    })? as usize;
+    debug!("Found PCI CAM at {:#x}-{:#x}", cam_addr, cam_addr + cam_size);
+    // Check that the CAM is the size we expect, so we don't later try accessing it beyond its
+    // bounds. If it is a different size then something is very wrong and we shouldn't continue to
+    // access it; maybe there is some new version of PCI we don't know about.
+    if cam_size != PCI_CFG_SIZE {
+        error!("FDT says PCI CAM is {} bytes but we expected {}.", cam_size, PCI_CFG_SIZE);
+        return Err(RebootReason::InvalidFdt);
+    }
+
+    // Map the CAM as MMIO.
+    memory.map_mmio_range(cam_addr..cam_addr + cam_size).map_err(|e| {
+        error!("Failed to map PCI CAM: {}", e);
+        RebootReason::InternalError
+    })?;
+
+    Ok(())
+}
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 cc1bbfd..fa8be93 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -324,6 +324,52 @@
     }
 
     @Test
+    @CddTest(requirements = {"9.17/C-1-1"})
+    public void vmFilesStoredInDeDirWhenCreatedFromDEContext() throws Exception {
+        final Context ctx = getContext().createDeviceProtectedStorageContext();
+        final int userId = ctx.getUserId();
+        final VirtualMachineManager vmm = ctx.getSystemService(VirtualMachineManager.class);
+        VirtualMachineConfig config =
+                newVmConfigBuilder().setPayloadBinaryPath("binary/path").build();
+        try {
+            VirtualMachine vm = vmm.create("vm-name", config);
+            // TODO(b/261430346): what about non-primary user?
+            assertThat(vm.getRootDir().getAbsolutePath())
+                    .isEqualTo(
+                            "/data/user_de/" + userId + "/com.android.microdroid.test/vm/vm-name");
+        } finally {
+            vmm.delete("vm-name");
+        }
+    }
+
+    @Test
+    @CddTest(requirements = {"9.17/C-1-1"})
+    public void vmFilesStoredInCeDirWhenCreatedFromCEContext() throws Exception {
+        final Context ctx = getContext().createCredentialProtectedStorageContext();
+        final int userId = ctx.getUserId();
+        final VirtualMachineManager vmm = ctx.getSystemService(VirtualMachineManager.class);
+        VirtualMachineConfig config =
+                newVmConfigBuilder().setPayloadBinaryPath("binary/path").build();
+        try {
+            VirtualMachine vm = vmm.create("vm-name", config);
+            // TODO(b/261430346): what about non-primary user?
+            assertThat(vm.getRootDir().getAbsolutePath())
+                    .isEqualTo("/data/user/" + userId + "/com.android.microdroid.test/vm/vm-name");
+        } finally {
+            vmm.delete("vm-name");
+        }
+    }
+
+    @Test
+    @CddTest(requirements = {"9.17/C-1-1"})
+    public void differentManagersForDifferentContexts() throws Exception {
+        final Context ceCtx = getContext().createCredentialProtectedStorageContext();
+        final Context deCtx = getContext().createDeviceProtectedStorageContext();
+        assertThat(ceCtx.getSystemService(VirtualMachineManager.class))
+                .isNotSameInstanceAs(deCtx.getSystemService(VirtualMachineManager.class));
+    }
+
+    @Test
     @CddTest(requirements = {
             "9.17/C-1-1",
             "9.17/C-1-2",