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';
- }
- 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);
- }
+ $cputype = 'cortex-a57';
}
- push @$cpuFlags , '+lahf_lm' if $cpu eq 'kvm64' && $arch eq 'x86_64';
+ my $cpu = {};
+ my $custom_cpu;
+ my $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";
- push @$cpuFlags , '-x2apic' if $ostype && $ostype eq 'solaris';
+ $cputype = $cpu->{cputype};
- push @$cpuFlags, '+sep' if $cpu eq 'kvm64' || $cpu eq 'kvm32';
+ if (is_custom_model($cputype)) {
+ $custom_cpu = get_custom_model($cputype);
- push @$cpuFlags, '-rdtscp' if $cpu =~ m/^Opteron/;
+ $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'};
+ }
- if (min_version($machine_version, 2, 3) && $arch eq 'x86_64') {
+ # 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 , '+kvm_pv_unhalt' if $kvm;
- push @$cpuFlags , '+kvm_pv_eoi' if $kvm;
+ my $pve_flags = get_pve_cpu_flags($conf, $kvm, $cputype, $arch,
+ $machine_version);
+
+ my $hv_flags = get_hyperv_enlightenments($winversion, $machine_version,
+ $conf->{bios}, $gpu_passthrough, $hv_vendor_id) if $kvm;
+
+ my $custom_cputype_flags = parse_cpuflag_list($cpu_flag_any_re,
+ "set by custom CPU model", $custom_cpu->{flags});
+
+ my $vm_flags = parse_cpuflag_list($cpu_flag_supported_re,
+ "manually set for VM", $cpu->{flags});
+
+ 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
}
- add_hyperv_enlightenments($cpuFlags, $winversion, $machine_version, $conf->{bios}, $gpu_passthrough, $hv_vendor_id) if $kvm;
+ my $cpu_str = $cputype;
- push @$cpuFlags, 'enforce' if $cpu ne 'host' && $kvm && $arch eq 'x86_64';
+ # will be resolved in parameter order
+ $cpu_str .= resolve_cpu_flags($pve_flags, $hv_flags, $custom_cputype_flags,
+ $vm_flags, $pve_forced_flags);
- push @$cpuFlags, 'kvm=off' if $kvm_off;
+ return ('-cpu', $cpu_str);
+}
- 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
+# 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",
+ };
}
- $cpu .= "," . join(',', @$cpuFlags) if scalar(@$cpuFlags);
-
- return ('-cpu', $cpu);
+ return $pve_flags;
}
-sub add_hyperv_enlightenments {
- my ($cpuFlags, $winversion, $machine_version, $bios, $gpu_passthrough, $hv_vendor_id) = @_;
+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 {
-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' \
-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' \
-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' \
-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' \
-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' \
-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' \
-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' \
-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' \
-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' \
-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' \
-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' \
-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' \
-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' \