mirror of
https://git.proxmox.com/git/qemu-server
synced 2025-04-30 07:39:02 +00:00
217 lines
5.9 KiB
Perl
217 lines
5.9 KiB
Perl
package PVE::QemuServer::Machine;
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
use PVE::QemuServer::Helpers;
|
|
use PVE::QemuServer::Monitor;
|
|
use PVE::JSONSchema qw(get_standard_option parse_property_string print_property_string);
|
|
|
|
# Bump this for VM HW layout changes during a release (where the QEMU machine
|
|
# version stays the same)
|
|
our $PVE_MACHINE_VERSION = {
|
|
'4.1' => 2,
|
|
};
|
|
|
|
my $machine_fmt = {
|
|
type => {
|
|
default_key => 1,
|
|
description => "Specifies the QEMU machine type.",
|
|
type => 'string',
|
|
pattern => '(pc|pc(-i440fx)?-\d+(\.\d+)+(\+pve\d+)?(\.pxe)?|q35|pc-q35-\d+(\.\d+)+(\+pve\d+)?(\.pxe)?|virt(?:-\d+(\.\d+)+)?(\+pve\d+)?)',
|
|
maxLength => 40,
|
|
format_description => 'machine type',
|
|
optional => 1,
|
|
},
|
|
viommu => {
|
|
type => 'string',
|
|
description => "Enable and set guest vIOMMU variant (Intel vIOMMU needs q35 to be set as"
|
|
." machine type).",
|
|
enum => ['intel', 'virtio'],
|
|
optional => 1,
|
|
},
|
|
};
|
|
|
|
PVE::JSONSchema::register_format('pve-qemu-machine-fmt', $machine_fmt);
|
|
|
|
PVE::JSONSchema::register_standard_option('pve-qemu-machine', {
|
|
description => "Specify the QEMU machine.",
|
|
type => 'string',
|
|
optional => 1,
|
|
format => PVE::JSONSchema::get_format('pve-qemu-machine-fmt'),
|
|
});
|
|
|
|
sub parse_machine {
|
|
my ($value) = @_;
|
|
|
|
return if !$value;
|
|
|
|
my $res = parse_property_string($machine_fmt, $value);
|
|
return $res;
|
|
}
|
|
|
|
sub print_machine {
|
|
my ($machine_conf) = @_;
|
|
return print_property_string($machine_conf, $machine_fmt);
|
|
}
|
|
|
|
my $default_machines = {
|
|
x86_64 => 'pc',
|
|
aarch64 => 'virt',
|
|
};
|
|
|
|
sub default_machine_for_arch {
|
|
my ($arch) = @_;
|
|
|
|
my $machine = $default_machines->{$arch} or die "unsupported architecture '$arch'\n";
|
|
return $machine;
|
|
}
|
|
|
|
sub assert_valid_machine_property {
|
|
my ($machine_conf) = @_;
|
|
my $q35 = $machine_conf->{type} && ($machine_conf->{type} =~ m/q35/) ? 1 : 0;
|
|
if ($machine_conf->{viommu} && $machine_conf->{viommu} eq "intel" && !$q35) {
|
|
die "to use Intel vIOMMU please set the machine type to q35\n";
|
|
}
|
|
}
|
|
|
|
sub machine_type_is_q35 {
|
|
my ($conf) = @_;
|
|
|
|
my $machine_conf = parse_machine($conf->{machine});
|
|
return $machine_conf->{type} && ($machine_conf->{type} =~ m/q35/) ? 1 : 0;
|
|
}
|
|
|
|
# In list context, also returns whether the current machine is deprecated or not.
|
|
sub current_from_query_machines {
|
|
my ($machines) = @_;
|
|
|
|
my ($current, $default);
|
|
for my $machine ($machines->@*) {
|
|
$default = $machine->{name} if $machine->{'is-default'};
|
|
|
|
if ($machine->{'is-current'}) {
|
|
$current = $machine->{name};
|
|
# pve-version only exists for the current machine
|
|
$current .= "+$machine->{'pve-version'}" if $machine->{'pve-version'};
|
|
return wantarray ? ($current, $machine->{deprecated} ? 1 : 0) : $current;
|
|
}
|
|
}
|
|
|
|
# fallback to the default machine if current is not supported by qemu - assume never deprecated
|
|
my $fallback = $default || 'pc';
|
|
return wantarray ? ($fallback, 0) : $fallback;
|
|
}
|
|
|
|
# This only works if VM is running.
|
|
# In list context, also returns whether the current machine is deprecated or not.
|
|
sub get_current_qemu_machine {
|
|
my ($vmid) = @_;
|
|
|
|
my $res = PVE::QemuServer::Monitor::mon_cmd($vmid, 'query-machines');
|
|
|
|
return current_from_query_machines($res);
|
|
}
|
|
|
|
# returns a string with major.minor+pve<VERSION>, patch version-part is ignored
|
|
# as it's seldom resembling a real QEMU machine type, so it would be '0' 99% of
|
|
# the time anyway.. This explicitly separates pveversion from the machine.
|
|
sub extract_version {
|
|
my ($machine_type, $kvmversion) = @_;
|
|
|
|
if (defined($machine_type) && $machine_type =~
|
|
m/^(?:pc(?:-i440fx|-q35)?|virt)-(\d+)\.(\d+)(?:\.(\d+))?(\+pve\d+)?(?:\.pxe)?/)
|
|
{
|
|
my $versionstr = "$1.$2";
|
|
$versionstr .= $4 if $4;
|
|
return $versionstr;
|
|
} elsif (defined($kvmversion)) {
|
|
if ($kvmversion =~ m/^(\d+)\.(\d+)/) {
|
|
my $pvever = get_pve_version($kvmversion);
|
|
return "$1.$2+pve$pvever";
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
sub machine_version {
|
|
my ($machine_type, $major, $minor, $pve) = @_;
|
|
|
|
return PVE::QemuServer::Helpers::min_version(
|
|
extract_version($machine_type), $major, $minor, $pve);
|
|
}
|
|
|
|
sub get_pve_version {
|
|
my ($verstr) = @_;
|
|
|
|
if ($verstr =~ m/^(\d+\.\d+)/) {
|
|
return $PVE_MACHINE_VERSION->{$1} // 0;
|
|
}
|
|
|
|
die "internal error: cannot get pve version for invalid string '$verstr'";
|
|
}
|
|
|
|
sub can_run_pve_machine_version {
|
|
my ($machine_version, $kvmversion) = @_;
|
|
|
|
$machine_version =~ m/^(\d+)\.(\d+)(?:\+pve(\d+))?(?:\.pxe)?$/;
|
|
my $major = $1;
|
|
my $minor = $2;
|
|
my $pvever = $3;
|
|
|
|
$kvmversion =~ m/(\d+)\.(\d+)/;
|
|
return 0 if PVE::QemuServer::Helpers::version_cmp($1, $major, $2, $minor) < 0;
|
|
|
|
# if $pvever is missing or 0, we definitely support it as long as we didn't
|
|
# fail the QEMU version check above
|
|
return 1 if !$pvever;
|
|
|
|
my $max_supported = get_pve_version("$major.$minor");
|
|
return 1 if $max_supported >= $pvever;
|
|
|
|
return 0;
|
|
}
|
|
|
|
sub qemu_machine_pxe {
|
|
my ($vmid, $conf) = @_;
|
|
|
|
my $machine = get_current_qemu_machine($vmid);
|
|
|
|
my $machine_conf = parse_machine($conf->{machine});
|
|
if ($machine_conf->{type} && $machine_conf->{type} =~ m/\.pxe$/) {
|
|
$machine .= '.pxe';
|
|
}
|
|
|
|
return $machine;
|
|
}
|
|
|
|
sub get_installed_machine_version {
|
|
my ($kvmversion) = @_;
|
|
$kvmversion = PVE::QemuServer::Helpers::kvm_user_version() if !defined($kvmversion);
|
|
$kvmversion =~ m/^(\d+\.\d+)/;
|
|
return $1;
|
|
}
|
|
|
|
sub windows_get_pinned_machine_version {
|
|
my ($machine, $base_version, $kvmversion) = @_;
|
|
|
|
my $pin_version = $base_version;
|
|
if (!defined($base_version) || !can_run_pve_machine_version($base_version, $kvmversion)) {
|
|
$pin_version = get_installed_machine_version($kvmversion);
|
|
}
|
|
if (!$machine || $machine eq 'pc') {
|
|
$machine = "pc-i440fx-$pin_version";
|
|
} elsif ($machine eq 'q35') {
|
|
$machine = "pc-q35-$pin_version";
|
|
} elsif ($machine eq 'virt') {
|
|
$machine = "virt-$pin_version";
|
|
} else {
|
|
warn "unknown machine type '$machine', not touching that!\n";
|
|
}
|
|
|
|
return $machine;
|
|
}
|
|
|
|
1;
|