diff --git a/Cargo.lock b/Cargo.lock index 85f8baf..1b59871 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2006,6 +2006,18 @@ dependencies = [ "vmm-sys-util", ] +[[package]] +name = "vhost" +version = "0.15.0" +source = "git+https://github.com/mtjhrc/vhost.git?branch=shmem#c686ebb25a0af340a7969f637ba0d1b1e160fe6c" +dependencies = [ + "bitflags 2.10.0", + "libc 0.2.177", + "uuid", + "vm-memory", + "vmm-sys-util", +] + [[package]] name = "vhost-device-can" version = "0.1.0" @@ -2017,8 +2029,8 @@ dependencies = [ "queues", "socketcan", "thiserror 2.0.17", - "vhost", - "vhost-user-backend", + "vhost 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", + "vhost-user-backend 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", "virtio-bindings", "virtio-queue", "vm-memory", @@ -2038,8 +2050,8 @@ dependencies = [ "log", "queues", "thiserror 2.0.17", - "vhost", - "vhost-user-backend", + "vhost 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", + "vhost-user-backend 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", "virtio-bindings", "virtio-queue", "vm-memory", @@ -2057,8 +2069,8 @@ dependencies = [ "libgpiod", "log", "thiserror 2.0.17", - "vhost", - "vhost-user-backend", + "vhost 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", + "vhost-user-backend 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", "virtio-bindings", "virtio-queue", "vm-memory", @@ -2080,8 +2092,8 @@ dependencies = [ "rutabaga_gfx", "tempfile", "thiserror 2.0.17", - "vhost", - "vhost-user-backend", + "vhost 0.15.0 (git+https://github.com/mtjhrc/vhost.git?branch=shmem)", + "vhost-user-backend 0.21.0 (git+https://github.com/mtjhrc/vhost.git?branch=shmem)", "virglrenderer", "virtio-bindings", "virtio-queue", @@ -2099,8 +2111,8 @@ dependencies = [ "libc 0.2.177", "log", "thiserror 2.0.17", - "vhost", - "vhost-user-backend", + "vhost 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", + "vhost-user-backend 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", "virtio-bindings", "virtio-queue", "vm-memory", @@ -2122,8 +2134,8 @@ dependencies = [ "rand", "tempfile", "thiserror 2.0.17", - "vhost", - "vhost-user-backend", + "vhost 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", + "vhost-user-backend 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", "virtio-bindings", "virtio-queue", "vm-memory", @@ -2143,8 +2155,8 @@ dependencies = [ "rand", "tempfile", "thiserror 2.0.17", - "vhost", - "vhost-user-backend", + "vhost 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", + "vhost-user-backend 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", "virtio-bindings", "virtio-queue", "vm-memory", @@ -2161,8 +2173,8 @@ dependencies = [ "itertools 0.14.0", "log", "thiserror 2.0.17", - "vhost", - "vhost-user-backend", + "vhost 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", + "vhost-user-backend 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", "virtio-bindings", "virtio-queue", "vm-memory", @@ -2180,8 +2192,8 @@ dependencies = [ "log", "tempfile", "thiserror 2.0.17", - "vhost", - "vhost-user-backend", + "vhost 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", + "vhost-user-backend 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", "virtio-bindings", "virtio-queue", "vm-memory", @@ -2205,8 +2217,8 @@ dependencies = [ "rusty-fork", "tempfile", "thiserror 2.0.17", - "vhost", - "vhost-user-backend", + "vhost 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", + "vhost-user-backend 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", "virtio-bindings", "virtio-queue", "vm-memory", @@ -2224,8 +2236,8 @@ dependencies = [ "libc 0.2.177", "log", "thiserror 2.0.17", - "vhost", - "vhost-user-backend", + "vhost 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", + "vhost-user-backend 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", "virtio-bindings", "virtio-queue", "vm-memory", @@ -2242,8 +2254,8 @@ dependencies = [ "libc 0.2.177", "log", "thiserror 2.0.17", - "vhost", - "vhost-user-backend", + "vhost 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", + "vhost-user-backend 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", "virtio-bindings", "virtio-queue", "vm-memory", @@ -2265,8 +2277,8 @@ dependencies = [ "serde", "tempfile", "thiserror 2.0.17", - "vhost", - "vhost-user-backend", + "vhost 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", + "vhost-user-backend 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", "virtio-bindings", "virtio-queue", "virtio-vsock", @@ -2283,7 +2295,21 @@ checksum = "783587813a59c42c36519a6e12bb31eb2d7fa517377428252ba4cc2312584243" dependencies = [ "libc 0.2.177", "log", - "vhost", + "vhost 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", + "virtio-bindings", + "virtio-queue", + "vm-memory", + "vmm-sys-util", +] + +[[package]] +name = "vhost-user-backend" +version = "0.21.0" +source = "git+https://github.com/mtjhrc/vhost.git?branch=shmem#c686ebb25a0af340a7969f637ba0d1b1e160fe6c" +dependencies = [ + "libc 0.2.177", + "log", + "vhost 0.15.0 (git+https://github.com/mtjhrc/vhost.git?branch=shmem)", "virtio-bindings", "virtio-queue", "vm-memory", @@ -2293,8 +2319,7 @@ dependencies = [ [[package]] name = "virglrenderer" version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14b3ceb5f84adcbd531661a6c6c0883c3d6cd83427886d3179675b19268f4450" +source = "git+https://gitlab.freedesktop.org/mtjhrc/virglrenderer-rs.git?branch=map_info#a4b3fb2efe39f06c5ea1d96a0a51c3f66df0f79b" dependencies = [ "libc 1.0.0-alpha.1", "log", diff --git a/vhost-device-gpu/Cargo.toml b/vhost-device-gpu/Cargo.toml index 6eae2d6..e6c40b0 100644 --- a/vhost-device-gpu/Cargo.toml +++ b/vhost-device-gpu/Cargo.toml @@ -28,9 +28,9 @@ log = "0.4" [target.'cfg(not(target_env = "musl"))'.dependencies] rutabaga_gfx = "0.1.75" thiserror = "2.0.17" -virglrenderer = {version = "0.1.2", optional = true } -vhost = { version = "0.15.0", features = ["vhost-user-backend"] } -vhost-user-backend = "0.21" +virglrenderer = { git = "https://gitlab.freedesktop.org/mtjhrc/virglrenderer-rs.git", branch = "map_info", optional = true } +vhost = { git = "https://github.com/mtjhrc/vhost.git", branch = "shmem", features = ["vhost-user-backend"] } +vhost-user-backend = { git = "https://github.com/mtjhrc/vhost.git", branch = "shmem" } virtio-bindings = "0.2.5" virtio-queue = "0.17.0" vm-memory = "0.17.1" diff --git a/vhost-device-gpu/src/backend/common.rs b/vhost-device-gpu/src/backend/common.rs index 147ad96..249d860 100644 --- a/vhost-device-gpu/src/backend/common.rs +++ b/vhost-device-gpu/src/backend/common.rs @@ -2,12 +2,16 @@ // // 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; @@ -21,6 +25,59 @@ use crate::{ renderer::Renderer, }; +/// 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}"); + + let map_request = VhostUserMMap { + shmid: 0, + 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: 0, + 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 c1b41ec..26ffdcf 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,32 +19,39 @@ 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 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, GuestMemory, 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, @@ -59,6 +69,8 @@ pub struct GfxstreamResource { scanouts: common::AssociatedScanouts, pub info_3d: Option, pub handle: Option>, + pub blob_size: u64, + pub blob_shmem_offset: Option, } impl GfxstreamResource { @@ -85,6 +97,8 @@ impl GfxstreamResource { scanouts: AssociatedScanouts::default(), info_3d: None, handle: None, + blob_size: 0, + blob_shmem_offset: None, } } } @@ -96,6 +110,8 @@ thread_local! { } pub struct GfxstreamAdapter { + #[allow(dead_code)] + backend: Backend, gpu_backend: GpuBackend, resources: BTreeMap, fence_state: Arc>, @@ -103,7 +119,12 @@ pub struct GfxstreamAdapter { } impl GfxstreamAdapter { - pub fn new(queue_ctl: &VringRwLock, gpu_config: &GpuConfig, gpu_backend: GpuBackend) -> Self { + pub fn new( + queue_ctl: &VringRwLock, + backend: Backend, + gpu_config: &GpuConfig, + gpu_backend: GpuBackend, + ) -> Self { let fence_state = Arc::new(Mutex::new(FenceState::default())); let fence = Self::create_fence_handler(queue_ctl.clone(), fence_state.clone()); @@ -117,6 +138,7 @@ impl GfxstreamAdapter { }); Self { + backend, gpu_backend, fence_state, resources: BTreeMap::new(), @@ -281,6 +303,8 @@ impl Renderer for GfxstreamAdapter { scanouts: AssociatedScanouts::default(), info_3d: None, handle: None, + blob_size: 0, + blob_shmem_offset: None, }; debug_assert!( !self.resources.contains_key(&resource_id), @@ -653,25 +677,119 @@ 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 { + panic!("GUEST_HANDLE unimplemented"); + } 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, + 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) } } @@ -732,6 +850,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). @@ -776,6 +899,7 @@ mod gfx_fence_tests { }); Some(GfxstreamAdapter { + backend: dummy_backend(), gpu_backend: dummy_gpu_backend(), resources: BTreeMap::default(), fence_state, diff --git a/vhost-device-gpu/src/backend/null.rs b/vhost-device-gpu/src/backend/null.rs index 6be1b3e..92cb659 100644 --- a/vhost-device-gpu/src/backend/null.rs +++ b/vhost-device-gpu/src/backend/null.rs @@ -13,7 +13,7 @@ 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 +27,7 @@ impl NullAdapter { pub fn new( _queue_ctl: &vhost_user_backend::VringRwLock, _config: &GpuConfig, + _backend: vhost::vhost_user::Backend, gpu_backend: GpuBackend, ) -> Self { trace!("NullAdapter created"); @@ -224,11 +225,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,15 +255,16 @@ mod tests { use crate::{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(), ); let vring = VringRwLock::new(mem, 0x100).unwrap(); let config = GpuConfig::new(GpuMode::Null, None, GpuFlags::default()).unwrap(); - NullAdapter::new(&vring, &config, gpu_backend) + NullAdapter::new(&vring, &config, backend, gpu_backend) } #[test] @@ -430,9 +430,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 b1b6279..70ae230 100644 --- a/vhost-device-gpu/src/backend/virgl.rs +++ b/vhost-device-gpu/src/backend/virgl.rs @@ -4,53 +4,54 @@ // // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause +use bitflags::Flags; +use libc::c_void; +use log::{debug, error, trace, warn}; +use rutabaga_gfx::RutabagaFence; use std::{ collections::BTreeMap, io::IoSliceMut, os::fd::{AsFd, FromRawFd, IntoRawFd, RawFd}, sync::{Arc, Mutex}, }; - -use libc::c_void; -use log::{debug, error, trace, warn}; -use rutabaga_gfx::RutabagaFence; use vhost::vhost_user::{ gpu_message::{ VhostUserGpuCursorPos, VhostUserGpuDMABUFScanout, VhostUserGpuDMABUFScanout2, VhostUserGpuEdidRequest, VhostUserGpuUpdate, }, - GpuBackend, + message::VhostUserMMapFlags, + Backend, GpuBackend, }; use vhost_user_backend::{VringRwLock, VringT}; use virglrenderer::{ - FenceHandler, Iovec, VirglContext, VirglRenderer, VirglRendererFlags, VirglResource, - VIRGL_HANDLE_TYPE_MEM_DMABUF, + FenceHandler, Iovec, VirglRenderer, VirglRendererFlags, VirglResource, + 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, GuestMemory, 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, }, renderer::Renderer, - GpuConfig, + GpuCapset, GpuConfig, }; -const CAPSET_ID_VIRGL: u32 = 1; -const CAPSET_ID_VIRGL2: u32 = 2; -const CAPSET_ID_VENUS: u32 = 4; - #[derive(Clone)] pub struct GpuResource { pub virgl_resource: VirglResource, @@ -58,6 +59,8 @@ pub struct GpuResource { // resource. Resource could be used for multiple scanouts. pub scanouts: AssociatedScanouts, pub backing_iovecs: Arc>>>, + pub blob_size: u64, + pub blob_shmem_offset: Option, } fn sglist_to_iovecs( @@ -134,18 +137,30 @@ impl FenceHandler for VirglFenceHandler { pub struct VirglRendererAdapter { renderer: VirglRenderer, + capsets: GpuCapset, + #[allow(dead_code)] + backend: Backend, gpu_backend: GpuBackend, fence_state: Arc>, resources: BTreeMap, - contexts: BTreeMap, scanouts: [Option; VIRTIO_GPU_MAX_SCANOUTS as usize], } impl VirglRendererAdapter { - pub fn new(queue_ctl: &VringRwLock, config: &GpuConfig, gpu_backend: GpuBackend) -> Self { + pub fn new( + queue_ctl: &VringRwLock, + backend: Backend, + config: &GpuConfig, + gpu_backend: GpuBackend, + ) -> Self { + 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_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) @@ -162,11 +177,12 @@ impl VirglRendererAdapter { let renderer = VirglRenderer::init(virglrenderer_flags, fence_handler, None) .expect("Failed to initialize virglrenderer"); Self { + capsets, renderer, + backend, gpu_backend, fence_state, resources: BTreeMap::new(), - contexts: BTreeMap::new(), scanouts: Default::default(), } } @@ -184,6 +200,8 @@ impl Renderer for VirglRendererAdapter { virgl_resource, scanouts: AssociatedScanouts::default(), backing_iovecs: Arc::new(Mutex::new(None)), + blob_size: 0, + blob_shmem_offset: None, }; self.resources.insert(resource_id, local_resource); Ok(OkNoData) @@ -320,13 +338,14 @@ 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, - 3 => CAPSET_ID_VENUS, - _ => return Err(ErrInvalidParameter), - }; + debug!("Looking up capset at index {index}"); + let capset_id = self + .capsets + .iter() + .nth(index as usize) + .ok_or(ErrInvalidParameter)? + .bits() as u32; + let (version, size) = self.renderer.get_capset_info(index); Ok(OkCapsetInfo { capset_id, @@ -346,41 +365,34 @@ impl Renderer for VirglRendererAdapter { context_init: u32, context_name: Option<&str>, ) -> VirtioGpuResult { - if self.contexts.contains_key(&ctx_id) { - return Err(ErrUnspec); - } + trace!("Creating context ctx_id={ctx_id}, '{context_name:?}', context_init={context_init}"); - // Create the VirglContext using virglrenderer - let ctx = virglrenderer::VirglContext::create_context(ctx_id, context_init, context_name) + // Create the context using virglrenderer (contexts are now managed internally) + self.renderer + .create_context(ctx_id, context_init, context_name) .map_err(|_| ErrInvalidContextId)?; - // Insert the newly created context into our local BTreeMap. - self.contexts.insert(ctx_id, ctx); Ok(OkNoData) } fn destroy_context(&mut self, ctx_id: u32) -> VirtioGpuResult { - self.contexts.remove(&ctx_id).ok_or(ErrInvalidContextId)?; + self.renderer.destroy_context(ctx_id); Ok(OkNoData) } fn context_attach_resource(&mut self, ctx_id: u32, resource_id: u32) -> VirtioGpuResult { - let ctx = self.contexts.get_mut(&ctx_id).ok_or(ErrInvalidContextId)?; - let resource = self - .resources - .get_mut(&resource_id) - .ok_or(ErrInvalidResourceId)?; - ctx.attach(&mut resource.virgl_resource); + if !self.resources.contains_key(&resource_id) { + return Err(ErrInvalidResourceId); + } + self.renderer.ctx_attach_resource(ctx_id, resource_id); Ok(OkNoData) } fn context_detach_resource(&mut self, ctx_id: u32, resource_id: u32) -> VirtioGpuResult { - let ctx = self.contexts.get_mut(&ctx_id).ok_or(ErrInvalidContextId)?; - let resource = self - .resources - .get_mut(&resource_id) - .ok_or(ErrInvalidResourceId)?; - ctx.detach(&resource.virgl_resource); + if !self.resources.contains_key(&resource_id) { + return Err(ErrInvalidResourceId); + } + self.renderer.ctx_detach_resource(ctx_id, resource_id); Ok(OkNoData) } @@ -390,9 +402,8 @@ impl Renderer for VirglRendererAdapter { commands: &mut [u8], fence_ids: &[u64], ) -> VirtioGpuResult { - let ctx = self.contexts.get_mut(&ctx_id).ok_or(ErrInvalidContextId)?; - - ctx.submit_cmd(commands, fence_ids) + self.renderer + .submit_cmd(ctx_id, commands, fence_ids) .map(|()| OkNoData) .map_err(|_| ErrUnspec) } @@ -612,25 +623,112 @@ 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, + 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); + } else if resource_create_blob.blob_mem != VIRTIO_GPU_BLOB_MEM_HOST3D { + virgl_iovecs = Some(sglist_to_iovecs(&vecs[..], mem).map_err(|_| ErrUnspec)?); + } + + 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)), + 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(ErrUnspec)?; + + let handle = resource.virgl_resource.handle.as_ref().ok_or(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) } } @@ -648,7 +746,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::{ @@ -672,6 +773,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) @@ -744,10 +850,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 = GpuConfig::new( GpuMode::VirglRenderer, - Some(GpuCapset::VIRGL | GpuCapset::VIRGL2), + Some(capsets), GpuFlags::default(), ).expect("GpuConfig"); @@ -757,8 +865,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, backend); + let backend = dummy_backend(); + let gpu_backend = dummy_gpu_backend(); + let mut gpu = VirglRendererAdapter::new(&vring, backend, &cfg, gpu_backend); gpu.event_poll(); let edid_req = VhostUserGpuEdidRequest { @@ -862,22 +971,34 @@ mod virgl_cov_tests { assert_matches!(gpu.flush_resource(0, dirty), Ok(GpuResponse::OkNoData)); // Test capset queries - for index in [0, 1, 3] { + 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 (not implemented) diff --git a/vhost-device-gpu/src/device.rs b/vhost-device-gpu/src/device.rs index fd67fed..de73801 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, @@ -140,6 +141,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,6 +175,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, @@ -257,18 +260,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()) } } } @@ -347,11 +359,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), ) } @@ -592,13 +604,17 @@ impl VhostUserGpuBackendInner { fn extract_backend_and_vring<'a>( &mut self, vrings: &'a [VringRwLock], - ) -> IoResult<(&'a VringRwLock, GpuBackend)> { + ) -> IoResult<(&'a VringRwLock, Backend, GpuBackend)> { let control_vring = &vrings[CONTROL_QUEUE as usize]; 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() .ok_or_else(|| 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( @@ -618,8 +634,8 @@ impl VhostUserGpuBackendInner { GpuMode::Gfxstream => handle_adapter!( GfxstreamAdapter, TLS_GFXSTREAM, - |control_vring, gpu_backend| { - GfxstreamAdapter::new(control_vring, &self.gpu_config, gpu_backend) + |control_vring, backend, gpu_backend| { + GfxstreamAdapter::new(control_vring, backend, &self.gpu_config, gpu_backend) }, self, device_event, @@ -630,8 +646,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, @@ -641,8 +657,8 @@ impl VhostUserGpuBackendInner { GpuMode::Null => handle_adapter!( NullAdapter, TLS_NULL, - |control_vring, gpu_backend| { - NullAdapter::new(control_vring, &self.gpu_config, gpu_backend) + |control_vring, backend, gpu_backend| { + NullAdapter::new(control_vring, &self.gpu_config, backend, gpu_backend) }, self, device_event, @@ -695,7 +711,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 set_event_idx(&self, enabled: bool) { @@ -709,6 +729,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(()) @@ -787,7 +812,7 @@ mod tests { use super::*; use crate::{ - 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, @@ -821,11 +846,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; @@ -917,6 +940,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(); @@ -1398,7 +1426,11 @@ mod tests { assert_eq!(backend.features(), 0x0101_7100_001B); 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()); @@ -1420,7 +1452,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) @@ -1439,6 +1471,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(); @@ -1541,6 +1574,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 6c9df9e..8380106 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 cf2c2cf..36c9860 100644 --- a/vhost-device-gpu/src/lib.rs +++ b/vhost-device-gpu/src/lib.rs @@ -30,7 +30,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}; @@ -68,7 +68,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; @@ -98,6 +100,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")] @@ -334,7 +338,7 @@ mod tests { #[test] fn test_default_num_capsets() { #[cfg(feature = "backend-virgl")] - assert_eq!(GpuConfig::DEFAULT_VIRGLRENDER_CAPSET_MASK.num_capsets(), 2); + assert_eq!(GpuConfig::DEFAULT_VIRGLRENDER_CAPSET_MASK.num_capsets(), 3); #[cfg(feature = "backend-gfxstream")] assert_eq!(GpuConfig::DEFAULT_GFXSTREAM_CAPSET_MASK.num_capsets(), 2); } diff --git a/vhost-device-gpu/src/main.rs b/vhost-device-gpu/src/main.rs index cda59b5..05693f1 100644 --- a/vhost-device-gpu/src/main.rs +++ b/vhost-device-gpu/src/main.rs @@ -24,6 +24,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 ffde38c..f66f7ed 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, @@ -441,14 +441,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 +702,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 +748,23 @@ impl fmt::Debug for GpuCommand { } } +fn read_mem_entries( + reader: &mut Reader, + num_entries: u32, +) -> Result, GpuCommandDecodeError> { + 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 +789,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 +836,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 +894,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 +1318,7 @@ mod tests { "CmdSubmit3d", ), ( - ResourceCreateBlob(virtio_gpu_resource_create_blob::default()), + ResourceCreateBlob(virtio_gpu_resource_create_blob::default(), Vec::new()), "ResourceCreateBlob", ), ( @@ -1343,8 +1350,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 +1361,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 49d3557..3a39bb7 100644 --- a/vhost-device-gpu/src/renderer.rs +++ b/vhost-device-gpu/src/renderer.rs @@ -8,7 +8,7 @@ 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}, }; @@ -91,11 +91,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;