]> git.proxmox.com Git - qemu-server.git/blob - PVE/QemuServer/CPUConfig.pm
os type: add Windows Server 2025 as supported with win11 type
[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 # 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
424 my $kvm = $conf->{kvm} // is_native_arch($arch);
425 my $cpu = get_default_cpu_type('x86_64', $kvm);
426 if (my $cputype = $conf->{cpu}) {
427 my $cpuconf = PVE::JSONSchema::parse_property_string('pve-vm-cpu-conf', $cputype)
428 or die "Cannot parse cpu description: $cputype\n";
429 $cpu = $cpuconf->{cputype};
430
431 if (my $model = $builtin_models->{$cpu}) {
432 $cpu = $model->{'reported-model'};
433 } elsif (is_custom_model($cputype)) {
434 my $custom_cpu = get_custom_model($cpu);
435
436 $cpu = $custom_cpu->{'reported-model'} // $cpu_fmt->{'reported-model'}->{default};
437 }
438 if (my $replacement_type = $depreacated_cpu_map->{$cpu}) {
439 $cpu = $replacement_type;
440 }
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
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 # }
464 sub 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
506 sub 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
515 sub parse_cpuflag_list {
516 my ($re, $reason, $flaglist) = @_;
517
518 my $res = {};
519 return $res if !$flaglist;
520
521 foreach my $flag (split(";", $flaglist)) {
522 if ($flag =~ m/^$re$/) {
523 $res->{$2} = { op => $1, reason => $reason };
524 }
525 }
526
527 return $res;
528 }
529
530 # Calculate QEMU's '-cpu' argument from a given VM configuration
531 sub get_cpu_options {
532 my ($conf, $arch, $kvm, $kvm_off, $machine_version, $winversion, $gpu_passthrough) = @_;
533
534 my $cputype = get_default_cpu_type($arch, $kvm);
535
536 my $cpu = {};
537 my $custom_cpu;
538 my $builtin_cpu;
539 my $hv_vendor_id;
540 if (my $cpu_prop_str = $conf->{cpu}) {
541 $cpu = PVE::JSONSchema::parse_property_string('pve-vm-cpu-conf', $cpu_prop_str)
542 or die "Cannot parse cpu description: $cpu_prop_str\n";
543
544 $cputype = $cpu->{cputype};
545 if (my $model = $builtin_models->{$cputype}) {
546 $cputype = $model->{'reported-model'};
547 $builtin_cpu->{flags} = $model->{'flags'};
548 } elsif (is_custom_model($cputype)) {
549 $custom_cpu = get_custom_model($cputype);
550
551 $cputype = $custom_cpu->{'reported-model'} // $cpu_fmt->{'reported-model'}->{default};
552 $kvm_off = $custom_cpu->{hidden} if defined($custom_cpu->{hidden});
553 $hv_vendor_id = $custom_cpu->{'hv-vendor-id'};
554 }
555
556 if (my $replacement_type = $depreacated_cpu_map->{$cputype}) {
557 $cputype = $replacement_type;
558 }
559
560 # VM-specific settings override custom CPU config
561 $kvm_off = $cpu->{hidden} if defined($cpu->{hidden});
562 $hv_vendor_id = $cpu->{'hv-vendor-id'} if defined($cpu->{'hv-vendor-id'});
563 }
564
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;
576
577 my $builtin_cputype_flags = parse_cpuflag_list(
578 $cpu_flag_any_re, "set by builtin CPU model", $builtin_cpu->{flags});
579
580 my $custom_cputype_flags = parse_cpuflag_list(
581 $cpu_flag_any_re, "set by custom CPU model", $custom_cpu->{flags});
582
583 my $vm_flags = parse_cpuflag_list(
584 $cpu_flag_supported_re, "manually set for VM", $cpu->{flags});
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
604 }
605
606 my $cpu_str = $cputype;
607
608 # will be resolved in parameter order
609 $cpu_str .= resolve_cpu_flags(
610 $pve_flags, $hv_flags, $builtin_cputype_flags, $custom_cputype_flags, $vm_flags, $pve_forced_flags);
611
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
625 return ('-cpu', $cpu_str);
626 }
627
628 # Some hardcoded flags required by certain configurations
629 sub 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 };
664 }
665
666 return $pve_flags;
667 }
668
669 sub get_hyperv_enlightenments {
670 my ($winversion, $machine_version, $bios, $gpu_passthrough, $hv_vendor_id) = @_;
671
672 return if $winversion < 6;
673 return if $bios && $bios eq 'ovmf' && $winversion < 8;
674
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) {
687 $hv_vendor_id //= 'proxmox';
688 $flagfn->('hv_vendor_id', $hv_vendor_id, $hv_vendor_set ?
689 "custom hv_vendor_id set" : "NVIDIA workaround for GPU passthrough");
690 }
691
692 if (min_version($machine_version, 2, 3)) {
693 $flagfn->('hv_spinlocks', '0x1fff');
694 $flagfn->('hv_vapic');
695 $flagfn->('hv_time');
696 } else {
697 $flagfn->('hv_spinlocks', '0xffff');
698 }
699
700 if (min_version($machine_version, 2, 6)) {
701 $flagfn->('hv_reset');
702 $flagfn->('hv_vpindex');
703 $flagfn->('hv_runtime');
704 }
705
706 if ($winversion >= 7) {
707 my $win7_reason = $default_reason . " 7 and higher";
708 $flagfn->('hv_relaxed', undef, $win7_reason);
709
710 if (min_version($machine_version, 2, 12)) {
711 $flagfn->('hv_synic', undef, $win7_reason);
712 $flagfn->('hv_stimer', undef, $win7_reason);
713 }
714
715 if (min_version($machine_version, 3, 1)) {
716 $flagfn->('hv_ipi', undef, $win7_reason);
717 }
718 }
719
720 return $flags;
721 }
722
723 sub 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
735 sub 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
744 sub is_native_arch($) {
745 my ($arch) = @_;
746 return get_host_arch() eq $arch;
747 }
748
749 sub 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
760 my $cputype = $cpu->{cputype};
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
776 __PACKAGE__->register();
777 __PACKAGE__->init();
778
779 1;