From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from atuin.qyliss.net (localhost [IPv6:::1]) by atuin.qyliss.net (Postfix) with ESMTP id 35C9D2B71; Wed, 08 Jul 2026 15:44:00 +0000 (UTC) Received: by atuin.qyliss.net (Postfix, from userid 993) id AF09E2B66; Wed, 08 Jul 2026 15:43:58 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-26) on atuin.qyliss.net X-Spam-Level: X-Spam-Status: No, score=-0.8 required=3.0 tests=DKIM_SIGNED,DKIM_VALID, DKIM_VALID_AU,DMARC_MISSING,RCVD_IN_DNSWL_LOW,SPF_HELO_PASS autolearn=unavailable autolearn_force=no version=4.0.1 Received: from fhigh-a6-smtp.messagingengine.com (fhigh-a6-smtp.messagingengine.com [103.168.172.157]) by atuin.qyliss.net (Postfix) with ESMTPS id 170D62B47 for ; Wed, 08 Jul 2026 15:43:55 +0000 (UTC) Received: from phl-compute-01.internal (phl-compute-01.internal [10.202.2.41]) by mailfhigh.phl.internal (Postfix) with ESMTP id 4ED681400096; Wed, 8 Jul 2026 11:43:54 -0400 (EDT) Received: from phl-frontend-04 ([10.202.2.163]) by phl-compute-01.internal (MEProxy); Wed, 08 Jul 2026 11:43:54 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=alyssa.is; h=cc :cc:content-transfer-encoding:content-type:content-type:date :date:from:from:in-reply-to:in-reply-to:message-id:mime-version :references:reply-to:subject:subject:to:to; s=fm1; t=1783525434; x=1783611834; bh=E3liGhqs+6oPeYVA7YIPD7Vn91ziZe43ZyEjgNm78gM=; b= oaCdKUyCXolK11C6EP1uBeXshxdrAAAsxPBMaWEaCo/AyTQ4OijQmIVZ51cKqBtu HkD/nRpCozaqFhVjZrEqFqzX+UjcVrbwtdHthbQOfhbI2AO1RUueie9peCXuPKNS G+5lTNAUJn+7ljqQACgW9qwz5N9neCV9maH18yTXbuQ91NXY7WJ1AojC8wTdeXGs 2r7EVBO35NoCPdYUUinh5lBba7UKozcRPZY8G1Lun8/9tzKNJXiWrWfLswNmKUSY XE1i4SqerBRdkqNXWgjy4du4G7qo9XPz+E87Cq2QG/EDvEdk1WhcEyDDnz3i5qG/ gXMAargd3W4DlmbAq32Wig== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:cc:content-transfer-encoding :content-type:content-type:date:date:feedback-id:feedback-id :from:from:in-reply-to:in-reply-to:message-id:mime-version :references:reply-to:subject:subject:to:to:x-me-proxy :x-me-sender:x-me-sender:x-sasl-enc; s=fm2; t=1783525434; x= 1783611834; bh=E3liGhqs+6oPeYVA7YIPD7Vn91ziZe43ZyEjgNm78gM=; b=M HUFoW+R95KIfIBtGiNRLrBPA2YK+48UtP6B0wSCUrP1QD/9+aEi/FsqtqyaUa6rc QRq6c/2cOLI/nyUEhgmrIl7AhIQZ8G7NR9uDRBsGdyiaqEzgodk/XW6e3xEQuxLf GtTOO0JZ9bBg2ClA0yFXXtwufFPMJua4S4htYZF5RtakUOGXhKMZ2nA/zEFxo2fL tbwSGs0AJwVW1/VfETQgfL/fmLfL9mluEzn/DhyQRKy0g5IUVtGQXFSF4MfE8Rbg A1DgLeMJTxDsJYfsTsXi2chRrXRStt4KqLigEgdqaB/K6FEka8xT0I1zrYJ75SIu veszvAr8/FeJ53L+lnDUA== X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: dmFkZTE3psKTvtvHhAJxeIp/PW0r+qT7Gd76UF5HBN3r/VHJq0S7zNGzuPPXKrljJq9MC3 Hw/rwSf4KnX81fCg5dRYwTuyqBgR48jyzvJ4Q5eGii61uPeqcUAxSv6ZUwLLbFrODPSBQ0 Qf8UZhvD0bKYaESW281e4oJKKe/E5bwSPZcKx3vcG8GqlX5vVKhLW+gor3uD2YFAl9dydh e962rNULlsZVe1wfrHADhUN/1h9VMnhEY6udLWvRD4cw5AW/hJXqf+zivH+X4UGg88g9iF jZQ2roj4MMOQRw2JUxyj18eQd8aH91OT8kMpVkzoqEjsSSx3LdHyNakXHvuTPwiKsPMooh 1cxAGf9J4ps91x7tFFgvciiQp2xVXzQtvSAE/cdzX2hNDMJrtcxx6TRVX/rsXKGMz2/592 FZBMg9lIUzPT3/FmWKZIgbdqkVqqzICNdYOe/phYF23eL6jQVy5qd+ni5wPPu0132B2WBs LHtwTqFaxG8RxAnIsDxcC6dhHhTrcBEivQPqFLviLpHMh/97TLrpP092vd4MT8n0ayXgMc pIMGWBxL/2XXhXETgY+ybqJwIc5Hi5bYOCBjZubjLyYzJFPKcabJhBt0WXN5sj71ptb/lc BpHp6AhxkNP3/UoNmE4lctMCqfGOByAEfx7+zdlWWyTUf9fv0xrE9luvKE9Rs0s30e5IkI T+ldTRrORR54Y3IctdVU4qTBUyGtz6u4Ec1qhYxnk5+UEzgrXZ0v8AvAsnFEPFdb5WBDJb w3ZK01I7MgoG5HsDg8CcwLZAlqQbpk5nssZDMcCRpZX7CeA3URBj44h7GuJxdVi694oL59 O7f92Z6CgMMEPHtl6fsupCDmGNIAQO/qz74J7gJb2jLDb7RShrV5+AZEHul0eGN2L5gzK0 VCIED61mUPjGMvHSRrHgL9Se3b+pRHjJq8yANVyvOfPayJVX7wpCJoMIbD/U7Uxpph8I+G FDVKN8kr24swOGUih/pNTkpe81GLY5qdQ/5t8bNHVpA3j78sx9k X-ME-Proxy: Feedback-ID: i12284293:Fastmail Received: by mail.messagingengine.com (Postfix) with ESMTPA; Wed, 8 Jul 2026 11:43:53 -0400 (EDT) Received: by mbp.qyliss.net (Postfix, from userid 1000) id 096D8889CEB5; Wed, 08 Jul 2026 17:43:52 +0200 (CEST) From: Alyssa Ross To: devel@spectrum-os.org Subject: [RFC PATCH 1/3] lib/nixpkgs.default.nix: update to master Date: Wed, 8 Jul 2026 17:43:13 +0200 Message-ID: <20260708154315.678807-2-hi@alyssa.is> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260708154315.678807-1-hi@alyssa.is> References: <20260708154315.678807-1-hi@alyssa.is> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Message-ID-Hash: PHW5CAFIBJ2YFVPDS5QIIOKM3WS3WD4N X-Message-ID-Hash: PHW5CAFIBJ2YFVPDS5QIIOKM3WS3WD4N X-MailFrom: hi@alyssa.is X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; header-match-devel.spectrum-os.org-0; header-match-devel.spectrum-os.org-1; header-match-devel.spectrum-os.org-2; header-match-devel.spectrum-os.org-3; header-match-devel.spectrum-os.org-4; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Valentin Gagarin , Demi Marie Obenour , colbyt X-Mailman-Version: 3.3.10 Precedence: list List-Id: Patches and low-level development discussion Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: 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 --- .../software/cloud-hypervisor/index.html | 19 +- host/efi.nix | 2 +- host/initramfs/shell.nix | 2 +- host/rootfs/shell.nix | 2 +- img/app/Makefile | 2 +- img/app/default.nix | 2 +- lib/nixpkgs.default.nix | 4 +- .../0001-build-use-local-vhost.patch | 55 - ...0002-virtio-devices-add-a-GPU-device.patch | 1632 ----------------- pkgs/cloud-hypervisor/default.nix | 34 +- pkgs/cloud-hypervisor/gpu.patch | 719 ++++++++ ...vhost_user-add-get_size-to-MsgHeader.patch | 82 - ...2-vhost-fix-receiving-reply-payloads.patch | 165 -- ...ser-add-shared-memory-region-support.patch | 484 ----- ...ost_user-add-protocol-flag-for-shmem.patch | 43 - pkgs/gtk3/default.nix | 13 - pkgs/overlay.nix | 4 - pkgs/skaware-packages/default.nix | 24 - release/checks/no-roothash.nix | 4 +- release/checks/wayland/default.nix | 2 +- .../checks/wayland/surface-notify/meson.build | 2 +- release/live/default.nix | 2 +- scripts/dist-cloud-hypervisor.sh | 1 - tools/start-vmm/ch.rs | 6 +- tools/start-vmm/lib.rs | 8 +- tools/start-vmm/tests/vm_command-basic.rs | 4 +- vm/sys/net/default.nix | 2 +- 27 files changed, 750 insertions(+), 2569 deletions(-) delete mode 100644 pkgs/cloud-hypervisor/0001-build-use-local-vhost.patch delete mode 100644 pkgs/cloud-hypervisor/0002-virtio-devices-add-a-GPU-device.patch create mode 100644 pkgs/cloud-hypervisor/gpu.patch delete mode 100644 pkgs/cloud-hypervisor/vhost/0001-vhost_user-add-get_size-to-MsgHeader.patch delete mode 100644 pkgs/cloud-hypervisor/vhost/0002-vhost-fix-receiving-reply-payloads.patch delete mode 100644 pkgs/cloud-hypervisor/vhost/0003-vhost_user-add-shared-memory-region-support.patch delete mode 100644 pkgs/cloud-hypervisor/vhost/0004-vhost_user-add-protocol-flag-for-shmem.patch delete mode 100644 pkgs/gtk3/default.nix delete mode 100644 pkgs/skaware-packages/default.nix diff --git a/Documentation/software/cloud-hypervisor/index.html b/Documentation/software/cloud-hypervisor/index.html index 399b5e5..82c7b24 100644 --- a/Documentation/software/cloud-hypervisor/index.html +++ b/Documentation/software/cloud-hypervisor/index.html @@ -25,24 +25,15 @@ compartmentalized desktop operating system project.

Building

-It’s necessary to patch both Cloud Hypervisor itself, and -the vhost -crate. The Cloud Hypervisor patches expect to find the patched vhost -source tree under the same parent directory as Cloud Hypervisor’s. -
  1. - Clone the repositories + Clone the repository for Cloud - Hypervisor - and vhost. 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.
  2. - 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.
  3. Use cargo build in the Cloud Hypervisor source tree as @@ -57,7 +48,7 @@ Start the crosvm GPU device: Connect to the GPU device with cloud-hypervisor: -
    cloud-hypervisor --gpu socket=gpu.sock …
    +
    cloud-hypervisor --generic-vhost-user virtio_id=gpu,socket=gpu.sock,queue_sizes='[16,256]' …

    Downloads

    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 -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 -SPDX-License-Identifier: Apache-2.0 AND LicenseRef-BSD-3-Clause-Google - -Signed-off-by: Alyssa Ross -Signed-off-by: Alyssa Ross ---- - 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 -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 -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 -Co-authored-by: Alyssa Ross -Signed-off-by: Alyssa Ross ---- - 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>; - fn vm_add_disk(&self, disk_config: &str) -> zbus::Result>; - fn vm_add_fs(&self, fs_config: &str) -> zbus::Result>; -+ fn vm_add_gpu(&self, gpu_config: &str) -> zbus::Result>; - fn vm_add_net(&self, net_config: &str) -> zbus::Result>; - fn vm_add_pmem(&self, pmem_config: &str) -> zbus::Result>; - fn vm_add_user_device(&self, vm_add_user_device: &str) -> zbus::Result>; -@@ -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::("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::("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 { - Ok(fs_config) - } - -+fn add_gpu_config(config: &str) -> Result { -+ 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 { - 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=") - .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, -- pub region_list: Vec, -+ pub region_list: BTreeMap, - } - - /// 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)> { - ] - } - -+fn virtio_vhost_gpu_thread_rules() -> Vec<(i64, Vec)> { -+ 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)> { - vec![] - } -@@ -271,6 +286,7 @@ fn get_seccomp_rules(thread_type: Thread) -> Vec<(i64, Vec)> { - 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 -+// 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, -+} -+ -+impl BackendReqHandler { -+ fn ptr_guard_mut(&self, offset: u64, len: u64) -> io::Result { -+ 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 { -+ 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 { -+ 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, -+ backend_req_support: bool, -+ seccomp_action: SeccompAction, -+ guest_memory: Option>, -+ epoll_thread: Option>, -+ 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, -+ interrupt_cb: Arc, -+ 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> { -+ // 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 { -+ 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, -+ ) -> std::result::Result<(), crate::Error> { -+ self.vu_common.add_memory_region(&self.guest_memory, region) -+ } -+ -+ fn userspace_mappings(&self) -> Vec { -+ 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> { -+ 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> { -+ 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> { - 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 = 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>, VmError>; - -+ fn vm_add_gpu(&mut self, gpu_cfg: GpuConfig) -> Result>, VmError>; -+ - fn vm_add_pmem(&mut self, pmem_cfg: PmemConfig) -> Result>, VmError>; - - fn vm_add_net(&mut self, net_cfg: NetConfig) -> Result>, VmError>; -@@ -539,6 +545,43 @@ impl ApiAction for VmAddFs { - } - } - -+pub struct VmAddGpu; -+ -+impl ApiAction for VmAddGpu { -+ type RequestBody = GpuConfig; -+ type ResponseBody = Option; -+ -+ fn request( -+ &self, -+ config: Self::RequestBody, -+ response_sender: Sender, -+ ) -> 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, -+ data: Self::RequestBody, -+ ) -> ApiResult { -+ 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>, -+ pub gpu: Option>, - pub pmem: Option>, - pub serial: &'a str, - pub console: &'a str, -@@ -451,6 +458,9 @@ impl<'a> VmParams<'a> { - let fs: Option> = args - .get_many::("fs") - .map(|x| x.map(|y| y as &str).collect()); -+ let gpu: Option> = args -+ .get_many::("gpu") -+ .map(|x| x.map(|y| y as &str).collect()); - let pmem: Option> = args - .get_many::("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=,id=,pci_segment=\""; -+ -+ pub fn parse(gpu: &str) -> Result { -+ 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=,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> = 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> = 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 { -+ 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>, -+ 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 { -+ 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 { - 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>, 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>, 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 { -+ 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 { - 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, -+ #[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, - pub fs: Option>, -+ pub gpu: Option>, - pub pmem: Option>, - #[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 +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 +Co-authored-by: Alyssa Ross +Signed-off-by: Alyssa Ross +--- + 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, +- pub region_list: Vec, ++ pub region_list: BTreeMap, + } + + 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 +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 +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 +Co-authored-by: Alyssa Ross +Signed-off-by: Alyssa Ross +--- + 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 + // 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, ++ mappings: Mutex>, ++} ++ + struct BackendReqHandler { + interrupt_cb: Arc, ++ region: Option, ++} ++ ++impl BackendReqHandler { ++ fn ptr_guard_mut(&self, offset: u64, len: u64) -> io::Result { ++ 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 { ++ 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 { ++ 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, + seccomp_action: SeccompAction, + guest_memory: Option>, + exit_evt: EventFd, +@@ -68,12 +210,11 @@ impl GenericVhostUser { + path: &str, + request_queue_sizes: Vec, + device_type: u32, +- cache: Option<(VirtioSharedMemoryList, MmapRegion)>, + seccomp_action: SeccompAction, + exit_evt: EventFd, + access_platform_enabled: bool, + state: Option, +- ) -> Result { ++ ) -> Result<(GenericVhostUser, BTreeMap)> { + // 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 { +@@ -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 { +- 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 { + 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 { ++ 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 +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 +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 +--- + .../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 -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 -SPDX-License-Identifier: Apache-2.0 - -Signed-off-by: Alyssa Ross ---- - 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 VhostUserGpuMsgHeader { - } - } - -- /// 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 VhostUserMsgValidator for VhostUserGpuMsgHeader { - impl MsgHeader for VhostUserGpuMsgHeader { - 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 { - impl MsgHeader for VhostUserMsgHeader { - type Request = R; - const MAX_MSG_SIZE: usize = MAX_MSG_SIZE; -+ -+ fn get_size(&self) -> u32 { -+ self.size -+ } - } - - impl Debug for VhostUserMsgHeader { -@@ -336,11 +343,6 @@ impl VhostUserMsgHeader { - } - } - -- /// 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 -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 -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 -Signed-off-by: Alyssa Ross ---- - 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 Endpoint { - /// 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 Endpoint { - #[allow(clippy::type_complexity)] - pub fn recv_payload_into_buf( - &mut self, -- buf: &mut [u8], -- ) -> Result<(H, T, usize, Option>)> { -- let mut hdr = H::default(); -+ ) -> Result<(H, T, Vec, Option>)> { - let mut body: T = Default::default(); -+ let (hdr, files) = self.recv_header()?; -+ -+ let payload_size = hdr.get_size() as usize - mem::size_of::(); -+ let mut buf: Vec = vec![0; payload_size]; - let mut iovs = [ -- iovec { -- iov_base: (&mut hdr as *mut H) as *mut c_void, -- iov_len: mem::size_of::(), -- }, - iovec { - iov_base: (&mut body as *mut T) as *mut c_void, - iov_len: mem::size_of::(), -@@ -570,19 +568,16 @@ impl Endpoint { - 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::() + mem::size_of::(); -- 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, - ) -> VhostUserResult<(T, Vec, Option>)> { -- if mem::size_of::() > MAX_MSG_SIZE -- || hdr.get_size() as usize <= mem::size_of::() -- || hdr.get_size() as usize > MAX_MSG_SIZE -- || hdr.is_reply() -- { -+ if mem::size_of::() > MAX_MSG_SIZE || hdr.is_reply() { - return Err(VhostUserError::InvalidParam); - } - self.check_state()?; - -- let mut buf: Vec = vec![0; hdr.get_size() as usize - mem::size_of::()]; -- let (reply, body, bytes, files) = self.main_sock.recv_payload_into_buf::(&mut buf)?; -- if !reply.is_reply_for(hdr) -- || reply.get_size() as usize != mem::size_of::() + bytes -- || files.is_some() -- || !body.is_valid() -- || bytes != buf.len() -- { -+ let (reply, body, buf, files) = self.main_sock.recv_payload_into_buf::()?; -+ 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::(&mut recv_buf) -+ let (hdr, req_body, recv_buf, fds) = frontend -+ .recv_payload_into_buf::() - .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::(&mut recv_buf) -+ let (hdr, req_body, recv_buf, fds) = frontend -+ .recv_payload_into_buf::() - .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 -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 -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 -Commit-Queue: David Stevens -Reviewed-by: Alexandre Courbot -Tested-by: kokoro -(cherry-picked from commit f436e2706011fa5f34dc415972434aa3299ebc43) -Signed-off-by: Alyssa Ross -[renumbered for crosvm commit e428c4ba88a26695f63929c680b4101a9b8d5cbc] -Signed-off-by: Alyssa Ross ---- - 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> { -+ 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) -> Result { -+ fn wait_for_reply(&mut self, hdr: &VhostUserMsgHeader) -> Result { - 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 { -+ 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 { -+ 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; - 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>; - fn set_device_state_fd( - &self, - direction: VhostTransferStateDirection, -@@ -139,6 +140,7 @@ pub trait VhostUserBackendReqHandlerMut { - fn get_max_mem_slots(&mut self) -> Result; - 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>; - fn set_device_state_fd( - &mut self, - direction: VhostTransferStateDirection, -@@ -274,6 +276,10 @@ impl VhostUserBackendReqHandler for Mutex { - self.lock().unwrap().remove_mem_region(region) - } - -+ fn get_shared_memory_regions(&self) -> Result> { -+ self.lock().unwrap().get_shared_memory_regions() -+ } -+ - fn set_device_state_fd( - &self, - direction: VhostTransferStateDirection, -@@ -634,6 +640,15 @@ impl BackendReqHandler { - 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> { -+ 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>; -+ - /// 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> { -+ 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::(&hdr)?; -+ let struct_size = mem::size_of::(); -+ 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 { - 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 { -+ Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) -+ } -+ -+ /// Handle shared memory region unmapping requests. -+ fn shmem_unmap(&self, _req: &VhostUserShmemUnmapMsg) -> HandlerResult { -+ 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 { -+ Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) -+ } -+ -+ /// Handle shared memory region unmapping requests. -+ fn shmem_unmap(&mut self, _req: &VhostUserShmemUnmapMsg) -> HandlerResult { -+ 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 VhostUserFrontendReqHandler for Mutex - ) -> HandlerResult { - self.lock().unwrap().shared_object_lookup(uuid, fd) - } -+ -+ fn shmem_map(&self, req: &VhostUserShmemMapMsg, fd: &dyn AsRawFd) -> HandlerResult { -+ self.lock().unwrap().shmem_map(req, fd) -+ } -+ -+ fn shmem_unmap(&self, req: &VhostUserShmemUnmapMsg) -> HandlerResult { -+ self.lock().unwrap().shmem_unmap(req) -+ } - } - - /// Server to handle service requests from backends from the backend communication channel. -@@ -241,10 +269,23 @@ impl FrontendReqHandler { - .shared_object_lookup(&msg, &files.unwrap()[0]) - .map_err(Error::ReqHandlerError) - } -+ Ok(BackendReq::SHMEM_MAP) => { -+ let msg = self.extract_msg_body::(&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::(&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 FrontendReqHandler { - files: &Option>, - ) -> 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 FrontendReqHandler { - )) - } - -- fn send_ack_message( -+ fn send_reply( - &mut self, - req: &VhostUserMsgHeader, - res: &Result, - ) -> 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::(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 -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 -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 -Commit-Queue: David Stevens -(cherry-picked from commit 60aa43629ae9be2cc3df37c648ab7e0e5ff2172c) -[renumbered for crosvm commit 6795b3edf3ddc0b3a2e2b928033f21dabcf8be78] -Signed-off-by: Alyssa Ross ---- - 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 -# 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 -# 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, pub socket: String, } @@ -76,7 +78,7 @@ pub struct VmConfig { pub console: ConsoleConfig, pub disks: Vec, pub fs: [FsConfig; 1], - pub gpu: [GpuConfig; 1], + pub generic_vhost_user: [GenericVhostUserConfig; 1], pub memory: MemoryConfig, pub net: Vec, 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 { "/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