]> git.proxmox.com Git - qemu-server.git/blame - PVE/QemuServer/CPUConfig.pm
bump version to 8.2.1
[qemu-server.git] / PVE / QemuServer / CPUConfig.pm
CommitLineData
d786a274
SR
1package PVE::QemuServer::CPUConfig;
2
3use strict;
4use warnings;
5
6use PVE::JSONSchema;
b3e89488 7use PVE::Cluster qw(cfs_register_file cfs_read_file);
5ebb6018 8use PVE::Tools qw(get_host_arch);
d786a274
SR
9use PVE::QemuServer::Helpers qw(min_version);
10
b3e89488 11use base qw(PVE::SectionConfig Exporter);
d786a274
SR
12
13our @EXPORT_OK = qw(
14print_cpu_device
15get_cpu_options
89d5b1c9 16get_cpu_bitness
5ebb6018 17is_native_arch
d786a274
SR
18);
19
b3e89488
SR
20# under certain race-conditions, this module might be loaded before pve-cluster
21# has started completely, so ensure we don't prevent the FUSE mount with our dir
1dbe979c 22if (PVE::Cluster::check_cfs_is_mounted(1)) {
b3e89488
SR
23 mkdir "/etc/pve/virtual-guest";
24}
25
26my $default_filename = "virtual-guest/cpu-models.conf";
d1901fe2
TL
27cfs_register_file(
28 $default_filename,
29 sub { PVE::QemuServer::CPUConfig->parse_config(@_); },
30 sub { PVE::QemuServer::CPUConfig->write_config(@_); },
31);
b3e89488
SR
32
33sub load_custom_model_conf {
34 return cfs_read_file($default_filename);
35}
36
1359e23f
AD
37#builtin models : reported-model is mandatory
38my $builtin_models = {
39 'x86-64-v2' => {
40 'reported-model' => 'qemu64',
41 flags => "+popcnt;+pni;+sse4.1;+sse4.2;+ssse3",
42 },
43 'x86-64-v2-AES' => {
44 'reported-model' => 'qemu64',
45 flags => "+aes;+popcnt;+pni;+sse4.1;+sse4.2;+ssse3",
46 },
47 'x86-64-v3' => {
48 'reported-model' => 'qemu64',
49 flags => "+aes;+popcnt;+pni;+sse4.1;+sse4.2;+ssse3;+avx;+avx2;+bmi1;+bmi2;+f16c;+fma;+abm;+movbe;+xsave",
50 },
51 'x86-64-v4' => {
52 'reported-model' => 'qemu64',
53 flags => "+aes;+popcnt;+pni;+sse4.1;+sse4.2;+ssse3;+avx;+avx2;+bmi1;+bmi2;+f16c;+fma;+abm;+movbe;+xsave;+avx512f;+avx512bw;+avx512cd;+avx512dq;+avx512vl",
54 },
55};
56
0d6962f9
TL
57my $depreacated_cpu_map = {
58 # there never was such a client CPU, so map it to the server one for backward compat
59 'Icelake-Client' => 'Icelake-Server',
60 'Icelake-Client-noTSX' => 'Icelake-Server-noTSX',
61};
62
89d5b1c9
FS
63my $cputypes_32bit = {
64 '486' => 1,
65 'pentium' => 1,
66 'pentium2' => 1,
67 'pentium3' => 1,
68 'coreduo' => 1,
69 'athlon' => 1,
70 'kvm32' => 1,
71 'qemu32' => 1,
72};
73
d786a274
SR
74my $cpu_vendor_list = {
75 # Intel CPUs
76 486 => 'GenuineIntel',
77 pentium => 'GenuineIntel',
78 pentium2 => 'GenuineIntel',
79 pentium3 => 'GenuineIntel',
80 coreduo => 'GenuineIntel',
81 core2duo => 'GenuineIntel',
82 Conroe => 'GenuineIntel',
83 Penryn => 'GenuineIntel',
84 Nehalem => 'GenuineIntel',
85 'Nehalem-IBRS' => 'GenuineIntel',
86 Westmere => 'GenuineIntel',
87 'Westmere-IBRS' => 'GenuineIntel',
88 SandyBridge => 'GenuineIntel',
89 'SandyBridge-IBRS' => 'GenuineIntel',
90 IvyBridge => 'GenuineIntel',
91 'IvyBridge-IBRS' => 'GenuineIntel',
92 Haswell => 'GenuineIntel',
93 'Haswell-IBRS' => 'GenuineIntel',
94 'Haswell-noTSX' => 'GenuineIntel',
95 'Haswell-noTSX-IBRS' => 'GenuineIntel',
96 Broadwell => 'GenuineIntel',
97 'Broadwell-IBRS' => 'GenuineIntel',
98 'Broadwell-noTSX' => 'GenuineIntel',
99 'Broadwell-noTSX-IBRS' => 'GenuineIntel',
100 'Skylake-Client' => 'GenuineIntel',
101 'Skylake-Client-IBRS' => 'GenuineIntel',
bb84db9d 102 'Skylake-Client-noTSX-IBRS' => 'GenuineIntel',
c961ab6d 103 'Skylake-Client-v4' => 'GenuineIntel',
d786a274
SR
104 'Skylake-Server' => 'GenuineIntel',
105 'Skylake-Server-IBRS' => 'GenuineIntel',
bb84db9d 106 'Skylake-Server-noTSX-IBRS' => 'GenuineIntel',
c961ab6d
AD
107 'Skylake-Server-v4' => 'GenuineIntel',
108 'Skylake-Server-v5' => 'GenuineIntel',
d786a274 109 'Cascadelake-Server' => 'GenuineIntel',
c961ab6d 110 'Cascadelake-Server-v2' => 'GenuineIntel',
bb84db9d 111 'Cascadelake-Server-noTSX' => 'GenuineIntel',
c961ab6d
AD
112 'Cascadelake-Server-v4' => 'GenuineIntel',
113 'Cascadelake-Server-v5' => 'GenuineIntel',
114 'Cooperlake' => 'GenuineIntel',
115 'Cooperlake-v2' => 'GenuineIntel',
d786a274 116 KnightsMill => 'GenuineIntel',
0d6962f9
TL
117 'Icelake-Client' => 'GenuineIntel', # depreacated, removed with QEMU 7.1
118 'Icelake-Client-noTSX' => 'GenuineIntel', # depreacated, removed with QEMU 7.1
bb84db9d 119 'Icelake-Server' => 'GenuineIntel',
d0cdb1de 120 'Icelake-Server-noTSX' => 'GenuineIntel',
c961ab6d
AD
121 'Icelake-Server-v3' => 'GenuineIntel',
122 'Icelake-Server-v4' => 'GenuineIntel',
123 'Icelake-Server-v5' => 'GenuineIntel',
124 'Icelake-Server-v6' => 'GenuineIntel',
125 'SapphireRapids' => 'GenuineIntel',
2f2da052
AD
126 'SapphireRapids-v2' => 'GenuineIntel',
127 'GraniteRapids' => 'GenuineIntel',
d786a274
SR
128
129 # AMD CPUs
130 athlon => 'AuthenticAMD',
131 phenom => 'AuthenticAMD',
132 Opteron_G1 => 'AuthenticAMD',
133 Opteron_G2 => 'AuthenticAMD',
134 Opteron_G3 => 'AuthenticAMD',
135 Opteron_G4 => 'AuthenticAMD',
136 Opteron_G5 => 'AuthenticAMD',
137 EPYC => 'AuthenticAMD',
138 'EPYC-IBPB' => 'AuthenticAMD',
c961ab6d 139 'EPYC-v3' => 'AuthenticAMD',
2f2da052 140 'EPYC-v4' => 'AuthenticAMD',
8cea210f 141 'EPYC-Rome' => 'AuthenticAMD',
c961ab6d 142 'EPYC-Rome-v2' => 'AuthenticAMD',
2f2da052
AD
143 'EPYC-Rome-v3' => 'AuthenticAMD',
144 'EPYC-Rome-v4' => 'AuthenticAMD',
9b1971c5 145 'EPYC-Milan' => 'AuthenticAMD',
2f2da052
AD
146 'EPYC-Milan-v2' => 'AuthenticAMD',
147 'EPYC-Genoa' => 'AuthenticAMD',
d786a274
SR
148
149 # generic types, use vendor from host node
150 host => 'default',
151 kvm32 => 'default',
152 kvm64 => 'default',
153 qemu32 => 'default',
154 qemu64 => 'default',
155 max => 'default',
156};
157
158my @supported_cpu_flags = (
159 'pcid',
160 'spec-ctrl',
161 'ibpb',
162 'ssbd',
163 'virt-ssbd',
164 'amd-ssbd',
165 'amd-no-ssb',
166 'pdpe1gb',
167 'md-clear',
168 'hv-tlbflush',
169 'hv-evmcs',
170 'aes'
171);
5d008ad3
SR
172my $cpu_flag_supported_re = qr/([+-])(@{[join('|', @supported_cpu_flags)]})/;
173my $cpu_flag_any_re = qr/([+-])([a-zA-Z0-9\-_\.]+)/;
d786a274 174
c15b5971 175our $qemu_cmdline_cpu_re = qr/^((?>[+-]?[\w\-\._=]+,?)+)$/;
58c64ad5 176
5d008ad3 177my $cpu_fmt = {
d786a274 178 cputype => {
b3e89488 179 description => "Emulated CPU type. Can be default or custom name (custom model names must be prefixed with 'custom-').",
d786a274 180 type => 'string',
b3e89488 181 format_description => 'string',
d786a274
SR
182 default => 'kvm64',
183 default_key => 1,
b3e89488
SR
184 optional => 1,
185 },
186 'reported-model' => {
187 description => "CPU model and vendor to report to the guest. Must be a QEMU/KVM supported model."
11f9264f 188 ." Only valid for custom CPU model definitions, default models will always report themselves to the guest OS.",
b3e89488
SR
189 type => 'string',
190 enum => [ sort { lc("$a") cmp lc("$b") } keys %$cpu_vendor_list ],
191 default => 'kvm64',
192 optional => 1,
d786a274
SR
193 },
194 hidden => {
195 description => "Do not identify as a KVM virtual machine.",
196 type => 'boolean',
197 optional => 1,
198 default => 0
199 },
200 'hv-vendor-id' => {
201 type => 'string',
202 pattern => qr/[a-zA-Z0-9]{1,12}/,
203 format_description => 'vendor-id',
204 description => 'The Hyper-V vendor ID. Some drivers or programs inside Windows guests need a specific ID.',
205 optional => 1,
206 },
207 flags => {
11f9264f
TL
208 description => "List of additional CPU flags separated by ';'. Use '+FLAG' to enable,"
209 ." '-FLAG' to disable a flag. Custom CPU models can specify any flag supported by"
210 ." QEMU/KVM, VM-specific flags must be from the following set for security reasons: "
211 . join(', ', @supported_cpu_flags),
d786a274
SR
212 format_description => '+FLAG[;-FLAG...]',
213 type => 'string',
5d008ad3 214 pattern => qr/$cpu_flag_any_re(;$cpu_flag_any_re)*/,
d786a274
SR
215 optional => 1,
216 },
9f9792d3
SR
217 'phys-bits' => {
218 type => 'string',
219 format => 'pve-phys-bits',
7b8c4de3 220 format_description => '8-64|host',
11f9264f
TL
221 description => "The physical memory address bits that are reported to the guest OS. Should"
222 ." be smaller or equal to the host's. Set to 'host' to use value from host CPU, but"
223 ." note that doing so will break live migration to CPUs with other values.",
9f9792d3
SR
224 optional => 1,
225 },
d786a274
SR
226};
227
9f9792d3
SR
228PVE::JSONSchema::register_format('pve-phys-bits', \&parse_phys_bits);
229sub parse_phys_bits {
230 my ($str, $noerr) = @_;
231
232 my $err_msg = "value must be an integer between 8 and 64 or 'host'\n";
233
234 if ($str !~ m/^(host|\d{1,2})$/) {
235 die $err_msg if !$noerr;
d1c1af4b 236 return;
9f9792d3
SR
237 }
238
239 if ($str =~ m/^\d+$/ && (int($str) < 8 || int($str) > 64)) {
240 die $err_msg if !$noerr;
d1c1af4b 241 return;
9f9792d3
SR
242 }
243
244 return $str;
245}
246
5d008ad3
SR
247# $cpu_fmt describes both the CPU config passed as part of a VM config, as well
248# as the definition of a custom CPU model. There are some slight differences
7b8c4de3
SR
249# though, which we catch in the custom validation functions below.
250PVE::JSONSchema::register_format('pve-cpu-conf', $cpu_fmt, \&validate_cpu_conf);
251sub validate_cpu_conf {
252 my ($cpu) = @_;
05eae0f2 253 # required, but can't be forced in schema since it's encoded in section header for custom models
7b8c4de3 254 die "CPU is missing cputype\n" if !$cpu->{cputype};
b2e813a6 255 return $cpu;
5d008ad3 256}
7b8c4de3
SR
257PVE::JSONSchema::register_format('pve-vm-cpu-conf', $cpu_fmt, \&validate_vm_cpu_conf);
258sub validate_vm_cpu_conf {
259 my ($cpu) = @_;
5d008ad3 260
7b8c4de3 261 validate_cpu_conf($cpu);
5d008ad3
SR
262
263 my $cputype = $cpu->{cputype};
264
265 # a VM-specific config is only valid if the cputype exists
266 if (is_custom_model($cputype)) {
7b8c4de3
SR
267 # dies on unknown model
268 get_custom_model($cputype);
5d008ad3 269 } else {
7b8c4de3 270 die "Built-in cputype '$cputype' is not defined (missing 'custom-' prefix?)\n"
1359e23f 271 if !defined($cpu_vendor_list->{$cputype}) && !defined($builtin_models->{$cputype});
5d008ad3
SR
272 }
273
274 # in a VM-specific config, certain properties are limited/forbidden
275
7b8c4de3 276 die "VM-specific CPU flags must be a subset of: @{[join(', ', @supported_cpu_flags)]}\n"
4893f9b9 277 if ($cpu->{flags} && $cpu->{flags} !~ m/^$cpu_flag_supported_re(;$cpu_flag_supported_re)*$/);
5d008ad3
SR
278
279 die "Property 'reported-model' not allowed in VM-specific CPU config.\n"
280 if defined($cpu->{'reported-model'});
281
282 return $cpu;
283}
284
b3e89488
SR
285# Section config settings
286my $defaultData = {
287 # shallow copy, since SectionConfig modifies propertyList internally
288 propertyList => { %$cpu_fmt },
289};
290
291sub private {
292 return $defaultData;
293}
294
295sub options {
296 return { %$cpu_fmt };
297}
298
299sub type {
300 return 'cpu-model';
301}
302
303sub parse_section_header {
304 my ($class, $line) = @_;
305
306 my ($type, $sectionId, $errmsg, $config) =
307 $class->SUPER::parse_section_header($line);
308
d1c1af4b 309 return if !$type;
b3e89488
SR
310 return ($type, $sectionId, $errmsg, {
311 # name is given by section header, and we can always prepend 'custom-'
312 # since we're reading the custom CPU file
313 cputype => "custom-$sectionId",
314 });
315}
316
317sub write_config {
318 my ($class, $filename, $cfg) = @_;
319
320 mkdir "/etc/pve/virtual-guest";
321
322 for my $model (keys %{$cfg->{ids}}) {
323 my $model_conf = $cfg->{ids}->{$model};
324
325 die "internal error: tried saving built-in CPU model (or missing prefix): $model_conf->{cputype}\n"
326 if !is_custom_model($model_conf->{cputype});
327
328 die "internal error: tried saving custom cpumodel with cputype (ignoring prefix: $model_conf->{cputype}) not equal to \$cfg->ids entry ($model)\n"
329 if "custom-$model" ne $model_conf->{cputype};
330
331 # saved in section header
332 delete $model_conf->{cputype};
333 }
334
335 $class->SUPER::write_config($filename, $cfg);
336}
337
1b7824d3
SR
338sub add_cpu_json_properties {
339 my ($prop) = @_;
340
341 foreach my $opt (keys %$cpu_fmt) {
342 $prop->{$opt} = $cpu_fmt->{$opt};
343 }
344
345 return $prop;
346}
347
a73cc993
SR
348sub get_cpu_models {
349 my ($include_custom) = @_;
350
351 my $models = [];
352
353 for my $default_model (keys %{$cpu_vendor_list}) {
354 push @$models, {
355 name => $default_model,
356 custom => 0,
357 vendor => $cpu_vendor_list->{$default_model},
358 };
359 }
360
1359e23f
AD
361 for my $model (keys %{$builtin_models}) {
362 my $reported_model = $builtin_models->{$model}->{'reported-model'};
363 my $vendor = $cpu_vendor_list->{$reported_model};
364 push @$models, {
365 name => $model,
366 custom => 0,
367 vendor => $vendor,
368 };
369 }
370
a73cc993
SR
371 return $models if !$include_custom;
372
373 my $conf = load_custom_model_conf();
374 for my $custom_model (keys %{$conf->{ids}}) {
375 my $reported_model = $conf->{ids}->{$custom_model}->{'reported-model'};
376 $reported_model //= $cpu_fmt->{'reported-model'}->{default};
377 my $vendor = $cpu_vendor_list->{$reported_model};
378 push @$models, {
379 name => "custom-$custom_model",
380 custom => 1,
381 vendor => $vendor,
382 };
383 }
384
385 return $models;
386}
387
b3e89488
SR
388sub is_custom_model {
389 my ($cputype) = @_;
390 return $cputype =~ m/^custom-/;
391}
392
393# Use this to get a single model in the format described by $cpu_fmt.
394# Allows names with and without custom- prefix.
395sub get_custom_model {
396 my ($name, $noerr) = @_;
397
398 $name =~ s/^custom-//;
399 my $conf = load_custom_model_conf();
400
401 my $entry = $conf->{ids}->{$name};
402 if (!defined($entry)) {
403 die "Custom cputype '$name' not found\n" if !$noerr;
d1c1af4b 404 return;
b3e89488
SR
405 }
406
407 my $model = {};
408 for my $property (keys %$cpu_fmt) {
409 if (my $value = $entry->{$property}) {
410 $model->{$property} = $value;
411 }
412 }
413
414 return $model;
415}
416
d786a274
SR
417# Print a QEMU device node for a given VM configuration for hotplugging CPUs
418sub print_cpu_device {
d1a7abd0 419 my ($conf, $arch, $id) = @_;
d786a274 420
caa88bc8
FS
421 # FIXME: hot plugging other architectures like our unofficial aarch64 support?
422 die "Hotplug of non x86_64 CPU not yet supported" if $arch ne 'x86_64';
423
d1a7abd0 424 my $kvm = $conf->{kvm} // is_native_arch($arch);
5416ff70 425 my $cpu = get_default_cpu_type('x86_64', $kvm);
d786a274 426 if (my $cputype = $conf->{cpu}) {
7b8c4de3 427 my $cpuconf = PVE::JSONSchema::parse_property_string('pve-vm-cpu-conf', $cputype)
d786a274
SR
428 or die "Cannot parse cpu description: $cputype\n";
429 $cpu = $cpuconf->{cputype};
b3e89488 430
1359e23f
AD
431 if (my $model = $builtin_models->{$cpu}) {
432 $cpu = $model->{'reported-model'};
433 } elsif (is_custom_model($cputype)) {
b3e89488
SR
434 my $custom_cpu = get_custom_model($cpu);
435
b0ab3463 436 $cpu = $custom_cpu->{'reported-model'} // $cpu_fmt->{'reported-model'}->{default};
adc67fe9
TL
437 }
438 if (my $replacement_type = $depreacated_cpu_map->{$cpu}) {
0d6962f9 439 $cpu = $replacement_type;
b3e89488 440 }
d786a274
SR
441 }
442
443 my $cores = $conf->{cores} || 1;
444
445 my $current_core = ($id - 1) % $cores;
446 my $current_socket = int(($id - 1 - $current_core)/$cores);
447
448 return "$cpu-x86_64-cpu,id=cpu$id,socket-id=$current_socket,core-id=$current_core,thread-id=0";
449}
450
45619185
SR
451# Resolves multiple arrays of hashes representing CPU flags with metadata to a
452# single string in QEMU "-cpu" compatible format. Later arrays have higher
453# priority.
454#
455# Hashes take the following format:
456# {
457# aes => {
458# op => "+", # defaults to "" if undefined
459# reason => "to support AES acceleration", # for override warnings
460# value => "" # needed for kvm=off (value: off) etc...
461# },
462# ...
463# }
464sub resolve_cpu_flags {
465 my $flags = {};
466
467 for my $hash (@_) {
468 for my $flag_name (keys %$hash) {
469 my $flag = $hash->{$flag_name};
470 my $old_flag = $flags->{$flag_name};
471
472 $flag->{op} //= "";
473 $flag->{reason} //= "unknown origin";
474
475 if ($old_flag) {
476 my $value_changed = (defined($flag->{value}) != defined($old_flag->{value})) ||
477 (defined($flag->{value}) && $flag->{value} ne $old_flag->{value});
478
479 if ($old_flag->{op} eq $flag->{op} && !$value_changed) {
480 $flags->{$flag_name}->{reason} .= " & $flag->{reason}";
481 next;
482 }
483
484 my $old = print_cpuflag_hash($flag_name, $flags->{$flag_name});
485 my $new = print_cpuflag_hash($flag_name, $flag);
486 warn "warning: CPU flag/setting $new overwrites $old\n";
487 }
488
489 $flags->{$flag_name} = $flag;
490 }
491 }
492
493 my $flag_str = '';
494 # sort for command line stability
495 for my $flag_name (sort keys %$flags) {
496 $flag_str .= ',';
497 $flag_str .= $flags->{$flag_name}->{op};
498 $flag_str .= $flag_name;
499 $flag_str .= "=$flags->{$flag_name}->{value}"
500 if $flags->{$flag_name}->{value};
501 }
502
503 return $flag_str;
504}
505
506sub print_cpuflag_hash {
507 my ($flag_name, $flag) = @_;
508 my $formatted = "'$flag->{op}$flag_name";
509 $formatted .= "=$flag->{value}" if defined($flag->{value});
510 $formatted .= "'";
511 $formatted .= " ($flag->{reason})" if defined($flag->{reason});
512 return $formatted;
513}
514
c4581b9c
SR
515sub parse_cpuflag_list {
516 my ($re, $reason, $flaglist) = @_;
517
518 my $res = {};
519 return $res if !$flaglist;
520
521 foreach my $flag (split(";", $flaglist)) {
4893f9b9 522 if ($flag =~ m/^$re$/) {
c4581b9c
SR
523 $res->{$2} = { op => $1, reason => $reason };
524 }
525 }
526
527 return $res;
528}
529
d786a274
SR
530# Calculate QEMU's '-cpu' argument from a given VM configuration
531sub get_cpu_options {
532 my ($conf, $arch, $kvm, $kvm_off, $machine_version, $winversion, $gpu_passthrough) = @_;
533
5416ff70 534 my $cputype = get_default_cpu_type($arch, $kvm);
d786a274 535
c4581b9c
SR
536 my $cpu = {};
537 my $custom_cpu;
1359e23f 538 my $builtin_cpu;
c4581b9c
SR
539 my $hv_vendor_id;
540 if (my $cpu_prop_str = $conf->{cpu}) {
7b8c4de3 541 $cpu = PVE::JSONSchema::parse_property_string('pve-vm-cpu-conf', $cpu_prop_str)
c4581b9c 542 or die "Cannot parse cpu description: $cpu_prop_str\n";
d786a274 543
c4581b9c 544 $cputype = $cpu->{cputype};
1359e23f
AD
545 if (my $model = $builtin_models->{$cputype}) {
546 $cputype = $model->{'reported-model'};
547 $builtin_cpu->{flags} = $model->{'flags'};
548 } elsif (is_custom_model($cputype)) {
c4581b9c 549 $custom_cpu = get_custom_model($cputype);
d786a274 550
b0ab3463
TL
551 $cputype = $custom_cpu->{'reported-model'} // $cpu_fmt->{'reported-model'}->{default};
552 $kvm_off = $custom_cpu->{hidden} if defined($custom_cpu->{hidden});
c4581b9c
SR
553 $hv_vendor_id = $custom_cpu->{'hv-vendor-id'};
554 }
d786a274 555
0d6962f9
TL
556 if (my $replacement_type = $depreacated_cpu_map->{$cputype}) {
557 $cputype = $replacement_type;
558 }
559
c4581b9c 560 # VM-specific settings override custom CPU config
b0ab3463
TL
561 $kvm_off = $cpu->{hidden} if defined($cpu->{hidden});
562 $hv_vendor_id = $cpu->{'hv-vendor-id'} if defined($cpu->{'hv-vendor-id'});
c4581b9c 563 }
d786a274 564
f7d1505b
TL
565 my $pve_flags = get_pve_cpu_flags($conf, $kvm, $cputype, $arch, $machine_version);
566
567 my $hv_flags = $kvm
568 ? get_hyperv_enlightenments(
569 $winversion,
570 $machine_version,
571 $conf->{bios},
572 $gpu_passthrough,
573 $hv_vendor_id,
574 )
575 : undef;
c4581b9c 576
1359e23f
AD
577 my $builtin_cputype_flags = parse_cpuflag_list(
578 $cpu_flag_any_re, "set by builtin CPU model", $builtin_cpu->{flags});
579
11f9264f
TL
580 my $custom_cputype_flags = parse_cpuflag_list(
581 $cpu_flag_any_re, "set by custom CPU model", $custom_cpu->{flags});
c4581b9c 582
11f9264f
TL
583 my $vm_flags = parse_cpuflag_list(
584 $cpu_flag_supported_re, "manually set for VM", $cpu->{flags});
c4581b9c
SR
585
586 my $pve_forced_flags = {};
587 $pve_forced_flags->{'enforce'} = {
588 reason => "error if requested CPU settings not available",
589 } if $cputype ne 'host' && $kvm && $arch eq 'x86_64';
590 $pve_forced_flags->{'kvm'} = {
591 value => "off",
592 reason => "hide KVM virtualization from guest",
593 } if $kvm_off;
594
595 # $cputype is the "reported-model" for custom types, so we can just look up
596 # the vendor in the default list
597 my $cpu_vendor = $cpu_vendor_list->{$cputype};
598 if ($cpu_vendor) {
599 $pve_forced_flags->{'vendor'} = {
600 value => $cpu_vendor,
601 } if $cpu_vendor ne 'default';
602 } elsif ($arch ne 'aarch64') {
603 die "internal error"; # should not happen
d786a274
SR
604 }
605
c4581b9c 606 my $cpu_str = $cputype;
d786a274 607
c4581b9c 608 # will be resolved in parameter order
11f9264f 609 $cpu_str .= resolve_cpu_flags(
1359e23f 610 $pve_flags, $hv_flags, $builtin_cputype_flags, $custom_cputype_flags, $vm_flags, $pve_forced_flags);
d786a274 611
9f9792d3
SR
612 my $phys_bits = '';
613 foreach my $conf ($custom_cpu, $cpu) {
614 next if !defined($conf);
615 my $conf_val = $conf->{'phys-bits'};
616 next if !$conf_val;
617 if ($conf_val eq 'host') {
618 $phys_bits = ",host-phys-bits=true";
619 } else {
620 $phys_bits = ",phys-bits=$conf_val";
621 }
622 }
623 $cpu_str .= $phys_bits;
624
c4581b9c
SR
625 return ('-cpu', $cpu_str);
626}
d786a274 627
c4581b9c
SR
628# Some hardcoded flags required by certain configurations
629sub get_pve_cpu_flags {
630 my ($conf, $kvm, $cputype, $arch, $machine_version) = @_;
631
632 my $pve_flags = {};
633 my $pve_msg = "set by PVE;";
634
635 $pve_flags->{'lahf_lm'} = {
636 op => '+',
637 reason => "$pve_msg to support Windows 8.1+",
638 } if $cputype eq 'kvm64' && $arch eq 'x86_64';
639
640 $pve_flags->{'x2apic'} = {
641 op => '-',
642 reason => "$pve_msg incompatible with Solaris",
643 } if $conf->{ostype} && $conf->{ostype} eq 'solaris';
644
645 $pve_flags->{'sep'} = {
646 op => '+',
647 reason => "$pve_msg to support Windows 8+ and improve Windows XP+",
648 } if $cputype eq 'kvm64' || $cputype eq 'kvm32';
649
650 $pve_flags->{'rdtscp'} = {
651 op => '-',
652 reason => "$pve_msg broken on AMD Opteron",
653 } if $cputype =~ m/^Opteron/;
654
655 if (min_version($machine_version, 2, 3) && $kvm && $arch eq 'x86_64') {
656 $pve_flags->{'kvm_pv_unhalt'} = {
657 op => '+',
658 reason => "$pve_msg to improve Linux guest spinlock performance",
659 };
660 $pve_flags->{'kvm_pv_eoi'} = {
661 op => '+',
662 reason => "$pve_msg to improve Linux guest interrupt performance",
663 };
d786a274
SR
664 }
665
c4581b9c 666 return $pve_flags;
d786a274
SR
667}
668
c4581b9c
SR
669sub get_hyperv_enlightenments {
670 my ($winversion, $machine_version, $bios, $gpu_passthrough, $hv_vendor_id) = @_;
d786a274
SR
671
672 return if $winversion < 6;
673 return if $bios && $bios eq 'ovmf' && $winversion < 8;
674
c4581b9c
SR
675 my $flags = {};
676 my $default_reason = "automatic Hyper-V enlightenment for Windows";
677 my $flagfn = sub {
678 my ($flag, $value, $reason) = @_;
679 $flags->{$flag} = {
680 reason => $reason // $default_reason,
681 value => $value,
682 }
683 };
684
685 my $hv_vendor_set = defined($hv_vendor_id);
686 if ($gpu_passthrough || $hv_vendor_set) {
d786a274 687 $hv_vendor_id //= 'proxmox';
c4581b9c
SR
688 $flagfn->('hv_vendor_id', $hv_vendor_id, $hv_vendor_set ?
689 "custom hv_vendor_id set" : "NVIDIA workaround for GPU passthrough");
d786a274
SR
690 }
691
692 if (min_version($machine_version, 2, 3)) {
c4581b9c
SR
693 $flagfn->('hv_spinlocks', '0x1fff');
694 $flagfn->('hv_vapic');
695 $flagfn->('hv_time');
d786a274 696 } else {
c4581b9c 697 $flagfn->('hv_spinlocks', '0xffff');
d786a274
SR
698 }
699
700 if (min_version($machine_version, 2, 6)) {
c4581b9c
SR
701 $flagfn->('hv_reset');
702 $flagfn->('hv_vpindex');
703 $flagfn->('hv_runtime');
d786a274
SR
704 }
705
706 if ($winversion >= 7) {
c4581b9c
SR
707 my $win7_reason = $default_reason . " 7 and higher";
708 $flagfn->('hv_relaxed', undef, $win7_reason);
d786a274
SR
709
710 if (min_version($machine_version, 2, 12)) {
c4581b9c
SR
711 $flagfn->('hv_synic', undef, $win7_reason);
712 $flagfn->('hv_stimer', undef, $win7_reason);
d786a274
SR
713 }
714
715 if (min_version($machine_version, 3, 1)) {
c4581b9c 716 $flagfn->('hv_ipi', undef, $win7_reason);
d786a274
SR
717 }
718 }
c4581b9c
SR
719
720 return $flags;
d786a274
SR
721}
722
58c64ad5
SR
723sub get_cpu_from_running_vm {
724 my ($pid) = @_;
725
726 my $cmdline = PVE::QemuServer::Helpers::parse_cmdline($pid);
727 die "could not read commandline of running machine\n"
728 if !$cmdline->{cpu}->{value};
729
730 # sanitize and untaint value
731 $cmdline->{cpu}->{value} =~ $qemu_cmdline_cpu_re;
732 return $1;
733}
734
5416ff70
FS
735sub get_default_cpu_type {
736 my ($arch, $kvm) = @_;
737
738 my $cputype = $kvm ? 'kvm64' : 'qemu64';
739 $cputype = 'cortex-a57' if $arch eq 'aarch64';
740
741 return $cputype;
742}
743
5ebb6018
TL
744sub is_native_arch($) {
745 my ($arch) = @_;
746 return get_host_arch() eq $arch;
747}
748
89d5b1c9
FS
749sub get_cpu_bitness {
750 my ($cpu_prop_str, $arch) = @_;
751
752 $arch //= get_host_arch();
753
754 my $cputype = get_default_cpu_type($arch, 0);
755
756 if ($cpu_prop_str) {
757 my $cpu = PVE::JSONSchema::parse_property_string('pve-vm-cpu-conf', $cpu_prop_str)
758 or die "Cannot parse cpu description: $cpu_prop_str\n";
759
67dca423 760 $cputype = $cpu->{cputype};
89d5b1c9
FS
761
762 if (my $model = $builtin_models->{$cputype}) {
763 $cputype = $model->{'reported-model'};
764 } elsif (is_custom_model($cputype)) {
765 my $custom_cpu = get_custom_model($cputype);
766 $cputype = $custom_cpu->{'reported-model'} // $cpu_fmt->{'reported-model'}->{default};
767 }
768 }
769
770 return $cputypes_32bit->{$cputype} ? 32 : 64 if $arch eq 'x86_64';
771 return 64 if $arch eq 'aarch64';
772
773 die "unsupported architecture '$arch'\n";
774}
775
b3e89488
SR
776__PACKAGE__->register();
777__PACKAGE__->init();
778
d786a274 7791;