diff --git a/coverage_config_x86_64.json b/coverage_config_x86_64.json index 69ca8a6df..7686469c0 100644 --- a/coverage_config_x86_64.json +++ b/coverage_config_x86_64.json @@ -1,5 +1,5 @@ { - "coverage_score": 83.51, + "coverage_score": 83.00, "exclude_path": "xtask", "crate_features": "" } 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/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 } diff --git a/vhost-device-gpu/README.md b/vhost-device-gpu/README.md index 92d6e0bbf..371087618 100644 --- a/vhost-device-gpu/README.md +++ b/vhost-device-gpu/README.md @@ -27,6 +27,7 @@ A virtio-gpu device using the vhost-user protocol. Possible values: - virgl: [virglrenderer] OpenGL implementation, superseded by Virgl2 - virgl2: [virglrenderer] OpenGL implementation + - venus: [virglrenderer] Vulkan implementation with hardware acceleration - gfxstream-vulkan: [gfxstream] Vulkan implementation (partial support only) NOTE: Can only be used for 2D display output for now, there is no hardware acceleration yet - gfxstream-gles: [gfxstream] OpenGL ES implementation (partial support only) @@ -82,21 +83,13 @@ with GNU libc, so the CI is setup to not build this device for musl targets. It might be possible to build those libraries using musl and then build the gpu device, but this is not tested. -We are currently only supporting sharing the display output to QEMU through a -socket using the transfer_read operation triggered by -`VIRTIO_GPU_CMD_TRANSFER_FROM_HOST_3D` to transfer data from and to virtio-gpu 3D -resources. It'll be nice to have support for directly sharing display output -resource using dmabuf. - -This device does not yet support the `VIRTIO_GPU_CMD_RESOURCE_CREATE_BLOB`, -`VIRTIO_GPU_CMD_SET_SCANOUT_BLOB` and `VIRTIO_GPU_CMD_RESOURCE_ASSIGN_UUID` features. -This requires https://github.com/rust-vmm/vhost/pull/251, which in turn requires QEMU API stabilization. -Because blob resources are not yet supported, some capsets are limited: -- Venus (Vulkan implementation in virglrenderer project) support is not available at all. -- gfxstream-vulkan and gfxstream-gles support are exposed, but can practically only be used for display output, there is no hardware acceleration yet. +This device does not yet support the `VIRTIO_GPU_CMD_SET_SCANOUT_BLOB` feature. +Regular SET_SCANOUT handles both 2D and blob resources. + ## Features This crate supports three GPU backends: virglrenderer, gfxstream (both enabled by default), and null. +Blob resource support enables Venus (Vulkan) for hardware-accelerated Vulkan rendering. The **virglrenderer** backend uses the [virglrenderer-rs](https://crates.io/crates/virglrenderer-rs) crate, which provides Rust bindings to the native virglrenderer library. It translates 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..29a2f8855 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,123 @@ 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)?; + + let handle = export.as_mesa_handle().ok_or(ErrUnspec)?; + + // Check handle type - we don't support OPAQUE_FD mapping + if handle.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, + &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 & 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 +896,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 +945,7 @@ mod gfx_fence_tests { }); Some(GfxstreamAdapter { + backend: dummy_backend(), gpu_backend: Some(dummy_gpu_backend()), resources: BTreeMap::default(), fence_state, @@ -873,7 +996,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) @@ -916,6 +1039,20 @@ mod gfx_fence_tests { gfxstream_gpu.create_context(1, 0, None).unwrap(); gfxstream_gpu.context_attach_resource(1, 1).unwrap_err(); gfxstream_gpu.context_detach_resource(1, 1).unwrap_err(); + + // Test blob resource creation (skip map/unmap becuase it require full vhost-user backend) + let gm_back = GuestMemoryMmap::from_ranges(&[(GuestAddress(0x1000), 0x1000)]).unwrap(); + let vecs = vec![(GuestAddress(0x1000), 4096usize)]; + let blob_create = ResourceCreateBlob { + resource_id: 2, + blob_id: 0, + blob_mem: 1, + blob_flags: 1, + size: 4096, + }; + gfxstream_gpu.resource_create_blob(1, blob_create, vecs, &gm_back).unwrap(); + gfxstream_gpu.unref_resource(2).unwrap(); + gfxstream_gpu.destroy_context(1).unwrap(); } @@ -964,7 +1101,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 +1275,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 +1296,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 +1314,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 9118c03af..06ee9d6a7 100644 --- a/vhost-device-gpu/src/backend/virgl.rs +++ b/vhost-device-gpu/src/backend/virgl.rs @@ -21,38 +21,40 @@ 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_MAX_SCANOUTS, + VirtioGpuResult, VIRTIO_GPU_BLOB_MEM_HOST3D_GUEST, VIRTIO_GPU_FLAG_INFO_RING_IDX, + 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}")] @@ -74,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( @@ -151,6 +157,8 @@ impl FenceHandler for VirglFenceHandler { pub struct VirglRendererAdapter { renderer: VirglRenderer, + capsets: GpuCapset, + backend: Backend, gpu_backend: Option, fence_state: Arc>, resources: BTreeMap, @@ -161,12 +169,18 @@ pub struct VirglRendererAdapter { impl VirglRendererAdapter { pub fn new( queue_ctl: &VringRwLock, + backend: Backend, config: &GpuConfig, gpu_backend: Option, ) -> io::Result { + let capsets = config.capsets(); + let venus_enabled = capsets.contains(GpuCapset::VENUS); + let virgl_enabled = capsets.intersects(GpuCapset::VIRGL | GpuCapset::VIRGL2); + let virglrenderer_flags = VirglRendererFlags::new() - .use_virgl(true) - .use_venus(true) + .use_virgl(virgl_enabled) + .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) @@ -191,7 +205,9 @@ impl VirglRendererAdapter { let renderer = VirglRenderer::init(virglrenderer_flags, fence_handler, None, drm_fd) .map_err(VirglAdapterError::InitVirglRenderer)?; Ok(Self { + capsets, renderer, + backend, gpu_backend, fence_state, resources: BTreeMap::new(), @@ -213,7 +229,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) @@ -285,20 +304,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) } @@ -372,13 +393,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, @@ -456,12 +480,24 @@ 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) } @@ -484,6 +520,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) { @@ -567,11 +606,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!( @@ -653,9 +695,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); @@ -667,6 +709,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 @@ -687,25 +738,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) } } @@ -723,7 +874,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::{ @@ -747,6 +901,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) @@ -819,10 +978,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"); @@ -833,8 +994,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 { @@ -854,11 +1016,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); @@ -884,7 +1046,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); @@ -938,22 +1100,34 @@ 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); } - // 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..0e96d1f32 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) @@ -51,8 +52,8 @@ use rutabaga_gfx::RutabagaFence; use thiserror::Error as ThisError; use vhost::vhost_user::{ gpu_message::{VhostUserGpuCursorPos, VhostUserGpuEdidRequest}, - message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures}, - GpuBackend, + message::{VhostUserProtocolFeatures, VhostUserShMemConfig, VhostUserVirtioFeatures}, + 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, @@ -88,12 +89,17 @@ use crate::{ GpuResponse::{ErrInvalidParameter, ErrUnspec}, GpuResponseEncodeError, VirtioGpuConfig, VirtioGpuResult, CONTROL_QUEUE, CURSOR_QUEUE, NUM_QUEUES, POLL_EVENT, QUEUE_SIZE, VIRTIO_GPU_BIND_RENDER_TARGET, VIRTIO_GPU_FLAG_FENCE, - VIRTIO_GPU_FLAG_INFO_RING_IDX, VIRTIO_GPU_MAX_SCANOUTS, VIRTIO_GPU_TEXTURE_2D, + VIRTIO_GPU_FLAG_INFO_RING_IDX, VIRTIO_GPU_F_BLOB_ALIGNMENT, VIRTIO_GPU_MAX_SCANOUTS, + VIRTIO_GPU_TEXTURE_2D, }, renderer::Renderer, GpuConfig, GpuMode, }; +/// Size of the HOST_VISIBLE shared memory region for blob resource mapping +/// (8GB). +const SHMEM_SIZE: u64 = 1 << 33; + type Result = std::result::Result; #[derive(Debug, ThisError)] @@ -141,6 +147,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, @@ -173,8 +180,11 @@ 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, gpu_backend: None, exit_consumer, exit_notifier, @@ -208,7 +218,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 +238,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 +278,50 @@ 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) => { + if (acked_features & (1 << VIRTIO_GPU_F_RESOURCE_BLOB)) == 0 { + warn!( + "VIRTIO_GPU_CMD_RESOURCE_CREATE_BLOB command received but \ + VIRTIO_GPU_F_RESOURCE_BLOB feature not negotiated" + ); + return Err(ErrInvalidParameter); + } + 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) => { + if (acked_features & (1 << VIRTIO_GPU_F_RESOURCE_BLOB)) == 0 { + warn!( + "VIRTIO_GPU_CMD_RESOURCE_MAP_BLOB command received but \ + VIRTIO_GPU_F_RESOURCE_BLOB feature not negotiated" + ); + return Err(ErrInvalidParameter); + } + renderer.resource_map_blob(info.resource_id.into(), info.offset.into()) } - GpuCommand::ResourceUnmapBlob(_) => { - panic!("virtio_gpu: GpuCommand::ResourceUnmapBlob unimplemented") + GpuCommand::ResourceUnmapBlob(info) => { + if (acked_features & (1 << VIRTIO_GPU_F_RESOURCE_BLOB)) == 0 { + warn!( + "VIRTIO_GPU_CMD_RESOURCE_UNMAP_BLOB command received but \ + VIRTIO_GPU_F_RESOURCE_BLOB feature not negotiated" + ); + return Err(ErrInvalidParameter); + } + renderer.resource_unmap_blob(info.resource_id.into()) } } } @@ -358,11 +400,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), ) } @@ -442,7 +484,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 +651,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 +683,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 +700,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 +711,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, )) }, @@ -717,6 +763,7 @@ impl VhostUserBackend for VhostUserGpuBackend { | (1 << VIRTIO_RING_F_EVENT_IDX) | (1 << VIRTIO_GPU_F_VIRGL) | (1 << VIRTIO_GPU_F_RESOURCE_BLOB) + | (1 << VIRTIO_GPU_F_BLOB_ALIGNMENT) | (1 << VIRTIO_GPU_F_RESOURCE_UUID) | (1 << VIRTIO_GPU_F_CONTEXT_INIT) | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits(); @@ -731,7 +778,15 @@ 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 get_shmem_config(&self) -> IoResult { + Ok(VhostUserShMemConfig::new(1, &[0, SHMEM_SIZE])) } fn acked_features(&self, features: u64) { @@ -750,6 +805,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,20 +889,26 @@ 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, - virtio_gpu_mem_entry, virtio_gpu_rect, virtio_gpu_resource_assign_uuid, - virtio_gpu_resource_attach_backing, virtio_gpu_resource_create_2d, + virtio_gpu_cmd_submit, 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, virtio_gpu_mem_entry, virtio_gpu_rect, + virtio_gpu_resource_assign_uuid, virtio_gpu_resource_attach_backing, + virtio_gpu_resource_create_2d, virtio_gpu_resource_create_blob, virtio_gpu_resource_detach_backing, virtio_gpu_resource_flush, + virtio_gpu_resource_map_blob, virtio_gpu_resource_unmap_blob, virtio_gpu_resource_unref, virtio_gpu_set_scanout, - GpuResponse::{self, OkCapset, OkCapsetInfo, OkDisplayInfo, OkEdid, OkNoData}, + GpuResponse::{ + self, OkCapset, OkCapsetInfo, OkDisplayInfo, OkEdid, OkMapInfo, OkNoData, + }, VIRTIO_GPU_BIND_RENDER_TARGET, VIRTIO_GPU_CMD_CTX_ATTACH_RESOURCE, VIRTIO_GPU_CMD_CTX_CREATE, VIRTIO_GPU_CMD_CTX_DESTROY, VIRTIO_GPU_CMD_CTX_DETACH_RESOURCE, VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING, - VIRTIO_GPU_CMD_RESOURCE_CREATE_2D, VIRTIO_GPU_CMD_RESOURCE_DETACH_BACKING, - VIRTIO_GPU_CMD_RESOURCE_FLUSH, VIRTIO_GPU_CMD_SET_SCANOUT, + VIRTIO_GPU_CMD_RESOURCE_CREATE_2D, VIRTIO_GPU_CMD_RESOURCE_CREATE_BLOB, + VIRTIO_GPU_CMD_RESOURCE_DETACH_BACKING, VIRTIO_GPU_CMD_RESOURCE_FLUSH, + VIRTIO_GPU_CMD_RESOURCE_MAP_BLOB, VIRTIO_GPU_CMD_RESOURCE_UNMAP_BLOB, + VIRTIO_GPU_CMD_SET_SCANOUT, VIRTIO_GPU_CMD_SUBMIT_3D, VIRTIO_GPU_CMD_TRANSFER_FROM_HOST_3D, VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D, VIRTIO_GPU_CMD_TRANSFER_TO_HOST_3D, VIRTIO_GPU_FORMAT_R8G8B8A8_UNORM, VIRTIO_GPU_RESP_ERR_UNSPEC, VIRTIO_GPU_RESP_OK_NODATA, VIRTIO_GPU_TEXTURE_2D, @@ -864,11 +930,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 +950,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,34 +1024,40 @@ 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(); let hdr = virtio_gpu_ctrl_hdr::default(); - let acked_features = 1 << VIRTIO_GPU_F_RESOURCE_UUID; - let test_cmd = |cmd: GpuCommand, setup: fn(&mut MockMockRenderer)| { + let test_cmd = |cmd: GpuCommand, features: u64, setup: fn(&mut MockMockRenderer)| { let mut mock_renderer = MockMockRenderer::new(); mock_renderer.expect_force_ctx_0().return_const(()); setup(&mut mock_renderer); VhostUserGpuBackendInner::process_gpu_command( &mut mock_renderer, - &mem.memory(), + mem.memory().into_inner(), hdr, cmd, - acked_features, + features, ) }; + let acked_features = 1 << VIRTIO_GPU_F_RESOURCE_UUID; + let cmd = GpuCommand::GetDisplayInfo; - let result = test_cmd(cmd, |g| { + let result = test_cmd(cmd, acked_features, |g| { g.expect_display_info() .return_once(|| Ok(OkDisplayInfo(vec![(1280, 720, true)]))); }); assert_matches!(result, Ok(OkDisplayInfo(_))); let cmd = GpuCommand::GetEdid(virtio_gpu_get_edid::default()); - let result = test_cmd(cmd, |g| { + let result = test_cmd(cmd, acked_features, |g| { g.expect_get_edid().return_once(|_| { Ok(OkEdid { blob: Box::new([0xff; 512]), @@ -997,32 +1067,32 @@ mod tests { assert_matches!(result, Ok(OkEdid { .. })); let cmd = GpuCommand::ResourceCreate2d(virtio_gpu_resource_create_2d::default()); - let result = test_cmd(cmd, |g| { + let result = test_cmd(cmd, acked_features, |g| { g.expect_resource_create_3d() .return_once(|_, _| Ok(OkNoData)); }); assert_matches!(result, Ok(OkNoData)); let cmd = GpuCommand::ResourceUnref(virtio_gpu_resource_unref::default()); - let result = test_cmd(cmd, |g| { + let result = test_cmd(cmd, acked_features, |g| { g.expect_unref_resource().return_once(|_| Ok(OkNoData)); }); assert_matches!(result, Ok(OkNoData)); let cmd = GpuCommand::SetScanout(virtio_gpu_set_scanout::default()); - let result = test_cmd(cmd, |g| { + let result = test_cmd(cmd, acked_features, |g| { g.expect_set_scanout().return_once(|_, _, _| Ok(OkNoData)); }); assert_matches!(result, Ok(OkNoData)); let cmd = GpuCommand::ResourceFlush(virtio_gpu_resource_flush::default()); - let result = test_cmd(cmd, |g| { + let result = test_cmd(cmd, acked_features, |g| { g.expect_flush_resource().return_once(|_, _| Ok(OkNoData)); }); assert_matches!(result, Ok(OkNoData)); let cmd = GpuCommand::TransferToHost2d(virtio_gpu_transfer_to_host_2d::default()); - let result = test_cmd(cmd, |g| { + let result = test_cmd(cmd, acked_features, |g| { g.expect_transfer_write_2d() .return_once(|_, _, _| Ok(OkNoData)); }); @@ -1032,33 +1102,33 @@ mod tests { virtio_gpu_resource_attach_backing::default(), Vec::default(), ); - let result = test_cmd(cmd, |g| { + let result = test_cmd(cmd, acked_features, |g| { g.expect_attach_backing() .return_once(|_, _, _| Ok(OkNoData)); }); assert_matches!(result, Ok(OkNoData)); let cmd = GpuCommand::ResourceDetachBacking(virtio_gpu_resource_detach_backing::default()); - let result = test_cmd(cmd, |g| { + let result = test_cmd(cmd, acked_features, |g| { g.expect_detach_backing().return_once(|_| Ok(OkNoData)); }); assert_matches!(result, Ok(OkNoData)); let cmd = GpuCommand::UpdateCursor(virtio_gpu_update_cursor::default()); - let result = test_cmd(cmd, |g| { + let result = test_cmd(cmd, acked_features, |g| { g.expect_update_cursor() .return_once(|_, _, _, _| Ok(OkNoData)); }); assert_matches!(result, Ok(OkNoData)); let cmd = GpuCommand::MoveCursor(virtio_gpu_update_cursor::default()); - let result = test_cmd(cmd, |g| { + let result = test_cmd(cmd, acked_features, |g| { g.expect_move_cursor().return_once(|_, _| Ok(OkNoData)); }); assert_matches!(result, Ok(OkNoData)); let cmd = GpuCommand::GetCapsetInfo(virtio_gpu_get_capset_info::default()); - let result = test_cmd(cmd, |g| { + let result = test_cmd(cmd, acked_features, |g| { g.expect_get_capset_info().return_once(|_| { Ok(OkCapsetInfo { capset_id: 1, @@ -1077,7 +1147,7 @@ mod tests { ); let cmd = GpuCommand::GetCapset(virtio_gpu_get_capset::default()); - let result = test_cmd(cmd, |g| { + let result = test_cmd(cmd, acked_features, |g| { // Fixed E0559: Correctly constructing the OkCapset tuple variant with a Vec g.expect_get_capset() .return_once(|_, _| Ok(OkCapset(vec![0; 1]))); @@ -1085,48 +1155,48 @@ mod tests { assert_matches!(result, Ok(OkCapset { .. })); let cmd = GpuCommand::CtxCreate(virtio_gpu_ctx_create::default()); - let result = test_cmd(cmd, |g| { + let result = test_cmd(cmd, acked_features, |g| { g.expect_create_context() .return_once(|_, _, _| Ok(OkNoData)); }); assert_matches!(result, Ok(OkNoData)); let cmd = GpuCommand::CtxDestroy(virtio_gpu_ctx_destroy::default()); - let result = test_cmd(cmd, |g| { + let result = test_cmd(cmd, acked_features, |g| { g.expect_destroy_context().return_once(|_| Ok(OkNoData)); }); assert_matches!(result, Ok(OkNoData)); let cmd = GpuCommand::CtxAttachResource(virtio_gpu_ctx_resource::default()); - let result = test_cmd(cmd, |g| { + let result = test_cmd(cmd, acked_features, |g| { g.expect_context_attach_resource() .return_once(|_, _| Ok(OkNoData)); }); assert_matches!(result, Ok(OkNoData)); let cmd = GpuCommand::CtxDetachResource(virtio_gpu_ctx_resource::default()); - let result = test_cmd(cmd, |g| { + let result = test_cmd(cmd, acked_features, |g| { g.expect_context_detach_resource() .return_once(|_, _| Ok(OkNoData)); }); assert_matches!(result, Ok(OkNoData)); let cmd = GpuCommand::ResourceCreate3d(virtio_gpu_resource_create_3d::default()); - let result = test_cmd(cmd, |g| { + let result = test_cmd(cmd, acked_features, |g| { g.expect_resource_create_3d() .return_once(|_, _| Ok(OkNoData)); }); assert_matches!(result, Ok(OkNoData)); let cmd = GpuCommand::TransferToHost3d(virtio_gpu_transfer_host_3d::default()); - let result = test_cmd(cmd, |g| { + let result = test_cmd(cmd, acked_features, |g| { g.expect_transfer_write() .return_once(|_, _, _| Ok(OkNoData)); }); assert_matches!(result, Ok(OkNoData)); let cmd = GpuCommand::TransferFromHost3d(virtio_gpu_transfer_host_3d::default()); - let result = test_cmd(cmd, |g| { + let result = test_cmd(cmd, acked_features, |g| { g.expect_transfer_read() .return_once(|_, _, _, _| Ok(OkNoData)); }); @@ -1136,14 +1206,14 @@ mod tests { cmd_data: vec![0xff; 512], fence_ids: vec![], }; - let result = test_cmd(cmd, |g| { + let result = test_cmd(cmd, acked_features, |g| { g.expect_submit_command() .return_once(|_, _, _| Ok(OkNoData)); }); assert_matches!(result, Ok(OkNoData)); let cmd = GpuCommand::ResourceAssignUuid(virtio_gpu_resource_assign_uuid::default()); - let result = test_cmd(cmd, |g| { + let result = test_cmd(cmd, acked_features, |g| { g.expect_resource_assign_uuid().return_once(|_| { Ok(GpuResponse::OkResourceUuid { uuid: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], @@ -1151,6 +1221,30 @@ mod tests { }); }); assert_matches!(result, Ok(GpuResponse::OkResourceUuid { .. })); + + // Test blob commands WITH VIRTIO_GPU_F_RESOURCE_BLOB feature + let blob_features = (1 << VIRTIO_GPU_F_RESOURCE_UUID) | (1 << VIRTIO_GPU_F_RESOURCE_BLOB); + + let cmd = + GpuCommand::ResourceCreateBlob(virtio_gpu_resource_create_blob::default(), vec![]); + let result = test_cmd(cmd, blob_features, |g| { + g.expect_resource_create_blob() + .return_once(|_, _, _, _| Ok(OkNoData)); + }); + assert_matches!(result, Ok(OkNoData)); + + let cmd = GpuCommand::ResourceMapBlob(virtio_gpu_resource_map_blob::default()); + let result = test_cmd(cmd, blob_features, |g| { + g.expect_resource_map_blob() + .return_once(|_, _| Ok(OkMapInfo { map_info: 0 })); + }); + assert_matches!(result, Ok(OkMapInfo { .. })); + + let cmd = GpuCommand::ResourceUnmapBlob(virtio_gpu_resource_unmap_blob::default()); + let result = test_cmd(cmd, blob_features, |g| { + g.expect_resource_unmap_blob().return_once(|_| Ok(OkNoData)); + }); + assert_matches!(result, Ok(OkNoData)); } fn create_control_vring( @@ -1470,7 +1564,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 { @@ -1499,7 +1596,7 @@ mod tests { #[test] fn test_verify_backend() { let gpu_config = GpuConfigBuilder::default() - .set_gpu_mode(GpuMode::VirglRenderer) + .set_gpu_mode(GpuMode::Null) .set_flags(GpuFlags::default()) .build() .unwrap(); @@ -1507,11 +1604,21 @@ mod tests { assert_eq!(backend.num_queues(), NUM_QUEUES); assert_eq!(backend.max_queue_size(), QUEUE_SIZE); - assert_eq!(backend.features(), 0x0101_7100_001F); + assert_eq!(backend.features(), 0x0101_7100_003F); assert_eq!( backend.protocol_features(), - VhostUserProtocolFeatures::CONFIG | VhostUserProtocolFeatures::MQ + VhostUserProtocolFeatures::CONFIG + | VhostUserProtocolFeatures::MQ + | VhostUserProtocolFeatures::BACKEND_REQ + | VhostUserProtocolFeatures::BACKEND_SEND_FD + | VhostUserProtocolFeatures::SHMEM ); + + let shmem_config = backend.get_shmem_config().unwrap(); + assert_eq!(shmem_config.nregions, 1); + assert_eq!(shmem_config.memory_sizes[0], 0); // Region 0 unused + assert_eq!(shmem_config.memory_sizes[1], SHMEM_SIZE); // Region 1 is HOST_VISIBLE + assert_eq!(backend.queues_per_thread(), vec![0xffff_ffff]); assert_eq!(backend.get_config(0, 0), Vec::::new()); @@ -1532,7 +1639,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 +1658,8 @@ mod tests { // Hit the loop part backend.set_event_idx(true); + backend.set_backend_req_fd(dummy_backend_request_socket()); + backend.set_gpu_socket(gpu_backend_pair().1).unwrap(); backend .handle_event(0, EventSet::IN, &[vring.clone()], 0) .unwrap(); @@ -1653,6 +1762,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 @@ -1756,6 +1866,24 @@ mod tests { writable_desc_lengths: &[RESP_SIZE], }; + // Construct a SUBMIT_3D command to submit commands to the context + let hdr_submit = virtio_gpu_ctrl_hdr { + type_: VIRTIO_GPU_CMD_SUBMIT_3D.into(), + flags: (VIRTIO_GPU_FLAG_FENCE | VIRTIO_GPU_FLAG_INFO_RING_IDX).into(), + fence_id: 1.into(), + ctx_id: 1.into(), + ring_idx: 0, + padding: [0u8; 3], + }; + let cmd_submit = virtio_gpu_cmd_submit { + size: 0.into(), + num_in_fences: 0.into(), + }; + let ctx_submit_cmd = TestingDescChainArgs { + readable_desc_bufs: &[hdr_submit.as_slice(), cmd_submit.as_slice()], + writable_desc_lengths: &[RESP_SIZE], + }; + // Construct a command to detach a context for the given ctx_id in the hdr let hdr = new_hdr(VIRTIO_GPU_CMD_CTX_DETACH_RESOURCE); let cmd = virtio_gpu_ctx_resource::default(); @@ -1788,6 +1916,49 @@ mod tests { writable_desc_lengths: &[RESP_SIZE], }; + // Create blob resource command with backing memory + let hdr = new_hdr(VIRTIO_GPU_CMD_RESOURCE_CREATE_BLOB); + let cmd = virtio_gpu_resource_create_blob { + resource_id: 2.into(), + blob_mem: 1.into(), + blob_flags: 1.into(), + nr_entries: 1.into(), + blob_id: 0.into(), + size: 4096.into(), + }; + let mem_entry = virtio_gpu_mem_entry { + addr: 0x1000.into(), + length: 4096.into(), + padding: 0.into(), + }; + let create_blob_cmd = TestingDescChainArgs { + readable_desc_bufs: &[hdr.as_slice(), cmd.as_slice(), mem_entry.as_slice()], + writable_desc_lengths: &[RESP_SIZE], + }; + + // Map blob resource command + let hdr = new_hdr(VIRTIO_GPU_CMD_RESOURCE_MAP_BLOB); + let cmd = virtio_gpu_resource_map_blob { + resource_id: 2.into(), + padding: Le32::default(), + offset: 0.into(), + }; + let map_blob_cmd = TestingDescChainArgs { + readable_desc_bufs: &[hdr.as_slice(), cmd.as_slice()], + writable_desc_lengths: &[RESP_SIZE + 16], + }; + + // Unmap blob resource command + let hdr = new_hdr(VIRTIO_GPU_CMD_RESOURCE_UNMAP_BLOB); + let cmd = virtio_gpu_resource_unmap_blob { + resource_id: 2.into(), + padding: Le32::default(), + }; + let unmap_blob_cmd = TestingDescChainArgs { + readable_desc_bufs: &[hdr.as_slice(), cmd.as_slice()], + writable_desc_lengths: &[RESP_SIZE], + }; + // Create a control queue with all the commands defined above let commands = [ create_resource_cmd, @@ -1799,8 +1970,12 @@ mod tests { detach_backing_cmd, ctx_create_cmd, ctx_attach_cmd, + ctx_submit_cmd, ctx_detach_cmd, ctx_destroy_cmd, + create_blob_cmd, + map_blob_cmd, + unmap_blob_cmd, ]; let (control_vring, _, _) = create_control_vring(&mem, &commands); 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/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 diff --git a/vhost-device-gpu/src/protocol.rs b/vhost-device-gpu/src/protocol.rs index a7df02629..49c98e413 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, @@ -81,15 +81,23 @@ 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; +/// Feature bit for blob_alignment field in config (not in virtio_bindings yet). +pub const VIRTIO_GPU_F_BLOB_ALIGNMENT: u32 = 5; + /// Virtio Gpu Configuration #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] #[repr(C)] @@ -102,6 +110,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 @@ -441,14 +451,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 { @@ -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)?) @@ -1189,7 +1210,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] @@ -1311,7 +1332,7 @@ mod tests { "CmdSubmit3d", ), ( - ResourceCreateBlob(virtio_gpu_resource_create_blob::default()), + ResourceCreateBlob(virtio_gpu_resource_create_blob::default(), Vec::new()), "ResourceCreateBlob", ), ( @@ -1343,8 +1364,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); @@ -1353,12 +1375,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] 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;