summary refs log tree commit diff
path: root/devices/src/virtio/gpu/virtio_2d_backend.rs
diff options
context:
space:
mode:
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
+    }
+}