pvmfw: Add MemoryTracker & MemorySlices
Add support for dynamically validating, mapping, and allocating
non-overlapping slices of main RAM in a safe manner in MemoryTracker and
use it in MemorySlices for the regions of interest, located through the
inputs.
Bug: 256148034
Bug: 249054080
Bug: 256827715
Test: atest MicrodroidTestApp
Change-Id: I25f8db10df763c0473c281bf96f30e6b5fbe1619
diff --git a/pvmfw/Android.bp b/pvmfw/Android.bp
index 1150b83..455b214 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -14,10 +14,14 @@
rustlibs: [
"libaarch64_paging",
"libbuddy_system_allocator",
+ "liblibfdt",
"liblog_rust_nostd",
"libpvmfw_embedded_key",
"libvmbase",
],
+ static_libs: [
+ "libarm-optimized-routines-mem",
+ ],
apex_available: ["com.android.virt"],
}
diff --git a/pvmfw/idmap.S b/pvmfw/idmap.S
index ec3ceaf..2ef0d42 100644
--- a/pvmfw/idmap.S
+++ b/pvmfw/idmap.S
@@ -40,13 +40,9 @@
/* level 1 */
.quad .L_BLOCK_DEV | 0x0 // 1 GB of device mappings
.quad .L_TT_TYPE_TABLE + 0f // Unmapped device memory, and pVM firmware
- .quad .L_TT_TYPE_TABLE + 1f // up to 1 GB of DRAM
- .fill 509, 8, 0x0 // 509 GB of remaining VA space
+ .fill 510, 8, 0x0 // 510 GB of remaining VA space
/* level 2 */
0: .fill 510, 8, 0x0
.quad .L_BLOCK_MEM_XIP | 0x7fc00000 // pVM firmware image
.quad .L_BLOCK_MEM | 0x7fe00000 // Writable memory for stack, heap &c.
-1: .quad .L_BLOCK_RO | 0x80000000 // DT provided by VMM
- .quad .L_BLOCK_RO | 0x80200000 // 2 MB of DRAM containing payload image
- .fill 510, 8, 0x0
diff --git a/pvmfw/src/entry.rs b/pvmfw/src/entry.rs
index 07dd0c5..18ff1c1 100644
--- a/pvmfw/src/entry.rs
+++ b/pvmfw/src/entry.rs
@@ -14,14 +14,18 @@
//! Low-level entry and exit points of pvmfw.
+use crate::fdt;
use crate::heap;
use crate::helpers;
+use crate::memory::MemoryTracker;
use crate::mmio_guard;
use crate::mmu;
use core::arch::asm;
+use core::num::NonZeroUsize;
use core::slice;
use log::debug;
use log::error;
+use log::info;
use log::LevelFilter;
use vmbase::{console, layout, logger, main, power::reboot};
@@ -31,6 +35,12 @@
InvalidBcc,
/// An unexpected internal error happened.
InternalError,
+ /// The provided FDT was invalid.
+ InvalidFdt,
+ /// The provided payload was invalid.
+ InvalidPayload,
+ /// The provided ramdisk was invalid.
+ InvalidRamdisk,
}
main!(start);
@@ -49,6 +59,98 @@
// if we reach this point and return, vmbase::entry::rust_entry() will call power::shutdown().
}
+struct MemorySlices<'a> {
+ fdt: &'a mut libfdt::Fdt,
+ kernel: &'a [u8],
+ ramdisk: Option<&'a [u8]>,
+}
+
+impl<'a> MemorySlices<'a> {
+ fn new(
+ fdt: usize,
+ payload: usize,
+ payload_size: usize,
+ memory: &mut MemoryTracker,
+ ) -> Result<Self, RebootReason> {
+ // SAFETY - SIZE_2MB is non-zero.
+ const FDT_SIZE: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(helpers::SIZE_2MB) };
+ // TODO - Only map the FDT as read-only, until we modify it right before jump_to_payload()
+ // e.g. by generating a DTBO for a template DT in main() and, on return, re-map DT as RW,
+ // overwrite with the template DT and apply the DTBO.
+ let range = memory.alloc_mut(fdt, FDT_SIZE).map_err(|e| {
+ error!("Failed to allocate the FDT range: {e}");
+ RebootReason::InternalError
+ })?;
+
+ // SAFETY - The tracker validated the range to be in main memory, mapped, and not overlap.
+ let fdt = unsafe { slice::from_raw_parts_mut(range.start as *mut u8, range.len()) };
+ let fdt = libfdt::Fdt::from_mut_slice(fdt).map_err(|e| {
+ error!("Failed to spawn the FDT wrapper: {e}");
+ RebootReason::InvalidFdt
+ })?;
+
+ debug!("Fdt passed validation!");
+
+ let memory_range = fdt
+ .memory()
+ .map_err(|e| {
+ error!("Failed to get /memory from the DT: {e}");
+ RebootReason::InvalidFdt
+ })?
+ .ok_or_else(|| {
+ error!("Node /memory was found empty");
+ RebootReason::InvalidFdt
+ })?
+ .next()
+ .ok_or_else(|| {
+ error!("Failed to read the memory size from the FDT");
+ RebootReason::InternalError
+ })?;
+
+ debug!("Resizing MemoryTracker to range {memory_range:#x?}");
+
+ memory.shrink(&memory_range).map_err(|_| {
+ error!("Failed to use memory range value from DT: {memory_range:#x?}");
+ RebootReason::InvalidFdt
+ })?;
+
+ let payload_size = NonZeroUsize::new(payload_size).ok_or_else(|| {
+ error!("Invalid payload size: {payload_size:#x}");
+ RebootReason::InvalidPayload
+ })?;
+
+ let payload_range = memory.alloc(payload, payload_size).map_err(|e| {
+ error!("Failed to obtain the payload range: {e}");
+ RebootReason::InternalError
+ })?;
+ // SAFETY - The tracker validated the range to be in main memory, mapped, and not overlap.
+ let kernel =
+ unsafe { slice::from_raw_parts(payload_range.start as *const u8, payload_range.len()) };
+
+ let ramdisk_range = fdt::initrd_range(fdt).map_err(|e| {
+ error!("An error occurred while locating the ramdisk in the device tree: {e}");
+ RebootReason::InternalError
+ })?;
+
+ let ramdisk = if let Some(r) = ramdisk_range {
+ debug!("Located ramdisk at {r:?}");
+ let r = memory.alloc_range(&r).map_err(|e| {
+ error!("Failed to obtain the initrd range: {e}");
+ RebootReason::InvalidRamdisk
+ })?;
+
+ // SAFETY - The region was validated by memory to be in main memory, mapped, and
+ // not overlap.
+ Some(unsafe { slice::from_raw_parts(r.start as *const u8, r.len()) })
+ } else {
+ info!("Couldn't locate the ramdisk from the device tree");
+ None
+ };
+
+ Ok(Self { fdt, kernel, ramdisk })
+ }
+}
+
/// Sets up the environment for main() and wraps its result for start().
///
/// Provide the abstractions necessary for start() to abort the pVM boot and for main() to run with
@@ -64,14 +166,6 @@
logger::init(LevelFilter::Info).map_err(|_| RebootReason::InternalError)?;
- const FDT_MAX_SIZE: usize = helpers::SIZE_2MB;
- // TODO: Check that the FDT is fully contained in RAM.
- // SAFETY - We trust the VMM, for now.
- let fdt = unsafe { slice::from_raw_parts_mut(fdt as *mut u8, FDT_MAX_SIZE) };
- // TODO: Check that the payload is fully contained in RAM and doesn't overlap with the FDT.
- // SAFETY - We trust the VMM, for now.
- let payload = unsafe { slice::from_raw_parts(payload as *const u8, payload_size) };
-
// Use debug!() to avoid printing to the UART if we failed to configure it as only local
// builds that have tweaked the logger::init() call will actually attempt to log the message.
@@ -121,8 +215,11 @@
unsafe { page_table.activate() };
debug!("... Success!");
+ let mut memory = MemoryTracker::new(page_table);
+ let slices = MemorySlices::new(fdt, payload, payload_size, &mut memory)?;
+
// This wrapper allows main() to be blissfully ignorant of platform details.
- crate::main(fdt, payload, bcc);
+ 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/fdt.rs b/pvmfw/src/fdt.rs
new file mode 100644
index 0000000..5b9efd2
--- /dev/null
+++ b/pvmfw/src/fdt.rs
@@ -0,0 +1,32 @@
+// 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.
+
+//! High-level FDT functions.
+
+use core::ffi::CStr;
+use core::ops::Range;
+
+/// Extract from /chosen the address range containing the pre-loaded ramdisk.
+pub fn initrd_range(fdt: &libfdt::Fdt) -> libfdt::Result<Option<Range<usize>>> {
+ let start = CStr::from_bytes_with_nul(b"linux,initrd-start\0").unwrap();
+ let end = CStr::from_bytes_with_nul(b"linux,initrd-end\0").unwrap();
+
+ if let Some(chosen) = fdt.chosen()? {
+ if let (Some(start), Some(end)) = (chosen.getprop_u32(start)?, chosen.getprop_u32(end)?) {
+ return Ok(Some((start as usize)..(end as usize)));
+ }
+ }
+
+ Ok(None)
+}
diff --git a/pvmfw/src/helpers.rs b/pvmfw/src/helpers.rs
index 59cf9f3..9062957 100644
--- a/pvmfw/src/helpers.rs
+++ b/pvmfw/src/helpers.rs
@@ -14,6 +14,8 @@
//! Miscellaneous helper functions.
+use core::arch::asm;
+
pub const SIZE_4KB: usize = 4 << 10;
pub const SIZE_2MB: usize = 2 << 20;
@@ -39,3 +41,30 @@
pub const fn page_4kb_of(addr: usize) -> usize {
unchecked_align_down(addr, SIZE_4KB)
}
+
+#[inline]
+fn min_dcache_line_size() -> usize {
+ const DMINLINE_SHIFT: usize = 16;
+ const DMINLINE_MASK: usize = 0xf;
+ let ctr_el0: usize;
+
+ unsafe { asm!("mrs {x}, ctr_el0", x = out(reg) ctr_el0) }
+
+ // DminLine: log2 of the number of words in the smallest cache line of all the data caches.
+ let dminline = (ctr_el0 >> DMINLINE_SHIFT) & DMINLINE_MASK;
+
+ 1 << dminline
+}
+
+#[inline]
+/// Flush data cache over the entire slice.
+pub fn flush_region(start: usize, size: usize) {
+ let line_size = min_dcache_line_size();
+ let end = start + size;
+ let start = unchecked_align_down(start, line_size);
+
+ for line in (start..end).step_by(line_size) {
+ // SAFETY - Clearing cache lines shouldn't have Rust-visible side effects.
+ unsafe { asm!("dc cvau, {x}", x = in(reg) line) }
+ }
+}
diff --git a/pvmfw/src/main.rs b/pvmfw/src/main.rs
index 8caf020..97a79ec 100644
--- a/pvmfw/src/main.rs
+++ b/pvmfw/src/main.rs
@@ -21,8 +21,10 @@
mod avb;
mod entry;
mod exceptions;
+mod fdt;
mod heap;
mod helpers;
+mod memory;
mod mmio_guard;
mod mmu;
mod smccc;
@@ -30,14 +32,15 @@
use avb::PUBLIC_KEY;
use log::{debug, info};
-fn main(fdt: &mut [u8], payload: &[u8], bcc: &[u8]) {
+fn main(fdt: &libfdt::Fdt, signed_kernel: &[u8], ramdisk: Option<&[u8]>, bcc: &[u8]) {
info!("pVM firmware");
- debug!(
- "fdt_address={:#018x}, payload_start={:#018x}, payload_size={:#018x}",
- fdt.as_ptr() as usize,
- payload.as_ptr() as usize,
- payload.len(),
- );
+ debug!("FDT: {:?}", fdt as *const libfdt::Fdt);
+ debug!("Signed kernel: {:?} ({:#x} bytes)", signed_kernel.as_ptr(), signed_kernel.len());
+ if let Some(rd) = ramdisk {
+ debug!("Ramdisk: {:?} ({:#x} bytes)", rd.as_ptr(), rd.len());
+ } else {
+ debug!("Ramdisk: None");
+ }
debug!("BCC: {:?} ({:#x} bytes)", bcc.as_ptr(), bcc.len());
debug!("AVB public key: addr={:?}, size={:#x} ({1})", PUBLIC_KEY.as_ptr(), PUBLIC_KEY.len());
info!("Starting payload...");
diff --git a/pvmfw/src/memory.rs b/pvmfw/src/memory.rs
new file mode 100644
index 0000000..0f1892d
--- /dev/null
+++ b/pvmfw/src/memory.rs
@@ -0,0 +1,205 @@
+// 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.
+
+//! Low-level allocation and tracking of main memory.
+
+use crate::helpers;
+use crate::mmu;
+use core::cmp::max;
+use core::cmp::min;
+use core::fmt;
+use core::mem;
+use core::mem::MaybeUninit;
+use core::num::NonZeroUsize;
+use core::ops::Range;
+use core::result;
+use log::error;
+
+type MemoryRange = Range<usize>;
+
+#[derive(Clone, Copy, Debug)]
+enum MemoryType {
+ ReadOnly,
+ ReadWrite,
+}
+
+#[derive(Clone, Debug)]
+struct MemoryRegion {
+ range: MemoryRange,
+ mem_type: MemoryType,
+}
+
+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)
+ }
+
+ /// True if the instance is fully contained within the passed range.
+ pub fn is_within(&self, range: &MemoryRange) -> bool {
+ let our: &MemoryRange = self.as_ref();
+ self.as_ref() == &(max(our.start, range.start)..min(our.end, range.end))
+ }
+}
+
+impl AsRef<MemoryRange> for MemoryRegion {
+ fn as_ref(&self) -> &MemoryRange {
+ &self.range
+ }
+}
+
+/// Tracks non-overlapping slices of main memory.
+pub struct MemoryTracker {
+ // TODO: Use tinyvec::ArrayVec
+ count: usize,
+ regions: [MaybeUninit<MemoryRegion>; MemoryTracker::CAPACITY],
+ total: MemoryRange,
+ page_table: mmu::PageTable,
+}
+
+/// Errors for MemoryTracker operations.
+#[derive(Debug, Clone)]
+pub enum MemoryTrackerError {
+ /// Tried to modify the memory base address.
+ DifferentBaseAddress,
+ /// Tried to shrink to a larger memory size.
+ SizeTooLarge,
+ /// Tracked regions would not fit in memory size.
+ SizeTooSmall,
+ /// Reached limit number of tracked regions.
+ Full,
+ /// Region is out of the tracked memory address space.
+ OutOfRange,
+ /// New region overlaps with tracked regions.
+ Overlaps,
+ /// Region couldn't be mapped.
+ FailedToMap,
+}
+
+impl fmt::Display for MemoryTrackerError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Self::DifferentBaseAddress => write!(f, "Received different base address"),
+ Self::SizeTooLarge => write!(f, "Tried to shrink to a larger memory size"),
+ Self::SizeTooSmall => write!(f, "Tracked regions would not fit in memory size"),
+ Self::Full => write!(f, "Reached limit number of tracked regions"),
+ 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"),
+ }
+ }
+}
+
+type Result<T> = result::Result<T, MemoryTrackerError>;
+
+impl MemoryTracker {
+ const 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.
+ const MAX_ADDR: usize = 1 << 39;
+
+ /// 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,
+ count: 0,
+ page_table,
+ // SAFETY - MaybeUninit items (of regions) do not require initialization.
+ regions: unsafe { MaybeUninit::uninit().assume_init() },
+ }
+ }
+
+ /// Resize the total RAM size.
+ ///
+ /// This function fails if it contains regions that are not included within the new size.
+ pub fn shrink(&mut self, range: &MemoryRange) -> Result<()> {
+ if range.start != self.total.start {
+ return Err(MemoryTrackerError::DifferentBaseAddress);
+ }
+ if self.total.end < range.end {
+ return Err(MemoryTrackerError::SizeTooLarge);
+ }
+ if !self.regions().iter().all(|r| r.is_within(range)) {
+ return Err(MemoryTrackerError::SizeTooSmall);
+ }
+
+ self.total = range.clone();
+ Ok(())
+ }
+
+ /// Allocate the address range for a const slice; returns None if failed.
+ pub fn alloc_range(&mut self, range: &MemoryRange) -> Result<MemoryRange> {
+ 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 })
+ }
+
+ /// Allocate the address range for a mutable slice; returns None if failed.
+ pub fn alloc_range_mut(&mut self, range: &MemoryRange) -> Result<MemoryRange> {
+ 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 })
+ }
+
+ /// Allocate the address range for a const slice; returns None if failed.
+ pub fn alloc(&mut self, base: usize, size: NonZeroUsize) -> Result<MemoryRange> {
+ self.alloc_range(&(base..(base + size.get())))
+ }
+
+ /// Allocate the address range for a mutable slice; returns None if failed.
+ pub fn alloc_mut(&mut self, base: usize, size: NonZeroUsize) -> Result<MemoryRange> {
+ self.alloc_range_mut(&(base..(base + size.get())))
+ }
+
+ fn regions(&self) -> &[MemoryRegion] {
+ // SAFETY - The first self.count regions have been properly initialized.
+ unsafe { mem::transmute::<_, &[MemoryRegion]>(&self.regions[..self.count]) }
+ }
+
+ fn add(&mut self, region: MemoryRegion) -> Result<MemoryRange> {
+ if !region.is_within(&self.total) {
+ return Err(MemoryTrackerError::OutOfRange);
+ }
+ if self.regions().iter().any(|r| r.overlaps(region.as_ref())) {
+ return Err(MemoryTrackerError::Overlaps);
+ }
+ if self.regions.len() == self.count {
+ return Err(MemoryTrackerError::Full);
+ }
+
+ let region = self.regions[self.count].write(region);
+ self.count += 1;
+ Ok(region.as_ref().clone())
+ }
+}
+
+impl Drop for MemoryTracker {
+ fn drop(&mut self) {
+ for region in self.regions().iter() {
+ match region.mem_type {
+ MemoryType::ReadWrite => {
+ // TODO: Use page table's dirty bit to only flush pages that were touched.
+ helpers::flush_region(region.range.start, region.range.len())
+ }
+ MemoryType::ReadOnly => {}
+ }
+ }
+ }
+}