]> git.proxmox.com Git - qemu-server.git/blob - PVE/QemuServer/CPUConfig.pm
cpu config: implement is_native_arch locally for now
[qemu-server.git] / PVE / QemuServer / CPUConfig.pm
1 package PVE::QemuServer::CPUConfig;
2
3 use strict;
4 use warnings;
5
6 use PVE::JSONSchema;
7 use PVE::Cluster qw(cfs_register_file cfs_read_file);
8 use PVE::Tools qw(get_host_arch);
9 use PVE::QemuServer::Helpers qw(min_version);
10
11 use base qw(PVE::SectionConfig Exporter);
12
13 our @EXPORT_OK = qw(
14 print_cpu_device
15 get_cpu_options
16 get_cpu_bitness
17 is_native_arch
18 );
19
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
22 if (PVE::Cluster::check_cfs_is_mounted(1)) {
23 mkdir "/etc/pve/virtual-guest";
24 }
25
26 my $default_filename = "virtual-guest/cpu-models.conf";
27 cfs_register_file(
28 $default_filename,
29 sub { PVE::QemuServer::CPUConfig->parse_config(@_); },
30 sub { PVE::QemuServer::CPUConfig->write_config(@_); },
31 );
32
33 sub load_custom_model_conf {
34 return cfs_read_file($default_filename);
35 }
36
37 #builtin models : reported-model is mandatory
38 my $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
57 my $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
63 my $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
74 my $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',
102 'Skylake-Client-noTSX-IBRS' => 'GenuineIntel',
103 'Skylake-Client-v4' => 'GenuineIntel',
104 'Skylake-Server' => 'GenuineIntel',
105 'Skylake-Server-IBRS' => 'GenuineIntel',
106 'Skylake-Server-noTSX-IBRS' => 'GenuineIntel',
107 'Skylake-Server-v4' => 'GenuineIntel',
108 'Skylake-Server-v5' => 'GenuineIntel',
109 'Cascadelake-Server' => 'GenuineIntel',
110 'Cascadelake-Server-v2' => 'GenuineIntel',
111 'Cascadelake-Server-noTSX' => 'GenuineIntel',
112 'Cascadelake-Server-v4' => 'GenuineIntel',
113 'Cascadelake-Server-v5' => 'GenuineIntel',
114 'Cooperlake' => 'GenuineIntel',
115 'Cooperlake-v2' => 'GenuineIntel',
116 KnightsMill => 'GenuineIntel',
117 'Icelake-Client' => 'GenuineIntel', # depreacated, removed with QEMU 7.1
118 'Icelake-Client-noTSX' => 'GenuineIntel', # depreacated, removed with QEMU 7.1
119 'Icelake-Server' => 'GenuineIntel',
120 'Icelake-Server-noTSX' => 'GenuineIntel',
121 'Icelake-Server-v3' => 'GenuineIntel',
122 'Icelake-Server-v4' => 'GenuineIntel',
123 'Icelake-Server-v5' => 'GenuineIntel',
124 'Icelake-Server-v6' => 'GenuineIntel',
125 'SapphireRapids' => 'GenuineIntel',
126 'SapphireRapids-v2' => 'GenuineIntel',
127 'GraniteRapids' => 'GenuineIntel',
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',
139 'EPYC-v3' => 'AuthenticAMD',
140 'EPYC-v4' => 'AuthenticAMD',
141 'EPYC-Rome' => 'AuthenticAMD',
142 'EPYC-Rome-v2' => 'AuthenticAMD',
143 'EPYC-Rome-v3' => 'AuthenticAMD',
144 'EPYC-Rome-v4' => 'AuthenticAMD',
145 'EPYC-Milan' => 'AuthenticAMD',
146 'EPYC-Milan-v2' => 'AuthenticAMD',
147 'EPYC-Genoa' => 'AuthenticAMD',
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
158 my @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 );
172 my $cpu_flag_supported_re = qr/([+-])(@{[join('|', @supported_cpu_flags)]})/;
173 my $cpu_flag_any_re = qr/([+-])([a-zA-Z0-9\-_\.]+)/;
174
175 our $qemu_cmdline_cpu_re = qr/^((?>[+-]?[\w\-\._=]+,?)+)$/;
176
177 my $cpu_fmt = {
178 cputype => {
179 description => "Emulated CPU type. Can be default or custom name (custom model names must be prefixed with 'custom-').",
180 type => 'string',
181 format_description => 'string',
182 default => 'kvm64',
183 default_key => 1,
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."
188 ." Only valid for custom CPU model definitions, default models will always report themselves to the guest OS.",
189 type => 'string',
190 enum => [ sort { lc("$a") cmp lc("$b") } keys %$cpu_vendor_list ],
191 default => 'kvm64',
192 optional => 1,
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 => {
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),
212 format_description => '+FLAG[;-FLAG...]',
213 type => 'string',
214 pattern => qr/$cpu_flag_any_re(;$cpu_flag_any_re)*/,
215 optional => 1,
216 },
217 'phys-bits' => {
218 type => 'string',
219 format => 'pve-phys-bits',
220 format_description => '8-64|host',
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.",
224 optional => 1,
225 },
226 };
227
228 PVE::JSONSchema::register_format('pve-phys-bits', \&parse_phys_bits);
229 sub 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;
236 return;
237 }
238
239 if ($str =~ m/^\d+$/ && (int($str) < 8 || int($str) > 64)) {
240 die $err_msg if !$noerr;
241 return;
242 }
243
244 return $str;
245 }
246
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
249 # though, which we catch in the custom validation functions below.
250 PVE::JSONSchema::register_format('pve-cpu-conf', $cpu_fmt, \&validate_cpu_conf);
251 sub validate_cpu_conf {
252 my ($cpu) = @_;
253 # required, but can't be forced in schema since it's encoded in section header for custom models
254 die "CPU is missing cputype\n" if !$cpu->{cputype};
255 return $cpu;
256 }
257 PVE::JSONSchema::register_format('pve-vm-cpu-conf', $cpu_fmt, \&validate_vm_cpu_conf);
258 sub validate_vm_cpu_conf {
259 my ($cpu) = @_;
260
261 validate_cpu_conf($cpu);
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)) {
267 # dies on unknown model
268 get_custom_model($cputype);
269 } else {
270 die "Built-in cputype '$cputype' is not defined (missing 'custom-' prefix?)\n"
271 if !defined($cpu_vendor_list->{$cputype}) && !defined($builtin_models->{$cputype});
272 }
273
274 # in a VM-specific config, certain properties are limited/forbidden
275
276 die "VM-specific CPU flags must be a subset of: @{[join(', ', @supported_cpu_flags)]}\n"
277 if ($cpu->{flags} && $cpu->{flags} !~ m/^$cpu_flag_supported_re(;$cpu_flag_supported_re)*$/);
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
285 # Section config settings
286 my $defaultData = {
287 # shallow copy, since SectionConfig modifies propertyList internally
288 propertyList => { %$cpu_fmt },
289 };
290
291 sub private {
292 return $defaultData;
293 }
294
295 sub options {
296 return { %$cpu_fmt };
297 }
298
299 sub type {
300 return 'cpu-model';
301 }
302
303 sub parse_section_header {
304 my ($class, $line) = @_;
305
306 my ($type, $sectionId, $errmsg, $config) =
307 $class->SUPER::parse_section_header($line);
308
309 return if !$type;
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
317 sub 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
338 sub 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
348 sub 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
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
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
388 sub 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.
395 sub 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;
404 return;
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
417 # Print a QEMU device node for a given VM configuration for hotplugging CPUs
418 sub print_cpu_device {
419 my ($conf, $arch, $id) = @_;
420
421 my $kvm = $conf->{kvm} // is_native_arch($arch);
422 my $cpu = get_default_cpu_type('x86_64', $kvm);
423 if (my $cputype = $conf->{cpu}) {
424 my $cpuconf = PVE::JSONSchema::parse_property_string('pve-vm-cpu-conf', $cputype)
425 or die "Cannot parse cpu description: $cputype\n";
426 $cpu = $cpuconf->{cputype};
427
428 if (my $model = $builtin_models->{$cpu}) {
429 $cpu = $model->{'reported-model'};
430 } elsif (is_custom_model($cputype)) {
431 my $custom_cpu = get_custom_model($cpu);
432
433 $cpu = $custom_cpu->{'reported-model'} // $cpu_fmt->{'reported-model'}->{default};
434 }
435 if (my $replacement_type = $depreacated_cpu_map->{$cpu}) {
436 $cpu = $replacement_type;
437 }
438 }
439
440 my $cores = $conf->{cores} || 1;
441
442 my $current_core = ($id - 1) % $cores;
443 my $current_socket = int(($id - 1 - $current_core)/$cores);
444
445 # FIXME: hot plugging other architectures like our unofficial arch64 support?
446 return "$cpu-x86_64-cpu,id=cpu$id,socket-id=$current_socket,core-id=$current_core,thread-id=0";
447 }
448
449 # Resolves multiple arrays of hashes representing CPU flags with metadata to a
450 # single string in QEMU "-cpu" compatible format. Later arrays have higher
451 # priority.
452 #
453 # Hashes take the following format:
454 # {
455 # aes => {
456 # op => "+", # defaults to "" if undefined
457 # reason => "to support AES acceleration", # for override warnings
458 # value => "" # needed for kvm=off (value: off) etc...
459 # },
460 # ...
461 # }
462 sub resolve_cpu_flags {
463 my $flags = {};
464
465 for my $hash (@_) {
466 for my $flag_name (keys %$hash) {
467 my $flag = $hash->{$flag_name};
468 my $old_flag = $flags->{$flag_name};
469
470 $flag->{op} //= "";
471 $flag->{reason} //= "unknown origin";
472
473 if ($old_flag) {
474 my $value_changed = (defined($flag->{value}) != defined($old_flag->{value})) ||
475 (defined($flag->{value}) && $flag->{value} ne $old_flag->{value});
476
477 if ($old_flag->{op} eq $flag->{op} && !$value_changed) {
478 $flags->{$flag_name}->{reason} .= " & $flag->{reason}";
479 next;
480 }
481
482 my $old = print_cpuflag_hash($flag_name, $flags->{$flag_name});
483 my $new = print_cpuflag_hash($flag_name, $flag);
484 warn "warning: CPU flag/setting $new overwrites $old\n";
485 }
486
487 $flags->{$flag_name} = $flag;
488 }
489 }
490
491 my $flag_str = '';
492 # sort for command line stability
493 for my $flag_name (sort keys %$flags) {
494 $flag_str .= ',';
495 $flag_str .= $flags->{$flag_name}->{op};
496 $flag_str .= $flag_name;
497 $flag_str .= "=$flags->{$flag_name}->{value}"
498 if $flags->{$flag_name}->{value};
499 }
500
501 return $flag_str;
502 }
503
504 sub print_cpuflag_hash {
505 my ($flag_name, $flag) = @_;
506 my $formatted = "'$flag->{op}$flag_name";
507 $formatted .= "=$flag->{value}" if defined($flag->{value});
508 $formatted .= "'";
509 $formatted .= " ($flag->{reason})" if defined($flag->{reason});
510 return $formatted;
511 }
512
513 sub parse_cpuflag_list {
514 my ($re, $reason, $flaglist) = @_;
515
516 my $res = {};
517 return $res if !$flaglist;
518
519 foreach my $flag (split(";", $flaglist)) {
520 if ($flag =~ m/^$re$/) {
521 $res->{$2} = { op => $1, reason => $reason };
522 }
523 }
524
525 return $res;
526 }
527
528 # Calculate QEMU's '-cpu' argument from a given VM configuration
529 sub get_cpu_options {
530 my ($conf, $arch, $kvm, $kvm_off, $machine_version, $winversion, $gpu_passthrough) = @_;
531
532 my $cputype = get_default_cpu_type($arch, $kvm);
533
534 my $cpu = {};
535 my $custom_cpu;
536 my $builtin_cpu;
537 my $hv_vendor_id;
538 if (my $cpu_prop_str = $conf->{cpu}) {
539 $cpu = PVE::JSONSchema::parse_property_string('pve-vm-cpu-conf', $cpu_prop_str)
540 or die "Cannot parse cpu description: $cpu_prop_str\n";
541
542 $cputype = $cpu->{cputype};
543 if (my $model = $builtin_models->{$cputype}) {
544 $cputype = $model->{'reported-model'};
545 $builtin_cpu->{flags} = $model->{'flags'};
546 } elsif (is_custom_model($cputype)) {
547 $custom_cpu = get_custom_model($cputype);
548
549 $cputype = $custom_cpu->{'reported-model'} // $cpu_fmt->{'reported-model'}->{default};
550 $kvm_off = $custom_cpu->{hidden} if defined($custom_cpu->{hidden});
551 $hv_vendor_id = $custom_cpu->{'hv-vendor-id'};
552 }
553
554 if (my $replacement_type = $depreacated_cpu_map->{$cputype}) {
555 $cputype = $replacement_type;
556 }
557
558 # VM-specific settings override custom CPU config
559 $kvm_off = $cpu->{hidden} if defined($cpu->{hidden});
560 $hv_vendor_id = $cpu->{'hv-vendor-id'} if defined($cpu->{'hv-vendor-id'});
561 }
562
563 my $pve_flags = get_pve_cpu_flags($conf, $kvm, $cputype, $arch, $machine_version);
564
565 my $hv_flags = $kvm
566 ? get_hyperv_enlightenments(
567 $winversion,
568 $machine_version,
569 $conf->{bios},
570 $gpu_passthrough,
571 $hv_vendor_id,
572 )
573 : undef;
574
575 my $builtin_cputype_flags = parse_cpuflag_list(
576 $cpu_flag_any_re, "set by builtin CPU model", $builtin_cpu->{flags});
577
578 my $custom_cputype_flags = parse_cpuflag_list(
579 $cpu_flag_any_re, "set by custom CPU model", $custom_cpu->{flags});
580
581 my $vm_flags = parse_cpuflag_list(
582 $cpu_flag_supported_re, "manually set for VM", $cpu->{flags});
583
584 my $pve_forced_flags = {};
585 $pve_forced_flags->{'enforce'} = {
586 reason => "error if requested CPU settings not available",
587 } if $cputype ne 'host' && $kvm && $arch eq 'x86_64';
588 $pve_forced_flags->{'kvm'} = {
589 value => "off",
590 reason => "hide KVM virtualization from guest",
591 } if $kvm_off;
592
593 # $cputype is the "reported-model" for custom types, so we can just look up
594 # the vendor in the default list
595 my $cpu_vendor = $cpu_vendor_list->{$cputype};
596 if ($cpu_vendor) {
597 $pve_forced_flags->{'vendor'} = {
598 value => $cpu_vendor,
599 } if $cpu_vendor ne 'default';
600 } elsif ($arch ne 'aarch64') {
601 die "internal error"; # should not happen
602 }
603
604 my $cpu_str = $cputype;
605
606 # will be resolved in parameter order
607 $cpu_str .= resolve_cpu_flags(
608 $pve_flags, $hv_flags, $builtin_cputype_flags, $custom_cputype_flags, $vm_flags, $pve_forced_flags);
609
610 my $phys_bits = '';
611 foreach my $conf ($custom_cpu, $cpu) {
612 next if !defined($conf);
613 my $conf_val = $conf->{'phys-bits'};
614 next if !$conf_val;
615 if ($conf_val eq 'host') {
616 $phys_bits = ",host-phys-bits=true";
617 } else {
618 $phys_bits = ",phys-bits=$conf_val";
619 }
620 }
621 $cpu_str .= $phys_bits;
622
623 return ('-cpu', $cpu_str);
624 }
625
626 # Some hardcoded flags required by certain configurations
627 sub get_pve_cpu_flags {
628 my ($conf, $kvm, $cputype, $arch, $machine_version) = @_;
629
630 my $pve_flags = {};
631 my $pve_msg = "set by PVE;";
632
633 $pve_flags->{'lahf_lm'} = {
634 op => '+',
635 reason => "$pve_msg to support Windows 8.1+",
636 } if $cputype eq 'kvm64' && $arch eq 'x86_64';
637
638 $pve_flags->{'x2apic'} = {
639 op => '-',
640 reason => "$pve_msg incompatible with Solaris",
641 } if $conf->{ostype} && $conf->{ostype} eq 'solaris';
642
643 $pve_flags->{'sep'} = {
644 op => '+',
645 reason => "$pve_msg to support Windows 8+ and improve Windows XP+",
646 } if $cputype eq 'kvm64' || $cputype eq 'kvm32';
647
648 $pve_flags->{'rdtscp'} = {
649 op => '-',
650 reason => "$pve_msg broken on AMD Opteron",
651 } if $cputype =~ m/^Opteron/;
652
653 if (min_version($machine_version, 2, 3) && $kvm && $arch eq 'x86_64') {
654 $pve_flags->{'kvm_pv_unhalt'} = {
655 op => '+',
656 reason => "$pve_msg to improve Linux guest spinlock performance",
657 };
658 $pve_flags->{'kvm_pv_eoi'} = {
659 op => '+',
660 reason => "$pve_msg to improve Linux guest interrupt performance",
661 };
662 }
663
664 return $pve_flags;
665 }
666
667 sub get_hyperv_enlightenments {
668 my ($winversion, $machine_version, $bios, $gpu_passthrough, $hv_vendor_id) = @_;
669
670 return if $winversion < 6;
671 return if $bios && $bios eq 'ovmf' && $winversion < 8;
672
673 my $flags = {};
674 my $default_reason = "automatic Hyper-V enlightenment for Windows";
675 my $flagfn = sub {
676 my ($flag, $value, $reason) = @_;
677 $flags->{$flag} = {
678 reason => $reason // $default_reason,
679 value => $value,
680 }
681 };
682
683 my $hv_vendor_set = defined($hv_vendor_id);
684 if ($gpu_passthrough || $hv_vendor_set) {
685 $hv_vendor_id //= 'proxmox';
686 $flagfn->('hv_vendor_id', $hv_vendor_id, $hv_vendor_set ?
687 "custom hv_vendor_id set" : "NVIDIA workaround for GPU passthrough");
688 }
689
690 if (min_version($machine_version, 2, 3)) {
691 $flagfn->('hv_spinlocks', '0x1fff');
692 $flagfn->('hv_vapic');
693 $flagfn->('hv_time');
694 } else {
695 $flagfn->('hv_spinlocks', '0xffff');
696 }
697
698 if (min_version($machine_version, 2, 6)) {
699 $flagfn->('hv_reset');
700 $flagfn->('hv_vpindex');
701 $flagfn->('hv_runtime');
702 }
703
704 if ($winversion >= 7) {
705 my $win7_reason = $default_reason . " 7 and higher";
706 $flagfn->('hv_relaxed', undef, $win7_reason);
707
708 if (min_version($machine_version, 2, 12)) {
709 $flagfn->('hv_synic', undef, $win7_reason);
710 $flagfn->('hv_stimer', undef, $win7_reason);
711 }
712
713 if (min_version($machine_version, 3, 1)) {
714 $flagfn->('hv_ipi', undef, $win7_reason);
715 }
716 }
717
718 return $flags;
719 }
720
721 sub get_cpu_from_running_vm {
722 my ($pid) = @_;
723
724 my $cmdline = PVE::QemuServer::Helpers::parse_cmdline($pid);
725 die "could not read commandline of running machine\n"
726 if !$cmdline->{cpu}->{value};
727
728 # sanitize and untaint value
729 $cmdline->{cpu}->{value} =~ $qemu_cmdline_cpu_re;
730 return $1;
731 }
732
733 sub get_default_cpu_type {
734 my ($arch, $kvm) = @_;
735
736 my $cputype = $kvm ? 'kvm64' : 'qemu64';
737 $cputype = 'cortex-a57' if $arch eq 'aarch64';
738
739 return $cputype;
740 }
741
742 sub is_native_arch($) {
743 my ($arch) = @_;
744 return get_host_arch() eq $arch;
745 }
746
747 sub get_cpu_bitness {
748 my ($cpu_prop_str, $arch) = @_;
749
750 $arch //= get_host_arch();
751
752 my $cputype = get_default_cpu_type($arch, 0);
753
754 if ($cpu_prop_str) {
755 my $cpu = PVE::JSONSchema::parse_property_string('pve-vm-cpu-conf', $cpu_prop_str)
756 or die "Cannot parse cpu description: $cpu_prop_str\n";
757
758 my $cputype = $cpu->{cputype};
759
760 if (my $model = $builtin_models->{$cputype}) {
761 $cputype = $model->{'reported-model'};
762 } elsif (is_custom_model($cputype)) {
763 my $custom_cpu = get_custom_model($cputype);
764 $cputype = $custom_cpu->{'reported-model'} // $cpu_fmt->{'reported-model'}->{default};
765 }
766 }
767
768 return $cputypes_32bit->{$cputype} ? 32 : 64 if $arch eq 'x86_64';
769 return 64 if $arch eq 'aarch64';
770
771 die "unsupported architecture '$arch'\n";
772 }
773
774 __PACKAGE__->register();
775 __PACKAGE__->init();
776
777 1;