From 4c2d05dd6435d449a3651a6dd314d9411b5f8146 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Fri, 30 Sep 2022 21:09:04 +0000 Subject: cloud-hypervisor: add virtio-gpu support The virtio-bindings changes update the bindings for recent kernels, and the vhost change is cherry-picked from crosvm's fork of the crate to add support for their custom extensions. Signed-off-by: Alyssa Ross Signed-off-by: Alyssa Ross --- .../0001-build-use-local-vhost.patch | 50 + .../0002-virtio-devices-add-a-GPU-device.patch | 1344 ++++++++++++++++++++ .../virtualization/cloud-hypervisor/Cargo.lock | 5 +- .../virtualization/cloud-hypervisor/default.nix | 33 + .../0001-vhost-fix-receiving-reply-payloads.patch | 113 ++ ...ost_user-add-shared-memory-region-support.patch | 492 +++++++ ...03-vhost-user-add-protocol-flag-for-shmem.patch | 38 + 7 files changed, 2071 insertions(+), 4 deletions(-) create mode 100644 pkgs/applications/virtualization/cloud-hypervisor/0001-build-use-local-vhost.patch create mode 100644 pkgs/applications/virtualization/cloud-hypervisor/0002-virtio-devices-add-a-GPU-device.patch create mode 100644 pkgs/applications/virtualization/cloud-hypervisor/vhost/0001-vhost-fix-receiving-reply-payloads.patch create mode 100644 pkgs/applications/virtualization/cloud-hypervisor/vhost/0002-vhost_user-add-shared-memory-region-support.patch create mode 100644 pkgs/applications/virtualization/cloud-hypervisor/vhost/0003-vhost-user-add-protocol-flag-for-shmem.patch (limited to 'pkgs') diff --git a/pkgs/applications/virtualization/cloud-hypervisor/0001-build-use-local-vhost.patch b/pkgs/applications/virtualization/cloud-hypervisor/0001-build-use-local-vhost.patch new file mode 100644 index 00000000000..edf1571fd10 --- /dev/null +++ b/pkgs/applications/virtualization/cloud-hypervisor/0001-build-use-local-vhost.patch @@ -0,0 +1,50 @@ +From 4c8d6c07213a9826545f5682b2d51cb84ffbb9ff Mon Sep 17 00:00:00 2001 +From: Alyssa Ross +Date: Wed, 28 Sep 2022 12:18:19 +0000 +Subject: [PATCH 1/2] build: use local vhost + +Signed-off-by: Alyssa Ross +Signed-off-by: Alyssa Ross +--- + Cargo.lock | 4 ---- + Cargo.toml | 2 ++ + 2 files changed, 2 insertions(+), 4 deletions(-) + +diff --git a/Cargo.lock b/Cargo.lock +index 346e3f45..d591569b 100644 +--- a/Cargo.lock ++++ b/Cargo.lock +@@ -2160,8 +2160,6 @@ dependencies = [ + [[package]] + name = "vhost" + version = "0.8.1" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "61957aeb36daf0b00b87fff9c10dd28a161bd35ab157553d340d183b3d8756e6" + dependencies = [ + "bitflags 1.3.2", + "libc", +@@ -2172,8 +2170,6 @@ dependencies = [ + [[package]] + name = "vhost-user-backend" + version = "0.10.1" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "ab069cdedaf18a0673766eb0a07a0f4ee3ed1b8e17fbfe4aafe5b988e2de1d01" + dependencies = [ + "libc", + "log", +diff --git a/Cargo.toml b/Cargo.toml +index 0f575076..3b39bede 100644 +--- a/Cargo.toml ++++ b/Cargo.toml +@@ -55,6 +55,8 @@ zbus = { version = "3.11.1", optional = true } + kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch-v0.6.0-tdx" } + kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "main" } + versionize_derive = { git = "https://github.com/cloud-hypervisor/versionize_derive", branch = "ch" } ++vhost = { path = "../vhost/crates/vhost" } ++vhost-user-backend = { path = "../vhost/crates/vhost-user-backend" } + + [dev-dependencies] + dirs = "5.0.0" +-- +2.42.0 + diff --git a/pkgs/applications/virtualization/cloud-hypervisor/0002-virtio-devices-add-a-GPU-device.patch b/pkgs/applications/virtualization/cloud-hypervisor/0002-virtio-devices-add-a-GPU-device.patch new file mode 100644 index 00000000000..c23caa94a67 --- /dev/null +++ b/pkgs/applications/virtualization/cloud-hypervisor/0002-virtio-devices-add-a-GPU-device.patch @@ -0,0 +1,1344 @@ +From 285370872d43dbc17ed017dbf67b1981df5c4b3b Mon Sep 17 00:00:00 2001 +From: Alyssa Ross +Date: Wed, 7 Sep 2022 14:16:29 +0000 +Subject: [PATCH 2/2] virtio-devices: add a GPU device + +This adds support for exposing a virtio-gpu device to guest by +implementing a vhost-user frontend compatible with crosvm's GPU device +backend. + +Note that this is not the same as the "vhost-user-gpu" protocol +implemented by QEMU. + +Adding a GPU device from the command line looks like this: + + --gpu socket=/path/to/crosvm-gpu-vhost-user.sock + +Signed-off-by: Alyssa Ross +Co-authored-by: Alyssa Ross +Signed-off-by: Alyssa Ross +--- + Cargo.lock | 1 + + src/main.rs | 12 + + virtio-devices/src/device.rs | 8 +- + virtio-devices/src/lib.rs | 4 +- + virtio-devices/src/seccomp_filters.rs | 16 + + virtio-devices/src/transport/pci_device.rs | 4 +- + virtio-devices/src/vhost_user/gpu.rs | 406 +++++++++++++++++++++ + virtio-devices/src/vhost_user/mod.rs | 2 + + vmm/Cargo.toml | 1 + + vmm/src/api/mod.rs | 10 +- + vmm/src/config.rs | 111 ++++++ + vmm/src/device_manager.rs | 140 ++++++- + vmm/src/lib.rs | 88 ++++- + vmm/src/vm.rs | 28 +- + vmm/src/vm_config.rs | 27 ++ + 15 files changed, 841 insertions(+), 17 deletions(-) + create mode 100644 virtio-devices/src/vhost_user/gpu.rs + +diff --git a/Cargo.lock b/Cargo.lock +index d591569b..e1a1ad02 100644 +--- a/Cargo.lock ++++ b/Cargo.lock +@@ -2370,6 +2370,7 @@ dependencies = [ + "versionize_derive", + "vfio-ioctls", + "vfio_user", ++ "virtio-bindings", + "virtio-devices", + "virtio-queue", + "vm-allocator", +diff --git a/src/main.rs b/src/main.rs +index 9521b7af..0dc315b7 100644 +--- a/src/main.rs ++++ b/src/main.rs +@@ -199,6 +199,10 @@ pub struct TopLevel { + /// tag=, socket=, num_queues=, queue_size=, id=, pci_segment= + fs: Vec, + ++ #[argh(option, long = "gpu")] ++ /// socket=,cache_size=,id=,pci_segment= ++ gpu: Vec, ++ + #[argh(option, long = "pmem")] + /// file=, size=, iommu=on|off, discard_writes=on|off, id=, pci_segment= + pmem: Vec, +@@ -333,6 +337,12 @@ impl TopLevel { + None + }; + ++ let gpu = if !self.gpu.is_empty() { ++ Some(self.gpu.iter().map(|x| x.as_str()).collect()) ++ } else { ++ None ++ }; ++ + let pmem = if !self.pmem.is_empty() { + Some(self.pmem.iter().map(|x| x.as_str()).collect()) + } else { +@@ -389,6 +399,7 @@ impl TopLevel { + rng, + balloon, + fs, ++ gpu, + pmem, + serial, + console, +@@ -803,6 +814,7 @@ mod unit_tests { + }, + balloon: None, + fs: None, ++ gpu: None, + pmem: None, + serial: ConsoleConfig { + file: None, +diff --git a/virtio-devices/src/device.rs b/virtio-devices/src/device.rs +index b70092f8..e091ddd6 100644 +--- a/virtio-devices/src/device.rs ++++ b/virtio-devices/src/device.rs +@@ -11,7 +11,7 @@ use crate::{ + VIRTIO_F_RING_INDIRECT_DESC, + }; + use libc::EFD_NONBLOCK; +-use std::collections::HashMap; ++use std::collections::{BTreeMap, HashMap}; + use std::io::Write; + use std::num::Wrapping; + use std::sync::{ +@@ -47,19 +47,19 @@ pub struct UserspaceMapping { + pub mergeable: bool, + } + +-#[derive(Clone)] ++#[derive(Clone, Debug)] + pub struct VirtioSharedMemory { + pub offset: u64, + pub len: u64, + } + +-#[derive(Clone)] ++#[derive(Clone, Debug)] + pub struct VirtioSharedMemoryList { + pub host_addr: u64, + pub mem_slot: u32, + pub addr: GuestAddress, + pub len: GuestUsize, +- pub region_list: Vec, ++ pub region_list: BTreeMap, + } + + /// Trait for virtio devices to be driven by a virtio transport. +diff --git a/virtio-devices/src/lib.rs b/virtio-devices/src/lib.rs +index 680cbe29..55428cd8 100644 +--- a/virtio-devices/src/lib.rs ++++ b/virtio-devices/src/lib.rs +@@ -44,7 +44,7 @@ pub use self::block::{Block, BlockState}; + pub use self::console::{Console, ConsoleResizer, Endpoint}; + pub use self::device::{ + DmaRemapping, UserspaceMapping, VirtioCommon, VirtioDevice, VirtioInterrupt, +- VirtioInterruptType, VirtioSharedMemoryList, ++ VirtioInterruptType, VirtioSharedMemory, VirtioSharedMemoryList, + }; + pub use self::epoll_helper::{ + EpollHelper, EpollHelperError, EpollHelperHandler, EPOLL_HELPER_EVENT_LAST, +@@ -93,6 +93,8 @@ pub enum ActivateError { + VhostUserFsSetup(vhost_user::Error), + #[error("Failed to setup vhost-user daemon: {0}")] + VhostUserSetup(vhost_user::Error), ++ #[error("Failed to setup vhost-user-gpu daemon: {0}")] ++ VhostUserGpuSetup(vhost_user::Error), + #[error("Failed to create seccomp filter: {0}")] + CreateSeccompFilter(seccompiler::Error), + #[error("Failed to create rate limiter: {0}")] +diff --git a/virtio-devices/src/seccomp_filters.rs b/virtio-devices/src/seccomp_filters.rs +index 43d723b8..8620419f 100644 +--- a/virtio-devices/src/seccomp_filters.rs ++++ b/virtio-devices/src/seccomp_filters.rs +@@ -22,6 +22,7 @@ pub enum Thread { + VirtioRng, + VirtioVhostBlock, + VirtioVhostFs, ++ VirtioVhostGpu, + VirtioVhostNet, + VirtioVhostNetCtl, + VirtioVsock, +@@ -163,6 +164,20 @@ fn virtio_vhost_fs_thread_rules() -> Vec<(i64, Vec)> { + ] + } + ++fn virtio_vhost_gpu_thread_rules() -> Vec<(i64, Vec)> { ++ vec![ ++ (libc::SYS_clock_nanosleep, vec![]), ++ (libc::SYS_connect, vec![]), ++ (libc::SYS_getcwd, vec![]), ++ (libc::SYS_nanosleep, vec![]), ++ (libc::SYS_recvmsg, vec![]), ++ (libc::SYS_recvmsg, vec![]), ++ (libc::SYS_sendmsg, vec![]), ++ (libc::SYS_sendmsg, vec![]), ++ (libc::SYS_socket, vec![]), ++ ] ++} ++ + fn virtio_vhost_net_ctl_thread_rules() -> Vec<(i64, Vec)> { + vec![] + } +@@ -234,6 +249,7 @@ fn get_seccomp_rules(thread_type: Thread) -> Vec<(i64, Vec)> { + Thread::VirtioRng => virtio_rng_thread_rules(), + Thread::VirtioVhostBlock => virtio_vhost_block_thread_rules(), + Thread::VirtioVhostFs => virtio_vhost_fs_thread_rules(), ++ Thread::VirtioVhostGpu => virtio_vhost_gpu_thread_rules(), + Thread::VirtioVhostNet => virtio_vhost_net_thread_rules(), + Thread::VirtioVhostNetCtl => virtio_vhost_net_ctl_thread_rules(), + Thread::VirtioVsock => virtio_vsock_thread_rules(), +diff --git a/virtio-devices/src/transport/pci_device.rs b/virtio-devices/src/transport/pci_device.rs +index 60979573..0348b53a 100644 +--- a/virtio-devices/src/transport/pci_device.rs ++++ b/virtio-devices/src/transport/pci_device.rs +@@ -1054,11 +1054,11 @@ impl PciDevice for VirtioPciDevice { + PciDeviceError::IoRegistrationFailed(shm_list.addr.raw_value(), e) + })?; + +- for (idx, shm) in shm_list.region_list.iter().enumerate() { ++ for (shmid, shm) in shm_list.region_list.iter() { + let shm_cap = VirtioPciCap64::new( + PciCapabilityType::SharedMemory, + VIRTIO_SHM_BAR_INDEX as u8, +- idx as u8, ++ *shmid, + shm.offset, + shm.len, + ); +diff --git a/virtio-devices/src/vhost_user/gpu.rs b/virtio-devices/src/vhost_user/gpu.rs +new file mode 100644 +index 00000000..416d9629 +--- /dev/null ++++ b/virtio-devices/src/vhost_user/gpu.rs +@@ -0,0 +1,406 @@ ++// Copyright 2019 Intel Corporation. All Rights Reserved. ++// Copyright 2022 Unikie ++// Copyright 2023 Alyssa Ross ++// SPDX-License-Identifier: Apache-2.0 ++ ++use super::vu_common_ctrl::VhostUserHandle; ++use super::{Error, Result}; ++use crate::seccomp_filters::Thread; ++use crate::thread_helper::spawn_virtio_thread; ++use crate::vhost_user::VhostUserCommon; ++use crate::{ ++ ActivateError, ActivateResult, UserspaceMapping, VirtioCommon, VirtioDevice, VirtioDeviceType, ++ VirtioInterrupt, VirtioSharedMemoryList, VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1, ++}; ++use crate::{GuestMemoryMmap, GuestRegionMmap, MmapRegion}; ++use seccompiler::SeccompAction; ++use std::io::{self, Write}; ++use std::os::unix::io::AsRawFd; ++use std::result; ++use std::sync::{Arc, Barrier, Mutex}; ++use std::thread; ++use vhost::vhost_user::message::{ ++ VhostUserConfigFlags, VhostUserProtocolFeatures, VhostUserShmemMapMsg, VhostUserShmemUnmapMsg, ++ VhostUserVirtioFeatures, ++}; ++use vhost::vhost_user::{ ++ HandlerResult, MasterReqHandler, VhostUserMaster, VhostUserMasterReqHandler, ++}; ++use virtio_bindings::virtio_gpu::{ ++ VIRTIO_GPU_F_CONTEXT_INIT, VIRTIO_GPU_F_RESOURCE_BLOB, VIRTIO_GPU_F_RESOURCE_UUID, ++ VIRTIO_GPU_F_VIRGL, ++}; ++use virtio_queue::Queue; ++use vm_memory::GuestMemoryAtomic; ++use vm_migration::{MigratableError, Pausable}; ++use vmm_sys_util::eventfd::EventFd; ++ ++const QUEUE_SIZES: &[u16] = &[256, 16]; ++const NUM_QUEUES: u16 = QUEUE_SIZES.len() as _; ++ ++struct SlaveReqHandler { ++ cache_size: u64, ++ mmap_cache_addr: u64, ++} ++ ++impl SlaveReqHandler { ++ // Make sure request is within cache range ++ fn is_req_valid(&self, offset: u64, len: u64) -> bool { ++ let end = match offset.checked_add(len) { ++ Some(n) => n, ++ None => return false, ++ }; ++ ++ !(offset >= self.cache_size || end > self.cache_size) ++ } ++} ++ ++impl VhostUserMasterReqHandler for SlaveReqHandler { ++ fn shmem_map(&self, req: &VhostUserShmemMapMsg, fd: &dyn AsRawFd) -> HandlerResult { ++ if !self.is_req_valid(req.shm_offset, req.len) { ++ return Err(io::Error::from_raw_os_error(libc::EINVAL)); ++ } ++ ++ let addr = self.mmap_cache_addr + req.shm_offset; ++ let ret = unsafe { ++ libc::mmap( ++ addr as *mut libc::c_void, ++ req.len as usize, ++ req.flags.bits() as i32, ++ // https://bugzilla.kernel.org/show_bug.cgi?id=217238 ++ if req.flags.bits() as i32 & libc::PROT_WRITE != 0 { ++ libc::MAP_SHARED ++ } else { ++ libc::MAP_PRIVATE ++ } | libc::MAP_FIXED, ++ fd.as_raw_fd(), ++ req.fd_offset as libc::off_t, ++ ) ++ }; ++ ++ if ret == libc::MAP_FAILED { ++ return Err(io::Error::last_os_error()); ++ } ++ ++ Ok(0) ++ } ++ ++ fn shmem_unmap(&self, req: &VhostUserShmemUnmapMsg) -> HandlerResult { ++ if !self.is_req_valid(req.shm_offset, req.len) { ++ return Err(io::Error::from_raw_os_error(libc::EINVAL)); ++ } ++ ++ let addr = self.mmap_cache_addr + req.shm_offset; ++ let ret = unsafe { ++ libc::mmap( ++ addr as *mut libc::c_void, ++ req.len as usize, ++ libc::PROT_NONE, ++ libc::MAP_ANONYMOUS | libc::MAP_PRIVATE | libc::MAP_FIXED, ++ -1, ++ 0, ++ ) ++ }; ++ if ret == libc::MAP_FAILED { ++ return Err(io::Error::last_os_error()); ++ } ++ ++ Ok(0) ++ } ++} ++ ++#[derive(Default)] ++#[repr(C, packed)] ++pub struct VirtioGpuConfig { ++ pub events_read: u32, ++ pub events_clear: u32, ++ pub num_scanouts: u32, ++ pub num_capsets: u32, ++} ++ ++pub struct Gpu { ++ common: VirtioCommon, ++ vu_common: VhostUserCommon, ++ id: String, ++ // Hold ownership of the memory that is allocated for the device ++ // which will be automatically dropped when the device is dropped ++ cache: Option<(VirtioSharedMemoryList, MmapRegion)>, ++ slave_req_support: bool, ++ seccomp_action: SeccompAction, ++ guest_memory: Option>, ++ epoll_thread: Option>, ++ exit_evt: EventFd, ++ iommu: bool, ++} ++ ++impl Gpu { ++ /// Create a new virtio-gpu device. ++ pub fn new( ++ id: String, ++ path: &str, ++ cache: Option<(VirtioSharedMemoryList, MmapRegion)>, ++ seccomp_action: SeccompAction, ++ exit_evt: EventFd, ++ iommu: bool, ++ ) -> Result { ++ // Connect to the vhost-user socket. ++ let mut vu = VhostUserHandle::connect_vhost_user(false, path, NUM_QUEUES as u64, false)?; ++ ++ let avail_features = 1 << VIRTIO_F_VERSION_1 ++ | 1 << VIRTIO_GPU_F_VIRGL ++ | 1 << VIRTIO_GPU_F_RESOURCE_UUID ++ | 1 << VIRTIO_GPU_F_RESOURCE_BLOB ++ | 1 << VIRTIO_GPU_F_CONTEXT_INIT ++ | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits(); ++ ++ let avail_protocol_features = ++ VhostUserProtocolFeatures::CONFIG | VhostUserProtocolFeatures::SLAVE_REQ; ++ // The SHARED_MEMORY_REGIONS protocol feature is a way for the backend to indicate ++ // that it supports the GET_SHARED_MEMORY_REGIONS request. Since we don't use that ++ // request, we don't ack SHARED_MEMORY_REGIONS. ++ ++ let (acked_features, acked_protocol_features) = ++ vu.negotiate_features_vhost_user(avail_features, avail_protocol_features)?; ++ ++ Ok(Gpu { ++ common: VirtioCommon { ++ device_type: VirtioDeviceType::Gpu as u32, ++ avail_features: acked_features, ++ // If part of the available features that have been acked, the ++ // PROTOCOL_FEATURES bit must be already set through the VIRTIO ++ // acked features as we know the guest would never ack it, this ++ // the feature would be lost. ++ acked_features: acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits(), ++ paused_sync: Some(Arc::new(Barrier::new(NUM_QUEUES as usize))), ++ queue_sizes: QUEUE_SIZES.to_vec(), ++ min_queues: NUM_QUEUES, ++ ..Default::default() ++ }, ++ vu_common: VhostUserCommon { ++ vu: Some(Arc::new(Mutex::new(vu))), ++ acked_protocol_features, ++ socket_path: path.to_string(), ++ vu_num_queues: NUM_QUEUES as usize, ++ ..Default::default() ++ }, ++ id, ++ cache, ++ slave_req_support: acked_protocol_features ++ & VhostUserProtocolFeatures::SLAVE_REQ.bits() ++ != 0, ++ seccomp_action, ++ guest_memory: None, ++ epoll_thread: None, ++ exit_evt, ++ iommu, ++ }) ++ } ++} ++ ++impl Drop for Gpu { ++ fn drop(&mut self) { ++ if let Some(kill_evt) = self.common.kill_evt.take() { ++ // Ignore the result because there is nothing we can do about it. ++ let _ = kill_evt.write(1); ++ } ++ } ++} ++ ++impl VirtioDevice for Gpu { ++ fn device_type(&self) -> u32 { ++ self.common.device_type ++ } ++ ++ fn queue_max_sizes(&self) -> &[u16] { ++ &self.common.queue_sizes ++ } ++ ++ fn features(&self) -> u64 { ++ let mut features = self.common.avail_features; ++ if self.iommu { ++ features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM; ++ } ++ features ++ } ++ ++ fn ack_features(&mut self, value: u64) { ++ self.common.ack_features(value) ++ } ++ ++ fn read_config(&self, offset: u64, mut data: &mut [u8]) { ++ if let Some(vu) = &self.vu_common.vu { ++ if let Err(e) = vu ++ .lock() ++ .unwrap() ++ .socket_handle() ++ .get_config( ++ offset as u32, ++ data.len() as u32, ++ VhostUserConfigFlags::WRITABLE, ++ data, ++ ) ++ .map_err(|e| format!("{:?}", e)) ++ .and_then(|(_, config)| data.write_all(&config).map_err(|e| format!("{:?}", e))) ++ { ++ error!("Failed getting vhost-user-gpu configuration: {:?}", e); ++ } ++ } ++ } ++ ++ fn activate( ++ &mut self, ++ mem: GuestMemoryAtomic, ++ interrupt_cb: Arc, ++ queues: Vec<(usize, Queue, EventFd)>, ++ ) -> ActivateResult { ++ self.common.activate(&queues, &interrupt_cb)?; ++ self.guest_memory = Some(mem.clone()); ++ ++ // Initialize slave communication. ++ let slave_req_handler = if self.slave_req_support { ++ if let Some(cache) = self.cache.as_ref() { ++ let vu_master_req_handler = Arc::new(SlaveReqHandler { ++ cache_size: cache.0.len, ++ mmap_cache_addr: cache.0.host_addr, ++ }); ++ ++ let mut req_handler = ++ MasterReqHandler::new(vu_master_req_handler).map_err(|e| { ++ ActivateError::VhostUserGpuSetup(Error::MasterReqHandlerCreation(e)) ++ })?; ++ ++ if self.vu_common.acked_protocol_features ++ & VhostUserProtocolFeatures::REPLY_ACK.bits() ++ != 0 ++ { ++ req_handler.set_reply_ack_flag(true); ++ } ++ ++ Some(req_handler) ++ } else { ++ None ++ } ++ } else { ++ None ++ }; ++ ++ // Run a dedicated thread for handling potential reconnections with ++ // the backend. ++ let (kill_evt, pause_evt) = self.common.dup_eventfds(); ++ ++ let mut handler = self.vu_common.activate( ++ mem, ++ queues, ++ interrupt_cb, ++ self.common.acked_features, ++ slave_req_handler, ++ kill_evt, ++ pause_evt, ++ )?; ++ ++ let paused = self.common.paused.clone(); ++ let paused_sync = self.common.paused_sync.clone(); ++ ++ let mut epoll_threads = Vec::new(); ++ spawn_virtio_thread( ++ &self.id, ++ &self.seccomp_action, ++ Thread::VirtioVhostGpu, ++ &mut epoll_threads, ++ &self.exit_evt, ++ move || handler.run(paused, paused_sync.unwrap()), ++ )?; ++ self.epoll_thread = Some(epoll_threads.remove(0)); ++ ++ event!("virtio-device", "activated", "id", &self.id); ++ Ok(()) ++ } ++ ++ fn reset(&mut self) -> Option> { ++ // We first must resume the virtio thread if it was paused. ++ if self.common.pause_evt.take().is_some() { ++ self.common.resume().ok()?; ++ } ++ ++ if let Some(vu) = &self.vu_common.vu { ++ if let Err(e) = vu.lock().unwrap().reset_vhost_user() { ++ error!("Failed to reset vhost-user daemon: {:?}", e); ++ return None; ++ } ++ } ++ ++ if let Some(kill_evt) = self.common.kill_evt.take() { ++ // Ignore the result because there is nothing we can do about it. ++ let _ = kill_evt.write(1); ++ } ++ ++ event!("virtio-device", "reset", "id", &self.id); ++ ++ // Return the interrupt ++ Some(self.common.interrupt_cb.take().unwrap()) ++ } ++ ++ fn shutdown(&mut self) { ++ self.vu_common.shutdown() ++ } ++ ++ fn get_shm_regions(&self) -> Option { ++ // It would be possible to get the size of the region from the ++ // backend over vhost-user, but since we need to know the size ++ // up front in cloud-hypervisor to construct Self, it wouldn't ++ // help. The user is thereforce responsible for configuring ++ // the correct region size in VM configuration. ++ self.cache.as_ref().map(|cache| cache.0.clone()) ++ } ++ ++ fn set_shm_regions( ++ &mut self, ++ shm_regions: VirtioSharedMemoryList, ++ ) -> std::result::Result<(), crate::Error> { ++ if let Some(cache) = self.cache.as_mut() { ++ cache.0 = shm_regions; ++ Ok(()) ++ } else { ++ Err(crate::Error::SetShmRegionsNotSupported) ++ } ++ } ++ ++ fn add_memory_region( ++ &mut self, ++ region: &Arc, ++ ) -> std::result::Result<(), crate::Error> { ++ self.vu_common.add_memory_region(&self.guest_memory, region) ++ } ++ ++ fn userspace_mappings(&self) -> Vec { ++ let mut mappings = Vec::new(); ++ if let Some(cache) = self.cache.as_ref() { ++ mappings.push(UserspaceMapping { ++ host_addr: cache.0.host_addr, ++ mem_slot: cache.0.mem_slot, ++ addr: cache.0.addr, ++ len: cache.0.len, ++ mergeable: false, ++ }) ++ } ++ ++ mappings ++ } ++} ++ ++impl Pausable for Gpu { ++ fn pause(&mut self) -> result::Result<(), MigratableError> { ++ self.vu_common.pause()?; ++ self.common.pause() ++ } ++ ++ fn resume(&mut self) -> result::Result<(), MigratableError> { ++ self.common.resume()?; ++ ++ if let Some(epoll_thread) = &self.epoll_thread { ++ epoll_thread.thread().unpark(); ++ } ++ ++ self.vu_common.resume() ++ } ++} +diff --git a/virtio-devices/src/vhost_user/mod.rs b/virtio-devices/src/vhost_user/mod.rs +index 2dee7324..d1b1c4e6 100644 +--- a/virtio-devices/src/vhost_user/mod.rs ++++ b/virtio-devices/src/vhost_user/mod.rs +@@ -31,11 +31,13 @@ use vu_common_ctrl::VhostUserHandle; + + pub mod blk; + pub mod fs; ++pub mod gpu; + pub mod net; + pub mod vu_common_ctrl; + + pub use self::blk::Blk; + pub use self::fs::*; ++pub use self::gpu::*; + pub use self::net::Net; + pub use self::vu_common_ctrl::VhostUserConfig; + +diff --git a/vmm/Cargo.toml b/vmm/Cargo.toml +index e256c90f..b3a374ab 100644 +--- a/vmm/Cargo.toml ++++ b/vmm/Cargo.toml +@@ -52,6 +52,7 @@ versionize = "0.1.10" + versionize_derive = "0.1.4" + vfio-ioctls = { git = "https://github.com/rust-vmm/vfio", branch = "main", default-features = false } + vfio_user = { git = "https://github.com/rust-vmm/vfio-user", branch = "main" } ++virtio-bindings = "0.2.0" + virtio-devices = { path = "../virtio-devices" } + virtio-queue = "0.9.0" + vm-allocator = { path = "../vm-allocator" } +diff --git a/vmm/src/api/mod.rs b/vmm/src/api/mod.rs +index 8aac5d3c..041a6c41 100644 +--- a/vmm/src/api/mod.rs ++++ b/vmm/src/api/mod.rs +@@ -38,8 +38,8 @@ pub use self::http::start_http_fd_thread; + pub use self::http::start_http_path_thread; + + use crate::config::{ +- DeviceConfig, DiskConfig, FsConfig, NetConfig, PmemConfig, RestoreConfig, UserDeviceConfig, +- VdpaConfig, VmConfig, VsockConfig, ++ DeviceConfig, DiskConfig, FsConfig, GpuConfig, NetConfig, PmemConfig, RestoreConfig, ++ UserDeviceConfig, VdpaConfig, VmConfig, VsockConfig, + }; + use crate::device_tree::DeviceTree; + use crate::vm::{Error as VmError, VmState}; +@@ -135,6 +135,9 @@ pub enum ApiError { + /// The fs could not be added to the VM. + VmAddFs(VmError), + ++ /// The gpu could not be added to the VM. ++ VmAddGpu(VmError), ++ + /// The pmem device could not be added to the VM. + VmAddPmem(VmError), + +@@ -305,6 +308,9 @@ pub enum ApiRequest { + /// Add a fs to the VM. + VmAddFs(Arc, Sender), + ++ /// Add a gpu to the VM. ++ VmAddGpu(Arc, Sender), ++ + /// Add a pmem device to the VM. + VmAddPmem(Arc, Sender), + +diff --git a/vmm/src/config.rs b/vmm/src/config.rs +index a6836fc6..4fb0f543 100644 +--- a/vmm/src/config.rs ++++ b/vmm/src/config.rs +@@ -26,6 +26,8 @@ pub enum Error { + ParseFsTagMissing, + /// Filesystem socket is missing + ParseFsSockMissing, ++ /// GPU socket is missing ++ ParseGpuSockMissing, + /// Missing persistent memory file parameter. + ParsePmemFileMissing, + /// Missing vsock socket path parameter. +@@ -54,6 +56,8 @@ pub enum Error { + ParseBalloon(OptionParserError), + /// Error parsing filesystem parameters + ParseFileSystem(OptionParserError), ++ /// Error parsing GPU parameters ++ ParseGpu(OptionParserError), + /// Error parsing persistent memory parameters + ParsePersistentMemory(OptionParserError), + /// Failed parsing console +@@ -304,6 +308,8 @@ impl fmt::Display for Error { + ParseFileSystem(o) => write!(f, "Error parsing --fs: {o}"), + ParseFsSockMissing => write!(f, "Error parsing --fs: socket missing"), + ParseFsTagMissing => write!(f, "Error parsing --fs: tag missing"), ++ ParseGpu(o) => write!(f, "Error parsing --gpu: {o}"), ++ ParseGpuSockMissing => write!(f, "Error parsing --gpu: socket missing"), + ParsePersistentMemory(o) => write!(f, "Error parsing --pmem: {o}"), + ParsePmemFileMissing => write!(f, "Error parsing --pmem: file missing"), + ParseVsock(o) => write!(f, "Error parsing --vsock: {o}"), +@@ -368,6 +374,7 @@ pub struct VmParams<'a> { + pub rng: &'a str, + pub balloon: Option<&'a str>, + pub fs: Option>, ++ pub gpu: Option>, + pub pmem: Option>, + pub serial: &'a str, + pub console: &'a str, +@@ -1272,6 +1279,56 @@ impl FsConfig { + } + } + ++impl GpuConfig { ++ pub fn parse(gpu: &str) -> Result { ++ let mut parser = OptionParser::new(); ++ parser ++ .add("socket") ++ .add("cache_size") ++ .add("id") ++ .add("pci_segment"); ++ parser.parse(gpu).map_err(Error::ParseGpu)?; ++ ++ let socket = PathBuf::from(parser.get("socket").ok_or(Error::ParseGpuSockMissing)?); ++ let cache_size = parser ++ .convert::("cache_size") ++ .map_err(Error::ParseGpu)? ++ .unwrap_or_else(|| ByteSized(default_gpuconfig_cache_size())) ++ .0; ++ let id = parser.get("id"); ++ ++ let pci_segment = parser ++ .convert("pci_segment") ++ .map_err(Error::ParseGpu)? ++ .unwrap_or_default(); ++ ++ Ok(GpuConfig { ++ socket, ++ cache_size, ++ id, ++ pci_segment, ++ }) ++ } ++ ++ pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { ++ if let Some(platform_config) = vm_config.platform.as_ref() { ++ if self.pci_segment >= platform_config.num_pci_segments { ++ return Err(ValidationError::InvalidPciSegment(self.pci_segment)); ++ } ++ ++ if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() { ++ if iommu_segments.contains(&self.pci_segment) { ++ return Err(ValidationError::IommuNotSupportedOnSegment( ++ self.pci_segment, ++ )); ++ } ++ } ++ } ++ ++ Ok(()) ++ } ++} ++ + impl PmemConfig { + pub fn parse(pmem: &str) -> Result { + let mut parser = OptionParser::new(); +@@ -1805,6 +1862,17 @@ impl VmConfig { + } + } + ++ if let Some(gpus) = &self.gpu { ++ if !gpus.is_empty() && !self.memory.shared { ++ return Err(ValidationError::VhostUserRequiresSharedMemory); ++ } ++ for gpu in gpus { ++ gpu.validate(self)?; ++ ++ Self::validate_identifier(&mut id_list, &gpu.id)?; ++ } ++ } ++ + if let Some(pmems) = &self.pmem { + for pmem in pmems { + pmem.validate(self)?; +@@ -1990,6 +2058,15 @@ impl VmConfig { + fs = Some(fs_config_list); + } + ++ let mut gpu: Option> = None; ++ if let Some(gpu_list) = &vm_params.gpu { ++ let mut gpu_config_list = Vec::new(); ++ for item in gpu_list.iter() { ++ gpu_config_list.push(GpuConfig::parse(item)?); ++ } ++ gpu = Some(gpu_config_list); ++ } ++ + let mut pmem: Option> = None; + if let Some(pmem_list) = &vm_params.pmem { + let mut pmem_config_list = Vec::new(); +@@ -2096,6 +2173,7 @@ impl VmConfig { + rng, + balloon, + fs, ++ gpu, + pmem, + serial, + console, +@@ -2150,6 +2228,13 @@ impl VmConfig { + removed |= fs.len() != len; + } + ++ // Remove if gpu device ++ if let Some(gpu) = self.gpu.as_mut() { ++ let len = gpu.len(); ++ gpu.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id)); ++ removed |= gpu.len() != len; ++ } ++ + // Remove if net device + if let Some(net) = self.net.as_mut() { + let len = net.len(); +@@ -2219,6 +2304,7 @@ impl Clone for VmConfig { + rng: self.rng.clone(), + balloon: self.balloon.clone(), + fs: self.fs.clone(), ++ gpu: self.gpu.clone(), + pmem: self.pmem.clone(), + serial: self.serial.clone(), + console: self.console.clone(), +@@ -2613,6 +2699,21 @@ mod tests { + Ok(()) + } + ++ #[test] ++ fn test_parse_gpu() -> Result<()> { ++ // "socket" must be supplied ++ assert!(GpuConfig::parse("").is_err()); ++ assert_eq!( ++ GpuConfig::parse("socket=/tmp/sock")?, ++ GpuConfig { ++ socket: PathBuf::from("/tmp/sock"), ++ ..Default::default() ++ } ++ ); ++ ++ Ok(()) ++ } ++ + #[test] + fn test_pmem_parsing() -> Result<()> { + // Must always give a file and size +@@ -2847,6 +2948,7 @@ mod tests { + }, + balloon: None, + fs: None, ++ gpu: None, + pmem: None, + serial: ConsoleConfig { + file: None, +@@ -3012,6 +3114,15 @@ mod tests { + Err(ValidationError::VhostUserRequiresSharedMemory) + ); + ++ let mut invalid_config = valid_config.clone(); ++ invalid_config.gpu = Some(vec![GpuConfig { ++ ..Default::default() ++ }]); ++ assert_eq!( ++ invalid_config.validate(), ++ Err(ValidationError::VhostUserRequiresSharedMemory) ++ ); ++ + let mut still_valid_config = valid_config.clone(); + still_valid_config.memory.shared = true; + assert!(still_valid_config.validate().is_ok()); +diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs +index 1f53390c..2d974480 100644 +--- a/vmm/src/device_manager.rs ++++ b/vmm/src/device_manager.rs +@@ -10,8 +10,8 @@ + // + + use crate::config::{ +- ConsoleOutputMode, DeviceConfig, DiskConfig, FsConfig, NetConfig, PmemConfig, UserDeviceConfig, +- VdpaConfig, VhostMode, VmConfig, VsockConfig, ++ ConsoleOutputMode, DeviceConfig, DiskConfig, FsConfig, GpuConfig, NetConfig, PmemConfig, ++ UserDeviceConfig, VdpaConfig, VhostMode, VmConfig, VsockConfig, + }; + use crate::cpu::{CpuManager, CPU_MANAGER_ACPI_SIZE}; + use crate::device_tree::{DeviceNode, DeviceTree}; +@@ -66,6 +66,7 @@ use serde::{Deserialize, Serialize}; + use std::collections::{BTreeSet, HashMap}; + use std::fs::{read_link, File, OpenOptions}; + use std::io::{self, stdout, Seek, SeekFrom}; ++use std::iter::once; + use std::mem::zeroed; + use std::num::Wrapping; + use std::os::unix::fs::OpenOptionsExt; +@@ -76,11 +77,13 @@ use std::sync::{Arc, Mutex}; + use std::time::Instant; + use tracer::trace_scoped; + use vfio_ioctls::{VfioContainer, VfioDevice, VfioDeviceFd}; ++use virtio_bindings::virtio_gpu::virtio_gpu_shm_id_VIRTIO_GPU_SHM_ID_HOST_VISIBLE as VIRTIO_GPU_SHM_ID_HOST_VISIBLE; + use virtio_devices::transport::VirtioTransport; + use virtio_devices::transport::{VirtioPciDevice, VirtioPciDeviceActivator}; + use virtio_devices::vhost_user::VhostUserConfig; + use virtio_devices::{ + AccessPlatformMapping, ActivateError, VdpaDmaMapping, VirtioMemMappingSource, ++ VirtioSharedMemory, VirtioSharedMemoryList, + }; + use virtio_devices::{Endpoint, IommuMapping}; + use vm_allocator::{AddressAllocator, SystemAllocator}; +@@ -122,6 +125,7 @@ const PVPANIC_DEVICE_NAME: &str = "__pvpanic"; + // identifiers if the user doesn't give one + const DISK_DEVICE_NAME_PREFIX: &str = "_disk"; + const FS_DEVICE_NAME_PREFIX: &str = "_fs"; ++const GPU_DEVICE_NAME_PREFIX: &str = "_gpu"; + const NET_DEVICE_NAME_PREFIX: &str = "_net"; + const PMEM_DEVICE_NAME_PREFIX: &str = "_pmem"; + const VDPA_DEVICE_NAME_PREFIX: &str = "_vdpa"; +@@ -158,9 +162,15 @@ pub enum DeviceManagerError { + /// Cannot create virtio-fs device + CreateVirtioFs(virtio_devices::vhost_user::Error), + ++ /// Cannot create virtio-gpu device ++ CreateVirtioGpu(virtio_devices::vhost_user::Error), ++ + /// Virtio-fs device was created without a socket. + NoVirtioFsSock, + ++ /// Virtio-gpu device was created without a socket. ++ NoVirtioGpuSock, ++ + /// Cannot create vhost-user-blk device + CreateVhostUserBlk(virtio_devices::vhost_user::Error), + +@@ -245,6 +255,9 @@ pub enum DeviceManagerError { + /// Cannot find a memory range for virtio-fs + FsRangeAllocation, + ++ /// Cannot find a memory range for virtio-gpu ++ GpuRangeAllocation, ++ + /// Error creating serial output file + SerialOutputFileOpen(io::Error), + +@@ -2140,6 +2153,9 @@ impl DeviceManager { + // Add virtio-fs if required + devices.append(&mut self.make_virtio_fs_devices()?); + ++ // Add virtio-gpu if required ++ devices.append(&mut self.make_virtio_gpu_devices()?); ++ + // Add virtio-pmem if required + devices.append(&mut self.make_virtio_pmem_devices()?); + +@@ -2655,6 +2671,118 @@ impl DeviceManager { + Ok(devices) + } + ++ fn make_virtio_gpu_device( ++ &mut self, ++ gpu_cfg: &mut GpuConfig, ++ ) -> DeviceManagerResult { ++ let id = if let Some(id) = &gpu_cfg.id { ++ id.clone() ++ } else { ++ let id = self.next_device_name(GPU_DEVICE_NAME_PREFIX)?; ++ gpu_cfg.id = Some(id.clone()); ++ id ++ }; ++ ++ info!("Creating virtio-gpu device: {:?}", gpu_cfg); ++ ++ let mut node = device_node!(id); ++ ++ if let Some(gpu_socket) = gpu_cfg.socket.to_str() { ++ let cache_size = gpu_cfg.cache_size; ++ // In crosvm, the 8 GiB bar is 8 GiB-aligned. ++ let cache_base = self.pci_segments[gpu_cfg.pci_segment as usize] ++ .allocator ++ .lock() ++ .unwrap() ++ .allocate(None, cache_size as GuestUsize, Some(cache_size)) ++ .ok_or(DeviceManagerError::GpuRangeAllocation)? ++ .raw_value(); ++ ++ // Update the node with correct resource information. ++ node.resources.push(Resource::MmioAddressRange { ++ base: cache_base, ++ size: cache_size, ++ }); ++ ++ let mmap_region = MmapRegion::build( ++ None, ++ cache_size as usize, ++ libc::PROT_NONE, ++ libc::MAP_ANONYMOUS | libc::MAP_PRIVATE, ++ ) ++ .map_err(DeviceManagerError::NewMmapRegion)?; ++ let host_addr: u64 = mmap_region.as_ptr() as u64; ++ ++ let mem_slot = self ++ .memory_manager ++ .lock() ++ .unwrap() ++ .create_userspace_mapping(cache_base, cache_size, host_addr, false, false, false) ++ .map_err(DeviceManagerError::MemoryManager)?; ++ ++ let region_list = once(( ++ VIRTIO_GPU_SHM_ID_HOST_VISIBLE as u8, ++ VirtioSharedMemory { ++ offset: 0, ++ len: cache_size, ++ }, ++ )) ++ .collect(); ++ ++ let cache = Some(( ++ VirtioSharedMemoryList { ++ host_addr, ++ mem_slot, ++ addr: GuestAddress(cache_base), ++ len: cache_size as GuestUsize, ++ region_list, ++ }, ++ mmap_region, ++ )); ++ ++ let virtio_gpu_device = Arc::new(Mutex::new( ++ virtio_devices::vhost_user::Gpu::new( ++ id.clone(), ++ gpu_socket, ++ cache, ++ self.seccomp_action.clone(), ++ self.exit_evt ++ .try_clone() ++ .map_err(DeviceManagerError::EventFd)?, ++ self.force_iommu, ++ ) ++ .map_err(DeviceManagerError::CreateVirtioGpu)?, ++ )); ++ ++ self.device_tree.lock().unwrap().insert(id.clone(), node); ++ ++ Ok(MetaVirtioDevice { ++ virtio_device: Arc::clone(&virtio_gpu_device) ++ as Arc>, ++ iommu: false, ++ id, ++ pci_segment: gpu_cfg.pci_segment, ++ dma_handler: None, ++ }) ++ } else { ++ Err(DeviceManagerError::NoVirtioGpuSock) ++ } ++ } ++ ++ fn make_virtio_gpu_devices(&mut self) -> DeviceManagerResult> { ++ let mut devices = Vec::new(); ++ ++ let mut gpu_devices = self.config.lock().unwrap().gpu.clone(); ++ if let Some(gpu_list_cfg) = &mut gpu_devices { ++ for gpu_cfg in gpu_list_cfg.iter_mut() { ++ devices.push(self.make_virtio_gpu_device(gpu_cfg)?); ++ } ++ } ++ self.config.lock().unwrap().gpu = gpu_devices; ++ ++ Ok(devices) ++ } ++ + fn make_virtio_pmem_device( + &mut self, + pmem_cfg: &mut PmemConfig, +@@ -3914,6 +4042,7 @@ impl DeviceManager { + | VirtioDeviceType::Block + | VirtioDeviceType::Pmem + | VirtioDeviceType::Fs ++ | VirtioDeviceType::Gpu + | VirtioDeviceType::Vsock => {} + _ => return Err(DeviceManagerError::RemovalNotAllowed(device_type)), + } +@@ -4182,6 +4311,13 @@ impl DeviceManager { + self.hotplug_virtio_pci_device(device) + } + ++ pub fn add_gpu(&mut self, gpu_cfg: &mut GpuConfig) -> DeviceManagerResult { ++ self.validate_identifier(&gpu_cfg.id)?; ++ ++ let device = self.make_virtio_gpu_device(gpu_cfg)?; ++ self.hotplug_virtio_pci_device(device) ++ } ++ + pub fn add_pmem(&mut self, pmem_cfg: &mut PmemConfig) -> DeviceManagerResult { + self.validate_identifier(&pmem_cfg.id)?; + +diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs +index 02ee347e..5abbdea5 100644 +--- a/vmm/src/lib.rs ++++ b/vmm/src/lib.rs +@@ -13,8 +13,8 @@ use crate::api::{ + VmSendMigrationData, VmmPingResponse, + }; + use crate::config::{ +- add_to_config, DeviceConfig, DiskConfig, FsConfig, NetConfig, PmemConfig, RestoreConfig, +- UserDeviceConfig, VdpaConfig, VmConfig, VsockConfig, ++ add_to_config, DeviceConfig, DiskConfig, FsConfig, GpuConfig, NetConfig, PmemConfig, ++ RestoreConfig, UserDeviceConfig, VdpaConfig, VmConfig, VsockConfig, + }; + #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] + use crate::coredump::GuestDebuggable; +@@ -25,8 +25,6 @@ use crate::migration::{recv_vm_config, recv_vm_state}; + use crate::seccomp_filters::{get_seccomp_filter, Thread}; + use crate::vm::{Error as VmError, Vm, VmState}; + use anyhow::anyhow; +-#[cfg(feature = "dbus_api")] +-use api::dbus::{DBusApiOptions, DBusApiShutdownChannels}; + use libc::{tcsetattr, termios, EFD_NONBLOCK, SIGINT, SIGTERM, TCSANOW}; + use memory_manager::MemoryManagerSnapshotData; + use pci::PciBdf; +@@ -1147,6 +1145,32 @@ impl Vmm { + } + } + ++ fn vm_add_gpu(&mut self, gpu_cfg: GpuConfig) -> result::Result>, VmError> { ++ self.vm_config.as_ref().ok_or(VmError::VmNotCreated)?; ++ ++ { ++ // Validate the configuration change in a cloned configuration ++ let mut config = self.vm_config.as_ref().unwrap().lock().unwrap().clone(); ++ add_to_config(&mut config.gpu, gpu_cfg.clone()); ++ config.validate().map_err(VmError::ConfigValidation)?; ++ } ++ ++ if let Some(ref mut vm) = self.vm { ++ let info = vm.add_gpu(gpu_cfg).map_err(|e| { ++ error!("Error when adding new gpu to the VM: {:?}", e); ++ e ++ })?; ++ serde_json::to_vec(&info) ++ .map(Some) ++ .map_err(VmError::SerializeJson) ++ } else { ++ // Update VmConfig by adding the new device. ++ let mut config = self.vm_config.as_ref().unwrap().lock().unwrap(); ++ add_to_config(&mut config.gpu, gpu_cfg); ++ Ok(None) ++ } ++ } ++ + fn vm_add_pmem(&mut self, pmem_cfg: PmemConfig) -> result::Result>, VmError> { + self.vm_config.as_ref().ok_or(VmError::VmNotCreated)?; + +@@ -2080,6 +2104,13 @@ impl Vmm { + .map(ApiResponsePayload::VmAction); + sender.send(response).map_err(Error::ApiResponseSend)?; + } ++ ApiRequest::VmAddGpu(add_gpu_data, sender) => { ++ let response = self ++ .vm_add_gpu(add_gpu_data.as_ref().clone()) ++ .map_err(ApiError::VmAddGpu) ++ .map(ApiResponsePayload::VmAction); ++ sender.send(response).map_err(Error::ApiResponseSend)?; ++ } + ApiRequest::VmAddPmem(add_pmem_data, sender) => { + let response = self + .vm_add_pmem(add_pmem_data.as_ref().clone()) +@@ -2245,6 +2276,7 @@ mod unit_tests { + }, + balloon: None, + fs: None, ++ gpu: None, + pmem: None, + serial: ConsoleConfig { + file: None, +@@ -2472,6 +2504,54 @@ mod unit_tests { + ); + } + ++ #[test] ++ fn test_vmm_vm_cold_add_gpu() { ++ let mut vmm = create_dummy_vmm(); ++ let gpu_config = GpuConfig::parse("socket=/tmp/sock").unwrap(); ++ ++ assert!(matches!( ++ vmm.vm_add_gpu(gpu_config.clone()), ++ Err(VmError::VmNotCreated) ++ )); ++ ++ let _ = vmm.vm_create(create_dummy_vm_config()); ++ assert!(vmm ++ .vm_config ++ .as_ref() ++ .unwrap() ++ .lock() ++ .unwrap() ++ .gpu ++ .is_none()); ++ ++ let result = vmm.vm_add_gpu(gpu_config.clone()); ++ assert!(result.is_ok()); ++ assert!(result.unwrap().is_none()); ++ assert_eq!( ++ vmm.vm_config ++ .as_ref() ++ .unwrap() ++ .lock() ++ .unwrap() ++ .gpu ++ .clone() ++ .unwrap() ++ .len(), ++ 1 ++ ); ++ assert_eq!( ++ vmm.vm_config ++ .as_ref() ++ .unwrap() ++ .lock() ++ .unwrap() ++ .gpu ++ .clone() ++ .unwrap()[0], ++ gpu_config ++ ); ++ } ++ + #[test] + fn test_vmm_vm_cold_add_pmem() { + let mut vmm = create_dummy_vmm(); +diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs +index 2b829df5..ff5a6905 100644 +--- a/vmm/src/vm.rs ++++ b/vmm/src/vm.rs +@@ -12,8 +12,8 @@ + // + + use crate::config::{ +- add_to_config, DeviceConfig, DiskConfig, FsConfig, HotplugMethod, NetConfig, PmemConfig, +- UserDeviceConfig, ValidationError, VdpaConfig, VmConfig, VsockConfig, ++ add_to_config, DeviceConfig, DiskConfig, FsConfig, GpuConfig, HotplugMethod, NetConfig, ++ PmemConfig, UserDeviceConfig, ValidationError, VdpaConfig, VmConfig, VsockConfig, + }; + use crate::config::{NumaConfig, PayloadConfig}; + #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] +@@ -1488,6 +1488,30 @@ impl Vm { + Ok(pci_device_info) + } + ++ pub fn add_gpu(&mut self, mut gpu_cfg: GpuConfig) -> Result { ++ let pci_device_info = self ++ .device_manager ++ .lock() ++ .unwrap() ++ .add_gpu(&mut gpu_cfg) ++ .map_err(Error::DeviceManager)?; ++ ++ // Update VmConfig by adding the new device. This is important to ++ // ensure the device would be created in case of a reboot. ++ { ++ let mut config = self.config.lock().unwrap(); ++ add_to_config(&mut config.gpu, gpu_cfg); ++ } ++ ++ self.device_manager ++ .lock() ++ .unwrap() ++ .notify_hotplug(AcpiNotificationFlags::PCI_DEVICES_CHANGED) ++ .map_err(Error::DeviceManager)?; ++ ++ Ok(pci_device_info) ++ } ++ + pub fn add_pmem(&mut self, mut pmem_cfg: PmemConfig) -> Result { + let pci_device_info = self + .device_manager +diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs +index 67ba0e12..79496a94 100644 +--- a/vmm/src/vm_config.rs ++++ b/vmm/src/vm_config.rs +@@ -419,6 +419,32 @@ impl Default for FsConfig { + } + } + ++#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] ++pub struct GpuConfig { ++ pub socket: PathBuf, ++ #[serde(default = "default_gpuconfig_cache_size")] ++ pub cache_size: u64, ++ #[serde(default)] ++ pub id: Option, ++ #[serde(default)] ++ pub pci_segment: u16, ++} ++ ++impl Default for GpuConfig { ++ fn default() -> Self { ++ Self { ++ socket: PathBuf::new(), ++ cache_size: default_gpuconfig_cache_size(), ++ id: None, ++ pci_segment: 0, ++ } ++ } ++} ++ ++pub fn default_gpuconfig_cache_size() -> u64 { ++ 1 << 33 ++} ++ + #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)] + pub struct PmemConfig { + pub file: PathBuf, +@@ -584,6 +610,7 @@ pub struct VmConfig { + pub rng: RngConfig, + pub balloon: Option, + pub fs: Option>, ++ pub gpu: Option>, + pub pmem: Option>, + #[serde(default = "default_serial")] + pub serial: ConsoleConfig, +-- +2.42.0 + diff --git a/pkgs/applications/virtualization/cloud-hypervisor/Cargo.lock b/pkgs/applications/virtualization/cloud-hypervisor/Cargo.lock index 346e3f45f63..e1a1ad02c48 100644 --- a/pkgs/applications/virtualization/cloud-hypervisor/Cargo.lock +++ b/pkgs/applications/virtualization/cloud-hypervisor/Cargo.lock @@ -2160,8 +2160,6 @@ dependencies = [ [[package]] name = "vhost" version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61957aeb36daf0b00b87fff9c10dd28a161bd35ab157553d340d183b3d8756e6" dependencies = [ "bitflags 1.3.2", "libc", @@ -2172,8 +2170,6 @@ dependencies = [ [[package]] name = "vhost-user-backend" version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab069cdedaf18a0673766eb0a07a0f4ee3ed1b8e17fbfe4aafe5b988e2de1d01" dependencies = [ "libc", "log", @@ -2374,6 +2370,7 @@ dependencies = [ "versionize_derive", "vfio-ioctls", "vfio_user", + "virtio-bindings", "virtio-devices", "virtio-queue", "vm-allocator", diff --git a/pkgs/applications/virtualization/cloud-hypervisor/default.nix b/pkgs/applications/virtualization/cloud-hypervisor/default.nix index d7182b81310..9831aced69b 100644 --- a/pkgs/applications/virtualization/cloud-hypervisor/default.nix +++ b/pkgs/applications/virtualization/cloud-hypervisor/default.nix @@ -28,6 +28,39 @@ rustPlatform.buildRustPackage rec { separateDebugInfo = true; + vhost = fetchFromGitHub { + name = "vhost"; + owner = "rust-vmm"; + repo = "vhost"; + rev = "vhost-user-backend-v0.10.1"; + hash = "sha256-pq545s7sqE0GFFkEkAvKwFKLuRArNThmRFqEYS3nNVo="; + }; + + postUnpack = '' + unpackFile ${vhost} + chmod -R +w vhost + ''; + + cargoPatches = [ + ./0001-build-use-local-vhost.patch + ./0002-virtio-devices-add-a-GPU-device.patch + ]; + + vhostPatches = [ + vhost/0001-vhost-fix-receiving-reply-payloads.patch + vhost/0002-vhost_user-add-shared-memory-region-support.patch + vhost/0003-vhost-user-add-protocol-flag-for-shmem.patch + ]; + + postPatch = '' + pushd ../vhost + for patch in $vhostPatches; do + echo applying patch $patch + patch -p1 < $patch + done + popd + ''; + nativeBuildInputs = [ pkg-config ]; buildInputs = [ openssl ] ++ lib.optional stdenv.isAarch64 dtc; diff --git a/pkgs/applications/virtualization/cloud-hypervisor/vhost/0001-vhost-fix-receiving-reply-payloads.patch b/pkgs/applications/virtualization/cloud-hypervisor/vhost/0001-vhost-fix-receiving-reply-payloads.patch new file mode 100644 index 00000000000..1b013b922d6 --- /dev/null +++ b/pkgs/applications/virtualization/cloud-hypervisor/vhost/0001-vhost-fix-receiving-reply-payloads.patch @@ -0,0 +1,113 @@ +From a1434fa3cb740ecf675c56db746f5098e6267a12 Mon Sep 17 00:00:00 2001 +From: David Stevens +Date: Wed, 15 Jun 2022 15:56:18 +0900 +Subject: [PATCH 1/3] vhost: fix receiving reply payloads + +The existing code confuses the length of the request with the length of +the reply in recv_reply_with_payload. This makes it impossible to use +for any requests where the reply differs in size. Fix this by +determining payload size after reading the reply header. + +(cherry-picked from crosvm commit 31f04e92709980a4ffc56b1631f8b4be437cc2fe) + +Co-authored-by: Alyssa Ross +Signed-off-by: Alyssa Ross +--- + crates/vhost/src/vhost_user/connection.rs | 29 ++++++++++------------- + crates/vhost/src/vhost_user/master.rs | 16 +++---------- + 2 files changed, 15 insertions(+), 30 deletions(-) + +diff --git a/crates/vhost/src/vhost_user/connection.rs b/crates/vhost/src/vhost_user/connection.rs +index 4a62e12..3c308a8 100644 +--- a/crates/vhost/src/vhost_user/connection.rs ++++ b/crates/vhost/src/vhost_user/connection.rs +@@ -551,7 +551,7 @@ impl Endpoint { + /// accepted and all other file descriptor will be discard silently. + /// + /// # Return: +- /// * - (message header, message body, size of payload, [received files]) on success. ++ /// * - (message header, message body, payload, [received files]) on success. + /// * - SocketRetry: temporary error caused by signals or short of resources. + /// * - SocketBroken: the underline socket is broken. + /// * - SocketError: other socket related errors. +@@ -560,15 +560,13 @@ impl Endpoint { + #[cfg_attr(feature = "cargo-clippy", allow(clippy::type_complexity))] + pub fn recv_payload_into_buf( + &mut self, +- buf: &mut [u8], +- ) -> Result<(VhostUserMsgHeader, T, usize, Option>)> { +- let mut hdr = VhostUserMsgHeader::default(); ++ ) -> Result<(VhostUserMsgHeader, T, Vec, Option>)> { + let mut body: T = Default::default(); ++ let (hdr, files) = self.recv_header()?; ++ ++ let payload_size = hdr.get_size() as usize - mem::size_of::(); ++ let mut buf: Vec = vec![0; payload_size]; + let mut iovs = [ +- iovec { +- iov_base: (&mut hdr as *mut VhostUserMsgHeader) as *mut c_void, +- iov_len: mem::size_of::>(), +- }, + iovec { + iov_base: (&mut body as *mut T) as *mut c_void, + iov_len: mem::size_of::(), +@@ -578,19 +576,16 @@ impl Endpoint { + iov_len: buf.len(), + }, + ]; +- // SAFETY: Safe because we own hdr and body and have a mutable borrow of buf, and +- // hdr and body are ByteValued, and it's safe to fill a byte slice with +- // arbitrary data. +- let (bytes, files) = unsafe { self.recv_into_iovec_all(&mut iovs[..])? }; +- +- let total = mem::size_of::>() + mem::size_of::(); +- if bytes < total { ++ // SAFETY: Safe because we own body and buf, and body is ByteValued, and it's safe ++ // to fill a byte slice with arbitrary data. ++ let (bytes, more_files) = unsafe { self.recv_into_iovec_all(&mut iovs)? }; ++ if bytes < hdr.get_size() as usize { + return Err(Error::PartialMessage); +- } else if !hdr.is_valid() || !body.is_valid() { ++ } else if !body.is_valid() || more_files.is_some() { + return Err(Error::InvalidMessage); + } + +- Ok((hdr, body, bytes - total, files)) ++ Ok((hdr, body, buf, files)) + } + } + +diff --git a/crates/vhost/src/vhost_user/master.rs b/crates/vhost/src/vhost_user/master.rs +index feeb984..89aec20 100644 +--- a/crates/vhost/src/vhost_user/master.rs ++++ b/crates/vhost/src/vhost_user/master.rs +@@ -673,23 +673,13 @@ impl MasterInternal { + &mut self, + hdr: &VhostUserMsgHeader, + ) -> VhostUserResult<(T, Vec, Option>)> { +- if mem::size_of::() > MAX_MSG_SIZE +- || hdr.get_size() as usize <= mem::size_of::() +- || hdr.get_size() as usize > MAX_MSG_SIZE +- || hdr.is_reply() +- { ++ if mem::size_of::() > MAX_MSG_SIZE || hdr.is_reply() { + return Err(VhostUserError::InvalidParam); + } + self.check_state()?; + +- let mut buf: Vec = vec![0; hdr.get_size() as usize - mem::size_of::()]; +- let (reply, body, bytes, files) = self.main_sock.recv_payload_into_buf::(&mut buf)?; +- if !reply.is_reply_for(hdr) +- || reply.get_size() as usize != mem::size_of::() + bytes +- || files.is_some() +- || !body.is_valid() +- || bytes != buf.len() +- { ++ let (reply, body, buf, files) = self.main_sock.recv_payload_into_buf::()?; ++ if !reply.is_reply_for(hdr) || files.is_some() || !body.is_valid() { + return Err(VhostUserError::InvalidMessage); + } + +-- +2.42.0 + diff --git a/pkgs/applications/virtualization/cloud-hypervisor/vhost/0002-vhost_user-add-shared-memory-region-support.patch b/pkgs/applications/virtualization/cloud-hypervisor/vhost/0002-vhost_user-add-shared-memory-region-support.patch new file mode 100644 index 00000000000..e4b7f96c030 --- /dev/null +++ b/pkgs/applications/virtualization/cloud-hypervisor/vhost/0002-vhost_user-add-shared-memory-region-support.patch @@ -0,0 +1,492 @@ +From e1f7130bfbe529a7fda1a598866e0dea41b6ba74 Mon Sep 17 00:00:00 2001 +From: David Stevens +Date: Wed, 15 Jun 2022 16:45:12 +0900 +Subject: [PATCH 2/3] vhost_user: add shared memory region support + +Add support for shared memory regions to vhost-user. This is adding +support for a front-end message to query for necessary shared memory +regions plus back-end message to support mapping/unmapping files from +the shared memory region. + +go/vvu-shared-memory + +BUG=b:201745804 +TEST=compiles + +Change-Id: I35c5d260ee09175b68f6778b81883e0070ee0265 +Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/3716344 +Reviewed-by: Keiichi Watanabe +Commit-Queue: David Stevens +Reviewed-by: Alexandre Courbot +Tested-by: kokoro +(cherry-picked from commit f436e2706011fa5f34dc415972434aa3299ebc43) +Signed-off-by: Alyssa Ross +--- + crates/vhost-user-backend/src/handler.rs | 10 +- + crates/vhost/src/vhost_user/dummy_slave.rs | 4 + + crates/vhost/src/vhost_user/master.rs | 25 ++++ + .../src/vhost_user/master_req_handler.rs | 57 ++++++-- + crates/vhost/src/vhost_user/message.rs | 136 +++++++++++++++++- + crates/vhost/src/vhost_user/slave_req.rs | 20 ++- + .../vhost/src/vhost_user/slave_req_handler.rs | 15 ++ + 7 files changed, 247 insertions(+), 20 deletions(-) + +diff --git a/crates/vhost-user-backend/src/handler.rs b/crates/vhost-user-backend/src/handler.rs +index 262bf6c..73402b2 100644 +--- a/crates/vhost-user-backend/src/handler.rs ++++ b/crates/vhost-user-backend/src/handler.rs +@@ -11,9 +11,9 @@ use std::sync::Arc; + use std::thread; + + use vhost::vhost_user::message::{ +- VhostUserConfigFlags, VhostUserMemoryRegion, VhostUserProtocolFeatures, +- VhostUserSingleMemoryRegion, VhostUserVirtioFeatures, VhostUserVringAddrFlags, +- VhostUserVringState, ++ VhostSharedMemoryRegion, VhostUserConfigFlags, VhostUserMemoryRegion, ++ VhostUserProtocolFeatures, VhostUserSingleMemoryRegion, VhostUserVirtioFeatures, ++ VhostUserVringAddrFlags, VhostUserVringState, + }; + use vhost::vhost_user::{ + Error as VhostUserError, Result as VhostUserResult, Slave, VhostUserSlaveReqHandlerMut, +@@ -602,6 +602,10 @@ where + + Ok(()) + } ++ ++ fn get_shared_memory_regions(&mut self) -> VhostUserResult> { ++ Ok(Vec::new()) ++ } + } + + impl Drop for VhostUserHandler { +diff --git a/crates/vhost/src/vhost_user/dummy_slave.rs b/crates/vhost/src/vhost_user/dummy_slave.rs +index ae728a0..00a1ae8 100644 +--- a/crates/vhost/src/vhost_user/dummy_slave.rs ++++ b/crates/vhost/src/vhost_user/dummy_slave.rs +@@ -291,4 +291,8 @@ impl VhostUserSlaveReqHandlerMut for DummySlaveReqHandler { + fn remove_mem_region(&mut self, _region: &VhostUserSingleMemoryRegion) -> Result<()> { + Ok(()) + } ++ ++ fn get_shared_memory_regions(&mut self) -> Result> { ++ Ok(Vec::new()) ++ } + } +diff --git a/crates/vhost/src/vhost_user/master.rs b/crates/vhost/src/vhost_user/master.rs +index 89aec20..6808cb0 100644 +--- a/crates/vhost/src/vhost_user/master.rs ++++ b/crates/vhost/src/vhost_user/master.rs +@@ -72,6 +72,9 @@ pub trait VhostUserMaster: VhostBackend { + + /// Remove a guest memory mapping from vhost. + fn remove_mem_region(&mut self, region: &VhostUserMemoryRegionInfo) -> Result<()>; ++ ++ /// Gets the shared memory regions used by the device. ++ fn get_shared_memory_regions(&self) -> Result>; + } + + fn error_code(err: VhostUserError) -> Result { +@@ -513,6 +516,28 @@ impl VhostUserMaster for Master { + let hdr = node.send_request_with_body(MasterReq::REM_MEM_REG, &body, None)?; + node.wait_for_ack(&hdr).map_err(|e| e.into()) + } ++ ++ fn get_shared_memory_regions(&self) -> Result> { ++ let mut node = self.node(); ++ let hdr = node.send_request_header(MasterReq::GET_SHARED_MEMORY_REGIONS, None)?; ++ let (body_reply, buf_reply, rfds) = node.recv_reply_with_payload::(&hdr)?; ++ let struct_size = mem::size_of::(); ++ if rfds.is_some() || buf_reply.len() != body_reply.value as usize * struct_size { ++ return error_code(VhostUserError::InvalidMessage); ++ } ++ let mut regions = Vec::new(); ++ let mut offset = 0; ++ for _ in 0..body_reply.value { ++ regions.push( ++ // Can't fail because the input is the correct size. ++ VhostSharedMemoryRegion::from_slice(&buf_reply[offset..(offset + struct_size)]) ++ .unwrap() ++ .clone(), ++ ); ++ offset += struct_size; ++ } ++ Ok(regions) ++ } + } + + impl AsRawFd for Master { +diff --git a/crates/vhost/src/vhost_user/master_req_handler.rs b/crates/vhost/src/vhost_user/master_req_handler.rs +index c9c528b..3d01542 100644 +--- a/crates/vhost/src/vhost_user/master_req_handler.rs ++++ b/crates/vhost/src/vhost_user/master_req_handler.rs +@@ -33,6 +33,16 @@ pub trait VhostUserMasterReqHandler { + Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) + } + ++ /// Handle shared memory region mapping requests. ++ fn shmem_map(&self, _req: &VhostUserShmemMapMsg, _fd: &dyn AsRawFd) -> HandlerResult { ++ Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) ++ } ++ ++ /// Handle shared memory region unmapping requests. ++ fn shmem_unmap(&self, _req: &VhostUserShmemUnmapMsg) -> HandlerResult { ++ Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) ++ } ++ + /// Handle virtio-fs map file requests. + fn fs_slave_map(&self, _fs: &VhostUserFSSlaveMsg, _fd: &dyn AsRawFd) -> HandlerResult { + Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) +@@ -66,6 +76,16 @@ pub trait VhostUserMasterReqHandlerMut { + Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) + } + ++ /// Handle shared memory region mapping requests. ++ fn shmem_map(&mut self, _req: &VhostUserShmemMapMsg, _fd: &dyn AsRawFd) -> HandlerResult { ++ Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) ++ } ++ ++ /// Handle shared memory region unmapping requests. ++ fn shmem_unmap(&mut self, _req: &VhostUserShmemUnmapMsg) -> HandlerResult { ++ Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) ++ } ++ + /// Handle virtio-fs map file requests. + fn fs_slave_map(&mut self, _fs: &VhostUserFSSlaveMsg, _fd: &dyn AsRawFd) -> HandlerResult { + Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) +@@ -95,6 +115,14 @@ impl VhostUserMasterReqHandler for Mutex { + self.lock().unwrap().handle_config_change() + } + ++ fn shmem_map(&self, req: &VhostUserShmemMapMsg, fd: &dyn AsRawFd) -> HandlerResult { ++ self.lock().unwrap().shmem_map(req, fd) ++ } ++ ++ fn shmem_unmap(&self, req: &VhostUserShmemUnmapMsg) -> HandlerResult { ++ self.lock().unwrap().shmem_unmap(req) ++ } ++ + fn fs_slave_map(&self, fs: &VhostUserFSSlaveMsg, fd: &dyn AsRawFd) -> HandlerResult { + self.lock().unwrap().fs_slave_map(fs, fd) + } +@@ -222,6 +250,19 @@ impl MasterReqHandler { + .handle_config_change() + .map_err(Error::ReqHandlerError) + } ++ Ok(SlaveReq::SHMEM_MAP) => { ++ let msg = self.extract_msg_body::(&hdr, size, &buf)?; ++ // check_attached_files() has validated files ++ self.backend ++ .shmem_map(&msg, &files.unwrap()[0]) ++ .map_err(Error::ReqHandlerError) ++ } ++ Ok(SlaveReq::SHMEM_UNMAP) => { ++ let msg = self.extract_msg_body::(&hdr, size, &buf)?; ++ self.backend ++ .shmem_unmap(&msg) ++ .map_err(Error::ReqHandlerError) ++ } + Ok(SlaveReq::FS_MAP) => { + let msg = self.extract_msg_body::(&hdr, size, &buf)?; + // check_attached_files() has validated files +@@ -251,7 +292,7 @@ impl MasterReqHandler { + _ => Err(Error::InvalidMessage), + }; + +- self.send_ack_message(&hdr, &res)?; ++ self.send_reply(&hdr, &res)?; + + res + } +@@ -285,7 +326,7 @@ impl MasterReqHandler { + files: &Option>, + ) -> Result<()> { + match hdr.get_code() { +- Ok(SlaveReq::FS_MAP | SlaveReq::FS_IO) => { ++ Ok(SlaveReq::SHMEM_MAP | SlaveReq::FS_MAP | SlaveReq::FS_IO) => { + // Expect a single file is passed. + match files { + Some(files) if files.len() == 1 => Ok(()), +@@ -327,12 +368,12 @@ impl MasterReqHandler { + )) + } + +- fn send_ack_message( +- &mut self, +- req: &VhostUserMsgHeader, +- res: &Result, +- ) -> Result<()> { +- if self.reply_ack_negotiated && req.is_need_reply() { ++ fn send_reply(&mut self, req: &VhostUserMsgHeader, res: &Result) -> Result<()> { ++ if matches!( ++ req.get_code(), ++ Ok(SlaveReq::SHMEM_MAP | SlaveReq::SHMEM_UNMAP) ++ ) || (self.reply_ack_negotiated && req.is_need_reply()) ++ { + let hdr = self.new_reply_header::(req)?; + let def_err = libc::EINVAL; + let val = match res { +diff --git a/crates/vhost/src/vhost_user/message.rs b/crates/vhost/src/vhost_user/message.rs +index bbd8eb9..be24992 100644 +--- a/crates/vhost/src/vhost_user/message.rs ++++ b/crates/vhost/src/vhost_user/message.rs +@@ -146,8 +146,10 @@ pub enum MasterReq { + /// Query the backend for its device status as defined in the VIRTIO + /// specification. + GET_STATUS = 40, ++ /// Get a list of the device's shared memory regions. ++ GET_SHARED_MEMORY_REGIONS = 41, + /// Upper bound of valid commands. +- MAX_CMD = 41, ++ MAX_CMD = 42, + } + + impl From for u32 { +@@ -178,16 +180,20 @@ pub enum SlaveReq { + VRING_CALL = 4, + /// Indicate that an error occurred on the specific vring. + VRING_ERR = 5, ++ /// Indicates a request to map a fd into a shared memory region. ++ SHMEM_MAP = 6, ++ /// Indicates a request to unmap part of a shared memory region. ++ SHMEM_UNMAP = 7, + /// Virtio-fs draft: map file content into the window. +- FS_MAP = 6, ++ FS_MAP = 8, + /// Virtio-fs draft: unmap file content from the window. +- FS_UNMAP = 7, ++ FS_UNMAP = 9, + /// Virtio-fs draft: sync file content. +- FS_SYNC = 8, ++ FS_SYNC = 10, + /// Virtio-fs draft: perform a read/write from an fd directly to GPA. +- FS_IO = 9, ++ FS_IO = 11, + /// Upper bound of valid commands. +- MAX_CMD = 10, ++ MAX_CMD = 12, + } + + impl From for u32 { +@@ -972,6 +978,99 @@ impl VhostUserMsgValidator for VhostUserFSSlaveMsg { + } + } + ++bitflags! { ++ #[derive(Default)] ++ /// Flags for SHMEM_MAP messages. ++ pub struct VhostUserShmemMapMsgFlags: u8 { ++ /// Empty permission. ++ const EMPTY = 0x0; ++ /// Read permission. ++ const MAP_R = 0x1; ++ /// Write permission. ++ const MAP_W = 0x2; ++ } ++} ++ ++/// Slave request message to map a file into a shared memory region. ++#[repr(C, packed)] ++#[derive(Default, Copy, Clone)] ++pub struct VhostUserShmemMapMsg { ++ /// Flags for the mmap operation ++ pub flags: VhostUserShmemMapMsgFlags, ++ /// Shared memory region id. ++ pub shmid: u8, ++ padding: [u8; 6], ++ /// Offset into the shared memory region. ++ pub shm_offset: u64, ++ /// File offset. ++ pub fd_offset: u64, ++ /// Size of region to map. ++ pub len: u64, ++} ++// Safe because it only has data and has no implicit padding. ++unsafe impl ByteValued for VhostUserShmemMapMsg {} ++ ++impl VhostUserMsgValidator for VhostUserShmemMapMsg { ++ fn is_valid(&self) -> bool { ++ (self.flags.bits() & !VhostUserFSSlaveMsgFlags::all().bits() as u8) == 0 ++ && self.fd_offset.checked_add(self.len).is_some() ++ && self.shm_offset.checked_add(self.len).is_some() ++ } ++} ++ ++impl VhostUserShmemMapMsg { ++ /// New instance of VhostUserShmemMapMsg struct ++ pub fn new( ++ shmid: u8, ++ shm_offset: u64, ++ fd_offset: u64, ++ len: u64, ++ flags: VhostUserShmemMapMsgFlags, ++ ) -> Self { ++ Self { ++ flags, ++ shmid, ++ padding: [0; 6], ++ shm_offset, ++ fd_offset, ++ len, ++ } ++ } ++} ++ ++/// Slave request message to unmap part of a shared memory region. ++#[repr(C, packed)] ++#[derive(Default, Copy, Clone)] ++pub struct VhostUserShmemUnmapMsg { ++ /// Shared memory region id. ++ pub shmid: u8, ++ padding: [u8; 7], ++ /// Offset into the shared memory region. ++ pub shm_offset: u64, ++ /// Size of region to unmap. ++ pub len: u64, ++} ++// Safe because it only has data and has no implicit padding. ++unsafe impl ByteValued for VhostUserShmemUnmapMsg {} ++ ++impl VhostUserMsgValidator for VhostUserShmemUnmapMsg { ++ fn is_valid(&self) -> bool { ++ self.shm_offset.checked_add(self.len).is_some() ++ } ++} ++ ++impl VhostUserShmemUnmapMsg { ++ /// New instance of VhostUserShmemUnmapMsg struct ++ pub fn new(shmid: u8, shm_offset: u64, len: u64) -> Self { ++ Self { ++ shmid, ++ padding: [0; 7], ++ shm_offset, ++ len, ++ } ++ } ++} ++ + /// Inflight I/O descriptor state for split virtqueues + #[repr(packed)] + #[derive(Clone, Copy, Default)] +@@ -1103,6 +1202,31 @@ impl QueueRegionPacked { + } + } + ++/// Virtio shared memory descriptor. ++#[repr(packed)] ++#[derive(Default, Copy, Clone)] ++pub struct VhostSharedMemoryRegion { ++ /// The shared memory region's shmid. ++ pub id: u8, ++ /// Padding ++ padding: [u8; 7], ++ /// The length of the shared memory region. ++ pub length: u64, ++} ++// Safe because it only has data and has no implicit padding. ++unsafe impl ByteValued for VhostSharedMemoryRegion {} ++ ++impl VhostSharedMemoryRegion { ++ /// New instance of VhostSharedMemoryRegion struct ++ pub fn new(id: u8, length: u64) -> Self { ++ VhostSharedMemoryRegion { ++ id, ++ padding: [0; 7], ++ length, ++ } ++ } ++} ++ + #[cfg(test)] + mod tests { + use super::*; +diff --git a/crates/vhost/src/vhost_user/slave_req.rs b/crates/vhost/src/vhost_user/slave_req.rs +index ade1e91..b7ecd20 100644 +--- a/crates/vhost/src/vhost_user/slave_req.rs ++++ b/crates/vhost/src/vhost_user/slave_req.rs +@@ -46,12 +46,16 @@ impl SlaveInternal { + } + self.sock.send_message(&hdr, body, fds)?; + +- self.wait_for_ack(&hdr) ++ self.wait_for_reply(&hdr) + } + +- fn wait_for_ack(&mut self, hdr: &VhostUserMsgHeader) -> Result { ++ fn wait_for_reply(&mut self, hdr: &VhostUserMsgHeader) -> Result { + self.check_state()?; +- if !self.reply_ack_negotiated { ++ if !matches!( ++ hdr.get_code(), ++ Ok(SlaveReq::SHMEM_MAP | SlaveReq::SHMEM_UNMAP) ++ ) && !self.reply_ack_negotiated ++ { + return Ok(0); + } + +@@ -129,6 +133,16 @@ impl Slave { + } + + impl VhostUserMasterReqHandler for Slave { ++ /// Handle shared memory region mapping requests. ++ fn shmem_map(&self, req: &VhostUserShmemMapMsg, fd: &dyn AsRawFd) -> HandlerResult { ++ self.send_message(SlaveReq::SHMEM_MAP, req, Some(&[fd.as_raw_fd()])) ++ } ++ ++ /// Handle shared memory region unmapping requests. ++ fn shmem_unmap(&self, req: &VhostUserShmemUnmapMsg) -> HandlerResult { ++ self.send_message(SlaveReq::SHMEM_UNMAP, req, None) ++ } ++ + /// Forward vhost-user-fs map file requests to the slave. + fn fs_slave_map(&self, fs: &VhostUserFSSlaveMsg, fd: &dyn AsRawFd) -> HandlerResult { + self.send_message(SlaveReq::FS_MAP, fs, Some(&[fd.as_raw_fd()])) +diff --git a/crates/vhost/src/vhost_user/slave_req_handler.rs b/crates/vhost/src/vhost_user/slave_req_handler.rs +index e998339..b84feca 100644 +--- a/crates/vhost/src/vhost_user/slave_req_handler.rs ++++ b/crates/vhost/src/vhost_user/slave_req_handler.rs +@@ -70,6 +70,7 @@ pub trait VhostUserSlaveReqHandler { + fn get_max_mem_slots(&self) -> Result; + fn add_mem_region(&self, region: &VhostUserSingleMemoryRegion, fd: File) -> Result<()>; + fn remove_mem_region(&self, region: &VhostUserSingleMemoryRegion) -> Result<()>; ++ fn get_shared_memory_regions(&self) -> Result>; + } + + /// Services provided to the master by the slave without interior mutability. +@@ -118,6 +119,7 @@ pub trait VhostUserSlaveReqHandlerMut { + fn get_max_mem_slots(&mut self) -> Result; + fn add_mem_region(&mut self, region: &VhostUserSingleMemoryRegion, fd: File) -> Result<()>; + fn remove_mem_region(&mut self, region: &VhostUserSingleMemoryRegion) -> Result<()>; ++ fn get_shared_memory_regions(&mut self) -> Result>; + } + + impl VhostUserSlaveReqHandler for Mutex { +@@ -226,6 +228,10 @@ impl VhostUserSlaveReqHandler for Mutex { + fn remove_mem_region(&self, region: &VhostUserSingleMemoryRegion) -> Result<()> { + self.lock().unwrap().remove_mem_region(region) + } ++ ++ fn get_shared_memory_regions(&self) -> Result> { ++ self.lock().unwrap().get_shared_memory_regions() ++ } + } + + /// Server to handle service requests from masters from the master communication channel. +@@ -519,6 +525,15 @@ impl SlaveReqHandler { + let res = self.backend.remove_mem_region(&msg); + self.send_ack_message(&hdr, res)?; + } ++ Ok(MasterReq::GET_SHARED_MEMORY_REGIONS) => { ++ let regions = self.backend.get_shared_memory_regions()?; ++ let mut buf = Vec::new(); ++ let msg = VhostUserU64::new(regions.len() as u64); ++ for r in regions { ++ buf.extend_from_slice(r.as_slice()) ++ } ++ self.send_reply_with_payload(&hdr, &msg, buf.as_slice())?; ++ } + _ => { + return Err(Error::InvalidMessage); + } +-- +2.42.0 + diff --git a/pkgs/applications/virtualization/cloud-hypervisor/vhost/0003-vhost-user-add-protocol-flag-for-shmem.patch b/pkgs/applications/virtualization/cloud-hypervisor/vhost/0003-vhost-user-add-protocol-flag-for-shmem.patch new file mode 100644 index 00000000000..f5e0e4e2b5f --- /dev/null +++ b/pkgs/applications/virtualization/cloud-hypervisor/vhost/0003-vhost-user-add-protocol-flag-for-shmem.patch @@ -0,0 +1,38 @@ +From 3a44f7aaad8dd8ce1c2c5b75a30326df1f043f22 Mon Sep 17 00:00:00 2001 +From: David Stevens +Date: Thu, 13 Oct 2022 10:37:47 +0900 +Subject: [PATCH 3/3] vhost-user: add protocol flag for shmem + +Add a vhost protocol feature flag for shared memory region support. This +is necessary to avoid sending the GET_SHARED_MEMORY_REGIONS message to +backends which don't support it. + +BUG=b:252901073 +TEST=crosvm device wl + +Change-Id: I044926e982526c3c76063b5386cab0db72524707 +Reviewed-on: https://chromium-review.googlesource.com/c/crosvm/crosvm/+/3951472 +Reviewed-by: Daniel Verkamp +Commit-Queue: David Stevens +(cherry-picked from commit 60aa43629ae9be2cc3df37c648ab7e0e5ff2172c) +Signed-off-by: Alyssa Ross +--- + crates/vhost/src/vhost_user/message.rs | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/crates/vhost/src/vhost_user/message.rs b/crates/vhost/src/vhost_user/message.rs +index be24992..5fb2fb8 100644 +--- a/crates/vhost/src/vhost_user/message.rs ++++ b/crates/vhost/src/vhost_user/message.rs +@@ -435,6 +435,8 @@ bitflags! { + const STATUS = 0x0001_0000; + /// Support Xen mmap. + const XEN_MMAP = 0x0002_0000; ++ /// Support shared memory regions. ++ const SHARED_MEMORY_REGIONS = 0x0002_0000; + } + } + +-- +2.42.0 + -- cgit 1.4.1