Snap for 7889170 from be1275c1d5d7af96afdfc64d8ad5ddc0927aadad to sc-v2-release

Change-Id: I52dcbd1b776bd21bb78759bc70782f6da679682e
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index f04998d..0000000
--- a/.gitmodules
+++ /dev/null
@@ -1,3 +0,0 @@
-[submodule "tpm2-sys/libtpm2"]
-	path = tpm2-sys/libtpm2
-	url = https://chromium.googlesource.com/chromiumos/third_party/tpm2
diff --git a/Android.bp b/Android.bp
index 2f8a461..2a8a902 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,16 +1,5 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --no-subdir.
-// Most modules in this file are edited manually:
-// * global defaults defined in crosvm_defaults, enabled for linux_glibc_x86_64
-//   * all modules use crosvm_defaults or other defaults derived from it
-// * root host binary is crosvm
-// * crosvm and libcrosvm have extra features:
-//   * audio, gfxstream, gpu
-// * crosvm has extra flags and ld_flags
-// * crosvm_host_test_tests_boot and crosvm_host_test_tests_plugins
-//   are not ready yet
-// We use cargo2android.py only to discover new changes.
-//   * Use --no-subdir to suppress output of subdirectory Android.bp files.
-//   * Run cargo2android.py in each subdirectory to generate .bp files.
+// This file is generated by cargo2android.py --config cargo2android.json.
+// Do not modify this file as changes will be overridden on upgrade.
 
 package {
     default_applicable_licenses: ["external_crosvm_license"],
@@ -43,44 +32,6 @@
     ],
 }
 
-rust_defaults {
-    name: "crosvm_defaults",
-    edition: "2018",
-    enabled: false,
-    target: {
-        linux_glibc_x86_64: {
-            enabled: true,
-            flags: [
-                "-L device/google/cuttlefish_vmm/x86_64-linux-gnu/bin/",
-            ],
-        },
-        android64: {
-            compile_multilib: "64",
-            enabled: true,
-        },
-        linux_bionic_arm64: {
-            enabled: true,
-        },
-        darwin: {
-            enabled: false,
-        },
-    },
-    apex_available: [
-        "//apex_available:platform",
-        "com.android.virt",
-    ],
-}
-
-rust_defaults {
-    name: "crosvm_proc_macro_defaults",
-    defaults: ["crosvm_defaults"],
-    target: {
-        darwin: {
-            enabled: true,
-        },
-    },
-}
-
 rust_binary {
     name: "crosvm",
     defaults: ["crosvm_defaults"],
@@ -88,287 +39,92 @@
     prefer_rlib: true,
     crate_name: "crosvm",
     srcs: ["src/main.rs"],
-
+    edition: "2018",
+    features: [
+        "audio",
+        "default",
+        "gfxstream",
+        "gpu",
+        "usb",
+    ],
+    rustlibs: [
+        "libacpi_tables",
+        "libarch",
+        "libassertions",
+        "libaudio_streams",
+        "libbase_rust",
+        "libbit_field",
+        "libcrosvm",
+        "libdata_model",
+        "libdevices",
+        "libdisk",
+        "libhypervisor",
+        "libkernel_cmdline",
+        "libkernel_loader",
+        "liblibc",
+        "liblibcras",
+        "libminijail_rust",
+        "libnet_util",
+        "libp9",
+        "libresources",
+        "librutabaga_gfx",
+        "libserde_json",
+        "libsync_rust",
+        "libtempfile",
+        "libvhost",
+        "libvm_control",
+        "libvm_memory",
+    ],
+    proc_macros: [
+        "libenumn",
+        "libremain",
+    ],
+    arch: {
+        x86_64: {
+            rustlibs: ["libx86_64_rust"],
+        },
+        arm64: {
+            rustlibs: ["libaarch64"],
+        },
+    },
     target: {
         linux_bionic_arm64: {
             relative_install_path: "aarch64-linux-bionic",
         },
+        darwin: {
+            enabled: false,
+        },
         linux_glibc_x86_64: {
+            relative_install_path: "x86_64-linux-gnu",
             features: [
                 "gdb",
                 "gdbstub",
             ],
-            relative_install_path: "x86_64-linux-gnu",
             rustlibs: [
                 "libgdbstub",
+                "libgdbstub_arch",
                 "libthiserror",
             ],
         },
-        darwin: {
-            enabled: false,
-        },
     },
-    arch: {
-        x86_64: {
-            rustlibs: ["libx86_64_rust"],
-        },
-        arm64: {
-            rustlibs: ["libaarch64"],
-        },
-    },
-
-    edition: "2018",
-    features: [
-        "audio",
-        "default",
-        "gpu",
-        "gfxstream",
-        "usb"
-    ],
-
-    flags: [
-        "-C overflow-checks=y",
-    ],
     ld_flags: [
         "-Wl,--rpath,\\$$ORIGIN",
         "-Wl,--rpath,\\$$ORIGIN/../../lib64",
     ],
-    rustlibs: [
-        "libacpi_tables",
-        "libarch",
-        "libassertions",
-        "libaudio_streams",
-        "libbase_rust",
-        "libbit_field",
-        "libcrosvm",
-        "libdata_model",
-        "libdevices",
-        "libdisk",
-        "libhypervisor",
-        "libkernel_cmdline",
-        "libkernel_loader",
-        "liblibc",
-        "liblibcras",
-        "libminijail_rust",
-        "libnet_util",
-        "libp9",
-        "librand_ish",
-        "libresources",
-        "librutabaga_gfx",
-        "libserde_json",
-        "libsync_rust",
-        "libtempfile",
-        "libvhost",
-        "libvhost_user_devices",
-        "libvm_control",
-        "libvm_memory",
-    ],
-    proc_macros: [
-        "libenumn",
-        "libremain",
-    ],
 }
 
 rust_defaults {
-    name: "crosvm_defaults_test",
+    name: "crosvm_test_defaults",
     defaults: ["crosvm_defaults"],
     crate_name: "crosvm",
     srcs: ["src/crosvm.rs"],
     test_suites: ["general-tests"],
     auto_gen_config: true,
     edition: "2018",
-    arch: {
-        x86_64: {
-            rustlibs: ["libx86_64_rust"],
-        },
-        arm64: {
-            rustlibs: ["libaarch64"],
-        },
-    },
-    target: {
-        linux_glibc_x86_64: {
-            features: [
-                "gdb",
-                "gdbstub",
-            ],
-            rustlibs: [
-                "libgdbstub",
-                "libthiserror",
-            ],
-        },
-    },
-    features: [
-        "default",
-    ],
-    rustlibs: [
-        "libacpi_tables",
-        "libarch",
-        "libassertions",
-        "libbase_rust",
-        "libbit_field",
-        "libdata_model",
-        "libdevices",
-        "libdisk",
-        "libhypervisor",
-        "libkernel_cmdline",
-        "libkernel_loader",
-        "liblibc",
-        "libminijail_rust",
-        "libnet_util",
-        "libp9",
-        "librand_ish",
-        "libresources",
-        "librutabaga_gfx",
-        "libserde_json",
-        "libsync_rust",
-        "libtempfile",
-        "libvhost",
-        "libvm_control",
-        "libvm_memory",
-    ],
-    proc_macros: [
-        "libenumn",
-        "libremain",
-    ],
-}
-
-rust_test_host {
-    name: "crosvm_host_test_src_crosvm",
-    defaults: ["crosvm_defaults_test"],
-    features: [
-        "audio",
-    ],
-    rustlibs: [
-        "libaudio_streams",
-        "liblibcras",
-    ],
-    test_options: {
-        unit_test: true,
-    },
-}
-
-rust_test {
-    name: "crosvm_device_test_src_crosvm",
-    defaults: ["crosvm_defaults_test"],
-    features: [
-        "audio",
-    ],
-    rustlibs: [
-        "libaudio_streams",
-        "liblibcras",
-    ],
-}
-
-rust_defaults {
-    name: "crosvm_defaults_crosvm",
-    defaults: ["crosvm_defaults"],
-    crate_name: "crosvm",
-    srcs: ["src/main.rs"],
-    test_suites: ["general-tests"],
-    auto_gen_config: true,
-    edition: "2018",
-    arch: {
-        x86_64: {
-            rustlibs: ["libx86_64_rust"],
-        },
-        arm64: {
-            rustlibs: ["libaarch64"],
-        },
-    },
-    features: [
-        "default",
-    ],
-    rustlibs: [
-        "libacpi_tables",
-        "libarch",
-        "libassertions",
-        "libaudio_streams",
-        "libbase_rust",
-        "libbit_field",
-        "libcrosvm",
-        "libdata_model",
-        "libdevices",
-        "libdisk",
-        "libhypervisor",
-        "libkernel_cmdline",
-        "libkernel_loader",
-        "liblibc",
-        "liblibcras",
-        "libminijail_rust",
-        "libnet_util",
-        "libp9",
-        "librand_ish",
-        "libresources",
-        "librutabaga_gfx",
-        "libserde_json",
-        "libsync_rust",
-        "libtempfile",
-        "libvhost",
-        "libvhost_user_devices",
-        "libvm_control",
-        "libvm_memory",
-    ],
-    proc_macros: [
-        "libenumn",
-        "libremain",
-    ],
-}
-
-rust_test_host {
-    name: "crosvm_host_test_src_main",
-    defaults: ["crosvm_defaults_crosvm"],
-    features: [
-        "audio",
-    ],
-    rustlibs: [
-        "libaudio_streams",
-        "liblibcras",
-    ],
-    test_options: {
-        unit_test: true,
-    },
-}
-
-rust_test {
-    name: "crosvm_device_test_src_main",
-    defaults: ["crosvm_defaults_crosvm"],
-}
-
-rust_library {
-    name: "libcrosvm",
-    defaults: ["crosvm_defaults"],
-    host_supported: true,
-    crate_name: "crosvm",
-    srcs: ["src/crosvm.rs"],
-    edition: "2018",
-    target: {
-        linux_glibc: {
-            features: [
-                "gfxstream",
-            ],
-        },
-        linux_glibc_x86_64: {
-            features: [
-                "gdb",
-                "gdbstub",
-            ],
-            rustlibs: [
-                "libgdbstub",
-                "libthiserror",
-            ],
-        },
-    },
-    arch: {
-        x86_64: {
-            rustlibs: ["libx86_64_rust"],
-        },
-        arm64: {
-            rustlibs: ["libaarch64"],
-        },
-    },
     features: [
         "audio",
         "default",
+        "gfxstream",
         "gpu",
         "usb",
     ],
@@ -390,14 +146,12 @@
         "libminijail_rust",
         "libnet_util",
         "libp9",
-        "librand_ish",
         "libresources",
         "librutabaga_gfx",
         "libserde_json",
         "libsync_rust",
         "libtempfile",
         "libvhost",
-        "libvhost_user_devices",
         "libvm_control",
         "libvm_memory",
     ],
@@ -407,62 +161,274 @@
     ],
 }
 
-// dependent_library ["feature_list"]
-//   ../adhd/audio_streams/src/audio_streams.rs
-//   ../adhd/cras/client/cras-sys/src/lib.rs
-//   ../adhd/cras/client/libcras/src/libcras.rs
-//   ../libchromeos-rs/src/lib.rs
-//   ../minijail/rust/minijail-sys/lib.rs
-//   ../minijail/rust/minijail/src/lib.rs
-//   ../rust/vmm_vhost/src/lib.rs "default,vhost-user,vhost-user-master,vhost-user-slave"
-//   ../vm_tools/p9/src/lib.rs
-//   ../vm_tools/p9/wire_format_derive/wire_format_derive.rs
-//   anyhow-1.0.41 "default,std"
-//   async-task-4.0.3 "default,std"
-//   async-trait-0.1.50
-//   autocfg-1.0.1
-//   bitflags-1.2.1 "default"
-//   cc-1.0.25
-//   cfg-if-1.0.0
-//   downcast-rs-1.2.0 "default,std"
-//   futures-0.3.15 "alloc,std"
-//   futures-channel-0.3.15 "alloc,futures-sink,sink,std"
-//   futures-core-0.3.15 "alloc,std"
-//   futures-io-0.3.15 "std"
-//   futures-sink-0.3.15 "alloc,std"
-//   futures-task-0.3.15 "alloc,std"
-//   futures-util-0.3.15 "alloc,channel,futures-channel,futures-io,futures-sink,io,memchr,sink,slab,std"
-//   getopts-0.2.21
-//   getrandom-0.2.3 "std"
-//   intrusive-collections-0.9.1 "alloc,default"
-//   itoa-0.4.7
-//   libc-0.2.97 "default,std"
-//   log-0.4.14
-//   memchr-2.4.0 "default,std"
-//   memoffset-0.5.6 "default"
-//   once_cell-1.8.0 "alloc,default,race,std"
-//   paste-1.0.5
-//   pin-project-lite-0.2.7
-//   pin-utils-0.1.0
-//   pkg-config-0.3.19
-//   ppv-lite86-0.2.10 "simd,std"
-//   proc-macro2-1.0.27 "default,proc-macro"
-//   protobuf-2.24.1
-//   quote-1.0.9 "default,proc-macro"
-//   rand-0.8.4 "alloc,default,getrandom,libc,rand_chacha,rand_hc,std,std_rng"
-//   rand_chacha-0.3.1 "std"
-//   rand_core-0.6.3 "alloc,getrandom,std"
-//   remain-0.2.2
-//   remove_dir_all-0.5.3
-//   ryu-1.0.5
-//   serde-1.0.126 "default,derive,serde_derive,std"
-//   serde_derive-1.0.126 "default"
-//   serde_json-1.0.64 "default,std"
-//   slab-0.4.3 "default,std"
-//   smallvec-1.6.1
-//   syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-//   tempfile-3.2.0
-//   thiserror-1.0.25
-//   thiserror-impl-1.0.25
-//   unicode-width-0.1.8 "default"
-//   unicode-xid-0.2.2 "default"
+rust_test_host {
+    name: "crosvm_host_test_src_crosvm",
+    defaults: ["crosvm_test_defaults"],
+    test_options: {
+        unit_test: true,
+    },
+    arch: {
+        x86_64: {
+            rustlibs: ["libx86_64_rust"],
+        },
+        arm64: {
+            rustlibs: ["libaarch64"],
+        },
+    },
+    target: {
+        linux_glibc_x86_64: {
+            features: [
+                "gdb",
+                "gdbstub",
+            ],
+            rustlibs: [
+                "libgdbstub",
+                "libgdbstub_arch",
+                "libthiserror",
+            ],
+        },
+    },
+    ld_flags: [
+        "-Wl,--rpath,\\$$ORIGIN",
+        "-Wl,--rpath,\\$$ORIGIN/../../lib64",
+    ],
+}
+
+rust_test {
+    name: "crosvm_device_test_src_crosvm",
+    defaults: ["crosvm_test_defaults"],
+    arch: {
+        x86_64: {
+            rustlibs: ["libx86_64_rust"],
+        },
+        arm64: {
+            rustlibs: ["libaarch64"],
+        },
+    },
+    target: {
+        linux_glibc_x86_64: {
+            features: [
+                "gdb",
+                "gdbstub",
+            ],
+            rustlibs: [
+                "libgdbstub",
+                "libgdbstub_arch",
+                "libthiserror",
+            ],
+        },
+    },
+    ld_flags: [
+        "-Wl,--rpath,\\$$ORIGIN",
+        "-Wl,--rpath,\\$$ORIGIN/../../lib64",
+    ],
+}
+
+rust_defaults {
+    name: "crosvm_test_defaults_crosvm",
+    defaults: ["crosvm_defaults"],
+    crate_name: "crosvm",
+    srcs: ["src/main.rs"],
+    test_suites: ["general-tests"],
+    auto_gen_config: true,
+    edition: "2018",
+    features: [
+        "audio",
+        "default",
+        "gfxstream",
+        "gpu",
+        "usb",
+    ],
+    rustlibs: [
+        "libacpi_tables",
+        "libarch",
+        "libassertions",
+        "libaudio_streams",
+        "libbase_rust",
+        "libbit_field",
+        "libcrosvm",
+        "libdata_model",
+        "libdevices",
+        "libdisk",
+        "libhypervisor",
+        "libkernel_cmdline",
+        "libkernel_loader",
+        "liblibc",
+        "liblibcras",
+        "libminijail_rust",
+        "libnet_util",
+        "libp9",
+        "libresources",
+        "librutabaga_gfx",
+        "libserde_json",
+        "libsync_rust",
+        "libtempfile",
+        "libvhost",
+        "libvm_control",
+        "libvm_memory",
+    ],
+    proc_macros: [
+        "libenumn",
+        "libremain",
+    ],
+}
+
+rust_test_host {
+    name: "crosvm_host_test_src_main",
+    defaults: ["crosvm_test_defaults_crosvm"],
+    test_options: {
+        unit_test: true,
+    },
+    arch: {
+        x86_64: {
+            rustlibs: ["libx86_64_rust"],
+        },
+        arm64: {
+            rustlibs: ["libaarch64"],
+        },
+    },
+    target: {
+        linux_glibc_x86_64: {
+            features: [
+                "gdb",
+                "gdbstub",
+            ],
+            rustlibs: [
+                "libgdbstub",
+                "libgdbstub_arch",
+                "libthiserror",
+            ],
+        },
+    },
+    ld_flags: [
+        "-Wl,--rpath,\\$$ORIGIN",
+        "-Wl,--rpath,\\$$ORIGIN/../../lib64",
+    ],
+}
+
+rust_test {
+    name: "crosvm_device_test_src_main",
+    defaults: ["crosvm_test_defaults_crosvm"],
+    arch: {
+        x86_64: {
+            rustlibs: ["libx86_64_rust"],
+        },
+        arm64: {
+            rustlibs: ["libaarch64"],
+        },
+    },
+    target: {
+        linux_glibc_x86_64: {
+            features: [
+                "gdb",
+                "gdbstub",
+            ],
+            rustlibs: [
+                "libgdbstub",
+                "libgdbstub_arch",
+                "libthiserror",
+            ],
+        },
+    },
+    ld_flags: [
+        "-Wl,--rpath,\\$$ORIGIN",
+        "-Wl,--rpath,\\$$ORIGIN/../../lib64",
+    ],
+}
+
+rust_library {
+    name: "libcrosvm",
+    defaults: ["crosvm_defaults"],
+    host_supported: true,
+    crate_name: "crosvm",
+    srcs: ["src/crosvm.rs"],
+    edition: "2018",
+    features: [
+        "audio",
+        "default",
+        "gfxstream",
+        "gpu",
+        "usb",
+    ],
+    rustlibs: [
+        "libacpi_tables",
+        "libarch",
+        "libassertions",
+        "libaudio_streams",
+        "libbase_rust",
+        "libbit_field",
+        "libdata_model",
+        "libdevices",
+        "libdisk",
+        "libhypervisor",
+        "libkernel_cmdline",
+        "libkernel_loader",
+        "liblibc",
+        "liblibcras",
+        "libminijail_rust",
+        "libnet_util",
+        "libp9",
+        "libresources",
+        "librutabaga_gfx",
+        "libserde_json",
+        "libsync_rust",
+        "libtempfile",
+        "libvhost",
+        "libvm_control",
+        "libvm_memory",
+    ],
+    proc_macros: [
+        "libenumn",
+        "libremain",
+    ],
+    arch: {
+        x86_64: {
+            rustlibs: ["libx86_64_rust"],
+        },
+        arm64: {
+            rustlibs: ["libaarch64"],
+        },
+    },
+    target: {
+        linux_glibc_x86_64: {
+            features: [
+                "gdb",
+                "gdbstub",
+            ],
+            rustlibs: [
+                "libgdbstub",
+                "libgdbstub_arch",
+                "libthiserror",
+            ],
+        },
+    },
+    ld_flags: [
+        "-Wl,--rpath,\\$$ORIGIN",
+        "-Wl,--rpath,\\$$ORIGIN/../../lib64",
+    ],
+}
+
+rust_defaults {
+    name: "crosvm_defaults",
+    edition: "2018",
+    enabled: false,
+    target: {
+        linux_glibc_x86_64: {
+            enabled: true,
+        },
+        android64: {
+            compile_multilib: "64",
+            enabled: true,
+        },
+        linux_bionic_arm64: {
+            enabled: true,
+        },
+        darwin: {
+            enabled: false,
+        },
+    },
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.virt",
+    ],
+}
diff --git a/Cargo.lock b/Cargo.lock
index fde8abf..370fab0 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -48,7 +48,7 @@
  "acpi_tables",
  "base",
  "devices",
- "gdbstub",
+ "gdbstub_arch",
  "hypervisor",
  "kernel_cmdline",
  "libc",
@@ -86,18 +86,14 @@
 name = "audio_streams"
 version = "0.1.0"
 dependencies = [
+ "async-trait",
+ "cros_async",
  "sync",
  "sys_util",
 ]
 
 [[package]]
 name = "autocfg"
-version = "0.1.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
-
-[[package]]
-name = "autocfg"
 version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
@@ -152,20 +148,18 @@
 checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
 
 [[package]]
-name = "cloudabi"
-version = "0.0.3"
+name = "cfg-if"
+version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
-dependencies = [
- "bitflags",
-]
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
 [[package]]
-name = "cras-sys"
-version = "0.1.0"
+name = "crc32fast"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
 dependencies = [
- "audio_streams",
- "data_model",
+ "cfg-if 1.0.0",
 ]
 
 [[package]]
@@ -179,6 +173,7 @@
  "intrusive-collections",
  "io_uring",
  "libc",
+ "once_cell",
  "paste",
  "pin-utils",
  "slab",
@@ -188,13 +183,6 @@
 ]
 
 [[package]]
-name = "cros_fuzz"
-version = "0.1.0"
-dependencies = [
- "rand_core 0.4.2",
-]
-
-[[package]]
 name = "crosvm"
 version = "0.1.0"
 dependencies = [
@@ -211,6 +199,7 @@
  "disk",
  "enumn",
  "gdbstub",
+ "gdbstub_arch",
  "hypervisor",
  "kernel_cmdline",
  "kernel_loader",
@@ -223,7 +212,6 @@
  "p9",
  "protobuf",
  "protos",
- "rand_ish",
  "remain",
  "resources",
  "rutabaga_gfx",
@@ -232,31 +220,12 @@
  "tempfile",
  "thiserror",
  "vhost",
- "vhost_user_devices",
  "vm_control",
  "vm_memory",
  "x86_64",
 ]
 
 [[package]]
-name = "crosvm-fuzz"
-version = "0.0.1"
-dependencies = [
- "base",
- "cros_fuzz",
- "data_model",
- "devices",
- "disk",
- "fuse",
- "kernel_loader",
- "libc",
- "rand",
- "tempfile",
- "usb_util",
- "vm_memory",
-]
-
-[[package]]
 name = "crosvm_plugin"
 version = "0.17.0"
 dependencies = [
@@ -314,7 +283,6 @@
  "p9",
  "power_monitor",
  "protos",
- "rand_ish",
  "remain",
  "resources",
  "rutabaga_gfx",
@@ -340,6 +308,7 @@
 dependencies = [
  "async-trait",
  "base",
+ "crc32fast",
  "cros_async",
  "data_model",
  "futures",
@@ -349,6 +318,7 @@
  "remain",
  "tempfile",
  "thiserror",
+ "uuid",
  "vm_memory",
 ]
 
@@ -368,12 +338,6 @@
 ]
 
 [[package]]
-name = "fuchsia-cprng"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
-
-[[package]]
 name = "fuse"
 version = "0.1.0"
 dependencies = [
@@ -387,9 +351,9 @@
 
 [[package]]
 name = "futures"
-version = "0.3.1"
+version = "0.3.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6f16056ecbb57525ff698bb955162d0cd03bee84e6241c27ff75c08d8ca5987"
+checksum = "7f55667319111d593ba876406af7c409c0ebb44dc4be6132a783ccf163ea14c1"
 dependencies = [
  "futures-channel",
  "futures-core",
@@ -401,9 +365,9 @@
 
 [[package]]
 name = "futures-channel"
-version = "0.3.1"
+version = "0.3.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fcae98ca17d102fd8a3603727b9259fcf7fa4239b603d2142926189bc8999b86"
+checksum = "8c2dd2df839b57db9ab69c2c9d8f3e8c81984781937fe2807dc6dcf3b2ad2939"
 dependencies = [
  "futures-core",
  "futures-sink",
@@ -411,33 +375,33 @@
 
 [[package]]
 name = "futures-core"
-version = "0.3.1"
+version = "0.3.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "79564c427afefab1dfb3298535b21eda083ef7935b4f0ecbfcb121f0aec10866"
+checksum = "15496a72fabf0e62bdc3df11a59a3787429221dd0710ba8ef163d6f7a9112c94"
 
 [[package]]
 name = "futures-io"
-version = "0.3.1"
+version = "0.3.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e676577d229e70952ab25f3945795ba5b16d63ca794ca9d2c860e5595d20b5ff"
+checksum = "d71c2c65c57704c32f5241c1223167c2c3294fd34ac020c807ddbe6db287ba59"
 
 [[package]]
 name = "futures-sink"
-version = "0.3.1"
+version = "0.3.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "171be33efae63c2d59e6dbba34186fe0d6394fb378069a76dfd80fdcffd43c16"
+checksum = "85754d98985841b7d4f5e8e6fbfa4a4ac847916893ec511a2917ccd8525b8bb3"
 
 [[package]]
 name = "futures-task"
-version = "0.3.1"
+version = "0.3.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0bae52d6b29cf440e298856fec3965ee6fa71b06aa7495178615953fd669e5f9"
+checksum = "fa189ef211c15ee602667a6fcfe1c1fd9e07d42250d2156382820fba33c9df80"
 
 [[package]]
 name = "futures-util"
-version = "0.3.1"
+version = "0.3.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0d66274fb76985d3c62c886d1da7ac4c0903a8c9f754e8fe0f35a6a6cc39e76"
+checksum = "1812c7ab8aedf8d6f2701a43e1243acdbcc2b36ab26e2ad421eb99ac963d96d1"
 dependencies = [
  "futures-channel",
  "futures-core",
@@ -445,17 +409,18 @@
  "futures-sink",
  "futures-task",
  "memchr",
+ "pin-project-lite",
  "pin-utils",
  "slab",
 ]
 
 [[package]]
 name = "gdbstub"
-version = "0.4.2"
+version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "224c17cf54ffe7e084343f25c7f2881a399bea69862ecaf5bc97f0f6586ba0dc"
+checksum = "7e135587d3f6eee6fa02c4ba174270c2337424e6d852c156942c0840b3c0f5cc"
 dependencies = [
- "cfg-if",
+ "cfg-if 0.1.10",
  "log",
  "managed",
  "num-traits",
@@ -463,15 +428,36 @@
 ]
 
 [[package]]
-name = "getopts"
-version = "0.2.18"
+name = "gdbstub_arch"
+version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0a7292d30132fb5424b354f5dc02512a86e4c516fe544bb7a25e7f266951b797"
+checksum = "e358b9c0e1468eae66099062e47bb502849308b987b74b5e72f1936397c33c16"
+dependencies = [
+ "gdbstub",
+ "num-traits",
+]
+
+[[package]]
+name = "getopts"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5"
 dependencies = [
  "unicode-width",
 ]
 
 [[package]]
+name = "getrandom"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee8025cf36f917e6a52cce185b7c7177689b838b7ec138364e50cc2277a56cf4"
+dependencies = [
+ "cfg-if 0.1.10",
+ "libc",
+ "wasi",
+]
+
+[[package]]
 name = "gpu_display"
 version = "0.1.0"
 dependencies = [
@@ -484,6 +470,15 @@
 ]
 
 [[package]]
+name = "hermit-abi"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "307c3c9f937f38e3534b1d6447ecf090cafcc9744e4a6360e8b037b2cf5af120"
+dependencies = [
+ "libc",
+]
+
+[[package]]
 name = "hypervisor"
 version = "0.1.0"
 dependencies = [
@@ -578,20 +573,25 @@
 
 [[package]]
 name = "libc"
-version = "0.2.93"
+version = "0.2.94"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41"
+checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e"
+
+[[package]]
+name = "libchromeos"
+version = "0.1.0"
+dependencies = [
+ "data_model",
+ "futures",
+ "intrusive-collections",
+ "libc",
+ "log",
+ "protobuf",
+]
 
 [[package]]
 name = "libcras"
 version = "0.1.0"
-dependencies = [
- "audio_streams",
- "cras-sys",
- "data_model",
- "libc",
- "sys_util",
-]
 
 [[package]]
 name = "libcrosvm_control"
@@ -631,11 +631,11 @@
 
 [[package]]
 name = "log"
-version = "0.4.5"
+version = "0.4.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d4fcce5fa49cc693c312001daf1d13411c4a5283796bac1084299ea3e567113f"
+checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
 ]
 
 [[package]]
@@ -646,9 +646,9 @@
 
 [[package]]
 name = "memchr"
-version = "2.3.0"
+version = "2.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3197e20c7edb283f87c071ddfc7a2cca8f8e0b888c242959846a6fce03c72223"
+checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
 
 [[package]]
 name = "memoffset"
@@ -656,7 +656,7 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa"
 dependencies = [
- "autocfg 1.0.1",
+ "autocfg",
 ]
 
 [[package]]
@@ -700,15 +700,16 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611"
 dependencies = [
- "autocfg 1.0.1",
+ "autocfg",
 ]
 
 [[package]]
 name = "num_cpus"
-version = "1.9.0"
+version = "1.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a69d464bdc213aaaff628444e99578ede64e9c854025aa43b9796530afa9238"
+checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
 dependencies = [
+ "hermit-abi",
  "libc",
 ]
 
@@ -723,7 +724,7 @@
 version = "0.1.0"
 dependencies = [
  "libc",
- "sys_util",
+ "libchromeos",
  "wire_format_derive",
 ]
 
@@ -734,10 +735,16 @@
 checksum = "ba7ae1a2180ed02ddfdb5ab70c70d596a26dd642e097bb6fe78b1bde8588ed97"
 
 [[package]]
-name = "pin-utils"
-version = "0.1.0-alpha.4"
+name = "pin-project-lite"
+version = "0.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587"
+checksum = "439697af366c49a6d0a010c56a0d97685bc140ce0d377b13a2ea2aa42d64a827"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
 
 [[package]]
 name = "pkg-config"
@@ -766,42 +773,43 @@
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.24"
+version = "1.0.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
+checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec"
 dependencies = [
  "unicode-xid",
 ]
 
 [[package]]
 name = "protobuf"
-version = "2.8.1"
+version = "2.24.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "40361836defdd5871ff7e84096c6f6444af7fc157f8ef1789f54f147687caa20"
+checksum = "db50e77ae196458ccd3dc58a31ea1a90b0698ab1b7928d89f644c25d72070267"
 
 [[package]]
 name = "protobuf-codegen"
-version = "2.8.1"
+version = "2.24.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "12c6abd78435445fc86898ebbd0521a68438063d4a73e23527b7134e6bf58b4a"
+checksum = "09321cef9bee9ddd36884f97b7f7cc92a586cdc74205c4b3aeba65b5fc9c6f90"
 dependencies = [
  "protobuf",
 ]
 
 [[package]]
 name = "protoc"
-version = "2.8.1"
+version = "2.24.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3998c4bc0af8ccbd3cc68245ee9f72663c5ae2fb78bc48ff7719aef11562edea"
+checksum = "c367feabb5f78ca3b2ec25e2c4a5f4f0826017d7fb634f52961afd1a6613d1fb"
 dependencies = [
  "log",
+ "which",
 ]
 
 [[package]]
 name = "protoc-rust"
-version = "2.8.1"
+version = "2.24.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "234c97039c32bb58a883d0deafa57db37e59428ce536f3bdfe1c46cffec04113"
+checksum = "7bb2c1038f8014a2e42fdffec03ffc03f574a8bf66b0ac32f1b6941681eb1317"
 dependencies = [
  "protobuf",
  "protobuf-codegen",
@@ -830,133 +838,14 @@
 
 [[package]]
 name = "quote"
-version = "1.0.2"
+version = "1.0.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
+checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
 dependencies = [
  "proc-macro2",
 ]
 
 [[package]]
-name = "rand"
-version = "0.6.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
-dependencies = [
- "autocfg 0.1.7",
- "libc",
- "rand_chacha",
- "rand_core 0.4.2",
- "rand_hc",
- "rand_isaac",
- "rand_jitter",
- "rand_os",
- "rand_pcg",
- "rand_xorshift",
- "winapi",
-]
-
-[[package]]
-name = "rand_chacha"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef"
-dependencies = [
- "autocfg 0.1.7",
- "rand_core 0.3.1",
-]
-
-[[package]]
-name = "rand_core"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
-dependencies = [
- "rand_core 0.4.2",
-]
-
-[[package]]
-name = "rand_core"
-version = "0.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
-
-[[package]]
-name = "rand_hc"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4"
-dependencies = [
- "rand_core 0.3.1",
-]
-
-[[package]]
-name = "rand_isaac"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08"
-dependencies = [
- "rand_core 0.3.1",
-]
-
-[[package]]
-name = "rand_ish"
-version = "0.1.0"
-
-[[package]]
-name = "rand_jitter"
-version = "0.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b"
-dependencies = [
- "libc",
- "rand_core 0.4.2",
- "winapi",
-]
-
-[[package]]
-name = "rand_os"
-version = "0.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
-dependencies = [
- "cloudabi",
- "fuchsia-cprng",
- "libc",
- "rand_core 0.4.2",
- "rdrand",
- "winapi",
-]
-
-[[package]]
-name = "rand_pcg"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44"
-dependencies = [
- "autocfg 0.1.7",
- "rand_core 0.4.2",
-]
-
-[[package]]
-name = "rand_xorshift"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
-dependencies = [
- "rand_core 0.3.1",
-]
-
-[[package]]
-name = "rdrand"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
-dependencies = [
- "rand_core 0.3.1",
-]
-
-[[package]]
 name = "remain"
 version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -994,18 +883,18 @@
 
 [[package]]
 name = "serde"
-version = "1.0.121"
+version = "1.0.125"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6159e3c76cab06f6bc466244d43b35e77e9500cd685da87620addadc2a4c40b1"
+checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.121"
+version = "1.0.125"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f3fcab8778dc651bc65cfab2e4eb64996f3c912b74002fb379c94517e1f27c46"
+checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1025,9 +914,9 @@
 
 [[package]]
 name = "slab"
-version = "0.4.2"
+version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
+checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527"
 
 [[package]]
 name = "smallvec"
@@ -1037,9 +926,9 @@
 
 [[package]]
 name = "syn"
-version = "1.0.58"
+version = "1.0.72"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc60a3d73ea6594cd712d830cc1f0390fd71542d8c8cd24e70cc54cdfd5e05d5"
+checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1109,15 +998,15 @@
 
 [[package]]
 name = "unicode-width"
-version = "0.1.5"
+version = "0.1.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526"
+checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
 
 [[package]]
 name = "unicode-xid"
-version = "0.2.0"
+version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
+checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
 
 [[package]]
 name = "usb_sys"
@@ -1139,6 +1028,15 @@
 ]
 
 [[package]]
+name = "uuid"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
 name = "vfio_sys"
 version = "0.1.0"
 dependencies = [
@@ -1162,6 +1060,7 @@
 version = "0.1.0"
 dependencies = [
  "anyhow",
+ "arch",
  "base",
  "cros_async",
  "data_model",
@@ -1195,7 +1094,7 @@
 dependencies = [
  "base",
  "data_model",
- "gdbstub",
+ "gdbstub_arch",
  "hypervisor",
  "libc",
  "resources",
@@ -1228,28 +1127,22 @@
 ]
 
 [[package]]
-name = "winapi"
-version = "0.3.9"
+name = "wasi"
+version = "0.9.0+wasi-snapshot-preview1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
+
+[[package]]
+name = "which"
+version = "4.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87c14ef7e1b8b8ecfc75d5eca37949410046e66f15d185c01d70824f1f8111ef"
 dependencies = [
- "winapi-i686-pc-windows-gnu",
- "winapi-x86_64-pc-windows-gnu",
+ "libc",
+ "thiserror",
 ]
 
 [[package]]
-name = "winapi-i686-pc-windows-gnu"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
-
-[[package]]
-name = "winapi-x86_64-pc-windows-gnu"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
-
-[[package]]
 name = "wire_format_derive"
 version = "0.1.0"
 dependencies = [
@@ -1268,7 +1161,7 @@
  "base",
  "data_model",
  "devices",
- "gdbstub",
+ "gdbstub_arch",
  "hypervisor",
  "kernel_cmdline",
  "kernel_loader",
diff --git a/Cargo.toml b/Cargo.toml
index 2b5312c..d967db1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -21,33 +21,42 @@
 panic = 'abort'
 overflow-checks = true
 
+# We currently need to exclude some crates from the workspace to allow
+# these crates to be independently built by portage. These crates will
+# eventually be moved into separate repositories.
+# The only workspace members that need to be explicitly specified here are those
+# that are not dependencies of the crosvm root crate.
 [workspace]
 members = [
-    # Disabled to support running cargo2android while cros-fuzz is not
-    # available in Android.
-    # "fuzz",
+    "fuzz",
     "qcow_utils",
     "integration_tests",
     "libcrosvm_control",
+    "vhost_user_devices",
 ]
 exclude = [
     "assertions",
     "base",
+    "common",
     "cros_async",
     "data_model",
-    "rand_ish",
     "sync",
     "sys_util",
     "tempfile",
     "vm_memory",
+    "audio_streams",
 ]
 
 [features]
+audio = ["devices/audio"]
+audio_cras = ["devices/audio_cras"]
+chromeos = ["base/chromeos", "audio_cras"]
+composite-disk = ["protos/composite-disk", "protobuf", "disk/composite-disk"]
 default = ["audio", "gpu", "usb"]
-chromeos = ["base/chromeos"]
 default-no-sandbox = []
 direct = ["devices/direct"]
-audio = ["devices/audio"]
+gdb = ["gdbstub", "gdbstub_arch", "thiserror",  "arch/gdb", "vm_control/gdb", "x86_64/gdb"]
+gfxstream = ["devices/gfxstream"]
 gpu = ["devices/gpu"]
 plugin = ["protos/plugin", "crosvm_plugin", "kvm", "kvm_sys", "protobuf"]
 power-monitor-powerd = ["arch/power-monitor-powerd"]
@@ -55,13 +64,10 @@
 usb = ["devices/usb"]
 video-decoder = ["devices/video-decoder"]
 video-encoder = ["devices/video-encoder"]
+virgl_renderer = ["devices/virgl_renderer"]
+virgl_renderer_next = ["rutabaga_gfx/virgl_renderer_next"]
 wl-dmabuf = ["devices/minigbm"]
 x = ["devices/x"]
-virgl_renderer_next = ["rutabaga_gfx/virgl_renderer_next"]
-composite-disk = ["protos/composite-disk", "protobuf", "disk/composite-disk"]
-virgl_renderer = ["devices/virgl_renderer"]
-gfxstream = ["devices/gfxstream"]
-gdb = ["gdbstub", "thiserror",  "arch/gdb", "vm_control/gdb", "x86_64/gdb"]
 
 [dependencies]
 arch = { path = "arch" }
@@ -74,7 +80,8 @@
 devices = { path = "devices" }
 disk = { path = "disk" }
 enumn = { path = "enumn" }
-gdbstub = { version = "0.4.0", optional = true }
+gdbstub = { version = "0.5.0", optional = true }
+gdbstub_arch = { version = "0.1.0", optional = true }
 rutabaga_gfx = { path = "rutabaga_gfx"}
 hypervisor = { path = "hypervisor" }
 kernel_cmdline = { path = "kernel_cmdline" }
@@ -85,10 +92,9 @@
 libcras = "*"
 minijail = "*" # provided by ebuild
 net_util = { path = "net_util" }
-p9 = { path = "../vm_tools/p9" }
+p9 = "*"
 protobuf = { version = "2.3", optional = true }
 protos = { path = "protos", optional = true }
-rand_ish = { path = "rand_ish" }
 remain = "*"
 resources = { path = "resources" }
 serde_json = "*"
@@ -96,7 +102,6 @@
 tempfile = "*"
 thiserror = { version = "1.0.20", optional = true }
 vhost = { path = "vhost" }
-vhost_user_devices = { path = "vhost_user_devices" }
 vm_control = { path = "vm_control" }
 acpi_tables = { path = "acpi_tables" }
 vm_memory = { path = "vm_memory" }
@@ -112,19 +117,15 @@
 
 [patch.crates-io]
 assertions = { path = "assertions" }
-audio_streams = { path = "../adhd/audio_streams" } # ignored by ebuild
+audio_streams = { path = "audio_streams" }
 base = { path = "base" }
-cras-sys = { path = "../adhd/cras/client/cras-sys" } # ignored by ebuild
-# Disabled to support running cargo2android while cros-fuzz is not
-# available in Android.
-# cros_fuzz = { path = "../../platform2/cros-fuzz" } # ignored by ebuild
+cros_fuzz = { path = "common/cros-fuzz" } # ignored by ebuild
 data_model = { path = "data_model" }
-libchromeos = { path = "../libchromeos-rs" } # ignored by ebuild
-libcras = { path = "../adhd/cras/client/libcras" } # ignored by ebuild
-minijail = { path = "../minijail/rust/minijail" } # ignored by ebuild
-p9 = { path = "../vm_tools/p9" } # ignored by ebuild
+libcras = { path = "libcras_stub" } # ignored by ebuild
+p9 = { path = "common/p9" } # ignored by ebuild
 sync = { path = "sync" }
 sys_util = { path = "sys_util" }
 tempfile = { path = "tempfile" }
-wire_format_derive = { path = "../vm_tools/p9/wire_format_derive" } # ignored by ebuild
-vmm_vhost = { path = "../rust/vmm_vhost", features = ["vhost-user-master"] } # ignored by ebuild
+wire_format_derive = { path = "common/p9/wire_format_derive" } # ignored by ebuild
+minijail = { path = "../minijail/rust/minijail" } # ignored by ebuild
+vmm_vhost = { path = "../rust/vmm_vhost", features = ["vhost-user-master", "vhost-user-slave"] } # ignored by ebuild
diff --git a/PRESUBMIT.cfg b/PRESUBMIT.cfg
deleted file mode 100644
index 91b4973..0000000
--- a/PRESUBMIT.cfg
+++ /dev/null
@@ -1,6 +0,0 @@
-[Hook Overrides]
-cargo_clippy_check: true
-
-[Hook Overrides Options]
-cargo_clippy_check:
-  --project=.:bin/preupload-clippy
diff --git a/README.md b/README.md
index 43c119f..68a1766 100644
--- a/README.md
+++ b/README.md
@@ -17,6 +17,10 @@
 See the [Chromium OS developer guide] for more on how to build and deploy with
 Portage.
 
+NOTE: `cros_workon_make` modifies crosvm's Cargo.toml and Cargo.lock. Please be
+careful not to commit the changes. Moreover, with the changes cargo will fail to
+build and clippy preupload check will fail.
+
 [Chromium OS developer guide]: https://chromium.googlesource.com/chromiumos/docs/+/HEAD/developer_guide.md
 
 ### Building with Docker
@@ -28,15 +32,17 @@
 
 >**NOTE:** Building for Linux natively is new and not fully supported.
 
-First, [set up depot_tools] and use `repo` to sync down the crosvm source
-tree. This is a subset of the entire Chromium OS manifest with just enough repos
-to build crosvm.
+Crosvm uses submodules to manage external dependencies. Initialize them via:
 
 ```sh
-mkdir crosvm
-cd crosvm
-repo init -g crosvm -u https://chromium.googlesource.com/chromiumos/manifest.git --repo-url=https://chromium.googlesource.com/external/repo.git
-repo sync
+git submodule update --init
+```
+
+It is recommended to enable automatic recursive operations to keep the
+submodules in sync with the main repository:
+
+```sh
+git config --global submodule.recurse true
 ```
 
 A basic crosvm build links against `libcap`. On a Debian-based system,
@@ -73,8 +79,6 @@
 
 And that's it! You should be able to `cargo build/run/test`.
 
-[set up depot_tools]: https://commondatastorage.googleapis.com/chrome-infra-docs/flat/depot_tools/docs/html/depot_tools_tutorial.html#_setting_up
-
 ## Usage
 
 To see the usage information for your version of crosvm, run `crosvm` or `crosvm
diff --git a/aarch64/Android.bp b/aarch64/Android.bp
index 833d4af..415b9bf 100644
--- a/aarch64/Android.bp
+++ b/aarch64/Android.bp
@@ -1,5 +1,5 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
-// Manually added target and arch to rules below.
+// This file is generated by cargo2android.py --config cargo2android.json.
+// Do not modify this file as changes will be overridden on upgrade.
 
 package {
     // See: http://go/android-license-faq
@@ -11,10 +11,9 @@
 }
 
 rust_defaults {
-    name: "aarch64_defaults",
+    name: "aarch64_test_defaults",
     defaults: ["crosvm_defaults"],
     crate_name: "aarch64",
-    // has rustc warnings
     srcs: ["src/lib.rs"],
     test_suites: ["general-tests"],
     auto_gen_config: true,
@@ -36,6 +35,14 @@
         "libvm_memory",
     ],
     proc_macros: ["libremain"],
+}
+
+rust_test_host {
+    name: "aarch64_host_test_src_lib",
+    defaults: ["aarch64_test_defaults"],
+    test_options: {
+        unit_test: true,
+    },
     target: {
         // It is necessary to disable this specifically as well as the arch below, because
         // crosvm_defaults enables it and the more specific target apparently takes precedence over
@@ -51,23 +58,27 @@
     },
 }
 
-rust_test_host {
-    name: "aarch64_host_test_src_lib",
-    defaults: ["aarch64_defaults"],
-    test_options: {
-        unit_test: true,
-    },
-}
-
 rust_test {
     name: "aarch64_device_test_src_lib",
-    defaults: ["aarch64_defaults"],
+    defaults: ["aarch64_test_defaults"],
+    target: {
+        // It is necessary to disable this specifically as well as the arch below, because
+        // crosvm_defaults enables it and the more specific target apparently takes precedence over
+        // the less specific arch.
+        linux_glibc_x86_64: {
+            enabled: false,
+        },
+    },
+    arch: {
+        x86_64: {
+            enabled: false,
+        },
+    },
 }
 
 rust_library {
     name: "libaarch64",
     defaults: ["crosvm_defaults"],
-    // has rustc warnings
     host_supported: true,
     crate_name: "aarch64",
     srcs: ["src/lib.rs"],
@@ -103,93 +114,3 @@
         },
     },
 }
-
-// dependent_library ["feature_list"]
-//   ../../adhd/audio_streams/src/audio_streams.rs
-//   ../../adhd/cras/client/cras-sys/src/lib.rs
-//   ../../adhd/cras/client/libcras/src/libcras.rs
-//   ../../libchromeos-rs/src/lib.rs
-//   ../../minijail/rust/minijail-sys/lib.rs
-//   ../../minijail/rust/minijail/src/lib.rs
-//   ../../rust/vmm_vhost/src/lib.rs "default,vhost-user,vhost-user-master"
-//   ../../vm_tools/p9/src/lib.rs
-//   ../../vm_tools/p9/wire_format_derive/wire_format_derive.rs
-//   ../acpi_tables/src/lib.rs
-//   ../arch/src/lib.rs
-//   ../assertions/src/lib.rs
-//   ../base/src/lib.rs
-//   ../bit_field/bit_field_derive/bit_field_derive.rs
-//   ../bit_field/src/lib.rs
-//   ../cros_async/src/lib.rs
-//   ../data_model/src/lib.rs
-//   ../devices/src/lib.rs
-//   ../disk/src/disk.rs
-//   ../enumn/src/lib.rs
-//   ../fuse/src/lib.rs
-//   ../hypervisor/src/lib.rs
-//   ../io_uring/src/lib.rs
-//   ../kernel_cmdline/src/kernel_cmdline.rs
-//   ../kvm/src/lib.rs
-//   ../kvm_sys/src/lib.rs
-//   ../linux_input_sys/src/lib.rs
-//   ../net_sys/src/lib.rs
-//   ../net_util/src/lib.rs
-//   ../power_monitor/src/lib.rs
-//   ../rand_ish/src/lib.rs
-//   ../resources/src/lib.rs
-//   ../rutabaga_gfx/src/lib.rs
-//   ../sync/src/lib.rs
-//   ../sys_util/poll_token_derive/poll_token_derive.rs
-//   ../sys_util/src/lib.rs
-//   ../tempfile/src/lib.rs
-//   ../usb_sys/src/lib.rs
-//   ../usb_util/src/lib.rs
-//   ../vfio_sys/src/lib.rs
-//   ../vhost/src/lib.rs
-//   ../virtio_sys/src/lib.rs
-//   ../vm_control/src/lib.rs
-//   ../vm_memory/src/lib.rs
-//   async-task-4.0.3 "default,std"
-//   async-trait-0.1.50
-//   autocfg-1.0.1
-//   bitflags-1.2.1 "default"
-//   cfg-if-1.0.0
-//   downcast-rs-1.2.0 "default,std"
-//   futures-0.3.15 "alloc,std"
-//   futures-channel-0.3.15 "alloc,futures-sink,sink,std"
-//   futures-core-0.3.15 "alloc,std"
-//   futures-io-0.3.15 "std"
-//   futures-sink-0.3.15 "alloc,std"
-//   futures-task-0.3.15 "alloc,std"
-//   futures-util-0.3.15 "alloc,channel,futures-channel,futures-io,futures-sink,io,memchr,sink,slab,std"
-//   getrandom-0.2.3 "std"
-//   intrusive-collections-0.9.1 "alloc,default"
-//   itoa-0.4.7
-//   libc-0.2.97 "default,std"
-//   log-0.4.14
-//   memchr-2.4.0 "default,std"
-//   memoffset-0.5.6 "default"
-//   paste-1.0.5
-//   pin-project-lite-0.2.7
-//   pin-utils-0.1.0
-//   pkg-config-0.3.19
-//   ppv-lite86-0.2.10 "simd,std"
-//   proc-macro2-1.0.27 "default,proc-macro"
-//   protobuf-2.24.1
-//   quote-1.0.9 "default,proc-macro"
-//   rand-0.8.4 "alloc,default,getrandom,libc,rand_chacha,rand_hc,std,std_rng"
-//   rand_chacha-0.3.1 "std"
-//   rand_core-0.6.3 "alloc,getrandom,std"
-//   remain-0.2.2
-//   remove_dir_all-0.5.3
-//   ryu-1.0.5
-//   serde-1.0.126 "default,derive,serde_derive,std"
-//   serde_derive-1.0.126 "default"
-//   serde_json-1.0.64 "default,std"
-//   slab-0.4.3 "default,std"
-//   smallvec-1.6.1
-//   syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-//   tempfile-3.2.0
-//   thiserror-1.0.25
-//   thiserror-impl-1.0.25
-//   unicode-xid-0.2.2 "default"
diff --git a/aarch64/cargo2android.json b/aarch64/cargo2android.json
new file mode 100644
index 0000000..42ec06f
--- /dev/null
+++ b/aarch64/cargo2android.json
@@ -0,0 +1,8 @@
+{
+  "add-module-block": "cargo2android_arch.bp",
+  "add_workspace": true,
+  "device": true,
+  "global_defaults": "crosvm_defaults",
+  "run": true,
+  "tests": true
+}
\ No newline at end of file
diff --git a/aarch64/cargo2android_arch.bp b/aarch64/cargo2android_arch.bp
new file mode 100644
index 0000000..54602b2
--- /dev/null
+++ b/aarch64/cargo2android_arch.bp
@@ -0,0 +1,13 @@
+target: {
+    // It is necessary to disable this specifically as well as the arch below, because
+    // crosvm_defaults enables it and the more specific target apparently takes precedence over
+    // the less specific arch.
+    linux_glibc_x86_64: {
+        enabled: false,
+    },
+},
+arch: {
+    x86_64: {
+        enabled: false,
+    },
+}
\ No newline at end of file
diff --git a/aarch64/src/fdt.rs b/aarch64/src/fdt.rs
index 376bd60..b4874a1 100644
--- a/aarch64/src/fdt.rs
+++ b/aarch64/src/fdt.rs
@@ -46,6 +46,7 @@
 // If we had a more complex interrupt architecture, then we'd need an enum for
 // these.
 const PHANDLE_GIC: u32 = 1;
+const PHANDLE_RESTRICTED_DMA_POOL: u32 = 2;
 
 // CPUs are assigned phandles starting with this number.
 const PHANDLE_CPU0: u32 = 0x100;
@@ -71,6 +72,27 @@
     Ok(())
 }
 
+fn create_resv_memory_node(fdt: &mut FdtWriter, resv_size: Option<u64>) -> Result<Option<u32>> {
+    if let Some(resv_size) = resv_size {
+        let resv_memory_node = fdt.begin_node("reserved-memory")?;
+        fdt.property_u32("#address-cells", 0x2)?;
+        fdt.property_u32("#size-cells", 0x2)?;
+        fdt.property_null("ranges")?;
+
+        let restricted_dma_pool = fdt.begin_node("restricted_dma_reserved")?;
+        fdt.property_u32("phandle", PHANDLE_RESTRICTED_DMA_POOL)?;
+        fdt.property_string("compatible", "restricted-dma-pool")?;
+        fdt.property_u64("size", resv_size)?;
+        fdt.property_u64("alignment", base::pagesize() as u64)?;
+        fdt.end_node(restricted_dma_pool)?;
+
+        fdt.end_node(resv_memory_node)?;
+        Ok(Some(PHANDLE_RESTRICTED_DMA_POOL))
+    } else {
+        Ok(None)
+    }
+}
+
 fn create_cpu_nodes(
     fdt: &mut FdtWriter,
     num_cpus: u32,
@@ -264,6 +286,7 @@
     pci_irqs: Vec<(PciAddress, u32, PciInterruptPin)>,
     pci_device_base: u64,
     pci_device_size: u64,
+    dma_pool_phandle: Option<u32>,
 ) -> Result<()> {
     // Add devicetree nodes describing a PCI generic host controller.
     // See Documentation/devicetree/bindings/pci/host-generic-pci.txt in the kernel
@@ -332,6 +355,9 @@
     fdt.property_array_u32("interrupt-map", &interrupts)?;
     fdt.property_array_u32("interrupt-map-mask", &masks)?;
     fdt.property_null("dma-coherent")?;
+    if let Some(dma_pool_phandle) = dma_pool_phandle {
+        fdt.property_u32("memory-region", dma_pool_phandle)?;
+    }
     fdt.end_node(pci_node)?;
 
     Ok(())
@@ -398,6 +424,7 @@
     is_gicv3: bool,
     use_pmu: bool,
     psci_version: PsciVersion,
+    swiotlb: Option<u64>,
 ) -> Result<()> {
     let mut fdt = FdtWriter::new(&[]);
 
@@ -412,6 +439,7 @@
     }
     create_chosen_node(&mut fdt, cmdline, initrd)?;
     create_memory_node(&mut fdt, guest_mem)?;
+    let dma_pool_phandle = create_resv_memory_node(&mut fdt, swiotlb)?;
     create_cpu_nodes(&mut fdt, num_cpus, cpu_clusters, cpu_capacity)?;
     create_gic_node(&mut fdt, is_gicv3, num_cpus as u64)?;
     create_timer_node(&mut fdt, num_cpus)?;
@@ -420,7 +448,13 @@
     }
     create_serial_nodes(&mut fdt)?;
     create_psci_node(&mut fdt, &psci_version)?;
-    create_pci_nodes(&mut fdt, pci_irqs, pci_device_base, pci_device_size)?;
+    create_pci_nodes(
+        &mut fdt,
+        pci_irqs,
+        pci_device_base,
+        pci_device_size,
+        dma_pool_phandle,
+    )?;
     create_rtc_node(&mut fdt)?;
     // End giant node
     fdt.end_node(root_node)?;
diff --git a/aarch64/src/lib.rs b/aarch64/src/lib.rs
index b0c7e9b..4b13558 100644
--- a/aarch64/src/lib.rs
+++ b/aarch64/src/lib.rs
@@ -248,6 +248,10 @@
         Ok(arch_memory_regions(components.memory_size))
     }
 
+    fn get_phys_max_addr() -> u64 {
+        u64::max_value()
+    }
+
     fn create_system_allocator(guest_mem: &GuestMemory) -> SystemAllocator {
         Self::get_resource_allocator(guest_mem.memory_size())
     }
@@ -274,14 +278,6 @@
 
         let mem = vm.get_memory().clone();
 
-        if components.protected_vm == ProtectionType::Protected {
-            vm.enable_protected_vm(
-                GuestAddress(AARCH64_PROTECTED_VM_FW_START),
-                AARCH64_PROTECTED_VM_FW_MAX_SIZE,
-            )
-            .map_err(Error::ProtectVm)?;
-        }
-
         let mut use_pmu = vm
             .get_hypervisor()
             .check_capability(&HypervisorCap::ArmPmuV3);
@@ -323,6 +319,14 @@
             .map_err(Error::MapPvtimeError)?;
         }
 
+        if components.protected_vm == ProtectionType::Protected {
+            vm.enable_protected_vm(
+                GuestAddress(AARCH64_PROTECTED_VM_FW_START),
+                AARCH64_PROTECTED_VM_FW_MAX_SIZE,
+            )
+            .map_err(Error::ProtectVm)?;
+        }
+
         for (vcpu_id, vcpu) in vcpus.iter().enumerate() {
             use_pmu &= vcpu.init_pmu(AARCH64_PMU_IRQ as u64 + 16).is_ok();
             if has_pvtime {
@@ -435,6 +439,7 @@
             irq_chip.get_vgic_version() == DeviceKind::ArmVgicV3,
             use_pmu,
             psci_version,
+            components.swiotlb,
         )
         .map_err(Error::CreateFdt)?;
 
@@ -451,7 +456,9 @@
             pid_debug_label_map,
             suspend_evt,
             rt_cpus: components.rt_cpus,
+            delay_rt: components.delay_rt,
             bat_control: None,
+            resume_notify_devices: Vec::new(),
         })
     }
 
diff --git a/acpi_tables/Android.bp b/acpi_tables/Android.bp
index 8b035df..8a16674 100644
--- a/acpi_tables/Android.bp
+++ b/acpi_tables/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
 // Do not modify this file as changes will be overridden on upgrade.
 
 package {
@@ -11,7 +11,7 @@
 }
 
 rust_defaults {
-    name: "acpi_tables_defaults",
+    name: "acpi_tables_test_defaults",
     defaults: ["crosvm_defaults"],
     crate_name: "acpi_tables",
     srcs: ["src/lib.rs"],
@@ -26,7 +26,7 @@
 
 rust_test_host {
     name: "acpi_tables_host_test_src_lib",
-    defaults: ["acpi_tables_defaults"],
+    defaults: ["acpi_tables_test_defaults"],
     test_options: {
         unit_test: true,
     },
@@ -34,7 +34,7 @@
 
 rust_test {
     name: "acpi_tables_device_test_src_lib",
-    defaults: ["acpi_tables_defaults"],
+    defaults: ["acpi_tables_test_defaults"],
 }
 
 rust_library {
@@ -49,15 +49,3 @@
         "libtempfile",
     ],
 }
-
-// dependent_library ["feature_list"]
-//   ../assertions/src/lib.rs
-//   ../data_model/src/lib.rs
-//   ../tempfile/src/lib.rs
-//   libc-0.2.97 "default,std"
-//   proc-macro2-1.0.27 "default,proc-macro"
-//   quote-1.0.9 "default,proc-macro"
-//   serde-1.0.126 "default,derive,serde_derive,std"
-//   serde_derive-1.0.126 "default"
-//   syn-1.0.73 "clone-impls,default,derive,parsing,printing,proc-macro,quote"
-//   unicode-xid-0.2.2 "default"
diff --git a/all2android.sh b/all2android.sh
index 1ab3f1d..f724ef2 100755
--- a/all2android.sh
+++ b/all2android.sh
@@ -6,15 +6,21 @@
 set -e
 
 cargo2android() {
-  cargo2android.py --run --device --tests --dependencies $@
-  rm -r cargo.out
+  # Some crates need special options to cargo2android.py, if there's a config file then use it.
+  if [[ -f "cargo2android.json" ]]; then
+    cargo2android.py --config cargo2android.json
+  else
+    cargo2android.py --run --device --tests $@
+  fi
+  rm -f cargo.out
   rm -rf target.tmp || /bin/true
 }
 
 # Run in the main crosvm directory.
 cargo2android --no-subdir
 
-for dir in */src
+initial_dir=`pwd`
+for dir in */src common/*/src
 do
   base=`dirname $dir`
   echo "$base"
@@ -37,5 +43,5 @@
     cargo2android --global_defaults=crosvm_defaults --add_workspace
   fi
 
-  cd ..
+  cd "$initial_dir"
 done
diff --git a/arch/Android.bp b/arch/Android.bp
index 01d23bb..b688c03 100644
--- a/arch/Android.bp
+++ b/arch/Android.bp
@@ -1,5 +1,5 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace --features=gdb.
-// NOTE: The --features=gdb should be applied only to the host (not the device) and there are inline changes to achieve this
+// This file is generated by cargo2android.py --config cargo2android.json.
+// Do not modify this file as changes will be overridden on upgrade.
 
 package {
     // See: http://go/android-license-faq
@@ -11,24 +11,13 @@
 }
 
 rust_defaults {
-    name: "arch_defaults",
+    name: "arch_test_defaults",
     defaults: ["crosvm_defaults"],
     crate_name: "arch",
     srcs: ["src/lib.rs"],
     test_suites: ["general-tests"],
     auto_gen_config: true,
     edition: "2018",
-    target: {
-        linux_glibc_x86_64: {
-            features: [
-                "gdb",
-                "gdbstub",
-            ],
-            rustlibs: [
-                "libgdbstub",
-            ],
-        },
-    },
     rustlibs: [
         "libacpi_tables",
         "libbase_rust",
@@ -44,22 +33,39 @@
         "libvm_control",
         "libvm_memory",
     ],
-    shared_libs: [
-        "libfdt",  // added manually
-    ],
 }
 
 rust_test_host {
     name: "arch_host_test_src_lib",
-    defaults: ["arch_defaults"],
+    defaults: ["arch_test_defaults"],
     test_options: {
         unit_test: true,
     },
+    target: {
+        linux_glibc_x86_64: {
+            features: [
+                "gdb",
+            ],
+            rustlibs: [
+                "libgdbstub_arch",
+            ],
+        },
+    },
 }
 
 rust_test {
     name: "arch_device_test_src_lib",
-    defaults: ["arch_defaults"],
+    defaults: ["arch_test_defaults"],
+    target: {
+        linux_glibc_x86_64: {
+            features: [
+                "gdb",
+            ],
+            rustlibs: [
+                "libgdbstub_arch",
+            ],
+        },
+    },
 }
 
 rust_library {
@@ -69,17 +75,6 @@
     crate_name: "arch",
     srcs: ["src/lib.rs"],
     edition: "2018",
-    target: {
-        linux_glibc_x86_64: {
-            features: [
-                "gdb",
-                "gdbstub",
-            ],
-            rustlibs: [
-                "libgdbstub",
-            ],
-        },
-    },
     rustlibs: [
         "libacpi_tables",
         "libbase_rust",
@@ -95,96 +90,14 @@
         "libvm_control",
         "libvm_memory",
     ],
-    shared_libs: [
-        "libfdt",  // added manually
-    ],
+    target: {
+        linux_glibc_x86_64: {
+            features: [
+                "gdb",
+            ],
+            rustlibs: [
+                "libgdbstub_arch",
+            ],
+        },
+    },
 }
-
-// dependent_library ["feature_list"]
-//   ../../adhd/audio_streams/src/audio_streams.rs
-//   ../../adhd/cras/client/cras-sys/src/lib.rs
-//   ../../adhd/cras/client/libcras/src/libcras.rs
-//   ../../libchromeos-rs/src/lib.rs
-//   ../../minijail/rust/minijail-sys/lib.rs
-//   ../../minijail/rust/minijail/src/lib.rs
-//   ../../rust/vmm_vhost/src/lib.rs "default,vhost-user,vhost-user-master"
-//   ../../vm_tools/p9/src/lib.rs
-//   ../../vm_tools/p9/wire_format_derive/wire_format_derive.rs
-//   ../acpi_tables/src/lib.rs
-//   ../assertions/src/lib.rs
-//   ../base/src/lib.rs
-//   ../bit_field/bit_field_derive/bit_field_derive.rs
-//   ../bit_field/src/lib.rs
-//   ../cros_async/src/lib.rs
-//   ../data_model/src/lib.rs
-//   ../devices/src/lib.rs
-//   ../disk/src/disk.rs
-//   ../enumn/src/lib.rs
-//   ../fuse/src/lib.rs
-//   ../hypervisor/src/lib.rs
-//   ../io_uring/src/lib.rs
-//   ../kernel_cmdline/src/kernel_cmdline.rs
-//   ../kvm/src/lib.rs
-//   ../kvm_sys/src/lib.rs
-//   ../linux_input_sys/src/lib.rs
-//   ../net_sys/src/lib.rs
-//   ../net_util/src/lib.rs
-//   ../power_monitor/src/lib.rs
-//   ../rand_ish/src/lib.rs
-//   ../resources/src/lib.rs
-//   ../rutabaga_gfx/src/lib.rs
-//   ../sync/src/lib.rs
-//   ../sys_util/poll_token_derive/poll_token_derive.rs
-//   ../sys_util/src/lib.rs
-//   ../tempfile/src/lib.rs
-//   ../usb_sys/src/lib.rs
-//   ../usb_util/src/lib.rs
-//   ../vfio_sys/src/lib.rs
-//   ../vhost/src/lib.rs
-//   ../virtio_sys/src/lib.rs
-//   ../vm_control/src/lib.rs
-//   ../vm_memory/src/lib.rs
-//   async-task-4.0.3 "default,std"
-//   async-trait-0.1.50
-//   autocfg-1.0.1
-//   bitflags-1.2.1 "default"
-//   cfg-if-1.0.0
-//   downcast-rs-1.2.0 "default,std"
-//   futures-0.3.15 "alloc,std"
-//   futures-channel-0.3.15 "alloc,futures-sink,sink,std"
-//   futures-core-0.3.15 "alloc,std"
-//   futures-io-0.3.15 "std"
-//   futures-sink-0.3.15 "alloc,std"
-//   futures-task-0.3.15 "alloc,std"
-//   futures-util-0.3.15 "alloc,channel,futures-channel,futures-io,futures-sink,io,memchr,sink,slab,std"
-//   getrandom-0.2.3 "std"
-//   intrusive-collections-0.9.1 "alloc,default"
-//   itoa-0.4.7
-//   libc-0.2.97 "default,std"
-//   log-0.4.14
-//   memchr-2.4.0 "default,std"
-//   memoffset-0.5.6 "default"
-//   paste-1.0.5
-//   pin-project-lite-0.2.7
-//   pin-utils-0.1.0
-//   pkg-config-0.3.19
-//   ppv-lite86-0.2.10 "simd,std"
-//   proc-macro2-1.0.27 "default,proc-macro"
-//   protobuf-2.24.1
-//   quote-1.0.9 "default,proc-macro"
-//   rand-0.8.4 "alloc,default,getrandom,libc,rand_chacha,rand_hc,std,std_rng"
-//   rand_chacha-0.3.1 "std"
-//   rand_core-0.6.3 "alloc,getrandom,std"
-//   remain-0.2.2
-//   remove_dir_all-0.5.3
-//   ryu-1.0.5
-//   serde-1.0.126 "default,derive,serde_derive,std"
-//   serde_derive-1.0.126 "default"
-//   serde_json-1.0.64 "default,std"
-//   slab-0.4.3 "default,std"
-//   smallvec-1.6.1
-//   syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-//   tempfile-3.2.0
-//   thiserror-1.0.25
-//   thiserror-impl-1.0.25
-//   unicode-xid-0.2.2 "default"
diff --git a/arch/Cargo.toml b/arch/Cargo.toml
index 095cc48..728e7fe 100644
--- a/arch/Cargo.toml
+++ b/arch/Cargo.toml
@@ -6,12 +6,12 @@
 
 [features]
 power-monitor-powerd = ["power_monitor/powerd"]
-gdb = ["gdbstub"]
+gdb = ["gdbstub_arch"]
 
 [dependencies]
 acpi_tables = { path = "../acpi_tables" }
 devices = { path = "../devices" }
-gdbstub = { version = "0.4.0", optional = true }
+gdbstub_arch = { version = "0.1.0", optional = true }
 hypervisor = { path = "../hypervisor" }
 kernel_cmdline = { path = "../kernel_cmdline" }
 libc = "*"
diff --git a/arch/cargo2android.json b/arch/cargo2android.json
new file mode 100644
index 0000000..2ae8879
--- /dev/null
+++ b/arch/cargo2android.json
@@ -0,0 +1,8 @@
+{
+  "add-module-block": "cargo2android_gdb.bp",
+  "add_workspace": true,
+  "device": true,
+  "global_defaults": "crosvm_defaults",
+  "run": true,
+  "tests": true
+}
\ No newline at end of file
diff --git a/arch/cargo2android_gdb.bp b/arch/cargo2android_gdb.bp
new file mode 100644
index 0000000..0787bb5
--- /dev/null
+++ b/arch/cargo2android_gdb.bp
@@ -0,0 +1,10 @@
+target: {
+    linux_glibc_x86_64: {
+        features: [
+            "gdb",
+        ],
+        rustlibs: [
+            "libgdbstub_arch",
+        ],
+    },
+}
\ No newline at end of file
diff --git a/arch/src/lib.rs b/arch/src/lib.rs
index e11c485..f900872 100644
--- a/arch/src/lib.rs
+++ b/arch/src/lib.rs
@@ -17,11 +17,11 @@
 
 use acpi_tables::aml::Aml;
 use acpi_tables::sdt::SDT;
-use base::{syslog, AsRawDescriptor, Event, Tube};
+use base::{syslog, AsRawDescriptor, AsRawDescriptors, Event, Tube};
 use devices::virtio::VirtioDevice;
 use devices::{
-    Bus, BusDevice, BusError, IrqChip, PciAddress, PciDevice, PciDeviceError, PciInterruptPin,
-    PciRoot, ProtectionType, ProxyDevice,
+    Bus, BusDevice, BusError, BusResumeDevice, IrqChip, PciAddress, PciDevice, PciDeviceError,
+    PciInterruptPin, PciRoot, ProtectionType, ProxyDevice,
 };
 use hypervisor::{IoEventAddress, Vm};
 use minijail::Minijail;
@@ -31,7 +31,7 @@
 use vm_memory::{GuestAddress, GuestMemory, GuestMemoryError};
 
 #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
-use gdbstub::arch::x86::reg::X86_64CoreRegs as GdbStubRegs;
+use gdbstub_arch::x86::reg::X86_64CoreRegs as GdbStubRegs;
 
 #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
 use {
@@ -76,6 +76,7 @@
 /// create a `RunnableLinuxVm`.
 pub struct VmComponents {
     pub memory_size: u64,
+    pub swiotlb: Option<u64>,
     pub vcpu_count: usize,
     pub vcpu_affinity: Option<VcpuAffinity>,
     pub cpu_clusters: Vec<Vec<usize>>,
@@ -87,9 +88,9 @@
     pub pstore: Option<Pstore>,
     pub initrd_image: Option<File>,
     pub extra_kernel_params: Vec<String>,
-    pub wayland_dmabuf: bool,
     pub acpi_sdts: Vec<SDT>,
     pub rt_cpus: Vec<usize>,
+    pub delay_rt: bool,
     pub protected_vm: ProtectionType,
     #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
     pub gdb: Option<(u32, Tube)>, // port and control tube.
@@ -113,9 +114,12 @@
     pub pid_debug_label_map: BTreeMap<u32, String>,
     pub suspend_evt: Event,
     pub rt_cpus: Vec<usize>,
+    pub delay_rt: bool,
     pub bat_control: Option<BatControl>,
     #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
     pub gdb: Option<(u32, Tube)>,
+    /// Devices to be notified before the system resumes from the S3 suspended state.
+    pub resume_notify_devices: Vec<Arc<Mutex<dyn BusResumeDevice>>>,
 }
 
 /// The device and optional jail.
@@ -146,6 +150,8 @@
     /// * `guest_mem` - The memory to be used as a template for the `SystemAllocator`.
     fn create_system_allocator(guest_mem: &GuestMemory) -> SystemAllocator;
 
+    fn get_phys_max_addr() -> u64;
+
     /// Takes `VmComponents` and generates a `RunnableLinuxVm`.
     ///
     /// # Arguments
@@ -365,6 +371,7 @@
         let address = device_addrs[dev_idx];
         let mut keep_rds = device.keep_rds();
         syslog::push_descriptors(&mut keep_rds);
+        keep_rds.append(&mut vm.get_memory().as_raw_descriptors());
 
         let irqfd = Event::new().map_err(DeviceRegistrationError::EventCreate)?;
         let irq_resample_fd = Event::new().map_err(DeviceRegistrationError::EventCreate)?;
diff --git a/assertions/Android.bp b/assertions/Android.bp
index 5692b59..734a4d4 100644
--- a/assertions/Android.bp
+++ b/assertions/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
 // Do not modify this file as changes will be overridden on upgrade.
 
 package {
@@ -11,7 +11,7 @@
 }
 
 rust_defaults {
-    name: "assertions_defaults",
+    name: "assertions_test_defaults",
     defaults: ["crosvm_defaults"],
     crate_name: "assertions",
     srcs: ["src/lib.rs"],
@@ -22,7 +22,7 @@
 
 rust_test_host {
     name: "assertions_host_test_src_lib",
-    defaults: ["assertions_defaults"],
+    defaults: ["assertions_test_defaults"],
     test_options: {
         unit_test: true,
     },
@@ -30,7 +30,7 @@
 
 rust_test {
     name: "assertions_device_test_src_lib",
-    defaults: ["assertions_defaults"],
+    defaults: ["assertions_test_defaults"],
 }
 
 rust_library {
diff --git a/audio_streams/Android.bp b/audio_streams/Android.bp
new file mode 100644
index 0000000..0268069
--- /dev/null
+++ b/audio_streams/Android.bp
@@ -0,0 +1,55 @@
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
+// Do not modify this file as changes will be overridden on upgrade.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "external_crosvm_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-BSD
+    default_applicable_licenses: ["external_crosvm_license"],
+}
+
+rust_defaults {
+    name: "audio_streams_test_defaults",
+    defaults: ["crosvm_defaults"],
+    crate_name: "audio_streams",
+    srcs: ["src/audio_streams.rs"],
+    test_suites: ["general-tests"],
+    auto_gen_config: true,
+    edition: "2018",
+    rustlibs: [
+        "libcros_async",
+        "libsync_rust",
+        "libsys_util",
+    ],
+    proc_macros: ["libasync_trait"],
+}
+
+rust_test_host {
+    name: "audio_streams_host_test_src_audio_streams",
+    defaults: ["audio_streams_test_defaults"],
+    test_options: {
+        unit_test: true,
+    },
+}
+
+rust_test {
+    name: "audio_streams_device_test_src_audio_streams",
+    defaults: ["audio_streams_test_defaults"],
+}
+
+rust_library {
+    name: "libaudio_streams",
+    defaults: ["crosvm_defaults"],
+    host_supported: true,
+    crate_name: "audio_streams",
+    srcs: ["src/audio_streams.rs"],
+    edition: "2018",
+    rustlibs: [
+        "libcros_async",
+        "libsync_rust",
+        "libsys_util",
+    ],
+    proc_macros: ["libasync_trait"],
+}
diff --git a/audio_streams/Cargo.toml b/audio_streams/Cargo.toml
new file mode 100644
index 0000000..49160a8
--- /dev/null
+++ b/audio_streams/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "audio_streams"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+edition = "2018"
+
+[lib]
+path = "src/audio_streams.rs"
+
+[dependencies]
+async-trait = "0.1.36"
+cros_async = { path = "../cros_async" } # provided by ebuild
+sync = { path = "../sync" } # provided by ebuild
+sys_util = { path = "../sys_util" } # provided by ebuild
diff --git a/audio_streams/README.md b/audio_streams/README.md
new file mode 100644
index 0000000..d3a02e8
--- /dev/null
+++ b/audio_streams/README.md
@@ -0,0 +1,6 @@
+# Audio Server and Stream interfaces
+
+The `audio_streams` crate provides a basic interface for playing audio.
+This will be used to enable playback to various audio subsystems such as
+Alsa and cras. To start, an empty playback example `NoopStreamSource`
+is provided.
diff --git a/audio_streams/src/audio_streams.rs b/audio_streams/src/audio_streams.rs
new file mode 100644
index 0000000..b125196
--- /dev/null
+++ b/audio_streams/src/audio_streams.rs
@@ -0,0 +1,838 @@
+// Copyright 2019 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.
+
+//! Provides an interface for playing and recording audio.
+//!
+//! When implementing an audio playback system, the `StreamSource` trait is implemented.
+//! Implementors of this trait allow creation of `PlaybackBufferStream` objects. The
+//! `PlaybackBufferStream` provides the actual audio buffers to be filled with audio samples. These
+//! buffers can be filled with `write_playback_buffer`.
+//!
+//! Users playing audio fill the provided buffers with audio. When a `PlaybackBuffer` is dropped,
+//! the samples written to it are committed to the `PlaybackBufferStream` it came from.
+//!
+//! ```
+//! use audio_streams::{BoxError, PlaybackBuffer, SampleFormat, StreamSource, NoopStreamSource};
+//! use std::io::Write;
+//!
+//! const buffer_size: usize = 120;
+//! const num_channels: usize = 2;
+//!
+//! # fn main() -> std::result::Result<(), BoxError> {
+//! let mut stream_source = NoopStreamSource::new();
+//! let sample_format = SampleFormat::S16LE;
+//! let frame_size = num_channels * sample_format.sample_bytes();
+//!
+//! let (_, mut stream) = stream_source
+//!     .new_playback_stream(num_channels, sample_format, 48000, buffer_size)?;
+//! // Play 10 buffers of DC.
+//! let mut buf = Vec::new();
+//! buf.resize(buffer_size * frame_size, 0xa5u8);
+//! for _ in 0..10 {
+//!     let mut copy_cb = |stream_buffer: &mut PlaybackBuffer| {
+//!         assert_eq!(stream_buffer.write(&buf)?, buffer_size * frame_size);
+//!         Ok(())
+//!     };
+//!     stream.write_playback_buffer(&mut copy_cb)?;
+//! }
+//! # Ok (())
+//! # }
+//! ```
+
+use async_trait::async_trait;
+use std::cmp::min;
+use std::error;
+use std::fmt::{self, Display};
+use std::io::{self, Read, Write};
+use std::os::unix::io::RawFd;
+use std::result::Result;
+use std::str::FromStr;
+use std::time::{Duration, Instant};
+
+use cros_async::{Executor, TimerAsync};
+
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub enum SampleFormat {
+    U8,
+    S16LE,
+    S24LE,
+    S32LE,
+}
+
+impl SampleFormat {
+    pub fn sample_bytes(self) -> usize {
+        use SampleFormat::*;
+        match self {
+            U8 => 1,
+            S16LE => 2,
+            S24LE => 4, // Not a typo, S24_LE samples are stored in 4 byte chunks.
+            S32LE => 4,
+        }
+    }
+}
+
+impl Display for SampleFormat {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        use SampleFormat::*;
+        match self {
+            U8 => write!(f, "Unsigned 8 bit"),
+            S16LE => write!(f, "Signed 16 bit Little Endian"),
+            S24LE => write!(f, "Signed 24 bit Little Endian"),
+            S32LE => write!(f, "Signed 32 bit Little Endian"),
+        }
+    }
+}
+
+/// Valid directions of an audio stream.
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub enum StreamDirection {
+    Playback,
+    Capture,
+}
+
+/// Valid effects for an audio stream.
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub enum StreamEffect {
+    NoEffect,
+    EchoCancellation,
+}
+
+pub mod capture;
+pub mod shm_streams;
+
+impl Default for StreamEffect {
+    fn default() -> Self {
+        StreamEffect::NoEffect
+    }
+}
+
+/// Errors that can pass across threads.
+pub type BoxError = Box<dyn error::Error + Send + Sync>;
+
+/// Errors that are possible from a `StreamEffect`.
+#[derive(Debug)]
+pub enum StreamEffectError {
+    InvalidEffect,
+}
+
+impl error::Error for StreamEffectError {}
+
+impl Display for StreamEffectError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            StreamEffectError::InvalidEffect => write!(f, "Must be in [EchoCancellation, aec]"),
+        }
+    }
+}
+
+impl FromStr for StreamEffect {
+    type Err = StreamEffectError;
+    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
+        match s {
+            "EchoCancellation" | "aec" => Ok(StreamEffect::EchoCancellation),
+            _ => Err(StreamEffectError::InvalidEffect),
+        }
+    }
+}
+
+#[derive(Debug)]
+pub enum Error {
+    Unimplemented,
+}
+
+impl error::Error for Error {}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            Error::Unimplemented => write!(f, "Unimplemented"),
+        }
+    }
+}
+
+/// `StreamSource` creates streams for playback or capture of audio.
+pub trait StreamSource: Send {
+    /// Returns a stream control and buffer generator object. These are separate as the buffer
+    /// generator might want to be passed to the audio stream.
+    #[allow(clippy::type_complexity)]
+    fn new_playback_stream(
+        &mut self,
+        num_channels: usize,
+        format: SampleFormat,
+        frame_rate: u32,
+        buffer_size: usize,
+    ) -> Result<(Box<dyn StreamControl>, Box<dyn PlaybackBufferStream>), BoxError>;
+
+    /// Returns a stream control and async buffer generator object. These are separate as the buffer
+    /// generator might want to be passed to the audio stream.
+    #[allow(clippy::type_complexity)]
+    fn new_async_playback_stream(
+        &mut self,
+        _num_channels: usize,
+        _format: SampleFormat,
+        _frame_rate: u32,
+        _buffer_size: usize,
+        _ex: &Executor,
+    ) -> Result<(Box<dyn StreamControl>, Box<dyn AsyncPlaybackBufferStream>), BoxError> {
+        Err(Box::new(Error::Unimplemented))
+    }
+
+    /// Returns a stream control and buffer generator object. These are separate as the buffer
+    /// generator might want to be passed to the audio stream.
+    /// Default implementation returns `NoopStreamControl` and `NoopCaptureStream`.
+    #[allow(clippy::type_complexity)]
+    fn new_capture_stream(
+        &mut self,
+        num_channels: usize,
+        format: SampleFormat,
+        frame_rate: u32,
+        buffer_size: usize,
+    ) -> Result<
+        (
+            Box<dyn StreamControl>,
+            Box<dyn capture::CaptureBufferStream>,
+        ),
+        BoxError,
+    > {
+        Ok((
+            Box::new(NoopStreamControl::new()),
+            Box::new(capture::NoopCaptureStream::new(
+                num_channels,
+                format,
+                frame_rate,
+                buffer_size,
+            )),
+        ))
+    }
+
+    /// Returns a stream control and async buffer generator object. These are separate as the buffer
+    /// generator might want to be passed to the audio stream.
+    /// Default implementation returns `NoopStreamControl` and `NoopCaptureStream`.
+    #[allow(clippy::type_complexity)]
+    fn new_async_capture_stream(
+        &mut self,
+        num_channels: usize,
+        format: SampleFormat,
+        frame_rate: u32,
+        buffer_size: usize,
+        _ex: &Executor,
+    ) -> Result<
+        (
+            Box<dyn StreamControl>,
+            Box<dyn capture::AsyncCaptureBufferStream>,
+        ),
+        BoxError,
+    > {
+        Ok((
+            Box::new(NoopStreamControl::new()),
+            Box::new(capture::NoopCaptureStream::new(
+                num_channels,
+                format,
+                frame_rate,
+                buffer_size,
+            )),
+        ))
+    }
+
+    /// Returns any open file descriptors needed by the implementor. The FD list helps users of the
+    /// StreamSource enter Linux jails making sure not to close needed FDs.
+    fn keep_fds(&self) -> Option<Vec<RawFd>> {
+        None
+    }
+}
+
+/// `PlaybackBufferStream` provides `PlaybackBuffer`s to fill with audio samples for playback.
+pub trait PlaybackBufferStream: Send {
+    fn next_playback_buffer<'b, 's: 'b>(&'s mut self) -> Result<PlaybackBuffer<'b>, BoxError>;
+
+    /// Call `f` with a `PlaybackBuffer`, and trigger the buffer done call back after. `f` should
+    /// write playback data to the given `PlaybackBuffer`.
+    fn write_playback_buffer<'b, 's: 'b>(
+        &'s mut self,
+        f: &mut dyn FnMut(&mut PlaybackBuffer<'b>) -> Result<(), BoxError>,
+    ) -> Result<(), BoxError> {
+        let mut buf = self.next_playback_buffer()?;
+        f(&mut buf)?;
+        buf.commit();
+        Ok(())
+    }
+}
+
+impl<S: PlaybackBufferStream + ?Sized> PlaybackBufferStream for &mut S {
+    fn next_playback_buffer<'b, 's: 'b>(&'s mut self) -> Result<PlaybackBuffer<'b>, BoxError> {
+        (**self).next_playback_buffer()
+    }
+}
+
+/// `PlaybackBufferStream` provides `PlaybackBuffer`s asynchronously to fill with audio samples for
+/// playback.
+#[async_trait(?Send)]
+pub trait AsyncPlaybackBufferStream: Send {
+    async fn next_playback_buffer<'a>(
+        &'a mut self,
+        _ex: &Executor,
+    ) -> Result<AsyncPlaybackBuffer<'a>, BoxError>;
+}
+
+#[async_trait(?Send)]
+impl<S: AsyncPlaybackBufferStream + ?Sized> AsyncPlaybackBufferStream for &mut S {
+    async fn next_playback_buffer<'a>(
+        &'a mut self,
+        ex: &Executor,
+    ) -> Result<AsyncPlaybackBuffer<'a>, BoxError> {
+        (**self).next_playback_buffer(ex).await
+    }
+}
+
+/// Call `f` with a `AsyncPlaybackBuffer`, and trigger the buffer done call back after. `f` should
+/// write playback data to the given `AsyncPlaybackBuffer`.
+///
+/// This cannot be a trait method because trait methods with generic parameters are not object safe.
+pub async fn async_write_playback_buffer<F>(
+    stream: &mut dyn AsyncPlaybackBufferStream,
+    f: F,
+    ex: &Executor,
+) -> Result<(), BoxError>
+where
+    F: for<'a> FnOnce(&'a mut AsyncPlaybackBuffer) -> Result<(), BoxError>,
+{
+    let mut buf = stream.next_playback_buffer(ex).await?;
+    f(&mut buf)?;
+    buf.commit().await;
+    Ok(())
+}
+
+/// `StreamControl` provides a way to set the volume and mute states of a stream. `StreamControl`
+/// is separate from the stream so it can be owned by a different thread if needed.
+pub trait StreamControl: Send + Sync {
+    fn set_volume(&mut self, _scaler: f64) {}
+    fn set_mute(&mut self, _mute: bool) {}
+}
+
+/// `BufferCommit` is a cleanup funcion that must be called before dropping the buffer,
+/// allowing arbitrary code to be run after the buffer is filled or read by the user.
+pub trait BufferCommit {
+    /// `write_playback_buffer` or `read_capture_buffer` would trigger this automatically. `nframes`
+    /// indicates the number of audio frames that were read or written to the device.
+    fn commit(&mut self, nframes: usize);
+}
+
+/// `AsyncBufferCommit` is a cleanup funcion that must be called before dropping the buffer,
+/// allowing arbitrary code to be run after the buffer is filled or read by the user.
+#[async_trait(?Send)]
+pub trait AsyncBufferCommit {
+    /// `async_write_playback_buffer` or `async_read_capture_buffer` would trigger this
+    /// automatically. `nframes` indicates the number of audio frames that were read or written to
+    /// the device.
+    async fn commit(&mut self, nframes: usize);
+}
+
+/// Errors that are possible from a `PlaybackBuffer`.
+#[derive(Debug)]
+pub enum PlaybackBufferError {
+    InvalidLength,
+}
+
+impl error::Error for PlaybackBufferError {}
+
+impl Display for PlaybackBufferError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            PlaybackBufferError::InvalidLength => write!(f, "Invalid buffer length"),
+        }
+    }
+}
+
+/// `AudioBuffer` is one buffer that holds buffer_size audio frames.
+/// It is the inner data of `PlaybackBuffer` and `CaptureBuffer`.
+struct AudioBuffer<'a> {
+    buffer: &'a mut [u8],
+    offset: usize,     // Read or Write offset in frames.
+    frame_size: usize, // Size of a frame in bytes.
+}
+
+impl<'a> AudioBuffer<'a> {
+    /// Returns the number of audio frames that fit in the buffer.
+    pub fn frame_capacity(&self) -> usize {
+        self.buffer.len() / self.frame_size
+    }
+
+    fn calc_len(&self, size: usize) -> usize {
+        min(
+            size / self.frame_size * self.frame_size,
+            self.buffer.len() - self.offset,
+        )
+    }
+
+    /// Writes up to `size` bytes directly to this buffer inside of the given callback function.
+    pub fn write_copy_cb<F: FnOnce(&mut [u8])>(&mut self, size: usize, cb: F) -> io::Result<usize> {
+        // only write complete frames.
+        let len = self.calc_len(size);
+        cb(&mut self.buffer[self.offset..(self.offset + len)]);
+        self.offset += len;
+        Ok(len)
+    }
+
+    /// Writes complete frames to `buf`, and return the number of bytes written.
+    pub fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        // only write complete frames.
+        let len = buf.len() / self.frame_size * self.frame_size;
+        let written = (&mut self.buffer[self.offset..]).write(&buf[..len])?;
+        self.offset += written;
+        Ok(written)
+    }
+
+    /// Reads up to `size` bytes directly from this buffer inside of the given callback function.
+    pub fn read_copy_cb<F: FnOnce(&[u8])>(&mut self, size: usize, cb: F) -> io::Result<usize> {
+        let len = self.calc_len(size);
+        cb(&self.buffer[self.offset..(self.offset + len)]);
+        self.offset += len;
+        Ok(len)
+    }
+}
+
+impl<'a> Write for AudioBuffer<'a> {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        // only write complete frames.
+        let len = buf.len() / self.frame_size * self.frame_size;
+        let written = (&mut self.buffer[self.offset..]).write(&buf[..len])?;
+        self.offset += written;
+        Ok(written)
+    }
+
+    fn flush(&mut self) -> io::Result<()> {
+        Ok(())
+    }
+}
+
+impl<'a> Read for AudioBuffer<'a> {
+    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+        let len = buf.len() / self.frame_size * self.frame_size;
+        let written = (&mut buf[..len]).write(&self.buffer[self.offset..])?;
+        self.offset += written;
+        Ok(written)
+    }
+}
+
+/// `PlaybackBuffer` is one buffer that holds buffer_size audio frames. It is used to temporarily
+/// allow access to an audio buffer and notifes the owning stream of write completion when dropped.
+pub struct PlaybackBuffer<'a> {
+    buffer: AudioBuffer<'a>,
+    drop: &'a mut dyn BufferCommit,
+}
+
+impl<'a> PlaybackBuffer<'a> {
+    /// Creates a new `PlaybackBuffer` that holds a reference to the backing memory specified in
+    /// `buffer`.
+    pub fn new<F>(
+        frame_size: usize,
+        buffer: &'a mut [u8],
+        drop: &'a mut F,
+    ) -> Result<Self, PlaybackBufferError>
+    where
+        F: BufferCommit,
+    {
+        if buffer.len() % frame_size != 0 {
+            return Err(PlaybackBufferError::InvalidLength);
+        }
+
+        Ok(PlaybackBuffer {
+            buffer: AudioBuffer {
+                buffer,
+                offset: 0,
+                frame_size,
+            },
+            drop,
+        })
+    }
+
+    /// Returns the number of audio frames that fit in the buffer.
+    pub fn frame_capacity(&self) -> usize {
+        self.buffer.frame_capacity()
+    }
+
+    /// This triggers the commit of `BufferCommit`. This should be called after the data is copied
+    /// to the buffer.
+    pub fn commit(&mut self) {
+        self.drop
+            .commit(self.buffer.offset / self.buffer.frame_size);
+    }
+
+    /// Writes up to `size` bytes directly to this buffer inside of the given callback function.
+    pub fn copy_cb<F: FnOnce(&mut [u8])>(&mut self, size: usize, cb: F) -> io::Result<usize> {
+        self.buffer.write_copy_cb(size, cb)
+    }
+}
+
+impl<'a> Write for PlaybackBuffer<'a> {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        self.buffer.write(buf)
+    }
+
+    fn flush(&mut self) -> io::Result<()> {
+        self.buffer.flush()
+    }
+}
+
+/// `AsyncPlaybackBuffer` is the async version of `PlaybackBuffer`.
+pub struct AsyncPlaybackBuffer<'a> {
+    buffer: AudioBuffer<'a>,
+    trigger: &'a mut dyn AsyncBufferCommit,
+}
+
+impl<'a> AsyncPlaybackBuffer<'a> {
+    /// Creates a new `AsyncPlaybackBuffer` that holds a reference to the backing memory specified
+    /// in `buffer`.
+    pub fn new<F>(
+        frame_size: usize,
+        buffer: &'a mut [u8],
+        trigger: &'a mut F,
+    ) -> Result<Self, PlaybackBufferError>
+    where
+        F: AsyncBufferCommit,
+    {
+        if buffer.len() % frame_size != 0 {
+            return Err(PlaybackBufferError::InvalidLength);
+        }
+
+        Ok(AsyncPlaybackBuffer {
+            buffer: AudioBuffer {
+                buffer,
+                offset: 0,
+                frame_size,
+            },
+            trigger,
+        })
+    }
+
+    /// Returns the number of audio frames that fit in the buffer.
+    pub fn frame_capacity(&self) -> usize {
+        self.buffer.frame_capacity()
+    }
+
+    /// This triggers the callback of `AsyncBufferCommit`. This should be called after the data is
+    /// copied to the buffer.
+    pub async fn commit(&mut self) {
+        self.trigger
+            .commit(self.buffer.offset / self.buffer.frame_size)
+            .await;
+    }
+
+    /// Writes up to `size` bytes directly to this buffer inside of the given callback function.
+    pub fn copy_cb<F: FnOnce(&mut [u8])>(&mut self, size: usize, cb: F) -> io::Result<usize> {
+        self.buffer.write_copy_cb(size, cb)
+    }
+}
+
+impl<'a> Write for AsyncPlaybackBuffer<'a> {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        self.buffer.write(buf)
+    }
+
+    fn flush(&mut self) -> io::Result<()> {
+        self.buffer.flush()
+    }
+}
+/// Stream that accepts playback samples but drops them.
+pub struct NoopStream {
+    buffer: Vec<u8>,
+    frame_size: usize,
+    interval: Duration,
+    next_frame: Duration,
+    start_time: Option<Instant>,
+    buffer_drop: NoopBufferCommit,
+}
+
+/// NoopStream data that is needed from the buffer complete callback.
+struct NoopBufferCommit {
+    which_buffer: bool,
+}
+
+impl BufferCommit for NoopBufferCommit {
+    fn commit(&mut self, _nwritten: usize) {
+        // When a buffer completes, switch to the other one.
+        self.which_buffer ^= true;
+    }
+}
+
+#[async_trait(?Send)]
+impl AsyncBufferCommit for NoopBufferCommit {
+    async fn commit(&mut self, _nwritten: usize) {
+        // When a buffer completes, switch to the other one.
+        self.which_buffer ^= true;
+    }
+}
+
+impl NoopStream {
+    pub fn new(
+        num_channels: usize,
+        format: SampleFormat,
+        frame_rate: u32,
+        buffer_size: usize,
+    ) -> Self {
+        let frame_size = format.sample_bytes() * num_channels;
+        let interval = Duration::from_millis(buffer_size as u64 * 1000 / frame_rate as u64);
+        NoopStream {
+            buffer: vec![0; buffer_size * frame_size],
+            frame_size,
+            interval,
+            next_frame: interval,
+            start_time: None,
+            buffer_drop: NoopBufferCommit {
+                which_buffer: false,
+            },
+        }
+    }
+}
+
+impl PlaybackBufferStream for NoopStream {
+    fn next_playback_buffer<'b, 's: 'b>(&'s mut self) -> Result<PlaybackBuffer<'b>, BoxError> {
+        if let Some(start_time) = self.start_time {
+            let elapsed = start_time.elapsed();
+            if elapsed < self.next_frame {
+                std::thread::sleep(self.next_frame - elapsed);
+            }
+            self.next_frame += self.interval;
+        } else {
+            self.start_time = Some(Instant::now());
+            self.next_frame = self.interval;
+        }
+        Ok(PlaybackBuffer::new(
+            self.frame_size,
+            &mut self.buffer,
+            &mut self.buffer_drop,
+        )?)
+    }
+}
+
+#[async_trait(?Send)]
+impl AsyncPlaybackBufferStream for NoopStream {
+    async fn next_playback_buffer<'a>(
+        &'a mut self,
+        ex: &Executor,
+    ) -> Result<AsyncPlaybackBuffer<'a>, BoxError> {
+        if let Some(start_time) = self.start_time {
+            let elapsed = start_time.elapsed();
+            if elapsed < self.next_frame {
+                TimerAsync::sleep(ex, self.next_frame - elapsed).await?;
+            }
+            self.next_frame += self.interval;
+        } else {
+            self.start_time = Some(Instant::now());
+            self.next_frame = self.interval;
+        }
+        Ok(AsyncPlaybackBuffer::new(
+            self.frame_size,
+            &mut self.buffer,
+            &mut self.buffer_drop,
+        )?)
+    }
+}
+
+/// No-op control for `NoopStream`s.
+#[derive(Default)]
+pub struct NoopStreamControl;
+
+impl NoopStreamControl {
+    pub fn new() -> Self {
+        NoopStreamControl {}
+    }
+}
+
+impl StreamControl for NoopStreamControl {}
+
+/// Source of `NoopStream` and `NoopStreamControl` objects.
+#[derive(Default)]
+pub struct NoopStreamSource;
+
+impl NoopStreamSource {
+    pub fn new() -> Self {
+        NoopStreamSource {}
+    }
+}
+
+impl StreamSource for NoopStreamSource {
+    #[allow(clippy::type_complexity)]
+    fn new_playback_stream(
+        &mut self,
+        num_channels: usize,
+        format: SampleFormat,
+        frame_rate: u32,
+        buffer_size: usize,
+    ) -> Result<(Box<dyn StreamControl>, Box<dyn PlaybackBufferStream>), BoxError> {
+        Ok((
+            Box::new(NoopStreamControl::new()),
+            Box::new(NoopStream::new(
+                num_channels,
+                format,
+                frame_rate,
+                buffer_size,
+            )),
+        ))
+    }
+
+    #[allow(clippy::type_complexity)]
+    fn new_async_playback_stream(
+        &mut self,
+        num_channels: usize,
+        format: SampleFormat,
+        frame_rate: u32,
+        buffer_size: usize,
+        _ex: &Executor,
+    ) -> Result<(Box<dyn StreamControl>, Box<dyn AsyncPlaybackBufferStream>), BoxError> {
+        Ok((
+            Box::new(NoopStreamControl::new()),
+            Box::new(NoopStream::new(
+                num_channels,
+                format,
+                frame_rate,
+                buffer_size,
+            )),
+        ))
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn invalid_buffer_length() {
+        // Playback buffers can't be created with a size that isn't divisible by the frame size.
+        let mut pb_buf = [0xa5u8; 480 * 2 * 2 + 1];
+        let mut buffer_drop = NoopBufferCommit {
+            which_buffer: false,
+        };
+        assert!(PlaybackBuffer::new(2, &mut pb_buf, &mut buffer_drop).is_err());
+    }
+
+    #[test]
+    fn commit() {
+        struct TestCommit {
+            frame_count: usize,
+        }
+        impl BufferCommit for TestCommit {
+            fn commit(&mut self, nwritten: usize) {
+                self.frame_count += nwritten;
+            }
+        }
+        let mut test_commit = TestCommit { frame_count: 0 };
+        {
+            const FRAME_SIZE: usize = 4;
+            let mut buf = [0u8; 480 * FRAME_SIZE];
+            let mut pb_buf = PlaybackBuffer::new(FRAME_SIZE, &mut buf, &mut test_commit).unwrap();
+            pb_buf.write_all(&[0xa5u8; 480 * FRAME_SIZE]).unwrap();
+            pb_buf.commit();
+        }
+        assert_eq!(test_commit.frame_count, 480);
+    }
+
+    #[test]
+    fn sixteen_bit_stereo() {
+        let mut server = NoopStreamSource::new();
+        let (_, mut stream) = server
+            .new_playback_stream(2, SampleFormat::S16LE, 48000, 480)
+            .unwrap();
+        let mut copy_cb = |buf: &mut PlaybackBuffer| {
+            assert_eq!(buf.buffer.frame_capacity(), 480);
+            let pb_buf = [0xa5u8; 480 * 2 * 2];
+            assert_eq!(buf.write(&pb_buf).unwrap(), 480 * 2 * 2);
+            Ok(())
+        };
+        stream.write_playback_buffer(&mut copy_cb).unwrap();
+    }
+
+    #[test]
+    fn consumption_rate() {
+        let mut server = NoopStreamSource::new();
+        let (_, mut stream) = server
+            .new_playback_stream(2, SampleFormat::S16LE, 48000, 480)
+            .unwrap();
+        let start = Instant::now();
+        {
+            let mut copy_cb = |buf: &mut PlaybackBuffer| {
+                let pb_buf = [0xa5u8; 480 * 2 * 2];
+                assert_eq!(buf.write(&pb_buf).unwrap(), 480 * 2 * 2);
+                Ok(())
+            };
+            stream.write_playback_buffer(&mut copy_cb).unwrap();
+        }
+        // The second call should block until the first buffer is consumed.
+        let mut assert_cb = |_: &mut PlaybackBuffer| {
+            let elapsed = start.elapsed();
+            assert!(
+                elapsed > Duration::from_millis(10),
+                "next_playback_buffer didn't block long enough {}",
+                elapsed.subsec_millis()
+            );
+            Ok(())
+        };
+        stream.write_playback_buffer(&mut assert_cb).unwrap();
+    }
+
+    #[test]
+    fn async_commit() {
+        struct TestCommit {
+            frame_count: usize,
+        }
+        #[async_trait(?Send)]
+        impl AsyncBufferCommit for TestCommit {
+            async fn commit(&mut self, nwritten: usize) {
+                self.frame_count += nwritten;
+            }
+        }
+        async fn this_test() {
+            let mut test_commit = TestCommit { frame_count: 0 };
+            {
+                const FRAME_SIZE: usize = 4;
+                let mut buf = [0u8; 480 * FRAME_SIZE];
+                let mut pb_buf =
+                    AsyncPlaybackBuffer::new(FRAME_SIZE, &mut buf, &mut test_commit).unwrap();
+                pb_buf.write_all(&[0xa5u8; 480 * FRAME_SIZE]).unwrap();
+                pb_buf.commit().await;
+            }
+            assert_eq!(test_commit.frame_count, 480);
+        }
+
+        let ex = Executor::new().expect("failed to create executor");
+        ex.run_until(this_test()).unwrap();
+    }
+
+    #[test]
+    fn consumption_rate_async() {
+        async fn this_test(ex: &Executor) {
+            let mut server = NoopStreamSource::new();
+            let (_, mut stream) = server
+                .new_async_playback_stream(2, SampleFormat::S16LE, 48000, 480, ex)
+                .unwrap();
+            let start = Instant::now();
+            {
+                let copy_func = |buf: &mut AsyncPlaybackBuffer| {
+                    let pb_buf = [0xa5u8; 480 * 2 * 2];
+                    assert_eq!(buf.write(&pb_buf).unwrap(), 480 * 2 * 2);
+                    Ok(())
+                };
+                async_write_playback_buffer(&mut *stream, copy_func, ex)
+                    .await
+                    .unwrap();
+            }
+            // The second call should block until the first buffer is consumed.
+            let assert_func = |_: &mut AsyncPlaybackBuffer| {
+                let elapsed = start.elapsed();
+                assert!(
+                    elapsed > Duration::from_millis(10),
+                    "write_playback_buffer didn't block long enough {}",
+                    elapsed.subsec_millis()
+                );
+                Ok(())
+            };
+            async_write_playback_buffer(&mut *stream, assert_func, ex)
+                .await
+                .unwrap();
+        }
+
+        let ex = Executor::new().expect("failed to create executor");
+        ex.run_until(this_test(&ex)).unwrap();
+    }
+}
diff --git a/audio_streams/src/capture.rs b/audio_streams/src/capture.rs
new file mode 100644
index 0000000..70d9832
--- /dev/null
+++ b/audio_streams/src/capture.rs
@@ -0,0 +1,465 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//! ```
+//! use audio_streams::{BoxError, capture::CaptureBuffer, SampleFormat, StreamSource,
+//!     NoopStreamSource};
+//! use std::io::Read;
+//!
+//! const buffer_size: usize = 120;
+//! const num_channels: usize = 2;
+//!
+//! # fn main() -> std::result::Result<(),BoxError> {
+//! let mut stream_source = NoopStreamSource::new();
+//! let sample_format = SampleFormat::S16LE;
+//! let frame_size = num_channels * sample_format.sample_bytes();
+//!
+//! let (_, mut stream) = stream_source
+//!     .new_capture_stream(num_channels, sample_format, 48000, buffer_size)?;
+//! // Capture 10 buffers of zeros.
+//! let mut buf = Vec::new();
+//! buf.resize(buffer_size * frame_size, 0xa5u8);
+//! for _ in 0..10 {
+//!     let mut copy_func = |stream_buffer: &mut CaptureBuffer| {
+//!         assert_eq!(stream_buffer.read(&mut buf)?, buffer_size * frame_size);
+//!         Ok(())
+//!     };
+//!     stream.read_capture_buffer(&mut copy_func)?;
+//! }
+//! # Ok (())
+//! # }
+//! ```
+
+use async_trait::async_trait;
+use std::{
+    error,
+    fmt::{self, Display},
+    io::{self, Read},
+    time::{Duration, Instant},
+};
+
+use super::{
+    AsyncBufferCommit, AudioBuffer, BoxError, BufferCommit, NoopBufferCommit, SampleFormat,
+};
+use cros_async::{Executor, TimerAsync};
+
+/// `CaptureBufferStream` provides `CaptureBuffer`s to read with audio samples from capture.
+pub trait CaptureBufferStream: Send {
+    fn next_capture_buffer<'b, 's: 'b>(&'s mut self) -> Result<CaptureBuffer<'b>, BoxError>;
+
+    /// Call `f` with a `CaptureBuffer`, and trigger the buffer done call back after. `f` can read
+    /// the capture data from the given `CaptureBuffer`.
+    fn read_capture_buffer<'b, 's: 'b>(
+        &'s mut self,
+        f: &mut dyn FnMut(&mut CaptureBuffer<'b>) -> Result<(), BoxError>,
+    ) -> Result<(), BoxError> {
+        let mut buf = self.next_capture_buffer()?;
+        f(&mut buf)?;
+        buf.commit();
+        Ok(())
+    }
+}
+
+impl<S: CaptureBufferStream + ?Sized> CaptureBufferStream for &mut S {
+    fn next_capture_buffer<'b, 's: 'b>(&'s mut self) -> Result<CaptureBuffer<'b>, BoxError> {
+        (**self).next_capture_buffer()
+    }
+}
+
+#[async_trait(?Send)]
+pub trait AsyncCaptureBufferStream: Send {
+    async fn next_capture_buffer<'a>(
+        &'a mut self,
+        _ex: &Executor,
+    ) -> Result<AsyncCaptureBuffer<'a>, BoxError>;
+}
+
+#[async_trait(?Send)]
+impl<S: AsyncCaptureBufferStream + ?Sized> AsyncCaptureBufferStream for &mut S {
+    async fn next_capture_buffer<'a>(
+        &'a mut self,
+        ex: &Executor,
+    ) -> Result<AsyncCaptureBuffer<'a>, BoxError> {
+        (**self).next_capture_buffer(ex).await
+    }
+}
+
+/// `CaptureBuffer` contains a block of audio samples got from capture stream. It provides
+/// temporary view to those samples and will notifies capture stream when dropped.
+/// Note that it'll always send `buffer.len() / frame_size` to drop function when it got destroyed
+/// since `CaptureBufferStream` assumes that users get all the samples from the buffer.
+pub struct CaptureBuffer<'a> {
+    buffer: AudioBuffer<'a>,
+    drop: &'a mut dyn BufferCommit,
+}
+
+/// Async version of 'CaptureBuffer`
+pub struct AsyncCaptureBuffer<'a> {
+    buffer: AudioBuffer<'a>,
+    trigger: &'a mut dyn AsyncBufferCommit,
+}
+
+/// Errors that are possible from a `CaptureBuffer`.
+#[derive(Debug)]
+pub enum CaptureBufferError {
+    InvalidLength,
+}
+
+impl error::Error for CaptureBufferError {}
+
+impl Display for CaptureBufferError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            CaptureBufferError::InvalidLength => write!(f, "Invalid buffer length"),
+        }
+    }
+}
+
+impl<'a> CaptureBuffer<'a> {
+    /// Creates a new `CaptureBuffer` that holds a reference to the backing memory specified in
+    /// `buffer`.
+    pub fn new<F>(
+        frame_size: usize,
+        buffer: &'a mut [u8],
+        drop: &'a mut F,
+    ) -> Result<Self, CaptureBufferError>
+    where
+        F: BufferCommit,
+    {
+        if buffer.len() % frame_size != 0 {
+            return Err(CaptureBufferError::InvalidLength);
+        }
+
+        Ok(CaptureBuffer {
+            buffer: AudioBuffer {
+                buffer,
+                frame_size,
+                offset: 0,
+            },
+            drop,
+        })
+    }
+
+    /// Returns the number of audio frames that fit in the buffer.
+    pub fn frame_capacity(&self) -> usize {
+        self.buffer.frame_capacity()
+    }
+
+    /// This triggers the callback of `BufferCommit`. This should be called after the data is read
+    /// from the buffer.
+    ///
+    /// Always sends `frame_capacity`.
+    pub fn commit(&mut self) {
+        self.drop.commit(self.frame_capacity());
+    }
+
+    /// Reads up to `size` bytes directly from this buffer inside of the given callback function.
+    pub fn copy_cb<F: FnOnce(&[u8])>(&mut self, size: usize, cb: F) -> io::Result<usize> {
+        self.buffer.read_copy_cb(size, cb)
+    }
+}
+
+impl<'a> Read for CaptureBuffer<'a> {
+    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+        self.buffer.read(buf)
+    }
+}
+
+impl<'a> AsyncCaptureBuffer<'a> {
+    /// Creates a new `AsyncCaptureBuffer` that holds a reference to the backing memory specified in
+    /// `buffer`.
+    pub fn new<F>(
+        frame_size: usize,
+        buffer: &'a mut [u8],
+        trigger: &'a mut F,
+    ) -> Result<Self, CaptureBufferError>
+    where
+        F: AsyncBufferCommit,
+    {
+        if buffer.len() % frame_size != 0 {
+            return Err(CaptureBufferError::InvalidLength);
+        }
+
+        Ok(AsyncCaptureBuffer {
+            buffer: AudioBuffer {
+                buffer,
+                frame_size,
+                offset: 0,
+            },
+            trigger,
+        })
+    }
+
+    /// Returns the number of audio frames that fit in the buffer.
+    pub fn frame_capacity(&self) -> usize {
+        self.buffer.frame_capacity()
+    }
+
+    /// This triggers the callback of `AsyncBufferCommit`. This should be called after the data is
+    /// read from the buffer.
+    ///
+    /// Always sends `frame_capacity`.
+    pub async fn commit(&mut self) {
+        self.trigger.commit(self.frame_capacity()).await;
+    }
+
+    /// Reads up to `size` bytes directly from this buffer inside of the given callback function.
+    pub fn copy_cb<F: FnOnce(&[u8])>(&mut self, size: usize, cb: F) -> io::Result<usize> {
+        self.buffer.read_copy_cb(size, cb)
+    }
+}
+
+impl<'a> Read for AsyncCaptureBuffer<'a> {
+    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+        self.buffer.read(buf)
+    }
+}
+
+/// Stream that provides null capture samples.
+pub struct NoopCaptureStream {
+    buffer: Vec<u8>,
+    frame_size: usize,
+    interval: Duration,
+    next_frame: Duration,
+    start_time: Option<Instant>,
+    buffer_drop: NoopBufferCommit,
+}
+
+impl NoopCaptureStream {
+    pub fn new(
+        num_channels: usize,
+        format: SampleFormat,
+        frame_rate: u32,
+        buffer_size: usize,
+    ) -> Self {
+        let frame_size = format.sample_bytes() * num_channels;
+        let interval = Duration::from_millis(buffer_size as u64 * 1000 / frame_rate as u64);
+        NoopCaptureStream {
+            buffer: vec![0; buffer_size * frame_size],
+            frame_size,
+            interval,
+            next_frame: interval,
+            start_time: None,
+            buffer_drop: NoopBufferCommit {
+                which_buffer: false,
+            },
+        }
+    }
+}
+
+impl CaptureBufferStream for NoopCaptureStream {
+    fn next_capture_buffer<'b, 's: 'b>(&'s mut self) -> Result<CaptureBuffer<'b>, BoxError> {
+        if let Some(start_time) = self.start_time {
+            let elapsed = start_time.elapsed();
+            if elapsed < self.next_frame {
+                std::thread::sleep(self.next_frame - elapsed);
+            }
+            self.next_frame += self.interval;
+        } else {
+            self.start_time = Some(Instant::now());
+            self.next_frame = self.interval;
+        }
+        Ok(CaptureBuffer::new(
+            self.frame_size,
+            &mut self.buffer,
+            &mut self.buffer_drop,
+        )?)
+    }
+}
+
+#[async_trait(?Send)]
+impl AsyncCaptureBufferStream for NoopCaptureStream {
+    async fn next_capture_buffer<'a>(
+        &'a mut self,
+        ex: &Executor,
+    ) -> Result<AsyncCaptureBuffer<'a>, BoxError> {
+        if let Some(start_time) = self.start_time {
+            let elapsed = start_time.elapsed();
+            if elapsed < self.next_frame {
+                TimerAsync::sleep(ex, self.next_frame - elapsed).await?;
+            }
+            self.next_frame += self.interval;
+        } else {
+            self.start_time = Some(Instant::now());
+            self.next_frame = self.interval;
+        }
+        Ok(AsyncCaptureBuffer::new(
+            self.frame_size,
+            &mut self.buffer,
+            &mut self.buffer_drop,
+        )?)
+    }
+}
+
+/// Call `f` with a `AsyncCaptureBuffer`, and trigger the buffer done call back after. `f` can read
+/// the capture data from the given `AsyncCaptureBuffer`.
+///
+/// This cannot be a trait method because trait methods with generic parameters are not object safe.
+pub async fn async_read_capture_buffer<F>(
+    stream: &mut dyn AsyncCaptureBufferStream,
+    f: F,
+    ex: &Executor,
+) -> Result<(), BoxError>
+where
+    F: FnOnce(&mut AsyncCaptureBuffer) -> Result<(), BoxError>,
+{
+    let mut buf = stream.next_capture_buffer(ex).await?;
+    f(&mut buf)?;
+    buf.commit().await;
+    Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+    use super::super::*;
+    use super::*;
+
+    #[test]
+    fn invalid_buffer_length() {
+        // Capture buffers can't be created with a size that isn't divisible by the frame size.
+        let mut cp_buf = [0xa5u8; 480 * 2 * 2 + 1];
+        let mut buffer_drop = NoopBufferCommit {
+            which_buffer: false,
+        };
+        assert!(CaptureBuffer::new(2, &mut cp_buf, &mut buffer_drop).is_err());
+    }
+
+    #[test]
+    fn commit() {
+        struct TestCommit {
+            frame_count: usize,
+        }
+        impl BufferCommit for TestCommit {
+            fn commit(&mut self, nwritten: usize) {
+                self.frame_count += nwritten;
+            }
+        }
+        let mut test_commit = TestCommit { frame_count: 0 };
+        {
+            const FRAME_SIZE: usize = 4;
+            let mut buf = [0u8; 480 * FRAME_SIZE];
+            let mut cp_buf = CaptureBuffer::new(FRAME_SIZE, &mut buf, &mut test_commit).unwrap();
+            let mut local_buf = [0u8; 240 * FRAME_SIZE];
+            assert_eq!(cp_buf.read(&mut local_buf).unwrap(), 240 * FRAME_SIZE);
+            cp_buf.commit();
+        }
+        // This should be 480 no matter how many samples are read.
+        assert_eq!(test_commit.frame_count, 480);
+    }
+
+    #[test]
+    fn sixteen_bit_stereo() {
+        let mut server = NoopStreamSource::new();
+        let (_, mut stream) = server
+            .new_capture_stream(2, SampleFormat::S16LE, 48000, 480)
+            .unwrap();
+        let mut copy_func = |b: &mut CaptureBuffer| {
+            assert_eq!(b.buffer.frame_capacity(), 480);
+            let mut pb_buf = [0xa5u8; 480 * 2 * 2];
+            assert_eq!(b.read(&mut pb_buf).unwrap(), 480 * 2 * 2);
+            Ok(())
+        };
+        stream.read_capture_buffer(&mut copy_func).unwrap();
+    }
+
+    #[test]
+    fn consumption_rate() {
+        let mut server = NoopStreamSource::new();
+        let (_, mut stream) = server
+            .new_capture_stream(2, SampleFormat::S16LE, 48000, 480)
+            .unwrap();
+        let start = Instant::now();
+        {
+            let mut copy_func = |b: &mut CaptureBuffer| {
+                let mut cp_buf = [0xa5u8; 480 * 2 * 2];
+                assert_eq!(b.read(&mut cp_buf).unwrap(), 480 * 2 * 2);
+                for buf in cp_buf.iter() {
+                    assert_eq!(*buf, 0, "Read samples should all be zeros.");
+                }
+                Ok(())
+            };
+            stream.read_capture_buffer(&mut copy_func).unwrap();
+        }
+        // The second call should block until the first buffer is consumed.
+        let mut assert_func = |_: &mut CaptureBuffer| {
+            let elapsed = start.elapsed();
+            assert!(
+                elapsed > Duration::from_millis(10),
+                "next_capture_buffer didn't block long enough {}",
+                elapsed.subsec_millis()
+            );
+            Ok(())
+        };
+        stream.read_capture_buffer(&mut assert_func).unwrap();
+    }
+
+    #[test]
+    fn async_commit() {
+        struct TestCommit {
+            frame_count: usize,
+        }
+        #[async_trait(?Send)]
+        impl AsyncBufferCommit for TestCommit {
+            async fn commit(&mut self, nwritten: usize) {
+                self.frame_count += nwritten;
+            }
+        }
+        async fn this_test() {
+            let mut test_commit = TestCommit { frame_count: 0 };
+            {
+                const FRAME_SIZE: usize = 4;
+                let mut buf = [0u8; 480 * FRAME_SIZE];
+                let mut cp_buf =
+                    AsyncCaptureBuffer::new(FRAME_SIZE, &mut buf, &mut test_commit).unwrap();
+                let mut local_buf = [0u8; 240 * FRAME_SIZE];
+                assert_eq!(cp_buf.read(&mut local_buf).unwrap(), 240 * FRAME_SIZE);
+                cp_buf.commit().await;
+            }
+            // This should be 480 no matter how many samples are read.
+            assert_eq!(test_commit.frame_count, 480);
+        }
+
+        let ex = Executor::new().expect("failed to create executor");
+        ex.run_until(this_test()).unwrap();
+    }
+
+    #[test]
+    fn consumption_rate_async() {
+        async fn this_test(ex: &Executor) {
+            let mut server = NoopStreamSource::new();
+            let (_, mut stream) = server
+                .new_async_capture_stream(2, SampleFormat::S16LE, 48000, 480, ex)
+                .unwrap();
+            let start = Instant::now();
+            {
+                let copy_func = |buf: &mut AsyncCaptureBuffer| {
+                    let mut cp_buf = [0xa5u8; 480 * 2 * 2];
+                    assert_eq!(buf.read(&mut cp_buf).unwrap(), 480 * 2 * 2);
+                    for buf in cp_buf.iter() {
+                        assert_eq!(*buf, 0, "Read samples should all be zeros.");
+                    }
+                    Ok(())
+                };
+                async_read_capture_buffer(&mut *stream, copy_func, ex)
+                    .await
+                    .unwrap();
+            }
+            // The second call should block until the first buffer is consumed.
+            let assert_func = |_: &mut AsyncCaptureBuffer| {
+                let elapsed = start.elapsed();
+                assert!(
+                    elapsed > Duration::from_millis(10),
+                    "next_capture_buffer didn't block long enough {}",
+                    elapsed.subsec_millis()
+                );
+                Ok(())
+            };
+            async_read_capture_buffer(&mut *stream, assert_func, ex)
+                .await
+                .unwrap();
+        }
+
+        let ex = Executor::new().expect("failed to create executor");
+        ex.run_until(this_test(&ex)).unwrap();
+    }
+}
diff --git a/audio_streams/src/shm_streams.rs b/audio_streams/src/shm_streams.rs
new file mode 100644
index 0000000..b11626f
--- /dev/null
+++ b/audio_streams/src/shm_streams.rs
@@ -0,0 +1,568 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+use std::error;
+use std::fmt;
+use std::os::unix::io::RawFd;
+use std::sync::Arc;
+use std::time::{Duration, Instant};
+
+use sync::{Condvar, Mutex};
+use sys_util::SharedMemory;
+
+use crate::{BoxError, SampleFormat, StreamDirection, StreamEffect};
+
+type GenericResult<T> = std::result::Result<T, BoxError>;
+
+/// `BufferSet` is used as a callback mechanism for `ServerRequest` objects.
+/// It is meant to be implemented by the audio stream, allowing arbitrary code
+/// to be run after a buffer offset and length is set.
+pub trait BufferSet {
+    /// Called when the client sets a buffer offset and length.
+    ///
+    /// `offset` is the offset within shared memory of the buffer and `frames`
+    /// indicates the number of audio frames that can be read from or written to
+    /// the buffer.
+    fn callback(&mut self, offset: usize, frames: usize) -> GenericResult<()>;
+
+    /// Called when the client ignores a request from the server.
+    fn ignore(&mut self) -> GenericResult<()>;
+}
+
+#[derive(Debug)]
+pub enum Error {
+    TooManyFrames(usize, usize),
+}
+
+impl error::Error for Error {}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            Error::TooManyFrames(provided, requested) => write!(
+                f,
+                "Provided number of frames {} exceeds requested number of frames {}",
+                provided, requested
+            ),
+        }
+    }
+}
+
+/// `ServerRequest` represents an active request from the server for the client
+/// to provide a buffer in shared memory to playback from or capture to.
+pub struct ServerRequest<'a> {
+    requested_frames: usize,
+    buffer_set: &'a mut dyn BufferSet,
+}
+
+impl<'a> ServerRequest<'a> {
+    /// Create a new ServerRequest object
+    ///
+    /// Create a ServerRequest object representing a request from the server
+    /// for a buffer `requested_frames` in size.
+    ///
+    /// When the client responds to this request by calling
+    /// [`set_buffer_offset_and_frames`](ServerRequest::set_buffer_offset_and_frames),
+    /// BufferSet::callback will be called on `buffer_set`.
+    ///
+    /// # Arguments
+    /// * `requested_frames` - The requested buffer size in frames.
+    /// * `buffer_set` - The object implementing the callback for when a buffer is provided.
+    pub fn new<D: BufferSet>(requested_frames: usize, buffer_set: &'a mut D) -> Self {
+        Self {
+            requested_frames,
+            buffer_set,
+        }
+    }
+
+    /// Get the number of frames of audio data requested by the server.
+    ///
+    /// The returned value should never be greater than the `buffer_size`
+    /// given in [`new_stream`](ShmStreamSource::new_stream).
+    pub fn requested_frames(&self) -> usize {
+        self.requested_frames
+    }
+
+    /// Sets the buffer offset and length for the requested buffer.
+    ///
+    /// Sets the buffer offset and length of the buffer that fulfills this
+    /// server request to `offset` and `length`, respectively. This means that
+    /// `length` bytes of audio samples may be read from/written to that
+    /// location in `client_shm` for a playback/capture stream, respectively.
+    /// This function may only be called once for a `ServerRequest`, at which
+    /// point the ServerRequest is dropped and no further calls are possible.
+    ///
+    /// # Arguments
+    ///
+    /// * `offset` - The value to use as the new buffer offset for the next buffer.
+    /// * `frames` - The length of the next buffer in frames.
+    ///
+    /// # Errors
+    ///
+    /// * If `frames` is greater than `requested_frames`.
+    pub fn set_buffer_offset_and_frames(self, offset: usize, frames: usize) -> GenericResult<()> {
+        if frames > self.requested_frames {
+            return Err(Box::new(Error::TooManyFrames(
+                frames,
+                self.requested_frames,
+            )));
+        }
+
+        self.buffer_set.callback(offset, frames)
+    }
+
+    /// Ignore this request
+    ///
+    /// If the client does not intend to respond to this ServerRequest with a
+    /// buffer, they should call this function. The stream will be notified that
+    /// the request has been ignored and will handle it properly.
+    pub fn ignore_request(self) -> GenericResult<()> {
+        self.buffer_set.ignore()
+    }
+}
+
+/// `ShmStream` allows a client to interact with an active CRAS stream.
+pub trait ShmStream: Send {
+    /// Get the size of a frame of audio data for this stream.
+    fn frame_size(&self) -> usize;
+
+    /// Get the number of channels of audio data for this stream.
+    fn num_channels(&self) -> usize;
+
+    /// Get the frame rate of audio data for this stream.
+    fn frame_rate(&self) -> u32;
+
+    /// Waits until the next server message indicating action is required.
+    ///
+    /// For playback streams, this will be `AUDIO_MESSAGE_REQUEST_DATA`, meaning
+    /// that we must set the buffer offset to the next location where playback
+    /// data can be found.
+    /// For capture streams, this will be `AUDIO_MESSAGE_DATA_READY`, meaning
+    /// that we must set the buffer offset to the next location where captured
+    /// data can be written to.
+    /// Will return early if `timeout` elapses before a message is received.
+    ///
+    /// # Arguments
+    ///
+    /// * `timeout` - The amount of time to wait until a message is received.
+    ///
+    /// # Return value
+    ///
+    /// Returns `Some(request)` where `request` is an object that implements the
+    /// [`ServerRequest`](ServerRequest) trait and which can be used to get the
+    /// number of bytes requested for playback streams or that have already been
+    /// written to shm for capture streams.
+    ///
+    /// If the timeout occurs before a message is received, returns `None`.
+    ///
+    /// # Errors
+    ///
+    /// * If an invalid message type is received for the stream.
+    fn wait_for_next_action_with_timeout(
+        &mut self,
+        timeout: Duration,
+    ) -> GenericResult<Option<ServerRequest>>;
+}
+
+/// `ShmStreamSource` creates streams for playback or capture of audio.
+pub trait ShmStreamSource: Send {
+    /// Creates a new [`ShmStream`](ShmStream)
+    ///
+    /// Creates a new `ShmStream` object, which allows:
+    /// * Waiting until the server has communicated that data is ready or
+    ///   requested that we make more data available.
+    /// * Setting the location and length of buffers for reading/writing audio data.
+    ///
+    /// # Arguments
+    ///
+    /// * `direction` - The direction of the stream, either `Playback` or `Capture`.
+    /// * `num_channels` - The number of audio channels for the stream.
+    /// * `format` - The audio format to use for audio samples.
+    /// * `frame_rate` - The stream's frame rate in Hz.
+    /// * `buffer_size` - The maximum size of an audio buffer. This will be the
+    ///                   size used for transfers of audio data between client
+    ///                   and server.
+    /// * `effects` - Audio effects to use for the stream, such as echo-cancellation.
+    /// * `client_shm` - The shared memory area that will contain samples.
+    /// * `buffer_offsets` - The two initial values to use as buffer offsets
+    ///                      for streams. This way, the server will not write
+    ///                      audio data to an arbitrary offset in `client_shm`
+    ///                      if the client fails to update offsets in time.
+    ///
+    /// # Errors
+    ///
+    /// * If sending the connect stream message to the server fails.
+    #[allow(clippy::too_many_arguments)]
+    fn new_stream(
+        &mut self,
+        direction: StreamDirection,
+        num_channels: usize,
+        format: SampleFormat,
+        frame_rate: u32,
+        buffer_size: usize,
+        effects: &[StreamEffect],
+        client_shm: &SharedMemory,
+        buffer_offsets: [u64; 2],
+    ) -> GenericResult<Box<dyn ShmStream>>;
+
+    /// Get a list of file descriptors used by the implementation.
+    ///
+    /// Returns any open file descriptors needed by the implementation.
+    /// This list helps users of the ShmStreamSource enter Linux jails without
+    /// closing needed file descriptors.
+    fn keep_fds(&self) -> Vec<RawFd> {
+        Vec::new()
+    }
+}
+
+/// Class that implements ShmStream trait but does nothing with the samples
+pub struct NullShmStream {
+    num_channels: usize,
+    frame_rate: u32,
+    buffer_size: usize,
+    frame_size: usize,
+    interval: Duration,
+    next_frame: Duration,
+    start_time: Instant,
+}
+
+impl NullShmStream {
+    /// Attempt to create a new NullShmStream with the given number of channels,
+    /// format, frame_rate, and buffer_size.
+    pub fn new(
+        buffer_size: usize,
+        num_channels: usize,
+        format: SampleFormat,
+        frame_rate: u32,
+    ) -> Self {
+        let interval = Duration::from_millis(buffer_size as u64 * 1000 / frame_rate as u64);
+        Self {
+            num_channels,
+            frame_rate,
+            buffer_size,
+            frame_size: format.sample_bytes() * num_channels,
+            interval,
+            next_frame: interval,
+            start_time: Instant::now(),
+        }
+    }
+}
+
+impl BufferSet for NullShmStream {
+    fn callback(&mut self, _offset: usize, _frames: usize) -> GenericResult<()> {
+        Ok(())
+    }
+
+    fn ignore(&mut self) -> GenericResult<()> {
+        Ok(())
+    }
+}
+
+impl ShmStream for NullShmStream {
+    fn frame_size(&self) -> usize {
+        self.frame_size
+    }
+
+    fn num_channels(&self) -> usize {
+        self.num_channels
+    }
+
+    fn frame_rate(&self) -> u32 {
+        self.frame_rate
+    }
+
+    fn wait_for_next_action_with_timeout(
+        &mut self,
+        timeout: Duration,
+    ) -> GenericResult<Option<ServerRequest>> {
+        let elapsed = self.start_time.elapsed();
+        if elapsed < self.next_frame {
+            if timeout < self.next_frame - elapsed {
+                std::thread::sleep(timeout);
+                return Ok(None);
+            } else {
+                std::thread::sleep(self.next_frame - elapsed);
+            }
+        }
+        self.next_frame += self.interval;
+        Ok(Some(ServerRequest::new(self.buffer_size, self)))
+    }
+}
+
+/// Source of `NullShmStream` objects.
+#[derive(Default)]
+pub struct NullShmStreamSource;
+
+impl NullShmStreamSource {
+    pub fn new() -> Self {
+        Self::default()
+    }
+}
+
+impl ShmStreamSource for NullShmStreamSource {
+    fn new_stream(
+        &mut self,
+        _direction: StreamDirection,
+        num_channels: usize,
+        format: SampleFormat,
+        frame_rate: u32,
+        buffer_size: usize,
+        _effects: &[StreamEffect],
+        _client_shm: &SharedMemory,
+        _buffer_offsets: [u64; 2],
+    ) -> GenericResult<Box<dyn ShmStream>> {
+        let new_stream = NullShmStream::new(buffer_size, num_channels, format, frame_rate);
+        Ok(Box::new(new_stream))
+    }
+}
+
+#[derive(Clone)]
+pub struct MockShmStream {
+    num_channels: usize,
+    frame_rate: u32,
+    request_size: usize,
+    frame_size: usize,
+    request_notifier: Arc<(Mutex<bool>, Condvar)>,
+}
+
+impl MockShmStream {
+    /// Attempt to create a new MockShmStream with the given number of
+    /// channels, frame_rate, format, and buffer_size.
+    pub fn new(
+        num_channels: usize,
+        frame_rate: u32,
+        format: SampleFormat,
+        buffer_size: usize,
+    ) -> Self {
+        Self {
+            num_channels,
+            frame_rate,
+            request_size: buffer_size,
+            frame_size: format.sample_bytes() * num_channels,
+            request_notifier: Arc::new((Mutex::new(false), Condvar::new())),
+        }
+    }
+
+    /// Call to request data from the stream, causing it to return from
+    /// `wait_for_next_action_with_timeout`. Will block until
+    /// `set_buffer_offset_and_frames` is called on the ServerRequest returned
+    /// from `wait_for_next_action_with_timeout`, or until `timeout` elapses.
+    /// Returns true if a response was successfully received.
+    pub fn trigger_callback_with_timeout(&mut self, timeout: Duration) -> bool {
+        let &(ref lock, ref cvar) = &*self.request_notifier;
+        let mut requested = lock.lock();
+        *requested = true;
+        cvar.notify_one();
+        let start_time = Instant::now();
+        while *requested {
+            requested = cvar.wait_timeout(requested, timeout).0;
+            if start_time.elapsed() > timeout {
+                // We failed to get a callback in time, mark this as false.
+                *requested = false;
+                return false;
+            }
+        }
+
+        true
+    }
+
+    fn notify_request(&mut self) {
+        let &(ref lock, ref cvar) = &*self.request_notifier;
+        let mut requested = lock.lock();
+        *requested = false;
+        cvar.notify_one();
+    }
+}
+
+impl BufferSet for MockShmStream {
+    fn callback(&mut self, _offset: usize, _frames: usize) -> GenericResult<()> {
+        self.notify_request();
+        Ok(())
+    }
+
+    fn ignore(&mut self) -> GenericResult<()> {
+        self.notify_request();
+        Ok(())
+    }
+}
+
+impl ShmStream for MockShmStream {
+    fn frame_size(&self) -> usize {
+        self.frame_size
+    }
+
+    fn num_channels(&self) -> usize {
+        self.num_channels
+    }
+
+    fn frame_rate(&self) -> u32 {
+        self.frame_rate
+    }
+
+    fn wait_for_next_action_with_timeout(
+        &mut self,
+        timeout: Duration,
+    ) -> GenericResult<Option<ServerRequest>> {
+        {
+            let start_time = Instant::now();
+            let &(ref lock, ref cvar) = &*self.request_notifier;
+            let mut requested = lock.lock();
+            while !*requested {
+                requested = cvar.wait_timeout(requested, timeout).0;
+                if start_time.elapsed() > timeout {
+                    return Ok(None);
+                }
+            }
+        }
+
+        Ok(Some(ServerRequest::new(self.request_size, self)))
+    }
+}
+
+/// Source of `MockShmStream` objects.
+#[derive(Clone, Default)]
+pub struct MockShmStreamSource {
+    last_stream: Arc<(Mutex<Option<MockShmStream>>, Condvar)>,
+}
+
+impl MockShmStreamSource {
+    pub fn new() -> Self {
+        Default::default()
+    }
+
+    /// Get the last stream that has been created from this source. If no stream
+    /// has been created, block until one has.
+    pub fn get_last_stream(&self) -> MockShmStream {
+        let &(ref last_stream, ref cvar) = &*self.last_stream;
+        let mut stream = last_stream.lock();
+        loop {
+            match &*stream {
+                None => stream = cvar.wait(stream),
+                Some(ref s) => return s.clone(),
+            };
+        }
+    }
+}
+
+impl ShmStreamSource for MockShmStreamSource {
+    fn new_stream(
+        &mut self,
+        _direction: StreamDirection,
+        num_channels: usize,
+        format: SampleFormat,
+        frame_rate: u32,
+        buffer_size: usize,
+        _effects: &[StreamEffect],
+        _client_shm: &SharedMemory,
+        _buffer_offsets: [u64; 2],
+    ) -> GenericResult<Box<dyn ShmStream>> {
+        let &(ref last_stream, ref cvar) = &*self.last_stream;
+        let mut stream = last_stream.lock();
+
+        let new_stream = MockShmStream::new(num_channels, frame_rate, format, buffer_size);
+        *stream = Some(new_stream.clone());
+        cvar.notify_one();
+        Ok(Box::new(new_stream))
+    }
+}
+
+#[cfg(test)]
+pub mod tests {
+    use super::*;
+
+    #[test]
+    fn mock_trigger_callback() {
+        let stream_source = MockShmStreamSource::new();
+        let mut thread_stream_source = stream_source.clone();
+
+        let buffer_size = 480;
+        let num_channels = 2;
+        let format = SampleFormat::S24LE;
+        let shm = SharedMemory::anon().expect("Failed to create shm");
+
+        let handle = std::thread::spawn(move || {
+            let mut stream = thread_stream_source
+                .new_stream(
+                    StreamDirection::Playback,
+                    num_channels,
+                    format,
+                    44100,
+                    buffer_size,
+                    &[],
+                    &shm,
+                    [400, 8000],
+                )
+                .expect("Failed to create stream");
+
+            let request = stream
+                .wait_for_next_action_with_timeout(Duration::from_secs(5))
+                .expect("Failed to wait for next action");
+            match request {
+                Some(r) => {
+                    let requested = r.requested_frames();
+                    r.set_buffer_offset_and_frames(872, requested)
+                        .expect("Failed to set buffer offset and frames");
+                    requested
+                }
+                None => 0,
+            }
+        });
+
+        let mut stream = stream_source.get_last_stream();
+        assert!(stream.trigger_callback_with_timeout(Duration::from_secs(1)));
+
+        let requested_frames = handle.join().expect("Failed to join thread");
+        assert_eq!(requested_frames, buffer_size);
+    }
+
+    #[test]
+    fn null_consumption_rate() {
+        let frame_rate = 44100;
+        let buffer_size = 480;
+        let interval = Duration::from_millis(buffer_size as u64 * 1000 / frame_rate as u64);
+
+        let shm = SharedMemory::anon().expect("Failed to create shm");
+
+        let start = Instant::now();
+
+        let mut stream_source = NullShmStreamSource::new();
+        let mut stream = stream_source
+            .new_stream(
+                StreamDirection::Playback,
+                2,
+                SampleFormat::S24LE,
+                frame_rate,
+                buffer_size,
+                &[],
+                &shm,
+                [400, 8000],
+            )
+            .expect("Failed to create stream");
+
+        let timeout = Duration::from_secs(5);
+        let request = stream
+            .wait_for_next_action_with_timeout(timeout)
+            .expect("Failed to wait for first request")
+            .expect("First request should not have timed out");
+        request
+            .set_buffer_offset_and_frames(276, 480)
+            .expect("Failed to set buffer offset and length");
+
+        // The second call should block until the first buffer is consumed.
+        let _request = stream
+            .wait_for_next_action_with_timeout(timeout)
+            .expect("Failed to wait for second request");
+        let elapsed = start.elapsed();
+        assert!(
+            elapsed > interval,
+            "wait_for_next_action_with_timeout didn't block long enough: {:?}",
+            elapsed
+        );
+
+        assert!(
+            elapsed < timeout,
+            "wait_for_next_action_with_timeout blocked for too long: {:?}",
+            elapsed
+        );
+    }
+}
diff --git a/base/Android.bp b/base/Android.bp
index e1a7f69..19e27d2 100644
--- a/base/Android.bp
+++ b/base/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
 // Do not modify this file as changes will be overridden on upgrade.
 
 package {
@@ -11,7 +11,7 @@
 }
 
 rust_defaults {
-    name: "base_defaults",
+    name: "base_test_defaults",
     defaults: ["crosvm_defaults"],
     crate_name: "base",
     srcs: ["src/lib.rs"],
@@ -33,7 +33,7 @@
 
 rust_test_host {
     name: "base_host_test_src_lib",
-    defaults: ["base_defaults"],
+    defaults: ["base_test_defaults"],
     test_options: {
         unit_test: true,
     },
@@ -41,7 +41,7 @@
 
 rust_test {
     name: "base_device_test_src_lib",
-    defaults: ["base_defaults"],
+    defaults: ["base_test_defaults"],
 }
 
 rust_library {
@@ -64,42 +64,3 @@
         "libthiserror",
     ],
 }
-
-// dependent_library ["feature_list"]
-//   ../assertions/src/lib.rs
-//   ../cros_async/src/lib.rs
-//   ../data_model/src/lib.rs
-//   ../io_uring/src/lib.rs
-//   ../sync/src/lib.rs
-//   ../sys_util/poll_token_derive/poll_token_derive.rs
-//   ../sys_util/src/lib.rs
-//   ../tempfile/src/lib.rs
-//   async-task-4.0.3 "default,std"
-//   async-trait-0.1.50
-//   autocfg-1.0.1
-//   futures-0.3.15 "alloc"
-//   futures-channel-0.3.15 "alloc,futures-sink,sink"
-//   futures-core-0.3.15 "alloc"
-//   futures-io-0.3.15
-//   futures-sink-0.3.15 "alloc"
-//   futures-task-0.3.15 "alloc"
-//   futures-util-0.3.15 "alloc,futures-sink,sink"
-//   intrusive-collections-0.9.1 "alloc,default"
-//   itoa-0.4.7
-//   libc-0.2.97 "default,std"
-//   memoffset-0.5.6 "default"
-//   paste-1.0.5
-//   pin-project-lite-0.2.7
-//   pin-utils-0.1.0
-//   proc-macro2-1.0.27 "default,proc-macro"
-//   quote-1.0.9 "default,proc-macro"
-//   ryu-1.0.5
-//   serde-1.0.126 "default,derive,serde_derive,std"
-//   serde_derive-1.0.126 "default"
-//   serde_json-1.0.64 "default,std"
-//   slab-0.4.3 "default,std"
-//   smallvec-1.6.1
-//   syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-//   thiserror-1.0.25
-//   thiserror-impl-1.0.25
-//   unicode-xid-0.2.2 "default"
diff --git a/base/src/ioctl.rs b/base/src/ioctl.rs
index 307d5d9..d5a91fc 100644
--- a/base/src/ioctl.rs
+++ b/base/src/ioctl.rs
@@ -6,16 +6,22 @@
 use std::os::raw::{c_int, c_ulong, c_void};
 
 /// Run an ioctl with no arguments.
+/// # Safety
+/// The caller is responsible for determining the safety of the particular ioctl.
 pub unsafe fn ioctl<F: AsRawDescriptor>(descriptor: &F, nr: IoctlNr) -> c_int {
     libc::ioctl(descriptor.as_raw_descriptor(), nr, 0)
 }
 
 /// Run an ioctl with a single value argument.
+/// # Safety
+/// The caller is responsible for determining the safety of the particular ioctl.
 pub unsafe fn ioctl_with_val(descriptor: &dyn AsRawDescriptor, nr: IoctlNr, arg: c_ulong) -> c_int {
     libc::ioctl(descriptor.as_raw_descriptor(), nr, arg)
 }
 
 /// Run an ioctl with an immutable reference.
+/// # Safety
+/// The caller is responsible for determining the safety of the particular ioctl.
 pub unsafe fn ioctl_with_ref<T>(descriptor: &dyn AsRawDescriptor, nr: IoctlNr, arg: &T) -> c_int {
     libc::ioctl(
         descriptor.as_raw_descriptor(),
@@ -25,6 +31,8 @@
 }
 
 /// Run an ioctl with a mutable reference.
+/// # Safety
+/// The caller is responsible for determining the safety of the particular ioctl.
 pub unsafe fn ioctl_with_mut_ref<T>(
     descriptor: &dyn AsRawDescriptor,
     nr: IoctlNr,
@@ -38,6 +46,8 @@
 }
 
 /// Run an ioctl with a raw pointer.
+/// # Safety
+/// The caller is responsible for determining the safety of the particular ioctl.
 pub unsafe fn ioctl_with_ptr<T>(
     descriptor: &dyn AsRawDescriptor,
     nr: IoctlNr,
@@ -47,6 +57,8 @@
 }
 
 /// Run an ioctl with a mutable raw pointer.
+/// # Safety
+/// The caller is responsible for determining the safety of the particular ioctl.
 pub unsafe fn ioctl_with_mut_ptr<T>(
     descriptor: &dyn AsRawDescriptor,
     nr: IoctlNr,
diff --git a/base/src/mmap.rs b/base/src/mmap.rs
index d2bd4cb..16444fb 100644
--- a/base/src/mmap.rs
+++ b/base/src/mmap.rs
@@ -74,6 +74,7 @@
 }
 
 pub trait MemoryMappingBuilderUnix<'a> {
+    #[allow(clippy::wrong_self_convention)]
     fn from_descriptor(self, descriptor: &'a dyn AsRawDescriptor) -> MemoryMappingBuilder;
 }
 
@@ -89,6 +90,7 @@
     /// Build the memory mapping given the specified descriptor to mapped memory
     ///
     /// Default: Create a new memory mapping.
+    #[allow(clippy::wrong_self_convention)]
     fn from_descriptor(mut self, descriptor: &'a dyn AsRawDescriptor) -> MemoryMappingBuilder {
         self.descriptor = Some(descriptor);
         self
@@ -114,7 +116,7 @@
     ///
     /// Note: this is a forward looking interface to accomodate platforms that
     /// require special handling for file backed mappings.
-    #[allow(unused_mut)]
+    #[allow(clippy::wrong_self_convention, unused_mut)]
     pub fn from_file(mut self, file: &'a File) -> MemoryMappingBuilder {
         self.descriptor = Some(file as &dyn AsRawDescriptor);
         self
diff --git a/base/src/shm.rs b/base/src/shm.rs
index a445f3d..eb4da79 100644
--- a/base/src/shm.rs
+++ b/base/src/shm.rs
@@ -87,9 +87,9 @@
     }
 }
 
-impl Into<SafeDescriptor> for SharedMemory {
-    fn into(self) -> SafeDescriptor {
+impl From<SharedMemory> for SafeDescriptor {
+    fn from(sm: SharedMemory) -> SafeDescriptor {
         // Safe because we own the SharedMemory at this point.
-        unsafe { SafeDescriptor::from_raw_descriptor(self.into_raw_descriptor()) }
+        unsafe { SafeDescriptor::from_raw_descriptor(sm.into_raw_descriptor()) }
     }
 }
diff --git a/base/src/tube.rs b/base/src/tube.rs
index 21917c9..613731b 100644
--- a/base/src/tube.rs
+++ b/base/src/tube.rs
@@ -104,13 +104,6 @@
         .map_err(Error::Json)
     }
 
-    /// Returns true if there is a packet ready to `recv` without blocking.
-    ///
-    /// If there is an error trying to determine if there is a packet ready, this returns false.
-    pub fn is_packet_ready(&self) -> bool {
-        self.socket.get_readable_bytes().unwrap_or(0) > 0
-    }
-
     pub fn set_send_timeout(&self, timeout: Option<Duration>) -> Result<()> {
         self.socket
             .set_write_timeout(timeout)
@@ -157,9 +150,9 @@
     }
 }
 
-impl Into<Tube> for AsyncTube {
-    fn into(self) -> Tube {
-        self.inner.into_source()
+impl From<AsyncTube> for Tube {
+    fn from(at: AsyncTube) -> Tube {
+        at.inner.into_source()
     }
 }
 
diff --git a/bin/clippy b/bin/clippy
index 6d8568d..3fa2a24 100755
--- a/bin/clippy
+++ b/bin/clippy
@@ -56,7 +56,6 @@
     should_implement_trait
     single_char_pattern
     too_many_arguments
-    transmute_ptr_to_ptr
     trivially_copy_pass_by_ref
     type_complexity
     unreadable_literal
@@ -65,6 +64,26 @@
     new-ret-no-self
 )
 
+FEATURES=(
+    default
+    direct
+    audio
+    gpu
+    plugin
+    tpm
+    usb
+    video-decoder
+    video-encoder
+    wl-dmabuf
+    x
+    virgl_renderer_next
+    composite-disk
+    virgl_renderer
+    gfxstream
+    gdb
+)
+printf -v FEATURES_LIST '%s,' "${FEATURES[@]}"
+
 # Needed or else clippy won't re-run on code that has already compiled.
 if [[ "${USE_CACHE}" == false ]]; then
     cargo clean
@@ -76,5 +95,5 @@
 RUSTFLAGS="${RUSTFLAGS:-}"
 export RUSTFLAGS="$RUSTFLAGS --sysroot=$RUST_SYSROOT"
 
-cargo clippy --all-features --all-targets -- ${SUPPRESS[@]/#/-Aclippy::} \
-    "${CLIPPY_ARGS[@]}" -D warnings
+cargo clippy --features ${FEATURES_LIST} --all-targets -- \
+    ${SUPPRESS[@]/#/-Aclippy::} "${CLIPPY_ARGS[@]}" -D warnings
diff --git a/bit_field/Android.bp b/bit_field/Android.bp
index 568f047..a5a7a7b 100644
--- a/bit_field/Android.bp
+++ b/bit_field/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace --no-subdir.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace --no-subdir.
 // Do not modify this file as changes will be overridden on upgrade.
 
 package {
@@ -11,7 +11,7 @@
 }
 
 rust_defaults {
-    name: "bit_field_defaults",
+    name: "bit_field_test_defaults",
     defaults: ["crosvm_defaults"],
     crate_name: "bit_field",
     srcs: ["src/lib.rs"],
@@ -23,7 +23,7 @@
 
 rust_test_host {
     name: "bit_field_host_test_src_lib",
-    defaults: ["bit_field_defaults"],
+    defaults: ["bit_field_test_defaults"],
     test_options: {
         unit_test: true,
     },
@@ -31,11 +31,11 @@
 
 rust_test {
     name: "bit_field_device_test_src_lib",
-    defaults: ["bit_field_defaults"],
+    defaults: ["bit_field_test_defaults"],
 }
 
 rust_defaults {
-    name: "bit_field_defaults_bit_field",
+    name: "bit_field_test_defaults_bit_field",
     defaults: ["crosvm_defaults"],
     crate_name: "bit_field",
     test_suites: ["general-tests"],
@@ -49,7 +49,7 @@
 
 rust_test_host {
     name: "bit_field_host_test_tests_test_enum",
-    defaults: ["bit_field_defaults_bit_field"],
+    defaults: ["bit_field_test_defaults_bit_field"],
     srcs: ["tests/test_enum.rs"],
     test_options: {
         unit_test: true,
@@ -58,13 +58,13 @@
 
 rust_test {
     name: "bit_field_device_test_tests_test_enum",
-    defaults: ["bit_field_defaults_bit_field"],
+    defaults: ["bit_field_test_defaults_bit_field"],
     srcs: ["tests/test_enum.rs"],
 }
 
 rust_test_host {
     name: "bit_field_host_test_tests_test_tuple_struct",
-    defaults: ["bit_field_defaults_bit_field"],
+    defaults: ["bit_field_test_defaults_bit_field"],
     srcs: ["tests/test_tuple_struct.rs"],
     test_options: {
         unit_test: true,
@@ -73,7 +73,7 @@
 
 rust_test {
     name: "bit_field_device_test_tests_test_tuple_struct",
-    defaults: ["bit_field_defaults_bit_field"],
+    defaults: ["bit_field_test_defaults_bit_field"],
     srcs: ["tests/test_tuple_struct.rs"],
 }
 
@@ -86,9 +86,3 @@
     edition: "2018",
     proc_macros: ["libbit_field_derive"],
 }
-
-// dependent_library ["feature_list"]
-//   proc-macro2-1.0.27 "default,proc-macro"
-//   quote-1.0.9 "default,proc-macro"
-//   syn-1.0.73 "clone-impls,default,derive,parsing,printing,proc-macro,quote"
-//   unicode-xid-0.2.2 "default"
diff --git a/bit_field/bit_field_derive/Android.bp b/bit_field/bit_field_derive/Android.bp
index d357ce7..e3923f0 100644
--- a/bit_field/bit_field_derive/Android.bp
+++ b/bit_field/bit_field_derive/Android.bp
@@ -1,5 +1,5 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
-// Manually disabled tests which depend on host only crate.
+// This file is generated by cargo2android.py --config cargo2android.json.
+// Do not modify this file as changes will be overridden on upgrade.
 
 package {
     // See: http://go/android-license-faq
@@ -10,34 +10,6 @@
     default_applicable_licenses: ["external_crosvm_license"],
 }
 
-rust_defaults {
-    name: "bit_field_derive_defaults",
-    defaults: ["crosvm_defaults"],
-    crate_name: "bit_field_derive",
-    srcs: ["bit_field_derive.rs"],
-    test_suites: ["general-tests"],
-    auto_gen_config: true,
-    edition: "2018",
-    rustlibs: [
-        "libproc_macro2",
-        "libquote",
-        "libsyn",
-    ],
-}
-
-//rust_test_host {
-//    name: "bit_field_derive_host_test_bit_field_derive",
-//    defaults: ["bit_field_derive_defaults"],
-//    test_options: {
-//        unit_test: true,
-//    },
-//}
-
-//rust_test {
-//    name: "bit_field_derive_device_test_bit_field_derive",
-//    defaults: ["bit_field_derive_defaults"],
-//}
-
 rust_proc_macro {
     name: "libbit_field_derive",
     defaults: ["crosvm_defaults"],
@@ -50,9 +22,3 @@
         "libsyn",
     ],
 }
-
-// dependent_library ["feature_list"]
-//   proc-macro2-1.0.27 "default,proc-macro"
-//   quote-1.0.9 "default,proc-macro"
-//   syn-1.0.73 "clone-impls,default,derive,parsing,printing,proc-macro,quote"
-//   unicode-xid-0.2.2 "default"
diff --git a/bit_field/bit_field_derive/cargo2android.json b/bit_field/bit_field_derive/cargo2android.json
new file mode 100644
index 0000000..9a6eee3
--- /dev/null
+++ b/bit_field/bit_field_derive/cargo2android.json
@@ -0,0 +1,7 @@
+{
+  "add_workspace": true,
+  "device": true,
+  "global_defaults": "crosvm_defaults",
+  "run": true,
+  "tests": false
+}
\ No newline at end of file
diff --git a/cargo2android.json b/cargo2android.json
new file mode 100644
index 0000000..176988e
--- /dev/null
+++ b/cargo2android.json
@@ -0,0 +1,15 @@
+{
+        "add-module-block": "cargo2android_module.bp",
+        "add-toplevel-block": "cargo2android_defaults.bp",
+        "dependencies": true,
+        "dependency-blocklist": ["aarch64", "x86_64"],
+        "device": true,
+        "features": "default,gfxstream",
+        "global_defaults": "crosvm_defaults",
+        "no-pkg-vers": true,
+        "no-subdir": true,
+        "patch": "patches/Android.bp.patch",
+        "run": true,
+        "test-blocklist": ["tests/plugins.rs"],
+        "tests": true
+}
\ No newline at end of file
diff --git a/cargo2android_defaults.bp b/cargo2android_defaults.bp
new file mode 100644
index 0000000..e046a78
--- /dev/null
+++ b/cargo2android_defaults.bp
@@ -0,0 +1,24 @@
+rust_defaults {
+    name: "crosvm_defaults",
+    edition: "2018",
+    enabled: false,
+    target: {
+        linux_glibc_x86_64: {
+            enabled: true,
+        },
+        android64: {
+            compile_multilib: "64",
+            enabled: true,
+        },
+        linux_bionic_arm64: {
+            enabled: true,
+        },
+        darwin: {
+            enabled: false,
+        },
+    },
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.virt",
+    ],
+}
\ No newline at end of file
diff --git a/cargo2android_module.bp b/cargo2android_module.bp
new file mode 100644
index 0000000..c4241d0
--- /dev/null
+++ b/cargo2android_module.bp
@@ -0,0 +1,25 @@
+arch: {
+    x86_64: {
+        rustlibs: ["libx86_64_rust"],
+    },
+    arm64: {
+        rustlibs: ["libaarch64"],
+    },
+},
+target: {
+    linux_glibc_x86_64: {
+        features: [
+            "gdb",
+            "gdbstub",
+        ],
+        rustlibs: [
+            "libgdbstub",
+            "libgdbstub_arch",
+            "libthiserror",
+        ],
+    },
+},
+ld_flags: [
+    "-Wl,--rpath,\\$$ORIGIN",
+    "-Wl,--rpath,\\$$ORIGIN/../../lib64",
+]
\ No newline at end of file
diff --git a/ci/README.md b/ci/README.md
index 52ac317..58ffa7e 100644
--- a/ci/README.md
+++ b/ci/README.md
@@ -10,19 +10,10 @@
 
 ### Setting up the source
 
-Since crosvm is part of chromiumos, and uses a couple of it's projects as
-dependencies, you need a standard chromiumos checkout as described by the
-[ChromiumOS Developer Guide](https://chromium.googlesource.com/chromiumos/docs/+/master/developer_guide.md#Get-the-Source).
-
-To reduce the number of repositories to download, you can use the `-g crosvm`
-argument on `repo init`. This will be significantly faster:
-
-In summary:
+Crosvm requires a bunch of dependencies that are checked out via submodules:
 
 ```
-$ repo init -u https://chromium.googlesource.com/chromiumos/manifest.git --repo-url https://chromium.googlesource.com/external/repo.git -g crosvm
-$ repo sync -j4
-$ cd src/platform/crosvm
+$ git submodule update --init
 ```
 
 ### Installing Podman (or Docker)
diff --git a/ci/build_environment/Makefile b/ci/build_environment/Makefile
index 716daa4..619310e 100644
--- a/ci/build_environment/Makefile
+++ b/ci/build_environment/Makefile
@@ -2,12 +2,12 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 #
-# This makefile is run by the ./ci/crosvm_* containers to build ChromiumOS
-# dependencies required by crosvm.
+# This makefile is run by the ./ci/crosvm_* containers to build native third
+# party dependencies required by crosvm.
 #
 # Setting TARGET_ARCH=aarch64 enables cross-compilation for aarch64.
 
-SRC ?= /workspace/src
+SRC ?= /workspace/src/crosvm
 BUILD ?= /workspace/scratch/build
 LIB ?= /workspace/scratch/lib
 TARGET_ARCH ?=
@@ -36,7 +36,7 @@
 
 tpm2:
 	mkdir -p $(BUILD)/tpm2
-	$(MAKE) -C $(SRC)/third_party/tpm2 \
+	$(MAKE) -C $(SRC)/tpm2-sys/libtpm2 \
 		obj=$(BUILD)/tpm2 \
 		AR=$(CROSS_COMPILE)ar \
 		CC=$(CROSS_COMPILE)gcc \
@@ -44,13 +44,13 @@
 
 minijail:
 	mkdir -p $(BUILD)/minijail
-	$(MAKE) -C $(SRC)/aosp/external/minijail \
+	$(MAKE) -C $(SRC)/third_party/minijail \
 		OUT=$(BUILD)/minijail \
 		CROSS_COMPILE=$(CROSS_COMPILE)
 
 minigbm:
 	mkdir -p $(BUILD)/minigbm
-	$(MAKE) -C $(SRC)/platform/minigbm \
+	$(MAKE) -C $(SRC)/third_party/minigbm \
 		OUT=$(BUILD)/minigbm  \
 		CROSS_COMPILE=$(CROSS_COMPILE)
 
@@ -60,7 +60,7 @@
 		$(SRC)/third_party/virglrenderer \
 		$(MESON_ARGS)
 
-	CPATH=$(SRC)/platform/minigbm \
+	CPATH=$(SRC)/third_party/minigbm \
 		meson compile -C $(BUILD)/virglrenderer
 
 
@@ -90,7 +90,7 @@
 	# minigbm
 	ln -sf $(BUILD)/minigbm/libminigbm.so.1.0.0 $(LIB)/libgbm.so
 	ln -sf $(LIB)/libgbm.so $(LIB)/libgbm.so.1
-	ln -sf $(SRC)/platform/minigbm/gbm.pc $(LIB)/pkgconfig/
+	ln -sf $(SRC)/third_party/minigbm/gbm.pc $(LIB)/pkgconfig/
 
 	# virglrenderer
 	ln -sf $(BUILD)/virglrenderer/src/libvirglrenderer.so $(LIB)
diff --git a/ci/crosvm_aarch64_builder/Dockerfile b/ci/crosvm_aarch64_builder/Dockerfile
index 42ba628..fa6830f 100644
--- a/ci/crosvm_aarch64_builder/Dockerfile
+++ b/ci/crosvm_aarch64_builder/Dockerfile
@@ -71,7 +71,7 @@
     /root/.ssh /root/.ssh
 
 # Setup entrypoint and interactive shell
-WORKDIR /workspace/src/platform/crosvm
+WORKDIR /workspace/src/crosvm
 COPY bashrc /root/.bashrc
 COPY entrypoint /workspace
 ENTRYPOINT ["/workspace/entrypoint"]
diff --git a/ci/crosvm_base/Dockerfile b/ci/crosvm_base/Dockerfile
index 690957d..c9d1091 100644
--- a/ci/crosvm_base/Dockerfile
+++ b/ci/crosvm_base/Dockerfile
@@ -52,8 +52,10 @@
 
 # Install the current crosvm rust toolchain via rustup.
 COPY rust-toolchain ./
-RUN curl https://sh.rustup.rs -sSf | sh -s -- \
-    -y \
+RUN curl https://static.rust-lang.org/rustup/archive/1.24.3/x86_64-unknown-linux-gnu/rustup-init -sSf -o rustup-init \
+    && echo "3dc5ef50861ee18657f9db2eeb7392f9c2a6c95c90ab41e45ab4ca71476b4338 rustup-init" | sha256sum --check \
+    && chmod +x rustup-init \
+    && ./rustup-init -y \
     --profile minimal \
     -c rustfmt,clippy \
     --default-toolchain $(cat rust-toolchain)
@@ -78,4 +80,4 @@
 ENV CROSVM_CROS_BUILD=1
 
 # All commands will be executed in the crosvm src directory.
-WORKDIR /workspace/src/platform/crosvm
+WORKDIR /workspace/src/crosvm
diff --git a/ci/crosvm_builder/Dockerfile b/ci/crosvm_builder/Dockerfile
index 8c70dd8..3b3e02b 100644
--- a/ci/crosvm_builder/Dockerfile
+++ b/ci/crosvm_builder/Dockerfile
@@ -43,7 +43,7 @@
     /root/.ssh /root/.ssh
 
 # Setup entrypoint and interactive shell
-WORKDIR /workspace/src/platform/crosvm
+WORKDIR /workspace/src/crosvm
 COPY bashrc /root/.bashrc
 COPY entrypoint /workspace
 ENTRYPOINT ["/workspace/entrypoint"]
diff --git a/ci/crosvm_test_vm/Dockerfile b/ci/crosvm_test_vm/Dockerfile
index aa19f17..aa34173 100644
--- a/ci/crosvm_test_vm/Dockerfile
+++ b/ci/crosvm_test_vm/Dockerfile
@@ -24,7 +24,7 @@
 WORKDIR /workspace/vm
 
 RUN curl -sSfL -o rootfs.qcow2 \
-    "http://cloud.debian.org/images/cloud/bullseye/daily/20210208-542/debian-11-generic-${VM_ARCH}-daily-20210208-542.qcow2"
+    "http://cloud.debian.org/images/cloud/bullseye/daily/20210702-691/debian-11-generic-${VM_ARCH}-daily-20210702-691.qcow2"
 
 # Package `cloud_init_data.yaml` to be loaded during `first_boot.expect`
 COPY build/cloud_init_data.yaml ./
diff --git a/ci/image_tag b/ci/image_tag
index 5aed675..d9830c0 100644
--- a/ci/image_tag
+++ b/ci/image_tag
@@ -1 +1 @@
-r0007
+r0009
diff --git a/ci/kokoro/common.sh b/ci/kokoro/common.sh
index c1e1e8b..ac05184 100755
--- a/ci/kokoro/common.sh
+++ b/ci/kokoro/common.sh
@@ -3,8 +3,6 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-crosvm_root="${KOKORO_ARTIFACTS_DIR}"/git/crosvm
-
 # Enable SSH access to the kokoro builder.
 # Use the fusion2/ UI to trigger a build and set the DEBUG_SSH_KEY environment
 # variable to your public key, that will allow you to connect to the builder
@@ -21,42 +19,13 @@
 fi
 
 setup_source() {
-  if [ -z "${KOKORO_ARTIFACTS_DIR}" ]; then
+  if [ -z "${KOKORO_ARTIFACTS_DIR}/git" ]; then
     echo "This script must be run in kokoro"
     exit 1
   fi
 
-  cd "${KOKORO_ARTIFACTS_DIR}"
-
-  echo ""
-  echo "Downloading crosvm dependencies to $(pwd)/cros..."
-  mkdir cros
-  cd cros
-
-  # repo gets confused by pyenv, make sure we select 3.6.1 as our default
-  # version.
-  if command -v pyenv >/dev/null; then
-    echo "Selecting Python 3.6.1"
-    pyenv global 3.6.1
-  fi
-  curl -s https://storage.googleapis.com/git-repo-downloads/repo >repo
-  chmod +x repo
-  ./repo init --depth 1 \
-    -u https://chromium.googlesource.com/chromiumos/manifest.git \
-    --repo-url https://chromium.googlesource.com/external/repo.git \
-    -g crosvm || return 1
-  ./repo sync -j8 -c || return 1
-
-  # Bind mount source into cros checkout.
-  echo ""
-  echo "Mounting crosvm source to $(pwd)/src/platform/crosvm..."
-  rm -rf src/platform/crosvm && mkdir -p src/platform/crosvm
-  if command -v bindfs >/dev/null; then
-    bindfs "${crosvm_root}" src/platform/crosvm || return 1
-  else
-    sudo mount --bind "${crosvm_root}" src/platform/crosvm || return 1
-  fi
-
+  cd "${KOKORO_ARTIFACTS_DIR}/git/crosvm"
+  git submodule update --init
 }
 
 cleanup() {
@@ -66,12 +35,6 @@
     sleep 1h
   fi
 
-  if command -v bindfs >/dev/null; then
-    fusermount -uz "${KOKORO_ARTIFACTS_DIR}/cros/src/platform/crosvm"
-  else
-    sudo umount --lazy "${KOKORO_ARTIFACTS_DIR}/cros/src/platform/crosvm"
-  fi
-
   # List files in the logs directory which are uploaded to sponge.
   echo "Build Artifacts:"
   ls "${KOKORO_ARTIFACTS_DIR}/logs"
@@ -86,4 +49,4 @@
 
 # Set logs directory so we can copy them to sponge
 export CROSVM_BUILDER_LOGS_DIR="${KOKORO_ARTIFACTS_DIR}/logs"
-cd "${KOKORO_ARTIFACTS_DIR}/cros/src/platform/crosvm"
+cd "${KOKORO_ARTIFACTS_DIR}/git/crosvm"
diff --git a/ci/kokoro/simulate b/ci/kokoro/simulate
index bd192ab..2673a90 100755
--- a/ci/kokoro/simulate
+++ b/ci/kokoro/simulate
@@ -19,7 +19,8 @@
 main() {
   echo "Copying ${crosvm_src}/ to ${kokoro_src}"
   mkdir -p "${kokoro_src}"
-  rsync -arq --exclude "target" --exclude ".git" "${crosvm_src}/" "${kokoro_src}"
+  rsync -arq --exclude "target" --exclude "__pycache__" \
+    "${crosvm_src}/" "${kokoro_src}"
 
   # Run user-provided kokoro build script.
   export KOKORO_ARTIFACTS_DIR="${kokoro_root}/src"
diff --git a/ci/run_container.sh b/ci/run_container.sh
index 27ecc2a..1ed298a 100755
--- a/ci/run_container.sh
+++ b/ci/run_container.sh
@@ -11,14 +11,7 @@
 # CROSVM_BUILDER_SCRATCH_DIR or CROSVM_BUILDER_LOGS_DIR.
 
 crosvm_root=$(realpath "$(dirname $0)/..")
-cros_root=$(realpath "${crosvm_root}/../../..")
 
-if [ ! -d "${cros_root}/.repo" ]; then
-    echo "The CI builder must be run from a cros checkout. See ci/README.md"
-    exit 1
-fi
-
-# Parse parameters
 builder="$1"
 shift
 
@@ -43,14 +36,14 @@
 version=$(cat $(dirname $0)/image_tag)
 echo "Using builder: ${builder}:${version}"
 
-src="${cros_root}/src"
-echo "Using source directory: ${src} (Available at /workspace/src)"
+echo "Using source directory: ${crosvm_root} \
+(Available at /workspace/src/crosvm)"
 
 docker_args=(
     --rm
     --device /dev/kvm
     --volume /dev/log:/dev/log
-    --volume "${src}":/workspace/src:rw
+    --volume "${crosvm_root}":/workspace/src/crosvm:rw
 )
 
 if [ ! -z "${CROSVM_BUILDER_SCRATCH_DIR}" ]; then
diff --git a/ci/test_runner.py b/ci/test_runner.py
index 1207207..85bdc78 100644
--- a/ci/test_runner.py
+++ b/ci/test_runner.py
@@ -620,6 +620,8 @@
         print("--require-all needs to be run with --use-vm or --run-privileged")
         exit(1)
 
+    os.environ["RUST_BACKTRACE"] = "1"
+
     execute_tests(
         crate_requirements,
         feature_requirements,
diff --git a/ci/vm_tools/exec_binary_in_vm b/ci/vm_tools/exec_binary_in_vm
index 7b2c230..d01987b 100755
--- a/ci/vm_tools/exec_binary_in_vm
+++ b/ci/vm_tools/exec_binary_in_vm
@@ -8,6 +8,8 @@
 
 ${0%/*}/wait_for_vm_with_timeout || exit 1
 
+vm_tmp_dir=/var/tmp
+
 if [ "$1" = "--no-sync" ]; then
     shift
 else
@@ -19,10 +21,10 @@
 filename=$(basename $filepath)
 
 echo "Executing $filename ${@:2}"
-scp -q $filepath vm:/tmp/$filename
-ssh vm -q -t "cd /tmp && sudo ./$filename ${@:2}"
+scp -q $filepath vm:$vm_tmp_dir/$filename
+ssh vm -q -t "cd $vm_tmp_dir && sudo ./$filename ${@:2}"
 
 # Make sure to preserve the exit code of $filename after cleaning up the file.
 ret=$?
-ssh vm -q -t "rm /tmp/$filename"
+ssh vm -q -t "rm $vm_tmp_dir/$filename"
 exit $ret
diff --git a/ci/vm_tools/sync_deps b/ci/vm_tools/sync_deps
index a97b019..3697180 100755
--- a/ci/vm_tools/sync_deps
+++ b/ci/vm_tools/sync_deps
@@ -8,12 +8,13 @@
 
 ${0%/*}/wait_for_vm_with_timeout || exit 1
 
-crosvm_root="/workspace/src/platform/crosvm"
+crosvm_root="/workspace/src/crosvm"
 rust_toolchain=$(cat ${crosvm_root}/rust-toolchain)
 target_dir=$(
     cargo metadata --no-deps --format-version 1 |
         jq -r ".target_directory"
 )
+vm_tmp_dir=/var/tmp
 
 # List of shared objects used by crosvm that need to be synced.
 shared_objects=(
@@ -34,4 +35,4 @@
     )
 fi
 
-rsync -azPLq --rsync-path="sudo rsync" ${runtime_files} vm:/tmp
+rsync -azPLq --rsync-path="sudo rsync" ${runtime_files} vm:$vm_tmp_dir
diff --git a/common/README.md b/common/README.md
new file mode 100644
index 0000000..bfb228d
--- /dev/null
+++ b/common/README.md
@@ -0,0 +1,12 @@
+# Crosvm General Purpose Libraries
+
+The crates in this folder are general purpose libraries used by other projects
+in ChromeOS as well.
+
+To make them accessible independendly of crosvm, each of these crates is
+excluded from the crosvm workspace.
+
+## List of libraries
+
+- [cros-fuzz](cros-fuzz/):  Support crate for fuzzing rust code in ChromeOS
+- [p9](p9): Server implementation of the 9p file system protocol
diff --git a/common/cros-fuzz/Cargo.toml b/common/cros-fuzz/Cargo.toml
new file mode 100644
index 0000000..e91baf2
--- /dev/null
+++ b/common/cros-fuzz/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "cros_fuzz"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+edition = "2018"
+include = ["Cargo.toml", "src/*.rs"]
+
+[dependencies]
+rand_core = "0.4"
+
+[workspace]
diff --git a/common/cros-fuzz/OWNERS b/common/cros-fuzz/OWNERS
new file mode 100644
index 0000000..8a33102
--- /dev/null
+++ b/common/cros-fuzz/OWNERS
@@ -0,0 +1 @@
+chirantan@chromium.org
diff --git a/common/cros-fuzz/README.md b/common/cros-fuzz/README.md
new file mode 100644
index 0000000..d988980
--- /dev/null
+++ b/common/cros-fuzz/README.md
@@ -0,0 +1,3 @@
+# Support crate for fuzzing rust code
+
+The crate provides support for fuzzing rust code on Chrome OS.
diff --git a/common/cros-fuzz/cargo2android.json b/common/cros-fuzz/cargo2android.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/common/cros-fuzz/cargo2android.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/common/cros-fuzz/src/lib.rs b/common/cros-fuzz/src/lib.rs
new file mode 100644
index 0000000..da5bd6d
--- /dev/null
+++ b/common/cros-fuzz/src/lib.rs
@@ -0,0 +1,95 @@
+// Copyright 2019 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.
+
+//! Support crate for writing fuzzers in Chrome OS.
+//!
+//! The major features provided by this crate are:
+//!
+//! * The [`fuzz_target`] macro which wraps the body of the fuzzing code with
+//!   all with all the boilerplate needed to build and run it as a fuzzer on
+//!   Chrome OS infrastructure.
+//! * The [`FuzzRng`] type that provides a random number generator using fuzzer
+//!   input as the source of its randomness.  Fuzzers that need to generate
+//!   structured data can use this type in conjunction with the [`rand`] crate
+//!   to generate the data they need.
+//!
+//! # Getting Started
+//!
+//! To use this crate add it as a dependency to the fuzzer's `Cargo.toml` along
+//! with the crate to be fuzzed:
+//!
+//! ```Cargo.toml
+//! [dependencies]
+//! cros_fuzz = "*"
+//! your_crate = "*"
+//! ```
+//!
+//! Then use the [`fuzz_target`] macro to write the body of the fuzzer.  All
+//! fuzzers should use the `#![no_main]` crate attribute as the main function
+//! will be provided by the fuzzer runtime.
+//!
+//! ```rust,ignore
+//! #![no_main]
+//!
+//! use cros_fuzz::fuzz_target;
+//! use your_crate::some_function;
+//!
+//! fuzz_target!(|data: &[u8]| {
+//!     some_function(data);
+//! });
+//! ```
+//!
+//! [`FuzzRng`]: rand/struct.FuzzRng.html
+//! [`fuzz_target`]: macro.fuzz_target.html
+//! [`rand`]: https://docs.rs/rand
+
+pub mod rand;
+
+/// The main macro for writing a fuzzer.  The fuzzer runtime will repeatedly
+/// call the body of `fuzz_target!` with a slice of pseudo-random bytes, until
+/// your program hits an error condition (segfault, panic, etc).
+///
+/// # Examples
+///
+/// ```
+/// use std::str;
+/// # #[macro_use] extern crate cros_fuzz;
+///
+/// fuzz_target!(|data: &[u8]| {
+///     let _ = str::from_utf8(data);
+/// });
+///
+/// # fn main() {
+/// #    let buf = b"hello, world!";
+/// #    llvm_fuzzer_test_one_input(buf.as_ptr(), buf.len());
+/// # }
+/// ```
+#[macro_export]
+macro_rules! fuzz_target {
+    (|$bytes:ident| $body:block) => {
+        use std::panic;
+        use std::process;
+        use std::slice;
+
+        #[export_name = "LLVMFuzzerTestOneInput"]
+        fn llvm_fuzzer_test_one_input(data: *const u8, size: usize) -> i32 {
+            // We cannot unwind past ffi boundaries.
+            panic::catch_unwind(|| {
+                // Safe because the libfuzzer runtime will guarantee that `data` is
+                // at least `size` bytes long and that it will be valid for the lifetime
+                // of this function.
+                let $bytes = unsafe { slice::from_raw_parts(data, size) };
+
+                $body
+            })
+            .err()
+            .map(|_| process::abort());
+
+            0
+        }
+    };
+    (|$bytes:ident: &[u8]| $body:block) => {
+        fuzz_target!(|$bytes| $body);
+    };
+}
diff --git a/common/cros-fuzz/src/rand.rs b/common/cros-fuzz/src/rand.rs
new file mode 100644
index 0000000..e78f53c
--- /dev/null
+++ b/common/cros-fuzz/src/rand.rs
@@ -0,0 +1,214 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::cmp::min;
+use std::fmt;
+use std::mem::size_of;
+use std::result::Result;
+
+use rand_core::{Error, ErrorKind, RngCore};
+
+/// A random number generator that uses fuzzer input as the source of its
+/// randomness.  When run on the same input, it provides the same output, as
+/// long as its methods are called in the same order and with the same
+/// arguments.
+pub struct FuzzRng<'a> {
+    buf: &'a [u8],
+}
+
+impl<'a> FuzzRng<'a> {
+    /// Creates a new `FuzzRng` from `buf`, which should be part or all of an
+    /// input buffer provided by a fuzzing library.
+    pub fn new(buf: &'a [u8]) -> FuzzRng<'a> {
+        FuzzRng { buf }
+    }
+
+    /// Consumes `self` and returns the inner slice.
+    pub fn into_inner(self) -> &'a [u8] {
+        let FuzzRng { buf } = self;
+        buf
+    }
+}
+
+impl<'a> RngCore for FuzzRng<'a> {
+    fn next_u32(&mut self) -> u32 {
+        let mut buf = [0u8; size_of::<u32>()];
+        self.fill_bytes(&mut buf);
+
+        u32::from_ne_bytes(buf)
+    }
+
+    fn next_u64(&mut self) -> u64 {
+        let mut buf = [0u8; size_of::<u64>()];
+        self.fill_bytes(&mut buf);
+
+        u64::from_ne_bytes(buf)
+    }
+
+    fn fill_bytes(&mut self, dest: &mut [u8]) {
+        let amt = min(self.buf.len(), dest.len());
+        let (a, b) = self.buf.split_at(amt);
+        dest[..amt].copy_from_slice(a);
+        self.buf = b;
+
+        if amt < dest.len() {
+            // We didn't have enough data to fill the whole buffer.  Fill the rest
+            // with zeroes.  The compiler is smart enough to turn this into a memset.
+            for b in &mut dest[amt..] {
+                *b = 0;
+            }
+        }
+    }
+
+    fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
+        if self.buf.len() >= dest.len() {
+            Ok(self.fill_bytes(dest))
+        } else {
+            Err(Error::new(
+                ErrorKind::Unavailable,
+                "not enough data in fuzzer input",
+            ))
+        }
+    }
+}
+
+impl<'a> fmt::Debug for FuzzRng<'a> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "FuzzRng {{ {} bytes }}", self.buf.len())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn gen_u32() {
+        let val = 0xc2a744u32;
+        let buf = val.to_ne_bytes();
+        let mut rng = FuzzRng::new(&buf);
+
+        assert_eq!(rng.next_u32(), val);
+        assert_eq!(rng.next_u32(), 0);
+    }
+
+    #[test]
+    fn gen_u64() {
+        let val = 0xac75689deeu64;
+        let buf = val.to_ne_bytes();
+        let mut rng = FuzzRng::new(&buf);
+
+        assert_eq!(rng.next_u64(), val);
+        assert_eq!(rng.next_u64(), 0);
+    }
+
+    #[test]
+    fn fill_bytes() {
+        let buf = &[
+            0xed, 0x90, 0xf3, 0xa4, 0x8f, 0xbf, 0x6e, 0xdb, 0x68, 0xb9, 0x1f, 0x9a, 0x13, 0xfc,
+            0x9f, 0xc8, 0x9e, 0xfa, 0x4a, 0x02, 0x5e, 0xc8, 0xb1, 0xe5, 0x2d, 0x59, 0x22, 0x89,
+            0x10, 0x23, 0xc3, 0x31, 0x6c, 0x42, 0x40, 0xce, 0xfe, 0x6e, 0x5c, 0x3d, 0x10, 0xba,
+            0x0d, 0x11, 0xbc, 0x6a, 0x1f, 0x21, 0xc9, 0x72, 0x37, 0xba, 0xfa, 0x00, 0xb2, 0xa8,
+            0x51, 0x6d, 0xb2, 0x94, 0xf2, 0x34, 0xf8, 0x3c, 0x21, 0xc9, 0x59, 0x24, 0xd8, 0x77,
+            0x51, 0x3f, 0x64, 0xde, 0x19, 0xc8, 0xb3, 0x03, 0x26, 0x81, 0x85, 0x4c, 0xef, 0xb0,
+            0xd5, 0xd8, 0x65, 0xe1, 0x89, 0x8f, 0xb7, 0x14, 0x9b, 0x0d, 0xd9, 0xcb, 0xda, 0x35,
+            0xb2, 0xff, 0xd5, 0xd1, 0xae, 0x38, 0x55, 0xd5, 0x65, 0xba, 0xdc, 0xa1, 0x82, 0x62,
+            0xbf, 0xe6, 0x3d, 0x7a, 0x8f, 0x13, 0x65, 0x2f, 0x4b, 0xdc, 0xcb, 0xee, 0xd8, 0x99,
+            0x2c, 0x21, 0x97, 0xc8, 0x6e, 0x8e, 0x09, 0x0f, 0xf1, 0x4b, 0x85, 0xb5, 0x0f, 0x52,
+            0x82, 0x7f, 0xe0, 0x23, 0xc5, 0x9a, 0x6a, 0x7c, 0xf1, 0x46, 0x7d, 0xbf, 0x3f, 0x14,
+            0x0d, 0x41, 0x09, 0xd5, 0x63, 0x70, 0xa1, 0x0e, 0x04, 0x3c, 0x06, 0x0a, 0x0b, 0x5c,
+            0x95, 0xaf, 0xbd, 0xf5, 0x4b, 0x7f, 0xbe, 0x8d, 0xe2, 0x09, 0xce, 0xa2, 0xf6, 0x1e,
+            0x58, 0xd8, 0xda, 0xd4, 0x56, 0x56, 0xe1, 0x32, 0x30, 0xef, 0x0f, 0x2e, 0xed, 0xb9,
+            0x14, 0x57, 0xa8, 0x8a, 0x9c, 0xd8, 0x58, 0x7f, 0xd9, 0x4f, 0x11, 0xb2, 0x7a, 0xcf,
+            0xc0, 0xef, 0xf3, 0xc7, 0xc1, 0xc5, 0x1e, 0x86, 0x47, 0xc6, 0x42, 0x71, 0x15, 0xc8,
+            0x25, 0x1d, 0x94, 0x00, 0x8d, 0x04, 0x37, 0xe7, 0xfe, 0xf6, 0x10, 0x28, 0xe5, 0xb2,
+            0xef, 0x95, 0xa6, 0x53, 0x20, 0xf8, 0x51, 0xdb, 0x54, 0x99, 0x40, 0x4a, 0x7c, 0xd6,
+            0x90, 0x4a, 0x55, 0xdc, 0x37, 0xb8, 0xbc, 0x0b, 0xc4, 0x54, 0xd1, 0x9b, 0xb3, 0x8c,
+            0x09, 0x55, 0x77, 0xf5, 0x1b, 0xa7, 0x36, 0x06, 0x29, 0x4c, 0xa3, 0x26, 0x35, 0x1b,
+            0x29, 0xa3, 0xa3, 0x45, 0x74, 0xee, 0x0b, 0x78, 0xf8, 0x69, 0x70, 0xa4, 0x1d, 0x11,
+            0x7a, 0x91, 0xca, 0x4c, 0x83, 0xb3, 0xbf, 0xf6, 0x7f, 0x54, 0xca, 0xdb, 0x1f, 0xc4,
+            0xd2, 0xb2, 0x23, 0xfa, 0xc0, 0x24, 0x77, 0x74, 0x61, 0x9e, 0x0b, 0x77, 0x49, 0x29,
+            0xf1, 0xd9, 0xbf, 0xf0, 0x5e, 0x99, 0xa6, 0xf1, 0x00, 0xa4, 0x7f, 0xa0, 0xb1, 0x6b,
+            0xd8, 0xbe, 0xef, 0xa0, 0xa1, 0xa5, 0x33, 0x9c, 0xc3, 0x95, 0xaa, 0x9f,
+        ];
+
+        let mut rng = FuzzRng::new(&buf[..]);
+        let mut dest = Vec::with_capacity(buf.len());
+        for chunk in buf.chunks(11) {
+            dest.resize(chunk.len(), 0);
+            rng.fill_bytes(&mut dest);
+
+            assert_eq!(chunk, &*dest);
+        }
+
+        dest.resize(97, 0x2c);
+        rng.fill_bytes(&mut dest);
+
+        let mut zero_buf = Vec::with_capacity(dest.len());
+        zero_buf.resize(dest.len(), 0);
+
+        assert_eq!(zero_buf, dest);
+    }
+
+    #[test]
+    fn try_fill_bytes() {
+        let buf = &[
+            0xdb, 0x35, 0xad, 0x4e, 0x9d, 0xf5, 0x2d, 0xf6, 0x0d, 0xc5, 0xd2, 0xfc, 0x9f, 0x4c,
+            0xb5, 0x12, 0xe3, 0x78, 0x40, 0x8d, 0x8b, 0xa1, 0x5c, 0xfe, 0x66, 0x49, 0xa9, 0xc0,
+            0x43, 0xa0, 0x95, 0xae, 0x31, 0x99, 0xd2, 0xaa, 0xbc, 0x85, 0x9e, 0x4b, 0x08, 0xca,
+            0x59, 0x21, 0x2b, 0x66, 0x37, 0x6a, 0xb9, 0xb2, 0xd8, 0x71, 0x84, 0xdd, 0xf6, 0x47,
+            0xa5, 0xb9, 0x87, 0x9f, 0x24, 0x97, 0x01, 0x65, 0x15, 0x38, 0x01, 0xd6, 0xb6, 0xf2,
+            0x80,
+        ];
+        let mut rng = FuzzRng::new(&buf[..]);
+        let mut dest = Vec::with_capacity(buf.len());
+        for chunk in buf.chunks(13) {
+            dest.resize(chunk.len(), 0);
+            rng.try_fill_bytes(&mut dest)
+                .expect("failed to fill bytes while data is remaining");
+
+            assert_eq!(chunk, &*dest);
+        }
+
+        dest.resize(buf.len(), 0);
+        rng.try_fill_bytes(&mut dest)
+            .expect_err("successfully filled bytes when no data is remaining");
+    }
+
+    #[test]
+    fn try_fill_bytes_partial() {
+        let buf = &[
+            0x8b, 0xe3, 0x20, 0x8d, 0xe0, 0x0b, 0xbe, 0x51, 0xa6, 0xec, 0x8a, 0xb5, 0xd6, 0x17,
+            0x04, 0x3f, 0x87, 0xae, 0xc8, 0xe8, 0xf8, 0xe7, 0xd4, 0xbd, 0xf3, 0x4e, 0x74, 0xcf,
+            0xbf, 0x0e, 0x9d, 0xe5, 0x78, 0xc3, 0xe6, 0x44, 0xb8, 0xd1, 0x40, 0xda, 0x63, 0x9f,
+            0x48, 0xf4, 0x09, 0x9c, 0x5c, 0x5f, 0x36, 0x0b, 0x0d, 0x2b, 0xe3, 0xc7, 0xcc, 0x3e,
+            0x9a, 0xb9, 0x0a, 0xca, 0x6d, 0x90, 0x77, 0x3b, 0x7a, 0x50, 0x16, 0x13, 0x5d, 0x20,
+            0x70, 0xc0, 0x88, 0x04, 0x9c, 0xac, 0x2b, 0xd6, 0x61, 0xa0, 0xbe, 0xa4, 0xff, 0xbd,
+            0xac, 0x9c, 0xa1, 0xb2, 0x95, 0x26, 0xeb, 0x99, 0x46, 0x67, 0xe4, 0xcd, 0x88, 0x7b,
+            0x20, 0x4d, 0xb2, 0x92, 0x40, 0x9f, 0x1c, 0xbd, 0xba, 0x22, 0xff, 0xca, 0x89, 0x3c,
+            0x3b,
+        ];
+
+        let mut rng = FuzzRng::new(&buf[..]);
+        let mut dest = Vec::with_capacity(buf.len());
+        dest.resize((buf.len() / 2) + 1, 0);
+
+        // The first time should be successful because there is enough data left
+        // in the buffer.
+        rng.try_fill_bytes(&mut dest).expect("failed to fill bytes");
+        assert_eq!(&buf[..dest.len()], &*dest);
+
+        // The second time should fail because while there is data in the buffer it
+        // is not enough to fill `dest`.
+        rng.try_fill_bytes(&mut dest)
+            .expect_err("filled bytes with insufficient data in buffer");
+
+        // This should succeed because `dest` is exactly big enough to hold all the remaining
+        // data in the buffer.
+        dest.resize(buf.len() - dest.len(), 0);
+        rng.try_fill_bytes(&mut dest)
+            .expect("failed to fill bytes with exact-sized buffer");
+        assert_eq!(&buf[buf.len() - dest.len()..], &*dest);
+    }
+}
diff --git a/rand_ish/Android.bp b/common/p9/Android.bp
similarity index 61%
copy from rand_ish/Android.bp
copy to common/p9/Android.bp
index aa1fd97..bb6142e 100644
--- a/rand_ish/Android.bp
+++ b/common/p9/Android.bp
@@ -1,6 +1,8 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace --no-subdir.
 // Do not modify this file as changes will be overridden on upgrade.
 
+
+
 package {
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
@@ -11,33 +13,43 @@
 }
 
 rust_library {
-    name: "librand_ish",
+    name: "libp9",
     defaults: ["crosvm_defaults"],
     host_supported: true,
-    crate_name: "rand_ish",
+    crate_name: "p9",
     srcs: ["src/lib.rs"],
     edition: "2018",
+    rustlibs: [
+        "liblibc",
+        "libsys_util",
+    ],
+    proc_macros: ["libwire_format_derive"],
 }
 
 rust_defaults {
-    name: "rand_ish_defaults",
+    name: "p9_test_defaults",
     defaults: ["crosvm_defaults"],
-    crate_name: "rand_ish",
+    crate_name: "p9",
     srcs: ["src/lib.rs"],
     test_suites: ["general-tests"],
     auto_gen_config: true,
     edition: "2018",
+    rustlibs: [
+        "liblibc",
+        "libsys_util",
+    ],
+    proc_macros: ["libwire_format_derive"],
 }
 
 rust_test_host {
-    name: "rand_ish_host_test_src_lib",
-    defaults: ["rand_ish_defaults"],
+    name: "p9_host_test_src_lib",
+    defaults: ["p9_test_defaults"],
     test_options: {
         unit_test: true,
     },
 }
 
 rust_test {
-    name: "rand_ish_device_test_src_lib",
-    defaults: ["rand_ish_defaults"],
+    name: "p9_device_test_src_lib",
+    defaults: ["p9_test_defaults"],
 }
diff --git a/common/p9/Cargo.toml b/common/p9/Cargo.toml
new file mode 100644
index 0000000..f3725dc
--- /dev/null
+++ b/common/p9/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "p9"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+edition = "2018"
+
+[dependencies]
+libc = "*"
+sys_util = { path = "../../sys_util" } # provided by ebuild
+wire_format_derive = { path = "wire_format_derive", version = "*" }
+
+[features]
+trace = []
+
+[workspace]
diff --git a/common/p9/OWNERS b/common/p9/OWNERS
new file mode 100644
index 0000000..8a33102
--- /dev/null
+++ b/common/p9/OWNERS
@@ -0,0 +1 @@
+chirantan@chromium.org
diff --git a/common/p9/README.md b/common/p9/README.md
new file mode 100644
index 0000000..b85cc15
--- /dev/null
+++ b/common/p9/README.md
@@ -0,0 +1,21 @@
+# p9 - Server implementation of the [9p] file system protocol
+
+This directory contains the protocol definition and a server implementation of
+the [9p] file system protocol.
+
+*   [wire_format_derive] - A [procedural macro] that derives the serialization
+    and de-serialization implementation for a struct into the [9p] wire
+    format.
+*   [src/protocol] - Defines all the messages used in the [9p] protocol.  Also
+    implements serialization and de-serialization for some base types
+    (integers, strings, vectors) that form the foundation of all [9p]
+    messages.  Wire format implementations for all other messages are derived
+    using the `wire_format_derive` macro.
+*   [src/server.rs] - Implements a full [9p] server, carrying out file system
+    requests on behalf of clients.
+
+[9p]: http://man.cat-v.org/plan_9/5/intro
+[procedural macro]: https://doc.rust-lang.org/proc_macro/index.html
+[src/protocol]: src/protocol/
+[src/server.rs]: src/server.rs
+[wire_format_derive]: wire_format_derive/
diff --git a/common/p9/fuzz/Cargo.toml b/common/p9/fuzz/Cargo.toml
new file mode 100644
index 0000000..fe9af4a
--- /dev/null
+++ b/common/p9/fuzz/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "p9-fuzz"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+edition = "2018"
+
+[dependencies]
+p9 = { path = "../" }
+cros_fuzz = { path = "../../cros-fuzz" }  # provided by ebuild
+
+[workspace]
+members = ["."]
+
+[[bin]]
+name = "p9_tframe_decode_fuzzer"
+path = "tframe_decode.rs"
+
+[patch.crates-io]
+wire_format_derive = { path = "../wire_format_derive" }
diff --git a/common/p9/fuzz/cargo2android.json b/common/p9/fuzz/cargo2android.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/common/p9/fuzz/cargo2android.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/common/p9/fuzz/tframe_decode.rs b/common/p9/fuzz/tframe_decode.rs
new file mode 100644
index 0000000..6cdeae3
--- /dev/null
+++ b/common/p9/fuzz/tframe_decode.rs
@@ -0,0 +1,12 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#![no_main]
+
+use cros_fuzz::fuzz_target;
+use p9::fuzzing::tframe_decode;
+
+fuzz_target!(|bytes: &[u8]| {
+    tframe_decode(bytes);
+});
diff --git a/common/p9/src/fuzzing.rs b/common/p9/src/fuzzing.rs
new file mode 100644
index 0000000..47aa070
--- /dev/null
+++ b/common/p9/src/fuzzing.rs
@@ -0,0 +1,13 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::io::Cursor;
+
+use crate::protocol::{Tframe, WireFormat};
+
+pub fn tframe_decode(bytes: &[u8]) {
+    let mut cursor = Cursor::new(bytes);
+
+    while Tframe::decode(&mut cursor).is_ok() {}
+}
diff --git a/common/p9/src/lib.rs b/common/p9/src/lib.rs
new file mode 100644
index 0000000..b00c706
--- /dev/null
+++ b/common/p9/src/lib.rs
@@ -0,0 +1,16 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+extern crate libc;
+
+#[macro_use]
+extern crate wire_format_derive;
+
+mod protocol;
+mod server;
+
+#[cfg(fuzzing)]
+pub mod fuzzing;
+
+pub use server::*;
diff --git a/common/p9/src/protocol/messages.rs b/common/p9/src/protocol/messages.rs
new file mode 100644
index 0000000..b5a03c0
--- /dev/null
+++ b/common/p9/src/protocol/messages.rs
@@ -0,0 +1,840 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::io::{self, ErrorKind, Read, Write};
+use std::mem;
+use std::string::String;
+use std::vec::Vec;
+
+use crate::protocol::wire_format::{Data, WireFormat};
+
+// Message type constants.  Taken from "include/net/9p/9p.h" in the linux kernel
+// tree.  The protocol specifies each R* message to be the corresponding T*
+// message plus one.
+const TLERROR: u8 = 6;
+const RLERROR: u8 = TLERROR + 1;
+const TSTATFS: u8 = 8;
+const RSTATFS: u8 = TSTATFS + 1;
+const TLOPEN: u8 = 12;
+const RLOPEN: u8 = TLOPEN + 1;
+const TLCREATE: u8 = 14;
+const RLCREATE: u8 = TLCREATE + 1;
+const TSYMLINK: u8 = 16;
+const RSYMLINK: u8 = TSYMLINK + 1;
+const TMKNOD: u8 = 18;
+const RMKNOD: u8 = TMKNOD + 1;
+const TRENAME: u8 = 20;
+const RRENAME: u8 = TRENAME + 1;
+const TREADLINK: u8 = 22;
+const RREADLINK: u8 = TREADLINK + 1;
+const TGETATTR: u8 = 24;
+const RGETATTR: u8 = TGETATTR + 1;
+const TSETATTR: u8 = 26;
+const RSETATTR: u8 = TSETATTR + 1;
+const TXATTRWALK: u8 = 30;
+const RXATTRWALK: u8 = TXATTRWALK + 1;
+const TXATTRCREATE: u8 = 32;
+const RXATTRCREATE: u8 = TXATTRCREATE + 1;
+const TREADDIR: u8 = 40;
+const RREADDIR: u8 = TREADDIR + 1;
+const TFSYNC: u8 = 50;
+const RFSYNC: u8 = TFSYNC + 1;
+const TLOCK: u8 = 52;
+const RLOCK: u8 = TLOCK + 1;
+const TGETLOCK: u8 = 54;
+const RGETLOCK: u8 = TGETLOCK + 1;
+const TLINK: u8 = 70;
+const RLINK: u8 = TLINK + 1;
+const TMKDIR: u8 = 72;
+const RMKDIR: u8 = TMKDIR + 1;
+const TRENAMEAT: u8 = 74;
+const RRENAMEAT: u8 = TRENAMEAT + 1;
+const TUNLINKAT: u8 = 76;
+const RUNLINKAT: u8 = TUNLINKAT + 1;
+const TVERSION: u8 = 100;
+const RVERSION: u8 = TVERSION + 1;
+const TAUTH: u8 = 102;
+const RAUTH: u8 = TAUTH + 1;
+const TATTACH: u8 = 104;
+const RATTACH: u8 = TATTACH + 1;
+const _TERROR: u8 = 106;
+const _RERROR: u8 = _TERROR + 1;
+const TFLUSH: u8 = 108;
+const RFLUSH: u8 = TFLUSH + 1;
+const TWALK: u8 = 110;
+const RWALK: u8 = TWALK + 1;
+const _TOPEN: u8 = 112;
+const _ROPEN: u8 = _TOPEN + 1;
+const _TCREATE: u8 = 114;
+const _RCREATE: u8 = _TCREATE + 1;
+const TREAD: u8 = 116;
+const RREAD: u8 = TREAD + 1;
+const TWRITE: u8 = 118;
+const RWRITE: u8 = TWRITE + 1;
+const TCLUNK: u8 = 120;
+const RCLUNK: u8 = TCLUNK + 1;
+const TREMOVE: u8 = 122;
+const RREMOVE: u8 = TREMOVE + 1;
+const _TSTAT: u8 = 124;
+const _RSTAT: u8 = _TSTAT + 1;
+const _TWSTAT: u8 = 126;
+const _RWSTAT: u8 = _TWSTAT + 1;
+
+/// A message sent from a 9P client to a 9P server.
+#[derive(Debug)]
+pub enum Tmessage {
+    Version(Tversion),
+    Flush(Tflush),
+    Walk(Twalk),
+    Read(Tread),
+    Write(Twrite),
+    Clunk(Tclunk),
+    Remove(Tremove),
+    Attach(Tattach),
+    Auth(Tauth),
+    Statfs(Tstatfs),
+    Lopen(Tlopen),
+    Lcreate(Tlcreate),
+    Symlink(Tsymlink),
+    Mknod(Tmknod),
+    Rename(Trename),
+    Readlink(Treadlink),
+    GetAttr(Tgetattr),
+    SetAttr(Tsetattr),
+    XattrWalk(Txattrwalk),
+    XattrCreate(Txattrcreate),
+    Readdir(Treaddir),
+    Fsync(Tfsync),
+    Lock(Tlock),
+    GetLock(Tgetlock),
+    Link(Tlink),
+    Mkdir(Tmkdir),
+    RenameAt(Trenameat),
+    UnlinkAt(Tunlinkat),
+}
+
+#[derive(Debug)]
+pub struct Tframe {
+    pub tag: u16,
+    pub msg: Tmessage,
+}
+
+impl WireFormat for Tframe {
+    fn byte_size(&self) -> u32 {
+        let msg_size = match self.msg {
+            Tmessage::Version(ref version) => version.byte_size(),
+            Tmessage::Flush(ref flush) => flush.byte_size(),
+            Tmessage::Walk(ref walk) => walk.byte_size(),
+            Tmessage::Read(ref read) => read.byte_size(),
+            Tmessage::Write(ref write) => write.byte_size(),
+            Tmessage::Clunk(ref clunk) => clunk.byte_size(),
+            Tmessage::Remove(ref remove) => remove.byte_size(),
+            Tmessage::Attach(ref attach) => attach.byte_size(),
+            Tmessage::Auth(ref auth) => auth.byte_size(),
+            Tmessage::Statfs(ref statfs) => statfs.byte_size(),
+            Tmessage::Lopen(ref lopen) => lopen.byte_size(),
+            Tmessage::Lcreate(ref lcreate) => lcreate.byte_size(),
+            Tmessage::Symlink(ref symlink) => symlink.byte_size(),
+            Tmessage::Mknod(ref mknod) => mknod.byte_size(),
+            Tmessage::Rename(ref rename) => rename.byte_size(),
+            Tmessage::Readlink(ref readlink) => readlink.byte_size(),
+            Tmessage::GetAttr(ref getattr) => getattr.byte_size(),
+            Tmessage::SetAttr(ref setattr) => setattr.byte_size(),
+            Tmessage::XattrWalk(ref xattrwalk) => xattrwalk.byte_size(),
+            Tmessage::XattrCreate(ref xattrcreate) => xattrcreate.byte_size(),
+            Tmessage::Readdir(ref readdir) => readdir.byte_size(),
+            Tmessage::Fsync(ref fsync) => fsync.byte_size(),
+            Tmessage::Lock(ref lock) => lock.byte_size(),
+            Tmessage::GetLock(ref getlock) => getlock.byte_size(),
+            Tmessage::Link(ref link) => link.byte_size(),
+            Tmessage::Mkdir(ref mkdir) => mkdir.byte_size(),
+            Tmessage::RenameAt(ref renameat) => renameat.byte_size(),
+            Tmessage::UnlinkAt(ref unlinkat) => unlinkat.byte_size(),
+        };
+
+        // size + type + tag + message size
+        (mem::size_of::<u32>() + mem::size_of::<u8>() + mem::size_of::<u16>()) as u32 + msg_size
+    }
+
+    fn encode<W: Write>(&self, writer: &mut W) -> io::Result<()> {
+        self.byte_size().encode(writer)?;
+
+        let ty = match self.msg {
+            Tmessage::Version(_) => TVERSION,
+            Tmessage::Flush(_) => TFLUSH,
+            Tmessage::Walk(_) => TWALK,
+            Tmessage::Read(_) => TREAD,
+            Tmessage::Write(_) => TWRITE,
+            Tmessage::Clunk(_) => TCLUNK,
+            Tmessage::Remove(_) => TREMOVE,
+            Tmessage::Attach(_) => TATTACH,
+            Tmessage::Auth(_) => TAUTH,
+            Tmessage::Statfs(_) => TSTATFS,
+            Tmessage::Lopen(_) => TLOPEN,
+            Tmessage::Lcreate(_) => TLCREATE,
+            Tmessage::Symlink(_) => TSYMLINK,
+            Tmessage::Mknod(_) => TMKNOD,
+            Tmessage::Rename(_) => TRENAME,
+            Tmessage::Readlink(_) => TREADLINK,
+            Tmessage::GetAttr(_) => TGETATTR,
+            Tmessage::SetAttr(_) => TSETATTR,
+            Tmessage::XattrWalk(_) => TXATTRWALK,
+            Tmessage::XattrCreate(_) => TXATTRCREATE,
+            Tmessage::Readdir(_) => TREADDIR,
+            Tmessage::Fsync(_) => TFSYNC,
+            Tmessage::Lock(_) => TLOCK,
+            Tmessage::GetLock(_) => TGETLOCK,
+            Tmessage::Link(_) => TLINK,
+            Tmessage::Mkdir(_) => TMKDIR,
+            Tmessage::RenameAt(_) => TRENAMEAT,
+            Tmessage::UnlinkAt(_) => TUNLINKAT,
+        };
+
+        ty.encode(writer)?;
+        self.tag.encode(writer)?;
+
+        match self.msg {
+            Tmessage::Version(ref version) => version.encode(writer),
+            Tmessage::Flush(ref flush) => flush.encode(writer),
+            Tmessage::Walk(ref walk) => walk.encode(writer),
+            Tmessage::Read(ref read) => read.encode(writer),
+            Tmessage::Write(ref write) => write.encode(writer),
+            Tmessage::Clunk(ref clunk) => clunk.encode(writer),
+            Tmessage::Remove(ref remove) => remove.encode(writer),
+            Tmessage::Attach(ref attach) => attach.encode(writer),
+            Tmessage::Auth(ref auth) => auth.encode(writer),
+            Tmessage::Statfs(ref statfs) => statfs.encode(writer),
+            Tmessage::Lopen(ref lopen) => lopen.encode(writer),
+            Tmessage::Lcreate(ref lcreate) => lcreate.encode(writer),
+            Tmessage::Symlink(ref symlink) => symlink.encode(writer),
+            Tmessage::Mknod(ref mknod) => mknod.encode(writer),
+            Tmessage::Rename(ref rename) => rename.encode(writer),
+            Tmessage::Readlink(ref readlink) => readlink.encode(writer),
+            Tmessage::GetAttr(ref getattr) => getattr.encode(writer),
+            Tmessage::SetAttr(ref setattr) => setattr.encode(writer),
+            Tmessage::XattrWalk(ref xattrwalk) => xattrwalk.encode(writer),
+            Tmessage::XattrCreate(ref xattrcreate) => xattrcreate.encode(writer),
+            Tmessage::Readdir(ref readdir) => readdir.encode(writer),
+            Tmessage::Fsync(ref fsync) => fsync.encode(writer),
+            Tmessage::Lock(ref lock) => lock.encode(writer),
+            Tmessage::GetLock(ref getlock) => getlock.encode(writer),
+            Tmessage::Link(ref link) => link.encode(writer),
+            Tmessage::Mkdir(ref mkdir) => mkdir.encode(writer),
+            Tmessage::RenameAt(ref renameat) => renameat.encode(writer),
+            Tmessage::UnlinkAt(ref unlinkat) => unlinkat.encode(writer),
+        }
+    }
+
+    fn decode<R: Read>(reader: &mut R) -> io::Result<Self> {
+        let byte_size: u32 = WireFormat::decode(reader)?;
+
+        // byte_size includes the size of byte_size so remove that from the
+        // expected length of the message.  Also make sure that byte_size is at least
+        // that long to begin with.
+        if byte_size < mem::size_of::<u32>() as u32 {
+            return Err(io::Error::new(
+                ErrorKind::InvalidData,
+                format!("byte_size(= {}) is less than 4 bytes", byte_size),
+            ));
+        }
+
+        let reader = &mut reader.take((byte_size - mem::size_of::<u32>() as u32) as u64);
+
+        let mut ty = [0u8];
+        reader.read_exact(&mut ty)?;
+
+        let tag: u16 = WireFormat::decode(reader)?;
+
+        let msg = match ty[0] {
+            TVERSION => Ok(Tmessage::Version(WireFormat::decode(reader)?)),
+            TFLUSH => Ok(Tmessage::Flush(WireFormat::decode(reader)?)),
+            TWALK => Ok(Tmessage::Walk(WireFormat::decode(reader)?)),
+            TREAD => Ok(Tmessage::Read(WireFormat::decode(reader)?)),
+            TWRITE => Ok(Tmessage::Write(WireFormat::decode(reader)?)),
+            TCLUNK => Ok(Tmessage::Clunk(WireFormat::decode(reader)?)),
+            TREMOVE => Ok(Tmessage::Remove(WireFormat::decode(reader)?)),
+            TATTACH => Ok(Tmessage::Attach(WireFormat::decode(reader)?)),
+            TAUTH => Ok(Tmessage::Auth(WireFormat::decode(reader)?)),
+            TSTATFS => Ok(Tmessage::Statfs(WireFormat::decode(reader)?)),
+            TLOPEN => Ok(Tmessage::Lopen(WireFormat::decode(reader)?)),
+            TLCREATE => Ok(Tmessage::Lcreate(WireFormat::decode(reader)?)),
+            TSYMLINK => Ok(Tmessage::Symlink(WireFormat::decode(reader)?)),
+            TMKNOD => Ok(Tmessage::Mknod(WireFormat::decode(reader)?)),
+            TRENAME => Ok(Tmessage::Rename(WireFormat::decode(reader)?)),
+            TREADLINK => Ok(Tmessage::Readlink(WireFormat::decode(reader)?)),
+            TGETATTR => Ok(Tmessage::GetAttr(WireFormat::decode(reader)?)),
+            TSETATTR => Ok(Tmessage::SetAttr(WireFormat::decode(reader)?)),
+            TXATTRWALK => Ok(Tmessage::XattrWalk(WireFormat::decode(reader)?)),
+            TXATTRCREATE => Ok(Tmessage::XattrCreate(WireFormat::decode(reader)?)),
+            TREADDIR => Ok(Tmessage::Readdir(WireFormat::decode(reader)?)),
+            TFSYNC => Ok(Tmessage::Fsync(WireFormat::decode(reader)?)),
+            TLOCK => Ok(Tmessage::Lock(WireFormat::decode(reader)?)),
+            TGETLOCK => Ok(Tmessage::GetLock(WireFormat::decode(reader)?)),
+            TLINK => Ok(Tmessage::Link(WireFormat::decode(reader)?)),
+            TMKDIR => Ok(Tmessage::Mkdir(WireFormat::decode(reader)?)),
+            TRENAMEAT => Ok(Tmessage::RenameAt(WireFormat::decode(reader)?)),
+            TUNLINKAT => Ok(Tmessage::UnlinkAt(WireFormat::decode(reader)?)),
+            err => Err(io::Error::new(
+                ErrorKind::InvalidData,
+                format!("unknown message type {}", err),
+            )),
+        }?;
+
+        Ok(Tframe { tag, msg })
+    }
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tversion {
+    pub msize: u32,
+    pub version: String,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tflush {
+    pub oldtag: u16,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Twalk {
+    pub fid: u32,
+    pub newfid: u32,
+    pub wnames: Vec<String>,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tread {
+    pub fid: u32,
+    pub offset: u64,
+    pub count: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Twrite {
+    pub fid: u32,
+    pub offset: u64,
+    pub data: Data,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tclunk {
+    pub fid: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tremove {
+    pub fid: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tauth {
+    pub afid: u32,
+    pub uname: String,
+    pub aname: String,
+    pub n_uname: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tattach {
+    pub fid: u32,
+    pub afid: u32,
+    pub uname: String,
+    pub aname: String,
+    pub n_uname: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tstatfs {
+    pub fid: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tlopen {
+    pub fid: u32,
+    pub flags: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tlcreate {
+    pub fid: u32,
+    pub name: String,
+    pub flags: u32,
+    pub mode: u32,
+    pub gid: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tsymlink {
+    pub fid: u32,
+    pub name: String,
+    pub symtgt: String,
+    pub gid: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tmknod {
+    pub dfid: u32,
+    pub name: String,
+    pub mode: u32,
+    pub major: u32,
+    pub minor: u32,
+    pub gid: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Trename {
+    pub fid: u32,
+    pub dfid: u32,
+    pub name: String,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Treadlink {
+    pub fid: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tgetattr {
+    pub fid: u32,
+    pub request_mask: u64,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tsetattr {
+    pub fid: u32,
+    pub valid: u32,
+    pub mode: u32,
+    pub uid: u32,
+    pub gid: u32,
+    pub size: u64,
+    pub atime_sec: u64,
+    pub atime_nsec: u64,
+    pub mtime_sec: u64,
+    pub mtime_nsec: u64,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Txattrwalk {
+    pub fid: u32,
+    pub newfid: u32,
+    pub name: String,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Txattrcreate {
+    pub fid: u32,
+    pub name: String,
+    pub attr_size: u64,
+    pub flags: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Treaddir {
+    pub fid: u32,
+    pub offset: u64,
+    pub count: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tfsync {
+    pub fid: u32,
+    pub datasync: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tlock {
+    pub fid: u32,
+    pub type_: u8,
+    pub flags: u32,
+    pub start: u64,
+    pub length: u64,
+    pub proc_id: u32,
+    pub client_id: String,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tgetlock {
+    pub fid: u32,
+    pub type_: u8,
+    pub start: u64,
+    pub length: u64,
+    pub proc_id: u32,
+    pub client_id: String,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tlink {
+    pub dfid: u32,
+    pub fid: u32,
+    pub name: String,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tmkdir {
+    pub dfid: u32,
+    pub name: String,
+    pub mode: u32,
+    pub gid: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Trenameat {
+    pub olddirfid: u32,
+    pub oldname: String,
+    pub newdirfid: u32,
+    pub newname: String,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tunlinkat {
+    pub dirfd: u32,
+    pub name: String,
+    pub flags: u32,
+}
+
+/// A message sent from a 9P server to a 9P client in response to a request from
+/// that client.  Encapsulates a full frame.
+#[derive(Debug)]
+pub enum Rmessage {
+    Version(Rversion),
+    Flush,
+    Walk(Rwalk),
+    Read(Rread),
+    Write(Rwrite),
+    Clunk,
+    Remove,
+    Attach(Rattach),
+    Auth(Rauth),
+    Statfs(Rstatfs),
+    Lopen(Rlopen),
+    Lcreate(Rlcreate),
+    Symlink(Rsymlink),
+    Mknod(Rmknod),
+    Rename,
+    Readlink(Rreadlink),
+    GetAttr(Rgetattr),
+    SetAttr,
+    XattrWalk(Rxattrwalk),
+    XattrCreate,
+    Readdir(Rreaddir),
+    Fsync,
+    Lock(Rlock),
+    GetLock(Rgetlock),
+    Link,
+    Mkdir(Rmkdir),
+    RenameAt,
+    UnlinkAt,
+    Lerror(Rlerror),
+}
+
+#[derive(Debug)]
+pub struct Rframe {
+    pub tag: u16,
+    pub msg: Rmessage,
+}
+
+impl WireFormat for Rframe {
+    fn byte_size(&self) -> u32 {
+        let msg_size = match self.msg {
+            Rmessage::Version(ref version) => version.byte_size(),
+            Rmessage::Flush => 0,
+            Rmessage::Walk(ref walk) => walk.byte_size(),
+            Rmessage::Read(ref read) => read.byte_size(),
+            Rmessage::Write(ref write) => write.byte_size(),
+            Rmessage::Clunk => 0,
+            Rmessage::Remove => 0,
+            Rmessage::Attach(ref attach) => attach.byte_size(),
+            Rmessage::Auth(ref auth) => auth.byte_size(),
+            Rmessage::Statfs(ref statfs) => statfs.byte_size(),
+            Rmessage::Lopen(ref lopen) => lopen.byte_size(),
+            Rmessage::Lcreate(ref lcreate) => lcreate.byte_size(),
+            Rmessage::Symlink(ref symlink) => symlink.byte_size(),
+            Rmessage::Mknod(ref mknod) => mknod.byte_size(),
+            Rmessage::Rename => 0,
+            Rmessage::Readlink(ref readlink) => readlink.byte_size(),
+            Rmessage::GetAttr(ref getattr) => getattr.byte_size(),
+            Rmessage::SetAttr => 0,
+            Rmessage::XattrWalk(ref xattrwalk) => xattrwalk.byte_size(),
+            Rmessage::XattrCreate => 0,
+            Rmessage::Readdir(ref readdir) => readdir.byte_size(),
+            Rmessage::Fsync => 0,
+            Rmessage::Lock(ref lock) => lock.byte_size(),
+            Rmessage::GetLock(ref getlock) => getlock.byte_size(),
+            Rmessage::Link => 0,
+            Rmessage::Mkdir(ref mkdir) => mkdir.byte_size(),
+            Rmessage::RenameAt => 0,
+            Rmessage::UnlinkAt => 0,
+            Rmessage::Lerror(ref lerror) => lerror.byte_size(),
+        };
+
+        // size + type + tag + message size
+        (mem::size_of::<u32>() + mem::size_of::<u8>() + mem::size_of::<u16>()) as u32 + msg_size
+    }
+
+    fn encode<W: Write>(&self, writer: &mut W) -> io::Result<()> {
+        self.byte_size().encode(writer)?;
+
+        let ty = match self.msg {
+            Rmessage::Version(_) => RVERSION,
+            Rmessage::Flush => RFLUSH,
+            Rmessage::Walk(_) => RWALK,
+            Rmessage::Read(_) => RREAD,
+            Rmessage::Write(_) => RWRITE,
+            Rmessage::Clunk => RCLUNK,
+            Rmessage::Remove => RREMOVE,
+            Rmessage::Attach(_) => RATTACH,
+            Rmessage::Auth(_) => RAUTH,
+            Rmessage::Statfs(_) => RSTATFS,
+            Rmessage::Lopen(_) => RLOPEN,
+            Rmessage::Lcreate(_) => RLCREATE,
+            Rmessage::Symlink(_) => RSYMLINK,
+            Rmessage::Mknod(_) => RMKNOD,
+            Rmessage::Rename => RRENAME,
+            Rmessage::Readlink(_) => RREADLINK,
+            Rmessage::GetAttr(_) => RGETATTR,
+            Rmessage::SetAttr => RSETATTR,
+            Rmessage::XattrWalk(_) => RXATTRWALK,
+            Rmessage::XattrCreate => RXATTRCREATE,
+            Rmessage::Readdir(_) => RREADDIR,
+            Rmessage::Fsync => RFSYNC,
+            Rmessage::Lock(_) => RLOCK,
+            Rmessage::GetLock(_) => RGETLOCK,
+            Rmessage::Link => RLINK,
+            Rmessage::Mkdir(_) => RMKDIR,
+            Rmessage::RenameAt => RRENAMEAT,
+            Rmessage::UnlinkAt => RUNLINKAT,
+            Rmessage::Lerror(_) => RLERROR,
+        };
+
+        ty.encode(writer)?;
+        self.tag.encode(writer)?;
+
+        match self.msg {
+            Rmessage::Version(ref version) => version.encode(writer),
+            Rmessage::Flush => Ok(()),
+            Rmessage::Walk(ref walk) => walk.encode(writer),
+            Rmessage::Read(ref read) => read.encode(writer),
+            Rmessage::Write(ref write) => write.encode(writer),
+            Rmessage::Clunk => Ok(()),
+            Rmessage::Remove => Ok(()),
+            Rmessage::Attach(ref attach) => attach.encode(writer),
+            Rmessage::Auth(ref auth) => auth.encode(writer),
+            Rmessage::Statfs(ref statfs) => statfs.encode(writer),
+            Rmessage::Lopen(ref lopen) => lopen.encode(writer),
+            Rmessage::Lcreate(ref lcreate) => lcreate.encode(writer),
+            Rmessage::Symlink(ref symlink) => symlink.encode(writer),
+            Rmessage::Mknod(ref mknod) => mknod.encode(writer),
+            Rmessage::Rename => Ok(()),
+            Rmessage::Readlink(ref readlink) => readlink.encode(writer),
+            Rmessage::GetAttr(ref getattr) => getattr.encode(writer),
+            Rmessage::SetAttr => Ok(()),
+            Rmessage::XattrWalk(ref xattrwalk) => xattrwalk.encode(writer),
+            Rmessage::XattrCreate => Ok(()),
+            Rmessage::Readdir(ref readdir) => readdir.encode(writer),
+            Rmessage::Fsync => Ok(()),
+            Rmessage::Lock(ref lock) => lock.encode(writer),
+            Rmessage::GetLock(ref getlock) => getlock.encode(writer),
+            Rmessage::Link => Ok(()),
+            Rmessage::Mkdir(ref mkdir) => mkdir.encode(writer),
+            Rmessage::RenameAt => Ok(()),
+            Rmessage::UnlinkAt => Ok(()),
+            Rmessage::Lerror(ref lerror) => lerror.encode(writer),
+        }
+    }
+
+    fn decode<R: Read>(reader: &mut R) -> io::Result<Self> {
+        let byte_size: u32 = WireFormat::decode(reader)?;
+
+        // byte_size includes the size of byte_size so remove that from the
+        // expected length of the message.
+        let reader = &mut reader.take((byte_size - mem::size_of::<u32>() as u32) as u64);
+
+        let mut ty = [0u8];
+        reader.read_exact(&mut ty)?;
+
+        let tag: u16 = WireFormat::decode(reader)?;
+
+        let msg = match ty[0] {
+            RVERSION => Ok(Rmessage::Version(WireFormat::decode(reader)?)),
+            RFLUSH => Ok(Rmessage::Flush),
+            RWALK => Ok(Rmessage::Walk(WireFormat::decode(reader)?)),
+            RREAD => Ok(Rmessage::Read(WireFormat::decode(reader)?)),
+            RWRITE => Ok(Rmessage::Write(WireFormat::decode(reader)?)),
+            RCLUNK => Ok(Rmessage::Clunk),
+            RREMOVE => Ok(Rmessage::Remove),
+            RATTACH => Ok(Rmessage::Attach(WireFormat::decode(reader)?)),
+            RAUTH => Ok(Rmessage::Auth(WireFormat::decode(reader)?)),
+            RSTATFS => Ok(Rmessage::Statfs(WireFormat::decode(reader)?)),
+            RLOPEN => Ok(Rmessage::Lopen(WireFormat::decode(reader)?)),
+            RLCREATE => Ok(Rmessage::Lcreate(WireFormat::decode(reader)?)),
+            RSYMLINK => Ok(Rmessage::Symlink(WireFormat::decode(reader)?)),
+            RMKNOD => Ok(Rmessage::Mknod(WireFormat::decode(reader)?)),
+            RRENAME => Ok(Rmessage::Rename),
+            RREADLINK => Ok(Rmessage::Readlink(WireFormat::decode(reader)?)),
+            RGETATTR => Ok(Rmessage::GetAttr(WireFormat::decode(reader)?)),
+            RSETATTR => Ok(Rmessage::SetAttr),
+            RXATTRWALK => Ok(Rmessage::XattrWalk(WireFormat::decode(reader)?)),
+            RXATTRCREATE => Ok(Rmessage::XattrCreate),
+            RREADDIR => Ok(Rmessage::Readdir(WireFormat::decode(reader)?)),
+            RFSYNC => Ok(Rmessage::Fsync),
+            RLOCK => Ok(Rmessage::Lock(WireFormat::decode(reader)?)),
+            RGETLOCK => Ok(Rmessage::GetLock(WireFormat::decode(reader)?)),
+            RLINK => Ok(Rmessage::Link),
+            RMKDIR => Ok(Rmessage::Mkdir(WireFormat::decode(reader)?)),
+            RRENAMEAT => Ok(Rmessage::RenameAt),
+            RUNLINKAT => Ok(Rmessage::UnlinkAt),
+            RLERROR => Ok(Rmessage::Lerror(WireFormat::decode(reader)?)),
+            err => Err(io::Error::new(
+                ErrorKind::InvalidData,
+                format!("unknown message type {}", err),
+            )),
+        }?;
+
+        Ok(Rframe { tag, msg })
+    }
+}
+
+#[derive(Debug, Copy, Clone, P9WireFormat)]
+pub struct Qid {
+    pub ty: u8,
+    pub version: u32,
+    pub path: u64,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Dirent {
+    pub qid: Qid,
+    pub offset: u64,
+    pub ty: u8,
+    pub name: String,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rversion {
+    pub msize: u32,
+    pub version: String,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rwalk {
+    pub wqids: Vec<Qid>,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rread {
+    pub data: Data,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rwrite {
+    pub count: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rauth {
+    pub aqid: Qid,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rattach {
+    pub qid: Qid,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rlerror {
+    pub ecode: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rstatfs {
+    pub ty: u32,
+    pub bsize: u32,
+    pub blocks: u64,
+    pub bfree: u64,
+    pub bavail: u64,
+    pub files: u64,
+    pub ffree: u64,
+    pub fsid: u64,
+    pub namelen: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rlopen {
+    pub qid: Qid,
+    pub iounit: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rlcreate {
+    pub qid: Qid,
+    pub iounit: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rsymlink {
+    pub qid: Qid,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rmknod {
+    pub qid: Qid,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rreadlink {
+    pub target: String,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rgetattr {
+    pub valid: u64,
+    pub qid: Qid,
+    pub mode: u32,
+    pub uid: u32,
+    pub gid: u32,
+    pub nlink: u64,
+    pub rdev: u64,
+    pub size: u64,
+    pub blksize: u64,
+    pub blocks: u64,
+    pub atime_sec: u64,
+    pub atime_nsec: u64,
+    pub mtime_sec: u64,
+    pub mtime_nsec: u64,
+    pub ctime_sec: u64,
+    pub ctime_nsec: u64,
+    pub btime_sec: u64,
+    pub btime_nsec: u64,
+    pub gen: u64,
+    pub data_version: u64,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rxattrwalk {
+    pub size: u64,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rreaddir {
+    pub data: Data,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rlock {
+    pub status: u8,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rgetlock {
+    pub ty: u8,
+    pub start: u64,
+    pub length: u64,
+    pub proc_id: u32,
+    pub client_id: String,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rmkdir {
+    pub qid: Qid,
+}
diff --git a/common/p9/src/protocol/mod.rs b/common/p9/src/protocol/mod.rs
new file mode 100644
index 0000000..9c278ee
--- /dev/null
+++ b/common/p9/src/protocol/mod.rs
@@ -0,0 +1,9 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+mod messages;
+mod wire_format;
+
+pub use self::messages::*;
+pub use self::wire_format::{Data, WireFormat};
diff --git a/common/p9/src/protocol/wire_format.rs b/common/p9/src/protocol/wire_format.rs
new file mode 100644
index 0000000..fc729cd
--- /dev/null
+++ b/common/p9/src/protocol/wire_format.rs
@@ -0,0 +1,698 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::fmt;
+use std::io;
+use std::io::{ErrorKind, Read, Write};
+use std::mem;
+use std::ops::{Deref, DerefMut};
+use std::string::String;
+use std::vec::Vec;
+
+/// A type that can be encoded on the wire using the 9P protocol.
+pub trait WireFormat: std::marker::Sized {
+    /// Returns the number of bytes necessary to fully encode `self`.
+    fn byte_size(&self) -> u32;
+
+    /// Encodes `self` into `writer`.
+    fn encode<W: Write>(&self, writer: &mut W) -> io::Result<()>;
+
+    /// Decodes `Self` from `reader`.
+    fn decode<R: Read>(reader: &mut R) -> io::Result<Self>;
+}
+
+// This doesn't really _need_ to be a macro but unfortunately there is no trait bound to
+// express "can be casted to another type", which means we can't write `T as u8` in a trait
+// based implementation.  So instead we have this macro, which is implemented for all the
+// stable unsigned types with the added benefit of not being implemented for the signed
+// types which are not allowed by the protocol.
+macro_rules! uint_wire_format_impl {
+    ($Ty:ty) => {
+        impl WireFormat for $Ty {
+            fn byte_size(&self) -> u32 {
+                mem::size_of::<$Ty>() as u32
+            }
+
+            fn encode<W: Write>(&self, writer: &mut W) -> io::Result<()> {
+                let mut buf = [0u8; mem::size_of::<$Ty>()];
+
+                // Encode the bytes into the buffer in little endian order.
+                for idx in 0..mem::size_of::<$Ty>() {
+                    buf[idx] = (self >> (8 * idx)) as u8;
+                }
+
+                writer.write_all(&buf)
+            }
+
+            fn decode<R: Read>(reader: &mut R) -> io::Result<Self> {
+                let mut buf = [0u8; mem::size_of::<$Ty>()];
+                reader.read_exact(&mut buf)?;
+
+                // Read bytes from the buffer in little endian order.
+                let mut result = 0;
+                for idx in 0..mem::size_of::<$Ty>() {
+                    result |= (buf[idx] as $Ty) << (8 * idx);
+                }
+
+                Ok(result)
+            }
+        }
+    };
+}
+uint_wire_format_impl!(u8);
+uint_wire_format_impl!(u16);
+uint_wire_format_impl!(u32);
+uint_wire_format_impl!(u64);
+
+// The 9P protocol requires that strings are UTF-8 encoded.  The wire format is a u16
+// count |N|, encoded in little endian, followed by |N| bytes of UTF-8 data.
+impl WireFormat for String {
+    fn byte_size(&self) -> u32 {
+        (mem::size_of::<u16>() + self.len()) as u32
+    }
+
+    fn encode<W: Write>(&self, writer: &mut W) -> io::Result<()> {
+        if self.len() > std::u16::MAX as usize {
+            return Err(io::Error::new(
+                ErrorKind::InvalidInput,
+                "string is too long",
+            ));
+        }
+
+        (self.len() as u16).encode(writer)?;
+        writer.write_all(self.as_bytes())
+    }
+
+    fn decode<R: Read>(reader: &mut R) -> io::Result<Self> {
+        let len: u16 = WireFormat::decode(reader)?;
+        let mut result = String::with_capacity(len as usize);
+        reader.take(len as u64).read_to_string(&mut result)?;
+        Ok(result)
+    }
+}
+
+// The wire format for repeated types is similar to that of strings: a little endian
+// encoded u16 |N|, followed by |N| instances of the given type.
+impl<T: WireFormat> WireFormat for Vec<T> {
+    fn byte_size(&self) -> u32 {
+        mem::size_of::<u16>() as u32 + self.iter().map(|elem| elem.byte_size()).sum::<u32>()
+    }
+
+    fn encode<W: Write>(&self, writer: &mut W) -> io::Result<()> {
+        if self.len() > std::u16::MAX as usize {
+            return Err(io::Error::new(
+                ErrorKind::InvalidInput,
+                "too many elements in vector",
+            ));
+        }
+
+        (self.len() as u16).encode(writer)?;
+        for elem in self {
+            elem.encode(writer)?;
+        }
+
+        Ok(())
+    }
+
+    fn decode<R: Read>(reader: &mut R) -> io::Result<Self> {
+        let len: u16 = WireFormat::decode(reader)?;
+        let mut result = Vec::with_capacity(len as usize);
+
+        for _ in 0..len {
+            result.push(WireFormat::decode(reader)?);
+        }
+
+        Ok(result)
+    }
+}
+
+/// A type that encodes an arbitrary number of bytes of data.  Typically used for Rread
+/// Twrite messages.  This differs from a `Vec<u8>` in that it encodes the number of bytes
+/// using a `u32` instead of a `u16`.
+#[derive(PartialEq)]
+pub struct Data(pub Vec<u8>);
+
+// The maximum length of a data buffer that we support.  In practice the server's max message
+// size should prevent us from reading too much data so this check is mainly to ensure a
+// malicious client cannot trick us into allocating massive amounts of memory.
+const MAX_DATA_LENGTH: u32 = 32 * 1024 * 1024;
+
+impl fmt::Debug for Data {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        // There may be a lot of data and we don't want to spew it all out in a trace.  Instead
+        // just print out the number of bytes in the buffer.
+        write!(f, "Data({} bytes)", self.len())
+    }
+}
+
+// Implement Deref and DerefMut so that we don't have to use self.0 everywhere.
+impl Deref for Data {
+    type Target = Vec<u8>;
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+impl DerefMut for Data {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.0
+    }
+}
+
+// Same as Vec<u8> except that it encodes the length as a u32 instead of a u16.
+impl WireFormat for Data {
+    fn byte_size(&self) -> u32 {
+        mem::size_of::<u32>() as u32 + self.iter().map(|elem| elem.byte_size()).sum::<u32>()
+    }
+
+    fn encode<W: Write>(&self, writer: &mut W) -> io::Result<()> {
+        if self.len() > std::u32::MAX as usize {
+            return Err(io::Error::new(ErrorKind::InvalidInput, "data is too large"));
+        }
+        (self.len() as u32).encode(writer)?;
+        writer.write_all(self)
+    }
+
+    fn decode<R: Read>(reader: &mut R) -> io::Result<Self> {
+        let len: u32 = WireFormat::decode(reader)?;
+        if len > MAX_DATA_LENGTH {
+            return Err(io::Error::new(
+                ErrorKind::InvalidData,
+                format!("data length ({} bytes) is too large", len),
+            ));
+        }
+
+        let mut buf = Vec::with_capacity(len as usize);
+        reader.take(len as u64).read_to_end(&mut buf)?;
+
+        if buf.len() == len as usize {
+            Ok(Data(buf))
+        } else {
+            Err(io::Error::new(
+                ErrorKind::UnexpectedEof,
+                format!(
+                    "unexpected end of data: want: {} bytes, got: {} bytes",
+                    len,
+                    buf.len()
+                ),
+            ))
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use std::io::Cursor;
+    use std::mem;
+    use std::string::String;
+
+    #[test]
+    fn integer_byte_size() {
+        assert_eq!(1, 0u8.byte_size());
+        assert_eq!(2, 0u16.byte_size());
+        assert_eq!(4, 0u32.byte_size());
+        assert_eq!(8, 0u64.byte_size());
+    }
+
+    #[test]
+    fn integer_decode() {
+        let buf: [u8; 8] = [0xef, 0xbe, 0xad, 0xde, 0x0d, 0xf0, 0xad, 0x8b];
+
+        assert_eq!(0xef_u8, WireFormat::decode(&mut Cursor::new(&buf)).unwrap());
+        assert_eq!(0xbeef_u16, u16::decode(&mut Cursor::new(&buf)).unwrap());
+        assert_eq!(0xdeadbeef_u32, u32::decode(&mut Cursor::new(&buf)).unwrap());
+        assert_eq!(
+            0x8bad_f00d_dead_beef_u64,
+            u64::decode(&mut Cursor::new(&buf)).unwrap()
+        );
+    }
+
+    #[test]
+    fn integer_encode() {
+        let value: u64 = 0x8bad_f00d_dead_beef;
+        let expected: [u8; 8] = [0xef, 0xbe, 0xad, 0xde, 0x0d, 0xf0, 0xad, 0x8b];
+
+        let mut buf = vec![0; 8];
+
+        (value as u8).encode(&mut Cursor::new(&mut *buf)).unwrap();
+        assert_eq!(expected[0..1], buf[0..1]);
+
+        (value as u16).encode(&mut Cursor::new(&mut *buf)).unwrap();
+        assert_eq!(expected[0..2], buf[0..2]);
+
+        (value as u32).encode(&mut Cursor::new(&mut *buf)).unwrap();
+        assert_eq!(expected[0..4], buf[0..4]);
+
+        value.encode(&mut Cursor::new(&mut *buf)).unwrap();
+        assert_eq!(expected[0..8], buf[0..8]);
+    }
+
+    #[test]
+    fn string_byte_size() {
+        let values = [
+            String::from("Google Video"),
+            String::from("网页 图片 资讯更多 »"),
+            String::from("Παγκόσμιος Ιστός"),
+            String::from("Поиск страниц на русском"),
+            String::from("전체서비스"),
+        ];
+
+        let exp = values
+            .iter()
+            .map(|v| (mem::size_of::<u16>() + v.len()) as u32);
+
+        for (value, expected) in values.iter().zip(exp) {
+            assert_eq!(expected, value.byte_size());
+        }
+    }
+
+    #[test]
+    fn zero_length_string() {
+        let s = String::from("");
+        assert_eq!(s.byte_size(), mem::size_of::<u16>() as u32);
+
+        let mut buf = [0xffu8; 4];
+
+        s.encode(&mut Cursor::new(&mut buf[..]))
+            .expect("failed to encode empty string");
+        assert_eq!(&[0, 0, 0xff, 0xff], &buf);
+
+        assert_eq!(
+            s,
+            <String as WireFormat>::decode(&mut Cursor::new(&[0, 0, 0x61, 0x61][..]))
+                .expect("failed to decode empty string")
+        );
+    }
+
+    #[test]
+    fn string_encode() {
+        let values = [
+            String::from("Google Video"),
+            String::from("网页 图片 资讯更多 »"),
+            String::from("Παγκόσμιος Ιστός"),
+            String::from("Поиск страниц на русском"),
+            String::from("전체서비스"),
+        ];
+
+        let expected = values.iter().map(|v| {
+            let len = v.as_bytes().len();
+            let mut buf = Vec::with_capacity(len + mem::size_of::<u16>());
+
+            buf.push(len as u8);
+            buf.push((len >> 8) as u8);
+
+            buf.extend_from_slice(v.as_bytes());
+
+            buf
+        });
+
+        for (val, exp) in values.iter().zip(expected) {
+            let mut buf = vec![0; exp.len()];
+
+            WireFormat::encode(val, &mut Cursor::new(&mut *buf)).unwrap();
+            assert_eq!(exp, buf);
+        }
+    }
+
+    #[test]
+    fn string_decode() {
+        assert_eq!(
+            String::from("Google Video"),
+            <String as WireFormat>::decode(&mut Cursor::new(
+                &[
+                    0x0c, 0x00, 0x47, 0x6F, 0x6F, 0x67, 0x6C, 0x65, 0x20, 0x56, 0x69, 0x64, 0x65,
+                    0x6F,
+                ][..]
+            ))
+            .unwrap()
+        );
+        assert_eq!(
+            String::from("网页 图片 资讯更多 »"),
+            <String as WireFormat>::decode(&mut Cursor::new(
+                &[
+                    0x1d, 0x00, 0xE7, 0xBD, 0x91, 0xE9, 0xA1, 0xB5, 0x20, 0xE5, 0x9B, 0xBE, 0xE7,
+                    0x89, 0x87, 0x20, 0xE8, 0xB5, 0x84, 0xE8, 0xAE, 0xAF, 0xE6, 0x9B, 0xB4, 0xE5,
+                    0xA4, 0x9A, 0x20, 0xC2, 0xBB,
+                ][..]
+            ))
+            .unwrap()
+        );
+        assert_eq!(
+            String::from("Παγκόσμιος Ιστός"),
+            <String as WireFormat>::decode(&mut Cursor::new(
+                &[
+                    0x1f, 0x00, 0xCE, 0xA0, 0xCE, 0xB1, 0xCE, 0xB3, 0xCE, 0xBA, 0xCF, 0x8C, 0xCF,
+                    0x83, 0xCE, 0xBC, 0xCE, 0xB9, 0xCE, 0xBF, 0xCF, 0x82, 0x20, 0xCE, 0x99, 0xCF,
+                    0x83, 0xCF, 0x84, 0xCF, 0x8C, 0xCF, 0x82,
+                ][..]
+            ))
+            .unwrap()
+        );
+        assert_eq!(
+            String::from("Поиск страниц на русском"),
+            <String as WireFormat>::decode(&mut Cursor::new(
+                &[
+                    0x2d, 0x00, 0xD0, 0x9F, 0xD0, 0xBE, 0xD0, 0xB8, 0xD1, 0x81, 0xD0, 0xBA, 0x20,
+                    0xD1, 0x81, 0xD1, 0x82, 0xD1, 0x80, 0xD0, 0xB0, 0xD0, 0xBD, 0xD0, 0xB8, 0xD1,
+                    0x86, 0x20, 0xD0, 0xBD, 0xD0, 0xB0, 0x20, 0xD1, 0x80, 0xD1, 0x83, 0xD1, 0x81,
+                    0xD1, 0x81, 0xD0, 0xBA, 0xD0, 0xBE, 0xD0, 0xBC,
+                ][..]
+            ))
+            .unwrap()
+        );
+        assert_eq!(
+            String::from("전체서비스"),
+            <String as WireFormat>::decode(&mut Cursor::new(
+                &[
+                    0x0f, 0x00, 0xEC, 0xA0, 0x84, 0xEC, 0xB2, 0xB4, 0xEC, 0x84, 0x9C, 0xEB, 0xB9,
+                    0x84, 0xEC, 0x8A, 0xA4,
+                ][..]
+            ))
+            .unwrap()
+        );
+    }
+
+    #[test]
+    fn invalid_string_decode() {
+        let _ = <String as WireFormat>::decode(&mut Cursor::new(&[
+            0x06, 0x00, 0xed, 0xa0, 0x80, 0xed, 0xbf, 0xbf,
+        ]))
+        .expect_err("surrogate code point");
+
+        let _ = <String as WireFormat>::decode(&mut Cursor::new(&[
+            0x05, 0x00, 0xf8, 0x80, 0x80, 0x80, 0xbf,
+        ]))
+        .expect_err("overlong sequence");
+
+        let _ =
+            <String as WireFormat>::decode(&mut Cursor::new(&[0x04, 0x00, 0xf4, 0x90, 0x80, 0x80]))
+                .expect_err("out of range");
+
+        let _ =
+            <String as WireFormat>::decode(&mut Cursor::new(&[0x04, 0x00, 0x63, 0x61, 0x66, 0xe9]))
+                .expect_err("ISO-8859-1");
+
+        let _ =
+            <String as WireFormat>::decode(&mut Cursor::new(&[0x04, 0x00, 0xb0, 0xa1, 0xb0, 0xa2]))
+                .expect_err("EUC-KR");
+    }
+
+    #[test]
+    fn vector_encode() {
+        let values: Vec<u32> = vec![291, 18_916, 2_497, 22, 797_162, 2_119_732, 3_213_929_716];
+        let mut expected: Vec<u8> =
+            Vec::with_capacity(values.len() * mem::size_of::<u32>() + mem::size_of::<u16>());
+        expected.push(values.len() as u8);
+        expected.push((values.len() >> 8) as u8);
+
+        const MASK: u32 = 0xff;
+        for val in &values {
+            expected.push((val & MASK) as u8);
+            expected.push(((val >> 8) & MASK) as u8);
+            expected.push(((val >> 16) & MASK) as u8);
+            expected.push(((val >> 24) & MASK) as u8);
+        }
+
+        let mut actual: Vec<u8> = vec![0; expected.len()];
+
+        WireFormat::encode(&values, &mut Cursor::new(&mut *actual))
+            .expect("failed to encode vector");
+        assert_eq!(expected, actual);
+    }
+
+    #[test]
+    fn vector_decode() {
+        let expected: Vec<u32> = vec![
+            2_498,
+            24,
+            897,
+            4_097_789_579,
+            8_498_119,
+            684_279,
+            961_189_198,
+            7,
+        ];
+        let mut input: Vec<u8> =
+            Vec::with_capacity(expected.len() * mem::size_of::<u32>() + mem::size_of::<u16>());
+        input.push(expected.len() as u8);
+        input.push((expected.len() >> 8) as u8);
+
+        const MASK: u32 = 0xff;
+        for val in &expected {
+            input.push((val & MASK) as u8);
+            input.push(((val >> 8) & MASK) as u8);
+            input.push(((val >> 16) & MASK) as u8);
+            input.push(((val >> 24) & MASK) as u8);
+        }
+
+        assert_eq!(
+            expected,
+            <Vec<u32> as WireFormat>::decode(&mut Cursor::new(&*input))
+                .expect("failed to decode vector")
+        );
+    }
+
+    #[test]
+    fn data_encode() {
+        let values = Data(vec![169, 155, 79, 67, 182, 199, 25, 73, 129, 200]);
+        let mut expected: Vec<u8> =
+            Vec::with_capacity(values.len() * mem::size_of::<u8>() + mem::size_of::<u32>());
+        expected.push(values.len() as u8);
+        expected.push((values.len() >> 8) as u8);
+        expected.push((values.len() >> 16) as u8);
+        expected.push((values.len() >> 24) as u8);
+        expected.extend_from_slice(&values);
+
+        let mut actual: Vec<u8> = vec![0; expected.len()];
+
+        WireFormat::encode(&values, &mut Cursor::new(&mut *actual))
+            .expect("failed to encode datar");
+        assert_eq!(expected, actual);
+    }
+
+    #[test]
+    fn data_decode() {
+        let expected = Data(vec![219, 15, 8, 155, 194, 129, 79, 91, 46, 53, 173]);
+        let mut input: Vec<u8> =
+            Vec::with_capacity(expected.len() * mem::size_of::<u8>() + mem::size_of::<u32>());
+        input.push(expected.len() as u8);
+        input.push((expected.len() >> 8) as u8);
+        input.push((expected.len() >> 16) as u8);
+        input.push((expected.len() >> 24) as u8);
+        input.extend_from_slice(&expected);
+
+        assert_eq!(
+            expected,
+            <Data as WireFormat>::decode(&mut Cursor::new(&mut *input))
+                .expect("failed to decode data")
+        );
+    }
+
+    #[test]
+    fn error_cases() {
+        // string is too long.
+        let mut long_str = String::with_capacity(std::u16::MAX as usize);
+        while long_str.len() < std::u16::MAX as usize {
+            long_str.push_str("long");
+        }
+        long_str.push('!');
+
+        let count = long_str.len() + mem::size_of::<u16>();
+        let mut buf = vec![0; count];
+
+        long_str
+            .encode(&mut Cursor::new(&mut *buf))
+            .expect_err("long string");
+
+        // vector is too long.
+        let mut long_vec: Vec<u32> = Vec::with_capacity(std::u16::MAX as usize);
+        while long_vec.len() < std::u16::MAX as usize {
+            long_vec.push(0x8bad_f00d);
+        }
+        long_vec.push(0x00ba_b10c);
+
+        let count = long_vec.len() * mem::size_of::<u32>();
+        let mut buf = vec![0; count];
+
+        WireFormat::encode(&long_vec, &mut Cursor::new(&mut *buf)).expect_err("long vector");
+    }
+
+    #[derive(Debug, PartialEq, P9WireFormat)]
+    struct Item {
+        a: u64,
+        b: String,
+        c: Vec<u16>,
+        buf: Data,
+    }
+
+    #[test]
+    fn struct_encode() {
+        let item = Item {
+            a: 0xdead_10cc_00ba_b10c,
+            b: String::from("冻住,不许走!"),
+            c: vec![359, 492, 8891],
+            buf: Data(vec![254, 129, 0, 62, 49, 172]),
+        };
+
+        let mut expected: Vec<u8> = vec![0x0c, 0xb1, 0xba, 0x00, 0xcc, 0x10, 0xad, 0xde];
+        let strlen = item.b.len() as u16;
+        expected.push(strlen as u8);
+        expected.push((strlen >> 8) as u8);
+        expected.extend_from_slice(item.b.as_bytes());
+
+        let veclen = item.c.len() as u16;
+        expected.push(veclen as u8);
+        expected.push((veclen >> 8) as u8);
+        for val in &item.c {
+            expected.push(*val as u8);
+            expected.push((val >> 8) as u8);
+        }
+
+        let buflen = item.buf.len() as u32;
+        expected.push(buflen as u8);
+        expected.push((buflen >> 8) as u8);
+        expected.push((buflen >> 16) as u8);
+        expected.push((buflen >> 24) as u8);
+        expected.extend_from_slice(&item.buf);
+
+        let mut actual = vec![0; expected.len()];
+
+        WireFormat::encode(&item, &mut Cursor::new(&mut *actual)).expect("failed to encode item");
+
+        assert_eq!(expected, actual);
+    }
+
+    #[test]
+    fn struct_decode() {
+        let expected = Item {
+            a: 0xface_b00c_0404_4b1d,
+            b: String::from("Огонь по готовности!"),
+            c: vec![20067, 32449, 549, 4972, 77, 1987],
+            buf: Data(vec![126, 236, 79, 59, 6, 159]),
+        };
+
+        let mut input: Vec<u8> = vec![0x1d, 0x4b, 0x04, 0x04, 0x0c, 0xb0, 0xce, 0xfa];
+        let strlen = expected.b.len() as u16;
+        input.push(strlen as u8);
+        input.push((strlen >> 8) as u8);
+        input.extend_from_slice(expected.b.as_bytes());
+
+        let veclen = expected.c.len() as u16;
+        input.push(veclen as u8);
+        input.push((veclen >> 8) as u8);
+        for val in &expected.c {
+            input.push(*val as u8);
+            input.push((val >> 8) as u8);
+        }
+
+        let buflen = expected.buf.len() as u32;
+        input.push(buflen as u8);
+        input.push((buflen >> 8) as u8);
+        input.push((buflen >> 16) as u8);
+        input.push((buflen >> 24) as u8);
+        input.extend_from_slice(&expected.buf);
+
+        let actual: Item =
+            WireFormat::decode(&mut Cursor::new(input)).expect("failed to decode item");
+
+        assert_eq!(expected, actual);
+    }
+
+    #[derive(Debug, PartialEq, P9WireFormat)]
+    struct Nested {
+        item: Item,
+        val: Vec<u64>,
+    }
+
+    fn build_encoded_buffer(value: &Nested) -> Vec<u8> {
+        let mut result: Vec<u8> = Vec::new();
+
+        // encode a
+        result.push(value.item.a as u8);
+        result.push((value.item.a >> 8) as u8);
+        result.push((value.item.a >> 16) as u8);
+        result.push((value.item.a >> 24) as u8);
+        result.push((value.item.a >> 32) as u8);
+        result.push((value.item.a >> 40) as u8);
+        result.push((value.item.a >> 48) as u8);
+        result.push((value.item.a >> 56) as u8);
+
+        // encode b
+        result.push(value.item.b.len() as u8);
+        result.push((value.item.b.len() >> 8) as u8);
+        result.extend_from_slice(value.item.b.as_bytes());
+
+        // encode c
+        result.push(value.item.c.len() as u8);
+        result.push((value.item.c.len() >> 8) as u8);
+        for val in &value.item.c {
+            result.push((val & 0xffu16) as u8);
+            result.push(((val >> 8) & 0xffu16) as u8);
+        }
+
+        // encode buf
+        result.push(value.item.buf.len() as u8);
+        result.push((value.item.buf.len() >> 8) as u8);
+        result.push((value.item.buf.len() >> 16) as u8);
+        result.push((value.item.buf.len() >> 24) as u8);
+        result.extend_from_slice(&value.item.buf);
+
+        // encode val
+        result.push(value.val.len() as u8);
+        result.push((value.val.len() >> 8) as u8);
+        for val in &value.val {
+            result.push(*val as u8);
+            result.push((val >> 8) as u8);
+            result.push((val >> 16) as u8);
+            result.push((val >> 24) as u8);
+            result.push((val >> 32) as u8);
+            result.push((val >> 40) as u8);
+            result.push((val >> 48) as u8);
+            result.push((val >> 56) as u8);
+        }
+
+        result
+    }
+
+    #[test]
+    fn nested_encode() {
+        let value = Nested {
+            item: Item {
+                a: 0xcafe_d00d_8bad_f00d,
+                b: String::from("龍が我が敵を喰らう!"),
+                c: vec![2679, 55_919, 44, 38_819, 792],
+                buf: Data(vec![129, 55, 200, 93, 7, 68]),
+            },
+            val: vec![1954978, 59, 4519, 15679],
+        };
+
+        let expected = build_encoded_buffer(&value);
+
+        let mut actual = vec![0; expected.len()];
+
+        WireFormat::encode(&value, &mut Cursor::new(&mut *actual)).expect("failed to encode value");
+        assert_eq!(expected, actual);
+    }
+
+    #[test]
+    fn nested_decode() {
+        let expected = Nested {
+            item: Item {
+                a: 0x0ff1ce,
+                b: String::from("龍神の剣を喰らえ!"),
+                c: vec![21687, 159, 55, 9217, 192],
+                buf: Data(vec![189, 22, 7, 59, 235]),
+            },
+            val: vec![15679, 8619196, 319746, 123957, 77, 0, 492],
+        };
+
+        let input = build_encoded_buffer(&expected);
+
+        assert_eq!(
+            expected,
+            <Nested as WireFormat>::decode(&mut Cursor::new(&*input))
+                .expect("failed to decode value")
+        );
+    }
+}
diff --git a/common/p9/src/server/mod.rs b/common/p9/src/server/mod.rs
new file mode 100644
index 0000000..cc01332
--- /dev/null
+++ b/common/p9/src/server/mod.rs
@@ -0,0 +1,994 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::cmp::min;
+use std::collections::{btree_map, BTreeMap};
+use std::ffi::{CStr, CString};
+use std::fs::File;
+use std::io::{self, Cursor, Read, Write};
+use std::mem::{self, MaybeUninit};
+use std::ops::Deref;
+use std::os::unix::ffi::OsStrExt;
+use std::os::unix::fs::FileExt;
+use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
+use std::path::Path;
+
+use sys_util::{read_dir::read_dir, syscall};
+
+use crate::protocol::*;
+
+// Tlopen and Tlcreate flags.  Taken from "include/net/9p/9p.h" in the linux tree.
+const P9_RDONLY: u32 = 0o00000000;
+const P9_WRONLY: u32 = 0o00000001;
+const P9_RDWR: u32 = 0o00000002;
+const P9_NOACCESS: u32 = 0o00000003;
+const P9_CREATE: u32 = 0o00000100;
+const P9_EXCL: u32 = 0o00000200;
+const P9_NOCTTY: u32 = 0o00000400;
+const P9_TRUNC: u32 = 0o00001000;
+const P9_APPEND: u32 = 0o00002000;
+const P9_NONBLOCK: u32 = 0o00004000;
+const P9_DSYNC: u32 = 0o00010000;
+const P9_FASYNC: u32 = 0o00020000;
+const P9_DIRECT: u32 = 0o00040000;
+const P9_LARGEFILE: u32 = 0o00100000;
+const P9_DIRECTORY: u32 = 0o00200000;
+const P9_NOFOLLOW: u32 = 0o00400000;
+const P9_NOATIME: u32 = 0o01000000;
+const _P9_CLOEXEC: u32 = 0o02000000;
+const P9_SYNC: u32 = 0o04000000;
+
+// Mapping from 9P flags to libc flags.
+const MAPPED_FLAGS: [(u32, i32); 16] = [
+    (P9_WRONLY, libc::O_WRONLY),
+    (P9_RDWR, libc::O_RDWR),
+    (P9_CREATE, libc::O_CREAT),
+    (P9_EXCL, libc::O_EXCL),
+    (P9_NOCTTY, libc::O_NOCTTY),
+    (P9_TRUNC, libc::O_TRUNC),
+    (P9_APPEND, libc::O_APPEND),
+    (P9_NONBLOCK, libc::O_NONBLOCK),
+    (P9_DSYNC, libc::O_DSYNC),
+    (P9_FASYNC, 0), // Unsupported
+    (P9_DIRECT, libc::O_DIRECT),
+    (P9_LARGEFILE, libc::O_LARGEFILE),
+    (P9_DIRECTORY, libc::O_DIRECTORY),
+    (P9_NOFOLLOW, libc::O_NOFOLLOW),
+    (P9_NOATIME, libc::O_NOATIME),
+    (P9_SYNC, libc::O_SYNC),
+];
+
+// 9P Qid types.  Taken from "include/net/9p/9p.h" in the linux tree.
+const P9_QTDIR: u8 = 0x80;
+const _P9_QTAPPEND: u8 = 0x40;
+const _P9_QTEXCL: u8 = 0x20;
+const _P9_QTMOUNT: u8 = 0x10;
+const _P9_QTAUTH: u8 = 0x08;
+const _P9_QTTMP: u8 = 0x04;
+const P9_QTSYMLINK: u8 = 0x02;
+const _P9_QTLINK: u8 = 0x01;
+const P9_QTFILE: u8 = 0x00;
+
+// Bitmask values for the getattr request.
+const _P9_GETATTR_MODE: u64 = 0x00000001;
+const _P9_GETATTR_NLINK: u64 = 0x00000002;
+const _P9_GETATTR_UID: u64 = 0x00000004;
+const _P9_GETATTR_GID: u64 = 0x00000008;
+const _P9_GETATTR_RDEV: u64 = 0x00000010;
+const _P9_GETATTR_ATIME: u64 = 0x00000020;
+const _P9_GETATTR_MTIME: u64 = 0x00000040;
+const _P9_GETATTR_CTIME: u64 = 0x00000080;
+const _P9_GETATTR_INO: u64 = 0x00000100;
+const _P9_GETATTR_SIZE: u64 = 0x00000200;
+const _P9_GETATTR_BLOCKS: u64 = 0x00000400;
+
+const _P9_GETATTR_BTIME: u64 = 0x00000800;
+const _P9_GETATTR_GEN: u64 = 0x00001000;
+const _P9_GETATTR_DATA_VERSION: u64 = 0x00002000;
+
+const P9_GETATTR_BASIC: u64 = 0x000007ff; /* Mask for fields up to BLOCKS */
+const _P9_GETATTR_ALL: u64 = 0x00003fff; /* Mask for All fields above */
+
+// Bitmask values for the setattr request.
+const P9_SETATTR_MODE: u32 = 0x00000001;
+const P9_SETATTR_UID: u32 = 0x00000002;
+const P9_SETATTR_GID: u32 = 0x00000004;
+const P9_SETATTR_SIZE: u32 = 0x00000008;
+const P9_SETATTR_ATIME: u32 = 0x00000010;
+const P9_SETATTR_MTIME: u32 = 0x00000020;
+const P9_SETATTR_CTIME: u32 = 0x00000040;
+const P9_SETATTR_ATIME_SET: u32 = 0x00000080;
+const P9_SETATTR_MTIME_SET: u32 = 0x00000100;
+
+// Minimum and maximum message size that we'll expect from the client.
+const MIN_MESSAGE_SIZE: u32 = 256;
+const MAX_MESSAGE_SIZE: u32 = ::std::u16::MAX as u32;
+
+#[derive(PartialEq, Eq)]
+enum FileType {
+    Regular,
+    Directory,
+    Other,
+}
+
+impl From<libc::mode_t> for FileType {
+    fn from(mode: libc::mode_t) -> Self {
+        match mode & libc::S_IFMT {
+            libc::S_IFREG => FileType::Regular,
+            libc::S_IFDIR => FileType::Directory,
+            _ => FileType::Other,
+        }
+    }
+}
+
+// Represents state that the server is holding on behalf of a client. Fids are somewhat like file
+// descriptors but are not restricted to open files and directories. Fids are identified by a unique
+// 32-bit number chosen by the client. Most messages sent by clients include a fid on which to
+// operate. The fid in a Tattach message represents the root of the file system tree that the client
+// is allowed to access. A client can create more fids by walking the directory tree from that fid.
+struct Fid {
+    path: File,
+    file: Option<File>,
+    filetype: FileType,
+}
+
+impl From<libc::stat64> for Qid {
+    fn from(st: libc::stat64) -> Qid {
+        let ty = match st.st_mode & libc::S_IFMT {
+            libc::S_IFDIR => P9_QTDIR,
+            libc::S_IFREG => P9_QTFILE,
+            libc::S_IFLNK => P9_QTSYMLINK,
+            _ => 0,
+        };
+
+        Qid {
+            ty,
+            // TODO: deal with the 2038 problem before 2038
+            version: st.st_mtime as u32,
+            path: st.st_ino,
+        }
+    }
+}
+
+fn statat(d: &File, name: &CStr, flags: libc::c_int) -> io::Result<libc::stat64> {
+    let mut st = MaybeUninit::<libc::stat64>::zeroed();
+
+    // Safe because the kernel will only write data in `st` and we check the return
+    // value.
+    let res = unsafe {
+        libc::fstatat64(
+            d.as_raw_fd(),
+            name.as_ptr(),
+            st.as_mut_ptr(),
+            flags | libc::AT_SYMLINK_NOFOLLOW,
+        )
+    };
+    if res >= 0 {
+        // Safe because the kernel guarantees that the struct is now fully initialized.
+        Ok(unsafe { st.assume_init() })
+    } else {
+        Err(io::Error::last_os_error())
+    }
+}
+
+fn stat(f: &File) -> io::Result<libc::stat64> {
+    // Safe because this is a constant value and a valid C string.
+    let pathname = unsafe { CStr::from_bytes_with_nul_unchecked(b"\0") };
+
+    statat(f, pathname, libc::AT_EMPTY_PATH)
+}
+
+fn string_to_cstring(s: String) -> io::Result<CString> {
+    CString::new(s).map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))
+}
+
+fn error_to_rmessage(err: io::Error) -> Rmessage {
+    let errno = if let Some(errno) = err.raw_os_error() {
+        errno
+    } else {
+        // Make a best-effort guess based on the kind.
+        match err.kind() {
+            io::ErrorKind::NotFound => libc::ENOENT,
+            io::ErrorKind::PermissionDenied => libc::EPERM,
+            io::ErrorKind::ConnectionRefused => libc::ECONNREFUSED,
+            io::ErrorKind::ConnectionReset => libc::ECONNRESET,
+            io::ErrorKind::ConnectionAborted => libc::ECONNABORTED,
+            io::ErrorKind::NotConnected => libc::ENOTCONN,
+            io::ErrorKind::AddrInUse => libc::EADDRINUSE,
+            io::ErrorKind::AddrNotAvailable => libc::EADDRNOTAVAIL,
+            io::ErrorKind::BrokenPipe => libc::EPIPE,
+            io::ErrorKind::AlreadyExists => libc::EEXIST,
+            io::ErrorKind::WouldBlock => libc::EWOULDBLOCK,
+            io::ErrorKind::InvalidInput => libc::EINVAL,
+            io::ErrorKind::InvalidData => libc::EINVAL,
+            io::ErrorKind::TimedOut => libc::ETIMEDOUT,
+            io::ErrorKind::WriteZero => libc::EIO,
+            io::ErrorKind::Interrupted => libc::EINTR,
+            io::ErrorKind::Other => libc::EIO,
+            io::ErrorKind::UnexpectedEof => libc::EIO,
+            _ => libc::EIO,
+        }
+    };
+
+    Rmessage::Lerror(Rlerror {
+        ecode: errno as u32,
+    })
+}
+
+// Sigh.. Cow requires the underlying type to implement Clone.
+enum MaybeOwned<'b, T> {
+    Borrowed(&'b T),
+    Owned(T),
+}
+
+impl<'a, T> Deref for MaybeOwned<'a, T> {
+    type Target = T;
+    fn deref(&self) -> &Self::Target {
+        use MaybeOwned::*;
+        match *self {
+            Borrowed(borrowed) => borrowed,
+            Owned(ref owned) => owned,
+        }
+    }
+}
+
+fn ebadf() -> io::Error {
+    io::Error::from_raw_os_error(libc::EBADF)
+}
+
+pub type ServerIdMap<T> = BTreeMap<T, T>;
+pub type ServerUidMap = ServerIdMap<libc::uid_t>;
+pub type ServerGidMap = ServerIdMap<libc::gid_t>;
+
+fn map_id_from_host<T: Clone + Ord>(map: &ServerIdMap<T>, id: T) -> T {
+    map.get(&id).map_or(id.clone(), |v| v.clone())
+}
+
+// Performs an ascii case insensitive lookup and returns an O_PATH fd for the entry, if found.
+fn ascii_casefold_lookup(proc: &File, parent: &File, name: &[u8]) -> io::Result<File> {
+    let mut dir = open_fid(proc, &parent, P9_DIRECTORY)?;
+    let mut dirents = read_dir(&mut dir, 0)?;
+
+    while let Some(entry) = dirents.next().transpose()? {
+        if name.eq_ignore_ascii_case(entry.name.to_bytes()) {
+            return lookup(parent, entry.name);
+        }
+    }
+
+    Err(io::Error::from_raw_os_error(libc::ENOENT))
+}
+
+fn lookup(parent: &File, name: &CStr) -> io::Result<File> {
+    // Safe because this doesn't modify any memory and we check the return value.
+    let fd = syscall!(unsafe {
+        libc::openat(
+            parent.as_raw_fd(),
+            name.as_ptr(),
+            libc::O_PATH | libc::O_NOFOLLOW | libc::O_CLOEXEC,
+        )
+    })?;
+
+    // Safe because we just opened this fd.
+    Ok(unsafe { File::from_raw_fd(fd) })
+}
+
+fn do_walk(
+    proc: &File,
+    wnames: Vec<String>,
+    start: File,
+    ascii_casefold: bool,
+    mds: &mut Vec<libc::stat64>,
+) -> io::Result<File> {
+    let mut current = start;
+
+    for wname in wnames {
+        let name = string_to_cstring(wname)?;
+        current = lookup(&current, &name).or_else(|e| {
+            if ascii_casefold {
+                if let Some(libc::ENOENT) = e.raw_os_error() {
+                    return ascii_casefold_lookup(proc, &current, name.to_bytes());
+                }
+            }
+
+            Err(e)
+        })?;
+        mds.push(stat(&current)?);
+    }
+
+    Ok(current)
+}
+
+fn open_fid(proc: &File, path: &File, p9_flags: u32) -> io::Result<File> {
+    let pathname = string_to_cstring(format!("self/fd/{}", path.as_raw_fd()))?;
+
+    // We always open files with O_CLOEXEC.
+    let mut flags: i32 = libc::O_CLOEXEC;
+    for &(p9f, of) in &MAPPED_FLAGS {
+        if (p9_flags & p9f) != 0 {
+            flags |= of;
+        }
+    }
+
+    if p9_flags & P9_NOACCESS == P9_RDONLY {
+        flags |= libc::O_RDONLY;
+    }
+
+    // Safe because this doesn't modify any memory and we check the return value. We need to
+    // clear the O_NOFOLLOW flag because we want to follow the proc symlink.
+    let fd = syscall!(unsafe {
+        libc::openat(
+            proc.as_raw_fd(),
+            pathname.as_ptr(),
+            flags & !libc::O_NOFOLLOW,
+        )
+    })?;
+
+    // Safe because we just opened this fd and we know it is valid.
+    Ok(unsafe { File::from_raw_fd(fd) })
+}
+
+#[derive(Clone)]
+pub struct Config {
+    pub root: Box<Path>,
+    pub msize: u32,
+
+    pub uid_map: ServerUidMap,
+    pub gid_map: ServerGidMap,
+
+    pub ascii_casefold: bool,
+}
+
+impl Default for Config {
+    fn default() -> Config {
+        Config {
+            root: Path::new("/").into(),
+            msize: MAX_MESSAGE_SIZE,
+            uid_map: Default::default(),
+            gid_map: Default::default(),
+            ascii_casefold: false,
+        }
+    }
+}
+pub struct Server {
+    fids: BTreeMap<u32, Fid>,
+    proc: File,
+    cfg: Config,
+}
+
+impl Server {
+    pub fn new<P: Into<Box<Path>>>(
+        root: P,
+        uid_map: ServerUidMap,
+        gid_map: ServerGidMap,
+    ) -> io::Result<Server> {
+        Server::with_config(Config {
+            root: root.into(),
+            msize: MAX_MESSAGE_SIZE,
+            uid_map,
+            gid_map,
+            ascii_casefold: false,
+        })
+    }
+
+    pub fn with_config(cfg: Config) -> io::Result<Server> {
+        // Safe because this is a valid c-string.
+        let proc_cstr = unsafe { CStr::from_bytes_with_nul_unchecked(b"/proc\0") };
+
+        // Safe because this doesn't modify any memory and we check the return value.
+        let fd = syscall!(unsafe {
+            libc::openat(
+                libc::AT_FDCWD,
+                proc_cstr.as_ptr(),
+                libc::O_PATH | libc::O_NOFOLLOW | libc::O_CLOEXEC,
+            )
+        })?;
+
+        // Safe because we just opened this fd and we know it is valid.
+        let proc = unsafe { File::from_raw_fd(fd) };
+        Ok(Server {
+            fids: BTreeMap::new(),
+            proc,
+            cfg,
+        })
+    }
+
+    pub fn keep_fds(&self) -> Vec<RawFd> {
+        vec![self.proc.as_raw_fd()]
+    }
+
+    pub fn handle_message<R: Read, W: Write>(
+        &mut self,
+        reader: &mut R,
+        writer: &mut W,
+    ) -> io::Result<()> {
+        let Tframe { tag, msg } = WireFormat::decode(&mut reader.take(self.cfg.msize as u64))?;
+
+        let rmsg = match msg {
+            Tmessage::Version(ref version) => self.version(version).map(Rmessage::Version),
+            Tmessage::Flush(ref flush) => self.flush(flush).and(Ok(Rmessage::Flush)),
+            Tmessage::Walk(walk) => self.walk(walk).map(Rmessage::Walk),
+            Tmessage::Read(ref read) => self.read(read).map(Rmessage::Read),
+            Tmessage::Write(ref write) => self.write(write).map(Rmessage::Write),
+            Tmessage::Clunk(ref clunk) => self.clunk(clunk).and(Ok(Rmessage::Clunk)),
+            Tmessage::Remove(ref remove) => self.remove(remove).and(Ok(Rmessage::Remove)),
+            Tmessage::Attach(ref attach) => self.attach(attach).map(Rmessage::Attach),
+            Tmessage::Auth(ref auth) => self.auth(auth).map(Rmessage::Auth),
+            Tmessage::Statfs(ref statfs) => self.statfs(statfs).map(Rmessage::Statfs),
+            Tmessage::Lopen(ref lopen) => self.lopen(lopen).map(Rmessage::Lopen),
+            Tmessage::Lcreate(lcreate) => self.lcreate(lcreate).map(Rmessage::Lcreate),
+            Tmessage::Symlink(ref symlink) => self.symlink(symlink).map(Rmessage::Symlink),
+            Tmessage::Mknod(ref mknod) => self.mknod(mknod).map(Rmessage::Mknod),
+            Tmessage::Rename(ref rename) => self.rename(rename).and(Ok(Rmessage::Rename)),
+            Tmessage::Readlink(ref readlink) => self.readlink(readlink).map(Rmessage::Readlink),
+            Tmessage::GetAttr(ref get_attr) => self.get_attr(get_attr).map(Rmessage::GetAttr),
+            Tmessage::SetAttr(ref set_attr) => self.set_attr(set_attr).and(Ok(Rmessage::SetAttr)),
+            Tmessage::XattrWalk(ref xattr_walk) => {
+                self.xattr_walk(xattr_walk).map(Rmessage::XattrWalk)
+            }
+            Tmessage::XattrCreate(ref xattr_create) => self
+                .xattr_create(xattr_create)
+                .and(Ok(Rmessage::XattrCreate)),
+            Tmessage::Readdir(ref readdir) => self.readdir(readdir).map(Rmessage::Readdir),
+            Tmessage::Fsync(ref fsync) => self.fsync(fsync).and(Ok(Rmessage::Fsync)),
+            Tmessage::Lock(ref lock) => self.lock(lock).map(Rmessage::Lock),
+            Tmessage::GetLock(ref get_lock) => self.get_lock(get_lock).map(Rmessage::GetLock),
+            Tmessage::Link(link) => self.link(link).and(Ok(Rmessage::Link)),
+            Tmessage::Mkdir(mkdir) => self.mkdir(mkdir).map(Rmessage::Mkdir),
+            Tmessage::RenameAt(rename_at) => self.rename_at(rename_at).and(Ok(Rmessage::RenameAt)),
+            Tmessage::UnlinkAt(unlink_at) => self.unlink_at(unlink_at).and(Ok(Rmessage::UnlinkAt)),
+        };
+
+        // Errors while handling requests are never fatal.
+        let response = Rframe {
+            tag,
+            msg: rmsg.unwrap_or_else(error_to_rmessage),
+        };
+
+        response.encode(writer)?;
+        writer.flush()
+    }
+
+    fn auth(&mut self, _auth: &Tauth) -> io::Result<Rauth> {
+        // Returning an error for the auth message means that the server does not require
+        // authentication.
+        Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
+    }
+
+    fn attach(&mut self, attach: &Tattach) -> io::Result<Rattach> {
+        // TODO: Check attach parameters
+        match self.fids.entry(attach.fid) {
+            btree_map::Entry::Vacant(entry) => {
+                let root = CString::new(self.cfg.root.as_os_str().as_bytes())
+                    .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
+
+                // Safe because this doesn't modify any memory and we check the return value.
+                let fd = syscall!(unsafe {
+                    libc::openat(
+                        libc::AT_FDCWD,
+                        root.as_ptr(),
+                        libc::O_PATH | libc::O_NOFOLLOW | libc::O_CLOEXEC,
+                    )
+                })?;
+
+                let root_path = unsafe { File::from_raw_fd(fd) };
+                let st = stat(&root_path)?;
+
+                let fid = Fid {
+                    // Safe because we just opened this fd.
+                    path: root_path,
+                    file: None,
+                    filetype: st.st_mode.into(),
+                };
+                let response = Rattach { qid: st.into() };
+                entry.insert(fid);
+                Ok(response)
+            }
+            btree_map::Entry::Occupied(_) => Err(io::Error::from_raw_os_error(libc::EBADF)),
+        }
+    }
+
+    fn version(&mut self, version: &Tversion) -> io::Result<Rversion> {
+        if version.msize < MIN_MESSAGE_SIZE {
+            return Err(io::Error::from_raw_os_error(libc::EINVAL));
+        }
+
+        // A Tversion request clunks all open fids and terminates any pending I/O.
+        self.fids.clear();
+        self.cfg.msize = min(self.cfg.msize, version.msize);
+
+        Ok(Rversion {
+            msize: self.cfg.msize,
+            version: if version.version == "9P2000.L" {
+                String::from("9P2000.L")
+            } else {
+                String::from("unknown")
+            },
+        })
+    }
+
+    #[allow(clippy::unnecessary_wraps)]
+    fn flush(&mut self, _flush: &Tflush) -> io::Result<()> {
+        // TODO: Since everything is synchronous we can't actually flush requests.
+        Ok(())
+    }
+
+    fn walk(&mut self, walk: Twalk) -> io::Result<Rwalk> {
+        // `newfid` must not currently be in use unless it is the same as `fid`.
+        if walk.fid != walk.newfid && self.fids.contains_key(&walk.newfid) {
+            return Err(io::Error::from_raw_os_error(libc::EBADF));
+        }
+
+        // We need to walk the tree.  First get the starting path.
+        let start = self
+            .fids
+            .get(&walk.fid)
+            .ok_or_else(ebadf)
+            .and_then(|fid| fid.path.try_clone())?;
+
+        // Now walk the tree and break on the first error, if any.
+        let expected_len = walk.wnames.len();
+        let mut mds = Vec::with_capacity(expected_len);
+        match do_walk(
+            &self.proc,
+            walk.wnames,
+            start,
+            self.cfg.ascii_casefold,
+            &mut mds,
+        ) {
+            Ok(end) => {
+                // Store the new fid if the full walk succeeded.
+                if mds.len() == expected_len {
+                    let st = mds.last().copied().map(Ok).unwrap_or_else(|| stat(&end))?;
+                    self.fids.insert(
+                        walk.newfid,
+                        Fid {
+                            path: end,
+                            file: None,
+                            filetype: st.st_mode.into(),
+                        },
+                    );
+                }
+            }
+            Err(e) => {
+                // Only return an error if it occurred on the first component.
+                if mds.is_empty() {
+                    return Err(e);
+                }
+            }
+        }
+
+        Ok(Rwalk {
+            wqids: mds.into_iter().map(Qid::from).collect(),
+        })
+    }
+
+    fn read(&mut self, read: &Tread) -> io::Result<Rread> {
+        // Thankfully, `read` cannot be used to read directories in 9P2000.L.
+        let file = self
+            .fids
+            .get_mut(&read.fid)
+            .and_then(|fid| fid.file.as_mut())
+            .ok_or_else(ebadf)?;
+
+        // Use an empty Rread struct to figure out the overhead of the header.
+        let header_size = Rframe {
+            tag: 0,
+            msg: Rmessage::Read(Rread {
+                data: Data(Vec::new()),
+            }),
+        }
+        .byte_size();
+
+        let capacity = min(self.cfg.msize - header_size, read.count);
+        let mut buf = Data(vec![0u8; capacity as usize]);
+
+        let count = file.read_at(&mut buf, read.offset)?;
+        buf.truncate(count);
+
+        Ok(Rread { data: buf })
+    }
+
+    fn write(&mut self, write: &Twrite) -> io::Result<Rwrite> {
+        let file = self
+            .fids
+            .get_mut(&write.fid)
+            .and_then(|fid| fid.file.as_mut())
+            .ok_or_else(ebadf)?;
+
+        let count = file.write_at(&write.data, write.offset)?;
+        Ok(Rwrite {
+            count: count as u32,
+        })
+    }
+
+    fn clunk(&mut self, clunk: &Tclunk) -> io::Result<()> {
+        match self.fids.entry(clunk.fid) {
+            btree_map::Entry::Vacant(_) => Err(io::Error::from_raw_os_error(libc::EBADF)),
+            btree_map::Entry::Occupied(entry) => {
+                entry.remove();
+                Ok(())
+            }
+        }
+    }
+
+    fn remove(&mut self, _remove: &Tremove) -> io::Result<()> {
+        // Since a file could be linked into multiple locations, there is no way to know exactly
+        // which path we are supposed to unlink. Linux uses unlink_at anyway, so we can just return
+        // an error here.
+        Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
+    }
+
+    fn statfs(&mut self, statfs: &Tstatfs) -> io::Result<Rstatfs> {
+        let fid = self.fids.get(&statfs.fid).ok_or_else(ebadf)?;
+        let mut buf = MaybeUninit::zeroed();
+
+        // Safe because this will only modify `out` and we check the return value.
+        syscall!(unsafe { libc::fstatfs64(fid.path.as_raw_fd(), buf.as_mut_ptr()) })?;
+
+        // Safe because this only has integer types and any value is valid.
+        let out = unsafe { buf.assume_init() };
+        Ok(Rstatfs {
+            ty: out.f_type as u32,
+            bsize: out.f_bsize as u32,
+            blocks: out.f_blocks,
+            bfree: out.f_bfree,
+            bavail: out.f_bavail,
+            files: out.f_files,
+            ffree: out.f_ffree,
+            // Safe because the fsid has only integer fields and the compiler will verify that is
+            // the same width as the `fsid` field in Rstatfs.
+            fsid: unsafe { mem::transmute(out.f_fsid) },
+            namelen: out.f_namelen as u32,
+        })
+    }
+
+    fn lopen(&mut self, lopen: &Tlopen) -> io::Result<Rlopen> {
+        let fid = self.fids.get_mut(&lopen.fid).ok_or_else(ebadf)?;
+
+        let file = open_fid(&self.proc, &fid.path, lopen.flags)?;
+        let st = stat(&file)?;
+
+        fid.file = Some(file);
+        let iounit = st.st_blksize as u32;
+        Ok(Rlopen {
+            qid: st.into(),
+            iounit,
+        })
+    }
+
+    fn lcreate(&mut self, lcreate: Tlcreate) -> io::Result<Rlcreate> {
+        let fid = self.fids.get_mut(&lcreate.fid).ok_or_else(ebadf)?;
+
+        if fid.filetype != FileType::Directory {
+            return Err(io::Error::from_raw_os_error(libc::ENOTDIR));
+        }
+
+        let mut flags: i32 = libc::O_CLOEXEC | libc::O_CREAT | libc::O_EXCL;
+        for &(p9f, of) in &MAPPED_FLAGS {
+            if (lcreate.flags & p9f) != 0 {
+                flags |= of;
+            }
+        }
+        if lcreate.flags & P9_NOACCESS == P9_RDONLY {
+            flags |= libc::O_RDONLY;
+        }
+
+        let name = string_to_cstring(lcreate.name)?;
+
+        // Safe because this doesn't modify any memory and we check the return value.
+        let fd = syscall!(unsafe {
+            libc::openat(fid.path.as_raw_fd(), name.as_ptr(), flags, lcreate.mode)
+        })?;
+
+        // Safe because we just opened this fd and we know it is valid.
+        let file = unsafe { File::from_raw_fd(fd) };
+        let st = stat(&file)?;
+        let iounit = st.st_blksize as u32;
+
+        fid.file = Some(file);
+
+        // This fid now refers to the newly created file so we need to update the O_PATH fd for it
+        // as well.
+        fid.path = lookup(&fid.path, &name)?;
+
+        Ok(Rlcreate {
+            qid: st.into(),
+            iounit,
+        })
+    }
+
+    fn symlink(&mut self, _symlink: &Tsymlink) -> io::Result<Rsymlink> {
+        // symlinks are not allowed.
+        Err(io::Error::from_raw_os_error(libc::EACCES))
+    }
+
+    fn mknod(&mut self, _mknod: &Tmknod) -> io::Result<Rmknod> {
+        // No nodes either.
+        Err(io::Error::from_raw_os_error(libc::EACCES))
+    }
+
+    fn rename(&mut self, _rename: &Trename) -> io::Result<()> {
+        // We cannot support this as an inode may be linked into multiple directories but we don't
+        // know which one the client wants us to rename. Linux uses rename_at anyway, so we don't
+        // need to worry about this.
+        Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
+    }
+
+    fn readlink(&mut self, _readlink: &Treadlink) -> io::Result<Rreadlink> {
+        // symlinks are not allowed
+        Err(io::Error::from_raw_os_error(libc::EACCES))
+    }
+
+    fn get_attr(&mut self, get_attr: &Tgetattr) -> io::Result<Rgetattr> {
+        let fid = self.fids.get_mut(&get_attr.fid).ok_or_else(ebadf)?;
+
+        let st = stat(&fid.path)?;
+
+        Ok(Rgetattr {
+            valid: P9_GETATTR_BASIC,
+            qid: st.into(),
+            mode: st.st_mode,
+            uid: map_id_from_host(&self.cfg.uid_map, st.st_uid),
+            gid: map_id_from_host(&self.cfg.gid_map, st.st_gid),
+            nlink: st.st_nlink as u64,
+            rdev: st.st_rdev,
+            size: st.st_size as u64,
+            blksize: st.st_blksize as u64,
+            blocks: st.st_blocks as u64,
+            atime_sec: st.st_atime as u64,
+            atime_nsec: st.st_atime_nsec as u64,
+            mtime_sec: st.st_mtime as u64,
+            mtime_nsec: st.st_mtime_nsec as u64,
+            ctime_sec: st.st_ctime as u64,
+            ctime_nsec: st.st_ctime_nsec as u64,
+            btime_sec: 0,
+            btime_nsec: 0,
+            gen: 0,
+            data_version: 0,
+        })
+    }
+
+    fn set_attr(&mut self, set_attr: &Tsetattr) -> io::Result<()> {
+        let fid = self.fids.get(&set_attr.fid).ok_or_else(ebadf)?;
+
+        let file = if let Some(ref file) = fid.file {
+            MaybeOwned::Borrowed(file)
+        } else {
+            let flags = match fid.filetype {
+                FileType::Regular => P9_RDWR,
+                FileType::Directory => P9_RDONLY | P9_DIRECTORY,
+                FileType::Other => P9_RDWR,
+            };
+            MaybeOwned::Owned(open_fid(&self.proc, &fid.path, P9_NONBLOCK | flags)?)
+        };
+
+        if set_attr.valid & P9_SETATTR_MODE != 0 {
+            // Safe because this doesn't modify any memory and we check the return value.
+            syscall!(unsafe { libc::fchmod(file.as_raw_fd(), set_attr.mode) })?;
+        }
+
+        if set_attr.valid & (P9_SETATTR_UID | P9_SETATTR_GID) != 0 {
+            let uid = if set_attr.valid & P9_SETATTR_UID != 0 {
+                set_attr.uid
+            } else {
+                -1i32 as u32
+            };
+            let gid = if set_attr.valid & P9_SETATTR_GID != 0 {
+                set_attr.gid
+            } else {
+                -1i32 as u32
+            };
+
+            // Safe because this doesn't modify any memory and we check the return value.
+            syscall!(unsafe { libc::fchown(file.as_raw_fd(), uid, gid) })?;
+        }
+
+        if set_attr.valid & P9_SETATTR_SIZE != 0 {
+            file.set_len(set_attr.size)?;
+        }
+
+        if set_attr.valid & (P9_SETATTR_ATIME | P9_SETATTR_MTIME) != 0 {
+            let times = [
+                libc::timespec {
+                    tv_sec: set_attr.atime_sec as _,
+                    tv_nsec: if set_attr.valid & P9_SETATTR_ATIME == 0 {
+                        libc::UTIME_OMIT
+                    } else if set_attr.valid & P9_SETATTR_ATIME_SET == 0 {
+                        libc::UTIME_NOW
+                    } else {
+                        set_attr.atime_nsec as _
+                    },
+                },
+                libc::timespec {
+                    tv_sec: set_attr.mtime_sec as _,
+                    tv_nsec: if set_attr.valid & P9_SETATTR_MTIME == 0 {
+                        libc::UTIME_OMIT
+                    } else if set_attr.valid & P9_SETATTR_MTIME_SET == 0 {
+                        libc::UTIME_NOW
+                    } else {
+                        set_attr.mtime_nsec as _
+                    },
+                },
+            ];
+
+            // Safe because file is valid and we have initialized times fully.
+            let ret = unsafe { libc::futimens(file.as_raw_fd(), &times as *const libc::timespec) };
+            if ret < 0 {
+                return Err(io::Error::last_os_error());
+            }
+        }
+
+        // The ctime would have been updated by any of the above operations so we only
+        // need to change it if it was the only option given.
+        if set_attr.valid & P9_SETATTR_CTIME != 0 && set_attr.valid & (!P9_SETATTR_CTIME) == 0 {
+            // Setting -1 as the uid and gid will not actually change anything but will
+            // still update the ctime.
+            let ret = unsafe {
+                libc::fchown(
+                    file.as_raw_fd(),
+                    libc::uid_t::max_value(),
+                    libc::gid_t::max_value(),
+                )
+            };
+            if ret < 0 {
+                return Err(io::Error::last_os_error());
+            }
+        }
+
+        Ok(())
+    }
+
+    fn xattr_walk(&mut self, _xattr_walk: &Txattrwalk) -> io::Result<Rxattrwalk> {
+        Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
+    }
+
+    fn xattr_create(&mut self, _xattr_create: &Txattrcreate) -> io::Result<()> {
+        Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
+    }
+
+    fn readdir(&mut self, readdir: &Treaddir) -> io::Result<Rreaddir> {
+        let fid = self.fids.get_mut(&readdir.fid).ok_or_else(ebadf)?;
+
+        if fid.filetype != FileType::Directory {
+            return Err(io::Error::from_raw_os_error(libc::ENOTDIR));
+        }
+
+        // Use an empty Rreaddir struct to figure out the maximum number of bytes that
+        // can be returned.
+        let header_size = Rframe {
+            tag: 0,
+            msg: Rmessage::Readdir(Rreaddir {
+                data: Data(Vec::new()),
+            }),
+        }
+        .byte_size();
+        let count = min(self.cfg.msize - header_size, readdir.count);
+        let mut cursor = Cursor::new(Vec::with_capacity(count as usize));
+
+        let dir = fid.file.as_mut().ok_or_else(ebadf)?;
+        let mut dirents = read_dir(dir, readdir.offset as libc::off64_t)?;
+        while let Some(dirent) = dirents.next().transpose()? {
+            let st = statat(&fid.path, &dirent.name, 0)?;
+
+            let name = dirent
+                .name
+                .to_str()
+                .map(String::from)
+                .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
+
+            let entry = Dirent {
+                qid: st.into(),
+                offset: dirent.offset,
+                ty: dirent.type_,
+                name,
+            };
+
+            let byte_size = entry.byte_size() as usize;
+
+            if cursor.get_ref().capacity() - cursor.get_ref().len() < byte_size {
+                // No more room in the buffer.
+                break;
+            }
+
+            entry.encode(&mut cursor)?;
+        }
+
+        Ok(Rreaddir {
+            data: Data(cursor.into_inner()),
+        })
+    }
+
+    fn fsync(&mut self, fsync: &Tfsync) -> io::Result<()> {
+        let file = self
+            .fids
+            .get(&fsync.fid)
+            .and_then(|fid| fid.file.as_ref())
+            .ok_or_else(ebadf)?;
+
+        if fsync.datasync == 0 {
+            file.sync_all()?;
+        } else {
+            file.sync_data()?;
+        }
+        Ok(())
+    }
+
+    fn lock(&mut self, _lock: &Tlock) -> io::Result<Rlock> {
+        // File locking is not supported.
+        Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
+    }
+    fn get_lock(&mut self, _get_lock: &Tgetlock) -> io::Result<Rgetlock> {
+        // File locking is not supported.
+        Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
+    }
+
+    fn link(&mut self, link: Tlink) -> io::Result<()> {
+        let target = self.fids.get(&link.fid).ok_or_else(ebadf)?;
+        let path = string_to_cstring(format!("self/fd/{}", target.path.as_raw_fd()))?;
+
+        let dir = self.fids.get(&link.dfid).ok_or_else(ebadf)?;
+        let name = string_to_cstring(link.name)?;
+
+        // Safe because this doesn't modify any memory and we check the return value.
+        syscall!(unsafe {
+            libc::linkat(
+                self.proc.as_raw_fd(),
+                path.as_ptr(),
+                dir.path.as_raw_fd(),
+                name.as_ptr(),
+                libc::AT_SYMLINK_FOLLOW,
+            )
+        })?;
+        Ok(())
+    }
+
+    fn mkdir(&mut self, mkdir: Tmkdir) -> io::Result<Rmkdir> {
+        let fid = self.fids.get(&mkdir.dfid).ok_or_else(ebadf)?;
+        let name = string_to_cstring(mkdir.name)?;
+
+        // Safe because this doesn't modify any memory and we check the return value.
+        syscall!(unsafe { libc::mkdirat(fid.path.as_raw_fd(), name.as_ptr(), mkdir.mode) })?;
+        Ok(Rmkdir {
+            qid: statat(&fid.path, &name, 0).map(Qid::from)?,
+        })
+    }
+
+    fn rename_at(&mut self, rename_at: Trenameat) -> io::Result<()> {
+        let olddir = self.fids.get(&rename_at.olddirfid).ok_or_else(ebadf)?;
+        let oldname = string_to_cstring(rename_at.oldname)?;
+
+        let newdir = self.fids.get(&rename_at.newdirfid).ok_or_else(ebadf)?;
+        let newname = string_to_cstring(rename_at.newname)?;
+
+        // Safe because this doesn't modify any memory and we check the return value.
+        syscall!(unsafe {
+            libc::renameat(
+                olddir.path.as_raw_fd(),
+                oldname.as_ptr(),
+                newdir.path.as_raw_fd(),
+                newname.as_ptr(),
+            )
+        })?;
+
+        Ok(())
+    }
+
+    fn unlink_at(&mut self, unlink_at: Tunlinkat) -> io::Result<()> {
+        let dir = self.fids.get(&unlink_at.dirfd).ok_or_else(ebadf)?;
+        let name = string_to_cstring(unlink_at.name)?;
+
+        syscall!(unsafe {
+            libc::unlinkat(
+                dir.path.as_raw_fd(),
+                name.as_ptr(),
+                unlink_at.flags as libc::c_int,
+            )
+        })?;
+
+        Ok(())
+    }
+}
+
+#[cfg(test)]
+mod tests;
diff --git a/common/p9/src/server/tests.rs b/common/p9/src/server/tests.rs
new file mode 100644
index 0000000..a273429
--- /dev/null
+++ b/common/p9/src/server/tests.rs
@@ -0,0 +1,1099 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use super::*;
+
+use std::borrow::Cow;
+use std::collections::{HashSet, VecDeque};
+use std::env;
+use std::ffi::{CString, OsString};
+use std::fs::{self, File};
+use std::io::{self, Cursor};
+use std::mem;
+use std::ops::Deref;
+use std::os::unix::ffi::OsStringExt;
+use std::os::unix::fs::MetadataExt;
+use std::path::{Component, Path, PathBuf};
+use std::u32;
+
+// Used to indicate that there is no fid associated with this message.
+const P9_NOFID: u32 = u32::MAX;
+
+// The fid associated with the root directory of the server.
+const ROOT_FID: u32 = 1;
+
+// How big we want the default buffer to be when running tests.
+const DEFAULT_BUFFER_SIZE: u32 = 4096;
+
+// Joins `path` to `buf`.  If `path` is '..', removes the last component from `buf`
+// only if `buf` != `root` but does nothing if `buf` == `root`.  Pushes `path` onto
+// `buf` if it is a normal path component.
+//
+// Returns an error if `path` is absolute, has more than one component, or contains
+// a '.' component.
+fn join_path<P: AsRef<Path>, R: AsRef<Path>>(
+    mut buf: PathBuf,
+    path: P,
+    root: R,
+) -> io::Result<PathBuf> {
+    let path = path.as_ref();
+    let root = root.as_ref();
+    debug_assert!(buf.starts_with(root));
+
+    if path.components().count() > 1 {
+        return Err(io::Error::from_raw_os_error(libc::EINVAL));
+    }
+
+    for component in path.components() {
+        match component {
+            // Prefix should only appear on windows systems.
+            Component::Prefix(_) => return Err(io::Error::from_raw_os_error(libc::EINVAL)),
+            // Absolute paths are not allowed.
+            Component::RootDir => return Err(io::Error::from_raw_os_error(libc::EINVAL)),
+            // '.' elements are not allowed.
+            Component::CurDir => return Err(io::Error::from_raw_os_error(libc::EINVAL)),
+            Component::ParentDir => {
+                // We only remove the parent path if we are not already at the root of the
+                // file system.
+                if buf != root {
+                    buf.pop();
+                }
+            }
+            Component::Normal(element) => buf.push(element),
+        }
+    }
+
+    Ok(buf)
+}
+
+// Automatically deletes the path it contains when it goes out of scope.
+struct ScopedPath<P: AsRef<Path>>(P);
+
+impl<P: AsRef<Path>> AsRef<Path> for ScopedPath<P> {
+    fn as_ref(&self) -> &Path {
+        self.0.as_ref()
+    }
+}
+
+impl<P: AsRef<Path>> Deref for ScopedPath<P> {
+    type Target = Path;
+
+    fn deref(&self) -> &Self::Target {
+        self.0.as_ref()
+    }
+}
+
+impl<P: AsRef<Path>> Drop for ScopedPath<P> {
+    fn drop(&mut self) {
+        if let Err(e) = fs::remove_dir_all(&**self) {
+            println!("Failed to remove {}: {}", self.display(), e);
+        }
+    }
+}
+
+enum DirEntry<'a> {
+    File {
+        name: &'a str,
+        content: &'a [u8],
+    },
+    Directory {
+        name: &'a str,
+        entries: &'a [DirEntry<'a>],
+    },
+}
+
+impl<'a> DirEntry<'a> {
+    // Creates `self` in the path given by `dir`.
+    fn create(&self, dir: &mut Cow<Path>) {
+        match *self {
+            DirEntry::File { name, content } => {
+                let mut f = File::create(dir.join(name)).expect("failed to create file");
+                f.write_all(content).expect("failed to write file content");
+            }
+            DirEntry::Directory { name, entries } => {
+                dir.to_mut().push(name);
+
+                fs::create_dir_all(&**dir).expect("failed to create directory");
+                for e in entries {
+                    e.create(dir);
+                }
+
+                assert!(dir.to_mut().pop());
+            }
+        }
+    }
+}
+
+// Creates a file with `name` in `dir` and fills it with random
+// content.
+fn create_local_file<P: AsRef<Path>>(dir: P, name: &str) -> Vec<u8> {
+    let mut content = Vec::new();
+    File::open("/dev/urandom")
+        .and_then(|f| f.take(200).read_to_end(&mut content))
+        .expect("failed to read from /dev/urandom");
+
+    let f = DirEntry::File {
+        name,
+        content: &content,
+    };
+    f.create(&mut Cow::from(dir.as_ref()));
+
+    content
+}
+
+fn check_qid(qid: &Qid, md: &fs::Metadata) {
+    let ty = if md.is_dir() {
+        P9_QTDIR
+    } else if md.is_file() {
+        P9_QTFILE
+    } else if md.file_type().is_symlink() {
+        P9_QTSYMLINK
+    } else {
+        panic!("unknown file type: {:?}", md.file_type());
+    };
+    assert_eq!(qid.ty, ty);
+    assert_eq!(qid.version, md.mtime() as u32);
+    assert_eq!(qid.path, md.ino());
+}
+
+fn check_attr(server: &mut Server, fid: u32, md: &fs::Metadata) {
+    let tgetattr = Tgetattr {
+        fid,
+        request_mask: P9_GETATTR_BASIC,
+    };
+
+    let rgetattr = server.get_attr(&tgetattr).expect("failed to call get_attr");
+
+    let ty = if md.is_dir() {
+        P9_QTDIR
+    } else if md.is_file() {
+        P9_QTFILE
+    } else if md.file_type().is_symlink() {
+        P9_QTSYMLINK
+    } else {
+        panic!("unknown file type: {:?}", md.file_type());
+    };
+    assert_eq!(rgetattr.valid, P9_GETATTR_BASIC);
+    assert_eq!(rgetattr.qid.ty, ty);
+    assert_eq!(rgetattr.qid.version, md.mtime() as u32);
+    assert_eq!(rgetattr.qid.path, md.ino());
+    assert_eq!(rgetattr.mode, md.mode());
+    assert_eq!(rgetattr.uid, md.uid());
+    assert_eq!(rgetattr.gid, md.gid());
+    assert_eq!(rgetattr.nlink, md.nlink());
+    assert_eq!(rgetattr.rdev, md.rdev());
+    assert_eq!(rgetattr.size, md.size());
+    assert_eq!(rgetattr.atime_sec, md.atime() as u64);
+    assert_eq!(rgetattr.atime_nsec, md.atime_nsec() as u64);
+    assert_eq!(rgetattr.mtime_sec, md.mtime() as u64);
+    assert_eq!(rgetattr.mtime_nsec, md.mtime_nsec() as u64);
+    assert_eq!(rgetattr.ctime_sec, md.ctime() as u64);
+    assert_eq!(rgetattr.ctime_nsec, md.ctime_nsec() as u64);
+    assert_eq!(rgetattr.btime_sec, 0);
+    assert_eq!(rgetattr.btime_nsec, 0);
+    assert_eq!(rgetattr.gen, 0);
+    assert_eq!(rgetattr.data_version, 0);
+}
+
+fn check_content(server: &mut Server, content: &[u8], fid: u32) {
+    for offset in 0..content.len() {
+        let tread = Tread {
+            fid,
+            offset: offset as u64,
+            count: DEFAULT_BUFFER_SIZE,
+        };
+
+        let rread = server.read(&tread).expect("failed to read file");
+        assert_eq!(content[offset..], rread.data[..]);
+    }
+}
+
+fn walk<P: Into<PathBuf>>(
+    server: &mut Server,
+    start: P,
+    fid: u32,
+    newfid: u32,
+    names: Vec<String>,
+) {
+    let mut mds = Vec::with_capacity(names.len());
+    let mut buf = start.into();
+    for name in &names {
+        buf.push(name);
+        mds.push(
+            buf.symlink_metadata()
+                .expect("failed to get metadata for path"),
+        );
+    }
+
+    let twalk = Twalk {
+        fid,
+        newfid,
+        wnames: names,
+    };
+
+    let rwalk = server.walk(twalk).expect("failed to walk directoy");
+    assert_eq!(mds.len(), rwalk.wqids.len());
+    for (md, qid) in mds.iter().zip(rwalk.wqids.iter()) {
+        check_qid(qid, md);
+    }
+}
+
+fn open<P: Into<PathBuf>>(
+    server: &mut Server,
+    dir: P,
+    dir_fid: u32,
+    name: &str,
+    fid: u32,
+    flags: u32,
+) -> io::Result<Rlopen> {
+    let wnames = if name.is_empty() {
+        vec![]
+    } else {
+        vec![String::from(name)]
+    };
+    walk(server, dir, dir_fid, fid, wnames);
+
+    let tlopen = Tlopen { fid, flags };
+
+    server.lopen(&tlopen)
+}
+
+fn write<P: AsRef<Path>>(server: &mut Server, dir: P, name: &str, fid: u32, flags: u32) {
+    let file_path = dir.as_ref().join(name);
+    let file_len = if file_path.exists() {
+        fs::symlink_metadata(&file_path)
+            .expect("unable to get metadata for file")
+            .len() as usize
+    } else {
+        0usize
+    };
+    let mut new_content = Vec::new();
+    File::open("/dev/urandom")
+        .and_then(|f| f.take(200).read_to_end(&mut new_content))
+        .expect("failed to read from /dev/urandom");
+
+    let twrite = Twrite {
+        fid,
+        offset: 0,
+        data: Data(new_content),
+    };
+
+    let rwrite = server.write(&twrite).expect("failed to write file");
+    assert_eq!(rwrite.count, twrite.data.len() as u32);
+
+    let tfsync = Tfsync { fid, datasync: 0 };
+    server.fsync(&tfsync).expect("failed to sync file contents");
+
+    let actual_content = fs::read(file_path).expect("failed to read back content from file");
+
+    // If the file was opened append-only, then the content should have been
+    // written to the end even though the offset was 0.
+    let idx = if flags & P9_APPEND == 0 { 0 } else { file_len };
+    assert_eq!(actual_content[idx..], twrite.data[..]);
+}
+
+fn create<P: Into<PathBuf>>(
+    server: &mut Server,
+    dir: P,
+    dir_fid: u32,
+    fid: u32,
+    name: &str,
+    flags: u32,
+    mode: u32,
+) -> io::Result<Rlcreate> {
+    // The `fid` in the lcreate call initially points to the directory
+    // but is supposed to point to the newly created file after the call
+    // completes.  Duplicate the fid so that we don't end up consuming the
+    // directory fid.
+    walk(server, dir, dir_fid, fid, Vec::new());
+
+    let tlcreate = Tlcreate {
+        fid,
+        name: String::from(name),
+        flags,
+        mode,
+        gid: 0,
+    };
+
+    server.lcreate(tlcreate)
+}
+
+struct Readdir<'a> {
+    server: &'a mut Server,
+    fid: u32,
+    offset: u64,
+    cursor: Cursor<Vec<u8>>,
+}
+
+impl<'a> Iterator for Readdir<'a> {
+    type Item = Dirent;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        if self.cursor.position() >= self.cursor.get_ref().len() as u64 {
+            let treaddir = Treaddir {
+                fid: self.fid,
+                offset: self.offset,
+                count: DEFAULT_BUFFER_SIZE,
+            };
+
+            let Rreaddir { data } = self
+                .server
+                .readdir(&treaddir)
+                .expect("failed to read directory");
+            if data.is_empty() {
+                // No more entries.
+                return None;
+            }
+
+            mem::drop(mem::replace(&mut self.cursor, Cursor::new(data.0)));
+        }
+
+        let dirent: Dirent = WireFormat::decode(&mut self.cursor).expect("failed to decode dirent");
+        self.offset = dirent.offset;
+
+        Some(dirent)
+    }
+}
+
+fn readdir(server: &mut Server, fid: u32) -> Readdir {
+    Readdir {
+        server,
+        fid,
+        offset: 0,
+        cursor: Cursor::new(Vec::new()),
+    }
+}
+
+// Sets up the server to start handling messages.  Creates a new temporary
+// directory to act as the server root and sends an initial Tattach message.
+// At the end of setup, fid 1 points to the root of the server.
+fn setup<P: AsRef<Path>>(name: P) -> (ScopedPath<OsString>, Server) {
+    let mut test_dir = env::var_os("T")
+        .map(PathBuf::from)
+        .unwrap_or_else(env::temp_dir);
+    test_dir.push(name);
+
+    let mut os_str = OsString::from(test_dir);
+    os_str.push(".XXXXXX");
+
+    // Create a c string and release ownership.  This seems like the only way
+    // to get a *mut c_char.
+    let buf = CString::new(os_str.into_vec())
+        .expect("failed to create CString")
+        .into_raw();
+
+    // Safe because this will only modify the contents of `buf`.
+    let ret = unsafe { libc::mkdtemp(buf) };
+
+    // Take ownership of the buffer back before checking the result.  Safe because
+    // this was created by a call to into_raw() above and mkdtemp will not overwrite
+    // the trailing '\0'.
+    let buf = unsafe { CString::from_raw(buf) };
+
+    assert!(!ret.is_null());
+
+    let test_dir = ScopedPath(OsString::from_vec(buf.into_bytes()));
+
+    // Create a basic file system hierarchy.
+    let entries = [
+        DirEntry::Directory {
+            name: "subdir",
+            entries: &[
+                DirEntry::File {
+                    name: "b",
+                    content: b"hello, world!",
+                },
+                DirEntry::Directory {
+                    name: "nested",
+                    entries: &[DirEntry::File {
+                        name: "Огонь по готовности!",
+                        content: &[
+                            0xe9u8, 0xbeu8, 0x8du8, 0xe3u8, 0x81u8, 0x8cu8, 0xe6u8, 0x88u8, 0x91u8,
+                            0xe3u8, 0x81u8, 0x8cu8, 0xe6u8, 0x95u8, 0xb5u8, 0xe3u8, 0x82u8, 0x92u8,
+                            0xe5u8, 0x96u8, 0xb0u8, 0xe3u8, 0x82u8, 0x89u8, 0xe3u8, 0x81u8, 0x86u8,
+                            0x21u8,
+                        ],
+                    }],
+                },
+            ],
+        },
+        DirEntry::File {
+            name: "世界.txt",
+            content: &[
+                0xe3u8, 0x81u8, 0x93u8, 0xe3u8, 0x82u8, 0x93u8, 0xe3u8, 0x81u8, 0xabu8, 0xe3u8,
+                0x81u8, 0xa1u8, 0xe3u8, 0x81u8, 0xafu8,
+            ],
+        },
+    ];
+
+    for e in &entries {
+        e.create(&mut Cow::from(&*test_dir));
+    }
+
+    let md = test_dir
+        .symlink_metadata()
+        .expect("failed to get metadata for root dir");
+
+    let mut server = Server::new(&*test_dir, Default::default(), Default::default())
+        .expect("Failed to create server");
+
+    let tversion = Tversion {
+        msize: DEFAULT_BUFFER_SIZE,
+        version: String::from("9P2000.L"),
+    };
+
+    let rversion = server
+        .version(&tversion)
+        .expect("failed to get version from server");
+    assert_eq!(rversion.msize, DEFAULT_BUFFER_SIZE);
+    assert_eq!(rversion.version, "9P2000.L");
+
+    let tattach = Tattach {
+        fid: ROOT_FID,
+        afid: P9_NOFID,
+        uname: String::from("unittest"),
+        aname: String::from(""),
+        n_uname: 1000,
+    };
+
+    let rattach = server.attach(&tattach).expect("failed to attach to server");
+    check_qid(&rattach.qid, &md);
+
+    (test_dir, server)
+}
+
+#[test]
+fn path_joins() {
+    let root = PathBuf::from("/a/b/c");
+    let path = PathBuf::from("/a/b/c/d/e/f");
+
+    assert_eq!(
+        &join_path(path.clone(), "nested", &root).expect("normal"),
+        Path::new("/a/b/c/d/e/f/nested")
+    );
+
+    let p1 = join_path(path, "..", &root).expect("parent 1");
+    assert_eq!(&p1, Path::new("/a/b/c/d/e/"));
+
+    let p2 = join_path(p1, "..", &root).expect("parent 2");
+    assert_eq!(&p2, Path::new("/a/b/c/d/"));
+
+    let p3 = join_path(p2, "..", &root).expect("parent 3");
+    assert_eq!(&p3, Path::new("/a/b/c/"));
+
+    let p4 = join_path(p3, "..", &root).expect("parent of root");
+    assert_eq!(&p4, Path::new("/a/b/c/"));
+}
+
+#[test]
+fn invalid_joins() {
+    let root = PathBuf::from("/a");
+    let path = PathBuf::from("/a/b");
+
+    join_path(path.clone(), ".", &root).expect_err("current directory");
+    join_path(path.clone(), "c/d/e", &root).expect_err("too many components");
+    join_path(path, "/c/d/e", &root).expect_err("absolute path");
+}
+
+#[test]
+fn clunk() {
+    let (_test_dir, mut server) = setup("clunk");
+
+    let tclunk = Tclunk { fid: ROOT_FID };
+    server.clunk(&tclunk).expect("failed to clunk root fid");
+}
+
+#[test]
+fn get_attr() {
+    let (test_dir, mut server) = setup("get_attr");
+
+    let md = test_dir
+        .symlink_metadata()
+        .expect("failed to get metadata for test dir");
+
+    check_attr(&mut server, ROOT_FID, &md);
+}
+
+#[test]
+fn tree_walk() {
+    let (test_dir, mut server) = setup("readdir");
+
+    let mut next_fid = ROOT_FID + 1;
+
+    let mut dirs = VecDeque::new();
+    dirs.push_back(test_dir.to_path_buf());
+
+    while let Some(dir) = dirs.pop_front() {
+        let dfid = next_fid;
+        next_fid += 1;
+
+        let wnames: Vec<String> = dir
+            .strip_prefix(&test_dir)
+            .expect("test directory is not prefix of subdir")
+            .components()
+            .map(|c| Path::new(&c).to_string_lossy().to_string())
+            .collect();
+        walk(&mut server, &*test_dir, ROOT_FID, dfid, wnames);
+
+        let md = dir.symlink_metadata().expect("failed to get metadata");
+
+        check_attr(&mut server, dfid, &md);
+
+        let fid = next_fid;
+        next_fid += 1;
+        open(&mut server, &dir, dfid, "", fid, P9_DIRECTORY).expect("Failed to open directory");
+        for dirent in readdir(&mut server, fid) {
+            if dirent.name == "." || dirent.name == ".." {
+                continue;
+            }
+
+            let entry_path = dir.join(&dirent.name);
+            assert!(
+                entry_path.exists(),
+                "directory entry \"{}\" does not exist",
+                entry_path.display()
+            );
+            let md = fs::symlink_metadata(&entry_path).expect("failed to get metadata for entry");
+
+            let ty = if md.is_dir() {
+                dirs.push_back(dir.join(dirent.name));
+                libc::DT_DIR
+            } else if md.is_file() {
+                libc::DT_REG
+            } else if md.file_type().is_symlink() {
+                libc::DT_LNK
+            } else {
+                panic!("unknown file type: {:?}", md.file_type());
+            };
+
+            assert_eq!(dirent.ty, ty);
+            check_qid(&dirent.qid, &md);
+        }
+
+        let tclunk = Tclunk { fid };
+        server.clunk(&tclunk).expect("failed to clunk fid");
+    }
+}
+
+#[test]
+fn create_existing_file() {
+    let (test_dir, mut server) = setup("create_existing");
+
+    let name = "existing";
+    create_local_file(&test_dir, name);
+
+    let fid = ROOT_FID + 1;
+    create(
+        &mut server,
+        &*test_dir,
+        ROOT_FID,
+        fid,
+        name,
+        P9_APPEND,
+        0o644,
+    )
+    .expect_err("successfully created existing file");
+}
+
+enum SetAttrKind {
+    File,
+    Directory,
+}
+
+fn set_attr_test<F>(kind: SetAttrKind, set_fields: F) -> io::Result<fs::Metadata>
+where
+    F: FnOnce(&mut Tsetattr),
+{
+    let (test_dir, mut server) = setup("set_attr");
+
+    let name = "existing";
+    match kind {
+        SetAttrKind::File => {
+            create_local_file(&test_dir, name);
+        }
+        SetAttrKind::Directory => {
+            let tmkdir = Tmkdir {
+                dfid: ROOT_FID,
+                name: String::from(name),
+                mode: 0o755,
+                gid: 0,
+            };
+
+            let rmkdir = server.mkdir(tmkdir).expect("failed to create directory");
+            let md = fs::symlink_metadata(test_dir.join(name))
+                .expect("failed to get metadata for directory");
+
+            assert!(md.is_dir());
+            check_qid(&rmkdir.qid, &md);
+        }
+    };
+
+    let fid = ROOT_FID + 1;
+    walk(
+        &mut server,
+        &*test_dir,
+        ROOT_FID,
+        fid,
+        vec![String::from(name)],
+    );
+
+    let mut tsetattr = Tsetattr {
+        fid,
+        valid: 0,
+        mode: 0,
+        uid: 0,
+        gid: 0,
+        size: 0,
+        atime_sec: 0,
+        atime_nsec: 0,
+        mtime_sec: 0,
+        mtime_nsec: 0,
+    };
+
+    set_fields(&mut tsetattr);
+    server.set_attr(&tsetattr)?;
+
+    fs::symlink_metadata(test_dir.join(name))
+}
+
+#[test]
+fn set_len() {
+    let len = 661;
+    let md = set_attr_test(SetAttrKind::File, |tsetattr| {
+        tsetattr.valid = P9_SETATTR_SIZE;
+        tsetattr.size = len;
+    })
+    .expect("failed to run set length of file");
+
+    assert_eq!(md.size(), len);
+}
+
+#[test]
+fn set_file_mode() {
+    let mode = 0o640;
+    let md = set_attr_test(SetAttrKind::File, |tsetattr| {
+        tsetattr.valid = P9_SETATTR_MODE;
+        tsetattr.mode = mode;
+    })
+    .expect("failed to set mode");
+
+    assert_eq!(md.mode() & 0o777, mode);
+}
+
+#[test]
+fn set_file_mtime() {
+    let (secs, nanos) = (1245247825, 524617);
+    let md = set_attr_test(SetAttrKind::File, |tsetattr| {
+        tsetattr.valid = P9_SETATTR_MTIME | P9_SETATTR_MTIME_SET;
+        tsetattr.mtime_sec = secs;
+        tsetattr.mtime_nsec = nanos;
+    })
+    .expect("failed to set mtime");
+
+    assert_eq!(md.mtime() as u64, secs);
+    assert_eq!(md.mtime_nsec() as u64, nanos);
+}
+
+#[test]
+fn set_file_atime() {
+    let (secs, nanos) = (9247605, 4016);
+    let md = set_attr_test(SetAttrKind::File, |tsetattr| {
+        tsetattr.valid = P9_SETATTR_ATIME | P9_SETATTR_ATIME_SET;
+        tsetattr.atime_sec = secs;
+        tsetattr.atime_nsec = nanos;
+    })
+    .expect("failed to set atime");
+
+    assert_eq!(md.atime() as u64, secs);
+    assert_eq!(md.atime_nsec() as u64, nanos);
+}
+
+#[test]
+fn set_dir_mode() {
+    let mode = 0o640;
+    let md = set_attr_test(SetAttrKind::Directory, |tsetattr| {
+        tsetattr.valid = P9_SETATTR_MODE;
+        tsetattr.mode = mode;
+    })
+    .expect("failed to set mode");
+
+    assert_eq!(md.mode() & 0o777, mode);
+}
+
+#[test]
+fn set_dir_mtime() {
+    let (secs, nanos) = (1245247825, 524617);
+    let md = set_attr_test(SetAttrKind::Directory, |tsetattr| {
+        tsetattr.valid = P9_SETATTR_MTIME | P9_SETATTR_MTIME_SET;
+        tsetattr.mtime_sec = secs;
+        tsetattr.mtime_nsec = nanos;
+    })
+    .expect("failed to set mtime");
+
+    assert_eq!(md.mtime() as u64, secs);
+    assert_eq!(md.mtime_nsec() as u64, nanos);
+}
+
+#[test]
+fn set_dir_atime() {
+    let (secs, nanos) = (9247605, 4016);
+    let md = set_attr_test(SetAttrKind::Directory, |tsetattr| {
+        tsetattr.valid = P9_SETATTR_ATIME | P9_SETATTR_ATIME_SET;
+        tsetattr.atime_sec = secs;
+        tsetattr.atime_nsec = nanos;
+    })
+    .expect("failed to set atime");
+
+    assert_eq!(md.atime() as u64, secs);
+    assert_eq!(md.atime_nsec() as u64, nanos);
+}
+
+#[test]
+fn huge_directory() {
+    let (test_dir, mut server) = setup("huge_directory");
+
+    let name = "newdir";
+    let newdir = test_dir.join(name);
+    fs::create_dir(&newdir).expect("failed to create directory");
+
+    let dfid = ROOT_FID + 1;
+    walk(
+        &mut server,
+        &*test_dir,
+        ROOT_FID,
+        dfid,
+        vec![String::from(name)],
+    );
+
+    // Create ~4K files in the directory and then attempt to read them all.
+    let mut filenames = HashSet::with_capacity(4096);
+    for i in 0..4096 {
+        let name = format!("file_{}", i);
+        create_local_file(&newdir, &name);
+        assert!(filenames.insert(name));
+    }
+
+    let fid = dfid + 1;
+    open(&mut server, &newdir, dfid, "", fid, P9_DIRECTORY).expect("Failed to open directory");
+    for f in readdir(&mut server, fid) {
+        let path = newdir.join(&f.name);
+
+        let md = fs::symlink_metadata(path).expect("failed to get metadata for path");
+        check_qid(&f.qid, &md);
+
+        if f.name == "." || f.name == ".." {
+            assert_eq!(f.ty, libc::DT_DIR);
+        } else {
+            assert_eq!(f.ty, libc::DT_REG);
+            assert!(filenames.remove(&f.name));
+        }
+    }
+
+    assert!(filenames.is_empty());
+}
+
+#[test]
+fn mkdir() {
+    let (test_dir, mut server) = setup("mkdir");
+
+    let name = "conan";
+    let tmkdir = Tmkdir {
+        dfid: ROOT_FID,
+        name: String::from(name),
+        mode: 0o755,
+        gid: 0,
+    };
+
+    let rmkdir = server.mkdir(tmkdir).expect("failed to create directory");
+    let md =
+        fs::symlink_metadata(test_dir.join(name)).expect("failed to get metadata for directory");
+
+    assert!(md.is_dir());
+    check_qid(&rmkdir.qid, &md);
+}
+
+#[test]
+fn unlink_all() {
+    let (test_dir, mut server) = setup("readdir");
+
+    let mut next_fid = ROOT_FID + 1;
+
+    let mut dirs = VecDeque::new();
+    dirs.push_back((ROOT_FID, test_dir.to_path_buf()));
+
+    // First iterate over the whole directory.
+    let mut unlinks = VecDeque::new();
+    while let Some((dfid, dir)) = dirs.pop_front() {
+        let mut names = VecDeque::new();
+        for entry in fs::read_dir(dir).expect("failed to read directory") {
+            let entry = entry.expect("unable to iterate over directory");
+            let ft = entry
+                .file_type()
+                .expect("failed to get file type for entry");
+            if ft.is_dir() {
+                let fid = next_fid;
+                next_fid += 1;
+
+                let wnames: Vec<String> = entry
+                    .path()
+                    .strip_prefix(&test_dir)
+                    .expect("test directory is not prefix of subdir")
+                    .components()
+                    .map(|c| Path::new(&c).to_string_lossy().to_string())
+                    .collect();
+                walk(&mut server, &*test_dir, ROOT_FID, fid, wnames);
+                dirs.push_back((fid, entry.path()));
+            }
+
+            names.push_back((
+                entry
+                    .file_name()
+                    .into_string()
+                    .expect("failed to convert entry name to string"),
+                if ft.is_dir() {
+                    libc::AT_REMOVEDIR as u32
+                } else {
+                    0
+                },
+            ));
+        }
+
+        unlinks.push_back((dfid, names));
+    }
+
+    // Now remove everything in reverse order.
+    while let Some((dfid, names)) = unlinks.pop_back() {
+        for (name, flags) in names {
+            let tunlinkat = Tunlinkat {
+                dirfd: dfid,
+                name,
+                flags,
+            };
+
+            server.unlink_at(tunlinkat).expect("failed to unlink path");
+        }
+    }
+}
+
+#[test]
+fn rename_at() {
+    let (test_dir, mut server) = setup("rename");
+
+    let name = "oldfile";
+    let content = create_local_file(&test_dir, name);
+
+    let newname = "newfile";
+    let trename = Trenameat {
+        olddirfid: ROOT_FID,
+        oldname: String::from(name),
+        newdirfid: ROOT_FID,
+        newname: String::from(newname),
+    };
+
+    server.rename_at(trename).expect("failed to rename file");
+
+    assert!(!test_dir.join(name).exists());
+
+    let mut newcontent = Vec::with_capacity(content.len());
+    let size = File::open(test_dir.join(newname))
+        .expect("failed to open file")
+        .read_to_end(&mut newcontent)
+        .expect("failed to read new file content");
+    assert_eq!(size, content.len());
+    assert_eq!(newcontent, content);
+}
+
+macro_rules! open_test {
+    ($name:ident, $flags:expr) => {
+        #[test]
+        fn $name() {
+            let (test_dir, mut server) = setup("open");
+
+            let fid = ROOT_FID + 1;
+            let name = "test.txt";
+            let content = create_local_file(&test_dir, name);
+
+            let rlopen = open(&mut server, &*test_dir, ROOT_FID, name, fid, $flags as u32)
+                .expect("failed to open file");
+
+            let md =
+                fs::symlink_metadata(test_dir.join(name)).expect("failed to get metadata for file");
+            check_qid(&rlopen.qid, &md);
+            assert_eq!(rlopen.iounit, md.blksize() as u32);
+
+            check_attr(&mut server, fid, &md);
+
+            // Check that the file has the proper contents as long as we didn't
+            // truncate it first.
+            if $flags & P9_TRUNC == 0 && $flags & P9_WRONLY == 0 {
+                check_content(&mut server, &content, fid);
+            }
+
+            // Check that we can write to the file.
+            if $flags & P9_RDWR != 0 || $flags & P9_WRONLY != 0 {
+                write(&mut server, &test_dir, name, fid, $flags);
+            }
+
+            let tclunk = Tclunk { fid };
+            server.clunk(&tclunk).expect("Unable to clunk file");
+        }
+    };
+    ($name:ident, $flags:expr, $expected_err:expr) => {
+        #[test]
+        fn $name() {
+            let (test_dir, mut server) = setup("open_fail");
+
+            let fid = ROOT_FID + 1;
+            let name = "test.txt";
+            create_local_file(&test_dir, name);
+
+            let err = open(&mut server, &*test_dir, ROOT_FID, name, fid, $flags as u32)
+                .expect_err("successfully opened file");
+            assert_eq!(err.kind(), $expected_err);
+
+            let tclunk = Tclunk { fid };
+            server.clunk(&tclunk).expect("Unable to clunk file");
+        }
+    };
+}
+
+open_test!(read_only_file_open, P9_RDONLY);
+open_test!(read_write_file_open, P9_RDWR);
+open_test!(write_only_file_open, P9_WRONLY);
+
+open_test!(create_read_only_file_open, P9_CREATE | P9_RDONLY);
+open_test!(create_read_write_file_open, P9_CREATE | P9_RDWR);
+open_test!(create_write_only_file_open, P9_CREATE | P9_WRONLY);
+
+open_test!(append_read_only_file_open, P9_APPEND | P9_RDONLY);
+open_test!(append_read_write_file_open, P9_APPEND | P9_RDWR);
+open_test!(append_write_only_file_open, P9_APPEND | P9_WRONLY);
+
+open_test!(trunc_read_only_file_open, P9_TRUNC | P9_RDONLY);
+open_test!(trunc_read_write_file_open, P9_TRUNC | P9_RDWR);
+open_test!(trunc_write_only_file_open, P9_TRUNC | P9_WRONLY);
+
+open_test!(
+    create_append_read_only_file_open,
+    P9_CREATE | P9_APPEND | P9_RDONLY
+);
+open_test!(
+    create_append_read_write_file_open,
+    P9_CREATE | P9_APPEND | P9_RDWR
+);
+open_test!(
+    create_append_wronly_file_open,
+    P9_CREATE | P9_APPEND | P9_WRONLY
+);
+
+open_test!(
+    create_trunc_read_only_file_open,
+    P9_CREATE | P9_TRUNC | P9_RDONLY
+);
+open_test!(
+    create_trunc_read_write_file_open,
+    P9_CREATE | P9_TRUNC | P9_RDWR
+);
+open_test!(
+    create_trunc_wronly_file_open,
+    P9_CREATE | P9_TRUNC | P9_WRONLY
+);
+
+open_test!(
+    append_trunc_read_only_file_open,
+    P9_APPEND | P9_TRUNC | P9_RDONLY
+);
+open_test!(
+    append_trunc_read_write_file_open,
+    P9_APPEND | P9_TRUNC | P9_RDWR
+);
+open_test!(
+    append_trunc_wronly_file_open,
+    P9_APPEND | P9_TRUNC | P9_WRONLY
+);
+
+open_test!(
+    create_append_trunc_read_only_file_open,
+    P9_CREATE | P9_APPEND | P9_TRUNC | P9_RDONLY
+);
+open_test!(
+    create_append_trunc_read_write_file_open,
+    P9_CREATE | P9_APPEND | P9_TRUNC | P9_RDWR
+);
+open_test!(
+    create_append_trunc_wronly_file_open,
+    P9_CREATE | P9_APPEND | P9_TRUNC | P9_WRONLY
+);
+
+open_test!(
+    create_excl_read_only_file_open,
+    P9_CREATE | P9_EXCL | P9_RDONLY,
+    io::ErrorKind::AlreadyExists
+);
+open_test!(
+    create_excl_read_write_file_open,
+    P9_CREATE | P9_EXCL | P9_RDWR,
+    io::ErrorKind::AlreadyExists
+);
+open_test!(
+    create_excl_wronly_file_open,
+    P9_CREATE | P9_EXCL | P9_WRONLY,
+    io::ErrorKind::AlreadyExists
+);
+
+macro_rules! create_test {
+    ($name:ident, $flags:expr, $mode:expr) => {
+        #[test]
+        fn $name() {
+            let (test_dir, mut server) = setup("create");
+
+            let name = "foo.txt";
+            let fid = ROOT_FID + 1;
+            let rlcreate = create(&mut server, &*test_dir, ROOT_FID, fid, name, $flags, $mode)
+                .expect("failed to create file");
+
+            let md =
+                fs::symlink_metadata(test_dir.join(name)).expect("failed to get metadata for file");
+            assert_eq!(rlcreate.iounit, md.blksize() as u32);
+            check_qid(&rlcreate.qid, &md);
+            check_attr(&mut server, fid, &md);
+
+            // Check that we can write to the file.
+            if $flags & P9_RDWR != 0 || $flags & P9_WRONLY != 0 {
+                write(&mut server, &test_dir, name, fid, $flags);
+            }
+
+            let tclunk = Tclunk { fid };
+            server.clunk(&tclunk).expect("Unable to clunk file");
+        }
+    };
+    ($name:ident, $flags:expr, $mode:expr, $expected_err:expr) => {
+        #[test]
+        fn $name() {
+            let (test_dir, mut server) = setup("create_fail");
+
+            let name = "foo.txt";
+            // The `fid` in the lcreate call initially points to the directory
+            // but is supposed to point to the newly created file after the call
+            // completes.  Duplicate the fid so that we don't end up consuming the
+            // root fid.
+            let fid = ROOT_FID + 1;
+            let err = create(&mut server, &*test_dir, ROOT_FID, fid, name, $flags, $mode)
+                .expect_err("successfully created file");
+            assert_eq!(err.kind(), $expected_err);
+        }
+    };
+}
+
+create_test!(read_only_file_create, P9_RDONLY, 0o600u32);
+create_test!(read_write_file_create, P9_RDWR, 0o600u32);
+create_test!(write_only_file_create, P9_WRONLY, 0o600u32);
+
+create_test!(
+    append_read_only_file_create,
+    P9_APPEND | P9_RDONLY,
+    0o600u32
+);
+create_test!(append_read_write_file_create, P9_APPEND | P9_RDWR, 0o600u32);
+create_test!(append_wronly_file_create, P9_APPEND | P9_WRONLY, 0o600u32);
diff --git a/common/p9/wire_format_derive/Android.bp b/common/p9/wire_format_derive/Android.bp
new file mode 100644
index 0000000..1bae7f8
--- /dev/null
+++ b/common/p9/wire_format_derive/Android.bp
@@ -0,0 +1,26 @@
+// This file is generated by cargo2android.py --config cargo2android.json.
+// Do not modify this file as changes will be overridden on upgrade.
+
+
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "external_crosvm_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-BSD
+    default_applicable_licenses: ["external_crosvm_license"],
+}
+
+rust_proc_macro {
+    name: "libwire_format_derive",
+    defaults: ["crosvm_defaults"],
+    crate_name: "wire_format_derive",
+    srcs: ["wire_format_derive.rs"],
+    edition: "2015",
+    rustlibs: [
+        "libproc_macro2",
+        "libquote",
+        "libsyn",
+    ],
+}
diff --git a/common/p9/wire_format_derive/Cargo.toml b/common/p9/wire_format_derive/Cargo.toml
new file mode 100644
index 0000000..52ab6a4
--- /dev/null
+++ b/common/p9/wire_format_derive/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "wire_format_derive"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+
+[dependencies]
+syn = "^1"
+quote = "^1"
+proc-macro2 = "^1"
+
+[lib]
+proc-macro = true
+path = "wire_format_derive.rs"
diff --git a/common/p9/wire_format_derive/cargo2android.json b/common/p9/wire_format_derive/cargo2android.json
new file mode 100644
index 0000000..9a6eee3
--- /dev/null
+++ b/common/p9/wire_format_derive/cargo2android.json
@@ -0,0 +1,7 @@
+{
+  "add_workspace": true,
+  "device": true,
+  "global_defaults": "crosvm_defaults",
+  "run": true,
+  "tests": false
+}
\ No newline at end of file
diff --git a/common/p9/wire_format_derive/wire_format_derive.rs b/common/p9/wire_format_derive/wire_format_derive.rs
new file mode 100644
index 0000000..290ffc5
--- /dev/null
+++ b/common/p9/wire_format_derive/wire_format_derive.rs
@@ -0,0 +1,303 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Derives a 9P wire format encoding for a struct by recursively calling
+//! `WireFormat::encode` or `WireFormat::decode` on the fields of the struct.
+//! This is only intended to be used from within the `p9` crate.
+
+#![recursion_limit = "256"]
+
+extern crate proc_macro;
+extern crate proc_macro2;
+
+#[macro_use]
+extern crate quote;
+
+#[macro_use]
+extern crate syn;
+
+use proc_macro2::{Span, TokenStream};
+use syn::spanned::Spanned;
+use syn::{Data, DeriveInput, Fields, Ident};
+
+/// The function that derives the actual implementation.
+#[proc_macro_derive(P9WireFormat)]
+pub fn p9_wire_format(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+    let input = parse_macro_input!(input as DeriveInput);
+    p9_wire_format_inner(input).into()
+}
+
+fn p9_wire_format_inner(input: DeriveInput) -> TokenStream {
+    if !input.generics.params.is_empty() {
+        return quote! {
+            compile_error!("derive(P9WireFormat) does not support generic parameters");
+        };
+    }
+
+    let container = input.ident;
+
+    let byte_size_impl = byte_size_sum(&input.data);
+    let encode_impl = encode_wire_format(&input.data);
+    let decode_impl = decode_wire_format(&input.data, &container);
+
+    let scope = format!("wire_format_{}", container).to_lowercase();
+    let scope = Ident::new(&scope, Span::call_site());
+    quote! {
+        mod #scope {
+            extern crate std;
+            use self::std::io;
+            use self::std::result::Result::Ok;
+
+            use super::#container;
+
+            use protocol::WireFormat;
+
+            impl WireFormat for #container {
+                fn byte_size(&self) -> u32 {
+                    #byte_size_impl
+                }
+
+                fn encode<W: io::Write>(&self, _writer: &mut W) -> io::Result<()> {
+                    #encode_impl
+                }
+
+                fn decode<R: io::Read>(_reader: &mut R) -> io::Result<Self> {
+                    #decode_impl
+                }
+            }
+        }
+    }
+}
+
+// Generate code that recursively calls byte_size on every field in the struct.
+fn byte_size_sum(data: &Data) -> TokenStream {
+    if let Data::Struct(ref data) = *data {
+        if let Fields::Named(ref fields) = data.fields {
+            let fields = fields.named.iter().map(|f| {
+                let field = &f.ident;
+                let span = field.span();
+                quote_spanned! {span=>
+                    WireFormat::byte_size(&self.#field)
+                }
+            });
+
+            quote! {
+                0 #(+ #fields)*
+            }
+        } else {
+            unimplemented!();
+        }
+    } else {
+        unimplemented!();
+    }
+}
+
+// Generate code that recursively calls encode on every field in the struct.
+fn encode_wire_format(data: &Data) -> TokenStream {
+    if let Data::Struct(ref data) = *data {
+        if let Fields::Named(ref fields) = data.fields {
+            let fields = fields.named.iter().map(|f| {
+                let field = &f.ident;
+                let span = field.span();
+                quote_spanned! {span=>
+                    WireFormat::encode(&self.#field, _writer)?;
+                }
+            });
+
+            quote! {
+                #(#fields)*
+
+                Ok(())
+            }
+        } else {
+            unimplemented!();
+        }
+    } else {
+        unimplemented!();
+    }
+}
+
+// Generate code that recursively calls decode on every field in the struct.
+fn decode_wire_format(data: &Data, container: &Ident) -> TokenStream {
+    if let Data::Struct(ref data) = *data {
+        if let Fields::Named(ref fields) = data.fields {
+            let values = fields.named.iter().map(|f| {
+                let field = &f.ident;
+                let span = field.span();
+                quote_spanned! {span=>
+                    let #field = WireFormat::decode(_reader)?;
+                }
+            });
+
+            let members = fields.named.iter().map(|f| {
+                let field = &f.ident;
+                quote! {
+                    #field: #field,
+                }
+            });
+
+            quote! {
+                #(#values)*
+
+                Ok(#container {
+                    #(#members)*
+                })
+            }
+        } else {
+            unimplemented!();
+        }
+    } else {
+        unimplemented!();
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn byte_size() {
+        let input: DeriveInput = parse_quote! {
+            struct Item {
+                ident: u32,
+                with_underscores: String,
+                other: u8,
+            }
+        };
+
+        let expected = quote! {
+            0
+                + WireFormat::byte_size(&self.ident)
+                + WireFormat::byte_size(&self.with_underscores)
+                + WireFormat::byte_size(&self.other)
+        };
+
+        assert_eq!(byte_size_sum(&input.data).to_string(), expected.to_string());
+    }
+
+    #[test]
+    fn encode() {
+        let input: DeriveInput = parse_quote! {
+            struct Item {
+                ident: u32,
+                with_underscores: String,
+                other: u8,
+            }
+        };
+
+        let expected = quote! {
+            WireFormat::encode(&self.ident, _writer)?;
+            WireFormat::encode(&self.with_underscores, _writer)?;
+            WireFormat::encode(&self.other, _writer)?;
+            Ok(())
+        };
+
+        assert_eq!(
+            encode_wire_format(&input.data).to_string(),
+            expected.to_string(),
+        );
+    }
+
+    #[test]
+    fn decode() {
+        let input: DeriveInput = parse_quote! {
+            struct Item {
+                ident: u32,
+                with_underscores: String,
+                other: u8,
+            }
+        };
+
+        let container = Ident::new("Item", Span::call_site());
+        let expected = quote! {
+            let ident = WireFormat::decode(_reader)?;
+            let with_underscores = WireFormat::decode(_reader)?;
+            let other = WireFormat::decode(_reader)?;
+            Ok(Item {
+                ident: ident,
+                with_underscores: with_underscores,
+                other: other,
+            })
+        };
+
+        assert_eq!(
+            decode_wire_format(&input.data, &container).to_string(),
+            expected.to_string(),
+        );
+    }
+
+    #[test]
+    fn end_to_end() {
+        let input: DeriveInput = parse_quote! {
+            struct Niijima_先輩 {
+                a: u8,
+                b: u16,
+                c: u32,
+                d: u64,
+                e: String,
+                f: Vec<String>,
+                g: Nested,
+            }
+        };
+
+        let expected = quote! {
+            mod wire_format_niijima_先輩 {
+                extern crate std;
+                use self::std::io;
+                use self::std::result::Result::Ok;
+
+                use super::Niijima_先輩;
+
+                use protocol::WireFormat;
+
+                impl WireFormat for Niijima_先輩 {
+                    fn byte_size(&self) -> u32 {
+                        0
+                        + WireFormat::byte_size(&self.a)
+                        + WireFormat::byte_size(&self.b)
+                        + WireFormat::byte_size(&self.c)
+                        + WireFormat::byte_size(&self.d)
+                        + WireFormat::byte_size(&self.e)
+                        + WireFormat::byte_size(&self.f)
+                        + WireFormat::byte_size(&self.g)
+                    }
+
+                    fn encode<W: io::Write>(&self, _writer: &mut W) -> io::Result<()> {
+                        WireFormat::encode(&self.a, _writer)?;
+                        WireFormat::encode(&self.b, _writer)?;
+                        WireFormat::encode(&self.c, _writer)?;
+                        WireFormat::encode(&self.d, _writer)?;
+                        WireFormat::encode(&self.e, _writer)?;
+                        WireFormat::encode(&self.f, _writer)?;
+                        WireFormat::encode(&self.g, _writer)?;
+                        Ok(())
+                    }
+                    fn decode<R: io::Read>(_reader: &mut R) -> io::Result<Self> {
+                        let a = WireFormat::decode(_reader)?;
+                        let b = WireFormat::decode(_reader)?;
+                        let c = WireFormat::decode(_reader)?;
+                        let d = WireFormat::decode(_reader)?;
+                        let e = WireFormat::decode(_reader)?;
+                        let f = WireFormat::decode(_reader)?;
+                        let g = WireFormat::decode(_reader)?;
+                        Ok(Niijima_先輩 {
+                            a: a,
+                            b: b,
+                            c: c,
+                            d: d,
+                            e: e,
+                            f: f,
+                            g: g,
+                        })
+                    }
+                }
+            }
+        };
+
+        assert_eq!(
+            p9_wire_format_inner(input).to_string(),
+            expected.to_string(),
+        );
+    }
+}
diff --git a/cros_async/Android.bp b/cros_async/Android.bp
index a33b151..5c54336 100644
--- a/cros_async/Android.bp
+++ b/cros_async/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace --verbose.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
 // Do not modify this file as changes will be overridden on upgrade.
 
 package {
@@ -11,7 +11,7 @@
 }
 
 rust_defaults {
-    name: "cros_async_defaults",
+    name: "cros_async_test_defaults",
     defaults: ["crosvm_defaults"],
     crate_name: "cros_async",
     srcs: ["src/lib.rs"],
@@ -27,6 +27,7 @@
         "libintrusive_collections",
         "libio_uring",
         "liblibc",
+        "libonce_cell",
         "libpin_utils",
         "libslab",
         "libsync_rust",
@@ -42,7 +43,7 @@
 
 rust_test_host {
     name: "cros_async_host_test_src_lib",
-    defaults: ["cros_async_defaults"],
+    defaults: ["cros_async_test_defaults"],
     test_options: {
         unit_test: true,
     },
@@ -50,13 +51,12 @@
 
 rust_test {
     name: "cros_async_device_test_src_lib",
-    defaults: ["cros_async_defaults"],
+    defaults: ["cros_async_test_defaults"],
 }
 
 rust_library {
     name: "libcros_async",
     defaults: ["crosvm_defaults"],
-    // has rustc warnings
     host_supported: true,
     crate_name: "cros_async",
     srcs: ["src/lib.rs"],
@@ -68,6 +68,7 @@
         "libintrusive_collections",
         "libio_uring",
         "liblibc",
+        "libonce_cell",
         "libpin_utils",
         "libslab",
         "libsync_rust",
@@ -79,46 +80,3 @@
         "libpaste",
     ],
 }
-
-// dependent_library ["feature_list"]
-//   ../assertions/src/lib.rs
-//   ../data_model/src/lib.rs
-//   ../io_uring/src/lib.rs
-//   ../sync/src/lib.rs
-//   ../sys_util/poll_token_derive/poll_token_derive.rs
-//   ../sys_util/src/lib.rs
-//   ../tempfile/src/lib.rs
-//   async-task-4.0.3 "default,std"
-//   async-trait-0.1.50
-//   autocfg-1.0.1
-//   futures-0.3.15 "alloc,async-await,default,executor,futures-executor,std"
-//   futures-channel-0.3.15 "alloc,futures-sink,sink,std"
-//   futures-core-0.3.15 "alloc,std"
-//   futures-executor-0.3.15 "default,num_cpus,std,thread-pool"
-//   futures-io-0.3.15 "std"
-//   futures-macro-0.3.15
-//   futures-sink-0.3.15 "alloc,std"
-//   futures-task-0.3.15 "alloc,std"
-//   futures-util-0.3.15 "alloc,async-await,async-await-macro,channel,default,futures-channel,futures-io,futures-macro,futures-sink,io,memchr,proc-macro-hack,proc-macro-nested,sink,slab,std"
-//   intrusive-collections-0.9.1 "alloc,default"
-//   itoa-0.4.7
-//   libc-0.2.97 "default,std"
-//   memchr-2.4.0 "default,std"
-//   memoffset-0.5.6 "default"
-//   num_cpus-1.13.0
-//   paste-1.0.5
-//   pin-project-lite-0.2.7
-//   pin-utils-0.1.0
-//   proc-macro-hack-0.5.19
-//   proc-macro-nested-0.1.7
-//   proc-macro2-1.0.27 "default,proc-macro"
-//   quote-1.0.9 "default,proc-macro"
-//   ryu-1.0.5
-//   serde-1.0.126 "default,derive,serde_derive,std"
-//   serde_derive-1.0.126 "default"
-//   serde_json-1.0.64 "default,std"
-//   slab-0.4.3 "default,std"
-//   syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-//   thiserror-1.0.25
-//   thiserror-impl-1.0.25
-//   unicode-xid-0.2.2 "default"
diff --git a/cros_async/Cargo.toml b/cros_async/Cargo.toml
index 165a322..cd9417c 100644
--- a/cros_async/Cargo.toml
+++ b/cros_async/Cargo.toml
@@ -7,15 +7,16 @@
 [dependencies]
 async-trait = "0.1.36"
 async-task = "4"
+data_model = { path = "../data_model" } # provided by ebuild
 intrusive-collections = "0.9"
 io_uring = { path = "../io_uring" } # provided by ebuild
 libc = "*"
+once_cell = "1.7.2"
 paste = "1.0"
 pin-utils = "0.1.0-alpha.4"
 slab = "0.4"
 sync = { path = "../sync" } # provided by ebuild
 sys_util = { path = "../sys_util" } # provided by ebuild
-data_model = { path = "../data_model" } # provided by ebuild
 thiserror = "1.0.20"
 
 [dependencies.futures]
diff --git a/cros_async/src/blocking.rs b/cros_async/src/blocking.rs
index ae79d2b..f6430a7 100644
--- a/cros_async/src/blocking.rs
+++ b/cros_async/src/blocking.rs
@@ -3,5 +3,7 @@
 // found in the LICENSE file.
 
 mod block_on;
+mod pool;
 
 pub use block_on::*;
+pub use pool::*;
diff --git a/cros_async/src/blocking/pool.rs b/cros_async/src/blocking/pool.rs
new file mode 100644
index 0000000..2109fd7
--- /dev/null
+++ b/cros_async/src/blocking/pool.rs
@@ -0,0 +1,489 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+    collections::VecDeque,
+    mem,
+    sync::{
+        mpsc::{channel, Receiver, Sender},
+        Arc,
+    },
+    thread::{self, JoinHandle},
+    time::{Duration, Instant},
+};
+
+use async_task::{Runnable, Task};
+use slab::Slab;
+use sync::{Condvar, Mutex};
+use sys_util::{error, warn};
+
+const DEFAULT_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(10);
+
+struct State {
+    tasks: VecDeque<Runnable>,
+    num_threads: usize,
+    num_idle: usize,
+    worker_threads: Slab<JoinHandle<()>>,
+    exited_threads: Option<Receiver<usize>>,
+    exit: Sender<usize>,
+    shutting_down: bool,
+}
+
+fn run_blocking_thread(idx: usize, inner: Arc<Inner>, exit: Sender<usize>) {
+    let mut state = inner.state.lock();
+    while !state.shutting_down {
+        if let Some(runnable) = state.tasks.pop_front() {
+            drop(state);
+            runnable.run();
+            state = inner.state.lock();
+            continue;
+        }
+
+        // No more tasks so wait for more work.
+        state.num_idle += 1;
+
+        let (guard, result) = inner
+            .condvar
+            .wait_timeout_while(state, inner.keepalive, |s| {
+                !s.shutting_down && s.tasks.is_empty()
+            });
+        state = guard;
+
+        // Only decrement the idle count if we timed out. Otherwise, it was decremented when new
+        // work was added to `state.tasks`.
+        if result.timed_out() {
+            state.num_idle -= 1;
+            break;
+        }
+    }
+
+    state.num_threads -= 1;
+
+    // If we're shutting down then the BlockingPool will take care of joining all the threads.
+    // Otherwise, we need to join the last worker thread that exited here.
+    let last_exited_thread = if let Some(exited_threads) = state.exited_threads.as_mut() {
+        exited_threads
+            .try_recv()
+            .map(|idx| state.worker_threads.remove(idx))
+            .ok()
+    } else {
+        None
+    };
+
+    // Drop the lock before trying to join the last exited thread.
+    drop(state);
+
+    if let Some(handle) = last_exited_thread {
+        let _ = handle.join();
+    }
+
+    if let Err(e) = exit.send(idx) {
+        error!("Failed to send thread exit event on channel: {}", e);
+    }
+}
+
+struct Inner {
+    state: Mutex<State>,
+    condvar: Condvar,
+    max_threads: usize,
+    keepalive: Duration,
+}
+
+impl Inner {
+    fn schedule(self: &Arc<Inner>, runnable: Runnable) {
+        let mut state = self.state.lock();
+
+        // If we're shutting down then nothing is going to run this task.
+        if state.shutting_down {
+            return;
+        }
+
+        state.tasks.push_back(runnable);
+
+        if state.num_idle == 0 {
+            // There are no idle threads.  Spawn a new one if possible.
+            if state.num_threads < self.max_threads {
+                state.num_threads += 1;
+                let exit = state.exit.clone();
+                let entry = state.worker_threads.vacant_entry();
+                let idx = entry.key();
+                let inner = self.clone();
+                entry.insert(
+                    thread::Builder::new()
+                        .name(format!("blockingPool{}", idx))
+                        .spawn(move || run_blocking_thread(idx, inner, exit))
+                        .unwrap(),
+                );
+            }
+        } else {
+            // We have idle threads, wake one up.
+            state.num_idle -= 1;
+            self.condvar.notify_one();
+        }
+    }
+}
+
+#[derive(Debug, thiserror::Error)]
+#[error("{0} BlockingPool threads did not exit in time and will be detached")]
+pub struct ShutdownTimedOut(usize);
+
+/// A thread pool for running work that may block.
+///
+/// It is generally discouraged to do any blocking work inside an async function. However, this is
+/// sometimes unavoidable when dealing with interfaces that don't provide async variants. In this
+/// case callers may use the `BlockingPool` to run the blocking work on a different thread and
+/// `await` for its result to finish, which will prevent blocking the main thread of the
+/// application.
+///
+/// Since the blocking work is sent to another thread, users should be careful when using the
+/// `BlockingPool` for latency-sensitive operations. Additionally, the `BlockingPool` is intended to
+/// be used for work that will eventually complete on its own. Users who want to spawn a thread
+/// should just use `thread::spawn` directly.
+///
+/// There is no way to cancel work once it has been picked up by one of the worker threads in the
+/// `BlockingPool`. Dropping or shutting down the pool will block up to a timeout (default 10
+/// seconds) to wait for any active blocking work to finish. Any threads running tasks that have not
+/// completed by that time will be detached.
+///
+/// # Examples
+///
+/// Spawn a task to run in the `BlockingPool` and await on its result.
+///
+/// ```edition2018
+/// use cros_async::BlockingPool;
+///
+/// # async fn do_it() {
+///     let pool = BlockingPool::default();
+///
+///     let res = pool.spawn(move || {
+///         // Do some CPU-intensive or blocking work here.
+///
+///         42
+///     }).await;
+///
+///     assert_eq!(res, 42);
+/// # }
+/// # cros_async::block_on(do_it());
+/// ```
+pub struct BlockingPool {
+    inner: Arc<Inner>,
+}
+
+impl BlockingPool {
+    /// Create a new `BlockingPool`.
+    ///
+    /// The `BlockingPool` will never spawn more than `max_threads` threads to do work, regardless
+    /// of the number of tasks that are added to it. This value should be set relatively low (for
+    /// example, the number of CPUs on the machine) if the pool is intended to run CPU intensive
+    /// work or it should be set relatively high (128 or more) if the pool is intended to be used
+    /// for various IO operations that cannot be completed asynchronously. The default value is 256.
+    ///
+    /// Worker threads are spawned on demand when new work is added to the pool and will
+    /// automatically exit after being idle for some time so there is no overhead for setting
+    /// `max_threads` to a large value when there is little to no work assigned to the
+    /// `BlockingPool`. `keepalive` determines the idle duration after which the worker thread will
+    /// exit. The default value is 10 seconds.
+    pub fn new(max_threads: usize, keepalive: Duration) -> BlockingPool {
+        let (exit, exited_threads) = channel();
+        BlockingPool {
+            inner: Arc::new(Inner {
+                state: Mutex::new(State {
+                    tasks: VecDeque::new(),
+                    num_threads: 0,
+                    num_idle: 0,
+                    worker_threads: Slab::new(),
+                    exited_threads: Some(exited_threads),
+                    exit,
+                    shutting_down: false,
+                }),
+                condvar: Condvar::new(),
+                max_threads,
+                keepalive,
+            }),
+        }
+    }
+
+    /// Like new but with pre-allocating capacity for up to `max_threads`.
+    pub fn with_capacity(max_threads: usize, keepalive: Duration) -> BlockingPool {
+        let (exit, exited_threads) = channel();
+        BlockingPool {
+            inner: Arc::new(Inner {
+                state: Mutex::new(State {
+                    tasks: VecDeque::new(),
+                    num_threads: 0,
+                    num_idle: 0,
+                    worker_threads: Slab::with_capacity(max_threads),
+                    exited_threads: Some(exited_threads),
+                    exit,
+                    shutting_down: false,
+                }),
+                condvar: Condvar::new(),
+                max_threads,
+                keepalive,
+            }),
+        }
+    }
+
+    /// Spawn a task to run in the `BlockingPool`.
+    ///
+    /// Callers may `await` the returned `Task` to be notified when the work is completed.
+    ///
+    /// # Panics
+    ///
+    /// `await`ing a `Task` after dropping the `BlockingPool` or calling `BlockingPool::shutdown`
+    /// will panic if the work was not completed before the pool was shut down.
+    pub fn spawn<F, R>(&self, f: F) -> Task<R>
+    where
+        F: FnOnce() -> R + Send + 'static,
+        R: Send + 'static,
+    {
+        let raw = Arc::downgrade(&self.inner);
+        let schedule = move |runnable| {
+            if let Some(i) = raw.upgrade() {
+                i.schedule(runnable);
+            }
+        };
+
+        let (runnable, task) = async_task::spawn(async move { f() }, schedule);
+        runnable.schedule();
+
+        task
+    }
+
+    /// Shut down the `BlockingPool`.
+    ///
+    /// If `deadline` is provided then this will block until either all worker threads exit or the
+    /// deadline is exceeded. If `deadline` is not given then this will block indefinitely until all
+    /// worker threads exit. Any work that was added to the `BlockingPool` but not yet picked up by
+    /// a worker thread will not complete and `await`ing on the `Task` for that work will panic.
+    pub fn shutdown(&self, deadline: Option<Instant>) -> Result<(), ShutdownTimedOut> {
+        let mut state = self.inner.state.lock();
+
+        if state.shutting_down {
+            // We've already shut down this BlockingPool.
+            return Ok(());
+        }
+
+        state.shutting_down = true;
+        let exited_threads = state.exited_threads.take().expect("exited_threads missing");
+        let unfinished_tasks = mem::replace(&mut state.tasks, VecDeque::new());
+        let mut worker_threads = mem::replace(&mut state.worker_threads, Slab::new());
+        drop(state);
+
+        self.inner.condvar.notify_all();
+
+        // Cancel any unfinished work after releasing the lock.
+        drop(unfinished_tasks);
+
+        // Now wait for all worker threads to exit.
+        if let Some(deadline) = deadline {
+            let mut now = Instant::now();
+            while now < deadline && !worker_threads.is_empty() {
+                if let Ok(idx) = exited_threads.recv_timeout(deadline - now) {
+                    let _ = worker_threads.remove(idx).join();
+                }
+                now = Instant::now();
+            }
+
+            // Any threads that have not yet joined will just be detached.
+            if !worker_threads.is_empty() {
+                return Err(ShutdownTimedOut(worker_threads.len()));
+            }
+
+            Ok(())
+        } else {
+            // Block indefinitely until all worker threads exit.
+            for handle in worker_threads.drain() {
+                let _ = handle.join();
+            }
+
+            Ok(())
+        }
+    }
+}
+
+impl Default for BlockingPool {
+    fn default() -> BlockingPool {
+        BlockingPool::new(256, Duration::from_secs(10))
+    }
+}
+
+impl Drop for BlockingPool {
+    fn drop(&mut self) {
+        if let Err(e) = self.shutdown(Some(Instant::now() + DEFAULT_SHUTDOWN_TIMEOUT)) {
+            warn!("{}", e);
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use std::{
+        sync::{Arc, Barrier},
+        thread,
+        time::{Duration, Instant},
+    };
+
+    use futures::{stream::FuturesUnordered, StreamExt};
+    use sync::{Condvar, Mutex};
+
+    use crate::{block_on, BlockingPool};
+
+    #[test]
+    fn blocking_sleep() {
+        let pool = BlockingPool::default();
+
+        let res = block_on(pool.spawn(|| 42));
+        assert_eq!(res, 42);
+    }
+
+    #[test]
+    fn more_tasks_than_threads() {
+        let pool = BlockingPool::new(4, Duration::from_secs(10));
+
+        let stream = (0..19)
+            .map(|_| pool.spawn(|| thread::sleep(Duration::from_millis(5))))
+            .collect::<FuturesUnordered<_>>();
+
+        let results = block_on(stream.collect::<Vec<_>>());
+        assert_eq!(results.len(), 19);
+    }
+
+    #[test]
+    fn shutdown() {
+        let pool = BlockingPool::default();
+
+        let stream = (0..19)
+            .map(|_| pool.spawn(|| thread::sleep(Duration::from_millis(5))))
+            .collect::<FuturesUnordered<_>>();
+
+        let results = block_on(stream.collect::<Vec<_>>());
+        assert_eq!(results.len(), 19);
+
+        pool.shutdown(Some(Instant::now() + Duration::from_secs(10)))
+            .unwrap();
+        let state = pool.inner.state.lock();
+        assert_eq!(state.num_threads, 0);
+    }
+
+    #[test]
+    fn keepalive_timeout() {
+        // Set the keepalive to a very low value so that threads will exit soon after they run out
+        // of work.
+        let pool = BlockingPool::new(7, Duration::from_millis(1));
+
+        let stream = (0..19)
+            .map(|_| pool.spawn(|| thread::sleep(Duration::from_millis(5))))
+            .collect::<FuturesUnordered<_>>();
+
+        let results = block_on(stream.collect::<Vec<_>>());
+        assert_eq!(results.len(), 19);
+
+        // Wait for all threads to exit.
+        let deadline = Instant::now() + Duration::from_secs(10);
+        while Instant::now() < deadline {
+            thread::sleep(Duration::from_millis(100));
+            let state = pool.inner.state.lock();
+            if state.num_threads == 0 {
+                break;
+            }
+        }
+
+        {
+            let state = pool.inner.state.lock();
+            assert_eq!(state.num_threads, 0);
+            assert_eq!(state.num_idle, 0);
+        }
+    }
+
+    #[test]
+    #[should_panic]
+    fn shutdown_with_pending_work() {
+        let pool = BlockingPool::new(1, Duration::from_secs(10));
+
+        let mu = Arc::new(Mutex::new(false));
+        let cv = Arc::new(Condvar::new());
+
+        // First spawn a thread that blocks the pool.
+        let task_mu = mu.clone();
+        let task_cv = cv.clone();
+        pool.spawn(move || {
+            let mut ready = task_mu.lock();
+            while !*ready {
+                ready = task_cv.wait(ready);
+            }
+        })
+        .detach();
+
+        // This task will never finish because we will shut down the pool first.
+        let unfinished = pool.spawn(|| 5);
+
+        // Spawn a thread to unblock the work we started earlier once it sees that the pool is
+        // shutting down.
+        let inner = pool.inner.clone();
+        thread::spawn(move || {
+            let mut state = inner.state.lock();
+            while !state.shutting_down {
+                state = inner.condvar.wait(state);
+            }
+
+            *mu.lock() = true;
+            cv.notify_all();
+        });
+        pool.shutdown(None).unwrap();
+
+        // This should panic.
+        assert_eq!(block_on(unfinished), 5);
+    }
+
+    #[test]
+    fn unfinished_worker_thread() {
+        let pool = BlockingPool::default();
+
+        let ready = Arc::new(Mutex::new(false));
+        let cv = Arc::new(Condvar::new());
+        let barrier = Arc::new(Barrier::new(2));
+
+        let thread_ready = ready.clone();
+        let thread_barrier = barrier.clone();
+        let thread_cv = cv.clone();
+
+        let task = pool.spawn(move || {
+            thread_barrier.wait();
+            let mut ready = thread_ready.lock();
+            while !*ready {
+                ready = thread_cv.wait(ready);
+            }
+        });
+
+        // Wait to shut down the pool until after the worker thread has started.
+        barrier.wait();
+        pool.shutdown(Some(Instant::now() + Duration::from_millis(5)))
+            .unwrap_err();
+
+        let num_threads = pool.inner.state.lock().num_threads;
+        assert_eq!(num_threads, 1);
+
+        // Now wake up the blocked task so we don't leak the thread.
+        *ready.lock() = true;
+        cv.notify_all();
+
+        block_on(task);
+
+        let deadline = Instant::now() + Duration::from_secs(10);
+        while Instant::now() < deadline {
+            thread::sleep(Duration::from_millis(100));
+            let state = pool.inner.state.lock();
+            if state.num_threads == 0 {
+                break;
+            }
+        }
+
+        {
+            let state = pool.inner.state.lock();
+            assert_eq!(state.num_threads, 0);
+            assert_eq!(state.num_idle, 0);
+        }
+    }
+}
diff --git a/cros_async/src/executor.rs b/cros_async/src/executor.rs
index 21295ef..56860f8 100644
--- a/cros_async/src/executor.rs
+++ b/cros_async/src/executor.rs
@@ -50,7 +50,7 @@
 /// // Write all bytes from `data` to `f`.
 /// async fn write_file(f: &dyn IoSourceExt<File>, mut data: Vec<u8>) -> AsyncResult<()> {
 ///     while data.len() > 0 {
-///         let (count, mut buf) = f.write_from_vec(0, data).await?;
+///         let (count, mut buf) = f.write_from_vec(None, data).await?;
 ///
 ///         data = buf.split_off(count);
 ///     }
@@ -68,7 +68,7 @@
 ///
 ///     while rem > 0 {
 ///         let buf = vec![0u8; min(rem, CHUNK_SIZE)];
-///         let (count, mut data) = from.read_to_vec(0, buf).await?;
+///         let (count, mut data) = from.read_to_vec(None, buf).await?;
 ///
 ///         if count == 0 {
 ///             // End of file. Return the number of bytes transferred.
@@ -227,6 +227,46 @@
         }
     }
 
+    /// Run the provided closure on a dedicated thread where blocking is allowed.
+    ///
+    /// Callers may `await` on the returned `Task` to wait for the result of `f`. Dropping or
+    /// canceling the returned `Task` may not cancel the operation if it was already started on a
+    /// worker thread.
+    ///
+    /// # Panics
+    ///
+    /// `await`ing the `Task` after the `Executor` is dropped will panic if the work was not already
+    /// completed.
+    ///
+    /// # Examples
+    ///
+    /// ```edition2018
+    /// # use cros_async::Executor;
+    ///
+    /// # async fn do_it(ex: &Executor) {
+    ///     let res = ex.spawn_blocking(move || {
+    ///         // Do some CPU-intensive or blocking work here.
+    ///
+    ///         42
+    ///     }).await;
+    ///
+    ///     assert_eq!(res, 42);
+    /// # }
+    ///
+    /// # let ex = Executor::new().unwrap();
+    /// # ex.run_until(do_it(&ex)).unwrap();
+    /// ```
+    pub fn spawn_blocking<F, R>(&self, f: F) -> Task<R>
+    where
+        F: FnOnce() -> R + Send + 'static,
+        R: Send + 'static,
+    {
+        match self {
+            Executor::Uring(ex) => ex.spawn_blocking(f),
+            Executor::Fd(ex) => ex.spawn_blocking(f),
+        }
+    }
+
     /// Run the executor indefinitely, driving all spawned futures to completion. This method will
     /// block the current thread and only return in the case of an error.
     ///
diff --git a/cros_async/src/fd_executor.rs b/cros_async/src/fd_executor.rs
index b3c2748..339f9fa 100644
--- a/cros_async/src/fd_executor.rs
+++ b/cros_async/src/fd_executor.rs
@@ -27,8 +27,8 @@
 use sys_util::{add_fd_flags, warn, EpollContext, EpollEvents, EventFd, WatchingEvents};
 use thiserror::Error as ThisError;
 
-use crate::queue::RunnableQueue;
 use crate::waker::{new_waker, WakerToken, WeakWake};
+use crate::{queue::RunnableQueue, BlockingPool};
 
 #[derive(Debug, ThisError)]
 pub enum Error {
@@ -253,6 +253,7 @@
     queue: RunnableQueue,
     poll_ctx: EpollContext<usize>,
     ops: Mutex<Slab<OpStatus>>,
+    blocking_pool: BlockingPool,
     state: AtomicI32,
     notify: EventFd,
 }
@@ -263,6 +264,7 @@
             queue: RunnableQueue::new(),
             poll_ctx: EpollContext::new().map_err(Error::CreatingContext)?,
             ops: Mutex::new(Slab::with_capacity(64)),
+            blocking_pool: Default::default(),
             state: AtomicI32::new(PROCESSING),
             notify,
         })
@@ -330,6 +332,14 @@
         task
     }
 
+    fn spawn_blocking<F, R>(self: &Arc<Self>, f: F) -> Task<R>
+    where
+        F: FnOnce() -> R + Send + 'static,
+        R: Send + 'static,
+    {
+        self.blocking_pool.spawn(f)
+    }
+
     fn run<F: Future>(&self, cx: &mut Context, done: F) -> Result<F::Output> {
         let events = EpollEvents::new();
         pin_mut!(done);
@@ -497,6 +507,14 @@
         self.raw.spawn_local(f)
     }
 
+    pub fn spawn_blocking<F, R>(&self, f: F) -> Task<R>
+    where
+        F: FnOnce() -> R + Send + 'static,
+        R: Send + 'static,
+    {
+        self.raw.spawn_blocking(f)
+    }
+
     pub fn run(&self) -> Result<()> {
         let waker = new_waker(Arc::downgrade(&self.raw));
         let mut cx = Context::from_waker(&waker);
diff --git a/cros_async/src/io_ext.rs b/cros_async/src/io_ext.rs
index 075c152..81ad987 100644
--- a/cros_async/src/io_ext.rs
+++ b/cros_async/src/io_ext.rs
@@ -52,12 +52,16 @@
 #[async_trait(?Send)]
 pub trait ReadAsync {
     /// Reads from the iosource at `file_offset` and fill the given `vec`.
-    async fn read_to_vec<'a>(&'a self, file_offset: u64, vec: Vec<u8>) -> Result<(usize, Vec<u8>)>;
+    async fn read_to_vec<'a>(
+        &'a self,
+        file_offset: Option<u64>,
+        vec: Vec<u8>,
+    ) -> Result<(usize, Vec<u8>)>;
 
     /// Reads to the given `mem` at the given offsets from the file starting at `file_offset`.
     async fn read_to_mem<'a>(
         &'a self,
-        file_offset: u64,
+        file_offset: Option<u64>,
         mem: Arc<dyn BackingMemory + Send + Sync>,
         mem_offsets: &'a [MemRegion],
     ) -> Result<usize>;
@@ -75,14 +79,14 @@
     /// Writes from the given `vec` to the file starting at `file_offset`.
     async fn write_from_vec<'a>(
         &'a self,
-        file_offset: u64,
+        file_offset: Option<u64>,
         vec: Vec<u8>,
     ) -> Result<(usize, Vec<u8>)>;
 
     /// Writes from the given `mem` from the given offsets to the file starting at `file_offset`.
     async fn write_from_mem<'a>(
         &'a self,
-        file_offset: u64,
+        file_offset: Option<u64>,
         mem: Arc<dyn BackingMemory + Send + Sync>,
         mem_offsets: &'a [MemRegion],
     ) -> Result<usize>;
@@ -212,7 +216,7 @@
         // Start a uring operation and then await the result from an FdExecutor.
         async fn go(source: UringSource<File>) {
             let v = vec![0xa4u8; 16];
-            let (len, vec) = source.read_to_vec(0, v).await.unwrap();
+            let (len, vec) = source.read_to_vec(None, v).await.unwrap();
             assert_eq!(len, 16);
             assert!(vec.iter().all(|&b| b == 0));
         }
@@ -243,7 +247,7 @@
         // Start a poll operation and then await the result from a URingExecutor.
         async fn go(source: PollSource<File>) {
             let v = vec![0x2cu8; 16];
-            let (len, vec) = source.read_to_vec(0, v).await.unwrap();
+            let (len, vec) = source.read_to_vec(None, v).await.unwrap();
             assert_eq!(len, 16);
             assert!(vec.iter().all(|&b| b == 0));
         }
@@ -274,7 +278,7 @@
         async fn go<F: AsRawFd>(async_source: Box<dyn IoSourceExt<F>>) {
             let v = vec![0x55u8; 32];
             let v_ptr = v.as_ptr();
-            let ret = async_source.read_to_vec(0, v).await.unwrap();
+            let ret = async_source.read_to_vec(None, v).await.unwrap();
             assert_eq!(ret.0, 32);
             let ret_v = ret.1;
             assert_eq!(v_ptr, ret_v.as_ptr());
@@ -297,7 +301,7 @@
         async fn go<F: AsRawFd>(async_source: Box<dyn IoSourceExt<F>>) {
             let v = vec![0x55u8; 32];
             let v_ptr = v.as_ptr();
-            let ret = async_source.write_from_vec(0, v).await.unwrap();
+            let ret = async_source.write_from_vec(None, v).await.unwrap();
             assert_eq!(ret.0, 32);
             let ret_v = ret.1;
             assert_eq!(v_ptr, ret_v.as_ptr());
@@ -320,7 +324,7 @@
             let mem = Arc::new(VecIoWrapper::from(vec![0x55u8; 8192]));
             let ret = async_source
                 .read_to_mem(
-                    0,
+                    None,
                     Arc::<VecIoWrapper>::clone(&mem),
                     &[
                         MemRegion { offset: 0, len: 32 },
@@ -360,7 +364,7 @@
             let mem = Arc::new(VecIoWrapper::from(vec![0x55u8; 8192]));
             let ret = async_source
                 .write_from_mem(
-                    0,
+                    None,
                     Arc::<VecIoWrapper>::clone(&mem),
                     &[MemRegion { offset: 0, len: 32 }],
                 )
@@ -421,7 +425,7 @@
         async fn go<F: AsRawFd>(source: Box<dyn IoSourceExt<F>>) {
             let v = vec![0x55u8; 32];
             let v_ptr = v.as_ptr();
-            let ret = source.write_from_vec(0, v).await.unwrap();
+            let ret = source.write_from_vec(None, v).await.unwrap();
             assert_eq!(ret.0, 32);
             let ret_v = ret.1;
             assert_eq!(v_ptr, ret_v.as_ptr());
diff --git a/cros_async/src/lib.rs b/cros_async/src/lib.rs
index d59df8b..f9604e5 100644
--- a/cros_async/src/lib.rs
+++ b/cros_async/src/lib.rs
@@ -74,7 +74,7 @@
 mod uring_source;
 mod waker;
 
-pub use blocking::block_on;
+pub use blocking::{block_on, BlockingPool};
 pub use event::EventAsync;
 pub use executor::Executor;
 pub use fd_executor::FdExecutor;
diff --git a/cros_async/src/poll_source.rs b/cros_async/src/poll_source.rs
index 1a2d4ae..1fd7f06 100644
--- a/cros_async/src/poll_source.rs
+++ b/cros_async/src/poll_source.rs
@@ -97,18 +97,28 @@
     /// Reads from the iosource at `file_offset` and fill the given `vec`.
     async fn read_to_vec<'a>(
         &'a self,
-        file_offset: u64,
+        file_offset: Option<u64>,
         mut vec: Vec<u8>,
     ) -> AsyncResult<(usize, Vec<u8>)> {
         loop {
             // Safe because this will only modify `vec` and we check the return value.
-            let res = unsafe {
-                libc::pread64(
-                    self.as_raw_fd(),
-                    vec.as_mut_ptr() as *mut libc::c_void,
-                    vec.len(),
-                    file_offset as libc::off64_t,
-                )
+            let res = if let Some(offset) = file_offset {
+                unsafe {
+                    libc::pread64(
+                        self.as_raw_fd(),
+                        vec.as_mut_ptr() as *mut libc::c_void,
+                        vec.len(),
+                        offset as libc::off64_t,
+                    )
+                }
+            } else {
+                unsafe {
+                    libc::read(
+                        self.as_raw_fd(),
+                        vec.as_mut_ptr() as *mut libc::c_void,
+                        vec.len(),
+                    )
+                }
             };
 
             if res >= 0 {
@@ -128,7 +138,7 @@
     /// Reads to the given `mem` at the given offsets from the file starting at `file_offset`.
     async fn read_to_mem<'a>(
         &'a self,
-        file_offset: u64,
+        file_offset: Option<u64>,
         mem: Arc<dyn BackingMemory + Send + Sync>,
         mem_offsets: &'a [MemRegion],
     ) -> AsyncResult<usize> {
@@ -140,13 +150,23 @@
         loop {
             // Safe because we trust the kernel not to write path the length given and the length is
             // guaranteed to be valid from the pointer by io_slice_mut.
-            let res = unsafe {
-                libc::preadv64(
-                    self.as_raw_fd(),
-                    iovecs.as_mut_ptr() as *mut _,
-                    iovecs.len() as i32,
-                    file_offset as libc::off64_t,
-                )
+            let res = if let Some(offset) = file_offset {
+                unsafe {
+                    libc::preadv64(
+                        self.as_raw_fd(),
+                        iovecs.as_mut_ptr() as *mut _,
+                        iovecs.len() as i32,
+                        offset as libc::off64_t,
+                    )
+                }
+            } else {
+                unsafe {
+                    libc::readv(
+                        self.as_raw_fd(),
+                        iovecs.as_mut_ptr() as *mut _,
+                        iovecs.len() as i32,
+                    )
+                }
             };
 
             if res >= 0 {
@@ -202,18 +222,28 @@
     /// Writes from the given `vec` to the file starting at `file_offset`.
     async fn write_from_vec<'a>(
         &'a self,
-        file_offset: u64,
+        file_offset: Option<u64>,
         vec: Vec<u8>,
     ) -> AsyncResult<(usize, Vec<u8>)> {
         loop {
             // Safe because this will not modify any memory and we check the return value.
-            let res = unsafe {
-                libc::pwrite64(
-                    self.as_raw_fd(),
-                    vec.as_ptr() as *const libc::c_void,
-                    vec.len(),
-                    file_offset as libc::off64_t,
-                )
+            let res = if let Some(offset) = file_offset {
+                unsafe {
+                    libc::pwrite64(
+                        self.as_raw_fd(),
+                        vec.as_ptr() as *const libc::c_void,
+                        vec.len(),
+                        offset as libc::off64_t,
+                    )
+                }
+            } else {
+                unsafe {
+                    libc::write(
+                        self.as_raw_fd(),
+                        vec.as_ptr() as *const libc::c_void,
+                        vec.len(),
+                    )
+                }
             };
 
             if res >= 0 {
@@ -233,7 +263,7 @@
     /// Writes from the given `mem` from the given offsets to the file starting at `file_offset`.
     async fn write_from_mem<'a>(
         &'a self,
-        file_offset: u64,
+        file_offset: Option<u64>,
         mem: Arc<dyn BackingMemory + Send + Sync>,
         mem_offsets: &'a [MemRegion],
     ) -> AsyncResult<usize> {
@@ -246,13 +276,23 @@
         loop {
             // Safe because we trust the kernel not to write path the length given and the length is
             // guaranteed to be valid from the pointer by io_slice_mut.
-            let res = unsafe {
-                libc::pwritev64(
-                    self.as_raw_fd(),
-                    iovecs.as_ptr() as *mut _,
-                    iovecs.len() as i32,
-                    file_offset as libc::off64_t,
-                )
+            let res = if let Some(offset) = file_offset {
+                unsafe {
+                    libc::pwritev64(
+                        self.as_raw_fd(),
+                        iovecs.as_ptr() as *mut _,
+                        iovecs.len() as i32,
+                        offset as libc::off64_t,
+                    )
+                }
+            } else {
+                unsafe {
+                    libc::writev(
+                        self.as_raw_fd(),
+                        iovecs.as_ptr() as *mut _,
+                        iovecs.len() as i32,
+                    )
+                }
             };
 
             if res >= 0 {
@@ -329,7 +369,7 @@
             let async_source = PollSource::new(f, ex).unwrap();
             let v = vec![0x55u8; 32];
             let v_ptr = v.as_ptr();
-            let ret = async_source.read_to_vec(0, v).await.unwrap();
+            let ret = async_source.read_to_vec(None, v).await.unwrap();
             assert_eq!(ret.0, 32);
             let ret_v = ret.1;
             assert_eq!(v_ptr, ret_v.as_ptr());
@@ -347,7 +387,7 @@
             let async_source = PollSource::new(f, ex).unwrap();
             let v = vec![0x55u8; 32];
             let v_ptr = v.as_ptr();
-            let ret = async_source.write_from_vec(0, v).await.unwrap();
+            let ret = async_source.write_from_vec(None, v).await.unwrap();
             assert_eq!(ret.0, 32);
             let ret_v = ret.1;
             assert_eq!(v_ptr, ret_v.as_ptr());
diff --git a/cros_async/src/queue.rs b/cros_async/src/queue.rs
index 3192703..f95de06 100644
--- a/cros_async/src/queue.rs
+++ b/cros_async/src/queue.rs
@@ -38,6 +38,12 @@
     }
 }
 
+impl Default for RunnableQueue {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
 impl<'q> IntoIterator for &'q RunnableQueue {
     type Item = Runnable;
     type IntoIter = RunnableQueueIter<'q>;
diff --git a/cros_async/src/uring_executor.rs b/cros_async/src/uring_executor.rs
index 08a6448..a38e6c7 100644
--- a/cros_async/src/uring_executor.rs
+++ b/cros_async/src/uring_executor.rs
@@ -52,13 +52,14 @@
 //! ensure it lives long enough.
 
 use std::convert::TryInto;
+use std::ffi::CStr;
 use std::fs::File;
 use std::future::Future;
 use std::io;
-use std::mem;
+use std::mem::{self, MaybeUninit};
 use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
 use std::pin::Pin;
-use std::sync::atomic::{AtomicI32, AtomicU32, Ordering};
+use std::sync::atomic::{AtomicI32, Ordering};
 use std::sync::{Arc, Weak};
 use std::task::Waker;
 use std::task::{Context, Poll};
@@ -67,15 +68,19 @@
 use async_task::Task;
 use futures::task::noop_waker;
 use io_uring::URingContext;
+use once_cell::sync::Lazy;
 use pin_utils::pin_mut;
 use slab::Slab;
 use sync::Mutex;
 use sys_util::{warn, WatchingEvents};
 use thiserror::Error as ThisError;
 
-use crate::mem::{BackingMemory, MemRegion};
 use crate::queue::RunnableQueue;
 use crate::waker::{new_waker, WakerToken, WeakWake};
+use crate::{
+    mem::{BackingMemory, MemRegion},
+    BlockingPool,
+};
 
 #[derive(Debug, ThisError)]
 pub enum Error {
@@ -130,29 +135,41 @@
     }
 }
 
+static USE_URING: Lazy<bool> = Lazy::new(|| {
+    let mut utsname = MaybeUninit::zeroed();
+
+    // Safe because this will only modify `utsname` and we check the return value.
+    let res = unsafe { libc::uname(utsname.as_mut_ptr()) };
+    if res < 0 {
+        return false;
+    }
+
+    // Safe because the kernel has initialized `utsname`.
+    let utsname = unsafe { utsname.assume_init() };
+
+    // Safe because the pointer is valid and the kernel guarantees that this is a valid C string.
+    let release = unsafe { CStr::from_ptr(utsname.release.as_ptr()) };
+
+    let mut components = match release.to_str().map(|r| r.split('.').map(str::parse)) {
+        Ok(c) => c,
+        Err(_) => return false,
+    };
+
+    // Kernels older than 5.10 either didn't support io_uring or had bugs in the implementation.
+    match (components.next(), components.next()) {
+        (Some(Ok(major)), Some(Ok(minor))) if (major, minor) >= (5, 10) => {
+            // The kernel version is new enough so check if we can actually make a uring context.
+            URingContext::new(8).is_ok()
+        }
+        _ => false,
+    }
+});
+
 // Checks if the uring executor is available.
 // Caches the result so that the check is only run once.
 // Useful for falling back to the FD executor on pre-uring kernels.
 pub(crate) fn use_uring() -> bool {
-    const UNKNOWN: u32 = 0;
-    const URING: u32 = 1;
-    const FD: u32 = 2;
-    static USE_URING: AtomicU32 = AtomicU32::new(UNKNOWN);
-    match USE_URING.load(Ordering::Relaxed) {
-        UNKNOWN => {
-            // Create a dummy uring context to check that the kernel understands the syscalls.
-            if URingContext::new(8).is_ok() {
-                USE_URING.store(URING, Ordering::Relaxed);
-                true
-            } else {
-                USE_URING.store(FD, Ordering::Relaxed);
-                false
-            }
-        }
-        URING => true,
-        FD => false,
-        _ => unreachable!("invalid use uring state"),
-    }
+    *USE_URING
 }
 
 pub struct RegisteredSource {
@@ -277,6 +294,7 @@
     ctx: URingContext,
     queue: RunnableQueue,
     ring: Mutex<Ring>,
+    blocking_pool: BlockingPool,
     thread_id: Mutex<Option<ThreadId>>,
     state: AtomicI32,
 }
@@ -290,6 +308,7 @@
                 ops: Slab::with_capacity(NUM_ENTRIES),
                 registered_sources: Slab::with_capacity(NUM_ENTRIES),
             }),
+            blocking_pool: Default::default(),
             thread_id: Mutex::new(None),
             state: AtomicI32::new(PROCESSING),
         })
@@ -351,6 +370,14 @@
         task
     }
 
+    fn spawn_blocking<F, R>(self: &Arc<Self>, f: F) -> Task<R>
+    where
+        F: FnOnce() -> R + Send + 'static,
+        R: Send + 'static,
+    {
+        self.blocking_pool.spawn(f)
+    }
+
     fn runs_tasks_on_current_thread(&self) -> bool {
         let executor_thread = self.thread_id.lock();
         executor_thread
@@ -753,6 +780,14 @@
         self.raw.spawn_local(f)
     }
 
+    pub fn spawn_blocking<F, R>(&self, f: F) -> Task<R>
+    where
+        F: FnOnce() -> R + Send + 'static,
+        R: Send + 'static,
+    {
+        self.raw.spawn_blocking(f)
+    }
+
     pub fn run(&self) -> Result<()> {
         let waker = new_waker(Arc::downgrade(&self.raw));
         let mut cx = Context::from_waker(&waker);
@@ -880,6 +915,10 @@
 
     #[test]
     fn dont_drop_backing_mem_read() {
+        if !use_uring() {
+            return;
+        }
+
         // Create a backing memory wrapped in an Arc and check that the drop isn't called while the
         // op is pending.
         let bm =
@@ -920,6 +959,10 @@
 
     #[test]
     fn dont_drop_backing_mem_write() {
+        if !use_uring() {
+            return;
+        }
+
         // Create a backing memory wrapped in an Arc and check that the drop isn't called while the
         // op is pending.
         let bm =
@@ -961,6 +1004,10 @@
 
     #[test]
     fn canceled_before_completion() {
+        if !use_uring() {
+            return;
+        }
+
         async fn cancel_io(op: PendingOperation) {
             mem::drop(op);
         }
@@ -999,6 +1046,10 @@
 
     #[test]
     fn drop_before_completion() {
+        if !use_uring() {
+            return;
+        }
+
         const VALUE: u64 = 0xef6c_a8df_b842_eb9c;
 
         async fn check_op(op: PendingOperation) {
@@ -1044,6 +1095,10 @@
 
     #[test]
     fn drop_on_different_thread() {
+        if !use_uring() {
+            return;
+        }
+
         let ex = URingExecutor::new().unwrap();
 
         let ex2 = ex.clone();
diff --git a/cros_async/src/uring_source.rs b/cros_async/src/uring_source.rs
index fa1b3c2..1223577 100644
--- a/cros_async/src/uring_source.rs
+++ b/cros_async/src/uring_source.rs
@@ -18,26 +18,6 @@
 /// `UringSource` wraps FD backed IO sources for use with io_uring. It is a thin wrapper around
 /// registering an IO source with the uring that provides an `IoSource` implementation.
 /// Most useful functions are provided by 'IoSourceExt'.
-///
-/// # Example
-/// ```rust
-/// use std::fs::File;
-/// use cros_async::{UringSource, ReadAsync, URingExecutor};
-///
-/// async fn read_four_bytes(source: &UringSource<File>) -> (usize, Vec<u8>) {
-///    let mem = vec![0u8; 4];
-///    source.read_to_vec(0, mem).await.unwrap()
-/// }
-///
-/// fn read_file(f: File) -> Result<(), Box<dyn std::error::Error>> {
-///     let ex = URingExecutor::new()?;
-///     let async_source = UringSource::new(f, &ex)?;
-///     let (nread, vec) = ex.run_until(read_four_bytes(&async_source))?;
-///     assert_eq!(nread, 4);
-///     assert_eq!(vec.len(), 4);
-///     Ok(())
-/// }
-/// ```
 pub struct UringSource<F: AsRawFd> {
     registered_source: RegisteredSource,
     source: F,
@@ -64,12 +44,12 @@
     /// Reads from the iosource at `file_offset` and fill the given `vec`.
     async fn read_to_vec<'a>(
         &'a self,
-        file_offset: u64,
+        file_offset: Option<u64>,
         vec: Vec<u8>,
     ) -> AsyncResult<(usize, Vec<u8>)> {
         let buf = Arc::new(VecIoWrapper::from(vec));
         let op = self.registered_source.start_read_to_mem(
-            file_offset,
+            file_offset.unwrap_or(0),
             buf.clone(),
             &[MemRegion {
                 offset: 0,
@@ -127,13 +107,13 @@
     /// Reads to the given `mem` at the given offsets from the file starting at `file_offset`.
     async fn read_to_mem<'a>(
         &'a self,
-        file_offset: u64,
+        file_offset: Option<u64>,
         mem: Arc<dyn BackingMemory + Send + Sync>,
         mem_offsets: &'a [MemRegion],
     ) -> AsyncResult<usize> {
-        let op = self
-            .registered_source
-            .start_read_to_mem(file_offset, mem, mem_offsets)?;
+        let op =
+            self.registered_source
+                .start_read_to_mem(file_offset.unwrap_or(0), mem, mem_offsets)?;
         let len = op.await?;
         Ok(len as usize)
     }
@@ -144,12 +124,12 @@
     /// Writes from the given `vec` to the file starting at `file_offset`.
     async fn write_from_vec<'a>(
         &'a self,
-        file_offset: u64,
+        file_offset: Option<u64>,
         vec: Vec<u8>,
     ) -> AsyncResult<(usize, Vec<u8>)> {
         let buf = Arc::new(VecIoWrapper::from(vec));
         let op = self.registered_source.start_write_from_mem(
-            file_offset,
+            file_offset.unwrap_or(0),
             buf.clone(),
             &[MemRegion {
                 offset: 0,
@@ -169,13 +149,15 @@
     /// Writes from the given `mem` from the given offsets to the file starting at `file_offset`.
     async fn write_from_mem<'a>(
         &'a self,
-        file_offset: u64,
+        file_offset: Option<u64>,
         mem: Arc<dyn BackingMemory + Send + Sync>,
         mem_offsets: &'a [MemRegion],
     ) -> AsyncResult<usize> {
-        let op = self
-            .registered_source
-            .start_write_from_mem(file_offset, mem, mem_offsets)?;
+        let op = self.registered_source.start_write_from_mem(
+            file_offset.unwrap_or(0),
+            mem,
+            mem_offsets,
+        )?;
         let len = op.await?;
         Ok(len as usize)
     }
@@ -236,12 +218,17 @@
     use std::path::PathBuf;
 
     use crate::io_ext::{ReadAsync, WriteAsync};
+    use crate::uring_executor::use_uring;
     use crate::UringSource;
 
     use super::*;
 
     #[test]
     fn read_to_mem() {
+        if !use_uring() {
+            return;
+        }
+
         use crate::mem::VecIoWrapper;
         use std::io::Write;
         use tempfile::tempfile;
@@ -258,7 +245,7 @@
         let buf: Arc<VecIoWrapper> = Arc::new(VecIoWrapper::from(vec![0x44; 8192]));
 
         let fut = io_obj.read_to_mem(
-            0,
+            None,
             Arc::<VecIoWrapper>::clone(&buf),
             &[MemRegion {
                 offset: 0,
@@ -275,12 +262,16 @@
 
     #[test]
     fn readvec() {
+        if !use_uring() {
+            return;
+        }
+
         async fn go(ex: &URingExecutor) {
             let f = File::open("/dev/zero").unwrap();
             let source = UringSource::new(f, ex).unwrap();
             let v = vec![0x55u8; 32];
             let v_ptr = v.as_ptr();
-            let ret = source.read_to_vec(0, v).await.unwrap();
+            let ret = source.read_to_vec(None, v).await.unwrap();
             assert_eq!(ret.0, 32);
             let ret_v = ret.1;
             assert_eq!(v_ptr, ret_v.as_ptr());
@@ -293,13 +284,20 @@
 
     #[test]
     fn readmulti() {
+        if !use_uring() {
+            return;
+        }
+
         async fn go(ex: &URingExecutor) {
             let f = File::open("/dev/zero").unwrap();
             let source = UringSource::new(f, ex).unwrap();
             let v = vec![0x55u8; 32];
             let v2 = vec![0x55u8; 32];
-            let (ret, ret2) =
-                futures::future::join(source.read_to_vec(0, v), source.read_to_vec(32, v2)).await;
+            let (ret, ret2) = futures::future::join(
+                source.read_to_vec(None, v),
+                source.read_to_vec(Some(32), v2),
+            )
+            .await;
 
             assert!(ret.unwrap().1.iter().all(|&b| b == 0));
             assert!(ret2.unwrap().1.iter().all(|&b| b == 0));
@@ -312,7 +310,7 @@
     async fn read_u64<T: AsRawFd>(source: &UringSource<T>) -> u64 {
         // Init a vec that translates to u64::max;
         let u64_mem = vec![0xffu8; std::mem::size_of::<u64>()];
-        let (ret, u64_mem) = source.read_to_vec(0, u64_mem).await.unwrap();
+        let (ret, u64_mem) = source.read_to_vec(None, u64_mem).await.unwrap();
         assert_eq!(ret as usize, std::mem::size_of::<u64>());
         let mut val = 0u64.to_ne_bytes();
         val.copy_from_slice(&u64_mem);
@@ -321,6 +319,10 @@
 
     #[test]
     fn u64_from_file() {
+        if !use_uring() {
+            return;
+        }
+
         let f = File::open("/dev/zero").unwrap();
         let ex = URingExecutor::new().unwrap();
         let source = UringSource::new(f, &ex).unwrap();
@@ -330,6 +332,10 @@
 
     #[test]
     fn event() {
+        if !use_uring() {
+            return;
+        }
+
         use sys_util::EventFd;
 
         async fn write_event(ev: EventFd, wait: EventFd, ex: &URingExecutor) {
@@ -367,6 +373,10 @@
 
     #[test]
     fn pend_on_pipe() {
+        if !use_uring() {
+            return;
+        }
+
         use std::io::Write;
 
         use futures::future::Either;
@@ -393,6 +403,10 @@
 
     #[test]
     fn readmem() {
+        if !use_uring() {
+            return;
+        }
+
         async fn go(ex: &URingExecutor) {
             let f = File::open("/dev/zero").unwrap();
             let source = UringSource::new(f, ex).unwrap();
@@ -400,7 +414,7 @@
             let vw = Arc::new(VecIoWrapper::from(v));
             let ret = source
                 .read_to_mem(
-                    0,
+                    None,
                     Arc::<VecIoWrapper>::clone(&vw),
                     &[MemRegion { offset: 0, len: 32 }],
                 )
@@ -419,7 +433,7 @@
             let vw = Arc::new(VecIoWrapper::from(v));
             let ret = source
                 .read_to_mem(
-                    0,
+                    None,
                     Arc::<VecIoWrapper>::clone(&vw),
                     &[MemRegion {
                         offset: 32,
@@ -443,6 +457,10 @@
 
     #[test]
     fn range_error() {
+        if !use_uring() {
+            return;
+        }
+
         async fn go(ex: &URingExecutor) {
             let f = File::open("/dev/zero").unwrap();
             let source = UringSource::new(f, ex).unwrap();
@@ -450,7 +468,7 @@
             let vw = Arc::new(VecIoWrapper::from(v));
             let ret = source
                 .read_to_mem(
-                    0,
+                    None,
                     Arc::<VecIoWrapper>::clone(&vw),
                     &[MemRegion {
                         offset: 32,
@@ -467,6 +485,10 @@
 
     #[test]
     fn fallocate() {
+        if !use_uring() {
+            return;
+        }
+
         async fn go(ex: &URingExecutor) {
             let dir = tempfile::TempDir::new().unwrap();
             let mut file_path = PathBuf::from(dir.path());
@@ -500,6 +522,10 @@
 
     #[test]
     fn fsync() {
+        if !use_uring() {
+            return;
+        }
+
         async fn go(ex: &URingExecutor) {
             let f = tempfile::tempfile().unwrap();
             let source = UringSource::new(f, ex).unwrap();
@@ -512,6 +538,10 @@
 
     #[test]
     fn wait_read() {
+        if !use_uring() {
+            return;
+        }
+
         async fn go(ex: &URingExecutor) {
             let f = File::open("/dev/zero").unwrap();
             let source = UringSource::new(f, ex).unwrap();
@@ -524,6 +554,10 @@
 
     #[test]
     fn writemem() {
+        if !use_uring() {
+            return;
+        }
+
         async fn go(ex: &URingExecutor) {
             let f = OpenOptions::new()
                 .create(true)
@@ -534,7 +568,7 @@
             let v = vec![0x55u8; 64];
             let vw = Arc::new(crate::mem::VecIoWrapper::from(v));
             let ret = source
-                .write_from_mem(0, vw, &[MemRegion { offset: 0, len: 32 }])
+                .write_from_mem(None, vw, &[MemRegion { offset: 0, len: 32 }])
                 .await
                 .unwrap();
             assert_eq!(32, ret);
@@ -546,6 +580,10 @@
 
     #[test]
     fn writevec() {
+        if !use_uring() {
+            return;
+        }
+
         async fn go(ex: &URingExecutor) {
             let f = OpenOptions::new()
                 .create(true)
@@ -556,7 +594,7 @@
             let source = UringSource::new(f, ex).unwrap();
             let v = vec![0x55u8; 32];
             let v_ptr = v.as_ptr();
-            let (ret, ret_v) = source.write_from_vec(0, v).await.unwrap();
+            let (ret, ret_v) = source.write_from_vec(None, v).await.unwrap();
             assert_eq!(32, ret);
             assert_eq!(v_ptr, ret_v.as_ptr());
         }
@@ -567,6 +605,10 @@
 
     #[test]
     fn writemulti() {
+        if !use_uring() {
+            return;
+        }
+
         async fn go(ex: &URingExecutor) {
             let f = OpenOptions::new()
                 .create(true)
@@ -577,9 +619,11 @@
             let source = UringSource::new(f, ex).unwrap();
             let v = vec![0x55u8; 32];
             let v2 = vec![0x55u8; 32];
-            let (r, r2) =
-                futures::future::join(source.write_from_vec(0, v), source.write_from_vec(32, v2))
-                    .await;
+            let (r, r2) = futures::future::join(
+                source.write_from_vec(None, v),
+                source.write_from_vec(Some(32), v2),
+            )
+            .await;
             assert_eq!(32, r.unwrap().0);
             assert_eq!(32, r2.unwrap().0);
         }
diff --git a/crosvm_plugin/Android.bp b/crosvm_plugin/Android.bp
index bf0e215..25e7b3e 100644
--- a/crosvm_plugin/Android.bp
+++ b/crosvm_plugin/Android.bp
@@ -1,62 +1 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
-// Do not modify this file as changes will be overridden on upgrade.
-
-// dependent_library ["feature_list"]
-//   ../assertions/src/lib.rs
-//   ../base/src/lib.rs
-//   ../cros_async/src/lib.rs
-//   ../data_model/src/lib.rs
-//   ../io_uring/src/lib.rs
-//   ../kvm/src/lib.rs
-//   ../kvm_sys/src/lib.rs
-//   ../protos/src/lib.rs "kvm_sys,plugin"
-//   ../sync/src/lib.rs
-//   ../sys_util/poll_token_derive/poll_token_derive.rs
-//   ../sys_util/src/lib.rs
-//   ../tempfile/src/lib.rs
-//   ../vm_memory/src/lib.rs
-//   async-task-4.0.3 "default,std"
-//   async-trait-0.1.50
-//   autocfg-1.0.1
-//   bitflags-1.2.1 "default"
-//   cfg-if-1.0.0
-//   either-1.6.1 "default,use_std"
-//   futures-0.3.14 "alloc"
-//   futures-channel-0.3.14 "alloc,futures-sink,sink"
-//   futures-core-0.3.14 "alloc"
-//   futures-io-0.3.14
-//   futures-sink-0.3.14 "alloc"
-//   futures-task-0.3.14 "alloc"
-//   futures-util-0.3.14 "alloc,futures-sink,sink"
-//   getrandom-0.2.2 "std"
-//   intrusive-collections-0.9.0 "alloc,default"
-//   itoa-0.4.7
-//   libc-0.2.93 "default,std"
-//   log-0.4.14
-//   memoffset-0.5.6 "default"
-//   paste-1.0.5
-//   pin-project-lite-0.2.6
-//   pin-utils-0.1.0
-//   ppv-lite86-0.2.10 "simd,std"
-//   proc-macro2-1.0.26 "default,proc-macro"
-//   protobuf-2.22.1
-//   protobuf-codegen-2.22.1
-//   protoc-2.22.1
-//   protoc-rust-2.22.1
-//   quote-1.0.9 "default,proc-macro"
-//   rand-0.8.3 "alloc,default,getrandom,libc,rand_chacha,rand_hc,std,std_rng"
-//   rand_chacha-0.3.0 "std"
-//   rand_core-0.6.2 "alloc,getrandom,std"
-//   remove_dir_all-0.5.3
-//   ryu-1.0.5
-//   serde-1.0.125 "default,derive,serde_derive,std"
-//   serde_derive-1.0.125 "default"
-//   serde_json-1.0.64 "default,std"
-//   slab-0.4.3 "default,std"
-//   smallvec-1.6.1
-//   syn-1.0.70 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-//   tempfile-3.2.0
-//   thiserror-1.0.24
-//   thiserror-impl-1.0.24
-//   unicode-xid-0.2.1 "default"
-//   which-4.1.0
+// Feature is disabled for Android
diff --git a/crosvm_plugin/cargo2android.json b/crosvm_plugin/cargo2android.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/crosvm_plugin/cargo2android.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/crosvm_plugin/src/lib.rs b/crosvm_plugin/src/lib.rs
index 86827a6..e0fb2f1 100644
--- a/crosvm_plugin/src/lib.rs
+++ b/crosvm_plugin/src/lib.rs
@@ -31,7 +31,7 @@
 
 use libc::{E2BIG, EINVAL, ENOENT, ENOTCONN, EPROTO};
 
-use protobuf::{parse_from_bytes, Message, ProtobufEnum, RepeatedField};
+use protobuf::{Message, ProtobufEnum, RepeatedField};
 
 use base::ScmSocket;
 
@@ -308,8 +308,8 @@
             .map(|&fd| unsafe { File::from_raw_fd(fd) })
             .collect();
 
-        let response: MainResponse =
-            parse_from_bytes(&self.response_buffer[..msg_size]).map_err(proto_error_to_int)?;
+        let response: MainResponse = Message::parse_from_bytes(&self.response_buffer[..msg_size])
+            .map_err(proto_error_to_int)?;
         if response.errno != 0 {
             return Err(response.errno);
         }
@@ -1070,7 +1070,7 @@
         if bytes == 0 || total_size > self.response_length {
             return Err(EINVAL);
         }
-        let response: VcpuResponse = parse_from_bytes(
+        let response: VcpuResponse = Message::parse_from_bytes(
             &self.response_buffer[self.response_base + bytes..self.response_base + total_size],
         )
         .map_err(proto_error_to_int)?;
diff --git a/data_model/Android.bp b/data_model/Android.bp
index ee96a8e..a4e8604 100644
--- a/data_model/Android.bp
+++ b/data_model/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
 // Do not modify this file as changes will be overridden on upgrade.
 
 package {
@@ -11,7 +11,7 @@
 }
 
 rust_defaults {
-    name: "data_model_defaults",
+    name: "data_model_test_defaults",
     defaults: ["crosvm_defaults"],
     crate_name: "data_model",
     srcs: ["src/lib.rs"],
@@ -27,7 +27,7 @@
 
 rust_test_host {
     name: "data_model_host_test_src_lib",
-    defaults: ["data_model_defaults"],
+    defaults: ["data_model_test_defaults"],
     test_options: {
         unit_test: true,
     },
@@ -35,7 +35,7 @@
 
 rust_test {
     name: "data_model_device_test_src_lib",
-    defaults: ["data_model_defaults"],
+    defaults: ["data_model_test_defaults"],
 }
 
 rust_library {
@@ -51,13 +51,3 @@
         "libserde",
     ],
 }
-
-// dependent_library ["feature_list"]
-//   ../assertions/src/lib.rs
-//   libc-0.2.97 "default,std"
-//   proc-macro2-1.0.27 "default,proc-macro"
-//   quote-1.0.9 "default,proc-macro"
-//   serde-1.0.126 "default,derive,serde_derive,std"
-//   serde_derive-1.0.126 "default"
-//   syn-1.0.73 "clone-impls,default,derive,parsing,printing,proc-macro,quote"
-//   unicode-xid-0.2.2 "default"
diff --git a/devices/Android.bp b/devices/Android.bp
index 154bc2b..6ab3fe8 100644
--- a/devices/Android.bp
+++ b/devices/Android.bp
@@ -1,5 +1,5 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
-// Manually added features: audio, gfxstream, gpu, gpu_buffer, gpu_display, gpu_renderer, usb.
+// This file is generated by cargo2android.py --config cargo2android.json.
+// Do not modify this file as changes will be overridden on upgrade.
 
 package {
     // See: http://go/android-license-faq
@@ -11,7 +11,7 @@
 }
 
 rust_defaults {
-    name: "devices_defaults",
+    name: "devices_test_defaults",
     defaults: ["crosvm_defaults"],
     crate_name: "devices",
     // has rustc warnings
@@ -19,6 +19,12 @@
     test_suites: ["general-tests"],
     auto_gen_config: true,
     edition: "2018",
+    features: [
+        "audio",
+        "gfxstream",
+        "gpu",
+        "usb",
+    ],
     rustlibs: [
         "libacpi_tables",
         "libaudio_streams",
@@ -29,19 +35,18 @@
         "libdisk",
         "libfuse_rust",
         "libfutures",
+        "libgpu_display",
         "libhypervisor",
         "libkvm_sys",
         "liblibc",
-        "liblibchromeos",
-        "liblibcras",
         "liblinux_input_sys",
         "libminijail_rust",
         "libnet_sys",
         "libnet_util",
         "libp9",
         "libpower_monitor",
-        "librand_ish",
         "libresources",
+        "librutabaga_gfx",
         "libserde",
         "libsmallvec",
         "libsync_rust",
@@ -60,31 +65,6 @@
         "libenumn",
         "libremain",
     ],
-}
-
-rust_test_host {
-    name: "devices_host_test_src_lib",
-    defaults: ["devices_defaults"],
-    test_options: {
-        unit_test: true,
-    },
-    shared_libs: ["libgfxstream_backend",],
-    features: [  // added manually
-        "audio",
-        "gpu",
-        "gpu_buffer",
-        "gpu_display",
-        "gpu_renderer",
-        "gfxstream",
-    ],
-    rustlibs: [
-        "libaudio_streams",  // moved manually
-        "libgpu_display",  // added manually
-        "liblibcras",  // moved manually
-        "librutabaga_gfx",  // added manually
-    ],
-
-    // added manually
     target: {
         host: {
             shared_libs: ["libvirglrenderer"],
@@ -100,9 +80,19 @@
     },
 }
 
+rust_test_host {
+    name: "devices_host_test_src_lib",
+    defaults: ["devices_test_defaults"],
+    test_options: {
+        unit_test: true,
+    },
+    shared_libs: ["libgfxstream_backend"],
+}
+
 rust_test {
     name: "devices_device_test_src_lib",
-    defaults: ["devices_defaults"],
+    defaults: ["devices_test_defaults"],
+    shared_libs: ["libgfxstream_backend"],
 }
 
 rust_library {
@@ -115,14 +105,10 @@
     edition: "2018",
     features: [
         "audio",
-        "gpu",
-        "gpu_buffer",
-        "gpu_display",
-        "gpu_renderer",
         "gfxstream",
+        "gpu",
         "usb",
     ],
-    shared_libs: ["libgfxstream_backend",],
     rustlibs: [
         "libacpi_tables",
         "libaudio_streams",
@@ -133,21 +119,18 @@
         "libdisk",
         "libfuse_rust",
         "libfutures",
-        "libgpu_display",  // added manually
+        "libgpu_display",
         "libhypervisor",
         "libkvm_sys",
         "liblibc",
-        "liblibchromeos",
-        "liblibcras",
         "liblinux_input_sys",
         "libminijail_rust",
         "libnet_sys",
         "libnet_util",
         "libp9",
         "libpower_monitor",
-        "librand_ish",
         "libresources",
-        "librutabaga_gfx",  // added manually
+        "librutabaga_gfx",
         "libserde",
         "libsmallvec",
         "libsync_rust",
@@ -165,91 +148,5 @@
         "libenumn",
         "libremain",
     ],
+    shared_libs: ["libgfxstream_backend"],
 }
-
-// dependent_library ["feature_list"]
-//   ../../adhd/audio_streams/src/audio_streams.rs
-//   ../../adhd/cras/client/cras-sys/src/lib.rs
-//   ../../adhd/cras/client/libcras/src/libcras.rs
-//   ../../libchromeos-rs/src/lib.rs
-//   ../../minijail/rust/minijail-sys/lib.rs
-//   ../../minijail/rust/minijail/src/lib.rs
-//   ../../rust/vmm_vhost/src/lib.rs "default,vhost-user,vhost-user-master"
-//   ../../vm_tools/p9/src/lib.rs
-//   ../../vm_tools/p9/wire_format_derive/wire_format_derive.rs
-//   ../acpi_tables/src/lib.rs
-//   ../assertions/src/lib.rs
-//   ../base/src/lib.rs
-//   ../bit_field/bit_field_derive/bit_field_derive.rs
-//   ../bit_field/src/lib.rs
-//   ../cros_async/src/lib.rs
-//   ../data_model/src/lib.rs
-//   ../disk/src/disk.rs
-//   ../enumn/src/lib.rs
-//   ../fuse/src/lib.rs
-//   ../hypervisor/src/lib.rs
-//   ../io_uring/src/lib.rs
-//   ../kvm/src/lib.rs
-//   ../kvm_sys/src/lib.rs
-//   ../linux_input_sys/src/lib.rs
-//   ../net_sys/src/lib.rs
-//   ../net_util/src/lib.rs
-//   ../power_monitor/src/lib.rs
-//   ../rand_ish/src/lib.rs
-//   ../resources/src/lib.rs
-//   ../rutabaga_gfx/src/lib.rs
-//   ../sync/src/lib.rs
-//   ../sys_util/poll_token_derive/poll_token_derive.rs
-//   ../sys_util/src/lib.rs
-//   ../tempfile/src/lib.rs
-//   ../usb_sys/src/lib.rs
-//   ../usb_util/src/lib.rs
-//   ../vfio_sys/src/lib.rs
-//   ../vhost/src/lib.rs
-//   ../virtio_sys/src/lib.rs
-//   ../vm_control/src/lib.rs
-//   ../vm_memory/src/lib.rs
-//   async-task-4.0.3 "default,std"
-//   async-trait-0.1.50
-//   autocfg-1.0.1
-//   bitflags-1.2.1 "default"
-//   cfg-if-1.0.0
-//   downcast-rs-1.2.0 "default,std"
-//   futures-0.3.15 "alloc,std"
-//   futures-channel-0.3.15 "alloc,futures-sink,sink,std"
-//   futures-core-0.3.15 "alloc,std"
-//   futures-io-0.3.15 "std"
-//   futures-sink-0.3.15 "alloc,std"
-//   futures-task-0.3.15 "alloc,std"
-//   futures-util-0.3.15 "alloc,channel,futures-channel,futures-io,futures-sink,io,memchr,sink,slab,std"
-//   getrandom-0.2.3 "std"
-//   intrusive-collections-0.9.1 "alloc,default"
-//   itoa-0.4.7
-//   libc-0.2.97 "default,std"
-//   log-0.4.14
-//   memchr-2.4.0 "default,std"
-//   memoffset-0.5.6 "default"
-//   paste-1.0.5
-//   pin-project-lite-0.2.7
-//   pin-utils-0.1.0
-//   pkg-config-0.3.19
-//   ppv-lite86-0.2.10 "simd,std"
-//   proc-macro2-1.0.27 "default,proc-macro"
-//   protobuf-2.24.1
-//   quote-1.0.9 "default,proc-macro"
-//   rand-0.8.4 "alloc,default,getrandom,libc,rand_chacha,rand_hc,std,std_rng"
-//   rand_chacha-0.3.1 "std"
-//   rand_core-0.6.3 "alloc,getrandom,std"
-//   remain-0.2.2
-//   remove_dir_all-0.5.3
-//   ryu-1.0.5
-//   serde-1.0.126 "default,derive,serde_derive,std"
-//   serde_derive-1.0.126 "default"
-//   serde_json-1.0.64 "default,std"
-//   slab-0.4.3 "default,std"
-//   smallvec-1.6.1
-//   syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-//   tempfile-3.2.0
-//   thiserror-1.0.25
-//   thiserror-impl-1.0.25
-//   unicode-xid-0.2.2 "default"
diff --git a/devices/Cargo.toml b/devices/Cargo.toml
index f459ca8..bac657d 100644
--- a/devices/Cargo.toml
+++ b/devices/Cargo.toml
@@ -6,9 +6,10 @@
 
 [features]
 audio = []
+audio_cras = ["libcras"]
 direct = []
 gpu = ["gpu_display","rutabaga_gfx"]
-tpm = ["protos/trunks", "tpm2"]
+tpm = ["tpm2"]
 usb = []
 video-decoder = []
 video-encoder = []
@@ -19,7 +20,7 @@
 
 [dependencies]
 acpi_tables = {path = "../acpi_tables" }
-audio_streams = { path = "../../adhd/audio_streams" } # ignored by ebuild
+audio_streams = { path = "../audio_streams" }
 base = { path = "../base" }
 bit_field = { path = "../bit_field" }
 cros_async = { path = "../cros_async" }
@@ -32,16 +33,15 @@
 hypervisor = { path = "../hypervisor" }
 kvm_sys = { path = "../kvm_sys" }
 libc = "*"
-libchromeos = { path = "../../libchromeos-rs" } # ignored by ebuild
-libcras = { path = "../../adhd/cras/client/libcras" } # ignored by ebuild
+libcras = { path = "../libcras_stub", optional = true }
+libvda = { path = "../libvda", optional = true }
 linux_input_sys = { path = "../linux_input_sys" }
 minijail = { path = "../../minijail/rust/minijail" } # ignored by ebuild
 net_sys = { path = "../net_sys" }
 net_util = { path = "../net_util" }
-p9 = { path = "../../vm_tools/p9" }
+p9 = { path = "../common/p9" }
 power_monitor = { path = "../power_monitor" }
 protos = { path = "../protos", optional = true }
-rand_ish = { path = "../rand_ish" }
 remain = "*"
 resources = { path = "../resources" }
 serde = { version = "1", features = [ "derive" ] }
diff --git a/devices/cargo2android.json b/devices/cargo2android.json
new file mode 100644
index 0000000..76c31af
--- /dev/null
+++ b/devices/cargo2android.json
@@ -0,0 +1,10 @@
+{
+  "add-module-block": "cargo2android_libs.bp",
+  "add_workspace": true,
+  "device": true,
+  "features": "audio,usb",
+  "global_defaults": "crosvm_defaults",
+  "patch": "patches/Android.bp.patch",
+  "run": true,
+  "tests": true
+}
\ No newline at end of file
diff --git a/devices/cargo2android_libs.bp b/devices/cargo2android_libs.bp
new file mode 100644
index 0000000..16bc744
--- /dev/null
+++ b/devices/cargo2android_libs.bp
@@ -0,0 +1 @@
+shared_libs: ["libgfxstream_backend"]
\ No newline at end of file
diff --git a/devices/patches/Android.bp.patch b/devices/patches/Android.bp.patch
new file mode 100644
index 0000000..7124ad6
--- /dev/null
+++ b/devices/patches/Android.bp.patch
@@ -0,0 +1,74 @@
+diff --git a/devices/Android.bp b/devices/Android.bp
+index 09290b81..8580691c 100644
+--- a/devices/Android.bp
++++ b/devices/Android.bp
+@@ -21,6 +21,8 @@ rust_defaults {
+     edition: "2018",
+     features: [
+         "audio",
++        "gfxstream",
++        "gpu",
+         "usb",
+     ],
+     rustlibs: [
+@@ -33,6 +35,7 @@ rust_defaults {
+         "libdisk",
+         "libfuse_rust",
+         "libfutures",
++        "libgpu_display",
+         "libhypervisor",
+         "libkvm_sys",
+         "liblibc",
+@@ -45,6 +48,7 @@ rust_defaults {
+         "libpower_monitor",
+         "librand_ish",
+         "libresources",
++        "librutabaga_gfx",
+         "libserde",
+         "libsmallvec",
+         "libsync_rust",
+@@ -63,6 +67,19 @@ rust_defaults {
+         "libenumn",
+         "libremain",
+     ],
++    target: {
++        host: {
++            shared_libs: ["libvirglrenderer"],
++        },
++        android: {
++            shared_libs: ["libdrm"],
++            static_libs: [
++                "libepoxy",
++                "libgbm",
++                "libvirglrenderer",
++            ],
++        },
++    },
+ }
+ 
+ rust_test_host {
+@@ -90,6 +107,8 @@ rust_library {
+     edition: "2018",
+     features: [
+         "audio",
++        "gfxstream",
++        "gpu",
+         "usb",
+     ],
+     rustlibs: [
+@@ -102,6 +121,7 @@ rust_library {
+         "libdisk",
+         "libfuse_rust",
+         "libfutures",
++        "libgpu_display",
+         "libhypervisor",
+         "libkvm_sys",
+         "liblibc",
+@@ -114,6 +134,7 @@ rust_library {
+         "libpower_monitor",
+         "librand_ish",
+         "libresources",
++        "librutabaga_gfx",
+         "libserde",
+         "libsmallvec",
+         "libsync_rust",
diff --git a/devices/src/bus.rs b/devices/src/bus.rs
index 90360cd..2984937 100644
--- a/devices/src/bus.rs
+++ b/devices/src/bus.rs
@@ -165,13 +165,9 @@
 ///
 /// This doesn't have any restrictions on what kind of device or address space this applies to. The
 /// only restriction is that no two devices can overlap in this address space.
-///
-/// the 'resume_notify_devices' contains the devices which requires to be notified before the system
-/// resume back from S3 suspended state.
 #[derive(Clone)]
 pub struct Bus {
     devices: BTreeMap<BusRange, BusDeviceEntry>,
-    resume_notify_devices: Vec<Arc<Mutex<dyn BusResumeDevice>>>,
     access_id: usize,
 }
 
@@ -180,7 +176,6 @@
     pub fn new() -> Bus {
         Bus {
             devices: BTreeMap::new(),
-            resume_notify_devices: Vec::new(),
             access_id: 0,
         }
     }
@@ -307,19 +302,6 @@
             false
         }
     }
-
-    /// Register `device` for notifications of VM resume from suspend.
-    pub fn notify_on_resume(&mut self, device: Arc<Mutex<dyn BusResumeDevice>>) {
-        self.resume_notify_devices.push(device);
-    }
-
-    /// Call `notify_resume` to notify the device that suspend resume is imminent.
-    pub fn notify_resume(&mut self) {
-        let devices = self.resume_notify_devices.clone();
-        for dev in devices {
-            dev.lock().resume_imminent();
-        }
-    }
 }
 
 #[cfg(test)]
diff --git a/devices/src/lib.rs b/devices/src/lib.rs
index 1ebd1b1..589f1cc 100644
--- a/devices/src/lib.rs
+++ b/devices/src/lib.rs
@@ -23,7 +23,7 @@
 pub mod acpi;
 pub mod bat;
 mod serial;
-mod serial_device;
+pub mod serial_device;
 #[cfg(feature = "usb")]
 pub mod usb;
 #[cfg(feature = "usb")]
diff --git a/devices/src/pci/ac97.rs b/devices/src/pci/ac97.rs
index 3b396ff..eaf6330 100644
--- a/devices/src/pci/ac97.rs
+++ b/devices/src/pci/ac97.rs
@@ -10,6 +10,7 @@
 
 use audio_streams::shm_streams::{NullShmStreamSource, ShmStreamSource};
 use base::{error, Event, RawDescriptor};
+#[cfg(feature = "audio_cras")]
 use libcras::{CrasClient, CrasClientType, CrasSocketType, CrasSysError};
 use resources::{Alloc, MmioType, SystemAllocator};
 use vm_memory::GuestMemory;
@@ -39,6 +40,7 @@
 #[derive(Debug, Clone)]
 pub enum Ac97Backend {
     NULL,
+    #[cfg(feature = "audio_cras")]
     CRAS,
     VIOS,
 }
@@ -71,6 +73,7 @@
     type Err = Ac97Error;
     fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
         match s {
+            #[cfg(feature = "audio_cras")]
             "cras" => Ok(Ac97Backend::CRAS),
             "vios" => Ok(Ac97Backend::VIOS),
             "null" => Ok(Ac97Backend::NULL),
@@ -85,6 +88,7 @@
     pub backend: Ac97Backend,
     pub capture: bool,
     pub vios_server_path: Option<PathBuf>,
+    #[cfg(feature = "audio_cras")]
     client_type: Option<CrasClientType>,
 }
 
@@ -92,6 +96,7 @@
     /// Set CRAS client type by given client type string.
     ///
     /// `client_type` - The client type string.
+    #[cfg(feature = "audio_cras")]
     pub fn set_client_type(&mut self, client_type: &str) -> std::result::Result<(), CrasSysError> {
         self.client_type = Some(client_type.parse()?);
         Ok(())
@@ -145,6 +150,7 @@
     /// to create `Ac97Dev` with the given back-end, it'll fallback to the null audio device.
     pub fn try_new(mem: GuestMemory, param: Ac97Parameters) -> Result<Self> {
         match param.backend {
+            #[cfg(feature = "audio_cras")]
             Ac97Backend::CRAS => Self::create_cras_audio_device(param, mem.clone()).or_else(|e| {
                 error!(
                     "Ac97Dev: create_cras_audio_device: {}. Fallback to null audio device",
@@ -160,12 +166,14 @@
     /// Return the minijail policy file path for the current Ac97Dev.
     pub fn minijail_policy(&self) -> &'static str {
         match self.backend {
+            #[cfg(feature = "audio_cras")]
             Ac97Backend::CRAS => "cras_audio_device",
             Ac97Backend::VIOS => "vios_audio_device",
             Ac97Backend::NULL => "null_audio_device",
         }
     }
 
+    #[cfg(feature = "audio_cras")]
     fn create_cras_audio_device(params: Ac97Parameters, mem: GuestMemory) -> Result<Self> {
         let mut server = Box::new(
             CrasClient::with_type(CrasSocketType::Unified)
diff --git a/devices/src/pci/msix.rs b/devices/src/pci/msix.rs
index 2852aef..3b065cc 100644
--- a/devices/src/pci/msix.rs
+++ b/devices/src/pci/msix.rs
@@ -100,7 +100,8 @@
         let mut table_entries: Vec<MsixTableEntry> = Vec::new();
         table_entries.resize_with(msix_vectors as usize, Default::default);
         let mut pba_entries: Vec<u64> = Vec::new();
-        let num_pba_entries: usize = ((msix_vectors as usize) / BITS_PER_PBA_ENTRY) + 1;
+        let num_pba_entries: usize =
+            ((msix_vectors as usize) + BITS_PER_PBA_ENTRY - 1) / BITS_PER_PBA_ENTRY;
         pba_entries.resize_with(num_pba_entries, Default::default);
 
         MsixConfig {
@@ -556,6 +557,11 @@
     fn id(&self) -> PciCapabilityID {
         PciCapabilityID::MSIX
     }
+
+    fn writable_bits(&self) -> Vec<u32> {
+        // Only msg_ctl[15:14] is writable
+        vec![0x3000_0000, 0, 0]
+    }
 }
 
 impl MsixCap {
diff --git a/devices/src/pci/pci_configuration.rs b/devices/src/pci/pci_configuration.rs
index 4cff20f..89ef933 100644
--- a/devices/src/pci/pci_configuration.rs
+++ b/devices/src/pci/pci_configuration.rs
@@ -187,6 +187,7 @@
 pub trait PciCapability {
     fn bytes(&self) -> &[u8];
     fn id(&self) -> PciCapabilityID;
+    fn writable_bits(&self) -> Vec<u32>;
 }
 
 /// Contains the configuration space of a PCI node.
@@ -320,14 +321,20 @@
             PciHeaderType::Device => {
                 registers[3] = 0x0000_0000; // Header type 0 (device)
                 writable_bits[15] = 0x0000_00ff; // Interrupt line (r/w)
+                registers[11] = u32::from(subsystem_id) << 16 | u32::from(subsystem_vendor_id);
             }
             PciHeaderType::Bridge => {
                 registers[3] = 0x0001_0000; // Header type 1 (bridge)
-                writable_bits[9] = 0xfff0_fff0; // Memory base and limit
+                writable_bits[6] = 0x00ff_ffff; // Primary/secondary/subordinate bus number,
+                                                // secondary latency timer
+                writable_bits[7] = 0xf900_0000; // IO base and limit, secondary status,
+                writable_bits[8] = 0xfff0_fff0; // Memory base and limit
+                writable_bits[9] = 0xfff0_fff0; // Prefetchable base and limit
+                writable_bits[10] = 0xffff_ffff; // Prefetchable base upper 32 bits
+                writable_bits[11] = 0xffff_ffff; // Prefetchable limit upper 32 bits
                 writable_bits[15] = 0xffff_00ff; // Bridge control (r/w), interrupt line (r/w)
             }
         };
-        registers[11] = u32::from(subsystem_id) << 16 | u32::from(subsystem_vendor_id);
 
         PciConfiguration {
             registers,
@@ -600,6 +607,10 @@
         for (i, byte) in cap_data.bytes().iter().enumerate().skip(2) {
             self.write_byte_internal(cap_offset + i, *byte, false);
         }
+        let reg_idx = cap_offset / 4;
+        for (i, dword) in cap_data.writable_bits().iter().enumerate() {
+            self.writable_bits[reg_idx + i] = *dword;
+        }
         self.last_capability = Some((cap_offset, total_len));
         Ok(cap_offset)
     }
@@ -695,6 +706,10 @@
         fn id(&self) -> PciCapabilityID {
             PciCapabilityID::VendorSpecific
         }
+
+        fn writable_bits(&self) -> Vec<u32> {
+            vec![0u32; 1]
+        }
     }
 
     #[test]
diff --git a/devices/src/pci/pci_device.rs b/devices/src/pci/pci_device.rs
index a316fcf..1c736ed 100644
--- a/devices/src/pci/pci_device.rs
+++ b/devices/src/pci/pci_device.rs
@@ -4,6 +4,8 @@
 
 use std::fmt::{self, Display};
 
+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+use acpi_tables::sdt::SDT;
 use base::{Event, RawDescriptor};
 use hypervisor::Datamatch;
 use resources::{Error as SystemAllocatorFaliure, SystemAllocator};
@@ -27,7 +29,7 @@
     /// Registering an IO BAR failed.
     IoRegistrationFailed(u64, pci_configuration::Error),
     /// Create cras client failed.
-    #[cfg(feature = "audio")]
+    #[cfg(all(feature = "audio", feature = "audio_cras"))]
     CreateCrasClientFailed(libcras::Error),
     /// Create VioS client failed.
     #[cfg(feature = "audio")]
@@ -36,6 +38,12 @@
     PciAllocationFailed,
     /// PCI Address is not allocated.
     PciAddressMissing,
+    /// MSIX Allocator encounters size of zero
+    MsixAllocatorSizeZero,
+    /// MSIX Allocator encounters overflow
+    MsixAllocatorOverflow { base: u64, size: u64 },
+    /// MSIX Allocator encounters out-of-space
+    MsixAllocatorOutOfSpace,
 }
 pub type Result<T> = std::result::Result<T, Error>;
 
@@ -45,7 +53,7 @@
 
         match self {
             CapabilitiesSetup(e) => write!(f, "failed to add capability {}", e),
-            #[cfg(feature = "audio")]
+            #[cfg(all(feature = "audio", feature = "audio_cras"))]
             CreateCrasClientFailed(e) => write!(f, "failed to create CRAS Client: {}", e),
             #[cfg(feature = "audio")]
             CreateViosClientFailed(e) => write!(f, "failed to create VioS Client: {}", e),
@@ -59,6 +67,13 @@
             }
             PciAllocationFailed => write!(f, "failed to allocate PCI address"),
             PciAddressMissing => write!(f, "PCI address is not allocated"),
+            MsixAllocatorSizeZero => write!(f, "Size of zero detected in MSIX Allocator"),
+            MsixAllocatorOverflow { base, size } => write!(
+                f,
+                "base={} + size={} overflows in MSIX Allocator",
+                base, size
+            ),
+            MsixAllocatorOutOfSpace => write!(f, "Out-of-space detected in MSIX Allocator"),
         }
     }
 }
@@ -132,6 +147,11 @@
     fn write_bar(&mut self, addr: u64, data: &[u8]);
     /// Invoked when the device is sandboxed.
     fn on_device_sandboxed(&mut self) {}
+
+    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+    fn generate_acpi(&mut self, sdts: Vec<SDT>) -> Option<Vec<SDT>> {
+        Some(sdts)
+    }
 }
 
 impl<T: PciDevice> BusDevice for T {
@@ -240,6 +260,11 @@
     fn on_device_sandboxed(&mut self) {
         (**self).on_device_sandboxed()
     }
+
+    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+    fn generate_acpi(&mut self, sdts: Vec<SDT>) -> Option<Vec<SDT>> {
+        (**self).generate_acpi(sdts)
+    }
 }
 
 #[cfg(test)]
diff --git a/devices/src/pci/pci_root.rs b/devices/src/pci/pci_root.rs
index de451f7..242ffda 100644
--- a/devices/src/pci/pci_root.rs
+++ b/devices/src/pci/pci_root.rs
@@ -111,6 +111,11 @@
             | ((Self::REGISTER_MASK & register as u32) << Self::REGISTER_OFFSET)
     }
 
+    /// Convert B:D:F PCI address to unsigned 32 bit integer
+    pub fn to_u32(&self) -> u32 {
+        self.to_config_address(0) >> Self::FUNCTION_OFFSET
+    }
+
     /// Returns true if the address points to PCI root host-bridge.
     fn is_root(&self) -> bool {
         matches!(
diff --git a/devices/src/pci/vfio_pci.rs b/devices/src/pci/vfio_pci.rs
index 70f8cdf..cfc4925 100644
--- a/devices/src/pci/vfio_pci.rs
+++ b/devices/src/pci/vfio_pci.rs
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+use std::cmp::{max, min};
+use std::collections::BTreeSet;
 use std::sync::Arc;
 use std::u32;
 
@@ -307,29 +309,37 @@
     table_size: u16,
     table_pci_bar: u32,
     table_offset: u64,
+    table_size_bytes: u64,
     pba_pci_bar: u32,
     pba_offset: u64,
+    pba_size_bytes: u64,
 }
 
 impl VfioMsixCap {
     fn new(config: &VfioPciConfig, msix_cap_start: u32, vm_socket_irq: Tube) -> Self {
         let msix_ctl = config.read_config_word(msix_cap_start + PCI_MSIX_FLAGS);
-        let table_size = (msix_ctl & PCI_MSIX_FLAGS_QSIZE) + 1;
+        let table_size = (msix_ctl & PCI_MSIX_FLAGS_QSIZE) as u64 + 1;
         let table = config.read_config_dword(msix_cap_start + PCI_MSIX_TABLE);
         let table_pci_bar = table & PCI_MSIX_TABLE_BIR;
         let table_offset = (table & PCI_MSIX_TABLE_OFFSET) as u64;
+        let table_size_bytes = table_size * MSIX_TABLE_ENTRIES_MODULO;
         let pba = config.read_config_dword(msix_cap_start + PCI_MSIX_PBA);
         let pba_pci_bar = pba & PCI_MSIX_PBA_BIR;
         let pba_offset = (pba & PCI_MSIX_PBA_OFFSET) as u64;
+        let pba_size_bytes = ((table_size + BITS_PER_PBA_ENTRY as u64 - 1)
+            / BITS_PER_PBA_ENTRY as u64)
+            * MSIX_PBA_ENTRIES_MODULO;
 
         VfioMsixCap {
-            config: MsixConfig::new(table_size, vm_socket_irq),
+            config: MsixConfig::new(table_size as u16, vm_socket_irq),
             offset: msix_cap_start,
-            table_size,
+            table_size: table_size as u16,
             table_pci_bar,
             table_offset,
+            table_size_bytes,
             pba_pci_bar,
             pba_offset,
+            pba_size_bytes,
         }
     }
 
@@ -362,10 +372,17 @@
     }
 
     fn is_msix_table(&self, bar_index: u32, offset: u64) -> bool {
-        let table_size: u64 = (self.table_size * (MSIX_TABLE_ENTRIES_MODULO as u16)).into();
         bar_index == self.table_pci_bar
             && offset >= self.table_offset
-            && offset < self.table_offset + table_size
+            && offset < self.table_offset + self.table_size_bytes
+    }
+
+    fn get_msix_table(&self, bar_index: u32) -> Option<(u64, u64)> {
+        if bar_index == self.table_pci_bar {
+            Some((self.table_offset, self.table_size_bytes))
+        } else {
+            None
+        }
     }
 
     fn read_table(&self, offset: u64, data: &mut [u8]) {
@@ -379,12 +396,17 @@
     }
 
     fn is_msix_pba(&self, bar_index: u32, offset: u64) -> bool {
-        let pba_size: u64 = (((self.table_size + BITS_PER_PBA_ENTRY as u16 - 1)
-            / BITS_PER_PBA_ENTRY as u16)
-            * MSIX_PBA_ENTRIES_MODULO as u16) as u64;
         bar_index == self.pba_pci_bar
             && offset >= self.pba_offset
-            && offset < self.pba_offset + pba_size
+            && offset < self.pba_offset + self.pba_size_bytes
+    }
+
+    fn get_msix_pba(&self, bar_index: u32) -> Option<(u64, u64)> {
+        if bar_index == self.pba_pci_bar {
+            Some((self.pba_offset, self.pba_size_bytes))
+        } else {
+            None
+        }
     }
 
     fn read_pba(&self, offset: u64, data: &mut [u8]) {
@@ -397,10 +419,6 @@
         self.config.write_pba_entries(offset, data);
     }
 
-    fn is_msix_bar(&self, bar_index: u32) -> bool {
-        bar_index == self.table_pci_bar || bar_index == self.pba_pci_bar
-    }
-
     fn get_msix_irqfds(&self) -> Option<Vec<&Event>> {
         let mut irqfds = Vec::new();
 
@@ -417,6 +435,61 @@
     }
 }
 
+struct VfioMsixAllocator {
+    // memory regions unoccupied by MSIX registers
+    // stores sets of (start, end) tuples, where `end` is the address of the
+    // last byte in the region
+    regions: BTreeSet<(u64, u64)>,
+}
+
+impl VfioMsixAllocator {
+    // Creates a new `VfioMsixAllocator` for managing a range of MSIX addresses.
+    // Can return `Err` if `base` + `size` overflows a u64.
+    //
+    // * `base` - The starting address of the range to manage.
+    // * `size` - The size of the address range in bytes.
+    fn new(base: u64, size: u64) -> Result<Self, PciDeviceError> {
+        if size == 0 {
+            return Err(PciDeviceError::MsixAllocatorSizeZero);
+        }
+        let end = base
+            .checked_add(size - 1)
+            .ok_or(PciDeviceError::MsixAllocatorOverflow { base, size })?;
+        let mut regions = BTreeSet::new();
+        regions.insert((base, end));
+        Ok(VfioMsixAllocator { regions })
+    }
+
+    // Allocates a range of addresses from the managed region with a required location.
+    // Returns OutOfSpace if requested range is not available (e.g. already allocated).
+    fn allocate_at(&mut self, start: u64, size: u64) -> Result<(), PciDeviceError> {
+        if size == 0 {
+            return Err(PciDeviceError::MsixAllocatorSizeZero);
+        }
+        let end = start
+            .checked_add(size - 1)
+            .ok_or(PciDeviceError::MsixAllocatorOutOfSpace)?;
+        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));
+                }
+                Ok(())
+            }
+            None => Err(PciDeviceError::MsixAllocatorOutOfSpace),
+        }
+    }
+}
+
 enum DeviceData {
     IntelGfxData { opregion_index: u32 },
 }
@@ -652,18 +725,77 @@
         self.enable_intx();
     }
 
+    fn add_bar_mmap_msix(
+        &self,
+        bar_index: u32,
+        bar_mmaps: Vec<vfio_region_sparse_mmap_area>,
+    ) -> Vec<vfio_region_sparse_mmap_area> {
+        let msix_cap = &self.msix_cap.as_ref().unwrap();
+        let mut msix_mmaps: Vec<(u64, u64)> = Vec::new();
+
+        if let Some(t) = msix_cap.get_msix_table(bar_index) {
+            msix_mmaps.push(t);
+        }
+        if let Some(p) = msix_cap.get_msix_pba(bar_index) {
+            msix_mmaps.push(p);
+        }
+
+        if msix_mmaps.is_empty() {
+            return bar_mmaps;
+        }
+
+        let mut mmaps: Vec<vfio_region_sparse_mmap_area> = Vec::with_capacity(bar_mmaps.len());
+        let pgmask = (pagesize() as u64) - 1;
+
+        for mmap in bar_mmaps.iter() {
+            let mmap_offset = mmap.offset as u64;
+            let mmap_size = mmap.size as u64;
+            let mut to_mmap = match VfioMsixAllocator::new(mmap_offset, mmap_size) {
+                Ok(a) => a,
+                Err(e) => {
+                    error!("add_bar_mmap_msix failed: {}", e);
+                    mmaps.clear();
+                    return mmaps;
+                }
+            };
+
+            // table/pba offsets are qword-aligned - align to page size
+            for &(msix_offset, msix_size) in msix_mmaps.iter() {
+                if msix_offset >= mmap_offset && msix_offset < mmap_offset + mmap_size {
+                    let begin = max(msix_offset, mmap_offset) & !pgmask;
+                    let end =
+                        (min(msix_offset + msix_size, mmap_offset + mmap_size) + pgmask) & !pgmask;
+                    if end > begin {
+                        if let Err(e) = to_mmap.allocate_at(begin, end - begin) {
+                            error!("add_bar_mmap_msix failed: {}", e);
+                            mmaps.clear();
+                            return mmaps;
+                        }
+                    }
+                }
+            }
+
+            for mmap in to_mmap.regions {
+                mmaps.push(vfio_region_sparse_mmap_area {
+                    offset: mmap.0,
+                    size: mmap.1 - mmap.0 + 1,
+                });
+            }
+        }
+
+        mmaps
+    }
+
     fn add_bar_mmap(&self, index: u32, bar_addr: u64) -> Vec<MemoryMapping> {
         let mut mem_map: Vec<MemoryMapping> = Vec::new();
         if self.device.get_region_flags(index) & VFIO_REGION_INFO_FLAG_MMAP != 0 {
             // the bar storing msix table and pba couldn't mmap.
             // these bars should be trapped, so that msix could be emulated.
-            if let Some(msix_cap) = &self.msix_cap {
-                if msix_cap.is_msix_bar(index) {
-                    return mem_map;
-                }
-            }
+            let mut mmaps = self.device.get_region_mmap(index);
 
-            let mmaps = self.device.get_region_mmap(index);
+            if self.msix_cap.is_some() {
+                mmaps = self.add_bar_mmap_msix(index, mmaps);
+            }
             if mmaps.is_empty() {
                 return mem_map;
             }
@@ -715,7 +847,8 @@
                         // the host pointer is correct and valid guaranteed by MemoryMapping interface.
                         // The size will be extened to page size aligned if it is not which is also
                         // safe because VFIO actually maps the BAR with page size aligned size.
-                        match unsafe { self.device.vfio_dma_map(guest_map_start, size, host) } {
+                        match unsafe { self.device.vfio_dma_map(guest_map_start, size, host, true) }
+                        {
                             Ok(_) => mem_map.push(mmap),
                             Err(e) => {
                                 error!(
diff --git a/devices/src/usb/host_backend/host_device.rs b/devices/src/usb/host_backend/host_device.rs
index 708f0e2..3b5cee0 100644
--- a/devices/src/usb/host_backend/host_device.rs
+++ b/devices/src/usb/host_backend/host_device.rs
@@ -273,10 +273,10 @@
 
     fn handle_control_transfer(&mut self, transfer: XhciTransfer) -> Result<()> {
         let xhci_transfer = Arc::new(transfer);
-        match xhci_transfer
+        let transfer_type = xhci_transfer
             .get_transfer_type()
-            .map_err(Error::GetXhciTransferType)?
-        {
+            .map_err(Error::GetXhciTransferType)?;
+        match transfer_type {
             XhciTransferType::SetupStage(setup) => {
                 if self.ctl_ep_state != ControlEndpointState::SetupStage {
                     error!("Control endpoint is in an inconsistant state");
@@ -320,7 +320,10 @@
             }
             _ => {
                 // Non control transfer should not be handled in this function.
-                error!("Non control (could be noop) transfer sent to control endpoint.");
+                error!(
+                    "Non control {} transfer sent to control endpoint.",
+                    transfer_type,
+                );
                 xhci_transfer
                     .on_transfer_complete(&TransferStatus::Completed, 0)
                     .map_err(Error::TransferComplete)?;
diff --git a/devices/src/usb/xhci/xhci_transfer.rs b/devices/src/usb/xhci/xhci_transfer.rs
index a008684..085f79f 100644
--- a/devices/src/usb/xhci/xhci_transfer.rs
+++ b/devices/src/usb/xhci/xhci_transfer.rs
@@ -108,6 +108,21 @@
     Noop,
 }
 
+impl Display for XhciTransferType {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        use self::XhciTransferType::*;
+
+        match self {
+            Normal(_) => write!(f, "Normal"),
+            SetupStage(_) => write!(f, "SetupStage"),
+            DataStage(_) => write!(f, "DataStage"),
+            StatusStage => write!(f, "StatusStage"),
+            Isochronous(_) => write!(f, "Isochronous"),
+            Noop => write!(f, "Noop"),
+        }
+    }
+}
+
 impl XhciTransferType {
     /// Analyze transfer descriptor and return transfer type.
     pub fn new(mem: GuestMemory, td: TransferDescriptor) -> Result<XhciTransferType> {
diff --git a/devices/src/vfio.rs b/devices/src/vfio.rs
index 48f09d7..f0c68d5 100644
--- a/devices/src/vfio.rs
+++ b/devices/src/vfio.rs
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 use data_model::vec_with_array_field;
+use std::cell::RefCell;
 use std::collections::HashMap;
 use std::ffi::CString;
 use std::fmt;
@@ -19,7 +20,6 @@
     ioctl, ioctl_with_mut_ref, ioctl_with_ptr, ioctl_with_ref, ioctl_with_val, warn,
     AsRawDescriptor, Error, Event, FromRawDescriptor, RawDescriptor, SafeDescriptor,
 };
-use hypervisor::{DeviceKind, Vm};
 use vm_memory::GuestMemory;
 
 use vfio_sys::*;
@@ -29,19 +29,20 @@
     OpenContainer(io::Error),
     OpenGroup(io::Error),
     GetGroupStatus(Error),
+    BorrowVfioContainer,
     GroupViable,
     VfioApiVersion,
     VfioType1V2,
     GroupSetContainer(Error),
     ContainerSetIOMMU(Error),
     GroupGetDeviceFD(Error),
-    CreateVfioKvmDevice(Error),
     KvmSetDeviceAttr(Error),
     VfioDeviceGetInfo(Error),
     VfioDeviceGetRegionInfo(Error),
     InvalidPath,
     IommuDmaMap(Error),
     IommuDmaUnmap(Error),
+    IommuGetInfo(Error),
     VfioIrqEnable(Error),
     VfioIrqDisable(Error),
     VfioIrqUnmask(Error),
@@ -54,19 +55,20 @@
             VfioError::OpenContainer(e) => write!(f, "failed to open /dev/vfio/vfio container: {}", e),
             VfioError::OpenGroup(e) => write!(f, "failed to open /dev/vfio/$group_num group: {}", e),
             VfioError::GetGroupStatus(e) => write!(f, "failed to get Group Status: {}", e),
+            VfioError::BorrowVfioContainer => write!(f, "failed to borrow global vfio container"),
             VfioError::GroupViable => write!(f, "group is inviable"),
             VfioError::VfioApiVersion => write!(f, "vfio API version doesn't match with VFIO_API_VERSION defined in vfio_sys/srv/vfio.rs"),
             VfioError::VfioType1V2 => write!(f, "container dones't support VfioType1V2 IOMMU driver type"),
             VfioError::GroupSetContainer(e) => write!(f, "failed to add vfio group into vfio container: {}", e),
             VfioError::ContainerSetIOMMU(e) => write!(f, "failed to set container's IOMMU driver type as VfioType1V2: {}", e),
             VfioError::GroupGetDeviceFD(e) => write!(f, "failed to get vfio device fd: {}", e),
-            VfioError::CreateVfioKvmDevice(e) => write!(f, "failed to create KVM vfio device: {}", e),
             VfioError::KvmSetDeviceAttr(e) => write!(f, "failed to set KVM vfio device's attribute: {}", e),
             VfioError::VfioDeviceGetInfo(e) => write!(f, "failed to get vfio device's info or info doesn't match: {}", e),
             VfioError::VfioDeviceGetRegionInfo(e) => write!(f, "failed to get vfio device's region info: {}", e),
             VfioError::InvalidPath => write!(f,"invalid file path"),
             VfioError::IommuDmaMap(e) => write!(f, "failed to add guest memory map into iommu table: {}", e),
             VfioError::IommuDmaUnmap(e) => write!(f, "failed to remove guest memory map from iommu table: {}", e),
+            VfioError::IommuGetInfo(e) => write!(f, "failed to get IOMMU info from host: {}", e),
             VfioError::VfioIrqEnable(e) => write!(f, "failed to enable vfio deviece's irq: {}", e),
             VfioError::VfioIrqDisable(e) => write!(f, "failed to disable vfio deviece's irq: {}", e),
             VfioError::VfioIrqUnmask(e) => write!(f, "failed to unmask vfio deviece's irq: {}", e),
@@ -82,7 +84,6 @@
 /// VfioContainer contain multi VfioGroup, and delegate an IOMMU domain table
 pub struct VfioContainer {
     container: File,
-    kvm_vfio_dev: Option<SafeDescriptor>,
     groups: HashMap<u32, Arc<VfioGroup>>,
 }
 
@@ -104,11 +105,14 @@
 
         Ok(VfioContainer {
             container,
-            kvm_vfio_dev: None,
             groups: HashMap::new(),
         })
     }
 
+    fn is_group_set(&self, group_id: u32) -> bool {
+        self.groups.get(&group_id).is_some()
+    }
+
     fn check_extension(&self, val: u32) -> bool {
         if val != VFIO_TYPE1_IOMMU && val != VFIO_TYPE1v2_IOMMU {
             panic!("IOMMU type error");
@@ -128,15 +132,25 @@
         unsafe { ioctl_with_val(self, VFIO_SET_IOMMU(), val.into()) }
     }
 
-    unsafe fn vfio_dma_map(&self, iova: u64, size: u64, user_addr: u64) -> Result<(), VfioError> {
-        let dma_map = vfio_iommu_type1_dma_map {
+    pub unsafe fn vfio_dma_map(
+        &self,
+        iova: u64,
+        size: u64,
+        user_addr: u64,
+        write_en: bool,
+    ) -> Result<(), VfioError> {
+        let mut dma_map = vfio_iommu_type1_dma_map {
             argsz: mem::size_of::<vfio_iommu_type1_dma_map>() as u32,
-            flags: VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE,
+            flags: VFIO_DMA_MAP_FLAG_READ,
             vaddr: user_addr,
             iova,
             size,
         };
 
+        if write_en {
+            dma_map.flags |= VFIO_DMA_MAP_FLAG_WRITE;
+        }
+
         let ret = ioctl_with_ref(self, VFIO_IOMMU_MAP_DMA(), &dma_map);
         if ret != 0 {
             return Err(VfioError::IommuDmaMap(get_error()));
@@ -145,7 +159,7 @@
         Ok(())
     }
 
-    fn vfio_dma_unmap(&self, iova: u64, size: u64) -> Result<(), VfioError> {
+    pub fn vfio_dma_unmap(&self, iova: u64, size: u64) -> Result<(), VfioError> {
         let mut dma_unmap = vfio_iommu_type1_dma_unmap {
             argsz: mem::size_of::<vfio_iommu_type1_dma_unmap>() as u32,
             flags: 0,
@@ -163,7 +177,24 @@
         Ok(())
     }
 
-    fn init(&mut self, vm: &impl Vm, guest_mem: &GuestMemory) -> Result<(), VfioError> {
+    pub fn vfio_get_iommu_page_size_mask(&self) -> Result<u64, VfioError> {
+        let mut iommu_info = vfio_iommu_type1_info {
+            argsz: mem::size_of::<vfio_iommu_type1_info>() as u32,
+            flags: 0,
+            iova_pgsizes: 0,
+        };
+
+        // Safe as file is vfio container, iommu_info has valid values,
+        // and we check the return value
+        let ret = unsafe { ioctl_with_mut_ref(self, VFIO_IOMMU_GET_INFO(), &mut iommu_info) };
+        if ret != 0 || (iommu_info.flags & VFIO_IOMMU_INFO_PGSIZES) == 0 {
+            return Err(VfioError::IommuGetInfo(get_error()));
+        }
+
+        Ok(iommu_info.iova_pgsizes)
+    }
+
+    fn init(&mut self, guest_mem: &GuestMemory, iommu_enabled: bool) -> Result<(), VfioError> {
         if !self.check_extension(VFIO_TYPE1v2_IOMMU) {
             return Err(VfioError::VfioType1V2);
         }
@@ -174,15 +205,12 @@
 
         // Add all guest memory regions into vfio container's iommu table,
         // then vfio kernel driver could access guest memory from gfn
-        guest_mem.with_regions(|_index, guest_addr, size, host_addr, _mmap, _fd_offset| {
-            // Safe because the guest regions are guaranteed not to overlap
-            unsafe { self.vfio_dma_map(guest_addr.0, size as u64, host_addr as u64) }
-        })?;
-
-        let vfio_descriptor = vm
-            .create_device(DeviceKind::Vfio)
-            .map_err(VfioError::CreateVfioKvmDevice)?;
-        self.kvm_vfio_dev = Some(vfio_descriptor);
+        if !iommu_enabled {
+            guest_mem.with_regions(|_index, guest_addr, size, host_addr, _mmap, _fd_offset| {
+                // Safe because the guest regions are guaranteed not to overlap
+                unsafe { self.vfio_dma_map(guest_addr.0, size as u64, host_addr as u64, true) }
+            })?;
+        }
 
         Ok(())
     }
@@ -190,8 +218,9 @@
     fn get_group(
         &mut self,
         id: u32,
-        vm: &impl Vm,
         guest_mem: &GuestMemory,
+        kvm_vfio_file: &SafeDescriptor,
+        iommu_enabled: bool,
     ) -> Result<Arc<VfioGroup>, VfioError> {
         match self.groups.get(&id) {
             Some(group) => Ok(group.clone()),
@@ -201,13 +230,9 @@
                 if self.groups.is_empty() {
                     // Before the first group is added into container, do once cotainer
                     // initialize for a vm
-                    self.init(vm, guest_mem)?;
+                    self.init(guest_mem, iommu_enabled)?;
                 }
 
-                let kvm_vfio_file = self
-                    .kvm_vfio_dev
-                    .as_ref()
-                    .expect("kvm vfio device should exist");
                 group.kvm_device_add_group(kvm_vfio_file)?;
 
                 self.groups.insert(id, group.clone());
@@ -272,6 +297,20 @@
         Ok(VfioGroup { group: group_file })
     }
 
+    fn get_group_id(sysfspath: &Path) -> Result<u32, VfioError> {
+        let mut uuid_path = PathBuf::new();
+        uuid_path.push(sysfspath);
+        uuid_path.push("iommu_group");
+        let group_path = uuid_path.read_link().map_err(|_| VfioError::InvalidPath)?;
+        let group_osstr = group_path.file_name().ok_or(VfioError::InvalidPath)?;
+        let group_str = group_osstr.to_str().ok_or(VfioError::InvalidPath)?;
+        let group_id = group_str
+            .parse::<u32>()
+            .map_err(|_| VfioError::InvalidPath)?;
+
+        Ok(group_id)
+    }
+
     fn kvm_device_add_group(&self, kvm_vfio_file: &SafeDescriptor) -> Result<(), VfioError> {
         let group_descriptor = self.as_raw_descriptor();
         let group_descriptor_ptr = &group_descriptor as *const i32;
@@ -318,6 +357,86 @@
     }
 }
 
+/// A helper trait for managing VFIO setup
+pub trait VfioCommonTrait: Send + Sync {
+    /// The single place to create a VFIO container for a PCI endpoint.
+    ///
+    /// The policy to determine whether an individual or a shared VFIO container
+    /// will be created for this device is governed by the physical PCI topology,
+    /// and the argument iommu_enabled.
+    ///
+    ///  # Arguments
+    ///
+    ///  * `sysfspath` - the path to the PCI device, e.g. /sys/bus/pci/devices/0000:02:00.0
+    ///  * `iommu_enabled` - whether virtio IOMMU is enabled on this device
+    fn vfio_get_container(
+        sysfspath: &Path,
+        iommu_enabled: bool,
+    ) -> Result<Arc<Mutex<VfioContainer>>, VfioError>;
+}
+
+thread_local! {
+
+    // One VFIO container is shared by all VFIO devices that don't
+    // attach to the virtio IOMMU device
+    static NO_IOMMU_CONTAINER: RefCell<Option<Arc<Mutex<VfioContainer>>>> = RefCell::new(None);
+
+    // For IOMMU enabled devices, all VFIO groups that share the same IOVA space
+    // are managed by one VFIO container
+    static IOMMU_CONTAINERS: RefCell<Option<Vec<Arc<Mutex<VfioContainer>>>>> = RefCell::new(Some(Default::default()));
+}
+
+pub struct VfioCommonSetup;
+
+impl VfioCommonTrait for VfioCommonSetup {
+    fn vfio_get_container(
+        sysfspath: &Path,
+        iommu_enabled: bool,
+    ) -> Result<Arc<Mutex<VfioContainer>>, VfioError> {
+        match iommu_enabled {
+            false => {
+                // One VFIO container is used for all IOMMU disabled groups
+                NO_IOMMU_CONTAINER.with(|v| {
+                    if v.borrow().is_some() {
+                        if let Some(ref container) = *v.borrow() {
+                            Ok(container.clone())
+                        } else {
+                            Err(VfioError::BorrowVfioContainer)
+                        }
+                    } else {
+                        let container = Arc::new(Mutex::new(VfioContainer::new()?));
+                        *v.borrow_mut() = Some(container.clone());
+                        Ok(container)
+                    }
+                })
+            }
+            true => {
+                let group_id = VfioGroup::get_group_id(sysfspath)?;
+
+                // One VFIO container is used for all devices belong to one VFIO group
+                IOMMU_CONTAINERS.with(|v| {
+                    if let Some(ref mut containers) = *v.borrow_mut() {
+                        let container = containers
+                            .iter()
+                            .find(|container| container.lock().is_group_set(group_id));
+
+                        match container {
+                            None => {
+                                let container = Arc::new(Mutex::new(VfioContainer::new()?));
+                                containers.push(container.clone());
+                                Ok(container)
+                            }
+                            Some(container) => Ok(container.clone()),
+                        }
+                    } else {
+                        Err(VfioError::BorrowVfioContainer)
+                    }
+                })
+            }
+        }
+    }
+}
+
 /// Vfio Irq type used to enable/disable/mask/unmask vfio irq
 pub enum VfioIrqType {
     Intx,
@@ -351,23 +470,20 @@
     /// Create a new vfio device, then guest read/write on this device could be
     /// transfered into kernel vfio.
     /// sysfspath specify the vfio device path in sys file system.
+    /// kvm_vfio_file specify a valid file descriptor returned from KVM_CREATE_DEVICE
+    /// with type KVM_DEV_TYPE_VFIO
     pub fn new(
         sysfspath: &Path,
-        vm: &impl Vm,
         guest_mem: &GuestMemory,
+        kvm_vfio_file: &SafeDescriptor,
         container: Arc<Mutex<VfioContainer>>,
+        iommu_enabled: bool,
     ) -> Result<Self, VfioError> {
-        let mut uuid_path = PathBuf::new();
-        uuid_path.push(sysfspath);
-        uuid_path.push("iommu_group");
-        let group_path = uuid_path.read_link().map_err(|_| VfioError::InvalidPath)?;
-        let group_osstr = group_path.file_name().ok_or(VfioError::InvalidPath)?;
-        let group_str = group_osstr.to_str().ok_or(VfioError::InvalidPath)?;
-        let group_id = group_str
-            .parse::<u32>()
-            .map_err(|_| VfioError::InvalidPath)?;
-
-        let group = container.lock().get_group(group_id, vm, guest_mem)?;
+        let group_id = VfioGroup::get_group_id(sysfspath)?;
+        let group =
+            container
+                .lock()
+                .get_group(group_id, guest_mem, kvm_vfio_file, iommu_enabled)?;
         let name_osstr = sysfspath.file_name().ok_or(VfioError::InvalidPath)?;
         let name_str = name_osstr.to_str().ok_or(VfioError::InvalidPath)?;
         let name = String::from(name_str);
@@ -590,7 +706,7 @@
                 let info_ptr = region_with_cap.as_ptr() as *mut u8;
                 let mut offset = region_with_cap[0].region_info.cap_offset;
                 while offset != 0 {
-                    if offset + cap_header_sz >= region_info_sz {
+                    if offset + cap_header_sz > region_info_sz {
                         break;
                     }
                     // Safe, as cap_header struct is in this function allocated region_with_cap
@@ -599,7 +715,7 @@
                     let cap_header =
                         unsafe { &*(cap_ptr as *mut u8 as *const vfio_info_cap_header) };
                     if cap_header.id as u32 == VFIO_REGION_INFO_CAP_SPARSE_MMAP {
-                        if offset + mmap_cap_sz >= region_info_sz {
+                        if offset + mmap_cap_sz > region_info_sz {
                             break;
                         }
                         // cap_ptr is vfio_region_info_cap_sparse_mmap here
@@ -631,6 +747,11 @@
                             unsafe { &*(cap_ptr as *mut u8 as *const vfio_region_info_cap_type) };
 
                         cap_info = Some((cap_type_info.type_, cap_type_info.subtype));
+                    } else if cap_header.id as u32 == VFIO_REGION_INFO_CAP_MSIX_MAPPABLE {
+                        mmaps.push(vfio_region_sparse_mmap_area {
+                            offset: region_with_cap[0].region_info.offset,
+                            size: region_with_cap[0].region_info.size,
+                        });
                     }
 
                     offset = cap_header.next;
@@ -794,8 +915,11 @@
         iova: u64,
         size: u64,
         user_addr: u64,
+        write_en: bool,
     ) -> Result<(), VfioError> {
-        self.container.lock().vfio_dma_map(iova, size, user_addr)
+        self.container
+            .lock()
+            .vfio_dma_map(iova, size, user_addr, write_en)
     }
 
     /// Remove (iova, user_addr) map from vfio container iommu table
diff --git a/devices/src/virtio/balloon.rs b/devices/src/virtio/balloon.rs
index e1bc984..ecd9a44 100644
--- a/devices/src/virtio/balloon.rs
+++ b/devices/src/virtio/balloon.rs
@@ -12,10 +12,10 @@
 use remain::sorted;
 use thiserror::Error as ThisError;
 
-use base::{self, error, info, warn, AsRawDescriptor, AsyncTube, Event, RawDescriptor, Tube};
+use base::{self, error, warn, AsRawDescriptor, AsyncTube, Event, RawDescriptor, Tube};
 use cros_async::{select6, EventAsync, Executor};
 use data_model::{DataInit, Le16, Le32, Le64};
-use vm_control::{BalloonControlCommand, BalloonControlResult, BalloonStats};
+use vm_control::{BalloonStats, BalloonTubeCommand, BalloonTubeResult};
 use vm_memory::{GuestAddress, GuestMemory};
 
 use super::{
@@ -178,7 +178,7 @@
             error!("balloon: failed to process inflate addresses: {}", e);
         }
         queue.add_used(mem, index, 0);
-        interrupt.borrow_mut().signal_used_queue(queue.vector);
+        queue.trigger_interrupt(mem, &*interrupt.borrow());
     }
 }
 
@@ -190,12 +190,34 @@
     mem: &GuestMemory,
     mut queue: Queue,
     mut queue_event: EventAsync,
-    mut stats_rx: mpsc::Receiver<()>,
+    mut stats_rx: mpsc::Receiver<u64>,
     command_tube: &Tube,
     config: Arc<BalloonConfig>,
     interrupt: Rc<RefCell<Interrupt>>,
 ) {
+    // Consume the first stats buffer sent from the guest at startup. It was not
+    // requested by anyone, and the stats are stale.
+    let mut index = match queue.next_async(mem, &mut queue_event).await {
+        Err(e) => {
+            error!("Failed to read descriptor {}", e);
+            return;
+        }
+        Ok(d) => d.index,
+    };
     loop {
+        // Wait for a request to read the stats.
+        let id = match stats_rx.next().await {
+            Some(id) => id,
+            None => {
+                error!("stats signal tube was closed");
+                break;
+            }
+        };
+
+        // Request a new stats_desc to the guest.
+        queue.add_used(&mem, index, 0);
+        queue.trigger_interrupt(&mem, &*interrupt.borrow());
+
         let stats_desc = match queue.next_async(mem, &mut queue_event).await {
             Err(e) => {
                 error!("Failed to read descriptor {}", e);
@@ -203,7 +225,7 @@
             }
             Ok(d) => d,
         };
-        let index = stats_desc.index;
+        index = stats_desc.index;
         let mut reader = match Reader::new(mem.clone(), stats_desc) {
             Ok(r) => r,
             Err(e) => {
@@ -222,23 +244,14 @@
             };
         }
         let actual_pages = config.actual_pages.load(Ordering::Relaxed) as u64;
-        let result = BalloonControlResult::Stats {
+        let result = BalloonTubeResult::Stats {
             balloon_actual: actual_pages << VIRTIO_BALLOON_PFN_SHIFT,
             stats,
+            id,
         };
         if let Err(e) = command_tube.send(&result) {
             error!("failed to send stats result: {}", e);
         }
-
-        // Wait for a request to read the stats again.
-        if stats_rx.next().await.is_none() {
-            error!("stats signal tube was closed");
-            break;
-        }
-
-        // Request a new stats_desc to the guest.
-        queue.add_used(&mem, index, 0);
-        interrupt.borrow_mut().signal_used_queue(queue.vector);
     }
 }
 
@@ -248,20 +261,19 @@
     command_tube: &AsyncTube,
     interrupt: Rc<RefCell<Interrupt>>,
     config: Arc<BalloonConfig>,
-    mut stats_tx: mpsc::Sender<()>,
+    mut stats_tx: mpsc::Sender<u64>,
 ) -> Result<()> {
     loop {
         match command_tube.next().await {
             Ok(command) => match command {
-                BalloonControlCommand::Adjust { num_bytes } => {
+                BalloonTubeCommand::Adjust { num_bytes } => {
                     let num_pages = (num_bytes >> VIRTIO_BALLOON_PFN_SHIFT) as usize;
-                    info!("balloon config changed to consume {} pages", num_pages);
 
                     config.num_pages.store(num_pages, Ordering::Relaxed);
                     interrupt.borrow_mut().signal_config_changed();
                 }
-                BalloonControlCommand::Stats => {
-                    if let Err(e) = stats_tx.try_send(()) {
+                BalloonTubeCommand::Stats { id } => {
+                    if let Err(e) = stats_tx.try_send(id) {
                         error!("failed to signal the stat handler: {}", e);
                     }
                 }
@@ -347,8 +359,10 @@
         );
         pin_mut!(deflate);
 
-        // The third queue is used for stats messages
-        let (stats_tx, stats_rx) = mpsc::channel::<()>(1);
+        // The third queue is used for stats messages. The message type is the
+        // id of the stats request, so we can detect if there are any stale
+        // stats results that were queued during an error condition.
+        let (stats_tx, stats_rx) = mpsc::channel::<u64>(1);
         let stats_event =
             EventAsync::new(queue_evts.remove(0).0, &ex).expect("failed to set up the stats event");
         let stats = handle_stats_queue(
diff --git a/devices/src/virtio/block/asynchronous.rs b/devices/src/virtio/block/asynchronous.rs
index f4bf888..6c963d8 100644
--- a/devices/src/virtio/block/asynchronous.rs
+++ b/devices/src/virtio/block/asynchronous.rs
@@ -223,7 +223,6 @@
     let mut queue = queue.borrow_mut();
     queue.add_used(&mem, descriptor_index, len as u32);
     queue.trigger_interrupt(&mem, interrupt);
-    queue.update_int_required(&mem);
 }
 
 // There is one async task running `handle_queue` per virtio queue in use.
@@ -940,8 +939,8 @@
             let b = BlockAsync::new(features, Box::new(f), false, true, 512, None, None).unwrap();
             // writable device should set VIRTIO_BLK_F_FLUSH + VIRTIO_BLK_F_DISCARD
             // + VIRTIO_BLK_F_WRITE_ZEROES + VIRTIO_F_VERSION_1 + VIRTIO_BLK_F_BLK_SIZE
-            // + VIRTIO_BLK_F_SEG_MAX + VIRTIO_BLK_F_MQ
-            assert_eq!(0x100007244, b.features());
+            // + VIRTIO_BLK_F_SEG_MAX + VIRTIO_BLK_F_MQ + VIRTIO_RING_F_EVENT_IDX
+            assert_eq!(0x120007244, b.features());
         }
 
         // read-write block device, non-sparse
@@ -951,8 +950,8 @@
             let b = BlockAsync::new(features, Box::new(f), false, false, 512, None, None).unwrap();
             // read-only device should set VIRTIO_BLK_F_FLUSH and VIRTIO_BLK_F_RO
             // + VIRTIO_F_VERSION_1 + VIRTIO_BLK_F_BLK_SIZE + VIRTIO_BLK_F_SEG_MAX
-            // + VIRTIO_BLK_F_MQ
-            assert_eq!(0x100005244, b.features());
+            // + VIRTIO_BLK_F_MQ + VIRTIO_RING_F_EVENT_IDX
+            assert_eq!(0x120005244, b.features());
         }
 
         // read-only block device
@@ -962,8 +961,8 @@
             let b = BlockAsync::new(features, Box::new(f), true, true, 512, None, None).unwrap();
             // read-only device should set VIRTIO_BLK_F_FLUSH and VIRTIO_BLK_F_RO
             // + VIRTIO_F_VERSION_1 + VIRTIO_BLK_F_BLK_SIZE + VIRTIO_BLK_F_SEG_MAX
-            // + VIRTIO_BLK_F_MQ
-            assert_eq!(0x100001264, b.features());
+            // + VIRTIO_BLK_F_MQ + VIRTIO_RING_F_EVENT_IDX
+            assert_eq!(0x120001264, b.features());
         }
     }
 
diff --git a/devices/src/virtio/block/block.rs b/devices/src/virtio/block/block.rs
index f7c55e2..e14810c 100644
--- a/devices/src/virtio/block/block.rs
+++ b/devices/src/virtio/block/block.rs
@@ -789,8 +789,8 @@
             let b = Block::new(features, Box::new(f), false, true, 512, None, None).unwrap();
             // writable device should set VIRTIO_BLK_F_FLUSH + VIRTIO_BLK_F_DISCARD
             // + VIRTIO_BLK_F_WRITE_ZEROES + VIRTIO_F_VERSION_1 + VIRTIO_BLK_F_BLK_SIZE
-            // + VIRTIO_BLK_F_SEG_MAX
-            assert_eq!(0x100006244, b.features());
+            // + VIRTIO_BLK_F_SEG_MAX + VIRTIO_RING_F_EVENT_IDX
+            assert_eq!(0x120006244, b.features());
         }
 
         // read-write block device, non-sparse
@@ -800,8 +800,8 @@
             let b = Block::new(features, Box::new(f), false, false, 512, None, None).unwrap();
             // writable device should set VIRTIO_BLK_F_FLUSH
             // + VIRTIO_BLK_F_WRITE_ZEROES + VIRTIO_F_VERSION_1 + VIRTIO_BLK_F_BLK_SIZE
-            // + VIRTIO_BLK_F_SEG_MAX
-            assert_eq!(0x100004244, b.features());
+            // + VIRTIO_BLK_F_SEG_MAX + VIRTIO_RING_F_EVENT_IDX
+            assert_eq!(0x120004244, b.features());
         }
 
         // read-only block device
@@ -811,7 +811,8 @@
             let b = Block::new(features, Box::new(f), true, true, 512, None, None).unwrap();
             // read-only device should set VIRTIO_BLK_F_FLUSH and VIRTIO_BLK_F_RO
             // + VIRTIO_F_VERSION_1 + VIRTIO_BLK_F_BLK_SIZE + VIRTIO_BLK_F_SEG_MAX
-            assert_eq!(0x100000264, b.features());
+            // + VIRTIO_RING_F_EVENT_IDX
+            assert_eq!(0x120000264, b.features());
         }
     }
 
diff --git a/devices/src/virtio/console.rs b/devices/src/virtio/console.rs
index 9ec6c7b..80284af 100644
--- a/devices/src/virtio/console.rs
+++ b/devices/src/virtio/console.rs
@@ -3,11 +3,13 @@
 // found in the LICENSE file.
 
 use std::io::{self, Read, Write};
+use std::result;
 use std::sync::mpsc::{channel, Receiver, TryRecvError};
 use std::thread;
 
 use base::{error, Event, PollToken, RawDescriptor, WaitContext};
 use data_model::{DataInit, Le16, Le32};
+use thiserror::Error as ThisError;
 use vm_memory::GuestMemory;
 
 use super::{
@@ -22,18 +24,144 @@
 // If VIRTIO_CONSOLE_F_MULTIPORT is implemented, more queues will be needed.
 const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE, QUEUE_SIZE];
 
+#[derive(ThisError, Debug)]
+pub enum ConsoleError {
+    /// There are no more available descriptors to receive into
+    #[error("no rx descriptors available")]
+    RxDescriptorsExhausted,
+    /// Input channel has been disconnected
+    #[error("input channel disconnected")]
+    RxDisconnected,
+}
+
 #[derive(Copy, Clone, Debug, Default)]
 #[repr(C)]
-pub(crate) struct virtio_console_config {
-    cols: Le16,
-    rows: Le16,
-    max_nr_ports: Le32,
-    emerg_wr: Le32,
+pub struct virtio_console_config {
+    pub cols: Le16,
+    pub rows: Le16,
+    pub max_nr_ports: Le32,
+    pub emerg_wr: Le32,
 }
 
 // Safe because it only has data and has no implicit padding.
 unsafe impl DataInit for virtio_console_config {}
 
+/// Checks for input from `in_channel_opt` and transfers it to the receive queue, if any.
+///
+/// # Arguments
+/// * `mem` - The GuestMemory to write the data into
+/// * `interrupt` - SignalableInterrupt used to signal that the queue has been used
+/// * `in_channel_opt` - Optional input channel to read data from
+/// * `receive_queue` - The receive virtio Queue
+pub fn handle_input<I: SignalableInterrupt>(
+    mem: &GuestMemory,
+    interrupt: &I,
+    in_channel: &Receiver<Vec<u8>>,
+    receive_queue: &mut Queue,
+) -> result::Result<(), ConsoleError> {
+    let mut exhausted_queue = false;
+
+    loop {
+        let desc = match receive_queue.peek(&mem) {
+            Some(d) => d,
+            None => {
+                exhausted_queue = true;
+                break;
+            }
+        };
+        let desc_index = desc.index;
+        // TODO(morg): Handle extra error cases as Err(ConsoleError) instead of just returning.
+        let mut writer = match Writer::new(mem.clone(), desc) {
+            Ok(w) => w,
+            Err(e) => {
+                error!("console: failed to create Writer: {}", e);
+                break;
+            }
+        };
+
+        let mut disconnected = false;
+        while writer.available_bytes() > 0 {
+            match in_channel.try_recv() {
+                Ok(data) => {
+                    writer.write_all(&data).unwrap();
+                }
+                Err(TryRecvError::Empty) => break,
+                Err(TryRecvError::Disconnected) => {
+                    disconnected = true;
+                    break;
+                }
+            }
+        }
+
+        let bytes_written = writer.bytes_written() as u32;
+
+        if bytes_written > 0 {
+            receive_queue.pop_peeked(&mem);
+            receive_queue.add_used(&mem, desc_index, bytes_written);
+            receive_queue.trigger_interrupt(&mem, interrupt);
+        }
+
+        if disconnected {
+            return Err(ConsoleError::RxDisconnected);
+        }
+
+        if bytes_written == 0 {
+            break;
+        }
+    }
+
+    if exhausted_queue {
+        Err(ConsoleError::RxDescriptorsExhausted)
+    } else {
+        Ok(())
+    }
+}
+
+/// Processes the data taken from the given transmit queue into the output sink.
+///
+/// # Arguments
+///
+/// * `mem` - The GuestMemory to take the data from
+/// * `interrupt` - SignalableInterrupt used to signal (if required) that the queue has been used
+/// * `transmit_queue` - The transmit virtio Queue
+/// * `output` - The output sink we are going to write the data into
+pub fn process_transmit_queue<I: SignalableInterrupt>(
+    mem: &GuestMemory,
+    interrupt: &I,
+    transmit_queue: &mut Queue,
+    output: &mut dyn io::Write,
+) {
+    let mut needs_interrupt = false;
+    while let Some(avail_desc) = transmit_queue.pop(&mem) {
+        let desc_index = avail_desc.index;
+
+        let reader = match Reader::new(mem.clone(), avail_desc) {
+            Ok(r) => r,
+            Err(e) => {
+                error!("console: failed to create reader: {}", e);
+                transmit_queue.add_used(&mem, desc_index, 0);
+                needs_interrupt = true;
+                continue;
+            }
+        };
+
+        let len = match process_transmit_request(reader, output) {
+            Ok(written) => written,
+            Err(e) => {
+                error!("console: process_transmit_request failed: {}", e);
+                0
+            }
+        };
+
+        transmit_queue.add_used(&mem, desc_index, len);
+        needs_interrupt = true;
+    }
+
+    if needs_interrupt {
+        transmit_queue.trigger_interrupt(mem, interrupt);
+    }
+}
+
 struct Worker {
     mem: GuestMemory,
     interrupt: Interrupt,
@@ -41,170 +169,82 @@
     output: Option<Box<dyn io::Write + Send>>,
 }
 
-fn write_output(output: &mut Box<dyn io::Write>, data: &[u8]) -> io::Result<()> {
+fn write_output(output: &mut dyn io::Write, data: &[u8]) -> io::Result<()> {
     output.write_all(&data)?;
     output.flush()
 }
 
-impl Worker {
-    fn process_transmit_request(
-        mut reader: Reader,
-        output: &mut Box<dyn io::Write>,
-    ) -> io::Result<u32> {
-        let len = reader.available_bytes();
-        let mut data = vec![0u8; len];
-        reader.read_exact(&mut data)?;
-        write_output(output, &data)?;
-        Ok(0)
-    }
+/// Starts a thread that reads rx_input and sends the input back via the returned channel.
+///
+/// # Arguments
+/// * `rx_input` - Data source that the reader thread will wait on to send data back to the channel
+/// * `in_avail_evt` - Event triggered by the thread when new input is available on the channel
+pub fn spawn_input_thread(
+    mut rx: Box<dyn io::Read + Send>,
+    in_avail_evt: &Event,
+) -> Option<Receiver<Vec<u8>>> {
+    let (send_channel, recv_channel) = channel();
 
-    fn process_transmit_queue(
-        &mut self,
-        transmit_queue: &mut Queue,
-        output: &mut Box<dyn io::Write>,
-    ) {
-        let mut needs_interrupt = false;
-        while let Some(avail_desc) = transmit_queue.pop(&self.mem) {
-            let desc_index = avail_desc.index;
-
-            let reader = match Reader::new(self.mem.clone(), avail_desc) {
-                Ok(r) => r,
-                Err(e) => {
-                    error!("console: failed to create reader: {}", e);
-                    transmit_queue.add_used(&self.mem, desc_index, 0);
-                    needs_interrupt = true;
-                    continue;
-                }
-            };
-
-            let len = match Self::process_transmit_request(reader, output) {
-                Ok(written) => written,
-                Err(e) => {
-                    error!("console: process_transmit_request failed: {}", e);
-                    0
-                }
-            };
-
-            transmit_queue.add_used(&self.mem, desc_index, len);
-            needs_interrupt = true;
-        }
-
-        if needs_interrupt {
-            self.interrupt.signal_used_queue(transmit_queue.vector);
-        }
-    }
-
-    // Start a thread that reads self.input and sends the input back via the returned channel.
-    //
-    // `in_avail_evt` will be triggered by the thread when new input is available.
-    fn spawn_input_thread(&mut self, in_avail_evt: &Event) -> Option<Receiver<Vec<u8>>> {
-        let mut rx = match self.input.take() {
-            Some(input) => input,
-            None => return None,
-        };
-
-        let (send_channel, recv_channel) = channel();
-
-        let thread_in_avail_evt = match in_avail_evt.try_clone() {
-            Ok(evt) => evt,
-            Err(e) => {
-                error!("failed to clone in_avail_evt: {}", e);
-                return None;
-            }
-        };
-
-        // The input thread runs in detached mode and will exit when channel is disconnected because
-        // the console device has been dropped.
-        let res = thread::Builder::new()
-            .name("console_input".to_string())
-            .spawn(move || {
-                loop {
-                    let mut rx_buf = vec![0u8; 1 << 12];
-                    match rx.read(&mut rx_buf) {
-                        Ok(0) => break, // Assume the stream of input has ended.
-                        Ok(size) => {
-                            rx_buf.truncate(size);
-                            if send_channel.send(rx_buf).is_err() {
-                                // The receiver has disconnected.
-                                break;
-                            }
-                            thread_in_avail_evt.write(1).unwrap();
-                        }
-                        Err(e) => {
-                            // Being interrupted is not an error, but everything else is.
-                            if e.kind() != io::ErrorKind::Interrupted {
-                                error!(
-                                    "failed to read for bytes to queue into console device: {}",
-                                    e
-                                );
-                                break;
-                            }
-                        }
-                    }
-                }
-            });
-        if let Err(e) = res {
-            error!("failed to spawn input thread: {}", e);
+    let thread_in_avail_evt = match in_avail_evt.try_clone() {
+        Ok(evt) => evt,
+        Err(e) => {
+            error!("failed to clone in_avail_evt: {}", e);
             return None;
         }
-        Some(recv_channel)
-    }
+    };
 
-    // Check for input from `in_channel_opt` and transfer it to the receive queue, if any.
-    fn handle_input(
-        &mut self,
-        in_channel_opt: &mut Option<Receiver<Vec<u8>>>,
-        receive_queue: &mut Queue,
-    ) {
-        let in_channel = match in_channel_opt.as_ref() {
-            Some(v) => v,
-            None => return,
-        };
-
-        while let Some(desc) = receive_queue.peek(&self.mem) {
-            let desc_index = desc.index;
-            let mut writer = match Writer::new(self.mem.clone(), desc) {
-                Ok(w) => w,
-                Err(e) => {
-                    error!("console: failed to create Writer: {}", e);
-                    break;
-                }
-            };
-
-            let mut disconnected = false;
-            while writer.available_bytes() > 0 {
-                match in_channel.try_recv() {
-                    Ok(data) => {
-                        writer.write_all(&data).unwrap();
+    // The input thread runs in detached mode and will exit when channel is disconnected because
+    // the console device has been dropped.
+    let res = thread::Builder::new()
+        .name("console_input".to_string())
+        .spawn(move || {
+            loop {
+                let mut rx_buf = vec![0u8; 1 << 12];
+                match rx.read(&mut rx_buf) {
+                    Ok(0) => break, // Assume the stream of input has ended.
+                    Ok(size) => {
+                        rx_buf.truncate(size);
+                        if send_channel.send(rx_buf).is_err() {
+                            // The receiver has disconnected.
+                            break;
+                        }
+                        thread_in_avail_evt.write(1).unwrap();
                     }
-                    Err(TryRecvError::Empty) => break,
-                    Err(TryRecvError::Disconnected) => {
-                        disconnected = true;
-                        break;
+                    Err(e) => {
+                        // Being interrupted is not an error, but everything else is.
+                        if e.kind() != io::ErrorKind::Interrupted {
+                            error!(
+                                "failed to read for bytes to queue into console device: {}",
+                                e
+                            );
+                            break;
+                        }
                     }
                 }
             }
-
-            let bytes_written = writer.bytes_written() as u32;
-
-            if bytes_written > 0 {
-                receive_queue.pop_peeked(&self.mem);
-                receive_queue.add_used(&self.mem, desc_index, bytes_written);
-                self.interrupt.signal_used_queue(receive_queue.vector);
-            }
-
-            if disconnected {
-                // Set in_channel to None so that future handle_input calls exit early.
-                in_channel_opt.take();
-                return;
-            }
-
-            if bytes_written == 0 {
-                break;
-            }
-        }
+        });
+    if let Err(e) = res {
+        error!("failed to spawn input thread: {}", e);
+        return None;
     }
+    Some(recv_channel)
+}
 
+/// Writes the available data from the reader into the given output queue.
+///
+/// # Arguments
+///
+/// * `reader` - The Reader with the data we want to write.
+/// * `output` - The output sink we are going to write the data to.
+pub fn process_transmit_request(mut reader: Reader, output: &mut dyn io::Write) -> io::Result<u32> {
+    let len = reader.available_bytes();
+    let mut data = vec![0u8; len];
+    reader.read_exact(&mut data)?;
+    write_output(output, &data)?;
+    Ok(0)
+}
+
+impl Worker {
     fn run(&mut self, mut queues: Vec<Queue>, mut queue_evts: Vec<Event>, kill_evt: Event) {
         #[derive(PollToken)]
         enum Token {
@@ -234,7 +274,10 @@
         // generic way to add an io::Read instance to a poll context (it may not be backed by a file
         // descriptor).  Moving the blocking read call to a separate thread and sending data back to
         // the main worker thread with an event for notification bridges this gap.
-        let mut in_channel = self.spawn_input_thread(&in_avail_evt);
+        let mut in_channel = match self.input.take() {
+            Some(input) => spawn_input_thread(input, &in_avail_evt),
+            None => None,
+        };
 
         let wait_ctx: WaitContext<Token> = match WaitContext::build_with(&[
             (&transmit_evt, Token::TransmitQueueAvailable),
@@ -279,21 +322,50 @@
                             error!("failed reading transmit queue Event: {}", e);
                             break 'wait;
                         }
-                        self.process_transmit_queue(&mut transmit_queue, &mut output);
+                        process_transmit_queue(
+                            &self.mem,
+                            &self.interrupt,
+                            &mut transmit_queue,
+                            &mut output,
+                        );
                     }
                     Token::ReceiveQueueAvailable => {
                         if let Err(e) = receive_evt.read() {
                             error!("failed reading receive queue Event: {}", e);
                             break 'wait;
                         }
-                        self.handle_input(&mut in_channel, &mut receive_queue);
+                        if let Some(ch) = in_channel.as_ref() {
+                            match handle_input(&self.mem, &self.interrupt, ch, &mut receive_queue) {
+                                Ok(()) => {}
+                                Err(ConsoleError::RxDisconnected) => {
+                                    // Set in_channel to None so that future handle_input calls exit early.
+                                    in_channel.take();
+                                }
+                                // Other console errors are no-ops, so just continue.
+                                Err(_) => {
+                                    continue;
+                                }
+                            }
+                        }
                     }
                     Token::InputAvailable => {
                         if let Err(e) = in_avail_evt.read() {
                             error!("failed reading in_avail_evt: {}", e);
                             break 'wait;
                         }
-                        self.handle_input(&mut in_channel, &mut receive_queue);
+                        if let Some(ch) = in_channel.as_ref() {
+                            match handle_input(&self.mem, &self.interrupt, ch, &mut receive_queue) {
+                                Ok(()) => {}
+                                Err(ConsoleError::RxDisconnected) => {
+                                    // Set in_channel to None so that future handle_input calls exit early.
+                                    in_channel.take();
+                                }
+                                // Other console errors are no-ops, so just continue.
+                                Err(_) => {
+                                    continue;
+                                }
+                            }
+                        }
                     }
                     Token::InterruptResample => {
                         self.interrupt.interrupt_resample();
diff --git a/devices/src/virtio/descriptor_utils.rs b/devices/src/virtio/descriptor_utils.rs
index fc839b6..d2ac32d 100644
--- a/devices/src/virtio/descriptor_utils.rs
+++ b/devices/src/virtio/descriptor_utils.rs
@@ -305,6 +305,24 @@
         }
     }
 
+    /// Reads data into a volatile slice up to the minimum of the slice's length or the number of
+    /// bytes remaining. Returns the number of bytes read.
+    pub fn read_to_volatile_slice(&mut self, slice: VolatileSlice) -> usize {
+        let mut read = 0usize;
+        let mut dst = slice;
+        for src in self.get_remaining() {
+            src.copy_to_volatile_slice(dst);
+            let copied = std::cmp::min(src.size(), dst.size());
+            read += copied;
+            dst = match dst.offset(copied) {
+                Ok(v) => v,
+                Err(_) => break, // The slice is fully consumed
+            };
+        }
+        self.regions.consume(read);
+        read
+    }
+
     /// Reads data from the descriptor chain buffer into a file descriptor.
     /// Returns the number of bytes read from the descriptor chain buffer.
     /// The number of bytes read can be less than `count` if there isn't
@@ -566,6 +584,24 @@
         self.regions.available_bytes()
     }
 
+    /// Reads data into a volatile slice up to the minimum of the slice's length or the number of
+    /// bytes remaining. Returns the number of bytes read.
+    pub fn write_from_volatile_slice(&mut self, slice: VolatileSlice) -> usize {
+        let mut written = 0usize;
+        let mut src = slice;
+        for dst in self.get_remaining() {
+            src.copy_to_volatile_slice(dst);
+            let copied = std::cmp::min(src.size(), dst.size());
+            written += copied;
+            src = match src.offset(copied) {
+                Ok(v) => v,
+                Err(_) => break, // The slice is fully consumed
+            };
+        }
+        self.regions.consume(written);
+        written
+    }
+
     /// Writes data to the descriptor chain buffer from a file descriptor.
     /// Returns the number of bytes written to the descriptor chain buffer.
     /// The number of bytes written can be less than `count` if
@@ -686,6 +722,21 @@
         self.regions.bytes_consumed()
     }
 
+    /// Returns a `&[VolatileSlice]` that represents all the remaining data in this `Writer`.
+    /// Calling this method does not actually advance the current position of the `Writer` in the
+    /// buffer and callers should call `consume_bytes` to advance the `Writer`. Not calling
+    /// `consume_bytes` with the amount of data copied into the returned `VolatileSlice`s will
+    /// result in that that data being overwritten the next time data is written into the `Writer`.
+    pub fn get_remaining(&self) -> SmallVec<[VolatileSlice; 16]> {
+        self.regions.get_remaining(&self.mem)
+    }
+
+    /// 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_bytes(&mut self, amt: usize) {
+        self.regions.consume(amt)
+    }
+
     /// Splits this `Writer` into two at the given offset in the `DescriptorChain` buffer. After the
     /// split, `self` will be able to write up to `offset` bytes while the returned `Writer` can
     /// write up to `available_bytes() - offset` bytes. If `offset > self.available_bytes()`, then
diff --git a/devices/src/virtio/fs/mod.rs b/devices/src/virtio/fs/mod.rs
index bc8c28f..4cf916a 100644
--- a/devices/src/virtio/fs/mod.rs
+++ b/devices/src/virtio/fs/mod.rs
@@ -5,12 +5,13 @@
 use std::fmt;
 use std::io;
 use std::mem;
-use std::sync::{Arc, Mutex};
+use std::sync::Arc;
 use std::thread;
 
 use base::{error, warn, AsRawDescriptor, Error as SysError, Event, RawDescriptor, Tube};
 use data_model::{DataInit, Le32};
 use resources::Alloc;
+use sync::Mutex;
 use vm_control::{FsMappingRequest, VmResponse};
 use vm_memory::GuestMemory;
 
diff --git a/devices/src/virtio/fs/worker.rs b/devices/src/virtio/fs/worker.rs
index 19fec11..e3bab81 100644
--- a/devices/src/virtio/fs/worker.rs
+++ b/devices/src/virtio/fs/worker.rs
@@ -6,10 +6,11 @@
 use std::fs::File;
 use std::io;
 use std::os::unix::io::AsRawFd;
-use std::sync::{Arc, Mutex};
+use std::sync::Arc;
 
 use base::{error, Event, PollToken, SafeDescriptor, Tube, WaitContext};
 use fuse::filesystem::{FileSystem, ZeroCopyReader, ZeroCopyWriter};
+use sync::Mutex;
 use vm_control::{FsMappingRequest, VmResponse};
 use vm_memory::GuestMemory;
 
@@ -19,6 +20,8 @@
 impl fuse::Reader for Reader {}
 
 impl fuse::Writer for Writer {
+    type ClosureWriter = Self;
+
     fn write_at<F>(&mut self, offset: usize, f: F) -> io::Result<usize>
     where
         F: Fn(&mut Self) -> io::Result<usize>,
@@ -55,10 +58,7 @@
     }
 
     fn process_request(&self, request: &FsMappingRequest) -> io::Result<()> {
-        let tube = self.tube.lock().map_err(|e| {
-            error!("failed to lock tube: {}", e);
-            io::Error::from_raw_os_error(libc::EINVAL)
-        })?;
+        let tube = self.tube.lock();
 
         tube.send(request).map_err(|e| {
             error!("failed to send request {:?}: {}", request, e);
@@ -171,7 +171,7 @@
         }
 
         if needs_interrupt {
-            self.irq.signal_used_queue(self.queue.vector);
+            self.queue.trigger_interrupt(&self.mem, &*self.irq);
         }
 
         Ok(())
diff --git a/devices/src/virtio/gpu/mod.rs b/devices/src/virtio/gpu/mod.rs
index 7103ee8..c95d65d 100644
--- a/devices/src/virtio/gpu/mod.rs
+++ b/devices/src/virtio/gpu/mod.rs
@@ -8,7 +8,7 @@
 mod virtio_gpu;
 
 use std::cell::RefCell;
-use std::collections::{BTreeMap, VecDeque};
+use std::collections::{BTreeMap, HashMap, VecDeque};
 use std::convert::TryFrom;
 use std::i64;
 use std::io::Read;
@@ -20,8 +20,8 @@
 use std::time::Duration;
 
 use base::{
-    debug, error, warn, AsRawDescriptor, AsRawDescriptors, Event, ExternalMapping, PollToken,
-    RawDescriptor, Tube, WaitContext,
+    debug, error, warn, AsRawDescriptor, Event, ExternalMapping, PollToken, RawDescriptor, Tube,
+    WaitContext,
 };
 
 use data_model::*;
@@ -118,10 +118,37 @@
     pub offsets: [u32; 4],
 }
 
+#[derive(Hash, Eq, PartialEq)]
+enum VirtioGpuRing {
+    Global,
+    ContextSpecific { ctx_id: u32, ring_idx: u32 },
+}
+
+struct FenceDescriptor {
+    ring: VirtioGpuRing,
+    fence_id: u64,
+    index: u16,
+    len: u32,
+}
+
+struct FenceState {
+    descs: Vec<FenceDescriptor>,
+    completed_fences: HashMap<VirtioGpuRing, u64>,
+}
+
+impl Default for FenceState {
+    fn default() -> Self {
+        FenceState {
+            descs: Vec::new(),
+            completed_fences: HashMap::new(),
+        }
+    }
+}
+
 trait QueueReader {
     fn pop(&self, mem: &GuestMemory) -> Option<DescriptorChain>;
     fn add_used(&self, mem: &GuestMemory, desc_index: u16, len: u32);
-    fn signal_used(&self);
+    fn signal_used(&self, mem: &GuestMemory);
 }
 
 struct LocalQueueReader {
@@ -147,8 +174,10 @@
         self.queue.borrow_mut().add_used(mem, desc_index, len)
     }
 
-    fn signal_used(&self) {
-        self.interrupt.signal_used_queue(self.queue.borrow().vector);
+    fn signal_used(&self, mem: &GuestMemory) {
+        self.queue
+            .borrow_mut()
+            .trigger_interrupt(mem, &*self.interrupt);
     }
 }
 
@@ -176,8 +205,8 @@
         self.queue.lock().add_used(mem, desc_index, len)
     }
 
-    fn signal_used(&self) {
-        self.interrupt.signal_used_queue(self.queue.lock().vector);
+    fn signal_used(&self, mem: &GuestMemory) {
+        self.queue.lock().trigger_interrupt(mem, &*self.interrupt);
     }
 }
 
@@ -227,58 +256,61 @@
     )
 }
 
+/// Create a handler that writes into the completed fence queue
+fn create_fence_handler(
+    mem: GuestMemory,
+    ctrl_queue: SharedQueueReader,
+    fence_state: Arc<Mutex<FenceState>>,
+) -> RutabagaFenceHandler {
+    RutabagaFenceClosure::new(move |completed_fence| {
+        let mut signal = false;
+
+        {
+            let ring = match completed_fence.flags & VIRTIO_GPU_FLAG_INFO_RING_IDX {
+                0 => VirtioGpuRing::Global,
+                _ => VirtioGpuRing::ContextSpecific {
+                    ctx_id: completed_fence.ctx_id,
+                    ring_idx: completed_fence.ring_idx,
+                },
+            };
+
+            let mut fence_state = fence_state.lock();
+            fence_state.descs.retain(|f_desc| {
+                if f_desc.ring == ring && f_desc.fence_id <= completed_fence.fence_id {
+                    ctrl_queue.add_used(&mem, f_desc.index, f_desc.len);
+                    signal = true;
+                    return false;
+                }
+                true
+            });
+            // Update the last completed fence for this context
+            fence_state
+                .completed_fences
+                .insert(ring, completed_fence.fence_id);
+        }
+
+        if signal {
+            ctrl_queue.signal_used(&mem);
+        }
+    })
+}
+
 struct ReturnDescriptor {
     index: u16,
     len: u32,
 }
 
-struct FenceDescriptor {
-    desc_fence: RutabagaFenceData,
-    index: u16,
-    len: u32,
-}
-
-fn fence_ctx_equal(desc_fence: &RutabagaFenceData, completed: &RutabagaFenceData) -> bool {
-    let desc_fence_ctx = desc_fence.flags & VIRTIO_GPU_FLAG_INFO_FENCE_CTX_IDX != 0;
-    let completed_fence_ctx = completed.flags & VIRTIO_GPU_FLAG_INFO_FENCE_CTX_IDX != 0;
-
-    // Both fences on global timeline -- only case with upstream kernel.  The rest of the logic
-    // is for per fence context prototype.
-    if !completed_fence_ctx && !desc_fence_ctx {
-        return true;
-    }
-
-    // One fence is on global timeline
-    if desc_fence_ctx != completed_fence_ctx {
-        return false;
-    }
-
-    // Different 3D contexts
-    if desc_fence.ctx_id != completed.ctx_id {
-        return false;
-    }
-
-    // Different fence contexts with same 3D context
-    if desc_fence.fence_ctx_idx != completed.fence_ctx_idx {
-        return false;
-    }
-
-    true
-}
-
 struct Frontend {
-    return_ctrl_descriptors: VecDeque<ReturnDescriptor>,
+    fence_state: Arc<Mutex<FenceState>>,
     return_cursor_descriptors: VecDeque<ReturnDescriptor>,
-    fence_descriptors: Vec<FenceDescriptor>,
     virtio_gpu: VirtioGpu,
 }
 
 impl Frontend {
-    fn new(virtio_gpu: VirtioGpu) -> Frontend {
+    fn new(virtio_gpu: VirtioGpu, fence_state: Arc<Mutex<FenceState>>) -> Frontend {
         Frontend {
-            return_ctrl_descriptors: Default::default(),
+            fence_state,
             return_cursor_descriptors: Default::default(),
-            fence_descriptors: Default::default(),
             virtio_gpu,
         }
     }
@@ -509,9 +541,7 @@
                 };
 
                 let entry_count = info.nr_entries.to_native();
-                if entry_count > VIRTIO_GPU_MAX_IOVEC_ENTRIES
-                    || (reader.available_bytes() == 0 && entry_count > 0)
-                {
+                if reader.available_bytes() == 0 && entry_count > 0 {
                     return Err(GpuResponse::ErrUnspec);
                 }
 
@@ -672,7 +702,7 @@
                         flags,
                         fence_id,
                         ctx_id,
-                        fence_ctx_idx: info,
+                        ring_idx: info,
                     };
                     gpu_response = match self.virtio_gpu.create_fence(fence_data) {
                         Ok(_) => gpu_response,
@@ -692,21 +722,30 @@
             }
 
             if flags & VIRTIO_GPU_FLAG_FENCE != 0 {
-                self.fence_descriptors.push(FenceDescriptor {
-                    desc_fence: RutabagaFenceData {
-                        flags,
-                        fence_id,
+                let ring = match flags & VIRTIO_GPU_FLAG_INFO_RING_IDX {
+                    0 => VirtioGpuRing::Global,
+                    _ => VirtioGpuRing::ContextSpecific {
                         ctx_id,
-                        fence_ctx_idx: info,
+                        ring_idx: info,
                     },
-                    index: desc_index,
-                    len,
-                });
+                };
 
-                return None;
+                // In case the fence is signaled immediately after creation, don't add a return
+                // FenceDescriptor.
+                let mut fence_state = self.fence_state.lock();
+                if fence_id > *fence_state.completed_fences.get(&ring).unwrap_or(&0) {
+                    fence_state.descs.push(FenceDescriptor {
+                        ring,
+                        fence_id,
+                        index: desc_index,
+                        len,
+                    });
+
+                    return None;
+                }
             }
 
-            // No fence, respond now.
+            // No fence (or already completed fence), respond now.
         }
         Some(ReturnDescriptor {
             index: desc_index,
@@ -717,29 +756,12 @@
     fn return_cursor(&mut self) -> Option<ReturnDescriptor> {
         self.return_cursor_descriptors.pop_front()
     }
-
-    fn return_ctrl(&mut self) -> Option<ReturnDescriptor> {
-        self.return_ctrl_descriptors.pop_front()
+    fn fence_poll(&mut self) {
+        self.virtio_gpu.fence_poll();
     }
 
-    fn fence_poll(&mut self) {
-        let completed_fences = self.virtio_gpu.fence_poll();
-        let return_descs = &mut self.return_ctrl_descriptors;
-
-        self.fence_descriptors.retain(|f_desc| {
-            for completed in &completed_fences {
-                if fence_ctx_equal(&f_desc.desc_fence, completed)
-                    && f_desc.desc_fence.fence_id <= completed.fence_id
-                {
-                    return_descs.push_back(ReturnDescriptor {
-                        index: f_desc.index,
-                        len: f_desc.len,
-                    });
-                    return false;
-                }
-            }
-            true
-        })
+    fn has_pending_fences(&self) -> bool {
+        !self.fence_state.lock().descs.is_empty()
     }
 }
 
@@ -808,7 +830,7 @@
         let mut process_resource_bridge = Vec::with_capacity(self.resource_bridges.len());
         'wait: loop {
             // If there are outstanding fences, wake up early to poll them.
-            let duration = if !self.state.fence_descriptors.is_empty() {
+            let duration = if self.state.has_pending_fences() {
                 Duration::from_millis(FENCE_POLL_MS)
             } else {
                 Duration::new(i64::MAX as u64, 0)
@@ -882,11 +904,6 @@
 
             self.state.fence_poll();
 
-            while let Some(desc) = self.state.return_ctrl() {
-                self.ctrl_queue.add_used(&self.mem, desc.index, desc.len);
-                signal_used_ctrl = true;
-            }
-
             // Process the entire control queue before the resource bridge in case a resource is
             // created or destroyed by the control queue. Processing the resource bridge first may
             // lead to a race condition.
@@ -902,11 +919,11 @@
             }
 
             if signal_used_ctrl {
-                self.ctrl_queue.signal_used();
+                self.ctrl_queue.signal_used(&self.mem);
             }
 
             if signal_used_cursor {
-                self.cursor_queue.signal_used();
+                self.cursor_queue.signal_used(&self.mem);
             }
         }
     }
@@ -952,7 +969,6 @@
     external_blob: bool,
     rutabaga_component: RutabagaComponentType,
     base_features: u64,
-    mem: GuestMemory,
     udmabuf: bool,
 }
 
@@ -968,7 +984,6 @@
         external_blob: bool,
         base_features: u64,
         channels: BTreeMap<String, PathBuf>,
-        mem: GuestMemory,
     ) -> Gpu {
         let virglrenderer_flags = VirglRendererFlags::new()
             .use_egl(gpu_parameters.renderer_use_egl)
@@ -1038,7 +1053,6 @@
             external_blob,
             rutabaga_component: component,
             base_features,
-            mem,
             udmabuf: gpu_parameters.udmabuf,
         }
     }
@@ -1105,10 +1119,6 @@
             keep_rds.push(libc::STDERR_FILENO);
         }
 
-        if self.udmabuf {
-            keep_rds.append(&mut self.mem.as_raw_descriptors());
-        }
-
         if let Some(ref gpu_device_tube) = self.gpu_device_tube {
             keep_rds.push(gpu_device_tube.as_raw_descriptor());
         }
@@ -1209,6 +1219,7 @@
         let map_request = Arc::clone(&self.map_request);
         let external_blob = self.external_blob;
         let udmabuf = self.udmabuf;
+        let fence_state = Arc::new(Mutex::new(Default::default()));
         if let (Some(gpu_device_tube), Some(pci_bar), Some(rutabaga_builder)) = (
             self.gpu_device_tube.take(),
             self.pci_bar.take(),
@@ -1218,7 +1229,11 @@
                 thread::Builder::new()
                     .name("virtio_gpu".to_string())
                     .spawn(move || {
-                        let fence_handler = RutabagaFenceClosure::new(|_completed_fence| {});
+                        let fence_handler = create_fence_handler(
+                            mem.clone(),
+                            ctrl_queue.clone(),
+                            fence_state.clone(),
+                        );
 
                         let virtio_gpu = match build(
                             &display_backends,
@@ -1240,13 +1255,13 @@
                             interrupt: irq,
                             exit_evt,
                             mem,
-                            ctrl_queue,
+                            ctrl_queue: ctrl_queue.clone(),
                             ctrl_evt,
                             cursor_queue,
                             cursor_evt,
                             resource_bridges,
                             kill_evt,
-                            state: Frontend::new(virtio_gpu),
+                            state: Frontend::new(virtio_gpu, fence_state),
                         }
                         .run()
                     });
diff --git a/devices/src/virtio/gpu/protocol.rs b/devices/src/virtio/gpu/protocol.rs
index 95306fd..953b690 100644
--- a/devices/src/virtio/gpu/protocol.rs
+++ b/devices/src/virtio/gpu/protocol.rs
@@ -97,9 +97,6 @@
 pub const VIRTIO_GPU_SHM_ID_NONE: u8 = 0x0000;
 pub const VIRTIO_GPU_SHM_ID_HOST_VISIBLE: u8 = 0x0001;
 
-/* This matches the limit in udmabuf.c */
-pub const VIRTIO_GPU_MAX_IOVEC_ENTRIES: u32 = 1024;
-
 pub fn virtio_gpu_cmd_str(cmd: u32) -> &'static str {
     match cmd {
         VIRTIO_GPU_CMD_GET_DISPLAY_INFO => "VIRTIO_GPU_CMD_GET_DISPLAY_INFO",
@@ -147,7 +144,7 @@
 
 pub const VIRTIO_GPU_FLAG_FENCE: u32 = 1 << 0;
 /* Fence context index info flag not upstreamed. */
-pub const VIRTIO_GPU_FLAG_INFO_FENCE_CTX_IDX: u32 = 1 << 1;
+pub const VIRTIO_GPU_FLAG_INFO_RING_IDX: u32 = 1 << 1;
 
 #[derive(Copy, Clone, Debug, Default)]
 #[repr(C)]
diff --git a/devices/src/virtio/input/mod.rs b/devices/src/virtio/input/mod.rs
index 12ccb5d..9e247a0 100644
--- a/devices/src/virtio/input/mod.rs
+++ b/devices/src/virtio/input/mod.rs
@@ -524,7 +524,8 @@
                 }
             }
             if needs_interrupt {
-                self.interrupt.signal_used_queue(self.event_queue.vector);
+                self.event_queue
+                    .trigger_interrupt(&self.guest_memory, &self.interrupt);
             }
         }
 
diff --git a/devices/src/virtio/iommu.rs b/devices/src/virtio/iommu.rs
new file mode 100644
index 0000000..6df1497
--- /dev/null
+++ b/devices/src/virtio/iommu.rs
@@ -0,0 +1,842 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use super::{
+    copy_config, DescriptorChain, DescriptorError, Interrupt, Queue, Reader, SignalableInterrupt,
+    VirtioDevice, Writer, TYPE_IOMMU,
+};
+use crate::pci::PciAddress;
+use crate::vfio::{VfioContainer, VfioError};
+use acpi_tables::sdt::SDT;
+use base::{
+    error, warn, AsRawDescriptor, Error as SysError, Event, PollToken, RawDescriptor,
+    Result as SysResult, WaitContext,
+};
+use data_model::DataInit;
+use std::collections::BTreeMap;
+use std::fmt::{self, Display};
+use std::io::{self, Write};
+use std::mem::size_of;
+use std::sync::Arc;
+use std::{result, thread};
+use sync::Mutex;
+use vm_memory::{GuestAddress, GuestMemory, GuestMemoryError};
+
+const QUEUE_SIZE: u16 = 256;
+const NUM_QUEUES: usize = 2;
+const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE; NUM_QUEUES];
+
+/// Virtio IOMMU features
+const VIRTIO_IOMMU_F_INPUT_RANGE: u32 = 0;
+const VIRTIO_IOMMU_F_MAP_UNMAP: u32 = 2;
+const VIRTIO_IOMMU_F_PROBE: u32 = 4;
+
+#[derive(Copy, Clone, Debug, Default)]
+#[repr(C, packed)]
+struct VirtioIommuRange64 {
+    start: u64,
+    end: u64,
+}
+
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for VirtioIommuRange64 {}
+
+#[derive(Copy, Clone, Debug, Default)]
+#[repr(C, packed)]
+struct VirtioIommuConfig {
+    page_size_mask: u64,
+    input_range: VirtioIommuRange64,
+    domain_range: [u32; 2],
+    probe_size: u32,
+}
+
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for VirtioIommuConfig {}
+
+const VIRTIO_IOMMU_VIOT_NODE_PCI_RANGE: u8 = 1;
+const VIRTIO_IOMMU_VIOT_NODE_VIRTIO_IOMMU_PCI: u8 = 3;
+
+#[derive(Copy, Clone, Debug, Default)]
+#[repr(C, packed)]
+struct VirtioIommuViotHeader {
+    node_count: u16,
+    node_offset: u16,
+    reserved: [u8; 8],
+}
+
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for VirtioIommuViotHeader {}
+
+#[derive(Copy, Clone, Debug, Default)]
+#[repr(C, packed)]
+struct VirtioIommuViotVirtioPciNode {
+    type_: u8,
+    reserved: [u8; 1],
+    length: u16,
+    segment: u16,
+    bdf: u16,
+    reserved2: [u8; 8],
+}
+
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for VirtioIommuViotVirtioPciNode {}
+
+#[derive(Copy, Clone, Debug, Default)]
+#[repr(C, packed)]
+struct VirtioIommuViotPciRangeNode {
+    type_: u8,
+    reserved: [u8; 1],
+    length: u16,
+    endpoint_start: u32,
+    segment_start: u16,
+    segment_end: u16,
+    bdf_start: u16,
+    bdf_end: u16,
+    output_node: u16,
+    reserved2: [u8; 2],
+    reserved3: [u8; 4],
+}
+
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for VirtioIommuViotPciRangeNode {}
+
+/// Virtio IOMMU request type
+const VIRTIO_IOMMU_T_ATTACH: u8 = 1;
+const VIRTIO_IOMMU_T_DETACH: u8 = 2;
+const VIRTIO_IOMMU_T_MAP: u8 = 3;
+const VIRTIO_IOMMU_T_UNMAP: u8 = 4;
+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+const VIRTIO_IOMMU_T_PROBE: u8 = 5;
+
+#[derive(Copy, Clone, Debug, Default)]
+#[repr(C, packed)]
+struct VirtioIommuReqHead {
+    type_: u8,
+    reserved: [u8; 3],
+}
+
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for VirtioIommuReqHead {}
+
+const VIRTIO_IOMMU_S_OK: u8 = 0;
+const VIRTIO_IOMMU_S_UNSUPP: u8 = 2;
+const VIRTIO_IOMMU_S_INVAL: u8 = 4;
+const VIRTIO_IOMMU_S_RANGE: u8 = 5;
+const VIRTIO_IOMMU_S_NOENT: u8 = 6;
+
+#[derive(Copy, Clone, Debug, Default)]
+#[repr(C, packed)]
+struct VirtioIommuReqTail {
+    status: u8,
+    reserved: [u8; 3],
+}
+
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for VirtioIommuReqTail {}
+
+#[derive(Copy, Clone, Debug, Default)]
+#[repr(C, packed)]
+struct VirtioIommuReqAttach {
+    domain: u32,
+    endpoint: u32,
+    reserved: [u8; 8],
+}
+
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for VirtioIommuReqAttach {}
+
+#[derive(Copy, Clone, Debug, Default)]
+#[repr(C, packed)]
+struct VirtioIommuReqDetach {
+    domain: u32,
+    endpoint: u32,
+    reserved: [u8; 8],
+}
+
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for VirtioIommuReqDetach {}
+
+const VIRTIO_IOMMU_MAP_F_READ: u32 = 1;
+const VIRTIO_IOMMU_MAP_F_WRITE: u32 = 2;
+const VIRTIO_IOMMU_MAP_F_MASK: u32 = VIRTIO_IOMMU_MAP_F_READ | VIRTIO_IOMMU_MAP_F_WRITE;
+
+#[derive(Copy, Clone, Debug, Default)]
+#[repr(C, packed)]
+struct VirtioIommuReqMap {
+    domain: u32,
+    virt_start: u64,
+    virt_end: u64,
+    phys_start: u64,
+    flags: u32,
+}
+
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for VirtioIommuReqMap {}
+
+#[derive(Copy, Clone, Debug, Default)]
+#[repr(C, packed)]
+struct VirtioIommuReqUnmap {
+    domain: u32,
+    virt_start: u64,
+    virt_end: u64,
+    reserved: [u8; 4],
+}
+
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for VirtioIommuReqUnmap {}
+
+#[derive(Copy, Clone, Debug, Default)]
+#[repr(C, packed)]
+struct VirtioIommuReqProbe {
+    endpoint: u32,
+    reserved: [u64; 8],
+}
+
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for VirtioIommuReqProbe {}
+
+// Size of struct virtio_iommu_probe_property
+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+const IOMMU_PROBE_SIZE: usize = size_of::<VirtioIommuProbeResvMem>();
+
+#[derive(Copy, Clone, Debug, Default)]
+#[repr(C, packed)]
+struct VirtioIommuProbeResvMem {
+    type_: u16,
+    length: u16,
+    subtype: u8,
+    reserved: [u8; 3],
+    start: u64,
+    end: u64,
+}
+
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for VirtioIommuProbeResvMem {}
+
+#[derive(Debug)]
+pub enum IommuError {
+    CreateWaitContext(SysError),
+    WaitError(SysError),
+    GuestMemoryRead(io::Error),
+    GuestMemoryWrite(io::Error),
+    CreateReader(DescriptorError),
+    CreateWriter(DescriptorError),
+    ReadQueueEvent(SysError),
+    UnexpectedDescriptor,
+    WriteBufferTooSmall,
+    VfioContainerError(VfioError),
+    GetHostAddress(GuestMemoryError),
+}
+
+impl Display for IommuError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        use self::IommuError::*;
+
+        match self {
+            CreateWaitContext(e) => write!(f, "failed to create wait context: {}", e),
+            WaitError(err) => write!(f, "failed to wait for events: {}", err),
+            GuestMemoryWrite(e) => write!(f, "failed to write to guest address: {}", e),
+            GuestMemoryRead(e) => write!(f, "failed to read from guest address: {}", e),
+            CreateReader(e) => write!(f, "failed to create reader: {}", e),
+            CreateWriter(e) => write!(f, "failed to create writer: {}", e),
+            ReadQueueEvent(err) => write!(f, "failed to read from virtio queue Event: {}", err),
+            UnexpectedDescriptor => write!(f, "unexpected descriptor error"),
+            WriteBufferTooSmall => write!(f, "write buffer length too small"),
+            VfioContainerError(e) => write!(f, "failed on VFIO ioctl call: {}", e),
+            GetHostAddress(e) => write!(f, "failed getting host address: {}", e),
+        }
+    }
+}
+
+struct Worker {
+    interrupt: Interrupt,
+    mem: GuestMemory,
+    page_mask: u64,
+    // contains all pass-through endpoints that attach to the IOMMU device
+    endpoints: BTreeMap<u32, Arc<Mutex<VfioContainer>>>,
+    // All PCI endpoints that attach to certain IOMMU domain
+    // key: endpoint PCI address
+    // value: attached domain ID
+    endpoint_map: BTreeMap<u32, u32>,
+    // All attached domains
+    // key: domain ID
+    // value: reference counter and VfioContainer
+    domain_map: BTreeMap<u32, (u32, Arc<Mutex<VfioContainer>>)>,
+}
+
+impl Worker {
+    // Remove the endpoint from the endpoint_map and
+    // decrement the reference counter (or remove the entry if the ref count is 1)
+    // from domain_map
+    fn dettach_endpoint(&mut self, endpoint: u32) {
+        // The endpoint has attached to an IOMMU domain
+        if let Some(attached_domain) = self.endpoint_map.get(&endpoint) {
+            // Remove the entry or update the domain reference count
+            if let Some(dm_val) = self.domain_map.get(&attached_domain) {
+                match dm_val.0 {
+                    0 => unreachable!(),
+                    1 => self.domain_map.remove(&attached_domain),
+                    _ => {
+                        let new_refs = dm_val.0 - 1;
+                        let vfio = dm_val.1.clone();
+                        self.domain_map.insert(*attached_domain, (new_refs, vfio))
+                    }
+                };
+            }
+        }
+
+        self.endpoint_map.remove(&endpoint);
+    }
+
+    // Notes: if a VFIO group contains multiple devices, it could violate the follow
+    // requirement from the virtio IOMMU spec: If the VIRTIO_IOMMU_F_BYPASS feature
+    // is negotiated, all accesses from unattached endpoints are allowed and translated
+    // by the IOMMU using the identity function. If the feature is not negotiated, any
+    // memory access from an unattached endpoint fails.
+    //
+    // This happens after the virtio-iommu device receives a VIRTIO_IOMMU_T_ATTACH
+    // request for the first endpoint in a VFIO group, any not yet attached endpoints
+    // in the VFIO group will be able to access the domain.
+    //
+    // This violation is benign for current virtualization use cases. Since device
+    // topology in the guest matches topology in the host, the guest doesn't expect
+    // the device in the same VFIO group are isolated from each other in the first place.
+    fn process_attach_request(
+        &mut self,
+        reader: &mut Reader,
+        tail: &mut VirtioIommuReqTail,
+    ) -> result::Result<usize, IommuError> {
+        let req: VirtioIommuReqAttach = reader.read_obj().map_err(IommuError::GuestMemoryRead)?;
+
+        // If the reserved field of an ATTACH request is not zero,
+        // the device MUST reject the request and set status to
+        // VIRTIO_IOMMU_S_INVAL.
+        if req.reserved.iter().any(|&x| x != 0) {
+            tail.status = VIRTIO_IOMMU_S_INVAL;
+            return Ok(0);
+        }
+
+        // If the endpoint identified by endpoint doesn’t exist,
+        // the device MUST reject the request and set status to
+        // VIRTIO_IOMMU_S_NOENT.
+        let domain = req.domain;
+        let endpoint = req.endpoint;
+        if !self.endpoints.contains_key(&endpoint) {
+            tail.status = VIRTIO_IOMMU_S_NOENT;
+            return Ok(0);
+        }
+
+        // If the endpoint identified by endpoint is already attached
+        // to another domain, then the device SHOULD first detach it
+        // from that domain and attach it to the one identified by domain.
+        if self.endpoint_map.contains_key(&endpoint) {
+            self.dettach_endpoint(endpoint);
+        }
+
+        if let Some(vfio_container) = self.endpoints.get(&endpoint) {
+            let new_ref = match self.domain_map.get(&domain) {
+                None => 1,
+                Some(val) => val.0 + 1,
+            };
+
+            self.endpoint_map.insert(endpoint, domain);
+            self.domain_map
+                .insert(domain, (new_ref, vfio_container.clone()));
+        }
+
+        Ok(0)
+    }
+
+    fn process_dma_map_request(
+        &mut self,
+        reader: &mut Reader,
+        tail: &mut VirtioIommuReqTail,
+    ) -> result::Result<usize, IommuError> {
+        let req: VirtioIommuReqMap = reader.read_obj().map_err(IommuError::GuestMemoryRead)?;
+
+        // If virt_start, phys_start or (virt_end + 1) is not aligned
+        // on the page granularity, the device SHOULD reject the
+        // request and set status to VIRTIO_IOMMU_S_RANGE
+        if self.page_mask & req.phys_start != 0
+            || self.page_mask & req.virt_start != 0
+            || self.page_mask & (req.virt_end + 1) != 0
+        {
+            tail.status = VIRTIO_IOMMU_S_RANGE;
+            return Ok(0);
+        }
+
+        // If the device doesn’t recognize a flags bit, it MUST reject
+        // the request and set status to VIRTIO_IOMMU_S_INVAL.
+        if req.flags & !VIRTIO_IOMMU_MAP_F_MASK != 0 {
+            tail.status = VIRTIO_IOMMU_S_INVAL;
+            return Ok(0);
+        }
+
+        let domain = req.domain;
+        if !self.domain_map.contains_key(&domain) {
+            // If domain does not exist, the device SHOULD reject
+            // the request and set status to VIRTIO_IOMMU_S_NOENT.
+            tail.status = VIRTIO_IOMMU_S_NOENT;
+            return Ok(0);
+        }
+
+        // The device MUST NOT allow writes to a range mapped
+        // without the VIRTIO_IOMMU_MAP_F_WRITE flag.
+        let write_en = req.flags & VIRTIO_IOMMU_MAP_F_WRITE != 0;
+
+        if let Some(vfio_container) = self.domain_map.get(&domain) {
+            let size = req.virt_end - req.virt_start + 1u64;
+            let host_addr = self
+                .mem
+                .get_host_address_range(GuestAddress(req.phys_start), size as usize)
+                .map_err(IommuError::GetHostAddress)?;
+
+            // Safe because both guest and host address are guaranteed by
+            // get_host_address_range() to be valid
+            let vfio_map_result = unsafe {
+                vfio_container.1.lock().vfio_dma_map(
+                    req.virt_start,
+                    size,
+                    host_addr as u64,
+                    write_en,
+                )
+            };
+
+            match vfio_map_result {
+                Ok(()) => (),
+                Err(e) => match sys_util::Error::last() {
+                    err if err.errno() == libc::EEXIST => {
+                        // If a mapping already exists in the requested range,
+                        // the device SHOULD reject the request and set status
+                        // to VIRTIO_IOMMU_S_INVAL.
+                        tail.status = VIRTIO_IOMMU_S_INVAL;
+                        return Ok(0);
+                    }
+                    _ => return Err(IommuError::VfioContainerError(e)),
+                },
+            }
+        }
+
+        Ok(0)
+    }
+
+    fn process_dma_unmap_request(
+        &mut self,
+        reader: &mut Reader,
+        tail: &mut VirtioIommuReqTail,
+    ) -> result::Result<usize, IommuError> {
+        let req: VirtioIommuReqUnmap = reader.read_obj().map_err(IommuError::GuestMemoryRead)?;
+
+        let domain = req.domain;
+        if let Some(vfio_container) = self.domain_map.get(&domain) {
+            let size = req.virt_end - req.virt_start + 1;
+            vfio_container
+                .1
+                .lock()
+                .vfio_dma_unmap(req.virt_start, size)
+                .map_err(IommuError::VfioContainerError)?;
+        } else {
+            // If domain does not exist, the device SHOULD set the
+            // request status to VIRTIO_IOMMU_S_NOENT
+            tail.status = VIRTIO_IOMMU_S_NOENT;
+        }
+
+        Ok(0)
+    }
+
+    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+    fn process_probe_request(
+        &mut self,
+        reader: &mut Reader,
+        writer: &mut Writer,
+        tail: &mut VirtioIommuReqTail,
+    ) -> result::Result<usize, IommuError> {
+        let req: VirtioIommuReqProbe = reader.read_obj().map_err(IommuError::GuestMemoryRead)?;
+        let endpoint = req.endpoint;
+
+        // If the endpoint identified by endpoint doesn’t exist,
+        // then the device SHOULD reject the request and set status
+        // to VIRTIO_IOMMU_S_NOENT.
+        if !self.endpoints.contains_key(&endpoint) {
+            tail.status = VIRTIO_IOMMU_S_NOENT;
+        }
+
+        let properties_size = writer.available_bytes() - size_of::<VirtioIommuReqTail>();
+
+        // It's OK if properties_size is larger than probe_size
+        // We are good even if properties_size is 0
+        if properties_size < IOMMU_PROBE_SIZE {
+            // If the properties list is smaller than probe_size, the device
+            // SHOULD NOT write any property. It SHOULD reject the request
+            // and set status to VIRTIO_IOMMU_S_INVAL.
+            tail.status = VIRTIO_IOMMU_S_INVAL;
+        } else if tail.status == VIRTIO_IOMMU_S_OK {
+            const VIRTIO_IOMMU_PROBE_T_RESV_MEM: u16 = 1;
+            const VIRTIO_IOMMU_RESV_MEM_T_MSI: u8 = 1;
+            const PROBE_PROPERTY_SIZE: u16 = 4;
+            const X86_MSI_IOVA_START: u64 = 0xfee0_0000;
+            const X86_MSI_IOVA_END: u64 = 0xfeef_ffff;
+
+            let properties = VirtioIommuProbeResvMem {
+                type_: VIRTIO_IOMMU_PROBE_T_RESV_MEM,
+                length: IOMMU_PROBE_SIZE as u16 - PROBE_PROPERTY_SIZE,
+                subtype: VIRTIO_IOMMU_RESV_MEM_T_MSI,
+                start: X86_MSI_IOVA_START,
+                end: X86_MSI_IOVA_END,
+                ..Default::default()
+            };
+            writer
+                .write_all(properties.as_slice())
+                .map_err(IommuError::GuestMemoryWrite)?;
+        }
+
+        // If the device doesn’t fill all probe_size bytes with properties,
+        // it SHOULD fill the remaining bytes of properties with zeroes.
+        let remaining_bytes = writer.available_bytes() - size_of::<VirtioIommuReqTail>();
+
+        if remaining_bytes > 0 {
+            let buffer: Vec<u8> = vec![0; remaining_bytes];
+            writer
+                .write_all(buffer.as_slice())
+                .map_err(IommuError::GuestMemoryWrite)?;
+        }
+
+        Ok(properties_size)
+    }
+
+    fn execute_request(
+        &mut self,
+        avail_desc: &DescriptorChain,
+    ) -> result::Result<usize, IommuError> {
+        let mut reader =
+            Reader::new(self.mem.clone(), avail_desc.clone()).map_err(IommuError::CreateReader)?;
+        let mut writer =
+            Writer::new(self.mem.clone(), avail_desc.clone()).map_err(IommuError::CreateWriter)?;
+
+        // at least we need space to write VirtioIommuReqTail
+        if writer.available_bytes() < size_of::<VirtioIommuReqTail>() {
+            return Err(IommuError::WriteBufferTooSmall);
+        }
+
+        let req_head: VirtioIommuReqHead =
+            reader.read_obj().map_err(IommuError::GuestMemoryRead)?;
+
+        let mut tail = VirtioIommuReqTail {
+            status: VIRTIO_IOMMU_S_OK,
+            ..Default::default()
+        };
+
+        let reply_len = match req_head.type_ {
+            VIRTIO_IOMMU_T_ATTACH => self.process_attach_request(&mut reader, &mut tail)?,
+            VIRTIO_IOMMU_T_DETACH => {
+                // A few reasons why we don't support VIRTIO_IOMMU_T_DETACH for now:
+                //
+                // 1. Linux virtio IOMMU front-end driver doesn't implement VIRTIO_IOMMU_T_DETACH request
+                // 2. Seems it's not possible to dynamically attach and detach a IOMMU domain if the
+                //    virtio IOMMU device is running on top of VFIO
+                // 3. Even if VIRTIO_IOMMU_T_DETACH is implemented in front-end driver, it could violate
+                //    the following virtio IOMMU spec: Detach an endpoint from a domain. when this request
+                //    completes, the endpoint cannot access any mapping from that domain anymore.
+                //
+                //    This is because VFIO doesn't support detaching a single device. When the virtio-iommu
+                //    device receives a VIRTIO_IOMMU_T_DETACH request, it can either to:
+                //    - detach a group: any other endpoints in the group lose access to the domain.
+                //    - do not detach the group at all: this breaks the above mentioned spec.
+                tail.status = VIRTIO_IOMMU_S_UNSUPP;
+                0
+            }
+            VIRTIO_IOMMU_T_MAP => self.process_dma_map_request(&mut reader, &mut tail)?,
+            VIRTIO_IOMMU_T_UNMAP => self.process_dma_unmap_request(&mut reader, &mut tail)?,
+            #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+            VIRTIO_IOMMU_T_PROBE => {
+                self.process_probe_request(&mut reader, &mut writer, &mut tail)?
+            }
+            _ => return Err(IommuError::UnexpectedDescriptor),
+        };
+
+        writer
+            .write_all(tail.as_slice())
+            .map_err(IommuError::GuestMemoryWrite)?;
+        Ok((reply_len as usize) + size_of::<VirtioIommuReqTail>())
+    }
+
+    fn request_queue(&mut self, req_queue: &mut Queue) -> bool {
+        let mut needs_interrupt = false;
+        while let Some(avail_desc) = req_queue.pop(&self.mem) {
+            let desc_index = avail_desc.index;
+
+            let len = match self.execute_request(&avail_desc) {
+                Ok(len) => len as u32,
+                Err(e) => {
+                    error!("{}", e);
+
+                    // If a request type is not recognized, the device SHOULD NOT write
+                    // the buffer and SHOULD set the used length to zero
+                    0
+                }
+            };
+
+            req_queue.add_used(&self.mem, desc_index, len as u32);
+            needs_interrupt = true;
+        }
+
+        needs_interrupt
+    }
+
+    fn run(
+        &mut self,
+        mut queues: Vec<Queue>,
+        mut queue_evts: Vec<Event>,
+        kill_evt: Event,
+    ) -> Result<(), IommuError> {
+        #[derive(PollToken)]
+        enum Token {
+            RequestQueue,
+            InterruptResample,
+            Kill,
+        }
+
+        let (mut req_queue, req_evt) = (queues.remove(0), queue_evts.remove(0));
+        let wait_ctx: WaitContext<Token> =
+            WaitContext::build_with(&[(&req_evt, Token::RequestQueue), (&kill_evt, Token::Kill)])
+                .map_err(IommuError::CreateWaitContext)?;
+
+        if let Some(resample_evt) = self.interrupt.get_resample_evt() {
+            wait_ctx
+                .add(resample_evt, Token::InterruptResample)
+                .map_err(IommuError::CreateWaitContext)?;
+        }
+
+        'wait: loop {
+            let mut needs_interrupt = false;
+            let events = wait_ctx.wait().map_err(IommuError::WaitError)?;
+            for event in events.iter().filter(|e| e.is_readable) {
+                match event.token {
+                    Token::RequestQueue => {
+                        req_evt.read().map_err(IommuError::ReadQueueEvent)?;
+                        needs_interrupt |= self.request_queue(&mut req_queue);
+                    }
+                    Token::InterruptResample => {
+                        self.interrupt.interrupt_resample();
+                    }
+                    Token::Kill => break 'wait,
+                }
+            }
+            if needs_interrupt {
+                req_queue.trigger_interrupt(&self.mem, &self.interrupt);
+            }
+        }
+        Ok(())
+    }
+}
+
+/// Virtio device for IOMMU memory management.
+pub struct Iommu {
+    kill_evt: Option<Event>,
+    worker_thread: Option<thread::JoinHandle<Worker>>,
+    config: VirtioIommuConfig,
+    avail_features: u64,
+    endpoints: BTreeMap<u32, Arc<Mutex<VfioContainer>>>,
+}
+
+impl Iommu {
+    /// Create a new virtio IOMMU device.
+    pub fn new(
+        base_features: u64,
+        endpoints: BTreeMap<u32, Arc<Mutex<VfioContainer>>>,
+        phys_max_addr: u64,
+    ) -> SysResult<Iommu> {
+        let mut page_size_mask = !0_u64;
+        for (_, container) in endpoints.iter() {
+            page_size_mask &= container
+                .lock()
+                .vfio_get_iommu_page_size_mask()
+                .map_err(|_e| SysError::new(libc::EIO))?;
+        }
+
+        if page_size_mask == 0 {
+            error!("failed to get IOMMU device valid page size masks");
+            return Err(SysError::new(libc::EIO));
+        }
+
+        let input_range = VirtioIommuRange64 {
+            start: 0_u64,
+            end: phys_max_addr,
+        };
+
+        let config = VirtioIommuConfig {
+            page_size_mask,
+            input_range,
+            #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+            probe_size: IOMMU_PROBE_SIZE as u32,
+            ..Default::default()
+        };
+
+        let mut avail_features: u64 = base_features;
+        avail_features |= 1 << VIRTIO_IOMMU_F_MAP_UNMAP | 1 << VIRTIO_IOMMU_F_INPUT_RANGE;
+
+        if cfg!(any(target_arch = "x86", target_arch = "x86_64")) {
+            avail_features |= 1 << VIRTIO_IOMMU_F_PROBE;
+        }
+
+        Ok(Iommu {
+            kill_evt: None,
+            worker_thread: None,
+            config,
+            avail_features,
+            endpoints,
+        })
+    }
+}
+
+impl Drop for Iommu {
+    fn drop(&mut self) {
+        if let Some(kill_evt) = self.kill_evt.take() {
+            let _ = kill_evt.write(1);
+        }
+
+        if let Some(worker_thread) = self.worker_thread.take() {
+            let _ = worker_thread.join();
+        }
+    }
+}
+
+impl VirtioDevice for Iommu {
+    fn keep_rds(&self) -> Vec<RawDescriptor> {
+        let mut rds = Vec::new();
+
+        for (_, vfio) in self.endpoints.iter() {
+            rds.push(vfio.lock().as_raw_descriptor());
+        }
+        rds
+    }
+
+    fn device_type(&self) -> u32 {
+        TYPE_IOMMU
+    }
+
+    fn queue_max_sizes(&self) -> &[u16] {
+        QUEUE_SIZES
+    }
+
+    fn features(&self) -> u64 {
+        self.avail_features
+    }
+
+    fn read_config(&self, offset: u64, data: &mut [u8]) {
+        let mut config: Vec<u8> = Vec::new();
+        config.extend_from_slice(self.config.as_slice());
+        copy_config(data, 0, config.as_slice(), offset);
+    }
+
+    fn activate(
+        &mut self,
+        mem: GuestMemory,
+        interrupt: Interrupt,
+        queues: Vec<Queue>,
+        queue_evts: Vec<Event>,
+    ) {
+        if queues.len() != QUEUE_SIZES.len() || queue_evts.len() != QUEUE_SIZES.len() {
+            return;
+        }
+
+        let (self_kill_evt, kill_evt) = match Event::new().and_then(|e| Ok((e.try_clone()?, e))) {
+            Ok(v) => v,
+            Err(e) => {
+                error!("failed to create kill Event pair: {}", e);
+                return;
+            }
+        };
+        self.kill_evt = Some(self_kill_evt);
+
+        // The least significant bit of page_size_masks defines the page
+        // granularity of IOMMU mappings
+        let page_mask = (1u64 << self.config.page_size_mask.trailing_zeros()) - 1;
+        let eps = self.endpoints.clone();
+        let worker_result = thread::Builder::new()
+            .name("virtio_iommu".to_string())
+            .spawn(move || {
+                let mut worker = Worker {
+                    interrupt,
+                    mem,
+                    page_mask,
+                    endpoints: eps,
+                    endpoint_map: BTreeMap::new(),
+                    domain_map: BTreeMap::new(),
+                };
+                let result = worker.run(queues, queue_evts, kill_evt);
+                if let Err(e) = result {
+                    error!("virtio-iommu worker thread exited with error: {}", e);
+                }
+                worker
+            });
+
+        match worker_result {
+            Err(e) => error!("failed to spawn virtio_iommu worker thread: {}", e),
+            Ok(join_handle) => self.worker_thread = Some(join_handle),
+        }
+    }
+
+    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+    fn generate_acpi(
+        &mut self,
+        pci_address: &Option<PciAddress>,
+        mut sdts: Vec<SDT>,
+    ) -> Option<Vec<SDT>> {
+        const OEM_REVISION: u32 = 1;
+        const VIOT_REVISION: u8 = 0;
+
+        for sdt in sdts.iter() {
+            // there should only be one VIOT table
+            if sdt.is_signature(b"VIOT") {
+                warn!("vIOMMU: duplicate VIOT table detected");
+                return None;
+            }
+        }
+
+        let mut viot = SDT::new(
+            *b"VIOT",
+            acpi_tables::HEADER_LEN,
+            VIOT_REVISION,
+            *b"CROSVM",
+            *b"CROSVMDT",
+            OEM_REVISION,
+        );
+        viot.append(VirtioIommuViotHeader {
+            // # of PCI range nodes + 1 virtio-pci node
+            node_count: (self.endpoints.len() + 1) as u16,
+            node_offset: (viot.len() + std::mem::size_of::<VirtioIommuViotHeader>()) as u16,
+            ..Default::default()
+        });
+
+        let bdf = pci_address
+            .or_else(|| {
+                error!("vIOMMU device has no PCI address");
+                None
+            })?
+            .to_u32() as u16;
+        let iommu_offset = viot.len();
+
+        viot.append(VirtioIommuViotVirtioPciNode {
+            type_: VIRTIO_IOMMU_VIOT_NODE_VIRTIO_IOMMU_PCI,
+            length: size_of::<VirtioIommuViotVirtioPciNode>() as u16,
+            bdf,
+            ..Default::default()
+        });
+
+        for (endpoint, _) in self.endpoints.iter() {
+            viot.append(VirtioIommuViotPciRangeNode {
+                type_: VIRTIO_IOMMU_VIOT_NODE_PCI_RANGE,
+                length: size_of::<VirtioIommuViotPciRangeNode>() as u16,
+                endpoint_start: *endpoint,
+                bdf_start: *endpoint as u16,
+                bdf_end: *endpoint as u16,
+                output_node: iommu_offset as u16,
+                ..Default::default()
+            });
+        }
+        sdts.push(viot);
+        Some(sdts)
+    }
+}
diff --git a/devices/src/virtio/mod.rs b/devices/src/virtio/mod.rs
index 0daf6ea..b9c069e 100644
--- a/devices/src/virtio/mod.rs
+++ b/devices/src/virtio/mod.rs
@@ -5,10 +5,10 @@
 //! Implements virtio devices, queues, and transport mechanisms.
 
 mod balloon;
-mod console;
 mod descriptor_utils;
 mod input;
 mod interrupt;
+mod iommu;
 mod p9;
 mod pmem;
 mod queue;
@@ -23,6 +23,7 @@
 pub mod wl;
 
 pub mod block;
+pub mod console;
 pub mod fs;
 #[cfg(feature = "gpu")]
 pub mod gpu;
@@ -41,11 +42,14 @@
 pub use self::gpu::*;
 pub use self::input::*;
 pub use self::interrupt::*;
+pub use self::iommu::*;
 pub use self::net::*;
 pub use self::p9::*;
 pub use self::pmem::*;
 pub use self::queue::*;
 pub use self::rng::*;
+#[cfg(feature = "audio")]
+pub use self::snd::*;
 #[cfg(feature = "tpm")]
 pub use self::tpm::*;
 #[cfg(any(feature = "video-decoder", feature = "video-encoder"))]
@@ -58,6 +62,8 @@
 use std::cmp;
 use std::convert::TryFrom;
 
+use virtio_sys::virtio_ring::VIRTIO_RING_F_EVENT_IDX;
+
 const DEVICE_RESET: u32 = 0x0;
 const DEVICE_ACKNOWLEDGE: u32 = 0x01;
 const DEVICE_DRIVER: u32 = 0x02;
@@ -81,8 +87,10 @@
 const TYPE_VSOCK: u32 = 19;
 const TYPE_CRYPTO: u32 = 20;
 const TYPE_IOMMU: u32 = 23;
+const TYPE_SOUND: u32 = 25;
 const TYPE_FS: u32 = 26;
 const TYPE_PMEM: u32 = 27;
+const TYPE_MAC80211_HWSIM: u32 = 29;
 const TYPE_VIDEO_ENC: u32 = 30;
 const TYPE_VIDEO_DEC: u32 = 31;
 // Additional types invented by crosvm
@@ -120,6 +128,7 @@
         TYPE_VSOCK => "vsock",
         TYPE_CRYPTO => "crypto",
         TYPE_IOMMU => "iommu",
+        TYPE_SOUND => "snd",
         TYPE_FS => "fs",
         TYPE_PMEM => "pmem",
         TYPE_WL => "wl",
@@ -156,7 +165,7 @@
 
 /// Returns the set of reserved base features common to all virtio devices.
 pub fn base_features(protected_vm: ProtectionType) -> u64 {
-    let mut features: u64 = 1 << VIRTIO_F_VERSION_1;
+    let mut features: u64 = 1 << VIRTIO_F_VERSION_1 | 1 << VIRTIO_RING_F_EVENT_IDX;
 
     if protected_vm == ProtectionType::Protected {
         features |= 1 << VIRTIO_F_ACCESS_PLATFORM;
diff --git a/devices/src/virtio/net.rs b/devices/src/virtio/net.rs
index 6dcad5c..a9153ac 100644
--- a/devices/src/virtio/net.rs
+++ b/devices/src/virtio/net.rs
@@ -191,7 +191,7 @@
     }
 
     if needs_interrupt {
-        interrupt.signal_used_queue(rx_queue.vector);
+        rx_queue.trigger_interrupt(mem, interrupt);
     }
 
     if exhausted_queue {
@@ -233,7 +233,7 @@
         tx_queue.add_used(mem, index, 0);
     }
 
-    interrupt.signal_used_queue(tx_queue.vector);
+    tx_queue.trigger_interrupt(mem, interrupt);
 }
 
 pub fn process_ctrl<I: SignalableInterrupt, T: TapT>(
@@ -306,7 +306,7 @@
         ctrl_queue.add_used(&mem, index, writer.bytes_written() as u32);
     }
 
-    interrupt.signal_used_queue(ctrl_queue.vector);
+    ctrl_queue.trigger_interrupt(mem, interrupt);
     Ok(())
 }
 
diff --git a/devices/src/virtio/p9.rs b/devices/src/virtio/p9.rs
index 670645d..a4f5629 100644
--- a/devices/src/virtio/p9.rs
+++ b/devices/src/virtio/p9.rs
@@ -99,8 +99,7 @@
             self.queue
                 .add_used(&self.mem, avail_desc.index, writer.bytes_written() as u32);
         }
-
-        self.interrupt.signal_used_queue(self.queue.vector);
+        self.queue.trigger_interrupt(&self.mem, &self.interrupt);
 
         Ok(())
     }
diff --git a/devices/src/virtio/pmem.rs b/devices/src/virtio/pmem.rs
index 9a3ca39..1e0c55b 100644
--- a/devices/src/virtio/pmem.rs
+++ b/devices/src/virtio/pmem.rs
@@ -217,7 +217,7 @@
                 }
             }
             if needs_interrupt {
-                self.interrupt.signal_used_queue(self.queue.vector);
+                self.queue.trigger_interrupt(&self.memory, &self.interrupt);
             }
         }
     }
diff --git a/devices/src/virtio/queue.rs b/devices/src/virtio/queue.rs
index 1fd614b..06ae892 100644
--- a/devices/src/virtio/queue.rs
+++ b/devices/src/virtio/queue.rs
@@ -345,6 +345,9 @@
     //
     // This value is only used if the `VIRTIO_F_EVENT_IDX` feature has been negotiated.
     fn set_avail_event(&mut self, mem: &GuestMemory, avail_index: Wrapping<u16>) {
+        // Ensure that all previous writes are available before this one.
+        fence(Ordering::Release);
+
         let avail_event_addr = self
             .used_ring
             .unchecked_add(4 + 8 * u64::from(self.actual_size()));
@@ -358,6 +361,10 @@
     #[allow(dead_code)]
     fn get_avail_flag(&self, mem: &GuestMemory, flag: u16) -> bool {
         let avail_flags: u16 = mem.read_obj_from_addr(self.avail_ring).unwrap();
+
+        // Don't allow subsequent reads to be ordered before the avail_flags read.
+        fence(Ordering::Acquire);
+
         avail_flags & flag == flag
     }
 
@@ -373,6 +380,10 @@
             .avail_ring
             .unchecked_add(4 + 2 * u64::from(self.actual_size()));
         let used_event: u16 = mem.read_obj_from_addr(used_event_addr).unwrap();
+
+        // Prevent any reads after this from being ordered before the used_event read.
+        fence(Ordering::Acquire);
+
         Wrapping(used_event)
     }
 
@@ -393,6 +404,9 @@
     //
     // Changes the bit specified by the mask in `flag` to `value`.
     fn set_used_flag(&mut self, mem: &GuestMemory, flag: u16, value: bool) {
+        // This fence ensures all descriptor writes are visible before the flag update.
+        fence(Ordering::Release);
+
         let mut used_flags: u16 = mem.read_obj_from_addr(self.used_ring).unwrap();
         if value {
             used_flags |= flag;
@@ -488,11 +502,6 @@
         self.set_used_index(mem, self.next_used);
     }
 
-    /// Updates the index at which the driver should signal the device next.
-    pub fn update_int_required(&mut self, mem: &GuestMemory) {
-        self.set_avail_event(mem, self.get_avail_index(mem));
-    }
-
     /// Enable / Disable guest notify device that requests are available on
     /// the descriptor chain.
     pub fn set_notify(&mut self, mem: &GuestMemory, enable: bool) {
@@ -502,9 +511,9 @@
             self.notification_disable_count += 1;
         }
 
-        if self.features & ((1u64) << VIRTIO_RING_F_EVENT_IDX) != 0 {
-            self.update_int_required(mem);
-        } else {
+        // We should only set VIRTQ_USED_F_NO_NOTIFY when the VIRTIO_RING_F_EVENT_IDX feature has
+        // not been negotiated.
+        if self.features & ((1u64) << VIRTIO_RING_F_EVENT_IDX) == 0 {
             self.set_used_flag(
                 mem,
                 VIRTQ_USED_F_NO_NOTIFY,
@@ -523,13 +532,7 @@
             // so no need to inject new interrupt.
             self.next_used - used_event - Wrapping(1) < self.next_used - self.last_used
         } else {
-            // TODO(b/172975852): This branch should check the flag that requests interrupt
-            // supression:
-            // ```
-            // !self.get_avail_flag(mem, VIRTQ_AVAIL_F_NO_INTERRUPT)
-            // ```
-            // Re-enable the flag check once the missing interrupt issue is debugged.
-            true
+            !self.get_avail_flag(mem, VIRTQ_AVAIL_F_NO_INTERRUPT)
         }
     }
 
diff --git a/devices/src/virtio/rng.rs b/devices/src/virtio/rng.rs
index 65d671e..5b9da29 100644
--- a/devices/src/virtio/rng.rs
+++ b/devices/src/virtio/rng.rs
@@ -119,7 +119,7 @@
                 }
             }
             if needs_interrupt {
-                self.interrupt.signal_used_queue(self.queue.vector);
+                self.queue.trigger_interrupt(&self.mem, &self.interrupt);
             }
         }
     }
diff --git a/devices/src/virtio/snd/constants.rs b/devices/src/virtio/snd/constants.rs
index 28a73a0..114d098 100644
--- a/devices/src/virtio/snd/constants.rs
+++ b/devices/src/virtio/snd/constants.rs
@@ -1,21 +1,41 @@
 // 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.
-pub const JACK_INFO: u32 = 1;
-pub const JACK_REMAP: u32 = 2;
+pub const VIRTIO_SND_R_JACK_INFO: u32 = 1;
+pub const VIRTIO_SND_R_JACK_REMAP: u32 = 2;
 
-pub const STREAM_INFO: u32 = 0x0100;
-pub const STREAM_SET_PARAMS: u32 = 0x0100 + 1;
-pub const STREAM_PREPARE: u32 = 0x0100 + 2;
-pub const STREAM_RELEASE: u32 = 0x0100 + 3;
-pub const STREAM_START: u32 = 0x0100 + 4;
-pub const STREAM_STOP: u32 = 0x0100 + 5;
+/* PCM control request types */
+pub const VIRTIO_SND_R_PCM_INFO: u32 = 0x0100;
+pub const VIRTIO_SND_R_PCM_SET_PARAMS: u32 = 0x0101;
+pub const VIRTIO_SND_R_PCM_PREPARE: u32 = 0x0102;
+pub const VIRTIO_SND_R_PCM_RELEASE: u32 = 0x0103;
+pub const VIRTIO_SND_R_PCM_START: u32 = 0x0104;
+pub const VIRTIO_SND_R_PCM_STOP: u32 = 0x0105;
 
-pub const CHANNEL_MAP_INFO: u32 = 0x0200;
+/* channel map control request types */
+pub const VIRTIO_SND_R_CHMAP_INFO: u32 = 0x0200;
 
+/* jack event types */
+pub const VIRTIO_SND_EVT_JACK_CONNECTED: u32 = 0x1000;
+pub const VIRTIO_SND_EVT_JACK_DISCONNECTED: u32 = 0x1001;
+
+/* PCM event types */
+pub const VIRTIO_SND_EVT_PCM_PERIOD_ELAPSED: u32 = 0x1100;
+pub const VIRTIO_SND_EVT_PCM_XRUN: u32 = 0x1101;
+
+/* common status codes */
+pub const VIRTIO_SND_S_OK: u32 = 0x8000;
+pub const VIRTIO_SND_S_BAD_MSG: u32 = 0x8001;
+pub const VIRTIO_SND_S_NOT_SUPP: u32 = 0x8002;
+pub const VIRTIO_SND_S_IO_ERR: u32 = 0x8003;
+
+/* stream direction */
 pub const VIRTIO_SND_D_OUTPUT: u8 = 0;
 pub const VIRTIO_SND_D_INPUT: u8 = 1;
 
+/* supported jack features */
+pub const VIRTIO_SND_JACK_F_REMAP: u32 = 0;
+
 /* supported PCM stream features */
 pub const VIRTIO_SND_PCM_F_SHMEM_HOST: u8 = 0;
 pub const VIRTIO_SND_PCM_F_SHMEM_GUEST: u8 = 1;
@@ -45,11 +65,13 @@
 pub const VIRTIO_SND_PCM_FMT_U32: u8 = 18;
 pub const VIRTIO_SND_PCM_FMT_FLOAT: u8 = 19;
 pub const VIRTIO_SND_PCM_FMT_FLOAT64: u8 = 20;
+/* digital formats (width / physical width) */
 pub const VIRTIO_SND_PCM_FMT_DSD_U8: u8 = 21;
 pub const VIRTIO_SND_PCM_FMT_DSD_U16: u8 = 22;
 pub const VIRTIO_SND_PCM_FMT_DSD_U32: u8 = 23;
 pub const VIRTIO_SND_PCM_FMT_IEC958_SUBFRAME: u8 = 24;
 
+/* supported PCM frame rates */
 pub const VIRTIO_SND_PCM_RATE_5512: u8 = 0;
 pub const VIRTIO_SND_PCM_RATE_8000: u8 = 1;
 pub const VIRTIO_SND_PCM_RATE_11025: u8 = 2;
@@ -65,38 +87,6 @@
 pub const VIRTIO_SND_PCM_RATE_192000: u8 = 12;
 pub const VIRTIO_SND_PCM_RATE_384000: u8 = 13;
 
-// From https://github.com/oasis-tcs/virtio-spec/blob/master/virtio-sound.tex
-/* jack control request types */
-pub const VIRTIO_SND_R_JACK_INFO: u32 = 1;
-pub const VIRTIO_SND_R_JACK_REMAP: u32 = 2;
-
-/* PCM control request types */
-pub const VIRTIO_SND_R_PCM_INFO: u32 = 0x0100;
-pub const VIRTIO_SND_R_PCM_SET_PARAMS: u32 = 0x0101;
-pub const VIRTIO_SND_R_PCM_PREPARE: u32 = 0x0102;
-pub const VIRTIO_SND_R_PCM_RELEASE: u32 = 0x0103;
-pub const VIRTIO_SND_R_PCM_START: u32 = 0x0104;
-pub const VIRTIO_SND_R_PCM_STOP: u32 = 0x0105;
-
-/* channel map control request types */
-pub const VIRTIO_SND_R_CHMAP_INFO: u32 = 0x0200;
-
-/* jack event types */
-pub const VIRTIO_SND_EVT_JACK_CONNECTED: u32 = 0x1000;
-pub const VIRTIO_SND_EVT_JACK_DISCONNECTED: u32 = 0x1001;
-
-/* PCM event types */
-pub const VIRTIO_SND_EVT_PCM_PERIOD_ELAPSED: u32 = 0x1100;
-pub const VIRTIO_SND_EVT_PCM_XRUN: u32 = 0x1101;
-
-/* common status codes */
-pub const VIRTIO_SND_S_OK: u32 = 0x8000;
-pub const VIRTIO_SND_S_BAD_MSG: u32 = 0x8001;
-pub const VIRTIO_SND_S_NOT_SUPP: u32 = 0x8002;
-pub const VIRTIO_SND_S_IO_ERR: u32 = 0x8003;
-
-pub const VIRTIO_SND_JACK_F_REMAP: u32 = 0;
-
 /* standard channel position definition */
 pub const VIRTIO_SND_CHMAP_NONE: u32 = 0; /* undefined */
 pub const VIRTIO_SND_CHMAP_NA: u32 = 1; /* silent */
diff --git a/devices/src/virtio/snd/layout.rs b/devices/src/virtio/snd/layout.rs
index 8881af0..a217bff 100644
--- a/devices/src/virtio/snd/layout.rs
+++ b/devices/src/virtio/snd/layout.rs
@@ -41,7 +41,7 @@
 // Safe because it only has data and has no implicit padding.
 unsafe impl DataInit for virtio_snd_event {}
 
-#[derive(Copy, Clone)]
+#[derive(Copy, Clone, Default)]
 #[repr(C)]
 pub struct virtio_snd_query_info {
     pub hdr: virtio_snd_hdr,
@@ -76,7 +76,7 @@
 // Safe because it only has data and has no implicit padding.
 unsafe impl DataInit for virtio_snd_pcm_info {}
 
-#[derive(Copy, Clone)]
+#[derive(Copy, Clone, Default)]
 #[repr(C)]
 pub struct virtio_snd_pcm_hdr {
     pub hdr: virtio_snd_hdr,
@@ -85,7 +85,7 @@
 // Safe because it only has data and has no implicit padding.
 unsafe impl DataInit for virtio_snd_pcm_hdr {}
 
-#[derive(Copy, Clone)]
+#[derive(Copy, Clone, Default)]
 #[repr(C)]
 pub struct virtio_snd_pcm_set_params {
     pub hdr: virtio_snd_pcm_hdr,
@@ -117,7 +117,7 @@
 // Safe because it only has data and has no implicit padding.
 unsafe impl DataInit for virtio_snd_pcm_status {}
 
-#[derive(Copy, Clone)]
+#[derive(Copy, Clone, Default)]
 #[repr(C)]
 pub struct virtio_snd_jack_info {
     pub hdr: virtio_snd_info,
@@ -130,7 +130,7 @@
 // Safe because it only has data and has no implicit padding.
 unsafe impl DataInit for virtio_snd_jack_info {}
 
-#[derive(Copy, Clone)]
+#[derive(Copy, Clone, Default)]
 #[repr(C)]
 pub struct virtio_snd_jack_remap {
     pub hdr: virtio_snd_jack_hdr, /* .code = VIRTIO_SND_R_JACK_REMAP */
@@ -140,7 +140,7 @@
 // Safe because it only has data and has no implicit padding.
 unsafe impl DataInit for virtio_snd_jack_remap {}
 
-#[derive(Copy, Clone)]
+#[derive(Copy, Clone, Default)]
 #[repr(C)]
 pub struct virtio_snd_chmap_info {
     pub hdr: virtio_snd_info,
diff --git a/devices/src/virtio/snd/mod.rs b/devices/src/virtio/snd/mod.rs
index e6b87ac..cb65893 100644
--- a/devices/src/virtio/snd/mod.rs
+++ b/devices/src/virtio/snd/mod.rs
@@ -8,3 +8,6 @@
 pub mod layout;
 
 pub mod vios_backend;
+
+pub use vios_backend::new_sound;
+pub use vios_backend::SoundError;
diff --git a/devices/src/virtio/snd/vios_backend/mod.rs b/devices/src/virtio/snd/vios_backend/mod.rs
index 602ec4d..c993b37 100644
--- a/devices/src/virtio/snd/vios_backend/mod.rs
+++ b/devices/src/virtio/snd/vios_backend/mod.rs
@@ -9,3 +9,217 @@
 pub use self::shm_streams::*;
 
 pub use self::shm_vios::*;
+
+pub mod streams;
+
+mod worker;
+
+use std::thread;
+
+use crate::virtio::{copy_config, DescriptorError, Interrupt, Queue, VirtioDevice, TYPE_SOUND};
+use base::{error, Error as BaseError, Event, RawDescriptor};
+use data_model::{DataInit, Le32};
+use sync::Mutex;
+use vm_memory::GuestMemory;
+
+use std::path::Path;
+use std::sync::mpsc::{RecvError, SendError};
+use std::sync::Arc;
+
+use super::layout::*;
+use streams::StreamMsg;
+use worker::*;
+
+use std::io::Error as IoError;
+use thiserror::Error as ThisError;
+
+const QUEUE_SIZES: &[u16] = &[64, 64, 64, 64];
+
+#[derive(ThisError, Debug)]
+pub enum SoundError {
+    #[error("Failed to create VioS client: {0}")]
+    ClientNew(Error),
+    #[error("Failed to get event notifier from VioS client: {0}")]
+    ClientEventNotifier(Error),
+    #[error("Error creating WaitContext: {0}")]
+    WaitCtx(BaseError),
+    #[error("Error consuming queue event: {0}")]
+    QueueEvt(BaseError),
+    #[error("Error with queue descriptor: {0}")]
+    Descriptor(DescriptorError),
+    #[error("The driver sent an invalid message")]
+    BadDriverMsg,
+    #[error("Failed to read/write from/to queue: {0}")]
+    QueueIO(IoError),
+    #[error("Failed to create thread: {0}")]
+    CreateThread(IoError),
+    #[error("Attempted a {0} operation while on the wrong state: {1}, this is a bug")]
+    ImpossibleState(&'static str, &'static str),
+    #[error("Failed to create event pair: {0}")]
+    CreateEvent(BaseError),
+    #[error("Failed to send message: {0}")]
+    StreamThreadSend(SendError<StreamMsg>),
+    #[error("Failed to receive message: {0}")]
+    StreamThreadRecv(RecvError),
+    #[error("Failed to create Reader from descriptor chain: {0}")]
+    CreateReader(DescriptorError),
+    #[error("Failed to create Writer from descriptor chain: {0}")]
+    CreateWriter(DescriptorError),
+}
+
+pub type Result<T> = std::result::Result<T, SoundError>;
+
+pub struct Sound {
+    config: virtio_snd_config,
+    virtio_features: u64,
+    worker_thread: Option<thread::JoinHandle<bool>>,
+    kill_evt: Option<Event>,
+    vios_client: Arc<VioSClient>,
+}
+
+impl VirtioDevice for Sound {
+    fn keep_rds(&self) -> Vec<RawDescriptor> {
+        self.vios_client.keep_fds()
+    }
+
+    fn device_type(&self) -> u32 {
+        TYPE_SOUND
+    }
+
+    fn queue_max_sizes(&self) -> &[u16] {
+        QUEUE_SIZES
+    }
+
+    fn read_config(&self, offset: u64, data: &mut [u8]) {
+        copy_config(data, 0, self.config.as_slice(), offset);
+    }
+
+    fn write_config(&mut self, _offset: u64, _data: &[u8]) {
+        error!("virtio-snd: driver attempted a config write which is not allowed by the spec");
+    }
+
+    fn features(&self) -> u64 {
+        self.virtio_features
+    }
+
+    fn activate(
+        &mut self,
+        mem: GuestMemory,
+        interrupt: Interrupt,
+        mut queues: Vec<Queue>,
+        mut queue_evts: Vec<Event>,
+    ) {
+        if self.worker_thread.is_some() {
+            error!("virtio-snd: Device is already active");
+            return;
+        }
+        if queues.len() != 4 || queue_evts.len() != 4 {
+            error!(
+                "virtio-snd: device activated with wrong number of queues: {}, {}",
+                queues.len(),
+                queue_evts.len()
+            );
+            return;
+        }
+        let (self_kill_evt, kill_evt) = match Event::new().and_then(|e| Ok((e.try_clone()?, e))) {
+            Ok(v) => v,
+            Err(e) => {
+                error!("virtio-snd: failed to create kill Event pair: {}", e);
+                return;
+            }
+        };
+        self.kill_evt = Some(self_kill_evt);
+        let control_queue = queues.remove(0);
+        let control_queue_evt = queue_evts.remove(0);
+        let event_queue = queues.remove(0);
+        let event_queue_evt = queue_evts.remove(0);
+        let tx_queue = queues.remove(0);
+        let tx_queue_evt = queue_evts.remove(0);
+        let rx_queue = queues.remove(0);
+        let rx_queue_evt = queue_evts.remove(0);
+
+        let vios_client = self.vios_client.clone();
+        if let Err(e) = vios_client.start_bg_thread() {
+            error!("Failed to start vios background thread: {}", e);
+        }
+
+        let thread_result = thread::Builder::new()
+            .name(String::from("virtio_snd"))
+            .spawn(move || {
+                match Worker::try_new(
+                    vios_client,
+                    Arc::new(interrupt),
+                    mem,
+                    Arc::new(Mutex::new(control_queue)),
+                    control_queue_evt,
+                    event_queue,
+                    event_queue_evt,
+                    Arc::new(Mutex::new(tx_queue)),
+                    tx_queue_evt,
+                    Arc::new(Mutex::new(rx_queue)),
+                    rx_queue_evt,
+                ) {
+                    Ok(mut worker) => match worker.control_loop(kill_evt) {
+                        Ok(_) => true,
+                        Err(e) => {
+                            error!("virtio-snd: Error in worker loop: {}", e);
+                            false
+                        }
+                    },
+                    Err(e) => {
+                        error!("virtio-snd: Failed to create worker: {}", e);
+                        false
+                    }
+                }
+            });
+        match thread_result {
+            Err(e) => {
+                error!("failed to spawn virtio_snd worker thread: {}", e);
+            }
+            Ok(join_handle) => {
+                self.worker_thread = Some(join_handle);
+            }
+        }
+    }
+
+    fn reset(&mut self) -> bool {
+        let mut ret = true;
+        if let Some(kill_evt) = self.kill_evt.take() {
+            if let Err(e) = kill_evt.write(1) {
+                error!("virtio-snd: failed to notify the kill event: {}", e);
+                ret = false;
+            }
+        } else if let Some(worker_thread) = self.worker_thread.take() {
+            match worker_thread.join() {
+                Err(e) => {
+                    error!("virtio-snd: Worker thread panicked: {:?}", e);
+                    ret = false;
+                }
+                Ok(worker_status) => {
+                    ret = worker_status;
+                }
+            }
+        }
+        if let Err(e) = self.vios_client.stop_bg_thread() {
+            error!("virtio-snd: Failed to stop vios background thread: {}", e);
+            ret = false;
+        }
+        ret
+    }
+}
+
+/// Creates a new virtio sound device connected to a VioS backend
+pub fn new_sound<P: AsRef<Path>>(path: P, virtio_features: u64) -> Result<Sound> {
+    let vios_client = Arc::new(VioSClient::try_new(path).map_err(SoundError::ClientNew)?);
+    Ok(Sound {
+        config: virtio_snd_config {
+            jacks: Le32::from(vios_client.num_jacks()),
+            streams: Le32::from(vios_client.num_streams()),
+            chmaps: Le32::from(vios_client.num_chmaps()),
+        },
+        virtio_features,
+        worker_thread: None,
+        kill_evt: None,
+        vios_client,
+    })
+}
diff --git a/devices/src/virtio/snd/vios_backend/shm_streams.rs b/devices/src/virtio/snd/vios_backend/shm_streams.rs
index 87bfbee..e839594 100644
--- a/devices/src/virtio/snd/vios_backend/shm_streams.rs
+++ b/devices/src/virtio/snd/vios_backend/shm_streams.rs
@@ -15,7 +15,9 @@
 use audio_streams::shm_streams::{BufferSet, ServerRequest, ShmStream, ShmStreamSource};
 use audio_streams::{BoxError, SampleFormat, StreamDirection, StreamEffect};
 
-use base::{error, SharedMemory, SharedMemoryUnix};
+use base::{error, MemoryMapping, MemoryMappingBuilder, SharedMemory, SharedMemoryUnix};
+use data_model::VolatileMemory;
+use sync::Mutex;
 
 use std::fs::File;
 use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
@@ -31,16 +33,43 @@
 // public there so it needs to be re-declared here. It also prevents the usage of anyhow::Error.
 type GenericResult<T> = std::result::Result<T, BoxError>;
 
+enum StreamState {
+    Available,
+    Acquired,
+    Active,
+}
+
+struct StreamDesc {
+    state: Arc<Mutex<StreamState>>,
+    direction: StreamDirection,
+}
+
 /// Adapter that provides the ShmStreamSource trait around the VioS backend.
 pub struct VioSShmStreamSource {
     vios_client: Arc<VioSClient>,
+    stream_descs: Vec<StreamDesc>,
 }
 
 impl VioSShmStreamSource {
     /// Creates a new stream source given the path to the audio server's socket.
     pub fn new<P: AsRef<Path>>(server: P) -> Result<VioSShmStreamSource> {
+        let vios_client = Arc::new(VioSClient::try_new(server)?);
+        let mut stream_descs: Vec<StreamDesc> = Vec::new();
+        let mut idx = 0u32;
+        while let Some(info) = vios_client.stream_info(idx) {
+            stream_descs.push(StreamDesc {
+                state: Arc::new(Mutex::new(StreamState::Active)),
+                direction: if info.direction == VIRTIO_SND_D_OUTPUT {
+                    StreamDirection::Playback
+                } else {
+                    StreamDirection::Capture
+                },
+            });
+            idx += 1;
+        }
         Ok(Self {
-            vios_client: Arc::new(VioSClient::try_new(server)?),
+            vios_client,
+            stream_descs,
         })
     }
 }
@@ -80,8 +109,19 @@
             direction,
             self.vios_client.clone(),
             client_shm,
+            self.stream_descs[stream_id as usize].state.clone(),
         )
     }
+
+    fn get_unused_stream_id(&self, direction: StreamDirection) -> Option<u32> {
+        self.stream_descs
+            .iter()
+            .position(|s| match &*s.state.lock() {
+                StreamState::Available => s.direction == direction,
+                _ => false,
+            })
+            .map(|idx| idx as u32)
+    }
 }
 
 impl ShmStreamSource for VioSShmStreamSource {
@@ -98,32 +138,30 @@
         client_shm: &SysSharedMemory,
         buffer_offsets: [u64; 2],
     ) -> GenericResult<Box<dyn ShmStream>> {
-        self.vios_client.ensure_bg_thread_started()?;
-        let virtio_dir = match direction {
-            StreamDirection::Playback => VIRTIO_SND_D_OUTPUT,
-            StreamDirection::Capture => VIRTIO_SND_D_INPUT,
-        };
+        self.vios_client.start_bg_thread()?;
         let stream_id = self
-            .vios_client
-            .get_unused_stream_id(virtio_dir)
+            .get_unused_stream_id(direction)
             .ok_or(Box::new(Error::NoStreamsAvailable))?;
-        self.new_stream_inner(
-            stream_id,
-            direction,
-            num_channels,
-            format,
-            frame_rate,
-            buffer_size,
-            effects,
-            client_shm,
-            buffer_offsets,
-        )
-        .map_err(|e| {
-            // Attempt to release the stream so that it can be used later. This is a best effort
-            // attempt, so we ignore any error it may return.
-            let _ = self.vios_client.release_stream(stream_id);
-            e
-        })
+        let stream = self
+            .new_stream_inner(
+                stream_id,
+                direction,
+                num_channels,
+                format,
+                frame_rate,
+                buffer_size,
+                effects,
+                client_shm,
+                buffer_offsets,
+            )
+            .map_err(|e| {
+                // Attempt to release the stream so that it can be used later. This is a best effort
+                // attempt, so we ignore any error it may return.
+                let _ = self.vios_client.release_stream(stream_id);
+                e
+            })?;
+        *self.stream_descs[stream_id as usize].state.lock() = StreamState::Acquired;
+        Ok(stream)
     }
 
     /// Get a list of file descriptors used by the implementation.
@@ -149,6 +187,7 @@
     direction: StreamDirection,
     vios_client: Arc<VioSClient>,
     client_shm: SharedMemory,
+    state: Arc<Mutex<StreamState>>,
 }
 
 impl VioSndShmStream {
@@ -162,6 +201,7 @@
         direction: StreamDirection,
         vios_client: Arc<VioSClient>,
         client_shm: &SysSharedMemory,
+        state: Arc<Mutex<StreamState>>,
     ) -> GenericResult<Box<dyn ShmStream>> {
         let interval = Duration::from_millis(buffer_size as u64 * 1000 / frame_rate as u64);
 
@@ -191,6 +231,7 @@
             direction,
             vios_client,
             client_shm: client_shm_clone,
+            state,
         }))
     }
 }
@@ -233,20 +274,54 @@
     fn callback(&mut self, offset: usize, frames: usize) -> GenericResult<()> {
         match self.direction {
             StreamDirection::Playback => {
-                self.vios_client.inject_audio_data(
+                let requested_size = frames * self.frame_size;
+                let shm_ref = &mut self.client_shm;
+                let (_, res) = self.vios_client.inject_audio_data::<Result<()>, _>(
                     self.stream_id,
-                    &mut self.client_shm,
-                    offset,
-                    frames * self.frame_size,
+                    requested_size,
+                    |slice| {
+                        if requested_size != slice.size() {
+                            error!(
+                                "Buffer size is different than the requested size: {} vs {}",
+                                requested_size,
+                                slice.size()
+                            );
+                        }
+                        let size = std::cmp::min(requested_size, slice.size());
+                        let (src_mmap, mmap_offset) = mmap_buffer(shm_ref, offset, size)?;
+                        let src_slice = src_mmap
+                            .get_slice(mmap_offset, size)
+                            .map_err(Error::VolatileMemoryError)?;
+                        src_slice.copy_to_volatile_slice(slice);
+                        Ok(())
+                    },
                 )?;
+                res?;
             }
             StreamDirection::Capture => {
-                self.vios_client.request_audio_data(
+                let requested_size = frames * self.frame_size;
+                let shm_ref = &mut self.client_shm;
+                let (_, res) = self.vios_client.request_audio_data::<Result<()>, _>(
                     self.stream_id,
-                    &mut self.client_shm,
-                    offset,
-                    frames * self.frame_size,
+                    requested_size,
+                    |slice| {
+                        if requested_size != slice.size() {
+                            error!(
+                                "Buffer size is different than the requested size: {} vs {}",
+                                requested_size,
+                                slice.size()
+                            );
+                        }
+                        let size = std::cmp::min(requested_size, slice.size());
+                        let (dst_mmap, mmap_offset) = mmap_buffer(shm_ref, offset, size)?;
+                        let dst_slice = dst_mmap
+                            .get_slice(mmap_offset, size)
+                            .map_err(Error::VolatileMemoryError)?;
+                        slice.copy_to_volatile_slice(dst_slice);
+                        Ok(())
+                    },
                 )?;
+                res?;
             }
         }
         Ok(())
@@ -267,5 +342,28 @@
         {
             error!("Failed to stop and release stream {}: {}", stream_id, e);
         }
+        *self.state.lock() = StreamState::Available;
     }
 }
+
+/// Memory map a shared memory object to access an audio buffer. The buffer may not be located at an
+/// offset aligned to page size, so the offset within the mapped region is returned along with the
+/// MemoryMapping struct.
+fn mmap_buffer(
+    src: &mut SharedMemory,
+    offset: usize,
+    size: usize,
+) -> Result<(MemoryMapping, usize)> {
+    // If the buffer is not aligned to page size a bigger region needs to be mapped.
+    let aligned_offset = offset & !(base::pagesize() - 1);
+    let offset_from_mapping_start = offset - aligned_offset;
+    let extended_size = size + offset_from_mapping_start;
+
+    let mmap = MemoryMappingBuilder::new(extended_size)
+        .offset(aligned_offset as u64)
+        .from_shared_memory(src)
+        .build()
+        .map_err(Error::GuestMmapError)?;
+
+    Ok((mmap, offset_from_mapping_start))
+}
diff --git a/devices/src/virtio/snd/vios_backend/shm_vios.rs b/devices/src/virtio/snd/vios_backend/shm_vios.rs
index 9aec672..696efac 100644
--- a/devices/src/virtio/snd/vios_backend/shm_vios.rs
+++ b/devices/src/virtio/snd/vios_backend/shm_vios.rs
@@ -8,11 +8,11 @@
 use base::{
     error, net::UnixSeqpacket, AsRawDescriptor, Error as BaseError, Event, FromRawDescriptor,
     IntoRawDescriptor, MemoryMapping, MemoryMappingBuilder, MmapError, PollToken, SafeDescriptor,
-    ScmSocket, SharedMemory, WaitContext,
+    ScmSocket, WaitContext,
 };
-use data_model::{DataInit, VolatileMemory, VolatileMemoryError};
+use data_model::{DataInit, VolatileMemory, VolatileMemoryError, VolatileSlice};
 
-use std::collections::HashMap;
+use std::collections::{HashMap, VecDeque};
 use std::fs::File;
 use std::io::{Error as IOError, ErrorKind as IOErrorKind, Seek, SeekFrom};
 use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
@@ -51,10 +51,10 @@
     ProtocolError(ProtocolErrorKind),
     #[error("No PCM streams available")]
     NoStreamsAvailable,
+    #[error("No jack with id {0}")]
+    InvalidJackId(u32),
     #[error("No stream with id {0}")]
     InvalidStreamId(u32),
-    #[error("Stream is in unexpected state: {0:?}")]
-    UnexpectedState(StreamState),
     #[error("Invalid operation for stream direction: {0}")]
     WrongDirection(u8),
     #[error("Insuficient space for the new buffer in the queue's buffer area")]
@@ -75,6 +75,8 @@
     EventCreateError(BaseError),
     #[error("Failed to dup Recv event: {0}")]
     EventDupError(BaseError),
+    #[error("Failed to signal event: {0}")]
+    EventWriteError(BaseError),
     #[error("Failed to create Recv thread's WaitContext: {0}")]
     WaitContextCreateError(BaseError),
     #[error("Error waiting for events")]
@@ -99,17 +101,28 @@
 /// notifications. It's thread safe, it can be encapsulated in an Arc smart pointer and shared
 /// between threads.
 pub struct VioSClient {
-    config: VioSConfig,
     // These mutexes should almost never be held simultaneously. If at some point they have to the
     // locking order should match the order in which they are declared here.
-    streams: Mutex<Vec<VioSStreamInfo>>,
+    config: VioSConfig,
+    jacks: Vec<virtio_snd_jack_info>,
+    streams: Vec<virtio_snd_pcm_info>,
+    chmaps: Vec<virtio_snd_chmap_info>,
+    // The control socket is used from multiple threads to send and wait for a reply, which needs
+    // to happen atomically, hence the need for a mutex instead of just sharing clones of the
+    // socket.
     control_socket: Mutex<UnixSeqpacket>,
-    event_socket: Mutex<UnixSeqpacket>,
-    tx: Mutex<IoBufferQueue>,
-    rx: Mutex<IoBufferQueue>,
-    rx_subscribers: Arc<Mutex<HashMap<usize, Sender<(u32, usize)>>>>,
-    recv_running: Arc<Mutex<bool>>,
-    recv_event: Mutex<Event>,
+    event_socket: UnixSeqpacket,
+    // These are thread safe and don't require locking
+    tx: IoBufferQueue,
+    rx: IoBufferQueue,
+    // This is accessed by the recv_thread and whatever thread processes the events
+    events: Arc<Mutex<VecDeque<virtio_snd_event>>>,
+    event_notifier: Event,
+    // These are accessed by the recv_thread and the stream threads
+    tx_subscribers: Arc<Mutex<HashMap<usize, Sender<BufferReleaseMsg>>>>,
+    rx_subscribers: Arc<Mutex<HashMap<usize, Sender<BufferReleaseMsg>>>>,
+    recv_thread_state: Arc<Mutex<ThreadFlags>>,
+    recv_event: Event,
     recv_thread: Mutex<Option<JoinHandle<Result<()>>>>,
 }
 
@@ -180,351 +193,472 @@
             ));
         }
 
-        let rx_subscribers: Arc<Mutex<HashMap<usize, Sender<(u32, usize)>>>> =
+        let tx_subscribers: Arc<Mutex<HashMap<usize, Sender<BufferReleaseMsg>>>> =
             Arc::new(Mutex::new(HashMap::new()));
-        let recv_running = Arc::new(Mutex::new(true));
+        let rx_subscribers: Arc<Mutex<HashMap<usize, Sender<BufferReleaseMsg>>>> =
+            Arc::new(Mutex::new(HashMap::new()));
+        let recv_thread_state = Arc::new(Mutex::new(ThreadFlags {
+            running: true,
+            reporting_events: false,
+        }));
         let recv_event = Event::new().map_err(Error::EventCreateError)?;
 
         let mut client = VioSClient {
             config,
-            streams: Mutex::new(Vec::new()),
+            jacks: Vec::new(),
+            streams: Vec::new(),
+            chmaps: Vec::new(),
             control_socket: Mutex::new(client_socket),
-            event_socket: Mutex::new(event_socket),
-            tx: Mutex::new(IoBufferQueue::new(tx_socket, tx_shm_file)?),
-            rx: Mutex::new(IoBufferQueue::new(rx_socket, rx_shm_file)?),
+            event_socket,
+            tx: IoBufferQueue::new(tx_socket, tx_shm_file)?,
+            rx: IoBufferQueue::new(rx_socket, rx_shm_file)?,
+            events: Arc::new(Mutex::new(VecDeque::new())),
+            event_notifier: Event::new().map_err(Error::EventCreateError)?,
+            tx_subscribers,
             rx_subscribers,
-            recv_running,
-            recv_event: Mutex::new(recv_event),
+            recv_thread_state,
+            recv_event,
             recv_thread: Mutex::new(None),
         };
-        client.request_and_cache_streams_info()?;
+        client.request_and_cache_info()?;
         Ok(client)
     }
 
-    pub fn ensure_bg_thread_started(&self) -> Result<()> {
+    /// Get the number of jacks
+    pub fn num_jacks(&self) -> u32 {
+        self.config.jacks
+    }
+
+    /// Get the number of pcm streams
+    pub fn num_streams(&self) -> u32 {
+        self.config.streams
+    }
+
+    /// Get the number of channel maps
+    pub fn num_chmaps(&self) -> u32 {
+        self.config.chmaps
+    }
+
+    /// Get the configuration information on a jack
+    pub fn jack_info(&self, idx: u32) -> Option<virtio_snd_jack_info> {
+        self.jacks.get(idx as usize).copied()
+    }
+
+    /// Get the configuration information on a pcm stream
+    pub fn stream_info(&self, idx: u32) -> Option<virtio_snd_pcm_info> {
+        self.streams.get(idx as usize).cloned()
+    }
+
+    /// Get the configuration information on a channel map
+    pub fn chmap_info(&self, idx: u32) -> Option<virtio_snd_chmap_info> {
+        self.chmaps.get(idx as usize).copied()
+    }
+
+    /// Starts the background thread that receives release messages from the server. If the thread
+    /// was already started this function does nothing.
+    /// This thread must be started prior to attempting any stream IO operation or the calling
+    /// thread would block.
+    pub fn start_bg_thread(&self) -> Result<()> {
         if self.recv_thread.lock().is_some() {
             return Ok(());
         }
+        let recv_event = self.recv_event.try_clone().map_err(Error::EventDupError)?;
+        let tx_socket = self.tx.try_clone_socket()?;
+        let rx_socket = self.rx.try_clone_socket()?;
         let event_socket = self
-            .recv_event
-            .lock()
-            .try_clone()
-            .map_err(Error::EventDupError)?;
-        let rx_socket = self
-            .rx
-            .lock()
-            .socket
+            .event_socket
             .try_clone()
             .map_err(Error::UnixSeqpacketDupError)?;
         let mut opt = self.recv_thread.lock();
         // The lock on recv_thread was released above to avoid holding more than one lock at a time
-        // while duplicating the fds. So we have to check again the condition.
+        // while duplicating the fds. So we have to check the condition again.
         if opt.is_none() {
             *opt = Some(spawn_recv_thread(
+                self.tx_subscribers.clone(),
                 self.rx_subscribers.clone(),
-                event_socket,
-                self.recv_running.clone(),
+                self.event_notifier
+                    .try_clone()
+                    .map_err(Error::EventDupError)?,
+                self.events.clone(),
+                recv_event,
+                self.recv_thread_state.clone(),
+                tx_socket,
                 rx_socket,
+                event_socket,
             ));
         }
         Ok(())
     }
 
-    /// Gets an unused stream id of the specified direction. `direction` must be one of
-    /// VIRTIO_SND_D_INPUT OR VIRTIO_SND_D_OUTPUT.
-    pub fn get_unused_stream_id(&self, direction: u8) -> Option<u32> {
-        self.streams
-            .lock()
-            .iter()
-            .filter(|s| s.state == StreamState::Available && s.direction == direction as u8)
-            .map(|s| s.id)
-            .next()
+    /// Stops the background thread.
+    pub fn stop_bg_thread(&self) -> Result<()> {
+        if self.recv_thread.lock().is_none() {
+            return Ok(());
+        }
+        self.recv_thread_state.lock().running = false;
+        self.recv_event
+            .write(1u64)
+            .map_err(Error::EventWriteError)?;
+        if let Some(handle) = self.recv_thread.lock().take() {
+            return match handle.join() {
+                Ok(r) => r,
+                Err(e) => {
+                    error!("Recv thread panicked: {:?}", e);
+                    Ok(())
+                }
+            };
+        }
+        Ok(())
+    }
+
+    /// Gets an Event object that will trigger every time an event is received from the server
+    pub fn get_event_notifier(&self) -> Result<Event> {
+        // Let the background thread know that there is at least one consumer of events
+        self.recv_thread_state.lock().reporting_events = true;
+        self.event_notifier
+            .try_clone()
+            .map_err(Error::EventDupError)
+    }
+
+    /// Retrieves one event. Callers should have received a notification through the event notifier
+    /// before calling this function.
+    pub fn pop_event(&self) -> Option<virtio_snd_event> {
+        self.events.lock().pop_front()
+    }
+
+    /// Remap a jack. This should only be called if the jack announces support for the operation
+    /// through the features field in the corresponding virtio_snd_jack_info struct.
+    pub fn remap_jack(&self, jack_id: u32, association: u32, sequence: u32) -> Result<()> {
+        if jack_id >= self.config.jacks {
+            return Err(Error::InvalidJackId(jack_id));
+        }
+        let msg = virtio_snd_jack_remap {
+            hdr: virtio_snd_jack_hdr {
+                hdr: virtio_snd_hdr {
+                    code: VIRTIO_SND_R_JACK_REMAP.into(),
+                },
+                jack_id: jack_id.into(),
+            },
+            association: association.into(),
+            sequence: sequence.into(),
+        };
+        let control_socket_lock = self.control_socket.lock();
+        send_cmd(&*control_socket_lock, msg)
     }
 
     /// Configures a stream with the given parameters.
     pub fn set_stream_parameters(&self, stream_id: u32, params: VioSStreamParams) -> Result<()> {
-        self.validate_stream_id(
-            stream_id,
-            &[StreamState::Available, StreamState::Acquired],
-            None,
-        )?;
+        self.streams
+            .get(stream_id as usize)
+            .ok_or(Error::InvalidStreamId(stream_id))?;
         let raw_params: virtio_snd_pcm_set_params = (stream_id, params).into();
-        self.send_cmd(raw_params)?;
-        self.streams.lock()[stream_id as usize].state = StreamState::Acquired;
-        Ok(())
+        let control_socket_lock = self.control_socket.lock();
+        send_cmd(&*control_socket_lock, raw_params)
+    }
+
+    /// Configures a stream with the given parameters.
+    pub fn set_stream_parameters_raw(&self, raw_params: virtio_snd_pcm_set_params) -> Result<()> {
+        let stream_id = raw_params.hdr.stream_id.to_native();
+        self.streams
+            .get(stream_id as usize)
+            .ok_or(Error::InvalidStreamId(stream_id))?;
+        let control_socket_lock = self.control_socket.lock();
+        send_cmd(&*control_socket_lock, raw_params)
     }
 
     /// Send the PREPARE_STREAM command to the server.
     pub fn prepare_stream(&self, stream_id: u32) -> Result<()> {
-        self.common_stream_op(
-            stream_id,
-            &[StreamState::Available, StreamState::Acquired],
-            StreamState::Acquired,
-            STREAM_PREPARE,
-        )
+        self.common_stream_op(stream_id, VIRTIO_SND_R_PCM_PREPARE)
     }
 
     /// Send the RELEASE_STREAM command to the server.
     pub fn release_stream(&self, stream_id: u32) -> Result<()> {
-        self.common_stream_op(
-            stream_id,
-            &[StreamState::Acquired],
-            StreamState::Available,
-            STREAM_RELEASE,
-        )
+        self.common_stream_op(stream_id, VIRTIO_SND_R_PCM_RELEASE)
     }
 
     /// Send the START_STREAM command to the server.
     pub fn start_stream(&self, stream_id: u32) -> Result<()> {
-        self.common_stream_op(
-            stream_id,
-            &[StreamState::Acquired],
-            StreamState::Active,
-            STREAM_START,
-        )
+        self.common_stream_op(stream_id, VIRTIO_SND_R_PCM_START)
     }
 
     /// Send the STOP_STREAM command to the server.
     pub fn stop_stream(&self, stream_id: u32) -> Result<()> {
-        self.common_stream_op(
-            stream_id,
-            &[StreamState::Active],
-            StreamState::Acquired,
-            STREAM_STOP,
-        )
+        self.common_stream_op(stream_id, VIRTIO_SND_R_PCM_STOP)
     }
 
-    /// Send audio frames to the server. The audio data is taken from a shared memory resource.
-    pub fn inject_audio_data(
+    /// Send audio frames to the server. Blocks the calling thread until the server acknowledges
+    /// the data.
+    pub fn inject_audio_data<R, Cb: FnOnce(VolatileSlice) -> R>(
         &self,
         stream_id: u32,
-        buffer: &mut SharedMemory,
-        src_offset: usize,
         size: usize,
-    ) -> Result<()> {
-        self.validate_stream_id(stream_id, &[StreamState::Active], Some(VIRTIO_SND_D_OUTPUT))?;
-        let mut tx_lock = self.tx.lock();
-        let tx = &mut *tx_lock;
-        let dst_offset = tx.push_buffer(buffer, src_offset, size)?;
-        let msg = IoTransferMsg::new(stream_id, dst_offset, size);
-        seq_socket_send(&tx.socket, msg)
-    }
-
-    pub fn request_audio_data(
-        &self,
-        stream_id: u32,
-        buffer: &mut SharedMemory,
-        dst_offset: usize,
-        size: usize,
-    ) -> Result<usize> {
-        self.validate_stream_id(stream_id, &[StreamState::Active], Some(VIRTIO_SND_D_INPUT))?;
-        let (src_offset, status_promise) = {
-            let mut rx_lock = self.rx.lock();
-            let rx = &mut *rx_lock;
-            let src_offset = rx.allocate_buffer(size)?;
-            // Register to receive the status before sending the buffer to the server
-            let (sender, receiver): (Sender<(u32, usize)>, Receiver<(u32, usize)>) = channel();
-            // It's OK to acquire rx_subscriber's lock after rx_lock
-            self.rx_subscribers.lock().insert(src_offset, sender);
-            let msg = IoTransferMsg::new(stream_id, src_offset, size);
-            seq_socket_send(&rx.socket, msg)?;
-            (src_offset, receiver)
-        };
-        // Make sure no mutexes are held while awaiting for the buffer to be written to
-        let recv_size = await_status(status_promise)?;
+        callback: Cb,
+    ) -> Result<(u32, R)> {
+        if self
+            .streams
+            .get(stream_id as usize)
+            .ok_or(Error::InvalidStreamId(stream_id))?
+            .direction
+            != VIRTIO_SND_D_OUTPUT
         {
-            let mut rx_lock = self.rx.lock();
-            rx_lock
-                .pop_buffer(buffer, dst_offset, recv_size, src_offset)
-                .map(|()| recv_size)
+            return Err(Error::WrongDirection(VIRTIO_SND_D_OUTPUT));
         }
+        self.streams
+            .get(stream_id as usize)
+            .ok_or(Error::InvalidStreamId(stream_id))?;
+        let dst_offset = self.tx.allocate_buffer(size)?;
+        let buffer_slice = self.tx.buffer_at(dst_offset, size)?;
+        let ret = callback(buffer_slice);
+        // Register to receive the status before sending the buffer to the server
+        let (sender, receiver): (Sender<BufferReleaseMsg>, Receiver<BufferReleaseMsg>) = channel();
+        self.tx_subscribers.lock().insert(dst_offset, sender);
+        self.tx.send_buffer(stream_id, dst_offset, size)?;
+        let (_, latency) = await_status(receiver)?;
+        Ok((latency, ret))
+    }
+
+    /// Request audio frames from the server. It blocks until the data is available.
+    pub fn request_audio_data<R, Cb: FnOnce(&VolatileSlice) -> R>(
+        &self,
+        stream_id: u32,
+        size: usize,
+        callback: Cb,
+    ) -> Result<(u32, R)> {
+        if self
+            .streams
+            .get(stream_id as usize)
+            .ok_or(Error::InvalidStreamId(stream_id))?
+            .direction
+            != VIRTIO_SND_D_INPUT
+        {
+            return Err(Error::WrongDirection(VIRTIO_SND_D_INPUT));
+        }
+        let src_offset = self.rx.allocate_buffer(size)?;
+        // Register to receive the status before sending the buffer to the server
+        let (sender, receiver): (Sender<BufferReleaseMsg>, Receiver<BufferReleaseMsg>) = channel();
+        self.rx_subscribers.lock().insert(src_offset, sender);
+        self.rx.send_buffer(stream_id, src_offset, size)?;
+        // Make sure no mutexes are held while awaiting for the buffer to be written to
+        let (recv_size, latency) = await_status(receiver)?;
+        let buffer_slice = self.rx.buffer_at(src_offset, recv_size)?;
+        Ok((latency, callback(&buffer_slice)))
     }
 
     /// Get a list of file descriptors used by the implementation.
     pub fn keep_fds(&self) -> Vec<RawFd> {
         let control_fd = self.control_socket.lock().as_raw_fd();
-        let event_fd = self.event_socket.lock().as_raw_fd();
-        let (tx_socket_fd, tx_shm_fd) = {
-            let lock = self.tx.lock();
-            (lock.socket.as_raw_fd(), lock.file.as_raw_fd())
-        };
-        let (rx_socket_fd, rx_shm_fd) = {
-            let lock = self.rx.lock();
-            (lock.socket.as_raw_fd(), lock.file.as_raw_fd())
-        };
-        let recv_event = self.recv_event.lock().as_raw_descriptor();
-        vec![
-            control_fd,
-            event_fd,
-            tx_socket_fd,
-            tx_shm_fd,
-            rx_socket_fd,
-            rx_shm_fd,
-            recv_event,
-        ]
+        let event_fd = self.event_socket.as_raw_fd();
+        let recv_event = self.recv_event.as_raw_descriptor();
+        let event_notifier = self.event_notifier.as_raw_descriptor();
+        let mut ret = vec![control_fd, event_fd, recv_event, event_notifier];
+        ret.append(&mut self.tx.keep_fds());
+        ret.append(&mut self.rx.keep_fds());
+        ret
     }
 
-    fn send_cmd<T: DataInit>(&self, data: T) -> Result<()> {
-        let mut control_socket_lock = self.control_socket.lock();
-        seq_socket_send(&*control_socket_lock, data)?;
-        recv_cmd_status(&mut *control_socket_lock)
-    }
-
-    fn validate_stream_id(
-        &self,
-        stream_id: u32,
-        permitted_states: &[StreamState],
-        direction: Option<u8>,
-    ) -> Result<()> {
-        let streams_lock = self.streams.lock();
-        let stream_idx = stream_id as usize;
-        if stream_idx >= streams_lock.len() {
-            return Err(Error::InvalidStreamId(stream_id));
-        }
-        if !permitted_states.contains(&streams_lock[stream_idx].state) {
-            return Err(Error::UnexpectedState(streams_lock[stream_idx].state));
-        }
-        match direction {
-            None => Ok(()),
-            Some(d) => {
-                if d == streams_lock[stream_idx].direction {
-                    Ok(())
-                } else {
-                    Err(Error::WrongDirection(streams_lock[stream_idx].direction))
-                }
-            }
-        }
-    }
-
-    fn common_stream_op(
-        &self,
-        stream_id: u32,
-        expected_states: &[StreamState],
-        new_state: StreamState,
-        op: u32,
-    ) -> Result<()> {
-        self.validate_stream_id(stream_id, expected_states, None)?;
+    fn common_stream_op(&self, stream_id: u32, op: u32) -> Result<()> {
+        self.streams
+            .get(stream_id as usize)
+            .ok_or(Error::InvalidStreamId(stream_id))?;
         let msg = virtio_snd_pcm_hdr {
             hdr: virtio_snd_hdr { code: op.into() },
             stream_id: stream_id.into(),
         };
-        self.send_cmd(msg)?;
-        self.streams.lock()[stream_id as usize].state = new_state;
+        let control_socket_lock = self.control_socket.lock();
+        send_cmd(&*control_socket_lock, msg)
+    }
+
+    fn request_and_cache_info(&mut self) -> Result<()> {
+        self.request_and_cache_jacks_info()?;
+        self.request_and_cache_streams_info()?;
+        self.request_and_cache_chmaps_info()?;
+        Ok(())
+    }
+
+    fn request_info<T: DataInit + Default + Copy + Clone>(
+        &self,
+        req_code: u32,
+        count: usize,
+    ) -> Result<Vec<T>> {
+        let info_size = std::mem::size_of::<T>();
+        let status_size = std::mem::size_of::<virtio_snd_hdr>();
+        let req = virtio_snd_query_info {
+            hdr: virtio_snd_hdr {
+                code: req_code.into(),
+            },
+            start_id: 0u32.into(),
+            count: (count as u32).into(),
+            size: (std::mem::size_of::<virtio_snd_query_info>() as u32).into(),
+        };
+        let control_socket_lock = self.control_socket.lock();
+        seq_socket_send(&*control_socket_lock, req)?;
+        let reply = control_socket_lock
+            .recv_as_vec()
+            .map_err(Error::ServerIOError)?;
+        let mut status: virtio_snd_hdr = Default::default();
+        status
+            .as_mut_slice()
+            .copy_from_slice(&reply[0..status_size]);
+        if status.code.to_native() != VIRTIO_SND_S_OK {
+            return Err(Error::CommandFailed(status.code.to_native()));
+        }
+        if reply.len() != status_size + count * info_size {
+            return Err(Error::ProtocolError(
+                ProtocolErrorKind::UnexpectedMessageSize(count * info_size, reply.len()),
+            ));
+        }
+        Ok(reply[status_size..]
+            .chunks(info_size)
+            .map(|info_buffer| {
+                let mut info: T = Default::default();
+                // Need to use copy_from_slice instead of T::from_slice because the info_buffer may
+                // not be aligned correctly
+                info.as_mut_slice().copy_from_slice(info_buffer);
+                info
+            })
+            .collect())
+    }
+
+    fn request_and_cache_jacks_info(&mut self) -> Result<()> {
+        let num_jacks = self.config.jacks as usize;
+        if num_jacks == 0 {
+            return Ok(());
+        }
+        self.jacks = self.request_info(VIRTIO_SND_R_JACK_INFO, num_jacks)?;
         Ok(())
     }
 
     fn request_and_cache_streams_info(&mut self) -> Result<()> {
         let num_streams = self.config.streams as usize;
-        let info_size = std::mem::size_of::<virtio_snd_pcm_info>();
-        let req = virtio_snd_query_info {
-            hdr: virtio_snd_hdr {
-                code: STREAM_INFO.into(),
-            },
-            start_id: 0u32.into(),
-            count: (num_streams as u32).into(),
-            size: (std::mem::size_of::<virtio_snd_query_info>() as u32).into(),
-        };
-        self.send_cmd(req)?;
-        let control_socket_lock = self.control_socket.lock();
-        let info_vec = control_socket_lock
-            .recv_as_vec()
-            .map_err(Error::ServerIOError)?;
-        if info_vec.len() != num_streams * info_size {
-            return Err(Error::ProtocolError(
-                ProtocolErrorKind::UnexpectedMessageSize(num_streams * info_size, info_vec.len()),
-            ));
+        if num_streams == 0 {
+            return Ok(());
         }
-        self.streams = Mutex::new(
-            info_vec
-                .chunks(info_size)
-                .enumerate()
-                .map(|(id, info_buffer)| {
-                    // unwrap is safe because we checked the size of the vector
-                    let virtio_stream_info = virtio_snd_pcm_info::from_slice(&info_buffer).unwrap();
-                    VioSStreamInfo::new(id as u32, &virtio_stream_info)
-                })
-                .collect(),
-        );
+        self.streams = self.request_info(VIRTIO_SND_R_PCM_INFO, num_streams)?;
+        Ok(())
+    }
+
+    fn request_and_cache_chmaps_info(&mut self) -> Result<()> {
+        let num_chmaps = self.config.chmaps as usize;
+        if num_chmaps == 0 {
+            return Ok(());
+        }
+        self.chmaps = self.request_info(VIRTIO_SND_R_CHMAP_INFO, num_chmaps)?;
         Ok(())
     }
 }
 
 impl Drop for VioSClient {
     fn drop(&mut self) {
-        // Stop the recv thread
-        *self.recv_running.lock() = false;
-        if let Err(e) = self.recv_event.lock().write(1u64) {
-            error!("Failed to notify recv thread: {:?}", e);
-        }
-        if let Some(handle) = self.recv_thread.lock().take() {
-            match handle.join() {
-                Ok(r) => {
-                    if let Err(e) = r {
-                        error!("Error detected on Recv Thread: {}", e);
-                    }
-                }
-                Err(e) => error!("Recv thread panicked: {:?}", e),
-            };
+        if let Err(e) = self.stop_bg_thread() {
+            error!("Error stopping Recv thread: {}", e);
         }
     }
 }
 
+#[derive(Clone, Copy)]
+struct ThreadFlags {
+    running: bool,
+    reporting_events: bool,
+}
+
 #[derive(PollToken)]
 enum Token {
     Notification,
+    TxBufferMsg,
     RxBufferMsg,
+    EventMsg,
+}
+
+fn recv_buffer_status_msg(
+    socket: &UnixSeqpacket,
+    subscribers: &Arc<Mutex<HashMap<usize, Sender<BufferReleaseMsg>>>>,
+) -> Result<()> {
+    let mut msg: IoStatusMsg = Default::default();
+    let size = socket
+        .recv(msg.as_mut_slice())
+        .map_err(Error::ServerIOError)?;
+    if size != std::mem::size_of::<IoStatusMsg>() {
+        return Err(Error::ProtocolError(
+            ProtocolErrorKind::UnexpectedMessageSize(std::mem::size_of::<IoStatusMsg>(), size),
+        ));
+    }
+    let mut status = msg.status.status.into();
+    if status == u32::MAX {
+        // Anyone waiting for this would continue to wait for as long as status is
+        // u32::MAX
+        status -= 1;
+    }
+    let latency = msg.status.latency_bytes.into();
+    let offset = msg.buffer_offset as usize;
+    let consumed_len = msg.consumed_len as usize;
+    let promise_opt = subscribers.lock().remove(&offset);
+    match promise_opt {
+        None => error!(
+            "Received an unexpected buffer status message: {}. This is a BUG!!",
+            offset
+        ),
+        Some(sender) => {
+            if let Err(e) = sender.send(BufferReleaseMsg {
+                status,
+                latency,
+                consumed_len,
+            }) {
+                error!("Failed to notify waiting thread: {:?}", e);
+            }
+        }
+    }
+    Ok(())
+}
+
+fn recv_event(socket: &UnixSeqpacket) -> Result<virtio_snd_event> {
+    let mut msg: virtio_snd_event = Default::default();
+    let size = socket
+        .recv(msg.as_mut_slice())
+        .map_err(Error::ServerIOError)?;
+    if size != std::mem::size_of::<virtio_snd_event>() {
+        return Err(Error::ProtocolError(
+            ProtocolErrorKind::UnexpectedMessageSize(std::mem::size_of::<virtio_snd_event>(), size),
+        ));
+    }
+    Ok(msg)
 }
 
 fn spawn_recv_thread(
-    rx_subscribers: Arc<Mutex<HashMap<usize, Sender<(u32, usize)>>>>,
+    tx_subscribers: Arc<Mutex<HashMap<usize, Sender<BufferReleaseMsg>>>>,
+    rx_subscribers: Arc<Mutex<HashMap<usize, Sender<BufferReleaseMsg>>>>,
+    event_notifier: Event,
+    event_queue: Arc<Mutex<VecDeque<virtio_snd_event>>>,
     event: Event,
-    running: Arc<Mutex<bool>>,
+    state: Arc<Mutex<ThreadFlags>>,
+    tx_socket: UnixSeqpacket,
     rx_socket: UnixSeqpacket,
+    event_socket: UnixSeqpacket,
 ) -> JoinHandle<Result<()>> {
     std::thread::spawn(move || {
         let wait_ctx: WaitContext<Token> = WaitContext::build_with(&[
+            (&tx_socket, Token::TxBufferMsg),
             (&rx_socket, Token::RxBufferMsg),
+            (&event_socket, Token::EventMsg),
             (&event, Token::Notification),
         ])
         .map_err(Error::WaitContextCreateError)?;
-        while *running.lock() {
+        loop {
+            let state_cpy = *state.lock();
+            if !state_cpy.running {
+                break;
+            }
             let events = wait_ctx.wait().map_err(Error::WaitError)?;
             for evt in events {
                 match evt.token {
-                    Token::RxBufferMsg => {
-                        let mut msg: IoStatusMsg = Default::default();
-                        let size = rx_socket
-                            .recv(msg.as_mut_slice())
-                            .map_err(Error::ServerIOError)?;
-                        if size != std::mem::size_of::<IoStatusMsg>() {
-                            return Err(Error::ProtocolError(
-                                ProtocolErrorKind::UnexpectedMessageSize(
-                                    std::mem::size_of::<IoStatusMsg>(),
-                                    size,
-                                ),
-                            ));
-                        }
-                        let mut status = msg.status.status.into();
-                        if status == u32::MAX {
-                            // Anyone waiting for this would continue to wait for as long as status is
-                            // u32::MAX
-                            status -= 1;
-                        }
-                        let offset = msg.buffer_offset as usize;
-                        let consumed_len = msg.consumed_len as usize;
-                        // Acquire and immediately release the mutex protecting the hashmap
-                        let promise_opt = rx_subscribers.lock().remove(&offset);
-                        match promise_opt {
-                            None => error!(
-                                "Received an unexpected buffer status message: {}. This is a BUG!!",
-                                offset
-                            ),
-                            Some(sender) => {
-                                if let Err(e) = sender.send((status, consumed_len)) {
-                                    error!("Failed to notify waiting thread: {:?}", e);
-                                }
-                            }
-                        }
+                    Token::TxBufferMsg => recv_buffer_status_msg(&tx_socket, &tx_subscribers)?,
+                    Token::RxBufferMsg => recv_buffer_status_msg(&rx_socket, &rx_subscribers)?,
+                    Token::EventMsg => {
+                        let evt = recv_event(&event_socket)?;
+                        let state_cpy = *state.lock();
+                        if state_cpy.reporting_events {
+                            event_queue.lock().push_back(evt);
+                            event_notifier.write(1).map_err(Error::EventWriteError)?;
+                        } // else just drop the events
                     }
                     Token::Notification => {
                         // Just consume the notification and check for termination on the next
@@ -540,10 +674,14 @@
     })
 }
 
-fn await_status(promise: Receiver<(u32, usize)>) -> Result<usize> {
-    let (status, consumed_len) = promise.recv().map_err(Error::BufferStatusSenderLost)?;
+fn await_status(promise: Receiver<BufferReleaseMsg>) -> Result<(usize, u32)> {
+    let BufferReleaseMsg {
+        status,
+        latency,
+        consumed_len,
+    } = promise.recv().map_err(Error::BufferStatusSenderLost)?;
     if status == VIRTIO_SND_S_OK {
-        Ok(consumed_len)
+        Ok((consumed_len, latency))
     } else {
         Err(Error::IOBufferError(status))
     }
@@ -554,7 +692,7 @@
     file: File,
     mmap: MemoryMapping,
     size: usize,
-    next: usize,
+    next: Mutex<usize>,
 }
 
 impl IoBufferQueue {
@@ -571,92 +709,45 @@
             file,
             mmap,
             size,
-            next: 0,
+            next: Mutex::new(0),
         })
     }
 
-    fn allocate_buffer(&mut self, size: usize) -> Result<usize> {
+    fn allocate_buffer(&self, size: usize) -> Result<usize> {
         if size > self.size {
             return Err(Error::OutOfSpace);
         }
-        let offset = if size > self.size - self.next {
+        let mut next_lock = self.next.lock();
+        let offset = if size > self.size - *next_lock {
             // Can't fit the new buffer at the end of the area, so put it at the beginning
             0
         } else {
-            self.next
+            *next_lock
         };
-        self.next = offset + size;
+        *next_lock = offset + size;
         Ok(offset)
     }
 
-    fn push_buffer(&mut self, src: &mut SharedMemory, offset: usize, size: usize) -> Result<usize> {
-        let shm_offset = self.allocate_buffer(size)?;
-        let (src_mmap, mmap_offset) = mmap_buffer(src, offset, size)?;
-        let src_slice = src_mmap
-            .get_slice(mmap_offset, size)
-            .map_err(Error::VolatileMemoryError)?;
-        let dst_slice = self
-            .mmap
-            .get_slice(shm_offset, size)
-            .map_err(Error::VolatileMemoryError)?;
-        src_slice.copy_to_volatile_slice(dst_slice);
-        Ok(shm_offset)
+    fn buffer_at(&self, offset: usize, len: usize) -> Result<VolatileSlice> {
+        self.mmap
+            .get_slice(offset, len)
+            .map_err(Error::VolatileMemoryError)
     }
 
-    fn pop_buffer(
-        &mut self,
-        dst: &mut SharedMemory,
-        dst_offset: usize,
-        size: usize,
-        src_offset: usize,
-    ) -> Result<()> {
-        let (dst_mmap, mmap_offset) = mmap_buffer(dst, dst_offset, size)?;
-        let dst_slice = dst_mmap
-            .get_slice(mmap_offset, size)
-            .map_err(Error::VolatileMemoryError)?;
-        let src_slice = self
-            .mmap
-            .get_slice(src_offset, size)
-            .map_err(Error::VolatileMemoryError)?;
-        src_slice.copy_to_volatile_slice(dst_slice);
-        Ok(())
+    fn try_clone_socket(&self) -> Result<UnixSeqpacket> {
+        self.socket
+            .try_clone()
+            .map_err(Error::UnixSeqpacketDupError)
     }
-}
 
-/// Description of a stream made available by the server.
-pub struct VioSStreamInfo {
-    pub id: u32,
-    pub hda_fn_nid: u32,
-    pub features: u32,
-    pub formats: u64,
-    pub rates: u64,
-    pub direction: u8,
-    pub channels_min: u8,
-    pub channels_max: u8,
-    state: StreamState,
-}
-
-impl VioSStreamInfo {
-    fn new(id: u32, info: &virtio_snd_pcm_info) -> VioSStreamInfo {
-        VioSStreamInfo {
-            id,
-            hda_fn_nid: info.hdr.hda_fn_nid.to_native(),
-            features: info.features.to_native(),
-            formats: info.formats.to_native(),
-            rates: info.rates.to_native(),
-            direction: info.direction,
-            channels_min: info.channels_min,
-            channels_max: info.channels_max,
-            state: StreamState::Available,
-        }
+    fn send_buffer(&self, stream_id: u32, offset: usize, size: usize) -> Result<()> {
+        let msg = IoTransferMsg::new(stream_id, offset, size);
+        seq_socket_send(&self.socket, msg)
     }
-}
 
-#[derive(PartialEq, Debug, Copy, Clone)]
-pub enum StreamState {
-    Available,
-    Acquired,
-    Active,
+    fn keep_fds(&self) -> Vec<RawFd> {
+        vec![self.file.as_raw_fd(), self.socket.as_raw_fd()]
+    }
 }
 
 /// Groups the parameters used to configure a stream prior to using it.
@@ -674,7 +765,7 @@
         virtio_snd_pcm_set_params {
             hdr: virtio_snd_pcm_hdr {
                 hdr: virtio_snd_hdr {
-                    code: STREAM_SET_PARAMS.into(),
+                    code: VIRTIO_SND_R_PCM_SET_PARAMS.into(),
                 },
                 stream_id: self.0.into(),
             },
@@ -689,29 +780,12 @@
     }
 }
 
-/// Memory map a shared memory object to access an audio buffer. The buffer may not be located at an
-/// offset aligned to page size, so the offset within the mapped region is returned along with the
-/// MemoryMapping struct.
-fn mmap_buffer(
-    src: &mut SharedMemory,
-    offset: usize,
-    size: usize,
-) -> Result<(MemoryMapping, usize)> {
-    // If the buffer is not aligned to page size a bigger region needs to be mapped.
-    let aligned_offset = offset & !(base::pagesize() - 1);
-    let offset_from_mapping_start = offset - aligned_offset;
-    let extended_size = size + offset_from_mapping_start;
-
-    let mmap = MemoryMappingBuilder::new(extended_size)
-        .offset(aligned_offset as u64)
-        .from_shared_memory(src)
-        .build()
-        .map_err(Error::GuestMmapError)?;
-
-    Ok((mmap, offset_from_mapping_start))
+fn send_cmd<T: DataInit>(control_socket: &UnixSeqpacket, data: T) -> Result<()> {
+    seq_socket_send(control_socket, data)?;
+    recv_cmd_status(control_socket)
 }
 
-fn recv_cmd_status(control_socket: &mut UnixSeqpacket) -> Result<()> {
+fn recv_cmd_status(control_socket: &UnixSeqpacket) -> Result<()> {
     let mut status: virtio_snd_hdr = Default::default();
     control_socket
         .recv(status.as_mut_slice())
@@ -739,7 +813,7 @@
     Ok(())
 }
 
-const VIOS_VERSION: u32 = 1;
+const VIOS_VERSION: u32 = 2;
 
 #[repr(C)]
 #[derive(Copy, Clone, Default)]
@@ -752,6 +826,12 @@
 // Safe because it only has data and has no implicit padding.
 unsafe impl DataInit for VioSConfig {}
 
+struct BufferReleaseMsg {
+    status: u32,
+    latency: u32,
+    consumed_len: usize,
+}
+
 #[repr(C)]
 #[derive(Copy, Clone)]
 struct IoTransferMsg {
diff --git a/devices/src/virtio/snd/vios_backend/streams.rs b/devices/src/virtio/snd/vios_backend/streams.rs
new file mode 100644
index 0000000..cf5166a
--- /dev/null
+++ b/devices/src/virtio/snd/vios_backend/streams.rs
@@ -0,0 +1,494 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::collections::VecDeque;
+use std::ops::Deref;
+use std::sync::mpsc::{channel, Receiver, Sender};
+use std::sync::Arc;
+use std::thread;
+use std::time::{Duration, Instant};
+
+use crate::virtio::{DescriptorChain, Interrupt, Queue, Reader, Writer};
+use base::{error, set_rt_prio_limit, set_rt_round_robin, warn};
+use data_model::Le32;
+use sync::Mutex;
+use vm_memory::GuestMemory;
+
+use super::Error as VioSError;
+use super::*;
+use super::{Result, SoundError};
+use crate::virtio::snd::common::from_virtio_frame_rate;
+use crate::virtio::snd::constants::*;
+use crate::virtio::snd::layout::*;
+
+/// Messages that the worker can send to the stream (thread).
+pub enum StreamMsg {
+    SetParams(DescriptorChain, virtio_snd_pcm_set_params),
+    Prepare(DescriptorChain),
+    Start(DescriptorChain),
+    Stop(DescriptorChain),
+    Release(DescriptorChain),
+    Buffer(DescriptorChain),
+    Break,
+}
+
+enum StreamState {
+    New,
+    ParamsSet,
+    Prepared,
+    Started,
+    Stopped,
+    Released,
+}
+
+pub struct Stream {
+    stream_id: u32,
+    receiver: Receiver<StreamMsg>,
+    vios_client: Arc<VioSClient>,
+    guest_memory: GuestMemory,
+    control_queue: Arc<Mutex<Queue>>,
+    io_queue: Arc<Mutex<Queue>>,
+    interrupt: Arc<Interrupt>,
+    capture: bool,
+    current_state: StreamState,
+    period: Duration,
+    start_time: Instant,
+    next_buffer: Duration,
+    buffer_queue: VecDeque<DescriptorChain>,
+}
+
+impl Stream {
+    /// Start a new stream thread and return its handler.
+    pub fn try_new(
+        stream_id: u32,
+        vios_client: Arc<VioSClient>,
+        guest_memory: GuestMemory,
+        interrupt: Arc<Interrupt>,
+        control_queue: Arc<Mutex<Queue>>,
+        io_queue: Arc<Mutex<Queue>>,
+        capture: bool,
+    ) -> Result<StreamProxy> {
+        let (sender, receiver): (Sender<StreamMsg>, Receiver<StreamMsg>) = channel();
+        let thread = thread::Builder::new()
+            .name(format!("virtio_snd stream {}", stream_id))
+            .spawn(move || {
+                try_set_real_time_priority();
+
+                let mut stream = Stream {
+                    stream_id,
+                    receiver,
+                    vios_client,
+                    guest_memory,
+                    control_queue,
+                    io_queue,
+                    interrupt,
+                    capture,
+                    current_state: StreamState::New,
+                    period: Duration::from_millis(0),
+                    start_time: Instant::now(),
+                    next_buffer: Duration::from_millis(0),
+                    buffer_queue: VecDeque::new(),
+                };
+
+                if let Err(e) = stream.stream_loop() {
+                    error!("virtio-snd: Error in stream {}: {}", stream_id, e);
+                }
+            })
+            .map_err(SoundError::CreateThread)?;
+        Ok(StreamProxy {
+            sender,
+            thread: Some(thread),
+        })
+    }
+
+    fn stream_loop(&mut self) -> Result<()> {
+        loop {
+            if !self.recv_msg()? {
+                break;
+            }
+            self.maybe_process_queued_buffers()?;
+        }
+        Ok(())
+    }
+
+    fn recv_msg(&mut self) -> Result<bool> {
+        let msg = self.receiver.recv().map_err(SoundError::StreamThreadRecv)?;
+        let (code, desc, next_state) = match msg {
+            StreamMsg::SetParams(desc, params) => {
+                let code = match self.vios_client.set_stream_parameters_raw(params) {
+                    Ok(()) => {
+                        let frame_rate = from_virtio_frame_rate(params.rate).unwrap_or(0) as u64;
+                        self.period = Duration::from_millis(
+                            (params.period_bytes.to_native() as u64 * 1000u64)
+                                / frame_rate
+                                / params.channels as u64
+                                / bytes_per_sample(params.format) as u64,
+                        );
+                        VIRTIO_SND_S_OK
+                    }
+                    Err(e) => {
+                        error!(
+                            "virtio-snd: Error setting parameters for stream {}: {}",
+                            self.stream_id, e
+                        );
+                        vios_error_to_status_code(e)
+                    }
+                };
+                (code, desc, StreamState::ParamsSet)
+            }
+            StreamMsg::Prepare(desc) => {
+                let code = match self.vios_client.prepare_stream(self.stream_id) {
+                    Ok(()) => VIRTIO_SND_S_OK,
+                    Err(e) => {
+                        error!(
+                            "virtio-snd: Failed to prepare stream {}: {}",
+                            self.stream_id, e
+                        );
+                        vios_error_to_status_code(e)
+                    }
+                };
+                (code, desc, StreamState::Prepared)
+            }
+            StreamMsg::Start(desc) => {
+                let code = match self.vios_client.start_stream(self.stream_id) {
+                    Ok(()) => VIRTIO_SND_S_OK,
+                    Err(e) => {
+                        error!(
+                            "virtio-snd: Failed to start stream {}: {}",
+                            self.stream_id, e
+                        );
+                        vios_error_to_status_code(e)
+                    }
+                };
+                self.start_time = Instant::now();
+                self.next_buffer = Duration::from_millis(0);
+                (code, desc, StreamState::Started)
+            }
+            StreamMsg::Stop(desc) => {
+                let code = match self.vios_client.stop_stream(self.stream_id) {
+                    Ok(()) => VIRTIO_SND_S_OK,
+                    Err(e) => {
+                        error!(
+                            "virtio-snd: Failed to stop stream {}: {}",
+                            self.stream_id, e
+                        );
+                        vios_error_to_status_code(e)
+                    }
+                };
+                (code, desc, StreamState::Stopped)
+            }
+            StreamMsg::Release(desc) => {
+                let code = match self.vios_client.release_stream(self.stream_id) {
+                    Ok(()) => VIRTIO_SND_S_OK,
+                    Err(e) => {
+                        error!(
+                            "virtio-snd: Failed to release stream {}: {}",
+                            self.stream_id, e
+                        );
+                        vios_error_to_status_code(e)
+                    }
+                };
+                (code, desc, StreamState::Released)
+            }
+            StreamMsg::Buffer(d) => {
+                // Buffers may arrive while in several states:
+                // - Prepared: Buffer should be queued and played when start cmd arrives
+                // - Started: Buffer should be processed immediately
+                // - Stopped: Buffer should be returned to the guest immediately
+                // Because we may need to wait to process the buffer, we always queue it and
+                // decide what to do with queued buffers after every message.
+                self.buffer_queue.push_back(d);
+                // return here to avoid replying on control queue below
+                return Ok(true);
+            }
+            StreamMsg::Break => {
+                return Ok(false);
+            }
+        };
+        reply_control_op_status(
+            code,
+            desc,
+            &self.guest_memory,
+            &self.control_queue,
+            self.interrupt.deref(),
+        )?;
+        self.current_state = next_state;
+        Ok(true)
+    }
+
+    fn maybe_process_queued_buffers(&mut self) -> Result<()> {
+        match self.current_state {
+            StreamState::Started => {
+                while let Some(desc) = self.buffer_queue.pop_front() {
+                    let mut reader = Reader::new(self.guest_memory.clone(), desc.clone())
+                        .map_err(SoundError::CreateReader)?;
+                    // Ignore the first buffer, it was already read by the time this thread
+                    // receives the descriptor
+                    reader.consume(std::mem::size_of::<virtio_snd_pcm_xfer>());
+                    let desc_index = desc.index;
+                    let mut writer = Writer::new(self.guest_memory.clone(), desc)
+                        .map_err(SoundError::CreateWriter)?;
+                    let io_res = if self.capture {
+                        let buffer_size =
+                            writer.available_bytes() - std::mem::size_of::<virtio_snd_pcm_status>();
+                        self.vios_client
+                            .request_audio_data(self.stream_id, buffer_size, |vslice| {
+                                writer.write_from_volatile_slice(*vslice)
+                            })
+                    } else {
+                        self.vios_client.inject_audio_data(
+                            self.stream_id,
+                            reader.available_bytes(),
+                            |vslice| reader.read_to_volatile_slice(vslice),
+                        )
+                    };
+                    let (code, latency) = match io_res {
+                        Ok((latency, _)) => (VIRTIO_SND_S_OK, latency),
+                        Err(e) => {
+                            error!(
+                                "virtio-snd: Failed IO operation in stream {}: {}",
+                                self.stream_id, e
+                            );
+                            (VIRTIO_SND_S_IO_ERR, 0)
+                        }
+                    };
+                    if let Err(e) = writer.write_obj(virtio_snd_pcm_status {
+                        status: Le32::from(code),
+                        latency_bytes: Le32::from(latency),
+                    }) {
+                        error!(
+                            "virtio-snd: Failed to write pcm status from stream {} thread: {}",
+                            self.stream_id, e
+                        );
+                    }
+
+                    self.next_buffer += self.period;
+                    let elapsed = self.start_time.elapsed();
+                    if elapsed < self.next_buffer {
+                        // Completing an IO request can be considered an elapsed period
+                        // notification by the driver, so we must wait the right amount of time to
+                        // release the buffer if the sound server client returned too soon.
+                        std::thread::sleep(self.next_buffer - elapsed);
+                    }
+                    {
+                        let mut io_queue_lock = self.io_queue.lock();
+                        io_queue_lock.add_used(
+                            &self.guest_memory,
+                            desc_index,
+                            writer.bytes_written() as u32,
+                        );
+                        io_queue_lock.trigger_interrupt(&self.guest_memory, self.interrupt.deref());
+                    }
+                }
+            }
+            StreamState::Stopped | StreamState::Released => {
+                // For some reason playback buffers can arrive after stop and release (maybe because
+                // buffer-ready notifications arrive over eventfds and those are processed in
+                // random order?). The spec requires the device to not confirm the release of a
+                // stream until all IO buffers have been released, but that's impossible to
+                // guarantee if a buffer arrives after release is requested. Luckily it seems to
+                // work fine if the buffer is released after the release command is completed.
+                while let Some(desc) = self.buffer_queue.pop_front() {
+                    reply_pcm_buffer_status(
+                        VIRTIO_SND_S_OK,
+                        0,
+                        desc,
+                        &self.guest_memory,
+                        &self.io_queue,
+                        self.interrupt.deref(),
+                    )?;
+                }
+            }
+            StreamState::Prepared => {} // Do nothing, any buffers will be processed after start
+            _ => {
+                if self.buffer_queue.len() > 0 {
+                    warn!("virtio-snd: Buffers received while in unexpected state");
+                }
+            }
+        }
+        Ok(())
+    }
+}
+
+impl Drop for Stream {
+    fn drop(&mut self) {
+        // Try to stop and release the stream in case it was playing, these operations will fail if the
+        // stream is already released, just ignore that failure
+        let _ = self.vios_client.stop_stream(self.stream_id);
+        let _ = self.vios_client.release_stream(self.stream_id);
+
+        // Also release any pending buffer
+        while let Some(desc) = self.buffer_queue.pop_front() {
+            if let Err(e) = reply_pcm_buffer_status(
+                VIRTIO_SND_S_IO_ERR,
+                0,
+                desc,
+                &self.guest_memory,
+                &self.io_queue,
+                self.interrupt.deref(),
+            ) {
+                error!(
+                    "virtio-snd: Failed to reply buffer on stream {}: {}",
+                    self.stream_id, e
+                );
+            }
+        }
+    }
+}
+
+/// Basically a proxy to the thread handling a particular stream.
+pub struct StreamProxy {
+    sender: Sender<StreamMsg>,
+    thread: Option<thread::JoinHandle<()>>,
+}
+
+impl StreamProxy {
+    /// Access the underlying sender to clone it or send messages
+    pub fn msg_sender(&self) -> &Sender<StreamMsg> {
+        &self.sender
+    }
+
+    /// Send a message to the stream thread on the other side of this sender
+    pub fn send_msg(sender: &Sender<StreamMsg>, msg: StreamMsg) -> Result<()> {
+        sender.send(msg).map_err(SoundError::StreamThreadSend)
+    }
+
+    /// Convenience function to send a message to this stream's thread
+    pub fn send(&self, msg: StreamMsg) -> Result<()> {
+        Self::send_msg(&self.sender, msg)
+    }
+
+    fn stop_thread(&mut self) {
+        if let Err(e) = self.send(StreamMsg::Break) {
+            error!(
+                "virtio-snd: Failed to send Break msg to stream thread: {}",
+                e
+            );
+        }
+        if let Some(th) = self.thread.take() {
+            if let Err(e) = th.join() {
+                error!("virtio-snd: Panic detected on stream thread: {:?}", e);
+            }
+        }
+    }
+}
+
+impl Drop for StreamProxy {
+    fn drop(&mut self) {
+        self.stop_thread();
+    }
+}
+
+/// Attempts to set the current thread's priority to a value hight enough to handle audio IO. This
+/// may fail due to insuficient permissions.
+pub fn try_set_real_time_priority() {
+    const AUDIO_THREAD_RTPRIO: u16 = 10; // Matches other cros audio clients.
+    if let Err(e) = set_rt_prio_limit(u64::from(AUDIO_THREAD_RTPRIO))
+        .and_then(|_| set_rt_round_robin(i32::from(AUDIO_THREAD_RTPRIO)))
+    {
+        warn!("Failed to set audio stream thread to real time: {}", e);
+    }
+}
+
+/// Gets the appropriate virtio-snd error to return to the driver from a VioSError.
+pub fn vios_error_to_status_code(e: VioSError) -> u32 {
+    match e {
+        VioSError::ServerIOError(_) => VIRTIO_SND_S_IO_ERR,
+        _ => VIRTIO_SND_S_NOT_SUPP,
+    }
+}
+
+/// Encapsulates sending the virtio_snd_hdr struct back to the driver.
+pub fn reply_control_op_status(
+    code: u32,
+    desc: DescriptorChain,
+    guest_memory: &GuestMemory,
+    queue: &Arc<Mutex<Queue>>,
+    interrupt: &Interrupt,
+) -> Result<()> {
+    let desc_index = desc.index;
+    let mut writer = Writer::new(guest_memory.clone(), desc).map_err(SoundError::Descriptor)?;
+    writer
+        .write_obj(virtio_snd_hdr {
+            code: Le32::from(code),
+        })
+        .map_err(SoundError::QueueIO)?;
+    {
+        let mut queue_lock = queue.lock();
+        queue_lock.add_used(guest_memory, desc_index, writer.bytes_written() as u32);
+        queue_lock.trigger_interrupt(guest_memory, interrupt);
+    }
+    Ok(())
+}
+
+/// Encapsulates sending the virtio_snd_pcm_status struct back to the driver.
+pub fn reply_pcm_buffer_status(
+    status: u32,
+    latency_bytes: u32,
+    desc: DescriptorChain,
+    guest_memory: &GuestMemory,
+    queue: &Arc<Mutex<Queue>>,
+    interrupt: &Interrupt,
+) -> Result<()> {
+    let desc_index = desc.index;
+    let mut writer = Writer::new(guest_memory.clone(), desc).map_err(SoundError::Descriptor)?;
+    if writer.available_bytes() > std::mem::size_of::<virtio_snd_pcm_status>() {
+        writer
+            .consume_bytes(writer.available_bytes() - std::mem::size_of::<virtio_snd_pcm_status>());
+    }
+    writer
+        .write_obj(virtio_snd_pcm_status {
+            status: Le32::from(status),
+            latency_bytes: Le32::from(latency_bytes),
+        })
+        .map_err(SoundError::QueueIO)?;
+    {
+        let mut queue_lock = queue.lock();
+        queue_lock.add_used(guest_memory, desc_index, writer.bytes_written() as u32);
+        queue_lock.trigger_interrupt(guest_memory, interrupt);
+    }
+    Ok(())
+}
+
+fn bytes_per_sample(format: u8) -> usize {
+    match format {
+        VIRTIO_SND_PCM_FMT_IMA_ADPCM => 1usize,
+        VIRTIO_SND_PCM_FMT_MU_LAW => 1usize,
+        VIRTIO_SND_PCM_FMT_A_LAW => 1usize,
+        VIRTIO_SND_PCM_FMT_S8 => 1usize,
+        VIRTIO_SND_PCM_FMT_U8 => 1usize,
+        VIRTIO_SND_PCM_FMT_S16 => 2usize,
+        VIRTIO_SND_PCM_FMT_U16 => 2usize,
+        VIRTIO_SND_PCM_FMT_S32 => 4usize,
+        VIRTIO_SND_PCM_FMT_U32 => 4usize,
+        VIRTIO_SND_PCM_FMT_FLOAT => 4usize,
+        VIRTIO_SND_PCM_FMT_FLOAT64 => 8usize,
+        // VIRTIO_SND_PCM_FMT_DSD_U8
+        // VIRTIO_SND_PCM_FMT_DSD_U16
+        // VIRTIO_SND_PCM_FMT_DSD_U32
+        // VIRTIO_SND_PCM_FMT_IEC958_SUBFRAME
+        // VIRTIO_SND_PCM_FMT_S18_3
+        // VIRTIO_SND_PCM_FMT_U18_3
+        // VIRTIO_SND_PCM_FMT_S20_3
+        // VIRTIO_SND_PCM_FMT_U20_3
+        // VIRTIO_SND_PCM_FMT_S24_3
+        // VIRTIO_SND_PCM_FMT_U24_3
+        // VIRTIO_SND_PCM_FMT_S20
+        // VIRTIO_SND_PCM_FMT_U20
+        // VIRTIO_SND_PCM_FMT_S24
+        // VIRTIO_SND_PCM_FMT_U24
+        _ => {
+            // Some of these formats are not consistently stored in a particular size (24bits is
+            // sometimes stored in a 32bit word) while others are of variable size.
+            // The size per sample estimated here is designed to greatly underestimate the time it
+            // takes to play a buffer and depend instead on timings provided by the sound server if
+            // it supports these formats.
+            warn!(
+                "Unknown sample size for format {}, depending on sound server timing instead.",
+                format
+            );
+            1000usize
+        }
+    }
+}
diff --git a/devices/src/virtio/snd/vios_backend/worker.rs b/devices/src/virtio/snd/vios_backend/worker.rs
new file mode 100644
index 0000000..7fe2235
--- /dev/null
+++ b/devices/src/virtio/snd/vios_backend/worker.rs
@@ -0,0 +1,607 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::io::Read;
+use std::ops::Deref;
+use std::sync::{mpsc::Sender, Arc};
+use std::thread;
+
+use crate::virtio::{DescriptorChain, Interrupt, Queue, Reader, SignalableInterrupt, Writer};
+use base::{error, warn, Event, PollToken, WaitContext};
+use data_model::{DataInit, Le32};
+use sync::Mutex;
+use vm_memory::GuestMemory;
+
+use super::super::constants::*;
+use super::super::layout::*;
+use super::streams::*;
+use super::*;
+use super::{Result, SoundError};
+
+pub struct Worker {
+    // Lock order: Must never hold more than one queue lock at the same time.
+    interrupt: Arc<Interrupt>,
+    control_queue: Arc<Mutex<Queue>>,
+    control_queue_evt: Event,
+    event_queue: Queue,
+    event_queue_evt: Event,
+    guest_memory: GuestMemory,
+    vios_client: Arc<VioSClient>,
+    streams: Vec<StreamProxy>,
+    io_thread: Option<thread::JoinHandle<Result<()>>>,
+    io_kill: Event,
+}
+
+impl Worker {
+    /// Creates a new virtio-snd worker.
+    pub fn try_new(
+        vios_client: Arc<VioSClient>,
+        interrupt: Arc<Interrupt>,
+        guest_memory: GuestMemory,
+        control_queue: Arc<Mutex<Queue>>,
+        control_queue_evt: Event,
+        event_queue: Queue,
+        event_queue_evt: Event,
+        tx_queue: Arc<Mutex<Queue>>,
+        tx_queue_evt: Event,
+        rx_queue: Arc<Mutex<Queue>>,
+        rx_queue_evt: Event,
+    ) -> Result<Worker> {
+        let mut streams: Vec<StreamProxy> = Vec::with_capacity(vios_client.num_streams() as usize);
+        {
+            for stream_id in 0..vios_client.num_streams() {
+                let capture = vios_client
+                    .stream_info(stream_id)
+                    .map(|i| i.direction == VIRTIO_SND_D_INPUT)
+                    .unwrap_or(false);
+                let io_queue = if capture { &rx_queue } else { &tx_queue };
+                streams.push(Stream::try_new(
+                    stream_id,
+                    vios_client.clone(),
+                    guest_memory.clone(),
+                    interrupt.clone(),
+                    control_queue.clone(),
+                    io_queue.clone(),
+                    capture,
+                )?);
+            }
+        }
+        let (self_kill_io, kill_io) = Event::new()
+            .and_then(|e| Ok((e.try_clone()?, e)))
+            .map_err(SoundError::CreateEvent)?;
+
+        let interrupt_clone = interrupt.clone();
+        let guest_memory_clone = guest_memory.clone();
+        let senders: Vec<Sender<StreamMsg>> =
+            streams.iter().map(|sp| sp.msg_sender().clone()).collect();
+        let io_thread = thread::Builder::new()
+            .name(String::from("virtio_snd_io"))
+            .spawn(move || {
+                try_set_real_time_priority();
+
+                io_loop(
+                    interrupt_clone,
+                    guest_memory_clone,
+                    tx_queue,
+                    tx_queue_evt,
+                    rx_queue,
+                    rx_queue_evt,
+                    senders,
+                    kill_io,
+                )
+            })
+            .map_err(SoundError::CreateThread)?;
+        Ok(Worker {
+            interrupt,
+            control_queue,
+            control_queue_evt,
+            event_queue,
+            event_queue_evt,
+            guest_memory,
+            vios_client,
+            streams,
+            io_thread: Some(io_thread),
+            io_kill: self_kill_io,
+        })
+    }
+
+    /// Emulates the virtio-snd device. It won't return until something is written to the kill_evt
+    /// event or an unrecoverable error occurs.
+    pub fn control_loop(&mut self, kill_evt: Event) -> Result<()> {
+        let event_notifier = self
+            .vios_client
+            .get_event_notifier()
+            .map_err(SoundError::ClientEventNotifier)?;
+        #[derive(PollToken)]
+        enum Token {
+            ControlQAvailable,
+            EventQAvailable,
+            InterruptResample,
+            EventTriggered,
+            Kill,
+        }
+        let wait_ctx: WaitContext<Token> = WaitContext::build_with(&[
+            (&self.control_queue_evt, Token::ControlQAvailable),
+            (&self.event_queue_evt, Token::EventQAvailable),
+            (&event_notifier, Token::EventTriggered),
+            (&kill_evt, Token::Kill),
+        ])
+        .map_err(SoundError::WaitCtx)?;
+
+        if let Some(resample_evt) = self.interrupt.get_resample_evt() {
+            wait_ctx
+                .add(resample_evt, Token::InterruptResample)
+                .map_err(SoundError::WaitCtx)?;
+        }
+        'wait: loop {
+            let wait_events = wait_ctx.wait().map_err(SoundError::WaitCtx)?;
+
+            for wait_evt in wait_events.iter().filter(|e| e.is_readable) {
+                match wait_evt.token {
+                    Token::ControlQAvailable => {
+                        self.control_queue_evt
+                            .read()
+                            .map_err(SoundError::QueueEvt)?;
+                        self.process_controlq_buffers()?;
+                    }
+                    Token::EventQAvailable => {
+                        // Just read from the event object to make sure the producer of such events
+                        // never blocks. The buffers will only be used when actual virtio-snd
+                        // events are triggered.
+                        self.event_queue_evt.read().map_err(SoundError::QueueEvt)?;
+                    }
+                    Token::EventTriggered => {
+                        event_notifier.read().map_err(SoundError::QueueEvt)?;
+                        self.process_event_triggered()?;
+                    }
+                    Token::InterruptResample => {
+                        self.interrupt.interrupt_resample();
+                    }
+                    Token::Kill => {
+                        let _ = kill_evt.read();
+                        break 'wait;
+                    }
+                }
+            }
+        }
+        Ok(())
+    }
+
+    fn stop_io_thread(&mut self) {
+        if let Err(e) = self.io_kill.write(1) {
+            error!(
+                "virtio-snd: Failed to send Break msg to stream thread: {}",
+                e
+            );
+        }
+        if let Some(th) = self.io_thread.take() {
+            match th.join() {
+                Err(e) => {
+                    error!("virtio-snd: Panic detected on stream thread: {:?}", e);
+                }
+                Ok(r) => {
+                    if let Err(e) = r {
+                        error!("virtio-snd: IO thread exited with and error: {}", e);
+                    }
+                }
+            }
+        }
+    }
+
+    // Pops and handles all available ontrol queue buffers. Logs minor errors, but returns an
+    // Err if it encounters an unrecoverable error.
+    fn process_controlq_buffers(&mut self) -> Result<()> {
+        while let Some(avail_desc) = lock_pop_unlock(&self.control_queue, &self.guest_memory) {
+            let mut reader = Reader::new(self.guest_memory.clone(), avail_desc.clone())
+                .map_err(SoundError::Descriptor)?;
+            let available_bytes = reader.available_bytes();
+            if available_bytes < std::mem::size_of::<virtio_snd_hdr>() {
+                error!(
+                    "virtio-snd: Message received on control queue is too small: {}",
+                    available_bytes
+                );
+                return reply_control_op_status(
+                    VIRTIO_SND_S_BAD_MSG,
+                    avail_desc,
+                    &self.guest_memory,
+                    &self.control_queue,
+                    self.interrupt.deref(),
+                );
+            }
+            let mut read_buf = vec![0u8; available_bytes];
+            reader
+                .read_exact(&mut read_buf)
+                .map_err(SoundError::QueueIO)?;
+            let mut code: Le32 = Default::default();
+            // need to copy because the buffer may not be properly aligned
+            code.as_mut_slice()
+                .copy_from_slice(&read_buf[..std::mem::size_of::<Le32>()]);
+            let request_type = code.to_native();
+            match request_type {
+                VIRTIO_SND_R_JACK_INFO => {
+                    let (code, info_vec) = {
+                        match self.parse_info_query(&read_buf) {
+                            None => (VIRTIO_SND_S_BAD_MSG, Vec::new()),
+                            Some((start_id, count)) => {
+                                let end_id = start_id.saturating_add(count);
+                                if end_id > self.vios_client.num_jacks() {
+                                    error!(
+                                        "virtio-snd: Requested info on invalid jacks ids: {}..{}",
+                                        start_id,
+                                        end_id - 1
+                                    );
+                                    (VIRTIO_SND_S_NOT_SUPP, Vec::new())
+                                } else {
+                                    (
+                                        VIRTIO_SND_S_OK,
+                                        // Safe to unwrap because we just ensured all the ids are valid
+                                        (start_id..end_id)
+                                            .map(|id| self.vios_client.jack_info(id).unwrap())
+                                            .collect(),
+                                    )
+                                }
+                            }
+                        }
+                    };
+                    self.send_info_reply(avail_desc, code, info_vec)?;
+                }
+                VIRTIO_SND_R_JACK_REMAP => {
+                    let code = if read_buf.len() != std::mem::size_of::<virtio_snd_jack_remap>() {
+                        error!(
+                        "virtio-snd: The driver sent the wrong number bytes for a jack_remap struct: {}",
+                        read_buf.len()
+                        );
+                        VIRTIO_SND_S_BAD_MSG
+                    } else {
+                        let mut request: virtio_snd_jack_remap = Default::default();
+                        request.as_mut_slice().copy_from_slice(&read_buf);
+                        let jack_id = request.hdr.jack_id.to_native();
+                        let association = request.association.to_native();
+                        let sequence = request.sequence.to_native();
+                        if let Err(e) = self.vios_client.remap_jack(jack_id, association, sequence)
+                        {
+                            error!("virtio-snd: Failed to remap jack: {}", e);
+                            vios_error_to_status_code(e)
+                        } else {
+                            VIRTIO_SND_S_OK
+                        }
+                    };
+                    let desc_index = avail_desc.index;
+                    let mut writer = Writer::new(self.guest_memory.clone(), avail_desc)
+                        .map_err(SoundError::Descriptor)?;
+                    writer
+                        .write_obj(virtio_snd_hdr {
+                            code: Le32::from(code),
+                        })
+                        .map_err(SoundError::QueueIO)?;
+                    {
+                        let mut queue_lock = self.control_queue.lock();
+                        queue_lock.add_used(
+                            &self.guest_memory,
+                            desc_index,
+                            writer.bytes_written() as u32,
+                        );
+                        queue_lock.trigger_interrupt(&self.guest_memory, self.interrupt.deref());
+                    }
+                }
+                VIRTIO_SND_R_CHMAP_INFO => {
+                    let (code, info_vec) = {
+                        match self.parse_info_query(&read_buf) {
+                            None => (VIRTIO_SND_S_BAD_MSG, Vec::new()),
+                            Some((start_id, count)) => {
+                                let end_id = start_id.saturating_add(count);
+                                if end_id > self.vios_client.num_chmaps() {
+                                    error!(
+                                        "virtio-snd: Requested info on invalid chmaps ids: {}..{}",
+                                        start_id,
+                                        end_id - 1
+                                    );
+                                    (VIRTIO_SND_S_NOT_SUPP, Vec::new())
+                                } else {
+                                    (
+                                        VIRTIO_SND_S_OK,
+                                        // Safe to unwrap because we just ensured all the ids are valid
+                                        (start_id..end_id)
+                                            .map(|id| self.vios_client.chmap_info(id).unwrap())
+                                            .collect(),
+                                    )
+                                }
+                            }
+                        }
+                    };
+                    self.send_info_reply(avail_desc, code, info_vec)?;
+                }
+                VIRTIO_SND_R_PCM_INFO => {
+                    let (code, info_vec) = {
+                        match self.parse_info_query(&read_buf) {
+                            None => (VIRTIO_SND_S_BAD_MSG, Vec::new()),
+                            Some((start_id, count)) => {
+                                let end_id = start_id.saturating_add(count);
+                                if end_id > self.vios_client.num_streams() {
+                                    error!(
+                                        "virtio-snd: Requested info on invalid stream ids: {}..{}",
+                                        start_id,
+                                        end_id - 1
+                                    );
+                                    (VIRTIO_SND_S_NOT_SUPP, Vec::new())
+                                } else {
+                                    (
+                                        VIRTIO_SND_S_OK,
+                                        // Safe to unwrap because we just ensured all the ids are valid
+                                        (start_id..end_id)
+                                            .map(|id| self.vios_client.stream_info(id).unwrap())
+                                            .collect(),
+                                    )
+                                }
+                            }
+                        }
+                    };
+                    self.send_info_reply(avail_desc, code, info_vec)?;
+                }
+                VIRTIO_SND_R_PCM_SET_PARAMS => self.process_set_params(avail_desc, &read_buf)?,
+                VIRTIO_SND_R_PCM_PREPARE => {
+                    self.try_parse_pcm_hdr_and_send_msg(&read_buf, StreamMsg::Prepare(avail_desc))?
+                }
+                VIRTIO_SND_R_PCM_RELEASE => {
+                    self.try_parse_pcm_hdr_and_send_msg(&read_buf, StreamMsg::Release(avail_desc))?
+                }
+                VIRTIO_SND_R_PCM_START => {
+                    self.try_parse_pcm_hdr_and_send_msg(&read_buf, StreamMsg::Start(avail_desc))?
+                }
+                VIRTIO_SND_R_PCM_STOP => {
+                    self.try_parse_pcm_hdr_and_send_msg(&read_buf, StreamMsg::Stop(avail_desc))?
+                }
+                _ => {
+                    error!(
+                        "virtio-snd: Unknown control queue mesage code: {}",
+                        request_type
+                    );
+                    reply_control_op_status(
+                        VIRTIO_SND_S_NOT_SUPP,
+                        avail_desc,
+                        &self.guest_memory,
+                        &self.control_queue,
+                        self.interrupt.deref(),
+                    )?;
+                }
+            }
+        }
+        Ok(())
+    }
+
+    fn process_event_triggered(&mut self) -> Result<()> {
+        while let Some(evt) = self.vios_client.pop_event() {
+            if let Some(desc) = self.event_queue.pop(&self.guest_memory) {
+                let desc_index = desc.index;
+                let mut writer =
+                    Writer::new(self.guest_memory.clone(), desc).map_err(SoundError::Descriptor)?;
+                writer.write_obj(evt).map_err(SoundError::QueueIO)?;
+                self.event_queue.add_used(
+                    &self.guest_memory,
+                    desc_index,
+                    writer.bytes_written() as u32,
+                );
+                {
+                    self.event_queue
+                        .trigger_interrupt(&self.guest_memory, self.interrupt.deref());
+                }
+            } else {
+                warn!("virtio-snd: Dropping event because there are no buffers in virtqueue");
+            }
+        }
+        Ok(())
+    }
+
+    fn parse_info_query(&mut self, read_buf: &[u8]) -> Option<(u32, u32)> {
+        if read_buf.len() != std::mem::size_of::<virtio_snd_query_info>() {
+            error!(
+                "virtio-snd: The driver sent the wrong number bytes for a pcm_info struct: {}",
+                read_buf.len()
+            );
+            return None;
+        }
+        let mut query: virtio_snd_query_info = Default::default();
+        query.as_mut_slice().copy_from_slice(&read_buf);
+        let start_id = query.start_id.to_native();
+        let count = query.count.to_native();
+        Some((start_id, count))
+    }
+
+    // Returns Err if it encounters an unrecoverable error, Ok otherwise
+    fn process_set_params(&mut self, desc: DescriptorChain, read_buf: &[u8]) -> Result<()> {
+        if read_buf.len() != std::mem::size_of::<virtio_snd_pcm_set_params>() {
+            error!(
+                "virtio-snd: The driver sent a buffer of the wrong size for a set_params struct: {}",
+                read_buf.len()
+                );
+            return reply_control_op_status(
+                VIRTIO_SND_S_BAD_MSG,
+                desc,
+                &self.guest_memory,
+                &self.control_queue,
+                self.interrupt.deref(),
+            );
+        }
+        let mut params: virtio_snd_pcm_set_params = Default::default();
+        params.as_mut_slice().copy_from_slice(&read_buf);
+        let stream_id = params.hdr.stream_id.to_native();
+        if stream_id < self.vios_client.num_streams() {
+            self.streams[stream_id as usize].send(StreamMsg::SetParams(desc, params))
+        } else {
+            error!(
+                "virtio-snd: Driver requested operation on invalid stream: {}",
+                stream_id
+            );
+            reply_control_op_status(
+                VIRTIO_SND_S_BAD_MSG,
+                desc,
+                &self.guest_memory,
+                &self.control_queue,
+                self.interrupt.deref(),
+            )
+        }
+    }
+
+    // Returns Err if it encounters an unrecoverable error, Ok otherwise
+    fn try_parse_pcm_hdr_and_send_msg(&mut self, read_buf: &[u8], msg: StreamMsg) -> Result<()> {
+        if read_buf.len() != std::mem::size_of::<virtio_snd_pcm_hdr>() {
+            error!(
+                "virtio-snd: The driver sent a buffer too small to contain a header: {}",
+                read_buf.len()
+            );
+            return reply_control_op_status(
+                VIRTIO_SND_S_BAD_MSG,
+                match msg {
+                    StreamMsg::Prepare(d)
+                    | StreamMsg::Start(d)
+                    | StreamMsg::Stop(d)
+                    | StreamMsg::Release(d) => d,
+                    _ => panic!("virtio-snd: Can't handle message. This is a BUG!!"),
+                },
+                &self.guest_memory,
+                &self.control_queue,
+                self.interrupt.deref(),
+            );
+        }
+        let mut pcm_hdr: virtio_snd_pcm_hdr = Default::default();
+        pcm_hdr.as_mut_slice().copy_from_slice(&read_buf);
+        let stream_id = pcm_hdr.stream_id.to_native();
+        if stream_id < self.vios_client.num_streams() {
+            self.streams[stream_id as usize].send(msg)
+        } else {
+            error!(
+                "virtio-snd: Driver requested operation on invalid stream: {}",
+                stream_id
+            );
+            reply_control_op_status(
+                VIRTIO_SND_S_BAD_MSG,
+                match msg {
+                    StreamMsg::Prepare(d)
+                    | StreamMsg::Start(d)
+                    | StreamMsg::Stop(d)
+                    | StreamMsg::Release(d) => d,
+                    _ => panic!("virtio-snd: Can't handle message. This is a BUG!!"),
+                },
+                &self.guest_memory,
+                &self.control_queue,
+                self.interrupt.deref(),
+            )
+        }
+    }
+
+    fn send_info_reply<T: DataInit>(
+        &mut self,
+        desc: DescriptorChain,
+        code: u32,
+        info_vec: Vec<T>,
+    ) -> Result<()> {
+        let desc_index = desc.index;
+        let mut writer =
+            Writer::new(self.guest_memory.clone(), desc).map_err(SoundError::Descriptor)?;
+        writer
+            .write_obj(virtio_snd_hdr {
+                code: Le32::from(code),
+            })
+            .map_err(SoundError::QueueIO)?;
+        for info in info_vec {
+            writer.write_obj(info).map_err(SoundError::QueueIO)?;
+        }
+        {
+            let mut queue_lock = self.control_queue.lock();
+            queue_lock.add_used(
+                &self.guest_memory,
+                desc_index,
+                writer.bytes_written() as u32,
+            );
+            queue_lock.trigger_interrupt(&self.guest_memory, self.interrupt.deref());
+        }
+        Ok(())
+    }
+}
+
+impl Drop for Worker {
+    fn drop(&mut self) {
+        self.stop_io_thread();
+    }
+}
+
+fn io_loop(
+    interrupt: Arc<Interrupt>,
+    guest_memory: GuestMemory,
+    tx_queue: Arc<Mutex<Queue>>,
+    tx_queue_evt: Event,
+    rx_queue: Arc<Mutex<Queue>>,
+    rx_queue_evt: Event,
+    senders: Vec<Sender<StreamMsg>>,
+    kill_evt: Event,
+) -> Result<()> {
+    #[derive(PollToken)]
+    enum Token {
+        TxQAvailable,
+        RxQAvailable,
+        Kill,
+    }
+    let wait_ctx: WaitContext<Token> = WaitContext::build_with(&[
+        (&tx_queue_evt, Token::TxQAvailable),
+        (&rx_queue_evt, Token::RxQAvailable),
+        (&kill_evt, Token::Kill),
+    ])
+    .map_err(SoundError::WaitCtx)?;
+
+    'wait: loop {
+        let wait_events = wait_ctx.wait().map_err(SoundError::WaitCtx)?;
+        for wait_evt in wait_events.iter().filter(|e| e.is_readable) {
+            let queue = match wait_evt.token {
+                Token::TxQAvailable => {
+                    tx_queue_evt.read().map_err(SoundError::QueueEvt)?;
+                    &tx_queue
+                }
+                Token::RxQAvailable => {
+                    rx_queue_evt.read().map_err(SoundError::QueueEvt)?;
+                    &rx_queue
+                }
+                Token::Kill => {
+                    let _ = kill_evt.read();
+                    break 'wait;
+                }
+            };
+            while let Some(avail_desc) = lock_pop_unlock(&queue, &guest_memory) {
+                let mut reader = Reader::new(guest_memory.clone(), avail_desc.clone())
+                    .map_err(SoundError::Descriptor)?;
+                let xfer: virtio_snd_pcm_xfer = reader.read_obj().map_err(SoundError::QueueIO)?;
+                let stream_id = xfer.stream_id.to_native();
+                if stream_id as usize >= senders.len() {
+                    error!(
+                        "virtio-snd: Driver sent buffer for invalid stream: {}",
+                        stream_id
+                    );
+                    reply_pcm_buffer_status(
+                        VIRTIO_SND_S_IO_ERR,
+                        0,
+                        avail_desc,
+                        &guest_memory,
+                        queue,
+                        interrupt.deref(),
+                    )?;
+                } else {
+                    StreamProxy::send_msg(
+                        &senders[stream_id as usize],
+                        StreamMsg::Buffer(avail_desc),
+                    )?;
+                }
+            }
+        }
+    }
+    Ok(())
+}
+
+// If queue.lock().pop() is used directly in the condition of a 'while' loop the lock is held over
+// the entire loop block. Encapsulating it in this fuction guarantees that the lock is dropped
+// immediately after pop() is called, which allows the code to remain somewhat simpler.
+fn lock_pop_unlock(
+    queue: &Arc<Mutex<Queue>>,
+    guest_memory: &GuestMemory,
+) -> Option<DescriptorChain> {
+    queue.lock().pop(guest_memory)
+}
diff --git a/devices/src/virtio/tpm.rs b/devices/src/virtio/tpm.rs
index e9e07c9..57f6c51 100644
--- a/devices/src/virtio/tpm.rs
+++ b/devices/src/virtio/tpm.rs
@@ -81,23 +81,23 @@
 
 impl Worker {
     fn process_queue(&mut self) -> NeedsInterrupt {
-        let avail_desc = match self.queue.pop(&self.mem) {
-            Some(avail_desc) => avail_desc,
-            None => return NeedsInterrupt::No,
-        };
+        let mut needs_interrupt = NeedsInterrupt::No;
+        while let Some(avail_desc) = self.queue.pop(&self.mem) {
+            let index = avail_desc.index;
 
-        let index = avail_desc.index;
+            let len = match self.device.perform_work(&self.mem, avail_desc) {
+                Ok(len) => len,
+                Err(err) => {
+                    error!("{}", err);
+                    0
+                }
+            };
 
-        let len = match self.device.perform_work(&self.mem, avail_desc) {
-            Ok(len) => len,
-            Err(err) => {
-                error!("{}", err);
-                0
-            }
-        };
+            self.queue.add_used(&self.mem, index, len);
+            needs_interrupt = NeedsInterrupt::Yes;
+        }
 
-        self.queue.add_used(&self.mem, index, len);
-        NeedsInterrupt::Yes
+        needs_interrupt
     }
 
     fn run(mut self) {
@@ -154,7 +154,7 @@
                 }
             }
             if needs_interrupt == NeedsInterrupt::Yes {
-                self.interrupt.signal_used_queue(self.queue.vector);
+                self.queue.trigger_interrupt(&self.mem, &self.interrupt);
             }
         }
     }
diff --git a/devices/src/virtio/vhost/user/block.rs b/devices/src/virtio/vhost/user/block.rs
index 135bff7..926d2cd 100644
--- a/devices/src/virtio/vhost/user/block.rs
+++ b/devices/src/virtio/vhost/user/block.rs
@@ -47,6 +47,7 @@
             | 1 << VIRTIO_BLK_F_DISCARD
             | 1 << VIRTIO_BLK_F_WRITE_ZEROES
             | 1 << VIRTIO_RING_F_EVENT_IDX
+            | base_features
             | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
         let init_features = base_features | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
         let allow_protocol_features = VhostUserProtocolFeatures::CONFIG;
diff --git a/devices/src/virtio/vhost/user/console.rs b/devices/src/virtio/vhost/user/console.rs
index 319f05b..df6076f 100644
--- a/devices/src/virtio/vhost/user/console.rs
+++ b/devices/src/virtio/vhost/user/console.rs
@@ -34,6 +34,7 @@
         let queues_num = 2;
 
         let allow_features = 1u64 << crate::virtio::VIRTIO_F_VERSION_1
+            | base_features
             | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
         let init_features = base_features | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
         let allow_protocol_features = VhostUserProtocolFeatures::CONFIG;
diff --git a/devices/src/virtio/vhost/user/fs.rs b/devices/src/virtio/vhost/user/fs.rs
index ca4be80..02dfa01 100644
--- a/devices/src/virtio/vhost/user/fs.rs
+++ b/devices/src/virtio/vhost/user/fs.rs
@@ -53,6 +53,7 @@
         let socket = UnixStream::connect(&socket_path).map_err(Error::SocketConnect)?;
 
         let allow_features = 1u64 << crate::virtio::VIRTIO_F_VERSION_1
+            | base_features
             | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
         let init_features = base_features | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
         let allow_protocol_features =
diff --git a/devices/src/virtio/vhost/user/mac80211_hwsim.rs b/devices/src/virtio/vhost/user/mac80211_hwsim.rs
new file mode 100644
index 0000000..f73fa43
--- /dev/null
+++ b/devices/src/virtio/vhost/user/mac80211_hwsim.rs
@@ -0,0 +1,168 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::cell::RefCell;
+use std::os::unix::net::UnixStream;
+use std::path::Path;
+use std::thread;
+use std::u32;
+
+use base::{error, Event, RawDescriptor};
+use cros_async::Executor;
+use thiserror::Error as ThisError;
+use vm_memory::GuestMemory;
+use vmm_vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
+
+use crate::virtio::vhost::user::handler::VhostUserHandler;
+use crate::virtio::vhost::user::worker::Worker;
+use crate::virtio::vhost::user::Error;
+use crate::virtio::{Interrupt, Queue, VirtioDevice, TYPE_MAC80211_HWSIM, VIRTIO_F_VERSION_1};
+
+use std::result::Result;
+
+#[derive(ThisError, Debug)]
+enum Mac80211HwsimError {
+    #[error("failed to activate queues: {0}")]
+    ActivateQueue(Error),
+    #[error("failed to kill event pair: {0}")]
+    CreateKillEventPair(base::Error),
+    #[error("failed to spawn mac80211_hwsim worker: {0}")]
+    SpawnWorker(std::io::Error),
+}
+
+const QUEUE_SIZE: u16 = 256;
+const QUEUE_COUNT: usize = 2;
+
+pub struct Mac80211Hwsim {
+    kill_evt: Option<Event>,
+    worker_thread: Option<thread::JoinHandle<Worker>>,
+    handler: RefCell<VhostUserHandler>,
+    queue_sizes: Vec<u16>,
+}
+
+impl Mac80211Hwsim {
+    pub fn new<P: AsRef<Path>>(base_features: u64, socket_path: P) -> Result<Mac80211Hwsim, Error> {
+        let socket = UnixStream::connect(&socket_path).map_err(Error::SocketConnect)?;
+
+        let allow_features = 1 << VIRTIO_F_VERSION_1
+            | base_features
+            | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
+        let init_features = base_features | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
+        let allow_protocol_features = VhostUserProtocolFeatures::empty();
+
+        let mut handler = VhostUserHandler::new_from_stream(
+            socket,
+            QUEUE_COUNT as u64, /* # of queues */
+            allow_features,
+            init_features,
+            allow_protocol_features,
+        )?;
+        let queue_sizes = handler.queue_sizes(QUEUE_SIZE, QUEUE_COUNT)?;
+
+        Ok(Mac80211Hwsim {
+            kill_evt: None,
+            worker_thread: None,
+            handler: RefCell::new(handler),
+            queue_sizes,
+        })
+    }
+
+    fn activate_internal(
+        &mut self,
+        mem: GuestMemory,
+        interrupt: Interrupt,
+        queues: Vec<Queue>,
+        queue_evts: Vec<Event>,
+    ) -> Result<(), Mac80211HwsimError> {
+        self.handler
+            .borrow_mut()
+            .activate(&mem, &interrupt, &queues, &queue_evts)
+            .map_err(Mac80211HwsimError::ActivateQueue)?;
+
+        let (self_kill_evt, kill_evt) = Event::new()
+            .and_then(|e| Ok((e.try_clone()?, e)))
+            .map_err(Mac80211HwsimError::CreateKillEventPair)?;
+
+        self.kill_evt = Some(self_kill_evt);
+
+        let join_handle = thread::Builder::new()
+            .name("vhost_user_mac80211_hwsim".to_string())
+            .spawn(move || {
+                let ex = Executor::new().expect("failed to create an executor");
+                let mut worker = Worker {
+                    queues,
+                    mem,
+                    kill_evt,
+                };
+                if let Err(e) = worker.run(&ex, interrupt) {
+                    error!("failed to start a worker: {}", e);
+                }
+                worker
+            })
+            .map_err(Mac80211HwsimError::SpawnWorker)?;
+
+        self.worker_thread = Some(join_handle);
+
+        Ok(())
+    }
+}
+
+impl Drop for Mac80211Hwsim {
+    fn drop(&mut self) {
+        if let Some(kill_evt) = self.kill_evt.take() {
+            if let Some(worker_thread) = self.worker_thread.take() {
+                if let Err(e) = kill_evt.write(1) {
+                    error!("failed to write to kill_evt: {}", e);
+                    return;
+                }
+                let _ = worker_thread.join();
+            }
+        }
+    }
+}
+
+impl VirtioDevice for Mac80211Hwsim {
+    fn keep_rds(&self) -> Vec<RawDescriptor> {
+        Vec::new()
+    }
+
+    fn features(&self) -> u64 {
+        self.handler.borrow().avail_features
+    }
+
+    fn ack_features(&mut self, features: u64) {
+        if let Err(e) = self.handler.borrow_mut().ack_features(features) {
+            error!("failed to enable features 0x{:x}: {}", features, e);
+        }
+    }
+
+    fn device_type(&self) -> u32 {
+        TYPE_MAC80211_HWSIM
+    }
+
+    fn queue_max_sizes(&self) -> &[u16] {
+        self.queue_sizes.as_slice()
+    }
+
+    fn activate(
+        &mut self,
+        mem: GuestMemory,
+        interrupt: Interrupt,
+        queues: Vec<Queue>,
+        queue_evts: Vec<Event>,
+    ) {
+        if let Err(e) = self.activate_internal(mem, interrupt, queues, queue_evts) {
+            error!("Failed to activate mac80211_hwsim: {}", e);
+        }
+    }
+
+    fn reset(&mut self) -> bool {
+        if let Err(e) = self.handler.borrow_mut().reset(self.queue_sizes.len()) {
+            error!("Failed to reset mac80211_hwsim device: {}", e);
+            false
+        } else {
+            true
+        }
+    }
+}
diff --git a/devices/src/virtio/vhost/user/mod.rs b/devices/src/virtio/vhost/user/mod.rs
index 568ab88..03d28e7 100644
--- a/devices/src/virtio/vhost/user/mod.rs
+++ b/devices/src/virtio/vhost/user/mod.rs
@@ -6,6 +6,7 @@
 mod console;
 mod fs;
 mod handler;
+mod mac80211_hwsim;
 mod net;
 mod wl;
 mod worker;
@@ -14,6 +15,7 @@
 pub use self::console::*;
 pub use self::fs::*;
 pub use self::handler::VhostUserHandler;
+pub use self::mac80211_hwsim::*;
 pub use self::net::*;
 pub use self::wl::*;
 
diff --git a/devices/src/virtio/vhost/user/net.rs b/devices/src/virtio/vhost/user/net.rs
index 90a0cf6..bff2783 100644
--- a/devices/src/virtio/vhost/user/net.rs
+++ b/devices/src/virtio/vhost/user/net.rs
@@ -46,6 +46,7 @@
             | 1 << virtio_net::VIRTIO_NET_F_HOST_UFO
             | 1 << virtio_net::VIRTIO_NET_F_MQ
             | 1 << VIRTIO_RING_F_EVENT_IDX
+            | base_features
             | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
         let init_features = base_features | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
         let allow_protocol_features =
diff --git a/devices/src/virtio/vhost/user/wl.rs b/devices/src/virtio/vhost/user/wl.rs
index cb6009e..89b5d6a 100644
--- a/devices/src/virtio/vhost/user/wl.rs
+++ b/devices/src/virtio/vhost/user/wl.rs
@@ -32,6 +32,7 @@
         let allow_features = 1u64 << crate::virtio::VIRTIO_F_VERSION_1
             | 1 << VIRTIO_WL_F_TRANS_FLAGS
             | 1 << VIRTIO_WL_F_SEND_FENCES
+            | base_features
             | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
         let init_features = base_features | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
         let allow_protocol_features =
diff --git a/devices/src/virtio/video/command.rs b/devices/src/virtio/video/command.rs
index 3e71850..48aa564 100644
--- a/devices/src/virtio/video/command.rs
+++ b/devices/src/virtio/video/command.rs
@@ -272,9 +272,12 @@
                 let virtio_video_get_control { control, .. } = r.read_obj()?;
                 let ctrl_type = match control.into() {
                     VIRTIO_VIDEO_CONTROL_BITRATE => CtrlType::Bitrate,
+                    VIRTIO_VIDEO_CONTROL_BITRATE_PEAK => CtrlType::BitratePeak,
+                    VIRTIO_VIDEO_CONTROL_BITRATE_MODE => CtrlType::BitrateMode,
                     VIRTIO_VIDEO_CONTROL_PROFILE => CtrlType::Profile,
                     VIRTIO_VIDEO_CONTROL_LEVEL => CtrlType::Level,
                     VIRTIO_VIDEO_CONTROL_FORCE_KEYFRAME => CtrlType::ForceKeyframe,
+                    VIRTIO_VIDEO_CONTROL_PREPEND_SPSPPS_TO_IDR => CtrlType::PrependSpsPpsToIdr,
                     t => {
                         return Err(ReadCmdError::UnsupportedCtrlType(t));
                     }
@@ -292,6 +295,16 @@
                             .bitrate
                             .into(),
                     ),
+                    VIRTIO_VIDEO_CONTROL_BITRATE_PEAK => CtrlVal::BitratePeak(
+                        r.read_obj::<virtio_video_control_val_bitrate_peak>()?
+                            .bitrate_peak
+                            .into(),
+                    ),
+                    VIRTIO_VIDEO_CONTROL_BITRATE_MODE => CtrlVal::BitrateMode(
+                        r.read_obj::<virtio_video_control_val_bitrate_mode>()?
+                            .bitrate_mode
+                            .try_into()?,
+                    ),
                     VIRTIO_VIDEO_CONTROL_PROFILE => CtrlVal::Profile(
                         r.read_obj::<virtio_video_control_val_profile>()?
                             .profile
@@ -302,7 +315,12 @@
                             .level
                             .try_into()?,
                     ),
-                    VIRTIO_VIDEO_CONTROL_FORCE_KEYFRAME => CtrlVal::ForceKeyframe(),
+                    VIRTIO_VIDEO_CONTROL_FORCE_KEYFRAME => CtrlVal::ForceKeyframe,
+                    VIRTIO_VIDEO_CONTROL_PREPEND_SPSPPS_TO_IDR => CtrlVal::PrependSpsPpsToIdr(
+                        r.read_obj::<virtio_video_control_val_prepend_spspps_to_idr>()?
+                            .prepend_spspps_to_idr
+                            != 0,
+                    ),
                     t => {
                         return Err(ReadCmdError::UnsupportedCtrlType(t));
                     }
diff --git a/devices/src/virtio/video/control.rs b/devices/src/virtio/video/control.rs
index e997a11..4fcaf8d 100644
--- a/devices/src/virtio/video/control.rs
+++ b/devices/src/virtio/video/control.rs
@@ -9,7 +9,7 @@
 
 use data_model::Le32;
 
-use crate::virtio::video::format::{Format, Level, Profile};
+use crate::virtio::video::format::{BitrateMode, Format, Level, Profile};
 use crate::virtio::video::protocol::*;
 use crate::virtio::video::response::Response;
 use crate::virtio::Writer;
@@ -54,6 +54,9 @@
     Profile,
     Level,
     ForceKeyframe,
+    BitrateMode,
+    BitratePeak,
+    PrependSpsPpsToIdr,
 }
 
 #[derive(Debug, Clone)]
@@ -61,7 +64,10 @@
     Bitrate(u32),
     Profile(Profile),
     Level(Level),
-    ForceKeyframe(),
+    ForceKeyframe,
+    BitrateMode(BitrateMode),
+    BitratePeak(u32),
+    PrependSpsPpsToIdr(bool),
 }
 
 impl Response for CtrlVal {
@@ -71,6 +77,14 @@
                 bitrate: Le32::from(*r),
                 ..Default::default()
             }),
+            CtrlVal::BitratePeak(r) => w.write_obj(virtio_video_control_val_bitrate_peak {
+                bitrate_peak: Le32::from(*r),
+                ..Default::default()
+            }),
+            CtrlVal::BitrateMode(m) => w.write_obj(virtio_video_control_val_bitrate_mode {
+                bitrate_mode: Le32::from(*m as u32),
+                ..Default::default()
+            }),
             CtrlVal::Profile(p) => w.write_obj(virtio_video_control_val_profile {
                 profile: Le32::from(*p as u32),
                 ..Default::default()
@@ -79,10 +93,16 @@
                 level: Le32::from(*l as u32),
                 ..Default::default()
             }),
-            CtrlVal::ForceKeyframe() => Err(io::Error::new(
+            CtrlVal::ForceKeyframe => Err(io::Error::new(
                 io::ErrorKind::InvalidInput,
                 "Button controls should not be queried.",
             )),
+            CtrlVal::PrependSpsPpsToIdr(p) => {
+                w.write_obj(virtio_video_control_val_prepend_spspps_to_idr {
+                    prepend_spspps_to_idr: Le32::from(*p as u32),
+                    ..Default::default()
+                })
+            }
         }
     }
 }
diff --git a/devices/src/virtio/video/decoder/mod.rs b/devices/src/virtio/video/decoder/mod.rs
index fa744fb..cefb683 100644
--- a/devices/src/virtio/video/decoder/mod.rs
+++ b/devices/src/virtio/video/decoder/mod.rs
@@ -159,10 +159,13 @@
     }
 }
 
-struct PictureReadyEvent {
-    picture_buffer_id: i32,
-    bitstream_id: i32,
-    visible_rect: Rect,
+enum PendingResponse {
+    PictureReady {
+        picture_buffer_id: i32,
+        bitstream_id: i32,
+        visible_rect: Rect,
+    },
+    FlushCompleted,
 }
 
 // Context is associated with one `DecoderSession`, which corresponds to one stream from the
@@ -179,7 +182,7 @@
     // Set the flag when we ask the decoder reset, and unset when the reset is done.
     is_resetting: bool,
 
-    pending_ready_pictures: VecDeque<PictureReadyEvent>,
+    pending_responses: VecDeque<PendingResponse>,
 
     session: Option<S>,
 }
@@ -199,57 +202,85 @@
             in_res: Default::default(),
             out_res: Default::default(),
             is_resetting: false,
-            pending_ready_pictures: Default::default(),
+            pending_responses: Default::default(),
             session: None,
         }
     }
 
-    fn output_pending_pictures(&mut self) -> Vec<VideoEvtResponseType> {
-        let mut responses = vec![];
-        while let Some(async_response) = self.output_pending_picture() {
-            responses.push(VideoEvtResponseType::AsyncCmd(async_response));
+    fn output_pending_responses(&mut self) -> Vec<VideoEvtResponseType> {
+        let mut event_responses = vec![];
+        while let Some(mut responses) = self.output_pending_response() {
+            event_responses.append(&mut responses);
         }
-        responses
+        event_responses
     }
 
-    fn output_pending_picture(&mut self) -> Option<AsyncCmdResponse> {
-        let response = {
-            let PictureReadyEvent {
+    fn output_pending_response(&mut self) -> Option<Vec<VideoEvtResponseType>> {
+        let responses = match self.pending_responses.front()? {
+            PendingResponse::PictureReady {
                 picture_buffer_id,
                 bitstream_id,
                 visible_rect,
-            } = self.pending_ready_pictures.front()?;
+            } => {
+                let plane_size = ((visible_rect.right - visible_rect.left)
+                    * (visible_rect.bottom - visible_rect.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 plane_size = ((visible_rect.right - visible_rect.left)
-                * (visible_rect.bottom - visible_rect.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 = self
+                    .out_res
+                    .dequeue_frame_buffer(*picture_buffer_id, self.stream_id)?;
+
+                vec![VideoEvtResponseType::AsyncCmd(
+                    AsyncCmdResponse::from_response(
+                        AsyncCmdTag::Queue {
+                            stream_id: self.stream_id,
+                            queue_type: QueueType::Output,
+                            resource_id,
+                        },
+                        CmdResponse::ResourceQueue {
+                            // Conversion from sec to nsec.
+                            timestamp: (*bitstream_id 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,
+                        },
+                    ),
+                )]
             }
-
-            let resource_id = self
-                .out_res
-                .dequeue_frame_buffer(*picture_buffer_id, self.stream_id)?;
-
-            AsyncCmdResponse::from_response(
-                AsyncCmdTag::Queue {
+            PendingResponse::FlushCompleted => {
+                let eos_resource_id = self.out_res.dequeue_eos_resource_id()?;
+                let eos_tag = AsyncCmdTag::Queue {
                     stream_id: self.stream_id,
                     queue_type: QueueType::Output,
-                    resource_id,
-                },
-                CmdResponse::ResourceQueue {
-                    // Conversion from sec to nsec.
-                    timestamp: (*bitstream_id 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.
+                    resource_id: eos_resource_id,
+                };
+                let eos_response = CmdResponse::ResourceQueue {
+                    timestamp: 0,
+                    flags: protocol::VIRTIO_VIDEO_BUFFER_FLAG_EOS,
                     size: 0,
-                },
-            )
+                };
+                vec![
+                    VideoEvtResponseType::AsyncCmd(AsyncCmdResponse::from_response(
+                        eos_tag,
+                        eos_response,
+                    )),
+                    VideoEvtResponseType::AsyncCmd(AsyncCmdResponse::from_response(
+                        AsyncCmdTag::Drain {
+                            stream_id: self.stream_id,
+                        },
+                        CmdResponse::NoData,
+                    )),
+                ]
+            }
         };
-        self.pending_ready_pictures.pop_front().unwrap();
+        self.pending_responses.pop_front().unwrap();
 
-        Some(response)
+        Some(responses)
     }
 
     fn get_resource_info(
@@ -805,7 +836,7 @@
             QueueType::Input => {
                 session.reset()?;
                 ctx.is_resetting = true;
-                ctx.pending_ready_pictures.clear();
+                ctx.pending_responses.clear();
                 Ok(VideoCmdResponseType::Async(AsyncCmdTag::Clear {
                     stream_id,
                     queue_type: QueueType::Input,
@@ -876,7 +907,7 @@
                 let resp = self.queue_output_resource(stream_id, resource_id);
                 if resp.is_ok() {
                     if let Ok(ctx) = self.contexts.get_mut(&stream_id) {
-                        event_ret = Some((stream_id, ctx.output_pending_pictures()));
+                        event_ret = Some((stream_id, ctx.output_pending_responses()));
                     }
                 }
                 resp
@@ -971,12 +1002,13 @@
                 if ctx.is_resetting {
                     vec![]
                 } else {
-                    ctx.pending_ready_pictures.push_back(PictureReadyEvent {
-                        picture_buffer_id,
-                        bitstream_id,
-                        visible_rect,
-                    });
-                    ctx.output_pending_pictures()
+                    ctx.pending_responses
+                        .push_back(PendingResponse::PictureReady {
+                            picture_buffer_id,
+                            bitstream_id,
+                            visible_rect,
+                        });
+                    ctx.output_pending_responses()
                 }
             }
             DecoderEvent::NotifyEndOfBitstreamBuffer(bitstream_id) => {
@@ -998,40 +1030,9 @@
             DecoderEvent::FlushCompleted(flush_result) => {
                 match flush_result {
                     Ok(()) => {
-                        let eos_resource_id = match ctx.out_res.dequeue_eos_resource_id() {
-                            Some(r) => r,
-                            None => {
-                                // TODO(b/168750131): Instead of trigger error, we should wait for
-                                // the next output buffer enqueued, then dequeue the buffer with
-                                // EOS flag.
-                                error!(
-                                    "No EOS resource available on successful flush response (stream id {})",
-                                    stream_id);
-                                return Some(vec![Event(VideoEvt {
-                                    typ: EvtType::Error,
-                                    stream_id,
-                                })]);
-                            }
-                        };
-
-                        let eos_tag = AsyncCmdTag::Queue {
-                            stream_id,
-                            queue_type: QueueType::Output,
-                            resource_id: eos_resource_id,
-                        };
-
-                        let eos_response = CmdResponse::ResourceQueue {
-                            timestamp: 0,
-                            flags: protocol::VIRTIO_VIDEO_BUFFER_FLAG_EOS,
-                            size: 0,
-                        };
-                        vec![
-                            AsyncCmd(AsyncCmdResponse::from_response(eos_tag, eos_response)),
-                            AsyncCmd(AsyncCmdResponse::from_response(
-                                AsyncCmdTag::Drain { stream_id },
-                                CmdResponse::NoData,
-                            )),
-                        ]
+                        ctx.pending_responses
+                            .push_back(PendingResponse::FlushCompleted);
+                        ctx.output_pending_responses()
                     }
                     Err(error) => {
                         // TODO(b/151810591): If `resp` is `libvda::decode::Response::Canceled`,
diff --git a/devices/src/virtio/video/encoder/backend/mod.rs b/devices/src/virtio/video/encoder/backend/mod.rs
index d6f25f4..868d460 100644
--- a/devices/src/virtio/video/encoder/backend/mod.rs
+++ b/devices/src/virtio/video/encoder/backend/mod.rs
@@ -9,7 +9,7 @@
 use base::AsRawDescriptor;
 
 use crate::virtio::video::error::VideoResult;
-use crate::virtio::video::format::FramePlane;
+use crate::virtio::video::format::{Bitrate, FramePlane};
 
 use super::encoder::{
     EncoderCapabilities, EncoderEvent, InputBufferId, OutputBufferId, SessionConfig,
@@ -48,7 +48,11 @@
     fn flush(&mut self) -> VideoResult<()>;
 
     /// Requests the encoder to use new encoding parameters provided by `bitrate` and `framerate`.
-    fn request_encoding_params_change(&mut self, bitrate: u32, framerate: u32) -> VideoResult<()>;
+    fn request_encoding_params_change(
+        &mut self,
+        bitrate: Bitrate,
+        framerate: u32,
+    ) -> VideoResult<()>;
 
     /// Returns the event pipe on which the availability of events will be signaled. Note that the
     /// returned value is borrowed and only valid as long as the session is alive.
diff --git a/devices/src/virtio/video/encoder/backend/vda.rs b/devices/src/virtio/video/encoder/backend/vda.rs
index 0ee0ea6..1ec02e6 100644
--- a/devices/src/virtio/video/encoder/backend/vda.rs
+++ b/devices/src/virtio/video/encoder/backend/vda.rs
@@ -12,7 +12,7 @@
 
 use super::*;
 use crate::virtio::video::format::{
-    Format, FormatDesc, FormatRange, FrameFormat, FramePlane, Level, Profile,
+    Bitrate, Format, FormatDesc, FormatRange, FrameFormat, FramePlane, Level, Profile,
 };
 use crate::virtio::video::{
     encoder::{encoder::*, EncoderDevice},
@@ -20,6 +20,23 @@
     Tube,
 };
 
+impl From<Bitrate> for libvda::encode::Bitrate {
+    fn from(bitrate: Bitrate) -> Self {
+        libvda::encode::Bitrate {
+            mode: match bitrate {
+                Bitrate::VBR { .. } => libvda::encode::BitrateMode::VBR,
+                Bitrate::CBR { .. } => libvda::encode::BitrateMode::CBR,
+            },
+            target: bitrate.target(),
+            peak: match &bitrate {
+                // No need to specify peak if mode is CBR.
+                Bitrate::CBR { .. } => 0,
+                Bitrate::VBR { peak, .. } => *peak,
+            },
+        }
+    }
+}
+
 pub struct LibvdaEncoder {
     instance: VeaInstance,
     capabilities: EncoderCapabilities,
@@ -213,7 +230,7 @@
             input_visible_width: config.src_params.frame_width,
             input_visible_height: config.src_params.frame_height,
             output_profile,
-            initial_bitrate: config.dst_bitrate,
+            bitrate: config.dst_bitrate.into(),
             initial_framerate: if config.frame_rate == 0 {
                 None
             } else {
@@ -317,9 +334,13 @@
         self.session.flush().map_err(VideoError::from)
     }
 
-    fn request_encoding_params_change(&mut self, bitrate: u32, framerate: u32) -> VideoResult<()> {
+    fn request_encoding_params_change(
+        &mut self,
+        bitrate: Bitrate,
+        framerate: u32,
+    ) -> VideoResult<()> {
         self.session
-            .request_encoding_params_change(bitrate, framerate)
+            .request_encoding_params_change(bitrate.into(), framerate)
             .map_err(VideoError::from)
     }
 
diff --git a/devices/src/virtio/video/encoder/encoder.rs b/devices/src/virtio/video/encoder/encoder.rs
index d9836f4..fa4544e 100644
--- a/devices/src/virtio/video/encoder/encoder.rs
+++ b/devices/src/virtio/video/encoder/encoder.rs
@@ -9,7 +9,7 @@
 use crate::virtio::video::params::Params;
 use crate::virtio::video::{
     error::{VideoError, VideoResult},
-    format::{find_closest_resolution, Format, FormatDesc, Level, PlaneFormat, Profile},
+    format::{find_closest_resolution, Bitrate, Format, FormatDesc, Level, PlaneFormat, Profile},
 };
 
 pub type InputBufferId = u32;
@@ -45,7 +45,7 @@
     pub src_params: Params,
     pub dst_params: Params,
     pub dst_profile: Profile,
-    pub dst_bitrate: u32,
+    pub dst_bitrate: Bitrate,
     pub dst_h264_level: Option<Level>,
     pub frame_rate: u32,
 }
diff --git a/devices/src/virtio/video/encoder/mod.rs b/devices/src/virtio/video/encoder/mod.rs
index 7b75c69..211b767 100644
--- a/devices/src/virtio/video/encoder/mod.rs
+++ b/devices/src/virtio/video/encoder/mod.rs
@@ -24,7 +24,9 @@
 };
 use crate::virtio::video::error::*;
 use crate::virtio::video::event::{EvtType, VideoEvt};
-use crate::virtio::video::format::{Format, FramePlane, Level, PlaneFormat, Profile};
+use crate::virtio::video::format::{
+    Bitrate, BitrateMode, Format, FramePlane, Level, PlaneFormat, Profile,
+};
 use crate::virtio::video::params::Params;
 use crate::virtio::video::protocol;
 use crate::virtio::video::response::CmdResponse;
@@ -70,7 +72,7 @@
     id: u32,
     src_params: Params,
     dst_params: Params,
-    dst_bitrate: u32,
+    dst_bitrate: Bitrate,
     dst_profile: Profile,
     dst_h264_level: Option<Level>,
     frame_rate: u32,
@@ -99,7 +101,12 @@
         const MAX_BUFFERS: u32 = 342;
         const DEFAULT_WIDTH: u32 = 640;
         const DEFAULT_HEIGHT: u32 = 480;
-        const DEFAULT_BITRATE: u32 = 6000;
+        const DEFAULT_BITRATE_TARGET: u32 = 6000;
+        const DEFAULT_BITRATE_PEAK: u32 = DEFAULT_BITRATE_TARGET * 2;
+        const DEFAULT_BITRATE: Bitrate = Bitrate::VBR {
+            target: DEFAULT_BITRATE_TARGET,
+            peak: DEFAULT_BITRATE_PEAK,
+        };
         const DEFAULT_BUFFER_SIZE: u32 = 2097152; // 2MB; chosen empirically for 1080p video
         const DEFAULT_FPS: u32 = 30;
 
@@ -917,93 +924,132 @@
             .get_mut(&stream_id)
             .ok_or(VideoError::InvalidStreamId(stream_id))?;
 
-        if stream.src_resources.len() > 0 || stream.dst_resources.len() > 0 {
-            // Buffers have already been queued and encoding has already started.
-            return Err(VideoError::InvalidOperation);
+        let mut create_session = stream.encoder_session.is_none();
+        let resources_queued = stream.src_resources.len() > 0 || stream.dst_resources.len() > 0;
+
+        // Dynamic framerate changes are allowed. The framerate can be set on either the input or
+        // output queue. Changing the framerate can influence the selected H.264 level, as the
+        // level might be adjusted to conform to the minimum requirements for the selected bitrate
+        // and framerate. As dynamic level changes are not supported we will just recreate the
+        // encoder session as long as no resources have been queued yet. If an encoder session is
+        // active we will request a dynamic framerate change instead, and it's up to the encoder
+        // backend to return an error on invalid requests.
+        if stream.frame_rate != frame_rate {
+            stream.frame_rate = frame_rate;
+            if let Some(ref mut encoder_session) = stream.encoder_session {
+                if !resources_queued {
+                    create_session = true;
+                } else if let Err(e) = encoder_session
+                    .request_encoding_params_change(stream.dst_bitrate, stream.frame_rate)
+                {
+                    error!("failed to dynamically request framerate change: {}", e);
+                    return Err(VideoError::InvalidOperation);
+                }
+            }
         }
 
         match queue_type {
             QueueType::Input => {
-                // There should be at least a single plane.
-                if plane_formats.is_empty() {
-                    return Err(VideoError::InvalidArgument);
-                }
+                if stream.src_params.frame_width != frame_width
+                    || stream.src_params.frame_height != frame_height
+                    || stream.src_params.format != format
+                    || stream.src_params.plane_formats != plane_formats
+                {
+                    if resources_queued {
+                        // Buffers have already been queued and encoding has already started.
+                        return Err(VideoError::InvalidOperation);
+                    }
 
-                let desired_format = format.or(stream.src_params.format).unwrap_or(Format::NV12);
-                self.cros_capabilities.populate_src_params(
-                    &mut stream.src_params,
-                    desired_format,
-                    frame_width,
-                    frame_height,
-                    plane_formats[0].stride,
-                )?;
+                    // There should be at least a single plane.
+                    if plane_formats.is_empty() {
+                        return Err(VideoError::InvalidArgument);
+                    }
 
-                // Following the V4L2 standard the framerate requested on the
-                // input queue should also be applied to the output queue.
-                if frame_rate > 0 {
-                    stream.frame_rate = frame_rate;
+                    let desired_format =
+                        format.or(stream.src_params.format).unwrap_or(Format::NV12);
+                    self.cros_capabilities.populate_src_params(
+                        &mut stream.src_params,
+                        desired_format,
+                        frame_width,
+                        frame_height,
+                        plane_formats[0].stride,
+                    )?;
+
+                    create_session = true
                 }
             }
             QueueType::Output => {
-                let desired_format = format.or(stream.dst_params.format).unwrap_or(Format::H264);
+                if stream.dst_params.format != format
+                    || stream.dst_params.plane_formats != plane_formats
+                {
+                    if resources_queued {
+                        // Buffers have already been queued and encoding has already started.
+                        return Err(VideoError::InvalidOperation);
+                    }
 
-                // There should be exactly one output buffer.
-                if plane_formats.len() != 1 {
-                    return Err(VideoError::InvalidArgument);
-                }
+                    let desired_format =
+                        format.or(stream.dst_params.format).unwrap_or(Format::H264);
 
-                self.cros_capabilities.populate_dst_params(
-                    &mut stream.dst_params,
-                    desired_format,
-                    plane_formats[0].plane_size,
-                )?;
+                    // There should be exactly one output buffer.
+                    if plane_formats.len() != 1 {
+                        return Err(VideoError::InvalidArgument);
+                    }
 
-                if frame_rate > 0 {
-                    stream.frame_rate = frame_rate;
-                }
+                    self.cros_capabilities.populate_dst_params(
+                        &mut stream.dst_params,
+                        desired_format,
+                        plane_formats[0].plane_size,
+                    )?;
 
-                // Format is always populated for encoder.
-                let new_format = stream
-                    .dst_params
-                    .format
-                    .ok_or(VideoError::InvalidArgument)?;
+                    // Format is always populated for encoder.
+                    let new_format = stream
+                        .dst_params
+                        .format
+                        .ok_or(VideoError::InvalidArgument)?;
 
-                // If the selected profile no longer corresponds to the selected coded format,
-                // reset it.
-                stream.dst_profile = self
-                    .cros_capabilities
-                    .get_default_profile(&new_format)
-                    .ok_or(VideoError::InvalidArgument)?;
+                    // If the selected profile no longer corresponds to the selected coded format,
+                    // reset it.
+                    stream.dst_profile = self
+                        .cros_capabilities
+                        .get_default_profile(&new_format)
+                        .ok_or(VideoError::InvalidArgument)?;
 
-                if new_format == Format::H264 {
-                    stream.dst_h264_level = Some(Level::H264_1_0);
-                } else {
-                    stream.dst_h264_level = None;
+                    if new_format == Format::H264 {
+                        stream.dst_h264_level = Some(Level::H264_1_0);
+                    } else {
+                        stream.dst_h264_level = None;
+                    }
+
+                    create_session = true;
                 }
             }
         }
 
-        // An encoder session has to be created immediately upon a SetParams
-        // (S_FMT) call, because we need to receive the RequireInputBuffers
-        // callback which has output buffer size info, in order to populate
-        // dst_params to have the correct size on subsequent GetParams (G_FMT) calls.
-        if stream.encoder_session.is_some() {
-            stream.clear_encode_session(wait_ctx)?;
-            if !stream.received_input_buffers_event {
-                // This could happen if two SetParams calls are occuring at the same time.
-                // For example, the user calls SetParams for the input queue on one thread,
-                // and a new encode session is created. Then on another thread, SetParams
-                // is called for the output queue before the first SetParams call has returned.
-                // At this point, there is a new EncodeSession being created that has not
-                // yet received a RequireInputBuffers event.
-                // Even if we clear the encoder session and recreate it, this case
-                // is handled because stream.pending_commands will still contain
-                // the waiting GetParams responses, which will then receive fresh data once
-                // the new session's RequireInputBuffers event happens.
-                warn!("New encoder session being created while waiting for RequireInputBuffers.")
+        if create_session {
+            // An encoder session has to be created immediately upon a SetParams
+            // (S_FMT) call, because we need to receive the RequireInputBuffers
+            // callback which has output buffer size info, in order to populate
+            // dst_params to have the correct size on subsequent GetParams (G_FMT) calls.
+            if stream.encoder_session.is_some() {
+                stream.clear_encode_session(wait_ctx)?;
+                if !stream.received_input_buffers_event {
+                    // This could happen if two SetParams calls are occuring at the same time.
+                    // For example, the user calls SetParams for the input queue on one thread,
+                    // and a new encode session is created. Then on another thread, SetParams
+                    // is called for the output queue before the first SetParams call has returned.
+                    // At this point, there is a new EncodeSession being created that has not
+                    // yet received a RequireInputBuffers event.
+                    // Even if we clear the encoder session and recreate it, this case
+                    // is handled because stream.pending_commands will still contain
+                    // the waiting GetParams responses, which will then receive fresh data once
+                    // the new session's RequireInputBuffers event happens.
+                    warn!(
+                        "New encoder session being created while waiting for RequireInputBuffers."
+                    )
+                }
             }
+            stream.set_encode_session(&mut self.encoder, wait_ctx)?;
         }
-        stream.set_encode_session(&mut self.encoder, wait_ctx)?;
         Ok(VideoCmdResponseType::Sync(CmdResponse::NoData))
     }
 
@@ -1057,7 +1103,13 @@
             .get(&stream_id)
             .ok_or(VideoError::InvalidStreamId(stream_id))?;
         let ctrl_val = match ctrl_type {
-            CtrlType::Bitrate => CtrlVal::Bitrate(stream.dst_bitrate),
+            CtrlType::BitrateMode => CtrlVal::BitrateMode(stream.dst_bitrate.mode()),
+            CtrlType::Bitrate => CtrlVal::Bitrate(stream.dst_bitrate.target()),
+            CtrlType::BitratePeak => CtrlVal::BitratePeak(match stream.dst_bitrate {
+                Bitrate::VBR { peak, .. } => peak,
+                // For CBR there is no peak, so return the target (which is technically correct).
+                Bitrate::CBR { target } => target,
+            }),
             CtrlType::Profile => CtrlVal::Profile(stream.dst_profile),
             CtrlType::Level => {
                 let format = stream
@@ -1076,6 +1128,9 @@
             }
             // Button controls should not be queried.
             CtrlType::ForceKeyframe => return Err(VideoError::UnsupportedControl(ctrl_type)),
+            // Prepending SPS and PPS to IDR is always enabled in the libvda backend.
+            // TODO (b/161495502): account for other backends
+            CtrlType::PrependSpsPpsToIdr => CtrlVal::PrependSpsPpsToIdr(true),
         };
         Ok(VideoCmdResponseType::Sync(CmdResponse::GetControl(
             ctrl_val,
@@ -1092,19 +1147,61 @@
             .get_mut(&stream_id)
             .ok_or(VideoError::InvalidStreamId(stream_id))?;
         match ctrl_val {
+            CtrlVal::BitrateMode(bitrate_mode) => {
+                if stream.encoder_session.is_some() {
+                    error!(
+                        "set control called for bitrate mode but encoder session already exists."
+                    );
+                    return Err(VideoError::InvalidOperation);
+                }
+
+                // We only need to care if there is a change.
+                if stream.dst_bitrate.mode() != bitrate_mode {
+                    stream.dst_bitrate = match bitrate_mode {
+                        BitrateMode::CBR => Bitrate::CBR {
+                            target: stream.dst_bitrate.target(),
+                        },
+                        BitrateMode::VBR => Bitrate::VBR {
+                            target: stream.dst_bitrate.target(),
+                            peak: stream.dst_bitrate.target(),
+                        },
+                    };
+                }
+            }
             CtrlVal::Bitrate(bitrate) => {
+                let mut new_bitrate = stream.dst_bitrate;
+                match &mut new_bitrate {
+                    Bitrate::CBR { target } | Bitrate::VBR { target, .. } => *target = bitrate,
+                }
                 if let Some(ref mut encoder_session) = stream.encoder_session {
-                    if let Err(e) =
-                        encoder_session.request_encoding_params_change(bitrate, stream.frame_rate)
+                    if let Err(e) = encoder_session
+                        .request_encoding_params_change(new_bitrate, stream.frame_rate)
                     {
-                        error!(
-                            "failed to dynamically request encoding params change: {}",
-                            e
-                        );
+                        error!("failed to dynamically request target bitrate change: {}", e);
                         return Err(VideoError::InvalidOperation);
                     }
                 }
-                stream.dst_bitrate = bitrate;
+                stream.dst_bitrate = new_bitrate;
+            }
+            CtrlVal::BitratePeak(bitrate) => {
+                let mut new_bitrate = stream.dst_bitrate;
+                match &mut new_bitrate {
+                    Bitrate::CBR { .. } => {
+                        // Trying to set the peak bitrate while in constant mode. This is not an
+                        // error, just ignored.
+                        return Ok(VideoCmdResponseType::Sync(CmdResponse::SetControl));
+                    }
+                    Bitrate::VBR { peak, .. } => *peak = bitrate,
+                }
+                if let Some(ref mut encoder_session) = stream.encoder_session {
+                    if let Err(e) = encoder_session
+                        .request_encoding_params_change(new_bitrate, stream.frame_rate)
+                    {
+                        error!("failed to dynamically request peak bitrate change: {}", e);
+                        return Err(VideoError::InvalidOperation);
+                    }
+                }
+                stream.dst_bitrate = new_bitrate;
             }
             CtrlVal::Profile(profile) => {
                 if stream.encoder_session.is_some() {
@@ -1148,9 +1245,17 @@
                 }
                 stream.dst_h264_level = Some(level);
             }
-            CtrlVal::ForceKeyframe() => {
+            CtrlVal::ForceKeyframe => {
                 stream.force_keyframe = true;
             }
+            CtrlVal::PrependSpsPpsToIdr(prepend_sps_pps_to_idr) => {
+                // Prepending SPS and PPS to IDR is always enabled in the libvda backend,
+                // disabling it will always fail.
+                // TODO (b/161495502): account for other backends
+                if !prepend_sps_pps_to_idr {
+                    return Err(VideoError::InvalidOperation);
+                }
+            }
         }
         Ok(VideoCmdResponseType::Sync(CmdResponse::SetControl))
     }
diff --git a/devices/src/virtio/video/format.rs b/devices/src/virtio/video/format.rs
index 321902b..1e99cb4 100644
--- a/devices/src/virtio/video/format.rs
+++ b/devices/src/virtio/video/format.rs
@@ -117,6 +117,38 @@
     }
 }
 
+#[derive(PartialEq, Eq, PartialOrd, Ord, N, Clone, Copy, Debug)]
+#[repr(u32)]
+pub enum BitrateMode {
+    VBR = VIRTIO_VIDEO_BITRATE_MODE_VBR,
+    CBR = VIRTIO_VIDEO_BITRATE_MODE_CBR,
+}
+impl_try_from_le32_for_enumn!(BitrateMode, "bitrate_mode");
+
+#[derive(Debug, Copy, Clone)]
+pub enum Bitrate {
+    /// Constant bitrate.
+    CBR { target: u32 },
+    /// Variable bitrate.
+    VBR { target: u32, peak: u32 },
+}
+
+impl Bitrate {
+    pub fn mode(&self) -> BitrateMode {
+        match self {
+            Bitrate::CBR { .. } => BitrateMode::CBR,
+            Bitrate::VBR { .. } => BitrateMode::VBR,
+        }
+    }
+
+    pub fn target(&self) -> u32 {
+        match self {
+            Bitrate::CBR { target } => *target,
+            Bitrate::VBR { target, .. } => *target,
+        }
+    }
+}
+
 #[derive(Debug, Default, Copy, Clone)]
 pub struct Crop {
     pub left: u32,
@@ -126,7 +158,7 @@
 }
 impl_from_for_interconvertible_structs!(virtio_video_crop, Crop, left, top, width, height);
 
-#[derive(Debug, Default, Clone, Copy)]
+#[derive(PartialEq, Eq, Debug, Default, Clone, Copy)]
 pub struct PlaneFormat {
     pub plane_size: u32,
     pub stride: u32,
diff --git a/devices/src/virtio/video/protocol.rs b/devices/src/virtio/video/protocol.rs
index ec03f8c..843d112 100644
--- a/devices/src/virtio/video/protocol.rs
+++ b/devices/src/virtio/video/protocol.rs
@@ -93,6 +93,10 @@
 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;
+pub const VIRTIO_VIDEO_BITRATE_MODE_VBR: virtio_video_bitrate_mode = 0;
+pub const VIRTIO_VIDEO_BITRATE_MODE_CBR: virtio_video_bitrate_mode = 1;
+pub type virtio_video_bitrate_mode = u32;
+
 #[repr(C)]
 #[derive(Debug, Default, Copy, Clone)]
 pub struct virtio_video_config {
@@ -363,6 +367,9 @@
 pub const VIRTIO_VIDEO_CONTROL_PROFILE: virtio_video_control_type = 2;
 pub const VIRTIO_VIDEO_CONTROL_LEVEL: virtio_video_control_type = 3;
 pub const VIRTIO_VIDEO_CONTROL_FORCE_KEYFRAME: virtio_video_control_type = 4;
+pub const VIRTIO_VIDEO_CONTROL_BITRATE_MODE: virtio_video_control_type = 5;
+pub const VIRTIO_VIDEO_CONTROL_BITRATE_PEAK: virtio_video_control_type = 6;
+pub const VIRTIO_VIDEO_CONTROL_PREPEND_SPSPPS_TO_IDR: virtio_video_control_type = 7;
 pub type virtio_video_control_type = u32;
 #[repr(C)]
 #[derive(Debug, Default, Copy, Clone)]
@@ -437,6 +444,24 @@
 
 #[repr(C)]
 #[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_control_val_bitrate_peak {
+    pub bitrate_peak: Le32,
+    pub padding: [u8; 4usize],
+}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_control_val_bitrate_peak {}
+
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_control_val_bitrate_mode {
+    pub bitrate_mode: Le32,
+    pub padding: [u8; 4usize],
+}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_control_val_bitrate_mode {}
+
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
 pub struct virtio_video_control_val_profile {
     pub profile: Le32,
     pub padding: [u8; 4usize],
@@ -455,6 +480,15 @@
 
 #[repr(C)]
 #[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_control_val_prepend_spspps_to_idr {
+    pub prepend_spspps_to_idr: Le32,
+    pub padding: [u8; 4usize],
+}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_control_val_prepend_spspps_to_idr {}
+
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
 pub struct virtio_video_get_control_resp {
     pub hdr: virtio_video_cmd_hdr,
 }
diff --git a/devices/src/virtio/video/worker.rs b/devices/src/virtio/video/worker.rs
index 38f9355..98087ac 100644
--- a/devices/src/virtio/video/worker.rs
+++ b/devices/src/virtio/video/worker.rs
@@ -75,7 +75,7 @@
             self.cmd_queue
                 .add_used(&self.mem, desc_index, writer.bytes_written() as u32);
         }
-        self.interrupt.signal_used_queue(self.cmd_queue.vector);
+        self.cmd_queue.trigger_interrupt(&self.mem, &self.interrupt);
         Ok(())
     }
 
@@ -94,7 +94,8 @@
             .map_err(|error| Error::WriteEventFailure { event, error })?;
         self.event_queue
             .add_used(&self.mem, desc_index, writer.bytes_written() as u32);
-        self.interrupt.signal_used_queue(self.event_queue.vector);
+        self.event_queue
+            .trigger_interrupt(&self.mem, &self.interrupt);
         Ok(())
     }
 
diff --git a/devices/src/virtio/virtio_device.rs b/devices/src/virtio/virtio_device.rs
index c181b44..be4264b 100644
--- a/devices/src/virtio/virtio_device.rs
+++ b/devices/src/virtio/virtio_device.rs
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+use acpi_tables::sdt::SDT;
 use base::{Event, RawDescriptor};
 use vm_memory::GuestMemory;
 
@@ -87,4 +89,13 @@
     fn on_device_sandboxed(&mut self) {}
 
     fn control_notify(&self, _behavior: MsixStatus) {}
+
+    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+    fn generate_acpi(
+        &mut self,
+        _pci_address: &Option<PciAddress>,
+        sdts: Vec<SDT>,
+    ) -> Option<Vec<SDT>> {
+        Some(sdts)
+    }
 }
diff --git a/devices/src/virtio/virtio_pci_device.rs b/devices/src/virtio/virtio_pci_device.rs
index 509dffb..1eaa3c1 100644
--- a/devices/src/virtio/virtio_pci_device.rs
+++ b/devices/src/virtio/virtio_pci_device.rs
@@ -6,6 +6,8 @@
 use std::sync::Arc;
 use sync::Mutex;
 
+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+use acpi_tables::sdt::SDT;
 use base::{warn, AsRawDescriptor, Event, RawDescriptor, Result, Tube};
 use data_model::{DataInit, Le32};
 use hypervisor::Datamatch;
@@ -57,6 +59,10 @@
     fn id(&self) -> PciCapabilityID {
         PciCapabilityID::VendorSpecific
     }
+
+    fn writable_bits(&self) -> Vec<u32> {
+        vec![0u32; 4]
+    }
 }
 
 impl VirtioPciCap {
@@ -93,6 +99,10 @@
     fn id(&self) -> PciCapabilityID {
         PciCapabilityID::VendorSpecific
     }
+
+    fn writable_bits(&self) -> Vec<u32> {
+        vec![0u32; 5]
+    }
 }
 
 impl VirtioPciNotifyCap {
@@ -138,6 +148,10 @@
     fn id(&self) -> PciCapabilityID {
         PciCapabilityID::VendorSpecific
     }
+
+    fn writable_bits(&self) -> Vec<u32> {
+        vec![0u32; 6]
+    }
 }
 
 impl VirtioPciShmCap {
@@ -744,4 +758,9 @@
     fn on_device_sandboxed(&mut self) {
         self.device.on_device_sandboxed();
     }
+
+    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+    fn generate_acpi(&mut self, sdts: Vec<SDT>) -> Option<Vec<SDT>> {
+        self.device.generate_acpi(&self.pci_address, sdts)
+    }
 }
diff --git a/devices/src/virtio/wl.rs b/devices/src/virtio/wl.rs
index d7eee75..6482c2c 100644
--- a/devices/src/virtio/wl.rs
+++ b/devices/src/virtio/wl.rs
@@ -1511,7 +1511,7 @@
     }
 
     if needs_interrupt {
-        interrupt.signal_used_queue(in_queue.vector);
+        in_queue.trigger_interrupt(mem, interrupt);
     }
 
     if exhausted_queue {
@@ -1560,7 +1560,7 @@
     }
 
     if needs_interrupt {
-        interrupt.signal_used_queue(out_queue.vector);
+        out_queue.trigger_interrupt(mem, interrupt);
     }
 }
 
diff --git a/disk/Android.bp b/disk/Android.bp
index ff801af..d3e3b9d 100644
--- a/disk/Android.bp
+++ b/disk/Android.bp
@@ -1,5 +1,5 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
-// Manually added "composite-disk" feature and proto libs.
+// This file is generated by cargo2android.py --config cargo2android.json.
+// Do not modify this file as changes will be overridden on upgrade.
 
 package {
     // See: http://go/android-license-faq
@@ -11,10 +11,9 @@
 }
 
 rust_defaults {
-    name: "disk_defaults",
+    name: "disk_test_defaults",
     defaults: ["crosvm_defaults"],
     crate_name: "disk",
-    // has rustc warnings
     srcs: ["src/disk.rs"],
     test_suites: ["general-tests"],
     auto_gen_config: true,
@@ -37,7 +36,7 @@
 
 rust_test_host {
     name: "disk_host_test_src_disk",
-    defaults: ["disk_defaults"],
+    defaults: ["disk_test_defaults"],
     test_options: {
         unit_test: true,
     },
@@ -45,13 +44,12 @@
 
 rust_test {
     name: "disk_device_test_src_disk",
-    defaults: ["disk_defaults"],
+    defaults: ["disk_test_defaults"],
 }
 
 rust_library {
     name: "libdisk",
     defaults: ["crosvm_defaults"],
-    // has rustc warnings
     host_supported: true,
     crate_name: "disk",
     srcs: ["src/disk.rs"],
@@ -61,6 +59,7 @@
     ],
     rustlibs: [
         "libbase_rust",
+        "libcrc32fast",  // Added manually
         "libcros_async",
         "libdata_model",
         "libfutures",
@@ -69,6 +68,7 @@
         "libprotos",  // Added manually
         "libtempfile",
         "libthiserror",
+        "libuuid",  // Added manually
         "libvm_memory",
     ],
     proc_macros: [
@@ -76,54 +76,3 @@
         "libremain",
     ],
 }
-
-// dependent_library ["feature_list"]
-//   ../assertions/src/lib.rs
-//   ../base/src/lib.rs
-//   ../cros_async/src/lib.rs
-//   ../data_model/src/lib.rs
-//   ../io_uring/src/lib.rs
-//   ../sync/src/lib.rs
-//   ../sys_util/poll_token_derive/poll_token_derive.rs
-//   ../sys_util/src/lib.rs
-//   ../tempfile/src/lib.rs
-//   ../vm_memory/src/lib.rs
-//   async-task-4.0.3 "default,std"
-//   async-trait-0.1.50
-//   autocfg-1.0.1
-//   bitflags-1.2.1 "default"
-//   cfg-if-1.0.0
-//   futures-0.3.15 "alloc"
-//   futures-channel-0.3.15 "alloc,futures-sink,sink"
-//   futures-core-0.3.15 "alloc"
-//   futures-io-0.3.15
-//   futures-sink-0.3.15 "alloc"
-//   futures-task-0.3.15 "alloc"
-//   futures-util-0.3.15 "alloc,futures-sink,sink"
-//   getrandom-0.2.3 "std"
-//   intrusive-collections-0.9.1 "alloc,default"
-//   itoa-0.4.7
-//   libc-0.2.97 "default,std"
-//   memoffset-0.5.6 "default"
-//   paste-1.0.5
-//   pin-project-lite-0.2.7
-//   pin-utils-0.1.0
-//   ppv-lite86-0.2.10 "simd,std"
-//   proc-macro2-1.0.27 "default,proc-macro"
-//   quote-1.0.9 "default,proc-macro"
-//   rand-0.8.4 "alloc,default,getrandom,libc,rand_chacha,rand_hc,std,std_rng"
-//   rand_chacha-0.3.1 "std"
-//   rand_core-0.6.3 "alloc,getrandom,std"
-//   remain-0.2.2
-//   remove_dir_all-0.5.3
-//   ryu-1.0.5
-//   serde-1.0.126 "default,derive,serde_derive,std"
-//   serde_derive-1.0.126 "default"
-//   serde_json-1.0.64 "default,std"
-//   slab-0.4.3 "default,std"
-//   smallvec-1.6.1
-//   syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-//   tempfile-3.2.0
-//   thiserror-1.0.25
-//   thiserror-impl-1.0.25
-//   unicode-xid-0.2.2 "default"
diff --git a/disk/Cargo.toml b/disk/Cargo.toml
index 8c49b45..da386f2 100644
--- a/disk/Cargo.toml
+++ b/disk/Cargo.toml
@@ -8,19 +8,21 @@
 path = "src/disk.rs"
 
 [features]
-composite-disk = ["protos", "protobuf"]
+composite-disk = ["crc32fast", "protos", "protobuf", "uuid"]
 
 [dependencies]
 async-trait = "0.1.36"
 base = { path = "../base" }
+crc32fast = { version = "1.2.1", optional = true }
 libc = "*"
 protobuf = { version = "2.3", optional = true }
 remain = "*"
 tempfile = "*"
 thiserror = "*"
+uuid = { version = "0.8.2", features = ["v4"], optional = true }
 cros_async = { path = "../cros_async" }
 data_model = { path = "../data_model" }
-protos = { path = "../protos", optional = true }
+protos = { path = "../protos", features = ["composite-disk"], optional = true }
 vm_memory = { path = "../vm_memory" }
 
 [dependencies.futures]
diff --git a/disk/cargo2android.json b/disk/cargo2android.json
new file mode 100644
index 0000000..a0c7bb4
--- /dev/null
+++ b/disk/cargo2android.json
@@ -0,0 +1,8 @@
+{
+  "add_workspace": true,
+  "device": true,
+  "global_defaults": "crosvm_defaults",
+  "patch": "patches/Android.bp.patch",
+  "run": true,
+  "tests": true
+}
\ No newline at end of file
diff --git a/disk/patches/Android.bp.patch b/disk/patches/Android.bp.patch
new file mode 100644
index 0000000..c8f9ef7
--- /dev/null
+++ b/disk/patches/Android.bp.patch
@@ -0,0 +1,26 @@
+diff --git a/disk/Android.bp b/disk/Android.bp
+index 78271ca1..e2ef84af 100644
+--- a/disk/Android.bp
++++ b/disk/Android.bp
+@@ -54,14 +54,21 @@ rust_library {
+     crate_name: "disk",
+     srcs: ["src/disk.rs"],
+     edition: "2018",
++    features: [
++        "composite-disk",  // Added manually
++    ],
+     rustlibs: [
+         "libbase_rust",
++        "libcrc32fast",  // Added manually
+         "libcros_async",
+         "libdata_model",
+         "libfutures",
+         "liblibc",
++        "libprotobuf",  // Added manually
++        "libprotos",  // Added manually
+         "libtempfile",
+         "libthiserror",
++        "libuuid",  // Added manually
+         "libvm_memory",
+     ],
+     proc_macros: [
diff --git a/disk/src/composite.rs b/disk/src/composite.rs
index efa5e1d..32d6edf 100644
--- a/disk/src/composite.rs
+++ b/disk/src/composite.rs
@@ -3,31 +3,65 @@
 // found in the LICENSE file.
 
 use std::cmp::{max, min};
+use std::collections::HashSet;
+use std::convert::TryInto;
 use std::fmt::{self, Display};
 use std::fs::{File, OpenOptions};
-use std::io::{self, ErrorKind, Read, Seek, SeekFrom};
+use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write};
 use std::ops::Range;
+use std::path::{Path, PathBuf};
 
-use crate::{create_disk_file, DiskFile, DiskGetLen, ImageType};
 use base::{
-    AsRawDescriptors, FileAllocate, FileReadWriteAtVolatile, FileSetLen, FileSync, PunchHole,
-    RawDescriptor, WriteZeroesAt,
+    open_file, AsRawDescriptors, FileAllocate, FileReadWriteAtVolatile, FileSetLen, FileSync,
+    PunchHole, RawDescriptor, WriteZeroesAt,
 };
+use crc32fast::Hasher;
 use data_model::VolatileSlice;
-use protos::cdisk_spec;
+use protobuf::Message;
+use protos::cdisk_spec::{self, ComponentDisk, CompositeDisk, ReadWriteCapability};
 use remain::sorted;
+use uuid::Uuid;
+
+use crate::gpt::{
+    self, write_gpt_header, write_protective_mbr, GptPartitionEntry, GPT_BEGINNING_SIZE,
+    GPT_END_SIZE, GPT_HEADER_SIZE, GPT_NUM_PARTITIONS, GPT_PARTITION_ENTRY_SIZE, SECTOR_SIZE,
+};
+use crate::{create_disk_file, DiskFile, DiskGetLen, ImageType};
+
+/// The amount of padding needed between the last partition entry and the first partition, to align
+/// the partition appropriately. The two sectors are for the MBR and the GPT header.
+const PARTITION_ALIGNMENT_SIZE: usize = GPT_BEGINNING_SIZE as usize
+    - 2 * SECTOR_SIZE as usize
+    - GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize;
+const HEADER_PADDING_LENGTH: usize = SECTOR_SIZE as usize - GPT_HEADER_SIZE as usize;
+// Keep all partitions 4k aligned for performance.
+const PARTITION_SIZE_SHIFT: u8 = 12;
+// Keep the disk size a multiple of 64k for crosvm's virtio_blk driver.
+const DISK_SIZE_SHIFT: u8 = 16;
+
+// From https://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_type_GUIDs.
+const LINUX_FILESYSTEM_GUID: Uuid = Uuid::from_u128(0x0FC63DAF_8483_4772_8E79_3D69D8477DE4);
+const EFI_SYSTEM_PARTITION_GUID: Uuid = Uuid::from_u128(0xC12A7328_F81F_11D2_BA4B_00A0C93EC93B);
 
 #[sorted]
 #[derive(Debug)]
 pub enum Error {
     DiskError(Box<crate::Error>),
+    DuplicatePartitionLabel(String),
+    GptError(gpt::Error),
     InvalidMagicHeader,
+    InvalidPath(PathBuf),
     InvalidProto(protobuf::ProtobufError),
     InvalidSpecification(String),
+    NoImageFiles(PartitionInfo),
     OpenFile(io::Error, String),
     ReadSpecificationError(io::Error),
+    UnalignedReadWrite(PartitionInfo),
     UnknownVersion(u64),
     UnsupportedComponent(ImageType),
+    WriteHeader(io::Error),
+    WriteProto(protobuf::ProtobufError),
+    WriteZeroFiller(io::Error),
 }
 
 impl Display for Error {
@@ -38,17 +72,40 @@
         #[sorted]
         match self {
             DiskError(e) => write!(f, "failed to use underlying disk: \"{}\"", e),
+            DuplicatePartitionLabel(label) => {
+                write!(f, "duplicate GPT partition label \"{}\"", label)
+            }
+            GptError(e) => write!(f, "failed to write GPT header: \"{}\"", e),
             InvalidMagicHeader => write!(f, "invalid magic header for composite disk format"),
+            InvalidPath(path) => write!(f, "invalid partition path {:?}", path),
             InvalidProto(e) => write!(f, "failed to parse specification proto: \"{}\"", e),
             InvalidSpecification(s) => write!(f, "invalid specification: \"{}\"", s),
+            NoImageFiles(partition) => write!(f, "no image files for partition {:?}", partition),
             OpenFile(e, p) => write!(f, "failed to open component file \"{}\": \"{}\"", p, e),
             ReadSpecificationError(e) => write!(f, "failed to read specification: \"{}\"", e),
+            UnalignedReadWrite(partition) => write!(
+                f,
+                "Read-write partition {:?} size is not a multiple of {}.",
+                partition,
+                1 << PARTITION_SIZE_SHIFT
+            ),
             UnknownVersion(v) => write!(f, "unknown version {} in specification", v),
             UnsupportedComponent(c) => write!(f, "unsupported component disk type \"{:?}\"", c),
+            WriteHeader(e) => write!(f, "failed to write composite disk header: \"{}\"", e),
+            WriteProto(e) => write!(f, "failed to write specification proto: \"{}\"", e),
+            WriteZeroFiller(e) => write!(f, "failed to write zero filler: \"{}\"", e),
         }
     }
 }
 
+impl std::error::Error for Error {}
+
+impl From<gpt::Error> for Error {
+    fn from(e: gpt::Error) -> Self {
+        Self::GptError(e)
+    }
+}
+
 pub type Result<T> = std::result::Result<T, Error>;
 
 #[derive(Debug)]
@@ -86,11 +143,14 @@
     }
 }
 
+/// The version of the composite disk format supported by this implementation.
+const COMPOSITE_DISK_VERSION: u64 = 1;
+
 /// A magic string placed at the beginning of a composite disk file to identify it.
-pub static CDISK_MAGIC: &str = "composite_disk\x1d";
+pub const CDISK_MAGIC: &str = "composite_disk\x1d";
 /// The length of the CDISK_MAGIC string. Created explicitly as a static constant so that it is
 /// possible to create a character array of the same length.
-pub const CDISK_MAGIC_LEN: usize = 15;
+pub const CDISK_MAGIC_LEN: usize = CDISK_MAGIC.len();
 
 impl CompositeDiskFile {
     fn new(mut disks: Vec<ComponentDiskPart>) -> Result<CompositeDiskFile> {
@@ -127,22 +187,21 @@
             return Err(Error::InvalidMagicHeader);
         }
         let proto: cdisk_spec::CompositeDisk =
-            protobuf::parse_from_reader(&mut file).map_err(Error::InvalidProto)?;
-        if proto.get_version() != 1 {
+            Message::parse_from_reader(&mut file).map_err(Error::InvalidProto)?;
+        if proto.get_version() != COMPOSITE_DISK_VERSION {
             return Err(Error::UnknownVersion(proto.get_version()));
         }
-        let mut open_options = OpenOptions::new();
-        open_options.read(true);
         let mut disks: Vec<ComponentDiskPart> = proto
             .get_component_disks()
             .iter()
             .map(|disk| {
-                open_options.write(
-                    disk.get_read_write_capability() == cdisk_spec::ReadWriteCapability::READ_WRITE,
-                );
-                let file = open_options
-                    .open(disk.get_file_path())
-                    .map_err(|e| Error::OpenFile(e, disk.get_file_path().to_string()))?;
+                let file = open_file(
+                    Path::new(disk.get_file_path()),
+                    disk.get_read_write_capability() != cdisk_spec::ReadWriteCapability::READ_WRITE,
+                    // TODO(b/190435784): add support for O_DIRECT.
+                    false, /*O_DIRECT*/
+                )
+                .map_err(|e| Error::OpenFile(e.into(), disk.get_file_path().to_string()))?;
                 Ok(ComponentDiskPart {
                     file: create_disk_file(file).map_err(|e| Error::DiskError(Box::new(e)))?,
                     offset: disk.get_offset(),
@@ -339,9 +398,284 @@
     }
 }
 
+/// Information about a partition to create.
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct PartitionInfo {
+    pub label: String,
+    pub path: PathBuf,
+    pub partition_type: ImagePartitionType,
+    pub writable: bool,
+    pub size: u64,
+}
+
+/// Round `val` up to the next multiple of 2**`align_log`.
+fn align_to_power_of_2(val: u64, align_log: u8) -> u64 {
+    let align = 1 << align_log;
+    ((val + (align - 1)) / align) * align
+}
+
+impl PartitionInfo {
+    fn aligned_size(&self) -> u64 {
+        align_to_power_of_2(self.size, PARTITION_SIZE_SHIFT)
+    }
+}
+
+/// The type of partition.
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub enum ImagePartitionType {
+    LinuxFilesystem,
+    EfiSystemPartition,
+}
+
+impl ImagePartitionType {
+    fn guid(self) -> Uuid {
+        match self {
+            Self::LinuxFilesystem => LINUX_FILESYSTEM_GUID,
+            Self::EfiSystemPartition => EFI_SYSTEM_PARTITION_GUID,
+        }
+    }
+}
+
+/// Write protective MBR and primary GPT table.
+fn write_beginning(
+    file: &mut impl Write,
+    disk_guid: Uuid,
+    partitions: &[u8],
+    partition_entries_crc32: u32,
+    secondary_table_offset: u64,
+    disk_size: u64,
+) -> Result<()> {
+    // Write the protective MBR to the first sector.
+    write_protective_mbr(file, disk_size)?;
+
+    // Write the GPT header, and pad out to the end of the sector.
+    write_gpt_header(
+        file,
+        disk_guid,
+        partition_entries_crc32,
+        secondary_table_offset,
+        false,
+    )?;
+    file.write_all(&[0; HEADER_PADDING_LENGTH])
+        .map_err(Error::WriteHeader)?;
+
+    // Write partition entries, including unused ones.
+    file.write_all(partitions).map_err(Error::WriteHeader)?;
+
+    // Write zeroes to align the first partition appropriately.
+    file.write_all(&[0; PARTITION_ALIGNMENT_SIZE])
+        .map_err(Error::WriteHeader)?;
+
+    Ok(())
+}
+
+/// Write secondary GPT table.
+fn write_end(
+    file: &mut impl Write,
+    disk_guid: Uuid,
+    partitions: &[u8],
+    partition_entries_crc32: u32,
+    secondary_table_offset: u64,
+    disk_size: u64,
+) -> Result<()> {
+    // Write partition entries, including unused ones.
+    file.write_all(partitions).map_err(Error::WriteHeader)?;
+
+    // Write the GPT header, and pad out to the end of the sector.
+    write_gpt_header(
+        file,
+        disk_guid,
+        partition_entries_crc32,
+        secondary_table_offset,
+        true,
+    )?;
+    file.write_all(&[0; HEADER_PADDING_LENGTH])
+        .map_err(Error::WriteHeader)?;
+
+    // Pad out to the aligned disk size.
+    let used_disk_size = secondary_table_offset + GPT_END_SIZE;
+    let padding = disk_size - used_disk_size;
+    file.write_all(&vec![0; padding as usize])
+        .map_err(Error::WriteHeader)?;
+
+    Ok(())
+}
+
+/// Create the `GptPartitionEntry` for the given partition.
+fn create_gpt_entry(partition: &PartitionInfo, offset: u64) -> GptPartitionEntry {
+    let mut partition_name: Vec<u16> = partition.label.encode_utf16().collect();
+    partition_name.resize(36, 0);
+
+    GptPartitionEntry {
+        partition_type_guid: partition.partition_type.guid(),
+        unique_partition_guid: Uuid::new_v4(),
+        first_lba: offset / SECTOR_SIZE,
+        last_lba: (offset + partition.aligned_size()) / SECTOR_SIZE - 1,
+        attributes: 0,
+        partition_name: partition_name.try_into().unwrap(),
+    }
+}
+
+/// Create one or more `ComponentDisk` proto messages for the given partition.
+fn create_component_disks(
+    partition: &PartitionInfo,
+    offset: u64,
+    zero_filler_path: &str,
+) -> Result<Vec<ComponentDisk>> {
+    let aligned_size = partition.aligned_size();
+
+    let mut component_disks = vec![ComponentDisk {
+        offset,
+        file_path: partition
+            .path
+            .to_str()
+            .ok_or_else(|| Error::InvalidPath(partition.path.to_owned()))?
+            .to_string(),
+        read_write_capability: if partition.writable {
+            ReadWriteCapability::READ_WRITE
+        } else {
+            ReadWriteCapability::READ_ONLY
+        },
+        ..ComponentDisk::new()
+    }];
+
+    if partition.size != aligned_size {
+        if partition.writable {
+            return Err(Error::UnalignedReadWrite(partition.to_owned()));
+        } else {
+            // Fill in the gap by reusing the zero filler file, because we know it is always bigger
+            // than the alignment size. Its size is 1 << PARTITION_SIZE_SHIFT (4k).
+            component_disks.push(ComponentDisk {
+                offset: offset + partition.size,
+                file_path: zero_filler_path.to_owned(),
+                read_write_capability: ReadWriteCapability::READ_ONLY,
+                ..ComponentDisk::new()
+            });
+        }
+    }
+
+    Ok(component_disks)
+}
+
+/// Create a new composite disk image containing the given partitions, and write it out to the given
+/// files.
+pub fn create_composite_disk(
+    partitions: &[PartitionInfo],
+    zero_filler_path: &Path,
+    header_path: &Path,
+    header_file: &mut File,
+    footer_path: &Path,
+    footer_file: &mut File,
+    output_composite: &mut File,
+) -> Result<()> {
+    let zero_filler_path = zero_filler_path
+        .to_str()
+        .ok_or_else(|| Error::InvalidPath(zero_filler_path.to_owned()))?
+        .to_string();
+    let header_path = header_path
+        .to_str()
+        .ok_or_else(|| Error::InvalidPath(header_path.to_owned()))?
+        .to_string();
+    let footer_path = footer_path
+        .to_str()
+        .ok_or_else(|| Error::InvalidPath(footer_path.to_owned()))?
+        .to_string();
+
+    let mut composite_proto = CompositeDisk::new();
+    composite_proto.version = COMPOSITE_DISK_VERSION;
+    composite_proto.component_disks.push(ComponentDisk {
+        file_path: header_path,
+        offset: 0,
+        read_write_capability: ReadWriteCapability::READ_ONLY,
+        ..ComponentDisk::new()
+    });
+
+    // Write partitions to a temporary buffer so that we can calculate the CRC, and construct the
+    // ComponentDisk proto messages at the same time.
+    let mut partitions_buffer =
+        [0u8; GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize];
+    let mut writer: &mut [u8] = &mut partitions_buffer;
+    let mut next_disk_offset = GPT_BEGINNING_SIZE;
+    let mut labels = HashSet::with_capacity(partitions.len());
+    for partition in partitions {
+        let gpt_entry = create_gpt_entry(partition, next_disk_offset);
+        if !labels.insert(gpt_entry.partition_name) {
+            return Err(Error::DuplicatePartitionLabel(partition.label.clone()));
+        }
+        gpt_entry.write_bytes(&mut writer)?;
+
+        for component_disk in
+            create_component_disks(partition, next_disk_offset, &zero_filler_path)?
+        {
+            composite_proto.component_disks.push(component_disk);
+        }
+
+        next_disk_offset += partition.aligned_size();
+    }
+    let secondary_table_offset = next_disk_offset;
+    let disk_size = align_to_power_of_2(secondary_table_offset + GPT_END_SIZE, DISK_SIZE_SHIFT);
+
+    composite_proto.component_disks.push(ComponentDisk {
+        file_path: footer_path,
+        offset: secondary_table_offset,
+        read_write_capability: ReadWriteCapability::READ_ONLY,
+        ..ComponentDisk::new()
+    });
+
+    // Calculate CRC32 of partition entries.
+    let mut hasher = Hasher::new();
+    hasher.update(&partitions_buffer);
+    let partition_entries_crc32 = hasher.finalize();
+
+    let disk_guid = Uuid::new_v4();
+    write_beginning(
+        header_file,
+        disk_guid,
+        &partitions_buffer,
+        partition_entries_crc32,
+        secondary_table_offset,
+        disk_size,
+    )?;
+    write_end(
+        footer_file,
+        disk_guid,
+        &partitions_buffer,
+        partition_entries_crc32,
+        secondary_table_offset,
+        disk_size,
+    )?;
+
+    composite_proto.length = disk_size;
+    output_composite
+        .write_all(CDISK_MAGIC.as_bytes())
+        .map_err(Error::WriteHeader)?;
+    composite_proto
+        .write_to_writer(output_composite)
+        .map_err(Error::WriteProto)?;
+
+    Ok(())
+}
+
+/// Create a zero filler file which can be used to fill the gaps between partition files.
+/// The filler is sized to be big enough to fill the gaps. (1 << PARTITION_SIZE_SHIFT)
+pub fn create_zero_filler<P: AsRef<Path>>(zero_filler_path: P) -> Result<()> {
+    let f = OpenOptions::new()
+        .create(true)
+        .read(true)
+        .write(true)
+        .truncate(true)
+        .open(zero_filler_path.as_ref())
+        .map_err(Error::WriteZeroFiller)?;
+    f.set_len(1 << PARTITION_SIZE_SHIFT)
+        .map_err(Error::WriteZeroFiller)
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
+
+    use std::matches;
+
     use base::AsRawDescriptor;
     use data_model::VolatileMemory;
     use tempfile::tempfile;
@@ -561,4 +895,146 @@
         }
         assert!(input_memory.iter().eq(output_memory.iter()));
     }
+
+    #[test]
+    fn beginning_size() {
+        let mut buffer = vec![];
+        let partitions = [0u8; GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize];
+        let disk_size = 1000 * SECTOR_SIZE;
+        write_beginning(
+            &mut buffer,
+            Uuid::from_u128(0x12345678_1234_5678_abcd_12345678abcd),
+            &partitions,
+            42,
+            disk_size - GPT_END_SIZE,
+            disk_size,
+        )
+        .unwrap();
+
+        assert_eq!(buffer.len(), GPT_BEGINNING_SIZE as usize);
+    }
+
+    #[test]
+    fn end_size() {
+        let mut buffer = vec![];
+        let partitions = [0u8; GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize];
+        let disk_size = 1000 * SECTOR_SIZE;
+        write_end(
+            &mut buffer,
+            Uuid::from_u128(0x12345678_1234_5678_abcd_12345678abcd),
+            &partitions,
+            42,
+            disk_size - GPT_END_SIZE,
+            disk_size,
+        )
+        .unwrap();
+
+        assert_eq!(buffer.len(), GPT_END_SIZE as usize);
+    }
+
+    #[test]
+    fn end_size_with_padding() {
+        let mut buffer = vec![];
+        let partitions = [0u8; GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize];
+        let disk_size = 1000 * SECTOR_SIZE;
+        let padding = 3 * SECTOR_SIZE;
+        write_end(
+            &mut buffer,
+            Uuid::from_u128(0x12345678_1234_5678_abcd_12345678abcd),
+            &partitions,
+            42,
+            disk_size - GPT_END_SIZE - padding,
+            disk_size,
+        )
+        .unwrap();
+
+        assert_eq!(buffer.len(), GPT_END_SIZE as usize + padding as usize);
+    }
+
+    /// Creates a composite disk image with no partitions.
+    #[test]
+    fn create_composite_disk_empty() {
+        let mut header_image = tempfile().unwrap();
+        let mut footer_image = tempfile().unwrap();
+        let mut composite_image = tempfile().unwrap();
+
+        create_composite_disk(
+            &[],
+            Path::new("/zero_filler.img"),
+            Path::new("/header_path.img"),
+            &mut header_image,
+            Path::new("/footer_path.img"),
+            &mut footer_image,
+            &mut composite_image,
+        )
+        .unwrap();
+    }
+
+    /// Creates a composite disk image with two partitions.
+    #[test]
+    fn create_composite_disk_success() {
+        let mut header_image = tempfile().unwrap();
+        let mut footer_image = tempfile().unwrap();
+        let mut composite_image = tempfile().unwrap();
+
+        create_composite_disk(
+            &[
+                PartitionInfo {
+                    label: "partition1".to_string(),
+                    path: "/partition1.img".to_string().into(),
+                    partition_type: ImagePartitionType::LinuxFilesystem,
+                    writable: false,
+                    size: 0,
+                },
+                PartitionInfo {
+                    label: "partition2".to_string(),
+                    path: "/partition2.img".to_string().into(),
+                    partition_type: ImagePartitionType::LinuxFilesystem,
+                    writable: true,
+                    size: 0,
+                },
+            ],
+            Path::new("/zero_filler.img"),
+            Path::new("/header_path.img"),
+            &mut header_image,
+            Path::new("/footer_path.img"),
+            &mut footer_image,
+            &mut composite_image,
+        )
+        .unwrap();
+    }
+
+    /// Attempts to create a composite disk image with two partitions with the same label.
+    #[test]
+    fn create_composite_disk_duplicate_label() {
+        let mut header_image = tempfile().unwrap();
+        let mut footer_image = tempfile().unwrap();
+        let mut composite_image = tempfile().unwrap();
+
+        let result = create_composite_disk(
+            &[
+                PartitionInfo {
+                    label: "label".to_string(),
+                    path: "/partition1.img".to_string().into(),
+                    partition_type: ImagePartitionType::LinuxFilesystem,
+                    writable: false,
+                    size: 0,
+                },
+                PartitionInfo {
+                    label: "label".to_string(),
+                    path: "/partition2.img".to_string().into(),
+                    partition_type: ImagePartitionType::LinuxFilesystem,
+                    writable: true,
+                    size: 0,
+                },
+            ],
+            Path::new("/zero_filler.img"),
+            Path::new("/header_path.img"),
+            &mut header_image,
+            Path::new("/footer_path.img"),
+            &mut footer_image,
+            &mut composite_image,
+        );
+        assert!(matches!(result, Err(Error::DuplicatePartitionLabel(label)) if label == "label"));
+    }
 }
diff --git a/disk/src/disk.rs b/disk/src/disk.rs
index 792075f..fd07334 100644
--- a/disk/src/disk.rs
+++ b/disk/src/disk.rs
@@ -26,6 +26,15 @@
 mod composite;
 #[cfg(feature = "composite-disk")]
 use composite::{CompositeDiskFile, CDISK_MAGIC, CDISK_MAGIC_LEN};
+#[cfg(feature = "composite-disk")]
+mod gpt;
+#[cfg(feature = "composite-disk")]
+pub use composite::{
+    create_composite_disk, create_zero_filler, Error as CompositeError, ImagePartitionType,
+    PartitionInfo,
+};
+#[cfg(feature = "composite-disk")]
+pub use gpt::Error as GptError;
 
 mod android_sparse;
 use android_sparse::{AndroidSparse, SPARSE_HEADER_MAGIC};
@@ -262,7 +271,13 @@
 
     // Try to read the disk in a nicely-aligned block size unless the whole file is smaller.
     const MAGIC_BLOCK_SIZE: usize = 4096;
-    let mut magic = [0u8; MAGIC_BLOCK_SIZE];
+    #[repr(align(512))]
+    struct BlockAlignedBuffer {
+        data: [u8; MAGIC_BLOCK_SIZE],
+    }
+    let mut magic = BlockAlignedBuffer {
+        data: [0u8; MAGIC_BLOCK_SIZE],
+    };
     let magic_read_len = if disk_size > MAGIC_BLOCK_SIZE as u64 {
         MAGIC_BLOCK_SIZE
     } else {
@@ -271,19 +286,19 @@
         disk_size as usize
     };
 
-    f.read_exact(&mut magic[0..magic_read_len])
+    f.read_exact(&mut magic.data[0..magic_read_len])
         .map_err(Error::ReadingHeader)?;
     f.seek(SeekFrom::Start(orig_seek))
         .map_err(Error::SeekingFile)?;
 
     #[cfg(feature = "composite-disk")]
-    if let Some(cdisk_magic) = magic.get(0..CDISK_MAGIC_LEN) {
+    if let Some(cdisk_magic) = magic.data.get(0..CDISK_MAGIC_LEN) {
         if cdisk_magic == CDISK_MAGIC.as_bytes() {
             return Ok(ImageType::CompositeDisk);
         }
     }
 
-    if let Some(magic4) = magic.get(0..4) {
+    if let Some(magic4) = magic.data.get(0..4) {
         if magic4 == QCOW_MAGIC.to_be_bytes() {
             return Ok(ImageType::Qcow2);
         } else if magic4 == SPARSE_HEADER_MAGIC.to_le_bytes() {
@@ -420,7 +435,7 @@
         mem_offsets: &'a [cros_async::MemRegion],
     ) -> Result<usize> {
         self.inner
-            .read_to_mem(file_offset, mem, mem_offsets)
+            .read_to_mem(Some(file_offset), mem, mem_offsets)
             .await
             .map_err(Error::ReadToMem)
     }
@@ -432,7 +447,7 @@
         mem_offsets: &'a [cros_async::MemRegion],
     ) -> Result<usize> {
         self.inner
-            .write_from_mem(file_offset, mem, mem_offsets)
+            .write_from_mem(Some(file_offset), mem, mem_offsets)
             .await
             .map_err(Error::WriteFromMem)
     }
@@ -471,7 +486,7 @@
             let buf = vec![0u8; write_size];
             nwritten += self
                 .inner
-                .write_from_vec(file_offset + nwritten as u64, buf)
+                .write_from_vec(Some(file_offset + nwritten as u64), buf)
                 .await
                 .map(|(n, _)| n as u64)
                 .map_err(Error::WriteFromVec)?;
diff --git a/disk/src/gpt.rs b/disk/src/gpt.rs
new file mode 100644
index 0000000..39e6d59
--- /dev/null
+++ b/disk/src/gpt.rs
@@ -0,0 +1,275 @@
+// Copyright 2021 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.
+
+//! Functions for writing GUID Partition Tables for use in a composite disk image.
+
+use std::convert::TryInto;
+use std::io::{self, Write};
+use std::num::TryFromIntError;
+
+use crc32fast::Hasher;
+use remain::sorted;
+use thiserror::Error as ThisError;
+use uuid::Uuid;
+
+/// The size in bytes of a disk sector (also called a block).
+pub const SECTOR_SIZE: u64 = 1 << 9;
+/// The size in bytes on an MBR partition entry.
+const MBR_PARTITION_ENTRY_SIZE: usize = 16;
+/// The size in bytes of a GPT header.
+pub const GPT_HEADER_SIZE: u32 = 92;
+/// The number of partition entries in the GPT, which is the maximum number of partitions which are
+/// supported.
+pub const GPT_NUM_PARTITIONS: u32 = 128;
+/// The size in bytes of a single GPT partition entry.
+pub const GPT_PARTITION_ENTRY_SIZE: u32 = 128;
+/// The size in bytes of everything before the first partition: i.e. the MBR, GPT header and GPT
+/// partition entries.
+pub const GPT_BEGINNING_SIZE: u64 = SECTOR_SIZE * 40;
+/// The size in bytes of everything after the last partition: i.e. the GPT partition entries and GPT
+/// footer.
+pub const GPT_END_SIZE: u64 = SECTOR_SIZE * 33;
+
+#[sorted]
+#[derive(ThisError, Debug)]
+pub enum Error {
+    /// The disk size was invalid (too large).
+    #[error("invalid disk size: {0}")]
+    InvalidDiskSize(TryFromIntError),
+    /// There was an error writing data to one of the image files.
+    #[error("failed to write data: {0}")]
+    WritingData(io::Error),
+}
+
+/// Write a protective MBR for a disk of the given total size (in bytes).
+///
+/// This should be written at the start of the disk, before the GPT header. It is one `SECTOR_SIZE`
+/// long.
+pub fn write_protective_mbr(file: &mut impl Write, disk_size: u64) -> Result<(), Error> {
+    // Bootstrap code
+    file.write_all(&[0; 446]).map_err(Error::WritingData)?;
+
+    // Partition status
+    file.write_all(&[0x00]).map_err(Error::WritingData)?;
+    // Begin CHS
+    file.write_all(&[0; 3]).map_err(Error::WritingData)?;
+    // Partition type
+    file.write_all(&[0xEE]).map_err(Error::WritingData)?;
+    // End CHS
+    file.write_all(&[0; 3]).map_err(Error::WritingData)?;
+    let first_lba: u32 = 1;
+    file.write_all(&first_lba.to_le_bytes())
+        .map_err(Error::WritingData)?;
+    let number_of_sectors: u32 = (disk_size / SECTOR_SIZE)
+        .try_into()
+        .map_err(Error::InvalidDiskSize)?;
+    file.write_all(&number_of_sectors.to_le_bytes())
+        .map_err(Error::WritingData)?;
+
+    // Three more empty partitions
+    file.write_all(&[0; MBR_PARTITION_ENTRY_SIZE * 3])
+        .map_err(Error::WritingData)?;
+
+    // Boot signature
+    file.write_all(&[0x55, 0xAA]).map_err(Error::WritingData)?;
+
+    Ok(())
+}
+
+#[derive(Clone, Debug, Default, Eq, PartialEq)]
+struct GptHeader {
+    signature: [u8; 8],
+    revision: [u8; 4],
+    header_size: u32,
+    header_crc32: u32,
+    current_lba: u64,
+    backup_lba: u64,
+    first_usable_lba: u64,
+    last_usable_lba: u64,
+    disk_guid: Uuid,
+    partition_entries_lba: u64,
+    num_partition_entries: u32,
+    partition_entry_size: u32,
+    partition_entries_crc32: u32,
+}
+
+impl GptHeader {
+    fn write_bytes(&self, out: &mut impl Write) -> Result<(), Error> {
+        out.write_all(&self.signature).map_err(Error::WritingData)?;
+        out.write_all(&self.revision).map_err(Error::WritingData)?;
+        out.write_all(&self.header_size.to_le_bytes())
+            .map_err(Error::WritingData)?;
+        out.write_all(&self.header_crc32.to_le_bytes())
+            .map_err(Error::WritingData)?;
+        // Reserved
+        out.write_all(&[0; 4]).map_err(Error::WritingData)?;
+        out.write_all(&self.current_lba.to_le_bytes())
+            .map_err(Error::WritingData)?;
+        out.write_all(&self.backup_lba.to_le_bytes())
+            .map_err(Error::WritingData)?;
+        out.write_all(&self.first_usable_lba.to_le_bytes())
+            .map_err(Error::WritingData)?;
+        out.write_all(&self.last_usable_lba.to_le_bytes())
+            .map_err(Error::WritingData)?;
+
+        // GUID is mixed-endian for some reason, so we can't just use `Uuid::as_bytes()`.
+        write_guid(out, self.disk_guid).map_err(Error::WritingData)?;
+
+        out.write_all(&self.partition_entries_lba.to_le_bytes())
+            .map_err(Error::WritingData)?;
+        out.write_all(&self.num_partition_entries.to_le_bytes())
+            .map_err(Error::WritingData)?;
+        out.write_all(&self.partition_entry_size.to_le_bytes())
+            .map_err(Error::WritingData)?;
+        out.write_all(&self.partition_entries_crc32.to_le_bytes())
+            .map_err(Error::WritingData)?;
+        Ok(())
+    }
+}
+
+/// Write a GPT header for the disk.
+///
+/// It may either be a primary header (which should go at LBA 1) or a secondary header (which should
+/// go at the end of the disk).
+pub fn write_gpt_header(
+    out: &mut impl Write,
+    disk_guid: Uuid,
+    partition_entries_crc32: u32,
+    secondary_table_offset: u64,
+    secondary: bool,
+) -> Result<(), Error> {
+    let primary_header_lba = 1;
+    let secondary_header_lba = (secondary_table_offset + GPT_END_SIZE) / SECTOR_SIZE - 1;
+    let mut gpt_header = GptHeader {
+        signature: *b"EFI PART",
+        revision: [0, 0, 1, 0],
+        header_size: GPT_HEADER_SIZE,
+        current_lba: if secondary {
+            secondary_header_lba
+        } else {
+            primary_header_lba
+        },
+        backup_lba: if secondary {
+            primary_header_lba
+        } else {
+            secondary_header_lba
+        },
+        first_usable_lba: GPT_BEGINNING_SIZE / SECTOR_SIZE,
+        last_usable_lba: secondary_table_offset / SECTOR_SIZE - 1,
+        disk_guid,
+        partition_entries_lba: 2,
+        num_partition_entries: GPT_NUM_PARTITIONS,
+        partition_entry_size: GPT_PARTITION_ENTRY_SIZE,
+        partition_entries_crc32,
+        header_crc32: 0,
+    };
+
+    // Write once to a temporary buffer to calculate the CRC.
+    let mut header_without_crc = [0u8; GPT_HEADER_SIZE as usize];
+    gpt_header.write_bytes(&mut &mut header_without_crc[..])?;
+    let mut hasher = Hasher::new();
+    hasher.update(&header_without_crc);
+    gpt_header.header_crc32 = hasher.finalize();
+
+    gpt_header.write_bytes(out)?;
+
+    Ok(())
+}
+
+/// A GPT entry for a particular partition.
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct GptPartitionEntry {
+    pub partition_type_guid: Uuid,
+    pub unique_partition_guid: Uuid,
+    pub first_lba: u64,
+    pub last_lba: u64,
+    pub attributes: u64,
+    /// UTF-16LE
+    pub partition_name: [u16; 36],
+}
+
+// This is implemented manually because `Default` isn't implemented in the standard library for
+// arrays of more than 32 elements. If that gets implemented (now than const generics are in) then
+// we can derive this instead.
+impl Default for GptPartitionEntry {
+    fn default() -> Self {
+        Self {
+            partition_type_guid: Default::default(),
+            unique_partition_guid: Default::default(),
+            first_lba: 0,
+            last_lba: 0,
+            attributes: 0,
+            partition_name: [0; 36],
+        }
+    }
+}
+
+impl GptPartitionEntry {
+    /// Write out the partition table entry. It will take
+    /// `GPT_PARTITION_ENTRY_SIZE` bytes.
+    pub fn write_bytes(&self, out: &mut impl Write) -> Result<(), Error> {
+        write_guid(out, self.partition_type_guid).map_err(Error::WritingData)?;
+        write_guid(out, self.unique_partition_guid).map_err(Error::WritingData)?;
+        out.write_all(&self.first_lba.to_le_bytes())
+            .map_err(Error::WritingData)?;
+        out.write_all(&self.last_lba.to_le_bytes())
+            .map_err(Error::WritingData)?;
+        out.write_all(&self.attributes.to_le_bytes())
+            .map_err(Error::WritingData)?;
+        for code_unit in &self.partition_name {
+            out.write_all(&code_unit.to_le_bytes())
+                .map_err(Error::WritingData)?;
+        }
+        Ok(())
+    }
+}
+
+/// Write a UUID in the mixed-endian format which GPT uses for GUIDs.
+fn write_guid(out: &mut impl Write, guid: Uuid) -> Result<(), io::Error> {
+    let guid_fields = guid.as_fields();
+    out.write_all(&guid_fields.0.to_le_bytes())?;
+    out.write_all(&guid_fields.1.to_le_bytes())?;
+    out.write_all(&guid_fields.2.to_le_bytes())?;
+    out.write_all(guid_fields.3)?;
+
+    Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn protective_mbr_size() {
+        let mut buffer = vec![];
+        write_protective_mbr(&mut buffer, 1000 * SECTOR_SIZE).unwrap();
+
+        assert_eq!(buffer.len(), SECTOR_SIZE as usize);
+    }
+
+    #[test]
+    fn header_size() {
+        let mut buffer = vec![];
+        write_gpt_header(
+            &mut buffer,
+            Uuid::from_u128(0x12345678_1234_5678_abcd_12345678abcd),
+            42,
+            1000 * SECTOR_SIZE,
+            false,
+        )
+        .unwrap();
+
+        assert_eq!(buffer.len(), GPT_HEADER_SIZE as usize);
+    }
+
+    #[test]
+    fn partition_entry_size() {
+        let mut buffer = vec![];
+        GptPartitionEntry::default()
+            .write_bytes(&mut buffer)
+            .unwrap();
+
+        assert_eq!(buffer.len(), GPT_PARTITION_ENTRY_SIZE as usize);
+    }
+}
diff --git a/disk/src/qcow/mod.rs b/disk/src/qcow/mod.rs
index c7c56cc..82c6590 100644
--- a/disk/src/qcow/mod.rs
+++ b/disk/src/qcow/mod.rs
@@ -7,7 +7,7 @@
 mod vec_cache;
 
 use base::{
-    error, AsRawDescriptor, AsRawDescriptors, FileAllocate, FileReadWriteAtVolatile,
+    error, open_file, AsRawDescriptor, AsRawDescriptors, FileAllocate, FileReadWriteAtVolatile,
     FileReadWriteVolatile, FileSetLen, FileSync, PunchHole, RawDescriptor, SeekHole, WriteZeroesAt,
 };
 use data_model::{VolatileMemory, VolatileSlice};
@@ -16,9 +16,10 @@
 
 use std::cmp::{max, min};
 use std::fmt::{self, Display};
-use std::fs::{File, OpenOptions};
+use std::fs::File;
 use std::io::{self, Read, Seek, SeekFrom, Write};
 use std::mem::size_of;
+use std::path::Path;
 use std::str;
 
 use crate::qcow::qcow_raw_file::QcowRawFile;
@@ -445,10 +446,13 @@
 
         let backing_file = if let Some(backing_file_path) = header.backing_file_path.as_ref() {
             let path = backing_file_path.clone();
-            let backing_raw_file = OpenOptions::new()
-                .read(true)
-                .open(path)
-                .map_err(Error::BackingFileIo)?;
+            let backing_raw_file = open_file(
+                Path::new(&path),
+                true, /*read_only*/
+                // TODO(b/190435784): Add support for O_DIRECT.
+                false, /*O_DIRECT*/
+            )
+            .map_err(|e| Error::BackingFileIo(e.into()))?;
             let backing_file = create_disk_file(backing_raw_file)
                 .map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
             Some(backing_file)
@@ -582,10 +586,13 @@
 
     /// Creates a new QcowFile at the given path.
     pub fn new_from_backing(file: File, backing_file_name: &str) -> Result<QcowFile> {
-        let backing_raw_file = OpenOptions::new()
-            .read(true)
-            .open(backing_file_name)
-            .map_err(Error::BackingFileIo)?;
+        let backing_raw_file = open_file(
+            Path::new(backing_file_name),
+            true, /*read_only*/
+            // TODO(b/190435784): add support for O_DIRECT.
+            false, /*O_DIRECT*/
+        )
+        .map_err(|e| Error::BackingFileIo(e.into()))?;
         let backing_file =
             create_disk_file(backing_raw_file).map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
         let size = backing_file.get_len().map_err(Error::BackingFileIo)?;
diff --git a/enumn/Android.bp b/enumn/Android.bp
index 7a7a112..4d335e8 100644
--- a/enumn/Android.bp
+++ b/enumn/Android.bp
@@ -1,5 +1,5 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
-// Manually disabled tests which depend on host only crate.
+// This file is generated by cargo2android.py --config cargo2android.json.
+// Do not modify this file as changes will be overridden on upgrade.
 
 package {
     // See: http://go/android-license-faq
@@ -10,34 +10,6 @@
     default_applicable_licenses: ["external_crosvm_license"],
 }
 
-rust_defaults {
-    name: "enumn_defaults",
-    defaults: ["crosvm_defaults"],
-    crate_name: "enumn",
-    srcs: ["src/lib.rs"],
-    test_suites: ["general-tests"],
-    auto_gen_config: true,
-    edition: "2018",
-    rustlibs: [
-        "libproc_macro2",
-        "libquote",
-        "libsyn",
-    ],
-}
-
-//rust_test_host {
-//    name: "enumn_host_test_src_lib",
-//    defaults: ["enumn_defaults"],
-//    test_options: {
-//        unit_test: true,
-//    },
-//}
-
-//rust_test {
-//    name: "enumn_device_test_src_lib",
-//    defaults: ["enumn_defaults"],
-//}
-
 rust_proc_macro {
     name: "libenumn",
     defaults: ["crosvm_defaults"],
@@ -50,9 +22,3 @@
         "libsyn",
     ],
 }
-
-// dependent_library ["feature_list"]
-//   proc-macro2-1.0.27 "default,proc-macro"
-//   quote-1.0.9 "default,proc-macro"
-//   syn-1.0.73 "clone-impls,default,derive,parsing,printing,proc-macro,quote"
-//   unicode-xid-0.2.2 "default"
diff --git a/enumn/cargo2android.json b/enumn/cargo2android.json
new file mode 100644
index 0000000..9a6eee3
--- /dev/null
+++ b/enumn/cargo2android.json
@@ -0,0 +1,7 @@
+{
+  "add_workspace": true,
+  "device": true,
+  "global_defaults": "crosvm_defaults",
+  "run": true,
+  "tests": false
+}
\ No newline at end of file
diff --git a/fuse/Android.bp b/fuse/Android.bp
index b364c07..392342f 100644
--- a/fuse/Android.bp
+++ b/fuse/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
 // Do not modify this file as changes will be overridden on upgrade.
 
 package {
@@ -11,7 +11,7 @@
 }
 
 rust_defaults {
-    name: "fuse_defaults",
+    name: "fuse_test_defaults",
     defaults: ["crosvm_defaults"],
     crate_name: "fuse",
     srcs: ["src/lib.rs"],
@@ -30,7 +30,7 @@
 
 rust_test_host {
     name: "fuse_host_test_src_lib",
-    defaults: ["fuse_defaults"],
+    defaults: ["fuse_test_defaults"],
     test_options: {
         unit_test: true,
     },
@@ -38,7 +38,7 @@
 
 rust_test {
     name: "fuse_device_test_src_lib",
-    defaults: ["fuse_defaults"],
+    defaults: ["fuse_test_defaults"],
 }
 
 rust_library {
@@ -58,45 +58,3 @@
     ],
     proc_macros: ["libenumn"],
 }
-
-// dependent_library ["feature_list"]
-//   ../assertions/src/lib.rs
-//   ../base/src/lib.rs
-//   ../cros_async/src/lib.rs
-//   ../data_model/src/lib.rs
-//   ../enumn/src/lib.rs
-//   ../io_uring/src/lib.rs
-//   ../sync/src/lib.rs
-//   ../sys_util/poll_token_derive/poll_token_derive.rs
-//   ../sys_util/src/lib.rs
-//   ../tempfile/src/lib.rs
-//   async-task-4.0.3 "default,std"
-//   async-trait-0.1.50
-//   autocfg-1.0.1
-//   bitflags-1.2.1 "default"
-//   futures-0.3.15 "alloc"
-//   futures-channel-0.3.15 "alloc,futures-sink,sink"
-//   futures-core-0.3.15 "alloc"
-//   futures-io-0.3.15
-//   futures-sink-0.3.15 "alloc"
-//   futures-task-0.3.15 "alloc"
-//   futures-util-0.3.15 "alloc,futures-sink,sink"
-//   intrusive-collections-0.9.1 "alloc,default"
-//   itoa-0.4.7
-//   libc-0.2.97 "default,std"
-//   memoffset-0.5.6 "default"
-//   paste-1.0.5
-//   pin-project-lite-0.2.7
-//   pin-utils-0.1.0
-//   proc-macro2-1.0.27 "default,proc-macro"
-//   quote-1.0.9 "default,proc-macro"
-//   ryu-1.0.5
-//   serde-1.0.126 "default,derive,serde_derive,std"
-//   serde_derive-1.0.126 "default"
-//   serde_json-1.0.64 "default,std"
-//   slab-0.4.3 "default,std"
-//   smallvec-1.6.1
-//   syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-//   thiserror-1.0.25
-//   thiserror-impl-1.0.25
-//   unicode-xid-0.2.2 "default"
diff --git a/fuse/src/server.rs b/fuse/src/server.rs
index f28e948..7538c4d 100644
--- a/fuse/src/server.rs
+++ b/fuse/src/server.rs
@@ -24,11 +24,17 @@
 /// A trait for reading from the underlying FUSE endpoint.
 pub trait Reader: io::Read {}
 
+impl<R: Reader> Reader for &'_ mut R {}
+
 /// A trait for writing to the underlying FUSE endpoint. The FUSE device expects the write
 /// operation to happen in one write transaction. Since there are cases when data needs to be
 /// generated earlier than the header, it implies the writer implementation to keep an internal
 /// buffer. The buffer then can be flushed once header and data are both prepared.
 pub trait Writer: io::Write {
+    /// The type passed in to the closure in `write_at`. For most implementations, this should be
+    /// `Self`.
+    type ClosureWriter: Writer + ZeroCopyWriter;
+
     /// Allows a closure to generate and write data at the current writer's offset. The current
     /// writer is passed as a mutable reference to the closure. As an example, this provides an
     /// adapter for the read implementation of a filesystem to write directly to the final buffer
@@ -41,12 +47,27 @@
     /// complexity.
     fn write_at<F>(&mut self, offset: usize, f: F) -> io::Result<usize>
     where
-        F: Fn(&mut Self) -> io::Result<usize>;
+        F: Fn(&mut Self::ClosureWriter) -> io::Result<usize>;
 
     /// Checks if the writer can still accept certain amount of data.
     fn has_sufficient_buffer(&self, size: u32) -> bool;
 }
 
+impl<W: Writer> Writer for &'_ mut W {
+    type ClosureWriter = W::ClosureWriter;
+
+    fn write_at<F>(&mut self, offset: usize, f: F) -> io::Result<usize>
+    where
+        F: Fn(&mut Self::ClosureWriter) -> io::Result<usize>,
+    {
+        (**self).write_at(offset, f)
+    }
+
+    fn has_sufficient_buffer(&self, size: u32) -> bool {
+        (**self).has_sufficient_buffer(size)
+    }
+}
+
 /// A trait for memory mapping for DAX.
 ///
 /// For some transports (like virtio) it may be possible to share a region of memory with the
diff --git a/fuse/src/worker.rs b/fuse/src/worker.rs
index 378e5ee..27dcbee 100644
--- a/fuse/src/worker.rs
+++ b/fuse/src/worker.rs
@@ -13,27 +13,31 @@
 use crate::sys;
 use crate::{Error, Result};
 
-struct DevFuseReader<'a> {
+struct DevFuseReader {
     // File representing /dev/fuse for reading, with sufficient buffer to accommodate a FUSE read
     // transaction.
-    reader: &'a mut BufReader<File>,
+    reader: BufReader<File>,
 }
 
-impl<'a> DevFuseReader<'a> {
-    pub fn new(reader: &'a mut BufReader<File>) -> Self {
+impl DevFuseReader {
+    pub fn new(reader: BufReader<File>) -> Self {
         DevFuseReader { reader }
     }
+
+    fn drain(&mut self) {
+        self.reader.consume(self.reader.buffer().len());
+    }
 }
 
-impl Read for DevFuseReader<'_> {
+impl Read for DevFuseReader {
     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
         self.reader.read(buf)
     }
 }
 
-impl Reader for DevFuseReader<'_> {}
+impl Reader for DevFuseReader {}
 
-impl ZeroCopyReader for DevFuseReader<'_> {
+impl ZeroCopyReader for DevFuseReader {
     fn read_to(&mut self, f: &mut File, count: usize, off: u64) -> io::Result<usize> {
         let buf = self.reader.fill_buf()?;
         let end = std::cmp::min(count, buf.len());
@@ -43,17 +47,17 @@
     }
 }
 
-struct DevFuseWriter<'a> {
+struct DevFuseWriter {
     // File representing /dev/fuse for writing.
-    dev_fuse: &'a mut File,
+    dev_fuse: File,
 
     // An internal buffer to allow generating data and header out of order, such that they can be
     // flushed at once. This is wrapped by a cursor for tracking the current written position.
-    write_buf: &'a mut Cursor<Vec<u8>>,
+    write_buf: Cursor<Vec<u8>>,
 }
 
-impl<'a> DevFuseWriter<'a> {
-    pub fn new(dev_fuse: &'a mut File, write_buf: &'a mut Cursor<Vec<u8>>) -> Self {
+impl DevFuseWriter {
+    pub fn new(dev_fuse: File, write_buf: Cursor<Vec<u8>>) -> Self {
         debug_assert_eq!(write_buf.position(), 0);
 
         DevFuseWriter {
@@ -63,7 +67,7 @@
     }
 }
 
-impl Write for DevFuseWriter<'_> {
+impl Write for DevFuseWriter {
     fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
         self.write_buf.write(buf)
     }
@@ -76,7 +80,9 @@
     }
 }
 
-impl Writer for DevFuseWriter<'_> {
+impl Writer for DevFuseWriter {
+    type ClosureWriter = Self;
+
     fn write_at<F>(&mut self, offset: usize, f: F) -> io::Result<usize>
     where
         F: Fn(&mut Self) -> io::Result<usize>,
@@ -94,7 +100,7 @@
     }
 }
 
-impl ZeroCopyWriter for DevFuseWriter<'_> {
+impl ZeroCopyWriter for DevFuseWriter {
     fn write_from(&mut self, f: &mut File, count: usize, off: u64) -> io::Result<usize> {
         let pos = self.write_buf.position() as usize;
         let end = pos + count;
@@ -145,20 +151,25 @@
     fs: F,
 ) -> Result<()> {
     let server = Server::new(fs);
-    let mut buf_reader = BufReader::with_capacity(
-        max_write as usize + size_of::<sys::InHeader>() + size_of::<sys::WriteIn>(),
-        dev_fuse.try_clone().map_err(Error::EndpointSetup)?,
-    );
-
-    let mut write_buf = Cursor::new(Vec::with_capacity(max_read as usize));
-    let mut wfile = dev_fuse.try_clone().map_err(Error::EndpointSetup)?;
+    let mut dev_fuse_reader = {
+        let rfile = dev_fuse.try_clone().map_err(Error::EndpointSetup)?;
+        let buf_reader = BufReader::with_capacity(
+            max_write as usize + size_of::<sys::InHeader>() + size_of::<sys::WriteIn>(),
+            rfile,
+        );
+        DevFuseReader::new(buf_reader)
+    };
+    let mut dev_fuse_writer = {
+        let wfile = dev_fuse.try_clone().map_err(Error::EndpointSetup)?;
+        let write_buf = Cursor::new(Vec::with_capacity(max_read as usize));
+        DevFuseWriter::new(wfile, write_buf)
+    };
+    let dev_fuse_mapper = DevFuseMapper::new();
     loop {
-        let dev_fuse_reader = DevFuseReader::new(&mut buf_reader);
-        let dev_fuse_writer = DevFuseWriter::new(&mut wfile, &mut write_buf);
-        let dev_fuse_mapper = DevFuseMapper::new();
+        server.handle_message(&mut dev_fuse_reader, &mut dev_fuse_writer, &dev_fuse_mapper)?;
 
-        if let Err(e) = server.handle_message(dev_fuse_reader, dev_fuse_writer, &dev_fuse_mapper) {
-            return Err(e);
-        }
+        // Since we're reusing the buffer to avoid repeated allocation, drain the possible
+        // residual from the buffer.
+        dev_fuse_reader.drain();
     }
 }
diff --git a/gpu_display/Android.bp b/gpu_display/Android.bp
index 9704970..a12dd9e 100644
--- a/gpu_display/Android.bp
+++ b/gpu_display/Android.bp
@@ -142,46 +142,3 @@
     generated_headers: ["gpu_display_server_protocol_headers"],
     export_generated_headers: ["gpu_display_server_protocol_headers"],
 }
-
-// dependent_library ["feature_list"]
-//   ../assertions/src/lib.rs
-//   ../base/src/lib.rs
-//   ../cros_async/src/lib.rs
-//   ../data_model/src/lib.rs
-//   ../io_uring/src/lib.rs
-//   ../linux_input_sys/src/lib.rs
-//   ../sync/src/lib.rs
-//   ../sys_util/poll_token_derive/poll_token_derive.rs
-//   ../sys_util/src/lib.rs
-//   ../tempfile/src/lib.rs
-//   async-task-4.0.3 "default,std"
-//   async-trait-0.1.50
-//   autocfg-1.0.1
-//   cc-1.0.25
-//   futures-0.3.15 "alloc"
-//   futures-channel-0.3.15 "alloc,futures-sink,sink"
-//   futures-core-0.3.15 "alloc"
-//   futures-io-0.3.15
-//   futures-sink-0.3.15 "alloc"
-//   futures-task-0.3.15 "alloc"
-//   futures-util-0.3.15 "alloc,futures-sink,sink"
-//   intrusive-collections-0.9.1 "alloc,default"
-//   itoa-0.4.7
-//   libc-0.2.95 "default,std"
-//   memoffset-0.5.6 "default"
-//   paste-1.0.5
-//   pin-project-lite-0.2.6
-//   pin-utils-0.1.0
-//   pkg-config-0.3.19
-//   proc-macro2-1.0.27 "default,proc-macro"
-//   quote-1.0.9 "default,proc-macro"
-//   ryu-1.0.5
-//   serde-1.0.126 "default,derive,serde_derive,std"
-//   serde_derive-1.0.126 "default"
-//   serde_json-1.0.64 "default,std"
-//   slab-0.4.3 "default,std"
-//   smallvec-1.6.1
-//   syn-1.0.72 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-//   thiserror-1.0.25
-//   thiserror-impl-1.0.25
-//   unicode-xid-0.2.2 "default"
diff --git a/gpu_display/cargo2android.json b/gpu_display/cargo2android.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/gpu_display/cargo2android.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/gpu_display/src/display_wl.c b/gpu_display/src/display_wl.c
index c1b396a..b243120 100644
--- a/gpu_display/src/display_wl.c
+++ b/gpu_display/src/display_wl.c
@@ -28,7 +28,11 @@
 #include "aura-shell.h"
 #include "linux-dmabuf-unstable-v1.h"
 #include "viewporter.h"
+#ifdef ANDROID
 #include "xdg-shell-client-protocol.h"
+#else
+#include "xdg-shell.h"
+#endif
 #include "virtio-gpu-metadata-v1.h"
 #include <wayland-client-core.h>
 #include <wayland-client-protocol.h>
diff --git a/hypervisor/Android.bp b/hypervisor/Android.bp
index 8b8bc33..9563000 100644
--- a/hypervisor/Android.bp
+++ b/hypervisor/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
 // Do not modify this file as changes will be overridden on upgrade.
 
 package {
@@ -11,7 +11,7 @@
 }
 
 rust_defaults {
-    name: "hypervisor_defaults",
+    name: "hypervisor_test_defaults",
     defaults: ["crosvm_defaults"],
     crate_name: "hypervisor",
     // has rustc warnings
@@ -36,7 +36,7 @@
 
 rust_test_host {
     name: "hypervisor_host_test_src_lib",
-    defaults: ["hypervisor_defaults"],
+    defaults: ["hypervisor_test_defaults"],
     test_options: {
         unit_test: true,
     },
@@ -44,7 +44,7 @@
 
 rust_test {
     name: "hypervisor_device_test_src_lib",
-    defaults: ["hypervisor_defaults"],
+    defaults: ["hypervisor_test_defaults"],
 }
 
 rust_library {
@@ -69,51 +69,3 @@
     ],
     proc_macros: ["libenumn"],
 }
-
-// dependent_library ["feature_list"]
-//   ../assertions/src/lib.rs
-//   ../base/src/lib.rs
-//   ../bit_field/bit_field_derive/bit_field_derive.rs
-//   ../bit_field/src/lib.rs
-//   ../cros_async/src/lib.rs
-//   ../data_model/src/lib.rs
-//   ../enumn/src/lib.rs
-//   ../io_uring/src/lib.rs
-//   ../kvm/src/lib.rs
-//   ../kvm_sys/src/lib.rs
-//   ../sync/src/lib.rs
-//   ../sys_util/poll_token_derive/poll_token_derive.rs
-//   ../sys_util/src/lib.rs
-//   ../tempfile/src/lib.rs
-//   ../vm_memory/src/lib.rs
-//   async-task-4.0.3 "default,std"
-//   async-trait-0.1.50
-//   autocfg-1.0.1
-//   bitflags-1.2.1 "default"
-//   downcast-rs-1.2.0 "default,std"
-//   futures-0.3.15 "alloc"
-//   futures-channel-0.3.15 "alloc,futures-sink,sink"
-//   futures-core-0.3.15 "alloc"
-//   futures-io-0.3.15
-//   futures-sink-0.3.15 "alloc"
-//   futures-task-0.3.15 "alloc"
-//   futures-util-0.3.15 "alloc,futures-sink,sink"
-//   intrusive-collections-0.9.1 "alloc,default"
-//   itoa-0.4.7
-//   libc-0.2.97 "default,std"
-//   memoffset-0.5.6 "default"
-//   paste-1.0.5
-//   pin-project-lite-0.2.7
-//   pin-utils-0.1.0
-//   proc-macro2-1.0.27 "default,proc-macro"
-//   quote-1.0.9 "default,proc-macro"
-//   ryu-1.0.5
-//   serde-1.0.126 "default,derive,serde_derive,std"
-//   serde_derive-1.0.126 "default"
-//   serde_json-1.0.64 "default,std"
-//   slab-0.4.3 "default,std"
-//   smallvec-1.6.1
-//   syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-//   thiserror-1.0.25
-//   thiserror-impl-1.0.25
-//   unicode-xid-0.2.2 "default"
diff --git a/hypervisor/src/kvm/x86_64.rs b/hypervisor/src/kvm/x86_64.rs
index 4a29c1b..aeedf57 100644
--- a/hypervisor/src/kvm/x86_64.rs
+++ b/hypervisor/src/kvm/x86_64.rs
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 use base::IoctlNr;
-use std::convert::TryInto;
 
 use libc::E2BIG;
 
@@ -824,14 +823,13 @@
         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());
+            let regs_slice = &mut state.regs[reg_offset..reg_offset + 4];
+
+            // to_le_bytes() produces an array of u8, not i8(c_char), so we can't directly use
+            // copy_from_slice().
+            for (i, v) in value.to_le_bytes().iter().enumerate() {
+                regs_slice[i] = *v as i8;
+            }
         }
         state
     }
@@ -844,12 +842,14 @@
         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());
+
+            // from_le_bytes() only works on arrays of u8, not i8(c_char).
+            let reg_slice = &item.regs[reg_offset..reg_offset + 4];
+            let mut bytes = [0u8; 4];
+            for i in 0..4 {
+                bytes[i] = reg_slice[i] as u8;
+            }
+            state.regs[reg] = u32::from_le_bytes(bytes);
         }
         state
     }
@@ -1356,10 +1356,7 @@
 
         // 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)
-            );
+            assert_eq!(kvm_state.regs[32 + i] as u8, 2u8.pow(i as u32));
         }
 
         // Test converting back to a LapicState
diff --git a/integration_tests/tests/boot.rs b/integration_tests/tests/boot.rs
index 877610c..4305990 100644
--- a/integration_tests/tests/boot.rs
+++ b/integration_tests/tests/boot.rs
@@ -6,7 +6,13 @@
 
 #[test]
 fn boot_test_vm() {
-    let mut vm = TestVm::new(&[], false).unwrap();
+    let mut vm = TestVm::new(&[], false /* debug */, false /* o_direct */).unwrap();
+    assert_eq!(vm.exec_in_guest("echo 42").unwrap().trim(), "42");
+}
+
+#[test]
+fn boot_test_vm_odirect() {
+    let mut vm = TestVm::new(&[], false /* debug */, true /* o_direct */).unwrap();
     assert_eq!(vm.exec_in_guest("echo 42").unwrap().trim(), "42");
 }
 
@@ -14,7 +20,7 @@
 fn boot_test_suspend_resume() {
     // There is no easy way for us to check if the VM is actually suspended. But at
     // least exercise the code-path.
-    let mut vm = TestVm::new(&[], false).unwrap();
+    let mut vm = TestVm::new(&[], false /* debug */, false /*o_direct */).unwrap();
     vm.suspend().unwrap();
     vm.resume().unwrap();
     assert_eq!(vm.exec_in_guest("echo 42").unwrap().trim(), "42");
diff --git a/integration_tests/tests/fixture.rs b/integration_tests/tests/fixture.rs
index 42a1604..398417c 100644
--- a/integration_tests/tests/fixture.rs
+++ b/integration_tests/tests/fixture.rs
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 use std::ffi::CString;
+use std::fs::File;
 use std::io::{self, BufRead, BufReader, Write};
 use std::path::{Path, PathBuf};
 use std::process::Command;
@@ -11,7 +12,6 @@
 use std::thread;
 use std::time::Duration;
 use std::{env, process::Child};
-use std::{fs::File, process::Stdio};
 
 use anyhow::{anyhow, Result};
 use base::syslog;
@@ -173,7 +173,9 @@
 
     /// Downloads prebuilts if needed.
     fn initialize_once() {
-        syslog::init().unwrap();
+        if let Err(e) = syslog::init() {
+            panic!("failed to initiailize syslog: {}", e);
+        }
 
         // It's possible the prebuilts downloaded by crosvm-9999.ebuild differ
         // from the version that crosvm was compiled for.
@@ -228,16 +230,21 @@
     }
 
     /// Configures the VM kernel and rootfs to load from the guest_under_test assets.
-    fn configure_kernel(command: &mut Command) {
+    fn configure_kernel(command: &mut Command, o_direct: bool) {
+        let rootfs_and_option = format!(
+            "{}{}",
+            rootfs_path().to_str().unwrap(),
+            if o_direct { ",o_direct=true" } else { "" }
+        );
         command
-            .args(&["--root", rootfs_path().to_str().unwrap()])
+            .args(&["--root", &rootfs_and_option])
             .args(&["--params", "init=/bin/delegate"])
             .arg(kernel_path());
     }
 
     /// Instanciate a new crosvm instance. The first call will trigger the download of prebuilt
     /// files if necessary.
-    pub fn new(additional_arguments: &[&str], debug: bool) -> Result<TestVm> {
+    pub fn new(additional_arguments: &[&str], debug: bool, o_direct: bool) -> Result<TestVm> {
         static PREP_ONCE: Once = Once::new();
         PREP_ONCE.call_once(|| TestVm::initialize_once());
 
@@ -256,13 +263,10 @@
         command.args(&["--socket", &control_socket_path.to_str().unwrap()]);
         command.args(additional_arguments);
 
-        TestVm::configure_kernel(&mut command);
+        TestVm::configure_kernel(&mut command, o_direct);
 
         println!("$ {:?}", command);
-        if !debug {
-            command.stdout(Stdio::null());
-            command.stderr(Stdio::null());
-        }
+
         let process = command.spawn()?;
 
         // Open pipes. Panic if we cannot connect after a timeout.
diff --git a/io_uring/Android.bp b/io_uring/Android.bp
index 9f21a6f..eae1ce5 100644
--- a/io_uring/Android.bp
+++ b/io_uring/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
 // Do not modify this file as changes will be overridden on upgrade.
 
 package {
@@ -11,7 +11,7 @@
 }
 
 rust_defaults {
-    name: "io_uring_defaults",
+    name: "io_uring_test_defaults",
     defaults: ["crosvm_defaults"],
     crate_name: "io_uring",
     srcs: ["src/lib.rs"],
@@ -30,7 +30,7 @@
 
 rust_test_host {
     name: "io_uring_host_test_src_lib",
-    defaults: ["io_uring_defaults"],
+    defaults: ["io_uring_test_defaults"],
     test_options: {
         unit_test: true,
     },
@@ -38,7 +38,7 @@
 
 rust_test {
     name: "io_uring_device_test_src_lib",
-    defaults: ["io_uring_defaults"],
+    defaults: ["io_uring_test_defaults"],
 }
 
 rust_library {
@@ -56,23 +56,3 @@
         "libthiserror",
     ],
 }
-
-// dependent_library ["feature_list"]
-//   ../assertions/src/lib.rs
-//   ../data_model/src/lib.rs
-//   ../sync/src/lib.rs
-//   ../sys_util/poll_token_derive/poll_token_derive.rs
-//   ../sys_util/src/lib.rs
-//   ../tempfile/src/lib.rs
-//   itoa-0.4.7
-//   libc-0.2.97 "default,std"
-//   proc-macro2-1.0.27 "default,proc-macro"
-//   quote-1.0.9 "default,proc-macro"
-//   ryu-1.0.5
-//   serde-1.0.126 "default,derive,serde_derive,std"
-//   serde_derive-1.0.126 "default"
-//   serde_json-1.0.64 "default,std"
-//   syn-1.0.73 "clone-impls,default,derive,parsing,printing,proc-macro,quote"
-//   thiserror-1.0.25
-//   thiserror-impl-1.0.25
-//   unicode-xid-0.2.2 "default"
diff --git a/io_uring/src/uring.rs b/io_uring/src/uring.rs
index 724b79e..3a516a5 100644
--- a/io_uring/src/uring.rs
+++ b/io_uring/src/uring.rs
@@ -310,6 +310,7 @@
             .add_rw_op(ptr, len, fd, offset, user_data, IORING_OP_READV as u8)
     }
 
+    /// # Safety
     /// See 'writev' but accepts an iterator instead of a vector if there isn't already a vector in
     /// existence.
     pub unsafe fn add_writev_iter<I>(
@@ -368,6 +369,7 @@
         Ok(())
     }
 
+    /// # Safety
     /// See 'readv' but accepts an iterator instead of a vector if there isn't already a vector in
     /// existence.
     pub unsafe fn add_readv_iter<I>(
diff --git a/kernel_cmdline/Android.bp b/kernel_cmdline/Android.bp
index 538b7a1..5250c8f 100644
--- a/kernel_cmdline/Android.bp
+++ b/kernel_cmdline/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
 // Do not modify this file as changes will be overridden on upgrade.
 
 package {
@@ -11,7 +11,7 @@
 }
 
 rust_defaults {
-    name: "kernel_cmdline_defaults",
+    name: "kernel_cmdline_test_defaults",
     defaults: ["crosvm_defaults"],
     crate_name: "kernel_cmdline",
     srcs: ["src/kernel_cmdline.rs"],
@@ -25,7 +25,7 @@
 
 rust_test_host {
     name: "kernel_cmdline_host_test_src_kernel_cmdline",
-    defaults: ["kernel_cmdline_defaults"],
+    defaults: ["kernel_cmdline_test_defaults"],
     test_options: {
         unit_test: true,
     },
@@ -33,7 +33,7 @@
 
 rust_test {
     name: "kernel_cmdline_device_test_src_kernel_cmdline",
-    defaults: ["kernel_cmdline_defaults"],
+    defaults: ["kernel_cmdline_test_defaults"],
 }
 
 rust_library {
@@ -47,6 +47,3 @@
         "liblibc",
     ],
 }
-
-// dependent_library ["feature_list"]
-//   libc-0.2.97 "default,std"
diff --git a/kernel_loader/Android.bp b/kernel_loader/Android.bp
index 630617d..606674d 100644
--- a/kernel_loader/Android.bp
+++ b/kernel_loader/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
 // Do not modify this file as changes will be overridden on upgrade.
 
 package {
@@ -11,7 +11,7 @@
 }
 
 rust_defaults {
-    name: "kernel_loader_defaults",
+    name: "kernel_loader_test_defaults",
     defaults: ["crosvm_defaults"],
     crate_name: "kernel_loader",
     srcs: ["src/lib.rs"],
@@ -28,7 +28,7 @@
 
 rust_test_host {
     name: "kernel_loader_host_test_src_lib",
-    defaults: ["kernel_loader_defaults"],
+    defaults: ["kernel_loader_test_defaults"],
     test_options: {
         unit_test: true,
     },
@@ -36,7 +36,7 @@
 
 rust_test {
     name: "kernel_loader_device_test_src_lib",
-    defaults: ["kernel_loader_defaults"],
+    defaults: ["kernel_loader_test_defaults"],
 }
 
 rust_library {
@@ -53,53 +53,3 @@
         "libvm_memory",
     ],
 }
-
-// dependent_library ["feature_list"]
-//   ../assertions/src/lib.rs
-//   ../base/src/lib.rs
-//   ../cros_async/src/lib.rs
-//   ../data_model/src/lib.rs
-//   ../io_uring/src/lib.rs
-//   ../sync/src/lib.rs
-//   ../sys_util/poll_token_derive/poll_token_derive.rs
-//   ../sys_util/src/lib.rs
-//   ../tempfile/src/lib.rs
-//   ../vm_memory/src/lib.rs
-//   async-task-4.0.3 "default,std"
-//   async-trait-0.1.50
-//   autocfg-1.0.1
-//   bitflags-1.2.1 "default"
-//   cfg-if-1.0.0
-//   futures-0.3.15 "alloc"
-//   futures-channel-0.3.15 "alloc,futures-sink,sink"
-//   futures-core-0.3.15 "alloc"
-//   futures-io-0.3.15
-//   futures-sink-0.3.15 "alloc"
-//   futures-task-0.3.15 "alloc"
-//   futures-util-0.3.15 "alloc,futures-sink,sink"
-//   getrandom-0.2.3 "std"
-//   intrusive-collections-0.9.1 "alloc,default"
-//   itoa-0.4.7
-//   libc-0.2.97 "default,std"
-//   memoffset-0.5.6 "default"
-//   paste-1.0.5
-//   pin-project-lite-0.2.7
-//   pin-utils-0.1.0
-//   ppv-lite86-0.2.10 "simd,std"
-//   proc-macro2-1.0.27 "default,proc-macro"
-//   quote-1.0.9 "default,proc-macro"
-//   rand-0.8.4 "alloc,default,getrandom,libc,rand_chacha,rand_hc,std,std_rng"
-//   rand_chacha-0.3.1 "std"
-//   rand_core-0.6.3 "alloc,getrandom,std"
-//   remove_dir_all-0.5.3
-//   ryu-1.0.5
-//   serde-1.0.126 "default,derive,serde_derive,std"
-//   serde_derive-1.0.126 "default"
-//   serde_json-1.0.64 "default,std"
-//   slab-0.4.3 "default,std"
-//   smallvec-1.6.1
-//   syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-//   tempfile-3.2.0
-//   thiserror-1.0.25
-//   thiserror-impl-1.0.25
-//   unicode-xid-0.2.2 "default"
diff --git a/kvm/Android.bp b/kvm/Android.bp
index d87344f..d0c81cc 100644
--- a/kvm/Android.bp
+++ b/kvm/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
 // Do not modify this file as changes will be overridden on upgrade.
 
 package {
@@ -11,7 +11,7 @@
 }
 
 rust_defaults {
-    name: "kvm_defaults",
+    name: "kvm_test_defaults",
     defaults: ["crosvm_defaults"],
     crate_name: "kvm",
     srcs: ["src/lib.rs"],
@@ -30,7 +30,7 @@
 
 rust_test_host {
     name: "kvm_host_test_src_lib",
-    defaults: ["kvm_defaults"],
+    defaults: ["kvm_test_defaults"],
     test_options: {
         unit_test: true,
     },
@@ -38,11 +38,11 @@
 
 rust_test {
     name: "kvm_device_test_src_lib",
-    defaults: ["kvm_defaults"],
+    defaults: ["kvm_test_defaults"],
 }
 
 rust_defaults {
-    name: "kvm_defaults_kvm",
+    name: "kvm_test_defaults_kvm",
     defaults: ["crosvm_defaults"],
     crate_name: "kvm",
     test_suites: ["general-tests"],
@@ -61,7 +61,7 @@
 
 rust_test_host {
     name: "kvm_host_test_tests_dirty_log",
-    defaults: ["kvm_defaults_kvm"],
+    defaults: ["kvm_test_defaults_kvm"],
     srcs: ["tests/dirty_log.rs"],
     test_options: {
         unit_test: true,
@@ -70,13 +70,13 @@
 
 rust_test {
     name: "kvm_device_test_tests_dirty_log",
-    defaults: ["kvm_defaults_kvm"],
+    defaults: ["kvm_test_defaults_kvm"],
     srcs: ["tests/dirty_log.rs"],
 }
 
 rust_test_host {
     name: "kvm_host_test_tests_read_only_memory",
-    defaults: ["kvm_defaults_kvm"],
+    defaults: ["kvm_test_defaults_kvm"],
     srcs: ["tests/read_only_memory.rs"],
     test_options: {
         unit_test: true,
@@ -85,13 +85,13 @@
 
 rust_test {
     name: "kvm_device_test_tests_read_only_memory",
-    defaults: ["kvm_defaults_kvm"],
+    defaults: ["kvm_test_defaults_kvm"],
     srcs: ["tests/read_only_memory.rs"],
 }
 
 rust_test_host {
     name: "kvm_host_test_tests_real_run_adder",
-    defaults: ["kvm_defaults_kvm"],
+    defaults: ["kvm_test_defaults_kvm"],
     srcs: ["tests/real_run_adder.rs"],
     test_options: {
         unit_test: true,
@@ -100,7 +100,7 @@
 
 rust_test {
     name: "kvm_device_test_tests_real_run_adder",
-    defaults: ["kvm_defaults_kvm"],
+    defaults: ["kvm_test_defaults_kvm"],
     srcs: ["tests/real_run_adder.rs"],
 }
 
@@ -120,46 +120,3 @@
         "libvm_memory",
     ],
 }
-
-// dependent_library ["feature_list"]
-//   ../assertions/src/lib.rs
-//   ../base/src/lib.rs
-//   ../cros_async/src/lib.rs
-//   ../data_model/src/lib.rs
-//   ../io_uring/src/lib.rs
-//   ../kvm_sys/src/lib.rs
-//   ../sync/src/lib.rs
-//   ../sys_util/poll_token_derive/poll_token_derive.rs
-//   ../sys_util/src/lib.rs
-//   ../tempfile/src/lib.rs
-//   ../vm_memory/src/lib.rs
-//   async-task-4.0.3 "default,std"
-//   async-trait-0.1.50
-//   autocfg-1.0.1
-//   bitflags-1.2.1 "default"
-//   futures-0.3.15 "alloc"
-//   futures-channel-0.3.15 "alloc,futures-sink,sink"
-//   futures-core-0.3.15 "alloc"
-//   futures-io-0.3.15
-//   futures-sink-0.3.15 "alloc"
-//   futures-task-0.3.15 "alloc"
-//   futures-util-0.3.15 "alloc,futures-sink,sink"
-//   intrusive-collections-0.9.1 "alloc,default"
-//   itoa-0.4.7
-//   libc-0.2.97 "default,std"
-//   memoffset-0.5.6 "default"
-//   paste-1.0.5
-//   pin-project-lite-0.2.7
-//   pin-utils-0.1.0
-//   proc-macro2-1.0.27 "default,proc-macro"
-//   quote-1.0.9 "default,proc-macro"
-//   ryu-1.0.5
-//   serde-1.0.126 "default,derive,serde_derive,std"
-//   serde_derive-1.0.126 "default"
-//   serde_json-1.0.64 "default,std"
-//   slab-0.4.3 "default,std"
-//   smallvec-1.6.1
-//   syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-//   thiserror-1.0.25
-//   thiserror-impl-1.0.25
-//   unicode-xid-0.2.2 "default"
diff --git a/kvm_sys/Android.bp b/kvm_sys/Android.bp
index 95ebf8d..4f26e2b 100644
--- a/kvm_sys/Android.bp
+++ b/kvm_sys/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
 // Do not modify this file as changes will be overridden on upgrade.
 
 package {
@@ -11,9 +11,10 @@
 }
 
 rust_defaults {
-    name: "kvm_sys_defaults",
+    name: "kvm_sys_test_defaults",
     defaults: ["crosvm_defaults"],
     crate_name: "kvm_sys",
+    // has rustc warnings
     srcs: ["src/lib.rs"],
     test_suites: ["general-tests"],
     auto_gen_config: true,
@@ -27,7 +28,7 @@
 
 rust_test_host {
     name: "kvm_sys_host_test_src_lib",
-    defaults: ["kvm_sys_defaults"],
+    defaults: ["kvm_sys_test_defaults"],
     test_options: {
         unit_test: true,
     },
@@ -35,13 +36,14 @@
 
 rust_test {
     name: "kvm_sys_device_test_src_lib",
-    defaults: ["kvm_sys_defaults"],
+    defaults: ["kvm_sys_test_defaults"],
 }
 
 rust_defaults {
-    name: "kvm_sys_defaults_basic",
+    name: "kvm_sys_test_defaults_basic",
     defaults: ["crosvm_defaults"],
     crate_name: "basic",
+    // has rustc warnings
     srcs: ["tests/basic.rs"],
     test_suites: ["general-tests"],
     auto_gen_config: true,
@@ -56,7 +58,7 @@
 
 rust_test_host {
     name: "kvm_sys_host_test_tests_basic",
-    defaults: ["kvm_sys_defaults_basic"],
+    defaults: ["kvm_sys_test_defaults_basic"],
     test_options: {
         unit_test: true,
     },
@@ -64,12 +66,13 @@
 
 rust_test {
     name: "kvm_sys_device_test_tests_basic",
-    defaults: ["kvm_sys_defaults_basic"],
+    defaults: ["kvm_sys_test_defaults_basic"],
 }
 
 rust_library {
     name: "libkvm_sys",
     defaults: ["crosvm_defaults"],
+    // has rustc warnings
     host_supported: true,
     crate_name: "kvm_sys",
     srcs: ["src/lib.rs"],
@@ -80,43 +83,3 @@
         "liblibc",
     ],
 }
-
-// dependent_library ["feature_list"]
-//   ../assertions/src/lib.rs
-//   ../base/src/lib.rs
-//   ../cros_async/src/lib.rs
-//   ../data_model/src/lib.rs
-//   ../io_uring/src/lib.rs
-//   ../sync/src/lib.rs
-//   ../sys_util/poll_token_derive/poll_token_derive.rs
-//   ../sys_util/src/lib.rs
-//   ../tempfile/src/lib.rs
-//   async-task-4.0.3 "default,std"
-//   async-trait-0.1.50
-//   autocfg-1.0.1
-//   futures-0.3.15 "alloc"
-//   futures-channel-0.3.15 "alloc,futures-sink,sink"
-//   futures-core-0.3.15 "alloc"
-//   futures-io-0.3.15
-//   futures-sink-0.3.15 "alloc"
-//   futures-task-0.3.15 "alloc"
-//   futures-util-0.3.15 "alloc,futures-sink,sink"
-//   intrusive-collections-0.9.1 "alloc,default"
-//   itoa-0.4.7
-//   libc-0.2.97 "default,std"
-//   memoffset-0.5.6 "default"
-//   paste-1.0.5
-//   pin-project-lite-0.2.7
-//   pin-utils-0.1.0
-//   proc-macro2-1.0.27 "default,proc-macro"
-//   quote-1.0.9 "default,proc-macro"
-//   ryu-1.0.5
-//   serde-1.0.126 "default,derive,serde_derive,std"
-//   serde_derive-1.0.126 "default"
-//   serde_json-1.0.64 "default,std"
-//   slab-0.4.3 "default,std"
-//   smallvec-1.6.1
-//   syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-//   thiserror-1.0.25
-//   thiserror-impl-1.0.25
-//   unicode-xid-0.2.2 "default"
diff --git a/rand_ish/Android.bp b/libcras_stub/Android.bp
similarity index 64%
rename from rand_ish/Android.bp
rename to libcras_stub/Android.bp
index aa1fd97..17f9af6 100644
--- a/rand_ish/Android.bp
+++ b/libcras_stub/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
 // Do not modify this file as changes will be overridden on upgrade.
 
 package {
@@ -10,34 +10,34 @@
     default_applicable_licenses: ["external_crosvm_license"],
 }
 
-rust_library {
-    name: "librand_ish",
-    defaults: ["crosvm_defaults"],
-    host_supported: true,
-    crate_name: "rand_ish",
-    srcs: ["src/lib.rs"],
-    edition: "2018",
-}
-
 rust_defaults {
-    name: "rand_ish_defaults",
+    name: "libcras_stub_test_defaults",
     defaults: ["crosvm_defaults"],
-    crate_name: "rand_ish",
-    srcs: ["src/lib.rs"],
+    crate_name: "libcras",
+    srcs: ["src/libcras.rs"],
     test_suites: ["general-tests"],
     auto_gen_config: true,
     edition: "2018",
 }
 
 rust_test_host {
-    name: "rand_ish_host_test_src_lib",
-    defaults: ["rand_ish_defaults"],
+    name: "libcras_stub_host_test_src_libcras",
+    defaults: ["libcras_stub_test_defaults"],
     test_options: {
         unit_test: true,
     },
 }
 
 rust_test {
-    name: "rand_ish_device_test_src_lib",
-    defaults: ["rand_ish_defaults"],
+    name: "libcras_stub_device_test_src_libcras",
+    defaults: ["libcras_stub_test_defaults"],
+}
+
+rust_library {
+    name: "liblibcras",
+    defaults: ["crosvm_defaults"],
+    host_supported: true,
+    crate_name: "libcras",
+    srcs: ["src/libcras.rs"],
+    edition: "2018",
 }
diff --git a/rand_ish/Cargo.toml b/libcras_stub/Cargo.toml
similarity index 63%
rename from rand_ish/Cargo.toml
rename to libcras_stub/Cargo.toml
index 63bb001..7d40cf2 100644
--- a/rand_ish/Cargo.toml
+++ b/libcras_stub/Cargo.toml
@@ -1,5 +1,8 @@
 [package]
-name = "rand_ish"
+name = "libcras"
 version = "0.1.0"
 authors = ["The Chromium OS Authors"]
 edition = "2018"
+
+[lib]
+path = "src/libcras.rs"
diff --git a/libcras_stub/README.md b/libcras_stub/README.md
new file mode 100644
index 0000000..bdd5e74
--- /dev/null
+++ b/libcras_stub/README.md
@@ -0,0 +1,12 @@
+# Stub crate for libcras
+
+libcras is used by ChromeOS to play audio through the cras server.
+
+In ChromeOS builds, the `audio_cras` cargo feature is enabled and this crate is
+replaced with the actual [libcras] implementation.
+
+On other platforms, the feature flag will remain disabled and this crate is used
+to satisfy cargo dependencies on libcras.
+
+[libcras]:
+    https://source.chromium.org/chromiumos/chromiumos/codesearch/+/main:src/third_party/adhd/cras/client/libcras/
diff --git a/libcras_stub/src/libcras.rs b/libcras_stub/src/libcras.rs
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/libcras_stub/src/libcras.rs
@@ -0,0 +1 @@
+
diff --git a/libcrosvm_control/Android.bp b/libcrosvm_control/Android.bp
index dedb8f9..2307595 100644
--- a/libcrosvm_control/Android.bp
+++ b/libcrosvm_control/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
 // Do not modify this file as changes will be overridden on upgrade.
 
 package {
@@ -11,7 +11,7 @@
 }
 
 rust_defaults {
-    name: "libcrosvm_control_defaults",
+    name: "libcrosvm_control_test_defaults",
     defaults: ["crosvm_defaults"],
     crate_name: "libcrosvm_control",
     srcs: ["src/lib.rs"],
@@ -27,7 +27,7 @@
 
 rust_test_host {
     name: "libcrosvm_control_host_test_src_lib",
-    defaults: ["libcrosvm_control_defaults"],
+    defaults: ["libcrosvm_control_test_defaults"],
     test_options: {
         unit_test: true,
     },
@@ -35,7 +35,7 @@
 
 rust_test {
     name: "libcrosvm_control_device_test_src_lib",
-    defaults: ["libcrosvm_control_defaults"],
+    defaults: ["libcrosvm_control_test_defaults"],
 }
 
 rust_ffi_shared {
@@ -52,55 +52,3 @@
         "libvm_control",
     ],
 }
-
-// dependent_library ["feature_list"]
-//   ../assertions/src/lib.rs
-//   ../base/src/lib.rs
-//   ../bit_field/bit_field_derive/bit_field_derive.rs
-//   ../bit_field/src/lib.rs
-//   ../cros_async/src/lib.rs
-//   ../data_model/src/lib.rs
-//   ../enumn/src/lib.rs
-//   ../hypervisor/src/lib.rs
-//   ../io_uring/src/lib.rs
-//   ../kvm/src/lib.rs
-//   ../kvm_sys/src/lib.rs
-//   ../resources/src/lib.rs
-//   ../rutabaga_gfx/src/lib.rs
-//   ../sync/src/lib.rs
-//   ../sys_util/poll_token_derive/poll_token_derive.rs
-//   ../sys_util/src/lib.rs
-//   ../tempfile/src/lib.rs
-//   ../vm_control/src/lib.rs
-//   ../vm_memory/src/lib.rs
-//   async-task-4.0.3 "default,std"
-//   async-trait-0.1.50
-//   autocfg-1.0.1
-//   bitflags-1.2.1 "default"
-//   downcast-rs-1.2.0 "default,std"
-//   futures-0.3.15 "alloc"
-//   futures-channel-0.3.15 "alloc,futures-sink,sink"
-//   futures-core-0.3.15 "alloc"
-//   futures-io-0.3.15
-//   futures-sink-0.3.15 "alloc"
-//   futures-task-0.3.15 "alloc"
-//   futures-util-0.3.15 "alloc,futures-sink,sink"
-//   intrusive-collections-0.9.1 "alloc,default"
-//   itoa-0.4.7
-//   libc-0.2.97 "default,std"
-//   memoffset-0.5.6 "default"
-//   paste-1.0.5
-//   pin-project-lite-0.2.7
-//   pin-utils-0.1.0
-//   proc-macro2-1.0.27 "default,proc-macro"
-//   quote-1.0.9 "default,proc-macro"
-//   ryu-1.0.5
-//   serde-1.0.126 "default,derive,serde_derive,std"
-//   serde_derive-1.0.126 "default"
-//   serde_json-1.0.64 "default,std"
-//   slab-0.4.3 "default,std"
-//   smallvec-1.6.1
-//   syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-//   thiserror-1.0.25
-//   thiserror-impl-1.0.25
-//   unicode-xid-0.2.2 "default"
diff --git a/libvda/Android.bp b/libvda/Android.bp
new file mode 100644
index 0000000..eeacbc9
--- /dev/null
+++ b/libvda/Android.bp
@@ -0,0 +1 @@
+// Manually removed, this doesn't build on Android.
diff --git a/libvda/Cargo.toml b/libvda/Cargo.toml
new file mode 100644
index 0000000..0be4e18
--- /dev/null
+++ b/libvda/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "libvda"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+edition = "2018"
+links = "vda"
+
+[dependencies]
+enumn = { path = "../enumn" }
+libc = "*"
+
+[build-dependencies]
+pkg-config = "*"
diff --git a/libvda/README.md b/libvda/README.md
new file mode 100644
index 0000000..38eaa08
--- /dev/null
+++ b/libvda/README.md
@@ -0,0 +1,31 @@
+# Libvda Rust wrapper
+
+Note: This crate is specific to ChromeOS and requires the native
+(libvda)[https://source.chromium.org/chromiumos/chromiumos/codesearch/+/main:src/platform2/arc/vm/libvda]
+library at link time.
+
+Rust wrapper for libvda. This library is used to enable communication with
+Chrome's GPU process to perform hardware accelerated decoding and encoding. It
+is currently in development to be used by crosvm's virtio-video device.
+
+### Building for the host environment
+
+You can also execute `cargo` directly for faster build and tests. This would be
+useful when you are developing this crate. Since this crate depends on
+libvda.so, you need to install it to host environment first.
+
+```shell
+(chroot)$ sudo emerge chromeos-base/libvda        # Install libvda.so to host.
+# Build
+(chroot)$ cargo build
+# Unit tests
+(chroot)$ cargo test
+```
+
+## Updating generated bindings
+
+`src/bindings.rs` is automatically generated from `libvda_common.h`.
+`src/decode/bindings.rs` is automatically generated from `libvda_decode.h`.
+`src/encode/bindings.rs` is automatically generated from `libvda_encode.h`.
+
+See the header of the bindings file for the generation command.
diff --git a/libvda/build.rs b/libvda/build.rs
new file mode 100644
index 0000000..253bba9
--- /dev/null
+++ b/libvda/build.rs
@@ -0,0 +1,16 @@
+// Copyright 2019 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.
+
+fn main() {
+    match pkg_config::probe_library("libvda") {
+        Ok(_) => (),
+        // Ignore a pkg-config failure to allow cargo-clippy to run even when libvda.pc doesn't
+        // exist.
+        Err(pkg_config::Error::Failure { command, .. })
+            if command == r#""pkg-config" "--libs" "--cflags" "libvda""# => {}
+        Err(e) => panic!("{}", e),
+    };
+
+    println!("cargo:rustc-link-lib=dylib=vda");
+}
diff --git a/libvda/cargo2android.json b/libvda/cargo2android.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/libvda/cargo2android.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/libvda/src/bindings.rs b/libvda/src/bindings.rs
new file mode 100644
index 0000000..bf67a44
--- /dev/null
+++ b/libvda/src/bindings.rs
@@ -0,0 +1,113 @@
+// 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.
+
+#![allow(
+    dead_code,
+    non_camel_case_types,
+    non_snake_case,
+    non_upper_case_globals
+)]
+
+/*
+automatically generated by rust-bindgen
+
+generated with the command:
+cd ${CHROMEOS_DIR}/src/platform2 && \
+bindgen arc/vm/libvda/libvda_common.h \
+  -o ../platform/crosvm/libvda/src/common/bindings.rs \
+  --whitelist-type "video_.*"
+*/
+
+pub type __int32_t = ::std::os::raw::c_int;
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct video_frame_plane {
+    pub offset: i32,
+    pub stride: i32,
+}
+#[test]
+fn bindgen_test_layout_video_frame_plane() {
+    assert_eq!(
+        ::std::mem::size_of::<video_frame_plane>(),
+        8usize,
+        concat!("Size of: ", stringify!(video_frame_plane))
+    );
+    assert_eq!(
+        ::std::mem::align_of::<video_frame_plane>(),
+        4usize,
+        concat!("Alignment of ", stringify!(video_frame_plane))
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<video_frame_plane>())).offset as *const _ as usize },
+        0usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(video_frame_plane),
+            "::",
+            stringify!(offset)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<video_frame_plane>())).stride as *const _ as usize },
+        4usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(video_frame_plane),
+            "::",
+            stringify!(stride)
+        )
+    );
+}
+pub type video_frame_plane_t = video_frame_plane;
+pub const video_codec_profile_VIDEO_CODEC_PROFILE_UNKNOWN: video_codec_profile = -1;
+pub const video_codec_profile_VIDEO_CODEC_PROFILE_MIN: video_codec_profile = -1;
+pub const video_codec_profile_H264PROFILE_MIN: video_codec_profile = 0;
+pub const video_codec_profile_H264PROFILE_BASELINE: video_codec_profile = 0;
+pub const video_codec_profile_H264PROFILE_MAIN: video_codec_profile = 1;
+pub const video_codec_profile_H264PROFILE_EXTENDED: video_codec_profile = 2;
+pub const video_codec_profile_H264PROFILE_HIGH: video_codec_profile = 3;
+pub const video_codec_profile_H264PROFILE_HIGH10PROFILE: video_codec_profile = 4;
+pub const video_codec_profile_H264PROFILE_HIGH422PROFILE: video_codec_profile = 5;
+pub const video_codec_profile_H264PROFILE_HIGH444PREDICTIVEPROFILE: video_codec_profile = 6;
+pub const video_codec_profile_H264PROFILE_SCALABLEBASELINE: video_codec_profile = 7;
+pub const video_codec_profile_H264PROFILE_SCALABLEHIGH: video_codec_profile = 8;
+pub const video_codec_profile_H264PROFILE_STEREOHIGH: video_codec_profile = 9;
+pub const video_codec_profile_H264PROFILE_MULTIVIEWHIGH: video_codec_profile = 10;
+pub const video_codec_profile_H264PROFILE_MAX: video_codec_profile = 10;
+pub const video_codec_profile_VP8PROFILE_MIN: video_codec_profile = 11;
+pub const video_codec_profile_VP8PROFILE_ANY: video_codec_profile = 11;
+pub const video_codec_profile_VP8PROFILE_MAX: video_codec_profile = 11;
+pub const video_codec_profile_VP9PROFILE_MIN: video_codec_profile = 12;
+pub const video_codec_profile_VP9PROFILE_PROFILE0: video_codec_profile = 12;
+pub const video_codec_profile_VP9PROFILE_PROFILE1: video_codec_profile = 13;
+pub const video_codec_profile_VP9PROFILE_PROFILE2: video_codec_profile = 14;
+pub const video_codec_profile_VP9PROFILE_PROFILE3: video_codec_profile = 15;
+pub const video_codec_profile_VP9PROFILE_MAX: video_codec_profile = 15;
+pub const video_codec_profile_HEVCPROFILE_MIN: video_codec_profile = 16;
+pub const video_codec_profile_HEVCPROFILE_MAIN: video_codec_profile = 16;
+pub const video_codec_profile_HEVCPROFILE_MAIN10: video_codec_profile = 17;
+pub const video_codec_profile_HEVCPROFILE_MAIN_STILL_PICTURE: video_codec_profile = 18;
+pub const video_codec_profile_HEVCPROFILE_MAX: video_codec_profile = 18;
+pub const video_codec_profile_DOLBYVISION_MIN: video_codec_profile = 19;
+pub const video_codec_profile_DOLBYVISION_PROFILE0: video_codec_profile = 19;
+pub const video_codec_profile_DOLBYVISION_PROFILE4: video_codec_profile = 20;
+pub const video_codec_profile_DOLBYVISION_PROFILE5: video_codec_profile = 21;
+pub const video_codec_profile_DOLBYVISION_PROFILE7: video_codec_profile = 22;
+pub const video_codec_profile_DOLBYVISION_MAX: video_codec_profile = 22;
+pub const video_codec_profile_THEORAPROFILE_MIN: video_codec_profile = 23;
+pub const video_codec_profile_THEORAPROFILE_ANY: video_codec_profile = 23;
+pub const video_codec_profile_THEORAPROFILE_MAX: video_codec_profile = 23;
+pub const video_codec_profile_AV1PROFILE_MIN: video_codec_profile = 24;
+pub const video_codec_profile_AV1PROFILE_PROFILE_MAIN: video_codec_profile = 24;
+pub const video_codec_profile_AV1PROFILE_PROFILE_HIGH: video_codec_profile = 25;
+pub const video_codec_profile_AV1PROFILE_PROFILE_PRO: video_codec_profile = 26;
+pub const video_codec_profile_AV1PROFILE_MAX: video_codec_profile = 26;
+pub const video_codec_profile_VIDEO_CODEC_PROFILE_MAX: video_codec_profile = 26;
+pub type video_codec_profile = i32;
+pub use self::video_codec_profile as video_codec_profile_t;
+pub const video_pixel_format_YV12: video_pixel_format = 0;
+pub const video_pixel_format_NV12: video_pixel_format = 1;
+pub const video_pixel_format_PIXEL_FORMAT_MAX: video_pixel_format = 1;
+pub type video_pixel_format = u32;
+pub use self::video_pixel_format as video_pixel_format_t;
diff --git a/libvda/src/decode/bindings.rs b/libvda/src/decode/bindings.rs
new file mode 100644
index 0000000..9562436
--- /dev/null
+++ b/libvda/src/decode/bindings.rs
@@ -0,0 +1,617 @@
+// 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.
+
+#![allow(
+    dead_code,
+    non_camel_case_types,
+    non_snake_case,
+    non_upper_case_globals
+)]
+
+/*
+automatically generated by rust-bindgen
+
+generated with the command:
+cd ${CHROMEOS_DIR}/src/platform2/ && \
+bindgen arc/vm/libvda/libvda_decode.h \
+  -o ../platform/crosvm/libvda/src/decode/bindings.rs \
+  --raw-line 'pub use crate::bindings::*;' \
+  --whitelist-function "initialize" \
+  --whitelist-function "deinitialize" \
+  --whitelist-function "get_vda_capabilities" \
+  --whitelist-function "init_decode_session" \
+  --whitelist-function "close_decode_session" \
+  --whitelist-function "vda_.*" \
+  --whitelist-type "vda_.*" \
+  --blacklist-type "video_.*" \
+  -- \
+  -I .
+*/
+
+pub use crate::bindings::*;
+
+pub type __int32_t = ::std::os::raw::c_int;
+pub type __uint32_t = ::std::os::raw::c_uint;
+pub const vda_impl_type_FAKE: vda_impl_type = 0;
+pub const vda_impl_type_GAVDA: vda_impl_type = 1;
+pub type vda_impl_type = u32;
+pub use self::vda_impl_type as vda_impl_type_t;
+pub const vda_result_SUCCESS: vda_result = 0;
+pub const vda_result_ILLEGAL_STATE: vda_result = 1;
+pub const vda_result_INVALID_ARGUMENT: vda_result = 2;
+pub const vda_result_UNREADABLE_INPUT: vda_result = 3;
+pub const vda_result_PLATFORM_FAILURE: vda_result = 4;
+pub const vda_result_INSUFFICIENT_RESOURCES: vda_result = 5;
+pub const vda_result_CANCELLED: vda_result = 6;
+pub type vda_result = u32;
+pub use self::vda_result as vda_result_t;
+pub use self::video_codec_profile_t as vda_profile_t;
+pub use self::video_pixel_format_t as vda_pixel_format_t;
+pub const vda_event_type_UNKNOWN: vda_event_type = 0;
+pub const vda_event_type_PROVIDE_PICTURE_BUFFERS: vda_event_type = 1;
+pub const vda_event_type_PICTURE_READY: vda_event_type = 2;
+pub const vda_event_type_NOTIFY_END_OF_BITSTREAM_BUFFER: vda_event_type = 3;
+pub const vda_event_type_NOTIFY_ERROR: vda_event_type = 4;
+pub const vda_event_type_RESET_RESPONSE: vda_event_type = 5;
+pub const vda_event_type_FLUSH_RESPONSE: vda_event_type = 6;
+pub type vda_event_type = u32;
+pub use self::vda_event_type as vda_event_type_t;
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct provide_picture_buffers_event_data {
+    pub min_num_buffers: u32,
+    pub width: i32,
+    pub height: i32,
+    pub visible_rect_left: i32,
+    pub visible_rect_top: i32,
+    pub visible_rect_right: i32,
+    pub visible_rect_bottom: i32,
+}
+#[test]
+fn bindgen_test_layout_provide_picture_buffers_event_data() {
+    assert_eq!(
+        ::std::mem::size_of::<provide_picture_buffers_event_data>(),
+        28usize,
+        concat!("Size of: ", stringify!(provide_picture_buffers_event_data))
+    );
+    assert_eq!(
+        ::std::mem::align_of::<provide_picture_buffers_event_data>(),
+        4usize,
+        concat!(
+            "Alignment of ",
+            stringify!(provide_picture_buffers_event_data)
+        )
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<provide_picture_buffers_event_data>())).min_num_buffers
+                as *const _ as usize
+        },
+        0usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(provide_picture_buffers_event_data),
+            "::",
+            stringify!(min_num_buffers)
+        )
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<provide_picture_buffers_event_data>())).width as *const _
+                as usize
+        },
+        4usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(provide_picture_buffers_event_data),
+            "::",
+            stringify!(width)
+        )
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<provide_picture_buffers_event_data>())).height as *const _
+                as usize
+        },
+        8usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(provide_picture_buffers_event_data),
+            "::",
+            stringify!(height)
+        )
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<provide_picture_buffers_event_data>())).visible_rect_left
+                as *const _ as usize
+        },
+        12usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(provide_picture_buffers_event_data),
+            "::",
+            stringify!(visible_rect_left)
+        )
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<provide_picture_buffers_event_data>())).visible_rect_top
+                as *const _ as usize
+        },
+        16usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(provide_picture_buffers_event_data),
+            "::",
+            stringify!(visible_rect_top)
+        )
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<provide_picture_buffers_event_data>())).visible_rect_right
+                as *const _ as usize
+        },
+        20usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(provide_picture_buffers_event_data),
+            "::",
+            stringify!(visible_rect_right)
+        )
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<provide_picture_buffers_event_data>())).visible_rect_bottom
+                as *const _ as usize
+        },
+        24usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(provide_picture_buffers_event_data),
+            "::",
+            stringify!(visible_rect_bottom)
+        )
+    );
+}
+pub type provide_picture_buffers_event_data_t = provide_picture_buffers_event_data;
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct picture_ready_event_data {
+    pub picture_buffer_id: i32,
+    pub bitstream_id: i32,
+    pub crop_left: i32,
+    pub crop_top: i32,
+    pub crop_right: i32,
+    pub crop_bottom: i32,
+}
+#[test]
+fn bindgen_test_layout_picture_ready_event_data() {
+    assert_eq!(
+        ::std::mem::size_of::<picture_ready_event_data>(),
+        24usize,
+        concat!("Size of: ", stringify!(picture_ready_event_data))
+    );
+    assert_eq!(
+        ::std::mem::align_of::<picture_ready_event_data>(),
+        4usize,
+        concat!("Alignment of ", stringify!(picture_ready_event_data))
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<picture_ready_event_data>())).picture_buffer_id as *const _
+                as usize
+        },
+        0usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(picture_ready_event_data),
+            "::",
+            stringify!(picture_buffer_id)
+        )
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<picture_ready_event_data>())).bitstream_id as *const _ as usize
+        },
+        4usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(picture_ready_event_data),
+            "::",
+            stringify!(bitstream_id)
+        )
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<picture_ready_event_data>())).crop_left as *const _ as usize
+        },
+        8usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(picture_ready_event_data),
+            "::",
+            stringify!(crop_left)
+        )
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<picture_ready_event_data>())).crop_top as *const _ as usize
+        },
+        12usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(picture_ready_event_data),
+            "::",
+            stringify!(crop_top)
+        )
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<picture_ready_event_data>())).crop_right as *const _ as usize
+        },
+        16usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(picture_ready_event_data),
+            "::",
+            stringify!(crop_right)
+        )
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<picture_ready_event_data>())).crop_bottom as *const _ as usize
+        },
+        20usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(picture_ready_event_data),
+            "::",
+            stringify!(crop_bottom)
+        )
+    );
+}
+pub type picture_ready_event_data_t = picture_ready_event_data;
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub union vda_event_data {
+    pub provide_picture_buffers: provide_picture_buffers_event_data_t,
+    pub picture_ready: picture_ready_event_data_t,
+    pub bitstream_id: i32,
+    pub result: vda_result_t,
+    _bindgen_union_align: [u32; 7usize],
+}
+#[test]
+fn bindgen_test_layout_vda_event_data() {
+    assert_eq!(
+        ::std::mem::size_of::<vda_event_data>(),
+        28usize,
+        concat!("Size of: ", stringify!(vda_event_data))
+    );
+    assert_eq!(
+        ::std::mem::align_of::<vda_event_data>(),
+        4usize,
+        concat!("Alignment of ", stringify!(vda_event_data))
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<vda_event_data>())).provide_picture_buffers as *const _ as usize
+        },
+        0usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vda_event_data),
+            "::",
+            stringify!(provide_picture_buffers)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<vda_event_data>())).picture_ready as *const _ as usize },
+        0usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vda_event_data),
+            "::",
+            stringify!(picture_ready)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<vda_event_data>())).bitstream_id as *const _ as usize },
+        0usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vda_event_data),
+            "::",
+            stringify!(bitstream_id)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<vda_event_data>())).result as *const _ as usize },
+        0usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vda_event_data),
+            "::",
+            stringify!(result)
+        )
+    );
+}
+pub type vda_event_data_t = vda_event_data;
+#[repr(C)]
+pub struct vda_input_format {
+    pub profile: vda_profile_t,
+    pub min_width: u32,
+    pub min_height: u32,
+    pub max_width: u32,
+    pub max_height: u32,
+}
+#[test]
+fn bindgen_test_layout_vda_input_format() {
+    assert_eq!(
+        ::std::mem::size_of::<vda_input_format>(),
+        20usize,
+        concat!("Size of: ", stringify!(vda_input_format))
+    );
+    assert_eq!(
+        ::std::mem::align_of::<vda_input_format>(),
+        4usize,
+        concat!("Alignment of ", stringify!(vda_input_format))
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<vda_input_format>())).profile as *const _ as usize },
+        0usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vda_input_format),
+            "::",
+            stringify!(profile)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<vda_input_format>())).min_width as *const _ as usize },
+        4usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vda_input_format),
+            "::",
+            stringify!(min_width)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<vda_input_format>())).min_height as *const _ as usize },
+        8usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vda_input_format),
+            "::",
+            stringify!(min_height)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<vda_input_format>())).max_width as *const _ as usize },
+        12usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vda_input_format),
+            "::",
+            stringify!(max_width)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<vda_input_format>())).max_height as *const _ as usize },
+        16usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vda_input_format),
+            "::",
+            stringify!(max_height)
+        )
+    );
+}
+pub type vda_input_format_t = vda_input_format;
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct vda_event {
+    pub event_type: vda_event_type_t,
+    pub event_data: vda_event_data_t,
+}
+#[test]
+fn bindgen_test_layout_vda_event() {
+    assert_eq!(
+        ::std::mem::size_of::<vda_event>(),
+        32usize,
+        concat!("Size of: ", stringify!(vda_event))
+    );
+    assert_eq!(
+        ::std::mem::align_of::<vda_event>(),
+        4usize,
+        concat!("Alignment of ", stringify!(vda_event))
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<vda_event>())).event_type as *const _ as usize },
+        0usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vda_event),
+            "::",
+            stringify!(event_type)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<vda_event>())).event_data as *const _ as usize },
+        4usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vda_event),
+            "::",
+            stringify!(event_data)
+        )
+    );
+}
+pub type vda_event_t = vda_event;
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct vda_capabilities {
+    pub num_input_formats: usize,
+    pub input_formats: *const vda_input_format_t,
+    pub num_output_formats: usize,
+    pub output_formats: *const vda_pixel_format_t,
+}
+#[test]
+fn bindgen_test_layout_vda_capabilities() {
+    assert_eq!(
+        ::std::mem::size_of::<vda_capabilities>(),
+        32usize,
+        concat!("Size of: ", stringify!(vda_capabilities))
+    );
+    assert_eq!(
+        ::std::mem::align_of::<vda_capabilities>(),
+        8usize,
+        concat!("Alignment of ", stringify!(vda_capabilities))
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<vda_capabilities>())).num_input_formats as *const _ as usize
+        },
+        0usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vda_capabilities),
+            "::",
+            stringify!(num_input_formats)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<vda_capabilities>())).input_formats as *const _ as usize },
+        8usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vda_capabilities),
+            "::",
+            stringify!(input_formats)
+        )
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<vda_capabilities>())).num_output_formats as *const _ as usize
+        },
+        16usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vda_capabilities),
+            "::",
+            stringify!(num_output_formats)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<vda_capabilities>())).output_formats as *const _ as usize },
+        24usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vda_capabilities),
+            "::",
+            stringify!(output_formats)
+        )
+    );
+}
+pub type vda_capabilities_t = vda_capabilities;
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct vda_session_info {
+    pub ctx: *mut ::std::os::raw::c_void,
+    pub event_pipe_fd: ::std::os::raw::c_int,
+}
+#[test]
+fn bindgen_test_layout_vda_session_info() {
+    assert_eq!(
+        ::std::mem::size_of::<vda_session_info>(),
+        16usize,
+        concat!("Size of: ", stringify!(vda_session_info))
+    );
+    assert_eq!(
+        ::std::mem::align_of::<vda_session_info>(),
+        8usize,
+        concat!("Alignment of ", stringify!(vda_session_info))
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<vda_session_info>())).ctx as *const _ as usize },
+        0usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vda_session_info),
+            "::",
+            stringify!(ctx)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<vda_session_info>())).event_pipe_fd as *const _ as usize },
+        8usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vda_session_info),
+            "::",
+            stringify!(event_pipe_fd)
+        )
+    );
+}
+pub type vda_session_info_t = vda_session_info;
+extern "C" {
+    pub fn initialize(impl_type: vda_impl_type_t) -> *mut ::std::os::raw::c_void;
+}
+extern "C" {
+    pub fn deinitialize(impl_: *mut ::std::os::raw::c_void);
+}
+extern "C" {
+    pub fn get_vda_capabilities(impl_: *mut ::std::os::raw::c_void) -> *const vda_capabilities_t;
+}
+extern "C" {
+    pub fn init_decode_session(
+        impl_: *mut ::std::os::raw::c_void,
+        profile: vda_profile_t,
+    ) -> *mut vda_session_info_t;
+}
+extern "C" {
+    pub fn close_decode_session(
+        impl_: *mut ::std::os::raw::c_void,
+        session_info: *mut vda_session_info_t,
+    );
+}
+extern "C" {
+    pub fn vda_decode(
+        ctx: *mut ::std::os::raw::c_void,
+        bitstream_id: i32,
+        fd: ::std::os::raw::c_int,
+        offset: u32,
+        bytes_used: u32,
+    ) -> vda_result_t;
+}
+extern "C" {
+    pub fn vda_set_output_buffer_count(
+        ctx: *mut ::std::os::raw::c_void,
+        num_output_buffers: usize,
+    ) -> vda_result_t;
+}
+extern "C" {
+    pub fn vda_use_output_buffer(
+        ctx: *mut ::std::os::raw::c_void,
+        picture_buffer_id: i32,
+        format: vda_pixel_format_t,
+        fd: ::std::os::raw::c_int,
+        num_planes: usize,
+        planes: *mut video_frame_plane_t,
+        modifier: u64,
+    ) -> vda_result_t;
+}
+extern "C" {
+    pub fn vda_reuse_output_buffer(
+        ctx: *mut ::std::os::raw::c_void,
+        picture_buffer_id: i32,
+    ) -> vda_result_t;
+}
+extern "C" {
+    pub fn vda_flush(ctx: *mut ::std::os::raw::c_void) -> vda_result_t;
+}
+extern "C" {
+    pub fn vda_reset(ctx: *mut ::std::os::raw::c_void) -> vda_result_t;
+}
diff --git a/libvda/src/decode/event.rs b/libvda/src/decode/event.rs
new file mode 100644
index 0000000..4e365b7
--- /dev/null
+++ b/libvda/src/decode/event.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.
+
+//! Events reported by VDA over pipe FD.
+
+use enumn::N;
+use std::fmt::{self, Display};
+
+use super::bindings;
+use crate::error::*;
+
+/// Represents a response from libvda.
+///
+/// Each value corresponds to a value of [`VideoDecodeAccelerator::Result`](https://cs.chromium.org/chromium/src/components/arc/common/video_decode_accelerator.mojom?rcl=128dc1f18791dc4593b9fd671aab84cb72bf6830&l=84).
+#[derive(Debug, Clone, Copy, N)]
+#[repr(u32)]
+pub enum Response {
+    Success = bindings::vda_result_SUCCESS,
+    IllegalState = bindings::vda_result_ILLEGAL_STATE,
+    InvalidArgument = bindings::vda_result_INVALID_ARGUMENT,
+    UnreadableInput = bindings::vda_result_UNREADABLE_INPUT,
+    PlatformFailure = bindings::vda_result_PLATFORM_FAILURE,
+    InsufficientResources = bindings::vda_result_INSUFFICIENT_RESOURCES,
+    Cancelled = bindings::vda_result_CANCELLED,
+}
+
+impl Response {
+    pub(crate) fn new(res: bindings::vda_result_t) -> Response {
+        Response::n(res).unwrap_or_else(|| panic!("Unknown response is reported from VDA: {}", res))
+    }
+}
+
+impl Display for Response {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        use self::Response::*;
+        match self {
+            Success => write!(f, "success"),
+            IllegalState => write!(f, "illegal state"),
+            InvalidArgument => write!(f, "invalid argument"),
+            UnreadableInput => write!(f, "unreadable input"),
+            PlatformFailure => write!(f, "platform failure"),
+            InsufficientResources => write!(f, "insufficient resources"),
+            Cancelled => write!(f, "cancelled"),
+        }
+    }
+}
+
+impl From<Response> for Result<()> {
+    fn from(r: Response) -> Self {
+        match r {
+            Response::Success => Ok(()),
+            _ => Err(Error::LibVdaFailure(r)),
+        }
+    }
+}
+
+/// Represents a notified event from libvda.
+#[derive(Debug)]
+pub enum Event {
+    /// Requests the users to provide output buffers.
+    ProvidePictureBuffers {
+        min_num_buffers: u32,
+        width: i32,
+        height: i32,
+        visible_rect_left: i32,
+        visible_rect_top: i32,
+        visible_rect_right: i32,
+        visible_rect_bottom: i32,
+    },
+    /// Notifies the user of a decoded frame ready for display.
+    /// These events will arrive in display order.
+    PictureReady {
+        buffer_id: i32,
+        bitstream_id: i32,
+        left: i32,
+        top: i32,
+        right: i32,
+        bottom: i32,
+    },
+    /// Notifies the end of bitstream buffer.
+    NotifyEndOfBitstreamBuffer {
+        bitstream_id: i32,
+    },
+    NotifyError(Response),
+    /// Notifies the result of operation issued by `Session::reset`.
+    ResetResponse(Response),
+    /// Notifies the result of operation issued by `Session::flush`.
+    FlushResponse(Response),
+}
+
+impl Event {
+    /// Creates a new `Event` from a `vda_event_t` instance.
+    /// This function is safe if `event` was a value read from libvda's pipe.
+    pub(crate) unsafe fn new(event: bindings::vda_event_t) -> Result<Event> {
+        use self::Event::*;
+
+        let data = event.event_data;
+        match event.event_type {
+            bindings::vda_event_type_PROVIDE_PICTURE_BUFFERS => {
+                let d = data.provide_picture_buffers;
+                Ok(ProvidePictureBuffers {
+                    min_num_buffers: d.min_num_buffers,
+                    width: d.width,
+                    height: d.height,
+                    visible_rect_left: d.visible_rect_left,
+                    visible_rect_top: d.visible_rect_top,
+                    visible_rect_right: d.visible_rect_right,
+                    visible_rect_bottom: d.visible_rect_bottom,
+                })
+            }
+            bindings::vda_event_type_PICTURE_READY => {
+                let d = data.picture_ready;
+                Ok(PictureReady {
+                    buffer_id: d.picture_buffer_id,
+                    bitstream_id: d.bitstream_id,
+                    left: d.crop_left,
+                    top: d.crop_top,
+                    right: d.crop_right,
+                    bottom: d.crop_bottom,
+                })
+            }
+            bindings::vda_event_type_NOTIFY_END_OF_BITSTREAM_BUFFER => {
+                Ok(NotifyEndOfBitstreamBuffer {
+                    bitstream_id: data.bitstream_id,
+                })
+            }
+            bindings::vda_event_type_NOTIFY_ERROR => Ok(NotifyError(Response::new(data.result))),
+            bindings::vda_event_type_RESET_RESPONSE => {
+                Ok(ResetResponse(Response::new(data.result)))
+            }
+            bindings::vda_event_type_FLUSH_RESPONSE => {
+                Ok(FlushResponse(Response::new(data.result)))
+            }
+            t => panic!("Unknown event is reported from VDA: {}", t),
+        }
+    }
+}
diff --git a/libvda/src/decode/format.rs b/libvda/src/decode/format.rs
new file mode 100644
index 0000000..0acdc3f
--- /dev/null
+++ b/libvda/src/decode/format.rs
@@ -0,0 +1,39 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use super::bindings;
+use crate::error::*;
+use crate::format::*;
+
+/// Represents an input video format for VDA.
+pub struct InputFormat {
+    pub profile: Profile,
+    pub min_width: u32,
+    pub min_height: u32,
+    pub max_width: u32,
+    pub max_height: u32,
+}
+
+impl InputFormat {
+    pub(crate) fn new(f: &bindings::vda_input_format_t) -> Result<InputFormat> {
+        let profile = Profile::n(f.profile).ok_or(Error::UnknownProfile(f.profile))?;
+
+        Ok(InputFormat {
+            profile,
+            min_width: f.min_width,
+            min_height: f.min_height,
+            max_width: f.max_width,
+            max_height: f.max_height,
+        })
+    }
+
+    // The callers must guarantee that `data` is valid for |`len`| elements when
+    // both `data` and `len` are valid.
+    pub(crate) unsafe fn from_raw_parts(
+        data: *const bindings::vda_input_format_t,
+        len: usize,
+    ) -> Result<Vec<Self>> {
+        validate_formats(data, len, Self::new)
+    }
+}
diff --git a/libvda/src/decode/mod.rs b/libvda/src/decode/mod.rs
new file mode 100644
index 0000000..9a02e5a
--- /dev/null
+++ b/libvda/src/decode/mod.rs
@@ -0,0 +1,14 @@
+// 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.
+
+mod bindings;
+mod event;
+mod format;
+mod session;
+mod vda_instance;
+
+pub use event::*;
+pub use format::*;
+pub use session::*;
+pub use vda_instance::*;
diff --git a/libvda/src/decode/session.rs b/libvda/src/decode/session.rs
new file mode 100644
index 0000000..dada37b
--- /dev/null
+++ b/libvda/src/decode/session.rs
@@ -0,0 +1,177 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::io::Read;
+use std::mem;
+use std::os::unix::io::FromRawFd;
+use std::{fs::File, rc::Rc};
+
+use super::event::*;
+use super::{bindings, VdaConnection};
+use crate::error::*;
+use crate::format::{BufferFd, FramePlane, PixelFormat, Profile};
+
+/// Represents a decode session.
+pub struct Session {
+    // Ensures the VDA connection remains open for as long as there are active sessions.
+    connection: Rc<VdaConnection>,
+    // Pipe file to be notified decode session events.
+    pipe: File,
+    session_ptr: *mut bindings::vda_session_info_t,
+}
+
+impl Session {
+    /// Creates a new `Session`.
+    pub(super) fn new(connection: &Rc<VdaConnection>, profile: Profile) -> Option<Self> {
+        // Safe because `conn_ptr()` is valid and won't be invalidated by `init_decode_session()`.
+        let session_ptr: *mut bindings::vda_session_info_t = unsafe {
+            bindings::init_decode_session(connection.conn_ptr(), profile.to_raw_profile())
+        };
+
+        if session_ptr.is_null() {
+            return None;
+        }
+
+        // Dereferencing `session_ptr` is safe because it is a valid pointer to a FD provided by
+        // libvda. We need to dup() the `event_pipe_fd` because File object close() the FD while
+        // libvda also close() it when `close_decode_session` is called.
+        let pipe = unsafe { File::from_raw_fd(libc::dup((*session_ptr).event_pipe_fd)) };
+
+        Some(Session {
+            connection: Rc::clone(connection),
+            pipe,
+            session_ptr,
+        })
+    }
+
+    /// Gets a reference of pipe that notifies events from VDA session.
+    pub fn pipe(&self) -> &File {
+        &self.pipe
+    }
+
+    /// Reads an `Event` object from a pipe provided a decode session.
+    pub fn read_event(&mut self) -> Result<Event> {
+        const BUF_SIZE: usize = mem::size_of::<bindings::vda_event_t>();
+        let mut buf = [0u8; BUF_SIZE];
+
+        self.pipe
+            .read_exact(&mut buf)
+            .map_err(Error::ReadEventFailure)?;
+
+        // Safe because libvda must have written vda_event_t to the pipe.
+        let vda_event = unsafe { mem::transmute::<[u8; BUF_SIZE], bindings::vda_event_t>(buf) };
+
+        // Safe because `vda_event` is a value read from `self.pipe`.
+        unsafe { Event::new(vda_event) }
+    }
+
+    /// Sends a decode request for a bitstream buffer given as `fd`.
+    ///
+    /// `fd` will be closed by Chrome after decoding has occurred.
+    pub fn decode(
+        &self,
+        bitstream_id: i32,
+        fd: BufferFd,
+        offset: u32,
+        bytes_used: u32,
+    ) -> Result<()> {
+        // Safe because `session_ptr` is valid and a libvda's API is called properly.
+        let r = unsafe {
+            bindings::vda_decode(
+                (*self.session_ptr).ctx,
+                bitstream_id,
+                fd,
+                offset,
+                bytes_used,
+            )
+        };
+        Response::new(r).into()
+    }
+
+    /// Sets the number of expected output buffers.
+    ///
+    /// This function must be called after `Event::ProvidePictureBuffers` are notified.
+    /// After calling this function, `user_output_buffer` must be called `num_output_buffers` times.
+    pub fn set_output_buffer_count(&self, num_output_buffers: usize) -> Result<()> {
+        // Safe because `session_ptr` is valid and a libvda's API is called properly.
+        let r = unsafe {
+            bindings::vda_set_output_buffer_count((*self.session_ptr).ctx, num_output_buffers)
+        };
+        Response::new(r).into()
+    }
+
+    /// Provides an output buffer that will be filled with decoded frames.
+    ///
+    /// Users calls this function after `set_output_buffer_count`. Then, libvda
+    /// will fill next frames in the buffer and noitify `Event::PictureReady`.
+    ///
+    /// This function is also used to notify that they consumed decoded frames
+    /// in the output buffer.
+    ///
+    /// This function takes ownership of `output_buffer`.
+    pub fn use_output_buffer(
+        &self,
+        picture_buffer_id: i32,
+        format: PixelFormat,
+        output_buffer: BufferFd,
+        planes: &[FramePlane],
+        modifier: u64,
+    ) -> Result<()> {
+        let mut planes: Vec<_> = planes.iter().map(FramePlane::to_raw_frame_plane).collect();
+
+        // Safe because `session_ptr` is valid and a libvda's API is called properly.
+        let r = unsafe {
+            bindings::vda_use_output_buffer(
+                (*self.session_ptr).ctx,
+                picture_buffer_id,
+                format.to_raw_pixel_format(),
+                output_buffer,
+                planes.len(),
+                planes.as_mut_ptr(),
+                modifier,
+            )
+        };
+        Response::new(r).into()
+    }
+
+    /// Returns an output buffer for reuse.
+    ///
+    /// `picture_buffer_id` must be a value for which `use_output_buffer` has been called already.
+    pub fn reuse_output_buffer(&self, picture_buffer_id: i32) -> Result<()> {
+        // Safe because `session_ptr` is valid and a libvda's API is called properly.
+        let r = unsafe {
+            bindings::vda_reuse_output_buffer((*self.session_ptr).ctx, picture_buffer_id)
+        };
+        Response::new(r).into()
+    }
+
+    /// Flushes the decode session.
+    ///
+    /// When this operation has completed, `Event::FlushResponse` will be notified.
+    pub fn flush(&self) -> Result<()> {
+        // Safe because `session_ptr` is valid and a libvda's API is called properly.
+        let r = unsafe { bindings::vda_flush((*self.session_ptr).ctx) };
+        Response::new(r).into()
+    }
+
+    /// Resets the decode session.
+    ///
+    /// When this operation has completed, Event::ResetResponse will be notified.
+    pub fn reset(&self) -> Result<()> {
+        // Safe because `session_ptr` is valid and a libvda's API is called properly.
+        let r = unsafe { bindings::vda_reset((*self.session_ptr).ctx) };
+        Response::new(r).into()
+    }
+}
+
+impl Drop for Session {
+    fn drop(&mut self) {
+        // Safe because `session_ptr` is unchanged from the time `new` was called, and
+        // `connection` also guarantees that the pointer returned by `conn_ptr()` is a valid
+        // connection to a VDA instance.
+        unsafe {
+            bindings::close_decode_session(self.connection.conn_ptr(), self.session_ptr);
+        }
+    }
+}
diff --git a/libvda/src/decode/vda_instance.rs b/libvda/src/decode/vda_instance.rs
new file mode 100644
index 0000000..23765f2
--- /dev/null
+++ b/libvda/src/decode/vda_instance.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.
+
+//! This module provides type safe interfaces for each operation exposed by Chrome's
+//! VideoDecodeAccelerator.
+
+use std::{os::raw::c_void, rc::Rc};
+
+use super::bindings;
+use super::format::*;
+use super::session::*;
+use crate::error::*;
+use crate::format::*;
+
+/// Represents a backend implementation of libvda.
+pub enum VdaImplType {
+    Fake,
+    Gavda, // GpuArcVideoDecodeAccelerator
+}
+
+/// Represents decoding capabilities of libvda instances.
+pub struct Capabilities {
+    pub input_formats: Vec<InputFormat>,
+    pub output_formats: Vec<PixelFormat>,
+}
+
+// An active connection to the VDA, which closes automatically as it is dropped.
+pub struct VdaConnection {
+    // `conn_ptr` must be a valid pointer obtained from `decode_bindings::initialize`.
+    conn_ptr: *mut c_void,
+}
+
+impl VdaConnection {
+    fn new(typ: VdaImplType) -> Result<Self> {
+        let impl_type = match typ {
+            VdaImplType::Fake => bindings::vda_impl_type_FAKE,
+            VdaImplType::Gavda => bindings::vda_impl_type_GAVDA,
+        };
+
+        // Safe because libvda's API is called properly.
+        match unsafe { bindings::initialize(impl_type) } {
+            ptr if ptr.is_null() => Err(Error::InstanceInitFailure),
+            conn_ptr => Ok(VdaConnection { conn_ptr }),
+        }
+    }
+
+    // Returns the raw pointer to the VDA connection instance that can be passed
+    // to bindings functions that require it.
+    pub(super) fn conn_ptr(&self) -> *mut c_void {
+        self.conn_ptr
+    }
+}
+
+impl Drop for VdaConnection {
+    fn drop(&mut self) {
+        // Safe because libvda's API is called properly.
+        unsafe { bindings::deinitialize(self.conn_ptr) }
+    }
+}
+
+/// Represents a libvda instance.
+pub struct VdaInstance {
+    connection: Rc<VdaConnection>,
+    caps: Capabilities,
+}
+
+impl VdaInstance {
+    /// Creates VdaInstance. `typ` specifies which backend will be used.
+    pub fn new(typ: VdaImplType) -> Result<Self> {
+        let connection = VdaConnection::new(typ)?;
+
+        // Get available input/output formats.
+        // Safe because `conn_ptr` is valid and `get_vda_capabilities()` won't invalidate it.
+        let vda_cap_ptr = unsafe { bindings::get_vda_capabilities(connection.conn_ptr) };
+        if vda_cap_ptr.is_null() {
+            return Err(Error::GetCapabilitiesFailure);
+        }
+        // Safe because `vda_cap_ptr` is not NULL.
+        let vda_cap = unsafe { *vda_cap_ptr };
+
+        // Safe because `input_formats` is valid for |`num_input_formats`| elements if both are valid.
+        let input_formats = unsafe {
+            InputFormat::from_raw_parts(vda_cap.input_formats, vda_cap.num_input_formats)?
+        };
+
+        // Output formats
+        // Safe because `output_formats` is valid for |`num_output_formats`| elements if both are valid.
+        let output_formats = unsafe {
+            PixelFormat::from_raw_parts(vda_cap.output_formats, vda_cap.num_output_formats)?
+        };
+
+        Ok(VdaInstance {
+            connection: Rc::new(connection),
+            caps: Capabilities {
+                input_formats,
+                output_formats,
+            },
+        })
+    }
+
+    /// Get media capabilities.
+    pub fn get_capabilities(&self) -> &Capabilities {
+        &self.caps
+    }
+
+    /// Opens a new `Session` for a given `Profile`.
+    pub fn open_session(&self, profile: Profile) -> Result<Session> {
+        Session::new(&self.connection, profile).ok_or(Error::SessionInitFailure(profile))
+    }
+}
diff --git a/libvda/src/encode/bindings.rs b/libvda/src/encode/bindings.rs
new file mode 100644
index 0000000..8f1198f
--- /dev/null
+++ b/libvda/src/encode/bindings.rs
@@ -0,0 +1,741 @@
+// 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.
+
+/*
+automatically generated by rust-bindgen
+
+generated with the command:
+cd ${CHROMEOS_DIR}/src/platform2/ && \
+bindgen arc/vm/libvda/libvda_encode.h \
+  -o ../platform/crosvm/libvda/src/encode/bindings.rs \
+  --raw-line 'pub use crate::bindings::*;' \
+  --whitelist-function "initialize_encode" \
+  --whitelist-function "deinitialize_encode" \
+  --whitelist-function "get_vea_capabilities" \
+  --whitelist-function "init_encode_session" \
+  --whitelist-function "close_encode_session" \
+  --whitelist-function "vea_.*" \
+  --whitelist-type "vea_.*" \
+  --blacklist-type "video_.*" \
+  -- \
+  -I .
+ */
+
+#![allow(non_camel_case_types)]
+#![allow(non_upper_case_globals)]
+#![allow(dead_code)]
+
+pub use crate::bindings::*;
+
+pub type __uint8_t = ::std::os::raw::c_uchar;
+pub type __int32_t = ::std::os::raw::c_int;
+pub type __uint32_t = ::std::os::raw::c_uint;
+pub type __int64_t = ::std::os::raw::c_long;
+pub const vea_impl_type_VEA_FAKE: vea_impl_type = 0;
+pub const vea_impl_type_GAVEA: vea_impl_type = 1;
+pub type vea_impl_type = u32;
+pub use self::vea_impl_type as vea_impl_type_t;
+#[repr(C)]
+pub struct vea_profile {
+    pub profile: video_codec_profile_t,
+    pub max_width: u32,
+    pub max_height: u32,
+    pub max_framerate_numerator: u32,
+    pub max_framerate_denominator: u32,
+}
+#[test]
+fn bindgen_test_layout_vea_profile() {
+    assert_eq!(
+        ::std::mem::size_of::<vea_profile>(),
+        20usize,
+        concat!("Size of: ", stringify!(vea_profile))
+    );
+    assert_eq!(
+        ::std::mem::align_of::<vea_profile>(),
+        4usize,
+        concat!("Alignment of ", stringify!(vea_profile))
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<vea_profile>())).profile as *const _ as usize },
+        0usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vea_profile),
+            "::",
+            stringify!(profile)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<vea_profile>())).max_width as *const _ as usize },
+        4usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vea_profile),
+            "::",
+            stringify!(max_width)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<vea_profile>())).max_height as *const _ as usize },
+        8usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vea_profile),
+            "::",
+            stringify!(max_height)
+        )
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<vea_profile>())).max_framerate_numerator as *const _ as usize
+        },
+        12usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vea_profile),
+            "::",
+            stringify!(max_framerate_numerator)
+        )
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<vea_profile>())).max_framerate_denominator as *const _ as usize
+        },
+        16usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vea_profile),
+            "::",
+            stringify!(max_framerate_denominator)
+        )
+    );
+}
+pub type vea_profile_t = vea_profile;
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct vea_capabilities {
+    pub num_input_formats: usize,
+    pub input_formats: *const video_pixel_format_t,
+    pub num_output_formats: usize,
+    pub output_formats: *const vea_profile_t,
+}
+#[test]
+fn bindgen_test_layout_vea_capabilities() {
+    assert_eq!(
+        ::std::mem::size_of::<vea_capabilities>(),
+        32usize,
+        concat!("Size of: ", stringify!(vea_capabilities))
+    );
+    assert_eq!(
+        ::std::mem::align_of::<vea_capabilities>(),
+        8usize,
+        concat!("Alignment of ", stringify!(vea_capabilities))
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<vea_capabilities>())).num_input_formats as *const _ as usize
+        },
+        0usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vea_capabilities),
+            "::",
+            stringify!(num_input_formats)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<vea_capabilities>())).input_formats as *const _ as usize },
+        8usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vea_capabilities),
+            "::",
+            stringify!(input_formats)
+        )
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<vea_capabilities>())).num_output_formats as *const _ as usize
+        },
+        16usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vea_capabilities),
+            "::",
+            stringify!(num_output_formats)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<vea_capabilities>())).output_formats as *const _ as usize },
+        24usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vea_capabilities),
+            "::",
+            stringify!(output_formats)
+        )
+    );
+}
+pub type vea_capabilities_t = vea_capabilities;
+pub const vea_bitrate_mode_VBR: vea_bitrate_mode = 0;
+pub const vea_bitrate_mode_CBR: vea_bitrate_mode = 1;
+pub type vea_bitrate_mode = u32;
+pub use self::vea_bitrate_mode as vea_bitrate_mode_t;
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct vea_bitrate {
+    pub mode: vea_bitrate_mode_t,
+    pub target: u32,
+    pub peak: u32,
+}
+#[test]
+fn bindgen_test_layout_vea_bitrate() {
+    assert_eq!(
+        ::std::mem::size_of::<vea_bitrate>(),
+        12usize,
+        concat!("Size of: ", stringify!(vea_bitrate))
+    );
+    assert_eq!(
+        ::std::mem::align_of::<vea_bitrate>(),
+        4usize,
+        concat!("Alignment of ", stringify!(vea_bitrate))
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<vea_bitrate>())).mode as *const _ as usize },
+        0usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vea_bitrate),
+            "::",
+            stringify!(mode)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<vea_bitrate>())).target as *const _ as usize },
+        4usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vea_bitrate),
+            "::",
+            stringify!(target)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<vea_bitrate>())).peak as *const _ as usize },
+        8usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vea_bitrate),
+            "::",
+            stringify!(peak)
+        )
+    );
+}
+pub type vea_bitrate_t = vea_bitrate;
+#[repr(C)]
+pub struct vea_config {
+    pub input_format: video_pixel_format_t,
+    pub input_visible_width: u32,
+    pub input_visible_height: u32,
+    pub output_profile: video_codec_profile_t,
+    pub bitrate: vea_bitrate_t,
+    pub initial_framerate: u32,
+    pub has_initial_framerate: u8,
+    pub h264_output_level: u8,
+    pub has_h264_output_level: u8,
+}
+#[test]
+fn bindgen_test_layout_vea_config() {
+    assert_eq!(
+        ::std::mem::size_of::<vea_config>(),
+        36usize,
+        concat!("Size of: ", stringify!(vea_config))
+    );
+    assert_eq!(
+        ::std::mem::align_of::<vea_config>(),
+        4usize,
+        concat!("Alignment of ", stringify!(vea_config))
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<vea_config>())).input_format as *const _ as usize },
+        0usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vea_config),
+            "::",
+            stringify!(input_format)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<vea_config>())).input_visible_width as *const _ as usize },
+        4usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vea_config),
+            "::",
+            stringify!(input_visible_width)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<vea_config>())).input_visible_height as *const _ as usize },
+        8usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vea_config),
+            "::",
+            stringify!(input_visible_height)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<vea_config>())).output_profile as *const _ as usize },
+        12usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vea_config),
+            "::",
+            stringify!(output_profile)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<vea_config>())).bitrate as *const _ as usize },
+        16usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vea_config),
+            "::",
+            stringify!(bitrate)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<vea_config>())).initial_framerate as *const _ as usize },
+        28usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vea_config),
+            "::",
+            stringify!(initial_framerate)
+        )
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<vea_config>())).has_initial_framerate as *const _ as usize
+        },
+        32usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vea_config),
+            "::",
+            stringify!(has_initial_framerate)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<vea_config>())).h264_output_level as *const _ as usize },
+        33usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vea_config),
+            "::",
+            stringify!(h264_output_level)
+        )
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<vea_config>())).has_h264_output_level as *const _ as usize
+        },
+        34usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vea_config),
+            "::",
+            stringify!(has_h264_output_level)
+        )
+    );
+}
+pub type vea_config_t = vea_config;
+pub const vea_error_ILLEGAL_STATE_ERROR: vea_error = 0;
+pub const vea_error_INVALID_ARGUMENT_ERROR: vea_error = 1;
+pub const vea_error_PLATFORM_FAILURE_ERROR: vea_error = 2;
+pub type vea_error = u32;
+pub use self::vea_error as vea_error_t;
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct vea_session_info {
+    pub ctx: *mut ::std::os::raw::c_void,
+    pub event_pipe_fd: ::std::os::raw::c_int,
+}
+#[test]
+fn bindgen_test_layout_vea_session_info() {
+    assert_eq!(
+        ::std::mem::size_of::<vea_session_info>(),
+        16usize,
+        concat!("Size of: ", stringify!(vea_session_info))
+    );
+    assert_eq!(
+        ::std::mem::align_of::<vea_session_info>(),
+        8usize,
+        concat!("Alignment of ", stringify!(vea_session_info))
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<vea_session_info>())).ctx as *const _ as usize },
+        0usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vea_session_info),
+            "::",
+            stringify!(ctx)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<vea_session_info>())).event_pipe_fd as *const _ as usize },
+        8usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vea_session_info),
+            "::",
+            stringify!(event_pipe_fd)
+        )
+    );
+}
+pub type vea_session_info_t = vea_session_info;
+pub type vea_input_buffer_id_t = i32;
+pub type vea_output_buffer_id_t = i32;
+pub const vea_event_type_REQUIRE_INPUT_BUFFERS: vea_event_type = 0;
+pub const vea_event_type_PROCESSED_INPUT_BUFFER: vea_event_type = 1;
+pub const vea_event_type_PROCESSED_OUTPUT_BUFFER: vea_event_type = 2;
+pub const vea_event_type_VEA_FLUSH_RESPONSE: vea_event_type = 3;
+pub const vea_event_type_VEA_NOTIFY_ERROR: vea_event_type = 4;
+pub type vea_event_type = u32;
+pub use self::vea_event_type as vea_event_type_t;
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct vea_require_input_buffers_event_data {
+    pub input_count: u32,
+    pub input_frame_width: u32,
+    pub input_frame_height: u32,
+    pub output_buffer_size: u32,
+}
+#[test]
+fn bindgen_test_layout_vea_require_input_buffers_event_data() {
+    assert_eq!(
+        ::std::mem::size_of::<vea_require_input_buffers_event_data>(),
+        16usize,
+        concat!(
+            "Size of: ",
+            stringify!(vea_require_input_buffers_event_data)
+        )
+    );
+    assert_eq!(
+        ::std::mem::align_of::<vea_require_input_buffers_event_data>(),
+        4usize,
+        concat!(
+            "Alignment of ",
+            stringify!(vea_require_input_buffers_event_data)
+        )
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<vea_require_input_buffers_event_data>())).input_count as *const _
+                as usize
+        },
+        0usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vea_require_input_buffers_event_data),
+            "::",
+            stringify!(input_count)
+        )
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<vea_require_input_buffers_event_data>())).input_frame_width
+                as *const _ as usize
+        },
+        4usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vea_require_input_buffers_event_data),
+            "::",
+            stringify!(input_frame_width)
+        )
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<vea_require_input_buffers_event_data>())).input_frame_height
+                as *const _ as usize
+        },
+        8usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vea_require_input_buffers_event_data),
+            "::",
+            stringify!(input_frame_height)
+        )
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<vea_require_input_buffers_event_data>())).output_buffer_size
+                as *const _ as usize
+        },
+        12usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vea_require_input_buffers_event_data),
+            "::",
+            stringify!(output_buffer_size)
+        )
+    );
+}
+pub type vea_require_input_buffers_event_data_t = vea_require_input_buffers_event_data;
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct vea_processed_output_buffer_event_data {
+    pub output_buffer_id: vea_output_buffer_id_t,
+    pub payload_size: u32,
+    pub key_frame: u8,
+    pub timestamp: i64,
+}
+#[test]
+fn bindgen_test_layout_vea_processed_output_buffer_event_data() {
+    assert_eq!(
+        ::std::mem::size_of::<vea_processed_output_buffer_event_data>(),
+        24usize,
+        concat!(
+            "Size of: ",
+            stringify!(vea_processed_output_buffer_event_data)
+        )
+    );
+    assert_eq!(
+        ::std::mem::align_of::<vea_processed_output_buffer_event_data>(),
+        8usize,
+        concat!(
+            "Alignment of ",
+            stringify!(vea_processed_output_buffer_event_data)
+        )
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<vea_processed_output_buffer_event_data>())).output_buffer_id
+                as *const _ as usize
+        },
+        0usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vea_processed_output_buffer_event_data),
+            "::",
+            stringify!(output_buffer_id)
+        )
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<vea_processed_output_buffer_event_data>())).payload_size
+                as *const _ as usize
+        },
+        4usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vea_processed_output_buffer_event_data),
+            "::",
+            stringify!(payload_size)
+        )
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<vea_processed_output_buffer_event_data>())).key_frame as *const _
+                as usize
+        },
+        8usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vea_processed_output_buffer_event_data),
+            "::",
+            stringify!(key_frame)
+        )
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<vea_processed_output_buffer_event_data>())).timestamp as *const _
+                as usize
+        },
+        16usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vea_processed_output_buffer_event_data),
+            "::",
+            stringify!(timestamp)
+        )
+    );
+}
+pub type vea_processed_output_buffer_event_data_t = vea_processed_output_buffer_event_data;
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub union vea_event_data {
+    pub require_input_buffers: vea_require_input_buffers_event_data_t,
+    pub processed_input_buffer_id: vea_input_buffer_id_t,
+    pub processed_output_buffer: vea_processed_output_buffer_event_data_t,
+    pub flush_done: u8,
+    pub error: vea_error_t,
+    _bindgen_union_align: [u64; 3usize],
+}
+#[test]
+fn bindgen_test_layout_vea_event_data() {
+    assert_eq!(
+        ::std::mem::size_of::<vea_event_data>(),
+        24usize,
+        concat!("Size of: ", stringify!(vea_event_data))
+    );
+    assert_eq!(
+        ::std::mem::align_of::<vea_event_data>(),
+        8usize,
+        concat!("Alignment of ", stringify!(vea_event_data))
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<vea_event_data>())).require_input_buffers as *const _ as usize
+        },
+        0usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vea_event_data),
+            "::",
+            stringify!(require_input_buffers)
+        )
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<vea_event_data>())).processed_input_buffer_id as *const _
+                as usize
+        },
+        0usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vea_event_data),
+            "::",
+            stringify!(processed_input_buffer_id)
+        )
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<vea_event_data>())).processed_output_buffer as *const _ as usize
+        },
+        0usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vea_event_data),
+            "::",
+            stringify!(processed_output_buffer)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<vea_event_data>())).flush_done as *const _ as usize },
+        0usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vea_event_data),
+            "::",
+            stringify!(flush_done)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<vea_event_data>())).error as *const _ as usize },
+        0usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vea_event_data),
+            "::",
+            stringify!(error)
+        )
+    );
+}
+pub type vea_event_data_t = vea_event_data;
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct vea_event {
+    pub event_type: vea_event_type_t,
+    pub event_data: vea_event_data_t,
+}
+#[test]
+fn bindgen_test_layout_vea_event() {
+    assert_eq!(
+        ::std::mem::size_of::<vea_event>(),
+        32usize,
+        concat!("Size of: ", stringify!(vea_event))
+    );
+    assert_eq!(
+        ::std::mem::align_of::<vea_event>(),
+        8usize,
+        concat!("Alignment of ", stringify!(vea_event))
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<vea_event>())).event_type as *const _ as usize },
+        0usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vea_event),
+            "::",
+            stringify!(event_type)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<vea_event>())).event_data as *const _ as usize },
+        8usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(vea_event),
+            "::",
+            stringify!(event_data)
+        )
+    );
+}
+pub type vea_event_t = vea_event;
+extern "C" {
+    pub fn initialize_encode(type_: vea_impl_type_t) -> *mut ::std::os::raw::c_void;
+}
+extern "C" {
+    pub fn deinitialize_encode(impl_: *mut ::std::os::raw::c_void);
+}
+extern "C" {
+    pub fn get_vea_capabilities(impl_: *mut ::std::os::raw::c_void) -> *const vea_capabilities_t;
+}
+extern "C" {
+    pub fn init_encode_session(
+        impl_: *mut ::std::os::raw::c_void,
+        config: *mut vea_config_t,
+    ) -> *mut vea_session_info_t;
+}
+extern "C" {
+    pub fn close_encode_session(
+        impl_: *mut ::std::os::raw::c_void,
+        session_info: *mut vea_session_info_t,
+    );
+}
+extern "C" {
+    pub fn vea_encode(
+        ctx: *mut ::std::os::raw::c_void,
+        input_buffer_id: vea_input_buffer_id_t,
+        fd: ::std::os::raw::c_int,
+        num_planes: usize,
+        planes: *mut video_frame_plane_t,
+        timestamp: i64,
+        force_keyframe: u8,
+    ) -> ::std::os::raw::c_int;
+}
+extern "C" {
+    pub fn vea_use_output_buffer(
+        ctx: *mut ::std::os::raw::c_void,
+        output_buffer_id: vea_output_buffer_id_t,
+        fd: ::std::os::raw::c_int,
+        offset: u32,
+        size: u32,
+    ) -> ::std::os::raw::c_int;
+}
+extern "C" {
+    pub fn vea_request_encoding_params_change(
+        ctx: *mut ::std::os::raw::c_void,
+        bitrate: vea_bitrate_t,
+        framerate: u32,
+    ) -> ::std::os::raw::c_int;
+}
+extern "C" {
+    pub fn vea_flush(ctx: *mut ::std::os::raw::c_void) -> ::std::os::raw::c_int;
+}
diff --git a/libvda/src/encode/event.rs b/libvda/src/encode/event.rs
new file mode 100644
index 0000000..93c8aa1
--- /dev/null
+++ b/libvda/src/encode/event.rs
@@ -0,0 +1,110 @@
+// 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 reported by VDA encode API over pipe FD.
+
+use enumn::N;
+use std::error;
+use std::fmt::{self, Display};
+
+use super::bindings;
+use super::session::{VeaInputBufferId, VeaOutputBufferId};
+use crate::error::*;
+
+/// Represents an error from a libvda encode session.
+#[derive(Debug, Clone, Copy, N)]
+#[repr(u32)]
+pub enum VeaError {
+    IllegalState = bindings::vea_error_ILLEGAL_STATE_ERROR,
+    InvalidArgument = bindings::vea_error_INVALID_ARGUMENT_ERROR,
+    PlatformFailure = bindings::vea_error_PLATFORM_FAILURE_ERROR,
+}
+
+impl error::Error for VeaError {}
+
+impl VeaError {
+    pub(crate) fn new(res: bindings::vea_error_t) -> VeaError {
+        VeaError::n(res).unwrap_or_else(|| panic!("Unknown error is reported from VEA: {}", res))
+    }
+}
+
+impl Display for VeaError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        use self::VeaError::*;
+        match self {
+            IllegalState => write!(f, "illegal state"),
+            InvalidArgument => write!(f, "invalid argument"),
+            PlatformFailure => write!(f, "platform failure"),
+        }
+    }
+}
+
+/// Represents a notified event from libvda.
+#[derive(Debug)]
+pub enum Event {
+    /// Requests the user to provide input buffers.
+    RequireInputBuffers {
+        input_count: u32,
+        input_frame_width: u32,
+        input_frame_height: u32,
+        output_buffer_size: u32,
+    },
+    /// Notifies the user that an input buffer has been processed.
+    ProcessedInputBuffer(VeaInputBufferId),
+    /// Notifies the user that an output buffer has been processed.
+    ProcessedOutputBuffer {
+        output_buffer_id: VeaOutputBufferId,
+        payload_size: u32,
+        key_frame: bool,
+        timestamp: i64,
+    },
+    /// Notifies the result of operation issued by `Session::flush`.
+    FlushResponse { flush_done: bool },
+    /// Notifies the user of an error.
+    NotifyError(VeaError),
+}
+
+impl Event {
+    /// Creates a new `Event` from a `vea_event_t` instance.
+    /// This function is safe if `event` was a value read from libvda's pipe.
+    pub(crate) unsafe fn new(event: bindings::vea_event_t) -> Result<Self> {
+        use self::Event::*;
+
+        let bindings::vea_event_t {
+            event_data,
+            event_type,
+        } = event;
+
+        match event_type {
+            bindings::vea_event_type_REQUIRE_INPUT_BUFFERS => {
+                let d = event_data.require_input_buffers;
+                Ok(RequireInputBuffers {
+                    input_count: d.input_count,
+                    input_frame_width: d.input_frame_width,
+                    input_frame_height: d.input_frame_height,
+                    output_buffer_size: d.output_buffer_size,
+                })
+            }
+            bindings::vea_event_type_PROCESSED_INPUT_BUFFER => {
+                Ok(ProcessedInputBuffer(event_data.processed_input_buffer_id))
+            }
+            bindings::vea_event_type_PROCESSED_OUTPUT_BUFFER => {
+                let d = event_data.processed_output_buffer;
+                Ok(ProcessedOutputBuffer {
+                    output_buffer_id: d.output_buffer_id,
+                    payload_size: d.payload_size,
+                    key_frame: d.key_frame == 1,
+                    timestamp: d.timestamp,
+                })
+            }
+            bindings::vea_event_type_VEA_FLUSH_RESPONSE => Ok(FlushResponse {
+                flush_done: event_data.flush_done == 1,
+            }),
+            bindings::vea_event_type_VEA_NOTIFY_ERROR => {
+                Ok(NotifyError(VeaError::new(event_data.error)))
+            }
+            t => panic!("Unknown event is reported from VEA: {}", t),
+        }
+    }
+}
diff --git a/libvda/src/encode/format.rs b/libvda/src/encode/format.rs
new file mode 100644
index 0000000..7b802f1
--- /dev/null
+++ b/libvda/src/encode/format.rs
@@ -0,0 +1,66 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use super::bindings;
+use crate::error::Result;
+use crate::format::*;
+use enumn::N;
+
+/// Represents an output profile for VEA.
+#[derive(Debug, Clone, Copy)]
+pub struct OutputProfile {
+    pub profile: Profile,
+    pub max_width: u32,
+    pub max_height: u32,
+    pub max_framerate_numerator: u32,
+    pub max_framerate_denominator: u32,
+}
+
+impl OutputProfile {
+    pub(crate) fn new(p: &bindings::vea_profile_t) -> Result<Self> {
+        Ok(Self {
+            profile: Profile::new(p.profile)?,
+            max_width: p.max_width,
+            max_height: p.max_height,
+            max_framerate_numerator: p.max_framerate_numerator,
+            max_framerate_denominator: p.max_framerate_denominator,
+        })
+    }
+
+    pub(crate) unsafe fn from_raw_parts(
+        data: *const bindings::vea_profile_t,
+        len: usize,
+    ) -> Result<Vec<Self>> {
+        validate_formats(data, len, Self::new)
+    }
+}
+
+/// Represents a bitrate mode for the VEA.
+#[derive(Debug, Clone, Copy, N)]
+#[repr(u32)]
+pub enum BitrateMode {
+    VBR = bindings::vea_bitrate_mode_VBR,
+    CBR = bindings::vea_bitrate_mode_CBR,
+}
+
+/// Represents a bitrate for the VEA.
+#[derive(Debug, Clone, Copy)]
+pub struct Bitrate {
+    pub mode: BitrateMode,
+    pub target: u32,
+    pub peak: u32,
+}
+
+impl Bitrate {
+    pub fn to_raw_bitrate(&self) -> bindings::vea_bitrate_t {
+        bindings::vea_bitrate_t {
+            mode: match self.mode {
+                BitrateMode::VBR => bindings::vea_bitrate_mode_VBR,
+                BitrateMode::CBR => bindings::vea_bitrate_mode_CBR,
+            },
+            target: self.target,
+            peak: self.peak,
+        }
+    }
+}
diff --git a/libvda/src/encode/mod.rs b/libvda/src/encode/mod.rs
new file mode 100644
index 0000000..ddb9f47
--- /dev/null
+++ b/libvda/src/encode/mod.rs
@@ -0,0 +1,14 @@
+// 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.
+
+mod bindings;
+mod event;
+mod format;
+mod session;
+mod vea_instance;
+
+pub use event::*;
+pub use format::*;
+pub use session::*;
+pub use vea_instance::*;
diff --git a/libvda/src/encode/session.rs b/libvda/src/encode/session.rs
new file mode 100644
index 0000000..7e5168a
--- /dev/null
+++ b/libvda/src/encode/session.rs
@@ -0,0 +1,186 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::io::Read;
+use std::mem;
+use std::os::unix::io::FromRawFd;
+use std::{fs::File, rc::Rc};
+
+use super::event::*;
+use super::format::Bitrate;
+use super::vea_instance::Config;
+use super::{bindings, VeaConnection};
+use crate::error::*;
+use crate::format::{BufferFd, FramePlane};
+
+pub type VeaInputBufferId = bindings::vea_input_buffer_id_t;
+pub type VeaOutputBufferId = bindings::vea_output_buffer_id_t;
+
+/// Represents an encode session.
+pub struct Session {
+    // Pipe file to be notified encode session events.
+    pipe: File,
+    // Ensures the VEA connection remains open for as long as there are active sessions.
+    connection: Rc<VeaConnection>,
+    session_ptr: *mut bindings::vea_session_info_t,
+}
+
+fn convert_error_code(code: i32) -> Result<()> {
+    if code == 0 {
+        Ok(())
+    } else {
+        Err(Error::EncodeSessionFailure(code))
+    }
+}
+
+impl Session {
+    /// Creates a new `Session`.
+    pub(super) fn new(connection: &Rc<VeaConnection>, config: Config) -> Option<Self> {
+        // Safe because `conn_ptr()` is valid and won't be invalidated by `init_encode_session()`.
+        let session_ptr: *mut bindings::vea_session_info_t = unsafe {
+            bindings::init_encode_session(connection.conn_ptr(), &mut config.to_raw_config())
+        };
+
+        if session_ptr.is_null() {
+            return None;
+        }
+
+        // Dereferencing `session_ptr` is safe because it is a valid pointer to a FD provided by
+        // libvda. We need to dup() the `event_pipe_fd` because File object close() the FD while
+        // libvda also close() it when `close_encode_session` is called.
+        // Calling `from_raw_fd` here is safe because the dup'ed FD is not going to be used by
+        // anything else and `pipe` has full ownership of it.
+        let pipe = unsafe { File::from_raw_fd(libc::dup((*session_ptr).event_pipe_fd)) };
+
+        Some(Session {
+            connection: Rc::clone(connection),
+            pipe,
+            session_ptr,
+        })
+    }
+
+    /// Returns a reference for the pipe that notifies of encode events.
+    pub fn pipe(&self) -> &File {
+        &self.pipe
+    }
+
+    /// Reads an `Event` object from a pipe provided by an encode session.
+    pub fn read_event(&mut self) -> Result<Event> {
+        const BUF_SIZE: usize = mem::size_of::<bindings::vea_event_t>();
+        let mut buf = [0u8; BUF_SIZE];
+
+        self.pipe
+            .read_exact(&mut buf)
+            .map_err(Error::ReadEventFailure)?;
+
+        // Safe because libvda must have written vea_event_t to the pipe.
+        let vea_event = unsafe { mem::transmute::<[u8; BUF_SIZE], bindings::vea_event_t>(buf) };
+
+        // Safe because `vea_event` is a value read from `self.pipe`.
+        unsafe { Event::new(vea_event) }
+    }
+
+    /// Sends an encode request for an input buffer given as `fd` with planes described
+    /// by `planes. The timestamp of the frame to encode is typically provided in
+    /// milliseconds by `timestamp`. `force_keyframe` indicates to the encoder that
+    /// the frame should be encoded as a keyframe.
+    ///
+    /// When the input buffer has been filled, an `EncoderEvent::ProcessedInputBuffer`
+    /// event can be read from the event pipe.
+    ///
+    /// The caller is responsible for passing in a unique value for `input_buffer_id`
+    /// which can be referenced when the event is received.
+    ///
+    /// `fd` will be closed after encoding has occurred.
+    pub fn encode(
+        &self,
+        input_buffer_id: VeaInputBufferId,
+        fd: BufferFd,
+        planes: &[FramePlane],
+        timestamp: i64,
+        force_keyframe: bool,
+    ) -> Result<()> {
+        let mut planes: Vec<_> = planes.iter().map(FramePlane::to_raw_frame_plane).collect();
+
+        // Safe because `session_ptr` is valid and libvda's encode API is called properly.
+        let r = unsafe {
+            bindings::vea_encode(
+                (*self.session_ptr).ctx,
+                input_buffer_id,
+                fd,
+                planes.len(),
+                planes.as_mut_ptr(),
+                timestamp,
+                if force_keyframe { 1 } else { 0 },
+            )
+        };
+        convert_error_code(r)
+    }
+
+    /// Provides a buffer for storing encoded output.
+    ///
+    /// When the output buffer has been filled, an `EncoderEvent::ProcessedOutputBuffer`
+    /// event can be read from the event pipe.
+    ///
+    /// The caller is responsible for passing in a unique value for `output_buffer_id`
+    /// which can be referenced when the event is received.
+    ///
+    /// This function takes ownership of `fd`.
+    pub fn use_output_buffer(
+        &self,
+        output_buffer_id: VeaOutputBufferId,
+        fd: BufferFd,
+        offset: u32,
+        size: u32,
+    ) -> Result<()> {
+        // Safe because `session_ptr` is valid and libvda's encode API is called properly.
+        let r = unsafe {
+            bindings::vea_use_output_buffer(
+                (*self.session_ptr).ctx,
+                output_buffer_id,
+                fd,
+                offset,
+                size,
+            )
+        };
+        convert_error_code(r)
+    }
+
+    /// Requests encoding parameter changes.
+    ///
+    /// The request is not guaranteed to be honored by libvda and could be ignored
+    /// by the backing encoder implementation.
+    pub fn request_encoding_params_change(&self, bitrate: Bitrate, framerate: u32) -> Result<()> {
+        // Safe because `session_ptr` is valid and libvda's encode API is called properly.
+        let r = unsafe {
+            bindings::vea_request_encoding_params_change(
+                (*self.session_ptr).ctx,
+                bitrate.to_raw_bitrate(),
+                framerate,
+            )
+        };
+        convert_error_code(r)
+    }
+
+    /// Flushes the encode session.
+    ///
+    /// When this operation has completed, Event::FlushResponse can be read from
+    /// the event pipe.
+    pub fn flush(&self) -> Result<()> {
+        // Safe because `session_ptr` is valid and libvda's encode API is called properly.
+        let r = unsafe { bindings::vea_flush((*self.session_ptr).ctx) };
+        convert_error_code(r)
+    }
+}
+
+impl Drop for Session {
+    fn drop(&mut self) {
+        // Safe because `session_ptr` is unchanged from the time `new` was called, and
+        // `connection` also guarantees that the pointer returned by `conn_ptr()` is a valid
+        // connection to a VEA instance.
+        unsafe {
+            bindings::close_encode_session(self.connection.conn_ptr(), self.session_ptr);
+        }
+    }
+}
diff --git a/libvda/src/encode/vea_instance.rs b/libvda/src/encode/vea_instance.rs
new file mode 100644
index 0000000..c628e7a
--- /dev/null
+++ b/libvda/src/encode/vea_instance.rs
@@ -0,0 +1,156 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{os::raw::c_void, rc::Rc};
+
+use super::bindings;
+use super::format::{Bitrate, OutputProfile};
+use super::session::*;
+use crate::error::*;
+use crate::format::*;
+
+/// Represents a backend implementation of libvda.
+pub enum VeaImplType {
+    Fake,
+    Gavea, // GpuArcVideoEncoderAccelerator
+}
+
+/// Represents encoding capabilities of libvda encode instances.
+pub struct EncodeCapabilities {
+    pub input_formats: Vec<PixelFormat>,
+    pub output_formats: Vec<OutputProfile>,
+}
+
+pub struct VeaConnection {
+    // `conn_ptr` must be a valid pointer obtained from `decode_bindings::initialize_encode`.
+    conn_ptr: *mut c_void,
+}
+
+impl VeaConnection {
+    fn new(typ: VeaImplType) -> Result<Self> {
+        let impl_type = match typ {
+            VeaImplType::Fake => bindings::vea_impl_type_VEA_FAKE,
+            VeaImplType::Gavea => bindings::vea_impl_type_GAVEA,
+        };
+
+        // Safe because libvda's API is called properly.
+        match unsafe { bindings::initialize_encode(impl_type) } {
+            ptr if ptr.is_null() => Err(Error::InstanceInitFailure),
+            conn_ptr => Ok(VeaConnection { conn_ptr }),
+        }
+    }
+
+    // Returns the raw pointer to the VEA connection instance that can be passed
+    // to bindings functions that require it.
+    pub(super) fn conn_ptr(&self) -> *mut c_void {
+        self.conn_ptr
+    }
+}
+
+impl Drop for VeaConnection {
+    fn drop(&mut self) {
+        // Safe because libvda's API is called properly.
+        unsafe { bindings::deinitialize_encode(self.conn_ptr) }
+    }
+}
+
+/// Represents a libvda encode instance.
+pub struct VeaInstance {
+    connection: Rc<VeaConnection>,
+    caps: EncodeCapabilities,
+}
+
+/// Represents an encoding configuration for a libvda encode session.
+#[derive(Debug, Clone, Copy)]
+pub struct Config {
+    pub input_format: PixelFormat,
+    pub input_visible_width: u32,
+    pub input_visible_height: u32,
+    pub output_profile: Profile,
+    pub bitrate: Bitrate,
+    pub initial_framerate: Option<u32>,
+    pub h264_output_level: Option<u8>,
+}
+
+impl Config {
+    pub(crate) fn to_raw_config(self) -> bindings::vea_config_t {
+        bindings::vea_config_t {
+            input_format: self.input_format.to_raw_pixel_format(),
+            input_visible_width: self.input_visible_width,
+            input_visible_height: self.input_visible_height,
+            output_profile: self.output_profile.to_raw_profile(),
+            bitrate: self.bitrate.to_raw_bitrate(),
+            initial_framerate: self.initial_framerate.unwrap_or(0),
+            has_initial_framerate: if self.initial_framerate.is_some() {
+                1
+            } else {
+                0
+            },
+            h264_output_level: self.h264_output_level.unwrap_or(0),
+            has_h264_output_level: if self.h264_output_level.is_some() {
+                1
+            } else {
+                0
+            },
+        }
+    }
+}
+
+impl VeaInstance {
+    /// Creates VeaInstance. `impl_type` specifies which backend will be used.
+    pub fn new(impl_type: VeaImplType) -> Result<Self> {
+        let connection = VeaConnection::new(impl_type)?;
+
+        // Get available input/output formats.
+        // Safe because libvda's API is called properly.
+        let vea_caps_ptr = unsafe { bindings::get_vea_capabilities(connection.conn_ptr()) };
+        if vea_caps_ptr.is_null() {
+            return Err(Error::GetCapabilitiesFailure);
+        }
+        // Safe because `vea_cap_ptr` is not NULL and provided by get_vea_capabilities().
+        let bindings::vea_capabilities_t {
+            num_input_formats,
+            input_formats,
+            num_output_formats,
+            output_formats,
+        } = unsafe { *vea_caps_ptr };
+
+        if num_input_formats == 0 || input_formats.is_null() {
+            return Err(Error::InvalidCapabilities(
+                "invalid input formats".to_string(),
+            ));
+        }
+        if num_output_formats == 0 || output_formats.is_null() {
+            return Err(Error::InvalidCapabilities(
+                "invalid output formats".to_string(),
+            ));
+        }
+
+        // Safe because `input_formats` is valid for |`num_input_formats`| elements.
+        let input_formats =
+            unsafe { PixelFormat::from_raw_parts(input_formats, num_input_formats)? };
+
+        // Safe because `output_formats` is valid for |`num_output_formats`| elements.
+        let output_formats =
+            unsafe { OutputProfile::from_raw_parts(output_formats, num_output_formats)? };
+
+        Ok(Self {
+            connection: Rc::new(connection),
+            caps: EncodeCapabilities {
+                input_formats,
+                output_formats,
+            },
+        })
+    }
+
+    /// Gets encoder capabilities.
+    pub fn get_capabilities(&self) -> &EncodeCapabilities {
+        &self.caps
+    }
+
+    /// Opens a new `Session` for a given `Config`.
+    pub fn open_session(&self, config: Config) -> Result<Session> {
+        Session::new(&self.connection, config).ok_or(Error::EncodeSessionInitFailure(config))
+    }
+}
diff --git a/libvda/src/error.rs b/libvda/src/error.rs
new file mode 100644
index 0000000..791ca39
--- /dev/null
+++ b/libvda/src/error.rs
@@ -0,0 +1,57 @@
+// 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 in LibVDA.
+
+use std::error;
+use std::fmt::{self, Display};
+
+use super::format;
+use crate::decode;
+use crate::encode;
+
+#[derive(Debug)]
+pub enum Error {
+    // Encode session error. The error code provided is specific
+    // to the implementation type (`VeaImplType`).
+    EncodeSessionFailure(i32),
+    EncodeSessionInitFailure(encode::Config),
+    GetCapabilitiesFailure,
+    InstanceInitFailure,
+    InvalidCapabilities(String),
+    LibVdaFailure(decode::Response),
+    ReadEventFailure(std::io::Error),
+    SessionIdAlreadyUsed(u32),
+    SessionInitFailure(format::Profile),
+    SessionNotFound(u32),
+    UnknownPixelFormat(u32),
+    UnknownProfile(i32),
+}
+
+pub type Result<T> = std::result::Result<T, Error>;
+
+impl error::Error for Error {}
+
+impl Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        use self::Error::*;
+
+        match self {
+            EncodeSessionFailure(e) => write!(f, "encode session error: {}", e),
+            EncodeSessionInitFailure(c) => {
+                write!(f, "failed to initialize encode session with config {:?}", c)
+            }
+            GetCapabilitiesFailure => write!(f, "failed to get capabilities"),
+            InstanceInitFailure => write!(f, "failed to initialize VDA instance"),
+            InvalidCapabilities(e) => write!(f, "obtained capabilities are invalid: {}", e),
+            LibVdaFailure(e) => write!(f, "error happened in libvda: {}", e),
+            ReadEventFailure(e) => write!(f, "failed to read event: {}", e),
+            SessionInitFailure(p) => write!(f, "failed to initialize decode session with {:?}", p),
+            SessionIdAlreadyUsed(id) => write!(f, "session_id {} is already used", id),
+            SessionNotFound(id) => write!(f, "no session has session_id {}", id),
+            UnknownPixelFormat(p) => write!(f, "unknown pixel format: {}", p),
+            UnknownProfile(p) => write!(f, "unknown profile: {}", p),
+        }
+    }
+}
diff --git a/libvda/src/format.rs b/libvda/src/format.rs
new file mode 100644
index 0000000..41f6a01
--- /dev/null
+++ b/libvda/src/format.rs
@@ -0,0 +1,123 @@
+// 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 representing coded/raw formats.
+
+use enumn::N;
+use std::os::unix::io::RawFd;
+
+use super::bindings;
+use super::error::*;
+
+/// Represents a FD for bitstream/frame buffer.
+/// Files described by BufferFd must be accessed from outside of this crate.
+pub type BufferFd = RawFd;
+
+/// Represents a video frame plane.
+pub struct FramePlane {
+    pub offset: i32,
+    pub stride: i32,
+}
+
+impl FramePlane {
+    pub fn to_raw_frame_plane(&self) -> bindings::video_frame_plane_t {
+        bindings::video_frame_plane_t {
+            offset: self.offset,
+            stride: self.stride,
+        }
+    }
+}
+
+// The callers must guarantee that `ptr` is valid for |`num`| elements when both `ptr` and `num`
+// are valid.
+pub(crate) unsafe fn validate_formats<T, U, F>(ptr: *const T, num: usize, f: F) -> Result<Vec<U>>
+where
+    F: FnMut(&T) -> Result<U>,
+{
+    if num == 0 {
+        return Err(Error::InvalidCapabilities("num must not be 0".to_string()));
+    }
+    if ptr.is_null() {
+        return Err(Error::InvalidCapabilities(
+            "pointer must not be NULL".to_string(),
+        ));
+    }
+
+    std::slice::from_raw_parts(ptr, num)
+        .iter()
+        .map(f)
+        .collect::<Result<Vec<_>>>()
+}
+
+/// Represents a video codec.
+#[derive(Debug, Clone, Copy, N)]
+#[repr(i32)]
+pub enum Profile {
+    H264ProfileBaseline = bindings::video_codec_profile_H264PROFILE_BASELINE,
+    H264ProfileMain = bindings::video_codec_profile_H264PROFILE_MAIN,
+    H264ProfileExtended = bindings::video_codec_profile_H264PROFILE_EXTENDED,
+    H264ProfileHigh = bindings::video_codec_profile_H264PROFILE_HIGH,
+    H264ProfileHigh10Profile = bindings::video_codec_profile_H264PROFILE_HIGH10PROFILE,
+    H264ProfileHigh422Profile = bindings::video_codec_profile_H264PROFILE_HIGH422PROFILE,
+    H264ProfileHigh444PredictiveProfile =
+        bindings::video_codec_profile_H264PROFILE_HIGH444PREDICTIVEPROFILE,
+    H264ProfileScalableBaseline = bindings::video_codec_profile_H264PROFILE_SCALABLEBASELINE,
+    H264ProfileScalableHigh = bindings::video_codec_profile_H264PROFILE_SCALABLEHIGH,
+    H264ProfileStereoHigh = bindings::video_codec_profile_H264PROFILE_STEREOHIGH,
+    H264ProfileMultiviewHigh = bindings::video_codec_profile_H264PROFILE_MULTIVIEWHIGH,
+    VP8 = bindings::video_codec_profile_VP8PROFILE_MIN,
+    VP9Profile0 = bindings::video_codec_profile_VP9PROFILE_PROFILE0,
+    VP9Profile1 = bindings::video_codec_profile_VP9PROFILE_PROFILE1,
+    VP9Profile2 = bindings::video_codec_profile_VP9PROFILE_PROFILE2,
+    VP9Profile3 = bindings::video_codec_profile_VP9PROFILE_PROFILE3,
+    HevcProfileMain = bindings::video_codec_profile_HEVCPROFILE_MAIN,
+    HevcProfileMain10 = bindings::video_codec_profile_HEVCPROFILE_MAIN10,
+    HevcProfileMainStillPicture = bindings::video_codec_profile_HEVCPROFILE_MAIN_STILL_PICTURE,
+    DolbyVisionProfile0 = bindings::video_codec_profile_DOLBYVISION_PROFILE0,
+    DolbyVisionProfile4 = bindings::video_codec_profile_DOLBYVISION_PROFILE4,
+    DolbyVisionProfile5 = bindings::video_codec_profile_DOLBYVISION_PROFILE5,
+    DolbyVisionProfile7 = bindings::video_codec_profile_DOLBYVISION_PROFILE7,
+    Theora = bindings::video_codec_profile_THEORAPROFILE_ANY,
+    Av1ProfileMain = bindings::video_codec_profile_AV1PROFILE_PROFILE_MAIN,
+    Av1ProfileHigh = bindings::video_codec_profile_AV1PROFILE_PROFILE_HIGH,
+    Av1ProfilePro = bindings::video_codec_profile_AV1PROFILE_PROFILE_PRO,
+}
+
+impl Profile {
+    pub(crate) fn new(p: bindings::video_codec_profile_t) -> Result<Self> {
+        Self::n(p).ok_or(Error::UnknownProfile(p))
+    }
+
+    pub(crate) fn to_raw_profile(self) -> bindings::video_codec_profile_t {
+        self as bindings::video_codec_profile_t
+    }
+}
+
+/// Represents a raw pixel format.
+#[derive(Debug, Clone, Copy, N)]
+#[repr(u32)]
+pub enum PixelFormat {
+    YV12 = bindings::video_pixel_format_YV12,
+    NV12 = bindings::video_pixel_format_NV12,
+}
+
+impl PixelFormat {
+    pub(crate) fn new(f: bindings::video_pixel_format_t) -> Result<PixelFormat> {
+        PixelFormat::n(f).ok_or(Error::UnknownPixelFormat(f))
+    }
+
+    pub(crate) fn to_raw_pixel_format(&self) -> bindings::video_pixel_format_t {
+        match *self {
+            PixelFormat::YV12 => bindings::video_pixel_format_YV12,
+            PixelFormat::NV12 => bindings::video_pixel_format_NV12,
+        }
+    }
+
+    pub(crate) unsafe fn from_raw_parts(
+        data: *const bindings::video_pixel_format_t,
+        len: usize,
+    ) -> Result<Vec<Self>> {
+        validate_formats(data, len, |f| Self::new(*f))
+    }
+}
diff --git a/libvda/src/lib.rs b/libvda/src/lib.rs
new file mode 100644
index 0000000..ba62434
--- /dev/null
+++ b/libvda/src/lib.rs
@@ -0,0 +1,13 @@
+// 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.
+
+pub mod decode;
+pub mod encode;
+
+mod bindings;
+mod error;
+mod format;
+
+pub use error::*;
+pub use format::*;
diff --git a/libvda/tests/decode_tests.rs b/libvda/tests/decode_tests.rs
new file mode 100644
index 0000000..e8f739e
--- /dev/null
+++ b/libvda/tests/decode_tests.rs
@@ -0,0 +1,58 @@
+// 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.
+
+//! Integration tests using LibVDA fake decode implemenation.
+
+use libvda::decode::*;
+use libvda::*;
+
+fn create_vda_instance() -> VdaInstance {
+    VdaInstance::new(VdaImplType::Fake).expect("failed to create VDAInstance")
+}
+
+#[test]
+fn test_create_instance() {
+    let instance = create_vda_instance();
+    let caps = instance.get_capabilities();
+
+    assert_ne!(caps.input_formats.len(), 0);
+    assert_ne!(caps.output_formats.len(), 0);
+}
+
+#[test]
+fn test_initialize_decode_session() {
+    let instance = create_vda_instance();
+    let _session = instance
+        .open_session(Profile::VP8)
+        .expect("failed to open a session for VP8");
+}
+
+#[test]
+fn test_decode_and_get_picture_ready_fake() {
+    let instance = create_vda_instance();
+    let mut session = instance
+        .open_session(Profile::VP8)
+        .expect("failed to open a session");
+
+    // Call decode() with dummy arguments.
+    let fake_bitstream_id = 12345;
+    session
+        .decode(
+            fake_bitstream_id,
+            1, // fd
+            0, // offset
+            0, // bytes_used
+        )
+        .expect("failed to send a decode request");
+
+    // Since we are using the fake backend,
+    // we must get a event immediately after calling decode().
+    match session.read_event() {
+        Ok(Event::PictureReady { bitstream_id, .. }) => {
+            assert_eq!(bitstream_id, fake_bitstream_id);
+        }
+        Ok(event) => panic!("Obtained event is not PictureReady but {:?}", event),
+        Err(msg) => panic!(msg),
+    }
+}
diff --git a/libvda/tests/encode_tests.rs b/libvda/tests/encode_tests.rs
new file mode 100644
index 0000000..687d37e
--- /dev/null
+++ b/libvda/tests/encode_tests.rs
@@ -0,0 +1,115 @@
+// 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.
+
+//! Integration tests using LibVDA fake encode implementation.
+
+use libvda::encode::*;
+use libvda::*;
+
+fn create_vea_instance() -> VeaInstance {
+    VeaInstance::new(VeaImplType::Fake).expect("failed to create VeaInstance")
+}
+
+fn create_config() -> Config {
+    Config {
+        input_format: PixelFormat::YV12,
+        input_visible_height: 320,
+        input_visible_width: 192,
+        output_profile: Profile::H264ProfileBaseline,
+        bitrate: Bitrate {
+            mode: BitrateMode::CBR,
+            target: 100,
+            peak: 0,
+        },
+        initial_framerate: None,
+        h264_output_level: None,
+    }
+}
+
+#[test]
+fn test_create_instance() {
+    let instance = create_vea_instance();
+    let caps = instance.get_capabilities();
+
+    assert_ne!(caps.input_formats.len(), 0);
+    assert_ne!(caps.output_formats.len(), 0);
+}
+
+#[test]
+fn test_initialize_encode_session() {
+    let instance = create_vea_instance();
+    let config = create_config();
+
+    let _session = instance
+        .open_session(config)
+        .expect("failed to open a session");
+}
+
+#[test]
+fn test_encode_and_get_buffer_back() {
+    let instance = create_vea_instance();
+    let config = create_config();
+    let mut session = instance
+        .open_session(config)
+        .expect("failed to open a session");
+
+    // Call encode() with dummy arguments.
+    let fake_input_buffer_id = 12345;
+    let fake_planes = vec![];
+    session
+        .encode(
+            fake_input_buffer_id,
+            1,            // fd
+            &fake_planes, // planes
+            0,            // timestamp
+            false,        // force_keyframe
+        )
+        .expect("failed to send an encode request");
+
+    // Since we are using the fake backend, we should get back
+    // the input buffer right away.
+    match session.read_event() {
+        Ok(Event::ProcessedInputBuffer(returned_input_buffer_id)) => {
+            assert_eq!(fake_input_buffer_id, returned_input_buffer_id);
+        }
+        Ok(event) => panic!("Obtained event is not ProcessedInputBuffer but {:?}", event),
+        Err(msg) => panic!(msg),
+    }
+}
+
+#[test]
+fn test_use_output_buffer_and_get_buffer_back() {
+    let instance = create_vea_instance();
+    let config = create_config();
+    let mut session = instance
+        .open_session(config)
+        .expect("failed to open a session");
+
+    // Call use_output_buffer with dummy arguments.
+    let fake_output_buffer_id = 12345;
+    session
+        .use_output_buffer(
+            fake_output_buffer_id,
+            2, // fd
+            0, // offset
+            0, // size
+        )
+        .expect("failed to send use_output_buffer request");
+
+    // Since we are using the fake backend, we should get back
+    // the input buffer right away.
+    match session.read_event() {
+        Ok(Event::ProcessedOutputBuffer {
+            output_buffer_id: returned_output_buffer_id,
+            ..
+        }) => {
+            assert_eq!(fake_output_buffer_id, returned_output_buffer_id);
+        }
+        Ok(event) => panic!(
+            "Obtained event is not ProcessedOutputBuffer but {:?}",
+            event
+        ),
+        Err(msg) => panic!(msg),
+    }
+}
diff --git a/linux_input_sys/Android.bp b/linux_input_sys/Android.bp
index 0dc2cc8..47fe99b 100644
--- a/linux_input_sys/Android.bp
+++ b/linux_input_sys/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
 // Do not modify this file as changes will be overridden on upgrade.
 
 package {
@@ -25,7 +25,7 @@
 }
 
 rust_defaults {
-    name: "linux_input_sys_defaults",
+    name: "linux_input_sys_test_defaults",
     defaults: ["crosvm_defaults"],
     crate_name: "linux_input_sys",
     srcs: ["src/lib.rs"],
@@ -41,7 +41,7 @@
 
 rust_test_host {
     name: "linux_input_sys_host_test_src_lib",
-    defaults: ["linux_input_sys_defaults"],
+    defaults: ["linux_input_sys_test_defaults"],
     test_options: {
         unit_test: true,
     },
@@ -49,45 +49,5 @@
 
 rust_test {
     name: "linux_input_sys_device_test_src_lib",
-    defaults: ["linux_input_sys_defaults"],
+    defaults: ["linux_input_sys_test_defaults"],
 }
-
-// dependent_library ["feature_list"]
-//   ../assertions/src/lib.rs
-//   ../base/src/lib.rs
-//   ../cros_async/src/lib.rs
-//   ../data_model/src/lib.rs
-//   ../io_uring/src/lib.rs
-//   ../sync/src/lib.rs
-//   ../sys_util/poll_token_derive/poll_token_derive.rs
-//   ../sys_util/src/lib.rs
-//   ../tempfile/src/lib.rs
-//   async-task-4.0.3 "default,std"
-//   async-trait-0.1.50
-//   autocfg-1.0.1
-//   futures-0.3.15 "alloc"
-//   futures-channel-0.3.15 "alloc,futures-sink,sink"
-//   futures-core-0.3.15 "alloc"
-//   futures-io-0.3.15
-//   futures-sink-0.3.15 "alloc"
-//   futures-task-0.3.15 "alloc"
-//   futures-util-0.3.15 "alloc,futures-sink,sink"
-//   intrusive-collections-0.9.1 "alloc,default"
-//   itoa-0.4.7
-//   libc-0.2.97 "default,std"
-//   memoffset-0.5.6 "default"
-//   paste-1.0.5
-//   pin-project-lite-0.2.7
-//   pin-utils-0.1.0
-//   proc-macro2-1.0.27 "default,proc-macro"
-//   quote-1.0.9 "default,proc-macro"
-//   ryu-1.0.5
-//   serde-1.0.126 "default,derive,serde_derive,std"
-//   serde_derive-1.0.126 "default"
-//   serde_json-1.0.64 "default,std"
-//   slab-0.4.3 "default,std"
-//   smallvec-1.6.1
-//   syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-//   thiserror-1.0.25
-//   thiserror-impl-1.0.25
-//   unicode-xid-0.2.2 "default"
diff --git a/net_sys/Android.bp b/net_sys/Android.bp
index 437fa68..d27975a 100644
--- a/net_sys/Android.bp
+++ b/net_sys/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
 // Do not modify this file as changes will be overridden on upgrade.
 
 package {
@@ -13,6 +13,7 @@
 rust_library {
     name: "libnet_sys",
     defaults: ["crosvm_defaults"],
+    // has rustc warnings
     host_supported: true,
     crate_name: "net_sys",
     srcs: ["src/lib.rs"],
@@ -23,9 +24,10 @@
 }
 
 rust_defaults {
-    name: "net_sys_defaults",
+    name: "net_sys_test_defaults",
     defaults: ["crosvm_defaults"],
     crate_name: "net_sys",
+    // has rustc warnings
     srcs: ["src/lib.rs"],
     test_suites: ["general-tests"],
     auto_gen_config: true,
@@ -37,7 +39,7 @@
 
 rust_test_host {
     name: "net_sys_host_test_src_lib",
-    defaults: ["net_sys_defaults"],
+    defaults: ["net_sys_test_defaults"],
     test_options: {
         unit_test: true,
     },
@@ -45,45 +47,5 @@
 
 rust_test {
     name: "net_sys_device_test_src_lib",
-    defaults: ["net_sys_defaults"],
+    defaults: ["net_sys_test_defaults"],
 }
-
-// dependent_library ["feature_list"]
-//   ../assertions/src/lib.rs
-//   ../base/src/lib.rs
-//   ../cros_async/src/lib.rs
-//   ../data_model/src/lib.rs
-//   ../io_uring/src/lib.rs
-//   ../sync/src/lib.rs
-//   ../sys_util/poll_token_derive/poll_token_derive.rs
-//   ../sys_util/src/lib.rs
-//   ../tempfile/src/lib.rs
-//   async-task-4.0.3 "default,std"
-//   async-trait-0.1.50
-//   autocfg-1.0.1
-//   futures-0.3.15 "alloc"
-//   futures-channel-0.3.15 "alloc,futures-sink,sink"
-//   futures-core-0.3.15 "alloc"
-//   futures-io-0.3.15
-//   futures-sink-0.3.15 "alloc"
-//   futures-task-0.3.15 "alloc"
-//   futures-util-0.3.15 "alloc,futures-sink,sink"
-//   intrusive-collections-0.9.1 "alloc,default"
-//   itoa-0.4.7
-//   libc-0.2.97 "default,std"
-//   memoffset-0.5.6 "default"
-//   paste-1.0.5
-//   pin-project-lite-0.2.7
-//   pin-utils-0.1.0
-//   proc-macro2-1.0.27 "default,proc-macro"
-//   quote-1.0.9 "default,proc-macro"
-//   ryu-1.0.5
-//   serde-1.0.126 "default,derive,serde_derive,std"
-//   serde_derive-1.0.126 "default"
-//   serde_json-1.0.64 "default,std"
-//   slab-0.4.3 "default,std"
-//   smallvec-1.6.1
-//   syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-//   thiserror-1.0.25
-//   thiserror-impl-1.0.25
-//   unicode-xid-0.2.2 "default"
diff --git a/net_util/Android.bp b/net_util/Android.bp
index 23db193..0000aa9 100644
--- a/net_util/Android.bp
+++ b/net_util/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
 // Do not modify this file as changes will be overridden on upgrade.
 
 package {
@@ -28,7 +28,7 @@
 }
 
 rust_defaults {
-    name: "net_util_defaults",
+    name: "net_util_test_defaults",
     defaults: ["crosvm_defaults"],
     crate_name: "net_util",
     srcs: ["src/lib.rs"],
@@ -47,7 +47,7 @@
 
 rust_test_host {
     name: "net_util_host_test_src_lib",
-    defaults: ["net_util_defaults"],
+    defaults: ["net_util_test_defaults"],
     test_options: {
         unit_test: true,
     },
@@ -55,46 +55,5 @@
 
 rust_test {
     name: "net_util_device_test_src_lib",
-    defaults: ["net_util_defaults"],
+    defaults: ["net_util_test_defaults"],
 }
-
-// dependent_library ["feature_list"]
-//   ../assertions/src/lib.rs
-//   ../base/src/lib.rs
-//   ../cros_async/src/lib.rs
-//   ../data_model/src/lib.rs
-//   ../io_uring/src/lib.rs
-//   ../net_sys/src/lib.rs
-//   ../sync/src/lib.rs
-//   ../sys_util/poll_token_derive/poll_token_derive.rs
-//   ../sys_util/src/lib.rs
-//   ../tempfile/src/lib.rs
-//   async-task-4.0.3 "default,std"
-//   async-trait-0.1.50
-//   autocfg-1.0.1
-//   futures-0.3.15 "alloc"
-//   futures-channel-0.3.15 "alloc,futures-sink,sink"
-//   futures-core-0.3.15 "alloc"
-//   futures-io-0.3.15
-//   futures-sink-0.3.15 "alloc"
-//   futures-task-0.3.15 "alloc"
-//   futures-util-0.3.15 "alloc,futures-sink,sink"
-//   intrusive-collections-0.9.1 "alloc,default"
-//   itoa-0.4.7
-//   libc-0.2.97 "default,std"
-//   memoffset-0.5.6 "default"
-//   paste-1.0.5
-//   pin-project-lite-0.2.7
-//   pin-utils-0.1.0
-//   proc-macro2-1.0.27 "default,proc-macro"
-//   quote-1.0.9 "default,proc-macro"
-//   ryu-1.0.5
-//   serde-1.0.126 "default,derive,serde_derive,std"
-//   serde_derive-1.0.126 "default"
-//   serde_json-1.0.64 "default,std"
-//   slab-0.4.3 "default,std"
-//   smallvec-1.6.1
-//   syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-//   thiserror-1.0.25
-//   thiserror-impl-1.0.25
-//   unicode-xid-0.2.2 "default"
diff --git a/patches/Android.bp.patch b/patches/Android.bp.patch
new file mode 100644
index 0000000..813d67e
--- /dev/null
+++ b/patches/Android.bp.patch
@@ -0,0 +1,27 @@
+diff --git a/Android.bp b/Android.bp
+index 21ebb3ac..519fb5d5 100644
+--- a/Android.bp
++++ b/Android.bp
+@@ -36,6 +36,7 @@ rust_binary {
+     name: "crosvm",
+     defaults: ["crosvm_defaults"],
+     host_supported: true,
++    prefer_rlib: true,
+     crate_name: "crosvm",
+     cargo_env_compat: true,
+     cargo_pkg_version: "0.1.0",
+@@ -92,7 +93,14 @@ rust_binary {
+         },
+     },
+     target: {
++        linux_bionic_arm64: {
++            relative_install_path: "aarch64-linux-bionic",
++        },
++        darwin: {
++            enabled: false,
++        },
+         linux_glibc_x86_64: {
++            relative_install_path: "x86_64-linux-gnu",
+             features: [
+                 "gdb",
+                 "gdbstub",
diff --git a/power_monitor/Android.bp b/power_monitor/Android.bp
index 354745d..d67a4d6 100644
--- a/power_monitor/Android.bp
+++ b/power_monitor/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
 // Do not modify this file as changes will be overridden on upgrade.
 
 package {
@@ -23,7 +23,7 @@
 }
 
 rust_defaults {
-    name: "power_monitor_defaults",
+    name: "power_monitor_test_defaults",
     defaults: ["crosvm_defaults"],
     crate_name: "power_monitor",
     srcs: ["src/lib.rs"],
@@ -37,7 +37,7 @@
 
 rust_test_host {
     name: "power_monitor_host_test_src_lib",
-    defaults: ["power_monitor_defaults"],
+    defaults: ["power_monitor_test_defaults"],
     test_options: {
         unit_test: true,
     },
@@ -45,45 +45,5 @@
 
 rust_test {
     name: "power_monitor_device_test_src_lib",
-    defaults: ["power_monitor_defaults"],
+    defaults: ["power_monitor_test_defaults"],
 }
-
-// dependent_library ["feature_list"]
-//   ../assertions/src/lib.rs
-//   ../base/src/lib.rs
-//   ../cros_async/src/lib.rs
-//   ../data_model/src/lib.rs
-//   ../io_uring/src/lib.rs
-//   ../sync/src/lib.rs
-//   ../sys_util/poll_token_derive/poll_token_derive.rs
-//   ../sys_util/src/lib.rs
-//   ../tempfile/src/lib.rs
-//   async-task-4.0.3 "default,std"
-//   async-trait-0.1.50
-//   autocfg-1.0.1
-//   futures-0.3.15 "alloc"
-//   futures-channel-0.3.15 "alloc,futures-sink,sink"
-//   futures-core-0.3.15 "alloc"
-//   futures-io-0.3.15
-//   futures-sink-0.3.15 "alloc"
-//   futures-task-0.3.15 "alloc"
-//   futures-util-0.3.15 "alloc,futures-sink,sink"
-//   intrusive-collections-0.9.1 "alloc,default"
-//   itoa-0.4.7
-//   libc-0.2.97 "default,std"
-//   memoffset-0.5.6 "default"
-//   paste-1.0.5
-//   pin-project-lite-0.2.7
-//   pin-utils-0.1.0
-//   proc-macro2-1.0.27 "default,proc-macro"
-//   quote-1.0.9 "default,proc-macro"
-//   ryu-1.0.5
-//   serde-1.0.126 "default,derive,serde_derive,std"
-//   serde_derive-1.0.126 "default"
-//   serde_json-1.0.64 "default,std"
-//   slab-0.4.3 "default,std"
-//   smallvec-1.6.1
-//   syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-//   thiserror-1.0.25
-//   thiserror-impl-1.0.25
-//   unicode-xid-0.2.2 "default"
diff --git a/power_monitor/build.rs b/power_monitor/build.rs
index b925c28..174389d 100644
--- a/power_monitor/build.rs
+++ b/power_monitor/build.rs
@@ -31,13 +31,12 @@
         let input_files = [power_manager_dir.join("power_supply_properties.proto")];
         let include_dirs = [power_manager_dir];
 
-        protoc_rust::run(protoc_rust::Args {
-            out_dir: out_dir.as_os_str().to_str().unwrap(),
-            input: &paths_to_strs(&input_files),
-            includes: &paths_to_strs(&include_dirs),
-            customize: Default::default(),
-        })
-        .expect("protoc");
+        protoc_rust::Codegen::new()
+            .inputs(&paths_to_strs(&input_files))
+            .includes(&paths_to_strs(&include_dirs))
+            .out_dir(out_dir.as_os_str().to_str().unwrap())
+            .run()
+            .expect("protoc");
 
         let mut path_include_mods = String::new();
         for input_file in input_files.iter() {
diff --git a/protos/Android.bp b/protos/Android.bp
index 655c084..00a570d 100644
--- a/protos/Android.bp
+++ b/protos/Android.bp
@@ -1,5 +1,5 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
-// Manually editted to only enable composite-disk and include cdisk_spec.rs for now.
+// This file is generated by cargo2android.py --config cargo2android.json.
+// Do not modify this file as changes will be overridden on upgrade.
 
 package {
     // See: http://go/android-license-faq
@@ -13,20 +13,45 @@
 rust_library {
     name: "libprotos",
     defaults: ["crosvm_defaults"],
-    // has rustc warnings
     host_supported: true,
     crate_name: "protos",
     srcs: ["src/lib.rs"],
-    features: [
-        "composite-disk",
-    ],
     edition: "2018",
+    features: ["composite-disk"],
     rustlibs: [
         "libcdisk_spec_proto",
         "libprotobuf",
     ],
 }
 
+rust_defaults {
+    name: "protos_test_defaults",
+    defaults: ["crosvm_defaults"],
+    crate_name: "protos",
+    srcs: ["src/lib.rs"],
+    test_suites: ["general-tests"],
+    auto_gen_config: true,
+    edition: "2018",
+    features: ["composite-disk"],
+    rustlibs: [
+        "libcdisk_spec_proto",
+        "libprotobuf",
+    ],
+}
+
+rust_test_host {
+    name: "protos_host_test_src_lib",
+    defaults: ["protos_test_defaults"],
+    test_options: {
+        unit_test: true,
+    },
+}
+
+rust_test {
+    name: "protos_device_test_src_lib",
+    defaults: ["protos_test_defaults"],
+}
+
 rust_protobuf {
     name: "libcdisk_spec_proto",
     crate_name: "cdisk_spec_proto",
@@ -46,80 +71,3 @@
     source_stem: "plugin",
     host_supported: true,
 }
-
-rust_defaults {
-    name: "protos_defaults",
-    defaults: ["crosvm_defaults"],
-    crate_name: "protos",
-    // has rustc warnings
-    srcs: ["src/lib.rs"],
-    features: [
-        "composite-disk",
-    ],
-    test_suites: ["general-tests"],
-    auto_gen_config: true,
-    edition: "2018",
-    rustlibs: [
-        "libcdisk_spec_proto",
-        "libprotobuf",
-    ],
-}
-
-rust_test_host {
-    name: "protos_host_test_src_lib",
-    defaults: ["protos_defaults"],
-    test_options: {
-        unit_test: true,
-    },
-}
-
-rust_test {
-    name: "protos_device_test_src_lib",
-    defaults: ["protos_defaults"],
-}
-
-rust_defaults {
-    name: "protos_defaults_trunks",
-    defaults: ["crosvm_defaults"],
-    crate_name: "trunks",
-    // has rustc warnings
-    srcs: ["tests/trunks.rs"],
-    test_suites: ["general-tests"],
-    auto_gen_config: true,
-    edition: "2018",
-    rustlibs: [
-        "libprotobuf",
-        "libprotos",
-    ],
-}
-
-rust_test_host {
-    name: "protos_host_test_tests_trunks",
-    defaults: ["protos_defaults_trunks"],
-    test_options: {
-        unit_test: true,
-    },
-}
-
-rust_test {
-    name: "protos_device_test_tests_trunks",
-    defaults: ["protos_defaults_trunks"],
-}
-
-// dependent_library ["feature_list"]
-//   cfg-if-1.0.0
-//   either-1.6.1 "default,use_std"
-//   getrandom-0.2.3 "std"
-//   libc-0.2.97 "default,std"
-//   log-0.4.14
-//   ppv-lite86-0.2.10 "simd,std"
-//   protobuf-2.24.1
-//   protobuf-codegen-2.24.1
-//   protoc-2.24.1
-//   protoc-rust-2.24.1
-//   rand-0.8.4 "alloc,default,getrandom,libc,rand_chacha,rand_hc,std,std_rng"
-//   rand_chacha-0.3.1 "std"
-//   rand_core-0.6.3 "alloc,getrandom,std"
-//   remove_dir_all-0.5.3
-//   tempfile-3.2.0
-//   which-4.1.0
diff --git a/protos/Cargo.toml b/protos/Cargo.toml
index 98cd4ae..d4286e6 100644
--- a/protos/Cargo.toml
+++ b/protos/Cargo.toml
@@ -7,7 +7,6 @@
 [features]
 plugin = ["kvm_sys"]
 composite-disk = []
-trunks = []
 
 [dependencies]
 kvm_sys = { path = "../kvm_sys", optional = true }
diff --git a/protos/build.rs b/protos/build.rs
index 2e870b4..5aa3ca2 100644
--- a/protos/build.rs
+++ b/protos/build.rs
@@ -29,15 +29,7 @@
 
 // Rustfmt bug: https://github.com/rust-lang/rustfmt/issues/3498
 #[rustfmt::skip]
-static EXTERNAL_PROTOS: &[ExternalProto] = &[
-    #[cfg(feature = "trunks")]
-    ExternalProto {
-        dir_relative_to_sysroot: "usr/include/chromeos/dbus/trunks",
-        dir_relative_to_us: "../../../platform2/trunks",
-        proto_file_name: "interface.proto",
-        module: "trunks",
-    },
-];
+static EXTERNAL_PROTOS: &[ExternalProto] = &[];
 
 struct LocalProto {
     // Corresponding to the input file src/$module.proto.
@@ -92,12 +84,12 @@
     fs::create_dir_all(&out_dir)?;
 
     // Invoke protobuf compiler.
-    protoc_rust::run(protoc_rust::Args {
-        out_dir: &out_dir,
-        includes: &[input_dir.as_os_str().to_str().unwrap()],
-        input: &[input_path.as_os_str().to_str().unwrap()],
-        ..Default::default()
-    })?;
+    protoc_rust::Codegen::new()
+        .input(input_path.as_os_str().to_str().unwrap())
+        .include(input_dir.as_os_str().to_str().unwrap())
+        .out_dir(&out_dir)
+        .run()
+        .expect("protoc");
 
     // Write out a `mod` that refers to the generated module.
     //
diff --git a/protos/cargo2android.json b/protos/cargo2android.json
new file mode 100644
index 0000000..8dac0dc
--- /dev/null
+++ b/protos/cargo2android.json
@@ -0,0 +1,9 @@
+{
+  "add-toplevel-block": "cargo2android_protobuf.bp",
+  "add_workspace": true,
+  "device": true,
+  "global_defaults": "crosvm_defaults",
+  "patch": "patches/Android.bp.patch",
+  "run": true,
+  "tests": true
+}
\ No newline at end of file
diff --git a/protos/cargo2android_protobuf.bp b/protos/cargo2android_protobuf.bp
new file mode 100644
index 0000000..a23e061
--- /dev/null
+++ b/protos/cargo2android_protobuf.bp
@@ -0,0 +1,19 @@
+rust_protobuf {
+    name: "libcdisk_spec_proto",
+    crate_name: "cdisk_spec_proto",
+    protos: ["src/cdisk_spec.proto"],
+    source_stem: "cdisk_spec",
+    host_supported: true,
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.virt",
+    ],
+}
+
+rust_protobuf {
+    name: "libcrosvm_plugin_proto",
+    crate_name: "crosvm_plugin_proto",
+    protos: ["src/plugin.proto"],
+    source_stem: "plugin",
+    host_supported: true,
+}
\ No newline at end of file
diff --git a/protos/patches/Android.bp.patch b/protos/patches/Android.bp.patch
new file mode 100644
index 0000000..977968a
--- /dev/null
+++ b/protos/patches/Android.bp.patch
@@ -0,0 +1,24 @@
+diff --git a/protos/Android.bp b/protos/Android.bp
+index eaf06cf1..32a8f81b 100644
+--- a/protos/Android.bp
++++ b/protos/Android.bp
+@@ -17,7 +17,9 @@ rust_library {
+     crate_name: "protos",
+     srcs: ["src/lib.rs"],
+     edition: "2018",
++    features: ["composite-disk"],
+     rustlibs: [
++        "libcdisk_spec_proto",
+         "libprotobuf",
+     ],
+ }
+@@ -30,7 +32,9 @@ rust_defaults {
+     test_suites: ["general-tests"],
+     auto_gen_config: true,
+     edition: "2018",
++    features: ["composite-disk"],
+     rustlibs: [
++        "libcdisk_spec_proto",
+         "libprotobuf",
+     ],
+ }
diff --git a/protos/src/lib.rs b/protos/src/lib.rs
index 785a5e3..5c2a751 100644
--- a/protos/src/lib.rs
+++ b/protos/src/lib.rs
@@ -5,8 +5,5 @@
 #[cfg(feature = "plugin")]
 pub use crosvm_plugin_proto::plugin;
 
-#[cfg(feature = "trunks")]
-pub mod trunks;
-
 #[cfg(feature = "composite-disk")]
 pub use cdisk_spec_proto::cdisk_spec;
diff --git a/protos/tests/trunks.rs b/protos/tests/trunks.rs
deleted file mode 100644
index 39723ae..0000000
--- a/protos/tests/trunks.rs
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2019 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.
-
-#![cfg(feature = "trunks")]
-
-mod common;
-
-use crate::common::test_round_trip;
-use protos::trunks::{SendCommandRequest, SendCommandResponse};
-
-#[test]
-fn send_command_request() {
-    let mut request = SendCommandRequest::new();
-    request.set_command(b"...".to_vec());
-    test_round_trip(request);
-}
-
-#[test]
-fn send_command_response() {
-    let mut response = SendCommandResponse::new();
-    response.set_response(b"...".to_vec());
-    test_round_trip(response);
-}
diff --git a/qcow_utils/Android.bp b/qcow_utils/Android.bp
index f8d68d3..25e7b3e 100644
--- a/qcow_utils/Android.bp
+++ b/qcow_utils/Android.bp
@@ -1,4 +1 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
-// Do not modify this file as changes will be overridden on upgrade.
-
 // Feature is disabled for Android
diff --git a/qcow_utils/cargo2android.json b/qcow_utils/cargo2android.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/qcow_utils/cargo2android.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/qcow_utils/src/qcow_img.rs b/qcow_utils/src/qcow_img.rs
index 3fdd269..1e28fc4 100644
--- a/qcow_utils/src/qcow_img.rs
+++ b/qcow_utils/src/qcow_img.rs
@@ -49,7 +49,7 @@
 
     let matches = match opts.parse(&args[1..]) {
         Ok(m) => m,
-        Err(f) => panic!(f.to_string()),
+        Err(f) => panic!("{}", f.to_string()),
     };
 
     if matches.free.len() < 2 {
diff --git a/rand_ish/src/lib.rs b/rand_ish/src/lib.rs
deleted file mode 100644
index c7b0ff4..0000000
--- a/rand_ish/src/lib.rs
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright 2019 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-use std::fs::File;
-use std::io::{self, Read};
-
-/// A simple prng based on a Linear congruential generator
-/// https://en.wikipedia.org/wiki/Linear_congruential_generator
-pub struct SimpleRng {
-    seed: u64,
-}
-
-impl SimpleRng {
-    /// Create a new SimpleRng
-    pub fn new(seed: u64) -> SimpleRng {
-        SimpleRng { seed }
-    }
-
-    /// Generate random u64
-    pub fn rng(&mut self) -> u64 {
-        // a simple Linear congruential generator
-        let a: u64 = 6364136223846793005;
-        let c: u64 = 1442695040888963407;
-        self.seed = a.wrapping_mul(self.seed).wrapping_add(c);
-        self.seed
-    }
-
-    /// Generate a random alphanumeric string.
-    pub fn str(&mut self, len: usize) -> String {
-        self.filter_map(|v| uniform_sample_ascii_alphanumeric(v as u8))
-            .take(len)
-            .collect()
-    }
-}
-
-impl Iterator for SimpleRng {
-    type Item = u64;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        Some(self.rng())
-    }
-}
-
-// Uniformly samples the ASCII alphanumeric characters given a random variable. If an `Err` is
-// passed in, the error is returned as `Some(Err(...))`. If the the random variable can not be used
-// to uniformly sample, `None` is returned.
-fn uniform_sample_ascii_alphanumeric(b: u8) -> Option<char> {
-    const ASCII_CHARS: &[u8] = b"\
-          ABCDEFGHIJKLMNOPQRSTUVWXYZ\
-          abcdefghijklmnopqrstuvwxyz\
-          0123456789";
-    let char_index = b as usize;
-    // Throw away numbers that would cause sampling bias.
-    if char_index >= ASCII_CHARS.len() * 4 {
-        None
-    } else {
-        Some(ASCII_CHARS[char_index % ASCII_CHARS.len()] as char)
-    }
-}
-
-/// Samples `/dev/urandom` and generates a random ASCII string of length `len`
-pub fn urandom_str(len: usize) -> io::Result<String> {
-    File::open("/dev/urandom")?
-        .bytes()
-        .filter_map(|b| b.map(uniform_sample_ascii_alphanumeric).transpose())
-        .take(len)
-        .collect::<io::Result<String>>()
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn check_100() {
-        for i in 0..100 {
-            let s = urandom_str(i).unwrap();
-            assert_eq!(s.len(), i);
-            assert!(s.is_ascii());
-            assert!(!s.contains(' '));
-            assert!(!s.contains(|c: char| !c.is_ascii_alphanumeric()));
-        }
-    }
-}
diff --git a/resources/Android.bp b/resources/Android.bp
index c6fc78a..02a85b8 100644
--- a/resources/Android.bp
+++ b/resources/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
 // Do not modify this file as changes will be overridden on upgrade.
 
 package {
@@ -25,7 +25,7 @@
 }
 
 rust_defaults {
-    name: "resources_defaults",
+    name: "resources_test_defaults",
     defaults: ["crosvm_defaults"],
     crate_name: "resources",
     srcs: ["src/lib.rs"],
@@ -41,7 +41,7 @@
 
 rust_test_host {
     name: "resources_host_test_src_lib",
-    defaults: ["resources_defaults"],
+    defaults: ["resources_test_defaults"],
     test_options: {
         unit_test: true,
     },
@@ -49,45 +49,5 @@
 
 rust_test {
     name: "resources_device_test_src_lib",
-    defaults: ["resources_defaults"],
+    defaults: ["resources_test_defaults"],
 }
-
-// dependent_library ["feature_list"]
-//   ../assertions/src/lib.rs
-//   ../base/src/lib.rs
-//   ../cros_async/src/lib.rs
-//   ../data_model/src/lib.rs
-//   ../io_uring/src/lib.rs
-//   ../sync/src/lib.rs
-//   ../sys_util/poll_token_derive/poll_token_derive.rs
-//   ../sys_util/src/lib.rs
-//   ../tempfile/src/lib.rs
-//   async-task-4.0.3 "default,std"
-//   async-trait-0.1.50
-//   autocfg-1.0.1
-//   futures-0.3.15 "alloc"
-//   futures-channel-0.3.15 "alloc,futures-sink,sink"
-//   futures-core-0.3.15 "alloc"
-//   futures-io-0.3.15
-//   futures-sink-0.3.15 "alloc"
-//   futures-task-0.3.15 "alloc"
-//   futures-util-0.3.15 "alloc,futures-sink,sink"
-//   intrusive-collections-0.9.1 "alloc,default"
-//   itoa-0.4.7
-//   libc-0.2.97 "default,std"
-//   memoffset-0.5.6 "default"
-//   paste-1.0.5
-//   pin-project-lite-0.2.7
-//   pin-utils-0.1.0
-//   proc-macro2-1.0.27 "default,proc-macro"
-//   quote-1.0.9 "default,proc-macro"
-//   ryu-1.0.5
-//   serde-1.0.126 "default,derive,serde_derive,std"
-//   serde_derive-1.0.126 "default"
-//   serde_json-1.0.64 "default,std"
-//   slab-0.4.3 "default,std"
-//   smallvec-1.6.1
-//   syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-//   thiserror-1.0.25
-//   thiserror-impl-1.0.25
-//   unicode-xid-0.2.2 "default"
diff --git a/run_tests b/run_tests
index 95c92b8..78c8744 100755
--- a/run_tests
+++ b/run_tests
@@ -20,6 +20,8 @@
     "base": [],
     "bit_field": [],
     "bit_field_derive": [],
+    "common/cros-fuzz": [Requirements.SEPARATE_WORKSPACE],
+    "common/p9": [Requirements.SEPARATE_WORKSPACE, Requirements.X86_64],
     "cros_async": [Requirements.DISABLED],
     "crosvm": [Requirements.DO_NOT_RUN],
     "crosvm_plugin": [Requirements.X86_64],
@@ -52,7 +54,6 @@
     "power_monitor": [],
     "protos": [],
     "qcow_utils": [],
-    "rand_ish": [],
     "resources": [],
     "rutabaga_gfx": [Requirements.CROS_BUILD, Requirements.PRIVILEGED],
     "sync": [],
@@ -73,11 +74,11 @@
 
 # Just like for crates, lists requirements for each cargo feature flag.
 FEATURE_REQUIREMENTS: Dict[str, List[Requirements]] = {
-    "chromeos": [],
+    "chromeos": [Requirements.DISABLED],
     "audio": [],
     "gpu": [Requirements.CROS_BUILD],
     "plugin": [Requirements.PRIVILEGED, Requirements.X86_64],
-    "power-monitor-powerd": [],
+    "power-monitor-powerd": [Requirements.DISABLED],
     "tpm": [Requirements.CROS_BUILD],
     "video-decoder": [Requirements.DISABLED],
     "video-encoder": [Requirements.DISABLED],
diff --git a/rutabaga_gfx/Android.bp b/rutabaga_gfx/Android.bp
index d0762d0..adf4d62 100644
--- a/rutabaga_gfx/Android.bp
+++ b/rutabaga_gfx/Android.bp
@@ -1,5 +1,5 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
-// Manually added features, shared_libs, and target to rules below.
+// This file is generated by cargo2android.py --config cargo2android.json.
+// Do not modify this file as changes will be overridden on upgrade.
 
 package {
     // See: http://go/android-license-faq
@@ -28,8 +28,6 @@
         "liblibc",
         "libsync_rust",
     ],
-
-    // added manually
     shared_libs: ["libgfxstream_backend"],
     target: {
         host: {
@@ -37,8 +35,8 @@
         },
         android: {
             shared_libs: [
-            "libdrm",
-        ],
+                "libdrm",
+            ],
             static_libs: [
                 "libepoxy",
                 "libgbm",
@@ -49,10 +47,9 @@
 }
 
 rust_defaults {
-    name: "rutabaga_gfx_defaults",
+    name: "rutabaga_gfx_test_defaults",
     defaults: ["crosvm_defaults"],
     crate_name: "rutabaga_gfx",
-    // has rustc warnings
     srcs: ["src/lib.rs"],
     test_suites: ["general-tests"],
     auto_gen_config: true,
@@ -68,8 +65,14 @@
         "liblibc",
         "libsync_rust",
     ],
+}
 
-    // added manually
+rust_test_host {
+    name: "rutabaga_gfx_host_test_src_lib",
+    defaults: ["rutabaga_gfx_test_defaults"],
+    test_options: {
+        unit_test: true,
+    },
     shared_libs: ["libgfxstream_backend"],
     target: {
         host: {
@@ -77,8 +80,8 @@
         },
         android: {
             shared_libs: [
-            "libdrm",
-        ],
+                "libdrm",
+            ],
             static_libs: [
                 "libepoxy",
                 "libgbm",
@@ -88,55 +91,23 @@
     },
 }
 
-rust_test_host {
-    name: "rutabaga_gfx_host_test_src_lib",
-    defaults: ["rutabaga_gfx_defaults"],
-    test_options: {
-        unit_test: true,
-    },
-}
-
 rust_test {
     name: "rutabaga_gfx_device_test_src_lib",
-    defaults: ["rutabaga_gfx_defaults"],
+    defaults: ["rutabaga_gfx_test_defaults"],
+    shared_libs: ["libgfxstream_backend"],
+    target: {
+        host: {
+            shared_libs: ["libvirglrenderer"],
+        },
+        android: {
+            shared_libs: [
+                "libdrm",
+            ],
+            static_libs: [
+                "libepoxy",
+                "libgbm",
+                "libvirglrenderer",
+            ],
+        },
+    },
 }
-
-// dependent_library ["feature_list"]
-//   ../assertions/src/lib.rs
-//   ../base/src/lib.rs
-//   ../cros_async/src/lib.rs
-//   ../data_model/src/lib.rs
-//   ../io_uring/src/lib.rs
-//   ../sync/src/lib.rs
-//   ../sys_util/poll_token_derive/poll_token_derive.rs
-//   ../sys_util/src/lib.rs
-//   ../tempfile/src/lib.rs
-//   async-task-4.0.3 "default,std"
-//   async-trait-0.1.50
-//   autocfg-1.0.1
-//   futures-0.3.15 "alloc"
-//   futures-channel-0.3.15 "alloc,futures-sink,sink"
-//   futures-core-0.3.15 "alloc"
-//   futures-io-0.3.15
-//   futures-sink-0.3.15 "alloc"
-//   futures-task-0.3.15 "alloc"
-//   futures-util-0.3.15 "alloc,futures-sink,sink"
-//   intrusive-collections-0.9.1 "alloc,default"
-//   itoa-0.4.7
-//   libc-0.2.97 "default,std"
-//   memoffset-0.5.6 "default"
-//   paste-1.0.5
-//   pin-project-lite-0.2.7
-//   pin-utils-0.1.0
-//   proc-macro2-1.0.27 "default,proc-macro"
-//   quote-1.0.9 "default,proc-macro"
-//   ryu-1.0.5
-//   serde-1.0.126 "default,derive,serde_derive,std"
-//   serde_derive-1.0.126 "default"
-//   serde_json-1.0.64 "default,std"
-//   slab-0.4.3 "default,std"
-//   smallvec-1.6.1
-//   syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-//   thiserror-1.0.25
-//   thiserror-impl-1.0.25
-//   unicode-xid-0.2.2 "default"
diff --git a/rutabaga_gfx/cargo2android.json b/rutabaga_gfx/cargo2android.json
new file mode 100644
index 0000000..09d4626
--- /dev/null
+++ b/rutabaga_gfx/cargo2android.json
@@ -0,0 +1,9 @@
+{
+  "add-module-block": "cargo2android_target.bp",
+  "add_workspace": true,
+  "device": true,
+  "features": "gfxstream,virgl_renderer,virgl_renderer_next",
+  "global_defaults": "crosvm_defaults",
+  "run": true,
+  "tests": true
+}
\ No newline at end of file
diff --git a/rutabaga_gfx/cargo2android_target.bp b/rutabaga_gfx/cargo2android_target.bp
new file mode 100644
index 0000000..bffc28a
--- /dev/null
+++ b/rutabaga_gfx/cargo2android_target.bp
@@ -0,0 +1,16 @@
+shared_libs: ["libgfxstream_backend"],
+target: {
+    host: {
+        shared_libs: ["libvirglrenderer"],
+    },
+    android: {
+        shared_libs: [
+            "libdrm",
+        ],
+        static_libs: [
+            "libepoxy",
+            "libgbm",
+            "libvirglrenderer",
+        ],
+    },
+}
\ No newline at end of file
diff --git a/rutabaga_gfx/src/cross_domain/cross_domain.rs b/rutabaga_gfx/src/cross_domain/cross_domain.rs
index 4426f51..37cfea4 100644
--- a/rutabaga_gfx/src/cross_domain/cross_domain.rs
+++ b/rutabaga_gfx/src/cross_domain/cross_domain.rs
@@ -6,9 +6,20 @@
 //! boundaries.
 
 use std::collections::BTreeMap as Map;
+use std::collections::VecDeque;
+use std::convert::TryInto;
+use std::fs::File;
+use std::io::{Seek, SeekFrom};
 use std::mem::size_of;
 use std::os::unix::net::UnixStream;
+use std::ptr::copy_nonoverlapping;
 use std::sync::Arc;
+use std::thread;
+
+use base::{
+    error, pipe, AsRawDescriptor, Event, FileFlags, FileReadWriteVolatile, FromRawDescriptor,
+    PollToken, SafeDescriptor, ScmSocket, WaitContext,
+};
 
 use crate::cross_domain::cross_domain_protocol::*;
 use crate::rutabaga_core::{RutabagaComponent, RutabagaContext, RutabagaResource};
@@ -19,13 +30,79 @@
 
 use data_model::{DataInit, VolatileMemory, VolatileSlice};
 
-use sync::Mutex;
+use sync::{Condvar, Mutex};
+
+const CROSS_DOMAIN_DEFAULT_BUFFER_SIZE: usize = 4096;
+const CROSS_DOMAIN_MAX_SEND_RECV_SIZE: usize =
+    CROSS_DOMAIN_DEFAULT_BUFFER_SIZE - size_of::<CrossDomainSendReceive>();
+
+#[derive(PollToken)]
+enum CrossDomainToken {
+    ContextChannel,
+    WaylandReadPipe(u32),
+    Resample,
+    Kill,
+}
+
+enum CrossDomainItem {
+    ImageRequirements(ImageMemoryRequirements),
+    WaylandKeymap(SafeDescriptor),
+    WaylandReadPipe(File),
+    WaylandWritePipe(File),
+}
+
+enum CrossDomainJob {
+    HandleFence(RutabagaFenceData),
+    AddReadPipe(u32),
+}
+
+enum RingWrite<'a, T> {
+    Write(T, Option<&'a [u8]>),
+    WriteFromFile(CrossDomainReadWrite, &'a mut File, bool),
+}
+
+type CrossDomainResources = Arc<Mutex<Map<u32, CrossDomainResource>>>;
+type CrossDomainJobs = Mutex<Option<VecDeque<CrossDomainJob>>>;
+type CrossDomainItemState = Arc<Mutex<CrossDomainItems>>;
 
 struct CrossDomainResource {
     pub handle: Option<Arc<RutabagaHandle>>,
     pub backing_iovecs: Option<Vec<RutabagaIovec>>,
 }
 
+struct CrossDomainItems {
+    descriptor_id: u32,
+    requirements_blob_id: u32,
+    table: Map<u32, CrossDomainItem>,
+}
+
+struct CrossDomainState {
+    context_resources: CrossDomainResources,
+    ring_id: u32,
+    connection: Option<UnixStream>,
+    jobs: CrossDomainJobs,
+    jobs_cvar: Condvar,
+}
+
+struct CrossDomainWorker {
+    wait_ctx: WaitContext<CrossDomainToken>,
+    state: Arc<CrossDomainState>,
+    item_state: CrossDomainItemState,
+    fence_handler: RutabagaFenceHandler,
+}
+
+struct CrossDomainContext {
+    channels: Option<Vec<RutabagaChannel>>,
+    gralloc: Arc<Mutex<RutabagaGralloc>>,
+    state: Option<Arc<CrossDomainState>>,
+    context_resources: CrossDomainResources,
+    item_state: CrossDomainItemState,
+    fence_handler: RutabagaFenceHandler,
+    worker_thread: Option<thread::JoinHandle<RutabagaResult<()>>>,
+    resample_evt: Option<Event>,
+    kill_evt: Option<Event>,
+}
+
 /// The CrossDomain component contains a list of channels that the guest may connect to and the
 /// ability to allocate memory.
 pub struct CrossDomain {
@@ -33,16 +110,369 @@
     gralloc: Arc<Mutex<RutabagaGralloc>>,
 }
 
-struct CrossDomainContext {
-    channels: Option<Vec<RutabagaChannel>>,
-    gralloc: Arc<Mutex<RutabagaGralloc>>,
-    connection: Option<UnixStream>,
-    ring_id: u32,
-    requirements_blobs: Map<u64, ImageMemoryRequirements>,
-    context_resources: Map<u32, CrossDomainResource>,
-    last_fence_data: Option<RutabagaFenceData>,
-    fence_handler: RutabagaFenceHandler,
-    blob_id: u64,
+// TODO(gurchetansingh): optimize the item tracker.  Each requirements blob is long-lived and can
+// be stored in a Slab or vector.  Descriptors received from the Wayland socket *seem* to come one
+// at a time, and can be stored as options.  Need to confirm.
+fn add_item(item_state: &CrossDomainItemState, item: CrossDomainItem) -> u32 {
+    let mut items = item_state.lock();
+
+    let item_id = match item {
+        CrossDomainItem::ImageRequirements(_) => {
+            items.requirements_blob_id += 2;
+            items.requirements_blob_id
+        }
+        _ => {
+            items.descriptor_id += 2;
+            items.descriptor_id
+        }
+    };
+
+    items.table.insert(item_id, item);
+
+    item_id
+}
+
+// Determine type of OS-specific descriptor.  See `from_file` in wl.rs  for explantation on the
+// current, Linux-based method.
+fn descriptor_analysis(
+    descriptor: &mut File,
+    descriptor_type: &mut u32,
+    size: &mut u32,
+) -> RutabagaResult<()> {
+    match descriptor.seek(SeekFrom::End(0)) {
+        Ok(seek_size) => {
+            *descriptor_type = CROSS_DOMAIN_ID_TYPE_VIRTGPU_BLOB;
+            *size = seek_size.try_into()?;
+            Ok(())
+        }
+        _ => {
+            // Expect to receive the write end of a Wayland pipe only.
+            *descriptor_type = match FileFlags::from_file(descriptor) {
+                Ok(FileFlags::Write) => CROSS_DOMAIN_ID_TYPE_WRITE_PIPE,
+                _ => return Err(RutabagaError::SpecViolation),
+            };
+            Ok(())
+        }
+    }
+}
+
+impl Default for CrossDomainItems {
+    fn default() -> Self {
+        // Odd for descriptors, and even for requirement blobs.
+        CrossDomainItems {
+            descriptor_id: 1,
+            requirements_blob_id: 2,
+            table: Default::default(),
+        }
+    }
+}
+
+impl CrossDomainState {
+    fn new(
+        ring_id: u32,
+        context_resources: CrossDomainResources,
+        connection: Option<UnixStream>,
+    ) -> CrossDomainState {
+        CrossDomainState {
+            ring_id,
+            context_resources,
+            connection,
+            jobs: Mutex::new(Some(VecDeque::new())),
+            jobs_cvar: Condvar::new(),
+        }
+    }
+
+    fn add_job(&self, job: CrossDomainJob) {
+        let mut jobs = self.jobs.lock();
+        if let Some(queue) = jobs.as_mut() {
+            queue.push_back(job);
+            self.jobs_cvar.notify_one();
+        }
+    }
+
+    fn end_jobs(&self) {
+        let mut jobs = self.jobs.lock();
+        *jobs = None;
+        // Only one worker thread in the current implementation.
+        self.jobs_cvar.notify_one();
+    }
+
+    fn wait_for_job(&self) -> Option<CrossDomainJob> {
+        let mut jobs = self.jobs.lock();
+        loop {
+            match jobs.as_mut()?.pop_front() {
+                Some(job) => return Some(job),
+                None => jobs = self.jobs_cvar.wait(jobs),
+            }
+        }
+    }
+
+    fn write_to_ring<T>(&self, mut ring_write: RingWrite<T>) -> RutabagaResult<usize>
+    where
+        T: DataInit,
+    {
+        let mut context_resources = self.context_resources.lock();
+        let mut bytes_read: usize = 0;
+
+        let resource = context_resources
+            .get_mut(&self.ring_id)
+            .ok_or(RutabagaError::InvalidResourceId)?;
+
+        let iovecs = resource
+            .backing_iovecs
+            .as_mut()
+            .ok_or(RutabagaError::InvalidIovec)?;
+
+        // Safe because we've verified the iovecs are attached and owned only by this context.
+        let slice =
+            unsafe { VolatileSlice::from_raw_parts(iovecs[0].base as *mut u8, iovecs[0].len) };
+
+        match ring_write {
+            RingWrite::Write(cmd, opaque_data_opt) => {
+                slice.copy_from(&[cmd]);
+                if let Some(opaque_data) = opaque_data_opt {
+                    let offset = size_of::<T>();
+                    let sub_slice = slice.sub_slice(offset, opaque_data.len())?;
+                    let dst_ptr = sub_slice.as_mut_ptr();
+                    let src_ptr = opaque_data.as_ptr();
+
+                    // Safe because:
+                    //
+                    // (1) The volatile slice has atleast `opaque_data.len()' bytes.
+                    // (2) The both the destination and source are non-overlapping.
+                    unsafe {
+                        copy_nonoverlapping(src_ptr, dst_ptr, opaque_data.len());
+                    }
+                }
+            }
+            RingWrite::WriteFromFile(mut cmd_read, ref mut file, readable) => {
+                let offset = size_of::<CrossDomainReadWrite>();
+                let sub_slice = slice.offset(offset)?;
+
+                if readable {
+                    bytes_read = file.read_volatile(sub_slice)?;
+                }
+
+                if bytes_read == 0 {
+                    cmd_read.hang_up = 1;
+                }
+
+                cmd_read.opaque_data_size = bytes_read.try_into()?;
+                slice.copy_from(&[cmd_read]);
+            }
+        }
+
+        Ok(bytes_read)
+    }
+
+    fn send_msg(
+        &self,
+        opaque_data: &[VolatileSlice],
+        descriptors: &[i32],
+    ) -> RutabagaResult<usize> {
+        self.connection
+            .as_ref()
+            .ok_or(RutabagaError::SpecViolation)
+            .and_then(|conn| Ok(conn.send_with_fds(opaque_data, descriptors)?))
+    }
+
+    fn receive_msg(
+        &self,
+        opaque_data: &mut Vec<u8>,
+        descriptors: &mut [i32; CROSS_DOMAIN_MAX_IDENTIFIERS],
+    ) -> RutabagaResult<(usize, Vec<File>)> {
+        // If any errors happen, the socket will get dropped, preventing more reading.
+        if let Some(connection) = &self.connection {
+            let mut files: Vec<File> = Vec::new();
+            let (len, file_count) = connection.recv_with_fds(&mut opaque_data[..], descriptors)?;
+
+            for descriptor in descriptors.iter_mut().take(file_count) {
+                // Safe since the descriptors from recv_with_fds(..) are owned by us and valid.
+                let file = unsafe { File::from_raw_descriptor(*descriptor) };
+                files.push(file);
+            }
+
+            Ok((len, files))
+        } else {
+            Err(RutabagaError::SpecViolation)
+        }
+    }
+}
+
+impl CrossDomainWorker {
+    fn new(
+        wait_ctx: WaitContext<CrossDomainToken>,
+        state: Arc<CrossDomainState>,
+        item_state: CrossDomainItemState,
+        fence_handler: RutabagaFenceHandler,
+    ) -> CrossDomainWorker {
+        CrossDomainWorker {
+            wait_ctx,
+            state,
+            item_state,
+            fence_handler,
+        }
+    }
+
+    // Handles the fence according the the token according to the event token.  On success, a
+    // boolean value indicating whether the worker thread should be stopped is returned.
+    fn handle_fence(
+        &mut self,
+        fence: RutabagaFenceData,
+        resample_evt: &Event,
+        receive_buf: &mut Vec<u8>,
+    ) -> RutabagaResult<bool> {
+        let events = self.wait_ctx.wait()?;
+        let mut stop_thread = false;
+
+        for event in &events {
+            match event.token {
+                CrossDomainToken::ContextChannel => {
+                    let mut descriptors = [0; CROSS_DOMAIN_MAX_IDENTIFIERS];
+
+                    let (len, files) = self.state.receive_msg(receive_buf, &mut descriptors)?;
+                    if len != 0 || files.len() != 0 {
+                        let mut cmd_receive: CrossDomainSendReceive = Default::default();
+
+                        let num_files = files.len();
+                        cmd_receive.hdr.cmd = CROSS_DOMAIN_CMD_RECEIVE;
+                        cmd_receive.num_identifiers = files.len().try_into()?;
+                        cmd_receive.opaque_data_size = len.try_into()?;
+
+                        let iter = cmd_receive
+                            .identifiers
+                            .iter_mut()
+                            .zip(cmd_receive.identifier_types.iter_mut())
+                            .zip(cmd_receive.identifier_sizes.iter_mut())
+                            .zip(files.into_iter())
+                            .take(num_files);
+
+                        for (((identifier, mut identifier_type), mut identifier_size), mut file) in
+                            iter
+                        {
+                            // Safe since the descriptors from receive_msg(..) are owned by us and valid.
+                            descriptor_analysis(
+                                &mut file,
+                                &mut identifier_type,
+                                &mut identifier_size,
+                            )?;
+
+                            *identifier = match *identifier_type {
+                                CROSS_DOMAIN_ID_TYPE_VIRTGPU_BLOB => add_item(
+                                    &self.item_state,
+                                    CrossDomainItem::WaylandKeymap(file.into()),
+                                ),
+                                CROSS_DOMAIN_ID_TYPE_WRITE_PIPE => add_item(
+                                    &self.item_state,
+                                    CrossDomainItem::WaylandWritePipe(file),
+                                ),
+                                _ => return Err(RutabagaError::SpecViolation),
+                            };
+                        }
+
+                        self.state.write_to_ring(RingWrite::Write(
+                            cmd_receive,
+                            Some(&receive_buf[0..len]),
+                        ))?;
+                        self.fence_handler.call(fence);
+                    }
+                }
+                CrossDomainToken::Resample => {
+                    // The resample event is triggered when the job queue is in the following state:
+                    //
+                    // [CrossDomain::AddReadPipe(..)] -> END
+                    //
+                    // After this event, the job queue will be the following state:
+                    //
+                    // [CrossDomain::AddReadPipe(..)] -> [CrossDomain::HandleFence(..)] -> END
+                    //
+                    // Fence handling is tied to some new data transfer across a pollable
+                    // descriptor.  When we're adding new descriptors, we stop polling.
+                    resample_evt.read()?;
+                    self.state.add_job(CrossDomainJob::HandleFence(fence));
+                }
+                CrossDomainToken::WaylandReadPipe(pipe_id) => {
+                    let mut items = self.item_state.lock();
+                    let mut cmd_read: CrossDomainReadWrite = Default::default();
+                    let bytes_read;
+
+                    cmd_read.hdr.cmd = CROSS_DOMAIN_CMD_READ;
+                    cmd_read.identifier = pipe_id;
+
+                    let item = items
+                        .table
+                        .get_mut(&pipe_id)
+                        .ok_or(RutabagaError::SpecViolation)?;
+
+                    match item {
+                        CrossDomainItem::WaylandReadPipe(ref mut file) => {
+                            let ring_write =
+                                RingWrite::WriteFromFile(cmd_read, file, event.is_readable);
+                            bytes_read = self
+                                .state
+                                .write_to_ring::<CrossDomainReadWrite>(ring_write)?;
+
+                            // Zero bytes read indicates end-of-file on POSIX.
+                            if event.is_hungup && bytes_read == 0 {
+                                self.wait_ctx.delete(file)?;
+                            }
+                        }
+                        _ => return Err(RutabagaError::SpecViolation),
+                    }
+
+                    if event.is_hungup && bytes_read == 0 {
+                        items.table.remove(&pipe_id);
+                    }
+
+                    self.fence_handler.call(fence);
+                }
+                CrossDomainToken::Kill => {
+                    self.fence_handler.call(fence);
+                    stop_thread = true;
+                }
+            }
+        }
+
+        Ok(stop_thread)
+    }
+
+    fn run(&mut self, kill_evt: Event, resample_evt: Event) -> RutabagaResult<()> {
+        self.wait_ctx
+            .add(&resample_evt, CrossDomainToken::Resample)?;
+        self.wait_ctx.add(&kill_evt, CrossDomainToken::Kill)?;
+        let mut receive_buf: Vec<u8> = vec![0; CROSS_DOMAIN_MAX_SEND_RECV_SIZE];
+
+        while let Some(job) = self.state.wait_for_job() {
+            match job {
+                CrossDomainJob::HandleFence(fence) => {
+                    match self.handle_fence(fence, &resample_evt, &mut receive_buf) {
+                        Ok(true) => return Ok(()),
+                        Ok(false) => (),
+                        Err(e) => {
+                            error!("Worker halting due to: {}", e);
+                            return Err(e);
+                        }
+                    }
+                }
+                CrossDomainJob::AddReadPipe(read_pipe_id) => {
+                    let items = self.item_state.lock();
+                    let item = items
+                        .table
+                        .get(&read_pipe_id)
+                        .ok_or(RutabagaError::SpecViolation)?;
+
+                    match item {
+                        CrossDomainItem::WaylandReadPipe(file) => self
+                            .wait_ctx
+                            .add(file, CrossDomainToken::WaylandReadPipe(read_pipe_id))?,
+                        _ => return Err(RutabagaError::SpecViolation),
+                    }
+                }
+            }
+        }
+
+        Ok(())
+    }
 }
 
 impl CrossDomain {
@@ -61,11 +491,17 @@
 
 impl CrossDomainContext {
     fn initialize(&mut self, cmd_init: &CrossDomainInit) -> RutabagaResult<()> {
-        if !self.context_resources.contains_key(&cmd_init.ring_id) {
+        if !self
+            .context_resources
+            .lock()
+            .contains_key(&cmd_init.ring_id)
+        {
             return Err(RutabagaError::InvalidResourceId);
         }
 
-        self.ring_id = cmd_init.ring_id;
+        let ring_id = cmd_init.ring_id;
+        let context_resources = self.context_resources.clone();
+
         // Zero means no requested channel.
         if cmd_init.channel_type != 0 {
             let channels = self.channels.take().ok_or(RutabagaError::SpecViolation)?;
@@ -75,7 +511,47 @@
                 .ok_or(RutabagaError::SpecViolation)?
                 .base_channel;
 
-            self.connection = Some(UnixStream::connect(base_channel)?);
+            let connection = UnixStream::connect(base_channel)?;
+
+            let (kill_evt, thread_kill_evt) = Event::new().and_then(|e| Ok((e.try_clone()?, e)))?;
+            let (resample_evt, thread_resample_evt) =
+                Event::new().and_then(|e| Ok((e.try_clone()?, e)))?;
+
+            let wait_ctx =
+                WaitContext::build_with(&[(&connection, CrossDomainToken::ContextChannel)])?;
+
+            let state = Arc::new(CrossDomainState::new(
+                ring_id,
+                context_resources,
+                Some(connection),
+            ));
+
+            let thread_state = state.clone();
+            let thread_items = self.item_state.clone();
+            let thread_fence_handler = self.fence_handler.clone();
+
+            let worker_result = thread::Builder::new()
+                .name("cross domain".to_string())
+                .spawn(move || -> RutabagaResult<()> {
+                    CrossDomainWorker::new(
+                        wait_ctx,
+                        thread_state,
+                        thread_items,
+                        thread_fence_handler,
+                    )
+                    .run(thread_kill_evt, thread_resample_evt)
+                });
+
+            self.worker_thread = Some(worker_result.unwrap());
+            self.state = Some(state);
+            self.resample_evt = Some(resample_evt);
+            self.kill_evt = Some(kill_evt);
+        } else {
+            self.state = Some(Arc::new(CrossDomainState::new(
+                ring_id,
+                context_resources,
+                None,
+            )));
         }
 
         Ok(())
@@ -92,7 +568,6 @@
             flags: RutabagaGrallocFlags::new(cmd_get_reqs.flags),
         };
 
-        self.blob_id += 1;
         let reqs = self.gralloc.lock().get_image_memory_requirements(info)?;
 
         let mut response = CrossDomainImageRequirements {
@@ -100,9 +575,8 @@
             offsets: reqs.offsets,
             modifier: reqs.modifier,
             size: reqs.size,
-            blob_id: self.blob_id,
+            blob_id: 0,
             map_info: reqs.map_info,
-            pad: 0,
             memory_idx: -1,
             physical_device_idx: -1,
         };
@@ -112,26 +586,156 @@
             response.physical_device_idx = vk_info.physical_device_idx as i32;
         }
 
-        let resource = self
-            .context_resources
-            .get_mut(&self.ring_id)
-            .ok_or(RutabagaError::InvalidResourceId)?;
+        if let Some(state) = &self.state {
+            response.blob_id = add_item(&self.item_state, CrossDomainItem::ImageRequirements(reqs));
+            state.write_to_ring(RingWrite::Write(response, None))?;
+            Ok(())
+        } else {
+            Err(RutabagaError::SpecViolation)
+        }
+    }
 
-        let iovecs = resource
-            .backing_iovecs
-            .take()
-            .ok_or(RutabagaError::InvalidIovec)?;
+    fn send(
+        &self,
+        cmd_send: &CrossDomainSendReceive,
+        opaque_data: &[VolatileSlice],
+    ) -> RutabagaResult<()> {
+        let mut descriptors = [0; CROSS_DOMAIN_MAX_IDENTIFIERS];
 
-        // Safe because we've verified the iovecs are attached and owned only by this context.
-        let slice =
-            unsafe { VolatileSlice::from_raw_parts(iovecs[0].base as *mut u8, iovecs[0].len) };
+        let mut write_pipe_opt: Option<File> = None;
+        let mut read_pipe_id_opt: Option<u32> = None;
 
-        // The copy_from(..) method guarantees out of bounds buffer accesses will not occur.
-        slice.copy_from(&[response]);
-        resource.backing_iovecs = Some(iovecs);
-        self.requirements_blobs.insert(self.blob_id, reqs);
+        let num_identifiers = cmd_send.num_identifiers.try_into()?;
+
+        if num_identifiers > CROSS_DOMAIN_MAX_IDENTIFIERS {
+            return Err(RutabagaError::SpecViolation);
+        }
+
+        let iter = cmd_send
+            .identifiers
+            .iter()
+            .zip(cmd_send.identifier_types.iter())
+            .zip(descriptors.iter_mut())
+            .take(num_identifiers);
+
+        for ((identifier, identifier_type), descriptor) in iter {
+            if *identifier_type == CROSS_DOMAIN_ID_TYPE_VIRTGPU_BLOB {
+                let context_resources = self.context_resources.lock();
+
+                let context_resource = context_resources
+                    .get(identifier)
+                    .ok_or(RutabagaError::InvalidResourceId)?;
+
+                if let Some(ref handle) = context_resource.handle {
+                    *descriptor = handle.os_handle.as_raw_descriptor();
+                } else {
+                    return Err(RutabagaError::SpecViolation);
+                }
+            } else if *identifier_type == CROSS_DOMAIN_ID_TYPE_READ_PIPE {
+                // In practice, just 1 write or read pipe per send is observed.  If we encounter
+                // more, this can be changed later.
+                if write_pipe_opt.is_some() {
+                    return Err(RutabagaError::SpecViolation);
+                }
+
+                let (read_pipe, write_pipe) = pipe(true)?;
+
+                *descriptor = write_pipe.as_raw_descriptor();
+                let read_pipe_id: u32 = add_item(
+                    &self.item_state,
+                    CrossDomainItem::WaylandReadPipe(read_pipe),
+                );
+
+                // For Wayland read pipes, the guest guesses which identifier the host will use to
+                // avoid waiting for the host to generate one.  Validate guess here.  This works
+                // because of the way Sommelier copy + paste works.  If the Sommelier sequence of events
+                // changes, it's always possible to wait for the host response.
+                if read_pipe_id != *identifier {
+                    return Err(RutabagaError::SpecViolation);
+                }
+
+                // The write pipe needs to be dropped after the send_msg(..) call is complete, so the read pipe
+                // can receive subsequent hang-up events.
+                write_pipe_opt = Some(write_pipe);
+                read_pipe_id_opt = Some(read_pipe_id);
+            } else {
+                // Don't know how to handle anything else yet.
+                return Err(RutabagaError::SpecViolation);
+            }
+        }
+
+        if let (Some(state), Some(resample_evt)) = (&self.state, &self.resample_evt) {
+            state.send_msg(opaque_data, &descriptors[..num_identifiers])?;
+
+            if let Some(read_pipe_id) = read_pipe_id_opt {
+                state.add_job(CrossDomainJob::AddReadPipe(read_pipe_id));
+                resample_evt.write(1)?;
+            }
+        } else {
+            return Err(RutabagaError::SpecViolation);
+        }
+
         Ok(())
     }
+
+    fn write(
+        &self,
+        cmd_write: &CrossDomainReadWrite,
+        opaque_data: VolatileSlice,
+    ) -> RutabagaResult<()> {
+        let mut items = self.item_state.lock();
+
+        // Most of the time, hang-up and writing will be paired.  In lieu of this, remove the
+        // item rather than getting a reference.  In case of an error, there's not much to do
+        // besides reporting it.
+        let item = items
+            .table
+            .remove(&cmd_write.identifier)
+            .ok_or(RutabagaError::SpecViolation)?;
+
+        let len: usize = cmd_write.opaque_data_size.try_into()?;
+        match item {
+            CrossDomainItem::WaylandWritePipe(mut file) => {
+                if len != 0 {
+                    file.write_all_volatile(opaque_data)?;
+                }
+
+                if cmd_write.hang_up == 0 {
+                    items.table.insert(
+                        cmd_write.identifier,
+                        CrossDomainItem::WaylandWritePipe(file),
+                    );
+                }
+
+                Ok(())
+            }
+            _ => Err(RutabagaError::SpecViolation),
+        }
+    }
+}
+
+impl Drop for CrossDomainContext {
+    fn drop(&mut self) {
+        if let Some(state) = &self.state {
+            state.end_jobs();
+        }
+
+        if let Some(kill_evt) = self.kill_evt.take() {
+            // Don't join the the worker thread unless the write to `kill_evt` is successful.
+            // Otherwise, this may block indefinitely.
+            match kill_evt.write(1) {
+                Ok(_) => (),
+                Err(e) => {
+                    error!("failed to write cross domain kill event: {}", e);
+                    return;
+                }
+            }
+
+            if let Some(worker_thread) = self.worker_thread.take() {
+                let _ = worker_thread.join();
+            }
+        }
+    }
 }
 
 impl RutabagaContext for CrossDomainContext {
@@ -141,45 +745,88 @@
         resource_create_blob: ResourceCreateBlob,
         handle: Option<RutabagaHandle>,
     ) -> RutabagaResult<RutabagaResource> {
-        let reqs = self
-            .requirements_blobs
-            .get(&resource_create_blob.blob_id)
-            .ok_or(RutabagaError::SpecViolation)?;
+        let item_id = resource_create_blob.blob_id as u32;
 
-        if reqs.size != resource_create_blob.size {
-            return Err(RutabagaError::SpecViolation);
+        // We don't want to remove requirements blobs, since they can be used for subsequent
+        // allocations.  We do want to remove Wayland keymaps, since they are mapped the guest
+        // and then never used again.  The current protocol encodes this as divisiblity by 2.
+        if item_id % 2 == 0 {
+            let items = self.item_state.lock();
+            let item = items
+                .table
+                .get(&item_id)
+                .ok_or(RutabagaError::SpecViolation)?;
+
+            match item {
+                CrossDomainItem::ImageRequirements(reqs) => {
+                    if reqs.size != resource_create_blob.size {
+                        return Err(RutabagaError::SpecViolation);
+                    }
+
+                    // Strictly speaking, it's against the virtio-gpu spec to allocate memory in the context
+                    // create blob function, which says "the actual allocation is done via
+                    // VIRTIO_GPU_CMD_SUBMIT_3D."  However, atomic resource creation is easiest for the
+                    // cross-domain use case, so whatever.
+                    let hnd = match handle {
+                        Some(handle) => handle,
+                        None => self.gralloc.lock().allocate_memory(*reqs)?,
+                    };
+
+                    let info_3d = Resource3DInfo {
+                        width: reqs.info.width,
+                        height: reqs.info.height,
+                        drm_fourcc: reqs.info.drm_format.into(),
+                        strides: reqs.strides,
+                        offsets: reqs.offsets,
+                        modifier: reqs.modifier,
+                    };
+
+                    Ok(RutabagaResource {
+                        resource_id,
+                        handle: Some(Arc::new(hnd)),
+                        blob: true,
+                        blob_mem: resource_create_blob.blob_mem,
+                        blob_flags: resource_create_blob.blob_flags,
+                        map_info: Some(reqs.map_info),
+                        info_2d: None,
+                        info_3d: Some(info_3d),
+                        vulkan_info: reqs.vulkan_info,
+                        backing_iovecs: None,
+                    })
+                }
+                _ => Err(RutabagaError::SpecViolation),
+            }
+        } else {
+            let item = self
+                .item_state
+                .lock()
+                .table
+                .remove(&item_id)
+                .ok_or(RutabagaError::SpecViolation)?;
+
+            match item {
+                CrossDomainItem::WaylandKeymap(descriptor) => {
+                    let hnd = RutabagaHandle {
+                        os_handle: descriptor,
+                        handle_type: RUTABAGA_MEM_HANDLE_TYPE_OPAQUE_FD,
+                    };
+
+                    Ok(RutabagaResource {
+                        resource_id,
+                        handle: Some(Arc::new(hnd)),
+                        blob: true,
+                        blob_mem: resource_create_blob.blob_mem,
+                        blob_flags: resource_create_blob.blob_flags,
+                        map_info: Some(RUTABAGA_MAP_CACHE_CACHED),
+                        info_2d: None,
+                        info_3d: None,
+                        vulkan_info: None,
+                        backing_iovecs: None,
+                    })
+                }
+                _ => Err(RutabagaError::SpecViolation),
+            }
         }
-
-        // Strictly speaking, it's against the virtio-gpu spec to allocate memory in the context
-        // create blob function, which says "the actual allocation is done via
-        // VIRTIO_GPU_CMD_SUBMIT_3D."  However, atomic resource creation is easiest for the
-        // cross-domain use case, so whatever.
-        let hnd = match handle {
-            Some(handle) => handle,
-            None => self.gralloc.lock().allocate_memory(*reqs)?,
-        };
-
-        let info_3d = Resource3DInfo {
-            width: reqs.info.width,
-            height: reqs.info.height,
-            drm_fourcc: reqs.info.drm_format.into(),
-            strides: reqs.strides,
-            offsets: reqs.offsets,
-            modifier: reqs.modifier,
-        };
-
-        Ok(RutabagaResource {
-            resource_id,
-            handle: Some(Arc::new(hnd)),
-            blob: true,
-            blob_mem: resource_create_blob.blob_mem,
-            blob_flags: resource_create_blob.blob_flags,
-            map_info: Some(reqs.map_info),
-            info_2d: None,
-            info_3d: Some(info_3d),
-            vulkan_info: reqs.vulkan_info,
-            backing_iovecs: None,
-        })
     }
 
     fn submit_cmd(&mut self, commands: &mut [u8]) -> RutabagaResult<()> {
@@ -202,6 +849,27 @@
 
                     self.get_image_requirements(&cmd_get_reqs)?;
                 }
+                CROSS_DOMAIN_CMD_SEND => {
+                    let opaque_data_offset = size_of::<CrossDomainSendReceive>();
+                    let cmd_send: CrossDomainSendReceive = slice.get_ref(offset)?.load();
+
+                    let opaque_data =
+                        slice.sub_slice(opaque_data_offset, cmd_send.opaque_data_size as usize)?;
+
+                    self.send(&cmd_send, &[opaque_data])?;
+                }
+                CROSS_DOMAIN_CMD_POLL => {
+                    // Actual polling is done in the subsequent when creating a fence.
+                }
+                CROSS_DOMAIN_CMD_WRITE => {
+                    let opaque_data_offset = size_of::<CrossDomainReadWrite>();
+                    let cmd_write: CrossDomainReadWrite = slice.get_ref(offset)?.load();
+
+                    let opaque_data =
+                        slice.sub_slice(opaque_data_offset, cmd_write.opaque_data_size as usize)?;
+
+                    self.write(&cmd_write, opaque_data)?;
+                }
                 _ => return Err(RutabagaError::Unsupported),
             }
 
@@ -213,7 +881,7 @@
 
     fn attach(&mut self, resource: &mut RutabagaResource) {
         if resource.blob_mem == RUTABAGA_BLOB_MEM_GUEST {
-            self.context_resources.insert(
+            self.context_resources.lock().insert(
                 resource.resource_id,
                 CrossDomainResource {
                     handle: None,
@@ -221,7 +889,7 @@
                 },
             );
         } else if let Some(ref handle) = resource.handle {
-            self.context_resources.insert(
+            self.context_resources.lock().insert(
                 resource.resource_id,
                 CrossDomainResource {
                     handle: Some(handle.clone()),
@@ -232,21 +900,21 @@
     }
 
     fn detach(&mut self, resource: &RutabagaResource) {
-        self.context_resources.remove(&resource.resource_id);
+        self.context_resources.lock().remove(&resource.resource_id);
     }
 
     fn context_create_fence(&mut self, fence_data: RutabagaFenceData) -> RutabagaResult<()> {
-        self.fence_handler.call(fence_data);
-        self.last_fence_data = Some(fence_data);
-        Ok(())
-    }
-
-    fn context_poll(&mut self) -> Option<Vec<RutabagaFenceData>> {
-        let fence_data = self.last_fence_data.take();
-        match fence_data {
-            Some(fence_data) => Some(vec![fence_data]),
-            None => None,
+        match fence_data.ring_idx {
+            CROSS_DOMAIN_QUERY_RING => self.fence_handler.call(fence_data),
+            CROSS_DOMAIN_CHANNEL_RING => {
+                if let Some(state) = &self.state {
+                    state.add_job(CrossDomainJob::HandleFence(fence_data));
+                }
+            }
+            _ => return Err(RutabagaError::SpecViolation),
         }
+
+        Ok(())
     }
 }
 
@@ -271,7 +939,7 @@
             caps.supports_external_gpu_memory = 1;
         }
 
-        // Version 1 supports all commands up to and including CROSS_DOMAIN_CMD_RECEIVE.
+        // Version 1 supports all commands up to and including CROSS_DOMAIN_CMD_WRITE.
         caps.version = 1;
         caps.as_slice().to_vec()
     }
@@ -285,13 +953,13 @@
         Ok(Box::new(CrossDomainContext {
             channels: self.channels.clone(),
             gralloc: self.gralloc.clone(),
-            connection: None,
-            ring_id: 0,
-            requirements_blobs: Default::default(),
-            context_resources: Default::default(),
-            last_fence_data: None,
+            state: None,
+            context_resources: Arc::new(Mutex::new(Default::default())),
+            item_state: Arc::new(Mutex::new(Default::default())),
             fence_handler,
-            blob_id: 0,
+            worker_thread: None,
+            resample_evt: None,
+            kill_evt: None,
         }))
     }
 }
diff --git a/rutabaga_gfx/src/cross_domain/cross_domain_protocol.rs b/rutabaga_gfx/src/cross_domain/cross_domain_protocol.rs
index 25dd6d3..d3f6865 100644
--- a/rutabaga_gfx/src/cross_domain/cross_domain_protocol.rs
+++ b/rutabaga_gfx/src/cross_domain/cross_domain_protocol.rs
@@ -12,11 +12,37 @@
 /// Cross-domain commands (only a maximum of 255 supported)
 pub const CROSS_DOMAIN_CMD_INIT: u8 = 1;
 pub const CROSS_DOMAIN_CMD_GET_IMAGE_REQUIREMENTS: u8 = 2;
+pub const CROSS_DOMAIN_CMD_POLL: u8 = 3;
+pub const CROSS_DOMAIN_CMD_SEND: u8 = 4;
+pub const CROSS_DOMAIN_CMD_RECEIVE: u8 = 5;
+pub const CROSS_DOMAIN_CMD_READ: u8 = 6;
+pub const CROSS_DOMAIN_CMD_WRITE: u8 = 7;
 
 /// Channel types (must match rutabaga channel types)
 pub const CROSS_DOMAIN_CHANNEL_TYPE_WAYLAND: u32 = 0x0001;
 pub const CROSS_DOMAIN_CHANNEL_TYPE_CAMERA: u32 = 0x0002;
 
+/// The maximum number of identifiers (value inspired by wp_linux_dmabuf)
+pub const CROSS_DOMAIN_MAX_IDENTIFIERS: usize = 4;
+
+/// virtgpu memory resource ID.  Also works with non-blob memory resources, despite the name.
+pub const CROSS_DOMAIN_ID_TYPE_VIRTGPU_BLOB: u32 = 1;
+/// virtgpu synchronization resource id.
+pub const CROSS_DOMAIN_ID_TYPE_VIRTGPU_SYNC: u32 = 2;
+/// ID for Wayland pipe used for reading.  The reading is done by the guest proxy and the host
+/// proxy.  The host sends the write end of the proxied pipe over the host Wayland socket.
+pub const CROSS_DOMAIN_ID_TYPE_READ_PIPE: u32 = 3;
+/// ID for Wayland pipe used for writing.  The writing is done by the guest and the host proxy.
+/// The host receives the write end of the pipe over the host Wayland socket.
+pub const CROSS_DOMAIN_ID_TYPE_WRITE_PIPE: u32 = 4;
+
+/// No ring
+pub const CROSS_DOMAIN_RING_NONE: u32 = 0xffffffff;
+/// A ring for metadata queries.
+pub const CROSS_DOMAIN_QUERY_RING: u32 = 0;
+/// A ring based on this particular context's channel.
+pub const CROSS_DOMAIN_CHANNEL_RING: u32 = 1;
+
 #[repr(C)]
 #[derive(Copy, Clone, Default)]
 pub struct CrossDomainCapabilities {
@@ -35,9 +61,8 @@
     pub offsets: [u32; 4],
     pub modifier: u64,
     pub size: u64,
-    pub blob_id: u64,
+    pub blob_id: u32,
     pub map_info: u32,
-    pub pad: u32,
     pub memory_idx: i32,
     pub physical_device_idx: i32,
 }
@@ -48,7 +73,7 @@
 #[derive(Copy, Clone, Default)]
 pub struct CrossDomainHeader {
     pub cmd: u8,
-    pub fence_ctx_idx: u8,
+    pub ring_idx: u8,
     pub cmd_size: u16,
     pub pad: u32,
 }
@@ -76,3 +101,30 @@
 }
 
 unsafe impl DataInit for CrossDomainGetImageRequirements {}
+
+#[repr(C)]
+#[derive(Copy, Clone, Default)]
+pub struct CrossDomainSendReceive {
+    pub hdr: CrossDomainHeader,
+    pub num_identifiers: u32,
+    pub opaque_data_size: u32,
+    pub identifiers: [u32; CROSS_DOMAIN_MAX_IDENTIFIERS],
+    pub identifier_types: [u32; CROSS_DOMAIN_MAX_IDENTIFIERS],
+    pub identifier_sizes: [u32; CROSS_DOMAIN_MAX_IDENTIFIERS],
+    // Data of size "opaque data size follows"
+}
+
+unsafe impl DataInit for CrossDomainSendReceive {}
+
+#[repr(C)]
+#[derive(Copy, Clone, Default)]
+pub struct CrossDomainReadWrite {
+    pub hdr: CrossDomainHeader,
+    pub identifier: u32,
+    pub hang_up: u32,
+    pub opaque_data_size: u32,
+    pub pad: u32,
+    // Data of size "opaque data size follows"
+}
+
+unsafe impl DataInit for CrossDomainReadWrite {}
diff --git a/rutabaga_gfx/src/renderer_utils.rs b/rutabaga_gfx/src/renderer_utils.rs
index f116d89..f8cb7c2 100644
--- a/rutabaga_gfx/src/renderer_utils.rs
+++ b/rutabaga_gfx/src/renderer_utils.rs
@@ -64,7 +64,7 @@
                     flags: RUTABAGA_FLAG_FENCE,
                     fence_id: latest_fence as u64,
                     ctx_id: 0,
-                    fence_ctx_idx: 0,
+                    ring_idx: 0,
                 });
             }
         }
diff --git a/rutabaga_gfx/src/rutabaga_core.rs b/rutabaga_gfx/src/rutabaga_core.rs
index 6ac5055..8913d28 100644
--- a/rutabaga_gfx/src/rutabaga_core.rs
+++ b/rutabaga_gfx/src/rutabaga_core.rs
@@ -181,7 +181,7 @@
     /// Implementations must stop using `resource` in this context's command stream.
     fn detach(&mut self, _resource: &RutabagaResource);
 
-    /// Implementations must create a fence on specified `fence_ctx_idx` in `fence_data`.  This
+    /// Implementations must create a fence on specified `ring_idx` in `fence_data`.  This
     /// allows for multiple syncrhonizations timelines per RutabagaContext.
     fn context_create_fence(&mut self, _fence_data: RutabagaFenceData) -> RutabagaResult<()> {
         Err(RutabagaError::Unsupported)
@@ -276,10 +276,10 @@
     }
 
     /// Creates a fence with the given `fence_data`.
-    /// If the flags include RUTABAGA_FLAG_PARAM_FENCE_CTX_IDX, then the fence is created on a
+    /// If the flags include RUTABAGA_FLAG_INFO_RING_IDX, then the fence is created on a
     /// specific timeline on the specific context.
     pub fn create_fence(&mut self, fence_data: RutabagaFenceData) -> RutabagaResult<()> {
-        if fence_data.flags & RUTABAGA_FLAG_INFO_FENCE_CTX_IDX != 0 {
+        if fence_data.flags & RUTABAGA_FLAG_INFO_RING_IDX != 0 {
             let ctx = self
                 .contexts
                 .get_mut(&fence_data.ctx_id)
@@ -303,7 +303,7 @@
     pub fn poll(&mut self) -> Vec<RutabagaFenceData> {
         let mut completed_fences: Vec<RutabagaFenceData> = Vec::new();
         // Poll the default component -- this the global timeline which does not take into account
-        // `ctx_id` or `fence_ctx_idx`.  This path exists for OpenGL legacy reasons and 2D mode.
+        // `ctx_id` or `ring_idx`.  This path exists for OpenGL legacy reasons and 2D mode.
         let component = self
             .components
             .get_mut(&self.default_component)
@@ -315,7 +315,7 @@
             flags: RUTABAGA_FLAG_FENCE,
             fence_id: global_fence_id as u64,
             ctx_id: 0,
-            fence_ctx_idx: 0,
+            ring_idx: 0,
         });
 
         for ctx in self.contexts.values_mut() {
diff --git a/rutabaga_gfx/src/rutabaga_utils.rs b/rutabaga_gfx/src/rutabaga_utils.rs
index d5dea79..a204f04 100644
--- a/rutabaga_gfx/src/rutabaga_utils.rs
+++ b/rutabaga_gfx/src/rutabaga_utils.rs
@@ -25,12 +25,16 @@
 
 /// Represents a buffer.  `base` contains the address of a buffer, while `len` contains the length
 /// of the buffer.
+#[repr(C)]
 #[derive(Copy, Clone)]
 pub struct RutabagaIovec {
     pub base: *mut c_void,
     pub len: usize,
 }
 
+unsafe impl Send for RutabagaIovec {}
+unsafe impl Sync for RutabagaIovec {}
+
 /// 3D resource creation parameters.  Also used to create 2D resource.  Constants based on Mesa's
 /// (internal) Gallium interface.  Not in the virtio-gpu spec, but should be since dumb resources
 /// can't work with gfxstream/virglrenderer without this.
@@ -89,7 +93,7 @@
 
 /// Rutabaga flags for creating fences (fence ctx idx info not upstreamed).
 pub const RUTABAGA_FLAG_FENCE: u32 = 1 << 0;
-pub const RUTABAGA_FLAG_INFO_FENCE_CTX_IDX: u32 = 1 << 1;
+pub const RUTABAGA_FLAG_INFO_RING_IDX: u32 = 1 << 1;
 
 /// Convenience struct for Rutabaga fences
 #[derive(Copy, Clone)]
@@ -97,7 +101,7 @@
     pub flags: u32,
     pub fence_id: u64,
     pub ctx_id: u32,
-    pub fence_ctx_idx: u32,
+    pub ring_idx: u32,
 }
 
 /// Mapped memory caching flags (see virtio_gpu spec)
diff --git a/seccomp/aarch64/common_device.policy b/seccomp/aarch64/common_device.policy
index 349afe9..771a988 100644
--- a/seccomp/aarch64/common_device.policy
+++ b/seccomp/aarch64/common_device.policy
@@ -37,6 +37,7 @@
 rt_sigprocmask: 1
 rt_sigreturn: 1
 sched_getaffinity: 1
+sched_yield: 1
 sendmsg: 1
 sendto: 1
 set_robust_list: 1
@@ -44,3 +45,4 @@
 write: 1
 writev: 1
 fcntl: 1
+uname: 1
diff --git a/seccomp/aarch64/cras_audio_device.policy b/seccomp/aarch64/cras_audio_device.policy
index 60797f9..74d71b2 100644
--- a/seccomp/aarch64/cras_audio_device.policy
+++ b/seccomp/aarch64/cras_audio_device.policy
@@ -12,3 +12,6 @@
 clock_gettime: 1
 openat: return ENOENT
 prctl: arg0 == PR_SET_NAME
+timerfd_create: 1
+timerfd_gettime: 1
+timerfd_settime: 1
diff --git a/seccomp/aarch64/gpu_device.policy b/seccomp/aarch64/gpu_device.policy
index 4ceac5c..d5541ed 100644
--- a/seccomp/aarch64/gpu_device.policy
+++ b/seccomp/aarch64/gpu_device.policy
@@ -42,6 +42,7 @@
 sigaltstack: 1
 write: 1
 writev: 1
+uname: 1
 
 # Required for perfetto tracing
 getsockopt: 1
@@ -58,6 +59,7 @@
 newfstatat: 1
 getdents64: 1
 sysinfo: 1
+fstatfs: 1
 
 # 0x6400 == DRM_IOCTL_BASE, 0x8000 = KBASE_IOCTL_TYPE (mali), 0x40086200 = DMA_BUF_IOCTL_SYNC, 0x40087543 == UDMABUF_CREATE_LIST
 ioctl: arg1 & 0x6400 || arg1 & 0x8000 || arg1 == 0x40086200 || arg1 == 0x40087543
@@ -77,7 +79,6 @@
 clock_gettime: 1
 
 # Rules specific to Mesa.
-uname: 1
 sched_setscheduler: 1
 sched_setaffinity: 1
 kcmp: 1
diff --git a/seccomp/aarch64/xhci.policy b/seccomp/aarch64/xhci.policy
index 684ae0d..b949ec2 100644
--- a/seccomp/aarch64/xhci.policy
+++ b/seccomp/aarch64/xhci.policy
@@ -17,7 +17,6 @@
 setsockopt: 1
 bind: 1
 socket: arg0 == AF_NETLINK
-uname: 1
 # The following ioctls are:
 # 0x4004550d == USBDEVFS_REAPURBNDELAY32
 # 0x550b     == USBDEVFS_DISCARDURB
diff --git a/seccomp/arm/common_device.policy b/seccomp/arm/common_device.policy
index 165bfda..f0adee9 100644
--- a/seccomp/arm/common_device.policy
+++ b/seccomp/arm/common_device.policy
@@ -46,6 +46,7 @@
 rt_sigprocmask: 1
 rt_sigreturn: 1
 sched_getaffinity: 1
+sched_yield: 1
 sendmsg: 1
 sendto: 1
 set_robust_list: 1
@@ -53,3 +54,4 @@
 write: 1
 writev: 1
 fcntl64: 1
+uname: 1
diff --git a/seccomp/arm/cras_audio_device.policy b/seccomp/arm/cras_audio_device.policy
index 20bf60e..21ebdb6 100644
--- a/seccomp/arm/cras_audio_device.policy
+++ b/seccomp/arm/cras_audio_device.policy
@@ -12,3 +12,8 @@
 sched_setscheduler: 1
 socketpair: arg0 == AF_UNIX
 prctl: arg0 == PR_SET_NAME
+timerfd_create: 1
+timerfd_gettime: 1
+timerfd_gettime64: 1
+timerfd_settime: 1
+timerfd_settime64: 1
diff --git a/seccomp/arm/gpu_device.policy b/seccomp/arm/gpu_device.policy
index ec5a5b4..88c9fcc 100644
--- a/seccomp/arm/gpu_device.policy
+++ b/seccomp/arm/gpu_device.policy
@@ -42,12 +42,14 @@
 rt_sigprocmask: 1
 rt_sigreturn: 1
 sched_getaffinity: 1
+sched_yield: 1
 sendmsg: 1
 sendto: 1
 set_robust_list: 1
 sigaltstack: 1
 write: 1
 writev: 1
+uname: 1
 
 # Required for perfetto tracing
 getsockopt: 1
@@ -64,6 +66,8 @@
 getdents: 1
 getdents64: 1
 sysinfo: 1
+fstatfs: 1
+fstatfs64: 1
 
 # 0x6400 == DRM_IOCTL_BASE, 0x8000 = KBASE_IOCTL_TYPE (mali), 0x40086200 = DMA_BUF_IOCTL_SYNC, 0x40087543 == UDMABUF_CREATE_LIST
 ioctl: arg1 & 0x6400 || arg1 & 0x8000 || arg1 == 0x40086200 || arg1 == 0x40087543
@@ -89,7 +93,6 @@
 clock_gettime64: 1
 
 # Rules specific to Mesa.
-uname: 1
 sched_setscheduler: 1
 sched_setaffinity: 1
 kcmp: 1
diff --git a/seccomp/arm/video_device.policy b/seccomp/arm/video_device.policy
index 2d82956..8169957 100644
--- a/seccomp/arm/video_device.policy
+++ b/seccomp/arm/video_device.policy
@@ -8,6 +8,8 @@
 clock_getres: 1
 clock_getres_time64: 1
 connect: 1
+fstatfs64: 1
+fstatfs: 1
 getegid32: 1
 geteuid32: 1
 getgid32: 1
@@ -20,7 +22,6 @@
 ioctl: arg1 & 0x6400
 memfd_create: 1
 openat: 1
-sched_yield: 1
 send: 1
 setpriority: 1
 socket: arg0 == AF_UNIX
diff --git a/seccomp/arm/xhci.policy b/seccomp/arm/xhci.policy
index ca1a73d..322a296 100644
--- a/seccomp/arm/xhci.policy
+++ b/seccomp/arm/xhci.policy
@@ -19,7 +19,6 @@
 socket: arg0 == AF_NETLINK
 stat: 1
 statx: 1
-uname: 1
 # The following ioctls are:
 # 0x4004550d == USBDEVFS_REAPURBNDELAY32
 # 0x550b     == USBDEVFS_DISCARDURB
diff --git a/seccomp/x86_64/common_device.policy b/seccomp/x86_64/common_device.policy
index 49d4520..8c72d0d 100644
--- a/seccomp/x86_64/common_device.policy
+++ b/seccomp/x86_64/common_device.policy
@@ -40,6 +40,7 @@
 rt_sigprocmask: 1
 rt_sigreturn: 1
 sched_getaffinity: 1
+sched_yield: 1
 sendmsg: 1
 sendto: 1
 set_robust_list: 1
@@ -47,3 +48,4 @@
 write: 1
 writev: 1
 fcntl: 1
+uname: 1
diff --git a/seccomp/x86_64/cras_audio_device.policy b/seccomp/x86_64/cras_audio_device.policy
index bbaffb0..416712b 100644
--- a/seccomp/x86_64/cras_audio_device.policy
+++ b/seccomp/x86_64/cras_audio_device.policy
@@ -13,3 +13,6 @@
 socketpair: arg0 == AF_UNIX
 clock_gettime: 1
 prctl: arg0 == PR_SET_NAME
+timerfd_create: 1
+timerfd_gettime: 1
+timerfd_settime: 1
diff --git a/seccomp/x86_64/gpu_device.policy b/seccomp/x86_64/gpu_device.policy
index dcfc081..d28f828 100644
--- a/seccomp/x86_64/gpu_device.policy
+++ b/seccomp/x86_64/gpu_device.policy
@@ -39,12 +39,14 @@
 rt_sigprocmask: 1
 rt_sigreturn: 1
 sched_getaffinity: 1
+sched_yield: 1
 sendmsg: 1
 sendto: 1
 set_robust_list: 1
 sigaltstack: 1
 write: 1
 writev: 1
+uname: 1
 
 # Rules specific to gpu
 connect: 1
@@ -75,6 +77,7 @@
 stat: 1
 statx: 1
 sysinfo: 1
+fstatfs: 1
 
 # Required for perfetto tracing
 # fcntl: arg1 == F_SETFD || arg1 == F_GETFL || arg1 == F_SETFL (merged above)
@@ -90,7 +93,6 @@
 unlink: 1
 
 # Rules specific to AMD gpus.
-uname: 1
 sched_setscheduler: 1
 sched_setaffinity: 1
 kcmp: 1
@@ -100,6 +102,3 @@
 getgid: 1
 getegid: 1
 getcwd: 1
-
-# Rules for virglrenderer
-sched_yield: 1
diff --git a/seccomp/x86_64/iommu_device.policy b/seccomp/x86_64/iommu_device.policy
new file mode 100644
index 0000000..28a2002
--- /dev/null
+++ b/seccomp/x86_64/iommu_device.policy
@@ -0,0 +1,13 @@
+# Copyright 2021 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
+
+prctl: arg0 == PR_SET_NAME
+open: return ENOENT
+openat: return ENOENT
+
+# 0x3B71: VFIO_IOMMU_MAP_DMA
+# 0x3B72: VFIO_IOMMU_UNMAP_DMA
+ioctl: arg1 == 0x3B71 || arg1 == 0x3B72
diff --git a/seccomp/x86_64/video_device.policy b/seccomp/x86_64/video_device.policy
index 1f81f73..650e029 100644
--- a/seccomp/x86_64/video_device.policy
+++ b/seccomp/x86_64/video_device.policy
@@ -20,11 +20,11 @@
 ioctl: arg1 & 0x6400
 memfd_create: 1
 openat: 1
-sched_yield: 1
 setpriority: 1
 socket: arg0 == AF_UNIX
 stat: 1
 fstat: 1
+fstatfs: 1
 
 # Rules needed for minigbm on AMD devices.
 getrandom: 1
@@ -35,7 +35,6 @@
 readlink: 1
 sched_setaffinity: 1
 sched_setscheduler: arg1 == SCHED_IDLE || arg1 == SCHED_BATCH
-uname: 1
 
 # Required by mesa on AMD GPU
 sysinfo: 1
diff --git a/seccomp/x86_64/xhci.policy b/seccomp/x86_64/xhci.policy
index 9ef3766..50b41d1 100644
--- a/seccomp/x86_64/xhci.policy
+++ b/seccomp/x86_64/xhci.policy
@@ -18,7 +18,6 @@
 openat: 1
 socket: arg0 == AF_NETLINK
 stat: 1
-uname: 1
 # The following ioctls are:
 # 0x4008550d == USBDEVFS_REAPURBNDELAY
 # 0x41045508 == USBDEVFS_GETDRIVER
diff --git a/setup_cros_cargo.sh b/setup_cros_cargo.sh
new file mode 100755
index 0000000..aa5a65a
--- /dev/null
+++ b/setup_cros_cargo.sh
@@ -0,0 +1,14 @@
+#!/usr/bin/env bash
+# Copyright 2021 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
+#
+# To build crosvm using cargo against libraries and crates provided by ChromeOS
+# use this script to update path references in Cargo.toml.
+#
+# TODO(b/194323235): Add documentation for ChromeOS developer workflows.
+
+sed -i 's|path = "libcras_stub"|path = "../../third_party/adhd/cras/client/libcras"|g' \
+    Cargo.toml
+
+echo "Modified Cargo.toml with new paths. Please do not commit those."
diff --git a/src/crosvm.rs b/src/crosvm.rs
index 30a0d43..a52fca3 100644
--- a/src/crosvm.rs
+++ b/src/crosvm.rs
@@ -6,6 +6,7 @@
 //! configs.
 
 pub mod argument;
+pub mod error;
 #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
 pub mod gdb;
 #[path = "linux.rs"]
@@ -54,6 +55,7 @@
     pub path: PathBuf,
     pub read_only: bool,
     pub sparse: bool,
+    pub o_direct: bool,
     pub block_size: u32,
     pub id: Option<[u8; DISK_ID_LEN]>,
 }
@@ -206,8 +208,10 @@
     pub vcpu_affinity: Option<VcpuAffinity>,
     pub cpu_clusters: Vec<Vec<usize>>,
     pub cpu_capacity: BTreeMap<usize, u32>, // CPU index -> capacity
+    pub delay_rt: bool,
     pub no_smt: bool,
     pub memory: Option<u64>,
+    pub swiotlb: Option<u64>,
     pub hugepages: bool,
     pub memory_file: Option<PathBuf>,
     pub executable_path: Option<Executable>,
@@ -229,7 +233,6 @@
     pub tap_fd: Vec<RawFd>,
     pub cid: Option<u64>,
     pub wayland_socket_paths: BTreeMap<String, PathBuf>,
-    pub wayland_dmabuf: bool,
     pub x_display: Option<String>,
     pub shared_dirs: Vec<SharedDir>,
     pub sandbox: bool,
@@ -242,6 +245,8 @@
     pub display_window_mouse: bool,
     #[cfg(feature = "audio")]
     pub ac97_parameters: Vec<Ac97Parameters>,
+    #[cfg(feature = "audio")]
+    pub sound: Option<PathBuf>,
     pub serial_parameters: BTreeMap<(SerialHardware, u8), SerialParameters>,
     pub syslog_tag: Option<String>,
     pub virtio_single_touch: Vec<TouchDeviceOption>,
@@ -252,7 +257,7 @@
     pub virtio_switches: Vec<PathBuf>,
     pub virtio_input_evdevs: Vec<PathBuf>,
     pub split_irqchip: bool,
-    pub vfio: Vec<PathBuf>,
+    pub vfio: BTreeMap<PathBuf, bool>,
     pub video_dec: bool,
     pub video_enc: bool,
     pub acpi_tables: Vec<PathBuf>,
@@ -264,6 +269,7 @@
     pub vhost_user_blk: Vec<VhostUserOption>,
     pub vhost_user_console: Vec<VhostUserOption>,
     pub vhost_user_fs: Vec<VhostUserFsOption>,
+    pub vhost_user_mac80211_hwsim: Option<VhostUserOption>,
     pub vhost_user_net: Vec<VhostUserOption>,
     pub vhost_user_wl: Vec<VhostUserWlOption>,
     #[cfg(feature = "direct")]
@@ -287,8 +293,10 @@
             vcpu_affinity: None,
             cpu_clusters: Vec::new(),
             cpu_capacity: BTreeMap::new(),
+            delay_rt: false,
             no_smt: false,
             memory: None,
+            swiotlb: None,
             hugepages: false,
             memory_file: None,
             executable_path: None,
@@ -313,7 +321,6 @@
             gpu_parameters: None,
             software_tpm: false,
             wayland_socket_paths: BTreeMap::new(),
-            wayland_dmabuf: false,
             x_display: None,
             display_window_keyboard: false,
             display_window_mouse: false,
@@ -323,6 +330,8 @@
             seccomp_log_failures: false,
             #[cfg(feature = "audio")]
             ac97_parameters: Vec::new(),
+            #[cfg(feature = "audio")]
+            sound: None,
             serial_parameters: BTreeMap::new(),
             syslog_tag: None,
             virtio_single_touch: Vec::new(),
@@ -333,7 +342,7 @@
             virtio_switches: Vec::new(),
             virtio_input_evdevs: Vec::new(),
             split_irqchip: false,
-            vfio: Vec::new(),
+            vfio: BTreeMap::new(),
             video_dec: false,
             video_enc: false,
             acpi_tables: Vec::new(),
@@ -345,6 +354,7 @@
             vhost_user_blk: Vec::new(),
             vhost_user_console: Vec::new(),
             vhost_user_fs: Vec::new(),
+            vhost_user_mac80211_hwsim: None,
             vhost_user_net: Vec::new(),
             vhost_user_wl: Vec::new(),
             #[cfg(feature = "direct")]
diff --git a/src/error.rs b/src/error.rs
new file mode 100644
index 0000000..f304d90
--- /dev/null
+++ b/src/error.rs
@@ -0,0 +1,314 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use arch::{self, LinuxArch};
+use base::TubeError;
+use devices::virtio;
+use devices::virtio::vhost::user::Error as VhostUserError;
+use std::error::Error as StdError;
+use std::fmt::{self, Display};
+use std::io;
+use std::num::ParseIntError;
+use std::path::PathBuf;
+#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
+use std::sync::mpsc;
+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+use x86_64::X8664arch as Arch;
+#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
+use {
+    aarch64::AArch64 as Arch,
+    devices::IrqChipAArch64 as IrqChipArch,
+    hypervisor::{VcpuAArch64 as VcpuArch, VmAArch64 as VmArch},
+};
+
+use net_util::Error as NetError;
+use remain::sorted;
+#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
+use vm_control::VcpuDebugStatusMessage;
+
+#[sorted]
+#[derive(Debug)]
+pub enum Error {
+    AddGpuDeviceMemory(base::Error),
+    AddIrqChipVcpu(base::Error),
+    AddPmemDeviceMemory(base::Error),
+    AllocateGpuDeviceAddress,
+    AllocatePmemDeviceAddress(resources::Error),
+    BalloonDeviceNew(virtio::BalloonError),
+    BlockDeviceNew(base::Error),
+    BlockSignal(base::signal::Error),
+    BuildVm(<Arch as LinuxArch>::Error),
+    ChownTpmStorage(base::Error),
+    CloneEvent(base::Error),
+    CloneVcpu(base::Error),
+    ConfigureVcpu(<Arch as LinuxArch>::Error),
+    ConnectTube(io::Error),
+    #[cfg(feature = "audio")]
+    CreateAc97(devices::PciDeviceError),
+    CreateConsole(arch::serial::Error),
+    CreateControlServer(io::Error),
+    CreateDiskError(disk::Error),
+    CreateEvent(base::Error),
+    CreateGrallocError(rutabaga_gfx::RutabagaError),
+    CreateGuestMemory(vm_memory::GuestMemoryError),
+    CreateIrqChip(base::Error),
+    CreateKvm(base::Error),
+    CreateSignalFd(base::SignalFdError),
+    CreateSocket(io::Error),
+    CreateTapDevice(NetError),
+    CreateTimer(base::Error),
+    CreateTpmStorage(PathBuf, io::Error),
+    CreateTube(TubeError),
+    #[cfg(feature = "usb")]
+    CreateUsbProvider(devices::usb::host_backend::error::Error),
+    CreateVcpu(base::Error),
+    CreateVfioDevice(devices::vfio::VfioError),
+    CreateVfioKvmDevice(base::Error),
+    CreateVirtioIommu(base::Error),
+    CreateVm(base::Error),
+    CreateWaitContext(base::Error),
+    DeviceJail(minijail::Error),
+    DevicePivotRoot(minijail::Error),
+    #[cfg(feature = "direct")]
+    DirectIo(io::Error),
+    #[cfg(feature = "direct")]
+    DirectIrq(devices::DirectIrqError),
+    Disk(PathBuf, io::Error),
+    DiskImageLock(base::Error),
+    DropCapabilities(base::Error),
+    FsDeviceNew(virtio::fs::Error),
+    GenerateAcpi,
+    GetMaxOpenFiles(io::Error),
+    GetSignalMask(base::signal::Error),
+    GuestMemoryLayout(<Arch as LinuxArch>::Error),
+    #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
+    HandleDebugCommand(<Arch as LinuxArch>::Error),
+    InputDeviceNew(virtio::InputError),
+    InputEventsOpen(io::Error),
+    InvalidWaylandPath,
+    IoJail(minijail::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),
+    OpenKernel(PathBuf, io::Error),
+    OpenVinput(PathBuf, io::Error),
+    P9DeviceNew(virtio::P9Error),
+    ParseMaxOpenFiles(ParseIntError),
+    PivotRootDoesntExist(&'static str),
+    PmemDeviceImageTooBig,
+    PmemDeviceNew(base::Error),
+    ReadMemAvailable(io::Error),
+    ReadStatm(io::Error),
+    RegisterBalloon(arch::DeviceRegistrationError),
+    RegisterBlock(arch::DeviceRegistrationError),
+    RegisterGpu(arch::DeviceRegistrationError),
+    RegisterNet(arch::DeviceRegistrationError),
+    RegisterP9(arch::DeviceRegistrationError),
+    RegisterRng(arch::DeviceRegistrationError),
+    RegisterSignalHandler(base::Error),
+    RegisterWayland(arch::DeviceRegistrationError),
+    ReserveGpuMemory(base::MmapError),
+    ReserveMemory(base::Error),
+    ReservePmemMemory(base::MmapError),
+    ResetTimer(base::Error),
+    RngDeviceNew(virtio::RngError),
+    RunnableVcpu(base::Error),
+    #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
+    SendDebugStatus(Box<mpsc::SendError<VcpuDebugStatusMessage>>),
+    SettingGidMap(minijail::Error),
+    SettingMaxOpenFiles(minijail::Error),
+    SettingSignalMask(base::Error),
+    SettingUidMap(minijail::Error),
+    SignalFd(base::SignalFdError),
+    #[cfg(feature = "audio")]
+    SoundDeviceNew(virtio::SoundError),
+    #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
+    SpawnGdbServer(io::Error),
+    SpawnVcpu(io::Error),
+    SwiotlbTooLarge,
+    Timer(base::Error),
+    ValidateRawDescriptor(base::Error),
+    VhostNetDeviceNew(virtio::vhost::Error),
+    VhostUserBlockDeviceNew(VhostUserError),
+    VhostUserConsoleDeviceNew(VhostUserError),
+    VhostUserFsDeviceNew(VhostUserError),
+    VhostUserMac80211HwsimNew(VhostUserError),
+    VhostUserNetDeviceNew(VhostUserError),
+    VhostUserNetWithNetArgs,
+    VhostUserWlDeviceNew(VhostUserError),
+    VhostVsockDeviceNew(virtio::vhost::Error),
+    VirtioPciDev(base::Error),
+    WaitContextAdd(base::Error),
+    WaitContextDelete(base::Error),
+    WaylandDeviceNew(base::Error),
+}
+
+impl Display for Error {
+    #[remain::check]
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        use self::Error::*;
+
+        #[sorted]
+        match self {
+            AddGpuDeviceMemory(e) => write!(f, "failed to add gpu device memory: {}", e),
+            AddIrqChipVcpu(e) => write!(f, "failed to add vcpu to irq chip: {}", e),
+            AddPmemDeviceMemory(e) => write!(f, "failed to add pmem device memory: {}", e),
+            AllocateGpuDeviceAddress => write!(f, "failed to allocate gpu device guest address"),
+            AllocatePmemDeviceAddress(e) => {
+                write!(f, "failed to allocate memory for pmem device: {}", e)
+            }
+            BalloonDeviceNew(e) => write!(f, "failed to create balloon: {}", e),
+            BlockDeviceNew(e) => write!(f, "failed to create block device: {}", e),
+            BlockSignal(e) => write!(f, "failed to block signal: {}", e),
+            BuildVm(e) => write!(f, "The architecture failed to build the vm: {}", e),
+            ChownTpmStorage(e) => write!(f, "failed to chown tpm storage: {}", e),
+            CloneEvent(e) => write!(f, "failed to clone event: {}", e),
+            CloneVcpu(e) => write!(f, "failed to clone vcpu: {}", e),
+            ConfigureVcpu(e) => write!(f, "failed to configure vcpu: {}", e),
+            ConnectTube(e) => write!(f, "failed to connect to tube: {}", e),
+            #[cfg(feature = "audio")]
+            CreateAc97(e) => write!(f, "failed to create ac97 device: {}", e),
+            CreateConsole(e) => write!(f, "failed to create console device: {}", e),
+            CreateControlServer(e) => write!(f, "failed to create control server: {}", e),
+            CreateDiskError(e) => write!(f, "failed to create virtual disk: {}", e),
+            CreateEvent(e) => write!(f, "failed to create event: {}", e),
+            CreateGrallocError(e) => write!(f, "failed to create gralloc: {}", e),
+            CreateGuestMemory(e) => write!(f, "failed to create guest memory: {}", e),
+            CreateIrqChip(e) => write!(f, "failed to create IRQ chip: {}", e),
+            CreateKvm(e) => write!(f, "failed to create kvm: {}", e),
+            CreateSignalFd(e) => write!(f, "failed to create signalfd: {}", e),
+            CreateSocket(e) => write!(f, "failed to create socket: {}", e),
+            CreateTapDevice(e) => write!(f, "failed to create tap device: {}", e),
+            CreateTimer(e) => write!(f, "failed to create Timer: {}", e),
+            CreateTpmStorage(p, e) => {
+                write!(f, "failed to create tpm storage dir {}: {}", p.display(), e)
+            }
+            CreateTube(e) => write!(f, "failed to create tube: {}", e),
+            #[cfg(feature = "usb")]
+            CreateUsbProvider(e) => write!(f, "failed to create usb provider: {}", e),
+            CreateVcpu(e) => write!(f, "failed to create vcpu: {}", e),
+            CreateVfioDevice(e) => write!(f, "Failed to create vfio device {}", e),
+            CreateVfioKvmDevice(e) => write!(f, "failed to create KVM vfio device: {}", e),
+            CreateVirtioIommu(e) => write!(f, "Failed to create IOMMU device {}", e),
+            CreateVm(e) => write!(f, "failed to create vm: {}", e),
+            CreateWaitContext(e) => write!(f, "failed to create wait context: {}", e),
+            DeviceJail(e) => write!(f, "failed to jail device: {}", e),
+            DevicePivotRoot(e) => write!(f, "failed to pivot root device: {}", e),
+            #[cfg(feature = "direct")]
+            DirectIo(e) => write!(f, "failed to open direct io device: {}", e),
+            #[cfg(feature = "direct")]
+            DirectIrq(e) => write!(f, "failed to enable interrupt forwarding: {}", e),
+            Disk(p, e) => write!(f, "failed to load disk image {}: {}", p.display(), e),
+            DiskImageLock(e) => write!(f, "failed to lock disk image: {}", e),
+            DropCapabilities(e) => write!(f, "failed to drop process capabilities: {}", e),
+            FsDeviceNew(e) => write!(f, "failed to create fs device: {}", e),
+            GenerateAcpi => write!(f, "failed to generate ACPI table"),
+            GetMaxOpenFiles(e) => write!(f, "failed to get max number of open files: {}", e),
+            GetSignalMask(e) => write!(f, "failed to retrieve signal mask for vcpu: {}", e),
+            GuestMemoryLayout(e) => write!(f, "failed to create guest memory layout: {}", e),
+            #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
+            HandleDebugCommand(e) => write!(f, "failed to handle a gdb command: {}", e),
+            InputDeviceNew(e) => write!(f, "failed to set up input device: {}", e),
+            InputEventsOpen(e) => write!(f, "failed to open event device: {}", e),
+            InvalidWaylandPath => write!(f, "wayland socket path has no parent or file name"),
+            IoJail(e) => write!(f, "{}", e),
+            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 {}: {}",
+                p.display(),
+                e
+            ),
+            OpenBios(p, e) => write!(f, "failed to open bios {}: {}", p.display(), e),
+            OpenInitrd(p, e) => write!(f, "failed to open initrd {}: {}", p.display(), e),
+            OpenKernel(p, e) => write!(f, "failed to open kernel image {}: {}", p.display(), e),
+            OpenVinput(p, e) => write!(f, "failed to open vinput device {}: {}", p.display(), e),
+            P9DeviceNew(e) => write!(f, "failed to create 9p device: {}", e),
+            ParseMaxOpenFiles(e) => write!(f, "failed to parse max number of open files: {}", e),
+            PivotRootDoesntExist(p) => write!(f, "{} doesn't exist, can't jail devices.", p),
+            PmemDeviceImageTooBig => {
+                write!(f, "failed to create pmem device: pmem device image too big")
+            }
+            PmemDeviceNew(e) => write!(f, "failed to create pmem device: {}", e),
+            ReadMemAvailable(e) => write!(
+                f,
+                "failed to read /sys/kernel/mm/chromeos-low_mem/available: {}",
+                e
+            ),
+            ReadStatm(e) => write!(f, "failed to read /proc/self/statm: {}", e),
+            RegisterBalloon(e) => write!(f, "error registering balloon device: {}", e),
+            RegisterBlock(e) => write!(f, "error registering block device: {}", e),
+            RegisterGpu(e) => write!(f, "error registering gpu device: {}", e),
+            RegisterNet(e) => write!(f, "error registering net device: {}", e),
+            RegisterP9(e) => write!(f, "error registering 9p device: {}", e),
+            RegisterRng(e) => write!(f, "error registering rng device: {}", e),
+            RegisterSignalHandler(e) => write!(f, "error registering signal handler: {}", e),
+            RegisterWayland(e) => write!(f, "error registering wayland device: {}", e),
+            ReserveGpuMemory(e) => write!(f, "failed to reserve gpu memory: {}", e),
+            ReserveMemory(e) => write!(f, "failed to reserve memory: {}", e),
+            ReservePmemMemory(e) => write!(f, "failed to reserve pmem memory: {}", e),
+            ResetTimer(e) => write!(f, "failed to reset Timer: {}", e),
+            RngDeviceNew(e) => write!(f, "failed to set up rng: {}", e),
+            RunnableVcpu(e) => write!(f, "failed to set thread id for vcpu: {}", e),
+            #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
+            SendDebugStatus(e) => write!(f, "failed to send a debug status to GDB thread: {}", e),
+            SettingGidMap(e) => write!(f, "error setting GID map: {}", e),
+            SettingMaxOpenFiles(e) => write!(f, "error setting max open files: {}", e),
+            SettingSignalMask(e) => write!(f, "failed to set the signal mask for vcpu: {}", e),
+            SettingUidMap(e) => write!(f, "error setting UID map: {}", e),
+            SignalFd(e) => write!(f, "failed to read signal fd: {}", e),
+            #[cfg(feature = "audio")]
+            SoundDeviceNew(e) => write!(f, "failed to create sound device: {}", e),
+            #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
+            SpawnGdbServer(e) => write!(f, "failed to spawn GDB thread: {}", e),
+            SpawnVcpu(e) => write!(f, "failed to spawn VCPU thread: {}", e),
+            SwiotlbTooLarge => write!(f, "requested swiotlb size too large"),
+            Timer(e) => write!(f, "failed to read timer fd: {}", e),
+            ValidateRawDescriptor(e) => write!(f, "failed to validate raw descriptor: {}", e),
+            VhostNetDeviceNew(e) => write!(f, "failed to set up vhost networking: {}", e),
+            VhostUserBlockDeviceNew(e) => {
+                write!(f, "failed to set up vhost-user block device: {}", e)
+            }
+            VhostUserConsoleDeviceNew(e) => {
+                write!(f, "failed to set up vhost-user console device: {}", e)
+            }
+            VhostUserFsDeviceNew(e) => write!(f, "failed to set up vhost-user fs device: {}", e),
+            VhostUserMac80211HwsimNew(e) => {
+                write!(f, "failed to set up vhost-user mac80211_hwsim device {}", e)
+            }
+            VhostUserNetDeviceNew(e) => write!(f, "failed to set up vhost-user net device: {}", e),
+            VhostUserNetWithNetArgs => write!(
+                f,
+                "vhost-user-net cannot be used with any of --host_ip, --netmask or --mac"
+            ),
+            VhostUserWlDeviceNew(e) => {
+                write!(f, "failed to set up vhost-user wl device: {}", e)
+            }
+            VhostVsockDeviceNew(e) => write!(f, "failed to set up virtual socket device: {}", e),
+            VirtioPciDev(e) => write!(f, "failed to create virtio pci dev: {}", e),
+            WaitContextAdd(e) => write!(f, "failed to add descriptor to wait context: {}", e),
+            WaitContextDelete(e) => {
+                write!(f, "failed to remove descriptor from wait context: {}", e)
+            }
+            WaylandDeviceNew(e) => write!(f, "failed to create wayland device: {}", e),
+        }
+    }
+}
+
+impl From<minijail::Error> for Error {
+    fn from(err: minijail::Error) -> Self {
+        Error::IoJail(err)
+    }
+}
+
+impl std::error::Error for Error {}
+
+pub type Result<T> = std::result::Result<T, Error>;
diff --git a/src/gdb.rs b/src/gdb.rs
index 0801241..42e094d 100644
--- a/src/gdb.rs
+++ b/src/gdb.rs
@@ -14,15 +14,15 @@
 };
 use vm_memory::GuestAddress;
 
-#[cfg(target_arch = "x86_64")]
-use gdbstub::arch::x86::X86_64_SSE as GdbArch;
 use gdbstub::arch::Arch;
 use gdbstub::target::ext::base::singlethread::{ResumeAction, SingleThreadOps, StopReason};
-use gdbstub::target::ext::base::BaseOps;
-use gdbstub::target::ext::breakpoints::{HwBreakpoint, HwBreakpointOps};
+use gdbstub::target::ext::base::{BaseOps, GdbInterrupt};
+use gdbstub::target::ext::breakpoints::{Breakpoints, BreakpointsOps, HwBreakpoint};
 use gdbstub::target::TargetError::NonFatal;
 use gdbstub::target::{Target, TargetResult};
 use gdbstub::Connection;
+#[cfg(target_arch = "x86_64")]
+use gdbstub_arch::x86::X86_64_SSE as GdbArch;
 use remain::sorted;
 use thiserror::Error as ThisError;
 
@@ -141,7 +141,7 @@
     }
 
     // TODO(keiichiw): sw_breakpoint, hw_watchpoint, extended_mode, monitor_cmd, section_offsets
-    fn hw_breakpoint(&mut self) -> Option<HwBreakpointOps<Self>> {
+    fn breakpoints(&mut self) -> Option<BreakpointsOps<Self>> {
         Some(self)
     }
 }
@@ -150,7 +150,7 @@
     fn resume(
         &mut self,
         action: ResumeAction,
-        check_gdb_interrupt: &mut dyn FnMut() -> bool,
+        check_gdb_interrupt: GdbInterrupt,
     ) -> Result<StopReason<ArchUsize>, Self::Error> {
         let single_step = ResumeAction::Step == action;
 
@@ -173,6 +173,7 @@
             "Failed to resume the target"
         })?;
 
+        let mut check_gdb_interrupt = check_gdb_interrupt.no_async();
         // Polling
         loop {
             // TODO(keiichiw): handle error?
@@ -194,7 +195,7 @@
                 }
             }
 
-            if check_gdb_interrupt() {
+            if check_gdb_interrupt.pending() {
                 self.vm_request(VmRequest::Suspend).map_err(|e| {
                     error!("Failed to suspend the target: {}", e);
                     "Failed to suspend the target"
@@ -294,7 +295,11 @@
 impl HwBreakpoint for GdbStub {
     /// Add a new hardware breakpoint.
     /// Return `Ok(false)` if the operation could not be completed.
-    fn add_hw_breakpoint(&mut self, addr: <Self::Arch as Arch>::Usize) -> TargetResult<bool, Self> {
+    fn add_hw_breakpoint(
+        &mut self,
+        addr: <Self::Arch as Arch>::Usize,
+        _kind: <Self::Arch as Arch>::BreakpointKind,
+    ) -> TargetResult<bool, Self> {
         // If we already have 4 breakpoints, we cannot set a new one.
         if self.hw_breakpoints.len() >= 4 {
             error!("Not allowed to set more than 4 HW breakpoints");
@@ -322,6 +327,7 @@
     fn remove_hw_breakpoint(
         &mut self,
         addr: <Self::Arch as Arch>::Usize,
+        _kind: <Self::Arch as Arch>::BreakpointKind,
     ) -> TargetResult<bool, Self> {
         self.hw_breakpoints.retain(|&b| b.0 != addr);
 
@@ -340,3 +346,5 @@
         }
     }
 }
+
+impl Breakpoints for GdbStub {}
diff --git a/src/linux.rs b/src/linux.rs
index 44ae93a..9c587cf 100644
--- a/src/linux.rs
+++ b/src/linux.rs
@@ -2,21 +2,17 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-use std::cell::RefCell;
 use std::cmp::Reverse;
+use std::collections::BTreeMap;
 use std::convert::TryFrom;
 #[cfg(feature = "gpu")]
 use std::env;
-use std::error::Error as StdError;
 use std::ffi::CStr;
-use std::fmt::{self, Display};
 use std::fs::{File, OpenOptions};
 use std::io::{self, stdin};
 use std::iter;
 use std::mem;
 use std::net::Ipv4Addr;
-use std::num::ParseIntError;
-use std::os::unix::io::FromRawFd;
 use std::os::unix::net::UnixStream;
 use std::path::{Path, PathBuf};
 use std::ptr;
@@ -27,23 +23,26 @@
 use std::thread;
 use std::thread::JoinHandle;
 
-use libc::{self, c_int, gid_t, uid_t};
+use libc::{self, c_int, gid_t, uid_t, EINVAL};
 
 use acpi_tables::sdt::SDT;
 
+use crate::error::{Error, Result};
 use base::net::{UnixSeqpacket, UnixSeqpacketListener, UnlinkUnixSeqpacketListener};
 use base::*;
+use devices::vfio::{VfioCommonSetup, VfioCommonTrait};
 #[cfg(feature = "gpu")]
 use devices::virtio::gpu::{DEFAULT_DISPLAY_HEIGHT, DEFAULT_DISPLAY_WIDTH};
 use devices::virtio::vhost::user::{
-    Block as VhostUserBlock, Console as VhostUserConsole, Error as VhostUserError,
-    Fs as VhostUserFs, Net as VhostUserNet, Wl as VhostUserWl,
+    Block as VhostUserBlock, Console as VhostUserConsole, Fs as VhostUserFs,
+    Mac80211Hwsim as VhostUserMac80211Hwsim, Net as VhostUserNet, Wl as VhostUserWl,
 };
 #[cfg(feature = "gpu")]
 use devices::virtio::EventDevice;
 use devices::virtio::{self, Console, VirtioDevice};
 #[cfg(feature = "audio")]
 use devices::Ac97Dev;
+use devices::ProtectionType;
 use devices::{
     self, IrqChip, IrqEventIndex, KvmKernelIrqChip, PciDevice, VcpuRunState, VfioContainer,
     VfioDevice, VfioPciDevice, VirtioPciDevice,
@@ -51,10 +50,9 @@
 #[cfg(feature = "usb")]
 use devices::{HostBackendDeviceProvider, XhciController};
 use hypervisor::kvm::{Kvm, KvmVcpu, KvmVm};
-use hypervisor::{HypervisorCap, Vcpu, VcpuExit, VcpuRunHandle, Vm, VmCap};
+use hypervisor::{DeviceKind, HypervisorCap, Vcpu, VcpuExit, VcpuRunHandle, Vm, VmCap};
 use minijail::{self, Minijail};
-use net_util::{Error as NetError, MacAddress, Tap};
-use remain::sorted;
+use net_util::{MacAddress, Tap};
 use resources::{Alloc, MmioType, SystemAllocator};
 use rutabaga_gfx::RutabagaGralloc;
 use sync::Mutex;
@@ -85,290 +83,6 @@
     x86_64::X8664arch as Arch,
 };
 
-#[sorted]
-#[derive(Debug)]
-pub enum Error {
-    AddGpuDeviceMemory(base::Error),
-    AddIrqChipVcpu(base::Error),
-    AddPmemDeviceMemory(base::Error),
-    AllocateGpuDeviceAddress,
-    AllocatePmemDeviceAddress(resources::Error),
-    BalloonActualTooLarge,
-    BalloonDeviceNew(virtio::BalloonError),
-    BlockDeviceNew(base::Error),
-    BlockSignal(base::signal::Error),
-    BorrowVfioContainer,
-    BuildVm(<Arch as LinuxArch>::Error),
-    ChownTpmStorage(base::Error),
-    CloneEvent(base::Error),
-    CloneVcpu(base::Error),
-    ConfigureVcpu(<Arch as LinuxArch>::Error),
-    ConnectTube(io::Error),
-    #[cfg(feature = "audio")]
-    CreateAc97(devices::PciDeviceError),
-    CreateConsole(arch::serial::Error),
-    CreateControlServer(io::Error),
-    CreateDiskError(disk::Error),
-    CreateEvent(base::Error),
-    CreateGrallocError(rutabaga_gfx::RutabagaError),
-    CreateGuestMemory(vm_memory::GuestMemoryError),
-    CreateIrqChip(base::Error),
-    CreateKvm(base::Error),
-    CreateSignalFd(base::SignalFdError),
-    CreateSocket(io::Error),
-    CreateTapDevice(NetError),
-    CreateTimer(base::Error),
-    CreateTpmStorage(PathBuf, io::Error),
-    CreateTube(TubeError),
-    #[cfg(feature = "usb")]
-    CreateUsbProvider(devices::usb::host_backend::error::Error),
-    CreateVcpu(base::Error),
-    CreateVfioDevice(devices::vfio::VfioError),
-    CreateVm(base::Error),
-    CreateWaitContext(base::Error),
-    DeviceJail(minijail::Error),
-    DevicePivotRoot(minijail::Error),
-    #[cfg(feature = "direct")]
-    DirectIo(io::Error),
-    #[cfg(feature = "direct")]
-    DirectIrq(devices::DirectIrqError),
-    Disk(PathBuf, io::Error),
-    DiskImageLock(base::Error),
-    DropCapabilities(base::Error),
-    FsDeviceNew(virtio::fs::Error),
-    GetMaxOpenFiles(io::Error),
-    GetSignalMask(signal::Error),
-    GuestCachedMissing(),
-    GuestCachedTooLarge(std::num::TryFromIntError),
-    GuestFreeMissing(),
-    GuestFreeTooLarge(std::num::TryFromIntError),
-    GuestMemoryLayout(<Arch as LinuxArch>::Error),
-    #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
-    HandleDebugCommand(<Arch as LinuxArch>::Error),
-    InputDeviceNew(virtio::InputError),
-    InputEventsOpen(std::io::Error),
-    InvalidFdPath,
-    InvalidWaylandPath,
-    IoJail(minijail::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),
-    OpenKernel(PathBuf, io::Error),
-    OpenVinput(PathBuf, io::Error),
-    P9DeviceNew(virtio::P9Error),
-    ParseMaxOpenFiles(ParseIntError),
-    PivotRootDoesntExist(&'static str),
-    PmemDeviceImageTooBig,
-    PmemDeviceNew(base::Error),
-    ReadMemAvailable(io::Error),
-    ReadStatm(io::Error),
-    RegisterBalloon(arch::DeviceRegistrationError),
-    RegisterBlock(arch::DeviceRegistrationError),
-    RegisterGpu(arch::DeviceRegistrationError),
-    RegisterNet(arch::DeviceRegistrationError),
-    RegisterP9(arch::DeviceRegistrationError),
-    RegisterRng(arch::DeviceRegistrationError),
-    RegisterSignalHandler(base::Error),
-    RegisterWayland(arch::DeviceRegistrationError),
-    ReserveGpuMemory(base::MmapError),
-    ReserveMemory(base::Error),
-    ReservePmemMemory(base::MmapError),
-    ResetTimer(base::Error),
-    RngDeviceNew(virtio::RngError),
-    RunnableVcpu(base::Error),
-    #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
-    SendDebugStatus(Box<mpsc::SendError<VcpuDebugStatusMessage>>),
-    SettingGidMap(minijail::Error),
-    SettingMaxOpenFiles(minijail::Error),
-    SettingSignalMask(base::Error),
-    SettingUidMap(minijail::Error),
-    SignalFd(base::SignalFdError),
-    #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
-    SpawnGdbServer(io::Error),
-    SpawnVcpu(io::Error),
-    Timer(base::Error),
-    ValidateRawDescriptor(base::Error),
-    VhostNetDeviceNew(virtio::vhost::Error),
-    VhostUserBlockDeviceNew(VhostUserError),
-    VhostUserConsoleDeviceNew(VhostUserError),
-    VhostUserFsDeviceNew(VhostUserError),
-    VhostUserNetDeviceNew(VhostUserError),
-    VhostUserNetWithNetArgs,
-    VhostUserWlDeviceNew(VhostUserError),
-    VhostVsockDeviceNew(virtio::vhost::Error),
-    VirtioPciDev(base::Error),
-    WaitContextAdd(base::Error),
-    WaitContextDelete(base::Error),
-    WaylandDeviceNew(base::Error),
-}
-
-impl Display for Error {
-    #[remain::check]
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        use self::Error::*;
-
-        #[sorted]
-        match self {
-            AddGpuDeviceMemory(e) => write!(f, "failed to add gpu device memory: {}", e),
-            AddIrqChipVcpu(e) => write!(f, "failed to add vcpu to irq chip: {}", e),
-            AddPmemDeviceMemory(e) => write!(f, "failed to add pmem device memory: {}", e),
-            AllocateGpuDeviceAddress => write!(f, "failed to allocate gpu device guest address"),
-            AllocatePmemDeviceAddress(e) => {
-                write!(f, "failed to allocate memory for pmem device: {}", e)
-            }
-            BalloonActualTooLarge => write!(f, "balloon actual size is too large"),
-            BalloonDeviceNew(e) => write!(f, "failed to create balloon: {}", e),
-            BlockDeviceNew(e) => write!(f, "failed to create block device: {}", e),
-            BlockSignal(e) => write!(f, "failed to block signal: {}", e),
-            BorrowVfioContainer => write!(f, "failed to borrow global vfio container"),
-            BuildVm(e) => write!(f, "The architecture failed to build the vm: {}", e),
-            ChownTpmStorage(e) => write!(f, "failed to chown tpm storage: {}", e),
-            CloneEvent(e) => write!(f, "failed to clone event: {}", e),
-            CloneVcpu(e) => write!(f, "failed to clone vcpu: {}", e),
-            ConfigureVcpu(e) => write!(f, "failed to configure vcpu: {}", e),
-            ConnectTube(e) => write!(f, "failed to connect to tube: {}", e),
-            #[cfg(feature = "audio")]
-            CreateAc97(e) => write!(f, "failed to create ac97 device: {}", e),
-            CreateConsole(e) => write!(f, "failed to create console device: {}", e),
-            CreateControlServer(e) => write!(f, "failed to create control server: {}", e),
-            CreateDiskError(e) => write!(f, "failed to create virtual disk: {}", e),
-            CreateEvent(e) => write!(f, "failed to create event: {}", e),
-            CreateGrallocError(e) => write!(f, "failed to create gralloc: {}", e),
-            CreateGuestMemory(e) => write!(f, "failed to create guest memory: {}", e),
-            CreateIrqChip(e) => write!(f, "failed to create IRQ chip: {}", e),
-            CreateKvm(e) => write!(f, "failed to create kvm: {}", e),
-            CreateSignalFd(e) => write!(f, "failed to create signalfd: {}", e),
-            CreateSocket(e) => write!(f, "failed to create socket: {}", e),
-            CreateTapDevice(e) => write!(f, "failed to create tap device: {}", e),
-            CreateTimer(e) => write!(f, "failed to create Timer: {}", e),
-            CreateTpmStorage(p, e) => {
-                write!(f, "failed to create tpm storage dir {}: {}", p.display(), e)
-            }
-            CreateTube(e) => write!(f, "failed to create tube: {}", e),
-            #[cfg(feature = "usb")]
-            CreateUsbProvider(e) => write!(f, "failed to create usb provider: {}", e),
-            CreateVcpu(e) => write!(f, "failed to create vcpu: {}", e),
-            CreateVfioDevice(e) => write!(f, "Failed to create vfio device {}", e),
-            CreateVm(e) => write!(f, "failed to create vm: {}", e),
-            CreateWaitContext(e) => write!(f, "failed to create wait context: {}", e),
-            DeviceJail(e) => write!(f, "failed to jail device: {}", e),
-            DevicePivotRoot(e) => write!(f, "failed to pivot root device: {}", e),
-            #[cfg(feature = "direct")]
-            DirectIo(e) => write!(f, "failed to open direct io device: {}", e),
-            #[cfg(feature = "direct")]
-            DirectIrq(e) => write!(f, "failed to enable interrupt forwarding: {}", e),
-            Disk(p, e) => write!(f, "failed to load disk image {}: {}", p.display(), e),
-            DiskImageLock(e) => write!(f, "failed to lock disk image: {}", e),
-            DropCapabilities(e) => write!(f, "failed to drop process capabilities: {}", e),
-            FsDeviceNew(e) => write!(f, "failed to create fs device: {}", e),
-            GetMaxOpenFiles(e) => write!(f, "failed to get max number of open files: {}", e),
-            GetSignalMask(e) => write!(f, "failed to retrieve signal mask for vcpu: {}", e),
-            GuestCachedMissing() => write!(f, "guest cached is missing from balloon stats"),
-            GuestCachedTooLarge(e) => write!(f, "guest cached is too large: {}", e),
-            GuestFreeMissing() => write!(f, "guest free is missing from balloon stats"),
-            GuestFreeTooLarge(e) => write!(f, "guest free is too large: {}", e),
-            GuestMemoryLayout(e) => write!(f, "failed to create guest memory layout: {}", e),
-            #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
-            HandleDebugCommand(e) => write!(f, "failed to handle a gdb command: {}", e),
-            InputDeviceNew(e) => write!(f, "failed to set up input device: {}", e),
-            InputEventsOpen(e) => write!(f, "failed to open event device: {}", e),
-            InvalidFdPath => write!(f, "failed parsing a /proc/self/fd/*"),
-            InvalidWaylandPath => write!(f, "wayland socket path has no parent or file name"),
-            IoJail(e) => write!(f, "{}", e),
-            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 {}: {}",
-                p.display(),
-                e
-            ),
-            OpenBios(p, e) => write!(f, "failed to open bios {}: {}", p.display(), e),
-            OpenInitrd(p, e) => write!(f, "failed to open initrd {}: {}", p.display(), e),
-            OpenKernel(p, e) => write!(f, "failed to open kernel image {}: {}", p.display(), e),
-            OpenVinput(p, e) => write!(f, "failed to open vinput device {}: {}", p.display(), e),
-            P9DeviceNew(e) => write!(f, "failed to create 9p device: {}", e),
-            ParseMaxOpenFiles(e) => write!(f, "failed to parse max number of open files: {}", e),
-            PivotRootDoesntExist(p) => write!(f, "{} doesn't exist, can't jail devices.", p),
-            PmemDeviceImageTooBig => {
-                write!(f, "failed to create pmem device: pmem device image too big")
-            }
-            PmemDeviceNew(e) => write!(f, "failed to create pmem device: {}", e),
-            ReadMemAvailable(e) => write!(
-                f,
-                "failed to read /sys/kernel/mm/chromeos-low_mem/available: {}",
-                e
-            ),
-            ReadStatm(e) => write!(f, "failed to read /proc/self/statm: {}", e),
-            RegisterBalloon(e) => write!(f, "error registering balloon device: {}", e),
-            RegisterBlock(e) => write!(f, "error registering block device: {}", e),
-            RegisterGpu(e) => write!(f, "error registering gpu device: {}", e),
-            RegisterNet(e) => write!(f, "error registering net device: {}", e),
-            RegisterP9(e) => write!(f, "error registering 9p device: {}", e),
-            RegisterRng(e) => write!(f, "error registering rng device: {}", e),
-            RegisterSignalHandler(e) => write!(f, "error registering signal handler: {}", e),
-            RegisterWayland(e) => write!(f, "error registering wayland device: {}", e),
-            ReserveGpuMemory(e) => write!(f, "failed to reserve gpu memory: {}", e),
-            ReserveMemory(e) => write!(f, "failed to reserve memory: {}", e),
-            ReservePmemMemory(e) => write!(f, "failed to reserve pmem memory: {}", e),
-            ResetTimer(e) => write!(f, "failed to reset Timer: {}", e),
-            RngDeviceNew(e) => write!(f, "failed to set up rng: {}", e),
-            RunnableVcpu(e) => write!(f, "failed to set thread id for vcpu: {}", e),
-            #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
-            SendDebugStatus(e) => write!(f, "failed to send a debug status to GDB thread: {}", e),
-            SettingGidMap(e) => write!(f, "error setting GID map: {}", e),
-            SettingMaxOpenFiles(e) => write!(f, "error setting max open files: {}", e),
-            SettingSignalMask(e) => write!(f, "failed to set the signal mask for vcpu: {}", e),
-            SettingUidMap(e) => write!(f, "error setting UID map: {}", e),
-            SignalFd(e) => write!(f, "failed to read signal fd: {}", e),
-            #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
-            SpawnGdbServer(e) => write!(f, "failed to spawn GDB thread: {}", e),
-            SpawnVcpu(e) => write!(f, "failed to spawn VCPU thread: {}", e),
-            Timer(e) => write!(f, "failed to read timer fd: {}", e),
-            ValidateRawDescriptor(e) => write!(f, "failed to validate raw descriptor: {}", e),
-            VhostNetDeviceNew(e) => write!(f, "failed to set up vhost networking: {}", e),
-            VhostUserBlockDeviceNew(e) => {
-                write!(f, "failed to set up vhost-user block device: {}", e)
-            }
-            VhostUserConsoleDeviceNew(e) => {
-                write!(f, "failed to set up vhost-user console device: {}", e)
-            }
-            VhostUserFsDeviceNew(e) => write!(f, "failed to set up vhost-user fs device: {}", e),
-            VhostUserNetDeviceNew(e) => write!(f, "failed to set up vhost-user net device: {}", e),
-            VhostUserNetWithNetArgs => write!(
-                f,
-                "vhost-user-net cannot be used with any of --host_ip, --netmask or --mac"
-            ),
-            VhostUserWlDeviceNew(e) => {
-                write!(f, "failed to set up vhost-user wl device: {}", e)
-            }
-            VhostVsockDeviceNew(e) => write!(f, "failed to set up virtual socket device: {}", e),
-            VirtioPciDev(e) => write!(f, "failed to create virtio pci dev: {}", e),
-            WaitContextAdd(e) => write!(f, "failed to add descriptor to wait context: {}", e),
-            WaitContextDelete(e) => {
-                write!(f, "failed to remove descriptor from wait context: {}", e)
-            }
-            WaylandDeviceNew(e) => write!(f, "failed to create wayland device: {}", e),
-        }
-    }
-}
-
-impl From<minijail::Error> for Error {
-    fn from(err: minijail::Error) -> Self {
-        Error::IoJail(err)
-    }
-}
-
-impl std::error::Error for Error {}
-
-type Result<T> = std::result::Result<T, Error>;
-
 enum TaggedControlTube {
     Fs(Tube),
     Vm(Tube),
@@ -512,33 +226,9 @@
 
 type DeviceResult<T = VirtioDeviceStub> = std::result::Result<T, Error>;
 
-/// Open the file with the given path, or if it is of the form `/proc/self/fd/N` then just use the
-/// file descriptor.
-///
-/// Note that this will not work properly if the same `/proc/self/fd/N` path is used twice in
-/// different places, as the metadata (including the offset) will be shared between both file
-/// descriptors.
-fn open_file<F>(path: &Path, read_only: bool, error_constructor: F) -> Result<File>
-where
-    F: FnOnce(PathBuf, io::Error) -> Error,
-{
-    // Special case '/proc/self/fd/*' paths. The FD is already open, just use it.
-    Ok(
-        if let Some(raw_descriptor) = raw_descriptor_from_path(path)? {
-            // Safe because we will validate |raw_fd|.
-            unsafe { File::from_raw_descriptor(raw_descriptor) }
-        } else {
-            OpenOptions::new()
-                .read(true)
-                .write(!read_only)
-                .open(path)
-                .map_err(|e| error_constructor(path.to_path_buf(), e))?
-        },
-    )
-}
-
 fn create_block_device(cfg: &Config, disk: &DiskOption, disk_device_tube: Tube) -> DeviceResult {
-    let raw_image: File = open_file(&disk.path, disk.read_only, Error::Disk)?;
+    let raw_image: File = open_file(&disk.path, disk.read_only, disk.o_direct)
+        .map_err(|e| Error::Disk(disk.path.clone(), e.into()))?;
     // Lock the disk image to prevent other crosvm instances from using it.
     let lock_op = if disk.read_only {
         FlockOperation::LockShared
@@ -620,6 +310,17 @@
     })
 }
 
+fn create_vhost_user_mac80211_hwsim_device(cfg: &Config, opt: &VhostUserOption) -> DeviceResult {
+    let dev = VhostUserMac80211Hwsim::new(virtio::base_features(cfg.protected_vm), &opt.socket)
+        .map_err(Error::VhostUserMac80211HwsimNew)?;
+
+    Ok(VirtioDeviceStub {
+        dev: Box::new(dev),
+        // no sandbox here because virtqueue handling is exported to a different process.
+        jail: None,
+    })
+}
+
 fn create_rng_device(cfg: &Config) -> DeviceResult {
     let dev =
         virtio::Rng::new(virtio::base_features(cfg.protected_vm)).map_err(Error::RngDeviceNew)?;
@@ -942,7 +643,6 @@
     x_display: Option<String>,
     event_devices: Vec<EventDevice>,
     map_request: Arc<Mutex<Option<ExternalMapping>>>,
-    mem: &GuestMemory,
 ) -> DeviceResult {
     let mut display_backends = vec![
         virtio::DisplayBackend::X(x_display),
@@ -974,7 +674,6 @@
         cfg.sandbox,
         virtio::base_features(cfg.protected_vm),
         cfg.wayland_socket_paths.clone(),
-        mem.clone(),
     );
 
     let jail = match simple_jail(&cfg, "gpu_device")? {
@@ -1325,8 +1024,8 @@
     index: usize,
     pmem_device_tube: Tube,
 ) -> DeviceResult {
-    let fd = open_file(&disk.path, disk.read_only, Error::Disk)?;
-
+    let fd = open_file(&disk.path, disk.read_only, false /*O_DIRECT*/)
+        .map_err(|e| Error::Disk(disk.path.clone(), e.into()))?;
     let arena_size = {
         let metadata =
             std::fs::metadata(&disk.path).map_err(|e| Error::Disk(disk.path.to_path_buf(), e))?;
@@ -1402,6 +1101,24 @@
     })
 }
 
+fn create_iommu_device(
+    cfg: &Config,
+    phys_max_addr: u64,
+    endpoints: BTreeMap<u32, Arc<Mutex<VfioContainer>>>,
+) -> DeviceResult {
+    let dev = virtio::Iommu::new(
+        virtio::base_features(cfg.protected_vm),
+        endpoints,
+        phys_max_addr,
+    )
+    .map_err(Error::CreateVirtioIommu)?;
+
+    Ok(VirtioDeviceStub {
+        dev: Box::new(dev),
+        jail: simple_jail(&cfg, "iommu_device")?,
+    })
+}
+
 fn create_console_device(cfg: &Config, param: &SerialParameters) -> DeviceResult {
     let mut keep_rds = Vec::new();
     let evt = Event::new().map_err(Error::CreateEvent)?;
@@ -1437,6 +1154,17 @@
     })
 }
 
+#[cfg(feature = "audio")]
+fn create_sound_device(path: &Path, cfg: &Config) -> DeviceResult {
+    let dev = virtio::new_sound(path, virtio::base_features(cfg.protected_vm))
+        .map_err(Error::SoundDeviceNew)?;
+
+    Ok(VirtioDeviceStub {
+        dev: Box::new(dev),
+        jail: simple_jail(&cfg, "vios_audio_device")?,
+    })
+}
+
 // gpu_device_tube is not used when GPU support is disabled.
 #[cfg_attr(not(feature = "gpu"), allow(unused_variables))]
 fn create_virtio_devices(
@@ -1667,7 +1395,6 @@
                 cfg.x_display.clone(),
                 event_devices,
                 map_request,
-                vm.get_memory(),
             )?);
         }
     }
@@ -1725,33 +1452,33 @@
         devs.push(dev);
     }
 
+    if let Some(vhost_user_mac80211_hwsim) = &cfg.vhost_user_mac80211_hwsim {
+        devs.push(create_vhost_user_mac80211_hwsim_device(
+            cfg,
+            &vhost_user_mac80211_hwsim,
+        )?);
+    }
+
+    #[cfg(feature = "audio")]
+    if let Some(path) = &cfg.sound {
+        devs.push(create_sound_device(&path, &cfg)?);
+    }
+
     Ok(devs)
 }
 
-thread_local!(static VFIO_CONTAINER: RefCell<Option<Arc<Mutex<VfioContainer>>>> = RefCell::new(None));
 fn create_vfio_device(
     cfg: &Config,
     vm: &impl Vm,
     resources: &mut SystemAllocator,
     control_tubes: &mut Vec<TaggedControlTube>,
     vfio_path: &Path,
+    kvm_vfio_file: &SafeDescriptor,
+    endpoints: &mut BTreeMap<u32, Arc<Mutex<VfioContainer>>>,
+    iommu_enabled: bool,
 ) -> DeviceResult<(Box<VfioPciDevice>, Option<Minijail>)> {
-    let vfio_container =
-        VFIO_CONTAINER.with::<_, DeviceResult<Arc<Mutex<VfioContainer>>>>(|v| {
-            if v.borrow().is_some() {
-                if let Some(container) = &(*v.borrow()) {
-                    Ok(container.clone())
-                } else {
-                    Err(Error::BorrowVfioContainer)
-                }
-            } else {
-                let container = Arc::new(Mutex::new(
-                    VfioContainer::new().map_err(Error::CreateVfioDevice)?,
-                ));
-                *v.borrow_mut() = Some(container.clone());
-                Ok(container)
-            }
-        })?;
+    let vfio_container = VfioCommonSetup::vfio_get_container(vfio_path, iommu_enabled)
+        .map_err(Error::CreateVfioDevice)?;
 
     // create MSI, MSI-X, and Mem request sockets for each vfio device
     let (vfio_host_tube_msi, vfio_device_tube_msi) = Tube::pair().map_err(Error::CreateTube)?;
@@ -1763,8 +1490,14 @@
     let (vfio_host_tube_mem, vfio_device_tube_mem) = Tube::pair().map_err(Error::CreateTube)?;
     control_tubes.push(TaggedControlTube::VmMemory(vfio_host_tube_mem));
 
-    let vfio_device = VfioDevice::new(vfio_path, vm, vm.get_memory(), vfio_container)
-        .map_err(Error::CreateVfioDevice)?;
+    let vfio_device = VfioDevice::new(
+        vfio_path,
+        vm.get_memory(),
+        &kvm_vfio_file,
+        vfio_container.clone(),
+        iommu_enabled,
+    )
+    .map_err(Error::CreateVfioDevice)?;
     let mut vfio_pci_device = Box::new(VfioPciDevice::new(
         vfio_device,
         vfio_device_tube_msi,
@@ -1772,13 +1505,18 @@
         vfio_device_tube_mem,
     ));
     // early reservation for pass-through PCI devices.
-    if vfio_pci_device.allocate_address(resources).is_err() {
+    let endpoint_addr = vfio_pci_device.allocate_address(resources);
+    if endpoint_addr.is_err() {
         warn!(
             "address reservation failed for vfio {}",
             vfio_pci_device.debug_label()
         );
     }
 
+    if iommu_enabled {
+        endpoints.insert(endpoint_addr.unwrap().to_u32(), vfio_container);
+    }
+
     Ok((vfio_pci_device, simple_jail(cfg, "vfio_device")?))
 }
 
@@ -1787,6 +1525,7 @@
     vm: &mut impl Vm,
     resources: &mut SystemAllocator,
     exit_evt: &Event,
+    phys_max_addr: u64,
     control_tubes: &mut Vec<TaggedControlTube>,
     wayland_device_tube: Tube,
     gpu_device_tube: Tube,
@@ -1837,10 +1576,43 @@
         pci_devices.push((usb_controller, simple_jail(&cfg, "xhci")?));
     }
 
-    for vfio_path in &cfg.vfio {
-        let (vfio_pci_device, jail) =
-            create_vfio_device(cfg, vm, resources, control_tubes, vfio_path.as_path())?;
-        pci_devices.push((vfio_pci_device, jail));
+    if !cfg.vfio.is_empty() {
+        let kvm_vfio_file = vm
+            .create_device(DeviceKind::Vfio)
+            .map_err(Error::CreateVfioKvmDevice)?;
+
+        let mut iommu_attached_endpoints: BTreeMap<u32, Arc<Mutex<VfioContainer>>> =
+            BTreeMap::new();
+
+        for (vfio_path, enable_iommu) in cfg.vfio.iter() {
+            let (vfio_pci_device, jail) = create_vfio_device(
+                cfg,
+                vm,
+                resources,
+                control_tubes,
+                vfio_path.as_path(),
+                &kvm_vfio_file,
+                &mut iommu_attached_endpoints,
+                *enable_iommu,
+            )?;
+
+            pci_devices.push((vfio_pci_device, jail));
+        }
+
+        if !iommu_attached_endpoints.is_empty() {
+            let iommu_dev = create_iommu_device(cfg, phys_max_addr, iommu_attached_endpoints)?;
+
+            let (msi_host_tube, msi_device_tube) = Tube::pair().map_err(Error::CreateTube)?;
+            control_tubes.push(TaggedControlTube::VmIrq(msi_host_tube));
+            let mut dev =
+                VirtioPciDevice::new(vm.get_memory().clone(), iommu_dev.dev, msi_device_tube)
+                    .map_err(Error::VirtioPciDev)?;
+            // early reservation for viommu.
+            dev.allocate_address(resources)
+                .map_err(|_| Error::VirtioPciDev(base::Error::new(EINVAL)))?;
+            let dev = Box::new(dev);
+            pci_devices.push((dev, iommu_dev.jail));
+        }
     }
 
     Ok(pci_devices)
@@ -1887,32 +1659,16 @@
     })
 }
 
-/// If the given path is of the form /proc/self/fd/N for some N, returns `Ok(Some(N))`. Otherwise
-/// returns `Ok(None`).
-fn raw_descriptor_from_path(path: &Path) -> Result<Option<RawDescriptor>> {
-    if path.parent() == Some(Path::new("/proc/self/fd")) {
-        let raw_descriptor = path
-            .file_name()
-            .and_then(|fd_osstr| fd_osstr.to_str())
-            .and_then(|fd_str| fd_str.parse::<c_int>().ok())
-            .ok_or(Error::InvalidFdPath)?;
-        validate_raw_descriptor(raw_descriptor)
-            .map_err(Error::ValidateRawDescriptor)
-            .map(Some)
-    } else {
-        Ok(None)
-    }
-}
-
 trait IntoUnixStream {
     fn into_unix_stream(self) -> Result<UnixStream>;
 }
 
 impl<'a> IntoUnixStream for &'a Path {
     fn into_unix_stream(self) -> Result<UnixStream> {
-        if let Some(raw_descriptor) = raw_descriptor_from_path(self)? {
-            // Safe because we will validate |raw_fd|.
-            unsafe { Ok(UnixStream::from_raw_fd(raw_descriptor)) }
+        if let Some(fd) =
+            safe_descriptor_from_path(self).map_err(|e| Error::InputEventsOpen(e.into()))?
+        {
+            Ok(fd.into())
         } else {
             UnixStream::connect(self).map_err(Error::InputEventsOpen)
         }
@@ -2115,6 +1871,7 @@
     vcpu_count: usize,
     run_rt: bool,
     vcpu_affinity: Vec<usize>,
+    delay_rt: bool,
     no_smt: bool,
     start_barrier: Arc<Barrier>,
     has_bios: bool,
@@ -2146,7 +1903,7 @@
                 vm,
                 irq_chip.as_mut(),
                 vcpu_count,
-                run_rt,
+                run_rt && !delay_rt,
                 vcpu_affinity,
                 no_smt,
                 has_bios,
@@ -2247,6 +2004,21 @@
                                         }
                                     }
                                 }
+                                VcpuControl::MakeRT => {
+                                    if run_rt && delay_rt {
+                                        info!("Making vcpu {} RT\n", cpu_id);
+                                        const DEFAULT_VCPU_RT_LEVEL: u16 = 6;
+                                        if let Err(e) = set_rt_prio_limit(
+                                            u64::from(DEFAULT_VCPU_RT_LEVEL))
+                                            .and_then(|_|
+                                                set_rt_round_robin(
+                                                i32::from(DEFAULT_VCPU_RT_LEVEL)
+                                            ))
+                                        {
+                                            warn!("Failed to set vcpu to real time: {}", e);
+                                        }
+                                    }
+                                }
                             }
                         }
                     }
@@ -2274,12 +2046,12 @@
                         Ok(VcpuExit::IoIn { port, mut size }) => {
                             let mut data = [0; 8];
                             if size > data.len() {
-                                error!("unsupported IoIn size of {} bytes", size);
+                                error!("unsupported IoIn size of {} bytes at port {:#x}", size, port);
                                 size = data.len();
                             }
                             io_bus.read(port as u64, &mut data[..size]);
                             if let Err(e) = vcpu.set_data(&data[..size]) {
-                                error!("failed to set return data for IoIn: {}", e);
+                                error!("failed to set return data for IoIn at port {:#x}: {}", port, e);
                             }
                         }
                         Ok(VcpuExit::IoOut {
@@ -2288,7 +2060,7 @@
                             data,
                         }) => {
                             if size > data.len() {
-                                error!("unsupported IoOut size of {} bytes", size);
+                                error!("unsupported IoOut size of {} bytes at port {:#x}", size, port);
                                 size = data.len();
                             }
                             io_bus.write(port as u64, &data[..size]);
@@ -2375,27 +2147,53 @@
 
 fn setup_vm_components(cfg: &Config) -> Result<VmComponents> {
     let initrd_image = if let Some(initrd_path) = &cfg.initrd_path {
-        Some(open_file(initrd_path, true, Error::OpenInitrd)?)
+        Some(
+            open_file(
+                initrd_path,
+                true,  /*read_only*/
+                false, /*O_DIRECT*/
+            )
+            .map_err(|e| Error::OpenInitrd(initrd_path.to_owned(), e.into()))?,
+        )
     } else {
         None
     };
 
     let vm_image = match cfg.executable_path {
-        Some(Executable::Kernel(ref kernel_path)) => {
-            VmImage::Kernel(open_file(kernel_path, true, Error::OpenKernel)?)
-        }
-        Some(Executable::Bios(ref bios_path)) => {
-            VmImage::Bios(open_file(bios_path, true, Error::OpenBios)?)
-        }
+        Some(Executable::Kernel(ref kernel_path)) => VmImage::Kernel(
+            open_file(
+                kernel_path,
+                true,  /*read_only*/
+                false, /*O_DIRECT*/
+            )
+            .map_err(|e| Error::OpenKernel(kernel_path.to_owned(), e.into()))?,
+        ),
+        Some(Executable::Bios(ref bios_path)) => VmImage::Bios(
+            open_file(bios_path, true /*read_only*/, false /*O_DIRECT*/)
+                .map_err(|e| Error::OpenBios(bios_path.to_owned(), e.into()))?,
+        ),
         _ => panic!("Did not receive a bios or kernel, should be impossible."),
     };
 
+    let swiotlb = if let Some(size) = cfg.swiotlb {
+        Some(
+            size.checked_mul(1024 * 1024)
+                .ok_or(Error::SwiotlbTooLarge)?,
+        )
+    } else {
+        match cfg.protected_vm {
+            ProtectionType::Protected => Some(64 * 1024 * 1024),
+            ProtectionType::Unprotected => None,
+        }
+    };
+
     Ok(VmComponents {
         memory_size: cfg
             .memory
             .unwrap_or(256)
             .checked_mul(1024 * 1024)
             .ok_or(Error::MemoryTooLarge)?,
+        swiotlb,
         vcpu_count: cfg.vcpu_count.unwrap_or(1),
         vcpu_affinity: cfg.vcpu_affinity.clone(),
         cpu_clusters: cfg.cpu_clusters.clone(),
@@ -2411,13 +2209,13 @@
         pstore: cfg.pstore.clone(),
         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>>>()?,
         rt_cpus: cfg.rt_cpus.clone(),
+        delay_rt: cfg.delay_rt,
         protected_vm: cfg.protected_vm,
         #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
         gdb: None,
@@ -2616,11 +2414,13 @@
 
     let exit_evt = Event::new().map_err(Error::CreateEvent)?;
     let mut sys_allocator = Arch::create_system_allocator(vm.get_memory());
-    let pci_devices = create_devices(
+    let phys_max_addr = Arch::get_phys_max_addr();
+    let mut pci_devices = create_devices(
         &cfg,
         &mut vm,
         &mut sys_allocator,
         &exit_evt,
+        phys_max_addr,
         &mut control_tubes,
         wayland_device_tube,
         gpu_device_tube,
@@ -2633,6 +2433,18 @@
         Arc::clone(&map_request),
     )?;
 
+    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+    for (device, _jail) in pci_devices.iter_mut() {
+        let sdts = device
+            .generate_acpi(components.acpi_sdts)
+            .or_else(|| {
+                error!("ACPI table generation error");
+                None
+            })
+            .ok_or(Error::GenerateAcpi)?;
+        components.acpi_sdts = sdts;
+    }
+
     #[cfg_attr(not(feature = "direct"), allow(unused_mut))]
     let mut linux = Arch::build_vm::<V, Vcpu>(
         components,
@@ -2828,6 +2640,7 @@
             linux.vcpu_count,
             linux.rt_cpus.contains(&cpu_id),
             vcpu_affinity,
+            linux.delay_rt,
             linux.no_smt,
             vcpu_thread_barrier.clone(),
             linux.has_bios,
@@ -2863,6 +2676,8 @@
 
     vcpu_thread_barrier.wait();
 
+    let mut balloon_stats_id: u64 = 0;
+
     'wait: loop {
         let events = {
             match wait_ctx.wait() {
@@ -2941,12 +2756,14 @@
                                     let response = request.execute(
                                         &mut run_mode_opt,
                                         &balloon_host_tube,
+                                        &mut balloon_stats_id,
                                         disk_host_tubes,
                                         #[cfg(feature = "usb")]
                                         Some(&usb_control_tube),
                                         #[cfg(not(feature = "usb"))]
                                         None,
                                         &mut linux.bat_control,
+                                        &vcpu_handles,
                                     );
                                     if let Err(e) = tube.send(&response) {
                                         error!("failed to send VmResponse: {}", e);
@@ -2959,7 +2776,9 @@
                                             }
                                             other => {
                                                 if other == VmRunMode::Running {
-                                                    linux.io_bus.notify_resume();
+                                                    for dev in &linux.resume_notify_devices {
+                                                        dev.lock().resume_imminent();
+                                                    }
                                                 }
                                                 kick_all_vcpus(
                                                     &vcpu_handles,
@@ -3084,25 +2903,17 @@
             }
         }
 
-        for event in events.iter().filter(|e| e.is_hungup) {
-            match event.token {
-                Token::Exit => {}
-                Token::Suspend => {}
-                Token::ChildSignal => {}
-                Token::IrqFd { index: _ } => {}
-                Token::VmControlServer => {}
-                Token::VmControl { index } => {
-                    // It's possible more data is readable and buffered while the socket is hungup,
-                    // so don't delete the tube from the poll context until we're sure all the
-                    // data is read.
-                    if control_tubes
-                        .get(index)
-                        .map(|s| !s.as_ref().is_packet_ready())
-                        .unwrap_or(false)
-                    {
-                        vm_control_indices_to_remove.push(index);
-                    }
-                }
+        // It's possible more data is readable and buffered while the socket is hungup,
+        // so don't delete the tube from the poll context until we're sure all the
+        // data is read.
+        // Below case covers a condition where we have received a hungup event and the tube is not
+        // readable.
+        // In case of readable tube, once all data is read, any attempt to read more data on hungup
+        // tube should fail. On such failure, we get Disconnected error and index gets added to
+        // vm_control_indices_to_remove by the time we reach here.
+        for event in events.iter().filter(|e| e.is_hungup && !e.is_readable) {
+            if let Token::VmControl { index } = event.token {
+                vm_control_indices_to_remove.push(index);
             }
         }
 
diff --git a/src/main.rs b/src/main.rs
index ada5715..1b9467b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -36,6 +36,10 @@
 #[cfg(feature = "audio")]
 use devices::{Ac97Backend, Ac97Parameters};
 use disk::QcowFile;
+#[cfg(feature = "composite-disk")]
+use disk::{
+    create_composite_disk, create_disk_file, create_zero_filler, ImagePartitionType, PartitionInfo,
+};
 use vm_control::{
     client::{
         do_modify_battery, do_usb_attach, do_usb_detach, do_usb_list, handle_request, vms_request,
@@ -529,6 +533,7 @@
                     argument::Error::Syntax(format!("invalid capture option: {}", e))
                 })?;
             }
+            #[cfg(feature = "audio_cras")]
             "client_type" => {
                 ac97_params
                     .set_client_type(v)
@@ -969,6 +974,9 @@
             }
             cfg.rt_cpus = parse_cpu_set(value.unwrap())?;
         }
+        "delay-rt" => {
+            cfg.delay_rt = true;
+        }
         "mem" => {
             if cfg.memory.is_some() {
                 return Err(argument::Error::TooManyArguments(
@@ -986,6 +994,24 @@
                         })?,
                 )
         }
+        #[cfg(target_arch = "aarch64")]
+        "swiotlb" => {
+            if cfg.swiotlb.is_some() {
+                return Err(argument::Error::TooManyArguments(
+                    "`swiotlb` already given".to_owned(),
+                ));
+            }
+            cfg.swiotlb =
+                Some(
+                    value
+                        .unwrap()
+                        .parse()
+                        .map_err(|_| argument::Error::InvalidValue {
+                            value: value.unwrap().to_owned(),
+                            expected: String::from("this value for `swiotlb` needs to be integer"),
+                        })?,
+                )
+        }
         "hugepages" => {
             cfg.hugepages = true;
         }
@@ -1001,6 +1027,11 @@
             }
             cfg.ac97_parameters.push(ac97_params);
         }
+        #[cfg(feature = "audio")]
+        "sound" => {
+            let client_path = PathBuf::from(value.unwrap());
+            cfg.sound = Some(client_path);
+        }
         "serial" => {
             let serial_params = parse_serial_options(value.unwrap())?;
             let num = serial_params.num;
@@ -1099,6 +1130,7 @@
             let mut disk = DiskOption {
                 path: disk_path,
                 read_only,
+                o_direct: false,
                 sparse: true,
                 block_size: 512,
                 id: None,
@@ -1123,6 +1155,14 @@
                         })?;
                         disk.sparse = sparse;
                     }
+                    "o_direct" => {
+                        let o_direct =
+                            value.parse().map_err(|_| argument::Error::InvalidValue {
+                                value: value.to_owned(),
+                                expected: String::from("`o_direct` must be a boolean"),
+                            })?;
+                        disk.o_direct = o_direct;
+                    }
                     "block_size" => {
                         let block_size =
                             value.parse().map_err(|_| argument::Error::InvalidValue {
@@ -1171,6 +1211,7 @@
                 path: disk_path,
                 read_only: !name.starts_with("rw"),
                 sparse: false,
+                o_direct: false,
                 block_size: base::pagesize() as u32,
                 id: None,
             });
@@ -1333,7 +1374,7 @@
             cfg.wayland_socket_paths.insert(name.to_string(), path);
         }
         #[cfg(feature = "wl-dmabuf")]
-        "wayland-dmabuf" => cfg.wayland_dmabuf = true,
+        "wayland-dmabuf" => {}
         "x-display" => {
             if cfg.x_display.is_some() {
                 return Err(argument::Error::TooManyArguments(
@@ -1695,7 +1736,12 @@
             cfg.executable_path = Some(Executable::Bios(PathBuf::from(value.unwrap().to_owned())));
         }
         "vfio" => {
-            let vfio_path = PathBuf::from(value.unwrap());
+            let mut param = value.unwrap().split(',');
+            let vfio_path =
+                PathBuf::from(param.next().ok_or_else(|| argument::Error::InvalidValue {
+                    value: value.unwrap().to_owned(),
+                    expected: String::from("missing vfio path"),
+                })?);
             if !vfio_path.exists() {
                 return Err(argument::Error::InvalidValue {
                     value: value.unwrap().to_owned(),
@@ -1709,7 +1755,33 @@
                 });
             }
 
-            cfg.vfio.push(vfio_path);
+            let mut enable_iommu = false;
+            if let Some(p) = param.next() {
+                let mut kv = p.splitn(2, '=');
+                if let (Some(kind), Some(value)) = (kv.next(), kv.next()) {
+                    match kind {
+                        "iommu" => (),
+                        _ => {
+                            return Err(argument::Error::InvalidValue {
+                                value: p.to_owned(),
+                                expected: String::from("option must be `iommu=on|off`"),
+                            })
+                        }
+                    }
+                    match value {
+                        "on" => enable_iommu = true,
+                        "off" => (),
+                        _ => {
+                            return Err(argument::Error::InvalidValue {
+                                value: p.to_owned(),
+                                expected: String::from("option must be `iommu=on|off`"),
+                            })
+                        }
+                    }
+                };
+            }
+
+            cfg.vfio.insert(vfio_path, enable_iommu);
         }
         "video-decoder" => {
             cfg.video_dec = true;
@@ -1736,7 +1808,6 @@
         }
         "protected-vm" => {
             cfg.protected_vm = ProtectionType::Protected;
-            cfg.params.push("swiotlb=force".to_string());
         }
         "battery" => {
             let params = parse_battery_options(value)?;
@@ -1771,6 +1842,11 @@
         "vhost-user-console" => cfg.vhost_user_console.push(VhostUserOption {
             socket: PathBuf::from(value.unwrap()),
         }),
+        "vhost-user-mac80211-hwsim" => {
+            cfg.vhost_user_mac80211_hwsim = Some(VhostUserOption {
+                socket: PathBuf::from(value.unwrap()),
+            });
+        }
         "vhost-user-net" => cfg.vhost_user_net.push(VhostUserOption {
             socket: PathBuf::from(value.unwrap()),
         }),
@@ -1960,6 +2036,7 @@
           Argument::value("cpu-capacity", "CPU=CAP[,CPU=CAP[,...]]", "Set the relative capacity of the given CPU (default: no capacity)"),
           Argument::flag("no-smt", "Don't use SMT in the guest"),
           Argument::value("rt-cpus", "CPUSET", "Comma-separated list of CPUs or CPU ranges to run VCPUs on. (e.g. 0,1-3,5) (default: none)"),
+          Argument::flag("delay-rt", "Don't set VCPUs real-time until make-rt command is run"),
           Argument::short_value('m',
                                 "mem",
                                 "N",
@@ -1977,7 +2054,8 @@
                               Valid keys:
                               sparse=BOOL - Indicates whether the disk should support the discard operation (default: true)
                               block_size=BYTES - Set the reported block size of the disk (default: 512)
-                              id=STRING - Set the block device identifier to an ASCII string, up to 20 characters (default: no ID)"),
+                              id=STRING - Set the block device identifier to an ASCII string, up to 20 characters (default: no ID)
+                              o_direct=BOOL - Use O_DIRECT mode to bypass page cache"),
           Argument::value("rwdisk", "PATH[,key=value[,key=value[,...]]", "Path to a writable disk image followed by optional comma-separated options.
                               See --disk for valid options."),
           Argument::value("rw-pmem-device", "PATH", "Path to a writable disk image."),
@@ -1993,33 +2071,35 @@
           Argument::value("ac97",
                           "[backend=BACKEND,capture=true,capture_effect=EFFECT,client_type=TYPE,shm-fd=FD,client-fd=FD,server-fd=FD]",
                           "Comma separated key=value pairs for setting up Ac97 devices. Can be given more than once .
-                          Possible key values:
-                          backend=(null, cras, vios) - Where to route the audio device. If not provided, backend will default to null.
-                          `null` for /dev/null, cras for CRAS server and vios for VioS server.
-                          capture - Enable audio capture
-                          capture_effects - | separated effects to be enabled for recording. The only supported effect value now is EchoCancellation or aec.
-                          client_type - Set specific client type for cras backend.
-                          server - The to the VIOS server (unix socket)."),
+                              Possible key values:
+                              backend=(null, cras, vios) - Where to route the audio device. If not provided, backend will default to null.
+                              `null` for /dev/null, cras for CRAS server and vios for VioS server.
+                              capture - Enable audio capture
+                              capture_effects - | separated effects to be enabled for recording. The only supported effect value now is EchoCancellation or aec.
+                              client_type - Set specific client type for cras backend.
+                              server - The to the VIOS server (unix socket)."),
+          #[cfg(feature = "audio")]
+          Argument::value("sound", "[PATH]", "Path to the VioS server socket for setting up virtio-snd devices."),
           Argument::value("serial",
                           "type=TYPE,[hardware=HW,num=NUM,path=PATH,input=PATH,console,earlycon,stdin]",
                           "Comma separated key=value pairs for setting up serial devices. Can be given more than once.
-                          Possible key values:
-                          type=(stdout,syslog,sink,file) - Where to route the serial device
-                          hardware=(serial,virtio-console) - Which type of serial hardware to emulate. Defaults to 8250 UART (serial).
-                          num=(1,2,3,4) - Serial Device Number. If not provided, num will default to 1.
-                          path=PATH - The path to the file to write to when type=file
-                          input=PATH - The path to the file to read from when not stdin
-                          console - Use this serial device as the guest console. Can only be given once. Will default to first serial port if not provided.
-                          earlycon - Use this serial device as the early console. Can only be given once.
-                          stdin - Direct standard input to this serial device. Can only be given once. Will default to first serial port if not provided.
-                          "),
+                              Possible key values:
+                              type=(stdout,syslog,sink,file) - Where to route the serial device
+                              hardware=(serial,virtio-console) - Which type of serial hardware to emulate. Defaults to 8250 UART (serial).
+                              num=(1,2,3,4) - Serial Device Number. If not provided, num will default to 1.
+                              path=PATH - The path to the file to write to when type=file
+                              input=PATH - The path to the file to read from when not stdin
+                              console - Use this serial device as the guest console. Can only be given once. Will default to first serial port if not provided.
+                              earlycon - Use this serial device as the early console. Can only be given once.
+                              stdin - Direct standard input to this serial device. Can only be given once. Will default to first serial port if not provided.
+                              "),
           Argument::value("syslog-tag", "TAG", "When logging to syslog, use the provided tag."),
           Argument::value("x-display", "DISPLAY", "X11 display name to use."),
           Argument::flag("display-window-keyboard", "Capture keyboard input from the display window."),
           Argument::flag("display-window-mouse", "Capture keyboard input from the display window."),
           Argument::value("wayland-sock", "PATH[,name=NAME]", "Path to the Wayland socket to use. The unnamed one is used for displaying virtual screens. Named ones are only for IPC."),
           #[cfg(feature = "wl-dmabuf")]
-          Argument::flag("wayland-dmabuf", "Enable support for DMABufs in Wayland device."),
+          Argument::flag("wayland-dmabuf", "DEPRECATED: Enable support for DMABufs in Wayland device."),
           Argument::short_value('s',
                                 "socket",
                                 "PATH",
@@ -2028,14 +2108,14 @@
           Argument::value("cid", "CID", "Context ID for virtual sockets."),
           Argument::value("shared-dir", "PATH:TAG[:type=TYPE:writeback=BOOL:timeout=SECONDS:uidmap=UIDMAP:gidmap=GIDMAP:cache=CACHE]",
                           "Colon-separated options for configuring a directory to be shared with the VM.
-The first field is the directory to be shared and the second field is the tag that the VM can use to identify the device.
-The remaining fields are key=value pairs that may appear in any order.  Valid keys are:
-type=(p9, fs) - Indicates whether the directory should be shared via virtio-9p or virtio-fs (default: p9).
-uidmap=UIDMAP - The uid map to use for the device's jail in the format \"inner outer count[,inner outer count]\" (default: 0 <current euid> 1).
-gidmap=GIDMAP - The gid map to use for the device's jail in the format \"inner outer count[,inner outer count]\" (default: 0 <current egid> 1).
-cache=(never, auto, always) - Indicates whether the VM can cache the contents of the shared directory (default: auto).  When set to \"auto\" and the type is \"fs\", the VM will use close-to-open consistency for file contents.
-timeout=SECONDS - How long the VM should consider file attributes and directory entries to be valid (default: 5).  If the VM has exclusive access to the directory, then this should be a large value.  If the directory can be modified by other processes, then this should be 0.
-writeback=BOOL - Indicates whether the VM can use writeback caching (default: false).  This is only safe to do when the VM has exclusive access to the files in a directory.  Additionally, the server should have read permission for all files as the VM may issue read requests even for files that are opened write-only.
+                              The first field is the directory to be shared and the second field is the tag that the VM can use to identify the device.
+                              The remaining fields are key=value pairs that may appear in any order.  Valid keys are:
+                              type=(p9, fs) - Indicates whether the directory should be shared via virtio-9p or virtio-fs (default: p9).
+                              uidmap=UIDMAP - The uid map to use for the device's jail in the format \"inner outer count[,inner outer count]\" (default: 0 <current euid> 1).
+                              gidmap=GIDMAP - The gid map to use for the device's jail in the format \"inner outer count[,inner outer count]\" (default: 0 <current egid> 1).
+                              cache=(never, auto, always) - Indicates whether the VM can cache the contents of the shared directory (default: auto).  When set to \"auto\" and the type is \"fs\", the VM will use close-to-open consistency for file contents.
+                              timeout=SECONDS - How long the VM should consider file attributes and directory entries to be valid (default: 5).  If the VM has exclusive access to the directory, then this should be a large value.  If the directory can be modified by other processes, then this should be 0.
+                              writeback=BOOL - Indicates whether the VM can use writeback caching (default: false).  This is only safe to do when the VM has exclusive access to the files in a directory.  Additionally, the server should have read permission for all files as the VM may issue read requests even for files that are opened write-only.
 "),
           Argument::value("seccomp-policy-dir", "PATH", "Path to seccomp .policy files."),
           Argument::flag("seccomp-log-failures", "Instead of seccomp filter failures being fatal, they will be logged instead."),
@@ -2059,25 +2139,23 @@
           Argument::flag_or_value("gpu",
                                   "[width=INT,height=INT]",
                                   "(EXPERIMENTAL) Comma separated key=value pairs for setting up a virtio-gpu device
-                                  Possible key values:
-                                  backend=(2d|virglrenderer|gfxstream) - Which backend to use for virtio-gpu (determining rendering protocol)
-                                  width=INT - The width of the virtual display connected to the virtio-gpu.
-                                  height=INT - The height of the virtual display connected to the virtio-gpu.
-                                  egl[=true|=false] - If the backend should use a EGL context for rendering.
-                                  glx[=true|=false] - If the backend should use a GLX context for rendering.
-                                  surfaceless[=true|=false] - If the backend should use a surfaceless context for rendering.
-                                  angle[=true|=false] - If the gfxstream backend should use ANGLE (OpenGL on Vulkan) as its native OpenGL driver.
-                                  syncfd[=true|=false] - If the gfxstream backend should support EGL_ANDROID_native_fence_sync
-                                  vulkan[=true|=false] - If the backend should support vulkan
-                                  "),
+                              Possible key values:
+                              backend=(2d|virglrenderer|gfxstream) - Which backend to use for virtio-gpu (determining rendering protocol)
+                              width=INT - The width of the virtual display connected to the virtio-gpu.
+                              height=INT - The height of the virtual display connected to the virtio-gpu.
+                              egl[=true|=false] - If the backend should use a EGL context for rendering.
+                              glx[=true|=false] - If the backend should use a GLX context for rendering.
+                              surfaceless[=true|=false] - If the backend should use a surfaceless context for rendering.
+                              angle[=true|=false] - If the gfxstream backend should use ANGLE (OpenGL on Vulkan) as its native OpenGL driver.
+                              syncfd[=true|=false] - If the gfxstream backend should support EGL_ANDROID_native_fence_sync
+                              vulkan[=true|=false] - If the backend should support vulkan"),
           #[cfg(feature = "gpu")]
           Argument::flag_or_value("gpu-display",
                                   "[width=INT,height=INT]",
                                   "(EXPERIMENTAL) Comma separated key=value pairs for setting up a display on the virtio-gpu device
-                                  Possible key values:
-                                  width=INT - The width of the virtual display connected to the virtio-gpu.
-                                  height=INT - The height of the virtual display connected to the virtio-gpu.
-                                  "),
+                              Possible key values:
+                              width=INT - The width of the virtual display connected to the virtio-gpu.
+                              height=INT - The height of the virtual display connected to the virtio-gpu."),
           #[cfg(feature = "tpm")]
           Argument::flag("software-tpm", "enable a software emulated trusted platform module device"),
           Argument::value("evdev", "PATH", "Path to an event device node. The device will be grabbed (unusable from the host) and made available to the guest with the same configuration it shows on the host"),
@@ -2090,23 +2168,26 @@
           #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
           Argument::flag("split-irqchip", "(EXPERIMENTAL) enable split-irqchip support"),
           Argument::value("bios", "PATH", "Path to BIOS/firmware ROM"),
-          Argument::value("vfio", "PATH", "Path to sysfs of pass through or mdev device"),
+          Argument::value("vfio", "PATH[,iommu=on|off]", "Path to sysfs of pass through or mdev device.
+iommu=on|off - indicates whether to enable virtio IOMMU for this 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::flag("protected-vm", "(EXPERIMENTAL) prevent host access to guest memory"),
+          #[cfg(target_arch = "aarch64")]
+          Argument::value("swiotlb", "N", "(EXPERIMENTAL) Size of virtio swiotlb buffer in MiB (default: 64 if `--protected-vm` is present)."),
           Argument::flag_or_value("battery",
                                   "[type=TYPE]",
                                   "Comma separated key=value pairs for setting up battery device
-                                  Possible key values:
-                                  type=goldfish - type of battery emulation, defaults to goldfish
-                                  "),
+                              Possible key values:
+                              type=goldfish - type of battery emulation, defaults to goldfish"),
           Argument::value("gdb", "PORT", "(EXPERIMENTAL) gdb on the given port"),
           Argument::value("balloon_bias_mib", "N", "Amount to bias balance of memory between host and guest as the balloon inflates, in MiB."),
           Argument::value("vhost-user-blk", "SOCKET_PATH", "Path to a socket for vhost-user block"),
           Argument::value("vhost-user-console", "SOCKET_PATH", "Path to a socket for vhost-user console"),
+          Argument::value("vhost-user-mac80211-hwsim", "SOCKET_PATH", "Path to a socket for vhost-user mac80211_hwsim"),
           Argument::value("vhost-user-net", "SOCKET_PATH", "Path to a socket for vhost-user net"),
           Argument::value("vhost-user-wl", "SOCKET_PATH:TUBE_PATH", "Paths to a vhost-user socket for wayland and a Tube socket for additional wayland-specific messages"),
           Argument::value("vhost-user-fs", "SOCKET_PATH:TAG",
@@ -2195,6 +2276,17 @@
     vms_request(&VmRequest::Resume, socket_path)
 }
 
+fn make_rt(mut args: std::env::Args) -> std::result::Result<(), ()> {
+    if args.len() == 0 {
+        print_help("crosvm make_rt", "VM_SOCKET...", &[]);
+        println!("Makes the crosvm instance listening on each `VM_SOCKET` given RT.");
+        return Err(());
+    }
+    let socket_path = &args.next().unwrap();
+    let socket_path = Path::new(&socket_path);
+    vms_request(&VmRequest::MakeRT, socket_path)
+}
+
 fn balloon_vms(mut args: std::env::Args) -> std::result::Result<(), ()> {
     if args.len() < 2 {
         print_help("crosvm balloon", "SIZE VM_SOCKET...", &[]);
@@ -2239,6 +2331,108 @@
     }
 }
 
+#[cfg(feature = "composite-disk")]
+fn create_composite(mut args: std::env::Args) -> std::result::Result<(), ()> {
+    if args.len() < 1 {
+        print_help("crosvm create_composite", "PATH [LABEL:PARTITION]..", &[]);
+        println!("Creates a new composite disk image containing the given partition images");
+        return Err(());
+    }
+
+    let composite_image_path = args.next().unwrap();
+    let zero_filler_path = format!("{}.filler", composite_image_path);
+    let header_path = format!("{}.header", composite_image_path);
+    let footer_path = format!("{}.footer", composite_image_path);
+
+    let mut composite_image_file = OpenOptions::new()
+        .create(true)
+        .read(true)
+        .write(true)
+        .truncate(true)
+        .open(&composite_image_path)
+        .map_err(|e| {
+            error!(
+                "Failed opening composite disk image file at '{}': {}",
+                composite_image_path, e
+            );
+        })?;
+    create_zero_filler(&zero_filler_path).map_err(|e| {
+        error!(
+            "Failed to create zero filler file at '{}': {}",
+            &zero_filler_path, e
+        );
+    })?;
+    let mut header_file = OpenOptions::new()
+        .create(true)
+        .read(true)
+        .write(true)
+        .truncate(true)
+        .open(&header_path)
+        .map_err(|e| {
+            error!(
+                "Failed opening header image file at '{}': {}",
+                header_path, e
+            );
+        })?;
+    let mut footer_file = OpenOptions::new()
+        .create(true)
+        .read(true)
+        .write(true)
+        .truncate(true)
+        .open(&footer_path)
+        .map_err(|e| {
+            error!(
+                "Failed opening footer image file at '{}': {}",
+                footer_path, e
+            );
+        })?;
+
+    let partitions = args
+        .into_iter()
+        .map(|partition_arg| {
+            if let [label, path] = partition_arg.split(":").collect::<Vec<_>>()[..] {
+                let partition_file = File::open(path)
+                    .map_err(|e| error!("Failed to open partition image: {}", e))?;
+                let size = create_disk_file(partition_file)
+                    .map_err(|e| error!("Failed to create DiskFile instance: {}", e))?
+                    .get_len()
+                    .map_err(|e| error!("Failed to get length of partition image: {}", e))?;
+                Ok(PartitionInfo {
+                    label: label.to_owned(),
+                    path: Path::new(path).to_owned(),
+                    partition_type: ImagePartitionType::LinuxFilesystem,
+                    writable: false,
+                    size,
+                })
+            } else {
+                error!(
+                    "Must specify label and path for partition '{}', like LABEL:PATH",
+                    partition_arg
+                );
+                Err(())
+            }
+        })
+        .collect::<Result<Vec<_>, _>>()?;
+
+    create_composite_disk(
+        &partitions,
+        &PathBuf::from(zero_filler_path),
+        &PathBuf::from(header_path),
+        &mut header_file,
+        &PathBuf::from(footer_path),
+        &mut footer_file,
+        &mut composite_image_file,
+    )
+    .map_err(|e| {
+        error!(
+            "Failed to create composite disk image at '{}': {}",
+            composite_image_path, e
+        );
+    })?;
+
+    Ok(())
+}
+
 fn create_qcow2(args: std::env::Args) -> std::result::Result<(), ()> {
     let arguments = [
         Argument::positional("PATH", "where to create the qcow2 image"),
@@ -2459,6 +2653,8 @@
     println!("    balloon - Set balloon size of the crosvm instance.");
     println!("    balloon_stats - Prints virtio balloon statistics.");
     println!("    battery - Modify battery.");
+    #[cfg(feature = "composite-disk")]
+    println!("    create_composite  - Create a new composite disk image file.");
     println!("    create_qcow2  - Create a new qcow2 disk image file.");
     println!("    disk - Manage attached virtual disk devices.");
     println!("    resume - Resumes the crosvm instance.");
@@ -2523,9 +2719,12 @@
         Some("stop") => stop_vms(args),
         Some("suspend") => suspend_vms(args),
         Some("resume") => resume_vms(args),
+        Some("make_rt") => make_rt(args),
         Some("run") => run_vm(args),
         Some("balloon") => balloon_vms(args),
         Some("balloon_stats") => balloon_stats(args),
+        #[cfg(feature = "composite-disk")]
+        Some("create_composite") => create_composite(args),
         Some("create_qcow2") => create_qcow2(args),
         Some("disk") => disk_cmd(args),
         Some("usb") => modify_usb(args),
@@ -2654,7 +2853,7 @@
         );
     }
 
-    #[cfg(feature = "audio")]
+    #[cfg(feature = "audio_cras")]
     #[test]
     fn parse_ac97_vaild() {
         parse_ac97_options("backend=cras").expect("parse should have succeded");
@@ -2666,13 +2865,13 @@
         parse_ac97_options("backend=null").expect("parse should have succeded");
     }
 
-    #[cfg(feature = "audio")]
+    #[cfg(feature = "audio_cras")]
     #[test]
     fn parse_ac97_capture_vaild() {
         parse_ac97_options("backend=cras,capture=true").expect("parse should have succeded");
     }
 
-    #[cfg(feature = "audio")]
+    #[cfg(feature = "audio_cras")]
     #[test]
     fn parse_ac97_client_type() {
         parse_ac97_options("backend=cras,capture=true,client_type=crosvm")
diff --git a/src/plugin/mod.rs b/src/plugin/mod.rs
index f9444f8..28f8ac5 100644
--- a/src/plugin/mod.rs
+++ b/src/plugin/mod.rs
@@ -477,12 +477,18 @@
                                     VcpuExit::IoIn { port, mut size } => {
                                         let mut data = [0; 256];
                                         if size > data.len() {
-                                            error!("unsupported IoIn size of {} bytes", size);
+                                            error!(
+                                                "unsupported IoIn size of {} bytes at port {:#x}",
+                                                size, port
+                                            );
                                             size = data.len();
                                         }
                                         vcpu_plugin.io_read(port as u64, &mut data[..size], &vcpu);
                                         if let Err(e) = vcpu.set_data(&data[..size]) {
-                                            error!("failed to set return data for IoIn: {}", e);
+                                            error!(
+                                                "failed to set return data for IoIn at port {:#x}: {}",
+                                                port, e
+                                            );
                                         }
                                     }
                                     VcpuExit::IoOut {
@@ -491,7 +497,7 @@
                                         data,
                                     } => {
                                         if size > data.len() {
-                                            error!("unsupported IoOut size of {} bytes", size);
+                                            error!("unsupported IoOut size of {} bytes at port {:#x}", size, port);
                                             size = data.len();
                                         }
                                         vcpu_plugin.io_write(port as u64, &data[..size], &vcpu);
diff --git a/src/plugin/process.rs b/src/plugin/process.rs
index c34b708..833616e 100644
--- a/src/plugin/process.rs
+++ b/src/plugin/process.rs
@@ -521,7 +521,7 @@
             return Err(Error::PluginSocketHup);
         }
 
-        let request = protobuf::parse_from_bytes::<MainRequest>(&self.request_buffer[..msg_size])
+        let request: MainRequest = Message::parse_from_bytes(&self.request_buffer[..msg_size])
             .map_err(Error::DecodeRequest)?;
 
         /// Use this to make it easier to stuff various kinds of File-like objects into the
diff --git a/src/plugin/vcpu.rs b/src/plugin/vcpu.rs
index d874f64..c5c93f7 100644
--- a/src/plugin/vcpu.rs
+++ b/src/plugin/vcpu.rs
@@ -13,7 +13,7 @@
 
 use libc::{EINVAL, ENOENT, ENOTTY, EPERM, EPIPE, EPROTO};
 
-use protobuf::Message;
+use protobuf::{CodedOutputStream, Message};
 
 use assertions::const_assert;
 use base::{error, LayoutAllocation};
@@ -23,7 +23,6 @@
     kvm_debugregs, kvm_enable_cap, kvm_fpu, kvm_lapic_state, kvm_mp_state, kvm_msr_entry, kvm_msrs,
     kvm_regs, kvm_sregs, kvm_vcpu_events, kvm_xcrs, KVM_CPUID_FLAG_SIGNIFCANT_INDEX,
 };
-use protobuf::CodedOutputStream;
 use protos::plugin::*;
 use sync::Mutex;
 
@@ -592,9 +591,8 @@
             let mut read_pipe = &self.read_pipe;
             let msg_size = read_pipe.read(&mut request_buffer).map_err(io_to_sys_err)?;
 
-            let mut request =
-                protobuf::parse_from_bytes::<VcpuRequest>(&request_buffer[..msg_size])
-                    .map_err(proto_to_sys_err)?;
+            let mut request: VcpuRequest =
+                Message::parse_from_bytes(&request_buffer[..msg_size]).map_err(proto_to_sys_err)?;
 
             let res = if request.has_wait() {
                 match wait_reason {
diff --git a/sync/Android.bp b/sync/Android.bp
index 176f78e..97414ee 100644
--- a/sync/Android.bp
+++ b/sync/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
 // Do not modify this file as changes will be overridden on upgrade.
 
 package {
@@ -21,7 +21,7 @@
 }
 
 rust_defaults {
-    name: "sync_defaults",
+    name: "sync_test_defaults",
     defaults: ["crosvm_defaults"],
     crate_name: "sync",
     srcs: ["src/lib.rs"],
@@ -32,7 +32,7 @@
 
 rust_test_host {
     name: "sync_host_test_src_lib",
-    defaults: ["sync_defaults"],
+    defaults: ["sync_test_defaults"],
     test_options: {
         unit_test: true,
     },
@@ -40,5 +40,5 @@
 
 rust_test {
     name: "sync_device_test_src_lib",
-    defaults: ["sync_defaults"],
+    defaults: ["sync_test_defaults"],
 }
diff --git a/sys_util/Android.bp b/sys_util/Android.bp
index ba98ac2..18d4048 100644
--- a/sys_util/Android.bp
+++ b/sys_util/Android.bp
@@ -1,5 +1,5 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace --no-subdir.
-// Manually added target and shared_libs to rules below.
+// This file is generated by cargo2android.py --config cargo2android.json.
+// Do not modify this file as changes will be overridden on upgrade.
 
 package {
     // See: http://go/android-license-faq
@@ -49,61 +49,3 @@
         },
     },
 }
-
-rust_defaults {
-    name: "sys_util_defaults",
-    defaults: ["crosvm_defaults"],
-    crate_name: "sys_util",
-    srcs: ["src/lib.rs"],
-    test_suites: ["general-tests"],
-    auto_gen_config: true,
-    edition: "2018",
-    rustlibs: [
-        "libdata_model",
-        "liblibc",
-        "libserde",
-        "libserde_json",
-        "libsync_rust",
-        "libtempfile",
-        "libthiserror",
-    ],
-    proc_macros: ["libpoll_token_derive"],
-    shared_libs: ["libcap"],  // specified in src/capabilities.rs
-}
-
-// TODO: This doesn't link because of missing getrandom in Bionic (host only, it's
-// available in the guest). Fix it upstream.
-// rust_test_host {
-//     name: "sys_util_host_test_src_lib",
-//     defaults: ["sys_util_defaults"],
-//     test_options: {
-//         unit_test: true,
-//     },
-// }
-
-// TODO: This doesn't build due to missing shm_open &c. in Bionic. Fix it upstream.
-//rust_test {
-//    name: "sys_util_device_test_src_lib",
-//    defaults: ["sys_util_defaults"],
-//    rustlibs: [
-//        "libandroid_log_sys",
-//    ],
-//}
-
-// dependent_library ["feature_list"]
-//   ../assertions/src/lib.rs
-//   ../data_model/src/lib.rs
-//   ../sync/src/lib.rs
-//   ../tempfile/src/lib.rs
-//   itoa-0.4.7
-//   libc-0.2.97 "default,std"
-//   proc-macro2-1.0.27 "default,proc-macro"
-//   quote-1.0.9 "default,proc-macro"
-//   ryu-1.0.5
-//   serde-1.0.126 "default,derive,serde_derive,std"
-//   serde_derive-1.0.126 "default"
-//   serde_json-1.0.64 "default,std"
-//   syn-1.0.73 "clone-impls,default,derive,parsing,printing,proc-macro,quote"
-//   thiserror-1.0.25
-//   thiserror-impl-1.0.25
-//   unicode-xid-0.2.2 "default"
diff --git a/sys_util/cargo2android.json b/sys_util/cargo2android.json
new file mode 100644
index 0000000..02b80cc
--- /dev/null
+++ b/sys_util/cargo2android.json
@@ -0,0 +1,9 @@
+{
+  "add-module-block": "cargo2android_target.bp",
+  "add_workspace": true,
+  "device": true,
+  "global_defaults": "crosvm_defaults",
+  "no-subdir": true,
+  "run": true,
+  "tests": false
+}
\ No newline at end of file
diff --git a/sys_util/cargo2android_target.bp b/sys_util/cargo2android_target.bp
new file mode 100644
index 0000000..7e4db94
--- /dev/null
+++ b/sys_util/cargo2android_target.bp
@@ -0,0 +1,21 @@
+shared_libs: ["libcap"],  // specified in src/capabilities.rs
+target: {
+    android: {
+        rustlibs: [
+            "libandroid_log_sys",
+        ],
+    },
+    linux_bionic_arm64: {
+        // For ARM architecture, we use aarch64-linux-android for BOTH
+        // device and host targets. As a result, host targets are also
+        // built with target_os = "android". Therefore, sys_util/src/android
+        // is used and thus this android module is required.
+        // This seems incorrect, but is inevitable because rustc doesn't
+        // yet support a Linux-based target using Bionic as libc. We can't
+        // use aarch64-unknown-linux-gnu because it's using glibc which
+        // we don't support for cross-host builds.
+        rustlibs: [
+            "libandroid_log_sys",
+        ],
+    },
+}
\ No newline at end of file
diff --git a/sys_util/poll_token_derive/Android.bp b/sys_util/poll_token_derive/Android.bp
index e533b91..cc2cd01 100644
--- a/sys_util/poll_token_derive/Android.bp
+++ b/sys_util/poll_token_derive/Android.bp
@@ -1,5 +1,5 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
-// Manually disabled tests which depend on host on crate.
+// This file is generated by cargo2android.py --config cargo2android.json.
+// Do not modify this file as changes will be overridden on upgrade.
 
 package {
     // See: http://go/android-license-faq
@@ -22,37 +22,3 @@
         "libsyn",
     ],
 }
-
-rust_defaults {
-    name: "poll_token_derive_defaults",
-    defaults: ["crosvm_defaults"],
-    crate_name: "poll_token_derive",
-    srcs: ["poll_token_derive.rs"],
-    test_suites: ["general-tests"],
-    auto_gen_config: true,
-    edition: "2018",
-    rustlibs: [
-        "libproc_macro2",
-        "libquote",
-        "libsyn",
-    ],
-}
-
-//rust_test_host {
-//    name: "poll_token_derive_host_test_poll_token_derive",
-//    defaults: ["poll_token_derive_defaults"],
-//    test_options: {
-//        unit_test: true,
-//    },
-//}
-
-//rust_test {
-//    name: "poll_token_derive_device_test_poll_token_derive",
-//    defaults: ["poll_token_derive_defaults"],
-//}
-
-// dependent_library ["feature_list"]
-//   proc-macro2-1.0.27 "default,proc-macro"
-//   quote-1.0.9 "default,proc-macro"
-//   syn-1.0.73 "clone-impls,default,derive,parsing,printing,proc-macro,quote"
-//   unicode-xid-0.2.2 "default"
diff --git a/sys_util/poll_token_derive/cargo2android.json b/sys_util/poll_token_derive/cargo2android.json
new file mode 100644
index 0000000..9a6eee3
--- /dev/null
+++ b/sys_util/poll_token_derive/cargo2android.json
@@ -0,0 +1,7 @@
+{
+  "add_workspace": true,
+  "device": true,
+  "global_defaults": "crosvm_defaults",
+  "run": true,
+  "tests": false
+}
\ No newline at end of file
diff --git a/sys_util/src/descriptor.rs b/sys_util/src/descriptor.rs
index 4bccc58..7ea4dfb 100644
--- a/sys_util/src/descriptor.rs
+++ b/sys_util/src/descriptor.rs
@@ -161,6 +161,13 @@
     }
 }
 
+impl From<SafeDescriptor> for UnixStream {
+    fn from(s: SafeDescriptor) -> Self {
+        // Safe because we own the SafeDescriptor at this point.
+        unsafe { Self::from_raw_fd(s.into_raw_descriptor()) }
+    }
+}
+
 /// For use cases where a simple wrapper around a RawDescriptor is needed.
 /// This is a simply a wrapper and does not manage the lifetime of the descriptor.
 /// Most usages should prefer SafeDescriptor or using a RawDescriptor directly
diff --git a/sys_util/src/lib.rs b/sys_util/src/lib.rs
index e59b9cf..3313265 100644
--- a/sys_util/src/lib.rs
+++ b/sys_util/src/lib.rs
@@ -94,22 +94,25 @@
 
 use std::cell::Cell;
 use std::ffi::CStr;
-use std::fs::{remove_file, File};
+use std::fs::{remove_file, File, OpenOptions};
 use std::mem;
+use std::os::unix::fs::OpenOptionsExt;
 use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
 use std::os::unix::net::UnixDatagram;
+use std::path::Path;
 use std::ptr;
 use std::time::Duration;
 
 use libc::{
-    c_int, c_long, fcntl, pipe2, syscall, sysconf, waitpid, SYS_getpid, SYS_gettid, F_GETFL,
-    F_SETFL, O_CLOEXEC, SIGKILL, WNOHANG, _SC_IOV_MAX, _SC_PAGESIZE,
+    c_int, c_long, fcntl, pipe2, syscall, sysconf, waitpid, SYS_getpid, SYS_gettid, EINVAL,
+    F_GETFL, F_SETFL, O_CLOEXEC, O_DIRECT, SIGKILL, WNOHANG, _SC_IOV_MAX, _SC_PAGESIZE,
 };
 
 /// Re-export libc types that are part of the API.
 pub type Pid = libc::pid_t;
 pub type Uid = libc::uid_t;
 pub type Gid = libc::gid_t;
+pub type Mode = libc::mode_t;
 
 /// Used to mark types as !Sync.
 pub type UnsyncMarker = std::marker::PhantomData<Cell<usize>>;
@@ -193,6 +196,20 @@
     syscall!(unsafe { libc::chown(path.as_ptr(), uid, gid) }).map(|_| ())
 }
 
+/// Safe wrapper for fchmod(2).
+#[inline(always)]
+pub fn fchmod<A: AsRawFd>(fd: &A, mode: Mode) -> Result<()> {
+    // Safe since the function does not operate on pointers and check the return value.
+    syscall!(unsafe { libc::fchmod(fd.as_raw_fd(), mode) }).map(|_| ())
+}
+
+/// Safe wrapper for fchown(2).
+#[inline(always)]
+pub fn fchown<A: AsRawFd>(fd: &A, uid: Uid, gid: Gid) -> Result<()> {
+    // Safe since the function does not operate on pointers and check the return value.
+    syscall!(unsafe { libc::fchown(fd.as_raw_fd(), uid, gid) }).map(|_| ())
+}
+
 /// The operation to perform with `flock`.
 pub enum FlockOperation {
     LockShared,
@@ -465,8 +482,49 @@
     Duration::new(libc::time_t::max_value() as u64, 999999999)
 }
 
+/// If the given path is of the form /proc/self/fd/N for some N, returns `Ok(Some(N))`. Otherwise
+/// returns `Ok(None`).
+pub fn safe_descriptor_from_path<P: AsRef<Path>>(path: P) -> Result<Option<SafeDescriptor>> {
+    let path = path.as_ref();
+    if path.parent() == Some(Path::new("/proc/self/fd")) {
+        let raw_descriptor = path
+            .file_name()
+            .and_then(|fd_osstr| fd_osstr.to_str())
+            .and_then(|fd_str| fd_str.parse::<RawFd>().ok())
+            .ok_or_else(|| Error::new(EINVAL))?;
+        let validated_fd = validate_raw_fd(raw_descriptor)?;
+        Ok(Some(
+            // Safe because nothing else has access to validated_fd after this call.
+            unsafe { SafeDescriptor::from_raw_descriptor(validated_fd) },
+        ))
+    } else {
+        Ok(None)
+    }
+}
+
+/// Open the file with the given path, or if it is of the form `/proc/self/fd/N` then just use the
+/// file descriptor.
+///
+/// Note that this will not work properly if the same `/proc/self/fd/N` path is used twice in
+/// different places, as the metadata (including the offset) will be shared between both file
+/// descriptors.
+pub fn open_file<P: AsRef<Path>>(path: P, read_only: bool, o_direct: bool) -> Result<File> {
+    let path = path.as_ref();
+    // Special case '/proc/self/fd/*' paths. The FD is already open, just use it.
+    Ok(if let Some(fd) = safe_descriptor_from_path(path)? {
+        fd.into()
+    } else {
+        let mut options = OpenOptions::new();
+        if o_direct {
+            options.custom_flags(O_DIRECT);
+        }
+        options.write(!read_only).read(true).open(path)?
+    })
+}
+
 #[cfg(test)]
 mod tests {
+    use libc::EBADF;
     use std::io::Write;
 
     use super::*;
@@ -481,4 +539,35 @@
         tx.write(&[0u8; 8])
             .expect_err("Write after fill didn't fail");
     }
+
+    #[test]
+    fn safe_descriptor_from_path_valid() {
+        assert!(safe_descriptor_from_path(Path::new("/proc/self/fd/2"))
+            .unwrap()
+            .is_some());
+    }
+
+    #[test]
+    fn safe_descriptor_from_path_invalid_integer() {
+        assert_eq!(
+            safe_descriptor_from_path(Path::new("/proc/self/fd/blah")),
+            Err(Error::new(EINVAL))
+        );
+    }
+
+    #[test]
+    fn safe_descriptor_from_path_invalid_fd() {
+        assert_eq!(
+            safe_descriptor_from_path(Path::new("/proc/self/fd/42")),
+            Err(Error::new(EBADF))
+        );
+    }
+
+    #[test]
+    fn safe_descriptor_from_path_none() {
+        assert_eq!(
+            safe_descriptor_from_path(Path::new("/something/else")).unwrap(),
+            None
+        );
+    }
 }
diff --git a/sys_util/src/sched.rs b/sys_util/src/sched.rs
index d9972c6..2a44ab8 100644
--- a/sys_util/src/sched.rs
+++ b/sys_util/src/sched.rs
@@ -72,7 +72,7 @@
     const PR_SCHED_CORE: i32 = 62;
     const PR_SCHED_CORE_CREATE: i32 = 1;
 
-    #[allow(non_camel_case_types, dead_code)]
+    #[allow(clippy::upper_case_acronyms, non_camel_case_types, dead_code)]
     /// Specifies the scope of the pid parameter of `PR_SCHED_CORE`.
     enum pid_type {
         /// `PID` refers to threads.
diff --git a/sys_util/src/vsock.rs b/sys_util/src/vsock.rs
index a51dfdb..b690fb7 100644
--- a/sys_util/src/vsock.rs
+++ b/sys_util/src/vsock.rs
@@ -53,7 +53,7 @@
 }
 
 /// The vsock equivalent of an IP address.
-#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
+#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
 pub enum VsockCid {
     /// Vsock equivalent of INADDR_ANY. Indicates the context id of the current endpoint.
     Any,
@@ -113,7 +113,7 @@
 }
 
 /// An address associated with a virtual socket.
-#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
+#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
 pub struct SocketAddr {
     pub cid: VsockCid,
     pub port: c_uint,
diff --git a/tempfile/Android.bp b/tempfile/Android.bp
index 578922c..d089af2 100644
--- a/tempfile/Android.bp
+++ b/tempfile/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
 // Do not modify this file as changes will be overridden on upgrade.
 
 package {
@@ -23,7 +23,7 @@
 }
 
 rust_defaults {
-    name: "tempfile_defaults",
+    name: "tempfile_test_defaults",
     defaults: ["crosvm_defaults"],
     crate_name: "tempfile",
     srcs: ["src/lib.rs"],
@@ -37,7 +37,7 @@
 
 rust_test_host {
     name: "tempfile_host_test_src_lib",
-    defaults: ["tempfile_defaults"],
+    defaults: ["tempfile_test_defaults"],
     test_options: {
         unit_test: true,
     },
@@ -45,8 +45,5 @@
 
 rust_test {
     name: "tempfile_device_test_src_lib",
-    defaults: ["tempfile_defaults"],
+    defaults: ["tempfile_test_defaults"],
 }
-
-// dependent_library ["feature_list"]
-//   libc-0.2.97 "default,std"
diff --git a/tests/plugins.rs b/tests/plugins.rs
index e7c872e..8992045 100644
--- a/tests/plugins.rs
+++ b/tests/plugins.rs
@@ -10,11 +10,11 @@
 use std::io::{Read, Write};
 use std::path::{Path, PathBuf};
 use std::process::{Command, Stdio};
+use std::sync::atomic::{AtomicUsize, Ordering};
 use std::thread::sleep;
 use std::time::Duration;
 
 use base::{ioctl, AsRawDescriptor};
-use rand_ish::urandom_str;
 use tempfile::tempfile;
 
 struct RemovePath(PathBuf);
@@ -50,10 +50,13 @@
 }
 
 fn build_plugin(src: &str) -> RemovePath {
+    static PLUGIN_NUM: AtomicUsize = AtomicUsize::new(0);
     let libcrosvm_plugin_dir = get_target_path();
     let mut out_bin = libcrosvm_plugin_dir.clone();
-    let randbin = urandom_str(10).expect("failed to generate random bin name");
-    out_bin.push(randbin);
+    out_bin.push(format!(
+        "plugin-test{}",
+        PLUGIN_NUM.fetch_add(1, Ordering::Relaxed)
+    ));
     let mut child = Command::new(var_os("CC").unwrap_or(OsString::from("cc")))
         .args(&["-Icrosvm_plugin", "-pthread", "-o"]) // crosvm.h location and set output path.
         .arg(&out_bin)
diff --git a/tpm2-sys/Android.bp b/tpm2-sys/Android.bp
index f8d68d3..25e7b3e 100644
--- a/tpm2-sys/Android.bp
+++ b/tpm2-sys/Android.bp
@@ -1,4 +1 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
-// Do not modify this file as changes will be overridden on upgrade.
-
 // Feature is disabled for Android
diff --git a/tpm2-sys/cargo2android.json b/tpm2-sys/cargo2android.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/tpm2-sys/cargo2android.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/tpm2-sys/libtpm2 b/tpm2-sys/libtpm2
deleted file mode 160000
index 6ab308b..0000000
--- a/tpm2-sys/libtpm2
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 6ab308b4578233be5c65d3a334be48419ea677a9
diff --git a/tpm2/Android.bp b/tpm2/Android.bp
index f8d68d3..25e7b3e 100644
--- a/tpm2/Android.bp
+++ b/tpm2/Android.bp
@@ -1,4 +1 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
-// Do not modify this file as changes will be overridden on upgrade.
-
 // Feature is disabled for Android
diff --git a/tpm2/cargo2android.json b/tpm2/cargo2android.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/tpm2/cargo2android.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/usb_sys/Android.bp b/usb_sys/Android.bp
index 502f4b3..5c99d31 100644
--- a/usb_sys/Android.bp
+++ b/usb_sys/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
 // Do not modify this file as changes will be overridden on upgrade.
 
 package {
@@ -23,7 +23,7 @@
 }
 
 rust_defaults {
-    name: "usb_sys_defaults",
+    name: "usb_sys_test_defaults",
     defaults: ["crosvm_defaults"],
     crate_name: "usb_sys",
     srcs: ["src/lib.rs"],
@@ -37,7 +37,7 @@
 
 rust_test_host {
     name: "usb_sys_host_test_src_lib",
-    defaults: ["usb_sys_defaults"],
+    defaults: ["usb_sys_test_defaults"],
     test_options: {
         unit_test: true,
     },
@@ -45,45 +45,5 @@
 
 rust_test {
     name: "usb_sys_device_test_src_lib",
-    defaults: ["usb_sys_defaults"],
+    defaults: ["usb_sys_test_defaults"],
 }
-
-// dependent_library ["feature_list"]
-//   ../assertions/src/lib.rs
-//   ../base/src/lib.rs
-//   ../cros_async/src/lib.rs
-//   ../data_model/src/lib.rs
-//   ../io_uring/src/lib.rs
-//   ../sync/src/lib.rs
-//   ../sys_util/poll_token_derive/poll_token_derive.rs
-//   ../sys_util/src/lib.rs
-//   ../tempfile/src/lib.rs
-//   async-task-4.0.3 "default,std"
-//   async-trait-0.1.50
-//   autocfg-1.0.1
-//   futures-0.3.15 "alloc"
-//   futures-channel-0.3.15 "alloc,futures-sink,sink"
-//   futures-core-0.3.15 "alloc"
-//   futures-io-0.3.15
-//   futures-sink-0.3.15 "alloc"
-//   futures-task-0.3.15 "alloc"
-//   futures-util-0.3.15 "alloc,futures-sink,sink"
-//   intrusive-collections-0.9.1 "alloc,default"
-//   itoa-0.4.7
-//   libc-0.2.97 "default,std"
-//   memoffset-0.5.6 "default"
-//   paste-1.0.5
-//   pin-project-lite-0.2.7
-//   pin-utils-0.1.0
-//   proc-macro2-1.0.27 "default,proc-macro"
-//   quote-1.0.9 "default,proc-macro"
-//   ryu-1.0.5
-//   serde-1.0.126 "default,derive,serde_derive,std"
-//   serde_derive-1.0.126 "default"
-//   serde_json-1.0.64 "default,std"
-//   slab-0.4.3 "default,std"
-//   smallvec-1.6.1
-//   syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-//   thiserror-1.0.25
-//   thiserror-impl-1.0.25
-//   unicode-xid-0.2.2 "default"
diff --git a/usb_util/Android.bp b/usb_util/Android.bp
index 250d7c2..84bad4e 100644
--- a/usb_util/Android.bp
+++ b/usb_util/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
 // Do not modify this file as changes will be overridden on upgrade.
 
 package {
@@ -28,7 +28,7 @@
 }
 
 rust_defaults {
-    name: "usb_util_defaults",
+    name: "usb_util_test_defaults",
     defaults: ["crosvm_defaults"],
     crate_name: "usb_util",
     srcs: ["src/lib.rs"],
@@ -47,7 +47,7 @@
 
 rust_test_host {
     name: "usb_util_host_test_src_lib",
-    defaults: ["usb_util_defaults"],
+    defaults: ["usb_util_test_defaults"],
     test_options: {
         unit_test: true,
     },
@@ -55,47 +55,5 @@
 
 rust_test {
     name: "usb_util_device_test_src_lib",
-    defaults: ["usb_util_defaults"],
+    defaults: ["usb_util_test_defaults"],
 }
-
-// dependent_library ["feature_list"]
-//   ../assertions/src/lib.rs
-//   ../base/src/lib.rs
-//   ../cros_async/src/lib.rs
-//   ../data_model/src/lib.rs
-//   ../io_uring/src/lib.rs
-//   ../sync/src/lib.rs
-//   ../sys_util/poll_token_derive/poll_token_derive.rs
-//   ../sys_util/src/lib.rs
-//   ../tempfile/src/lib.rs
-//   ../usb_sys/src/lib.rs
-//   async-task-4.0.3 "default,std"
-//   async-trait-0.1.50
-//   autocfg-1.0.1
-//   futures-0.3.15 "alloc"
-//   futures-channel-0.3.15 "alloc,futures-sink,sink"
-//   futures-core-0.3.15 "alloc"
-//   futures-io-0.3.15
-//   futures-sink-0.3.15 "alloc"
-//   futures-task-0.3.15 "alloc"
-//   futures-util-0.3.15 "alloc,futures-sink,sink"
-//   intrusive-collections-0.9.1 "alloc,default"
-//   itoa-0.4.7
-//   libc-0.2.97 "default,std"
-//   memoffset-0.5.6 "default"
-//   paste-1.0.5
-//   pin-project-lite-0.2.7
-//   pin-utils-0.1.0
-//   proc-macro2-1.0.27 "default,proc-macro"
-//   quote-1.0.9 "default,proc-macro"
-//   remain-0.2.2
-//   ryu-1.0.5
-//   serde-1.0.126 "default,derive,serde_derive,std"
-//   serde_derive-1.0.126 "default"
-//   serde_json-1.0.64 "default,std"
-//   slab-0.4.3 "default,std"
-//   smallvec-1.6.1
-//   syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-//   thiserror-1.0.25
-//   thiserror-impl-1.0.25
-//   unicode-xid-0.2.2 "default"
diff --git a/usb_util/src/descriptor.rs b/usb_util/src/descriptor.rs
index 88bdbc0..09da4b7 100644
--- a/usb_util/src/descriptor.rs
+++ b/usb_util/src/descriptor.rs
@@ -148,6 +148,13 @@
                 *offset += hdr.bLength as usize - size_of::<DescriptorHeader>();
                 return Ok((*desc, desc_offset));
             } else {
+                // Finding a ConfigDescriptor while looking for InterfaceDescriptor means
+                // that we should advance to the next device configuration.
+                if desc_type == types::InterfaceDescriptor::descriptor_type() as u8
+                    && hdr.bDescriptorType == types::ConfigDescriptor::descriptor_type() as u8
+                {
+                    return Err(Error::DescriptorParse);
+                }
                 // Skip this entire descriptor, since it's not the right type.
                 *offset += hdr.bLength as usize;
             }
@@ -173,43 +180,44 @@
                 interface_descriptors: BTreeMap::new(),
             };
 
-            for intf_idx in 0..config_descriptor.bNumInterfaces {
-                if let Ok((raw_interface_descriptor, interface_offset)) =
-                    next_descriptor::<types::InterfaceDescriptor>(
-                        &device_descriptor.raw,
-                        &mut offset,
-                    )
-                {
-                    let mut interface_descriptor = InterfaceDescriptorTree {
-                        offset: interface_offset,
-                        inner: raw_interface_descriptor,
-                        endpoint_descriptors: BTreeMap::new(),
-                    };
+            while let Ok((raw_interface_descriptor, interface_offset)) =
+                next_descriptor::<types::InterfaceDescriptor>(&device_descriptor.raw, &mut offset)
+            {
+                let mut interface_descriptor = InterfaceDescriptorTree {
+                    offset: interface_offset,
+                    inner: raw_interface_descriptor,
+                    endpoint_descriptors: BTreeMap::new(),
+                };
 
-                    for ep_idx in 0..interface_descriptor.bNumEndpoints {
-                        if let Ok((endpoint_descriptor, _)) = next_descriptor::<EndpointDescriptor>(
-                            &device_descriptor.raw,
-                            &mut offset,
-                        ) {
-                            interface_descriptor
-                                .endpoint_descriptors
-                                .insert(ep_idx, endpoint_descriptor);
-                        } else {
-                            warn!("Could not read endpoint descriptor {}", ep_idx);
-                            break;
-                        }
+                for ep_idx in 0..interface_descriptor.bNumEndpoints {
+                    if let Ok((endpoint_descriptor, _)) =
+                        next_descriptor::<EndpointDescriptor>(&device_descriptor.raw, &mut offset)
+                    {
+                        interface_descriptor
+                            .endpoint_descriptors
+                            .insert(ep_idx, endpoint_descriptor);
+                    } else {
+                        warn!("Could not read endpoint descriptor {}", ep_idx);
+                        break;
                     }
+                }
 
-                    config_descriptor.interface_descriptors.insert(
-                        (
-                            interface_descriptor.bInterfaceNumber,
-                            interface_descriptor.bAlternateSetting,
-                        ),
-                        interface_descriptor,
-                    );
-                } else {
-                    warn!("Could not read interface descriptor {}", intf_idx);
-                    break;
+                config_descriptor.interface_descriptors.insert(
+                    (
+                        interface_descriptor.bInterfaceNumber,
+                        interface_descriptor.bAlternateSetting,
+                    ),
+                    interface_descriptor,
+                );
+            }
+
+            for intf_idx in 0..config_descriptor.bNumInterfaces {
+                if config_descriptor
+                    .interface_descriptors
+                    .get(&(intf_idx, 0))
+                    .is_none()
+                {
+                    warn!("device interface {} has no interface descriptors", intf_idx);
                 }
             }
 
@@ -542,4 +550,109 @@
         assert_eq!(u16::from(e.wMaxPacketSize), 0x0200);
         assert_eq!(e.bInterval, 0);
     }
+
+    #[test]
+    fn parse_descriptors_multiple_altsettings() {
+        let data: &[u8] = &[
+            // DeviceDescriptor
+            0x12, 0x01, 0x00, 0x02, 0xef, 0x02, 0x01, 0x40, 0x6d, 0x04, 0x43, 0x08, 0x13, 0x00,
+            0x00, 0x02, 0x01, 0x01, // ConfigDescriptor
+            0x09, 0x02, 0x0d, 0x0a, 0x03, 0x01, 0x00, 0x80, 0xfa,
+            // InterfaceDescriptor 0, 0
+            0x09, 0x04, 0x00, 0x00, 0x01, 0x0e, 0x01, 0x00, 0x00, // EndpointDescriptor
+            0x07, 0x05, 0x86, 0x03, 0x40, 0x00, 0x08, // InterfaceDescriptor 1, 0
+            0x09, 0x04, 0x01, 0x00, 0x00, 0x0e, 0x02, 0x00, 0x00,
+            // InterfaceDescriptor 1, 1
+            0x09, 0x04, 0x01, 0x01, 0x01, 0x0e, 0x02, 0x00, 0x00, // EndpointDescriptor
+            0x07, 0x05, 0x81, 0x05, 0xc0, 0x00, 0x01, // InterfaceDescriptor 2, 0
+            0x09, 0x04, 0x02, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+        ];
+
+        let d = parse_usbfs_descriptors(data).expect("parse_usbfs_descriptors failed");
+
+        // The seemingly-redundant u16::from() calls avoid borrows of packed fields.
+
+        assert_eq!(u16::from(d.bcdUSB), 0x02_00);
+        assert_eq!(d.bDeviceClass, 0xef);
+        assert_eq!(d.bDeviceSubClass, 0x02);
+        assert_eq!(d.bDeviceProtocol, 0x01);
+        assert_eq!(d.bMaxPacketSize0, 64);
+        assert_eq!(u16::from(d.idVendor), 0x046d);
+        assert_eq!(u16::from(d.idProduct), 0x0843);
+        assert_eq!(u16::from(d.bcdDevice), 0x00_13);
+        assert_eq!(d.iManufacturer, 0);
+        assert_eq!(d.iProduct, 2);
+        assert_eq!(d.iSerialNumber, 1);
+        assert_eq!(d.bNumConfigurations, 1);
+
+        let c = d
+            .get_config_descriptor(1)
+            .expect("could not get config descriptor 1");
+        assert_eq!(u16::from(c.wTotalLength), 2573);
+        assert_eq!(c.bNumInterfaces, 3);
+        assert_eq!(c.bConfigurationValue, 1);
+        assert_eq!(c.iConfiguration, 0);
+        assert_eq!(c.bmAttributes, 0x80);
+        assert_eq!(c.bMaxPower, 250);
+
+        let i = c
+            .get_interface_descriptor(0, 0)
+            .expect("could not get interface descriptor 0 alt setting 0");
+        assert_eq!(i.bInterfaceNumber, 0);
+        assert_eq!(i.bAlternateSetting, 0);
+        assert_eq!(i.bNumEndpoints, 1);
+        assert_eq!(i.bInterfaceClass, 0x0e);
+        assert_eq!(i.bInterfaceSubClass, 0x01);
+        assert_eq!(i.bInterfaceProtocol, 0x00);
+        assert_eq!(i.iInterface, 0x00);
+
+        let e = i
+            .get_endpoint_descriptor(0)
+            .expect("could not get endpoint 0 descriptor");
+        assert_eq!(e.bEndpointAddress, 0x86);
+        assert_eq!(e.bmAttributes, 0x03);
+        assert_eq!(u16::from(e.wMaxPacketSize), 0x40);
+        assert_eq!(e.bInterval, 0x08);
+
+        let i = c
+            .get_interface_descriptor(1, 0)
+            .expect("could not get interface descriptor 1 alt setting 0");
+        assert_eq!(i.bInterfaceNumber, 1);
+        assert_eq!(i.bAlternateSetting, 0);
+        assert_eq!(i.bNumEndpoints, 0);
+        assert_eq!(i.bInterfaceClass, 0x0e);
+        assert_eq!(i.bInterfaceSubClass, 0x02);
+        assert_eq!(i.bInterfaceProtocol, 0x00);
+        assert_eq!(i.iInterface, 0x00);
+
+        let i = c
+            .get_interface_descriptor(1, 1)
+            .expect("could not get interface descriptor 1 alt setting 1");
+        assert_eq!(i.bInterfaceNumber, 1);
+        assert_eq!(i.bAlternateSetting, 1);
+        assert_eq!(i.bNumEndpoints, 1);
+        assert_eq!(i.bInterfaceClass, 0x0e);
+        assert_eq!(i.bInterfaceSubClass, 0x02);
+        assert_eq!(i.bInterfaceProtocol, 0x00);
+        assert_eq!(i.iInterface, 0x00);
+
+        let e = i
+            .get_endpoint_descriptor(0)
+            .expect("could not get endpoint 0 descriptor");
+        assert_eq!(e.bEndpointAddress, 0x81);
+        assert_eq!(e.bmAttributes, 0x05);
+        assert_eq!(u16::from(e.wMaxPacketSize), 0xc0);
+        assert_eq!(e.bInterval, 0x01);
+
+        let i = c
+            .get_interface_descriptor(2, 0)
+            .expect("could not get interface descriptor 2 alt setting 0");
+        assert_eq!(i.bInterfaceNumber, 2);
+        assert_eq!(i.bAlternateSetting, 0);
+        assert_eq!(i.bNumEndpoints, 0);
+        assert_eq!(i.bInterfaceClass, 0x01);
+        assert_eq!(i.bInterfaceSubClass, 0x01);
+        assert_eq!(i.bInterfaceProtocol, 0x00);
+        assert_eq!(i.iInterface, 0x00);
+    }
 }
diff --git a/vfio_sys/Android.bp b/vfio_sys/Android.bp
index def2958..7d829bb 100644
--- a/vfio_sys/Android.bp
+++ b/vfio_sys/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
 // Do not modify this file as changes will be overridden on upgrade.
 
 package {
@@ -23,7 +23,7 @@
 }
 
 rust_defaults {
-    name: "vfio_sys_defaults",
+    name: "vfio_sys_test_defaults",
     defaults: ["crosvm_defaults"],
     crate_name: "vfio_sys",
     srcs: ["src/lib.rs"],
@@ -37,7 +37,7 @@
 
 rust_test_host {
     name: "vfio_sys_host_test_src_lib",
-    defaults: ["vfio_sys_defaults"],
+    defaults: ["vfio_sys_test_defaults"],
     test_options: {
         unit_test: true,
     },
@@ -45,45 +45,5 @@
 
 rust_test {
     name: "vfio_sys_device_test_src_lib",
-    defaults: ["vfio_sys_defaults"],
+    defaults: ["vfio_sys_test_defaults"],
 }
-
-// dependent_library ["feature_list"]
-//   ../assertions/src/lib.rs
-//   ../base/src/lib.rs
-//   ../cros_async/src/lib.rs
-//   ../data_model/src/lib.rs
-//   ../io_uring/src/lib.rs
-//   ../sync/src/lib.rs
-//   ../sys_util/poll_token_derive/poll_token_derive.rs
-//   ../sys_util/src/lib.rs
-//   ../tempfile/src/lib.rs
-//   async-task-4.0.3 "default,std"
-//   async-trait-0.1.50
-//   autocfg-1.0.1
-//   futures-0.3.15 "alloc"
-//   futures-channel-0.3.15 "alloc,futures-sink,sink"
-//   futures-core-0.3.15 "alloc"
-//   futures-io-0.3.15
-//   futures-sink-0.3.15 "alloc"
-//   futures-task-0.3.15 "alloc"
-//   futures-util-0.3.15 "alloc,futures-sink,sink"
-//   intrusive-collections-0.9.1 "alloc,default"
-//   itoa-0.4.7
-//   libc-0.2.97 "default,std"
-//   memoffset-0.5.6 "default"
-//   paste-1.0.5
-//   pin-project-lite-0.2.7
-//   pin-utils-0.1.0
-//   proc-macro2-1.0.27 "default,proc-macro"
-//   quote-1.0.9 "default,proc-macro"
-//   ryu-1.0.5
-//   serde-1.0.126 "default,derive,serde_derive,std"
-//   serde_derive-1.0.126 "default"
-//   serde_json-1.0.64 "default,std"
-//   slab-0.4.3 "default,std"
-//   smallvec-1.6.1
-//   syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-//   thiserror-1.0.25
-//   thiserror-impl-1.0.25
-//   unicode-xid-0.2.2 "default"
diff --git a/vfio_sys/src/vfio.rs b/vfio_sys/src/vfio.rs
index 622b5db..315ba50 100644
--- a/vfio_sys/src/vfio.rs
+++ b/vfio_sys/src/vfio.rs
@@ -91,12 +91,12 @@
 pub const VFIO_REGION_INFO_FLAG_CAPS: u32 = 8;
 pub const VFIO_REGION_INFO_CAP_SPARSE_MMAP: u32 = 1;
 pub const VFIO_REGION_INFO_CAP_TYPE: u32 = 2;
+pub const VFIO_REGION_INFO_CAP_MSIX_MAPPABLE: u32 = 3;
 pub const VFIO_REGION_TYPE_PCI_VENDOR_TYPE: u32 = 2147483648;
 pub const VFIO_REGION_TYPE_PCI_VENDOR_MASK: u32 = 65535;
 pub const VFIO_REGION_SUBTYPE_INTEL_IGD_OPREGION: u32 = 1;
 pub const VFIO_REGION_SUBTYPE_INTEL_IGD_HOST_CFG: u32 = 2;
 pub const VFIO_REGION_SUBTYPE_INTEL_IGD_LPC_CFG: u32 = 3;
-pub const VFIO_REGION_INFO_CAP_MSIX_MAPPABLE: u32 = 3;
 pub const VFIO_IRQ_INFO_EVENTFD: u32 = 1;
 pub const VFIO_IRQ_INFO_MASKABLE: u32 = 2;
 pub const VFIO_IRQ_INFO_AUTOMASKED: u32 = 4;
diff --git a/vhost/Android.bp b/vhost/Android.bp
index c359eda..1af4254 100644
--- a/vhost/Android.bp
+++ b/vhost/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
 // Do not modify this file as changes will be overridden on upgrade.
 
 package {
@@ -28,7 +28,7 @@
 }
 
 rust_defaults {
-    name: "vhost_defaults",
+    name: "vhost_test_defaults",
     defaults: ["crosvm_defaults"],
     crate_name: "vhost",
     srcs: ["src/lib.rs"],
@@ -47,7 +47,7 @@
 
 rust_test_host {
     name: "vhost_host_test_src_lib",
-    defaults: ["vhost_defaults"],
+    defaults: ["vhost_test_defaults"],
     test_options: {
         unit_test: true,
     },
@@ -55,50 +55,5 @@
 
 rust_test {
     name: "vhost_device_test_src_lib",
-    defaults: ["vhost_defaults"],
+    defaults: ["vhost_test_defaults"],
 }
-
-// dependent_library ["feature_list"]
-//   ../assertions/src/lib.rs
-//   ../base/src/lib.rs
-//   ../cros_async/src/lib.rs
-//   ../data_model/src/lib.rs
-//   ../io_uring/src/lib.rs
-//   ../net_sys/src/lib.rs
-//   ../net_util/src/lib.rs
-//   ../sync/src/lib.rs
-//   ../sys_util/poll_token_derive/poll_token_derive.rs
-//   ../sys_util/src/lib.rs
-//   ../tempfile/src/lib.rs
-//   ../virtio_sys/src/lib.rs
-//   ../vm_memory/src/lib.rs
-//   async-task-4.0.3 "default,std"
-//   async-trait-0.1.50
-//   autocfg-1.0.1
-//   bitflags-1.2.1 "default"
-//   futures-0.3.15 "alloc"
-//   futures-channel-0.3.15 "alloc,futures-sink,sink"
-//   futures-core-0.3.15 "alloc"
-//   futures-io-0.3.15
-//   futures-sink-0.3.15 "alloc"
-//   futures-task-0.3.15 "alloc"
-//   futures-util-0.3.15 "alloc,futures-sink,sink"
-//   intrusive-collections-0.9.1 "alloc,default"
-//   itoa-0.4.7
-//   libc-0.2.97 "default,std"
-//   memoffset-0.5.6 "default"
-//   paste-1.0.5
-//   pin-project-lite-0.2.7
-//   pin-utils-0.1.0
-//   proc-macro2-1.0.27 "default,proc-macro"
-//   quote-1.0.9 "default,proc-macro"
-//   ryu-1.0.5
-//   serde-1.0.126 "default,derive,serde_derive,std"
-//   serde_derive-1.0.126 "default"
-//   serde_json-1.0.64 "default,std"
-//   slab-0.4.3 "default,std"
-//   smallvec-1.6.1
-//   syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-//   thiserror-1.0.25
-//   thiserror-impl-1.0.25
-//   unicode-xid-0.2.2 "default"
diff --git a/vhost_user_devices/Android.bp b/vhost_user_devices/Android.bp
index c492cab..9491e84 100644
--- a/vhost_user_devices/Android.bp
+++ b/vhost_user_devices/Android.bp
@@ -1,5 +1,5 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace --verbose.
-// Manually removed unused 'vhost_user_wl_device' related rules.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
+// Do not modify this file as changes will be overridden on upgrade.
 
 package {
     // See: http://go/android-license-faq
@@ -13,22 +13,89 @@
 rust_library {
     name: "libvhost_user_devices",
     defaults: ["crosvm_defaults"],
-    // has rustc warnings
     host_supported: true,
     crate_name: "vhost_user_devices",
     srcs: ["src/lib.rs"],
     edition: "2018",
     rustlibs: [
         "libanyhow",
+        "libarch",
         "libbase_rust",
         "libcros_async",
+        "libdata_model",
         "libdevices",
+        "libdisk",
         "libfutures",
+        "libgetopts",
         "liblibc",
+        "libnet_util",
         "libonce_cell",
         "libsync_rust",
         "libsys_util",
         "libthiserror",
+        "libvirtio_sys",
+        "libvm_memory",
+        "libvmm_vhost",
+    ],
+    proc_macros: ["libremain"],
+}
+
+rust_binary {
+    name: "vhost_user_block_device",
+    defaults: ["crosvm_defaults"],
+    host_supported: true,
+    crate_name: "vhost_user_block_device",
+    srcs: ["src/block.rs"],
+    edition: "2018",
+    rustlibs: [
+        "libanyhow",
+        "libarch",
+        "libbase_rust",
+        "libcros_async",
+        "libdata_model",
+        "libdevices",
+        "libdisk",
+        "libfutures",
+        "libgetopts",
+        "liblibc",
+        "libnet_util",
+        "libonce_cell",
+        "libsync_rust",
+        "libsys_util",
+        "libthiserror",
+        "libvhost_user_devices",
+        "libvirtio_sys",
+        "libvm_memory",
+        "libvmm_vhost",
+    ],
+    proc_macros: ["libremain"],
+}
+
+rust_binary {
+    name: "vhost_user_console_device",
+    defaults: ["crosvm_defaults"],
+    host_supported: true,
+    crate_name: "vhost_user_console_device",
+    srcs: ["src/console.rs"],
+    edition: "2018",
+    rustlibs: [
+        "libanyhow",
+        "libarch",
+        "libbase_rust",
+        "libcros_async",
+        "libdata_model",
+        "libdevices",
+        "libdisk",
+        "libfutures",
+        "libgetopts",
+        "liblibc",
+        "libnet_util",
+        "libonce_cell",
+        "libsync_rust",
+        "libsys_util",
+        "libthiserror",
+        "libvhost_user_devices",
+        "libvirtio_sys",
         "libvm_memory",
         "libvmm_vhost",
     ],
@@ -36,27 +103,123 @@
 }
 
 rust_defaults {
-    name: "vhost_user_devices_defaults",
+    name: "vhost_user_devices_test_defaults",
+    defaults: ["crosvm_defaults"],
+    crate_name: "vhost_user_block_device",
+    srcs: ["src/block.rs"],
+    test_suites: ["general-tests"],
+    auto_gen_config: true,
+    edition: "2018",
+    rustlibs: [
+        "libanyhow",
+        "libarch",
+        "libbase_rust",
+        "libcros_async",
+        "libdata_model",
+        "libdevices",
+        "libdisk",
+        "libfutures",
+        "libgetopts",
+        "liblibc",
+        "libnet_util",
+        "libonce_cell",
+        "libsync_rust",
+        "libsys_util",
+        "libtempfile",
+        "libthiserror",
+        "libvhost_user_devices",
+        "libvirtio_sys",
+        "libvm_memory",
+        "libvmm_vhost",
+    ],
+    proc_macros: ["libremain"],
+}
+
+rust_test_host {
+    name: "vhost_user_devices_host_test_src_block",
+    defaults: ["vhost_user_devices_test_defaults"],
+    test_options: {
+        unit_test: true,
+    },
+}
+
+rust_test {
+    name: "vhost_user_devices_device_test_src_block",
+    defaults: ["vhost_user_devices_test_defaults"],
+}
+
+rust_defaults {
+    name: "vhost_user_devices_test_defaults_vhost_user_console_device",
+    defaults: ["crosvm_defaults"],
+    crate_name: "vhost_user_console_device",
+    srcs: ["src/console.rs"],
+    test_suites: ["general-tests"],
+    auto_gen_config: true,
+    edition: "2018",
+    rustlibs: [
+        "libanyhow",
+        "libarch",
+        "libbase_rust",
+        "libcros_async",
+        "libdata_model",
+        "libdevices",
+        "libdisk",
+        "libfutures",
+        "libgetopts",
+        "liblibc",
+        "libnet_util",
+        "libonce_cell",
+        "libsync_rust",
+        "libsys_util",
+        "libtempfile",
+        "libthiserror",
+        "libvhost_user_devices",
+        "libvirtio_sys",
+        "libvm_memory",
+        "libvmm_vhost",
+    ],
+    proc_macros: ["libremain"],
+}
+
+rust_test_host {
+    name: "vhost_user_devices_host_test_src_console",
+    defaults: ["vhost_user_devices_test_defaults_vhost_user_console_device"],
+    test_options: {
+        unit_test: true,
+    },
+}
+
+rust_test {
+    name: "vhost_user_devices_device_test_src_console",
+    defaults: ["vhost_user_devices_test_defaults_vhost_user_console_device"],
+}
+
+rust_defaults {
+    name: "vhost_user_devices_test_defaults_vhost_user_devices",
     defaults: ["crosvm_defaults"],
     crate_name: "vhost_user_devices",
-    // has rustc warnings
     srcs: ["src/lib.rs"],
     test_suites: ["general-tests"],
     auto_gen_config: true,
     edition: "2018",
     rustlibs: [
         "libanyhow",
+        "libarch",
         "libbase_rust",
         "libcros_async",
         "libdata_model",
         "libdevices",
+        "libdisk",
         "libfutures",
+        "libgetopts",
         "liblibc",
+        "libnet_util",
         "libonce_cell",
         "libsync_rust",
         "libsys_util",
         "libtempfile",
         "libthiserror",
+        "libvirtio_sys",
         "libvm_memory",
         "libvmm_vhost",
     ],
@@ -65,7 +228,7 @@
 
 rust_test_host {
     name: "vhost_user_devices_host_test_src_lib",
-    defaults: ["vhost_user_devices_defaults"],
+    defaults: ["vhost_user_devices_test_defaults_vhost_user_devices"],
     test_options: {
         unit_test: true,
     },
@@ -73,97 +236,159 @@
 
 rust_test {
     name: "vhost_user_devices_device_test_src_lib",
-    defaults: ["vhost_user_devices_defaults"],
+    defaults: ["vhost_user_devices_test_defaults_vhost_user_devices"],
 }
 
-// dependent_library ["feature_list"]
-//   ../../adhd/audio_streams/src/audio_streams.rs
-//   ../../adhd/cras/client/cras-sys/src/lib.rs
-//   ../../adhd/cras/client/libcras/src/libcras.rs
-//   ../../libchromeos-rs/src/lib.rs
-//   ../../minijail/rust/minijail-sys/lib.rs
-//   ../../minijail/rust/minijail/src/lib.rs
-//   ../../rust/vmm_vhost/src/lib.rs "default,vhost-user,vhost-user-master,vhost-user-slave"
-//   ../../vm_tools/p9/src/lib.rs
-//   ../../vm_tools/p9/wire_format_derive/wire_format_derive.rs
-//   ../acpi_tables/src/lib.rs
-//   ../assertions/src/lib.rs
-//   ../base/src/lib.rs
-//   ../bit_field/bit_field_derive/bit_field_derive.rs
-//   ../bit_field/src/lib.rs
-//   ../cros_async/src/lib.rs
-//   ../data_model/src/lib.rs
-//   ../devices/src/lib.rs
-//   ../disk/src/disk.rs
-//   ../enumn/src/lib.rs
-//   ../fuse/src/lib.rs
-//   ../hypervisor/src/lib.rs
-//   ../io_uring/src/lib.rs
-//   ../kvm/src/lib.rs
-//   ../kvm_sys/src/lib.rs
-//   ../linux_input_sys/src/lib.rs
-//   ../net_sys/src/lib.rs
-//   ../net_util/src/lib.rs
-//   ../power_monitor/src/lib.rs
-//   ../rand_ish/src/lib.rs
-//   ../resources/src/lib.rs
-//   ../rutabaga_gfx/src/lib.rs
-//   ../sync/src/lib.rs
-//   ../sys_util/poll_token_derive/poll_token_derive.rs
-//   ../sys_util/src/lib.rs
-//   ../tempfile/src/lib.rs
-//   ../usb_sys/src/lib.rs
-//   ../usb_util/src/lib.rs
-//   ../vfio_sys/src/lib.rs
-//   ../vhost/src/lib.rs
-//   ../virtio_sys/src/lib.rs
-//   ../vm_control/src/lib.rs
-//   ../vm_memory/src/lib.rs
-//   anyhow-1.0.41 "default,std"
-//   async-task-4.0.3 "default,std"
-//   async-trait-0.1.50
-//   autocfg-1.0.1
-//   bitflags-1.2.1 "default"
-//   cfg-if-1.0.0
-//   downcast-rs-1.2.0 "default,std"
-//   futures-0.3.15 "alloc,std"
-//   futures-channel-0.3.15 "alloc,futures-sink,sink,std"
-//   futures-core-0.3.15 "alloc,std"
-//   futures-io-0.3.15 "std"
-//   futures-sink-0.3.15 "alloc,std"
-//   futures-task-0.3.15 "alloc,std"
-//   futures-util-0.3.15 "alloc,channel,futures-channel,futures-io,futures-sink,io,memchr,sink,slab,std"
-//   getopts-0.2.21
-//   getrandom-0.2.3 "std"
-//   intrusive-collections-0.9.1 "alloc,default"
-//   itoa-0.4.7
-//   libc-0.2.97 "default,std"
-//   log-0.4.14
-//   memchr-2.4.0 "default,std"
-//   memoffset-0.5.6 "default"
-//   once_cell-1.8.0 "alloc,default,race,std"
-//   paste-1.0.5
-//   pin-project-lite-0.2.7
-//   pin-utils-0.1.0
-//   pkg-config-0.3.19
-//   ppv-lite86-0.2.10 "simd,std"
-//   proc-macro2-1.0.27 "default,proc-macro"
-//   protobuf-2.24.1
-//   quote-1.0.9 "default,proc-macro"
-//   rand-0.8.4 "alloc,default,getrandom,libc,rand_chacha,rand_hc,std,std_rng"
-//   rand_chacha-0.3.1 "std"
-//   rand_core-0.6.3 "alloc,getrandom,std"
-//   remain-0.2.2
-//   remove_dir_all-0.5.3
-//   ryu-1.0.5
-//   serde-1.0.126 "default,derive,serde_derive,std"
-//   serde_derive-1.0.126 "default"
-//   serde_json-1.0.64 "default,std"
-//   slab-0.4.3 "default,std"
-//   smallvec-1.6.1
-//   syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-//   tempfile-3.2.0
-//   thiserror-1.0.25
-//   thiserror-impl-1.0.25
-//   unicode-width-0.1.8 "default"
-//   unicode-xid-0.2.2 "default"
+rust_defaults {
+    name: "vhost_user_devices_test_defaults_vhost_user_net_device",
+    defaults: ["crosvm_defaults"],
+    crate_name: "vhost_user_net_device",
+    srcs: ["src/net.rs"],
+    test_suites: ["general-tests"],
+    auto_gen_config: true,
+    edition: "2018",
+    rustlibs: [
+        "libanyhow",
+        "libarch",
+        "libbase_rust",
+        "libcros_async",
+        "libdata_model",
+        "libdevices",
+        "libdisk",
+        "libfutures",
+        "libgetopts",
+        "liblibc",
+        "libnet_util",
+        "libonce_cell",
+        "libsync_rust",
+        "libsys_util",
+        "libtempfile",
+        "libthiserror",
+        "libvhost_user_devices",
+        "libvirtio_sys",
+        "libvm_memory",
+        "libvmm_vhost",
+    ],
+    proc_macros: ["libremain"],
+}
+
+rust_test_host {
+    name: "vhost_user_devices_host_test_src_net",
+    defaults: ["vhost_user_devices_test_defaults_vhost_user_net_device"],
+    test_options: {
+        unit_test: true,
+    },
+}
+
+rust_test {
+    name: "vhost_user_devices_device_test_src_net",
+    defaults: ["vhost_user_devices_test_defaults_vhost_user_net_device"],
+}
+
+rust_defaults {
+    name: "vhost_user_devices_test_defaults_vhost_user_wl_device",
+    defaults: ["crosvm_defaults"],
+    crate_name: "vhost_user_wl_device",
+    srcs: ["src/wl.rs"],
+    test_suites: ["general-tests"],
+    auto_gen_config: true,
+    edition: "2018",
+    rustlibs: [
+        "libanyhow",
+        "libarch",
+        "libbase_rust",
+        "libcros_async",
+        "libdata_model",
+        "libdevices",
+        "libdisk",
+        "libfutures",
+        "libgetopts",
+        "liblibc",
+        "libnet_util",
+        "libonce_cell",
+        "libsync_rust",
+        "libsys_util",
+        "libtempfile",
+        "libthiserror",
+        "libvhost_user_devices",
+        "libvirtio_sys",
+        "libvm_memory",
+        "libvmm_vhost",
+    ],
+    proc_macros: ["libremain"],
+}
+
+rust_test_host {
+    name: "vhost_user_devices_host_test_src_wl",
+    defaults: ["vhost_user_devices_test_defaults_vhost_user_wl_device"],
+    test_options: {
+        unit_test: true,
+    },
+}
+
+rust_test {
+    name: "vhost_user_devices_device_test_src_wl",
+    defaults: ["vhost_user_devices_test_defaults_vhost_user_wl_device"],
+}
+
+rust_binary {
+    name: "vhost_user_net_device",
+    defaults: ["crosvm_defaults"],
+    host_supported: true,
+    crate_name: "vhost_user_net_device",
+    srcs: ["src/net.rs"],
+    edition: "2018",
+    rustlibs: [
+        "libanyhow",
+        "libarch",
+        "libbase_rust",
+        "libcros_async",
+        "libdata_model",
+        "libdevices",
+        "libdisk",
+        "libfutures",
+        "libgetopts",
+        "liblibc",
+        "libnet_util",
+        "libonce_cell",
+        "libsync_rust",
+        "libsys_util",
+        "libthiserror",
+        "libvhost_user_devices",
+        "libvirtio_sys",
+        "libvm_memory",
+        "libvmm_vhost",
+    ],
+    proc_macros: ["libremain"],
+}
+
+rust_binary {
+    name: "vhost_user_wl_device",
+    defaults: ["crosvm_defaults"],
+    host_supported: true,
+    crate_name: "vhost_user_wl_device",
+    srcs: ["src/wl.rs"],
+    edition: "2018",
+    rustlibs: [
+        "libanyhow",
+        "libarch",
+        "libbase_rust",
+        "libcros_async",
+        "libdata_model",
+        "libdevices",
+        "libdisk",
+        "libfutures",
+        "libgetopts",
+        "liblibc",
+        "libnet_util",
+        "libonce_cell",
+        "libsync_rust",
+        "libsys_util",
+        "libthiserror",
+        "libvhost_user_devices",
+        "libvirtio_sys",
+        "libvm_memory",
+        "libvmm_vhost",
+    ],
+    proc_macros: ["libremain"],
+}
diff --git a/vhost_user_devices/Cargo.toml b/vhost_user_devices/Cargo.toml
index b3ecc71..4c6c4a8 100644
--- a/vhost_user_devices/Cargo.toml
+++ b/vhost_user_devices/Cargo.toml
@@ -4,18 +4,16 @@
 authors = ["The Chromium OS Authors"]
 edition = "2018"
 
-[features]
-net = ["data_model", "net_util", "virtio_sys"]
-wl = ["devices/minigbm"]
-block = ["data_model", "disk"]
-
 [lib]
 path = "src/lib.rs"
 
 [[bin]]
+name = "vhost-user-console-device"
+path = "src/console.rs"
+
+[[bin]]
 name = "vhost-user-net-device"
 path = "src/net.rs"
-required-features = ["net"]
 
 [[bin]]
 name = "vhost-user-wl-device"
@@ -24,24 +22,24 @@
 [[bin]]
 name = "vhost-user-block-device"
 path = "src/block.rs"
-required-features = ["block"]
 
 [dependencies]
 anyhow = "*"
+arch = { path = "../arch" }
 base = { path = "../base" }
 cros_async = { path = "../cros_async" }
-data_model = { path = "../data_model", optional = true }
-devices = { path = "../devices" }
-disk = { path = "../disk", optional = true}
+data_model = { path = "../data_model" }
+devices = { path = "../devices", features = ["minigbm"] }
+disk = { path = "../disk" }
 getopts = { version = "0.2" }
 libc = "*"
-net_util = { path = "../net_util", optional = true }
+net_util = { path = "../net_util" }
 once_cell = "1.7.2"
 remain = "*"
 sync = { path = "../sync" }
 sys_util = { path = "../sys_util" }
 thiserror = "*"
-virtio_sys = { path = "../virtio_sys", optional = true }
+virtio_sys = { path = "../virtio_sys" }
 vm_memory = { path = "../vm_memory" }
 vmm_vhost = { path = "../../rust/vmm_vhost", version = "*", features = ["vhost-user-slave"] }
 
diff --git a/vhost_user_devices/src/block.rs b/vhost_user_devices/src/block.rs
index 745931d..a732907 100644
--- a/vhost_user_devices/src/block.rs
+++ b/vhost_user_devices/src/block.rs
@@ -192,7 +192,7 @@
     fn start_queue(
         &mut self,
         idx: usize,
-        queue: virtio::Queue,
+        mut queue: virtio::Queue,
         mem: GuestMemory,
         call_evt: Arc<Mutex<CallEvent>>,
         kick_evt: Event,
@@ -202,6 +202,9 @@
             handle.abort();
         }
 
+        // Enable any virtqueue features that were negotiated (like VIRTIO_RING_F_EVENT_IDX).
+        queue.ack_features(self.acked_features);
+
         // Safe because the executor is initialized in main() below.
         let ex = BLOCK_EXECUTOR.get().expect("Executor not initialized");
 
diff --git a/vhost_user_devices/src/console.rs b/vhost_user_devices/src/console.rs
new file mode 100644
index 0000000..4275955
--- /dev/null
+++ b/vhost_user_devices/src/console.rs
@@ -0,0 +1,348 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::io::{self, stdin};
+use std::path::PathBuf;
+use std::sync::mpsc::Receiver;
+use std::sync::Arc;
+
+use devices::virtio::copy_config;
+
+use anyhow::{anyhow, bail, Context};
+use arch::serial::{SerialHardware, SerialParameters, SerialType};
+use base::{error, warn, Event, RawDescriptor, Terminal};
+use cros_async::{EventAsync, Executor};
+use data_model::DataInit;
+use devices::serial_device::SerialDevice;
+use devices::virtio;
+use devices::virtio::console::{
+    handle_input, process_transmit_queue, spawn_input_thread, virtio_console_config, ConsoleError,
+};
+use devices::ProtectionType;
+use futures::future::{AbortHandle, Abortable};
+use getopts::Options;
+use once_cell::sync::OnceCell;
+use sync::Mutex;
+use vhost_user_devices::{CallEvent, DeviceRequestHandler, VhostUserBackend};
+use vm_memory::GuestMemory;
+use vmm_vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
+
+static CONSOLE_EXECUTOR: OnceCell<Executor> = OnceCell::new();
+
+async fn run_tx_queue(
+    mut queue: virtio::Queue,
+    mem: GuestMemory,
+    call_evt: Arc<Mutex<CallEvent>>,
+    kick_evt: EventAsync,
+    mut output: Box<dyn io::Write>,
+) {
+    loop {
+        if let Err(e) = kick_evt.next_val().await {
+            error!("Failed to read kick event for tx queue: {}", e);
+            break;
+        }
+        process_transmit_queue(&mem, &call_evt, &mut queue, &mut output);
+    }
+}
+
+async fn run_rx_queue(
+    mut queue: virtio::Queue,
+    mem: GuestMemory,
+    call_evt: Arc<Mutex<CallEvent>>,
+    kick_evt: EventAsync,
+    in_channel: Receiver<Vec<u8>>,
+    in_avail_evt: EventAsync,
+) {
+    loop {
+        if let Err(e) = in_avail_evt.next_val().await {
+            error!("Failed reading in_avail_evt: {}", e);
+            break;
+        }
+        match handle_input(&mem, &call_evt, &in_channel, &mut queue) {
+            Ok(()) => {}
+            Err(ConsoleError::RxDescriptorsExhausted) => {
+                if let Err(e) = kick_evt.next_val().await {
+                    error!("Failed to read kick event for rx queue: {}", e);
+                    break;
+                }
+            }
+            Err(e) => {
+                error!("Failed to process rx queue: {}", e);
+                break;
+            }
+        }
+    }
+}
+
+struct ConsoleBackend {
+    input: Option<Box<dyn io::Read + Send>>,
+    output: Option<Box<dyn io::Write + Send>>,
+    avail_features: u64,
+    acked_features: u64,
+    acked_protocol_features: VhostUserProtocolFeatures,
+    workers: [Option<AbortHandle>; Self::MAX_QUEUE_NUM],
+}
+
+impl SerialDevice for ConsoleBackend {
+    fn new(
+        protected_vm: ProtectionType,
+        _evt: Event,
+        input: Option<Box<dyn io::Read + Send>>,
+        output: Option<Box<dyn io::Write + Send>>,
+        _keep_rds: Vec<RawDescriptor>,
+    ) -> ConsoleBackend {
+        let avail_features = 1u64 << crate::virtio::VIRTIO_F_VERSION_1
+            | virtio::base_features(protected_vm)
+            | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
+        ConsoleBackend {
+            input,
+            output,
+            avail_features,
+            acked_features: 0,
+            acked_protocol_features: VhostUserProtocolFeatures::empty(),
+            workers: Default::default(),
+        }
+    }
+}
+
+impl VhostUserBackend for ConsoleBackend {
+    const MAX_QUEUE_NUM: usize = 2; /* transmit and receive queues */
+    const MAX_VRING_LEN: u16 = 256;
+
+    type Error = anyhow::Error;
+
+    fn features(&self) -> u64 {
+        self.avail_features
+    }
+
+    fn ack_features(&mut self, value: u64) -> anyhow::Result<()> {
+        let unrequested_features = value & !self.avail_features;
+        if unrequested_features != 0 {
+            bail!("invalid features are given: {:#x}", unrequested_features);
+        }
+
+        self.acked_features |= value;
+
+        Ok(())
+    }
+
+    fn acked_features(&self) -> u64 {
+        self.acked_features
+    }
+
+    fn protocol_features(&self) -> VhostUserProtocolFeatures {
+        VhostUserProtocolFeatures::CONFIG
+    }
+
+    fn ack_protocol_features(&mut self, features: u64) -> anyhow::Result<()> {
+        let features = VhostUserProtocolFeatures::from_bits(features)
+            .ok_or_else(|| anyhow!("invalid protocol features are given: {:#x}", features))?;
+        let supported = self.protocol_features();
+        self.acked_protocol_features = features & supported;
+        Ok(())
+    }
+
+    fn acked_protocol_features(&self) -> u64 {
+        self.acked_protocol_features.bits()
+    }
+
+    fn read_config(&self, offset: u64, data: &mut [u8]) {
+        let config = virtio_console_config {
+            max_nr_ports: 1.into(),
+            ..Default::default()
+        };
+        copy_config(data, 0, config.as_slice(), offset);
+    }
+
+    fn reset(&mut self) {
+        for handle in self.workers.iter_mut().filter_map(Option::take) {
+            handle.abort();
+        }
+    }
+
+    fn start_queue(
+        &mut self,
+        idx: usize,
+        mut queue: virtio::Queue,
+        mem: GuestMemory,
+        call_evt: Arc<Mutex<CallEvent>>,
+        kick_evt: Event,
+    ) -> anyhow::Result<()> {
+        if let Some(handle) = self.workers.get_mut(idx).and_then(Option::take) {
+            warn!("Starting new queue handler without stopping old handler");
+            handle.abort();
+        }
+
+        // Enable any virtqueue features that were negotiated (like VIRTIO_RING_F_EVENT_IDX).
+        queue.ack_features(self.acked_features);
+
+        // Safe because the executor is initialized in main() below.
+        let ex = CONSOLE_EXECUTOR.get().expect("Executor not initialized.");
+
+        let kick_evt =
+            EventAsync::new(kick_evt.0, ex).context("Failed to create EventAsync for kick_evt")?;
+        let (handle, registration) = AbortHandle::new_pair();
+        match idx {
+            // ReceiveQueue
+            0 => {
+                // See explanation in devices/src/virtio/console.rs
+                // We need a multithreaded input polling because io::Read only provides
+                // a blocking interface which we cannot use in an async function.
+                let in_avail_evt = match Event::new() {
+                    Ok(evt) => evt,
+                    Err(e) => {
+                        bail!("Failed creating Event: {}", e);
+                    }
+                };
+
+                let input_unpacked = self
+                    .input
+                    .take()
+                    .ok_or_else(|| anyhow!("input source unavailable"))?;
+                let in_channel = spawn_input_thread(input_unpacked, &in_avail_evt)
+                    .take()
+                    .ok_or_else(|| anyhow!("input channel unavailable"))?;
+
+                // Create the async 'in' event so we can await on it.
+                let in_avail_async_evt = EventAsync::new(in_avail_evt.0, ex)
+                    .context("Failed to create EventAsync for in_avail_evt")?;
+
+                ex.spawn_local(Abortable::new(
+                    run_rx_queue(
+                        queue,
+                        mem,
+                        call_evt,
+                        kick_evt,
+                        in_channel,
+                        in_avail_async_evt,
+                    ),
+                    registration,
+                ))
+                .detach();
+            }
+            // TransmitQueue
+            1 => {
+                // Take ownership of output writer.
+                // Safe because output should always be initialized to something
+                let output_unwrapped: Box<dyn io::Write + Send> = self
+                    .output
+                    .take()
+                    .ok_or_else(|| anyhow!("no output available"))?;
+                ex.spawn_local(Abortable::new(
+                    run_tx_queue(queue, mem, call_evt, kick_evt, output_unwrapped),
+                    registration,
+                ))
+                .detach();
+            }
+            _ => bail!("attempted to start unknown queue: {}", idx),
+        }
+
+        self.workers[idx] = Some(handle);
+        Ok(())
+    }
+
+    fn stop_queue(&mut self, idx: usize) {
+        if let Some(handle) = self.workers.get_mut(idx).and_then(Option::take) {
+            handle.abort();
+        }
+    }
+}
+
+fn run_console(params: &SerialParameters, socket: &String) -> anyhow::Result<()> {
+    // We need to pass an event as per Serial Device API but we don't really use it anyway.
+    let evt = Event::new()?;
+    // Same for keep_rds, we don't really use this.
+    let mut keep_rds = Vec::new();
+    let console = match params.create_serial_device::<ConsoleBackend>(
+        ProtectionType::Unprotected,
+        &evt,
+        &mut keep_rds,
+    ) {
+        Ok(c) => c,
+        Err(e) => bail!(e),
+    };
+
+    let handler = DeviceRequestHandler::new(console);
+    let ex = Executor::new().context("Failed to create executor")?;
+
+    let _ = CONSOLE_EXECUTOR.set(ex.clone());
+
+    if let Err(e) = ex.run_until(handler.run(socket, &ex)) {
+        bail!(e);
+    }
+    Ok(())
+}
+
+fn main() -> anyhow::Result<()> {
+    let mut args = std::env::args();
+    let mut opts = Options::new();
+    opts.optflag("h", "help", "print this help menu");
+    opts.optopt("", "socket", "path to a socket", "PATH");
+    opts.optopt("", "output-file", "path to a file", "OUTFILE");
+    opts.optopt("", "input-file", "path to a file", "INFILE");
+
+    let program_name = args.next().expect("empty args");
+
+    let matches = match opts.parse(args) {
+        Ok(m) => m,
+        Err(e) => {
+            eprintln!("{}", e);
+            eprintln!("{}", opts.short_usage(&program_name));
+            return Ok(());
+        }
+    };
+
+    if matches.opt_present("h") {
+        println!("{}", opts.usage(&program_name));
+        return Ok(());
+    }
+
+    if !matches.opt_present("socket") {
+        println!("Must specify the socket for the vhost user device.");
+        println!("{}", opts.usage(&program_name));
+        return Ok(());
+    }
+
+    // We can unwrap after `opt_str()` safely because we just checked for it being present.
+    let socket = matches.opt_str("socket").unwrap();
+
+    let output_file = matches.opt_str("output-file").map(PathBuf::from);
+    let input_file = matches.opt_str("input-file").map(PathBuf::from);
+
+    base::syslog::init().context("Failed to initialize syslog")?;
+
+    // Set stdin() in raw mode so we can send over individual keystrokes unbuffered
+    stdin()
+        .set_raw_mode()
+        .context("Failed to set terminal raw mode")?;
+
+    let type_ = match output_file {
+        Some(_) => SerialType::File,
+        None => SerialType::Stdout,
+    };
+
+    let params = SerialParameters {
+        type_,
+        hardware: SerialHardware::VirtioConsole,
+        // Required only if type_ is SerialType::File or SerialType::UnixSocket
+        path: output_file,
+        input: input_file,
+        num: 1,
+        console: true,
+        earlycon: false,
+        // We do not support stdin-less mode
+        stdin: true,
+    };
+
+    if let Err(e) = run_console(&params, &socket) {
+        error!("Failed to run console device: {}", e);
+    }
+
+    // Restore terminal capabilities back to what they were before
+    stdin()
+        .set_canon_mode()
+        .context("Failed to restore canonical mode for terminal")?;
+
+    Ok(())
+}
diff --git a/vhost_user_devices/src/lib.rs b/vhost_user_devices/src/lib.rs
index 831112f..60eb6d6 100644
--- a/vhost_user_devices/src/lib.rs
+++ b/vhost_user_devices/src/lib.rs
@@ -46,12 +46,16 @@
 // implementation (this is what our devices implement).
 
 use std::convert::TryFrom;
+use std::fs::File;
 use std::num::Wrapping;
-use std::os::unix::io::{AsRawFd, RawFd};
+use std::os::unix::io::AsRawFd;
 use std::path::Path;
 use std::sync::Arc;
 
-use base::{error, Event, FromRawDescriptor, SafeDescriptor, SharedMemory, SharedMemoryUnix};
+use base::{
+    error, Event, FromRawDescriptor, IntoRawDescriptor, SafeDescriptor, SharedMemory,
+    SharedMemoryUnix,
+};
 use cros_async::{AsyncError, AsyncWrapper, Executor};
 use devices::virtio::{Queue, SignalableInterrupt};
 use remain::sorted;
@@ -60,12 +64,12 @@
 use thiserror::Error as ThisError;
 use vm_memory::{GuestAddress, GuestMemory, MemoryRegion};
 use vmm_vhost::vhost_user::message::{
-    VhostUserConfigFlags, VhostUserMemoryRegion, VhostUserProtocolFeatures,
+    VhostUserConfigFlags, VhostUserInflight, VhostUserMemoryRegion, VhostUserProtocolFeatures,
     VhostUserSingleMemoryRegion, VhostUserVirtioFeatures, VhostUserVringAddrFlags,
     VhostUserVringState,
 };
 use vmm_vhost::vhost_user::{
-    Error as VhostError, Listener, Result as VhostResult, SlaveFsCacheReq, SlaveListener,
+    Error as VhostError, Listener, Result as VhostResult, SlaveListener,
     VhostUserSlaveReqHandlerMut,
 };
 
@@ -202,15 +206,9 @@
     /// Failed to handle a vhost-user request.
     #[error("failed to handle a vhost-user request: {0}")]
     HandleVhostUserRequest(VhostError),
-    /// Invalid queue index is given.
-    #[error("invalid queue index is given: {index}")]
-    InvalidQueueIndex { index: usize },
     /// Failed to wait for the handler socket to become readable.
     #[error("failed to wait for the handler socket to become readable: {0}")]
     WaitForHandler(AsyncError),
-    /// Failed to wait for the listener socket to become readable.
-    #[error("failed to wait for the listener socket to become readable: {0}")]
-    WaitForListener(AsyncError),
 }
 
 type HandlerResult<T> = std::result::Result<T, HandlerError>;
@@ -343,26 +341,19 @@
     fn set_mem_table(
         &mut self,
         contexts: &[VhostUserMemoryRegion],
-        fds: &[RawFd],
+        files: Vec<File>,
     ) -> VhostResult<()> {
-        if fds.len() != contexts.len() {
+        if files.len() != contexts.len() {
             return Err(VhostError::InvalidParam);
         }
 
-        let mut regions = Vec::with_capacity(fds.len());
-        for (region, &fd) in contexts.iter().zip(fds.iter()) {
-            let rd = base::validate_raw_descriptor(fd).map_err(|e| {
-                error!("invalid fd is given: {}", e);
-                VhostError::InvalidParam
-            })?;
-            // Safe because we verified that we are the unique owner of `rd`.
-            let sd = unsafe { SafeDescriptor::from_raw_descriptor(rd) };
-
+        let mut regions = Vec::with_capacity(files.len());
+        for (region, file) in contexts.iter().zip(files.into_iter()) {
             let region = MemoryRegion::new(
                 region.memory_size,
                 GuestAddress(region.guest_phys_addr),
                 region.mmap_offset,
-                Arc::new(SharedMemory::from_safe_descriptor(sd).unwrap()),
+                Arc::new(SharedMemory::from_safe_descriptor(SafeDescriptor::from(file)).unwrap()),
             )
             .map_err(|e| {
                 error!("failed to create a memory region: {}", e);
@@ -457,7 +448,7 @@
         ))
     }
 
-    fn set_vring_kick(&mut self, index: u8, fd: Option<RawFd>) -> VhostResult<()> {
+    fn set_vring_kick(&mut self, index: u8, file: Option<File>) -> VhostResult<()> {
         if index as usize >= self.vrings.len() {
             return Err(VhostError::InvalidParam);
         }
@@ -468,24 +459,16 @@
             return Err(VhostError::InvalidOperation);
         }
 
-        if let Some(fd) = fd {
-            // TODO(b/186625058): The current code returns an error when `FD_CLOEXEC` is already
-            // set, which is not harmful. Once we update the vhost crate's API to pass around `File`
-            // instead of `RawFd`, we won't need this validation.
-            let rd = base::validate_raw_descriptor(fd).map_err(|e| {
-                error!("invalid fd is given: {}", e);
-                VhostError::InvalidParam
-            })?;
-
+        if let Some(file) = file {
             // Remove O_NONBLOCK from kick_fd. Otherwise, uring_executor will fails when we read
             // values via `next_val()` later.
-            if let Err(e) = clear_fd_flags(rd, libc::O_NONBLOCK) {
+            if let Err(e) = clear_fd_flags(file.as_raw_fd(), libc::O_NONBLOCK) {
                 error!("failed to remove O_NONBLOCK for kick fd: {}", e);
                 return Err(VhostError::InvalidParam);
             }
 
-            // Safe because the FD is now owned.
-            let kick_evt = unsafe { Event::from_raw_descriptor(rd) };
+            // Safe because we own the file.
+            let kick_evt = unsafe { Event::from_raw_descriptor(file.into_raw_descriptor()) };
 
             let vring = &mut self.vrings[index as usize];
             vring.queue.ready = true;
@@ -515,18 +498,15 @@
         Ok(())
     }
 
-    fn set_vring_call(&mut self, index: u8, fd: Option<RawFd>) -> VhostResult<()> {
+    fn set_vring_call(&mut self, index: u8, file: Option<File>) -> VhostResult<()> {
         if index as usize >= self.vrings.len() {
             return Err(VhostError::InvalidParam);
         }
 
-        if let Some(fd) = fd {
-            let rd = base::validate_raw_descriptor(fd).map_err(|e| {
-                error!("invalid fd is given: {}", e);
-                VhostError::InvalidParam
-            })?;
-            // Safe because the FD is now owned.
-            let call_evt = CallEvent(unsafe { Event::from_raw_descriptor(rd) });
+        if let Some(file) = file {
+            // Safe because we own the file.
+            let call_evt =
+                CallEvent(unsafe { Event::from_raw_descriptor(file.into_raw_descriptor()) });
             match &self.vrings[index as usize].call_evt {
                 None => {
                     self.vrings[index as usize].call_evt = Some(Arc::new(Mutex::new(call_evt)));
@@ -541,7 +521,7 @@
         Ok(())
     }
 
-    fn set_vring_err(&mut self, _index: u8, _fd: Option<RawFd>) -> VhostResult<()> {
+    fn set_vring_err(&mut self, _index: u8, _fd: Option<File>) -> VhostResult<()> {
         // TODO
         Ok(())
     }
@@ -591,10 +571,21 @@
         Ok(())
     }
 
-    fn set_slave_req_fd(&mut self, _vu_req: SlaveFsCacheReq) {
+    fn set_slave_req_fd(&mut self, _vu_req: File) {
         // TODO
     }
 
+    fn get_inflight_fd(
+        &mut self,
+        _inflight: &VhostUserInflight,
+    ) -> VhostResult<(VhostUserInflight, File)> {
+        unimplemented!("get_inflight_fd");
+    }
+
+    fn set_inflight_fd(&mut self, _inflight: &VhostUserInflight, _file: File) -> VhostResult<()> {
+        unimplemented!("set_inflight_fd");
+    }
+
     fn get_max_mem_slots(&mut self) -> VhostResult<u64> {
         //TODO
         Ok(0)
@@ -603,7 +594,7 @@
     fn add_mem_region(
         &mut self,
         _region: &VhostUserSingleMemoryRegion,
-        _fd: RawFd,
+        _fd: File,
     ) -> VhostResult<()> {
         //TODO
         Ok(())
diff --git a/vhost_user_devices/src/net.rs b/vhost_user_devices/src/net.rs
index 3ebb450..34d585b 100644
--- a/vhost_user_devices/src/net.rs
+++ b/vhost_user_devices/src/net.rs
@@ -262,7 +262,7 @@
     fn start_queue(
         &mut self,
         idx: usize,
-        queue: virtio::Queue,
+        mut queue: virtio::Queue,
         mem: GuestMemory,
         call_evt: Arc<Mutex<CallEvent>>,
         kick_evt: Event,
@@ -272,6 +272,9 @@
             handle.abort();
         }
 
+        // Enable any virtqueue features that were negotiated (like VIRTIO_RING_F_EVENT_IDX).
+        queue.ack_features(self.acked_features);
+
         NET_EXECUTOR.with(|ex| {
             // Safe because the executor is initialized in main() below.
             let ex = ex.get().expect("Executor not initialized");
diff --git a/vhost_user_devices/src/wl.rs b/vhost_user_devices/src/wl.rs
index 511daee..bc2494d 100644
--- a/vhost_user_devices/src/wl.rs
+++ b/vhost_user_devices/src/wl.rs
@@ -79,6 +79,7 @@
     use_transition_flags: bool,
     use_send_vfd_v2: bool,
     features: u64,
+    acked_features: u64,
     wlstate: Option<Rc<RefCell<wl::WlState>>>,
     workers: [Option<AbortHandle>; Self::MAX_QUEUE_NUM],
 }
@@ -100,6 +101,7 @@
             use_transition_flags: false,
             use_send_vfd_v2: false,
             features,
+            acked_features: 0,
             wlstate: None,
             workers: Default::default(),
         }
@@ -117,17 +119,25 @@
     }
 
     fn ack_features(&mut self, value: u64) -> anyhow::Result<()> {
+        let unrequested_features = value & !self.features();
+        if unrequested_features != 0 {
+            bail!("invalid features are given: {:#x}", unrequested_features);
+        }
+
+        self.acked_features |= value;
+
         if value & (1 << wl::VIRTIO_WL_F_TRANS_FLAGS) != 0 {
             self.use_transition_flags = true;
         }
         if value & (1 << wl::VIRTIO_WL_F_SEND_FENCES) != 0 {
             self.use_send_vfd_v2 = true;
         }
+
         Ok(())
     }
 
     fn acked_features(&self) -> u64 {
-        self.features
+        self.acked_features
     }
 
     fn protocol_features(&self) -> VhostUserProtocolFeatures {
@@ -151,7 +161,7 @@
     fn start_queue(
         &mut self,
         idx: usize,
-        queue: Queue,
+        mut queue: Queue,
         mem: GuestMemory,
         call_evt: Arc<Mutex<CallEvent>>,
         kick_evt: Event,
@@ -161,6 +171,9 @@
             handle.abort();
         }
 
+        // Enable any virtqueue features that were negotiated (like VIRTIO_RING_F_EVENT_IDX).
+        queue.ack_features(self.acked_features);
+
         // Safe because the executor is initialized in main() below.
         let ex = WL_EXECUTOR.get().expect("Executor not initialized");
 
diff --git a/virtio_sys/Android.bp b/virtio_sys/Android.bp
index d564dfc..d82392d 100644
--- a/virtio_sys/Android.bp
+++ b/virtio_sys/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
 // Do not modify this file as changes will be overridden on upgrade.
 
 package {
@@ -13,6 +13,7 @@
 rust_library {
     name: "libvirtio_sys",
     defaults: ["crosvm_defaults"],
+    // has rustc warnings
     host_supported: true,
     crate_name: "virtio_sys",
     srcs: ["src/lib.rs"],
@@ -23,9 +24,10 @@
 }
 
 rust_defaults {
-    name: "virtio_sys_defaults",
+    name: "virtio_sys_test_defaults",
     defaults: ["crosvm_defaults"],
     crate_name: "virtio_sys",
+    // has rustc warnings
     srcs: ["src/lib.rs"],
     test_suites: ["general-tests"],
     auto_gen_config: true,
@@ -37,7 +39,7 @@
 
 rust_test_host {
     name: "virtio_sys_host_test_src_lib",
-    defaults: ["virtio_sys_defaults"],
+    defaults: ["virtio_sys_test_defaults"],
     test_options: {
         unit_test: true,
     },
@@ -45,45 +47,5 @@
 
 rust_test {
     name: "virtio_sys_device_test_src_lib",
-    defaults: ["virtio_sys_defaults"],
+    defaults: ["virtio_sys_test_defaults"],
 }
-
-// dependent_library ["feature_list"]
-//   ../assertions/src/lib.rs
-//   ../base/src/lib.rs
-//   ../cros_async/src/lib.rs
-//   ../data_model/src/lib.rs
-//   ../io_uring/src/lib.rs
-//   ../sync/src/lib.rs
-//   ../sys_util/poll_token_derive/poll_token_derive.rs
-//   ../sys_util/src/lib.rs
-//   ../tempfile/src/lib.rs
-//   async-task-4.0.3 "default,std"
-//   async-trait-0.1.50
-//   autocfg-1.0.1
-//   futures-0.3.15 "alloc"
-//   futures-channel-0.3.15 "alloc,futures-sink,sink"
-//   futures-core-0.3.15 "alloc"
-//   futures-io-0.3.15
-//   futures-sink-0.3.15 "alloc"
-//   futures-task-0.3.15 "alloc"
-//   futures-util-0.3.15 "alloc,futures-sink,sink"
-//   intrusive-collections-0.9.1 "alloc,default"
-//   itoa-0.4.7
-//   libc-0.2.97 "default,std"
-//   memoffset-0.5.6 "default"
-//   paste-1.0.5
-//   pin-project-lite-0.2.7
-//   pin-utils-0.1.0
-//   proc-macro2-1.0.27 "default,proc-macro"
-//   quote-1.0.9 "default,proc-macro"
-//   ryu-1.0.5
-//   serde-1.0.126 "default,derive,serde_derive,std"
-//   serde_derive-1.0.126 "default"
-//   serde_json-1.0.64 "default,std"
-//   slab-0.4.3 "default,std"
-//   smallvec-1.6.1
-//   syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-//   thiserror-1.0.25
-//   thiserror-impl-1.0.25
-//   unicode-xid-0.2.2 "default"
diff --git a/vm_control/Android.bp b/vm_control/Android.bp
index c0250be..dbc326e 100644
--- a/vm_control/Android.bp
+++ b/vm_control/Android.bp
@@ -1,5 +1,5 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
-// NOTE: The --features=gdb should be applied only to the host (not the device) and there are inline changes to achieve this
+// This file is generated by cargo2android.py --config cargo2android.json.
+// Do not modify this file as changes will be overridden on upgrade.
 
 package {
     // See: http://go/android-license-faq
@@ -17,17 +17,6 @@
     crate_name: "vm_control",
     srcs: ["src/lib.rs"],
     edition: "2018",
-    target: {
-        linux_glibc_x86_64: {
-            features: [
-                "gdb",
-                "gdbstub",
-            ],
-            rustlibs: [
-                "libgdbstub",
-            ],
-        },
-    },
     rustlibs: [
         "libbase_rust",
         "libdata_model",
@@ -40,27 +29,26 @@
         "libsync_rust",
         "libvm_memory",
     ],
+    target: {
+        linux_glibc_x86_64: {
+            features: [
+                "gdb",
+            ],
+            rustlibs: [
+                "libgdbstub_arch",
+            ],
+        },
+    },
 }
 
 rust_defaults {
-    name: "vm_control_defaults",
+    name: "vm_control_test_defaults",
     defaults: ["crosvm_defaults"],
     crate_name: "vm_control",
     srcs: ["src/lib.rs"],
     test_suites: ["general-tests"],
     auto_gen_config: true,
     edition: "2018",
-    target: {
-        linux_glibc_x86_64: {
-            features: [
-                "gdb",
-                "gdbstub",
-            ],
-            rustlibs: [
-                "libgdbstub",
-            ],
-        },
-    },
     rustlibs: [
         "libbase_rust",
         "libdata_model",
@@ -77,64 +65,33 @@
 
 rust_test_host {
     name: "vm_control_host_test_src_lib",
-    defaults: ["vm_control_defaults"],
+    defaults: ["vm_control_test_defaults"],
     test_options: {
         unit_test: true,
     },
+    target: {
+        linux_glibc_x86_64: {
+            features: [
+                "gdb",
+            ],
+            rustlibs: [
+                "libgdbstub_arch",
+            ],
+        },
+    },
 }
 
 rust_test {
     name: "vm_control_device_test_src_lib",
-    defaults: ["vm_control_defaults"],
+    defaults: ["vm_control_test_defaults"],
+    target: {
+        linux_glibc_x86_64: {
+            features: [
+                "gdb",
+            ],
+            rustlibs: [
+                "libgdbstub_arch",
+            ],
+        },
+    },
 }
-
-// dependent_library ["feature_list"]
-//   ../assertions/src/lib.rs
-//   ../base/src/lib.rs
-//   ../bit_field/bit_field_derive/bit_field_derive.rs
-//   ../bit_field/src/lib.rs
-//   ../cros_async/src/lib.rs
-//   ../data_model/src/lib.rs
-//   ../enumn/src/lib.rs
-//   ../hypervisor/src/lib.rs
-//   ../io_uring/src/lib.rs
-//   ../kvm/src/lib.rs
-//   ../kvm_sys/src/lib.rs
-//   ../resources/src/lib.rs
-//   ../rutabaga_gfx/src/lib.rs
-//   ../sync/src/lib.rs
-//   ../sys_util/poll_token_derive/poll_token_derive.rs
-//   ../sys_util/src/lib.rs
-//   ../tempfile/src/lib.rs
-//   ../vm_memory/src/lib.rs
-//   async-task-4.0.3 "default,std"
-//   async-trait-0.1.50
-//   autocfg-1.0.1
-//   bitflags-1.2.1 "default"
-//   downcast-rs-1.2.0 "default,std"
-//   futures-0.3.15 "alloc"
-//   futures-channel-0.3.15 "alloc,futures-sink,sink"
-//   futures-core-0.3.15 "alloc"
-//   futures-io-0.3.15
-//   futures-sink-0.3.15 "alloc"
-//   futures-task-0.3.15 "alloc"
-//   futures-util-0.3.15 "alloc,futures-sink,sink"
-//   intrusive-collections-0.9.1 "alloc,default"
-//   itoa-0.4.7
-//   libc-0.2.97 "default,std"
-//   memoffset-0.5.6 "default"
-//   paste-1.0.5
-//   pin-project-lite-0.2.7
-//   pin-utils-0.1.0
-//   proc-macro2-1.0.27 "default,proc-macro"
-//   quote-1.0.9 "default,proc-macro"
-//   ryu-1.0.5
-//   serde-1.0.126 "default,derive,serde_derive,std"
-//   serde_derive-1.0.126 "default"
-//   serde_json-1.0.64 "default,std"
-//   slab-0.4.3 "default,std"
-//   smallvec-1.6.1
-//   syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-//   thiserror-1.0.25
-//   thiserror-impl-1.0.25
-//   unicode-xid-0.2.2 "default"
diff --git a/vm_control/Cargo.toml b/vm_control/Cargo.toml
index d2de37b..7dfa4f1 100644
--- a/vm_control/Cargo.toml
+++ b/vm_control/Cargo.toml
@@ -5,12 +5,12 @@
 edition = "2018"
 
 [features]
-gdb = ["gdbstub"]
+gdb = ["gdbstub_arch"]
 
 [dependencies]
 base = { path = "../base" }
 data_model = { path = "../data_model" }
-gdbstub = { version = "0.4.0", optional = true }
+gdbstub_arch = { version = "0.1.0", optional = true }
 hypervisor = { path = "../hypervisor" }
 libc = "*"
 resources = { path = "../resources" }
diff --git a/vm_control/cargo2android.json b/vm_control/cargo2android.json
new file mode 100644
index 0000000..2ae8879
--- /dev/null
+++ b/vm_control/cargo2android.json
@@ -0,0 +1,8 @@
+{
+  "add-module-block": "cargo2android_gdb.bp",
+  "add_workspace": true,
+  "device": true,
+  "global_defaults": "crosvm_defaults",
+  "run": true,
+  "tests": true
+}
\ No newline at end of file
diff --git a/vm_control/cargo2android_gdb.bp b/vm_control/cargo2android_gdb.bp
new file mode 100644
index 0000000..0787bb5
--- /dev/null
+++ b/vm_control/cargo2android_gdb.bp
@@ -0,0 +1,10 @@
+target: {
+    linux_glibc_x86_64: {
+        features: [
+            "gdb",
+        ],
+        rustlibs: [
+            "libgdbstub_arch",
+        ],
+    },
+}
\ No newline at end of file
diff --git a/vm_control/src/gdb.rs b/vm_control/src/gdb.rs
index 1cb4d47..4146a6f 100644
--- a/vm_control/src/gdb.rs
+++ b/vm_control/src/gdb.rs
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 #[cfg(target_arch = "x86_64")]
-use gdbstub::arch::x86::reg::X86_64CoreRegs as CoreRegs;
+use gdbstub_arch::x86::reg::X86_64CoreRegs as CoreRegs;
 use vm_memory::GuestAddress;
 
 /// Messages that can be sent to a vCPU to set/get its state from the debugger.
diff --git a/vm_control/src/lib.rs b/vm_control/src/lib.rs
index 75a7ecd..59994b6 100644
--- a/vm_control/src/lib.rs
+++ b/vm_control/src/lib.rs
@@ -20,15 +20,18 @@
 use std::os::raw::c_int;
 use std::result::Result as StdResult;
 use std::str::FromStr;
-use std::sync::Arc;
+use std::sync::{mpsc, Arc};
+
+use std::thread::JoinHandle;
 
 use libc::{EINVAL, EIO, ENODEV};
 use serde::{Deserialize, Serialize};
 
 use base::{
     error, with_as_descriptor, AsRawDescriptor, Error as SysError, Event, ExternalMapping, Fd,
-    FromRawDescriptor, IntoRawDescriptor, MappedRegion, MemoryMappingArena, MemoryMappingBuilder,
-    MemoryMappingBuilderUnix, MmapError, Protection, Result, SafeDescriptor, SharedMemory, Tube,
+    FromRawDescriptor, IntoRawDescriptor, Killable, MappedRegion, MemoryMappingArena,
+    MemoryMappingBuilder, MemoryMappingBuilderUnix, MmapError, Protection, Result, SafeDescriptor,
+    SharedMemory, Tube, SIGRTMIN,
 };
 use hypervisor::{IrqRoute, IrqSource, Vm};
 use resources::{Alloc, MmioType, SystemAllocator};
@@ -62,6 +65,7 @@
     #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
     Debug(VcpuDebug),
     RunState(VmRunMode),
+    MakeRT,
 }
 
 /// Mode of execution for the VM.
@@ -103,6 +107,7 @@
 /// require adding a big dependency for a single const.
 pub const USB_CONTROL_MAX_PORTS: usize = 16;
 
+// Balloon commands that are sent on the crosvm control socket.
 #[derive(Serialize, Deserialize, Debug)]
 pub enum BalloonControlCommand {
     /// Set the size of the VM's balloon.
@@ -112,6 +117,17 @@
     Stats,
 }
 
+// Balloon commands that are send on the balloon command tube.
+//
+// This is the same as BalloonControlCommand above, but includes an ID for the
+// Stats request so that we can discard stale stats if any previous stats
+// request failed or timed out.
+#[derive(Serialize, Deserialize, Debug)]
+pub enum BalloonTubeCommand {
+    Adjust { num_bytes: u64 },
+    Stats { id: u64 },
+}
+
 // BalloonStats holds stats returned from the stats_queue.
 #[derive(Default, Serialize, Deserialize, Debug)]
 pub struct BalloonStats {
@@ -127,6 +143,7 @@
     pub hugetlb_failures: Option<u64>,
 }
 
+// BalloonControlResult holds results for BalloonControlCommand defined above.
 #[derive(Serialize, Deserialize, Debug)]
 pub enum BalloonControlResult {
     Stats {
@@ -135,6 +152,18 @@
     },
 }
 
+// BalloonTubeResult are results to BalloonTubeCommand defined above. This is
+// the same as BalloonControlResult, but with an added ID so that we can detect
+// stale balloon stats values queued to the balloon command tube.
+#[derive(Serialize, Deserialize, Debug)]
+pub enum BalloonTubeResult {
+    Stats {
+        stats: BalloonStats,
+        balloon_actual: u64,
+        id: u64,
+    },
+}
+
 #[derive(Serialize, Deserialize, Debug)]
 pub enum DiskControlCommand {
     /// Resize a disk to `new_size` in bytes.
@@ -870,6 +899,8 @@
     Suspend,
     /// Resume the VM's VCPUs that were previously suspended.
     Resume,
+    /// Make the VM's RT VCPU real-time.
+    MakeRT,
     /// Command for balloon driver.
     BalloonCommand(BalloonControlCommand),
     /// Send a command to a disk chosen by `disk_index`.
@@ -944,9 +975,11 @@
         &self,
         run_mode: &mut Option<VmRunMode>,
         balloon_host_tube: &Tube,
+        balloon_stats_id: &mut u64,
         disk_host_tubes: &[Tube],
         usb_control_tube: Option<&Tube>,
         bat_control: &mut Option<BatControl>,
+        vcpu_handles: &[(JoinHandle<()>, mpsc::Sender<VcpuControl>)],
     ) -> VmResponse {
         match *self {
             VmRequest::Exit => {
@@ -961,27 +994,60 @@
                 *run_mode = Some(VmRunMode::Running);
                 VmResponse::Ok
             }
+            VmRequest::MakeRT => {
+                for (handle, channel) in vcpu_handles {
+                    if let Err(e) = channel.send(VcpuControl::MakeRT) {
+                        error!("failed to send MakeRT: {}", e);
+                    }
+                    let _ = handle.kill(SIGRTMIN() + 0);
+                }
+                VmResponse::Ok
+            }
             VmRequest::BalloonCommand(BalloonControlCommand::Adjust { num_bytes }) => {
-                match balloon_host_tube.send(&BalloonControlCommand::Adjust { num_bytes }) {
+                match balloon_host_tube.send(&BalloonTubeCommand::Adjust { num_bytes }) {
                     Ok(_) => VmResponse::Ok,
                     Err(_) => VmResponse::Err(SysError::last()),
                 }
             }
             VmRequest::BalloonCommand(BalloonControlCommand::Stats) => {
-                match balloon_host_tube.send(&BalloonControlCommand::Stats {}) {
-                    Ok(_) => match balloon_host_tube.recv() {
-                        Ok(BalloonControlResult::Stats {
-                            stats,
-                            balloon_actual,
-                        }) => VmResponse::BalloonStats {
-                            stats,
-                            balloon_actual,
-                        },
-                        Err(e) => {
-                            error!("balloon socket recv failed: {}", e);
-                            VmResponse::Err(SysError::last())
+                // NB: There are a few reasons stale balloon stats could be left
+                // in balloon_host_tube:
+                //  - the send succeeds, but the recv fails because the device
+                //      is not ready yet. So when the device is ready, there are
+                //      extra stats requests queued.
+                //  - the send succeed, but the recv times out. When the device
+                //      does return the stats, there will be no consumer.
+                //
+                // To guard against this, add an `id` to the stats request. If
+                // the id returned to us doesn't match, we keep trying to read
+                // until it does.
+                *balloon_stats_id = (*balloon_stats_id).wrapping_add(1);
+                let sent_id = *balloon_stats_id;
+                match balloon_host_tube.send(&BalloonTubeCommand::Stats { id: sent_id }) {
+                    Ok(_) => {
+                        loop {
+                            match balloon_host_tube.recv() {
+                                Ok(BalloonTubeResult::Stats {
+                                    stats,
+                                    balloon_actual,
+                                    id,
+                                }) => {
+                                    if sent_id != id {
+                                        // Keep trying to get the fresh stats.
+                                        continue;
+                                    }
+                                    break VmResponse::BalloonStats {
+                                        stats,
+                                        balloon_actual,
+                                    };
+                                }
+                                Err(e) => {
+                                    error!("balloon socket recv failed: {}", e);
+                                    break VmResponse::Err(SysError::last());
+                                }
+                            }
                         }
-                    },
+                    }
                     Err(_) => VmResponse::Err(SysError::last()),
                 }
             }
diff --git a/vm_memory/Android.bp b/vm_memory/Android.bp
index 6cf3342..852890f 100644
--- a/vm_memory/Android.bp
+++ b/vm_memory/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
 // Do not modify this file as changes will be overridden on upgrade.
 
 package {
@@ -27,7 +27,7 @@
 }
 
 rust_defaults {
-    name: "vm_memory_defaults",
+    name: "vm_memory_test_defaults",
     defaults: ["crosvm_defaults"],
     crate_name: "vm_memory",
     srcs: ["src/lib.rs"],
@@ -45,7 +45,7 @@
 
 rust_test_host {
     name: "vm_memory_host_test_src_lib",
-    defaults: ["vm_memory_defaults"],
+    defaults: ["vm_memory_test_defaults"],
     test_options: {
         unit_test: true,
     },
@@ -53,46 +53,5 @@
 
 rust_test {
     name: "vm_memory_device_test_src_lib",
-    defaults: ["vm_memory_defaults"],
+    defaults: ["vm_memory_test_defaults"],
 }
-
-// dependent_library ["feature_list"]
-//   ../assertions/src/lib.rs
-//   ../base/src/lib.rs
-//   ../cros_async/src/lib.rs
-//   ../data_model/src/lib.rs
-//   ../io_uring/src/lib.rs
-//   ../sync/src/lib.rs
-//   ../sys_util/poll_token_derive/poll_token_derive.rs
-//   ../sys_util/src/lib.rs
-//   ../tempfile/src/lib.rs
-//   async-task-4.0.3 "default,std"
-//   async-trait-0.1.50
-//   autocfg-1.0.1
-//   bitflags-1.2.1 "default"
-//   futures-0.3.15 "alloc"
-//   futures-channel-0.3.15 "alloc,futures-sink,sink"
-//   futures-core-0.3.15 "alloc"
-//   futures-io-0.3.15
-//   futures-sink-0.3.15 "alloc"
-//   futures-task-0.3.15 "alloc"
-//   futures-util-0.3.15 "alloc,futures-sink,sink"
-//   intrusive-collections-0.9.1 "alloc,default"
-//   itoa-0.4.7
-//   libc-0.2.97 "default,std"
-//   memoffset-0.5.6 "default"
-//   paste-1.0.5
-//   pin-project-lite-0.2.7
-//   pin-utils-0.1.0
-//   proc-macro2-1.0.27 "default,proc-macro"
-//   quote-1.0.9 "default,proc-macro"
-//   ryu-1.0.5
-//   serde-1.0.126 "default,derive,serde_derive,std"
-//   serde_derive-1.0.126 "default"
-//   serde_json-1.0.64 "default,std"
-//   slab-0.4.3 "default,std"
-//   smallvec-1.6.1
-//   syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-//   thiserror-1.0.25
-//   thiserror-impl-1.0.25
-//   unicode-xid-0.2.2 "default"
diff --git a/x86_64/Android.bp b/x86_64/Android.bp
index 3afaa60..4d8274a 100644
--- a/x86_64/Android.bp
+++ b/x86_64/Android.bp
@@ -1,5 +1,5 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace --features=gdb.
-// NOTE: The --features=gdb should be applied only to the host (not the device) and there are inline changes to achieve this
+// This file is generated by cargo2android.py --config cargo2android.json.
+// Do not modify this file as changes will be overridden on upgrade.
 
 package {
     // See: http://go/android-license-faq
@@ -14,21 +14,11 @@
     name: "libx86_64_rust",
     defaults: ["crosvm_defaults"],
     stem: "libx86_64",
+    // has rustc warnings
     host_supported: true,
     crate_name: "x86_64",
     srcs: ["src/lib.rs"],
     edition: "2018",
-    target: {
-        linux_glibc_x86_64: {
-            features: [
-                "gdb",
-                "gdbstub",
-            ],
-            rustlibs: [
-                "libgdbstub",
-            ],
-        },
-    },
     rustlibs: [
         "libacpi_tables",
         "libarch",
@@ -47,6 +37,16 @@
         "libvm_memory",
     ],
     proc_macros: ["libremain"],
+    target: {
+        linux_glibc_x86_64: {
+            features: [
+                "gdb",
+            ],
+            rustlibs: [
+                "libgdbstub_arch",
+            ],
+        },
+    },
     // Exclude arm family manually
     arch: {
         arm: {
@@ -59,24 +59,14 @@
 }
 
 rust_defaults {
-    name: "x86_64_defaults",
+    name: "x86_64_test_defaults",
     defaults: ["crosvm_defaults"],
     crate_name: "x86_64",
+    // has rustc warnings
     srcs: ["src/lib.rs"],
     test_suites: ["general-tests"],
     auto_gen_config: true,
     edition: "2018",
-    target: {
-        linux_glibc_x86_64: {
-            features: [
-                "gdb",
-                "gdbstub",
-            ],
-            rustlibs: [
-                "libgdbstub",
-            ],
-        },
-    },
     rustlibs: [
         "libacpi_tables",
         "libarch",
@@ -95,6 +85,24 @@
         "libvm_memory",
     ],
     proc_macros: ["libremain"],
+}
+
+rust_test_host {
+    name: "x86_64_host_test_src_lib",
+    defaults: ["x86_64_test_defaults"],
+    test_options: {
+        unit_test: true,
+    },
+    target: {
+        linux_glibc_x86_64: {
+            features: [
+                "gdb",
+            ],
+            rustlibs: [
+                "libgdbstub_arch",
+            ],
+        },
+    },
     // Exclude arm family manually
     arch: {
         arm: {
@@ -106,106 +114,26 @@
     },
 }
 
-rust_test_host {
-    name: "x86_64_host_test_src_lib",
-    defaults: ["x86_64_defaults"],
-    test_options: {
-        unit_test: true,
-    },
-}
-
 rust_test {
     name: "x86_64_device_test_src_lib",
-    defaults: ["x86_64_defaults"],
+    defaults: ["x86_64_test_defaults"],
+    target: {
+        linux_glibc_x86_64: {
+            features: [
+                "gdb",
+            ],
+            rustlibs: [
+                "libgdbstub_arch",
+            ],
+        },
+    },
+    // Exclude arm family manually
+    arch: {
+        arm: {
+            enabled: false,
+        },
+        arm64: {
+            enabled: false,
+        },
+    },
 }
-
-// dependent_library ["feature_list"]
-//   ../../adhd/audio_streams/src/audio_streams.rs
-//   ../../adhd/cras/client/cras-sys/src/lib.rs
-//   ../../adhd/cras/client/libcras/src/libcras.rs
-//   ../../libchromeos-rs/src/lib.rs
-//   ../../minijail/rust/minijail-sys/lib.rs
-//   ../../minijail/rust/minijail/src/lib.rs
-//   ../../rust/vmm_vhost/src/lib.rs "default,vhost-user,vhost-user-master"
-//   ../../vm_tools/p9/src/lib.rs
-//   ../../vm_tools/p9/wire_format_derive/wire_format_derive.rs
-//   ../acpi_tables/src/lib.rs
-//   ../arch/src/lib.rs
-//   ../assertions/src/lib.rs
-//   ../base/src/lib.rs
-//   ../bit_field/bit_field_derive/bit_field_derive.rs
-//   ../bit_field/src/lib.rs
-//   ../cros_async/src/lib.rs
-//   ../data_model/src/lib.rs
-//   ../devices/src/lib.rs
-//   ../disk/src/disk.rs
-//   ../enumn/src/lib.rs
-//   ../fuse/src/lib.rs
-//   ../hypervisor/src/lib.rs
-//   ../io_uring/src/lib.rs
-//   ../kernel_cmdline/src/kernel_cmdline.rs
-//   ../kernel_loader/src/lib.rs
-//   ../kvm/src/lib.rs
-//   ../kvm_sys/src/lib.rs
-//   ../linux_input_sys/src/lib.rs
-//   ../net_sys/src/lib.rs
-//   ../net_util/src/lib.rs
-//   ../power_monitor/src/lib.rs
-//   ../rand_ish/src/lib.rs
-//   ../resources/src/lib.rs
-//   ../rutabaga_gfx/src/lib.rs
-//   ../sync/src/lib.rs
-//   ../sys_util/poll_token_derive/poll_token_derive.rs
-//   ../sys_util/src/lib.rs
-//   ../tempfile/src/lib.rs
-//   ../usb_sys/src/lib.rs
-//   ../usb_util/src/lib.rs
-//   ../vfio_sys/src/lib.rs
-//   ../vhost/src/lib.rs
-//   ../virtio_sys/src/lib.rs
-//   ../vm_control/src/lib.rs
-//   ../vm_memory/src/lib.rs
-//   async-task-4.0.3 "default,std"
-//   async-trait-0.1.50
-//   autocfg-1.0.1
-//   bitflags-1.2.1 "default"
-//   cfg-if-1.0.0
-//   downcast-rs-1.2.0 "default,std"
-//   futures-0.3.15 "alloc,std"
-//   futures-channel-0.3.15 "alloc,futures-sink,sink,std"
-//   futures-core-0.3.15 "alloc,std"
-//   futures-io-0.3.15 "std"
-//   futures-sink-0.3.15 "alloc,std"
-//   futures-task-0.3.15 "alloc,std"
-//   futures-util-0.3.15 "alloc,channel,futures-channel,futures-io,futures-sink,io,memchr,sink,slab,std"
-//   getrandom-0.2.3 "std"
-//   intrusive-collections-0.9.1 "alloc,default"
-//   itoa-0.4.7
-//   libc-0.2.97 "default,std"
-//   log-0.4.14
-//   memchr-2.4.0 "default,std"
-//   memoffset-0.5.6 "default"
-//   paste-1.0.5
-//   pin-project-lite-0.2.7
-//   pin-utils-0.1.0
-//   pkg-config-0.3.19
-//   ppv-lite86-0.2.10 "simd,std"
-//   proc-macro2-1.0.27 "default,proc-macro"
-//   protobuf-2.24.1
-//   quote-1.0.9 "default,proc-macro"
-//   rand-0.8.4 "alloc,default,getrandom,libc,rand_chacha,rand_hc,std,std_rng"
-//   rand_chacha-0.3.1 "std"
-//   rand_core-0.6.3 "alloc,getrandom,std"
-//   remain-0.2.2
-//   remove_dir_all-0.5.3
-//   ryu-1.0.5
-//   serde-1.0.126 "default,derive,serde_derive,std"
-//   serde_derive-1.0.126 "default"
-//   serde_json-1.0.64 "default,std"
-//   slab-0.4.3 "default,std"
-//   smallvec-1.6.1
-//   syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-//   tempfile-3.2.0
-//   thiserror-1.0.25
-//   thiserror-impl-1.0.25
-//   unicode-xid-0.2.2 "default"
diff --git a/x86_64/Cargo.toml b/x86_64/Cargo.toml
index 07fac9d..06abdf1 100644
--- a/x86_64/Cargo.toml
+++ b/x86_64/Cargo.toml
@@ -5,14 +5,14 @@
 edition = "2018"
 
 [features]
-gdb = ["gdbstub", "arch/gdb"]
+gdb = ["gdbstub_arch", "arch/gdb"]
 
 [dependencies]
 arch = { path = "../arch" }
 assertions = { path = "../assertions" }
 data_model = { path = "../data_model" }
 devices = { path = "../devices" }
-gdbstub = { version = "0.4.0", optional = true }
+gdbstub_arch = { version = "0.1.0", optional = true }
 hypervisor = { path = "../hypervisor" }
 kernel_cmdline = { path = "../kernel_cmdline" }
 kernel_loader = { path = "../kernel_loader" }
diff --git a/x86_64/cargo2android.json b/x86_64/cargo2android.json
new file mode 100644
index 0000000..2ae8879
--- /dev/null
+++ b/x86_64/cargo2android.json
@@ -0,0 +1,8 @@
+{
+  "add-module-block": "cargo2android_gdb.bp",
+  "add_workspace": true,
+  "device": true,
+  "global_defaults": "crosvm_defaults",
+  "run": true,
+  "tests": true
+}
\ No newline at end of file
diff --git a/x86_64/cargo2android_gdb.bp b/x86_64/cargo2android_gdb.bp
new file mode 100644
index 0000000..7e5892c
--- /dev/null
+++ b/x86_64/cargo2android_gdb.bp
@@ -0,0 +1,19 @@
+target: {
+    linux_glibc_x86_64: {
+        features: [
+            "gdb",
+        ],
+        rustlibs: [
+            "libgdbstub_arch",
+        ],
+    },
+},
+// Exclude arm family manually
+arch: {
+    arm: {
+        enabled: false,
+    },
+    arm64: {
+        enabled: false,
+    },
+}
\ No newline at end of file
diff --git a/x86_64/src/lib.rs b/x86_64/src/lib.rs
index 4165858..632d062 100644
--- a/x86_64/src/lib.rs
+++ b/x86_64/src/lib.rs
@@ -54,14 +54,14 @@
 use std::sync::Arc;
 
 use crate::bootparam::boot_params;
-use acpi_tables::aml::Aml;
 use acpi_tables::sdt::SDT;
+use acpi_tables::{aml, aml::Aml};
 use arch::{
-    get_serial_cmdline, GetSerialCmdlineError, RunnableLinuxVm, SerialHardware, SerialParameters,
-    VmComponents, VmImage,
+    get_serial_cmdline, GetSerialCmdlineError, LinuxArch, RunnableLinuxVm, SerialHardware,
+    SerialParameters, VmComponents, VmImage,
 };
 use base::Event;
-use devices::{IrqChip, IrqChipX86_64, PciConfigIo, PciDevice, ProtectionType};
+use devices::{BusResumeDevice, IrqChip, IrqChipX86_64, PciConfigIo, PciDevice, ProtectionType};
 use hypervisor::{HypervisorX86_64, VcpuX86_64, VmX86_64};
 use minijail::Minijail;
 use remain::sorted;
@@ -71,7 +71,7 @@
 use vm_memory::{GuestAddress, GuestMemory, GuestMemoryError};
 #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
 use {
-    gdbstub::arch::x86::reg::X86_64CoreRegs,
+    gdbstub_arch::x86::reg::{X86SegmentRegs, X86_64CoreRegs},
     hypervisor::x86_64::{Regs, Sregs},
 };
 
@@ -359,6 +359,10 @@
         Ok(arch_memory_regions(components.memory_size, bios_size))
     }
 
+    fn get_phys_max_addr() -> u64 {
+        (1u64 << cpuid::phy_max_address_bits()) - 1
+    }
+
     fn create_system_allocator(guest_mem: &GuestMemory) -> SystemAllocator {
         let high_mmio_start = Self::get_high_mmio_base(guest_mem);
         SystemAllocator::builder()
@@ -430,7 +434,10 @@
             serial_jail,
         )?;
 
+        let mut resume_notify_devices = Vec::new();
+
         let (acpi_dev_resource, bat_control) = Self::setup_acpi_devices(
+            &mem,
             &mut io_bus,
             system_allocator,
             suspend_evt.try_clone().map_err(Error::CloneEvent)?,
@@ -439,10 +446,17 @@
             irq_chip.as_irq_chip_mut(),
             battery,
             &mut mmio_bus,
+            &mut resume_notify_devices,
         )?;
 
-        // Use ACPI description if provided by the user.
-        let noacpi = acpi_dev_resource.sdts.is_empty();
+        // Use IRQ info in ACPI if provided by the user.
+        let mut noirq = true;
+
+        for sdt in acpi_dev_resource.sdts.iter() {
+            if sdt.is_signature(b"DSDT") || sdt.is_signature(b"APIC") {
+                noirq = false;
+            }
+        }
 
         let ramoops_region = match components.pstore {
             Some(pstore) => Some(
@@ -476,8 +490,8 @@
             VmImage::Kernel(ref mut kernel_image) => {
                 let mut cmdline = Self::get_base_linux_cmdline();
 
-                if noacpi {
-                    cmdline.insert_str("pci=noacpi").unwrap();
+                if noirq {
+                    cmdline.insert_str("acpi=noirq").unwrap();
                 }
 
                 get_serial_cmdline(&mut cmdline, serial_parameters, "io")
@@ -533,7 +547,9 @@
             mmio_bus,
             pid_debug_label_map,
             suspend_evt,
+            resume_notify_devices,
             rt_cpus: components.rt_cpus,
+            delay_rt: components.delay_rt,
             bat_control,
             #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
             gdb: components.gdb,
@@ -592,12 +608,14 @@
 
         // Segment registers: CS, SS, DS, ES, FS, GS
         let sregs = vcpu.get_sregs().map_err(Error::ReadRegs)?;
-        let sgs = [sregs.cs, sregs.ss, sregs.ds, sregs.es, sregs.fs, sregs.gs];
-        let mut segments = [0u32; 6];
-        // GDB uses only the selectors.
-        for i in 0..sgs.len() {
-            segments[i] = sgs[i].selector as u32;
-        }
+        let segments = X86SegmentRegs {
+            cs: sregs.cs.selector as u32,
+            ss: sregs.ss.selector as u32,
+            ds: sregs.ds.selector as u32,
+            es: sregs.es.selector as u32,
+            fs: sregs.fs.selector as u32,
+            gs: sregs.gs.selector as u32,
+        };
 
         // TODO(keiichiw): Other registers such as FPU, xmm and mxcsr.
 
@@ -640,12 +658,12 @@
         // Segment registers: CS, SS, DS, ES, FS, GS
         // Since GDB care only selectors, we call get_sregs() first.
         let mut sregs = vcpu.get_sregs().map_err(Error::ReadRegs)?;
-        sregs.cs.selector = regs.segments[0] as u16;
-        sregs.ss.selector = regs.segments[1] as u16;
-        sregs.ds.selector = regs.segments[2] as u16;
-        sregs.es.selector = regs.segments[3] as u16;
-        sregs.fs.selector = regs.segments[4] as u16;
-        sregs.gs.selector = regs.segments[5] as u16;
+        sregs.cs.selector = regs.segments.cs as u16;
+        sregs.ss.selector = regs.segments.ss as u16;
+        sregs.ds.selector = regs.segments.ds as u16;
+        sregs.es.selector = regs.segments.es as u16;
+        sregs.fs.selector = regs.segments.fs as u16;
+        sregs.gs.selector = regs.segments.gs as u16;
 
         vcpu.set_sregs(&sregs).map_err(Error::WriteRegs)?;
 
@@ -1029,6 +1047,7 @@
     /// * - `battery` indicate whether to create the battery
     /// * - `mmio_bus` the MMIO bus to add the devices to
     fn setup_acpi_devices(
+        mem: &GuestMemory,
         io_bus: &mut devices::Bus,
         resources: &mut SystemAllocator,
         suspend_evt: Event,
@@ -1037,6 +1056,7 @@
         irq_chip: &mut dyn IrqChip,
         battery: (&Option<BatteryType>, Option<Minijail>),
         mmio_bus: &mut devices::Bus,
+        resume_notify_devices: &mut Vec<Arc<Mutex<dyn BusResumeDevice>>>,
     ) -> Result<(acpi::ACPIDevResource, Option<BatControl>)> {
         // The AML data for the acpi devices
         let mut amls = Vec::new();
@@ -1056,6 +1076,43 @@
 
         let pmresource = devices::ACPIPMResource::new(suspend_evt, exit_evt);
         Aml::to_aml_bytes(&pmresource, &mut amls);
+
+        let mut pci_dsdt_inner_data: Vec<&dyn aml::Aml> = Vec::new();
+        let hid = aml::Name::new("_HID".into(), &aml::EISAName::new("PNP0A08"));
+        pci_dsdt_inner_data.push(&hid);
+        let cid = aml::Name::new("_CID".into(), &aml::EISAName::new("PNP0A03"));
+        pci_dsdt_inner_data.push(&cid);
+        let adr = aml::Name::new("_ADR".into(), &aml::ZERO);
+        pci_dsdt_inner_data.push(&adr);
+        let seg = aml::Name::new("_SEG".into(), &aml::ZERO);
+        pci_dsdt_inner_data.push(&seg);
+        let uid = aml::Name::new("_UID".into(), &aml::ZERO);
+        pci_dsdt_inner_data.push(&uid);
+        let supp = aml::Name::new("SUPP".into(), &aml::ZERO);
+        pci_dsdt_inner_data.push(&supp);
+        let crs = aml::Name::new(
+            "_CRS".into(),
+            &aml::ResourceTemplate::new(vec![
+                &aml::AddressSpace::new_bus_number(0x0u16, 0xffu16),
+                &aml::IO::new(0xcf8, 0xcf8, 1, 0x8),
+                &aml::AddressSpace::new_memory(
+                    aml::AddressSpaceCachable::NotCacheable,
+                    true,
+                    END_ADDR_BEFORE_32BITS as u32,
+                    (END_ADDR_BEFORE_32BITS + MMIO_SIZE - 1) as u32,
+                ),
+                &aml::AddressSpace::new_memory(
+                    aml::AddressSpaceCachable::NotCacheable,
+                    true,
+                    Self::get_high_mmio_base(mem),
+                    X8664arch::get_phys_max_addr(),
+                ),
+            ]),
+        );
+        pci_dsdt_inner_data.push(&crs);
+
+        aml::Device::new("_SB_.PCI0".into(), pci_dsdt_inner_data).to_aml_bytes(&mut amls);
+
         let pm = Arc::new(Mutex::new(pmresource));
         io_bus
             .insert(
@@ -1064,7 +1121,7 @@
                 devices::acpi::ACPIPM_RESOURCE_LEN as u64,
             )
             .unwrap();
-        io_bus.notify_on_resume(pm);
+        resume_notify_devices.push(pm);
 
         let bat_control = if let Some(battery_type) = battery.0 {
             match battery_type {
diff --git a/x86_64/src/test_integration.rs b/x86_64/src/test_integration.rs
index c3f77cd..75ecdf3 100644
--- a/x86_64/src/test_integration.rs
+++ b/x86_64/src/test_integration.rs
@@ -160,7 +160,7 @@
     )
     .unwrap();
 
-    let param_args = "nokaslr pci=noacpi";
+    let param_args = "nokaslr acpi=noirq";
 
     let mut cmdline = X8664arch::get_base_linux_cmdline();
 
@@ -178,7 +178,9 @@
     // let (params, kernel_end) = X8664arch::load_kernel(&guest_mem, &mut kernel_image).expect("failed to load kernel");
 
     let suspend_evt = Event::new().unwrap();
+    let mut resume_notify_devices = Vec::new();
     let acpi_dev_resource = X8664arch::setup_acpi_devices(
+        &guest_mem,
         &mut io_bus,
         &mut resources,
         suspend_evt
@@ -189,6 +191,7 @@
         &mut irq_chip,
         (&None, None),
         &mut mmio_bus,
+        &mut resume_notify_devices,
     )
     .unwrap();