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