• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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