From b23d801d103274e9b284e3c96c0bc51185352d5e Mon Sep 17 00:00:00 2001 From: Dorinda Bassey Date: Mon, 3 Aug 2026 16:38:35 +0200 Subject: [PATCH 1/9] gpu/virgl: Update virglrenderer-rs dependency Update virglrenderer-rs to pull in new API wrappers needed for Venus support: context_create_fence, context_poll, and export_resource_dmabuf. Signed-off-by: Dorinda Bassey --- Cargo.lock | 8 ++++---- vhost-device-gpu/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d12671ba2..78f10958b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2463,9 +2463,9 @@ dependencies = [ [[package]] name = "virglrenderer" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40d0ffbdb42cc9dac7fd353d7a29b77b0f4d963dcce7dc46fa8903a7216bd520" +checksum = "2d3c093d78f4dd4fa9d9e29d5a91a29445d1df2e7f71e64f046d147b6ec7dac2" dependencies = [ "libc", "log", @@ -2475,9 +2475,9 @@ dependencies = [ [[package]] name = "virglrenderer-sys" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e1cd0732acd1881433c4689bb2d359d64b9a64ddf64ab7231d9db35edbd181a" +checksum = "7b1925101d32994ee2ab4b711ce7f01a96ddc031fb5a3761347667523e343140" dependencies = [ "bindgen 0.72.1", "pkg-config", diff --git a/vhost-device-gpu/Cargo.toml b/vhost-device-gpu/Cargo.toml index c86325864..3f05dc9ff 100644 --- a/vhost-device-gpu/Cargo.toml +++ b/vhost-device-gpu/Cargo.toml @@ -28,7 +28,7 @@ log = "0.4" [target.'cfg(not(target_env = "musl"))'.dependencies] rutabaga_gfx = "0.1.75" thiserror = "2.0.18" -virglrenderer = { version = "0.1.4", optional = true } +virglrenderer = { version = "0.1.6", optional = true } vhost = { workspace = true } vhost-user-backend = { workspace = true } virtio-bindings = { workspace = true } From f50dc837ad32d3b86d4111520e0ed1ee6cf90205 Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Tue, 9 Dec 2025 17:14:57 +0100 Subject: [PATCH 2/9] gpu/virgl: Make capset handling robust Instead of hardcoding the capset indices, lookup the capset index dynamically based on the configured capsets in the config. Convert the capset bitmask to a capset ID using trailing_zeros() since bits() returns a power-of-2 bitmask, not the capset index. Signed-off-by: Matej Hrica Signed-off-by: Dorinda Bassey --- vhost-device-gpu/src/backend/virgl.rs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/vhost-device-gpu/src/backend/virgl.rs b/vhost-device-gpu/src/backend/virgl.rs index 9118c03af..f3d42522b 100644 --- a/vhost-device-gpu/src/backend/virgl.rs +++ b/vhost-device-gpu/src/backend/virgl.rs @@ -46,13 +46,9 @@ use crate::{ VirtioGpuResult, VIRTIO_GPU_MAX_SCANOUTS, }, renderer::Renderer, - GpuConfig, + GpuCapset, GpuConfig, }; -const CAPSET_ID_VIRGL: u32 = 1; -const CAPSET_ID_VIRGL2: u32 = 2; -const CAPSET_ID_VENUS: u32 = 4; - #[derive(Debug, ThisError)] pub enum VirglAdapterError { #[error("Failed to clone GPU device FD: {0}")] @@ -151,6 +147,7 @@ impl FenceHandler for VirglFenceHandler { pub struct VirglRendererAdapter { renderer: VirglRenderer, + capsets: GpuCapset, gpu_backend: Option, fence_state: Arc>, resources: BTreeMap, @@ -164,6 +161,7 @@ impl VirglRendererAdapter { config: &GpuConfig, gpu_backend: Option, ) -> io::Result { + let capsets = config.capsets(); let virglrenderer_flags = VirglRendererFlags::new() .use_virgl(true) .use_venus(true) @@ -191,6 +189,7 @@ impl VirglRendererAdapter { let renderer = VirglRenderer::init(virglrenderer_flags, fence_handler, None, drm_fd) .map_err(VirglAdapterError::InitVirglRenderer)?; Ok(Self { + capsets, renderer, gpu_backend, fence_state, @@ -372,13 +371,16 @@ impl Renderer for VirglRendererAdapter { } fn get_capset_info(&self, index: u32) -> VirtioGpuResult { - debug!("the capset index is {index}"); - let capset_id = match index { - 0 => CAPSET_ID_VIRGL, - 1 => CAPSET_ID_VIRGL2, - 2 => CAPSET_ID_VENUS, - _ => return Err(ErrInvalidParameter), - }; + debug!("Looking up capset at index {index}"); + let capset_bitmask = self + .capsets + .iter() + .nth(index as usize) + .ok_or(ErrInvalidParameter)? + .bits(); + + let capset_id = capset_bitmask.trailing_zeros(); + let (version, size) = self.renderer.get_capset_info(capset_id); Ok(OkCapsetInfo { capset_id, From 3d592fe0400fabc82806974d45a1c0ff86fb6425 Mon Sep 17 00:00:00 2001 From: Dorinda Bassey Date: Mon, 3 Aug 2026 16:32:25 +0200 Subject: [PATCH 3/9] gpu/virgl: Add per-context fence support Dispatch to context_create_fence() when the guest sets VIRTIO_GPU_FLAG_INFO_RING_IDX, and poll per-context fences in event_poll(). This is required for Venus multi-timeline fence signaling. Signed-off-by: Dorinda Bassey --- vhost-device-gpu/src/backend/virgl.rs | 30 ++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/vhost-device-gpu/src/backend/virgl.rs b/vhost-device-gpu/src/backend/virgl.rs index f3d42522b..e2f4f6d68 100644 --- a/vhost-device-gpu/src/backend/virgl.rs +++ b/vhost-device-gpu/src/backend/virgl.rs @@ -43,7 +43,7 @@ use crate::{ ErrInvalidContextId, ErrInvalidParameter, ErrInvalidResourceId, ErrInvalidScanoutId, ErrUnspec, OkCapset, OkCapsetInfo, OkNoData, }, - VirtioGpuResult, VIRTIO_GPU_MAX_SCANOUTS, + VirtioGpuResult, VIRTIO_GPU_FLAG_INFO_RING_IDX, VIRTIO_GPU_MAX_SCANOUTS, }, renderer::Renderer, GpuCapset, GpuConfig, @@ -458,12 +458,25 @@ impl Renderer for VirglRendererAdapter { } fn create_fence(&mut self, fence: RutabagaFence) -> VirtioGpuResult { - // Convert the fence ID to u32 - let fence_id_u32 = u32::try_from(fence.fence_id).map_err(|_| GpuResponse::ErrUnspec)?; - - self.renderer - .create_fence(fence_id_u32, fence.ctx_id) - .map_err(|_| ErrUnspec)?; + if fence.flags & VIRTIO_GPU_FLAG_INFO_RING_IDX != 0 { + self.renderer + .context_create_fence( + fence.ctx_id, + fence.flags, + fence.ring_idx as u32, + fence.fence_id, + ) + .map_err(|e| { + error!("context_create_fence failed: {:?}", e); + ErrUnspec + })?; + } else { + let fence_id_u32 = + u32::try_from(fence.fence_id).map_err(|_| GpuResponse::ErrUnspec)?; + self.renderer + .create_fence(fence_id_u32, fence.ctx_id) + .map_err(|_| ErrUnspec)?; + } Ok(OkNoData) } @@ -486,6 +499,9 @@ impl Renderer for VirglRendererAdapter { fn event_poll(&self) { self.renderer.event_poll(); + for &ctx_id in &self.context_ids { + self.renderer.context_poll(ctx_id); + } } fn force_ctx_0(&self) { From b9185d02e2ace01c90af73261f097651bd4f4d5e Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Tue, 9 Dec 2025 18:53:58 +0100 Subject: [PATCH 4/9] gpu: Add blob resource support Implement blob resource support and shared memory mapping. This is required for Venus support and gfxstream-{vulkan, gles} enablement. Updated to use vhost 0.17 and vhost-user-backend 0.23 for shmem. Added UUID field initialization for blob resources using Uuid::new_v4(). Signed-off-by: Matej Hrica Co-authored-by: Dorinda Bassey --- vhost-device-gpu/CHANGELOG.md | 1 + vhost-device-gpu/src/backend/common.rs | 73 ++++++- vhost-device-gpu/src/backend/gfxstream.rs | 181 ++++++++++++++--- vhost-device-gpu/src/backend/null.rs | 43 ++-- vhost-device-gpu/src/backend/virgl.rs | 234 ++++++++++++++++++---- vhost-device-gpu/src/device.rs | 109 ++++++---- vhost-device-gpu/src/gpu_types.rs | 31 +++ vhost-device-gpu/src/protocol.rs | 49 +++-- vhost-device-gpu/src/renderer.rs | 14 +- 9 files changed, 591 insertions(+), 144 deletions(-) diff --git a/vhost-device-gpu/CHANGELOG.md b/vhost-device-gpu/CHANGELOG.md index b0fdbae82..1b1e3791c 100644 --- a/vhost-device-gpu/CHANGELOG.md +++ b/vhost-device-gpu/CHANGELOG.md @@ -3,6 +3,7 @@ ### Added +- [[#988]] (https://github.com/rust-vmm/vhost-device/pull/988) vhost-device-gpu: Add blob resource support and Venus capset for virglrenderer backend - [[#945]] (https://github.com/rust-vmm/vhost-device/pull/945) Implement RESOURCE_ASSIGN_UUID command - [[#932]] (https://github.com/rust-vmm/vhost-device/pull/932) vhost-device-gpu: Add support for GPU device path - [[#927]] (https://github.com/rust-vmm/vhost-device/pull/927) vhost-device-gpu: Introduce headless mode diff --git a/vhost-device-gpu/src/backend/common.rs b/vhost-device-gpu/src/backend/common.rs index a7211294b..2c83c69dc 100644 --- a/vhost-device-gpu/src/backend/common.rs +++ b/vhost-device-gpu/src/backend/common.rs @@ -2,24 +2,89 @@ // // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause -use std::sync::{Arc, Mutex}; +use std::{ + os::fd::{AsFd, AsRawFd}, + sync::{Arc, Mutex}, +}; -use log::{debug, error}; +use log::{debug, error, trace}; use vhost::vhost_user::{ gpu_message::{VhostUserGpuCursorPos, VhostUserGpuCursorUpdate, VhostUserGpuEdidRequest}, - GpuBackend, + message::{VhostUserMMap, VhostUserMMapFlags}, + Backend, GpuBackend, VhostUserFrontendReqHandler, }; use vm_memory::VolatileSlice; use crate::{ gpu_types::{FenceDescriptor, FenceState, Transfer3DDesc, VirtioGpuRing}, protocol::{ - GpuResponse::{self, ErrUnspec, OkDisplayInfo, OkEdid, OkNoData}, + GpuResponse::{self, ErrInvalidParameter, ErrUnspec, OkDisplayInfo, OkEdid, OkNoData}, VirtioGpuResult, EDID_BLOB_MAX_SIZE, VIRTIO_GPU_MAX_SCANOUTS, }, renderer::Renderer, }; +/// Shared memory region ID for host-visible memory (blob resources) +const VIRTIO_GPU_SHM_ID_HOST_VISIBLE: u8 = 1; + +/// Common helper for mapping blob resources. +/// Maps a blob resource using the vhost-user backend's shmem_map. +pub fn common_map_blob( + backend: &Backend, + flags: VhostUserMMapFlags, + handle_fd: &(impl AsFd + AsRawFd), + blob_size: u64, + offset: u64, + resource_id: u32, +) -> Result<(), GpuResponse> { + trace!("Mapping blob resource_id={resource_id} offset={offset} size={blob_size}"); + + if blob_size > usize::MAX as u64 { + error!("Blob size {blob_size} exceeds platform limits for resource_id={resource_id}"); + return Err(ErrInvalidParameter); + } + + let map_request = VhostUserMMap { + shmid: VIRTIO_GPU_SHM_ID_HOST_VISIBLE, + padding: Default::default(), + fd_offset: 0, + shm_offset: offset, + len: blob_size, + flags: flags.bits(), + }; + + backend.shmem_map(&map_request, handle_fd).map_err(|e| { + error!("Failed to mmap by frontend: {e:?}"); + ErrUnspec + })?; + + Ok(()) +} + +/// Common helper for unmapping blob resources. +/// Unmaps a blob resource using the vhost-user backend's shmem_unmap. +pub fn common_unmap_blob( + backend: &Backend, + blob_size: u64, + offset: u64, +) -> Result<(), GpuResponse> { + let unmap_request = VhostUserMMap { + shmid: VIRTIO_GPU_SHM_ID_HOST_VISIBLE, + padding: Default::default(), + fd_offset: 0, + shm_offset: offset, + len: blob_size, + flags: 0, + }; + + backend.shmem_unmap(&unmap_request).map_err(|e| { + error!("Failed to unmap by frontend: {e:?}"); + ErrUnspec + })?; + + Ok(()) +} + #[derive(Debug, Clone)] pub struct VirtioGpuScanout { pub resource_id: u32, diff --git a/vhost-device-gpu/src/backend/gfxstream.rs b/vhost-device-gpu/src/backend/gfxstream.rs index 438df0b03..7fde0b448 100644 --- a/vhost-device-gpu/src/backend/gfxstream.rs +++ b/vhost-device-gpu/src/backend/gfxstream.rs @@ -8,7 +8,10 @@ use std::{ cell::RefCell, collections::BTreeMap, io::IoSliceMut, - os::{fd::FromRawFd, raw::c_void}, + os::{ + fd::{AsFd, FromRawFd}, + raw::c_void, + }, sync::{Arc, Mutex}, }; @@ -16,33 +19,40 @@ use log::{debug, error, warn}; use rutabaga_gfx::{ ResourceCreate3D, Rutabaga, RutabagaBuilder, RutabagaComponentType, RutabagaFence, RutabagaFenceHandler, RutabagaHandle, RutabagaIntoRawDescriptor, RutabagaIovec, Transfer3D, + RUTABAGA_HANDLE_TYPE_MEM_OPAQUE_FD, RUTABAGA_MAP_ACCESS_MASK, RUTABAGA_MAP_ACCESS_READ, + RUTABAGA_MAP_ACCESS_RW, RUTABAGA_MAP_ACCESS_WRITE, RUTABAGA_MAP_CACHE_MASK, }; use uuid::Uuid; use vhost::vhost_user::{ gpu_message::{ VhostUserGpuCursorPos, VhostUserGpuEdidRequest, VhostUserGpuScanout, VhostUserGpuUpdate, }, - GpuBackend, + message::VhostUserMMapFlags, + Backend, GpuBackend, }; use vhost_user_backend::{VringRwLock, VringT}; +use virtio_bindings::virtio_gpu::VIRTIO_GPU_BLOB_MEM_HOST3D; use vm_memory::{GuestAddress, GuestMemoryBackend, GuestMemoryMmap, VolatileSlice}; use vmm_sys_util::eventfd::EventFd; use crate::{ backend::{ common, - common::{common_set_scanout_disable, AssociatedScanouts, CursorConfig, VirtioGpuScanout}, + common::{ + common_map_blob, common_set_scanout_disable, common_unmap_blob, AssociatedScanouts, + CursorConfig, VirtioGpuScanout, + }, }, device::Error, - gpu_types::{FenceState, ResourceCreate3d, Transfer3DDesc, VirtioGpuRing}, + gpu_types::{FenceState, ResourceCreate3d, ResourceCreateBlob, Transfer3DDesc, VirtioGpuRing}, protocol::{ virtio_gpu_rect, GpuResponse, GpuResponse::{ - ErrInvalidParameter, ErrInvalidResourceId, ErrUnspec, OkCapset, OkCapsetInfo, OkNoData, - OkResourcePlaneInfo, + ErrInvalidParameter, ErrInvalidResourceId, ErrUnspec, OkCapset, OkCapsetInfo, + OkMapInfo, OkNoData, OkResourcePlaneInfo, }, - GpuResponsePlaneInfo, VirtioGpuResult, VIRTIO_GPU_FLAG_INFO_RING_IDX, - VIRTIO_GPU_MAX_SCANOUTS, + GpuResponsePlaneInfo, VirtioGpuResult, VIRTIO_GPU_BLOB_FLAG_CREATE_GUEST_HANDLE, + VIRTIO_GPU_FLAG_INFO_RING_IDX, VIRTIO_GPU_MAX_SCANOUTS, }, renderer::Renderer, GpuConfig, @@ -61,6 +71,8 @@ pub struct GfxstreamResource { pub info_3d: Option, pub handle: Option>, pub uuid: Uuid, + pub blob_size: u64, + pub blob_shmem_offset: Option, } impl GfxstreamResource { @@ -88,6 +100,8 @@ impl GfxstreamResource { info_3d: None, handle: None, uuid: Uuid::new_v4(), + blob_size: 0, + blob_shmem_offset: None, } } } @@ -99,6 +113,7 @@ thread_local! { } pub struct GfxstreamAdapter { + backend: Backend, gpu_backend: Option, resources: BTreeMap, fence_state: Arc>, @@ -108,6 +123,7 @@ pub struct GfxstreamAdapter { impl GfxstreamAdapter { pub fn new( queue_ctl: &VringRwLock, + backend: Backend, gpu_config: &GpuConfig, gpu_backend: Option, ) -> Self { @@ -124,6 +140,7 @@ impl GfxstreamAdapter { }); Self { + backend, gpu_backend, fence_state, resources: BTreeMap::new(), @@ -289,6 +306,8 @@ impl Renderer for GfxstreamAdapter { info_3d: None, handle: None, uuid: Uuid::new_v4(), + blob_size: 0, + blob_shmem_offset: None, }; debug_assert!( !self.resources.contains_key(&resource_id), @@ -368,11 +387,11 @@ impl Renderer for GfxstreamAdapter { fn attach_backing( &mut self, resource_id: u32, - mem: &GuestMemoryMmap, + mem: Arc, vecs: Vec<(GuestAddress, usize)>, ) -> VirtioGpuResult { - let rutabaga_iovecs = - Self::sglist_to_rutabaga_iovecs(&vecs[..], mem).map_err(|()| GpuResponse::ErrUnspec)?; + let rutabaga_iovecs = Self::sglist_to_rutabaga_iovecs(&vecs[..], &mem) + .map_err(|()| GpuResponse::ErrUnspec)?; Self::with_rutabaga(|rutabaga| rutabaga.attach_backing(resource_id, rutabaga_iovecs))?; Ok(OkNoData) } @@ -700,25 +719,121 @@ impl Renderer for GfxstreamAdapter { fn resource_create_blob( &mut self, - _ctx_id: u32, - _resource_id: u32, - _blob_id: u64, - _size: u64, - _blob_mem: u32, - _blob_flags: u32, + ctx_id: u32, + resource_create_blob: ResourceCreateBlob, + vecs: Vec<(vm_memory::GuestAddress, usize)>, + mem: &vm_memory::GuestMemoryMmap, ) -> VirtioGpuResult { - error!("Not implemented: resource_create_blob"); - Err(ErrUnspec) + let mut rutabaga_iovecs = None; + + if resource_create_blob.blob_flags & VIRTIO_GPU_BLOB_FLAG_CREATE_GUEST_HANDLE != 0 { + error!("GUEST_HANDLE unimplemented for gfxstream backend"); + return Err(ErrUnspec); + } else if resource_create_blob.blob_mem != VIRTIO_GPU_BLOB_MEM_HOST3D { + rutabaga_iovecs = + Some(Self::sglist_to_rutabaga_iovecs(&vecs[..], mem).map_err(|_| ErrUnspec)?); + } + + Self::with_rutabaga(|rutabaga| { + rutabaga.resource_create_blob( + ctx_id, + resource_create_blob.resource_id, + resource_create_blob.into(), + rutabaga_iovecs, + None, + ) + })?; + + let resource = GfxstreamResource { + id: resource_create_blob.resource_id, + width: 0, + height: 0, + scanouts: AssociatedScanouts::default(), + info_3d: None, + handle: None, + uuid: Uuid::new_v4(), + blob_size: resource_create_blob.size, + blob_shmem_offset: None, + }; + + debug_assert!( + !self + .resources + .contains_key(&resource_create_blob.resource_id), + "Resource ID {} already exists in the resources map.", + resource_create_blob.resource_id + ); + + // Rely on rutabaga to check for duplicate resource ids. + self.resources + .insert(resource_create_blob.resource_id, resource); + Ok(Self::result_from_query(resource_create_blob.resource_id)) } - fn resource_map_blob(&mut self, _resource_id: u32, _offset: u64) -> VirtioGpuResult { - error!("Not implemented: resource_map_blob"); - Err(ErrUnspec) + fn resource_map_blob(&mut self, resource_id: u32, offset: u64) -> VirtioGpuResult { + let resource = self + .resources + .get_mut(&resource_id) + .ok_or(ErrInvalidResourceId)?; + + let map_info = Self::with_rutabaga(|rutabaga| rutabaga.map_info(resource_id)) + .map_err(|_| ErrUnspec)?; + + let export = Self::with_rutabaga(|rutabaga| rutabaga.export_blob(resource_id)) + .map_err(|_| ErrUnspec)?; + + // Check handle type - we don't support OPAQUE_FD mapping + if export.handle_type == RUTABAGA_HANDLE_TYPE_MEM_OPAQUE_FD { + return Err(ErrUnspec); + } + + // Convert map_info access flags to VhostUserMMapFlags + let flags = match map_info & RUTABAGA_MAP_ACCESS_MASK { + RUTABAGA_MAP_ACCESS_READ => VhostUserMMapFlags::default(), + RUTABAGA_MAP_ACCESS_WRITE => VhostUserMMapFlags::WRITABLE, + RUTABAGA_MAP_ACCESS_RW => VhostUserMMapFlags::WRITABLE, + _ => { + error!("Invalid access mask for blob resource, map_info: {map_info}"); + return Err(ErrUnspec); + } + }; + + common_map_blob( + &self.backend, + flags, + &export.os_handle.as_fd(), + resource.blob_size, + offset, + resource_id, + )?; + + resource.blob_shmem_offset = Some(offset); + + // Return cache flags only (access flags not part of virtio-gpu spec) + Ok(OkMapInfo { + map_info: map_info & RUTABAGA_MAP_CACHE_MASK, + }) } - fn resource_unmap_blob(&mut self, _resource_id: u32) -> VirtioGpuResult { - error!("Not implemented: resource_unmap_blob"); - Err(ErrUnspec) + fn resource_unmap_blob(&mut self, resource_id: u32) -> VirtioGpuResult { + let resource = self + .resources + .get_mut(&resource_id) + .ok_or(ErrInvalidResourceId)?; + + let Some(offset) = resource.blob_shmem_offset else { + log::warn!( + "Guest tried to unmap blob resource with resource_id={resource_id}, but it is not \ + mapped!" + ); + return Err(ErrInvalidParameter); + }; + + common_unmap_blob(&self.backend, resource.blob_size, offset)?; + + resource.blob_shmem_offset = None; + + Ok(OkNoData) } } @@ -779,6 +894,11 @@ mod gfx_fence_tests { GpuBackend::from_stream(backend) } + fn dummy_backend() -> Backend { + let (_, backend) = UnixStream::pair().unwrap(); + Backend::from_stream(backend) + } + /// Attempts to create a GPU adapter for testing. /// Returns None if gfxstream initialization fails (e.g., in CI without GPU /// drivers). @@ -823,6 +943,7 @@ mod gfx_fence_tests { }); Some(GfxstreamAdapter { + backend: dummy_backend(), gpu_backend: Some(dummy_gpu_backend()), resources: BTreeMap::default(), fence_state, @@ -873,7 +994,7 @@ mod gfx_fence_tests { // Attach backing for cursor resource to ensure transfer_read works let cursor_backing = GuestMemoryMmap::from_ranges(&[(GuestAddress(0xC0000), 0x10000)]).unwrap(); - gfxstream_gpu.attach_backing(cursor_resource_id, &cursor_backing, vec![(GuestAddress(0xC0000), 16384usize)]).unwrap(); + gfxstream_gpu.attach_backing(cursor_resource_id, Arc::new(cursor_backing), vec![(GuestAddress(0xC0000), 16384usize)]).unwrap(); // The resource exists, the dimensions are correct, and backing is attached // This exercises common_read_cursor_resource and then fails at cursor_update (no frontend) @@ -964,7 +1085,7 @@ mod gfx_fence_tests { // Attach that single iovec (addr,len) to the resource. let sg = vec![(GuestAddress(0xA0000), 4usize)]; - gfxstream_gpu.attach_backing(1, &gm_back, sg).expect("attach_backing"); + gfxstream_gpu.attach_backing(1, Arc::new(gm_back), sg).expect("attach_backing"); // Detach the backing memory from the resource gfxstream_gpu.detach_backing(1).expect("detach_backing"); gfxstream_gpu.set_scanout(1, 1, rect).unwrap_err(); @@ -1138,6 +1259,8 @@ mod gfx_fence_tests { info_3d: None, handle: None, uuid: Uuid::new_v4(), + blob_size: 0, + blob_shmem_offset: None, }; // 64 * 64 * 4 BPP = 16384 assert_eq!( @@ -1157,6 +1280,8 @@ mod gfx_fence_tests { info_3d: None, handle: None, uuid: Uuid::new_v4(), + blob_size: 0, + blob_shmem_offset: None, }; r.calculate_size().unwrap_err(); } @@ -1173,6 +1298,8 @@ mod gfx_fence_tests { info_3d: None, handle: None, uuid: Uuid::new_v4(), + blob_size: 0, + blob_shmem_offset: None, }; // On 64-bit this should error; if it happens to fit on 32-bit, the guard still // holds elsewhere. diff --git a/vhost-device-gpu/src/backend/null.rs b/vhost-device-gpu/src/backend/null.rs index f7bbf938a..491d4909e 100644 --- a/vhost-device-gpu/src/backend/null.rs +++ b/vhost-device-gpu/src/backend/null.rs @@ -3,17 +3,19 @@ // // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause +use std::sync::Arc; + use log::trace; use rutabaga_gfx::RutabagaFence; use vhost::vhost_user::{ gpu_message::{VhostUserGpuCursorPos, VhostUserGpuEdidRequest}, - GpuBackend, + Backend, GpuBackend, }; use vm_memory::{GuestAddress, GuestMemoryMmap, VolatileSlice}; use vmm_sys_util::eventfd::EventFd; use crate::{ - gpu_types::{ResourceCreate3d, Transfer3DDesc, VirtioGpuRing}, + gpu_types::{ResourceCreate3d, ResourceCreateBlob, Transfer3DDesc, VirtioGpuRing}, protocol::{virtio_gpu_rect, GpuResponse, VirtioGpuResult}, renderer::Renderer, GpuConfig, @@ -27,6 +29,7 @@ impl NullAdapter { pub fn new( _queue_ctl: &vhost_user_backend::VringRwLock, _config: &GpuConfig, + _backend: Backend, gpu_backend: Option, ) -> Self { trace!("NullAdapter created"); @@ -85,7 +88,7 @@ impl Renderer for NullAdapter { fn attach_backing( &mut self, _resource_id: u32, - _mem: &GuestMemoryMmap, + _mem: Arc, _vecs: Vec<(GuestAddress, usize)>, ) -> VirtioGpuResult { trace!("NullAdapter::attach_backing - no-op"); @@ -224,11 +227,9 @@ impl Renderer for NullAdapter { fn resource_create_blob( &mut self, _ctx_id: u32, - _resource_id: u32, - _blob_id: u64, - _size: u64, - _blob_mem: u32, - _blob_flags: u32, + _resource_create_blob: ResourceCreateBlob, + _vecs: Vec<(vm_memory::GuestAddress, usize)>, + _mem: &vm_memory::GuestMemoryMmap, ) -> VirtioGpuResult { trace!("NullAdapter::resource_create_blob - no-op"); Ok(GpuResponse::OkNoData) @@ -256,8 +257,9 @@ mod tests { use crate::{GpuConfigBuilder, GpuFlags, GpuMode}; fn create_null_adapter() -> NullAdapter { - let (_, backend) = UnixStream::pair().unwrap(); - let gpu_backend = GpuBackend::from_stream(backend); + let (stream1, stream2) = UnixStream::pair().unwrap(); + let backend = vhost::vhost_user::Backend::from_stream(stream1); + let gpu_backend = GpuBackend::from_stream(stream2); let mem = GuestMemoryAtomic::new( GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(), ); @@ -268,7 +270,7 @@ mod tests { .build() .unwrap(); - NullAdapter::new(&vring, &config, Some(gpu_backend)) + NullAdapter::new(&vring, &config, backend, Some(gpu_backend)) } #[test] @@ -302,8 +304,9 @@ mod tests { assert!(matches!(result, Ok(GpuResponse::OkNoData))); // Verify attaching and detaching backing memory succeeds - let mem = GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(); - let result = adapter.attach_backing(1, &mem, vec![]); + let mem = + Arc::new(GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap()); + let result = adapter.attach_backing(1, mem, vec![]); assert!(matches!(result, Ok(GpuResponse::OkNoData))); let result = adapter.detach_backing(1); @@ -434,9 +437,21 @@ mod tests { #[test] fn test_null_adapter_blob_operations() { let mut adapter = create_null_adapter(); + let mem = GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(); // Verify blob resource creation succeeds - let result = adapter.resource_create_blob(0, 1, 1, 4096, 0, 0); + let result = adapter.resource_create_blob( + 0, + ResourceCreateBlob { + resource_id: 1, + blob_id: 1, + blob_mem: 0, + blob_flags: 0, + size: 4096, + }, + vec![], + &mem, + ); assert!(matches!(result, Ok(GpuResponse::OkNoData))); // Verify mapping blob resource succeeds diff --git a/vhost-device-gpu/src/backend/virgl.rs b/vhost-device-gpu/src/backend/virgl.rs index e2f4f6d68..0c3408caf 100644 --- a/vhost-device-gpu/src/backend/virgl.rs +++ b/vhost-device-gpu/src/backend/virgl.rs @@ -21,29 +21,35 @@ use vhost::vhost_user::{ VhostUserGpuCursorPos, VhostUserGpuDMABUFScanout, VhostUserGpuDMABUFScanout2, VhostUserGpuEdidRequest, VhostUserGpuUpdate, }, - GpuBackend, + message::VhostUserMMapFlags, + Backend, GpuBackend, }; use vhost_user_backend::{VringRwLock, VringT}; use virglrenderer::{ FenceHandler, Iovec, VirglRenderer, VirglRendererFlags, VirglResource, - VIRGL_HANDLE_TYPE_MEM_DMABUF, + VIRGL_HANDLE_TYPE_MEM_DMABUF, VIRGL_HANDLE_TYPE_MEM_OPAQUE_FD, VIRGL_MAP_CACHE_MASK, }; +use virtio_bindings::virtio_gpu::VIRTIO_GPU_BLOB_MEM_HOST3D; use vm_memory::{GuestAddress, GuestMemoryBackend, GuestMemoryMmap, VolatileSlice}; use vmm_sys_util::eventfd::EventFd; use crate::{ backend::{ common, - common::{common_set_scanout_disable, AssociatedScanouts, CursorConfig, VirtioGpuScanout}, + common::{ + common_map_blob, common_set_scanout_disable, common_unmap_blob, AssociatedScanouts, + CursorConfig, VirtioGpuScanout, + }, }, - gpu_types::{FenceState, ResourceCreate3d, Transfer3DDesc, VirtioGpuRing}, + gpu_types::{FenceState, ResourceCreate3d, ResourceCreateBlob, Transfer3DDesc, VirtioGpuRing}, protocol::{ virtio_gpu_rect, GpuResponse, GpuResponse::{ ErrInvalidContextId, ErrInvalidParameter, ErrInvalidResourceId, ErrInvalidScanoutId, - ErrUnspec, OkCapset, OkCapsetInfo, OkNoData, + ErrUnspec, OkCapset, OkCapsetInfo, OkMapInfo, OkNoData, }, - VirtioGpuResult, VIRTIO_GPU_FLAG_INFO_RING_IDX, VIRTIO_GPU_MAX_SCANOUTS, + VirtioGpuResult, VIRTIO_GPU_BLOB_MEM_HOST3D_GUEST, VIRTIO_GPU_FLAG_INFO_RING_IDX, + VIRTIO_GPU_MAX_SCANOUTS, }, renderer::Renderer, GpuCapset, GpuConfig, @@ -70,7 +76,11 @@ pub struct GpuResource { // resource. Resource could be used for multiple scanouts. pub scanouts: AssociatedScanouts, pub backing_iovecs: Arc>>>, + // Prevents the guest memory mapping from being unmapped while iovecs reference it. + pub backing_mem: Option>, pub uuid: Uuid, + pub blob_size: u64, + pub blob_shmem_offset: Option, } fn sglist_to_iovecs( @@ -148,6 +158,7 @@ impl FenceHandler for VirglFenceHandler { pub struct VirglRendererAdapter { renderer: VirglRenderer, capsets: GpuCapset, + backend: Backend, gpu_backend: Option, fence_state: Arc>, resources: BTreeMap, @@ -158,6 +169,7 @@ pub struct VirglRendererAdapter { impl VirglRendererAdapter { pub fn new( queue_ctl: &VringRwLock, + backend: Backend, config: &GpuConfig, gpu_backend: Option, ) -> io::Result { @@ -191,6 +203,7 @@ impl VirglRendererAdapter { Ok(Self { capsets, renderer, + backend, gpu_backend, fence_state, resources: BTreeMap::new(), @@ -212,7 +225,10 @@ impl Renderer for VirglRendererAdapter { virgl_resource, scanouts: AssociatedScanouts::default(), backing_iovecs: Arc::new(Mutex::new(None)), + backing_mem: None, uuid: Uuid::new_v4(), + blob_size: 0, + blob_shmem_offset: None, }; self.resources.insert(resource_id, local_resource); Ok(OkNoData) @@ -284,20 +300,22 @@ impl Renderer for VirglRendererAdapter { fn attach_backing( &mut self, resource_id: u32, - mem: &GuestMemoryMmap, + mem: Arc, vecs: Vec<(GuestAddress, usize)>, ) -> VirtioGpuResult { - let mut iovs: Vec = sglist_to_iovecs(&vecs, mem).map_err(|()| ErrUnspec)?; + let mut iovs: Vec = sglist_to_iovecs(&vecs, &mem).map_err(|()| ErrUnspec)?; // Tell virgl to use our iovec array (pointer must stay valid afterwards) self.renderer.attach_backing(resource_id, &mut iovs)?; - // Keep the Vec alive so the buffer’s pointer stays valid + // Keep both the Vec and the GuestMemoryMmap alive so the iovec pointers stay + // valid. let res = self .resources .get_mut(&resource_id) .ok_or(ErrInvalidResourceId)?; res.backing_iovecs.lock().unwrap().replace(iovs); + res.backing_mem = Some(mem); Ok(OkNoData) } @@ -471,8 +489,7 @@ impl Renderer for VirglRendererAdapter { ErrUnspec })?; } else { - let fence_id_u32 = - u32::try_from(fence.fence_id).map_err(|_| GpuResponse::ErrUnspec)?; + let fence_id_u32 = u32::try_from(fence.fence_id).map_err(|_| GpuResponse::ErrUnspec)?; self.renderer .create_fence(fence_id_u32, fence.ctx_id) .map_err(|_| ErrUnspec)?; @@ -585,11 +602,14 @@ impl Renderer for VirglRendererAdapter { .get_mut(&resource_id) .ok_or(ErrInvalidResourceId)?; - // Extract the DMABUF information (handle and info_3d) - let handle = resource.virgl_resource.handle.as_ref().ok_or_else(|| { - error!("resource {resource_id} has no handle"); - ErrUnspec - })?; + // Re-export a fresh FD every time; cached handles cause flickering with Venus. + let handle = self + .renderer + .export_resource_dmabuf(resource_id) + .map_err(|e| { + error!("Failed to export dmabuf for resource {resource_id}: {e:?}"); + ErrUnspec + })?; if handle.handle_type != VIRGL_HANDLE_TYPE_MEM_DMABUF { error!( @@ -671,9 +691,9 @@ impl Renderer for VirglRendererAdapter { } fn flush_resource(&mut self, resource_id: u32, _rect: virtio_gpu_rect) -> VirtioGpuResult { - let Some(gpu_backend) = self.gpu_backend.as_ref() else { + if self.gpu_backend.is_none() { return Ok(OkNoData); - }; + } if resource_id == 0 { return Ok(OkNoData); @@ -685,6 +705,15 @@ impl Renderer for VirglRendererAdapter { .ok_or(ErrInvalidResourceId)? .clone(); + if resource.blob_size == 0 && resource.scanouts.has_any_enabled() { + if let Err(e) = + self.transfer_read(0, resource_id, Transfer3DDesc::new_2d(0, 0, 1, 1, 0), None) + { + warn!("transfer_read sync failed for resource {resource_id}: {e:?}"); + } + } + + let gpu_backend = self.gpu_backend.as_ref().unwrap(); for scanout_id in resource.scanouts.iter_enabled() { // For VirglRenderer, use update_dmabuf_scanout (no image copy) gpu_backend @@ -705,25 +734,125 @@ impl Renderer for VirglRendererAdapter { fn resource_create_blob( &mut self, - _ctx_id: u32, - _resource_id: u32, - _blob_id: u64, - _size: u64, - _blob_mem: u32, - _blob_flags: u32, + ctx_id: u32, + mut resource_create_blob: ResourceCreateBlob, + vecs: Vec<(GuestAddress, usize)>, + mem: &GuestMemoryMmap, ) -> VirtioGpuResult { - error!("Not implemented: resource_create_blob"); - Err(ErrUnspec) + let mut virgl_iovecs = None; + + if resource_create_blob.blob_flags + & crate::protocol::VIRTIO_GPU_BLOB_FLAG_CREATE_GUEST_HANDLE + != 0 + { + error!("GUEST_HANDLE unimplemented for virgl backend"); + return Err(ErrUnspec); + } + + if !vecs.is_empty() { + virgl_iovecs = Some(sglist_to_iovecs(&vecs[..], mem).map_err(|_| ErrUnspec)?); + if resource_create_blob.blob_mem == VIRTIO_GPU_BLOB_MEM_HOST3D { + resource_create_blob.blob_mem = VIRTIO_GPU_BLOB_MEM_HOST3D_GUEST; + } + } + + let virgl_resource = self + .renderer + .create_blob( + ctx_id, + 0, // width + 0, // height + resource_create_blob.resource_id, + resource_create_blob.into(), + virgl_iovecs.as_deref(), + ) + .map_err(|_| ErrUnspec)?; + + let resource = GpuResource { + virgl_resource, + scanouts: AssociatedScanouts::default(), + backing_iovecs: Arc::new(Mutex::new(virgl_iovecs)), + backing_mem: None, + uuid: Uuid::new_v4(), + blob_size: resource_create_blob.size, + blob_shmem_offset: None, + }; + + debug_assert!( + !self + .resources + .contains_key(&resource_create_blob.resource_id), + "Resource ID {} already exists in the resources map.", + resource_create_blob.resource_id + ); + + self.resources + .insert(resource_create_blob.resource_id, resource); + Ok(OkNoData) } - fn resource_map_blob(&mut self, _resource_id: u32, _offset: u64) -> VirtioGpuResult { - error!("Not implemented: resource_map_blob"); - Err(ErrUnspec) + fn resource_map_blob(&mut self, resource_id: u32, offset: u64) -> VirtioGpuResult { + let resource = self + .resources + .get_mut(&resource_id) + .ok_or(ErrInvalidResourceId)?; + + let map_info = resource.virgl_resource.map_info.ok_or_else(|| { + error!("ResourceMapBlob: resource {} has no map_info", resource_id); + ErrUnspec + })?; + + let handle = resource.virgl_resource.handle.as_ref().ok_or_else(|| { + error!("ResourceMapBlob: resource {} has no handle", resource_id); + ErrUnspec + })?; + + // Check handle type - we don't support OPAQUE_FD mapping + if handle.handle_type == VIRGL_HANDLE_TYPE_MEM_OPAQUE_FD { + error!("VIRGL_HANDLE_TYPE_MEM_OPAQUE_FD not supported for mapping"); + return Err(ErrUnspec); + } + + // virgl doesn't provide detailed permissions for mapping, map everything as + // writable + let flags = VhostUserMMapFlags::WRITABLE; + + common_map_blob( + &self.backend, + flags, + &handle.os_handle.as_fd(), + resource.blob_size, + offset, + resource_id, + )?; + + resource.blob_shmem_offset = Some(offset); + + // Return cache flags only (access flags not part of virtio-gpu spec) + Ok(OkMapInfo { + map_info: map_info & VIRGL_MAP_CACHE_MASK, + }) } - fn resource_unmap_blob(&mut self, _resource_id: u32) -> VirtioGpuResult { - error!("Not implemented: resource_unmap_blob"); - Err(ErrUnspec) + fn resource_unmap_blob(&mut self, resource_id: u32) -> VirtioGpuResult { + let resource = self + .resources + .get_mut(&resource_id) + .ok_or(ErrInvalidResourceId)?; + + let Some(offset) = resource.blob_shmem_offset else { + warn!( + "Guest tried to unmap blob resource with resource_id={resource_id}, but it is not \ + mapped!" + ); + return Err(ErrInvalidParameter); + }; + + common_unmap_blob(&self.backend, resource.blob_size, offset)?; + + resource.blob_shmem_offset = None; + + Ok(OkNoData) } } @@ -741,7 +870,10 @@ mod virgl_cov_tests { use super::*; use crate::{ - gpu_types::{FenceDescriptor, FenceState, ResourceCreate3d, Transfer3DDesc, VirtioGpuRing}, + gpu_types::{ + FenceDescriptor, FenceState, ResourceCreate3d, ResourceCreateBlob, Transfer3DDesc, + VirtioGpuRing, + }, protocol::{virtio_gpu_rect, GpuResponse, VIRTIO_GPU_FORMAT_R8G8B8A8_UNORM}, renderer::Renderer, testutils::{ @@ -765,6 +897,11 @@ mod virgl_cov_tests { GpuBackend::from_stream(backend) } + fn dummy_backend() -> Backend { + let (_, backend) = UnixStream::pair().unwrap(); + Backend::from_stream(backend) + } + #[test] fn sglist_to_iovecs_err_on_invalid_slice() { // Single region: 0x1000..0x2000 (4 KiB) @@ -851,8 +988,9 @@ mod virgl_cov_tests { let (vring, _outs, _call_evt) = create_vring(&mem, &[] as &[TestingDescChainArgs], GuestAddress(0x2000), GuestAddress(0x4000), 64); - let backend = dummy_gpu_backend(); - let mut gpu = VirglRendererAdapter::new(&vring, &cfg, Some(backend)).unwrap(); + let backend = dummy_backend(); + let gpu_backend = dummy_gpu_backend(); + let mut gpu = VirglRendererAdapter::new(&vring, backend, &cfg, Some(gpu_backend)).unwrap(); gpu.event_poll(); let edid_req = VhostUserGpuEdidRequest { @@ -872,11 +1010,11 @@ mod virgl_cov_tests { }; gpu.resource_create_3d(res_id, req).unwrap(); - let gm_back = GuestMemoryMmap::from_ranges(&[(GuestAddress(0xA0000), 0x1000)]).unwrap(); + let gm_back = Arc::new(GuestMemoryMmap::from_ranges(&[(GuestAddress(0xA0000), 0x1000)]).unwrap()); let pattern = [0xAA, 0xBB, 0xCC, 0xDD]; gm_back.write(&pattern, GuestAddress(0xA0000)).unwrap(); - gpu.attach_backing(res_id, &gm_back, vec![(GuestAddress(0xA0000), 4usize)]).unwrap(); + gpu.attach_backing(res_id, Arc::clone(&gm_back), vec![(GuestAddress(0xA0000), 4usize)]).unwrap(); // move_cursor: expected to Err with invalid resource id test_move_cursor(&mut gpu); @@ -902,7 +1040,7 @@ mod virgl_cov_tests { // Attach backing for cursor resource let cursor_backing = GuestMemoryMmap::from_ranges(&[(GuestAddress(0xB0000), 0x10000)]).unwrap(); - gpu.attach_backing(cursor_res_id, &cursor_backing, vec![(GuestAddress(0xB0000), 16384usize)]).unwrap(); + gpu.attach_backing(cursor_res_id, Arc::new(cursor_backing), vec![(GuestAddress(0xB0000), 16384usize)]).unwrap(); // This should exercise common_read_cursor_resource and then fail at cursor_update (no frontend) let result = gpu.update_cursor(cursor_res_id, cursor_pos, 5, 5); @@ -960,18 +1098,30 @@ mod virgl_cov_tests { test_capset_operations(&gpu, index); } - // Test blob resource functions (all should return ErrUnspec - not implemented) + // Test blob resource functions assert_matches!( - gpu.resource_create_blob(1, 100, 0, 4096, 0, 0), + gpu.resource_create_blob( + 1, + ResourceCreateBlob { + resource_id: 100, + blob_id: 0, + blob_mem: 0, + blob_flags: 0, + size: 4096, + }, + vec![], + &gm_back + ), Err(GpuResponse::ErrUnspec) ); + // resource 100 was never created, so these return ErrInvalidResourceId assert_matches!( gpu.resource_map_blob(100, 0), - Err(GpuResponse::ErrUnspec) + Err(GpuResponse::ErrInvalidResourceId) ); assert_matches!( gpu.resource_unmap_blob(100), - Err(GpuResponse::ErrUnspec) + Err(GpuResponse::ErrInvalidResourceId) ); // Test resource_assign_uuid with invalid resource diff --git a/vhost-device-gpu/src/device.rs b/vhost-device-gpu/src/device.rs index 2c49b4497..ae6f23e84 100644 --- a/vhost-device-gpu/src/device.rs +++ b/vhost-device-gpu/src/device.rs @@ -22,9 +22,10 @@ macro_rules! handle_adapter { Some(renderer) => renderer, None => { // Pass $vrings to the call - let (control_vring, gpu_backend) = $self.extract_backend_and_vring($vrings)?; + let (control_vring, backend, gpu_backend) = + $self.extract_backend_and_vring($vrings)?; - let renderer = $new_adapter(control_vring, gpu_backend)?; + let renderer = $new_adapter(control_vring, backend, gpu_backend)?; event_poll_fd = renderer.get_event_poll_fd(); maybe_renderer.insert(renderer) @@ -52,7 +53,7 @@ use thiserror::Error as ThisError; use vhost::vhost_user::{ gpu_message::{VhostUserGpuCursorPos, VhostUserGpuEdidRequest}, message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures}, - GpuBackend, + Backend, GpuBackend, }; use vhost_user_backend::{VhostUserBackend, VringEpollHandler, VringRwLock, VringT}; use virtio_bindings::{ @@ -79,7 +80,7 @@ use crate::backend::gfxstream::GfxstreamAdapter; use crate::backend::virgl::VirglRendererAdapter; use crate::{ backend::null::NullAdapter, - gpu_types::{ResourceCreate3d, Transfer3DDesc, VirtioGpuRing}, + gpu_types::{ResourceCreate3d, ResourceCreateBlob, Transfer3DDesc, VirtioGpuRing}, protocol::{ virtio_gpu_ctrl_hdr, virtio_gpu_ctx_create, virtio_gpu_get_edid, virtio_gpu_resource_create_2d, virtio_gpu_resource_create_3d, virtio_gpu_transfer_host_3d, @@ -141,6 +142,7 @@ impl From for io::Error { struct VhostUserGpuBackendInner { virtio_cfg: VirtioGpuConfig, event_idx_enabled: bool, + backend: Option, gpu_backend: Option, exit_consumer: EventConsumer, exit_notifier: EventNotifier, @@ -175,6 +177,7 @@ impl VhostUserGpuBackend { num_capsets: Le32::from(gpu_config.capsets().num_capsets()), }, event_idx_enabled: false, + backend: None, gpu_backend: None, exit_consumer, exit_notifier, @@ -208,7 +211,7 @@ impl VhostUserGpuBackend { impl VhostUserGpuBackendInner { fn process_gpu_command( renderer: &mut dyn Renderer, - mem: &GuestMemoryMmap, + mem: Arc, hdr: virtio_gpu_ctrl_hdr, cmd: GpuCommand, acked_features: u64, @@ -228,7 +231,7 @@ impl VhostUserGpuBackendInner { } GpuCommand::TransferToHost2d(req) => Self::handle_transfer_to_host_2d(renderer, req), GpuCommand::ResourceAttachBacking(req, iovecs) => { - renderer.attach_backing(req.resource_id.into(), mem, iovecs) + renderer.attach_backing(req.resource_id.into(), mem.clone(), iovecs) } GpuCommand::ResourceDetachBacking(req) => { renderer.detach_backing(req.resource_id.into()) @@ -268,18 +271,27 @@ impl VhostUserGpuBackendInner { fence_ids, mut cmd_data, } => renderer.submit_command(hdr.ctx_id.into(), &mut cmd_data, &fence_ids), - GpuCommand::ResourceCreateBlob(_) => { - panic!("virtio_gpu: GpuCommand::ResourceCreateBlob unimplemented") - } + GpuCommand::ResourceCreateBlob(info, vecs) => renderer.resource_create_blob( + hdr.ctx_id.into(), + ResourceCreateBlob { + resource_id: info.resource_id.into(), + blob_id: info.blob_id.into(), + blob_mem: info.blob_mem.into(), + blob_flags: info.blob_flags.into(), + size: info.size.into(), + }, + vecs, + &mem, + ), GpuCommand::SetScanoutBlob(_) => { panic!("virtio_gpu: GpuCommand::SetScanoutBlob unimplemented") } - GpuCommand::ResourceMapBlob(_) => { - panic!("virtio_gpu: GpuCommand::ResourceMapBlob unimplemented") + GpuCommand::ResourceMapBlob(info) => { + renderer.resource_map_blob(info.resource_id.into(), info.offset.into()) } - GpuCommand::ResourceUnmapBlob(_) => { - panic!("virtio_gpu: GpuCommand::ResourceUnmapBlob unimplemented") + GpuCommand::ResourceUnmapBlob(info) => { + renderer.resource_unmap_blob(info.resource_id.into()) } } } @@ -442,7 +454,7 @@ impl VhostUserGpuBackendInner { let cmd_name = gpu_cmd.command_name(); let response_result = Self::process_gpu_command( renderer, - &mem, + mem.clone(), ctrl_hdr, gpu_cmd, self.acked_features, @@ -609,17 +621,19 @@ impl VhostUserGpuBackendInner { fn extract_backend_and_vring<'a>( &mut self, vrings: &'a [VringRwLock], - ) -> IoResult<(&'a VringRwLock, Option)> { + ) -> IoResult<(&'a VringRwLock, Backend, Option)> { let control_vring = &vrings[CONTROL_QUEUE as usize]; - let backend = self.gpu_backend.take(); - - if !self.gpu_config.flags().headless && backend.is_none() { + let backend = self + .backend + .take() + .ok_or_else(|| io::Error::other("set_backend_req_fd() not called, Backend missing"))?; + let gpu_backend = self.gpu_backend.take(); + if !self.gpu_config.flags().headless && gpu_backend.is_none() { return Err(io::Error::other( "set_gpu_socket() not called, GpuBackend missing", )); } - - Ok((control_vring, backend)) + Ok((control_vring, backend, gpu_backend)) } fn lazy_init_and_handle_event( @@ -639,9 +653,10 @@ impl VhostUserGpuBackendInner { GpuMode::Gfxstream => handle_adapter!( GfxstreamAdapter, TLS_GFXSTREAM, - |control_vring, gpu_backend| -> io::Result { + |control_vring, backend, gpu_backend| -> io::Result { Ok(GfxstreamAdapter::new( control_vring, + backend, &self.gpu_config, gpu_backend, )) @@ -655,8 +670,8 @@ impl VhostUserGpuBackendInner { GpuMode::VirglRenderer => handle_adapter!( VirglRendererAdapter, TLS_VIRGL, - |control_vring, gpu_backend| { - VirglRendererAdapter::new(control_vring, &self.gpu_config, gpu_backend) + |control_vring, backend, gpu_backend| { + VirglRendererAdapter::new(control_vring, backend, &self.gpu_config, gpu_backend) }, self, device_event, @@ -666,10 +681,11 @@ impl VhostUserGpuBackendInner { GpuMode::Null => handle_adapter!( NullAdapter, TLS_NULL, - |control_vring, gpu_backend| -> io::Result { + |control_vring, backend, gpu_backend| -> io::Result { Ok(NullAdapter::new( control_vring, &self.gpu_config, + backend, gpu_backend, )) }, @@ -731,7 +747,11 @@ impl VhostUserBackend for VhostUserGpuBackend { fn protocol_features(&self) -> VhostUserProtocolFeatures { debug!("Protocol features called"); - VhostUserProtocolFeatures::CONFIG | VhostUserProtocolFeatures::MQ + VhostUserProtocolFeatures::CONFIG + | VhostUserProtocolFeatures::MQ + | VhostUserProtocolFeatures::BACKEND_REQ + | VhostUserProtocolFeatures::BACKEND_SEND_FD + | VhostUserProtocolFeatures::SHMEM } fn acked_features(&self, features: u64) { @@ -750,6 +770,11 @@ impl VhostUserBackend for VhostUserGpuBackend { Ok(()) } + fn set_backend_req_fd(&self, backend: Backend) { + trace!("Got set_backend_req_fd"); + self.inner.lock().unwrap().backend = Some(backend); + } + fn set_gpu_socket(&self, backend: GpuBackend) -> IoResult<()> { self.inner.lock().unwrap().gpu_backend = Some(backend); Ok(()) @@ -829,7 +854,7 @@ mod tests { use super::*; use crate::{ backend::virgl::VirglRendererAdapter, - gpu_types::{ResourceCreate3d, Transfer3DDesc, VirtioGpuRing}, + gpu_types::{ResourceCreate3d, ResourceCreateBlob, Transfer3DDesc, VirtioGpuRing}, protocol::{ virtio_gpu_ctrl_hdr, virtio_gpu_ctx_create, virtio_gpu_ctx_destroy, virtio_gpu_ctx_resource, virtio_gpu_get_capset, virtio_gpu_get_capset_info, @@ -864,11 +889,9 @@ mod tests { fn resource_create_blob( &mut self, ctx_id: u32, - resource_id: u32, - blob_id: u64, - size: u64, - blob_mem: u32, - blob_flags: u32, + resource_create_blob: ResourceCreateBlob, + vecs: Vec<(GuestAddress, usize)>, + mem: &GuestMemoryMmap, ) -> VirtioGpuResult; fn resource_map_blob(&mut self, resource_id: u32, offset: u64) -> VirtioGpuResult; fn resource_unmap_blob(&mut self, resource_id: u32) -> VirtioGpuResult; @@ -886,7 +909,7 @@ mod tests { fn attach_backing( &mut self, resource_id: u32, - mem: &GuestMemoryMmap, + mem: Arc, vecs: Vec<(GuestAddress, usize)>, ) -> VirtioGpuResult; fn detach_backing(&mut self, resource_id: u32) -> VirtioGpuResult; @@ -960,6 +983,11 @@ mod tests { (frontend, backend) } + fn dummy_backend_request_socket() -> Backend { + let (_frontend, backend) = UnixStream::pair().unwrap(); + Backend::from_stream(backend) + } + #[test] fn test_process_gpu_command() { let (_, mem) = init(); @@ -972,7 +1000,7 @@ mod tests { setup(&mut mock_renderer); VhostUserGpuBackendInner::process_gpu_command( &mut mock_renderer, - &mem.memory(), + mem.memory().into_inner(), hdr, cmd, acked_features, @@ -1470,7 +1498,10 @@ mod tests { .unwrap(); let (control_vring, _, _) = create_control_vring(&mem, &[]); - let mut adapter = VirglRendererAdapter::new(&control_vring, &config, None).unwrap(); + let (backend_stream, _) = UnixStream::pair().unwrap(); + let backend = Backend::from_stream(backend_stream); + let mut adapter = + VirglRendererAdapter::new(&control_vring, backend, &config, None).unwrap(); let resource_id = 42; let create_req = ResourceCreate3d { @@ -1510,7 +1541,11 @@ mod tests { assert_eq!(backend.features(), 0x0101_7100_001F); assert_eq!( backend.protocol_features(), - VhostUserProtocolFeatures::CONFIG | VhostUserProtocolFeatures::MQ + VhostUserProtocolFeatures::CONFIG + | VhostUserProtocolFeatures::MQ + | VhostUserProtocolFeatures::BACKEND_REQ + | VhostUserProtocolFeatures::BACKEND_SEND_FD + | VhostUserProtocolFeatures::SHMEM ); assert_eq!(backend.queues_per_thread(), vec![0xffff_ffff]); assert_eq!(backend.get_config(0, 0), Vec::::new()); @@ -1532,7 +1567,7 @@ mod tests { let vring = VringRwLock::new(mem, 0x1000).unwrap(); vring.set_queue_info(0x100, 0x200, 0x300).unwrap(); vring.set_queue_ready(true); - + backend.set_backend_req_fd(dummy_backend_request_socket()); assert_eq!( backend .handle_event(0, EventSet::OUT, &[vring.clone()], 0) @@ -1551,6 +1586,7 @@ mod tests { // Hit the loop part backend.set_event_idx(true); + backend.set_backend_req_fd(dummy_backend_request_socket()); backend .handle_event(0, EventSet::IN, &[vring.clone()], 0) .unwrap(); @@ -1653,6 +1689,7 @@ mod tests { .unwrap(); backend.set_gpu_socket(gpu_backend).unwrap(); + backend.set_backend_req_fd(dummy_backend_request_socket()); // Unfortunately, there is no way to create a VringEpollHandler directly (the ::new is not public) // So we create a daemon to create the epoll handler for us here diff --git a/vhost-device-gpu/src/gpu_types.rs b/vhost-device-gpu/src/gpu_types.rs index 6c9df9e6b..8380106a9 100644 --- a/vhost-device-gpu/src/gpu_types.rs +++ b/vhost-device-gpu/src/gpu_types.rs @@ -48,6 +48,22 @@ macro_rules! impl_from_resource_create3d { }; } +#[cfg(any(feature = "backend-virgl", feature = "backend-gfxstream"))] +macro_rules! impl_from_resource_create_blob { + ($target:ty) => { + impl From for $target { + fn from(r: ResourceCreateBlob) -> Self { + Self { + blob_id: r.blob_id, + blob_mem: r.blob_mem, + blob_flags: r.blob_flags, + size: r.size, + } + } + } + }; +} + use std::{collections::BTreeMap, os::raw::c_void}; #[cfg(feature = "backend-gfxstream")] @@ -147,6 +163,21 @@ impl_from_resource_create3d!(rutabaga_gfx::ResourceCreate3D); #[cfg(feature = "backend-virgl")] impl_from_resource_create3d!(virglrenderer::ResourceCreate3D); +#[cfg(feature = "backend-gfxstream")] +impl_from_resource_create_blob!(rutabaga_gfx::ResourceCreateBlob); +#[cfg(feature = "backend-virgl")] +impl_from_resource_create_blob!(virglrenderer::ResourceCreateBlob); + +/// Parameters for creating a blob resource. +#[derive(Debug, Clone, Copy)] +pub struct ResourceCreateBlob { + pub resource_id: u32, + pub blob_id: u64, + pub blob_mem: u32, + pub blob_flags: u32, + pub size: u64, +} + #[derive(Debug, Clone, Copy)] pub struct ResourceCreate2d { pub resource_id: u32, diff --git a/vhost-device-gpu/src/protocol.rs b/vhost-device-gpu/src/protocol.rs index a7df02629..cbb53f3a3 100644 --- a/vhost-device-gpu/src/protocol.rs +++ b/vhost-device-gpu/src/protocol.rs @@ -81,11 +81,16 @@ pub const VIRTIO_GPU_BIND_RENDER_TARGET: u32 = 2; pub const VIRTIO_GPU_MAX_SCANOUTS: u32 = 16; +/// Limit on memory entries per resource to prevent excessive allocation +pub const MAX_MEM_ENTRIES: u32 = 1024; + /// `CHROMIUM(b/277982577)` success responses pub const VIRTIO_GPU_RESP_OK_RESOURCE_PLANE_INFO: u32 = 0x11FF; /// Create a OS-specific handle from guest memory (not upstreamed). pub const VIRTIO_GPU_BLOB_FLAG_CREATE_GUEST_HANDLE: u32 = 0x0008; +/// Host memory with guest-visible iovecs (not in virtio_bindings yet). +pub const VIRTIO_GPU_BLOB_MEM_HOST3D_GUEST: u32 = 0x0003; pub const VIRTIO_GPU_FLAG_FENCE: u32 = 1 << 0; pub const VIRTIO_GPU_FLAG_INFO_RING_IDX: u32 = 1 << 1; @@ -707,7 +712,7 @@ pub enum GpuCommand { cmd_data: Vec, fence_ids: Vec, }, - ResourceCreateBlob(virtio_gpu_resource_create_blob), + ResourceCreateBlob(virtio_gpu_resource_create_blob, Vec<(GuestAddress, usize)>), ResourceMapBlob(virtio_gpu_resource_map_blob), ResourceUnmapBlob(virtio_gpu_resource_unmap_blob), UpdateCursor(virtio_gpu_update_cursor), @@ -753,6 +758,27 @@ impl fmt::Debug for GpuCommand { } } +fn read_mem_entries( + reader: &mut Reader, + num_entries: u32, +) -> Result, GpuCommandDecodeError> { + if num_entries > MAX_MEM_ENTRIES { + return Err(Error::DescriptorReadFailed.into()); + } + + let mut entries = Vec::with_capacity(num_entries as usize); + + for _ in 0..num_entries { + let entry: virtio_gpu_mem_entry = + reader.read_obj().map_err(|_| Error::DescriptorReadFailed)?; + entries.push(( + GuestAddress(entry.addr.into()), + entry.length.to_native() as usize, + )) + } + Ok(entries) +} + impl GpuCommand { pub const fn command_name(&self) -> &'static str { use GpuCommand::*; @@ -777,7 +803,7 @@ impl GpuCommand { TransferToHost3d(_info) => "TransferToHost3d", TransferFromHost3d(_info) => "TransferFromHost3d", CmdSubmit3d { .. } => "CmdSubmit3d", - ResourceCreateBlob(_info) => "ResourceCreateBlob", + ResourceCreateBlob(_info, _) => "ResourceCreateBlob", ResourceMapBlob(_info) => "ResourceMapBlob", ResourceUnmapBlob(_info) => "ResourceUnmapBlob", UpdateCursor(_info) => "UpdateCursor", @@ -824,16 +850,7 @@ impl GpuCommand { VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING => { let info: virtio_gpu_resource_attach_backing = reader.read_obj().map_err(|_| Error::DescriptorReadFailed)?; - let mut entries = - Vec::with_capacity(>::into(info.nr_entries) as usize); - for _ in 0..info.nr_entries.into() { - let entry: virtio_gpu_mem_entry = - reader.read_obj().map_err(|_| Error::DescriptorReadFailed)?; - entries.push(( - GuestAddress(entry.addr.into()), - >::into(entry.length) as usize, - )); - } + let entries = read_mem_entries(reader, info.nr_entries.into())?; ResourceAttachBacking(info, entries) } VIRTIO_GPU_CMD_RESOURCE_DETACH_BACKING => { @@ -891,7 +908,11 @@ impl GpuCommand { } } VIRTIO_GPU_CMD_RESOURCE_CREATE_BLOB => { - ResourceCreateBlob(reader.read_obj().map_err(|_| Error::DescriptorReadFailed)?) + let info: virtio_gpu_resource_create_blob = + reader.read_obj().map_err(|_| Error::DescriptorReadFailed)?; + + let entries = read_mem_entries(reader, info.nr_entries.into())?; + ResourceCreateBlob(info, entries) } VIRTIO_GPU_CMD_RESOURCE_MAP_BLOB => { ResourceMapBlob(reader.read_obj().map_err(|_| Error::DescriptorReadFailed)?) @@ -1311,7 +1332,7 @@ mod tests { "CmdSubmit3d", ), ( - ResourceCreateBlob(virtio_gpu_resource_create_blob::default()), + ResourceCreateBlob(virtio_gpu_resource_create_blob::default(), Vec::new()), "ResourceCreateBlob", ), ( diff --git a/vhost-device-gpu/src/renderer.rs b/vhost-device-gpu/src/renderer.rs index 49d35573b..85f15c340 100644 --- a/vhost-device-gpu/src/renderer.rs +++ b/vhost-device-gpu/src/renderer.rs @@ -2,13 +2,15 @@ // // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause +use std::sync::Arc; + use rutabaga_gfx::RutabagaFence; use vhost::vhost_user::gpu_message::{VhostUserGpuCursorPos, VhostUserGpuEdidRequest}; use vm_memory::{GuestAddress, GuestMemoryMmap, VolatileSlice}; use vmm_sys_util::eventfd::EventFd; use crate::{ - gpu_types::{ResourceCreate3d, Transfer3DDesc, VirtioGpuRing}, + gpu_types::{ResourceCreate3d, ResourceCreateBlob, Transfer3DDesc, VirtioGpuRing}, protocol::{virtio_gpu_rect, VirtioGpuResult}, }; @@ -38,7 +40,7 @@ pub trait Renderer: Send + Sync { fn attach_backing( &mut self, resource_id: u32, - mem: &GuestMemoryMmap, + mem: Arc, vecs: Vec<(GuestAddress, usize)>, ) -> VirtioGpuResult; fn detach_backing(&mut self, resource_id: u32) -> VirtioGpuResult; @@ -91,11 +93,9 @@ pub trait Renderer: Send + Sync { fn resource_create_blob( &mut self, ctx_id: u32, - resource_id: u32, - blob_id: u64, - size: u64, - blob_mem: u32, - blob_flags: u32, + resource_create_blob: ResourceCreateBlob, + vecs: Vec<(vm_memory::GuestAddress, usize)>, + mem: &vm_memory::GuestMemoryMmap, ) -> VirtioGpuResult; fn resource_map_blob(&mut self, resource_id: u32, offset: u64) -> VirtioGpuResult; fn resource_unmap_blob(&mut self, resource_id: u32) -> VirtioGpuResult; From e128fdb4001bb4048ad8f40fc46cb24b5cfb6705 Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Tue, 9 Dec 2025 19:13:20 +0100 Subject: [PATCH 5/9] gpu: Expose Venus capset for virglrenderer backend Expose Venus capability set support using the virglrenderer backend, enabled by the new blob resource support. Signed-off-by: Matej Hrica --- vhost-device-gpu/src/backend/virgl.rs | 5 ++++- vhost-device-gpu/src/lib.rs | 10 +++++++--- vhost-device-gpu/src/main.rs | 4 ++++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/vhost-device-gpu/src/backend/virgl.rs b/vhost-device-gpu/src/backend/virgl.rs index 0c3408caf..7b93ec5c0 100644 --- a/vhost-device-gpu/src/backend/virgl.rs +++ b/vhost-device-gpu/src/backend/virgl.rs @@ -174,9 +174,12 @@ impl VirglRendererAdapter { gpu_backend: Option, ) -> io::Result { let capsets = config.capsets(); + let venus_enabled = capsets.contains(GpuCapset::VENUS); + let virglrenderer_flags = VirglRendererFlags::new() .use_virgl(true) - .use_venus(true) + .use_venus(venus_enabled) + .use_render_server(venus_enabled) .use_egl(config.flags().use_egl) .use_gles(config.flags().use_gles) .use_glx(config.flags().use_glx) diff --git a/vhost-device-gpu/src/lib.rs b/vhost-device-gpu/src/lib.rs index 47d2c3634..39d939d48 100644 --- a/vhost-device-gpu/src/lib.rs +++ b/vhost-device-gpu/src/lib.rs @@ -36,7 +36,7 @@ use log::info; #[cfg(feature = "backend-gfxstream")] use rutabaga_gfx::{RUTABAGA_CAPSET_GFXSTREAM_GLES, RUTABAGA_CAPSET_GFXSTREAM_VULKAN}; #[cfg(feature = "backend-virgl")] -use rutabaga_gfx::{RUTABAGA_CAPSET_VIRGL, RUTABAGA_CAPSET_VIRGL2}; +use rutabaga_gfx::{RUTABAGA_CAPSET_VENUS, RUTABAGA_CAPSET_VIRGL, RUTABAGA_CAPSET_VIRGL2}; use thiserror::Error as ThisError; use vhost_user_backend::VhostUserDaemon; use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap}; @@ -80,7 +80,9 @@ bitflags! { #[cfg(feature = "backend-virgl")] const VIRGL2 = 1 << RUTABAGA_CAPSET_VIRGL2 as u64; #[cfg(feature = "backend-virgl")] - const ALL_VIRGLRENDERER_CAPSETS = Self::VIRGL.bits() | Self::VIRGL2.bits(); + const VENUS = 1 << RUTABAGA_CAPSET_VENUS as u64; + #[cfg(feature = "backend-virgl")] + const ALL_VIRGLRENDERER_CAPSETS = Self::VIRGL.bits() | Self::VIRGL2.bits() | Self::VENUS.bits(); #[cfg(feature = "backend-gfxstream")] const GFXSTREAM_VULKAN = 1 << RUTABAGA_CAPSET_GFXSTREAM_VULKAN as u64; @@ -110,6 +112,8 @@ impl Display for GpuCapset { Self::VIRGL => write!(f, "virgl")?, #[cfg(feature = "backend-virgl")] Self::VIRGL2 => write!(f, "virgl2")?, + #[cfg(feature = "backend-virgl")] + Self::VENUS => write!(f, "venus")?, #[cfg(feature = "backend-gfxstream")] Self::GFXSTREAM_VULKAN => write!(f, "gfxstream-vulkan")?, #[cfg(feature = "backend-gfxstream")] @@ -443,7 +447,7 @@ mod tests { #[test] fn test_default_num_capsets() { #[cfg(feature = "backend-virgl")] - assert_eq!(DEFAULT_VIRGLRENDER_CAPSET_MASK.num_capsets(), 2); + assert_eq!(DEFAULT_VIRGLRENDER_CAPSET_MASK.num_capsets(), 3); #[cfg(feature = "backend-gfxstream")] assert_eq!(DEFAULT_GFXSTREAM_CAPSET_MASK.num_capsets(), 2); } diff --git a/vhost-device-gpu/src/main.rs b/vhost-device-gpu/src/main.rs index 53fe8bea4..97cf2dc87 100644 --- a/vhost-device-gpu/src/main.rs +++ b/vhost-device-gpu/src/main.rs @@ -26,6 +26,10 @@ pub enum CapsetName { #[cfg(feature = "backend-virgl")] Virgl2 = GpuCapset::VIRGL2.bits(), + /// [virglrenderer] Venus (Vulkan) implementation + #[cfg(feature = "backend-virgl")] + Venus = GpuCapset::VENUS.bits(), + /// [gfxstream] Vulkan implementation (partial support only){n} /// NOTE: Can only be used for 2D display output for now, there is no /// hardware acceleration yet From 3afefbe12e0d523ee9d99f44444e54d3066d7589 Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Tue, 9 Dec 2025 17:01:10 +0100 Subject: [PATCH 6/9] gpu: Fix incorrect assertion in test virgl_end_to_end_once The test was trying to obtain capset info for 3 capsets, but only 2 were configured. The test previously passed because the virgl backend incorrectly returned information for the additional capset. Now with 3 capsets (VIRGL, VIRGL2, VENUS), update the test to properly iterate over all configured capsets. Signed-off-by: Matej Hrica Co-authored-by: Dorinda Bassey --- vhost-device-gpu/src/backend/virgl.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/vhost-device-gpu/src/backend/virgl.rs b/vhost-device-gpu/src/backend/virgl.rs index 7b93ec5c0..972491169 100644 --- a/vhost-device-gpu/src/backend/virgl.rs +++ b/vhost-device-gpu/src/backend/virgl.rs @@ -977,10 +977,12 @@ mod virgl_cov_tests { } assert!(call_b.read().is_err(), "no signal when no match"); + let capsets = GpuCapset::VIRGL | GpuCapset::VIRGL2; + // Initialize virgl ONCE in this forked process; exercise adapter paths let cfg = GpuConfigBuilder::default() .set_gpu_mode(GpuMode::VirglRenderer) - .set_capset(GpuCapset::VIRGL | GpuCapset::VIRGL2) + .set_capset(capsets) .set_flags(GpuFlags::default()) .build() .expect("GpuConfig"); @@ -1097,7 +1099,7 @@ mod virgl_cov_tests { assert_matches!(gpu.flush_resource(0, dirty), Ok(GpuResponse::OkNoData)); // Test capset queries - for index in [0, 1, 2] { + for index in 0..capsets.num_capsets() { test_capset_operations(&gpu, index); } From 2f384b3959841ed298b2c8a147460b918e1f1c98 Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Tue, 9 Dec 2025 17:18:20 +0100 Subject: [PATCH 7/9] gpu: Fix context name parsing for raw strings Use String::from_utf8_lossy() instead of CStr::from_bytes_with_nul() since the virtio-gpu driver sends debug_name without null terminator. Signed-off-by: Matej Hrica Signed-off-by: Dorinda Bassey --- vhost-device-gpu/src/device.rs | 4 ++-- vhost-device-gpu/src/protocol.rs | 38 ++++++++++++++++++++------------ 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/vhost-device-gpu/src/device.rs b/vhost-device-gpu/src/device.rs index ae6f23e84..e0bdeed00 100644 --- a/vhost-device-gpu/src/device.rs +++ b/vhost-device-gpu/src/device.rs @@ -370,11 +370,11 @@ impl VhostUserGpuBackendInner { hdr: virtio_gpu_ctrl_hdr, req: virtio_gpu_ctx_create, ) -> VirtioGpuResult { - let context_name: Option = Some(req.get_debug_name()); + let context_name = req.get_debug_name(); renderer.create_context( hdr.ctx_id.into(), req.context_init.into(), - context_name.as_deref(), + Some(&context_name), ) } diff --git a/vhost-device-gpu/src/protocol.rs b/vhost-device-gpu/src/protocol.rs index cbb53f3a3..3adbaa03f 100644 --- a/vhost-device-gpu/src/protocol.rs +++ b/vhost-device-gpu/src/protocol.rs @@ -6,9 +6,9 @@ #![allow(non_camel_case_types)] use std::{ + borrow::Cow, cmp::min, convert::From, - ffi::CStr, fmt::{self, Display}, io::{self, Read, Write}, marker::PhantomData, @@ -446,14 +446,9 @@ impl Default for virtio_gpu_ctx_create { } impl virtio_gpu_ctx_create { - pub fn get_debug_name(&self) -> String { - CStr::from_bytes_with_nul( - &self.debug_name[..min(64, >::into(self.nlen) as usize)], - ) - .map_or_else( - |err| format!("Err({err})"), - |c_str| c_str.to_string_lossy().into_owned(), - ) + pub fn get_debug_name(&self) -> Cow<'_, str> { + let len = min(64, >::into(self.nlen) as usize); + String::from_utf8_lossy(&self.debug_name[..len]) } } impl fmt::Debug for virtio_gpu_ctx_create { @@ -1364,8 +1359,9 @@ mod tests { #[test] fn test_virtio_gpu_ctx_create_debug() { - let bytes = b"test_debug\0"; - let original = virtio_gpu_ctx_create { + // Test without null terminator (typical case) + let bytes = b"test_debug"; + let ctx = virtio_gpu_ctx_create { debug_name: { let mut debug_name = [0; 64]; debug_name[..bytes.len()].copy_from_slice(bytes); @@ -1374,12 +1370,26 @@ mod tests { context_init: 0.into(), nlen: (bytes.len() as u32).into(), }; - - let debug_string = format!("{original:?}"); assert_eq!( - debug_string, + format!("{ctx:?}"), "virtio_gpu_ctx_create { debug_name: \"test_debug\", context_init: Le32(0), .. }" ); + + // Test with null terminator included in nlen (edge case - should preserve it) + let bytes_with_null = b"test_debug\0"; + let ctx_with_null = virtio_gpu_ctx_create { + debug_name: { + let mut debug_name = [0; 64]; + debug_name[..bytes_with_null.len()].copy_from_slice(bytes_with_null); + debug_name + }, + context_init: 0.into(), + nlen: (bytes_with_null.len() as u32).into(), + }; + assert_eq!( + format!("{ctx_with_null:?}"), + "virtio_gpu_ctx_create { debug_name: \"test_debug\\0\", context_init: Le32(0), .. }" + ); } #[test] From 2ee5f58eaa5b9490188514f962438bf9809c6485 Mon Sep 17 00:00:00 2001 From: Matej Hrica Date: Wed, 10 Dec 2025 18:10:41 +0100 Subject: [PATCH 8/9] gpu: Enable VIRGL only when requested in capsets Configure virglrenderer based on the user's capset selection. Enable virgl renderer if VIRGL or VIRGL2 capsets are requested, and Enable Venus/render-server if VENUS capset is requested. Signed-off-by: Matej Hrica Co-authored-by: Dorinda Bassey --- vhost-device-gpu/src/backend/virgl.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vhost-device-gpu/src/backend/virgl.rs b/vhost-device-gpu/src/backend/virgl.rs index 972491169..8ca9dd05a 100644 --- a/vhost-device-gpu/src/backend/virgl.rs +++ b/vhost-device-gpu/src/backend/virgl.rs @@ -175,9 +175,10 @@ impl VirglRendererAdapter { ) -> io::Result { let capsets = config.capsets(); let venus_enabled = capsets.contains(GpuCapset::VENUS); + let virgl_enabled = !(capsets & (GpuCapset::VIRGL | GpuCapset::VIRGL2)).is_empty(); let virglrenderer_flags = VirglRendererFlags::new() - .use_virgl(true) + .use_virgl(virgl_enabled) .use_venus(venus_enabled) .use_render_server(venus_enabled) .use_egl(config.flags().use_egl) From d9746dfdebf26d535b24e48746d4d7632adc56b5 Mon Sep 17 00:00:00 2001 From: Dorinda Bassey Date: Mon, 3 Aug 2026 17:00:15 +0200 Subject: [PATCH 9/9] gpu: Add blob_alignment config field Add the blob_alignment field to VirtioGpuConfig to match the virtio-gpu spec. Set it to the system page size. This is a new config introduced in the spec and QEMU. Signed-off-by: Dorinda Bassey --- vhost-device-gpu/src/device.rs | 2 ++ vhost-device-gpu/src/protocol.rs | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/vhost-device-gpu/src/device.rs b/vhost-device-gpu/src/device.rs index e0bdeed00..b320f58e5 100644 --- a/vhost-device-gpu/src/device.rs +++ b/vhost-device-gpu/src/device.rs @@ -175,6 +175,8 @@ impl VhostUserGpuBackend { events_clear: 0.into(), num_scanouts: Le32::from(VIRTIO_GPU_MAX_SCANOUTS), num_capsets: Le32::from(gpu_config.capsets().num_capsets()), + // SAFETY: sysconf(_SC_PAGESIZE) is always safe and returns the page size. + blob_alignment: Le32::from(unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as u32), }, event_idx_enabled: false, backend: None, diff --git a/vhost-device-gpu/src/protocol.rs b/vhost-device-gpu/src/protocol.rs index 3adbaa03f..3dadd9d8d 100644 --- a/vhost-device-gpu/src/protocol.rs +++ b/vhost-device-gpu/src/protocol.rs @@ -107,6 +107,8 @@ pub struct VirtioGpuConfig { pub num_scanouts: Le32, /// Maximum number of capability sets supported by the device pub num_capsets: Le32, + /// Minimum resource blob alignment + pub blob_alignment: Le32, } // SAFETY: The layout of the structure is fixed and can be initialized by @@ -1205,7 +1207,7 @@ mod tests { #[test] fn test_virtio_gpu_config() { // Test VirtioGpuConfig size - assert_eq!(std::mem::size_of::(), 16); + assert_eq!(std::mem::size_of::(), 20); } #[test]