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