[pve-devel] [PATCH qemu-server 02/31] introduce OVMF module
Fiona Ebner
f.ebner at proxmox.com
Wed Jun 25 17:56:25 CEST 2025
Signed-off-by: Fiona Ebner <f.ebner at proxmox.com>
---
Changes since previous series:
* keep get_efivars_size() as a wrapper in QemuServer module
* keep early check for CPU bitness in QemuServer module
src/PVE/API2/Qemu.pm | 3 +-
src/PVE/QemuServer.pm | 155 +++--------------------------
src/PVE/QemuServer/Makefile | 1 +
src/PVE/QemuServer/OVMF.pm | 166 +++++++++++++++++++++++++++++++
src/test/MigrationTest/Shared.pm | 4 +
5 files changed, 185 insertions(+), 144 deletions(-)
create mode 100644 src/PVE/QemuServer/OVMF.pm
diff --git a/src/PVE/API2/Qemu.pm b/src/PVE/API2/Qemu.pm
index ce6f362d..6830ea1e 100644
--- a/src/PVE/API2/Qemu.pm
+++ b/src/PVE/API2/Qemu.pm
@@ -36,6 +36,7 @@ use PVE::QemuServer::Monitor qw(mon_cmd);
use PVE::QemuServer::Machine;
use PVE::QemuServer::Memory qw(get_current_memory);
use PVE::QemuServer::MetaInfo;
+use PVE::QemuServer::OVMF;
use PVE::QemuServer::PCI;
use PVE::QemuServer::QMPHelpers;
use PVE::QemuServer::RNG;
@@ -612,7 +613,7 @@ my sub create_disks : prototype($$$$$$$$$$$) {
"SEV-SNP uses consolidated read-only firmware and does not require an EFI disk\n"
if $amd_sev_type && $amd_sev_type eq 'snp';
- ($volid, $size) = PVE::QemuServer::create_efidisk(
+ ($volid, $size) = PVE::QemuServer::OVMF::create_efidisk(
$storecfg, $storeid, $vmid, $fmt, $arch, $disk, $smm, $amd_sev_type,
);
} elsif ($ds eq 'tpmstate0') {
diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm
index 6926182b..bb10f116 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -71,6 +71,7 @@ use PVE::QemuServer::Machine;
use PVE::QemuServer::Memory qw(get_current_memory);
use PVE::QemuServer::MetaInfo;
use PVE::QemuServer::Monitor qw(mon_cmd);
+use PVE::QemuServer::OVMF;
use PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr print_pcie_root_port parse_hostpci);
use PVE::QemuServer::QemuImage;
use PVE::QemuServer::QMPHelpers qw(qemu_deviceadd qemu_devicedel qemu_objectadd qemu_objectdel);
@@ -98,41 +99,6 @@ my sub vm_is_ha_managed {
return PVE::HA::Config::vm_is_ha_managed($vmid);
}
-my $EDK2_FW_BASE = '/usr/share/pve-edk2-firmware/';
-my $OVMF = {
- x86_64 => {
- '4m-no-smm' => [
- "$EDK2_FW_BASE/OVMF_CODE_4M.fd", "$EDK2_FW_BASE/OVMF_VARS_4M.fd",
- ],
- '4m-no-smm-ms' => [
- "$EDK2_FW_BASE/OVMF_CODE_4M.fd", "$EDK2_FW_BASE/OVMF_VARS_4M.ms.fd",
- ],
- '4m' => [
- "$EDK2_FW_BASE/OVMF_CODE_4M.secboot.fd", "$EDK2_FW_BASE/OVMF_VARS_4M.fd",
- ],
- '4m-ms' => [
- "$EDK2_FW_BASE/OVMF_CODE_4M.secboot.fd", "$EDK2_FW_BASE/OVMF_VARS_4M.ms.fd",
- ],
- '4m-sev' => [
- "$EDK2_FW_BASE/OVMF_CVM_CODE_4M.fd", "$EDK2_FW_BASE/OVMF_CVM_VARS_4M.fd",
- ],
- '4m-snp' => [
- "$EDK2_FW_BASE/OVMF_CVM_4M.fd",
- ],
- # FIXME: These are legacy 2MB-sized images that modern OVMF doesn't supports to build
- # anymore. how can we deperacate this sanely without breaking existing instances, or using
- # older backups and snapshot?
- default => [
- "$EDK2_FW_BASE/OVMF_CODE.fd", "$EDK2_FW_BASE/OVMF_VARS.fd",
- ],
- },
- aarch64 => {
- default => [
- "$EDK2_FW_BASE/AAVMF_CODE.fd", "$EDK2_FW_BASE/AAVMF_VARS.fd",
- ],
- },
-};
-
my $cpuinfo = PVE::ProcFSTools::read_cpuinfo();
# Note about locking: we use flock on the config file protect against concurrent actions.
@@ -3293,36 +3259,6 @@ sub vga_conf_has_spice {
return $1 || 1;
}
-sub get_ovmf_files($$$$) {
- my ($arch, $efidisk, $smm, $amd_sev_type) = @_;
-
- my $types = $OVMF->{$arch}
- or die "no OVMF images known for architecture '$arch'\n";
-
- my $type = 'default';
- if ($arch eq 'x86_64') {
- if ($amd_sev_type && $amd_sev_type eq 'snp') {
- $type = "4m-snp";
- my ($ovmf) = $types->{$type}->@*;
- die "EFI base image '$ovmf' not found\n" if !-f $ovmf;
- return ($ovmf);
- } elsif ($amd_sev_type) {
- $type = "4m-sev";
- } elsif (defined($efidisk->{efitype}) && $efidisk->{efitype} eq '4m') {
- $type = $smm ? "4m" : "4m-no-smm";
- $type .= '-ms' if $efidisk->{'pre-enrolled-keys'};
- } else {
- # TODO: log_warn about use of legacy images for x86_64 with Promxox VE 9
- }
- }
-
- my ($ovmf_code, $ovmf_vars) = $types->{$type}->@*;
- die "EFI base image '$ovmf_code' not found\n" if !-f $ovmf_code;
- die "EFI vars image '$ovmf_vars' not found\n" if !-f $ovmf_vars;
-
- return ($ovmf_code, $ovmf_vars);
-}
-
# To use query_supported_cpu_flags and query_understood_cpu_flags to get flags
# to use in a QEMU command line (-cpu element), first array_intersect the result
# of query_supported_ with query_understood_. This is necessary because:
@@ -3464,49 +3400,6 @@ my sub should_disable_smm {
&& $vga->{type} =~ m/^(serial\d+|none)$/;
}
-my sub print_ovmf_drive_commandlines {
- my ($conf, $storecfg, $vmid, $hw_info, $version_guard) = @_;
-
- my ($amd_sev_type, $arch, $q35) = $hw_info->@{qw(amd-sev-type arch q35)};
-
- my $d = $conf->{efidisk0} ? parse_drive('efidisk0', $conf->{efidisk0}) : undef;
-
- die "Attempting to configure SEV-SNP with pflash devices instead of using `-bios`\n"
- if $amd_sev_type && $amd_sev_type eq 'snp';
-
- my ($ovmf_code, $ovmf_vars) = get_ovmf_files($arch, $d, $q35, $amd_sev_type);
-
- my $var_drive_str = "if=pflash,unit=1,id=drive-efidisk0";
- if ($d) {
- my ($storeid, $volname) = PVE::Storage::parse_volume_id($d->{file}, 1);
- my ($path, $format) = $d->@{ 'file', 'format' };
- if ($storeid) {
- $path = PVE::Storage::path($storecfg, $d->{file});
- $format //= checked_volume_format($storecfg, $d->{file});
- } elsif (!defined($format)) {
- die "efidisk format must be specified\n";
- }
- # SPI flash does lots of read-modify-write OPs, without writeback this gets really slow #3329
- if ($path =~ m/^rbd:/) {
- $var_drive_str .= ',cache=writeback';
- $path .= ':rbd_cache_policy=writeback'; # avoid write-around, we *need* to cache writes too
- }
- $var_drive_str .= ",format=$format,file=$path";
-
- $var_drive_str .= ",size=" . (-s $ovmf_vars)
- if $format eq 'raw' && $version_guard->(4, 1, 2);
- $var_drive_str .= ',readonly=on' if drive_is_read_only($conf, $d);
- } else {
- log_warn("no efidisk configured! Using temporary efivars disk.");
- my $path = "/tmp/$vmid-ovmf.fd";
- PVE::Tools::file_copy($ovmf_vars, $path, -s $ovmf_vars);
- $var_drive_str .= ",format=raw,file=$path";
- $var_drive_str .= ",size=" . (-s $ovmf_vars) if $version_guard->(4, 1, 2);
- }
-
- return ("if=pflash,unit=0,format=raw,readonly=on,file=$ovmf_code", $var_drive_str);
-}
-
my sub get_vga_properties {
my ($conf, $arch, $machine_version, $winversion) = @_;
@@ -3684,23 +3577,15 @@ sub config_to_command {
die "OVMF (UEFI) BIOS is not supported on 32-bit CPU types\n"
if !$forcecpu && get_cpu_bitness($conf->{cpu}, $arch) == 32;
- my $amd_sev_type = get_amd_sev_type($conf);
- if ($amd_sev_type && $amd_sev_type eq 'snp') {
- if (defined($conf->{efidisk0})) {
- log_warn("EFI disks are not supported with SEV-SNP and will be ignored");
- }
- push $cmd->@*, '-bios', get_ovmf_files($arch, undef, undef, $amd_sev_type);
- } else {
- my $hw_info = {
- 'amd-sev-type' => $amd_sev_type,
- arch => $arch,
- q35 => $q35,
- };
- my ($code_drive_str, $var_drive_str) =
- print_ovmf_drive_commandlines($conf, $storecfg, $vmid, $hw_info, $version_guard);
- push $cmd->@*, '-drive', $code_drive_str;
- push $cmd->@*, '-drive', $var_drive_str;
- }
+ my $hw_info = {
+ 'amd-sev-type' => get_amd_sev_type($conf),
+ arch => $arch,
+ q35 => $q35,
+ };
+ my $ovmf_cmd = PVE::QemuServer::OVMF::print_ovmf_commandline(
+ $conf, $storecfg, $vmid, $hw_info, $version_guard,
+ );
+ push $cmd->@*, $ovmf_cmd->@*;
}
if ($q35) { # tell QEMU to load q35 config early
@@ -8866,8 +8751,8 @@ sub get_efivars_size {
$efidisk //= $conf->{efidisk0} ? parse_drive('efidisk0', $conf->{efidisk0}) : undef;
my $smm = PVE::QemuServer::Machine::machine_type_is_q35($conf);
my $amd_sev_type = get_amd_sev_type($conf);
- my (undef, $ovmf_vars) = get_ovmf_files($arch, $efidisk, $smm, $amd_sev_type);
- return -s $ovmf_vars;
+
+ return PVE::QemuServer::OVMF::get_efivars_size($arch, $efidisk, $smm, $amd_sev_type);
}
sub update_efidisk_size {
@@ -8890,22 +8775,6 @@ sub update_tpmstate_size {
$conf->{tpmstate0} = print_drive($disk);
}
-sub create_efidisk($$$$$$$$) {
- my ($storecfg, $storeid, $vmid, $fmt, $arch, $efidisk, $smm, $amd_sev_type) = @_;
-
- my (undef, $ovmf_vars) = get_ovmf_files($arch, $efidisk, $smm, $amd_sev_type);
-
- my $vars_size_b = -s $ovmf_vars;
- my $vars_size = PVE::Tools::convert_size($vars_size_b, 'b' => 'kb');
- my $volid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid, $fmt, undef, $vars_size);
- PVE::Storage::activate_volumes($storecfg, [$volid]);
-
- PVE::QemuServer::QemuImage::convert($ovmf_vars, $volid, $vars_size_b);
- my $size = PVE::Storage::volume_size_info($storecfg, $volid, 3);
-
- return ($volid, $size / 1024);
-}
-
sub vm_iothreads_list {
my ($vmid) = @_;
diff --git a/src/PVE/QemuServer/Makefile b/src/PVE/QemuServer/Makefile
index a34ec83b..dd6fe505 100644
--- a/src/PVE/QemuServer/Makefile
+++ b/src/PVE/QemuServer/Makefile
@@ -14,6 +14,7 @@ SOURCES=Agent.pm \
Memory.pm \
MetaInfo.pm \
Monitor.pm \
+ OVMF.pm \
PCI.pm \
QemuImage.pm \
QMPHelpers.pm \
diff --git a/src/PVE/QemuServer/OVMF.pm b/src/PVE/QemuServer/OVMF.pm
new file mode 100644
index 00000000..66da21ce
--- /dev/null
+++ b/src/PVE/QemuServer/OVMF.pm
@@ -0,0 +1,166 @@
+package PVE::QemuServer::OVMF;
+
+use strict;
+use warnings;
+
+use PVE::RESTEnvironment qw(log_warn);
+use PVE::Storage;
+use PVE::Tools;
+
+use PVE::QemuServer::Drive qw(checked_volume_format drive_is_read_only parse_drive print_drive);
+use PVE::QemuServer::QemuImage;
+
+my $EDK2_FW_BASE = '/usr/share/pve-edk2-firmware/';
+my $OVMF = {
+ x86_64 => {
+ '4m-no-smm' => [
+ "$EDK2_FW_BASE/OVMF_CODE_4M.fd", "$EDK2_FW_BASE/OVMF_VARS_4M.fd",
+ ],
+ '4m-no-smm-ms' => [
+ "$EDK2_FW_BASE/OVMF_CODE_4M.fd", "$EDK2_FW_BASE/OVMF_VARS_4M.ms.fd",
+ ],
+ '4m' => [
+ "$EDK2_FW_BASE/OVMF_CODE_4M.secboot.fd", "$EDK2_FW_BASE/OVMF_VARS_4M.fd",
+ ],
+ '4m-ms' => [
+ "$EDK2_FW_BASE/OVMF_CODE_4M.secboot.fd", "$EDK2_FW_BASE/OVMF_VARS_4M.ms.fd",
+ ],
+ '4m-sev' => [
+ "$EDK2_FW_BASE/OVMF_CVM_CODE_4M.fd", "$EDK2_FW_BASE/OVMF_CVM_VARS_4M.fd",
+ ],
+ '4m-snp' => [
+ "$EDK2_FW_BASE/OVMF_CVM_4M.fd",
+ ],
+ # FIXME: These are legacy 2MB-sized images that modern OVMF doesn't supports to build
+ # anymore. how can we deperacate this sanely without breaking existing instances, or using
+ # older backups and snapshot?
+ default => [
+ "$EDK2_FW_BASE/OVMF_CODE.fd", "$EDK2_FW_BASE/OVMF_VARS.fd",
+ ],
+ },
+ aarch64 => {
+ default => [
+ "$EDK2_FW_BASE/AAVMF_CODE.fd", "$EDK2_FW_BASE/AAVMF_VARS.fd",
+ ],
+ },
+};
+
+my sub get_ovmf_files($$$$) {
+ my ($arch, $efidisk, $smm, $amd_sev_type) = @_;
+
+ my $types = $OVMF->{$arch}
+ or die "no OVMF images known for architecture '$arch'\n";
+
+ my $type = 'default';
+ if ($arch eq 'x86_64') {
+ if ($amd_sev_type && $amd_sev_type eq 'snp') {
+ $type = "4m-snp";
+ my ($ovmf) = $types->{$type}->@*;
+ die "EFI base image '$ovmf' not found\n" if !-f $ovmf;
+ return ($ovmf);
+ } elsif ($amd_sev_type) {
+ $type = "4m-sev";
+ } elsif (defined($efidisk->{efitype}) && $efidisk->{efitype} eq '4m') {
+ $type = $smm ? "4m" : "4m-no-smm";
+ $type .= '-ms' if $efidisk->{'pre-enrolled-keys'};
+ } else {
+ # TODO: log_warn about use of legacy images for x86_64 with Promxox VE 9
+ }
+ }
+
+ my ($ovmf_code, $ovmf_vars) = $types->{$type}->@*;
+ die "EFI base image '$ovmf_code' not found\n" if !-f $ovmf_code;
+ die "EFI vars image '$ovmf_vars' not found\n" if !-f $ovmf_vars;
+
+ return ($ovmf_code, $ovmf_vars);
+}
+
+my sub print_ovmf_drive_commandlines {
+ my ($conf, $storecfg, $vmid, $hw_info, $version_guard) = @_;
+
+ my ($amd_sev_type, $arch, $q35) = $hw_info->@{qw(amd-sev-type arch q35)};
+
+ my $d = $conf->{efidisk0} ? parse_drive('efidisk0', $conf->{efidisk0}) : undef;
+
+ die "Attempting to configure SEV-SNP with pflash devices instead of using `-bios`\n"
+ if $amd_sev_type && $amd_sev_type eq 'snp';
+
+ my ($ovmf_code, $ovmf_vars) = get_ovmf_files($arch, $d, $q35, $amd_sev_type);
+
+ my $var_drive_str = "if=pflash,unit=1,id=drive-efidisk0";
+ if ($d) {
+ my ($storeid, $volname) = PVE::Storage::parse_volume_id($d->{file}, 1);
+ my ($path, $format) = $d->@{ 'file', 'format' };
+ if ($storeid) {
+ $path = PVE::Storage::path($storecfg, $d->{file});
+ $format //= checked_volume_format($storecfg, $d->{file});
+ } elsif (!defined($format)) {
+ die "efidisk format must be specified\n";
+ }
+ # SPI flash does lots of read-modify-write OPs, without writeback this gets really slow #3329
+ if ($path =~ m/^rbd:/) {
+ $var_drive_str .= ',cache=writeback';
+ $path .= ':rbd_cache_policy=writeback'; # avoid write-around, we *need* to cache writes too
+ }
+ $var_drive_str .= ",format=$format,file=$path";
+
+ $var_drive_str .= ",size=" . (-s $ovmf_vars)
+ if $format eq 'raw' && $version_guard->(4, 1, 2);
+ $var_drive_str .= ',readonly=on' if drive_is_read_only($conf, $d);
+ } else {
+ log_warn("no efidisk configured! Using temporary efivars disk.");
+ my $path = "/tmp/$vmid-ovmf.fd";
+ PVE::Tools::file_copy($ovmf_vars, $path, -s $ovmf_vars);
+ $var_drive_str .= ",format=raw,file=$path";
+ $var_drive_str .= ",size=" . (-s $ovmf_vars) if $version_guard->(4, 1, 2);
+ }
+
+ return ("if=pflash,unit=0,format=raw,readonly=on,file=$ovmf_code", $var_drive_str);
+}
+
+sub get_efivars_size {
+ my ($arch, $efidisk, $smm, $amd_sev_type) = @_;
+
+ my (undef, $ovmf_vars) = get_ovmf_files($arch, $efidisk, $smm, $amd_sev_type);
+ return -s $ovmf_vars;
+}
+
+sub create_efidisk($$$$$$$$) {
+ my ($storecfg, $storeid, $vmid, $fmt, $arch, $efidisk, $smm, $amd_sev_type) = @_;
+
+ my (undef, $ovmf_vars) = get_ovmf_files($arch, $efidisk, $smm, $amd_sev_type);
+
+ my $vars_size_b = -s $ovmf_vars;
+ my $vars_size = PVE::Tools::convert_size($vars_size_b, 'b' => 'kb');
+ my $volid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid, $fmt, undef, $vars_size);
+ PVE::Storage::activate_volumes($storecfg, [$volid]);
+
+ PVE::QemuServer::QemuImage::convert($ovmf_vars, $volid, $vars_size_b);
+ my $size = PVE::Storage::volume_size_info($storecfg, $volid, 3);
+
+ return ($volid, $size / 1024);
+}
+
+sub print_ovmf_commandline {
+ my ($conf, $storecfg, $vmid, $hw_info, $version_guard) = @_;
+
+ my $amd_sev_type = $hw_info->{'amd-sev-type'};
+
+ my $cmd = [];
+
+ if ($amd_sev_type && $amd_sev_type eq 'snp') {
+ if (defined($conf->{efidisk0})) {
+ log_warn("EFI disks are not supported with SEV-SNP and will be ignored");
+ }
+ push $cmd->@*, '-bios', get_ovmf_files($hw_info->{arch}, undef, undef, $amd_sev_type);
+ } else {
+ my ($code_drive_str, $var_drive_str) =
+ print_ovmf_drive_commandlines($conf, $storecfg, $vmid, $hw_info, $version_guard);
+ push $cmd->@*, '-drive', $code_drive_str;
+ push $cmd->@*, '-drive', $var_drive_str;
+ }
+
+ return $cmd;
+}
+
+1;
diff --git a/src/test/MigrationTest/Shared.pm b/src/test/MigrationTest/Shared.pm
index 0b1ac7a0..e29cd1df 100644
--- a/src/test/MigrationTest/Shared.pm
+++ b/src/test/MigrationTest/Shared.pm
@@ -150,6 +150,10 @@ $qemu_server_module->mock(
vm_stop_cleanup => sub {
return;
},
+);
+
+our $qemu_server_ovmf_module = Test::MockModule->new("PVE::QemuServer::OVMF");
+$qemu_server_ovmf_module->mock(
get_efivars_size => sub {
return 128 * 1024;
},
--
2.47.2
More information about the pve-devel
mailing list