diff --git a/PVE/QemuServer/CPUConfig.pm b/PVE/QemuServer/CPUConfig.pm index 9fa6af98..af31b2b0 100644 --- a/PVE/QemuServer/CPUConfig.pm +++ b/PVE/QemuServer/CPUConfig.pm @@ -384,99 +384,190 @@ sub print_cpuflag_hash { return $formatted; } +sub parse_cpuflag_list { + my ($re, $reason, $flaglist) = @_; + + my $res = {}; + return $res if !$flaglist; + + foreach my $flag (split(";", $flaglist)) { + if ($flag =~ $re) { + $res->{$2} = { op => $1, reason => $reason }; + } + } + + return $res; +} + # Calculate QEMU's '-cpu' argument from a given VM configuration sub get_cpu_options { my ($conf, $arch, $kvm, $kvm_off, $machine_version, $winversion, $gpu_passthrough) = @_; - my $cpuFlags = []; - my $ostype = $conf->{ostype}; - - my $cpu = $kvm ? "kvm64" : "qemu64"; + my $cputype = $kvm ? "kvm64" : "qemu64"; if ($arch eq 'aarch64') { - $cpu = 'cortex-a57'; + $cputype = 'cortex-a57'; } + + my $cpu = {}; + my $custom_cpu; 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 (my $cpu_prop_str = $conf->{cpu}) { + $cpu = parse_vm_cpu_conf($cpu_prop_str) + or die "Cannot parse cpu description: $cpu_prop_str\n"; - if (defined(my $flags = $cpuconf->{flags})) { - push @$cpuFlags, split(";", $flags); + $cputype = $cpu->{cputype}; + + if (is_custom_model($cputype)) { + $custom_cpu = get_custom_model($cputype); + + $cputype = $custom_cpu->{'reported-model'} // + $cpu_fmt->{'reported-model'}->{default}; + $kvm_off = $custom_cpu->{hidden} + if defined($custom_cpu->{hidden}); + $hv_vendor_id = $custom_cpu->{'hv-vendor-id'}; } + + # VM-specific settings override custom CPU config + $kvm_off = $cpu->{hidden} + if defined($cpu->{hidden}); + $hv_vendor_id = $cpu->{'hv-vendor-id'} + if defined($cpu->{'hv-vendor-id'}); } - push @$cpuFlags , '+lahf_lm' if $cpu eq 'kvm64' && $arch eq 'x86_64'; + my $pve_flags = get_pve_cpu_flags($conf, $kvm, $cputype, $arch, + $machine_version); - push @$cpuFlags , '-x2apic' if $ostype && $ostype eq 'solaris'; + my $hv_flags = get_hyperv_enlightenments($winversion, $machine_version, + $conf->{bios}, $gpu_passthrough, $hv_vendor_id) if $kvm; - push @$cpuFlags, '+sep' if $cpu eq 'kvm64' || $cpu eq 'kvm32'; + my $custom_cputype_flags = parse_cpuflag_list($cpu_flag_any_re, + "set by custom CPU model", $custom_cpu->{flags}); - push @$cpuFlags, '-rdtscp' if $cpu =~ m/^Opteron/; + my $vm_flags = parse_cpuflag_list($cpu_flag_supported_re, + "manually set for VM", $cpu->{flags}); - if (min_version($machine_version, 2, 3) && $arch eq 'x86_64') { + my $pve_forced_flags = {}; + $pve_forced_flags->{'enforce'} = { + reason => "error if requested CPU settings not available", + } if $cputype ne 'host' && $kvm && $arch eq 'x86_64'; + $pve_forced_flags->{'kvm'} = { + value => "off", + reason => "hide KVM virtualization from guest", + } if $kvm_off; - push @$cpuFlags , '+kvm_pv_unhalt' if $kvm; - push @$cpuFlags , '+kvm_pv_eoi' if $kvm; - } - - add_hyperv_enlightenments($cpuFlags, $winversion, $machine_version, $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'; + # $cputype is the "reported-model" for custom types, so we can just look up + # the vendor in the default list + my $cpu_vendor = $cpu_vendor_list->{$cputype}; + if ($cpu_vendor) { + $pve_forced_flags->{'vendor'} = { + value => $cpu_vendor, + } if $cpu_vendor ne 'default'; } elsif ($arch ne 'aarch64') { die "internal error"; # should not happen } - $cpu .= "," . join(',', @$cpuFlags) if scalar(@$cpuFlags); + my $cpu_str = $cputype; - return ('-cpu', $cpu); + # will be resolved in parameter order + $cpu_str .= resolve_cpu_flags($pve_flags, $hv_flags, $custom_cputype_flags, + $vm_flags, $pve_forced_flags); + + return ('-cpu', $cpu_str); } -sub add_hyperv_enlightenments { - my ($cpuFlags, $winversion, $machine_version, $bios, $gpu_passthrough, $hv_vendor_id) = @_; +# Some hardcoded flags required by certain configurations +sub get_pve_cpu_flags { + my ($conf, $kvm, $cputype, $arch, $machine_version) = @_; + + my $pve_flags = {}; + my $pve_msg = "set by PVE;"; + + $pve_flags->{'lahf_lm'} = { + op => '+', + reason => "$pve_msg to support Windows 8.1+", + } if $cputype eq 'kvm64' && $arch eq 'x86_64'; + + $pve_flags->{'x2apic'} = { + op => '-', + reason => "$pve_msg incompatible with Solaris", + } if $conf->{ostype} && $conf->{ostype} eq 'solaris'; + + $pve_flags->{'sep'} = { + op => '+', + reason => "$pve_msg to support Windows 8+ and improve Windows XP+", + } if $cputype eq 'kvm64' || $cputype eq 'kvm32'; + + $pve_flags->{'rdtscp'} = { + op => '-', + reason => "$pve_msg broken on AMD Opteron", + } if $cputype =~ m/^Opteron/; + + if (min_version($machine_version, 2, 3) && $kvm && $arch eq 'x86_64') { + $pve_flags->{'kvm_pv_unhalt'} = { + op => '+', + reason => "$pve_msg to improve Linux guest spinlock performance", + }; + $pve_flags->{'kvm_pv_eoi'} = { + op => '+', + reason => "$pve_msg to improve Linux guest interrupt performance", + }; + } + + return $pve_flags; +} + +sub get_hyperv_enlightenments { + my ($winversion, $machine_version, $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)) { + my $flags = {}; + my $default_reason = "automatic Hyper-V enlightenment for Windows"; + my $flagfn = sub { + my ($flag, $value, $reason) = @_; + $flags->{$flag} = { + reason => $reason // $default_reason, + value => $value, + } + }; + + my $hv_vendor_set = defined($hv_vendor_id); + if ($gpu_passthrough || $hv_vendor_set) { $hv_vendor_id //= 'proxmox'; - push @$cpuFlags , "hv_vendor_id=$hv_vendor_id"; + $flagfn->('hv_vendor_id', $hv_vendor_id, $hv_vendor_set ? + "custom hv_vendor_id set" : "NVIDIA workaround for GPU passthrough"); } if (min_version($machine_version, 2, 3)) { - push @$cpuFlags , 'hv_spinlocks=0x1fff'; - push @$cpuFlags , 'hv_vapic'; - push @$cpuFlags , 'hv_time'; + $flagfn->('hv_spinlocks', '0x1fff'); + $flagfn->('hv_vapic'); + $flagfn->('hv_time'); } else { - push @$cpuFlags , 'hv_spinlocks=0xffff'; + $flagfn->('hv_spinlocks', '0xffff'); } if (min_version($machine_version, 2, 6)) { - push @$cpuFlags , 'hv_reset'; - push @$cpuFlags , 'hv_vpindex'; - push @$cpuFlags , 'hv_runtime'; + $flagfn->('hv_reset'); + $flagfn->('hv_vpindex'); + $flagfn->('hv_runtime'); } if ($winversion >= 7) { - push @$cpuFlags , 'hv_relaxed'; + my $win7_reason = $default_reason . " 7 and higher"; + $flagfn->('hv_relaxed', undef, $win7_reason); if (min_version($machine_version, 2, 12)) { - push @$cpuFlags , 'hv_synic'; - push @$cpuFlags , 'hv_stimer'; + $flagfn->('hv_synic', undef, $win7_reason); + $flagfn->('hv_stimer', undef, $win7_reason); } if (min_version($machine_version, 3, 1)) { - push @$cpuFlags , 'hv_ipi'; + $flagfn->('hv_ipi', undef, $win7_reason); } } + + return $flags; } sub get_cpu_from_running_vm { diff --git a/test/cfg2cmd/efi-raw-old.conf.cmd b/test/cfg2cmd/efi-raw-old.conf.cmd index 5526a489..666cdb17 100644 --- a/test/cfg2cmd/efi-raw-old.conf.cmd +++ b/test/cfg2cmd/efi-raw-old.conf.cmd @@ -14,7 +14,7 @@ -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 \ - -cpu kvm64,+lahf_lm,+sep,+kvm_pv_unhalt,+kvm_pv_eoi,enforce \ + -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' \ diff --git a/test/cfg2cmd/efi-raw.conf.cmd b/test/cfg2cmd/efi-raw.conf.cmd index a3369327..cb0e984b 100644 --- a/test/cfg2cmd/efi-raw.conf.cmd +++ b/test/cfg2cmd/efi-raw.conf.cmd @@ -14,7 +14,7 @@ -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 \ - -cpu kvm64,+lahf_lm,+sep,+kvm_pv_unhalt,+kvm_pv_eoi,enforce \ + -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' \ diff --git a/test/cfg2cmd/i440fx-win10-hostpci.conf.cmd b/test/cfg2cmd/i440fx-win10-hostpci.conf.cmd index 43741ae9..2fe34a1d 100644 --- a/test/cfg2cmd/i440fx-win10-hostpci.conf.cmd +++ b/test/cfg2cmd/i440fx-win10-hostpci.conf.cmd @@ -15,7 +15,7 @@ -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \ -vnc unix:/var/run/qemu-server/8006.vnc,password \ -no-hpet \ - -cpu 'kvm64,+lahf_lm,+sep,+kvm_pv_unhalt,+kvm_pv_eoi,hv_spinlocks=0x1fff,hv_vapic,hv_time,hv_reset,hv_vpindex,hv_runtime,hv_relaxed,hv_synic,hv_stimer,hv_ipi,enforce' \ + -cpu 'kvm64,enforce,hv_ipi,hv_relaxed,hv_reset,hv_runtime,hv_spinlocks=0x1fff,hv_stimer,hv_synic,hv_time,hv_vapic,hv_vpindex,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep' \ -m 512 \ -object 'memory-backend-ram,id=ram-node0,size=256M' \ -numa 'node,nodeid=0,cpus=0,memdev=ram-node0' \ diff --git a/test/cfg2cmd/minimal-defaults.conf.cmd b/test/cfg2cmd/minimal-defaults.conf.cmd index c499a859..0735f43e 100644 --- a/test/cfg2cmd/minimal-defaults.conf.cmd +++ b/test/cfg2cmd/minimal-defaults.conf.cmd @@ -12,7 +12,7 @@ -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 \ - -cpu kvm64,+lahf_lm,+sep,+kvm_pv_unhalt,+kvm_pv_eoi,enforce \ + -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' \ diff --git a/test/cfg2cmd/pinned-version.conf.cmd b/test/cfg2cmd/pinned-version.conf.cmd index 2fc31fc7..a7d0ae21 100644 --- a/test/cfg2cmd/pinned-version.conf.cmd +++ b/test/cfg2cmd/pinned-version.conf.cmd @@ -12,7 +12,7 @@ -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 \ - -cpu kvm64,+lahf_lm,+sep,+kvm_pv_unhalt,+kvm_pv_eoi,enforce \ + -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \ -m 1024 \ -readconfig /usr/share/qemu-server/pve-q35.cfg \ -device 'vmgenid,guid=bdd46b98-fefc-11e9-97b4-d72c378e0f96' \ diff --git a/test/cfg2cmd/q35-linux-hostpci-multifunction.conf.cmd b/test/cfg2cmd/q35-linux-hostpci-multifunction.conf.cmd index c766d338..a008939e 100644 --- a/test/cfg2cmd/q35-linux-hostpci-multifunction.conf.cmd +++ b/test/cfg2cmd/q35-linux-hostpci-multifunction.conf.cmd @@ -14,7 +14,7 @@ -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 \ - -cpu kvm64,+lahf_lm,+sep,+kvm_pv_unhalt,+kvm_pv_eoi,enforce \ + -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \ -m 512 \ -object 'memory-backend-ram,id=ram-node0,size=256M' \ -numa 'node,nodeid=0,cpus=0,memdev=ram-node0' \ diff --git a/test/cfg2cmd/q35-linux-hostpci.conf.cmd b/test/cfg2cmd/q35-linux-hostpci.conf.cmd index 7059b298..7e829ae2 100644 --- a/test/cfg2cmd/q35-linux-hostpci.conf.cmd +++ b/test/cfg2cmd/q35-linux-hostpci.conf.cmd @@ -14,7 +14,7 @@ -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 \ - -cpu kvm64,+lahf_lm,+sep,+kvm_pv_unhalt,+kvm_pv_eoi,enforce \ + -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \ -m 512 \ -object 'memory-backend-ram,id=ram-node0,size=256M' \ -numa 'node,nodeid=0,cpus=0,memdev=ram-node0' \ diff --git a/test/cfg2cmd/q35-win10-hostpci.conf.cmd b/test/cfg2cmd/q35-win10-hostpci.conf.cmd index 3397324b..133c0863 100644 --- a/test/cfg2cmd/q35-win10-hostpci.conf.cmd +++ b/test/cfg2cmd/q35-win10-hostpci.conf.cmd @@ -15,7 +15,7 @@ -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \ -vnc unix:/var/run/qemu-server/8006.vnc,password \ -no-hpet \ - -cpu 'kvm64,+lahf_lm,+sep,+kvm_pv_unhalt,+kvm_pv_eoi,hv_spinlocks=0x1fff,hv_vapic,hv_time,hv_reset,hv_vpindex,hv_runtime,hv_relaxed,hv_synic,hv_stimer,hv_ipi,enforce' \ + -cpu 'kvm64,enforce,hv_ipi,hv_relaxed,hv_reset,hv_runtime,hv_spinlocks=0x1fff,hv_stimer,hv_synic,hv_time,hv_vapic,hv_vpindex,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep' \ -m 512 \ -object 'memory-backend-ram,id=ram-node0,size=256M' \ -numa 'node,nodeid=0,cpus=0,memdev=ram-node0' \ diff --git a/test/cfg2cmd/simple1.conf.cmd b/test/cfg2cmd/simple1.conf.cmd index b5c06cf4..34850644 100644 --- a/test/cfg2cmd/simple1.conf.cmd +++ b/test/cfg2cmd/simple1.conf.cmd @@ -12,7 +12,7 @@ -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 \ - -cpu kvm64,+lahf_lm,+sep,+kvm_pv_unhalt,+kvm_pv_eoi,enforce \ + -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \ -m 768 \ -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' \ diff --git a/test/cfg2cmd/spice-enhancments.conf.cmd b/test/cfg2cmd/spice-enhancments.conf.cmd index 2d49b3a2..3951c065 100644 --- a/test/cfg2cmd/spice-enhancments.conf.cmd +++ b/test/cfg2cmd/spice-enhancments.conf.cmd @@ -12,7 +12,7 @@ -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 \ - -cpu kvm64,+lahf_lm,+sep,+kvm_pv_unhalt,+kvm_pv_eoi,enforce \ + -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' \ diff --git a/test/cfg2cmd/spice-linux-4.1.conf.cmd b/test/cfg2cmd/spice-linux-4.1.conf.cmd index 6aa781b9..2748cc99 100644 --- a/test/cfg2cmd/spice-linux-4.1.conf.cmd +++ b/test/cfg2cmd/spice-linux-4.1.conf.cmd @@ -12,7 +12,7 @@ -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 \ - -cpu kvm64,+lahf_lm,+sep,+kvm_pv_unhalt,+kvm_pv_eoi,enforce \ + -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \ -m 768 \ -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' \ diff --git a/test/cfg2cmd/spice-usb3.conf.cmd b/test/cfg2cmd/spice-usb3.conf.cmd index 627c0777..c5156444 100644 --- a/test/cfg2cmd/spice-usb3.conf.cmd +++ b/test/cfg2cmd/spice-usb3.conf.cmd @@ -12,7 +12,7 @@ -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 \ - -cpu kvm64,+lahf_lm,+sep,+kvm_pv_unhalt,+kvm_pv_eoi,enforce \ + -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \ -m 768 \ -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' \ diff --git a/test/cfg2cmd/spice-win.conf.cmd b/test/cfg2cmd/spice-win.conf.cmd index 6f943584..22dfa9d7 100644 --- a/test/cfg2cmd/spice-win.conf.cmd +++ b/test/cfg2cmd/spice-win.conf.cmd @@ -13,7 +13,7 @@ -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \ -vnc unix:/var/run/qemu-server/8006.vnc,password \ -no-hpet \ - -cpu 'kvm64,+lahf_lm,+sep,+kvm_pv_unhalt,+kvm_pv_eoi,hv_spinlocks=0x1fff,hv_vapic,hv_time,hv_reset,hv_vpindex,hv_runtime,hv_relaxed,hv_synic,hv_stimer,hv_ipi,enforce' \ + -cpu 'kvm64,enforce,hv_ipi,hv_relaxed,hv_reset,hv_runtime,hv_spinlocks=0x1fff,hv_stimer,hv_synic,hv_time,hv_vapic,hv_vpindex,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep' \ -m 768 \ -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' \