Merge changes Icea5d27b,Ie6e0d01c

* changes:
  Update kernel to builds 9365138
  Update kernel to builds 9365138
diff --git a/javalib/api/system-current.txt b/javalib/api/system-current.txt
index 16995c5..f38d8fd 100644
--- a/javalib/api/system-current.txt
+++ b/javalib/api/system-current.txt
@@ -61,7 +61,6 @@
     method @IntRange(from=0) public int getMemoryMib();
     method @IntRange(from=1) public int getNumCpus();
     method @Nullable public String getPayloadBinaryPath();
-    method @Nullable public String getPayloadConfigPath();
     method public boolean isCompatibleWith(@NonNull android.system.virtualmachine.VirtualMachineConfig);
     method public boolean isProtectedVm();
     field public static final int DEBUG_LEVEL_APP_ONLY = 1; // 0x1
@@ -77,7 +76,6 @@
     method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setMemoryMib(@IntRange(from=0) int);
     method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setNumCpus(@IntRange(from=1) int);
     method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setPayloadBinaryPath(@NonNull String);
-    method @NonNull @RequiresPermission(android.system.virtualmachine.VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION) public android.system.virtualmachine.VirtualMachineConfig.Builder setPayloadConfigPath(@NonNull String);
     method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setProtectedVm(boolean);
   }
 
diff --git a/javalib/api/test-current.txt b/javalib/api/test-current.txt
index d802177..42ad060 100644
--- a/javalib/api/test-current.txt
+++ b/javalib/api/test-current.txt
@@ -1 +1,13 @@
 // Signature format: 2.0
+package android.system.virtualmachine {
+
+  public final class VirtualMachineConfig {
+    method @Nullable public String getPayloadConfigPath();
+  }
+
+  public static final class VirtualMachineConfig.Builder {
+    method @NonNull @RequiresPermission(android.system.virtualmachine.VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION) public android.system.virtualmachine.VirtualMachineConfig.Builder setPayloadConfigPath(@NonNull String);
+  }
+
+}
+
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
index b432bde..f9f29a1 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -27,6 +27,7 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.content.Context;
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
@@ -284,7 +285,7 @@
      *
      * @hide
      */
-    @SystemApi // TODO(b/243512115): Switch back to @TestApi
+    @TestApi
     @Nullable
     public String getPayloadConfigPath() {
         return mPayloadConfigPath;
@@ -471,7 +472,7 @@
          * @hide
          */
         @RequiresPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION)
-        @SystemApi // TODO(b/243512115): Switch to @TestApi
+        @TestApi
         @NonNull
         public Builder setPayloadConfigPath(@NonNull String payloadConfigPath) {
             mPayloadConfigPath = requireNonNull(payloadConfigPath);
diff --git a/libs/apkverify/Android.bp b/libs/apkverify/Android.bp
index 1862820..e556842 100644
--- a/libs/apkverify/Android.bp
+++ b/libs/apkverify/Android.bp
@@ -12,6 +12,7 @@
         "libanyhow",
         "libbyteorder",
         "libbytes",
+        "libhex",
         "liblog_rust",
         "libnum_traits",
         "libopenssl",
@@ -33,7 +34,6 @@
     name: "libapkverify.test",
     defaults: ["libapkverify.defaults"],
     test_suites: ["general-tests"],
-    rustlibs: ["libhex"],
     data: ["tests/data/*"],
 }
 
diff --git a/libs/apkverify/src/sigutil.rs b/libs/apkverify/src/sigutil.rs
index bfa51c1..395b493 100644
--- a/libs/apkverify/src/sigutil.rs
+++ b/libs/apkverify/src/sigutil.rs
@@ -235,7 +235,7 @@
     use std::fs::File;
     use std::mem::size_of_val;
 
-    use crate::v3::{to_hex_string, APK_SIGNATURE_SCHEME_V3_BLOCK_ID};
+    use crate::v3::APK_SIGNATURE_SCHEME_V3_BLOCK_ID;
 
     const CENTRAL_DIRECTORY_HEADER_SIGNATURE: u32 = 0x02014b50;
 
@@ -276,8 +276,8 @@
         let mut apk_sections = ApkSections::new(apk_file).unwrap();
         let digest = apk_sections.compute_digest(SignatureAlgorithmID::DsaWithSha256).unwrap();
         assert_eq!(
-            "0DF2426EA33AEDAF495D88E5BE0C6A1663FF0A81C5ED12D5B2929AE4B4300F2F",
-            to_hex_string(&digest[..])
+            "0df2426ea33aedaf495d88e5be0c6a1663ff0a81c5ed12d5b2929ae4b4300f2f",
+            hex::encode(&digest[..])
         );
     }
 
diff --git a/libs/apkverify/src/v3.rs b/libs/apkverify/src/v3.rs
index db7d8cc..fcd966b 100644
--- a/libs/apkverify/src/v3.rs
+++ b/libs/apkverify/src/v3.rs
@@ -196,8 +196,8 @@
         ensure!(
             computed == digest.digest.as_ref(),
             "Digest mismatch: computed={:?} vs expected={:?}",
-            to_hex_string(&computed),
-            to_hex_string(&digest.digest),
+            hex::encode(&computed),
+            hex::encode(digest.digest.as_ref()),
         );
 
         // 7. Verify that public key of the first certificate of certificates is identical
@@ -261,8 +261,3 @@
         Ok(PKey::public_key_from_der(raw_public_key.as_ref())?)
     }
 }
-
-#[inline]
-pub(crate) fn to_hex_string(buf: &[u8]) -> String {
-    buf.iter().map(|b| format!("{:02X}", b)).collect()
-}
diff --git a/libs/libfdt/src/iterators.rs b/libs/libfdt/src/iterators.rs
new file mode 100644
index 0000000..41fd492
--- /dev/null
+++ b/libs/libfdt/src/iterators.rs
@@ -0,0 +1,207 @@
+// 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.
+
+//! Iterators over cells, and various layers on top of them.
+
+use crate::{AddrCells, SizeCells};
+use core::marker::PhantomData;
+use core::{mem::size_of, ops::Range, slice::ChunksExact};
+
+/// Iterator over cells of a DT property.
+#[derive(Debug)]
+pub struct CellIterator<'a> {
+    chunks: ChunksExact<'a, u8>,
+}
+
+impl<'a> CellIterator<'a> {
+    pub(crate) fn new(bytes: &'a [u8]) -> Self {
+        const CHUNK_SIZE: usize = size_of::<<CellIterator as Iterator>::Item>();
+
+        Self { chunks: bytes.chunks_exact(CHUNK_SIZE) }
+    }
+}
+
+impl<'a> Iterator for CellIterator<'a> {
+    type Item = u32;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        Some(Self::Item::from_be_bytes(self.chunks.next()?.try_into().ok()?))
+    }
+}
+
+/// Iterator over a 'reg' property of a DT node.
+#[derive(Debug)]
+pub struct RegIterator<'a> {
+    cells: CellIterator<'a>,
+    addr_cells: AddrCells,
+    size_cells: SizeCells,
+}
+
+/// Represents a contiguous region within the address space defined by the parent bus.
+/// Commonly means the offsets and lengths of MMIO blocks, but may have a different meaning on some
+/// bus types. Addresses in the address space defined by the root node are CPU real addresses.
+#[derive(Copy, Clone, Debug)]
+pub struct Reg<T> {
+    /// Base address of the region.
+    pub addr: T,
+    /// Size of the region (optional).
+    pub size: Option<T>,
+}
+
+impl<'a> RegIterator<'a> {
+    pub(crate) fn new(
+        cells: CellIterator<'a>,
+        addr_cells: AddrCells,
+        size_cells: SizeCells,
+    ) -> Self {
+        Self { cells, addr_cells, size_cells }
+    }
+}
+
+impl<'a> Iterator for RegIterator<'a> {
+    type Item = Reg<u64>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let addr = FromAddrCells::from_addr_cells(&mut self.cells, self.addr_cells)?;
+        // If the parent node specifies a value of 0 for #size-cells, 'size' shall be omitted.
+        let size = if self.size_cells == SizeCells::None {
+            None
+        } else {
+            Some(FromSizeCells::from_size_cells(&mut self.cells, self.size_cells)?)
+        };
+
+        Some(Self::Item { addr, size })
+    }
+}
+
+/// Iterator over the address ranges defined by the /memory/ node.
+#[derive(Debug)]
+pub struct MemRegIterator<'a> {
+    reg: RegIterator<'a>,
+}
+
+impl<'a> MemRegIterator<'a> {
+    pub(crate) fn new(reg: RegIterator<'a>) -> Self {
+        Self { reg }
+    }
+}
+
+impl<'a> Iterator for MemRegIterator<'a> {
+    type Item = Range<usize>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let next = self.reg.next()?;
+        let addr = usize::try_from(next.addr).ok()?;
+        let size = usize::try_from(next.size?).ok()?;
+
+        Some(addr..addr.checked_add(size)?)
+    }
+}
+
+/// Iterator over the 'ranges' property of a DT node.
+#[derive(Debug)]
+pub struct RangesIterator<'a, A, P, S> {
+    cells: CellIterator<'a>,
+    addr_cells: AddrCells,
+    parent_addr_cells: AddrCells,
+    size_cells: SizeCells,
+    _addr: PhantomData<A>,
+    _parent_addr: PhantomData<P>,
+    _size: PhantomData<S>,
+}
+
+/// An address range from the 'ranges' property of a DT node.
+#[derive(Clone, Debug)]
+pub struct AddressRange<A, P, S> {
+    /// The physical address of the range within the child bus's address space.
+    pub addr: A,
+    /// The physical address of the range in the parent bus's address space.
+    pub parent_addr: P,
+    /// The size of the range in the child's address space.
+    pub size: S,
+}
+
+impl<'a, A, P, S> RangesIterator<'a, A, P, S> {
+    pub(crate) fn new(
+        cells: CellIterator<'a>,
+        addr_cells: AddrCells,
+        parent_addr_cells: AddrCells,
+        size_cells: SizeCells,
+    ) -> Self {
+        Self {
+            cells,
+            addr_cells,
+            parent_addr_cells,
+            size_cells,
+            _addr: Default::default(),
+            _parent_addr: Default::default(),
+            _size: Default::default(),
+        }
+    }
+}
+
+impl<'a, A: FromAddrCells, P: FromAddrCells, S: FromSizeCells> Iterator
+    for RangesIterator<'a, A, P, S>
+{
+    type Item = AddressRange<A, P, S>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let addr = FromAddrCells::from_addr_cells(&mut self.cells, self.addr_cells)?;
+        let parent_addr = FromAddrCells::from_addr_cells(&mut self.cells, self.parent_addr_cells)?;
+        let size = FromSizeCells::from_size_cells(&mut self.cells, self.size_cells)?;
+        Some(AddressRange { addr, parent_addr, size })
+    }
+}
+
+trait FromAddrCells: Sized {
+    fn from_addr_cells(cells: &mut CellIterator, cell_count: AddrCells) -> Option<Self>;
+}
+
+impl FromAddrCells for u64 {
+    fn from_addr_cells(cells: &mut CellIterator, cell_count: AddrCells) -> Option<Self> {
+        Some(match cell_count {
+            AddrCells::Single => cells.next()?.into(),
+            AddrCells::Double => (cells.next()? as Self) << 32 | cells.next()? as Self,
+            _ => panic!("Invalid addr_cells {:?} for u64", cell_count),
+        })
+    }
+}
+
+impl FromAddrCells for u128 {
+    fn from_addr_cells(cells: &mut CellIterator, cell_count: AddrCells) -> Option<Self> {
+        Some(match cell_count {
+            AddrCells::Single => cells.next()?.into(),
+            AddrCells::Double => (cells.next()? as Self) << 32 | cells.next()? as Self,
+            AddrCells::Triple => {
+                (cells.next()? as Self) << 64
+                    | (cells.next()? as Self) << 32
+                    | cells.next()? as Self
+            }
+        })
+    }
+}
+
+trait FromSizeCells: Sized {
+    fn from_size_cells(cells: &mut CellIterator, cell_count: SizeCells) -> Option<Self>;
+}
+
+impl FromSizeCells for u64 {
+    fn from_size_cells(cells: &mut CellIterator, cell_count: SizeCells) -> Option<Self> {
+        Some(match cell_count {
+            SizeCells::Single => cells.next()?.into(),
+            SizeCells::Double => (cells.next()? as Self) << 32 | cells.next()? as Self,
+            _ => panic!("Invalid size_cells {:?} for u64", cell_count),
+        })
+    }
+}
diff --git a/libs/libfdt/src/lib.rs b/libs/libfdt/src/lib.rs
index ff1db63..64e6746 100644
--- a/libs/libfdt/src/lib.rs
+++ b/libs/libfdt/src/lib.rs
@@ -18,12 +18,14 @@
 #![no_std]
 #![feature(let_else)] // Stabilized in 1.65.0
 
+mod iterators;
+
+pub use iterators::{AddressRange, CellIterator, MemRegIterator, RangesIterator, Reg, RegIterator};
+
 use core::ffi::{c_int, c_void, CStr};
 use core::fmt;
 use core::mem;
-use core::ops::Range;
 use core::result;
-use core::slice;
 
 /// Error type corresponding to libfdt error codes.
 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
@@ -146,10 +148,11 @@
 }
 
 /// Value of a #address-cells property.
-#[derive(Copy, Clone, Debug)]
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
 enum AddrCells {
     Single = 1,
     Double = 2,
+    Triple = 3,
 }
 
 impl TryFrom<c_int> for AddrCells {
@@ -159,13 +162,14 @@
         match fdt_err(res)? {
             x if x == Self::Single as c_int => Ok(Self::Single),
             x if x == Self::Double as c_int => Ok(Self::Double),
+            x if x == Self::Triple as c_int => Ok(Self::Triple),
             _ => Err(FdtError::BadNCells),
         }
     }
 }
 
 /// Value of a #size-cells property.
-#[derive(Copy, Clone, Debug)]
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
 enum SizeCells {
     None = 0,
     Single = 1,
@@ -185,98 +189,6 @@
     }
 }
 
-/// Iterator over cells of a DT property.
-#[derive(Debug)]
-pub struct CellIterator<'a> {
-    chunks: slice::ChunksExact<'a, u8>,
-}
-
-impl<'a> CellIterator<'a> {
-    fn new(bytes: &'a [u8]) -> Self {
-        const CHUNK_SIZE: usize = mem::size_of::<<CellIterator as Iterator>::Item>();
-
-        Self { chunks: bytes.chunks_exact(CHUNK_SIZE) }
-    }
-}
-
-impl<'a> Iterator for CellIterator<'a> {
-    type Item = u32;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        Some(Self::Item::from_be_bytes(self.chunks.next()?.try_into().ok()?))
-    }
-}
-
-/// Iterator over a 'reg' property of a DT node.
-#[derive(Debug)]
-pub struct RegIterator<'a> {
-    cells: CellIterator<'a>,
-    addr_cells: AddrCells,
-    size_cells: SizeCells,
-}
-
-/// Represents a contiguous region within the address space defined by the parent bus.
-/// Commonly means the offsets and lengths of MMIO blocks, but may have a different meaning on some
-/// bus types. Addresses in the address space defined by the root node are CPU real addresses.
-#[derive(Copy, Clone, Debug)]
-pub struct Reg<T> {
-    /// Base address of the region.
-    pub addr: T,
-    /// Size of the region (optional).
-    pub size: Option<T>,
-}
-
-impl<'a> RegIterator<'a> {
-    fn new(cells: CellIterator<'a>, addr_cells: AddrCells, size_cells: SizeCells) -> Self {
-        Self { cells, addr_cells, size_cells }
-    }
-}
-
-impl<'a> Iterator for RegIterator<'a> {
-    type Item = Reg<u64>;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        let make_double = |a, b| (u64::from(a) << 32) | u64::from(b);
-
-        let addr = match self.addr_cells {
-            AddrCells::Single => self.cells.next()?.into(),
-            AddrCells::Double => make_double(self.cells.next()?, self.cells.next()?),
-        };
-        // If the parent node specifies a value of 0 for #size-cells, 'size' shall be omitted.
-        let size = match self.size_cells {
-            SizeCells::None => None,
-            SizeCells::Single => Some(self.cells.next()?.into()),
-            SizeCells::Double => Some(make_double(self.cells.next()?, self.cells.next()?)),
-        };
-
-        Some(Self::Item { addr, size })
-    }
-}
-
-/// Iterator over the address ranges defined by the /memory/ node.
-#[derive(Debug)]
-pub struct MemRegIterator<'a> {
-    reg: RegIterator<'a>,
-}
-
-impl<'a> MemRegIterator<'a> {
-    fn new(reg: RegIterator<'a>) -> Self {
-        Self { reg }
-    }
-}
-
-impl<'a> Iterator for MemRegIterator<'a> {
-    type Item = Range<usize>;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        let next = self.reg.next()?;
-        let addr = usize::try_from(next.addr).ok()?;
-        let size = usize::try_from(next.size?).ok()?;
-
-        Some(addr..addr.checked_add(size)?)
-    }
-}
-
 /// DT node.
 #[derive(Clone, Copy)]
 pub struct FdtNode<'a> {
@@ -314,6 +226,25 @@
         }
     }
 
+    /// Retrieves the standard ranges property.
+    pub fn ranges<A, P, S>(&self) -> Result<Option<RangesIterator<'a, A, P, S>>> {
+        let ranges = CStr::from_bytes_with_nul(b"ranges\0").unwrap();
+        if let Some(cells) = self.getprop_cells(ranges)? {
+            let parent = self.parent()?;
+            let addr_cells = self.address_cells()?;
+            let parent_addr_cells = parent.address_cells()?;
+            let size_cells = self.size_cells()?;
+            Ok(Some(RangesIterator::<A, P, S>::new(
+                cells,
+                addr_cells,
+                parent_addr_cells,
+                size_cells,
+            )))
+        } else {
+            Ok(None)
+        }
+    }
+
     /// Retrieve the value of a given <string> property.
     pub fn getprop_str(&self, name: &CStr) -> Result<Option<&CStr>> {
         let value = if let Some(bytes) = self.getprop(name)? {
diff --git a/pvmfw/src/entry.rs b/pvmfw/src/entry.rs
index a274210..ee32509 100644
--- a/pvmfw/src/entry.rs
+++ b/pvmfw/src/entry.rs
@@ -32,7 +32,7 @@
 use vmbase::{console, layout, logger, main, power::reboot};
 
 #[derive(Debug, Clone)]
-enum RebootReason {
+pub(crate) enum RebootReason {
     /// A malformed BCC was received.
     InvalidBcc,
     /// An invalid configuration was appended to pvmfw.
@@ -59,7 +59,7 @@
 
     match main_wrapper(fdt_address as usize, payload_start as usize, payload_size as usize) {
         Ok(_) => jump_to_payload(fdt_address, payload_start),
-        Err(_) => reboot(),
+        Err(_) => reboot(), // TODO(b/220071963) propagate the reason back to the host.
     }
 
     // if we reach this point and return, vmbase::entry::rust_entry() will call power::shutdown().
@@ -225,10 +225,7 @@
     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).map_err(|e| {
-        error!("Failed to verify the payload: {e}");
-        RebootReason::PayloadVerificationError
-    })?;
+    crate::main(slices.fdt, slices.kernel, slices.ramdisk, bcc)?;
 
     // TODO: Overwrite BCC before jumping to payload to avoid leaking our sealing key.
 
diff --git a/pvmfw/src/main.rs b/pvmfw/src/main.rs
index 3d5629a..cf7e90a 100644
--- a/pvmfw/src/main.rs
+++ b/pvmfw/src/main.rs
@@ -31,17 +31,17 @@
 mod mmu;
 mod smccc;
 
+use crate::entry::RebootReason;
 use avb::PUBLIC_KEY;
-use avb_nostd::{verify_image, AvbImageVerifyError};
-use log::{debug, info};
+use avb_nostd::verify_image;
+use log::{debug, error, info};
 
-/// TODO(b/256148034): Return RebootReason as error here
 fn main(
     fdt: &libfdt::Fdt,
     signed_kernel: &[u8],
     ramdisk: Option<&[u8]>,
     bcc: &[u8],
-) -> Result<(), AvbImageVerifyError> {
+) -> Result<(), RebootReason> {
     info!("pVM firmware");
     debug!("FDT: {:?}", fdt as *const libfdt::Fdt);
     debug!("Signed kernel: {:?} ({:#x} bytes)", signed_kernel.as_ptr(), signed_kernel.len());
@@ -51,7 +51,10 @@
         debug!("Ramdisk: None");
     }
     debug!("BCC: {:?} ({:#x} bytes)", bcc.as_ptr(), bcc.len());
-    verify_image(signed_kernel, PUBLIC_KEY)?;
+    verify_image(signed_kernel, PUBLIC_KEY).map_err(|e| {
+        error!("Failed to verify the payload: {e}");
+        RebootReason::PayloadVerificationError
+    })?;
     info!("Payload verified. Starting payload...");
     Ok(())
 }
diff --git a/tests/helper/src/java/com/android/microdroid/test/common/ProcessUtil.java b/tests/helper/src/java/com/android/microdroid/test/common/ProcessUtil.java
index d85929d..940ec9c 100644
--- a/tests/helper/src/java/com/android/microdroid/test/common/ProcessUtil.java
+++ b/tests/helper/src/java/com/android/microdroid/test/common/ProcessUtil.java
@@ -99,12 +99,14 @@
             if (line.length() == 0) {
                 continue;
             }
-            if (line.contains(": ")) {
+            // Each line is '<metrics>:        <number> kB'.
+            // EX : Pss_Anon:        70712 kB
+            // EX : Active(file):     5792 kB
+            // EX : ProtectionKey:       0
+            if (line.matches("[\\w()]+:\\s+.*")) {
                 if (entries.size() == 0) {
                     throw new RuntimeException("unexpected line: " + line);
                 }
-                // Each line is '<metrics>:        <number> kB'.
-                // EX : Pss_Anon:        70712 kB
                 if (line.endsWith(" kB")) line = line.substring(0, line.length() - 3);
                 String[] elems = line.split(":");
                 String name = elems[0].trim();
diff --git a/vmbase/example/Android.bp b/vmbase/example/Android.bp
index 505de6b..e0a87db 100644
--- a/vmbase/example/Android.bp
+++ b/vmbase/example/Android.bp
@@ -14,6 +14,7 @@
         "libdice_nostd",
         "liblibfdt",
         "liblog_rust_nostd",
+        "libvirtio_drivers",
         "libvmbase",
     ],
     static_libs: [
diff --git a/vmbase/example/src/main.rs b/vmbase/example/src/main.rs
index bb64651..96c10a4 100644
--- a/vmbase/example/src/main.rs
+++ b/vmbase/example/src/main.rs
@@ -20,6 +20,7 @@
 
 mod exceptions;
 mod layout;
+mod pci;
 
 extern crate alloc;
 
@@ -27,17 +28,18 @@
     bionic_tls, dtb_range, print_addresses, rodata_range, stack_chk_guard, text_range,
     writable_region, DEVICE_REGION,
 };
-use aarch64_paging::{
-    idmap::IdMap,
-    paging::{Attributes, MemoryRegion},
-};
+use crate::pci::{check_pci, pci_node, PciMemory32Allocator};
+use aarch64_paging::{idmap::IdMap, paging::Attributes};
 use alloc::{vec, vec::Vec};
 use buddy_system_allocator::LockedHeap;
 use core::ffi::CStr;
 use libfdt::Fdt;
-use log::{info, LevelFilter};
+use log::{debug, info, trace, LevelFilter};
 use vmbase::{logger, main, println};
 
+/// PCI MMIO configuration region size.
+const AARCH64_PCI_CFG_SIZE: u64 = 0x1000000;
+
 static INITIALISED_DATA: [u32; 4] = [1, 2, 3, 4];
 static mut ZEROED_DATA: [u32; 10] = [0; 10];
 static mut MUTABLE_DATA: [u32; 4] = [1, 2, 3, 4];
@@ -62,7 +64,27 @@
     assert_eq!(arg0, dtb_range().start.0 as u64);
     check_data();
     check_stack_guard();
-    check_fdt();
+
+    info!("Checking FDT...");
+    let fdt = dtb_range();
+    let fdt =
+        unsafe { core::slice::from_raw_parts_mut(fdt.start.0 as *mut u8, fdt.end.0 - fdt.start.0) };
+    let fdt = Fdt::from_mut_slice(fdt).unwrap();
+    info!("FDT passed verification.");
+    check_fdt(fdt);
+
+    let pci_node = pci_node(fdt);
+    // Parse reg property to find CAM.
+    let pci_reg = pci_node.reg().unwrap().unwrap().next().unwrap();
+    debug!("Found PCI CAM at {:#x}-{:#x}", pci_reg.addr, pci_reg.addr + pci_reg.size.unwrap());
+    // 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.
+    assert_eq!(pci_reg.size.unwrap(), AARCH64_PCI_CFG_SIZE);
+    // Parse ranges property to find memory ranges from which to allocate PCI BARs.
+    let mut pci_allocator = PciMemory32Allocator::for_pci_ranges(&pci_node);
+
+    modify_fdt(fdt);
 
     unsafe {
         HEAP_ALLOCATOR.lock().init(HEAP.as_mut_ptr() as usize, HEAP.len());
@@ -93,14 +115,31 @@
             Attributes::NORMAL | Attributes::NON_GLOBAL | Attributes::EXECUTE_NEVER,
         )
         .unwrap();
+    idmap
+        .map_range(
+            &dtb_range().into(),
+            Attributes::NORMAL
+                | Attributes::NON_GLOBAL
+                | Attributes::READ_ONLY
+                | Attributes::EXECUTE_NEVER,
+        )
+        .unwrap();
+    idmap
+        .map_range(
+            &pci_allocator.get_region(),
+            Attributes::DEVICE_NGNRE | Attributes::EXECUTE_NEVER,
+        )
+        .unwrap();
 
     info!("Activating IdMap...");
-    info!("{:?}", idmap);
+    trace!("{:?}", idmap);
     idmap.activate();
     info!("Activated.");
 
     check_data();
     check_dice();
+
+    check_pci(pci_reg, &mut pci_allocator);
 }
 
 fn check_stack_guard() {
@@ -144,13 +183,7 @@
     info!("Data looks good");
 }
 
-fn check_fdt() {
-    info!("Checking FDT...");
-    let fdt = MemoryRegion::from(layout::dtb_range());
-    let fdt = unsafe { core::slice::from_raw_parts_mut(fdt.start().0 as *mut u8, fdt.len()) };
-
-    let reader = Fdt::from_slice(fdt).unwrap();
-    info!("FDT passed verification.");
+fn check_fdt(reader: &Fdt) {
     for reg in reader.memory().unwrap().unwrap() {
         info!("memory @ {reg:#x?}");
     }
@@ -161,8 +194,9 @@
         let reg = c.reg().unwrap().unwrap().next().unwrap();
         info!("node compatible with '{}' at {reg:?}", compatible.to_str().unwrap());
     }
+}
 
-    let writer = Fdt::from_mut_slice(fdt).unwrap();
+fn modify_fdt(writer: &mut Fdt) {
     writer.unpack().unwrap();
     info!("FDT successfully unpacked.");
 
diff --git a/vmbase/example/src/pci.rs b/vmbase/example/src/pci.rs
new file mode 100644
index 0000000..e4a4b04
--- /dev/null
+++ b/vmbase/example/src/pci.rs
@@ -0,0 +1,247 @@
+// 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 aarch64_paging::paging::MemoryRegion;
+use alloc::alloc::{alloc, dealloc, Layout};
+use core::ffi::CStr;
+use libfdt::{Fdt, FdtNode, Reg};
+use log::{debug, info};
+use virtio_drivers::{
+    pci::{
+        bus::{BarInfo, Cam, Command, DeviceFunction, MemoryBarType, PciRoot},
+        virtio_device_type, PciTransport,
+    },
+    DeviceType, Hal, PhysAddr, Transport, VirtAddr, VirtIOBlk, PAGE_SIZE,
+};
+
+/// The standard sector size of a VirtIO block device, in bytes.
+const SECTOR_SIZE_BYTES: u64 = 512;
+
+/// Finds an FDT node with compatible=pci-host-cam-generic.
+pub fn pci_node(fdt: &Fdt) -> FdtNode {
+    fdt.compatible_nodes(CStr::from_bytes_with_nul(b"pci-host-cam-generic\0").unwrap())
+        .unwrap()
+        .next()
+        .unwrap()
+}
+
+pub fn check_pci(reg: Reg<u64>, allocator: &mut PciMemory32Allocator) {
+    let mut pci_root = unsafe { PciRoot::new(reg.addr as *mut u8, Cam::MmioCam) };
+    for (device_function, info) in pci_root.enumerate_bus(0) {
+        let (status, command) = pci_root.get_status_command(device_function);
+        info!("Found {} at {}, status {:?} command {:?}", info, device_function, status, command);
+        if let Some(virtio_type) = virtio_device_type(&info) {
+            info!("  VirtIO {:?}", virtio_type);
+            allocate_bars(&mut pci_root, device_function, allocator);
+            let mut transport =
+                PciTransport::new::<HalImpl>(&mut pci_root, device_function).unwrap();
+            info!(
+                "Detected virtio PCI device with device type {:?}, features {:#018x}",
+                transport.device_type(),
+                transport.read_device_features(),
+            );
+            instantiate_virtio_driver(transport, virtio_type);
+        }
+    }
+}
+
+fn instantiate_virtio_driver(transport: impl Transport, device_type: DeviceType) {
+    if device_type == DeviceType::Block {
+        let blk = VirtIOBlk::<HalImpl, _>::new(transport).expect("failed to create blk driver");
+        info!("Found {} KiB block device.", blk.capacity() * SECTOR_SIZE_BYTES / 1024);
+    }
+}
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+struct PciMemoryFlags(u32);
+
+impl PciMemoryFlags {
+    pub fn prefetchable(self) -> bool {
+        self.0 & 0x80000000 != 0
+    }
+
+    pub fn range_type(self) -> PciRangeType {
+        PciRangeType::from((self.0 & 0x3000000) >> 24)
+    }
+}
+
+/// Allocates 32-bit memory addresses for PCI BARs.
+pub struct PciMemory32Allocator {
+    start: u32,
+    end: u32,
+}
+
+impl PciMemory32Allocator {
+    /// Creates a new allocator based on the ranges property of the given PCI node.
+    pub fn for_pci_ranges(pci_node: &FdtNode) -> Self {
+        let mut memory_32_address = 0;
+        let mut memory_32_size = 0;
+        for range in pci_node
+            .ranges::<u128, u64, u64>()
+            .expect("Error getting ranges property from PCI node")
+            .expect("PCI node missing ranges property.")
+        {
+            let flags = PciMemoryFlags((range.addr >> 64) as u32);
+            let prefetchable = flags.prefetchable();
+            let range_type = flags.range_type();
+            let bus_address = range.addr as u64;
+            let cpu_physical = range.parent_addr;
+            let size = range.size;
+            info!(
+                "range: {:?} {}prefetchable bus address: {:#018x} host physical address: {:#018x} size: {:#018x}",
+                range_type,
+                if prefetchable { "" } else { "non-" },
+                bus_address,
+                cpu_physical,
+                size,
+            );
+            if !prefetchable
+                && ((range_type == PciRangeType::Memory32 && size > memory_32_size.into())
+                    || (range_type == PciRangeType::Memory64
+                        && size > memory_32_size.into()
+                        && bus_address + size < u32::MAX.into()))
+            {
+                // Use the 64-bit range for 32-bit memory, if it is low enough.
+                assert_eq!(bus_address, cpu_physical);
+                memory_32_address = u32::try_from(cpu_physical).unwrap();
+                memory_32_size = u32::try_from(size).unwrap();
+            }
+        }
+        if memory_32_size == 0 {
+            panic!("No PCI memory regions found.");
+        }
+
+        Self { start: memory_32_address, end: memory_32_address + memory_32_size }
+    }
+
+    /// Gets a memory region covering the address space from which this allocator will allocate.
+    pub fn get_region(&self) -> MemoryRegion {
+        MemoryRegion::new(self.start as usize, self.end as usize)
+    }
+
+    /// Allocates a 32-bit memory address region for a PCI BAR of the given power-of-2 size.
+    ///
+    /// It will have alignment matching the size. The size must be a power of 2.
+    pub fn allocate_memory_32(&mut self, size: u32) -> Option<u32> {
+        assert!(size.is_power_of_two());
+        let allocated_address = align_up(self.start, size);
+        if allocated_address + size <= self.end {
+            self.start = allocated_address + size;
+            Some(allocated_address)
+        } else {
+            None
+        }
+    }
+}
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+enum PciRangeType {
+    ConfigurationSpace,
+    IoSpace,
+    Memory32,
+    Memory64,
+}
+
+impl From<u32> for PciRangeType {
+    fn from(value: u32) -> Self {
+        match value {
+            0 => Self::ConfigurationSpace,
+            1 => Self::IoSpace,
+            2 => Self::Memory32,
+            3 => Self::Memory64,
+            _ => panic!("Tried to convert invalid range type {}", value),
+        }
+    }
+}
+
+/// Allocates appropriately-sized memory regions and assigns them to the device's BARs.
+fn allocate_bars(
+    root: &mut PciRoot,
+    device_function: DeviceFunction,
+    allocator: &mut PciMemory32Allocator,
+) {
+    let mut bar_index = 0;
+    while bar_index < 6 {
+        let info = root.bar_info(device_function, bar_index).unwrap();
+        debug!("BAR {}: {}", bar_index, info);
+        // Ignore I/O bars, as they aren't required for the VirtIO driver.
+        if let BarInfo::Memory { address_type, size, .. } = info {
+            match address_type {
+                _ if size == 0 => {}
+                MemoryBarType::Width32 => {
+                    let address = allocator.allocate_memory_32(size).unwrap();
+                    debug!("Allocated address {:#010x}", address);
+                    root.set_bar_32(device_function, bar_index, address);
+                }
+                MemoryBarType::Width64 => {
+                    let address = allocator.allocate_memory_32(size).unwrap();
+                    debug!("Allocated address {:#010x}", address);
+                    root.set_bar_64(device_function, bar_index, address.into());
+                }
+                _ => panic!("Memory BAR address type {:?} not supported.", address_type),
+            }
+        }
+
+        bar_index += 1;
+        if info.takes_two_entries() {
+            bar_index += 1;
+        }
+    }
+
+    // Enable the device to use its BARs.
+    root.set_command(
+        device_function,
+        Command::IO_SPACE | Command::MEMORY_SPACE | Command::BUS_MASTER,
+    );
+    let (status, command) = root.get_status_command(device_function);
+    debug!("Allocated BARs and enabled device, status {:?} command {:?}", status, command);
+}
+
+const fn align_up(value: u32, alignment: u32) -> u32 {
+    ((value - 1) | (alignment - 1)) + 1
+}
+
+struct HalImpl;
+
+impl Hal for HalImpl {
+    fn dma_alloc(pages: usize) -> PhysAddr {
+        debug!("dma_alloc: pages={}", pages);
+        let layout = Layout::from_size_align(pages * PAGE_SIZE, PAGE_SIZE).unwrap();
+        // Safe because the layout has a non-zero size.
+        let vaddr = unsafe { alloc(layout) } as VirtAddr;
+        Self::virt_to_phys(vaddr)
+    }
+
+    fn dma_dealloc(paddr: PhysAddr, pages: usize) -> i32 {
+        debug!("dma_dealloc: paddr={:#x}, pages={}", paddr, pages);
+        let vaddr = Self::phys_to_virt(paddr);
+        let layout = Layout::from_size_align(pages * PAGE_SIZE, PAGE_SIZE).unwrap();
+        // Safe because the memory was allocated by `dma_alloc` above using the same allocator, and
+        // the layout is the same as was used then.
+        unsafe {
+            dealloc(vaddr as *mut u8, layout);
+        }
+        0
+    }
+
+    fn phys_to_virt(paddr: PhysAddr) -> VirtAddr {
+        paddr
+    }
+
+    fn virt_to_phys(vaddr: VirtAddr) -> PhysAddr {
+        vaddr
+    }
+}