1 // Copyright 2024 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 use std::collections::btree_map::Entry;
6 use std::collections::BTreeMap as Map;
7 use std::collections::BTreeSet as Set;
8 use std::io::Cursor;
9 use std::os::raw::c_void;
10 use std::path::Path;
11 use std::sync::Arc;
12 use std::sync::Mutex;
13
14 use log::error;
15 use rutabaga_gfx::calculate_capset_mask;
16 use rutabaga_gfx::kumquat_support::kumquat_gpu_protocol::*;
17 use rutabaga_gfx::kumquat_support::RutabagaEvent;
18 use rutabaga_gfx::kumquat_support::RutabagaMemoryMapping;
19 use rutabaga_gfx::kumquat_support::RutabagaSharedMemory;
20 use rutabaga_gfx::kumquat_support::RutabagaStream;
21 use rutabaga_gfx::kumquat_support::RutabagaTube;
22 use rutabaga_gfx::ResourceCreate3D;
23 use rutabaga_gfx::ResourceCreateBlob;
24 use rutabaga_gfx::Rutabaga;
25 use rutabaga_gfx::RutabagaAsBorrowedDescriptor as AsBorrowedDescriptor;
26 use rutabaga_gfx::RutabagaBuilder;
27 use rutabaga_gfx::RutabagaComponentType;
28 use rutabaga_gfx::RutabagaDescriptor;
29 use rutabaga_gfx::RutabagaError;
30 use rutabaga_gfx::RutabagaFence;
31 use rutabaga_gfx::RutabagaFenceHandler;
32 use rutabaga_gfx::RutabagaHandle;
33 use rutabaga_gfx::RutabagaIovec;
34 use rutabaga_gfx::RutabagaResult;
35 use rutabaga_gfx::RutabagaWsi;
36 use rutabaga_gfx::Transfer3D;
37 use rutabaga_gfx::VulkanInfo;
38 use rutabaga_gfx::RUTABAGA_FLAG_FENCE;
39 use rutabaga_gfx::RUTABAGA_FLAG_FENCE_HOST_SHAREABLE;
40 use rutabaga_gfx::RUTABAGA_HANDLE_TYPE_MEM_SHM;
41 use rutabaga_gfx::RUTABAGA_MAP_ACCESS_RW;
42 use rutabaga_gfx::RUTABAGA_MAP_CACHE_CACHED;
43
44 const SNAPSHOT_DIR: &str = "/tmp/";
45
46 pub struct KumquatGpuConnection {
47 stream: RutabagaStream,
48 }
49
50 pub struct KumquatGpuResource {
51 attached_contexts: Set<u32>,
52 mapping: Option<RutabagaMemoryMapping>,
53 }
54
55 pub struct FenceData {
56 pub pending_fences: Map<u64, RutabagaEvent>,
57 }
58
59 pub type FenceState = Arc<Mutex<FenceData>>;
60
create_fence_handler(fence_state: FenceState) -> RutabagaFenceHandler61 pub fn create_fence_handler(fence_state: FenceState) -> RutabagaFenceHandler {
62 RutabagaFenceHandler::new(move |completed_fence: RutabagaFence| {
63 let mut state = fence_state.lock().unwrap();
64 match (*state).pending_fences.entry(completed_fence.fence_id) {
65 Entry::Occupied(o) => {
66 let (_, mut event) = o.remove_entry();
67 event.signal().unwrap();
68 }
69 Entry::Vacant(_) => {
70 // This is fine, since an actual fence doesn't create emulated sync
71 // entry
72 }
73 }
74 })
75 }
76
77 pub struct KumquatGpu {
78 rutabaga: Rutabaga,
79 fence_state: FenceState,
80 id_allocator: u32,
81 resources: Map<u32, KumquatGpuResource>,
82 }
83
84 impl KumquatGpu {
new(capset_names: String, renderer_features: String) -> RutabagaResult<KumquatGpu>85 pub fn new(capset_names: String, renderer_features: String) -> RutabagaResult<KumquatGpu> {
86 let capset_mask = calculate_capset_mask(capset_names.as_str().split(":"));
87 let fence_state = Arc::new(Mutex::new(FenceData {
88 pending_fences: Default::default(),
89 }));
90
91 let fence_handler = create_fence_handler(fence_state.clone());
92
93 let renderer_features_opt = if renderer_features.is_empty() {
94 None
95 } else {
96 Some(renderer_features)
97 };
98
99 let rutabaga = RutabagaBuilder::new(RutabagaComponentType::CrossDomain, capset_mask)
100 .set_use_external_blob(true)
101 .set_use_egl(true)
102 .set_wsi(RutabagaWsi::Surfaceless)
103 .set_renderer_features(renderer_features_opt)
104 .build(fence_handler, None)?;
105
106 Ok(KumquatGpu {
107 rutabaga,
108 fence_state,
109 id_allocator: 0,
110 resources: Default::default(),
111 })
112 }
113
allocate_id(&mut self) -> u32114 pub fn allocate_id(&mut self) -> u32 {
115 self.id_allocator = self.id_allocator + 1;
116 self.id_allocator
117 }
118 }
119
120 impl KumquatGpuConnection {
new(connection: RutabagaTube) -> KumquatGpuConnection121 pub fn new(connection: RutabagaTube) -> KumquatGpuConnection {
122 KumquatGpuConnection {
123 stream: RutabagaStream::new(connection),
124 }
125 }
126
process_command(&mut self, kumquat_gpu: &mut KumquatGpu) -> RutabagaResult<bool>127 pub fn process_command(&mut self, kumquat_gpu: &mut KumquatGpu) -> RutabagaResult<bool> {
128 let mut hung_up = false;
129 let protocols = self.stream.read()?;
130
131 for protocol in protocols {
132 match protocol {
133 KumquatGpuProtocol::GetNumCapsets => {
134 let resp = kumquat_gpu_protocol_ctrl_hdr {
135 type_: KUMQUAT_GPU_PROTOCOL_RESP_NUM_CAPSETS,
136 payload: kumquat_gpu.rutabaga.get_num_capsets(),
137 };
138
139 self.stream.write(KumquatGpuProtocolWrite::Cmd(resp))?;
140 }
141 KumquatGpuProtocol::GetCapsetInfo(capset_index) => {
142 let (capset_id, version, size) =
143 kumquat_gpu.rutabaga.get_capset_info(capset_index)?;
144
145 let resp = kumquat_gpu_protocol_resp_capset_info {
146 hdr: kumquat_gpu_protocol_ctrl_hdr {
147 type_: KUMQUAT_GPU_PROTOCOL_RESP_CAPSET_INFO,
148 ..Default::default()
149 },
150 capset_id,
151 version,
152 size,
153 ..Default::default()
154 };
155
156 self.stream.write(KumquatGpuProtocolWrite::Cmd(resp))?;
157 }
158 KumquatGpuProtocol::GetCapset(cmd) => {
159 let capset = kumquat_gpu
160 .rutabaga
161 .get_capset(cmd.capset_id, cmd.capset_version)?;
162
163 let resp = kumquat_gpu_protocol_ctrl_hdr {
164 type_: KUMQUAT_GPU_PROTOCOL_RESP_CAPSET,
165 payload: capset.len().try_into()?,
166 };
167
168 self.stream
169 .write(KumquatGpuProtocolWrite::CmdWithData(resp, capset))?;
170 }
171 KumquatGpuProtocol::CtxCreate(cmd) => {
172 let context_id = kumquat_gpu.allocate_id();
173 let context_name: Option<String> =
174 String::from_utf8(cmd.debug_name.to_vec()).ok();
175
176 kumquat_gpu.rutabaga.create_context(
177 context_id,
178 cmd.context_init,
179 context_name.as_deref(),
180 )?;
181
182 let resp = kumquat_gpu_protocol_ctrl_hdr {
183 type_: KUMQUAT_GPU_PROTOCOL_RESP_CONTEXT_CREATE,
184 payload: context_id,
185 };
186
187 self.stream.write(KumquatGpuProtocolWrite::Cmd(resp))?;
188 }
189 KumquatGpuProtocol::CtxDestroy(ctx_id) => {
190 kumquat_gpu.rutabaga.destroy_context(ctx_id)?;
191 }
192 KumquatGpuProtocol::CtxAttachResource(cmd) => {
193 kumquat_gpu
194 .rutabaga
195 .context_attach_resource(cmd.ctx_id, cmd.resource_id)?;
196 }
197 KumquatGpuProtocol::CtxDetachResource(cmd) => {
198 kumquat_gpu
199 .rutabaga
200 .context_detach_resource(cmd.ctx_id, cmd.resource_id)?;
201
202 let mut resource = kumquat_gpu
203 .resources
204 .remove(&cmd.resource_id)
205 .ok_or(RutabagaError::InvalidResourceId)?;
206
207 resource.attached_contexts.remove(&cmd.ctx_id);
208 if resource.attached_contexts.len() == 0 {
209 if resource.mapping.is_some() {
210 kumquat_gpu.rutabaga.detach_backing(cmd.resource_id)?;
211 }
212
213 kumquat_gpu.rutabaga.unref_resource(cmd.resource_id)?;
214 } else {
215 kumquat_gpu.resources.insert(cmd.resource_id, resource);
216 }
217 }
218 KumquatGpuProtocol::ResourceCreate3d(cmd) => {
219 let resource_create_3d = ResourceCreate3D {
220 target: cmd.target,
221 format: cmd.format,
222 bind: cmd.bind,
223 width: cmd.width,
224 height: cmd.height,
225 depth: cmd.depth,
226 array_size: cmd.array_size,
227 last_level: cmd.last_level,
228 nr_samples: cmd.nr_samples,
229 flags: cmd.flags,
230 };
231
232 let size = cmd.size as usize;
233 let descriptor: RutabagaDescriptor =
234 RutabagaSharedMemory::new("rutabaga_server", size as u64)?.into();
235
236 let clone = descriptor.try_clone()?;
237 let mut vecs: Vec<RutabagaIovec> = Vec::new();
238
239 // Creating the mapping closes the cloned descriptor.
240 let mapping = RutabagaMemoryMapping::from_safe_descriptor(
241 clone,
242 size,
243 RUTABAGA_MAP_CACHE_CACHED | RUTABAGA_MAP_ACCESS_RW,
244 )?;
245 let rutabaga_mapping = mapping.as_rutabaga_mapping();
246
247 vecs.push(RutabagaIovec {
248 base: rutabaga_mapping.ptr as *mut c_void,
249 len: size,
250 });
251
252 let resource_id = kumquat_gpu.allocate_id();
253
254 kumquat_gpu
255 .rutabaga
256 .resource_create_3d(resource_id, resource_create_3d)?;
257
258 kumquat_gpu.rutabaga.attach_backing(resource_id, vecs)?;
259 kumquat_gpu.resources.insert(
260 resource_id,
261 KumquatGpuResource {
262 attached_contexts: Default::default(),
263 mapping: Some(mapping),
264 },
265 );
266
267 kumquat_gpu
268 .rutabaga
269 .context_attach_resource(cmd.ctx_id, resource_id)?;
270
271 let resp = kumquat_gpu_protocol_resp_resource_create {
272 hdr: kumquat_gpu_protocol_ctrl_hdr {
273 type_: KUMQUAT_GPU_PROTOCOL_RESP_RESOURCE_CREATE,
274 ..Default::default()
275 },
276 resource_id,
277 ..Default::default()
278 };
279
280 self.stream.write(KumquatGpuProtocolWrite::CmdWithHandle(
281 resp,
282 RutabagaHandle {
283 os_handle: descriptor,
284 handle_type: RUTABAGA_HANDLE_TYPE_MEM_SHM,
285 },
286 ))?;
287 }
288 KumquatGpuProtocol::TransferToHost3d(cmd, emulated_fence) => {
289 let resource_id = cmd.resource_id;
290
291 let transfer = Transfer3D {
292 x: cmd.box_.x,
293 y: cmd.box_.y,
294 z: cmd.box_.z,
295 w: cmd.box_.w,
296 h: cmd.box_.h,
297 d: cmd.box_.d,
298 level: cmd.level,
299 stride: cmd.stride,
300 layer_stride: cmd.layer_stride,
301 offset: cmd.offset,
302 };
303
304 kumquat_gpu
305 .rutabaga
306 .transfer_write(cmd.ctx_id, resource_id, transfer)?;
307
308 let mut event: RutabagaEvent = emulated_fence.try_into()?;
309 event.signal()?;
310 }
311 KumquatGpuProtocol::TransferFromHost3d(cmd, emulated_fence) => {
312 let resource_id = cmd.resource_id;
313
314 let transfer = Transfer3D {
315 x: cmd.box_.x,
316 y: cmd.box_.y,
317 z: cmd.box_.z,
318 w: cmd.box_.w,
319 h: cmd.box_.h,
320 d: cmd.box_.d,
321 level: cmd.level,
322 stride: cmd.stride,
323 layer_stride: cmd.layer_stride,
324 offset: cmd.offset,
325 };
326
327 kumquat_gpu
328 .rutabaga
329 .transfer_read(cmd.ctx_id, resource_id, transfer, None)?;
330
331 let mut event: RutabagaEvent = emulated_fence.try_into()?;
332 event.signal()?;
333 }
334 KumquatGpuProtocol::CmdSubmit3d(cmd, mut cmd_buf, fence_ids) => {
335 kumquat_gpu.rutabaga.submit_command(
336 cmd.ctx_id,
337 &mut cmd_buf[..],
338 &fence_ids[..],
339 )?;
340
341 if cmd.flags & RUTABAGA_FLAG_FENCE != 0 {
342 let fence_id = kumquat_gpu.allocate_id() as u64;
343 let fence = RutabagaFence {
344 flags: cmd.flags,
345 fence_id,
346 ctx_id: cmd.ctx_id,
347 ring_idx: cmd.ring_idx,
348 };
349
350 let mut fence_descriptor_opt: Option<RutabagaHandle> = None;
351 let actual_fence = cmd.flags & RUTABAGA_FLAG_FENCE_HOST_SHAREABLE != 0;
352 if !actual_fence {
353 let event: RutabagaEvent = RutabagaEvent::new()?;
354 let clone = event.try_clone()?;
355 let emulated_fence: RutabagaHandle = clone.into();
356
357 fence_descriptor_opt = Some(emulated_fence);
358 let mut fence_state = kumquat_gpu.fence_state.lock().unwrap();
359 (*fence_state).pending_fences.insert(fence_id, event);
360 }
361
362 kumquat_gpu.rutabaga.create_fence(fence)?;
363
364 if actual_fence {
365 fence_descriptor_opt =
366 Some(kumquat_gpu.rutabaga.export_fence(fence_id)?);
367 kumquat_gpu.rutabaga.destroy_fences(&[fence_id])?;
368 }
369
370 let fence_descriptor = fence_descriptor_opt
371 .ok_or(RutabagaError::SpecViolation("No fence descriptor"))?;
372
373 let resp = kumquat_gpu_protocol_resp_cmd_submit_3d {
374 hdr: kumquat_gpu_protocol_ctrl_hdr {
375 type_: KUMQUAT_GPU_PROTOCOL_RESP_CMD_SUBMIT_3D,
376 ..Default::default()
377 },
378 fence_id,
379 handle_type: fence_descriptor.handle_type,
380 ..Default::default()
381 };
382
383 self.stream.write(KumquatGpuProtocolWrite::CmdWithHandle(
384 resp,
385 fence_descriptor,
386 ))?;
387 }
388 }
389 KumquatGpuProtocol::ResourceCreateBlob(cmd) => {
390 let resource_id = kumquat_gpu.allocate_id();
391
392 let resource_create_blob = ResourceCreateBlob {
393 blob_mem: cmd.blob_mem,
394 blob_flags: cmd.blob_flags,
395 blob_id: cmd.blob_id,
396 size: cmd.size,
397 };
398
399 kumquat_gpu.rutabaga.resource_create_blob(
400 cmd.ctx_id,
401 resource_id,
402 resource_create_blob,
403 None,
404 None,
405 )?;
406
407 let handle = kumquat_gpu.rutabaga.export_blob(resource_id)?;
408 let mut vk_info: VulkanInfo = Default::default();
409 if let Ok(vulkan_info) = kumquat_gpu.rutabaga.vulkan_info(resource_id) {
410 vk_info = vulkan_info;
411 }
412
413 kumquat_gpu.resources.insert(
414 resource_id,
415 KumquatGpuResource {
416 attached_contexts: Set::from([cmd.ctx_id]),
417 mapping: None,
418 },
419 );
420
421 let resp = kumquat_gpu_protocol_resp_resource_create {
422 hdr: kumquat_gpu_protocol_ctrl_hdr {
423 type_: KUMQUAT_GPU_PROTOCOL_RESP_RESOURCE_CREATE,
424 ..Default::default()
425 },
426 resource_id,
427 handle_type: handle.handle_type,
428 vulkan_info: vk_info,
429 };
430
431 self.stream
432 .write(KumquatGpuProtocolWrite::CmdWithHandle(resp, handle))?;
433
434 kumquat_gpu
435 .rutabaga
436 .context_attach_resource(cmd.ctx_id, resource_id)?;
437 }
438 KumquatGpuProtocol::SnapshotSave => {
439 kumquat_gpu.rutabaga.snapshot(&Path::new(SNAPSHOT_DIR))?;
440
441 let resp = kumquat_gpu_protocol_ctrl_hdr {
442 type_: KUMQUAT_GPU_PROTOCOL_RESP_OK_SNAPSHOT,
443 payload: 0,
444 };
445
446 self.stream.write(KumquatGpuProtocolWrite::Cmd(resp))?;
447 }
448 KumquatGpuProtocol::SnapshotRestore => {
449 kumquat_gpu.rutabaga.restore(&Path::new(SNAPSHOT_DIR))?;
450
451 let resp = kumquat_gpu_protocol_ctrl_hdr {
452 type_: KUMQUAT_GPU_PROTOCOL_RESP_OK_SNAPSHOT,
453 payload: 0,
454 };
455
456 self.stream.write(KumquatGpuProtocolWrite::Cmd(resp))?;
457 }
458 KumquatGpuProtocol::OkNoData => {
459 hung_up = true;
460 }
461 _ => {
462 error!("Unsupported protocol {:?}", protocol);
463 return Err(RutabagaError::Unsupported);
464 }
465 };
466 }
467
468 Ok(hung_up)
469 }
470 }
471
472 impl AsBorrowedDescriptor for KumquatGpuConnection {
as_borrowed_descriptor(&self) -> &RutabagaDescriptor473 fn as_borrowed_descriptor(&self) -> &RutabagaDescriptor {
474 self.stream.as_borrowed_descriptor()
475 }
476 }
477