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