summary refs log tree commit diff
path: root/pkgs
diff options
context:
space:
mode:
authorAlyssa Ross <alyssa.ross@unikie.com>2022-09-30 21:09:04 +0000
committerAlyssa Ross <hi@alyssa.is>2023-11-21 16:10:48 +0100
commit4c2d05dd6435d449a3651a6dd314d9411b5f8146 (patch)
treef8f5850ff05521ab82d65745894714a8796cbfb6 /pkgs
parentc20cc4f1f089f4a93651a1b667299ddac73f59ba (diff)
downloadnixpkgs-4c2d05dd6435d449a3651a6dd314d9411b5f8146.tar
nixpkgs-4c2d05dd6435d449a3651a6dd314d9411b5f8146.tar.gz
nixpkgs-4c2d05dd6435d449a3651a6dd314d9411b5f8146.tar.bz2
nixpkgs-4c2d05dd6435d449a3651a6dd314d9411b5f8146.tar.lz
nixpkgs-4c2d05dd6435d449a3651a6dd314d9411b5f8146.tar.xz
nixpkgs-4c2d05dd6435d449a3651a6dd314d9411b5f8146.tar.zst
nixpkgs-4c2d05dd6435d449a3651a6dd314d9411b5f8146.zip
cloud-hypervisor: add virtio-gpu support
The virtio-bindings changes update the bindings for recent kernels,
and the vhost change is cherry-picked from crosvm's fork of the crate
to add support for their custom extensions.

Signed-off-by: Alyssa Ross <alyssa.ross@unikie.com>
Signed-off-by: Alyssa Ross <hi@alyssa.is>
Diffstat (limited to 'pkgs')
-rw-r--r--pkgs/applications/virtualization/cloud-hypervisor/0001-build-use-local-vhost.patch50
-rw-r--r--pkgs/applications/virtualization/cloud-hypervisor/0002-virtio-devices-add-a-GPU-device.patch1344
-rw-r--r--pkgs/applications/virtualization/cloud-hypervisor/Cargo.lock5
-rw-r--r--pkgs/applications/virtualization/cloud-hypervisor/default.nix33
-rw-r--r--pkgs/applications/virtualization/cloud-hypervisor/vhost/0001-vhost-fix-receiving-reply-payloads.patch113
-rw-r--r--pkgs/applications/virtualization/cloud-hypervisor/vhost/0002-vhost_user-add-shared-memory-region-support.patch492
-rw-r--r--pkgs/applications/virtualization/cloud-hypervisor/vhost/0003-vhost-user-add-protocol-flag-for-shmem.patch38
7 files changed, 2071 insertions, 4 deletions
diff --git a/pkgs/applications/virtualization/cloud-hypervisor/0001-build-use-local-vhost.patch b/pkgs/applications/virtualization/cloud-hypervisor/0001-build-use-local-vhost.patch
new file mode 100644
index 00000000000..edf1571fd10
--- /dev/null
+++ b/pkgs/applications/virtualization/cloud-hypervisor/0001-build-use-local-vhost.patch
@@ -0,0 +1,50 @@
+From 4c8d6c07213a9826545f5682b2d51cb84ffbb9ff Mon Sep 17 00:00:00 2001
+From: Alyssa Ross <alyssa.ross@unikie.com>
+Date: Wed, 28 Sep 2022 12:18:19 +0000
+Subject: [PATCH 1/2] build: use local vhost
+
+Signed-off-by: Alyssa Ross <alyssa.ross@unikie.com>
+Signed-off-by: Alyssa Ross <hi@alyssa.is>
+---
+ Cargo.lock | 4 ----
+ Cargo.toml | 2 ++
+ 2 files changed, 2 insertions(+), 4 deletions(-)
+
+diff --git a/Cargo.lock b/Cargo.lock
+index 346e3f45..d591569b 100644
+--- a/Cargo.lock
++++ b/Cargo.lock
+@@ -2160,8 +2160,6 @@ dependencies = [
+ [[package]]
+ name = "vhost"
+ version = "0.8.1"
+-source = "registry+https://github.com/rust-lang/crates.io-index"
+-checksum = "61957aeb36daf0b00b87fff9c10dd28a161bd35ab157553d340d183b3d8756e6"
+ dependencies = [
+  "bitflags 1.3.2",
+  "libc",
+@@ -2172,8 +2170,6 @@ dependencies = [
+ [[package]]
+ name = "vhost-user-backend"
+ version = "0.10.1"
+-source = "registry+https://github.com/rust-lang/crates.io-index"
+-checksum = "ab069cdedaf18a0673766eb0a07a0f4ee3ed1b8e17fbfe4aafe5b988e2de1d01"
+ dependencies = [
+  "libc",
+  "log",
+diff --git a/Cargo.toml b/Cargo.toml
+index 0f575076..3b39bede 100644
+--- a/Cargo.toml
++++ b/Cargo.toml
+@@ -55,6 +55,8 @@ zbus = { version = "3.11.1", optional = true }
+ kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch-v0.6.0-tdx" }
+ kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "main" }
+ versionize_derive = { git = "https://github.com/cloud-hypervisor/versionize_derive", branch = "ch" }
++vhost = { path = "../vhost/crates/vhost" }
++vhost-user-backend = { path = "../vhost/crates/vhost-user-backend" }
+ 
+ [dev-dependencies]
+ dirs = "5.0.0"
+-- 
+2.42.0
+
diff --git a/pkgs/applications/virtualization/cloud-hypervisor/0002-virtio-devices-add-a-GPU-device.patch b/pkgs/applications/virtualization/cloud-hypervisor/0002-virtio-devices-add-a-GPU-device.patch
new file mode 100644
index 00000000000..c23caa94a67
--- /dev/null
+++ b/pkgs/applications/virtualization/cloud-hypervisor/0002-virtio-devices-add-a-GPU-device.patch
@@ -0,0 +1,1344 @@
+From 285370872d43dbc17ed017dbf67b1981df5c4b3b Mon Sep 17 00:00:00 2001
+From: Alyssa Ross <alyssa.ross@unikie.com>
+Date: Wed, 7 Sep 2022 14:16:29 +0000
+Subject: [PATCH 2/2] virtio-devices: add a GPU device
+
+This adds support for exposing a virtio-gpu device to guest by
+implementing a vhost-user frontend compatible with crosvm's GPU device
+backend.
+
+Note that this is not the same as the "vhost-user-gpu" protocol
+implemented by QEMU.
+
+Adding a GPU device from the command line looks like this:
+
+    --gpu socket=/path/to/crosvm-gpu-vhost-user.sock
+
+Signed-off-by: Alyssa Ross <alyssa.ross@unikie.com>
+Co-authored-by: Alyssa Ross <hi@alyssa.is>
+Signed-off-by: Alyssa Ross <hi@alyssa.is>
+---
+ Cargo.lock                                 |   1 +
+ src/main.rs                                |  12 +
+ virtio-devices/src/device.rs               |   8 +-
+ virtio-devices/src/lib.rs                  |   4 +-
+ virtio-devices/src/seccomp_filters.rs      |  16 +
+ virtio-devices/src/transport/pci_device.rs |   4 +-
+ virtio-devices/src/vhost_user/gpu.rs       | 406 +++++++++++++++++++++
+ virtio-devices/src/vhost_user/mod.rs       |   2 +
+ vmm/Cargo.toml                             |   1 +
+ vmm/src/api/mod.rs                         |  10 +-
+ vmm/src/config.rs                          | 111 ++++++
+ vmm/src/device_manager.rs                  | 140 ++++++-
+ vmm/src/lib.rs                             |  88 ++++-
+ vmm/src/vm.rs                              |  28 +-
+ vmm/src/vm_config.rs                       |  27 ++
+ 15 files changed, 841 insertions(+), 17 deletions(-)
+ create mode 100644 virtio-devices/src/vhost_user/gpu.rs
+
+diff --git a/Cargo.lock b/Cargo.lock
+index d591569b..e1a1ad02 100644
+--- a/Cargo.lock
++++ b/Cargo.lock
+@@ -2370,6 +2370,7 @@ dependencies = [
+  "versionize_derive",
+  "vfio-ioctls",
+  "vfio_user",
++ "virtio-bindings",
+  "virtio-devices",
+  "virtio-queue",
+  "vm-allocator",
+diff --git a/src/main.rs b/src/main.rs
+index 9521b7af..0dc315b7 100644
+--- a/src/main.rs
++++ b/src/main.rs
+@@ -199,6 +199,10 @@ pub struct TopLevel {
+     /// tag=<tag_name>, socket=<socket_path>, num_queues=<number_of_queues>, queue_size=<size_of_each_queue>, id=<device_id>, pci_segment=<segment_id>
+     fs: Vec<String>,
+ 
++    #[argh(option, long = "gpu")]
++    /// socket=<socket_path>,cache_size=<default 8GiB>,id=<device_id>,pci_segment=<segment_id>
++    gpu: Vec<String>,
++
+     #[argh(option, long = "pmem")]
+     /// file=<backing_file_path>, size=<persistent_memory_size>, iommu=on|off, discard_writes=on|off, id=<device_id>, pci_segment=<segment_id>
+     pmem: Vec<String>,
+@@ -333,6 +337,12 @@ impl TopLevel {
+             None
+         };
+ 
++        let gpu = if !self.gpu.is_empty() {
++            Some(self.gpu.iter().map(|x| x.as_str()).collect())
++        } else {
++            None
++        };
++
+         let pmem = if !self.pmem.is_empty() {
+             Some(self.pmem.iter().map(|x| x.as_str()).collect())
+         } else {
+@@ -389,6 +399,7 @@ impl TopLevel {
+             rng,
+             balloon,
+             fs,
++            gpu,
+             pmem,
+             serial,
+             console,
+@@ -803,6 +814,7 @@ mod unit_tests {
+             },
+             balloon: None,
+             fs: None,
++            gpu: None,
+             pmem: None,
+             serial: ConsoleConfig {
+                 file: None,
+diff --git a/virtio-devices/src/device.rs b/virtio-devices/src/device.rs
+index b70092f8..e091ddd6 100644
+--- a/virtio-devices/src/device.rs
++++ b/virtio-devices/src/device.rs
+@@ -11,7 +11,7 @@ use crate::{
+     VIRTIO_F_RING_INDIRECT_DESC,
+ };
+ use libc::EFD_NONBLOCK;
+-use std::collections::HashMap;
++use std::collections::{BTreeMap, HashMap};
+ use std::io::Write;
+ use std::num::Wrapping;
+ use std::sync::{
+@@ -47,19 +47,19 @@ pub struct UserspaceMapping {
+     pub mergeable: bool,
+ }
+ 
+-#[derive(Clone)]
++#[derive(Clone, Debug)]
+ pub struct VirtioSharedMemory {
+     pub offset: u64,
+     pub len: u64,
+ }
+ 
+-#[derive(Clone)]
++#[derive(Clone, Debug)]
+ pub struct VirtioSharedMemoryList {
+     pub host_addr: u64,
+     pub mem_slot: u32,
+     pub addr: GuestAddress,
+     pub len: GuestUsize,
+-    pub region_list: Vec<VirtioSharedMemory>,
++    pub region_list: BTreeMap<u8, VirtioSharedMemory>,
+ }
+ 
+ /// Trait for virtio devices to be driven by a virtio transport.
+diff --git a/virtio-devices/src/lib.rs b/virtio-devices/src/lib.rs
+index 680cbe29..55428cd8 100644
+--- a/virtio-devices/src/lib.rs
++++ b/virtio-devices/src/lib.rs
+@@ -44,7 +44,7 @@ pub use self::block::{Block, BlockState};
+ pub use self::console::{Console, ConsoleResizer, Endpoint};
+ pub use self::device::{
+     DmaRemapping, UserspaceMapping, VirtioCommon, VirtioDevice, VirtioInterrupt,
+-    VirtioInterruptType, VirtioSharedMemoryList,
++    VirtioInterruptType, VirtioSharedMemory, VirtioSharedMemoryList,
+ };
+ pub use self::epoll_helper::{
+     EpollHelper, EpollHelperError, EpollHelperHandler, EPOLL_HELPER_EVENT_LAST,
+@@ -93,6 +93,8 @@ pub enum ActivateError {
+     VhostUserFsSetup(vhost_user::Error),
+     #[error("Failed to setup vhost-user daemon: {0}")]
+     VhostUserSetup(vhost_user::Error),
++    #[error("Failed to setup vhost-user-gpu daemon: {0}")]
++    VhostUserGpuSetup(vhost_user::Error),
+     #[error("Failed to create seccomp filter: {0}")]
+     CreateSeccompFilter(seccompiler::Error),
+     #[error("Failed to create rate limiter: {0}")]
+diff --git a/virtio-devices/src/seccomp_filters.rs b/virtio-devices/src/seccomp_filters.rs
+index 43d723b8..8620419f 100644
+--- a/virtio-devices/src/seccomp_filters.rs
++++ b/virtio-devices/src/seccomp_filters.rs
+@@ -22,6 +22,7 @@ pub enum Thread {
+     VirtioRng,
+     VirtioVhostBlock,
+     VirtioVhostFs,
++    VirtioVhostGpu,
+     VirtioVhostNet,
+     VirtioVhostNetCtl,
+     VirtioVsock,
+@@ -163,6 +164,20 @@ fn virtio_vhost_fs_thread_rules() -> Vec<(i64, Vec<SeccompRule>)> {
+     ]
+ }
+ 
++fn virtio_vhost_gpu_thread_rules() -> Vec<(i64, Vec<SeccompRule>)> {
++    vec![
++        (libc::SYS_clock_nanosleep, vec![]),
++        (libc::SYS_connect, vec![]),
++        (libc::SYS_getcwd, vec![]),
++        (libc::SYS_nanosleep, vec![]),
++        (libc::SYS_recvmsg, vec![]),
++        (libc::SYS_recvmsg, vec![]),
++        (libc::SYS_sendmsg, vec![]),
++        (libc::SYS_sendmsg, vec![]),
++        (libc::SYS_socket, vec![]),
++    ]
++}
++
+ fn virtio_vhost_net_ctl_thread_rules() -> Vec<(i64, Vec<SeccompRule>)> {
+     vec![]
+ }
+@@ -234,6 +249,7 @@ fn get_seccomp_rules(thread_type: Thread) -> Vec<(i64, Vec<SeccompRule>)> {
+         Thread::VirtioRng => virtio_rng_thread_rules(),
+         Thread::VirtioVhostBlock => virtio_vhost_block_thread_rules(),
+         Thread::VirtioVhostFs => virtio_vhost_fs_thread_rules(),
++        Thread::VirtioVhostGpu => virtio_vhost_gpu_thread_rules(),
+         Thread::VirtioVhostNet => virtio_vhost_net_thread_rules(),
+         Thread::VirtioVhostNetCtl => virtio_vhost_net_ctl_thread_rules(),
+         Thread::VirtioVsock => virtio_vsock_thread_rules(),
+diff --git a/virtio-devices/src/transport/pci_device.rs b/virtio-devices/src/transport/pci_device.rs
+index 60979573..0348b53a 100644
+--- a/virtio-devices/src/transport/pci_device.rs
++++ b/virtio-devices/src/transport/pci_device.rs
+@@ -1054,11 +1054,11 @@ impl PciDevice for VirtioPciDevice {
+                     PciDeviceError::IoRegistrationFailed(shm_list.addr.raw_value(), e)
+                 })?;
+ 
+-                for (idx, shm) in shm_list.region_list.iter().enumerate() {
++                for (shmid, shm) in shm_list.region_list.iter() {
+                     let shm_cap = VirtioPciCap64::new(
+                         PciCapabilityType::SharedMemory,
+                         VIRTIO_SHM_BAR_INDEX as u8,
+-                        idx as u8,
++                        *shmid,
+                         shm.offset,
+                         shm.len,
+                     );
+diff --git a/virtio-devices/src/vhost_user/gpu.rs b/virtio-devices/src/vhost_user/gpu.rs
+new file mode 100644
+index 00000000..416d9629
+--- /dev/null
++++ b/virtio-devices/src/vhost_user/gpu.rs
+@@ -0,0 +1,406 @@
++// Copyright 2019 Intel Corporation. All Rights Reserved.
++// Copyright 2022 Unikie
++// Copyright 2023 Alyssa Ross <hi@alyssa.is>
++// SPDX-License-Identifier: Apache-2.0
++
++use super::vu_common_ctrl::VhostUserHandle;
++use super::{Error, Result};
++use crate::seccomp_filters::Thread;
++use crate::thread_helper::spawn_virtio_thread;
++use crate::vhost_user::VhostUserCommon;
++use crate::{
++    ActivateError, ActivateResult, UserspaceMapping, VirtioCommon, VirtioDevice, VirtioDeviceType,
++    VirtioInterrupt, VirtioSharedMemoryList, VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1,
++};
++use crate::{GuestMemoryMmap, GuestRegionMmap, MmapRegion};
++use seccompiler::SeccompAction;
++use std::io::{self, Write};
++use std::os::unix::io::AsRawFd;
++use std::result;
++use std::sync::{Arc, Barrier, Mutex};
++use std::thread;
++use vhost::vhost_user::message::{
++    VhostUserConfigFlags, VhostUserProtocolFeatures, VhostUserShmemMapMsg, VhostUserShmemUnmapMsg,
++    VhostUserVirtioFeatures,
++};
++use vhost::vhost_user::{
++    HandlerResult, MasterReqHandler, VhostUserMaster, VhostUserMasterReqHandler,
++};
++use virtio_bindings::virtio_gpu::{
++    VIRTIO_GPU_F_CONTEXT_INIT, VIRTIO_GPU_F_RESOURCE_BLOB, VIRTIO_GPU_F_RESOURCE_UUID,
++    VIRTIO_GPU_F_VIRGL,
++};
++use virtio_queue::Queue;
++use vm_memory::GuestMemoryAtomic;
++use vm_migration::{MigratableError, Pausable};
++use vmm_sys_util::eventfd::EventFd;
++
++const QUEUE_SIZES: &[u16] = &[256, 16];
++const NUM_QUEUES: u16 = QUEUE_SIZES.len() as _;
++
++struct SlaveReqHandler {
++    cache_size: u64,
++    mmap_cache_addr: u64,
++}
++
++impl SlaveReqHandler {
++    // Make sure request is within cache range
++    fn is_req_valid(&self, offset: u64, len: u64) -> bool {
++        let end = match offset.checked_add(len) {
++            Some(n) => n,
++            None => return false,
++        };
++
++        !(offset >= self.cache_size || end > self.cache_size)
++    }
++}
++
++impl VhostUserMasterReqHandler for SlaveReqHandler {
++    fn shmem_map(&self, req: &VhostUserShmemMapMsg, fd: &dyn AsRawFd) -> HandlerResult<u64> {
++        if !self.is_req_valid(req.shm_offset, req.len) {
++            return Err(io::Error::from_raw_os_error(libc::EINVAL));
++        }
++
++        let addr = self.mmap_cache_addr + req.shm_offset;
++        let ret = unsafe {
++            libc::mmap(
++                addr as *mut libc::c_void,
++                req.len as usize,
++                req.flags.bits() as i32,
++                // https://bugzilla.kernel.org/show_bug.cgi?id=217238
++                if req.flags.bits() as i32 & libc::PROT_WRITE != 0 {
++                    libc::MAP_SHARED
++                } else {
++                    libc::MAP_PRIVATE
++                } | libc::MAP_FIXED,
++                fd.as_raw_fd(),
++                req.fd_offset as libc::off_t,
++            )
++        };
++
++        if ret == libc::MAP_FAILED {
++            return Err(io::Error::last_os_error());
++        }
++
++        Ok(0)
++    }
++
++    fn shmem_unmap(&self, req: &VhostUserShmemUnmapMsg) -> HandlerResult<u64> {
++        if !self.is_req_valid(req.shm_offset, req.len) {
++            return Err(io::Error::from_raw_os_error(libc::EINVAL));
++        }
++
++        let addr = self.mmap_cache_addr + req.shm_offset;
++        let ret = unsafe {
++            libc::mmap(
++                addr as *mut libc::c_void,
++                req.len as usize,
++                libc::PROT_NONE,
++                libc::MAP_ANONYMOUS | libc::MAP_PRIVATE | libc::MAP_FIXED,
++                -1,
++                0,
++            )
++        };
++        if ret == libc::MAP_FAILED {
++            return Err(io::Error::last_os_error());
++        }
++
++        Ok(0)
++    }
++}
++
++#[derive(Default)]
++#[repr(C, packed)]
++pub struct VirtioGpuConfig {
++    pub events_read: u32,
++    pub events_clear: u32,
++    pub num_scanouts: u32,
++    pub num_capsets: u32,
++}
++
++pub struct Gpu {
++    common: VirtioCommon,
++    vu_common: VhostUserCommon,
++    id: String,
++    // Hold ownership of the memory that is allocated for the device
++    // which will be automatically dropped when the device is dropped
++    cache: Option<(VirtioSharedMemoryList, MmapRegion)>,
++    slave_req_support: bool,
++    seccomp_action: SeccompAction,
++    guest_memory: Option<GuestMemoryAtomic<GuestMemoryMmap>>,
++    epoll_thread: Option<thread::JoinHandle<()>>,
++    exit_evt: EventFd,
++    iommu: bool,
++}
++
++impl Gpu {
++    /// Create a new virtio-gpu device.
++    pub fn new(
++        id: String,
++        path: &str,
++        cache: Option<(VirtioSharedMemoryList, MmapRegion)>,
++        seccomp_action: SeccompAction,
++        exit_evt: EventFd,
++        iommu: bool,
++    ) -> Result<Gpu> {
++        // Connect to the vhost-user socket.
++        let mut vu = VhostUserHandle::connect_vhost_user(false, path, NUM_QUEUES as u64, false)?;
++
++        let avail_features = 1 << VIRTIO_F_VERSION_1
++            | 1 << VIRTIO_GPU_F_VIRGL
++            | 1 << VIRTIO_GPU_F_RESOURCE_UUID
++            | 1 << VIRTIO_GPU_F_RESOURCE_BLOB
++            | 1 << VIRTIO_GPU_F_CONTEXT_INIT
++            | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
++
++        let avail_protocol_features =
++            VhostUserProtocolFeatures::CONFIG | VhostUserProtocolFeatures::SLAVE_REQ;
++        // The SHARED_MEMORY_REGIONS protocol feature is a way for the backend to indicate
++        // that it supports the GET_SHARED_MEMORY_REGIONS request.  Since we don't use that
++        // request, we don't ack SHARED_MEMORY_REGIONS.
++
++        let (acked_features, acked_protocol_features) =
++            vu.negotiate_features_vhost_user(avail_features, avail_protocol_features)?;
++
++        Ok(Gpu {
++            common: VirtioCommon {
++                device_type: VirtioDeviceType::Gpu as u32,
++                avail_features: acked_features,
++                // If part of the available features that have been acked, the
++                // PROTOCOL_FEATURES bit must be already set through the VIRTIO
++                // acked features as we know the guest would never ack it, this
++                // the feature would be lost.
++                acked_features: acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits(),
++                paused_sync: Some(Arc::new(Barrier::new(NUM_QUEUES as usize))),
++                queue_sizes: QUEUE_SIZES.to_vec(),
++                min_queues: NUM_QUEUES,
++                ..Default::default()
++            },
++            vu_common: VhostUserCommon {
++                vu: Some(Arc::new(Mutex::new(vu))),
++                acked_protocol_features,
++                socket_path: path.to_string(),
++                vu_num_queues: NUM_QUEUES as usize,
++                ..Default::default()
++            },
++            id,
++            cache,
++            slave_req_support: acked_protocol_features
++                & VhostUserProtocolFeatures::SLAVE_REQ.bits()
++                != 0,
++            seccomp_action,
++            guest_memory: None,
++            epoll_thread: None,
++            exit_evt,
++            iommu,
++        })
++    }
++}
++
++impl Drop for Gpu {
++    fn drop(&mut self) {
++        if let Some(kill_evt) = self.common.kill_evt.take() {
++            // Ignore the result because there is nothing we can do about it.
++            let _ = kill_evt.write(1);
++        }
++    }
++}
++
++impl VirtioDevice for Gpu {
++    fn device_type(&self) -> u32 {
++        self.common.device_type
++    }
++
++    fn queue_max_sizes(&self) -> &[u16] {
++        &self.common.queue_sizes
++    }
++
++    fn features(&self) -> u64 {
++        let mut features = self.common.avail_features;
++        if self.iommu {
++            features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM;
++        }
++        features
++    }
++
++    fn ack_features(&mut self, value: u64) {
++        self.common.ack_features(value)
++    }
++
++    fn read_config(&self, offset: u64, mut data: &mut [u8]) {
++        if let Some(vu) = &self.vu_common.vu {
++            if let Err(e) = vu
++                .lock()
++                .unwrap()
++                .socket_handle()
++                .get_config(
++                    offset as u32,
++                    data.len() as u32,
++                    VhostUserConfigFlags::WRITABLE,
++                    data,
++                )
++                .map_err(|e| format!("{:?}", e))
++                .and_then(|(_, config)| data.write_all(&config).map_err(|e| format!("{:?}", e)))
++            {
++                error!("Failed getting vhost-user-gpu configuration: {:?}", e);
++            }
++        }
++    }
++
++    fn activate(
++        &mut self,
++        mem: GuestMemoryAtomic<GuestMemoryMmap>,
++        interrupt_cb: Arc<dyn VirtioInterrupt>,
++        queues: Vec<(usize, Queue, EventFd)>,
++    ) -> ActivateResult {
++        self.common.activate(&queues, &interrupt_cb)?;
++        self.guest_memory = Some(mem.clone());
++
++        // Initialize slave communication.
++        let slave_req_handler = if self.slave_req_support {
++            if let Some(cache) = self.cache.as_ref() {
++                let vu_master_req_handler = Arc::new(SlaveReqHandler {
++                    cache_size: cache.0.len,
++                    mmap_cache_addr: cache.0.host_addr,
++                });
++
++                let mut req_handler =
++                    MasterReqHandler::new(vu_master_req_handler).map_err(|e| {
++                        ActivateError::VhostUserGpuSetup(Error::MasterReqHandlerCreation(e))
++                    })?;
++
++                if self.vu_common.acked_protocol_features
++                    & VhostUserProtocolFeatures::REPLY_ACK.bits()
++                    != 0
++                {
++                    req_handler.set_reply_ack_flag(true);
++                }
++
++                Some(req_handler)
++            } else {
++                None
++            }
++        } else {
++            None
++        };
++
++        // Run a dedicated thread for handling potential reconnections with
++        // the backend.
++        let (kill_evt, pause_evt) = self.common.dup_eventfds();
++
++        let mut handler = self.vu_common.activate(
++            mem,
++            queues,
++            interrupt_cb,
++            self.common.acked_features,
++            slave_req_handler,
++            kill_evt,
++            pause_evt,
++        )?;
++
++        let paused = self.common.paused.clone();
++        let paused_sync = self.common.paused_sync.clone();
++
++        let mut epoll_threads = Vec::new();
++        spawn_virtio_thread(
++            &self.id,
++            &self.seccomp_action,
++            Thread::VirtioVhostGpu,
++            &mut epoll_threads,
++            &self.exit_evt,
++            move || handler.run(paused, paused_sync.unwrap()),
++        )?;
++        self.epoll_thread = Some(epoll_threads.remove(0));
++
++        event!("virtio-device", "activated", "id", &self.id);
++        Ok(())
++    }
++
++    fn reset(&mut self) -> Option<Arc<dyn VirtioInterrupt>> {
++        // We first must resume the virtio thread if it was paused.
++        if self.common.pause_evt.take().is_some() {
++            self.common.resume().ok()?;
++        }
++
++        if let Some(vu) = &self.vu_common.vu {
++            if let Err(e) = vu.lock().unwrap().reset_vhost_user() {
++                error!("Failed to reset vhost-user daemon: {:?}", e);
++                return None;
++            }
++        }
++
++        if let Some(kill_evt) = self.common.kill_evt.take() {
++            // Ignore the result because there is nothing we can do about it.
++            let _ = kill_evt.write(1);
++        }
++
++        event!("virtio-device", "reset", "id", &self.id);
++
++        // Return the interrupt
++        Some(self.common.interrupt_cb.take().unwrap())
++    }
++
++    fn shutdown(&mut self) {
++        self.vu_common.shutdown()
++    }
++
++    fn get_shm_regions(&self) -> Option<VirtioSharedMemoryList> {
++        // It would be possible to get the size of the region from the
++        // backend over vhost-user, but since we need to know the size
++        // up front in cloud-hypervisor to construct Self, it wouldn't
++        // help.  The user is thereforce responsible for configuring
++        // the correct region size in VM configuration.
++        self.cache.as_ref().map(|cache| cache.0.clone())
++    }
++
++    fn set_shm_regions(
++        &mut self,
++        shm_regions: VirtioSharedMemoryList,
++    ) -> std::result::Result<(), crate::Error> {
++        if let Some(cache) = self.cache.as_mut() {
++            cache.0 = shm_regions;
++            Ok(())
++        } else {
++            Err(crate::Error::SetShmRegionsNotSupported)
++        }
++    }
++
++    fn add_memory_region(
++        &mut self,
++        region: &Arc<GuestRegionMmap>,
++    ) -> std::result::Result<(), crate::Error> {
++        self.vu_common.add_memory_region(&self.guest_memory, region)
++    }
++
++    fn userspace_mappings(&self) -> Vec<UserspaceMapping> {
++        let mut mappings = Vec::new();
++        if let Some(cache) = self.cache.as_ref() {
++            mappings.push(UserspaceMapping {
++                host_addr: cache.0.host_addr,
++                mem_slot: cache.0.mem_slot,
++                addr: cache.0.addr,
++                len: cache.0.len,
++                mergeable: false,
++            })
++        }
++
++        mappings
++    }
++}
++
++impl Pausable for Gpu {
++    fn pause(&mut self) -> result::Result<(), MigratableError> {
++        self.vu_common.pause()?;
++        self.common.pause()
++    }
++
++    fn resume(&mut self) -> result::Result<(), MigratableError> {
++        self.common.resume()?;
++
++        if let Some(epoll_thread) = &self.epoll_thread {
++            epoll_thread.thread().unpark();
++        }
++
++        self.vu_common.resume()
++    }
++}
+diff --git a/virtio-devices/src/vhost_user/mod.rs b/virtio-devices/src/vhost_user/mod.rs
+index 2dee7324..d1b1c4e6 100644
+--- a/virtio-devices/src/vhost_user/mod.rs
++++ b/virtio-devices/src/vhost_user/mod.rs
+@@ -31,11 +31,13 @@ use vu_common_ctrl::VhostUserHandle;
+ 
+ pub mod blk;
+ pub mod fs;
++pub mod gpu;
+ pub mod net;
+ pub mod vu_common_ctrl;
+ 
+ pub use self::blk::Blk;
+ pub use self::fs::*;
++pub use self::gpu::*;
+ pub use self::net::Net;
+ pub use self::vu_common_ctrl::VhostUserConfig;
+ 
+diff --git a/vmm/Cargo.toml b/vmm/Cargo.toml
+index e256c90f..b3a374ab 100644
+--- a/vmm/Cargo.toml
++++ b/vmm/Cargo.toml
+@@ -52,6 +52,7 @@ versionize = "0.1.10"
+ versionize_derive = "0.1.4"
+ vfio-ioctls = { git = "https://github.com/rust-vmm/vfio", branch = "main", default-features = false }
+ vfio_user = { git = "https://github.com/rust-vmm/vfio-user", branch = "main" }
++virtio-bindings = "0.2.0"
+ virtio-devices = { path = "../virtio-devices" }
+ virtio-queue = "0.9.0"
+ vm-allocator = { path = "../vm-allocator" }
+diff --git a/vmm/src/api/mod.rs b/vmm/src/api/mod.rs
+index 8aac5d3c..041a6c41 100644
+--- a/vmm/src/api/mod.rs
++++ b/vmm/src/api/mod.rs
+@@ -38,8 +38,8 @@ pub use self::http::start_http_fd_thread;
+ pub use self::http::start_http_path_thread;
+ 
+ use crate::config::{
+-    DeviceConfig, DiskConfig, FsConfig, NetConfig, PmemConfig, RestoreConfig, UserDeviceConfig,
+-    VdpaConfig, VmConfig, VsockConfig,
++    DeviceConfig, DiskConfig, FsConfig, GpuConfig, NetConfig, PmemConfig, RestoreConfig,
++    UserDeviceConfig, VdpaConfig, VmConfig, VsockConfig,
+ };
+ use crate::device_tree::DeviceTree;
+ use crate::vm::{Error as VmError, VmState};
+@@ -135,6 +135,9 @@ pub enum ApiError {
+     /// The fs could not be added to the VM.
+     VmAddFs(VmError),
+ 
++    /// The gpu could not be added to the VM.
++    VmAddGpu(VmError),
++
+     /// The pmem device could not be added to the VM.
+     VmAddPmem(VmError),
+ 
+@@ -305,6 +308,9 @@ pub enum ApiRequest {
+     /// Add a fs to the VM.
+     VmAddFs(Arc<FsConfig>, Sender<ApiResponse>),
+ 
++    /// Add a gpu to the VM.
++    VmAddGpu(Arc<GpuConfig>, Sender<ApiResponse>),
++
+     /// Add a pmem device to the VM.
+     VmAddPmem(Arc<PmemConfig>, Sender<ApiResponse>),
+ 
+diff --git a/vmm/src/config.rs b/vmm/src/config.rs
+index a6836fc6..4fb0f543 100644
+--- a/vmm/src/config.rs
++++ b/vmm/src/config.rs
+@@ -26,6 +26,8 @@ pub enum Error {
+     ParseFsTagMissing,
+     /// Filesystem socket is missing
+     ParseFsSockMissing,
++    /// GPU socket is missing
++    ParseGpuSockMissing,
+     /// Missing persistent memory file parameter.
+     ParsePmemFileMissing,
+     /// Missing vsock socket path parameter.
+@@ -54,6 +56,8 @@ pub enum Error {
+     ParseBalloon(OptionParserError),
+     /// Error parsing filesystem parameters
+     ParseFileSystem(OptionParserError),
++    /// Error parsing GPU parameters
++    ParseGpu(OptionParserError),
+     /// Error parsing persistent memory parameters
+     ParsePersistentMemory(OptionParserError),
+     /// Failed parsing console
+@@ -304,6 +308,8 @@ impl fmt::Display for Error {
+             ParseFileSystem(o) => write!(f, "Error parsing --fs: {o}"),
+             ParseFsSockMissing => write!(f, "Error parsing --fs: socket missing"),
+             ParseFsTagMissing => write!(f, "Error parsing --fs: tag missing"),
++            ParseGpu(o) => write!(f, "Error parsing --gpu: {o}"),
++            ParseGpuSockMissing => write!(f, "Error parsing --gpu: socket missing"),
+             ParsePersistentMemory(o) => write!(f, "Error parsing --pmem: {o}"),
+             ParsePmemFileMissing => write!(f, "Error parsing --pmem: file missing"),
+             ParseVsock(o) => write!(f, "Error parsing --vsock: {o}"),
+@@ -368,6 +374,7 @@ pub struct VmParams<'a> {
+     pub rng: &'a str,
+     pub balloon: Option<&'a str>,
+     pub fs: Option<Vec<&'a str>>,
++    pub gpu: Option<Vec<&'a str>>,
+     pub pmem: Option<Vec<&'a str>>,
+     pub serial: &'a str,
+     pub console: &'a str,
+@@ -1272,6 +1279,56 @@ impl FsConfig {
+     }
+ }
+ 
++impl GpuConfig {
++    pub fn parse(gpu: &str) -> Result<Self> {
++        let mut parser = OptionParser::new();
++        parser
++            .add("socket")
++            .add("cache_size")
++            .add("id")
++            .add("pci_segment");
++        parser.parse(gpu).map_err(Error::ParseGpu)?;
++
++        let socket = PathBuf::from(parser.get("socket").ok_or(Error::ParseGpuSockMissing)?);
++        let cache_size = parser
++            .convert::<ByteSized>("cache_size")
++            .map_err(Error::ParseGpu)?
++            .unwrap_or_else(|| ByteSized(default_gpuconfig_cache_size()))
++            .0;
++        let id = parser.get("id");
++
++        let pci_segment = parser
++            .convert("pci_segment")
++            .map_err(Error::ParseGpu)?
++            .unwrap_or_default();
++
++        Ok(GpuConfig {
++            socket,
++            cache_size,
++            id,
++            pci_segment,
++        })
++    }
++
++    pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> {
++        if let Some(platform_config) = vm_config.platform.as_ref() {
++            if self.pci_segment >= platform_config.num_pci_segments {
++                return Err(ValidationError::InvalidPciSegment(self.pci_segment));
++            }
++
++            if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() {
++                if iommu_segments.contains(&self.pci_segment) {
++                    return Err(ValidationError::IommuNotSupportedOnSegment(
++                        self.pci_segment,
++                    ));
++                }
++            }
++        }
++
++        Ok(())
++    }
++}
++
+ impl PmemConfig {
+     pub fn parse(pmem: &str) -> Result<Self> {
+         let mut parser = OptionParser::new();
+@@ -1805,6 +1862,17 @@ impl VmConfig {
+             }
+         }
+ 
++        if let Some(gpus) = &self.gpu {
++            if !gpus.is_empty() && !self.memory.shared {
++                return Err(ValidationError::VhostUserRequiresSharedMemory);
++            }
++            for gpu in gpus {
++                gpu.validate(self)?;
++
++                Self::validate_identifier(&mut id_list, &gpu.id)?;
++            }
++        }
++
+         if let Some(pmems) = &self.pmem {
+             for pmem in pmems {
+                 pmem.validate(self)?;
+@@ -1990,6 +2058,15 @@ impl VmConfig {
+             fs = Some(fs_config_list);
+         }
+ 
++        let mut gpu: Option<Vec<GpuConfig>> = None;
++        if let Some(gpu_list) = &vm_params.gpu {
++            let mut gpu_config_list = Vec::new();
++            for item in gpu_list.iter() {
++                gpu_config_list.push(GpuConfig::parse(item)?);
++            }
++            gpu = Some(gpu_config_list);
++        }
++
+         let mut pmem: Option<Vec<PmemConfig>> = None;
+         if let Some(pmem_list) = &vm_params.pmem {
+             let mut pmem_config_list = Vec::new();
+@@ -2096,6 +2173,7 @@ impl VmConfig {
+             rng,
+             balloon,
+             fs,
++            gpu,
+             pmem,
+             serial,
+             console,
+@@ -2150,6 +2228,13 @@ impl VmConfig {
+             removed |= fs.len() != len;
+         }
+ 
++        // Remove if gpu device
++        if let Some(gpu) = self.gpu.as_mut() {
++            let len = gpu.len();
++            gpu.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id));
++            removed |= gpu.len() != len;
++        }
++
+         // Remove if net device
+         if let Some(net) = self.net.as_mut() {
+             let len = net.len();
+@@ -2219,6 +2304,7 @@ impl Clone for VmConfig {
+             rng: self.rng.clone(),
+             balloon: self.balloon.clone(),
+             fs: self.fs.clone(),
++            gpu: self.gpu.clone(),
+             pmem: self.pmem.clone(),
+             serial: self.serial.clone(),
+             console: self.console.clone(),
+@@ -2613,6 +2699,21 @@ mod tests {
+         Ok(())
+     }
+ 
++    #[test]
++    fn test_parse_gpu() -> Result<()> {
++        // "socket" must be supplied
++        assert!(GpuConfig::parse("").is_err());
++        assert_eq!(
++            GpuConfig::parse("socket=/tmp/sock")?,
++            GpuConfig {
++                socket: PathBuf::from("/tmp/sock"),
++                ..Default::default()
++            }
++        );
++
++        Ok(())
++    }
++
+     #[test]
+     fn test_pmem_parsing() -> Result<()> {
+         // Must always give a file and size
+@@ -2847,6 +2948,7 @@ mod tests {
+             },
+             balloon: None,
+             fs: None,
++            gpu: None,
+             pmem: None,
+             serial: ConsoleConfig {
+                 file: None,
+@@ -3012,6 +3114,15 @@ mod tests {
+             Err(ValidationError::VhostUserRequiresSharedMemory)
+         );
+ 
++        let mut invalid_config = valid_config.clone();
++        invalid_config.gpu = Some(vec![GpuConfig {
++            ..Default::default()
++        }]);
++        assert_eq!(
++            invalid_config.validate(),
++            Err(ValidationError::VhostUserRequiresSharedMemory)
++        );
++
+         let mut still_valid_config = valid_config.clone();
+         still_valid_config.memory.shared = true;
+         assert!(still_valid_config.validate().is_ok());
+diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs
+index 1f53390c..2d974480 100644
+--- a/vmm/src/device_manager.rs
++++ b/vmm/src/device_manager.rs
+@@ -10,8 +10,8 @@
+ //
+ 
+ use crate::config::{
+-    ConsoleOutputMode, DeviceConfig, DiskConfig, FsConfig, NetConfig, PmemConfig, UserDeviceConfig,
+-    VdpaConfig, VhostMode, VmConfig, VsockConfig,
++    ConsoleOutputMode, DeviceConfig, DiskConfig, FsConfig, GpuConfig, NetConfig, PmemConfig,
++    UserDeviceConfig, VdpaConfig, VhostMode, VmConfig, VsockConfig,
+ };
+ use crate::cpu::{CpuManager, CPU_MANAGER_ACPI_SIZE};
+ use crate::device_tree::{DeviceNode, DeviceTree};
+@@ -66,6 +66,7 @@ use serde::{Deserialize, Serialize};
+ use std::collections::{BTreeSet, HashMap};
+ use std::fs::{read_link, File, OpenOptions};
+ use std::io::{self, stdout, Seek, SeekFrom};
++use std::iter::once;
+ use std::mem::zeroed;
+ use std::num::Wrapping;
+ use std::os::unix::fs::OpenOptionsExt;
+@@ -76,11 +77,13 @@ use std::sync::{Arc, Mutex};
+ use std::time::Instant;
+ use tracer::trace_scoped;
+ use vfio_ioctls::{VfioContainer, VfioDevice, VfioDeviceFd};
++use virtio_bindings::virtio_gpu::virtio_gpu_shm_id_VIRTIO_GPU_SHM_ID_HOST_VISIBLE as VIRTIO_GPU_SHM_ID_HOST_VISIBLE;
+ use virtio_devices::transport::VirtioTransport;
+ use virtio_devices::transport::{VirtioPciDevice, VirtioPciDeviceActivator};
+ use virtio_devices::vhost_user::VhostUserConfig;
+ use virtio_devices::{
+     AccessPlatformMapping, ActivateError, VdpaDmaMapping, VirtioMemMappingSource,
++    VirtioSharedMemory, VirtioSharedMemoryList,
+ };
+ use virtio_devices::{Endpoint, IommuMapping};
+ use vm_allocator::{AddressAllocator, SystemAllocator};
+@@ -122,6 +125,7 @@ const PVPANIC_DEVICE_NAME: &str = "__pvpanic";
+ // identifiers if the user doesn't give one
+ const DISK_DEVICE_NAME_PREFIX: &str = "_disk";
+ const FS_DEVICE_NAME_PREFIX: &str = "_fs";
++const GPU_DEVICE_NAME_PREFIX: &str = "_gpu";
+ const NET_DEVICE_NAME_PREFIX: &str = "_net";
+ const PMEM_DEVICE_NAME_PREFIX: &str = "_pmem";
+ const VDPA_DEVICE_NAME_PREFIX: &str = "_vdpa";
+@@ -158,9 +162,15 @@ pub enum DeviceManagerError {
+     /// Cannot create virtio-fs device
+     CreateVirtioFs(virtio_devices::vhost_user::Error),
+ 
++    /// Cannot create virtio-gpu device
++    CreateVirtioGpu(virtio_devices::vhost_user::Error),
++
+     /// Virtio-fs device was created without a socket.
+     NoVirtioFsSock,
+ 
++    /// Virtio-gpu device was created without a socket.
++    NoVirtioGpuSock,
++
+     /// Cannot create vhost-user-blk device
+     CreateVhostUserBlk(virtio_devices::vhost_user::Error),
+ 
+@@ -245,6 +255,9 @@ pub enum DeviceManagerError {
+     /// Cannot find a memory range for virtio-fs
+     FsRangeAllocation,
+ 
++    /// Cannot find a memory range for virtio-gpu
++    GpuRangeAllocation,
++
+     /// Error creating serial output file
+     SerialOutputFileOpen(io::Error),
+ 
+@@ -2140,6 +2153,9 @@ impl DeviceManager {
+         // Add virtio-fs if required
+         devices.append(&mut self.make_virtio_fs_devices()?);
+ 
++        // Add virtio-gpu if required
++        devices.append(&mut self.make_virtio_gpu_devices()?);
++
+         // Add virtio-pmem if required
+         devices.append(&mut self.make_virtio_pmem_devices()?);
+ 
+@@ -2655,6 +2671,118 @@ impl DeviceManager {
+         Ok(devices)
+     }
+ 
++    fn make_virtio_gpu_device(
++        &mut self,
++        gpu_cfg: &mut GpuConfig,
++    ) -> DeviceManagerResult<MetaVirtioDevice> {
++        let id = if let Some(id) = &gpu_cfg.id {
++            id.clone()
++        } else {
++            let id = self.next_device_name(GPU_DEVICE_NAME_PREFIX)?;
++            gpu_cfg.id = Some(id.clone());
++            id
++        };
++
++        info!("Creating virtio-gpu device: {:?}", gpu_cfg);
++
++        let mut node = device_node!(id);
++
++        if let Some(gpu_socket) = gpu_cfg.socket.to_str() {
++            let cache_size = gpu_cfg.cache_size;
++            // In crosvm, the 8 GiB bar is 8 GiB-aligned.
++            let cache_base = self.pci_segments[gpu_cfg.pci_segment as usize]
++                .allocator
++                .lock()
++                .unwrap()
++                .allocate(None, cache_size as GuestUsize, Some(cache_size))
++                .ok_or(DeviceManagerError::GpuRangeAllocation)?
++                .raw_value();
++
++            // Update the node with correct resource information.
++            node.resources.push(Resource::MmioAddressRange {
++                base: cache_base,
++                size: cache_size,
++            });
++
++            let mmap_region = MmapRegion::build(
++                None,
++                cache_size as usize,
++                libc::PROT_NONE,
++                libc::MAP_ANONYMOUS | libc::MAP_PRIVATE,
++            )
++            .map_err(DeviceManagerError::NewMmapRegion)?;
++            let host_addr: u64 = mmap_region.as_ptr() as u64;
++
++            let mem_slot = self
++                .memory_manager
++                .lock()
++                .unwrap()
++                .create_userspace_mapping(cache_base, cache_size, host_addr, false, false, false)
++                .map_err(DeviceManagerError::MemoryManager)?;
++
++            let region_list = once((
++                VIRTIO_GPU_SHM_ID_HOST_VISIBLE as u8,
++                VirtioSharedMemory {
++                    offset: 0,
++                    len: cache_size,
++                },
++            ))
++            .collect();
++
++            let cache = Some((
++                VirtioSharedMemoryList {
++                    host_addr,
++                    mem_slot,
++                    addr: GuestAddress(cache_base),
++                    len: cache_size as GuestUsize,
++                    region_list,
++                },
++                mmap_region,
++            ));
++
++            let virtio_gpu_device = Arc::new(Mutex::new(
++                virtio_devices::vhost_user::Gpu::new(
++                    id.clone(),
++                    gpu_socket,
++                    cache,
++                    self.seccomp_action.clone(),
++                    self.exit_evt
++                        .try_clone()
++                        .map_err(DeviceManagerError::EventFd)?,
++                    self.force_iommu,
++                )
++                .map_err(DeviceManagerError::CreateVirtioGpu)?,
++            ));
++
++            self.device_tree.lock().unwrap().insert(id.clone(), node);
++
++            Ok(MetaVirtioDevice {
++                virtio_device: Arc::clone(&virtio_gpu_device)
++                    as Arc<Mutex<dyn virtio_devices::VirtioDevice>>,
++                iommu: false,
++                id,
++                pci_segment: gpu_cfg.pci_segment,
++                dma_handler: None,
++            })
++        } else {
++            Err(DeviceManagerError::NoVirtioGpuSock)
++        }
++    }
++
++    fn make_virtio_gpu_devices(&mut self) -> DeviceManagerResult<Vec<MetaVirtioDevice>> {
++        let mut devices = Vec::new();
++
++        let mut gpu_devices = self.config.lock().unwrap().gpu.clone();
++        if let Some(gpu_list_cfg) = &mut gpu_devices {
++            for gpu_cfg in gpu_list_cfg.iter_mut() {
++                devices.push(self.make_virtio_gpu_device(gpu_cfg)?);
++            }
++        }
++        self.config.lock().unwrap().gpu = gpu_devices;
++
++        Ok(devices)
++    }
++
+     fn make_virtio_pmem_device(
+         &mut self,
+         pmem_cfg: &mut PmemConfig,
+@@ -3914,6 +4042,7 @@ impl DeviceManager {
+                 | VirtioDeviceType::Block
+                 | VirtioDeviceType::Pmem
+                 | VirtioDeviceType::Fs
++                | VirtioDeviceType::Gpu
+                 | VirtioDeviceType::Vsock => {}
+                 _ => return Err(DeviceManagerError::RemovalNotAllowed(device_type)),
+             }
+@@ -4182,6 +4311,13 @@ impl DeviceManager {
+         self.hotplug_virtio_pci_device(device)
+     }
+ 
++    pub fn add_gpu(&mut self, gpu_cfg: &mut GpuConfig) -> DeviceManagerResult<PciDeviceInfo> {
++        self.validate_identifier(&gpu_cfg.id)?;
++
++        let device = self.make_virtio_gpu_device(gpu_cfg)?;
++        self.hotplug_virtio_pci_device(device)
++    }
++
+     pub fn add_pmem(&mut self, pmem_cfg: &mut PmemConfig) -> DeviceManagerResult<PciDeviceInfo> {
+         self.validate_identifier(&pmem_cfg.id)?;
+ 
+diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs
+index 02ee347e..5abbdea5 100644
+--- a/vmm/src/lib.rs
++++ b/vmm/src/lib.rs
+@@ -13,8 +13,8 @@ use crate::api::{
+     VmSendMigrationData, VmmPingResponse,
+ };
+ use crate::config::{
+-    add_to_config, DeviceConfig, DiskConfig, FsConfig, NetConfig, PmemConfig, RestoreConfig,
+-    UserDeviceConfig, VdpaConfig, VmConfig, VsockConfig,
++    add_to_config, DeviceConfig, DiskConfig, FsConfig, GpuConfig, NetConfig, PmemConfig,
++    RestoreConfig, UserDeviceConfig, VdpaConfig, VmConfig, VsockConfig,
+ };
+ #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))]
+ use crate::coredump::GuestDebuggable;
+@@ -25,8 +25,6 @@ use crate::migration::{recv_vm_config, recv_vm_state};
+ use crate::seccomp_filters::{get_seccomp_filter, Thread};
+ use crate::vm::{Error as VmError, Vm, VmState};
+ use anyhow::anyhow;
+-#[cfg(feature = "dbus_api")]
+-use api::dbus::{DBusApiOptions, DBusApiShutdownChannels};
+ use libc::{tcsetattr, termios, EFD_NONBLOCK, SIGINT, SIGTERM, TCSANOW};
+ use memory_manager::MemoryManagerSnapshotData;
+ use pci::PciBdf;
+@@ -1147,6 +1145,32 @@ impl Vmm {
+         }
+     }
+ 
++    fn vm_add_gpu(&mut self, gpu_cfg: GpuConfig) -> result::Result<Option<Vec<u8>>, VmError> {
++        self.vm_config.as_ref().ok_or(VmError::VmNotCreated)?;
++
++        {
++            // Validate the configuration change in a cloned configuration
++            let mut config = self.vm_config.as_ref().unwrap().lock().unwrap().clone();
++            add_to_config(&mut config.gpu, gpu_cfg.clone());
++            config.validate().map_err(VmError::ConfigValidation)?;
++        }
++
++        if let Some(ref mut vm) = self.vm {
++            let info = vm.add_gpu(gpu_cfg).map_err(|e| {
++                error!("Error when adding new gpu to the VM: {:?}", e);
++                e
++            })?;
++            serde_json::to_vec(&info)
++                .map(Some)
++                .map_err(VmError::SerializeJson)
++        } else {
++            // Update VmConfig by adding the new device.
++            let mut config = self.vm_config.as_ref().unwrap().lock().unwrap();
++            add_to_config(&mut config.gpu, gpu_cfg);
++            Ok(None)
++        }
++    }
++
+     fn vm_add_pmem(&mut self, pmem_cfg: PmemConfig) -> result::Result<Option<Vec<u8>>, VmError> {
+         self.vm_config.as_ref().ok_or(VmError::VmNotCreated)?;
+ 
+@@ -2080,6 +2104,13 @@ impl Vmm {
+                                         .map(ApiResponsePayload::VmAction);
+                                     sender.send(response).map_err(Error::ApiResponseSend)?;
+                                 }
++                                ApiRequest::VmAddGpu(add_gpu_data, sender) => {
++                                    let response = self
++                                        .vm_add_gpu(add_gpu_data.as_ref().clone())
++                                        .map_err(ApiError::VmAddGpu)
++                                        .map(ApiResponsePayload::VmAction);
++                                    sender.send(response).map_err(Error::ApiResponseSend)?;
++                                }
+                                 ApiRequest::VmAddPmem(add_pmem_data, sender) => {
+                                     let response = self
+                                         .vm_add_pmem(add_pmem_data.as_ref().clone())
+@@ -2245,6 +2276,7 @@ mod unit_tests {
+             },
+             balloon: None,
+             fs: None,
++            gpu: None,
+             pmem: None,
+             serial: ConsoleConfig {
+                 file: None,
+@@ -2472,6 +2504,54 @@ mod unit_tests {
+         );
+     }
+ 
++    #[test]
++    fn test_vmm_vm_cold_add_gpu() {
++        let mut vmm = create_dummy_vmm();
++        let gpu_config = GpuConfig::parse("socket=/tmp/sock").unwrap();
++
++        assert!(matches!(
++            vmm.vm_add_gpu(gpu_config.clone()),
++            Err(VmError::VmNotCreated)
++        ));
++
++        let _ = vmm.vm_create(create_dummy_vm_config());
++        assert!(vmm
++            .vm_config
++            .as_ref()
++            .unwrap()
++            .lock()
++            .unwrap()
++            .gpu
++            .is_none());
++
++        let result = vmm.vm_add_gpu(gpu_config.clone());
++        assert!(result.is_ok());
++        assert!(result.unwrap().is_none());
++        assert_eq!(
++            vmm.vm_config
++                .as_ref()
++                .unwrap()
++                .lock()
++                .unwrap()
++                .gpu
++                .clone()
++                .unwrap()
++                .len(),
++            1
++        );
++        assert_eq!(
++            vmm.vm_config
++                .as_ref()
++                .unwrap()
++                .lock()
++                .unwrap()
++                .gpu
++                .clone()
++                .unwrap()[0],
++            gpu_config
++        );
++    }
++
+     #[test]
+     fn test_vmm_vm_cold_add_pmem() {
+         let mut vmm = create_dummy_vmm();
+diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs
+index 2b829df5..ff5a6905 100644
+--- a/vmm/src/vm.rs
++++ b/vmm/src/vm.rs
+@@ -12,8 +12,8 @@
+ //
+ 
+ use crate::config::{
+-    add_to_config, DeviceConfig, DiskConfig, FsConfig, HotplugMethod, NetConfig, PmemConfig,
+-    UserDeviceConfig, ValidationError, VdpaConfig, VmConfig, VsockConfig,
++    add_to_config, DeviceConfig, DiskConfig, FsConfig, GpuConfig, HotplugMethod, NetConfig,
++    PmemConfig, UserDeviceConfig, ValidationError, VdpaConfig, VmConfig, VsockConfig,
+ };
+ use crate::config::{NumaConfig, PayloadConfig};
+ #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))]
+@@ -1488,6 +1488,30 @@ impl Vm {
+         Ok(pci_device_info)
+     }
+ 
++    pub fn add_gpu(&mut self, mut gpu_cfg: GpuConfig) -> Result<PciDeviceInfo> {
++        let pci_device_info = self
++            .device_manager
++            .lock()
++            .unwrap()
++            .add_gpu(&mut gpu_cfg)
++            .map_err(Error::DeviceManager)?;
++
++        // Update VmConfig by adding the new device. This is important to
++        // ensure the device would be created in case of a reboot.
++        {
++            let mut config = self.config.lock().unwrap();
++            add_to_config(&mut config.gpu, gpu_cfg);
++        }
++
++        self.device_manager
++            .lock()
++            .unwrap()
++            .notify_hotplug(AcpiNotificationFlags::PCI_DEVICES_CHANGED)
++            .map_err(Error::DeviceManager)?;
++
++        Ok(pci_device_info)
++    }
++
+     pub fn add_pmem(&mut self, mut pmem_cfg: PmemConfig) -> Result<PciDeviceInfo> {
+         let pci_device_info = self
+             .device_manager
+diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs
+index 67ba0e12..79496a94 100644
+--- a/vmm/src/vm_config.rs
++++ b/vmm/src/vm_config.rs
+@@ -419,6 +419,32 @@ impl Default for FsConfig {
+     }
+ }
+ 
++#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
++pub struct GpuConfig {
++    pub socket: PathBuf,
++    #[serde(default = "default_gpuconfig_cache_size")]
++    pub cache_size: u64,
++    #[serde(default)]
++    pub id: Option<String>,
++    #[serde(default)]
++    pub pci_segment: u16,
++}
++
++impl Default for GpuConfig {
++    fn default() -> Self {
++        Self {
++            socket: PathBuf::new(),
++            cache_size: default_gpuconfig_cache_size(),
++            id: None,
++            pci_segment: 0,
++        }
++    }
++}
++
++pub fn default_gpuconfig_cache_size() -> u64 {
++    1 << 33
++}
++
+ #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
+ pub struct PmemConfig {
+     pub file: PathBuf,
+@@ -584,6 +610,7 @@ pub struct VmConfig {
+     pub rng: RngConfig,
+     pub balloon: Option<BalloonConfig>,
+     pub fs: Option<Vec<FsConfig>>,
++    pub gpu: Option<Vec<GpuConfig>>,
+     pub pmem: Option<Vec<PmemConfig>>,
+     #[serde(default = "default_serial")]
+     pub serial: ConsoleConfig,
+-- 
+2.42.0
+
diff --git a/pkgs/applications/virtualization/cloud-hypervisor/Cargo.lock b/pkgs/applications/virtualization/cloud-hypervisor/Cargo.lock
index 346e3f45f63..e1a1ad02c48 100644
--- a/pkgs/applications/virtualization/cloud-hypervisor/Cargo.lock
+++ b/pkgs/applications/virtualization/cloud-hypervisor/Cargo.lock
@@ -2160,8 +2160,6 @@ dependencies = [
 [[package]]
 name = "vhost"
 version = "0.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61957aeb36daf0b00b87fff9c10dd28a161bd35ab157553d340d183b3d8756e6"
 dependencies = [
  "bitflags 1.3.2",
  "libc",
@@ -2172,8 +2170,6 @@ dependencies = [
 [[package]]
 name = "vhost-user-backend"
 version = "0.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ab069cdedaf18a0673766eb0a07a0f4ee3ed1b8e17fbfe4aafe5b988e2de1d01"
 dependencies = [
  "libc",
  "log",
@@ -2374,6 +2370,7 @@ dependencies = [
  "versionize_derive",
  "vfio-ioctls",
  "vfio_user",
+ "virtio-bindings",
  "virtio-devices",
  "virtio-queue",
  "vm-allocator",
diff --git a/pkgs/applications/virtualization/cloud-hypervisor/default.nix b/pkgs/applications/virtualization/cloud-hypervisor/default.nix
index d7182b81310..9831aced69b 100644
--- a/pkgs/applications/virtualization/cloud-hypervisor/default.nix
+++ b/pkgs/applications/virtualization/cloud-hypervisor/default.nix
@@ -28,6 +28,39 @@ rustPlatform.buildRustPackage rec {
 
   separateDebugInfo = true;
 
+  vhost = fetchFromGitHub {
+    name = "vhost";
+    owner = "rust-vmm";
+    repo = "vhost";
+    rev = "vhost-user-backend-v0.10.1";
+    hash = "sha256-pq545s7sqE0GFFkEkAvKwFKLuRArNThmRFqEYS3nNVo=";
+  };
+
+  postUnpack = ''
+    unpackFile ${vhost}
+    chmod -R +w vhost
+  '';
+
+  cargoPatches = [
+    ./0001-build-use-local-vhost.patch
+    ./0002-virtio-devices-add-a-GPU-device.patch
+  ];
+
+  vhostPatches = [
+    vhost/0001-vhost-fix-receiving-reply-payloads.patch
+    vhost/0002-vhost_user-add-shared-memory-region-support.patch
+    vhost/0003-vhost-user-add-protocol-flag-for-shmem.patch
+  ];
+
+  postPatch = ''
+    pushd ../vhost
+    for patch in $vhostPatches; do
+        echo applying patch $patch
+        patch -p1 < $patch
+    done
+    popd
+  '';
+
   nativeBuildInputs = [ pkg-config ];
   buildInputs = [ openssl ] ++ lib.optional stdenv.isAarch64 dtc;
 
diff --git a/pkgs/applications/virtualization/cloud-hypervisor/vhost/0001-vhost-fix-receiving-reply-payloads.patch b/pkgs/applications/virtualization/cloud-hypervisor/vhost/0001-vhost-fix-receiving-reply-payloads.patch
new file mode 100644
index 00000000000..1b013b922d6
--- /dev/null
+++ b/pkgs/applications/virtualization/cloud-hypervisor/vhost/0001-vhost-fix-receiving-reply-payloads.patch
@@ -0,0 +1,113 @@
+From a1434fa3cb740ecf675c56db746f5098e6267a12 Mon Sep 17 00:00:00 2001
+From: David Stevens <stevensd@chromium.org>
+Date: Wed, 15 Jun 2022 15:56:18 +0900
+Subject: [PATCH 1/3] vhost: fix receiving reply payloads
+
+The existing code confuses the length of the request with the length of
+the reply in recv_reply_with_payload. This makes it impossible to use
+for any requests where the reply differs in size. Fix this by
+determining payload size after reading the reply header.
+
+(cherry-picked from crosvm commit 31f04e92709980a4ffc56b1631f8b4be437cc2fe)
+
+Co-authored-by: Alyssa Ross <hi@alyssa.is>
+Signed-off-by: Alyssa Ross <hi@alyssa.is>
+---
+ crates/vhost/src/vhost_user/connection.rs | 29 ++++++++++-------------
+ crates/vhost/src/vhost_user/master.rs     | 16 +++----------
+ 2 files changed, 15 insertions(+), 30 deletions(-)
+
+diff --git a/crates/vhost/src/vhost_user/connection.rs b/crates/vhost/src/vhost_user/connection.rs
+index 4a62e12..3c308a8 100644
+--- a/crates/vhost/src/vhost_user/connection.rs
++++ b/crates/vhost/src/vhost_user/connection.rs
+@@ -551,7 +551,7 @@ impl<R: Req> Endpoint<R> {
+     /// accepted and all other file descriptor will be discard silently.
+     ///
+     /// # Return:
+-    /// * - (message header, message body, size of payload, [received files]) on success.
++    /// * - (message header, message body, payload, [received files]) on success.
+     /// * - SocketRetry: temporary error caused by signals or short of resources.
+     /// * - SocketBroken: the underline socket is broken.
+     /// * - SocketError: other socket related errors.
+@@ -560,15 +560,13 @@ impl<R: Req> Endpoint<R> {
+     #[cfg_attr(feature = "cargo-clippy", allow(clippy::type_complexity))]
+     pub fn recv_payload_into_buf<T: ByteValued + Sized + VhostUserMsgValidator>(
+         &mut self,
+-        buf: &mut [u8],
+-    ) -> Result<(VhostUserMsgHeader<R>, T, usize, Option<Vec<File>>)> {
+-        let mut hdr = VhostUserMsgHeader::default();
++    ) -> Result<(VhostUserMsgHeader<R>, T, Vec<u8>, Option<Vec<File>>)> {
+         let mut body: T = Default::default();
++        let (hdr, files) = self.recv_header()?;
++
++        let payload_size = hdr.get_size() as usize - mem::size_of::<T>();
++        let mut buf: Vec<u8> = vec![0; payload_size];
+         let mut iovs = [
+-            iovec {
+-                iov_base: (&mut hdr as *mut VhostUserMsgHeader<R>) as *mut c_void,
+-                iov_len: mem::size_of::<VhostUserMsgHeader<R>>(),
+-            },
+             iovec {
+                 iov_base: (&mut body as *mut T) as *mut c_void,
+                 iov_len: mem::size_of::<T>(),
+@@ -578,19 +576,16 @@ impl<R: Req> Endpoint<R> {
+                 iov_len: buf.len(),
+             },
+         ];
+-        // SAFETY: Safe because we own hdr and body and have a mutable borrow of buf, and
+-        // hdr and body are ByteValued, and it's safe to fill a byte slice with
+-        // arbitrary data.
+-        let (bytes, files) = unsafe { self.recv_into_iovec_all(&mut iovs[..])? };
+-
+-        let total = mem::size_of::<VhostUserMsgHeader<R>>() + mem::size_of::<T>();
+-        if bytes < total {
++        // SAFETY: Safe because we own body and buf, and body is ByteValued, and it's safe
++        // to fill a byte slice with arbitrary data.
++        let (bytes, more_files) = unsafe { self.recv_into_iovec_all(&mut iovs)? };
++        if bytes < hdr.get_size() as usize {
+             return Err(Error::PartialMessage);
+-        } else if !hdr.is_valid() || !body.is_valid() {
++        } else if !body.is_valid() || more_files.is_some() {
+             return Err(Error::InvalidMessage);
+         }
+ 
+-        Ok((hdr, body, bytes - total, files))
++        Ok((hdr, body, buf, files))
+     }
+ }
+ 
+diff --git a/crates/vhost/src/vhost_user/master.rs b/crates/vhost/src/vhost_user/master.rs
+index feeb984..89aec20 100644
+--- a/crates/vhost/src/vhost_user/master.rs
++++ b/crates/vhost/src/vhost_user/master.rs
+@@ -673,23 +673,13 @@ impl MasterInternal {
+         &mut self,
+         hdr: &VhostUserMsgHeader<MasterReq>,
+     ) -> VhostUserResult<(T, Vec<u8>, Option<Vec<File>>)> {
+-        if mem::size_of::<T>() > MAX_MSG_SIZE
+-            || hdr.get_size() as usize <= mem::size_of::<T>()
+-            || hdr.get_size() as usize > MAX_MSG_SIZE
+-            || hdr.is_reply()
+-        {
++        if mem::size_of::<T>() > MAX_MSG_SIZE || hdr.is_reply() {
+             return Err(VhostUserError::InvalidParam);
+         }
+         self.check_state()?;
+ 
+-        let mut buf: Vec<u8> = vec![0; hdr.get_size() as usize - mem::size_of::<T>()];
+-        let (reply, body, bytes, files) = self.main_sock.recv_payload_into_buf::<T>(&mut buf)?;
+-        if !reply.is_reply_for(hdr)
+-            || reply.get_size() as usize != mem::size_of::<T>() + bytes
+-            || files.is_some()
+-            || !body.is_valid()
+-            || bytes != buf.len()
+-        {
++        let (reply, body, buf, files) = self.main_sock.recv_payload_into_buf::<T>()?;
++        if !reply.is_reply_for(hdr) || files.is_some() || !body.is_valid() {
+             return Err(VhostUserError::InvalidMessage);
+         }
+ 
+-- 
+2.42.0
+
diff --git a/pkgs/applications/virtualization/cloud-hypervisor/vhost/0002-vhost_user-add-shared-memory-region-support.patch b/pkgs/applications/virtualization/cloud-hypervisor/vhost/0002-vhost_user-add-shared-memory-region-support.patch
new file mode 100644
index 00000000000..e4b7f96c030
--- /dev/null
+++ b/pkgs/applications/virtualization/cloud-hypervisor/vhost/0002-vhost_user-add-shared-memory-region-support.patch
@@ -0,0 +1,492 @@
+From e1f7130bfbe529a7fda1a598866e0dea41b6ba74 Mon Sep 17 00:00:00 2001
+From: David Stevens <stevensd@chromium.org>
+Date: Wed, 15 Jun 2022 16:45:12 +0900
+Subject: [PATCH 2/3] vhost_user: add shared memory region support
+
+Add support for shared memory regions to vhost-user. This is adding
+support for a front-end message to query for necessary shared memory
+regions plus back-end message to support mapping/unmapping files from
+the shared memory region.
+
+go/vvu-shared-memory
+
+BUG=b:201745804
+TEST=compiles
+
+Change-Id: I35c5d260ee09175b68f6778b81883e0070ee0265
+Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/3716344
+Reviewed-by: Keiichi Watanabe <keiichiw@chromium.org>
+Commit-Queue: David Stevens <stevensd@chromium.org>
+Reviewed-by: Alexandre Courbot <acourbot@chromium.org>
+Tested-by: kokoro <noreply+kokoro@google.com>
+(cherry-picked from commit f436e2706011fa5f34dc415972434aa3299ebc43)
+Signed-off-by: Alyssa Ross <alyssa.ross@unikie.com>
+---
+ crates/vhost-user-backend/src/handler.rs      |  10 +-
+ crates/vhost/src/vhost_user/dummy_slave.rs    |   4 +
+ crates/vhost/src/vhost_user/master.rs         |  25 ++++
+ .../src/vhost_user/master_req_handler.rs      |  57 ++++++--
+ crates/vhost/src/vhost_user/message.rs        | 136 +++++++++++++++++-
+ crates/vhost/src/vhost_user/slave_req.rs      |  20 ++-
+ .../vhost/src/vhost_user/slave_req_handler.rs |  15 ++
+ 7 files changed, 247 insertions(+), 20 deletions(-)
+
+diff --git a/crates/vhost-user-backend/src/handler.rs b/crates/vhost-user-backend/src/handler.rs
+index 262bf6c..73402b2 100644
+--- a/crates/vhost-user-backend/src/handler.rs
++++ b/crates/vhost-user-backend/src/handler.rs
+@@ -11,9 +11,9 @@ use std::sync::Arc;
+ use std::thread;
+ 
+ use vhost::vhost_user::message::{
+-    VhostUserConfigFlags, VhostUserMemoryRegion, VhostUserProtocolFeatures,
+-    VhostUserSingleMemoryRegion, VhostUserVirtioFeatures, VhostUserVringAddrFlags,
+-    VhostUserVringState,
++    VhostSharedMemoryRegion, VhostUserConfigFlags, VhostUserMemoryRegion,
++    VhostUserProtocolFeatures, VhostUserSingleMemoryRegion, VhostUserVirtioFeatures,
++    VhostUserVringAddrFlags, VhostUserVringState,
+ };
+ use vhost::vhost_user::{
+     Error as VhostUserError, Result as VhostUserResult, Slave, VhostUserSlaveReqHandlerMut,
+@@ -602,6 +602,10 @@ where
+ 
+         Ok(())
+     }
++
++    fn get_shared_memory_regions(&mut self) -> VhostUserResult<Vec<VhostSharedMemoryRegion>> {
++        Ok(Vec::new())
++    }
+ }
+ 
+ impl<S, V, B: Bitmap> Drop for VhostUserHandler<S, V, B> {
+diff --git a/crates/vhost/src/vhost_user/dummy_slave.rs b/crates/vhost/src/vhost_user/dummy_slave.rs
+index ae728a0..00a1ae8 100644
+--- a/crates/vhost/src/vhost_user/dummy_slave.rs
++++ b/crates/vhost/src/vhost_user/dummy_slave.rs
+@@ -291,4 +291,8 @@ impl VhostUserSlaveReqHandlerMut for DummySlaveReqHandler {
+     fn remove_mem_region(&mut self, _region: &VhostUserSingleMemoryRegion) -> Result<()> {
+         Ok(())
+     }
++
++    fn get_shared_memory_regions(&mut self) -> Result<Vec<VhostSharedMemoryRegion>> {
++        Ok(Vec::new())
++    }
+ }
+diff --git a/crates/vhost/src/vhost_user/master.rs b/crates/vhost/src/vhost_user/master.rs
+index 89aec20..6808cb0 100644
+--- a/crates/vhost/src/vhost_user/master.rs
++++ b/crates/vhost/src/vhost_user/master.rs
+@@ -72,6 +72,9 @@ pub trait VhostUserMaster: VhostBackend {
+ 
+     /// Remove a guest memory mapping from vhost.
+     fn remove_mem_region(&mut self, region: &VhostUserMemoryRegionInfo) -> Result<()>;
++
++    /// Gets the shared memory regions used by the device.
++    fn get_shared_memory_regions(&self) -> Result<Vec<VhostSharedMemoryRegion>>;
+ }
+ 
+ fn error_code<T>(err: VhostUserError) -> Result<T> {
+@@ -513,6 +516,28 @@ impl VhostUserMaster for Master {
+         let hdr = node.send_request_with_body(MasterReq::REM_MEM_REG, &body, None)?;
+         node.wait_for_ack(&hdr).map_err(|e| e.into())
+     }
++
++    fn get_shared_memory_regions(&self) -> Result<Vec<VhostSharedMemoryRegion>> {
++        let mut node = self.node();
++        let hdr = node.send_request_header(MasterReq::GET_SHARED_MEMORY_REGIONS, None)?;
++        let (body_reply, buf_reply, rfds) = node.recv_reply_with_payload::<VhostUserU64>(&hdr)?;
++        let struct_size = mem::size_of::<VhostSharedMemoryRegion>();
++        if rfds.is_some() || buf_reply.len() != body_reply.value as usize * struct_size {
++            return error_code(VhostUserError::InvalidMessage);
++        }
++        let mut regions = Vec::new();
++        let mut offset = 0;
++        for _ in 0..body_reply.value {
++            regions.push(
++                // Can't fail because the input is the correct size.
++                VhostSharedMemoryRegion::from_slice(&buf_reply[offset..(offset + struct_size)])
++                    .unwrap()
++                    .clone(),
++            );
++            offset += struct_size;
++        }
++        Ok(regions)
++    }
+ }
+ 
+ impl AsRawFd for Master {
+diff --git a/crates/vhost/src/vhost_user/master_req_handler.rs b/crates/vhost/src/vhost_user/master_req_handler.rs
+index c9c528b..3d01542 100644
+--- a/crates/vhost/src/vhost_user/master_req_handler.rs
++++ b/crates/vhost/src/vhost_user/master_req_handler.rs
+@@ -33,6 +33,16 @@ pub trait VhostUserMasterReqHandler {
+         Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
+     }
+ 
++    /// Handle shared memory region mapping requests.
++    fn shmem_map(&self, _req: &VhostUserShmemMapMsg, _fd: &dyn AsRawFd) -> HandlerResult<u64> {
++        Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
++    }
++
++    /// Handle shared memory region unmapping requests.
++    fn shmem_unmap(&self, _req: &VhostUserShmemUnmapMsg) -> HandlerResult<u64> {
++        Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
++    }
++
+     /// Handle virtio-fs map file requests.
+     fn fs_slave_map(&self, _fs: &VhostUserFSSlaveMsg, _fd: &dyn AsRawFd) -> HandlerResult<u64> {
+         Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
+@@ -66,6 +76,16 @@ pub trait VhostUserMasterReqHandlerMut {
+         Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
+     }
+ 
++    /// Handle shared memory region mapping requests.
++    fn shmem_map(&mut self, _req: &VhostUserShmemMapMsg, _fd: &dyn AsRawFd) -> HandlerResult<u64> {
++        Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
++    }
++
++    /// Handle shared memory region unmapping requests.
++    fn shmem_unmap(&mut self, _req: &VhostUserShmemUnmapMsg) -> HandlerResult<u64> {
++        Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
++    }
++
+     /// Handle virtio-fs map file requests.
+     fn fs_slave_map(&mut self, _fs: &VhostUserFSSlaveMsg, _fd: &dyn AsRawFd) -> HandlerResult<u64> {
+         Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
+@@ -95,6 +115,14 @@ impl<S: VhostUserMasterReqHandlerMut> VhostUserMasterReqHandler for Mutex<S> {
+         self.lock().unwrap().handle_config_change()
+     }
+ 
++    fn shmem_map(&self, req: &VhostUserShmemMapMsg, fd: &dyn AsRawFd) -> HandlerResult<u64> {
++        self.lock().unwrap().shmem_map(req, fd)
++    }
++
++    fn shmem_unmap(&self, req: &VhostUserShmemUnmapMsg) -> HandlerResult<u64> {
++        self.lock().unwrap().shmem_unmap(req)
++    }
++
+     fn fs_slave_map(&self, fs: &VhostUserFSSlaveMsg, fd: &dyn AsRawFd) -> HandlerResult<u64> {
+         self.lock().unwrap().fs_slave_map(fs, fd)
+     }
+@@ -222,6 +250,19 @@ impl<S: VhostUserMasterReqHandler> MasterReqHandler<S> {
+                     .handle_config_change()
+                     .map_err(Error::ReqHandlerError)
+             }
++            Ok(SlaveReq::SHMEM_MAP) => {
++                let msg = self.extract_msg_body::<VhostUserShmemMapMsg>(&hdr, size, &buf)?;
++                // check_attached_files() has validated files
++                self.backend
++                    .shmem_map(&msg, &files.unwrap()[0])
++                    .map_err(Error::ReqHandlerError)
++            }
++            Ok(SlaveReq::SHMEM_UNMAP) => {
++                let msg = self.extract_msg_body::<VhostUserShmemUnmapMsg>(&hdr, size, &buf)?;
++                self.backend
++                    .shmem_unmap(&msg)
++                    .map_err(Error::ReqHandlerError)
++            }
+             Ok(SlaveReq::FS_MAP) => {
+                 let msg = self.extract_msg_body::<VhostUserFSSlaveMsg>(&hdr, size, &buf)?;
+                 // check_attached_files() has validated files
+@@ -251,7 +292,7 @@ impl<S: VhostUserMasterReqHandler> MasterReqHandler<S> {
+             _ => Err(Error::InvalidMessage),
+         };
+ 
+-        self.send_ack_message(&hdr, &res)?;
++        self.send_reply(&hdr, &res)?;
+ 
+         res
+     }
+@@ -285,7 +326,7 @@ impl<S: VhostUserMasterReqHandler> MasterReqHandler<S> {
+         files: &Option<Vec<File>>,
+     ) -> Result<()> {
+         match hdr.get_code() {
+-            Ok(SlaveReq::FS_MAP | SlaveReq::FS_IO) => {
++            Ok(SlaveReq::SHMEM_MAP | SlaveReq::FS_MAP | SlaveReq::FS_IO) => {
+                 // Expect a single file is passed.
+                 match files {
+                     Some(files) if files.len() == 1 => Ok(()),
+@@ -327,12 +368,12 @@ impl<S: VhostUserMasterReqHandler> MasterReqHandler<S> {
+         ))
+     }
+ 
+-    fn send_ack_message(
+-        &mut self,
+-        req: &VhostUserMsgHeader<SlaveReq>,
+-        res: &Result<u64>,
+-    ) -> Result<()> {
+-        if self.reply_ack_negotiated && req.is_need_reply() {
++    fn send_reply(&mut self, req: &VhostUserMsgHeader<SlaveReq>, res: &Result<u64>) -> Result<()> {
++        if matches!(
++            req.get_code(),
++            Ok(SlaveReq::SHMEM_MAP | SlaveReq::SHMEM_UNMAP)
++        ) || (self.reply_ack_negotiated && req.is_need_reply())
++        {
+             let hdr = self.new_reply_header::<VhostUserU64>(req)?;
+             let def_err = libc::EINVAL;
+             let val = match res {
+diff --git a/crates/vhost/src/vhost_user/message.rs b/crates/vhost/src/vhost_user/message.rs
+index bbd8eb9..be24992 100644
+--- a/crates/vhost/src/vhost_user/message.rs
++++ b/crates/vhost/src/vhost_user/message.rs
+@@ -146,8 +146,10 @@ pub enum MasterReq {
+     /// Query the backend for its device status as defined in the VIRTIO
+     /// specification.
+     GET_STATUS = 40,
++    /// Get a list of the device's shared memory regions.
++    GET_SHARED_MEMORY_REGIONS = 41,
+     /// Upper bound of valid commands.
+-    MAX_CMD = 41,
++    MAX_CMD = 42,
+ }
+ 
+ impl From<MasterReq> for u32 {
+@@ -178,16 +180,20 @@ pub enum SlaveReq {
+     VRING_CALL = 4,
+     /// Indicate that an error occurred on the specific vring.
+     VRING_ERR = 5,
++    /// Indicates a request to map a fd into a shared memory region.
++    SHMEM_MAP = 6,
++    /// Indicates a request to unmap part of a shared memory region.
++    SHMEM_UNMAP = 7,
+     /// Virtio-fs draft: map file content into the window.
+-    FS_MAP = 6,
++    FS_MAP = 8,
+     /// Virtio-fs draft: unmap file content from the window.
+-    FS_UNMAP = 7,
++    FS_UNMAP = 9,
+     /// Virtio-fs draft: sync file content.
+-    FS_SYNC = 8,
++    FS_SYNC = 10,
+     /// Virtio-fs draft: perform a read/write from an fd directly to GPA.
+-    FS_IO = 9,
++    FS_IO = 11,
+     /// Upper bound of valid commands.
+-    MAX_CMD = 10,
++    MAX_CMD = 12,
+ }
+ 
+ impl From<SlaveReq> for u32 {
+@@ -972,6 +978,99 @@ impl VhostUserMsgValidator for VhostUserFSSlaveMsg {
+     }
+ }
+ 
++bitflags! {
++    #[derive(Default)]
++    /// Flags for SHMEM_MAP messages.
++    pub struct VhostUserShmemMapMsgFlags: u8 {
++        /// Empty permission.
++        const EMPTY = 0x0;
++        /// Read permission.
++        const MAP_R = 0x1;
++        /// Write permission.
++        const MAP_W = 0x2;
++    }
++}
++
++/// Slave request message to map a file into a shared memory region.
++#[repr(C, packed)]
++#[derive(Default, Copy, Clone)]
++pub struct VhostUserShmemMapMsg {
++    /// Flags for the mmap operation
++    pub flags: VhostUserShmemMapMsgFlags,
++    /// Shared memory region id.
++    pub shmid: u8,
++    padding: [u8; 6],
++    /// Offset into the shared memory region.
++    pub shm_offset: u64,
++    /// File offset.
++    pub fd_offset: u64,
++    /// Size of region to map.
++    pub len: u64,
++}
++// Safe because it only has data and has no implicit padding.
++unsafe impl ByteValued for VhostUserShmemMapMsg {}
++
++impl VhostUserMsgValidator for VhostUserShmemMapMsg {
++    fn is_valid(&self) -> bool {
++        (self.flags.bits() & !VhostUserFSSlaveMsgFlags::all().bits() as u8) == 0
++            && self.fd_offset.checked_add(self.len).is_some()
++            && self.shm_offset.checked_add(self.len).is_some()
++    }
++}
++
++impl VhostUserShmemMapMsg {
++    /// New instance of VhostUserShmemMapMsg struct
++    pub fn new(
++        shmid: u8,
++        shm_offset: u64,
++        fd_offset: u64,
++        len: u64,
++        flags: VhostUserShmemMapMsgFlags,
++    ) -> Self {
++        Self {
++            flags,
++            shmid,
++            padding: [0; 6],
++            shm_offset,
++            fd_offset,
++            len,
++        }
++    }
++}
++
++/// Slave request message to unmap part of a shared memory region.
++#[repr(C, packed)]
++#[derive(Default, Copy, Clone)]
++pub struct VhostUserShmemUnmapMsg {
++    /// Shared memory region id.
++    pub shmid: u8,
++    padding: [u8; 7],
++    /// Offset into the shared memory region.
++    pub shm_offset: u64,
++    /// Size of region to unmap.
++    pub len: u64,
++}
++// Safe because it only has data and has no implicit padding.
++unsafe impl ByteValued for VhostUserShmemUnmapMsg {}
++
++impl VhostUserMsgValidator for VhostUserShmemUnmapMsg {
++    fn is_valid(&self) -> bool {
++        self.shm_offset.checked_add(self.len).is_some()
++    }
++}
++
++impl VhostUserShmemUnmapMsg {
++    /// New instance of VhostUserShmemUnmapMsg struct
++    pub fn new(shmid: u8, shm_offset: u64, len: u64) -> Self {
++        Self {
++            shmid,
++            padding: [0; 7],
++            shm_offset,
++            len,
++        }
++    }
++}
++
+ /// Inflight I/O descriptor state for split virtqueues
+ #[repr(packed)]
+ #[derive(Clone, Copy, Default)]
+@@ -1103,6 +1202,31 @@ impl QueueRegionPacked {
+     }
+ }
+ 
++/// Virtio shared memory descriptor.
++#[repr(packed)]
++#[derive(Default, Copy, Clone)]
++pub struct VhostSharedMemoryRegion {
++    /// The shared memory region's shmid.
++    pub id: u8,
++    /// Padding
++    padding: [u8; 7],
++    /// The length of the shared memory region.
++    pub length: u64,
++}
++// Safe because it only has data and has no implicit padding.
++unsafe impl ByteValued for VhostSharedMemoryRegion {}
++
++impl VhostSharedMemoryRegion {
++    /// New instance of VhostSharedMemoryRegion struct
++    pub fn new(id: u8, length: u64) -> Self {
++        VhostSharedMemoryRegion {
++            id,
++            padding: [0; 7],
++            length,
++        }
++    }
++}
++
+ #[cfg(test)]
+ mod tests {
+     use super::*;
+diff --git a/crates/vhost/src/vhost_user/slave_req.rs b/crates/vhost/src/vhost_user/slave_req.rs
+index ade1e91..b7ecd20 100644
+--- a/crates/vhost/src/vhost_user/slave_req.rs
++++ b/crates/vhost/src/vhost_user/slave_req.rs
+@@ -46,12 +46,16 @@ impl SlaveInternal {
+         }
+         self.sock.send_message(&hdr, body, fds)?;
+ 
+-        self.wait_for_ack(&hdr)
++        self.wait_for_reply(&hdr)
+     }
+ 
+-    fn wait_for_ack(&mut self, hdr: &VhostUserMsgHeader<SlaveReq>) -> Result<u64> {
++    fn wait_for_reply(&mut self, hdr: &VhostUserMsgHeader<SlaveReq>) -> Result<u64> {
+         self.check_state()?;
+-        if !self.reply_ack_negotiated {
++        if !matches!(
++            hdr.get_code(),
++            Ok(SlaveReq::SHMEM_MAP | SlaveReq::SHMEM_UNMAP)
++        ) && !self.reply_ack_negotiated
++        {
+             return Ok(0);
+         }
+ 
+@@ -129,6 +133,16 @@ impl Slave {
+ }
+ 
+ impl VhostUserMasterReqHandler for Slave {
++    /// Handle shared memory region mapping requests.
++    fn shmem_map(&self, req: &VhostUserShmemMapMsg, fd: &dyn AsRawFd) -> HandlerResult<u64> {
++        self.send_message(SlaveReq::SHMEM_MAP, req, Some(&[fd.as_raw_fd()]))
++    }
++
++    /// Handle shared memory region unmapping requests.
++    fn shmem_unmap(&self, req: &VhostUserShmemUnmapMsg) -> HandlerResult<u64> {
++        self.send_message(SlaveReq::SHMEM_UNMAP, req, None)
++    }
++
+     /// Forward vhost-user-fs map file requests to the slave.
+     fn fs_slave_map(&self, fs: &VhostUserFSSlaveMsg, fd: &dyn AsRawFd) -> HandlerResult<u64> {
+         self.send_message(SlaveReq::FS_MAP, fs, Some(&[fd.as_raw_fd()]))
+diff --git a/crates/vhost/src/vhost_user/slave_req_handler.rs b/crates/vhost/src/vhost_user/slave_req_handler.rs
+index e998339..b84feca 100644
+--- a/crates/vhost/src/vhost_user/slave_req_handler.rs
++++ b/crates/vhost/src/vhost_user/slave_req_handler.rs
+@@ -70,6 +70,7 @@ pub trait VhostUserSlaveReqHandler {
+     fn get_max_mem_slots(&self) -> Result<u64>;
+     fn add_mem_region(&self, region: &VhostUserSingleMemoryRegion, fd: File) -> Result<()>;
+     fn remove_mem_region(&self, region: &VhostUserSingleMemoryRegion) -> Result<()>;
++    fn get_shared_memory_regions(&self) -> Result<Vec<VhostSharedMemoryRegion>>;
+ }
+ 
+ /// Services provided to the master by the slave without interior mutability.
+@@ -118,6 +119,7 @@ pub trait VhostUserSlaveReqHandlerMut {
+     fn get_max_mem_slots(&mut self) -> Result<u64>;
+     fn add_mem_region(&mut self, region: &VhostUserSingleMemoryRegion, fd: File) -> Result<()>;
+     fn remove_mem_region(&mut self, region: &VhostUserSingleMemoryRegion) -> Result<()>;
++    fn get_shared_memory_regions(&mut self) -> Result<Vec<VhostSharedMemoryRegion>>;
+ }
+ 
+ impl<T: VhostUserSlaveReqHandlerMut> VhostUserSlaveReqHandler for Mutex<T> {
+@@ -226,6 +228,10 @@ impl<T: VhostUserSlaveReqHandlerMut> VhostUserSlaveReqHandler for Mutex<T> {
+     fn remove_mem_region(&self, region: &VhostUserSingleMemoryRegion) -> Result<()> {
+         self.lock().unwrap().remove_mem_region(region)
+     }
++
++    fn get_shared_memory_regions(&self) -> Result<Vec<VhostSharedMemoryRegion>> {
++        self.lock().unwrap().get_shared_memory_regions()
++    }
+ }
+ 
+ /// Server to handle service requests from masters from the master communication channel.
+@@ -519,6 +525,15 @@ impl<S: VhostUserSlaveReqHandler> SlaveReqHandler<S> {
+                 let res = self.backend.remove_mem_region(&msg);
+                 self.send_ack_message(&hdr, res)?;
+             }
++            Ok(MasterReq::GET_SHARED_MEMORY_REGIONS) => {
++                let regions = self.backend.get_shared_memory_regions()?;
++                let mut buf = Vec::new();
++                let msg = VhostUserU64::new(regions.len() as u64);
++                for r in regions {
++                    buf.extend_from_slice(r.as_slice())
++                }
++                self.send_reply_with_payload(&hdr, &msg, buf.as_slice())?;
++            }
+             _ => {
+                 return Err(Error::InvalidMessage);
+             }
+-- 
+2.42.0
+
diff --git a/pkgs/applications/virtualization/cloud-hypervisor/vhost/0003-vhost-user-add-protocol-flag-for-shmem.patch b/pkgs/applications/virtualization/cloud-hypervisor/vhost/0003-vhost-user-add-protocol-flag-for-shmem.patch
new file mode 100644
index 00000000000..f5e0e4e2b5f
--- /dev/null
+++ b/pkgs/applications/virtualization/cloud-hypervisor/vhost/0003-vhost-user-add-protocol-flag-for-shmem.patch
@@ -0,0 +1,38 @@
+From 3a44f7aaad8dd8ce1c2c5b75a30326df1f043f22 Mon Sep 17 00:00:00 2001
+From: David Stevens <stevensd@chromium.org>
+Date: Thu, 13 Oct 2022 10:37:47 +0900
+Subject: [PATCH 3/3] vhost-user: add protocol flag for shmem
+
+Add a vhost protocol feature flag for shared memory region support. This
+is necessary to avoid sending the GET_SHARED_MEMORY_REGIONS message to
+backends which don't support it.
+
+BUG=b:252901073
+TEST=crosvm device wl
+
+Change-Id: I044926e982526c3c76063b5386cab0db72524707
+Reviewed-on: https://chromium-review.googlesource.com/c/crosvm/crosvm/+/3951472
+Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
+Commit-Queue: David Stevens <stevensd@chromium.org>
+(cherry-picked from commit 60aa43629ae9be2cc3df37c648ab7e0e5ff2172c)
+Signed-off-by: Alyssa Ross <hi@alyssa.is>
+---
+ crates/vhost/src/vhost_user/message.rs | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/crates/vhost/src/vhost_user/message.rs b/crates/vhost/src/vhost_user/message.rs
+index be24992..5fb2fb8 100644
+--- a/crates/vhost/src/vhost_user/message.rs
++++ b/crates/vhost/src/vhost_user/message.rs
+@@ -435,6 +435,8 @@ bitflags! {
+         const STATUS = 0x0001_0000;
+         /// Support Xen mmap.
+         const XEN_MMAP = 0x0002_0000;
++        /// Support shared memory regions.
++        const SHARED_MEMORY_REGIONS = 0x0002_0000;
+     }
+ }
+ 
+-- 
+2.42.0
+