Implement platform API to assign devices
Clients can specify either device types or sysfs nodes when creating a
VM. The app should have USE_CUSTOM_VIRTUAL_MACHINE permission to do so.
Bug: 287379025
Test: TH
Test: adb root && adb shell /apex/com.android.virt/bin/vm \
run-microdroid --devices /sys/bus/platform/devices/16d00000.eh \
--protected
Change-Id: I375d455fa1fa9cbad6e552cdb7b3e9a2138f9278
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
index 4cad2e3..b307854 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -62,6 +62,8 @@
public final class VirtualMachineConfig {
private static final String TAG = "VirtualMachineConfig";
+ private static String[] EMPTY_STRING_ARRAY = {};
+
// These define the schema of the config file persisted on disk.
private static final int VERSION = 6;
private static final String KEY_VERSION = "version";
@@ -517,7 +519,8 @@
if (mVendorDiskImage != null) {
VirtualMachineAppConfig.CustomConfig customConfig =
new VirtualMachineAppConfig.CustomConfig();
- customConfig.taskProfiles = new String[0];
+ customConfig.taskProfiles = EMPTY_STRING_ARRAY;
+ customConfig.devices = EMPTY_STRING_ARRAY;
try {
customConfig.vendorImage =
ParcelFileDescriptor.open(mVendorDiskImage, MODE_READ_ONLY);
diff --git a/libs/vmconfig/src/lib.rs b/libs/vmconfig/src/lib.rs
index 7ca8272..50f3c8e 100644
--- a/libs/vmconfig/src/lib.rs
+++ b/libs/vmconfig/src/lib.rs
@@ -21,7 +21,7 @@
binder::ParcelFileDescriptor,
};
-use anyhow::{bail, Context, Error, Result};
+use anyhow::{anyhow, bail, Context, Error, Result};
use semver::VersionReq;
use serde::{Deserialize, Serialize};
use std::convert::TryInto;
@@ -57,6 +57,9 @@
/// Version or range of versions of the virtual platform that this config is compatible with.
/// The format follows SemVer (https://semver.org).
pub platform_version: VersionReq,
+ /// SysFS paths of devices assigned to the VM.
+ #[serde(default)]
+ pub devices: Vec<PathBuf>,
}
impl VmConfig {
@@ -103,6 +106,13 @@
protectedVm: self.protected,
memoryMib: memory_mib,
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<_>>()?,
..Default::default()
})
}
diff --git a/rialto/tests/test.rs b/rialto/tests/test.rs
index 4ad8eb8..8089016 100644
--- a/rialto/tests/test.rs
+++ b/rialto/tests/test.rs
@@ -117,8 +117,8 @@
memoryMib: 300,
cpuTopology: CpuTopology::ONE_CPU,
platformVersion: "~1.0".to_string(),
- taskProfiles: vec![],
gdbPort: 0, // No gdb
+ ..Default::default()
});
let vm = VmInstance::create(
service.as_ref(),
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index daee7c5..15e5407 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -62,9 +62,10 @@
use rpcbinder::RpcServer;
use rustutils::system_properties;
use semver::VersionReq;
+use std::collections::HashSet;
use std::convert::TryInto;
use std::ffi::CStr;
-use std::fs::{read_dir, remove_file, File, OpenOptions};
+use std::fs::{canonicalize, read_dir, remove_file, File, OpenOptions};
use std::io::{BufRead, BufReader, Error, ErrorKind, Write};
use std::num::{NonZeroU16, NonZeroU32};
use std::os::unix::io::{FromRawFd, IntoRawFd};
@@ -337,7 +338,8 @@
// - controlling CPUs;
// - specifying a config file in the APK; (this one is not part of CustomConfig)
// - gdbPort is set, meaning that crosvm will start a gdb server;
- // - using anything other than the default kernel.
+ // - using anything other than the default kernel;
+ // - specifying devices to be assigned.
config.customConfig.is_some() || matches!(config.payload, Payload::ConfigPath(_))
}
};
@@ -456,6 +458,27 @@
}
};
+ let devices_dtbo = if !config.devices.is_empty() {
+ let mut set = HashSet::new();
+ for device in config.devices.iter() {
+ let path = canonicalize(device).map_err(|e| {
+ Status::new_exception_str(
+ ExceptionCode::ILLEGAL_ARGUMENT,
+ Some(format!("can't canonicalize {device}: {e:?}")),
+ )
+ })?;
+ if !set.insert(path) {
+ return Err(Status::new_exception_str(
+ ExceptionCode::ILLEGAL_ARGUMENT,
+ Some(format!("duplicated device {device}")),
+ ));
+ }
+ }
+ Some(clone_file(&GLOBAL_SERVICE.bindDevicesToVfioDriver(&config.devices)?)?)
+ } else {
+ None
+ };
+
// Actually start the VM.
let crosvm_config = CrosvmConfig {
cid,
@@ -479,6 +502,8 @@
platform_version: parse_platform_version_req(&config.platformVersion)?,
detect_hangup: is_app_config,
gdb_port,
+ vfio_devices: config.devices.iter().map(PathBuf::from).collect(),
+ devices_dtbo,
};
let instance = Arc::new(
VmInstance::new(
@@ -641,6 +666,8 @@
add_microdroid_vendor_image(clone_file(file)?, &mut vm_config);
append_kernel_param("androidboot.microdroid.mount_vendor=1", &mut vm_config)
}
+
+ vm_config.devices = custom_config.devices.clone();
}
if config.memoryMib > 0 {
diff --git a/virtualizationmanager/src/crosvm.rs b/virtualizationmanager/src/crosvm.rs
index 31db3f6..68cc7f2 100644
--- a/virtualizationmanager/src/crosvm.rs
+++ b/virtualizationmanager/src/crosvm.rs
@@ -115,6 +115,8 @@
pub platform_version: VersionReq,
pub detect_hangup: bool,
pub gdb_port: Option<NonZeroU16>,
+ pub vfio_devices: Vec<PathBuf>,
+ pub devices_dtbo: Option<File>,
}
/// A disk image to pass to crosvm for a VM.
@@ -185,6 +187,7 @@
if let VmState::NotStarted { config } = state {
let detect_hangup = config.detect_hangup;
let (failure_pipe_read, failure_pipe_write) = create_pipe()?;
+ let vfio_devices = config.vfio_devices.clone();
// If this fails and returns an error, `self` will be left in the `Failed` state.
let child =
@@ -199,7 +202,7 @@
let child_clone = child.clone();
let instance_clone = instance.clone();
let monitor_vm_exit_thread = Some(thread::spawn(move || {
- instance_clone.monitor_vm_exit(child_clone, failure_pipe_read);
+ instance_clone.monitor_vm_exit(child_clone, failure_pipe_read, vfio_devices);
}));
if detect_hangup {
@@ -336,7 +339,12 @@
/// Monitors the exit of the VM (i.e. termination of the `child` process). When that happens,
/// handles the event by updating the state, noityfing the event to clients by calling
/// callbacks, and removing temporary files for the VM.
- fn monitor_vm_exit(&self, child: Arc<SharedChild>, mut failure_pipe_read: File) {
+ fn monitor_vm_exit(
+ &self,
+ child: Arc<SharedChild>,
+ mut failure_pipe_read: File,
+ vfio_devices: Vec<PathBuf>,
+ ) {
let result = child.wait();
match &result {
Err(e) => error!("Error waiting for crosvm({}) instance to die: {}", child.id(), e),
@@ -394,6 +402,11 @@
remove_temporary_files(&self.temporary_directory).unwrap_or_else(|e| {
error!("Error removing temporary files from {:?}: {}", self.temporary_directory, e);
});
+
+ // TODO(b/278008182): clean up assigned devices.
+ for device in vfio_devices.iter() {
+ info!("NOT RELEASING {device:?}");
+ }
}
/// Waits until payload is started, or timeout expires. When timeout occurs, kill
@@ -677,6 +690,39 @@
}
}
+const SYSFS_PLATFORM_DEVICES_PATH: &str = "/sys/devices/platform/";
+const VFIO_PLATFORM_DRIVER_PATH: &str = "/sys/bus/platform/drivers/vfio-platform";
+
+fn vfio_argument_for_platform_device(path: &Path) -> Result<String, Error> {
+ // Check platform device exists
+ let path = path.canonicalize()?;
+ if !path.starts_with(SYSFS_PLATFORM_DEVICES_PATH) {
+ bail!("{path:?} is not a platform device");
+ }
+
+ // Check platform device is bound to VFIO driver
+ let dev_driver_path = path.join("driver").canonicalize()?;
+ if dev_driver_path != Path::new(VFIO_PLATFORM_DRIVER_PATH) {
+ bail!("{path:?} is not bound to VFIO-platform driver");
+ }
+
+ if let Some(p) = path.to_str() {
+ Ok(format!("--vfio={p},iommu=viommu"))
+ } else {
+ bail!("invalid path {path:?}");
+ }
+}
+
+fn append_platform_devices(command: &mut Command, config: &CrosvmConfig) -> Result<(), Error> {
+ for device in &config.vfio_devices {
+ command.arg(vfio_argument_for_platform_device(device)?);
+ }
+ if let Some(_dtbo) = &config.devices_dtbo {
+ // TODO(b/291192693): add dtbo to command line
+ }
+ Ok(())
+}
+
/// Starts an instance of `crosvm` to manage a new VM.
fn run_vm(
config: CrosvmConfig,
@@ -833,6 +879,8 @@
.arg("--socket")
.arg(add_preserved_fd(&mut preserved_fds, &control_server_socket.as_raw_descriptor()));
+ append_platform_devices(&mut command, &config)?;
+
debug!("Preserving FDs {:?}", preserved_fds);
command.preserved_fds(preserved_fds);
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl
index 2b762c4..9021055 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl
@@ -108,6 +108,9 @@
/** A disk image containing vendor specific modules. */
@nullable ParcelFileDescriptor vendorImage;
+
+ /** List of SysFS nodes of devices to be assigned */
+ String[] devices;
}
/** Configuration parameters guarded by android.permission.USE_CUSTOM_VIRTUAL_MACHINE */
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
index 87d4ba2..7c0ed0c 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
@@ -69,4 +69,7 @@
* If set to zero, then gdb server won't be started.
*/
int gdbPort = 0;
+
+ /** List of SysFS nodes of devices to be assigned */
+ String[] devices;
}
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 2dbb6e2..8aea556 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -37,7 +37,7 @@
use libc::VMADDR_CID_HOST;
use log::{error, info, warn};
use rustutils::system_properties;
-use std::collections::{HashMap, HashSet};
+use std::collections::HashMap;
use std::fs::{canonicalize, create_dir, remove_dir_all, set_permissions, File, Permissions};
use std::io::{Read, Write};
use std::os::fd::FromRawFd;
@@ -188,16 +188,7 @@
fn bindDevicesToVfioDriver(&self, devices: &[String]) -> binder::Result<ParcelFileDescriptor> {
check_use_custom_virtual_machine()?;
- let mut set = HashSet::new();
- for device in devices.iter() {
- if !set.insert(device) {
- return Err(Status::new_exception_str(
- ExceptionCode::ILLEGAL_ARGUMENT,
- Some(format!("duplicated device {device}")),
- ));
- }
- bind_device(device)?;
- }
+ devices.iter().try_for_each(|x| bind_device(x))?;
// TODO(b/278008182): create a file descriptor containing DTBO for devices.
let (raw_read, raw_write) = pipe2(OFlag::O_CLOEXEC).map_err(|e| {
diff --git a/virtualizationservice/src/rkpvm.rs b/virtualizationservice/src/rkpvm.rs
index ebfb667..63160f4 100644
--- a/virtualizationservice/src/rkpvm.rs
+++ b/virtualizationservice/src/rkpvm.rs
@@ -76,8 +76,8 @@
memoryMib: 300,
cpuTopology: CpuTopology::ONE_CPU,
platformVersion: "~1.0".to_string(),
- taskProfiles: vec![],
gdbPort: 0, // No gdb
+ ..Default::default()
});
let vm = VmInstance::create(service.as_ref(), &config, None, None, None, None)
.context("Failed to create service VM")?;
diff --git a/vm/src/main.rs b/vm/src/main.rs
index 0c99acb..64bcd02 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -119,6 +119,10 @@
/// Path to disk image containing vendor-specific modules.
#[clap(long)]
vendor: Option<PathBuf>,
+
+ /// SysFS nodes of devices to assign to VM
+ #[clap(long)]
+ devices: Vec<PathBuf>,
},
/// Run a virtual machine with Microdroid inside
RunMicrodroid {
@@ -187,6 +191,10 @@
/// Path to disk image containing vendor-specific modules.
#[clap(long)]
vendor: Option<PathBuf>,
+
+ /// SysFS nodes of devices to assign to VM
+ #[clap(long)]
+ devices: Vec<PathBuf>,
},
/// Run a virtual machine
Run {
@@ -308,6 +316,7 @@
gdb,
kernel,
vendor,
+ devices,
} => command_run_app(
name,
get_service()?.as_ref(),
@@ -330,6 +339,7 @@
gdb,
kernel.as_deref(),
vendor.as_deref(),
+ devices,
),
Opt::RunMicrodroid {
name,
@@ -347,6 +357,7 @@
gdb,
kernel,
vendor,
+ devices,
} => command_run_microdroid(
name,
get_service()?.as_ref(),
@@ -364,6 +375,7 @@
gdb,
kernel.as_deref(),
vendor.as_deref(),
+ devices,
),
Opt::Run { name, config, cpu_topology, task_profiles, console, console_in, log, gdb } => {
command_run(
@@ -420,6 +432,18 @@
println!("/dev/kvm does not exist.");
}
+ if Path::new("/dev/vfio/vfio").exists() {
+ println!("/dev/vfio/vfio exists.");
+ } else {
+ println!("/dev/vfio/vfio does not exist.");
+ }
+
+ if Path::new("/sys/bus/platform/drivers/vfio-platform").exists() {
+ println!("VFIO-platform is supported.");
+ } else {
+ println!("VFIO-platform is not supported.");
+ }
+
Ok(())
}
diff --git a/vm/src/run.rs b/vm/src/run.rs
index f50bd50..250c56c 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -66,6 +66,7 @@
gdb: Option<NonZeroU16>,
kernel: Option<&Path>,
vendor: Option<&Path>,
+ devices: Vec<PathBuf>,
) -> Result<(), Error> {
let apk_file = File::open(apk).context("Failed to open APK file")?;
@@ -148,6 +149,12 @@
gdbPort: gdb.map(u16::from).unwrap_or(0) as i32, // 0 means no gdb
taskProfiles: task_profiles,
vendorImage: vendor,
+ devices: devices
+ .iter()
+ .map(|x| {
+ x.to_str().map(String::from).ok_or(anyhow!("Failed to convert {x:?} to String"))
+ })
+ .collect::<Result<_, _>>()?,
};
let config = VirtualMachineConfig::AppConfig(VirtualMachineAppConfig {
@@ -208,6 +215,7 @@
gdb: Option<NonZeroU16>,
kernel: Option<&Path>,
vendor: Option<&Path>,
+ devices: Vec<PathBuf>,
) -> Result<(), Error> {
let apk = find_empty_payload_apk_path()?;
println!("found path {}", apk.display());
@@ -242,6 +250,7 @@
gdb,
kernel,
vendor,
+ devices,
)
}
diff --git a/vmbase/example/tests/test.rs b/vmbase/example/tests/test.rs
index 2b6dfbc..17ff947 100644
--- a/vmbase/example/tests/test.rs
+++ b/vmbase/example/tests/test.rs
@@ -101,8 +101,8 @@
memoryMib: 300,
cpuTopology: CpuTopology::ONE_CPU,
platformVersion: "~1.0".to_string(),
- taskProfiles: vec![],
gdbPort: 0, // no gdb
+ ..Default::default()
});
let (handle, console) = android_log_fd()?;
let (mut log_reader, log_writer) = pipe()?;