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