1 // Copyright 2021 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 //! The cross-domain component type, specialized for allocating and sharing resources across domain
6 //! boundaries.
7
8 use std::cmp::max;
9 use std::collections::BTreeMap as Map;
10 use std::collections::VecDeque;
11 use std::convert::TryInto;
12 use std::fs::File;
13 use std::mem::size_of;
14 use std::sync::Arc;
15 use std::sync::Condvar;
16 use std::sync::Mutex;
17 use std::thread;
18
19 use log::error;
20 use zerocopy::AsBytes;
21 use zerocopy::FromBytes;
22 use zerocopy::FromZeroes;
23
24 use crate::cross_domain::cross_domain_protocol::*;
25 use crate::cross_domain::sys::channel;
26 use crate::cross_domain::sys::channel_signal;
27 use crate::cross_domain::sys::channel_wait;
28 use crate::cross_domain::sys::descriptor_analysis;
29 use crate::cross_domain::sys::read_volatile;
30 use crate::cross_domain::sys::write_volatile;
31 use crate::cross_domain::sys::Receiver;
32 use crate::cross_domain::sys::Sender;
33 use crate::cross_domain::sys::SystemStream;
34 use crate::cross_domain::sys::WaitContext;
35 use crate::rutabaga_core::RutabagaComponent;
36 use crate::rutabaga_core::RutabagaContext;
37 use crate::rutabaga_core::RutabagaResource;
38 use crate::rutabaga_os::SafeDescriptor;
39 use crate::rutabaga_utils::*;
40 use crate::DrmFormat;
41 use crate::ImageAllocationInfo;
42 use crate::ImageMemoryRequirements;
43 use crate::RutabagaGralloc;
44 use crate::RutabagaGrallocBackendFlags;
45 use crate::RutabagaGrallocFlags;
46
47 mod cross_domain_protocol;
48 mod sys;
49
50 #[allow(dead_code)]
51 const WAIT_CONTEXT_MAX: usize = 16;
52
53 pub struct CrossDomainEvent {
54 token: CrossDomainToken,
55 hung_up: bool,
56 readable: bool,
57 }
58
59 #[derive(Copy, Clone, PartialEq, Eq)]
60 pub enum CrossDomainToken {
61 ContextChannel,
62 WaylandReadPipe(u32),
63 Resample,
64 Kill,
65 }
66
67 const CROSS_DOMAIN_DEFAULT_BUFFER_SIZE: usize = 4096;
68 const CROSS_DOMAIN_MAX_SEND_RECV_SIZE: usize =
69 CROSS_DOMAIN_DEFAULT_BUFFER_SIZE - size_of::<CrossDomainSendReceive>();
70
71 pub(crate) enum CrossDomainItem {
72 ImageRequirements(ImageMemoryRequirements),
73 WaylandKeymap(SafeDescriptor),
74 #[allow(dead_code)] // `WaylandReadPipe` is never constructed on Windows.
75 WaylandReadPipe(File),
76 WaylandWritePipe(File),
77 }
78
79 pub(crate) enum CrossDomainJob {
80 HandleFence(RutabagaFence),
81 #[allow(dead_code)] // `AddReadPipe` is never constructed on Windows.
82 AddReadPipe(u32),
83 Finish,
84 }
85
86 enum RingWrite<'a, T> {
87 Write(T, Option<&'a [u8]>),
88 WriteFromFile(CrossDomainReadWrite, &'a mut File, bool),
89 }
90
91 pub(crate) type CrossDomainResources = Arc<Mutex<Map<u32, CrossDomainResource>>>;
92 type CrossDomainJobs = Mutex<Option<VecDeque<CrossDomainJob>>>;
93 pub(crate) type CrossDomainItemState = Arc<Mutex<CrossDomainItems>>;
94
95 pub(crate) struct CrossDomainResource {
96 #[allow(dead_code)] // `handle` is never used on Windows.
97 pub handle: Option<Arc<RutabagaHandle>>,
98 pub backing_iovecs: Option<Vec<RutabagaIovec>>,
99 }
100
101 pub(crate) struct CrossDomainItems {
102 descriptor_id: u32,
103 requirements_blob_id: u32,
104 read_pipe_id: u32,
105 table: Map<u32, CrossDomainItem>,
106 }
107
108 pub(crate) struct CrossDomainState {
109 context_resources: CrossDomainResources,
110 query_ring_id: u32,
111 channel_ring_id: u32,
112 #[allow(dead_code)] // `connection` is never used on Windows.
113 pub(crate) connection: Option<SystemStream>,
114 jobs: CrossDomainJobs,
115 jobs_cvar: Condvar,
116 }
117
118 struct CrossDomainWorker {
119 wait_ctx: WaitContext,
120 state: Arc<CrossDomainState>,
121 pub(crate) item_state: CrossDomainItemState,
122 fence_handler: RutabagaFenceHandler,
123 }
124
125 pub(crate) struct CrossDomainContext {
126 #[allow(dead_code)] // `channels` is unused on Windows.
127 pub(crate) channels: Option<Vec<RutabagaChannel>>,
128 gralloc: Arc<Mutex<RutabagaGralloc>>,
129 pub(crate) state: Option<Arc<CrossDomainState>>,
130 pub(crate) context_resources: CrossDomainResources,
131 pub(crate) item_state: CrossDomainItemState,
132 fence_handler: RutabagaFenceHandler,
133 worker_thread: Option<thread::JoinHandle<RutabagaResult<()>>>,
134 pub(crate) resample_evt: Option<Sender>,
135 kill_evt: Option<Sender>,
136 }
137
138 /// The CrossDomain component contains a list of channels that the guest may connect to and the
139 /// ability to allocate memory.
140 pub struct CrossDomain {
141 channels: Option<Vec<RutabagaChannel>>,
142 gralloc: Arc<Mutex<RutabagaGralloc>>,
143 fence_handler: RutabagaFenceHandler,
144 }
145
146 // TODO(gurchetansingh): optimize the item tracker. Each requirements blob is long-lived and can
147 // be stored in a Slab or vector. Descriptors received from the Wayland socket *seem* to come one
148 // at a time, and can be stored as options. Need to confirm.
add_item(item_state: &CrossDomainItemState, item: CrossDomainItem) -> u32149 pub(crate) fn add_item(item_state: &CrossDomainItemState, item: CrossDomainItem) -> u32 {
150 let mut items = item_state.lock().unwrap();
151
152 let item_id = match item {
153 CrossDomainItem::ImageRequirements(_) => {
154 items.requirements_blob_id += 2;
155 items.requirements_blob_id
156 }
157 CrossDomainItem::WaylandReadPipe(_) => {
158 items.read_pipe_id += 1;
159 max(items.read_pipe_id, CROSS_DOMAIN_PIPE_READ_START)
160 }
161 _ => {
162 items.descriptor_id += 2;
163 items.descriptor_id
164 }
165 };
166
167 items.table.insert(item_id, item);
168
169 item_id
170 }
171
172 impl Default for CrossDomainItems {
default() -> Self173 fn default() -> Self {
174 // Odd for descriptors, and even for requirement blobs.
175 CrossDomainItems {
176 descriptor_id: 1,
177 requirements_blob_id: 2,
178 read_pipe_id: CROSS_DOMAIN_PIPE_READ_START,
179 table: Default::default(),
180 }
181 }
182 }
183
184 impl CrossDomainState {
new( query_ring_id: u32, channel_ring_id: u32, context_resources: CrossDomainResources, connection: Option<SystemStream>, ) -> CrossDomainState185 fn new(
186 query_ring_id: u32,
187 channel_ring_id: u32,
188 context_resources: CrossDomainResources,
189 connection: Option<SystemStream>,
190 ) -> CrossDomainState {
191 CrossDomainState {
192 query_ring_id,
193 channel_ring_id,
194 context_resources,
195 connection,
196 jobs: Mutex::new(Some(VecDeque::new())),
197 jobs_cvar: Condvar::new(),
198 }
199 }
200
add_job(&self, job: CrossDomainJob)201 pub(crate) fn add_job(&self, job: CrossDomainJob) {
202 let mut jobs = self.jobs.lock().unwrap();
203 if let Some(queue) = jobs.as_mut() {
204 queue.push_back(job);
205 self.jobs_cvar.notify_one();
206 }
207 }
208
wait_for_job(&self) -> Option<CrossDomainJob>209 fn wait_for_job(&self) -> Option<CrossDomainJob> {
210 let mut jobs = self.jobs.lock().unwrap();
211 loop {
212 match jobs.as_mut()?.pop_front() {
213 Some(job) => return Some(job),
214 None => jobs = self.jobs_cvar.wait(jobs).unwrap(),
215 }
216 }
217 }
218
write_to_ring<T>(&self, mut ring_write: RingWrite<T>, ring_id: u32) -> RutabagaResult<usize> where T: FromBytes + AsBytes,219 fn write_to_ring<T>(&self, mut ring_write: RingWrite<T>, ring_id: u32) -> RutabagaResult<usize>
220 where
221 T: FromBytes + AsBytes,
222 {
223 let mut context_resources = self.context_resources.lock().unwrap();
224 let mut bytes_read: usize = 0;
225
226 let resource = context_resources
227 .get_mut(&ring_id)
228 .ok_or(RutabagaError::InvalidResourceId)?;
229
230 let iovecs = resource
231 .backing_iovecs
232 .as_mut()
233 .ok_or(RutabagaError::InvalidIovec)?;
234 let slice =
235 // SAFETY:
236 // Safe because we've verified the iovecs are attached and owned only by this context.
237 unsafe { std::slice::from_raw_parts_mut(iovecs[0].base as *mut u8, iovecs[0].len) };
238
239 match ring_write {
240 RingWrite::Write(cmd, opaque_data_opt) => {
241 if slice.len() < size_of::<T>() {
242 return Err(RutabagaError::InvalidIovec);
243 }
244 let (cmd_slice, opaque_data_slice) = slice.split_at_mut(size_of::<T>());
245 cmd_slice.copy_from_slice(cmd.as_bytes());
246 if let Some(opaque_data) = opaque_data_opt {
247 if opaque_data_slice.len() < opaque_data.len() {
248 return Err(RutabagaError::InvalidIovec);
249 }
250 opaque_data_slice[..opaque_data.len()].copy_from_slice(opaque_data);
251 }
252 }
253 RingWrite::WriteFromFile(mut cmd_read, ref mut file, readable) => {
254 if slice.len() < size_of::<CrossDomainReadWrite>() {
255 return Err(RutabagaError::InvalidIovec);
256 }
257 let (cmd_slice, opaque_data_slice) =
258 slice.split_at_mut(size_of::<CrossDomainReadWrite>());
259
260 if readable {
261 bytes_read = read_volatile(file, opaque_data_slice)?;
262 }
263
264 if bytes_read == 0 {
265 cmd_read.hang_up = 1;
266 }
267
268 cmd_read.opaque_data_size = bytes_read.try_into()?;
269 cmd_slice.copy_from_slice(cmd_read.as_bytes());
270 }
271 }
272
273 Ok(bytes_read)
274 }
275 }
276
277 impl CrossDomainWorker {
new( wait_ctx: WaitContext, state: Arc<CrossDomainState>, item_state: CrossDomainItemState, fence_handler: RutabagaFenceHandler, ) -> CrossDomainWorker278 fn new(
279 wait_ctx: WaitContext,
280 state: Arc<CrossDomainState>,
281 item_state: CrossDomainItemState,
282 fence_handler: RutabagaFenceHandler,
283 ) -> CrossDomainWorker {
284 CrossDomainWorker {
285 wait_ctx,
286 state,
287 item_state,
288 fence_handler,
289 }
290 }
291
292 // Handles the fence according the the token according to the event token. On success, a
293 // boolean value indicating whether the worker thread should be stopped is returned.
handle_fence( &mut self, fence: RutabagaFence, thread_resample_evt: &Receiver, receive_buf: &mut [u8], ) -> RutabagaResult<()>294 fn handle_fence(
295 &mut self,
296 fence: RutabagaFence,
297 thread_resample_evt: &Receiver,
298 receive_buf: &mut [u8],
299 ) -> RutabagaResult<()> {
300 let events = self.wait_ctx.wait()?;
301
302 // The worker thread must:
303 //
304 // (1) Poll the ContextChannel (usually Wayland)
305 // (2) Poll a number of WaylandReadPipes
306 // (3) handle jobs from the virtio-gpu thread.
307 //
308 // We can only process one event at a time, because each `handle_fence` call is associated
309 // with a guest virtio-gpu fence. Signaling the fence means it's okay for the guest to
310 // access ring data. If two events are available at the same time (say a ContextChannel
311 // event and a WaylandReadPipe event), and we write responses for both using the same guest
312 // fence data, that will break the expected order of events. We need the guest to generate
313 // a new fence before we can resume polling.
314 //
315 // The CrossDomainJob queue gurantees a new fence has been generated before polling is
316 // resumed.
317 if let Some(event) = events.first() {
318 match event.token {
319 CrossDomainToken::ContextChannel => {
320 let (len, files) = self.state.receive_msg(receive_buf)?;
321 if len != 0 || !files.is_empty() {
322 let mut cmd_receive: CrossDomainSendReceive = Default::default();
323
324 let num_files = files.len();
325 cmd_receive.hdr.cmd = CROSS_DOMAIN_CMD_RECEIVE;
326 cmd_receive.num_identifiers = files.len().try_into()?;
327 cmd_receive.opaque_data_size = len.try_into()?;
328
329 let iter = cmd_receive
330 .identifiers
331 .iter_mut()
332 .zip(cmd_receive.identifier_types.iter_mut())
333 .zip(cmd_receive.identifier_sizes.iter_mut())
334 .zip(files)
335 .take(num_files);
336
337 for (((identifier, identifier_type), identifier_size), mut file) in iter {
338 // Safe since the descriptors from receive_msg(..) are owned by us and
339 // valid.
340 descriptor_analysis(&mut file, identifier_type, identifier_size)?;
341
342 *identifier = match *identifier_type {
343 CROSS_DOMAIN_ID_TYPE_VIRTGPU_BLOB => add_item(
344 &self.item_state,
345 CrossDomainItem::WaylandKeymap(file.into()),
346 ),
347 CROSS_DOMAIN_ID_TYPE_WRITE_PIPE => add_item(
348 &self.item_state,
349 CrossDomainItem::WaylandWritePipe(file),
350 ),
351 _ => return Err(RutabagaError::InvalidCrossDomainItemType),
352 };
353 }
354
355 self.state.write_to_ring(
356 RingWrite::Write(cmd_receive, Some(&receive_buf[0..len])),
357 self.state.channel_ring_id,
358 )?;
359 self.fence_handler.call(fence);
360 }
361 }
362 CrossDomainToken::Resample => {
363 // The resample event is triggered when the job queue is in the following state:
364 //
365 // [CrossDomain::AddReadPipe(..)] -> END
366 //
367 // After this event, the job queue will be the following state:
368 //
369 // [CrossDomain::AddReadPipe(..)] -> [CrossDomain::HandleFence(..)] -> END
370 //
371 // Fence handling is tied to some new data transfer across a pollable
372 // descriptor. When we're adding new descriptors, we stop polling.
373 channel_wait(thread_resample_evt)?;
374 self.state.add_job(CrossDomainJob::HandleFence(fence));
375 }
376 CrossDomainToken::WaylandReadPipe(pipe_id) => {
377 let mut items = self.item_state.lock().unwrap();
378 let mut cmd_read: CrossDomainReadWrite = Default::default();
379 let bytes_read;
380
381 cmd_read.hdr.cmd = CROSS_DOMAIN_CMD_READ;
382 cmd_read.identifier = pipe_id;
383
384 let item = items
385 .table
386 .get_mut(&pipe_id)
387 .ok_or(RutabagaError::InvalidCrossDomainItemId)?;
388
389 match item {
390 CrossDomainItem::WaylandReadPipe(ref mut file) => {
391 let ring_write =
392 RingWrite::WriteFromFile(cmd_read, file, event.readable);
393 bytes_read = self.state.write_to_ring::<CrossDomainReadWrite>(
394 ring_write,
395 self.state.channel_ring_id,
396 )?;
397
398 // Zero bytes read indicates end-of-file on POSIX.
399 if event.hung_up && bytes_read == 0 {
400 self.wait_ctx
401 .delete(CrossDomainToken::WaylandReadPipe(pipe_id), file)?;
402 }
403 }
404 _ => return Err(RutabagaError::InvalidCrossDomainItemType),
405 }
406
407 if event.hung_up && bytes_read == 0 {
408 items.table.remove(&pipe_id);
409 }
410
411 self.fence_handler.call(fence);
412 }
413 CrossDomainToken::Kill => {
414 self.fence_handler.call(fence);
415 }
416 }
417 }
418
419 Ok(())
420 }
421
run( &mut self, thread_kill_evt: Receiver, thread_resample_evt: Receiver, ) -> RutabagaResult<()>422 fn run(
423 &mut self,
424 thread_kill_evt: Receiver,
425 thread_resample_evt: Receiver,
426 ) -> RutabagaResult<()> {
427 self.wait_ctx
428 .add(CrossDomainToken::Resample, &thread_resample_evt)?;
429 self.wait_ctx
430 .add(CrossDomainToken::Kill, &thread_kill_evt)?;
431 let mut receive_buf: Vec<u8> = vec![0; CROSS_DOMAIN_MAX_SEND_RECV_SIZE];
432
433 while let Some(job) = self.state.wait_for_job() {
434 match job {
435 CrossDomainJob::HandleFence(fence) => {
436 match self.handle_fence(fence, &thread_resample_evt, &mut receive_buf) {
437 Ok(()) => (),
438 Err(e) => {
439 error!("Worker halting due to: {}", e);
440 return Err(e);
441 }
442 }
443 }
444 CrossDomainJob::AddReadPipe(read_pipe_id) => {
445 let items = self.item_state.lock().unwrap();
446 let item = items
447 .table
448 .get(&read_pipe_id)
449 .ok_or(RutabagaError::InvalidCrossDomainItemId)?;
450
451 match item {
452 CrossDomainItem::WaylandReadPipe(file) => self
453 .wait_ctx
454 .add(CrossDomainToken::WaylandReadPipe(read_pipe_id), file)?,
455 _ => return Err(RutabagaError::InvalidCrossDomainItemType),
456 }
457 }
458 CrossDomainJob::Finish => return Ok(()),
459 }
460 }
461
462 Ok(())
463 }
464 }
465
466 impl CrossDomain {
467 /// Initializes the cross-domain component by taking the the rutabaga channels (if any) and
468 /// initializing rutabaga gralloc.
init( channels: Option<Vec<RutabagaChannel>>, fence_handler: RutabagaFenceHandler, ) -> RutabagaResult<Box<dyn RutabagaComponent>>469 pub fn init(
470 channels: Option<Vec<RutabagaChannel>>,
471 fence_handler: RutabagaFenceHandler,
472 ) -> RutabagaResult<Box<dyn RutabagaComponent>> {
473 let gralloc = RutabagaGralloc::new(RutabagaGrallocBackendFlags::new())?;
474 Ok(Box::new(CrossDomain {
475 channels,
476 gralloc: Arc::new(Mutex::new(gralloc)),
477 fence_handler,
478 }))
479 }
480 }
481
482 impl CrossDomainContext {
initialize(&mut self, cmd_init: &CrossDomainInit) -> RutabagaResult<()>483 fn initialize(&mut self, cmd_init: &CrossDomainInit) -> RutabagaResult<()> {
484 if !self
485 .context_resources
486 .lock()
487 .unwrap()
488 .contains_key(&cmd_init.query_ring_id)
489 {
490 return Err(RutabagaError::InvalidResourceId);
491 }
492
493 let query_ring_id = cmd_init.query_ring_id;
494 let channel_ring_id = cmd_init.channel_ring_id;
495 let context_resources = self.context_resources.clone();
496
497 // Zero means no requested channel.
498 if cmd_init.channel_type != 0 {
499 if !self
500 .context_resources
501 .lock()
502 .unwrap()
503 .contains_key(&cmd_init.channel_ring_id)
504 {
505 return Err(RutabagaError::InvalidResourceId);
506 }
507
508 let connection = self.get_connection(cmd_init)?;
509
510 let (kill_evt, thread_kill_evt) = channel()?;
511 let (resample_evt, thread_resample_evt) = channel()?;
512
513 let mut wait_ctx = WaitContext::new()?;
514 match &connection {
515 Some(connection) => {
516 wait_ctx.add(CrossDomainToken::ContextChannel, connection)?;
517 }
518 None => return Err(RutabagaError::Unsupported),
519 };
520
521 let state = Arc::new(CrossDomainState::new(
522 query_ring_id,
523 channel_ring_id,
524 context_resources,
525 connection,
526 ));
527
528 let thread_state = state.clone();
529 let thread_items = self.item_state.clone();
530 let thread_fence_handler = self.fence_handler.clone();
531
532 let worker_result = thread::Builder::new()
533 .name("cross domain".to_string())
534 .spawn(move || -> RutabagaResult<()> {
535 CrossDomainWorker::new(
536 wait_ctx,
537 thread_state,
538 thread_items,
539 thread_fence_handler,
540 )
541 .run(thread_kill_evt, thread_resample_evt)
542 });
543
544 self.worker_thread = Some(worker_result.unwrap());
545 self.state = Some(state);
546 self.resample_evt = Some(resample_evt);
547 self.kill_evt = Some(kill_evt);
548 } else {
549 self.state = Some(Arc::new(CrossDomainState::new(
550 query_ring_id,
551 channel_ring_id,
552 context_resources,
553 None,
554 )));
555 }
556
557 Ok(())
558 }
559
get_image_requirements( &mut self, cmd_get_reqs: &CrossDomainGetImageRequirements, ) -> RutabagaResult<()>560 fn get_image_requirements(
561 &mut self,
562 cmd_get_reqs: &CrossDomainGetImageRequirements,
563 ) -> RutabagaResult<()> {
564 let info = ImageAllocationInfo {
565 width: cmd_get_reqs.width,
566 height: cmd_get_reqs.height,
567 drm_format: DrmFormat::from(cmd_get_reqs.drm_format),
568 flags: RutabagaGrallocFlags::new(cmd_get_reqs.flags),
569 };
570
571 let reqs = self
572 .gralloc
573 .lock()
574 .unwrap()
575 .get_image_memory_requirements(info)?;
576
577 let mut response = CrossDomainImageRequirements {
578 strides: reqs.strides,
579 offsets: reqs.offsets,
580 modifier: reqs.modifier,
581 size: reqs.size,
582 blob_id: 0,
583 map_info: reqs.map_info,
584 memory_idx: -1,
585 physical_device_idx: -1,
586 };
587
588 if let Some(ref vk_info) = reqs.vulkan_info {
589 response.memory_idx = vk_info.memory_idx as i32;
590 // We return -1 for now since physical_device_idx is deprecated. If this backend is
591 // put back into action, it should be using device_id from the request instead.
592 response.physical_device_idx = -1;
593 }
594
595 if let Some(state) = &self.state {
596 response.blob_id = add_item(&self.item_state, CrossDomainItem::ImageRequirements(reqs));
597 state.write_to_ring(RingWrite::Write(response, None), state.query_ring_id)?;
598 Ok(())
599 } else {
600 Err(RutabagaError::InvalidCrossDomainState)
601 }
602 }
603
write(&self, cmd_write: &CrossDomainReadWrite, opaque_data: &[u8]) -> RutabagaResult<()>604 fn write(&self, cmd_write: &CrossDomainReadWrite, opaque_data: &[u8]) -> RutabagaResult<()> {
605 let mut items = self.item_state.lock().unwrap();
606
607 // Most of the time, hang-up and writing will be paired. In lieu of this, remove the
608 // item rather than getting a reference. In case of an error, there's not much to do
609 // besides reporting it.
610 let item = items
611 .table
612 .remove(&cmd_write.identifier)
613 .ok_or(RutabagaError::InvalidCrossDomainItemId)?;
614
615 let len: usize = cmd_write.opaque_data_size.try_into()?;
616 match item {
617 CrossDomainItem::WaylandWritePipe(file) => {
618 if len != 0 {
619 write_volatile(&file, opaque_data)?;
620 }
621
622 if cmd_write.hang_up == 0 {
623 items.table.insert(
624 cmd_write.identifier,
625 CrossDomainItem::WaylandWritePipe(file),
626 );
627 }
628
629 Ok(())
630 }
631 _ => Err(RutabagaError::InvalidCrossDomainItemType),
632 }
633 }
634 }
635
636 impl Drop for CrossDomainContext {
drop(&mut self)637 fn drop(&mut self) {
638 if let Some(state) = &self.state {
639 state.add_job(CrossDomainJob::Finish);
640 }
641
642 if let Some(kill_evt) = self.kill_evt.take() {
643 // Log the error, but still try to join the worker thread
644 match channel_signal(&kill_evt) {
645 Ok(_) => (),
646 Err(e) => {
647 error!("failed to write cross domain kill event: {}", e);
648 }
649 }
650
651 if let Some(worker_thread) = self.worker_thread.take() {
652 let _ = worker_thread.join();
653 }
654 }
655 }
656 }
657
658 #[repr(C)]
659 #[derive(Copy, Clone, Default, AsBytes, FromZeroes, FromBytes)]
660 struct CrossDomainInitLegacy {
661 hdr: CrossDomainHeader,
662 query_ring_id: u32,
663 channel_type: u32,
664 }
665
666 impl RutabagaContext for CrossDomainContext {
context_create_blob( &mut self, resource_id: u32, resource_create_blob: ResourceCreateBlob, handle_opt: Option<RutabagaHandle>, ) -> RutabagaResult<RutabagaResource>667 fn context_create_blob(
668 &mut self,
669 resource_id: u32,
670 resource_create_blob: ResourceCreateBlob,
671 handle_opt: Option<RutabagaHandle>,
672 ) -> RutabagaResult<RutabagaResource> {
673 let item_id = resource_create_blob.blob_id as u32;
674
675 // We don't want to remove requirements blobs, since they can be used for subsequent
676 // allocations. We do want to remove Wayland keymaps, since they are mapped the guest
677 // and then never used again. The current protocol encodes this as divisiblity by 2.
678 if item_id % 2 == 0 {
679 let items = self.item_state.lock().unwrap();
680 let item = items
681 .table
682 .get(&item_id)
683 .ok_or(RutabagaError::InvalidCrossDomainItemId)?;
684
685 match item {
686 CrossDomainItem::ImageRequirements(reqs) => {
687 if reqs.size != resource_create_blob.size {
688 return Err(RutabagaError::SpecViolation("blob size mismatch"));
689 }
690
691 // Strictly speaking, it's against the virtio-gpu spec to allocate memory in the
692 // context create blob function, which says "the actual
693 // allocation is done via VIRTIO_GPU_CMD_SUBMIT_3D."
694 // However, atomic resource creation is easiest for the
695 // cross-domain use case, so whatever.
696 let hnd = match handle_opt {
697 Some(handle) => handle,
698 None => self.gralloc.lock().unwrap().allocate_memory(*reqs)?,
699 };
700
701 let info_3d = Resource3DInfo {
702 width: reqs.info.width,
703 height: reqs.info.height,
704 drm_fourcc: reqs.info.drm_format.into(),
705 strides: reqs.strides,
706 offsets: reqs.offsets,
707 modifier: reqs.modifier,
708 guest_cpu_mappable: (resource_create_blob.blob_flags
709 & RUTABAGA_BLOB_FLAG_USE_MAPPABLE)
710 != 0,
711 };
712
713 Ok(RutabagaResource {
714 resource_id,
715 handle: Some(Arc::new(hnd)),
716 blob: true,
717 blob_mem: resource_create_blob.blob_mem,
718 blob_flags: resource_create_blob.blob_flags,
719 map_info: Some(reqs.map_info | RUTABAGA_MAP_ACCESS_RW),
720 info_2d: None,
721 info_3d: Some(info_3d),
722 vulkan_info: reqs.vulkan_info,
723 backing_iovecs: None,
724 component_mask: 1 << (RutabagaComponentType::CrossDomain as u8),
725 size: resource_create_blob.size,
726 mapping: None,
727 })
728 }
729 _ => Err(RutabagaError::InvalidCrossDomainItemType),
730 }
731 } else {
732 let item = self
733 .item_state
734 .lock()
735 .unwrap()
736 .table
737 .remove(&item_id)
738 .ok_or(RutabagaError::InvalidCrossDomainItemId)?;
739
740 match item {
741 CrossDomainItem::WaylandKeymap(descriptor) => {
742 let hnd = RutabagaHandle {
743 os_handle: descriptor,
744 handle_type: RUTABAGA_MEM_HANDLE_TYPE_SHM,
745 };
746
747 Ok(RutabagaResource {
748 resource_id,
749 handle: Some(Arc::new(hnd)),
750 blob: true,
751 blob_mem: resource_create_blob.blob_mem,
752 blob_flags: resource_create_blob.blob_flags,
753 map_info: Some(RUTABAGA_MAP_CACHE_CACHED | RUTABAGA_MAP_ACCESS_READ),
754 info_2d: None,
755 info_3d: None,
756 vulkan_info: None,
757 backing_iovecs: None,
758 component_mask: 1 << (RutabagaComponentType::CrossDomain as u8),
759 size: resource_create_blob.size,
760 mapping: None,
761 })
762 }
763 _ => Err(RutabagaError::InvalidCrossDomainItemType),
764 }
765 }
766 }
767
submit_cmd(&mut self, mut commands: &mut [u8], fence_ids: &[u64]) -> RutabagaResult<()>768 fn submit_cmd(&mut self, mut commands: &mut [u8], fence_ids: &[u64]) -> RutabagaResult<()> {
769 if !fence_ids.is_empty() {
770 return Err(RutabagaError::Unsupported);
771 }
772
773 while !commands.is_empty() {
774 let hdr = CrossDomainHeader::read_from_prefix(commands.as_bytes())
775 .ok_or(RutabagaError::InvalidCommandBuffer)?;
776
777 match hdr.cmd {
778 CROSS_DOMAIN_CMD_INIT => {
779 let cmd_init = match CrossDomainInit::read_from_prefix(commands.as_bytes()) {
780 Some(cmd_init) => cmd_init,
781 None => {
782 if let Some(cmd_init) =
783 CrossDomainInitLegacy::read_from_prefix(commands.as_bytes())
784 {
785 CrossDomainInit {
786 hdr: cmd_init.hdr,
787 query_ring_id: cmd_init.query_ring_id,
788 channel_ring_id: cmd_init.query_ring_id,
789 channel_type: cmd_init.channel_type,
790 }
791 } else {
792 return Err(RutabagaError::InvalidCommandBuffer);
793 }
794 }
795 };
796
797 self.initialize(&cmd_init)?;
798 }
799 CROSS_DOMAIN_CMD_GET_IMAGE_REQUIREMENTS => {
800 let cmd_get_reqs =
801 CrossDomainGetImageRequirements::read_from_prefix(commands.as_bytes())
802 .ok_or(RutabagaError::InvalidCommandBuffer)?;
803
804 self.get_image_requirements(&cmd_get_reqs)?;
805 }
806 CROSS_DOMAIN_CMD_SEND => {
807 let opaque_data_offset = size_of::<CrossDomainSendReceive>();
808 let cmd_send = CrossDomainSendReceive::read_from_prefix(commands.as_bytes())
809 .ok_or(RutabagaError::InvalidCommandBuffer)?;
810
811 let opaque_data = commands
812 .get_mut(
813 opaque_data_offset
814 ..opaque_data_offset + cmd_send.opaque_data_size as usize,
815 )
816 .ok_or(RutabagaError::InvalidCommandSize(
817 cmd_send.opaque_data_size as usize,
818 ))?;
819
820 self.send(&cmd_send, opaque_data)?;
821 }
822 CROSS_DOMAIN_CMD_POLL => {
823 // Actual polling is done in the subsequent when creating a fence.
824 }
825 CROSS_DOMAIN_CMD_WRITE => {
826 let opaque_data_offset = size_of::<CrossDomainReadWrite>();
827 let cmd_write = CrossDomainReadWrite::read_from_prefix(commands.as_bytes())
828 .ok_or(RutabagaError::InvalidCommandBuffer)?;
829
830 let opaque_data = commands
831 .get_mut(
832 opaque_data_offset
833 ..opaque_data_offset + cmd_write.opaque_data_size as usize,
834 )
835 .ok_or(RutabagaError::InvalidCommandSize(
836 cmd_write.opaque_data_size as usize,
837 ))?;
838
839 self.write(&cmd_write, opaque_data)?;
840 }
841 _ => return Err(RutabagaError::SpecViolation("invalid cross domain command")),
842 }
843
844 commands = commands
845 .get_mut(hdr.cmd_size as usize..)
846 .ok_or(RutabagaError::InvalidCommandSize(hdr.cmd_size as usize))?;
847 }
848
849 Ok(())
850 }
851
attach(&mut self, resource: &mut RutabagaResource)852 fn attach(&mut self, resource: &mut RutabagaResource) {
853 if resource.blob_mem == RUTABAGA_BLOB_MEM_GUEST {
854 self.context_resources.lock().unwrap().insert(
855 resource.resource_id,
856 CrossDomainResource {
857 handle: None,
858 backing_iovecs: resource.backing_iovecs.take(),
859 },
860 );
861 } else if let Some(ref handle) = resource.handle {
862 self.context_resources.lock().unwrap().insert(
863 resource.resource_id,
864 CrossDomainResource {
865 handle: Some(handle.clone()),
866 backing_iovecs: None,
867 },
868 );
869 }
870 }
871
detach(&mut self, resource: &RutabagaResource)872 fn detach(&mut self, resource: &RutabagaResource) {
873 self.context_resources
874 .lock()
875 .unwrap()
876 .remove(&resource.resource_id);
877 }
878
context_create_fence(&mut self, fence: RutabagaFence) -> RutabagaResult<()>879 fn context_create_fence(&mut self, fence: RutabagaFence) -> RutabagaResult<()> {
880 match fence.ring_idx as u32 {
881 CROSS_DOMAIN_QUERY_RING => self.fence_handler.call(fence),
882 CROSS_DOMAIN_CHANNEL_RING => {
883 if let Some(state) = &self.state {
884 state.add_job(CrossDomainJob::HandleFence(fence));
885 }
886 }
887 _ => return Err(RutabagaError::SpecViolation("unexpected ring type")),
888 }
889
890 Ok(())
891 }
892
component_type(&self) -> RutabagaComponentType893 fn component_type(&self) -> RutabagaComponentType {
894 RutabagaComponentType::CrossDomain
895 }
896 }
897
898 impl RutabagaComponent for CrossDomain {
get_capset_info(&self, _capset_id: u32) -> (u32, u32)899 fn get_capset_info(&self, _capset_id: u32) -> (u32, u32) {
900 (0u32, size_of::<CrossDomainCapabilities>() as u32)
901 }
902
get_capset(&self, _capset_id: u32, _version: u32) -> Vec<u8>903 fn get_capset(&self, _capset_id: u32, _version: u32) -> Vec<u8> {
904 let mut caps: CrossDomainCapabilities = Default::default();
905 if let Some(ref channels) = self.channels {
906 for channel in channels {
907 caps.supported_channels = 1 << channel.channel_type;
908 }
909 }
910
911 if self.gralloc.lock().unwrap().supports_dmabuf() {
912 caps.supports_dmabuf = 1;
913 }
914
915 if self.gralloc.lock().unwrap().supports_external_gpu_memory() {
916 caps.supports_external_gpu_memory = 1;
917 }
918
919 // Version 1 supports all commands up to and including CROSS_DOMAIN_CMD_WRITE.
920 caps.version = 1;
921 caps.as_bytes().to_vec()
922 }
923
create_blob( &mut self, _ctx_id: u32, resource_id: u32, resource_create_blob: ResourceCreateBlob, iovec_opt: Option<Vec<RutabagaIovec>>, _handle_opt: Option<RutabagaHandle>, ) -> RutabagaResult<RutabagaResource>924 fn create_blob(
925 &mut self,
926 _ctx_id: u32,
927 resource_id: u32,
928 resource_create_blob: ResourceCreateBlob,
929 iovec_opt: Option<Vec<RutabagaIovec>>,
930 _handle_opt: Option<RutabagaHandle>,
931 ) -> RutabagaResult<RutabagaResource> {
932 if resource_create_blob.blob_mem != RUTABAGA_BLOB_MEM_GUEST
933 && resource_create_blob.blob_flags != RUTABAGA_BLOB_FLAG_USE_MAPPABLE
934 {
935 return Err(RutabagaError::SpecViolation(
936 "expected only guest memory blobs",
937 ));
938 }
939
940 Ok(RutabagaResource {
941 resource_id,
942 handle: None,
943 blob: true,
944 blob_mem: resource_create_blob.blob_mem,
945 blob_flags: resource_create_blob.blob_flags,
946 map_info: None,
947 info_2d: None,
948 info_3d: None,
949 vulkan_info: None,
950 backing_iovecs: iovec_opt,
951 component_mask: 1 << (RutabagaComponentType::CrossDomain as u8),
952 size: resource_create_blob.size,
953 mapping: None,
954 })
955 }
956
create_context( &self, _ctx_id: u32, _context_init: u32, _context_name: Option<&str>, fence_handler: RutabagaFenceHandler, ) -> RutabagaResult<Box<dyn RutabagaContext>>957 fn create_context(
958 &self,
959 _ctx_id: u32,
960 _context_init: u32,
961 _context_name: Option<&str>,
962 fence_handler: RutabagaFenceHandler,
963 ) -> RutabagaResult<Box<dyn RutabagaContext>> {
964 Ok(Box::new(CrossDomainContext {
965 channels: self.channels.clone(),
966 gralloc: self.gralloc.clone(),
967 state: None,
968 context_resources: Arc::new(Mutex::new(Default::default())),
969 item_state: Arc::new(Mutex::new(Default::default())),
970 fence_handler,
971 worker_thread: None,
972 resample_evt: None,
973 kill_evt: None,
974 }))
975 }
976
977 // With "drm/virtio: Conditionally allocate virtio_gpu_fence" in the kernel, global fences for
978 // cross-domain aren't created. However, that change is projected to land in the v6.6 kernel.
979 // For older kernels, signal the fence immediately on creation.
create_fence(&mut self, fence: RutabagaFence) -> RutabagaResult<()>980 fn create_fence(&mut self, fence: RutabagaFence) -> RutabagaResult<()> {
981 self.fence_handler.call(fence);
982 Ok(())
983 }
984 }
985