From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [IPv6:2a0f:8001:1:32::40]) by lore.proxmox.com (Postfix) with ESMTPS id 34E661FF0B7 for ; Tue, 25 Aug 2026 13:35:28 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id D36A921639; Tue, 25 Aug 2026 13:35:13 +0200 (CEST) ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=TI0mJ94sEMaC/+oxKxIFeU9LMMhw+lAyTP2z1dsxdy1CvJnisO0J5VhP3b5kVt5WxfxVnkASamcPnnubZbmgBQVKffXq8me3639h9ZaoqnF9JN6vhPSPUHAwnflFFu2ZV2WmETUF4VhmPSuW/eac93sY6yvX8cCXGpADoC5nLrcUXx9kiisHcQRHUSC+RIN4EJ1gWPoIlGyw6TQot9HRt2VtoMKz5Ng9xMB0msiHp4dIaMEFGvO3qntAB/D0sunYVsYY3084F6z1aMLxuYstRDCVVtHiXR6sS7tvvHcQILYrciSaN6BufCAJ9kmP/ATWsINrme0Wsq4Z4IaUE0OHBw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector10001; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=3rUGTuEEIcoMBan6N7BvW0nEp5kGppe1vx8aeQcMDnw=; b=FA2hWV+rIvL7NfPjTmLhMqOXqLLMg433mAqarqb/Fojf7jpOnoy92IVVg0OeNNBnCTQBlul/+r466TUWA4qqD9pvUuIDubyuR3Xd0avpEBdfqlm3hmzDSyAVsM/wAPEHEoQz3DWlnevoWs08tLbwcnhXMVGPu5WZYJDXAYMh4kwAtSiK4C0pYkjOIo2sB4EsJfWgbWt2dqQvfMVDSd+VlSCrAlcHECtxw4pnv0wJYCrr5P2QXHlJm17d3Qv4KCNSCfOVQZrfT4h9v3mwmXUUp/Qvdt7OCMeCDxrPHnN+NQq/5vgOggQh6osMmP29Vu1kAe+cyBZSSv+somUwMLB8Vg== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=groupe-cyllene.com; dmarc=pass action=none header.from=groupe-cyllene.com; dkim=pass header.d=groupe-cyllene.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=groupecyllene.onmicrosoft.com; s=selector2-groupecyllene-onmicrosoft-com; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=3rUGTuEEIcoMBan6N7BvW0nEp5kGppe1vx8aeQcMDnw=; b=0yeFRArM1lh4oHXb40rik0+NW3SG7KSiM8A2hPx/WwZNbCOJJIlioylce9G0a8Awq3LVZL5OD7q1tJ3v8Ev+eobrtKCHe9rBzlQ4/nz4GhuRREpdh9lOQkJnvSzBLLcTzZTESaWKFux4hVEbzUkIRFX+z/4mMyMPBU5xkvHSIkM= Authentication-Results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=groupe-cyllene.com; From: Alexandre Derumier To: pve-devel@lists.proxmox.com Subject: [RFC qemu-server 05/13] add experimental kyber-gl display Date: Tue, 25 Aug 2026 13:34:30 +0200 Message-ID: <20260825113442.947620-6-alexandre.derumier@groupe-cyllene.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260825113442.947620-1-alexandre.derumier@groupe-cyllene.com> References: <20260825113442.947620-1-alexandre.derumier@groupe-cyllene.com> Content-Transfer-Encoding: 8bit Content-Type: text/plain X-ClientProxiedBy: PR3P192CA0021.EURP192.PROD.OUTLOOK.COM (2603:10a6:102:56::26) To PR0P264MB3691.FRAP264.PROD.OUTLOOK.COM (2603:10a6:102:14b::12) MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: PR0P264MB3691:EE_|PATP264MB7783:EE_ X-MS-Office365-Filtering-Correlation-Id: 58bf2701-bd06-480e-d7fa-08df029ce564 X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0;ARA:13230040|52116014|376014|1800799024|23010399003|366016|38350700014|6133799003|3023799007|56012099006|10067099003|5023799004|20052099010|18002099003|22082099003; X-Microsoft-Antispam-Message-Info: JEpbQHx4o6gXqI1pDkrd017IIhzpEiLL2reE4e73Rtzc5eLkmNpp4Fe358jXDt3qYJ4g9zTwadzXgbDP2nzwPnj6iRYtw09y+6Etzb1NDFF8zINlCKkSo2HJ4Hlq5Tu3ZwvJjDb1c2mGeGq3B99av4/lKnxSeM062qEVp2aUHooP+w+w8dqym/oyUBxRM369GxlEifgygnOHqcS/QK/sAnz3l7HfVjLfnNxC7e32r36orrMST/NtNBne5xrQZSc/D4FC6wR8Zfr+BxxhGDvvAaSVrK73GG9N3c/FZtQNBcKdwV1WsWoMeBZXNuQnb88qvcsm3LJ18h1OlOsAvbroxD2nP9GCnycBdd8dXPiA6XHA+S5z8w7isvt5IhqeV3ns3PZ8Z06FqmIDoygUE5KsETaUAJttTT7C+oVf3YYHdJSqAt0pcXYqPSsijYGSb/fECOJR3815DyuDTyYXfIIKuNnc1Ijby/65dsjHBox5H2IFPKfS3Uc/q5BXPjSAd3jPbqhISMxYm4JGN/zbvsCC4rnrd0J3yxU2iB3IofE/H2ISSROrqRTDGyBTpYnLR10TnrferSyn6wYjyuo8+GmVIxRoR5y34jk2sDo0eFeccDKL1C1xr1dK46mfA4Qo7slWFD7hDD/KAm90J1As19gkz/2tY1gM3/ZWjNP9vPN8UmVzR2Cr7S8y9xCz3gU+OnFShJucgvai0pkto+sU+MlL0XE6LLpO98/xAcxAOrvfPyI= X-Forefront-Antispam-Report: CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:PR0P264MB3691.FRAP264.PROD.OUTLOOK.COM;PTR:;CAT:NONE;SFS:(13230040)(52116014)(376014)(1800799024)(23010399003)(366016)(38350700014)(6133799003)(3023799007)(56012099006)(10067099003)(5023799004)(20052099010)(18002099003)(22082099003);DIR:OUT;SFP:1102; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: =?us-ascii?Q?3SUTUDZmdW6IilnSDK7KyHuxtM4pjK+wEqnEmzA8jRk9gWLm8UANmjOE/MTV?= =?us-ascii?Q?muzogWL9TNLHjnv5AnWtZYvsG/LVf2QGtgAuqylbSpun0oBgsSweZTCNxCvn?= =?us-ascii?Q?JASeLkaW1ixRYr+349+ycRLrubTGnWAlnJp2SN2RM2p2G+hGvMv7cE7QwzqR?= =?us-ascii?Q?X/rx6oIQgDxyoj8e89pjvU/aAb2N3G9fCgQ2IwjdD0Ic1m7h+CQXEm6VQO3H?= =?us-ascii?Q?Mbsk+GULrkEIeppQtPAG0M+sfnQqA6jCI4SxiI+q9pV8+jkwRXmlaX11K34k?= =?us-ascii?Q?oaBjPGg2CTTiBpIRDsfvjUN0EBy+KHUu7gzZAdyJgneEo1FiSYJg7/QxIAuV?= =?us-ascii?Q?4o7vXDgrMELzi0r9V/Tls8YLiUxJ6F3nRf/cqyc24zYa8SUKGqs0+KSV4mS1?= =?us-ascii?Q?JdAqaSUjbQBlXWoMviJWTuSzAshzs7ck20Kjg+iG2zhiSgpfUSasislELCIP?= =?us-ascii?Q?CvXP8c6Y23/dHA8n5FdAKunxLtTPSZ4soPKDb00QTgNRq/vGjhShSXQ8RAQk?= =?us-ascii?Q?cnwp5WdkbX7sGvu1487R3tUciGFslMih2WJmOBmTAoxhvcBEw23SrDcHVQXA?= =?us-ascii?Q?J0HAeuEpkNG45pPVQbJ/FdrJZWpiL4miZ4r9J8SkCpt4IJjaZZGcjIiIGfDo?= =?us-ascii?Q?GY/VrhKkv2jQ7f+2smV8KwlgWWQOCcloR8pi5l6p6IRVwdiavJIx3U/KHDfE?= =?us-ascii?Q?0vXBGnvei0hFcrH3SJsUAL5MdMu6wCErS8kBXcwlejhNSX3wn7OTeWUuE8g6?= =?us-ascii?Q?zHLOwl0nokBHFETezbFjk8Bt1EPwQu6R//AARGFG+rREU0Qt/q8UVDs4g4vu?= =?us-ascii?Q?kwZSb6kJcuevDq2WCwDsMW3lQywCqpys9rMmDK4oZ7Q2j4R+A4FvPrFJnRZO?= =?us-ascii?Q?xArVjpfo/Qu/uD077tjuTtJwz24/hDqCjYMmSPwdkFJhJEDgXW0XuVT0l5eK?= =?us-ascii?Q?fJ88rKCVIHrRtb66y6AFzIuc3DxhQhtthsSFdXe68dIqncGH1vR1ve9Wrobx?= =?us-ascii?Q?m640E2rSu22Xn3in3uKROFn2t166BDtF+bpxN1VqfRWp04eDe4m68dKniAEG?= =?us-ascii?Q?Uk8S725c/OGe/S27F2dfTvIeE4xaNT/RPmOb2yEXh4iz5zWWT/IcLLjStS2K?= =?us-ascii?Q?wSrS4CRphCWLf8F6PcFNvZaeQfkI/iB8pyRul8RObuLDNtP+1nrRYxI6XhwH?= =?us-ascii?Q?Xlc8zERMXBHt7jgSZsvG44W67EuEnPB4ixlZhK3X4hPeuqGquoy/7xKWYw6d?= =?us-ascii?Q?/W9E/yxSlk6gtAZFi8FKyNOwNvDXkJu9tpzGhEZ6fsRq3FzPHxpXQWtlyLk1?= =?us-ascii?Q?UOZQoZ/J7CrzoY8xLxCO7ByRgbuV3/MjOfrOv+8KfuM31Nedc+/3gLht7M9c?= =?us-ascii?Q?Fchk1eCkDRRGvDGkdFdLAyqFoUT5tR5tB7HsCwwXzT4mA+OTOuKirq3bdw46?= =?us-ascii?Q?Jxk9tOA0Bgu2f4yDOq6vKpYkWzj2At3xY5x1QNZOfMEIomjwZFZPeD8aumRg?= =?us-ascii?Q?Muo/U1SPYnaH8CiZ+ZSinNUWfY1lKez7V6YL2ah2klRsU0gQQpnS/8p97Boe?= =?us-ascii?Q?Opg9v8Kq/EiitGgQGsQQqdjUatMXTdfmuxEeguh16cziFREb/JCF+KkVzv4y?= =?us-ascii?Q?+kZy5jyDr4qL2IydxuPP9GvWCk8aqL0Na9Np5nNRuk+FoawNs+gj7Zo33Z8f?= =?us-ascii?Q?F+BwwyHqDdD0+uVHdTkzQpfC4JPvJRhAbGTGc76m7mkdkze+1dfRdIWxP8cE?= =?us-ascii?Q?JHi4oWbdD3jKh2wTq/gbb4pGV8Qex3X2qRojwxakvTphj488T5Ox?= X-OriginatorOrg: groupe-cyllene.com X-MS-Exchange-CrossTenant-Network-Message-Id: 58bf2701-bd06-480e-d7fa-08df029ce564 X-MS-Exchange-CrossTenant-AuthSource: PR0P264MB3691.FRAP264.PROD.OUTLOOK.COM X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 25 Aug 2026 11:34:59.9704 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: ee11ccf7-112c-4284-848b-f229745e715b X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: 0O5rsM5fir2SHG8wm6gLWlo7vlI0buKHmd7ibdQOXA1kw+NrRcnc5gG+/5crUy0z/sVkAx1AArZcUucyVkz4KnCtN2TbnzgEd3LP1z4BnPcitiVCact1ZgLlR0NOjJlQ X-MS-Exchange-Transport-CrossTenantHeadersStamped: PATP264MB7783 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.000 Adjusted score from AWL reputation of From: address DKIM_SIGNED 0.1 Message has a DKIM or DK signature, not necessarily valid DKIM_VALID -0.1 Message has at least one valid DKIM or DK signature DMARC_PASS -0.1 DMARC pass policy RCVD_IN_DNSWL_NONE -0.0001 Sender listed at https://www.dnswl.org/, no trust SPF_HELO_PASS -0.001 SPF: HELO matches SPF record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: HKGODL6TXFEQGV3FMYKUTZSSWYRJGQIS X-Message-ID-Hash: HKGODL6TXFEQGV3FMYKUTZSSWYRJGQIS X-MailFrom: Alexandre.DERUMIER@groupe-cyllene.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: kyber-gl display is mapping on vhost-user-gpu-pci. virgl in a helper process rather than inside QEMU: it exports a dmabuf, so QEMU emits ScanoutDMABUF2 and the encoder imports it instead of reading pixels back. Currently experimental because of detected bugs: - tried vhost-user-gpu instead vhost-user-gpu-pci, but deadlock intermittently on QEMU 11.0.2, blocking the main thread under the BQL, so the guest freezes rather than just the stream. - Modes whose stride is not a multiple of 256 - 800x600 and 1360x768 - fail to import on radeonsi and are never streamed. The rest import once the retry is made with an explicit DRM_FORMAT_MOD_LINEAR, QEMU labelling its scanouts DRM_FORMAT_MOD_INVALID; those two need a copy instead. - The picture is flipped vertically through the kernel and plymouth phase, while GRUB and the desktop are both correct, so the origin changes twice within a session and y0_top does not track it. Signed-off-by: Alexandre Derumier --- src/PVE/API2/Qemu.pm | 9 ++- src/PVE/QemuServer.pm | 29 ++++++-- src/PVE/QemuServer/Kyber.pm | 4 +- src/PVE/QemuServer/Makefile | 1 + src/PVE/QemuServer/VhostUserGPU.pm | 103 +++++++++++++++++++++++++++++ src/test/cfg2cmd/kyber-gl.conf | 3 + src/test/cfg2cmd/kyber-gl.conf.cmd | 29 ++++++++ 7 files changed, 169 insertions(+), 9 deletions(-) create mode 100644 src/PVE/QemuServer/VhostUserGPU.pm create mode 100644 src/test/cfg2cmd/kyber-gl.conf create mode 100644 src/test/cfg2cmd/kyber-gl.conf.cmd diff --git a/src/PVE/API2/Qemu.pm b/src/PVE/API2/Qemu.pm index bae345b..79aab86 100644 --- a/src/PVE/API2/Qemu.pm +++ b/src/PVE/API2/Qemu.pm @@ -3371,9 +3371,10 @@ __PACKAGE__->register_method({ my $conf = PVE::QemuConfig->load_config($vmid, $node); my $vga = PVE::QemuServer::parse_vga($conf->{vga} // ''); + my $vga_type = $vga->{type} // ''; die "VM $vmid is not configured for the Kyber console" - . " - set its display to 'kyber' and restart it\n" - if ($vga->{type} // '') ne 'kyber'; + . " - set its display to 'kyber' or 'kyber-gl' and restart it\n" + if $vga_type !~ /^kyber(?:-gl)?$/; die "VM $vmid is not running\n" if !PVE::QemuServer::Helpers::vm_running_locally($vmid); @@ -3397,7 +3398,9 @@ __PACKAGE__->register_method({ # Only 'vnc' adds the vdagent chardev the guest needs to share a clipboard. my $clipboard = ($vga->{clipboard} // '') eq 'vnc'; - PVE::QemuServer::Kyber::write_env($vmid, $secret, $port, $clipboard); + my $dmabuf = $vga_type eq 'kyber-gl'; + + PVE::QemuServer::Kyber::write_env($vmid, $secret, $port, $clipboard, $dmabuf); PVE::QemuServer::Kyber::restart_controller($vmid); } diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm index 33a5e40..652f3ea 100644 --- a/src/PVE/QemuServer.pm +++ b/src/PVE/QemuServer.pm @@ -97,6 +97,7 @@ use PVE::QemuServer::RNG qw(parse_rng print_rng_device_commandline print_rng_obj use PVE::QemuServer::RunState; use PVE::QemuServer::StateFile; use PVE::QemuServer::USB; +use PVE::QemuServer::VhostUserGPU; use PVE::QemuServer::Virtiofs qw(max_virtiofs start_all_virtiofsd); use PVE::QemuServer::VolumeChain; use PVE::QemuServer::DBusDisplay; @@ -171,7 +172,7 @@ my $vga_fmt = { optional => 1, default_key => 1, enum => [ - qw(cirrus kyber qxl qxl2 qxl3 qxl4 none rdp serial0 serial1 serial2 serial3 std virtio virtio-gl vmware) + qw(cirrus kyber kyber-gl qxl qxl2 qxl3 qxl4 none rdp serial0 serial1 serial2 serial3 std virtio virtio-gl vmware) ], }, memory => { @@ -1491,6 +1492,7 @@ my $vga_map = { # A display transport, not a card, so it picks one: virtio-vga rather than a GL # variant, both of which currently break QEMU. 'kyber' => 'virtio-vga', + 'kyber-gl' => 'vhost-user-vga', # Same reasoning for the RDP console: it reads the same D-Bus display. 'rdp' => 'virtio-vga', }; @@ -1501,6 +1503,7 @@ my $vga_map_aarch64 = { 'virtio' => 'virtio-gpu', 'virtio-gl' => 'virtio-gpu-gl', 'kyber' => 'virtio-gpu', + 'kyber-gl' => 'vhost-user-gpu-pci', 'rdp' => 'virtio-gpu', }; @@ -1548,6 +1551,12 @@ sub print_vga_device { $memory = ",ram_size=67108864,vram_size=33554432"; } + my $chardev = ""; + if ($vga->{type} eq 'kyber-gl') { + $memory = ""; + $chardev = ",chardev=" . PVE::QemuServer::VhostUserGPU::chardev_id(); + } + my $edidoff = ""; if ($type eq 'VGA' && windows_version($conf->{ostype})) { $edidoff = ",edid=off" if (!defined($conf->{bios}) || $conf->{bios} ne 'ovmf'); @@ -1576,7 +1585,7 @@ sub print_vga_device { if !PVE::Tools::dir_glob_regex('/dev/dri/', "renderD.*"); } - return "$type,id=${vgaid}${memory}${max_outputs}${pciaddr}${edidoff}"; + return "$type,id=${vgaid}${memory}${chardev}${max_outputs}${pciaddr}${edidoff}"; } sub vm_is_volid_owner { @@ -3429,9 +3438,15 @@ sub config_to_command { push @$cmd, '-display', 'egl-headless,gl=core' if $vga->{type} eq 'virtio-gl'; # VIRGL - if ($vga->{type} =~ /^(?:kyber|rdp)$/) { + if ($vga->{type} =~ /^(?:kyber|kyber-gl|rdp)$/) { my $dbus = PVE::QemuServer::Helpers::dbus_socket($vmid); my $display = "dbus,addr=unix:path=$dbus"; + if ($vga->{type} eq 'kyber-gl') { + $display .= ",gl=on"; + my $socket = PVE::QemuServer::VhostUserGPU::socket_file($vmid); + my $id = PVE::QemuServer::VhostUserGPU::chardev_id(); + push @$cmd, '-chardev', "socket,id=$id,path=$socket"; + } # The display exports org.qemu.Display1.Audio only when told which audiodev to # read, and nothing else can consume a dbus audiodev. @@ -3474,6 +3489,7 @@ sub config_to_command { } my $virtiofs_enabled = PVE::QemuServer::Virtiofs::virtiofs_enabled($conf); + my $shared_memory = $virtiofs_enabled || ($vga->{type} // '') eq 'kyber-gl'; PVE::QemuServer::Memory::config( $conf, @@ -3481,7 +3497,7 @@ sub config_to_command { $sockets, $cores, $hotplug_features->{memory}, - $virtiofs_enabled, + $shared_memory, $cmd, $machineFlags, ); @@ -5849,7 +5865,9 @@ sub vm_start_nolock { # QEMU connects to the D-Bus address, so the bus has to be listening first. my $dbus_vga = parse_vga($conf->{vga} // ''); PVE::QemuServer::DBusDisplay::start($vmid) - if ($dbus_vga->{type} // '') =~ /^(?:kyber|rdp)$/; + if ($dbus_vga->{type} // '') =~ /^(?:kyber|kyber-gl|rdp)$/; + PVE::QemuServer::VhostUserGPU::start($vmid) + if ($dbus_vga->{type} // '') eq 'kyber-gl'; my $tpmpid; if ((my $tpm = $conf->{tpmstate0}) && !PVE::QemuConfig->is_template($conf)) { @@ -6242,6 +6260,7 @@ sub vm_stop_cleanup { PVE::QemuServer::Kyber::stop_controller($vmid); PVE::QemuServer::RDP::stop_server($vmid); PVE::QemuServer::DBusDisplay::stop($vmid); + PVE::QemuServer::VhostUserGPU::stop($vmid); }; warn $@ if $@; diff --git a/src/PVE/QemuServer/Kyber.pm b/src/PVE/QemuServer/Kyber.pm index 3115f82..953413b 100644 --- a/src/PVE/QemuServer/Kyber.pm +++ b/src/PVE/QemuServer/Kyber.pm @@ -84,14 +84,16 @@ sub running_secret { } sub write_env { - my ($vmid, $secret, $dataplane_port, $clipboard) = @_; + my ($vmid, $secret, $dataplane_port, $clipboard, $dmabuf) = @_; my $clipboard_env = $clipboard ? 1 : 0; + my $dmabuf_env = $dmabuf ? 1 : 0; my $env = <<"EOF"; KYBER_JWT_KEY=$secret KYBER_DATAPLANE_PORT=$dataplane_port KQS_CLIPBOARD=$clipboard_env +KQS_DMABUF=$dmabuf_env EOF my $path = env_file($vmid); diff --git a/src/PVE/QemuServer/Makefile b/src/PVE/QemuServer/Makefile index 38e5aa6..1bd7a2f 100644 --- a/src/PVE/QemuServer/Makefile +++ b/src/PVE/QemuServer/Makefile @@ -32,6 +32,7 @@ SOURCES=Agent.pm \ RunState.pm \ StateFile.pm \ USB.pm \ + VhostUserGPU.pm \ Virtiofs.pm \ VolumeChain.pm diff --git a/src/PVE/QemuServer/VhostUserGPU.pm b/src/PVE/QemuServer/VhostUserGPU.pm new file mode 100644 index 0000000..63d571b --- /dev/null +++ b/src/PVE/QemuServer/VhostUserGPU.pm @@ -0,0 +1,103 @@ +package PVE::QemuServer::VhostUserGPU; + +# virgl in a helper process rather than inside QEMU, for the 'kyber-gl' display. +# The helper renders and exports a dmabuf, so QEMU emits ScanoutDMABUF2 and the +# console encoder imports it instead of reading back pixels. + +use strict; +use warnings; + +use POSIX; +use Time::HiRes qw(usleep); + +use PVE::ProcFSTools; +use PVE::Tools qw(file_set_contents file_read_firstline); +use PVE::QemuServer::Helpers; + +my $BINARY = '/usr/lib/kvm/vhost-user-gpu'; + +sub socket_file { + my ($vmid) = @_; + return "$PVE::QemuServer::Helpers::var_run_tmpdir/$vmid.vhost-user-gpu.sock"; +} + +sub pidfile { + my ($vmid) = @_; + return "$PVE::QemuServer::Helpers::var_run_tmpdir/$vmid.vhost-user-gpu.pid"; +} + +sub chardev_id { + return 'vhost-user-gpu'; +} + +sub start { + my ($vmid) = @_; + + die "vhost-user-gpu is not installed ($BINARY)\n" if !-x $BINARY; + + stop($vmid); + + my $socket = socket_file($vmid); + my $pidfile = pidfile($vmid); + my $log = "$PVE::QemuServer::Helpers::var_run_tmpdir/$vmid-vhost-user-gpu.log"; + + my $pid = fork(); + die "could not fork to start vhost-user-gpu for VM $vmid\n" if !defined($pid); + + if ($pid == 0) { + POSIX::setsid(); + $0 = "task pve-vm$vmid-vhost-user-gpu"; + + my $pid2 = fork(); + if (!defined($pid2)) { + POSIX::_exit(1); + } elsif ($pid2 == 0) { + open(STDIN, '<', '/dev/null'); + open(STDOUT, '>>', $log); + open(STDERR, '>&', \*STDOUT); + exec($BINARY, '--virgl', '--socket-path', $socket); + POSIX::_exit(1); + } + + eval { file_set_contents($pidfile, "$pid2\n") }; + POSIX::_exit(0); + } + + waitpid($pid, 0); + + for (my $waited = 0; $waited < 5; $waited += 0.05) { + last if -S $socket; + usleep(50_000); + } + die "vhost-user-gpu for VM $vmid did not create $socket - see $log\n" if !-S $socket; + + return; +} + +sub stop { + my ($vmid) = @_; + + my $pidfile = pidfile($vmid); + if (my $pid = eval { file_read_firstline($pidfile) }) { + if ($pid =~ m/^(\d+)$/) { + $pid = $1; + kill('TERM', $pid); + + for (my $waited = 0; $waited < 5; $waited += 0.05) { + last if !PVE::ProcFSTools::check_process_running($pid); + usleep(50_000); + } + if (PVE::ProcFSTools::check_process_running($pid)) { + warn "vhost-user-gpu for VM $vmid did not exit, killing it\n"; + kill('KILL', $pid); + } + } + } + + unlink $pidfile; + unlink socket_file($vmid); + + return; +} + +1; diff --git a/src/test/cfg2cmd/kyber-gl.conf b/src/test/cfg2cmd/kyber-gl.conf new file mode 100644 index 0000000..e127bea --- /dev/null +++ b/src/test/cfg2cmd/kyber-gl.conf @@ -0,0 +1,3 @@ +# TEST: Kyber console on the vhost-user GPU +memory: 2048 +vga: kyber-gl diff --git a/src/test/cfg2cmd/kyber-gl.conf.cmd b/src/test/cfg2cmd/kyber-gl.conf.cmd new file mode 100644 index 0000000..d95b7df --- /dev/null +++ b/src/test/cfg2cmd/kyber-gl.conf.cmd @@ -0,0 +1,29 @@ +/usr/bin/kvm +-id 8006 +-name vm8006 +-no-shutdown +-chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' +-mon 'chardev=qmp,mode=control' +-chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' +-mon 'chardev=qmp-event,mode=control' +-pidfile /var/run/qemu-server/8006.pid +-daemonize +-smp '1,sockets=1,cores=1,maxcpus=1' +-nodefaults +-boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' +-chardev 'socket,id=vhost-user-gpu,path=/var/run/qemu-server/8006.vhost-user-gpu.sock' +-display 'dbus,addr=unix:path=/var/run/qemu-server/8006.dbusdisplay,gl=on' +-vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' +-cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep +-m 2048 +-object 'memory-backend-memfd,id=virtiofs-mem,size=2048M,share=on' +-global 'PIIX4_PM.disable_s3=1' +-global 'PIIX4_PM.disable_s4=1' +-device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' +-device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' +-device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' +-device 'usb-tablet,id=tablet,bus=uhci.0,port=1' +-device 'vhost-user-vga,id=vga,chardev=vhost-user-gpu,bus=pci.0,addr=0x2' +-device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' +-iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' +-machine 'memory-backend=virtiofs-mem,type=pc+pve0' \ No newline at end of file -- 2.55.0