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