patches and low-level development discussion
 help / color / mirror / code / Atom feed
From: Alyssa Ross <hi@alyssa.is>
To: devel@spectrum-os.org
Cc: Valentin Gagarin <valentin@gagarin.work>,
	Demi Marie Obenour <demiobenour@gmail.com>,
	colbyt <colby@colbyt.com>
Subject: [RFC PATCH 1/3] lib/nixpkgs.default.nix: update to master
Date: Wed,  8 Jul 2026 17:43:13 +0200	[thread overview]
Message-ID: <20260708154315.678807-2-hi@alyssa.is> (raw)
In-Reply-To: <20260708154315.678807-1-hi@alyssa.is>

Cloud Hypervisor now contains the generic vhost-user device, and all
necessary vhost patches are now upstream, so we can simplify the GPU
patch quite a bit.

Signed-off-by: Alyssa Ross <hi@alyssa.is>
---
 .../software/cloud-hypervisor/index.html      |   19 +-
 host/efi.nix                                  |    2 +-
 host/initramfs/shell.nix                      |    2 +-
 host/rootfs/shell.nix                         |    2 +-
 img/app/Makefile                              |    2 +-
 img/app/default.nix                           |    2 +-
 lib/nixpkgs.default.nix                       |    4 +-
 .../0001-build-use-local-vhost.patch          |   55 -
 ...0002-virtio-devices-add-a-GPU-device.patch | 1632 -----------------
 pkgs/cloud-hypervisor/default.nix             |   34 +-
 pkgs/cloud-hypervisor/gpu.patch               |  719 ++++++++
 ...vhost_user-add-get_size-to-MsgHeader.patch |   82 -
 ...2-vhost-fix-receiving-reply-payloads.patch |  165 --
 ...ser-add-shared-memory-region-support.patch |  484 -----
 ...ost_user-add-protocol-flag-for-shmem.patch |   43 -
 pkgs/gtk3/default.nix                         |   13 -
 pkgs/overlay.nix                              |    4 -
 pkgs/skaware-packages/default.nix             |   24 -
 release/checks/no-roothash.nix                |    4 +-
 release/checks/wayland/default.nix            |    2 +-
 .../checks/wayland/surface-notify/meson.build |    2 +-
 release/live/default.nix                      |    2 +-
 scripts/dist-cloud-hypervisor.sh              |    1 -
 tools/start-vmm/ch.rs                         |    6 +-
 tools/start-vmm/lib.rs                        |    8 +-
 tools/start-vmm/tests/vm_command-basic.rs     |    4 +-
 vm/sys/net/default.nix                        |    2 +-
 27 files changed, 750 insertions(+), 2569 deletions(-)
 delete mode 100644 pkgs/cloud-hypervisor/0001-build-use-local-vhost.patch
 delete mode 100644 pkgs/cloud-hypervisor/0002-virtio-devices-add-a-GPU-device.patch
 create mode 100644 pkgs/cloud-hypervisor/gpu.patch
 delete mode 100644 pkgs/cloud-hypervisor/vhost/0001-vhost_user-add-get_size-to-MsgHeader.patch
 delete mode 100644 pkgs/cloud-hypervisor/vhost/0002-vhost-fix-receiving-reply-payloads.patch
 delete mode 100644 pkgs/cloud-hypervisor/vhost/0003-vhost_user-add-shared-memory-region-support.patch
 delete mode 100644 pkgs/cloud-hypervisor/vhost/0004-vhost_user-add-protocol-flag-for-shmem.patch
 delete mode 100644 pkgs/gtk3/default.nix
 delete mode 100644 pkgs/skaware-packages/default.nix

diff --git a/Documentation/software/cloud-hypervisor/index.html b/Documentation/software/cloud-hypervisor/index.html
index 399b5e5..82c7b24 100644
--- a/Documentation/software/cloud-hypervisor/index.html
+++ b/Documentation/software/cloud-hypervisor/index.html
@@ -25,24 +25,15 @@ compartmentalized desktop operating system project.
 
 <h2>Building</h2>
 
-It’s necessary to patch both Cloud Hypervisor itself, and
-the <a href="https://github.com/rust-vmm/vhost/tree/main/vhost">vhost</a>
-crate.  The Cloud Hypervisor patches expect to find the patched vhost
-source tree under the same parent directory as Cloud Hypervisor’s.
-
 <ol>
   <li>
-  Clone the repositories
+  Clone the repository
   for <a href="https://github.com/cloud-hypervisor/cloud-hypervisor">Cloud
-  Hypervisor</a>
-  and <a href="https://github.com/rust-vmm/vhost">vhost</a>.  Make
-  sure the revision of the vhost repository you have checked out
-  contains the versions of the vhost and vhost-user-backend crates
-  expected by your version of Cloud Hypervisor.
+  Hypervisor</a>.
 
   <li>
-  Download and apply the patches for the correct versions of Cloud
-  Hypervisor and vhost.
+  Download and apply the patchset for the correct version of Cloud
+  Hypervisor.
 
   <li>
   Use <code>cargo build</code> in the Cloud Hypervisor source tree as
@@ -57,7 +48,7 @@ Start the crosvm GPU device:
 
 Connect to the GPU device with cloud-hypervisor:
 
-<pre><code>cloud-hypervisor --gpu socket=gpu.sock …</code></pre>
+<pre><code>cloud-hypervisor --generic-vhost-user virtio_id=gpu,socket=gpu.sock,queue_sizes='[16,256]' …</code></pre>
 
 <h2>Downloads</h2>
 
diff --git a/host/efi.nix b/host/efi.nix
index ecedb6b..8cb24ed 100644
--- a/host/efi.nix
+++ b/host/efi.nix
@@ -8,7 +8,7 @@ import ../lib/call-package.nix (
 }:
 let
   initramfs = callSpectrumPackage ./initramfs {};
-  kernel = "${rootfs.kernel}/${stdenv.hostPlatform.linux-kernel.target}";
+  kernel = "${rootfs.kernel}/${rootfs.kernel.target}";
   systemd = systemdUkify.overrideAttrs ({ mesonFlags ? [], ... }: {
     # The default limit is too low to build a generic aarch64 distro image:
     # https://github.com/systemd/systemd/pull/37417
diff --git a/host/initramfs/shell.nix b/host/initramfs/shell.nix
index 76b744b..b418954 100644
--- a/host/initramfs/shell.nix
+++ b/host/initramfs/shell.nix
@@ -17,7 +17,7 @@ initramfs.overrideAttrs ({ nativeBuildInputs ? [], env ? {}, ... }: {
   ];
 
   env = env // {
-    KERNEL = "${rootfs.kernel}/${stdenv.hostPlatform.linux-kernel.target}";
+    KERNEL = "${rootfs.kernel}/${rootfs.kernel.target}";
     ROOT_FS = rootfs;
     VERSION = config.version;
   };
diff --git a/host/rootfs/shell.nix b/host/rootfs/shell.nix
index 27f93e0..6becabe 100644
--- a/host/rootfs/shell.nix
+++ b/host/rootfs/shell.nix
@@ -18,7 +18,7 @@ rootfs.overrideAttrs (
 
   env = env // {
     INITRAMFS = callSpectrumPackage ../initramfs {};
-    KERNEL = "${passthru.kernel}/${stdenv.hostPlatform.linux-kernel.target}";
+    KERNEL = "${passthru.kernel}/${passthru.kernel.target}";
     LINUX_SRC = srcOnly passthru.kernel.configfile;
     VMLINUX = "${passthru.kernel.dev}/vmlinux";
     VERSION = config.version;
diff --git a/img/app/Makefile b/img/app/Makefile
index 3996831..4464845 100644
--- a/img/app/Makefile
+++ b/img/app/Makefile
@@ -119,7 +119,7 @@ run-cloud-hypervisor: $(imgdir)/appvm/blk/root.img start-vhost-user-gpu start-vh
 	    --memory size=1G,shared=on \
 	    --disk path=$(imgdir)/appvm/blk/root.img,readonly=on \
 	    --fs tag=host,socket=build/virtiofsd.sock \
-	    --gpu socket=build/vhost-user-gpu.sock \
+	    --generic-vhost-user virtio_id=gpu,socket=build/vhost-user-gpu.sock,queue_sizes='[16,256]' \
 	    --vsock cid=3,socket=build/vsock.sock \
 	    --net mac=02:00:00:00:00:01,vhost_user=on,socket=build/vhost-user-net.sock \
 	    --kernel $(KERNEL) \
diff --git a/img/app/default.nix b/img/app/default.nix
index 71e6fa0..0a65793 100644
--- a/img/app/default.nix
+++ b/img/app/default.nix
@@ -19,7 +19,7 @@ let
       # the kernel that *hasn't* been built to be compressed.  Weird!
       "compressed/vmlinux.bin"
     else
-      stdenvNoCC.hostPlatform.linux-kernel.target;
+      kernel.target;
 
   kernel = (linux_latest.override {
     structuredExtraConfig = with lib.kernel; {
diff --git a/lib/nixpkgs.default.nix b/lib/nixpkgs.default.nix
index 6b1b26f..efee89e 100644
--- a/lib/nixpkgs.default.nix
+++ b/lib/nixpkgs.default.nix
@@ -4,6 +4,6 @@
 # Generated by scripts/update-nixpkgs.sh.
 
 import (builtins.fetchTarball {
-  url = "https://github.com/NixOS/nixpkgs/archive/cab52b36dca5a5a368f05bd03fca0b543b5b9a3e.tar.gz";
-  sha256 = "161vhvxj5lmarlagrgcak8yvgwd1llcbfm7g1vqhppy1a7v73y2a";
+  url = "https://github.com/NixOS/nixpkgs/archive/8e5e44b12ea0c3c33f2c96377bde386ef22f7d24.tar.gz";
+  sha256 = "1i6dyhj3wi1ql9dd77prw3fla0x7h7k54mfacyiacxjjk763fb8w";
 })
diff --git a/pkgs/cloud-hypervisor/0001-build-use-local-vhost.patch b/pkgs/cloud-hypervisor/0001-build-use-local-vhost.patch
deleted file mode 100644
index 3e00250..0000000
--- a/pkgs/cloud-hypervisor/0001-build-use-local-vhost.patch
+++ /dev/null
@@ -1,55 +0,0 @@
-From fca364182f5c4e595384297316e9c74069ca5169 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
-SPDX-FileCopyrightText: 2022 Unikie
-SPDX-FileCopyrightText: 2023 Alyssa Ross <hi@alyssa.is>
-SPDX-License-Identifier: Apache-2.0 AND LicenseRef-BSD-3-Clause-Google
-
-Signed-off-by: Alyssa Ross <alyssa.ross@unikie.com>
-Signed-off-by: Alyssa Ross <hi@alyssa.is>
----
- Cargo.lock | 4 ----
- Cargo.toml | 4 ++--
- 2 files changed, 2 insertions(+), 6 deletions(-)
-
-diff --git a/Cargo.lock b/Cargo.lock
-index 62bbf709f..39df61015 100644
---- a/Cargo.lock
-+++ b/Cargo.lock
-@@ -2304,8 +2304,6 @@ dependencies = [
- [[package]]
- name = "vhost"
- version = "0.14.0"
--source = "registry+https://github.com/rust-lang/crates.io-index"
--checksum = "2a4dcad85a129d97d5d4b2f3c47a4affdeedd76bdcd02094bcb5d9b76cac2d05"
- dependencies = [
-  "bitflags 2.10.0",
-  "libc",
-@@ -2317,8 +2315,6 @@ dependencies = [
- [[package]]
- name = "vhost-user-backend"
- version = "0.20.0"
--source = "registry+https://github.com/rust-lang/crates.io-index"
--checksum = "e183205a9ba7cb9c47fcb0fc0a07fc295a110efbb11ab78ad0d793b0a38a7bde"
- dependencies = [
-  "libc",
-  "log",
-diff --git a/Cargo.toml b/Cargo.toml
-index b738dde5b..1118f1eb1 100644
---- a/Cargo.toml
-+++ b/Cargo.toml
-@@ -55,8 +55,8 @@ seccompiler = "0.5.0"
- vfio-bindings = { version = "0.6.0", default-features = false }
- vfio-ioctls = { version = "0.5.1", default-features = false }
- vfio_user = { version = "0.1.1", default-features = false }
--vhost = { version = "0.14.0", default-features = false }
--vhost-user-backend = { version = "0.20.0", default-features = false }
-+vhost = { path = "../vhost/vhost" }
-+vhost-user-backend = { path = "../vhost/vhost-user-backend" }
- virtio-bindings = "0.2.6"
- virtio-queue = "0.16.0"
- vm-fdt = "0.3.0"
--- 
-2.53.0
-
diff --git a/pkgs/cloud-hypervisor/0002-virtio-devices-add-a-GPU-device.patch b/pkgs/cloud-hypervisor/0002-virtio-devices-add-a-GPU-device.patch
deleted file mode 100644
index d53755b..0000000
--- a/pkgs/cloud-hypervisor/0002-virtio-devices-add-a-GPU-device.patch
+++ /dev/null
@@ -1,1632 +0,0 @@
-From e4f29830f953c826c0fca137ef87ab3b67ac74ae 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
-SPDX-FileCopyrightText: The Cloud Hypervisor Authors
-SPDX-FileCopyrightText: 2017-2018 The Chromium OS Authors. All rights reserved.
-SPDX-FileCopyrightText: 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-SPDX-FileCopyrightText: 2019-2020, 2022 Intel Corporation
-SPDX-FileCopyrightText: 2020, Oracle and/or its affiliates.
-SPDX-FileCopyrightText: 2022 Unikie
-SPDX-FileCopyrightText: 2023 Sartura Ltd.
-SPDX-FileCopyrightText: 2023-2025 Alyssa Ross <hi@alyssa.is>
-SPDX-License-Identifier: Apache-2.0 AND LicenseRef-BSD-3-Clause-Google
-
-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>
----
- cloud-hypervisor/src/bin/ch-remote.rs         |  46 +-
- cloud-hypervisor/src/main.rs                  |  12 +-
- virtio-devices/src/device.rs                  |   4 +-
- 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          | 411 ++++++++++++++++++
- virtio-devices/src/vhost_user/mod.rs          |   6 +
- .../src/vhost_user/vu_common_ctrl.rs          |   9 +-
- vmm/src/api/dbus/mod.rs                       |   7 +-
- vmm/src/api/http/http_endpoint.rs             |   9 +-
- vmm/src/api/http/mod.rs                       |  12 +-
- vmm/src/api/mod.rs                            |  47 +-
- vmm/src/api/openapi/cloud-hypervisor.yaml     |  39 ++
- vmm/src/config.rs                             | 109 +++++
- vmm/src/device_manager.rs                     | 144 +++++-
- vmm/src/lib.rs                                |  79 +++-
- vmm/src/vm.rs                                 |  28 +-
- vmm/src/vm_config.rs                          |  10 +
- 19 files changed, 967 insertions(+), 29 deletions(-)
- create mode 100644 virtio-devices/src/vhost_user/gpu.rs
-
-REUSE-IgnoreStart
-
-diff --git a/cloud-hypervisor/src/bin/ch-remote.rs b/cloud-hypervisor/src/bin/ch-remote.rs
-index fd48ffab1..680a2b62f 100644
---- a/cloud-hypervisor/src/bin/ch-remote.rs
-+++ b/cloud-hypervisor/src/bin/ch-remote.rs
-@@ -22,8 +22,8 @@ use option_parser::{ByteSized, ByteSizedParseError};
- use thiserror::Error;
- use vmm::config::RestoreConfig;
- use vmm::vm_config::{
--    DeviceConfig, DiskConfig, FsConfig, NetConfig, PmemConfig, UserDeviceConfig, VdpaConfig,
--    VsockConfig,
-+    DeviceConfig, DiskConfig, FsConfig, GpuConfig, NetConfig, PmemConfig, UserDeviceConfig,
-+    VdpaConfig, VsockConfig,
- };
- #[cfg(feature = "dbus_api")]
- use zbus::{proxy, zvariant::Optional};
-@@ -49,6 +49,8 @@ enum Error {
-     AddDiskConfig(#[source] vmm::config::Error),
-     #[error("Error parsing filesystem syntax")]
-     AddFsConfig(#[source] vmm::config::Error),
-+    #[error("Error parsing GPU syntax: {0}")]
-+    AddGpuConfig(#[source] vmm::config::Error),
-     #[error("Error parsing persistent memory syntax")]
-     AddPmemConfig(#[source] vmm::config::Error),
-     #[error("Error parsing network syntax")]
-@@ -83,6 +85,7 @@ trait DBusApi1 {
-     fn vm_add_device(&self, device_config: &str) -> zbus::Result<Optional<String>>;
-     fn vm_add_disk(&self, disk_config: &str) -> zbus::Result<Optional<String>>;
-     fn vm_add_fs(&self, fs_config: &str) -> zbus::Result<Optional<String>>;
-+    fn vm_add_gpu(&self, gpu_config: &str) -> zbus::Result<Optional<String>>;
-     fn vm_add_net(&self, net_config: &str) -> zbus::Result<Optional<String>>;
-     fn vm_add_pmem(&self, pmem_config: &str) -> zbus::Result<Optional<String>>;
-     fn vm_add_user_device(&self, vm_add_user_device: &str) -> zbus::Result<Optional<String>>;
-@@ -155,6 +158,10 @@ impl<'a> DBusApi1ProxyBlocking<'a> {
-         self.print_response(self.vm_add_fs(fs_config))
-     }
- 
-+    fn api_vm_add_gpu(&self, gpu_config: &str) -> ApiResult {
-+        self.print_response(self.vm_add_gpu(gpu_config))
-+    }
-+
-     fn api_vm_add_net(&self, net_config: &str) -> ApiResult {
-         self.print_response(self.vm_add_net(net_config))
-     }
-@@ -398,6 +405,17 @@ fn rest_api_do_command(matches: &ArgMatches, socket: &mut UnixStream) -> ApiResu
-             simple_api_command(socket, "PUT", "add-fs", Some(&fs_config))
-                 .map_err(Error::HttpApiClient)
-         }
-+        Some("add-gpu") => {
-+            let gpu_config = add_gpu_config(
-+                matches
-+                    .subcommand_matches("add-gpu")
-+                    .unwrap()
-+                    .get_one::<String>("gpu_config")
-+                    .unwrap(),
-+            )?;
-+            simple_api_command(socket, "PUT", "add-gpu", Some(&gpu_config))
-+                .map_err(Error::HttpApiClient)
-+        }
-         Some("add-pmem") => {
-             let pmem_config = add_pmem_config(
-                 matches
-@@ -620,6 +638,16 @@ fn dbus_api_do_command(matches: &ArgMatches, proxy: &DBusApi1ProxyBlocking<'_>)
-             )?;
-             proxy.api_vm_add_fs(&fs_config)
-         }
-+        Some("add-gpu") => {
-+            let gpu_config = add_gpu_config(
-+                matches
-+                    .subcommand_matches("add-gpu")
-+                    .unwrap()
-+                    .get_one::<String>("gpu_config")
-+                    .unwrap(),
-+            )?;
-+            proxy.api_vm_add_gpu(&gpu_config)
-+        }
-         Some("add-pmem") => {
-             let pmem_config = add_pmem_config(
-                 matches
-@@ -835,6 +863,13 @@ fn add_fs_config(config: &str) -> Result<String, Error> {
-     Ok(fs_config)
- }
- 
-+fn add_gpu_config(config: &str) -> Result<String, Error> {
-+    let gpu_config = GpuConfig::parse(config).map_err(Error::AddGpuConfig)?;
-+    let gpu_config = serde_json::to_string(&gpu_config).unwrap();
-+
-+    Ok(gpu_config)
-+}
-+
- fn add_pmem_config(config: &str) -> Result<String, Error> {
-     let pmem_config = PmemConfig::parse(config).map_err(Error::AddPmemConfig)?;
-     let pmem_config = serde_json::to_string(&pmem_config).unwrap();
-@@ -981,6 +1016,13 @@ fn get_cli_commands_sorted() -> Box<[Command]> {
-                     .index(1)
-                     .help(vmm::vm_config::FsConfig::SYNTAX),
-             ),
-+        Command::new("add-gpu")
-+            .about("Add virtio-gpu backed gpu device")
-+            .arg(
-+                Arg::new("gpu_config")
-+                    .index(1)
-+                    .help(vmm::vm_config::GpuConfig::SYNTAX),
-+            ),
-         Command::new("add-net")
-             .about("Add network device")
-             .arg(Arg::new("net_config").index(1).help(NetConfig::SYNTAX)),
-diff --git a/cloud-hypervisor/src/main.rs b/cloud-hypervisor/src/main.rs
-index d08293b6e..cde764e8a 100644
---- a/cloud-hypervisor/src/main.rs
-+++ b/cloud-hypervisor/src/main.rs
-@@ -32,9 +32,9 @@ use vmm::vm_config::FwCfgConfig;
- #[cfg(feature = "ivshmem")]
- use vmm::vm_config::IvshmemConfig;
- use vmm::vm_config::{
--    BalloonConfig, DeviceConfig, DiskConfig, FsConfig, LandlockConfig, NetConfig, NumaConfig,
--    PciSegmentConfig, PmemConfig, RateLimiterGroupConfig, TpmConfig, UserDeviceConfig, VdpaConfig,
--    VmConfig, VsockConfig,
-+    BalloonConfig, DeviceConfig, DiskConfig, FsConfig, GpuConfig, LandlockConfig, NetConfig,
-+    NumaConfig, PciSegmentConfig, PmemConfig, RateLimiterGroupConfig, TpmConfig, UserDeviceConfig,
-+    VdpaConfig, VmConfig, VsockConfig,
- };
- use vmm_sys_util::eventfd::EventFd;
- use vmm_sys_util::signal::block_signal;
-@@ -280,6 +280,11 @@ fn get_cli_options_sorted(
-             .help("GDB socket (UNIX domain socket): path=</path/to/a/file>")
-             .num_args(1)
-             .group("vmm-config"),
-+        Arg::new("gpu")
-+            .long("gpu")
-+            .help(GpuConfig::SYNTAX)
-+            .num_args(1..)
-+            .group("vm-config"),
-         #[cfg(feature = "igvm")]
-         Arg::new("igvm")
-             .long("igvm")
-@@ -998,6 +1003,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 f0ed28f51..892bf08ee 100644
---- a/virtio-devices/src/device.rs
-+++ b/virtio-devices/src/device.rs
-@@ -6,7 +6,7 @@
- //
- // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
- 
--use std::collections::HashMap;
-+use std::collections::{BTreeMap, HashMap};
- use std::io::Write;
- use std::num::Wrapping;
- use std::sync::atomic::{AtomicBool, Ordering};
-@@ -50,7 +50,7 @@ pub struct VirtioSharedMemoryList {
-     pub mem_slot: u32,
-     pub addr: GuestAddress,
-     pub mapping: Arc<MmapRegion>,
--    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 da4f1c91b..4298e23ae 100644
---- a/virtio-devices/src/lib.rs
-+++ b/virtio-devices/src/lib.rs
-@@ -43,7 +43,7 @@ pub use self::block::{Block, BlockState};
- pub use self::console::{Console, ConsoleResizer, Endpoint};
- pub use self::device::{
-     DmaRemapping, VirtioCommon, VirtioDevice, VirtioInterrupt, VirtioInterruptType,
--    VirtioSharedMemoryList,
-+    VirtioSharedMemory, VirtioSharedMemoryList,
- };
- pub use self::epoll_helper::{
-     EPOLL_HELPER_EVENT_LAST, EpollHelper, EpollHelperError, EpollHelperHandler,
-@@ -91,6 +91,8 @@ pub enum ActivateError {
-     #[error("Failed to setup vhost-user daemon")]
-     VhostUserSetup(#[source] vhost_user::Error),
-     #[error("Failed to create seccomp filter")]
-+    VhostUserGpuSetup(#[source] vhost_user::Error),
-+    #[error("Failed to create seccomp filter: {0}")]
-     CreateSeccompFilter(#[source] seccompiler::Error),
-     #[error("Failed to create rate limiter")]
-     CreateRateLimiter(#[source] std::io::Error),
-diff --git a/virtio-devices/src/seccomp_filters.rs b/virtio-devices/src/seccomp_filters.rs
-index 07601b68a..68910c756 100644
---- a/virtio-devices/src/seccomp_filters.rs
-+++ b/virtio-devices/src/seccomp_filters.rs
-@@ -24,6 +24,7 @@ pub enum Thread {
-     VirtioRng,
-     VirtioVhostBlock,
-     VirtioVhostFs,
-+    VirtioVhostGpu,
-     VirtioVhostNet,
-     VirtioVhostNetCtl,
-     VirtioVsock,
-@@ -192,6 +193,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![]
- }
-@@ -271,6 +286,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 408611e29..08ba91908 100644
---- a/virtio-devices/src/transport/pci_device.rs
-+++ b/virtio-devices/src/transport/pci_device.rs
-@@ -1036,11 +1036,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 000000000..7eec07ba6
---- /dev/null
-+++ b/virtio-devices/src/vhost_user/gpu.rs
-@@ -0,0 +1,411 @@
-+// Copyright 2019 Intel Corporation. All Rights Reserved.
-+// Copyright 2022 Unikie
-+// Copyright 2023 Alyssa Ross <hi@alyssa.is>
-+// SPDX-License-Identifier: Apache-2.0
-+
-+use std::io::{self, Write};
-+use std::os::unix::io::AsRawFd;
-+use std::sync::{Arc, Barrier, Mutex};
-+use std::{result, thread};
-+
-+use event_monitor::event;
-+use log::error;
-+use seccompiler::SeccompAction;
-+use vhost::vhost_user::message::{
-+    VhostSharedMemoryRegion, VhostUserConfigFlags, VhostUserProtocolFeatures, VhostUserShmemMapMsg,
-+    VhostUserShmemUnmapMsg, VhostUserVirtioFeatures,
-+};
-+use vhost::vhost_user::{
-+    FrontendReqHandler, HandlerResult, VhostUserFrontend, VhostUserFrontendReqHandler,
-+};
-+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_device::UserspaceMapping;
-+use vm_memory::volatile_memory::PtrGuardMut;
-+use vm_memory::{GuestMemoryAtomic, VolatileMemory};
-+use vm_migration::{MigratableError, Pausable};
-+use vmm_sys_util::eventfd::EventFd;
-+
-+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, GuestMemoryMmap, GuestRegionMmap, MmapRegion,
-+    VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1, VirtioCommon, VirtioDevice, VirtioDeviceType,
-+    VirtioInterrupt, VirtioSharedMemoryList,
-+};
-+
-+const QUEUE_SIZES: &[u16] = &[256, 16];
-+const NUM_QUEUES: u16 = QUEUE_SIZES.len() as _;
-+
-+struct BackendReqHandler {
-+    mapping: Arc<MmapRegion>,
-+}
-+
-+impl BackendReqHandler {
-+    fn ptr_guard_mut(&self, offset: u64, len: u64) -> io::Result<PtrGuardMut> {
-+        let shm_offset = offset
-+            .try_into()
-+            .map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))?;
-+        let len = len
-+            .try_into()
-+            .map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))?;
-+        Ok(self
-+            .mapping
-+            .get_slice(shm_offset, len)
-+            .map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))?
-+            .ptr_guard_mut())
-+    }
-+}
-+
-+impl VhostUserFrontendReqHandler for BackendReqHandler {
-+    fn shmem_map(&self, req: &VhostUserShmemMapMsg, fd: &dyn AsRawFd) -> HandlerResult<u64> {
-+        let target = self.ptr_guard_mut(req.shm_offset, req.len)?;
-+
-+        // SAFETY: we've checked we're only giving addr and length
-+        // within the region, and are passing MAP_FIXED to ensure they
-+        // are respected.
-+        let ret = unsafe {
-+            libc::mmap(
-+                target.as_ptr().cast(),
-+                target.len(),
-+                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> {
-+        let target = self.ptr_guard_mut(req.shm_offset, req.len)?;
-+
-+        // SAFETY: we've checked we're only giving addr and length
-+        // within the region, and are passing MAP_FIXED to ensure they
-+        // are respected.
-+        let ret = unsafe {
-+            libc::mmap(
-+                target.as_ptr().cast(),
-+                target.len(),
-+                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)
-+    }
-+}
-+
-+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>,
-+    backend_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,
-+        seccomp_action: SeccompAction,
-+        exit_evt: EventFd,
-+        iommu: bool,
-+    ) -> Result<(Gpu, VhostSharedMemoryRegion)> {
-+        // 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::BACKEND_REQ
-+            | VhostUserProtocolFeatures::SHARED_MEMORY_REGIONS;
-+
-+        let (acked_features, acked_protocol_features) =
-+            vu.negotiate_features_vhost_user(avail_features, avail_protocol_features)?;
-+
-+        let shm_regions = vu.get_shared_memory_regions()?;
-+        if shm_regions.len() != 1 {
-+            return Err(Error::VhostUserUnexpectedSharedMemoryRegionsCount(
-+                1,
-+                shm_regions.len(),
-+            ));
-+        }
-+        let shm_region = shm_regions[0];
-+
-+        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: None,
-+                backend_req_support: acked_protocol_features
-+                    & VhostUserProtocolFeatures::BACKEND_REQ.bits()
-+                    != 0,
-+                seccomp_action,
-+                guest_memory: None,
-+                epoll_thread: None,
-+                exit_evt,
-+                iommu,
-+            },
-+            shm_region,
-+        ))
-+    }
-+
-+    pub fn set_cache(&mut self, cache: VirtioSharedMemoryList) {
-+        self.cache = Some(cache);
-+    }
-+}
-+
-+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
-+            && 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.clone())?;
-+        self.guest_memory = Some(mem.clone());
-+
-+        // Initialize backend communication.
-+        let backend_req_handler = if self.backend_req_support {
-+            if let Some(cache) = self.cache.as_ref() {
-+                let vu_frontend_req_handler = Arc::new(BackendReqHandler {
-+                    mapping: cache.mapping.clone(),
-+                });
-+
-+                let mut req_handler =
-+                    FrontendReqHandler::new(vu_frontend_req_handler).map_err(|e| {
-+                        ActivateError::VhostUserGpuSetup(Error::FrontendReqHandlerCreation(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,
-+            backend_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.as_ref().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
-+            && 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> {
-+        self.cache.clone()
-+    }
-+
-+    fn set_shm_regions(
-+        &mut self,
-+        shm_regions: VirtioSharedMemoryList,
-+    ) -> std::result::Result<(), crate::Error> {
-+        if let Some(cache) = self.cache.as_mut() {
-+            *cache = 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 {
-+                mapping: cache.mapping.clone(),
-+                mem_slot: cache.mem_slot,
-+                addr: cache.addr,
-+                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 05233a0be..f2e83ff84 100644
---- a/virtio-devices/src/vhost_user/mod.rs
-+++ b/virtio-devices/src/vhost_user/mod.rs
-@@ -33,11 +33,13 @@ use crate::{
- 
- 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;
- 
-@@ -75,6 +77,8 @@ pub enum Error {
-     VhostUserGetQueueMaxNum(#[source] VhostError),
-     #[error("Get protocol features failed")]
-     VhostUserGetProtocolFeatures(#[source] VhostError),
-+    #[error("Get shared memory regions failed")]
-+    VhostUserGetSharedMemoryRegions(#[source] VhostError),
-     #[error("Get vring base failed")]
-     VhostUserGetVringBase(#[source] VhostError),
-     #[error("Vhost-user Backend not support vhost-user protocol")]
-@@ -123,6 +127,8 @@ pub enum Error {
-     VhostUserSetInflight(#[source] VhostError),
-     #[error("Failed setting the log base")]
-     VhostUserSetLogBase(#[source] VhostError),
-+    #[error("Expected {0} shared memory regions; got {1}")]
-+    VhostUserUnexpectedSharedMemoryRegionsCount(usize, usize),
-     #[error("Invalid used address")]
-     UsedAddress,
-     #[error("Invalid features provided from vhost-user backend")]
-diff --git a/virtio-devices/src/vhost_user/vu_common_ctrl.rs b/virtio-devices/src/vhost_user/vu_common_ctrl.rs
-index 264635149..220b49112 100644
---- a/virtio-devices/src/vhost_user/vu_common_ctrl.rs
-+++ b/virtio-devices/src/vhost_user/vu_common_ctrl.rs
-@@ -13,7 +13,8 @@ use std::time::{Duration, Instant};
- use log::{error, info};
- use vhost::vhost_kern::vhost_binding::{VHOST_F_LOG_ALL, VHOST_VRING_F_LOG};
- use vhost::vhost_user::message::{
--    VhostUserHeaderFlag, VhostUserInflight, VhostUserProtocolFeatures, VhostUserVirtioFeatures,
-+    VhostSharedMemoryRegion, VhostUserHeaderFlag, VhostUserInflight, VhostUserProtocolFeatures,
-+    VhostUserVirtioFeatures,
- };
- use vhost::vhost_user::{
-     Frontend, FrontendReqHandler, VhostUserFrontend, VhostUserFrontendReqHandler,
-@@ -107,6 +108,12 @@ impl VhostUserHandle {
-             .map_err(Error::VhostUserAddMemReg)
-     }
- 
-+    pub fn get_shared_memory_regions(&self) -> Result<Vec<VhostSharedMemoryRegion>> {
-+        self.vu
-+            .get_shared_memory_regions()
-+            .map_err(Error::VhostUserGetSharedMemoryRegions)
-+    }
-+
-     pub fn negotiate_features_vhost_user(
-         &mut self,
-         avail_features: u64,
-diff --git a/vmm/src/api/dbus/mod.rs b/vmm/src/api/dbus/mod.rs
-index 6f75fb5cd..78275925d 100644
---- a/vmm/src/api/dbus/mod.rs
-+++ b/vmm/src/api/dbus/mod.rs
-@@ -22,7 +22,7 @@ use super::{ApiAction, ApiRequest};
- #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))]
- use crate::api::VmCoredump;
- use crate::api::{
--    AddDisk, Body, VmAddDevice, VmAddFs, VmAddNet, VmAddPmem, VmAddUserDevice, VmAddVdpa,
-+    AddDisk, Body, VmAddDevice, VmAddFs, VmAddGpu, VmAddNet, VmAddPmem, VmAddUserDevice, VmAddVdpa,
-     VmAddVsock, VmBoot, VmCounters, VmCreate, VmDelete, VmInfo, VmPause, VmPowerButton, VmReboot,
-     VmReceiveMigration, VmRemoveDevice, VmResize, VmResizeZone, VmRestore, VmResume,
-     VmSendMigration, VmShutdown, VmSnapshot, VmmPing, VmmShutdown,
-@@ -144,6 +144,11 @@ impl DBusApi {
-         self.vm_action(&VmAddFs, fs_config).await
-     }
- 
-+    async fn vm_add_gpu(&self, gpu_config: String) -> Result<Optional<String>> {
-+        let gpu_config = serde_json::from_str(&gpu_config).map_err(api_error)?;
-+        self.vm_action(&VmAddGpu, gpu_config).await
-+    }
-+
-     async fn vm_add_net(&self, net_config: String) -> Result<Optional<String>> {
-         let mut net_config: NetConfig = serde_json::from_str(&net_config).map_err(api_error)?;
-         if net_config.fds.is_some() {
-diff --git a/vmm/src/api/http/http_endpoint.rs b/vmm/src/api/http/http_endpoint.rs
-index e463a2081..0936eff22 100644
---- a/vmm/src/api/http/http_endpoint.rs
-+++ b/vmm/src/api/http/http_endpoint.rs
-@@ -45,10 +45,10 @@ use crate::api::VmCoredump;
- use crate::api::http::http_endpoint::fds_helper::{attach_fds_to_cfg, attach_fds_to_cfgs};
- use crate::api::http::{EndpointHandler, HttpError, error_response};
- use crate::api::{
--    AddDisk, ApiAction, ApiError, ApiRequest, NetConfig, VmAddDevice, VmAddFs, VmAddNet, VmAddPmem,
--    VmAddUserDevice, VmAddVdpa, VmAddVsock, VmBoot, VmConfig, VmCounters, VmDelete, VmNmi, VmPause,
--    VmPowerButton, VmReboot, VmReceiveMigration, VmRemoveDevice, VmResize, VmResizeDisk,
--    VmResizeZone, VmRestore, VmResume, VmSendMigration, VmShutdown, VmSnapshot,
-+    AddDisk, ApiAction, ApiError, ApiRequest, NetConfig, VmAddDevice, VmAddFs, VmAddGpu, VmAddNet,
-+    VmAddPmem, VmAddUserDevice, VmAddVdpa, VmAddVsock, VmBoot, VmConfig, VmCounters, VmDelete,
-+    VmNmi, VmPause, VmPowerButton, VmReboot, VmReceiveMigration, VmRemoveDevice, VmResize,
-+    VmResizeDisk, VmResizeZone, VmRestore, VmResume, VmSendMigration, VmShutdown, VmSnapshot,
- };
- use crate::config::RestoreConfig;
- use crate::cpu::Error as CpuError;
-@@ -419,6 +419,7 @@ vm_action_put_handler!(VmNmi);
- vm_action_put_handler_body!(VmAddDevice);
- vm_action_put_handler_body!(AddDisk);
- vm_action_put_handler_body!(VmAddFs);
-+vm_action_put_handler_body!(VmAddGpu);
- vm_action_put_handler_body!(VmAddPmem);
- vm_action_put_handler_body!(VmAddVdpa);
- vm_action_put_handler_body!(VmAddVsock);
-diff --git a/vmm/src/api/http/mod.rs b/vmm/src/api/http/mod.rs
-index 2aa52e8e3..7eccb57d1 100644
---- a/vmm/src/api/http/mod.rs
-+++ b/vmm/src/api/http/mod.rs
-@@ -28,10 +28,10 @@ use self::http_endpoint::{VmActionHandler, VmCreate, VmInfo, VmmPing, VmmShutdow
- #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))]
- use crate::api::VmCoredump;
- use crate::api::{
--    AddDisk, ApiError, ApiRequest, VmAddDevice, VmAddFs, VmAddNet, VmAddPmem, VmAddUserDevice,
--    VmAddVdpa, VmAddVsock, VmBoot, VmCounters, VmDelete, VmNmi, VmPause, VmPowerButton, VmReboot,
--    VmReceiveMigration, VmRemoveDevice, VmResize, VmResizeDisk, VmResizeZone, VmRestore, VmResume,
--    VmSendMigration, VmShutdown, VmSnapshot,
-+    AddDisk, ApiError, ApiRequest, VmAddDevice, VmAddFs, VmAddGpu, VmAddNet, VmAddPmem,
-+    VmAddUserDevice, VmAddVdpa, VmAddVsock, VmBoot, VmCounters, VmDelete, VmNmi, VmPause,
-+    VmPowerButton, VmReboot, VmReceiveMigration, VmRemoveDevice, VmResize, VmResizeDisk,
-+    VmResizeZone, VmRestore, VmResume, VmSendMigration, VmShutdown, VmSnapshot,
- };
- use crate::landlock::Landlock;
- use crate::seccomp_filters::{Thread, get_seccomp_filter};
-@@ -196,6 +196,10 @@ pub static HTTP_ROUTES: LazyLock<HttpRoutes> = LazyLock::new(|| {
-         endpoint!("/vm.add-fs"),
-         Box::new(VmActionHandler::new(&VmAddFs)),
-     );
-+    r.routes.insert(
-+        endpoint!("/vm.add-gpu"),
-+        Box::new(VmActionHandler::new(&VmAddGpu)),
-+    );
-     r.routes.insert(
-         endpoint!("/vm.add-net"),
-         Box::new(VmActionHandler::new(&VmAddNet)),
-diff --git a/vmm/src/api/mod.rs b/vmm/src/api/mod.rs
-index 12ca6b987..aaafe3843 100644
---- a/vmm/src/api/mod.rs
-+++ b/vmm/src/api/mod.rs
-@@ -51,8 +51,8 @@ use crate::config::RestoreConfig;
- use crate::device_tree::DeviceTree;
- use crate::vm::{Error as VmError, VmState};
- use crate::vm_config::{
--    DeviceConfig, DiskConfig, FsConfig, NetConfig, PmemConfig, UserDeviceConfig, VdpaConfig,
--    VmConfig, VsockConfig,
-+    DeviceConfig, DiskConfig, FsConfig, GpuConfig, NetConfig, PmemConfig, UserDeviceConfig,
-+    VdpaConfig, VmConfig, VsockConfig,
- };
- 
- /// API errors are sent back from the VMM API server through the ApiResponse.
-@@ -170,6 +170,10 @@ pub enum ApiError {
-     #[error("The fs could not be added to the VM")]
-     VmAddFs(#[source] VmError),
- 
-+    /// The gpu could not be added to the VM.
-+    #[error("The GPU could not be added to the VM: {0}")]
-+    VmAddGpu(#[source] VmError),
-+
-     /// The pmem device could not be added to the VM.
-     #[error("The pmem device could not be added to the VM")]
-     VmAddPmem(#[source] VmError),
-@@ -340,6 +344,8 @@ pub trait RequestHandler {
- 
-     fn vm_add_fs(&mut self, fs_cfg: FsConfig) -> Result<Option<Vec<u8>>, VmError>;
- 
-+    fn vm_add_gpu(&mut self, gpu_cfg: GpuConfig) -> Result<Option<Vec<u8>>, VmError>;
-+
-     fn vm_add_pmem(&mut self, pmem_cfg: PmemConfig) -> Result<Option<Vec<u8>>, VmError>;
- 
-     fn vm_add_net(&mut self, net_cfg: NetConfig) -> Result<Option<Vec<u8>>, VmError>;
-@@ -539,6 +545,43 @@ impl ApiAction for VmAddFs {
-     }
- }
- 
-+pub struct VmAddGpu;
-+
-+impl ApiAction for VmAddGpu {
-+    type RequestBody = GpuConfig;
-+    type ResponseBody = Option<Body>;
-+
-+    fn request(
-+        &self,
-+        config: Self::RequestBody,
-+        response_sender: Sender<ApiResponse>,
-+    ) -> ApiRequest {
-+        Box::new(move |vmm| {
-+            info!("API request event: VmAddGpu {config:?}");
-+
-+            let response = vmm
-+                .vm_add_gpu(config)
-+                .map_err(ApiError::VmAddGpu)
-+                .map(ApiResponsePayload::VmAction);
-+
-+            response_sender
-+                .send(response)
-+                .map_err(VmmError::ApiResponseSend)?;
-+
-+            Ok(false)
-+        })
-+    }
-+
-+    fn send(
-+        &self,
-+        api_evt: EventFd,
-+        api_sender: Sender<ApiRequest>,
-+        data: Self::RequestBody,
-+    ) -> ApiResult<Self::ResponseBody> {
-+        get_response_body(self, api_evt, api_sender, data)
-+    }
-+}
-+
- pub struct VmAddPmem;
- 
- impl ApiAction for VmAddPmem {
-diff --git a/vmm/src/api/openapi/cloud-hypervisor.yaml b/vmm/src/api/openapi/cloud-hypervisor.yaml
-index 1fa3d9b51..cc290e27f 100644
---- a/vmm/src/api/openapi/cloud-hypervisor.yaml
-+++ b/vmm/src/api/openapi/cloud-hypervisor.yaml
-@@ -277,6 +277,28 @@ paths:
-         500:
-           description: The new device could not be added to the VM instance.
- 
-+  /vm.add-gpu:
-+    put:
-+      summary: Add a new virtio-gpu device to the VM
-+      requestBody:
-+        description: The details of the new virtio-gpu
-+        content:
-+          application/json:
-+            schema:
-+              $ref: "#/components/schemas/GpuConfig"
-+        required: true
-+      responses:
-+        200:
-+          description: The new device was successfully added to the VM instance.
-+          content:
-+            application/json:
-+              schema:
-+                $ref: "#/components/schemas/PciDeviceInfo"
-+        204:
-+          description: The new device was successfully (cold) added to the VM instance.
-+        500:
-+          description: The new device could not be added to the VM instance.
-+
-   /vm.add-pmem:
-     put:
-       summary: Add a new pmem device to the VM
-@@ -603,6 +625,10 @@ components:
-           type: array
-           items:
-             $ref: "#/components/schemas/FsConfig"
-+        gpu:
-+          type: array
-+          items:
-+            $ref: "#/components/schemas/GpuConfig"
-         pmem:
-           type: array
-           items:
-@@ -1044,6 +1070,19 @@ components:
-         id:
-           type: string
- 
-+    GpuConfig:
-+      required:
-+        - socket
-+      type: object
-+      properties:
-+        socket:
-+          type: string
-+        pci_segment:
-+          type: integer
-+          format: int16
-+        id:
-+          type: string
-+
-     PmemConfig:
-       required:
-         - file
-diff --git a/vmm/src/config.rs b/vmm/src/config.rs
-index 78d6f9f1e..e1a54b0fa 100644
---- a/vmm/src/config.rs
-+++ b/vmm/src/config.rs
-@@ -45,6 +45,9 @@ pub enum Error {
-     /// Filesystem socket is missing
-     #[error("Error parsing --fs: socket missing")]
-     ParseFsSockMissing,
-+    /// GPU socket is missing
-+    #[error("Error parsing --gpu: socket missing")]
-+    ParseGpuSockMissing,
-     /// Missing persistent memory file parameter.
-     #[error("Error parsing --pmem: file missing")]
-     ParsePmemFileMissing,
-@@ -90,6 +93,9 @@ pub enum Error {
-     /// Error parsing filesystem parameters
-     #[error("Error parsing --fs")]
-     ParseFileSystem(#[source] OptionParserError),
-+    /// Error parsing GPU parameters
-+    #[error("Error parsing --gpu")]
-+    ParseGpu(#[source] OptionParserError),
-     /// Error parsing persistent memory parameters
-     #[error("Error parsing --pmem")]
-     ParsePersistentMemory(#[source] OptionParserError),
-@@ -390,6 +396,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,
-@@ -451,6 +458,9 @@ impl<'a> VmParams<'a> {
-         let fs: Option<Vec<&str>> = args
-             .get_many::<String>("fs")
-             .map(|x| x.map(|y| y as &str).collect());
-+        let gpu: Option<Vec<&str>> = args
-+            .get_many::<String>("gpu")
-+            .map(|x| x.map(|y| y as &str).collect());
-         let pmem: Option<Vec<&str>> = args
-             .get_many::<String>("pmem")
-             .map(|x| x.map(|y| y as &str).collect());
-@@ -505,6 +515,7 @@ impl<'a> VmParams<'a> {
-             rng,
-             balloon,
-             fs,
-+            gpu,
-             pmem,
-             serial,
-             console,
-@@ -1766,6 +1777,49 @@ impl FwCfgItem {
-     }
- }
- 
-+impl GpuConfig {
-+    pub const SYNTAX: &'static str = "virtio-gpu parameters \
-+    \"socket=<socket_path>,id=<device_id>,pci_segment=<segment_id>\"";
-+
-+    pub fn parse(gpu: &str) -> Result<Self> {
-+        let mut parser = OptionParser::new();
-+        parser.add("socket").add("id").add("pci_segment");
-+        parser.parse(gpu).map_err(Error::ParseGpu)?;
-+
-+        let socket = PathBuf::from(parser.get("socket").ok_or(Error::ParseGpuSockMissing)?);
-+        let id = parser.get("id");
-+
-+        let pci_segment = parser
-+            .convert("pci_segment")
-+            .map_err(Error::ParseGpu)?
-+            .unwrap_or_default();
-+
-+        Ok(GpuConfig {
-+            socket,
-+            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()
-+                && iommu_segments.contains(&self.pci_segment)
-+            {
-+                return Err(ValidationError::IommuNotSupportedOnSegment(
-+                    self.pci_segment,
-+                ));
-+            }
-+        }
-+
-+        Ok(())
-+    }
-+}
-+
- impl PmemConfig {
-     pub const SYNTAX: &'static str = "Persistent memory parameters \
-     \"file=<backing_file_path>,size=<persistent_memory_size>,iommu=on|off,\
-@@ -2624,6 +2678,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)?;
-@@ -2876,6 +2941,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();
-@@ -3012,6 +3086,7 @@ impl VmConfig {
-             rng,
-             balloon,
-             fs,
-+            gpu,
-             pmem,
-             serial,
-             console,
-@@ -3073,6 +3148,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();
-@@ -3145,6 +3227,7 @@ impl Clone for VmConfig {
-             #[cfg(feature = "pvmemcontrol")]
-             pvmemcontrol: self.pvmemcontrol.clone(),
-             fs: self.fs.clone(),
-+            gpu: self.gpu.clone(),
-             pmem: self.pmem.clone(),
-             serial: self.serial.clone(),
-             console: self.console.clone(),
-@@ -3665,6 +3748,23 @@ mod unit_tests {
-         Ok(())
-     }
- 
-+    fn gpu_fixture() -> GpuConfig {
-+        GpuConfig {
-+            socket: PathBuf::from("/tmp/sock"),
-+            id: None,
-+            pci_segment: 0,
-+        }
-+    }
-+
-+    #[test]
-+    fn test_parse_gpu() -> Result<()> {
-+        // "socket" must be supplied
-+        assert!(GpuConfig::parse("").is_err());
-+        assert_eq!(GpuConfig::parse("socket=/tmp/sock")?, gpu_fixture());
-+
-+        Ok(())
-+    }
-+
-     fn pmem_fixture() -> PmemConfig {
-         PmemConfig {
-             file: PathBuf::from("/tmp/pmem"),
-@@ -3936,6 +4036,7 @@ mod unit_tests {
-             rng: RngConfig::default(),
-             balloon: None,
-             fs: None,
-+            gpu: None,
-             pmem: None,
-             serial: default_serial(),
-             console: default_console(),
-@@ -4138,6 +4239,7 @@ mod unit_tests {
-             },
-             balloon: None,
-             fs: None,
-+            gpu: None,
-             pmem: None,
-             serial: ConsoleConfig {
-                 file: None,
-@@ -4334,6 +4436,13 @@ mod unit_tests {
-             Err(ValidationError::VhostUserRequiresSharedMemory)
-         );
- 
-+        let mut invalid_config = valid_config.clone();
-+        invalid_config.gpu = Some(vec![gpu_fixture()]);
-+        assert_eq!(
-+            invalid_config.validate(),
-+            Err(ValidationError::VhostUserRequiresSharedMemory)
-+        );
-+
-         let mut still_valid_config = valid_config.clone();
-         still_valid_config.memory.shared = true;
-         still_valid_config.validate().unwrap();
-diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs
-index 6d465047b..7eb1358e1 100644
---- a/vmm/src/device_manager.rs
-+++ b/vmm/src/device_manager.rs
-@@ -12,6 +12,7 @@
- use std::collections::{BTreeMap, BTreeSet, HashMap};
- use std::fs::{File, OpenOptions};
- use std::io::{self, IsTerminal, Seek, SeekFrom, stdout};
-+use std::iter::once;
- use std::num::Wrapping;
- use std::os::unix::fs::OpenOptionsExt;
- use std::os::unix::io::{AsRawFd, FromRawFd};
-@@ -91,7 +92,7 @@ use virtio_devices::transport::{VirtioPciDevice, VirtioPciDeviceActivator, Virti
- use virtio_devices::vhost_user::VhostUserConfig;
- use virtio_devices::{
-     AccessPlatformMapping, ActivateError, Block, Endpoint, IommuMapping, VdpaDmaMapping,
--    VirtioMemMappingSource,
-+    VirtioMemMappingSource, VirtioSharedMemory, VirtioSharedMemoryList,
- };
- use vm_allocator::{AddressAllocator, SystemAllocator};
- use vm_device::dma_mapping::ExternalDmaMapping;
-@@ -124,8 +125,8 @@ use crate::serial_manager::{Error as SerialManagerError, SerialManager};
- use crate::vm_config::IvshmemConfig;
- use crate::vm_config::{
-     ConsoleOutputMode, DEFAULT_IOMMU_ADDRESS_WIDTH_BITS, DEFAULT_PCI_SEGMENT_APERTURE_WEIGHT,
--    DeviceConfig, DiskConfig, FsConfig, NetConfig, PmemConfig, UserDeviceConfig, VdpaConfig,
--    VhostMode, VmConfig, VsockConfig,
-+    DeviceConfig, DiskConfig, FsConfig, GpuConfig, NetConfig, PmemConfig, UserDeviceConfig,
-+    VdpaConfig, VhostMode, VmConfig, VsockConfig,
- };
- use crate::{DEVICE_MANAGER_SNAPSHOT_ID, GuestRegionMmap, PciDeviceInfo, device_node};
- 
-@@ -154,6 +155,7 @@ const IVSHMEM_DEVICE_NAME: &str = "__ivshmem";
- // 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";
-@@ -198,10 +200,18 @@ pub enum DeviceManagerError {
-     #[error("Cannot create virtio-fs device")]
-     CreateVirtioFs(#[source] virtio_devices::vhost_user::Error),
- 
-+    /// Cannot create virtio-gpu device
-+    #[error("Cannot create virtio-gpu device: {0}")]
-+    CreateVirtioGpu(#[source] virtio_devices::vhost_user::Error),
-+
-     /// Virtio-fs device was created without a socket.
-     #[error("Virtio-fs device was created without a socket")]
-     NoVirtioFsSock,
- 
-+    /// Virtio-gpu device was created without a socket.
-+    #[error("Virtio-gpu device was created without a socket")]
-+    NoVirtioGpuSock,
-+
-     /// Cannot create vhost-user-blk device
-     #[error("Cannot create vhost-user-blk device")]
-     CreateVhostUserBlk(#[source] virtio_devices::vhost_user::Error),
-@@ -319,6 +329,10 @@ pub enum DeviceManagerError {
-     #[error("Cannot find a memory range for virtio-fs")]
-     FsRangeAllocation,
- 
-+    /// Cannot find a memory range for virtio-gpu
-+    #[error("Cannot find a memory range for virtio-gpu")]
-+    GpuRangeAllocation,
-+
-     /// Error creating serial output file
-     #[error("Error creating serial output file")]
-     SerialOutputFileOpen(#[source] io::Error),
-@@ -2537,6 +2551,9 @@ impl DeviceManager {
-         // Add virtio-fs if required
-         self.make_virtio_fs_devices()?;
- 
-+        // Add virtio-gpu if required
-+        self.make_virtio_gpu_devices()?;
-+
-         // Add virtio-pmem if required
-         self.make_virtio_pmem_devices()?;
- 
-@@ -3117,6 +3134,119 @@ impl DeviceManager {
-         Ok(())
-     }
- 
-+    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 (mut virtio_gpu_device, region) = virtio_devices::vhost_user::Gpu::new(
-+                id.clone(),
-+                gpu_socket,
-+                self.seccomp_action.clone(),
-+                self.exit_evt
-+                    .try_clone()
-+                    .map_err(DeviceManagerError::EventFd)?,
-+                self.force_iommu,
-+            )
-+            .map_err(DeviceManagerError::CreateVirtioGpu)?;
-+
-+            // In crosvm, the 8 GiB bar is 8 GiB-aligned.
-+            let cache_base = self.pci_segments[gpu_cfg.pci_segment as usize]
-+                .mem64_allocator
-+                .lock()
-+                .unwrap()
-+                .allocate(None, region.length as GuestUsize, Some(region.length))
-+                .ok_or(DeviceManagerError::GpuRangeAllocation)?
-+                .raw_value();
-+
-+            // Update the node with correct resource information.
-+            node.resources.push(Resource::MmioAddressRange {
-+                base: cache_base,
-+                size: region.length,
-+            });
-+
-+            let mmap_region = MmapRegion::build(
-+                None,
-+                region.length as usize,
-+                libc::PROT_NONE,
-+                libc::MAP_ANONYMOUS | libc::MAP_PRIVATE,
-+            )
-+            .map_err(DeviceManagerError::NewMmapRegion)?;
-+
-+            // SAFETY: `mmap_region.size()` and `mmap_region.as_ptr()` refer to an allocation.
-+            // We remove the userspace mapping before dropping the device if the device is
-+            // ejected.
-+            let mem_slot = unsafe {
-+                self.memory_manager
-+                    .lock()
-+                    .unwrap()
-+                    .create_userspace_mapping(
-+                        cache_base,
-+                        mmap_region.size(),
-+                        mmap_region.as_ptr(),
-+                        false,
-+                        false,
-+                        false,
-+                    )
-+                    .map_err(DeviceManagerError::MemoryManager)?
-+            };
-+
-+            let region_list = once((
-+                region.id,
-+                VirtioSharedMemory {
-+                    offset: 0,
-+                    len: region.length,
-+                },
-+            ))
-+            .collect();
-+
-+            virtio_gpu_device.set_cache(VirtioSharedMemoryList {
-+                mapping: Arc::new(mmap_region),
-+                mem_slot,
-+                addr: GuestAddress(cache_base),
-+                region_list,
-+            });
-+
-+            self.device_tree.lock().unwrap().insert(id.clone(), node);
-+
-+            Ok(MetaVirtioDevice {
-+                virtio_device: Arc::new(Mutex::new(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<()> {
-+        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() {
-+                let device = self.make_virtio_gpu_device(gpu_cfg)?;
-+                self.virtio_devices.push(device);
-+            }
-+        }
-+        self.config.lock().unwrap().gpu = gpu_devices;
-+
-+        Ok(())
-+    }
-+
-     fn make_virtio_pmem_device(
-         &mut self,
-         pmem_cfg: &mut PmemConfig,
-@@ -4541,6 +4671,7 @@ impl DeviceManager {
-                 VirtioDeviceType::Block
-                 | VirtioDeviceType::Pmem
-                 | VirtioDeviceType::Fs
-+                | VirtioDeviceType::Gpu
-                 | VirtioDeviceType::Vsock => {}
-                 _ => return Err(DeviceManagerError::RemovalNotAllowed(device_type)),
-             }
-@@ -4827,6 +4958,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 6917e005e..5a3db353a 100644
---- a/vmm/src/lib.rs
-+++ b/vmm/src/lib.rs
-@@ -59,8 +59,8 @@ use crate::migration::{recv_vm_config, recv_vm_state};
- use crate::seccomp_filters::{Thread, get_seccomp_filter};
- use crate::vm::{Error as VmError, Vm, VmState};
- use crate::vm_config::{
--    DeviceConfig, DiskConfig, FsConfig, NetConfig, PmemConfig, UserDeviceConfig, VdpaConfig,
--    VmConfig, VsockConfig,
-+    DeviceConfig, DiskConfig, FsConfig, GpuConfig, NetConfig, PmemConfig, UserDeviceConfig,
-+    VdpaConfig, VmConfig, VsockConfig,
- };
- 
- mod acpi;
-@@ -2120,6 +2120,31 @@ impl RequestHandler for 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).inspect_err(|e| {
-+                error!("Error when adding new gpu to the VM: {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)?;
- 
-@@ -2438,6 +2463,7 @@ mod unit_tests {
-             },
-             balloon: None,
-             fs: None,
-+            gpu: None,
-             pmem: None,
-             serial: ConsoleConfig {
-                 file: None,
-@@ -2674,6 +2700,55 @@ 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 536481885..d2e5de0fe 100644
---- a/vmm/src/vm.rs
-+++ b/vmm/src/vm.rs
-@@ -100,8 +100,8 @@ use crate::migration::{SNAPSHOT_CONFIG_FILE, SNAPSHOT_STATE_FILE, url_to_path};
- #[cfg(feature = "fw_cfg")]
- use crate::vm_config::FwCfgConfig;
- use crate::vm_config::{
--    DeviceConfig, DiskConfig, FsConfig, HotplugMethod, NetConfig, NumaConfig, PayloadConfig,
--    PmemConfig, UserDeviceConfig, VdpaConfig, VmConfig, VsockConfig,
-+    DeviceConfig, DiskConfig, FsConfig, GpuConfig, HotplugMethod, NetConfig, NumaConfig,
-+    PayloadConfig, PmemConfig, UserDeviceConfig, VdpaConfig, VmConfig, VsockConfig,
- };
- use crate::{
-     CPU_MANAGER_SNAPSHOT_ID, DEVICE_MANAGER_SNAPSHOT_ID, GuestMemoryMmap,
-@@ -1869,6 +1869,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 9c28e536d..f21974ac2 100644
---- a/vmm/src/vm_config.rs
-+++ b/vmm/src/vm_config.rs
-@@ -461,6 +461,15 @@ impl ApplyLandlock for FsConfig {
-     }
- }
- 
-+#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
-+pub struct GpuConfig {
-+    pub socket: PathBuf,
-+    #[serde(default)]
-+    pub id: Option<String>,
-+    #[serde(default)]
-+    pub pci_segment: u16,
-+}
-+
- #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
- pub struct PmemConfig {
-     pub file: PathBuf,
-@@ -913,6 +922,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.53.0
-
diff --git a/pkgs/cloud-hypervisor/default.nix b/pkgs/cloud-hypervisor/default.nix
index ee5a7ea..f224a59 100644
--- a/pkgs/cloud-hypervisor/default.nix
+++ b/pkgs/cloud-hypervisor/default.nix
@@ -8,40 +8,10 @@ super.cloud-hypervisor.overrideAttrs (oldAttrs: rec {
   cargoDeps = final.rustPlatform.fetchCargoVendor {
     inherit patches;
     inherit (oldAttrs) src;
-    hash = "sha256-wGtsyKDg1z1QK9mJ1Q43NSjoPbm3m81p++DoD8ipIUI=";
-  };
-
-  vhost = final.fetchFromGitHub {
-    name = "vhost";
-    owner = "rust-vmm";
-    repo = "vhost";
-    rev = "vhost-user-backend-v0.20.0";
-    hash = "sha256-KK1+mwYQr7YkyGT9+51v7TJael9D0lle2JXfRoTqYq8=";
+    hash = "sha256-ZNj1H3Iq+IUSe0McHJjrwPOoR+YRB+rsSmZHMhXsHy0=";
   };
 
   patches = oldAttrs.patches or [] ++ [
-    ./0001-build-use-local-vhost.patch
-    ./0002-virtio-devices-add-a-GPU-device.patch
+    ./gpu.patch
   ];
-
-  vhostPatches = [
-    vhost/0001-vhost_user-add-get_size-to-MsgHeader.patch
-    vhost/0002-vhost-fix-receiving-reply-payloads.patch
-    vhost/0003-vhost_user-add-shared-memory-region-support.patch
-    vhost/0004-vhost_user-add-protocol-flag-for-shmem.patch
-  ];
-
-  postUnpack = oldAttrs.postUnpack or "" + ''
-    unpackFile $vhost
-    chmod -R +w vhost
-  '';
-
-  postPatch = oldAttrs.postPatch or "" + ''
-    pushd ../vhost
-    for patch in $vhostPatches; do
-        echo applying patch $patch
-        patch -p1 < $patch
-    done
-    popd
-  '';
 }))
diff --git a/pkgs/cloud-hypervisor/gpu.patch b/pkgs/cloud-hypervisor/gpu.patch
new file mode 100644
index 0000000..47f562d
--- /dev/null
+++ b/pkgs/cloud-hypervisor/gpu.patch
@@ -0,0 +1,719 @@
+From 7aec033b6371aadba8924c317282821565cbba48 Mon Sep 17 00:00:00 2001
+From: Alyssa Ross <hi@alyssa.is>
+Date: Thu, 14 May 2026 15:54:02 +0200
+Subject: [PATCH 1/3] virtio-devices: use map for Shared Memory Regions
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+SPDX-FileCopyrightText: The Cloud Hypervisor Authors
+SPDX-FileCopyrightText: 2018 The Chromium OS Authors. All rights reserved.
+SPDX-FileCopyrightText: 2019 Intel Corporation
+SPDX-License-Identifier: Apache-2.0 AND LicenseRef-BSD-3-Clause-Google
+
+A vhost-user backend can use whichever shared memory indices it likes
+— it might decide only to use index 200 — so we have to be able to
+handle the case where lower indices are not used.  BTreeMap is used so
+that capabilities are added in a consistent order.
+
+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>
+---
+ virtio-devices/src/device.rs               | 4 ++--
+ virtio-devices/src/transport/pci_device.rs | 4 ++--
+ 2 files changed, 4 insertions(+), 4 deletions(-)
+
+REUSE-IgnoreStart
+
+diff --git a/virtio-devices/src/device.rs b/virtio-devices/src/device.rs
+index 4c61ba35d..8b4410173 100644
+--- a/virtio-devices/src/device.rs
++++ b/virtio-devices/src/device.rs
+@@ -6,7 +6,7 @@
+ //
+ // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
+ 
+-use std::collections::HashMap;
++use std::collections::{BTreeMap, HashMap};
+ use std::io::Write;
+ use std::num::Wrapping;
+ use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
+@@ -58,7 +58,7 @@ pub struct VirtioSharedMemoryList {
+     pub mem_slot: u32,
+     pub addr: GuestAddress,
+     pub mapping: Arc<MmapRegion>,
+-    pub region_list: Vec<VirtioSharedMemory>,
++    pub region_list: BTreeMap<u8, VirtioSharedMemory>,
+ }
+ 
+ pub struct ActivationContext {
+diff --git a/virtio-devices/src/transport/pci_device.rs b/virtio-devices/src/transport/pci_device.rs
+index ac37c5893..5ce57a895 100644
+--- a/virtio-devices/src/transport/pci_device.rs
++++ b/virtio-devices/src/transport/pci_device.rs
+@@ -1092,11 +1092,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,
+                     );
+-- 
+2.54.0
+
+REUSE-IgnoreEnd
+
+From cffac7b3aa9d8b630ed5231cf87442a23741c9d9 Mon Sep 17 00:00:00 2001
+From: Alyssa Ross <hi@alyssa.is>
+Date: Mon, 16 Mar 2026 17:51:36 +0100
+Subject: [PATCH 2/3] virtio-devices: implement VHOST_USER_PROTOCOL_F_SHMEM
+SPDX-FileCopyrightText: The Cloud Hypervisor Authors
+SPDX-FileCopyrightText: 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+SPDX-FileCopyrightText: 2017 The Chromium OS Authors. All rights reserved.
+SPDX-FileCopyrightText: 2019 Intel Corporation
+SPDX-FileCopyrightText: 2022 Unikie
+SPDX-FileCopyrightText: 2023-2026 Alyssa Ross <hi@alyssa.is>
+SPDX-FileCopyrightText: 2025 Demi Marie Obenour.
+SPDX-License-Identifier: Apache-2.0 AND LicenseRef-BSD-3-Clause-Google
+
+This was originally adapted from the code previously in Cloud
+Hypervisor to support virtio-fs DAX, which used some somewhat similar
+non-standard messages, but it has been adapted a lot over the years,
+first to crosvm's non-standard but non-DAX-specific mapping messages,
+and now to the standardised messages.  The existing cache member on
+certain devices in Cloud Hypervisor is also a remnant of this, because
+that's what shared memory was used for with virtio-fs.  Here I've
+renamed it to the more generic "shared_memory".
+
+One aspect of the implementation that's not ideal is the back and
+forth between the device and the device manager.  Cloud Hypervisor is
+designed so that the device manager sets up the shared memory regions,
+but it can't do that until after vhost-user messages have been
+exchanged to figure out how much memory is required, so the device has
+to exist first.  This means we call GenericVhostUser::new, allocate
+the regions, and then call GenericVhostUser::set_shared_memory to get
+those regions into the device, even though it would be nicer if those
+were just an argument to new.  A better way to do this is not obvious
+to me.
+
+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>
+---
+ virtio-devices/src/lib.rs                     |   4 +-
+ .../src/vhost_user/generic_vhost_user.rs      | 243 +++++++++++++++---
+ virtio-devices/src/vhost_user/mod.rs          |   6 +
+ .../src/vhost_user/vu_common_ctrl.rs          |   8 +-
+ vmm/src/device_manager.rs                     |  85 +++++-
+ 5 files changed, 302 insertions(+), 44 deletions(-)
+
+REUSE-IgnoreStart
+
+diff --git a/virtio-devices/src/lib.rs b/virtio-devices/src/lib.rs
+index 6ac397798..72b10ac6d 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::{
+     ActivationContext, DmaRemapping, VirtioCommon, VirtioDevice, VirtioInterrupt,
+-    VirtioInterruptType, VirtioSharedMemoryList,
++    VirtioInterruptType, VirtioSharedMemory, VirtioSharedMemoryList,
+ };
+ pub use self::epoll_helper::{
+     EPOLL_HELPER_EVENT_LAST, EpollHelper, EpollHelperError, EpollHelperHandler,
+@@ -114,6 +114,8 @@ pub enum ActivateError {
+     VhostUserFsSetup(#[source] vhost_user::Error),
+     #[error("Failed to setup vhost-user daemon")]
+     VhostUserSetup(#[source] vhost_user::Error),
++    #[error("Failed to setup generic vhost-user daemon")]
++    GenericVhostUserSetup(#[source] vhost_user::Error),
+     #[error("Failed to create seccomp filter")]
+     CreateSeccompFilter(#[source] seccompiler::Error),
+     #[error("Failed to create rate limiter")]
+diff --git a/virtio-devices/src/vhost_user/generic_vhost_user.rs b/virtio-devices/src/vhost_user/generic_vhost_user.rs
+index 5a302dc55..c904bb470 100644
+--- a/virtio-devices/src/vhost_user/generic_vhost_user.rs
++++ b/virtio-devices/src/vhost_user/generic_vhost_user.rs
+@@ -1,7 +1,12 @@
+ // Copyright 2019 Intel Corporation. All Rights Reserved.
++// Copyright 2022 Unikie
++// Copyright 2023, 2025-2026 Alyssa Ross <hi@alyssa.is>
+ // Copyright 2025 Demi Marie Obenour.
+ // SPDX-License-Identifier: Apache-2.0
+ 
++use std::collections::{BTreeMap, HashMap};
++use std::io::{self, ErrorKind};
++use std::os::fd::AsRawFd;
+ use std::result;
+ use std::sync::atomic::{AtomicBool, Ordering};
+ use std::sync::{Arc, Barrier, Mutex};
+@@ -10,11 +15,15 @@ use event_monitor::event;
+ use log::{error, info, warn};
+ use seccompiler::SeccompAction;
+ use vhost::vhost_user::message::{
+-    VhostUserConfigFlags, VhostUserProtocolFeatures, VhostUserVirtioFeatures,
++    VhostUserConfigFlags, VhostUserMMap, VhostUserMMapFlags, VhostUserProtocolFeatures,
++    VhostUserVirtioFeatures,
++};
++use vhost::vhost_user::{
++    FrontendReqHandler, HandlerResult, VhostUserFrontend, VhostUserFrontendReqHandler,
+ };
+-use vhost::vhost_user::{FrontendReqHandler, VhostUserFrontend, VhostUserFrontendReqHandler};
+ use vm_device::UserspaceMapping;
+-use vm_memory::GuestMemoryAtomic;
++use vm_memory::volatile_memory::PtrGuardMut;
++use vm_memory::{GuestMemoryAtomic, VolatileMemory};
+ use vm_migration::protocol::MemoryRangeTable;
+ use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
+ use vmm_sys_util::eventfd::EventFd;
+@@ -31,8 +40,33 @@ use crate::{
+ 
+ pub type State = VhostUserState<()>;
+ 
++struct ShmemRegion {
++    region: Arc<MmapRegion>,
++    mappings: Mutex<HashMap<u64, u64>>,
++}
++
+ struct BackendReqHandler {
+     interrupt_cb: Arc<dyn VirtioInterrupt>,
++    region: Option<ShmemRegion>,
++}
++
++impl BackendReqHandler {
++    fn ptr_guard_mut(&self, offset: u64, len: u64) -> io::Result<PtrGuardMut> {
++        let shm_offset = offset
++            .try_into()
++            .map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))?;
++        let len = len
++            .try_into()
++            .map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))?;
++        Ok(self
++            .region
++            .as_ref()
++            .ok_or(io::Error::from_raw_os_error(libc::EINVAL))?
++            .region
++            .get_slice(shm_offset, len)
++            .map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))?
++            .ptr_guard_mut())
++    }
+ }
+ 
+ impl VhostUserFrontendReqHandler for BackendReqHandler {
+@@ -45,6 +79,114 @@ impl VhostUserFrontendReqHandler for BackendReqHandler {
+             })?;
+         Ok(0)
+     }
++
++    fn shmem_map(&self, req: &VhostUserMMap, fd: &dyn AsRawFd) -> HandlerResult<u64> {
++        let target = self.ptr_guard_mut(req.shm_offset, req.len)?;
++
++        let Some(flags) = VhostUserMMapFlags::from_bits(req.flags) else {
++            return Err(ErrorKind::InvalidInput.into());
++        };
++
++        if !(flags - VhostUserMMapFlags::WRITABLE).is_empty() {
++            return Err(ErrorKind::InvalidInput.into());
++        }
++
++        let Some(ref region) = self.region else {
++            return Err(ErrorKind::InvalidInput.into());
++        };
++
++        let mut mappings = region.mappings.lock().unwrap();
++
++        // Overflows are disallowed.
++        for (&mapped_offset, mapped_len) in mappings.iter() {
++            // ptr_guard_mut has already checked that addition does not overflow.
++            if (req.shm_offset >= mapped_offset && req.shm_offset < mapped_offset + mapped_len)
++                || (req.shm_offset + req.len >= mapped_offset
++                    && req.shm_offset + req.len < mapped_offset + mapped_len)
++            {
++                return Err(ErrorKind::InvalidInput.into());
++            }
++        }
++
++        // SAFETY: we've checked we're only giving addr and length
++        // within the region, and are passing MAP_FIXED to ensure they
++        // are respected.
++        let ret = unsafe {
++            libc::mmap(
++                target.as_ptr().cast(),
++                target.len(),
++                if flags.contains(VhostUserMMapFlags::WRITABLE) {
++                    libc::PROT_WRITE
++                } else {
++                    0
++                } | libc::PROT_READ,
++                // https://bugzilla.kernel.org/show_bug.cgi?id=217238
++                if flags.contains(VhostUserMMapFlags::WRITABLE) {
++                    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());
++        }
++
++        mappings.insert(req.shm_offset, req.len);
++
++        Ok(0)
++    }
++
++    fn shmem_unmap(&self, req: &VhostUserMMap) -> HandlerResult<u64> {
++        let target = self.ptr_guard_mut(req.shm_offset, req.len)?;
++
++        if req.flags != 0 {
++            return Err(ErrorKind::InvalidInput.into());
++        }
++
++        let Some(ref region) = self.region else {
++            return Err(ErrorKind::InvalidInput.into());
++        };
++
++        let mut mappings = region.mappings.lock().unwrap();
++
++        if mappings.get(&{ req.shm_offset }) != Some(&{ req.len }) {
++            return Err(ErrorKind::InvalidInput.into());
++        }
++
++        // SAFETY: we control this mapping, and we know the mapping
++        // for the whole MmapRegion is behind it.
++        if unsafe { libc::munmap(target.as_ptr().cast(), target.len()) } == -1 {
++            return Err(io::Error::last_os_error());
++        }
++
++        mappings.remove(&{ req.shm_offset });
++
++        Ok(0)
++    }
++}
++
++impl Drop for BackendReqHandler {
++    fn drop(&mut self) {
++        let Some(region) = self.region.take() else {
++            return;
++        };
++        for (mapped_offset, mapped_len) in region.mappings.lock().unwrap().drain() {
++            let slice = self.ptr_guard_mut(mapped_offset, mapped_len).unwrap();
++
++            // SAFETY: we control this mapping, and we know the mapping
++            // for the whole MmapRegion is behind it.
++            if unsafe { libc::munmap(slice.as_ptr().cast(), slice.len()) } == -1 {
++                warn!(
++                    "Unmapping VHOST_USER_PROTOCOL_F_SHMEM mapping: {}",
++                    io::Error::last_os_error()
++                );
++            }
++        }
++    }
+ }
+ 
+ pub struct GenericVhostUser {
+@@ -52,7 +194,7 @@ pub struct GenericVhostUser {
+     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)>,
++    shared_memory: Option<VirtioSharedMemoryList>,
+     seccomp_action: SeccompAction,
+     guest_memory: Option<GuestMemoryAtomic<GuestMemoryMmap>>,
+     exit_evt: EventFd,
+@@ -68,12 +210,11 @@ impl GenericVhostUser {
+         path: &str,
+         request_queue_sizes: Vec<u16>,
+         device_type: u32,
+-        cache: Option<(VirtioSharedMemoryList, MmapRegion)>,
+         seccomp_action: SeccompAction,
+         exit_evt: EventFd,
+         access_platform_enabled: bool,
+         state: Option<State>,
+-    ) -> Result<GenericVhostUser> {
++    ) -> Result<(GenericVhostUser, BTreeMap<u8, u64>)> {
+         // Calculate the actual number of queues needed.
+         let num_queues = request_queue_sizes.len();
+ 
+@@ -112,6 +253,7 @@ impl GenericVhostUser {
+                 | VhostUserProtocolFeatures::REPLY_ACK
+                 | VhostUserProtocolFeatures::INFLIGHT_SHMFD
+                 | VhostUserProtocolFeatures::LOG_SHMFD
++                | VhostUserProtocolFeatures::SHMEM
+                 | VhostUserProtocolFeatures::DEVICE_STATE
+                 | VhostUserProtocolFeatures::BACKEND_REQ;
+ 
+@@ -151,33 +293,56 @@ since the backend only supports {backend_num_queues}\n",
+             )
+         };
+ 
+-        Ok(GenericVhostUser {
+-            vu_common: VhostUserCommon {
+-                virtio_common: VirtioCommon {
+-                    device_type,
+-                    avail_features,
+-                    acked_features,
+-                    queue_sizes: request_queue_sizes,
+-                    paused_sync: Some(Arc::new(Barrier::new(2))),
+-                    min_queues: 1,
+-                    paused: Arc::new(AtomicBool::new(paused)),
++        let shm_regions =
++            if (acked_protocol_features & VhostUserProtocolFeatures::SHMEM.bits()) == 0 {
++                Default::default()
++            } else {
++                vu.get_shmem_config()?
++            };
++
++        let shm_regions = shm_regions
++            .memory_sizes
++            .into_iter()
++            .enumerate()
++            .filter(|&(_, size)| size != 0)
++            .map(|(id, size)| (id as u8, size))
++            .take(shm_regions.nregions.try_into().unwrap())
++            .collect();
++
++        Ok((
++            GenericVhostUser {
++                vu_common: VhostUserCommon {
++                    virtio_common: VirtioCommon {
++                        device_type,
++                        avail_features,
++                        acked_features,
++                        queue_sizes: request_queue_sizes,
++                        paused_sync: Some(Arc::new(Barrier::new(2))),
++                        min_queues: 1,
++                        paused: Arc::new(AtomicBool::new(paused)),
++                        ..Default::default()
++                    },
++                    vu: Some(Arc::new(Mutex::new(vu))),
++                    acked_protocol_features,
++                    socket_path: path.to_string(),
++                    vu_num_queues,
++                    vring_bases,
+                     ..Default::default()
+                 },
+-                vu: Some(Arc::new(Mutex::new(vu))),
+-                acked_protocol_features,
+-                socket_path: path.to_string(),
+-                vu_num_queues,
+-                vring_bases,
+-                ..Default::default()
++                id,
++                shared_memory: None,
++                seccomp_action,
++                guest_memory: None,
++                exit_evt,
++                access_platform_enabled,
++                cfg_warning: AtomicBool::new(false),
+             },
+-            id,
+-            cache,
+-            seccomp_action,
+-            guest_memory: None,
+-            exit_evt,
+-            access_platform_enabled,
+-            cfg_warning: AtomicBool::new(false),
+-        })
++            shm_regions,
++        ))
++    }
++
++    pub fn set_shared_memory(&mut self, shared_memory: VirtioSharedMemoryList) {
++        self.shared_memory = Some(shared_memory);
+     }
+ 
+     fn state(&self) -> std::result::Result<State, MigratableError> {
+@@ -299,6 +464,10 @@ impl VirtioDevice for GenericVhostUser {
+             .then(|| {
+                 let mut handler = FrontendReqHandler::new(Arc::new(BackendReqHandler {
+                     interrupt_cb: interrupt_cb.clone(),
++                    region: self.shared_memory.as_ref().map(|list| ShmemRegion {
++                        region: list.mapping.clone(),
++                        mappings: Mutex::new(HashMap::new()),
++                    }),
+                 }))
+                 .map_err(|e| {
+                     crate::ActivateError::VhostUserSetup(Error::FrontendReqHandlerCreation(e))
+@@ -359,15 +528,15 @@ impl VirtioDevice for GenericVhostUser {
+     }
+ 
+     fn get_shm_regions(&self) -> Option<VirtioSharedMemoryList> {
+-        self.cache.as_ref().map(|cache| cache.0.clone())
++        self.shared_memory.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;
++        if let Some(cache) = self.shared_memory.as_mut() {
++            *cache = shm_regions;
+             Ok(())
+         } else {
+             Err(crate::Error::SetShmRegionsNotSupported)
+@@ -383,11 +552,11 @@ impl VirtioDevice for GenericVhostUser {
+ 
+     fn userspace_mappings(&self) -> Vec<UserspaceMapping> {
+         let mut mappings = Vec::new();
+-        if let Some(cache) = self.cache.as_ref() {
++        if let Some(cache) = self.shared_memory.as_ref() {
+             mappings.push(UserspaceMapping {
+-                mem_slot: cache.0.mem_slot,
+-                addr: cache.0.addr,
+-                mapping: cache.0.mapping.clone(),
++                mapping: cache.mapping.clone(),
++                mem_slot: cache.mem_slot,
++                addr: cache.addr,
+                 mergeable: false,
+             });
+         }
+diff --git a/virtio-devices/src/vhost_user/mod.rs b/virtio-devices/src/vhost_user/mod.rs
+index babc823ae..d97fe2a6a 100644
+--- a/virtio-devices/src/vhost_user/mod.rs
++++ b/virtio-devices/src/vhost_user/mod.rs
+@@ -82,6 +82,8 @@ pub enum Error {
+     VhostUserGetQueueMaxNum(#[source] VhostError),
+     #[error("Get protocol features failed")]
+     VhostUserGetProtocolFeatures(#[source] VhostError),
++    #[error("Get shared memory regions failed")]
++    VhostUserGetSharedMemoryRegions(#[source] VhostError),
+     #[error("Get vring base failed")]
+     VhostUserGetVringBase(#[source] VhostError),
+     #[error("Vhost-user Backend not support vhost-user protocol")]
+@@ -130,6 +132,10 @@ pub enum Error {
+     VhostUserSetInflight(#[source] VhostError),
+     #[error("Failed setting the log base")]
+     VhostUserSetLogBase(#[source] VhostError),
++    #[error("Expected {0} shared memory regions; got {1}")]
++    VhostUserUnexpectedSharedMemoryRegionsCount(usize, u32),
++    #[error("No shared memory region with non-zero length")]
++    VhostUserMissingSharedMemoryRegion,
+     #[error("Invalid used address")]
+     UsedAddress,
+     #[error("Invalid features provided from vhost-user backend")]
+diff --git a/virtio-devices/src/vhost_user/vu_common_ctrl.rs b/virtio-devices/src/vhost_user/vu_common_ctrl.rs
+index 23a37c335..8c668a3f5 100644
+--- a/virtio-devices/src/vhost_user/vu_common_ctrl.rs
++++ b/virtio-devices/src/vhost_user/vu_common_ctrl.rs
+@@ -14,7 +14,7 @@ use log::{error, info};
+ use vhost::vhost_kern::vhost_binding::VHOST_VRING_F_LOG;
+ use vhost::vhost_user::message::{
+     VhostTransferStateDirection, VhostTransferStatePhase, VhostUserHeaderFlag, VhostUserInflight,
+-    VhostUserProtocolFeatures, VhostUserVirtioFeatures,
++    VhostUserProtocolFeatures, VhostUserShMemConfig, VhostUserVirtioFeatures,
+ };
+ use vhost::vhost_user::{
+     Frontend, FrontendReqHandler, VhostUserFrontend, VhostUserFrontendReqHandler,
+@@ -114,6 +114,12 @@ impl VhostUserHandle {
+             .map_err(Error::VhostUserAddMemReg)
+     }
+ 
++    pub fn get_shmem_config(&mut self) -> Result<VhostUserShMemConfig> {
++        self.vu
++            .get_shmem_config()
++            .map_err(Error::VhostUserGetSharedMemoryRegions)
++    }
++
+     pub fn negotiate_features_vhost_user(
+         &mut self,
+         avail_features: u64,
+diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs
+index e6902a4cb..f6f27b9d6 100644
+--- a/vmm/src/device_manager.rs
++++ b/vmm/src/device_manager.rs
+@@ -89,7 +89,7 @@ use virtio_devices::transport::{VirtioPciDevice, VirtioPciDeviceActivator, Virti
+ use virtio_devices::vhost_user::VhostUserConfig;
+ use virtio_devices::{
+     AccessPlatformMapping, ActivateError, Block, Endpoint, IommuMapping, VdpaDmaMapping,
+-    VirtioMemMappingSource,
++    VirtioMemMappingSource, VirtioSharedMemory, VirtioSharedMemoryList,
+ };
+ use vm_allocator::{AddressAllocator, InterruptAllocError, SystemAllocator};
+ use vm_device::dma_mapping::ExternalDmaMapping;
+@@ -318,6 +318,10 @@ pub enum DeviceManagerError {
+     #[error("Cannot find a memory range for virtio-fs")]
+     FsRangeAllocation,
+ 
++    /// Cannot find a memory range for generic vhost-user
++    #[error("Cannot find a memory range for generic vhost-user")]
++    GenericVhostUserRangeAllocation,
++
+     /// Error creating serial output file
+     #[error("Error creating serial output file")]
+     SerialOutputFileOpen(#[source] io::Error),
+@@ -3122,13 +3126,12 @@ impl DeviceManager {
+         let mut node = device_node!(id);
+ 
+         if let Some(generic_vhost_user_socket) = generic_vhost_user_cfg.socket.to_str() {
+-            let generic_vhost_user_device = Arc::new(Mutex::new(
++            let (mut generic_vhost_user_device, shm_regions) =
+                 virtio_devices::vhost_user::GenericVhostUser::new(
+                     id.clone(),
+                     generic_vhost_user_socket,
+                     generic_vhost_user_cfg.queue_sizes.clone(),
+                     generic_vhost_user_cfg.device_type,
+-                    None,
+                     self.seccomp_action.clone(),
+                     self.exit_evt
+                         .try_clone()
+@@ -3137,8 +3140,80 @@ impl DeviceManager {
+                     state_from_id(self.snapshot.as_ref(), id.as_str())
+                         .map_err(DeviceManagerError::RestoreGetState)?,
+                 )
+-                .map_err(DeviceManagerError::CreateGenericVhostUser)?,
+-            ));
++                .map_err(DeviceManagerError::CreateGenericVhostUser)?;
++
++            if !shm_regions.is_empty() {
++                let mut total_len = 0u64;
++                for &len in shm_regions.values() {
++                    total_len = total_len
++                        .checked_add(len)
++                        .ok_or(DeviceManagerError::GenericVhostUserRangeAllocation)?;
++                }
++
++                let cache_base = self.pci_segments
++                    [generic_vhost_user_cfg.pci_common.pci_segment as usize]
++                    .mem64_allocator
++                    .lock()
++                    .unwrap()
++                    // Aligning to the full size of the allocation is cargo-culted from crosvm,
++                    // which allocates an 8GiB-aligned 8GiB region.
++                    .allocate(None, total_len, Some(total_len))
++                    .ok_or(DeviceManagerError::GenericVhostUserRangeAllocation)?
++                    .raw_value();
++
++                // Update the node with correct resource information.
++                node.resources.push(Resource::MmioAddressRange {
++                    base: cache_base,
++                    size: total_len,
++                });
++
++                let mmap_region = MmapRegion::build(
++                    None,
++                    total_len as usize,
++                    libc::PROT_NONE,
++                    libc::MAP_ANONYMOUS | libc::MAP_PRIVATE,
++                )
++                    .map_err(DeviceManagerError::NewMmapRegion)?;
++
++                // SAFETY: `mmap_region.size()` and `mmap_region.as_ptr()` refer to an allocation.
++                // We remove the userspace mapping before dropping the device if the device is
++                // ejected.
++                let mem_slot = unsafe {
++                    self.memory_manager
++                        .lock()
++                        .unwrap()
++                        .create_userspace_mapping(
++                            cache_base,
++                            mmap_region.size(),
++                            mmap_region.as_ptr(),
++                            false,
++                            false,
++                            false,
++                        )
++                        .map_err(DeviceManagerError::MemoryManager)?
++                };
++
++                let region_list = {
++                    let mut offset = 0;
++                    shm_regions
++                        .into_iter()
++                        .map(|(id, len)| {
++                            let mem = VirtioSharedMemory { offset, len };
++                            offset += len;
++                            (id, mem)
++                        })
++                        .collect()
++                };
++
++                generic_vhost_user_device.set_shared_memory(VirtioSharedMemoryList {
++                    mapping: Arc::new(mmap_region),
++                    mem_slot,
++                    addr: GuestAddress(cache_base),
++                    region_list,
++                });
++            }
++
++            let generic_vhost_user_device = Arc::new(Mutex::new(generic_vhost_user_device));
+ 
+             // Update the device tree with the migratable device.
+             node.migratable =
+-- 
+2.54.0
+
+REUSE-IgnoreEnd
+
+From da312aef2aa8657498b996146c532729d626f323 Mon Sep 17 00:00:00 2001
+From: Alyssa Ross <hi@alyssa.is>
+Date: Tue, 17 Mar 2026 10:19:54 +0100
+Subject: [PATCH 3/3] virtio-devices: support vhost-user GPU features
+SPDX-FileCopyrightText: The Cloud Hypervisor Authors
+SPDX-FileCopyrightText: 2019 Intel Corporation
+SPDX-FileCopyrightText: 2025 Demi Marie Obenour.
+SPDX-FileCopyrightText: 2026 Alyssa Ross <hi@alyssa.is>
+SPDX-License-Identifier: Apache-2.0
+
+None of these require any special support in the vhost-user frontend;
+they're entirely between the backend and the driver.  We can add
+similar entries for any other device-specific features the generic
+vhost-user device supports with no extra code.
+
+Signed-off-by: Alyssa Ross <hi@alyssa.is>
+---
+ .../src/vhost_user/generic_vhost_user.rs         | 16 +++++++++++++++-
+ 1 file changed, 15 insertions(+), 1 deletion(-)
+
+REUSE-IgnoreStart
+
+diff --git a/virtio-devices/src/vhost_user/generic_vhost_user.rs b/virtio-devices/src/vhost_user/generic_vhost_user.rs
+index c904bb470..82fd01ef4 100644
+--- a/virtio-devices/src/vhost_user/generic_vhost_user.rs
++++ b/virtio-devices/src/vhost_user/generic_vhost_user.rs
+@@ -21,6 +21,11 @@ use vhost::vhost_user::message::{
+ use vhost::vhost_user::{
+     FrontendReqHandler, HandlerResult, VhostUserFrontend, VhostUserFrontendReqHandler,
+ };
++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_bindings::virtio_ids::VIRTIO_ID_GPU;
+ use vm_device::UserspaceMapping;
+ use vm_memory::volatile_memory::PtrGuardMut;
+ use vm_memory::{GuestMemoryAtomic, VolatileMemory};
+@@ -257,7 +262,16 @@ impl GenericVhostUser {
+                 | VhostUserProtocolFeatures::DEVICE_STATE
+                 | VhostUserProtocolFeatures::BACKEND_REQ;
+ 
+-            let avail_features = super::DEFAULT_VIRTIO_FEATURES;
++            let avail_features = super::DEFAULT_VIRTIO_FEATURES
++                | match device_type {
++                    VIRTIO_ID_GPU => {
++                        1 << VIRTIO_GPU_F_VIRGL
++                            | 1 << VIRTIO_GPU_F_RESOURCE_UUID
++                            | 1 << VIRTIO_GPU_F_RESOURCE_BLOB
++                            | 1 << VIRTIO_GPU_F_CONTEXT_INIT
++                    }
++                    _ => 0,
++                };
+ 
+             let (acked_features, acked_protocol_features) =
+                 vu.negotiate_features_vhost_user(avail_features, avail_protocol_features)?;
+-- 
+2.54.0
+
diff --git a/pkgs/cloud-hypervisor/vhost/0001-vhost_user-add-get_size-to-MsgHeader.patch b/pkgs/cloud-hypervisor/vhost/0001-vhost_user-add-get_size-to-MsgHeader.patch
deleted file mode 100644
index cecd033..0000000
--- a/pkgs/cloud-hypervisor/vhost/0001-vhost_user-add-get_size-to-MsgHeader.patch
+++ /dev/null
@@ -1,82 +0,0 @@
-From 667b6aa7d3fd81a832d57b54e24bdef1614ebdd5 Mon Sep 17 00:00:00 2001
-From: Alyssa Ross <hi@alyssa.is>
-Date: Thu, 24 Oct 2024 17:11:17 +0200
-Subject: [PATCH 1/4] vhost_user: add get_size() to MsgHeader
-SPDX-FileCopyrightText: 2019 Alibaba Cloud Computing. All rights reserved.
-SPDX-FileCopyrightText: 2024 Red Hat, Inc.
-SPDX-FileCopyrightText: 2024 Alyssa Ross <hi@alyssa.is>
-SPDX-License-Identifier: Apache-2.0
-
-Signed-off-by: Alyssa Ross <hi@alyssa.is>
----
- vhost/src/vhost_user/gpu_message.rs |  9 ++++-----
- vhost/src/vhost_user/message.rs     | 12 +++++++-----
- 2 files changed, 11 insertions(+), 10 deletions(-)
-
-diff --git a/vhost/src/vhost_user/gpu_message.rs b/vhost/src/vhost_user/gpu_message.rs
-index 6be26f20d..75073dc4c 100644
---- a/vhost/src/vhost_user/gpu_message.rs
-+++ b/vhost/src/vhost_user/gpu_message.rs
-@@ -135,11 +135,6 @@ impl<R: Req> VhostUserGpuMsgHeader<R> {
-         }
-     }
- 
--    /// Get message size.
--    pub fn get_size(&self) -> u32 {
--        self.size
--    }
--
-     /// Set message size.
-     pub fn set_size(&mut self, size: u32) {
-         self.size = size;
-@@ -169,6 +164,10 @@ impl<T: Req> VhostUserMsgValidator for VhostUserGpuMsgHeader<T> {
- impl<R: Req> MsgHeader for VhostUserGpuMsgHeader<R> {
-     type Request = R;
-     const MAX_MSG_SIZE: usize = u32::MAX as usize;
-+
-+    fn get_size(&self) -> u32 {
-+        self.size
-+    }
- }
- 
- // Bit mask for vhost-user-gpu protocol feature flags.
-diff --git a/vhost/src/vhost_user/message.rs b/vhost/src/vhost_user/message.rs
-index c66bd4468..8359eb1fd 100644
---- a/vhost/src/vhost_user/message.rs
-+++ b/vhost/src/vhost_user/message.rs
-@@ -70,6 +70,9 @@ pub(super) trait MsgHeader: ByteValued + Copy + Default + VhostUserMsgValidator
- 
-     /// The maximum size of a msg that can be encapsulated by this MsgHeader
-     const MAX_MSG_SIZE: usize;
-+
-+    /// Get message size.
-+    fn get_size(&self) -> u32;
- }
- 
- enum_value! {
-@@ -241,6 +244,10 @@ pub(super) struct VhostUserMsgHeader<R: Req> {
- impl<R: Req> MsgHeader for VhostUserMsgHeader<R> {
-     type Request = R;
-     const MAX_MSG_SIZE: usize = MAX_MSG_SIZE;
-+
-+    fn get_size(&self) -> u32 {
-+        self.size
-+    }
- }
- 
- impl<R: Req> Debug for VhostUserMsgHeader<R> {
-@@ -336,11 +343,6 @@ impl<R: Req> VhostUserMsgHeader<R> {
-         }
-     }
- 
--    /// Get message size.
--    pub fn get_size(&self) -> u32 {
--        self.size
--    }
--
-     /// Set message size.
-     pub fn set_size(&mut self, size: u32) {
-         self.size = size;
--- 
-2.50.0
-
diff --git a/pkgs/cloud-hypervisor/vhost/0002-vhost-fix-receiving-reply-payloads.patch b/pkgs/cloud-hypervisor/vhost/0002-vhost-fix-receiving-reply-payloads.patch
deleted file mode 100644
index e80a610..0000000
--- a/pkgs/cloud-hypervisor/vhost/0002-vhost-fix-receiving-reply-payloads.patch
+++ /dev/null
@@ -1,165 +0,0 @@
-From d56efa072f28c203bdf6b0c6e62a04984ebb1e28 Mon Sep 17 00:00:00 2001
-From: David Stevens <stevensd@chromium.org>
-Date: Wed, 15 Jun 2022 15:56:18 +0900
-Subject: [PATCH 2/4] vhost: fix receiving reply payloads
-SPDX-FileCopyrightText: 2019 Alibaba Cloud Computing. All rights reserved.
-SPDX-FileCopyrightText: The Chromium OS Authors
-SPDX-FileCopyrightText: 2023 Alyssa Ross <hi@alyssa.is>
-SPDX-FileCopyrightText: 2024 Red Hat, Inc.
-SPDX-License-Identifier: Apache-2.0
-
-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>
----
- vhost/src/vhost_user/connection.rs      | 29 ++++++++++---------------
- vhost/src/vhost_user/frontend.rs        | 16 +++-----------
- vhost/src/vhost_user/gpu_backend_req.rs | 14 +++++-------
- 3 files changed, 21 insertions(+), 38 deletions(-)
-
-diff --git a/vhost/src/vhost_user/connection.rs b/vhost/src/vhost_user/connection.rs
-index 4fca9c5bc..0ce72154e 100644
---- a/vhost/src/vhost_user/connection.rs
-+++ b/vhost/src/vhost_user/connection.rs
-@@ -543,7 +543,7 @@ impl<H: MsgHeader> Endpoint<H> {
-     /// 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.
-@@ -552,15 +552,13 @@ impl<H: MsgHeader> Endpoint<H> {
-     #[allow(clippy::type_complexity)]
-     pub fn recv_payload_into_buf<T: ByteValued + Sized + VhostUserMsgValidator + Default>(
-         &mut self,
--        buf: &mut [u8],
--    ) -> Result<(H, T, usize, Option<Vec<File>>)> {
--        let mut hdr = H::default();
-+    ) -> Result<(H, 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 H) as *mut c_void,
--                iov_len: mem::size_of::<H>(),
--            },
-             iovec {
-                 iov_base: (&mut body as *mut T) as *mut c_void,
-                 iov_len: mem::size_of::<T>(),
-@@ -570,19 +568,16 @@ impl<H: MsgHeader> Endpoint<H> {
-                 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::<H>() + 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/vhost/src/vhost_user/frontend.rs b/vhost/src/vhost_user/frontend.rs
-index ea6284980..195a6af1e 100644
---- a/vhost/src/vhost_user/frontend.rs
-+++ b/vhost/src/vhost_user/frontend.rs
-@@ -756,23 +756,13 @@ impl FrontendInternal {
-         &mut self,
-         hdr: &VhostUserMsgHeader<FrontendReq>,
-     ) -> 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);
-         }
- 
-diff --git a/vhost/src/vhost_user/gpu_backend_req.rs b/vhost/src/vhost_user/gpu_backend_req.rs
-index 140063093..f7160c9c4 100644
---- a/vhost/src/vhost_user/gpu_backend_req.rs
-+++ b/vhost/src/vhost_user/gpu_backend_req.rs
-@@ -437,9 +437,8 @@ mod tests {
-             let _: () = backend.update_scanout(&request, &payload).unwrap();
-         });
- 
--        let mut recv_buf = [0u8; 4096];
--        let (hdr, req_body, recv_buf_len, fds) = frontend
--            .recv_payload_into_buf::<VhostUserGpuUpdate>(&mut recv_buf)
-+        let (hdr, req_body, recv_buf, fds) = frontend
-+            .recv_payload_into_buf::<VhostUserGpuUpdate>()
-             .unwrap();
-         assert!(fds.is_none());
-         assert_hdr(
-@@ -449,7 +448,7 @@ mod tests {
-         );
-         assert_eq!(req_body, request);
- 
--        assert_eq!(&payload[..], &recv_buf[..recv_buf_len]);
-+        assert_eq!(&payload[..], recv_buf);
- 
-         sender_thread.join().expect("Failed to send!");
-     }
-@@ -611,9 +610,8 @@ mod tests {
-             let _: () = backend.cursor_update(&request, &payload).unwrap();
-         });
- 
--        let mut recv_buf = vec![0u8; 1 + size_of_val(&payload)];
--        let (hdr, req_body, recv_buf_len, fds) = frontend
--            .recv_payload_into_buf::<VhostUserGpuCursorUpdate>(&mut recv_buf)
-+        let (hdr, req_body, recv_buf, fds) = frontend
-+            .recv_payload_into_buf::<VhostUserGpuCursorUpdate>()
-             .unwrap();
-         assert!(fds.is_none());
-         assert_hdr(
-@@ -623,7 +621,7 @@ mod tests {
-         );
-         assert_eq!(req_body, request);
- 
--        assert_eq!(&payload[..], &recv_buf[..recv_buf_len]);
-+        assert_eq!(&payload[..], recv_buf);
- 
-         sender_thread.join().expect("Failed to send!");
-     }
--- 
-2.50.0
-
diff --git a/pkgs/cloud-hypervisor/vhost/0003-vhost_user-add-shared-memory-region-support.patch b/pkgs/cloud-hypervisor/vhost/0003-vhost_user-add-shared-memory-region-support.patch
deleted file mode 100644
index a12b5e6..0000000
--- a/pkgs/cloud-hypervisor/vhost/0003-vhost_user-add-shared-memory-region-support.patch
+++ /dev/null
@@ -1,484 +0,0 @@
-From 7012cacaee3c8c8ce1bd6992be6ae16879110d23 Mon Sep 17 00:00:00 2001
-From: David Stevens <stevensd@chromium.org>
-Date: Wed, 15 Jun 2022 16:45:12 +0900
-Subject: [PATCH 3/4] vhost_user: add shared memory region support
-SPDX-FileCopyrightText: 2019 Intel Corporation. All Rights Reserved.
-SPDX-FileCopyrightText: 2019 Alibaba Cloud Computing. All rights reserved.
-SPDX-FileCopyrightText: 2019-2021 Alibaba Cloud. All rights reserved.
-SPDX-FileCopyrightText: The Chromium OS Authors
-SPDX-FileCopyrightText: 2022 Unikie
-SPDX-FileCopyrightText: 2023-2024 Alyssa Ross <hi@alyssa.is>
-SPDX-License-Identifier: Apache-2.0
-
-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>
-[renumbered for crosvm commit e428c4ba88a26695f63929c680b4101a9b8d5cbc]
-Signed-off-by: Alyssa Ross <hi@alyssa.is>
----
- vhost-user-backend/src/handler.rs            |  12 +-
- vhost/src/vhost_user/backend_req.rs          |  20 ++-
- vhost/src/vhost_user/backend_req_handler.rs  |  15 +++
- vhost/src/vhost_user/dummy_backend.rs        |   4 +
- vhost/src/vhost_user/frontend.rs             |  24 ++++
- vhost/src/vhost_user/frontend_req_handler.rs |  53 +++++++-
- vhost/src/vhost_user/message.rs              | 126 +++++++++++++++++++
- 7 files changed, 243 insertions(+), 11 deletions(-)
-
-diff --git a/vhost-user-backend/src/handler.rs b/vhost-user-backend/src/handler.rs
-index 27e8e9a1f..b242a72c9 100644
---- a/vhost-user-backend/src/handler.rs
-+++ b/vhost-user-backend/src/handler.rs
-@@ -17,10 +17,10 @@ use crate::bitmap::{BitmapReplace, MemRegionBitmap, MmapLogReg};
- #[cfg(feature = "postcopy")]
- use userfaultfd::{Uffd, UffdBuilder};
- use vhost::vhost_user::message::{
--    VhostTransferStateDirection, VhostTransferStatePhase, VhostUserConfigFlags, VhostUserLog,
--    VhostUserMemoryRegion, VhostUserProtocolFeatures, VhostUserSharedMsg,
--    VhostUserSingleMemoryRegion, VhostUserVirtioFeatures, VhostUserVringAddrFlags,
--    VhostUserVringState,
-+    VhostSharedMemoryRegion, VhostTransferStateDirection, VhostTransferStatePhase,
-+    VhostUserConfigFlags, VhostUserLog, VhostUserMemoryRegion, VhostUserProtocolFeatures,
-+    VhostUserSharedMsg, VhostUserSingleMemoryRegion, VhostUserVirtioFeatures,
-+    VhostUserVringAddrFlags, VhostUserVringState,
- };
- use vhost::vhost_user::GpuBackend;
- use vhost::vhost_user::{
-@@ -671,6 +671,10 @@ where
-         Ok(())
-     }
- 
-+    fn get_shared_memory_regions(&mut self) -> VhostUserResult<Vec<VhostSharedMemoryRegion>> {
-+        Ok(Vec::new())
-+    }
-+
-     fn set_device_state_fd(
-         &mut self,
-         direction: VhostTransferStateDirection,
-diff --git a/vhost/src/vhost_user/backend_req.rs b/vhost/src/vhost_user/backend_req.rs
-index eb5ef65af..69665ce0e 100644
---- a/vhost/src/vhost_user/backend_req.rs
-+++ b/vhost/src/vhost_user/backend_req.rs
-@@ -49,12 +49,16 @@ impl BackendInternal {
-         }
-         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<BackendReq>) -> Result<u64> {
-+    fn wait_for_reply(&mut self, hdr: &VhostUserMsgHeader<BackendReq>) -> Result<u64> {
-         self.check_state()?;
--        if !self.reply_ack_negotiated {
-+        if !matches!(
-+            hdr.get_code(),
-+            Ok(BackendReq::SHMEM_MAP | BackendReq::SHMEM_UNMAP)
-+        ) && !self.reply_ack_negotiated
-+        {
-             return Ok(0);
-         }
- 
-@@ -183,6 +187,16 @@ impl VhostUserFrontendReqHandler for Backend {
-             Some(&[fd.as_raw_fd()]),
-         )
-     }
-+
-+    /// Handle shared memory region mapping requests.
-+    fn shmem_map(&self, req: &VhostUserShmemMapMsg, fd: &dyn AsRawFd) -> HandlerResult<u64> {
-+        self.send_message(BackendReq::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(BackendReq::SHMEM_UNMAP, req, None)
-+    }
- }
- 
- #[cfg(test)]
-diff --git a/vhost/src/vhost_user/backend_req_handler.rs b/vhost/src/vhost_user/backend_req_handler.rs
-index d74b04558..076b09362 100644
---- a/vhost/src/vhost_user/backend_req_handler.rs
-+++ b/vhost/src/vhost_user/backend_req_handler.rs
-@@ -74,6 +74,7 @@ pub trait VhostUserBackendReqHandler {
-     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>>;
-     fn set_device_state_fd(
-         &self,
-         direction: VhostTransferStateDirection,
-@@ -139,6 +140,7 @@ pub trait VhostUserBackendReqHandlerMut {
-     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>>;
-     fn set_device_state_fd(
-         &mut self,
-         direction: VhostTransferStateDirection,
-@@ -274,6 +276,10 @@ impl<T: VhostUserBackendReqHandlerMut> VhostUserBackendReqHandler for Mutex<T> {
-         self.lock().unwrap().remove_mem_region(region)
-     }
- 
-+    fn get_shared_memory_regions(&self) -> Result<Vec<VhostSharedMemoryRegion>> {
-+        self.lock().unwrap().get_shared_memory_regions()
-+    }
-+
-     fn set_device_state_fd(
-         &self,
-         direction: VhostTransferStateDirection,
-@@ -634,6 +640,15 @@ impl<S: VhostUserBackendReqHandler> BackendReqHandler<S> {
-                 let res = self.backend.remove_mem_region(&msg);
-                 self.send_ack_message(&hdr, res)?;
-             }
-+            Ok(FrontendReq::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())?;
-+            }
-             Ok(FrontendReq::SET_DEVICE_STATE_FD) => {
-                 let file = take_single_file(files).ok_or(Error::IncorrectFds)?;
-                 let msg =
-diff --git a/vhost/src/vhost_user/dummy_backend.rs b/vhost/src/vhost_user/dummy_backend.rs
-index a45d3b47f..9b2040d6f 100644
---- a/vhost/src/vhost_user/dummy_backend.rs
-+++ b/vhost/src/vhost_user/dummy_backend.rs
-@@ -310,6 +310,10 @@ impl VhostUserBackendReqHandlerMut for DummyBackendReqHandler {
-         Ok(())
-     }
- 
-+    fn get_shared_memory_regions(&mut self) -> Result<Vec<VhostSharedMemoryRegion>> {
-+        Ok(Vec::new())
-+    }
-+
-     fn set_device_state_fd(
-         &mut self,
-         _direction: VhostTransferStateDirection,
-diff --git a/vhost/src/vhost_user/frontend.rs b/vhost/src/vhost_user/frontend.rs
-index 195a6af1e..41b49cbd3 100644
---- a/vhost/src/vhost_user/frontend.rs
-+++ b/vhost/src/vhost_user/frontend.rs
-@@ -79,6 +79,9 @@ pub trait VhostUserFrontend: 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>>;
-+
-     /// Sends VHOST_USER_POSTCOPY_ADVISE msg to the backend
-     /// initiating the beginning of the postcopy process.
-     /// Backend will return a userfaultfd.
-@@ -567,6 +570,27 @@ impl VhostUserFrontend for Frontend {
-         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(FrontendReq::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(),
-+            );
-+            offset += struct_size;
-+        }
-+        Ok(regions)
-+    }
-+
-     #[cfg(feature = "postcopy")]
-     fn postcopy_advise(&mut self) -> Result<File> {
-         let mut node = self.node();
-diff --git a/vhost/src/vhost_user/frontend_req_handler.rs b/vhost/src/vhost_user/frontend_req_handler.rs
-index 77d4bf55a..f26cd6491 100644
---- a/vhost/src/vhost_user/frontend_req_handler.rs
-+++ b/vhost/src/vhost_user/frontend_req_handler.rs
-@@ -52,6 +52,16 @@ pub trait VhostUserFrontendReqHandler {
-         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))
-+    }
-+
-     // fn handle_iotlb_msg(&mut self, iotlb: VhostUserIotlb);
-     // fn handle_vring_host_notifier(&mut self, area: VhostUserVringArea, fd: &dyn AsRawFd);
- }
-@@ -84,6 +94,16 @@ pub trait VhostUserFrontendReqHandlerMut {
-         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))
-+    }
-+
-     // fn handle_iotlb_msg(&mut self, iotlb: VhostUserIotlb);
-     // fn handle_vring_host_notifier(&mut self, area: VhostUserVringArea, fd: RawFd);
- }
-@@ -111,6 +131,14 @@ impl<S: VhostUserFrontendReqHandlerMut> VhostUserFrontendReqHandler for Mutex<S>
-     ) -> HandlerResult<u64> {
-         self.lock().unwrap().shared_object_lookup(uuid, fd)
-     }
-+
-+    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)
-+    }
- }
- 
- /// Server to handle service requests from backends from the backend communication channel.
-@@ -241,10 +269,23 @@ impl<S: VhostUserFrontendReqHandler> FrontendReqHandler<S> {
-                     .shared_object_lookup(&msg, &files.unwrap()[0])
-                     .map_err(Error::ReqHandlerError)
-             }
-+            Ok(BackendReq::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(BackendReq::SHMEM_UNMAP) => {
-+                let msg = self.extract_msg_body::<VhostUserShmemUnmapMsg>(&hdr, size, &buf)?;
-+                self.backend
-+                    .shmem_unmap(&msg)
-+                    .map_err(Error::ReqHandlerError)
-+            }
-             _ => Err(Error::InvalidMessage),
-         };
- 
--        self.send_ack_message(&hdr, &res)?;
-+        self.send_reply(&hdr, &res)?;
- 
-         res
-     }
-@@ -278,7 +319,7 @@ impl<S: VhostUserFrontendReqHandler> FrontendReqHandler<S> {
-         files: &Option<Vec<File>>,
-     ) -> Result<()> {
-         match hdr.get_code() {
--            Ok(BackendReq::SHARED_OBJECT_LOOKUP) => {
-+            Ok(BackendReq::SHARED_OBJECT_LOOKUP | BackendReq::SHMEM_MAP) => {
-                 // Expect a single file is passed.
-                 match files {
-                     Some(files) if files.len() == 1 => Ok(()),
-@@ -320,12 +361,16 @@ impl<S: VhostUserFrontendReqHandler> FrontendReqHandler<S> {
-         ))
-     }
- 
--    fn send_ack_message(
-+    fn send_reply(
-         &mut self,
-         req: &VhostUserMsgHeader<BackendReq>,
-         res: &Result<u64>,
-     ) -> Result<()> {
--        if self.reply_ack_negotiated && req.is_need_reply() {
-+        if matches!(
-+            req.get_code(),
-+            Ok(BackendReq::SHMEM_MAP | BackendReq::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/vhost/src/vhost_user/message.rs b/vhost/src/vhost_user/message.rs
-index 8359eb1fd..b847885a4 100644
---- a/vhost/src/vhost_user/message.rs
-+++ b/vhost/src/vhost_user/message.rs
-@@ -172,6 +172,8 @@ enum_value! {
-         /// After transferring state, check the backend for any errors that may have
-         /// occurred during the transfer
-         CHECK_DEVICE_STATE = 43,
-+        /// Get a list of the device's shared memory regions.
-+        GET_SHARED_MEMORY_REGIONS = 1004,
-     }
- }
- 
-@@ -197,6 +199,12 @@ enum_value! {
-         SHARED_OBJECT_REMOVE = 7,
-         /// Lookup for a virtio shared object.
-         SHARED_OBJECT_LOOKUP = 8,
-+
-+        // Non-standard message types.
-+        /// Indicates a request to map a fd into a shared memory region.
-+        SHMEM_MAP = 1000,
-+        /// Indicates a request to unmap part of a shared memory region.
-+        SHMEM_UNMAP = 1001,
-     }
- }
- 
-@@ -992,6 +1000,99 @@ impl VhostUserMsgValidator for VhostUserTransferDeviceState {
-     }
- }
- 
-+bitflags! {
-+    #[derive(Default, Copy, Clone)]
-+    /// 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;
-+    }
-+}
-+
-+/// Backend 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() & !VhostUserShmemMapMsgFlags::all().bits()) == 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,
-+        }
-+    }
-+}
-+
-+/// Backend 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(C, packed)]
- #[derive(Clone, Copy, Default)]
-@@ -1123,6 +1224,31 @@ impl QueueRegionPacked {
-     }
- }
- 
-+/// Virtio shared memory descriptor.
-+#[repr(C, 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::*;
--- 
-2.50.0
-
diff --git a/pkgs/cloud-hypervisor/vhost/0004-vhost_user-add-protocol-flag-for-shmem.patch b/pkgs/cloud-hypervisor/vhost/0004-vhost_user-add-protocol-flag-for-shmem.patch
deleted file mode 100644
index 7bd03f8..0000000
--- a/pkgs/cloud-hypervisor/vhost/0004-vhost_user-add-protocol-flag-for-shmem.patch
+++ /dev/null
@@ -1,43 +0,0 @@
-From 6f505091fee73b059fceef76eabe60f385706673 Mon Sep 17 00:00:00 2001
-From: David Stevens <stevensd@chromium.org>
-Date: Thu, 13 Oct 2022 10:37:47 +0900
-Subject: [PATCH 4/4] vhost_user: add protocol flag for shmem
-SPDX-FileCopyrightText: 2019 Alibaba Cloud Computing. All rights reserved.
-SPDX-FileCopyrightText: The Chromium OS Authors
-SPDX-FileCopyrightText: 2023-2024 Alyssa Ross <hi@alyssa.is>
-SPDX-License-Identifier: Apache-2.0
-
-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)
-[renumbered for crosvm commit 6795b3edf3ddc0b3a2e2b928033f21dabcf8be78]
-Signed-off-by: Alyssa Ross <hi@alyssa.is>
----
- vhost/src/vhost_user/message.rs | 2 ++
- 1 file changed, 2 insertions(+)
-
-diff --git a/vhost/src/vhost_user/message.rs b/vhost/src/vhost_user/message.rs
-index b847885a4..69cebde75 100644
---- a/vhost/src/vhost_user/message.rs
-+++ b/vhost/src/vhost_user/message.rs
-@@ -444,6 +444,8 @@ bitflags! {
-         const SHARED_OBJECT = 0x0004_0000;
-         /// Support transferring internal device state.
-         const DEVICE_STATE = 0x0008_0000;
-+        /// Support shared memory regions. (Non-standard.)
-+        const SHARED_MEMORY_REGIONS = 0x8000_0000;
-     }
- }
- 
--- 
-2.50.0
-
diff --git a/pkgs/gtk3/default.nix b/pkgs/gtk3/default.nix
deleted file mode 100644
index 72445c9..0000000
--- a/pkgs/gtk3/default.nix
+++ /dev/null
@@ -1,13 +0,0 @@
-# SPDX-FileCopyrightText: 2025 Alyssa Ross <hi@alyssa.is>
-# SPDX-License-Identifier: MIT
-
-import ../../lib/overlay-package.nix [ "gtk3" ] ({ final, super }:
-
-super.gtk3.overrideAttrs ({ patches ? [], ... }: {
-  patches = patches ++ [
-    (final.fetchpatch {
-      url = "https://gitlab.gnome.org/GNOME/gtk/-/commit/8569e206badbee1b27ff0e27316391b8d8c3f987.patch";
-      hash = "sha256-OdBhCGtz+3HS8LRhp+GCj3dL4pntybiI9b3A3kc5+OY=";
-    })
-  ];
-}))
diff --git a/pkgs/overlay.nix b/pkgs/overlay.nix
index 3e3336e..62929ad 100644
--- a/pkgs/overlay.nix
+++ b/pkgs/overlay.nix
@@ -10,11 +10,7 @@
     }
   );
 
-  gtk3 = import ./gtk3 { inherit final super; };
-
   mailutils = super.mailutils.overrideAttrs (_: (
     final.lib.optionalAttrs final.stdenv.hostPlatform.isMusl { doCheck = false; }
   ));
-
-  skawarePackages = import ./skaware-packages { inherit final super; };
 })
diff --git a/pkgs/skaware-packages/default.nix b/pkgs/skaware-packages/default.nix
deleted file mode 100644
index 4c7855b..0000000
--- a/pkgs/skaware-packages/default.nix
+++ /dev/null
@@ -1,24 +0,0 @@
-# SPDX-FileCopyrightText: 2025 Alyssa Ross <hi@alyssa.is>
-# SPDX-License-Identifier: MIT
-
-import ../../lib/overlay-package.nix [ "skawarePackages" ] ({ final, super }:
-
-super.skawarePackages.overrideScope (_: prev: {
-  s6 = prev.s6.overrideAttrs ({ patches ? [], ... }: {
-    patches = patches ++ [
-      (final.fetchpatch {
-        url = "https://github.com/skarnet/s6/commit/c3a8ef7034fb2bc02f35381a8970ac026822a810.patch";
-        hash = "sha256-lgCoPbEYru6/a2bpVpLsZ2Rq2OHhNVs0lDgFO/df1Aw=";
-      })
-    ];
-  });
-
-  mdevd = prev.mdevd.overrideAttrs ({ patches ? [], ... }: {
-    patches = patches ++ [
-      (final.fetchpatch {
-        url = "https://github.com/skarnet/mdevd/commit/252f241e425bf09ddfb4a824e40403f40da0da1e.patch";
-        hash = "sha256-0tEC+yJGyPapsxBqzBXPztF3bl7OwjVAGjhNXtwZQ0g=";
-      })
-    ];
-  });
-}))
diff --git a/release/checks/no-roothash.nix b/release/checks/no-roothash.nix
index 91e3bef..623297d 100644
--- a/release/checks/no-roothash.nix
+++ b/release/checks/no-roothash.nix
@@ -9,7 +9,7 @@ lib.fix (self: testers.nixosTest ({ pkgs, stdenv, ... }:
 let
   initramfs = callSpectrumPackage ../../host/initramfs {};
 
-  inherit (import (path + /nixos/lib/qemu-common.nix) { inherit lib pkgs; })
+  inherit (import (path + /nixos/lib/qemu-common.nix) { inherit lib stdenv; })
     qemuBinary qemuSerialDevice;
 in {
   name = "spectrum-test-initramfs-no-roothash";
@@ -20,7 +20,7 @@ in {
 
     flags = "${qemuBinary self.config.qemu.package} " + " ".join(map(shlex.quote, [
       "-m", "512",
-      "-kernel", "${rootfs.kernel}/${stdenv.hostPlatform.linux-kernel.target}",
+      "-kernel", "${rootfs.kernel}/${rootfs.kernel.target}",
       "-initrd", "${initramfs}",
       "-append", "console=${qemuSerialDevice} panic=-1",
     ]))
diff --git a/release/checks/wayland/default.nix b/release/checks/wayland/default.nix
index 40f9619..e768d3d 100644
--- a/release/checks/wayland/default.nix
+++ b/release/checks/wayland/default.nix
@@ -32,7 +32,7 @@ testers.nixosTest ({ lib, pkgs, ... }: {
     systemd.services.cloud-hypervisor = {
       after = [ "weston.service" ];
       requires = [ "weston.service" ];
-      serviceConfig.ExecStart = "${lib.getExe pkgs.cloud-hypervisor} --memory shared=on --disk path=${appvm}/lib/spectrum/img/appvm/blk/root.img,readonly=on --cmdline \"console=ttyS0 root=PARTLABEL=root\" --fs socket=/run/virtiofsd.sock,tag=host --gpu socket=/run/crosvm-gpu.sock --vsock cid=3,socket=/run/vsock.sock --serial tty --console null --kernel ${appvm}/lib/spectrum/img/appvm/vmlinux";
+      serviceConfig.ExecStart = "${lib.getExe pkgs.cloud-hypervisor} --memory shared=on --disk path=${appvm}/lib/spectrum/img/appvm/blk/root.img,readonly=on --cmdline \"console=ttyS0 root=PARTLABEL=root\" --fs socket=/run/virtiofsd.sock,tag=host --generic-vhost-user virtio_id=gpu,socket=/run/crosvm-gpu.sock,queue_sizes=[16,256] --vsock cid=3,socket=/run/vsock.sock --serial tty --console null --kernel ${appvm}/lib/spectrum/img/appvm/vmlinux";
     };
 
     systemd.services.crosvm = {
diff --git a/release/checks/wayland/surface-notify/meson.build b/release/checks/wayland/surface-notify/meson.build
index e98e10b..37df1ce 100644
--- a/release/checks/wayland/surface-notify/meson.build
+++ b/release/checks/wayland/surface-notify/meson.build
@@ -3,7 +3,7 @@
 
 project('surface-notify', 'c', default_options : [ 'warning_level=2' ])
 
-libweston = dependency('libweston-14')
+libweston = dependency('libweston-15')
 wayland_server = dependency('wayland-server')
 
 shared_library('surface-notify', 'module.c',
diff --git a/release/live/default.nix b/release/live/default.nix
index aa8dddd..3b5f0db 100644
--- a/release/live/default.nix
+++ b/release/live/default.nix
@@ -40,7 +40,7 @@ stdenv.mkDerivation {
   ];
 
   env = {
-    KERNEL = "${efi.rootfs.kernel}/${stdenv.hostPlatform.linux-kernel.target}";
+    KERNEL = "${efi.rootfs.kernel}/${efi.rootfs.kernel.target}";
     ROOT_FS = "${efi.rootfs}";
     SYSTEMD_BOOT_EFI = "${efi.systemd}/lib/systemd/boot/efi/systemd-boot${efiArch}.efi";
     SPECTRUM_EFI = efi;
diff --git a/scripts/dist-cloud-hypervisor.sh b/scripts/dist-cloud-hypervisor.sh
index 22bcbaa..5ac99d5 100755
--- a/scripts/dist-cloud-hypervisor.sh
+++ b/scripts/dist-cloud-hypervisor.sh
@@ -17,5 +17,4 @@ mkdir -p -- "$dir/$name/LICENSES"
 cp -- LICENSES/Apache-2.0.txt LICENSES/LicenseRef-BSD-3-Clause-Google.txt \
 	"$dir/$name/LICENSES"
 cat pkgs/cloud-hypervisor/*.patch >"$dir/$name/cloud-hypervisor.patch"
-cat pkgs/cloud-hypervisor/vhost/*.patch >"$dir/$name/vhost.patch"
 tar -C "$dir" -czf "$name.tar.gz" -- "$name"
diff --git a/tools/start-vmm/ch.rs b/tools/start-vmm/ch.rs
index 239d08e..e81876b 100644
--- a/tools/start-vmm/ch.rs
+++ b/tools/start-vmm/ch.rs
@@ -35,7 +35,9 @@ pub struct FsConfig {
 }
 
 #[derive(Serialize)]
-pub struct GpuConfig {
+pub struct GenericVhostUserConfig {
+    pub device_type: u32,
+    pub queue_sizes: Vec<u16>,
     pub socket: String,
 }
 
@@ -76,7 +78,7 @@ pub struct VmConfig {
     pub console: ConsoleConfig,
     pub disks: Vec<DiskConfig>,
     pub fs: [FsConfig; 1],
-    pub gpu: [GpuConfig; 1],
+    pub generic_vhost_user: [GenericVhostUserConfig; 1],
     pub memory: MemoryConfig,
     pub net: Vec<NetConfig>,
     pub payload: PayloadConfig,
diff --git a/tools/start-vmm/lib.rs b/tools/start-vmm/lib.rs
index 1b55e04..9621b08 100644
--- a/tools/start-vmm/lib.rs
+++ b/tools/start-vmm/lib.rs
@@ -15,8 +15,8 @@ use std::io::ErrorKind;
 use std::path::Path;
 
 use ch::{
-    ConsoleConfig, DiskConfig, FsConfig, GpuConfig, LandlockConfig, MemoryConfig, NetConfig,
-    PayloadConfig, VmConfig, VsockConfig,
+    ConsoleConfig, DiskConfig, FsConfig, GenericVhostUserConfig, LandlockConfig, MemoryConfig,
+    NetConfig, PayloadConfig, VmConfig, VsockConfig,
 };
 use net::MacAddress;
 
@@ -87,10 +87,12 @@ pub fn vm_config(vm_dir: &Path) -> Result<VmConfig, String> {
                 "/run/service/vm-services/instance/{vm_name}/data/service/vhost-user-fs/env/virtiofsd.sock"
             ),
         }],
-        gpu: [GpuConfig {
+        generic_vhost_user: [GenericVhostUserConfig {
+            queue_sizes: vec![256, 16],
             socket: format!(
                 "/run/service/vm-services/instance/{vm_name}/data/service/vhost-user-gpu/env/crosvm.sock"
             ),
+            device_type: 16,
         }],
         memory: MemoryConfig {
             size: 1 << 30,
diff --git a/tools/start-vmm/tests/vm_command-basic.rs b/tools/start-vmm/tests/vm_command-basic.rs
index 80937ef..fcc7c96 100644
--- a/tools/start-vmm/tests/vm_command-basic.rs
+++ b/tools/start-vmm/tests/vm_command-basic.rs
@@ -32,8 +32,8 @@ fn main() -> std::io::Result<()> {
     let expected =
         "/run/service/vm-services/instance/testvm/data/service/vhost-user-fs/env/virtiofsd.sock";
     assert_eq!(fs1.socket, expected);
-    assert_eq!(config.gpu.len(), 1);
-    let gpu1 = &config.gpu[0];
+    assert_eq!(config.generic_vhost_user.len(), 1);
+    let gpu1 = &config.generic_vhost_user[0];
     let expected =
         "/run/service/vm-services/instance/testvm/data/service/vhost-user-gpu/env/crosvm.sock";
     assert_eq!(gpu1.socket, expected);
diff --git a/vm/sys/net/default.nix b/vm/sys/net/default.nix
index a722b02..1933c07 100644
--- a/vm/sys/net/default.nix
+++ b/vm/sys/net/default.nix
@@ -25,7 +25,7 @@ let
       # the kernel that *hasn't* been built to be compressed.  Weird!
       "compressed/vmlinux.bin"
     else
-      stdenvNoCC.hostPlatform.linux-kernel.target;
+      kernel.target;
 
   kernel = (linux_latest.override {
     structuredExtraConfig = with lib.kernel; {
-- 
2.54.0


  reply	other threads:[~2026-07-08 15:44 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-08 15:43 [RFC PATCH 0/3] lib/nixpkgs.default.nix: update to master Alyssa Ross
2026-07-08 15:43 ` Alyssa Ross [this message]
2026-07-10 10:28   ` [RFC PATCH 1/3] " Alyssa Ross
2026-07-11 18:33     ` Alyssa Ross
2026-07-08 15:43 ` [RFC PATCH 2/3] Documentation: update Cloud Hypervisor GPU outlook Alyssa Ross
2026-07-08 15:44 ` [RFC PATCH 3/3] xorg.lndir -> lndir Alyssa Ross

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260708154315.678807-2-hi@alyssa.is \
    --to=hi@alyssa.is \
    --cc=colby@colbyt.com \
    --cc=demiobenour@gmail.com \
    --cc=devel@spectrum-os.org \
    --cc=valentin@gagarin.work \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://spectrum-os.org/git/doc
	https://spectrum-os.org/git/mktuntap
	https://spectrum-os.org/git/spectrum
	https://spectrum-os.org/git/ucspi-vsock

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).