public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [PATCH v2 0/16] Support for custom EFI firmware
@ 2026-09-28  5:47 Christian Ludwig
  2026-09-28  5:47 ` [PATCH v2 pve-storage 1/16] plugin: add efi-firmware content type Christian Ludwig
                   ` (15 more replies)
  0 siblings, 16 replies; 17+ messages in thread
From: Christian Ludwig @ 2026-09-28  5:47 UTC (permalink / raw)
  To: pve-devel

Hi,

here is an updated patch series that brings support for custom UEFI
firmware images. You can find the v1 series at [1] for reference.

In Confidential Computing the aim is to not trust the hypervisor, yet it
runs its bundled firmware in each VM. Some VM appliances also ship their
own firmware images. This series allows to bring your own firmware for
OVMF based VMs.

Today the Proxmox-VE API allows to import a custom efidisk0 (EFIVAR)
already. EFI firmware and EFIVAR data have to match. Otherwise, the VM
might not boot anymore. Therefore, with this series you can set a custom
EFI firmware via the API, along with a custom efidisk. But these are two
steps. And there is no safety net. The GUI is missing a way to set a
custom EFIVAR. So this series only allows to set a custom firmware image
for confidential computing VMs that do not need EFIVAR storage. The
defaults do not change.

The changes in detail:

pve-storage:
 - declares the new 'efi-firmware' content type for file based storage
 - provides upload/download API. There is no limit in file names,
   besides the usual safe character class

qemu-server:
 - adds a 'efi-firmware' config key that can point to 'efi-firmware'
   storage content
 - that config key is only allowed to be set for OVMF based VMs
 - adds API plumbing

pve-manager:
 - adds UI plumbing for the 'efi-firmware' storage content type
 - extends the BIOS chooser dialog with an 'OVMF (custom)' option

pve-docs:
 - adds a custom firmware subsection in the BIOS section

Note that you choose the cutom firmware from the BIOS dialog in the GUI,
therefore the 'efi-firmware' config key has the 'VM.Config.Options'
permission. The same as the BIOS option that the dialog hosts already.

Changes from v1:
 - GUI additions
 - Move 'efi-firmware' VM config option permissions from
   'VM.Config.HWType' to 'VM.Config.Options'
 - some cleanups

Tests and feedback welcome.


 - Christian

[1] https://lore.proxmox.com/pve-devel/20260817115919.abQyMsVBJeHupthBjG6HbiJWe2o8RfIy9CmAttembto@z/



^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v2 pve-storage 1/16] plugin: add efi-firmware content type
  2026-09-28  5:47 [PATCH v2 0/16] Support for custom EFI firmware Christian Ludwig
@ 2026-09-28  5:47 ` Christian Ludwig
  2026-09-28  5:47 ` [PATCH v2 pve-storage 2/16] test: get_subdir: cover the " Christian Ludwig
                   ` (14 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Christian Ludwig @ 2026-09-28  5:47 UTC (permalink / raw)
  To: pve-devel

Holds custom OVMF code images in the efi-firmware/ subdirectory of a
storage. File names are restricted to the safe character class.

Signed-off-by: Christian Ludwig <christian_ludwig@genua.de>
---
 src/PVE/Storage/Plugin.pm | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/src/PVE/Storage/Plugin.pm b/src/PVE/Storage/Plugin.pm
index 4f69f9b..f6ca140 100644
--- a/src/PVE/Storage/Plugin.pm
+++ b/src/PVE/Storage/Plugin.pm
@@ -372,7 +372,7 @@ PVE::JSONSchema::register_format('pve-storage-content', \&verify_content);
 sub verify_content {
     my ($ct, $noerr) = @_;
 
-    return $ct if $ct eq 'import';
+    return $ct if $ct eq 'import' || $ct eq 'efi-firmware';
 
     my $valid_content = valid_content_types('dir'); # dir includes all other types
 
@@ -831,6 +831,8 @@ sub parse_volname {
         m!^import/(${PVE::Storage::SAFE_CHAR_WITH_WHITESPACE_CLASS_RE}+$PVE::Storage::IMPORT_EXT_RE_1)$!
     ) {
         return ('import', $1, undef, undef, undef, undef, $2);
+    } elsif ($volname =~ m!^efi-firmware/(${PVE::Storage::SAFE_CHAR_CLASS_RE}+)$!) {
+        return ('efi-firmware', $1, undef, undef, undef, undef, 'raw');
     }
 
     die "unable to parse directory volume name '$volname'\n";
@@ -844,6 +846,7 @@ my $vtype_subdirs = {
     backup => 'dump',
     snippets => 'snippets',
     import => 'import',
+    'efi-firmware' => 'efi-firmware',
 };
 
 sub get_vtype_subdirs {
@@ -1753,6 +1756,10 @@ my $get_subdir_files = sub {
                 m!/(${PVE::Storage::SAFE_CHAR_CLASS_RE}+$PVE::Storage::IMPORT_EXT_RE_1)$!i;
 
             $info = { volid => "$sid:import/$1", format => "$2" };
+        } elsif ($tt eq 'efi-firmware') {
+            next if $fn !~ m!/(${PVE::Storage::SAFE_CHAR_CLASS_RE}+)$!i;
+
+            $info = { volid => "$sid:efi-firmware/$1", format => 'raw' };
         }
 
         $info->{size} = $st->size;
@@ -1789,6 +1796,8 @@ sub list_volumes {
                 $data = $get_subdir_files->($storeid, $path, 'snippets');
             } elsif ($type eq 'import') {
                 $data = $get_subdir_files->($storeid, $path, 'import');
+            } elsif ($type eq 'efi-firmware' && !defined($vmid)) {
+                $data = $get_subdir_files->($storeid, $path, 'efi-firmware');
             }
         }
 
-- 
2.34.1




^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 pve-storage 2/16] test: get_subdir: cover the efi-firmware content type
  2026-09-28  5:47 [PATCH v2 0/16] Support for custom EFI firmware Christian Ludwig
  2026-09-28  5:47 ` [PATCH v2 pve-storage 1/16] plugin: add efi-firmware content type Christian Ludwig
@ 2026-09-28  5:47 ` Christian Ludwig
  2026-09-28  5:48 ` [PATCH v2 pve-storage 3/16] plugins: allow the efi-firmware content type on file based storages Christian Ludwig
                   ` (13 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Christian Ludwig @ 2026-09-28  5:47 UTC (permalink / raw)
  To: pve-devel

Check that the vtype maps to the efi-firmware/ subdirectory.

Signed-off-by: Christian Ludwig <christian_ludwig@genua.de>
---
 src/test/get_subdir_test.pm | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/test/get_subdir_test.pm b/src/test/get_subdir_test.pm
index 5fb5445..5be6de8 100644
--- a/src/test/get_subdir_test.pm
+++ b/src/test/get_subdir_test.pm
@@ -19,6 +19,8 @@ my $tests = [
     # failed matches
     [$scfg_with_path, 'none', "unknown vtype 'none'\n"],
     [{}, 'iso', "storage definition has no path\n"],
+    # efi-firmware vtype returns <path>/efi-firmware
+    [$scfg_with_path, 'efi-firmware', "$scfg_with_path->{path}/efi-firmware"],
 ];
 
 # creates additional positive tests
-- 
2.34.1




^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 pve-storage 3/16] plugins: allow the efi-firmware content type on file based storages
  2026-09-28  5:47 [PATCH v2 0/16] Support for custom EFI firmware Christian Ludwig
  2026-09-28  5:47 ` [PATCH v2 pve-storage 1/16] plugin: add efi-firmware content type Christian Ludwig
  2026-09-28  5:47 ` [PATCH v2 pve-storage 2/16] test: get_subdir: cover the " Christian Ludwig
@ 2026-09-28  5:48 ` Christian Ludwig
  2026-09-28  5:48 ` [PATCH v2 pve-storage 4/16] api: status: support efi-firmware in upload and download-url Christian Ludwig
                   ` (12 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Christian Ludwig @ 2026-09-28  5:48 UTC (permalink / raw)
  To: pve-devel

Enable it for dir, NFS, CIFS, BTRFS and CephFS.

Signed-off-by: Christian Ludwig <christian_ludwig@genua.de>
---
 src/PVE/Storage/BTRFSPlugin.pm  |  1 +
 src/PVE/Storage/CIFSPlugin.pm   |  1 +
 src/PVE/Storage/CephFSPlugin.pm | 13 +++++++++++--
 src/PVE/Storage/DirPlugin.pm    |  1 +
 src/PVE/Storage/NFSPlugin.pm    |  1 +
 5 files changed, 15 insertions(+), 2 deletions(-)

diff --git a/src/PVE/Storage/BTRFSPlugin.pm b/src/PVE/Storage/BTRFSPlugin.pm
index fb47aa0..2f999b2 100644
--- a/src/PVE/Storage/BTRFSPlugin.pm
+++ b/src/PVE/Storage/BTRFSPlugin.pm
@@ -41,6 +41,7 @@ sub plugindata {
                 snippets => 1,
                 none => 1,
                 import => 1,
+                'efi-firmware' => 1,
             },
             { images => 1, rootdir => 1 },
         ],
diff --git a/src/PVE/Storage/CIFSPlugin.pm b/src/PVE/Storage/CIFSPlugin.pm
index 54f0f4e..724e5f0 100644
--- a/src/PVE/Storage/CIFSPlugin.pm
+++ b/src/PVE/Storage/CIFSPlugin.pm
@@ -121,6 +121,7 @@ sub plugindata {
                 backup => 1,
                 snippets => 1,
                 import => 1,
+                'efi-firmware' => 1,
             },
             { images => 1 },
         ],
diff --git a/src/PVE/Storage/CephFSPlugin.pm b/src/PVE/Storage/CephFSPlugin.pm
index fbc9711..fe7975e 100644
--- a/src/PVE/Storage/CephFSPlugin.pm
+++ b/src/PVE/Storage/CephFSPlugin.pm
@@ -116,8 +116,17 @@ sub type {
 
 sub plugindata {
     return {
-        content =>
-            [{ vztmpl => 1, iso => 1, backup => 1, snippets => 1, import => 1 }, { backup => 1 }],
+        content => [
+            {
+                vztmpl => 1,
+                iso => 1,
+                backup => 1,
+                snippets => 1,
+                import => 1,
+                'efi-firmware' => 1,
+            },
+            { backup => 1 },
+        ],
         'sensitive-properties' => { keyring => 1 },
     };
 }
diff --git a/src/PVE/Storage/DirPlugin.pm b/src/PVE/Storage/DirPlugin.pm
index 80c4a03..ab7911d 100644
--- a/src/PVE/Storage/DirPlugin.pm
+++ b/src/PVE/Storage/DirPlugin.pm
@@ -34,6 +34,7 @@ sub plugindata {
                 snippets => 1,
                 none => 1,
                 import => 1,
+                'efi-firmware' => 1,
             },
             { images => 1, rootdir => 1 },
         ],
diff --git a/src/PVE/Storage/NFSPlugin.pm b/src/PVE/Storage/NFSPlugin.pm
index 4cc02c9..e8a3661 100644
--- a/src/PVE/Storage/NFSPlugin.pm
+++ b/src/PVE/Storage/NFSPlugin.pm
@@ -62,6 +62,7 @@ sub plugindata {
                 backup => 1,
                 snippets => 1,
                 import => 1,
+                'efi-firmware' => 1,
             },
             { images => 1 },
         ],
-- 
2.34.1




^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 pve-storage 4/16] api: status: support efi-firmware in upload and download-url
  2026-09-28  5:47 [PATCH v2 0/16] Support for custom EFI firmware Christian Ludwig
                   ` (2 preceding siblings ...)
  2026-09-28  5:48 ` [PATCH v2 pve-storage 3/16] plugins: allow the efi-firmware content type on file based storages Christian Ludwig
@ 2026-09-28  5:48 ` Christian Ludwig
  2026-09-28  5:48 ` [PATCH v2 pve-storage 5/16] test: volume access: cover efi-firmware volumes Christian Ludwig
                   ` (11 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Christian Ludwig @ 2026-09-28  5:48 UTC (permalink / raw)
  To: pve-devel

Allow the content type in both endpoints and reject file names outside
the safe character class.

Signed-off-by: Christian Ludwig <christian_ludwig@genua.de>
---
 src/PVE/API2/Storage/Status.pm | 14 ++++++++++++--
 src/PVE/Storage.pm             | 18 ++++++++++++++++--
 2 files changed, 28 insertions(+), 4 deletions(-)

diff --git a/src/PVE/API2/Storage/Status.pm b/src/PVE/API2/Storage/Status.pm
index 741d514..a6fc317 100644
--- a/src/PVE/API2/Storage/Status.pm
+++ b/src/PVE/API2/Storage/Status.pm
@@ -533,7 +533,7 @@ __PACKAGE__->register_method({
                 description => "Content type.",
                 type => 'string',
                 format => 'pve-storage-content',
-                enum => ['iso', 'vztmpl', 'import'],
+                enum => ['iso', 'vztmpl', 'import', 'efi-firmware'],
             },
             filename => {
                 description =>
@@ -618,6 +618,11 @@ __PACKAGE__->register_method({
             }
 
             $path = PVE::Storage::get_import_dir($cfg, $storage);
+        } elsif ($content eq 'efi-firmware') {
+            if ($filename !~ m!${PVE::Storage::SAFE_CHAR_CLASS_RE}+$!) {
+                raise_param_exc({ filename => "invalid file name" });
+            }
+            $path = PVE::Storage::get_efi_firmware_dir($cfg, $storage);
         } else {
             raise_param_exc({ content => "upload content type '$content' not allowed" });
         }
@@ -770,7 +775,7 @@ __PACKAGE__->register_method({
                 description => "Content type.", # TODO: could be optional & detected in most cases
                 type => 'string',
                 format => 'pve-storage-content',
-                enum => ['iso', 'vztmpl', 'import'],
+                enum => ['iso', 'vztmpl', 'import', 'efi-firmware'],
             },
             filename => {
                 description =>
@@ -859,6 +864,11 @@ __PACKAGE__->register_method({
             }
 
             $path = PVE::Storage::get_import_dir($cfg, $storage);
+        } elsif ($content eq 'efi-firmware') {
+            if ($filename !~ m!${PVE::Storage::SAFE_CHAR_CLASS_RE}+$!) {
+                raise_param_exc({ filename => "invalid file name" });
+            }
+            $path = PVE::Storage::get_efi_firmware_dir($cfg, $storage);
         } else {
             raise_param_exc({ content => "upload content-type '$content' is not allowed" });
         }
diff --git a/src/PVE/Storage.pm b/src/PVE/Storage.pm
index 64ea9da..112a828 100755
--- a/src/PVE/Storage.pm
+++ b/src/PVE/Storage.pm
@@ -555,6 +555,15 @@ sub get_iso_dir {
     return $plugin->get_subdir($scfg, 'iso');
 }
 
+sub get_efi_firmware_dir {
+    my ($cfg, $storeid) = @_;
+
+    my $scfg = storage_config($cfg, $storeid);
+    my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
+
+    return $plugin->get_subdir($scfg, 'efi-firmware');
+}
+
 sub get_import_dir {
     my ($cfg, $storeid) = @_;
 
@@ -629,7 +638,12 @@ sub check_volume_access {
 
         return if $rpcenv->check($user, "/storage/$sid", ['Datastore.Allocate'], 1);
 
-        if ($vtype eq 'iso' || $vtype eq 'vztmpl' || $vtype eq 'import') {
+        if (
+            $vtype eq 'iso'
+            || $vtype eq 'vztmpl'
+            || $vtype eq 'import'
+            || $vtype eq 'efi-firmware'
+        ) {
             # require at least read access to storage, (custom) templates/ISOs could be sensitive
             $rpcenv->check_any(
                 $user,
@@ -1297,7 +1311,7 @@ sub template_list {
 sub volume_list {
     my ($cfg, $storeid, $vmid, $content) = @_;
 
-    my @ctypes = qw(rootdir images vztmpl iso backup snippets import);
+    my @ctypes = qw(rootdir images vztmpl iso backup snippets import efi-firmware);
 
     my $cts = $content ? [$content] : [@ctypes];
 
-- 
2.34.1




^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 pve-storage 5/16] test: volume access: cover efi-firmware volumes
  2026-09-28  5:47 [PATCH v2 0/16] Support for custom EFI firmware Christian Ludwig
                   ` (3 preceding siblings ...)
  2026-09-28  5:48 ` [PATCH v2 pve-storage 4/16] api: status: support efi-firmware in upload and download-url Christian Ludwig
@ 2026-09-28  5:48 ` Christian Ludwig
  2026-09-28  5:48 ` [PATCH v2 pve-storage 6/16] test: list volumes: " Christian Ludwig
                   ` (10 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Christian Ludwig @ 2026-09-28  5:48 UTC (permalink / raw)
  To: pve-devel

Check that access is granted based on the content type.

Signed-off-by: Christian Ludwig <christian_ludwig@genua.de>
---
 src/test/run_volume_access_tests.pl | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/src/test/run_volume_access_tests.pl b/src/test/run_volume_access_tests.pl
index 3448708..72331e5 100755
--- a/src/test/run_volume_access_tests.pl
+++ b/src/test/run_volume_access_tests.pl
@@ -15,7 +15,7 @@ use PVE::Storage::Plugin;
 my $storage_cfg = <<'EOF';
 dir: dir
 	path /mnt/pve/dir
-	content vztmpl,snippets,iso,backup,rootdir,images
+	content vztmpl,snippets,iso,backup,rootdir,images,efi-firmware
 EOF
 
 my $user_cfg = <<'EOF';
@@ -103,6 +103,13 @@ my @tests = (
             'iso' => 1,
         },
     },
+    {
+        volid => 'dir:efi-firmware/custom-uefi.fd',
+        denied_users => {},
+        allowed_types => {
+            'efi-firmware' => 1,
+        },
+    },
     {
         volid => 'dir:111/subvol-111-disk-0.subvol',
         denied_users => {
-- 
2.34.1




^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 pve-storage 6/16] test: list volumes: cover efi-firmware volumes
  2026-09-28  5:47 [PATCH v2 0/16] Support for custom EFI firmware Christian Ludwig
                   ` (4 preceding siblings ...)
  2026-09-28  5:48 ` [PATCH v2 pve-storage 5/16] test: volume access: cover efi-firmware volumes Christian Ludwig
@ 2026-09-28  5:48 ` Christian Ludwig
  2026-09-28  5:48 ` [PATCH v2 qemu-server 07/16] config: add the efi-firmware option Christian Ludwig
                   ` (9 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Christian Ludwig @ 2026-09-28  5:48 UTC (permalink / raw)
  To: pve-devel

Check that files in efi-firmware/ are listed as efi-firmware volumes,
regardless of their extension.

Signed-off-by: Christian Ludwig <christian_ludwig@genua.de>
---
 src/test/list_volumes_test.pm | 51 ++++++++++++++++++++++++++++++++++-
 1 file changed, 50 insertions(+), 1 deletion(-)

diff --git a/src/test/list_volumes_test.pm b/src/test/list_volumes_test.pm
index 0876902..a1e98d1 100644
--- a/src/test/list_volumes_test.pm
+++ b/src/test/list_volumes_test.pm
@@ -72,6 +72,7 @@ my $scfg = {
         'images' => 1,
         'snippets' => 1,
         'backup' => 1,
+        'efi-firmware' => 1,
     },
 };
 
@@ -462,6 +463,54 @@ my @tests = (
         ],
         expected => [], # returns empty list
     },
+    {
+        description => 'VMID: none, efi-firmware .fd files listed',
+        vmid => undef,
+        files => [
+            "$storage_dir/efi-firmware/custom.fd",
+            "$storage_dir/efi-firmware/vendor-bios-v2.fd",
+        ],
+        expected => [
+            {
+                'content' => 'efi-firmware',
+                'ctime' => DEFAULT_CTIME,
+                'format' => 'raw',
+                'size' => DEFAULT_SIZE,
+                'volid' => 'local:efi-firmware/custom.fd',
+            },
+            {
+                'content' => 'efi-firmware',
+                'ctime' => DEFAULT_CTIME,
+                'format' => 'raw',
+                'size' => DEFAULT_SIZE,
+                'volid' => 'local:efi-firmware/vendor-bios-v2.fd',
+            },
+        ],
+    },
+    {
+        description => 'VMID: none, all efi-firmware files listed regardless of extension',
+        vmid => undef,
+        files => [
+            "$storage_dir/efi-firmware/custom.fd",
+            "$storage_dir/efi-firmware/custom.iso",
+        ],
+        expected => [
+            {
+                'content' => 'efi-firmware',
+                'ctime' => DEFAULT_CTIME,
+                'format' => 'raw',
+                'size' => DEFAULT_SIZE,
+                'volid' => 'local:efi-firmware/custom.fd',
+            },
+            {
+                'content' => 'efi-firmware',
+                'ctime' => DEFAULT_CTIME,
+                'format' => 'raw',
+                'size' => DEFAULT_SIZE,
+                'volid' => 'local:efi-firmware/custom.iso',
+            },
+        ],
+    },
 );
 
 # provide static vmlist for tests
@@ -520,7 +569,7 @@ plan tests => $plan + 1;
 
 {
     my $sid = 'local';
-    my $types = ['rootdir', 'images', 'vztmpl', 'iso', 'backup', 'snippets'];
+    my $types = ['rootdir', 'images', 'vztmpl', 'iso', 'backup', 'snippets', 'efi-firmware'];
     my @suffixes = ('qcow2', 'raw', 'vmdk', 'vhdx');
 
     # run through test cases
-- 
2.34.1




^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 qemu-server 07/16] config: add the efi-firmware option
  2026-09-28  5:47 [PATCH v2 0/16] Support for custom EFI firmware Christian Ludwig
                   ` (5 preceding siblings ...)
  2026-09-28  5:48 ` [PATCH v2 pve-storage 6/16] test: list volumes: " Christian Ludwig
@ 2026-09-28  5:48 ` Christian Ludwig
  2026-09-28  5:48 ` [PATCH v2 qemu-server 08/16] api: allow setting " Christian Ludwig
                   ` (8 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Christian Ludwig @ 2026-09-28  5:48 UTC (permalink / raw)
  To: pve-devel

Add the 'efi-firmware' config key, pointing to a volume of the
efi-firmware content type. A custom firmware image does not make sense
for a legacy BIOS, so require bios=ovmf when generating the command
line.

Include the volume when activating a VM's volumes. Firmware images are
content and not owned by the VM, like ISO images, so refuse migration
unless they are on a shared storage.

Signed-off-by: Christian Ludwig <christian_ludwig@genua.de>
---
 src/PVE/QemuConfig.pm  |  2 +-
 src/PVE/QemuMigrate.pm |  2 ++
 src/PVE/QemuServer.pm  | 18 ++++++++++++++++--
 3 files changed, 19 insertions(+), 3 deletions(-)

diff --git a/src/PVE/QemuConfig.pm b/src/PVE/QemuConfig.pm
index 26f0fda2..1d73048a 100644
--- a/src/PVE/QemuConfig.pm
+++ b/src/PVE/QemuConfig.pm
@@ -105,7 +105,7 @@ sub parse_volume {
     my ($class, $key, $volume_string, $noerr) = @_;
 
     my $volume;
-    if ($key eq 'vmstate') {
+    if ($key eq 'vmstate' || $key eq 'efi-firmware') {
         eval { PVE::JSONSchema::check_format('pve-volume-id', $volume_string) };
         if (my $err = $@) {
             return if $noerr;
diff --git a/src/PVE/QemuMigrate.pm b/src/PVE/QemuMigrate.pm
index 8da6f15d..1ccdffab 100644
--- a/src/PVE/QemuMigrate.pm
+++ b/src/PVE/QemuMigrate.pm
@@ -440,6 +440,8 @@ sub scan_local_volumes {
             $self->target_storage_check_available($storecfg, $targetsid, $volid);
             return if $scfg->{shared} && !$self->{opts}->{remote};
 
+            die "local efi-firmware image\n" if $attr->{is_firmware};
+
             $local_volumes->{$volid}->{ref} = 'pending' if $attr->{referenced_in_pending};
             $local_volumes->{$volid}->{ref} = 'snapshot' if $attr->{referenced_in_snapshot};
             $local_volumes->{$volid}->{ref} = 'unused' if $attr->{is_unused};
diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm
index 63d8c135..53b35b22 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -663,6 +663,14 @@ EODESCR
         description => "Select BIOS implementation.",
         default => 'seabios',
     },
+    'efi-firmware' => {
+        optional => 1,
+        type => 'string',
+        format => 'pve-volume-id',
+        description => "Custom EFI firmware code image (pflash0). Must be a volid "
+            . "referencing a 'efi-firmware' content type volume (e.g. "
+            . "'local:efi-firmware/custom.fd'). Requires bios=ovmf.",
+    },
     vmgenid => {
         type => 'string',
         pattern => '(?:[a-fA-F0-9]{8}(?:-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}|[01])',
@@ -3255,6 +3263,9 @@ sub config_to_command {
         push @$cmd, '-smbios', "type=1" . $smbios_string;
     }
 
+    die "efi-firmware requires bios=ovmf\n"
+        if $conf->{'efi-firmware'} && (!$conf->{bios} || $conf->{bios} ne 'ovmf');
+
     if ($conf->{bios} && $conf->{bios} eq 'ovmf') {
         die "OVMF (UEFI) BIOS is not supported on 32-bit CPU types\n"
             if !$forcecpu && get_cpu_bitness($conf->{cpu}, $arch) == 32;
@@ -4568,11 +4579,14 @@ sub foreach_volid {
         $volhash->{$volid}->{is_tpmstate} //= 0;
         $volhash->{$volid}->{is_tpmstate} = 1 if $key eq 'tpmstate0';
 
+        $volhash->{$volid}->{is_firmware} //= 0;
+        $volhash->{$volid}->{is_firmware} = 1 if $key eq 'efi-firmware';
+
         $volhash->{$volid}->{drivename} = $key if is_valid_drivename($key);
     };
 
     my $include_opts = {
-        extra_keys => ['vmstate'],
+        extra_keys => ['vmstate', 'efi-firmware'],
         include_unused => 1,
     };
 
@@ -6123,7 +6137,7 @@ sub get_current_vm_volumes {
 
     PVE::QemuConfig->foreach_volume_full(
         $conf,
-        { extra_keys => ['vmstate'] },
+        { extra_keys => ['vmstate', 'efi-firmware'] },
         sub {
             my ($ds, $drive) = @_;
 
-- 
2.34.1




^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 qemu-server 08/16] api: allow setting the efi-firmware option
  2026-09-28  5:47 [PATCH v2 0/16] Support for custom EFI firmware Christian Ludwig
                   ` (6 preceding siblings ...)
  2026-09-28  5:48 ` [PATCH v2 qemu-server 07/16] config: add the efi-firmware option Christian Ludwig
@ 2026-09-28  5:48 ` Christian Ludwig
  2026-09-28  5:48 ` [PATCH v2 qemu-server 09/16] ovmf: use a custom firmware image if configured Christian Ludwig
                   ` (7 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Christian Ludwig @ 2026-09-28  5:48 UTC (permalink / raw)
  To: pve-devel

Add the option to the POST/PUT {vmid}/config endpoints. It needs
VM.Config.Options permission, like the 'bios' option it modifies, and is
rejected without bios=ovmf.

Deleting it does not trigger volume cleanup, firmware images are shared.

Signed-off-by: Christian Ludwig <christian_ludwig@genua.de>
---
 src/PVE/API2/Qemu.pm | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/src/PVE/API2/Qemu.pm b/src/PVE/API2/Qemu.pm
index 71247eec..8b12bb53 100644
--- a/src/PVE/API2/Qemu.pm
+++ b/src/PVE/API2/Qemu.pm
@@ -277,6 +277,10 @@ my $check_storage_access = sub {
         "/storage/$settings->{vmstatestorage}",
         ['Datastore.AllocateSpace'],
     ) if defined($settings->{vmstatestorage});
+
+    PVE::Storage::check_volume_access(
+        $rpcenv, $authuser, $storecfg, $vmid, $settings->{'efi-firmware'}, 'efi-firmware',
+    ) if defined($settings->{'efi-firmware'});
 };
 
 my $check_storage_access_clone = sub {
@@ -825,6 +829,7 @@ my $generaloptions = {
     'autostart' => 1,
     'bios' => 1,
     'description' => 1,
+    'efi-firmware' => 1,
     'keyboard' => 1,
     'localtime' => 1,
     'migrate_downtime' => 1,
@@ -1366,6 +1371,9 @@ __PACKAGE__->register_method({
 
             $check_drive_param->($param, $storecfg);
 
+            raise_param_exc({ 'efi-firmware' => "requires bios=ovmf" })
+                if $param->{'efi-firmware'} && ($param->{bios} // '') ne 'ovmf';
+
             PVE::QemuServer::Network::add_random_macs($param);
         }
 
@@ -2527,6 +2535,13 @@ my $update_vm_api = sub {
                     print "automatic pinning of machine version failed - $@" if $@;
                 }
                 $conf->{pending}->{$opt} = $param->{$opt};
+            } elsif ($opt eq 'efi-firmware') {
+                PVE::Storage::check_volume_access(
+                    $rpcenv, $authuser, $storecfg, $vmid, $param->{$opt}, 'efi-firmware',
+                );
+                my $bios = $param->{bios} // $conf->{pending}->{bios} // $conf->{bios} // '';
+                raise_param_exc({ $opt => "requires bios=ovmf" }) if $bios ne 'ovmf';
+                $conf->{pending}->{$opt} = $param->{$opt};
             } elsif ($opt eq 'cipassword') {
                 if (!PVE::QemuServer::Helpers::windows_version($conf->{ostype})) {
                     # Same logic as in cloud-init (but with the regex fixed...)
-- 
2.34.1




^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 qemu-server 09/16] ovmf: use a custom firmware image if configured
  2026-09-28  5:47 [PATCH v2 0/16] Support for custom EFI firmware Christian Ludwig
                   ` (7 preceding siblings ...)
  2026-09-28  5:48 ` [PATCH v2 qemu-server 08/16] api: allow setting " Christian Ludwig
@ 2026-09-28  5:48 ` Christian Ludwig
  2026-09-28  5:48 ` [PATCH v2 qemu-server 10/16] test: efi-firmware key in VM config Christian Ludwig
                   ` (6 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Christian Ludwig @ 2026-09-28  5:48 UTC (permalink / raw)
  To: pve-devel

Replace the system OVMF code image with the configured one, for the
pflash, blockdev and '-bios' variants. Die if the volume does not exist.

Signed-off-by: Christian Ludwig <christian_ludwig@genua.de>
---
 src/PVE/QemuServer/OVMF.pm | 20 +++++++++++++++++++-
 1 file changed, 19 insertions(+), 1 deletion(-)

diff --git a/src/PVE/QemuServer/OVMF.pm b/src/PVE/QemuServer/OVMF.pm
index 67665c7c..fe301353 100644
--- a/src/PVE/QemuServer/OVMF.pm
+++ b/src/PVE/QemuServer/OVMF.pm
@@ -123,6 +123,9 @@ my sub print_ovmf_drive_commandlines {
         if $cvm_type && $cvm_type eq 'tdx';
 
     my ($ovmf_code, $ovmf_vars) = get_ovmf_files($arch, $d, $q35, $cvm_type);
+    if ($conf->{'efi-firmware'}) {
+        $ovmf_code = PVE::Storage::path($storecfg, $conf->{'efi-firmware'});
+    }
     my $ovmf_vars_size = file_get_size($ovmf_vars);
 
     my $var_drive_str = "if=pflash,unit=1,id=drive-efidisk0";
@@ -219,6 +222,9 @@ my sub generate_ovmf_blockdev {
         if $cvm_type && $cvm_type eq 'snp';
 
     my ($ovmf_code, $ovmf_vars) = get_ovmf_files($arch, $drive, $q35, $cvm_type);
+    if ($conf->{'efi-firmware'}) {
+        $ovmf_code = PVE::Storage::path($storecfg, $conf->{'efi-firmware'});
+    }
 
     my $ovmf_code_blockdev = {
         driver => 'raw',
@@ -268,6 +274,12 @@ sub print_ovmf_commandline {
 
     my $cvm_type = $hw_info->{'cvm-type'};
 
+    if ($conf->{'efi-firmware'}) {
+        my $fw_path = PVE::Storage::path($storecfg, $conf->{'efi-firmware'});
+        die "efi-firmware volume '$conf->{'efi-firmware'}' not found at '$fw_path'\n"
+            if !file_exists($fw_path);
+    }
+
     my $cmd = [];
     my $machine_flags = [];
 
@@ -277,7 +289,13 @@ sub print_ovmf_commandline {
                 "EFI disks are not supported with Confidential Virtual Machines and will be ignored"
             );
         }
-        push $cmd->@*, '-bios', get_ovmf_files($hw_info->{arch}, undef, undef, $cvm_type);
+        my $bios_path;
+        if ($conf->{'efi-firmware'}) {
+            $bios_path = PVE::Storage::path($storecfg, $conf->{'efi-firmware'});
+        } else {
+            ($bios_path) = get_ovmf_files($hw_info->{arch}, undef, undef, $cvm_type);
+        }
+        push $cmd->@*, '-bios', $bios_path;
     } else {
         if ($version_guard->(10, 0, 0)) { # for the switch to -blockdev
             my ($code_blockdev, $vars_blockdev, $throttle_group) =
-- 
2.34.1




^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 qemu-server 10/16] test: efi-firmware key in VM config
  2026-09-28  5:47 [PATCH v2 0/16] Support for custom EFI firmware Christian Ludwig
                   ` (8 preceding siblings ...)
  2026-09-28  5:48 ` [PATCH v2 qemu-server 09/16] ovmf: use a custom firmware image if configured Christian Ludwig
@ 2026-09-28  5:48 ` Christian Ludwig
  2026-09-28  5:48 ` [PATCH v2 qemu-server 11/16] test: efi-firmware volumes replication Christian Ludwig
                   ` (5 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Christian Ludwig @ 2026-09-28  5:48 UTC (permalink / raw)
  To: pve-devel

Add test cases for different efi-firmware settings in VM config. Also
enhance the PVE::QemuServer::OVMF test mock to handle nonexistent files.

Signed-off-by: Christian Ludwig <christian_ludwig@genua.de>
---
 .../cfg2cmd/efi-custom-firmware-legacy.conf   |  6 ++++
 .../efi-custom-firmware-legacy.conf.cmd       | 27 ++++++++++++++++
 .../efi-custom-firmware-not-found.conf        |  6 ++++
 src/test/cfg2cmd/efi-custom-firmware-old.conf |  6 ++++
 .../cfg2cmd/efi-custom-firmware-old.conf.cmd  | 27 ++++++++++++++++
 .../cfg2cmd/efi-custom-firmware-seabios.conf  |  4 +++
 src/test/cfg2cmd/efi-custom-firmware-sev.conf |  7 +++++
 .../cfg2cmd/efi-custom-firmware-sev.conf.cmd  | 31 +++++++++++++++++++
 src/test/cfg2cmd/efi-custom-firmware-snp.conf |  6 ++++
 .../cfg2cmd/efi-custom-firmware-snp.conf.cmd  | 29 +++++++++++++++++
 src/test/cfg2cmd/efi-custom-firmware-tdx.conf |  6 ++++
 .../cfg2cmd/efi-custom-firmware-tdx.conf.cmd  | 29 +++++++++++++++++
 src/test/cfg2cmd/efi-custom-firmware.conf     |  5 +++
 src/test/cfg2cmd/efi-custom-firmware.conf.cmd | 30 ++++++++++++++++++
 src/test/parse-config-input/efi-firmware.conf | 13 ++++++++
 .../regular-vm-efifirmware.conf               | 17 ++++++++++
 src/test/run_config2command_tests.pl          |  2 ++
 src/test/run_parse_config_tests.pl            |  2 +-
 18 files changed, 252 insertions(+), 1 deletion(-)
 create mode 100644 src/test/cfg2cmd/efi-custom-firmware-legacy.conf
 create mode 100644 src/test/cfg2cmd/efi-custom-firmware-legacy.conf.cmd
 create mode 100644 src/test/cfg2cmd/efi-custom-firmware-not-found.conf
 create mode 100644 src/test/cfg2cmd/efi-custom-firmware-old.conf
 create mode 100644 src/test/cfg2cmd/efi-custom-firmware-old.conf.cmd
 create mode 100644 src/test/cfg2cmd/efi-custom-firmware-seabios.conf
 create mode 100644 src/test/cfg2cmd/efi-custom-firmware-sev.conf
 create mode 100644 src/test/cfg2cmd/efi-custom-firmware-sev.conf.cmd
 create mode 100644 src/test/cfg2cmd/efi-custom-firmware-snp.conf
 create mode 100644 src/test/cfg2cmd/efi-custom-firmware-snp.conf.cmd
 create mode 100644 src/test/cfg2cmd/efi-custom-firmware-tdx.conf
 create mode 100644 src/test/cfg2cmd/efi-custom-firmware-tdx.conf.cmd
 create mode 100644 src/test/cfg2cmd/efi-custom-firmware.conf
 create mode 100644 src/test/cfg2cmd/efi-custom-firmware.conf.cmd
 create mode 100644 src/test/parse-config-input/efi-firmware.conf
 create mode 100644 src/test/parse-config-input/regular-vm-efifirmware.conf

diff --git a/src/test/cfg2cmd/efi-custom-firmware-legacy.conf b/src/test/cfg2cmd/efi-custom-firmware-legacy.conf
new file mode 100644
index 00000000..27d2a59d
--- /dev/null
+++ b/src/test/cfg2cmd/efi-custom-firmware-legacy.conf
@@ -0,0 +1,6 @@
+# TEST: Custom efi-firmware replaces system OVMF_CODE path in legacy -drive command line
+smbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465
+bios: ovmf
+machine: pc-i440fx-4.1+pve0
+efidisk0: local:100/vm-100-disk-0.raw
+efi-firmware: local:efi-firmware/custom.fd
diff --git a/src/test/cfg2cmd/efi-custom-firmware-legacy.conf.cmd b/src/test/cfg2cmd/efi-custom-firmware-legacy.conf.cmd
new file mode 100644
index 00000000..e7e870a5
--- /dev/null
+++ b/src/test/cfg2cmd/efi-custom-firmware-legacy.conf.cmd
@@ -0,0 +1,27 @@
+/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 \
+  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \
+  -drive 'if=pflash,unit=0,format=raw,readonly=on,file=/var/lib/vz/efi-firmware/custom.fd' \
+  -drive 'if=pflash,unit=1,id=drive-efidisk0,format=raw,file=/var/lib/vz/images/100/vm-100-disk-0.raw' \
+  -smp '1,sockets=1,cores=1,maxcpus=1' \
+  -nodefaults \
+  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \
+  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \
+  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \
+  -m 512 \
+  -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 'VGA,id=vga,bus=pci.0,addr=0x2' \
+  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3' \
+  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \
+  -machine 'type=pc-i440fx-4.1+pve0'
diff --git a/src/test/cfg2cmd/efi-custom-firmware-not-found.conf b/src/test/cfg2cmd/efi-custom-firmware-not-found.conf
new file mode 100644
index 00000000..fcc8774a
--- /dev/null
+++ b/src/test/cfg2cmd/efi-custom-firmware-not-found.conf
@@ -0,0 +1,6 @@
+# TEST: efi-firmware pointing to a nonexistent file causes die
+# EXPECT_ERROR: efi-firmware volume 'local:efi-firmware/nonexistent.fd' not found at '/var/lib/vz/efi-firmware/nonexistent.fd'
+smbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465
+bios: ovmf
+efidisk0: local:100/vm-100-disk-0.raw
+efi-firmware: local:efi-firmware/nonexistent.fd
diff --git a/src/test/cfg2cmd/efi-custom-firmware-old.conf b/src/test/cfg2cmd/efi-custom-firmware-old.conf
new file mode 100644
index 00000000..5ac13d9c
--- /dev/null
+++ b/src/test/cfg2cmd/efi-custom-firmware-old.conf
@@ -0,0 +1,6 @@
+# TEST: Custom efi-firmware replaces system OVMF_CODE path in legacy -drive command line (old naming)
+smbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465
+bios: ovmf
+machine: pc-i440fx-4.1+pve0
+efidisk0: local:100/vm-100-disk-0.raw
+efi-firmware: local:efi-firmware/custom.fd
diff --git a/src/test/cfg2cmd/efi-custom-firmware-old.conf.cmd b/src/test/cfg2cmd/efi-custom-firmware-old.conf.cmd
new file mode 100644
index 00000000..e7e870a5
--- /dev/null
+++ b/src/test/cfg2cmd/efi-custom-firmware-old.conf.cmd
@@ -0,0 +1,27 @@
+/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 \
+  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \
+  -drive 'if=pflash,unit=0,format=raw,readonly=on,file=/var/lib/vz/efi-firmware/custom.fd' \
+  -drive 'if=pflash,unit=1,id=drive-efidisk0,format=raw,file=/var/lib/vz/images/100/vm-100-disk-0.raw' \
+  -smp '1,sockets=1,cores=1,maxcpus=1' \
+  -nodefaults \
+  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \
+  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \
+  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \
+  -m 512 \
+  -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 'VGA,id=vga,bus=pci.0,addr=0x2' \
+  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3' \
+  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \
+  -machine 'type=pc-i440fx-4.1+pve0'
diff --git a/src/test/cfg2cmd/efi-custom-firmware-seabios.conf b/src/test/cfg2cmd/efi-custom-firmware-seabios.conf
new file mode 100644
index 00000000..c9d68c2b
--- /dev/null
+++ b/src/test/cfg2cmd/efi-custom-firmware-seabios.conf
@@ -0,0 +1,4 @@
+# TEST: efi-firmware without bios=ovmf causes die
+# EXPECT_ERROR: efi-firmware requires bios=ovmf
+smbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465
+efi-firmware: local:efi-firmware/custom.fd
diff --git a/src/test/cfg2cmd/efi-custom-firmware-sev.conf b/src/test/cfg2cmd/efi-custom-firmware-sev.conf
new file mode 100644
index 00000000..cb0d6a9e
--- /dev/null
+++ b/src/test/cfg2cmd/efi-custom-firmware-sev.conf
@@ -0,0 +1,7 @@
+# TEST: Custom efi-firmware replaces system OVMF_SEV_CODE path in SEV CVM blockdev command line
+# HW_CAPABILITIES: amd-turin-9005
+smbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465
+bios: ovmf
+efidisk0: local:100/vm-100-disk-0.raw,efitype=4m,pre-enrolled-keys=1,size=528K
+amd-sev: type=std
+efi-firmware: local:efi-firmware/custom.fd
diff --git a/src/test/cfg2cmd/efi-custom-firmware-sev.conf.cmd b/src/test/cfg2cmd/efi-custom-firmware-sev.conf.cmd
new file mode 100644
index 00000000..4b3a67dc
--- /dev/null
+++ b/src/test/cfg2cmd/efi-custom-firmware-sev.conf.cmd
@@ -0,0 +1,31 @@
+/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 \
+  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \
+  -object '{"id":"throttle-drive-efidisk0","limits":{},"qom-type":"throttle-group"}' \
+  -blockdev '{"driver":"raw","file":{"driver":"file","filename":"/var/lib/vz/efi-firmware/custom.fd"},"node-name":"pflash0","read-only":true}' \
+  -blockdev '{"detect-zeroes":"on","discard":"ignore","driver":"throttle","file":{"cache":{"direct":false,"no-flush":false},"detect-zeroes":"on","discard":"ignore","driver":"raw","file":{"aio":"io_uring","cache":{"direct":false,"no-flush":false},"detect-zeroes":"on","discard":"ignore","driver":"file","filename":"/var/lib/vz/images/100/vm-100-disk-0.raw","node-name":"e1175f2a490414e7c53337589fde17a","read-only":false},"node-name":"f1175f2a490414e7c53337589fde17a","read-only":false,"size":540672},"node-name":"drive-efidisk0","read-only":false,"throttle-group":"throttle-drive-efidisk0"}' \
+  -smp '1,sockets=1,cores=1,maxcpus=1' \
+  -nodefaults \
+  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \
+  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \
+  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \
+  -m 512 \
+  -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 'VGA,id=vga,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' \
+  -object 'sev-guest,id=sev0,cbitpos=51,reduced-phys-bits=6,policy=0x8' \
+  -machine 'pflash0=pflash0,pflash1=drive-efidisk0,type=pc+pve0,confidential-guest-support=sev0'
diff --git a/src/test/cfg2cmd/efi-custom-firmware-snp.conf b/src/test/cfg2cmd/efi-custom-firmware-snp.conf
new file mode 100644
index 00000000..07a80a29
--- /dev/null
+++ b/src/test/cfg2cmd/efi-custom-firmware-snp.conf
@@ -0,0 +1,6 @@
+# TEST: Custom efi-firmware replaces default SNP firmware in -bios argument
+# HW_CAPABILITIES: amd-turin-9005
+smbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465
+bios: ovmf
+amd-sev: type=snp
+efi-firmware: local:efi-firmware/custom.fd
diff --git a/src/test/cfg2cmd/efi-custom-firmware-snp.conf.cmd b/src/test/cfg2cmd/efi-custom-firmware-snp.conf.cmd
new file mode 100644
index 00000000..678d1356
--- /dev/null
+++ b/src/test/cfg2cmd/efi-custom-firmware-snp.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 \
+  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \
+  -bios /var/lib/vz/efi-firmware/custom.fd \
+  -smp '1,sockets=1,cores=1,maxcpus=1' \
+  -nodefaults \
+  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \
+  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \
+  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \
+  -m 512 \
+  -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 'VGA,id=vga,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' \
+  -object 'sev-snp-guest,id=sev0,cbitpos=51,reduced-phys-bits=6,policy=0xb0000' \
+  -machine 'type=pc+pve0,confidential-guest-support=sev0'
diff --git a/src/test/cfg2cmd/efi-custom-firmware-tdx.conf b/src/test/cfg2cmd/efi-custom-firmware-tdx.conf
new file mode 100644
index 00000000..8219dad3
--- /dev/null
+++ b/src/test/cfg2cmd/efi-custom-firmware-tdx.conf
@@ -0,0 +1,6 @@
+# TEST: Custom efi-firmware replaces default TDX firmware in -bios argument
+# HW_CAPABILITIES: {"intel-tdx":{"tdx-support":true},"amd-sev":{"cbitpos":0,"reduced-phys-bits":0,"sev-support":false,"sev-support-es":false,"sev-support-snp":false}}
+smbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465
+bios: ovmf
+intel-tdx: tdx,attestation=0
+efi-firmware: local:efi-firmware/custom.fd
diff --git a/src/test/cfg2cmd/efi-custom-firmware-tdx.conf.cmd b/src/test/cfg2cmd/efi-custom-firmware-tdx.conf.cmd
new file mode 100644
index 00000000..7902aaef
--- /dev/null
+++ b/src/test/cfg2cmd/efi-custom-firmware-tdx.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 \
+  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \
+  -bios /var/lib/vz/efi-firmware/custom.fd \
+  -smp '1,sockets=1,cores=1,maxcpus=1' \
+  -nodefaults \
+  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \
+  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \
+  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \
+  -m 512 \
+  -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 'VGA,id=vga,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' \
+  -object '{"id":"tdx0","qom-type":"tdx-guest"}' \
+  -machine 'type=pc+pve0,confidential-guest-support=tdx0,kernel_irqchip=split'
diff --git a/src/test/cfg2cmd/efi-custom-firmware.conf b/src/test/cfg2cmd/efi-custom-firmware.conf
new file mode 100644
index 00000000..25ca7bc4
--- /dev/null
+++ b/src/test/cfg2cmd/efi-custom-firmware.conf
@@ -0,0 +1,5 @@
+# TEST: Custom efi-firmware replaces system OVMF_CODE path in blockdev command line
+smbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465
+bios: ovmf
+efidisk0: local:100/vm-100-disk-0.raw
+efi-firmware: local:efi-firmware/custom.fd
diff --git a/src/test/cfg2cmd/efi-custom-firmware.conf.cmd b/src/test/cfg2cmd/efi-custom-firmware.conf.cmd
new file mode 100644
index 00000000..526d99c3
--- /dev/null
+++ b/src/test/cfg2cmd/efi-custom-firmware.conf.cmd
@@ -0,0 +1,30 @@
+/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 \
+  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \
+  -object '{"id":"throttle-drive-efidisk0","limits":{},"qom-type":"throttle-group"}' \
+  -blockdev '{"driver":"raw","file":{"driver":"file","filename":"/var/lib/vz/efi-firmware/custom.fd"},"node-name":"pflash0","read-only":true}' \
+  -blockdev '{"detect-zeroes":"on","discard":"ignore","driver":"throttle","file":{"cache":{"direct":false,"no-flush":false},"detect-zeroes":"on","discard":"ignore","driver":"raw","file":{"aio":"io_uring","cache":{"direct":false,"no-flush":false},"detect-zeroes":"on","discard":"ignore","driver":"file","filename":"/var/lib/vz/images/100/vm-100-disk-0.raw","node-name":"e1175f2a490414e7c53337589fde17a","read-only":false},"node-name":"f1175f2a490414e7c53337589fde17a","read-only":false,"size":131072},"node-name":"drive-efidisk0","read-only":false,"throttle-group":"throttle-drive-efidisk0"}' \
+  -smp '1,sockets=1,cores=1,maxcpus=1' \
+  -nodefaults \
+  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \
+  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \
+  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \
+  -m 512 \
+  -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 'VGA,id=vga,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 'pflash0=pflash0,pflash1=drive-efidisk0,type=pc+pve0'
diff --git a/src/test/parse-config-input/efi-firmware.conf b/src/test/parse-config-input/efi-firmware.conf
new file mode 100644
index 00000000..c39570d9
--- /dev/null
+++ b/src/test/parse-config-input/efi-firmware.conf
@@ -0,0 +1,13 @@
+bios: ovmf
+boot: order=scsi0
+cores: 2
+efi-firmware: local:efi-firmware/custom.fd
+efidisk0: local-lvm:vm-100-disk-0,efitype=4m,pre-enrolled-keys=1
+memory: 1024
+name: test-vm
+net0: virtio=BC:24:11:2C:69:EC,bridge=vmbr0
+numa: 0
+ostype: l26
+scsi0: local-lvm:vm-100-disk-1,size=8G
+scsihw: virtio-scsi-pci
+sockets: 1
diff --git a/src/test/parse-config-input/regular-vm-efifirmware.conf b/src/test/parse-config-input/regular-vm-efifirmware.conf
new file mode 100644
index 00000000..78a03e71
--- /dev/null
+++ b/src/test/parse-config-input/regular-vm-efifirmware.conf
@@ -0,0 +1,17 @@
+# regular VM with an EFI disk and custom firmware
+bios: ovmf
+boot: order=scsi0;ide2;net0
+cores: 1
+efi-firmware: mydir:efi-firmware/custom.fd
+efidisk0: mydir:139/vm-139-disk-0.qcow2,size=128K
+ide2: local:iso/debian-10.6.0-amd64-netinst.iso,media=cdrom
+memory: 2048
+name: eficloneclone
+net0: virtio=7A:6C:A5:8B:11:93,bridge=vmbr0,firewall=1
+numa: 0
+ostype: l26
+scsi0: rbdkvm:vm-139-disk-1,size=4G
+scsihw: virtio-scsi-pci
+smbios1: uuid=21a7e7bc-3cd2-4232-a009-a41f4ee992ae
+sockets: 1
+vmgenid: 0
diff --git a/src/test/run_config2command_tests.pl b/src/test/run_config2command_tests.pl
index 47250c67..a2e95add 100755
--- a/src/test/run_config2command_tests.pl
+++ b/src/test/run_config2command_tests.pl
@@ -31,6 +31,7 @@ my $base_env = {
                 content => {
                     images => 1,
                     iso => 1,
+                    'efi-firmware' => 1,
                 },
                 path => '/var/lib/vz',
                 type => 'dir',
@@ -285,6 +286,7 @@ my $qemu_server_ovmf_module = Test::MockModule->new("PVE::QemuServer::OVMF");
 $qemu_server_ovmf_module->mock(
     file_exists => sub {
         my ($path) = @_;
+        return 0 if $path =~ m/nonexistent/;
         return 1;
     },
     file_get_size => sub {
diff --git a/src/test/run_parse_config_tests.pl b/src/test/run_parse_config_tests.pl
index 62e36ee0..b2a02374 100755
--- a/src/test/run_parse_config_tests.pl
+++ b/src/test/run_parse_config_tests.pl
@@ -26,7 +26,7 @@ my $OUTPUT_DIR = './parse-config-output';
 my $EXPECTED_DIR = './parse-config-expected';
 
 # NOTE update when you add/remove tests
-plan tests => 2 * 10;
+plan tests => 2 * 12;
 
 sub run_tests {
     my ($strict) = @_;
-- 
2.34.1




^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 qemu-server 11/16] test: efi-firmware volumes replication
  2026-09-28  5:47 [PATCH v2 0/16] Support for custom EFI firmware Christian Ludwig
                   ` (9 preceding siblings ...)
  2026-09-28  5:48 ` [PATCH v2 qemu-server 10/16] test: efi-firmware key in VM config Christian Ludwig
@ 2026-09-28  5:48 ` Christian Ludwig
  2026-09-28  5:48 ` [PATCH v2 pve-manager 12/16] ui: storage: add efi-firmware content type support Christian Ludwig
                   ` (4 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Christian Ludwig @ 2026-09-28  5:48 UTC (permalink / raw)
  To: pve-devel

Test that efi-firmware volumes are excluded from volume replication.

Signed-off-by: Christian Ludwig <christian_ludwig@genua.de>
---
 src/test/test_get_replicatable_volumes.pl | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/src/test/test_get_replicatable_volumes.pl b/src/test/test_get_replicatable_volumes.pl
index 6a3d0338..7ecda208 100755
--- a/src/test/test_get_replicatable_volumes.pl
+++ b/src/test/test_get_replicatable_volumes.pl
@@ -22,6 +22,7 @@ my $storecfg = {
                 'backup' => 1,
                 'images' => 1,
                 'rootdir' => 1,
+                'efi-firmware' => 1,
             },
             path => "/var/lib/vz",
         },
@@ -159,5 +160,15 @@ $conf = PVE::QemuServer::parse_vm_config("/qemu-server/$vmid.conf", $rawconf);
 eval { $volumes = PVE::QemuConfig->get_replicatable_volumes($storecfg, $vmid, $conf, 0, 0); };
 is($@, "missing replicate feature on volume 'local:900/vm-900-disk-2.raw'\n", $test_name);
 
+$test_name = "efi-firmware is shared, not owned by VM";
+$rawconf = <<__EOD__;
+bios: ovmf
+efi-firmware: local:efi-firmware/custom.fd
+__EOD__
+
+$conf = PVE::QemuServer::parse_vm_config("/qemu-server/$vmid.conf", $rawconf);
+$volumes = PVE::QemuConfig->get_replicatable_volumes($storecfg, $vmid, $conf, 0, 0);
+is_deeply($volumes, {}, $test_name);
+
 done_testing();
 exit(0);
-- 
2.34.1




^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 pve-manager 12/16] ui: storage: add efi-firmware content type support
  2026-09-28  5:47 [PATCH v2 0/16] Support for custom EFI firmware Christian Ludwig
                   ` (10 preceding siblings ...)
  2026-09-28  5:48 ` [PATCH v2 qemu-server 11/16] test: efi-firmware volumes replication Christian Ludwig
@ 2026-09-28  5:48 ` Christian Ludwig
  2026-09-28  5:48 ` [PATCH v2 pve-manager 13/16] ui: form: support other content types in the ISO selector Christian Ludwig
                   ` (3 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Christian Ludwig @ 2026-09-28  5:48 UTC (permalink / raw)
  To: pve-devel

Add the 'efi-firmware' storage content type to the Web UI:

- Utils.js: add display name for the content type
- ContentTypeSelector: include in the default content type list
- CephFSEdit: include in its explicit content type list, the backend
  CephFS plugin allows the type as well
- Browser: add a tab for browsing and uploading firmware images; the
  format is always 'raw' for this type, so drop that column
- UploadToStorage: allow uploading files without extension restrictions,
  the backend validates the file name by character class instead of by
  extension

Signed-off-by: Christian Ludwig <christian_ludwig@genua.de>
---
 www/manager6/Utils.js                    |  1 +
 www/manager6/form/ContentTypeSelector.js | 11 ++++++++++-
 www/manager6/storage/Browser.js          | 14 ++++++++++++++
 www/manager6/storage/CephFSEdit.js       |  2 +-
 www/manager6/window/UploadToStorage.js   |  1 +
 5 files changed, 27 insertions(+), 2 deletions(-)

diff --git a/www/manager6/Utils.js b/www/manager6/Utils.js
index c86a00c5..1f1be06b 100644
--- a/www/manager6/Utils.js
+++ b/www/manager6/Utils.js
@@ -740,6 +740,7 @@ Ext.define('PVE.Utils', {
             rootdir: gettext('Container'),
             snippets: gettext('Snippets'),
             import: gettext('Import'),
+            'efi-firmware': gettext('EFI Firmware'),
         },
 
         // volume can be a full volume info object, in which case the format parameter is ignored, or
diff --git a/www/manager6/form/ContentTypeSelector.js b/www/manager6/form/ContentTypeSelector.js
index 60532251..b33eb124 100644
--- a/www/manager6/form/ContentTypeSelector.js
+++ b/www/manager6/form/ContentTypeSelector.js
@@ -10,7 +10,16 @@ Ext.define('PVE.form.ContentTypeSelector', {
         me.comboItems = [];
 
         if (me.cts === undefined) {
-            me.cts = ['images', 'iso', 'vztmpl', 'backup', 'rootdir', 'snippets', 'import'];
+            me.cts = [
+                'images',
+                'iso',
+                'vztmpl',
+                'backup',
+                'rootdir',
+                'snippets',
+                'import',
+                'efi-firmware',
+            ];
         }
 
         Ext.Array.each(me.cts, function (ct) {
diff --git a/www/manager6/storage/Browser.js b/www/manager6/storage/Browser.js
index d0237948..611c0009 100644
--- a/www/manager6/storage/Browser.js
+++ b/www/manager6/storage/Browser.js
@@ -189,6 +189,20 @@ Ext.define('PVE.storage.Browser', {
                     pluginType: plugin,
                 });
             }
+            if (contents.includes('efi-firmware')) {
+                me.items.push({
+                    xtype: 'pveStorageContentView',
+                    title: gettext('EFI Firmware'),
+                    iconCls: 'fa fa-microchip',
+                    itemId: 'contentEfiFirmware',
+                    content: 'efi-firmware',
+                    showColumns: ['name', 'date', 'size'],
+                    pluginType: plugin,
+                    enableUploadButton: enableUpload,
+                    enableDownloadUrlButton: enableDownloadUrl,
+                    useUploadButton: true,
+                });
+            }
         }
 
         if (caps.storage['Permissions.Modify']) {
diff --git a/www/manager6/storage/CephFSEdit.js b/www/manager6/storage/CephFSEdit.js
index db54df87..3b7a49f3 100644
--- a/www/manager6/storage/CephFSEdit.js
+++ b/www/manager6/storage/CephFSEdit.js
@@ -95,7 +95,7 @@ Ext.define('PVE.storage.CephFSInputPanel', {
         me.column2 = [
             {
                 xtype: 'pveContentTypeSelector',
-                cts: ['backup', 'iso', 'vztmpl', 'snippets', 'import'],
+                cts: ['backup', 'iso', 'vztmpl', 'snippets', 'import', 'efi-firmware'],
                 fieldLabel: gettext('Content'),
                 name: 'content',
                 value: 'backup',
diff --git a/www/manager6/window/UploadToStorage.js b/www/manager6/window/UploadToStorage.js
index cc53596d..3897334d 100644
--- a/www/manager6/window/UploadToStorage.js
+++ b/www/manager6/window/UploadToStorage.js
@@ -12,6 +12,7 @@ Ext.define('PVE.window.UploadToStorage', {
         import: ['.ova', '.qcow2', '.raw', '.vmdk'],
         iso: ['.img', '.iso'],
         vztmpl: ['.tar.gz', '.tar.xz', '.tar.zst'],
+        'efi-firmware': [],
     },
 
     // accepted for file selection, will be renamed to real extension
-- 
2.34.1




^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 pve-manager 13/16] ui: form: support other content types in the ISO selector
  2026-09-28  5:47 [PATCH v2 0/16] Support for custom EFI firmware Christian Ludwig
                   ` (11 preceding siblings ...)
  2026-09-28  5:48 ` [PATCH v2 pve-manager 12/16] ui: storage: add efi-firmware content type support Christian Ludwig
@ 2026-09-28  5:48 ` Christian Ludwig
  2026-09-28  5:48 ` [PATCH v2 pve-manager 14/16] ui: qemu: allow selecting a custom EFI firmware image Christian Ludwig
                   ` (2 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Christian Ludwig @ 2026-09-28  5:48 UTC (permalink / raw)
  To: pve-devel

The selector pairs a storage selector with a file selector and keeps
both in sync, which is useful for any content type, not just ISO images.
Filter both lists by the 'storageContent' config. The file selector
label gets configured via 'fileLabel'.

The defaults keep the existing ISO behavior.

Signed-off-by: Christian Ludwig <christian_ludwig@genua.de>
---
 www/manager6/form/IsoSelector.js | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/www/manager6/form/IsoSelector.js b/www/manager6/form/IsoSelector.js
index b2d94ed3..508c0eaf 100644
--- a/www/manager6/form/IsoSelector.js
+++ b/www/manager6/form/IsoSelector.js
@@ -13,11 +13,17 @@ Ext.define('PVE.form.IsoSelector', {
     labelWidth: undefined,
     labelAlign: 'right',
 
+    // Storage content type to select from, default: iso.
+    storageContent: 'iso',
+    fileLabel: gettext('ISO image'),
+
     cbindData: function () {
         let me = this;
         return {
             nodename: me.nodename,
             insideWizard: me.insideWizard,
+            storageContent: me.storageContent,
+            fieldLabel: me.fileLabel,
         };
     },
 
@@ -71,9 +77,9 @@ Ext.define('PVE.form.IsoSelector', {
             reference: 'storage',
             isFormField: false,
             fieldLabel: gettext('Storage'),
-            storageContent: 'iso',
             allowBlank: false,
             cbind: {
+                storageContent: '{storageContent}',
                 nodename: '{nodename}',
                 autoSelect: '{insideWizard}',
                 insideWizard: '{insideWizard}',
@@ -94,10 +100,10 @@ Ext.define('PVE.form.IsoSelector', {
             xtype: 'pveFileSelector',
             reference: 'file',
             isFormField: false,
-            storageContent: 'iso',
-            fieldLabel: gettext('ISO image'),
             labelAlign: 'right',
             cbind: {
+                storageContent: '{storageContent}',
+                fieldLabel: '{fieldLabel}',
                 nodename: '{nodename}',
                 disabled: '{disabled}',
                 labelWidth: '{labelWidth}',
-- 
2.34.1




^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 pve-manager 14/16] ui: qemu: allow selecting a custom EFI firmware image
  2026-09-28  5:47 [PATCH v2 0/16] Support for custom EFI firmware Christian Ludwig
                   ` (12 preceding siblings ...)
  2026-09-28  5:48 ` [PATCH v2 pve-manager 13/16] ui: form: support other content types in the ISO selector Christian Ludwig
@ 2026-09-28  5:48 ` Christian Ludwig
  2026-09-28  5:48 ` [PATCH v2 pve-docs 15/16] pvesm: document the efi-firmware content type Christian Ludwig
  2026-09-28  5:48 ` [PATCH v2 pve-docs 16/16] qm: document the efi-firmware VM option Christian Ludwig
  15 siblings, 0 replies; 17+ messages in thread
From: Christian Ludwig @ 2026-09-28  5:48 UTC (permalink / raw)
  To: pve-devel

Offer a custom EFI firmware image in the BIOS editor, as it modifies
the 'bios' setting rather than being a device of its own. The BIOS
selector gains an 'ovmf-custom' pseudo value, which maps to 'bios=ovmf'
plus an 'efi-firmware' volume and reveals a storage and image selector.
Choosing plain OVMF again deletes the option. The 'bios' hardware row
reflects both keys, so a custom image shows up as 'OVMF (custom)' and
can be reverted while pending.

The image replaces the OVMF code image without touching the EFI vars
store, so only offer it for guests that load the firmware read-only
via '-bios' and have no vars store, i.e. confidential VMs. The API
stays general, so an image that is already set is always shown and can
be cleared, with a hint about the vars store when an EFI disk exists.

Signed-off-by: Christian Ludwig <christian_ludwig@genua.de>
---
 www/manager6/Utils.js                 |   2 +
 www/manager6/form/QemuBiosSelector.js |  22 +++-
 www/manager6/qemu/Architecture.js     |   4 +-
 www/manager6/qemu/HardwareView.js     |  35 ++++++-
 www/manager6/qemu/QemuBiosEdit.js     | 140 ++++++++++++++++++++------
 5 files changed, 164 insertions(+), 39 deletions(-)

diff --git a/www/manager6/Utils.js b/www/manager6/Utils.js
index 1f1be06b..a4a5ab4b 100644
--- a/www/manager6/Utils.js
+++ b/www/manager6/Utils.js
@@ -552,6 +552,8 @@ Ext.define('PVE.Utils', {
                 return 'SeaBIOS';
             } else if (value === 'ovmf') {
                 return 'OVMF (UEFI)';
+            } else if (value === 'ovmf-custom') {
+                return gettext('OVMF (custom)');
             } else {
                 return value;
             }
diff --git a/www/manager6/form/QemuBiosSelector.js b/www/manager6/form/QemuBiosSelector.js
index 40abb589..62f67e32 100644
--- a/www/manager6/form/QemuBiosSelector.js
+++ b/www/manager6/form/QemuBiosSelector.js
@@ -2,13 +2,25 @@ Ext.define('PVE.form.QemuBiosSelector', {
     extend: 'PVE.form.FilteredKVComboBox',
     alias: ['widget.pveQemuBiosSelector'],
 
-    comboItems: [
-        ['__default__', PVE.Utils.render_qemu_bios('')],
-        ['seabios', PVE.Utils.render_qemu_bios('seabios')],
-        ['ovmf', PVE.Utils.render_qemu_bios('ovmf')],
-    ],
+    withCustomFirmware: false,
 
     allowedValuesPerCategory: PVE.qemu.Architecture.allowedFirmware,
 
     setDefaultDisplay: (arch) => PVE.Utils.render_qemu_bios('', arch),
+
+    initComponent: function () {
+        let me = this;
+
+        me.comboItems = [
+            ['__default__', PVE.Utils.render_qemu_bios('')],
+            ['seabios', PVE.Utils.render_qemu_bios('seabios')],
+            ['ovmf', PVE.Utils.render_qemu_bios('ovmf')],
+        ];
+
+        if (me.withCustomFirmware) {
+            me.comboItems.push(['ovmf-custom', PVE.Utils.render_qemu_bios('ovmf-custom')]);
+        }
+
+        me.callParent();
+    },
 });
diff --git a/www/manager6/qemu/Architecture.js b/www/manager6/qemu/Architecture.js
index 7b6ef402..6db8f02e 100644
--- a/www/manager6/qemu/Architecture.js
+++ b/www/manager6/qemu/Architecture.js
@@ -63,8 +63,8 @@ Ext.define('PVE.qemu.Architecture', {
     },
 
     allowedFirmware: {
-        x86_64: ['__default__', 'seabios', 'ovmf'], // default is seabios
-        aarch64: ['ovmf'],
+        x86_64: ['__default__', 'seabios', 'ovmf', 'ovmf-custom'], // default is seabios
+        aarch64: ['ovmf', 'ovmf-custom'],
     },
 
     render_vcpu_architecture: function (value) {
diff --git a/www/manager6/qemu/HardwareView.js b/www/manager6/qemu/HardwareView.js
index e6c02299..90dd1c72 100644
--- a/www/manager6/qemu/HardwareView.js
+++ b/www/manager6/qemu/HardwareView.js
@@ -173,7 +173,18 @@ Ext.define('PVE.qemu.HardwareView', {
                 editor: caps.vms['VM.Config.Options'] ? 'PVE.qemu.BiosEdit' : undefined,
                 defaultValue: '',
                 iconCls: 'microchip',
-                renderer: PVE.Utils.render_qemu_bios,
+                multiKey: ['bios', 'efi-firmware'],
+                renderer: function (value, metaData, record, ri, ci, store, pending) {
+                    let firmware = me.getObjectValue('efi-firmware', undefined, pending);
+                    if (firmware && value === 'ovmf') {
+                        return Ext.String.format(
+                            '{0} ({1})',
+                            PVE.Utils.render_qemu_bios('ovmf-custom'),
+                            Ext.htmlEncode(firmware),
+                        );
+                    }
+                    return PVE.Utils.render_qemu_bios(value);
+                },
             },
             vga: {
                 header: gettext('Display'),
@@ -257,6 +268,15 @@ Ext.define('PVE.qemu.HardwareView', {
             affinity: {
                 visible: false,
             },
+            'efi-firmware': {
+                visible: false,
+            },
+            'amd-sev': {
+                visible: false,
+            },
+            'intel-tdx': {
+                visible: false,
+            },
         };
 
         PVE.Utils.forEachBus(undefined, function (type, id) {
@@ -438,6 +458,19 @@ Ext.define('PVE.qemu.HardwareView', {
                 },
             };
 
+            if (rec.data.key === 'bios') {
+                // A custom EFI firmware image replaces the OVMF code image without touching
+                // the EFI vars store. Only offer when EFI vars are not necessary, i.e. for
+                // confidential VMs. Also offer when one is set already.
+                let value = (key) => me.getObjectValue(key, undefined, true);
+                let cvmType =
+                    PVE.Parser.parsePropertyString(value('amd-sev'), 'type')?.type ??
+                    PVE.Parser.parsePropertyString(value('intel-tdx'), 'type')?.type;
+
+                commonOpts.withCustomFirmware =
+                    ['snp', 'tdx'].includes(cvmType) || !!value('efi-firmware');
+            }
+
             if (Ext.isString(editor)) {
                 Ext.create(editor, commonOpts);
             } else {
diff --git a/www/manager6/qemu/QemuBiosEdit.js b/www/manager6/qemu/QemuBiosEdit.js
index a637ed25..cd11f45b 100644
--- a/www/manager6/qemu/QemuBiosEdit.js
+++ b/www/manager6/qemu/QemuBiosEdit.js
@@ -1,9 +1,13 @@
-Ext.define('PVE.qemu.BiosEdit', {
-    extend: 'Proxmox.window.Edit',
-    alias: 'widget.pveQemuBiosEdit',
+Ext.define('PVE.qemu.BiosInputPanel', {
+    extend: 'Proxmox.panel.InputPanel',
+    xtype: 'pveQemuBiosInputPanel',
 
     onlineHelp: 'qm_bios_and_uefi',
-    subject: 'BIOS',
+
+    nodename: undefined,
+
+    withCustomFirmware: false,
+    hadCustomFirmware: false,
 
     viewModel: {
         data: {
@@ -12,38 +16,100 @@ Ext.define('PVE.qemu.BiosEdit', {
         },
         formulas: {
             showEFIDiskHint: (get) => get('bios') === 'ovmf' && !get('efidisk0'),
+            customFirmware: (get) => get('bios') === 'ovmf-custom',
+            // the vars store of an existing EFI disk is kept as it is, so it can go out of sync
+            showVarStoreHint: (get) => get('bios') === 'ovmf-custom' && !!get('efidisk0'),
         },
     },
 
-    items: [
-        {
-            xtype: 'pveQemuBiosSelector',
-            onlineHelp: 'qm_bios_and_uefi',
-            name: 'bios',
-            value: '__default__',
-            bind: {
-                value: '{bios}',
-                category: '{arch}',
+    onGetValues: function (values) {
+        let me = this;
+
+        if (values.bios === 'ovmf-custom') {
+            values.bios = 'ovmf';
+            return values;
+        }
+
+        // Remove custom firmware configuration
+        delete values['efi-firmware'];
+        if (me.hadCustomFirmware) {
+            let deleted = values.delete ? [].concat(values.delete) : [];
+            deleted.push('efi-firmware');
+            values.delete = deleted.join(',');
+        }
+
+        return values;
+    },
+
+    initComponent: function () {
+        let me = this;
+
+        me.items = [
+            {
+                xtype: 'pveQemuBiosSelector',
+                onlineHelp: 'qm_bios_and_uefi',
+                name: 'bios',
+                value: '__default__',
+                withCustomFirmware: me.withCustomFirmware,
+                bind: {
+                    value: '{bios}',
+                    category: '{arch}',
+                },
+                fieldLabel: 'BIOS',
             },
-            fieldLabel: 'BIOS',
-        },
-        {
-            xtype: 'displayfield',
-            name: 'efidisk0',
-            bind: '{efidisk0}',
-            hidden: true,
-        },
-        {
-            xtype: 'displayfield',
-            userCls: 'pmx-hint',
-            value: gettext(
-                'You need to add an EFI disk for storing the EFI settings. See the online help for details.',
-            ),
-            bind: {
-                hidden: '{!showEFIDiskHint}',
+            {
+                xtype: 'displayfield',
+                name: 'efidisk0',
+                bind: '{efidisk0}',
+                hidden: true,
             },
-        },
-    ],
+            {
+                xtype: 'displayfield',
+                userCls: 'pmx-hint',
+                value: gettext(
+                    'You need to add an EFI disk for storing the EFI settings. See the online help for details.',
+                ),
+                bind: {
+                    hidden: '{!showEFIDiskHint}',
+                },
+            },
+            {
+                xtype: 'pveIsoSelector',
+                name: 'efi-firmware',
+                storageContent: 'efi-firmware',
+                fileLabel: gettext('Firmware Image'),
+                nodename: me.nodename,
+                hidden: true,
+                disabled: true,
+                bind: {
+                    hidden: '{!customFirmware}',
+                    disabled: '{!customFirmware}',
+                },
+            },
+            {
+                xtype: 'displayfield',
+                userCls: 'pmx-hint',
+                value: gettext(
+                    'Note: The EFI disk keeps its vars store, which needs to match the custom firmware, otherwise the guest might not boot.',
+                ),
+                bind: {
+                    hidden: '{!showVarStoreHint}',
+                },
+            },
+        ];
+
+        me.callParent();
+    },
+});
+
+Ext.define('PVE.qemu.BiosEdit', {
+    extend: 'Proxmox.window.Edit',
+    alias: 'widget.pveQemuBiosEdit',
+
+    onlineHelp: 'qm_bios_and_uefi',
+    subject: 'BIOS',
+
+    withCustomFirmware: false,
 
     initComponent: function () {
         let me = this;
@@ -54,6 +120,14 @@ Ext.define('PVE.qemu.BiosEdit', {
             throw 'no nodename given';
         }
 
+        me.items = [
+            {
+                xtype: 'pveQemuBiosInputPanel',
+                nodename: me.nodename,
+                withCustomFirmware: me.withCustomFirmware,
+            },
+        ];
+
         me.callParent();
 
         if (!me.isCreate) {
@@ -61,6 +135,10 @@ Ext.define('PVE.qemu.BiosEdit', {
                 success: function ({ result }) {
                     let values = result.data;
                     let arch = PVE.qemu.Architecture.getGuestArchitecture(values.arch, me.nodename);
+                    if (values['efi-firmware'] && values.bios === 'ovmf') {
+                        values.bios = 'ovmf-custom';
+                        me.down('pveQemuBiosInputPanel').hadCustomFirmware = true;
+                    }
                     me.setValues(values);
                     me.down('pveQemuBiosSelector').setCategory(arch);
                 },
-- 
2.34.1




^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 pve-docs 15/16] pvesm: document the efi-firmware content type
  2026-09-28  5:47 [PATCH v2 0/16] Support for custom EFI firmware Christian Ludwig
                   ` (13 preceding siblings ...)
  2026-09-28  5:48 ` [PATCH v2 pve-manager 14/16] ui: qemu: allow selecting a custom EFI firmware image Christian Ludwig
@ 2026-09-28  5:48 ` Christian Ludwig
  2026-09-28  5:48 ` [PATCH v2 pve-docs 16/16] qm: document the efi-firmware VM option Christian Ludwig
  15 siblings, 0 replies; 17+ messages in thread
From: Christian Ludwig @ 2026-09-28  5:48 UTC (permalink / raw)
  To: pve-devel

Describe the content type and list it for the backends that support it.

Signed-off-by: Christian Ludwig <christian_ludwig@genua.de>
---
 pve-storage-cephfs.adoc | 4 ++--
 pve-storage-cifs.adoc   | 4 ++--
 pve-storage-dir.adoc    | 5 +++--
 pve-storage-nfs.adoc    | 4 ++--
 pvesm.adoc              | 4 ++++
 5 files changed, 13 insertions(+), 8 deletions(-)

diff --git a/pve-storage-cephfs.adoc b/pve-storage-cephfs.adoc
index ab8d850..5ae22e3 100644
--- a/pve-storage-cephfs.adoc
+++ b/pve-storage-cephfs.adoc
@@ -128,8 +128,8 @@ The `cephfs` backend is a POSIX-compliant filesystem, on top of a Ceph cluster.
 .Storage features for backend `cephfs`
 [width="100%",cols="m,m,3*d",options="header"]
 |==============================================================================
-|Content types              |Image formats  |Shared |Snapshots |Clones
-|vztmpl iso backup snippets |none           |yes    |yes^[1]^  |no
+|Content types                           |Image formats  |Shared |Snapshots |Clones
+|vztmpl iso backup snippets efi-firmware |none           |yes    |yes^[1]^  |no
 |==============================================================================
 ^[1]^ While no known bugs exist, snapshots are not yet guaranteed to be stable,
 as they lack sufficient testing.
diff --git a/pve-storage-cifs.adoc b/pve-storage-cifs.adoc
index 1664764..53ef141 100644
--- a/pve-storage-cifs.adoc
+++ b/pve-storage-cifs.adoc
@@ -98,8 +98,8 @@ features available.
 .Storage features for backend `cifs`
 [width="100%",cols="m,m,3*d",options="header"]
 |==============================================================================
-|Content types                             |Image formats   |Shared |Snapshots |Clones
-|images rootdir vztmpl iso backup snippets |raw qcow2 vmdk  |yes    |qcow2     |qcow2
+|Content types                                          |Image formats   |Shared |Snapshots |Clones
+|images rootdir vztmpl iso backup snippets efi-firmware |raw qcow2 vmdk  |yes    |qcow2     |qcow2
 |==============================================================================
 
 Examples
diff --git a/pve-storage-dir.adoc b/pve-storage-dir.adoc
index 9905017..250ef58 100644
--- a/pve-storage-dir.adoc
+++ b/pve-storage-dir.adoc
@@ -41,6 +41,7 @@ storage backends.
 |Backup files        |`dump/`
 |Snippets            |`snippets/`
 |Import              |`import/`
+|EFI firmware        |`efi-firmware/`
 |===========================================================
 
 
@@ -119,8 +120,8 @@ feature to create clones.
 .Storage features for backend `dir`
 [width="100%",cols="m,m,3*d",options="header"]
 |==============================================================================
-|Content types                              |Image formats         |Shared |Snapshots |Clones
-|images rootdir vztmpl iso backup snippets  |raw qcow2 vmdk subvol |no     |qcow2     |qcow2
+|Content types                                          |Image formats         |Shared |Snapshots |Clones
+|images rootdir vztmpl iso backup snippets efi-firmware |raw qcow2 vmdk subvol |no     |qcow2     |qcow2
 |==============================================================================
 
 
diff --git a/pve-storage-nfs.adoc b/pve-storage-nfs.adoc
index ea5bbdc..33793b3 100644
--- a/pve-storage-nfs.adoc
+++ b/pve-storage-nfs.adoc
@@ -73,8 +73,8 @@ to implement snapshots and cloning.
 .Storage features for backend `nfs`
 [width="100%",cols="m,m,3*d",options="header"]
 |==============================================================================
-|Content types                              |Image formats  |Shared |Snapshots |Clones
-|images rootdir vztmpl iso backup snippets  |raw qcow2 vmdk |yes    |qcow2     |qcow2
+|Content types                                          |Image formats  |Shared |Snapshots |Clones
+|images rootdir vztmpl iso backup snippets efi-firmware |raw qcow2 vmdk |yes    |qcow2     |qcow2
 |==============================================================================
 
 Examples
diff --git a/pvesm.adoc b/pvesm.adoc
index 5bd24b2..ebbd0bb 100644
--- a/pvesm.adoc
+++ b/pvesm.adoc
@@ -250,6 +250,10 @@ import:::
 
 OVAs and VM disk images that can be imported from this storage
 
+efi-firmware:::
+
+Custom EFI firmware images for use with OVMF-based VMs
+
 shared::
 
 Indicate that this is a single storage with the same contents on all nodes (or
-- 
2.34.1




^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 pve-docs 16/16] qm: document the efi-firmware VM option
  2026-09-28  5:47 [PATCH v2 0/16] Support for custom EFI firmware Christian Ludwig
                   ` (14 preceding siblings ...)
  2026-09-28  5:48 ` [PATCH v2 pve-docs 15/16] pvesm: document the efi-firmware content type Christian Ludwig
@ 2026-09-28  5:48 ` Christian Ludwig
  15 siblings, 0 replies; 17+ messages in thread
From: Christian Ludwig @ 2026-09-28  5:48 UTC (permalink / raw)
  To: pve-devel

Describe how to set and remove a custom EFI firmware image.

Signed-off-by: Christian Ludwig <christian_ludwig@genua.de>
---
 qm.adoc | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 48 insertions(+)

diff --git a/qm.adoc b/qm.adoc
index 5b46cdc..4186067 100644
--- a/qm.adoc
+++ b/qm.adoc
@@ -1246,6 +1246,54 @@ NOTE: The markers `ms-cert=2023` and `ms-cert=2023w` may indicate partial
 enrollment. The VM start task log will warn about this. You should apply the
 enrollment procedure for such EFI disks too.
 
+[[qm_custom_efi_firmware]]
+Custom EFI Firmware
+^^^^^^^^^^^^^^^^^^^
+
+By default, {pve} uses the system-provided OVMF firmware images. If you need a
+custom or vendor-specific EFI firmware code image, you can override the default
+firmware with the `efi-firmware` VM option.
+
+The firmware image must first be uploaded to a storage that has the
+`efi-firmware` content type enabled.
+
+To configure a VM to use a custom firmware image:
+
+----
+# qm set <vmid> -efi-firmware <storage>:efi-firmware/<name>
+----
+
+For example:
+
+----
+# qm set 100 -efi-firmware local:efi-firmware/custom-ovmf-code.fd
+----
+
+NOTE: The `efi-firmware` option requires `bios` to be set to `ovmf`.
+
+The custom image replaces the firmware code (pflash0) only. The EFI vars disk
+(`efidisk0`) keeps its contents, which are created from the vars template of
+the system-provided firmware. If they do not match the custom image, the guest
+may fail to boot. A matching vars image can be imported with:
+
+----
+# qm set <vmid> -efidisk0 <storage>:0,efitype=4m,pre-enrolled-keys=0,import-from=<source>
+----
+
+Confidential VMs (AMD SEV-SNP, Intel TDX) load the firmware read-only via
+`-bios` and use no EFI vars disk at all, so this does not apply to them. For
+that reason, the web interface only offers custom firmware images for such VMs,
+through the 'BIOS' entry in the 'Hardware' panel of a VM.
+
+The firmware image is not copied when a VM migrates, so it has to be on a
+shared storage.
+
+To remove a custom firmware assignment and revert to the default OVMF image:
+
+----
+# qm set <vmid> -delete efi-firmware
+----
+
 [[qm_tpm]]
 Trusted Platform Module (TPM)
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- 
2.34.1




^ permalink raw reply related	[flat|nested] 17+ messages in thread

end of thread, other threads:[~2026-09-28  5:50 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-28  5:47 [PATCH v2 0/16] Support for custom EFI firmware Christian Ludwig
2026-09-28  5:47 ` [PATCH v2 pve-storage 1/16] plugin: add efi-firmware content type Christian Ludwig
2026-09-28  5:47 ` [PATCH v2 pve-storage 2/16] test: get_subdir: cover the " Christian Ludwig
2026-09-28  5:48 ` [PATCH v2 pve-storage 3/16] plugins: allow the efi-firmware content type on file based storages Christian Ludwig
2026-09-28  5:48 ` [PATCH v2 pve-storage 4/16] api: status: support efi-firmware in upload and download-url Christian Ludwig
2026-09-28  5:48 ` [PATCH v2 pve-storage 5/16] test: volume access: cover efi-firmware volumes Christian Ludwig
2026-09-28  5:48 ` [PATCH v2 pve-storage 6/16] test: list volumes: " Christian Ludwig
2026-09-28  5:48 ` [PATCH v2 qemu-server 07/16] config: add the efi-firmware option Christian Ludwig
2026-09-28  5:48 ` [PATCH v2 qemu-server 08/16] api: allow setting " Christian Ludwig
2026-09-28  5:48 ` [PATCH v2 qemu-server 09/16] ovmf: use a custom firmware image if configured Christian Ludwig
2026-09-28  5:48 ` [PATCH v2 qemu-server 10/16] test: efi-firmware key in VM config Christian Ludwig
2026-09-28  5:48 ` [PATCH v2 qemu-server 11/16] test: efi-firmware volumes replication Christian Ludwig
2026-09-28  5:48 ` [PATCH v2 pve-manager 12/16] ui: storage: add efi-firmware content type support Christian Ludwig
2026-09-28  5:48 ` [PATCH v2 pve-manager 13/16] ui: form: support other content types in the ISO selector Christian Ludwig
2026-09-28  5:48 ` [PATCH v2 pve-manager 14/16] ui: qemu: allow selecting a custom EFI firmware image Christian Ludwig
2026-09-28  5:48 ` [PATCH v2 pve-docs 15/16] pvesm: document the efi-firmware content type Christian Ludwig
2026-09-28  5:48 ` [PATCH v2 pve-docs 16/16] qm: document the efi-firmware VM option Christian Ludwig

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal