summary refs log tree commit diff
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
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'
-rw-r--r--Cargo.lock15
-rw-r--r--Cargo.toml3
-rw-r--r--acpi_tables/src/sdt.rs21
-rw-r--r--arch/Cargo.toml1
-rw-r--r--arch/src/android.rs2
-rw-r--r--arch/src/lib.rs2
-rw-r--r--arch/src/serial.rs8
-rw-r--r--crosvm_plugin/src/lib.rs4
-rw-r--r--data_model/Cargo.toml1
-rw-r--r--data_model/src/volatile_memory.rs252
-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
-rw-r--r--disk/src/android_sparse.rs47
-rw-r--r--disk/src/composite.rs24
-rw-r--r--disk/src/qcow/mod.rs51
-rw-r--r--disk/src/qcow/qcow_raw_file.rs13
-rw-r--r--docker/checkout_commits.env2
-rw-r--r--fuzz/virtqueue_fuzzer.rs5
-rw-r--r--gpu_buffer/src/lib.rs1
-rw-r--r--gpu_display/examples/simple_open.rs2
-rw-r--r--gpu_display/src/gpu_display_stub.rs4
-rw-r--r--gpu_display/src/gpu_display_wl.rs5
-rw-r--r--gpu_display/src/gpu_display_x.rs2
-rw-r--r--gpu_display/src/lib.rs2
-rw-r--r--gpu_renderer/src/lib.rs30
-rw-r--r--hypervisor/Cargo.toml2
-rw-r--r--hypervisor/src/kvm/x86_64.rs384
-rw-r--r--hypervisor/src/x86_64.rs294
-rw-r--r--msg_socket/src/lib.rs5
-rw-r--r--resources/src/address_allocator.rs229
-rw-r--r--resources/src/lib.rs6
-rw-r--r--seccomp/x86_64/video_device.policy24
-rw-r--r--src/crosvm.rs6
-rw-r--r--src/linux.rs97
-rw-r--r--src/main.rs35
-rw-r--r--src/plugin/process.rs4
-rw-r--r--sys_util/src/file_traits.rs52
-rw-r--r--sys_util/src/guest_memory.rs140
-rw-r--r--sys_util/src/mmap.rs18
-rw-r--r--sys_util/src/sock_ctrl_msg.rs78
-rw-r--r--x86_64/src/acpi.rs61
-rw-r--r--x86_64/src/lib.rs17
-rw-r--r--x86_64/src/mptable.rs3
65 files changed, 5064 insertions, 558 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 4df284d..7a9a5c4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -30,6 +30,7 @@ dependencies = [
 name = "arch"
 version = "0.1.0"
 dependencies = [
+ "acpi_tables 0.1.0",
  "devices 0.1.0",
  "io_jail 0.1.0",
  "kernel_cmdline 0.1.0",
@@ -161,6 +162,7 @@ name = "data_model"
 version = "0.1.0"
 dependencies = [
  "assertions 0.1.0",
+ "libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -183,6 +185,7 @@ dependencies = [
  "kvm_sys 0.1.0",
  "libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)",
  "libcras 0.1.0",
+ "libvda 0.1.0",
  "linux_input_sys 0.1.0",
  "msg_on_socket_derive 0.1.0",
  "msg_socket 0.1.0",
@@ -347,9 +350,12 @@ dependencies = [
 name = "hypervisor"
 version = "0.1.0"
 dependencies = [
+ "bit_field 0.1.0",
+ "enumn 0.1.0",
  "kvm 0.1.0",
  "kvm_sys 0.1.0",
  "libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)",
+ "sync 0.1.0",
  "sys_util 0.1.0",
 ]
 
@@ -412,6 +418,15 @@ dependencies = [
 ]
 
 [[package]]
+name = "libvda"
+version = "0.1.0"
+dependencies = [
+ "enumn 0.1.0",
+ "libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
 name = "linux_input_sys"
 version = "0.1.0"
 dependencies = [
diff --git a/Cargo.toml b/Cargo.toml
index 3fdad6b..55f7df0 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -35,6 +35,8 @@ default-no-sandbox = []
 gpu = ["devices/gpu"]
 plugin = ["protos/plugin", "crosvm_plugin", "protobuf"]
 tpm = ["devices/tpm"]
+video-decoder = ["devices/video-decoder"]
+video-encoder = ["devices/video-encoder"]
 wl-dmabuf = ["devices/wl-dmabuf", "gpu_buffer", "resources/wl-dmabuf"]
 x = ["devices/x"]
 virtio-gpu-next = ["gpu_renderer/virtio-gpu-next"]
@@ -89,6 +91,7 @@ assertions = { path = "assertions" }
 audio_streams = { path = "../../third_party/adhd/audio_streams" } # ignored by ebuild
 data_model = { path = "data_model" }
 libcras = { path = "../../third_party/adhd/cras/client/libcras" } # ignored by ebuild
+libvda = { path = "../../platform2/arc/vm/libvda/rust" } # ignored by ebuild
 minijail-sys = { path = "../../aosp/external/minijail" } # ignored by ebuild
 poll_token_derive = { path = "sys_util/poll_token_derive" }
 sync = { path = "sync" }
diff --git a/acpi_tables/src/sdt.rs b/acpi_tables/src/sdt.rs
index 0ea61a0..96f4d0f 100644
--- a/acpi_tables/src/sdt.rs
+++ b/acpi_tables/src/sdt.rs
@@ -2,6 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+use std::fs::File;
+use std::io::{ErrorKind, Read, Result};
+use std::path::PathBuf;
+
 use data_model::DataInit;
 
 /// SDT represents for System Description Table. The structure SDT is a
@@ -54,6 +58,23 @@ impl SDT {
         sdt
     }
 
+    /// Set up the ACPI table from file content. Verify file checksum.
+    pub fn from_file(path: &PathBuf) -> Result<Self> {
+        let mut file = File::open(path)?;
+        let mut data = Vec::new();
+        file.read_to_end(&mut data)?;
+        let checksum = super::generate_checksum(data.as_slice());
+        if checksum == 0 {
+            Ok(SDT { data })
+        } else {
+            Err(ErrorKind::InvalidData.into())
+        }
+    }
+
+    pub fn is_signature(&self, signature: &[u8; 4]) -> bool {
+        self.data[0..4] == *signature
+    }
+
     fn update_checksum(&mut self) {
         self.data[CHECKSUM_OFFSET] = 0;
         let checksum = super::generate_checksum(self.data.as_slice());
diff --git a/arch/Cargo.toml b/arch/Cargo.toml
index 6b4e529..68b40a3 100644
--- a/arch/Cargo.toml
+++ b/arch/Cargo.toml
@@ -5,6 +5,7 @@ authors = ["The Chromium OS Authors"]
 edition = "2018"
 
 [dependencies]
+acpi_tables = { path = "../acpi_tables" }
 devices = { path = "../devices" }
 io_jail = { path = "../io_jail" }
 kernel_cmdline = { path = "../kernel_cmdline" }
diff --git a/arch/src/android.rs b/arch/src/android.rs
index 5311d3f..a3242ab 100644
--- a/arch/src/android.rs
+++ b/arch/src/android.rs
@@ -36,7 +36,7 @@ pub fn create_android_fdt(fdt: &mut Vec<u8>, fstab: File) -> Result<()> {
     begin_node(fdt, "vendor")?;
     for vec in dtprop {
         let content = std::fs::read_to_string(&vec[2]).map_err(Error::FdtIoError)?;
-        property_string(fdt, &vec[1], &content);
+        property_string(fdt, &vec[1], &content)?;
     }
     end_node(fdt)?; // vendor
     begin_node(fdt, "fstab")?;
diff --git a/arch/src/lib.rs b/arch/src/lib.rs
index 3445f18..bab679a 100644
--- a/arch/src/lib.rs
+++ b/arch/src/lib.rs
@@ -16,6 +16,7 @@ use std::os::unix::io::AsRawFd;
 use std::path::PathBuf;
 use std::sync::Arc;
 
+use acpi_tables::sdt::SDT;
 use devices::split_irqchip_common::GsiRelay;
 use devices::virtio::VirtioDevice;
 use devices::{
@@ -57,6 +58,7 @@ pub struct VmComponents {
     pub initrd_image: Option<File>,
     pub extra_kernel_params: Vec<String>,
     pub wayland_dmabuf: bool,
+    pub acpi_sdts: Vec<SDT>,
 }
 
 /// Holds the elements needed to run a Linux VM. Created by `build_vm`.
diff --git a/arch/src/serial.rs b/arch/src/serial.rs
index f24f4bc..b817cfd 100644
--- a/arch/src/serial.rs
+++ b/arch/src/serial.rs
@@ -4,7 +4,7 @@
 
 use std::collections::BTreeMap;
 use std::fmt::{self, Display};
-use std::fs::File;
+use std::fs::{File, OpenOptions};
 use std::io::{self, stdin, stdout};
 use std::os::unix::io::{AsRawFd, RawFd};
 use std::path::PathBuf;
@@ -170,7 +170,11 @@ impl SerialParameters {
             }
             SerialType::File => match &self.path {
                 Some(path) => {
-                    let file = File::create(path.as_path()).map_err(Error::FileError)?;
+                    let file = OpenOptions::new()
+                        .append(true)
+                        .create(true)
+                        .open(path.as_path())
+                        .map_err(Error::FileError)?;
                     keep_fds.push(file.as_raw_fd());
                     Some(Box::new(file))
                 }
diff --git a/crosvm_plugin/src/lib.rs b/crosvm_plugin/src/lib.rs
index 05c19bf..e9d135c 100644
--- a/crosvm_plugin/src/lib.rs
+++ b/crosvm_plugin/src/lib.rs
@@ -17,7 +17,7 @@
 
 use std::env;
 use std::fs::File;
-use std::io::{Read, Write};
+use std::io::{IoSlice, Read, Write};
 use std::mem::{size_of, swap};
 use std::os::raw::{c_int, c_void};
 use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
@@ -290,7 +290,7 @@ impl crosvm {
             .write_to_vec(&mut self.request_buffer)
             .map_err(proto_error_to_int)?;
         self.socket
-            .send_with_fds(self.request_buffer.as_slice(), fds)
+            .send_with_fds(&[IoSlice::new(self.request_buffer.as_slice())], fds)
             .map_err(|e| -e.errno())?;
 
         let mut datagram_fds = [0; MAX_DATAGRAM_FD];
diff --git a/data_model/Cargo.toml b/data_model/Cargo.toml
index b1447a6..3909956 100644
--- a/data_model/Cargo.toml
+++ b/data_model/Cargo.toml
@@ -7,5 +7,6 @@ include = ["src/**/*", "Cargo.toml"]
 
 [dependencies]
 assertions = { path = "../assertions" } # provided by ebuild
+libc = "*"
 
 [workspace]
diff --git a/data_model/src/volatile_memory.rs b/data_model/src/volatile_memory.rs
index d834f0b..026be71 100644
--- a/data_model/src/volatile_memory.rs
+++ b/data_model/src/volatile_memory.rs
@@ -20,21 +20,25 @@
 //! not reordered or elided the access.
 
 use std::cmp::min;
-use std::fmt::{self, Display};
+use std::ffi::c_void;
+use std::fmt::{self, Debug, Display};
 use std::marker::PhantomData;
 use std::mem::size_of;
 use std::ptr::{copy, null_mut, read_volatile, write_bytes, write_volatile};
 use std::result;
+use std::slice;
 use std::usize;
 
+use libc::iovec;
+
 use crate::DataInit;
 
 #[derive(Eq, PartialEq, Debug)]
 pub enum VolatileMemoryError {
     /// `addr` is out of bounds of the volatile memory slice.
-    OutOfBounds { addr: u64 },
-    /// Taking a slice at `base` with `offset` would overflow `u64`.
-    Overflow { base: u64, offset: u64 },
+    OutOfBounds { addr: usize },
+    /// Taking a slice at `base` with `offset` would overflow `usize`.
+    Overflow { base: usize, offset: usize },
 }
 
 impl Display for VolatileMemoryError {
@@ -65,7 +69,7 @@ type Result<T> = VolatileMemoryResult<T>;
 ///
 /// ```
 /// # use data_model::*;
-/// # fn get_slice(offset: u64, count: u64) -> VolatileMemoryResult<()> {
+/// # fn get_slice(offset: usize, count: usize) -> VolatileMemoryResult<()> {
 ///   let mem_end = calc_offset(offset, count)?;
 ///   if mem_end > 100 {
 ///       return Err(VolatileMemoryError::OutOfBounds{addr: mem_end});
@@ -73,7 +77,7 @@ type Result<T> = VolatileMemoryResult<T>;
 /// # Ok(())
 /// # }
 /// ```
-pub fn calc_offset(base: u64, offset: u64) -> Result<u64> {
+pub fn calc_offset(base: usize, offset: usize) -> Result<usize> {
     match base.checked_add(offset) {
         None => Err(Error::Overflow { base, offset }),
         Some(m) => Ok(m),
@@ -84,109 +88,149 @@ pub fn calc_offset(base: u64, offset: u64) -> Result<u64> {
 pub trait VolatileMemory {
     /// Gets a slice of memory at `offset` that is `count` bytes in length and supports volatile
     /// access.
-    fn get_slice(&self, offset: u64, count: u64) -> Result<VolatileSlice>;
+    fn get_slice(&self, offset: usize, count: usize) -> Result<VolatileSlice>;
 
     /// Gets a `VolatileRef` at `offset`.
-    fn get_ref<T: DataInit>(&self, offset: u64) -> Result<VolatileRef<T>> {
-        let slice = self.get_slice(offset, size_of::<T>() as u64)?;
+    fn get_ref<T: DataInit>(&self, offset: usize) -> Result<VolatileRef<T>> {
+        let slice = self.get_slice(offset, size_of::<T>())?;
         Ok(VolatileRef {
-            addr: slice.addr as *mut T,
+            addr: slice.as_mut_ptr() as *mut T,
             phantom: PhantomData,
         })
     }
 }
 
-impl<'a> VolatileMemory for &'a mut [u8] {
-    fn get_slice(&self, offset: u64, count: u64) -> Result<VolatileSlice> {
-        let mem_end = calc_offset(offset, count)?;
-        if mem_end > self.len() as u64 {
-            return Err(Error::OutOfBounds { addr: mem_end });
-        }
-        Ok(unsafe { VolatileSlice::new((self.as_ptr() as u64 + offset) as *mut _, count) })
-    }
-}
-
-/// A slice of raw memory that supports volatile access.
-#[derive(Copy, Clone, Debug)]
+/// A slice of raw memory that supports volatile access. Like `std::io::IoSliceMut`, this type is
+/// guaranteed to be ABI-compatible with `libc::iovec` but unlike `IoSliceMut`, it doesn't
+/// automatically deref to `&mut [u8]`.
+#[derive(Copy, Clone)]
+#[repr(transparent)]
 pub struct VolatileSlice<'a> {
-    addr: *mut u8,
-    size: u64,
-    phantom: PhantomData<&'a u8>,
+    iov: iovec,
+    phantom: PhantomData<&'a mut [u8]>,
 }
 
 impl<'a> Default for VolatileSlice<'a> {
     fn default() -> VolatileSlice<'a> {
         VolatileSlice {
-            addr: null_mut(),
-            size: 0,
+            iov: iovec {
+                iov_base: null_mut(),
+                iov_len: 0,
+            },
             phantom: PhantomData,
         }
     }
 }
 
+struct DebugIovec(iovec);
+impl Debug for DebugIovec {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        f.debug_struct("iovec")
+            .field("iov_base", &self.0.iov_base)
+            .field("iov_len", &self.0.iov_len)
+            .finish()
+    }
+}
+
+impl<'a> Debug for VolatileSlice<'a> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        f.debug_struct("VolatileSlice")
+            .field("iov", &DebugIovec(self.iov))
+            .field("phantom", &self.phantom)
+            .finish()
+    }
+}
+
 impl<'a> VolatileSlice<'a> {
     /// Creates a slice of raw memory that must support volatile access.
+    pub fn new(buf: &mut [u8]) -> VolatileSlice {
+        VolatileSlice {
+            iov: iovec {
+                iov_base: buf.as_mut_ptr() as *mut c_void,
+                iov_len: buf.len(),
+            },
+            phantom: PhantomData,
+        }
+    }
+
+    /// Creates a `VolatileSlice` from a pointer and a length.
     ///
-    /// To use this safely, the caller must guarantee that the memory at `addr` is `size` bytes long
-    /// and is available for the duration of the lifetime of the new `VolatileSlice`. The caller
-    /// must also guarantee that all other users of the given chunk of memory are using volatile
-    /// accesses.
-    pub unsafe fn new(addr: *mut u8, size: u64) -> VolatileSlice<'a> {
+    /// # Safety
+    ///
+    /// In order to use this method safely, `addr` must be valid for reads and writes of `len` bytes
+    /// and should live for the entire duration of lifetime `'a`.
+    pub unsafe fn from_raw_parts(addr: *mut u8, len: usize) -> VolatileSlice<'a> {
         VolatileSlice {
-            addr,
-            size,
+            iov: iovec {
+                iov_base: addr as *mut c_void,
+                iov_len: len,
+            },
             phantom: PhantomData,
         }
     }
 
-    /// Gets the address of this slice's memory.
-    pub fn as_ptr(&self) -> *mut u8 {
-        self.addr
+    /// Gets a const pointer to this slice's memory.
+    pub fn as_ptr(&self) -> *const u8 {
+        self.iov.iov_base as *const u8
+    }
+
+    /// Gets a mutable pointer to this slice's memory.
+    pub fn as_mut_ptr(&self) -> *mut u8 {
+        self.iov.iov_base as *mut u8
     }
 
     /// Gets the size of this slice.
-    pub fn size(&self) -> u64 {
-        self.size
+    pub fn size(&self) -> usize {
+        self.iov.iov_len
+    }
+
+    /// Returns this `VolatileSlice` as an iovec.
+    pub fn as_iovec(&self) -> iovec {
+        self.iov
+    }
+
+    /// Converts a slice of `VolatileSlice`s into a slice of `iovec`s.
+    pub fn as_iovecs<'slice>(iovs: &'slice [VolatileSlice<'_>]) -> &'slice [iovec] {
+        // Safe because `VolatileSlice` is ABI-compatible with `iovec`.
+        unsafe { slice::from_raw_parts(iovs.as_ptr() as *const iovec, iovs.len()) }
     }
 
     /// Creates a copy of this slice with the address increased by `count` bytes, and the size
     /// reduced by `count` bytes.
-    pub fn offset(self, count: u64) -> Result<VolatileSlice<'a>> {
-        let new_addr =
-            (self.addr as u64)
-                .checked_add(count)
-                .ok_or(VolatileMemoryError::Overflow {
-                    base: self.addr as u64,
-                    offset: count,
-                })?;
-        if new_addr > usize::MAX as u64 {
-            return Err(VolatileMemoryError::Overflow {
-                base: self.addr as u64,
+    pub fn offset(self, count: usize) -> Result<VolatileSlice<'a>> {
+        let new_addr = (self.as_mut_ptr() as usize).checked_add(count).ok_or(
+            VolatileMemoryError::Overflow {
+                base: self.as_mut_ptr() as usize,
                 offset: count,
-            });
-        }
+            },
+        )?;
         let new_size = self
-            .size
+            .size()
             .checked_sub(count)
             .ok_or(VolatileMemoryError::OutOfBounds { addr: new_addr })?;
+
         // Safe because the memory has the same lifetime and points to a subset of the memory of the
         // original slice.
-        unsafe { Ok(VolatileSlice::new(new_addr as *mut u8, new_size)) }
+        unsafe { Ok(VolatileSlice::from_raw_parts(new_addr as *mut u8, new_size)) }
     }
 
     /// Similar to `get_slice` but the returned slice outlives this slice.
     ///
     /// The returned slice's lifetime is still limited by the underlying data's lifetime.
-    pub fn sub_slice(self, offset: u64, count: u64) -> Result<VolatileSlice<'a>> {
+    pub fn sub_slice(self, offset: usize, count: usize) -> Result<VolatileSlice<'a>> {
         let mem_end = calc_offset(offset, count)?;
-        if mem_end > self.size {
+        if mem_end > self.size() {
             return Err(Error::OutOfBounds { addr: mem_end });
         }
-        Ok(VolatileSlice {
-            addr: (self.addr as u64 + offset) as *mut _,
-            size: count,
-            phantom: PhantomData,
-        })
+        let new_addr = (self.as_mut_ptr() as usize).checked_add(offset).ok_or(
+            VolatileMemoryError::Overflow {
+                base: self.as_mut_ptr() as usize,
+                offset,
+            },
+        )?;
+
+        // Safe because we have verified that the new memory is a subset of the original slice.
+        Ok(unsafe { VolatileSlice::from_raw_parts(new_addr as *mut u8, count) })
     }
 
     /// Sets each byte of this slice with the given byte, similar to `memset`.
@@ -196,13 +240,12 @@ impl<'a> VolatileSlice<'a> {
     /// # Examples
     ///
     /// ```
-    /// # use data_model::VolatileMemory;
+    /// # use data_model::VolatileSlice;
     /// # fn test_write_45() -> Result<(), ()> {
     /// let mut mem = [0u8; 32];
-    /// let mem_ref = &mut mem[..];
-    /// let vslice = mem_ref.get_slice(0, 32).map_err(|_| ())?;
+    /// let vslice = VolatileSlice::new(&mut mem[..]);
     /// vslice.write_bytes(45);
-    /// for &mut v in mem_ref {
+    /// for &v in &mem[..] {
     ///     assert_eq!(v, 45);
     /// }
     /// # Ok(())
@@ -210,7 +253,7 @@ impl<'a> VolatileSlice<'a> {
     pub fn write_bytes(&self, value: u8) {
         // Safe because the memory is valid and needs only byte alignment.
         unsafe {
-            write_bytes(self.as_ptr(), value, self.size as usize);
+            write_bytes(self.as_mut_ptr(), value, self.size());
         }
     }
 
@@ -224,11 +267,10 @@ impl<'a> VolatileSlice<'a> {
     /// ```
     /// # use std::fs::File;
     /// # use std::path::Path;
-    /// # use data_model::VolatileMemory;
+    /// # use data_model::VolatileSlice;
     /// # fn test_write_null() -> Result<(), ()> {
     /// let mut mem = [0u8; 32];
-    /// let mem_ref = &mut mem[..];
-    /// let vslice = mem_ref.get_slice(0, 32).map_err(|_| ())?;
+    /// let vslice = VolatileSlice::new(&mut mem[..]);
     /// let mut buf = [5u8; 16];
     /// vslice.copy_to(&mut buf[..]);
     /// for v in &buf[..] {
@@ -241,8 +283,8 @@ impl<'a> VolatileSlice<'a> {
     where
         T: DataInit,
     {
-        let mut addr = self.addr;
-        for v in buf.iter_mut().take(self.size as usize / size_of::<T>()) {
+        let mut addr = self.as_mut_ptr() as *const u8;
+        for v in buf.iter_mut().take(self.size() / size_of::<T>()) {
             unsafe {
                 *v = read_volatile(addr as *const T);
                 addr = addr.add(size_of::<T>());
@@ -256,18 +298,21 @@ impl<'a> VolatileSlice<'a> {
     /// # Examples
     ///
     /// ```
-    /// # use data_model::VolatileMemory;
+    /// # use data_model::{VolatileMemory, VolatileSlice};
     /// # fn test_write_null() -> Result<(), ()> {
     /// let mut mem = [0u8; 32];
-    /// let mem_ref = &mut mem[..];
-    /// let vslice = mem_ref.get_slice(0, 32).map_err(|_| ())?;
+    /// let vslice = VolatileSlice::new(&mut mem[..]);
     /// vslice.copy_to_volatile_slice(vslice.get_slice(16, 16).map_err(|_| ())?);
     /// # Ok(())
     /// # }
     /// ```
     pub fn copy_to_volatile_slice(&self, slice: VolatileSlice) {
         unsafe {
-            copy(self.addr, slice.addr, min(self.size, slice.size) as usize);
+            copy(
+                self.as_mut_ptr() as *const u8,
+                slice.as_mut_ptr(),
+                min(self.size(), slice.size()),
+            );
         }
     }
 
@@ -281,11 +326,10 @@ impl<'a> VolatileSlice<'a> {
     /// ```
     /// # use std::fs::File;
     /// # use std::path::Path;
-    /// # use data_model::VolatileMemory;
+    /// # use data_model::{VolatileMemory, VolatileSlice};
     /// # fn test_write_null() -> Result<(), ()> {
     /// let mut mem = [0u8; 32];
-    /// let mem_ref = &mut mem[..];
-    /// let vslice = mem_ref.get_slice(0, 32).map_err(|_| ())?;
+    /// let vslice = VolatileSlice::new(&mut mem[..]);
     /// let buf = [5u8; 64];
     /// vslice.copy_from(&buf[..]);
     /// for i in 0..4 {
@@ -298,8 +342,8 @@ impl<'a> VolatileSlice<'a> {
     where
         T: DataInit,
     {
-        let mut addr = self.addr;
-        for &v in buf.iter().take(self.size as usize / size_of::<T>()) {
+        let mut addr = self.as_mut_ptr();
+        for &v in buf.iter().take(self.size() / size_of::<T>()) {
             unsafe {
                 write_volatile(addr as *mut T, v);
                 addr = addr.add(size_of::<T>());
@@ -309,16 +353,8 @@ impl<'a> VolatileSlice<'a> {
 }
 
 impl<'a> VolatileMemory for VolatileSlice<'a> {
-    fn get_slice(&self, offset: u64, count: u64) -> Result<VolatileSlice> {
-        let mem_end = calc_offset(offset, count)?;
-        if mem_end > self.size {
-            return Err(Error::OutOfBounds { addr: mem_end });
-        }
-        Ok(VolatileSlice {
-            addr: (self.addr as u64 + offset) as *mut _,
-            size: count,
-            phantom: PhantomData,
-        })
+    fn get_slice(&self, offset: usize, count: usize) -> Result<VolatileSlice> {
+        self.sub_slice(offset, count)
     }
 }
 
@@ -358,7 +394,7 @@ impl<'a, T: DataInit> VolatileRef<'a, T> {
     }
 
     /// Gets the address of this slice's memory.
-    pub fn as_ptr(&self) -> *mut T {
+    pub fn as_mut_ptr(&self) -> *mut T {
         self.addr
     }
 
@@ -370,10 +406,10 @@ impl<'a, T: DataInit> VolatileRef<'a, T> {
     /// # use std::mem::size_of;
     /// # use data_model::VolatileRef;
     ///   let v_ref = unsafe { VolatileRef::new(0 as *mut u32) };
-    ///   assert_eq!(v_ref.size(), size_of::<u32>() as u64);
+    ///   assert_eq!(v_ref.size(), size_of::<u32>());
     /// ```
-    pub fn size(&self) -> u64 {
-        size_of::<T>() as u64
+    pub fn size(&self) -> usize {
+        size_of::<T>()
     }
 
     /// Does a volatile write of the value `v` to the address of this ref.
@@ -393,7 +429,7 @@ impl<'a, T: DataInit> VolatileRef<'a, T> {
 
     /// Converts this `T` reference to a raw slice with the same size and address.
     pub fn to_slice(&self) -> VolatileSlice<'a> {
-        unsafe { VolatileSlice::new(self.addr as *mut u8, size_of::<T>() as u64) }
+        unsafe { VolatileSlice::from_raw_parts(self.as_mut_ptr() as *mut u8, self.size()) }
     }
 }
 
@@ -418,19 +454,27 @@ mod tests {
     }
 
     impl VolatileMemory for VecMem {
-        fn get_slice(&self, offset: u64, count: u64) -> Result<VolatileSlice> {
+        fn get_slice(&self, offset: usize, count: usize) -> Result<VolatileSlice> {
             let mem_end = calc_offset(offset, count)?;
-            if mem_end > self.mem.len() as u64 {
+            if mem_end > self.mem.len() {
                 return Err(Error::OutOfBounds { addr: mem_end });
             }
-            Ok(unsafe { VolatileSlice::new((self.mem.as_ptr() as u64 + offset) as *mut _, count) })
+
+            let new_addr = (self.mem.as_ptr() as usize).checked_add(offset).ok_or(
+                VolatileMemoryError::Overflow {
+                    base: self.mem.as_ptr() as usize,
+                    offset,
+                },
+            )?;
+
+            Ok(unsafe { VolatileSlice::from_raw_parts(new_addr as *mut u8, count) })
         }
     }
 
     #[test]
     fn ref_store() {
         let mut a = [0u8; 1];
-        let a_ref = &mut a[..];
+        let a_ref = VolatileSlice::new(&mut a[..]);
         let v_ref = a_ref.get_ref(0).unwrap();
         v_ref.store(2u8);
         assert_eq!(a[0], 2);
@@ -440,7 +484,7 @@ mod tests {
     fn ref_load() {
         let mut a = [5u8; 1];
         {
-            let a_ref = &mut a[..];
+            let a_ref = VolatileSlice::new(&mut a[..]);
             let c = {
                 let v_ref = a_ref.get_ref::<u8>(0).unwrap();
                 assert_eq!(v_ref.load(), 5u8);
@@ -457,11 +501,11 @@ mod tests {
     #[test]
     fn ref_to_slice() {
         let mut a = [1u8; 5];
-        let a_ref = &mut a[..];
+        let a_ref = VolatileSlice::new(&mut a[..]);
         let v_ref = a_ref.get_ref(1).unwrap();
         v_ref.store(0x12345678u32);
         let ref_slice = v_ref.to_slice();
-        assert_eq!(v_ref.as_ptr() as u64, ref_slice.as_ptr() as u64);
+        assert_eq!(v_ref.as_mut_ptr() as usize, ref_slice.as_mut_ptr() as usize);
         assert_eq!(v_ref.size(), ref_slice.size());
     }
 
@@ -506,7 +550,7 @@ mod tests {
 
     #[test]
     fn slice_overflow_error() {
-        use std::u64::MAX;
+        use std::usize::MAX;
         let a = VecMem::new(1);
         let res = a.get_slice(MAX, 1).unwrap_err();
         assert_eq!(
@@ -528,7 +572,7 @@ mod tests {
 
     #[test]
     fn ref_overflow_error() {
-        use std::u64::MAX;
+        use std::usize::MAX;
         let a = VecMem::new(1);
         let res = a.get_ref::<u8>(MAX).unwrap_err();
         assert_eq!(
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.
diff --git a/disk/src/android_sparse.rs b/disk/src/android_sparse.rs
index 0772bfc..6e1a50c 100644
--- a/disk/src/android_sparse.rs
+++ b/disk/src/android_sparse.rs
@@ -291,9 +291,9 @@ impl FileReadWriteAtVolatile for AndroidSparse {
         ))?;
         let chunk_offset = offset - chunk_start;
         let chunk_size = *expanded_size;
-        let subslice = if chunk_offset + slice.size() > chunk_size {
+        let subslice = if chunk_offset + (slice.size() as u64) > chunk_size {
             slice
-                .sub_slice(0, chunk_size - chunk_offset)
+                .sub_slice(0, (chunk_size - chunk_offset) as usize)
                 .map_err(|e| io::Error::new(ErrorKind::InvalidData, format!("{:?}", e)))?
         } else {
             slice
@@ -331,7 +331,6 @@ impl FileReadWriteAtVolatile for AndroidSparse {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use data_model::VolatileMemory;
     use std::io::{Cursor, Write};
     use sys_util::SharedMemory;
 
@@ -435,12 +434,11 @@ mod tests {
         }];
         let mut image = test_image(chunks);
         let mut input_memory = [55u8; 100];
-        let input_volatile_memory = &mut input_memory[..];
         image
-            .read_exact_at_volatile(input_volatile_memory.get_slice(0, 100).unwrap(), 0)
+            .read_exact_at_volatile(VolatileSlice::new(&mut input_memory[..]), 0)
             .expect("Could not read");
-        let input_vec: Vec<u8> = input_memory.into_iter().cloned().collect();
-        assert_eq!(input_vec, vec![0u8; 100]);
+        let expected = [0u8; 100];
+        assert_eq!(&expected[..], &input_memory[..]);
     }
 
     #[test]
@@ -451,12 +449,11 @@ mod tests {
         }];
         let mut image = test_image(chunks);
         let mut input_memory = [55u8; 8];
-        let input_volatile_memory = &mut input_memory[..];
         image
-            .read_exact_at_volatile(input_volatile_memory.get_slice(0, 8).unwrap(), 0)
+            .read_exact_at_volatile(VolatileSlice::new(&mut input_memory[..]), 0)
             .expect("Could not read");
-        let input_vec: Vec<u8> = input_memory.into_iter().cloned().collect();
-        assert_eq!(input_vec, vec![10, 20, 10, 20, 10, 20, 10, 20]);
+        let expected = [10, 20, 10, 20, 10, 20, 10, 20];
+        assert_eq!(&expected[..], &input_memory[..]);
     }
 
     #[test]
@@ -467,12 +464,11 @@ mod tests {
         }];
         let mut image = test_image(chunks);
         let mut input_memory = [55u8; 6];
-        let input_volatile_memory = &mut input_memory[..];
         image
-            .read_exact_at_volatile(input_volatile_memory.get_slice(0, 6).unwrap(), 1)
+            .read_exact_at_volatile(VolatileSlice::new(&mut input_memory[..]), 1)
             .expect("Could not read");
-        let input_vec: Vec<u8> = input_memory.into_iter().cloned().collect();
-        assert_eq!(input_vec, vec![20, 30, 10, 20, 30, 10]);
+        let expected = [20, 30, 10, 20, 30, 10];
+        assert_eq!(&expected[..], &input_memory[..]);
     }
 
     #[test]
@@ -489,12 +485,11 @@ mod tests {
         ];
         let mut image = test_image(chunks);
         let mut input_memory = [55u8; 7];
-        let input_volatile_memory = &mut input_memory[..];
         image
-            .read_exact_at_volatile(input_volatile_memory.get_slice(0, 7).unwrap(), 39)
+            .read_exact_at_volatile(VolatileSlice::new(&mut input_memory[..]), 39)
             .expect("Could not read");
-        let input_vec: Vec<u8> = input_memory.into_iter().cloned().collect();
-        assert_eq!(input_vec, vec![20, 30, 10, 20, 30, 10, 20]);
+        let expected = [20, 30, 10, 20, 30, 10, 20];
+        assert_eq!(&expected[..], &input_memory[..]);
     }
 
     #[test]
@@ -506,12 +501,11 @@ mod tests {
         let mut image = test_image(chunks);
         write!(image.file, "hello").expect("Failed to write into internal file");
         let mut input_memory = [55u8; 5];
-        let input_volatile_memory = &mut input_memory[..];
         image
-            .read_exact_at_volatile(input_volatile_memory.get_slice(0, 5).unwrap(), 0)
+            .read_exact_at_volatile(VolatileSlice::new(&mut input_memory[..]), 0)
             .expect("Could not read");
-        let input_vec: Vec<u8> = input_memory.into_iter().cloned().collect();
-        assert_eq!(input_vec, vec![104, 101, 108, 108, 111]);
+        let expected = [104, 101, 108, 108, 111];
+        assert_eq!(&expected[..], &input_memory[..]);
     }
 
     #[test]
@@ -528,11 +522,10 @@ mod tests {
         ];
         let mut image = test_image(chunks);
         let mut input_memory = [55u8; 8];
-        let input_volatile_memory = &mut input_memory[..];
         image
-            .read_exact_at_volatile(input_volatile_memory.get_slice(0, 8).unwrap(), 0)
+            .read_exact_at_volatile(VolatileSlice::new(&mut input_memory[..]), 0)
             .expect("Could not read");
-        let input_vec: Vec<u8> = input_memory.into_iter().cloned().collect();
-        assert_eq!(input_vec, vec![10, 20, 10, 20, 30, 40, 30, 40]);
+        let expected = [10, 20, 10, 20, 30, 40, 30, 40];
+        assert_eq!(&expected[..], &input_memory[..]);
     }
 }
diff --git a/disk/src/composite.rs b/disk/src/composite.rs
index e95c8e9..e316f3b 100644
--- a/disk/src/composite.rs
+++ b/disk/src/composite.rs
@@ -246,10 +246,10 @@ impl FileReadWriteAtVolatile for CompositeDiskFile {
     fn read_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> io::Result<usize> {
         let cursor_location = offset;
         let disk = self.disk_at_offset(cursor_location)?;
-        let subslice = if cursor_location + slice.size() > disk.offset + disk.length {
+        let subslice = if cursor_location + slice.size() as u64 > disk.offset + disk.length {
             let new_size = disk.offset + disk.length - cursor_location;
             slice
-                .sub_slice(0, new_size)
+                .sub_slice(0, new_size as usize)
                 .map_err(|e| io::Error::new(ErrorKind::InvalidData, format!("{:?}", e)))?
         } else {
             slice
@@ -260,10 +260,10 @@ impl FileReadWriteAtVolatile for CompositeDiskFile {
     fn write_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> io::Result<usize> {
         let cursor_location = offset;
         let disk = self.disk_at_offset(cursor_location)?;
-        let subslice = if cursor_location + slice.size() > disk.offset + disk.length {
+        let subslice = if cursor_location + slice.size() as u64 > disk.offset + disk.length {
             let new_size = disk.offset + disk.length - cursor_location;
             slice
-                .sub_slice(0, new_size)
+                .sub_slice(0, new_size as usize)
                 .map_err(|e| io::Error::new(ErrorKind::InvalidData, format!("{:?}", e)))?
         } else {
             slice
@@ -392,12 +392,12 @@ mod tests {
         };
         let mut composite = CompositeDiskFile::new(vec![disk_part]).unwrap();
         let mut input_memory = [55u8; 5];
-        let input_volatile_memory = &mut input_memory[..];
+        let input_volatile_memory = VolatileSlice::new(&mut input_memory[..]);
         composite
             .write_all_at_volatile(input_volatile_memory.get_slice(0, 5).unwrap(), 0)
             .unwrap();
         let mut output_memory = [0u8; 5];
-        let output_volatile_memory = &mut output_memory[..];
+        let output_volatile_memory = VolatileSlice::new(&mut output_memory[..]);
         composite
             .read_exact_at_volatile(output_volatile_memory.get_slice(0, 5).unwrap(), 0)
             .unwrap();
@@ -455,12 +455,12 @@ mod tests {
         let mut composite =
             CompositeDiskFile::new(vec![disk_part1, disk_part2, disk_part3]).unwrap();
         let mut input_memory = [55u8; 200];
-        let input_volatile_memory = &mut input_memory[..];
+        let input_volatile_memory = VolatileSlice::new(&mut input_memory[..]);
         composite
             .write_all_at_volatile(input_volatile_memory.get_slice(0, 200).unwrap(), 50)
             .unwrap();
         let mut output_memory = [0u8; 200];
-        let output_volatile_memory = &mut output_memory[..];
+        let output_volatile_memory = VolatileSlice::new(&mut output_memory[..]);
         composite
             .read_exact_at_volatile(output_volatile_memory.get_slice(0, 200).unwrap(), 50)
             .unwrap();
@@ -490,13 +490,13 @@ mod tests {
         let mut composite =
             CompositeDiskFile::new(vec![disk_part1, disk_part2, disk_part3]).unwrap();
         let mut input_memory = [55u8; 300];
-        let input_volatile_memory = &mut input_memory[..];
+        let input_volatile_memory = VolatileSlice::new(&mut input_memory[..]);
         composite
             .write_all_at_volatile(input_volatile_memory.get_slice(0, 300).unwrap(), 0)
             .unwrap();
         composite.punch_hole(50, 200).unwrap();
         let mut output_memory = [0u8; 300];
-        let output_volatile_memory = &mut output_memory[..];
+        let output_volatile_memory = VolatileSlice::new(&mut output_memory[..]);
         composite
             .read_exact_at_volatile(output_volatile_memory.get_slice(0, 300).unwrap(), 0)
             .unwrap();
@@ -530,7 +530,7 @@ mod tests {
         let mut composite =
             CompositeDiskFile::new(vec![disk_part1, disk_part2, disk_part3]).unwrap();
         let mut input_memory = [55u8; 300];
-        let input_volatile_memory = &mut input_memory[..];
+        let input_volatile_memory = VolatileSlice::new(&mut input_memory[..]);
         composite
             .write_all_at_volatile(input_volatile_memory.get_slice(0, 300).unwrap(), 0)
             .unwrap();
@@ -541,7 +541,7 @@ mod tests {
                 .unwrap();
         }
         let mut output_memory = [0u8; 300];
-        let output_volatile_memory = &mut output_memory[..];
+        let output_volatile_memory = VolatileSlice::new(&mut output_memory[..]);
         composite
             .read_exact_at_volatile(output_volatile_memory.get_slice(0, 300).unwrap(), 0)
             .unwrap();
diff --git a/disk/src/qcow/mod.rs b/disk/src/qcow/mod.rs
index c5e119d..c699e50 100644
--- a/disk/src/qcow/mod.rs
+++ b/disk/src/qcow/mod.rs
@@ -1090,8 +1090,7 @@ impl QcowFile {
                     let cluster_size = self.raw_file.cluster_size();
                     let cluster_begin = address - (address % cluster_size);
                     let mut cluster_data = vec![0u8; cluster_size as usize];
-                    let raw_slice = cluster_data.as_mut_slice();
-                    let volatile_slice = raw_slice.get_slice(0, cluster_size).unwrap();
+                    let volatile_slice = VolatileSlice::new(&mut cluster_data);
                     backing.read_exact_at_volatile(volatile_slice, cluster_begin)?;
                     Some(cluster_data)
                 } else {
@@ -1537,12 +1536,13 @@ impl AsRawFds for QcowFile {
 
 impl Read for QcowFile {
     fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
-        let slice = buf.get_slice(0, buf.len() as u64).unwrap();
+        let len = buf.len();
+        let slice = VolatileSlice::new(buf);
         let read_count = self.read_cb(
             self.current_offset,
-            buf.len(),
+            len,
             |file, already_read, offset, count| {
-                let sub_slice = slice.get_slice(already_read as u64, count as u64).unwrap();
+                let sub_slice = slice.get_slice(already_read, count).unwrap();
                 match file {
                     Some(f) => f.read_exact_at_volatile(sub_slice, offset),
                     None => {
@@ -1610,9 +1610,9 @@ impl FileReadWriteVolatile for QcowFile {
     fn read_volatile(&mut self, slice: VolatileSlice) -> io::Result<usize> {
         let read_count = self.read_cb(
             self.current_offset,
-            slice.size() as usize,
+            slice.size(),
             |file, read, offset, count| {
-                let sub_slice = slice.get_slice(read as u64, count as u64).unwrap();
+                let sub_slice = slice.get_slice(read, count).unwrap();
                 match file {
                     Some(f) => f.read_exact_at_volatile(sub_slice, offset),
                     None => {
@@ -1627,14 +1627,11 @@ impl FileReadWriteVolatile for QcowFile {
     }
 
     fn write_volatile(&mut self, slice: VolatileSlice) -> io::Result<usize> {
-        let write_count = self.write_cb(
-            self.current_offset,
-            slice.size() as usize,
-            |file, offset, count| {
-                let sub_slice = slice.get_slice(offset as u64, count as u64).unwrap();
+        let write_count =
+            self.write_cb(self.current_offset, slice.size(), |file, offset, count| {
+                let sub_slice = slice.get_slice(offset, count).unwrap();
                 file.write_all_volatile(sub_slice)
-            },
-        )?;
+            })?;
         self.current_offset += write_count as u64;
         Ok(write_count)
     }
@@ -1642,25 +1639,21 @@ impl FileReadWriteVolatile for QcowFile {
 
 impl FileReadWriteAtVolatile for QcowFile {
     fn read_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> io::Result<usize> {
-        self.read_cb(
-            offset,
-            slice.size() as usize,
-            |file, read, offset, count| {
-                let sub_slice = slice.get_slice(read as u64, count as u64).unwrap();
-                match file {
-                    Some(f) => f.read_exact_at_volatile(sub_slice, offset),
-                    None => {
-                        sub_slice.write_bytes(0);
-                        Ok(())
-                    }
+        self.read_cb(offset, slice.size(), |file, read, offset, count| {
+            let sub_slice = slice.get_slice(read, count).unwrap();
+            match file {
+                Some(f) => f.read_exact_at_volatile(sub_slice, offset),
+                None => {
+                    sub_slice.write_bytes(0);
+                    Ok(())
                 }
-            },
-        )
+            }
+        })
     }
 
     fn write_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> io::Result<usize> {
-        self.write_cb(offset, slice.size() as usize, |file, offset, count| {
-            let sub_slice = slice.get_slice(offset as u64, count as u64).unwrap();
+        self.write_cb(offset, slice.size(), |file, offset, count| {
+            let sub_slice = slice.get_slice(offset, count).unwrap();
             file.write_all_volatile(sub_slice)
         })
     }
diff --git a/disk/src/qcow/qcow_raw_file.rs b/disk/src/qcow/qcow_raw_file.rs
index 09d2176..5ffdc30 100644
--- a/disk/src/qcow/qcow_raw_file.rs
+++ b/disk/src/qcow/qcow_raw_file.rs
@@ -6,7 +6,7 @@ use std::fs::File;
 use std::io::{self, BufWriter, Read, Seek, SeekFrom, Write};
 use std::mem::size_of;
 
-use data_model::VolatileMemory;
+use data_model::VolatileSlice;
 use sys_util::{FileReadWriteAtVolatile, WriteZeroes};
 
 /// A qcow file. Allows reading/writing clusters and appending clusters.
@@ -149,10 +149,13 @@ impl QcowRawFile {
 
     /// Writes
     pub fn write_cluster(&mut self, address: u64, mut initial_data: Vec<u8>) -> io::Result<()> {
-        let raw_slice = initial_data.as_mut_slice();
-        let volatile_slice = raw_slice
-            .get_slice(0, self.cluster_size)
-            .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{:?}", e)))?;
+        if (initial_data.len() as u64) < self.cluster_size {
+            return Err(io::Error::new(
+                io::ErrorKind::InvalidInput,
+                "`initial_data` is too small",
+            ));
+        }
+        let volatile_slice = VolatileSlice::new(&mut initial_data[..self.cluster_size as usize]);
         self.file.write_all_at_volatile(volatile_slice, address)
     }
 }
diff --git a/docker/checkout_commits.env b/docker/checkout_commits.env
index 712b5f3..67ec77b 100644
--- a/docker/checkout_commits.env
+++ b/docker/checkout_commits.env
@@ -2,5 +2,5 @@ MESON_COMMIT=a1a8772034aef90e8d58230d8bcfce54ab27bf6a
 LIBEPOXY_COMMIT=af38a466caf9c2ae49b8acda4ff842ae44d57f78
 TPM2_COMMIT=a9bc45bb7fafc65ea8a787894434d409f533b1f1
 PLATFORM2_COMMIT=fabad43f1182bf71b3771735b4488180d08f3d59
-ADHD_COMMIT=8405d713c2c293646723a424c218af4a72e598f2
+ADHD_COMMIT=e64841b1bba00f6e2c113b002f6d1e98bc904b0d
 DRM_COMMIT=00320d7d68ddc7d815d073bb7c92d9a1f9bb8c31
diff --git a/fuzz/virtqueue_fuzzer.rs b/fuzz/virtqueue_fuzzer.rs
index dc746d4..70ad330 100644
--- a/fuzz/virtqueue_fuzzer.rs
+++ b/fuzz/virtqueue_fuzzer.rs
@@ -8,7 +8,6 @@ use std::mem::size_of;
 
 use cros_fuzz::fuzz_target;
 use cros_fuzz::rand::FuzzRng;
-use data_model::VolatileMemory;
 use devices::virtio::{DescriptorChain, Queue};
 use rand::{Rng, RngCore};
 use sys_util::{GuestAddress, GuestMemory};
@@ -71,7 +70,9 @@ fuzz_target!(|data: &[u8]| {
         }
 
         // First zero out all of the memory.
-        let vs = mem.get_slice(0, MEM_SIZE).unwrap();
+        let vs = mem
+            .get_slice_at_addr(GuestAddress(0), MEM_SIZE as usize)
+            .unwrap();
         vs.write_bytes(0);
 
         // Fill in the descriptor table.
diff --git a/gpu_buffer/src/lib.rs b/gpu_buffer/src/lib.rs
index 5d25fa2..4b56f19 100644
--- a/gpu_buffer/src/lib.rs
+++ b/gpu_buffer/src/lib.rs
@@ -461,7 +461,6 @@ impl AsRawFd for Buffer {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use data_model::VolatileMemory;
     use std::fmt::Write;
 
     #[test]
diff --git a/gpu_display/examples/simple_open.rs b/gpu_display/examples/simple_open.rs
index bb7b1a2..f1b5721 100644
--- a/gpu_display/examples/simple_open.rs
+++ b/gpu_display/examples/simple_open.rs
@@ -13,7 +13,7 @@ fn main() {
             row[x] = b | (g << 8);
         }
         mem.as_volatile_slice()
-            .offset((1280 * 4 * y) as u64)
+            .offset(1280 * 4 * y)
             .unwrap()
             .copy_from(&row);
     }
diff --git a/gpu_display/src/gpu_display_stub.rs b/gpu_display/src/gpu_display_stub.rs
index 605e009..3089f1f 100644
--- a/gpu_display/src/gpu_display_stub.rs
+++ b/gpu_display/src/gpu_display_stub.rs
@@ -18,7 +18,6 @@ struct Buffer {
     width: u32,
     height: u32,
     bytes_per_pixel: u32,
-    bytes_total: u64,
     bytes: Vec<u8>,
 }
 
@@ -28,7 +27,7 @@ impl Drop for Buffer {
 
 impl Buffer {
     fn as_volatile_slice(&mut self) -> VolatileSlice {
-        unsafe { VolatileSlice::new(self.bytes.as_mut_ptr(), self.bytes_total) }
+        VolatileSlice::new(self.bytes.as_mut_slice())
     }
 
     fn stride(&self) -> usize {
@@ -66,7 +65,6 @@ impl Surface {
                 width: self.width,
                 height: self.height,
                 bytes_per_pixel,
-                bytes_total,
                 bytes: vec![0; bytes_total as usize],
             });
         }
diff --git a/gpu_display/src/gpu_display_wl.rs b/gpu_display/src/gpu_display_wl.rs
index b96b8ef..9a3a969 100644
--- a/gpu_display/src/gpu_display_wl.rs
+++ b/gpu_display/src/gpu_display_wl.rs
@@ -255,10 +255,7 @@ impl DisplayT for DisplayWl {
         let buffer_index = (surface.buffer_index.get() + 1) % BUFFER_COUNT;
         let framebuffer = surface
             .buffer_mem
-            .get_slice(
-                (buffer_index * surface.buffer_size) as u64,
-                surface.buffer_size as u64,
-            )
+            .get_slice(buffer_index * surface.buffer_size, surface.buffer_size)
             .ok()?;
         Some(GpuDisplayFramebuffer::new(
             framebuffer,
diff --git a/gpu_display/src/gpu_display_x.rs b/gpu_display/src/gpu_display_x.rs
index 0882dc9..223fe08 100644
--- a/gpu_display/src/gpu_display_x.rs
+++ b/gpu_display/src/gpu_display_x.rs
@@ -202,7 +202,7 @@ impl Drop for Buffer {
 
 impl Buffer {
     fn as_volatile_slice(&self) -> VolatileSlice {
-        unsafe { VolatileSlice::new(self.segment_info.shmaddr as *mut _, self.size as u64) }
+        unsafe { VolatileSlice::from_raw_parts(self.segment_info.shmaddr as *mut _, self.size) }
     }
 
     fn stride(&self) -> usize {
diff --git a/gpu_display/src/lib.rs b/gpu_display/src/lib.rs
index dccf1c7..5b1cf94 100644
--- a/gpu_display/src/lib.rs
+++ b/gpu_display/src/lib.rs
@@ -107,7 +107,7 @@ impl<'a> GpuDisplayFramebuffer<'a> {
             .checked_add(width_bytes)?;
         let slice = self
             .framebuffer
-            .sub_slice(byte_offset as u64, count as u64)
+            .sub_slice(byte_offset as usize, count as usize)
             .unwrap();
 
         Some(GpuDisplayFramebuffer { slice, ..*self })
diff --git a/gpu_renderer/src/lib.rs b/gpu_renderer/src/lib.rs
index 5e5ab95..9cdb5a3 100644
--- a/gpu_renderer/src/lib.rs
+++ b/gpu_renderer/src/lib.rs
@@ -13,7 +13,6 @@ use std::cell::RefCell;
 use std::ffi::CString;
 use std::fmt::{self, Display};
 use std::fs::File;
-use std::marker::PhantomData;
 use std::mem::{size_of, transmute};
 use std::os::raw::{c_char, c_void};
 use std::os::unix::io::FromRawFd;
@@ -24,7 +23,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
 
 use libc::close;
 
-use data_model::{VolatileMemory, VolatileSlice};
+use data_model::VolatileSlice;
 use sys_util::{debug, GuestAddress, GuestMemory};
 
 use crate::generated::p_defines::{
@@ -222,7 +221,6 @@ impl From<RendererFlags> for i32 {
 
 /// The global renderer handle used to query capability sets, and create resources and contexts.
 pub struct Renderer {
-    no_sync_send: PhantomData<*mut ()>,
     fence_state: Rc<RefCell<FenceState>>,
 }
 
@@ -266,10 +264,7 @@ impl Renderer {
         };
         ret_to_res(ret)?;
 
-        Ok(Renderer {
-            no_sync_send: PhantomData,
-            fence_state,
-        })
+        Ok(Renderer { fence_state })
     }
 
     /// Gets the version and size for the given capability set ID.
@@ -309,10 +304,7 @@ impl Renderer {
             )
         };
         ret_to_res(ret)?;
-        Ok(Context {
-            id,
-            no_sync_send: PhantomData,
-        })
+        Ok(Context { id })
     }
 
     /// Creates a resource with the given arguments.
@@ -328,7 +320,6 @@ impl Renderer {
             id: args.handle,
             backing_iovecs: Vec::new(),
             backing_mem: None,
-            no_sync_send: PhantomData,
         })
     }
 
@@ -420,7 +411,7 @@ impl Renderer {
         {
             if vecs
                 .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 Err(Error::InvalidIovec);
             }
@@ -428,9 +419,9 @@ impl Renderer {
             let mut iovecs = Vec::new();
             for &(addr, len) in vecs {
                 // Unwrap will not panic because we already checked the slices.
-                let slice = mem.get_slice(addr.offset(), len as u64).unwrap();
+                let slice = mem.get_slice_at_addr(addr, len).unwrap();
                 iovecs.push(VirglVec {
-                    base: slice.as_ptr() as *mut c_void,
+                    base: slice.as_mut_ptr() as *mut c_void,
                     len,
                 });
             }
@@ -453,7 +444,6 @@ impl Renderer {
                 id: resource_id,
                 backing_iovecs: iovecs,
                 backing_mem: None,
-                no_sync_send: PhantomData,
             })
         }
         #[cfg(not(feature = "virtio-gpu-next"))]
@@ -486,7 +476,6 @@ impl Renderer {
 /// A context in which resources can be attached/detached and commands can be submitted.
 pub struct Context {
     id: u32,
-    no_sync_send: PhantomData<*mut ()>,
 }
 
 impl Context {
@@ -543,7 +532,6 @@ pub struct Resource {
     id: u32,
     backing_iovecs: Vec<VirglVec>,
     backing_mem: Option<GuestMemory>,
-    no_sync_send: PhantomData<*mut ()>,
 }
 
 impl Resource {
@@ -603,7 +591,7 @@ impl Resource {
     ) -> Result<()> {
         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 Err(Error::InvalidIovec);
         }
@@ -611,9 +599,9 @@ impl Resource {
         self.backing_mem = Some(mem.clone());
         for &(addr, len) in iovecs {
             // Unwrap will not panic because we already checked the slices.
-            let slice = mem.get_slice(addr.offset(), len as u64).unwrap();
+            let slice = mem.get_slice_at_addr(addr, len).unwrap();
             self.backing_iovecs.push(VirglVec {
-                base: slice.as_ptr() as *mut c_void,
+                base: slice.as_mut_ptr() as *mut c_void,
                 len,
             });
         }
diff --git a/hypervisor/Cargo.toml b/hypervisor/Cargo.toml
index 8f07d5d..db3a095 100644
--- a/hypervisor/Cargo.toml
+++ b/hypervisor/Cargo.toml
@@ -5,6 +5,8 @@ authors = ["The Chromium OS Authors"]
 edition = "2018"
 
 [dependencies]
+bit_field = { path = "../bit_field" }
+enumn = { path = "../enumn" }
 libc = "*"
 kvm = { path = "../kvm" }
 kvm_sys = { path = "../kvm_sys" }
diff --git a/hypervisor/src/kvm/x86_64.rs b/hypervisor/src/kvm/x86_64.rs
index 43d6c97..06774f4 100644
--- a/hypervisor/src/kvm/x86_64.rs
+++ b/hypervisor/src/kvm/x86_64.rs
@@ -2,12 +2,17 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+use std::convert::TryInto;
+
 use kvm_sys::*;
 use libc::E2BIG;
 use sys_util::{ioctl_with_mut_ptr, Error, Result};
 
 use super::{Kvm, KvmVcpu, KvmVm};
-use crate::{CpuId, CpuIdEntry, HypervisorX86_64, Regs, VcpuX86_64, VmX86_64};
+use crate::{
+    CpuId, CpuIdEntry, HypervisorX86_64, IoapicRedirectionTableEntry, IoapicState, LapicState,
+    PicState, PitChannelState, PitState, Regs, VcpuX86_64, VmX86_64,
+};
 
 type KvmCpuId = kvm::CpuId;
 
@@ -93,11 +98,222 @@ impl VcpuX86_64 for KvmVcpu {
     }
 }
 
+impl From<&kvm_pic_state> for PicState {
+    fn from(item: &kvm_pic_state) -> Self {
+        PicState {
+            last_irr: item.last_irr,
+            irr: item.irr,
+            imr: item.imr,
+            isr: item.isr,
+            priority_add: item.priority_add,
+            irq_base: item.irq_base,
+            read_reg_select: item.read_reg_select != 0,
+            poll: item.poll != 0,
+            special_mask: item.special_mask != 0,
+            init_state: item.init_state.into(),
+            auto_eoi: item.auto_eoi != 0,
+            rotate_on_auto_eoi: item.rotate_on_auto_eoi != 0,
+            special_fully_nested_mode: item.special_fully_nested_mode != 0,
+            use_4_byte_icw: item.init4 != 0,
+            elcr: item.elcr,
+            elcr_mask: item.elcr_mask,
+        }
+    }
+}
+
+impl From<&PicState> for kvm_pic_state {
+    fn from(item: &PicState) -> Self {
+        kvm_pic_state {
+            last_irr: item.last_irr,
+            irr: item.irr,
+            imr: item.imr,
+            isr: item.isr,
+            priority_add: item.priority_add,
+            irq_base: item.irq_base,
+            read_reg_select: item.read_reg_select as u8,
+            poll: item.poll as u8,
+            special_mask: item.special_mask as u8,
+            init_state: item.init_state as u8,
+            auto_eoi: item.auto_eoi as u8,
+            rotate_on_auto_eoi: item.rotate_on_auto_eoi as u8,
+            special_fully_nested_mode: item.special_fully_nested_mode as u8,
+            init4: item.use_4_byte_icw as u8,
+            elcr: item.elcr,
+            elcr_mask: item.elcr_mask,
+        }
+    }
+}
+
+impl From<&kvm_ioapic_state> for IoapicState {
+    fn from(item: &kvm_ioapic_state) -> Self {
+        let mut state = IoapicState {
+            base_address: item.base_address,
+            ioregsel: item.ioregsel,
+            ioapicid: item.id,
+            current_interrupt_level_bitmap: item.irr,
+            redirect_table: [IoapicRedirectionTableEntry::default(); 24],
+        };
+        for (in_state, out_state) in item.redirtbl.iter().zip(state.redirect_table.iter_mut()) {
+            *out_state = in_state.into();
+        }
+        state
+    }
+}
+
+impl From<&IoapicRedirectionTableEntry> for kvm_ioapic_state__bindgen_ty_1 {
+    fn from(item: &IoapicRedirectionTableEntry) -> Self {
+        kvm_ioapic_state__bindgen_ty_1 {
+            // IoapicRedirectionTableEntry layout matches the exact bit layout of a hardware
+            // ioapic redirection table entry, so we can simply do a 64-bit copy
+            bits: item.get(0, 64),
+        }
+    }
+}
+
+impl From<&kvm_ioapic_state__bindgen_ty_1> for IoapicRedirectionTableEntry {
+    fn from(item: &kvm_ioapic_state__bindgen_ty_1) -> Self {
+        let mut entry = IoapicRedirectionTableEntry::default();
+        // Safe because the 64-bit layout of the IoapicRedirectionTableEntry matches the kvm_sys
+        // table entry layout
+        entry.set(0, 64, unsafe { item.bits as u64 });
+        entry
+    }
+}
+
+impl From<&IoapicState> for kvm_ioapic_state {
+    fn from(item: &IoapicState) -> Self {
+        let mut state = kvm_ioapic_state {
+            base_address: item.base_address,
+            ioregsel: item.ioregsel,
+            id: item.ioapicid,
+            irr: item.current_interrupt_level_bitmap,
+            ..Default::default()
+        };
+        for (in_state, out_state) in item.redirect_table.iter().zip(state.redirtbl.iter_mut()) {
+            *out_state = in_state.into();
+        }
+        state
+    }
+}
+
+impl From<&LapicState> for kvm_lapic_state {
+    fn from(item: &LapicState) -> Self {
+        let mut state = kvm_lapic_state::default();
+        // There are 64 lapic registers
+        for (reg, value) in item.regs.iter().enumerate() {
+            // Each lapic register is 16 bytes, but only the first 4 are used
+            let reg_offset = 16 * reg;
+            let sliceu8 = unsafe {
+                // This array is only accessed as parts of a u32 word, so interpret it as a u8 array.
+                // to_le_bytes() produces an array of u8, not i8(c_char).
+                std::mem::transmute::<&mut [i8], &mut [u8]>(
+                    &mut state.regs[reg_offset..reg_offset + 4],
+                )
+            };
+            sliceu8.copy_from_slice(&value.to_le_bytes());
+        }
+        state
+    }
+}
+
+impl From<&kvm_lapic_state> for LapicState {
+    fn from(item: &kvm_lapic_state) -> Self {
+        let mut state = LapicState { regs: [0; 64] };
+        // There are 64 lapic registers
+        for reg in 0..64 {
+            // Each lapic register is 16 bytes, but only the first 4 are used
+            let reg_offset = 16 * reg;
+            let bytes = unsafe {
+                // This array is only accessed as parts of a u32 word, so interpret it as a u8 array.
+                // from_le_bytes() only works on arrays of u8, not i8(c_char).
+                std::mem::transmute::<&[i8], &[u8]>(&item.regs[reg_offset..reg_offset + 4])
+            };
+            state.regs[reg] = u32::from_le_bytes(bytes.try_into().unwrap());
+        }
+        state
+    }
+}
+
+impl From<&PitState> for kvm_pit_state2 {
+    fn from(item: &PitState) -> Self {
+        kvm_pit_state2 {
+            channels: [
+                kvm_pit_channel_state::from(&item.channels[0]),
+                kvm_pit_channel_state::from(&item.channels[1]),
+                kvm_pit_channel_state::from(&item.channels[2]),
+            ],
+            flags: item.flags,
+            ..Default::default()
+        }
+    }
+}
+
+impl From<&kvm_pit_state2> for PitState {
+    fn from(item: &kvm_pit_state2) -> Self {
+        PitState {
+            channels: [
+                PitChannelState::from(&item.channels[0]),
+                PitChannelState::from(&item.channels[1]),
+                PitChannelState::from(&item.channels[2]),
+            ],
+            flags: item.flags,
+        }
+    }
+}
+
+impl From<&PitChannelState> for kvm_pit_channel_state {
+    fn from(item: &PitChannelState) -> Self {
+        kvm_pit_channel_state {
+            count: item.count,
+            latched_count: item.latched_count,
+            count_latched: item.count_latched as u8,
+            status_latched: item.status_latched as u8,
+            status: item.status,
+            read_state: item.read_state as u8,
+            write_state: item.write_state as u8,
+            // kvm's write_latch only stores the low byte of the reload value
+            write_latch: item.reload_value as u8,
+            rw_mode: item.rw_mode as u8,
+            mode: item.mode,
+            bcd: item.bcd as u8,
+            gate: item.gate as u8,
+            count_load_time: item.count_load_time as i64,
+        }
+    }
+}
+
+impl From<&kvm_pit_channel_state> for PitChannelState {
+    fn from(item: &kvm_pit_channel_state) -> Self {
+        PitChannelState {
+            count: item.count,
+            latched_count: item.latched_count,
+            count_latched: item.count_latched.into(),
+            status_latched: item.status_latched != 0,
+            status: item.status,
+            read_state: item.read_state.into(),
+            write_state: item.write_state.into(),
+            // kvm's write_latch only stores the low byte of the reload value
+            reload_value: item.write_latch as u16,
+            rw_mode: item.rw_mode.into(),
+            mode: item.mode,
+            bcd: item.bcd != 0,
+            gate: item.gate != 0,
+            count_load_time: item.count_load_time as u64,
+        }
+    }
+}
+
 #[cfg(test)]
 mod tests {
+    use crate::{
+        DeliveryMode, DeliveryStatus, DestinationMode, IoapicRedirectionTableEntry, IoapicState,
+        LapicState, PicInitState, PicState, PitChannelState, PitRWMode, PitRWState, PitState,
+        TriggerMode,
+    };
+    use kvm_sys::*;
+
     use super::Kvm;
     use crate::HypervisorX86_64;
-    use kvm_sys::*;
 
     #[test]
     fn get_supported_cpuid() {
@@ -121,4 +337,168 @@ mod tests {
             .unwrap();
         assert!(cpuid.cpu_id_entries.len() > 4);
     }
+
+    #[test]
+    fn pic_state() {
+        let state = PicState {
+            last_irr: 0b00000001,
+            irr: 0b00000010,
+            imr: 0b00000100,
+            isr: 0b00001000,
+            priority_add: 0b00010000,
+            irq_base: 0b00100000,
+            read_reg_select: false,
+            poll: true,
+            special_mask: true,
+            init_state: PicInitState::Icw3,
+            auto_eoi: true,
+            rotate_on_auto_eoi: false,
+            special_fully_nested_mode: true,
+            use_4_byte_icw: true,
+            elcr: 0b01000000,
+            elcr_mask: 0b10000000,
+        };
+
+        let kvm_state = kvm_pic_state::from(&state);
+
+        assert_eq!(kvm_state.last_irr, 0b00000001);
+        assert_eq!(kvm_state.irr, 0b00000010);
+        assert_eq!(kvm_state.imr, 0b00000100);
+        assert_eq!(kvm_state.isr, 0b00001000);
+        assert_eq!(kvm_state.priority_add, 0b00010000);
+        assert_eq!(kvm_state.irq_base, 0b00100000);
+        assert_eq!(kvm_state.read_reg_select, 0);
+        assert_eq!(kvm_state.poll, 1);
+        assert_eq!(kvm_state.special_mask, 1);
+        assert_eq!(kvm_state.init_state, 0b10);
+        assert_eq!(kvm_state.auto_eoi, 1);
+        assert_eq!(kvm_state.rotate_on_auto_eoi, 0);
+        assert_eq!(kvm_state.special_fully_nested_mode, 1);
+        assert_eq!(kvm_state.auto_eoi, 1);
+        assert_eq!(kvm_state.elcr, 0b01000000);
+        assert_eq!(kvm_state.elcr_mask, 0b10000000);
+
+        let orig_state = PicState::from(&kvm_state);
+        assert_eq!(state, orig_state);
+    }
+
+    #[test]
+    fn ioapic_state() {
+        let mut entry = IoapicRedirectionTableEntry::default();
+        // default entry should be 0
+        assert_eq!(entry.get(0, 64), 0);
+
+        // set some values on our entry
+        entry.set_vector(0b11111111);
+        entry.set_delivery_mode(DeliveryMode::SMI);
+        entry.set_dest_mode(DestinationMode::Physical);
+        entry.set_delivery_status(DeliveryStatus::Pending);
+        entry.set_polarity(1);
+        entry.set_remote_irr(true);
+        entry.set_trigger_mode(TriggerMode::Level);
+        entry.set_interrupt_mask(true);
+        entry.set_dest_id(0b10101010);
+
+        // Bit repr as:  destid-reserved--------------------------------flags----vector--
+        let bit_repr = 0b1010101000000000000000000000000000000000000000011111001011111111;
+        // where flags is [interrupt_mask(1), trigger_mode(Level=1), remote_irr(1), polarity(1),
+        //   delivery_status(Pending=1), dest_mode(Physical=0), delivery_mode(SMI=010)]
+
+        assert_eq!(entry.get(0, 64), bit_repr);
+
+        let state = IoapicState {
+            base_address: 1,
+            ioregsel: 2,
+            ioapicid: 4,
+            current_interrupt_level_bitmap: 8,
+            redirect_table: [entry; 24],
+        };
+
+        let kvm_state = kvm_ioapic_state::from(&state);
+        assert_eq!(kvm_state.base_address, 1);
+        assert_eq!(kvm_state.ioregsel, 2);
+        assert_eq!(kvm_state.id, 4);
+        assert_eq!(kvm_state.irr, 8);
+        assert_eq!(kvm_state.pad, 0);
+        // check our entries
+        for i in 0..24 {
+            assert_eq!(unsafe { kvm_state.redirtbl[i].bits }, bit_repr);
+        }
+
+        // compare with a conversion back
+        assert_eq!(state, IoapicState::from(&kvm_state));
+    }
+
+    #[test]
+    fn lapic_state() {
+        let mut state = LapicState { regs: [0; 64] };
+        // Apic id register, 4 bytes each with a different bit set
+        state.regs[2] = 1 | 2 << 8 | 4 << 16 | 8 << 24;
+
+        let kvm_state = kvm_lapic_state::from(&state);
+
+        // check little endian bytes in kvm_state
+        for i in 0..4 {
+            assert_eq!(
+                unsafe { std::mem::transmute::<i8, u8>(kvm_state.regs[32 + i]) } as u8,
+                2u8.pow(i as u32)
+            );
+        }
+
+        // Test converting back to a LapicState
+        assert_eq!(state, LapicState::from(&kvm_state));
+    }
+
+    #[test]
+    fn pit_state() {
+        let channel = PitChannelState {
+            count: 256,
+            latched_count: 512,
+            count_latched: PitRWState::LSB,
+            status_latched: false,
+            status: 7,
+            read_state: PitRWState::MSB,
+            write_state: PitRWState::Word1,
+            reload_value: 8,
+            rw_mode: PitRWMode::Both,
+            mode: 5,
+            bcd: false,
+            gate: true,
+            count_load_time: 1024,
+        };
+
+        let kvm_channel = kvm_pit_channel_state::from(&channel);
+
+        // compare the various field translations
+        assert_eq!(kvm_channel.count, 256);
+        assert_eq!(kvm_channel.latched_count, 512);
+        assert_eq!(kvm_channel.count_latched, 1);
+        assert_eq!(kvm_channel.status_latched, 0);
+        assert_eq!(kvm_channel.status, 7);
+        assert_eq!(kvm_channel.read_state, 2);
+        assert_eq!(kvm_channel.write_state, 4);
+        assert_eq!(kvm_channel.write_latch, 8);
+        assert_eq!(kvm_channel.rw_mode, 3);
+        assert_eq!(kvm_channel.mode, 5);
+        assert_eq!(kvm_channel.bcd, 0);
+        assert_eq!(kvm_channel.gate, 1);
+        assert_eq!(kvm_channel.count_load_time, 1024);
+
+        // convert back and compare
+        assert_eq!(channel, PitChannelState::from(&kvm_channel));
+
+        // convert the full pitstate
+        let state = PitState {
+            channels: [channel, channel, channel],
+            flags: 255,
+        };
+        let kvm_state = kvm_pit_state2::from(&state);
+
+        assert_eq!(kvm_state.flags, 255);
+
+        // compare a channel
+        assert_eq!(channel, PitChannelState::from(&kvm_state.channels[0]));
+        // convert back and compare
+        assert_eq!(state, PitState::from(&kvm_state));
+    }
 }
diff --git a/hypervisor/src/x86_64.rs b/hypervisor/src/x86_64.rs
index 87f0777..c311924 100644
--- a/hypervisor/src/x86_64.rs
+++ b/hypervisor/src/x86_64.rs
@@ -2,7 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-use sys_util::Result;
+use bit_field::*;
+use sys_util::{error, Result};
 
 use crate::{Hypervisor, Vcpu, Vm};
 
@@ -34,6 +35,8 @@ pub trait VcpuX86_64: Vcpu {
 /// about the hypervisor or vm. Information is returned in the eax, ebx, ecx and edx registers
 /// by the cpu for a given function and index/subfunction (passed into the cpu via the eax and ecx
 /// register respectively).
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
 pub struct CpuIdEntry {
     pub function: u32,
     pub index: u32,
@@ -50,3 +53,292 @@ pub struct CpuId {
 
 /// The state of a vcpu's general-purpose registers.
 pub struct Regs {}
+
+#[bitfield]
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum DestinationMode {
+    Physical = 0,
+    Logical = 1,
+}
+
+#[bitfield]
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum TriggerMode {
+    Edge = 0,
+    Level = 1,
+}
+
+#[bitfield]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum DeliveryMode {
+    Fixed = 0b000,
+    Lowest = 0b001,
+    SMI = 0b010,        // System management interrupt
+    RemoteRead = 0b011, // This is no longer supported by intel.
+    NMI = 0b100,        // Non maskable interrupt
+    Init = 0b101,
+    Startup = 0b110,
+    External = 0b111,
+}
+
+#[bitfield]
+#[derive(Clone, Copy, PartialEq, Eq)]
+pub struct MsiAddressMessage {
+    pub reserved: BitField2,
+    #[bits = 1]
+    pub destination_mode: DestinationMode,
+    pub redirection_hint: BitField1,
+    pub reserved_2: BitField8,
+    pub destination_id: BitField8,
+    // According to Intel's implementation of MSI, these bits must always be 0xfee.
+    pub always_0xfee: BitField12,
+}
+
+#[bitfield]
+#[derive(Clone, Copy, PartialEq, Eq)]
+pub struct MsiDataMessage {
+    pub vector: BitField8,
+    #[bits = 3]
+    pub delivery_mode: DeliveryMode,
+    pub reserved: BitField3,
+    pub level: BitField1,
+    #[bits = 1]
+    pub trigger: TriggerMode,
+    pub reserved2: BitField16,
+}
+
+#[bitfield]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum DeliveryStatus {
+    Idle = 0,
+    Pending = 1,
+}
+
+/// Represents a IOAPIC redirection table entry.
+#[bitfield]
+#[derive(Clone, Copy, Default, PartialEq, Eq)]
+pub struct IoapicRedirectionTableEntry {
+    vector: BitField8,
+    #[bits = 3]
+    delivery_mode: DeliveryMode,
+    #[bits = 1]
+    dest_mode: DestinationMode,
+    #[bits = 1]
+    delivery_status: DeliveryStatus,
+    polarity: BitField1,
+    remote_irr: bool,
+    #[bits = 1]
+    trigger_mode: TriggerMode,
+    interrupt_mask: bool, // true iff interrupts are masked.
+    reserved: BitField39,
+    dest_id: BitField8,
+}
+
+/// Number of pins on the IOAPIC.
+pub const NUM_IOAPIC_PINS: usize = 24;
+
+/// Represents the state of the IOAPIC.
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
+pub struct IoapicState {
+    /// base_address is the memory base address for this IOAPIC. It cannot be changed.
+    pub base_address: u64,
+    /// ioregsel register. Used for selecting which entry of the redirect table to read/write.
+    pub ioregsel: u32,
+    /// ioapicid register. Bits 24 - 27 contain the APIC ID for this device.
+    pub ioapicid: u32,
+    /// current_interrupt_level_bitmap represents a bitmap of the state of all of the irq lines
+    pub current_interrupt_level_bitmap: u32,
+    /// redirect_table contains the irq settings for each irq line
+    pub redirect_table: [IoapicRedirectionTableEntry; 24],
+}
+
+#[repr(C)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum PicSelect {
+    Primary = 0,
+    Secondary = 1,
+}
+
+#[repr(C)]
+#[derive(enumn::N, Debug, Clone, Copy, PartialEq, Eq)]
+pub enum PicInitState {
+    Icw1 = 0,
+    Icw2 = 1,
+    Icw3 = 2,
+    Icw4 = 3,
+}
+
+/// Convenience implementation for converting from a u8
+impl From<u8> for PicInitState {
+    fn from(item: u8) -> Self {
+        PicInitState::n(item).unwrap_or_else(|| {
+            error!("Invalid PicInitState {}, setting to 0", item);
+            PicInitState::Icw1
+        })
+    }
+}
+
+/// Represents the state of the PIC.
+#[repr(C)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct PicState {
+    /// Edge detection.
+    pub last_irr: u8,
+    /// Interrupt Request Register.
+    pub irr: u8,
+    /// Interrupt Mask Register.
+    pub imr: u8,
+    /// Interrupt Service Register.
+    pub isr: u8,
+    /// Highest priority, for priority rotation.
+    pub priority_add: u8,
+    pub irq_base: u8,
+    pub read_reg_select: bool,
+    pub poll: bool,
+    pub special_mask: bool,
+    pub init_state: PicInitState,
+    pub auto_eoi: bool,
+    pub rotate_on_auto_eoi: bool,
+    pub special_fully_nested_mode: bool,
+    /// PIC takes either 3 or 4 bytes of initialization command word during
+    /// initialization. use_4_byte_icw is true if 4 bytes of ICW are needed.
+    pub use_4_byte_icw: bool,
+    /// "Edge/Level Control Registers", for edge trigger selection.
+    /// When a particular bit is set, the corresponding IRQ is in level-triggered mode. Otherwise it
+    /// is in edge-triggered mode.
+    pub elcr: u8,
+    pub elcr_mask: u8,
+}
+
+/// The LapicState represents the state of an x86 CPU's Local APIC.
+/// The Local APIC consists of 64 128-bit registers, but only the first 32-bits of each register
+/// can be used, so this structure only stores the first 32-bits of each register.
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct LapicState {
+    pub regs: [LapicRegister; 64],
+}
+
+pub type LapicRegister = u32;
+
+// rust arrays longer than 32 need custom implementations of Debug
+impl std::fmt::Debug for LapicState {
+    fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
+        self.regs[..].fmt(formatter)
+    }
+}
+
+// rust arrays longer than 32 need custom implementations of PartialEq
+impl PartialEq for LapicState {
+    fn eq(&self, other: &LapicState) -> bool {
+        self.regs[..] == other.regs[..]
+    }
+}
+
+// Lapic equality is reflexive, so we impl Eq
+impl Eq for LapicState {}
+
+/// The PitState represents the state of the PIT (aka the Programmable Interval Timer).
+/// The state is simply the state of it's three channels.
+#[repr(C)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct PitState {
+    pub channels: [PitChannelState; 3],
+    /// Hypervisor-specific flags for setting the pit state.
+    pub flags: u32,
+}
+
+/// The PitRWMode enum represents the access mode of a PIT channel.
+/// Reads and writes to the Pit happen over Port-mapped I/O, which happens one byte at a time,
+/// but the count values and latch values are two bytes. So the access mode controls which of the
+/// two bytes will be read when.
+#[repr(C)]
+#[derive(enumn::N, Clone, Copy, Debug, PartialEq, Eq)]
+pub enum PitRWMode {
+    /// None mode means that no access mode has been set.
+    None = 0,
+    /// Least mode means all reads/writes will read/write the least significant byte.
+    Least = 1,
+    /// Most mode means all reads/writes will read/write the most significant byte.
+    Most = 2,
+    /// Both mode means first the least significant byte will be read/written, then the
+    /// next read/write will read/write the most significant byte.
+    Both = 3,
+}
+
+/// Convenience implementation for converting from a u8
+impl From<u8> for PitRWMode {
+    fn from(item: u8) -> Self {
+        PitRWMode::n(item).unwrap_or_else(|| {
+            error!("Invalid PitRWMode value {}, setting to 0", item);
+            PitRWMode::None
+        })
+    }
+}
+
+/// The PitRWState enum represents the state of reading to or writing from a channel.
+/// This is related to the PitRWMode, it mainly gives more detail about the state of the channel
+/// with respect to PitRWMode::Both.
+#[repr(C)]
+#[derive(enumn::N, Clone, Copy, Debug, PartialEq, Eq)]
+pub enum PitRWState {
+    /// None mode means that no access mode has been set.
+    None = 0,
+    /// LSB means that the channel is in PitRWMode::Least access mode.
+    LSB = 1,
+    /// MSB means that the channel is in PitRWMode::Most access mode.
+    MSB = 2,
+    /// Word0 means that the channel is in PitRWMode::Both mode, and the least sginificant byte
+    /// has not been read/written yet.
+    Word0 = 3,
+    /// Word1 means that the channel is in PitRWMode::Both mode and the least significant byte
+    /// has already been read/written, and the next byte to be read/written will be the most
+    /// significant byte.
+    Word1 = 4,
+}
+
+/// Convenience implementation for converting from a u8
+impl From<u8> for PitRWState {
+    fn from(item: u8) -> Self {
+        PitRWState::n(item).unwrap_or_else(|| {
+            error!("Invalid PitRWState value {}, setting to 0", item);
+            PitRWState::None
+        })
+    }
+}
+
+/// The PitChannelState represents the state of one of the PIT's three counters.
+#[repr(C)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct PitChannelState {
+    /// The starting value for the counter.
+    pub count: u32,
+    /// Stores the channel count from the last time the count was latched.
+    pub latched_count: u16,
+    /// Indicates the PitRWState state of reading the latch value.
+    pub count_latched: PitRWState,
+    /// Indicates whether ReadBack status has been latched.
+    pub status_latched: bool,
+    /// Stores the channel status from the last time the status was latched. The status contains
+    /// information about the access mode of this channel, but changing those bits in the status
+    /// will not change the behavior of the pit.
+    pub status: u8,
+    /// Indicates the PitRWState state of reading the counter.
+    pub read_state: PitRWState,
+    /// Indicates the PitRWState state of writing the counter.
+    pub write_state: PitRWState,
+    /// Stores the value with which the counter was initialized. Counters are 16-
+    /// bit values with an effective range of 1-65536 (65536 represented by 0).
+    pub reload_value: u16,
+    /// The command access mode of this channel.
+    pub rw_mode: PitRWMode,
+    /// The operation mode of this channel.
+    pub mode: u8,
+    /// Whether or not we are in bcd mode. Not supported by KVM or crosvm's PIT implementation.
+    pub bcd: bool,
+    /// Value of the gate input pin. This only applies to channel 2.
+    pub gate: bool,
+    /// Guest boot nanosecond timestamp of when the count value was loaded.
+    pub count_load_time: u64,
+}
diff --git a/msg_socket/src/lib.rs b/msg_socket/src/lib.rs
index 7f5d1a6..540d49d 100644
--- a/msg_socket/src/lib.rs
+++ b/msg_socket/src/lib.rs
@@ -4,7 +4,7 @@
 
 mod msg_on_socket;
 
-use std::io::Result;
+use std::io::{IoSlice, Result};
 use std::marker::PhantomData;
 use std::os::unix::io::{AsRawFd, RawFd};
 use std::pin::Pin;
@@ -138,7 +138,8 @@ pub trait MsgSender: AsRef<UnixSeqpacket> {
             handle_eintr!(sock.send(&msg_buffer))
                 .map_err(|e| MsgError::Send(SysError::new(e.raw_os_error().unwrap_or(0))))?;
         } else {
-            sock.send_with_fds(&msg_buffer[..], &fd_buffer[0..fd_size])
+            let ioslice = IoSlice::new(&msg_buffer[..]);
+            sock.send_with_fds(&[ioslice], &fd_buffer[0..fd_size])
                 .map_err(MsgError::Send)?;
         }
         Ok(())
diff --git a/resources/src/address_allocator.rs b/resources/src/address_allocator.rs
index 11978ee..88f652a 100644
--- a/resources/src/address_allocator.rs
+++ b/resources/src/address_allocator.rs
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 use std::cmp;
-use std::collections::HashMap;
+use std::collections::{BTreeSet, HashMap};
 
 use crate::{Alloc, Error, Result};
 
@@ -26,11 +26,9 @@ use crate::{Alloc, Error, Result};
 /// ```
 #[derive(Debug, Eq, PartialEq)]
 pub struct AddressAllocator {
-    pool_base: u64,
-    pool_end: u64,
     alignment: u64,
-    next_addr: u64,
     allocs: HashMap<Alloc, (u64, u64, String)>,
+    regions: BTreeSet<(u64, u64)>,
 }
 
 impl AddressAllocator {
@@ -55,12 +53,12 @@ impl AddressAllocator {
         if !alignment.is_power_of_two() || alignment == 0 {
             return Err(Error::BadAlignment);
         }
+        let mut regions = BTreeSet::new();
+        regions.insert((pool_base, pool_end));
         Ok(AddressAllocator {
-            pool_base,
-            pool_end,
             alignment,
-            next_addr: pool_base,
             allocs: HashMap::new(),
+            regions,
         })
     }
 
@@ -85,36 +83,138 @@ impl AddressAllocator {
         if !alignment.is_power_of_two() {
             return Err(Error::BadAlignment);
         }
-        let align_adjust = if self.next_addr % alignment != 0 {
-            alignment - (self.next_addr % alignment)
-        } else {
-            0
-        };
-        let addr = self
-            .next_addr
-            .checked_add(align_adjust)
-            .ok_or(Error::OutOfSpace)?;
-        let end_addr = addr.checked_add(size - 1).ok_or(Error::OutOfSpace)?;
-        if end_addr > self.pool_end {
-            return Err(Error::OutOfSpace);
-        }
 
-        // TODO(dgreid): Use a smarter allocation strategy. The current strategy is just
-        // bumping this pointer, meaning it will eventually exhaust available addresses.
-        self.next_addr = end_addr.saturating_add(1);
+        // finds first region matching alignment and size.
+        match self
+            .regions
+            .iter()
+            .find(|range| {
+                match range.0 % alignment {
+                    0 => range.0.checked_add(size - 1),
+                    r => range.0.checked_add(size - 1 + alignment - r),
+                }
+                .map_or(false, |end| end <= range.1)
+            })
+            .cloned()
+        {
+            Some(slot) => {
+                self.regions.remove(&slot);
+                let start = match slot.0 % alignment {
+                    0 => slot.0,
+                    r => slot.0 + alignment - r,
+                };
+                let end = start + size - 1;
+                if slot.0 < start {
+                    self.regions.insert((slot.0, start - 1));
+                }
+                if slot.1 > end {
+                    self.regions.insert((end + 1, slot.1));
+                }
+                self.allocs.insert(alloc, (start, size, tag));
 
-        self.allocs.insert(alloc, (addr, size, tag));
-        Ok(addr)
+                Ok(start)
+            }
+            None => Err(Error::OutOfSpace),
+        }
     }
 
     pub fn allocate(&mut self, size: u64, alloc: Alloc, tag: String) -> Result<u64> {
         self.allocate_with_align(size, alloc, tag, self.alignment)
     }
 
+    /// Allocates a range of addresses from the managed region with an optional tag
+    /// and required location. Allocation alignment is not enforced.
+    /// Returns OutOfSpace if requested range is not available (e.g. already allocated
+    /// with a different alloc tag).
+    pub fn allocate_at(&mut self, start: u64, size: u64, alloc: Alloc, tag: String) -> Result<()> {
+        if self.allocs.contains_key(&alloc) {
+            return Err(Error::ExistingAlloc(alloc));
+        }
+        if size == 0 {
+            return Err(Error::AllocSizeZero);
+        }
+
+        let end = start.checked_add(size - 1).ok_or(Error::OutOfSpace)?;
+        match self
+            .regions
+            .iter()
+            .find(|range| range.0 <= start && range.1 >= end)
+            .cloned()
+        {
+            Some(slot) => {
+                self.regions.remove(&slot);
+                if slot.0 < start {
+                    self.regions.insert((slot.0, start - 1));
+                }
+                if slot.1 > end {
+                    self.regions.insert((end + 1, slot.1));
+                }
+                self.allocs.insert(alloc, (start, size, tag));
+
+                Ok(())
+            }
+            None => Err(Error::OutOfSpace),
+        }
+    }
+
+    /// Releases exising allocation back to free pool.
+    pub fn release(&mut self, alloc: Alloc) -> Result<()> {
+        self.allocs
+            .remove(&alloc)
+            .map_or_else(|| Err(Error::BadAlloc(alloc)), |v| self.insert_at(v.0, v.1))
+    }
+
     /// Returns allocation associated with `alloc`, or None if no such allocation exists.
     pub fn get(&self, alloc: &Alloc) -> Option<&(u64, u64, String)> {
         self.allocs.get(alloc)
     }
+
+    /// Insert range of addresses into the pool, coalescing neighboring regions.
+    fn insert_at(&mut self, start: u64, size: u64) -> Result<()> {
+        if size == 0 {
+            return Err(Error::AllocSizeZero);
+        }
+
+        let mut slot = (start, start.checked_add(size - 1).ok_or(Error::OutOfSpace)?);
+        let mut left = None;
+        let mut right = None;
+        // simple coalescing with linear search over free regions.
+        //
+        // Calculating the distance between start and end of two regions we can
+        // detect if they are disjoint (>1), adjacent (=1) or possibly
+        // overlapping (<1). Saturating arithmetic is used to avoid overflow.
+        // Overlapping regions are detected if both oposite ends are overlapping.
+        // Algorithm assumes all existing regions are disjoined and represented
+        // as pair of inclusive location point (start, end), where end >= start.
+        for range in self.regions.iter() {
+            match (
+                slot.0.saturating_sub(range.1),
+                range.0.saturating_sub(slot.1),
+            ) {
+                (1, 0) => {
+                    left = Some(*range);
+                }
+                (0, 1) => {
+                    right = Some(*range);
+                }
+                (0, 0) => {
+                    return Err(Error::RegionOverlap { base: start, size });
+                }
+                (_, _) => (),
+            }
+        }
+        if let Some(left) = left {
+            self.regions.remove(&left);
+            slot.0 = left.0;
+        }
+        if let Some(right) = right {
+            self.regions.remove(&right);
+            slot.1 = right.1;
+        }
+        self.regions.insert(slot);
+
+        Ok(())
+    }
 }
 
 #[cfg(test)]
@@ -172,6 +272,44 @@ mod tests {
     }
 
     #[test]
+    fn allocate_with_special_alignment() {
+        let mut pool = AddressAllocator::new(0x1000, 0x1000, Some(0x100)).unwrap();
+        assert_eq!(
+            pool.allocate(0x10, Alloc::Anon(0), String::from("bar0")),
+            Ok(0x1000)
+        );
+        assert_eq!(
+            pool.allocate_at(0x1200, 0x100, Alloc::Anon(1), String::from("bar1")),
+            Ok(())
+        );
+        assert_eq!(
+            pool.allocate_with_align(0x800, Alloc::Anon(2), String::from("bar2"), 0x800),
+            Ok(0x1800)
+        );
+    }
+
+    #[test]
+    fn allocate_and_split_allocate_at() {
+        let mut pool = AddressAllocator::new(0x1000, 0x1000, Some(0x100)).unwrap();
+        assert_eq!(
+            pool.allocate_at(0x1200, 0x800, Alloc::Anon(0), String::from("bar0")),
+            Ok(())
+        );
+        assert_eq!(
+            pool.allocate(0x800, Alloc::Anon(1), String::from("bar1")),
+            Err(Error::OutOfSpace)
+        );
+        assert_eq!(
+            pool.allocate(0x600, Alloc::Anon(2), String::from("bar2")),
+            Ok(0x1a00)
+        );
+        assert_eq!(
+            pool.allocate(0x200, Alloc::Anon(3), String::from("bar3")),
+            Ok(0x1000)
+        );
+    }
+
+    #[test]
     fn allocate_alignment() {
         let mut pool = AddressAllocator::new(0x1000, 0x10000, Some(0x100)).unwrap();
         assert_eq!(
@@ -243,4 +381,45 @@ mod tests {
             .allocate_with_align(0x110, Alloc::Anon(0), String::from("bar0"), 200)
             .is_err());
     }
+
+    #[test]
+    fn allocate_with_release() {
+        let mut pool = AddressAllocator::new(0x1000, 0x1000, None).unwrap();
+        assert_eq!(
+            pool.allocate_with_align(0x100, Alloc::Anon(0), String::from("bar0"), 0x100),
+            Ok(0x1000)
+        );
+        assert!(pool.release(Alloc::Anon(0)).is_ok());
+        assert_eq!(
+            pool.allocate_with_align(0x1000, Alloc::Anon(0), String::from("bar0"), 0x100),
+            Ok(0x1000)
+        );
+    }
+
+    #[test]
+    fn coalescing_and_overlap() {
+        let mut pool = AddressAllocator::new(0x1000, 0x1000, None).unwrap();
+        assert!(pool.insert_at(0x3000, 0x1000).is_ok());
+        assert!(pool.insert_at(0x1fff, 0x20).is_err());
+        assert!(pool.insert_at(0x2ff1, 0x10).is_err());
+        assert!(pool.insert_at(0x1800, 0x1000).is_err());
+        assert!(pool.insert_at(0x2000, 0x1000).is_ok());
+        assert_eq!(
+            pool.allocate(0x3000, Alloc::Anon(0), String::from("bar0")),
+            Ok(0x1000)
+        );
+    }
+
+    #[test]
+    fn coalescing_single_addresses() {
+        let mut pool = AddressAllocator::new(0x1000, 0x1000, None).unwrap();
+        assert!(pool.insert_at(0x2001, 1).is_ok());
+        assert!(pool.insert_at(0x2003, 1).is_ok());
+        assert!(pool.insert_at(0x2000, 1).is_ok());
+        assert!(pool.insert_at(0x2002, 1).is_ok());
+        assert_eq!(
+            pool.allocate(0x1004, Alloc::Anon(0), String::from("bar0")),
+            Ok(0x1000)
+        );
+    }
 }
diff --git a/resources/src/lib.rs b/resources/src/lib.rs
index cf36cc1..6195e91 100644
--- a/resources/src/lib.rs
+++ b/resources/src/lib.rs
@@ -46,6 +46,8 @@ pub enum Error {
     OutOfSpace,
     PoolOverflow { base: u64, size: u64 },
     PoolSizeZero,
+    RegionOverlap { base: u64, size: u64 },
+    BadAlloc(Alloc),
 }
 
 pub type Result<T> = std::result::Result<T, Error>;
@@ -64,6 +66,10 @@ impl Display for Error {
             OutOfSpace => write!(f, "Out of space"),
             PoolOverflow { base, size } => write!(f, "base={} + size={} overflows", base, size),
             PoolSizeZero => write!(f, "Pool cannot have size of 0"),
+            RegionOverlap { base, size } => {
+                write!(f, "Overlapping region base={} size={}", base, size)
+            }
+            BadAlloc(tag) => write!(f, "Alloc does not exists: {:?}", tag),
         }
     }
 }
diff --git a/seccomp/x86_64/video_device.policy b/seccomp/x86_64/video_device.policy
new file mode 100644
index 0000000..fdc5935
--- /dev/null
+++ b/seccomp/x86_64/video_device.policy
@@ -0,0 +1,24 @@
+# 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.
+
+@include /usr/share/policy/crosvm/common_device.policy
+
+# Syscalls specific to video devices.
+clock_getres: 1
+connect: 1
+fcntl: arg1 == F_GETFL || arg1 == F_SETFL || arg1 == F_DUPFD_CLOEXEC || arg1 == F_GETFD || arg1 == F_SETFD
+getdents: 1
+getegid: 1
+geteuid: 1
+getgid: 1
+getresgid: 1
+getresuid: 1
+getsockname: 1
+getuid: 1
+# ioctl: arg1 == DRM_IOCTL_*
+ioctl: arg1 & 0x6400
+openat: 1
+setpriority: 1
+socket: arg0 == AF_UNIX
+stat: 1
diff --git a/src/crosvm.rs b/src/crosvm.rs
index 49a08c0..1ea7e0d 100644
--- a/src/crosvm.rs
+++ b/src/crosvm.rs
@@ -202,6 +202,9 @@ pub struct Config {
     pub virtio_input_evdevs: Vec<PathBuf>,
     pub split_irqchip: bool,
     pub vfio: Vec<PathBuf>,
+    pub video_dec: bool,
+    pub video_enc: bool,
+    pub acpi_tables: Vec<PathBuf>,
 }
 
 impl Default for Config {
@@ -250,6 +253,9 @@ impl Default for Config {
             virtio_input_evdevs: Vec::new(),
             split_irqchip: false,
             vfio: Vec::new(),
+            video_dec: false,
+            video_enc: false,
+            acpi_tables: Vec::new(),
         }
     }
 }
diff --git a/src/linux.rs b/src/linux.rs
index 574493d..3e2045f 100644
--- a/src/linux.rs
+++ b/src/linux.rs
@@ -26,6 +26,8 @@ use std::time::Duration;
 
 use libc::{self, c_int, gid_t, uid_t};
 
+use acpi_tables::sdt::SDT;
+
 #[cfg(feature = "gpu")]
 use devices::virtio::EventDevice;
 use devices::virtio::{self, Console, VirtioDevice};
@@ -109,6 +111,7 @@ pub enum Error {
     LoadKernel(Box<dyn StdError>),
     MemoryTooLarge,
     NetDeviceNew(virtio::NetError),
+    OpenAcpiTable(PathBuf, io::Error),
     OpenAndroidFstab(PathBuf, io::Error),
     OpenBios(PathBuf, io::Error),
     OpenInitrd(PathBuf, io::Error),
@@ -196,6 +199,7 @@ impl Display for Error {
             LoadKernel(e) => write!(f, "failed to load kernel: {}", e),
             MemoryTooLarge => write!(f, "requested memory size too large"),
             NetDeviceNew(e) => write!(f, "failed to set up virtio networking: {}", e),
+            OpenAcpiTable(p, e) => write!(f, "failed to open ACPI file {}: {}", p.display(), e),
             OpenAndroidFstab(p, e) => write!(
                 f,
                 "failed to open android fstab file {}: {}",
@@ -792,6 +796,70 @@ fn create_wayland_device(
     })
 }
 
+#[cfg(any(feature = "video-decoder", feature = "video-encoder"))]
+fn create_video_device(
+    cfg: &Config,
+    typ: devices::virtio::VideoDeviceType,
+    resource_bridge: virtio::resource_bridge::ResourceRequestSocket,
+) -> DeviceResult {
+    let jail = match simple_jail(&cfg, "video_device")? {
+        Some(mut jail) => {
+            match typ {
+                devices::virtio::VideoDeviceType::Decoder => {
+                    add_crosvm_user_to_jail(&mut jail, "video-decoder")?
+                }
+                devices::virtio::VideoDeviceType::Encoder => {
+                    add_crosvm_user_to_jail(&mut jail, "video-encoder")?
+                }
+            };
+
+            // Create a tmpfs in the device's root directory so that we can bind mount files.
+            jail.mount_with_data(
+                Path::new("none"),
+                Path::new("/"),
+                "tmpfs",
+                (libc::MS_NOSUID | libc::MS_NODEV | libc::MS_NOEXEC) as usize,
+                "size=67108864",
+            )?;
+
+            // Render node for libvda.
+            let dev_dri_path = Path::new("/dev/dri/renderD128");
+            jail.mount_bind(dev_dri_path, dev_dri_path, false)?;
+
+            // Device nodes required by libchrome which establishes Mojo connection in libvda.
+            let dev_urandom_path = Path::new("/dev/urandom");
+            jail.mount_bind(dev_urandom_path, dev_urandom_path, false)?;
+            let system_bus_socket_path = Path::new("/run/dbus/system_bus_socket");
+            jail.mount_bind(system_bus_socket_path, system_bus_socket_path, true)?;
+
+            Some(jail)
+        }
+        None => None,
+    };
+
+    Ok(VirtioDeviceStub {
+        dev: Box::new(devices::virtio::VideoDevice::new(
+            typ,
+            Some(resource_bridge),
+        )),
+        jail,
+    })
+}
+
+#[cfg(any(feature = "video-decoder", feature = "video-encoder"))]
+fn register_video_device(
+    devs: &mut Vec<VirtioDeviceStub>,
+    resource_bridges: &mut Vec<virtio::resource_bridge::ResourceResponseSocket>,
+    cfg: &Config,
+    typ: devices::virtio::VideoDeviceType,
+) -> std::result::Result<(), Error> {
+    let (video_socket, gpu_socket) =
+        virtio::resource_bridge::pair().map_err(Error::CreateSocket)?;
+    resource_bridges.push(gpu_socket);
+    devs.push(create_video_device(cfg, typ, video_socket)?);
+    Ok(())
+}
+
 fn create_vhost_vsock_device(cfg: &Config, cid: u64, mem: &GuestMemory) -> DeviceResult {
     let dev = virtio::vhost::Vsock::new(cid, mem).map_err(Error::VhostVsockDeviceNew)?;
 
@@ -1088,6 +1156,30 @@ fn create_virtio_devices(
         )?);
     }
 
+    #[cfg(feature = "video-decoder")]
+    {
+        if cfg.video_dec {
+            register_video_device(
+                &mut devs,
+                &mut resource_bridges,
+                cfg,
+                devices::virtio::VideoDeviceType::Decoder,
+            )?;
+        }
+    }
+
+    #[cfg(feature = "video-encoder")]
+    {
+        if cfg.video_enc {
+            register_video_device(
+                &mut devs,
+                &mut resource_bridges,
+                cfg,
+                devices::virtio::VideoDeviceType::Encoder,
+            )?;
+        }
+    }
+
     #[cfg(feature = "gpu")]
     {
         if let Some(gpu_parameters) = &cfg.gpu_parameters {
@@ -1624,6 +1716,11 @@ pub fn run_config(cfg: Config) -> Result<()> {
         initrd_image,
         extra_kernel_params: cfg.params.clone(),
         wayland_dmabuf: cfg.wayland_dmabuf,
+        acpi_sdts: cfg
+            .acpi_tables
+            .iter()
+            .map(|path| SDT::from_file(path).map_err(|e| Error::OpenAcpiTable(path.clone(), e)))
+            .collect::<Result<Vec<SDT>>>()?,
     };
 
     let control_server_socket = match &cfg.socket_path {
diff --git a/src/main.rs b/src/main.rs
index 7fd3eca..ed7bb30 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1223,6 +1223,29 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
 
             cfg.vfio.push(vfio_path);
         }
+        "video-decoder" => {
+            cfg.video_dec = true;
+        }
+        "video-encoder" => {
+            cfg.video_enc = true;
+        }
+        "acpi-table" => {
+            let acpi_table = PathBuf::from(value.unwrap());
+            if !acpi_table.exists() {
+                return Err(argument::Error::InvalidValue {
+                    value: value.unwrap().to_owned(),
+                    expected: String::from("the acpi-table path does not exist"),
+                });
+            }
+            if !acpi_table.is_file() {
+                return Err(argument::Error::InvalidValue {
+                    value: value.unwrap().to_owned(),
+                    expected: String::from("the acpi-table path should be a file"),
+                });
+            }
+
+            cfg.acpi_tables.push(acpi_table);
+        }
 
         "help" => return Err(argument::Error::PrintHelp),
         _ => unreachable!(),
@@ -1677,6 +1700,18 @@ Enable split-irqchip support.
             "PATH",
             "Path to sysfs of pass through or mdev device",
         ),
+        #[cfg(feature = "video-decoder")]
+        Argument::flag("video-decoder", "\
+(EXPERIMENTAL)
+Enable virtio-video decoder device.
+",
+        ),
+        #[cfg(feature = "video-encoder")]
+        Argument::flag("video-encoder", "\
+(EXPERIMENTAL)
+Enable virtio-video encoder device.
+"),
+        Argument::value("acpi-table", "PATH", "Path to user provided ACPI table."),
         Argument::short_flag('h', "help", "Print help message."),
     ];
 
diff --git a/src/plugin/process.rs b/src/plugin/process.rs
index 783239a..688aa85 100644
--- a/src/plugin/process.rs
+++ b/src/plugin/process.rs
@@ -5,7 +5,7 @@
 use std::collections::hash_map::{Entry, HashMap, VacantEntry};
 use std::env::set_var;
 use std::fs::File;
-use std::io::Write;
+use std::io::{IoSlice, Write};
 use std::mem::transmute;
 use std::os::unix::io::{IntoRawFd, RawFd};
 use std::os::unix::net::UnixDatagram;
@@ -730,7 +730,7 @@ impl Process {
             .map_err(Error::EncodeResponse)?;
         assert_ne!(self.response_buffer.len(), 0);
         self.request_sockets[index]
-            .send_with_fds(&self.response_buffer[..], &response_fds)
+            .send_with_fds(&[IoSlice::new(&self.response_buffer[..])], &response_fds)
             .map_err(Error::PluginSocketSend)?;
 
         Ok(())
diff --git a/sys_util/src/file_traits.rs b/sys_util/src/file_traits.rs
index 54e710f..bd763c7 100644
--- a/sys_util/src/file_traits.rs
+++ b/sys_util/src/file_traits.rs
@@ -98,7 +98,7 @@ pub trait FileReadWriteVolatile {
             }
             // Will panic if read_volatile read more bytes than we gave it, which would be worthy of
             // a panic.
-            slice = slice.offset(bytes_read as u64).unwrap();
+            slice = slice.offset(bytes_read).unwrap();
         }
         Ok(())
     }
@@ -129,7 +129,7 @@ pub trait FileReadWriteVolatile {
             }
             // Will panic if read_volatile read more bytes than we gave it, which would be worthy of
             // a panic.
-            slice = slice.offset(bytes_written as u64).unwrap();
+            slice = slice.offset(bytes_written).unwrap();
         }
         Ok(())
     }
@@ -187,7 +187,7 @@ pub trait FileReadWriteAtVolatile {
             match self.read_at_volatile(slice, offset) {
                 Ok(0) => return Err(Error::from(ErrorKind::UnexpectedEof)),
                 Ok(n) => {
-                    slice = slice.offset(n as u64).unwrap();
+                    slice = slice.offset(n).unwrap();
                     offset = offset.checked_add(n as u64).unwrap();
                 }
                 Err(ref e) if e.kind() == ErrorKind::Interrupted => {}
@@ -221,7 +221,7 @@ pub trait FileReadWriteAtVolatile {
             match self.write_at_volatile(slice, offset) {
                 Ok(0) => return Err(Error::from(ErrorKind::WriteZero)),
                 Ok(n) => {
-                    slice = slice.offset(n as u64).unwrap();
+                    slice = slice.offset(n).unwrap();
                     offset = offset.checked_add(n as u64).unwrap();
                 }
                 Err(ref e) if e.kind() == ErrorKind::Interrupted => {}
@@ -282,7 +282,7 @@ macro_rules! volatile_impl {
                 let ret = unsafe {
                     $crate::file_traits::lib::read(
                         self.as_raw_fd(),
-                        slice.as_ptr() as *mut std::ffi::c_void,
+                        slice.as_mut_ptr() as *mut std::ffi::c_void,
                         slice.size() as usize,
                     )
                 };
@@ -297,13 +297,7 @@ macro_rules! volatile_impl {
                 &mut self,
                 bufs: &[$crate::file_traits::lib::VolatileSlice],
             ) -> std::io::Result<usize> {
-                let iovecs: Vec<$crate::file_traits::lib::iovec> = bufs
-                    .iter()
-                    .map(|s| $crate::file_traits::lib::iovec {
-                        iov_base: s.as_ptr() as *mut std::ffi::c_void,
-                        iov_len: s.size() as $crate::file_traits::lib::size_t,
-                    })
-                    .collect();
+                let iovecs = $crate::file_traits::lib::VolatileSlice::as_iovecs(bufs);
 
                 if iovecs.is_empty() {
                     return Ok(0);
@@ -314,7 +308,7 @@ macro_rules! volatile_impl {
                 let ret = unsafe {
                     $crate::file_traits::lib::readv(
                         self.as_raw_fd(),
-                        &iovecs[0],
+                        iovecs.as_ptr(),
                         iovecs.len() as std::os::raw::c_int,
                     )
                 };
@@ -349,13 +343,7 @@ macro_rules! volatile_impl {
                 &mut self,
                 bufs: &[$crate::file_traits::lib::VolatileSlice],
             ) -> std::io::Result<usize> {
-                let iovecs: Vec<$crate::file_traits::lib::iovec> = bufs
-                    .iter()
-                    .map(|s| $crate::file_traits::lib::iovec {
-                        iov_base: s.as_ptr() as *mut std::ffi::c_void,
-                        iov_len: s.size() as $crate::file_traits::lib::size_t,
-                    })
-                    .collect();
+                let iovecs = $crate::file_traits::lib::VolatileSlice::as_iovecs(bufs);
 
                 if iovecs.is_empty() {
                     return Ok(0);
@@ -366,7 +354,7 @@ macro_rules! volatile_impl {
                 let ret = unsafe {
                     $crate::file_traits::lib::writev(
                         self.as_raw_fd(),
-                        &iovecs[0],
+                        iovecs.as_ptr(),
                         iovecs.len() as std::os::raw::c_int,
                     )
                 };
@@ -394,7 +382,7 @@ macro_rules! volatile_at_impl {
                 let ret = unsafe {
                     $crate::file_traits::lib::pread64(
                         self.as_raw_fd(),
-                        slice.as_ptr() as *mut std::ffi::c_void,
+                        slice.as_mut_ptr() as *mut std::ffi::c_void,
                         slice.size() as usize,
                         offset as $crate::file_traits::lib::off64_t,
                     )
@@ -412,13 +400,7 @@ macro_rules! volatile_at_impl {
                 bufs: &[$crate::file_traits::lib::VolatileSlice],
                 offset: u64,
             ) -> std::io::Result<usize> {
-                let iovecs: Vec<$crate::file_traits::lib::iovec> = bufs
-                    .iter()
-                    .map(|s| $crate::file_traits::lib::iovec {
-                        iov_base: s.as_ptr() as *mut std::ffi::c_void,
-                        iov_len: s.size() as $crate::file_traits::lib::size_t,
-                    })
-                    .collect();
+                let iovecs = $crate::file_traits::lib::VolatileSlice::as_iovecs(bufs);
 
                 if iovecs.is_empty() {
                     return Ok(0);
@@ -429,7 +411,7 @@ macro_rules! volatile_at_impl {
                 let ret = unsafe {
                     $crate::file_traits::lib::preadv64(
                         self.as_raw_fd(),
-                        &iovecs[0],
+                        iovecs.as_ptr(),
                         iovecs.len() as std::os::raw::c_int,
                         offset as $crate::file_traits::lib::off64_t,
                     )
@@ -469,13 +451,7 @@ macro_rules! volatile_at_impl {
                 bufs: &[$crate::file_traits::lib::VolatileSlice],
                 offset: u64,
             ) -> std::io::Result<usize> {
-                let iovecs: Vec<$crate::file_traits::lib::iovec> = bufs
-                    .iter()
-                    .map(|s| $crate::file_traits::lib::iovec {
-                        iov_base: s.as_ptr() as *mut std::ffi::c_void,
-                        iov_len: s.size() as $crate::file_traits::lib::size_t,
-                    })
-                    .collect();
+                let iovecs = $crate::file_traits::lib::VolatileSlice::as_iovecs(bufs);
 
                 if iovecs.is_empty() {
                     return Ok(0);
@@ -486,7 +462,7 @@ macro_rules! volatile_at_impl {
                 let ret = unsafe {
                     $crate::file_traits::lib::pwritev64(
                         self.as_raw_fd(),
-                        &iovecs[0],
+                        iovecs.as_ptr(),
                         iovecs.len() as std::os::raw::c_int,
                         offset as $crate::file_traits::lib::off64_t,
                     )
diff --git a/sys_util/src/guest_memory.rs b/sys_util/src/guest_memory.rs
index e8f620b..60b775a 100644
--- a/sys_util/src/guest_memory.rs
+++ b/sys_util/src/guest_memory.rs
@@ -7,6 +7,7 @@
 use std::convert::AsRef;
 use std::convert::TryFrom;
 use std::fmt::{self, Display};
+use std::mem::size_of;
 use std::os::unix::io::{AsRawFd, RawFd};
 use std::result;
 use std::sync::Arc;
@@ -87,11 +88,19 @@ struct MemoryRegion {
     memfd_offset: u64,
 }
 
-fn region_end(region: &MemoryRegion) -> GuestAddress {
-    // unchecked_add is safe as the region bounds were checked when it was created.
-    region
-        .guest_base
-        .unchecked_add(region.mapping.size() as u64)
+impl MemoryRegion {
+    fn start(&self) -> GuestAddress {
+        self.guest_base
+    }
+
+    fn end(&self) -> GuestAddress {
+        // unchecked_add is safe as the region bounds were checked when it was created.
+        self.guest_base.unchecked_add(self.mapping.size() as u64)
+    }
+
+    fn contains(&self, addr: GuestAddress) -> bool {
+        addr >= self.guest_base && addr < self.end()
+    }
 }
 
 /// Tracks a memory region and where it is mapped in the guest, along with a shm
@@ -200,8 +209,8 @@ impl GuestMemory {
     pub fn end_addr(&self) -> GuestAddress {
         self.regions
             .iter()
-            .max_by_key(|region| region.guest_base)
-            .map_or(GuestAddress(0), |region| region_end(region))
+            .max_by_key(|region| region.start())
+            .map_or(GuestAddress(0), MemoryRegion::end)
     }
 
     /// Returns the total size of memory in bytes.
@@ -214,9 +223,7 @@ impl GuestMemory {
 
     /// Returns true if the given address is within the memory range available to the guest.
     pub fn address_in_range(&self, addr: GuestAddress) -> bool {
-        self.regions
-            .iter()
-            .any(|region| region.guest_base <= addr && addr < region_end(region))
+        self.regions.iter().any(|region| region.contains(addr))
     }
 
     /// Returns true if the given range (start, end) is overlap with the memory range
@@ -224,7 +231,7 @@ impl GuestMemory {
     pub fn range_overlap(&self, start: GuestAddress, end: GuestAddress) -> bool {
         self.regions
             .iter()
-            .any(|region| region.guest_base < end && start < region_end(region))
+            .any(|region| region.start() < end && start < region.end())
     }
 
     /// Returns the address plus the offset if it is in range.
@@ -267,7 +274,7 @@ impl GuestMemory {
         for (index, region) in self.regions.iter().enumerate() {
             cb(
                 index,
-                region.guest_base,
+                region.start(),
                 region.mapping.size(),
                 region.mapping.as_ptr() as usize,
                 region.memfd_offset,
@@ -442,6 +449,61 @@ impl GuestMemory {
         })
     }
 
+    /// Returns a `VolatileSlice` of `len` bytes starting at `addr`. Returns an error if the slice
+    /// is not a subset of this `GuestMemory`.
+    ///
+    /// # Examples
+    /// * Write `99` to 30 bytes starting at guest address 0x1010.
+    ///
+    /// ```
+    /// # use sys_util::{GuestAddress, GuestMemory, GuestMemoryError, MemoryMapping};
+    /// # fn test_volatile_slice() -> Result<(), GuestMemoryError> {
+    /// #   let start_addr = GuestAddress(0x1000);
+    /// #   let mut gm = GuestMemory::new(&vec![(start_addr, 0x400)])?;
+    ///     let vslice = gm.get_slice_at_addr(GuestAddress(0x1010), 30)?;
+    ///     vslice.write_bytes(99);
+    /// #   Ok(())
+    /// # }
+    /// ```
+    pub fn get_slice_at_addr(&self, addr: GuestAddress, len: usize) -> Result<VolatileSlice> {
+        self.regions
+            .iter()
+            .find(|region| region.contains(addr))
+            .ok_or(Error::InvalidGuestAddress(addr))
+            .and_then(|region| {
+                // The cast to a usize is safe here because we know that `region.contains(addr)` and
+                // it's not possible for a memory region to be larger than what fits in a usize.
+                region
+                    .mapping
+                    .get_slice(addr.offset_from(region.start()) as usize, len)
+                    .map_err(Error::VolatileMemoryAccess)
+            })
+    }
+
+    /// Returns a `VolatileRef` to an object at `addr`. Returns Ok(()) if the object fits, or Err if
+    /// it extends past the end.
+    ///
+    /// # Examples
+    /// * Get a &u64 at offset 0x1010.
+    ///
+    /// ```
+    /// # use sys_util::{GuestAddress, GuestMemory, GuestMemoryError, MemoryMapping};
+    /// # fn test_ref_u64() -> Result<(), GuestMemoryError> {
+    /// #   let start_addr = GuestAddress(0x1000);
+    /// #   let mut gm = GuestMemory::new(&vec![(start_addr, 0x400)])?;
+    ///     gm.write_obj_at_addr(47u64, GuestAddress(0x1010))?;
+    ///     let vref = gm.get_ref_at_addr::<u64>(GuestAddress(0x1010))?;
+    ///     assert_eq!(vref.load(), 47u64);
+    /// #   Ok(())
+    /// # }
+    /// ```
+    pub fn get_ref_at_addr<T: DataInit>(&self, addr: GuestAddress) -> Result<VolatileRef<T>> {
+        let buf = self.get_slice_at_addr(addr, size_of::<T>())?;
+        // Safe because we have know that `buf` is at least `size_of::<T>()` bytes and that the
+        // returned reference will not outlive this `GuestMemory`.
+        Ok(unsafe { VolatileRef::new(buf.as_mut_ptr() as *mut T) })
+    }
+
     /// Reads data from a file descriptor and writes it to guest memory.
     ///
     /// # Arguments
@@ -550,15 +612,16 @@ impl GuestMemory {
     where
         F: FnOnce(&MemoryMapping, usize) -> Result<T>,
     {
-        for region in self.regions.iter() {
-            if guest_addr >= region.guest_base && guest_addr < region_end(region) {
-                return cb(
+        self.regions
+            .iter()
+            .find(|region| region.contains(guest_addr))
+            .ok_or(Error::InvalidGuestAddress(guest_addr))
+            .and_then(|region| {
+                cb(
                     &region.mapping,
-                    guest_addr.offset_from(region.guest_base) as usize,
-                );
-            }
-        }
-        Err(Error::InvalidGuestAddress(guest_addr))
+                    guest_addr.offset_from(region.start()) as usize,
+                )
+            })
     }
 
     /// Convert a GuestAddress into an offset within self.memfd.
@@ -585,25 +648,11 @@ impl GuestMemory {
     /// assert_eq!(offset, 0x3500);
     /// ```
     pub fn offset_from_base(&self, guest_addr: GuestAddress) -> Result<u64> {
-        for region in self.regions.iter() {
-            if guest_addr >= region.guest_base && guest_addr < region_end(region) {
-                return Ok(region.memfd_offset + guest_addr.offset_from(region.guest_base) as u64);
-            }
-        }
-        Err(Error::InvalidGuestAddress(guest_addr))
-    }
-}
-
-impl VolatileMemory for GuestMemory {
-    fn get_slice(&self, offset: u64, count: u64) -> VolatileMemoryResult<VolatileSlice> {
-        for region in self.regions.iter() {
-            if offset >= region.guest_base.0 && offset < region_end(region).0 {
-                return region
-                    .mapping
-                    .get_slice(offset - region.guest_base.0, count);
-            }
-        }
-        Err(VolatileMemoryError::OutOfBounds { addr: offset })
+        self.regions
+            .iter()
+            .find(|region| region.contains(guest_addr))
+            .ok_or(Error::InvalidGuestAddress(guest_addr))
+            .map(|region| region.memfd_offset + guest_addr.offset_from(region.start()))
     }
 }
 
@@ -690,8 +739,11 @@ mod tests {
         gm.write_obj_at_addr(val1, GuestAddress(0x500)).unwrap();
         gm.write_obj_at_addr(val2, GuestAddress(0x1000 + 32))
             .unwrap();
-        let num1: u64 = gm.get_ref(0x500).unwrap().load();
-        let num2: u64 = gm.get_ref(0x1000 + 32).unwrap().load();
+        let num1: u64 = gm.get_ref_at_addr(GuestAddress(0x500)).unwrap().load();
+        let num2: u64 = gm
+            .get_ref_at_addr(GuestAddress(0x1000 + 32))
+            .unwrap()
+            .load();
         assert_eq!(val1, num1);
         assert_eq!(val2, num2);
     }
@@ -704,8 +756,10 @@ mod tests {
 
         let val1: u64 = 0xaa55aa55aa55aa55;
         let val2: u64 = 0x55aa55aa55aa55aa;
-        gm.get_ref(0x500).unwrap().store(val1);
-        gm.get_ref(0x1000 + 32).unwrap().store(val2);
+        gm.get_ref_at_addr(GuestAddress(0x500)).unwrap().store(val1);
+        gm.get_ref_at_addr(GuestAddress(0x1000 + 32))
+            .unwrap()
+            .store(val2);
         let num1: u64 = gm.read_obj_from_addr(GuestAddress(0x500)).unwrap();
         let num2: u64 = gm.read_obj_from_addr(GuestAddress(0x1000 + 32)).unwrap();
         assert_eq!(val1, num1);
diff --git a/sys_util/src/mmap.rs b/sys_util/src/mmap.rs
index c6a52ea..64ffe17 100644
--- a/sys_util/src/mmap.rs
+++ b/sys_util/src/mmap.rs
@@ -608,15 +608,23 @@ impl MemoryMapping {
 }
 
 impl VolatileMemory for MemoryMapping {
-    fn get_slice(&self, offset: u64, count: u64) -> VolatileMemoryResult<VolatileSlice> {
+    fn get_slice(&self, offset: usize, count: usize) -> VolatileMemoryResult<VolatileSlice> {
         let mem_end = calc_offset(offset, count)?;
-        if mem_end > self.size as u64 {
+        if mem_end > self.size {
             return Err(VolatileMemoryError::OutOfBounds { addr: mem_end });
         }
 
+        let new_addr =
+            (self.as_ptr() as usize)
+                .checked_add(offset)
+                .ok_or(VolatileMemoryError::Overflow {
+                    base: self.as_ptr() as usize,
+                    offset,
+                })?;
+
         // Safe because we checked that offset + count was within our range and we only ever hand
         // out volatile accessors.
-        Ok(unsafe { VolatileSlice::new((self.addr as usize + offset as usize) as *mut _, count) })
+        Ok(unsafe { VolatileSlice::from_raw_parts(new_addr as *mut u8, count) })
     }
 }
 
@@ -888,11 +896,11 @@ mod tests {
     #[test]
     fn slice_overflow_error() {
         let m = MemoryMapping::new(5).unwrap();
-        let res = m.get_slice(std::u64::MAX, 3).unwrap_err();
+        let res = m.get_slice(std::usize::MAX, 3).unwrap_err();
         assert_eq!(
             res,
             VolatileMemoryError::Overflow {
-                base: std::u64::MAX,
+                base: std::usize::MAX,
                 offset: 3,
             }
         );
diff --git a/sys_util/src/sock_ctrl_msg.rs b/sys_util/src/sock_ctrl_msg.rs
index d4b953b..77a724d 100644
--- a/sys_util/src/sock_ctrl_msg.rs
+++ b/sys_util/src/sock_ctrl_msg.rs
@@ -6,10 +6,12 @@
 //! (e.g. Unix domain sockets).
 
 use std::fs::File;
+use std::io::{IoSlice, IoSliceMut};
 use std::mem::size_of;
 use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
 use std::os::unix::net::{UnixDatagram, UnixStream};
 use std::ptr::{copy_nonoverlapping, null_mut, write_unaligned};
+use std::slice;
 
 use libc::{
     c_long, c_void, cmsghdr, iovec, msghdr, recvmsg, sendmsg, MSG_NOSIGNAL, SCM_RIGHTS, SOL_SOCKET,
@@ -103,17 +105,17 @@ impl CmsgBuffer {
     }
 }
 
-fn raw_sendmsg<D: IntoIovec>(fd: RawFd, out_data: D, out_fds: &[RawFd]) -> Result<usize> {
+fn raw_sendmsg<D: IntoIovec>(fd: RawFd, out_data: &[D], out_fds: &[RawFd]) -> Result<usize> {
     let cmsg_capacity = CMSG_SPACE!(size_of::<RawFd>() * out_fds.len());
     let mut cmsg_buffer = CmsgBuffer::with_capacity(cmsg_capacity);
 
-    let mut iovec = out_data.into_iovec();
+    let iovec = IntoIovec::as_iovecs(out_data);
 
     let mut msg = msghdr {
         msg_name: null_mut(),
         msg_namelen: 0,
-        msg_iov: iovec.as_mut_ptr(),
-        msg_iovlen: 1,
+        msg_iov: iovec.as_ptr() as *mut iovec,
+        msg_iovlen: iovec.len(),
         msg_control: null_mut(),
         msg_controllen: 0,
         msg_flags: 0,
@@ -230,7 +232,7 @@ pub trait ScmSocket {
     ///
     /// * `buf` - A buffer of data to send on the `socket`.
     /// * `fd` - A file descriptors to be sent.
-    fn send_with_fd<D: IntoIovec>(&self, buf: D, fd: RawFd) -> Result<usize> {
+    fn send_with_fd<D: IntoIovec>(&self, buf: &[D], fd: RawFd) -> Result<usize> {
         self.send_with_fds(buf, &[fd])
     }
 
@@ -242,7 +244,7 @@ pub trait ScmSocket {
     ///
     /// * `buf` - A buffer of data to send on the `socket`.
     /// * `fds` - A list of file descriptors to be sent.
-    fn send_with_fds<D: IntoIovec>(&self, buf: D, fd: &[RawFd]) -> Result<usize> {
+    fn send_with_fds<D: IntoIovec>(&self, buf: &[D], fd: &[RawFd]) -> Result<usize> {
         raw_sendmsg(self.socket_fd(), buf, fd)
     }
 
@@ -307,30 +309,55 @@ impl ScmSocket for UnixSeqpacket {
 ///
 /// This trait is unsafe because interfaces that use this trait depend on the base pointer and size
 /// being accurate.
-pub unsafe trait IntoIovec {
-    /// Gets a vector of structures describing each contiguous region of a memory buffer.
-    fn into_iovec(&self) -> Vec<libc::iovec>;
+pub unsafe trait IntoIovec: Sized {
+    /// Returns a `iovec` that describes a contiguous region of memory.
+    fn into_iovec(&self) -> iovec;
+
+    /// Returns a slice of `iovec`s that each describe a contiguous region of memory.
+    fn as_iovecs(bufs: &[Self]) -> &[iovec];
+}
+
+// Safe because there are no other mutable references to the memory described by `IoSlice` and it is
+// guaranteed to be ABI-compatible with `iovec`.
+unsafe impl<'a> IntoIovec for IoSlice<'a> {
+    fn into_iovec(&self) -> iovec {
+        iovec {
+            iov_base: self.as_ptr() as *mut c_void,
+            iov_len: self.len(),
+        }
+    }
+
+    fn as_iovecs(bufs: &[Self]) -> &[iovec] {
+        // Safe because `IoSlice` is guaranteed to be ABI-compatible with `iovec`.
+        unsafe { slice::from_raw_parts(bufs.as_ptr() as *const iovec, bufs.len()) }
+    }
 }
 
-// Safe because this slice can not have another mutable reference and it's pointer and size are
-// guaranteed to be valid.
-unsafe impl<'a> IntoIovec for &'a [u8] {
-    fn into_iovec(&self) -> Vec<libc::iovec> {
-        vec![libc::iovec {
-            iov_base: self.as_ref().as_ptr() as *const c_void as *mut c_void,
+// Safe because there are no other references to the memory described by `IoSliceMut` and it is
+// guaranteed to be ABI-compatible with `iovec`.
+unsafe impl<'a> IntoIovec for IoSliceMut<'a> {
+    fn into_iovec(&self) -> iovec {
+        iovec {
+            iov_base: self.as_ptr() as *mut c_void,
             iov_len: self.len(),
-        }]
+        }
+    }
+
+    fn as_iovecs(bufs: &[Self]) -> &[iovec] {
+        // Safe because `IoSliceMut` is guaranteed to be ABI-compatible with `iovec`.
+        unsafe { slice::from_raw_parts(bufs.as_ptr() as *const iovec, bufs.len()) }
     }
 }
 
 // Safe because volatile slices are only ever accessed with other volatile interfaces and the
 // pointer and size are guaranteed to be accurate.
 unsafe impl<'a> IntoIovec for VolatileSlice<'a> {
-    fn into_iovec(&self) -> Vec<libc::iovec> {
-        vec![libc::iovec {
-            iov_base: self.as_ptr() as *const c_void as *mut c_void,
-            iov_len: self.size() as usize,
-        }]
+    fn into_iovec(&self) -> iovec {
+        self.as_iovec()
+    }
+
+    fn as_iovecs(bufs: &[Self]) -> &[iovec] {
+        VolatileSlice::as_iovecs(bufs)
     }
 }
 
@@ -388,8 +415,9 @@ mod tests {
     fn send_recv_no_fd() {
         let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair");
 
+        let ioslice = IoSlice::new([1u8, 1, 2, 21, 34, 55].as_ref());
         let write_count = s1
-            .send_with_fds([1u8, 1, 2, 21, 34, 55].as_ref(), &[])
+            .send_with_fds(&[ioslice], &[])
             .expect("failed to send data");
 
         assert_eq!(write_count, 6);
@@ -410,8 +438,9 @@ mod tests {
         let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair");
 
         let evt = EventFd::new().expect("failed to create eventfd");
+        let ioslice = IoSlice::new([].as_ref());
         let write_count = s1
-            .send_with_fd([].as_ref(), evt.as_raw_fd())
+            .send_with_fd(&[ioslice], evt.as_raw_fd())
             .expect("failed to send fd");
 
         assert_eq!(write_count, 0);
@@ -437,8 +466,9 @@ mod tests {
         let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair");
 
         let evt = EventFd::new().expect("failed to create eventfd");
+        let ioslice = IoSlice::new([237].as_ref());
         let write_count = s1
-            .send_with_fds([237].as_ref(), &[evt.as_raw_fd()])
+            .send_with_fds(&[ioslice], &[evt.as_raw_fd()])
             .expect("failed to send fd");
 
         assert_eq!(write_count, 1);
diff --git a/x86_64/src/acpi.rs b/x86_64/src/acpi.rs
index c1d92ee..e6fc15a 100644
--- a/x86_64/src/acpi.rs
+++ b/x86_64/src/acpi.rs
@@ -8,6 +8,8 @@ use sys_util::{GuestAddress, GuestMemory};
 pub struct ACPIDevResource {
     pub amls: Vec<u8>,
     pub pm_iobase: u64,
+    /// Additional system descriptor tables.
+    pub sdts: Vec<SDT>,
 }
 
 #[repr(C)]
@@ -102,17 +104,35 @@ pub fn create_acpi_tables(
     num_cpus: u8,
     sci_irq: u32,
     acpi_dev_resource: ACPIDevResource,
-) -> GuestAddress {
+) -> Option<GuestAddress> {
     // RSDP is at the HI RSDP WINDOW
     let rsdp_offset = GuestAddress(super::ACPI_HI_RSDP_WINDOW_BASE);
+    let mut offset = rsdp_offset.checked_add(RSDP::len() as u64)?;
     let mut tables: Vec<u64> = Vec::new();
+    let mut dsdt_offset: Option<GuestAddress> = None;
+
+    // User supplied System Description Tables, e.g. SSDT.
+    for sdt in acpi_dev_resource.sdts.iter() {
+        guest_mem.write_at_addr(sdt.as_slice(), offset).ok()?;
+        if sdt.is_signature(b"DSDT") {
+            dsdt_offset = Some(offset);
+        } else {
+            tables.push(offset.0);
+        }
+        offset = offset.checked_add(sdt.len() as u64)?;
+    }
 
     // DSDT
-    let dsdt = create_dsdt_table(acpi_dev_resource.amls);
-    let dsdt_offset = rsdp_offset.checked_add(RSDP::len() as u64).unwrap();
-    guest_mem
-        .write_at_addr(dsdt.as_slice(), dsdt_offset)
-        .expect("Error writing DSDT table");
+    let dsdt_offset = match dsdt_offset {
+        Some(dsdt_offset) => dsdt_offset,
+        None => {
+            let dsdt_offset = offset;
+            let dsdt = create_dsdt_table(acpi_dev_resource.amls);
+            guest_mem.write_at_addr(dsdt.as_slice(), offset).ok()?;
+            offset = offset.checked_add(dsdt.len() as u64)?;
+            dsdt_offset
+        }
+    };
 
     // FACP aka FADT
     // Revision 6 of the ACPI FADT table is 276 bytes long
@@ -160,11 +180,9 @@ pub fn create_acpi_tables(
 
     facp.write(FADT_FIELD_HYPERVISOR_ID, *b"CROSVM"); // Hypervisor Vendor Identity
 
-    let facp_offset = dsdt_offset.checked_add(dsdt.len() as u64).unwrap();
-    guest_mem
-        .write_at_addr(facp.as_slice(), facp_offset)
-        .expect("Error writing FACP table");
-    tables.push(facp_offset.0);
+    guest_mem.write_at_addr(facp.as_slice(), offset).ok()?;
+    tables.push(offset.0);
+    offset = offset.checked_add(facp.len() as u64)?;
 
     // MADT
     let mut madt = SDT::new(
@@ -198,11 +216,9 @@ pub fn create_acpi_tables(
         ..Default::default()
     });
 
-    let madt_offset = facp_offset.checked_add(facp.len() as u64).unwrap();
-    guest_mem
-        .write_at_addr(madt.as_slice(), madt_offset)
-        .expect("Error writing MADT table");
-    tables.push(madt_offset.0);
+    guest_mem.write_at_addr(madt.as_slice(), offset).ok()?;
+    tables.push(offset.0);
+    offset = offset.checked_add(madt.len() as u64)?;
 
     // XSDT
     let mut xsdt = SDT::new(
@@ -217,16 +233,11 @@ pub fn create_acpi_tables(
         xsdt.append(table);
     }
 
-    let xsdt_offset = madt_offset.checked_add(madt.len() as u64).unwrap();
-    guest_mem
-        .write_at_addr(xsdt.as_slice(), xsdt_offset)
-        .expect("Error writing XSDT table");
+    guest_mem.write_at_addr(xsdt.as_slice(), offset).ok()?;
 
     // RSDP
-    let rsdp = RSDP::new(*b"CROSVM", xsdt_offset.0);
-    guest_mem
-        .write_at_addr(rsdp.as_slice(), rsdp_offset)
-        .expect("Error writing RSDP");
+    let rsdp = RSDP::new(*b"CROSVM", offset.0);
+    guest_mem.write_at_addr(rsdp.as_slice(), rsdp_offset).ok()?;
 
-    rsdp_offset
+    Some(rsdp_offset)
 }
diff --git a/x86_64/src/lib.rs b/x86_64/src/lib.rs
index b4c4aa7..9714638 100644
--- a/x86_64/src/lib.rs
+++ b/x86_64/src/lib.rs
@@ -55,6 +55,7 @@ use std::sync::Arc;
 
 use crate::bootparam::boot_params;
 use acpi_tables::aml::Aml;
+use acpi_tables::sdt::SDT;
 use arch::{
     get_serial_cmdline, GetSerialCmdlineError, RunnableLinuxVm, SerialHardware, SerialParameters,
     VmComponents, VmImage,
@@ -283,9 +284,11 @@ fn configure_system(
         .write_obj_at_addr(params, zero_page_addr)
         .map_err(|_| Error::ZeroPageSetup)?;
 
-    let rsdp_addr =
-        acpi::create_acpi_tables(guest_mem, num_cpus, X86_64_SCI_IRQ, acpi_dev_resource);
-    params.acpi_rsdp_addr = rsdp_addr.0;
+    if let Some(rsdp_addr) =
+        acpi::create_acpi_tables(guest_mem, num_cpus, X86_64_SCI_IRQ, acpi_dev_resource)
+    {
+        params.acpi_rsdp_addr = rsdp_addr.0;
+    }
 
     Ok(())
 }
@@ -442,6 +445,7 @@ impl arch::LinuxArch for X8664arch {
             &mut io_bus,
             &mut resources,
             suspend_evt.try_clone().map_err(Error::CloneEventFd)?,
+            components.acpi_sdts,
         )?;
 
         let ramoops_region = match components.pstore {
@@ -853,6 +857,7 @@ impl X8664arch {
         io_bus: &mut devices::Bus,
         resources: &mut SystemAllocator,
         suspend_evt: EventFd,
+        sdts: Vec<SDT>,
     ) -> Result<acpi::ACPIDevResource> {
         // The AML data for the acpi devices
         let mut amls = Vec::new();
@@ -883,7 +888,11 @@ impl X8664arch {
             .unwrap();
         io_bus.notify_on_resume(pm);
 
-        Ok(acpi::ACPIDevResource { amls, pm_iobase })
+        Ok(acpi::ACPIDevResource {
+            amls,
+            pm_iobase,
+            sdts,
+        })
     }
 
     /// Sets up the serial devices for this platform. Returns the serial port number and serial
diff --git a/x86_64/src/mptable.rs b/x86_64/src/mptable.rs
index 218f561..de489f2 100644
--- a/x86_64/src/mptable.rs
+++ b/x86_64/src/mptable.rs
@@ -10,7 +10,6 @@ use std::slice;
 
 use libc::c_char;
 
-use data_model::VolatileMemory;
 use devices::{PciAddress, PciInterruptPin};
 use sys_util::{GuestAddress, GuestMemory};
 
@@ -140,7 +139,7 @@ pub fn setup_mptable(
         return Err(Error::AddressOverflow);
     }
 
-    mem.get_slice(base_mp.0, mp_size as u64)
+    mem.get_slice_at_addr(base_mp, mp_size)
         .map_err(|_| Error::Clear)?
         .write_bytes(0);