rutabaga_gfx/
rutabaga_2d.rs

1// Copyright 2020 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! rutabaga_2d: Handles 2D virtio-gpu hypercalls.
6
7use std::cmp::max;
8use std::cmp::min;
9use std::cmp::Ordering;
10use std::io::IoSliceMut;
11
12use crate::rutabaga_core::Rutabaga2DInfo;
13use crate::rutabaga_core::RutabagaComponent;
14use crate::rutabaga_core::RutabagaResource;
15use crate::rutabaga_utils::*;
16
17/// Transfers a resource from potentially many chunked src slices to a dst slice.
18fn transfer_2d(
19    resource_w: u32,
20    resource_h: u32,
21    rect_x: u32,
22    rect_y: u32,
23    rect_w: u32,
24    rect_h: u32,
25    dst_stride: u32,
26    dst_offset: u64,
27    mut dst: IoSliceMut,
28    src_stride: u32,
29    src_offset: u64,
30    srcs: &[&[u8]],
31) -> RutabagaResult<()> {
32    if rect_w == 0 || rect_h == 0 {
33        return Ok(());
34    }
35
36    checked_range!(checked_arithmetic!(rect_x + rect_w)?; <= resource_w)?;
37    checked_range!(checked_arithmetic!(rect_y + rect_h)?; <= resource_h)?;
38
39    let bytes_per_pixel = 4u64;
40
41    let rect_x = rect_x as u64;
42    let rect_y = rect_y as u64;
43    let rect_w = rect_w as u64;
44    let rect_h = rect_h as u64;
45
46    let dst_stride = dst_stride as u64;
47    let dst_resource_offset = dst_offset + (rect_y * dst_stride) + (rect_x * bytes_per_pixel);
48
49    let src_stride = src_stride as u64;
50    let src_resource_offset = src_offset + (rect_y * src_stride) + (rect_x * bytes_per_pixel);
51
52    let mut next_src;
53    let mut next_line;
54    let mut current_height = 0u64;
55    let mut srcs = srcs.iter();
56    let mut src_opt = srcs.next();
57
58    // Cumulative start offset of the current src.
59    let mut src_start_offset = 0u64;
60    while let Some(src) = src_opt {
61        if current_height >= rect_h {
62            break;
63        }
64
65        let src_size = src.len() as u64;
66
67        // Cumulative end offset of the current src.
68        let src_end_offset = checked_arithmetic!(src_start_offset + src_size)?;
69
70        let src_line_vertical_offset = checked_arithmetic!(current_height * src_stride)?;
71        let src_line_horizontal_offset = checked_arithmetic!(rect_w * bytes_per_pixel)?;
72
73        // Cumulative start/end offsets of the next line to copy within all srcs.
74        let src_line_start_offset =
75            checked_arithmetic!(src_resource_offset + src_line_vertical_offset)?;
76        let src_line_end_offset =
77            checked_arithmetic!(src_line_start_offset + src_line_horizontal_offset)?;
78
79        // Clamp the line start/end offset to be inside the current src.
80        let src_copyable_start_offset = max(src_line_start_offset, src_start_offset);
81        let src_copyable_end_offset = min(src_line_end_offset, src_end_offset);
82
83        if src_copyable_start_offset < src_copyable_end_offset {
84            let copyable_size =
85                checked_arithmetic!(src_copyable_end_offset - src_copyable_start_offset)?;
86
87            let offset_within_src = src_copyable_start_offset.saturating_sub(src_start_offset);
88
89            match src_line_end_offset.cmp(&src_end_offset) {
90                Ordering::Greater => {
91                    next_src = true;
92                    next_line = false;
93                }
94                Ordering::Equal => {
95                    next_src = true;
96                    next_line = true;
97                }
98                Ordering::Less => {
99                    next_src = false;
100                    next_line = true;
101                }
102            }
103
104            let src_end = offset_within_src + copyable_size;
105            let src_subslice = src
106                .get(offset_within_src as usize..src_end as usize)
107                .ok_or(RutabagaError::InvalidIovec)?;
108
109            let dst_line_vertical_offset = checked_arithmetic!(current_height * dst_stride)?;
110            let dst_line_horizontal_offset =
111                checked_arithmetic!(src_copyable_start_offset - src_line_start_offset)?;
112            let dst_line_offset =
113                checked_arithmetic!(dst_line_vertical_offset + dst_line_horizontal_offset)?;
114            let dst_start_offset = checked_arithmetic!(dst_resource_offset + dst_line_offset)?;
115
116            let dst_end_offset = dst_start_offset + copyable_size;
117            let dst_subslice = dst
118                .get_mut(dst_start_offset as usize..dst_end_offset as usize)
119                .ok_or(RutabagaError::InvalidIovec)?;
120
121            dst_subslice.copy_from_slice(src_subslice);
122        } else if src_line_start_offset >= src_start_offset {
123            next_src = true;
124            next_line = false;
125        } else {
126            next_src = false;
127            next_line = true;
128        };
129
130        if next_src {
131            src_start_offset = checked_arithmetic!(src_start_offset + src_size)?;
132            src_opt = srcs.next();
133        }
134
135        if next_line {
136            current_height += 1;
137        }
138    }
139
140    Ok(())
141}
142
143pub struct Rutabaga2D {
144    fence_handler: RutabagaFenceHandler,
145}
146
147impl Rutabaga2D {
148    pub fn init(fence_handler: RutabagaFenceHandler) -> RutabagaResult<Box<dyn RutabagaComponent>> {
149        Ok(Box::new(Rutabaga2D { fence_handler }))
150    }
151}
152
153impl RutabagaComponent for Rutabaga2D {
154    fn create_fence(&mut self, fence: RutabagaFence) -> RutabagaResult<()> {
155        self.fence_handler.call(fence);
156        Ok(())
157    }
158
159    fn create_3d(
160        &self,
161        resource_id: u32,
162        resource_create_3d: ResourceCreate3D,
163    ) -> RutabagaResult<RutabagaResource> {
164        // All virtio formats are 4 bytes per pixel.
165        let resource_bpp = 4;
166        let resource_stride = resource_bpp * resource_create_3d.width;
167        let resource_size = (resource_stride as usize) * (resource_create_3d.height as usize);
168        let info_2d = Rutabaga2DInfo {
169            width: resource_create_3d.width,
170            height: resource_create_3d.height,
171            host_mem: vec![0; resource_size],
172        };
173
174        Ok(RutabagaResource {
175            resource_id,
176            handle: None,
177            blob: false,
178            blob_mem: 0,
179            blob_flags: 0,
180            map_info: None,
181            info_2d: Some(info_2d),
182            info_3d: None,
183            vulkan_info: None,
184            backing_iovecs: None,
185            component_mask: 1 << (RutabagaComponentType::Rutabaga2D as u8),
186            size: resource_size as u64,
187            mapping: None,
188        })
189    }
190
191    fn transfer_write(
192        &self,
193        _ctx_id: u32,
194        resource: &mut RutabagaResource,
195        transfer: Transfer3D,
196    ) -> RutabagaResult<()> {
197        if transfer.is_empty() {
198            return Ok(());
199        }
200
201        let mut info_2d = resource
202            .info_2d
203            .take()
204            .ok_or(RutabagaError::Invalid2DInfo)?;
205
206        let iovecs = resource
207            .backing_iovecs
208            .take()
209            .ok_or(RutabagaError::InvalidIovec)?;
210
211        // All offical virtio_gpu formats are 4 bytes per pixel.
212        let resource_bpp = 4;
213        let mut src_slices = Vec::with_capacity(iovecs.len());
214        for iovec in &iovecs {
215            // SAFETY:
216            // Safe because Rutabaga users should have already checked the iovecs.
217            let slice = unsafe { std::slice::from_raw_parts(iovec.base as *mut u8, iovec.len) };
218            src_slices.push(slice);
219        }
220
221        let src_stride = resource_bpp * info_2d.width;
222        let src_offset = transfer.offset;
223
224        let dst_stride = resource_bpp * info_2d.width;
225        let dst_offset = 0;
226
227        transfer_2d(
228            info_2d.width,
229            info_2d.height,
230            transfer.x,
231            transfer.y,
232            transfer.w,
233            transfer.h,
234            dst_stride,
235            dst_offset,
236            IoSliceMut::new(info_2d.host_mem.as_mut_slice()),
237            src_stride,
238            src_offset,
239            &src_slices,
240        )?;
241
242        resource.info_2d = Some(info_2d);
243        resource.backing_iovecs = Some(iovecs);
244        Ok(())
245    }
246
247    fn transfer_read(
248        &self,
249        _ctx_id: u32,
250        resource: &mut RutabagaResource,
251        transfer: Transfer3D,
252        buf: Option<IoSliceMut>,
253    ) -> RutabagaResult<()> {
254        let mut info_2d = resource
255            .info_2d
256            .take()
257            .ok_or(RutabagaError::Invalid2DInfo)?;
258
259        // All offical virtio_gpu formats are 4 bytes per pixel.
260        let resource_bpp = 4;
261        let src_stride = resource_bpp * info_2d.width;
262        let src_offset = 0;
263        let dst_offset = 0;
264
265        let dst_slice = buf.ok_or(RutabagaError::SpecViolation(
266            "need a destination slice for transfer read",
267        ))?;
268
269        transfer_2d(
270            info_2d.width,
271            info_2d.height,
272            transfer.x,
273            transfer.y,
274            transfer.w,
275            transfer.h,
276            transfer.stride,
277            dst_offset,
278            dst_slice,
279            src_stride,
280            src_offset,
281            &[info_2d.host_mem.as_mut_slice()],
282        )?;
283
284        resource.info_2d = Some(info_2d);
285        Ok(())
286    }
287}