aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorErwin Jansen <jansene@google.com>2024-02-13 14:42:38 -0800
committerErwin Jansen <jansene@google.com>2024-02-14 16:42:56 +0000
commit68114839c53631df7468eb4e26a30648f184b6a4 (patch)
tree5664325f1c3bf3b9a751d7da4894ba764a5fc26b
parentff2fc477ccd475b3124ad18611ebc243280be322 (diff)
parente23ea9b0607d07209f8b4c20f2864538079ea560 (diff)
downloadcrosvm-emu-dev.tar.gz
Merge remote-tracking branch 'aosp/upstream-main'emu-dev
Bug: 324640237 Change-Id: I64c2f6b9a31a98ca930ba3f979cef677dce4c52e
-rw-r--r--Cargo.lock2
-rw-r--r--Cargo.toml11
-rw-r--r--aarch64/src/lib.rs7
-rw-r--r--arch/src/lib.rs7
-rw-r--r--arch/src/pstore.rs2
-rw-r--r--base/src/sys/linux/mod.rs9
-rw-r--r--base/src/sys/macos/mod.rs33
-rw-r--r--base/src/sys/unix/descriptor.rs26
-rw-r--r--base/src/sys/unix/file_flags.rs2
-rw-r--r--base/src/sys/windows/file_util.rs6
-rw-r--r--cros_async/src/common_executor.rs4
-rw-r--r--cros_async/src/sys/linux/executor.rs2
-rw-r--r--cros_async/src/sys/linux/fd_executor.rs4
-rw-r--r--cros_async/src/sys/linux/poll_source.rs2
-rw-r--r--cros_async/src/sys/linux/uring_executor.rs8
-rw-r--r--cros_async/src/sys/linux/uring_source.rs2
-rw-r--r--cros_async/src/sys/windows/executor.rs2
-rw-r--r--devices/Cargo.toml1
-rw-r--r--devices/src/pci/coiommu.rs2
-rw-r--r--devices/src/pci/pci_root.rs11
-rw-r--r--devices/src/pci/vfio_pci.rs26
-rw-r--r--devices/src/platform/vfio_platform.rs10
-rw-r--r--devices/src/virtio/gpu/mod.rs10
-rw-r--r--devices/src/virtio/gpu/parameters.rs5
-rw-r--r--devices/src/virtio/gpu/virtio_gpu.rs29
-rw-r--r--devices/src/virtio/net.rs84
-rw-r--r--devices/src/virtio/snd/common_backend/stream_info.rs2
-rw-r--r--devices/src/virtio/snd/sys/windows.rs5
-rw-r--r--devices/src/virtio/vhost/net.rs19
-rw-r--r--devices/src/virtio/vhost/user/device/gpu.rs122
-rw-r--r--devices/src/virtio/vhost/user/device/handler.rs111
-rw-r--r--devices/src/virtio/vhost/user/vmm/handler.rs33
-rw-r--r--devices/src/virtio/virtio_device.rs10
-rw-r--r--devices/src/virtio/virtio_pci_device.rs29
-rw-r--r--devices/src/virtio/wl.rs10
-rw-r--r--docs/book/src/SUMMARY.md1
-rw-r--r--docs/book/src/devices/index.md2
-rw-r--r--docs/book/src/devices/input.md182
-rw-r--r--e2e_tests/guest_under_test/Makefile35
-rw-r--r--e2e_tests/guest_under_test/kernel/patches/virtio_pvclock.patch590
-rw-r--r--gpu_display/examples/simple.rs8
-rw-r--r--gpu_display/examples/simple_open.rs8
-rw-r--r--gpu_display/src/gpu_display_stub.rs1
-rw-r--r--gpu_display/src/gpu_display_win/mod.rs1
-rw-r--r--gpu_display/src/gpu_display_wl.rs17
-rw-r--r--gpu_display/src/gpu_display_x.rs1
-rw-r--r--gpu_display/src/lib.rs19
-rw-r--r--hypervisor/Cargo.toml1
-rw-r--r--hypervisor/src/caps.rs2
-rw-r--r--hypervisor/src/geniezone/geniezone_sys/mod.rs4
-rw-r--r--hypervisor/src/geniezone/mod.rs3
-rw-r--r--hypervisor/src/gunyah/mod.rs2
-rw-r--r--hypervisor/src/haxm/vm.rs11
-rw-r--r--hypervisor/src/kvm/mod.rs29
-rw-r--r--hypervisor/src/lib.rs14
-rw-r--r--hypervisor/src/whpx/vm.rs37
-rw-r--r--hypervisor/tests/dirty_log.rs1
-rw-r--r--hypervisor/tests/kvm/main.rs41
-rw-r--r--hypervisor/tests/read_only_memory.rs2
-rw-r--r--hypervisor/tests/remove_memory.rs2
-rw-r--r--infra/README.recipes.md32
-rw-r--r--infra/config/recipes.cfg4
-rw-r--r--io_uring/tests/uring.rs2
-rw-r--r--jail/seccomp/aarch64/gpu_common.policy5
-rw-r--r--jail/seccomp/arm/gpu_common.policy4
-rw-r--r--kernel_loader/src/arm64.rs9
-rw-r--r--kvm/src/cap.rs1
-rwxr-xr-xkvm_sys/bindgen.sh4
-rw-r--r--kvm_sys/src/aarch64/bindings.rs4
-rw-r--r--kvm_sys/src/riscv64/bindings.rs4
-rw-r--r--kvm_sys/src/x86/bindings.rs4
-rw-r--r--metrics/src/sys/windows.rs4
-rw-r--r--metrics/src/sys/windows/system_metrics.rs12
-rw-r--r--net_util/src/slirp/sys/windows/handler.rs2
-rw-r--r--riscv64/src/lib.rs1
-rw-r--r--rutabaga_gfx/Cargo.toml12
-rw-r--r--rutabaga_gfx/build.rs7
-rw-r--r--rutabaga_gfx/ffi/Cargo.toml4
-rw-r--r--rutabaga_gfx/ffi/Makefile2
-rw-r--r--rutabaga_gfx/ffi/build.rs2
-rw-r--r--rutabaga_gfx/ffi/src/include/rutabaga_gfx_ffi.h6
-rw-r--r--rutabaga_gfx/ffi/src/lib.rs39
-rw-r--r--rutabaga_gfx/src/cross_domain/cross_domain_protocol.rs4
-rw-r--r--rutabaga_gfx/src/cross_domain/sys/mod.rs3
-rw-r--r--rutabaga_gfx/src/gfxstream.rs31
-rw-r--r--rutabaga_gfx/src/rutabaga_core.rs188
-rw-r--r--rutabaga_gfx/src/rutabaga_gralloc/minigbm.rs57
-rw-r--r--rutabaga_gfx/src/rutabaga_gralloc/minigbm_bindings.rs20
-rw-r--r--rutabaga_gfx/src/rutabaga_gralloc/mod.rs1
-rw-r--r--rutabaga_gfx/src/rutabaga_gralloc/rendernode.rs121
-rw-r--r--rutabaga_gfx/src/rutabaga_os/sys/mod.rs4
-rw-r--r--rutabaga_gfx/src/rutabaga_os/sys/windows/descriptor.rs16
-rw-r--r--src/crosvm/cmdline.rs143
-rw-r--r--src/crosvm/config.rs431
-rw-r--r--src/crosvm/gpu_config.rs8
-rw-r--r--src/crosvm/plugin/mod.rs6
-rw-r--r--src/crosvm/sys/linux.rs262
-rw-r--r--src/crosvm/sys/linux/config.rs141
-rw-r--r--src/crosvm/sys/linux/device_helpers.rs51
-rw-r--r--src/crosvm/sys/linux/gpu.rs27
-rw-r--r--src/crosvm/sys/linux/jail_warden.rs2
-rw-r--r--src/crosvm/sys/windows/broker.rs92
-rw-r--r--src/main.rs31
-rw-r--r--src/sys/windows.rs74
-rw-r--r--src/sys/windows/generic.rs5
-rw-r--r--src/sys/windows/main.rs6
-rw-r--r--swap/src/page_handler.rs2
-rw-r--r--swap/tests/main.rs789
l---------third_party/minigbm1
-rw-r--r--third_party/vmm_vhost/src/lib.rs3
-rw-r--r--third_party/vmm_vhost/src/master.rs18
-rw-r--r--third_party/vmm_vhost/src/master_req_handler.rs12
-rw-r--r--third_party/vmm_vhost/src/message.rs38
-rw-r--r--third_party/vmm_vhost/src/slave_proxy.rs6
-rw-r--r--third_party/vmm_vhost/src/slave_req_handler.rs6
-rw-r--r--tools/contrib/vcpu_blocker_analyzer/Cargo.lock556
-rw-r--r--tools/contrib/vcpu_blocker_analyzer/Cargo.toml22
-rw-r--r--tools/contrib/vcpu_blocker_analyzer/README.md119
-rw-r--r--tools/contrib/vcpu_blocker_analyzer/images/mechanism.pngbin0 -> 28296 bytes
-rw-r--r--tools/contrib/vcpu_blocker_analyzer/images/timer_defect.pngbin0 -> 40967 bytes
-rw-r--r--tools/contrib/vcpu_blocker_analyzer/src/main.rs542
-rw-r--r--tools/contrib/vcpu_blocker_analyzer/src/parse.rs261
-rwxr-xr-xtools/custom_checks1
-rw-r--r--tools/examples/example_simple.ps149
-rw-r--r--vm_control/src/api.rs22
-rw-r--r--vm_control/src/lib.rs53
-rw-r--r--vm_control/src/sys/linux.rs9
-rw-r--r--win_util/src/dpapi.rs181
-rw-r--r--win_util/src/lib.rs2
129 files changed, 5217 insertions, 1025 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 1717697c4..600c1c6d3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2325,7 +2325,7 @@ dependencies = [
[[package]]
name = "rutabaga_gfx"
-version = "0.1.2"
+version = "0.1.3"
dependencies = [
"anyhow",
"cfg-if",
diff --git a/Cargo.toml b/Cargo.toml
index c3b40091a..ec4521ee5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -205,6 +205,10 @@ gfxstream_stub = ["rutabaga_gfx/gfxstream_stub"]
## Enables 3D acceleration for the guest via the virglrenderer library over virtio-gpu.
virgl_renderer = ["devices/virgl_renderer"]
+# Enables the highly experimental vulkan graphics buffer allocator.
+# see rutabaga_gfx/Cargo.toml for instructions on building with enabled.
+vulkano = ["rutabaga_gfx/vulkano"]
+
#! ### Video features
#!
#! See [Video Device](https://crosvm.dev/book/devices/video.html) for more information.
@@ -237,6 +241,12 @@ trace_marker = ["cros_tracing/trace_marker"]
## Facilitate tracing all syscalls by sandboxed processes.
seccomp_trace = ["jail/seccomp_trace","base/seccomp_trace","devices/seccomp_trace"]
+## Enables virtio-gpu devices to request a non-coherent memory mapping from the
+## hypervisor. Currently only supported in KVM on x86 and requires kernel
+## patches from:
+## <https://lore.kernel.org/all/20240105091535.24760-1-yan.y.zhao@intel.com/>
+noncoherent-dma = ["devices/noncoherent-dma", "hypervisor/noncoherent-dma"]
+
#! ### Windows-specific feature flags
#!
#! These feature flags are only available on Windows builds of crosvm.
@@ -324,6 +334,7 @@ all-default = [
"gfxstream_stub",
"libvda-stub",
"net",
+ "noncoherent-dma",
"panic-memfd",
"pci-hotplug",
"power-monitor-powerd",
diff --git a/aarch64/src/lib.rs b/aarch64/src/lib.rs
index e47e61be3..d9cafc50f 100644
--- a/aarch64/src/lib.rs
+++ b/aarch64/src/lib.rs
@@ -50,6 +50,7 @@ use hypervisor::CpuConfigAArch64;
use hypervisor::DeviceKind;
use hypervisor::Hypervisor;
use hypervisor::HypervisorCap;
+use hypervisor::MemCacheType;
use hypervisor::ProtectionType;
use hypervisor::VcpuAArch64;
use hypervisor::VcpuFeature;
@@ -522,6 +523,7 @@ impl arch::LinuxArch for AArch64 {
Box::new(pvtime_mem),
false,
false,
+ MemCacheType::CacheCoherent,
)
.map_err(Error::MapPvtimeError)?;
}
@@ -650,6 +652,10 @@ impl arch::LinuxArch for AArch64 {
#[cfg(any(target_os = "android", target_os = "linux"))]
if !components.cpu_frequencies.is_empty() {
+ // TODO: Revisit and optimization after benchmarking
+ let socket = components
+ .virt_cpufreq_socket
+ .map(|s| Arc::new(Mutex::new(s)));
for vcpu in 0..vcpu_count {
let vcpu_affinity = match components.vcpu_affinity.clone() {
Some(VcpuAffinity::Global(v)) => v,
@@ -659,6 +665,7 @@ impl arch::LinuxArch for AArch64 {
let virt_cpufreq = Arc::new(Mutex::new(VirtCpufreq::new(
vcpu_affinity[0].try_into().unwrap(),
+ socket.clone(),
)));
if vcpu as u64 * AARCH64_VIRTFREQ_SIZE + AARCH64_VIRTFREQ_SIZE
diff --git a/arch/src/lib.rs b/arch/src/lib.rs
index f71455c22..6c2ac7df9 100644
--- a/arch/src/lib.rs
+++ b/arch/src/lib.rs
@@ -61,6 +61,7 @@ pub use fdt::DtbOverlay;
#[cfg(feature = "gdb")]
use gdbstub::arch::Arch;
use hypervisor::IoEventAddress;
+use hypervisor::MemCacheType;
use hypervisor::Vm;
#[cfg(windows)]
use jail::FakeMinijailStub as Minijail;
@@ -374,6 +375,11 @@ pub struct VmComponents {
pub swiotlb: Option<u64>,
pub vcpu_affinity: Option<VcpuAffinity>,
pub vcpu_count: usize,
+ #[cfg(all(
+ any(target_arch = "arm", target_arch = "aarch64"),
+ any(target_os = "android", target_os = "linux")
+ ))]
+ pub virt_cpufreq_socket: Option<std::os::unix::net::UnixStream>,
pub vm_image: VmImage,
}
@@ -1160,6 +1166,7 @@ pub fn generate_pci_root(
Box::new(mmap),
false,
false,
+ MemCacheType::CacheCoherent,
);
}
}
diff --git a/arch/src/pstore.rs b/arch/src/pstore.rs
index 5f108dff9..7fc06544c 100644
--- a/arch/src/pstore.rs
+++ b/arch/src/pstore.rs
@@ -8,6 +8,7 @@ use anyhow::bail;
use anyhow::Context;
use anyhow::Result;
use base::MemoryMappingBuilder;
+use hypervisor::MemCacheType;
use hypervisor::Vm;
use resources::AddressRange;
use vm_memory::GuestAddress;
@@ -52,6 +53,7 @@ pub fn create_memory_region(
Box::new(memory_mapping),
false,
false,
+ MemCacheType::CacheCoherent,
)
.context("failed to add pstore region")?;
diff --git a/base/src/sys/linux/mod.rs b/base/src/sys/linux/mod.rs
index 993fb3d90..532e70b2a 100644
--- a/base/src/sys/linux/mod.rs
+++ b/base/src/sys/linux/mod.rs
@@ -357,14 +357,13 @@ pub fn kill_process_group() -> Result<()> {
/// Spawns a pipe pair where the first pipe is the read end and the second pipe is the write end.
///
-/// If `close_on_exec` is true, the `O_CLOEXEC` flag will be set during pipe creation.
-pub fn pipe(close_on_exec: bool) -> Result<(File, File)> {
- let flags = if close_on_exec { O_CLOEXEC } else { 0 };
+/// The `O_CLOEXEC` flag will be set during pipe creation.
+pub fn pipe() -> Result<(File, File)> {
let mut pipe_fds = [-1; 2];
// SAFETY:
// Safe because pipe2 will only write 2 element array of i32 to the given pointer, and we check
// for error.
- let ret = unsafe { pipe2(&mut pipe_fds[0], flags) };
+ let ret = unsafe { pipe2(&mut pipe_fds[0], O_CLOEXEC) };
if ret == -1 {
errno_result()
} else {
@@ -395,7 +394,7 @@ pub fn set_pipe_size(fd: RawFd, size: usize) -> Result<usize> {
pub fn new_pipe_full() -> Result<(File, File)> {
use std::io::Write;
- let (rx, mut tx) = pipe(true)?;
+ let (rx, mut tx) = pipe()?;
// The smallest allowed size of a pipe is the system page size on linux.
let page_size = set_pipe_size(tx.as_raw_descriptor(), round_up_to_page_size(1))?;
diff --git a/base/src/sys/macos/mod.rs b/base/src/sys/macos/mod.rs
index 6c62ba73b..912ddf366 100644
--- a/base/src/sys/macos/mod.rs
+++ b/base/src/sys/macos/mod.rs
@@ -2,7 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+use std::fs::File;
+
+use crate::descriptor::FromRawDescriptor;
use crate::sys::unix::RawDescriptor;
+use crate::unix::set_descriptor_cloexec;
use crate::MmapError;
mod net;
@@ -351,3 +355,32 @@ pub(crate) use libc::pread;
pub(crate) use libc::preadv;
pub(crate) use libc::pwrite;
pub(crate) use libc::pwritev;
+
+/// Spawns a pipe pair where the first pipe is the read end and the second pipe is the write end.
+///
+/// The `O_CLOEXEC` flag will be applied after pipe creation.
+pub fn pipe() -> crate::errno::Result<(File, File)> {
+ let mut pipe_fds = [-1; 2];
+ // SAFETY:
+ // Safe because pipe will only write 2 element array of i32 to the given pointer, and we check
+ // for error.
+ let ret = unsafe { libc::pipe(pipe_fds.as_mut_ptr()) };
+ if ret == -1 {
+ return crate::errno::errno_result();
+ }
+
+ // SAFETY:
+ // Safe because both fds must be valid for pipe to have returned sucessfully and we have
+ // exclusive ownership of them.
+ let pipes = unsafe {
+ (
+ File::from_raw_descriptor(pipe_fds[0]),
+ File::from_raw_descriptor(pipe_fds[1]),
+ )
+ };
+
+ set_descriptor_cloexec(&pipes.0)?;
+ set_descriptor_cloexec(&pipes.1)?;
+
+ Ok(pipes)
+}
diff --git a/base/src/sys/unix/descriptor.rs b/base/src/sys/unix/descriptor.rs
index 67c0fac29..d044fc1bf 100644
--- a/base/src/sys/unix/descriptor.rs
+++ b/base/src/sys/unix/descriptor.rs
@@ -53,25 +53,37 @@ fn clone_fd(fd: &dyn AsRawFd) -> Result<RawFd> {
}
}
+/// Adds CLOEXEC flag on descriptor
+pub fn set_descriptor_cloexec<A: AsRawDescriptor>(fd_owner: &A) -> Result<()> {
+ modify_descriptor_flags(fd_owner.as_raw_descriptor(), |flags| {
+ flags | libc::FD_CLOEXEC
+ })
+}
+
/// Clears CLOEXEC flag on descriptor
pub fn clear_descriptor_cloexec<A: AsRawDescriptor>(fd_owner: &A) -> Result<()> {
- clear_fd_cloexec(&fd_owner.as_raw_descriptor())
+ modify_descriptor_flags(fd_owner.as_raw_descriptor(), |flags| {
+ flags & !libc::FD_CLOEXEC
+ })
}
-/// Clears CLOEXEC flag on fd
-fn clear_fd_cloexec<A: AsRawFd>(fd_owner: &A) -> Result<()> {
- let fd = fd_owner.as_raw_fd();
+/// Apply the specified modification to the file descriptor's flags.
+fn modify_descriptor_flags(
+ desc: RawDescriptor,
+ modify_flags: impl FnOnce(libc::c_int) -> libc::c_int,
+) -> Result<()> {
// SAFETY:
// Safe because fd is read only.
- let flags = unsafe { libc::fcntl(fd, libc::F_GETFD) };
+ let flags = unsafe { libc::fcntl(desc, libc::F_GETFD) };
if flags == -1 {
return errno_result();
}
- let masked_flags = flags & !libc::FD_CLOEXEC;
+ let new_flags = modify_flags(flags);
+
// SAFETY:
// Safe because this has no side effect(s) on the current process.
- if masked_flags != flags && unsafe { libc::fcntl(fd, libc::F_SETFD, masked_flags) } == -1 {
+ if new_flags != flags && unsafe { libc::fcntl(desc, libc::F_SETFD, new_flags) } == -1 {
errno_result()
} else {
Ok(())
diff --git a/base/src/sys/unix/file_flags.rs b/base/src/sys/unix/file_flags.rs
index 2ff767556..fad81f84f 100644
--- a/base/src/sys/unix/file_flags.rs
+++ b/base/src/sys/unix/file_flags.rs
@@ -49,7 +49,7 @@ mod tests {
#[test]
fn pipe_pair() {
- let (read_pipe, write_pipe) = pipe(true).unwrap();
+ let (read_pipe, write_pipe) = pipe().unwrap();
assert_eq!(FileFlags::from_file(&read_pipe).unwrap(), FileFlags::Read);
assert_eq!(FileFlags::from_file(&write_pipe).unwrap(), FileFlags::Write);
}
diff --git a/base/src/sys/windows/file_util.rs b/base/src/sys/windows/file_util.rs
index 3e44feaa1..f4a61b260 100644
--- a/base/src/sys/windows/file_util.rs
+++ b/base/src/sys/windows/file_util.rs
@@ -38,11 +38,7 @@ pub fn set_sparse_file<T: AsRawDescriptor>(handle: &T) -> io::Result<()> {
// Safe because we check the return value and handle is guaranteed to be a
// valid file handle by the caller.
let result = unsafe {
- super::ioctl::ioctl_with_ptr(
- handle,
- FSCTL_SET_SPARSE,
- std::ptr::null_mut() as *mut c_void,
- )
+ super::ioctl::ioctl_with_ptr(handle, FSCTL_SET_SPARSE, std::ptr::null_mut::<c_void>())
};
if result != 0 {
return Err(io::Error::from_raw_os_error(result));
diff --git a/cros_async/src/common_executor.rs b/cros_async/src/common_executor.rs
index 80841e410..862f466b3 100644
--- a/cros_async/src/common_executor.rs
+++ b/cros_async/src/common_executor.rs
@@ -99,7 +99,7 @@ impl<Re: Reactor> RawExecutor<Re> {
}
fn wake(&self) {
- let oldstate = self.state.swap(WOKEN, Ordering::Release);
+ let oldstate = self.state.swap(WOKEN, Ordering::AcqRel);
if oldstate == WAITING {
self.reactor.wake();
}
@@ -179,7 +179,7 @@ impl<Re: Reactor> RawExecutor<Re> {
let oldstate = self.state.compare_exchange(
PROCESSING,
WAITING,
- Ordering::Acquire,
+ Ordering::AcqRel,
Ordering::Acquire,
);
if let Err(oldstate) = oldstate {
diff --git a/cros_async/src/sys/linux/executor.rs b/cros_async/src/sys/linux/executor.rs
index 5c7fe342e..3df4786ed 100644
--- a/cros_async/src/sys/linux/executor.rs
+++ b/cros_async/src/sys/linux/executor.rs
@@ -94,7 +94,7 @@ use crate::IoSource;
/// # fn do_it() -> Result<(), Box<dyn Error>> {
/// let ex = Executor::new()?;
///
-/// let (rx, tx) = base::linux::pipe(true)?;
+/// let (rx, tx) = base::linux::pipe()?;
/// let zero = File::open("/dev/zero")?;
/// let zero_bytes = CHUNK_SIZE * 7;
/// let zero_to_pipe = transfer_data(
diff --git a/cros_async/src/sys/linux/fd_executor.rs b/cros_async/src/sys/linux/fd_executor.rs
index 77cbe59b3..84cf97e91 100644
--- a/cros_async/src/sys/linux/fd_executor.rs
+++ b/cros_async/src/sys/linux/fd_executor.rs
@@ -405,7 +405,7 @@ mod test {
#[test]
fn test_it() {
async fn do_test(ex: &Arc<RawExecutor<EpollReactor>>) {
- let (r, _w) = base::pipe(true).unwrap();
+ let (r, _w) = base::pipe().unwrap();
let done = Box::pin(async { 5usize });
let source = RegisteredSource::new(ex, r).unwrap();
let pending = source.wait_readable().unwrap();
@@ -448,7 +448,7 @@ mod test {
}
}
- let (mut rx, tx) = base::pipe(true).expect("Pipe failed");
+ let (mut rx, tx) = base::pipe().expect("Pipe failed");
let ex = RawExecutor::<EpollReactor>::new().unwrap();
diff --git a/cros_async/src/sys/linux/poll_source.rs b/cros_async/src/sys/linux/poll_source.rs
index faecaa601..86f12b10c 100644
--- a/cros_async/src/sys/linux/poll_source.rs
+++ b/cros_async/src/sys/linux/poll_source.rs
@@ -390,7 +390,7 @@ mod tests {
let _ = source.wait_readable().await;
}
- let (rx, _tx) = base::pipe(true).unwrap();
+ let (rx, _tx) = base::pipe().unwrap();
let ex = RawExecutor::<EpollReactor>::new().unwrap();
let source = PollSource::new(rx, &ex).unwrap();
ex.spawn_local(owns_poll_source(source)).detach();
diff --git a/cros_async/src/sys/linux/uring_executor.rs b/cros_async/src/sys/linux/uring_executor.rs
index 06211b46b..8f7d21d84 100644
--- a/cros_async/src/sys/linux/uring_executor.rs
+++ b/cros_async/src/sys/linux/uring_executor.rs
@@ -925,7 +925,7 @@ mod tests {
Arc::new(VecIoWrapper::from(vec![0u8; 4096])) as Arc<dyn BackingMemory + Send + Sync>;
// Use pipes to create a future that will block forever.
- let (rx, mut tx) = base::pipe(true).unwrap();
+ let (rx, mut tx) = base::pipe().unwrap();
// Set up the TLS for the uring_executor by creating one.
let ex = RawExecutor::<UringReactor>::new().unwrap();
@@ -1026,7 +1026,7 @@ mod tests {
let bm =
Arc::new(VecIoWrapper::from(vec![0u8; 16])) as Arc<dyn BackingMemory + Send + Sync>;
- let (rx, tx) = base::pipe(true).expect("Pipe failed");
+ let (rx, tx) = base::pipe().expect("Pipe failed");
let ex = RawExecutor::<UringReactor>::new().unwrap();
@@ -1074,7 +1074,7 @@ mod tests {
}
}
- let (mut rx, mut tx) = base::pipe(true).expect("Pipe failed");
+ let (mut rx, mut tx) = base::pipe().expect("Pipe failed");
let ex = RawExecutor::<UringReactor>::new().unwrap();
@@ -1180,7 +1180,7 @@ mod tests {
// Leave an uncompleted operation in the queue so that the drop impl will try to drive it to
// completion.
- let (_rx, tx) = base::pipe(true).expect("Pipe failed");
+ let (_rx, tx) = base::pipe().expect("Pipe failed");
let tx = ex
.reactor
.register_source(&ex, &tx)
diff --git a/cros_async/src/sys/linux/uring_source.rs b/cros_async/src/sys/linux/uring_source.rs
index 957f1eb17..7ba5ea465 100644
--- a/cros_async/src/sys/linux/uring_source.rs
+++ b/cros_async/src/sys/linux/uring_source.rs
@@ -269,7 +269,7 @@ mod tests {
use futures::future::Either;
async fn do_test(ex: &Arc<RawExecutor<UringReactor>>) {
- let (read_source, mut w) = base::pipe(true).unwrap();
+ let (read_source, mut w) = base::pipe().unwrap();
let source = UringSource::new(read_source, ex).unwrap();
let done = Box::pin(async { 5usize });
let pending = Box::pin(read_u64(&source));
diff --git a/cros_async/src/sys/windows/executor.rs b/cros_async/src/sys/windows/executor.rs
index f5c5bdfe0..3f5c53894 100644
--- a/cros_async/src/sys/windows/executor.rs
+++ b/cros_async/src/sys/windows/executor.rs
@@ -88,7 +88,7 @@ pub const DEFAULT_IO_CONCURRENCY: u32 = 1;
/// # fn do_it() -> Result<(), Box<dyn Error>> {
/// let ex = Executor::new()?;
///
-/// let (rx, tx) = base::pipe(true)?;
+/// let (rx, tx) = base::pipe()?;
/// let zero = File::open("/dev/zero")?;
/// let zero_bytes = CHUNK_SIZE * 7;
/// let zero_to_pipe = transfer_data(
diff --git a/devices/Cargo.toml b/devices/Cargo.toml
index 90a00d766..a6e79c3e8 100644
--- a/devices/Cargo.toml
+++ b/devices/Cargo.toml
@@ -30,6 +30,7 @@ seccomp_trace = []
swap = ["swap/enable"]
whpx = []
pci-hotplug = []
+noncoherent-dma = []
[dependencies]
argh = "0.1.7"
diff --git a/devices/src/pci/coiommu.rs b/devices/src/pci/coiommu.rs
index 2db44e8d7..d58e6c06f 100644
--- a/devices/src/pci/coiommu.rs
+++ b/devices/src/pci/coiommu.rs
@@ -49,6 +49,7 @@ use base::TubeError;
use base::WaitContext;
use base::WorkerThread;
use hypervisor::Datamatch;
+use hypervisor::MemCacheType;
use resources::Alloc;
use resources::AllocOptions;
use resources::SystemAllocator;
@@ -1139,6 +1140,7 @@ impl CoIommuDev {
},
VmMemoryDestination::GuestPhysicalAddress(gpa),
prot,
+ MemCacheType::CacheCoherent,
)
.context("register_mmap register_memory failed")?;
Ok(())
diff --git a/devices/src/pci/pci_root.rs b/devices/src/pci/pci_root.rs
index 9744c8125..a029bae9a 100644
--- a/devices/src/pci/pci_root.rs
+++ b/devices/src/pci/pci_root.rs
@@ -19,6 +19,7 @@ use base::RawDescriptor;
use base::SendTube;
use base::SharedMemory;
use base::VmEventType;
+use hypervisor::MemCacheType;
use hypervisor::Vm;
use resources::SystemAllocator;
use serde::Deserialize;
@@ -599,8 +600,14 @@ impl<T: Vm> PciMmioMapper for T {
.protection(Protection::read())
.build()
.context("failed to map shmem")?;
- self.add_memory_region(addr, Box::new(mapping), true, false)
- .context("failed to create vm mapping")
+ self.add_memory_region(
+ addr,
+ Box::new(mapping),
+ true,
+ false,
+ MemCacheType::CacheCoherent,
+ )
+ .context("failed to create vm mapping")
}
}
diff --git a/devices/src/pci/vfio_pci.rs b/devices/src/pci/vfio_pci.rs
index b6dd21356..94cfd7e75 100644
--- a/devices/src/pci/vfio_pci.rs
+++ b/devices/src/pci/vfio_pci.rs
@@ -28,6 +28,7 @@ use base::RawDescriptor;
use base::Tube;
use base::WaitContext;
use base::WorkerThread;
+use hypervisor::MemCacheType;
use resources::AddressRange;
use resources::Alloc;
use resources::AllocOptions;
@@ -684,6 +685,7 @@ pub struct VfioPciDevice {
activated: bool,
acpi_notifier_val: Arc<Mutex<Vec<u32>>>,
gpe: Option<u32>,
+ base_class_code: PciClassCode,
}
impl VfioPciDevice {
@@ -731,6 +733,8 @@ impl VfioPciDevice {
let mut cap_next: u32 = config.read_config::<u8>(PCI_CAPABILITY_LIST).into();
let vendor_id: u16 = config.read_config(PCI_VENDOR_ID);
let device_id: u16 = config.read_config(PCI_DEVICE_ID);
+ let base_class_code = PciClassCode::try_from(config.read_config::<u8>(PCI_BASE_CLASS_CODE))
+ .unwrap_or(PciClassCode::Other);
let pci_id = PciId::new(vendor_id, device_id);
@@ -811,10 +815,8 @@ impl VfioPciDevice {
ext_caps.reverse();
}
- let class_code: u8 = config.read_config(PCI_BASE_CLASS_CODE);
-
- let is_intel_gfx = vendor_id == PCI_VENDOR_ID_INTEL
- && class_code == PciClassCode::DisplayController.get_register_value();
+ let is_intel_gfx =
+ base_class_code == PciClassCode::DisplayController && vendor_id == PCI_VENDOR_ID_INTEL;
let device_data = if is_intel_gfx {
Some(DeviceData::IntelGfxData {
opregion_index: u32::max_value(),
@@ -851,6 +853,7 @@ impl VfioPciDevice {
activated: false,
acpi_notifier_val: Arc::new(Mutex::new(Vec::new())),
gpe: None,
+ base_class_code,
})
}
@@ -859,16 +862,12 @@ impl VfioPciDevice {
self.pci_address
}
- fn is_intel_gfx(&self) -> bool {
- let mut ret = false;
-
- if let Some(device_data) = &self.device_data {
- match *device_data {
- DeviceData::IntelGfxData { .. } => ret = true,
- }
- }
+ pub fn is_gfx(&self) -> bool {
+ self.base_class_code == PciClassCode::DisplayController
+ }
- ret
+ fn is_intel_gfx(&self) -> bool {
+ matches!(self.device_data, Some(DeviceData::IntelGfxData { .. }))
}
fn enable_acpi_notification(&mut self) -> Result<(), PciDeviceError> {
@@ -1182,6 +1181,7 @@ impl VfioPciDevice {
},
VmMemoryDestination::GuestPhysicalAddress(guest_map_start),
Protection::read_write(),
+ MemCacheType::CacheCoherent,
) {
Ok(id) => {
mmaps_ids.push(id);
diff --git a/devices/src/platform/vfio_platform.rs b/devices/src/platform/vfio_platform.rs
index 723640dfc..9f15707ac 100644
--- a/devices/src/platform/vfio_platform.rs
+++ b/devices/src/platform/vfio_platform.rs
@@ -23,6 +23,7 @@ use base::MemoryMappingBuilder;
use base::MemoryMappingBuilderWindows;
use base::Protection;
use base::RawDescriptor;
+use hypervisor::MemCacheType;
use hypervisor::Vm;
use resources::SystemAllocator;
use vfio_sys::*;
@@ -214,7 +215,13 @@ impl VfioPlatformDevice {
let host = mmap.as_ptr();
let guest_addr = GuestAddress(guest_map_start);
- if let Err(e) = vm.add_memory_region(guest_addr, Box::new(mmap), false, false) {
+ if let Err(e) = vm.add_memory_region(
+ guest_addr,
+ Box::new(mmap),
+ false,
+ false,
+ MemCacheType::CacheCoherent,
+ ) {
error!("{e}, index: {index}, guest_addr:{guest_addr}, host:{host:?}");
break;
}
@@ -258,6 +265,7 @@ impl VfioPlatformDevice {
},
VmMemoryDestination::GuestPhysicalAddress(guest_map_start),
Protection::read_write(),
+ MemCacheType::CacheCoherent,
) {
Ok(_region) => {
// Even if vm has mapped this region, but it is in vm main process,
diff --git a/devices/src/virtio/gpu/mod.rs b/devices/src/virtio/gpu/mod.rs
index 15f063fd4..9d0b3f82b 100644
--- a/devices/src/virtio/gpu/mod.rs
+++ b/devices/src/virtio/gpu/mod.rs
@@ -243,6 +243,7 @@ fn build(
rutabaga: Rutabaga,
mapper: Arc<Mutex<Option<Box<dyn SharedMemoryMapper>>>>,
external_blob: bool,
+ fixed_blob_mapping: bool,
#[cfg(windows)] wndproc_thread: &mut Option<WindowProcedureThread>,
udmabuf: bool,
#[cfg(windows)] gpu_display_wait_descriptor_ctrl_wr: SendTube,
@@ -280,6 +281,7 @@ fn build(
rutabaga,
mapper,
external_blob,
+ fixed_blob_mapping,
udmabuf,
)
}
@@ -1172,6 +1174,7 @@ pub struct Gpu {
pci_address: Option<PciAddress>,
pci_bar_size: u64,
external_blob: bool,
+ fixed_blob_mapping: bool,
rutabaga_component: RutabagaComponentType,
#[cfg(windows)]
wndproc_thread: Option<WindowProcedureThread>,
@@ -1283,6 +1286,7 @@ impl Gpu {
pci_address: gpu_parameters.pci_address,
pci_bar_size: gpu_parameters.pci_bar_size,
external_blob: gpu_parameters.external_blob,
+ fixed_blob_mapping: gpu_parameters.fixed_blob_mapping,
rutabaga_component: component,
#[cfg(windows)]
wndproc_thread: Some(wndproc_thread),
@@ -1327,6 +1331,7 @@ impl Gpu {
rutabaga,
mapper,
self.external_blob,
+ self.fixed_blob_mapping,
#[cfg(windows)]
&mut self.wndproc_thread,
self.udmabuf,
@@ -1371,6 +1376,7 @@ impl Gpu {
let display_event = self.display_event.clone();
let event_devices = self.event_devices.take().expect("missing event_devices");
let external_blob = self.external_blob;
+ let fixed_blob_mapping = self.fixed_blob_mapping;
let udmabuf = self.udmabuf;
let fence_state = Arc::new(Mutex::new(Default::default()));
@@ -1436,6 +1442,7 @@ impl Gpu {
rutabaga,
mapper,
external_blob,
+ fixed_blob_mapping,
#[cfg(windows)]
&mut wndproc_thread,
udmabuf,
@@ -1765,7 +1772,8 @@ impl VirtioDevice for Gpu {
}
fn expose_shmem_descriptors_with_viommu(&self) -> bool {
- true
+ // TODO(b/323368701): integrate with fixed_blob_mapping so this can always return true.
+ !self.fixed_blob_mapping
}
// Notes on sleep/wake/snapshot/restore functionality.
diff --git a/devices/src/virtio/gpu/parameters.rs b/devices/src/virtio/gpu/parameters.rs
index c36789f4d..526f133ba 100644
--- a/devices/src/virtio/gpu/parameters.rs
+++ b/devices/src/virtio/gpu/parameters.rs
@@ -69,6 +69,9 @@ pub struct GpuParameters {
// enforce that blob resources MUST be exportable as file descriptors
pub external_blob: bool,
pub system_blob: bool,
+ // enable use of descriptor mapping to fixed host VA within a prepared vMMU mapping (e.g. kvm
+ // user memslot)
+ pub fixed_blob_mapping: bool,
#[serde(rename = "implicit-render-server")]
pub allow_implicit_render_server_exec: bool,
}
@@ -94,6 +97,8 @@ impl Default for GpuParameters {
capset_mask: 0,
external_blob: false,
system_blob: false,
+ // TODO(b/324649619): not yet fully compatible with other platforms (windows)
+ fixed_blob_mapping: cfg!(target_os = "linux"),
allow_implicit_render_server_exec: false,
}
}
diff --git a/devices/src/virtio/gpu/virtio_gpu.rs b/devices/src/virtio/gpu/virtio_gpu.rs
index 945750879..e1d0e3317 100644
--- a/devices/src/virtio/gpu/virtio_gpu.rs
+++ b/devices/src/virtio/gpu/virtio_gpu.rs
@@ -21,6 +21,7 @@ use base::Protection;
use base::SafeDescriptor;
use base::VolatileSlice;
use gpu_display::*;
+use hypervisor::MemCacheType;
use libc::c_void;
use rutabaga_gfx::ResourceCreate3D;
use rutabaga_gfx::ResourceCreateBlob;
@@ -38,6 +39,7 @@ use rutabaga_gfx::RUTABAGA_MAP_ACCESS_MASK;
use rutabaga_gfx::RUTABAGA_MAP_ACCESS_READ;
use rutabaga_gfx::RUTABAGA_MAP_ACCESS_RW;
use rutabaga_gfx::RUTABAGA_MAP_ACCESS_WRITE;
+use rutabaga_gfx::RUTABAGA_MAP_CACHE_CACHED;
use rutabaga_gfx::RUTABAGA_MAP_CACHE_MASK;
use rutabaga_gfx::RUTABAGA_MEM_HANDLE_TYPE_DMABUF;
use rutabaga_gfx::RUTABAGA_MEM_HANDLE_TYPE_OPAQUE_FD;
@@ -284,15 +286,12 @@ impl VirtioGpuScanout {
let surface_id = display.create_surface(
self.parent_surface_id,
+ self.scanout_id,
self.width,
self.height,
self.scanout_type,
)?;
- if let Some(scanout_id) = self.scanout_id {
- display.set_scanout_id(surface_id, scanout_id)?;
- }
-
self.surface_id = Some(surface_id);
Ok(OkNoData)
@@ -425,6 +424,7 @@ pub struct VirtioGpu {
rutabaga: Rutabaga,
resources: Map<u32, VirtioGpuResource>,
external_blob: bool,
+ fixed_blob_mapping: bool,
udmabuf_driver: Option<UdmabufDriver>,
}
@@ -498,6 +498,7 @@ impl VirtioGpu {
rutabaga: Rutabaga,
mapper: Arc<Mutex<Option<Box<dyn SharedMemoryMapper>>>>,
external_blob: bool,
+ fixed_blob_mapping: bool,
udmabuf: bool,
) -> Option<VirtioGpu> {
let mut udmabuf_driver = None;
@@ -530,6 +531,7 @@ impl VirtioGpu {
rutabaga,
resources: Default::default(),
external_blob,
+ fixed_blob_mapping,
udmabuf_driver,
})
}
@@ -1017,9 +1019,10 @@ impl VirtioGpu {
}
}
- // fallback to ExternalMapping via rutabaga if sandboxing (hence external_blob) is disabled.
+ // fallback to ExternalMapping via rutabaga if sandboxing (hence external_blob) and fixed
+ // mapping are both disabled as neither is currently compatible.
if source.is_none() {
- if self.external_blob {
+ if self.external_blob || self.fixed_blob_mapping {
return Err(ErrUnspec);
}
@@ -1039,11 +1042,19 @@ impl VirtioGpu {
_ => return Err(ErrUnspec),
};
+ let cache = if cfg!(feature = "noncoherent-dma")
+ && map_info & RUTABAGA_MAP_CACHE_MASK != RUTABAGA_MAP_CACHE_CACHED
+ {
+ MemCacheType::CacheNonCoherent
+ } else {
+ MemCacheType::CacheCoherent
+ };
+
self.mapper
.lock()
.as_mut()
.expect("No backend request connection found")
- .add_mapping(source.unwrap(), offset, prot)
+ .add_mapping(source.unwrap(), offset, prot, cache)
.map_err(|_| ErrUnspec)?;
resource.shmem_offset = Some(offset);
@@ -1235,7 +1246,7 @@ impl VirtioGpu {
rutabaga: {
let mut buffer = std::io::Cursor::new(Vec::new());
self.rutabaga
- .snapshot(&mut buffer)
+ .snapshot(&mut buffer, "")
.context("failed to snapshot rutabaga")?;
buffer.into_inner()
},
@@ -1275,7 +1286,7 @@ impl VirtioGpu {
)?;
self.rutabaga
- .restore(&mut &snapshot.rutabaga[..])
+ .restore(&mut &snapshot.rutabaga[..], "")
.context("failed to restore rutabaga")?;
for (id, s) in snapshot.resources.into_iter() {
diff --git a/devices/src/virtio/net.rs b/devices/src/virtio/net.rs
index 6d532ed2f..6fc30530b 100644
--- a/devices/src/virtio/net.rs
+++ b/devices/src/virtio/net.rs
@@ -54,6 +54,7 @@ use super::Interrupt;
use super::Queue;
use super::Reader;
use super::VirtioDevice;
+use crate::PciAddress;
/// The maximum buffer size when segmentation offload is enabled. This
/// includes the 12-byte virtio net header.
@@ -205,6 +206,7 @@ pub struct NetParameters {
pub vhost_net: Option<VhostNetParameters>,
#[serde(default)]
pub packed_queue: bool,
+ pub pci_address: Option<PciAddress>,
}
impl FromStr for NetParameters {
@@ -482,6 +484,7 @@ pub struct Net<T: TapT + ReadNotifier + 'static> {
avail_features: u64,
acked_features: u64,
mtu: u16,
+ pci_address: Option<PciAddress>,
#[cfg(windows)]
slirp_kill_evt: Option<Event>,
}
@@ -504,6 +507,7 @@ where
vq_pairs: u16,
mac_addr: Option<MacAddress>,
use_packed_queue: bool,
+ pci_address: Option<PciAddress>,
) -> Result<Net<T>, NetError> {
let taps = tap.into_mq_taps(vq_pairs).map_err(NetError::TapOpen)?;
@@ -550,6 +554,7 @@ where
avail_features,
mtu,
mac_addr,
+ pci_address,
#[cfg(windows)]
None,
)
@@ -560,6 +565,7 @@ where
avail_features: u64,
mtu: u16,
mac_addr: Option<MacAddress>,
+ pci_address: Option<PciAddress>,
#[cfg(windows)] slirp_kill_evt: Option<Event>,
) -> Result<Self, NetError> {
let net = Self {
@@ -570,6 +576,7 @@ where
avail_features,
acked_features: 0u64,
mtu,
+ pci_address,
#[cfg(windows)]
slirp_kill_evt: None,
};
@@ -739,6 +746,10 @@ where
Ok(())
}
+ fn pci_address(&self) -> Option<PciAddress> {
+ self.pci_address
+ }
+
fn virtio_sleep(&mut self) -> anyhow::Result<Option<BTreeMap<usize, Queue>>> {
if self.worker_threads.is_empty() {
return Ok(None);
@@ -852,7 +863,8 @@ mod tests {
tap_name: "tap".to_string(),
mac: None
},
- packed_queue: false
+ packed_queue: false,
+ pci_address: None,
}
);
@@ -867,7 +879,8 @@ mod tests {
tap_name: "tap".to_string(),
mac: Some(MacAddress::from_str("3d:70:eb:61:1a:91").unwrap())
},
- packed_queue: false
+ packed_queue: false,
+ pci_address: None,
}
);
@@ -883,6 +896,7 @@ mod tests {
mac: None
},
packed_queue: false,
+ pci_address: None,
}
);
@@ -897,7 +911,8 @@ mod tests {
tap_fd: 12,
mac: Some(MacAddress::from_str("3d:70:eb:61:1a:91").unwrap())
},
- packed_queue: false
+ packed_queue: false,
+ pci_address: None,
}
);
@@ -916,10 +931,34 @@ mod tests {
netmask: Ipv4Addr::from_str("255.255.255.0").unwrap(),
mac: MacAddress::from_str("3d:70:eb:61:1a:91").unwrap(),
},
- packed_queue: false
+ packed_queue: false,
+ pci_address: None,
+ }
+ );
+
+ let params = from_net_arg("tap-fd=12,pci-address=00:01.1").unwrap();
+ assert_eq!(
+ params,
+ NetParameters {
+ #[cfg(any(target_os = "android", target_os = "linux"))]
+ vhost_net: None,
+ vq_pairs: None,
+ mode: NetParametersMode::TapFd {
+ tap_fd: 12,
+ mac: None,
+ },
+ packed_queue: false,
+ pci_address: Some(PciAddress {
+ bus: 0,
+ dev: 1,
+ func: 1,
+ }),
}
);
+ // wrong pci format
+ assert!(from_net_arg("tap-fd=12,pci-address=hello").is_err());
+
// missing netmask
assert!(from_net_arg("host-ip=\"192.168.10.1\",mac=\"3d:70:eb:61:1a:91\"").is_err());
@@ -949,7 +988,8 @@ mod tests {
netmask: Ipv4Addr::from_str("255.255.255.0").unwrap(),
mac: MacAddress::from_str("3d:70:eb:61:1a:91").unwrap(),
},
- packed_queue: false
+ packed_queue: false,
+ pci_address: None,
}
);
@@ -963,7 +1003,8 @@ mod tests {
tap_fd: 3,
mac: None
},
- packed_queue: false
+ packed_queue: false,
+ pci_address: None,
}
);
@@ -977,7 +1018,8 @@ mod tests {
tap_name: "crosvm_tap".to_owned(),
mac: None
},
- packed_queue: false
+ packed_queue: false,
+ pci_address: None,
}
);
@@ -992,7 +1034,8 @@ mod tests {
tap_name: "crosvm_tap".to_owned(),
mac: Some(MacAddress::from_str("3d:70:eb:61:1a:91").unwrap())
},
- packed_queue: false
+ packed_queue: false,
+ pci_address: None,
}
);
@@ -1007,7 +1050,8 @@ mod tests {
tap_name: "tap".to_string(),
mac: None
},
- packed_queue: true
+ packed_queue: true,
+ pci_address: None,
}
);
@@ -1022,7 +1066,27 @@ mod tests {
tap_name: "tap".to_string(),
mac: None
},
- packed_queue: true
+ packed_queue: true,
+ pci_address: None,
+ }
+ );
+
+ let params = from_net_arg("vhost-net,tap-name=crosvm_tap,pci-address=00:01.1").unwrap();
+ assert_eq!(
+ params,
+ NetParameters {
+ vhost_net: Some(Default::default()),
+ vq_pairs: None,
+ mode: NetParametersMode::TapName {
+ tap_name: "crosvm_tap".to_owned(),
+ mac: None,
+ },
+ packed_queue: false,
+ pci_address: Some(PciAddress {
+ bus: 0,
+ dev: 1,
+ func: 1,
+ }),
}
);
diff --git a/devices/src/virtio/snd/common_backend/stream_info.rs b/devices/src/virtio/snd/common_backend/stream_info.rs
index d4132fc13..495f230f0 100644
--- a/devices/src/virtio/snd/common_backend/stream_info.rs
+++ b/devices/src/virtio/snd/common_backend/stream_info.rs
@@ -106,7 +106,7 @@ pub struct StreamInfo {
#[cfg(windows)]
pub(crate) playback_stream_cache: Option<(
Arc<AsyncRwLock<Box<dyn audio_streams::AsyncPlaybackBufferStream>>>,
- Arc<AsyncRwLock<Box<dyn PlaybackBufferWriter>>>,
+ Rc<AsyncRwLock<Box<dyn PlaybackBufferWriter>>>,
)>,
}
diff --git a/devices/src/virtio/snd/sys/windows.rs b/devices/src/virtio/snd/sys/windows.rs
index 7196a7d10..983bf8f87 100644
--- a/devices/src/virtio/snd/sys/windows.rs
+++ b/devices/src/virtio/snd/sys/windows.rs
@@ -6,6 +6,7 @@
// will be in win_audio now
use std::io;
use std::io::Read;
+use std::rc::Rc;
use std::slice;
use std::sync::Arc;
@@ -56,7 +57,7 @@ pub(crate) type SysBufferReader = WinBufferReader;
pub struct SysDirectionOutput {
pub async_playback_buffer_stream:
Arc<AsyncRwLock<Box<dyn audio_streams::AsyncPlaybackBufferStream>>>,
- pub buffer_writer: Arc<AsyncRwLock<Box<dyn PlaybackBufferWriter>>>,
+ pub buffer_writer: Rc<AsyncRwLock<Box<dyn PlaybackBufferWriter>>>,
}
pub(crate) struct SysAsyncStreamObjects {
@@ -160,7 +161,7 @@ impl StreamInfo {
self.playback_stream_cache = Some((
Arc::new(AsyncRwLock::new(async_playback_buffer_stream)),
- Arc::new(AsyncRwLock::new(Box::new(buffer_writer))),
+ Rc::new(AsyncRwLock::new(Box::new(buffer_writer))),
));
}
let playback_stream_cache = self
diff --git a/devices/src/virtio/vhost/net.rs b/devices/src/virtio/vhost/net.rs
index cf44a0d99..08b67bbd4 100644
--- a/devices/src/virtio/vhost/net.rs
+++ b/devices/src/virtio/vhost/net.rs
@@ -34,6 +34,7 @@ use crate::virtio::DeviceType;
use crate::virtio::Interrupt;
use crate::virtio::Queue;
use crate::virtio::VirtioDevice;
+use crate::PciAddress;
const QUEUE_SIZE: u16 = 256;
const NUM_QUEUES: usize = 2;
@@ -49,6 +50,7 @@ pub struct Net<T: TapT + 'static, U: VhostNetT<T> + 'static> {
acked_features: u64,
request_tube: Tube,
response_tube: Option<Tube>,
+ pci_address: Option<PciAddress>,
}
impl<T, U> Net<T, U>
@@ -64,6 +66,7 @@ where
tap: T,
mac_addr: Option<MacAddress>,
use_packed_queue: bool,
+ pci_address: Option<PciAddress>,
) -> Result<Net<T, U>> {
// Set offload flags to match the virtio features below.
tap.set_offload(
@@ -112,6 +115,7 @@ where
acked_features: 0u64,
request_tube,
response_tube: Some(response_tube),
+ pci_address,
})
}
}
@@ -247,6 +251,10 @@ where
Ok(())
}
+ fn pci_address(&self) -> Option<PciAddress> {
+ self.pci_address
+ }
+
fn on_device_sandboxed(&mut self) {
// ignore the error but to log the error. We don't need to do
// anything here because when activate, the other vhost set up
@@ -361,8 +369,15 @@ pub mod tests {
tap.enable().unwrap();
let features = base_features(ProtectionType::Unprotected);
- Net::<FakeTap, FakeNet<FakeTap>>::new(&PathBuf::from(""), features, tap, Some(mac), false)
- .unwrap()
+ Net::<FakeTap, FakeNet<FakeTap>>::new(
+ &PathBuf::from(""),
+ features,
+ tap,
+ Some(mac),
+ false,
+ None,
+ )
+ .unwrap()
}
#[test]
diff --git a/devices/src/virtio/vhost/user/device/gpu.rs b/devices/src/virtio/vhost/user/device/gpu.rs
index ed7e3fa49..c7e1c2fd1 100644
--- a/devices/src/virtio/vhost/user/device/gpu.rs
+++ b/devices/src/virtio/vhost/user/device/gpu.rs
@@ -153,60 +153,72 @@ impl VhostUserBackend for GpuBackend {
self.stop_queue(idx)?;
}
- match idx {
- // ctrl queue.
- 0 => {}
- // We don't currently handle the cursor queue.
- 1 => return Ok(()),
- _ => bail!("attempted to start unknown queue: {}", idx),
- }
-
- let kick_evt = queue
- .event()
- .try_clone()
- .context("failed to clone queue event")?;
- let kick_evt = EventAsync::new(kick_evt, &self.ex)
- .context("failed to create EventAsync for kick_evt")?;
-
+ // Create a refcounted queue. The GPU control queue uses a SharedReader which allows us to
+ // handle fences in the RutabagaFenceHandler, and also handle queue messages in
+ // `run_ctrl_queue`.
+ // For the cursor queue, we still create the refcounted queue to support retrieving the
+ // queue for snapshotting (but don't handle any messages).
let queue = Arc::new(Mutex::new(queue));
- let reader = SharedReader {
- queue: queue.clone(),
- doorbell: doorbell.clone(),
- };
- let state = if let Some(s) = self.state.as_ref() {
- s.clone()
- } else {
- let fence_handler_resources =
- Arc::new(Mutex::new(Some(gpu::FenceHandlerActivationResources {
- mem: mem.clone(),
- ctrl_queue: reader.clone(),
- })));
- let fence_handler =
- gpu::create_fence_handler(fence_handler_resources, self.fence_state.clone());
-
- let state = Rc::new(RefCell::new(
- self.gpu
- .borrow_mut()
- .initialize_frontend(
+ // Spawn a worker for the queue.
+ let queue_task = match idx {
+ 0 => {
+ // Set up worker for the control queue.
+ let kick_evt = queue
+ .lock()
+ .event()
+ .try_clone()
+ .context("failed to clone queue event")?;
+ let kick_evt = EventAsync::new(kick_evt, &self.ex)
+ .context("failed to create EventAsync for kick_evt")?;
+ let reader = SharedReader {
+ queue: queue.clone(),
+ doorbell: doorbell.clone(),
+ };
+
+ let state = if let Some(s) = self.state.as_ref() {
+ s.clone()
+ } else {
+ let fence_handler_resources =
+ Arc::new(Mutex::new(Some(gpu::FenceHandlerActivationResources {
+ mem: mem.clone(),
+ ctrl_queue: reader.clone(),
+ })));
+ let fence_handler = gpu::create_fence_handler(
+ fence_handler_resources,
self.fence_state.clone(),
- fence_handler,
- Arc::clone(&self.shmem_mapper),
- )
- .ok_or_else(|| anyhow!("failed to initialize gpu frontend"))?,
- ));
- self.state = Some(state.clone());
- state
+ );
+
+ let state = Rc::new(RefCell::new(
+ self.gpu
+ .borrow_mut()
+ .initialize_frontend(
+ self.fence_state.clone(),
+ fence_handler,
+ Arc::clone(&self.shmem_mapper),
+ )
+ .ok_or_else(|| anyhow!("failed to initialize gpu frontend"))?,
+ ));
+ self.state = Some(state.clone());
+ state
+ };
+
+ // Start handling platform-specific workers.
+ self.start_platform_workers(doorbell)?;
+
+ // Start handling the control queue.
+ self.ex
+ .spawn_local(run_ctrl_queue(reader, mem, kick_evt, state))
+ }
+ 1 => {
+ // For the cursor queue, spawn an empty worker, as we don't process it at all.
+ // We don't handle the cursor queue because no current users of vhost-user GPU pass
+ // any messages on it.
+ self.ex.spawn_local(async {})
+ }
+ _ => bail!("attempted to start unknown queue: {}", idx),
};
- // Start handling platform-specific workers.
- self.start_platform_workers(doorbell)?;
-
- // Start handling the control queue.
- let queue_task = self
- .ex
- .spawn_local(run_ctrl_queue(reader, mem, kick_evt, state));
-
self.queue_workers[idx] = Some(WorkerState { queue_task, queue });
Ok(())
}
@@ -216,8 +228,16 @@ impl VhostUserBackend for GpuBackend {
// Wait for queue_task to be aborted.
let _ = self.ex.run_until(worker.queue_task.cancel());
- // Valid as the GPU device has a single Queue, so clearing the state here is ok.
- self.state = None;
+ if idx == 0 {
+ // Stop the non-queue workers if this is the control queue (where we start them).
+ self.stop_non_queue_workers()?;
+
+ // After we stop all workers, we have only one reference left to self.state.
+ // Clearing it allows the GPU state to be destroyed, which gets rid of the
+ // remaining control queue reference from RutabagaFenceHandler.
+ // This allows our worker.queue to be recovered as it has no further references.
+ self.state = None;
+ }
let queue = match Arc::try_unwrap(worker.queue) {
Ok(queue_mutex) => queue_mutex.into_inner(),
diff --git a/devices/src/virtio/vhost/user/device/handler.rs b/devices/src/virtio/vhost/user/device/handler.rs
index ea0026e94..d7f3942c2 100644
--- a/devices/src/virtio/vhost/user/device/handler.rs
+++ b/devices/src/virtio/vhost/user/device/handler.rs
@@ -68,6 +68,7 @@ use base::Protection;
use base::SafeDescriptor;
use base::SharedMemory;
use cros_async::TaskHandle;
+use hypervisor::MemCacheType;
use serde::Deserialize;
use serde::Serialize;
use sync::Mutex;
@@ -78,6 +79,7 @@ use vm_memory::GuestMemory;
use vm_memory::MemoryRegion;
use vmm_vhost::message::VhostSharedMemoryRegion;
use vmm_vhost::message::VhostUserConfigFlags;
+use vmm_vhost::message::VhostUserExternalMapMsg;
use vmm_vhost::message::VhostUserGpuMapMsg;
use vmm_vhost::message::VhostUserInflight;
use vmm_vhost::message::VhostUserMemoryRegion;
@@ -877,62 +879,75 @@ impl SharedMemoryMapper for VhostShmemMapper {
source: VmMemorySource,
offset: u64,
prot: Protection,
+ _cache: MemCacheType,
) -> anyhow::Result<()> {
- // True if we should send gpu_map instead of shmem_map.
- let is_gpu = matches!(&source, &VmMemorySource::Vulkan { .. });
-
- let size = if is_gpu {
- match source {
- VmMemorySource::Vulkan {
- descriptor,
- handle_type,
+ let size = match source {
+ VmMemorySource::Vulkan {
+ descriptor,
+ handle_type,
+ memory_idx,
+ device_uuid,
+ driver_uuid,
+ size,
+ } => {
+ let msg = VhostUserGpuMapMsg::new(
+ self.shmem_info.shmid,
+ offset,
+ size,
memory_idx,
+ handle_type,
device_uuid,
driver_uuid,
- size,
- } => {
- let msg = VhostUserGpuMapMsg::new(
- self.shmem_info.shmid,
+ );
+ self.conn
+ .lock()
+ .gpu_map(&msg, &descriptor)
+ .context("failed to map memory")?;
+ size
+ }
+ VmMemorySource::ExternalMapping { ptr, size } => {
+ let msg = VhostUserExternalMapMsg::new(self.shmem_info.shmid, offset, size, ptr);
+ self.conn
+ .lock()
+ .external_map(&msg)
+ .context("failed to map memory")?;
+ size
+ }
+ source => {
+ // The last two sources use the same VhostUserShmemMapMsg, continue matching here
+ // on the aliased `source` above.
+ let (descriptor, fd_offset, size) = match source {
+ VmMemorySource::Descriptor {
+ descriptor,
offset,
size,
- memory_idx,
- handle_type,
- device_uuid,
- driver_uuid,
- );
- self.conn
- .lock()
- .gpu_map(&msg, &descriptor)
- .context("failed to map memory")?;
- size
- }
- _ => unreachable!("inconsistent pattern match"),
- }
- } else {
- let (descriptor, fd_offset, size) = match source {
- VmMemorySource::Descriptor {
- descriptor,
+ } => (descriptor, offset, size),
+ VmMemorySource::SharedMemory(shmem) => {
+ let size = shmem.size();
+ let descriptor =
+ // SAFETY:
+ // Safe because we own shmem.
+ unsafe {
+ SafeDescriptor::from_raw_descriptor(shmem.into_raw_descriptor())
+ };
+ (descriptor, 0, size)
+ }
+ _ => bail!("unsupported source"),
+ };
+ let flags = VhostUserShmemMapMsgFlags::from(prot);
+ let msg = VhostUserShmemMapMsg::new(
+ self.shmem_info.shmid,
offset,
+ fd_offset,
size,
- } => (descriptor, offset, size),
- VmMemorySource::SharedMemory(shmem) => {
- let size = shmem.size();
- let descriptor =
- // SAFETY:
- // Safe because we own shmem.
- unsafe { SafeDescriptor::from_raw_descriptor(shmem.into_raw_descriptor()) };
- (descriptor, 0, size)
- }
- _ => bail!("unsupported source"),
- };
- let flags = VhostUserShmemMapMsgFlags::from(prot);
- let msg =
- VhostUserShmemMapMsg::new(self.shmem_info.shmid, offset, fd_offset, size, flags);
- self.conn
- .lock()
- .shmem_map(&msg, &descriptor)
- .context("failed to map memory")?;
- size
+ flags,
+ );
+ self.conn
+ .lock()
+ .shmem_map(&msg, &descriptor)
+ .context("failed to map memory")?;
+ size
+ }
};
self.shmem_info.mapped_regions.insert(offset, size);
diff --git a/devices/src/virtio/vhost/user/vmm/handler.rs b/devices/src/virtio/vhost/user/vmm/handler.rs
index 61af7e802..7ed77f096 100644
--- a/devices/src/virtio/vhost/user/vmm/handler.rs
+++ b/devices/src/virtio/vhost/user/vmm/handler.rs
@@ -15,9 +15,11 @@ use base::Event;
use base::Protection;
use base::SafeDescriptor;
use base::WorkerThread;
+use hypervisor::MemCacheType;
use vm_control::VmMemorySource;
use vm_memory::GuestMemory;
use vmm_vhost::message::VhostUserConfigFlags;
+use vmm_vhost::message::VhostUserExternalMapMsg;
use vmm_vhost::message::VhostUserGpuMapMsg;
use vmm_vhost::message::VhostUserProtocolFeatures;
use vmm_vhost::message::VhostUserShmemMapMsg;
@@ -452,6 +454,7 @@ impl VhostUserMasterReqHandler for BackendReqHandlerImpl {
},
req.shm_offset,
Protection::from(req.flags),
+ MemCacheType::CacheCoherent,
) {
Ok(()) => Ok(0),
Err(e) => {
@@ -510,6 +513,36 @@ impl VhostUserMasterReqHandler for BackendReqHandlerImpl {
},
req.shm_offset,
Protection::read_write(),
+ MemCacheType::CacheCoherent,
+ ) {
+ Ok(()) => Ok(0),
+ Err(e) => {
+ error!("failed to create mapping {:?}", e);
+ Err(std::io::Error::from_raw_os_error(libc::EINVAL))
+ }
+ }
+ }
+
+ fn external_map(&mut self, req: &VhostUserExternalMapMsg) -> HandlerResult<u64> {
+ let shared_mapper_state = self
+ .shared_mapper_state
+ .as_mut()
+ .ok_or_else(|| std::io::Error::from_raw_os_error(libc::EINVAL))?;
+ if req.shmid != shared_mapper_state.shmid {
+ error!(
+ "bad shmid {}, expected {}",
+ req.shmid, shared_mapper_state.shmid
+ );
+ return Err(std::io::Error::from_raw_os_error(libc::EINVAL));
+ }
+ match shared_mapper_state.mapper.add_mapping(
+ VmMemorySource::ExternalMapping {
+ ptr: req.ptr,
+ size: req.len,
+ },
+ req.shm_offset,
+ Protection::read_write(),
+ MemCacheType::CacheCoherent,
) {
Ok(()) => Ok(0),
Err(e) => {
diff --git a/devices/src/virtio/virtio_device.rs b/devices/src/virtio/virtio_device.rs
index baa244349..907370746 100644
--- a/devices/src/virtio/virtio_device.rs
+++ b/devices/src/virtio/virtio_device.rs
@@ -11,6 +11,7 @@ use anyhow::Result;
use base::Event;
use base::Protection;
use base::RawDescriptor;
+use hypervisor::MemCacheType;
use sync::Mutex;
use vm_control::VmMemorySource;
use vm_memory::GuestAddress;
@@ -42,7 +43,13 @@ pub struct SharedMemoryRegion {
/// Trait for mapping memory into the device's shared memory region.
pub trait SharedMemoryMapper: Send {
/// Maps the given |source| into the shared memory region at |offset|.
- fn add_mapping(&mut self, source: VmMemorySource, offset: u64, prot: Protection) -> Result<()>;
+ fn add_mapping(
+ &mut self,
+ source: VmMemorySource,
+ offset: u64,
+ prot: Protection,
+ cache: MemCacheType,
+ ) -> Result<()>;
/// Removes the mapping beginning at |offset|.
fn remove_mapping(&mut self, offset: u64) -> Result<()>;
@@ -173,6 +180,7 @@ pub trait VirtioDevice: Send {
/// PCI bar into their IO address space with virtio-iommu.
///
/// NOTE: Not all vm_control::VmMemorySource types are supported.
+ /// NOTE: Not yet compatible with PrepareSharedMemoryRegion (aka fixed mapping).
fn expose_shmem_descriptors_with_viommu(&self) -> bool {
false
}
diff --git a/devices/src/virtio/virtio_pci_device.rs b/devices/src/virtio/virtio_pci_device.rs
index a97c54039..2e8e95951 100644
--- a/devices/src/virtio/virtio_pci_device.rs
+++ b/devices/src/virtio/virtio_pci_device.rs
@@ -21,6 +21,7 @@ use base::SharedMemory;
use base::Tube;
use data_model::Le32;
use hypervisor::Datamatch;
+use hypervisor::MemCacheType;
use libc::ERANGE;
use resources::Alloc;
use resources::AllocOptions;
@@ -1131,7 +1132,10 @@ impl Suspendable for VirtioPciDevice {
if let Some(queues) = self.device.virtio_sleep()? {
anyhow::ensure!(
self.device_activated,
- "unactivated device returned queues on sleep"
+ format!(
+ "unactivated device {} returned queues on sleep",
+ self.debug_label()
+ ),
);
self.sleep_state = Some(SleepState::Active {
activated_queues: queues,
@@ -1139,7 +1143,10 @@ impl Suspendable for VirtioPciDevice {
} else {
anyhow::ensure!(
!self.device_activated,
- "activated device didn't return queues on sleep"
+ format!(
+ "activated device {} didn't return queues on sleep",
+ self.debug_label()
+ ),
);
self.sleep_state = Some(SleepState::Inactive);
}
@@ -1157,9 +1164,12 @@ impl Suspendable for VirtioPciDevice {
// If the device is already awake, we should not request it to wake again.
}
Some(SleepState::Inactive) => {
- self.device
- .virtio_wake(None)
- .expect("virtio_wake failed, can't recover");
+ self.device.virtio_wake(None).with_context(|| {
+ format!(
+ "virtio_wake failed for {}, can't recover",
+ self.debug_label(),
+ )
+ })?;
}
Some(SleepState::Active { activated_queues }) => {
self.device
@@ -1170,7 +1180,12 @@ impl Suspendable for VirtioPciDevice {
.expect("interrupt missing for already active queues"),
activated_queues,
)))
- .expect("virtio_wake failed, can't recover");
+ .with_context(|| {
+ format!(
+ "virtio_wake failed for {}, can't recover",
+ self.debug_label(),
+ )
+ })?;
}
};
Ok(())
@@ -1398,6 +1413,7 @@ impl SharedMemoryMapper for VmRequester {
source: VmMemorySource,
offset: u64,
prot: Protection,
+ cache: MemCacheType,
) -> anyhow::Result<()> {
if self.needs_prepare {
self.vm_memory_client
@@ -1415,6 +1431,7 @@ impl SharedMemoryMapper for VmRequester {
offset,
},
prot,
+ cache,
)
.context("register_memory failed")?;
diff --git a/devices/src/virtio/wl.rs b/devices/src/virtio/wl.rs
index 1d4c1ea23..22a9518e1 100644
--- a/devices/src/virtio/wl.rs
+++ b/devices/src/virtio/wl.rs
@@ -88,6 +88,7 @@ use base::WaitContext;
use base::WorkerThread;
use data_model::Le32;
use data_model::Le64;
+use hypervisor::MemCacheType;
#[cfg(feature = "minigbm")]
use libc::EBADF;
#[cfg(feature = "minigbm")]
@@ -584,7 +585,10 @@ impl VmRequester {
.context("failed to allocate offset")
.map_err(WlError::ShmemMapperError)?;
- match state.mapper.add_mapping(source, offset, prot) {
+ match state
+ .mapper
+ .add_mapping(source, offset, prot, MemCacheType::CacheCoherent)
+ {
Ok(()) => {
state.allocs.insert(offset, alloc);
Ok(offset)
@@ -887,7 +891,7 @@ impl WlVfd {
}
fn pipe_remote_read_local_write() -> WlResult<WlVfd> {
- let (read_pipe, write_pipe) = pipe(true).map_err(WlError::NewPipe)?;
+ let (read_pipe, write_pipe) = pipe().map_err(WlError::NewPipe)?;
let mut vfd = WlVfd::default();
vfd.remote_pipe = Some(read_pipe);
vfd.local_pipe = Some((VIRTIO_WL_VFD_WRITE, write_pipe));
@@ -895,7 +899,7 @@ impl WlVfd {
}
fn pipe_remote_write_local_read() -> WlResult<WlVfd> {
- let (read_pipe, write_pipe) = pipe(true).map_err(WlError::NewPipe)?;
+ let (read_pipe, write_pipe) = pipe().map_err(WlError::NewPipe)?;
let mut vfd = WlVfd::default();
vfd.remote_pipe = Some(write_pipe);
vfd.local_pipe = Some((VIRTIO_WL_VFD_READ, read_pipe));
diff --git a/docs/book/src/SUMMARY.md b/docs/book/src/SUMMARY.md
index 949603d3d..4daa93293 100644
--- a/docs/book/src/SUMMARY.md
+++ b/docs/book/src/SUMMARY.md
@@ -16,6 +16,7 @@
- [Fuzzing](./testing/fuzzing.md)
- [Devices](./devices/index.md)
- [Block](./devices/block.md)
+ - [Input](./devices/input.md)
- [Network](./devices/net.md)
- [Balloon](./devices/balloon.md)
- [SCSI (experimental)](./devices/scsi.md)
diff --git a/docs/book/src/devices/index.md b/docs/book/src/devices/index.md
index 83bf0ddd2..80caf454f 100644
--- a/docs/book/src/devices/index.md
+++ b/docs/book/src/devices/index.md
@@ -73,7 +73,7 @@ Currently, only network devices are supported.
[`fs`]: https://chromium.googlesource.com/crosvm/crosvm/+/refs/heads/main/devices/src/virtio/fs/
[`gpu`]: https://chromium.googlesource.com/crosvm/crosvm/+/refs/heads/main/devices/src/virtio/gpu/
[`i8042`]: https://chromium.googlesource.com/crosvm/crosvm/+/refs/heads/main/devices/src/i8042.rs
-[`input`]: https://chromium.googlesource.com/crosvm/crosvm/+/refs/heads/main/devices/src/virtio/input/
+[`input`]: input.md
[`iommu`]: https://chromium.googlesource.com/crosvm/crosvm/+/refs/heads/main/devices/src/virtio/iommu.rs
[`net`]: net.md
[`p9`]: https://chromium.googlesource.com/crosvm/crosvm/+/refs/heads/main/devices/src/virtio/p9.rs
diff --git a/docs/book/src/devices/input.md b/docs/book/src/devices/input.md
new file mode 100644
index 000000000..0efa6dccc
--- /dev/null
+++ b/docs/book/src/devices/input.md
@@ -0,0 +1,182 @@
+# Input
+
+crosvm supports
+[virtio-input](https://docs.oasis-open.org/virtio/virtio/v1.2/csd01/virtio-v1.2-csd01.html#x1-3850008)
+devices that provide human input devices like multi-touch devices, trackpads, keyboards, and mice.
+
+Events may be sent to the input device via a socket carrying `virtio_input_event` structures. On
+Unix-like platforms, this socket must be a UNIX domain socket in stream mode (`AF_UNIX`/`AF_LOCAL`,
+`SOCK_STREAM`). Typically this will be created by a separate program that listens and accepts a
+connection on this socket and sends the desired events.
+
+On Linux, it is also possible to grab an `evdev` device and forward its events to the guest.
+
+The general syntax of the input option is as follows:
+
+```
+--input DEVICE-TYPE[KEY=VALUE,KEY=VALUE,...]
+```
+
+For example, to create a 1920x1080 multi-touch device reading data from `/tmp/multi-touch-socket`:
+
+```sh
+crosvm run \
+ ...
+ --input multi-touch[path=/tmp/multi-touch-socket,width=1920,height=1080]
+ ...
+```
+
+The available device types and their specific options are listed below.
+
+## Input device types
+
+### Evdev
+
+Linux only.
+
+Passes an [event device](https://docs.kernel.org/input/input.html#evdev) node into the VM. The
+device will be grabbed (unusable from the host) and made available to the guest with the same
+configuration it shows on the host.
+
+Options:
+
+- `path` (required): path to `evdev` device, e.g. `/dev/input/event0`
+
+Example:
+
+```sh
+crosvm run \
+ --input evdev[path=/dev/input/event0] \
+ ...
+```
+
+### Keyboard
+
+Add a keyboard virtio-input device.
+
+Options:
+
+- `path` (required): path to event source socket
+
+Example:
+
+```sh
+crosvm run \
+ --input keyboard[path=/tmp/keyboard-socket] \
+ ...
+```
+
+### Mouse
+
+Add a mouse virtio-input device.
+
+Options:
+
+- `path` (required): path to event source socket
+
+Example:
+
+```sh
+crosvm run \
+ --input mouse[path=/tmp/mouse-socket] \
+ ...
+```
+
+### Multi-Touch
+
+Add a multi-touch touchscreen virtio-input device.
+
+Options:
+
+- `path` (required): path to event source socket
+- `width` (optional): width of the touchscreen in pixels (default: 1280)
+- `height` (optional): height of the touchscreen in pixels (default: 1024)
+- `name` (optional): device name string
+
+If `width` and `height` are not specified, the first multi-touch input device is sized to match the
+GPU display size, if specified.
+
+Example:
+
+```sh
+crosvm run \
+ ...
+ --input multi-touch[path=/tmp/multi-touch-socket,width=1920,height=1080,name=mytouch2]
+ ...
+```
+
+### Rotary
+
+Add a rotating side button/wheel virtio-input device.
+
+Options:
+
+- `path` (required): path to event source socket
+
+Example:
+
+```sh
+crosvm run \
+ --input rotary[path=/tmp/rotary-socket] \
+ ...
+```
+
+### Single-Touch
+
+Add a single-touch touchscreen virtio-input device.
+
+Options:
+
+- `path` (required): path to event source socket
+- `width` (optional): width of the touchscreen in pixels (default: 1280)
+- `height` (optional): height of the touchscreen in pixels (default: 1024)
+- `name` (optional): device name string
+
+If `width` and `height` are not specified, the first single-touch input device is sized to match the
+GPU display size, if specified.
+
+Example:
+
+```sh
+crosvm run \
+ ...
+ --input single-touch[path=/tmp/single-touch-socket,width=1920,height=1080,name=mytouch1]
+ ...
+```
+
+### Switches
+
+Add a switches virtio-input device. Switches are often used for accessibility, such as with the
+Android [Switch Access](https://support.google.com/accessibility/android/topic/6151780) feature.
+
+Options:
+
+- `path` (required): path to event source socket
+
+Example:
+
+```sh
+crosvm run \
+ --input switches[path=/tmp/switches-socket] \
+ ...
+```
+
+### Trackpad
+
+Add a trackpad virtio-input device.
+
+Options:
+
+- `path` (required): path to event source socket
+- `width` (optional): width of the touchscreen in pixels (default: 1280)
+- `height` (optional): height of the touchscreen in pixels (default: 1024)
+- `name` (optional): device name string
+
+Example:
+
+```sh
+crosvm run \
+ ...
+ --input trackpad[path=/tmp/trackpad-socket,width=1920,height=1080,name=mytouch1]
+ ...
+```
diff --git a/e2e_tests/guest_under_test/Makefile b/e2e_tests/guest_under_test/Makefile
index 8db694437..2fbff6efb 100644
--- a/e2e_tests/guest_under_test/Makefile
+++ b/e2e_tests/guest_under_test/Makefile
@@ -39,6 +39,10 @@ $(shell mkdir -p $(TARGET))
# Parameteters for building the kernel locally
KERNEL_REPO ?= https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
KERNEL_BRANCH ?= linux-6.1.y
+KERNEL_SRC_BASE ?= $(TARGET)/kernel-source-$(KERNEL_BRANCH)
+KERNEL_SRC_PATCHED ?= $(KERNEL_SRC_BASE)-patched
+KERNEL_BUILD ?= $(TARGET)/kernel-build
+KERNEL_PATCHES ?= "`readlink -f ./kernel/patches`"
################################################################################
# Main targets
@@ -116,27 +120,32 @@ $(TARGET)/initramfs: $(TARGET)/rootfs-build/delegate initramfs/Containerfile ini
################################################################################
# Build kernel
-kernel : $(TARGET)/bzImage
-
-$(TARGET)/bzImage: $(TARGET)/kernel-source $(TARGET)/kernel-build
- cd $(TARGET)/kernel-source \
- && make O=$(TARGET)/kernel-build \
+kernel: $(TARGET)/bzImage
+
+# Make this target PHONY to make sure everything is up to date with the kernel's make recipe.
+# You can use custom kernel source by running:
+# make kernel KERNEL_SRC_PATCHED=${PATH_TO_YOUR_KERNEL}
+# (Note: you have to manually apply the patches applied in %-patched recipe to pass all the tests.)
+.PHONY : $(TARGET)/bzImage
+$(TARGET)/bzImage : $(KERNEL_SRC_PATCHED)
+ mkdir -p $(KERNEL_BUILD)
+ cat kernel/common.config kernel/$(KERNEL_ARCH).config > $(KERNEL_BUILD)/.config
+ make -C $(KERNEL_SRC_PATCHED) O=$(KERNEL_BUILD) \
ARCH=$(KERNEL_ARCH) \
CROSS_COMPILE=$(CROSS_COMPILE) \
-j$(shell nproc)\
olddefconfig \
$(KERNEL_BINARY)
+ cp $(KERNEL_BUILD)/arch/${KERNEL_ARCH}/boot/$(KERNEL_BINARY) $@
- cp $(TARGET)/kernel-build/arch/${KERNEL_ARCH}/boot/$(KERNEL_BINARY) $@
-
-$(TARGET)/kernel-build: $(TARGET)/kernel-source kernel/$(KERNEL_ARCH).config kernel/common.config
- mkdir -p $@
- cat kernel/common.config kernel/$(KERNEL_ARCH).config > $@/.config
- touch $@
+$(KERNEL_SRC_PATCHED): $(KERNEL_SRC_BASE)
+ rm -rf $@.tmp ; true # ignore failure
+ cp -r $(KERNEL_SRC_BASE) $@.tmp
+ git -C $@.tmp am $(KERNEL_PATCHES)/virtio_pvclock.patch
+ mv $@.tmp $@
-$(TARGET)/kernel-source:
+$(KERNEL_SRC_BASE):
rm -rf $@
git clone --depth 1 --branch $(KERNEL_BRANCH) $(KERNEL_REPO) $@
-
.PHONY: clean all update-prebuilts
diff --git a/e2e_tests/guest_under_test/kernel/patches/virtio_pvclock.patch b/e2e_tests/guest_under_test/kernel/patches/virtio_pvclock.patch
new file mode 100644
index 000000000..84a47f0cc
--- /dev/null
+++ b/e2e_tests/guest_under_test/kernel/patches/virtio_pvclock.patch
@@ -0,0 +1,590 @@
+From 7f8eba774852bad453f9015ca408612337acc86d Mon Sep 17 00:00:00 2001
+From: Hikaru Nishida <hikalium@chromium.org>
+Date: Wed, 24 Jan 2024 14:23:40 +0900
+Subject: [PATCH] CHROMIUM: virtio_pvclock: port driver impl from Android
+
+Initial virtio_pvclock device driver implementation from Android.
+
+This is a squash of aosp/1959549, aosp/1962079, aosp/2395934:
+- ANDROID: virtio: virtio_pvclock: initial driver impl
+- ANDROID: virtio: virtio_pvclock: call vclocks_set_used
+- ANDROID: virtio: virtio_pvclock: fix rating decl
+
+BUG=b:271057959, b:295256641
+TEST=make O=../v5.10-arcvm_build x86_64_arcvm_defconfig
+TEST=make -j`nproc` O=../v5.10-arcvm_build bzImage
+TEST=tast run ${DUT} arc.PlayStore.vm arc.Suspend.*
+UPSTREAM-TASK=b:321618282
+
+Change-Id: I068da510e17283b13791e3ae51542b74d4601975
+Signed-off-by: Hikaru Nishida <hikalium@chromium.org>
+---
+ arch/x86/entry/vdso/vma.c | 1 +
+ arch/x86/kernel/pvclock.c | 2 +
+ drivers/virtio/Kconfig | 36 +++
+ drivers/virtio/Makefile | 1 +
+ drivers/virtio/virtio_pvclock.c | 345 ++++++++++++++++++++++++++++
+ include/uapi/linux/virtio_ids.h | 3 +
+ include/uapi/linux/virtio_pvclock.h | 74 ++++++
+ kernel/time/timekeeping.c | 4 +
+ 8 files changed, 466 insertions(+)
+ create mode 100644 drivers/virtio/virtio_pvclock.c
+ create mode 100644 include/uapi/linux/virtio_pvclock.h
+
+diff --git a/arch/x86/entry/vdso/vma.c b/arch/x86/entry/vdso/vma.c
+index 128866139..51520db4a 100644
+--- a/arch/x86/entry/vdso/vma.c
++++ b/arch/x86/entry/vdso/vma.c
+@@ -39,6 +39,7 @@ struct vdso_data *arch_get_vdso_data(void *vvar_page)
+ #undef EMIT_VVAR
+
+ unsigned int vclocks_used __read_mostly;
++EXPORT_SYMBOL_GPL(vclocks_used);
+
+ #if defined(CONFIG_X86_64)
+ unsigned int __read_mostly vdso64_enabled = 1;
+diff --git a/arch/x86/kernel/pvclock.c b/arch/x86/kernel/pvclock.c
+index eda37df01..54b41d759 100644
+--- a/arch/x86/kernel/pvclock.c
++++ b/arch/x86/kernel/pvclock.c
+@@ -109,6 +109,7 @@ u64 pvclock_clocksource_read(struct pvclock_vcpu_time_info *src)
+
+ return ret;
+ }
++EXPORT_SYMBOL_GPL(pvclock_clocksource_read);
+
+ void pvclock_read_wallclock(struct pvclock_wall_clock *wall_clock,
+ struct pvclock_vcpu_time_info *vcpu_time,
+@@ -148,6 +149,7 @@ void pvclock_set_pvti_cpu0_va(struct pvclock_vsyscall_time_info *pvti)
+ WARN_ON(vclock_was_used(VDSO_CLOCKMODE_PVCLOCK));
+ pvti_cpu0_va = pvti;
+ }
++EXPORT_SYMBOL_GPL(pvclock_set_pvti_cpu0_va);
+
+ struct pvclock_vsyscall_time_info *pvclock_get_pvti_cpu0_va(void)
+ {
+diff --git a/drivers/virtio/Kconfig b/drivers/virtio/Kconfig
+index 0a53a6123..72921084e 100644
+--- a/drivers/virtio/Kconfig
++++ b/drivers/virtio/Kconfig
+@@ -173,4 +173,40 @@ config VIRTIO_DMA_SHARED_BUFFER
+ This option adds a flavor of dma buffers that are backed by
+ virtio resources.
+
++config VIRTIO_PVCLOCK
++ tristate "Virtio pvclock driver"
++ depends on VIRTIO
++ depends on X86
++ select PARAVIRT_CLOCK
++ help
++ This driver supports virtio pvclock devices.
++ It helps emulating CLOCK_BOOTTIME behavior around host's suspend / resume
++ without actually suspends the guest with the hypervisor's support.
++
++ If unsure, say M.
++
++config VIRTIO_PVCLOCK
++ tristate "Virtio pvclock driver"
++ depends on VIRTIO
++ depends on X86
++ select PARAVIRT_CLOCK
++ help
++ This driver supports virtio pvclock devices.
++ It helps emulating CLOCK_BOOTTIME behavior around host's suspend / resume
++ without actually suspends the guest with the hypervisor's support.
++
++ If unsure, say M.
++
++config VIRTIO_PVCLOCK
++ tristate "Virtio pvclock driver"
++ depends on VIRTIO
++ depends on X86
++ select PARAVIRT_CLOCK
++ help
++ This driver supports virtio pvclock devices.
++ It helps emulating CLOCK_BOOTTIME behavior around host's suspend / resume
++ without actually suspends the guest with the hypervisor's support.
++
++ If unsure, say M.
++
+ endif # VIRTIO_MENU
+diff --git a/drivers/virtio/Makefile b/drivers/virtio/Makefile
+index 8e98d2491..79e6dea7c 100644
+--- a/drivers/virtio/Makefile
++++ b/drivers/virtio/Makefile
+@@ -12,3 +12,4 @@ obj-$(CONFIG_VIRTIO_INPUT) += virtio_input.o
+ obj-$(CONFIG_VIRTIO_VDPA) += virtio_vdpa.o
+ obj-$(CONFIG_VIRTIO_MEM) += virtio_mem.o
+ obj-$(CONFIG_VIRTIO_DMA_SHARED_BUFFER) += virtio_dma_buf.o
++obj-$(CONFIG_VIRTIO_PVCLOCK) += virtio_pvclock.o
+diff --git a/drivers/virtio/virtio_pvclock.c b/drivers/virtio/virtio_pvclock.c
+new file mode 100644
+index 000000000..7d6fd0b52
+--- /dev/null
++++ b/drivers/virtio/virtio_pvclock.c
+@@ -0,0 +1,345 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++/*
++ * Virtio pvclock implementation.
++ *
++ * Copyright (C) 2021 Google, Inc.
++ */
++
++#include <linux/clocksource.h>
++#include <linux/dma-mapping.h>
++#include <linux/module.h>
++#include <linux/slab.h>
++#include <linux/virtio.h>
++#include <linux/virtio_pvclock.h>
++#include <linux/workqueue.h>
++#include <asm/pvclock.h>
++
++enum virtio_pvclock_vq {
++ VIRTIO_PVCLOCK_VQ_SET_PVCLOCK_PAGE,
++ VIRTIO_PVCLOCK_VQ_MAX
++};
++
++struct virtio_pvclock {
++ struct virtio_device *vdev;
++ struct virtqueue *set_pvclock_page_vq;
++ struct virtio_pvclock_set_pvclock_page_req set_page_request;
++
++ /* Updating the suspend time happens via scheduled work. */
++ struct work_struct update_suspend_time_work;
++ /* Creating the clocksource happens via scheduled work. */
++ struct work_struct create_clocksource_work;
++
++ /* Synchronize access/update to injected_suspend_ns. */
++ struct mutex inject_suspend_lock;
++ /* Total ns injected as sleep time. */
++ u64 injected_suspend_ns;
++
++ /* DMA address of virtio_pvclock_page. */
++ dma_addr_t pvclock_page_dma_addr;
++};
++
++/* CPU accessible pointer to pvclock page. */
++static struct pvclock_vsyscall_time_info *virtio_pvclock_page;
++
++static struct virtio_device_id id_table[] = {
++ { VIRTIO_ID_PVCLOCK, VIRTIO_DEV_ANY_ID },
++ { 0 },
++};
++
++void update_suspend_time(struct work_struct *work)
++{
++ u64 suspend_ns, suspend_time_delta = 0;
++ struct timespec64 inject_time;
++ struct virtio_pvclock *vp;
++
++ vp = container_of(work, struct virtio_pvclock,
++ update_suspend_time_work);
++
++ virtio_cread(vp->vdev, struct virtio_pvclock_config, suspend_time_ns,
++ &suspend_ns);
++
++ mutex_lock(&vp->inject_suspend_lock);
++ if (suspend_ns > vp->injected_suspend_ns) {
++ suspend_time_delta = suspend_ns - vp->injected_suspend_ns;
++ vp->injected_suspend_ns = suspend_ns;
++ }
++ mutex_unlock(&vp->inject_suspend_lock);
++
++ if (suspend_time_delta == 0) {
++ dev_err(&vp->vdev->dev,
++ "%s: suspend_time_ns is less than injected_suspend_ns\n",
++ __func__);
++ return;
++ }
++
++ inject_time = ns_to_timespec64(suspend_time_delta);
++
++ timekeeping_inject_sleeptime64(&inject_time);
++
++ dev_info(&vp->vdev->dev, "injected sleeptime: %llu ns\n",
++ suspend_time_delta);
++}
++
++static u64 virtio_pvclock_clocksource_read(struct clocksource *cs)
++{
++ u64 ret;
++
++ preempt_disable_notrace();
++ ret = pvclock_clocksource_read(&virtio_pvclock_page->pvti);
++ preempt_enable_notrace();
++ return ret;
++}
++
++static int virtio_pvclock_cs_enable(struct clocksource *cs)
++{
++ if (cs->vdso_clock_mode == VDSO_CLOCKMODE_PVCLOCK)
++ vclocks_set_used(VDSO_CLOCKMODE_PVCLOCK);
++ return 0;
++}
++
++static struct clocksource virtio_pvclock_clocksource = {
++ .name = "virtio-pvclock",
++ .rating = 200, /* default rating, updated by virtpvclock_validate */
++ .read = virtio_pvclock_clocksource_read,
++ .mask = CLOCKSOURCE_MASK(64),
++ .flags = CLOCK_SOURCE_IS_CONTINUOUS,
++ .enable = virtio_pvclock_cs_enable,
++};
++
++static void set_pvclock_page_callback(struct virtqueue *vq)
++{
++ struct virtio_pvclock *vp = vq->vdev->priv;
++
++ if (vp->set_page_request.status != VIRTIO_PVCLOCK_S_OK) {
++ dev_err(&vq->vdev->dev,
++ "%s: set_pvclock_page req status is %u\n", __func__,
++ vp->set_page_request.status);
++ return;
++ }
++
++ /*
++ * Create the actual clocksource via a work queue because we're in an
++ * interrupt handler right now.
++ */
++ schedule_work(&vp->create_clocksource_work);
++}
++
++static void create_clocksource(struct work_struct *work)
++{
++ struct virtio_pvclock *vp;
++
++ vp = container_of(work, struct virtio_pvclock, create_clocksource_work);
++
++ /*
++ * VDSO pvclock can only be used if the TSCs are stable. The device also
++ * must set PVCLOCK_TSC_STABLE_BIT in the pvclock flags field.
++ */
++ if (virtio_has_feature(vp->vdev, VIRTIO_PVCLOCK_F_TSC_STABLE)) {
++ pvclock_set_pvti_cpu0_va(virtio_pvclock_page);
++ virtio_pvclock_clocksource.vdso_clock_mode =
++ VDSO_CLOCKMODE_PVCLOCK;
++ }
++
++ clocksource_register_hz(&virtio_pvclock_clocksource, NSEC_PER_SEC);
++
++ dev_info(&vp->vdev->dev, "registered clocksource\n");
++}
++
++static void virtpvclock_changed(struct virtio_device *vdev)
++{
++ struct virtio_pvclock *vp = vdev->priv;
++
++ schedule_work(&vp->update_suspend_time_work);
++}
++
++static int set_pvclock_page(struct virtio_pvclock *vp)
++{
++ struct scatterlist sg;
++ int err;
++
++ vp->set_page_request.pvclock_page_pa = vp->pvclock_page_dma_addr;
++ vp->set_page_request.system_time = ktime_get();
++ vp->set_page_request.tsc_timestamp = rdtsc_ordered();
++
++ sg_init_one(&sg, &vp->set_page_request, sizeof(vp->set_page_request));
++ err = virtqueue_add_outbuf(vp->set_pvclock_page_vq, &sg, 1, vp,
++ GFP_KERNEL);
++
++ if (err) {
++ dev_err(&vp->vdev->dev, "%s: failed to add output\n", __func__);
++ return err;
++ }
++ virtqueue_kick(vp->set_pvclock_page_vq);
++
++ return 0;
++}
++
++static int init_vqs(struct virtio_pvclock *vp)
++{
++ vq_callback_t *callbacks[VIRTIO_PVCLOCK_VQ_MAX];
++ struct virtqueue *vqs[VIRTIO_PVCLOCK_VQ_MAX];
++ const char *names[VIRTIO_PVCLOCK_VQ_MAX];
++ int err;
++
++ callbacks[VIRTIO_PVCLOCK_VQ_SET_PVCLOCK_PAGE] =
++ set_pvclock_page_callback;
++ names[VIRTIO_PVCLOCK_VQ_SET_PVCLOCK_PAGE] = "set_pvclock_page";
++
++ err = vp->vdev->config->find_vqs(vp->vdev, VIRTIO_PVCLOCK_VQ_MAX, vqs,
++ callbacks, names, NULL, NULL);
++ if (err)
++ return err;
++
++ vp->set_pvclock_page_vq = vqs[VIRTIO_PVCLOCK_VQ_SET_PVCLOCK_PAGE];
++
++ return set_pvclock_page(vp);
++}
++
++static int virtpvclock_probe(struct virtio_device *vdev)
++{
++ struct virtio_pvclock *vp;
++ int err;
++
++ if (!vdev->config->get) {
++ dev_err(&vdev->dev, "%s: config access disabled\n", __func__);
++ return -EINVAL;
++ }
++
++ vp = kzalloc(sizeof(*vp), GFP_KERNEL);
++ if (!vp) {
++ err = -ENOMEM;
++ goto out;
++ }
++
++ virtio_pvclock_page =
++ dma_alloc_coherent(vdev->dev.parent,
++ sizeof(*virtio_pvclock_page),
++ &vp->pvclock_page_dma_addr, GFP_KERNEL);
++
++ if (!virtio_pvclock_page) {
++ err = -ENOMEM;
++ goto out_free_vp;
++ }
++
++ INIT_WORK(&vp->update_suspend_time_work, update_suspend_time);
++ INIT_WORK(&vp->create_clocksource_work, create_clocksource);
++ mutex_init(&vp->inject_suspend_lock);
++
++ vp->vdev = vdev;
++ vdev->priv = vp;
++
++ err = init_vqs(vp);
++ if (err)
++ goto out_free_pvclock_page;
++
++ virtio_device_ready(vdev);
++
++ return 0;
++
++out_free_pvclock_page:
++ dma_free_coherent(vdev->dev.parent, sizeof(*virtio_pvclock_page),
++ virtio_pvclock_page, vp->pvclock_page_dma_addr);
++
++out_free_vp:
++ kfree(vp);
++out:
++ return err;
++}
++
++static void remove_common(struct virtio_pvclock *vp)
++{
++ /* Now we reset the device so we can clean up the queues. */
++ vp->vdev->config->reset(vp->vdev);
++
++ vp->vdev->config->del_vqs(vp->vdev);
++}
++
++static void virtpvclock_remove(struct virtio_device *vdev)
++{
++ struct virtio_pvclock *vp = vdev->priv;
++
++ remove_common(vp);
++
++ dma_free_coherent(vdev->dev.parent, sizeof(*virtio_pvclock_page),
++ virtio_pvclock_page, vp->pvclock_page_dma_addr);
++
++ kfree(vp);
++}
++
++#ifdef CONFIG_PM_SLEEP
++static int virtpvclock_freeze(struct virtio_device *vdev)
++{
++ struct virtio_pvclock *vp = vdev->priv;
++
++ /*
++ * The workqueue is already frozen by the PM core before this
++ * function is called.
++ */
++ remove_common(vp);
++ return 0;
++}
++
++static int virtpvclock_restore(struct virtio_device *vdev)
++{
++ int ret;
++
++ ret = init_vqs(vdev->priv);
++ if (ret)
++ return ret;
++
++ virtio_device_ready(vdev);
++
++ return 0;
++}
++#endif
++
++#define MAX_CLOCKSOURCE_RATING 450
++
++static int virtpvclock_validate(struct virtio_device *vdev)
++{
++ uint32_t rating;
++
++ if (!virtio_has_feature(vdev, VIRTIO_PVCLOCK_F_CLOCKSOURCE_RATING))
++ return 0;
++
++ rating = virtio_cread32(vdev, offsetof(struct virtio_pvclock_config,
++ clocksource_rating));
++ if (rating > MAX_CLOCKSOURCE_RATING) {
++ dev_warn(
++ &vdev->dev,
++ "device clocksource rating too high: %u, using max rating: %u\n",
++ rating, MAX_CLOCKSOURCE_RATING);
++ __virtio_clear_bit(vdev, VIRTIO_PVCLOCK_F_CLOCKSOURCE_RATING);
++ virtio_pvclock_clocksource.rating = (int)MAX_CLOCKSOURCE_RATING;
++ } else {
++ dev_info(&vdev->dev, "clocksource rating set to %u\n", rating);
++ virtio_pvclock_clocksource.rating = (int)rating;
++ }
++
++ return 0;
++}
++
++static unsigned int features[] = { VIRTIO_PVCLOCK_F_TSC_STABLE,
++ VIRTIO_PVCLOCK_F_INJECT_SLEEP,
++ VIRTIO_PVCLOCK_F_CLOCKSOURCE_RATING };
++
++static struct virtio_driver virtio_pvclock_driver = {
++ .feature_table = features,
++ .feature_table_size = ARRAY_SIZE(features),
++ .driver.name = KBUILD_MODNAME,
++ .driver.owner = THIS_MODULE,
++ .id_table = id_table,
++ .validate = virtpvclock_validate,
++ .probe = virtpvclock_probe,
++ .remove = virtpvclock_remove,
++ .config_changed = virtpvclock_changed,
++#ifdef CONFIG_PM_SLEEP
++ .freeze = virtpvclock_freeze,
++ .restore = virtpvclock_restore,
++#endif
++};
++
++module_virtio_driver(virtio_pvclock_driver);
++MODULE_DEVICE_TABLE(virtio, id_table);
++MODULE_DESCRIPTION("Virtio pvclock driver");
++MODULE_LICENSE("GPL");
+diff --git a/include/uapi/linux/virtio_ids.h b/include/uapi/linux/virtio_ids.h
+index 7aa2eb766..c4ce86b44 100644
+--- a/include/uapi/linux/virtio_ids.h
++++ b/include/uapi/linux/virtio_ids.h
+@@ -69,6 +69,9 @@
+ #define VIRTIO_ID_BT 40 /* virtio bluetooth */
+ #define VIRTIO_ID_GPIO 41 /* virtio gpio */
+
++/* Chrome OS-specific devices */
++#define VIRTIO_ID_PVCLOCK 61 /* virtio pvclock (experimental id) */
++
+ /*
+ * Virtio Transitional IDs
+ */
+diff --git a/include/uapi/linux/virtio_pvclock.h b/include/uapi/linux/virtio_pvclock.h
+new file mode 100644
+index 000000000..808d47b21
+--- /dev/null
++++ b/include/uapi/linux/virtio_pvclock.h
+@@ -0,0 +1,74 @@
++/* SPDX-License-Identifier: (GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause */
++/* This header is BSD licensed so anyone can use the definitions to implement
++ * compatible drivers/servers.
++ *
++ * Redistribution and use in source and binary forms, with or without
++ * modification, are permitted provided that the following conditions
++ * are met:
++ * 1. Redistributions of source code must retain the above copyright
++ * notice, this list of conditions and the following disclaimer.
++ * 2. Redistributions in binary form must reproduce the above copyright
++ * notice, this list of conditions and the following disclaimer in the
++ * documentation and/or other materials provided with the distribution.
++ * 3. Neither the name of IBM nor the names of its contributors
++ * may be used to endorse or promote products derived from this software
++ * without specific prior written permission.
++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND
++ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
++ * ARE DISCLAIMED. IN NO EVENT SHALL IBM OR CONTRIBUTORS BE LIABLE
++ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
++ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
++ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
++ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
++ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
++ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
++ * SUCH DAMAGE.
++ */
++
++#ifndef _LINUX_VIRTIO_PVCLOCK_H
++#define _LINUX_VIRTIO_PVCLOCK_H
++
++#include <linux/types.h>
++#include <linux/virtio_types.h>
++#include <linux/virtio_ids.h>
++#include <linux/virtio_config.h>
++
++/* The feature bitmap for virtio pvclock */
++/* TSC is stable */
++#define VIRTIO_PVCLOCK_F_TSC_STABLE 0
++/* Inject sleep for suspend */
++#define VIRTIO_PVCLOCK_F_INJECT_SLEEP 1
++/* Use device clocksource rating */
++#define VIRTIO_PVCLOCK_F_CLOCKSOURCE_RATING 2
++
++struct virtio_pvclock_config {
++ /* Number of ns the VM has been suspended without guest suspension. */
++ __u64 suspend_time_ns;
++ /* Device-suggested rating of the pvclock clocksource. */
++ __u32 clocksource_rating;
++ __u32 padding;
++};
++
++/* Status values for a virtio_pvclock request. */
++#define VIRTIO_PVCLOCK_S_OK 0
++#define VIRTIO_PVCLOCK_S_IOERR 1
++#define VIRTIO_PVCLOCK_S_UNSUPP 2
++
++/*
++ * Virtio pvclock set pvclock page request. Sets up the shared memory
++ * pvclock_vsyscall_time_info struct.
++ */
++struct virtio_pvclock_set_pvclock_page_req {
++ /* Physical address of pvclock_vsyscall_time_info. */
++ __u64 pvclock_page_pa;
++ /* Current system time. */
++ __u64 system_time;
++ /* Current tsc value. */
++ __u64 tsc_timestamp;
++ /* Status of this request, one of VIRTIO_PVCLOCK_S_*. */
++ __u8 status;
++ __u8 padding[7];
++};
++
++#endif /* _LINUX_VIRTIO_PVCLOCK_H */
+diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c
+index 221c8c404..3fd9bb166 100644
+--- a/kernel/time/timekeeping.c
++++ b/kernel/time/timekeeping.c
+@@ -1735,7 +1735,10 @@ bool timekeeping_rtc_skipsuspend(void)
+ {
+ return persistent_clock_exists;
+ }
++#endif
+
++#if (defined(CONFIG_PM_SLEEP) && defined(CONFIG_RTC_HCTOSYS_DEVICE)) || \
++ defined(CONFIG_VIRTIO_PVCLOCK)
+ /**
+ * timekeeping_inject_sleeptime64 - Adds suspend interval to timeekeeping values
+ * @delta: pointer to a timespec64 delta value
+@@ -1769,6 +1772,7 @@ void timekeeping_inject_sleeptime64(const struct timespec64 *delta)
+ /* Signal hrtimers about time change */
+ clock_was_set(CLOCK_SET_WALL | CLOCK_SET_BOOT);
+ }
++EXPORT_SYMBOL_GPL(timekeeping_inject_sleeptime64);
+ #endif
+
+ /**
+--
+2.43.0.429.g432eaa2c6b-goog
+
diff --git a/gpu_display/examples/simple.rs b/gpu_display/examples/simple.rs
index f82aba378..9741dc44a 100644
--- a/gpu_display/examples/simple.rs
+++ b/gpu_display/examples/simple.rs
@@ -13,7 +13,13 @@ mod platform {
pub fn run() -> Result<()> {
let mut disp = GpuDisplay::open_wayland(None::<&str>).context("open_wayland")?;
let surface_id = disp
- .create_surface(None, 1280, 1024, SurfaceType::Scanout)
+ .create_surface(
+ None,
+ /* scanout_id= */ Some(0),
+ 1280,
+ 1024,
+ SurfaceType::Scanout,
+ )
.context("create_surface")?;
disp.flip(surface_id);
disp.commit(surface_id).context("commit")?;
diff --git a/gpu_display/examples/simple_open.rs b/gpu_display/examples/simple_open.rs
index a6f773e2f..73bdef757 100644
--- a/gpu_display/examples/simple_open.rs
+++ b/gpu_display/examples/simple_open.rs
@@ -12,7 +12,13 @@ use gpu_display::SurfaceType;
fn run() -> Result<()> {
let mut disp = GpuDisplay::open_x(None::<&str>).context("open_x")?;
let surface_id = disp
- .create_surface(None, 1280, 1024, SurfaceType::Scanout)
+ .create_surface(
+ None,
+ /* scanout_id= */ Some(0),
+ 1280,
+ 1024,
+ SurfaceType::Scanout,
+ )
.context("create_surface")?;
let mem = disp.framebuffer(surface_id).context("framebuffer")?;
diff --git a/gpu_display/src/gpu_display_stub.rs b/gpu_display/src/gpu_display_stub.rs
index 7c9fedcdb..53403561c 100644
--- a/gpu_display/src/gpu_display_stub.rs
+++ b/gpu_display/src/gpu_display_stub.rs
@@ -102,6 +102,7 @@ impl DisplayT for DisplayStub {
&mut self,
parent_surface_id: Option<u32>,
_surface_id: u32,
+ _scanout_id: Option<u32>,
width: u32,
height: u32,
_surf_type: SurfaceType,
diff --git a/gpu_display/src/gpu_display_win/mod.rs b/gpu_display/src/gpu_display_win/mod.rs
index aad0e9151..9a69f3763 100644
--- a/gpu_display/src/gpu_display_win/mod.rs
+++ b/gpu_display/src/gpu_display_win/mod.rs
@@ -207,6 +207,7 @@ impl DisplayT for DisplayWin {
&mut self,
parent_surface_id: Option<u32>,
_surface_id: u32,
+ _scanout_id: Option<u32>,
virtual_display_width: u32,
virtual_display_height: u32,
surface_type: SurfaceType,
diff --git a/gpu_display/src/gpu_display_wl.rs b/gpu_display/src/gpu_display_wl.rs
index cdc097468..0ac4a855f 100644
--- a/gpu_display/src/gpu_display_wl.rs
+++ b/gpu_display/src/gpu_display_wl.rs
@@ -180,14 +180,6 @@ impl GpuDisplaySurface for WaylandSurface {
dwl_surface_set_position(self.surface(), x, y);
}
}
-
- fn set_scanout_id(&mut self, scanout_id: u32) {
- // SAFETY:
- // Safe because only a valid surface is used.
- unsafe {
- dwl_surface_set_scanout_id(self.surface(), scanout_id);
- }
- }
}
/// A connection to the compositor and associated collection of state.
@@ -370,6 +362,7 @@ impl DisplayT for DisplayWl {
&mut self,
parent_surface_id: Option<u32>,
surface_id: u32,
+ scanout_id: Option<u32>,
width: u32,
height: u32,
surf_type: SurfaceType,
@@ -411,6 +404,14 @@ impl DisplayT for DisplayWl {
return Err(GpuDisplayError::CreateSurface);
}
+ if let Some(scanout_id) = scanout_id {
+ // SAFETY:
+ // Safe because only a valid surface is used.
+ unsafe {
+ dwl_surface_set_scanout_id(surface.0, scanout_id);
+ }
+ }
+
Ok(Box::new(WaylandSurface {
surface,
row_size,
diff --git a/gpu_display/src/gpu_display_x.rs b/gpu_display/src/gpu_display_x.rs
index b2250941a..56b68cd08 100644
--- a/gpu_display/src/gpu_display_x.rs
+++ b/gpu_display/src/gpu_display_x.rs
@@ -698,6 +698,7 @@ impl DisplayT for DisplayX {
&mut self,
parent_surface_id: Option<u32>,
_surface_id: u32,
+ _scanout_id: Option<u32>,
width: u32,
height: u32,
_surf_type: SurfaceType,
diff --git a/gpu_display/src/lib.rs b/gpu_display/src/lib.rs
index 9d74f1b31..8f9bbf9ff 100644
--- a/gpu_display/src/lib.rs
+++ b/gpu_display/src/lib.rs
@@ -243,11 +243,6 @@ trait GpuDisplaySurface {
fn on_shm_completion(&mut self, _shm_complete: u64) {
// no-op
}
-
- /// Sets the scanout ID for the surface.
- fn set_scanout_id(&mut self, _scanout_id: u32) {
- // no-op
- }
}
struct GpuDisplayEvents {
@@ -285,6 +280,7 @@ trait DisplayT: AsRawDescriptor {
&mut self,
parent_surface_id: Option<u32>,
surface_id: u32,
+ scanout_id: Option<u32>,
width: u32,
height: u32,
surf_type: SurfaceType,
@@ -455,6 +451,7 @@ impl GpuDisplay {
pub fn create_surface(
&mut self,
parent_surface_id: Option<u32>,
+ scanout_id: Option<u32>,
width: u32,
height: u32,
surf_type: SurfaceType,
@@ -469,6 +466,7 @@ impl GpuDisplay {
let new_surface = self.inner.create_surface(
parent_surface_id,
new_surface_id,
+ scanout_id,
width,
height,
surf_type,
@@ -598,15 +596,4 @@ impl GpuDisplay {
surface.set_position(x, y);
Ok(())
}
-
- /// Associates the scanout id with the given surface.
- pub fn set_scanout_id(&mut self, surface_id: u32, scanout_id: u32) -> GpuDisplayResult<()> {
- let surface = self
- .surfaces
- .get_mut(&surface_id)
- .ok_or(GpuDisplayError::InvalidSurfaceId)?;
-
- surface.set_scanout_id(scanout_id);
- Ok(())
- }
}
diff --git a/hypervisor/Cargo.toml b/hypervisor/Cargo.toml
index a43b4cef2..3ea794b38 100644
--- a/hypervisor/Cargo.toml
+++ b/hypervisor/Cargo.toml
@@ -10,6 +10,7 @@ whpx = []
gdb = ["gdbstub", "gdbstub_arch"]
geniezone = []
gunyah = []
+noncoherent-dma = []
[dependencies]
anyhow = "*"
diff --git a/hypervisor/src/caps.rs b/hypervisor/src/caps.rs
index 42e245ab9..b8a9c6905 100644
--- a/hypervisor/src/caps.rs
+++ b/hypervisor/src/caps.rs
@@ -54,4 +54,6 @@ pub enum VmCap {
BusLockDetect,
/// Supports read-only memory regions.
ReadOnlyMemoryRegion,
+ /// VM can set guest memory cache noncoherent DMA flag
+ MemNoncoherentDma,
}
diff --git a/hypervisor/src/geniezone/geniezone_sys/mod.rs b/hypervisor/src/geniezone/geniezone_sys/mod.rs
index 0a9c57e37..67865a935 100644
--- a/hypervisor/src/geniezone/geniezone_sys/mod.rs
+++ b/hypervisor/src/geniezone/geniezone_sys/mod.rs
@@ -9,7 +9,7 @@
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
-#[cfg(any(target_arch = "aarch64"))]
+#[cfg(target_arch = "aarch64")]
pub mod aarch64 {
pub mod bindings;
use base::ioctl_io_nr;
@@ -47,5 +47,5 @@ pub mod aarch64 {
ioctl_iow_nr!(GZVM_SET_DTB_CONFIG, GZVM_IOC_MAGIC, 0xff, gzvm_dtb_config);
}
-#[cfg(any(target_arch = "aarch64"))]
+#[cfg(target_arch = "aarch64")]
pub use aarch64::*;
diff --git a/hypervisor/src/geniezone/mod.rs b/hypervisor/src/geniezone/mod.rs
index 08f8dbc99..07bdeeb2e 100644
--- a/hypervisor/src/geniezone/mod.rs
+++ b/hypervisor/src/geniezone/mod.rs
@@ -68,6 +68,7 @@ use crate::HypervisorCap;
use crate::IoEventAddress;
use crate::IoOperation;
use crate::IoParams;
+use crate::MemCacheType;
use crate::MemSlot;
use crate::PsciVersion;
use crate::Vcpu;
@@ -912,6 +913,7 @@ impl Vm for GeniezoneVm {
VmCap::Protected => self.check_raw_capability(GeniezoneCap::ArmProtectedVm),
VmCap::EarlyInitCpuid => false,
VmCap::ReadOnlyMemoryRegion => false,
+ VmCap::MemNoncoherentDma => false,
}
}
@@ -929,6 +931,7 @@ impl Vm for GeniezoneVm {
mem: Box<dyn MappedRegion>,
read_only: bool,
log_dirty_pages: bool,
+ _cache: MemCacheType,
) -> Result<MemSlot> {
let pgsz = pagesize() as u64;
// GZVM require to set the user memory region with page size aligned size. Safe to extend
diff --git a/hypervisor/src/gunyah/mod.rs b/hypervisor/src/gunyah/mod.rs
index 898a4d94b..71503c8b9 100644
--- a/hypervisor/src/gunyah/mod.rs
+++ b/hypervisor/src/gunyah/mod.rs
@@ -438,6 +438,7 @@ impl Vm for GunyahVm {
#[cfg(target_arch = "x86_64")]
VmCap::BusLockDetect => false,
VmCap::ReadOnlyMemoryRegion => false,
+ VmCap::MemNoncoherentDma => false,
}
}
@@ -455,6 +456,7 @@ impl Vm for GunyahVm {
mem_region: Box<dyn MappedRegion>,
read_only: bool,
_log_dirty_pages: bool,
+ _cache: MemCacheType,
) -> Result<MemSlot> {
let pgsz = pagesize() as u64;
// Gunyah require to set the user memory region with page size aligned size. Safe to extend
diff --git a/hypervisor/src/haxm/vm.rs b/hypervisor/src/haxm/vm.rs
index 4314ff272..d28f91f7e 100644
--- a/hypervisor/src/haxm/vm.rs
+++ b/hypervisor/src/haxm/vm.rs
@@ -45,6 +45,7 @@ use crate::Datamatch;
use crate::DeviceKind;
use crate::Hypervisor;
use crate::IoEventAddress;
+use crate::MemCacheType;
use crate::MemSlot;
use crate::VcpuX86_64;
use crate::Vm;
@@ -224,6 +225,7 @@ impl Vm for HaxmVm {
VmCap::EarlyInitCpuid => false,
VmCap::BusLockDetect => false,
VmCap::ReadOnlyMemoryRegion => false,
+ VmCap::MemNoncoherentDma => false,
}
}
@@ -237,6 +239,7 @@ impl Vm for HaxmVm {
mem: Box<dyn MappedRegion>,
read_only: bool,
_log_dirty_pages: bool,
+ _cache: MemCacheType,
) -> Result<MemSlot> {
let size = mem.size() as u64;
let end_addr = guest_addr.checked_add(size).ok_or(Error::new(EOVERFLOW))?;
@@ -623,7 +626,13 @@ mod tests {
.unwrap();
let mem_ptr = mem.as_ptr();
let slot = vm
- .add_memory_region(GuestAddress(0x1000), Box::new(mem), false, false)
+ .add_memory_region(
+ GuestAddress(0x1000),
+ Box::new(mem),
+ false,
+ false,
+ MemCacheType::CacheCoherent,
+ )
.unwrap();
let removed_mem = vm.remove_memory_region(slot).unwrap();
assert_eq!(removed_mem.size(), mem_size);
diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs
index ce4fac1e0..e948a559c 100644
--- a/hypervisor/src/kvm/mod.rs
+++ b/hypervisor/src/kvm/mod.rs
@@ -81,6 +81,7 @@ use crate::IoParams;
use crate::IrqRoute;
use crate::IrqSource;
use crate::MPState;
+use crate::MemCacheType;
use crate::MemSlot;
use crate::Vcpu;
use crate::VcpuExit;
@@ -99,6 +100,7 @@ unsafe fn set_user_memory_region(
slot: MemSlot,
read_only: bool,
log_dirty_pages: bool,
+ cache: MemCacheType,
guest_addr: u64,
memory_size: u64,
userspace_addr: *mut u8,
@@ -107,6 +109,9 @@ unsafe fn set_user_memory_region(
if log_dirty_pages {
flags |= KVM_MEM_LOG_DIRTY_PAGES;
}
+ if cache == MemCacheType::CacheNonCoherent {
+ flags |= KVM_MEM_NON_COHERENT_DMA;
+ }
let region = kvm_userspace_memory_region {
slot,
flags,
@@ -255,6 +260,7 @@ impl KvmVm {
region.index as MemSlot,
false,
false,
+ MemCacheType::CacheCoherent,
region.guest_addr.offset(),
region.size as u64,
region.host_addr as *mut u8,
@@ -565,6 +571,10 @@ impl Vm for KvmVm {
// When pKVM is the hypervisor, read-only memslots aren't supported, even for
// non-protected VMs.
VmCap::ReadOnlyMemoryRegion => !self.is_pkvm(),
+ VmCap::MemNoncoherentDma => {
+ cfg!(feature = "noncoherent-dma")
+ && self.check_raw_capability(KvmCap::MemNoncoherentDma)
+ }
}
}
@@ -599,6 +609,7 @@ impl Vm for KvmVm {
mem: Box<dyn MappedRegion>,
read_only: bool,
log_dirty_pages: bool,
+ cache: MemCacheType,
) -> Result<MemSlot> {
let pgsz = pagesize() as u64;
// KVM require to set the user memory region with page size aligned size. Safe to extend
@@ -618,6 +629,12 @@ impl Vm for KvmVm {
None => (regions.len() + self.guest_mem.num_regions() as usize) as MemSlot,
};
+ let cache_type = if self.check_capability(VmCap::MemNoncoherentDma) {
+ cache
+ } else {
+ MemCacheType::CacheCoherent
+ };
+
// SAFETY:
// Safe because we check that the given guest address is valid and has no overlaps. We also
// know that the pointer and size are correct because the MemoryMapping interface ensures
@@ -629,6 +646,7 @@ impl Vm for KvmVm {
slot,
read_only,
log_dirty_pages,
+ cache_type,
guest_addr.offset(),
size,
mem.as_ptr(),
@@ -663,7 +681,16 @@ impl Vm for KvmVm {
// SAFETY:
// Safe because the slot is checked against the list of memory slots.
unsafe {
- set_user_memory_region(&self.vm, slot, false, false, 0, 0, std::ptr::null_mut())?;
+ set_user_memory_region(
+ &self.vm,
+ slot,
+ false,
+ false,
+ MemCacheType::CacheCoherent,
+ 0,
+ 0,
+ std::ptr::null_mut(),
+ )?;
}
self.mem_slot_gaps.lock().push(Reverse(slot));
// This remove will always succeed because of the contains_key check above.
diff --git a/hypervisor/src/lib.rs b/hypervisor/src/lib.rs
index 9e2c959b0..87b1fce88 100644
--- a/hypervisor/src/lib.rs
+++ b/hypervisor/src/lib.rs
@@ -56,6 +56,12 @@ pub struct MemRegion {
pub size: u64,
}
+#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
+pub enum MemCacheType {
+ CacheCoherent,
+ CacheNonCoherent,
+}
+
/// This is intended for use with virtio-balloon, where a guest driver determines unused ranges and
/// requests they be freed. Use without the guest's knowledge is sure to break something.
pub enum BalloonEvent {
@@ -121,12 +127,20 @@ pub trait Vm: Send {
///
/// If `log_dirty_pages` is true, the slot number can be used to retrieve the pages written to
/// by the guest with `get_dirty_log`.
+ ///
+ /// `cache` can be used to set guest mem cache attribute if supported. Default is cache coherent
+ /// memory. Noncoherent memory means this memory might not be coherent from all access points,
+ /// e.g this could be the case when host GPU doesn't set the memory to be coherent with CPU
+ /// access. Setting this attribute would allow hypervisor to adjust guest mem control to ensure
+ /// synchronized guest access in noncoherent DMA case.
+ ///
fn add_memory_region(
&mut self,
guest_addr: GuestAddress,
mem_region: Box<dyn MappedRegion>,
read_only: bool,
log_dirty_pages: bool,
+ cache: MemCacheType,
) -> Result<MemSlot>;
/// Does a synchronous msync of the memory mapped at `slot`, syncing `size` bytes starting at
diff --git a/hypervisor/src/whpx/vm.rs b/hypervisor/src/whpx/vm.rs
index f7c553c4e..ea4ac14a0 100644
--- a/hypervisor/src/whpx/vm.rs
+++ b/hypervisor/src/whpx/vm.rs
@@ -54,6 +54,7 @@ use crate::DestinationMode;
use crate::DeviceKind;
use crate::IoEventAddress;
use crate::LapicState;
+use crate::MemCacheType;
use crate::MemSlot;
use crate::TriggerMode;
use crate::VcpuX86_64;
@@ -516,6 +517,7 @@ impl Vm for WhpxVm {
#[cfg(target_arch = "x86_64")]
VmCap::BusLockDetect => false,
VmCap::ReadOnlyMemoryRegion => true,
+ VmCap::MemNoncoherentDma => false,
}
}
@@ -529,6 +531,7 @@ impl Vm for WhpxVm {
mem: Box<dyn MappedRegion>,
read_only: bool,
log_dirty_pages: bool,
+ _cache: MemCacheType,
) -> Result<MemSlot> {
let size = mem.size() as u64;
let end_addr = guest_addr.checked_add(size).ok_or(Error::new(EOVERFLOW))?;
@@ -1002,8 +1005,14 @@ mod tests {
.from_shared_memory(&shm)
.build()
.unwrap();
- vm.add_memory_region(GuestAddress(0x1000), Box::new(mem), true, false)
- .unwrap();
+ vm.add_memory_region(
+ GuestAddress(0x1000),
+ Box::new(mem),
+ true,
+ false,
+ MemCacheType::CacheCoherent,
+ )
+ .unwrap();
}
#[test]
@@ -1023,7 +1032,13 @@ mod tests {
.unwrap();
let mem_ptr = mem.as_ptr();
let slot = vm
- .add_memory_region(GuestAddress(0x1000), Box::new(mem), false, false)
+ .add_memory_region(
+ GuestAddress(0x1000),
+ Box::new(mem),
+ false,
+ false,
+ MemCacheType::CacheCoherent,
+ )
.unwrap();
let removed_mem = vm.remove_memory_region(slot).unwrap();
assert_eq!(removed_mem.size(), mem_size);
@@ -1058,7 +1073,13 @@ mod tests {
.build()
.unwrap();
assert!(vm
- .add_memory_region(GuestAddress(0x2000), Box::new(mem), false, false)
+ .add_memory_region(
+ GuestAddress(0x2000),
+ Box::new(mem),
+ false,
+ false,
+ MemCacheType::CacheCoherent
+ )
.is_err());
}
@@ -1078,7 +1099,13 @@ mod tests {
.build()
.unwrap();
let slot = vm
- .add_memory_region(GuestAddress(0x10000), Box::new(mem), false, false)
+ .add_memory_region(
+ GuestAddress(0x10000),
+ Box::new(mem),
+ false,
+ false,
+ MemCacheType::CacheCoherent,
+ )
.unwrap();
vm.msync_memory_region(slot, mem_size - 1, 0).unwrap();
vm.msync_memory_region(slot, 0, mem_size).unwrap();
diff --git a/hypervisor/tests/dirty_log.rs b/hypervisor/tests/dirty_log.rs
index 8a2cba610..e6549caae 100644
--- a/hypervisor/tests/dirty_log.rs
+++ b/hypervisor/tests/dirty_log.rs
@@ -116,6 +116,7 @@ where
),
false,
true,
+ MemCacheType::CacheCoherent,
)
.expect("failed to register memory");
diff --git a/hypervisor/tests/kvm/main.rs b/hypervisor/tests/kvm/main.rs
index 125c4253a..2f9b83a2e 100644
--- a/hypervisor/tests/kvm/main.rs
+++ b/hypervisor/tests/kvm/main.rs
@@ -25,6 +25,7 @@ use hypervisor::Datamatch;
use hypervisor::Hypervisor;
use hypervisor::HypervisorCap;
use hypervisor::IoEventAddress;
+use hypervisor::MemCacheType::CacheCoherent;
use hypervisor::Vm;
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
use hypervisor::VmAArch64;
@@ -125,14 +126,21 @@ fn add_memory() {
let mut vm = KvmVm::new(&kvm, gm, Default::default()).unwrap();
let mem_size = 0x1000;
let mem = MemoryMappingBuilder::new(mem_size).build().unwrap();
- vm.add_memory_region(GuestAddress(pagesize() as u64), Box::new(mem), false, false)
- .unwrap();
+ vm.add_memory_region(
+ GuestAddress(pagesize() as u64),
+ Box::new(mem),
+ false,
+ false,
+ CacheCoherent,
+ )
+ .unwrap();
let mem = MemoryMappingBuilder::new(mem_size).build().unwrap();
vm.add_memory_region(
GuestAddress(0x10 * pagesize() as u64),
Box::new(mem),
false,
false,
+ CacheCoherent,
)
.unwrap();
}
@@ -144,8 +152,14 @@ fn add_memory_ro() {
let mut vm = KvmVm::new(&kvm, gm, Default::default()).unwrap();
let mem_size = 0x1000;
let mem = MemoryMappingBuilder::new(mem_size).build().unwrap();
- vm.add_memory_region(GuestAddress(pagesize() as u64), Box::new(mem), true, false)
- .unwrap();
+ vm.add_memory_region(
+ GuestAddress(pagesize() as u64),
+ Box::new(mem),
+ true,
+ false,
+ CacheCoherent,
+ )
+ .unwrap();
}
#[test]
@@ -157,7 +171,13 @@ fn remove_memory() {
let mem = MemoryMappingBuilder::new(mem_size).build().unwrap();
let mem_ptr = mem.as_ptr();
let slot = vm
- .add_memory_region(GuestAddress(pagesize() as u64), Box::new(mem), false, false)
+ .add_memory_region(
+ GuestAddress(pagesize() as u64),
+ Box::new(mem),
+ false,
+ false,
+ CacheCoherent,
+ )
.unwrap();
let removed_mem = vm.remove_memory_region(slot).unwrap();
assert_eq!(removed_mem.size(), mem_size);
@@ -184,7 +204,8 @@ fn overlap_memory() {
GuestAddress(2 * pagesize() as u64),
Box::new(mem),
false,
- false
+ false,
+ CacheCoherent,
)
.is_err());
}
@@ -201,7 +222,13 @@ fn sync_memory() {
let mem_size = pagesize();
let mem = MemoryMappingArena::new(mem_size).unwrap();
let slot = vm
- .add_memory_region(GuestAddress(pagesize() as u64), Box::new(mem), false, false)
+ .add_memory_region(
+ GuestAddress(pagesize() as u64),
+ Box::new(mem),
+ false,
+ false,
+ CacheCoherent,
+ )
.unwrap();
vm.msync_memory_region(slot, mem_size, 0).unwrap();
assert!(vm.msync_memory_region(slot, mem_size + 1, 0).is_err());
diff --git a/hypervisor/tests/read_only_memory.rs b/hypervisor/tests/read_only_memory.rs
index 6610f9459..ac19ea0f9 100644
--- a/hypervisor/tests/read_only_memory.rs
+++ b/hypervisor/tests/read_only_memory.rs
@@ -125,6 +125,7 @@ where
),
false,
false,
+ MemCacheType::CacheCoherent,
)
.expect("failed to register memory");
@@ -149,6 +150,7 @@ where
),
true,
false,
+ MemCacheType::CacheCoherent,
)
.expect("failed to register memory");
diff --git a/hypervisor/tests/remove_memory.rs b/hypervisor/tests/remove_memory.rs
index 4ca5fcc5a..a0a424a26 100644
--- a/hypervisor/tests/remove_memory.rs
+++ b/hypervisor/tests/remove_memory.rs
@@ -110,6 +110,7 @@ where
),
false,
false,
+ MemCacheType::CacheCoherent,
)
.expect("failed to register memory");
@@ -134,6 +135,7 @@ where
),
false,
false,
+ MemCacheType::CacheCoherent,
)
.expect("failed to register memory");
diff --git a/infra/README.recipes.md b/infra/README.recipes.md
index 3922a18bd..90c9efb1a 100644
--- a/infra/README.recipes.md
+++ b/infra/README.recipes.md
@@ -174,19 +174,19 @@ This recipe requires ambient luci authentication. To test locally run:
&mdash; **def [RunSteps](/infra/recipes/update_chromeos_merges.py#14)(api):**
-[depot_tools/recipe_modules/bot_update]: https://chromium.googlesource.com/chromium/tools/depot_tools.git/+/68c511349d6ee2a50e3b7e3ce96b8a06034d304e/recipes/README.recipes.md#recipe_modules-bot_update
-[depot_tools/recipe_modules/depot_tools]: https://chromium.googlesource.com/chromium/tools/depot_tools.git/+/68c511349d6ee2a50e3b7e3ce96b8a06034d304e/recipes/README.recipes.md#recipe_modules-depot_tools
-[depot_tools/recipe_modules/gclient]: https://chromium.googlesource.com/chromium/tools/depot_tools.git/+/68c511349d6ee2a50e3b7e3ce96b8a06034d304e/recipes/README.recipes.md#recipe_modules-gclient
-[depot_tools/recipe_modules/git]: https://chromium.googlesource.com/chromium/tools/depot_tools.git/+/68c511349d6ee2a50e3b7e3ce96b8a06034d304e/recipes/README.recipes.md#recipe_modules-git
-[depot_tools/recipe_modules/gsutil]: https://chromium.googlesource.com/chromium/tools/depot_tools.git/+/68c511349d6ee2a50e3b7e3ce96b8a06034d304e/recipes/README.recipes.md#recipe_modules-gsutil
-[recipe_engine/recipe_modules/buildbucket]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/1438ab1429e2c113301edbfc216d91843a218538/README.recipes.md#recipe_modules-buildbucket
-[recipe_engine/recipe_modules/cipd]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/1438ab1429e2c113301edbfc216d91843a218538/README.recipes.md#recipe_modules-cipd
-[recipe_engine/recipe_modules/context]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/1438ab1429e2c113301edbfc216d91843a218538/README.recipes.md#recipe_modules-context
-[recipe_engine/recipe_modules/file]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/1438ab1429e2c113301edbfc216d91843a218538/README.recipes.md#recipe_modules-file
-[recipe_engine/recipe_modules/json]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/1438ab1429e2c113301edbfc216d91843a218538/README.recipes.md#recipe_modules-json
-[recipe_engine/recipe_modules/path]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/1438ab1429e2c113301edbfc216d91843a218538/README.recipes.md#recipe_modules-path
-[recipe_engine/recipe_modules/platform]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/1438ab1429e2c113301edbfc216d91843a218538/README.recipes.md#recipe_modules-platform
-[recipe_engine/recipe_modules/properties]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/1438ab1429e2c113301edbfc216d91843a218538/README.recipes.md#recipe_modules-properties
-[recipe_engine/recipe_modules/raw_io]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/1438ab1429e2c113301edbfc216d91843a218538/README.recipes.md#recipe_modules-raw_io
-[recipe_engine/recipe_modules/step]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/1438ab1429e2c113301edbfc216d91843a218538/README.recipes.md#recipe_modules-step
-[recipe_engine/wkt/RecipeApi]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/1438ab1429e2c113301edbfc216d91843a218538/recipe_engine/recipe_api.py#473
+[depot_tools/recipe_modules/bot_update]: https://chromium.googlesource.com/chromium/tools/depot_tools.git/+/1592a89c9f98ae6a9cd757aed8b7f4cac07d5f1e/recipes/README.recipes.md#recipe_modules-bot_update
+[depot_tools/recipe_modules/depot_tools]: https://chromium.googlesource.com/chromium/tools/depot_tools.git/+/1592a89c9f98ae6a9cd757aed8b7f4cac07d5f1e/recipes/README.recipes.md#recipe_modules-depot_tools
+[depot_tools/recipe_modules/gclient]: https://chromium.googlesource.com/chromium/tools/depot_tools.git/+/1592a89c9f98ae6a9cd757aed8b7f4cac07d5f1e/recipes/README.recipes.md#recipe_modules-gclient
+[depot_tools/recipe_modules/git]: https://chromium.googlesource.com/chromium/tools/depot_tools.git/+/1592a89c9f98ae6a9cd757aed8b7f4cac07d5f1e/recipes/README.recipes.md#recipe_modules-git
+[depot_tools/recipe_modules/gsutil]: https://chromium.googlesource.com/chromium/tools/depot_tools.git/+/1592a89c9f98ae6a9cd757aed8b7f4cac07d5f1e/recipes/README.recipes.md#recipe_modules-gsutil
+[recipe_engine/recipe_modules/buildbucket]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/d68051a0c1478ad1b8bef1b54361b615beeb1a75/README.recipes.md#recipe_modules-buildbucket
+[recipe_engine/recipe_modules/cipd]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/d68051a0c1478ad1b8bef1b54361b615beeb1a75/README.recipes.md#recipe_modules-cipd
+[recipe_engine/recipe_modules/context]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/d68051a0c1478ad1b8bef1b54361b615beeb1a75/README.recipes.md#recipe_modules-context
+[recipe_engine/recipe_modules/file]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/d68051a0c1478ad1b8bef1b54361b615beeb1a75/README.recipes.md#recipe_modules-file
+[recipe_engine/recipe_modules/json]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/d68051a0c1478ad1b8bef1b54361b615beeb1a75/README.recipes.md#recipe_modules-json
+[recipe_engine/recipe_modules/path]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/d68051a0c1478ad1b8bef1b54361b615beeb1a75/README.recipes.md#recipe_modules-path
+[recipe_engine/recipe_modules/platform]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/d68051a0c1478ad1b8bef1b54361b615beeb1a75/README.recipes.md#recipe_modules-platform
+[recipe_engine/recipe_modules/properties]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/d68051a0c1478ad1b8bef1b54361b615beeb1a75/README.recipes.md#recipe_modules-properties
+[recipe_engine/recipe_modules/raw_io]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/d68051a0c1478ad1b8bef1b54361b615beeb1a75/README.recipes.md#recipe_modules-raw_io
+[recipe_engine/recipe_modules/step]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/d68051a0c1478ad1b8bef1b54361b615beeb1a75/README.recipes.md#recipe_modules-step
+[recipe_engine/wkt/RecipeApi]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/d68051a0c1478ad1b8bef1b54361b615beeb1a75/recipe_engine/recipe_api.py#473
diff --git a/infra/config/recipes.cfg b/infra/config/recipes.cfg
index 0470c3db8..ebf3bc248 100644
--- a/infra/config/recipes.cfg
+++ b/infra/config/recipes.cfg
@@ -20,12 +20,12 @@
"deps": {
"depot_tools": {
"branch": "refs/heads/main",
- "revision": "68c511349d6ee2a50e3b7e3ce96b8a06034d304e",
+ "revision": "1592a89c9f98ae6a9cd757aed8b7f4cac07d5f1e",
"url": "https://chromium.googlesource.com/chromium/tools/depot_tools.git"
},
"recipe_engine": {
"branch": "refs/heads/main",
- "revision": "1438ab1429e2c113301edbfc216d91843a218538",
+ "revision": "d68051a0c1478ad1b8bef1b54361b615beeb1a75",
"url": "https://chromium.googlesource.com/infra/luci/recipes-py.git"
}
},
diff --git a/io_uring/tests/uring.rs b/io_uring/tests/uring.rs
index 648ec4bfc..bb3cd53e4 100644
--- a/io_uring/tests/uring.rs
+++ b/io_uring/tests/uring.rs
@@ -463,7 +463,7 @@ fn wake_with_nop() {
const BUF_DATA: [u8; 16] = [0xf4; 16];
let uring = URingContext::new(4, None).map(Arc::new).unwrap();
- let (pipe_out, mut pipe_in) = pipe(true).unwrap();
+ let (pipe_out, mut pipe_in) = pipe().unwrap();
let (tx, rx) = channel();
let uring2 = uring.clone();
diff --git a/jail/seccomp/aarch64/gpu_common.policy b/jail/seccomp/aarch64/gpu_common.policy
index 7c8985112..abe6fb174 100644
--- a/jail/seccomp/aarch64/gpu_common.policy
+++ b/jail/seccomp/aarch64/gpu_common.policy
@@ -107,4 +107,7 @@ getegid: 1
## Rules for vmm-swap
userfaultfd: 1
# 0xc018aa3f == UFFDIO_API, 0xaa00 == USERFAULTFD_IOC_NEW
-# ioctl: arg1 == 0xc018aa3f || arg1 == 0xaa00
+ioctl: arg1 == 0xc018aa3f || arg1 == 0xaa00
+
+## Rules for mali shader dump (debug workflow)
+mkdirat: 1
diff --git a/jail/seccomp/arm/gpu_common.policy b/jail/seccomp/arm/gpu_common.policy
index d2aa18c9f..53907856e 100644
--- a/jail/seccomp/arm/gpu_common.policy
+++ b/jail/seccomp/arm/gpu_common.policy
@@ -120,3 +120,7 @@ getegid32: 1
userfaultfd: 1
# 0xc018aa3f == UFFDIO_API, 0xaa00 == USERFAULTFD_IOC_NEW
ioctl: arg1 == 0xc018aa3f || arg1 == 0xaa00
+
+## Rules for mali shader dump (debug workflow)
+mkdir: 1
+mkdirat: 1
diff --git a/kernel_loader/src/arm64.rs b/kernel_loader/src/arm64.rs
index e3dae40ad..a5b80b1bd 100644
--- a/kernel_loader/src/arm64.rs
+++ b/kernel_loader/src/arm64.rs
@@ -5,6 +5,7 @@
//! Linux arm64 kernel loader.
//! <https://www.kernel.org/doc/Documentation/arm64/booting.txt>
+use std::cmp::max;
use std::io;
use std::io::BufRead;
use std::io::Read;
@@ -12,6 +13,7 @@ use std::io::Seek;
use std::io::SeekFrom;
use std::mem::size_of_val;
+use base::warn;
use base::FileGetLen;
use base::FileReadWriteAtVolatile;
use base::VolatileSlice;
@@ -67,6 +69,7 @@ impl Arm64ImageHeader {
let image_size: u64 = self.image_size.into();
if image_size == 0 {
+ warn!("arm64 Image header has an effective size of zero");
// arm64/booting.txt:
// "Where image_size is zero, text_offset can be assumed to be 0x80000."
text_offset = ARM64_TEXT_OFFSET_DEFAULT;
@@ -95,6 +98,7 @@ where
let file_size = kernel_image.get_len().map_err(|_| Error::SeekKernelEnd)?;
let load_size = usize::try_from(file_size).map_err(|_| Error::InvalidKernelSize)?;
+ let range_size = max(file_size, u64::from(header.image_size));
let guest_slice = guest_mem
.get_slice_at_addr(load_addr, load_size)
@@ -105,7 +109,7 @@ where
Ok(LoadedKernel {
size: file_size,
- address_range: AddressRange::from_start_and_size(load_addr.offset(), file_size)
+ address_range: AddressRange::from_start_and_size(load_addr.offset(), range_size)
.ok_or(Error::InvalidKernelSize)?,
entry: load_addr,
})
@@ -156,9 +160,10 @@ fn load_arm64_kernel_from_reader<F: BufRead>(
}
let file_size = current_addr.offset_from(load_addr);
+ let range_size = max(file_size, u64::from(header.image_size));
Ok(LoadedKernel {
size: file_size,
- address_range: AddressRange::from_start_and_size(load_addr.offset(), file_size)
+ address_range: AddressRange::from_start_and_size(load_addr.offset(), range_size)
.ok_or(Error::InvalidKernelSize)?,
entry: load_addr,
})
diff --git a/kvm/src/cap.rs b/kvm/src/cap.rs
index 4c200e7c0..032403a00 100644
--- a/kvm/src/cap.rs
+++ b/kvm/src/cap.rs
@@ -125,4 +125,5 @@ pub enum Cap {
ArmMte = KVM_CAP_ARM_MTE,
#[cfg(target_arch = "x86_64")]
BusLockDetect = KVM_CAP_X86_BUS_LOCK_EXIT,
+ MemNoncoherentDma = KVM_CAP_USER_CONFIGURE_NONCOHERENT_DMA,
}
diff --git a/kvm_sys/bindgen.sh b/kvm_sys/bindgen.sh
index c2f0d2a53..28dc64399 100755
--- a/kvm_sys/bindgen.sh
+++ b/kvm_sys/bindgen.sh
@@ -15,6 +15,10 @@ use zerocopy::AsBytes;
use zerocopy::FromBytes;
use zerocopy::FromZeroes;
+// TODO(b/316337317): Update if new memslot flag is accepted in upstream
+pub const KVM_MEM_NON_COHERENT_DMA: u32 = 8;
+pub const KVM_CAP_USER_CONFIGURE_NONCOHERENT_DMA: u32 = 236;
+
// TODO(qwandor): Update this once the pKVM patches are merged upstream with a stable capability ID.
pub const KVM_CAP_ARM_PROTECTED_VM: u32 = 0xffbadab1;
pub const KVM_CAP_ARM_PROTECTED_VM_FLAGS_SET_FW_IPA: u32 = 0;
diff --git a/kvm_sys/src/aarch64/bindings.rs b/kvm_sys/src/aarch64/bindings.rs
index 5b05df226..43ec6f18d 100644
--- a/kvm_sys/src/aarch64/bindings.rs
+++ b/kvm_sys/src/aarch64/bindings.rs
@@ -13,6 +13,10 @@ use zerocopy::AsBytes;
use zerocopy::FromBytes;
use zerocopy::FromZeroes;
+// TODO(b/316337317): Update if new memslot flag is accepted in upstream
+pub const KVM_MEM_NON_COHERENT_DMA: u32 = 8;
+pub const KVM_CAP_USER_CONFIGURE_NONCOHERENT_DMA: u32 = 236;
+
// TODO(qwandor): Update this once the pKVM patches are merged upstream with a stable capability ID.
pub const KVM_CAP_ARM_PROTECTED_VM: u32 = 0xffbadab1;
pub const KVM_CAP_ARM_PROTECTED_VM_FLAGS_SET_FW_IPA: u32 = 0;
diff --git a/kvm_sys/src/riscv64/bindings.rs b/kvm_sys/src/riscv64/bindings.rs
index e55ae3d29..0c4f6e910 100644
--- a/kvm_sys/src/riscv64/bindings.rs
+++ b/kvm_sys/src/riscv64/bindings.rs
@@ -13,6 +13,10 @@ use zerocopy::AsBytes;
use zerocopy::FromBytes;
use zerocopy::FromZeroes;
+// TODO(b/316337317): Update if new memslot flag is accepted in upstream
+pub const KVM_MEM_NON_COHERENT_DMA: u32 = 8;
+pub const KVM_CAP_USER_CONFIGURE_NONCOHERENT_DMA: u32 = 236;
+
// TODO(qwandor): Update this once the pKVM patches are merged upstream with a stable capability ID.
pub const KVM_CAP_ARM_PROTECTED_VM: u32 = 0xffbadab1;
pub const KVM_CAP_ARM_PROTECTED_VM_FLAGS_SET_FW_IPA: u32 = 0;
diff --git a/kvm_sys/src/x86/bindings.rs b/kvm_sys/src/x86/bindings.rs
index 6a8717338..c491d6f43 100644
--- a/kvm_sys/src/x86/bindings.rs
+++ b/kvm_sys/src/x86/bindings.rs
@@ -13,6 +13,10 @@ use zerocopy::AsBytes;
use zerocopy::FromBytes;
use zerocopy::FromZeroes;
+// TODO(b/316337317): Update if new memslot flag is accepted in upstream
+pub const KVM_MEM_NON_COHERENT_DMA: u32 = 8;
+pub const KVM_CAP_USER_CONFIGURE_NONCOHERENT_DMA: u32 = 236;
+
// TODO(qwandor): Update this once the pKVM patches are merged upstream with a stable capability ID.
pub const KVM_CAP_ARM_PROTECTED_VM: u32 = 0xffbadab1;
pub const KVM_CAP_ARM_PROTECTED_VM_FLAGS_SET_FW_IPA: u32 = 0;
diff --git a/metrics/src/sys/windows.rs b/metrics/src/sys/windows.rs
index 4cdd5f23a..0c6f40731 100644
--- a/metrics/src/sys/windows.rs
+++ b/metrics/src/sys/windows.rs
@@ -7,13 +7,15 @@ pub mod gpu_metrics;
pub mod system_metrics;
pub mod wmi;
+use std::time::Duration;
+
pub use gpu_metrics::*;
pub(crate) use system_metrics::*;
use win_util::ProcessType;
use crate::protos::event_details::EmulatorProcessType;
-pub const METRIC_UPLOAD_INTERVAL_SECONDS: i64 = 60;
+pub const METRICS_UPLOAD_INTERVAL: Duration = Duration::from_secs(60);
pub const API_GUEST_ANGLE_VK_ENUM_NAME: &str = "API_GUEST_ANGLE_VK";
pub const API_HOST_ANGLE_D3D_ENUM_NAME: &str = "API_HOST_ANGLE_D3D";
pub const API_UNKNOWN_ENUM_NAME: &str = "API_UNKNOWN";
diff --git a/metrics/src/sys/windows/system_metrics.rs b/metrics/src/sys/windows/system_metrics.rs
index 86b9d781b..46402e17f 100644
--- a/metrics/src/sys/windows/system_metrics.rs
+++ b/metrics/src/sys/windows/system_metrics.rs
@@ -11,6 +11,7 @@ use std::sync::Weak;
use std::thread;
use std::thread::JoinHandle;
use std::time::Duration;
+use std::time::Instant;
use base::error;
use base::AsRawDescriptor;
@@ -41,7 +42,7 @@ use winapi::um::winnt::SYNCHRONIZE;
use crate::log_metric;
use crate::sys::windows::Error;
use crate::sys::windows::Result;
-use crate::sys::windows::METRIC_UPLOAD_INTERVAL_SECONDS;
+use crate::sys::windows::METRICS_UPLOAD_INTERVAL;
use crate::MetricEventType;
const BYTES_PER_MB: usize = 1024 * 1024;
@@ -73,7 +74,7 @@ impl Worker {
return;
}
};
- let mut last_metric_upload_time = Local::now();
+ let mut last_metric_upload_time = Instant::now();
'poll: loop {
let events = match event_ctx.wait_timeout(WORKER_REPORT_INTERVAL) {
Ok(events) => events,
@@ -107,9 +108,8 @@ impl Worker {
);
}
- fn upload_metrics(&self, last_metric_upload_time: &mut DateTime<Local>) {
- let time_elapsed = (Local::now() - *last_metric_upload_time).num_seconds();
- if time_elapsed >= METRIC_UPLOAD_INTERVAL_SECONDS {
+ fn upload_metrics(&self, last_metric_upload_time: &mut Instant) {
+ if last_metric_upload_time.elapsed() >= METRICS_UPLOAD_INTERVAL {
let mut memory_acc = self.memory_acc.lock().unwrap();
if let Some(acc) = &*memory_acc {
let mem = acc.accumulated.physical / acc.accumulated_count / BYTES_PER_MB;
@@ -166,7 +166,7 @@ impl Worker {
}
}
*io = None;
- *last_metric_upload_time = Local::now();
+ *last_metric_upload_time = Instant::now();
}
}
diff --git a/net_util/src/slirp/sys/windows/handler.rs b/net_util/src/slirp/sys/windows/handler.rs
index 66099bebb..455edc1d2 100644
--- a/net_util/src/slirp/sys/windows/handler.rs
+++ b/net_util/src/slirp/sys/windows/handler.rs
@@ -300,7 +300,7 @@ fn poll_sockets(mut sockets: Vec<WSAPOLLFD>) -> io::Result<Vec<WSAPOLLFD>> {
// Safe because sockets is guaranteed to be valid, and we handle error return codes below.
let poll_result = unsafe {
WSAPoll(
- sockets.as_mut_ptr() as *mut WSAPOLLFD,
+ sockets.as_mut_ptr(),
sockets.len() as u32,
1, /* timeout in ms */
)
diff --git a/riscv64/src/lib.rs b/riscv64/src/lib.rs
index d9245a63f..08dbf565f 100644
--- a/riscv64/src/lib.rs
+++ b/riscv64/src/lib.rs
@@ -13,6 +13,7 @@ use std::sync::mpsc;
use std::sync::Arc;
use arch::get_serial_cmdline;
+use arch::CpuSet;
use arch::DtbOverlay;
use arch::GetSerialCmdlineError;
use arch::RunnableLinuxVm;
diff --git a/rutabaga_gfx/Cargo.toml b/rutabaga_gfx/Cargo.toml
index ba1b858e8..99172e3ca 100644
--- a/rutabaga_gfx/Cargo.toml
+++ b/rutabaga_gfx/Cargo.toml
@@ -1,9 +1,9 @@
[package]
name = "rutabaga_gfx"
-version = "0.1.2"
+version = "0.1.3"
authors = ["The ChromiumOS Authors + Android Open Source Project"]
edition = "2021"
-description = "[highly unstable] Handling virtio-gpu protocols"
+description = "Handling virtio-gpu protocols"
license-file = "LICENSE"
[features]
@@ -25,14 +25,14 @@ zerocopy = { version = "0.7", features = ["derive"] }
log = "0.4"
+# To build latest Vulkano, change version to git = "https://github.com/vulkano-rs/vulkano.git"
+# vulkano = { version = "0.31.0", optional = true }
+
[target.'cfg(any(target_os = "android", target_os = "linux"))'.dependencies]
nix = { version = "0.27.1", features = ["event", "feature", "fs", "mman", "socket", "uio", "ioctl"] }
[target.'cfg(windows)'.dependencies]
-winapi = "0.3"
-
-# To build latest Vulkano, change version to git = "https:/github.com/vulkano-rs/vulkano.git"
-# vulkano = { version = "0.31.0", optional = true }
+winapi = {version = "0.3", features = ["winnt", "handleapi", "processthreadsapi", "winbase"]}
[build-dependencies]
pkg-config = "0.3"
diff --git a/rutabaga_gfx/build.rs b/rutabaga_gfx/build.rs
index 725e70443..3acb58ea4 100644
--- a/rutabaga_gfx/build.rs
+++ b/rutabaga_gfx/build.rs
@@ -248,7 +248,12 @@ fn gfxstream() -> Result<()> {
println!("cargo:rustc-link-search={}", gfxstream_path);
Ok(())
} else {
- pkg_config::Config::new().probe("gfxstream_backend")?;
+ let gfxstream_lib = pkg_config::Config::new().probe("gfxstream_backend")?;
+
+ if gfxstream_lib.defines.contains_key("GFXSTREAM_UNSTABLE") {
+ println!("cargo:rustc-cfg=gfxstream_unstable");
+ }
+
pkg_config::Config::new().probe("aemu_base")?;
pkg_config::Config::new().probe("aemu_host_common")?;
pkg_config::Config::new().probe("aemu_logging")?;
diff --git a/rutabaga_gfx/ffi/Cargo.toml b/rutabaga_gfx/ffi/Cargo.toml
index 928b07aa3..8842efd25 100644
--- a/rutabaga_gfx/ffi/Cargo.toml
+++ b/rutabaga_gfx/ffi/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "rutabaga_gfx_ffi"
-version = "0.1.2"
+version = "0.1.3"
authors = ["The ChromiumOS Authors + Android Open Source Project"]
edition = "2021"
description = "Handling virtio-gpu protocols with C API"
@@ -11,7 +11,7 @@ name = "rutabaga_gfx_ffi"
crate-type = ["cdylib", "staticlib"]
[dependencies]
-rutabaga_gfx = { path = "../", version = "0.1.2"}
+rutabaga_gfx = { path = "../", version = "0.1.3"}
libc = "0.2.93"
log = "0.4"
once_cell = "1.7"
diff --git a/rutabaga_gfx/ffi/Makefile b/rutabaga_gfx/ffi/Makefile
index cd6b194b0..f8c7820bf 100644
--- a/rutabaga_gfx/ffi/Makefile
+++ b/rutabaga_gfx/ffi/Makefile
@@ -39,7 +39,7 @@ ifeq ($(shell pkg-config --exists $(GFXSTREAM_DEP) && echo 1),1)
gfxstream_feature :=--features=gfxstream
endif
-RUTABAGA_VERSION := $(RUTABAGA_VERSION_MAJOR).1.2
+RUTABAGA_VERSION := $(RUTABAGA_VERSION_MAJOR).1.3
all: build
diff --git a/rutabaga_gfx/ffi/build.rs b/rutabaga_gfx/ffi/build.rs
index 3118ab858..3a407c711 100644
--- a/rutabaga_gfx/ffi/build.rs
+++ b/rutabaga_gfx/ffi/build.rs
@@ -26,7 +26,7 @@ libdir=${exec_prefix}/lib
Name: rutabaga_gfx_ffi
Description: C FFI bindings to Rutabaga VGI
-Version: 0.1.2
+Version: 0.1.3
Cflags: -I${includedir}
Libs: -L${libdir} -lrutabaga_gfx_ffi
"###,
diff --git a/rutabaga_gfx/ffi/src/include/rutabaga_gfx_ffi.h b/rutabaga_gfx/ffi/src/include/rutabaga_gfx_ffi.h
index 4370fc27a..fe3ccc50b 100644
--- a/rutabaga_gfx/ffi/src/include/rutabaga_gfx_ffi.h
+++ b/rutabaga_gfx/ffi/src/include/rutabaga_gfx_ffi.h
@@ -27,7 +27,7 @@ extern "C" {
*/
#define RUTABAGA_VERSION_MAJOR 0
#define RUTABAGA_VERSION_MINOR 1
-#define RUTABAGA_VERSION_PATCH 2
+#define RUTABAGA_VERSION_PATCH 3
/**
* Rutabaga capsets.
@@ -163,7 +163,7 @@ struct rutabaga_command {
uint8_t *cmd;
/**
- * Unstable, don't use until version > 0.1.2
+ * Unstable, don't use until version > 0.1.3
*/
uint32_t num_in_fences;
uint64_t *fence_ids;
@@ -348,6 +348,7 @@ int32_t rutabaga_create_fence(struct rutabaga *ptr, const struct rutabaga_fence
*
* # Safety
* - `dir` must be a null-terminated C-string.
+ * - Unstable, don't use until version > 0.1.3
*/
int32_t rutabaga_snapshot(struct rutabaga *ptr, const char *dir);
@@ -356,6 +357,7 @@ int32_t rutabaga_snapshot(struct rutabaga *ptr, const char *dir);
*
* # Safety
* - `dir` must be a null-terminated C-string.
+ * - Unstable, don't use until version > 0.1.3
*/
int32_t rutabaga_restore(struct rutabaga *ptr, const char *dir);
diff --git a/rutabaga_gfx/ffi/src/lib.rs b/rutabaga_gfx/ffi/src/lib.rs
index 7bb016572..ef058a420 100644
--- a/rutabaga_gfx/ffi/src/lib.rs
+++ b/rutabaga_gfx/ffi/src/lib.rs
@@ -23,11 +23,19 @@ use std::ptr::null_mut;
use std::slice::from_raw_parts;
use std::slice::from_raw_parts_mut;
+#[cfg(unix)]
use libc::iovec;
use libc::EINVAL;
use libc::ESRCH;
use rutabaga_gfx::*;
+#[cfg(not(unix))]
+#[repr(C)]
+pub struct iovec {
+ pub iov_base: *mut c_void,
+ pub iov_len: usize,
+}
+
const NO_ERROR: i32 = 0;
const RUTABAGA_WSI_SURFACELESS: u64 = 1;
@@ -500,6 +508,10 @@ pub unsafe extern "C" fn rutabaga_resource_create_blob(
}
let mut handle_opt: Option<RutabagaHandle> = None;
+
+ // Only needed on Unix, since there is no way to create a handle from guest memory on
+ // Windows.
+ #[cfg(unix)]
if let Some(hnd) = handle {
handle_opt = Some(RutabagaHandle {
os_handle: RutabagaDescriptor::from_raw_descriptor(
@@ -613,12 +625,13 @@ pub extern "C" fn rutabaga_create_fence(ptr: &mut rutabaga, fence: &rutabaga_fen
pub extern "C" fn rutabaga_snapshot(ptr: &mut rutabaga, dir: *const c_char) -> i32 {
catch_unwind(AssertUnwindSafe(|| {
// SAFETY: Caller guarantees a valid C string.
- let dir = match unsafe { CStr::from_ptr(dir) }.to_str() {
- Ok(x) => x,
- Err(_) => return -EINVAL,
- };
- let file = return_on_io_error!(File::create(Path::new(dir).join("snapshot")));
- let result = ptr.snapshot(&mut std::io::BufWriter::new(file));
+ let c_str_slice = unsafe { CStr::from_ptr(dir) };
+
+ let result = c_str_slice.to_str();
+ let directory = return_on_error!(result);
+
+ let file = return_on_io_error!(File::create(Path::new(directory).join("snapshot")));
+ let result = ptr.snapshot(&mut std::io::BufWriter::new(file), directory);
return_result(result)
}))
.unwrap_or(-ESRCH)
@@ -628,13 +641,13 @@ pub extern "C" fn rutabaga_snapshot(ptr: &mut rutabaga, dir: *const c_char) -> i
pub extern "C" fn rutabaga_restore(ptr: &mut rutabaga, dir: *const c_char) -> i32 {
catch_unwind(AssertUnwindSafe(|| {
// SAFETY: Caller guarantees a valid C string.
- let dir = unsafe { CStr::from_ptr(dir) };
- let dir = match dir.to_str() {
- Ok(x) => x,
- Err(_) => return -EINVAL,
- };
- let file = return_on_io_error!(File::open(Path::new(dir).join("snapshot")));
- let result = ptr.restore(&mut std::io::BufReader::new(file));
+ let c_str_slice = unsafe { CStr::from_ptr(dir) };
+
+ let result = c_str_slice.to_str();
+ let directory = return_on_error!(result);
+
+ let file = return_on_io_error!(File::open(Path::new(directory).join("snapshot")));
+ let result = ptr.restore(&mut std::io::BufReader::new(file), directory);
return_result(result)
}))
.unwrap_or(-ESRCH)
diff --git a/rutabaga_gfx/src/cross_domain/cross_domain_protocol.rs b/rutabaga_gfx/src/cross_domain/cross_domain_protocol.rs
index ce7d5fb5c..d51d58662 100644
--- a/rutabaga_gfx/src/cross_domain/cross_domain_protocol.rs
+++ b/rutabaga_gfx/src/cross_domain/cross_domain_protocol.rs
@@ -24,8 +24,8 @@ pub const CROSS_DOMAIN_CMD_WRITE: u8 = 7;
pub const CROSS_DOMAIN_CHANNEL_TYPE_WAYLAND: u32 = 0x0001;
pub const CROSS_DOMAIN_CHANNEL_TYPE_CAMERA: u32 = 0x0002;
-/// The maximum number of identifiers (value inspired by wp_linux_dmabuf)
-pub const CROSS_DOMAIN_MAX_IDENTIFIERS: usize = 4;
+/// The maximum number of identifiers
+pub const CROSS_DOMAIN_MAX_IDENTIFIERS: usize = 28;
/// virtgpu memory resource ID. Also works with non-blob memory resources, despite the name.
pub const CROSS_DOMAIN_ID_TYPE_VIRTGPU_BLOB: u32 = 1;
diff --git a/rutabaga_gfx/src/cross_domain/sys/mod.rs b/rutabaga_gfx/src/cross_domain/sys/mod.rs
index fbfd01cf2..51f40cd25 100644
--- a/rutabaga_gfx/src/cross_domain/sys/mod.rs
+++ b/rutabaga_gfx/src/cross_domain/sys/mod.rs
@@ -7,7 +7,8 @@ cfg_if::cfg_if! {
pub(crate) mod linux;
mod epoll_internal;
use linux as platform;
- } else if #[cfg(any(target_os = "fuchsia",target_os = "windows", target_os = "macos"))] {
+ } else if #[cfg(any(target_os = "fuchsia",target_os = "windows", target_os = "macos",
+ target_os = "nto"))] {
pub(crate) mod stub;
use stub as platform;
} else {
diff --git a/rutabaga_gfx/src/gfxstream.rs b/rutabaga_gfx/src/gfxstream.rs
index 545eecc81..f1bff351c 100644
--- a/rutabaga_gfx/src/gfxstream.rs
+++ b/rutabaga_gfx/src/gfxstream.rs
@@ -9,6 +9,8 @@
#![cfg(feature = "gfxstream")]
use std::convert::TryInto;
+#[cfg(gfxstream_unstable)]
+use std::ffi::CString;
use std::io::IoSliceMut;
use std::mem::size_of;
use std::os::raw::c_char;
@@ -182,6 +184,12 @@ extern "C" {
name: *const c_char,
context_init: u32,
) -> c_int;
+
+ #[cfg(gfxstream_unstable)]
+ fn stream_renderer_snapshot(dir: *const c_char) -> c_int;
+
+ #[cfg(gfxstream_unstable)]
+ fn stream_renderer_restore(dir: *const c_char) -> c_int;
}
/// The virtio-gpu backend state tracker which supports accelerated rendering.
@@ -733,4 +741,27 @@ impl RutabagaComponent for Gfxstream {
fence_handler,
}))
}
+
+ #[cfg(gfxstream_unstable)]
+ fn snapshot(&self, directory: &str) -> RutabagaResult<()> {
+ let cstring = CString::new(directory)?;
+
+ // SAFETY:
+ // Safe because directory string is valid
+ let ret = unsafe { stream_renderer_snapshot(cstring.as_ptr() as *const c_char) };
+ ret_to_res(ret)?;
+
+ Ok(())
+ }
+
+ #[cfg(gfxstream_unstable)]
+ fn restore(&self, directory: &str) -> RutabagaResult<()> {
+ let cstring = CString::new(directory)?;
+
+ // SAFETY:
+ // Safe because directory string is valid
+ let ret = unsafe { stream_renderer_restore(cstring.as_ptr() as *const c_char) };
+ ret_to_res(ret)?;
+ Ok(())
+ }
}
diff --git a/rutabaga_gfx/src/rutabaga_core.rs b/rutabaga_gfx/src/rutabaga_core.rs
index 740f840fd..9e41c9a2e 100644
--- a/rutabaga_gfx/src/rutabaga_core.rs
+++ b/rutabaga_gfx/src/rutabaga_core.rs
@@ -199,6 +199,16 @@ pub trait RutabagaComponent {
) -> RutabagaResult<Box<dyn RutabagaContext>> {
Err(RutabagaError::Unsupported)
}
+
+ /// Implementations must snapshot to the specified directory
+ fn snapshot(&self, _directory: &str) -> RutabagaResult<()> {
+ Err(RutabagaError::Unsupported)
+ }
+
+ /// Implementations must restore from the specified directory
+ fn restore(&self, _directory: &str) -> RutabagaResult<()> {
+ Err(RutabagaError::Unsupported)
+ }
}
pub trait RutabagaContext {
@@ -339,54 +349,40 @@ pub struct Rutabaga {
impl Rutabaga {
/// Take a snapshot of Rutabaga's current state. The snapshot is serialized into an opaque byte
/// stream and written to `w`.
- ///
- /// Only supports Mode2D.
- pub fn snapshot(&self, w: &mut impl Write) -> RutabagaResult<()> {
- // We current only support snapshotting Rutabaga2D.
- if !(self.contexts.is_empty()
- && self
+ pub fn snapshot(&self, w: &mut impl Write, directory: &str) -> RutabagaResult<()> {
+ if self.default_component == RutabagaComponentType::Gfxstream {
+ let component = self
.components
- .keys()
- .all(|t| *t == RutabagaComponentType::Rutabaga2D)
- && self.default_component == RutabagaComponentType::Rutabaga2D
- && self.capset_info.is_empty())
- {
- return Err(RutabagaError::Unsupported);
+ .get(&self.default_component)
+ .ok_or(RutabagaError::InvalidComponent)?;
+
+ component.snapshot(directory)?
+ } else if self.default_component == RutabagaComponentType::Rutabaga2D {
+ let snapshot = RutabagaSnapshot {
+ resources: self
+ .resources
+ .iter()
+ .map(|(i, r)| {
+ let info = r.info_2d.as_ref().ok_or(RutabagaError::Unsupported)?;
+ assert_eq!(
+ usize::try_from(info.width * info.height * 4).unwrap(),
+ info.host_mem.len()
+ );
+ assert_eq!(usize::try_from(r.size).unwrap(), info.host_mem.len());
+ let s = RutabagaResourceSnapshot {
+ resource_id: r.resource_id,
+ width: info.width,
+ height: info.height,
+ };
+ Ok((*i, s))
+ })
+ .collect::<RutabagaResult<_>>()?,
+ };
+
+ return snapshot.serialize_to(w).map_err(RutabagaError::IoError);
}
- let snapshot = RutabagaSnapshot {
- resources: self
- .resources
- .iter()
- .map(|(i, r)| {
- if !(r.handle.is_none()
- && !r.blob
- && r.blob_mem == 0
- && r.blob_flags == 0
- && r.map_info.is_none()
- && r.info_3d.is_none()
- && r.vulkan_info.is_none()
- && r.component_mask == 1 << (RutabagaComponentType::Rutabaga2D as u8)
- && r.mapping.is_none())
- {
- return Err(RutabagaError::Unsupported);
- }
- let info = r.info_2d.as_ref().ok_or(RutabagaError::Unsupported)?;
- assert_eq!(
- usize::try_from(info.width * info.height * 4).unwrap(),
- info.host_mem.len()
- );
- assert_eq!(usize::try_from(r.size).unwrap(), info.host_mem.len());
- let s = RutabagaResourceSnapshot {
- resource_id: r.resource_id,
- width: info.width,
- height: info.height,
- };
- Ok((*i, s))
- })
- .collect::<RutabagaResult<_>>()?,
- };
- snapshot.serialize_to(w).map_err(RutabagaError::IoError)
+ Err(RutabagaError::Unsupported)
}
/// Restore Rutabaga to a previously snapshot'd state.
@@ -403,62 +399,60 @@ impl Rutabaga {
/// * ModeVirglRenderer
/// * Not supported.
/// * ModeGfxstream
- /// * Not supported.
+ /// * WiP support.
///
/// NOTES: This is required because the pointers to backing memory aren't stable, help from the
/// VMM is necessary. In an alternative approach, the VMM could supply Rutabaga with callbacks
/// to translate to/from stable guest physical addresses, but it is unclear how well that
/// approach would scale to support 3D modes, which have others problems that require VMM help,
/// like resource handles.
- pub fn restore(&mut self, r: &mut impl Read) -> RutabagaResult<()> {
- let snapshot = RutabagaSnapshot::deserialize_from(r).map_err(RutabagaError::IoError)?;
-
- // We currently only support restoring to a fresh Rutabaga2D instance.
- if !(self.resources.is_empty()
- && self.contexts.is_empty()
- && self
+ pub fn restore(&mut self, r: &mut impl Read, directory: &str) -> RutabagaResult<()> {
+ if self.default_component == RutabagaComponentType::Gfxstream {
+ let component = self
.components
- .keys()
- .all(|t| *t == RutabagaComponentType::Rutabaga2D)
- && self.default_component == RutabagaComponentType::Rutabaga2D
- && self.capset_info.is_empty())
- {
- return Err(RutabagaError::Unsupported);
+ .get_mut(&self.default_component)
+ .ok_or(RutabagaError::InvalidComponent)?;
+ component.restore(directory)?
+ } else if self.default_component == RutabagaComponentType::Rutabaga2D {
+ let snapshot = RutabagaSnapshot::deserialize_from(r).map_err(RutabagaError::IoError)?;
+
+ self.resources = snapshot
+ .resources
+ .into_iter()
+ .map(|(i, s)| {
+ let size = u64::from(s.width * s.height * 4);
+ let r = RutabagaResource {
+ resource_id: s.resource_id,
+ handle: None,
+ blob: false,
+ blob_mem: 0,
+ blob_flags: 0,
+ map_info: None,
+ info_2d: Some(Rutabaga2DInfo {
+ width: s.width,
+ height: s.height,
+ host_mem: vec![0; usize::try_from(size).unwrap()],
+ }),
+ info_3d: None,
+ vulkan_info: None,
+ // NOTE: `RutabagaResource::backing_iovecs` isn't snapshotted because the
+ // pointers won't be valid at restore time, see the `Rutabaga::restore` doc.
+ // If the client doesn't attach new iovecs, the restored resource will
+ // behave as if they had been detached (instead of segfaulting on the stale
+ // iovec pointers).
+ backing_iovecs: None,
+ component_mask: 1 << (RutabagaComponentType::Rutabaga2D as u8),
+ size,
+ mapping: None,
+ };
+ (i, r)
+ })
+ .collect();
+
+ return Ok(());
}
- self.resources = snapshot
- .resources
- .into_iter()
- .map(|(i, s)| {
- let size = u64::from(s.width * s.height * 4);
- let r = RutabagaResource {
- resource_id: s.resource_id,
- handle: None,
- blob: false,
- blob_mem: 0,
- blob_flags: 0,
- map_info: None,
- info_2d: Some(Rutabaga2DInfo {
- width: s.width,
- height: s.height,
- host_mem: vec![0; usize::try_from(size).unwrap()],
- }),
- info_3d: None,
- vulkan_info: None,
- // NOTE: `RutabagaResource::backing_iovecs` isn't snapshotted because the
- // pointers won't be valid at restore time, see the `Rutabaga::restore` doc. If
- // the client doesn't attach new iovecs, the restored resource will behave as
- // if they had been detached (instead of segfaulting on the stale iovec
- // pointers).
- backing_iovecs: None,
- component_mask: 1 << (RutabagaComponentType::Rutabaga2D as u8),
- size,
- mapping: None,
- };
- (i, r)
- })
- .collect();
- Ok(())
+ Err(RutabagaError::Unsupported)
}
fn capset_id_to_component_type(&self, capset_id: u32) -> RutabagaResult<RutabagaComponentType> {
@@ -883,7 +877,7 @@ impl Rutabaga {
component.export_fence(fence_id)
}
- /// Creates a context with the given `ctx_id` and `context_init` variable.
+ /// Creates snapshotth the given `ctx_id` and `context_init` variable.
/// `context_init` is used to determine which rutabaga component creates the context.
pub fn create_context(
&mut self,
@@ -1242,10 +1236,10 @@ mod tests {
let mut buffer = std::io::Cursor::new(Vec::new());
let rutabaga1 = new_2d();
- rutabaga1.snapshot(&mut buffer).unwrap();
+ rutabaga1.snapshot(&mut buffer, "").unwrap();
let mut rutabaga1 = new_2d();
- rutabaga1.restore(&mut &buffer.get_ref()[..]).unwrap();
+ rutabaga1.restore(&mut &buffer.get_ref()[..], "").unwrap();
}
#[test]
@@ -1279,10 +1273,10 @@ mod tests {
}],
)
.unwrap();
- rutabaga1.snapshot(&mut buffer).unwrap();
+ rutabaga1.snapshot(&mut buffer, "").unwrap();
let mut rutabaga2 = new_2d();
- rutabaga2.restore(&mut &buffer.get_ref()[..]).unwrap();
+ rutabaga2.restore(&mut &buffer.get_ref()[..], "").unwrap();
assert_eq!(rutabaga2.resources.len(), 1);
let rutabaga_resource = rutabaga2.resources.get(&resource_id).unwrap();
diff --git a/rutabaga_gfx/src/rutabaga_gralloc/minigbm.rs b/rutabaga_gfx/src/rutabaga_gralloc/minigbm.rs
index 812f32a27..a7c12ae56 100644
--- a/rutabaga_gfx/src/rutabaga_gralloc/minigbm.rs
+++ b/rutabaga_gfx/src/rutabaga_gralloc/minigbm.rs
@@ -8,12 +8,11 @@
#![cfg(feature = "minigbm")]
-use std::ffi::CStr;
use std::fs::File;
use std::io::Error;
use std::io::Seek;
use std::io::SeekFrom;
-use std::os::raw::c_char;
+use std::os::fd::FromRawFd;
use std::sync::Arc;
use crate::rutabaga_gralloc::formats::DrmFormat;
@@ -21,8 +20,6 @@ use crate::rutabaga_gralloc::gralloc::Gralloc;
use crate::rutabaga_gralloc::gralloc::ImageAllocationInfo;
use crate::rutabaga_gralloc::gralloc::ImageMemoryRequirements;
use crate::rutabaga_gralloc::minigbm_bindings::*;
-use crate::rutabaga_gralloc::rendernode;
-use crate::rutabaga_os::AsRawDescriptor;
use crate::rutabaga_os::FromRawDescriptor;
use crate::rutabaga_utils::*;
@@ -53,38 +50,34 @@ impl Drop for MinigbmDeviceInner {
pub struct MinigbmDevice {
minigbm_device: Arc<MinigbmDeviceInner>,
last_buffer: Option<Arc<MinigbmBuffer>>,
- device_name: &'static str,
}
impl MinigbmDevice {
/// Returns a new `MinigbmDevice` if there is a rendernode in `/dev/dri/` that is accepted by
/// the minigbm library.
pub fn init() -> RutabagaResult<Box<dyn Gralloc>> {
- let undesired: &[&str] = &["vgem", "pvr"];
- let fd = rendernode::open_device(undesired)?;
-
+ let descriptor: File;
+ let gbm: *mut gbm_device;
// SAFETY:
- // gbm_create_device is safe to call with a valid fd, and we check that a valid one is
- // returned. If the fd does not refer to a DRM device, gbm_create_device will reject it.
- let gbm = unsafe { gbm_create_device(fd.as_raw_descriptor()) };
- if gbm.is_null() {
- return Err(RutabagaError::IoError(Error::last_os_error()));
- }
+ // Safe because minigbm_create_default_device is safe to call with an unused fd,
+ // and fd is guaranteed to be overwritten with a valid descriptor when a non-null
+ // pointer is returned.
+ unsafe {
+ let mut fd = -1;
- // SAFETY:
- // Safe because a valid minigbm device has a statically allocated string associated with
- // it, which is valid for the lifetime of the process.
- let backend_name: *const c_char = unsafe { gbm_device_get_backend_name(gbm) };
- // SAFETY:
- // Safe because a valid minigbm device has a statically allocated string associated with
- // it, which is valid for the lifetime of the process.
- let c_str: &CStr = unsafe { CStr::from_ptr(backend_name) };
- let device_name: &str = c_str.to_str()?;
+ gbm = minigbm_create_default_device(&mut fd);
+ if gbm.is_null() {
+ return Err(RutabagaError::IoError(Error::last_os_error()));
+ }
+ descriptor = File::from_raw_fd(fd);
+ }
Ok(Box::new(MinigbmDevice {
- minigbm_device: Arc::new(MinigbmDeviceInner { _fd: fd, gbm }),
+ minigbm_device: Arc::new(MinigbmDeviceInner {
+ _fd: descriptor,
+ gbm,
+ }),
last_buffer: None,
- device_name,
}))
}
}
@@ -120,11 +113,7 @@ impl Gralloc for MinigbmDevice {
let mut reqs: ImageMemoryRequirements = Default::default();
let gbm_buffer = MinigbmBuffer(bo, self.clone());
- // Intel GPUs typically only use cached memory buffers. This will change with dGPUs, but
- // perhaps minigbm will be deprecated by then. Other display drivers (rockchip, mediatek,
- // amdgpu) typically use write combine memory. We can also consider use flags too if this
- // heuristic proves insufficient.
- if self.device_name == "i915" {
+ if gbm_buffer.cached() {
reqs.map_info = RUTABAGA_MAP_CACHE_CACHED;
} else {
reqs.map_info = RUTABAGA_MAP_CACHE_WC;
@@ -254,6 +243,14 @@ impl MinigbmBuffer {
unsafe { gbm_bo_get_stride_for_plane(self.0, plane) }
}
+ /// Should buffer use cached mapping to guest
+ pub fn cached(&self) -> bool {
+ // SAFETY:
+ // This is always safe to call with a valid gbm_bo pointer.
+ let mode = unsafe { gbm_bo_get_map_info(self.0) };
+ mode == gbm_bo_map_cache_mode::GBM_BO_MAP_CACHE_CACHED
+ }
+
/// Exports a new dmabuf/prime file descriptor.
pub fn export(&self) -> RutabagaResult<File> {
// SAFETY:
diff --git a/rutabaga_gfx/src/rutabaga_gralloc/minigbm_bindings.rs b/rutabaga_gfx/src/rutabaga_gralloc/minigbm_bindings.rs
index 8ed422114..b47d68c86 100644
--- a/rutabaga_gfx/src/rutabaga_gralloc/minigbm_bindings.rs
+++ b/rutabaga_gfx/src/rutabaga_gralloc/minigbm_bindings.rs
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-// Generated with bindgen --allowlist-function='gbm_.*' --allowlist-type='gbm_.*' minigbm/gbm.h
+// Generated with bindgen --default-enum-style=rust --allowlist-function='gbm_.*' --allowlist-type='gbm_.*' minigbm/gbm.h
// Then modified manually
#![cfg(feature = "minigbm")]
@@ -149,6 +149,21 @@ pub type gbm_bo_transfer_flags = u32;
extern "C" {
pub fn gbm_bo_unmap(bo: *mut gbm_bo, map_data: *mut c_void);
}
+#[repr(u32)]
+#[doc = " Enum to indicate the cache attributes of CPU mapping returned by"]
+#[doc = " gbm_bo_map()"]
+#[doc = ""]
+#[doc = " Note that definition aligns with VIRTIO_GPU_MAP_CACHE_*, RUTABAGA_MAP_CACHE_*,"]
+#[doc = " and VIRGL_RENDERER_MAP_CACHE_* (but skipping the _NONE and _UNCACHED values as"]
+#[doc = " those don't actually make sense to use)."]
+#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
+pub enum gbm_bo_map_cache_mode {
+ GBM_BO_MAP_CACHE_CACHED = 1,
+ GBM_BO_MAP_CACHE_WC = 3,
+}
+extern "C" {
+ pub fn gbm_bo_get_map_info(bo: *mut gbm_bo) -> gbm_bo_map_cache_mode;
+}
extern "C" {
pub fn gbm_bo_get_width(bo: *mut gbm_bo) -> u32;
}
@@ -272,3 +287,6 @@ extern "C" {
plane: c_int,
) -> *mut c_void;
}
+extern "C" {
+ pub fn minigbm_create_default_device(out_fd: *mut c_int) -> *mut gbm_device;
+}
diff --git a/rutabaga_gfx/src/rutabaga_gralloc/mod.rs b/rutabaga_gfx/src/rutabaga_gralloc/mod.rs
index c4ceb2e5c..a4ea8e57a 100644
--- a/rutabaga_gfx/src/rutabaga_gralloc/mod.rs
+++ b/rutabaga_gfx/src/rutabaga_gralloc/mod.rs
@@ -12,7 +12,6 @@ mod formats;
mod gralloc;
mod minigbm;
mod minigbm_bindings;
-mod rendernode;
mod system_gralloc;
mod vulkano_gralloc;
diff --git a/rutabaga_gfx/src/rutabaga_gralloc/rendernode.rs b/rutabaga_gfx/src/rutabaga_gralloc/rendernode.rs
deleted file mode 100644
index 4ff5f4e88..000000000
--- a/rutabaga_gfx/src/rutabaga_gralloc/rendernode.rs
+++ /dev/null
@@ -1,121 +0,0 @@
-// Copyright 2018 The ChromiumOS Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#![cfg(feature = "minigbm")]
-
-use std::ffi::CString;
-use std::fs::File;
-use std::fs::OpenOptions;
-use std::os::raw::c_char;
-use std::os::raw::c_int;
-use std::os::raw::c_uint;
-#[cfg(target_pointer_width = "64")]
-use std::os::raw::c_ulong;
-use std::os::unix::io::AsRawFd;
-use std::path::Path;
-use std::ptr::null_mut;
-
-use nix::ioctl_readwrite;
-
-use crate::rutabaga_utils::RutabagaError;
-use crate::rutabaga_utils::RutabagaResult;
-
-// Consistent with __kernel_size_t in include/uapi/asm-generic/posix_types.h.
-#[cfg(not(target_pointer_width = "64"))]
-#[allow(non_camel_case_types)]
-type __kernel_size_t = c_uint;
-#[cfg(target_pointer_width = "64")]
-#[allow(non_camel_case_types)]
-type __kernel_size_t = c_ulong;
-
-const DRM_IOCTL_BASE: c_uint = 0x64;
-const DRM_IOCTL_VERSION: c_uint = 0x00;
-
-#[repr(C)]
-#[derive(Copy, Clone)]
-pub struct drm_version {
- version_major: c_int,
- version_minor: c_int,
- version_patchlevel: c_int,
- name_len: __kernel_size_t,
- name: *mut c_char,
- date_len: __kernel_size_t,
- date: *mut c_char,
- desc_len: __kernel_size_t,
- desc: *mut c_char,
-}
-
-ioctl_readwrite!(
- drm_get_version,
- DRM_IOCTL_BASE,
- DRM_IOCTL_VERSION,
- drm_version
-);
-
-fn get_drm_device_name(fd: &File) -> RutabagaResult<String> {
- let mut version = drm_version {
- version_major: 0,
- version_minor: 0,
- version_patchlevel: 0,
- name_len: 0,
- name: null_mut(),
- date_len: 0,
- date: null_mut(),
- desc_len: 0,
- desc: null_mut(),
- };
-
- // TODO(b/315870313): Add safety comment
- #[allow(clippy::undocumented_unsafe_blocks)]
- // Get the length of the device name.
- unsafe {
- drm_get_version(fd.as_raw_fd(), &mut version)?;
- }
-
- // Enough bytes to hold the device name and terminating null character.
- let mut name_bytes: Vec<u8> = vec![0; (version.name_len + 1) as usize];
- let mut version = drm_version {
- version_major: 0,
- version_minor: 0,
- version_patchlevel: 0,
- name_len: name_bytes.len() as __kernel_size_t,
- name: name_bytes.as_mut_ptr() as *mut c_char,
- date_len: 0,
- date: null_mut(),
- desc_len: 0,
- desc: null_mut(),
- };
-
- // SAFETY:
- // Safe as no more than name_len + 1 bytes will be written to name.
- unsafe {
- drm_get_version(fd.as_raw_fd(), &mut version)?;
- }
-
- CString::new(&name_bytes[..(version.name_len as usize)])?
- .into_string()
- .map_err(|_| RutabagaError::SpecViolation("couldn't convert string"))
-}
-
-/// Returns a `fd` for an opened rendernode device, while filtering out specified
-/// undesired drivers.
-pub fn open_device(undesired: &[&str]) -> RutabagaResult<File> {
- const DRM_DIR_NAME: &str = "/dev/dri";
- const DRM_MAX_MINOR: u32 = 15;
- const RENDER_NODE_START: u32 = 128;
-
- for n in RENDER_NODE_START..=RENDER_NODE_START + DRM_MAX_MINOR {
- let path = Path::new(DRM_DIR_NAME).join(format!("renderD{}", n));
-
- if let Ok(fd) = OpenOptions::new().read(true).write(true).open(path) {
- if let Ok(name) = get_drm_device_name(&fd) {
- if !undesired.iter().any(|item| *item == name) {
- return Ok(fd);
- }
- }
- }
- }
-
- Err(RutabagaError::SpecViolation("no DRM rendernode opened"))
-}
diff --git a/rutabaga_gfx/src/rutabaga_os/sys/mod.rs b/rutabaga_gfx/src/rutabaga_os/sys/mod.rs
index dd1d0fea1..b60636259 100644
--- a/rutabaga_gfx/src/rutabaga_os/sys/mod.rs
+++ b/rutabaga_gfx/src/rutabaga_os/sys/mod.rs
@@ -5,7 +5,7 @@
#[cfg(any(target_os = "android", target_os = "linux"))]
pub mod linux;
-#[cfg(any(target_os = "fuchsia", target_os = "macos"))]
+#[cfg(any(target_os = "fuchsia", target_os = "macos", target_os = "nto"))]
pub mod stub;
#[cfg(windows)]
@@ -16,7 +16,7 @@ cfg_if::cfg_if! {
pub use linux as platform;
} else if #[cfg(windows)] {
pub use windows as platform;
- } else if #[cfg(any(target_os = "fuchsia", target_os = "macos"))] {
+ } else if #[cfg(any(target_os = "fuchsia", target_os = "macos", target_os = "nto"))] {
pub use stub as platform;
} else {
compile_error!("Unsupported platform");
diff --git a/rutabaga_gfx/src/rutabaga_os/sys/windows/descriptor.rs b/rutabaga_gfx/src/rutabaga_os/sys/windows/descriptor.rs
index 99096f098..6535863b8 100644
--- a/rutabaga_gfx/src/rutabaga_os/sys/windows/descriptor.rs
+++ b/rutabaga_gfx/src/rutabaga_os/sys/windows/descriptor.rs
@@ -34,7 +34,7 @@ pub type RawDescriptor = RawHandle;
impl Drop for SafeDescriptor {
fn drop(&mut self) {
// SAFETY: Safe because we own the descriptor.
- unsafe { CloseHandle(self.descriptor) };
+ unsafe { CloseHandle(self.descriptor as _) };
}
}
@@ -54,12 +54,12 @@ pub fn duplicate_handle_from_source_process(
// 2. new_handle_ptr points to a valid location on the stack
// 3. Caller guarantees hndl is a real valid handle.
unsafe {
- let mut new_handle: RawHandle = std::ptr::null_mut();
+ let new_handle: RawHandle = std::ptr::null_mut();
let success_flag = DuplicateHandle(
- /* hSourceProcessHandle= */ source_process_handle,
- /* hSourceHandle= */ hndl,
- /* hTargetProcessHandle= */ target_process_handle,
- /* lpTargetHandle= */ &mut new_handle,
+ /* hSourceProcessHandle= */ source_process_handle as _,
+ /* hSourceHandle= */ hndl as _,
+ /* hTargetProcessHandle= */ target_process_handle as _,
+ /* lpTargetHandle= */ new_handle as _,
/* dwDesiredAccess= */ 0,
/* bInheritHandle= */ TRUE,
/* dwOptions= */ DUPLICATE_SAME_ACCESS,
@@ -80,7 +80,7 @@ fn duplicate_handle_with_target_handle(
duplicate_handle_from_source_process(
// SAFETY:
// Safe because `GetCurrentProcess` just gets the current process handle.
- unsafe { GetCurrentProcess() },
+ unsafe { GetCurrentProcess() as _ },
hndl,
target_process_handle,
)
@@ -89,7 +89,7 @@ fn duplicate_handle_with_target_handle(
pub fn duplicate_handle(hndl: RawHandle) -> io::Result<RawHandle> {
// SAFETY:
// Safe because `GetCurrentProcess` just gets the current process handle.
- duplicate_handle_with_target_handle(hndl, unsafe { GetCurrentProcess() })
+ duplicate_handle_with_target_handle(hndl, unsafe { GetCurrentProcess() as _ })
}
impl TryFrom<&dyn AsRawHandle> for SafeDescriptor {
diff --git a/src/crosvm/cmdline.rs b/src/crosvm/cmdline.rs
index abad7aab7..b7becced2 100644
--- a/src/crosvm/cmdline.rs
+++ b/src/crosvm/cmdline.rs
@@ -88,6 +88,7 @@ use crate::crosvm::config::DtboOption;
use crate::crosvm::config::Executable;
use crate::crosvm::config::FileBackedMappingParameters;
use crate::crosvm::config::HypervisorKind;
+use crate::crosvm::config::InputDeviceOption;
use crate::crosvm::config::IrqChipKind;
use crate::crosvm::config::MemOptions;
use crate::crosvm::config::TouchDeviceOption;
@@ -176,6 +177,9 @@ pub struct BalloonCommand {
#[argh(positional, arg_name = "VM_SOCKET")]
/// VM Socket path
pub socket_path: String,
+ /// wait for response
+ #[argh(switch)]
+ pub wait: bool,
}
#[derive(argh::FromArgs)]
@@ -1313,6 +1317,8 @@ pub struct RunCommand {
/// implicit-render-server[=true|=false] - If the render
/// server process should be allowed to autostart
/// (ignored when sandboxing is enabled)
+ /// fixed-blob-mapping[=true|=false] - if gpu memory blobs
+ /// should use fixed address mapping.
///
/// Possible key values for GpuDisplayParameters:
/// mode=(borderless_full_screen|windowed[width,height]) -
@@ -1415,6 +1421,24 @@ pub struct RunCommand {
/// initial ramdisk to load
pub initrd: Option<PathBuf>,
+ #[argh(option, arg_name = "TYPE[OPTIONS]")]
+ #[serde(default)]
+ #[merge(strategy = append)]
+ /// virtio-input device
+ /// TYPE is an input device type, and OPTIONS are key=value
+ /// pairs specific to the device type:
+ /// evdev[path=PATH]
+ /// keyboard[path=PATH]
+ /// mouse[path=PATH]
+ /// multi-touch[path=PATH,width=W,height=H,name=N]
+ /// rotary[path=PATH]
+ /// single-touch[path=PATH,width=W,height=H,name=N]
+ /// switches[path=PATH]
+ /// trackpad[path=PATH,width=W,height=H,name=N]
+ /// See <https://crosvm.dev/book/devices/input.html> for more
+ /// information.
+ pub input: Vec<InputDeviceOption>,
+
#[argh(option, arg_name = "kernel|split|userspace")]
#[merge(strategy = overwrite_option)]
/// type of interrupt controller emulation. "split" is only available for x86 KVM.
@@ -1518,7 +1542,7 @@ pub struct RunCommand {
#[cfg(all(unix, feature = "net"))]
#[argh(
option,
- arg_name = "(tap-name=TAP_NAME,mac=MAC_ADDRESS|tap-fd=TAP_FD,mac=MAC_ADDRESS|host-ip=IP,netmask=NETMASK,mac=MAC_ADDRESS),vhost-net=VHOST_NET,vq-pairs=N"
+ arg_name = "(tap-name=TAP_NAME,mac=MAC_ADDRESS|tap-fd=TAP_FD,mac=MAC_ADDRESS|host-ip=IP,netmask=NETMASK,mac=MAC_ADDRESS),vhost-net=VHOST_NET,vq-pairs=N,pci-address=ADDR"
)]
#[serde(default)]
#[merge(strategy = append)]
@@ -1557,6 +1581,8 @@ pub struct RunCommand {
/// If not set or set to false, it will
/// use split virtqueue.
/// Default: false. [Optional]
+ /// pci-address - preferred PCI address, e.g. "00:01.0"
+ /// Default: automatic PCI address assignment. [Optional]
///
/// Either one tap_name, one tap_fd or a triplet of host_ip,
/// netmask and mac must be specified.
@@ -2357,10 +2383,20 @@ pub struct RunCommand {
/// enable a virtual cpu freq device
pub virt_cpufreq: Option<bool>,
+ #[cfg(all(
+ any(target_arch = "arm", target_arch = "aarch64"),
+ any(target_os = "android", target_os = "linux")
+ ))]
+ #[argh(option, arg_name = "SOCKET_PATH")]
+ #[serde(skip)]
+ #[merge(strategy = overwrite_option)]
+ /// (EXPERIMENTAL) use UDS for a virtual cpu freq device
+ pub virt_cpufreq_socket: Option<PathBuf>,
+
#[cfg(feature = "audio")]
#[argh(
option,
- arg_name = "[capture=true,backend=BACKEND,num_output_devices=1,
+ arg_name = "[capture=true,backend=BACKEND,num_output_devices=1,\
num_input_devices=1,num_output_streams=1,num_input_streams=1]"
)]
#[serde(skip)] // TODO(b/255223604)
@@ -2551,6 +2587,7 @@ impl TryFrom<RunCommand> for super::config::Config {
))]
{
cfg.virt_cpufreq = cmd.virt_cpufreq.unwrap_or_default();
+ cfg.virt_cpufreq_socket = cmd.virt_cpufreq_socket;
}
cfg.vcpu_cgroup_path = cmd.vcpu_cgroup_path;
@@ -2890,14 +2927,97 @@ impl TryFrom<RunCommand> for super::config::Config {
cfg.vtpm_proxy = cmd.vtpm_proxy.unwrap_or_default();
}
- cfg.virtio_single_touch = cmd.single_touch;
- cfg.virtio_multi_touch = cmd.multi_touch;
- cfg.virtio_trackpad = cmd.trackpad;
- cfg.virtio_mice = cmd.mouse;
- cfg.virtio_keyboard = cmd.keyboard;
- cfg.virtio_switches = cmd.switches;
- cfg.virtio_rotary = cmd.rotary;
- cfg.virtio_input_evdevs = cmd.evdev;
+ cfg.virtio_input = cmd.input;
+
+ if !cmd.single_touch.is_empty() {
+ log::warn!("`--single-touch` is deprecated; please use `--input single-touch[...]`");
+ cfg.virtio_input
+ .extend(
+ cmd.single_touch
+ .into_iter()
+ .map(|touch| InputDeviceOption::SingleTouch {
+ path: touch.path,
+ width: touch.width,
+ height: touch.height,
+ name: touch.name,
+ }),
+ );
+ }
+
+ if !cmd.multi_touch.is_empty() {
+ log::warn!("`--multi-touch` is deprecated; please use `--input multi-touch[...]`");
+ cfg.virtio_input
+ .extend(
+ cmd.multi_touch
+ .into_iter()
+ .map(|touch| InputDeviceOption::MultiTouch {
+ path: touch.path,
+ width: touch.width,
+ height: touch.height,
+ name: touch.name,
+ }),
+ );
+ }
+
+ if !cmd.trackpad.is_empty() {
+ log::warn!("`--trackpad` is deprecated; please use `--input trackpad[...]`");
+ cfg.virtio_input
+ .extend(
+ cmd.trackpad
+ .into_iter()
+ .map(|trackpad| InputDeviceOption::Trackpad {
+ path: trackpad.path,
+ width: trackpad.width,
+ height: trackpad.height,
+ name: trackpad.name,
+ }),
+ );
+ }
+
+ if !cmd.mouse.is_empty() {
+ log::warn!("`--mouse` is deprecated; please use `--input mouse[...]`");
+ cfg.virtio_input.extend(
+ cmd.mouse
+ .into_iter()
+ .map(|path| InputDeviceOption::Mouse { path }),
+ );
+ }
+
+ if !cmd.keyboard.is_empty() {
+ log::warn!("`--keyboard` is deprecated; please use `--input keyboard[...]`");
+ cfg.virtio_input.extend(
+ cmd.keyboard
+ .into_iter()
+ .map(|path| InputDeviceOption::Keyboard { path }),
+ )
+ }
+
+ if !cmd.switches.is_empty() {
+ log::warn!("`--switches` is deprecated; please use `--input switches[...]`");
+ cfg.virtio_input.extend(
+ cmd.switches
+ .into_iter()
+ .map(|path| InputDeviceOption::Switches { path }),
+ );
+ }
+
+ if !cmd.rotary.is_empty() {
+ log::warn!("`--rotary` is deprecated; please use `--input rotary[...]`");
+ cfg.virtio_input.extend(
+ cmd.rotary
+ .into_iter()
+ .map(|path| InputDeviceOption::Rotary { path }),
+ );
+ }
+
+ if !cmd.evdev.is_empty() {
+ log::warn!("`--evdev` is deprecated; please use `--input evdev[...]`");
+ cfg.virtio_input.extend(
+ cmd.evdev
+ .into_iter()
+ .map(|path| InputDeviceOption::Evdev { path }),
+ );
+ }
cfg.irq_chip = cmd.irqchip;
@@ -3027,6 +3147,7 @@ impl TryFrom<RunCommand> for super::config::Config {
vhost_net: vhost_net_config.clone(),
vq_pairs: cmd.net_vq_pairs,
packed_queue: false,
+ pci_address: None,
});
}
@@ -3040,6 +3161,7 @@ impl TryFrom<RunCommand> for super::config::Config {
vhost_net: vhost_net_config.clone(),
vq_pairs: cmd.net_vq_pairs,
packed_queue: false,
+ pci_address: None,
});
}
@@ -3078,6 +3200,7 @@ impl TryFrom<RunCommand> for super::config::Config {
vhost_net: vhost_net_config,
vq_pairs: cmd.net_vq_pairs,
packed_queue: false,
+ pci_address: None,
});
}
diff --git a/src/crosvm/config.rs b/src/crosvm/config.rs
index d3c6e5ec2..c9fc6c020 100644
--- a/src/crosvm/config.rs
+++ b/src/crosvm/config.rs
@@ -7,7 +7,6 @@ use std::arch::x86_64::__cpuid;
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::__cpuid_count;
use std::collections::BTreeMap;
-use std::path::Path;
use std::path::PathBuf;
use std::str::FromStr;
@@ -235,81 +234,10 @@ pub const DEFAULT_TOUCH_DEVICE_WIDTH: u32 = 1280;
#[derive(Serialize, Deserialize, Debug, FromKeyValues)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct TouchDeviceOption {
- path: PathBuf,
- width: Option<u32>,
- height: Option<u32>,
- name: Option<String>,
-
- #[serde(skip, default = "default_touch_device_width")]
- default_width: u32,
-
- #[serde(skip, default = "default_touch_device_height")]
- default_height: u32,
-}
-
-fn default_touch_device_width() -> u32 {
- DEFAULT_TOUCH_DEVICE_WIDTH
-}
-
-fn default_touch_device_height() -> u32 {
- DEFAULT_TOUCH_DEVICE_HEIGHT
-}
-
-impl TouchDeviceOption {
- pub fn new(path: PathBuf) -> TouchDeviceOption {
- TouchDeviceOption {
- path,
- width: None,
- height: None,
- name: None,
- default_width: DEFAULT_TOUCH_DEVICE_WIDTH,
- default_height: DEFAULT_TOUCH_DEVICE_HEIGHT,
- }
- }
-
- /// Getter for the path to the input event streams.
- #[cfg_attr(windows, allow(unused))]
- pub fn get_path(&self) -> &Path {
- self.path.as_path()
- }
-
- /// When a user specifies the parameters for a touch device, width and height are optional.
- /// If the width and height are missing, default values are used. Default values can be set
- /// dynamically, for example from the display sizes specified by the gpu argument.
- #[cfg(feature = "gpu")]
- pub fn set_default_size(&mut self, width: u32, height: u32) {
- self.default_width = width;
- self.default_height = height;
- }
-
- /// Setter for the width specified by the user.
- pub fn set_width(&mut self, width: u32) {
- self.width.replace(width);
- }
-
- /// Setter for the height specified by the user.
- pub fn set_height(&mut self, height: u32) {
- self.height.replace(height);
- }
-
- /// If the user specifies the size, use it. Otherwise, use the default values.
- #[cfg(any(unix, feature = "gpu"))]
- pub fn get_size(&self) -> (u32, u32) {
- (
- self.width.unwrap_or(self.default_width),
- self.height.unwrap_or(self.default_height),
- )
- }
-
- /// Setter for the input device's name specified by the user.
- pub fn set_name(&mut self, name: String) {
- self.name.replace(name);
- }
-
- /// Getter for the input device's name
- pub fn get_name(&self) -> Option<&str> {
- self.name.as_deref()
- }
+ pub path: PathBuf,
+ pub width: Option<u32>,
+ pub height: Option<u32>,
+ pub name: Option<String>,
}
/// Try to parse a colon-separated touch device option.
@@ -317,21 +245,28 @@ impl TouchDeviceOption {
/// The expected format is "PATH:WIDTH:HEIGHT:NAME", with all fields except PATH being optional.
fn parse_touch_device_option_legacy(s: &str) -> Option<TouchDeviceOption> {
let mut it = s.split(':');
- let mut touch_spec = TouchDeviceOption::new(PathBuf::from(it.next()?.to_owned()));
- if let Some(width) = it.next() {
- touch_spec.set_width(width.trim().parse().ok()?);
- }
- if let Some(height) = it.next() {
- touch_spec.set_height(height.trim().parse().ok()?);
- }
- if let Some(name) = it.next() {
- touch_spec.set_name(name.trim().to_string());
- }
+ let path = PathBuf::from(it.next()?.to_owned());
+ let width = if let Some(width) = it.next() {
+ Some(width.trim().parse().ok()?)
+ } else {
+ None
+ };
+ let height = if let Some(height) = it.next() {
+ Some(height.trim().parse().ok()?)
+ } else {
+ None
+ };
+ let name = it.next().map(|name| name.trim().to_string());
if it.next().is_some() {
return None;
}
- Some(touch_spec)
+ Some(TouchDeviceOption {
+ path,
+ width,
+ height,
+ name,
+ })
}
/// Parse virtio-input touch device options from a string.
@@ -344,7 +279,7 @@ pub fn parse_touch_device_option(s: &str) -> Result<TouchDeviceOption, String> {
if let Some(touch_spec) = parse_touch_device_option_legacy(s) {
log::warn!(
"colon-separated touch device options are deprecated; \
- please use key=value form instead"
+ please use --input instead"
);
return Ok(touch_spec);
}
@@ -353,6 +288,45 @@ pub fn parse_touch_device_option(s: &str) -> Result<TouchDeviceOption, String> {
from_key_values::<TouchDeviceOption>(s)
}
+/// virtio-input device configuration
+#[derive(Serialize, Deserialize, Debug, FromKeyValues, Eq, PartialEq)]
+#[serde(deny_unknown_fields, rename_all = "kebab-case")]
+pub enum InputDeviceOption {
+ Evdev {
+ path: PathBuf,
+ },
+ Keyboard {
+ path: PathBuf,
+ },
+ Mouse {
+ path: PathBuf,
+ },
+ MultiTouch {
+ path: PathBuf,
+ width: Option<u32>,
+ height: Option<u32>,
+ name: Option<String>,
+ },
+ Rotary {
+ path: PathBuf,
+ },
+ SingleTouch {
+ path: PathBuf,
+ width: Option<u32>,
+ height: Option<u32>,
+ name: Option<String>,
+ },
+ Switches {
+ path: PathBuf,
+ },
+ Trackpad {
+ path: PathBuf,
+ width: Option<u32>,
+ height: Option<u32>,
+ name: Option<String>,
+ },
+}
+
#[derive(Debug, Serialize, Deserialize, FromKeyValues)]
#[serde(deny_unknown_fields)]
pub struct FileBackedMappingParameters {
@@ -682,6 +656,8 @@ pub struct Config {
pub device_tree_overlay: Vec<DtboOption>,
pub disable_virtio_intx: bool,
pub disks: Vec<DiskOption>,
+ pub display_input_height: Option<u32>,
+ pub display_input_width: Option<u32>,
pub display_window_keyboard: bool,
pub display_window_mouse: bool,
pub dump_device_tree_blob: Option<PathBuf>,
@@ -828,17 +804,11 @@ pub struct Config {
any(target_os = "android", target_os = "linux")
))]
pub virt_cpufreq: bool,
- pub virtio_input_evdevs: Vec<PathBuf>,
- pub virtio_keyboard: Vec<PathBuf>,
- pub virtio_mice: Vec<PathBuf>,
- pub virtio_multi_touch: Vec<TouchDeviceOption>,
- pub virtio_rotary: Vec<PathBuf>,
- pub virtio_single_touch: Vec<TouchDeviceOption>,
+ pub virt_cpufreq_socket: Option<PathBuf>,
+ pub virtio_input: Vec<InputDeviceOption>,
#[cfg(feature = "audio")]
#[serde(skip)]
pub virtio_snds: Vec<SndParameters>,
- pub virtio_switches: Vec<PathBuf>,
- pub virtio_trackpad: Vec<TouchDeviceOption>,
pub vsock: Option<VsockConfig>,
#[cfg(feature = "vtpm")]
pub vtpm_proxy: bool,
@@ -886,6 +856,8 @@ impl Default for Config {
device_tree_overlay: Vec::new(),
disks: Vec::new(),
disable_virtio_intx: false,
+ display_input_height: None,
+ display_input_width: None,
display_window_keyboard: false,
display_window_mouse: false,
dump_device_tree_blob: None,
@@ -1034,16 +1006,10 @@ impl Default for Config {
any(target_os = "android", target_os = "linux")
))]
virt_cpufreq: false,
- virtio_input_evdevs: Vec::new(),
- virtio_keyboard: Vec::new(),
- virtio_mice: Vec::new(),
- virtio_multi_touch: Vec::new(),
- virtio_rotary: Vec::new(),
- virtio_single_touch: Vec::new(),
+ virt_cpufreq_socket: None,
+ virtio_input: Vec::new(),
#[cfg(feature = "audio")]
virtio_snds: Vec::new(),
- virtio_switches: Vec::new(),
- virtio_trackpad: Vec::new(),
#[cfg(feature = "vtpm")]
vtpm_proxy: false,
wayland_socket_paths: BTreeMap::new(),
@@ -1974,12 +1940,21 @@ mod tests {
)
.unwrap();
- assert_eq!(cfg.virtio_multi_touch.len(), 1);
- let touch = &cfg.virtio_multi_touch[0];
- assert_eq!(touch.path.to_str(), Some("my_socket"));
- assert_eq!(touch.width, Some(867));
- assert_eq!(touch.height, Some(5309));
- assert_eq!(touch.name, None);
+ assert_eq!(cfg.virtio_input.len(), 1);
+ let multi_touch = cfg
+ .virtio_input
+ .iter()
+ .find(|input| matches!(input, InputDeviceOption::MultiTouch { .. }))
+ .unwrap();
+ assert_eq!(
+ *multi_touch,
+ InputDeviceOption::MultiTouch {
+ path: PathBuf::from("my_socket"),
+ width: Some(867),
+ height: Some(5309),
+ name: None
+ }
+ );
}
#[test]
@@ -1993,11 +1968,235 @@ mod tests {
)
.unwrap();
- assert_eq!(cfg.virtio_multi_touch.len(), 1);
- let touch = &cfg.virtio_multi_touch[0];
- assert_eq!(touch.path.to_str(), Some(r"C:\path"));
- assert_eq!(touch.width, Some(867));
- assert_eq!(touch.height, Some(5309));
- assert_eq!(touch.name, None);
+ assert_eq!(cfg.virtio_input.len(), 1);
+ let multi_touch = cfg
+ .virtio_input
+ .iter()
+ .find(|input| matches!(input, InputDeviceOption::MultiTouch { .. }))
+ .unwrap();
+ assert_eq!(
+ *multi_touch,
+ InputDeviceOption::MultiTouch {
+ path: PathBuf::from(r"C:\path"),
+ width: Some(867),
+ height: Some(5309),
+ name: None
+ }
+ );
+ }
+
+ #[test]
+ fn single_touch_spec_and_track_pad_spec_default_size() {
+ let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
+ &[],
+ &[
+ "--single-touch",
+ "/dev/single-touch-test",
+ "--trackpad",
+ "/dev/single-touch-test",
+ "/dev/null",
+ ],
+ )
+ .unwrap()
+ .try_into()
+ .unwrap();
+
+ let single_touch = config
+ .virtio_input
+ .iter()
+ .find(|input| matches!(input, InputDeviceOption::SingleTouch { .. }))
+ .unwrap();
+ let trackpad = config
+ .virtio_input
+ .iter()
+ .find(|input| matches!(input, InputDeviceOption::Trackpad { .. }))
+ .unwrap();
+
+ assert_eq!(
+ *single_touch,
+ InputDeviceOption::SingleTouch {
+ path: PathBuf::from("/dev/single-touch-test"),
+ width: None,
+ height: None,
+ name: None
+ }
+ );
+ assert_eq!(
+ *trackpad,
+ InputDeviceOption::Trackpad {
+ path: PathBuf::from("/dev/single-touch-test"),
+ width: None,
+ height: None,
+ name: None
+ }
+ );
+ }
+
+ #[cfg(feature = "gpu")]
+ #[test]
+ fn single_touch_spec_default_size_from_gpu() {
+ let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
+ &[],
+ &[
+ "--single-touch",
+ "/dev/single-touch-test",
+ "--gpu",
+ "width=1024,height=768",
+ "/dev/null",
+ ],
+ )
+ .unwrap()
+ .try_into()
+ .unwrap();
+
+ let single_touch = config
+ .virtio_input
+ .iter()
+ .find(|input| matches!(input, InputDeviceOption::SingleTouch { .. }))
+ .unwrap();
+ assert_eq!(
+ *single_touch,
+ InputDeviceOption::SingleTouch {
+ path: PathBuf::from("/dev/single-touch-test"),
+ width: None,
+ height: None,
+ name: None
+ }
+ );
+
+ assert_eq!(config.display_input_width, Some(1024));
+ assert_eq!(config.display_input_height, Some(768));
+ }
+
+ #[test]
+ fn single_touch_spec_and_track_pad_spec_with_size() {
+ let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
+ &[],
+ &[
+ "--single-touch",
+ "/dev/single-touch-test:12345:54321",
+ "--trackpad",
+ "/dev/single-touch-test:5678:9876",
+ "/dev/null",
+ ],
+ )
+ .unwrap()
+ .try_into()
+ .unwrap();
+
+ let single_touch = config
+ .virtio_input
+ .iter()
+ .find(|input| matches!(input, InputDeviceOption::SingleTouch { .. }))
+ .unwrap();
+ let trackpad = config
+ .virtio_input
+ .iter()
+ .find(|input| matches!(input, InputDeviceOption::Trackpad { .. }))
+ .unwrap();
+
+ assert_eq!(
+ *single_touch,
+ InputDeviceOption::SingleTouch {
+ path: PathBuf::from("/dev/single-touch-test"),
+ width: Some(12345),
+ height: Some(54321),
+ name: None
+ }
+ );
+ assert_eq!(
+ *trackpad,
+ InputDeviceOption::Trackpad {
+ path: PathBuf::from("/dev/single-touch-test"),
+ width: Some(5678),
+ height: Some(9876),
+ name: None
+ }
+ );
+ }
+
+ #[cfg(feature = "gpu")]
+ #[test]
+ fn single_touch_spec_with_size_independent_from_gpu() {
+ let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
+ &[],
+ &[
+ "--single-touch",
+ "/dev/single-touch-test:12345:54321",
+ "--gpu",
+ "width=1024,height=768",
+ "/dev/null",
+ ],
+ )
+ .unwrap()
+ .try_into()
+ .unwrap();
+
+ let single_touch = config
+ .virtio_input
+ .iter()
+ .find(|input| matches!(input, InputDeviceOption::SingleTouch { .. }))
+ .unwrap();
+
+ assert_eq!(
+ *single_touch,
+ InputDeviceOption::SingleTouch {
+ path: PathBuf::from("/dev/single-touch-test"),
+ width: Some(12345),
+ height: Some(54321),
+ name: None
+ }
+ );
+
+ assert_eq!(config.display_input_width, Some(1024));
+ assert_eq!(config.display_input_height, Some(768));
+ }
+
+ #[test]
+ fn virtio_switches() {
+ let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
+ &[],
+ &["--switches", "/dev/switches-test", "/dev/null"],
+ )
+ .unwrap()
+ .try_into()
+ .unwrap();
+
+ let switches = config
+ .virtio_input
+ .iter()
+ .find(|input| matches!(input, InputDeviceOption::Switches { .. }))
+ .unwrap();
+
+ assert_eq!(
+ *switches,
+ InputDeviceOption::Switches {
+ path: PathBuf::from("/dev/switches-test")
+ }
+ );
+ }
+
+ #[test]
+ fn virtio_rotary() {
+ let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
+ &[],
+ &["--rotary", "/dev/rotary-test", "/dev/null"],
+ )
+ .unwrap()
+ .try_into()
+ .unwrap();
+
+ let rotary = config
+ .virtio_input
+ .iter()
+ .find(|input| matches!(input, InputDeviceOption::Rotary { .. }))
+ .unwrap();
+
+ assert_eq!(
+ *rotary,
+ InputDeviceOption::Rotary {
+ path: PathBuf::from("/dev/rotary-test")
+ }
+ );
}
}
diff --git a/src/crosvm/gpu_config.rs b/src/crosvm/gpu_config.rs
index 0e99c34db..650f4ae47 100644
--- a/src/crosvm/gpu_config.rs
+++ b/src/crosvm/gpu_config.rs
@@ -137,12 +137,8 @@ pub(crate) fn validate_gpu_config(cfg: &mut Config) -> Result<(), String> {
let is_4k_uhd_enabled = false;
let (width, height) =
gpu_parameters.display_params[0].get_virtual_display_size_4k_uhd(is_4k_uhd_enabled);
- if let Some(virtio_multi_touch) = cfg.virtio_multi_touch.first_mut() {
- virtio_multi_touch.set_default_size(width, height);
- }
- if let Some(virtio_single_touch) = cfg.virtio_single_touch.first_mut() {
- virtio_single_touch.set_default_size(width, height);
- }
+ cfg.display_input_width = Some(width);
+ cfg.display_input_height = Some(height);
}
Ok(())
}
diff --git a/src/crosvm/plugin/mod.rs b/src/crosvm/plugin/mod.rs
index 9c33c38bd..e194354a4 100644
--- a/src/crosvm/plugin/mod.rs
+++ b/src/crosvm/plugin/mod.rs
@@ -139,8 +139,8 @@ struct VcpuPipe {
}
fn new_pipe_pair() -> SysResult<VcpuPipe> {
- let to_crosvm = pipe(true)?;
- let to_plugin = pipe(true)?;
+ let to_crosvm = pipe()?;
+ let to_plugin = pipe()?;
// Increasing the pipe size can be a nice-to-have to make sure that
// messages get across atomically (and made sure that writes don't block),
// though it's not necessary a hard requirement for things to work.
@@ -488,7 +488,7 @@ pub fn run_config(cfg: Config) -> Result<()> {
let sigchld_fd = SignalFd::new(SIGCHLD).context("failed to create signalfd")?;
// Create a pipe to capture error messages from plugin and minijail.
- let (mut stderr_rd, stderr_wr) = pipe(true).context("failed to create stderr pipe")?;
+ let (mut stderr_rd, stderr_wr) = pipe().context("failed to create stderr pipe")?;
add_fd_flags(stderr_rd.as_raw_descriptor(), O_NONBLOCK)
.context("error marking stderr nonblocking")?;
diff --git a/src/crosvm/sys/linux.rs b/src/crosvm/sys/linux.rs
index 34cc1ef92..2a9346ea1 100644
--- a/src/crosvm/sys/linux.rs
+++ b/src/crosvm/sys/linux.rs
@@ -149,6 +149,7 @@ use hypervisor::CpuConfigRiscv64;
use hypervisor::CpuConfigX86_64;
use hypervisor::Hypervisor;
use hypervisor::HypervisorCap;
+use hypervisor::MemCacheType;
use hypervisor::ProtectionType;
use hypervisor::Vm;
use hypervisor::VmCap;
@@ -187,7 +188,10 @@ use crate::crosvm::config::Config;
use crate::crosvm::config::Executable;
use crate::crosvm::config::FileBackedMappingParameters;
use crate::crosvm::config::HypervisorKind;
+use crate::crosvm::config::InputDeviceOption;
use crate::crosvm::config::IrqChipKind;
+use crate::crosvm::config::DEFAULT_TOUCH_DEVICE_HEIGHT;
+use crate::crosvm::config::DEFAULT_TOUCH_DEVICE_WIDTH;
#[cfg(feature = "gdb")]
use crate::crosvm::gdb::gdb_thread;
#[cfg(feature = "gdb")]
@@ -219,6 +223,7 @@ fn create_virtio_devices(
fs_device_tubes: &mut Vec<Tube>,
#[cfg(feature = "gpu")] gpu_control_tube: Tube,
#[cfg(feature = "gpu")] render_server_fd: Option<SafeDescriptor>,
+ #[cfg(feature = "gpu")] has_vfio_gfx_device: bool,
#[cfg(feature = "registered_events")] registered_evt_q: &SendTube,
) -> DeviceResult<Vec<VirtioDeviceStub>> {
let mut devs = Vec::new();
@@ -286,14 +291,29 @@ fn create_virtio_devices(
let (event_device_socket, virtio_dev_socket) =
StreamChannel::pair(BlockingMode::Nonblocking, FramingMode::Byte)
.context("failed to create socket")?;
- let multi_touch_spec = cfg.virtio_multi_touch.first();
- let (multi_touch_width, multi_touch_height) = multi_touch_spec
- .as_ref()
- .map(|multi_touch_spec| multi_touch_spec.get_size())
- .unwrap_or((gpu_display_w, gpu_display_h));
- let multi_touch_name = multi_touch_spec
- .as_ref()
- .and_then(|multi_touch_spec| multi_touch_spec.get_name());
+ let mut multi_touch_width = gpu_display_w;
+ let mut multi_touch_height = gpu_display_h;
+ let mut multi_touch_name = None;
+ for input in &cfg.virtio_input {
+ if let InputDeviceOption::MultiTouch {
+ width,
+ height,
+ name,
+ ..
+ } = input
+ {
+ if let Some(width) = width {
+ multi_touch_width = *width;
+ }
+ if let Some(height) = height {
+ multi_touch_height = *height;
+ }
+ if let Some(name) = name {
+ multi_touch_name = Some(name.as_str());
+ }
+ break;
+ }
+ }
let dev = virtio::input::new_multi_touch(
// u32::MAX is the least likely to collide with the indices generated above for
// the multi_touch options, which begin at 0.
@@ -336,6 +356,7 @@ fn create_virtio_devices(
gpu_control_tube,
resource_bridges,
render_server_fd,
+ has_vfio_gfx_device,
event_devices,
)?);
}
@@ -390,75 +411,134 @@ fn create_virtio_devices(
}
}
- for (idx, single_touch_spec) in cfg.virtio_single_touch.iter().enumerate() {
- devs.push(create_single_touch_device(
- cfg.protection_type,
- &cfg.jail_config,
- single_touch_spec,
- idx as u32,
- )?);
- }
-
- for (idx, multi_touch_spec) in cfg.virtio_multi_touch.iter().enumerate() {
- devs.push(create_multi_touch_device(
- cfg.protection_type,
- &cfg.jail_config,
- multi_touch_spec,
- idx as u32,
- )?);
- }
-
- for (idx, trackpad_spec) in cfg.virtio_trackpad.iter().enumerate() {
- devs.push(create_trackpad_device(
- cfg.protection_type,
- &cfg.jail_config,
- trackpad_spec,
- idx as u32,
- )?);
- }
-
- for (idx, mouse_socket) in cfg.virtio_mice.iter().enumerate() {
- devs.push(create_mouse_device(
- cfg.protection_type,
- &cfg.jail_config,
- mouse_socket,
- idx as u32,
- )?);
- }
-
- for (idx, keyboard_socket) in cfg.virtio_keyboard.iter().enumerate() {
- devs.push(create_keyboard_device(
- cfg.protection_type,
- &cfg.jail_config,
- keyboard_socket,
- idx as u32,
- )?);
- }
-
- for (idx, switches_socket) in cfg.virtio_switches.iter().enumerate() {
- devs.push(create_switches_device(
- cfg.protection_type,
- &cfg.jail_config,
- switches_socket,
- idx as u32,
- )?);
- }
-
- for (idx, rotary_socket) in cfg.virtio_rotary.iter().enumerate() {
- devs.push(create_rotary_device(
- cfg.protection_type,
- &cfg.jail_config,
- rotary_socket,
- idx as u32,
- )?);
- }
-
- for dev_path in &cfg.virtio_input_evdevs {
- devs.push(create_vinput_device(
- cfg.protection_type,
- &cfg.jail_config,
- dev_path,
- )?);
+ let mut keyboard_idx = 0;
+ let mut mouse_idx = 0;
+ let mut rotary_idx = 0;
+ let mut switches_idx = 0;
+ let mut multi_touch_idx = 0;
+ let mut single_touch_idx = 0;
+ let mut trackpad_idx = 0;
+ for input in &cfg.virtio_input {
+ let input_dev = match input {
+ InputDeviceOption::Evdev { path } => {
+ create_vinput_device(cfg.protection_type, &cfg.jail_config, path.as_path())?
+ }
+ InputDeviceOption::Keyboard { path } => {
+ let dev = create_keyboard_device(
+ cfg.protection_type,
+ &cfg.jail_config,
+ path.as_path(),
+ keyboard_idx,
+ )?;
+ keyboard_idx += 1;
+ dev
+ }
+ InputDeviceOption::Mouse { path } => {
+ let dev = create_mouse_device(
+ cfg.protection_type,
+ &cfg.jail_config,
+ path.as_path(),
+ mouse_idx,
+ )?;
+ mouse_idx += 1;
+ dev
+ }
+ InputDeviceOption::MultiTouch {
+ path,
+ width,
+ height,
+ name,
+ } => {
+ let mut width = *width;
+ let mut height = *height;
+ if multi_touch_idx == 0 {
+ if width.is_none() {
+ width = cfg.display_input_width;
+ }
+ if height.is_none() {
+ height = cfg.display_input_height;
+ }
+ }
+ let dev = create_multi_touch_device(
+ cfg.protection_type,
+ &cfg.jail_config,
+ path.as_path(),
+ width.unwrap_or(DEFAULT_TOUCH_DEVICE_WIDTH),
+ height.unwrap_or(DEFAULT_TOUCH_DEVICE_HEIGHT),
+ name.as_deref(),
+ multi_touch_idx,
+ )?;
+ multi_touch_idx += 1;
+ dev
+ }
+ InputDeviceOption::Rotary { path } => {
+ let dev = create_rotary_device(
+ cfg.protection_type,
+ &cfg.jail_config,
+ path.as_path(),
+ rotary_idx,
+ )?;
+ rotary_idx += 1;
+ dev
+ }
+ InputDeviceOption::SingleTouch {
+ path,
+ width,
+ height,
+ name,
+ } => {
+ let mut width = *width;
+ let mut height = *height;
+ if single_touch_idx == 0 {
+ if width.is_none() {
+ width = cfg.display_input_width;
+ }
+ if height.is_none() {
+ height = cfg.display_input_height;
+ }
+ }
+ let dev = create_single_touch_device(
+ cfg.protection_type,
+ &cfg.jail_config,
+ path.as_path(),
+ width.unwrap_or(DEFAULT_TOUCH_DEVICE_WIDTH),
+ height.unwrap_or(DEFAULT_TOUCH_DEVICE_HEIGHT),
+ name.as_deref(),
+ single_touch_idx,
+ )?;
+ single_touch_idx += 1;
+ dev
+ }
+ InputDeviceOption::Switches { path } => {
+ let dev = create_switches_device(
+ cfg.protection_type,
+ &cfg.jail_config,
+ path.as_path(),
+ switches_idx,
+ )?;
+ switches_idx += 1;
+ dev
+ }
+ InputDeviceOption::Trackpad {
+ path,
+ width,
+ height,
+ name,
+ } => {
+ let dev = create_trackpad_device(
+ cfg.protection_type,
+ &cfg.jail_config,
+ path.as_path(),
+ width.unwrap_or(DEFAULT_TOUCH_DEVICE_WIDTH),
+ height.unwrap_or(DEFAULT_TOUCH_DEVICE_HEIGHT),
+ name.as_deref(),
+ trackpad_idx,
+ )?;
+ trackpad_idx += 1;
+ dev
+ }
+ };
+ devs.push(input_dev);
}
#[cfg(feature = "balloon")]
@@ -639,6 +719,8 @@ fn create_devices(
let mut devices: Vec<(Box<dyn BusDeviceObj>, Option<Minijail>)> = Vec::new();
#[cfg(feature = "balloon")]
let mut balloon_inflate_tube: Option<Tube> = None;
+ #[cfg(feature = "gpu")]
+ let mut has_vfio_gfx_device = false;
if !cfg.vfio.is_empty() {
let mut coiommu_attached_endpoints = Vec::new();
@@ -665,6 +747,11 @@ fn create_devices(
iova_max_addr.unwrap_or(0),
));
+ #[cfg(feature = "gpu")]
+ if vfio_pci_device.is_gfx() {
+ has_vfio_gfx_device = true;
+ }
+
if let Some(viommu_mapper) = viommu_mapper {
iommu_attached_endpoints.insert(
vfio_pci_device
@@ -768,6 +855,8 @@ fn create_devices(
gpu_control_tube,
#[cfg(feature = "gpu")]
render_server_fd,
+ #[cfg(feature = "gpu")]
+ has_vfio_gfx_device,
#[cfg(feature = "registered_events")]
registered_evt_q,
)?;
@@ -897,6 +986,7 @@ fn create_file_backed_mappings(
Box::new(memory_mapping),
!mapping.writable,
/* log_dirty_pages = */ false,
+ MemCacheType::CacheCoherent,
)
.context("failed to configure file-backed mapping")?;
}
@@ -1063,6 +1153,8 @@ fn setup_vm_components(cfg: &Config) -> Result<VmComponents> {
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
let mut cpu_frequencies = BTreeMap::new();
+ #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
+ let mut virt_cpufreq_socket = None;
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
if cfg.virt_cpufreq {
@@ -1092,6 +1184,18 @@ fn setup_vm_components(cfg: &Config) -> Result<VmComponents> {
panic!("No frequency domain for cpu:{}", cpu_id);
}
}
+
+ virt_cpufreq_socket = if let Some(path) = &cfg.virt_cpufreq_socket {
+ let file = base::open_file_or_duplicate(path, OpenOptions::new().write(true))
+ .with_context(|| {
+ format!("failed to open virt_cpufreq_socket {}", path.display())
+ })?;
+ let fd: std::os::fd::OwnedFd = file.into();
+ let socket: std::os::unix::net::UnixStream = fd.into();
+ Some(socket)
+ } else {
+ None
+ };
}
// if --enable-fw-cfg or --fw-cfg was given, we want to enable fw_cfg
@@ -1122,6 +1226,8 @@ fn setup_vm_components(cfg: &Config) -> Result<VmComponents> {
vcpu_affinity: cfg.vcpu_affinity.clone(),
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
cpu_frequencies,
+ #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
+ virt_cpufreq_socket,
fw_cfg_parameters: cfg.fw_cfg_parameters.clone(),
cpu_clusters,
cpu_capacity,
@@ -2098,6 +2204,7 @@ fn start_pci_root_worker(
source: VmMemorySource::SharedMemory(shmem),
dest: VmMemoryDestination::GuestPhysicalAddress(addr.0),
prot: Protection::read(),
+ cache: MemCacheType::CacheCoherent,
})
.context("failed to send request")?;
match self.vm_control_tube.recv::<VmMemoryResponse>() {
@@ -2369,6 +2476,7 @@ fn handle_hotplug_net_add<V: VmArch, Vcpu: VcpuArch>(
vhost_net: None,
vq_pairs: None,
packed_queue: false,
+ pci_address: None,
};
let ret = add_hotplug_net(
linux,
diff --git a/src/crosvm/sys/linux/config.rs b/src/crosvm/sys/linux/config.rs
index 3a1faf615..8ea63b7e6 100644
--- a/src/crosvm/sys/linux/config.rs
+++ b/src/crosvm/sys/linux/config.rs
@@ -233,8 +233,6 @@ mod tests {
use super::*;
use crate::crosvm::config::from_key_values;
- use crate::crosvm::config::DEFAULT_TOUCH_DEVICE_HEIGHT;
- use crate::crosvm::config::DEFAULT_TOUCH_DEVICE_WIDTH;
#[test]
fn parse_coiommu_options() {
@@ -323,145 +321,6 @@ mod tests {
}
#[test]
- fn single_touch_spec_and_track_pad_spec_default_size() {
- let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
- &[],
- &[
- "--single-touch",
- "/dev/single-touch-test",
- "--trackpad",
- "/dev/single-touch-test",
- "/dev/null",
- ],
- )
- .unwrap()
- .try_into()
- .unwrap();
-
- assert_eq!(
- config.virtio_single_touch.first().unwrap().get_size(),
- (DEFAULT_TOUCH_DEVICE_WIDTH, DEFAULT_TOUCH_DEVICE_HEIGHT)
- );
- assert_eq!(
- config.virtio_trackpad.first().unwrap().get_size(),
- (DEFAULT_TOUCH_DEVICE_WIDTH, DEFAULT_TOUCH_DEVICE_HEIGHT)
- );
- }
-
- #[cfg(feature = "gpu")]
- #[test]
- fn single_touch_spec_default_size_from_gpu() {
- let width = 12345u32;
- let height = 54321u32;
-
- let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
- &[],
- &[
- "--single-touch",
- "/dev/single-touch-test",
- "--gpu",
- &format!("width={},height={}", width, height),
- "/dev/null",
- ],
- )
- .unwrap()
- .try_into()
- .unwrap();
-
- assert_eq!(
- config.virtio_single_touch.first().unwrap().get_size(),
- (width, height)
- );
- }
-
- #[test]
- fn single_touch_spec_and_track_pad_spec_with_size() {
- let width = 12345u32;
- let height = 54321u32;
- let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
- &[],
- &[
- "--single-touch",
- &format!("/dev/single-touch-test:{}:{}", width, height),
- "--trackpad",
- &format!("/dev/single-touch-test:{}:{}", width, height),
- "/dev/null",
- ],
- )
- .unwrap()
- .try_into()
- .unwrap();
-
- assert_eq!(
- config.virtio_single_touch.first().unwrap().get_size(),
- (width, height)
- );
- assert_eq!(
- config.virtio_trackpad.first().unwrap().get_size(),
- (width, height)
- );
- }
-
- #[cfg(feature = "gpu")]
- #[test]
- fn single_touch_spec_with_size_independent_from_gpu() {
- let touch_width = 12345u32;
- let touch_height = 54321u32;
- let display_width = 1234u32;
- let display_height = 5432u32;
- let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
- &[],
- &[
- "--single-touch",
- &format!("/dev/single-touch-test:{}:{}", touch_width, touch_height),
- "--gpu",
- &format!("width={},height={}", display_width, display_height),
- "/dev/null",
- ],
- )
- .unwrap()
- .try_into()
- .unwrap();
-
- assert_eq!(
- config.virtio_single_touch.first().unwrap().get_size(),
- (touch_width, touch_height)
- );
- }
-
- #[test]
- fn virtio_switches() {
- let mut config: Config = crate::crosvm::cmdline::RunCommand::from_args(
- &[],
- &["--switches", "/dev/switches-test", "/dev/null"],
- )
- .unwrap()
- .try_into()
- .unwrap();
-
- assert_eq!(
- config.virtio_switches.pop().unwrap(),
- PathBuf::from("/dev/switches-test")
- );
- }
-
- #[test]
- fn virtio_rotary() {
- let mut config: Config = crate::crosvm::cmdline::RunCommand::from_args(
- &[],
- &["--rotary", "/dev/rotary-test", "/dev/null"],
- )
- .unwrap()
- .try_into()
- .unwrap();
-
- assert_eq!(
- config.virtio_rotary.pop().unwrap(),
- PathBuf::from("/dev/rotary-test")
- );
- }
-
- #[test]
fn vfio_pci_path() {
let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
&[],
diff --git a/src/crosvm/sys/linux/device_helpers.rs b/src/crosvm/sys/linux/device_helpers.rs
index a9de7cd62..3644c89ea 100644
--- a/src/crosvm/sys/linux/device_helpers.rs
+++ b/src/crosvm/sys/linux/device_helpers.rs
@@ -66,6 +66,7 @@ use devices::VfioPciDevice;
use devices::VfioPlatformDevice;
#[cfg(feature = "vtpm")]
use devices::VtpmProxy;
+use hypervisor::MemCacheType;
use hypervisor::ProtectionType;
use hypervisor::Vm;
use jail::*;
@@ -83,7 +84,6 @@ use sync::Mutex;
use vm_control::api::VmMemoryClient;
use vm_memory::GuestAddress;
-use crate::crosvm::config::TouchDeviceOption;
use crate::crosvm::config::VhostUserFrontendOption;
use crate::crosvm::config::VhostUserFsOption;
@@ -458,19 +458,19 @@ pub fn create_vtpm_proxy_device(
})
}
-pub fn create_single_touch_device(
+pub fn create_single_touch_device<T: IntoUnixStream>(
protection_type: ProtectionType,
jail_config: &Option<JailConfig>,
- single_touch_spec: &TouchDeviceOption,
+ single_touch_socket: T,
+ width: u32,
+ height: u32,
+ name: Option<&str>,
idx: u32,
) -> DeviceResult {
- let socket = single_touch_spec
- .get_path()
+ let socket = single_touch_socket
.into_unix_stream()
.context("failed configuring virtio single touch")?;
- let (width, height) = single_touch_spec.get_size();
- let name = single_touch_spec.get_name();
let dev = virtio::input::new_single_touch(
idx,
socket,
@@ -486,19 +486,19 @@ pub fn create_single_touch_device(
})
}
-pub fn create_multi_touch_device(
+pub fn create_multi_touch_device<T: IntoUnixStream>(
protection_type: ProtectionType,
jail_config: &Option<JailConfig>,
- multi_touch_spec: &TouchDeviceOption,
+ multi_touch_socket: T,
+ width: u32,
+ height: u32,
+ name: Option<&str>,
idx: u32,
) -> DeviceResult {
- let socket = multi_touch_spec
- .get_path()
+ let socket = multi_touch_socket
.into_unix_stream()
.context("failed configuring virtio multi touch")?;
- let (width, height) = multi_touch_spec.get_size();
- let name = multi_touch_spec.get_name();
let dev = virtio::input::new_multi_touch(
idx,
socket,
@@ -515,19 +515,19 @@ pub fn create_multi_touch_device(
})
}
-pub fn create_trackpad_device(
+pub fn create_trackpad_device<T: IntoUnixStream>(
protection_type: ProtectionType,
jail_config: &Option<JailConfig>,
- trackpad_spec: &TouchDeviceOption,
+ trackpad_socket: T,
+ width: u32,
+ height: u32,
+ name: Option<&str>,
idx: u32,
) -> DeviceResult {
- let socket = trackpad_spec
- .get_path()
+ let socket = trackpad_socket
.into_unix_stream()
.context("failed configuring virtio trackpad")?;
- let (width, height) = trackpad_spec.get_size();
- let name = trackpad_spec.get_name();
let dev = virtio::input::new_trackpad(
idx,
socket,
@@ -695,13 +695,21 @@ impl VirtioDeviceBuilder for &NetParameters {
tap,
mac,
self.packed_queue,
+ self.pci_address,
)
.context("failed to set up virtio-vhost networking")?,
) as Box<dyn VirtioDevice>
} else {
Box::new(
- virtio::Net::new(features, tap, vq_pairs, mac, self.packed_queue)
- .context("failed to set up virtio networking")?,
+ virtio::Net::new(
+ features,
+ tap,
+ vq_pairs,
+ mac,
+ self.packed_queue,
+ self.pci_address,
+ )
+ .context("failed to set up virtio networking")?,
) as Box<dyn VirtioDevice>
})
}
@@ -1133,6 +1141,7 @@ pub fn create_pmem_device(
Box::new(arena),
/* read_only = */ disk.read_only,
/* log_dirty_pages = */ false,
+ MemCacheType::CacheCoherent,
)
.context("failed to add pmem device memory")?;
diff --git a/src/crosvm/sys/linux/gpu.rs b/src/crosvm/sys/linux/gpu.rs
index 00ac8fee9..613ea9bc1 100644
--- a/src/crosvm/sys/linux/gpu.rs
+++ b/src/crosvm/sys/linux/gpu.rs
@@ -88,11 +88,36 @@ pub fn create_gpu_device(
gpu_control_tube: Tube,
resource_bridges: Vec<Tube>,
render_server_fd: Option<SafeDescriptor>,
+ has_vfio_gfx_device: bool,
event_devices: Vec<EventDevice>,
) -> DeviceResult {
let is_sandboxed = cfg.jail_config.is_some();
let mut gpu_params = cfg.gpu_parameters.clone().unwrap();
- gpu_params.external_blob = is_sandboxed;
+
+ if gpu_params.fixed_blob_mapping {
+ if has_vfio_gfx_device {
+ // TODO(b/323368701): make fixed_blob_mapping compatible with vfio dma_buf mapping for
+ // GPU pci passthrough.
+ debug!("gpu fixed blob mapping disabled: not compatible with passthrough GPU.");
+ gpu_params.fixed_blob_mapping = false;
+ } else if cfg!(feature = "vulkano") {
+ // TODO(b/244591751): make fixed_blob_mapping compatible with vulkano for opaque_fd blob
+ // mapping.
+ debug!("gpu fixed blob mapping disabled: not compatible with vulkano");
+ gpu_params.fixed_blob_mapping = false;
+ } else if cfg!(feature = "noncoherent-dma") {
+ // TODO(b/246334944): make fixed_blob_mapping compatible with noncoherent-dma.
+ debug!("gpu fixed blob mapping disabled: not compatible with noncoherent-dma");
+ gpu_params.fixed_blob_mapping = false;
+ }
+ }
+
+ // external_blob must be enforced to ensure that a blob can be exported to a mappable descriptor
+ // (dma_buf, shmem, ...), since:
+ // - is_sandboxed implies that blob mapping will be done out-of-process by the crosvm
+ // hypervisor process.
+ // - fixed_blob_mapping is not yet compatible with VmMemorySource::ExternalMapping
+ gpu_params.external_blob = is_sandboxed || gpu_params.fixed_blob_mapping;
// Implicit launch is not allowed when sandboxed. A socket fd from a separate sandboxed
// render_server process must be provided instead.
diff --git a/src/crosvm/sys/linux/jail_warden.rs b/src/crosvm/sys/linux/jail_warden.rs
index bdb4737b4..3cd9e3420 100644
--- a/src/crosvm/sys/linux/jail_warden.rs
+++ b/src/crosvm/sys/linux/jail_warden.rs
@@ -126,7 +126,7 @@ impl JailWardenImpl {
#[cfg(feature = "swap")]
swap_device_helper,
) {
- panic!("jail_worker_process exited with error: {:?}", e);
+ error!("jail_worker_process exited with error: {:#}", e);
}
})?;
Ok(Self {
diff --git a/src/crosvm/sys/windows/broker.rs b/src/crosvm/sys/windows/broker.rs
index 8a93be8b5..c0067fd52 100644
--- a/src/crosvm/sys/windows/broker.rs
+++ b/src/crosvm/sys/windows/broker.rs
@@ -119,6 +119,7 @@ use win_util::ProcessType;
use winapi::shared::winerror::ERROR_ACCESS_DENIED;
use winapi::um::processthreadsapi::TerminateProcess;
+use crate::crosvm::config::InputDeviceOption;
#[cfg(feature = "gpu")]
use crate::sys::windows::get_gpu_product_configs;
#[cfg(feature = "audio")]
@@ -701,31 +702,35 @@ fn run_internal(mut cfg: Config, log_args: LogArgs) -> Result<()> {
)?;
#[cfg(feature = "gpu")]
- let _gpu_child = if !cfg
- .vhost_user
- .iter()
- .any(|opt| opt.type_ == DeviceType::Gpu)
- {
- // Pass both backend and frontend configs to main process.
- cfg.gpu_backend_config = Some(gpu_cfg.0);
- cfg.gpu_vmm_config = Some(gpu_cfg.1);
- None
+ let _gpu_child = if let Some(gpu_cfg) = gpu_cfg {
+ if !cfg
+ .vhost_user
+ .iter()
+ .any(|opt| opt.type_ == DeviceType::Gpu)
+ {
+ // Pass both backend and frontend configs to main process.
+ cfg.gpu_backend_config = Some(gpu_cfg.0);
+ cfg.gpu_vmm_config = Some(gpu_cfg.1);
+ None
+ } else {
+ Some(start_up_gpu(
+ &mut cfg,
+ &log_args,
+ gpu_cfg,
+ &mut input_event_split_config,
+ &mut main_child,
+ &mut children,
+ &mut wait_ctx,
+ &mut metric_tubes,
+ window_procedure_thread_builder
+ .take()
+ .ok_or_else(|| anyhow!("window_procedure_thread_builder is missing."))?,
+ #[cfg(feature = "process-invariants")]
+ &process_invariants,
+ )?)
+ }
} else {
- Some(start_up_gpu(
- &mut cfg,
- &log_args,
- gpu_cfg,
- &mut input_event_split_config,
- &mut main_child,
- &mut children,
- &mut wait_ctx,
- &mut metric_tubes,
- window_procedure_thread_builder
- .take()
- .ok_or_else(|| anyhow!("window_procedure_thread_builder is missing."))?,
- #[cfg(feature = "process-invariants")]
- &process_invariants,
- )?)
+ None
};
#[cfg(feature = "gpu")]
@@ -1677,20 +1682,24 @@ fn platform_create_input_event_config(cfg: &Config) -> Result<InputEventSplitCon
let mut mouse_pipes = vec![];
let mut keyboard_pipes = vec![];
- for _ in cfg.virtio_multi_touch.iter() {
- let (event_device_pipe, virtio_input_pipe) =
- StreamChannel::pair(BlockingMode::Nonblocking, FramingMode::Byte)
- .exit_context(Exit::EventDeviceSetup, "failed to set up EventDevice")?;
- event_devices.push(EventDevice::touchscreen(event_device_pipe));
- multi_touch_pipes.push(virtio_input_pipe);
- }
-
- for _ in cfg.virtio_mice.iter() {
- let (event_device_pipe, virtio_input_pipe) =
- StreamChannel::pair(BlockingMode::Nonblocking, FramingMode::Byte)
- .exit_context(Exit::EventDeviceSetup, "failed to set up EventDevice")?;
- event_devices.push(EventDevice::mouse(event_device_pipe));
- mouse_pipes.push(virtio_input_pipe);
+ for input in &cfg.virtio_input {
+ match input {
+ InputDeviceOption::MultiTouch { .. } => {
+ let (event_device_pipe, virtio_input_pipe) =
+ StreamChannel::pair(BlockingMode::Nonblocking, FramingMode::Byte)
+ .exit_context(Exit::EventDeviceSetup, "failed to set up EventDevice")?;
+ event_devices.push(EventDevice::touchscreen(event_device_pipe));
+ multi_touch_pipes.push(virtio_input_pipe);
+ }
+ InputDeviceOption::Mouse { .. } => {
+ let (event_device_pipe, virtio_input_pipe) =
+ StreamChannel::pair(BlockingMode::Nonblocking, FramingMode::Byte)
+ .exit_context(Exit::EventDeviceSetup, "failed to set up EventDevice")?;
+ event_devices.push(EventDevice::mouse(event_device_pipe));
+ mouse_pipes.push(virtio_input_pipe);
+ }
+ _ => {}
+ }
}
// One keyboard
@@ -1740,7 +1749,10 @@ fn platform_create_gpu(
exit_evt_wrtube: SendTube,
gpu_control_host_tube: Tube,
gpu_control_device_tube: Tube,
-) -> Result<(GpuBackendConfig, GpuVmmConfig)> {
+) -> Result<Option<(GpuBackendConfig, GpuVmmConfig)>> {
+ if cfg.gpu_parameters.is_none() {
+ return Ok(None);
+ }
let exit_event = Event::new().exit_context(Exit::CreateEvent, "failed to create exit event")?;
exit_events.push(
exit_event
@@ -1770,7 +1782,7 @@ fn platform_create_gpu(
product_config: vmm_config_product,
};
- Ok((backend_config, vmm_config))
+ Ok(Some((backend_config, vmm_config)))
}
#[cfg(feature = "gpu")]
diff --git a/src/main.rs b/src/main.rs
index 337287115..bb2544612 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -230,7 +230,7 @@ fn inject_gpe(cmd: cmdline::GpeCommand) -> std::result::Result<(), ()> {
fn balloon_vms(cmd: cmdline::BalloonCommand) -> std::result::Result<(), ()> {
let command = BalloonControlCommand::Adjust {
num_bytes: cmd.num_bytes,
- wait_for_success: false,
+ wait_for_success: cmd.wait,
};
vms_request(&VmRequest::BalloonCommand(command), cmd.socket_path)
}
@@ -681,6 +681,19 @@ fn prepare_argh_args<I: IntoIterator<Item = String>>(args_iter: I) -> Vec<String
args
}
+fn shorten_usage(help: &str) -> String {
+ let mut lines = help.lines().collect::<Vec<_>>();
+ let first_line = lines[0].split(char::is_whitespace).collect::<Vec<_>>();
+
+ // Shorten the usage line if it's for `crovm run` command that has so many options.
+ let run_usage = format!("Usage: {} run <options> KERNEL", first_line[1]);
+ if first_line[0] == "Usage:" && first_line[2] == "run" {
+ lines[0] = &run_usage;
+ }
+
+ lines.join("\n")
+}
+
fn crosvm_main<I: IntoIterator<Item = String>>(args: I) -> Result<CommandStatus> {
let _library_watcher = sys::get_library_watcher();
@@ -700,7 +713,8 @@ fn crosvm_main<I: IntoIterator<Item = String>>(args: I) -> Result<CommandStatus>
Err(e) if e.status.is_ok() => {
// If parsing succeeded and the user requested --help, print the usage message to stdout
// and exit with success.
- println!("{}", e.output);
+ let help = shorten_usage(&e.output);
+ println!("{help}");
return Ok(CommandStatus::SuccessOrVmStop);
}
Err(e) => {
@@ -1003,4 +1017,17 @@ mod tests {
))
);
}
+
+ #[test]
+ fn test_shorten_run_usage() {
+ let help = r"Usage: crosvm run [<KERNEL>] [options] <very long line>...
+
+Start a new crosvm instance";
+ assert_eq!(
+ shorten_usage(help),
+ r"Usage: crosvm run <options> KERNEL
+
+Start a new crosvm instance"
+ );
+ }
}
diff --git a/src/sys/windows.rs b/src/sys/windows.rs
index 65b6ea343..4fcdcbff5 100644
--- a/src/sys/windows.rs
+++ b/src/sys/windows.rs
@@ -218,10 +218,13 @@ use x86_64::X8664arch as Arch;
use crate::crosvm::config::Config;
use crate::crosvm::config::Executable;
+use crate::crosvm::config::InputDeviceOption;
#[cfg(any(feature = "gvm", feature = "whpx"))]
use crate::crosvm::config::IrqChipKind;
#[cfg(feature = "gpu")]
use crate::crosvm::config::TouchDeviceOption;
+use crate::crosvm::config::DEFAULT_TOUCH_DEVICE_HEIGHT;
+use crate::crosvm::config::DEFAULT_TOUCH_DEVICE_WIDTH;
use crate::crosvm::sys::config::HypervisorKind;
use crate::crosvm::sys::windows::broker::BrokerTubes;
#[cfg(feature = "stats")]
@@ -408,12 +411,12 @@ fn create_vhost_user_snd_device(base_features: u64, vhost_user_tube: Tube) -> De
#[cfg(feature = "gpu")]
fn create_multi_touch_device(
cfg: &Config,
- multi_touch_spec: &TouchDeviceOption,
event_pipe: StreamChannel,
+ width: u32,
+ height: u32,
+ name: Option<&str>,
idx: u32,
) -> DeviceResult {
- let (width, height) = multi_touch_spec.get_size();
- let name = multi_touch_spec.get_name();
let dev = virtio::input::new_multi_touch(
idx,
event_pipe,
@@ -696,23 +699,48 @@ fn create_virtio_input_event_devices(
) -> DeviceResult<Vec<VirtioDeviceStub>> {
let mut devs = Vec::new();
- if !cfg.virtio_single_touch.is_empty() {
- unimplemented!("--single-touch is no longer supported. Use --multi-touch instead.");
- }
-
// Iterate event devices, create the VMM end.
- for (idx, pipe) in input_event_vmm_config
+ let mut multi_touch_pipes = input_event_vmm_config
.multi_touch_pipes
.drain(..)
- .enumerate()
- {
- devs.push(create_multi_touch_device(
- cfg,
- &cfg.virtio_multi_touch[idx],
- pipe,
- idx as u32,
- )?);
+ .enumerate();
+ for input in &cfg.virtio_input {
+ match input {
+ InputDeviceOption::SingleTouch { .. } => {
+ unimplemented!("--single-touch is no longer supported. Use --multi-touch instead.");
+ }
+ InputDeviceOption::MultiTouch {
+ width,
+ height,
+ name,
+ ..
+ } => {
+ let Some((idx, pipe)) = multi_touch_pipes.next() else {
+ break;
+ };
+ let mut width = *width;
+ let mut height = *height;
+ if idx == 0 {
+ if width.is_none() {
+ width = cfg.display_input_width;
+ }
+ if height.is_none() {
+ height = cfg.display_input_height;
+ }
+ }
+ devs.push(create_multi_touch_device(
+ cfg,
+ pipe,
+ width.unwrap_or(DEFAULT_TOUCH_DEVICE_WIDTH),
+ height.unwrap_or(DEFAULT_TOUCH_DEVICE_HEIGHT),
+ name.as_deref(),
+ idx as u32,
+ )?);
+ }
+ _ => {}
+ }
}
+ drop(multi_touch_pipes);
product::push_mouse_device(cfg, &mut input_event_vmm_config, &mut devs)?;
@@ -1954,12 +1982,11 @@ fn create_whpx_split_irq_chip(
)
}
-fn create_userspace_irq_chip<Vm, Vcpu>(
+fn create_userspace_irq_chip<Vcpu>(
vcpu_count: usize,
ioapic_device_tube: Tube,
) -> base::Result<UserspaceIrqChip<Vcpu>>
where
- Vm: VmArch + 'static,
Vcpu: VcpuArch + 'static,
{
info!("Creating userspace irqchip");
@@ -2252,6 +2279,7 @@ pub fn run_config_for_broker(raw_tube_transporter: RawDescriptor) -> Result<Exit
.recv::<Event>()
.exit_context(Exit::TubeFailure, "failed to read bootstrap tube")?,
);
+ #[cfg(feature = "crash-report")]
let crash_tube_map = bootstrap_tube
.recv::<HashMap<ProcessType, Vec<SendTube>>>()
.exit_context(Exit::TubeFailure, "failed to read bootstrap tube")?;
@@ -2327,10 +2355,8 @@ fn run_config_inner(
let vm = create_haxm_vm(haxm, guest_mem, &cfg.kernel_log_file)?;
let (ioapic_host_tube, ioapic_device_tube) =
Tube::pair().exit_context(Exit::CreateTube, "failed to create tube")?;
- let irq_chip = create_userspace_irq_chip::<HaxmVm, HaxmVcpu>(
- components.vcpu_count,
- ioapic_device_tube,
- )?;
+ let irq_chip =
+ create_userspace_irq_chip::<HaxmVcpu>(components.vcpu_count, ioapic_device_tube)?;
run_vm::<HaxmVcpu, HaxmVm>(
cfg,
components,
@@ -2387,7 +2413,7 @@ fn run_config_inner(
WindowsIrqChip::WhpxSplit(create_whpx_split_irq_chip(&vm, ioapic_device_tube)?)
}
IrqChipKind::Userspace => {
- WindowsIrqChip::Userspace(create_userspace_irq_chip::<WhpxVm, WhpxVcpu>(
+ WindowsIrqChip::Userspace(create_userspace_irq_chip::<WhpxVcpu>(
components.vcpu_count,
ioapic_device_tube,
)?)
@@ -2420,7 +2446,7 @@ fn run_config_inner(
let (host_tube, ioapic_device_tube) =
Tube::pair().exit_context(Exit::CreateTube, "failed to create tube")?;
ioapic_host_tube = Some(host_tube);
- WindowsIrqChip::Userspace(create_userspace_irq_chip::<GvmVm, GvmVcpu>(
+ WindowsIrqChip::Userspace(create_userspace_irq_chip::<GvmVcpu>(
components.vcpu_count,
ioapic_device_tube,
)?)
diff --git a/src/sys/windows/generic.rs b/src/sys/windows/generic.rs
index 7979e2929..17b1ff66f 100644
--- a/src/sys/windows/generic.rs
+++ b/src/sys/windows/generic.rs
@@ -3,13 +3,16 @@
// found in the LICENSE file.
use std::collections::BTreeMap;
+use std::thread;
use std::thread::JoinHandle;
+use std::time::Duration;
use anyhow::Result;
use arch::RunnableLinuxVm;
use arch::VcpuArch;
use arch::VirtioDeviceStub;
use arch::VmArch;
+use base::info;
use base::AsRawDescriptor;
use base::CloseNotifier;
use base::Event;
@@ -296,6 +299,8 @@ pub(super) fn virtio_sound_enabled() -> bool {
}
pub(crate) fn run_metrics(_args: RunMetricsCommand) -> Result<()> {
+ info!("sleep forever. We will get killed by broker");
+ thread::sleep(Duration::MAX);
Ok(())
}
diff --git a/src/sys/windows/main.rs b/src/sys/windows/main.rs
index c4fa44899..b1853c68b 100644
--- a/src/sys/windows/main.rs
+++ b/src/sys/windows/main.rs
@@ -25,7 +25,7 @@ use crosvm_cli::sys::windows::exit::ExitContextAnyhow;
use metrics::protos::event_details::EmulatorDllDetails;
use metrics::protos::event_details::RecordDetails;
use metrics::MetricEventType;
-#[cfg(all(feature = "slirp"))]
+#[cfg(feature = "slirp")]
use net_util::slirp::sys::windows::SlirpStartupConfig;
use tube_transporter::TubeToken;
use tube_transporter::TubeTransporterReader;
@@ -36,13 +36,13 @@ use crate::crosvm::cmdline::RunCommand;
use crate::crosvm::sys::cmdline::Commands;
use crate::crosvm::sys::cmdline::DeviceSubcommand;
use crate::crosvm::sys::cmdline::RunMainCommand;
-#[cfg(all(feature = "slirp"))]
+#[cfg(feature = "slirp")]
use crate::crosvm::sys::cmdline::RunSlirpCommand;
use crate::sys::windows::product::run_metrics;
use crate::CommandStatus;
use crate::Config;
-#[cfg(all(feature = "slirp"))]
+#[cfg(feature = "slirp")]
pub(crate) fn run_slirp(args: RunSlirpCommand) -> Result<()> {
let raw_transport_tube = args.bootstrap as RawDescriptor;
diff --git a/swap/src/page_handler.rs b/swap/src/page_handler.rs
index 58929cd71..ff2d3d59c 100644
--- a/swap/src/page_handler.rs
+++ b/swap/src/page_handler.rs
@@ -699,7 +699,7 @@ impl SwapInContext<'_> {
let PageHandleContext { regions, file, .. } = &mut *ctx;
for region in regions.iter_mut() {
let region_tail_idx_in_file = region.base_page_idx_in_file + region.num_pages;
- if idx_range_in_file.start > region_tail_idx_in_file {
+ if idx_range_in_file.start >= region_tail_idx_in_file {
continue;
} else if idx_range_in_file.start < region.base_page_idx_in_file {
return Err(Error::File(FileError::OutOfRange));
diff --git a/swap/tests/main.rs b/swap/tests/main.rs
index 335623344..f09390b48 100644
--- a/swap/tests/main.rs
+++ b/swap/tests/main.rs
@@ -9,6 +9,8 @@ mod common;
#[cfg(all(unix, feature = "enable"))]
mod test {
+ use std::time::Duration;
+
use base::pagesize;
use base::sys::wait_for_pid;
use base::AsRawDescriptor;
@@ -19,6 +21,11 @@ mod test {
use swap::userfaultfd::register_regions;
use swap::userfaultfd::unregister_regions;
use swap::userfaultfd::Userfaultfd;
+ use swap::SwapController;
+ use swap::SwapState;
+ use vm_memory::GuestAddress;
+ use vm_memory::GuestMemory;
+ use vm_memory::MemoryRegionOptions;
use super::common::*;
@@ -98,6 +105,744 @@ mod test {
// no error from ENOMEM
assert_eq!(result.is_ok(), true);
}
+
+ fn create_guest_memory() -> GuestMemory {
+ // guest memory with 2 regions. The address and size are from a real device.
+ GuestMemory::new_with_options(&[
+ (
+ GuestAddress(0x0000000000000000),
+ 3489660928,
+ MemoryRegionOptions::new(),
+ ),
+ (
+ GuestAddress(0x0000000100000000),
+ 3537895424,
+ MemoryRegionOptions::new(),
+ ),
+ ])
+ .unwrap()
+ }
+
+ fn wait_for_state(controller: &SwapController, state: SwapState) -> bool {
+ for _ in 0..10 {
+ if controller.status().unwrap().state == state {
+ return true;
+ }
+ std::thread::sleep(Duration::from_millis(10));
+ }
+ false
+ }
+
+ pub fn controller_enable() {
+ let dir = tempfile::tempdir().unwrap();
+ let guest_memory = create_guest_memory();
+
+ let controller = SwapController::launch(guest_memory.clone(), dir.path(), &None).unwrap();
+
+ guest_memory
+ .write_all_at_addr(&[1u8; 4096], GuestAddress(0x0000000000000000))
+ .unwrap();
+ guest_memory
+ .write_all_at_addr(&[2u8; 4096], GuestAddress(0x0000000000000000 + 4096))
+ .unwrap();
+ guest_memory
+ .write_all_at_addr(&[3u8; 4096], GuestAddress(0x0000000000000000 + 10 * 4096))
+ .unwrap();
+ guest_memory
+ .write_all_at_addr(
+ &[4u8; 3 * 1024 * 1024],
+ GuestAddress(0x0000000000000000 + 20 * 4096),
+ )
+ .unwrap();
+ guest_memory
+ .write_all_at_addr(&[5u8; 4096], GuestAddress(0x0000000100000000))
+ .unwrap();
+ guest_memory
+ .write_all_at_addr(&[6u8; 4096], GuestAddress(0x0000000100000000 + 4096))
+ .unwrap();
+ guest_memory
+ .write_all_at_addr(&[7u8; 4096], GuestAddress(0x0000000100000000 + 10 * 4096))
+ .unwrap();
+ guest_memory
+ .write_all_at_addr(
+ &[8u8; 3 * 1024 * 1024],
+ GuestAddress(0x0000000100000000 + 20 * 4096),
+ )
+ .unwrap();
+
+ controller.enable().unwrap();
+
+ let status = controller.status().unwrap();
+ assert_eq!(status.state, SwapState::Pending);
+ assert_eq!(status.state_transition.pages, 1542);
+
+ let mut buf = [0u8; 4096];
+ let mut long_buf = [0u8; 3 * 1024 * 1024];
+
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000))
+ .unwrap();
+ assert_eq!(buf, [1u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000 + 4096))
+ .unwrap();
+ assert_eq!(buf, [2u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000 + 2 * 4096))
+ .unwrap();
+ assert_eq!(buf, [0u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000 + 10 * 4096))
+ .unwrap();
+ assert_eq!(buf, [3u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut long_buf, GuestAddress(0x0000000000000000 + 20 * 4096))
+ .unwrap();
+ assert_eq!(long_buf, [4u8; 3 * 1024 * 1024]);
+
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000))
+ .unwrap();
+ assert_eq!(buf, [5u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000 + 4096))
+ .unwrap();
+ assert_eq!(buf, [6u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000 + 2 * 4096))
+ .unwrap();
+ assert_eq!(buf, [0u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000 + 10 * 4096))
+ .unwrap();
+ assert_eq!(buf, [7u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut long_buf, GuestAddress(0x0000000100000000 + 20 * 4096))
+ .unwrap();
+ assert_eq!(long_buf, [8u8; 3 * 1024 * 1024]);
+
+ controller.enable().unwrap();
+ assert_eq!(controller.status().unwrap().state_transition.pages, 1544);
+
+ guest_memory
+ .write_all_at_addr(&[9u8; 4096], GuestAddress(0x0000000000000000 + 4096))
+ .unwrap();
+ guest_memory
+ .write_all_at_addr(&[10u8; 4096], GuestAddress(0x0000000000000000 + 2 * 4096))
+ .unwrap();
+
+ controller.enable().unwrap();
+ assert_eq!(controller.status().unwrap().state_transition.pages, 2);
+
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000))
+ .unwrap();
+ assert_eq!(buf, [1u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000 + 4096))
+ .unwrap();
+ assert_eq!(buf, [9u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000 + 2 * 4096))
+ .unwrap();
+ assert_eq!(buf, [10u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000 + 10 * 4096))
+ .unwrap();
+ assert_eq!(buf, [3u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut long_buf, GuestAddress(0x0000000000000000 + 20 * 4096))
+ .unwrap();
+ assert_eq!(long_buf, [4u8; 3 * 1024 * 1024]);
+
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000))
+ .unwrap();
+ assert_eq!(buf, [5u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000 + 4096))
+ .unwrap();
+ assert_eq!(buf, [6u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000 + 2 * 4096))
+ .unwrap();
+ assert_eq!(buf, [0u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000 + 10 * 4096))
+ .unwrap();
+ assert_eq!(buf, [7u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut long_buf, GuestAddress(0x0000000100000000 + 20 * 4096))
+ .unwrap();
+ assert_eq!(long_buf, [8u8; 3 * 1024 * 1024]);
+
+ controller.enable().unwrap();
+ drop(controller);
+
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000))
+ .unwrap();
+ assert_eq!(buf, [1u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000 + 4096))
+ .unwrap();
+ assert_eq!(buf, [9u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000 + 2 * 4096))
+ .unwrap();
+ assert_eq!(buf, [10u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000 + 10 * 4096))
+ .unwrap();
+ assert_eq!(buf, [3u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut long_buf, GuestAddress(0x0000000000000000 + 20 * 4096))
+ .unwrap();
+ assert_eq!(long_buf, [4u8; 3 * 1024 * 1024]);
+
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000))
+ .unwrap();
+ assert_eq!(buf, [5u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000 + 4096))
+ .unwrap();
+ assert_eq!(buf, [6u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000 + 2 * 4096))
+ .unwrap();
+ assert_eq!(buf, [0u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000 + 10 * 4096))
+ .unwrap();
+ assert_eq!(buf, [7u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut long_buf, GuestAddress(0x0000000100000000 + 20 * 4096))
+ .unwrap();
+ assert_eq!(long_buf, [8u8; 3 * 1024 * 1024]);
+ }
+
+ pub fn controller_swap_out() {
+ let dir = tempfile::tempdir().unwrap();
+ let guest_memory = create_guest_memory();
+
+ let controller = SwapController::launch(guest_memory.clone(), dir.path(), &None).unwrap();
+
+ guest_memory
+ .write_all_at_addr(&[1u8; 4096], GuestAddress(0x0000000000000000))
+ .unwrap();
+ guest_memory
+ .write_all_at_addr(&[2u8; 4096], GuestAddress(0x0000000000000000 + 4096))
+ .unwrap();
+ guest_memory
+ .write_all_at_addr(&[3u8; 4096], GuestAddress(0x0000000000000000 + 10 * 4096))
+ .unwrap();
+ guest_memory
+ .write_all_at_addr(
+ &[4u8; 3 * 1024 * 1024],
+ GuestAddress(0x0000000000000000 + 20 * 4096),
+ )
+ .unwrap();
+ guest_memory
+ .write_all_at_addr(&[5u8; 4096], GuestAddress(0x0000000100000000))
+ .unwrap();
+ guest_memory
+ .write_all_at_addr(&[6u8; 4096], GuestAddress(0x0000000100000000 + 4096))
+ .unwrap();
+ guest_memory
+ .write_all_at_addr(&[7u8; 4096], GuestAddress(0x0000000100000000 + 10 * 4096))
+ .unwrap();
+ guest_memory
+ .write_all_at_addr(
+ &[8u8; 3 * 1024 * 1024],
+ GuestAddress(0x0000000100000000 + 20 * 4096),
+ )
+ .unwrap();
+
+ controller.enable().unwrap();
+ controller.swap_out().unwrap();
+ assert!(wait_for_state(&controller, SwapState::Active));
+ assert_eq!(controller.status().unwrap().state_transition.pages, 1542);
+
+ let mut buf = [0u8; 4096];
+ let mut long_buf = [0u8; 3 * 1024 * 1024];
+
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000))
+ .unwrap();
+ assert_eq!(buf, [1u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000 + 4096))
+ .unwrap();
+ assert_eq!(buf, [2u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000 + 2 * 4096))
+ .unwrap();
+ assert_eq!(buf, [0u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000 + 10 * 4096))
+ .unwrap();
+ assert_eq!(buf, [3u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut long_buf, GuestAddress(0x0000000000000000 + 20 * 4096))
+ .unwrap();
+ assert_eq!(long_buf, [4u8; 3 * 1024 * 1024]);
+
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000))
+ .unwrap();
+ assert_eq!(buf, [5u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000 + 4096))
+ .unwrap();
+ assert_eq!(buf, [6u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000 + 2 * 4096))
+ .unwrap();
+ assert_eq!(buf, [0u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000 + 10 * 4096))
+ .unwrap();
+ assert_eq!(buf, [7u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut long_buf, GuestAddress(0x0000000100000000 + 20 * 4096))
+ .unwrap();
+ assert_eq!(long_buf, [8u8; 3 * 1024 * 1024]);
+
+ controller.enable().unwrap();
+ controller.swap_out().unwrap();
+ assert!(wait_for_state(&controller, SwapState::Active));
+ assert_eq!(controller.status().unwrap().state_transition.pages, 1544);
+
+ guest_memory
+ .write_all_at_addr(&[9u8; 4096], GuestAddress(0x0000000000000000 + 4096))
+ .unwrap();
+ guest_memory
+ .write_all_at_addr(&[10u8; 4096], GuestAddress(0x0000000000000000 + 2 * 4096))
+ .unwrap();
+
+ controller.enable().unwrap();
+ controller.swap_out().unwrap();
+ assert!(wait_for_state(&controller, SwapState::Active));
+ assert_eq!(controller.status().unwrap().state_transition.pages, 2);
+
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000))
+ .unwrap();
+ assert_eq!(buf, [1u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000 + 4096))
+ .unwrap();
+ assert_eq!(buf, [9u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000 + 2 * 4096))
+ .unwrap();
+ assert_eq!(buf, [10u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000 + 10 * 4096))
+ .unwrap();
+ assert_eq!(buf, [3u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut long_buf, GuestAddress(0x0000000000000000 + 20 * 4096))
+ .unwrap();
+ assert_eq!(long_buf, [4u8; 3 * 1024 * 1024]);
+
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000))
+ .unwrap();
+ assert_eq!(buf, [5u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000 + 4096))
+ .unwrap();
+ assert_eq!(buf, [6u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000 + 2 * 4096))
+ .unwrap();
+ assert_eq!(buf, [0u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000 + 10 * 4096))
+ .unwrap();
+ assert_eq!(buf, [7u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut long_buf, GuestAddress(0x0000000100000000 + 20 * 4096))
+ .unwrap();
+ assert_eq!(long_buf, [8u8; 3 * 1024 * 1024]);
+
+ controller.enable().unwrap();
+ controller.swap_out().unwrap();
+ assert!(wait_for_state(&controller, SwapState::Active));
+ drop(controller);
+
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000))
+ .unwrap();
+ assert_eq!(buf, [1u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000 + 4096))
+ .unwrap();
+ assert_eq!(buf, [9u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000 + 2 * 4096))
+ .unwrap();
+ assert_eq!(buf, [10u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000 + 10 * 4096))
+ .unwrap();
+ assert_eq!(buf, [3u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut long_buf, GuestAddress(0x0000000000000000 + 20 * 4096))
+ .unwrap();
+ assert_eq!(long_buf, [4u8; 3 * 1024 * 1024]);
+
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000))
+ .unwrap();
+ assert_eq!(buf, [5u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000 + 4096))
+ .unwrap();
+ assert_eq!(buf, [6u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000 + 2 * 4096))
+ .unwrap();
+ assert_eq!(buf, [0u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000 + 10 * 4096))
+ .unwrap();
+ assert_eq!(buf, [7u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut long_buf, GuestAddress(0x0000000100000000 + 20 * 4096))
+ .unwrap();
+ assert_eq!(long_buf, [8u8; 3 * 1024 * 1024]);
+ }
+
+ pub fn controller_trim() {
+ let dir = tempfile::tempdir().unwrap();
+ let guest_memory = create_guest_memory();
+
+ let controller = SwapController::launch(guest_memory.clone(), dir.path(), &None).unwrap();
+
+ guest_memory
+ .write_all_at_addr(&[1u8; 4096], GuestAddress(0x0000000000000000))
+ .unwrap();
+ guest_memory
+ .write_all_at_addr(&[2u8; 4096], GuestAddress(0x0000000000000000 + 4096))
+ .unwrap();
+ guest_memory
+ .write_all_at_addr(&[0u8; 4096], GuestAddress(0x0000000000000000 + 2 * 4096))
+ .unwrap();
+ guest_memory
+ .write_all_at_addr(&[3u8; 4096], GuestAddress(0x0000000000000000 + 10 * 4096))
+ .unwrap();
+ guest_memory
+ .write_all_at_addr(
+ &[4u8; 3 * 1024 * 1024],
+ GuestAddress(0x0000000000000000 + 20 * 4096),
+ )
+ .unwrap();
+ guest_memory
+ .write_all_at_addr(&[5u8; 4096], GuestAddress(0x0000000100000000))
+ .unwrap();
+ guest_memory
+ .write_all_at_addr(&[6u8; 4096], GuestAddress(0x0000000100000000 + 4096))
+ .unwrap();
+ guest_memory
+ .write_all_at_addr(&[0u8; 4096], GuestAddress(0x0000000100000000 + 2 * 4096))
+ .unwrap();
+ guest_memory
+ .write_all_at_addr(&[7u8; 4096], GuestAddress(0x0000000100000000 + 10 * 4096))
+ .unwrap();
+ guest_memory
+ .write_all_at_addr(
+ &[8u8; 3 * 1024 * 1024],
+ GuestAddress(0x0000000100000000 + 20 * 4096),
+ )
+ .unwrap();
+
+ controller.enable().unwrap();
+ controller.trim().unwrap();
+ assert!(wait_for_state(&controller, SwapState::Pending));
+ assert_eq!(controller.status().unwrap().state_transition.pages, 2);
+ controller.swap_out().unwrap();
+ assert!(wait_for_state(&controller, SwapState::Active));
+ assert_eq!(controller.status().unwrap().state_transition.pages, 1542);
+
+ let mut buf = [0u8; 4096];
+ let mut long_buf = [0u8; 3 * 1024 * 1024];
+
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000))
+ .unwrap();
+ assert_eq!(buf, [1u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000 + 4096))
+ .unwrap();
+ assert_eq!(buf, [2u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000 + 2 * 4096))
+ .unwrap();
+ assert_eq!(buf, [0u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000 + 3 * 4096))
+ .unwrap();
+ assert_eq!(buf, [0u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000 + 10 * 4096))
+ .unwrap();
+ assert_eq!(buf, [3u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut long_buf, GuestAddress(0x0000000000000000 + 20 * 4096))
+ .unwrap();
+ assert_eq!(long_buf, [4u8; 3 * 1024 * 1024]);
+
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000))
+ .unwrap();
+ assert_eq!(buf, [5u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000 + 4096))
+ .unwrap();
+ assert_eq!(buf, [6u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000 + 2 * 4096))
+ .unwrap();
+ assert_eq!(buf, [0u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000 + 3 * 4096))
+ .unwrap();
+ assert_eq!(buf, [0u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000 + 10 * 4096))
+ .unwrap();
+ assert_eq!(buf, [7u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut long_buf, GuestAddress(0x0000000100000000 + 20 * 4096))
+ .unwrap();
+ assert_eq!(long_buf, [8u8; 3 * 1024 * 1024]);
+
+ guest_memory
+ .write_all_at_addr(&[9u8; 4096], GuestAddress(0x0000000000000000 + 4096))
+ .unwrap();
+ guest_memory
+ .write_all_at_addr(&[10u8; 4096], GuestAddress(0x0000000000000000 + 3 * 4096))
+ .unwrap();
+
+ controller.enable().unwrap();
+ controller.trim().unwrap();
+ assert!(wait_for_state(&controller, SwapState::Pending));
+ assert_eq!(controller.status().unwrap().state_transition.pages, 1544);
+ controller.swap_out().unwrap();
+ assert!(wait_for_state(&controller, SwapState::Active));
+ assert_eq!(controller.status().unwrap().state_transition.pages, 2);
+ drop(controller);
+
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000))
+ .unwrap();
+ assert_eq!(buf, [1u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000 + 4096))
+ .unwrap();
+ assert_eq!(buf, [9u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000 + 2 * 4096))
+ .unwrap();
+ assert_eq!(buf, [0u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000 + 3 * 4096))
+ .unwrap();
+ assert_eq!(buf, [10u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000 + 10 * 4096))
+ .unwrap();
+ assert_eq!(buf, [3u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut long_buf, GuestAddress(0x0000000000000000 + 20 * 4096))
+ .unwrap();
+ assert_eq!(long_buf, [4u8; 3 * 1024 * 1024]);
+
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000))
+ .unwrap();
+ assert_eq!(buf, [5u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000 + 4096))
+ .unwrap();
+ assert_eq!(buf, [6u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000 + 2 * 4096))
+ .unwrap();
+ assert_eq!(buf, [0u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000 + 10 * 4096))
+ .unwrap();
+ assert_eq!(buf, [7u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut long_buf, GuestAddress(0x0000000100000000 + 20 * 4096))
+ .unwrap();
+ assert_eq!(long_buf, [8u8; 3 * 1024 * 1024]);
+ }
+
+ pub fn controller_disable() {
+ let dir = tempfile::tempdir().unwrap();
+ let guest_memory = create_guest_memory();
+
+ let controller = SwapController::launch(guest_memory.clone(), dir.path(), &None).unwrap();
+
+ guest_memory
+ .write_all_at_addr(&[1u8; 4096], GuestAddress(0x0000000000000000))
+ .unwrap();
+ guest_memory
+ .write_all_at_addr(&[2u8; 4096], GuestAddress(0x0000000000000000 + 4096))
+ .unwrap();
+ guest_memory
+ .write_all_at_addr(&[0u8; 4096], GuestAddress(0x0000000000000000 + 2 * 4096))
+ .unwrap();
+ guest_memory
+ .write_all_at_addr(&[3u8; 4096], GuestAddress(0x0000000000000000 + 10 * 4096))
+ .unwrap();
+ guest_memory
+ .write_all_at_addr(
+ &[4u8; 3 * 1024 * 1024],
+ GuestAddress(0x0000000000000000 + 20 * 4096),
+ )
+ .unwrap();
+ guest_memory
+ .write_all_at_addr(&[5u8; 4096], GuestAddress(0x0000000100000000))
+ .unwrap();
+ guest_memory
+ .write_all_at_addr(&[6u8; 4096], GuestAddress(0x0000000100000000 + 4096))
+ .unwrap();
+ guest_memory
+ .write_all_at_addr(&[0u8; 4096], GuestAddress(0x0000000100000000 + 2 * 4096))
+ .unwrap();
+ guest_memory
+ .write_all_at_addr(&[7u8; 4096], GuestAddress(0x0000000100000000 + 10 * 4096))
+ .unwrap();
+ guest_memory
+ .write_all_at_addr(
+ &[8u8; 3 * 1024 * 1024],
+ GuestAddress(0x0000000100000000 + 20 * 4096),
+ )
+ .unwrap();
+
+ controller.enable().unwrap();
+ controller.disable(false).unwrap();
+ assert!(wait_for_state(&controller, SwapState::Ready));
+ assert_eq!(controller.status().unwrap().state_transition.pages, 1544);
+
+ let mut buf = [0u8; 4096];
+ let mut long_buf = [0u8; 3 * 1024 * 1024];
+
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000))
+ .unwrap();
+ assert_eq!(buf, [1u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000 + 4096))
+ .unwrap();
+ assert_eq!(buf, [2u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000 + 2 * 4096))
+ .unwrap();
+ assert_eq!(buf, [0u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000 + 3 * 4096))
+ .unwrap();
+ assert_eq!(buf, [0u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000 + 10 * 4096))
+ .unwrap();
+ assert_eq!(buf, [3u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut long_buf, GuestAddress(0x0000000000000000 + 20 * 4096))
+ .unwrap();
+ assert_eq!(long_buf, [4u8; 3 * 1024 * 1024]);
+
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000))
+ .unwrap();
+ assert_eq!(buf, [5u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000 + 4096))
+ .unwrap();
+ assert_eq!(buf, [6u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000 + 2 * 4096))
+ .unwrap();
+ assert_eq!(buf, [0u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000 + 3 * 4096))
+ .unwrap();
+ assert_eq!(buf, [0u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000 + 10 * 4096))
+ .unwrap();
+ assert_eq!(buf, [7u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut long_buf, GuestAddress(0x0000000100000000 + 20 * 4096))
+ .unwrap();
+ assert_eq!(long_buf, [8u8; 3 * 1024 * 1024]);
+
+ controller.enable().unwrap();
+ controller.trim().unwrap();
+ assert!(wait_for_state(&controller, SwapState::Pending));
+ controller.swap_out().unwrap();
+ assert!(wait_for_state(&controller, SwapState::Active));
+ controller.disable(false).unwrap();
+ assert!(wait_for_state(&controller, SwapState::Ready));
+ assert_eq!(controller.status().unwrap().state_transition.pages, 1542);
+
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000))
+ .unwrap();
+ assert_eq!(buf, [1u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000 + 4096))
+ .unwrap();
+ assert_eq!(buf, [2u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000 + 2 * 4096))
+ .unwrap();
+ assert_eq!(buf, [0u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000 + 3 * 4096))
+ .unwrap();
+ assert_eq!(buf, [0u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000000000000 + 10 * 4096))
+ .unwrap();
+ assert_eq!(buf, [3u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut long_buf, GuestAddress(0x0000000000000000 + 20 * 4096))
+ .unwrap();
+ assert_eq!(long_buf, [4u8; 3 * 1024 * 1024]);
+
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000))
+ .unwrap();
+ assert_eq!(buf, [5u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000 + 4096))
+ .unwrap();
+ assert_eq!(buf, [6u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000 + 2 * 4096))
+ .unwrap();
+ assert_eq!(buf, [0u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000 + 3 * 4096))
+ .unwrap();
+ assert_eq!(buf, [0u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut buf, GuestAddress(0x0000000100000000 + 10 * 4096))
+ .unwrap();
+ assert_eq!(buf, [7u8; 4096]);
+ guest_memory
+ .read_exact_at_addr(&mut long_buf, GuestAddress(0x0000000100000000 + 20 * 4096))
+ .unwrap();
+ assert_eq!(long_buf, [8u8; 3 * 1024 * 1024]);
+ }
}
fn main() {
@@ -130,6 +875,50 @@ fn main() {
Ok(())
})
.with_ignored_flag(true),
+ #[cfg(all(unix, feature = "enable"))]
+ libtest_mimic::Trial::test("controller_enable", move || {
+ base::test_utils::call_test_with_sudo("controller_enable_impl");
+ Ok(())
+ }),
+ #[cfg(all(unix, feature = "enable"))]
+ libtest_mimic::Trial::test("controller_enable_impl", move || {
+ test::controller_enable();
+ Ok(())
+ })
+ .with_ignored_flag(true),
+ #[cfg(all(unix, feature = "enable"))]
+ libtest_mimic::Trial::test("controller_swap_out", move || {
+ base::test_utils::call_test_with_sudo("controller_swap_out_impl");
+ Ok(())
+ }),
+ #[cfg(all(unix, feature = "enable"))]
+ libtest_mimic::Trial::test("controller_swap_out_impl", move || {
+ test::controller_swap_out();
+ Ok(())
+ })
+ .with_ignored_flag(true),
+ #[cfg(all(unix, feature = "enable"))]
+ libtest_mimic::Trial::test("controller_trim", move || {
+ base::test_utils::call_test_with_sudo("controller_trim_impl");
+ Ok(())
+ }),
+ #[cfg(all(unix, feature = "enable"))]
+ libtest_mimic::Trial::test("controller_trim_impl", move || {
+ test::controller_trim();
+ Ok(())
+ })
+ .with_ignored_flag(true),
+ #[cfg(all(unix, feature = "enable"))]
+ libtest_mimic::Trial::test("controller_disable", move || {
+ base::test_utils::call_test_with_sudo("controller_disable_impl");
+ Ok(())
+ }),
+ #[cfg(all(unix, feature = "enable"))]
+ libtest_mimic::Trial::test("controller_disable_impl", move || {
+ test::controller_disable();
+ Ok(())
+ })
+ .with_ignored_flag(true),
];
libtest_mimic::run(&args, tests).exit();
}
diff --git a/third_party/minigbm b/third_party/minigbm
deleted file mode 120000
index 81f957dc0..000000000
--- a/third_party/minigbm
+++ /dev/null
@@ -1 +0,0 @@
-../../minigbm/ \ No newline at end of file
diff --git a/third_party/vmm_vhost/src/lib.rs b/third_party/vmm_vhost/src/lib.rs
index 64f88ff15..6db01f677 100644
--- a/third_party/vmm_vhost/src/lib.rs
+++ b/third_party/vmm_vhost/src/lib.rs
@@ -155,6 +155,9 @@ pub enum Error {
/// Error from invalid vring index.
#[error("Vring index not found: {0}")]
VringIndexNotFound(usize),
+ /// Failure to run device specific wake.
+ #[error("Failed to run device specific wake: {0}")]
+ WakeError(anyhow::Error),
}
impl From<base::TubeError> for Error {
diff --git a/third_party/vmm_vhost/src/master.rs b/third_party/vmm_vhost/src/master.rs
index d78eb13f2..9fc2eb771 100644
--- a/third_party/vmm_vhost/src/master.rs
+++ b/third_party/vmm_vhost/src/master.rs
@@ -251,13 +251,27 @@ impl Master {
/// Put the device to sleep.
pub fn sleep(&self) -> Result<()> {
let hdr = self.send_request_header(MasterReq::SLEEP, None)?;
- self.wait_for_ack(&hdr)
+ let reply = self.recv_reply::<VhostUserSuccess>(&hdr)?;
+ if !reply.success() {
+ Err(VhostUserError::SleepError(anyhow!(
+ "Device process responded with a failure on SLEEP."
+ )))
+ } else {
+ Ok(())
+ }
}
/// Wake the device up.
pub fn wake(&self) -> Result<()> {
let hdr = self.send_request_header(MasterReq::WAKE, None)?;
- self.wait_for_ack(&hdr)
+ let reply = self.recv_reply::<VhostUserSuccess>(&hdr)?;
+ if !reply.success() {
+ Err(VhostUserError::WakeError(anyhow!(
+ "Device process responded with a failure on WAKE."
+ )))
+ } else {
+ Ok(())
+ }
}
/// Snapshot the device and receive serialized state of the device.
diff --git a/third_party/vmm_vhost/src/master_req_handler.rs b/third_party/vmm_vhost/src/master_req_handler.rs
index a93ef6867..9e722e0d7 100644
--- a/third_party/vmm_vhost/src/master_req_handler.rs
+++ b/third_party/vmm_vhost/src/master_req_handler.rs
@@ -95,6 +95,11 @@ pub trait VhostUserMasterReqHandler {
) -> HandlerResult<u64> {
Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
}
+
+ /// Handle external memory region mapping requests.
+ fn external_map(&mut self, _req: &VhostUserExternalMapMsg) -> HandlerResult<u64> {
+ Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
+ }
}
/// The [MasterReqHandler] acts as a server on the master side, to handle service requests from
@@ -240,6 +245,12 @@ impl<S: VhostUserMasterReqHandler> MasterReqHandler<S> {
.gpu_map(&msg, &files[0])
.map_err(Error::ReqHandlerError)
}
+ Ok(SlaveReq::EXTERNAL_MAP) => {
+ let msg = self.extract_msg_body::<VhostUserExternalMapMsg>(&hdr, size, &buf)?;
+ self.backend
+ .external_map(&msg)
+ .map_err(Error::ReqHandlerError)
+ }
_ => Err(Error::InvalidMessage),
};
@@ -313,6 +324,7 @@ impl<S: VhostUserMasterReqHandler> MasterReqHandler<S> {
if code == SlaveReq::SHMEM_MAP
|| code == SlaveReq::SHMEM_UNMAP
|| code == SlaveReq::GPU_MAP
+ || code == SlaveReq::EXTERNAL_MAP
|| (self.reply_ack_negotiated && req.is_need_reply())
{
let hdr = self.new_reply_header::<VhostUserU64>(req)?;
diff --git a/third_party/vmm_vhost/src/message.rs b/third_party/vmm_vhost/src/message.rs
index 8a8c6e533..62737090a 100644
--- a/third_party/vmm_vhost/src/message.rs
+++ b/third_party/vmm_vhost/src/message.rs
@@ -196,6 +196,8 @@ pub enum SlaveReq {
FS_IO = 11,
/// Indicates a request to map GPU memory into a shared memory region.
GPU_MAP = 12,
+ /// Indicates a request to map external memory into a shared memory region.
+ EXTERNAL_MAP = 13,
}
impl From<SlaveReq> for u32 {
@@ -1036,6 +1038,40 @@ impl VhostUserGpuMapMsg {
}
}
+/// Slave request message to map external memory into a shared memory region.
+#[repr(C, packed)]
+#[derive(Default, Copy, Clone, AsBytes, FromZeroes, FromBytes)]
+pub struct VhostUserExternalMapMsg {
+ /// Shared memory region id.
+ pub shmid: u8,
+ padding: [u8; 7],
+ /// Offset into the shared memory region.
+ pub shm_offset: u64,
+ /// Size of region to map.
+ pub len: u64,
+ /// Pointer to the memory.
+ pub ptr: u64,
+}
+
+impl VhostUserMsgValidator for VhostUserExternalMapMsg {
+ fn is_valid(&self) -> bool {
+ self.len > 0
+ }
+}
+
+impl VhostUserExternalMapMsg {
+ /// New instance of VhostUserExternalMapMsg struct
+ pub fn new(shmid: u8, shm_offset: u64, len: u64, ptr: u64) -> Self {
+ Self {
+ shmid,
+ padding: [0; 7],
+ shm_offset,
+ len,
+ ptr,
+ }
+ }
+}
+
/// Slave request message to unmap part of a shared memory region.
#[repr(C, packed)]
#[derive(Default, Copy, Clone, FromZeroes, FromBytes, AsBytes)]
@@ -1238,7 +1274,7 @@ mod tests {
#[test]
fn check_slave_request_code() {
SlaveReq::try_from(0).expect_err("invalid value");
- SlaveReq::try_from(13).expect_err("invalid value");
+ SlaveReq::try_from(14).expect_err("invalid value");
SlaveReq::try_from(10000).expect_err("invalid value");
let code = SlaveReq::try_from(SlaveReq::CONFIG_CHANGE_MSG as u32).unwrap();
diff --git a/third_party/vmm_vhost/src/slave_proxy.rs b/third_party/vmm_vhost/src/slave_proxy.rs
index 8aed1eaa8..6ae2933be 100644
--- a/third_party/vmm_vhost/src/slave_proxy.rs
+++ b/third_party/vmm_vhost/src/slave_proxy.rs
@@ -77,6 +77,7 @@ impl Slave {
if code != SlaveReq::SHMEM_MAP
&& code != SlaveReq::SHMEM_UNMAP
&& code != SlaveReq::GPU_MAP
+ && code != SlaveReq::EXTERNAL_MAP
&& !self.reply_ack_negotiated
{
return Ok(0);
@@ -154,6 +155,11 @@ impl VhostUserMasterReqHandler for Slave {
Some(&[descriptor.as_raw_descriptor()]),
)
}
+
+ /// Handle external memory region mapping requests.
+ fn external_map(&mut self, req: &VhostUserExternalMapMsg) -> HandlerResult<u64> {
+ self.send_message(SlaveReq::EXTERNAL_MAP, req, None)
+ }
}
#[cfg(test)]
diff --git a/third_party/vmm_vhost/src/slave_req_handler.rs b/third_party/vmm_vhost/src/slave_req_handler.rs
index a76279cb5..706b415af 100644
--- a/third_party/vmm_vhost/src/slave_req_handler.rs
+++ b/third_party/vmm_vhost/src/slave_req_handler.rs
@@ -722,11 +722,13 @@ impl<S: VhostUserSlaveReqHandler> SlaveReqHandler<S> {
}
Ok(MasterReq::SLEEP) => {
let res = self.backend.sleep();
- self.slave_req_helper.send_ack_message(&hdr, res.is_ok())?;
+ let msg = VhostUserSuccess::new(res.is_ok());
+ self.slave_req_helper.send_reply_message(&hdr, &msg)?;
}
Ok(MasterReq::WAKE) => {
let res = self.backend.wake();
- self.slave_req_helper.send_ack_message(&hdr, res.is_ok())?;
+ let msg = VhostUserSuccess::new(res.is_ok());
+ self.slave_req_helper.send_reply_message(&hdr, &msg)?;
}
Ok(MasterReq::SNAPSHOT) => {
let (success_msg, payload) = match self.backend.snapshot() {
diff --git a/tools/contrib/vcpu_blocker_analyzer/Cargo.lock b/tools/contrib/vcpu_blocker_analyzer/Cargo.lock
new file mode 100644
index 000000000..5fb553f45
--- /dev/null
+++ b/tools/contrib/vcpu_blocker_analyzer/Cargo.lock
@@ -0,0 +1,556 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
+
+[[package]]
+name = "argh"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7af5ba06967ff7214ce4c7419c7d185be7ecd6cc4965a8f6e1d8ce0398aad219"
+dependencies = [
+ "argh_derive",
+ "argh_shared",
+]
+
+[[package]]
+name = "argh_derive"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56df0aeedf6b7a2fc67d06db35b09684c3e8da0c95f8f27685cb17e08413d87a"
+dependencies = [
+ "argh_shared",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "argh_shared"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5693f39141bda5760ecc4111ab08da40565d1771038c4a0250f03457ec707531"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "bitflags"
+version = "2.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "env_logger"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580"
+dependencies = [
+ "humantime",
+ "is-terminal",
+ "log",
+ "regex",
+ "termcolor",
+]
+
+[[package]]
+name = "errno"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
+dependencies = [
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "futures"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
+
+[[package]]
+name = "futures-task"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
+
+[[package]]
+name = "futures-timer"
+version = "3.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c"
+
+[[package]]
+name = "futures-util"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "glob"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f"
+
+[[package]]
+name = "humantime"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
+
+[[package]]
+name = "is-terminal"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455"
+dependencies = [
+ "hermit-abi",
+ "rustix",
+ "windows-sys",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
+
+[[package]]
+name = "libc"
+version = "0.2.152"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
+
+[[package]]
+name = "log"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+
+[[package]]
+name = "memchr"
+version = "2.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "regex"
+version = "1.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b7fa1134405e2ec9353fd416b17f8dacd46c473d7d3fd1cf202706a14eb792a"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
+
+[[package]]
+name = "relative-path"
+version = "1.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e898588f33fdd5b9420719948f9f2a32c922a246964576f71ba7f24f80610fbc"
+
+[[package]]
+name = "rstest"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97eeab2f3c0a199bc4be135c36c924b6590b88c377d416494288c14f2db30199"
+dependencies = [
+ "futures",
+ "futures-timer",
+ "rstest_macros",
+ "rustc_version",
+]
+
+[[package]]
+name = "rstest_macros"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d428f8247852f894ee1be110b375111b586d4fa431f6c46e64ba5a0dcccbe605"
+dependencies = [
+ "cfg-if",
+ "glob",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "relative-path",
+ "rustc_version",
+ "syn",
+ "unicode-ident",
+]
+
+[[package]]
+name = "rustc_version"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustix"
+version = "0.38.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
+
+[[package]]
+name = "semver"
+version = "1.0.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0"
+
+[[package]]
+name = "serde"
+version = "1.0.195"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.195"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.111"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.48"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "vcpu_blocker_analyzer_rust"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "argh",
+ "env_logger",
+ "log",
+ "once_cell",
+ "regex",
+ "rstest",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
diff --git a/tools/contrib/vcpu_blocker_analyzer/Cargo.toml b/tools/contrib/vcpu_blocker_analyzer/Cargo.toml
new file mode 100644
index 000000000..8744ce9bd
--- /dev/null
+++ b/tools/contrib/vcpu_blocker_analyzer/Cargo.toml
@@ -0,0 +1,22 @@
+[package]
+name = "vcpu_blocker_analyzer_rust"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+anyhow = "1.0"
+argh = "0.1"
+env_logger = "0.10"
+log = "0.4"
+regex = "1.3"
+serde = { version = "*", features = ["derive"] }
+serde_json = "1"
+once_cell = "1"
+
+[dev-dependencies]
+rstest = "*"
+
+
+[workspace]
diff --git a/tools/contrib/vcpu_blocker_analyzer/README.md b/tools/contrib/vcpu_blocker_analyzer/README.md
new file mode 100644
index 000000000..9f31aea79
--- /dev/null
+++ b/tools/contrib/vcpu_blocker_analyzer/README.md
@@ -0,0 +1,119 @@
+# VCPU process state time analysis tool
+
+This tool helps you analyze what virtio device is likely to be the bottleneck of the given workload
+from an output of `trace-cmd report`. This tool analyzes the running time, waiting time, and
+preemption time of crosvm's vcpu processes. The running time is the total time crosvm's vcpu
+processes run on the host processors, the waiting time is the total time crosvm's vcpu processes
+wait for other processes, and the preemption time is the total time crosvm's vcpu processes were
+preempted by other processes.
+
+In practice, it means... if the vcpu processes are mostly waiting for the vitio-blk process, the
+bottleneck is likely to be the block device. If the vcpu processes are frequently preempted by other
+processes, the bottleneck is likely to be the processor performance.
+
+![What it measures](./images/mechanism.png)
+
+This mechanism is far from perfect due to the defects outlined in the next section. However, it
+gives you a gut feeling of the bottleneck virtio-device. It also estimates the time savings of your
+workload you can achieve by improving the performance of a virtio device.
+
+## Defect
+
+This tool underestimates the time vcpu processes are waiting for a virtio device. This is due to two
+major reasons; timer/periodic interrupts and soft-irq.
+
+This tool calculates the time a virtio device process blocks vcpu processes by accumulating how long
+a vcpu process was sleeping when a virtio device wakes up the vcpu process. Thus, if a periodic
+interrupt waked up a vcpu process in the middle of the VCPU's sleep, the block time is
+underestimated. Similarly, if a device interrupts is handled by some running vcpu and then delivered
+to other sleeping vcpu via soft-irq inside the guest, the block time is also underestimated.
+
+On the other hand, this tool can estimate the time savings of your workload by improving the
+performance of a virtio device. If we reduce block / preempt time of some virtio-process, that will
+always reduce the total time.
+
+![Underestimation](./images/timer_defect.png)
+
+## How to run
+
+### 1. Make a `trace.dat` file by `trace-cmd record` for your workload.
+
+This tool requires `sched:*` and `task:task_rename` events from `trace-cmd`.
+
+#### When you run crosvm manually
+
+To make a `trace.dat` file, you can use `trace-cmd record`.
+
+```console
+$ trace-cmd record -e sched:* -e task:task_rename -o trace.dat crosvm run ...
+```
+
+This will generate `trace.dat` in the current directory.
+
+#### When you run ChromeOS Tast tests
+
+Some ChromeOS Tast tests such as `arc.RegularBoot.boot` have built-in `trace-cmd record` feature.
+
+To get a trace file from `arc.RegularBoot.vm`, you can use `tast run` with
+`arc.RegularBoot.traceCmdEvents`. Then you can get `0-trace.dat` in your result directory.
+
+```console
+$ tast run -var arc.RegularBoot.traceCmdEvents="task:task_rename,sched:*" $DUT arc.RegularBoot.vm
+```
+
+Refer to the source code of your Tast test to see if it supports `traceCmdEvents`.
+
+### 2. Generate the text data via `trace-cmd report`
+
+This tool's input is currently the text data generated by `trace-cmd report`, not `trace.dat`
+itself.
+
+```console
+$ trace-cmd report trace.dat > trace.txt
+```
+
+### 3. Run this tool.
+
+```console
+$ cargo run --release -- -i trace.txt
+```
+
+## How to read the result
+
+The output of this tool looks like this. This is taken from a trace of `RegularBoot.vm` on a Brya
+with some modification. The description of each metric is added in the comments inside parentheses.
+
+From the below, you can read that the majority of VCPU states are sleep, so the workload is not so
+processor-heavy on this device, and virtio-blk is the major waker of the VCPU, thus it has an
+opportunity to be improved to make the workload faster.
+
+```
+(time in seconds by VCPU process state for each VCPU)
+vcpu0_running 3.2823 sec (ratio: 27.913% of vcpu0_time)
+vcpu0_sleep 8.1406 sec (ratio: 69.229% of vcpu0_time)
+vcpu0_runnable 0.2446 sec (ratio: 2.080% of vcpu0_time)
+vcpu0_preempted 0.0914 sec (ratio: 0.778% of vcpu0_time)
+---snip---
+(That of the total VCPUs instead of each VCPU)
+total_vcpu_running 23.3277 sec (ratio: 25.081% of total_vcpu_time)
+total_vcpu_sleep 67.7202 sec (ratio: 72.810% of total_vcpu_time)
+total_vcpu_runnable 1.3776 sec (ratio: 1.481% of total_vcpu_time)
+total_vcpu_preempted 0.5844 sec (ratio: 0.628% of total_vcpu_time)
+total_vcpu_time 93.0099 sec
+---snip---
+(Total time VCPU processes were waiting for other processes)
+blocked_virtio_blk 6.2771 sec (ratio: 6.749% of total_vcpu_time)
+---snip---
+blocked_v_fs:_data:1 1.7611 sec (ratio: 1.893% of total_vcpu_time)
+blocked_v_balloon 1.3707 sec (ratio: 1.474% of total_vcpu_time)
+blocked_v_console 0.6851 sec (ratio: 0.737% of total_vcpu_time)
+---snip---
+(Total time VCPU processes were preempted by other processes)
+(*This is taken from another Octopus data since Brya had little preemption)
+preempted_v_fs:_data:1 2.2760 sec (ratio: 3.179% of total_vcpu_time)
+preempted_chrome 1.1027 sec (ratio: 1.538% of total_vcpu_time)
+---snip---
+```
+
+From the result above, you can see that virtio-blk is one of the bottleneck of this workload; it
+blocks vcpu processes for around 6.3 seconds and preempts 1.6 second.
diff --git a/tools/contrib/vcpu_blocker_analyzer/images/mechanism.png b/tools/contrib/vcpu_blocker_analyzer/images/mechanism.png
new file mode 100644
index 000000000..cec65ccd2
--- /dev/null
+++ b/tools/contrib/vcpu_blocker_analyzer/images/mechanism.png
Binary files differ
diff --git a/tools/contrib/vcpu_blocker_analyzer/images/timer_defect.png b/tools/contrib/vcpu_blocker_analyzer/images/timer_defect.png
new file mode 100644
index 000000000..664e09b6b
--- /dev/null
+++ b/tools/contrib/vcpu_blocker_analyzer/images/timer_defect.png
Binary files differ
diff --git a/tools/contrib/vcpu_blocker_analyzer/src/main.rs b/tools/contrib/vcpu_blocker_analyzer/src/main.rs
new file mode 100644
index 000000000..fbe903345
--- /dev/null
+++ b/tools/contrib/vcpu_blocker_analyzer/src/main.rs
@@ -0,0 +1,542 @@
+// Copyright 2024 The ChromiumOS Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::collections::HashMap;
+use std::fs::File;
+use std::io;
+use std::io::stdout;
+use std::io::BufRead;
+use std::io::BufReader;
+use std::io::Write;
+
+use anyhow::Context;
+use anyhow::Result;
+use argh::FromArgs;
+use env_logger::Env;
+use parse::parse_event;
+use parse::parse_sched_switch;
+use parse::parse_sched_waking;
+use parse::parse_task_rename;
+use parse::parse_vcpu_id;
+use parse::Event;
+
+mod parse;
+
+const VCPU_PROC_PREFIX: &str = "crosvm_vcpu";
+
+#[derive(Debug, FromArgs)]
+/// Bottleneck analysis of virtio device processes.
+struct Args {
+ /// path to the input trace-cmd report output
+ #[argh(option, short = 'i')]
+ input: String,
+
+ /// log level (default: INFO)
+ #[argh(option, short = 'l', default = "String::from(\"INFO\")")]
+ log_level: String,
+
+ /// show the result in the tast JSON format
+ #[argh(switch, short = 't')]
+ tast_json: bool,
+
+ /// minimum duration to show a process (default: 0.2s)
+ #[argh(option, short = 'm', default = "0.2")]
+ minimum_duration: f64,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum ProcState {
+ Unknown,
+ Running,
+ Sleep,
+ Runnable,
+ Preempted,
+ Dead,
+ Other,
+}
+
+impl std::fmt::Display for ProcState {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ ProcState::Unknown => write!(f, "unknown"),
+ ProcState::Running => write!(f, "running"),
+ ProcState::Sleep => write!(f, "sleep"),
+ ProcState::Runnable => write!(f, "runnable"),
+ ProcState::Preempted => write!(f, "preempted"),
+ ProcState::Dead => write!(f, "dead"),
+ ProcState::Other => write!(f, "other"),
+ }
+ }
+}
+
+struct VCPUState {
+ state: ProcState,
+ timestamp: f64,
+ last_preemptor: i32,
+}
+
+impl VCPUState {
+ fn new() -> Self {
+ VCPUState {
+ state: ProcState::Unknown,
+ timestamp: 0.0,
+ last_preemptor: -1,
+ }
+ }
+
+ fn set_state(&mut self, new_state: ProcState, time_stamp: f64) -> (ProcState, f64) {
+ let duration = if self.timestamp != 0.0 {
+ time_stamp - self.timestamp
+ } else {
+ 0.0
+ };
+ let prev_state = self.state;
+ self.state = new_state;
+ self.timestamp = time_stamp;
+ (prev_state, duration)
+ }
+}
+
+fn main() -> Result<()> {
+ let args: Args = argh::from_env();
+
+ env_logger::Builder::from_env(Env::default().default_filter_or(&args.log_level)).init();
+
+ let file = File::open(args.input)?;
+ let reader = BufReader::new(file);
+
+ let (vcpu_state_durations, block_duration, preempted_duration, proc_names) =
+ calculate_durations(reader)?;
+
+ let metrics = make_metrics(
+ &vcpu_state_durations,
+ &block_duration,
+ &preempted_duration,
+ &proc_names,
+ args.minimum_duration,
+ );
+
+ if args.tast_json {
+ print_tast_json(&metrics)?;
+ } else {
+ print_text(&metrics);
+ }
+
+ Ok(())
+}
+
+fn calculate_durations<T: io::Read>(
+ mut reader: BufReader<T>,
+) -> Result<(
+ Vec<HashMap<ProcState, f64>>,
+ HashMap<i32, f64>,
+ HashMap<i32, f64>,
+ HashMap<i32, String>,
+)> {
+ // Initialization
+ let mut vcpu_state_durations = Vec::<HashMap<ProcState, f64>>::new();
+ let mut block_duration = HashMap::<i32, f64>::new();
+ let mut preempted_duration = HashMap::<i32, f64>::new();
+ let mut proc_names = HashMap::<i32, String>::new();
+ let mut vcpu_states = Vec::<VCPUState>::new();
+
+ // Read the first line to get the number of CPUs
+ let mut line = String::new();
+ reader.read_line(&mut line)?;
+ let num_cpus = line
+ .trim()
+ .strip_prefix("cpus=")
+ .and_then(|n| n.parse::<i32>().ok())
+ .context("Failed to parse number of CPUs")?;
+
+ // Initialize data structures for VCPUs
+ for _ in 0..num_cpus {
+ vcpu_state_durations.push(HashMap::new());
+ vcpu_states.push(VCPUState::new());
+ }
+
+ log::info!("Start processing.");
+
+ // Process lines from the trace-cmd file
+ let mut line_number = 0;
+ for line_result in reader.lines() {
+ let line = line_result?;
+
+ if !line.contains(VCPU_PROC_PREFIX) && !line.contains("task_rename") {
+ continue; // Skip irrelevant lines
+ }
+
+ let event = parse_event(&line).with_context(|| {
+ format!("Failed to parse event (line {}): {}", line_number + 1, line)
+ })?;
+ update_durations(
+ &event,
+ &mut vcpu_states,
+ &mut vcpu_state_durations,
+ &mut block_duration,
+ &mut preempted_duration,
+ &mut proc_names,
+ )
+ .with_context(|| {
+ format!(
+ "Failed to process event (line {}): {}",
+ line_number + 1,
+ line
+ )
+ })?;
+
+ line_number += 1;
+ }
+ log::info!("Read {line_number} lines.");
+
+ Ok((
+ vcpu_state_durations,
+ block_duration,
+ preempted_duration,
+ proc_names,
+ ))
+}
+
+fn update_durations(
+ event: &Event,
+ vcpu_states: &mut [VCPUState],
+ vcpu_state_durations: &mut [HashMap<ProcState, f64>],
+ block_duration: &mut HashMap<i32, f64>,
+ preempted_duration: &mut HashMap<i32, f64>,
+ proc_names: &mut HashMap<i32, String>,
+) -> Result<()> {
+ match event.name.as_str() {
+ // Update the VCPU process state duration and the VCPU-blocking time of a process which waked up a VCPU process.
+ "sched_waking" => {
+ let sched_waking = parse_sched_waking(&event.details)?;
+ if !sched_waking.waked_proc_name.starts_with(VCPU_PROC_PREFIX) {
+ // skip non-VCPU processes
+ return Ok(());
+ }
+
+ // Ensure a valid VCPU ID
+ let vcpu_id = parse_vcpu_id(&sched_waking.waked_proc_name)?;
+
+ if vcpu_states[vcpu_id].state != ProcState::Unknown {
+ *block_duration.entry(event.pid).or_default() +=
+ event.time - vcpu_states[vcpu_id].timestamp;
+ }
+
+ let (prev_state, dur) = vcpu_states[vcpu_id].set_state(ProcState::Runnable, event.time);
+ *vcpu_state_durations[vcpu_id].entry(prev_state).or_default() += dur;
+
+ update_proc_name_if_missing(
+ &sched_waking.waked_proc_name,
+ sched_waking.waked_pid,
+ proc_names,
+ );
+ }
+ // Update the VCPU process state duration and the VCPU-preemption time of a process if it preempted a VCPU process.
+ "sched_switch" => {
+ let sched_switch = parse_sched_switch(&event.details)?;
+ if sched_switch.prev_proc_name.starts_with(VCPU_PROC_PREFIX) {
+ let vcpu_id = parse_vcpu_id(&sched_switch.prev_proc_name)?;
+ if sched_switch.prev_proc_state == ProcState::Preempted {
+ vcpu_states[vcpu_id].last_preemptor = sched_switch.new_pid;
+ }
+ let (prev_state, dur) =
+ vcpu_states[vcpu_id].set_state(sched_switch.prev_proc_state, event.time);
+ *vcpu_state_durations[vcpu_id].entry(prev_state).or_default() += dur;
+
+ update_proc_name_if_missing(
+ &sched_switch.new_proc_name,
+ sched_switch.new_pid,
+ proc_names,
+ );
+ }
+ if sched_switch.new_proc_name.starts_with(VCPU_PROC_PREFIX) {
+ let vcpu_id = parse_vcpu_id(&sched_switch.new_proc_name)?;
+ let (prev_state, dur) =
+ vcpu_states[vcpu_id].set_state(ProcState::Running, event.time);
+ *vcpu_state_durations[vcpu_id].entry(prev_state).or_default() += dur;
+
+ if prev_state == ProcState::Preempted {
+ *preempted_duration
+ .entry(vcpu_states[vcpu_id].last_preemptor)
+ .or_default() += dur;
+ vcpu_states[vcpu_id].last_preemptor = -1;
+ }
+
+ update_proc_name_if_missing(
+ &sched_switch.prev_proc_name,
+ sched_switch.prev_pid,
+ proc_names,
+ );
+ }
+ }
+ "task_rename" => {
+ let comm = parse_task_rename(&event.details)?;
+ proc_names.insert(event.pid, comm);
+ }
+ _ => {}
+ }
+
+ Ok(())
+}
+
+// Update the process name only when it is missing. Callers which should not
+// update the process name if we already know the name of pid calls this
+// function. For example, process names which appear in events will not reflect
+// task rename and might keep old names.
+fn update_proc_name_if_missing(proc_name: &str, pid: i32, proc_names: &mut HashMap<i32, String>) {
+ if pid == 0 {
+ // Special handling for "<idle>"
+ proc_names.insert(pid, "<idle>".to_string());
+ } else {
+ proc_names
+ .entry(pid)
+ .or_insert_with(|| proc_name.to_string());
+ }
+}
+
+#[derive(Debug)]
+struct Metric {
+ name: String,
+ /// The value of the metric. Currently the unit is always seconds.
+ value: f64,
+ /// The ratio of the value if it has any total value.
+ ratio: Option<Ratio>,
+}
+
+#[derive(Debug)]
+struct Ratio {
+ /// Unit: percent
+ value: f64,
+ /// Description of the total value of the ratio.
+ total_value_text: String,
+}
+
+fn make_metrics(
+ vcpu_state_durations: &[HashMap<ProcState, f64>],
+ block_duration: &HashMap<i32, f64>,
+ preempted_duration: &HashMap<i32, f64>,
+ proc_names: &HashMap<i32, String>,
+ minimum_duration: f64,
+) -> Vec<Metric> {
+ let mut metrics = Vec::new();
+
+ // VCPU state metrics
+ let total_vcpu_proc_duration: HashMap<ProcState, f64> =
+ vcpu_state_durations
+ .iter()
+ .fold(HashMap::new(), |mut acc, durations| {
+ for (state, dur) in durations.iter() {
+ *acc.entry(*state).or_default() += dur;
+ }
+ acc
+ });
+
+ let vcpu_times: Vec<f64> = vcpu_state_durations
+ .iter()
+ .map(|durations| durations.values().sum())
+ .collect();
+
+ let total_vcpu_time: f64 = vcpu_times.iter().sum(); // Sum of durations across all vCPUs
+
+ let proc_states_to_report: &[ProcState] = &[
+ ProcState::Running,
+ ProcState::Sleep,
+ ProcState::Runnable,
+ ProcState::Preempted,
+ ];
+ let proc_states_to_ignore: &[ProcState] =
+ &[ProcState::Dead, ProcState::Other, ProcState::Unknown];
+
+ for (cpu, durations) in vcpu_state_durations.iter().enumerate() {
+ for state in proc_states_to_report {
+ metrics.push(Metric {
+ name: format!("vcpu{}_{}", cpu, state),
+ value: durations.get(state).copied().unwrap_or(0.0),
+ ratio: Some(Ratio {
+ value: durations.get(state).copied().unwrap_or(0.0) / vcpu_times[cpu] * 100.0,
+ total_value_text: format!("vcpu{cpu}_time"),
+ }),
+ });
+ }
+ for state in proc_states_to_ignore {
+ if *durations.get(state).unwrap_or(&minimum_duration) > minimum_duration {
+ log::warn!(
+ "{:?} duration {} > {}",
+ state,
+ durations.get(state).unwrap(),
+ minimum_duration
+ );
+ }
+ }
+ }
+
+ // Total VCPU metrics
+ for state in proc_states_to_report {
+ // Safety: TODO
+ metrics.push(Metric {
+ name: format!("total_vcpu_{}", state),
+ value: total_vcpu_proc_duration.get(state).copied().unwrap_or(0.0),
+ ratio: Some(Ratio {
+ value: total_vcpu_proc_duration.get(state).copied().unwrap_or(0.0)
+ / total_vcpu_time
+ * 100.0,
+ total_value_text: "total_vcpu_time".to_string(),
+ }),
+ });
+ }
+ metrics.push(Metric {
+ name: "total_vcpu_time".to_string(),
+ value: total_vcpu_time,
+ ratio: None,
+ });
+
+ // Preempted and Blocked metrics
+ metrics.extend(make_sorted_duration_metrics(
+ preempted_duration,
+ proc_names,
+ total_vcpu_time,
+ minimum_duration,
+ "preempted",
+ ));
+ metrics.extend(make_sorted_duration_metrics(
+ block_duration,
+ proc_names,
+ total_vcpu_time,
+ minimum_duration,
+ "blocked",
+ ));
+
+ metrics
+}
+
+fn make_sorted_duration_metrics(
+ durations_by_pid: &HashMap<i32, f64>,
+ names: &HashMap<i32, String>,
+ total_time: f64,
+ filter_minimum: f64,
+ metric_prefix: &str,
+) -> Vec<Metric> {
+ let mut durations_by_name: HashMap<String, f64> = HashMap::new();
+ for (pid, dur) in durations_by_pid.iter() {
+ let proc_name = names
+ .get(pid)
+ .unwrap_or(&format!("NoProcName({})", pid))
+ .clone();
+ *durations_by_name.entry(proc_name).or_default() += dur;
+ }
+
+ let mut names_sorted: Vec<&String> = durations_by_name.keys().collect();
+ names_sorted.sort_by(|a, b| {
+ durations_by_name[b.as_str()]
+ .partial_cmp(&durations_by_name[a.as_str()])
+ .unwrap()
+ });
+
+ let mut metrics = Vec::new();
+ for n in names_sorted {
+ if durations_by_name[n] < filter_minimum {
+ break; // Stop if we reach durations below the threshold
+ }
+ metrics.push(Metric {
+ name: format!("{}_{}", metric_prefix, n),
+ value: durations_by_name[n],
+ ratio: Some(Ratio {
+ value: durations_by_name[n] / total_time * 100.0,
+ total_value_text: "total_vcpu_time".to_string(),
+ }),
+ });
+ }
+
+ metrics
+}
+
+fn print_tast_json(metrics: &[Metric]) -> Result<()> {
+ println!("{{");
+
+ stdout()
+ .write_all(
+ metrics
+ .iter()
+ .map(build_tast_metric)
+ .collect::<Vec<_>>()
+ .join(",")
+ .as_bytes(),
+ )
+ .with_context(|| "Failed to write to stdout")?;
+
+ println!("\n}}");
+ Ok(())
+}
+
+fn build_tast_metric(metric: &Metric) -> String {
+ // Convert a Metric to a TAST metric json string
+ let name = &metric.name;
+ let value = metric.value;
+ let mut json = format!(
+ r#"
+ "{name}": {{
+ "summary": {{
+ "units": "sec",
+ "improvement_direction": "down",
+ "type": "scalar",
+ "value": {value}
+ }}
+ }}"#
+ );
+ // Append ratio metric if present
+ if let Some(ratio) = &metric.ratio {
+ let name = format!("{}_ratio", metric.name);
+ let value = ratio.value;
+ json.push_str(&format!(
+ r#",
+ "{name}": {{
+ "summary": {{
+ "units": "percent",
+ "improvement_direction": "down",
+ "type": "scalar",
+ "value": {value}
+ }}
+ }}"#
+ ));
+ }
+ json
+}
+
+fn print_text(metrics: &[Metric]) {
+ for metric in metrics {
+ print!("{}\t{:.4} sec", metric.name, metric.value);
+ if let Some(ratio) = &metric.ratio {
+ print!(
+ "\t(ratio: {:.3}% of {})",
+ ratio.value, ratio.total_value_text
+ );
+ }
+ println!();
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use rstest::*;
+
+ use super::*;
+
+ #[rstest]
+ #[case(
+ r#"cpus=1
+ <idle>-0 [000] 10.00: sched_waking: comm=crosvm_vcpu0 pid=2 prio=120 target_cpu=000
+ other-17 [000] 20.00: sched_stat_runtime: comm=other pid=17 runtime=1 [ns] vruntime=1 [ns]
+ <idle>-0 [000] 30.00: sched_switch: swapper/0:0 [120] R ==> crosvm_vcpu0:2 [120]
+ "#,
+ vec![(ProcState::Unknown, 0.0), (ProcState::Runnable, 20.0)].into_iter().collect()
+ )]
+ fn test_calculate_stats(
+ #[case] test_data: &str,
+ #[case] expected_vcpu_dur: HashMap<ProcState, f64>,
+ ) {
+ let reader = BufReader::new(test_data.as_bytes());
+ let (v_cpudur, _, _, _) = calculate_durations(reader).unwrap();
+ assert_eq!(v_cpudur[0], expected_vcpu_dur);
+ }
+}
diff --git a/tools/contrib/vcpu_blocker_analyzer/src/parse.rs b/tools/contrib/vcpu_blocker_analyzer/src/parse.rs
new file mode 100644
index 000000000..65826dc0b
--- /dev/null
+++ b/tools/contrib/vcpu_blocker_analyzer/src/parse.rs
@@ -0,0 +1,261 @@
+// Copyright 2024 The ChromiumOS Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use anyhow::anyhow;
+use anyhow::Context;
+use anyhow::Result;
+use once_cell::sync::OnceCell;
+use regex::Regex;
+
+use crate::ProcState;
+
+#[derive(Debug)]
+pub struct Event {
+ pub pid: i32,
+ pub proc_name: String,
+ pub name: String,
+ pub details: String,
+ pub time: f64,
+}
+
+impl PartialEq for Event {
+ fn eq(&self, other: &Self) -> bool {
+ self.pid == other.pid
+ && self.proc_name == other.proc_name
+ && self.name == other.name
+ && self.details == other.details
+ && self.time.to_bits() == other.time.to_bits()
+ }
+}
+
+static EVENT_PATTERN: OnceCell<Regex> = OnceCell::new();
+
+pub fn parse_event(line: &str) -> Option<Event> {
+ let event_pattern = EVENT_PATTERN.get_or_init(|| {
+ Regex::new(
+ r" +(?P<proc>.*)-(?P<pid>\d+) +\[(?P<cpu>\d+)\] +(?P<ts>\d+\.\d+): +(?P<event>\S*): +",
+ )
+ .expect("Failed to compile event pattern")
+ });
+
+ let event_captures = event_pattern.captures(line)?;
+
+ Some(Event {
+ pid: event_captures["pid"].parse::<i32>().ok()?,
+ proc_name: event_captures["proc"].to_string(),
+ name: event_captures["event"].to_string(),
+ details: line[event_pattern.find(line)?.end()..].to_string(),
+ time: event_captures["ts"].parse::<f64>().ok()?,
+ })
+}
+
+static VCPU_ID_PATTERN: OnceCell<Regex> = OnceCell::new();
+
+pub fn parse_vcpu_id(proc_name: &str) -> Result<usize> {
+ let vcpu_id_pattern = VCPU_ID_PATTERN.get_or_init(|| Regex::new(r"crosvm_vcpu(\d+)").unwrap());
+
+ if let Some(captures) = vcpu_id_pattern.captures(proc_name) {
+ captures
+ .get(1)
+ .and_then(|id_match| id_match.as_str().parse::<usize>().ok())
+ .ok_or_else(|| anyhow::anyhow!("Invalid vCPU ID format in process name: {}", proc_name))
+ } else {
+ Err(anyhow::anyhow!(
+ "VCPU ID not found in process name: {}",
+ proc_name
+ ))
+ }
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct SchedWaking {
+ pub waked_proc_name: String,
+ pub waked_pid: i32,
+}
+
+static SCHED_WAKING_PATTERN: OnceCell<Regex> = OnceCell::new();
+
+pub fn parse_sched_waking(details: &str) -> Result<SchedWaking> {
+ let sched_waking_pattern = SCHED_WAKING_PATTERN
+ .get_or_init(|| Regex::new(r"comm=(?P<proc>.*) pid=(?P<pid>\d+)").unwrap());
+
+ let sched_waking_captures = sched_waking_pattern
+ .captures(details)
+ .ok_or_else(|| anyhow!("Failed to parse sched_waking"))?;
+ Ok(SchedWaking {
+ waked_proc_name: sched_waking_captures["proc"].to_string(),
+ waked_pid: sched_waking_captures["pid"]
+ .parse::<i32>()
+ .with_context(|| format!("Failed to parse pid: {}", &sched_waking_captures["pid"]))?,
+ })
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct SchedSwitch {
+ pub prev_proc_name: String,
+ pub prev_pid: i32,
+ pub prev_proc_state: ProcState,
+ pub new_proc_name: String,
+ pub new_pid: i32,
+}
+
+static SCHED_SWITCH_PATTERN: OnceCell<Regex> = OnceCell::new();
+
+pub fn parse_sched_switch(details: &str) -> Result<SchedSwitch> {
+ let sched_switch_pattern = SCHED_SWITCH_PATTERN.get_or_init(|| Regex::new(r"(?P<prev>.*):(?P<prev_pid>\d+) \[-?\d+\] (?P<state>\S+) ==> (?P<new>.*):(?P<new_pid>\d+) \[-?\d+\]").expect("failed to compile regex"));
+
+ let sched_switch_captures = sched_switch_pattern
+ .captures(details)
+ .with_context(|| format!("Failed to parse sched_switch: {}", details))?;
+
+ let prev_state = match &sched_switch_captures["state"] {
+ "R" | "R+" => ProcState::Preempted,
+ "D" | "S" => ProcState::Sleep,
+ "X" => ProcState::Dead,
+ _ => ProcState::Other,
+ };
+
+ Ok(SchedSwitch {
+ prev_proc_name: sched_switch_captures["prev"].to_string(),
+ prev_pid: sched_switch_captures["prev_pid"]
+ .parse::<i32>()
+ .with_context(|| {
+ format!(
+ "Failed to parse pid: {}",
+ &sched_switch_captures["prev_pid"]
+ )
+ })?,
+ prev_proc_state: prev_state,
+ new_proc_name: sched_switch_captures["new"].to_string(),
+ new_pid: sched_switch_captures["new_pid"]
+ .parse::<i32>()
+ .with_context(|| {
+ format!("Failed to parse pid: {}", &sched_switch_captures["new_pid"])
+ })?,
+ })
+}
+
+static TASK_RENAME_PATTERN: OnceCell<Regex> = OnceCell::new();
+
+pub fn parse_task_rename(details: &str) -> Result<String> {
+ // Match a line like "newcomm=D-Bus Thread oom_score_adj="
+ let task_rename_pattern = TASK_RENAME_PATTERN.get_or_init(|| {
+ Regex::new(r"newcomm=(?P<comm>.*) +oom_score_adj=").expect("failed to compile regex")
+ });
+
+ Ok(task_rename_pattern
+ .captures(details)
+ .with_context(|| format!("Failed to parse task_rename: {}", details))?
+ .name("comm")
+ .with_context(|| format!("Failed to parse comm: {}", details))?
+ .as_str()
+ .to_owned())
+}
+
+#[cfg(test)]
+mod tests {
+ use rstest::*;
+
+ use super::*;
+
+ #[rstest]
+ #[case(
+ "normal",
+ " trace-cmd-6563 [006] 575400.854473: sched_stat_runtime: comm=trace-cmd pid=6563 runtime=44314 [ns] vruntime=112263744696292 [ns]",
+ Event {
+ proc_name: "trace-cmd".to_string(),
+ pid: 6563,
+ name: "sched_stat_runtime".to_string(),
+ details: "comm=trace-cmd pid=6563 runtime=44314 [ns] vruntime=112263744696292 [ns]".to_string(),
+ time: 575400.854473,
+ }
+ )]
+ #[case(
+ "proc name with a space",
+ " D-Bus thread-3284 [002] 575401.489380842: sched_waking: comm=chrome pid=3269 prio=112 target_cpu=004",
+ Event {
+ proc_name: "D-Bus thread".to_string(),
+ pid: 3284,
+ name: "sched_waking".to_string(),
+ details: "comm=chrome pid=3269 prio=112 target_cpu=004".to_string(),
+ time: 575401.489380842,
+ }
+ )]
+ fn test_parse_event(#[case] name: &str, #[case] line: &str, #[case] want: Event) {
+ assert_eq!(parse_event(line), Some(want), "Test case: {}", name);
+ }
+
+ #[rstest]
+ #[case(
+ "normal",
+ "comm=VizCompositorTh pid=3338 prio=112 target_cpu=000",
+ SchedWaking {
+ waked_proc_name: "VizCompositorTh".to_string(),
+ waked_pid: 3338,
+ }
+ )]
+ #[case(
+ "proc name with a space",
+ "comm=D-Bus thread pid=3338 prio=112 target_cpu=000",
+ SchedWaking {
+ waked_proc_name: "D-Bus thread".to_string(),
+ waked_pid: 3338,
+ }
+ )]
+ fn test_parse_sched_waking(#[case] name: &str, #[case] line: &str, #[case] want: SchedWaking) {
+ assert_eq!(
+ parse_sched_waking(line).unwrap(),
+ want,
+ "Test case: {}",
+ name
+ );
+ }
+
+ #[rstest]
+ #[case(
+ "normal",
+ "trace-cmd:6559 [120] D ==> swapper/2:0 [120]",
+ SchedSwitch {
+ prev_proc_name: "trace-cmd".to_string(),
+ prev_pid: 6559,
+ prev_proc_state: ProcState::Sleep,
+ new_proc_name: "swapper/2".to_string(),
+ new_pid: 0,
+ }
+ )]
+ #[case(
+ "preempted",
+ "trace-cmd:6559 [120] R+ ==> swapper/2:0 [120]",
+ SchedSwitch {
+ prev_proc_name: "trace-cmd".to_string(),
+ prev_pid: 6559,
+ prev_proc_state: ProcState::Preempted,
+ new_proc_name: "swapper/2".to_string(),
+ new_pid: 0,
+ }
+ )]
+ // ... add more test cases as needed
+ fn test_parse_sched_switch(#[case] name: &str, #[case] line: &str, #[case] want: SchedSwitch) {
+ assert_eq!(
+ parse_sched_switch(line).unwrap(),
+ want,
+ "Test case: {}",
+ name
+ );
+ }
+
+ #[rstest]
+ #[case("crosvm_vcpu123", Some(123))]
+ #[case("crosvm_vcpu4", Some(4))]
+ #[case("invalid_format", None)]
+ #[case("crosvm_vcpuXYZ", None)]
+ #[case("", None)]
+ fn test_parse_vcpu_id(#[case] input: &str, #[case] expected_output: Option<usize>) {
+ // Test logic
+ match super::parse_vcpu_id(input) {
+ Ok(id) => assert_eq!(id, expected_output.unwrap()),
+ Err(_) => assert!(expected_output.is_none()), // Assert an error was expected
+ }
+ }
+}
diff --git a/tools/custom_checks b/tools/custom_checks
index 265624ce8..4f6a38b33 100755
--- a/tools/custom_checks
+++ b/tools/custom_checks
@@ -94,6 +94,7 @@ KNOWN_DISABLED_FEATURES = [
"default-no-sandbox",
"libvda",
"seccomp_trace",
+ "vulkano",
"whpx",
]
diff --git a/tools/examples/example_simple.ps1 b/tools/examples/example_simple.ps1
new file mode 100644
index 000000000..29d13188b
--- /dev/null
+++ b/tools/examples/example_simple.ps1
@@ -0,0 +1,49 @@
+<#
+.Description
+Runs an ubuntu image. The image itself needs to be built on linux as per instructions at
+https://crosvm.dev/book/running_crosvm/example_usage.html#preparing-the-guest-os-image
+
+The console is a pipe at \\.\pipe\crosvm-debug that you can connect to using apps like
+putty.
+.PARAMETER IMAGE_DIR
+Directory where initrd, rootfs and vmlinuz are located. Defaults to user's tmp directory.
+.PARAMETER LOGS_DIR
+Directory where logs will be written to. Defaults to user's tmp directory.
+#>
+param (
+ [Parameter(
+ Position = 0
+ )]
+ [string]$IMAGE_DIR = $Env:TEMP, ##
+ [Parameter(
+ Position = 1
+ )]
+ [string]$LOGS_DIR = $Env:TEMP ##
+)
+
+$VMLINUZ = Join-Path $IMAGE_DIR "vmlinuz"
+$ROOTFS = Join-Path $IMAGE_DIR "rootfs"
+$INITRD = Join-Path $IMAGE_DIR "initrd"
+$SERIAL = "\\.\pipe\crosvm-debug"
+$LOGS_DIR = Join-Path $LOGS_DIR "\"
+
+$PATHS = $IMAGE_DIR, $VMLINUZ, $ROOTFS, $INITRD, $LOGS_DIR
+
+foreach ($path in $PATHS) {
+ if (!(Test-Path $path)) {
+ throw (New-Object System.IO.FileNotFoundException("Path not found: $path", $path))
+ }
+}
+
+cargo run --features "all-msvc64,whpx" -- `
+ --log-level INFO `
+ run-mp `
+ --logs-directory $LOGS_DIR `
+ --cpus 1 `
+ --mem 4096 `
+ --serial "hardware=serial,type=namedpipe,path=$SERIAL,num=1,console=true" `
+ --params "nopat clocksource=jiffies root=/dev/vda5 loglevel=7 console=/dev/ttyS1 console=/dev/ttyS0" `
+ --host-guid "dontcare" `
+ --rwdisk $ROOTFS `
+ --initrd $INITRD `
+ $VMLINUZ
diff --git a/vm_control/src/api.rs b/vm_control/src/api.rs
index e0ed8a239..c23a1035d 100644
--- a/vm_control/src/api.rs
+++ b/vm_control/src/api.rs
@@ -11,6 +11,7 @@ use base::RawDescriptor;
use base::Tube;
use base::TubeError;
use hypervisor::Datamatch;
+use hypervisor::MemCacheType;
use remain::sorted;
use resources::Alloc;
use serde::Deserialize;
@@ -65,14 +66,6 @@ impl VmMemoryClient {
}
}
- fn request_register_memory(&self, request: &VmMemoryRequest) -> Result<VmMemoryRegionId> {
- match self.request(request)? {
- VmMemoryResponse::Err(e) => Err(ApiClientError::RequestFailed(e)),
- VmMemoryResponse::RegisterMemory(region_id) => Ok(region_id),
- _other => Err(ApiClientError::UnexpectedResponse),
- }
- }
-
/// Prepare a shared memory region to make later operations more efficient. This
/// may be a no-op depending on underlying platform support.
pub fn prepare_shared_memory_region(&self, alloc: Alloc) -> Result<()> {
@@ -84,8 +77,19 @@ impl VmMemoryClient {
source: VmMemorySource,
dest: VmMemoryDestination,
prot: Protection,
+ cache: MemCacheType,
) -> Result<VmMemoryRegionId> {
- self.request_register_memory(&VmMemoryRequest::RegisterMemory { source, dest, prot })
+ let request = VmMemoryRequest::RegisterMemory {
+ source,
+ dest,
+ prot,
+ cache,
+ };
+ match self.request(&request)? {
+ VmMemoryResponse::Err(e) => Err(ApiClientError::RequestFailed(e)),
+ VmMemoryResponse::RegisterMemory(region_id) => Ok(region_id),
+ _other => Err(ApiClientError::UnexpectedResponse),
+ }
}
/// Call hypervisor to free the given memory range.
diff --git a/vm_control/src/lib.rs b/vm_control/src/lib.rs
index bcf19a28c..1cec61c35 100644
--- a/vm_control/src/lib.rs
+++ b/vm_control/src/lib.rs
@@ -21,6 +21,7 @@ use base::linux::MemoryMappingBuilderUnix;
#[cfg(windows)]
use base::MemoryMappingBuilderWindows;
use hypervisor::BalloonEvent;
+use hypervisor::MemCacheType;
use hypervisor::MemRegion;
#[cfg(feature = "balloon")]
@@ -421,6 +422,19 @@ unsafe impl MappedRegion for RutabagaMemoryRegion {
}
}
+impl Display for VmMemorySource {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use self::VmMemorySource::*;
+
+ match self {
+ SharedMemory(..) => write!(f, "VmMemorySource::SharedMemory"),
+ Descriptor { .. } => write!(f, "VmMemorySource::Descriptor"),
+ Vulkan { .. } => write!(f, "VmMemorySource::Vulkan"),
+ ExternalMapping { .. } => write!(f, "VmMemorySource::ExternalMapping"),
+ }
+ }
+}
+
impl VmMemorySource {
/// Map the resource and return its mapping and size in bytes.
pub fn map(
@@ -533,6 +547,8 @@ pub enum VmMemoryRequest {
dest: VmMemoryDestination,
/// Whether to map the memory read only (true) or read-write (false).
prot: Protection,
+ /// Cache attribute for guest memory setting
+ cache: MemCacheType,
},
/// Call hypervisor to free the given memory range.
DynamicallyFreeMemoryRange {
@@ -598,7 +614,7 @@ impl Default for VmMemoryRegionState {
}
}
-fn handle_prepared_region(
+fn try_map_to_prepared_region(
vm: &mut impl Vm,
region_state: &mut VmMemoryRegionState,
source: &VmMemorySource,
@@ -625,7 +641,13 @@ fn handle_prepared_region(
let size = shm.size() as usize;
(Descriptor(shm.as_raw_descriptor()), 0, size)
}
- _ => return Some(VmMemoryResponse::Err(SysError::new(EINVAL))),
+ _ => {
+ error!(
+ "source {} is not compatible with fixed mapping into prepared memory region",
+ source
+ );
+ return Some(VmMemoryResponse::Err(SysError::new(EINVAL)));
+ }
};
if let Err(err) = vm.add_fd_mapping(
*slot,
@@ -666,9 +688,13 @@ impl VmMemoryRequest {
use self::VmMemoryRequest::*;
match self {
PrepareSharedMemoryRegion { alloc } => {
- // Currently the iommu_client is only used by virtio-gpu, and virtio-gpu
- // is incompatible with PrepareSharedMemoryRegion because we can't use
- // add_fd_mapping with VmMemorySource::Vulkan.
+ // Currently the iommu_client is only used by virtio-gpu when used alongside GPU
+ // pci-passthrough.
+ //
+ // TODO(b/323368701): Make compatible with iommu_client by ensuring that
+ // VirtioIOMMUVfioCommand::VfioDmabufMap is submitted for both dynamic mappings and
+ // fixed mappings (i.e. whether or not try_map_to_prepared_region succeeds in
+ // RegisterMemory case below).
assert!(iommu_client.is_none());
if !sys::should_prepare_memory_region() {
@@ -683,8 +709,14 @@ impl VmMemoryRequest {
Err(e) => VmMemoryResponse::Err(e),
}
}
- RegisterMemory { source, dest, prot } => {
- if let Some(resp) = handle_prepared_region(vm, region_state, &source, &dest, &prot)
+ RegisterMemory {
+ source,
+ dest,
+ prot,
+ cache,
+ } => {
+ if let Some(resp) =
+ try_map_to_prepared_region(vm, region_state, &source, &dest, &prot)
{
return resp;
}
@@ -706,6 +738,7 @@ impl VmMemoryRequest {
mapped_region,
prot == Protection::read(),
false,
+ cache,
) {
Ok(slot) => slot,
Err(e) => return VmMemoryResponse::Err(e),
@@ -2030,6 +2063,7 @@ fn do_snapshot(
let snapshot_writer = SnapshotWriter::new(snapshot_path)?;
// Snapshot Vcpus
+ info!("VCPUs snapshotting...");
let (send_chan, recv_chan) = mpsc::channel();
kick_vcpus(VcpuControl::Snapshot(
snapshot_writer.add_namespace("vcpu")?,
@@ -2042,14 +2076,18 @@ fn do_snapshot(
.context("Failed to recv Vcpu snapshot response")?
.context("Failed to snapshot Vcpu")?;
}
+ info!("VCPUs snapshotted.");
// Snapshot irqchip
+ info!("Snapshotting irqchip...");
let irqchip_snap = snapshot_irqchip()?;
snapshot_writer
.write_fragment("irqchip", &irqchip_snap)
.context("Failed to write irqchip state")?;
+ info!("Snapshotted irqchip.");
// Snapshot devices
+ info!("Devices snapshotting...");
device_control_tube
.send(&DeviceControlCommand::SnapshotDevices { snapshot_writer })
.context("send command to devices control socket")?;
@@ -2059,6 +2097,7 @@ fn do_snapshot(
if !matches!(resp, VmResponse::Ok) {
bail!("unexpected SnapshotDevices response: {resp}");
}
+ info!("Devices snapshotted.");
Ok(())
}
diff --git a/vm_control/src/sys/linux.rs b/vm_control/src/sys/linux.rs
index d125f4c88..eb8d84c50 100644
--- a/vm_control/src/sys/linux.rs
+++ b/vm_control/src/sys/linux.rs
@@ -18,6 +18,7 @@ use base::Protection;
use base::SafeDescriptor;
use base::Tube;
use base::UnixSeqpacket;
+use hypervisor::MemCacheType;
use hypervisor::MemSlot;
use hypervisor::Vm;
use libc::EINVAL;
@@ -173,7 +174,13 @@ pub fn prepare_shared_memory_region(
_ => return Err(SysError::new(EINVAL)),
};
- match vm.add_memory_region(GuestAddress(range.start), Box::new(arena), false, false) {
+ match vm.add_memory_region(
+ GuestAddress(range.start),
+ Box::new(arena),
+ false,
+ false,
+ MemCacheType::CacheCoherent,
+ ) {
Ok(slot) => Ok((range.start >> 12, slot)),
Err(e) => Err(e),
}
diff --git a/win_util/src/dpapi.rs b/win_util/src/dpapi.rs
new file mode 100644
index 000000000..d40054b37
--- /dev/null
+++ b/win_util/src/dpapi.rs
@@ -0,0 +1,181 @@
+// Copyright 2024 The ChromiumOS Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#![deny(unsafe_op_in_unsafe_fn)]
+
+//! Safe, Rusty wrappers around DPAPI.
+
+use std::ffi::c_void;
+use std::ptr;
+use std::slice;
+
+use anyhow::Context;
+use anyhow::Result;
+use winapi::um::dpapi::CryptProtectData;
+use winapi::um::dpapi::CryptUnprotectData;
+use winapi::um::winbase::LocalFree;
+use winapi::um::wincrypt::DATA_BLOB;
+
+use crate::syscall_bail;
+
+/// Wrapper around buffers allocated by DPAPI that can be freed with LocalFree.
+pub struct LocalAllocBuffer {
+ ptr: *mut u8,
+ len: usize,
+}
+
+impl LocalAllocBuffer {
+ /// # Safety
+ /// 0. ptr is a valid buffer of length len and is safe to free with LocalFree.
+ /// 1. The caller transfers ownership of the buffer to this object on construction.
+ unsafe fn new(ptr: *mut u8, len: usize) -> Self {
+ Self { ptr, len }
+ }
+
+ pub fn as_mut_slice(&mut self) -> &mut [u8] {
+ // SAFETY: ptr is a pointer to a buffer of length len.
+ unsafe { slice::from_raw_parts_mut(self.ptr, self.len) }
+ }
+
+ pub fn as_slice(&self) -> &[u8] {
+ // SAFETY: ptr is a pointer to a buffer of length len.
+ unsafe { slice::from_raw_parts(self.ptr, self.len) }
+ }
+}
+
+impl Drop for LocalAllocBuffer {
+ fn drop(&mut self) {
+ // SAFETY: when this struct is created, the caller guarantees
+ // ptr is a valid pointer to a buffer that can be freed with LocalFree.
+ unsafe {
+ LocalFree(self.ptr as *mut c_void);
+ }
+ }
+}
+
+/// # Summary
+/// Wrapper around CryptProtectData that displays no UI.
+pub fn crypt_protect_data(plaintext: &mut [u8]) -> Result<LocalAllocBuffer> {
+ let mut plaintext_blob = DATA_BLOB {
+ cbData: plaintext
+ .len()
+ .try_into()
+ .context("plaintext size won't fit in DWORD")?,
+ pbData: plaintext.as_mut_ptr(),
+ };
+ let mut ciphertext_blob = DATA_BLOB {
+ cbData: 0,
+ pbData: ptr::null_mut(),
+ };
+
+ // SAFETY: the FFI call is safe because
+ // 1. plaintext_blob lives longer than the call.
+ // 2. ciphertext_blob lives longer than the call, and we later give
+ // ownership of the memory the kernel allocates to LocalAllocBuffer
+ // which guarantees it is freed.
+ let res = unsafe {
+ CryptProtectData(
+ &mut plaintext_blob as *mut _,
+ /* szDataDescr= */ ptr::null_mut(),
+ /* pOptionalEntropy= */ ptr::null_mut(),
+ /* pvReserved= */ ptr::null_mut(),
+ /* pPromptStruct */ ptr::null_mut(),
+ /* dwFlags */ 0,
+ &mut ciphertext_blob as *mut _,
+ )
+ };
+ if res == 0 {
+ syscall_bail!("CryptProtectData failed");
+ }
+
+ let ciphertext_len: usize = ciphertext_blob
+ .cbData
+ .try_into()
+ .context("resulting ciphertext had an invalid size")?;
+
+ // SAFETY: safe because ciphertext_blob refers to a valid buffer of the specified length. This
+ // is guaranteed because CryptProtectData returned success.
+ Ok(unsafe { LocalAllocBuffer::new(ciphertext_blob.pbData, ciphertext_len) })
+}
+
+/// # Summary
+/// Wrapper around CryptProtectData that displays no UI.
+pub fn crypt_unprotect_data(ciphertext: &mut [u8]) -> Result<LocalAllocBuffer> {
+ let mut ciphertext_blob = DATA_BLOB {
+ cbData: ciphertext
+ .len()
+ .try_into()
+ .context("plaintext size won't fit in DWORD")?,
+ pbData: ciphertext.as_mut_ptr(),
+ };
+ let mut plaintext_blob = DATA_BLOB {
+ cbData: 0,
+ pbData: ptr::null_mut(),
+ };
+
+ // SAFETY: the FFI call is safe because
+ // 1. ciphertext_blob lives longer than the call.
+ // 2. plaintext_blob lives longer than the call, and we later give
+ // ownership of the memory the kernel allocates to LocalAllocBuffer
+ // which guarantees it is freed.
+ let res = unsafe {
+ CryptUnprotectData(
+ &mut ciphertext_blob as *mut _,
+ /* szDataDescr= */ ptr::null_mut(),
+ /* pOptionalEntropy= */ ptr::null_mut(),
+ /* pvReserved= */ ptr::null_mut(),
+ /* pPromptStruct */ ptr::null_mut(),
+ /* dwFlags */ 0,
+ &mut plaintext_blob as *mut _,
+ )
+ };
+ if res == 0 {
+ syscall_bail!("CryptUnprotectData failed");
+ }
+
+ let plaintext_len: usize = plaintext_blob
+ .cbData
+ .try_into()
+ .context("resulting plaintext had an invalid size")?;
+
+ // SAFETY: safe because plaintext_blob refers to a valid buffer of the specified length. This
+ // is guaranteed because CryptUnprotectData returned success.
+ Ok(unsafe { LocalAllocBuffer::new(plaintext_blob.pbData, plaintext_len) })
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn encrypt_empty_string_is_valid() {
+ let plaintext_str = "";
+ let mut plaintext_buffer = Vec::from(plaintext_str.as_bytes());
+
+ let mut ciphertext_buffer = crypt_protect_data(plaintext_buffer.as_mut_slice()).unwrap();
+ let decrypted_plaintext_buffer =
+ crypt_unprotect_data(ciphertext_buffer.as_mut_slice()).unwrap();
+ let decrypted_plaintext_str =
+ std::str::from_utf8(decrypted_plaintext_buffer.as_slice()).unwrap();
+ assert_eq!(plaintext_str, decrypted_plaintext_str);
+ }
+
+ #[test]
+ fn encrypt_decrypt_plaintext_matches() {
+ let plaintext_str = "test plaintext";
+ let mut plaintext_buffer = Vec::from(plaintext_str.as_bytes());
+
+ let mut ciphertext_buffer = crypt_protect_data(plaintext_buffer.as_mut_slice()).unwrap();
+
+ // If our plaintext & ciphertext are the same, something is very wrong.
+ assert_ne!(plaintext_str.as_bytes(), ciphertext_buffer.as_slice());
+
+ // Decrypt the ciphertext and make sure it's our original plaintext.
+ let decrypted_plaintext_buffer =
+ crypt_unprotect_data(ciphertext_buffer.as_mut_slice()).unwrap();
+ let decrypted_plaintext_str =
+ std::str::from_utf8(decrypted_plaintext_buffer.as_slice()).unwrap();
+ assert_eq!(plaintext_str, decrypted_plaintext_str);
+ }
+}
diff --git a/win_util/src/lib.rs b/win_util/src/lib.rs
index 1e9005248..861961063 100644
--- a/win_util/src/lib.rs
+++ b/win_util/src/lib.rs
@@ -55,6 +55,8 @@ use winapi::um::winnt::WCHAR;
pub use crate::dll_notification::*;
+pub mod dpapi;
+
#[macro_export]
macro_rules! syscall_bail {
($details:expr) => {