Merge "Announce action hint when focused on cursor" into main
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
index cfb1cbe..397a546 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
@@ -21,7 +21,6 @@
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
-import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Bitmap;
@@ -72,6 +71,7 @@
public class MainActivity extends BaseActivity
implements VmLauncherService.VmLauncherServiceCallback, AccessibilityStateChangeListener {
static final String TAG = "VmTerminalApp";
+ static final String KEY_DISK_SIZE = "disk_size";
private static final String VM_ADDR = "192.168.0.2";
private static final int TTYD_PORT = 7681;
private static final int REQUEST_CODE_INSTALLER = 0x33;
@@ -509,17 +509,11 @@
}
private void resizeDiskIfNecessary(InstalledImage image) {
- String prefKey = getString(R.string.preference_file_key);
- String key = getString(R.string.preference_disk_size_key);
- SharedPreferences sharedPref = this.getSharedPreferences(prefKey, Context.MODE_PRIVATE);
try {
- // Use current size as default value to ensure if its size is multiple of 4096
- long newSize = sharedPref.getLong(key, image.getSize());
- Log.d(TAG, "Resizing disk to " + newSize + " bytes");
- newSize = image.resize(newSize);
- sharedPref.edit().putLong(key, newSize).apply();
+ // TODO(b/382190982): Show snackbar message instead when it's recoverable.
+ image.resize(getIntent().getLongExtra(KEY_DISK_SIZE, image.getSize()));
} catch (IOException e) {
- Log.e(TAG, "Failed to resize disk", e);
+ ErrorActivity.start(this, new Exception("Failed to resize disk", e));
return;
}
}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsDiskResizeActivity.kt b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsDiskResizeActivity.kt
index 30475f5..b893d9e 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsDiskResizeActivity.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsDiskResizeActivity.kt
@@ -15,9 +15,7 @@
*/
package com.android.virtualization.terminal
-import android.content.Context
import android.content.Intent
-import android.content.SharedPreferences
import android.icu.text.MeasureFormat
import android.icu.text.NumberFormat
import android.icu.util.Measure
@@ -37,12 +35,12 @@
import java.util.regex.Pattern
class SettingsDiskResizeActivity : AppCompatActivity() {
+ // TODO(b/382191950): Calculate the maxDiskSizeMb based on the device storage usage
private val maxDiskSizeMb: Long = 16 shl 10
private val numberPattern: Pattern = Pattern.compile("[\\d]*[\\٫.,]?[\\d]+")
private var diskSizeStepMb: Long = 0
private var diskSizeMb: Long = 0
- private lateinit var sharedPref: SharedPreferences
private lateinit var buttons: View
private lateinit var cancelButton: View
private lateinit var resizeButton: View
@@ -71,13 +69,8 @@
diskSizeStepMb = 1L shl resources.getInteger(R.integer.disk_size_round_up_step_size_in_mb)
- sharedPref =
- this.getSharedPreferences(getString(R.string.preference_file_key), Context.MODE_PRIVATE)
- diskSizeMb =
- bytesToMb(
- sharedPref.getLong(getString(R.string.preference_disk_size_key), /* defValue= */ 0)
- )
val image = InstalledImage.getDefault(this)
+ diskSizeMb = bytesToMb(image.getSize())
val minDiskSizeMb = bytesToMb(image.getSmallestSizePossible()).coerceAtMost(diskSizeMb)
diskSizeText = findViewById<TextView>(R.id.settings_disk_resize_resize_gb_assigned)!!
@@ -139,16 +132,14 @@
.show()
}
- fun resize() {
+ private fun resize() {
diskSizeMb = progressToMb(diskSizeSlider.progress)
buttons.isVisible = false
- val editor = sharedPref.edit()
- editor.putLong(getString(R.string.preference_disk_size_key), mbToBytes(diskSizeMb))
- editor.apply()
// Restart terminal
val intent = baseContext.packageManager.getLaunchIntentForPackage(baseContext.packageName)
intent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ intent?.putExtra(MainActivity.KEY_DISK_SIZE, mbToBytes(diskSizeMb))
finish()
startActivity(intent)
}
diff --git a/android/TerminalApp/res/values/config.xml b/android/TerminalApp/res/values/config.xml
index 713e1a5..7f0b5e6 100644
--- a/android/TerminalApp/res/values/config.xml
+++ b/android/TerminalApp/res/values/config.xml
@@ -15,8 +15,5 @@
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="preference_file_key" translatable="false">com.android.virtualization.terminal.PREFERENCE_FILE_KEY</string>
- <string name="preference_disk_size_key" translatable="false">PREFERENCE_DISK_SIZE_KEY</string>
-
<bool name="terminal_portrait_only">true</bool>
</resources>
diff --git a/android/virtmgr/src/aidl.rs b/android/virtmgr/src/aidl.rs
index c2f7663..0f81f3d 100644
--- a/android/virtmgr/src/aidl.rs
+++ b/android/virtmgr/src/aidl.rs
@@ -53,6 +53,7 @@
};
use android_hardware_security_secretkeeper::aidl::android::hardware::security::secretkeeper::ISecretkeeper::{BnSecretkeeper, ISecretkeeper};
use android_hardware_security_secretkeeper::aidl::android::hardware::security::secretkeeper::SecretId::SecretId;
+use android_hardware_security_secretkeeper::aidl::android::hardware::security::secretkeeper::PublicKey::PublicKey;
use android_hardware_security_authgraph::aidl::android::hardware::security::authgraph::{
Arc::Arc as AuthgraphArc, IAuthGraphKeyExchange::IAuthGraphKeyExchange,
IAuthGraphKeyExchange::BnAuthGraphKeyExchange, Identity::Identity, KeInitResult::KeInitResult,
@@ -888,16 +889,33 @@
.context("Failed to extract vendor hashtree digest")
.or_service_specific_exception(-1)?;
- let trusted_props = if let Some(ref vendor_hashtree_digest) = vendor_hashtree_digest {
+ let vendor_hashtree_digest = if let Some(ref vendor_hashtree_digest) = vendor_hashtree_digest {
info!(
"Passing vendor hashtree digest to pvmfw. This will be rejected if it doesn't \
match the trusted digest in the pvmfw config, causing the VM to fail to start."
);
- vec![(cstr!("vendor_hashtree_descriptor_root_digest"), vendor_hashtree_digest.as_slice())]
+ Some((cstr!("vendor_hashtree_descriptor_root_digest"), vendor_hashtree_digest.as_slice()))
} else {
- vec![]
+ None
};
+ let key_material;
+ let secretkeeper_public_key = if is_secretkeeper_supported() {
+ let sk: Strong<dyn ISecretkeeper> = binder::wait_for_interface(SECRETKEEPER_IDENTIFIER)?;
+ if sk.getInterfaceVersion()? >= 2 {
+ let PublicKey { keyMaterial } = sk.getSecretkeeperIdentity()?;
+ key_material = keyMaterial;
+ Some((cstr!("secretkeeper_public_key"), key_material.as_slice()))
+ } else {
+ None
+ }
+ } else {
+ None
+ };
+
+ let trusted_props: Vec<(&CStr, &[u8])> =
+ vec![vendor_hashtree_digest, secretkeeper_public_key].into_iter().flatten().collect();
+
let instance_id;
let mut untrusted_props = Vec::with_capacity(2);
if cfg!(llpvm_changes) {
@@ -2042,6 +2060,14 @@
fn deleteAll(&self) -> binder::Result<()> {
self.0.deleteAll()
}
+
+ fn getSecretkeeperIdentity(&self) -> binder::Result<PublicKey> {
+ // SecretkeeperProxy is really a RPC binder service for PVM (It is called by
+ // MicrodroidManager). PVMs do not & must not (for security reason) rely on
+ // getSecretKeeperIdentity, so we throw an exception if someone attempts to
+ // use this API from the proxy.
+ Err(ExceptionCode::SECURITY.into())
+ }
}
struct AuthGraphKeyExchangeProxy(Strong<dyn IAuthGraphKeyExchange>);
diff --git a/android/virtualizationservice/aidl/Android.bp b/android/virtualizationservice/aidl/Android.bp
index 79a9d40..db7be71 100644
--- a/android/virtualizationservice/aidl/Android.bp
+++ b/android/virtualizationservice/aidl/Android.bp
@@ -111,7 +111,7 @@
name: "android.system.virtualmachineservice",
srcs: ["android/system/virtualmachineservice/**/*.aidl"],
imports: [
- "android.hardware.security.secretkeeper-V1",
+ "android.hardware.security.secretkeeper-V2",
"android.system.virtualizationcommon",
],
unstable: true,
diff --git a/android/virtualizationservice/src/maintenance.rs b/android/virtualizationservice/src/maintenance.rs
index 8e04075..87ba412 100644
--- a/android/virtualizationservice/src/maintenance.rs
+++ b/android/virtualizationservice/src/maintenance.rs
@@ -297,7 +297,9 @@
mod tests {
use super::*;
use android_hardware_security_authgraph::aidl::android::hardware::security::authgraph;
- use android_hardware_security_secretkeeper::aidl::android::hardware::security::secretkeeper;
+ use android_hardware_security_secretkeeper::aidl::android::hardware::security::secretkeeper::{
+ self, PublicKey::PublicKey,
+ };
use authgraph::IAuthGraphKeyExchange::IAuthGraphKeyExchange;
use secretkeeper::ISecretkeeper::BnSecretkeeper;
use std::sync::{Arc, Mutex};
@@ -335,6 +337,10 @@
self.history.lock().unwrap().push(SkOp::DeleteAll);
Ok(())
}
+
+ fn getSecretkeeperIdentity(&self) -> binder::Result<PublicKey> {
+ unimplemented!()
+ }
}
impl binder::Interface for FakeSk {}
diff --git a/build/apex/Android.bp b/build/apex/Android.bp
index 6541764..946bc8c 100644
--- a/build/apex/Android.bp
+++ b/build/apex/Android.bp
@@ -108,6 +108,7 @@
"rialto_bin",
"android_bootloader_crosvm_aarch64",
],
+ native_shared_libs: ["libavf"],
},
x86_64: {
binaries: [
@@ -128,6 +129,7 @@
prebuilts: [
"android_bootloader_crosvm_x86_64",
],
+ native_shared_libs: ["libavf"],
},
},
binaries: [
diff --git a/build/debian/build.sh b/build/debian/build.sh
index 7231a7c..9104adc 100755
--- a/build/debian/build.sh
+++ b/build/debian/build.sh
@@ -10,15 +10,14 @@
echo "Builds a debian image and save it to FILE. [sudo is required]"
echo "Options:"
echo "-h Print usage and this help message and exit."
- echo "-a ARCH Architecture of the image [default is aarch64]"
+ echo "-a ARCH Architecture of the image [default is host arch: $(uname -m)]"
echo "-r Release mode build"
- echo "-w Save temp work directory (for debugging)"
+ echo "-w Save temp work directory [for debugging]"
}
check_sudo() {
if [ "$EUID" -ne 0 ]; then
- echo "Please run as root."
- exit
+ echo "Please run as root." ; exit 1
fi
}
@@ -26,17 +25,10 @@
while getopts "a:hrw" option; do
case ${option} in
h)
- show_help
- exit;;
+ show_help ; exit
+ ;;
a)
- if [[ "$OPTARG" != "aarch64" && "$OPTARG" != "x86_64" ]]; then
- echo "Invalid architecture: $OPTARG"
- exit
- fi
arch="$OPTARG"
- if [[ "$arch" == "x86_64" ]]; then
- debian_arch="amd64"
- fi
;;
r)
mode=release
@@ -45,11 +37,21 @@
save_workdir=1
;;
*)
- echo "Invalid option: $OPTARG"
- exit
+ echo "Invalid option: $OPTARG" ; exit 1
;;
esac
done
+ case "$arch" in
+ aarch64)
+ debian_arch="arm64"
+ ;;
+ x86_64)
+ debian_arch="amd64"
+ ;;
+ *)
+ echo "Invalid architecture: $arch" ; exit 1
+ ;;
+ esac
if [[ "${*:$OPTIND:1}" ]]; then
built_image="${*:$OPTIND:1}"
fi
@@ -68,6 +70,7 @@
install_prerequisites() {
apt update
packages=(
+ apt-utils
automake
binfmt-support
build-essential
@@ -184,6 +187,7 @@
cp -R "${src}"/* "${dst}"
cp "$(dirname "$0")/image.yaml" "${resources_dir}"
+ cp -R "$(dirname "$0")/localdebs/" "${debian_cloud_image}/"
build_ttyd
build_rust_binary_and_copy forwarder_guest
build_rust_binary_and_copy forwarder_guest_launcher
@@ -217,7 +221,7 @@
}
clean_up() {
- [ "$save_workdir" -eq 0 ] || rm -rf "${workdir}"
+ [ "$save_workdir" -eq 1 ] || rm -rf "${workdir}"
}
set -e
@@ -230,8 +234,7 @@
debian_version=bookworm
config_space=${debian_cloud_image}/config_space/${debian_version}
resources_dir=${debian_cloud_image}/src/debian_cloud_images/resources
-arch=aarch64
-debian_arch=arm64
+arch="$(uname -m)"
mode=debug
save_workdir=0
diff --git a/build/debian/build_in_container.sh b/build/debian/build_in_container.sh
index 7fd4c00..5028b74 100755
--- a/build/debian/build_in_container.sh
+++ b/build/debian/build_in_container.sh
@@ -1,35 +1,56 @@
#!/bin/bash
-if [ -z "$ANDROID_BUILD_TOP" ]; then echo "forgot to source build/envsetup.sh?" && exit 1; fi
+show_help() {
+ echo "Usage: sudo $0 [OPTION]..."
+ echo "Builds a debian image and save it to image.raw."
+ echo "Options:"
+ echo "-h Print usage and this help message and exit."
+ echo "-a ARCH Architecture of the image [default is host arch: $(uname -m)]"
+ echo "-r Release mode build"
+ echo "-s Leave a shell open [default: only if the build fails]"
+ echo "-w Save temp work directory in the container [for debugging]"
+}
-arch=aarch64
+arch="$(uname -m)"
release_flag=
save_workdir_flag=
+shell_condition="||"
-while getopts "a:rw" option; do
+while getopts "a:rsw" option; do
case ${option} in
a)
- if [[ "$OPTARG" != "aarch64" && "$OPTARG" != "x86_64" ]]; then
- echo "Invalid architecture: $OPTARG"
- exit
- fi
arch="$OPTARG"
;;
+ h)
+ show_help ; exit
+ ;;
r)
release_flag="-r"
;;
+ s)
+ shell_condition=";"
+ ;;
w)
save_workdir_flag="-w"
;;
*)
- echo "Invalid option: $OPTARG"
- exit
+ echo "Invalid option: $OPTARG" ; exit 1
;;
esac
done
+if [[ "$arch" != "aarch64" && "$arch" != "x86_64" ]]; then
+ echo "Invalid architecture: $arch" ; exit 1
+fi
+
+if [ -z "$ANDROID_BUILD_TOP" ] ; then
+ echo '`ANDROID_BUILD_TOP` is undefined.'
+ echo 'Please `lunch` an Android target, or manually set the variable.'
+ exit 1
+fi
+
docker run --privileged -it -v /dev:/dev \
-v "$ANDROID_BUILD_TOP/packages/modules/Virtualization:/root/Virtualization" \
--workdir /root/Virtualization/build/debian \
ubuntu:22.04 \
- bash -c "/root/Virtualization/build/debian/build.sh -a $arch $release_flag $save_workdir_flag || bash"
+ bash -c "/root/Virtualization/build/debian/build.sh -a $arch $release_flag $save_workdir_flag $shell_condition bash"
diff --git a/build/debian/kokoro/gcp_ubuntu_docker/aarch64/build.sh b/build/debian/kokoro/gcp_ubuntu_docker/aarch64/build.sh
index 130e691..43f0338 100644
--- a/build/debian/kokoro/gcp_ubuntu_docker/aarch64/build.sh
+++ b/build/debian/kokoro/gcp_ubuntu_docker/aarch64/build.sh
@@ -5,7 +5,7 @@
cd "${KOKORO_ARTIFACTS_DIR}/git/avf/build/debian/"
sudo losetup -D
grep vmx /proc/cpuinfo || true
-sudo ./build.sh -r
+sudo ./build.sh -r -a aarch64
sudo mv images.tar.gz ${KOKORO_ARTIFACTS_DIR} || true
mkdir -p ${KOKORO_ARTIFACTS_DIR}/logs
sudo cp -r /var/log/fai/* ${KOKORO_ARTIFACTS_DIR}/logs || true
diff --git a/build/debian/kokoro/gcp_ubuntu_docker/x86_64/build.sh b/build/debian/kokoro/gcp_ubuntu_docker/x86_64/build.sh
index 50ded7b..22ac595 100644
--- a/build/debian/kokoro/gcp_ubuntu_docker/x86_64/build.sh
+++ b/build/debian/kokoro/gcp_ubuntu_docker/x86_64/build.sh
@@ -5,8 +5,7 @@
cd "${KOKORO_ARTIFACTS_DIR}/git/avf/build/debian/"
sudo losetup -D
grep vmx /proc/cpuinfo || true
-sudo ./build.sh -a x86_64 -r
+sudo ./build.sh -r -a x86_64
sudo mv images.tar.gz ${KOKORO_ARTIFACTS_DIR} || true
-
mkdir -p ${KOKORO_ARTIFACTS_DIR}/logs
sudo cp -r /var/log/fai/* ${KOKORO_ARTIFACTS_DIR}/logs || true
diff --git a/build/debian/localdebs/.gitkeep b/build/debian/localdebs/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/build/debian/localdebs/.gitkeep
diff --git a/build/microdroid/Android.bp b/build/microdroid/Android.bp
index 7f23ae6..f750f62 100644
--- a/build/microdroid/Android.bp
+++ b/build/microdroid/Android.bp
@@ -50,6 +50,7 @@
avb_private_key: ":microdroid_sign_key",
avb_algorithm: "SHA256_RSA4096",
avb_hash_algorithm: "sha256",
+ use_fec: false,
partition_name: "system",
deps: [
"init_second_stage.microdroid",
@@ -245,6 +246,7 @@
avb_private_key: ":microdroid_sign_key",
avb_algorithm: "SHA256_RSA4096",
avb_hash_algorithm: "sha256",
+ use_fec: false,
file_contexts: ":microdroid_vendor_file_contexts.gen",
// For deterministic output, use fake_timestamp, hard-coded uuid
fake_timestamp: "1611569676",
diff --git a/guest/pvmfw/src/entry.rs b/guest/pvmfw/src/entry.rs
index 48585f3..2f0b391 100644
--- a/guest/pvmfw/src/entry.rs
+++ b/guest/pvmfw/src/entry.rs
@@ -17,21 +17,20 @@
use crate::config;
use crate::memory;
use core::arch::asm;
-use core::mem::{drop, size_of};
+use core::mem::size_of;
use core::ops::Range;
use core::slice;
-use hypervisor_backends::get_mmio_guard;
use log::error;
-use log::info;
use log::warn;
use log::LevelFilter;
use vmbase::util::RangeExt as _;
use vmbase::{
arch::aarch64::min_dcache_line_size,
- configure_heap, console_writeln,
- layout::{self, crosvm, UART_PAGE_ADDR},
- main,
- memory::{MemoryTracker, MEMORY, SIZE_128KB, SIZE_4KB},
+ configure_heap, console_writeln, layout, limit_stack_size, main,
+ memory::{
+ deactivate_dynamic_page_tables, map_image_footer, unshare_all_memory,
+ unshare_all_mmio_except_uart, unshare_uart, MemoryTrackerError, SIZE_128KB, SIZE_4KB,
+ },
power::reboot,
};
use zeroize::Zeroize;
@@ -73,6 +72,7 @@
main!(start);
configure_heap!(SIZE_128KB);
+limit_stack_size!(SIZE_4KB * 12);
/// Entry point for pVM firmware.
pub fn start(fdt_address: u64, payload_start: u64, payload_size: u64, _arg3: u64) {
@@ -108,15 +108,11 @@
log::set_max_level(LevelFilter::Info);
- let page_table = memory::init_page_table().map_err(|e| {
- error!("Failed to set up the dynamic page tables: {e}");
+ let appended_data = get_appended_data_slice().map_err(|e| {
+ error!("Failed to map the appended data: {e}");
RebootReason::InternalError
})?;
- // SAFETY: We only get the appended payload from here, once. The region was statically mapped,
- // then remapped by `init_page_table()`.
- let appended_data = unsafe { get_appended_data_slice() };
-
let appended = AppendedPayload::new(appended_data).ok_or_else(|| {
error!("No valid configuration found");
RebootReason::InvalidConfig
@@ -124,14 +120,6 @@
let config_entries = appended.get_entries();
- // Up to this point, we were using the built-in static (from .rodata) page tables.
- MEMORY.lock().replace(MemoryTracker::new(
- page_table,
- crosvm::MEM_START..layout::MAX_VIRT_ADDR,
- crosvm::MMIO_RANGE,
- Some(layout::image_footer_range()),
- ));
-
let slices = memory::MemorySlices::new(
fdt,
payload,
@@ -152,27 +140,23 @@
// Writable-dirty regions will be flushed when MemoryTracker is dropped.
config_entries.bcc.zeroize();
- info!("Expecting a bug making MMIO_GUARD_UNMAP return NOT_SUPPORTED on success");
- MEMORY.lock().as_mut().unwrap().unshare_all_mmio().map_err(|e| {
+ unshare_all_mmio_except_uart().map_err(|e| {
error!("Failed to unshare MMIO ranges: {e}");
RebootReason::InternalError
})?;
// Call unshare_all_memory here (instead of relying on the dtor) while UART is still mapped.
- MEMORY.lock().as_mut().unwrap().unshare_all_memory();
+ unshare_all_memory();
- if let Some(mmio_guard) = get_mmio_guard() {
- if cfg!(debuggable_vms_improvements) && debuggable_payload {
- // Keep UART MMIO_GUARD-ed for debuggable payloads, to enable earlycon.
- } else {
- mmio_guard.unmap(UART_PAGE_ADDR).map_err(|e| {
- error!("Failed to unshare the UART: {e}");
- RebootReason::InternalError
- })?;
- }
+ if cfg!(debuggable_vms_improvements) && debuggable_payload {
+ // Keep UART MMIO_GUARD-ed for debuggable payloads, to enable earlycon.
+ } else {
+ unshare_uart().map_err(|e| {
+ error!("Failed to unshare the UART: {e}");
+ RebootReason::InternalError
+ })?;
}
- // Drop MemoryTracker and deactivate page table.
- drop(MEMORY.lock().take());
+ deactivate_dynamic_page_tables();
Ok((slices.kernel.as_ptr() as usize, next_bcc))
}
@@ -199,7 +183,7 @@
assert_eq!(bcc.start % ASM_STP_ALIGN, 0, "Misaligned guest BCC.");
assert_eq!(bcc.end % ASM_STP_ALIGN, 0, "Misaligned guest BCC.");
- let stack = memory::stack_range();
+ let stack = layout::stack_range();
assert_ne!(stack.end - stack.start, 0, "stack region is empty.");
assert_eq!(stack.start.0 % ASM_STP_ALIGN, 0, "Misaligned stack region.");
@@ -321,15 +305,11 @@
};
}
-/// # Safety
-///
-/// This must only be called once, since we are returning a mutable reference.
-/// The appended data region must be mapped.
-unsafe fn get_appended_data_slice() -> &'static mut [u8] {
- let range = layout::image_footer_range();
- // SAFETY: This region is mapped and the linker script prevents it from overlapping with other
- // objects.
- unsafe { slice::from_raw_parts_mut(range.start.0 as *mut u8, range.end - range.start) }
+fn get_appended_data_slice() -> Result<&'static mut [u8], MemoryTrackerError> {
+ let range = map_image_footer()?;
+ // SAFETY: This region was just mapped for the first time (as map_image_footer() didn't fail)
+ // and the linker script prevents it from overlapping with other objects.
+ Ok(unsafe { slice::from_raw_parts_mut(range.start as *mut u8, range.len()) })
}
enum AppendedPayload<'a> {
diff --git a/guest/pvmfw/src/helpers.rs b/guest/pvmfw/src/helpers.rs
index 8981408..0552640 100644
--- a/guest/pvmfw/src/helpers.rs
+++ b/guest/pvmfw/src/helpers.rs
@@ -14,7 +14,6 @@
//! Miscellaneous helper functions.
-use vmbase::memory::{PAGE_SIZE, SIZE_4KB};
+use vmbase::memory::SIZE_4KB;
pub const GUEST_PAGE_SIZE: usize = SIZE_4KB;
-pub const PVMFW_PAGE_SIZE: usize = PAGE_SIZE;
diff --git a/guest/pvmfw/src/main.rs b/guest/pvmfw/src/main.rs
index 612281b..bde03ff 100644
--- a/guest/pvmfw/src/main.rs
+++ b/guest/pvmfw/src/main.rs
@@ -55,7 +55,6 @@
use vmbase::fdt::pci::{PciError, PciInfo};
use vmbase::heap;
use vmbase::memory::flush;
-use vmbase::memory::MEMORY;
use vmbase::rand;
use vmbase::virtio::pci;
@@ -101,7 +100,7 @@
// Set up PCI bus for VirtIO devices.
let pci_info = PciInfo::from_fdt(fdt).map_err(handle_pci_error)?;
debug!("PCI: {:#x?}", pci_info);
- let mut pci_root = pci::initialize(pci_info, MEMORY.lock().as_mut().unwrap()).map_err(|e| {
+ let mut pci_root = pci::initialize(pci_info).map_err(|e| {
error!("Failed to initialize PCI: {e}");
RebootReason::InternalError
})?;
diff --git a/guest/pvmfw/src/memory.rs b/guest/pvmfw/src/memory.rs
index 7d49bca..35bfd3a 100644
--- a/guest/pvmfw/src/memory.rs
+++ b/guest/pvmfw/src/memory.rs
@@ -16,48 +16,17 @@
use crate::entry::RebootReason;
use crate::fdt;
-use crate::helpers::PVMFW_PAGE_SIZE;
-use aarch64_paging::paging::VirtualAddress;
-use aarch64_paging::MapError;
use core::num::NonZeroUsize;
-use core::ops::Range;
-use core::result;
use core::slice;
-use hypervisor_backends::get_mem_sharer;
use log::debug;
use log::error;
use log::info;
use log::warn;
use vmbase::{
- layout::{self, crosvm},
- memory::{PageTable, MEMORY},
+ layout::crosvm,
+ memory::{init_shared_pool, map_data, map_rodata, resize_available_memory},
};
-/// Region allocated for the stack.
-pub fn stack_range() -> Range<VirtualAddress> {
- const STACK_PAGES: usize = 12;
-
- layout::stack_range(STACK_PAGES * PVMFW_PAGE_SIZE)
-}
-
-pub fn init_page_table() -> result::Result<PageTable, MapError> {
- let mut page_table = PageTable::default();
-
- // Stack and scratch ranges are explicitly zeroed and flushed before jumping to payload,
- // so dirty state management can be omitted.
- page_table.map_data(&layout::data_bss_range().into())?;
- page_table.map_data(&layout::eh_stack_range().into())?;
- page_table.map_data(&stack_range().into())?;
- page_table.map_code(&layout::text_range().into())?;
- page_table.map_rodata(&layout::rodata_range().into())?;
- page_table.map_data_dbm(&layout::image_footer_range().into())?;
- if let Err(e) = page_table.map_device(&layout::console_uart_page().into()) {
- error!("Failed to remap the UART as a dynamic page table entry: {e}");
- return Err(e);
- }
- Ok(page_table)
-}
-
pub(crate) struct MemorySlices<'a> {
pub fdt: &'a mut libfdt::Fdt,
pub kernel: &'a [u8],
@@ -76,13 +45,13 @@
// 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.lock().as_mut().unwrap().alloc_mut(fdt, fdt_size).map_err(|e| {
+ map_data(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()) };
+ // SAFETY: map_data validated the range to be in main memory, mapped, and not overlap.
+ let fdt = unsafe { slice::from_raw_parts_mut(fdt as *mut u8, fdt_size.into()) };
let info = fdt::sanitize_device_tree(fdt, vm_dtbo, vm_ref_dt)?;
let fdt = libfdt::Fdt::from_mut_slice(fdt).map_err(|e| {
@@ -93,67 +62,56 @@
let memory_range = info.memory_range;
debug!("Resizing MemoryTracker to range {memory_range:#x?}");
- MEMORY.lock().as_mut().unwrap().shrink(&memory_range).map_err(|e| {
+ resize_available_memory(&memory_range).map_err(|e| {
error!("Failed to use memory range value from DT: {memory_range:#x?}: {e}");
RebootReason::InvalidFdt
})?;
- if let Some(mem_sharer) = get_mem_sharer() {
- let granule = mem_sharer.granule().map_err(|e| {
- error!("Failed to get memory protection granule: {e}");
- RebootReason::InternalError
- })?;
- MEMORY.lock().as_mut().unwrap().init_dynamic_shared_pool(granule).map_err(|e| {
- error!("Failed to initialize dynamically shared pool: {e}");
- RebootReason::InternalError
- })?;
- } else {
- let range = info.swiotlb_info.fixed_range().ok_or_else(|| {
- error!("Pre-shared pool range not specified in swiotlb node");
- RebootReason::InvalidFdt
- })?;
+ init_shared_pool(info.swiotlb_info.fixed_range()).map_err(|e| {
+ error!("Failed to initialize shared pool: {e}");
+ RebootReason::InternalError
+ })?;
- MEMORY.lock().as_mut().unwrap().init_static_shared_pool(range).map_err(|e| {
- error!("Failed to initialize pre-shared pool {e}");
- RebootReason::InvalidFdt
- })?;
- }
-
- let kernel_range = if let Some(r) = info.kernel_range {
- MEMORY.lock().as_mut().unwrap().alloc_range(&r).map_err(|e| {
- error!("Failed to obtain the kernel range with DT range: {e}");
+ let (kernel_start, kernel_size) = if let Some(r) = info.kernel_range {
+ let size = r.len().try_into().map_err(|_| {
+ error!("Invalid kernel size: {:#x}", r.len());
RebootReason::InternalError
- })?
+ })?;
+ (r.start, size)
} else if cfg!(feature = "legacy") {
warn!("Failed to find the kernel range in the DT; falling back to legacy ABI");
-
- let kernel_size = NonZeroUsize::new(kernel_size).ok_or_else(|| {
+ let size = NonZeroUsize::new(kernel_size).ok_or_else(|| {
error!("Invalid kernel size: {kernel_size:#x}");
RebootReason::InvalidPayload
})?;
-
- MEMORY.lock().as_mut().unwrap().alloc(kernel, kernel_size).map_err(|e| {
- error!("Failed to obtain the kernel range with legacy range: {e}");
- RebootReason::InternalError
- })?
+ (kernel, size)
} else {
error!("Failed to locate the kernel from the DT");
return Err(RebootReason::InvalidPayload);
};
- let kernel = kernel_range.start as *const u8;
- // SAFETY: The tracker validated the range to be in main memory, mapped, and not overlap.
- let kernel = unsafe { slice::from_raw_parts(kernel, kernel_range.len()) };
+ map_rodata(kernel_start, kernel_size).map_err(|e| {
+ error!("Failed to map kernel range: {e}");
+ RebootReason::InternalError
+ })?;
+
+ let kernel = kernel_start as *const u8;
+ // SAFETY: map_rodata validated the range to be in main memory, mapped, and not overlap.
+ let kernel = unsafe { slice::from_raw_parts(kernel, kernel_size.into()) };
let ramdisk = if let Some(r) = info.initrd_range {
debug!("Located ramdisk at {r:?}");
- let r = MEMORY.lock().as_mut().unwrap().alloc_range(&r).map_err(|e| {
+ let ramdisk_size = r.len().try_into().map_err(|_| {
+ error!("Invalid ramdisk size: {:#x}", r.len());
+ RebootReason::InvalidRamdisk
+ })?;
+ map_rodata(r.start, ramdisk_size).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.
+ // SAFETY: map_rodata validated the range 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");
diff --git a/guest/rialto/src/main.rs b/guest/rialto/src/main.rs
index 61e9948..04d18be 100644
--- a/guest/rialto/src/main.rs
+++ b/guest/rialto/src/main.rs
@@ -32,8 +32,6 @@
use core::num::NonZeroUsize;
use core::slice;
use diced_open_dice::{bcc_handover_parse, DiceArtifacts};
-use hypervisor_backends::get_mem_sharer;
-use libfdt::FdtError;
use log::{debug, error, info};
use service_vm_comm::{ServiceVmRequest, VmType};
use service_vm_fake_chain::service_vm;
@@ -48,9 +46,12 @@
fdt::pci::PciInfo,
fdt::SwiotlbInfo,
generate_image_header,
- layout::{self, crosvm},
+ layout::crosvm,
main,
- memory::{MemoryTracker, PageTable, MEMORY, PAGE_SIZE, SIZE_128KB},
+ memory::{
+ init_shared_pool, map_rodata, map_rodata_outside_main_memory, resize_available_memory,
+ SIZE_128KB,
+ },
power::reboot,
virtio::{
pci::{self, PciTransportIterator, VirtIOSocket},
@@ -70,82 +71,45 @@
}
}
-fn new_page_table() -> Result<PageTable> {
- let mut page_table = PageTable::default();
-
- page_table.map_data(&layout::data_bss_range().into())?;
- page_table.map_data(&layout::eh_stack_range().into())?;
- page_table.map_data(&layout::stack_range(40 * PAGE_SIZE).into())?;
- page_table.map_code(&layout::text_range().into())?;
- page_table.map_rodata(&layout::rodata_range().into())?;
- page_table.map_device(&layout::console_uart_page().into())?;
-
- Ok(page_table)
-}
-
/// # Safety
///
/// Behavior is undefined if any of the following conditions are violated:
/// * The `fdt_addr` must be a valid pointer and points to a valid `Fdt`.
unsafe fn try_main(fdt_addr: usize) -> Result<()> {
info!("Welcome to Rialto!");
- let page_table = new_page_table()?;
- MEMORY.lock().replace(MemoryTracker::new(
- page_table,
- crosvm::MEM_START..layout::MAX_VIRT_ADDR,
- crosvm::MMIO_RANGE,
- None, // Rialto doesn't have any payload for now.
- ));
-
- let fdt_range = MEMORY
- .lock()
- .as_mut()
- .unwrap()
- .alloc(fdt_addr, NonZeroUsize::new(crosvm::FDT_MAX_SIZE).unwrap())?;
+ let fdt_size = NonZeroUsize::new(crosvm::FDT_MAX_SIZE).unwrap();
+ map_rodata(fdt_addr, fdt_size)?;
// SAFETY: The tracker validated the range to be in main memory, mapped, and not overlap.
- let fdt = unsafe { slice::from_raw_parts(fdt_range.start as *mut u8, fdt_range.len()) };
+ let fdt = unsafe { slice::from_raw_parts(fdt_addr as *mut u8, fdt_size.into()) };
// We do not need to validate the DT since it is already validated in pvmfw.
let fdt = libfdt::Fdt::from_slice(fdt)?;
let memory_range = fdt.first_memory_range()?;
- MEMORY.lock().as_mut().unwrap().shrink(&memory_range).inspect_err(|_| {
+ resize_available_memory(&memory_range).inspect_err(|_| {
error!("Failed to use memory range value from DT: {memory_range:#x?}");
})?;
- if let Some(mem_sharer) = get_mem_sharer() {
- let granule = mem_sharer.granule()?;
- MEMORY.lock().as_mut().unwrap().init_dynamic_shared_pool(granule).inspect_err(|_| {
- error!("Failed to initialize dynamically shared pool.");
- })?;
- } else if let Ok(Some(swiotlb_info)) = SwiotlbInfo::new_from_fdt(fdt) {
- let range = swiotlb_info.fixed_range().ok_or_else(|| {
- error!("Pre-shared pool range not specified in swiotlb node");
- Error::from(FdtError::BadValue)
- })?;
- MEMORY.lock().as_mut().unwrap().init_static_shared_pool(range).inspect_err(|_| {
- error!("Failed to initialize pre-shared pool.");
- })?;
- } else {
- info!("No MEM_SHARE capability detected or swiotlb found: allocating buffers from heap.");
- MEMORY.lock().as_mut().unwrap().init_heap_shared_pool().inspect_err(|_| {
- error!("Failed to initialize heap-based pseudo-shared pool.");
- })?;
- }
+ let swiotlb_range = SwiotlbInfo::new_from_fdt(fdt)
+ .inspect_err(|_| {
+ error!("Rialto failed when access swiotlb");
+ })?
+ .and_then(|info| info.fixed_range());
+ init_shared_pool(swiotlb_range).inspect_err(|_| {
+ error!("Failed to initialize shared pool.");
+ })?;
let bcc_handover: Box<dyn DiceArtifacts> = match vm_type(fdt)? {
VmType::ProtectedVm => {
let dice_range = read_dice_range_from(fdt)?;
info!("DICE range: {dice_range:#x?}");
- // SAFETY: This region was written by pvmfw in its writable_data region. The region
- // has no overlap with the main memory region and is safe to be mapped as read-only
- // data.
- let res = unsafe {
- MEMORY.lock().as_mut().unwrap().alloc_range_outside_main_memory(&dice_range)
- };
- res.inspect_err(|_| {
- error!("Failed to use DICE range from DT: {dice_range:#x?}");
- })?;
+ let dice_size = dice_range.len().try_into().unwrap();
+ // SAFETY: The DICE memory region has been generated by pvmfw and doesn't overlap.
+ unsafe { map_rodata_outside_main_memory(dice_range.start, dice_size) }.inspect_err(
+ |_| {
+ error!("Failed to use DICE range from DT: {dice_range:#x?}");
+ },
+ )?;
let dice_start = dice_range.start as *const u8;
// SAFETY: There's no memory overlap and the region is mapped as read-only data.
let bcc_handover = unsafe { slice::from_raw_parts(dice_start, dice_range.len()) };
@@ -158,8 +122,7 @@
let pci_info = PciInfo::from_fdt(fdt)?;
debug!("PCI: {pci_info:#x?}");
- let mut pci_root = pci::initialize(pci_info, MEMORY.lock().as_mut().unwrap())
- .map_err(Error::PciInitializationFailed)?;
+ let mut pci_root = pci::initialize(pci_info).map_err(Error::PciInitializationFailed)?;
debug!("PCI root: {pci_root:#x?}");
let socket_device = find_socket_device::<HalImpl>(&mut pci_root)?;
debug!("Found socket device: guest cid = {:?}", socket_device.guest_cid());
diff --git a/guest/trusty/security_vm/TEST_MAPPING b/guest/trusty/security_vm/TEST_MAPPING
new file mode 100644
index 0000000..ad7b899
--- /dev/null
+++ b/guest/trusty/security_vm/TEST_MAPPING
@@ -0,0 +1,15 @@
+{
+ "trusty-security_vm-presubmit": [
+ ],
+ "trusty-security_vm-postsubmit": [
+ {
+ "name": "VtsAidlKeyMintTargetTest"
+ },
+ {
+ "name": "VtsAidlSharedSecretTargetTest"
+ },
+ {
+ "name": "VtsHalRemotelyProvisionedComponentTargetTest"
+ }
+ ]
+}
diff --git a/guest/vmbase_example/src/layout.rs b/guest/vmbase_example/src/layout.rs
index 4e87e4e..bafce10 100644
--- a/guest/vmbase_example/src/layout.rs
+++ b/guest/vmbase_example/src/layout.rs
@@ -14,15 +14,8 @@
//! Memory layout.
-use aarch64_paging::paging::VirtualAddress;
-use core::ops::Range;
use log::info;
-use vmbase::{layout, memory::PAGE_SIZE};
-
-/// Writable data region for the stack.
-pub fn boot_stack_range() -> Range<VirtualAddress> {
- layout::stack_range(40 * PAGE_SIZE)
-}
+use vmbase::layout;
pub fn print_addresses() {
let text = layout::text_range();
@@ -40,7 +33,7 @@
);
let bss = layout::bss_range();
info!("bss: {}..{} ({} bytes)", bss.start, bss.end, bss.end - bss.start);
- let boot_stack = boot_stack_range();
+ let boot_stack = layout::stack_range();
info!(
"boot_stack: {}..{} ({} bytes)",
boot_stack.start,
diff --git a/guest/vmbase_example/src/main.rs b/guest/vmbase_example/src/main.rs
index f00effa..4c5e880 100644
--- a/guest/vmbase_example/src/main.rs
+++ b/guest/vmbase_example/src/main.rs
@@ -23,12 +23,9 @@
extern crate alloc;
-use crate::layout::{boot_stack_range, print_addresses};
-use crate::pci::{check_pci, get_bar_region, get_cam_region};
-use aarch64_paging::paging::VirtualAddress;
-use aarch64_paging::MapError;
+use crate::layout::print_addresses;
+use crate::pci::check_pci;
use alloc::{vec, vec::Vec};
-use core::mem;
use core::ptr::addr_of_mut;
use cstr::cstr;
use libfdt::Fdt;
@@ -37,12 +34,9 @@
bionic, configure_heap,
fdt::pci::PciInfo,
generate_image_header,
- layout::{
- console_uart_page, crosvm::FDT_MAX_SIZE, data_bss_range, eh_stack_range, rodata_range,
- text_range,
- },
+ layout::crosvm::FDT_MAX_SIZE,
linker, logger, main,
- memory::{PageTable, SIZE_64KB},
+ memory::{deactivate_dynamic_page_tables, map_data, SIZE_64KB},
};
static INITIALISED_DATA: [u32; 4] = [1, 2, 3, 4];
@@ -53,25 +47,6 @@
main!(main);
configure_heap!(SIZE_64KB);
-fn init_page_table(page_table: &mut PageTable) -> Result<(), MapError> {
- page_table.map_device(&console_uart_page().into())?;
- page_table.map_code(&text_range().into())?;
- page_table.map_rodata(&rodata_range().into())?;
- page_table.map_data(&data_bss_range().into())?;
- page_table.map_data(&eh_stack_range().into())?;
- page_table.map_data(&boot_stack_range().into())?;
-
- info!("Activating IdMap...");
- // SAFETY: page_table duplicates the static mappings for everything that the Rust code is
- // aware of so activating it shouldn't have any visible effect.
- unsafe {
- page_table.activate();
- }
- info!("Activated.");
-
- Ok(())
-}
-
/// Entry point for VM bootloader.
pub fn main(arg0: u64, arg1: u64, arg2: u64, arg3: u64) {
log::set_max_level(LevelFilter::Debug);
@@ -82,15 +57,11 @@
check_data();
check_stack_guard();
- let mut page_table = PageTable::default();
- init_page_table(&mut page_table).unwrap();
-
info!("Checking FDT...");
let fdt_addr = usize::try_from(arg0).unwrap();
// SAFETY: The DTB range is valid, writable memory, and we don't construct any aliases to it.
let fdt = unsafe { core::slice::from_raw_parts_mut(fdt_addr as *mut u8, FDT_MAX_SIZE) };
- let fdt_region = (VirtualAddress(fdt_addr)..VirtualAddress(fdt_addr + fdt.len())).into();
- page_table.map_data(&fdt_region).unwrap();
+ map_data(fdt_addr, FDT_MAX_SIZE.try_into().unwrap()).unwrap();
let fdt = Fdt::from_mut_slice(fdt).unwrap();
info!("FDT passed verification.");
check_fdt(fdt);
@@ -101,23 +72,16 @@
modify_fdt(fdt);
check_alloc();
-
- let cam_region = get_cam_region(&pci_info);
- page_table.map_device(&cam_region).unwrap();
- let bar_region = get_bar_region(&pci_info);
- page_table.map_device(&bar_region).unwrap();
-
check_data();
check_dice();
- // SAFETY: This is the only place where `make_pci_root` is called.
- let mut pci_root = unsafe { pci_info.make_pci_root() };
+ let mut pci_root = vmbase::virtio::pci::initialize(pci_info).unwrap();
check_pci(&mut pci_root);
emit_suppressed_log();
info!("De-activating IdMap...");
- mem::drop(page_table); // Release PageTable and switch back to idmap.S
+ deactivate_dynamic_page_tables();
info!("De-activated.");
}
diff --git a/guest/vmbase_example/src/pci.rs b/guest/vmbase_example/src/pci.rs
index 379425d..32ab9f6 100644
--- a/guest/vmbase_example/src/pci.rs
+++ b/guest/vmbase_example/src/pci.rs
@@ -14,7 +14,6 @@
//! Functions to scan the PCI bus for VirtIO device.
-use aarch64_paging::paging::MemoryRegion;
use alloc::alloc::{alloc_zeroed, dealloc, handle_alloc_error, Layout};
use core::{mem::size_of, ptr::NonNull};
use log::{debug, info};
@@ -26,10 +25,7 @@
},
BufferDirection, Error, Hal, PhysAddr, PAGE_SIZE,
};
-use vmbase::{
- fdt::pci::PciInfo,
- virtio::pci::{self, PciTransportIterator},
-};
+use vmbase::virtio::pci::{self, PciTransportIterator};
/// The standard sector size of a VirtIO block device, in bytes.
const SECTOR_SIZE_BYTES: usize = 512;
@@ -115,16 +111,6 @@
info!("Wrote to VirtIO console.");
}
-/// Gets the memory region in which BARs are allocated.
-pub fn get_bar_region(pci_info: &PciInfo) -> MemoryRegion {
- MemoryRegion::new(pci_info.bar_range.start as usize, pci_info.bar_range.end as usize)
-}
-
-/// Gets the PCI CAM memory region.
-pub fn get_cam_region(pci_info: &PciInfo) -> MemoryRegion {
- MemoryRegion::new(pci_info.cam_range.start, pci_info.cam_range.end)
-}
-
struct HalImpl;
/// SAFETY: See the 'Implementation Safety' comments on methods below for how they fulfill the
diff --git a/libs/dice/open_dice/Android.bp b/libs/dice/open_dice/Android.bp
index 4241c47..f799fb1 100644
--- a/libs/dice/open_dice/Android.bp
+++ b/libs/dice/open_dice/Android.bp
@@ -156,7 +156,7 @@
"--allowlist-var=DICE_INLINE_CONFIG_SIZE",
"--allowlist-var=DICE_PRIVATE_KEY_SEED_SIZE",
"--allowlist-var=DICE_ID_SIZE",
- "--allowlist-var=DICE_PRIVATE_KEY_SIZE",
+ "--allowlist-var=DICE_PRIVATE_KEY_BUFFER_SIZE",
],
}
diff --git a/libs/dice/open_dice/src/dice.rs b/libs/dice/open_dice/src/dice.rs
index 206769c..6c7d48d 100644
--- a/libs/dice/open_dice/src/dice.rs
+++ b/libs/dice/open_dice/src/dice.rs
@@ -21,7 +21,7 @@
use open_dice_cbor_bindgen::{
DiceConfigType, DiceDeriveCdiCertificateId, DiceDeriveCdiPrivateKeySeed, DiceInputValues,
DiceMainFlow, DICE_CDI_SIZE, DICE_HASH_SIZE, DICE_HIDDEN_SIZE, DICE_ID_SIZE,
- DICE_INLINE_CONFIG_SIZE, DICE_PRIVATE_KEY_SEED_SIZE, DICE_PRIVATE_KEY_SIZE,
+ DICE_INLINE_CONFIG_SIZE, DICE_PRIVATE_KEY_BUFFER_SIZE, DICE_PRIVATE_KEY_SEED_SIZE,
};
#[cfg(feature = "multialg")]
use open_dice_cbor_bindgen::{DiceContext_, DiceKeyAlgorithm};
@@ -41,7 +41,7 @@
/// The size of a private key seed.
pub const PRIVATE_KEY_SEED_SIZE: usize = DICE_PRIVATE_KEY_SEED_SIZE as usize;
/// The size of a private key.
-pub const PRIVATE_KEY_SIZE: usize = DICE_PRIVATE_KEY_SIZE as usize;
+pub const PRIVATE_KEY_SIZE: usize = DICE_PRIVATE_KEY_BUFFER_SIZE as usize;
/// The size of an ID.
pub const ID_SIZE: usize = DICE_ID_SIZE as usize;
diff --git a/libs/libavf/Android.bp b/libs/libavf/Android.bp
new file mode 100644
index 0000000..e143709
--- /dev/null
+++ b/libs/libavf/Android.bp
@@ -0,0 +1,58 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_bindgen {
+ name: "libavf_bindgen",
+ wrapper_src: "include/android/virtualization.h",
+ crate_name: "avf_bindgen",
+ defaults: ["avf_build_flags_rust"],
+ source_stem: "bindings",
+ bindgen_flags: ["--default-enum-style rust"],
+ apex_available: ["com.android.virt"],
+}
+
+rust_defaults {
+ name: "libavf.default",
+ crate_name: "avf",
+ defaults: ["avf_build_flags_rust"],
+ srcs: ["src/lib.rs"],
+ edition: "2021",
+ rustlibs: [
+ "libvmclient",
+ "android.system.virtualizationcommon-rust",
+ "android.system.virtualizationservice-rust",
+ "libavf_bindgen",
+ "libbinder_rs",
+ "liblibc",
+ "liblog_rust",
+ "librpcbinder_rs",
+ ],
+ apex_available: ["com.android.virt"],
+}
+
+rust_ffi_static {
+ name: "libavf_impl",
+ defaults: ["libavf.default"],
+ export_include_dirs: ["include"],
+}
+
+cc_library {
+ name: "libavf",
+ llndk: {
+ symbol_file: "libavf.map.txt",
+ moved_to_apex: true,
+ },
+ whole_static_libs: ["libavf_impl"],
+ shared_libs: [
+ "libbinder_ndk",
+ "libbinder_rpc_unstable",
+ "liblog",
+ ],
+ export_static_lib_headers: ["libavf_impl"],
+ apex_available: ["com.android.virt"],
+ version_script: "libavf.map.txt",
+ stubs: {
+ symbol_file: "libavf.map.txt",
+ },
+}
diff --git a/libs/libavf/include/android/virtualization.h b/libs/libavf/include/android/virtualization.h
new file mode 100644
index 0000000..f33ee75
--- /dev/null
+++ b/libs/libavf/include/android/virtualization.h
@@ -0,0 +1,326 @@
+/*
+ * Copyright 2024 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.
+ */
+#pragma once
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+__BEGIN_DECLS
+
+/**
+ * Represents a handle on a virtual machine raw config.
+ */
+typedef struct AVirtualMachineRawConfig AVirtualMachineRawConfig;
+
+/**
+ * Create a new virtual machine raw config object with no properties.
+ *
+ * This only creates the raw config object. `name` and `kernel` must be set with
+ * calls to {@link AVirtualMachineRawConfig_setName} and {@link AVirtualMachineRawConfig_setKernel}.
+ * Other properties, set by {@link AVirtualMachineRawConfig_setMemoryMib},
+ * {@link AVirtualMachineRawConfig_setInitRd}, {@link AVirtualMachineRawConfig_addDisk},
+ * {@link AVirtualMachineRawConfig_setProtectedVm}, and {@link AVirtualMachineRawConfig_setBalloon}
+ * are optional.
+ *
+ * The caller takes ownership of the returned raw config object, and is responsible for creating a
+ * VM by calling {@link AVirtualMachine_createRaw} or releasing it by calling
+ * {@link AVirtualMachineRawConfig_destroy}.
+ *
+ * \return A new virtual machine raw config object.
+ */
+AVirtualMachineRawConfig* AVirtualMachineRawConfig_create();
+
+/**
+ * Destroy a virtual machine config object.
+ *
+ * \param config a virtual machine config object.
+ *
+ * `AVirtualMachineRawConfig_destroy` does nothing if `config` is null. A destroyed config object
+ * must not be reused.
+ */
+void AVirtualMachineRawConfig_destroy(AVirtualMachineRawConfig* config);
+
+/**
+ * Set a name of a virtual machine.
+ *
+ * \param config a virtual machine config object.
+ * \param name a pointer to a null-terminated string for the name.
+ *
+ * \return If successful, it returns 0.
+ */
+int AVirtualMachineRawConfig_setName(AVirtualMachineRawConfig* config, const char* name);
+
+/**
+ * Set an instance ID of a virtual machine.
+ *
+ * \param config a virtual machine config object.
+ * \param instanceId a pointer to a 64-byte buffer for the instance ID.
+ *
+ * \return If successful, it returns 0.
+ */
+int AVirtualMachineRawConfig_setInstanceId(AVirtualMachineRawConfig* config,
+ const int8_t* instanceId);
+
+/**
+ * Set a kernel image of a virtual machine.
+ *
+ * \param config a virtual machine config object.
+ * \param fd a readable file descriptor containing the kernel image, or -1 to unset.
+ * `AVirtualMachineRawConfig_setKernel` takes ownership of `fd`.
+ *
+ * \return If successful, it returns 0.
+ */
+int AVirtualMachineRawConfig_setKernel(AVirtualMachineRawConfig* config, int fd);
+
+/**
+ * Set an init rd of a virtual machine.
+ *
+ * \param config a virtual machine config object.
+ * \param fd a readable file descriptor containing the kernel image, or -1 to unset.
+ * `AVirtualMachineRawConfig_setInitRd` takes ownership of `fd`.
+ *
+ * \return If successful, it returns 0.
+ */
+int AVirtualMachineRawConfig_setInitRd(AVirtualMachineRawConfig* config, int fd);
+
+/**
+ * Add a disk for a virtual machine.
+ *
+ * \param config a virtual machine config object.
+ * \param fd a readable file descriptor containing the disk image.
+ * `AVirtualMachineRawConfig_addDisk` takes ownership of `fd`.
+ *
+ * \return If successful, it returns 0. If `fd` is invalid, it returns -EINVAL.
+ */
+int AVirtualMachineRawConfig_addDisk(AVirtualMachineRawConfig* config, int fd);
+
+/**
+ * Set how much memory will be given to a virtual machine.
+ *
+ * \param config a virtual machine config object.
+ * \param memoryMib the amount of RAM to give the virtual machine, in MiB. 0 or negative to use the
+ * default.
+ *
+ * \return If successful, it returns 0.
+ */
+int AVirtualMachineRawConfig_setMemoryMib(AVirtualMachineRawConfig* config, int32_t memoryMib);
+
+/**
+ * Set whether a virtual machine is protected or not.
+ *
+ * \param config a virtual machine config object.
+ * \param protectedVm whether the virtual machine should be protected.
+ *
+ * \return If successful, it returns 0.
+ */
+int AVirtualMachineRawConfig_setProtectedVm(AVirtualMachineRawConfig* config, bool protectedVm);
+
+/**
+ * Set whether a virtual machine uses memory ballooning or not.
+ *
+ * \param config a virtual machine config object.
+ * \param balloon whether the virtual machine should use memory ballooning.
+ *
+ * \return If successful, it returns 0.
+ */
+int AVirtualMachineRawConfig_setBalloon(AVirtualMachineRawConfig* config, bool balloon);
+
+/**
+ * Set whether to use an alternate, hypervisor-specific authentication method
+ * for protected VMs. You don't want to use this.
+ *
+ * \return If successful, it returns 0. It returns `-ENOTSUP` if the hypervisor doesn't have an
+ * alternate auth mode.
+ */
+int AVirtualMachineRawConfig_setHypervisorSpecificAuthMethod(AVirtualMachineRawConfig* config,
+ bool enable);
+
+/**
+ * Use the specified fd as the backing memfd for a range of the guest
+ * physical memory.
+ *
+ * \param config a virtual machine config object.
+ * \param fd a memfd
+ * \param rangeStart range start IPA
+ * \param rangeEnd range end IPA
+ *
+ * \return If successful, it returns 0. It returns `-ENOTSUP` if the hypervisor doesn't support
+ * backing memfd.
+ */
+int AVirtualMachineRawConfig_addCustomMemoryBackingFile(AVirtualMachineRawConfig* config, int fd,
+ size_t rangeStart, size_t rangeEnd);
+
+/**
+ * Represents a handle on a virtualization service, responsible for managing virtual machines.
+ */
+typedef struct AVirtualizationService AVirtualizationService;
+
+/**
+ * Spawn a new instance of `virtmgr`, a child process that will host the `VirtualizationService`
+ * service, and connect to the child process.
+ *
+ * The caller takes ownership of the returned service object, and is responsible for releasing it
+ * by calling {@link AVirtualizationService_destroy}.
+ *
+ * \param early set to true when running a service for early virtual machines. See
+ * [`early_vm.md`](../../../../docs/early_vm.md) for more details on early virtual machines.
+ * \param service an out parameter that will be set to the service handle.
+ *
+ * \return
+ * - If successful, it sets `service` and returns 0.
+ * - If it fails to spawn `virtmgr`, it leaves `service` untouched and returns a negative value
+ * representing the OS error code.
+ * - If it fails to connect to the spawned `virtmgr`, it leaves `service` untouched and returns
+ * `-ECONNREFUSED`.
+ */
+int AVirtualizationService_create(AVirtualizationService** service, bool early);
+
+/**
+ * Destroy a VirtualizationService object.
+ *
+ * `AVirtualizationService_destroy` does nothing if `service` is null. A destroyed service object
+ * must not be reused.
+ *
+ * \param service a handle on a virtualization service.
+ */
+void AVirtualizationService_destroy(AVirtualizationService* service);
+
+/**
+ * Represents a handle on a virtual machine.
+ */
+typedef struct AVirtualMachine AVirtualMachine;
+
+/**
+ * The reason why a virtual machine stopped.
+ * @see AVirtualMachine_waitForStop
+ */
+enum StopReason : int32_t {
+ /**
+ * VirtualizationService died.
+ */
+ VIRTUALIZATION_SERVICE_DIED = 1,
+ /**
+ * There was an error waiting for the virtual machine.
+ */
+ INFRASTRUCTURE_ERROR = 2,
+ /**
+ * The virtual machine was killed.
+ */
+ KILLED = 3,
+ /**
+ * The virtual machine stopped for an unknown reason.
+ */
+ UNKNOWN = 4,
+ /**
+ * The virtual machine requested to shut down.
+ */
+ SHUTDOWN = 5,
+ /**
+ * crosvm had an error starting the virtual machine.
+ */
+ START_FAILED = 6,
+ /**
+ * The virtual machine requested to reboot, possibly as the result of a kernel panic.
+ */
+ REBOOT = 7,
+ /**
+ * The virtual machine or crosvm crashed.
+ */
+ CRASH = 8,
+ /**
+ * The pVM firmware failed to verify the VM because the public key doesn't match.
+ */
+ PVM_FIRMWARE_PUBLIC_KEY_MISMATCH = 9,
+ /**
+ * The pVM firmware failed to verify the VM because the instance image changed.
+ */
+ PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED = 10,
+ /**
+ * The virtual machine was killed due to hangup.
+ */
+ HANGUP = 11,
+ /**
+ * VirtualizationService sent a stop reason which was not recognised by the client library.
+ */
+ UNRECOGNISED = 0,
+};
+
+/**
+ * Create a virtual machine with given raw `config`.
+ *
+ * The created virtual machine is in stopped state. To run the created virtual machine, call
+ * {@link AVirtualMachine_start}.
+ *
+ * The caller takes ownership of the returned virtual machine object, and is responsible for
+ * releasing it by calling {@link AVirtualMachine_destroy}.
+ *
+ * \param service a handle on a virtualization service.
+ * \param config a virtual machine config object. Ownership will always be transferred from the
+ * caller, even if unsuccessful. `config` must not be reused.
+ * \param consoleOutFd a writable file descriptor for the console output, or -1. Ownership will
+ * always be transferred from the caller, even if unsuccessful.
+ * \param consoleInFd a readable file descriptor for the console input, or -1. Ownership will always
+ * be transferred from the caller, even if unsuccessful.
+ * \param logFd a writable file descriptor for the log output, or -1. Ownership will always be
+ * transferred from the caller, even if unsuccessful.
+ * \param vm an out parameter that will be set to the virtual machine handle.
+ *
+ * \return If successful, it sets `vm` and returns 0. Otherwise, it leaves `vm` untouched and
+ * returns `-EIO`.
+ */
+int AVirtualMachine_createRaw(const AVirtualizationService* service,
+ AVirtualMachineRawConfig* config, int consoleOutFd, int consoleInFd,
+ int logFd, AVirtualMachine** vm);
+
+/**
+ * Start a virtual machine.
+ *
+ * \param vm a handle on a virtual machine.
+ *
+ * \return If successful, it returns 0. Otherwise, it returns `-EIO`.
+ */
+int AVirtualMachine_start(AVirtualMachine* vm);
+
+/**
+ * Stop a virtual machine.
+ *
+ * \param vm a handle on a virtual machine.
+ *
+ * \return If successful, it returns 0. Otherwise, it returns `-EIO`.
+ */
+int AVirtualMachine_stop(AVirtualMachine* vm);
+
+/**
+ * Wait until a virtual machine stops.
+ *
+ * \param vm a handle on a virtual machine.
+ *
+ * \return The reason why the virtual machine stopped.
+ */
+enum StopReason AVirtualMachine_waitForStop(AVirtualMachine* vm);
+
+/**
+ * Destroy a virtual machine.
+ *
+ * `AVirtualMachine_destroy` does nothing if `vm` is null. A destroyed virtual machine must not be
+ * reused.
+ *
+ * \param vm a handle on a virtual machine.
+ */
+void AVirtualMachine_destroy(AVirtualMachine* vm);
+
+__END_DECLS
diff --git a/libs/libavf/libavf.map.txt b/libs/libavf/libavf.map.txt
new file mode 100644
index 0000000..ecb4cc9
--- /dev/null
+++ b/libs/libavf/libavf.map.txt
@@ -0,0 +1,24 @@
+LIBAVF {
+ global:
+ AVirtualMachineRawConfig_create; # apex llndk
+ AVirtualMachineRawConfig_destroy; # apex llndk
+ AVirtualMachineRawConfig_setName; # apex llndk
+ AVirtualMachineRawConfig_setInstanceId; # apex llndk
+ AVirtualMachineRawConfig_setKernel; # apex llndk
+ AVirtualMachineRawConfig_setInitRd; # apex llndk
+ AVirtualMachineRawConfig_addDisk; # apex llndk
+ AVirtualMachineRawConfig_setMemoryMib; # apex llndk
+ AVirtualMachineRawConfig_setProtectedVm; # apex llndk
+ AVirtualMachineRawConfig_setBalloon; # apex llndk
+ AVirtualMachineRawConfig_setHypervisorSpecificAuthMethod; # apex llndk
+ AVirtualMachineRawConfig_addCustomMemoryBackingFile; # apex llndk
+ AVirtualizationService_create; # apex llndk
+ AVirtualizationService_destroy; # apex llndk
+ AVirtualMachine_createRaw; # apex llndk
+ AVirtualMachine_start; # apex llndk
+ AVirtualMachine_stop; # apex llndk
+ AVirtualMachine_waitForStop; # apex llndk
+ AVirtualMachine_destroy; # apex llndk
+ local:
+ *;
+};
diff --git a/libs/libavf/src/lib.rs b/libs/libavf/src/lib.rs
new file mode 100644
index 0000000..0a8f891
--- /dev/null
+++ b/libs/libavf/src/lib.rs
@@ -0,0 +1,413 @@
+// Copyright 2024 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.
+
+//! Stable C library for AVF.
+
+use std::ffi::CStr;
+use std::fs::File;
+use std::os::fd::FromRawFd;
+use std::os::raw::{c_char, c_int};
+use std::ptr;
+
+use android_system_virtualizationservice::{
+ aidl::android::system::virtualizationservice::{
+ DiskImage::DiskImage, IVirtualizationService::IVirtualizationService,
+ VirtualMachineConfig::VirtualMachineConfig,
+ VirtualMachineRawConfig::VirtualMachineRawConfig,
+ },
+ binder::{ParcelFileDescriptor, Strong},
+};
+use avf_bindgen::StopReason;
+use vmclient::{DeathReason, VirtualizationService, VmInstance};
+
+/// Create a new virtual machine config object with no properties.
+#[no_mangle]
+pub extern "C" fn AVirtualMachineRawConfig_create() -> *mut VirtualMachineRawConfig {
+ let config = Box::new(VirtualMachineRawConfig {
+ platformVersion: "~1.0".to_owned(),
+ ..Default::default()
+ });
+ Box::into_raw(config)
+}
+
+/// Destroy a virtual machine config object.
+///
+/// # Safety
+/// `config` must be a pointer returned by `AVirtualMachineRawConfig_create`. `config` must not be
+/// used after deletion.
+#[no_mangle]
+pub unsafe extern "C" fn AVirtualMachineRawConfig_destroy(config: *mut VirtualMachineRawConfig) {
+ if !config.is_null() {
+ // SAFETY: `config` is assumed to be a valid, non-null pointer returned by
+ // AVirtualMachineRawConfig_create. It's the only reference to the object.
+ unsafe {
+ let _ = Box::from_raw(config);
+ }
+ }
+}
+
+/// Set a name of a virtual machine.
+///
+/// # Safety
+/// `config` must be a pointer returned by `AVirtualMachineRawConfig_create`.
+#[no_mangle]
+pub unsafe extern "C" fn AVirtualMachineRawConfig_setName(
+ config: *mut VirtualMachineRawConfig,
+ name: *const c_char,
+) -> c_int {
+ // SAFETY: `config` is assumed to be a valid, non-null pointer returned by
+ // AVirtualMachineRawConfig_create. It's the only reference to the object.
+ let config = unsafe { &mut *config };
+ // SAFETY: `name` is assumed to be a pointer to a valid C string.
+ config.name = unsafe { CStr::from_ptr(name) }.to_string_lossy().into_owned();
+ 0
+}
+
+/// Set an instance ID of a virtual machine.
+///
+/// # Safety
+/// `config` must be a pointer returned by `AVirtualMachineRawConfig_create`. `instanceId` must be a
+/// valid, non-null pointer to 64-byte data.
+#[no_mangle]
+pub unsafe extern "C" fn AVirtualMachineRawConfig_setInstanceId(
+ config: *mut VirtualMachineRawConfig,
+ instance_id: *const u8,
+) -> c_int {
+ // SAFETY: `config` is assumed to be a valid, non-null pointer returned by
+ // AVirtualMachineRawConfig_create. It's the only reference to the object.
+ let config = unsafe { &mut *config };
+ // SAFETY: `instanceId` is assumed to be a valid pointer to 64 bytes of memory. `config`
+ // is assumed to be a valid object returned by AVirtuaMachineConfig_create.
+ // Both never overlap.
+ unsafe {
+ ptr::copy_nonoverlapping(instance_id, config.instanceId.as_mut_ptr(), 64);
+ }
+ 0
+}
+
+/// Set a kernel image of a virtual machine.
+///
+/// # Safety
+/// `config` must be a pointer returned by `AVirtualMachineRawConfig_create`. `fd` must be a valid
+/// file descriptor or -1. `AVirtualMachineRawConfig_setKernel` takes ownership of `fd` and `fd`
+/// will be closed upon `AVirtualMachineRawConfig_delete`.
+#[no_mangle]
+pub unsafe extern "C" fn AVirtualMachineRawConfig_setKernel(
+ config: *mut VirtualMachineRawConfig,
+ fd: c_int,
+) -> c_int {
+ let file = get_file_from_fd(fd);
+ // SAFETY: `config` is assumed to be a valid, non-null pointer returned by
+ // AVirtualMachineRawConfig_create. It's the only reference to the object.
+ let config = unsafe { &mut *config };
+ config.kernel = file.map(ParcelFileDescriptor::new);
+ 0
+}
+
+/// Set an init rd of a virtual machine.
+///
+/// # Safety
+/// `config` must be a pointer returned by `AVirtualMachineRawConfig_create`. `fd` must be a valid
+/// file descriptor or -1. `AVirtualMachineRawConfig_setInitRd` takes ownership of `fd` and `fd`
+/// will be closed upon `AVirtualMachineRawConfig_delete`.
+#[no_mangle]
+pub unsafe extern "C" fn AVirtualMachineRawConfig_setInitRd(
+ config: *mut VirtualMachineRawConfig,
+ fd: c_int,
+) -> c_int {
+ let file = get_file_from_fd(fd);
+ // SAFETY: `config` is assumed to be a valid, non-null pointer returned by
+ // AVirtualMachineRawConfig_create. It's the only reference to the object.
+ let config = unsafe { &mut *config };
+ config.initrd = file.map(ParcelFileDescriptor::new);
+ 0
+}
+
+/// Add a disk for a virtual machine.
+///
+/// # Safety
+/// `config` must be a pointer returned by `AVirtualMachineRawConfig_create`. `fd` must be a valid
+/// file descriptor. `AVirtualMachineRawConfig_addDisk` takes ownership of `fd` and `fd` will be
+/// closed upon `AVirtualMachineRawConfig_delete`.
+#[no_mangle]
+pub unsafe extern "C" fn AVirtualMachineRawConfig_addDisk(
+ config: *mut VirtualMachineRawConfig,
+ fd: c_int,
+ writable: bool,
+) -> c_int {
+ let file = get_file_from_fd(fd);
+ // SAFETY: `config` is assumed to be a valid, non-null pointer returned by
+ // AVirtualMachineRawConfig_create. It's the only reference to the object.
+ let config = unsafe { &mut *config };
+ match file {
+ // partition not supported yet
+ None => -libc::EINVAL,
+ Some(file) => {
+ config.disks.push(DiskImage {
+ image: Some(ParcelFileDescriptor::new(file)),
+ writable,
+ ..Default::default()
+ });
+ 0
+ }
+ }
+}
+
+/// Set how much memory will be given to a virtual machine.
+///
+/// # Safety
+/// `config` must be a pointer returned by `AVirtualMachineRawConfig_create`.
+#[no_mangle]
+pub unsafe extern "C" fn AVirtualMachineRawConfig_setMemoryMib(
+ config: *mut VirtualMachineRawConfig,
+ memory_mib: i32,
+) -> c_int {
+ // SAFETY: `config` is assumed to be a valid, non-null pointer returned by
+ // AVirtualMachineRawConfig_create. It's the only reference to the object.
+ let config = unsafe { &mut *config };
+ config.memoryMib = memory_mib;
+ 0
+}
+
+/// Set whether a virtual machine is protected or not.
+///
+/// # Safety
+/// `config` must be a pointer returned by `AVirtualMachineRawConfig_create`.
+#[no_mangle]
+pub unsafe extern "C" fn AVirtualMachineRawConfig_setProtectedVm(
+ config: *mut VirtualMachineRawConfig,
+ protected_vm: bool,
+) -> c_int {
+ // SAFETY: `config` is assumed to be a valid, non-null pointer returned by
+ // AVirtualMachineRawConfig_create. It's the only reference to the object.
+ let config = unsafe { &mut *config };
+ config.protectedVm = protected_vm;
+ 0
+}
+
+/// Set whether a virtual machine uses memory ballooning or not.
+///
+/// # Safety
+/// `config` must be a pointer returned by `AVirtualMachineRawConfig_create`.
+#[no_mangle]
+pub unsafe extern "C" fn AVirtualMachineRawConfig_setBalloon(
+ config: *mut VirtualMachineRawConfig,
+ balloon: bool,
+) -> c_int {
+ // SAFETY: `config` is assumed to be a valid, non-null pointer returned by
+ // AVirtualMachineRawConfig_create. It's the only reference to the object.
+ let config = unsafe { &mut *config };
+ config.noBalloon = !balloon;
+ 0
+}
+
+/// NOT IMPLEMENTED.
+///
+/// # Returns
+/// It always returns `-ENOTSUP`.
+#[no_mangle]
+pub extern "C" fn AVirtualMachineRawConfig_setHypervisorSpecificAuthMethod(
+ _config: *mut VirtualMachineRawConfig,
+ _enable: bool,
+) -> c_int {
+ -libc::ENOTSUP
+}
+
+/// NOT IMPLEMENTED.
+///
+/// # Returns
+/// It always returns `-ENOTSUP`.
+#[no_mangle]
+pub extern "C" fn AVirtualMachineRawConfig_addCustomMemoryBackingFile(
+ _config: *mut VirtualMachineRawConfig,
+ _fd: c_int,
+ _range_start: usize,
+ _range_end: usize,
+) -> c_int {
+ -libc::ENOTSUP
+}
+
+/// Spawn a new instance of `virtmgr`, a child process that will host the `VirtualizationService`
+/// AIDL service, and connect to the child process.
+///
+/// # Safety
+/// `service_ptr` must be a valid, non-null pointer to a mutable raw pointer.
+#[no_mangle]
+pub unsafe extern "C" fn AVirtualizationService_create(
+ service_ptr: *mut *mut Strong<dyn IVirtualizationService>,
+ early: bool,
+) -> c_int {
+ let virtmgr =
+ if early { VirtualizationService::new_early() } else { VirtualizationService::new() };
+ let virtmgr = match virtmgr {
+ Ok(virtmgr) => virtmgr,
+ Err(e) => return -e.raw_os_error().unwrap_or(libc::EIO),
+ };
+ match virtmgr.connect() {
+ Ok(service) => {
+ // SAFETY: `service` is assumed to be a valid, non-null pointer to a mutable raw
+ // pointer. `service` is the only reference here and `config` takes
+ // ownership.
+ unsafe {
+ *service_ptr = Box::into_raw(Box::new(service));
+ }
+ 0
+ }
+ Err(_) => -libc::ECONNREFUSED,
+ }
+}
+
+/// Destroy a VirtualizationService object.
+///
+/// # Safety
+/// `service` must be a pointer returned by `AVirtualizationService_create` or
+/// `AVirtualizationService_create_early`. `service` must not be reused after deletion.
+#[no_mangle]
+pub unsafe extern "C" fn AVirtualizationService_destroy(
+ service: *mut Strong<dyn IVirtualizationService>,
+) {
+ if !service.is_null() {
+ // SAFETY: `service` is assumed to be a valid, non-null pointer returned by
+ // `AVirtualizationService_create`. It's the only reference to the object.
+ unsafe {
+ let _ = Box::from_raw(service);
+ }
+ }
+}
+
+/// Create a virtual machine with given `config`.
+///
+/// # Safety
+/// `config` must be a pointer returned by `AVirtualMachineRawConfig_create`. `service` must be a
+/// pointer returned by `AVirtualMachineRawConfig_create`. `vm_ptr` must be a valid, non-null
+/// pointer to a mutable raw pointer. `console_out_fd`, `console_in_fd`, and `log_fd` must be a
+/// valid file descriptor or -1. `AVirtualMachine_create` takes ownership of `console_out_fd`,
+/// `console_in_fd`, and `log_fd`, and taken file descriptors must not be reused.
+#[no_mangle]
+pub unsafe extern "C" fn AVirtualMachine_createRaw(
+ service: *const Strong<dyn IVirtualizationService>,
+ config: *mut VirtualMachineRawConfig,
+ console_out_fd: c_int,
+ console_in_fd: c_int,
+ log_fd: c_int,
+ vm_ptr: *mut *mut VmInstance,
+) -> c_int {
+ // SAFETY: `service` is assumed to be a valid, non-null pointer returned by
+ // `AVirtualizationService_create` or `AVirtualizationService_create_early`. It's the only
+ // reference to the object.
+ let service = unsafe { &*service };
+
+ // SAFETY: `config` is assumed to be a valid, non-null pointer returned by
+ // `AVirtualMachineRawConfig_create`. It's the only reference to the object.
+ let config = unsafe { *Box::from_raw(config) };
+ let config = VirtualMachineConfig::RawConfig(config);
+
+ let console_out = get_file_from_fd(console_out_fd);
+ let console_in = get_file_from_fd(console_in_fd);
+ let log = get_file_from_fd(log_fd);
+
+ match VmInstance::create(service.as_ref(), &config, console_out, console_in, log, None, None) {
+ Ok(vm) => {
+ // SAFETY: `vm_ptr` is assumed to be a valid, non-null pointer to a mutable raw pointer.
+ // `vm` is the only reference here and `vm_ptr` takes ownership.
+ unsafe {
+ *vm_ptr = Box::into_raw(Box::new(vm));
+ }
+ 0
+ }
+ Err(_) => -libc::EIO,
+ }
+}
+
+/// Start a virtual machine.
+///
+/// # Safety
+/// `vm` must be a pointer returned by `AVirtualMachine_createRaw`.
+#[no_mangle]
+pub unsafe extern "C" fn AVirtualMachine_start(vm: *const VmInstance) -> c_int {
+ // SAFETY: `vm` is assumed to be a valid, non-null pointer returned by
+ // `AVirtualMachine_createRaw`. It's the only reference to the object.
+ let vm = unsafe { &*vm };
+ match vm.start() {
+ Ok(_) => 0,
+ Err(_) => -libc::EIO,
+ }
+}
+
+/// Stop a virtual machine.
+///
+/// # Safety
+/// `vm` must be a pointer returned by `AVirtualMachine_create`.
+#[no_mangle]
+pub unsafe extern "C" fn AVirtualMachine_stop(vm: *const VmInstance) -> c_int {
+ // SAFETY: `vm` is assumed to be a valid, non-null pointer returned by
+ // `AVirtualMachine_createRaw`. It's the only reference to the object.
+ let vm = unsafe { &*vm };
+ match vm.stop() {
+ Ok(_) => 0,
+ Err(_) => -libc::EIO,
+ }
+}
+
+/// Wait until a virtual machine stops.
+///
+/// # Safety
+/// `vm` must be a pointer returned by `AVirtualMachine_createRaw`.
+#[no_mangle]
+pub unsafe extern "C" fn AVirtualMachine_waitForStop(vm: *const VmInstance) -> StopReason {
+ // SAFETY: `vm` is assumed to be a valid, non-null pointer returned by
+ // AVirtualMachine_create. It's the only reference to the object.
+ let vm = unsafe { &*vm };
+ match vm.wait_for_death() {
+ DeathReason::VirtualizationServiceDied => StopReason::VIRTUALIZATION_SERVICE_DIED,
+ DeathReason::InfrastructureError => StopReason::INFRASTRUCTURE_ERROR,
+ DeathReason::Killed => StopReason::KILLED,
+ DeathReason::Unknown => StopReason::UNKNOWN,
+ DeathReason::Shutdown => StopReason::SHUTDOWN,
+ DeathReason::StartFailed => StopReason::START_FAILED,
+ DeathReason::Reboot => StopReason::REBOOT,
+ DeathReason::Crash => StopReason::CRASH,
+ DeathReason::PvmFirmwarePublicKeyMismatch => StopReason::PVM_FIRMWARE_PUBLIC_KEY_MISMATCH,
+ DeathReason::PvmFirmwareInstanceImageChanged => {
+ StopReason::PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED
+ }
+ DeathReason::Hangup => StopReason::HANGUP,
+ _ => StopReason::UNRECOGNISED,
+ }
+}
+
+/// Destroy a virtual machine.
+///
+/// # Safety
+/// `vm` must be a pointer returned by `AVirtualMachine_createRaw`. `vm` must not be reused after
+/// deletion.
+#[no_mangle]
+pub unsafe extern "C" fn AVirtualMachine_destroy(vm: *mut VmInstance) {
+ if !vm.is_null() {
+ // SAFETY: `vm` is assumed to be a valid, non-null pointer returned by
+ // AVirtualMachine_create. It's the only reference to the object.
+ unsafe {
+ let _ = Box::from_raw(vm);
+ }
+ }
+}
+
+fn get_file_from_fd(fd: i32) -> Option<File> {
+ if fd == -1 {
+ None
+ } else {
+ // SAFETY: transferring ownership of `fd` from the caller
+ Some(unsafe { File::from_raw_fd(fd) })
+ }
+}
diff --git a/libs/libvmbase/sections.ld b/libs/libvmbase/sections.ld
index 222edae..9d69935 100644
--- a/libs/libvmbase/sections.ld
+++ b/libs/libvmbase/sections.ld
@@ -132,3 +132,10 @@
*(.note.gnu.build-id)
}
}
+
+/*
+ * Make calling the limit_stack_size!() macro optional by providing a default.
+ */
+PROVIDE(vmbase_stack_limit = DEFINED(vmbase_stack_limit_client) ?
+ vmbase_stack_limit_client :
+ vmbase_stack_limit_default);
diff --git a/libs/libvmbase/src/entry.rs b/libs/libvmbase/src/entry.rs
index 2433722..b681aea 100644
--- a/libs/libvmbase/src/entry.rs
+++ b/libs/libvmbase/src/entry.rs
@@ -18,7 +18,7 @@
bionic, console, heap,
layout::{UART_ADDRESSES, UART_PAGE_ADDR},
logger,
- memory::{PAGE_SIZE, SIZE_16KB, SIZE_4KB},
+ memory::{switch_to_dynamic_page_tables, PAGE_SIZE, SIZE_16KB, SIZE_4KB},
power::{reboot, shutdown},
rand,
};
@@ -82,6 +82,8 @@
bionic::__get_tls().stack_guard = u64::from_ne_bytes(stack_guard);
+ switch_to_dynamic_page_tables();
+
// Note: If rust_entry ever returned (which it shouldn't by being -> !), the compiler-injected
// stack guard comparison would detect a mismatch and call __stack_chk_fail.
diff --git a/libs/libvmbase/src/layout.rs b/libs/libvmbase/src/layout.rs
index a8f7827..9a702b0 100644
--- a/libs/libvmbase/src/layout.rs
+++ b/libs/libvmbase/src/layout.rs
@@ -17,7 +17,7 @@
pub mod crosvm;
use crate::linker::__stack_chk_guard;
-use crate::memory::{page_4kb_of, PAGE_SIZE};
+use crate::memory::{max_stack_size, page_4kb_of, PAGE_SIZE};
use aarch64_paging::paging::VirtualAddress;
use core::ops::Range;
use core::ptr::addr_of;
@@ -91,10 +91,16 @@
}
/// Writable data region for the stack.
-pub fn stack_range(stack_size: usize) -> Range<VirtualAddress> {
+pub fn stack_range() -> Range<VirtualAddress> {
let end = linker_addr!(init_stack_pointer);
- let start = VirtualAddress(end.0.checked_sub(stack_size).unwrap());
- assert!(start >= linker_addr!(stack_limit));
+ let start = if let Some(stack_size) = max_stack_size() {
+ assert_eq!(stack_size % PAGE_SIZE, 0);
+ let start = VirtualAddress(end.0.checked_sub(stack_size).unwrap());
+ assert!(start >= linker_addr!(stack_limit));
+ start
+ } else {
+ linker_addr!(stack_limit)
+ };
start..end
}
diff --git a/libs/libvmbase/src/memory.rs b/libs/libvmbase/src/memory.rs
index e0ea207..fd4706f 100644
--- a/libs/libvmbase/src/memory.rs
+++ b/libs/libvmbase/src/memory.rs
@@ -18,17 +18,24 @@
mod error;
mod page_table;
mod shared;
+mod stack;
mod tracker;
mod util;
pub use error::MemoryTrackerError;
pub use page_table::PageTable;
pub use shared::MemoryRange;
-pub use tracker::{MemoryTracker, MEMORY};
+pub use tracker::{
+ deactivate_dynamic_page_tables, init_shared_pool, map_data, map_device, map_image_footer,
+ map_rodata, map_rodata_outside_main_memory, resize_available_memory, unshare_all_memory,
+ unshare_all_mmio_except_uart, unshare_uart,
+};
pub use util::{
flush, flushed_zeroize, page_4kb_of, PAGE_SIZE, SIZE_128KB, SIZE_16KB, SIZE_2MB, SIZE_4KB,
SIZE_4MB, SIZE_64KB,
};
pub(crate) use shared::{alloc_shared, dealloc_shared};
+pub(crate) use stack::max_stack_size;
+pub(crate) use tracker::{switch_to_dynamic_page_tables, MEMORY};
pub(crate) use util::{phys_to_virt, virt_to_phys};
diff --git a/libs/libvmbase/src/memory/error.rs b/libs/libvmbase/src/memory/error.rs
index 870e4c9..2c00518 100644
--- a/libs/libvmbase/src/memory/error.rs
+++ b/libs/libvmbase/src/memory/error.rs
@@ -21,6 +21,8 @@
/// Errors for MemoryTracker operations.
#[derive(Debug, Clone)]
pub enum MemoryTrackerError {
+ /// MemoryTracker not configured or deactivated.
+ Unavailable,
/// Tried to modify the memory base address.
DifferentBaseAddress,
/// Tried to shrink to a larger memory size.
@@ -43,6 +45,8 @@
SharedMemorySetFailure,
/// Failure to set `SHARED_POOL`.
SharedPoolSetFailure,
+ /// Rejected request to map footer that is already mapped.
+ FooterAlreadyMapped,
/// Invalid page table entry.
InvalidPte,
/// Failed to flush memory region.
@@ -58,6 +62,7 @@
impl fmt::Display for MemoryTrackerError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
+ Self::Unavailable => write!(f, "MemoryTracker is not available"),
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"),
@@ -69,6 +74,7 @@
Self::Hypervisor(e) => e.fmt(f),
Self::SharedMemorySetFailure => write!(f, "Failed to set SHARED_MEMORY"),
Self::SharedPoolSetFailure => write!(f, "Failed to set SHARED_POOL"),
+ Self::FooterAlreadyMapped => write!(f, "Refused to map image footer again"),
Self::InvalidPte => write!(f, "Page table entry is not valid"),
Self::FlushRegionFailed => write!(f, "Failed to flush memory region"),
Self::SetPteDirtyFailed => write!(f, "Failed to set PTE dirty state"),
diff --git a/libs/libvmbase/src/memory/stack.rs b/libs/libvmbase/src/memory/stack.rs
new file mode 100644
index 0000000..639029e
--- /dev/null
+++ b/libs/libvmbase/src/memory/stack.rs
@@ -0,0 +1,41 @@
+// Copyright 2024, 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 stack support.
+
+/// Configures the maximum size of the stack.
+#[macro_export]
+macro_rules! limit_stack_size {
+ ($len:expr) => {
+ #[export_name = "vmbase_stack_limit_client"]
+ fn __vmbase_stack_limit_client() -> Option<usize> {
+ Some($len)
+ }
+ };
+}
+
+pub(crate) fn max_stack_size() -> Option<usize> {
+ extern "Rust" {
+ fn vmbase_stack_limit() -> Option<usize>;
+ }
+ // SAFETY: This function is safe to call as the linker script aliases it to either:
+ // - the safe vmbase_stack_limit_default();
+ // - the safe vmbase_stack_limit_client() potentially defined using limit_stack_size!()
+ unsafe { vmbase_stack_limit() }
+}
+
+#[no_mangle]
+fn vmbase_stack_limit_default() -> Option<usize> {
+ None
+}
diff --git a/libs/libvmbase/src/memory/tracker.rs b/libs/libvmbase/src/memory/tracker.rs
index c1f5d54..3416dc6 100644
--- a/libs/libvmbase/src/memory/tracker.rs
+++ b/libs/libvmbase/src/memory/tracker.rs
@@ -19,6 +19,7 @@
use super::page_table::{PageTable, MMIO_LAZY_MAP_FLAG};
use super::shared::{SHARED_MEMORY, SHARED_POOL};
use crate::dsb;
+use crate::layout;
use crate::memory::shared::{MemoryRange, MemorySharer, MmioSharer};
use crate::util::RangeExt as _;
use aarch64_paging::paging::{Attributes, Descriptor, MemoryRegion as VaRange, VirtualAddress};
@@ -28,13 +29,13 @@
use core::num::NonZeroUsize;
use core::ops::Range;
use core::result;
-use hypervisor_backends::get_mmio_guard;
-use log::{debug, error};
-use spin::mutex::SpinMutex;
+use hypervisor_backends::{get_mem_sharer, get_mmio_guard};
+use log::{debug, error, info};
+use spin::mutex::{SpinMutex, SpinMutexGuard};
use tinyvec::ArrayVec;
/// A global static variable representing the system memory tracker, protected by a spin mutex.
-pub static MEMORY: SpinMutex<Option<MemoryTracker>> = SpinMutex::new(None);
+pub(crate) static MEMORY: SpinMutex<Option<MemoryTracker>> = SpinMutex::new(None);
fn get_va_range(range: &MemoryRange) -> VaRange {
VaRange::new(range.start, range.end)
@@ -42,6 +43,140 @@
type Result<T> = result::Result<T, MemoryTrackerError>;
+/// Attempts to lock `MEMORY`, returns an error if already deactivated.
+fn try_lock_memory_tracker() -> Result<SpinMutexGuard<'static, Option<MemoryTracker>>> {
+ // Being single-threaded, we only spin if `deactivate_dynamic_page_tables()` leaked the lock.
+ MEMORY.try_lock().ok_or(MemoryTrackerError::Unavailable)
+}
+
+/// Switch the MMU to the provided PageTable.
+///
+/// Panics if called more than once.
+pub(crate) fn switch_to_dynamic_page_tables() {
+ let mut locked_tracker = try_lock_memory_tracker().unwrap();
+ if locked_tracker.is_some() {
+ panic!("switch_to_dynamic_page_tables() called more than once.");
+ }
+
+ locked_tracker.replace(MemoryTracker::new(
+ layout::crosvm::MEM_START..layout::MAX_VIRT_ADDR,
+ layout::crosvm::MMIO_RANGE,
+ ));
+}
+
+/// Switch the MMU back to the static page tables (see `idmap` C symbol).
+///
+/// Panics if called before `switch_to_dynamic_page_tables()` or more than once.
+pub fn deactivate_dynamic_page_tables() {
+ let locked_tracker = try_lock_memory_tracker().unwrap();
+ // Force future calls to try_lock_memory_tracker() to fail by leaking this lock guard.
+ let leaked_tracker = SpinMutexGuard::leak(locked_tracker);
+ // Force deallocation/unsharing of all the resources used by the MemoryTracker.
+ drop(leaked_tracker.take())
+}
+
+/// Redefines the actual mappable range of memory.
+///
+/// Fails if a region has already been mapped beyond the new upper limit.
+pub fn resize_available_memory(memory_range: &Range<usize>) -> Result<()> {
+ let mut locked_tracker = try_lock_memory_tracker()?;
+ let tracker = locked_tracker.as_mut().ok_or(MemoryTrackerError::Unavailable)?;
+ tracker.shrink(memory_range)
+}
+
+/// Initialize the memory pool for page sharing with the host.
+pub fn init_shared_pool(static_range: Option<Range<usize>>) -> Result<()> {
+ let mut locked_tracker = try_lock_memory_tracker()?;
+ let tracker = locked_tracker.as_mut().ok_or(MemoryTrackerError::Unavailable)?;
+ if let Some(mem_sharer) = get_mem_sharer() {
+ let granule = mem_sharer.granule()?;
+ tracker.init_dynamic_shared_pool(granule)
+ } else if let Some(r) = static_range {
+ tracker.init_static_shared_pool(r)
+ } else {
+ info!("Initialized shared pool from heap memory without MEM_SHARE");
+ tracker.init_heap_shared_pool()
+ }
+}
+
+/// Unshare all MMIO that was previously shared with the host, with the exception of the UART page.
+pub fn unshare_all_mmio_except_uart() -> Result<()> {
+ let Ok(mut locked_tracker) = try_lock_memory_tracker() else { return Ok(()) };
+ let Some(tracker) = locked_tracker.as_mut() else { return Ok(()) };
+ if cfg!(feature = "compat_android_13") {
+ info!("Expecting a bug making MMIO_GUARD_UNMAP return NOT_SUPPORTED on success");
+ }
+ tracker.unshare_all_mmio()
+}
+
+/// Unshare all memory that was previously shared with the host.
+pub fn unshare_all_memory() {
+ let Ok(mut locked_tracker) = try_lock_memory_tracker() else { return };
+ let Some(tracker) = locked_tracker.as_mut() else { return };
+ tracker.unshare_all_memory()
+}
+
+/// Unshare the UART page, previously shared with the host.
+pub fn unshare_uart() -> Result<()> {
+ let Some(mmio_guard) = get_mmio_guard() else { return Ok(()) };
+ Ok(mmio_guard.unmap(layout::UART_PAGE_ADDR)?)
+}
+
+/// Map the provided range as normal memory, with R/W permissions.
+///
+/// This fails if the range has already been (partially) mapped.
+pub fn map_data(addr: usize, size: NonZeroUsize) -> Result<()> {
+ let mut locked_tracker = try_lock_memory_tracker()?;
+ let tracker = locked_tracker.as_mut().ok_or(MemoryTrackerError::Unavailable)?;
+ let _ = tracker.alloc_mut(addr, size)?;
+ Ok(())
+}
+
+/// Map the region potentially holding data appended to the image, with read-write permissions.
+///
+/// This fails if the footer has already been mapped.
+pub fn map_image_footer() -> Result<Range<usize>> {
+ let mut locked_tracker = try_lock_memory_tracker()?;
+ let tracker = locked_tracker.as_mut().ok_or(MemoryTrackerError::Unavailable)?;
+ let range = tracker.map_image_footer()?;
+ Ok(range)
+}
+
+/// Map the provided range as normal memory, with read-only permissions.
+///
+/// This fails if the range has already been (partially) mapped.
+pub fn map_rodata(addr: usize, size: NonZeroUsize) -> Result<()> {
+ let mut locked_tracker = try_lock_memory_tracker()?;
+ let tracker = locked_tracker.as_mut().ok_or(MemoryTrackerError::Unavailable)?;
+ let _ = tracker.alloc(addr, size)?;
+ Ok(())
+}
+
+// TODO(ptosi): Merge this into map_rodata.
+/// Map the provided range as normal memory, with read-only permissions.
+///
+/// # Safety
+///
+/// Callers of this method need to ensure that the `range` is valid for mapping as read-only data.
+pub unsafe fn map_rodata_outside_main_memory(addr: usize, size: NonZeroUsize) -> Result<()> {
+ let mut locked_tracker = try_lock_memory_tracker()?;
+ let tracker = locked_tracker.as_mut().ok_or(MemoryTrackerError::Unavailable)?;
+ let end = addr + usize::from(size);
+ // SAFETY: Caller has checked that it is valid to map the range.
+ let _ = unsafe { tracker.alloc_range_outside_main_memory(&(addr..end)) }?;
+ Ok(())
+}
+
+/// Map the provided range as device memory.
+///
+/// This fails if the range has already been (partially) mapped.
+pub fn map_device(addr: usize, size: NonZeroUsize) -> Result<()> {
+ let mut locked_tracker = try_lock_memory_tracker()?;
+ let tracker = locked_tracker.as_mut().ok_or(MemoryTrackerError::Unavailable)?;
+ let range = addr..(addr + usize::from(size));
+ tracker.map_mmio_range(range.clone())
+}
+
#[derive(Clone, Copy, Debug, Default, PartialEq)]
enum MemoryType {
#[default]
@@ -56,13 +191,13 @@
}
/// Tracks non-overlapping slices of main memory.
-pub struct MemoryTracker {
+pub(crate) struct MemoryTracker {
total: MemoryRange,
page_table: PageTable,
regions: ArrayVec<[MemoryRegion; MemoryTracker::CAPACITY]>,
mmio_regions: ArrayVec<[MemoryRange; MemoryTracker::MMIO_CAPACITY]>,
mmio_range: MemoryRange,
- payload_range: Option<MemoryRange>,
+ image_footer_mapped: bool,
mmio_sharer: MmioSharer,
}
@@ -71,17 +206,13 @@
const MMIO_CAPACITY: usize = 5;
/// Creates a new instance from an active page table, covering the maximum RAM size.
- pub fn new(
- mut page_table: PageTable,
- total: MemoryRange,
- mmio_range: MemoryRange,
- payload_range: Option<Range<VirtualAddress>>,
- ) -> Self {
+ fn new(total: MemoryRange, mmio_range: MemoryRange) -> Self {
assert!(
!total.overlaps(&mmio_range),
"MMIO space should not overlap with the main memory region."
);
+ let mut page_table = Self::initialize_dynamic_page_tables();
// Activate dirty state management first, otherwise we may get permission faults immediately
// after activating the new page table. This has no effect before the new page table is
// activated because none of the entries in the initial idmap have the DBM flag.
@@ -99,7 +230,7 @@
regions: ArrayVec::new(),
mmio_regions: ArrayVec::new(),
mmio_range,
- payload_range: payload_range.map(|r| r.start.0..r.end.0),
+ image_footer_mapped: false,
mmio_sharer: MmioSharer::new().unwrap(),
}
}
@@ -107,7 +238,7 @@
/// 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<()> {
+ fn shrink(&mut self, range: &MemoryRange) -> Result<()> {
if range.start != self.total.start {
return Err(MemoryTrackerError::DifferentBaseAddress);
}
@@ -123,7 +254,7 @@
}
/// Allocate the address range for a const slice; returns None if failed.
- pub fn alloc_range(&mut self, range: &MemoryRange) -> Result<MemoryRange> {
+ fn alloc_range(&mut self, range: &MemoryRange) -> Result<MemoryRange> {
let region = MemoryRegion { range: range.clone(), mem_type: MemoryType::ReadOnly };
self.check_allocatable(®ion)?;
self.page_table.map_rodata(&get_va_range(range)).map_err(|e| {
@@ -139,7 +270,7 @@
///
/// Callers of this method need to ensure that the `range` is valid for mapping as read-only
/// data.
- pub unsafe fn alloc_range_outside_main_memory(
+ unsafe fn alloc_range_outside_main_memory(
&mut self,
range: &MemoryRange,
) -> Result<MemoryRange> {
@@ -153,7 +284,7 @@
}
/// Allocate the address range for a mutable slice; returns None if failed.
- pub fn alloc_range_mut(&mut self, range: &MemoryRange) -> Result<MemoryRange> {
+ fn alloc_range_mut(&mut self, range: &MemoryRange) -> Result<MemoryRange> {
let region = MemoryRegion { range: range.clone(), mem_type: MemoryType::ReadWrite };
self.check_allocatable(®ion)?;
self.page_table.map_data_dbm(&get_va_range(range)).map_err(|e| {
@@ -163,19 +294,33 @@
self.add(region)
}
+ /// Maps the image footer read-write, with permissions.
+ fn map_image_footer(&mut self) -> Result<MemoryRange> {
+ if self.image_footer_mapped {
+ return Err(MemoryTrackerError::FooterAlreadyMapped);
+ }
+ let range = layout::image_footer_range();
+ self.page_table.map_data_dbm(&range.clone().into()).map_err(|e| {
+ error!("Error during image footer map: {e}");
+ MemoryTrackerError::FailedToMap
+ })?;
+ self.image_footer_mapped = true;
+ Ok(range.start.0..range.end.0)
+ }
+
/// Allocate the address range for a const slice; returns None if failed.
- pub fn alloc(&mut self, base: usize, size: NonZeroUsize) -> Result<MemoryRange> {
+ 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> {
+ fn alloc_mut(&mut self, base: usize, size: NonZeroUsize) -> Result<MemoryRange> {
self.alloc_range_mut(&(base..(base + size.get())))
}
/// 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<()> {
+ fn map_mmio_range(&mut self, range: MemoryRange) -> Result<()> {
if !range.is_within(&self.mmio_range) {
return Err(MemoryTrackerError::OutOfRange);
}
@@ -237,14 +382,14 @@
}
/// Unshares any MMIO region previously shared with the MMIO guard.
- pub fn unshare_all_mmio(&mut self) -> Result<()> {
+ fn unshare_all_mmio(&mut self) -> Result<()> {
self.mmio_sharer.unshare_all();
Ok(())
}
/// Initialize the shared heap to dynamically share memory from the global allocator.
- pub fn init_dynamic_shared_pool(&mut self, granule: usize) -> Result<()> {
+ fn init_dynamic_shared_pool(&mut self, granule: usize) -> Result<()> {
const INIT_CAP: usize = 10;
let previous = SHARED_MEMORY.lock().replace(MemorySharer::new(granule, INIT_CAP));
@@ -266,7 +411,7 @@
/// of guest memory as "shared" ahead of guest starting its execution. The
/// shared memory region is indicated in swiotlb node. On such platforms use
/// a separate heap to allocate buffers that can be shared with host.
- pub fn init_static_shared_pool(&mut self, range: Range<usize>) -> Result<()> {
+ fn init_static_shared_pool(&mut self, range: Range<usize>) -> Result<()> {
let size = NonZeroUsize::new(range.len()).unwrap();
let range = self.alloc_mut(range.start, size)?;
let shared_pool = LockedFrameAllocator::<32>::new();
@@ -285,7 +430,7 @@
/// When running on "non-protected" hypervisors which permit host direct accesses to guest
/// memory, there is no need to perform any memory sharing and/or allocate buffers from a
/// dedicated region so this function instructs the shared pool to use the global allocator.
- pub fn init_heap_shared_pool(&mut self) -> Result<()> {
+ fn init_heap_shared_pool(&mut self) -> Result<()> {
// As MemorySharer only calls MEM_SHARE methods if the hypervisor supports them, internally
// using init_dynamic_shared_pool() on a non-protected platform will make use of the heap
// without any actual "dynamic memory sharing" taking place and, as such, the granule may
@@ -336,11 +481,17 @@
// observed before reading PTE flags to determine dirty state.
dsb!("ish");
// Now flush writable-dirty pages in those regions.
- for range in writable_regions.chain(self.payload_range.as_ref().into_iter()) {
+ for range in writable_regions {
self.page_table
.walk_range(&get_va_range(range), &flush_dirty_range)
.map_err(|_| MemoryTrackerError::FlushRegionFailed)?;
}
+ if self.image_footer_mapped {
+ let range = layout::image_footer_range();
+ self.page_table
+ .walk_range(&range.into(), &flush_dirty_range)
+ .map_err(|_| MemoryTrackerError::FlushRegionFailed)?;
+ }
Ok(())
}
@@ -352,6 +503,28 @@
.modify_range(&(addr..addr + 1).into(), &mark_dirty_block)
.map_err(|_| MemoryTrackerError::SetPteDirtyFailed)
}
+
+ // TODO(ptosi): Move this and `PageTable` references to crate::arch::aarch64
+ /// Produces a `PageTable` that can safely replace the static PTs.
+ fn initialize_dynamic_page_tables() -> PageTable {
+ let text = layout::text_range();
+ let rodata = layout::rodata_range();
+ let data_bss = layout::data_bss_range();
+ let eh_stack = layout::eh_stack_range();
+ let stack = layout::stack_range();
+ let console_uart_page = layout::console_uart_page();
+
+ let mut page_table = PageTable::default();
+
+ page_table.map_device(&console_uart_page.into()).unwrap();
+ page_table.map_code(&text.into()).unwrap();
+ page_table.map_rodata(&rodata.into()).unwrap();
+ page_table.map_data(&data_bss.into()).unwrap();
+ page_table.map_data(&eh_stack.into()).unwrap();
+ page_table.map_data(&stack.into()).unwrap();
+
+ page_table
+ }
}
impl Drop for MemoryTracker {
diff --git a/libs/libvmbase/src/virtio/pci.rs b/libs/libvmbase/src/virtio/pci.rs
index 72e648b..ec89b6b 100644
--- a/libs/libvmbase/src/virtio/pci.rs
+++ b/libs/libvmbase/src/virtio/pci.rs
@@ -16,7 +16,7 @@
use crate::{
fdt::pci::PciInfo,
- memory::{MemoryTracker, MemoryTrackerError},
+ memory::{map_device, MemoryTrackerError},
};
use alloc::boxed::Box;
use core::fmt;
@@ -65,16 +65,19 @@
/// 2. Stores the `PciInfo` for the VirtIO HAL to use later.
/// 3. Creates and returns a `PciRoot`.
///
-/// This must only be called once; it will panic if it is called a second time.
-pub fn initialize(pci_info: PciInfo, memory: &mut MemoryTracker) -> Result<PciRoot, PciError> {
+/// This must only be called once and after having switched to the dynamic page tables.
+pub fn initialize(pci_info: PciInfo) -> Result<PciRoot, PciError> {
PCI_INFO.set(Box::new(pci_info.clone())).map_err(|_| PciError::DuplicateInitialization)?;
- memory.map_mmio_range(pci_info.cam_range.clone()).map_err(PciError::CamMapFailed)?;
- let bar_range = pci_info.bar_range.start as usize..pci_info.bar_range.end as usize;
- memory.map_mmio_range(bar_range).map_err(PciError::BarMapFailed)?;
+ let cam_start = pci_info.cam_range.start;
+ let cam_size = pci_info.cam_range.len().try_into().unwrap();
+ map_device(cam_start, cam_size).map_err(PciError::CamMapFailed)?;
- // Safety: This is the only place where we call make_pci_root, and `PCI_INFO.set` above will
- // panic if it is called a second time.
+ let bar_start = pci_info.bar_range.start.try_into().unwrap();
+ let bar_size = pci_info.bar_range.len().try_into().unwrap();
+ map_device(bar_start, bar_size).map_err(PciError::BarMapFailed)?;
+
+ // SAFETY: This is the only place where we call make_pci_root, validated by `PCI_INFO.set`.
Ok(unsafe { pci_info.make_pci_root() })
}
diff --git a/libs/libvmclient/src/lib.rs b/libs/libvmclient/src/lib.rs
index 13630c0..c0baea5 100644
--- a/libs/libvmclient/src/lib.rs
+++ b/libs/libvmclient/src/lib.rs
@@ -243,6 +243,11 @@
self.vm.start()
}
+ /// Stops the VM.
+ pub fn stop(&self) -> BinderResult<()> {
+ self.vm.stop()
+ }
+
/// Returns the CID used for vsock connections to the VM.
pub fn cid(&self) -> i32 {
self.cid