1 package PVE
::QemuServer
::CPUConfig
;
7 use PVE
::Cluster
qw(cfs_register_file cfs_read_file);
8 use PVE
::QemuServer
::Helpers
qw(min_version);
10 use base
qw(PVE::SectionConfig Exporter);
17 # under certain race-conditions, this module might be loaded before pve-cluster
18 # has started completely, so ensure we don't prevent the FUSE mount with our dir
19 if (PVE
::Cluster
::check_cfs_is_mounted
(1)) {
20 mkdir "/etc/pve/virtual-guest";
23 my $default_filename = "virtual-guest/cpu-models.conf";
26 sub { PVE
::QemuServer
::CPUConfig-
>parse_config(@_); },
27 sub { PVE
::QemuServer
::CPUConfig-
>write_config(@_); },
30 sub load_custom_model_conf
{
31 return cfs_read_file
($default_filename);
34 #builtin models : reported-model is mandatory
35 my $builtin_models = {
37 'reported-model' => 'qemu64',
38 flags
=> "+popcnt;+pni;+sse4.1;+sse4.2;+ssse3",
41 'reported-model' => 'qemu64',
42 flags
=> "+aes;+popcnt;+pni;+sse4.1;+sse4.2;+ssse3",
45 'reported-model' => 'qemu64',
46 flags
=> "+aes;+popcnt;+pni;+sse4.1;+sse4.2;+ssse3;+avx;+avx2;+bmi1;+bmi2;+f16c;+fma;+abm;+movbe;+xsave",
49 'reported-model' => 'qemu64',
50 flags
=> "+aes;+popcnt;+pni;+sse4.1;+sse4.2;+ssse3;+avx;+avx2;+bmi1;+bmi2;+f16c;+fma;+abm;+movbe;+xsave;+avx512f;+avx512bw;+avx512cd;+avx512dq;+avx512vl",
54 my $depreacated_cpu_map = {
55 # there never was such a client CPU, so map it to the server one for backward compat
56 'Icelake-Client' => 'Icelake-Server',
57 'Icelake-Client-noTSX' => 'Icelake-Server-noTSX',
60 my $cpu_vendor_list = {
62 486 => 'GenuineIntel',
63 pentium
=> 'GenuineIntel',
64 pentium2
=> 'GenuineIntel',
65 pentium3
=> 'GenuineIntel',
66 coreduo
=> 'GenuineIntel',
67 core2duo
=> 'GenuineIntel',
68 Conroe
=> 'GenuineIntel',
69 Penryn
=> 'GenuineIntel',
70 Nehalem
=> 'GenuineIntel',
71 'Nehalem-IBRS' => 'GenuineIntel',
72 Westmere
=> 'GenuineIntel',
73 'Westmere-IBRS' => 'GenuineIntel',
74 SandyBridge
=> 'GenuineIntel',
75 'SandyBridge-IBRS' => 'GenuineIntel',
76 IvyBridge
=> 'GenuineIntel',
77 'IvyBridge-IBRS' => 'GenuineIntel',
78 Haswell
=> 'GenuineIntel',
79 'Haswell-IBRS' => 'GenuineIntel',
80 'Haswell-noTSX' => 'GenuineIntel',
81 'Haswell-noTSX-IBRS' => 'GenuineIntel',
82 Broadwell
=> 'GenuineIntel',
83 'Broadwell-IBRS' => 'GenuineIntel',
84 'Broadwell-noTSX' => 'GenuineIntel',
85 'Broadwell-noTSX-IBRS' => 'GenuineIntel',
86 'Skylake-Client' => 'GenuineIntel',
87 'Skylake-Client-IBRS' => 'GenuineIntel',
88 'Skylake-Client-noTSX-IBRS' => 'GenuineIntel',
89 'Skylake-Server' => 'GenuineIntel',
90 'Skylake-Server-IBRS' => 'GenuineIntel',
91 'Skylake-Server-noTSX-IBRS' => 'GenuineIntel',
92 'Cascadelake-Server' => 'GenuineIntel',
93 'Cascadelake-Server-noTSX' => 'GenuineIntel',
94 KnightsMill
=> 'GenuineIntel',
95 'Icelake-Client' => 'GenuineIntel', # depreacated, removed with QEMU 7.1
96 'Icelake-Client-noTSX' => 'GenuineIntel', # depreacated, removed with QEMU 7.1
97 'Icelake-Server' => 'GenuineIntel',
98 'Icelake-Server-noTSX' => 'GenuineIntel',
101 athlon
=> 'AuthenticAMD',
102 phenom
=> 'AuthenticAMD',
103 Opteron_G1
=> 'AuthenticAMD',
104 Opteron_G2
=> 'AuthenticAMD',
105 Opteron_G3
=> 'AuthenticAMD',
106 Opteron_G4
=> 'AuthenticAMD',
107 Opteron_G5
=> 'AuthenticAMD',
108 EPYC
=> 'AuthenticAMD',
109 'EPYC-IBPB' => 'AuthenticAMD',
110 'EPYC-Rome' => 'AuthenticAMD',
111 'EPYC-Milan' => 'AuthenticAMD',
113 # generic types, use vendor from host node
122 my @supported_cpu_flags = (
136 my $cpu_flag_supported_re = qr/([+-])(@{[join('|', @supported_cpu_flags)]})/;
137 my $cpu_flag_any_re = qr/([+-])([a-zA-Z0-9\-_\.]+)/;
139 our $qemu_cmdline_cpu_re = qr/^((?>[+-]?[\w\-\._=]+,?)+)$/;
143 description
=> "Emulated CPU type. Can be default or custom name (custom model names must be prefixed with 'custom-').",
145 format_description
=> 'string',
150 'reported-model' => {
151 description
=> "CPU model and vendor to report to the guest. Must be a QEMU/KVM supported model."
152 ." Only valid for custom CPU model definitions, default models will always report themselves to the guest OS.",
154 enum
=> [ sort { lc("$a") cmp lc("$b") } keys %$cpu_vendor_list ],
159 description
=> "Do not identify as a KVM virtual machine.",
166 pattern
=> qr/[a-zA-Z0-9]{1,12}/,
167 format_description
=> 'vendor-id',
168 description
=> 'The Hyper-V vendor ID. Some drivers or programs inside Windows guests need a specific ID.',
172 description
=> "List of additional CPU flags separated by ';'. Use '+FLAG' to enable,"
173 ." '-FLAG' to disable a flag. Custom CPU models can specify any flag supported by"
174 ." QEMU/KVM, VM-specific flags must be from the following set for security reasons: "
175 . join(', ', @supported_cpu_flags),
176 format_description
=> '+FLAG[;-FLAG...]',
178 pattern
=> qr/$cpu_flag_any_re(;$cpu_flag_any_re)*/,
183 format
=> 'pve-phys-bits',
184 format_description
=> '8-64|host',
185 description
=> "The physical memory address bits that are reported to the guest OS. Should"
186 ." be smaller or equal to the host's. Set to 'host' to use value from host CPU, but"
187 ." note that doing so will break live migration to CPUs with other values.",
192 PVE
::JSONSchema
::register_format
('pve-phys-bits', \
&parse_phys_bits
);
193 sub parse_phys_bits
{
194 my ($str, $noerr) = @_;
196 my $err_msg = "value must be an integer between 8 and 64 or 'host'\n";
198 if ($str !~ m/^(host|\d{1,2})$/) {
199 die $err_msg if !$noerr;
203 if ($str =~ m/^\d+$/ && (int($str) < 8 || int($str) > 64)) {
204 die $err_msg if !$noerr;
211 # $cpu_fmt describes both the CPU config passed as part of a VM config, as well
212 # as the definition of a custom CPU model. There are some slight differences
213 # though, which we catch in the custom validation functions below.
214 PVE
::JSONSchema
::register_format
('pve-cpu-conf', $cpu_fmt, \
&validate_cpu_conf
);
215 sub validate_cpu_conf
{
217 # required, but can't be forced in schema since it's encoded in section header for custom models
218 die "CPU is missing cputype\n" if !$cpu->{cputype
};
221 PVE
::JSONSchema
::register_format
('pve-vm-cpu-conf', $cpu_fmt, \
&validate_vm_cpu_conf
);
222 sub validate_vm_cpu_conf
{
225 validate_cpu_conf
($cpu);
227 my $cputype = $cpu->{cputype
};
229 # a VM-specific config is only valid if the cputype exists
230 if (is_custom_model
($cputype)) {
231 # dies on unknown model
232 get_custom_model
($cputype);
234 die "Built-in cputype '$cputype' is not defined (missing 'custom-' prefix?)\n"
235 if !defined($cpu_vendor_list->{$cputype}) && !defined($builtin_models->{$cputype});
238 # in a VM-specific config, certain properties are limited/forbidden
240 die "VM-specific CPU flags must be a subset of: @{[join(', ', @supported_cpu_flags)]}\n"
241 if ($cpu->{flags
} && $cpu->{flags
} !~ m/^$cpu_flag_supported_re(;$cpu_flag_supported_re)*$/);
243 die "Property 'reported-model' not allowed in VM-specific CPU config.\n"
244 if defined($cpu->{'reported-model'});
249 # Section config settings
251 # shallow copy, since SectionConfig modifies propertyList internally
252 propertyList
=> { %$cpu_fmt },
260 return { %$cpu_fmt };
267 sub parse_section_header
{
268 my ($class, $line) = @_;
270 my ($type, $sectionId, $errmsg, $config) =
271 $class->SUPER::parse_section_header
($line);
274 return ($type, $sectionId, $errmsg, {
275 # name is given by section header, and we can always prepend 'custom-'
276 # since we're reading the custom CPU file
277 cputype
=> "custom-$sectionId",
282 my ($class, $filename, $cfg) = @_;
284 mkdir "/etc/pve/virtual-guest";
286 for my $model (keys %{$cfg->{ids
}}) {
287 my $model_conf = $cfg->{ids
}->{$model};
289 die "internal error: tried saving built-in CPU model (or missing prefix): $model_conf->{cputype}\n"
290 if !is_custom_model
($model_conf->{cputype
});
292 die "internal error: tried saving custom cpumodel with cputype (ignoring prefix: $model_conf->{cputype}) not equal to \$cfg->ids entry ($model)\n"
293 if "custom-$model" ne $model_conf->{cputype
};
295 # saved in section header
296 delete $model_conf->{cputype
};
299 $class->SUPER::write_config
($filename, $cfg);
302 sub add_cpu_json_properties
{
305 foreach my $opt (keys %$cpu_fmt) {
306 $prop->{$opt} = $cpu_fmt->{$opt};
313 my ($include_custom) = @_;
317 for my $default_model (keys %{$cpu_vendor_list}) {
319 name
=> $default_model,
321 vendor
=> $cpu_vendor_list->{$default_model},
325 for my $model (keys %{$builtin_models}) {
326 my $reported_model = $builtin_models->{$model}->{'reported-model'};
327 my $vendor = $cpu_vendor_list->{$reported_model};
335 return $models if !$include_custom;
337 my $conf = load_custom_model_conf
();
338 for my $custom_model (keys %{$conf->{ids
}}) {
339 my $reported_model = $conf->{ids
}->{$custom_model}->{'reported-model'};
340 $reported_model //= $cpu_fmt->{'reported-model'}->{default};
341 my $vendor = $cpu_vendor_list->{$reported_model};
343 name
=> "custom-$custom_model",
352 sub is_custom_model
{
354 return $cputype =~ m/^custom-/;
357 # Use this to get a single model in the format described by $cpu_fmt.
358 # Allows names with and without custom- prefix.
359 sub get_custom_model
{
360 my ($name, $noerr) = @_;
362 $name =~ s/^custom-//;
363 my $conf = load_custom_model_conf
();
365 my $entry = $conf->{ids
}->{$name};
366 if (!defined($entry)) {
367 die "Custom cputype '$name' not found\n" if !$noerr;
372 for my $property (keys %$cpu_fmt) {
373 if (my $value = $entry->{$property}) {
374 $model->{$property} = $value;
381 # Print a QEMU device node for a given VM configuration for hotplugging CPUs
382 sub print_cpu_device
{
383 my ($conf, $id) = @_;
385 my $kvm = $conf->{kvm
} // 1;
386 my $cpu = $kvm ?
"kvm64" : "qemu64";
387 if (my $cputype = $conf->{cpu
}) {
388 my $cpuconf = PVE
::JSONSchema
::parse_property_string
('pve-vm-cpu-conf', $cputype)
389 or die "Cannot parse cpu description: $cputype\n";
390 $cpu = $cpuconf->{cputype
};
392 if (my $model = $builtin_models->{$cpu}) {
393 $cpu = $model->{'reported-model'};
394 } elsif (is_custom_model
($cputype)) {
395 my $custom_cpu = get_custom_model
($cpu);
397 $cpu = $custom_cpu->{'reported-model'} // $cpu_fmt->{'reported-model'}->{default};
399 if (my $replacement_type = $depreacated_cpu_map->{$cpu}) {
400 $cpu = $replacement_type;
404 my $cores = $conf->{cores
} || 1;
406 my $current_core = ($id - 1) % $cores;
407 my $current_socket = int(($id - 1 - $current_core)/$cores);
409 # FIXME: hot plugging other architectures like our unofficial arch64 support?
410 return "$cpu-x86_64-cpu,id=cpu$id,socket-id=$current_socket,core-id=$current_core,thread-id=0";
413 # Resolves multiple arrays of hashes representing CPU flags with metadata to a
414 # single string in QEMU "-cpu" compatible format. Later arrays have higher
417 # Hashes take the following format:
420 # op => "+", # defaults to "" if undefined
421 # reason => "to support AES acceleration", # for override warnings
422 # value => "" # needed for kvm=off (value: off) etc...
426 sub resolve_cpu_flags
{
430 for my $flag_name (keys %$hash) {
431 my $flag = $hash->{$flag_name};
432 my $old_flag = $flags->{$flag_name};
435 $flag->{reason
} //= "unknown origin";
438 my $value_changed = (defined($flag->{value
}) != defined($old_flag->{value
})) ||
439 (defined($flag->{value
}) && $flag->{value
} ne $old_flag->{value
});
441 if ($old_flag->{op
} eq $flag->{op
} && !$value_changed) {
442 $flags->{$flag_name}->{reason
} .= " & $flag->{reason}";
446 my $old = print_cpuflag_hash
($flag_name, $flags->{$flag_name});
447 my $new = print_cpuflag_hash
($flag_name, $flag);
448 warn "warning: CPU flag/setting $new overwrites $old\n";
451 $flags->{$flag_name} = $flag;
456 # sort for command line stability
457 for my $flag_name (sort keys %$flags) {
459 $flag_str .= $flags->{$flag_name}->{op
};
460 $flag_str .= $flag_name;
461 $flag_str .= "=$flags->{$flag_name}->{value}"
462 if $flags->{$flag_name}->{value
};
468 sub print_cpuflag_hash
{
469 my ($flag_name, $flag) = @_;
470 my $formatted = "'$flag->{op}$flag_name";
471 $formatted .= "=$flag->{value}" if defined($flag->{value
});
473 $formatted .= " ($flag->{reason})" if defined($flag->{reason
});
477 sub parse_cpuflag_list
{
478 my ($re, $reason, $flaglist) = @_;
481 return $res if !$flaglist;
483 foreach my $flag (split(";", $flaglist)) {
484 if ($flag =~ m/^$re$/) {
485 $res->{$2} = { op
=> $1, reason
=> $reason };
492 # Calculate QEMU's '-cpu' argument from a given VM configuration
493 sub get_cpu_options
{
494 my ($conf, $arch, $kvm, $kvm_off, $machine_version, $winversion, $gpu_passthrough) = @_;
496 my $cputype = $kvm ?
"kvm64" : "qemu64";
497 if ($arch eq 'aarch64') {
498 $cputype = 'cortex-a57';
505 if (my $cpu_prop_str = $conf->{cpu
}) {
506 $cpu = PVE
::JSONSchema
::parse_property_string
('pve-vm-cpu-conf', $cpu_prop_str)
507 or die "Cannot parse cpu description: $cpu_prop_str\n";
509 $cputype = $cpu->{cputype
};
510 if (my $model = $builtin_models->{$cputype}) {
511 $cputype = $model->{'reported-model'};
512 $builtin_cpu->{flags
} = $model->{'flags'};
513 } elsif (is_custom_model
($cputype)) {
514 $custom_cpu = get_custom_model
($cputype);
516 $cputype = $custom_cpu->{'reported-model'} // $cpu_fmt->{'reported-model'}->{default};
517 $kvm_off = $custom_cpu->{hidden
} if defined($custom_cpu->{hidden
});
518 $hv_vendor_id = $custom_cpu->{'hv-vendor-id'};
521 if (my $replacement_type = $depreacated_cpu_map->{$cputype}) {
522 $cputype = $replacement_type;
525 # VM-specific settings override custom CPU config
526 $kvm_off = $cpu->{hidden
} if defined($cpu->{hidden
});
527 $hv_vendor_id = $cpu->{'hv-vendor-id'} if defined($cpu->{'hv-vendor-id'});
530 my $pve_flags = get_pve_cpu_flags
($conf, $kvm, $cputype, $arch, $machine_version);
533 ? get_hyperv_enlightenments
(
542 my $builtin_cputype_flags = parse_cpuflag_list
(
543 $cpu_flag_any_re, "set by builtin CPU model", $builtin_cpu->{flags
});
545 my $custom_cputype_flags = parse_cpuflag_list
(
546 $cpu_flag_any_re, "set by custom CPU model", $custom_cpu->{flags
});
548 my $vm_flags = parse_cpuflag_list
(
549 $cpu_flag_supported_re, "manually set for VM", $cpu->{flags
});
551 my $pve_forced_flags = {};
552 $pve_forced_flags->{'enforce'} = {
553 reason
=> "error if requested CPU settings not available",
554 } if $cputype ne 'host' && $kvm && $arch eq 'x86_64';
555 $pve_forced_flags->{'kvm'} = {
557 reason
=> "hide KVM virtualization from guest",
560 # $cputype is the "reported-model" for custom types, so we can just look up
561 # the vendor in the default list
562 my $cpu_vendor = $cpu_vendor_list->{$cputype};
564 $pve_forced_flags->{'vendor'} = {
565 value
=> $cpu_vendor,
566 } if $cpu_vendor ne 'default';
567 } elsif ($arch ne 'aarch64') {
568 die "internal error"; # should not happen
571 my $cpu_str = $cputype;
573 # will be resolved in parameter order
574 $cpu_str .= resolve_cpu_flags
(
575 $pve_flags, $hv_flags, $builtin_cputype_flags, $custom_cputype_flags, $vm_flags, $pve_forced_flags);
578 foreach my $conf ($custom_cpu, $cpu) {
579 next if !defined($conf);
580 my $conf_val = $conf->{'phys-bits'};
582 if ($conf_val eq 'host') {
583 $phys_bits = ",host-phys-bits=true";
585 $phys_bits = ",phys-bits=$conf_val";
588 $cpu_str .= $phys_bits;
590 return ('-cpu', $cpu_str);
593 # Some hardcoded flags required by certain configurations
594 sub get_pve_cpu_flags
{
595 my ($conf, $kvm, $cputype, $arch, $machine_version) = @_;
598 my $pve_msg = "set by PVE;";
600 $pve_flags->{'lahf_lm'} = {
602 reason
=> "$pve_msg to support Windows 8.1+",
603 } if $cputype eq 'kvm64' && $arch eq 'x86_64';
605 $pve_flags->{'x2apic'} = {
607 reason
=> "$pve_msg incompatible with Solaris",
608 } if $conf->{ostype
} && $conf->{ostype
} eq 'solaris';
610 $pve_flags->{'sep'} = {
612 reason
=> "$pve_msg to support Windows 8+ and improve Windows XP+",
613 } if $cputype eq 'kvm64' || $cputype eq 'kvm32';
615 $pve_flags->{'rdtscp'} = {
617 reason
=> "$pve_msg broken on AMD Opteron",
618 } if $cputype =~ m/^Opteron/;
620 if (min_version
($machine_version, 2, 3) && $kvm && $arch eq 'x86_64') {
621 $pve_flags->{'kvm_pv_unhalt'} = {
623 reason
=> "$pve_msg to improve Linux guest spinlock performance",
625 $pve_flags->{'kvm_pv_eoi'} = {
627 reason
=> "$pve_msg to improve Linux guest interrupt performance",
634 sub get_hyperv_enlightenments
{
635 my ($winversion, $machine_version, $bios, $gpu_passthrough, $hv_vendor_id) = @_;
637 return if $winversion < 6;
638 return if $bios && $bios eq 'ovmf' && $winversion < 8;
641 my $default_reason = "automatic Hyper-V enlightenment for Windows";
643 my ($flag, $value, $reason) = @_;
645 reason
=> $reason // $default_reason,
650 my $hv_vendor_set = defined($hv_vendor_id);
651 if ($gpu_passthrough || $hv_vendor_set) {
652 $hv_vendor_id //= 'proxmox';
653 $flagfn->('hv_vendor_id', $hv_vendor_id, $hv_vendor_set ?
654 "custom hv_vendor_id set" : "NVIDIA workaround for GPU passthrough");
657 if (min_version
($machine_version, 2, 3)) {
658 $flagfn->('hv_spinlocks', '0x1fff');
659 $flagfn->('hv_vapic');
660 $flagfn->('hv_time');
662 $flagfn->('hv_spinlocks', '0xffff');
665 if (min_version
($machine_version, 2, 6)) {
666 $flagfn->('hv_reset');
667 $flagfn->('hv_vpindex');
668 $flagfn->('hv_runtime');
671 if ($winversion >= 7) {
672 my $win7_reason = $default_reason . " 7 and higher";
673 $flagfn->('hv_relaxed', undef, $win7_reason);
675 if (min_version
($machine_version, 2, 12)) {
676 $flagfn->('hv_synic', undef, $win7_reason);
677 $flagfn->('hv_stimer', undef, $win7_reason);
680 if (min_version
($machine_version, 3, 1)) {
681 $flagfn->('hv_ipi', undef, $win7_reason);
688 sub get_cpu_from_running_vm
{
691 my $cmdline = PVE
::QemuServer
::Helpers
::parse_cmdline
($pid);
692 die "could not read commandline of running machine\n"
693 if !$cmdline->{cpu
}->{value
};
695 # sanitize and untaint value
696 $cmdline->{cpu
}->{value
} =~ $qemu_cmdline_cpu_re;
700 __PACKAGE__-
>register();