virtmgr: Pass VM DTBO path to crosvm if devices assigned
crosvm needs to know the path of VM DTBO to be able to populate the
guest device tree. Add a new method on the global VirtualizationService
which creates the file in a temporary directory common to all VMs and
has the file populated by VfioHandler.
Client virtmgr instances can then request a read-only FD for the file
from VirtualizationService. They then pass it as a preserved FD to
crosvm.
Bug: 297313212
Test: vm run-microdroid and see crosvm argument
Change-Id: I5491b9d084a8e845c1ad82d5cfad45b3374bf495
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index 6ae3bbd..acd182c 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -482,7 +482,7 @@
}
};
- let vfio_devices = if !config.devices.is_empty() {
+ let (vfio_devices, dtbo) = if !config.devices.is_empty() {
let mut set = HashSet::new();
for device in config.devices.iter() {
let path = canonicalize(device)
@@ -493,16 +493,25 @@
.or_binder_exception(ExceptionCode::ILLEGAL_ARGUMENT);
}
}
- GLOBAL_SERVICE
+ let devices = GLOBAL_SERVICE
.bindDevicesToVfioDriver(&config.devices)?
.into_iter()
.map(|x| VfioDevice {
sysfs_path: PathBuf::from(&x.sysfsPath),
dtbo_label: x.dtboLabel,
})
- .collect::<Vec<_>>()
+ .collect::<Vec<_>>();
+ let dtbo_file = File::from(
+ GLOBAL_SERVICE
+ .getDtboFile()?
+ .as_ref()
+ .try_clone()
+ .context("Failed to create File from ParcelFileDescriptor")
+ .or_binder_exception(ExceptionCode::BAD_PARCELABLE)?,
+ );
+ (devices, Some(dtbo_file))
} else {
- vec![]
+ (vec![], None)
};
// Actually start the VM.
@@ -529,6 +538,7 @@
detect_hangup: is_app_config,
gdb_port,
vfio_devices,
+ dtbo,
dtbo_vendor,
};
let instance = Arc::new(
diff --git a/virtualizationmanager/src/crosvm.rs b/virtualizationmanager/src/crosvm.rs
index 9a50776..2ba0e0e 100644
--- a/virtualizationmanager/src/crosvm.rs
+++ b/virtualizationmanager/src/crosvm.rs
@@ -116,6 +116,7 @@
pub detect_hangup: bool,
pub gdb_port: Option<NonZeroU16>,
pub vfio_devices: Vec<VfioDevice>,
+ pub dtbo: Option<File>,
pub dtbo_vendor: Option<File>,
}
@@ -723,11 +724,23 @@
}
}
-fn append_platform_devices(command: &mut Command, config: &CrosvmConfig) -> Result<(), Error> {
+fn append_platform_devices(
+ command: &mut Command,
+ preserved_fds: &mut Vec<RawFd>,
+ config: &CrosvmConfig,
+) -> Result<(), Error> {
+ if config.vfio_devices.is_empty() {
+ return Ok(());
+ }
+
+ let Some(dtbo) = &config.dtbo else {
+ bail!("VFIO devices assigned but no DTBO available");
+ };
+ command.arg(format!("--device-tree-overlay={},filter", add_preserved_fd(preserved_fds, dtbo)));
+
for device in &config.vfio_devices {
command.arg(vfio_argument_for_platform_device(device)?);
}
- // TODO(b/291192693): add dtbo to command line when assigned device is not empty.
Ok(())
}
@@ -889,7 +902,7 @@
// TODO(b/285855436): Pass dtbo_vendor after --device-tree-overlay crosvm option is supported.
- append_platform_devices(&mut command, &config)?;
+ append_platform_devices(&mut command, &mut preserved_fds, &config)?;
debug!("Preserving FDs {:?}", preserved_fds);
command.preserved_fds(preserved_fds);
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
index c384a6f..a2cb693 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
@@ -81,4 +81,7 @@
* @return a list of pairs (sysfs path, DTBO node label) for devices.
*/
BoundDevice[] bindDevicesToVfioDriver(in String[] devices);
+
+ /** Returns a read-only file descriptor of the VM DTBO file. */
+ ParcelFileDescriptor getDtboFile();
}
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 938225e..7cdfdc6 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -43,7 +43,7 @@
use rustutils::system_properties;
use serde::Deserialize;
use std::collections::{HashMap, HashSet};
-use std::fs::{self, create_dir, remove_dir_all, set_permissions, File, Permissions};
+use std::fs::{self, create_dir, remove_dir_all, remove_file, set_permissions, File, Permissions};
use std::io::{Read, Write};
use std::os::unix::fs::PermissionsExt;
use std::os::unix::raw::{pid_t, uid_t};
@@ -212,18 +212,8 @@
let vfio_service: Strong<dyn IVfioHandler> =
wait_for_interface(<BpVfioHandler as IVfioHandler>::get_descriptor())?;
-
vfio_service.bindDevicesToVfioDriver(devices)?;
- let dtbo_path = Path::new(TEMPORARY_DIRECTORY).join("common").join("dtbo");
- if !dtbo_path.exists() {
- // open a writable file descriptor for vfio_handler
- let dtbo = File::create(&dtbo_path)
- .context("Failed to create VM DTBO file")
- .or_service_specific_exception(-1)?;
- vfio_service.writeVmDtbo(&ParcelFileDescriptor::new(dtbo))?;
- }
-
Ok(get_assignable_devices()?
.device
.into_iter()
@@ -236,6 +226,14 @@
})
.collect::<Vec<_>>())
}
+
+ fn getDtboFile(&self) -> binder::Result<ParcelFileDescriptor> {
+ check_use_custom_virtual_machine()?;
+
+ let state = &mut *self.state.lock().unwrap();
+ let file = state.get_dtbo_file().or_service_specific_exception(-1)?;
+ Ok(ParcelFileDescriptor::new(file))
+ }
}
// KEEP IN SYNC WITH assignable_devices.xsd
@@ -314,6 +312,9 @@
/// VM contexts currently allocated to running VMs. A CID is never recycled as long
/// as there is a strong reference held by a GlobalVmContext.
held_contexts: HashMap<Cid, Weak<GlobalVmInstance>>,
+
+ /// Cached read-only FD of VM DTBO file. Also serves as a lock for creating the file.
+ dtbo_file: Mutex<Option<File>>,
}
impl GlobalState {
@@ -377,26 +378,64 @@
let cid = self.get_next_available_cid()?;
let instance = Arc::new(GlobalVmInstance { cid, requester_uid, requester_debug_pid });
- create_temporary_directory(&instance.get_temp_dir(), requester_uid)?;
+ create_temporary_directory(&instance.get_temp_dir(), Some(requester_uid))?;
self.held_contexts.insert(cid, Arc::downgrade(&instance));
let binder = GlobalVmContext { instance, ..Default::default() };
Ok(BnGlobalVmContext::new_binder(binder, BinderFeatures::default()))
}
+
+ fn get_dtbo_file(&mut self) -> Result<File> {
+ let mut file = self.dtbo_file.lock().unwrap();
+
+ let fd = if let Some(ref_fd) = &*file {
+ ref_fd.try_clone()?
+ } else {
+ let path = get_or_create_common_dir()?.join("vm.dtbo");
+ if path.exists() {
+ // All temporary files are deleted when the service is started.
+ // If the file exists but the FD is not cached, the file is
+ // likely corrupted.
+ remove_file(&path).context("Failed to clone cached VM DTBO file descriptor")?;
+ }
+
+ // Open a write-only file descriptor for vfio_handler.
+ let write_fd = File::create(&path).context("Failed to create VM DTBO file")?;
+
+ let vfio_service: Strong<dyn IVfioHandler> =
+ wait_for_interface(<BpVfioHandler as IVfioHandler>::get_descriptor())?;
+ vfio_service.writeVmDtbo(&ParcelFileDescriptor::new(write_fd))?;
+
+ // Open read-only. This FD will be cached and returned to clients.
+ let read_fd = File::open(&path).context("Failed to open VM DTBO file")?;
+ let read_fd_clone =
+ read_fd.try_clone().context("Failed to clone VM DTBO file descriptor")?;
+ *file = Some(read_fd);
+ read_fd_clone
+ };
+
+ Ok(fd)
+ }
}
-fn create_temporary_directory(path: &PathBuf, requester_uid: uid_t) -> Result<()> {
+fn create_temporary_directory(path: &PathBuf, requester_uid: Option<uid_t>) -> Result<()> {
+ // Directory may exist if previous attempt to create it had failed.
+ // Delete it before trying again.
if path.as_path().exists() {
remove_temporary_dir(path).unwrap_or_else(|e| {
warn!("Could not delete temporary directory {:?}: {}", path, e);
});
}
- // Create a directory that is owned by client's UID but system's GID, and permissions 0700.
+ // Create directory.
+ create_dir(path).with_context(|| format!("Could not create temporary directory {:?}", path))?;
+ // If provided, change ownership to client's UID but system's GID, and permissions 0700.
// If the chown() fails, this will leave behind an empty directory that will get removed
// at the next attempt, or if virtualizationservice is restarted.
- create_dir(path).with_context(|| format!("Could not create temporary directory {:?}", path))?;
- chown(path, Some(Uid::from_raw(requester_uid)), None)
- .with_context(|| format!("Could not set ownership of temporary directory {:?}", path))?;
+ if let Some(uid) = requester_uid {
+ chown(path, Some(Uid::from_raw(uid)), None).with_context(|| {
+ format!("Could not set ownership of temporary directory {:?}", path)
+ })?;
+ }
Ok(())
}
@@ -410,6 +449,14 @@
Ok(())
}
+fn get_or_create_common_dir() -> Result<PathBuf> {
+ let path = Path::new(TEMPORARY_DIRECTORY).join("common");
+ if !path.exists() {
+ create_temporary_directory(&path, None)?;
+ }
+ Ok(path)
+}
+
/// Implementation of the AIDL `IGlobalVmContext` interface.
#[derive(Debug, Default)]
struct GlobalVmContext {