public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Fiona Ebner <f.ebner@proxmox.com>
To: Christian Ludwig <christian_ludwig@genua.de>,
	pve-devel@lists.proxmox.com
Subject: Re: [PATCH qemu-server v3 10/19] bios: add a custom firmware sub-property
Date: Thu, 5 Nov 2026 16:23:58 +0100	[thread overview]
Message-ID: <ea06dc3e-5449-498f-a487-a48aaf0dfa43@proxmox.com> (raw)
In-Reply-To: <fd50e4438008341d57d9d369e88c24954474d196.1793029144.git@genua.de>

Am 27.10.26 um 11:14 AM schrieb Christian Ludwig:
> Add the 'firmware' sub-property to the bios option, pointing to a volume
> of the efi-firmware content type:
> 
>   bios: ovmf,firmware=local:efi-firmware/custom.fd
> 
> Custom firmware images are only supported with OVMF for now, so require
> the 'ovmf' BIOS type when generating the command line.
> 
> The image replaces the system OVMF code image for the pflash, blockdev
> and '-bios' variants. Include the volume when activating a VM's volumes.
> Check the volume's storage and content type before that, so that errors
> are reported in the context of the custom firmware, e.g. when a template
> or a restored VM references an image that is not available on the node.
> 
> Firmware images are content and not owned by the VM, like ISO images, so
> refuse migration unless they are on a shared storage. They are never
> copied, so refuse remote migration too.
> 
> Signed-off-by: Christian Ludwig <christian_ludwig@genua.de>
> ---
>  src/PVE/QemuConfig.pm      | 17 ++++++++++
>  src/PVE/QemuMigrate.pm     |  6 ++++
>  src/PVE/QemuServer.pm      | 20 ++++++++---
>  src/PVE/QemuServer/BIOS.pm | 28 ++++++++++++++++
>  src/PVE/QemuServer/OVMF.pm | 68 ++++++++++++++++++++++++++++++++------
>  5 files changed, 125 insertions(+), 14 deletions(-)
> 
> diff --git a/src/PVE/QemuConfig.pm b/src/PVE/QemuConfig.pm
> index d83a474c..c2b2cab7 100644
> --- a/src/PVE/QemuConfig.pm
> +++ b/src/PVE/QemuConfig.pm
> @@ -113,6 +113,17 @@ sub parse_volume {
>              die $err;
>          }
>          $volume = { 'file' => $volume_string };
> +    } elsif ($key eq 'bios') {
> +        my $bios = eval { PVE::QemuServer::BIOS::parse_bios($volume_string) };
> +        if (my $err = $@) {
> +            return if $noerr;
> +            die $err;
> +        }
> +        # only a custom firmware image is a volume, plain BIOS settings are not
> +        return if !defined($bios->{firmware});
> +
> +        $bios->{file} = delete($bios->{firmware});
> +        $volume = $bios;

It is a bit hacky, but I suppose it can be fine. An alternative would be
to handle it in foreach_volid() instead, or in all transitive callers
where actually required, but that is probably not nicer, so let's just
keep it like this.

>      } else {
>          $volume = PVE::QemuServer::Drive::parse_drive($key, $volume_string);
>      }
> @@ -125,6 +136,12 @@ sub parse_volume {
>  sub print_volume {
>      my ($class, $key, $volume) = @_;
>  
> +    if ($key eq 'bios') {
> +        my $bios = { $volume->%* };
> +        $bios->{firmware} = delete($bios->{file});
> +        return PVE::QemuServer::BIOS::print_bios($bios);
> +    }

Even if going with the above for parse_volume(), IMHO there should
rather be an error when encountering a 'bios' key here, as this is not
how the 'bios' property is supposed to be printed.

> +
>      return PVE::QemuServer::Drive::print_drive($volume);
>  }
>  
> diff --git a/src/PVE/QemuMigrate.pm b/src/PVE/QemuMigrate.pm
> index 8da6f15d..78416896 100644
> --- a/src/PVE/QemuMigrate.pm
> +++ b/src/PVE/QemuMigrate.pm
> @@ -431,6 +431,10 @@ sub scan_local_volumes {
>              # check if storage is available on both nodes
>              my $scfg = PVE::Storage::storage_check_enabled($storecfg, $sid);
>  
> +            # the custom firmware image is not owned by the VM, so it is never copied over
> +            die "custom firmware image is not available on the remote cluster\n"
> +                if $attr->{is_firmware} && $self->{opts}->{remote};

Nit: it might be if the user placed it there manually. So the error
message should rather state something like "remote migration with custom
BIOS firmware image is not possible".

> +
>              my $targetsid = $sid;
>              # NOTE: local ignores shared mappings, remote maps them
>              if (!$scfg->{shared} || $self->{opts}->{remote}) {
> @@ -440,6 +444,8 @@ sub scan_local_volumes {
>              $self->target_storage_check_available($storecfg, $targetsid, $volid);
>              return if $scfg->{shared} && !$self->{opts}->{remote};
>  
> +            die "local custom firmware image - use a shared storage\n" if $attr->{is_firmware};

Should mention that it's the one from the BIOS and/or the volume ID for
context.

> +
>              $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 c288ba22..87baa1f0 100644
> --- a/src/PVE/QemuServer.pm
> +++ b/src/PVE/QemuServer.pm
> @@ -3259,6 +3259,9 @@ sub config_to_command {
>          push @$cmd, '-smbios', "type=1" . $smbios_string;
>      }
>  
> +    PVE::QemuServer::BIOS::check_bios(PVE::QemuServer::BIOS::parse_bios($conf->{bios}));
> +    PVE::QemuServer::OVMF::check_custom_firmware($storecfg, $conf);
> +
>      if (get_bios_type($conf) eq 'ovmf') {
>          die "OVMF (UEFI) BIOS is not supported on 32-bit CPU types\n"
>              if !$forcecpu && get_cpu_bitness($conf->{cpu}, $arch) == 32;
> @@ -4519,11 +4522,14 @@ sub foreach_volid {

The get_replicatable_volumes() caller of foreach_volid() should skip
volumes with the 'is_firmware' attribute.

In vm_stop_cleanup(), there is a get_vm_volumes() call, but that
actually should skip efi-firmware volumes, as those should not be
deactivated there. You could add a flag to get_vm_volumes() for skipping
volumes with the 'is_firmware' attribute.

>          $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 'bios';
> +
>          $volhash->{$volid}->{drivename} = $key if is_valid_drivename($key);
>      };
>  
>      my $include_opts = {
> -        extra_keys => ['vmstate'],
> +        extra_keys => ['vmstate', 'bios'],
>          include_unused => 1,
>      };
>  
> @@ -6074,7 +6080,8 @@ sub get_vm_volumes {
>      return $vollist;
>  }
>  
> -# Get volumes defined in the current VM configuration, including the VM state file.
> +# Get volumes defined in the current VM configuration, including the VM state file and the custom
> +# firmware image.
>  sub get_current_vm_volumes {
>      my ($storecfg, $conf) = @_;
>  
> @@ -6082,12 +6089,17 @@ sub get_current_vm_volumes {
>  
>      PVE::QemuConfig->foreach_volume_full(
>          $conf,
> -        { extra_keys => ['vmstate'] },
> +        { extra_keys => ['vmstate', 'bios'] },
>          sub {
>              my ($ds, $drive) = @_;
>  
>              if (PVE::Storage::parse_volume_id($drive->{file}, 1)) {
> -                check_volume_storage_type($storecfg, $drive->{file});
> +                if ($ds eq 'bios') {
> +                    # report errors in the context of the custom firmware
> +                    PVE::QemuServer::OVMF::check_custom_firmware($storecfg, $conf);
> +                } else {
> +                    check_volume_storage_type($storecfg, $drive->{file});
> +                }
>                  push $volumes->@*, $drive->{file};
>              }
>          },
> diff --git a/src/PVE/QemuServer/BIOS.pm b/src/PVE/QemuServer/BIOS.pm
> index 4e1a3936..eecf877f 100644
> --- a/src/PVE/QemuServer/BIOS.pm
> +++ b/src/PVE/QemuServer/BIOS.pm
> @@ -11,6 +11,8 @@ our @EXPORT_OK = qw(
>      parse_bios
>      print_bios
>      get_bios_type
> +    get_bios_firmware
> +    check_bios
>  );
>  
>  my $bios_fmt = {
> @@ -20,6 +22,15 @@ my $bios_fmt = {
>          default_key => 1,
>          description => "Select BIOS implementation.",
>      },
> +    firmware => {
> +        type => 'string',
> +        format => 'pve-volume-id',
> +        format_description => 'volume',
> +        description => "Custom firmware image to use instead of the one provided by the system."
> +            . " Must reference a volume with content type 'efi-firmware', for example"
> +            . " 'local:efi-firmware/custom.fd'. Only supported with the 'ovmf' BIOS type.",
> +        optional => 1,
> +    },
>  };
>  PVE::JSONSchema::register_format('pve-qm-bios', $bios_fmt);
>  
> @@ -45,4 +56,21 @@ sub get_bios_type {
>      return parse_bios($conf->{bios})->{type} // 'seabios';
>  }
>  
> +# Returns the volume ID of the custom firmware image, if one is configured.
> +sub get_bios_firmware {
> +    my ($conf) = @_;
> +
> +    return parse_bios($conf->{bios})->{firmware};
> +}
> +
> +# Dies if the combination of settings in the parsed bios property-string is not supported.
> +sub check_bios {
> +    my ($bios) = @_;

Style nit: both the caller here and in the next patch use
PVE::QemuServer::BIOS::check_bios(PVE::QemuServer::BIOS::parse_bios(...))
so you could also require the property string as a parameter and do the
parsing within here instead.

> +
> +    die "a custom firmware image requires the 'ovmf' BIOS type\n"
> +        if defined($bios->{firmware}) && $bios->{type} ne 'ovmf';
> +
> +    return;
> +}
> +
>  1;
---snip 8<---




  reply	other threads:[~2026-11-05 15:24 UTC|newest]

Thread overview: 25+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-27 10:12 [PATCH v3 00/19] Support for custom EFI firmware Christian Ludwig
2026-10-27 10:12 ` [PATCH pve-storage v3 01/19] plugin: add efi-firmware content type Christian Ludwig
2026-11-03 16:31   ` Fiona Ebner
2026-10-27 10:12 ` [PATCH pve-storage v3 02/19] test: get_subdir: cover the " Christian Ludwig
2026-10-27 10:12 ` [PATCH pve-storage v3 03/19] plugins: allow the efi-firmware content type on file based storages Christian Ludwig
2026-10-27 10:12 ` [PATCH pve-storage v3 04/19] plugin: support efi-firmware volumes in volume export and import Christian Ludwig
2026-10-27 10:12 ` [PATCH pve-storage v3 05/19] api: status: support efi-firmware in upload and download-url Christian Ludwig
2026-11-03 16:31   ` Fiona Ebner
2026-10-27 10:12 ` [PATCH pve-storage v3 06/19] test: volume access: cover efi-firmware volumes Christian Ludwig
2026-10-27 10:12 ` [PATCH pve-storage v3 07/19] test: list volumes: " Christian Ludwig
2026-10-27 10:12 ` [PATCH pve-storage v3 08/19] test: cover efi-firmware volumes in parse_volname and path_to_volume_id Christian Ludwig
2026-10-27 10:12 ` [PATCH qemu-server v3 09/19] config: define bios schema as property-string Christian Ludwig
2026-11-05 15:23   ` Fiona Ebner
2026-10-27 10:12 ` [PATCH qemu-server v3 10/19] bios: add a custom firmware sub-property Christian Ludwig
2026-11-05 15:23   ` Fiona Ebner [this message]
2026-10-27 10:12 ` [PATCH qemu-server v3 11/19] api: check the custom firmware of the bios option Christian Ludwig
2026-11-05 15:23   ` Fiona Ebner
2026-10-27 10:12 ` [PATCH qemu-server v3 12/19] test: custom firmware in " Christian Ludwig
2026-10-27 10:12 ` [PATCH qemu-server v3 13/19] test: custom firmware volumes in replication and migration Christian Ludwig
2026-10-27 10:12 ` [PATCH pve-manager v3 14/19] ui: storage: add efi-firmware content type support Christian Ludwig
2026-10-27 10:12 ` [PATCH pve-manager v3 15/19] ui: form: support other content types in the ISO selector Christian Ludwig
2026-10-27 10:12 ` [PATCH pve-manager v3 16/19] ui: qemu: allow selecting a custom EFI firmware image Christian Ludwig
2026-10-27 10:12 ` [PATCH pve-manager v3 17/19] ui: migrate: refuse migration with a local custom " Christian Ludwig
2026-10-27 10:12 ` [PATCH pve-docs v3 18/19] pvesm: document the efi-firmware content type Christian Ludwig
2026-10-27 10:12 ` [PATCH pve-docs v3 19/19] qm: document the custom firmware of the bios option Christian Ludwig

Reply instructions:

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

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

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

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

  git send-email \
    --in-reply-to=ea06dc3e-5449-498f-a487-a48aaf0dfa43@proxmox.com \
    --to=f.ebner@proxmox.com \
    --cc=christian_ludwig@genua.de \
    --cc=pve-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

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

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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