The package will be used for custom CPU models as a SectionConfig, hence the name. For now we simply move some CPU related helper functions and declarations over from QemuServer to reduce clutter there.
Signed-off-by: Stefan Reiter <s.rei...@proxmox.com> --- PVE/QemuServer.pm | 242 +--------------------------------- PVE/QemuServer/CPUConfig.pm | 251 ++++++++++++++++++++++++++++++++++++ PVE/QemuServer/Makefile | 1 + 3 files changed, 256 insertions(+), 238 deletions(-) create mode 100644 PVE/QemuServer/CPUConfig.pm diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm index 20c1061..a95006f 100644 --- a/PVE/QemuServer.pm +++ b/PVE/QemuServer.pm @@ -33,6 +33,7 @@ use PVE::QemuConfig; use PVE::QMPClient; use PVE::RPCEnvironment; use PVE::GuestHelpers; +use PVE::QemuServer::CPUConfig qw(qemu_machine_feature_enabled print_cpu_device get_cpu_options); use PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr print_pcie_root_port); use PVE::QemuServer::Memory; use PVE::QemuServer::USB qw(parse_usb_device); @@ -116,105 +117,6 @@ mkdir $var_run_tmpdir; my $lock_dir = "/var/lock/qemu-server"; mkdir $lock_dir; -my $cpu_vendor_list = { - # Intel CPUs - 486 => 'GenuineIntel', - pentium => 'GenuineIntel', - pentium2 => 'GenuineIntel', - pentium3 => 'GenuineIntel', - coreduo => 'GenuineIntel', - core2duo => 'GenuineIntel', - Conroe => 'GenuineIntel', - Penryn => 'GenuineIntel', - Nehalem => 'GenuineIntel', - 'Nehalem-IBRS' => 'GenuineIntel', - Westmere => 'GenuineIntel', - 'Westmere-IBRS' => 'GenuineIntel', - SandyBridge => 'GenuineIntel', - 'SandyBridge-IBRS' => 'GenuineIntel', - IvyBridge => 'GenuineIntel', - 'IvyBridge-IBRS' => 'GenuineIntel', - Haswell => 'GenuineIntel', - 'Haswell-IBRS' => 'GenuineIntel', - 'Haswell-noTSX' => 'GenuineIntel', - 'Haswell-noTSX-IBRS' => 'GenuineIntel', - Broadwell => 'GenuineIntel', - 'Broadwell-IBRS' => 'GenuineIntel', - 'Broadwell-noTSX' => 'GenuineIntel', - 'Broadwell-noTSX-IBRS' => 'GenuineIntel', - 'Skylake-Client' => 'GenuineIntel', - 'Skylake-Client-IBRS' => 'GenuineIntel', - 'Skylake-Server' => 'GenuineIntel', - 'Skylake-Server-IBRS' => 'GenuineIntel', - - # AMD CPUs - athlon => 'AuthenticAMD', - phenom => 'AuthenticAMD', - Opteron_G1 => 'AuthenticAMD', - Opteron_G2 => 'AuthenticAMD', - Opteron_G3 => 'AuthenticAMD', - Opteron_G4 => 'AuthenticAMD', - Opteron_G5 => 'AuthenticAMD', - EPYC => 'AuthenticAMD', - 'EPYC-IBPB' => 'AuthenticAMD', - - # generic types, use vendor from host node - host => 'default', - kvm32 => 'default', - kvm64 => 'default', - qemu32 => 'default', - qemu64 => 'default', - max => 'default', -}; - -my @supported_cpu_flags = ( - 'pcid', - 'spec-ctrl', - 'ibpb', - 'ssbd', - 'virt-ssbd', - 'amd-ssbd', - 'amd-no-ssb', - 'pdpe1gb', - 'md-clear', - 'hv-tlbflush', - 'hv-evmcs', - 'aes' -); -my $cpu_flag = qr/[+-](@{[join('|', @supported_cpu_flags)]})/; - -my $cpu_fmt = { - cputype => { - description => "Emulated CPU type.", - type => 'string', - enum => [ sort { "\L$a" cmp "\L$b" } keys %$cpu_vendor_list ], - default => 'kvm64', - default_key => 1, - }, - hidden => { - description => "Do not identify as a KVM virtual machine.", - type => 'boolean', - optional => 1, - default => 0 - }, - 'hv-vendor-id' => { - type => 'string', - pattern => qr/[a-zA-Z0-9]{1,12}/, - format_description => 'vendor-id', - description => 'The Hyper-V vendor ID. Some drivers or programs inside Windows guests need a specific ID.', - optional => 1, - }, - flags => { - description => "List of additional CPU flags separated by ';'." - . " Use '+FLAG' to enable, '-FLAG' to disable a flag." - . " Currently supported flags: @{[join(', ', @supported_cpu_flags)]}.", - format_description => '+FLAG[;-FLAG...]', - type => 'string', - pattern => qr/$cpu_flag(;$cpu_flag)*/, - optional => 1, - }, -}; - my $watchdog_fmt = { model => { default_key => 1, @@ -603,7 +505,7 @@ EODESCR optional => 1, description => "Emulated CPU type.", type => 'string', - format => $cpu_fmt, + format => $PVE::QemuServer::CPUConfig::cpu_fmt, }, parent => get_standard_option('pve-snapshot-name', { optional => 1, @@ -2133,26 +2035,6 @@ sub print_netdev_full { return $netdev; } - -sub print_cpu_device { - my ($conf, $id) = @_; - - my $kvm = $conf->{kvm} // 1; - my $cpu = $kvm ? "kvm64" : "qemu64"; - if (my $cputype = $conf->{cpu}) { - my $cpuconf = PVE::JSONSchema::parse_property_string($cpu_fmt, $cputype) - or die "Cannot parse cpu description: $cputype\n"; - $cpu = $cpuconf->{cputype}; - } - - my $cores = $conf->{cores} || 1; - - my $current_core = ($id - 1) % $cores; - my $current_socket = int(($id - 1 - $current_core)/$cores); - - return "$cpu-x86_64-cpu,id=cpu$id,socket-id=$current_socket,core-id=$current_core,thread-id=0"; -} - my $vga_map = { 'cirrus' => 'cirrus-vga', 'std' => 'VGA', @@ -3643,62 +3525,6 @@ sub query_understood_cpu_flags { return $flags; } -sub get_cpu_options { - my ($conf, $arch, $kvm, $machine_type, $kvm_off, $kvmver, $winversion, $gpu_passthrough) = @_; - - my $cpuFlags = []; - my $ostype = $conf->{ostype}; - - my $cpu = $kvm ? "kvm64" : "qemu64"; - if ($arch eq 'aarch64') { - $cpu = 'cortex-a57'; - } - my $hv_vendor_id; - if (my $cputype = $conf->{cpu}) { - my $cpuconf = PVE::JSONSchema::parse_property_string($cpu_fmt, $cputype) - or die "Cannot parse cpu description: $cputype\n"; - $cpu = $cpuconf->{cputype}; - $kvm_off = 1 if $cpuconf->{hidden}; - $hv_vendor_id = $cpuconf->{'hv-vendor-id'}; - - if (defined(my $flags = $cpuconf->{flags})) { - push @$cpuFlags, split(";", $flags); - } - } - - push @$cpuFlags , '+lahf_lm' if $cpu eq 'kvm64' && $arch eq 'x86_64'; - - push @$cpuFlags , '-x2apic' - if $conf->{ostype} && $conf->{ostype} eq 'solaris'; - - push @$cpuFlags, '+sep' if $cpu eq 'kvm64' || $cpu eq 'kvm32'; - - push @$cpuFlags, '-rdtscp' if $cpu =~ m/^Opteron/; - - if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 3) && $arch eq 'x86_64') { - - push @$cpuFlags , '+kvm_pv_unhalt' if $kvm; - push @$cpuFlags , '+kvm_pv_eoi' if $kvm; - } - - add_hyperv_enlightenments($cpuFlags, $winversion, $machine_type, $kvmver, $conf->{bios}, $gpu_passthrough, $hv_vendor_id) if $kvm; - - push @$cpuFlags, 'enforce' if $cpu ne 'host' && $kvm && $arch eq 'x86_64'; - - push @$cpuFlags, 'kvm=off' if $kvm_off; - - if (my $cpu_vendor = $cpu_vendor_list->{$cpu}) { - push @$cpuFlags, "vendor=${cpu_vendor}" - if $cpu_vendor ne 'default'; - } elsif ($arch ne 'aarch64') { - die "internal error"; # should not happen - } - - $cpu .= "," . join(',', @$cpuFlags) if scalar(@$cpuFlags); - - return ('-cpu', $cpu); -} - sub config_to_command { my ($storecfg, $vmid, $conf, $defaults, $forcemachine) = @_; @@ -4084,7 +3910,8 @@ sub config_to_command { push @$rtcFlags, 'base=localtime'; } - push @$cmd, get_cpu_options($conf, $arch, $kvm, $machine_type, $kvm_off, $kvmver, $winversion, $gpu_passthrough); + push @$cmd, get_cpu_options($conf, $arch, $kvm, + $machine_type, $kvm_off, $kvmver, $winversion, $gpu_passthrough); PVE::QemuServer::Memory::config($conf, $vmid, $sockets, $cores, $defaults, $hotplug_features, $cmd); @@ -7319,28 +7146,6 @@ sub get_running_qemu_version { return "$res->{qemu}->{major}.$res->{qemu}->{minor}"; } -sub qemu_machine_feature_enabled { - my ($machine, $kvmver, $version_major, $version_minor) = @_; - - my $current_major; - my $current_minor; - - if ($machine && $machine =~ m/^((?:pc(-i440fx|-q35)?|virt)-(\d+)\.(\d+))/) { - - $current_major = $3; - $current_minor = $4; - - } elsif ($kvmver =~ m/^(\d+)\.(\d+)/) { - - $current_major = $1; - $current_minor = $2; - } - - return 1 if $current_major > $version_major || - ($current_major == $version_major && - $current_minor >= $version_minor); -} - sub qemu_machine_pxe { my ($vmid, $conf) = @_; @@ -7426,45 +7231,6 @@ sub scsihw_infos { return ($maxdev, $controller, $controller_prefix); } -sub add_hyperv_enlightenments { - my ($cpuFlags, $winversion, $machine_type, $kvmver, $bios, $gpu_passthrough, $hv_vendor_id) = @_; - - return if $winversion < 6; - return if $bios && $bios eq 'ovmf' && $winversion < 8; - - if ($gpu_passthrough || defined($hv_vendor_id)) { - $hv_vendor_id //= 'proxmox'; - push @$cpuFlags , "hv_vendor_id=$hv_vendor_id"; - } - - if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 3)) { - push @$cpuFlags , 'hv_spinlocks=0x1fff'; - push @$cpuFlags , 'hv_vapic'; - push @$cpuFlags , 'hv_time'; - } else { - push @$cpuFlags , 'hv_spinlocks=0xffff'; - } - - if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 6)) { - push @$cpuFlags , 'hv_reset'; - push @$cpuFlags , 'hv_vpindex'; - push @$cpuFlags , 'hv_runtime'; - } - - if ($winversion >= 7) { - push @$cpuFlags , 'hv_relaxed'; - - if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 12)) { - push @$cpuFlags , 'hv_synic'; - push @$cpuFlags , 'hv_stimer'; - } - - if (qemu_machine_feature_enabled ($machine_type, $kvmver, 3, 1)) { - push @$cpuFlags , 'hv_ipi'; - } - } -} - sub windows_version { my ($ostype) = @_; diff --git a/PVE/QemuServer/CPUConfig.pm b/PVE/QemuServer/CPUConfig.pm new file mode 100644 index 0000000..c994228 --- /dev/null +++ b/PVE/QemuServer/CPUConfig.pm @@ -0,0 +1,251 @@ +package PVE::QemuServer::CPUConfig; + +use strict; +use warnings; +use PVE::JSONSchema; + +use base 'Exporter'; +our @EXPORT_OK = qw( +print_cpu_device +get_cpu_options +qemu_machine_feature_enabled +); + +my $cpu_vendor_list = { + # Intel CPUs + 486 => 'GenuineIntel', + pentium => 'GenuineIntel', + pentium2 => 'GenuineIntel', + pentium3 => 'GenuineIntel', + coreduo => 'GenuineIntel', + core2duo => 'GenuineIntel', + Conroe => 'GenuineIntel', + Penryn => 'GenuineIntel', + Nehalem => 'GenuineIntel', + 'Nehalem-IBRS' => 'GenuineIntel', + Westmere => 'GenuineIntel', + 'Westmere-IBRS' => 'GenuineIntel', + SandyBridge => 'GenuineIntel', + 'SandyBridge-IBRS' => 'GenuineIntel', + IvyBridge => 'GenuineIntel', + 'IvyBridge-IBRS' => 'GenuineIntel', + Haswell => 'GenuineIntel', + 'Haswell-IBRS' => 'GenuineIntel', + 'Haswell-noTSX' => 'GenuineIntel', + 'Haswell-noTSX-IBRS' => 'GenuineIntel', + Broadwell => 'GenuineIntel', + 'Broadwell-IBRS' => 'GenuineIntel', + 'Broadwell-noTSX' => 'GenuineIntel', + 'Broadwell-noTSX-IBRS' => 'GenuineIntel', + 'Skylake-Client' => 'GenuineIntel', + 'Skylake-Client-IBRS' => 'GenuineIntel', + 'Skylake-Server' => 'GenuineIntel', + 'Skylake-Server-IBRS' => 'GenuineIntel', + + # AMD CPUs + athlon => 'AuthenticAMD', + phenom => 'AuthenticAMD', + Opteron_G1 => 'AuthenticAMD', + Opteron_G2 => 'AuthenticAMD', + Opteron_G3 => 'AuthenticAMD', + Opteron_G4 => 'AuthenticAMD', + Opteron_G5 => 'AuthenticAMD', + EPYC => 'AuthenticAMD', + 'EPYC-IBPB' => 'AuthenticAMD', + + # generic types, use vendor from host node + host => 'default', + kvm32 => 'default', + kvm64 => 'default', + qemu32 => 'default', + qemu64 => 'default', + max => 'default', +}; + +my @supported_cpu_flags = ( + 'pcid', + 'spec-ctrl', + 'ibpb', + 'ssbd', + 'virt-ssbd', + 'amd-ssbd', + 'amd-no-ssb', + 'pdpe1gb', + 'md-clear', + 'hv-tlbflush', + 'hv-evmcs', + 'aes' +); +my $cpu_flag = qr/[+-](@{[join('|', @supported_cpu_flags)]})/; + +our $cpu_fmt = { + cputype => { + description => "Emulated CPU type.", + type => 'string', + enum => [ sort { "\L$a" cmp "\L$b" } keys %$cpu_vendor_list ], + default => 'kvm64', + default_key => 1, + }, + hidden => { + description => "Do not identify as a KVM virtual machine.", + type => 'boolean', + optional => 1, + default => 0 + }, + 'hv-vendor-id' => { + type => 'string', + pattern => qr/[a-zA-Z0-9]{1,12}/, + format_description => 'vendor-id', + description => 'The Hyper-V vendor ID. Some drivers or programs inside Windows guests need a specific ID.', + optional => 1, + }, + flags => { + description => "List of additional CPU flags separated by ';'." + . " Use '+FLAG' to enable, '-FLAG' to disable a flag." + . " Currently supported flags: @{[join(', ', @supported_cpu_flags)]}.", + format_description => '+FLAG[;-FLAG...]', + type => 'string', + pattern => qr/$cpu_flag(;$cpu_flag)*/, + optional => 1, + }, +}; + +# Print a QEMU device node for a given VM configuration for hotplugging CPUs +sub print_cpu_device { + my ($conf, $id) = @_; + + my $kvm = $conf->{kvm} // 1; + my $cpu = $kvm ? "kvm64" : "qemu64"; + if (my $cputype = $conf->{cpu}) { + my $cpuconf = PVE::JSONSchema::parse_property_string($cpu_fmt, $cputype) + or die "Cannot parse cpu description: $cputype\n"; + $cpu = $cpuconf->{cputype}; + } + + my $cores = $conf->{cores} || 1; + + my $current_core = ($id - 1) % $cores; + my $current_socket = int(($id - 1 - $current_core)/$cores); + + return "$cpu-x86_64-cpu,id=cpu$id,socket-id=$current_socket,core-id=$current_core,thread-id=0"; +} + +# Calculate QEMU's '-cpu' argument from a given VM configuration +sub get_cpu_options { + my ($conf, $arch, $kvm, $machine_type, $kvm_off, $kvmver, $winversion, $gpu_passthrough) = @_; + + my $cpuFlags = []; + my $ostype = $conf->{ostype}; + + my $cpu = $kvm ? "kvm64" : "qemu64"; + if ($arch eq 'aarch64') { + $cpu = 'cortex-a57'; + } + my $hv_vendor_id; + if (my $cputype = $conf->{cpu}) { + my $cpuconf = PVE::JSONSchema::parse_property_string($cpu_fmt, $cputype) + or die "Cannot parse cpu description: $cputype\n"; + $cpu = $cpuconf->{cputype}; + $kvm_off = 1 if $cpuconf->{hidden}; + $hv_vendor_id = $cpuconf->{'hv-vendor-id'}; + + if (defined(my $flags = $cpuconf->{flags})) { + push @$cpuFlags, split(";", $flags); + } + } + + push @$cpuFlags , '+lahf_lm' if $cpu eq 'kvm64' && $arch eq 'x86_64'; + + push @$cpuFlags , '-x2apic' + if $conf->{ostype} && $conf->{ostype} eq 'solaris'; + + push @$cpuFlags, '+sep' if $cpu eq 'kvm64' || $cpu eq 'kvm32'; + + push @$cpuFlags, '-rdtscp' if $cpu =~ m/^Opteron/; + + if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 3) && $arch eq 'x86_64') { + + push @$cpuFlags , '+kvm_pv_unhalt' if $kvm; + push @$cpuFlags , '+kvm_pv_eoi' if $kvm; + } + + add_hyperv_enlightenments($cpuFlags, $winversion, $machine_type, $kvmver, $conf->{bios}, $gpu_passthrough, $hv_vendor_id) if $kvm; + + push @$cpuFlags, 'enforce' if $cpu ne 'host' && $kvm && $arch eq 'x86_64'; + + push @$cpuFlags, 'kvm=off' if $kvm_off; + + if (my $cpu_vendor = $cpu_vendor_list->{$cpu}) { + push @$cpuFlags, "vendor=${cpu_vendor}" + if $cpu_vendor ne 'default'; + } elsif ($arch ne 'aarch64') { + die "internal error"; # should not happen + } + + $cpu .= "," . join(',', @$cpuFlags) if scalar(@$cpuFlags); + + return ('-cpu', $cpu); +} + +sub add_hyperv_enlightenments { + my ($cpuFlags, $winversion, $machine_type, $kvmver, $bios, $gpu_passthrough, $hv_vendor_id) = @_; + + return if $winversion < 6; + return if $bios && $bios eq 'ovmf' && $winversion < 8; + + if ($gpu_passthrough || defined($hv_vendor_id)) { + $hv_vendor_id //= 'proxmox'; + push @$cpuFlags , "hv_vendor_id=$hv_vendor_id"; + } + + if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 3)) { + push @$cpuFlags , 'hv_spinlocks=0x1fff'; + push @$cpuFlags , 'hv_vapic'; + push @$cpuFlags , 'hv_time'; + } else { + push @$cpuFlags , 'hv_spinlocks=0xffff'; + } + + if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 6)) { + push @$cpuFlags , 'hv_reset'; + push @$cpuFlags , 'hv_vpindex'; + push @$cpuFlags , 'hv_runtime'; + } + + if ($winversion >= 7) { + push @$cpuFlags , 'hv_relaxed'; + + if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 12)) { + push @$cpuFlags , 'hv_synic'; + push @$cpuFlags , 'hv_stimer'; + } + + if (qemu_machine_feature_enabled ($machine_type, $kvmver, 3, 1)) { + push @$cpuFlags , 'hv_ipi'; + } + } +} + +sub qemu_machine_feature_enabled { + my ($machine, $kvmver, $version_major, $version_minor) = @_; + + my $current_major; + my $current_minor; + + if ($machine && $machine =~ m/^((?:pc(-i440fx|-q35)?|virt)-(\d+)\.(\d+))/) { + + $current_major = $3; + $current_minor = $4; + + } elsif ($kvmver =~ m/^(\d+)\.(\d+)/) { + + $current_major = $1; + $current_minor = $2; + } + + return 1 if $current_major > $version_major || + ($current_major == $version_major && + $current_minor >= $version_minor); +} + +1; diff --git a/PVE/QemuServer/Makefile b/PVE/QemuServer/Makefile index afc39a3..a3054f1 100644 --- a/PVE/QemuServer/Makefile +++ b/PVE/QemuServer/Makefile @@ -5,6 +5,7 @@ SOURCES=PCI.pm \ OVF.pm \ Cloudinit.pm \ Agent.pm \ + CPUConfig.pm \ .PHONY: install install: ${SOURCES} -- 2.20.1 _______________________________________________ pve-devel mailing list pve-devel@pve.proxmox.com https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel