diff options
author | Erwin Jansen <jansene@google.com> | 2024-02-13 14:42:38 -0800 |
---|---|---|
committer | Erwin Jansen <jansene@google.com> | 2024-02-14 16:42:56 +0000 |
commit | 68114839c53631df7468eb4e26a30648f184b6a4 (patch) | |
tree | 5664325f1c3bf3b9a751d7da4894ba764a5fc26b | |
parent | ff2fc477ccd475b3124ad18611ebc243280be322 (diff) | |
parent | e23ea9b0607d07209f8b4c20f2864538079ea560 (diff) | |
download | crosvm-emu-dev.tar.gz |
Merge remote-tracking branch 'aosp/upstream-main'emu-dev
Bug: 324640237
Change-Id: I64c2f6b9a31a98ca930ba3f979cef677dce4c52e
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: — **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 Binary files differnew file mode 100644 index 000000000..cec65ccd2 --- /dev/null +++ b/tools/contrib/vcpu_blocker_analyzer/images/mechanism.png diff --git a/tools/contrib/vcpu_blocker_analyzer/images/timer_defect.png b/tools/contrib/vcpu_blocker_analyzer/images/timer_defect.png Binary files differnew file mode 100644 index 000000000..664e09b6b --- /dev/null +++ b/tools/contrib/vcpu_blocker_analyzer/images/timer_defect.png 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) => { |