rutabaga_gfx/cross_domain/
mod.rs

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