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