]> git.proxmox.com Git - qemu-server.git/blob - PVE/QemuServer/CPUConfig.pm
cpu config: add QEMU 8.1 cpu models
[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::QemuServer::Helpers qw(min_version);
9
10 use base qw(PVE::SectionConfig Exporter);
11
12 our @EXPORT_OK = qw(
13 print_cpu_device
14 get_cpu_options
15 );
16
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
19 if (PVE::Cluster::check_cfs_is_mounted(1)) {
20 mkdir "/etc/pve/virtual-guest";
21 }
22
23 my $default_filename = "virtual-guest/cpu-models.conf";
24 cfs_register_file(
25 $default_filename,
26 sub { PVE::QemuServer::CPUConfig->parse_config(@_); },
27 sub { PVE::QemuServer::CPUConfig->write_config(@_); },
28 );
29
30 sub load_custom_model_conf {
31 return cfs_read_file($default_filename);
32 }
33
34 #builtin models : reported-model is mandatory
35 my $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
54 my $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
60 my $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',
88 'Skylake-Client-noTSX-IBRS' => 'GenuineIntel',
89 'Skylake-Client-v4' => 'GenuineIntel',
90 'Skylake-Server' => 'GenuineIntel',
91 'Skylake-Server-IBRS' => 'GenuineIntel',
92 'Skylake-Server-noTSX-IBRS' => 'GenuineIntel',
93 'Skylake-Server-v4' => 'GenuineIntel',
94 'Skylake-Server-v5' => 'GenuineIntel',
95 'Cascadelake-Server' => 'GenuineIntel',
96 'Cascadelake-Server-v2' => 'GenuineIntel',
97 'Cascadelake-Server-noTSX' => 'GenuineIntel',
98 'Cascadelake-Server-v4' => 'GenuineIntel',
99 'Cascadelake-Server-v5' => 'GenuineIntel',
100 'Cooperlake' => 'GenuineIntel',
101 'Cooperlake-v2' => 'GenuineIntel',
102 KnightsMill => 'GenuineIntel',
103 'Icelake-Client' => 'GenuineIntel', # depreacated, removed with QEMU 7.1
104 'Icelake-Client-noTSX' => 'GenuineIntel', # depreacated, removed with QEMU 7.1
105 'Icelake-Server' => 'GenuineIntel',
106 'Icelake-Server-noTSX' => 'GenuineIntel',
107 'Icelake-Server-v3' => 'GenuineIntel',
108 'Icelake-Server-v4' => 'GenuineIntel',
109 'Icelake-Server-v5' => 'GenuineIntel',
110 'Icelake-Server-v6' => 'GenuineIntel',
111 'SapphireRapids' => 'GenuineIntel',
112 'SapphireRapids-v2' => 'GenuineIntel',
113 'GraniteRapids' => 'GenuineIntel',
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',
125 'EPYC-v3' => 'AuthenticAMD',
126 'EPYC-v4' => 'AuthenticAMD',
127 'EPYC-Rome' => 'AuthenticAMD',
128 'EPYC-Rome-v2' => 'AuthenticAMD',
129 'EPYC-Rome-v3' => 'AuthenticAMD',
130 'EPYC-Rome-v4' => 'AuthenticAMD',
131 'EPYC-Milan' => 'AuthenticAMD',
132 'EPYC-Milan-v2' => 'AuthenticAMD',
133 'EPYC-Genoa' => 'AuthenticAMD',
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
144 my @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 );
158 my $cpu_flag_supported_re = qr/([+-])(@{[join('|', @supported_cpu_flags)]})/;
159 my $cpu_flag_any_re = qr/([+-])([a-zA-Z0-9\-_\.]+)/;
160
161 our $qemu_cmdline_cpu_re = qr/^((?>[+-]?[\w\-\._=]+,?)+)$/;
162
163 my $cpu_fmt = {
164 cputype => {
165 description => "Emulated CPU type. Can be default or custom name (custom model names must be prefixed with 'custom-').",
166 type => 'string',
167 format_description => 'string',
168 default => 'kvm64',
169 default_key => 1,
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."
174 ." Only valid for custom CPU model definitions, default models will always report themselves to the guest OS.",
175 type => 'string',
176 enum => [ sort { lc("$a") cmp lc("$b") } keys %$cpu_vendor_list ],
177 default => 'kvm64',
178 optional => 1,
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 => {
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),
198 format_description => '+FLAG[;-FLAG...]',
199 type => 'string',
200 pattern => qr/$cpu_flag_any_re(;$cpu_flag_any_re)*/,
201 optional => 1,
202 },
203 'phys-bits' => {
204 type => 'string',
205 format => 'pve-phys-bits',
206 format_description => '8-64|host',
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.",
210 optional => 1,
211 },
212 };
213
214 PVE::JSONSchema::register_format('pve-phys-bits', \&parse_phys_bits);
215 sub 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;
222 return;
223 }
224
225 if ($str =~ m/^\d+$/ && (int($str) < 8 || int($str) > 64)) {
226 die $err_msg if !$noerr;
227 return;
228 }
229
230 return $str;
231 }
232
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
235 # though, which we catch in the custom validation functions below.
236 PVE::JSONSchema::register_format('pve-cpu-conf', $cpu_fmt, \&validate_cpu_conf);
237 sub validate_cpu_conf {
238 my ($cpu) = @_;
239 # required, but can't be forced in schema since it's encoded in section header for custom models
240 die "CPU is missing cputype\n" if !$cpu->{cputype};
241 return $cpu;
242 }
243 PVE::JSONSchema::register_format('pve-vm-cpu-conf', $cpu_fmt, \&validate_vm_cpu_conf);
244 sub validate_vm_cpu_conf {
245 my ($cpu) = @_;
246
247 validate_cpu_conf($cpu);
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)) {
253 # dies on unknown model
254 get_custom_model($cputype);
255 } else {
256 die "Built-in cputype '$cputype' is not defined (missing 'custom-' prefix?)\n"
257 if !defined($cpu_vendor_list->{$cputype}) && !defined($builtin_models->{$cputype});
258 }
259
260 # in a VM-specific config, certain properties are limited/forbidden
261
262 die "VM-specific CPU flags must be a subset of: @{[join(', ', @supported_cpu_flags)]}\n"
263 if ($cpu->{flags} && $cpu->{flags} !~ m/^$cpu_flag_supported_re(;$cpu_flag_supported_re)*$/);
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
271 # Section config settings
272 my $defaultData = {
273 # shallow copy, since SectionConfig modifies propertyList internally
274 propertyList => { %$cpu_fmt },
275 };
276
277 sub private {
278 return $defaultData;
279 }
280
281 sub options {
282 return { %$cpu_fmt };
283 }
284
285 sub type {
286 return 'cpu-model';
287 }
288
289 sub parse_section_header {
290 my ($class, $line) = @_;
291
292 my ($type, $sectionId, $errmsg, $config) =
293 $class->SUPER::parse_section_header($line);
294
295 return if !$type;
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
303 sub 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
324 sub 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
334 sub 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
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
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
374 sub 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.
381 sub 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;
390 return;
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
403 # Print a QEMU device node for a given VM configuration for hotplugging CPUs
404 sub 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}) {
410 my $cpuconf = PVE::JSONSchema::parse_property_string('pve-vm-cpu-conf', $cputype)
411 or die "Cannot parse cpu description: $cputype\n";
412 $cpu = $cpuconf->{cputype};
413
414 if (my $model = $builtin_models->{$cpu}) {
415 $cpu = $model->{'reported-model'};
416 } elsif (is_custom_model($cputype)) {
417 my $custom_cpu = get_custom_model($cpu);
418
419 $cpu = $custom_cpu->{'reported-model'} // $cpu_fmt->{'reported-model'}->{default};
420 }
421 if (my $replacement_type = $depreacated_cpu_map->{$cpu}) {
422 $cpu = $replacement_type;
423 }
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
431 # FIXME: hot plugging other architectures like our unofficial arch64 support?
432 return "$cpu-x86_64-cpu,id=cpu$id,socket-id=$current_socket,core-id=$current_core,thread-id=0";
433 }
434
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 # }
448 sub 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
490 sub 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
499 sub parse_cpuflag_list {
500 my ($re, $reason, $flaglist) = @_;
501
502 my $res = {};
503 return $res if !$flaglist;
504
505 foreach my $flag (split(";", $flaglist)) {
506 if ($flag =~ m/^$re$/) {
507 $res->{$2} = { op => $1, reason => $reason };
508 }
509 }
510
511 return $res;
512 }
513
514 # Calculate QEMU's '-cpu' argument from a given VM configuration
515 sub get_cpu_options {
516 my ($conf, $arch, $kvm, $kvm_off, $machine_version, $winversion, $gpu_passthrough) = @_;
517
518 my $cputype = $kvm ? "kvm64" : "qemu64";
519 if ($arch eq 'aarch64') {
520 $cputype = 'cortex-a57';
521 }
522
523 my $cpu = {};
524 my $custom_cpu;
525 my $builtin_cpu;
526 my $hv_vendor_id;
527 if (my $cpu_prop_str = $conf->{cpu}) {
528 $cpu = PVE::JSONSchema::parse_property_string('pve-vm-cpu-conf', $cpu_prop_str)
529 or die "Cannot parse cpu description: $cpu_prop_str\n";
530
531 $cputype = $cpu->{cputype};
532 if (my $model = $builtin_models->{$cputype}) {
533 $cputype = $model->{'reported-model'};
534 $builtin_cpu->{flags} = $model->{'flags'};
535 } elsif (is_custom_model($cputype)) {
536 $custom_cpu = get_custom_model($cputype);
537
538 $cputype = $custom_cpu->{'reported-model'} // $cpu_fmt->{'reported-model'}->{default};
539 $kvm_off = $custom_cpu->{hidden} if defined($custom_cpu->{hidden});
540 $hv_vendor_id = $custom_cpu->{'hv-vendor-id'};
541 }
542
543 if (my $replacement_type = $depreacated_cpu_map->{$cputype}) {
544 $cputype = $replacement_type;
545 }
546
547 # VM-specific settings override custom CPU config
548 $kvm_off = $cpu->{hidden} if defined($cpu->{hidden});
549 $hv_vendor_id = $cpu->{'hv-vendor-id'} if defined($cpu->{'hv-vendor-id'});
550 }
551
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;
563
564 my $builtin_cputype_flags = parse_cpuflag_list(
565 $cpu_flag_any_re, "set by builtin CPU model", $builtin_cpu->{flags});
566
567 my $custom_cputype_flags = parse_cpuflag_list(
568 $cpu_flag_any_re, "set by custom CPU model", $custom_cpu->{flags});
569
570 my $vm_flags = parse_cpuflag_list(
571 $cpu_flag_supported_re, "manually set for VM", $cpu->{flags});
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
591 }
592
593 my $cpu_str = $cputype;
594
595 # will be resolved in parameter order
596 $cpu_str .= resolve_cpu_flags(
597 $pve_flags, $hv_flags, $builtin_cputype_flags, $custom_cputype_flags, $vm_flags, $pve_forced_flags);
598
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
612 return ('-cpu', $cpu_str);
613 }
614
615 # Some hardcoded flags required by certain configurations
616 sub 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 };
651 }
652
653 return $pve_flags;
654 }
655
656 sub get_hyperv_enlightenments {
657 my ($winversion, $machine_version, $bios, $gpu_passthrough, $hv_vendor_id) = @_;
658
659 return if $winversion < 6;
660 return if $bios && $bios eq 'ovmf' && $winversion < 8;
661
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) {
674 $hv_vendor_id //= 'proxmox';
675 $flagfn->('hv_vendor_id', $hv_vendor_id, $hv_vendor_set ?
676 "custom hv_vendor_id set" : "NVIDIA workaround for GPU passthrough");
677 }
678
679 if (min_version($machine_version, 2, 3)) {
680 $flagfn->('hv_spinlocks', '0x1fff');
681 $flagfn->('hv_vapic');
682 $flagfn->('hv_time');
683 } else {
684 $flagfn->('hv_spinlocks', '0xffff');
685 }
686
687 if (min_version($machine_version, 2, 6)) {
688 $flagfn->('hv_reset');
689 $flagfn->('hv_vpindex');
690 $flagfn->('hv_runtime');
691 }
692
693 if ($winversion >= 7) {
694 my $win7_reason = $default_reason . " 7 and higher";
695 $flagfn->('hv_relaxed', undef, $win7_reason);
696
697 if (min_version($machine_version, 2, 12)) {
698 $flagfn->('hv_synic', undef, $win7_reason);
699 $flagfn->('hv_stimer', undef, $win7_reason);
700 }
701
702 if (min_version($machine_version, 3, 1)) {
703 $flagfn->('hv_ipi', undef, $win7_reason);
704 }
705 }
706
707 return $flags;
708 }
709
710 sub 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
722 __PACKAGE__->register();
723 __PACKAGE__->init();
724
725 1;