]>
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', | |
82 | ||
83 | # generic types, use vendor from host node | |
84 | host => 'default', | |
85 | kvm32 => 'default', | |
86 | kvm64 => 'default', | |
87 | qemu32 => 'default', | |
88 | qemu64 => 'default', | |
89 | max => 'default', | |
90 | }; | |
91 | ||
92 | my @supported_cpu_flags = ( | |
93 | 'pcid', | |
94 | 'spec-ctrl', | |
95 | 'ibpb', | |
96 | 'ssbd', | |
97 | 'virt-ssbd', | |
98 | 'amd-ssbd', | |
99 | 'amd-no-ssb', | |
100 | 'pdpe1gb', | |
101 | 'md-clear', | |
102 | 'hv-tlbflush', | |
103 | 'hv-evmcs', | |
104 | 'aes' | |
105 | ); | |
5d008ad3 SR |
106 | my $cpu_flag_supported_re = qr/([+-])(@{[join('|', @supported_cpu_flags)]})/; |
107 | my $cpu_flag_any_re = qr/([+-])([a-zA-Z0-9\-_\.]+)/; | |
d786a274 | 108 | |
5d008ad3 | 109 | my $cpu_fmt = { |
d786a274 | 110 | cputype => { |
b3e89488 | 111 | description => "Emulated CPU type. Can be default or custom name (custom model names must be prefixed with 'custom-').", |
d786a274 | 112 | type => 'string', |
b3e89488 | 113 | format_description => 'string', |
d786a274 SR |
114 | default => 'kvm64', |
115 | default_key => 1, | |
b3e89488 SR |
116 | optional => 1, |
117 | }, | |
118 | 'reported-model' => { | |
119 | description => "CPU model and vendor to report to the guest. Must be a QEMU/KVM supported model." | |
120 | . " Only valid for custom CPU model definitions, default models will always report themselves to the guest OS.", | |
121 | type => 'string', | |
122 | enum => [ sort { lc("$a") cmp lc("$b") } keys %$cpu_vendor_list ], | |
123 | default => 'kvm64', | |
124 | optional => 1, | |
d786a274 SR |
125 | }, |
126 | hidden => { | |
127 | description => "Do not identify as a KVM virtual machine.", | |
128 | type => 'boolean', | |
129 | optional => 1, | |
130 | default => 0 | |
131 | }, | |
132 | 'hv-vendor-id' => { | |
133 | type => 'string', | |
134 | pattern => qr/[a-zA-Z0-9]{1,12}/, | |
135 | format_description => 'vendor-id', | |
136 | description => 'The Hyper-V vendor ID. Some drivers or programs inside Windows guests need a specific ID.', | |
137 | optional => 1, | |
138 | }, | |
139 | flags => { | |
140 | description => "List of additional CPU flags separated by ';'." | |
141 | . " Use '+FLAG' to enable, '-FLAG' to disable a flag." | |
5d008ad3 SR |
142 | . " Custom CPU models can specify any flag supported by" |
143 | . " QEMU/KVM, VM-specific flags must be from the following" | |
144 | . " set for security reasons: @{[join(', ', @supported_cpu_flags)]}.", | |
d786a274 SR |
145 | format_description => '+FLAG[;-FLAG...]', |
146 | type => 'string', | |
5d008ad3 | 147 | pattern => qr/$cpu_flag_any_re(;$cpu_flag_any_re)*/, |
d786a274 SR |
148 | optional => 1, |
149 | }, | |
150 | }; | |
151 | ||
5d008ad3 SR |
152 | # $cpu_fmt describes both the CPU config passed as part of a VM config, as well |
153 | # as the definition of a custom CPU model. There are some slight differences | |
154 | # though, which we catch in the custom verification function below. | |
155 | PVE::JSONSchema::register_format('pve-cpu-conf', \&parse_cpu_conf_basic); | |
156 | sub parse_cpu_conf_basic { | |
157 | my ($cpu_str, $noerr) = @_; | |
158 | ||
159 | my $cpu = eval { PVE::JSONSchema::parse_property_string($cpu_fmt, $cpu_str) }; | |
160 | if ($@) { | |
161 | die $@ if !$noerr; | |
162 | return undef; | |
163 | } | |
164 | ||
165 | # required, but can't be forced in schema since it's encoded in section | |
166 | # header for custom models | |
167 | if (!$cpu->{cputype}) { | |
168 | die "CPU is missing cputype\n" if !$noerr; | |
169 | return undef; | |
170 | } | |
171 | ||
172 | return $cpu; | |
173 | } | |
174 | ||
175 | PVE::JSONSchema::register_format('pve-vm-cpu-conf', \&parse_vm_cpu_conf); | |
176 | sub parse_vm_cpu_conf { | |
177 | my ($cpu_str, $noerr) = @_; | |
178 | ||
179 | my $cpu = parse_cpu_conf_basic($cpu_str, $noerr); | |
180 | return undef if !$cpu; | |
181 | ||
182 | my $cputype = $cpu->{cputype}; | |
183 | ||
184 | # a VM-specific config is only valid if the cputype exists | |
185 | if (is_custom_model($cputype)) { | |
186 | eval { get_custom_model($cputype); }; | |
187 | if ($@) { | |
188 | die $@ if !$noerr; | |
189 | return undef; | |
190 | } | |
191 | } else { | |
192 | if (!defined($cpu_vendor_list->{$cputype})) { | |
193 | die "Built-in cputype '$cputype' is not defined (missing 'custom-' prefix?)\n" if !$noerr; | |
194 | return undef; | |
195 | } | |
196 | } | |
197 | ||
198 | # in a VM-specific config, certain properties are limited/forbidden | |
199 | ||
200 | if ($cpu->{flags} && $cpu->{flags} !~ m/$cpu_flag_supported_re(;$cpu_flag_supported_re)*/) { | |
201 | die "VM-specific CPU flags must be a subset of: @{[join(', ', @supported_cpu_flags)]}\n" | |
202 | if !$noerr; | |
203 | return undef; | |
204 | } | |
205 | ||
206 | die "Property 'reported-model' not allowed in VM-specific CPU config.\n" | |
207 | if defined($cpu->{'reported-model'}); | |
208 | ||
209 | return $cpu; | |
210 | } | |
211 | ||
b3e89488 SR |
212 | # Section config settings |
213 | my $defaultData = { | |
214 | # shallow copy, since SectionConfig modifies propertyList internally | |
215 | propertyList => { %$cpu_fmt }, | |
216 | }; | |
217 | ||
218 | sub private { | |
219 | return $defaultData; | |
220 | } | |
221 | ||
222 | sub options { | |
223 | return { %$cpu_fmt }; | |
224 | } | |
225 | ||
226 | sub type { | |
227 | return 'cpu-model'; | |
228 | } | |
229 | ||
230 | sub parse_section_header { | |
231 | my ($class, $line) = @_; | |
232 | ||
233 | my ($type, $sectionId, $errmsg, $config) = | |
234 | $class->SUPER::parse_section_header($line); | |
235 | ||
236 | return undef if !$type; | |
237 | return ($type, $sectionId, $errmsg, { | |
238 | # name is given by section header, and we can always prepend 'custom-' | |
239 | # since we're reading the custom CPU file | |
240 | cputype => "custom-$sectionId", | |
241 | }); | |
242 | } | |
243 | ||
244 | sub write_config { | |
245 | my ($class, $filename, $cfg) = @_; | |
246 | ||
247 | mkdir "/etc/pve/virtual-guest"; | |
248 | ||
249 | for my $model (keys %{$cfg->{ids}}) { | |
250 | my $model_conf = $cfg->{ids}->{$model}; | |
251 | ||
252 | die "internal error: tried saving built-in CPU model (or missing prefix): $model_conf->{cputype}\n" | |
253 | if !is_custom_model($model_conf->{cputype}); | |
254 | ||
255 | die "internal error: tried saving custom cpumodel with cputype (ignoring prefix: $model_conf->{cputype}) not equal to \$cfg->ids entry ($model)\n" | |
256 | if "custom-$model" ne $model_conf->{cputype}; | |
257 | ||
258 | # saved in section header | |
259 | delete $model_conf->{cputype}; | |
260 | } | |
261 | ||
262 | $class->SUPER::write_config($filename, $cfg); | |
263 | } | |
264 | ||
265 | sub is_custom_model { | |
266 | my ($cputype) = @_; | |
267 | return $cputype =~ m/^custom-/; | |
268 | } | |
269 | ||
270 | # Use this to get a single model in the format described by $cpu_fmt. | |
271 | # Allows names with and without custom- prefix. | |
272 | sub get_custom_model { | |
273 | my ($name, $noerr) = @_; | |
274 | ||
275 | $name =~ s/^custom-//; | |
276 | my $conf = load_custom_model_conf(); | |
277 | ||
278 | my $entry = $conf->{ids}->{$name}; | |
279 | if (!defined($entry)) { | |
280 | die "Custom cputype '$name' not found\n" if !$noerr; | |
281 | return undef; | |
282 | } | |
283 | ||
284 | my $model = {}; | |
285 | for my $property (keys %$cpu_fmt) { | |
286 | if (my $value = $entry->{$property}) { | |
287 | $model->{$property} = $value; | |
288 | } | |
289 | } | |
290 | ||
291 | return $model; | |
292 | } | |
293 | ||
d786a274 SR |
294 | # Print a QEMU device node for a given VM configuration for hotplugging CPUs |
295 | sub print_cpu_device { | |
296 | my ($conf, $id) = @_; | |
297 | ||
298 | my $kvm = $conf->{kvm} // 1; | |
299 | my $cpu = $kvm ? "kvm64" : "qemu64"; | |
300 | if (my $cputype = $conf->{cpu}) { | |
5d008ad3 | 301 | my $cpuconf = parse_cpu_conf_basic($cputype) |
d786a274 SR |
302 | or die "Cannot parse cpu description: $cputype\n"; |
303 | $cpu = $cpuconf->{cputype}; | |
b3e89488 SR |
304 | |
305 | if (is_custom_model($cpu)) { | |
306 | my $custom_cpu = get_custom_model($cpu); | |
307 | ||
308 | $cpu = $custom_cpu->{'reported-model'} // | |
309 | $cpu_fmt->{'reported-model'}->{default}; | |
310 | } | |
d786a274 SR |
311 | } |
312 | ||
313 | my $cores = $conf->{cores} || 1; | |
314 | ||
315 | my $current_core = ($id - 1) % $cores; | |
316 | my $current_socket = int(($id - 1 - $current_core)/$cores); | |
317 | ||
318 | return "$cpu-x86_64-cpu,id=cpu$id,socket-id=$current_socket,core-id=$current_core,thread-id=0"; | |
319 | } | |
320 | ||
321 | # Calculate QEMU's '-cpu' argument from a given VM configuration | |
322 | sub get_cpu_options { | |
323 | my ($conf, $arch, $kvm, $kvm_off, $machine_version, $winversion, $gpu_passthrough) = @_; | |
324 | ||
325 | my $cpuFlags = []; | |
326 | my $ostype = $conf->{ostype}; | |
327 | ||
328 | my $cpu = $kvm ? "kvm64" : "qemu64"; | |
329 | if ($arch eq 'aarch64') { | |
330 | $cpu = 'cortex-a57'; | |
331 | } | |
332 | my $hv_vendor_id; | |
333 | if (my $cputype = $conf->{cpu}) { | |
334 | my $cpuconf = PVE::JSONSchema::parse_property_string($cpu_fmt, $cputype) | |
335 | or die "Cannot parse cpu description: $cputype\n"; | |
336 | $cpu = $cpuconf->{cputype}; | |
337 | $kvm_off = 1 if $cpuconf->{hidden}; | |
338 | $hv_vendor_id = $cpuconf->{'hv-vendor-id'}; | |
339 | ||
340 | if (defined(my $flags = $cpuconf->{flags})) { | |
341 | push @$cpuFlags, split(";", $flags); | |
342 | } | |
343 | } | |
344 | ||
345 | push @$cpuFlags , '+lahf_lm' if $cpu eq 'kvm64' && $arch eq 'x86_64'; | |
346 | ||
347 | push @$cpuFlags , '-x2apic' if $ostype && $ostype eq 'solaris'; | |
348 | ||
349 | push @$cpuFlags, '+sep' if $cpu eq 'kvm64' || $cpu eq 'kvm32'; | |
350 | ||
351 | push @$cpuFlags, '-rdtscp' if $cpu =~ m/^Opteron/; | |
352 | ||
353 | if (min_version($machine_version, 2, 3) && $arch eq 'x86_64') { | |
354 | ||
355 | push @$cpuFlags , '+kvm_pv_unhalt' if $kvm; | |
356 | push @$cpuFlags , '+kvm_pv_eoi' if $kvm; | |
357 | } | |
358 | ||
359 | add_hyperv_enlightenments($cpuFlags, $winversion, $machine_version, $conf->{bios}, $gpu_passthrough, $hv_vendor_id) if $kvm; | |
360 | ||
361 | push @$cpuFlags, 'enforce' if $cpu ne 'host' && $kvm && $arch eq 'x86_64'; | |
362 | ||
363 | push @$cpuFlags, 'kvm=off' if $kvm_off; | |
364 | ||
365 | if (my $cpu_vendor = $cpu_vendor_list->{$cpu}) { | |
366 | push @$cpuFlags, "vendor=${cpu_vendor}" | |
367 | if $cpu_vendor ne 'default'; | |
368 | } elsif ($arch ne 'aarch64') { | |
369 | die "internal error"; # should not happen | |
370 | } | |
371 | ||
372 | $cpu .= "," . join(',', @$cpuFlags) if scalar(@$cpuFlags); | |
373 | ||
374 | return ('-cpu', $cpu); | |
375 | } | |
376 | ||
377 | sub add_hyperv_enlightenments { | |
378 | my ($cpuFlags, $winversion, $machine_version, $bios, $gpu_passthrough, $hv_vendor_id) = @_; | |
379 | ||
380 | return if $winversion < 6; | |
381 | return if $bios && $bios eq 'ovmf' && $winversion < 8; | |
382 | ||
383 | if ($gpu_passthrough || defined($hv_vendor_id)) { | |
384 | $hv_vendor_id //= 'proxmox'; | |
385 | push @$cpuFlags , "hv_vendor_id=$hv_vendor_id"; | |
386 | } | |
387 | ||
388 | if (min_version($machine_version, 2, 3)) { | |
389 | push @$cpuFlags , 'hv_spinlocks=0x1fff'; | |
390 | push @$cpuFlags , 'hv_vapic'; | |
391 | push @$cpuFlags , 'hv_time'; | |
392 | } else { | |
393 | push @$cpuFlags , 'hv_spinlocks=0xffff'; | |
394 | } | |
395 | ||
396 | if (min_version($machine_version, 2, 6)) { | |
397 | push @$cpuFlags , 'hv_reset'; | |
398 | push @$cpuFlags , 'hv_vpindex'; | |
399 | push @$cpuFlags , 'hv_runtime'; | |
400 | } | |
401 | ||
402 | if ($winversion >= 7) { | |
403 | push @$cpuFlags , 'hv_relaxed'; | |
404 | ||
405 | if (min_version($machine_version, 2, 12)) { | |
406 | push @$cpuFlags , 'hv_synic'; | |
407 | push @$cpuFlags , 'hv_stimer'; | |
408 | } | |
409 | ||
410 | if (min_version($machine_version, 3, 1)) { | |
411 | push @$cpuFlags , 'hv_ipi'; | |
412 | } | |
413 | } | |
414 | } | |
415 | ||
b3e89488 SR |
416 | __PACKAGE__->register(); |
417 | __PACKAGE__->init(); | |
418 | ||
d786a274 | 419 | 1; |