]> git.proxmox.com Git - qemu-server.git/blame - PVE/QemuServer/CPUConfig.pm
cpu config: map depreacated IceLake-Client CPU type to IceLake-Server
[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";
24cfs_register_file($default_filename,
25 sub { PVE::QemuServer::CPUConfig->parse_config(@_); },
26 sub { PVE::QemuServer::CPUConfig->write_config(@_); });
27
28sub load_custom_model_conf {
29 return cfs_read_file($default_filename);
30}
31
0d6962f9
TL
32my $depreacated_cpu_map = {
33 # there never was such a client CPU, so map it to the server one for backward compat
34 'Icelake-Client' => 'Icelake-Server',
35 'Icelake-Client-noTSX' => 'Icelake-Server-noTSX',
36};
37
d786a274
SR
38my $cpu_vendor_list = {
39 # Intel CPUs
40 486 => 'GenuineIntel',
41 pentium => 'GenuineIntel',
42 pentium2 => 'GenuineIntel',
43 pentium3 => 'GenuineIntel',
44 coreduo => 'GenuineIntel',
45 core2duo => 'GenuineIntel',
46 Conroe => 'GenuineIntel',
47 Penryn => 'GenuineIntel',
48 Nehalem => 'GenuineIntel',
49 'Nehalem-IBRS' => 'GenuineIntel',
50 Westmere => 'GenuineIntel',
51 'Westmere-IBRS' => 'GenuineIntel',
52 SandyBridge => 'GenuineIntel',
53 'SandyBridge-IBRS' => 'GenuineIntel',
54 IvyBridge => 'GenuineIntel',
55 'IvyBridge-IBRS' => 'GenuineIntel',
56 Haswell => 'GenuineIntel',
57 'Haswell-IBRS' => 'GenuineIntel',
58 'Haswell-noTSX' => 'GenuineIntel',
59 'Haswell-noTSX-IBRS' => 'GenuineIntel',
60 Broadwell => 'GenuineIntel',
61 'Broadwell-IBRS' => 'GenuineIntel',
62 'Broadwell-noTSX' => 'GenuineIntel',
63 'Broadwell-noTSX-IBRS' => 'GenuineIntel',
64 'Skylake-Client' => 'GenuineIntel',
65 'Skylake-Client-IBRS' => 'GenuineIntel',
bb84db9d 66 'Skylake-Client-noTSX-IBRS' => 'GenuineIntel',
d786a274
SR
67 'Skylake-Server' => 'GenuineIntel',
68 'Skylake-Server-IBRS' => 'GenuineIntel',
bb84db9d 69 'Skylake-Server-noTSX-IBRS' => 'GenuineIntel',
d786a274 70 'Cascadelake-Server' => 'GenuineIntel',
bb84db9d 71 'Cascadelake-Server-noTSX' => 'GenuineIntel',
d786a274 72 KnightsMill => 'GenuineIntel',
0d6962f9
TL
73 'Icelake-Client' => 'GenuineIntel', # depreacated, removed with QEMU 7.1
74 'Icelake-Client-noTSX' => 'GenuineIntel', # depreacated, removed with QEMU 7.1
bb84db9d 75 'Icelake-Server' => 'GenuineIntel',
d0cdb1de 76 'Icelake-Server-noTSX' => 'GenuineIntel',
d786a274
SR
77
78 # AMD CPUs
79 athlon => 'AuthenticAMD',
80 phenom => 'AuthenticAMD',
81 Opteron_G1 => 'AuthenticAMD',
82 Opteron_G2 => 'AuthenticAMD',
83 Opteron_G3 => 'AuthenticAMD',
84 Opteron_G4 => 'AuthenticAMD',
85 Opteron_G5 => 'AuthenticAMD',
86 EPYC => 'AuthenticAMD',
87 'EPYC-IBPB' => 'AuthenticAMD',
8cea210f 88 'EPYC-Rome' => 'AuthenticAMD',
9b1971c5 89 'EPYC-Milan' => 'AuthenticAMD',
d786a274
SR
90
91 # generic types, use vendor from host node
92 host => 'default',
93 kvm32 => 'default',
94 kvm64 => 'default',
95 qemu32 => 'default',
96 qemu64 => 'default',
97 max => 'default',
98};
99
100my @supported_cpu_flags = (
101 'pcid',
102 'spec-ctrl',
103 'ibpb',
104 'ssbd',
105 'virt-ssbd',
106 'amd-ssbd',
107 'amd-no-ssb',
108 'pdpe1gb',
109 'md-clear',
110 'hv-tlbflush',
111 'hv-evmcs',
112 'aes'
113);
5d008ad3
SR
114my $cpu_flag_supported_re = qr/([+-])(@{[join('|', @supported_cpu_flags)]})/;
115my $cpu_flag_any_re = qr/([+-])([a-zA-Z0-9\-_\.]+)/;
d786a274 116
c15b5971 117our $qemu_cmdline_cpu_re = qr/^((?>[+-]?[\w\-\._=]+,?)+)$/;
58c64ad5 118
5d008ad3 119my $cpu_fmt = {
d786a274 120 cputype => {
b3e89488 121 description => "Emulated CPU type. Can be default or custom name (custom model names must be prefixed with 'custom-').",
d786a274 122 type => 'string',
b3e89488 123 format_description => 'string',
d786a274
SR
124 default => 'kvm64',
125 default_key => 1,
b3e89488
SR
126 optional => 1,
127 },
128 'reported-model' => {
129 description => "CPU model and vendor to report to the guest. Must be a QEMU/KVM supported model."
11f9264f 130 ." Only valid for custom CPU model definitions, default models will always report themselves to the guest OS.",
b3e89488
SR
131 type => 'string',
132 enum => [ sort { lc("$a") cmp lc("$b") } keys %$cpu_vendor_list ],
133 default => 'kvm64',
134 optional => 1,
d786a274
SR
135 },
136 hidden => {
137 description => "Do not identify as a KVM virtual machine.",
138 type => 'boolean',
139 optional => 1,
140 default => 0
141 },
142 'hv-vendor-id' => {
143 type => 'string',
144 pattern => qr/[a-zA-Z0-9]{1,12}/,
145 format_description => 'vendor-id',
146 description => 'The Hyper-V vendor ID. Some drivers or programs inside Windows guests need a specific ID.',
147 optional => 1,
148 },
149 flags => {
11f9264f
TL
150 description => "List of additional CPU flags separated by ';'. Use '+FLAG' to enable,"
151 ." '-FLAG' to disable a flag. Custom CPU models can specify any flag supported by"
152 ." QEMU/KVM, VM-specific flags must be from the following set for security reasons: "
153 . join(', ', @supported_cpu_flags),
d786a274
SR
154 format_description => '+FLAG[;-FLAG...]',
155 type => 'string',
5d008ad3 156 pattern => qr/$cpu_flag_any_re(;$cpu_flag_any_re)*/,
d786a274
SR
157 optional => 1,
158 },
9f9792d3
SR
159 'phys-bits' => {
160 type => 'string',
161 format => 'pve-phys-bits',
7b8c4de3 162 format_description => '8-64|host',
11f9264f
TL
163 description => "The physical memory address bits that are reported to the guest OS. Should"
164 ." be smaller or equal to the host's. Set to 'host' to use value from host CPU, but"
165 ." note that doing so will break live migration to CPUs with other values.",
9f9792d3
SR
166 optional => 1,
167 },
d786a274
SR
168};
169
9f9792d3
SR
170PVE::JSONSchema::register_format('pve-phys-bits', \&parse_phys_bits);
171sub parse_phys_bits {
172 my ($str, $noerr) = @_;
173
174 my $err_msg = "value must be an integer between 8 and 64 or 'host'\n";
175
176 if ($str !~ m/^(host|\d{1,2})$/) {
177 die $err_msg if !$noerr;
d1c1af4b 178 return;
9f9792d3
SR
179 }
180
181 if ($str =~ m/^\d+$/ && (int($str) < 8 || int($str) > 64)) {
182 die $err_msg if !$noerr;
d1c1af4b 183 return;
9f9792d3
SR
184 }
185
186 return $str;
187}
188
5d008ad3
SR
189# $cpu_fmt describes both the CPU config passed as part of a VM config, as well
190# as the definition of a custom CPU model. There are some slight differences
7b8c4de3
SR
191# though, which we catch in the custom validation functions below.
192PVE::JSONSchema::register_format('pve-cpu-conf', $cpu_fmt, \&validate_cpu_conf);
193sub validate_cpu_conf {
194 my ($cpu) = @_;
5d008ad3
SR
195
196 # required, but can't be forced in schema since it's encoded in section
197 # header for custom models
7b8c4de3 198 die "CPU is missing cputype\n" if !$cpu->{cputype};
b2e813a6
FE
199
200 return $cpu;
5d008ad3 201}
7b8c4de3
SR
202PVE::JSONSchema::register_format('pve-vm-cpu-conf', $cpu_fmt, \&validate_vm_cpu_conf);
203sub validate_vm_cpu_conf {
204 my ($cpu) = @_;
5d008ad3 205
7b8c4de3 206 validate_cpu_conf($cpu);
5d008ad3
SR
207
208 my $cputype = $cpu->{cputype};
209
210 # a VM-specific config is only valid if the cputype exists
211 if (is_custom_model($cputype)) {
7b8c4de3
SR
212 # dies on unknown model
213 get_custom_model($cputype);
5d008ad3 214 } else {
7b8c4de3
SR
215 die "Built-in cputype '$cputype' is not defined (missing 'custom-' prefix?)\n"
216 if !defined($cpu_vendor_list->{$cputype});
5d008ad3
SR
217 }
218
219 # in a VM-specific config, certain properties are limited/forbidden
220
7b8c4de3 221 die "VM-specific CPU flags must be a subset of: @{[join(', ', @supported_cpu_flags)]}\n"
4893f9b9 222 if ($cpu->{flags} && $cpu->{flags} !~ m/^$cpu_flag_supported_re(;$cpu_flag_supported_re)*$/);
5d008ad3
SR
223
224 die "Property 'reported-model' not allowed in VM-specific CPU config.\n"
225 if defined($cpu->{'reported-model'});
226
227 return $cpu;
228}
229
b3e89488
SR
230# Section config settings
231my $defaultData = {
232 # shallow copy, since SectionConfig modifies propertyList internally
233 propertyList => { %$cpu_fmt },
234};
235
236sub private {
237 return $defaultData;
238}
239
240sub options {
241 return { %$cpu_fmt };
242}
243
244sub type {
245 return 'cpu-model';
246}
247
248sub parse_section_header {
249 my ($class, $line) = @_;
250
251 my ($type, $sectionId, $errmsg, $config) =
252 $class->SUPER::parse_section_header($line);
253
d1c1af4b 254 return if !$type;
b3e89488
SR
255 return ($type, $sectionId, $errmsg, {
256 # name is given by section header, and we can always prepend 'custom-'
257 # since we're reading the custom CPU file
258 cputype => "custom-$sectionId",
259 });
260}
261
262sub write_config {
263 my ($class, $filename, $cfg) = @_;
264
265 mkdir "/etc/pve/virtual-guest";
266
267 for my $model (keys %{$cfg->{ids}}) {
268 my $model_conf = $cfg->{ids}->{$model};
269
270 die "internal error: tried saving built-in CPU model (or missing prefix): $model_conf->{cputype}\n"
271 if !is_custom_model($model_conf->{cputype});
272
273 die "internal error: tried saving custom cpumodel with cputype (ignoring prefix: $model_conf->{cputype}) not equal to \$cfg->ids entry ($model)\n"
274 if "custom-$model" ne $model_conf->{cputype};
275
276 # saved in section header
277 delete $model_conf->{cputype};
278 }
279
280 $class->SUPER::write_config($filename, $cfg);
281}
282
1b7824d3
SR
283sub add_cpu_json_properties {
284 my ($prop) = @_;
285
286 foreach my $opt (keys %$cpu_fmt) {
287 $prop->{$opt} = $cpu_fmt->{$opt};
288 }
289
290 return $prop;
291}
292
a73cc993
SR
293sub get_cpu_models {
294 my ($include_custom) = @_;
295
296 my $models = [];
297
298 for my $default_model (keys %{$cpu_vendor_list}) {
299 push @$models, {
300 name => $default_model,
301 custom => 0,
302 vendor => $cpu_vendor_list->{$default_model},
303 };
304 }
305
306 return $models if !$include_custom;
307
308 my $conf = load_custom_model_conf();
309 for my $custom_model (keys %{$conf->{ids}}) {
310 my $reported_model = $conf->{ids}->{$custom_model}->{'reported-model'};
311 $reported_model //= $cpu_fmt->{'reported-model'}->{default};
312 my $vendor = $cpu_vendor_list->{$reported_model};
313 push @$models, {
314 name => "custom-$custom_model",
315 custom => 1,
316 vendor => $vendor,
317 };
318 }
319
320 return $models;
321}
322
b3e89488
SR
323sub is_custom_model {
324 my ($cputype) = @_;
325 return $cputype =~ m/^custom-/;
326}
327
328# Use this to get a single model in the format described by $cpu_fmt.
329# Allows names with and without custom- prefix.
330sub get_custom_model {
331 my ($name, $noerr) = @_;
332
333 $name =~ s/^custom-//;
334 my $conf = load_custom_model_conf();
335
336 my $entry = $conf->{ids}->{$name};
337 if (!defined($entry)) {
338 die "Custom cputype '$name' not found\n" if !$noerr;
d1c1af4b 339 return;
b3e89488
SR
340 }
341
342 my $model = {};
343 for my $property (keys %$cpu_fmt) {
344 if (my $value = $entry->{$property}) {
345 $model->{$property} = $value;
346 }
347 }
348
349 return $model;
350}
351
d786a274
SR
352# Print a QEMU device node for a given VM configuration for hotplugging CPUs
353sub print_cpu_device {
354 my ($conf, $id) = @_;
355
356 my $kvm = $conf->{kvm} // 1;
357 my $cpu = $kvm ? "kvm64" : "qemu64";
358 if (my $cputype = $conf->{cpu}) {
7b8c4de3 359 my $cpuconf = PVE::JSONSchema::parse_property_string('pve-vm-cpu-conf', $cputype)
d786a274
SR
360 or die "Cannot parse cpu description: $cputype\n";
361 $cpu = $cpuconf->{cputype};
b3e89488
SR
362
363 if (is_custom_model($cpu)) {
364 my $custom_cpu = get_custom_model($cpu);
365
b0ab3463 366 $cpu = $custom_cpu->{'reported-model'} // $cpu_fmt->{'reported-model'}->{default};
0d6962f9
TL
367 } elsif (my $replacement_type = $depreacated_cpu_map->{$cpu}) {
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;