virtwl: Add DMABuf allocation support.

This implements DMABuf allocation type in the virtio wayland
device.

We attempt to locate a supported DRM device prior to engaging
the device jail. If found, the DRM device is passed to the
wayland device code and used to serve DMABuf allocations.

DMABuf support can be disabled by not providing crosvm with
access to any DRM device nodes.

The guest is expected to handle the case when DMABuf allocation
fails and fall-back to standard shared memory.

This initial change uses DRM directly but is structured in a
way that would allow the allocator to be replaced by minigbm
with minimal effort.

BUG=chromium:837209
TEST=crosvm finds drm device and returns valid dmabufs to guest

Change-Id: Ic1fd776dfdfefae2d7b321d449273ef269e9cc62
Reviewed-on: https://chromium-review.googlesource.com/1034088
Commit-Ready: David Reveman <reveman@chromium.org>
Tested-by: David Reveman <reveman@chromium.org>
Reviewed-by: Zach Reizner <zachr@chromium.org>
diff --git a/src/linux.rs b/src/linux.rs
index 6192478..8aae432 100644
--- a/src/linux.rs
+++ b/src/linux.rs
@@ -18,6 +18,8 @@
 
 use libc;
 use libc::c_int;
+#[cfg(feature = "wl-dmabuf")]
+use libc::EINVAL;
 
 use device_manager;
 use devices;
@@ -29,7 +31,9 @@
 use sys_util::*;
 use sys_util;
 use vhost;
-use vm_control::VmRequest;
+use vm_control::{VmRequest, GpuMemoryAllocator};
+#[cfg(feature = "wl-dmabuf")]
+use gpu_buffer;
 
 use Config;
 use DiskType;
@@ -48,6 +52,7 @@
     CloneEventFd(sys_util::Error),
     Cmdline(kernel_cmdline::Error),
     CreateEventFd(sys_util::Error),
+    CreateGpuBufferDevice,
     CreateGuestMemory(Box<error::Error>),
     CreateIrqChip(Box<error::Error>),
     CreateKvm(sys_util::Error),
@@ -66,6 +71,7 @@
     NetDeviceNew(devices::virtio::NetError),
     NoVarEmpty,
     OpenKernel(PathBuf, io::Error),
+    OpenGpuBufferDevice,
     PollContextAdd(sys_util::Error),
     QcowDeviceCreate(qcow::Error),
     RegisterBalloon(device_manager::Error),
@@ -99,6 +105,7 @@
             &Error::CloneEventFd(ref e) => write!(f, "failed to clone eventfd: {:?}", e),
             &Error::Cmdline(ref e) => write!(f, "the given kernel command line was invalid: {}", e),
             &Error::CreateEventFd(ref e) => write!(f, "failed to create eventfd: {:?}", e),
+            &Error::CreateGpuBufferDevice => write!(f, "failed to create GPU buffer device"),
             &Error::CreateGuestMemory(ref e) => write!(f, "failed to create guest memory: {:?}", e),
             &Error::CreateIrqChip(ref e) => {
                 write!(f, "failed to create in-kernel IRQ chip: {:?}", e)
@@ -123,6 +130,7 @@
             &Error::OpenKernel(ref p, ref e) => {
                 write!(f, "failed to open kernel image {:?}: {}", p, e)
             }
+            &Error::OpenGpuBufferDevice => write!(f, "failed to open GPU buffer device"),
             &Error::PollContextAdd(ref e) => write!(f, "failed to add fd to poll context: {:?}", e),
             &Error::QcowDeviceCreate(ref e) => {
                 write!(f, "failed to read qcow formatted file {:?}", e)
@@ -543,6 +551,56 @@
         .map_err(Error::SpawnVcpu)
 }
 
+#[cfg(feature = "wl-dmabuf")]
+struct GpuBufferDevice {
+    device: gpu_buffer::Device,
+}
+
+#[cfg(feature = "wl-dmabuf")]
+impl GpuMemoryAllocator for GpuBufferDevice {
+    fn allocate(&self, width: u32, height: u32, format: u32) -> sys_util::Result<(File, u32)> {
+        let buffer = match self.device.create_buffer(
+            width,
+            height,
+            gpu_buffer::Format::from(format),
+            // Linear layout is a requirement as virtio wayland guest expects
+            // this for CPU access to the buffer. Scanout and texturing are
+            // optional as the consumer (wayland compositor) is expected to
+            // fall-back to a less efficient meachnisms for presentation if
+            // neccesary. In practice, linear buffers for commonly used formats
+            // will also support scanout and texturing.
+            gpu_buffer::Flags::empty().use_linear(true)) {
+            Ok(v) => v,
+            Err(_) => return Err(sys_util::Error::new(EINVAL)),
+        };
+        // We only support the first plane. Buffers with more planes are not
+        // a problem but additional planes will not be registered for access
+        // from guest.
+        let fd = match buffer.export_plane_fd(0) {
+            Ok(v) => v,
+            Err(e) => return Err(sys_util::Error::new(e)),
+        };
+
+        Ok((fd, buffer.stride()))
+    }
+}
+
+#[cfg(feature = "wl-dmabuf")]
+fn create_gpu_memory_allocator() -> Result<Option<Box<GpuMemoryAllocator>>> {
+    let undesired: &[&str] = &["vgem"];
+    let fd = gpu_buffer::rendernode::open_device(undesired)
+        .map_err(|_| Error::OpenGpuBufferDevice)?;
+    let device = gpu_buffer::Device::new(fd)
+        .map_err(|_| Error::CreateGpuBufferDevice)?;
+    info!("created GPU buffer device for DMABuf allocations");
+    Ok(Some(Box::new(GpuBufferDevice { device })))
+}
+
+#[cfg(not(feature = "wl-dmabuf"))]
+fn create_gpu_memory_allocator() -> Result<Option<Box<GpuMemoryAllocator>>> {
+    Ok(None)
+}
+
 fn run_control(vm: &mut Vm,
                control_sockets: Vec<UnlinkUnixDatagram>,
                next_dev_pfn: &mut u64,
@@ -552,7 +610,8 @@
                kill_signaled: Arc<AtomicBool>,
                vcpu_handles: Vec<JoinHandle<()>>,
                balloon_host_socket: UnixDatagram,
-               _irqchip_fd: Option<File>)
+               _irqchip_fd: Option<File>,
+               gpu_memory_allocator: Option<Box<GpuMemoryAllocator>>)
                -> Result<()> {
     const MAX_VM_FD_RECV: usize = 1;
 
@@ -638,8 +697,13 @@
                             Ok(request) => {
                                 let mut running = true;
                                 let response =
-                                    request.execute(vm, next_dev_pfn,
-                                                    &mut running, &balloon_host_socket);
+                                    request.execute(vm,
+                                                    next_dev_pfn,
+                                                    &mut running,
+                                                    &balloon_host_socket,
+                                                    gpu_memory_allocator.as_ref().map(|v| {
+                                                                                          v.as_ref()
+                                                                                      }));
                                 if let Err(e) = response.send(&mut scm, socket.as_ref()) {
                                     error!("failed to send VmResponse: {:?}", e);
                                 }
@@ -751,6 +815,12 @@
                                   &mut control_sockets,
                                   balloon_device_socket)?;
 
+    let gpu_memory_allocator = if cfg.wayland_dmabuf {
+        create_gpu_memory_allocator()?
+    } else {
+        None
+    };
+
     for param in &cfg.params {
         cmdline.insert_str(&param).map_err(Error::Cmdline)?;
     }
@@ -787,5 +857,6 @@
                 kill_signaled,
                 vcpu_handles,
                 balloon_host_socket,
-                irq_chip)
+                irq_chip,
+                gpu_memory_allocator)
 }
diff --git a/src/main.rs b/src/main.rs
index f8ed715..7c248e2 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -29,6 +29,8 @@
 extern crate plugin_proto;
 #[cfg(feature = "plugin")]
 extern crate protobuf;
+#[cfg(feature = "wl-dmabuf")]
+extern crate gpu_buffer;
 
 pub mod argument;
 pub mod linux;
@@ -71,6 +73,7 @@
     mac_address: Option<net_util::MacAddress>,
     vhost_net: bool,
     wayland_socket_path: Option<PathBuf>,
+    wayland_dmabuf: bool,
     socket_path: Option<PathBuf>,
     multiprocess: bool,
     seccomp_policy_dir: PathBuf,
@@ -92,6 +95,7 @@
             mac_address: None,
             vhost_net: false,
             wayland_socket_path: None,
+            wayland_dmabuf: false,
             socket_path: None,
             multiprocess: !cfg!(feature = "default-no-sandbox"),
             seccomp_policy_dir: PathBuf::from(SECCOMP_POLICY_DIR),
@@ -269,6 +273,10 @@
             }
             cfg.wayland_socket_path = Some(wayland_socket_path);
         }
+        #[cfg(feature = "wl-dmabuf")]
+        "enable-wayland-dmabuf" => {
+            cfg.wayland_dmabuf = true
+        },
         "socket" => {
             if cfg.socket_path.is_some() {
                 return Err(argument::Error::TooManyArguments("`socket` already given".to_owned()));
@@ -363,6 +371,8 @@
           Argument::value("wayland-group",
                           "GROUP",
                           "Name of the group with access to the Wayland socket."),
+          #[cfg(feature = "wl-dmabuf")]
+          Argument::flag("wayland-dmabuf", "Enable support for DMABufs in Wayland device."),
           Argument::short_value('s',
                                 "socket",
                                 "PATH",