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