From 7aec033b6371aadba8924c317282821565cbba48 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Thu, 14 May 2026 15:54:02 +0200 Subject: [PATCH 1/3] virtio-devices: use map for Shared Memory Regions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SPDX-FileCopyrightText: The Cloud Hypervisor Authors SPDX-FileCopyrightText: 2018 The Chromium OS Authors. All rights reserved. SPDX-FileCopyrightText: 2019 Intel Corporation SPDX-License-Identifier: Apache-2.0 AND LicenseRef-BSD-3-Clause-Google A vhost-user backend can use whichever shared memory indices it likes — it might decide only to use index 200 — so we have to be able to handle the case where lower indices are not used. BTreeMap is used so that capabilities are added in a consistent order. Signed-off-by: Alyssa Ross Co-authored-by: Alyssa Ross Signed-off-by: Alyssa Ross --- virtio-devices/src/device.rs | 4 ++-- virtio-devices/src/transport/pci_device.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) REUSE-IgnoreStart diff --git a/virtio-devices/src/device.rs b/virtio-devices/src/device.rs index 4c61ba35d..8b4410173 100644 --- a/virtio-devices/src/device.rs +++ b/virtio-devices/src/device.rs @@ -6,7 +6,7 @@ // // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::io::Write; use std::num::Wrapping; use std::sync::atomic::{AtomicBool, AtomicU8, Ordering}; @@ -58,7 +58,7 @@ pub struct VirtioSharedMemoryList { pub mem_slot: u32, pub addr: GuestAddress, pub mapping: Arc, - pub region_list: Vec, + pub region_list: BTreeMap, } pub struct ActivationContext { diff --git a/virtio-devices/src/transport/pci_device.rs b/virtio-devices/src/transport/pci_device.rs index ac37c5893..5ce57a895 100644 --- a/virtio-devices/src/transport/pci_device.rs +++ b/virtio-devices/src/transport/pci_device.rs @@ -1092,11 +1092,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, ); -- 2.54.0 REUSE-IgnoreEnd From cffac7b3aa9d8b630ed5231cf87442a23741c9d9 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Mon, 16 Mar 2026 17:51:36 +0100 Subject: [PATCH 2/3] virtio-devices: implement VHOST_USER_PROTOCOL_F_SHMEM SPDX-FileCopyrightText: The Cloud Hypervisor Authors SPDX-FileCopyrightText: 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-FileCopyrightText: 2017 The Chromium OS Authors. All rights reserved. SPDX-FileCopyrightText: 2019 Intel Corporation SPDX-FileCopyrightText: 2022 Unikie SPDX-FileCopyrightText: 2023-2026 Alyssa Ross SPDX-FileCopyrightText: 2025 Demi Marie Obenour. SPDX-License-Identifier: Apache-2.0 AND LicenseRef-BSD-3-Clause-Google This was originally adapted from the code previously in Cloud Hypervisor to support virtio-fs DAX, which used some somewhat similar non-standard messages, but it has been adapted a lot over the years, first to crosvm's non-standard but non-DAX-specific mapping messages, and now to the standardised messages. The existing cache member on certain devices in Cloud Hypervisor is also a remnant of this, because that's what shared memory was used for with virtio-fs. Here I've renamed it to the more generic "shared_memory". One aspect of the implementation that's not ideal is the back and forth between the device and the device manager. Cloud Hypervisor is designed so that the device manager sets up the shared memory regions, but it can't do that until after vhost-user messages have been exchanged to figure out how much memory is required, so the device has to exist first. This means we call GenericVhostUser::new, allocate the regions, and then call GenericVhostUser::set_shared_memory to get those regions into the device, even though it would be nicer if those were just an argument to new. A better way to do this is not obvious to me. Signed-off-by: Alyssa Ross Co-authored-by: Alyssa Ross Signed-off-by: Alyssa Ross --- virtio-devices/src/lib.rs | 4 +- .../src/vhost_user/generic_vhost_user.rs | 243 +++++++++++++++--- virtio-devices/src/vhost_user/mod.rs | 6 + .../src/vhost_user/vu_common_ctrl.rs | 8 +- vmm/src/device_manager.rs | 85 +++++- 5 files changed, 302 insertions(+), 44 deletions(-) REUSE-IgnoreStart diff --git a/virtio-devices/src/lib.rs b/virtio-devices/src/lib.rs index 6ac397798..72b10ac6d 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::{ ActivationContext, DmaRemapping, VirtioCommon, VirtioDevice, VirtioInterrupt, - VirtioInterruptType, VirtioSharedMemoryList, + VirtioInterruptType, VirtioSharedMemory, VirtioSharedMemoryList, }; pub use self::epoll_helper::{ EPOLL_HELPER_EVENT_LAST, EpollHelper, EpollHelperError, EpollHelperHandler, @@ -114,6 +114,8 @@ pub enum ActivateError { VhostUserFsSetup(#[source] vhost_user::Error), #[error("Failed to setup vhost-user daemon")] VhostUserSetup(#[source] vhost_user::Error), + #[error("Failed to setup generic vhost-user daemon")] + GenericVhostUserSetup(#[source] vhost_user::Error), #[error("Failed to create seccomp filter")] CreateSeccompFilter(#[source] seccompiler::Error), #[error("Failed to create rate limiter")] diff --git a/virtio-devices/src/vhost_user/generic_vhost_user.rs b/virtio-devices/src/vhost_user/generic_vhost_user.rs index 5a302dc55..c904bb470 100644 --- a/virtio-devices/src/vhost_user/generic_vhost_user.rs +++ b/virtio-devices/src/vhost_user/generic_vhost_user.rs @@ -1,7 +1,12 @@ // Copyright 2019 Intel Corporation. All Rights Reserved. +// Copyright 2022 Unikie +// Copyright 2023, 2025-2026 Alyssa Ross // Copyright 2025 Demi Marie Obenour. // SPDX-License-Identifier: Apache-2.0 +use std::collections::{BTreeMap, HashMap}; +use std::io::{self, ErrorKind}; +use std::os::fd::AsRawFd; use std::result; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Barrier, Mutex}; @@ -10,11 +15,15 @@ use event_monitor::event; use log::{error, info, warn}; use seccompiler::SeccompAction; use vhost::vhost_user::message::{ - VhostUserConfigFlags, VhostUserProtocolFeatures, VhostUserVirtioFeatures, + VhostUserConfigFlags, VhostUserMMap, VhostUserMMapFlags, VhostUserProtocolFeatures, + VhostUserVirtioFeatures, +}; +use vhost::vhost_user::{ + FrontendReqHandler, HandlerResult, VhostUserFrontend, VhostUserFrontendReqHandler, }; -use vhost::vhost_user::{FrontendReqHandler, VhostUserFrontend, VhostUserFrontendReqHandler}; use vm_device::UserspaceMapping; -use vm_memory::GuestMemoryAtomic; +use vm_memory::volatile_memory::PtrGuardMut; +use vm_memory::{GuestMemoryAtomic, VolatileMemory}; use vm_migration::protocol::MemoryRangeTable; use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable}; use vmm_sys_util::eventfd::EventFd; @@ -31,8 +40,33 @@ use crate::{ pub type State = VhostUserState<()>; +struct ShmemRegion { + region: Arc, + mappings: Mutex>, +} + struct BackendReqHandler { interrupt_cb: Arc, + region: Option, +} + +impl BackendReqHandler { + fn ptr_guard_mut(&self, offset: u64, len: u64) -> io::Result { + let shm_offset = offset + .try_into() + .map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))?; + let len = len + .try_into() + .map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))?; + Ok(self + .region + .as_ref() + .ok_or(io::Error::from_raw_os_error(libc::EINVAL))? + .region + .get_slice(shm_offset, len) + .map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))? + .ptr_guard_mut()) + } } impl VhostUserFrontendReqHandler for BackendReqHandler { @@ -45,6 +79,114 @@ impl VhostUserFrontendReqHandler for BackendReqHandler { })?; Ok(0) } + + fn shmem_map(&self, req: &VhostUserMMap, fd: &dyn AsRawFd) -> HandlerResult { + let target = self.ptr_guard_mut(req.shm_offset, req.len)?; + + let Some(flags) = VhostUserMMapFlags::from_bits(req.flags) else { + return Err(ErrorKind::InvalidInput.into()); + }; + + if !(flags - VhostUserMMapFlags::WRITABLE).is_empty() { + return Err(ErrorKind::InvalidInput.into()); + } + + let Some(ref region) = self.region else { + return Err(ErrorKind::InvalidInput.into()); + }; + + let mut mappings = region.mappings.lock().unwrap(); + + // Overflows are disallowed. + for (&mapped_offset, mapped_len) in mappings.iter() { + // ptr_guard_mut has already checked that addition does not overflow. + if (req.shm_offset >= mapped_offset && req.shm_offset < mapped_offset + mapped_len) + || (req.shm_offset + req.len >= mapped_offset + && req.shm_offset + req.len < mapped_offset + mapped_len) + { + return Err(ErrorKind::InvalidInput.into()); + } + } + + // SAFETY: we've checked we're only giving addr and length + // within the region, and are passing MAP_FIXED to ensure they + // are respected. + let ret = unsafe { + libc::mmap( + target.as_ptr().cast(), + target.len(), + if flags.contains(VhostUserMMapFlags::WRITABLE) { + libc::PROT_WRITE + } else { + 0 + } | libc::PROT_READ, + // https://bugzilla.kernel.org/show_bug.cgi?id=217238 + if flags.contains(VhostUserMMapFlags::WRITABLE) { + 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()); + } + + mappings.insert(req.shm_offset, req.len); + + Ok(0) + } + + fn shmem_unmap(&self, req: &VhostUserMMap) -> HandlerResult { + let target = self.ptr_guard_mut(req.shm_offset, req.len)?; + + if req.flags != 0 { + return Err(ErrorKind::InvalidInput.into()); + } + + let Some(ref region) = self.region else { + return Err(ErrorKind::InvalidInput.into()); + }; + + let mut mappings = region.mappings.lock().unwrap(); + + if mappings.get(&{ req.shm_offset }) != Some(&{ req.len }) { + return Err(ErrorKind::InvalidInput.into()); + } + + // SAFETY: we control this mapping, and we know the mapping + // for the whole MmapRegion is behind it. + if unsafe { libc::munmap(target.as_ptr().cast(), target.len()) } == -1 { + return Err(io::Error::last_os_error()); + } + + mappings.remove(&{ req.shm_offset }); + + Ok(0) + } +} + +impl Drop for BackendReqHandler { + fn drop(&mut self) { + let Some(region) = self.region.take() else { + return; + }; + for (mapped_offset, mapped_len) in region.mappings.lock().unwrap().drain() { + let slice = self.ptr_guard_mut(mapped_offset, mapped_len).unwrap(); + + // SAFETY: we control this mapping, and we know the mapping + // for the whole MmapRegion is behind it. + if unsafe { libc::munmap(slice.as_ptr().cast(), slice.len()) } == -1 { + warn!( + "Unmapping VHOST_USER_PROTOCOL_F_SHMEM mapping: {}", + io::Error::last_os_error() + ); + } + } + } } pub struct GenericVhostUser { @@ -52,7 +194,7 @@ pub struct GenericVhostUser { 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)>, + shared_memory: Option, seccomp_action: SeccompAction, guest_memory: Option>, exit_evt: EventFd, @@ -68,12 +210,11 @@ impl GenericVhostUser { path: &str, request_queue_sizes: Vec, device_type: u32, - cache: Option<(VirtioSharedMemoryList, MmapRegion)>, seccomp_action: SeccompAction, exit_evt: EventFd, access_platform_enabled: bool, state: Option, - ) -> Result { + ) -> Result<(GenericVhostUser, BTreeMap)> { // Calculate the actual number of queues needed. let num_queues = request_queue_sizes.len(); @@ -112,6 +253,7 @@ impl GenericVhostUser { | VhostUserProtocolFeatures::REPLY_ACK | VhostUserProtocolFeatures::INFLIGHT_SHMFD | VhostUserProtocolFeatures::LOG_SHMFD + | VhostUserProtocolFeatures::SHMEM | VhostUserProtocolFeatures::DEVICE_STATE | VhostUserProtocolFeatures::BACKEND_REQ; @@ -151,33 +293,56 @@ since the backend only supports {backend_num_queues}\n", ) }; - Ok(GenericVhostUser { - vu_common: VhostUserCommon { - virtio_common: VirtioCommon { - device_type, - avail_features, - acked_features, - queue_sizes: request_queue_sizes, - paused_sync: Some(Arc::new(Barrier::new(2))), - min_queues: 1, - paused: Arc::new(AtomicBool::new(paused)), + let shm_regions = + if (acked_protocol_features & VhostUserProtocolFeatures::SHMEM.bits()) == 0 { + Default::default() + } else { + vu.get_shmem_config()? + }; + + let shm_regions = shm_regions + .memory_sizes + .into_iter() + .enumerate() + .filter(|&(_, size)| size != 0) + .map(|(id, size)| (id as u8, size)) + .take(shm_regions.nregions.try_into().unwrap()) + .collect(); + + Ok(( + GenericVhostUser { + vu_common: VhostUserCommon { + virtio_common: VirtioCommon { + device_type, + avail_features, + acked_features, + queue_sizes: request_queue_sizes, + paused_sync: Some(Arc::new(Barrier::new(2))), + min_queues: 1, + paused: Arc::new(AtomicBool::new(paused)), + ..Default::default() + }, + vu: Some(Arc::new(Mutex::new(vu))), + acked_protocol_features, + socket_path: path.to_string(), + vu_num_queues, + vring_bases, ..Default::default() }, - vu: Some(Arc::new(Mutex::new(vu))), - acked_protocol_features, - socket_path: path.to_string(), - vu_num_queues, - vring_bases, - ..Default::default() + id, + shared_memory: None, + seccomp_action, + guest_memory: None, + exit_evt, + access_platform_enabled, + cfg_warning: AtomicBool::new(false), }, - id, - cache, - seccomp_action, - guest_memory: None, - exit_evt, - access_platform_enabled, - cfg_warning: AtomicBool::new(false), - }) + shm_regions, + )) + } + + pub fn set_shared_memory(&mut self, shared_memory: VirtioSharedMemoryList) { + self.shared_memory = Some(shared_memory); } fn state(&self) -> std::result::Result { @@ -299,6 +464,10 @@ impl VirtioDevice for GenericVhostUser { .then(|| { let mut handler = FrontendReqHandler::new(Arc::new(BackendReqHandler { interrupt_cb: interrupt_cb.clone(), + region: self.shared_memory.as_ref().map(|list| ShmemRegion { + region: list.mapping.clone(), + mappings: Mutex::new(HashMap::new()), + }), })) .map_err(|e| { crate::ActivateError::VhostUserSetup(Error::FrontendReqHandlerCreation(e)) @@ -359,15 +528,15 @@ impl VirtioDevice for GenericVhostUser { } fn get_shm_regions(&self) -> Option { - self.cache.as_ref().map(|cache| cache.0.clone()) + self.shared_memory.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; + if let Some(cache) = self.shared_memory.as_mut() { + *cache = shm_regions; Ok(()) } else { Err(crate::Error::SetShmRegionsNotSupported) @@ -383,11 +552,11 @@ impl VirtioDevice for GenericVhostUser { fn userspace_mappings(&self) -> Vec { let mut mappings = Vec::new(); - if let Some(cache) = self.cache.as_ref() { + if let Some(cache) = self.shared_memory.as_ref() { mappings.push(UserspaceMapping { - mem_slot: cache.0.mem_slot, - addr: cache.0.addr, - mapping: cache.0.mapping.clone(), + mapping: cache.mapping.clone(), + mem_slot: cache.mem_slot, + addr: cache.addr, mergeable: false, }); } diff --git a/virtio-devices/src/vhost_user/mod.rs b/virtio-devices/src/vhost_user/mod.rs index babc823ae..d97fe2a6a 100644 --- a/virtio-devices/src/vhost_user/mod.rs +++ b/virtio-devices/src/vhost_user/mod.rs @@ -82,6 +82,8 @@ pub enum Error { VhostUserGetQueueMaxNum(#[source] VhostError), #[error("Get protocol features failed")] VhostUserGetProtocolFeatures(#[source] VhostError), + #[error("Get shared memory regions failed")] + VhostUserGetSharedMemoryRegions(#[source] VhostError), #[error("Get vring base failed")] VhostUserGetVringBase(#[source] VhostError), #[error("Vhost-user Backend not support vhost-user protocol")] @@ -130,6 +132,10 @@ pub enum Error { VhostUserSetInflight(#[source] VhostError), #[error("Failed setting the log base")] VhostUserSetLogBase(#[source] VhostError), + #[error("Expected {0} shared memory regions; got {1}")] + VhostUserUnexpectedSharedMemoryRegionsCount(usize, u32), + #[error("No shared memory region with non-zero length")] + VhostUserMissingSharedMemoryRegion, #[error("Invalid used address")] UsedAddress, #[error("Invalid features provided from vhost-user backend")] diff --git a/virtio-devices/src/vhost_user/vu_common_ctrl.rs b/virtio-devices/src/vhost_user/vu_common_ctrl.rs index 23a37c335..8c668a3f5 100644 --- a/virtio-devices/src/vhost_user/vu_common_ctrl.rs +++ b/virtio-devices/src/vhost_user/vu_common_ctrl.rs @@ -14,7 +14,7 @@ use log::{error, info}; use vhost::vhost_kern::vhost_binding::VHOST_VRING_F_LOG; use vhost::vhost_user::message::{ VhostTransferStateDirection, VhostTransferStatePhase, VhostUserHeaderFlag, VhostUserInflight, - VhostUserProtocolFeatures, VhostUserVirtioFeatures, + VhostUserProtocolFeatures, VhostUserShMemConfig, VhostUserVirtioFeatures, }; use vhost::vhost_user::{ Frontend, FrontendReqHandler, VhostUserFrontend, VhostUserFrontendReqHandler, @@ -114,6 +114,12 @@ impl VhostUserHandle { .map_err(Error::VhostUserAddMemReg) } + pub fn get_shmem_config(&mut self) -> Result { + self.vu + .get_shmem_config() + .map_err(Error::VhostUserGetSharedMemoryRegions) + } + pub fn negotiate_features_vhost_user( &mut self, avail_features: u64, diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index e6902a4cb..f6f27b9d6 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -89,7 +89,7 @@ use virtio_devices::transport::{VirtioPciDevice, VirtioPciDeviceActivator, Virti use virtio_devices::vhost_user::VhostUserConfig; use virtio_devices::{ AccessPlatformMapping, ActivateError, Block, Endpoint, IommuMapping, VdpaDmaMapping, - VirtioMemMappingSource, + VirtioMemMappingSource, VirtioSharedMemory, VirtioSharedMemoryList, }; use vm_allocator::{AddressAllocator, InterruptAllocError, SystemAllocator}; use vm_device::dma_mapping::ExternalDmaMapping; @@ -318,6 +318,10 @@ pub enum DeviceManagerError { #[error("Cannot find a memory range for virtio-fs")] FsRangeAllocation, + /// Cannot find a memory range for generic vhost-user + #[error("Cannot find a memory range for generic vhost-user")] + GenericVhostUserRangeAllocation, + /// Error creating serial output file #[error("Error creating serial output file")] SerialOutputFileOpen(#[source] io::Error), @@ -3122,13 +3126,12 @@ impl DeviceManager { let mut node = device_node!(id); if let Some(generic_vhost_user_socket) = generic_vhost_user_cfg.socket.to_str() { - let generic_vhost_user_device = Arc::new(Mutex::new( + let (mut generic_vhost_user_device, shm_regions) = virtio_devices::vhost_user::GenericVhostUser::new( id.clone(), generic_vhost_user_socket, generic_vhost_user_cfg.queue_sizes.clone(), generic_vhost_user_cfg.device_type, - None, self.seccomp_action.clone(), self.exit_evt .try_clone() @@ -3137,8 +3140,80 @@ impl DeviceManager { state_from_id(self.snapshot.as_ref(), id.as_str()) .map_err(DeviceManagerError::RestoreGetState)?, ) - .map_err(DeviceManagerError::CreateGenericVhostUser)?, - )); + .map_err(DeviceManagerError::CreateGenericVhostUser)?; + + if !shm_regions.is_empty() { + let mut total_len = 0u64; + for &len in shm_regions.values() { + total_len = total_len + .checked_add(len) + .ok_or(DeviceManagerError::GenericVhostUserRangeAllocation)?; + } + + let cache_base = self.pci_segments + [generic_vhost_user_cfg.pci_common.pci_segment as usize] + .mem64_allocator + .lock() + .unwrap() + // Aligning to the full size of the allocation is cargo-culted from crosvm, + // which allocates an 8GiB-aligned 8GiB region. + .allocate(None, total_len, Some(total_len)) + .ok_or(DeviceManagerError::GenericVhostUserRangeAllocation)? + .raw_value(); + + // Update the node with correct resource information. + node.resources.push(Resource::MmioAddressRange { + base: cache_base, + size: total_len, + }); + + let mmap_region = MmapRegion::build( + None, + total_len as usize, + libc::PROT_NONE, + libc::MAP_ANONYMOUS | libc::MAP_PRIVATE, + ) + .map_err(DeviceManagerError::NewMmapRegion)?; + + // SAFETY: `mmap_region.size()` and `mmap_region.as_ptr()` refer to an allocation. + // We remove the userspace mapping before dropping the device if the device is + // ejected. + let mem_slot = unsafe { + self.memory_manager + .lock() + .unwrap() + .create_userspace_mapping( + cache_base, + mmap_region.size(), + mmap_region.as_ptr(), + false, + false, + false, + ) + .map_err(DeviceManagerError::MemoryManager)? + }; + + let region_list = { + let mut offset = 0; + shm_regions + .into_iter() + .map(|(id, len)| { + let mem = VirtioSharedMemory { offset, len }; + offset += len; + (id, mem) + }) + .collect() + }; + + generic_vhost_user_device.set_shared_memory(VirtioSharedMemoryList { + mapping: Arc::new(mmap_region), + mem_slot, + addr: GuestAddress(cache_base), + region_list, + }); + } + + let generic_vhost_user_device = Arc::new(Mutex::new(generic_vhost_user_device)); // Update the device tree with the migratable device. node.migratable = -- 2.54.0 REUSE-IgnoreEnd From da312aef2aa8657498b996146c532729d626f323 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Tue, 17 Mar 2026 10:19:54 +0100 Subject: [PATCH 3/3] virtio-devices: support vhost-user GPU features SPDX-FileCopyrightText: The Cloud Hypervisor Authors SPDX-FileCopyrightText: 2019 Intel Corporation SPDX-FileCopyrightText: 2025 Demi Marie Obenour. SPDX-FileCopyrightText: 2026 Alyssa Ross SPDX-License-Identifier: Apache-2.0 None of these require any special support in the vhost-user frontend; they're entirely between the backend and the driver. We can add similar entries for any other device-specific features the generic vhost-user device supports with no extra code. Signed-off-by: Alyssa Ross --- .../src/vhost_user/generic_vhost_user.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) REUSE-IgnoreStart diff --git a/virtio-devices/src/vhost_user/generic_vhost_user.rs b/virtio-devices/src/vhost_user/generic_vhost_user.rs index c904bb470..82fd01ef4 100644 --- a/virtio-devices/src/vhost_user/generic_vhost_user.rs +++ b/virtio-devices/src/vhost_user/generic_vhost_user.rs @@ -21,6 +21,11 @@ use vhost::vhost_user::message::{ use vhost::vhost_user::{ FrontendReqHandler, HandlerResult, VhostUserFrontend, VhostUserFrontendReqHandler, }; +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_bindings::virtio_ids::VIRTIO_ID_GPU; use vm_device::UserspaceMapping; use vm_memory::volatile_memory::PtrGuardMut; use vm_memory::{GuestMemoryAtomic, VolatileMemory}; @@ -257,7 +262,16 @@ impl GenericVhostUser { | VhostUserProtocolFeatures::DEVICE_STATE | VhostUserProtocolFeatures::BACKEND_REQ; - let avail_features = super::DEFAULT_VIRTIO_FEATURES; + let avail_features = super::DEFAULT_VIRTIO_FEATURES + | match device_type { + VIRTIO_ID_GPU => { + 1 << VIRTIO_GPU_F_VIRGL + | 1 << VIRTIO_GPU_F_RESOURCE_UUID + | 1 << VIRTIO_GPU_F_RESOURCE_BLOB + | 1 << VIRTIO_GPU_F_CONTEXT_INIT + } + _ => 0, + }; let (acked_features, acked_protocol_features) = vu.negotiate_features_vhost_user(avail_features, avail_protocol_features)?; -- 2.54.0