Merge "Import translations. DO NOT MERGE ANYWHERE" into main
diff --git a/android/TerminalApp/Android.bp b/android/TerminalApp/Android.bp
index 4bb9703..59f18df 100644
--- a/android/TerminalApp/Android.bp
+++ b/android/TerminalApp/Android.bp
@@ -18,6 +18,7 @@
"debian-service-grpclib-lite",
"gson",
"VmTerminalApp.aidl-java",
+ "MicrodroidTestHelper", // for DeviceProperties class
],
jni_libs: [
"libforwarder_host_jni",
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/ErrorActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/ErrorActivity.java
index 44dcce5..7099f22 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/ErrorActivity.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/ErrorActivity.java
@@ -25,6 +25,10 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
public class ErrorActivity extends BaseActivity {
private static final String EXTRA_CAUSE = "cause";
@@ -59,7 +63,8 @@
Exception e = intent.getParcelableExtra(EXTRA_CAUSE, Exception.class);
TextView cause = findViewById(R.id.cause);
if (e != null) {
- cause.setText(getString(R.string.error_code, e.toString()));
+ String stackTrace = getStackTrace(e);
+ cause.setText(getString(R.string.error_code, stackTrace));
} else {
cause.setText(null);
}
@@ -69,4 +74,15 @@
Intent intent = new Intent(this, SettingsRecoveryActivity.class);
startActivity(intent);
}
+
+ private static String getStackTrace(Exception e) {
+ try (StringWriter sWriter = new StringWriter();
+ PrintWriter pWriter = new PrintWriter(sWriter)) {
+ e.printStackTrace(pWriter);
+ return sWriter.toString();
+ } catch (IOException ex) {
+ // This shall never happen
+ throw new RuntimeException(ex);
+ }
+ }
}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
index 316c8c4..016af83 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
@@ -33,6 +33,7 @@
import android.os.ConditionVariable;
import android.os.Environment;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.provider.Settings;
import android.util.Log;
import android.view.KeyEvent;
@@ -56,6 +57,7 @@
import androidx.annotation.NonNull;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.microdroid.test.common.DeviceProperties;
import com.google.android.material.appbar.MaterialToolbar;
@@ -77,10 +79,19 @@
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 TERMINAL_CONNECTION_TIMEOUT_MS = 20_000;
+ private static final int TERMINAL_CONNECTION_TIMEOUT_MS;
private static final int REQUEST_CODE_INSTALLER = 0x33;
private static final int FONT_SIZE_DEFAULT = 13;
+ static {
+ DeviceProperties prop = DeviceProperties.create(SystemProperties::get);
+ if (prop.isCuttlefish() || prop.isGoldfish()) {
+ TERMINAL_CONNECTION_TIMEOUT_MS = 180_000; // 3 minutes
+ } else {
+ TERMINAL_CONNECTION_TIMEOUT_MS = 20_000; // 20 sec
+ }
+ }
+
private ExecutorService mExecutorService;
private InstalledImage mImage;
private X509Certificate[] mCertificates;
diff --git a/android/VmAttestationDemoApp/src/main.rs b/android/VmAttestationDemoApp/src/main.rs
index 26df52c..bcc056d 100644
--- a/android/VmAttestationDemoApp/src/main.rs
+++ b/android/VmAttestationDemoApp/src/main.rs
@@ -16,7 +16,6 @@
use anyhow::{ensure, Context, Result};
use log::{error, info};
-use std::panic;
use vm_payload::AttestationError;
vm_payload::main!(main);
@@ -28,10 +27,6 @@
.with_tag("service_vm_client")
.with_max_level(log::LevelFilter::Debug),
);
- // Redirect panic messages to logcat.
- panic::set_hook(Box::new(|panic_info| {
- error!("{}", panic_info);
- }));
if let Err(e) = try_main() {
error!("failed with {:?}", e);
std::process::exit(1);
diff --git a/android/compos_verify/verify.rs b/android/compos_verify/verify.rs
index b94ebbc..a88d00c 100644
--- a/android/compos_verify/verify.rs
+++ b/android/compos_verify/verify.rs
@@ -34,7 +34,6 @@
use std::fs;
use std::fs::File;
use std::io::Read;
-use std::panic;
use std::path::Path;
const MAX_FILE_SIZE_BYTES: u64 = 100 * 1024;
@@ -65,11 +64,6 @@
.with_log_buffer(LogId::System), // Needed to log successfully early in boot
);
- // Redirect panic messages to logcat.
- panic::set_hook(Box::new(|panic_info| {
- error!("{}", panic_info);
- }));
-
if let Err(e) = try_main() {
error!("{:?}", e);
std::process::exit(1)
diff --git a/android/composd/src/composd_main.rs b/android/composd/src/composd_main.rs
index e5d6c75..0b142f2 100644
--- a/android/composd/src/composd_main.rs
+++ b/android/composd/src/composd_main.rs
@@ -28,7 +28,6 @@
use anyhow::{Context, Result};
use binder::{register_lazy_service, ProcessState};
use log::{error, info};
-use std::panic;
use std::sync::Arc;
#[allow(clippy::eq_op)]
@@ -39,11 +38,6 @@
android_logger::Config::default().with_tag("composd").with_max_level(log_level),
);
- // Redirect panic messages to logcat.
- panic::set_hook(Box::new(|panic_info| {
- log::error!("{}", panic_info);
- }));
-
ProcessState::start_thread_pool();
let virtmgr =
diff --git a/android/virtmgr/src/aidl.rs b/android/virtmgr/src/aidl.rs
index 15a80a6..0cde751 100644
--- a/android/virtmgr/src/aidl.rs
+++ b/android/virtmgr/src/aidl.rs
@@ -20,7 +20,7 @@
use crate::crosvm::{AudioConfig, CrosvmConfig, DiskFile, SharedPathConfig, DisplayConfig, GpuConfig, InputDeviceOption, PayloadState, UsbConfig, VmContext, VmInstance, VmState};
use crate::debug_config::{DebugConfig, DebugPolicy};
use crate::dt_overlay::{create_device_tree_overlay, VM_DT_OVERLAY_MAX_SIZE, VM_DT_OVERLAY_PATH};
-use crate::payload::{add_microdroid_payload_images, add_microdroid_system_images, add_microdroid_vendor_image};
+use crate::payload::{ApexInfoList, add_microdroid_payload_images, add_microdroid_system_images, add_microdroid_vendor_image};
use crate::selinux::{check_tee_service_permission, getfilecon, getprevcon, SeContext};
use android_os_permissions_aidl::aidl::android::os::IPermissionController;
use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::{
@@ -29,6 +29,7 @@
ErrorCode::ErrorCode,
};
use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
+ AssignedDevices::AssignedDevices,
AssignableDevice::AssignableDevice,
CpuTopology::CpuTopology,
DiskImage::DiskImage,
@@ -137,6 +138,21 @@
static SUPPORTED_OS_NAMES: LazyLock<HashSet<String>> =
LazyLock::new(|| get_supported_os_names().expect("Failed to get list of supported os names"));
+static CALLING_EXE_PATH: LazyLock<Option<PathBuf>> = LazyLock::new(|| {
+ let calling_exe_link = format!("/proc/{}/exe", get_calling_pid());
+ match fs::read_link(&calling_exe_link) {
+ Ok(calling_exe_path) => Some(calling_exe_path),
+ Err(e) => {
+ // virtmgr forked from apps fails to read /proc probably due to hidepid=2. As we
+ // discourage vendor apps, regarding such cases as system is safer.
+ // TODO(b/383969737): determine if this is okay. Or find a way how to track origins of
+ // apps.
+ warn!("can't read_link '{calling_exe_link}': {e:?}; regarding as system");
+ None
+ }
+ }
+});
+
fn create_or_update_idsig_file(
input_fd: &ParcelFileDescriptor,
idsig_fd: &ParcelFileDescriptor,
@@ -406,10 +422,38 @@
}
}
-fn find_partition(path: &Path) -> binder::Result<String> {
- match path.components().nth(1) {
+fn find_partition(path: Option<&Path>) -> binder::Result<String> {
+ let path = match path {
+ Some(path) => path,
+ None => return Ok("system".to_owned()),
+ };
+ let mut components = path.components();
+ match components.nth(1) {
Some(std::path::Component::Normal(partition)) => {
- Ok(partition.to_string_lossy().into_owned())
+ if partition != "apex" {
+ return Ok(partition.to_string_lossy().into_owned());
+ }
+
+ // If path is under /apex, find a partition of the preinstalled .apex path
+ let apex_name = match components.next() {
+ Some(std::path::Component::Normal(name)) => name.to_string_lossy(),
+ _ => {
+ return Err(anyhow!("Can't find apex name for '{}'", path.display()))
+ .or_service_specific_exception(-1)
+ }
+ };
+
+ let apex_info_list = ApexInfoList::load()
+ .context("Failed to get apex info list")
+ .or_service_specific_exception(-1)?;
+
+ for apex_info in apex_info_list.list.iter() {
+ if apex_info.name == apex_name {
+ return Ok(apex_info.partition.to_lowercase());
+ }
+ }
+
+ Err(anyhow!("Can't find apex info for '{apex_name}'")).or_service_specific_exception(-1)
}
_ => Err(anyhow!("Can't find partition in '{}'", path.display()))
.or_service_specific_exception(-1),
@@ -424,24 +468,27 @@
fn create_early_vm_context(
&self,
config: &VirtualMachineConfig,
+ calling_exe_path: Option<&Path>,
) -> binder::Result<(VmContext, Cid, PathBuf)> {
- let calling_exe_path = format!("/proc/{}/exe", get_calling_pid());
- let link = fs::read_link(&calling_exe_path)
- .context(format!("can't read_link '{calling_exe_path}'"))
- .or_service_specific_exception(-1)?;
- let partition = find_partition(&link)?;
-
let name = match config {
VirtualMachineConfig::RawConfig(config) => &config.name,
VirtualMachineConfig::AppConfig(config) => &config.name,
};
- let early_vm =
- find_early_vm_for_partition(&partition, name).or_service_specific_exception(-1)?;
- if Path::new(&early_vm.path) != link {
+ let calling_partition = find_partition(calling_exe_path)?;
+ let early_vm = find_early_vm_for_partition(&calling_partition, name)
+ .or_service_specific_exception(-1)?;
+ let calling_exe_path = match calling_exe_path {
+ Some(path) => path,
+ None => {
+ return Err(anyhow!("Can't verify the path of PID {}", get_calling_pid()))
+ .or_service_specific_exception(-1)
+ }
+ };
+ if Path::new(&early_vm.path) != calling_exe_path {
return Err(anyhow!(
- "VM '{name}' in partition '{partition}' must be created with '{}', not '{}'",
+ "VM '{name}' in partition '{calling_partition}' must be created with '{}', not '{}'",
&early_vm.path,
- link.display()
+ calling_exe_path.display()
))
.or_service_specific_exception(-1);
}
@@ -525,7 +572,7 @@
// Allocating VM context checks the MANAGE_VIRTUAL_MACHINE permission.
let (vm_context, cid, temporary_directory) = if cfg!(early) {
- self.create_early_vm_context(config)?
+ self.create_early_vm_context(config, CALLING_EXE_PATH.as_deref())?
} else {
self.create_vm_context(requester_debug_pid, config)?
};
@@ -541,7 +588,19 @@
check_gdb_allowed(config)?;
}
- let device_tree_overlay = maybe_create_device_tree_overlay(config, &temporary_directory)?;
+ let mut device_tree_overlays = vec![];
+ if let Some(dt_overlay) = maybe_create_reference_dt_overlay(config, &temporary_directory)? {
+ device_tree_overlays.push(dt_overlay);
+ }
+ if let Some(dtbo) = get_dtbo(config) {
+ let dtbo = File::from(
+ dtbo.as_ref()
+ .try_clone()
+ .context("Failed to create VM DTBO from ParcelFileDescriptor")
+ .or_binder_exception(ExceptionCode::BAD_PARCELABLE)?,
+ );
+ device_tree_overlays.push(dtbo);
+ }
let debug_config = DebugConfig::new(config);
let ramdump = if !uses_gki_kernel(config) && debug_config.is_ramdump_needed() {
@@ -625,7 +684,8 @@
// Check if files for payloads and bases are NOT coming from /vendor and /odm, as they may
// have unstable interfaces.
// TODO(b/316431494): remove once Treble interfaces are stabilized.
- check_partitions_for_files(config).or_service_specific_exception(-1)?;
+ check_partitions_for_files(config, &find_partition(CALLING_EXE_PATH.as_deref())?)
+ .or_service_specific_exception(-1)?;
let zero_filler_path = temporary_directory.join("zero.img");
write_zero_filler(&zero_filler_path)
@@ -668,29 +728,30 @@
}
};
- let (vfio_devices, dtbo) = if !config.devices.is_empty() {
- let mut set = HashSet::new();
- for device in config.devices.iter() {
- let path = canonicalize(device)
- .with_context(|| format!("can't canonicalize {device}"))
- .or_service_specific_exception(-1)?;
- if !set.insert(path) {
- return Err(anyhow!("duplicated device {device}"))
- .or_binder_exception(ExceptionCode::ILLEGAL_ARGUMENT);
+ let (vfio_devices, dtbo) = match &config.devices {
+ AssignedDevices::Devices(devices) if !devices.is_empty() => {
+ let mut set = HashSet::new();
+ for device in devices.iter() {
+ let path = canonicalize(device)
+ .with_context(|| format!("can't canonicalize {device}"))
+ .or_service_specific_exception(-1)?;
+ if !set.insert(path) {
+ return Err(anyhow!("duplicated device {device}"))
+ .or_binder_exception(ExceptionCode::ILLEGAL_ARGUMENT);
+ }
}
+ let devices = GLOBAL_SERVICE.bindDevicesToVfioDriver(devices)?;
+ let dtbo_file = File::from(
+ GLOBAL_SERVICE
+ .getDtboFile()?
+ .as_ref()
+ .try_clone()
+ .context("Failed to create VM DTBO from ParcelFileDescriptor")
+ .or_binder_exception(ExceptionCode::BAD_PARCELABLE)?,
+ );
+ (devices, Some(dtbo_file))
}
- let devices = GLOBAL_SERVICE.bindDevicesToVfioDriver(&config.devices)?;
- let dtbo_file = File::from(
- GLOBAL_SERVICE
- .getDtboFile()?
- .as_ref()
- .try_clone()
- .context("Failed to create VM DTBO from ParcelFileDescriptor")
- .or_binder_exception(ExceptionCode::BAD_PARCELABLE)?,
- );
- (devices, Some(dtbo_file))
- } else {
- (vec![], None)
+ _ => (vec![], None),
};
let display_config = if cfg!(paravirtualized_devices) {
config
@@ -774,6 +835,7 @@
.ok()
.and_then(NonZeroU32::new)
.unwrap_or(NonZeroU32::new(256).unwrap()),
+ swiotlb_mib: config.swiotlbMib.try_into().ok().and_then(NonZeroU32::new),
cpus,
host_cpu_topology,
console_out_fd,
@@ -786,7 +848,7 @@
gdb_port,
vfio_devices,
dtbo,
- device_tree_overlay,
+ device_tree_overlays,
display_config,
input_device_options,
hugepages: config.hugePages,
@@ -883,7 +945,7 @@
Err(anyhow!("No hashtree digest is extracted from microdroid vendor image"))
}
-fn maybe_create_device_tree_overlay(
+fn maybe_create_reference_dt_overlay(
config: &VirtualMachineConfig,
temporary_directory: &Path,
) -> binder::Result<Option<File>> {
@@ -952,6 +1014,16 @@
Ok(device_tree_overlay)
}
+fn get_dtbo(config: &VirtualMachineConfig) -> Option<&ParcelFileDescriptor> {
+ let VirtualMachineConfig::RawConfig(config) = config else {
+ return None;
+ };
+ match &config.devices {
+ AssignedDevices::Dtbo(dtbo) => dtbo.as_ref(),
+ _ => None,
+ }
+}
+
fn write_zero_filler(zero_filler_path: &Path) -> Result<()> {
let file = OpenOptions::new()
.create_new(true)
@@ -1212,7 +1284,7 @@
append_kernel_param("androidboot.microdroid.mount_vendor=1", &mut vm_config)
}
- vm_config.devices.clone_from(&custom_config.devices);
+ vm_config.devices = AssignedDevices::Devices(custom_config.devices.clone());
vm_config.networkSupported = custom_config.networkSupported;
for param in custom_config.extraKernelCmdlineParams.iter() {
@@ -1260,7 +1332,7 @@
Ok(vm_config)
}
-fn check_partition_for_file(fd: &ParcelFileDescriptor) -> Result<()> {
+fn check_partition_for_file(fd: &ParcelFileDescriptor, calling_partition: &str) -> Result<()> {
let path = format!("/proc/self/fd/{}", fd.as_raw_fd());
let link = fs::read_link(&path).context(format!("can't read_link {path}"))?;
@@ -1270,24 +1342,39 @@
return Ok(());
}
- if link.starts_with("/vendor") || link.starts_with("/odm") {
- bail!("vendor or odm file {} can't be used for VM", link.display());
+ let is_fd_vendor = link.starts_with("/vendor") || link.starts_with("/odm");
+ let is_caller_vendor = calling_partition == "vendor" || calling_partition == "odm";
+
+ if is_fd_vendor != is_caller_vendor {
+ bail!("{} can't be used for VM client in {calling_partition}", link.display());
}
Ok(())
}
-fn check_partitions_for_files(config: &VirtualMachineRawConfig) -> Result<()> {
+fn check_partitions_for_files(
+ config: &VirtualMachineRawConfig,
+ calling_partition: &str,
+) -> Result<()> {
config
.disks
.iter()
.flat_map(|disk| disk.partitions.iter())
.filter_map(|partition| partition.image.as_ref())
- .try_for_each(check_partition_for_file)?;
+ .try_for_each(|fd| check_partition_for_file(fd, calling_partition))?;
- config.kernel.as_ref().map_or(Ok(()), check_partition_for_file)?;
- config.initrd.as_ref().map_or(Ok(()), check_partition_for_file)?;
- config.bootloader.as_ref().map_or(Ok(()), check_partition_for_file)?;
+ config
+ .disks
+ .iter()
+ .filter_map(|disk| disk.image.as_ref())
+ .try_for_each(|fd| check_partition_for_file(fd, calling_partition))?;
+
+ config.kernel.as_ref().map_or(Ok(()), |fd| check_partition_for_file(fd, calling_partition))?;
+ config.initrd.as_ref().map_or(Ok(()), |fd| check_partition_for_file(fd, calling_partition))?;
+ config
+ .bootloader
+ .as_ref()
+ .map_or(Ok(()), |fd| check_partition_for_file(fd, calling_partition))?;
Ok(())
}
@@ -2138,6 +2225,7 @@
match partition {
"system" => Ok(100..200),
"system_ext" | "product" => Ok(200..300),
+ "vendor" | "odm" => Ok(300..400),
_ => Err(anyhow!("Early VMs are not supported for {partition}")),
}
}
diff --git a/android/virtmgr/src/crosvm.rs b/android/virtmgr/src/crosvm.rs
index 2bfa4e1..f3b669f 100644
--- a/android/virtmgr/src/crosvm.rs
+++ b/android/virtmgr/src/crosvm.rs
@@ -112,6 +112,7 @@
pub protected: bool,
pub debug_config: DebugConfig,
pub memory_mib: NonZeroU32,
+ pub swiotlb_mib: Option<NonZeroU32>,
pub cpus: Option<NonZeroU32>,
pub host_cpu_topology: bool,
pub console_out_fd: Option<File>,
@@ -124,7 +125,7 @@
pub gdb_port: Option<NonZeroU16>,
pub vfio_devices: Vec<VfioDevice>,
pub dtbo: Option<File>,
- pub device_tree_overlay: Option<File>,
+ pub device_tree_overlays: Vec<File>,
pub display_config: Option<DisplayConfig>,
pub input_device_options: Vec<InputDeviceOption>,
pub hugepages: bool,
@@ -1000,11 +1001,18 @@
_ => command.arg("--protected-vm"),
};
- // 3 virtio-console devices + vsock = 4.
- let virtio_pci_device_count = 4 + config.disks.len();
- // crosvm virtio queue has 256 entries, so 2 MiB per device (2 pages per entry) should be
- // enough.
- let swiotlb_size_mib = 2 * virtio_pci_device_count as u32;
+ let swiotlb_size_mib = config.swiotlb_mib.map(u32::from).unwrap_or({
+ // 3 virtio-console devices + vsock = 4.
+ // TODO: Count more device types, like balloon, input, and sound.
+ let virtio_pci_device_count = 4 + config.disks.len();
+ // crosvm virtio queue has 256 entries, so 2 MiB per device (2 pages per entry) should
+ // be enough.
+ // NOTE: The above explanation isn't completely accurate, e.g., circa 2024q4, each
+ // virtio-block has 16 queues with 256 entries each and each virito-console has 2
+ // queues of 256 entries each. So, it is allocating less than 2 pages per entry, but
+ // seems to work well enough in practice.
+ 2 * virtio_pci_device_count as u32
+ });
command.arg("--swiotlb").arg(swiotlb_size_mib.to_string());
// b/346770542 for consistent "usable" memory across protected and non-protected VMs.
@@ -1149,9 +1157,10 @@
.context("failed to create control listener")?;
command.arg("--socket").arg(add_preserved_fd(&mut preserved_fds, control_sock));
- if let Some(dt_overlay) = config.device_tree_overlay {
- command.arg("--device-tree-overlay").arg(add_preserved_fd(&mut preserved_fds, dt_overlay));
- }
+ config.device_tree_overlays.into_iter().for_each(|dt_overlay| {
+ let arg = add_preserved_fd(&mut preserved_fds, dt_overlay);
+ command.arg("--device-tree-overlay").arg(arg);
+ });
if cfg!(paravirtualized_devices) {
if let Some(gpu_config) = &config.gpu_config {
diff --git a/android/virtmgr/src/payload.rs b/android/virtmgr/src/payload.rs
index 5811314..bd6bf10 100644
--- a/android/virtmgr/src/payload.rs
+++ b/android/virtmgr/src/payload.rs
@@ -48,15 +48,19 @@
/// Represents the list of APEXes
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
-struct ApexInfoList {
+pub(crate) struct ApexInfoList {
+ /// The list of APEXes
#[serde(rename = "apex-info")]
- list: Vec<ApexInfo>,
+ pub(crate) list: Vec<ApexInfo>,
}
+/// Represents info of an APEX
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
-struct ApexInfo {
+pub(crate) struct ApexInfo {
+ /// Name of APEX
#[serde(rename = "moduleName")]
- name: String,
+ pub(crate) name: String,
+
#[serde(rename = "versionCode")]
version: u64,
#[serde(rename = "modulePath")]
@@ -80,11 +84,15 @@
#[serde(rename = "preinstalledModulePath")]
preinstalled_path: PathBuf,
+
+ /// Partition of APEX
+ #[serde(default)]
+ pub(crate) partition: String,
}
impl ApexInfoList {
/// Loads ApexInfoList
- fn load() -> Result<&'static ApexInfoList> {
+ pub(crate) fn load() -> Result<&'static ApexInfoList> {
static INSTANCE: OnceCell<ApexInfoList> = OnceCell::new();
INSTANCE.get_or_try_init(|| {
let apex_info_list = File::open(APEX_INFO_LIST_PATH)
diff --git a/android/virtualizationservice/aidl/android/system/virtualizationservice/AssignedDevices.aidl b/android/virtualizationservice/aidl/android/system/virtualizationservice/AssignedDevices.aidl
new file mode 100644
index 0000000..984f596
--- /dev/null
+++ b/android/virtualizationservice/aidl/android/system/virtualizationservice/AssignedDevices.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2021 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.
+ */
+package android.system.virtualizationservice;
+
+/** Assigned devices */
+union AssignedDevices {
+ /** List of SysFS nodes of devices to be assigned for VFIO */
+ String[] devices = {};
+
+ /** Device tree overlay for non-VFIO case */
+ ParcelFileDescriptor dtbo;
+}
diff --git a/android/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl b/android/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
index d98fdcc..62a6d57 100644
--- a/android/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
+++ b/android/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
@@ -15,6 +15,7 @@
*/
package android.system.virtualizationservice;
+import android.system.virtualizationservice.AssignedDevices;
import android.system.virtualizationservice.AudioConfig;
import android.system.virtualizationservice.CpuTopology;
import android.system.virtualizationservice.DiskImage;
@@ -62,6 +63,9 @@
/** The amount of RAM to give the VM, in MiB. 0 or negative to use the default. */
int memoryMib;
+ /** The amount of swiotlb to give the VM, in MiB. 0 or negative to use the default. */
+ int swiotlbMib;
+
/** The vCPU topology that will be generated for the VM. Default to 1 vCPU. */
CpuTopology cpuTopology = CpuTopology.ONE_CPU;
@@ -88,8 +92,8 @@
*/
boolean hugePages;
- /** List of SysFS nodes of devices to be assigned */
- String[] devices;
+ /** Assigned devices */
+ AssignedDevices devices;
@nullable DisplayConfig displayConfig;
diff --git a/guest/compsvc/src/compsvc_main.rs b/guest/compsvc/src/compsvc_main.rs
index 9bc522c..a8202e1 100644
--- a/guest/compsvc/src/compsvc_main.rs
+++ b/guest/compsvc/src/compsvc_main.rs
@@ -25,7 +25,6 @@
use anyhow::Result;
use compos_common::COMPOS_VSOCK_PORT;
use log::{debug, error};
-use std::panic;
fn main() {
if let Err(e) = try_main() {
@@ -40,10 +39,6 @@
.with_tag("compsvc")
.with_max_level(log::LevelFilter::Debug),
);
- // Redirect panic messages to logcat.
- panic::set_hook(Box::new(|panic_info| {
- error!("{}", panic_info);
- }));
debug!("compsvc is starting as a rpc service.");
vm_payload::run_single_vsock_service(compsvc::new_binder()?, COMPOS_VSOCK_PORT)
diff --git a/guest/microdroid_manager/microdroid_manager.rc b/guest/microdroid_manager/microdroid_manager.rc
index da38564..9fa8a9d 100644
--- a/guest/microdroid_manager/microdroid_manager.rc
+++ b/guest/microdroid_manager/microdroid_manager.rc
@@ -2,8 +2,6 @@
disabled
# print android log to kmsg
file /dev/kmsg w
- # redirect stdout/stderr to kmsg_debug
- stdio_to_kmsg
setenv RUST_LOG info
# TODO(jooyung) remove this when microdroid_manager becomes a daemon
oneshot
diff --git a/guest/microdroid_manager/src/main.rs b/guest/microdroid_manager/src/main.rs
index 4b261c4..451c3c8 100644
--- a/guest/microdroid_manager/src/main.rs
+++ b/guest/microdroid_manager/src/main.rs
@@ -670,9 +670,20 @@
});
}
- if !is_debuggable()? {
- command.stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null());
- }
+ // Never accept input from outside
+ command.stdin(Stdio::null());
+
+ // If the VM is debuggable, let stdout/stderr go outside via /dev/kmsg to ease the debugging
+ let (stdout, stderr) = if is_debuggable()? {
+ use std::os::fd::FromRawFd;
+ let kmsg_fd = env::var("ANDROID_FILE__dev_kmsg").unwrap().parse::<i32>().unwrap();
+ // SAFETY: no one closes kmsg_fd
+ unsafe { (Stdio::from_raw_fd(kmsg_fd), Stdio::from_raw_fd(kmsg_fd)) }
+ } else {
+ (Stdio::null(), Stdio::null())
+ };
+ command.stdout(stdout);
+ command.stderr(stderr);
info!("notifying payload started");
service.notifyPayloadStarted()?;
diff --git a/guest/pvmfw/avb/tests/api_test.rs b/guest/pvmfw/avb/tests/api_test.rs
index 0ed0279..23e05d4 100644
--- a/guest/pvmfw/avb/tests/api_test.rs
+++ b/guest/pvmfw/avb/tests/api_test.rs
@@ -190,7 +190,7 @@
&load_latest_signed_kernel()?,
&load_latest_initrd_normal()?,
/* trusted_public_key= */ &[0u8; 0],
- SlotVerifyError::PublicKeyRejected.into(),
+ SlotVerifyError::PublicKeyRejected(None).into(),
)
}
@@ -200,7 +200,7 @@
&load_latest_signed_kernel()?,
&load_latest_initrd_normal()?,
/* trusted_public_key= */ &[0u8; 512],
- SlotVerifyError::PublicKeyRejected.into(),
+ SlotVerifyError::PublicKeyRejected(None).into(),
)
}
@@ -210,7 +210,7 @@
&load_latest_signed_kernel()?,
&load_latest_initrd_normal()?,
&fs::read(PUBLIC_KEY_RSA2048_PATH)?,
- SlotVerifyError::PublicKeyRejected.into(),
+ SlotVerifyError::PublicKeyRejected(None).into(),
)
}
diff --git a/guest/rialto/tests/test.rs b/guest/rialto/tests/test.rs
index 09f9cc3..c94a0e3 100644
--- a/guest/rialto/tests/test.rs
+++ b/guest/rialto/tests/test.rs
@@ -40,7 +40,6 @@
use service_vm_manager::{ServiceVm, VM_MEMORY_MB};
use std::fs;
use std::fs::File;
-use std::panic;
use std::path::PathBuf;
use std::str::FromStr;
use vmclient::VmInstance;
@@ -300,10 +299,6 @@
.with_tag("rialto")
.with_max_level(log::LevelFilter::Debug),
);
- // Redirect panic messages to logcat.
- panic::set_hook(Box::new(|panic_info| {
- log::error!("{}", panic_info);
- }));
// We need to start the thread pool for Binder to work properly, especially link_to_death.
ProcessState::start_thread_pool();
ServiceVm::start_vm(vm_instance(vm_type, vm_memory_mb)?, vm_type)
diff --git a/guest/trusty/security_vm/launcher/src/main.rs b/guest/trusty/security_vm/launcher/src/main.rs
index 62febf4..8dd7c43 100644
--- a/guest/trusty/security_vm/launcher/src/main.rs
+++ b/guest/trusty/security_vm/launcher/src/main.rs
@@ -88,7 +88,6 @@
memoryMib: args.memory_size_mib,
cpuTopology: args.cpu_topology,
platformVersion: "~1.0".to_owned(),
- balloon: true, // TODO: probably don't want ballooning.
// TODO: add instanceId
..Default::default()
});
diff --git a/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineConfig.java b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineConfig.java
index d6b38ea..6311168 100644
--- a/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -40,6 +40,7 @@
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.sysprop.HypervisorProperties;
+import android.system.virtualizationservice.AssignedDevices;
import android.system.virtualizationservice.DiskImage;
import android.system.virtualizationservice.Partition;
import android.system.virtualizationservice.SharedPath;
@@ -807,7 +808,7 @@
config.memoryMib = bytesToMebiBytes(mMemoryBytes);
config.cpuTopology = (byte) this.mCpuTopology;
config.consoleInputDevice = mConsoleInputDevice;
- config.devices = EMPTY_STRING_ARRAY;
+ config.devices = AssignedDevices.devices(EMPTY_STRING_ARRAY);
config.platformVersion = "~1.0";
config.audioConfig =
Optional.ofNullable(customImageConfig.getAudioConfig())
diff --git a/libs/libavf/Android.bp b/libs/libavf/Android.bp
index e143709..079f4ae 100644
--- a/libs/libavf/Android.bp
+++ b/libs/libavf/Android.bp
@@ -34,7 +34,6 @@
rust_ffi_static {
name: "libavf_impl",
defaults: ["libavf.default"],
- export_include_dirs: ["include"],
}
cc_library {
@@ -49,7 +48,7 @@
"libbinder_rpc_unstable",
"liblog",
],
- export_static_lib_headers: ["libavf_impl"],
+ export_include_dirs: ["include"],
apex_available: ["com.android.virt"],
version_script: "libavf.map.txt",
stubs: {
diff --git a/libs/libavf/include/android/virtualization.h b/libs/libavf/include/android/virtualization.h
index 7ab7431..6b54bf7 100644
--- a/libs/libavf/include/android/virtualization.h
+++ b/libs/libavf/include/android/virtualization.h
@@ -33,7 +33,7 @@
*
* 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},
+ * Other properties, set by {@link AVirtualMachineRawConfig_setMemoryMiB},
* {@link AVirtualMachineRawConfig_setInitRd}, {@link AVirtualMachineRawConfig_addDisk},
* {@link AVirtualMachineRawConfig_setProtectedVm}, and {@link AVirtualMachineRawConfig_setBalloon}
* are optional.
@@ -44,7 +44,7 @@
*
* \return A new virtual machine raw config object. On failure (such as out of memory), it aborts.
*/
-AVirtualMachineRawConfig* _Nonnull AVirtualMachineRawConfig_create() __INTRODUCED_IN(36);
+AVirtualMachineRawConfig* _Nonnull AVirtualMachineRawConfig_create(void) __INTRODUCED_IN(36);
/**
* Destroy a virtual machine config object.
@@ -79,8 +79,8 @@
* \return If successful, it returns 0. If `instanceIdSize` is incorrect, it returns -EINVAL.
*/
int AVirtualMachineRawConfig_setInstanceId(AVirtualMachineRawConfig* _Nonnull config,
- const int8_t* _Nonnull instanceId, size_t instanceIdSize)
- __INTRODUCED_IN(36);
+ const uint8_t* _Nonnull instanceId,
+ size_t instanceIdSize) __INTRODUCED_IN(36);
/**
* Set a kernel image of a virtual machine.
@@ -121,12 +121,39 @@
/**
* Set how much memory will be given to a virtual machine.
*
+ * When `AVirtualMachineRawConfig_setProtectedVm(..., true)` is set, the memory
+ * size provided here will be automatically augmented with the swiotlb size.
+ *
* \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
+ * \param memoryMiB the amount of RAM to give the virtual machine, in MiB. 0 or negative to use the
* default.
*/
-void AVirtualMachineRawConfig_setMemoryMib(AVirtualMachineRawConfig* _Nonnull config,
- int32_t memoryMib) __INTRODUCED_IN(36);
+void AVirtualMachineRawConfig_setMemoryMiB(AVirtualMachineRawConfig* _Nonnull config,
+ int32_t memoryMiB) __INTRODUCED_IN(36);
+
+/**
+ * Set how much swiotlb will be given to a virtual machine.
+ *
+ * Only applicable when `AVirtualMachineRawConfig_setProtectedVm(..., true)` is
+ * set.
+ *
+ * For information on swiotlb, see https://docs.kernel.org/core-api/swiotlb.html.
+ *
+ * \param config a virtual machine config object.
+ * \param memoryMiB the amount of swiotlb to give the virtual machine, in MiB.
+ * 0 or negative to use the default.
+ */
+void AVirtualMachineRawConfig_setSwiotlbMiB(AVirtualMachineRawConfig* _Nonnull config,
+ int32_t swiotlbMiB) __INTRODUCED_IN(36);
+
+/**
+ * Set vCPU count. The default is 1.
+ *
+ * \param config a virtual machine config object.
+ * \param n number of vCPUs. Must be positive.
+ */
+void AVirtualMachineRawConfig_setVCpuCount(AVirtualMachineRawConfig* _Nonnull config, int32_t n)
+ __INTRODUCED_IN(36);
/**
* Set whether the virtual machine's memory will be protected from the host, so the host can't
@@ -166,6 +193,19 @@
int AVirtualMachineRawConfig_addCustomMemoryBackingFile(AVirtualMachineRawConfig* _Nonnull config,
int fd, uint64_t rangeStart,
uint64_t rangeEnd) __INTRODUCED_IN(36);
+/**
+ * Use the specified fd as the device tree overlay blob for booting VM.
+ *
+ * Here's the format of the device tree overlay blob.
+ * link: https://source.android.com/docs/core/architecture/dto
+ *
+ * \param config a virtual machine config object.
+ * \param fd a readable, seekable, and sized (i.e. report a valid size using fstat()) file
+ * descriptor containing device tree overlay, or -1 to unset.
+ * `AVirtualMachineRawConfig_setDeviceTreeOverlay` takes ownership of `fd`.
+ */
+void AVirtualMachineRawConfig_setDeviceTreeOverlay(AVirtualMachineRawConfig* _Nonnull config,
+ int fd) __INTRODUCED_IN(36);
/**
* Represents a handle on a virtualization service, responsible for managing virtual machines.
diff --git a/libs/libavf/libavf.map.txt b/libs/libavf/libavf.map.txt
index efc368a..589c5d3 100644
--- a/libs/libavf/libavf.map.txt
+++ b/libs/libavf/libavf.map.txt
@@ -7,10 +7,13 @@
AVirtualMachineRawConfig_setKernel; # apex llndk
AVirtualMachineRawConfig_setInitRd; # apex llndk
AVirtualMachineRawConfig_addDisk; # apex llndk
- AVirtualMachineRawConfig_setMemoryMib; # apex llndk
+ AVirtualMachineRawConfig_setMemoryMiB; # apex llndk
+ AVirtualMachineRawConfig_setSwiotlbMiB; # apex llndk
+ AVirtualMachineRawConfig_setVCpuCount; # apex llndk
AVirtualMachineRawConfig_setProtectedVm; # apex llndk
AVirtualMachineRawConfig_setHypervisorSpecificAuthMethod; # apex llndk
AVirtualMachineRawConfig_addCustomMemoryBackingFile; # apex llndk
+ AVirtualMachineRawConfig_setDeviceTreeOverlay; # apex llndk
AVirtualizationService_create; # apex llndk
AVirtualizationService_destroy; # apex llndk
AVirtualMachine_createRaw; # apex llndk
diff --git a/libs/libavf/src/lib.rs b/libs/libavf/src/lib.rs
index 56cdfb7..4f23da4 100644
--- a/libs/libavf/src/lib.rs
+++ b/libs/libavf/src/lib.rs
@@ -23,14 +23,15 @@
use android_system_virtualizationservice::{
aidl::android::system::virtualizationservice::{
- DiskImage::DiskImage, IVirtualizationService::IVirtualizationService,
- VirtualMachineConfig::VirtualMachineConfig,
+ AssignedDevices::AssignedDevices, CpuTopology::CpuTopology, DiskImage::DiskImage,
+ IVirtualizationService::IVirtualizationService, VirtualMachineConfig::VirtualMachineConfig,
VirtualMachineRawConfig::VirtualMachineRawConfig,
},
binder::{ParcelFileDescriptor, Strong},
};
use avf_bindgen::AVirtualMachineStopReason;
use libc::timespec;
+use log::error;
use vmclient::{DeathReason, VirtualizationService, VmInstance};
/// Create a new virtual machine config object with no properties.
@@ -180,7 +181,7 @@
/// # Safety
/// `config` must be a pointer returned by `AVirtualMachineRawConfig_create`.
#[no_mangle]
-pub unsafe extern "C" fn AVirtualMachineRawConfig_setMemoryMib(
+pub unsafe extern "C" fn AVirtualMachineRawConfig_setMemoryMiB(
config: *mut VirtualMachineRawConfig,
memory_mib: i32,
) {
@@ -190,6 +191,37 @@
config.memoryMib = memory_mib;
}
+/// Set how much swiotlb 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_setSwiotlbMiB(
+ config: *mut VirtualMachineRawConfig,
+ swiotlb_mib: i32,
+) {
+ // 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.swiotlbMib = swiotlb_mib;
+}
+
+/// Set vCPU count.
+///
+/// # Safety
+/// `config` must be a pointer returned by `AVirtualMachineRawConfig_create`.
+#[no_mangle]
+pub unsafe extern "C" fn AVirtualMachineRawConfig_setVCpuCount(
+ config: *mut VirtualMachineRawConfig,
+ n: i32,
+) {
+ // 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.cpuTopology = CpuTopology::CUSTOM;
+ config.customVcpuCount = n;
+}
+
/// Set whether a virtual machine is protected or not.
///
/// # Safety
@@ -231,6 +263,32 @@
-libc::ENOTSUP
}
+/// Add device tree overlay blob
+///
+/// # Safety
+/// `config` must be a pointer returned by `AVirtualMachineRawConfig_create`. `fd` must be a valid
+/// file descriptor or -1. `AVirtualMachineRawConfig_setDeviceTreeOverlay` takes ownership of `fd`
+/// and `fd` will be closed upon `AVirtualMachineRawConfig_delete`.
+#[no_mangle]
+pub unsafe extern "C" fn AVirtualMachineRawConfig_setDeviceTreeOverlay(
+ config: *mut VirtualMachineRawConfig,
+ fd: 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 };
+
+ match get_file_from_fd(fd) {
+ Some(file) => {
+ let fd = ParcelFileDescriptor::new(file);
+ config.devices = AssignedDevices::Dtbo(Some(fd));
+ }
+ _ => {
+ config.devices = Default::default();
+ }
+ };
+}
+
/// Spawn a new instance of `virtmgr`, a child process that will host the `VirtualizationService`
/// AIDL service, and connect to the child process.
///
@@ -319,7 +377,10 @@
}
0
}
- Err(_) => -libc::EIO,
+ Err(e) => {
+ error!("AVirtualMachine_createRaw failed: {e:?}");
+ -libc::EIO
+ }
}
}
@@ -334,7 +395,10 @@
let vm = unsafe { &*vm };
match vm.start() {
Ok(_) => 0,
- Err(_) => -libc::EIO,
+ Err(e) => {
+ error!("AVirtualMachine_start failed: {e:?}");
+ -libc::EIO
+ }
}
}
@@ -349,7 +413,10 @@
let vm = unsafe { &*vm };
match vm.stop() {
Ok(_) => 0,
- Err(_) => -libc::EIO,
+ Err(e) => {
+ error!("AVirtualMachine_stop failed: {e:?}");
+ -libc::EIO
+ }
}
}
@@ -364,7 +431,10 @@
let vm = unsafe { &*vm };
match vm.connect_vsock(port) {
Ok(pfd) => pfd.into_raw_fd(),
- Err(_) => -libc::EIO,
+ Err(e) => {
+ error!("AVirtualMachine_connectVsock failed: {e:?}");
+ -libc::EIO
+ }
}
}
diff --git a/libs/libfdt/src/result.rs b/libs/libfdt/src/result.rs
index 52291ca..d671647 100644
--- a/libs/libfdt/src/result.rs
+++ b/libs/libfdt/src/result.rs
@@ -14,6 +14,7 @@
//! Rust types related to the libfdt C integer results.
+use core::error;
use core::ffi::{c_int, c_uint};
use core::fmt;
use core::result;
@@ -91,6 +92,8 @@
}
}
+impl error::Error for FdtError {}
+
/// Result type with FdtError enum.
pub type Result<T> = result::Result<T, FdtError>;
diff --git a/libs/libservice_vm_manager/src/lib.rs b/libs/libservice_vm_manager/src/lib.rs
index 5bb97d7..0f322bb 100644
--- a/libs/libservice_vm_manager/src/lib.rs
+++ b/libs/libservice_vm_manager/src/lib.rs
@@ -238,8 +238,7 @@
memoryMib: VM_MEMORY_MB,
cpuTopology: CpuTopology::ONE_CPU,
platformVersion: "~1.0".to_string(),
- gdbPort: 0, // No gdb
- balloon: true, // TODO: probably don't want ballooning.
+ gdbPort: 0, // No gdb
..Default::default()
});
let console_out = Some(android_log_fd()?);
diff --git a/libs/vmconfig/src/lib.rs b/libs/vmconfig/src/lib.rs
index e520f0e..8357f99 100644
--- a/libs/vmconfig/src/lib.rs
+++ b/libs/vmconfig/src/lib.rs
@@ -15,6 +15,7 @@
//! Struct for VM configuration with JSON (de)serialization and AIDL parcelables
use android_system_virtualizationservice::{
+ aidl::android::system::virtualizationservice::AssignedDevices::AssignedDevices,
aidl::android::system::virtualizationservice::CpuTopology::CpuTopology,
aidl::android::system::virtualizationservice::DiskImage::DiskImage as AidlDiskImage,
aidl::android::system::virtualizationservice::Partition::Partition as AidlPartition,
@@ -124,13 +125,16 @@
memoryMib: memory_mib,
cpuTopology: cpu_topology,
platformVersion: self.platform_version.to_string(),
- devices: self
- .devices
- .iter()
- .map(|x| {
- x.to_str().map(String::from).ok_or(anyhow!("Failed to convert {x:?} to String"))
- })
- .collect::<Result<_>>()?,
+ devices: AssignedDevices::Devices(
+ self.devices
+ .iter()
+ .map(|x| {
+ x.to_str()
+ .map(String::from)
+ .ok_or(anyhow!("Failed to convert {x:?} to String"))
+ })
+ .collect::<Result<_>>()?,
+ ),
consoleInputDevice: self.console_input_device.clone(),
usbConfig: usb_config,
balloon: true,
diff --git a/tests/Terminal/AndroidManifest.xml b/tests/Terminal/AndroidManifest.xml
index 0abcb5c..08bcb39 100644
--- a/tests/Terminal/AndroidManifest.xml
+++ b/tests/Terminal/AndroidManifest.xml
@@ -21,6 +21,6 @@
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.google.android.virtualization.terminal"
+ android:targetPackage="com.android.virtualization.terminal"
android:label="Test for Linux Terminal"/>
</manifest>
diff --git a/tests/Terminal/src/com/android/virtualization/terminal/TerminalAppTest.java b/tests/Terminal/src/com/android/virtualization/terminal/TerminalAppTest.java
index 4a18ee8..b0afb54 100644
--- a/tests/Terminal/src/com/android/virtualization/terminal/TerminalAppTest.java
+++ b/tests/Terminal/src/com/android/virtualization/terminal/TerminalAppTest.java
@@ -22,10 +22,12 @@
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
+import android.os.SystemProperties;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.microdroid.test.common.DeviceProperties;
import com.android.microdroid.test.common.MetricsProcessor;
import org.junit.After;
@@ -42,12 +44,14 @@
public class TerminalAppTest {
private Instrumentation mInstr;
private Context mTargetContext;
+ private DeviceProperties mProperties;
private final MetricsProcessor mMetricsProc = new MetricsProcessor("avf_perf/terminal/");
@Before
public void setup() {
mInstr = InstrumentationRegistry.getInstrumentation();
mTargetContext = mInstr.getTargetContext();
+ mProperties = DeviceProperties.create(SystemProperties::get);
installVmImage();
}
@@ -66,7 +70,8 @@
@Test
public void boot() throws Exception {
- final long BOOT_TIMEOUT_MILLIS = 30_000; // 30 sec
+ final boolean isNestedVirt = mProperties.isCuttlefish() || mProperties.isGoldfish();
+ final long BOOT_TIMEOUT_MILLIS = isNestedVirt ? 180_000 : 30_000; // 30 sec (or 3 min)
Intent intent = new Intent(mTargetContext, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
diff --git a/tests/backcompat_test/Android.bp b/tests/backcompat_test/Android.bp
new file mode 100644
index 0000000..aa1e089
--- /dev/null
+++ b/tests/backcompat_test/Android.bp
@@ -0,0 +1,36 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_test {
+ name: "avf_backcompat_tests",
+ crate_name: "backcompat_test",
+ srcs: ["src/main.rs"],
+ prefer_rlib: true,
+ edition: "2021",
+ rustlibs: [
+ "android.system.virtualizationservice-rust",
+ "libandroid_logger",
+ "libanyhow",
+ "liblibc",
+ "libnix",
+ "libvmclient",
+ "liblog_rust",
+ ],
+ test_config: "AndroidTest.xml",
+ data: [
+ "goldens/dt_dump_*",
+ ":vmbase_example_kernel_bin",
+ ],
+ data_bins: [
+ "dtc_static",
+ "dtcompare",
+ ],
+ test_suites: ["general-tests"],
+ enabled: false,
+ target: {
+ android_arm64: {
+ enabled: true,
+ },
+ },
+}
diff --git a/tests/backcompat_test/AndroidTest.xml b/tests/backcompat_test/AndroidTest.xml
new file mode 100644
index 0000000..dd8b43d
--- /dev/null
+++ b/tests/backcompat_test/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 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.
+-->
+<configuration description="Config to run backcompat_tests.">
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <option name="push-file" key="avf_backcompat_tests" value="/data/local/tmp/avf_backcompat_tests" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.rust.RustBinaryTest" >
+ <option name="module-name" value="avf_backcompat_tests" />
+ <!-- Run tests serially because the VMs may overwrite the generated Device Tree. -->
+ <option name="native-test-flag" value="--test-threads=1" />
+ </test>
+</configuration>
diff --git a/tests/backcompat_test/goldens/dt_dump_golden.dts b/tests/backcompat_test/goldens/dt_dump_golden.dts
new file mode 100644
index 0000000..a583514
--- /dev/null
+++ b/tests/backcompat_test/goldens/dt_dump_golden.dts
@@ -0,0 +1,143 @@
+/dts-v1/;
+
+/ {
+ #address-cells = <0x02>;
+ #size-cells = <0x02>;
+ compatible = "linux,dummy-virt";
+ interrupt-parent = <0x01>;
+ name = "reference";
+
+ U6_16550A@2e8 {
+ clock-frequency = <0x1c2000>;
+ compatible = "ns16550a";
+ interrupts = <0x00 0x02 0x01>;
+ reg = <0x00 0x2e8 0x00 0x08>;
+ };
+
+ U6_16550A@2f8 {
+ clock-frequency = <0x1c2000>;
+ compatible = "ns16550a";
+ interrupts = <0x00 0x02 0x01>;
+ reg = <0x00 0x2f8 0x00 0x08>;
+ };
+
+ U6_16550A@3e8 {
+ clock-frequency = <0x1c2000>;
+ compatible = "ns16550a";
+ interrupts = <0x00 0x00 0x01>;
+ reg = <0x00 0x3e8 0x00 0x08>;
+ };
+
+ U6_16550A@3f8 {
+ clock-frequency = <0x1c2000>;
+ compatible = "ns16550a";
+ interrupts = <0x00 0x00 0x01>;
+ reg = <0x00 0x3f8 0x00 0x08>;
+ };
+
+ __symbols__ {
+ intc = "/intc";
+ };
+
+ avf {
+ secretkeeper_public_key = [a4 01 01 03 27 20 06 21 58 20 de c2 79 41 b5 2a d8 1e eb dd 8a c5 a0 2f e4 56 12 42 5e b5 a4 c6 6a 8c 32 81 65 75 1c 6e b2 87];
+
+ untrusted {
+ defer-rollback-protection;
+ instance-id = <0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00>;
+ };
+ };
+
+ chosen {
+ bootargs = "panic=-1";
+ kaslr-seed = <0xab3b03c7 0xbb04cfd9>;
+ linux,pci-probe-only = <0x01>;
+ rng-seed = <0xa738baa8 0xf125e39b 0x5016f377 0xe2439805 0x94624c7e 0xac404bf6 0x68ece261 0xd45cca77 0x72328c0d 0xfdb9674f 0x74c1eb50 0x5665af83 0x1e8ccb52 0x120ed001 0xdc057599 0xbb3d33ea 0x6f9eb8e7 0x44f0517e 0x65d1cd16 0xeb4506a7 0x63fe5a00 0x8e330a52 0x2ab37c64 0x9aec3871 0x80f24353 0xfcdea704 0xd0e4fa1b 0x86412d49 0xed12a31d 0x1fbe26f3 0x97e442c5 0x25b31828 0xbe8626eb 0xea8098b8 0x6bf93ad9 0x3676d94a 0xcdbf695a 0x8b68008c 0xf598963b 0x483d0817 0xcea64b84 0xbbe0d7af 0xb09d31d7 0xfa461596 0xc47f9be8 0xd992c480 0x98372ef6 0xe1e70464 0xdc2752e4 0xe40a042c 0x5bb3a936 0x8af0aaff 0xd52f6723 0x8ac81a1b 0x15ed83d 0xee00b9eb 0x107f8ce 0xda99d512 0xed26543c 0x959f76f 0x1b85d5dc 0xa0b36c99 0xcdc8351 0xa4196327>;
+ stdout-path = "/U6_16550A@3f8";
+ };
+
+ config {
+ kernel-address = <0x80000000>;
+ kernel-size = <0x2c880>;
+ };
+
+ cpufreq {
+ compatible = "virtual,kvm-cpufreq";
+ };
+
+ cpus {
+ #address-cells = <0x01>;
+ #size-cells = <0x00>;
+
+ cpu@0 {
+ compatible = "arm,armv8";
+ device_type = "cpu";
+ phandle = <0x100>;
+ reg = <0x00>;
+ };
+ };
+
+ intc {
+ #address-cells = <0x02>;
+ #interrupt-cells = <0x03>;
+ #size-cells = <0x02>;
+ compatible = "arm,gic-v3";
+ interrupt-controller;
+ phandle = <0x01>;
+ reg = <0x00 0x3fff0000 0x00 0x10000 0x00 0x3ffd0000 0x00 0x20000>;
+ };
+
+ memory {
+ device_type = "memory";
+ reg = <0x00 0x80000000 0x00 0x12c00000>;
+ };
+
+ pci {
+ #address-cells = <0x03>;
+ #interrupt-cells = <0x01>;
+ #size-cells = <0x02>;
+ bus-range = <0x00 0x00>;
+ compatible = "pci-host-cam-generic";
+ device_type = "pci";
+ dma-coherent;
+ interrupt-map = <0x800 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x04 0x04 0x1000 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x05 0x04 0x1800 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x06 0x04 0x2000 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x07 0x04 0x2800 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x08 0x04 0x3000 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x09 0x04 0x3800 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x0a 0x04 0x4000 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x0b 0x04>;
+ interrupt-map-mask = <0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07>;
+ ranges = <0x3000000 0x00 0x70000000 0x00 0x70000000 0x00 0x2000000 0x43000000 0x00 0x93400000 0x00 0x93400000 0xff 0x6cc00000>;
+ reg = <0x00 0x72000000 0x00 0x1000000>;
+ };
+
+ pclk@3M {
+ #clock-cells = <0x00>;
+ clock-frequency = <0x2fefd8>;
+ compatible = "fixed-clock";
+ phandle = <0x18>;
+ };
+
+ psci {
+ compatible = "arm,psci-1.0\0arm,psci-0.2";
+ method = "hvc";
+ };
+
+ rtc@2000 {
+ arm,primecell-periphid = <0x41030>;
+ clock-names = "apb_pclk";
+ clocks = <0x18>;
+ compatible = "arm,primecell";
+ interrupts = <0x00 0x01 0x04>;
+ reg = <0x00 0x2000 0x00 0x1000>;
+ };
+
+ timer {
+ always-on;
+ compatible = "arm,armv8-timer";
+ interrupts = <0x01 0x0d 0x108 0x01 0x0e 0x108 0x01 0x0b 0x108 0x01 0x0a 0x108>;
+ };
+
+ vmwdt@3000 {
+ clock-frequency = <0x02>;
+ compatible = "qemu,vcpu-stall-detector";
+ interrupts = <0x01 0x0f 0x101>;
+ reg = <0x00 0x3000 0x00 0x1000>;
+ timeout-sec = <0x0a>;
+ };
+};
diff --git a/tests/backcompat_test/goldens/dt_dump_protected_golden.dts b/tests/backcompat_test/goldens/dt_dump_protected_golden.dts
new file mode 100644
index 0000000..656958d
--- /dev/null
+++ b/tests/backcompat_test/goldens/dt_dump_protected_golden.dts
@@ -0,0 +1,157 @@
+/dts-v1/;
+
+/ {
+ #address-cells = <0x02>;
+ #size-cells = <0x02>;
+ compatible = "linux,dummy-virt";
+ interrupt-parent = <0x01>;
+ name = "reference";
+
+ U6_16550A@2e8 {
+ clock-frequency = <0x1c2000>;
+ compatible = "ns16550a";
+ interrupts = <0x00 0x02 0x01>;
+ reg = <0x00 0x2e8 0x00 0x08>;
+ };
+
+ U6_16550A@2f8 {
+ clock-frequency = <0x1c2000>;
+ compatible = "ns16550a";
+ interrupts = <0x00 0x02 0x01>;
+ reg = <0x00 0x2f8 0x00 0x08>;
+ };
+
+ U6_16550A@3e8 {
+ clock-frequency = <0x1c2000>;
+ compatible = "ns16550a";
+ interrupts = <0x00 0x00 0x01>;
+ reg = <0x00 0x3e8 0x00 0x08>;
+ };
+
+ U6_16550A@3f8 {
+ clock-frequency = <0x1c2000>;
+ compatible = "ns16550a";
+ interrupts = <0x00 0x00 0x01>;
+ reg = <0x00 0x3f8 0x00 0x08>;
+ };
+
+ __symbols__ {
+ intc = "/intc";
+ };
+
+ avf {
+ secretkeeper_public_key = [a4 01 01 03 27 20 06 21 58 20 de c2 79 41 b5 2a d8 1e eb dd 8a c5 a0 2f e4 56 12 42 5e b5 a4 c6 6a 8c 32 81 65 75 1c 6e b2 87];
+
+ untrusted {
+ defer-rollback-protection;
+ instance-id = <0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00>;
+ };
+ };
+
+ chosen {
+ bootargs = "panic=-1";
+ kaslr-seed = <0xbbf0472d 0xbef495c>;
+ linux,pci-probe-only = <0x01>;
+ rng-seed = <0xb6e3fa0c 0xa0546147 0xeca61840 0x4f07da9d 0xacb41a21 0x8aa7ff1f 0xd32dd43 0x93fb4ad3 0xab5f9bf1 0x66d5913d 0x2b389a9f 0xc2c268d2 0xfd1d9a22 0xa8dba850 0xd443014d 0x10b3dfcb 0x77597882 0x66008b71 0x3d29575c 0xd917ee2f 0xb6e98504 0x6a5c9fde 0xa02daf16 0x3a60b1d5 0xa4416447 0x9e8a996d 0x3b4bf5e9 0xdf7639cb 0x4b608f7e 0x3434d9b4 0xb84cd15 0x86d724ae 0x404a1353 0x8afc6a43 0x916c4b8d 0xebe878c0 0xd67a99a4 0x94fb22ca 0xef53a3bf 0xaf5fc4b 0xd6d405d8 0xb6ed6cb5 0xc4d13a21 0x6aff3f79 0x93b56581 0x622e8da3 0x59047c4b 0x9a7562ee 0x93762d9a 0xeab995f7 0x33e1cdea 0x5d071401 0x2d57f0d1 0x73367772 0x532a74b6 0x3fb875fe 0x7340d4dd 0x492fa79f 0x7749f27 0xe8eefd10 0xeb00c401 0xd51bd6b3 0x904b5ac8 0x4316f75b>;
+ stdout-path = "/U6_16550A@3f8";
+ };
+
+ config {
+ kernel-address = <0x80000000>;
+ kernel-size = <0x2c880>;
+ };
+
+ cpufreq {
+ compatible = "virtual,kvm-cpufreq";
+ };
+
+ cpus {
+ #address-cells = <0x01>;
+ #size-cells = <0x00>;
+
+ cpu@0 {
+ compatible = "arm,armv8";
+ device_type = "cpu";
+ phandle = <0x100>;
+ reg = <0x00>;
+ };
+ };
+
+ intc {
+ #address-cells = <0x02>;
+ #interrupt-cells = <0x03>;
+ #size-cells = <0x02>;
+ compatible = "arm,gic-v3";
+ interrupt-controller;
+ phandle = <0x01>;
+ reg = <0x00 0x3fff0000 0x00 0x10000 0x00 0x3ffd0000 0x00 0x20000>;
+ };
+
+ memory {
+ device_type = "memory";
+ reg = <0x00 0x80000000 0x00 0x13800000>;
+ };
+
+ pci {
+ #address-cells = <0x03>;
+ #interrupt-cells = <0x01>;
+ #size-cells = <0x02>;
+ bus-range = <0x00 0x00>;
+ compatible = "pci-host-cam-generic";
+ device_type = "pci";
+ dma-coherent;
+ interrupt-map = <0x800 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x04 0x04 0x1000 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x05 0x04 0x1800 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x06 0x04 0x2000 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x07 0x04 0x2800 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x08 0x04 0x3000 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x09 0x04 0x3800 0x00 0x00 0x01 0x01 0x00 0x00 0x00 0x0a 0x04>;
+ interrupt-map-mask = <0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07 0xf800 0x00 0x00 0x07>;
+ memory-region = <0x02>;
+ ranges = <0x3000000 0x00 0x70000000 0x00 0x70000000 0x00 0x2000000 0x43000000 0x00 0x94000000 0x00 0x94000000 0xff 0x6c000000>;
+ reg = <0x00 0x72000000 0x00 0x1000000>;
+ };
+
+ pclk@3M {
+ #clock-cells = <0x00>;
+ clock-frequency = <0x2fefd8>;
+ compatible = "fixed-clock";
+ phandle = <0x18>;
+ };
+
+ psci {
+ compatible = "arm,psci-1.0\0arm,psci-0.2";
+ method = "hvc";
+ };
+
+ reserved-memory {
+ #address-cells = <0x02>;
+ #size-cells = <0x02>;
+ ranges;
+
+ restricted_dma_reserved {
+ alignment = <0x00 0x1000>;
+ compatible = "restricted-dma-pool";
+ phandle = <0x02>;
+ size = <0x00 0xc00000>;
+ };
+ };
+
+ rtc@2000 {
+ arm,primecell-periphid = <0x41030>;
+ clock-names = "apb_pclk";
+ clocks = <0x18>;
+ compatible = "arm,primecell";
+ interrupts = <0x00 0x01 0x04>;
+ reg = <0x00 0x2000 0x00 0x1000>;
+ };
+
+ timer {
+ always-on;
+ compatible = "arm,armv8-timer";
+ interrupts = <0x01 0x0d 0x108 0x01 0x0e 0x108 0x01 0x0b 0x108 0x01 0x0a 0x108>;
+ };
+
+ vmwdt@3000 {
+ clock-frequency = <0x02>;
+ compatible = "qemu,vcpu-stall-detector";
+ interrupts = <0x01 0x0f 0x101>;
+ reg = <0x00 0x3000 0x00 0x1000>;
+ timeout-sec = <0x0a>;
+ };
+};
diff --git a/tests/backcompat_test/src/main.rs b/tests/backcompat_test/src/main.rs
new file mode 100644
index 0000000..4113881
--- /dev/null
+++ b/tests/backcompat_test/src/main.rs
@@ -0,0 +1,204 @@
+// 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.
+
+//! Integration test for VMs on device.
+
+use android_system_virtualizationservice::{
+ aidl::android::system::virtualizationservice::{
+ CpuTopology::CpuTopology, DiskImage::DiskImage, VirtualMachineConfig::VirtualMachineConfig,
+ VirtualMachineRawConfig::VirtualMachineRawConfig,
+ },
+ binder::{ParcelFileDescriptor, ProcessState},
+};
+use anyhow::anyhow;
+use anyhow::Context;
+use anyhow::Error;
+use log::error;
+use log::info;
+use std::fs::read_to_string;
+use std::fs::File;
+use std::io::Write;
+use std::process::Command;
+use vmclient::VmInstance;
+
+const VMBASE_EXAMPLE_KERNEL_PATH: &str = "vmbase_example_kernel.bin";
+const TEST_DISK_IMAGE_PATH: &str = "test_disk.img";
+const EMPTY_DISK_IMAGE_PATH: &str = "empty_disk.img";
+const GOLDEN_DEVICE_TREE: &str = "./goldens/dt_dump_golden.dts";
+const GOLDEN_DEVICE_TREE_PROTECTED: &str = "./goldens/dt_dump_protected_golden.dts";
+
+/// Runs an unprotected VM and validates it against a golden device tree.
+#[test]
+fn test_device_tree_compat() -> Result<(), Error> {
+ run_test(false, GOLDEN_DEVICE_TREE)
+}
+
+/// Runs a protected VM and validates it against a golden device tree.
+#[test]
+fn test_device_tree_protected_compat() -> Result<(), Error> {
+ run_test(true, GOLDEN_DEVICE_TREE_PROTECTED)
+}
+
+fn run_test(protected: bool, golden_dt: &str) -> Result<(), Error> {
+ let kernel = Some(open_payload(VMBASE_EXAMPLE_KERNEL_PATH)?);
+ android_logger::init_once(
+ android_logger::Config::default()
+ .with_tag("backcompat")
+ .with_max_level(log::LevelFilter::Debug),
+ );
+
+ // We need to start the thread pool for Binder to work properly, especially link_to_death.
+ ProcessState::start_thread_pool();
+
+ let virtmgr =
+ vmclient::VirtualizationService::new().context("Failed to spawn VirtualizationService")?;
+ let service = virtmgr.connect().context("Failed to connect to VirtualizationService")?;
+
+ // Make file for test disk image.
+ let mut test_image = File::options()
+ .create(true)
+ .read(true)
+ .write(true)
+ .truncate(true)
+ .open(TEST_DISK_IMAGE_PATH)
+ .with_context(|| format!("Failed to open test disk image {}", TEST_DISK_IMAGE_PATH))?;
+ // Write 4 sectors worth of 4-byte numbers counting up.
+ for i in 0u32..512 {
+ test_image.write_all(&i.to_le_bytes())?;
+ }
+ let test_image = ParcelFileDescriptor::new(test_image);
+ let disk_image = DiskImage { image: Some(test_image), writable: false, partitions: vec![] };
+
+ // Make file for empty test disk image.
+ let empty_image = File::options()
+ .create(true)
+ .read(true)
+ .write(true)
+ .truncate(true)
+ .open(EMPTY_DISK_IMAGE_PATH)
+ .with_context(|| format!("Failed to open empty disk image {}", EMPTY_DISK_IMAGE_PATH))?;
+ let empty_image = ParcelFileDescriptor::new(empty_image);
+ let empty_disk_image =
+ DiskImage { image: Some(empty_image), writable: false, partitions: vec![] };
+
+ let config = VirtualMachineConfig::RawConfig(VirtualMachineRawConfig {
+ name: String::from("VmBaseTest"),
+ kernel,
+ disks: vec![disk_image, empty_disk_image],
+ protectedVm: protected,
+ memoryMib: 300,
+ cpuTopology: CpuTopology::ONE_CPU,
+ platformVersion: "~1.0".to_string(),
+ ..Default::default()
+ });
+
+ let dump_dt = File::options()
+ .create(true)
+ .read(true)
+ .write(true)
+ .truncate(true)
+ .open("dump_dt.dtb")
+ .with_context(|| "Failed to open device tree dump file dump_dt.dtb")?;
+ let vm = VmInstance::create(
+ service.as_ref(),
+ &config,
+ None,
+ /* consoleIn */ None,
+ None,
+ Some(dump_dt),
+ None,
+ )
+ .context("Failed to create VM")?;
+ vm.start().context("Failed to start VM")?;
+ info!("Started example VM.");
+
+ // Wait for VM to finish
+ let _ = vm.wait_for_death();
+
+ if !Command::new("./dtc_static")
+ .arg("-I")
+ .arg("dts")
+ .arg("-O")
+ .arg("dtb")
+ .arg("-qqq")
+ .arg("-f")
+ .arg("-s")
+ .arg("-o")
+ .arg("dump_dt_golden.dtb")
+ .arg(golden_dt)
+ .output()?
+ .status
+ .success()
+ {
+ return Err(anyhow!("failed to execute dtc"));
+ }
+ let dtcompare_res = Command::new("./dtcompare")
+ .arg("--dt1")
+ .arg("dump_dt_golden.dtb")
+ .arg("--dt2")
+ .arg("dump_dt.dtb")
+ .arg("--ignore-path-value")
+ .arg("/chosen/kaslr-seed")
+ .arg("--ignore-path-value")
+ .arg("/chosen/rng-seed")
+ .arg("--ignore-path-value")
+ .arg("/avf/untrusted/instance-id")
+ .arg("--ignore-path-value")
+ .arg("/chosen/linuxinitrd-start")
+ .arg("--ignore-path-value")
+ .arg("/chosen/linuxinitrd-end")
+ .arg("--ignore-path-value")
+ .arg("/avf/secretkeeper_public_key")
+ .arg("--ignore-path")
+ .arg("/avf/name")
+ .output()
+ .context("failed to execute dtcompare")?;
+ if !dtcompare_res.status.success() {
+ if !Command::new("./dtc_static")
+ .arg("-I")
+ .arg("dtb")
+ .arg("-O")
+ .arg("dts")
+ .arg("-qqq")
+ .arg("-f")
+ .arg("-s")
+ .arg("-o")
+ .arg("dump_dt_failed.dts")
+ .arg("dump_dt.dtb")
+ .output()?
+ .status
+ .success()
+ {
+ return Err(anyhow!("failed to execute dtc"));
+ }
+ let dt2 = read_to_string("dump_dt_failed.dts")?;
+ error!(
+ "Device tree 2 does not match golden DT.\n
+ Device Tree 2: {}",
+ dt2
+ );
+ return Err(anyhow!(
+ "stdout: {:?}\n stderr: {:?}",
+ dtcompare_res.stdout,
+ dtcompare_res.stderr
+ ));
+ }
+
+ Ok(())
+}
+
+fn open_payload(path: &str) -> Result<ParcelFileDescriptor, Error> {
+ let file = File::open(path).with_context(|| format!("Failed to open VM image {path}"))?;
+ Ok(ParcelFileDescriptor::new(file))
+}
diff --git a/tests/dtcompare/Android.bp b/tests/dtcompare/Android.bp
new file mode 100644
index 0000000..988f420
--- /dev/null
+++ b/tests/dtcompare/Android.bp
@@ -0,0 +1,18 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_binary {
+ name: "dtcompare",
+ crate_root: "src/main.rs",
+ srcs: ["src/main.rs"],
+ edition: "2021",
+ rustlibs: [
+ "libanyhow",
+ "libclap",
+ "libhex_nostd",
+ "liblibfdt_nostd",
+ "liblog_rust",
+ ],
+ visibility: ["//packages/modules/Virtualization:__subpackages__"],
+}
diff --git a/tests/dtcompare/src/main.rs b/tests/dtcompare/src/main.rs
new file mode 100644
index 0000000..db3aac2
--- /dev/null
+++ b/tests/dtcompare/src/main.rs
@@ -0,0 +1,192 @@
+// 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.
+
+//! Compare device tree contents.
+//! Allows skipping over fields provided.
+
+use anyhow::anyhow;
+use anyhow::Context;
+use anyhow::Result;
+use clap::Parser;
+use hex::encode;
+use libfdt::Fdt;
+use libfdt::FdtNode;
+
+use std::collections::BTreeMap;
+use std::collections::BTreeSet;
+use std::fs::read;
+use std::path::PathBuf;
+
+#[derive(Debug, Parser)]
+/// Device Tree Compare arguments.
+struct DtCompareArgs {
+ /// first device tree
+ #[arg(long)]
+ dt1: PathBuf,
+ /// second device tree
+ #[arg(long)]
+ dt2: PathBuf,
+ /// list of properties that should exist but are expected to hold different values in the
+ /// trees.
+ #[arg(short = 'I', long)]
+ ignore_path_value: Vec<String>,
+ /// list of paths that will be ignored, whether added, removed, or changed.
+ /// Paths can be nodes, subnodes, or even properties:
+ /// Ex: /avf/unstrusted // this is a path to a subnode. All properties and subnodes underneath
+ /// // it will also be ignored.
+ /// /avf/name // This is a path for a property. Only this property will be ignored.
+ #[arg(short = 'S', long)]
+ ignore_path: Vec<String>,
+}
+
+fn main() -> Result<()> {
+ let args = DtCompareArgs::parse();
+ let dt1: Vec<u8> = read(args.dt1)?;
+ let dt2: Vec<u8> = read(args.dt2)?;
+ let ignore_value_set = BTreeSet::from_iter(args.ignore_path_value);
+ let ignore_set = BTreeSet::from_iter(args.ignore_path);
+ compare_device_trees(dt1.as_slice(), dt2.as_slice(), ignore_value_set, ignore_set)
+}
+
+// Compare device trees by doing a pre-order traversal of the trees.
+fn compare_device_trees(
+ dt1: &[u8],
+ dt2: &[u8],
+ ignore_value_set: BTreeSet<String>,
+ ignore_set: BTreeSet<String>,
+) -> Result<()> {
+ let fdt1 = Fdt::from_slice(dt1).context("invalid device tree: Dt1")?;
+ let fdt2 = Fdt::from_slice(dt2).context("invalid device tree: Dt2")?;
+ let mut errors = Vec::new();
+ compare_subnodes(
+ &fdt1.root(),
+ &fdt2.root(),
+ &ignore_value_set,
+ &ignore_set,
+ /* path */ &mut ["".to_string()],
+ &mut errors,
+ )?;
+ if !errors.is_empty() {
+ return Err(anyhow!(
+ "Following properties had different values: [\n{}\n]\ndetected {} diffs",
+ errors.join("\n"),
+ errors.len()
+ ));
+ }
+ Ok(())
+}
+
+fn compare_props(
+ root1: &FdtNode,
+ root2: &FdtNode,
+ ignore_value_set: &BTreeSet<String>,
+ ignore_set: &BTreeSet<String>,
+ path: &mut [String],
+ errors: &mut Vec<String>,
+) -> Result<()> {
+ let mut prop_map: BTreeMap<String, &[u8]> = BTreeMap::new();
+ for prop in root1.properties().context("Error getting properties")? {
+ let prop_path =
+ path.join("/") + "/" + prop.name().context("Error getting property name")?.to_str()?;
+ // Do not add to prop map if skipping
+ if ignore_set.contains(&prop_path) {
+ continue;
+ }
+ let value = prop.value().context("Error getting value")?;
+ if prop_map.insert(prop_path.clone(), value).is_some() {
+ return Err(anyhow!("Duplicate property detected in subnode: {}", prop_path));
+ }
+ }
+ for prop in root2.properties().context("Error getting properties")? {
+ let prop_path =
+ path.join("/") + "/" + prop.name().context("Error getting property name")?.to_str()?;
+ if ignore_set.contains(&prop_path) {
+ continue;
+ }
+ let Some(prop1_value) = prop_map.remove(&prop_path) else {
+ errors.push(format!("added prop_path: {}", prop_path));
+ continue;
+ };
+ let prop_compare = prop1_value == prop.value().context("Error getting value")?;
+ // Check if value should be ignored. If yes, skip field.
+ if ignore_value_set.contains(&prop_path) {
+ continue;
+ }
+ if !prop_compare {
+ errors.push(format!(
+ "prop {} value mismatch: old: {} -> new: {}",
+ prop_path,
+ encode(prop1_value),
+ encode(prop.value().context("Error getting value")?)
+ ));
+ }
+ }
+ if !prop_map.is_empty() {
+ errors.push(format!("missing properties: {:?}", prop_map));
+ }
+ Ok(())
+}
+
+fn compare_subnodes(
+ node1: &FdtNode,
+ node2: &FdtNode,
+ ignore_value_set: &BTreeSet<String>,
+ ignore_set: &BTreeSet<String>,
+ path: &mut [String],
+ errors: &mut Vec<String>,
+) -> Result<()> {
+ let mut subnodes_map: BTreeMap<String, FdtNode> = BTreeMap::new();
+ for subnode in node1.subnodes().context("Error getting subnodes of first FDT")? {
+ let sn_path = path.join("/")
+ + "/"
+ + subnode.name().context("Error getting property name")?.to_str()?;
+ // Do not add to subnode map if skipping
+ if ignore_set.contains(&sn_path) {
+ continue;
+ }
+ if subnodes_map.insert(sn_path.clone(), subnode).is_some() {
+ return Err(anyhow!("Duplicate subnodes detected: {}", sn_path));
+ }
+ }
+ for sn2 in node2.subnodes().context("Error getting subnodes of second FDT")? {
+ let sn_path =
+ path.join("/") + "/" + sn2.name().context("Error getting subnode name")?.to_str()?;
+ let sn1 = subnodes_map.remove(&sn_path);
+ match sn1 {
+ Some(sn) => {
+ compare_props(
+ &sn,
+ &sn2,
+ ignore_value_set,
+ ignore_set,
+ &mut [sn_path.clone()],
+ errors,
+ )?;
+ compare_subnodes(
+ &sn,
+ &sn2,
+ ignore_value_set,
+ ignore_set,
+ &mut [sn_path.clone()],
+ errors,
+ )?;
+ }
+ None => errors.push(format!("added node: {}", sn_path)),
+ }
+ }
+ if !subnodes_map.is_empty() {
+ errors.push(format!("missing nodes: {:?}", subnodes_map));
+ }
+ Ok(())
+}
diff --git a/tests/helper/Android.bp b/tests/helper/Android.bp
index 1c38d12..2402721 100644
--- a/tests/helper/Android.bp
+++ b/tests/helper/Android.bp
@@ -7,6 +7,7 @@
srcs: ["src/java/com/android/microdroid/test/common/*.java"],
host_supported: true,
sdk_version: "system_current",
+ apex_available: ["com.android.virt"],
}
java_library_static {
diff --git a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
index e6f15ff..a932d32 100644
--- a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
+++ b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
@@ -207,9 +207,6 @@
assume().withMessage("Skip where protected VMs aren't supported")
.that(capabilities & VirtualMachineManager.CAPABILITY_PROTECTED_VM)
.isNotEqualTo(0);
- assume().withMessage("Testing protected VMs on GSI isn't supported. b/272443823")
- .that(isGsi())
- .isFalse();
// TODO(b/376870129): remove this
assume().withMessage("pVMs with 16k kernel are not supported yet :(")
.that(mOs)
@@ -259,11 +256,11 @@
}
/**
- * @return The first vendor API level when the vendor images for an SoC that is qualified for
- * vendor freeze are first released with this property, or 0 if the property is not set.
+ * @return The vendor API level that the device as a whole must conform to, this value should be
+ * available on both GRF and non-GRF devices.
*/
protected static int getFirstVendorApiLevel() {
- return SystemProperties.getInt("ro.board.first_api_level", 0);
+ return SystemProperties.getInt("ro.vendor.api_level", -1);
}
protected void assumeSupportedDevice() {
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index e6ac490..9c66765 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -1298,6 +1298,7 @@
@Test
@CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-7"})
+ @VsrTest(requirements = {"VSR-7.1-001.005"})
public void bccIsSuperficiallyWellFormed() throws Exception {
assumeSupportedDevice();
@@ -1342,7 +1343,7 @@
}
@Test
- @VsrTest(requirements = {"VSR-7.1-001.004"})
+ @VsrTest(requirements = {"VSR-7.1-001.005"})
public void protectedVmHasValidDiceChain() throws Exception {
// This test validates two things regarding the pVM DICE chain:
// 1. The DICE chain is well-formed that all the entries conform to the DICE spec.
diff --git a/tests/testapk/src/native/testbinary.cpp b/tests/testapk/src/native/testbinary.cpp
index 632f648..7edabfd 100644
--- a/tests/testapk/src/native/testbinary.cpp
+++ b/tests/testapk/src/native/testbinary.cpp
@@ -99,6 +99,9 @@
char* line = nullptr;
size_t size = 0;
if (getline(&line, &size, input) < 0) {
+ if (errno == 0) {
+ return {}; // the input was closed
+ }
return ErrnoError() << "Failed to read";
}
@@ -136,12 +139,12 @@
}
std::thread accept_thread{[listening_fd = std::move(server_fd)] {
- auto result = run_echo_reverse_server(listening_fd);
- if (!result.ok()) {
- __android_log_write(ANDROID_LOG_ERROR, TAG, result.error().message().c_str());
- // Make sure the VM exits so the test will fail solidly
- exit(1);
+ Result<void> result;
+ while ((result = run_echo_reverse_server(listening_fd)).ok()) {
}
+ __android_log_write(ANDROID_LOG_ERROR, TAG, result.error().message().c_str());
+ // Make sure the VM exits so the test will fail solidly
+ exit(1);
}};
accept_thread.detach();
diff --git a/tests/testapk/src/native/testbinary.rs b/tests/testapk/src/native/testbinary.rs
index e479342..2502113 100644
--- a/tests/testapk/src/native/testbinary.rs
+++ b/tests/testapk/src/native/testbinary.rs
@@ -26,7 +26,6 @@
};
use cstr::cstr;
use log::{error, info};
-use std::panic;
use std::process::exit;
use std::string::String;
use std::vec::Vec;
@@ -40,10 +39,6 @@
.with_tag("microdroid_testlib_rust")
.with_max_level(log::LevelFilter::Debug),
);
- // Redirect panic messages to logcat.
- panic::set_hook(Box::new(|panic_info| {
- error!("{panic_info}");
- }));
if let Err(e) = try_main() {
error!("failed with {:?}", e);
exit(1);
diff --git a/tests/vm_attestation/src/native/main.rs b/tests/vm_attestation/src/native/main.rs
index 52635ad..e8038b5 100644
--- a/tests/vm_attestation/src/native/main.rs
+++ b/tests/vm_attestation/src/native/main.rs
@@ -24,10 +24,7 @@
binder::{self, BinderFeatures, Interface, IntoBinderResult, Strong},
};
use log::{error, info};
-use std::{
- panic,
- sync::{Arc, Mutex},
-};
+use std::sync::{Arc, Mutex};
use vm_payload::{AttestationError, AttestationResult};
vm_payload::main!(main);
@@ -39,10 +36,6 @@
.with_tag("service_vm_client")
.with_max_level(log::LevelFilter::Debug),
);
- // Redirect panic messages to logcat.
- panic::set_hook(Box::new(|panic_info| {
- error!("{}", panic_info);
- }));
if let Err(e) = try_main() {
error!("failed with {:?}", e);
std::process::exit(1);
diff --git a/tests/vmbase_example/src/main.rs b/tests/vmbase_example/src/main.rs
index 34a2b0b..cbe90d8 100644
--- a/tests/vmbase_example/src/main.rs
+++ b/tests/vmbase_example/src/main.rs
@@ -27,7 +27,7 @@
collections::{HashSet, VecDeque},
fs::File,
io::{self, BufRead, BufReader, Read, Write},
- panic, thread,
+ thread,
};
use vmclient::{DeathReason, VmInstance};
@@ -58,11 +58,6 @@
.with_max_level(log::LevelFilter::Debug),
);
- // Redirect panic messages to logcat.
- panic::set_hook(Box::new(|panic_info| {
- log::error!("{}", panic_info);
- }));
-
// We need to start the thread pool for Binder to work properly, especially link_to_death.
ProcessState::start_thread_pool();