summary refs log tree commit diff
path: root/devices/src/virtio/gpu/virtio_2d_backend.rs
diff options
context:
space:
mode:
authorJason Macnak <natsu@google.com>2020-01-10 12:45:36 -0800
committerCommit Bot <commit-bot@chromium.org>2020-02-03 11:14:22 +0000
commit327fc2454cdfe36fb612c73c9994435b8602c81a (patch)
treea1fc9a44068612168c1db7884189714d9e4f78ee /devices/src/virtio/gpu/virtio_2d_backend.rs
parentbc499ec2780510083f001bef569449808ad55850 (diff)
downloadcrosvm-327fc2454cdfe36fb612c73c9994435b8602c81a.tar
crosvm-327fc2454cdfe36fb612c73c9994435b8602c81a.tar.gz
crosvm-327fc2454cdfe36fb612c73c9994435b8602c81a.tar.bz2
crosvm-327fc2454cdfe36fb612c73c9994435b8602c81a.tar.lz
crosvm-327fc2454cdfe36fb612c73c9994435b8602c81a.tar.xz
crosvm-327fc2454cdfe36fb612c73c9994435b8602c81a.tar.zst
crosvm-327fc2454cdfe36fb612c73c9994435b8602c81a.zip
virtio-gpu: implement 2D GPU Backend
... which does not require virglrenderer (or any renderer).

This will allow the Cuttlefish team to use minigbm as its gralloc
implementation when both hardware acceleration is available and
unavailable.

Adds a GPU `Backend` trait with all of the existing methods of the
current backend and converts the existing `Backend` into
`Virtio3DBackend` which implements the new trait.

Adds a `Virtio2DBackend` which creates resources with byte vectors on
the host and implements transfers via the old code from
gpu_buffer/src/lib.rs.

Adds a runtime flag to select between 2D and 3D mode with 3D mode as
the default.

Moves the process_resource_bridge() function to the `Frontend` and
instead expose a export_resource() function on the `Backend` to avoid
some code duplication.

BUG=b:123764798
TEST=build + launch cuttlefish w/ 2D mode (minigbm + custom hwcomposer)
TEST=built + launch cuttlefish w/ 2D mode (minigbm + drm_hwcomposer)
TEST=built + launch cuttlefish w/ 3D mode (minigbm + drm_hwcomposer)

Change-Id: Ie5b7a6f80f7e0da72a910644ba42d2f34b246be8
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1993913
Commit-Queue: Jason Macnak <natsu@google.com>
Tested-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Zach Reizner <zachr@chromium.org>
Reviewed-by: Lingfeng Yang <lfy@google.com>
Diffstat (limited to 'devices/src/virtio/gpu/virtio_2d_backend.rs')
-rw-r--r--devices/src/virtio/gpu/virtio_2d_backend.rs644
1 files changed, 644 insertions, 0 deletions
diff --git a/devices/src/virtio/gpu/virtio_2d_backend.rs b/devices/src/virtio/gpu/virtio_2d_backend.rs
new file mode 100644
index 0000000..d92b015
--- /dev/null
+++ b/devices/src/virtio/gpu/virtio_2d_backend.rs
@@ -0,0 +1,644 @@
+// 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 which supports only display.
+
+use std::cell::RefCell;
+use std::cmp::{max, min};
+use std::collections::btree_map::Entry;
+use std::collections::BTreeMap as Map;
+use std::fmt::{self, Display};
+use std::fs::File;
+use std::marker::PhantomData;
+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};
+
+#[derive(Debug)]
+pub enum Error {
+    CheckedArithmetic {
+        field1: (&'static str, usize),
+        field2: (&'static str, usize),
+        op: &'static str,
+    },
+    CheckedRange {
+        field1: (&'static str, usize),
+        field2: (&'static str, usize),
+    },
+    MemCopy(VolatileMemoryError),
+}
+
+impl Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        use self::Error::*;
+
+        match self {
+            CheckedArithmetic {
+                field1: (label1, value1),
+                field2: (label2, value2),
+                op,
+            } => write!(
+                f,
+                "arithmetic failed: {}({}) {} {}({})",
+                label1, value1, op, label2, value2
+            ),
+            CheckedRange {
+                field1: (label1, value1),
+                field2: (label2, value2),
+            } => write!(
+                f,
+                "range check failed: {}({}) vs {}({})",
+                label1, value1, label2, value2
+            ),
+            MemCopy(e) => write!(f, "{}", e),
+        }
+    }
+}
+
+macro_rules! checked_arithmetic {
+    ($x:ident $op:ident $y:ident $op_name:expr) => {
+        $x.$op($y).ok_or_else(|| Error::CheckedArithmetic {
+            field1: (stringify!($x), $x as usize),
+            field2: (stringify!($y), $y as usize),
+            op: $op_name,
+        })
+    };
+    ($x:ident + $y:ident) => {
+        checked_arithmetic!($x checked_add $y "+")
+    };
+    ($x:ident - $y:ident) => {
+        checked_arithmetic!($x checked_sub $y "-")
+    };
+    ($x:ident * $y:ident) => {
+        checked_arithmetic!($x checked_mul $y "*")
+    };
+}
+
+macro_rules! checked_range {
+    ($x:expr; <= $y:expr) => {
+        if $x <= $y {
+            Ok(())
+        } else {
+            Err(Error::CheckedRange {
+                field1: (stringify!($x), $x as usize),
+                field2: (stringify!($y), $y as usize),
+            })
+        }
+    };
+    ($x:ident <= $y:ident) => {
+        check_range!($x; <= $y)
+    };
+}
+
+pub struct Virtio2DResource {
+    width: u32,
+    height: u32,
+    guest_iovecs: Vec<(GuestAddress, usize)>,
+    guest_mem: Option<GuestMemory>,
+    host_mem: Vec<u8>,
+    host_mem_stride: u32,
+    no_sync_send: PhantomData<*mut ()>,
+}
+
+/// Transfers a resource from potentially many chunked src VolatileSlices to a dst VolatileSlice.
+pub fn transfer<'a, S: Iterator<Item = VolatileSlice<'a>>>(
+    resource_w: u32,
+    resource_h: u32,
+    rect_x: u32,
+    rect_y: u32,
+    rect_w: u32,
+    rect_h: u32,
+    dst_stride: u32,
+    dst_offset: u64,
+    dst: VolatileSlice,
+    src_stride: u32,
+    src_offset: u64,
+    mut srcs: S,
+) -> Result<(), Error> {
+    if rect_w == 0 || rect_h == 0 {
+        return Ok(());
+    }
+
+    checked_range!(checked_arithmetic!(rect_x + rect_w)?; <= resource_w)?;
+    checked_range!(checked_arithmetic!(rect_y + rect_h)?; <= resource_h)?;
+
+    let bytes_per_pixel = 4 as u64;
+
+    let rect_x = rect_x as u64;
+    let rect_y = rect_y as u64;
+    let rect_w = rect_w as u64;
+    let rect_h = rect_h as u64;
+
+    let dst_stride = dst_stride as u64;
+    let dst_offset = dst_offset as u64;
+    let dst_resource_offset = dst_offset + (rect_y * dst_stride) + (rect_x * bytes_per_pixel);
+
+    let src_stride = src_stride as u64;
+    let src_offset = src_offset as u64;
+    let src_resource_offset = src_offset + (rect_y * src_stride) + (rect_x * bytes_per_pixel);
+
+    let mut next_src;
+    let mut next_line;
+    let mut current_height = 0 as u64;
+    let mut src_opt = srcs.next();
+
+    // Cumulative start offset of the current src.
+    let mut src_start_offset = 0 as u64;
+    while let Some(src) = src_opt {
+        if current_height >= rect_h {
+            break;
+        }
+
+        let src_size = src.size() as u64;
+
+        // Cumulative end offset of the current src.
+        let src_end_offset = checked_arithmetic!(src_start_offset + src_size)?;
+
+        let src_line_vertical_offset = checked_arithmetic!(current_height * src_stride)?;
+        let src_line_horizontal_offset = checked_arithmetic!(rect_w * bytes_per_pixel)?;
+
+        // Cumulative start/end offsets of the next line to copy within all srcs.
+        let src_line_start_offset =
+            checked_arithmetic!(src_resource_offset + src_line_vertical_offset)?;
+        let src_line_end_offset =
+            checked_arithmetic!(src_line_start_offset + src_line_horizontal_offset)?;
+
+        // Clamp the line start/end offset to be inside the current src.
+        let src_copyable_start_offset = max(src_line_start_offset, src_start_offset);
+        let src_copyable_end_offset = min(src_line_end_offset, src_end_offset);
+
+        if src_copyable_start_offset < src_copyable_end_offset {
+            let copyable_size =
+                checked_arithmetic!(src_copyable_end_offset - src_copyable_start_offset)?;
+
+            let offset_within_src = match src_copyable_start_offset.checked_sub(src_start_offset) {
+                Some(difference) => difference,
+                None => 0,
+            };
+
+            if src_line_end_offset > src_end_offset {
+                next_src = true;
+                next_line = false;
+            } else if src_line_end_offset == src_end_offset {
+                next_src = true;
+                next_line = true;
+            } else {
+                next_src = false;
+                next_line = true;
+            }
+
+            let src_subslice = src
+                .get_slice(offset_within_src, copyable_size)
+                .map_err(|e| Error::MemCopy(e))?;
+
+            let dst_line_vertical_offset = checked_arithmetic!(current_height * dst_stride)?;
+            let dst_line_horizontal_offset =
+                checked_arithmetic!(src_copyable_start_offset - src_line_start_offset)?;
+            let dst_line_offset =
+                checked_arithmetic!(dst_line_vertical_offset + dst_line_horizontal_offset)?;
+            let dst_start_offset = checked_arithmetic!(dst_resource_offset + dst_line_offset)?;
+
+            let dst_subslice = dst
+                .get_slice(dst_start_offset, copyable_size)
+                .map_err(|e| Error::MemCopy(e))?;
+
+            src_subslice.copy_to_volatile_slice(dst_subslice);
+        } else {
+            if src_line_start_offset >= src_start_offset {
+                next_src = true;
+                next_line = false;
+            } else {
+                next_src = false;
+                next_line = true;
+            }
+        };
+
+        if next_src {
+            src_start_offset = checked_arithmetic!(src_start_offset + src_size)?;
+            src_opt = srcs.next();
+        }
+
+        if next_line {
+            current_height += 1;
+        }
+    }
+
+    Ok(())
+}
+
+impl Virtio2DResource {
+    /// Attaches scatter-gather memory to this resource.
+    pub fn attach_backing(
+        &mut self,
+        iovecs: Vec<(GuestAddress, usize)>,
+        mem: &GuestMemory,
+    ) -> bool {
+        if iovecs
+            .iter()
+            .any(|&(addr, len)| mem.get_slice(addr.offset(), len as u64).is_err())
+        {
+            return false;
+        }
+        self.detach_backing();
+        self.guest_mem = Some(mem.clone());
+        for (addr, len) in iovecs {
+            self.guest_iovecs.push((addr, len));
+        }
+        true
+    }
+
+    /// Detaches previously attached scatter-gather memory from this resource.
+    pub fn detach_backing(&mut self) {
+        self.guest_iovecs.clear();
+        self.guest_mem = None;
+    }
+
+    fn as_mut(&mut self) -> &mut dyn VirtioResource {
+        self
+    }
+}
+
+impl VirtioResource for Virtio2DResource {
+    fn width(&self) -> u32 {
+        self.width
+    }
+
+    fn height(&self) -> u32 {
+        self.height
+    }
+
+    fn import_to_display(&mut self, _display: &Rc<RefCell<GpuDisplay>>) -> Option<u32> {
+        None
+    }
+
+    /// Performs a transfer to the given host side resource from its backing in guest memory.
+    fn write_from_guest_memory(
+        &mut self,
+        x: u32,
+        y: u32,
+        width: u32,
+        height: u32,
+        src_offset: u64,
+        _mem: &GuestMemory,
+    ) {
+        let guest_mem = match &self.guest_mem {
+            Some(mem) => mem,
+            None => {
+                error!("failed to write to resource: no guest memory attached");
+                return;
+            }
+        };
+
+        if self
+            .guest_iovecs
+            .iter()
+            .any(|&(addr, len)| guest_mem.get_slice(addr.offset(), len as u64).is_err())
+        {
+            error!("failed to write to resource: invalid iovec attached");
+            return;
+        }
+
+        let mut src_slices = Vec::new();
+        for (addr, len) in &self.guest_iovecs {
+            // Unwrap will not panic because we already checked the slices.
+            src_slices.push(guest_mem.get_slice(addr.offset(), *len as u64).unwrap());
+        }
+
+        let host_mem_len = self.host_mem.len() as u64;
+
+        let src_stride = self.host_mem_stride;
+        let src_offset = src_offset;
+
+        let dst_stride = self.host_mem_stride;
+        let dst_offset = 0;
+
+        if let Err(e) = transfer(
+            self.width(),
+            self.height(),
+            x,
+            y,
+            width,
+            height,
+            dst_stride,
+            dst_offset,
+            self.host_mem
+                .as_mut_slice()
+                .get_slice(0, host_mem_len)
+                .unwrap(),
+            src_stride,
+            src_offset,
+            src_slices.iter().cloned(),
+        ) {
+            error!("failed to write to resource: {}", e);
+        }
+    }
+
+    /// Reads from this host side resource to a volatile slice of memory.
+    fn read_to_volatile(
+        &mut self,
+        x: u32,
+        y: u32,
+        width: u32,
+        height: u32,
+        dst: VolatileSlice,
+        dst_stride: u32,
+    ) {
+        let src_stride = self.host_mem_stride;
+        let src_offset = 0;
+
+        let dst_offset = 0;
+
+        let host_mem_len = self.host_mem.len() as u64;
+
+        if let Err(e) = transfer(
+            self.width(),
+            self.height(),
+            x,
+            y,
+            width,
+            height,
+            dst_stride,
+            dst_offset,
+            dst,
+            src_stride,
+            src_offset,
+            [self
+                .host_mem
+                .as_mut_slice()
+                .get_slice(0, host_mem_len)
+                .unwrap()]
+            .iter()
+            .cloned(),
+        ) {
+            error!("failed to read from resource: {}", e);
+        }
+    }
+}
+
+/// The virtio-gpu backend state tracker which does not support accelerated rendering.
+pub struct Virtio2DBackend {
+    base: VirtioBackend,
+    resources: Map<u32, Virtio2DResource>,
+    /// All commands processed by this 2D backend are synchronous and are completed immediately so
+    /// we just need to keep track of the latest created fence and return that in fence_poll().
+    latest_created_fence_id: u32,
+}
+
+impl Virtio2DBackend {
+    pub fn new(display: GpuDisplay, display_width: u32, display_height: u32) -> Virtio2DBackend {
+        Virtio2DBackend {
+            base: VirtioBackend {
+                display: Rc::new(RefCell::new(display)),
+                display_width,
+                display_height,
+                event_devices: Default::default(),
+                scanout_resource_id: None,
+                scanout_surface_id: None,
+                cursor_resource_id: None,
+                cursor_surface_id: None,
+            },
+            resources: Default::default(),
+            latest_created_fence_id: 0,
+        }
+    }
+}
+
+impl Backend for Virtio2DBackend {
+    /// Returns the number of capsets provided by the Backend.
+    fn capsets() -> u32 {
+        0
+    }
+
+    /// Returns the bitset of virtio features provided by the Backend.
+    fn features() -> u64 {
+        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(Virtio2DBackend::new(
+            display,
+            display_width,
+            display_height,
+        )))
+    }
+
+    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 {
+        self.latest_created_fence_id = fence_id;
+
+        GpuResponse::OkNoData
+    }
+
+    /// Returns the id of the latest fence to complete.
+    fn fence_poll(&mut self) -> u32 {
+        self.latest_created_fence_id
+    }
+
+    fn create_resource_2d(
+        &mut self,
+        id: u32,
+        width: u32,
+        height: u32,
+        _format: u32,
+    ) -> GpuResponse {
+        if id == 0 {
+            return GpuResponse::ErrInvalidResourceId;
+        }
+        match self.resources.entry(id) {
+            Entry::Vacant(slot) => {
+                // All virtio formats are 4 bytes per pixel.
+                let resource_bpp = 4;
+                let resource_stride = resource_bpp * width;
+                let resource_size = (resource_stride as usize) * (height as usize);
+
+                let gpu_resource = Virtio2DResource {
+                    width,
+                    height,
+                    guest_iovecs: Vec::new(),
+                    guest_mem: None,
+                    host_mem: vec![0; resource_size],
+                    host_mem_stride: resource_stride,
+                    no_sync_send: PhantomData,
+                };
+                slot.insert(gpu_resource);
+                GpuResponse::OkNoData
+            }
+            Entry::Occupied(_) => GpuResponse::ErrInvalidResourceId,
+        }
+    }
+
+    /// 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(_) => GpuResponse::OkNoData,
+            None => GpuResponse::ErrInvalidResourceId,
+        }
+    }
+
+    /// 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 {
+        if resource_id == 0 || self.resources.get_mut(&resource_id).is_some() {
+            self.base.set_scanout(resource_id)
+        } else {
+            GpuResponse::ErrInvalidResourceId
+        }
+    }
+
+    /// 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 {
+        if id == 0 {
+            return GpuResponse::OkNoData;
+        }
+
+        let resource = match self.resources.get_mut(&id) {
+            Some(r) => r,
+            None => return GpuResponse::ErrInvalidResourceId,
+        };
+
+        self.base.flush_resource(resource, id)
+    }
+
+    /// 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 {
+        if let Some(resource) = self.resources.get_mut(&id) {
+            resource.write_from_guest_memory(x, y, width, height, src_offset, mem);
+            GpuResponse::OkNoData
+        } else {
+            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(resource) => {
+                if resource.attach_backing(vecs, mem) {
+                    GpuResponse::OkNoData
+                } else {
+                    GpuResponse::ErrUnspec
+                }
+            }
+            None => GpuResponse::ErrInvalidResourceId,
+        }
+    }
+
+    /// 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(resource) => {
+                resource.detach_backing();
+                GpuResponse::OkNoData
+            }
+            None => GpuResponse::ErrInvalidResourceId,
+        }
+    }
+
+    /// Updates the cursor's memory to the given id, and sets its position to the given coordinates.
+    fn update_cursor(&mut self, id: u32, x: u32, y: u32) -> GpuResponse {
+        let resource = self.resources.get_mut(&id).map(|r| r.as_mut());
+
+        self.base.update_cursor(id, x, y, resource)
+    }
+
+    /// Moves the cursor's position to the given coordinates.
+    fn move_cursor(&mut self, x: u32, y: u32) -> GpuResponse {
+        self.base.move_cursor(x, y)
+    }
+
+    /// Gets the renderer's capset information associated with `index`.
+    fn get_capset_info(&self, _index: u32) -> GpuResponse {
+        GpuResponse::ErrUnspec
+    }
+
+    /// Gets the capset of `version` associated with `id`.
+    fn get_capset(&self, _id: u32, _version: u32) -> GpuResponse {
+        GpuResponse::ErrUnspec
+    }
+}