summary refs log blame commit diff
path: root/devices/src/virtio/gpu/virtio_2d_backend.rs
blob: 9bdcc9b78f4bb72fdb63749c367a66ed258685ca (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11










                                                                                          












                                                               
                                                      
                                                     














































































































































































                                                                                                   
                                                                              









                                                                                               
                                                                             


































                                                                                 
                                                                          























































                                                                                             
                                                                                




                                                                          

                                                                         
                                                                           
                                                                             

         














                                              
                                                             






















                                                                         











                                 


                                                              














































                                                                                                  
                            






                                                         



























                                                                                                  

                                                                 






























































































































































                                                                                                    
// 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::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, VIRTIO_F_VERSION_1};
use crate::virtio::resource_bridge::ResourceResponse;

#[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 as usize, copyable_size as usize)
                .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 as usize, copyable_size as usize)
                .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_at_addr(addr, len).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_at_addr(addr, len).is_err())
        {
            error!("failed to write to resource: invalid iovec attached");
            return;
        }

        let mut src_slices = Vec::with_capacity(self.guest_iovecs.len());
        for &(addr, len) in &self.guest_iovecs {
            // Unwrap will not panic because we already checked the slices.
            src_slices.push(guest_mem.get_slice_at_addr(addr, len).unwrap());
        }

        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,
            VolatileSlice::new(self.host_mem.as_mut_slice()),
            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;

        if let Err(e) = transfer(
            self.width(),
            self.height(),
            x,
            y,
            width,
            height,
            dst_stride,
            dst_offset,
            dst,
            src_stride,
            src_offset,
            [VolatileSlice::new(self.host_mem.as_mut_slice())]
                .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(
        display: GpuDisplay,
        display_width: u32,
        display_height: u32,
        _renderer_flags: RendererFlags,
        _event_devices: Vec<EventDevice>,
        _gpu_device_socket: VmMemoryControlRequestSocket,
        _pci_bar: Alloc,
    ) -> Option<Box<dyn Backend>> {
        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) -> ResourceResponse {
        ResourceResponse::Invalid
    }

    /// 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
    }
}