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