summary refs log tree commit diff
path: root/devices/src/virtio
diff options
context:
space:
mode:
Diffstat (limited to 'devices/src/virtio')
-rw-r--r--devices/src/virtio/console.rs449
-rw-r--r--devices/src/virtio/descriptor_utils.rs15
-rw-r--r--devices/src/virtio/fs/filesystem.rs30
-rw-r--r--devices/src/virtio/fs/fuse.rs91
-rw-r--r--devices/src/virtio/fs/passthrough.rs67
-rw-r--r--devices/src/virtio/fs/server.rs39
-rw-r--r--devices/src/virtio/gpu/mod.rs131
-rw-r--r--devices/src/virtio/gpu/protocol.rs120
-rw-r--r--devices/src/virtio/gpu/virtio_3d_backend.rs269
-rw-r--r--devices/src/virtio/gpu/virtio_gfxstream_backend.rs6
-rw-r--r--devices/src/virtio/interrupt.rs2
-rw-r--r--devices/src/virtio/mod.rs2
-rw-r--r--devices/src/virtio/vhost/control_socket.rs36
-rw-r--r--devices/src/virtio/vhost/mod.rs2
-rw-r--r--devices/src/virtio/vhost/net.rs66
-rw-r--r--devices/src/virtio/vhost/vsock.rs1
-rw-r--r--devices/src/virtio/vhost/worker.rs135
-rw-r--r--devices/src/virtio/virtio_device.rs4
-rw-r--r--devices/src/virtio/virtio_pci_device.rs7
19 files changed, 1155 insertions, 317 deletions
diff --git a/devices/src/virtio/console.rs b/devices/src/virtio/console.rs
new file mode 100644
index 0000000..38f5bf1
--- /dev/null
+++ b/devices/src/virtio/console.rs
@@ -0,0 +1,449 @@
+// Copyright 2020 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.
+
+use std::io::{self, Read};
+use std::os::unix::io::RawFd;
+use std::sync::mpsc::{channel, Receiver, TryRecvError};
+use std::thread;
+
+use data_model::{DataInit, Le16, Le32};
+use sys_util::{error, EventFd, GuestMemory, PollContext, PollToken};
+
+use super::{
+    copy_config, Interrupt, Queue, Reader, VirtioDevice, Writer, TYPE_CONSOLE, VIRTIO_F_VERSION_1,
+};
+
+const QUEUE_SIZE: u16 = 256;
+
+// For now, just implement port 0 (receiveq and transmitq).
+// If VIRTIO_CONSOLE_F_MULTIPORT is implemented, more queues will be needed.
+const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE, QUEUE_SIZE];
+
+#[derive(Copy, Clone, Debug, Default)]
+#[repr(C)]
+struct virtio_console_config {
+    cols: Le16,
+    rows: Le16,
+    max_nr_ports: Le32,
+    emerg_wr: Le32,
+}
+
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for virtio_console_config {}
+
+struct Worker {
+    mem: GuestMemory,
+    interrupt: Interrupt,
+    input: Option<Box<dyn io::Read + Send>>,
+    output: Option<Box<dyn io::Write + Send>>,
+}
+
+fn write_output(output: &mut Box<dyn io::Write>, data: &[u8]) -> io::Result<()> {
+    output.write_all(&data)?;
+    output.flush()
+}
+
+impl Worker {
+    fn process_transmit_request(
+        mut reader: Reader,
+        output: &mut Box<dyn io::Write>,
+    ) -> io::Result<u32> {
+        let len = reader.available_bytes();
+        let mut data = vec![0u8; len];
+        reader.read_exact(&mut data)?;
+        write_output(output, &data)?;
+        Ok(0)
+    }
+
+    fn process_transmit_queue(
+        &mut self,
+        transmit_queue: &mut Queue,
+        output: &mut Box<dyn io::Write>,
+    ) {
+        let mut needs_interrupt = false;
+        while let Some(avail_desc) = transmit_queue.pop(&self.mem) {
+            let desc_index = avail_desc.index;
+
+            let reader = match Reader::new(&self.mem, avail_desc) {
+                Ok(r) => r,
+                Err(e) => {
+                    error!("console: failed to create reader: {}", e);
+                    transmit_queue.add_used(&self.mem, desc_index, 0);
+                    needs_interrupt = true;
+                    continue;
+                }
+            };
+
+            let len = match Self::process_transmit_request(reader, output) {
+                Ok(written) => written,
+                Err(e) => {
+                    error!("console: process_transmit_request failed: {}", e);
+                    0
+                }
+            };
+
+            transmit_queue.add_used(&self.mem, desc_index, len);
+            needs_interrupt = true;
+        }
+
+        if needs_interrupt {
+            self.interrupt.signal_used_queue(transmit_queue.vector);
+        }
+    }
+
+    // Start a thread that reads self.input and sends the input back via the returned channel.
+    //
+    // `in_avail_evt` will be triggered by the thread when new input is available.
+    fn spawn_input_thread(&mut self, in_avail_evt: &EventFd) -> Option<Receiver<u8>> {
+        let mut rx = match self.input.take() {
+            Some(input) => input,
+            None => return None,
+        };
+
+        let (send_channel, recv_channel) = channel();
+
+        let thread_in_avail_evt = match in_avail_evt.try_clone() {
+            Ok(evt) => evt,
+            Err(e) => {
+                error!("failed to clone in_avail_evt: {}", e);
+                return None;
+            }
+        };
+
+        // The input thread runs in detached mode and will exit when channel is disconnected because
+        // the console device has been dropped.
+        let res = thread::Builder::new()
+            .name(format!("console_input"))
+            .spawn(move || {
+                let mut rx_buf = [0u8; 1];
+                loop {
+                    match rx.read(&mut rx_buf) {
+                        Ok(0) => break, // Assume the stream of input has ended.
+                        Ok(_) => {
+                            if send_channel.send(rx_buf[0]).is_err() {
+                                // The receiver has disconnected.
+                                break;
+                            }
+                            thread_in_avail_evt.write(1).unwrap();
+                        }
+                        Err(e) => {
+                            // Being interrupted is not an error, but everything else is.
+                            if e.kind() != io::ErrorKind::Interrupted {
+                                error!(
+                                    "failed to read for bytes to queue into console device: {}",
+                                    e
+                                );
+                                break;
+                            }
+                        }
+                    }
+                }
+            });
+        if let Err(e) = res {
+            error!("failed to spawn input thread: {}", e);
+            return None;
+        }
+        Some(recv_channel)
+    }
+
+    // Check for input from `in_channel_opt` and transfer it to the receive queue, if any.
+    fn handle_input(
+        &mut self,
+        in_channel_opt: &mut Option<Receiver<u8>>,
+        receive_queue: &mut Queue,
+    ) {
+        let in_channel = match in_channel_opt.as_ref() {
+            Some(v) => v,
+            None => return,
+        };
+
+        while let Some(desc) = receive_queue.peek(&self.mem) {
+            let desc_index = desc.index;
+            let mut writer = match Writer::new(&self.mem, desc) {
+                Ok(w) => w,
+                Err(e) => {
+                    error!("console: failed to create Writer: {}", e);
+                    break;
+                }
+            };
+
+            let mut disconnected = false;
+            while writer.available_bytes() > 0 {
+                match in_channel.try_recv() {
+                    Ok(byte) => {
+                        writer.write_obj(byte).unwrap();
+                    }
+                    Err(TryRecvError::Empty) => break,
+                    Err(TryRecvError::Disconnected) => {
+                        disconnected = true;
+                        break;
+                    }
+                }
+            }
+
+            let bytes_written = writer.bytes_written() as u32;
+
+            if bytes_written > 0 {
+                receive_queue.pop_peeked(&self.mem);
+                receive_queue.add_used(&self.mem, desc_index, bytes_written);
+                self.interrupt.signal_used_queue(receive_queue.vector);
+            }
+
+            if disconnected {
+                // Set in_channel to None so that future handle_input calls exit early.
+                in_channel_opt.take();
+                return;
+            }
+
+            if bytes_written == 0 {
+                break;
+            }
+        }
+    }
+
+    fn run(&mut self, mut queues: Vec<Queue>, mut queue_evts: Vec<EventFd>, kill_evt: EventFd) {
+        #[derive(PollToken)]
+        enum Token {
+            ReceiveQueueAvailable,
+            TransmitQueueAvailable,
+            InputAvailable,
+            InterruptResample,
+            Kill,
+        }
+
+        // Device -> driver
+        let (mut receive_queue, receive_evt) = (queues.remove(0), queue_evts.remove(0));
+
+        // Driver -> device
+        let (mut transmit_queue, transmit_evt) = (queues.remove(0), queue_evts.remove(0));
+
+        let in_avail_evt = match EventFd::new() {
+            Ok(evt) => evt,
+            Err(e) => {
+                error!("failed creating EventFd: {}", e);
+                return;
+            }
+        };
+
+        // Spawn a separate thread to poll self.input.
+        // A thread is used because io::Read only provides a blocking interface, and there is no
+        // generic way to add an io::Read instance to a poll context (it may not be backed by a file
+        // descriptor).  Moving the blocking read call to a separate thread and sending data back to
+        // the main worker thread with an event for notification bridges this gap.
+        let mut in_channel = self.spawn_input_thread(&in_avail_evt);
+
+        let poll_ctx: PollContext<Token> = match PollContext::build_with(&[
+            (&transmit_evt, Token::TransmitQueueAvailable),
+            (&receive_evt, Token::ReceiveQueueAvailable),
+            (&in_avail_evt, Token::InputAvailable),
+            (self.interrupt.get_resample_evt(), Token::InterruptResample),
+            (&kill_evt, Token::Kill),
+        ]) {
+            Ok(pc) => pc,
+            Err(e) => {
+                error!("failed creating PollContext: {}", e);
+                return;
+            }
+        };
+
+        let mut output: Box<dyn io::Write> = match self.output.take() {
+            Some(o) => o,
+            None => Box::new(io::sink()),
+        };
+
+        'poll: loop {
+            let events = match poll_ctx.wait() {
+                Ok(v) => v,
+                Err(e) => {
+                    error!("failed polling for events: {}", e);
+                    break;
+                }
+            };
+
+            for event in events.iter_readable() {
+                match event.token() {
+                    Token::TransmitQueueAvailable => {
+                        if let Err(e) = transmit_evt.read() {
+                            error!("failed reading transmit queue EventFd: {}", e);
+                            break 'poll;
+                        }
+                        self.process_transmit_queue(&mut transmit_queue, &mut output);
+                    }
+                    Token::ReceiveQueueAvailable => {
+                        if let Err(e) = receive_evt.read() {
+                            error!("failed reading receive queue EventFd: {}", e);
+                            break 'poll;
+                        }
+                        self.handle_input(&mut in_channel, &mut receive_queue);
+                    }
+                    Token::InputAvailable => {
+                        if let Err(e) = in_avail_evt.read() {
+                            error!("failed reading in_avail_evt: {}", e);
+                            break 'poll;
+                        }
+                        self.handle_input(&mut in_channel, &mut receive_queue);
+                    }
+                    Token::InterruptResample => {
+                        self.interrupt.interrupt_resample();
+                    }
+                    Token::Kill => break 'poll,
+                }
+            }
+        }
+    }
+}
+
+/// Virtio console device.
+pub struct Console {
+    kill_evt: Option<EventFd>,
+    worker_thread: Option<thread::JoinHandle<Worker>>,
+    input: Option<Box<dyn io::Read + Send>>,
+    output: Option<Box<dyn io::Write + Send>>,
+    keep_fds: Vec<RawFd>,
+}
+
+impl Console {
+    fn new(
+        input: Option<Box<dyn io::Read + Send>>,
+        output: Option<Box<dyn io::Write + Send>>,
+        keep_fds: Vec<RawFd>,
+    ) -> Console {
+        Console {
+            kill_evt: None,
+            worker_thread: None,
+            input,
+            output,
+            keep_fds,
+        }
+    }
+
+    /// Constructs a console with input and output streams.
+    pub fn new_in_out(
+        input: Box<dyn io::Read + Send>,
+        out: Box<dyn io::Write + Send>,
+        keep_fds: Vec<RawFd>,
+    ) -> Console {
+        Self::new(Some(input), Some(out), keep_fds)
+    }
+
+    /// Constructs a console with an output stream but no input.
+    pub fn new_out(out: Box<dyn io::Write + Send>, keep_fds: Vec<RawFd>) -> Console {
+        Self::new(None, Some(out), keep_fds)
+    }
+
+    /// Constructs a console with no connected input or output.
+    pub fn new_sink() -> Console {
+        Self::new(None, None, Vec::new())
+    }
+}
+
+impl Drop for Console {
+    fn drop(&mut self) {
+        if let Some(kill_evt) = self.kill_evt.take() {
+            // Ignore the result because there is nothing we can do about it.
+            let _ = kill_evt.write(1);
+        }
+
+        if let Some(worker_thread) = self.worker_thread.take() {
+            let _ = worker_thread.join();
+        }
+    }
+}
+
+impl VirtioDevice for Console {
+    fn keep_fds(&self) -> Vec<RawFd> {
+        self.keep_fds.clone()
+    }
+
+    fn features(&self) -> u64 {
+        1 << VIRTIO_F_VERSION_1
+    }
+
+    fn device_type(&self) -> u32 {
+        TYPE_CONSOLE
+    }
+
+    fn queue_max_sizes(&self) -> &[u16] {
+        QUEUE_SIZES
+    }
+
+    fn read_config(&self, offset: u64, data: &mut [u8]) {
+        let config = virtio_console_config {
+            max_nr_ports: 1.into(),
+            ..Default::default()
+        };
+        copy_config(data, 0, config.as_slice(), offset);
+    }
+
+    fn activate(
+        &mut self,
+        mem: GuestMemory,
+        interrupt: Interrupt,
+        queues: Vec<Queue>,
+        queue_evts: Vec<EventFd>,
+    ) {
+        if queues.len() < 2 || queue_evts.len() < 2 {
+            return;
+        }
+
+        let (self_kill_evt, kill_evt) = match EventFd::new().and_then(|e| Ok((e.try_clone()?, e))) {
+            Ok(v) => v,
+            Err(e) => {
+                error!("failed creating kill EventFd pair: {}", e);
+                return;
+            }
+        };
+        self.kill_evt = Some(self_kill_evt);
+
+        let input = self.input.take();
+        let output = self.output.take();
+
+        let worker_result = thread::Builder::new()
+            .name("virtio_console".to_string())
+            .spawn(move || {
+                let mut worker = Worker {
+                    mem,
+                    interrupt,
+                    input,
+                    output,
+                };
+                worker.run(queues, queue_evts, kill_evt);
+                worker
+            });
+
+        match worker_result {
+            Err(e) => {
+                error!("failed to spawn virtio_console worker: {}", e);
+                return;
+            }
+            Ok(join_handle) => {
+                self.worker_thread = Some(join_handle);
+            }
+        }
+    }
+
+    fn reset(&mut self) -> bool {
+        if let Some(kill_evt) = self.kill_evt.take() {
+            if kill_evt.write(1).is_err() {
+                error!("{}: failed to notify the kill event", self.debug_label());
+                return false;
+            }
+        }
+
+        if let Some(worker_thread) = self.worker_thread.take() {
+            match worker_thread.join() {
+                Err(_) => {
+                    error!("{}: failed to get back resources", self.debug_label());
+                    return false;
+                }
+                Ok(worker) => {
+                    self.input = worker.input;
+                    self.output = worker.output;
+                    return true;
+                }
+            }
+        }
+        false
+    }
+}
diff --git a/devices/src/virtio/descriptor_utils.rs b/devices/src/virtio/descriptor_utils.rs
index 723b0c6..990f147 100644
--- a/devices/src/virtio/descriptor_utils.rs
+++ b/devices/src/virtio/descriptor_utils.rs
@@ -216,8 +216,8 @@ pub struct Reader<'a> {
     buffer: DescriptorChainConsumer<'a>,
 }
 
-// An iterator over `DataInit` objects on readable descriptors in the descriptor chain.
-struct ReaderIterator<'a, T: DataInit> {
+/// An iterator over `DataInit` objects on readable descriptors in the descriptor chain.
+pub struct ReaderIterator<'a, T: DataInit> {
     reader: &'a mut Reader<'a>,
     phantom: PhantomData<T>,
 }
@@ -283,10 +283,17 @@ impl<'a> Reader<'a> {
     /// them as a collection. Returns an error if the size of the remaining data is indivisible by
     /// the size of an object of type `T`.
     pub fn collect<C: FromIterator<io::Result<T>>, T: DataInit>(&'a mut self) -> C {
-        C::from_iter(ReaderIterator {
+        C::from_iter(self.iter())
+    }
+
+    /// Creates an iterator for sequentially reading `DataInit` objects from the `Reader`. Unlike
+    /// `collect`, this doesn't consume all the remaining data in the `Reader` and doesn't require
+    /// the objects to be stored in a separate collection.
+    pub fn iter<T: DataInit>(&'a mut self) -> ReaderIterator<'a, T> {
+        ReaderIterator {
             reader: self,
             phantom: PhantomData,
-        })
+        }
     }
 
     /// Reads data from the descriptor chain buffer into a file descriptor.
diff --git a/devices/src/virtio/fs/filesystem.rs b/devices/src/virtio/fs/filesystem.rs
index 232ff99..eb9726c 100644
--- a/devices/src/virtio/fs/filesystem.rs
+++ b/devices/src/virtio/fs/filesystem.rs
@@ -1139,4 +1139,34 @@ pub trait FileSystem {
     fn lseek(&self) -> io::Result<()> {
         Err(io::Error::from_raw_os_error(libc::ENOSYS))
     }
+
+    /// Copy a range of data from one file to another
+    ///
+    /// Performs an optimized copy between two file descriptors without the additional cost of
+    /// transferring data through the kernel module to user space (glibc) and then back into
+    /// the file system again.
+    ///
+    /// In case this method is not implemented, glibc falls back to reading data from the source and
+    /// writing to the destination.
+    ///
+    /// If this method fails with an `ENOSYS` error, then the kernel will treat that as a permanent
+    /// failure. The kernel will return `EOPNOTSUPP` for all future calls to `copy_file_range`
+    /// without forwarding them to the file system.
+    ///
+    /// All values accepted by the `copy_file_range(2)` system call are valid values for `flags` and
+    /// must be handled by the file system.
+    fn copy_file_range(
+        &self,
+        ctx: Context,
+        inode_src: Self::Inode,
+        handle_src: Self::Handle,
+        offset_src: u64,
+        inode_dst: Self::Inode,
+        handle_dst: Self::Handle,
+        offset_dst: u64,
+        length: u64,
+        flags: u64,
+    ) -> io::Result<usize> {
+        Err(io::Error::from_raw_os_error(libc::ENOSYS))
+    }
 }
diff --git a/devices/src/virtio/fs/fuse.rs b/devices/src/virtio/fs/fuse.rs
index 612202b..5c531cf 100644
--- a/devices/src/virtio/fs/fuse.rs
+++ b/devices/src/virtio/fs/fuse.rs
@@ -12,8 +12,11 @@ use libc;
 /// Version number of this interface.
 pub const KERNEL_VERSION: u32 = 7;
 
+/// Oldest supported minor version of the fuse interface.
+pub const OLDEST_SUPPORTED_KERNEL_MINOR_VERSION: u32 = 27;
+
 /// Minor version number of this interface.
-pub const KERNEL_MINOR_VERSION: u32 = 27;
+pub const KERNEL_MINOR_VERSION: u32 = 31;
 
 /// The ID of the inode corresponding to the root directory of the file system.
 pub const ROOT_ID: u64 = 1;
@@ -56,6 +59,12 @@ const FOPEN_KEEP_CACHE: u32 = 2;
 /// The file is not seekable.
 const FOPEN_NONSEEKABLE: u32 = 4;
 
+/// Allow caching the directory entries.
+const FOPEN_CACHE_DIR: u32 = 8;
+
+/// This file is stream-like (i.e., no file position).
+const FOPEN_STREAM: u32 = 16;
+
 bitflags! {
     /// Options controlling the behavior of files opened by the server in response
     /// to an open or create request.
@@ -63,6 +72,8 @@ bitflags! {
         const DIRECT_IO = FOPEN_DIRECT_IO;
         const KEEP_CACHE = FOPEN_KEEP_CACHE;
         const NONSEEKABLE = FOPEN_NONSEEKABLE;
+        const CACHE_DIR = FOPEN_CACHE_DIR;
+        const STREAM = FOPEN_STREAM;
     }
 }
 
@@ -131,6 +142,24 @@ const HANDLE_KILLPRIV: u32 = 524288;
 /// FileSystem supports posix acls.
 const POSIX_ACL: u32 = 1048576;
 
+/// Reading the device after an abort returns `ECONNABORTED`.
+const ABORT_ERROR: u32 = 2097152;
+
+/// The reply to the `init` message contains the max number of request pages.
+const MAX_PAGES: u32 = 4194304;
+
+/// Cache `readlink` responses.
+const CACHE_SYMLINKS: u32 = 8388608;
+
+/// Kernel supports zero-message opens for directories.
+const NO_OPENDIR_SUPPORT: u32 = 16777216;
+
+/// Kernel supports explicit cache invalidation.
+const EXPLICIT_INVAL_DATA: u32 = 33554432;
+
+/// The `map_alignment` field of the `InitOut` struct is valid.
+const MAP_ALIGNMENT: u32 = 67108864;
+
 bitflags! {
     /// A bitfield passed in as a parameter to and returned from the `init` method of the
     /// `FileSystem` trait.
@@ -297,6 +326,37 @@ bitflags! {
         ///
         /// This feature is disabled by default.
         const POSIX_ACL = POSIX_ACL;
+
+        /// Indicates that the kernel may cache responses to `readlink` calls.
+        const CACHE_SYMLINKS = CACHE_SYMLINKS;
+
+        /// Indicates support for zero-message opens for directories. If this flag is set in the
+        /// `capable` parameter of the `init` trait method, then the file system may return `ENOSYS`
+        /// from the opendir() handler to indicate success. Further attempts to open directories
+        /// will be handled in the kernel. (If this flag is not set, returning ENOSYS will be
+        /// treated as an error and signaled to the caller).
+        ///
+        /// Setting (or not setting) the field in the `FsOptions` returned from the `init` method
+        /// has no effect.
+        const ZERO_MESSAGE_OPENDIR = NO_OPENDIR_SUPPORT;
+
+        /// Indicates support for invalidating cached pages only on explicit request.
+        ///
+        /// If this flag is set in the `capable` parameter of the `init` trait method, then the FUSE
+        /// kernel module supports invalidating cached pages only on explicit request by the
+        /// filesystem.
+        ///
+        /// By setting this flag in the return value of the `init` trait method, the filesystem is
+        /// responsible for invalidating cached pages through explicit requests to the kernel.
+        ///
+        /// Note that setting this flag does not prevent the cached pages from being flushed by OS
+        /// itself and/or through user actions.
+        ///
+        /// Note that if both EXPLICIT_INVAL_DATA and AUTO_INVAL_DATA are set in the `capable`
+        /// parameter of the `init` trait method then AUTO_INVAL_DATA takes precedence.
+        ///
+        /// This feature is disabled by default.
+        const EXPLICIT_INVAL_DATA = EXPLICIT_INVAL_DATA;
     }
 }
 
@@ -318,6 +378,9 @@ pub const WRITE_CACHE: u32 = 1;
 /// `lock_owner` field is valid.
 pub const WRITE_LOCKOWNER: u32 = 2;
 
+/// Kill the suid and sgid bits.
+pub const WRITE_KILL_PRIV: u32 = 3;
+
 // Read flags.
 pub const READ_LOCKOWNER: u32 = 2;
 
@@ -338,6 +401,9 @@ const IOCTL_32BIT: u32 = 8;
 /// Is a directory
 const IOCTL_DIR: u32 = 16;
 
+/// 32-bit compat ioctl on 64-bit machine with 64-bit time_t.
+const IOCTL_COMPAT_X32: u32 = 32;
+
 /// Maximum of in_iovecs + out_iovecs
 pub const IOCTL_MAX_IOV: usize = 256;
 
@@ -357,6 +423,9 @@ bitflags! {
 
         /// Is a directory
         const DIR = IOCTL_DIR;
+
+        /// 32-bit compat ioctl on 64-bit machine with 64-bit time_t.
+        const COMPAT_X32 = IOCTL_COMPAT_X32;
     }
 }
 
@@ -511,6 +580,9 @@ pub enum Opcode {
     Readdirplus = 44,
     Rename2 = 45,
     Lseek = 46,
+    CopyFileRange = 47,
+    SetUpMapping = 48,
+    RemoveMapping = 49,
 }
 
 #[repr(u32)]
@@ -830,7 +902,9 @@ pub struct InitOut {
     pub congestion_threshold: u16,
     pub max_write: u32,
     pub time_gran: u32,
-    pub unused: [u32; 9],
+    pub max_pages: u16,
+    pub map_alignment: u16,
+    pub unused: [u32; 8],
 }
 unsafe impl DataInit for InitOut {}
 
@@ -1049,3 +1123,16 @@ pub struct LseekOut {
     pub offset: u64,
 }
 unsafe impl DataInit for LseekOut {}
+
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct CopyFileRangeIn {
+    pub fh_src: u64,
+    pub off_src: u64,
+    pub nodeid_dst: u64,
+    pub fh_dst: u64,
+    pub off_dst: u64,
+    pub len: u64,
+    pub flags: u64,
+}
+unsafe impl DataInit for CopyFileRangeIn {}
diff --git a/devices/src/virtio/fs/passthrough.rs b/devices/src/virtio/fs/passthrough.rs
index bdc1c6f..419d466 100644
--- a/devices/src/virtio/fs/passthrough.rs
+++ b/devices/src/virtio/fs/passthrough.rs
@@ -395,7 +395,7 @@ impl PassthroughFs {
             libc::openat(
                 self.proc.as_raw_fd(),
                 pathname.as_ptr(),
-                (flags | libc::O_CLOEXEC) & (!libc::O_NOFOLLOW),
+                (flags | libc::O_CLOEXEC) & !(libc::O_NOFOLLOW | libc::O_DIRECT),
             )
         };
         if fd < 0 {
@@ -592,7 +592,13 @@ impl PassthroughFs {
                 OpenOptions::DIRECT_IO,
                 flags & (libc::O_DIRECTORY as u32) == 0,
             ),
-            CachePolicy::Always => opts |= OpenOptions::KEEP_CACHE,
+            CachePolicy::Always => {
+                opts |= if flags & (libc::O_DIRECTORY as u32) == 0 {
+                    OpenOptions::KEEP_CACHE
+                } else {
+                    OpenOptions::CACHE_DIR
+                }
+            }
             _ => {}
         };
 
@@ -965,7 +971,8 @@ impl FileSystem for PassthroughFs {
             libc::openat(
                 data.file.as_raw_fd(),
                 name.as_ptr(),
-                flags as i32 | libc::O_CREAT | libc::O_CLOEXEC | libc::O_NOFOLLOW,
+                (flags as i32 | libc::O_CREAT | libc::O_CLOEXEC | libc::O_NOFOLLOW)
+                    & !libc::O_DIRECT,
                 mode & !(umask & 0o777),
             )
         };
@@ -1689,4 +1696,58 @@ impl FileSystem for PassthroughFs {
             Err(io::Error::from_raw_os_error(libc::ENOTTY))
         }
     }
+
+    fn copy_file_range(
+        &self,
+        ctx: Context,
+        inode_src: Inode,
+        handle_src: Handle,
+        offset_src: u64,
+        inode_dst: Inode,
+        handle_dst: Handle,
+        offset_dst: u64,
+        length: u64,
+        flags: u64,
+    ) -> io::Result<usize> {
+        // We need to change credentials during a write so that the kernel will remove setuid or
+        // setgid bits from the file if it was written to by someone other than the owner.
+        let (_uid, _gid) = set_creds(ctx.uid, ctx.gid)?;
+        let src_data = self
+            .handles
+            .read()
+            .unwrap()
+            .get(&handle_src)
+            .filter(|hd| hd.inode == inode_src)
+            .map(Arc::clone)
+            .ok_or_else(ebadf)?;
+        let dst_data = self
+            .handles
+            .read()
+            .unwrap()
+            .get(&handle_dst)
+            .filter(|hd| hd.inode == inode_dst)
+            .map(Arc::clone)
+            .ok_or_else(ebadf)?;
+
+        let src = src_data.file.lock().as_raw_fd();
+        let dst = dst_data.file.lock().as_raw_fd();
+
+        let res = unsafe {
+            libc::syscall(
+                libc::SYS_copy_file_range,
+                src,
+                &offset_src,
+                dst,
+                offset_dst,
+                length,
+                flags,
+            )
+        };
+
+        if res >= 0 {
+            Ok(res as usize)
+        } else {
+            Err(io::Error::last_os_error())
+        }
+    }
 }
diff --git a/devices/src/virtio/fs/server.rs b/devices/src/virtio/fs/server.rs
index 32b5008..c9025f2 100644
--- a/devices/src/virtio/fs/server.rs
+++ b/devices/src/virtio/fs/server.rs
@@ -118,7 +118,8 @@ impl<F: FileSystem + Sync> Server<F> {
             Some(Opcode::Readdirplus) => self.readdirplus(in_header, r, w),
             Some(Opcode::Rename2) => self.rename2(in_header, r, w),
             Some(Opcode::Lseek) => self.lseek(in_header, r, w),
-            None => reply_error(
+            Some(Opcode::CopyFileRange) => self.copy_file_range(in_header, r, w),
+            Some(Opcode::SetUpMapping) | Some(Opcode::RemoveMapping) | None => reply_error(
                 io::Error::from_raw_os_error(libc::ENOSYS),
                 in_header.unique,
                 w,
@@ -809,7 +810,7 @@ impl<F: FileSystem + Sync> Server<F> {
             return reply_ok(Some(out), None, in_header.unique, w);
         }
 
-        if minor < KERNEL_MINOR_VERSION {
+        if minor < OLDEST_SUPPORTED_KERNEL_MINOR_VERSION {
             error!(
                 "Unsupported fuse protocol minor version: {}.{}",
                 major, minor
@@ -1208,6 +1209,40 @@ impl<F: FileSystem + Sync> Server<F> {
             Ok(0)
         }
     }
+
+    fn copy_file_range(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result<usize> {
+        let CopyFileRangeIn {
+            fh_src,
+            off_src,
+            nodeid_dst,
+            fh_dst,
+            off_dst,
+            len,
+            flags,
+        } = r.read_obj().map_err(Error::DecodeMessage)?;
+
+        match self.fs.copy_file_range(
+            Context::from(in_header),
+            in_header.nodeid.into(),
+            fh_src.into(),
+            off_src,
+            nodeid_dst.into(),
+            fh_dst.into(),
+            off_dst,
+            len,
+            flags,
+        ) {
+            Ok(count) => {
+                let out = WriteOut {
+                    size: count as u32,
+                    ..Default::default()
+                };
+
+                reply_ok(Some(out), None, in_header.unique, w)
+            }
+            Err(e) => reply_error(e, in_header.unique, w),
+        }
+    }
 }
 
 fn retry_ioctl(
diff --git a/devices/src/virtio/gpu/mod.rs b/devices/src/virtio/gpu/mod.rs
index 8cc211f..93d004f 100644
--- a/devices/src/virtio/gpu/mod.rs
+++ b/devices/src/virtio/gpu/mod.rs
@@ -277,30 +277,24 @@ trait Backend {
         GpuResponse::ErrUnspec
     }
 
-    fn allocation_metadata(
-        &mut self,
-        _request_id: u32,
-        _request: Vec<u8>,
-        mut _response: Vec<u8>,
-    ) -> GpuResponse {
-        GpuResponse::ErrUnspec
-    }
-
     fn resource_create_v2(
         &mut self,
         _resource_id: u32,
-        _guest_memory_type: u32,
-        _guest_caching_type: u32,
+        _ctx_id: u32,
+        _flags: u32,
         _size: u64,
-        _pci_addr: u64,
-        _mem: &GuestMemory,
+        _memory_id: u64,
         _vecs: Vec<(GuestAddress, usize)>,
-        _args: Vec<u8>,
+        _mem: &GuestMemory,
     ) -> GpuResponse {
         GpuResponse::ErrUnspec
     }
 
-    fn resource_v2_unref(&mut self, _resource_id: u32) -> GpuResponse {
+    fn resource_map(&mut self, _resource_id: u32, _pci_addr: u64) -> GpuResponse {
+        GpuResponse::ErrUnspec
+    }
+
+    fn resource_unmap(&mut self, _resource_id: u32) -> GpuResponse {
         GpuResponse::ErrUnspec
     }
 }
@@ -474,19 +468,19 @@ impl Frontend {
                 let available_bytes = reader.available_bytes();
                 if available_bytes != 0 {
                     let entry_count = info.nr_entries.to_native() as usize;
-                    let mut iovecs = Vec::with_capacity(entry_count);
+                    let mut vecs = Vec::with_capacity(entry_count);
                     for _ in 0..entry_count {
                         match reader.read_obj::<virtio_gpu_mem_entry>() {
                             Ok(entry) => {
                                 let addr = GuestAddress(entry.addr.to_native());
                                 let len = entry.length.to_native() as usize;
-                                iovecs.push((addr, len))
+                                vecs.push((addr, len))
                             }
                             Err(_) => return GpuResponse::ErrUnspec,
                         }
                     }
                     self.backend
-                        .attach_backing(info.resource_id.to_native(), mem, iovecs)
+                        .attach_backing(info.resource_id.to_native(), mem, vecs)
                 } else {
                     error!("missing data for command {:?}", cmd);
                     GpuResponse::ErrUnspec
@@ -610,78 +604,49 @@ impl Frontend {
                     GpuResponse::OkNoData
                 }
             }
-            GpuCommand::AllocationMetadata(info) => {
-                if reader.available_bytes() != 0 {
-                    let id = info.request_id.to_native();
-                    let request_size = info.request_size.to_native();
-                    let response_size = info.response_size.to_native();
-                    if request_size > VIRTIO_GPU_MAX_BLOB_ARGUMENT_SIZE
-                        || response_size > VIRTIO_GPU_MAX_BLOB_ARGUMENT_SIZE
-                    {
-                        return GpuResponse::ErrUnspec;
-                    }
-
-                    let mut request_buf = vec![0; request_size as usize];
-                    let response_buf = vec![0; response_size as usize];
-                    if reader.read_exact(&mut request_buf[..]).is_ok() {
-                        self.backend
-                            .allocation_metadata(id, request_buf, response_buf)
-                    } else {
-                        GpuResponse::ErrInvalidParameter
-                    }
-                } else {
-                    GpuResponse::ErrUnspec
-                }
-            }
             GpuCommand::ResourceCreateV2(info) => {
-                if reader.available_bytes() != 0 {
-                    let resource_id = info.resource_id.to_native();
-                    let guest_memory_type = info.guest_memory_type.to_native();
-                    let size = info.size.to_native();
-                    let guest_caching_type = info.guest_caching_type.to_native();
-                    let pci_addr = info.pci_addr.to_native();
-                    let entry_count = info.nr_entries.to_native();
-                    let args_size = info.args_size.to_native();
-                    if args_size > VIRTIO_GPU_MAX_BLOB_ARGUMENT_SIZE
-                        || entry_count > VIRTIO_GPU_MAX_IOVEC_ENTRIES
-                    {
-                        return GpuResponse::ErrUnspec;
-                    }
-
-                    let mut iovecs = Vec::with_capacity(entry_count as usize);
-                    let mut args = vec![0; args_size as usize];
+                let resource_id = info.resource_id.to_native();
+                let ctx_id = info.hdr.ctx_id.to_native();
+                let flags = info.flags.to_native();
+                let size = info.size.to_native();
+                let memory_id = info.memory_id.to_native();
+                let entry_count = info.nr_entries.to_native();
+                if entry_count > VIRTIO_GPU_MAX_IOVEC_ENTRIES
+                    || (reader.available_bytes() == 0 && entry_count > 0)
+                {
+                    return GpuResponse::ErrUnspec;
+                }
 
-                    for _ in 0..entry_count {
-                        match reader.read_obj::<virtio_gpu_mem_entry>() {
-                            Ok(entry) => {
-                                let addr = GuestAddress(entry.addr.to_native());
-                                let len = entry.length.to_native() as usize;
-                                iovecs.push((addr, len))
-                            }
-                            Err(_) => return GpuResponse::ErrUnspec,
+                let mut vecs = Vec::with_capacity(entry_count as usize);
+                for _ in 0..entry_count {
+                    match reader.read_obj::<virtio_gpu_mem_entry>() {
+                        Ok(entry) => {
+                            let addr = GuestAddress(entry.addr.to_native());
+                            let len = entry.length.to_native() as usize;
+                            vecs.push((addr, len))
                         }
+                        Err(_) => return GpuResponse::ErrUnspec,
                     }
-
-                    match reader.read_exact(&mut args[..]) {
-                        Ok(_) => self.backend.resource_create_v2(
-                            resource_id,
-                            guest_memory_type,
-                            guest_caching_type,
-                            size,
-                            pci_addr,
-                            mem,
-                            iovecs,
-                            args,
-                        ),
-                        Err(_) => GpuResponse::ErrUnspec,
-                    }
-                } else {
-                    GpuResponse::ErrUnspec
                 }
+
+                self.backend.resource_create_v2(
+                    resource_id,
+                    ctx_id,
+                    flags,
+                    size,
+                    memory_id,
+                    vecs,
+                    mem,
+                )
+            }
+            GpuCommand::ResourceMap(info) => {
+                let resource_id = info.resource_id.to_native();
+                let offset = info.offset.to_native();
+                self.backend.resource_map(resource_id, offset)
             }
-            GpuCommand::ResourceV2Unref(info) => {
+            GpuCommand::ResourceUnmap(info) => {
                 let resource_id = info.resource_id.to_native();
-                self.backend.resource_v2_unref(resource_id)
+                self.backend.resource_unmap(resource_id)
             }
         }
     }
diff --git a/devices/src/virtio/gpu/protocol.rs b/devices/src/virtio/gpu/protocol.rs
index b4610b2..c98e289 100644
--- a/devices/src/virtio/gpu/protocol.rs
+++ b/devices/src/virtio/gpu/protocol.rs
@@ -19,9 +19,10 @@ use data_model::{DataInit, Le32, Le64};
 pub const VIRTIO_GPU_F_VIRGL: u32 = 0;
 pub const VIRTIO_GPU_F_EDID: u32 = 1;
 /* The following capabilities are not upstreamed. */
-pub const VIRTIO_GPU_F_MEMORY: u32 = 2;
-pub const VIRTIO_GPU_F_SHARED_GUEST: u32 = 3;
-pub const VIRTIO_GPU_F_HOST_COHERENT: u32 = 4;
+pub const VIRTIO_GPU_F_RESOURCE_UUID: u32 = 2;
+pub const VIRTIO_GPU_F_RESOURCE_V2: u32 = 3;
+pub const VIRTIO_GPU_F_HOST_VISIBLE: u32 = 4;
+pub const VIRTIO_GPU_F_VULKAN: u32 = 5;
 
 pub const VIRTIO_GPU_UNDEFINED: u32 = 0x0;
 
@@ -49,8 +50,8 @@ pub const VIRTIO_GPU_CMD_TRANSFER_FROM_HOST_3D: u32 = 0x206;
 pub const VIRTIO_GPU_CMD_SUBMIT_3D: u32 = 0x207;
 /* The following hypercalls are not upstreamed. */
 pub const VIRTIO_GPU_CMD_RESOURCE_CREATE_V2: u32 = 0x208;
-pub const VIRTIO_GPU_CMD_RESOURCE_CREATE_V2_UNREF: u32 = 0x209;
-pub const VIRTIO_GPU_CMD_ALLOCATION_METADATA: u32 = 0x20a;
+pub const VIRTIO_GPU_CMD_RESOURCE_MAP: u32 = 0x209;
+pub const VIRTIO_GPU_CMD_RESOURCE_UNMAP: u32 = 0x20a;
 
 /* cursor commands */
 pub const VIRTIO_GPU_CMD_UPDATE_CURSOR: u32 = 0x300;
@@ -63,7 +64,6 @@ pub const VIRTIO_GPU_RESP_OK_CAPSET_INFO: u32 = 0x1102;
 pub const VIRTIO_GPU_RESP_OK_CAPSET: u32 = 0x1103;
 pub const VIRTIO_GPU_RESP_OK_RESOURCE_PLANE_INFO: u32 = 0x1104;
 pub const VIRTIO_GPU_RESP_OK_EDID: u32 = 0x1105;
-pub const VIRTIO_GPU_RESP_OK_ALLOCATION_METADATA: u32 = 0x1106;
 
 /* error responses */
 pub const VIRTIO_GPU_RESP_ERR_UNSPEC: u32 = 0x1200;
@@ -73,20 +73,22 @@ pub const VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID: u32 = 0x1203;
 pub const VIRTIO_GPU_RESP_ERR_INVALID_CONTEXT_ID: u32 = 0x1204;
 pub const VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER: u32 = 0x1205;
 
-/* guest memory types (not upstreamed) */
-pub const VIRTIO_GPU_MEMORY_UNDEFINED: u32 = 0;
-pub const VIRTIO_GPU_MEMORY_TRANSFER: u32 = 1;
-pub const VIRTIO_GPU_MEMORY_SHARED_GUEST: u32 = 2;
-pub const VIRTIO_GPU_MEMORY_HOST_COHERENT: u32 = 3;
+/* Resource flags (not upstreamed) */
+pub const VIRTIO_GPU_RESOURCE_GUEST_NONE: u32 = 0x0000;
+pub const VIRTIO_GPU_RESOURCE_GUEST_MASK: u32 = 0x000f;
+pub const VIRTIO_GPU_RESOURCE_GUEST_SYSTEM: u32 = 0x0001;
 
-/* guest caching types (not upstreamed) */
-pub const VIRTIO_GPU_UNDEFINED_CACHING: u32 = 0;
-pub const VIRTIO_GPU_CACHED: u32 = 1;
-pub const VIRTIO_GPU_WRITE_COMBINE: u32 = 2;
-pub const VIRTIO_GPU_UNCACHED: u32 = 3;
+pub const VIRTIO_GPU_RESOURCE_HOST_NONE: u32 = 0x0000;
+pub const VIRTIO_GPU_RESOURCE_HOST_MASK: u32 = 0x00f0;
+pub const VIRTIO_GPU_RESOURCE_HOST: u32 = 0x0010;
+pub const VIRTIO_GPU_RESOURCE_HOST_FROM_GUEST: u32 = 0x0020;
+
+pub const VIRTIO_GPU_RESOURCE_USE_NONE: u32 = 0x0000;
+pub const VIRTIO_GPU_RESOURCE_USE_MASK: u32 = 0x0f00;
+pub const VIRTIO_GPU_RESOURCE_USE_MAPPABLE: u32 = 0x0100;
+pub const VIRTIO_GPU_RESOURCE_USE_SHAREABLE: u32 = 0x0200;
+pub const VIRTIO_GPU_RESOURCE_USE_CROSS_DEVICE: u32 = 0x0400;
 
-/* Limits on virtio-gpu stream (not upstreamed) */
-pub const VIRTIO_GPU_MAX_BLOB_ARGUMENT_SIZE: u32 = 4096;
 /* This matches the limit in udmabuf.c */
 pub const VIRTIO_GPU_MAX_IOVEC_ENTRIES: u32 = 1024;
 
@@ -112,8 +114,8 @@ pub fn virtio_gpu_cmd_str(cmd: u32) -> &'static str {
         VIRTIO_GPU_CMD_TRANSFER_FROM_HOST_3D => "VIRTIO_GPU_CMD_TRANSFER_FROM_HOST_3D",
         VIRTIO_GPU_CMD_SUBMIT_3D => "VIRTIO_GPU_CMD_SUBMIT_3D",
         VIRTIO_GPU_CMD_RESOURCE_CREATE_V2 => "VIRTIO_GPU_CMD_RESOURCE_CREATE_V2",
-        VIRTIO_GPU_CMD_RESOURCE_CREATE_V2_UNREF => "VIRTIO_GPU_CMD_RESOURCE_CREATE_V2_UNREF",
-        VIRTIO_GPU_CMD_ALLOCATION_METADATA => "VIRTIO_GPU_CMD_ALLOCATION_METADATA",
+        VIRTIO_GPU_CMD_RESOURCE_MAP => "VIRTIO_GPU_RESOURCE_MAP",
+        VIRTIO_GPU_CMD_RESOURCE_UNMAP => "VIRTIO_GPU_RESOURCE_UNMAP",
         VIRTIO_GPU_CMD_UPDATE_CURSOR => "VIRTIO_GPU_CMD_UPDATE_CURSOR",
         VIRTIO_GPU_CMD_MOVE_CURSOR => "VIRTIO_GPU_CMD_MOVE_CURSOR",
         VIRTIO_GPU_RESP_OK_NODATA => "VIRTIO_GPU_RESP_OK_NODATA",
@@ -497,52 +499,38 @@ unsafe impl DataInit for virtio_gpu_config {}
 
 #[derive(Copy, Clone, Debug, Default)]
 #[repr(C)]
-pub struct virtio_gpu_allocation_metadata {
+pub struct virtio_gpu_resource_create_v2 {
     pub hdr: virtio_gpu_ctrl_hdr,
-    pub request_id: Le32,
+    pub resource_id: Le32,
+    pub flags: Le32,
+    pub size: Le64,
+    pub memory_id: Le64,
+    pub nr_entries: Le32,
     pub padding: Le32,
-    pub request_size: Le32,
-    pub response_size: Le32,
 }
 
-unsafe impl DataInit for virtio_gpu_allocation_metadata {}
-
-/* VIRTIO_GPU_RESP_OK_ALLOCATION_METADATA */
-#[derive(Copy, Clone, Debug, Default)]
-#[repr(C)]
-pub struct virtio_gpu_resp_allocation_metadata {
-    pub hdr: virtio_gpu_ctrl_hdr,
-    pub request_id: Le32,
-    pub response_size: Le32,
-}
-
-unsafe impl DataInit for virtio_gpu_resp_allocation_metadata {}
+unsafe impl DataInit for virtio_gpu_resource_create_v2 {}
 
 #[derive(Copy, Clone, Debug, Default)]
 #[repr(C)]
-pub struct virtio_gpu_resource_create_v2 {
+pub struct virtio_gpu_resource_map {
     pub hdr: virtio_gpu_ctrl_hdr,
     pub resource_id: Le32,
-    pub guest_memory_type: Le32,
-    pub guest_caching_type: Le32,
-    pub padding: Le32,
-    pub size: Le64,
-    pub pci_addr: Le64,
-    pub args_size: Le32,
-    pub nr_entries: Le32,
+    pub map_flags: Le32,
+    pub offset: Le64,
 }
 
-unsafe impl DataInit for virtio_gpu_resource_create_v2 {}
+unsafe impl DataInit for virtio_gpu_resource_map {}
 
 #[derive(Copy, Clone, Debug, Default)]
 #[repr(C)]
-pub struct virtio_gpu_resource_v2_unref {
+pub struct virtio_gpu_resource_unmap {
     pub hdr: virtio_gpu_ctrl_hdr,
     pub resource_id: Le32,
     pub padding: Le32,
 }
 
-unsafe impl DataInit for virtio_gpu_resource_v2_unref {}
+unsafe impl DataInit for virtio_gpu_resource_unmap {}
 
 /* simple formats for fbcon/X use */
 pub const VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM: u32 = 1;
@@ -576,8 +564,8 @@ pub enum GpuCommand {
     TransferFromHost3d(virtio_gpu_transfer_host_3d),
     CmdSubmit3d(virtio_gpu_cmd_submit),
     ResourceCreateV2(virtio_gpu_resource_create_v2),
-    ResourceV2Unref(virtio_gpu_resource_v2_unref),
-    AllocationMetadata(virtio_gpu_allocation_metadata),
+    ResourceMap(virtio_gpu_resource_map),
+    ResourceUnmap(virtio_gpu_resource_unmap),
     UpdateCursor(virtio_gpu_update_cursor),
     MoveCursor(virtio_gpu_update_cursor),
 }
@@ -645,8 +633,8 @@ impl fmt::Debug for GpuCommand {
             TransferFromHost3d(_info) => f.debug_struct("TransferFromHost3d").finish(),
             CmdSubmit3d(_info) => f.debug_struct("CmdSubmit3d").finish(),
             ResourceCreateV2(_info) => f.debug_struct("ResourceCreateV2").finish(),
-            ResourceV2Unref(_info) => f.debug_struct("ResourceV2Unref").finish(),
-            AllocationMetadata(_info) => f.debug_struct("AllocationMetadata").finish(),
+            ResourceMap(_info) => f.debug_struct("ResourceMap").finish(),
+            ResourceUnmap(_info) => f.debug_struct("ResourceUnmap").finish(),
             UpdateCursor(_info) => f.debug_struct("UpdateCursor").finish(),
             MoveCursor(_info) => f.debug_struct("MoveCursor").finish(),
         }
@@ -678,8 +666,8 @@ impl GpuCommand {
             VIRTIO_GPU_CMD_TRANSFER_FROM_HOST_3D => TransferFromHost3d(cmd.read_obj()?),
             VIRTIO_GPU_CMD_SUBMIT_3D => CmdSubmit3d(cmd.read_obj()?),
             VIRTIO_GPU_CMD_RESOURCE_CREATE_V2 => ResourceCreateV2(cmd.read_obj()?),
-            VIRTIO_GPU_CMD_RESOURCE_CREATE_V2_UNREF => ResourceV2Unref(cmd.read_obj()?),
-            VIRTIO_GPU_CMD_ALLOCATION_METADATA => AllocationMetadata(cmd.read_obj()?),
+            VIRTIO_GPU_CMD_RESOURCE_MAP => ResourceMap(cmd.read_obj()?),
+            VIRTIO_GPU_CMD_RESOURCE_UNMAP => ResourceUnmap(cmd.read_obj()?),
             VIRTIO_GPU_CMD_UPDATE_CURSOR => UpdateCursor(cmd.read_obj()?),
             VIRTIO_GPU_CMD_MOVE_CURSOR => MoveCursor(cmd.read_obj()?),
             _ => return Err(GpuCommandDecodeError::InvalidType(hdr.type_.into())),
@@ -709,8 +697,8 @@ impl GpuCommand {
             TransferFromHost3d(info) => &info.hdr,
             CmdSubmit3d(info) => &info.hdr,
             ResourceCreateV2(info) => &info.hdr,
-            ResourceV2Unref(info) => &info.hdr,
-            AllocationMetadata(info) => &info.hdr,
+            ResourceMap(info) => &info.hdr,
+            ResourceUnmap(info) => &info.hdr,
             UpdateCursor(info) => &info.hdr,
             MoveCursor(info) => &info.hdr,
         }
@@ -723,12 +711,6 @@ pub struct GpuResponsePlaneInfo {
     pub offset: u32,
 }
 
-#[derive(Default, Debug, PartialEq)]
-pub struct AllocationMetadataResponse {
-    pub request_id: u32,
-    pub response: Vec<u8>,
-}
-
 /// A response to a `GpuCommand`. These correspond to `VIRTIO_GPU_RESP_*`.
 #[derive(Debug, PartialEq)]
 pub enum GpuResponse {
@@ -744,9 +726,6 @@ pub enum GpuResponse {
         format_modifier: u64,
         plane_info: Vec<GpuResponsePlaneInfo>,
     },
-    OkAllocationMetadata {
-        res_info: AllocationMetadataResponse,
-    },
     ErrUnspec,
     ErrOutOfMemory,
     ErrInvalidScanoutId,
@@ -880,17 +859,6 @@ impl GpuResponse {
                     size_of_val(&hdr)
                 }
             }
-            GpuResponse::OkAllocationMetadata { ref res_info } => {
-                let resp_info = virtio_gpu_resp_allocation_metadata {
-                    hdr,
-                    request_id: Le32::from(res_info.request_id),
-                    response_size: Le32::from(res_info.response.len() as u32),
-                };
-
-                resp.write_obj(resp_info)?;
-                resp.write_all(&res_info.response)?;
-                size_of_val(&resp_info) + res_info.response.len()
-            }
             _ => {
                 resp.write_obj(hdr)?;
                 size_of_val(&hdr)
@@ -907,7 +875,6 @@ impl GpuResponse {
             GpuResponse::OkCapsetInfo { .. } => VIRTIO_GPU_RESP_OK_CAPSET_INFO,
             GpuResponse::OkCapset(_) => VIRTIO_GPU_RESP_OK_CAPSET,
             GpuResponse::OkResourcePlaneInfo { .. } => VIRTIO_GPU_RESP_OK_RESOURCE_PLANE_INFO,
-            GpuResponse::OkAllocationMetadata { .. } => VIRTIO_GPU_RESP_OK_ALLOCATION_METADATA,
             GpuResponse::ErrUnspec => VIRTIO_GPU_RESP_ERR_UNSPEC,
             GpuResponse::ErrOutOfMemory => VIRTIO_GPU_RESP_ERR_OUT_OF_MEMORY,
             GpuResponse::ErrInvalidScanoutId => VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID,
@@ -925,7 +892,6 @@ impl GpuResponse {
             GpuResponse::OkCapsetInfo { .. } => true,
             GpuResponse::OkCapset(_) => true,
             GpuResponse::OkResourcePlaneInfo { .. } => true,
-            GpuResponse::OkAllocationMetadata { .. } => true,
             _ => false,
         }
     }
diff --git a/devices/src/virtio/gpu/virtio_3d_backend.rs b/devices/src/virtio/gpu/virtio_3d_backend.rs
index 7ae044c..3ede8a6 100644
--- a/devices/src/virtio/gpu/virtio_3d_backend.rs
+++ b/devices/src/virtio/gpu/virtio_3d_backend.rs
@@ -26,13 +26,13 @@ use gpu_renderer::{
 };
 
 use super::protocol::{
-    AllocationMetadataResponse, GpuResponse, GpuResponsePlaneInfo, VIRTIO_GPU_CAPSET3,
-    VIRTIO_GPU_CAPSET_VIRGL, VIRTIO_GPU_CAPSET_VIRGL2, VIRTIO_GPU_MEMORY_HOST_COHERENT,
+    GpuResponse, GpuResponsePlaneInfo, VIRTIO_GPU_CAPSET3, VIRTIO_GPU_CAPSET_VIRGL,
+    VIRTIO_GPU_CAPSET_VIRGL2, VIRTIO_GPU_RESOURCE_USE_MAPPABLE, VIRTIO_GPU_RESOURCE_USE_MASK,
 };
 pub use crate::virtio::gpu::virtio_backend::{VirtioBackend, VirtioResource};
 use crate::virtio::gpu::{
-    Backend, DisplayBackend, VIRTIO_F_VERSION_1, VIRTIO_GPU_F_HOST_COHERENT, VIRTIO_GPU_F_MEMORY,
-    VIRTIO_GPU_F_VIRGL,
+    Backend, DisplayBackend, VIRTIO_F_VERSION_1, VIRTIO_GPU_F_HOST_VISIBLE,
+    VIRTIO_GPU_F_RESOURCE_UUID, VIRTIO_GPU_F_RESOURCE_V2, VIRTIO_GPU_F_VIRGL, VIRTIO_GPU_F_VULKAN,
 };
 use crate::virtio::resource_bridge::{PlaneInfo, ResourceInfo, ResourceResponse};
 
@@ -44,6 +44,8 @@ struct Virtio3DResource {
     gpu_resource: GpuRendererResource,
     display_import: Option<(Rc<RefCell<GpuDisplay>>, u32)>,
     kvm_slot: Option<u32>,
+    size: u64,
+    flags: u32,
 }
 
 impl Virtio3DResource {
@@ -54,27 +56,38 @@ impl Virtio3DResource {
             gpu_resource,
             display_import: None,
             kvm_slot: None,
+            flags: 0,
+            // The size of the host resource isn't really zero, but it's undefined by
+            // virtio_gpu_resource_create_3d
+            size: 0,
         }
     }
 
     pub fn v2_new(
         width: u32,
         height: u32,
-        kvm_slot: u32,
         gpu_resource: GpuRendererResource,
+        flags: u32,
+        size: u64,
     ) -> Virtio3DResource {
         Virtio3DResource {
             width,
             height,
             gpu_resource,
             display_import: None,
-            kvm_slot: Some(kvm_slot),
+            kvm_slot: None,
+            flags,
+            size,
         }
     }
 
     fn as_mut(&mut self) -> &mut dyn VirtioResource {
         self
     }
+
+    fn use_flags(&self) -> u32 {
+        self.flags & VIRTIO_GPU_RESOURCE_USE_MASK
+    }
 }
 
 impl VirtioResource for Virtio3DResource {
@@ -225,8 +238,10 @@ impl Backend for Virtio3DBackend {
     fn features() -> u64 {
         1 << VIRTIO_GPU_F_VIRGL
             | 1 << VIRTIO_F_VERSION_1
-            | 1 << VIRTIO_GPU_F_MEMORY
-            | 1 << VIRTIO_GPU_F_HOST_COHERENT
+            | 1 << VIRTIO_GPU_F_RESOURCE_UUID
+            | 1 << VIRTIO_GPU_F_RESOURCE_V2
+            | 1 << VIRTIO_GPU_F_HOST_VISIBLE
+            | 1 << VIRTIO_GPU_F_VULKAN
     }
 
     /// Returns the underlying Backend.
@@ -757,51 +772,26 @@ impl Backend for Virtio3DBackend {
         }
     }
 
-    fn allocation_metadata(
-        &mut self,
-        request_id: u32,
-        request: Vec<u8>,
-        mut response: Vec<u8>,
-    ) -> GpuResponse {
-        let res = self.renderer.allocation_metadata(&request, &mut response);
-
-        match res {
-            Ok(_) => {
-                let res_info = AllocationMetadataResponse {
-                    request_id,
-                    response,
-                };
-
-                GpuResponse::OkAllocationMetadata { res_info }
-            }
-            Err(_) => {
-                error!("failed to get metadata");
-                GpuResponse::ErrUnspec
-            }
-        }
-    }
-
     fn resource_create_v2(
         &mut self,
         resource_id: u32,
-        guest_memory_type: u32,
-        guest_caching_type: u32,
+        ctx_id: u32,
+        flags: u32,
         size: u64,
-        pci_addr: u64,
-        mem: &GuestMemory,
+        memory_id: u64,
         vecs: Vec<(GuestAddress, usize)>,
-        args: Vec<u8>,
+        mem: &GuestMemory,
     ) -> GpuResponse {
         match self.resources.entry(resource_id) {
             Entry::Vacant(entry) => {
                 let resource = match self.renderer.resource_create_v2(
                     resource_id,
-                    guest_memory_type,
-                    guest_caching_type,
+                    ctx_id,
+                    flags,
                     size,
-                    mem,
+                    memory_id,
                     &vecs,
-                    &args,
+                    mem,
                 ) {
                     Ok(resource) => resource,
                     Err(e) => {
@@ -810,102 +800,119 @@ impl Backend for Virtio3DBackend {
                     }
                 };
 
-                match guest_memory_type {
-                    VIRTIO_GPU_MEMORY_HOST_COHERENT => {
-                        let dma_buf_fd = match resource.export() {
-                            Ok(export) => export.1,
-                            Err(e) => {
-                                error!("failed to export plane fd: {}", e);
-                                return GpuResponse::ErrUnspec;
-                            }
-                        };
-
-                        let request = VmMemoryRequest::RegisterMemoryAtAddress(
-                            self.pci_bar,
-                            MaybeOwnedFd::Borrowed(dma_buf_fd.as_raw_fd()),
-                            size as usize,
-                            pci_addr,
-                        );
-
-                        match self.gpu_device_socket.send(&request) {
-                            Ok(_resq) => match self.gpu_device_socket.recv() {
-                                Ok(response) => match response {
-                                    VmMemoryResponse::RegisterMemory { pfn: _, slot } => {
-                                        entry.insert(Virtio3DResource::v2_new(
-                                            self.base.display_width,
-                                            self.base.display_height,
-                                            slot,
-                                            resource,
-                                        ));
-                                        GpuResponse::OkNoData
-                                    }
-                                    VmMemoryResponse::Err(e) => {
-                                        error!("received an error: {}", e);
-                                        GpuResponse::ErrUnspec
-                                    }
-                                    _ => {
-                                        error!("recieved an unexpected response");
-                                        GpuResponse::ErrUnspec
-                                    }
-                                },
-                                Err(e) => {
-                                    error!("failed to receive data: {}", e);
-                                    GpuResponse::ErrUnspec
-                                }
-                            },
-                            Err(e) => {
-                                error!("failed to send request: {}", e);
-                                GpuResponse::ErrUnspec
-                            }
-                        }
-                    }
-                    _ => {
-                        entry.insert(Virtio3DResource::new(
-                            self.base.display_width,
-                            self.base.display_height,
-                            resource,
-                        ));
+                entry.insert(Virtio3DResource::v2_new(
+                    self.base.display_width,
+                    self.base.display_height,
+                    resource,
+                    flags,
+                    size,
+                ));
 
-                        GpuResponse::OkNoData
-                    }
-                }
+                GpuResponse::OkNoData
             }
             Entry::Occupied(_) => GpuResponse::ErrInvalidResourceId,
         }
     }
 
-    fn resource_v2_unref(&mut self, resource_id: u32) -> GpuResponse {
-        match self.resources.remove(&resource_id) {
-            Some(entry) => match entry.kvm_slot {
-                Some(kvm_slot) => {
-                    let request = VmMemoryRequest::UnregisterMemory(kvm_slot);
-                    match self.gpu_device_socket.send(&request) {
-                        Ok(_resq) => match self.gpu_device_socket.recv() {
-                            Ok(response) => match response {
-                                VmMemoryResponse::Ok => GpuResponse::OkNoData,
-                                VmMemoryResponse::Err(e) => {
-                                    error!("received an error: {}", e);
-                                    GpuResponse::ErrUnspec
-                                }
-                                _ => {
-                                    error!("recieved an unexpected response");
-                                    GpuResponse::ErrUnspec
-                                }
-                            },
-                            Err(e) => {
-                                error!("failed to receive data: {}", e);
-                                GpuResponse::ErrUnspec
-                            }
-                        },
-                        Err(e) => {
-                            error!("failed to send request: {}", e);
-                            GpuResponse::ErrUnspec
-                        }
-                    }
-                }
-                None => GpuResponse::OkNoData,
-            },
-            None => GpuResponse::ErrInvalidResourceId,
+    fn resource_map(&mut self, resource_id: u32, offset: u64) -> GpuResponse {
+        let resource = match self.resources.get_mut(&resource_id) {
+            Some(r) => r,
+            None => return GpuResponse::ErrInvalidResourceId,
+        };
+
+        if resource.use_flags() & VIRTIO_GPU_RESOURCE_USE_MAPPABLE == 0 {
+            error!("resource not mappable");
+            return GpuResponse::ErrUnspec;
+        }
+
+        let dma_buf_fd = match resource.gpu_resource.export() {
+            Ok(export) => export.1,
+            Err(e) => {
+                error!("failed to export plane fd: {}", e);
+                return GpuResponse::ErrUnspec;
+            }
+        };
+
+        let request = VmMemoryRequest::RegisterFdAtPciBarOffset(
+            self.pci_bar,
+            MaybeOwnedFd::Borrowed(dma_buf_fd.as_raw_fd()),
+            resource.size as usize,
+            offset,
+        );
+
+        match self.gpu_device_socket.send(&request) {
+            Ok(_) => (),
+            Err(e) => {
+                error!("failed to send request: {}", e);
+                return GpuResponse::ErrUnspec;
+            }
+        }
+
+        let response = match self.gpu_device_socket.recv() {
+            Ok(response) => response,
+            Err(e) => {
+                error!("failed to receive data: {}", e);
+                return GpuResponse::ErrUnspec;
+            }
+        };
+
+        match response {
+            VmMemoryResponse::RegisterMemory { pfn: _, slot } => {
+                resource.kvm_slot = Some(slot);
+                GpuResponse::OkNoData
+            }
+            VmMemoryResponse::Err(e) => {
+                error!("received an error: {}", e);
+                GpuResponse::ErrUnspec
+            }
+            _ => {
+                error!("recieved an unexpected response");
+                GpuResponse::ErrUnspec
+            }
+        }
+    }
+
+    fn resource_unmap(&mut self, resource_id: u32) -> GpuResponse {
+        let resource = match self.resources.get_mut(&resource_id) {
+            Some(r) => r,
+            None => return GpuResponse::ErrInvalidResourceId,
+        };
+
+        let kvm_slot = match resource.kvm_slot {
+            Some(kvm_slot) => kvm_slot,
+            None => return GpuResponse::ErrUnspec,
+        };
+
+        let request = VmMemoryRequest::UnregisterMemory(kvm_slot);
+        match self.gpu_device_socket.send(&request) {
+            Ok(_) => (),
+            Err(e) => {
+                error!("failed to send request: {}", e);
+                return GpuResponse::ErrUnspec;
+            }
+        }
+
+        let response = match self.gpu_device_socket.recv() {
+            Ok(response) => response,
+            Err(e) => {
+                error!("failed to receive data: {}", e);
+                return GpuResponse::ErrUnspec;
+            }
+        };
+
+        match response {
+            VmMemoryResponse::Ok => {
+                resource.kvm_slot = None;
+                GpuResponse::OkNoData
+            }
+            VmMemoryResponse::Err(e) => {
+                error!("received an error: {}", e);
+                GpuResponse::ErrUnspec
+            }
+            _ => {
+                error!("recieved an unexpected response");
+                GpuResponse::ErrUnspec
+            }
         }
     }
 }
diff --git a/devices/src/virtio/gpu/virtio_gfxstream_backend.rs b/devices/src/virtio/gpu/virtio_gfxstream_backend.rs
index aa02e15..2a49da8 100644
--- a/devices/src/virtio/gpu/virtio_gfxstream_backend.rs
+++ b/devices/src/virtio/gpu/virtio_gfxstream_backend.rs
@@ -10,7 +10,6 @@
 use std::cell::RefCell;
 use std::collections::btree_map::Entry;
 use std::collections::BTreeMap as Map;
-use std::fs::File;
 use std::mem::transmute;
 use std::os::raw::{c_char, c_int, c_uchar, c_uint, c_void};
 use std::panic;
@@ -27,6 +26,7 @@ use vm_control::VmMemoryControlRequestSocket;
 use super::protocol::GpuResponse;
 pub use super::virtio_backend::{VirtioBackend, VirtioResource};
 use crate::virtio::gpu::{Backend, DisplayBackend, VIRTIO_F_VERSION_1, VIRTIO_GPU_F_VIRGL};
+use crate::virtio::resource_bridge::ResourceResponse;
 
 // C definitions related to gfxstream
 // In gfxstream, only write_fence is used
@@ -340,8 +340,8 @@ impl Backend for VirtioGfxStreamBackend {
     }
 
     /// If supported, export the resource with the given id to a file.
-    fn export_resource(&mut self, _id: u32) -> Option<File> {
-        None
+    fn export_resource(&mut self, _id: u32) -> ResourceResponse {
+        ResourceResponse::Invalid
     }
 
     /// Creates a fence with the given id that can be used to determine when the previous command
diff --git a/devices/src/virtio/interrupt.rs b/devices/src/virtio/interrupt.rs
index f808d84..91a3942 100644
--- a/devices/src/virtio/interrupt.rs
+++ b/devices/src/virtio/interrupt.rs
@@ -13,7 +13,7 @@ pub struct Interrupt {
     interrupt_status: Arc<AtomicUsize>,
     interrupt_evt: EventFd,
     interrupt_resample_evt: EventFd,
-    msix_config: Option<Arc<Mutex<MsixConfig>>>,
+    pub msix_config: Option<Arc<Mutex<MsixConfig>>>,
     config_msix_vector: u16,
 }
 
diff --git a/devices/src/virtio/mod.rs b/devices/src/virtio/mod.rs
index 7716fe0..4d5d2cb 100644
--- a/devices/src/virtio/mod.rs
+++ b/devices/src/virtio/mod.rs
@@ -6,6 +6,7 @@
 
 mod balloon;
 mod block;
+mod console;
 mod descriptor_utils;
 mod input;
 mod interrupt;
@@ -29,6 +30,7 @@ pub mod vhost;
 
 pub use self::balloon::*;
 pub use self::block::*;
+pub use self::console::*;
 pub use self::descriptor_utils::Error as DescriptorError;
 pub use self::descriptor_utils::*;
 #[cfg(feature = "gpu")]
diff --git a/devices/src/virtio/vhost/control_socket.rs b/devices/src/virtio/vhost/control_socket.rs
new file mode 100644
index 0000000..a1ccfaf
--- /dev/null
+++ b/devices/src/virtio/vhost/control_socket.rs
@@ -0,0 +1,36 @@
+// Copyright 2020 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.
+
+use msg_socket::{MsgOnSocket, MsgSocket};
+use sys_util::Error as SysError;
+
+#[derive(MsgOnSocket, Debug)]
+pub enum VhostDevRequest {
+    /// Mask or unmask all the MSI entries for a Virtio Vhost device.
+    MsixChanged,
+    /// Mask or unmask a MSI entry for a Virtio Vhost device.
+    MsixEntryChanged(usize),
+}
+
+#[derive(MsgOnSocket, Debug)]
+pub enum VhostDevResponse {
+    Ok,
+    Err(SysError),
+}
+
+pub type VhostDevRequestSocket = MsgSocket<VhostDevRequest, VhostDevResponse>;
+pub type VhostDevResponseSocket = MsgSocket<VhostDevResponse, VhostDevRequest>;
+
+/// Create control socket pair. This pair is used to communicate with the
+/// virtio device process.
+/// Mainly between the virtio and activate thread.
+pub fn create_control_sockets() -> (
+    Option<VhostDevRequestSocket>,
+    Option<VhostDevResponseSocket>,
+) {
+    match msg_socket::pair::<VhostDevRequest, VhostDevResponse>() {
+        Ok((request, response)) => (Some(request), Some(response)),
+        _ => (None, None),
+    }
+}
diff --git a/devices/src/virtio/vhost/mod.rs b/devices/src/virtio/vhost/mod.rs
index 66c62d0..86ed81e 100644
--- a/devices/src/virtio/vhost/mod.rs
+++ b/devices/src/virtio/vhost/mod.rs
@@ -12,10 +12,12 @@ use remain::sorted;
 use sys_util::Error as SysError;
 use vhost::Error as VhostError;
 
+mod control_socket;
 mod net;
 mod vsock;
 mod worker;
 
+pub use self::control_socket::*;
 pub use self::net::Net;
 pub use self::vsock::Vsock;
 
diff --git a/devices/src/virtio/vhost/net.rs b/devices/src/virtio/vhost/net.rs
index ff72970..542423a 100644
--- a/devices/src/virtio/vhost/net.rs
+++ b/devices/src/virtio/vhost/net.rs
@@ -14,9 +14,12 @@ use sys_util::{error, warn, EventFd, GuestMemory};
 use vhost::NetT as VhostNetT;
 use virtio_sys::virtio_net;
 
+use super::control_socket::*;
 use super::worker::Worker;
 use super::{Error, Result};
+use crate::pci::MsixStatus;
 use crate::virtio::{Interrupt, Queue, VirtioDevice, TYPE_NET};
+use msg_socket::{MsgReceiver, MsgSender};
 
 const QUEUE_SIZE: u16 = 256;
 const NUM_QUEUES: usize = 2;
@@ -31,6 +34,8 @@ pub struct Net<T: TapT, U: VhostNetT<T>> {
     vhost_interrupt: Option<Vec<EventFd>>,
     avail_features: u64,
     acked_features: u64,
+    request_socket: Option<VhostDevRequestSocket>,
+    response_socket: Option<VhostDevResponseSocket>,
 }
 
 impl<T, U> Net<T, U>
@@ -85,6 +90,8 @@ where
             vhost_interrupt.push(EventFd::new().map_err(Error::VhostIrqCreate)?);
         }
 
+        let (request_socket, response_socket) = create_control_sockets();
+
         Ok(Net {
             workers_kill_evt: Some(kill_evt.try_clone().map_err(Error::CloneKillEventFd)?),
             kill_evt,
@@ -94,6 +101,8 @@ where
             vhost_interrupt: Some(vhost_interrupt),
             avail_features,
             acked_features: 0u64,
+            request_socket,
+            response_socket,
         })
     }
 }
@@ -143,6 +152,14 @@ where
         }
         keep_fds.push(self.kill_evt.as_raw_fd());
 
+        if let Some(request_socket) = &self.request_socket {
+            keep_fds.push(request_socket.as_raw_fd());
+        }
+
+        if let Some(response_socket) = &self.response_socket {
+            keep_fds.push(response_socket.as_raw_fd());
+        }
+
         keep_fds
     }
 
@@ -189,6 +206,11 @@ where
                 if let Some(vhost_interrupt) = self.vhost_interrupt.take() {
                     if let Some(kill_evt) = self.workers_kill_evt.take() {
                         let acked_features = self.acked_features;
+                        let socket = if self.response_socket.is_some() {
+                            self.response_socket.take()
+                        } else {
+                            None
+                        };
                         let worker_result = thread::Builder::new()
                             .name("vhost_net".to_string())
                             .spawn(move || {
@@ -199,6 +221,7 @@ where
                                     interrupt,
                                     acked_features,
                                     kill_evt,
+                                    socket,
                                 );
                                 let activate_vqs = |handle: &U| -> Result<()> {
                                     for idx in 0..NUM_QUEUES {
@@ -251,6 +274,48 @@ where
         }
     }
 
+    fn control_notify(&self, behavior: MsixStatus) {
+        if self.worker_thread.is_none() || self.request_socket.is_none() {
+            return;
+        }
+        if let Some(socket) = &self.request_socket {
+            match behavior {
+                MsixStatus::EntryChanged(index) => {
+                    if let Err(e) = socket.send(&VhostDevRequest::MsixEntryChanged(index)) {
+                        error!(
+                            "{} failed to send VhostMsixEntryChanged request for entry {}: {:?}",
+                            self.debug_label(),
+                            index,
+                            e
+                        );
+                        return;
+                    }
+                    if let Err(e) = socket.recv() {
+                        error!("{} failed to receive VhostMsixEntryChanged response for entry {}: {:?}", self.debug_label(), index, e);
+                    }
+                }
+                MsixStatus::Changed => {
+                    if let Err(e) = socket.send(&VhostDevRequest::MsixChanged) {
+                        error!(
+                            "{} failed to send VhostMsixChanged request: {:?}",
+                            self.debug_label(),
+                            e
+                        );
+                        return;
+                    }
+                    if let Err(e) = socket.recv() {
+                        error!(
+                            "{} failed to receive VhostMsixChanged response {:?}",
+                            self.debug_label(),
+                            e
+                        );
+                    }
+                }
+                _ => {}
+            }
+        }
+    }
+
     fn reset(&mut self) -> bool {
         // Only kill the child if it claimed its eventfd.
         if self.workers_kill_evt.is_none() && self.kill_evt.write(1).is_err() {
@@ -269,6 +334,7 @@ where
                     self.tap = Some(tap);
                     self.vhost_interrupt = Some(worker.vhost_interrupt);
                     self.workers_kill_evt = Some(worker.kill_evt);
+                    self.response_socket = worker.response_socket;
                     return true;
                 }
             }
diff --git a/devices/src/virtio/vhost/vsock.rs b/devices/src/virtio/vhost/vsock.rs
index 03826ff..390c857 100644
--- a/devices/src/virtio/vhost/vsock.rs
+++ b/devices/src/virtio/vhost/vsock.rs
@@ -169,6 +169,7 @@ impl VirtioDevice for Vsock {
                                 interrupt,
                                 acked_features,
                                 kill_evt,
+                                None,
                             );
                             let activate_vqs = |handle: &VhostVsockHandle| -> Result<()> {
                                 handle.set_cid(cid).map_err(Error::VhostVsockSetCid)?;
diff --git a/devices/src/virtio/vhost/worker.rs b/devices/src/virtio/vhost/worker.rs
index 1eff01f..ca02a63 100644
--- a/devices/src/virtio/vhost/worker.rs
+++ b/devices/src/virtio/vhost/worker.rs
@@ -4,16 +4,16 @@
 
 use std::os::raw::c_ulonglong;
 
-use sys_util::{EventFd, PollContext, PollToken};
+use sys_util::{error, Error as SysError, EventFd, PollContext, PollToken};
 use vhost::Vhost;
 
+use super::control_socket::{VhostDevRequest, VhostDevResponse, VhostDevResponseSocket};
 use super::{Error, Result};
 use crate::virtio::{Interrupt, Queue};
+use libc::EIO;
+use msg_socket::{MsgReceiver, MsgSender};
 
-/// Worker that takes care of running the vhost device.  This mainly involves forwarding interrupts
-/// from the vhost driver to the guest VM because crosvm only supports the virtio-mmio transport,
-/// which requires a bit to be set in the interrupt status register before triggering the interrupt
-/// and the vhost driver doesn't do this for us.
+/// Worker that takes care of running the vhost device.
 pub struct Worker<T: Vhost> {
     interrupt: Interrupt,
     queues: Vec<Queue>,
@@ -21,6 +21,7 @@ pub struct Worker<T: Vhost> {
     pub vhost_interrupt: Vec<EventFd>,
     acked_features: u64,
     pub kill_evt: EventFd,
+    pub response_socket: Option<VhostDevResponseSocket>,
 }
 
 impl<T: Vhost> Worker<T> {
@@ -31,6 +32,7 @@ impl<T: Vhost> Worker<T> {
         interrupt: Interrupt,
         acked_features: u64,
         kill_evt: EventFd,
+        response_socket: Option<VhostDevResponseSocket>,
     ) -> Worker<T> {
         Worker {
             interrupt,
@@ -39,6 +41,7 @@ impl<T: Vhost> Worker<T> {
             vhost_interrupt,
             acked_features,
             kill_evt,
+            response_socket,
         }
     }
 
@@ -87,9 +90,7 @@ impl<T: Vhost> Worker<T> {
             self.vhost_handle
                 .set_vring_base(queue_index, 0)
                 .map_err(Error::VhostSetVringBase)?;
-            self.vhost_handle
-                .set_vring_call(queue_index, &self.vhost_interrupt[queue_index])
-                .map_err(Error::VhostSetVringCall)?;
+            self.set_vring_call_for_entry(queue_index, queue.vector as usize)?;
             self.vhost_handle
                 .set_vring_kick(queue_index, &queue_evts[queue_index])
                 .map_err(Error::VhostSetVringKick)?;
@@ -102,6 +103,7 @@ impl<T: Vhost> Worker<T> {
             VhostIrqi { index: usize },
             InterruptResample,
             Kill,
+            ControlNotify,
         }
 
         let poll_ctx: PollContext<Token> = PollContext::build_with(&[
@@ -115,6 +117,11 @@ impl<T: Vhost> Worker<T> {
                 .add(vhost_int, Token::VhostIrqi { index })
                 .map_err(Error::CreatePollContext)?;
         }
+        if let Some(socket) = &self.response_socket {
+            poll_ctx
+                .add(socket, Token::ControlNotify)
+                .map_err(Error::CreatePollContext)?;
+        }
 
         'poll: loop {
             let events = poll_ctx.wait().map_err(Error::PollError)?;
@@ -134,10 +141,122 @@ impl<T: Vhost> Worker<T> {
                         let _ = self.kill_evt.read();
                         break 'poll;
                     }
+                    Token::ControlNotify => {
+                        if let Some(socket) = &self.response_socket {
+                            match socket.recv() {
+                                Ok(VhostDevRequest::MsixEntryChanged(index)) => {
+                                    let mut qindex = 0;
+                                    for (queue_index, queue) in self.queues.iter().enumerate() {
+                                        if queue.vector == index as u16 {
+                                            qindex = queue_index;
+                                            break;
+                                        }
+                                    }
+                                    let response =
+                                        match self.set_vring_call_for_entry(qindex, index) {
+                                            Ok(()) => VhostDevResponse::Ok,
+                                            Err(e) => {
+                                                error!(
+                                                "Set vring call failed for masked entry {}: {:?}",
+                                                index, e
+                                            );
+                                                VhostDevResponse::Err(SysError::new(EIO))
+                                            }
+                                        };
+                                    if let Err(e) = socket.send(&response) {
+                                        error!("Vhost failed to send VhostMsixEntryMasked Response for entry {}: {:?}", index, e);
+                                    }
+                                }
+                                Ok(VhostDevRequest::MsixChanged) => {
+                                    let response = match self.set_vring_calls() {
+                                        Ok(()) => VhostDevResponse::Ok,
+                                        Err(e) => {
+                                            error!("Set vring calls failed: {:?}", e);
+                                            VhostDevResponse::Err(SysError::new(EIO))
+                                        }
+                                    };
+                                    if let Err(e) = socket.send(&response) {
+                                        error!(
+                                            "Vhost failed to send VhostMsixMasked Response: {:?}",
+                                            e
+                                        );
+                                    }
+                                }
+                                Err(e) => {
+                                    error!("Vhost failed to receive Control request: {:?}", e);
+                                }
+                            }
+                        }
+                    }
                 }
             }
         }
         cleanup_vqs(&self.vhost_handle)?;
         Ok(())
     }
+
+    fn set_vring_call_for_entry(&self, queue_index: usize, vector: usize) -> Result<()> {
+        // No response_socket means it doesn't have any control related
+        // with the msix. Due to this, cannot use the direct irq fd but
+        // should fall back to indirect irq fd.
+        if self.response_socket.is_some() {
+            if let Some(msix_config) = &self.interrupt.msix_config {
+                let msix_config = msix_config.lock();
+                let msix_masked = msix_config.masked();
+                if msix_masked {
+                    return Ok(());
+                }
+                if !msix_config.table_masked(vector) {
+                    if let Some(irqfd) = msix_config.get_irqfd(vector) {
+                        self.vhost_handle
+                            .set_vring_call(queue_index, irqfd)
+                            .map_err(Error::VhostSetVringCall)?;
+                    } else {
+                        self.vhost_handle
+                            .set_vring_call(queue_index, &self.vhost_interrupt[queue_index])
+                            .map_err(Error::VhostSetVringCall)?;
+                    }
+                    return Ok(());
+                }
+            }
+        }
+
+        self.vhost_handle
+            .set_vring_call(queue_index, &self.vhost_interrupt[queue_index])
+            .map_err(Error::VhostSetVringCall)?;
+        Ok(())
+    }
+
+    fn set_vring_calls(&self) -> Result<()> {
+        if let Some(msix_config) = &self.interrupt.msix_config {
+            let msix_config = msix_config.lock();
+            if msix_config.masked() {
+                for (queue_index, _) in self.queues.iter().enumerate() {
+                    self.vhost_handle
+                        .set_vring_call(queue_index, &self.vhost_interrupt[queue_index])
+                        .map_err(Error::VhostSetVringCall)?;
+                }
+            } else {
+                for (queue_index, queue) in self.queues.iter().enumerate() {
+                    let vector = queue.vector as usize;
+                    if !msix_config.table_masked(vector) {
+                        if let Some(irqfd) = msix_config.get_irqfd(vector) {
+                            self.vhost_handle
+                                .set_vring_call(queue_index, irqfd)
+                                .map_err(Error::VhostSetVringCall)?;
+                        } else {
+                            self.vhost_handle
+                                .set_vring_call(queue_index, &self.vhost_interrupt[queue_index])
+                                .map_err(Error::VhostSetVringCall)?;
+                        }
+                    } else {
+                        self.vhost_handle
+                            .set_vring_call(queue_index, &self.vhost_interrupt[queue_index])
+                            .map_err(Error::VhostSetVringCall)?;
+                    }
+                }
+            }
+        }
+        Ok(())
+    }
 }
diff --git a/devices/src/virtio/virtio_device.rs b/devices/src/virtio/virtio_device.rs
index 806a98f..6eb5548 100644
--- a/devices/src/virtio/virtio_device.rs
+++ b/devices/src/virtio/virtio_device.rs
@@ -7,7 +7,7 @@ use std::os::unix::io::RawFd;
 use sys_util::{EventFd, GuestMemory};
 
 use super::*;
-use crate::pci::{PciBarConfiguration, PciCapability};
+use crate::pci::{MsixStatus, PciBarConfiguration, PciCapability};
 
 /// Trait for virtio devices to be driven by a virtio transport.
 ///
@@ -86,4 +86,6 @@ pub trait VirtioDevice: Send {
 
     /// Invoked when the device is sandboxed.
     fn on_device_sandboxed(&mut self) {}
+
+    fn control_notify(&self, _behavior: MsixStatus) {}
 }
diff --git a/devices/src/virtio/virtio_pci_device.rs b/devices/src/virtio/virtio_pci_device.rs
index c6d6786..e63abe9 100644
--- a/devices/src/virtio/virtio_pci_device.rs
+++ b/devices/src/virtio/virtio_pci_device.rs
@@ -535,7 +535,8 @@ impl PciDevice for VirtioPciDevice {
     fn write_config_register(&mut self, reg_idx: usize, offset: u64, data: &[u8]) {
         if let Some(msix_cap_reg_idx) = self.msix_cap_reg_idx {
             if msix_cap_reg_idx == reg_idx {
-                self.msix_config.lock().write_msix_capability(offset, data);
+                let behavior = self.msix_config.lock().write_msix_capability(offset, data);
+                self.device.control_notify(behavior);
             }
         }
 
@@ -626,9 +627,11 @@ impl PciDevice for VirtioPciDevice {
                 // Handled with ioeventfds.
             }
             o if MSIX_TABLE_BAR_OFFSET <= o && o < MSIX_TABLE_BAR_OFFSET + MSIX_TABLE_SIZE => {
-                self.msix_config
+                let behavior = self
+                    .msix_config
                     .lock()
                     .write_msix_table(o - MSIX_TABLE_BAR_OFFSET, data);
+                self.device.control_notify(behavior);
             }
             o if MSIX_PBA_BAR_OFFSET <= o && o < MSIX_PBA_BAR_OFFSET + MSIX_PBA_SIZE => {
                 self.msix_config