[pve-devel] [PATCH v3 qemu-server 09/11] Rework get_cpu_options and allow custom CPU models

Stefan Reiter s.reiter at proxmox.com
Thu Oct 3 16:56:24 CEST 2019


If the "built-in" property on the cpu config is not set, try to load the
cputype from the custom CPU model config, and set values accordingly.

While at it, extract currently hardcoded values into seperate sub and add
reasonings.

Since the new flag resolving outputs flags in sorted order for
consistency, adapt the test cases to not break. Only the order is
changed, not which flags are present.

Signed-off-by: Stefan Reiter <s.reiter at proxmox.com>
---

v3: Since it's just a few lines, I included the test changes into this patch
directly. This way all patches in the series should be buildable without
problems.

v2: It was quite interesting to dig through old commit messages/mail archives to
find the actual reasons some of the hardcoded flags are included. I do feel like
the "reason" field is quite useful though, both for future developers and users.

 PVE/QemuServer/CPUConfig.pm                | 189 +++++++++++++++------
 test/cfg2cmd/i440fx-win10-hostpci.conf.cmd |   2 +-
 test/cfg2cmd/minimal-defaults.conf.cmd     |   2 +-
 test/cfg2cmd/q35-linux-hostpci.conf.cmd    |   2 +-
 test/cfg2cmd/q35-win10-hostpci.conf.cmd    |   2 +-
 test/cfg2cmd/simple1.conf.cmd              |   2 +-
 test/cfg2cmd/spice-usb3.conf.cmd           |   2 +-
 7 files changed, 147 insertions(+), 54 deletions(-)

diff --git a/PVE/QemuServer/CPUConfig.pm b/PVE/QemuServer/CPUConfig.pm
index 6924b5b..6e2ae80 100644
--- a/PVE/QemuServer/CPUConfig.pm
+++ b/PVE/QemuServer/CPUConfig.pm
@@ -172,7 +172,8 @@ sub verify_vm_cpu_conf {
 
     my $cputype = $cpu->{cputype};
 
-    if (!$cpu->{'built-in'}) {
+    # if undefined, we assume built-in = 1
+    if (defined($cpu->{'built-in'}) && !$cpu->{'built-in'}) {
 	# Custom model only has to exist, all props are allowed
 	my $config = load_custom_model_conf();
 	return $cpu if $config && defined($config->{ids}->{$cputype});
@@ -369,96 +370,188 @@ sub print_cpuflag_hash {
 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";
+    my $cputype = $kvm ? "kvm64" : "qemu64";
     if ($arch eq 'aarch64') {
-	$cpu = 'cortex-a57';
+	$cputype = 'cortex-a57';
     }
+
+    my $cpuconf = {};
+    my $custom_cpuconf;
     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}) {
+	$cpuconf = verify_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 = $cpuconf->{cputype};
 
-    push @$cpuFlags , '+lahf_lm' if $cpu eq 'kvm64' && $arch eq 'x86_64';
+	# if undefined, we assume built-in = 1
+	if (defined($cpuconf->{'built-in'}) && !$cpuconf->{'built-in'}) {
+	    my $custom_conf = load_custom_model_conf();
+	    $custom_cpuconf = get_model_by_name($custom_conf, $cputype)
+		or die "invalid custom model definition for '$cputype'\n";
 
-    push @$cpuFlags , '-x2apic'
-	if $conf->{ostype} && $conf->{ostype} eq 'solaris';
+	    $cputype = $custom_cpuconf->{'reported-model'};
+	    $kvm_off = $custom_cpuconf->{hidden};
+	    $hv_vendor_id = $custom_cpuconf->{'hv-vendor-id'};
+	}
 
-    push @$cpuFlags, '+sep' if $cpu eq 'kvm64' || $cpu eq 'kvm32';
+	# VM-specific settings override custom CPU config
+	$kvm_off = $cpuconf->{hidden}
+	    if defined($cpuconf->{hidden});
+	$hv_vendor_id = $cpuconf->{'hv-vendor-id'}
+	    if defined($cpuconf->{'hv-vendor-id'});
+    }
 
-    push @$cpuFlags, '-rdtscp' if $cpu =~ m/^Opteron/;
+    my $pve_flags = get_pve_cpu_flags($conf, $kvm, $cputype, $arch,
+				      $machine_type, $kvmver);
 
-    if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 3) && $arch eq 'x86_64') {
+    my $hv_flags = get_hyperv_enlightenments($winversion, $machine_type, $kvmver,
+	$conf->{bios}, $gpu_passthrough, $hv_vendor_id) if $kvm;
 
-	push @$cpuFlags , '+kvm_pv_unhalt' if $kvm;
-	push @$cpuFlags , '+kvm_pv_eoi' if $kvm;
+    my $custom_cputype_flags = {};
+    if ($custom_cpuconf && defined($custom_cpuconf->{flags})) {
+	foreach my $flag (split(";", $custom_cpuconf->{flags})) {
+	    $flag =~ m/^([+-])(.*)$/;
+	    $custom_cputype_flags->{$2} = {
+		op => $1,
+		reason => "set by custom CPU model",
+	    }
+	}
     }
 
-    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;
+    my $vm_flags = {};
+    if (defined(my $flags = $cpuconf->{flags})) {
+	foreach my $flag (split(";", $flags)) {
+	    if ($flag =~ $cpu_flag_re) {
+		$vm_flags->{$2} = {
+		    op => $1,
+		    reason => "manually set for VM",
+		}
+	    }
+	}
+    }
 
-    if (my $cpu_vendor = $cpu_vendor_list->{$cpu}) {
-	push @$cpuFlags, "vendor=${cpu_vendor}"
-	    if $cpu_vendor ne 'default';
+    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;
+
+    # $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 = $cputype;
+
+    # will be resolved in parameter order
+    $cpu .= resolve_cpu_flags($pve_flags, $hv_flags, $custom_cputype_flags,
+			      $vm_flags, $pve_forced_flags);
 
     return ('-cpu', $cpu);
 }
 
-sub add_hyperv_enlightenments {
-    my ($cpuFlags, $winversion, $machine_type, $kvmver, $bios, $gpu_passthrough, $hv_vendor_id) = @_;
+# Some hardcoded flags required by certain configurations
+sub get_pve_cpu_flags {
+    my ($conf, $kvm, $cputype, $arch, $machine_type, $kvmver) = @_;
+
+    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 (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 3)
+	&& $arch eq 'x86_64' && $kvm) {
+	$pve_flags->{'kvm_pv_unhalt'} = {
+	    op => '+',
+	    reason => "$pve_msg improves Linux guest spinlock performance",
+	};
+	$pve_flags->{'kvm_pv_eoi'} = {
+	    op => '+',
+	    reason => "$pve_msg improves Linux guest interrupt performance",
+	};
+    }
+
+    return $pve_flags;
+}
+
+sub get_hyperv_enlightenments {
+    my ($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)) {
+    my $flags = {};
+    my $flagfn = sub {
+	my ($flag, $value, $reason) = @_;
+	$flags->{$flag} = {
+	    reason => $reason // "automatic Hyper-V enlightenment for Windows",
+	    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 (qemu_machine_feature_enabled ($machine_type, $kvmver, 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 (qemu_machine_feature_enabled ($machine_type, $kvmver, 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';
+	$flagfn->('hv_relaxed');
 
 	if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 12)) {
-	    push @$cpuFlags , 'hv_synic';
-	    push @$cpuFlags , 'hv_stimer';
+	    $flagfn->('hv_synic');
+	    $flagfn->('hv_stimer');
 	}
 
 	if (qemu_machine_feature_enabled ($machine_type, $kvmver, 3, 1)) {
-	    push @$cpuFlags , 'hv_ipi';
+	    $flagfn->('hv_ipi');
 	}
     }
+
+    return $flags;
 }
 
 sub qemu_machine_feature_enabled {
diff --git a/test/cfg2cmd/i440fx-win10-hostpci.conf.cmd b/test/cfg2cmd/i440fx-win10-hostpci.conf.cmd
index ff5d635..2a9174d 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 5abebe9..444050b 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/q35-linux-hostpci.conf.cmd b/test/cfg2cmd/q35-linux-hostpci.conf.cmd
index 21fb18b..2072295 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 f2c08ca..81e43d4 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 b5c06cf..3485064 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-usb3.conf.cmd b/test/cfg2cmd/spice-usb3.conf.cmd
index 680fa64..66b4e8d 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.2,chassis_nr=2,bus=pci.0,addr=0x1f' \
   -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \
-- 
2.20.1





More information about the pve-devel mailing list