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