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 ++++++++
| 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
next prev parent 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).