summary refs log tree commit diff
diff options
context:
space:
mode:
authorZach Reizner <zachr@google.com>2018-08-15 10:46:32 -0700
committerchrome-bot <chrome-bot@chromium.org>2018-12-11 19:33:56 -0800
commitaa5756669a8331420b84a22e29ddbfc13b791da5 (patch)
tree3a2767b6ecc2779ca1e9f64ff7c60d8ae3bf8fdf
parent42c409c4d7c661ead9794c54811ce5fadf0ae8e7 (diff)
downloadcrosvm-aa5756669a8331420b84a22e29ddbfc13b791da5.tar
crosvm-aa5756669a8331420b84a22e29ddbfc13b791da5.tar.gz
crosvm-aa5756669a8331420b84a22e29ddbfc13b791da5.tar.bz2
crosvm-aa5756669a8331420b84a22e29ddbfc13b791da5.tar.lz
crosvm-aa5756669a8331420b84a22e29ddbfc13b791da5.tar.xz
crosvm-aa5756669a8331420b84a22e29ddbfc13b791da5.tar.zst
crosvm-aa5756669a8331420b84a22e29ddbfc13b791da5.zip
devices: allow virtio-wayland to use virtgpu resources
This change uses the resource bridge between virtio-gpu and virtio-cpu
to send resources over the host wayland connection that originated from
the virtio-gpu device. This will help support gpu accelerated wayland
surfaces.

BUG=chromium:875998
TEST=wayland-simple-egl

Change-Id: I3340ecef438779be5cb3643b2de8bb8c33097d75
Reviewed-on: https://chromium-review.googlesource.com/1182793
Commit-Ready: ChromeOS CL Exonerator Bot <chromiumos-cl-exonerator@appspot.gserviceaccount.com>
Tested-by: Zach Reizner <zachr@chromium.org>
Reviewed-by: Zach Reizner <zachr@chromium.org>
-rw-r--r--Cargo.lock1
-rw-r--r--devices/Cargo.toml3
-rw-r--r--devices/src/lib.rs6
-rw-r--r--devices/src/virtio/gpu/backend.rs26
-rw-r--r--devices/src/virtio/gpu/mod.rs37
-rw-r--r--devices/src/virtio/mod.rs1
-rw-r--r--devices/src/virtio/resource_bridge.rs30
-rw-r--r--devices/src/virtio/wl.rs177
-rw-r--r--gpu_renderer/src/pipe_format_fourcc.rs6
-rw-r--r--src/linux.rs179
10 files changed, 351 insertions, 115 deletions
diff --git a/Cargo.lock b/Cargo.lock
index b09ca11..5677291 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -146,6 +146,7 @@ dependencies = [
  "io_jail 0.1.0",
  "kvm 0.1.0",
  "libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)",
+ "msg_on_socket_derive 0.1.0",
  "msg_socket 0.1.0",
  "net_sys 0.1.0",
  "net_util 0.1.0",
diff --git a/devices/Cargo.toml b/devices/Cargo.toml
index 8ed200d..3556bb6 100644
--- a/devices/Cargo.toml
+++ b/devices/Cargo.toml
@@ -15,8 +15,9 @@ gpu_display = { path = "../gpu_display", optional = true }
 gpu_renderer = { path = "../gpu_renderer", optional = true }
 kvm = { path = "../kvm" }
 libc = "*"
-msg_socket = { path = "../msg_socket" }
 io_jail = { path = "../io_jail" }
+msg_on_socket_derive = { path = "../msg_socket/msg_on_socket_derive" }
+msg_socket = { path = "../msg_socket" }
 net_sys = { path = "../net_sys" }
 net_util = { path = "../net_util" }
 p9 = { path = "../p9" }
diff --git a/devices/src/lib.rs b/devices/src/lib.rs
index 5173a7d..ee252ab 100644
--- a/devices/src/lib.rs
+++ b/devices/src/lib.rs
@@ -9,18 +9,18 @@ extern crate data_model;
 extern crate io_jail;
 extern crate kvm;
 extern crate libc;
+extern crate msg_on_socket_derive;
+extern crate msg_socket;
 extern crate net_sys;
 extern crate net_util;
 extern crate p9;
 extern crate resources;
+extern crate sync;
 #[macro_use]
 extern crate sys_util;
 extern crate vhost;
 extern crate virtio_sys;
 extern crate vm_control;
-#[macro_use]
-extern crate msg_socket;
-extern crate sync;
 
 mod bus;
 mod cmos;
diff --git a/devices/src/virtio/gpu/backend.rs b/devices/src/virtio/gpu/backend.rs
index 634cc55..139fd43 100644
--- a/devices/src/virtio/gpu/backend.rs
+++ b/devices/src/virtio/gpu/backend.rs
@@ -13,6 +13,7 @@ use std::usize;
 
 use data_model::*;
 
+use msg_socket::{MsgReceiver, MsgSender};
 use sys_util::{GuestAddress, GuestMemory};
 
 use super::gpu_buffer::{Buffer, Device, Flags, Format};
@@ -22,6 +23,7 @@ use super::gpu_renderer::{
     Renderer, Resource as GpuRendererResource, ResourceCreateArgs,
 };
 
+use super::super::resource_bridge::*;
 use super::protocol::GpuResponse;
 use super::protocol::{VIRTIO_GPU_CAPSET_VIRGL, VIRTIO_GPU_CAPSET_VIRGL2};
 
@@ -349,6 +351,30 @@ impl Backend {
             .unwrap_or(false)
     }
 
+    pub fn process_resource_bridge(&self, resource_bridge: &ResourceResponseSocket) {
+        let request = match resource_bridge.recv() {
+            Ok(msg) => msg,
+            Err(e) => {
+                error!("error receiving resource bridge request: {:?}", e);
+                return;
+            }
+        };
+
+        let response = match request {
+            ResourceRequest::GetResource { id } => self
+                .resources
+                .get(&id)
+                .and_then(|resource| resource.buffer())
+                .and_then(|buffer| buffer.export_plane_fd(0).ok())
+                .map(|fd| ResourceResponse::Resource(fd))
+                .unwrap_or(ResourceResponse::Invalid),
+        };
+
+        if let Err(e) = resource_bridge.send(&response) {
+            error!("error sending resource bridge request: {:?}", e);
+        }
+    }
+
     /// Gets the list of supported display resolutions as a slice of `(width, height)` tuples.
     pub fn display_info(&self) -> &[(u32, u32)] {
         &[(DEFAULT_WIDTH, DEFAULT_HEIGHT)]
diff --git a/devices/src/virtio/gpu/mod.rs b/devices/src/virtio/gpu/mod.rs
index f13c06d..bcab27e 100644
--- a/devices/src/virtio/gpu/mod.rs
+++ b/devices/src/virtio/gpu/mod.rs
@@ -30,7 +30,8 @@ use self::gpu_display::*;
 use self::gpu_renderer::{format_fourcc, Renderer};
 
 use super::{
-    AvailIter, Queue, VirtioDevice, INTERRUPT_STATUS_USED_RING, TYPE_GPU, VIRTIO_F_VERSION_1,
+    resource_bridge::*, AvailIter, Queue, VirtioDevice, INTERRUPT_STATUS_USED_RING, TYPE_GPU,
+    VIRTIO_F_VERSION_1,
 };
 
 use self::backend::Backend;
@@ -89,6 +90,10 @@ impl Frontend {
         self.backend.process_display()
     }
 
+    fn process_resource_bridge(&self, resource_bridge: &ResourceResponseSocket) {
+        self.backend.process_resource_bridge(resource_bridge);
+    }
+
     fn process_gpu_command(
         &mut self,
         mem: &GuestMemory,
@@ -462,6 +467,7 @@ struct Worker {
     ctrl_evt: EventFd,
     cursor_queue: Queue,
     cursor_evt: EventFd,
+    resource_bridge: Option<ResourceResponseSocket>,
     kill_evt: EventFd,
     state: Frontend,
 }
@@ -479,6 +485,7 @@ impl Worker {
             CtrlQueue,
             CursorQueue,
             Display,
+            ResourceBridge,
             InterruptResample,
             Kill,
         }
@@ -501,6 +508,12 @@ impl Worker {
             }
         };
 
+        if let Some(ref resource_bridge) = self.resource_bridge {
+            if let Err(e) = poll_ctx.add(resource_bridge, Token::ResourceBridge) {
+                error!("failed to add resource bridge to PollContext: {:?}", e);
+            }
+        }
+
         'poll: loop {
             // If there are outstanding fences, wake up early to poll them.
             let duration = if !self.state.fence_descriptors.is_empty() {
@@ -517,6 +530,7 @@ impl Worker {
                 }
             };
             let mut signal_used = false;
+            let mut process_resource_bridge = false;
             for event in events.iter_readable() {
                 match event.token() {
                     Token::CtrlQueue => {
@@ -535,6 +549,7 @@ impl Worker {
                             let _ = self.exit_evt.write(1);
                         }
                     }
+                    Token::ResourceBridge => process_resource_bridge = true,
                     Token::InterruptResample => {
                         let _ = self.interrupt_resample_evt.read();
                         if self.interrupt_status.load(Ordering::SeqCst) != 0 {
@@ -570,6 +585,15 @@ impl Worker {
                 }
             }
 
+            // Process the entire control queue before the resource bridge in case a resource is
+            // created or destroyed by the control queue. Processing the resource bridge first may
+            // lead to a race condition.
+            if process_resource_bridge {
+                if let Some(ref resource_bridge) = self.resource_bridge {
+                    self.state.process_resource_bridge(resource_bridge);
+                }
+            }
+
             if signal_used {
                 self.signal_used_queue();
             }
@@ -580,15 +604,21 @@ impl Worker {
 pub struct Gpu {
     config_event: bool,
     exit_evt: EventFd,
+    resource_bridge: Option<ResourceResponseSocket>,
     kill_evt: Option<EventFd>,
     wayland_socket_path: PathBuf,
 }
 
 impl Gpu {
-    pub fn new<P: AsRef<Path>>(exit_evt: EventFd, wayland_socket_path: P) -> Gpu {
+    pub fn new<P: AsRef<Path>>(
+        exit_evt: EventFd,
+        resource_bridge: Option<ResourceResponseSocket>,
+        wayland_socket_path: P,
+    ) -> Gpu {
         Gpu {
             config_event: false,
             exit_evt,
+            resource_bridge,
             kill_evt: None,
             wayland_socket_path: wayland_socket_path.as_ref().to_path_buf(),
         }
@@ -695,6 +725,8 @@ impl VirtioDevice for Gpu {
         };
         self.kill_evt = Some(self_kill_evt);
 
+        let resource_bridge = self.resource_bridge.take();
+
         let ctrl_queue = queues.remove(0);
         let ctrl_evt = queue_evts.remove(0);
         let cursor_queue = queues.remove(0);
@@ -744,6 +776,7 @@ impl VirtioDevice for Gpu {
                 ctrl_evt,
                 cursor_queue,
                 cursor_evt,
+                resource_bridge,
                 kill_evt,
                 state: Frontend::new(Backend::new(device, display, renderer)),
             }.run()
diff --git a/devices/src/virtio/mod.rs b/devices/src/virtio/mod.rs
index 12b74ec..ed152b2 100644
--- a/devices/src/virtio/mod.rs
+++ b/devices/src/virtio/mod.rs
@@ -17,6 +17,7 @@ mod virtio_pci_common_config;
 mod virtio_pci_device;
 mod wl;
 
+pub mod resource_bridge;
 pub mod vhost;
 
 pub use self::balloon::*;
diff --git a/devices/src/virtio/resource_bridge.rs b/devices/src/virtio/resource_bridge.rs
new file mode 100644
index 0000000..d3a3375
--- /dev/null
+++ b/devices/src/virtio/resource_bridge.rs
@@ -0,0 +1,30 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! This module defines the protocol between `virtio-wayland` and `virtio-gpu` for sharing resources
+//! that are backed by file descriptors.
+
+use std::fs::File;
+use std::io::Result;
+
+use msg_on_socket_derive::MsgOnSocket;
+use msg_socket::MsgSocket;
+
+#[derive(MsgOnSocket)]
+pub enum ResourceRequest {
+    GetResource { id: u32 },
+}
+
+#[derive(MsgOnSocket)]
+pub enum ResourceResponse {
+    Resource(File),
+    Invalid,
+}
+
+pub type ResourceRequestSocket = MsgSocket<ResourceRequest, ResourceResponse>;
+pub type ResourceResponseSocket = MsgSocket<ResourceResponse, ResourceRequest>;
+
+pub fn pair() -> Result<(ResourceRequestSocket, ResourceResponseSocket)> {
+    msg_socket::pair()
+}
diff --git a/devices/src/virtio/wl.rs b/devices/src/virtio/wl.rs
index 9c4e424..9c3b517 100644
--- a/devices/src/virtio/wl.rs
+++ b/devices/src/virtio/wl.rs
@@ -67,6 +67,7 @@ use sys_util::{
 #[cfg(feature = "wl-dmabuf")]
 use sys_util::ioctl_with_ref;
 
+use super::resource_bridge::*;
 use super::{
     DescriptorChain, Queue, VirtioDevice, INTERRUPT_STATUS_USED_RING, TYPE_WL, VIRTIO_F_VERSION_1,
 };
@@ -84,6 +85,8 @@ const VIRTIO_WL_CMD_VFD_HUP: u32 = 262;
 const VIRTIO_WL_CMD_VFD_NEW_DMABUF: u32 = 263;
 #[cfg(feature = "wl-dmabuf")]
 const VIRTIO_WL_CMD_VFD_DMABUF_SYNC: u32 = 264;
+#[cfg(feature = "gpu")]
+const VIRTIO_WL_CMD_VFD_SEND_FOREIGN_ID: u32 = 265;
 const VIRTIO_WL_RESP_OK: u32 = 4096;
 const VIRTIO_WL_RESP_VFD_NEW: u32 = 4097;
 #[cfg(feature = "wl-dmabuf")]
@@ -123,6 +126,9 @@ struct dma_buf_sync {
 #[cfg(feature = "wl-dmabuf")]
 ioctl_iow_nr!(DMA_BUF_IOCTL_SYNC, DMA_BUF_IOCTL_BASE, 0, dma_buf_sync);
 
+const VIRTIO_WL_CTRL_VFD_SEND_KIND_LOCAL: u32 = 0;
+const VIRTIO_WL_CTRL_VFD_SEND_KIND_VIRTGPU: u32 = 1;
+
 fn parse_new(addr: GuestAddress, mem: &GuestMemory) -> WlResult<WlOp> {
     const ID_OFFSET: u64 = 8;
     const FLAGS_OFFSET: u64 = 12;
@@ -214,7 +220,7 @@ fn parse_dmabuf_sync(addr: GuestAddress, mem: &GuestMemory) -> WlResult<WlOp> {
     })
 }
 
-fn parse_send(addr: GuestAddress, len: u32, mem: &GuestMemory) -> WlResult<WlOp> {
+fn parse_send(addr: GuestAddress, len: u32, foreign_id: bool, mem: &GuestMemory) -> WlResult<WlOp> {
     const ID_OFFSET: u64 = 8;
     const VFD_COUNT_OFFSET: u64 = 12;
     const VFDS_OFFSET: u64 = 16;
@@ -231,15 +237,21 @@ fn parse_send(addr: GuestAddress, len: u32, mem: &GuestMemory) -> WlResult<WlOp>
     let vfds_addr = mem
         .checked_offset(addr, VFDS_OFFSET)
         .ok_or(WlError::CheckedOffset)?;
+    let vfds_element_size = if foreign_id {
+        size_of::<CtrlVfdSendVfd>()
+    } else {
+        size_of::<Le32>()
+    } as u32;
     let data_addr = mem
-        .checked_offset(vfds_addr, (vfd_count * 4) as u64)
+        .checked_offset(vfds_addr, (vfd_count * vfds_element_size) as u64)
         .ok_or(WlError::CheckedOffset)?;
     Ok(WlOp::Send {
         id: id.into(),
+        foreign_id,
         vfds_addr,
         vfd_count,
         data_addr,
-        data_len: len - (VFDS_OFFSET as u32) - vfd_count * 4,
+        data_len: len - (VFDS_OFFSET as u32) - vfd_count * vfds_element_size,
     })
 }
 
@@ -259,7 +271,7 @@ fn parse_desc(desc: &DescriptorChain, mem: &GuestMemory) -> WlResult<WlOp> {
         VIRTIO_WL_CMD_VFD_CLOSE => Ok(WlOp::Close {
             id: parse_id(desc.addr, mem)?,
         }),
-        VIRTIO_WL_CMD_VFD_SEND => parse_send(desc.addr, desc.len, mem),
+        VIRTIO_WL_CMD_VFD_SEND => parse_send(desc.addr, desc.len, false, mem),
         VIRTIO_WL_CMD_VFD_NEW_CTX => Ok(WlOp::NewCtx {
             id: parse_id(desc.addr, mem)?,
         }),
@@ -268,6 +280,8 @@ fn parse_desc(desc: &DescriptorChain, mem: &GuestMemory) -> WlResult<WlOp> {
         VIRTIO_WL_CMD_VFD_NEW_DMABUF => parse_new_dmabuf(desc.addr, mem),
         #[cfg(feature = "wl-dmabuf")]
         VIRTIO_WL_CMD_VFD_DMABUF_SYNC => parse_dmabuf_sync(desc.addr, mem),
+        #[cfg(feature = "gpu")]
+        VIRTIO_WL_CMD_VFD_SEND_FOREIGN_ID => parse_send(desc.addr, desc.len, true, mem),
         v => Ok(WlOp::InvalidCommand { op_type: v }),
     }
 }
@@ -553,6 +567,15 @@ struct CtrlVfd {
 
 unsafe impl DataInit for CtrlVfd {}
 
+#[repr(C)]
+#[derive(Copy, Clone, Default)]
+struct CtrlVfdSendVfd {
+    kind: Le32,
+    id: Le32,
+}
+
+unsafe impl DataInit for CtrlVfdSendVfd {}
+
 #[derive(Debug)]
 enum WlOp {
     NewAlloc {
@@ -565,6 +588,7 @@ enum WlOp {
     },
     Send {
         id: u32,
+        foreign_id: bool,
         vfds_addr: GuestAddress,
         vfd_count: u32,
         data_addr: GuestAddress,
@@ -966,6 +990,7 @@ enum WlRecv {
 struct WlState {
     wayland_path: PathBuf,
     vm: VmRequester,
+    resource_bridge: Option<ResourceRequestSocket>,
     use_transition_flags: bool,
     poll_ctx: PollContext<u32>,
     vfds: Map<u32, WlVfd>,
@@ -977,10 +1002,16 @@ struct WlState {
 }
 
 impl WlState {
-    fn new(wayland_path: PathBuf, vm_socket: UnixDatagram, use_transition_flags: bool) -> WlState {
+    fn new(
+        wayland_path: PathBuf,
+        vm_socket: UnixDatagram,
+        use_transition_flags: bool,
+        resource_bridge: Option<ResourceRequestSocket>,
+    ) -> WlState {
         WlState {
             wayland_path,
             vm: VmRequester::new(vm_socket),
+            resource_bridge,
             poll_ctx: PollContext::new().expect("failed to create PollContext"),
             use_transition_flags,
             vfds: Map::new(),
@@ -1179,21 +1210,96 @@ impl WlState {
         }
     }
 
-    fn send(&mut self, vfd_id: u32, vfds: VolatileSlice, data: VolatileSlice) -> WlResult<WlResp> {
-        let vfd_count = vfds.size() as usize / size_of::<Le32>();
-        let mut vfd_ids = [Le32::from(0); VIRTWL_SEND_MAX_ALLOCS];
-        vfds.copy_to(&mut vfd_ids[..]);
+    fn send(
+        &mut self,
+        vfd_id: u32,
+        foreign_id: bool,
+        vfds: VolatileSlice,
+        data: VolatileSlice,
+    ) -> WlResult<WlResp> {
+        // First stage gathers and normalizes all id information from guest memory.
+        let mut send_vfd_ids = [CtrlVfdSendVfd::default(); VIRTWL_SEND_MAX_ALLOCS];
+        let vfd_count = if foreign_id {
+            vfds.copy_to(&mut send_vfd_ids[..]);
+            vfds.size() as usize / size_of::<CtrlVfdSendVfd>()
+        } else {
+            let vfd_count = vfds.size() as usize / size_of::<Le32>();
+            let mut vfd_ids = [Le32::from(0); VIRTWL_SEND_MAX_ALLOCS];
+            vfds.copy_to(&mut vfd_ids[..]);
+            send_vfd_ids[..vfd_count]
+                .iter_mut()
+                .zip(vfd_ids[..vfd_count].iter())
+                .for_each(|(send_vfd_id, &vfd_id)| {
+                    *send_vfd_id = CtrlVfdSendVfd {
+                        kind: Le32::from(VIRTIO_WL_CTRL_VFD_SEND_KIND_LOCAL),
+                        id: vfd_id,
+                    }
+                });
+            vfd_count
+        };
+
+        // Next stage collects corresponding file descriptors for each id.
         let mut fds = [0; VIRTWL_SEND_MAX_ALLOCS];
-        for (&id, fd) in vfd_ids[..vfd_count].iter().zip(fds.iter_mut()) {
-            match self.vfds.get(&id.into()) {
-                Some(vfd) => match vfd.send_fd() {
-                    Some(vfd_fd) => *fd = vfd_fd,
-                    None => return Ok(WlResp::InvalidType),
+        #[cfg(feature = "gpu")]
+        let mut bridged_files = Vec::new();
+        for (&send_vfd_id, fd) in send_vfd_ids[..vfd_count].iter().zip(fds.iter_mut()) {
+            let id = send_vfd_id.id.to_native();
+            match send_vfd_id.kind.to_native() {
+                VIRTIO_WL_CTRL_VFD_SEND_KIND_LOCAL => match self.vfds.get(&id) {
+                    Some(vfd) => match vfd.send_fd() {
+                        Some(vfd_fd) => *fd = vfd_fd,
+                        None => return Ok(WlResp::InvalidType),
+                    },
+                    None => {
+                        warn!("attempt to send non-existant vfd 0x{:08x}", id);
+                        return Ok(WlResp::InvalidId);
+                    }
                 },
-                None => return Ok(WlResp::InvalidId),
+                #[cfg(feature = "gpu")]
+                VIRTIO_WL_CTRL_VFD_SEND_KIND_VIRTGPU if self.resource_bridge.is_some() =>
+                {
+                    if let Err(e) = self
+                        .resource_bridge
+                        .as_ref()
+                        .unwrap()
+                        .send(&ResourceRequest::GetResource { id })
+                    {
+                        error!("error sending resource bridge request: {:?}", e);
+                        return Ok(WlResp::InvalidId);
+                    }
+                    match self.resource_bridge.as_ref().unwrap().recv() {
+                        Ok(ResourceResponse::Resource(bridged_file)) => {
+                            *fd = bridged_file.as_raw_fd();
+                            bridged_files.push(bridged_file);
+                        }
+                        Ok(ResourceResponse::Invalid) => {
+                            warn!("attempt to send non-existant gpu resource {}", id);
+                            return Ok(WlResp::InvalidId);
+                        }
+                        Err(e) => {
+                            error!("error receiving resource bridge response: {:?}", e);
+                            // If there was an error with the resource bridge, it can no longer be
+                            // trusted to continue to function.
+                            self.resource_bridge = None;
+                            return Ok(WlResp::InvalidId);
+                        }
+                    }
+                }
+                VIRTIO_WL_CTRL_VFD_SEND_KIND_VIRTGPU => {
+                    let _ = self.resource_bridge.as_ref();
+                    warn!("attempt to send foreign resource kind but feature is disabled");
+                }
+                kind => {
+                    warn!(
+                        "attempt to send unknown foreign resource kind: {} id: {:08x}",
+                        kind, id
+                    );
+                    return Ok(WlResp::InvalidId);
+                }
             }
         }
 
+        // Final stage sends file descriptors and data to the target vfd's socket.
         match self.vfds.get_mut(&vfd_id) {
             Some(vfd) => match vfd.send(&fds[..vfd_count], data)? {
                 WlResp::Ok => {}
@@ -1203,10 +1309,12 @@ impl WlState {
         }
         // The vfds with remote FDs need to be closed so that the local side can receive
         // hangup events.
-        for &id in &vfd_ids[..vfd_count] {
-            // The following unwrap can not panic because the IDs were already checked earlier in
-            // this method.
-            self.vfds.get_mut(&id.into()).unwrap().close_remote();
+        for &send_vfd_id in &send_vfd_ids[..vfd_count] {
+            if send_vfd_id.kind == VIRTIO_WL_CTRL_VFD_SEND_KIND_LOCAL {
+                self.vfds
+                    .get_mut(&send_vfd_id.id.into())
+                    .map(|vfd| vfd.close_remote());
+            }
         }
         Ok(WlResp::Ok)
     }
@@ -1247,15 +1355,20 @@ impl WlState {
             WlOp::Close { id } => self.close(id),
             WlOp::Send {
                 id,
+                foreign_id,
                 vfds_addr,
                 vfd_count,
                 data_addr,
                 data_len,
             } => {
-                let vfd_mem =
-                    mem.get_slice(vfds_addr.0, (vfd_count as u64) * size_of::<Le32>() as u64)?;
+                let vfd_size = if foreign_id {
+                    size_of::<CtrlVfdSendVfd>()
+                } else {
+                    size_of::<Le32>()
+                } as u32;
+                let vfd_mem = mem.get_slice(vfds_addr.0, (vfd_count * vfd_size) as u64)?;
                 let data_mem = mem.get_slice(data_addr.0, data_len as u64)?;
-                self.send(id, vfd_mem, data_mem)
+                self.send(id, foreign_id, vfd_mem, data_mem)
             }
             WlOp::NewCtx { id } => self.new_context(id),
             WlOp::NewPipe { id, flags } => self.new_pipe(id, flags),
@@ -1378,6 +1491,7 @@ impl Worker {
         wayland_path: PathBuf,
         vm_socket: UnixDatagram,
         use_transition_flags: bool,
+        resource_bridge: Option<ResourceRequestSocket>,
     ) -> Worker {
         Worker {
             mem,
@@ -1386,7 +1500,12 @@ impl Worker {
             interrupt_status,
             in_queue,
             out_queue,
-            state: WlState::new(wayland_path, vm_socket, use_transition_flags),
+            state: WlState::new(
+                wayland_path,
+                vm_socket,
+                use_transition_flags,
+                resource_bridge,
+            ),
             in_desc_chains: VecDeque::with_capacity(QUEUE_SIZE as usize),
         }
     }
@@ -1561,17 +1680,21 @@ pub struct Wl {
     kill_evt: Option<EventFd>,
     wayland_path: PathBuf,
     vm_socket: Option<UnixDatagram>,
+    resource_bridge: Option<ResourceRequestSocket>,
     use_transition_flags: bool,
 }
 
 impl Wl {
-    pub fn new<P: AsRef<Path>>(wayland_path: P, vm_socket: UnixDatagram) -> Result<Wl> {
-        // let kill_evt = EventFd::new()?;
-        //     workers_kill_evt: Some(kill_evt.try_clone()?),
+    pub fn new<P: AsRef<Path>>(
+        wayland_path: P,
+        vm_socket: UnixDatagram,
+        resource_bridge: Option<ResourceRequestSocket>,
+    ) -> Result<Wl> {
         Ok(Wl {
             kill_evt: None,
             wayland_path: wayland_path.as_ref().to_owned(),
             vm_socket: Some(vm_socket),
+            resource_bridge,
             use_transition_flags: false,
         })
     }
@@ -1640,6 +1763,7 @@ impl VirtioDevice for Wl {
         if let Some(vm_socket) = self.vm_socket.take() {
             let wayland_path = self.wayland_path.clone();
             let use_transition_flags = self.use_transition_flags;
+            let resource_bridge = self.resource_bridge.take();
             let worker_result =
                 thread::Builder::new()
                     .name("virtio_wl".to_string())
@@ -1654,6 +1778,7 @@ impl VirtioDevice for Wl {
                             wayland_path,
                             vm_socket,
                             use_transition_flags,
+                            resource_bridge,
                         ).run(queue_evts, kill_evt);
                     });
 
diff --git a/gpu_renderer/src/pipe_format_fourcc.rs b/gpu_renderer/src/pipe_format_fourcc.rs
index 828f372..165f02c 100644
--- a/gpu_renderer/src/pipe_format_fourcc.rs
+++ b/gpu_renderer/src/pipe_format_fourcc.rs
@@ -14,7 +14,13 @@ macro_rules! fourcc {
 /// unrecognized.
 pub fn pipe_format_fourcc(f: p_format::pipe_format) -> Option<u32> {
     match f {
+        p_format::PIPE_FORMAT_B8G8R8A8_UNORM => fourcc!('A', 'R', '2', '4'),
         p_format::PIPE_FORMAT_B8G8R8X8_UNORM => fourcc!('X', 'R', '2', '4'),
+        p_format::PIPE_FORMAT_R8G8B8A8_UNORM => fourcc!('A', 'B', '2', '4'),
+        p_format::PIPE_FORMAT_R8G8B8X8_UNORM => fourcc!('X', 'B', '2', '4'),
+        // p_format::PIPE_FORMAT_B5G6R5_UNORM => fourcc!('R', 'G', '1', '6'),
+        // p_format::PIPE_FORMAT_R8_UNORM => fourcc!('R', '8', ' ', ' '),
+        // p_format::PIPE_FORMAT_G8R8_UNORM => fourcc!('R', 'G', '8', '8'),
         _ => None,
     }
 }
diff --git a/src/linux.rs b/src/linux.rs
index d4094fa..1cb99cc 100644
--- a/src/linux.rs
+++ b/src/linux.rs
@@ -382,6 +382,98 @@ fn create_virtio_devs(
         }
     }
 
+    #[cfg(feature = "gpu")]
+    let mut resource_bridge_wl_socket: Option<
+        devices::virtio::resource_bridge::ResourceRequestSocket,
+    > = None;
+    #[cfg(feature = "gpu")]
+    {
+        if cfg.gpu {
+            if let Some(wayland_socket_path) = cfg.wayland_socket_path.as_ref() {
+                let (wl_socket, gpu_socket) =
+                    devices::virtio::resource_bridge::pair().map_err(Error::CreateSocket)?;
+                resource_bridge_wl_socket = Some(wl_socket);
+
+                let jailed_wayland_path = Path::new("/wayland-0");
+
+                let gpu_box = Box::new(devices::virtio::Gpu::new(
+                    _exit_evt.try_clone().map_err(Error::CloneEventFd)?,
+                    Some(gpu_socket),
+                    if cfg.multiprocess {
+                        &jailed_wayland_path
+                    } else {
+                        wayland_socket_path.as_path()
+                    },
+                ));
+
+                let jail = if cfg.multiprocess {
+                    let policy_path: PathBuf = cfg.seccomp_policy_dir.join("gpu_device.policy");
+                    let mut jail = create_base_minijail(empty_root_path, &policy_path)?;
+
+                    // Create a tmpfs in the device's root directory so that we can bind mount the
+                    // dri directory into it.  The size=67108864 is size=64*1024*1024 or size=64MB.
+                    jail.mount_with_data(
+                        Path::new("none"),
+                        Path::new("/"),
+                        "tmpfs",
+                        (libc::MS_NOSUID | libc::MS_NODEV | libc::MS_NOEXEC) as usize,
+                        "size=67108864",
+                    ).unwrap();
+
+                    // Device nodes required for DRM.
+                    let sys_dev_char_path = Path::new("/sys/dev/char");
+                    jail.mount_bind(sys_dev_char_path, sys_dev_char_path, false)
+                        .unwrap();
+                    let sys_devices_path = Path::new("/sys/devices");
+                    jail.mount_bind(sys_devices_path, sys_devices_path, false)
+                        .unwrap();
+                    let drm_dri_path = Path::new("/dev/dri");
+                    jail.mount_bind(drm_dri_path, drm_dri_path, false).unwrap();
+
+                    // Libraries that are required when mesa drivers are dynamically loaded.
+                    let lib_path = Path::new("/lib64");
+                    jail.mount_bind(lib_path, lib_path, false).unwrap();
+                    let usr_lib_path = Path::new("/usr/lib64");
+                    jail.mount_bind(usr_lib_path, usr_lib_path, false).unwrap();
+
+                    // Bind mount the wayland socket into jail's root. This is necessary since each
+                    // new wayland context must open() the socket.
+                    jail.mount_bind(wayland_socket_path.as_path(), jailed_wayland_path, true)
+                        .unwrap();
+
+                    // Set the uid/gid for the jailed process, and give a basic id map. This
+                    // is required for the above bind mount to work.
+                    let crosvm_user_group = CStr::from_bytes_with_nul(b"crosvm\0").unwrap();
+                    let crosvm_uid = match get_user_id(&crosvm_user_group) {
+                        Ok(u) => u,
+                        Err(e) => {
+                            warn!("falling back to current user id for gpu: {:?}", e);
+                            geteuid()
+                        }
+                    };
+                    let crosvm_gid = match get_group_id(&crosvm_user_group) {
+                        Ok(u) => u,
+                        Err(e) => {
+                            warn!("falling back to current group id for gpu: {:?}", e);
+                            getegid()
+                        }
+                    };
+                    jail.change_uid(crosvm_uid);
+                    jail.change_gid(crosvm_gid);
+                    jail.uidmap(&format!("{0} {0} 1", crosvm_uid))
+                        .map_err(Error::SettingUidMap)?;
+                    jail.gidmap(&format!("{0} {0} 1", crosvm_gid))
+                        .map_err(Error::SettingGidMap)?;
+
+                    Some(jail)
+                } else {
+                    None
+                };
+                devs.push(VirtioDeviceStub { dev: gpu_box, jail });
+            }
+        }
+    }
+
     if let Some(wayland_socket_path) = cfg.wayland_socket_path.as_ref() {
         let wayland_socket_dir = wayland_socket_path
             .parent()
@@ -392,6 +484,9 @@ fn create_virtio_devs(
         let jailed_wayland_dir = Path::new("/wayland");
         let jailed_wayland_path = jailed_wayland_dir.join(wayland_socket_name);
 
+        #[cfg(not(feature = "gpu"))]
+        let resource_bridge_wl_socket = None;
+
         let wl_box = Box::new(
             devices::virtio::Wl::new(
                 if cfg.multiprocess {
@@ -400,6 +495,7 @@ fn create_virtio_devs(
                     wayland_socket_path.as_path()
                 },
                 wayland_device_socket,
+                resource_bridge_wl_socket,
             ).map_err(Error::WaylandDeviceNew)?,
         );
 
@@ -474,89 +570,6 @@ fn create_virtio_devs(
         });
     }
 
-    #[cfg(feature = "gpu")]
-    {
-        if cfg.gpu {
-            if let Some(wayland_socket_path) = cfg.wayland_socket_path.as_ref() {
-                let jailed_wayland_path = Path::new("/wayland-0");
-
-                let gpu_box = Box::new(devices::virtio::Gpu::new(
-                    _exit_evt.try_clone().map_err(Error::CloneEventFd)?,
-                    if cfg.multiprocess {
-                        &jailed_wayland_path
-                    } else {
-                        wayland_socket_path.as_path()
-                    },
-                ));
-
-                let jail = if cfg.multiprocess {
-                    let policy_path: PathBuf = cfg.seccomp_policy_dir.join("gpu_device.policy");
-                    let mut jail = create_base_minijail(empty_root_path, &policy_path)?;
-
-                    // Create a tmpfs in the device's root directory so that we can bind mount the
-                    // dri directory into it.  The size=67108864 is size=64*1024*1024 or size=64MB.
-                    jail.mount_with_data(
-                        Path::new("none"),
-                        Path::new("/"),
-                        "tmpfs",
-                        (libc::MS_NOSUID | libc::MS_NODEV | libc::MS_NOEXEC) as usize,
-                        "size=67108864",
-                    ).unwrap();
-
-                    // Device nodes required for DRM.
-                    let sys_dev_char_path = Path::new("/sys/dev/char");
-                    jail.mount_bind(sys_dev_char_path, sys_dev_char_path, false)
-                        .unwrap();
-                    let sys_devices_path = Path::new("/sys/devices");
-                    jail.mount_bind(sys_devices_path, sys_devices_path, false)
-                        .unwrap();
-                    let drm_dri_path = Path::new("/dev/dri");
-                    jail.mount_bind(drm_dri_path, drm_dri_path, false).unwrap();
-
-                    // Libraries that are required when mesa drivers are dynamically loaded.
-                    let lib_path = Path::new("/lib64");
-                    jail.mount_bind(lib_path, lib_path, false).unwrap();
-                    let usr_lib_path = Path::new("/usr/lib64");
-                    jail.mount_bind(usr_lib_path, usr_lib_path, false).unwrap();
-
-                    // Bind mount the wayland socket into jail's root. This is necessary since each
-                    // new wayland context must open() the socket.
-                    jail.mount_bind(wayland_socket_path.as_path(), jailed_wayland_path, true)
-                        .unwrap();
-
-                    // Set the uid/gid for the jailed process, and give a basic id map. This
-                    // is required for the above bind mount to work.
-                    let crosvm_user_group = CStr::from_bytes_with_nul(b"crosvm\0").unwrap();
-                    let crosvm_uid = match get_user_id(&crosvm_user_group) {
-                        Ok(u) => u,
-                        Err(e) => {
-                            warn!("falling back to current user id for gpu: {:?}", e);
-                            geteuid()
-                        }
-                    };
-                    let crosvm_gid = match get_group_id(&crosvm_user_group) {
-                        Ok(u) => u,
-                        Err(e) => {
-                            warn!("falling back to current group id for gpu: {:?}", e);
-                            getegid()
-                        }
-                    };
-                    jail.change_uid(crosvm_uid);
-                    jail.change_gid(crosvm_gid);
-                    jail.uidmap(&format!("{0} {0} 1", crosvm_uid))
-                        .map_err(Error::SettingUidMap)?;
-                    jail.gidmap(&format!("{0} {0} 1", crosvm_gid))
-                        .map_err(Error::SettingGidMap)?;
-
-                    Some(jail)
-                } else {
-                    None
-                };
-                devs.push(VirtioDeviceStub { dev: gpu_box, jail });
-            }
-        }
-    }
-
     let chronos_user_group = CStr::from_bytes_with_nul(b"chronos\0").unwrap();
     let chronos_uid = match get_user_id(&chronos_user_group) {
         Ok(u) => u,