summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml1
-rw-r--r--devices/Cargo.toml1
-rw-r--r--devices/src/virtio/gpu/mod.rs23
-rw-r--r--devices/src/virtio/gpu/virtio_gfxstream_backend.rs722
-rw-r--r--src/main.rs26
5 files changed, 773 insertions, 0 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 9afef46..7eb7215 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -38,6 +38,7 @@ wl-dmabuf = ["devices/wl-dmabuf", "gpu_buffer", "resources/wl-dmabuf"]
 x = ["devices/x"]
 virtio-gpu-next = ["gpu_renderer/virtio-gpu-next"]
 composite-disk = ["protos/composite-disk", "protobuf", "disk/composite-disk"]
+gfxstream = ["devices/gfxstream"]
 
 [dependencies]
 arch = { path = "arch" }
diff --git a/devices/Cargo.toml b/devices/Cargo.toml
index 236cf4e..83aa406 100644
--- a/devices/Cargo.toml
+++ b/devices/Cargo.toml
@@ -9,6 +9,7 @@ gpu = ["gpu_buffer", "gpu_display", "gpu_renderer"]
 tpm = ["protos/trunks", "tpm2"]
 wl-dmabuf = []
 x = ["gpu_display/x"]
+gfxstream = ["gpu"]
 
 [dependencies]
 audio_streams = "*"
diff --git a/devices/src/virtio/gpu/mod.rs b/devices/src/virtio/gpu/mod.rs
index 49fdff4..0fcc453 100644
--- a/devices/src/virtio/gpu/mod.rs
+++ b/devices/src/virtio/gpu/mod.rs
@@ -6,6 +6,7 @@ mod protocol;
 mod virtio_2d_backend;
 mod virtio_3d_backend;
 mod virtio_backend;
+mod virtio_gfxstream_backend;
 
 use std::cell::RefCell;
 use std::collections::VecDeque;
@@ -40,6 +41,8 @@ use super::{PciCapabilityType, VirtioPciShmCap, VirtioPciShmCapID};
 use self::protocol::*;
 use self::virtio_2d_backend::Virtio2DBackend;
 use self::virtio_3d_backend::Virtio3DBackend;
+#[cfg(feature = "gfxstream")]
+use self::virtio_gfxstream_backend::VirtioGfxStreamBackend;
 use crate::pci::{PciBarConfiguration, PciBarPrefetchable, PciBarRegionType, PciCapability};
 
 use vm_control::VmMemoryControlRequestSocket;
@@ -51,6 +54,8 @@ pub const DEFAULT_DISPLAY_HEIGHT: u32 = 1024;
 pub enum GpuMode {
     Mode2D,
     Mode3D,
+    #[cfg(feature = "gfxstream")]
+    ModeGfxStream,
 }
 
 #[derive(Debug)]
@@ -305,6 +310,8 @@ trait Backend {
 enum BackendKind {
     Virtio2D,
     Virtio3D,
+    #[cfg(feature = "gfxstream")]
+    VirtioGfxStream,
 }
 
 impl BackendKind {
@@ -313,6 +320,8 @@ impl BackendKind {
         match self {
             BackendKind::Virtio2D => Virtio2DBackend::capsets(),
             BackendKind::Virtio3D => Virtio3DBackend::capsets(),
+            #[cfg(feature = "gfxstream")]
+            BackendKind::VirtioGfxStream => VirtioGfxStreamBackend::capsets(),
         }
     }
 
@@ -321,6 +330,8 @@ impl BackendKind {
         match self {
             BackendKind::Virtio2D => Virtio2DBackend::features(),
             BackendKind::Virtio3D => Virtio3DBackend::features(),
+            #[cfg(feature = "gfxstream")]
+            BackendKind::VirtioGfxStream => VirtioGfxStreamBackend::features(),
         }
     }
 
@@ -354,6 +365,16 @@ impl BackendKind {
                 gpu_device_socket,
                 pci_bar,
             ),
+            #[cfg(feature = "gfxstream")]
+            BackendKind::VirtioGfxStream => VirtioGfxStreamBackend::build(
+                possible_displays,
+                display_width,
+                display_height,
+                renderer_flags,
+                event_devices,
+                gpu_device_socket,
+                pci_bar,
+            ),
         }
     }
 }
@@ -1033,6 +1054,8 @@ impl Gpu {
         let backend_kind = match gpu_parameters.mode {
             GpuMode::Mode2D => BackendKind::Virtio2D,
             GpuMode::Mode3D => BackendKind::Virtio3D,
+            #[cfg(feature = "gfxstream")]
+            GpuMode::ModeGfxStream => BackendKind::VirtioGfxStream,
         };
 
         Gpu {
diff --git a/devices/src/virtio/gpu/virtio_gfxstream_backend.rs b/devices/src/virtio/gpu/virtio_gfxstream_backend.rs
new file mode 100644
index 0000000..aa02e15
--- /dev/null
+++ b/devices/src/virtio/gpu/virtio_gfxstream_backend.rs
@@ -0,0 +1,722 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Implementation of a virtio-gpu protocol command processor for
+//! API passthrough.
+
+#![cfg(feature = "gfxstream")]
+
+use std::cell::RefCell;
+use std::collections::btree_map::Entry;
+use std::collections::BTreeMap as Map;
+use std::fs::File;
+use std::mem::transmute;
+use std::os::raw::{c_char, c_int, c_uchar, c_uint, c_void};
+use std::panic;
+use std::rc::Rc;
+use std::usize;
+
+use data_model::*;
+use gpu_display::*;
+use gpu_renderer::RendererFlags;
+use resources::Alloc;
+use sys_util::{error, GuestAddress, GuestMemory};
+use vm_control::VmMemoryControlRequestSocket;
+
+use super::protocol::GpuResponse;
+pub use super::virtio_backend::{VirtioBackend, VirtioResource};
+use crate::virtio::gpu::{Backend, DisplayBackend, VIRTIO_F_VERSION_1, VIRTIO_GPU_F_VIRGL};
+
+// C definitions related to gfxstream
+// In gfxstream, only write_fence is used
+// (for synchronization of commands delivered)
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct GfxStreamRendererCallbacks {
+    pub version: c_int,
+    pub write_fence: unsafe extern "C" fn(cookie: *mut c_void, fence: u32),
+}
+
+// virtio-gpu-3d transfer-related structs (begin)
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct virgl_renderer_resource_create_args {
+    pub handle: u32,
+    pub target: u32,
+    pub format: u32,
+    pub bind: u32,
+    pub width: u32,
+    pub height: u32,
+    pub depth: u32,
+    pub array_size: u32,
+    pub last_level: u32,
+    pub nr_samples: u32,
+    pub flags: u32,
+}
+
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct virgl_renderer_resource_info {
+    pub handle: u32,
+    pub virgl_format: u32,
+    pub width: u32,
+    pub height: u32,
+    pub depth: u32,
+    pub flags: u32,
+    pub tex_id: u32,
+    pub stride: u32,
+    pub drm_fourcc: c_int,
+}
+
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct virgl_box {
+    pub x: u32,
+    pub y: u32,
+    pub z: u32,
+    pub w: u32,
+    pub h: u32,
+    pub d: u32,
+}
+
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct iovec {
+    pub iov_base: *mut c_void,
+    pub iov_len: usize,
+}
+
+// virtio-gpu-3d transfer-related structs (end)
+
+#[link(name = "gfxstream_backend")]
+extern "C" {
+
+    // Function to globally init gfxstream backend's internal state, taking display/renderer
+    // parameters.
+    fn gfxstream_backend_init(
+        display_width: u32,
+        display_height: u32,
+        display_type: u32,
+        renderer_cookie: *mut c_void,
+        renderer_flags: i32,
+        renderer_callbacks: *mut GfxStreamRendererCallbacks,
+    );
+
+    // virtio-gpu-3d ioctl functions (begin)
+
+    // In gfxstream, the resource create/transfer ioctls correspond to creating buffers for API
+    // forwarding and the notification of new API calls forwarded by the guest, unless they
+    // correspond to minigbm resource targets (PIPE_TEXTURE_2D), in which case they create globally
+    // visible shared GL textures to support gralloc.
+    fn pipe_virgl_renderer_poll();
+    fn pipe_virgl_renderer_resource_create(
+        args: *mut virgl_renderer_resource_create_args,
+        iov: *mut iovec,
+        num_iovs: u32,
+    ) -> c_int;
+
+    fn pipe_virgl_renderer_resource_unref(res_handle: u32);
+    fn pipe_virgl_renderer_context_create(handle: u32, nlen: u32, name: *const c_char) -> c_int;
+    fn pipe_virgl_renderer_context_destroy(handle: u32);
+    fn pipe_virgl_renderer_transfer_read_iov(
+        handle: u32,
+        ctx_id: u32,
+        level: u32,
+        stride: u32,
+        layer_stride: u32,
+        box_: *mut virgl_box,
+        offset: u64,
+        iov: *mut iovec,
+        iovec_cnt: c_int,
+    ) -> c_int;
+    fn pipe_virgl_renderer_transfer_write_iov(
+        handle: u32,
+        ctx_id: u32,
+        level: c_int,
+        stride: u32,
+        layer_stride: u32,
+        box_: *mut virgl_box,
+        offset: u64,
+        iovec: *mut iovec,
+        iovec_cnt: c_uint,
+    ) -> c_int;
+    fn pipe_virgl_renderer_resource_attach_iov(
+        res_handle: c_int,
+        iov: *mut iovec,
+        num_iovs: c_int,
+    ) -> c_int;
+    fn pipe_virgl_renderer_resource_detach_iov(
+        res_handle: c_int,
+        iov: *mut *mut iovec,
+        num_iovs: *mut c_int,
+    );
+    fn pipe_virgl_renderer_create_fence(client_fence_id: c_int, ctx_id: u32) -> c_int;
+    fn pipe_virgl_renderer_ctx_attach_resource(ctx_id: c_int, res_handle: c_int);
+    fn pipe_virgl_renderer_ctx_detach_resource(ctx_id: c_int, res_handle: c_int);
+
+    fn stream_renderer_flush_resource_and_readback(
+        res_handle: u32,
+        x: u32,
+        y: u32,
+        width: u32,
+        height: u32,
+        pixels: *mut c_uchar,
+        max_bytes: u32,
+    );
+}
+
+// Fence state stuff (begin)
+
+struct FenceState {
+    latest_fence: u32,
+}
+impl FenceState {
+    pub fn write(&mut self, latest_fence: u32) {
+        if latest_fence > self.latest_fence {
+            self.latest_fence = latest_fence;
+        }
+    }
+}
+
+struct VirglCookie {
+    fence_state: Rc<RefCell<FenceState>>,
+}
+
+extern "C" fn write_fence(cookie: *mut c_void, fence: u32) {
+    assert!(!cookie.is_null());
+    let cookie = unsafe { &*(cookie as *mut VirglCookie) };
+
+    // Track the most recent fence.
+    let mut fence_state = cookie.fence_state.borrow_mut();
+    fence_state.write(fence);
+}
+
+const GFXSTREAM_RENDERER_CALLBACKS: &GfxStreamRendererCallbacks = &GfxStreamRendererCallbacks {
+    version: 1,
+    write_fence,
+};
+
+// Fence state stuff (end)
+
+pub struct VirtioGfxStreamBackend {
+    base: VirtioBackend,
+
+    /// Mapping from resource ids to in-use GuestMemory.
+    resources: Map<u32, Option<GuestMemory>>,
+
+    /// All commands processed by this backend are synchronous
+    /// and are either completed immediately or handled in a different layer,
+    /// so we just need to keep track of the latest created fence
+    /// and return that in fence_poll().
+    fence_state: Rc<RefCell<FenceState>>,
+}
+
+impl VirtioGfxStreamBackend {
+    pub fn new(
+        display: GpuDisplay,
+        display_width: u32,
+        display_height: u32,
+        _gpu_device_socket: VmMemoryControlRequestSocket,
+        _pci_bar: Alloc,
+    ) -> VirtioGfxStreamBackend {
+        let fence_state = Rc::new(RefCell::new(FenceState { latest_fence: 0 }));
+        let cookie: *mut VirglCookie = Box::into_raw(Box::new(VirglCookie {
+            fence_state: Rc::clone(&fence_state),
+        }));
+
+        let renderer_flags: RendererFlags = RendererFlags::new().use_surfaceless(true);
+
+        let display_rc_refcell = Rc::new(RefCell::new(display));
+
+        let scanout_surface = match (display_rc_refcell.borrow_mut()).create_surface(
+            None,
+            display_width,
+            display_height,
+        ) {
+            Ok(surface) => surface,
+            Err(e) => {
+                error!("Failed to create display surface: {}", e);
+                0
+            }
+        };
+
+        unsafe {
+            gfxstream_backend_init(
+                display_width,
+                display_height,
+                1, /* default to shmem display */
+                cookie as *mut c_void,
+                renderer_flags.into(),
+                transmute(GFXSTREAM_RENDERER_CALLBACKS),
+            );
+        }
+
+        VirtioGfxStreamBackend {
+            base: VirtioBackend {
+                display: Rc::clone(&display_rc_refcell),
+                display_width,
+                display_height,
+                event_devices: Default::default(),
+                scanout_resource_id: None,
+                scanout_surface_id: Some(scanout_surface),
+                cursor_resource_id: None,
+                cursor_surface_id: None,
+            },
+            resources: Default::default(),
+            fence_state,
+        }
+    }
+}
+
+impl Backend for VirtioGfxStreamBackend {
+    /// Returns the number of capsets provided by the Backend.
+    fn capsets() -> u32 {
+        1
+    }
+
+    /// Returns the bitset of virtio features provided by the Backend.
+    fn features() -> u64 {
+        1 << VIRTIO_GPU_F_VIRGL | 1 << VIRTIO_F_VERSION_1
+    }
+
+    /// Returns the underlying Backend.
+    fn build(
+        possible_displays: &[DisplayBackend],
+        display_width: u32,
+        display_height: u32,
+        _renderer_flags: RendererFlags,
+        _event_devices: Vec<EventDevice>,
+        gpu_device_socket: VmMemoryControlRequestSocket,
+        pci_bar: Alloc,
+    ) -> Option<Box<dyn Backend>> {
+        let mut display_opt = None;
+        for display in possible_displays {
+            match display.build() {
+                Ok(c) => {
+                    display_opt = Some(c);
+                    break;
+                }
+                Err(e) => error!("failed to open display: {}", e),
+            };
+        }
+
+        let display = match display_opt {
+            Some(d) => d,
+            None => {
+                error!("failed to open any displays");
+                return None;
+            }
+        };
+
+        Some(Box::new(VirtioGfxStreamBackend::new(
+            display,
+            display_width,
+            display_height,
+            gpu_device_socket,
+            pci_bar,
+        )))
+    }
+
+    /// Gets a reference to the display passed into `new`.
+    fn display(&self) -> &Rc<RefCell<GpuDisplay>> {
+        &self.base.display
+    }
+
+    /// Processes the internal `display` events and returns `true` if the main display was closed.
+    fn process_display(&mut self) -> bool {
+        self.base.process_display()
+    }
+
+    /// Gets the list of supported display resolutions as a slice of `(width, height)` tuples.
+    fn display_info(&self) -> [(u32, u32); 1] {
+        self.base.display_info()
+    }
+
+    /// Attaches the given input device to the given surface of the display (to allow for input
+    /// from a X11 window for example).
+    fn import_event_device(&mut self, event_device: EventDevice, scanout: u32) {
+        self.base.import_event_device(event_device, scanout);
+    }
+
+    /// If supported, export the resource with the given id to a file.
+    fn export_resource(&mut self, _id: u32) -> Option<File> {
+        None
+    }
+
+    /// Creates a fence with the given id that can be used to determine when the previous command
+    /// completed.
+    fn create_fence(&mut self, ctx_id: u32, fence_id: u32) -> GpuResponse {
+        unsafe {
+            pipe_virgl_renderer_create_fence(fence_id as i32, ctx_id);
+        }
+        GpuResponse::OkNoData
+    }
+
+    /// Returns the id of the latest fence to complete.
+    fn fence_poll(&mut self) -> u32 {
+        unsafe {
+            pipe_virgl_renderer_poll();
+        }
+        self.fence_state.borrow().latest_fence
+    }
+
+    fn create_resource_2d(
+        &mut self,
+        _id: u32,
+        _width: u32,
+        _height: u32,
+        _format: u32,
+    ) -> GpuResponse {
+        // Not considered for gfxstream
+        GpuResponse::ErrUnspec
+    }
+
+    /// Removes the guest's reference count for the given resource id.
+    fn unref_resource(&mut self, id: u32) -> GpuResponse {
+        match self.resources.remove(&id) {
+            Some(_) => (),
+            None => {
+                return GpuResponse::ErrInvalidResourceId;
+            }
+        }
+
+        unsafe {
+            pipe_virgl_renderer_resource_unref(id);
+        }
+
+        GpuResponse::OkNoData
+    }
+
+    /// Sets the given resource id as the source of scanout to the display.
+    fn set_scanout(&mut self, _scanout_id: u32, _resource_id: u32) -> GpuResponse {
+        GpuResponse::OkNoData
+    }
+
+    /// Flushes the given rectangle of pixels of the given resource to the display.
+    fn flush_resource(
+        &mut self,
+        id: u32,
+        _x: u32,
+        _y: u32,
+        _width: u32,
+        _height: u32,
+    ) -> GpuResponse {
+        // For now, always update the whole display.
+        let mut display_ref = self.base.display.borrow_mut();
+
+        let scanout_surface_id = match self.base.scanout_surface_id {
+            Some(id) => id,
+            _ => {
+                error!("No scanout surface created for backend!");
+                return GpuResponse::ErrInvalidResourceId;
+            }
+        };
+
+        let fb = match display_ref.framebuffer_region(
+            scanout_surface_id,
+            0,
+            0,
+            self.base.display_width,
+            self.base.display_height,
+        ) {
+            Some(fb) => fb,
+            None => {
+                panic!(
+                    "failed to access framebuffer for surface {}",
+                    scanout_surface_id
+                );
+            }
+        };
+
+        let fb_volatile_slice = fb.as_volatile_slice();
+        let fb_begin = fb_volatile_slice.as_ptr() as *mut c_uchar;
+        let fb_bytes = fb_volatile_slice.size() as usize;
+
+        unsafe {
+            stream_renderer_flush_resource_and_readback(
+                id,
+                0,
+                0,
+                self.base.display_width,
+                self.base.display_height,
+                fb_begin,
+                fb_bytes as u32,
+            );
+        }
+
+        display_ref.flip(scanout_surface_id);
+
+        GpuResponse::OkNoData
+    }
+
+    /// Copes the given rectangle of pixels of the given resource's backing memory to the host side
+    /// resource.
+    fn transfer_to_resource_2d(
+        &mut self,
+        _id: u32,
+        _x: u32,
+        _y: u32,
+        _width: u32,
+        _height: u32,
+        _src_offset: u64,
+        _mem: &GuestMemory,
+    ) -> GpuResponse {
+        // Not considered for gfxstream
+        GpuResponse::ErrInvalidResourceId
+    }
+
+    /// Attaches backing memory to the given resource, represented by a `Vec` of `(address, size)`
+    /// tuples in the guest's physical address space.
+    fn attach_backing(
+        &mut self,
+        id: u32,
+        mem: &GuestMemory,
+        vecs: Vec<(GuestAddress, usize)>,
+    ) -> GpuResponse {
+        match self.resources.get_mut(&id) {
+            Some(entry) => {
+                *entry = Some(mem.clone());
+            }
+            None => {
+                return GpuResponse::ErrInvalidResourceId;
+            }
+        }
+
+        let mut backing_iovecs: Vec<iovec> = Vec::new();
+
+        for (addr, len) in vecs {
+            let slice = mem.get_slice(addr.offset(), len as u64).unwrap();
+            backing_iovecs.push(iovec {
+                iov_base: slice.as_ptr() as *mut c_void,
+                iov_len: len as usize,
+            });
+        }
+
+        unsafe {
+            pipe_virgl_renderer_resource_attach_iov(
+                id as i32,
+                backing_iovecs.as_mut_ptr() as *mut iovec,
+                backing_iovecs.len() as i32,
+            );
+        }
+        GpuResponse::OkNoData
+    }
+
+    /// Detaches any backing memory from the given resource, if there is any.
+    fn detach_backing(&mut self, id: u32) -> GpuResponse {
+        match self.resources.get_mut(&id) {
+            Some(entry) => {
+                *entry = None;
+            }
+            None => {
+                return GpuResponse::ErrInvalidResourceId;
+            }
+        }
+
+        unsafe {
+            pipe_virgl_renderer_resource_detach_iov(
+                id as i32,
+                std::ptr::null_mut(),
+                std::ptr::null_mut(),
+            );
+        }
+        GpuResponse::OkNoData
+    }
+
+    fn update_cursor(&mut self, _id: u32, _x: u32, _y: u32) -> GpuResponse {
+        // Not considered for gfxstream
+        GpuResponse::OkNoData
+    }
+
+    fn move_cursor(&mut self, _x: u32, _y: u32) -> GpuResponse {
+        // Not considered for gfxstream
+        GpuResponse::OkNoData
+    }
+
+    fn get_capset_info(&self, index: u32) -> GpuResponse {
+        if 0 != index {
+            return GpuResponse::ErrUnspec;
+        }
+        GpuResponse::OkCapsetInfo {
+            id: index,
+            version: 1,
+            size: 0,
+        }
+    }
+
+    fn get_capset(&self, id: u32, _version: u32) -> GpuResponse {
+        if 0 != id {
+            return GpuResponse::ErrUnspec;
+        }
+        GpuResponse::OkCapset(Vec::new())
+    }
+
+    fn create_renderer_context(&mut self, id: u32) -> GpuResponse {
+        unsafe {
+            pipe_virgl_renderer_context_create(id, 1, std::ptr::null_mut());
+        }
+        GpuResponse::OkNoData
+    }
+
+    fn destroy_renderer_context(&mut self, id: u32) -> GpuResponse {
+        unsafe {
+            pipe_virgl_renderer_context_destroy(id);
+        }
+        GpuResponse::OkNoData
+    }
+
+    fn context_attach_resource(&mut self, ctx_id: u32, res_id: u32) -> GpuResponse {
+        unsafe {
+            pipe_virgl_renderer_ctx_attach_resource(ctx_id as i32, res_id as i32);
+        }
+        GpuResponse::OkNoData
+    }
+
+    fn context_detach_resource(&mut self, ctx_id: u32, res_id: u32) -> GpuResponse {
+        unsafe {
+            pipe_virgl_renderer_ctx_detach_resource(ctx_id as i32, res_id as i32);
+        }
+        GpuResponse::OkNoData
+    }
+
+    fn resource_create_3d(
+        &mut self,
+        id: u32,
+        target: u32,
+        format: u32,
+        bind: u32,
+        width: u32,
+        height: u32,
+        depth: u32,
+        array_size: u32,
+        last_level: u32,
+        nr_samples: u32,
+        flags: u32,
+    ) -> GpuResponse {
+        if id == 0 {
+            return GpuResponse::ErrInvalidResourceId;
+        }
+
+        match self.resources.entry(id) {
+            Entry::Vacant(slot) => {
+                slot.insert(None /* no guest memory attached yet */);
+            }
+            Entry::Occupied(_) => {
+                return GpuResponse::ErrInvalidResourceId;
+            }
+        }
+
+        let mut create_args = virgl_renderer_resource_create_args {
+            handle: id,
+            target,
+            format,
+            bind,
+            width,
+            height,
+            depth,
+            array_size,
+            last_level,
+            nr_samples,
+            flags,
+        };
+
+        unsafe {
+            pipe_virgl_renderer_resource_create(
+                &mut create_args as *mut virgl_renderer_resource_create_args,
+                std::ptr::null_mut(),
+                0,
+            );
+        }
+
+        GpuResponse::OkNoData
+    }
+
+    fn transfer_to_resource_3d(
+        &mut self,
+        ctx_id: u32,
+        res_id: u32,
+        x: u32,
+        y: u32,
+        z: u32,
+        width: u32,
+        height: u32,
+        depth: u32,
+        level: u32,
+        stride: u32,
+        layer_stride: u32,
+        offset: u64,
+    ) -> GpuResponse {
+        let mut transfer_box = virgl_box {
+            x,
+            y,
+            z,
+            w: width,
+            h: height,
+            d: depth,
+        };
+
+        unsafe {
+            pipe_virgl_renderer_transfer_write_iov(
+                res_id,
+                ctx_id,
+                level as i32,
+                stride,
+                layer_stride,
+                &mut transfer_box as *mut virgl_box,
+                offset,
+                std::ptr::null_mut(),
+                0,
+            );
+        }
+        GpuResponse::OkNoData
+    }
+
+    fn transfer_from_resource_3d(
+        &mut self,
+        ctx_id: u32,
+        res_id: u32,
+        x: u32,
+        y: u32,
+        z: u32,
+        width: u32,
+        height: u32,
+        depth: u32,
+        level: u32,
+        stride: u32,
+        layer_stride: u32,
+        offset: u64,
+    ) -> GpuResponse {
+        let mut transfer_box = virgl_box {
+            x,
+            y,
+            z,
+            w: width,
+            h: height,
+            d: depth,
+        };
+
+        unsafe {
+            pipe_virgl_renderer_transfer_read_iov(
+                res_id,
+                ctx_id,
+                level,
+                stride,
+                layer_stride,
+                &mut transfer_box as *mut virgl_box,
+                offset,
+                std::ptr::null_mut(),
+                0,
+            );
+        }
+        GpuResponse::OkNoData
+    }
+
+    // Not considered for gfxstream
+    fn submit_command(&mut self, _ctx_id: u32, _commands: &mut [u8]) -> GpuResponse {
+        GpuResponse::ErrUnspec
+    }
+
+    // Not considered for gfxstream
+    fn force_ctx_0(&mut self) {}
+}
diff --git a/src/main.rs b/src/main.rs
index 1e86e47..569935c 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -124,12 +124,37 @@ fn parse_gpu_options(s: Option<&str>) -> argument::Result<GpuParameters> {
 
         for (k, v) in opts {
             match k {
+                // Deprecated: Specifying --gpu=<mode> Not great as the mode can be set multiple
+                // times if the user specifies several modes (--gpu=2d,3d,gfxstream)
                 "2d" | "2D" => {
                     gpu_params.mode = GpuMode::Mode2D;
                 }
                 "3d" | "3D" => {
                     gpu_params.mode = GpuMode::Mode3D;
                 }
+                #[cfg(feature = "gfxstream")]
+                "gfxstream" => {
+                    gpu_params.mode = GpuMode::ModeGfxStream;
+                }
+                // Preferred: Specifying --gpu,backend=<mode>
+                "backend" => match v {
+                    "2d" | "2D" => {
+                        gpu_params.mode = GpuMode::Mode2D;
+                    }
+                    "3d" | "3D" => {
+                        gpu_params.mode = GpuMode::Mode3D;
+                    }
+                    #[cfg(feature = "gfxstream")]
+                    "gfxstream" => {
+                        gpu_params.mode = GpuMode::ModeGfxStream;
+                    }
+                    _ => {
+                        return Err(argument::Error::InvalidValue {
+                            value: v.to_string(),
+                            expected: "gpu parameter 'backend' should be one of (2d|3d|gfxstream)",
+                        });
+                    }
+                },
                 "egl" => match v {
                     "true" | "" => {
                         gpu_params.renderer_use_egl = true;
@@ -1209,6 +1234,7 @@ writeback=BOOL - Indicates whether the VM can use writeback caching (default: fa
                                   "[width=INT,height=INT]",
                                   "(EXPERIMENTAL) Comma separated key=value pairs for setting up a virtio-gpu device
                                   Possible key values:
+                                  backend=(2d|3d|gfxstream) - Which backend to use for virtio-gpu (determining rendering protocol)
                                   width=INT - The width of the virtual display connected to the virtio-gpu.
                                   height=INT - The height of the virtual display connected to the virtio-gpu.
                                   egl[=true|=false] - If the virtio-gpu backend should use a EGL context for rendering.