summary refs log tree commit diff
path: root/devices
diff options
context:
space:
mode:
authorAlyssa Ross <hi@alyssa.is>2020-06-02 03:03:26 +0000
committerAlyssa Ross <hi@alyssa.is>2020-06-14 11:23:24 +0000
commit28d9682698d287d14cbe67a0ed7acc1427add320 (patch)
tree669ed98d9b1388b553c8e0f0189678cc68dd4162 /devices
parent460406d10bbfaa890d56d616b4610813da63a312 (diff)
parent4264464153a7a788ef73c5015ac8bbde5f8ebe1c (diff)
downloadcrosvm-28d9682698d287d14cbe67a0ed7acc1427add320.tar
crosvm-28d9682698d287d14cbe67a0ed7acc1427add320.tar.gz
crosvm-28d9682698d287d14cbe67a0ed7acc1427add320.tar.bz2
crosvm-28d9682698d287d14cbe67a0ed7acc1427add320.tar.lz
crosvm-28d9682698d287d14cbe67a0ed7acc1427add320.tar.xz
crosvm-28d9682698d287d14cbe67a0ed7acc1427add320.tar.zst
crosvm-28d9682698d287d14cbe67a0ed7acc1427add320.zip
Merge remote-tracking branch 'origin/master'
Diffstat (limited to 'devices')
-rw-r--r--devices/Cargo.toml3
-rw-r--r--devices/src/pci/ac97_bus_master.rs9
-rw-r--r--devices/src/usb/xhci/ring_buffer.rs85
-rw-r--r--devices/src/virtio/descriptor_utils.rs261
-rw-r--r--devices/src/virtio/gpu/virtio_2d_backend.rs33
-rw-r--r--devices/src/virtio/gpu/virtio_backend.rs8
-rw-r--r--devices/src/virtio/gpu/virtio_gfxstream_backend.rs3
-rw-r--r--devices/src/virtio/mod.rs8
-rw-r--r--devices/src/virtio/video/command.rs335
-rw-r--r--devices/src/virtio/video/control.rs83
-rw-r--r--devices/src/virtio/video/decoder/capability.rs138
-rw-r--r--devices/src/virtio/video/decoder/mod.rs976
-rw-r--r--devices/src/virtio/video/device.rs91
-rw-r--r--devices/src/virtio/video/encoder/mod.rs40
-rw-r--r--devices/src/virtio/video/error.rs83
-rw-r--r--devices/src/virtio/video/event.rs35
-rw-r--r--devices/src/virtio/video/format.rs116
-rw-r--r--devices/src/virtio/video/macros.rs46
-rw-r--r--devices/src/virtio/video/mod.rs255
-rw-r--r--devices/src/virtio/video/params.rs111
-rw-r--r--devices/src/virtio/video/protocol.rs487
-rw-r--r--devices/src/virtio/video/response.rs94
-rw-r--r--devices/src/virtio/video/worker.rs362
-rw-r--r--devices/src/virtio/wl.rs8
24 files changed, 3502 insertions, 168 deletions
diff --git a/devices/Cargo.toml b/devices/Cargo.toml
index 931ea36..8bea78d 100644
--- a/devices/Cargo.toml
+++ b/devices/Cargo.toml
@@ -7,6 +7,8 @@ edition = "2018"
 [features]
 gpu = ["gpu_buffer", "gpu_display", "gpu_renderer"]
 tpm = ["protos/trunks", "tpm2"]
+video-decoder = ["libvda"]
+video-encoder = ["libvda"]
 wl-dmabuf = []
 x = ["gpu_display/x"]
 gfxstream = ["gpu"]
@@ -28,6 +30,7 @@ kvm = { path = "../kvm" }
 kvm_sys = { path = "../kvm_sys" }
 libc = "*"
 libcras = "*"
+libvda = { version = "*", optional = true }
 linux_input_sys = { path = "../linux_input_sys" }
 msg_on_socket_derive = { path = "../msg_socket/msg_on_socket_derive" }
 msg_socket = { path = "../msg_socket" }
diff --git a/devices/src/pci/ac97_bus_master.rs b/devices/src/pci/ac97_bus_master.rs
index 22f3c92..1334125 100644
--- a/devices/src/pci/ac97_bus_master.rs
+++ b/devices/src/pci/ac97_bus_master.rs
@@ -5,7 +5,6 @@
 use std::collections::VecDeque;
 use std::convert::AsRef;
 use std::convert::TryInto;
-use std::error::Error;
 use std::fmt::{self, Display};
 use std::os::unix::io::{AsRawFd, RawFd};
 use std::sync::atomic::{AtomicBool, Ordering};
@@ -15,7 +14,7 @@ use std::time::{Duration, Instant};
 
 use audio_streams::{
     shm_streams::{ShmStream, ShmStreamSource},
-    DummyStreamControl, SampleFormat, StreamControl, StreamDirection, StreamEffect,
+    BoxError, DummyStreamControl, SampleFormat, StreamControl, StreamDirection, StreamEffect,
 };
 use sync::{Condvar, Mutex};
 use sys_util::{
@@ -105,7 +104,7 @@ type GuestMemoryResult<T> = std::result::Result<T, GuestMemoryError>;
 #[derive(Debug)]
 enum AudioError {
     // Failed to create a new stream.
-    CreateStream(Box<dyn Error>),
+    CreateStream(BoxError),
     // Invalid buffer offset received from the audio server.
     InvalidBufferOffset,
     // Guest did not provide a buffer when needed.
@@ -113,9 +112,9 @@ enum AudioError {
     // Failure to read guest memory.
     ReadingGuestError(GuestMemoryError),
     // Failure to respond to the ServerRequest.
-    RespondRequest(Box<dyn Error>),
+    RespondRequest(BoxError),
     // Failure to wait for a request from the stream.
-    WaitForAction(Box<dyn Error>),
+    WaitForAction(BoxError),
 }
 
 impl std::error::Error for AudioError {}
diff --git a/devices/src/usb/xhci/ring_buffer.rs b/devices/src/usb/xhci/ring_buffer.rs
index 3033b0e..91806c6 100644
--- a/devices/src/usb/xhci/ring_buffer.rs
+++ b/devices/src/usb/xhci/ring_buffer.rs
@@ -264,4 +264,89 @@ mod test {
         let descriptor = transfer_ring.dequeue_transfer_descriptor().unwrap();
         assert_eq!(descriptor.is_none(), true);
     }
+
+    #[test]
+    fn ring_test_toggle_cycle() {
+        let trb_size = size_of::<Trb>() as u64;
+        let gm = GuestMemory::new(&vec![(GuestAddress(0), 0x1000)]).unwrap();
+        let mut transfer_ring = RingBuffer::new(String::new(), gm.clone());
+
+        let mut trb = NormalTrb::new();
+        trb.set_trb_type(TrbType::Normal);
+        trb.set_data_buffer(1);
+        trb.set_chain(false);
+        trb.set_cycle(false);
+        gm.write_obj_at_addr(trb.clone(), GuestAddress(0x100))
+            .unwrap();
+
+        let mut ltrb = LinkTrb::new();
+        ltrb.set_trb_type(TrbType::Link);
+        ltrb.set_ring_segment_pointer(0x100);
+        ltrb.set_toggle_cycle(true);
+        ltrb.set_cycle(false);
+        gm.write_obj_at_addr(ltrb, GuestAddress(0x100 + trb_size))
+            .unwrap();
+
+        // Initial state: consumer cycle = false
+        transfer_ring.set_dequeue_pointer(GuestAddress(0x100));
+        transfer_ring.set_consumer_cycle_state(false);
+
+        // Read first transfer descriptor.
+        let descriptor = transfer_ring
+            .dequeue_transfer_descriptor()
+            .unwrap()
+            .unwrap();
+        assert_eq!(descriptor.len(), 1);
+        assert_eq!(descriptor[0].trb.get_parameter(), 1);
+
+        // Cycle bit should be unchanged since we haven't advanced past the Link TRB yet.
+        assert_eq!(transfer_ring.consumer_cycle_state, false);
+
+        // Overwrite the first TRB with a new one (data = 2)
+        // with the new producer cycle bit state (true).
+        let mut trb = NormalTrb::new();
+        trb.set_trb_type(TrbType::Normal);
+        trb.set_data_buffer(2);
+        trb.set_cycle(true); // Link TRB toggled the cycle.
+        gm.write_obj_at_addr(trb.clone(), GuestAddress(0x100))
+            .unwrap();
+
+        // Read new transfer descriptor.
+        let descriptor = transfer_ring
+            .dequeue_transfer_descriptor()
+            .unwrap()
+            .unwrap();
+        assert_eq!(descriptor.len(), 1);
+        assert_eq!(descriptor[0].trb.get_parameter(), 2);
+
+        assert_eq!(transfer_ring.consumer_cycle_state, true);
+
+        // Update the Link TRB with the new cycle bit.
+        let mut ltrb = LinkTrb::new();
+        ltrb.set_trb_type(TrbType::Link);
+        ltrb.set_ring_segment_pointer(0x100);
+        ltrb.set_toggle_cycle(true);
+        ltrb.set_cycle(true); // Producer cycle state is now 1.
+        gm.write_obj_at_addr(ltrb, GuestAddress(0x100 + trb_size))
+            .unwrap();
+
+        // Overwrite the first TRB again with a new one (data = 3)
+        // with the new producer cycle bit state (false).
+        let mut trb = NormalTrb::new();
+        trb.set_trb_type(TrbType::Normal);
+        trb.set_data_buffer(3);
+        trb.set_cycle(false); // Link TRB toggled the cycle.
+        gm.write_obj_at_addr(trb.clone(), GuestAddress(0x100))
+            .unwrap();
+
+        // Read new transfer descriptor.
+        let descriptor = transfer_ring
+            .dequeue_transfer_descriptor()
+            .unwrap()
+            .unwrap();
+        assert_eq!(descriptor.len(), 1);
+        assert_eq!(descriptor[0].trb.get_parameter(), 3);
+
+        assert_eq!(transfer_ring.consumer_cycle_state, false);
+    }
 }
diff --git a/devices/src/virtio/descriptor_utils.rs b/devices/src/virtio/descriptor_utils.rs
index d65341b..902e3c3 100644
--- a/devices/src/virtio/descriptor_utils.rs
+++ b/devices/src/virtio/descriptor_utils.rs
@@ -2,9 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+use std::borrow::Cow;
 use std::cmp;
 use std::convert::TryInto;
-use std::ffi::c_void;
 use std::fmt::{self, Display};
 use std::io::{self, Read, Write};
 use std::iter::FromIterator;
@@ -13,10 +13,8 @@ use std::mem::{size_of, MaybeUninit};
 use std::ptr::copy_nonoverlapping;
 use std::result;
 
-use data_model::{DataInit, Le16, Le32, Le64, VolatileMemory, VolatileMemoryError, VolatileSlice};
-use sys_util::{
-    FileReadWriteAtVolatile, FileReadWriteVolatile, GuestAddress, GuestMemory, IntoIovec,
-};
+use data_model::{DataInit, Le16, Le32, Le64, VolatileMemoryError, VolatileSlice};
+use sys_util::{FileReadWriteAtVolatile, FileReadWriteVolatile, GuestAddress, GuestMemory};
 
 use super::DescriptorChain;
 
@@ -54,10 +52,9 @@ impl std::error::Error for Error {}
 
 #[derive(Clone)]
 struct DescriptorChainConsumer<'a> {
-    buffers: Vec<libc::iovec>,
+    buffers: Vec<VolatileSlice<'a>>,
     current: usize,
     bytes_consumed: usize,
-    mem: PhantomData<&'a GuestMemory>,
 }
 
 impl<'a> DescriptorChainConsumer<'a> {
@@ -67,7 +64,7 @@ impl<'a> DescriptorChainConsumer<'a> {
         // `Reader::new()` and `Writer::new()`).
         self.get_remaining()
             .iter()
-            .fold(0usize, |count, buf| count + buf.iov_len)
+            .fold(0usize, |count, buf| count + buf.size())
     }
 
     fn bytes_consumed(&self) -> usize {
@@ -78,10 +75,38 @@ impl<'a> DescriptorChainConsumer<'a> {
     /// consume any bytes from the `DescriptorChain`. Instead callers should use the `consume`
     /// method to advance the `DescriptorChain`. Multiple calls to `get` with no intervening calls
     /// to `consume` will return the same data.
-    fn get_remaining(&self) -> &[libc::iovec] {
+    fn get_remaining(&self) -> &[VolatileSlice] {
         &self.buffers[self.current..]
     }
 
+    /// Like `get_remaining` but guarantees that the combined length of all the returned iovecs is
+    /// not greater than `count`. The combined length of the returned iovecs may be less than
+    /// `count` but will always be greater than 0 as long as there is still space left in the
+    /// `DescriptorChain`.
+    fn get_remaining_with_count(&self, count: usize) -> Cow<[VolatileSlice]> {
+        let iovs = self.get_remaining();
+        let mut iov_count = 0;
+        let mut rem = count;
+        for iov in iovs {
+            if rem < iov.size() {
+                break;
+            }
+
+            iov_count += 1;
+            rem -= iov.size();
+        }
+
+        // Special case where the number of bytes to be copied is smaller than the `size()` of the
+        // first iovec.
+        if iov_count == 0 && iovs.len() > 0 && count > 0 {
+            debug_assert!(count < iovs[0].size());
+            // Safe because we know that count is smaller than the length of the first slice.
+            Cow::Owned(vec![iovs[0].sub_slice(0, count).unwrap()])
+        } else {
+            Cow::Borrowed(&iovs[..iov_count])
+        }
+    }
+
     /// Consumes `count` bytes from the `DescriptorChain`. If `count` is larger than
     /// `self.available_bytes()` then all remaining bytes in the `DescriptorChain` will be consumed.
     ///
@@ -99,19 +124,18 @@ impl<'a> DescriptorChainConsumer<'a> {
                 break;
             }
 
-            let consumed = if count < buf.iov_len {
+            let consumed = if count < buf.size() {
                 // Safe because we know that the iovec pointed to valid memory and we are adding a
                 // value that is smaller than the length of the memory.
-                buf.iov_base = unsafe { (buf.iov_base as *mut u8).add(count) as *mut c_void };
-                buf.iov_len -= count;
+                *buf = buf.offset(count).unwrap();
                 count
             } else {
                 self.current += 1;
-                buf.iov_len
+                buf.size()
             };
 
-            // This shouldn't overflow because `consumed <= buf.iov_len` and we already verified
-            // that adding all `buf.iov_len` values will not overflow when the Reader/Writer was
+            // This shouldn't overflow because `consumed <= buf.size()` and we already verified
+            // that adding all `buf.size()` values will not overflow when the Reader/Writer was
             // constructed.
             self.bytes_consumed += consumed;
             count -= consumed;
@@ -126,81 +150,20 @@ impl<'a> DescriptorChainConsumer<'a> {
         let mut rem = offset;
         let mut end = self.current;
         for buf in &mut self.buffers[self.current..] {
-            if rem < buf.iov_len {
-                buf.iov_len = rem;
+            if rem < buf.size() {
+                // Safe because we are creating a smaller sub-slice.
+                *buf = buf.sub_slice(0, rem).unwrap();
                 break;
             }
 
             end += 1;
-            rem -= buf.iov_len;
+            rem -= buf.size();
         }
 
         self.buffers.truncate(end + 1);
 
         other
     }
-
-    // Temporary method for converting iovecs into VolatileSlices until we can change the
-    // ReadWriteVolatile traits. The irony here is that the standard implementation of the
-    // ReadWriteVolatile traits will convert the VolatileSlices back into iovecs.
-    fn get_volatile_slices(&mut self, mut count: usize) -> Vec<VolatileSlice> {
-        let bufs = self.get_remaining();
-        let mut iovs = Vec::with_capacity(bufs.len());
-        for b in bufs {
-            // Safe because we verified during construction that the memory at `b.iov_base` is
-            // `b.iov_len` bytes long. The lifetime of the `VolatileSlice` is tied to the lifetime
-            // of this `DescriptorChainConsumer`, which is in turn tied to the lifetime of the
-            // `GuestMemory` used to create it and so the memory will be available for the duration
-            // of the `VolatileSlice`.
-            let iov = unsafe {
-                if count < b.iov_len {
-                    VolatileSlice::new(
-                        b.iov_base as *mut u8,
-                        count.try_into().expect("usize doesn't fit in u64"),
-                    )
-                } else {
-                    VolatileSlice::new(
-                        b.iov_base as *mut u8,
-                        b.iov_len.try_into().expect("usize doesn't fit in u64"),
-                    )
-                }
-            };
-
-            count -= iov.size() as usize;
-            iovs.push(iov);
-        }
-
-        iovs
-    }
-
-    fn get_iovec(&mut self, len: usize) -> io::Result<DescriptorIovec<'a>> {
-        let mut iovec = Vec::with_capacity(self.get_remaining().len());
-
-        let mut rem = len;
-        for buf in self.get_remaining() {
-            let iov = if rem < buf.iov_len {
-                libc::iovec {
-                    iov_base: buf.iov_base,
-                    iov_len: rem,
-                }
-            } else {
-                buf.clone()
-            };
-
-            rem -= iov.iov_len;
-            iovec.push(iov);
-
-            if rem == 0 {
-                break;
-            }
-        }
-        self.consume(len);
-
-        Ok(DescriptorIovec {
-            iovec,
-            mem: PhantomData,
-        })
-    }
 }
 
 /// Provides high-level interface over the sequence of memory regions
@@ -249,21 +212,18 @@ impl<'a> Reader<'a> {
                     .checked_add(desc.len as usize)
                     .ok_or(Error::DescriptorChainOverflow)?;
 
-                let vs = mem
-                    .get_slice(desc.addr.offset(), desc.len.into())
-                    .map_err(Error::VolatileMemoryError)?;
-                Ok(libc::iovec {
-                    iov_base: vs.as_ptr() as *mut c_void,
-                    iov_len: vs.size() as usize,
-                })
+                mem.get_slice_at_addr(
+                    desc.addr,
+                    desc.len.try_into().expect("u32 doesn't fit in usize"),
+                )
+                .map_err(Error::GuestMemoryError)
             })
-            .collect::<Result<Vec<libc::iovec>>>()?;
+            .collect::<Result<Vec<VolatileSlice>>>()?;
         Ok(Reader {
             buffer: DescriptorChainConsumer {
                 buffers,
                 current: 0,
                 bytes_consumed: 0,
-                mem: PhantomData,
             },
         })
     }
@@ -311,7 +271,7 @@ impl<'a> Reader<'a> {
         mut dst: F,
         count: usize,
     ) -> io::Result<usize> {
-        let iovs = self.buffer.get_volatile_slices(count);
+        let iovs = self.buffer.get_remaining_with_count(count);
         let written = dst.write_vectored_volatile(&iovs[..])?;
         self.buffer.consume(written);
         Ok(written)
@@ -327,7 +287,7 @@ impl<'a> Reader<'a> {
         count: usize,
         off: u64,
     ) -> io::Result<usize> {
-        let iovs = self.buffer.get_volatile_slices(count);
+        let iovs = self.buffer.get_remaining_with_count(count);
         let written = dst.write_vectored_at_volatile(&iovs[..], off)?;
         self.buffer.consume(written);
         Ok(written)
@@ -392,6 +352,19 @@ impl<'a> Reader<'a> {
         self.buffer.bytes_consumed()
     }
 
+    /// Returns a `&[VolatileSlice]` that represents all the remaining data in this `Reader`.
+    /// Calling this method does not actually consume any data from the `Reader` and callers should
+    /// call `consume` to advance the `Reader`.
+    pub fn get_remaining(&self) -> &[VolatileSlice] {
+        self.buffer.get_remaining()
+    }
+
+    /// Consumes `amt` bytes from the underlying descriptor chain. If `amt` is larger than the
+    /// remaining data left in this `Reader`, then all remaining data will be consumed.
+    pub fn consume(&mut self, amt: usize) {
+        self.buffer.consume(amt)
+    }
+
     /// Splits this `Reader` into two at the given offset in the `DescriptorChain` buffer. After the
     /// split, `self` will be able to read up to `offset` bytes while the returned `Reader` can read
     /// up to `available_bytes() - offset` bytes. If `offset > self.available_bytes()`, then the
@@ -401,12 +374,6 @@ impl<'a> Reader<'a> {
             buffer: self.buffer.split_at(offset),
         }
     }
-
-    /// Returns a DescriptorIovec for the next `len` bytes of the descriptor chain
-    /// buffer, which can be used as an IntoIovec.
-    pub fn get_iovec(&mut self, len: usize) -> io::Result<DescriptorIovec<'a>> {
-        self.buffer.get_iovec(len)
-    }
 }
 
 impl<'a> io::Read for Reader<'a> {
@@ -418,11 +385,11 @@ impl<'a> io::Read for Reader<'a> {
                 break;
             }
 
-            let count = cmp::min(rem.len(), b.iov_len);
+            let count = cmp::min(rem.len(), b.size());
 
             // Safe because we have already verified that `b` points to valid memory.
             unsafe {
-                copy_nonoverlapping(b.iov_base as *const u8, rem.as_mut_ptr(), count);
+                copy_nonoverlapping(b.as_ptr(), rem.as_mut_ptr(), count);
             }
             rem = &mut rem[count..];
             total += count;
@@ -460,21 +427,18 @@ impl<'a> Writer<'a> {
                     .checked_add(desc.len as usize)
                     .ok_or(Error::DescriptorChainOverflow)?;
 
-                let vs = mem
-                    .get_slice(desc.addr.offset(), desc.len.into())
-                    .map_err(Error::VolatileMemoryError)?;
-                Ok(libc::iovec {
-                    iov_base: vs.as_ptr() as *mut c_void,
-                    iov_len: vs.size() as usize,
-                })
+                mem.get_slice_at_addr(
+                    desc.addr,
+                    desc.len.try_into().expect("u32 doesn't fit in usize"),
+                )
+                .map_err(Error::GuestMemoryError)
             })
-            .collect::<Result<Vec<libc::iovec>>>()?;
+            .collect::<Result<Vec<VolatileSlice>>>()?;
         Ok(Writer {
             buffer: DescriptorChainConsumer {
                 buffers,
                 current: 0,
                 bytes_consumed: 0,
-                mem: PhantomData,
             },
         })
     }
@@ -512,7 +476,7 @@ impl<'a> Writer<'a> {
         mut src: F,
         count: usize,
     ) -> io::Result<usize> {
-        let iovs = self.buffer.get_volatile_slices(count);
+        let iovs = self.buffer.get_remaining_with_count(count);
         let read = src.read_vectored_volatile(&iovs[..])?;
         self.buffer.consume(read);
         Ok(read)
@@ -528,7 +492,7 @@ impl<'a> Writer<'a> {
         count: usize,
         off: u64,
     ) -> io::Result<usize> {
-        let iovs = self.buffer.get_volatile_slices(count);
+        let iovs = self.buffer.get_remaining_with_count(count);
         let read = src.read_vectored_at_volatile(&iovs[..], off)?;
         self.buffer.consume(read);
         Ok(read)
@@ -595,12 +559,6 @@ impl<'a> Writer<'a> {
             buffer: self.buffer.split_at(offset),
         }
     }
-
-    /// Returns a DescriptorIovec for the next `len` bytes of the descriptor chain
-    /// buffer, which can be used as an IntoIovec.
-    pub fn get_iovec(&mut self, len: usize) -> io::Result<DescriptorIovec<'a>> {
-        self.buffer.get_iovec(len)
-    }
 }
 
 impl<'a> io::Write for Writer<'a> {
@@ -612,10 +570,10 @@ impl<'a> io::Write for Writer<'a> {
                 break;
             }
 
-            let count = cmp::min(rem.len(), b.iov_len);
+            let count = cmp::min(rem.len(), b.size());
             // Safe because we have already verified that `vs` points to valid memory.
             unsafe {
-                copy_nonoverlapping(rem.as_ptr(), b.iov_base as *mut u8, count);
+                copy_nonoverlapping(rem.as_ptr(), b.as_mut_ptr(), count);
             }
             rem = &rem[count..];
             total += count;
@@ -631,18 +589,6 @@ impl<'a> io::Write for Writer<'a> {
     }
 }
 
-pub struct DescriptorIovec<'a> {
-    iovec: Vec<libc::iovec>,
-    mem: PhantomData<&'a GuestMemory>,
-}
-
-// Safe because the lifetime of DescriptorIovec is tied to the underlying GuestMemory.
-unsafe impl<'a> IntoIovec for DescriptorIovec<'a> {
-    fn into_iovec(&self) -> Vec<libc::iovec> {
-        self.iovec.clone()
-    }
-}
-
 const VIRTQ_DESC_F_NEXT: u16 = 0x1;
 const VIRTQ_DESC_F_WRITE: u16 = 0x2;
 
@@ -1266,4 +1212,59 @@ mod tests {
             .expect("failed to collect() values");
         assert_eq!(vs, vs_read);
     }
+
+    #[test]
+    fn get_remaining_with_count() {
+        use DescriptorType::*;
+
+        let memory_start_addr = GuestAddress(0x0);
+        let memory = GuestMemory::new(&vec![(memory_start_addr, 0x10000)]).unwrap();
+
+        let chain = create_descriptor_chain(
+            &memory,
+            GuestAddress(0x0),
+            GuestAddress(0x100),
+            vec![
+                (Readable, 16),
+                (Readable, 16),
+                (Readable, 96),
+                (Writable, 64),
+                (Writable, 1),
+                (Writable, 3),
+            ],
+            0,
+        )
+        .expect("create_descriptor_chain failed");
+
+        let Reader { mut buffer } = Reader::new(&memory, chain).expect("failed to create Reader");
+
+        let drain = buffer
+            .get_remaining_with_count(::std::usize::MAX)
+            .iter()
+            .fold(0usize, |total, iov| total + iov.size());
+        assert_eq!(drain, 128);
+
+        let exact = buffer
+            .get_remaining_with_count(32)
+            .iter()
+            .fold(0usize, |total, iov| total + iov.size());
+        assert!(exact > 0);
+        assert!(exact <= 32);
+
+        let split = buffer
+            .get_remaining_with_count(24)
+            .iter()
+            .fold(0usize, |total, iov| total + iov.size());
+        assert!(split > 0);
+        assert!(split <= 24);
+
+        buffer.consume(64);
+
+        let first = buffer
+            .get_remaining_with_count(8)
+            .iter()
+            .fold(0usize, |total, iov| total + iov.size());
+        assert!(first > 0);
+        assert!(first <= 8);
+    }
 }
diff --git a/devices/src/virtio/gpu/virtio_2d_backend.rs b/devices/src/virtio/gpu/virtio_2d_backend.rs
index fd85bef..9bdcc9b 100644
--- a/devices/src/virtio/gpu/virtio_2d_backend.rs
+++ b/devices/src/virtio/gpu/virtio_2d_backend.rs
@@ -199,7 +199,7 @@ pub fn transfer<'a, S: Iterator<Item = VolatileSlice<'a>>>(
             }
 
             let src_subslice = src
-                .get_slice(offset_within_src, copyable_size)
+                .get_slice(offset_within_src as usize, copyable_size as usize)
                 .map_err(|e| Error::MemCopy(e))?;
 
             let dst_line_vertical_offset = checked_arithmetic!(current_height * dst_stride)?;
@@ -210,7 +210,7 @@ pub fn transfer<'a, S: Iterator<Item = VolatileSlice<'a>>>(
             let dst_start_offset = checked_arithmetic!(dst_resource_offset + dst_line_offset)?;
 
             let dst_subslice = dst
-                .get_slice(dst_start_offset, copyable_size)
+                .get_slice(dst_start_offset as usize, copyable_size as usize)
                 .map_err(|e| Error::MemCopy(e))?;
 
             src_subslice.copy_to_volatile_slice(dst_subslice);
@@ -246,7 +246,7 @@ impl Virtio2DResource {
     ) -> bool {
         if iovecs
             .iter()
-            .any(|&(addr, len)| mem.get_slice(addr.offset(), len as u64).is_err())
+            .any(|&(addr, len)| mem.get_slice_at_addr(addr, len).is_err())
         {
             return false;
         }
@@ -303,20 +303,18 @@ impl VirtioResource for Virtio2DResource {
         if self
             .guest_iovecs
             .iter()
-            .any(|&(addr, len)| guest_mem.get_slice(addr.offset(), len as u64).is_err())
+            .any(|&(addr, len)| guest_mem.get_slice_at_addr(addr, len).is_err())
         {
             error!("failed to write to resource: invalid iovec attached");
             return;
         }
 
-        let mut src_slices = Vec::new();
-        for (addr, len) in &self.guest_iovecs {
+        let mut src_slices = Vec::with_capacity(self.guest_iovecs.len());
+        for &(addr, len) in &self.guest_iovecs {
             // Unwrap will not panic because we already checked the slices.
-            src_slices.push(guest_mem.get_slice(addr.offset(), *len as u64).unwrap());
+            src_slices.push(guest_mem.get_slice_at_addr(addr, len).unwrap());
         }
 
-        let host_mem_len = self.host_mem.len() as u64;
-
         let src_stride = self.host_mem_stride;
         let src_offset = src_offset;
 
@@ -332,10 +330,7 @@ impl VirtioResource for Virtio2DResource {
             height,
             dst_stride,
             dst_offset,
-            self.host_mem
-                .as_mut_slice()
-                .get_slice(0, host_mem_len)
-                .unwrap(),
+            VolatileSlice::new(self.host_mem.as_mut_slice()),
             src_stride,
             src_offset,
             src_slices.iter().cloned(),
@@ -359,8 +354,6 @@ impl VirtioResource for Virtio2DResource {
 
         let dst_offset = 0;
 
-        let host_mem_len = self.host_mem.len() as u64;
-
         if let Err(e) = transfer(
             self.width(),
             self.height(),
@@ -373,13 +366,9 @@ impl VirtioResource for Virtio2DResource {
             dst,
             src_stride,
             src_offset,
-            [self
-                .host_mem
-                .as_mut_slice()
-                .get_slice(0, host_mem_len)
-                .unwrap()]
-            .iter()
-            .cloned(),
+            [VolatileSlice::new(self.host_mem.as_mut_slice())]
+                .iter()
+                .cloned(),
         ) {
             error!("failed to read from resource: {}", e);
         }
diff --git a/devices/src/virtio/gpu/virtio_backend.rs b/devices/src/virtio/gpu/virtio_backend.rs
index bb1db4a..3200fa2 100644
--- a/devices/src/virtio/gpu/virtio_backend.rs
+++ b/devices/src/virtio/gpu/virtio_backend.rs
@@ -124,9 +124,9 @@ impl VirtioBackend {
     ) -> GpuResponse {
         let mut response = GpuResponse::OkNoData;
 
-        if let Some(scannout_resource_id) = self.scanout_resource_id {
-            if scannout_resource_id.get() == resource_id {
-                response = self.flush_scannout_resource_to_surface(resource);
+        if let Some(scanout_resource_id) = self.scanout_resource_id {
+            if scanout_resource_id.get() == resource_id {
+                response = self.flush_scanout_resource_to_surface(resource);
             }
         }
 
@@ -143,7 +143,7 @@ impl VirtioBackend {
         response
     }
 
-    pub fn flush_scannout_resource_to_surface(
+    pub fn flush_scanout_resource_to_surface(
         &mut self,
         resource: &mut dyn VirtioResource,
     ) -> GpuResponse {
diff --git a/devices/src/virtio/gpu/virtio_gfxstream_backend.rs b/devices/src/virtio/gpu/virtio_gfxstream_backend.rs
index d8ef793..b2a9fb7 100644
--- a/devices/src/virtio/gpu/virtio_gfxstream_backend.rs
+++ b/devices/src/virtio/gpu/virtio_gfxstream_backend.rs
@@ -16,7 +16,6 @@ use std::panic;
 use std::rc::Rc;
 use std::usize;
 
-use data_model::*;
 use gpu_display::*;
 use gpu_renderer::RendererFlags;
 use resources::Alloc;
@@ -467,7 +466,7 @@ impl Backend for VirtioGfxStreamBackend {
         let mut backing_iovecs: Vec<iovec> = Vec::new();
 
         for (addr, len) in vecs {
-            let slice = mem.get_slice(addr.offset(), len as u64).unwrap();
+            let slice = mem.get_slice_at_addr(addr, len).unwrap();
             backing_iovecs.push(iovec {
                 iov_base: slice.as_ptr() as *mut c_void,
                 iov_len: len as usize,
diff --git a/devices/src/virtio/mod.rs b/devices/src/virtio/mod.rs
index 4d5d2cb..ce62551 100644
--- a/devices/src/virtio/mod.rs
+++ b/devices/src/virtio/mod.rs
@@ -17,6 +17,8 @@ mod queue;
 mod rng;
 #[cfg(feature = "tpm")]
 mod tpm;
+#[cfg(any(feature = "video-decoder", feature = "video-encoder"))]
+mod video;
 mod virtio_device;
 mod virtio_pci_common_config;
 mod virtio_pci_device;
@@ -44,6 +46,8 @@ pub use self::queue::*;
 pub use self::rng::*;
 #[cfg(feature = "tpm")]
 pub use self::tpm::*;
+#[cfg(any(feature = "video-decoder", feature = "video-encoder"))]
+pub use self::video::*;
 pub use self::virtio_device::*;
 pub use self::virtio_pci_device::*;
 pub use self::wl::*;
@@ -76,6 +80,8 @@ const TYPE_CRYPTO: u32 = 20;
 const TYPE_IOMMU: u32 = 23;
 const TYPE_FS: u32 = 26;
 const TYPE_PMEM: u32 = 27;
+const TYPE_VIDEO_ENC: u32 = 30;
+const TYPE_VIDEO_DEC: u32 = 31;
 // Additional types invented by crosvm
 const MAX_VIRTIO_DEVICE_ID: u32 = 63;
 const TYPE_WL: u32 = MAX_VIRTIO_DEVICE_ID;
@@ -114,6 +120,8 @@ pub fn type_to_str(type_: u32) -> Option<&'static str> {
         TYPE_PMEM => "pmem",
         TYPE_WL => "wl",
         TYPE_TPM => "tpm",
+        TYPE_VIDEO_DEC => "video-decoder",
+        TYPE_VIDEO_ENC => "video-encoder",
         _ => return None,
     })
 }
diff --git a/devices/src/virtio/video/command.rs b/devices/src/virtio/video/command.rs
new file mode 100644
index 0000000..7bdb335
--- /dev/null
+++ b/devices/src/virtio/video/command.rs
@@ -0,0 +1,335 @@
+// 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.
+
+//! Data structures for commands of virtio video devices.
+
+use std::convert::{TryFrom, TryInto};
+use std::fmt;
+use std::io;
+
+use data_model::Le32;
+use enumn::N;
+use sys_util::error;
+
+use crate::virtio::video::control::*;
+use crate::virtio::video::format::*;
+use crate::virtio::video::params::Params;
+use crate::virtio::video::protocol::*;
+use crate::virtio::Reader;
+
+/// An error indicating a failure while reading a request from the guest.
+#[derive(Debug)]
+pub enum ReadCmdError {
+    /// Failure while reading an object.
+    IoError(io::Error),
+    /// Invalid arguement is passed,
+    InvalidArgument,
+    /// The type of the command was invalid.
+    InvalidCmdType(u32),
+    /// The type of the requested control was unsupported.
+    UnsupportedCtrlType(u32),
+}
+
+impl fmt::Display for ReadCmdError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        use self::ReadCmdError::*;
+        match self {
+            IoError(e) => write!(f, "failed to read an object: {}", e),
+            InvalidArgument => write!(f, "invalid arguement is passed in command"),
+            InvalidCmdType(t) => write!(f, "invalid command type: {}", t),
+            UnsupportedCtrlType(t) => write!(f, "unsupported control type: {}", t),
+        }
+    }
+}
+
+impl std::error::Error for ReadCmdError {}
+
+impl From<io::Error> for ReadCmdError {
+    fn from(e: io::Error) -> ReadCmdError {
+        ReadCmdError::IoError(e)
+    }
+}
+
+#[derive(PartialEq, Eq, PartialOrd, Ord, N, Clone, Copy, Debug)]
+#[repr(u32)]
+pub enum QueueType {
+    Input = VIRTIO_VIDEO_QUEUE_TYPE_INPUT,
+    Output = VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT,
+}
+impl_try_from_le32_for_enumn!(QueueType, "queue_type");
+
+pub enum VideoCmd {
+    QueryCapability {
+        queue_type: QueueType,
+    },
+    StreamCreate {
+        stream_id: u32,
+        coded_format: Format,
+    },
+    StreamDestroy {
+        stream_id: u32,
+    },
+    StreamDrain {
+        stream_id: u32,
+    },
+    ResourceCreate {
+        stream_id: u32,
+        queue_type: QueueType,
+        resource_id: u32,
+        plane_offsets: Vec<u32>,
+        uuid: u128,
+    },
+    ResourceQueue {
+        stream_id: u32,
+        queue_type: QueueType,
+        resource_id: u32,
+        timestamp: u64,
+        data_sizes: Vec<u32>,
+    },
+    ResourceDestroyAll {
+        stream_id: u32,
+    },
+    QueueClear {
+        stream_id: u32,
+        queue_type: QueueType,
+    },
+    GetParams {
+        stream_id: u32,
+        queue_type: QueueType,
+    },
+    SetParams {
+        stream_id: u32,
+        queue_type: QueueType,
+        params: Params,
+    },
+    QueryControl {
+        query_ctrl_type: QueryCtrlType,
+    },
+    GetControl {
+        stream_id: u32,
+        ctrl_type: CtrlType,
+    },
+    SetControl {
+        stream_id: u32,
+        ctrl_val: CtrlVal,
+    },
+}
+
+impl<'a> VideoCmd {
+    /// Reads a request on virtqueue and construct a VideoCmd value.
+    pub fn from_reader(r: &'a mut Reader<'a>) -> Result<Self, ReadCmdError> {
+        use self::ReadCmdError::*;
+        use self::VideoCmd::*;
+
+        // Unlike structs in virtio_video.h in the kernel, our command structs in protocol.rs don't
+        // have a field of `struct virtio_video_cmd_hdr`. So, we first read the header here and
+        // a body below.
+        let hdr = r.read_obj::<virtio_video_cmd_hdr>()?;
+
+        Ok(match hdr.type_.into() {
+            VIRTIO_VIDEO_CMD_QUERY_CAPABILITY => {
+                let virtio_video_query_capability { queue_type, .. } = r.read_obj()?;
+                QueryCapability {
+                    queue_type: queue_type.try_into()?,
+                }
+            }
+            VIRTIO_VIDEO_CMD_STREAM_CREATE => {
+                let virtio_video_stream_create {
+                    in_mem_type,
+                    out_mem_type,
+                    coded_format,
+                    ..
+                } = r.read_obj()?;
+
+                if in_mem_type != VIRTIO_VIDEO_MEM_TYPE_VIRTIO_OBJECT
+                    || out_mem_type != VIRTIO_VIDEO_MEM_TYPE_VIRTIO_OBJECT
+                {
+                    error!("mem_type must be VIRTIO_OBJECT");
+                    return Err(InvalidArgument);
+                }
+                StreamCreate {
+                    stream_id: hdr.stream_id.into(),
+                    coded_format: coded_format.try_into()?,
+                }
+            }
+            VIRTIO_VIDEO_CMD_STREAM_DESTROY => {
+                let virtio_video_stream_destroy { .. } = r.read_obj()?;
+                StreamDestroy {
+                    stream_id: hdr.stream_id.into(),
+                }
+            }
+            VIRTIO_VIDEO_CMD_STREAM_DRAIN => {
+                let virtio_video_stream_drain { .. } = r.read_obj()?;
+                StreamDrain {
+                    stream_id: hdr.stream_id.into(),
+                }
+            }
+            VIRTIO_VIDEO_CMD_RESOURCE_CREATE => {
+                let virtio_video_resource_create {
+                    queue_type,
+                    resource_id,
+                    planes_layout,
+                    num_planes,
+                    plane_offsets,
+                    ..
+                } = r.read_obj()?;
+
+                // Assume ChromeOS-specific requirements.
+                if Into::<u32>::into(planes_layout) != VIRTIO_VIDEO_PLANES_LAYOUT_SINGLE_BUFFER {
+                    error!(
+                        "each buffer must be a single DMAbuf: {}",
+                        Into::<u32>::into(planes_layout),
+                    );
+                    return Err(InvalidArgument);
+                }
+
+                let num_planes: u32 = num_planes.into();
+                if num_planes as usize > plane_offsets.len() {
+                    error!(
+                        "num_planes must not exceed {} but {}",
+                        plane_offsets.len(),
+                        num_planes
+                    );
+                    return Err(InvalidArgument);
+                }
+                let plane_offsets = plane_offsets[0..num_planes as usize]
+                    .iter()
+                    .map(|x| Into::<u32>::into(*x))
+                    .collect::<Vec<u32>>();
+
+                let virtio_video_object_entry { uuid } = r.read_obj()?;
+
+                ResourceCreate {
+                    stream_id: hdr.stream_id.into(),
+                    queue_type: queue_type.try_into()?,
+                    resource_id: resource_id.into(),
+                    plane_offsets,
+                    uuid: u128::from_be_bytes(uuid),
+                }
+            }
+            VIRTIO_VIDEO_CMD_RESOURCE_QUEUE => {
+                let virtio_video_resource_queue {
+                    queue_type,
+                    resource_id,
+                    timestamp,
+                    num_data_sizes,
+                    data_sizes,
+                    ..
+                } = r.read_obj()?;
+
+                let num_data_sizes: u32 = num_data_sizes.into();
+                if num_data_sizes as usize > data_sizes.len() {
+                    return Err(InvalidArgument);
+                }
+                let data_sizes = data_sizes[0..num_data_sizes as usize]
+                    .iter()
+                    .map(|x| Into::<u32>::into(*x))
+                    .collect::<Vec<u32>>();
+                ResourceQueue {
+                    stream_id: hdr.stream_id.into(),
+                    queue_type: queue_type.try_into()?,
+                    resource_id: resource_id.into(),
+                    timestamp: timestamp.into(),
+                    data_sizes,
+                }
+            }
+            VIRTIO_VIDEO_CMD_RESOURCE_DESTROY_ALL => {
+                let virtio_video_resource_destroy_all {
+
+                    // `queue_type` should be ignored because destroy_all will affect both queues.
+                    // This field exists here by mistake.
+                    ..
+                } = r.read_obj()?;
+                ResourceDestroyAll {
+                    stream_id: hdr.stream_id.into(),
+                }
+            }
+            VIRTIO_VIDEO_CMD_QUEUE_CLEAR => {
+                let virtio_video_queue_clear { queue_type, .. } = r.read_obj()?;
+                QueueClear {
+                    stream_id: hdr.stream_id.into(),
+                    queue_type: queue_type.try_into()?,
+                }
+            }
+            VIRTIO_VIDEO_CMD_GET_PARAMS => {
+                let virtio_video_get_params { queue_type, .. } = r.read_obj()?;
+                GetParams {
+                    stream_id: hdr.stream_id.into(),
+                    queue_type: queue_type.try_into()?,
+                }
+            }
+            VIRTIO_VIDEO_CMD_SET_PARAMS => {
+                let virtio_video_set_params { params } = r.read_obj()?;
+                SetParams {
+                    stream_id: hdr.stream_id.into(),
+                    queue_type: params.queue_type.try_into()?,
+                    params: params.try_into()?,
+                }
+            }
+            VIRTIO_VIDEO_CMD_QUERY_CONTROL => {
+                let body = r.read_obj::<virtio_video_query_control>()?;
+                let query_ctrl_type = match body.control.into() {
+                    VIRTIO_VIDEO_CONTROL_BITRATE => QueryCtrlType::Bitrate,
+                    VIRTIO_VIDEO_CONTROL_PROFILE => QueryCtrlType::Profile(
+                        r.read_obj::<virtio_video_query_control_profile>()?
+                            .format
+                            .try_into()?,
+                    ),
+                    VIRTIO_VIDEO_CONTROL_LEVEL => QueryCtrlType::Level(
+                        r.read_obj::<virtio_video_query_control_level>()?
+                            .format
+                            .try_into()?,
+                    ),
+                    t => {
+                        return Err(ReadCmdError::UnsupportedCtrlType(t));
+                    }
+                };
+                QueryControl { query_ctrl_type }
+            }
+            VIRTIO_VIDEO_CMD_GET_CONTROL => {
+                let virtio_video_get_control { control, .. } = r.read_obj()?;
+                let ctrl_type = match control.into() {
+                    VIRTIO_VIDEO_CONTROL_BITRATE => CtrlType::Bitrate,
+                    VIRTIO_VIDEO_CONTROL_PROFILE => CtrlType::Profile,
+                    VIRTIO_VIDEO_CONTROL_LEVEL => CtrlType::Level,
+                    t => {
+                        return Err(ReadCmdError::UnsupportedCtrlType(t));
+                    }
+                };
+                GetControl {
+                    stream_id: hdr.stream_id.into(),
+                    ctrl_type,
+                }
+            }
+            VIRTIO_VIDEO_CMD_SET_CONTROL => {
+                let virtio_video_set_control { control, .. } = r.read_obj()?;
+                let ctrl_val = match control.into() {
+                    VIRTIO_VIDEO_CONTROL_BITRATE => CtrlVal::Bitrate(
+                        r.read_obj::<virtio_video_control_val_bitrate>()?
+                            .bitrate
+                            .into(),
+                    ),
+                    VIRTIO_VIDEO_CONTROL_PROFILE => CtrlVal::Profile(
+                        r.read_obj::<virtio_video_control_val_profile>()?
+                            .profile
+                            .try_into()?,
+                    ),
+                    VIRTIO_VIDEO_CONTROL_LEVEL => CtrlVal::Level(
+                        r.read_obj::<virtio_video_control_val_level>()?
+                            .level
+                            .try_into()?,
+                    ),
+                    t => {
+                        return Err(ReadCmdError::UnsupportedCtrlType(t));
+                    }
+                };
+                SetControl {
+                    stream_id: hdr.stream_id.into(),
+                    ctrl_val,
+                }
+            }
+            _ => return Err(ReadCmdError::InvalidCmdType(hdr.type_.into())),
+        })
+    }
+}
diff --git a/devices/src/virtio/video/control.rs b/devices/src/virtio/video/control.rs
new file mode 100644
index 0000000..b9756c9
--- /dev/null
+++ b/devices/src/virtio/video/control.rs
@@ -0,0 +1,83 @@
+// 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.
+
+//! Implementation of data structures for virtio-video controls.
+
+use std::convert::From;
+use std::io;
+
+use data_model::Le32;
+
+use crate::virtio::video::format::{Format, Level, Profile};
+use crate::virtio::video::protocol::*;
+use crate::virtio::video::response::Response;
+use crate::virtio::Writer;
+
+#[derive(Debug)]
+pub enum QueryCtrlType {
+    Bitrate,
+    Profile(Format),
+    Level(Format),
+}
+
+#[derive(Debug)]
+pub enum QueryCtrlResponse {
+    Profile(Vec<Profile>),
+    #[allow(dead_code)]
+    Level(Vec<Level>),
+}
+
+impl Response for QueryCtrlResponse {
+    fn write(&self, w: &mut Writer) -> Result<(), io::Error> {
+        match self {
+            QueryCtrlResponse::Profile(ps) => {
+                w.write_obj(virtio_video_query_control_resp_profile {
+                    num: Le32::from(ps.len() as u32),
+                    ..Default::default()
+                })?;
+                w.write_iter(ps.iter().map(|p| Le32::from(*p as u32)))
+            }
+            QueryCtrlResponse::Level(ls) => {
+                w.write_obj(virtio_video_query_control_resp_level {
+                    num: Le32::from(ls.len() as u32),
+                    ..Default::default()
+                })?;
+                w.write_iter(ls.iter().map(|l| Le32::from(*l as u32)))
+            }
+        }
+    }
+}
+
+#[derive(Debug)]
+pub enum CtrlType {
+    Bitrate,
+    Profile,
+    Level,
+}
+
+#[derive(Debug)]
+pub enum CtrlVal {
+    Bitrate(u32),
+    Profile(Profile),
+    Level(Level),
+}
+
+impl Response for CtrlVal {
+    fn write(&self, w: &mut Writer) -> Result<(), io::Error> {
+        match self {
+            CtrlVal::Bitrate(r) => w.write_obj(virtio_video_control_val_bitrate {
+                bitrate: Le32::from(*r),
+                ..Default::default()
+            }),
+            CtrlVal::Profile(p) => w.write_obj(virtio_video_control_val_profile {
+                profile: Le32::from(*p as u32),
+                ..Default::default()
+            }),
+            CtrlVal::Level(l) => w.write_obj(virtio_video_control_val_level {
+                level: Le32::from(*l as u32),
+                ..Default::default()
+            }),
+        }
+    }
+}
diff --git a/devices/src/virtio/video/decoder/capability.rs b/devices/src/virtio/video/decoder/capability.rs
new file mode 100644
index 0000000..b6d09af
--- /dev/null
+++ b/devices/src/virtio/video/decoder/capability.rs
@@ -0,0 +1,138 @@
+// 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.
+
+//! Capablities of the virtio video decoder device.
+
+use std::collections::BTreeMap;
+
+use crate::virtio::video::control::*;
+use crate::virtio::video::format::*;
+
+fn from_input_format(fmt: &libvda::InputFormat, mask: u64) -> FormatDesc {
+    let format = match fmt.profile {
+        libvda::Profile::VP8 => Format::VP8,
+        libvda::Profile::VP9Profile0 => Format::VP9,
+        libvda::Profile::H264 => Format::H264,
+    };
+    FormatDesc {
+        mask,
+        format,
+        frame_formats: vec![Default::default()],
+    }
+}
+
+fn from_pixel_format(
+    fmt: &libvda::PixelFormat,
+    mask: u64,
+    width_range: FormatRange,
+    height_range: FormatRange,
+) -> FormatDesc {
+    let format = match fmt {
+        libvda::PixelFormat::NV12 => Format::NV12,
+        libvda::PixelFormat::YV12 => Format::YUV420,
+    };
+
+    let frame_formats = vec![FrameFormat {
+        width: width_range,
+        height: height_range,
+        bitrates: Vec::new(),
+    }];
+
+    FormatDesc {
+        mask,
+        format,
+        frame_formats,
+    }
+}
+
+pub struct Capability {
+    pub in_fmts: Vec<FormatDesc>,
+    pub out_fmts: Vec<FormatDesc>,
+
+    // Stores supporterd profiles and levels for each format.
+    profiles: BTreeMap<Format, Vec<Profile>>,
+    levels: BTreeMap<Format, Vec<Level>>,
+}
+
+impl Capability {
+    pub fn new(caps: &libvda::Capabilities) -> Self {
+        // Raise the first |# of supported raw formats|-th bits because we can assume that any
+        // combination of (a coded format, a raw format) is valid in Chrome.
+        let mask = !(u64::max_value() << caps.output_formats.len());
+        let in_fmts = caps
+            .input_formats
+            .iter()
+            .map(|fmt| from_input_format(fmt, mask))
+            .collect();
+
+        // Prepare {min, max} of {width, height}.
+        // While these values are associated with each input format in libvda,
+        // they are associated with each output format in virtio-video protocol.
+        // Thus, we compute max of min values and min of max values here.
+        let min_width = caps.input_formats.iter().map(|fmt| fmt.min_width).max();
+        let max_width = caps.input_formats.iter().map(|fmt| fmt.max_width).min();
+        let min_height = caps.input_formats.iter().map(|fmt| fmt.min_height).max();
+        let max_height = caps.input_formats.iter().map(|fmt| fmt.max_height).min();
+        let width_range = FormatRange {
+            min: min_width.unwrap_or(0),
+            max: max_width.unwrap_or(0),
+            step: 1,
+        };
+        let height_range = FormatRange {
+            min: min_height.unwrap_or(0),
+            max: max_height.unwrap_or(0),
+            step: 1,
+        };
+
+        // Raise the first |# of supported coded formats|-th bits because we can assume that any
+        // combination of (a coded format, a raw format) is valid in Chrome.
+        let mask = !(u64::max_value() << caps.input_formats.len());
+        let out_fmts = caps
+            .output_formats
+            .iter()
+            .map(|fmt| from_pixel_format(fmt, mask, width_range, height_range))
+            .collect();
+
+        let mut profiles: BTreeMap<Format, Vec<Profile>> = Default::default();
+        let mut levels: BTreeMap<Format, Vec<Level>> = Default::default();
+        for fmt in caps.input_formats.iter() {
+            match fmt.profile {
+                libvda::Profile::VP8 => {
+                    profiles.insert(Format::VP8, vec![Profile::VP8Profile0]);
+                }
+                libvda::Profile::VP9Profile0 => {
+                    profiles.insert(Format::VP9, vec![Profile::VP9Profile0]);
+                }
+                libvda::Profile::H264 => {
+                    profiles.insert(Format::H264, vec![Profile::H264Baseline]);
+                    levels.insert(Format::H264, vec![Level::H264_1_0]);
+                }
+            };
+        }
+
+        Capability {
+            in_fmts,
+            out_fmts,
+            profiles,
+            levels,
+        }
+    }
+
+    pub fn query_control(&self, t: &QueryCtrlType) -> Option<QueryCtrlResponse> {
+        use QueryCtrlType::*;
+        match *t {
+            Profile(fmt) => {
+                let profiles = self.profiles.get(&fmt)?;
+                Some(QueryCtrlResponse::Profile(
+                    profiles.iter().copied().collect(),
+                ))
+            }
+            Level(fmt) => {
+                let levels = self.levels.get(&fmt)?;
+                Some(QueryCtrlResponse::Level(levels.iter().copied().collect()))
+            }
+            _ => None,
+        }
+    }
+}
diff --git a/devices/src/virtio/video/decoder/mod.rs b/devices/src/virtio/video/decoder/mod.rs
new file mode 100644
index 0000000..3516cad
--- /dev/null
+++ b/devices/src/virtio/video/decoder/mod.rs
@@ -0,0 +1,976 @@
+// 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.
+
+//! Implementation of a virtio video decoder device backed by LibVDA.
+
+use std::collections::btree_map::Entry;
+use std::collections::BTreeMap;
+use std::convert::TryInto;
+use std::fs::File;
+use std::os::unix::io::{AsRawFd, IntoRawFd};
+
+use sys_util::{error, PollContext};
+
+use crate::virtio::resource_bridge::{self, ResourceInfo, ResourceRequestSocket};
+use crate::virtio::video::command::{QueueType, VideoCmd};
+use crate::virtio::video::control::{CtrlType, CtrlVal, QueryCtrlResponse, QueryCtrlType};
+use crate::virtio::video::device::*;
+use crate::virtio::video::error::*;
+use crate::virtio::video::event::*;
+use crate::virtio::video::format::*;
+use crate::virtio::video::params::Params;
+use crate::virtio::video::response::CmdResponse;
+
+mod capability;
+use capability::*;
+
+type StreamId = u32;
+type ResourceId = u32;
+
+// ResourceId given by the driver
+type InputResourceId = u32;
+type OutputResourceId = u32;
+
+// Id for a frame buffer passed to Chrome.
+// We cannot use OutputResourceId as is because this ID must be between 0 and ((# of buffers) - 1).
+//
+// TODO(b/1518105): Once we decide to generate resource_id in the device side,
+// we don't need this value and can pass OutputResourceId to Chrome directly.
+type FrameBufferId = i32;
+
+type ResourceHandle = u32;
+type Timestamp = u64;
+
+// Represents queue types of pending Clear commands if exist.
+#[derive(Default)]
+struct PendingClearCmds {
+    input: bool,
+    output: bool,
+}
+
+// Context is associated with one `libvda::Session`, which corresponds to one stream from the
+// virtio-video's point of view.
+#[derive(Default)]
+struct Context {
+    stream_id: StreamId,
+
+    in_params: Params,
+    out_params: Params,
+
+    // Timestamp -> InputResourceId
+    timestamp_to_input_res_id: BTreeMap<Timestamp, InputResourceId>,
+    // {Input,Output}ResourceId -> ResourceHandle
+    res_id_to_res_handle: BTreeMap<u32, ResourceHandle>,
+
+    // OutputResourceId <-> FrameBufferId
+    res_id_to_frame_buf_id: BTreeMap<OutputResourceId, FrameBufferId>,
+    frame_buf_id_to_res_id: BTreeMap<FrameBufferId, OutputResourceId>,
+
+    keep_resources: Vec<File>,
+
+    // Stores queue types of pending Clear commands if exist.
+    // This is needed because libvda's Reset API clears both queues while virtio-video's Clear is
+    // called for each queue.
+    pending_clear_cmds: PendingClearCmds,
+
+    // This is a flag that shows whether libvda's set_output_buffer_count is called.
+    // This will be set to true when ResourceCreate for OutputBuffer is called for the first time.
+    //
+    // TODO(b/1518105): This field is added as a hack because the current virtio-video v3 spec
+    // doesn't have a way to send a number of frame buffers the guest provides.
+    // Once we have the way in the virtio-video protocol, we should remove this flag.
+    set_output_buffer_count: bool,
+
+    // Reserves output resource that will be used to notify EOS.
+    // This resource must not be enqueued to Chrome.
+    keep_notification_output_buffer: Option<OutputResourceId>,
+}
+
+impl Context {
+    fn new(stream_id: StreamId, format: Format) -> Self {
+        Context {
+            stream_id,
+            in_params: Params {
+                format: Some(format),
+                min_buffers: 2,
+                max_buffers: 32,
+                plane_formats: vec![Default::default()],
+                ..Default::default()
+            },
+            out_params: Default::default(),
+            set_output_buffer_count: false,
+            ..Default::default()
+        }
+    }
+
+    fn get_resource_info(
+        &self,
+        res_bridge: &ResourceRequestSocket,
+        resource_id: u32,
+    ) -> VideoResult<ResourceInfo> {
+        let handle = self.res_id_to_res_handle.get(&resource_id).copied().ok_or(
+            VideoError::InvalidResourceId {
+                stream_id: self.stream_id,
+                resource_id,
+            },
+        )?;
+        resource_bridge::get_resource_info(res_bridge, handle)
+            .map_err(VideoError::ResourceBridgeFailure)
+    }
+
+    fn register_buffer(&mut self, resource_id: u32, uuid: &u128) {
+        // TODO(stevensd): `Virtio3DBackend::resource_assign_uuid` is currently implemented to use
+        // 32-bits resource_handles as UUIDs. Once it starts using real UUIDs, we need to update
+        // this conversion.
+        let handle = TryInto::<u32>::try_into(*uuid).expect("uuid is larger than 32 bits");
+        self.res_id_to_res_handle.insert(resource_id, handle);
+    }
+
+    fn register_queued_frame_buffer(&mut self, resource_id: OutputResourceId) -> FrameBufferId {
+        // Generate a new FrameBufferId
+        let id = self.res_id_to_frame_buf_id.len() as FrameBufferId;
+        self.res_id_to_frame_buf_id.insert(resource_id, id);
+        self.frame_buf_id_to_res_id.insert(id, resource_id);
+        id
+    }
+
+    fn reset(&mut self) {
+        // Reset `Context` except parameters.
+        *self = Context {
+            stream_id: self.stream_id,
+            in_params: self.in_params.clone(),
+            out_params: self.out_params.clone(),
+            ..Default::default()
+        }
+    }
+
+    /*
+     * Functions handling libvda events.
+     */
+
+    fn handle_provide_picture_buffers(
+        &mut self,
+        min_num_buffers: u32,
+        width: i32,
+        height: i32,
+        visible_rect_left: i32,
+        visible_rect_top: i32,
+        visible_rect_right: i32,
+        visible_rect_bottom: i32,
+    ) {
+        // We only support NV12.
+        let format = Some(Format::NV12);
+
+        let rect_width: u32 = (visible_rect_right - visible_rect_left) as u32;
+        let rect_height: u32 = (visible_rect_bottom - visible_rect_top) as u32;
+
+        let plane_size = rect_width * rect_height;
+        let stride = rect_width;
+        let plane_formats = vec![
+            PlaneFormat { plane_size, stride },
+            PlaneFormat { plane_size, stride },
+        ];
+
+        self.out_params = Params {
+            format,
+            // Note that rect_width is sometimes smaller.
+            frame_width: width as u32,
+            frame_height: height as u32,
+            // Adding 1 to `min_buffers` to reserve a resource for `keep_notification_output_buffer`.
+            min_buffers: min_num_buffers + 1,
+            max_buffers: 32,
+            crop: Crop {
+                left: visible_rect_left as u32,
+                top: visible_rect_top as u32,
+                width: rect_width,
+                height: rect_height,
+            },
+            plane_formats,
+            // No need to set `frame_rate`, as it's only for the encoder.
+            ..Default::default()
+        };
+    }
+
+    fn handle_picture_ready(
+        &mut self,
+        buffer_id: FrameBufferId,
+        left: i32,
+        top: i32,
+        right: i32,
+        bottom: i32,
+    ) -> Option<ResourceId> {
+        let plane_size = ((right - left) * (bottom - top)) as u32;
+        for fmt in self.out_params.plane_formats.iter_mut() {
+            fmt.plane_size = plane_size;
+            // We don't need to set `plane_formats[i].stride` for the decoder.
+        }
+
+        let resource_id: OutputResourceId = match self.frame_buf_id_to_res_id.get(&buffer_id) {
+            Some(id) => *id,
+            None => {
+                error!(
+                    "unknown frame buffer id {} for stream {:?}",
+                    buffer_id, self.stream_id
+                );
+                return None;
+            }
+        };
+
+        Some(resource_id)
+    }
+
+    fn handle_notify_end_of_bitstream_buffer(&mut self, bitstream_id: i32) -> Option<ResourceId> {
+        // `bitstream_id` in libvda is a timestamp passed via RESOURCE_QUEUE for the input buffer
+        // in second.
+        let timestamp: u64 = (bitstream_id as u64) * 1_000_000_000;
+        self.timestamp_to_input_res_id
+            .remove(&(timestamp as u64))
+            .or_else(|| {
+                error!("failed to remove a timestamp {}", timestamp);
+                None
+            })
+    }
+
+    fn handle_reset_response(&mut self) -> Option<QueueType> {
+        if self.pending_clear_cmds.input {
+            self.pending_clear_cmds.input = false;
+            Some(QueueType::Input)
+        } else if self.pending_clear_cmds.output {
+            self.pending_clear_cmds.output = false;
+
+            Some(QueueType::Output)
+        } else {
+            error!("unexpected ResetResponse");
+            None
+        }
+    }
+}
+
+/// A thin wrapper of a map of contexts with error handlings.
+#[derive(Default)]
+struct ContextMap {
+    map: BTreeMap<StreamId, Context>,
+}
+
+impl ContextMap {
+    fn insert(&mut self, ctx: Context) -> VideoResult<()> {
+        match self.map.entry(ctx.stream_id) {
+            Entry::Vacant(e) => {
+                e.insert(ctx);
+                Ok(())
+            }
+            Entry::Occupied(_) => {
+                error!("session {} already exists", ctx.stream_id);
+                Err(VideoError::InvalidStreamId(ctx.stream_id))
+            }
+        }
+    }
+
+    fn get(&self, stream_id: &StreamId) -> VideoResult<&Context> {
+        self.map.get(&stream_id).ok_or_else(|| {
+            error!("failed to get context of stream {}", *stream_id);
+            VideoError::InvalidStreamId(*stream_id)
+        })
+    }
+
+    fn get_mut(&mut self, stream_id: &StreamId) -> VideoResult<&mut Context> {
+        self.map.get_mut(&stream_id).ok_or_else(|| {
+            error!("failed to get context of stream {}", *stream_id);
+            VideoError::InvalidStreamId(*stream_id)
+        })
+    }
+}
+
+/// A thin wrapper of a map of libvda sesssions with error handlings.
+#[derive(Default)]
+struct SessionMap<'a> {
+    map: BTreeMap<u32, libvda::Session<'a>>,
+}
+
+impl<'a> SessionMap<'a> {
+    fn contains_key(&self, stream_id: StreamId) -> bool {
+        self.map.contains_key(&stream_id)
+    }
+
+    fn get(&self, stream_id: &StreamId) -> VideoResult<&libvda::Session<'a>> {
+        self.map.get(&stream_id).ok_or_else(|| {
+            error!("failed to get libvda session {}", *stream_id);
+            VideoError::InvalidStreamId(*stream_id)
+        })
+    }
+
+    fn get_mut(&mut self, stream_id: &StreamId) -> VideoResult<&mut libvda::Session<'a>> {
+        self.map.get_mut(&stream_id).ok_or_else(|| {
+            error!("failed to get libvda session {}", *stream_id);
+            VideoError::InvalidStreamId(*stream_id)
+        })
+    }
+
+    fn insert(
+        &mut self,
+        stream_id: StreamId,
+        session: libvda::Session<'a>,
+    ) -> Option<libvda::Session<'a>> {
+        self.map.insert(stream_id, session)
+    }
+}
+
+/// Represents information of a decoder backed with `libvda`.
+pub struct Decoder<'a> {
+    vda: &'a libvda::VdaInstance,
+    capability: Capability,
+    contexts: ContextMap,
+    sessions: SessionMap<'a>,
+}
+
+impl<'a> Decoder<'a> {
+    pub fn new(vda: &'a libvda::VdaInstance) -> Self {
+        let capability = Capability::new(vda.get_capabilities());
+        Decoder {
+            vda,
+            capability,
+            contexts: Default::default(),
+            sessions: Default::default(),
+        }
+    }
+
+    /*
+     * Functions processing virtio-video commands.
+     */
+
+    fn query_capabilities(&self, queue_type: QueueType) -> CmdResponse {
+        let descs = match queue_type {
+            QueueType::Input => self.capability.in_fmts.clone(),
+            QueueType::Output => self.capability.out_fmts.clone(),
+        };
+
+        CmdResponse::QueryCapability(descs)
+    }
+
+    fn create_stream(&mut self, stream_id: StreamId, coded_format: Format) -> VideoResult<()> {
+        // Create an instance of `Context`.
+        // Note that `libvda::Session` will be created not here but at the first call of
+        // `ResourceCreate`. This is because we need to fix a coded format for it, which
+        // will be set by `SetParams`.
+        self.contexts.insert(Context::new(stream_id, coded_format))
+    }
+
+    fn destroy_stream(&mut self, stream_id: StreamId) {
+        if self.contexts.map.remove(&stream_id).is_none() {
+            error!("Tried to destroy an invalid stream context {}", stream_id);
+        }
+
+        // Close a libVDA session, as closing will be done in `Drop` for `session`.
+        // Note that `sessions` doesn't have an instance for `stream_id` if the
+        // first `ResourceCreate` haven't been called yet.
+        self.sessions.map.remove(&stream_id);
+    }
+
+    fn create_resource(
+        &mut self,
+        poll_ctx: &PollContext<Token>,
+        stream_id: StreamId,
+        queue_type: QueueType,
+        resource_id: ResourceId,
+        uuid: u128,
+    ) -> VideoResult<()> {
+        // Create a instance of `libvda::Session` at the first time `ResourceCreate` is
+        // called here.
+        if !self.sessions.contains_key(stream_id) {
+            let ctx = self.contexts.get(&stream_id)?;
+            let profile = match ctx.in_params.format {
+                Some(Format::VP8) => Ok(libvda::Profile::VP8),
+                Some(Format::VP9) => Ok(libvda::Profile::VP9Profile0),
+                Some(Format::H264) => Ok(libvda::Profile::H264),
+                Some(f) => {
+                    error!("specified format is invalid for bitstream: {:?}", f);
+                    Err(VideoError::InvalidParameter)
+                }
+                None => {
+                    error!("bitstream format is not specified");
+                    Err(VideoError::InvalidParameter)
+                }
+            }?;
+
+            let session = self.vda.open_session(profile).map_err(|e| {
+                error!(
+                    "failed to open a session {} for {:?}: {}",
+                    stream_id, profile, e
+                );
+                VideoError::InvalidOperation
+            })?;
+
+            poll_ctx
+                .add(session.pipe(), Token::EventFd { id: stream_id })
+                .map_err(|e| {
+                    error!(
+                        "failed to add FD to poll context for session {}: {}",
+                        stream_id, e
+                    );
+                    VideoError::InvalidOperation
+                })?;
+
+            self.sessions.insert(stream_id, session);
+        }
+
+        self.contexts
+            .get_mut(&stream_id)?
+            .register_buffer(resource_id, &uuid);
+
+        if queue_type == QueueType::Input {
+            return Ok(());
+        };
+
+        // Set output_buffer_count when ResourceCreate is called for frame buffers for the
+        // first time.
+        let mut ctx = self.contexts.get_mut(&stream_id)?;
+        if !ctx.set_output_buffer_count {
+            const OUTPUT_BUFFER_COUNT: usize = 32;
+
+            // Set the buffer count to the maximum value.
+            // TODO(b/1518105): This is a hack due to the lack of way of telling a number of
+            // frame buffers explictly in virtio-video v3 RFC. Once we have the way,
+            // set_output_buffer_count should be called with a value passed by the guest.
+            self.sessions
+                .get(&stream_id)?
+                .set_output_buffer_count(OUTPUT_BUFFER_COUNT)
+                .map_err(VideoError::VdaError)?;
+            ctx.set_output_buffer_count = true;
+        }
+
+        // We assume ResourceCreate is not called to an output resource that is already
+        // imported to Chrome for now.
+        // TODO(keiichiw): We need to support this case for a guest client who may use
+        // arbitrary numbers of buffers. (e.g. C2V4L2Component in ARCVM)
+        // Such a client is valid as long as it uses at most 32 buffers at the same time.
+        if let Some(frame_buf_id) = ctx.res_id_to_frame_buf_id.get(&resource_id) {
+            error!(
+                "resource {} has already been imported to Chrome as a frame buffer {}",
+                resource_id, frame_buf_id
+            );
+            return Err(VideoError::InvalidOperation);
+        }
+
+        Ok(())
+    }
+
+    fn destroy_all_resources(&mut self, stream_id: StreamId) -> VideoResult<()> {
+        // Reset the associated context.
+        self.contexts.get_mut(&stream_id)?.reset();
+        Ok(())
+    }
+
+    fn queue_input_resource(
+        &mut self,
+        resource_bridge: &ResourceRequestSocket,
+        stream_id: StreamId,
+        resource_id: ResourceId,
+        timestamp: u64,
+        data_sizes: Vec<u32>,
+    ) -> VideoResult<()> {
+        let session = self.sessions.get(&stream_id)?;
+
+        if data_sizes.len() != 1 {
+            error!("num_data_sizes must be 1 but {}", data_sizes.len());
+            return Err(VideoError::InvalidOperation);
+        }
+
+        // Take an ownership of this file by `into_raw_fd()` as this file will be closed by libvda.
+        let fd = self
+            .contexts
+            .get_mut(&stream_id)?
+            .get_resource_info(resource_bridge, resource_id)?
+            .file
+            .into_raw_fd();
+
+        // Register  a mapping of timestamp to resource_id
+        self.contexts
+            .get_mut(&stream_id)?
+            .timestamp_to_input_res_id
+            .insert(timestamp, resource_id);
+
+        // While the virtio-video driver handles timestamps as nanoseconds,
+        // Chrome assumes per-second timestamps coming. So, we need a conversion from nsec
+        // to sec.
+        // Note that this value should not be an unix time stamp but a frame number that
+        // a guest passes to a driver as a 32-bit integer in our implementation.
+        // So, overflow must not happen in this conversion.
+        let ts_sec: i32 = (timestamp / 1_000_000_000) as i32;
+        session
+            .decode(
+                ts_sec,
+                fd,            // fd
+                0,             // offset is always 0 due to the driver implementation.
+                data_sizes[0], // bytes_used
+            )
+            .map_err(VideoError::VdaError)?;
+
+        Ok(())
+    }
+
+    fn queue_output_resource(
+        &mut self,
+        resource_bridge: &ResourceRequestSocket,
+        stream_id: StreamId,
+        resource_id: ResourceId,
+    ) -> VideoResult<()> {
+        let session = self.sessions.get(&stream_id)?;
+        let ctx = self.contexts.get_mut(&stream_id)?;
+
+        // Check if the current pixel format is set to NV12.
+        match ctx.out_params.format {
+            Some(Format::NV12) => (), // OK
+            Some(f) => {
+                error!(
+                    "video decoder only supports NV12 as a frame format but {:?}",
+                    f
+                );
+                return Err(VideoError::InvalidOperation);
+            }
+            None => {
+                error!("output format is not set");
+                return Err(VideoError::InvalidOperation);
+            }
+        };
+
+        if ctx.keep_notification_output_buffer.is_none() {
+            // Stores an output buffer to notify EOS.
+            ctx.keep_notification_output_buffer = Some(resource_id);
+
+            // Don't enqueue this resource to Chrome.
+            return Ok(());
+        }
+
+        // In case a given resource has been imported to VDA, call
+        // `session.reuse_output_buffer()` and return a response.
+        // Otherwise, `session.use_output_buffer()` will be called below.
+        match ctx.res_id_to_frame_buf_id.get(&resource_id) {
+            Some(buffer_id) => {
+                session
+                    .reuse_output_buffer(*buffer_id)
+                    .map_err(VideoError::VdaError)?;
+                return Ok(());
+            }
+            None => (),
+        };
+
+        let resource_info = ctx.get_resource_info(resource_bridge, resource_id)?;
+        let fd = resource_info.file.as_raw_fd();
+
+        // Take an ownership of `resource_info.file`.
+        // This file will be kept until the stream is destroyed.
+        self.contexts
+            .get_mut(&stream_id)?
+            .keep_resources
+            .push(resource_info.file);
+
+        let planes = vec![
+            libvda::FramePlane {
+                offset: resource_info.planes[0].offset as i32,
+                stride: resource_info.planes[0].stride as i32,
+            },
+            libvda::FramePlane {
+                offset: resource_info.planes[1].offset as i32,
+                stride: resource_info.planes[1].stride as i32,
+            },
+        ];
+
+        let buffer_id = self
+            .contexts
+            .get_mut(&stream_id)?
+            .register_queued_frame_buffer(resource_id);
+
+        session
+            .use_output_buffer(buffer_id as i32, libvda::PixelFormat::NV12, fd, &planes)
+            .map_err(VideoError::VdaError)?;
+
+        Ok(())
+    }
+
+    fn get_params(&self, stream_id: StreamId, queue_type: QueueType) -> VideoResult<Params> {
+        let ctx = self.contexts.get(&stream_id)?;
+        Ok(match queue_type {
+            QueueType::Input => ctx.in_params.clone(),
+            QueueType::Output => ctx.out_params.clone(),
+        })
+    }
+
+    fn set_params(
+        &mut self,
+        stream_id: StreamId,
+        queue_type: QueueType,
+        params: Params,
+    ) -> VideoResult<()> {
+        let ctx = self.contexts.get_mut(&stream_id)?;
+        match queue_type {
+            QueueType::Input => {
+                if self.sessions.contains_key(stream_id) {
+                    error!("parameter for input cannot be changed once decoding started");
+                    return Err(VideoError::InvalidParameter);
+                }
+
+                // Only a few parameters can be changed by the guest.
+                ctx.in_params.format = params.format;
+                ctx.in_params.plane_formats = params.plane_formats.clone();
+            }
+            QueueType::Output => {
+                // The guest cannot update parameters for output queue in the decoder.
+            }
+        };
+        Ok(())
+    }
+
+    fn query_control(&self, ctrl_type: QueryCtrlType) -> VideoResult<QueryCtrlResponse> {
+        self.capability.query_control(&ctrl_type).ok_or_else(|| {
+            error!("querying an unsupported control: {:?}", ctrl_type);
+            VideoError::InvalidArgument
+        })
+    }
+
+    fn get_control(&self, stream_id: StreamId, ctrl_type: CtrlType) -> VideoResult<CtrlVal> {
+        let ctx = self.contexts.get(&stream_id)?;
+        match ctrl_type {
+            CtrlType::Profile => {
+                let profile = match ctx.in_params.format {
+                    Some(Format::VP8) => Profile::VP8Profile0,
+                    Some(Format::VP9) => Profile::VP9Profile0,
+                    Some(Format::H264) => Profile::H264Baseline,
+                    Some(f) => {
+                        error!("specified format is invalid: {:?}", f);
+                        return Err(VideoError::InvalidArgument);
+                    }
+                    None => {
+                        error!("bitstream format is not set");
+                        return Err(VideoError::InvalidArgument);
+                    }
+                };
+
+                Ok(CtrlVal::Profile(profile))
+            }
+            CtrlType::Level => {
+                let level = match ctx.in_params.format {
+                    Some(Format::H264) => Level::H264_1_0,
+                    Some(f) => {
+                        error!("specified format has no level: {:?}", f);
+                        return Err(VideoError::InvalidArgument);
+                    }
+                    None => {
+                        error!("bitstream format is not set");
+                        return Err(VideoError::InvalidArgument);
+                    }
+                };
+
+                Ok(CtrlVal::Level(level))
+            }
+            t => {
+                error!("cannot get a control value: {:?}", t);
+                Err(VideoError::InvalidArgument)
+            }
+        }
+    }
+
+    fn drain_stream(&mut self, stream_id: StreamId) -> VideoResult<()> {
+        self.sessions
+            .get(&stream_id)?
+            .flush()
+            .map_err(VideoError::VdaError)?;
+        Ok(())
+    }
+
+    fn clear_queue(&mut self, stream_id: StreamId, queue_type: QueueType) -> VideoResult<()> {
+        let ctx = self.contexts.get_mut(&stream_id)?;
+        let session = self.sessions.get(&stream_id)?;
+
+        let pending_clear_cmd = match queue_type {
+            QueueType::Input => &mut ctx.pending_clear_cmds.input,
+            QueueType::Output => &mut ctx.pending_clear_cmds.output,
+        };
+
+        if *pending_clear_cmd {
+            error!("Clear command is already in process");
+            return Err(VideoError::InvalidOperation);
+        }
+
+        // TODO(b/153406792): Though QUEUE_CLEAR is defined as a per-queue command in the
+        // specification, Chrome VDA's `reset()` clears both input and output buffers.
+        // So, this code can be a problem when a guest application wants to reset only one
+        // queue by REQBUFS(0).
+        // To handle this problem, we need to
+        // (i) update libvda interface or,
+        // (ii) re-enqueue discarded requests that were pending for the other direction.
+        session.reset().map_err(VideoError::VdaError)?;
+
+        *pending_clear_cmd = true;
+
+        Ok(())
+    }
+}
+
+impl<'a> Device for Decoder<'a> {
+    fn process_cmd(
+        &mut self,
+        cmd: VideoCmd,
+        poll_ctx: &PollContext<Token>,
+        resource_bridge: &ResourceRequestSocket,
+    ) -> VideoResult<VideoCmdResponseType> {
+        use VideoCmd::*;
+        use VideoCmdResponseType::{Async, Sync};
+
+        match cmd {
+            QueryCapability { queue_type } => Ok(Sync(self.query_capabilities(queue_type))),
+            StreamCreate {
+                stream_id,
+                coded_format,
+            } => {
+                self.create_stream(stream_id, coded_format)?;
+                Ok(Sync(CmdResponse::NoData))
+            }
+            StreamDestroy { stream_id } => {
+                self.destroy_stream(stream_id);
+                Ok(Sync(CmdResponse::NoData))
+            }
+            ResourceCreate {
+                stream_id,
+                queue_type,
+                resource_id,
+                uuid,
+                // ignore `plane_offsets` as we use `resource_info` given by `resource_bridge` instead.
+                ..
+            } => {
+                self.create_resource(poll_ctx, stream_id, queue_type, resource_id, uuid)?;
+                Ok(Sync(CmdResponse::NoData))
+            }
+            ResourceDestroyAll { stream_id } => {
+                self.destroy_all_resources(stream_id)?;
+                Ok(Sync(CmdResponse::NoData))
+            }
+            ResourceQueue {
+                stream_id,
+                queue_type: QueueType::Input,
+                resource_id,
+                timestamp,
+                data_sizes,
+            } => {
+                self.queue_input_resource(
+                    resource_bridge,
+                    stream_id,
+                    resource_id,
+                    timestamp,
+                    data_sizes,
+                )?;
+                Ok(Async(AsyncCmdTag::Queue {
+                    stream_id,
+                    queue_type: QueueType::Input,
+                    resource_id,
+                }))
+            }
+            ResourceQueue {
+                stream_id,
+                queue_type: QueueType::Output,
+                resource_id,
+                ..
+            } => {
+                self.queue_output_resource(resource_bridge, stream_id, resource_id)?;
+                Ok(Async(AsyncCmdTag::Queue {
+                    stream_id,
+                    queue_type: QueueType::Output,
+                    resource_id,
+                }))
+            }
+            GetParams {
+                stream_id,
+                queue_type,
+            } => {
+                let params = self.get_params(stream_id, queue_type)?;
+                Ok(Sync(CmdResponse::GetParams { queue_type, params }))
+            }
+            SetParams {
+                stream_id,
+                queue_type,
+                params,
+            } => {
+                self.set_params(stream_id, queue_type, params)?;
+                Ok(Sync(CmdResponse::NoData))
+            }
+            QueryControl { query_ctrl_type } => {
+                let resp = self.query_control(query_ctrl_type)?;
+                Ok(Sync(CmdResponse::QueryControl(resp)))
+            }
+            GetControl {
+                stream_id,
+                ctrl_type,
+            } => {
+                let ctrl_val = self.get_control(stream_id, ctrl_type)?;
+                Ok(Sync(CmdResponse::GetControl(ctrl_val)))
+            }
+            SetControl { .. } => {
+                error!("SET_CONTROL is not allowed for decoder");
+                Err(VideoError::InvalidOperation)
+            }
+            StreamDrain { stream_id } => {
+                self.drain_stream(stream_id)?;
+                Ok(Async(AsyncCmdTag::Drain { stream_id }))
+            }
+            QueueClear {
+                stream_id,
+                queue_type,
+            } => {
+                self.clear_queue(stream_id, queue_type)?;
+                Ok(Async(AsyncCmdTag::Clear {
+                    stream_id,
+                    queue_type,
+                }))
+            }
+        }
+    }
+
+    fn process_event_fd(&mut self, stream_id: u32) -> Option<VideoEvtResponseType> {
+        use crate::virtio::video::device::VideoEvtResponseType::*;
+        use libvda::Event::*;
+
+        let session = match self.sessions.get_mut(&stream_id) {
+            Ok(s) => s,
+            Err(_) => {
+                error!("an event notified for an unknown session {}", stream_id);
+                return None;
+            }
+        };
+
+        let event = match session.read_event() {
+            Ok(event) => event,
+            Err(e) => {
+                error!("failed to read an event from session {}: {}", stream_id, e);
+                return None;
+            }
+        };
+
+        let ctx = match self.contexts.get_mut(&stream_id) {
+            Ok(ctx) => ctx,
+            Err(_) => {
+                error!(
+                    "failed to get a context for session {}: {:?}",
+                    stream_id, event
+                );
+                return None;
+            }
+        };
+
+        match event {
+            ProvidePictureBuffers {
+                min_num_buffers,
+                width,
+                height,
+                visible_rect_left,
+                visible_rect_top,
+                visible_rect_right,
+                visible_rect_bottom,
+            } => {
+                ctx.handle_provide_picture_buffers(
+                    min_num_buffers,
+                    width,
+                    height,
+                    visible_rect_left,
+                    visible_rect_top,
+                    visible_rect_right,
+                    visible_rect_bottom,
+                );
+                Some(Event(VideoEvt {
+                    typ: EvtType::DecResChanged,
+                    stream_id,
+                }))
+            }
+            PictureReady {
+                buffer_id, // FrameBufferId
+                bitstream_id: ts_sec,
+                left,
+                top,
+                right,
+                bottom,
+            } => {
+                let resource_id = ctx.handle_picture_ready(buffer_id, left, top, right, bottom)?;
+                Some(AsyncCmd {
+                    tag: AsyncCmdTag::Queue {
+                        stream_id,
+                        queue_type: QueueType::Output,
+                        resource_id,
+                    },
+                    resp: Ok(CmdResponse::ResourceQueue {
+                        // Conversion from sec to nsec.
+                        timestamp: (ts_sec as u64) * 1_000_000_000,
+                        // TODO(b/149725148): Set buffer flags once libvda exposes them.
+                        flags: 0,
+                        // `size` is only used for the encoder.
+                        size: 0,
+                    }),
+                })
+            }
+            NotifyEndOfBitstreamBuffer { bitstream_id } => {
+                let resource_id = ctx.handle_notify_end_of_bitstream_buffer(bitstream_id)?;
+                Some(AsyncCmd {
+                    tag: AsyncCmdTag::Queue {
+                        stream_id,
+                        queue_type: QueueType::Input,
+                        resource_id,
+                    },
+                    resp: Ok(CmdResponse::ResourceQueue {
+                        timestamp: 0, // ignored for bitstream buffers.
+                        flags: 0,     // no flag is raised, as it's returned successfully.
+                        size: 0,      // this field is only for encoder
+                    }),
+                })
+            }
+            FlushResponse(resp) => {
+                let tag = AsyncCmdTag::Drain { stream_id };
+                match resp {
+                    libvda::Response::Success => Some(AsyncCmd {
+                        tag,
+                        resp: Ok(CmdResponse::NoData),
+                    }),
+                    _ => {
+                        // TODO(b/151810591): If `resp` is `libvda::Response::Canceled`,
+                        // we should notify it to the driver in some way.
+                        error!("failed to 'Flush' in VDA: {:?}", resp);
+                        Some(AsyncCmd {
+                            tag,
+                            resp: Err(VideoError::VdaFailure(resp)),
+                        })
+                    }
+                }
+            }
+            ResetResponse(resp) => {
+                let tag = AsyncCmdTag::Clear {
+                    stream_id,
+                    queue_type: ctx.handle_reset_response()?,
+                };
+                match resp {
+                    libvda::Response::Success => Some(AsyncCmd {
+                        tag,
+                        resp: Ok(CmdResponse::NoData),
+                    }),
+                    _ => {
+                        error!("failed to 'Reset' in VDA: {:?}", resp);
+                        Some(AsyncCmd {
+                            tag,
+                            resp: Err(VideoError::VdaFailure(resp)),
+                        })
+                    }
+                }
+            }
+            NotifyError(resp) => {
+                error!("an error is notified by VDA: {}", resp);
+                Some(Event(VideoEvt {
+                    typ: EvtType::Error,
+                    stream_id,
+                }))
+            }
+        }
+    }
+
+    fn take_resource_id_to_notify_eos(&mut self, stream_id: u32) -> Option<u32> {
+        self.contexts
+            .get_mut(&stream_id)
+            .ok()
+            .and_then(|s| s.keep_notification_output_buffer.take())
+    }
+}
diff --git a/devices/src/virtio/video/device.rs b/devices/src/virtio/video/device.rs
new file mode 100644
index 0000000..b9d80d6
--- /dev/null
+++ b/devices/src/virtio/video/device.rs
@@ -0,0 +1,91 @@
+// 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.
+
+//! Definition of the trait `Device` that each backend video device must implement.
+
+use sys_util::{PollContext, PollToken};
+
+use crate::virtio::resource_bridge::ResourceRequestSocket;
+use crate::virtio::video::command::{QueueType, VideoCmd};
+use crate::virtio::video::error::*;
+use crate::virtio::video::event::VideoEvt;
+use crate::virtio::video::response;
+
+#[derive(PollToken, Debug)]
+pub enum Token {
+    CmdQueue,
+    EventQueue,
+    EventFd { id: u32 },
+    Kill,
+    InterruptResample,
+}
+
+/// A tag for commands being processed asynchronously in the back-end device.
+/// TODO(b/149720783): Remove this enum by using async primitives.
+#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug)]
+pub enum AsyncCmdTag {
+    Queue {
+        stream_id: u32,
+        queue_type: QueueType,
+        resource_id: u32,
+    },
+    Drain {
+        stream_id: u32,
+    },
+    Clear {
+        stream_id: u32,
+        queue_type: QueueType,
+    },
+}
+
+/// A return value when a command from the guest is processed.
+#[derive(Debug)]
+pub enum VideoCmdResponseType {
+    /// The response for a synchronous command. This can be returned to the guest immediately via
+    /// command virtqueue.
+    Sync(response::CmdResponse),
+    /// The tag for an asynchronous command that the back-end device will complete.
+    /// Once the command is completed, its result will be sent with the same tag.
+    /// This can be seen as a poor man's future pattern.
+    Async(AsyncCmdTag),
+}
+
+/// A return value when processing a event the back-end device sent.
+#[derive(Debug)]
+pub enum VideoEvtResponseType {
+    /// The response for an asynchronous command that was enqueued through `process_cmd` before.
+    /// The `tag` must be same as the one returned when the command is enqueued.
+    AsyncCmd {
+        tag: AsyncCmdTag,
+        resp: VideoResult<response::CmdResponse>,
+    },
+    /// The event that happened in the back-end device.
+    Event(VideoEvt),
+}
+
+pub trait Device {
+    /// Processes a virtio-video command.
+    /// If the command expects a synchronous response, it returns a response as `VideoCmdResponseType::Sync`.
+    /// Otherwise, it returns a name of the descriptor chain that will be used when a response is prepared.
+    /// Implementations of this method is passed a PollContext object which can be used to add or remove
+    /// FDs to poll. It is expected that only Token::EventFd items would be added. When a Token::EventFd
+    /// event arrives, process_event_fd() will be invoked.
+    /// TODO(b/149720783): Make this an async function.
+    fn process_cmd(
+        &mut self,
+        cmd: VideoCmd,
+        poll_ctx: &PollContext<Token>,
+        resource_bridge: &ResourceRequestSocket,
+    ) -> VideoResult<VideoCmdResponseType>;
+
+    /// Processes an available Token::EventFd event.
+    /// If the message is sent via commandq, the return value is `VideoEvtResponseType::AsyncCmd`.
+    /// Otherwise (i.e. case of eventq), it's `VideoEvtResponseType::Event`.
+    /// TODO(b/149720783): Make this an async function.
+    fn process_event_fd(&mut self, stream_id: u32) -> Option<VideoEvtResponseType>;
+
+    /// Returns an ID for an available output resource that can be used to notify EOS.
+    /// Note that this resource must be enqueued by `ResourceQueue` and not be returned yet.
+    fn take_resource_id_to_notify_eos(&mut self, stream_id: u32) -> Option<u32>;
+}
diff --git a/devices/src/virtio/video/encoder/mod.rs b/devices/src/virtio/video/encoder/mod.rs
new file mode 100644
index 0000000..d6b8cef
--- /dev/null
+++ b/devices/src/virtio/video/encoder/mod.rs
@@ -0,0 +1,40 @@
+// 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.
+
+//! Implementation of the the `Encoder` struct, which is responsible for translation between the
+//! virtio protocols and LibVDA APIs.
+
+use sys_util::PollContext;
+
+use crate::virtio::resource_bridge::ResourceRequestSocket;
+use crate::virtio::video::command::VideoCmd;
+use crate::virtio::video::device::{Device, Token, VideoCmdResponseType, VideoEvtResponseType};
+use crate::virtio::video::error::*;
+
+pub struct Encoder;
+
+impl Encoder {
+    pub fn new() -> Self {
+        Encoder {}
+    }
+}
+
+impl Device for Encoder {
+    fn process_cmd(
+        &mut self,
+        _cmd: VideoCmd,
+        _poll_ctx: &PollContext<Token>,
+        _resource_bridge: &ResourceRequestSocket,
+    ) -> VideoResult<VideoCmdResponseType> {
+        Err(VideoError::InvalidOperation)
+    }
+
+    fn process_event_fd(&mut self, _stream_id: u32) -> Option<VideoEvtResponseType> {
+        None
+    }
+
+    fn take_resource_id_to_notify_eos(&mut self, _stream_id: u32) -> Option<u32> {
+        None
+    }
+}
diff --git a/devices/src/virtio/video/error.rs b/devices/src/virtio/video/error.rs
new file mode 100644
index 0000000..5357c1f
--- /dev/null
+++ b/devices/src/virtio/video/error.rs
@@ -0,0 +1,83 @@
+// 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.
+
+//! Errors that can happen while encoding or decoding.
+
+use std::fmt;
+use std::io;
+
+use data_model::Le32;
+
+use crate::virtio::resource_bridge::ResourceBridgeError;
+use crate::virtio::video::protocol::*;
+use crate::virtio::video::response::Response;
+use crate::virtio::Writer;
+
+/// An error indicating something went wrong while encoding or decoding.
+/// Unlike `virtio::video::Error`, `VideoError` is not fatal for `Worker`.
+#[derive(Debug)]
+pub enum VideoError {
+    /// Invalid argument.
+    InvalidArgument,
+    /// Invalid operation
+    InvalidOperation,
+    /// Invalid stream ID is specified.
+    InvalidStreamId(u32),
+    /// Invalid resource ID is specified.
+    InvalidResourceId { stream_id: u32, resource_id: u32 },
+    /// Invalid parameters are specified.
+    InvalidParameter,
+    /// Failed to get a resource FD via resource_bridge.
+    ResourceBridgeFailure(ResourceBridgeError),
+    /// `libvda` returned an error.
+    VdaError(libvda::Error),
+    /// `libvda` returned a failure response.
+    VdaFailure(libvda::Response),
+}
+
+impl fmt::Display for VideoError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        use self::VideoError::*;
+        match self {
+            InvalidArgument => write!(f, "invalid argument"),
+            InvalidOperation => write!(f, "invalid operation"),
+            InvalidStreamId(id) => write!(f, "invalid stream ID {}", id),
+            InvalidResourceId {
+                stream_id,
+                resource_id,
+            } => write!(
+                f,
+                "invalid resource ID {} for stream {}",
+                resource_id, stream_id
+            ),
+            InvalidParameter => write!(f, "invalid parameter"),
+            ResourceBridgeFailure(id) => write!(f, "failed to get resource FD for id {}", id),
+            VdaError(e) => write!(f, "error occurred in libvda: {}", e),
+            VdaFailure(r) => write!(f, "failed while processing a requst in VDA: {}", r),
+        }
+    }
+}
+
+impl std::error::Error for VideoError {}
+
+pub type VideoResult<T> = Result<T, VideoError>;
+
+impl Response for VideoError {
+    fn write(&self, w: &mut Writer) -> Result<(), io::Error> {
+        use VideoError::*;
+
+        let type_ = Le32::from(match *self {
+            InvalidResourceId { .. } => VIRTIO_VIDEO_RESP_ERR_INVALID_RESOURCE_ID,
+            InvalidStreamId(_) => VIRTIO_VIDEO_RESP_ERR_INVALID_STREAM_ID,
+            InvalidParameter => VIRTIO_VIDEO_RESP_ERR_INVALID_PARAMETER,
+            // TODO(b/1518105): Add more detailed error code if a new protocol supports ones.
+            _ => VIRTIO_VIDEO_RESP_ERR_INVALID_OPERATION,
+        });
+
+        w.write_obj(virtio_video_cmd_hdr {
+            type_,
+            ..Default::default()
+        })
+    }
+}
diff --git a/devices/src/virtio/video/event.rs b/devices/src/virtio/video/event.rs
new file mode 100644
index 0000000..072f353
--- /dev/null
+++ b/devices/src/virtio/video/event.rs
@@ -0,0 +1,35 @@
+// 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.
+
+//! Events can happen in virtio video devices.
+
+use std::io;
+
+use data_model::Le32;
+use enumn::N;
+
+use crate::virtio::video::protocol::*;
+use crate::virtio::video::response::Response;
+use crate::virtio::Writer;
+
+#[derive(Debug, Copy, Clone, N)]
+pub enum EvtType {
+    Error = VIRTIO_VIDEO_EVENT_ERROR as isize,
+    DecResChanged = VIRTIO_VIDEO_EVENT_DECODER_RESOLUTION_CHANGED as isize,
+}
+
+#[derive(Debug, Clone)]
+pub struct VideoEvt {
+    pub typ: EvtType,
+    pub stream_id: u32,
+}
+
+impl Response for VideoEvt {
+    fn write(&self, w: &mut Writer) -> Result<(), io::Error> {
+        w.write_obj(virtio_video_event {
+            event_type: Le32::from(self.typ as u32),
+            stream_id: Le32::from(self.stream_id),
+        })
+    }
+}
diff --git a/devices/src/virtio/video/format.rs b/devices/src/virtio/video/format.rs
new file mode 100644
index 0000000..fc2c441
--- /dev/null
+++ b/devices/src/virtio/video/format.rs
@@ -0,0 +1,116 @@
+// 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.
+
+//! Data structures that represent video format information in virtio video devices.
+
+use std::convert::{From, Into, TryFrom};
+use std::io;
+
+use data_model::Le32;
+use enumn::N;
+use sys_util::error;
+
+use crate::virtio::video::command::ReadCmdError;
+use crate::virtio::video::protocol::*;
+use crate::virtio::video::response::Response;
+use crate::virtio::Writer;
+
+#[derive(PartialEq, Eq, PartialOrd, Ord, N, Clone, Copy, Debug)]
+#[repr(u32)]
+pub enum Profile {
+    VP8Profile0 = VIRTIO_VIDEO_PROFILE_VP8_PROFILE0,
+    VP9Profile0 = VIRTIO_VIDEO_PROFILE_VP9_PROFILE0,
+    H264Baseline = VIRTIO_VIDEO_PROFILE_H264_BASELINE,
+}
+impl_try_from_le32_for_enumn!(Profile, "profile");
+
+#[derive(PartialEq, Eq, PartialOrd, Ord, N, Clone, Copy, Debug)]
+#[repr(u32)]
+pub enum Level {
+    H264_1_0 = VIRTIO_VIDEO_LEVEL_H264_1_0,
+}
+impl_try_from_le32_for_enumn!(Level, "level");
+
+#[derive(PartialEq, Eq, PartialOrd, Ord, N, Clone, Copy, Debug)]
+#[repr(u32)]
+pub enum Format {
+    // Raw formats
+    NV12 = VIRTIO_VIDEO_FORMAT_NV12,
+    YUV420 = VIRTIO_VIDEO_FORMAT_YUV420,
+
+    // Bitstream formats
+    H264 = VIRTIO_VIDEO_FORMAT_H264,
+    VP8 = VIRTIO_VIDEO_FORMAT_VP8,
+    VP9 = VIRTIO_VIDEO_FORMAT_VP9,
+}
+impl_try_from_le32_for_enumn!(Format, "format");
+
+#[derive(Debug, Default, Copy, Clone)]
+pub struct Crop {
+    pub left: u32,
+    pub top: u32,
+    pub width: u32,
+    pub height: u32,
+}
+impl_from_for_interconvertible_structs!(virtio_video_crop, Crop, left, top, width, height);
+
+#[derive(Debug, Default, Clone, Copy)]
+pub struct PlaneFormat {
+    pub plane_size: u32,
+    pub stride: u32,
+}
+impl_from_for_interconvertible_structs!(virtio_video_plane_format, PlaneFormat, plane_size, stride);
+
+#[derive(Debug, Default, Clone, Copy)]
+pub struct FormatRange {
+    pub min: u32,
+    pub max: u32,
+    pub step: u32,
+}
+impl_from_for_interconvertible_structs!(virtio_video_format_range, FormatRange, min, max, step);
+
+#[derive(Debug, Default, Clone)]
+pub struct FrameFormat {
+    pub width: FormatRange,
+    pub height: FormatRange,
+    pub bitrates: Vec<FormatRange>,
+}
+
+impl Response for FrameFormat {
+    fn write(&self, w: &mut Writer) -> Result<(), io::Error> {
+        w.write_obj(virtio_video_format_frame {
+            width: self.width.into(),
+            height: self.height.into(),
+            num_rates: Le32::from(self.bitrates.len() as u32),
+            ..Default::default()
+        })?;
+        w.write_iter(
+            self.bitrates
+                .iter()
+                .map(|r| Into::<virtio_video_format_range>::into(*r)),
+        )
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct FormatDesc {
+    pub mask: u64,
+    pub format: Format,
+    pub frame_formats: Vec<FrameFormat>,
+}
+
+impl Response for FormatDesc {
+    fn write(&self, w: &mut Writer) -> Result<(), io::Error> {
+        w.write_obj(virtio_video_format_desc {
+            mask: self.mask.into(),
+            format: Le32::from(self.format as u32),
+            // ChromeOS only supports single-buffer mode.
+            planes_layout: Le32::from(VIRTIO_VIDEO_PLANES_LAYOUT_SINGLE_BUFFER),
+            // No alignment is required on boards that we currently support.
+            plane_align: Le32::from(0),
+            num_frames: Le32::from(self.frame_formats.len() as u32),
+        })?;
+        self.frame_formats.iter().map(|ff| ff.write(w)).collect()
+    }
+}
diff --git a/devices/src/virtio/video/macros.rs b/devices/src/virtio/video/macros.rs
new file mode 100644
index 0000000..d1ed386
--- /dev/null
+++ b/devices/src/virtio/video/macros.rs
@@ -0,0 +1,46 @@
+// 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.
+
+//! Macros that helps virtio video implementation.
+
+/// Implements TryFrom<data_model::Le32> for an enum that implements `enumn::N`.
+#[macro_export]
+macro_rules! impl_try_from_le32_for_enumn {
+    ($ty:ty, $name:literal) => {
+        impl TryFrom<Le32> for $ty {
+            type Error = ReadCmdError;
+
+            fn try_from(x: Le32) -> Result<Self, Self::Error> {
+                let v: u32 = x.into();
+                Self::n(v).ok_or_else(|| {
+                    error!(concat!("invalid ", $name, ": {}"), v);
+                    ReadCmdError::InvalidArgument
+                })
+            }
+        }
+    };
+}
+
+/// Implements `From` between two structs whose each field implements `From` each other.
+#[macro_export]
+macro_rules! impl_from_for_interconvertible_structs {
+    ($t1:ident, $t2:ident, $($v:ident),+) => {
+        impl_from_for_interconvertible_structs_core!($t1, $t2, $( $v ),+ );
+        impl_from_for_interconvertible_structs_core!($t2, $t1, $( $v ),+ );
+    };
+}
+
+macro_rules! impl_from_for_interconvertible_structs_core {
+    ($t1:ident, $t2:ident, $($v:ident),+) => {
+        impl From<$t1> for $t2 {
+            #[allow(clippy::needless_update)]
+            fn from(x :$t1) -> Self {
+                $t2 {
+                    $( $v: x.$v.into(), )+
+                    ..Default::default() // for paddings
+                }
+            }
+        }
+    };
+}
diff --git a/devices/src/virtio/video/mod.rs b/devices/src/virtio/video/mod.rs
new file mode 100644
index 0000000..1b3058a
--- /dev/null
+++ b/devices/src/virtio/video/mod.rs
@@ -0,0 +1,255 @@
+// 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.
+
+//! This module implements the virtio video encoder and decoder devices.
+//! The current implementation uses [v3 RFC] of the virtio-video protocol.
+//!
+//! [v3 RFC]: https://markmail.org/thread/wxdne5re7aaugbjg
+
+use std::fmt::{self, Display};
+use std::os::unix::io::{AsRawFd, RawFd};
+use std::thread;
+
+use data_model::{DataInit, Le32};
+use sys_util::{error, Error as SysError, EventFd, GuestMemory};
+
+use crate::virtio::resource_bridge::ResourceRequestSocket;
+use crate::virtio::virtio_device::VirtioDevice;
+use crate::virtio::{self, copy_config, DescriptorError, Interrupt, VIRTIO_F_VERSION_1};
+
+#[macro_use]
+mod macros;
+mod command;
+mod control;
+mod decoder;
+mod device;
+mod encoder;
+mod error;
+mod event;
+mod format;
+mod params;
+mod protocol;
+mod response;
+mod worker;
+
+use command::ReadCmdError;
+use device::AsyncCmdTag;
+use worker::Worker;
+
+const QUEUE_SIZE: u16 = 256;
+const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE, QUEUE_SIZE];
+
+/// An error indicating something went wrong in virtio-video's worker.
+#[derive(Debug)]
+pub enum Error {
+    /// Failed to create a libvda instance.
+    LibvdaCreationFailed(libvda::Error),
+    /// Creating PollContext failed.
+    PollContextCreationFailed(SysError),
+    /// A DescriptorChain contains invalid data.
+    InvalidDescriptorChain(DescriptorError),
+    /// Invalid output buffer is specified for EOS notification.
+    InvalidEOSResource { stream_id: u32, resource_id: u32 },
+    /// No available descriptor in which an event is written to.
+    DescriptorNotAvailable,
+    /// Output buffer for EOS is unavailable.
+    NoEOSBuffer { stream_id: u32 },
+    /// Error while polling for events.
+    PollError(SysError),
+    /// Failed to read a virtio-video command.
+    ReadFailure(ReadCmdError),
+    /// Got response for an unexpected asynchronous command.
+    UnexpectedResponse(AsyncCmdTag),
+    /// Failed to write an event into the event queue.
+    WriteEventFailure {
+        event: event::VideoEvt,
+        error: std::io::Error,
+    },
+}
+
+impl Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        use Error::*;
+        match self {
+            LibvdaCreationFailed(e) => write!(f, "failed to create a libvda instance: {}", e),
+            PollContextCreationFailed(e) => write!(f, "failed to create PollContext: {}", e),
+            InvalidDescriptorChain(e) => write!(f, "DescriptorChain contains invalid data: {}", e),
+            InvalidEOSResource {
+                stream_id,
+                resource_id,
+            } => write!(
+                f,
+                "invalid resource {} was specified for stream {}'s EOS",
+                resource_id, stream_id
+            ),
+            DescriptorNotAvailable => {
+                write!(f, "no available descriptor in which an event is written to")
+            }
+            NoEOSBuffer { stream_id } => write!(
+                f,
+                "no output resource is available to notify EOS: {}",
+                stream_id
+            ),
+            PollError(err) => write!(f, "failed to poll events: {}", err),
+            ReadFailure(e) => write!(f, "failed to read a command from the guest: {}", e),
+            UnexpectedResponse(tag) => {
+                write!(f, "got a response for an untracked command: {:?}", tag)
+            }
+            WriteEventFailure { event, error } => write!(
+                f,
+                "failed to write an event {:?} into event queue: {}",
+                event, error
+            ),
+        }
+    }
+}
+
+impl std::error::Error for Error {}
+pub type Result<T> = std::result::Result<T, Error>;
+
+#[derive(Debug)]
+pub enum VideoDeviceType {
+    Decoder,
+    Encoder,
+}
+
+pub struct VideoDevice {
+    device_type: VideoDeviceType,
+    kill_evt: Option<EventFd>,
+    resource_bridge: Option<ResourceRequestSocket>,
+}
+
+impl VideoDevice {
+    pub fn new(
+        device_type: VideoDeviceType,
+        resource_bridge: Option<ResourceRequestSocket>,
+    ) -> VideoDevice {
+        VideoDevice {
+            device_type,
+            kill_evt: None,
+            resource_bridge,
+        }
+    }
+}
+
+impl Drop for VideoDevice {
+    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);
+        }
+    }
+}
+
+impl VirtioDevice for VideoDevice {
+    fn keep_fds(&self) -> Vec<RawFd> {
+        let mut keep_fds = Vec::new();
+        if let Some(resource_bridge) = &self.resource_bridge {
+            keep_fds.push(resource_bridge.as_raw_fd());
+        }
+        keep_fds
+    }
+
+    fn device_type(&self) -> u32 {
+        match &self.device_type {
+            VideoDeviceType::Decoder => virtio::TYPE_VIDEO_DEC,
+            VideoDeviceType::Encoder => virtio::TYPE_VIDEO_ENC,
+        }
+    }
+
+    fn queue_max_sizes(&self) -> &[u16] {
+        QUEUE_SIZES
+    }
+
+    fn features(&self) -> u64 {
+        1u64 << VIRTIO_F_VERSION_1
+            | 1u64 << protocol::VIRTIO_VIDEO_F_RESOURCE_NON_CONTIG
+            | 1u64 << protocol::VIRTIO_VIDEO_F_RESOURCE_VIRTIO_OBJECT
+    }
+
+    fn read_config(&self, offset: u64, data: &mut [u8]) {
+        let mut cfg = protocol::virtio_video_config {
+            version: Le32::from(0),
+            max_caps_length: Le32::from(1024), // Set a big number
+            max_resp_length: Le32::from(1024), // Set a big number
+        };
+        copy_config(data, 0, cfg.as_mut_slice(), offset);
+    }
+
+    fn activate(
+        &mut self,
+        mem: GuestMemory,
+        interrupt: Interrupt,
+        mut queues: Vec<virtio::queue::Queue>,
+        mut queue_evts: Vec<EventFd>,
+    ) {
+        if queues.len() != QUEUE_SIZES.len() {
+            error!(
+                "wrong number of queues are passed: expected {}, actual {}",
+                queues.len(),
+                QUEUE_SIZES.len()
+            );
+            return;
+        }
+        if queue_evts.len() != QUEUE_SIZES.len() {
+            error!(
+                "wrong number of event FDs are passed: expected {}, actual {}",
+                queue_evts.len(),
+                QUEUE_SIZES.len()
+            );
+        }
+
+        let (self_kill_evt, kill_evt) = match EventFd::new().and_then(|e| Ok((e.try_clone()?, e))) {
+            Ok(v) => v,
+            Err(e) => {
+                error!("failed to create kill EventFd pair: {:?}", e);
+                return;
+            }
+        };
+        self.kill_evt = Some(self_kill_evt);
+
+        let cmd_queue = queues.remove(0);
+        let cmd_evt = queue_evts.remove(0);
+        let event_queue = queues.remove(0);
+        let event_evt = queue_evts.remove(0);
+        let resource_bridge = match self.resource_bridge.take() {
+            Some(r) => r,
+            None => {
+                error!("no resource bridge is passed");
+                return;
+            }
+        };
+        let mut worker = Worker {
+            interrupt,
+            mem,
+            cmd_evt,
+            event_evt,
+            kill_evt,
+            resource_bridge,
+        };
+        let worker_result = match &self.device_type {
+            VideoDeviceType::Decoder => thread::Builder::new()
+                .name("virtio video decoder".to_owned())
+                .spawn(move || {
+                    let vda = libvda::VdaInstance::new(libvda::VdaImplType::Gavda)
+                        .map_err(Error::LibvdaCreationFailed)?;
+                    let device = decoder::Decoder::new(&vda);
+                    worker.run(cmd_queue, event_queue, device)
+                }),
+            VideoDeviceType::Encoder => thread::Builder::new()
+                .name("virtio video encoder".to_owned())
+                .spawn(move || {
+                    let device = encoder::Encoder::new();
+                    worker.run(cmd_queue, event_queue, device)
+                }),
+        };
+        if let Err(e) = worker_result {
+            error!(
+                "failed to spawn virtio_video worker for {:?}: {}",
+                &self.device_type, e
+            );
+            return;
+        }
+    }
+}
diff --git a/devices/src/virtio/video/params.rs b/devices/src/virtio/video/params.rs
new file mode 100644
index 0000000..e4bb614
--- /dev/null
+++ b/devices/src/virtio/video/params.rs
@@ -0,0 +1,111 @@
+// 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.
+
+//! Parameters for streams in virtio video devices.
+
+use std::convert::{From, Into, TryFrom};
+
+use data_model::Le32;
+use sys_util::error;
+
+use crate::virtio::video::command::{QueueType, ReadCmdError};
+use crate::virtio::video::format::*;
+use crate::virtio::video::protocol::*;
+
+/// Safe wrapper of `virtio_video_params`.
+/// Note that this struct doesn't have a field corresponding to `queue_type` in
+/// `virtio_video_params`. The type of queue should be stored by one that has an `Params` instance.
+#[derive(Debug, Default, Clone)]
+pub struct Params {
+    // Use `Option<Format>` instead of `Format` because an image format may not be determined until
+    // video decoding is started in the decoder.
+    pub format: Option<Format>,
+    pub frame_width: u32,
+    pub frame_height: u32,
+    pub min_buffers: u32,
+    pub max_buffers: u32,
+    pub crop: Crop,
+    pub frame_rate: u32,
+    pub plane_formats: Vec<PlaneFormat>,
+}
+
+impl TryFrom<virtio_video_params> for Params {
+    type Error = ReadCmdError;
+
+    fn try_from(
+        virtio_video_params {
+            format,
+            frame_width,
+            frame_height,
+            min_buffers,
+            max_buffers,
+            crop,
+            frame_rate,
+            num_planes,
+            plane_formats,
+            ..
+        }: virtio_video_params,
+    ) -> Result<Self, Self::Error> {
+        let num_planes = Into::<u32>::into(num_planes); // as usize;
+        if num_planes as usize > plane_formats.len() {
+            error!(
+                "num_planes must not exceed {} but {}",
+                plane_formats.len(),
+                Into::<u32>::into(num_planes)
+            );
+            return Err(ReadCmdError::InvalidArgument);
+        }
+        let plane_formats = plane_formats[0..num_planes as usize]
+            .iter()
+            .map(|x| Into::<PlaneFormat>::into(*x))
+            .collect::<Vec<_>>();
+
+        Ok(Params {
+            format: Format::n(format.into()),
+            frame_width: frame_width.into(),
+            frame_height: frame_height.into(),
+            min_buffers: min_buffers.into(),
+            max_buffers: max_buffers.into(),
+            crop: crop.into(),
+            frame_rate: frame_rate.into(),
+            plane_formats,
+        })
+    }
+}
+
+impl Params {
+    pub fn to_virtio_video_params(&self, queue_type: QueueType) -> virtio_video_params {
+        let Params {
+            format,
+            frame_width,
+            frame_height,
+            min_buffers,
+            max_buffers,
+            crop,
+            frame_rate,
+            plane_formats,
+        } = self;
+        let num_planes = Le32::from(plane_formats.len() as u32);
+        let mut p_fmts: [virtio_video_plane_format; VIRTIO_VIDEO_MAX_PLANES as usize] =
+            Default::default();
+        for (i, pf) in plane_formats.iter().enumerate() {
+            p_fmts[i] = Into::<virtio_video_plane_format>::into(*pf);
+        }
+
+        virtio_video_params {
+            queue_type: (queue_type as u32).into(),
+            format: format
+                .map(|f| Le32::from(f as u32))
+                .unwrap_or_else(|| Le32::from(0)),
+            frame_width: Le32::from(*frame_width),
+            frame_height: Le32::from(*frame_height),
+            min_buffers: Le32::from(*min_buffers),
+            max_buffers: Le32::from(*max_buffers),
+            crop: virtio_video_crop::from(*crop),
+            frame_rate: Le32::from(*frame_rate),
+            num_planes,
+            plane_formats: p_fmts,
+        }
+    }
+}
diff --git a/devices/src/virtio/video/protocol.rs b/devices/src/virtio/video/protocol.rs
new file mode 100644
index 0000000..0e2106e
--- /dev/null
+++ b/devices/src/virtio/video/protocol.rs
@@ -0,0 +1,487 @@
+// 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.
+
+//! This file was generated by the following commands and modified manually.
+//!
+//! ```
+//! $ bindgen virtio_video.h              \
+//!     --whitelist-type "virtio_video.*" \
+//!     --whitelist-var "VIRTIO_VIDEO_.*" \
+//!     --with-derive-default            \
+//!     --no-layout-tests                \
+//!     --no-prepend-enum-name > protocol.rs
+//! $ sed -i 's/u/u/g' protocol.rs
+//! $ sed -i 's/Le/Le/g' protocol.rs
+//! ```
+//!
+//! The main points of the manual modifications are as follows:
+//! * Removed `hdr` from each command struct so that we can read the header and a command body separately.
+//!   (cf. [related discussion](https://markmail.org/message/tr5g6axqq2zzq64y))
+//! * Added implementations of DataInit for each struct.
+
+#![allow(dead_code, non_snake_case, non_camel_case_types)]
+
+use data_model::{DataInit, Le32, Le64};
+
+pub const VIRTIO_VIDEO_F_RESOURCE_GUEST_PAGES: u32 = 0;
+pub const VIRTIO_VIDEO_F_RESOURCE_NON_CONTIG: u32 = 1;
+pub const VIRTIO_VIDEO_F_RESOURCE_VIRTIO_OBJECT: u32 = 2;
+pub const VIRTIO_VIDEO_MAX_PLANES: u32 = 8;
+pub const VIRTIO_VIDEO_FORMAT_RAW_MIN: virtio_video_format = 1;
+pub const VIRTIO_VIDEO_FORMAT_ARGB8888: virtio_video_format = 1;
+pub const VIRTIO_VIDEO_FORMAT_BGRA8888: virtio_video_format = 2;
+pub const VIRTIO_VIDEO_FORMAT_NV12: virtio_video_format = 3;
+pub const VIRTIO_VIDEO_FORMAT_YUV420: virtio_video_format = 4;
+pub const VIRTIO_VIDEO_FORMAT_YVU420: virtio_video_format = 5;
+pub const VIRTIO_VIDEO_FORMAT_RAW_MAX: virtio_video_format = 5;
+pub const VIRTIO_VIDEO_FORMAT_CODED_MIN: virtio_video_format = 4096;
+pub const VIRTIO_VIDEO_FORMAT_MPEG2: virtio_video_format = 4096;
+pub const VIRTIO_VIDEO_FORMAT_MPEG4: virtio_video_format = 4097;
+pub const VIRTIO_VIDEO_FORMAT_H264: virtio_video_format = 4098;
+pub const VIRTIO_VIDEO_FORMAT_HEVC: virtio_video_format = 4099;
+pub const VIRTIO_VIDEO_FORMAT_VP8: virtio_video_format = 4100;
+pub const VIRTIO_VIDEO_FORMAT_VP9: virtio_video_format = 4101;
+pub const VIRTIO_VIDEO_FORMAT_CODED_MAX: virtio_video_format = 4101;
+pub type virtio_video_format = u32;
+pub const VIRTIO_VIDEO_PROFILE_H264_MIN: virtio_video_profile = 256;
+pub const VIRTIO_VIDEO_PROFILE_H264_BASELINE: virtio_video_profile = 256;
+pub const VIRTIO_VIDEO_PROFILE_H264_MAIN: virtio_video_profile = 257;
+pub const VIRTIO_VIDEO_PROFILE_H264_EXTENDED: virtio_video_profile = 258;
+pub const VIRTIO_VIDEO_PROFILE_H264_HIGH: virtio_video_profile = 259;
+pub const VIRTIO_VIDEO_PROFILE_H264_HIGH10PROFILE: virtio_video_profile = 260;
+pub const VIRTIO_VIDEO_PROFILE_H264_HIGH422PROFILE: virtio_video_profile = 261;
+pub const VIRTIO_VIDEO_PROFILE_H264_HIGH444PREDICTIVEPROFILE: virtio_video_profile = 262;
+pub const VIRTIO_VIDEO_PROFILE_H264_SCALABLEBASELINE: virtio_video_profile = 263;
+pub const VIRTIO_VIDEO_PROFILE_H264_SCALABLEHIGH: virtio_video_profile = 264;
+pub const VIRTIO_VIDEO_PROFILE_H264_STEREOHIGH: virtio_video_profile = 265;
+pub const VIRTIO_VIDEO_PROFILE_H264_MULTIVIEWHIGH: virtio_video_profile = 266;
+pub const VIRTIO_VIDEO_PROFILE_H264_MAX: virtio_video_profile = 266;
+pub const VIRTIO_VIDEO_PROFILE_HEVC_MIN: virtio_video_profile = 512;
+pub const VIRTIO_VIDEO_PROFILE_HEVC_MAIN: virtio_video_profile = 512;
+pub const VIRTIO_VIDEO_PROFILE_HEVC_MAIN10: virtio_video_profile = 513;
+pub const VIRTIO_VIDEO_PROFILE_HEVC_MAIN_STILL_PICTURE: virtio_video_profile = 514;
+pub const VIRTIO_VIDEO_PROFILE_HEVC_MAX: virtio_video_profile = 514;
+pub const VIRTIO_VIDEO_PROFILE_VP8_MIN: virtio_video_profile = 768;
+pub const VIRTIO_VIDEO_PROFILE_VP8_PROFILE0: virtio_video_profile = 768;
+pub const VIRTIO_VIDEO_PROFILE_VP8_PROFILE1: virtio_video_profile = 769;
+pub const VIRTIO_VIDEO_PROFILE_VP8_PROFILE2: virtio_video_profile = 770;
+pub const VIRTIO_VIDEO_PROFILE_VP8_PROFILE3: virtio_video_profile = 771;
+pub const VIRTIO_VIDEO_PROFILE_VP8_MAX: virtio_video_profile = 771;
+pub const VIRTIO_VIDEO_PROFILE_VP9_MIN: virtio_video_profile = 1024;
+pub const VIRTIO_VIDEO_PROFILE_VP9_PROFILE0: virtio_video_profile = 1024;
+pub const VIRTIO_VIDEO_PROFILE_VP9_PROFILE1: virtio_video_profile = 1025;
+pub const VIRTIO_VIDEO_PROFILE_VP9_PROFILE2: virtio_video_profile = 1026;
+pub const VIRTIO_VIDEO_PROFILE_VP9_PROFILE3: virtio_video_profile = 1027;
+pub const VIRTIO_VIDEO_PROFILE_VP9_MAX: virtio_video_profile = 1027;
+pub type virtio_video_profile = u32;
+pub const VIRTIO_VIDEO_LEVEL_H264_MIN: virtio_video_level = 256;
+pub const VIRTIO_VIDEO_LEVEL_H264_1_0: virtio_video_level = 256;
+pub const VIRTIO_VIDEO_LEVEL_H264_1_1: virtio_video_level = 257;
+pub const VIRTIO_VIDEO_LEVEL_H264_1_2: virtio_video_level = 258;
+pub const VIRTIO_VIDEO_LEVEL_H264_1_3: virtio_video_level = 259;
+pub const VIRTIO_VIDEO_LEVEL_H264_2_0: virtio_video_level = 260;
+pub const VIRTIO_VIDEO_LEVEL_H264_2_1: virtio_video_level = 261;
+pub const VIRTIO_VIDEO_LEVEL_H264_2_2: virtio_video_level = 262;
+pub const VIRTIO_VIDEO_LEVEL_H264_3_0: virtio_video_level = 263;
+pub const VIRTIO_VIDEO_LEVEL_H264_3_1: virtio_video_level = 264;
+pub const VIRTIO_VIDEO_LEVEL_H264_3_2: virtio_video_level = 265;
+pub const VIRTIO_VIDEO_LEVEL_H264_4_0: virtio_video_level = 266;
+pub const VIRTIO_VIDEO_LEVEL_H264_4_1: virtio_video_level = 267;
+pub const VIRTIO_VIDEO_LEVEL_H264_4_2: virtio_video_level = 268;
+pub const VIRTIO_VIDEO_LEVEL_H264_5_0: virtio_video_level = 269;
+pub const VIRTIO_VIDEO_LEVEL_H264_5_1: virtio_video_level = 270;
+pub const VIRTIO_VIDEO_LEVEL_H264_MAX: virtio_video_level = 270;
+pub type virtio_video_level = u32;
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_config {
+    pub version: Le32,
+    pub max_caps_length: Le32,
+    pub max_resp_length: Le32,
+}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_config {}
+
+pub const VIRTIO_VIDEO_CMD_QUERY_CAPABILITY: virtio_video_cmd_type = 256;
+pub const VIRTIO_VIDEO_CMD_STREAM_CREATE: virtio_video_cmd_type = 257;
+pub const VIRTIO_VIDEO_CMD_STREAM_DESTROY: virtio_video_cmd_type = 258;
+pub const VIRTIO_VIDEO_CMD_STREAM_DRAIN: virtio_video_cmd_type = 259;
+pub const VIRTIO_VIDEO_CMD_RESOURCE_CREATE: virtio_video_cmd_type = 260;
+pub const VIRTIO_VIDEO_CMD_RESOURCE_QUEUE: virtio_video_cmd_type = 261;
+pub const VIRTIO_VIDEO_CMD_RESOURCE_DESTROY_ALL: virtio_video_cmd_type = 262;
+pub const VIRTIO_VIDEO_CMD_QUEUE_CLEAR: virtio_video_cmd_type = 263;
+pub const VIRTIO_VIDEO_CMD_GET_PARAMS: virtio_video_cmd_type = 264;
+pub const VIRTIO_VIDEO_CMD_SET_PARAMS: virtio_video_cmd_type = 265;
+pub const VIRTIO_VIDEO_CMD_QUERY_CONTROL: virtio_video_cmd_type = 266;
+pub const VIRTIO_VIDEO_CMD_GET_CONTROL: virtio_video_cmd_type = 267;
+pub const VIRTIO_VIDEO_CMD_SET_CONTROL: virtio_video_cmd_type = 268;
+pub const VIRTIO_VIDEO_RESP_OK_NODATA: virtio_video_cmd_type = 512;
+pub const VIRTIO_VIDEO_RESP_OK_QUERY_CAPABILITY: virtio_video_cmd_type = 513;
+pub const VIRTIO_VIDEO_RESP_OK_RESOURCE_QUEUE: virtio_video_cmd_type = 514;
+pub const VIRTIO_VIDEO_RESP_OK_GET_PARAMS: virtio_video_cmd_type = 515;
+pub const VIRTIO_VIDEO_RESP_OK_QUERY_CONTROL: virtio_video_cmd_type = 516;
+pub const VIRTIO_VIDEO_RESP_OK_GET_CONTROL: virtio_video_cmd_type = 517;
+pub const VIRTIO_VIDEO_RESP_ERR_INVALID_OPERATION: virtio_video_cmd_type = 768;
+pub const VIRTIO_VIDEO_RESP_ERR_OUT_OF_MEMORY: virtio_video_cmd_type = 769;
+pub const VIRTIO_VIDEO_RESP_ERR_INVALID_STREAM_ID: virtio_video_cmd_type = 770;
+pub const VIRTIO_VIDEO_RESP_ERR_INVALID_RESOURCE_ID: virtio_video_cmd_type = 771;
+pub const VIRTIO_VIDEO_RESP_ERR_INVALID_PARAMETER: virtio_video_cmd_type = 772;
+pub const VIRTIO_VIDEO_RESP_ERR_UNSUPPORTED_CONTROL: virtio_video_cmd_type = 773;
+pub type virtio_video_cmd_type = u32;
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_cmd_hdr {
+    pub type_: Le32,
+    pub stream_id: Le32,
+}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_cmd_hdr {}
+
+pub const VIRTIO_VIDEO_QUEUE_TYPE_INPUT: virtio_video_queue_type = 256;
+pub const VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT: virtio_video_queue_type = 257;
+pub type virtio_video_queue_type = u32;
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_query_capability {
+    pub queue_type: Le32,
+    pub padding: [u8; 4usize],
+}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_query_capability {}
+
+pub const VIRTIO_VIDEO_PLANES_LAYOUT_SINGLE_BUFFER: virtio_video_planes_layout_flag = 1;
+pub const VIRTIO_VIDEO_PLANES_LAYOUT_PER_PLANE: virtio_video_planes_layout_flag = 2;
+pub type virtio_video_planes_layout_flag = u32;
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_format_range {
+    pub min: Le32,
+    pub max: Le32,
+    pub step: Le32,
+    pub padding: [u8; 4usize],
+}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_format_range {}
+
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_format_frame {
+    pub width: virtio_video_format_range,
+    pub height: virtio_video_format_range,
+    pub num_rates: Le32,
+    pub padding: [u8; 4usize],
+}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_format_frame {}
+
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_format_desc {
+    pub mask: Le64,
+    pub format: Le32,
+    pub planes_layout: Le32,
+    pub plane_align: Le32,
+    pub num_frames: Le32,
+}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_format_desc {}
+
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_query_capability_resp {
+    pub hdr: virtio_video_cmd_hdr,
+    pub num_descs: Le32,
+    pub padding: [u8; 4usize],
+}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_query_capability_resp {}
+
+pub const VIRTIO_VIDEO_MEM_TYPE_GUEST_PAGES: virtio_video_mem_type = 0;
+pub const VIRTIO_VIDEO_MEM_TYPE_VIRTIO_OBJECT: virtio_video_mem_type = 1;
+pub type virtio_video_mem_type = u32;
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct virtio_video_stream_create {
+    pub in_mem_type: Le32,
+    pub out_mem_type: Le32,
+    pub coded_format: Le32,
+    pub padding: [u8; 4usize],
+    pub tag: [u8; 64usize],
+}
+impl Default for virtio_video_stream_create {
+    fn default() -> Self {
+        unsafe { ::std::mem::zeroed() }
+    }
+}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_stream_create {}
+
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_stream_destroy {}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_stream_destroy {}
+
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_stream_drain {}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_stream_drain {}
+
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_mem_entry {
+    pub addr: Le64,
+    pub length: Le32,
+    pub padding: [u8; 4usize],
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_object_entry {
+    pub uuid: [u8; 16usize],
+}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_object_entry {}
+
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_resource_create {
+    pub queue_type: Le32,
+    pub resource_id: Le32,
+    pub planes_layout: Le32,
+    pub num_planes: Le32,
+    pub plane_offsets: [Le32; 8usize],
+    pub num_entries: [Le32; 8usize],
+}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_resource_create {}
+
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_resource_queue {
+    pub queue_type: Le32,
+    pub resource_id: Le32,
+    pub timestamp: Le64,
+    pub num_data_sizes: Le32,
+    pub data_sizes: [Le32; 8usize],
+    pub padding: [u8; 4usize],
+}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_resource_queue {}
+
+pub const VIRTIO_VIDEO_BUFFER_FLAG_ERR: virtio_video_buffer_flag = 1;
+pub const VIRTIO_VIDEO_BUFFER_FLAG_EOS: virtio_video_buffer_flag = 2;
+pub const VIRTIO_VIDEO_BUFFER_FLAG_IFRAME: virtio_video_buffer_flag = 4;
+pub const VIRTIO_VIDEO_BUFFER_FLAG_PFRAME: virtio_video_buffer_flag = 8;
+pub const VIRTIO_VIDEO_BUFFER_FLAG_BFRAME: virtio_video_buffer_flag = 16;
+pub type virtio_video_buffer_flag = u32;
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_resource_queue_resp {
+    pub hdr: virtio_video_cmd_hdr,
+    pub timestamp: Le64,
+    pub flags: Le32,
+    pub size: Le32,
+}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_resource_queue_resp {}
+
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_resource_destroy_all {
+    pub queue_type: Le32,
+    pub padding: [u8; 4usize],
+}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_resource_destroy_all {}
+
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_queue_clear {
+    pub queue_type: Le32,
+    pub padding: [u8; 4usize],
+}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_queue_clear {}
+
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_plane_format {
+    pub plane_size: Le32,
+    pub stride: Le32,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_crop {
+    pub left: Le32,
+    pub top: Le32,
+    pub width: Le32,
+    pub height: Le32,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_params {
+    pub queue_type: Le32,
+    pub format: Le32,
+    pub frame_width: Le32,
+    pub frame_height: Le32,
+    pub min_buffers: Le32,
+    pub max_buffers: Le32,
+    pub crop: virtio_video_crop,
+    pub frame_rate: Le32,
+    pub num_planes: Le32,
+    pub plane_formats: [virtio_video_plane_format; 8usize],
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_get_params {
+    pub queue_type: Le32,
+    pub padding: [u8; 4usize],
+}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_get_params {}
+
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_get_params_resp {
+    pub hdr: virtio_video_cmd_hdr,
+    pub params: virtio_video_params,
+}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_get_params_resp {}
+
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_set_params {
+    pub params: virtio_video_params,
+}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_set_params {}
+
+pub const VIRTIO_VIDEO_CONTROL_BITRATE: virtio_video_control_type = 1;
+pub const VIRTIO_VIDEO_CONTROL_PROFILE: virtio_video_control_type = 2;
+pub const VIRTIO_VIDEO_CONTROL_LEVEL: virtio_video_control_type = 3;
+pub type virtio_video_control_type = u32;
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_query_control_profile {
+    pub format: Le32,
+    pub padding: [u8; 4usize],
+}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_query_control_profile {}
+
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_query_control_level {
+    pub format: Le32,
+    pub padding: [u8; 4usize],
+}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_query_control_level {}
+
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_query_control {
+    pub control: Le32,
+    pub padding: [u8; 4usize],
+}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_query_control {}
+
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_query_control_resp_profile {
+    pub num: Le32,
+    pub padding: [u8; 4usize],
+}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_query_control_resp_profile {}
+
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_query_control_resp_level {
+    pub num: Le32,
+    pub padding: [u8; 4usize],
+}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_query_control_resp_level {}
+
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_query_control_resp {
+    pub hdr: virtio_video_cmd_hdr,
+}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_query_control_resp {}
+
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_get_control {
+    pub control: Le32,
+    pub padding: [u8; 4usize],
+}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_get_control {}
+
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_control_val_bitrate {
+    pub bitrate: Le32,
+    pub padding: [u8; 4usize],
+}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_control_val_bitrate {}
+
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_control_val_profile {
+    pub profile: Le32,
+    pub padding: [u8; 4usize],
+}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_control_val_profile {}
+
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_control_val_level {
+    pub level: Le32,
+    pub padding: [u8; 4usize],
+}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_control_val_level {}
+
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_get_control_resp {
+    pub hdr: virtio_video_cmd_hdr,
+}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_get_control_resp {}
+
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_set_control {
+    pub control: Le32,
+    pub padding: [u8; 4usize],
+}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_set_control {}
+
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_set_control_resp {
+    pub hdr: virtio_video_cmd_hdr,
+}
+pub const VIRTIO_VIDEO_EVENT_ERROR: virtio_video_event_type = 256;
+pub const VIRTIO_VIDEO_EVENT_DECODER_RESOLUTION_CHANGED: virtio_video_event_type = 512;
+pub type virtio_video_event_type = u32;
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_event {
+    pub event_type: Le32,
+    pub stream_id: Le32,
+}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_event {}
diff --git a/devices/src/virtio/video/response.rs b/devices/src/virtio/video/response.rs
new file mode 100644
index 0000000..1313280
--- /dev/null
+++ b/devices/src/virtio/video/response.rs
@@ -0,0 +1,94 @@
+// 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.
+
+//! Data structures for commands of virtio video devices.
+
+use std::io;
+
+use data_model::{Le32, Le64};
+
+use crate::virtio::video::command::QueueType;
+use crate::virtio::video::control::*;
+use crate::virtio::video::format::*;
+use crate::virtio::video::params::Params;
+use crate::virtio::video::protocol::*;
+use crate::virtio::Writer;
+
+pub trait Response {
+    /// Writes an object to virtqueue.
+    fn write(&self, w: &mut Writer) -> Result<(), io::Error>;
+}
+
+/// A response to a `VideoCmd`. These correspond to `VIRTIO_VIDEO_RESP_*`.
+#[derive(Debug)]
+pub enum CmdResponse {
+    NoData,
+    QueryCapability(Vec<FormatDesc>),
+    ResourceQueue {
+        timestamp: u64,
+        flags: u32,
+        size: u32,
+    },
+    GetParams {
+        queue_type: QueueType,
+        params: Params,
+    },
+    QueryControl(QueryCtrlResponse),
+    GetControl(CtrlVal),
+}
+
+impl Response for CmdResponse {
+    /// Writes a response to virtqueue.
+    fn write(&self, w: &mut Writer) -> Result<(), io::Error> {
+        use CmdResponse::*;
+
+        let type_ = Le32::from(match self {
+            NoData => VIRTIO_VIDEO_RESP_OK_NODATA,
+            QueryCapability(_) => VIRTIO_VIDEO_RESP_OK_QUERY_CAPABILITY,
+            ResourceQueue { .. } => VIRTIO_VIDEO_RESP_OK_RESOURCE_QUEUE,
+            GetParams { .. } => VIRTIO_VIDEO_RESP_OK_GET_PARAMS,
+            QueryControl(_) => VIRTIO_VIDEO_RESP_OK_QUERY_CONTROL,
+            GetControl(_) => VIRTIO_VIDEO_RESP_OK_GET_CONTROL,
+        });
+
+        let hdr = virtio_video_cmd_hdr {
+            type_,
+            ..Default::default()
+        };
+
+        match self {
+            NoData => w.write_obj(hdr),
+            QueryCapability(descs) => {
+                w.write_obj(virtio_video_query_capability_resp {
+                    hdr,
+                    num_descs: Le32::from(descs.len() as u32),
+                    ..Default::default()
+                })?;
+                descs.iter().map(|d| d.write(w)).collect()
+            }
+            ResourceQueue {
+                timestamp,
+                flags,
+                size,
+            } => w.write_obj(virtio_video_resource_queue_resp {
+                hdr,
+                timestamp: Le64::from(*timestamp),
+                flags: Le32::from(*flags),
+                size: Le32::from(*size),
+            }),
+            GetParams { queue_type, params } => {
+                let params = params.to_virtio_video_params(*queue_type);
+                w.write_obj(virtio_video_get_params_resp { hdr, params })
+            }
+            QueryControl(r) => {
+                w.write_obj(virtio_video_query_control_resp { hdr })?;
+                r.write(w)
+            }
+            GetControl(val) => {
+                w.write_obj(virtio_video_get_control_resp { hdr })?;
+                val.write(w)
+            }
+        }
+    }
+}
diff --git a/devices/src/virtio/video/worker.rs b/devices/src/virtio/video/worker.rs
new file mode 100644
index 0000000..195854f
--- /dev/null
+++ b/devices/src/virtio/video/worker.rs
@@ -0,0 +1,362 @@
+// 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.
+
+//! Worker that runs in a virtio-video thread.
+
+use std::collections::{BTreeMap, VecDeque};
+
+use sys_util::{error, EventFd, GuestMemory, PollContext};
+
+use crate::virtio::queue::{DescriptorChain, Queue};
+use crate::virtio::resource_bridge::ResourceRequestSocket;
+use crate::virtio::video::command::{QueueType, VideoCmd};
+use crate::virtio::video::device::{
+    AsyncCmdTag, Device, Token, VideoCmdResponseType, VideoEvtResponseType,
+};
+use crate::virtio::video::error::{VideoError, VideoResult};
+use crate::virtio::video::event::{self, EvtType, VideoEvt};
+use crate::virtio::video::protocol;
+use crate::virtio::video::response::{self, CmdResponse, Response};
+use crate::virtio::video::{Error, Result};
+use crate::virtio::{Interrupt, Reader, Writer};
+
+pub struct Worker {
+    pub interrupt: Interrupt,
+    pub mem: GuestMemory,
+    pub cmd_evt: EventFd,
+    pub event_evt: EventFd,
+    pub kill_evt: EventFd,
+    pub resource_bridge: ResourceRequestSocket,
+}
+
+/// BTreeMap which stores descriptor chains in which asynchronous responses will be written.
+type DescPool<'a> = BTreeMap<AsyncCmdTag, DescriptorChain<'a>>;
+/// Pair of a descriptor chain and a response to be written.
+type WritableResp<'a> = (DescriptorChain<'a>, VideoResult<response::CmdResponse>);
+
+/// Invalidates all pending asynchronous commands in a given `DescPool` value and returns an updated
+/// `DescPool` value and a list of `WritableResp` to be sent to the guest.
+fn cancel_pending_requests<'a>(
+    s_id: u32,
+    desc_pool: DescPool<'a>,
+) -> (DescPool<'a>, Vec<WritableResp<'a>>) {
+    let mut new_desc_pool: DescPool<'a> = Default::default();
+    let mut resps = vec![];
+
+    for (key, value) in desc_pool.into_iter() {
+        match key {
+            AsyncCmdTag::Queue { stream_id, .. } if stream_id == s_id => {
+                resps.push((
+                    value,
+                    Ok(CmdResponse::ResourceQueue {
+                        timestamp: 0,
+                        flags: protocol::VIRTIO_VIDEO_BUFFER_FLAG_ERR,
+                        size: 0,
+                    }),
+                ));
+            }
+            AsyncCmdTag::Drain { stream_id } | AsyncCmdTag::Clear { stream_id, .. }
+                if stream_id == s_id =>
+            {
+                // TODO(b/1518105): Use more appropriate error code if a new protocol supports one.
+                resps.push((value, Err(VideoError::InvalidOperation)));
+            }
+            AsyncCmdTag::Queue { .. } | AsyncCmdTag::Drain { .. } | AsyncCmdTag::Clear { .. } => {
+                // Keep commands for other streams.
+                new_desc_pool.insert(key, value);
+            }
+        }
+    }
+
+    (new_desc_pool, resps)
+}
+
+impl Worker {
+    /// Writes responses into the command queue.
+    fn write_responses<'a>(
+        &self,
+        cmd_queue: &mut Queue,
+        responses: &mut VecDeque<WritableResp>,
+    ) -> Result<()> {
+        let mut needs_interrupt_commandq = false;
+        // Write responses into command virtqueue.
+        while let Some((desc, resp)) = responses.pop_front() {
+            let desc_index = desc.index;
+            let mut writer = Writer::new(&self.mem, desc).map_err(Error::InvalidDescriptorChain)?;
+            match resp {
+                Ok(r) => {
+                    if let Err(e) = r.write(&mut writer) {
+                        error!("failed to write an OK response for {:?}: {}", r, e);
+                    }
+                }
+                Err(err) => {
+                    if let Err(e) = err.write(&mut writer) {
+                        error!("failed to write an Error response for {:?}: {}", err, e);
+                    }
+                }
+            }
+
+            cmd_queue.add_used(&self.mem, desc_index, writer.bytes_written() as u32);
+            needs_interrupt_commandq = true;
+        }
+        if needs_interrupt_commandq {
+            self.interrupt.signal_used_queue(cmd_queue.vector);
+        }
+        Ok(())
+    }
+
+    /// Writes a `VideoEvt` into the event queue.
+    fn write_event(&self, event_queue: &mut Queue, event: &mut event::VideoEvt) -> Result<()> {
+        let desc = event_queue
+            .peek(&self.mem)
+            .ok_or_else(|| Error::DescriptorNotAvailable)?;
+        event_queue.pop_peeked(&self.mem);
+
+        let desc_index = desc.index;
+        let mut writer = Writer::new(&self.mem, desc).map_err(Error::InvalidDescriptorChain)?;
+        event
+            .write(&mut writer)
+            .map_err(|error| Error::WriteEventFailure {
+                event: event.clone(),
+                error,
+            })?;
+        event_queue.add_used(&self.mem, desc_index, writer.bytes_written() as u32);
+        self.interrupt.signal_used_queue(event_queue.vector);
+        Ok(())
+    }
+
+    /// Handles a `DescriptorChain` value sent via the command queue and returns an updated
+    /// `DescPool` and `VecDeque` of `WritableResp` to be sent to the guest.
+    fn handle_command_desc<'a, T: Device>(
+        &'a self,
+        device: &mut T,
+        poll_ctx: &PollContext<Token>,
+        mut desc_pool: DescPool<'a>,
+        desc: DescriptorChain<'a>,
+    ) -> Result<(DescPool<'a>, VecDeque<WritableResp<'a>>)> {
+        let mut resps: VecDeque<WritableResp> = Default::default();
+        let mut reader =
+            Reader::new(&self.mem, desc.clone()).map_err(Error::InvalidDescriptorChain)?;
+
+        let cmd = VideoCmd::from_reader(&mut reader).map_err(Error::ReadFailure)?;
+
+        // If a destruction command comes, cancel pending requests.
+        match cmd {
+            VideoCmd::ResourceDestroyAll { stream_id } | VideoCmd::StreamDestroy { stream_id } => {
+                let (next_desc_pool, rs) = cancel_pending_requests(stream_id, desc_pool);
+                desc_pool = next_desc_pool;
+                resps.append(&mut Into::<VecDeque<_>>::into(rs));
+            }
+            _ => (),
+        };
+
+        // Process the command by the device.
+        let resp = device.process_cmd(cmd, &poll_ctx, &self.resource_bridge);
+
+        match resp {
+            Ok(VideoCmdResponseType::Sync(r)) => {
+                resps.push_back((desc.clone(), Ok(r)));
+            }
+            Ok(VideoCmdResponseType::Async(tag)) => {
+                // If the command expects an asynchronous response,
+                // store `desc` to use it after the back-end device notifies the
+                // completion.
+                desc_pool.insert(tag, desc);
+            }
+            Err(e) => {
+                resps.push_back((desc.clone(), Err(e)));
+            }
+        }
+
+        Ok((desc_pool, resps))
+    }
+
+    /// Handles the command queue returns an updated `DescPool`.
+    fn handle_command_queue<'a, T: Device>(
+        &'a self,
+        cmd_queue: &mut Queue,
+        device: &mut T,
+        poll_ctx: &PollContext<Token>,
+        mut desc_pool: DescPool<'a>,
+    ) -> Result<DescPool<'a>> {
+        let _ = self.cmd_evt.read();
+
+        while let Some(desc) = cmd_queue.pop(&self.mem) {
+            let (next_desc_pool, mut resps) =
+                self.handle_command_desc(device, poll_ctx, desc_pool, desc)?;
+            desc_pool = next_desc_pool;
+            self.write_responses(cmd_queue, &mut resps)?;
+        }
+        Ok(desc_pool)
+    }
+
+    /// Handles a `VideoEvtResponseType` value and returns an updated `DescPool` and `VecDeque` of
+    /// `WritableResp` to be sent to the guest.
+    fn handle_event_resp<'a, T: Device>(
+        &'a self,
+        event_queue: &mut Queue,
+        device: &mut T,
+        mut desc_pool: DescPool<'a>,
+        resp: VideoEvtResponseType,
+    ) -> Result<(DescPool<'a>, VecDeque<WritableResp>)> {
+        let mut responses: VecDeque<WritableResp> = Default::default();
+        match resp {
+            VideoEvtResponseType::AsyncCmd {
+                tag: AsyncCmdTag::Drain { stream_id },
+                resp,
+            } => {
+                let tag = AsyncCmdTag::Drain { stream_id };
+                let drain_desc = desc_pool
+                    .remove(&tag)
+                    .ok_or_else(|| Error::UnexpectedResponse(tag))?;
+
+                // When `Drain` request is completed, returns an empty output resource
+                // with EOS flag first.
+                let resource_id = device
+                    .take_resource_id_to_notify_eos(stream_id)
+                    .ok_or_else(|| Error::NoEOSBuffer { stream_id })?;
+
+                let q_desc = desc_pool
+                    .remove(&AsyncCmdTag::Queue {
+                        stream_id,
+                        queue_type: QueueType::Output,
+                        resource_id,
+                    })
+                    .ok_or_else(|| Error::InvalidEOSResource {
+                        stream_id,
+                        resource_id,
+                    })?;
+
+                responses.push_back((
+                    q_desc,
+                    Ok(CmdResponse::ResourceQueue {
+                        timestamp: 0,
+                        flags: protocol::VIRTIO_VIDEO_BUFFER_FLAG_EOS,
+                        size: 0,
+                    }),
+                ));
+
+                // Then, responds the Drain request.
+                responses.push_back((drain_desc, resp));
+            }
+            VideoEvtResponseType::AsyncCmd {
+                tag:
+                    AsyncCmdTag::Clear {
+                        queue_type,
+                        stream_id,
+                    },
+                resp,
+            } => {
+                let tag = AsyncCmdTag::Clear {
+                    queue_type,
+                    stream_id,
+                };
+                let desc = desc_pool
+                    .remove(&tag)
+                    .ok_or_else(|| Error::UnexpectedResponse(tag))?;
+
+                // When `Clear` request is completed, invalidate all pending requests.
+                let (next_desc_pool, resps) = cancel_pending_requests(stream_id, desc_pool);
+                desc_pool = next_desc_pool;
+                responses.append(&mut Into::<VecDeque<_>>::into(resps));
+
+                // Then, responds the `Clear` request.
+                responses.push_back((desc, resp));
+            }
+            VideoEvtResponseType::AsyncCmd { tag, resp } => {
+                let desc = desc_pool
+                    .remove(&tag)
+                    .ok_or_else(|| Error::UnexpectedResponse(tag))?;
+                responses.push_back((desc, resp));
+            }
+            VideoEvtResponseType::Event(mut evt) => {
+                self.write_event(event_queue, &mut evt)?;
+            }
+        };
+        Ok((desc_pool, responses))
+    }
+
+    /// Handles an event notified via an event FD and returns an updated `DescPool`.
+    fn handle_event_fd<'a, T: Device>(
+        &'a self,
+        cmd_queue: &mut Queue,
+        event_queue: &mut Queue,
+        device: &mut T,
+        desc_pool: DescPool<'a>,
+        stream_id: u32,
+    ) -> Result<DescPool<'a>> {
+        let resp = device.process_event_fd(stream_id);
+        match resp {
+            Some(r) => match self.handle_event_resp(event_queue, device, desc_pool, r) {
+                Ok((updated_desc_pool, mut resps)) => {
+                    self.write_responses(cmd_queue, &mut resps)?;
+                    Ok(updated_desc_pool)
+                }
+                Err(e) => {
+                    // Ignore result of write_event for a fatal error.
+                    let _ = self.write_event(
+                        event_queue,
+                        &mut VideoEvt {
+                            typ: EvtType::Error,
+                            stream_id,
+                        },
+                    );
+                    Err(e)
+                }
+            },
+            None => Ok(desc_pool),
+        }
+    }
+
+    pub fn run<T: Device>(
+        &mut self,
+        mut cmd_queue: Queue,
+        mut event_queue: Queue,
+        mut device: T,
+    ) -> Result<()> {
+        let poll_ctx: PollContext<Token> = PollContext::build_with(&[
+            (&self.cmd_evt, Token::CmdQueue),
+            (&self.event_evt, Token::EventQueue),
+            (&self.kill_evt, Token::Kill),
+            (self.interrupt.get_resample_evt(), Token::InterruptResample),
+        ])
+        .map_err(Error::PollContextCreationFailed)?;
+
+        // Stores descriptors in which responses for asynchronous commands will be written.
+        let mut desc_pool: DescPool<'_> = Default::default();
+
+        loop {
+            let poll_events = poll_ctx.wait().map_err(Error::PollError)?;
+
+            for poll_event in poll_events.iter_readable() {
+                match poll_event.token() {
+                    Token::CmdQueue => {
+                        desc_pool = self.handle_command_queue(
+                            &mut cmd_queue,
+                            &mut device,
+                            &poll_ctx,
+                            desc_pool,
+                        )?;
+                    }
+                    Token::EventQueue => {
+                        let _ = self.event_evt.read();
+                    }
+                    Token::EventFd { id } => {
+                        desc_pool = self.handle_event_fd(
+                            &mut cmd_queue,
+                            &mut event_queue,
+                            &mut device,
+                            desc_pool,
+                            id,
+                        )?;
+                    }
+                    Token::InterruptResample => {
+                        self.interrupt.interrupt_resample();
+                    }
+                    Token::Kill => return Ok(()),
+                }
+            }
+        }
+    }
+}
diff --git a/devices/src/virtio/wl.rs b/devices/src/virtio/wl.rs
index afeea3c..6fefd51 100644
--- a/devices/src/virtio/wl.rs
+++ b/devices/src/virtio/wl.rs
@@ -727,12 +727,10 @@ impl WlVfd {
     fn send(&mut self, fds: &[RawFd], data: &mut Reader) -> WlResult<WlResp> {
         if let Some(socket) = &self.socket {
             socket
-                .send_with_fds(
-                    data.get_iovec(usize::max_value())
-                        .map_err(WlError::ParseDesc)?,
-                    fds,
-                )
+                .send_with_fds(data.get_remaining(), fds)
                 .map_err(WlError::SendVfd)?;
+            // All remaining data in `data` is now considered consumed.
+            data.consume(::std::usize::MAX);
             Ok(WlResp::Ok)
         } else if let Some((_, local_pipe)) = &mut self.local_pipe {
             // Impossible to send fds over a simple pipe.