devices: gpu: support creating udmabufs

This patch adds support for creating udmabufs via a guest provided
sg-list.  Ideally, we'd create the udmabuf from a virtio-gpu guest
dedicated heap, but that needs further investigation.

In terms of the protocol, these following prototype items are added:

BLOB_CREATE_GUEST_HANDLE: "create an udmabuf" or an OS-specific
equivalent. This can be used with the guest dedicated heap or system
memory.  Right now, only system memory is used.

We also want to associate the udmabuf with any host side metadata.  For
example, SET_SCANOUT_BLOB doesn't passthrough the modifiers since
virtio-gpu KMS + modifiers is annoying.  Simple solution: just ask the
host for the modifier.  This could also enable different caching types
if the guest blob is mappable (for example, the MSM GPU driver currently
only supports WC mappings.  We might also want cached mappings for
camera).

Incidentals:
  * Add a placeholder for RESOURCE_SYNC

BUG=chromium:892806, b:173630595
TEST=create a bunch of udmabufs from the guest

Change-Id: I4686d9f48938f10e7c265750a931d7d2d2d9611b
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2786291
Tested-by: Gurchetan Singh <gurchetansingh@chromium.org>
Reviewed-by: Zach Reizner <zachr@chromium.org>
Commit-Queue: Gurchetan Singh <gurchetansingh@chromium.org>
diff --git a/Cargo.toml b/Cargo.toml
index 3b2f0cf..8303109 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -60,7 +60,6 @@
 virgl_renderer = ["devices/virgl_renderer"]
 gfxstream = ["devices/gfxstream"]
 gdb = ["gdbstub", "thiserror",  "arch/gdb", "vm_control/gdb", "x86_64/gdb"]
-udmabuf = ["devices/udmabuf"]
 
 [dependencies]
 arch = { path = "arch" }
diff --git a/devices/Cargo.toml b/devices/Cargo.toml
index 3feab6a..1284401 100644
--- a/devices/Cargo.toml
+++ b/devices/Cargo.toml
@@ -15,7 +15,6 @@
 x = ["gpu_display/x"]
 virgl_renderer = ["gpu", "rutabaga_gfx/virgl_renderer"]
 gfxstream = ["gpu", "rutabaga_gfx/gfxstream"]
-udmabuf = []
 
 [dependencies]
 acpi_tables = {path = "../acpi_tables" }
diff --git a/devices/src/virtio/gpu/mod.rs b/devices/src/virtio/gpu/mod.rs
index 13d24f3..99c0fa3 100644
--- a/devices/src/virtio/gpu/mod.rs
+++ b/devices/src/virtio/gpu/mod.rs
@@ -20,11 +20,9 @@
 use std::thread;
 use std::time::Duration;
 
-#[cfg(feature = "udmabuf")]
-use base::AsRawDescriptors;
 use base::{
-    debug, error, warn, AsRawDescriptor, Event, ExternalMapping, PollToken, RawDescriptor, Tube,
-    WaitContext,
+    debug, error, warn, AsRawDescriptor, AsRawDescriptors, Event, ExternalMapping, PollToken,
+    RawDescriptor, Tube, WaitContext,
 };
 
 use data_model::*;
@@ -73,6 +71,7 @@
     pub gfxstream_use_guest_angle: bool,
     pub gfxstream_use_syncfd: bool,
     pub gfxstream_support_vulkan: bool,
+    pub udmabuf: bool,
     pub mode: GpuMode,
     pub cache_path: Option<String>,
     pub cache_size: Option<String>,
@@ -102,6 +101,7 @@
             mode: GpuMode::Mode3D,
             cache_path: None,
             cache_size: None,
+            udmabuf: false,
         }
     }
 }
@@ -126,6 +126,7 @@
     pci_bar: Alloc,
     map_request: Arc<Mutex<Option<ExternalMapping>>>,
     external_blob: bool,
+    udmabuf: bool,
 ) -> Option<VirtioGpu> {
     let mut display_opt = None;
     for display in possible_displays {
@@ -156,6 +157,7 @@
         pci_bar,
         map_request,
         external_blob,
+        udmabuf,
     )
 }
 
@@ -875,8 +877,8 @@
     external_blob: bool,
     rutabaga_component: RutabagaComponentType,
     base_features: u64,
-    #[allow(dead_code)]
     mem: GuestMemory,
+    udmabuf: bool,
 }
 
 impl Gpu {
@@ -957,6 +959,7 @@
             rutabaga_component: component,
             base_features,
             mem,
+            udmabuf: gpu_parameters.udmabuf,
         }
     }
 
@@ -1022,8 +1025,9 @@
             keep_rds.push(libc::STDERR_FILENO);
         }
 
-        #[cfg(feature = "udmabuf")]
-        keep_rds.append(&mut self.mem.as_raw_descriptors());
+        if self.udmabuf {
+            keep_rds.append(&mut self.mem.as_raw_descriptors());
+        }
 
         if let Some(ref gpu_device_tube) = self.gpu_device_tube {
             keep_rds.push(gpu_device_tube.as_raw_descriptor());
@@ -1049,10 +1053,19 @@
         let rutabaga_features = match self.rutabaga_component {
             RutabagaComponentType::Rutabaga2D => 0,
             _ => {
-                1 << VIRTIO_GPU_F_VIRGL
+                let mut features_3d = 0;
+
+                features_3d |= 1 << VIRTIO_GPU_F_VIRGL
                     | 1 << VIRTIO_GPU_F_RESOURCE_UUID
                     | 1 << VIRTIO_GPU_F_RESOURCE_BLOB
                     | 1 << VIRTIO_GPU_F_CONTEXT_INIT
+                    | 1 << VIRTIO_GPU_F_RESOURCE_SYNC;
+
+                if self.udmabuf {
+                    features_3d |= 1 << VIRTIO_GPU_F_CREATE_GUEST_HANDLE;
+                }
+
+                features_3d
             }
         };
 
@@ -1115,6 +1128,7 @@
         let event_devices = self.event_devices.split_off(0);
         let map_request = Arc::clone(&self.map_request);
         let external_blob = self.external_blob;
+        let udmabuf = self.udmabuf;
         if let (Some(gpu_device_tube), Some(pci_bar), Some(rutabaga_builder)) = (
             self.gpu_device_tube.take(),
             self.pci_bar.take(),
@@ -1134,6 +1148,7 @@
                             pci_bar,
                             map_request,
                             external_blob,
+                            udmabuf,
                         ) {
                             Some(backend) => backend,
                             None => return,
diff --git a/devices/src/virtio/gpu/protocol.rs b/devices/src/virtio/gpu/protocol.rs
index 32f309d..c2f9b3e 100644
--- a/devices/src/virtio/gpu/protocol.rs
+++ b/devices/src/virtio/gpu/protocol.rs
@@ -21,12 +21,16 @@
 use gpu_display::GpuDisplayError;
 use rutabaga_gfx::RutabagaError;
 
+use crate::virtio::gpu::udmabuf::UdmabufError;
+
 pub const VIRTIO_GPU_F_VIRGL: u32 = 0;
 pub const VIRTIO_GPU_F_EDID: u32 = 1;
 pub const VIRTIO_GPU_F_RESOURCE_UUID: u32 = 2;
 pub const VIRTIO_GPU_F_RESOURCE_BLOB: u32 = 3;
 /* The following capabilities are not upstreamed. */
 pub const VIRTIO_GPU_F_CONTEXT_INIT: u32 = 4;
+pub const VIRTIO_GPU_F_CREATE_GUEST_HANDLE: u32 = 5;
+pub const VIRTIO_GPU_F_RESOURCE_SYNC: u32 = 6;
 
 pub const VIRTIO_GPU_UNDEFINED: u32 = 0x0;
 
@@ -87,6 +91,8 @@
 pub const VIRTIO_GPU_BLOB_FLAG_USE_MAPPABLE: u32 = 0x0001;
 pub const VIRTIO_GPU_BLOB_FLAG_USE_SHAREABLE: u32 = 0x0002;
 pub const VIRTIO_GPU_BLOB_FLAG_USE_CROSS_DEVICE: u32 = 0x0004;
+/* Create a OS-specific handle from guest memory (not upstreamed). */
+pub const VIRTIO_GPU_BLOB_FLAG_CREATE_GUEST_HANDLE: u32 = 0x0008;
 
 pub const VIRTIO_GPU_SHM_ID_NONE: u8 = 0x0000;
 pub const VIRTIO_GPU_SHM_ID_HOST_VISIBLE: u8 = 0x0001;
@@ -812,6 +818,7 @@
     ErrInvalidResourceId,
     ErrInvalidContextId,
     ErrInvalidParameter,
+    ErrUdmabuf(UdmabufError),
 }
 
 impl From<TubeError> for GpuResponse {
@@ -838,6 +845,12 @@
     }
 }
 
+impl From<UdmabufError> for GpuResponse {
+    fn from(e: UdmabufError) -> GpuResponse {
+        GpuResponse::ErrUdmabuf(e)
+    }
+}
+
 impl Display for GpuResponse {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         use self::GpuResponse::*;
@@ -847,6 +860,7 @@
             ErrRutabaga(e) => write!(f, "renderer error: {}", e),
             ErrDisplay(e) => write!(f, "display error: {}", e),
             ErrScanout { num_scanouts } => write!(f, "non-zero scanout: {}", num_scanouts),
+            ErrUdmabuf(e) => write!(f, "udmabuf error: {}", e),
             _ => Ok(()),
         }
     }
@@ -1023,6 +1037,7 @@
             GpuResponse::ErrRutabaga(_) => VIRTIO_GPU_RESP_ERR_UNSPEC,
             GpuResponse::ErrDisplay(_) => VIRTIO_GPU_RESP_ERR_UNSPEC,
             GpuResponse::ErrMapping(_) => VIRTIO_GPU_RESP_ERR_UNSPEC,
+            GpuResponse::ErrUdmabuf(_) => VIRTIO_GPU_RESP_ERR_UNSPEC,
             GpuResponse::ErrScanout { num_scanouts: _ } => VIRTIO_GPU_RESP_ERR_UNSPEC,
             GpuResponse::ErrOutOfMemory => VIRTIO_GPU_RESP_ERR_OUT_OF_MEMORY,
             GpuResponse::ErrInvalidScanoutId => VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID,
diff --git a/devices/src/virtio/gpu/udmabuf.rs b/devices/src/virtio/gpu/udmabuf.rs
index 5f7ce3e..812080b 100644
--- a/devices/src/virtio/gpu/udmabuf.rs
+++ b/devices/src/virtio/gpu/udmabuf.rs
@@ -2,7 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#![cfg(feature = "udmabuf")]
 #![allow(dead_code)]
 
 use std::fs::{File, OpenOptions};
@@ -16,12 +15,12 @@
     SafeDescriptor,
 };
 
-use crate::virtio::gpu::udmabuf_bindings::*;
-
 use data_model::{FlexibleArray, FlexibleArrayWrapper};
 
 use rutabaga_gfx::{RutabagaHandle, RUTABAGA_MEM_HANDLE_TYPE_DMABUF};
 
+use super::udmabuf_bindings::*;
+
 use vm_memory::{GuestAddress, GuestMemory, GuestMemoryError};
 
 const UDMABUF_IOCTL_BASE: c_uint = 0x75;
diff --git a/devices/src/virtio/gpu/udmabuf_bindings.rs b/devices/src/virtio/gpu/udmabuf_bindings.rs
index ddea70e..dcd89a6 100644
--- a/devices/src/virtio/gpu/udmabuf_bindings.rs
+++ b/devices/src/virtio/gpu/udmabuf_bindings.rs
@@ -6,7 +6,6 @@
  * lost to history.  Should be easy to duplicate, if needed.
  */
 
-#![cfg(feature = "udmabuf")]
 #![allow(dead_code)]
 #![allow(non_camel_case_types)]
 
diff --git a/devices/src/virtio/gpu/virtio_gpu.rs b/devices/src/virtio/gpu/virtio_gpu.rs
index 4d9b9f2..d2ba1b6 100644
--- a/devices/src/virtio/gpu/virtio_gpu.rs
+++ b/devices/src/virtio/gpu/virtio_gpu.rs
@@ -26,8 +26,10 @@
 
 use super::protocol::{
     GpuResponse::{self, *},
-    GpuResponsePlaneInfo, VirtioGpuResult,
+    GpuResponsePlaneInfo, VirtioGpuResult, VIRTIO_GPU_BLOB_FLAG_CREATE_GUEST_HANDLE,
+    VIRTIO_GPU_BLOB_MEM_HOST3D,
 };
+use super::udmabuf::UdmabufDriver;
 use super::VirtioScanoutBlobData;
 use sync::Mutex;
 
@@ -83,6 +85,7 @@
     rutabaga: Rutabaga,
     resources: Map<u32, VirtioGpuResource>,
     external_blob: bool,
+    udmabuf_driver: Option<UdmabufDriver>,
 }
 
 fn sglist_to_rutabaga_iovecs(
@@ -119,11 +122,22 @@
         pci_bar: Alloc,
         map_request: Arc<Mutex<Option<ExternalMapping>>>,
         external_blob: bool,
+        udmabuf: bool,
     ) -> Option<VirtioGpu> {
         let rutabaga = rutabaga_builder
             .build()
             .map_err(|e| error!("failed to build rutabaga {}", e))
             .ok()?;
+
+        let mut udmabuf_driver = None;
+        if udmabuf {
+            udmabuf_driver = Some(
+                UdmabufDriver::new()
+                    .map_err(|e| error!("failed to initialize udmabuf: {}", e))
+                    .ok()?,
+            );
+        }
+
         let mut virtio_gpu = VirtioGpu {
             display: Rc::new(RefCell::new(display)),
             display_width,
@@ -139,6 +153,7 @@
             rutabaga,
             resources: Default::default(),
             external_blob,
+            udmabuf_driver,
         };
 
         for event_device in event_devices {
@@ -587,12 +602,25 @@
         vecs: Vec<(GuestAddress, usize)>,
         mem: &GuestMemory,
     ) -> VirtioGpuResult {
-        let rutabaga_iovecs = sglist_to_rutabaga_iovecs(&vecs[..], mem).map_err(|_| ErrUnspec)?;
+        let mut rutabaga_handle = None;
+        let mut rutabaga_iovecs = None;
+
+        if resource_create_blob.blob_flags & VIRTIO_GPU_BLOB_FLAG_CREATE_GUEST_HANDLE != 0 {
+            rutabaga_handle = match self.udmabuf_driver {
+                Some(ref driver) => Some(driver.create_udmabuf(mem, &vecs[..])?),
+                None => return Err(ErrUnspec),
+            }
+        } else if resource_create_blob.blob_mem != VIRTIO_GPU_BLOB_MEM_HOST3D {
+            rutabaga_iovecs =
+                Some(sglist_to_rutabaga_iovecs(&vecs[..], mem).map_err(|_| ErrUnspec)?);
+        }
+
         self.rutabaga.resource_create_blob(
             ctx_id,
             resource_id,
             resource_create_blob,
             rutabaga_iovecs,
+            rutabaga_handle,
         )?;
 
         let resource = VirtioGpuResource::new(resource_id, 0, 0, resource_create_blob.size);
diff --git a/rutabaga_gfx/src/cross_domain/cross_domain.rs b/rutabaga_gfx/src/cross_domain/cross_domain.rs
index 52f8e24..a7b3ac9 100644
--- a/rutabaga_gfx/src/cross_domain/cross_domain.rs
+++ b/rutabaga_gfx/src/cross_domain/cross_domain.rs
@@ -138,6 +138,7 @@
         &mut self,
         resource_id: u32,
         resource_create_blob: ResourceCreateBlob,
+        handle: Option<RutabagaHandle>,
     ) -> RutabagaResult<RutabagaResource> {
         let reqs = self
             .requirements_blobs
@@ -152,7 +153,11 @@
         // create blob function, which says "the actual allocation is done via
         // VIRTIO_GPU_CMD_SUBMIT_3D."  However, atomic resource creation is easiest for the
         // cross-domain use case, so whatever.
-        let handle = self.gralloc.lock().allocate_memory(*reqs)?;
+        let hnd = match handle {
+            Some(handle) => handle,
+            None => self.gralloc.lock().allocate_memory(*reqs)?,
+        };
+
         let info_3d = Resource3DInfo {
             width: reqs.info.width,
             height: reqs.info.height,
@@ -164,7 +169,7 @@
 
         Ok(RutabagaResource {
             resource_id,
-            handle: Some(Arc::new(handle)),
+            handle: Some(Arc::new(hnd)),
             blob: true,
             blob_mem: resource_create_blob.blob_mem,
             blob_flags: resource_create_blob.blob_flags,
diff --git a/rutabaga_gfx/src/gfxstream.rs b/rutabaga_gfx/src/gfxstream.rs
index 2ae9a87..81d3b8a 100644
--- a/rutabaga_gfx/src/gfxstream.rs
+++ b/rutabaga_gfx/src/gfxstream.rs
@@ -411,7 +411,7 @@
         _ctx_id: u32,
         resource_id: u32,
         resource_create_blob: ResourceCreateBlob,
-        _iovecs: Vec<RutabagaIovec>,
+        _iovec_opt: Option<Vec<RutabagaIovec>>,
     ) -> RutabagaResult<RutabagaResource> {
         unsafe {
             stream_renderer_resource_create_v2(resource_id, resource_create_blob.blob_id);
diff --git a/rutabaga_gfx/src/rutabaga_core.rs b/rutabaga_gfx/src/rutabaga_core.rs
index b41e210e..e0b336e 100644
--- a/rutabaga_gfx/src/rutabaga_core.rs
+++ b/rutabaga_gfx/src/rutabaga_core.rs
@@ -132,7 +132,7 @@
         _ctx_id: u32,
         _resource_id: u32,
         _resource_create_blob: ResourceCreateBlob,
-        _backing_iovecs: Vec<RutabagaIovec>,
+        _iovec_opt: Option<Vec<RutabagaIovec>>,
     ) -> RutabagaResult<RutabagaResource> {
         Err(RutabagaError::Unsupported)
     }
@@ -166,6 +166,7 @@
         &mut self,
         _resource_id: u32,
         _resource_create_blob: ResourceCreateBlob,
+        _handle: Option<RutabagaHandle>,
     ) -> RutabagaResult<RutabagaResource> {
         Err(RutabagaError::Unsupported)
     }
@@ -442,13 +443,15 @@
     }
 
     /// Creates a blob resource with the `ctx_id` and `resource_create_blob` metadata.
-    /// Associates `iovecs` with the resource, if there are any.
+    /// Associates `iovecs` with the resource, if there are any.  Associates externally
+    /// created `handle` with the resource, if there is any.
     pub fn resource_create_blob(
         &mut self,
         ctx_id: u32,
         resource_id: u32,
         resource_create_blob: ResourceCreateBlob,
-        iovecs: Vec<RutabagaIovec>,
+        iovecs: Option<Vec<RutabagaIovec>>,
+        handle: Option<RutabagaHandle>,
     ) -> RutabagaResult<()> {
         if self.resources.contains_key(&resource_id) {
             return Err(RutabagaError::InvalidResourceId);
@@ -463,7 +466,8 @@
                 .get_mut(&ctx_id)
                 .ok_or(RutabagaError::InvalidContextId)?;
 
-            if let Ok(resource) = ctx.context_create_blob(resource_id, resource_create_blob) {
+            if let Ok(resource) = ctx.context_create_blob(resource_id, resource_create_blob, handle)
+            {
                 self.resources.insert(resource_id, resource);
                 return Ok(());
             }
diff --git a/rutabaga_gfx/src/virgl_renderer.rs b/rutabaga_gfx/src/virgl_renderer.rs
index fcd2156..cdea147 100644
--- a/rutabaga_gfx/src/virgl_renderer.rs
+++ b/rutabaga_gfx/src/virgl_renderer.rs
@@ -480,10 +480,17 @@
         ctx_id: u32,
         resource_id: u32,
         resource_create_blob: ResourceCreateBlob,
-        mut iovecs: Vec<RutabagaIovec>,
+        mut iovec_opt: Option<Vec<RutabagaIovec>>,
     ) -> RutabagaResult<RutabagaResource> {
         #[cfg(feature = "virgl_renderer_next")]
         {
+            let mut iovec_ptr = null_mut();
+            let mut num_iovecs = 0;
+            if let Some(ref mut iovecs) = iovec_opt {
+                iovec_ptr = iovecs.as_mut_ptr();
+                num_iovecs = iovecs.len();
+            }
+
             let resource_create_args = virgl_renderer_resource_create_blob_args {
                 res_handle: resource_id,
                 ctx_id,
@@ -491,17 +498,13 @@
                 blob_flags: resource_create_blob.blob_flags,
                 blob_id: resource_create_blob.blob_id,
                 size: resource_create_blob.size,
-                iovecs: iovecs.as_mut_ptr() as *const iovec,
-                num_iovs: iovecs.len() as u32,
+                iovecs: iovec_ptr as *const iovec,
+                num_iovs: num_iovecs as u32,
             };
+
             let ret = unsafe { virgl_renderer_resource_create_blob(&resource_create_args) };
             ret_to_res(ret)?;
 
-            let iovec_opt = match resource_create_blob.blob_mem {
-                RUTABAGA_BLOB_MEM_GUEST => Some(iovecs),
-                _ => None,
-            };
-
             Ok(RutabagaResource {
                 resource_id,
                 handle: self.export_blob(resource_id).ok(),
diff --git a/src/main.rs b/src/main.rs
index ab3786e..ab62f25 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -342,6 +342,20 @@
                 }
                 "cache-path" => gpu_params.cache_path = Some(v.to_string()),
                 "cache-size" => gpu_params.cache_size = Some(v.to_string()),
+                "udmabuf" => match v {
+                    "true" | "" => {
+                        gpu_params.udmabuf = true;
+                    }
+                    "false" => {
+                        gpu_params.udmabuf = false;
+                    }
+                    _ => {
+                        return Err(argument::Error::InvalidValue {
+                            value: v.to_string(),
+                            expected: String::from("gpu parameter 'udmabuf' should be a boolean"),
+                        });
+                    }
+                },
                 "" => {}
                 _ => {
                     return Err(argument::Error::UnknownArgument(format!(