// 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. //! rutabaga_2d: Handles 2D virtio-gpu hypercalls. use std::cmp::{max, min}; use data_model::*; use crate::rutabaga_core::{Rutabaga2DInfo, RutabagaComponent, RutabagaResource}; use crate::rutabaga_utils::*; /// Transfers a resource from potentially many chunked src VolatileSlices to a dst VolatileSlice. pub fn transfer_2d<'a, S: Iterator>>( 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, ) -> RutabagaResult<()> { 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 = 4u64; 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 = 0u64; let mut src_opt = srcs.next(); // Cumulative start offset of the current src. let mut src_start_offset = 0u64; 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 = src_copyable_start_offset.saturating_sub(src_start_offset); 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)?; 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)?; 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(()) } pub struct Rutabaga2D { latest_created_fence_id: u32, } impl Rutabaga2D { pub fn init() -> RutabagaResult> { Ok(Box::new(Rutabaga2D { latest_created_fence_id: 0, })) } } impl RutabagaComponent for Rutabaga2D { fn create_fence(&mut self, fence_data: RutabagaFenceData) -> RutabagaResult<()> { self.latest_created_fence_id = fence_data.fence_id as u32; Ok(()) } fn poll(&self) -> u32 { self.latest_created_fence_id } fn create_3d( &self, resource_id: u32, resource_create_3d: ResourceCreate3D, ) -> RutabagaResult { // All virtio formats are 4 bytes per pixel. let resource_bpp = 4; let resource_stride = resource_bpp * resource_create_3d.width; let resource_size = (resource_stride as usize) * (resource_create_3d.height as usize); let info_2d = Rutabaga2DInfo { width: resource_create_3d.width, height: resource_create_3d.height, host_mem: vec![0; resource_size], }; Ok(RutabagaResource { resource_id, handle: None, blob: false, blob_mem: 0, blob_flags: 0, map_info: None, info_2d: Some(info_2d), info_3d: None, vulkan_info: None, backing_iovecs: None, }) } fn transfer_write( &self, _ctx_id: u32, resource: &mut RutabagaResource, transfer: Transfer3D, ) -> RutabagaResult<()> { if transfer.is_empty() { return Ok(()); } let mut info_2d = resource.info_2d.take().ok_or(RutabagaError::Unsupported)?; let iovecs = resource .backing_iovecs .take() .ok_or(RutabagaError::Unsupported)?; // All offical virtio_gpu formats are 4 bytes per pixel. let resource_bpp = 4; let mut src_slices = Vec::with_capacity(iovecs.len()); for iovec in &iovecs { // Safe because Rutabaga users should have already checked the iovecs. let slice = unsafe { VolatileSlice::from_raw_parts(iovec.base as *mut u8, iovec.len) }; src_slices.push(slice); } let src_stride = resource_bpp * info_2d.width; let src_offset = transfer.offset; let dst_stride = resource_bpp * info_2d.width; let dst_offset = 0; transfer_2d( info_2d.width, info_2d.height, transfer.x, transfer.y, transfer.w, transfer.h, dst_stride, dst_offset, VolatileSlice::new(info_2d.host_mem.as_mut_slice()), src_stride, src_offset, src_slices.iter().cloned(), )?; resource.info_2d = Some(info_2d); resource.backing_iovecs = Some(iovecs); Ok(()) } fn transfer_read( &self, _ctx_id: u32, resource: &mut RutabagaResource, transfer: Transfer3D, buf: Option, ) -> RutabagaResult<()> { let mut info_2d = resource.info_2d.take().ok_or(RutabagaError::Unsupported)?; // All offical virtio_gpu formats are 4 bytes per pixel. let resource_bpp = 4; let src_stride = resource_bpp * info_2d.width; let src_offset = 0; let dst_offset = 0; let dst_slice = buf.ok_or(RutabagaError::Unsupported)?; transfer_2d( info_2d.width, info_2d.height, transfer.x, transfer.y, transfer.w, transfer.h, transfer.stride, dst_offset, dst_slice, src_stride, src_offset, [VolatileSlice::new(info_2d.host_mem.as_mut_slice())] .iter() .cloned(), )?; resource.info_2d = Some(info_2d); Ok(()) } }