]> git.proxmox.com Git - qemu-server.git/blob - PVE/QemuServer/PCI.pm
tree wide cleanups
[qemu-server.git] / PVE / QemuServer / PCI.pm
1 package PVE::QemuServer::PCI;
2
3 use PVE::JSONSchema;
4 use PVE::SysFSTools;
5
6 use base 'Exporter';
7
8 our @EXPORT_OK = qw(
9 print_pci_addr
10 print_pcie_addr
11 print_pcie_root_port
12 parse_hostpci
13 );
14
15 our $MAX_HOSTPCI_DEVICES = 16;
16
17 my $PCIRE = qr/([a-f0-9]{4}:)?[a-f0-9]{2}:[a-f0-9]{2}(?:\.[a-f0-9])?/;
18 my $hostpci_fmt = {
19 host => {
20 default_key => 1,
21 type => 'string',
22 pattern => qr/$PCIRE(;$PCIRE)*/,
23 format_description => 'HOSTPCIID[;HOSTPCIID2...]',
24 description => <<EODESCR,
25 Host PCI device pass through. The PCI ID of a host's PCI device or a list
26 of PCI virtual functions of the host. HOSTPCIID syntax is:
27
28 'bus:dev.func' (hexadecimal numbers)
29
30 You can us the 'lspci' command to list existing PCI devices.
31 EODESCR
32 },
33 rombar => {
34 type => 'boolean',
35 description => "Specify whether or not the device's ROM will be visible in the"
36 ." guest's memory map.",
37 optional => 1,
38 default => 1,
39 },
40 romfile => {
41 type => 'string',
42 pattern => '[^,;]+',
43 format_description => 'string',
44 description => "Custom pci device rom filename (must be located in /usr/share/kvm/).",
45 optional => 1,
46 },
47 pcie => {
48 type => 'boolean',
49 description => "Choose the PCI-express bus (needs the 'q35' machine model).",
50 optional => 1,
51 default => 0,
52 },
53 'x-vga' => {
54 type => 'boolean',
55 description => "Enable vfio-vga device support.",
56 optional => 1,
57 default => 0,
58 },
59 'legacy-igd' => {
60 type => 'boolean',
61 description => "Pass this device in legacy IGD mode, making it the primary and exclusive"
62 ." graphics device in the VM. Requires 'pc-i440fx' machine type and VGA set to 'none'.",
63 optional => 1,
64 default => 0,
65 },
66 'mdev' => {
67 type => 'string',
68 format_description => 'string',
69 pattern => '[^/\.:]+',
70 optional => 1,
71 description => <<EODESCR
72 The type of mediated device to use.
73 An instance of this type will be created on startup of the VM and
74 will be cleaned up when the VM stops.
75 EODESCR
76 }
77 };
78 PVE::JSONSchema::register_format('pve-qm-hostpci', $hostpci_fmt);
79
80 our $hostpcidesc = {
81 optional => 1,
82 type => 'string', format => 'pve-qm-hostpci',
83 description => "Map host PCI devices into guest.",
84 verbose_description => <<EODESCR,
85 Map host PCI devices into guest.
86
87 NOTE: This option allows direct access to host hardware. So it is no longer
88 possible to migrate such machines - use with special care.
89
90 CAUTION: Experimental! User reported problems with this option.
91 EODESCR
92 };
93 PVE::JSONSchema::register_standard_option("pve-qm-hostpci", $hostpcidesc);
94
95 my $pci_addr_map;
96 sub get_pci_addr_map {
97 $pci_addr_map = {
98 piix3 => { bus => 0, addr => 1, conflict_ok => qw(ehci) },
99 ehci => { bus => 0, addr => 1, conflict_ok => qw(piix3) }, # instead of piix3 on arm
100 vga => { bus => 0, addr => 2, conflict_ok => qw(legacy-igd) },
101 'legacy-igd' => { bus => 0, addr => 2, conflict_ok => qw(vga) }, # legacy-igd requires vga=none
102 balloon0 => { bus => 0, addr => 3 },
103 watchdog => { bus => 0, addr => 4 },
104 scsihw0 => { bus => 0, addr => 5, conflict_ok => qw(pci.3) },
105 'pci.3' => { bus => 0, addr => 5, conflict_ok => qw(scsihw0) }, # also used for virtio-scsi-single bridge
106 scsihw1 => { bus => 0, addr => 6 },
107 ahci0 => { bus => 0, addr => 7 },
108 qga0 => { bus => 0, addr => 8 },
109 spice => { bus => 0, addr => 9 },
110 virtio0 => { bus => 0, addr => 10 },
111 virtio1 => { bus => 0, addr => 11 },
112 virtio2 => { bus => 0, addr => 12 },
113 virtio3 => { bus => 0, addr => 13 },
114 virtio4 => { bus => 0, addr => 14 },
115 virtio5 => { bus => 0, addr => 15 },
116 hostpci0 => { bus => 0, addr => 16 },
117 hostpci1 => { bus => 0, addr => 17 },
118 net0 => { bus => 0, addr => 18 },
119 net1 => { bus => 0, addr => 19 },
120 net2 => { bus => 0, addr => 20 },
121 net3 => { bus => 0, addr => 21 },
122 net4 => { bus => 0, addr => 22 },
123 net5 => { bus => 0, addr => 23 },
124 vga1 => { bus => 0, addr => 24 },
125 vga2 => { bus => 0, addr => 25 },
126 vga3 => { bus => 0, addr => 26 },
127 hostpci2 => { bus => 0, addr => 27 },
128 hostpci3 => { bus => 0, addr => 28 },
129 #addr29 : usb-host (pve-usb.cfg)
130 'pci.1' => { bus => 0, addr => 30 },
131 'pci.2' => { bus => 0, addr => 31 },
132 'net6' => { bus => 1, addr => 1 },
133 'net7' => { bus => 1, addr => 2 },
134 'net8' => { bus => 1, addr => 3 },
135 'net9' => { bus => 1, addr => 4 },
136 'net10' => { bus => 1, addr => 5 },
137 'net11' => { bus => 1, addr => 6 },
138 'net12' => { bus => 1, addr => 7 },
139 'net13' => { bus => 1, addr => 8 },
140 'net14' => { bus => 1, addr => 9 },
141 'net15' => { bus => 1, addr => 10 },
142 'net16' => { bus => 1, addr => 11 },
143 'net17' => { bus => 1, addr => 12 },
144 'net18' => { bus => 1, addr => 13 },
145 'net19' => { bus => 1, addr => 14 },
146 'net20' => { bus => 1, addr => 15 },
147 'net21' => { bus => 1, addr => 16 },
148 'net22' => { bus => 1, addr => 17 },
149 'net23' => { bus => 1, addr => 18 },
150 'net24' => { bus => 1, addr => 19 },
151 'net25' => { bus => 1, addr => 20 },
152 'net26' => { bus => 1, addr => 21 },
153 'net27' => { bus => 1, addr => 22 },
154 'net28' => { bus => 1, addr => 23 },
155 'net29' => { bus => 1, addr => 24 },
156 'net30' => { bus => 1, addr => 25 },
157 'net31' => { bus => 1, addr => 26 },
158 'xhci' => { bus => 1, addr => 27 },
159 'pci.4' => { bus => 1, addr => 28 },
160 'rng0' => { bus => 1, addr => 29 },
161 'pci.2-igd' => { bus => 1, addr => 30 }, # replaces pci.2 in case a legacy IGD device is passed through
162 'virtio6' => { bus => 2, addr => 1 },
163 'virtio7' => { bus => 2, addr => 2 },
164 'virtio8' => { bus => 2, addr => 3 },
165 'virtio9' => { bus => 2, addr => 4 },
166 'virtio10' => { bus => 2, addr => 5 },
167 'virtio11' => { bus => 2, addr => 6 },
168 'virtio12' => { bus => 2, addr => 7 },
169 'virtio13' => { bus => 2, addr => 8 },
170 'virtio14' => { bus => 2, addr => 9 },
171 'virtio15' => { bus => 2, addr => 10 },
172 'ivshmem' => { bus => 2, addr => 11 },
173 'audio0' => { bus => 2, addr => 12 },
174 hostpci4 => { bus => 2, addr => 13 },
175 hostpci5 => { bus => 2, addr => 14 },
176 hostpci6 => { bus => 2, addr => 15 },
177 hostpci7 => { bus => 2, addr => 16 },
178 hostpci8 => { bus => 2, addr => 17 },
179 hostpci9 => { bus => 2, addr => 18 },
180 hostpci10 => { bus => 2, addr => 19 },
181 hostpci11 => { bus => 2, addr => 20 },
182 hostpci12 => { bus => 2, addr => 21 },
183 hostpci13 => { bus => 2, addr => 22 },
184 hostpci14 => { bus => 2, addr => 23 },
185 hostpci15 => { bus => 2, addr => 24 },
186 'virtioscsi0' => { bus => 3, addr => 1 },
187 'virtioscsi1' => { bus => 3, addr => 2 },
188 'virtioscsi2' => { bus => 3, addr => 3 },
189 'virtioscsi3' => { bus => 3, addr => 4 },
190 'virtioscsi4' => { bus => 3, addr => 5 },
191 'virtioscsi5' => { bus => 3, addr => 6 },
192 'virtioscsi6' => { bus => 3, addr => 7 },
193 'virtioscsi7' => { bus => 3, addr => 8 },
194 'virtioscsi8' => { bus => 3, addr => 9 },
195 'virtioscsi9' => { bus => 3, addr => 10 },
196 'virtioscsi10' => { bus => 3, addr => 11 },
197 'virtioscsi11' => { bus => 3, addr => 12 },
198 'virtioscsi12' => { bus => 3, addr => 13 },
199 'virtioscsi13' => { bus => 3, addr => 14 },
200 'virtioscsi14' => { bus => 3, addr => 15 },
201 'virtioscsi15' => { bus => 3, addr => 16 },
202 'virtioscsi16' => { bus => 3, addr => 17 },
203 'virtioscsi17' => { bus => 3, addr => 18 },
204 'virtioscsi18' => { bus => 3, addr => 19 },
205 'virtioscsi19' => { bus => 3, addr => 20 },
206 'virtioscsi20' => { bus => 3, addr => 21 },
207 'virtioscsi21' => { bus => 3, addr => 22 },
208 'virtioscsi22' => { bus => 3, addr => 23 },
209 'virtioscsi23' => { bus => 3, addr => 24 },
210 'virtioscsi24' => { bus => 3, addr => 25 },
211 'virtioscsi25' => { bus => 3, addr => 26 },
212 'virtioscsi26' => { bus => 3, addr => 27 },
213 'virtioscsi27' => { bus => 3, addr => 28 },
214 'virtioscsi28' => { bus => 3, addr => 29 },
215 'virtioscsi29' => { bus => 3, addr => 30 },
216 'virtioscsi30' => { bus => 3, addr => 31 },
217 'scsihw2' => { bus => 4, addr => 1 },
218 'scsihw3' => { bus => 4, addr => 2 },
219 'scsihw4' => { bus => 4, addr => 3 },
220 } if !defined($pci_addr_map);
221 return $pci_addr_map;
222 }
223
224 my $get_addr_mapping_from_id = sub {
225 my ($map, $id) = @_;
226
227 my $d = $map->{$id};
228 return if !defined($d) || !defined($d->{bus}) || !defined($d->{addr});
229
230 return { bus => $d->{bus}, addr => sprintf("0x%x", $d->{addr}) };
231 };
232
233 sub print_pci_addr {
234 my ($id, $bridges, $arch, $machine) = @_;
235
236 my $res = '';
237
238 # using same bus slots on all HW, so we need to check special cases here:
239 my $busname = 'pci';
240 if ($arch eq 'aarch64' && $machine =~ /^virt/) {
241 die "aarch64/virt cannot use IDE devices\n" if $id =~ /^ide/;
242 $busname = 'pcie';
243 }
244
245 my $map = get_pci_addr_map();
246 if (my $d = $get_addr_mapping_from_id->($map, $id)) {
247 $res = ",bus=$busname.$d->{bus},addr=$d->{addr}";
248 $bridges->{$d->{bus}} = 1 if $bridges;
249 }
250
251 return $res;
252 }
253
254 my $pcie_addr_map;
255 sub get_pcie_addr_map {
256 $pcie_addr_map = {
257 vga => { bus => 'pcie.0', addr => 1 },
258 hostpci0 => { bus => "ich9-pcie-port-1", addr => 0 },
259 hostpci1 => { bus => "ich9-pcie-port-2", addr => 0 },
260 hostpci2 => { bus => "ich9-pcie-port-3", addr => 0 },
261 hostpci3 => { bus => "ich9-pcie-port-4", addr => 0 },
262 hostpci4 => { bus => "ich9-pcie-port-5", addr => 0 },
263 hostpci5 => { bus => "ich9-pcie-port-6", addr => 0 },
264 hostpci6 => { bus => "ich9-pcie-port-7", addr => 0 },
265 hostpci7 => { bus => "ich9-pcie-port-8", addr => 0 },
266 hostpci8 => { bus => "ich9-pcie-port-9", addr => 0 },
267 hostpci9 => { bus => "ich9-pcie-port-10", addr => 0 },
268 hostpci10 => { bus => "ich9-pcie-port-11", addr => 0 },
269 hostpci11 => { bus => "ich9-pcie-port-12", addr => 0 },
270 hostpci12 => { bus => "ich9-pcie-port-13", addr => 0 },
271 hostpci13 => { bus => "ich9-pcie-port-14", addr => 0 },
272 hostpci14 => { bus => "ich9-pcie-port-15", addr => 0 },
273 hostpci15 => { bus => "ich9-pcie-port-16", addr => 0 },
274 # win7 is picky about pcie assignments
275 hostpci0bus0 => { bus => "pcie.0", addr => 16 },
276 hostpci1bus0 => { bus => "pcie.0", addr => 17 },
277 hostpci2bus0 => { bus => "pcie.0", addr => 18 },
278 hostpci3bus0 => { bus => "pcie.0", addr => 19 },
279 ivshmem => { bus => 'pcie.0', addr => 20 },
280 hostpci4bus0 => { bus => "pcie.0", addr => 9 },
281 hostpci5bus0 => { bus => "pcie.0", addr => 10 },
282 hostpci6bus0 => { bus => "pcie.0", addr => 11 },
283 hostpci7bus0 => { bus => "pcie.0", addr => 12 },
284 hostpci8bus0 => { bus => "pcie.0", addr => 13 },
285 hostpci9bus0 => { bus => "pcie.0", addr => 14 },
286 hostpci10bus0 => { bus => "pcie.0", addr => 15 },
287 hostpci11bus0 => { bus => "pcie.0", addr => 21 },
288 hostpci12bus0 => { bus => "pcie.0", addr => 22 },
289 hostpci13bus0 => { bus => "pcie.0", addr => 23 },
290 hostpci14bus0 => { bus => "pcie.0", addr => 24 },
291 hostpci15bus0 => { bus => "pcie.0", addr => 25 },
292 } if !defined($pcie_addr_map);
293
294 return $pcie_addr_map;
295 }
296
297 sub print_pcie_addr {
298 my ($id) = @_;
299
300 my $res = '';
301
302 my $map = get_pcie_addr_map($id);
303 if (my $d = $get_addr_mapping_from_id->($map, $id)) {
304 $res = ",bus=$d->{bus},addr=$d->{addr}";
305 }
306
307 return $res;
308 }
309
310 # Generates the device strings for additional pcie root ports. The first 4 pcie
311 # root ports are defined in the pve-q35*.cfg files.
312 sub print_pcie_root_port {
313 my ($i) = @_;
314 my $res = '';
315
316 my $root_port_addresses = {
317 4 => "10.0",
318 5 => "10.1",
319 6 => "10.2",
320 7 => "10.3",
321 8 => "10.4",
322 9 => "10.5",
323 10 => "10.6",
324 11 => "10.7",
325 12 => "11.0",
326 13 => "11.1",
327 14 => "11.2",
328 15 => "11.3",
329 };
330
331 if (defined($root_port_addresses->{$i})) {
332 my $id = $i + 1;
333 $res = "pcie-root-port,id=ich9-pcie-port-${id}";
334 $res .= ",addr=$root_port_addresses->{$i}";
335 $res .= ",x-speed=16,x-width=32,multifunction=on,bus=pcie.0";
336 $res .= ",port=${id},chassis=${id}";
337 }
338
339 return $res;
340 }
341
342 sub parse_hostpci {
343 my ($value) = @_;
344
345 return if !$value;
346
347 my $res = PVE::JSONSchema::parse_property_string($hostpci_fmt, $value);
348
349 my @idlist = split(/;/, $res->{host});
350 delete $res->{host};
351 foreach my $id (@idlist) {
352 my $devs = PVE::SysFSTools::lspci($id);
353 die "no PCI device found for '$id'\n" if !scalar(@$devs);
354 push @{$res->{pciid}}, @$devs;
355 }
356 return $res;
357 }
358
359 sub print_hostpci_devices {
360 my ($vmid, $conf, $devices, $winversion, $q35, $bridges, $arch, $machine_type, $bootorder) = @_;
361
362 my $kvm_off = 0;
363 my $gpu_passthrough = 0;
364 my $legacy_igd = 0;
365
366 my $pciaddr;
367 for (my $i = 0; $i < $MAX_HOSTPCI_DEVICES; $i++) {
368 my $id = "hostpci$i";
369 my $d = parse_hostpci($conf->{$id});
370 next if !$d;
371
372 if (my $pcie = $d->{pcie}) {
373 die "q35 machine model is not enabled" if !$q35;
374 # win7 wants to have the pcie devices directly on the pcie bus
375 # instead of in the root port
376 if ($winversion == 7) {
377 $pciaddr = print_pcie_addr("${id}bus0");
378 } else {
379 # add more root ports if needed, 4 are present by default
380 # by pve-q35 cfgs, rest added here on demand.
381 if ($i > 3) {
382 push @$devices, '-device', print_pcie_root_port($i);
383 }
384 $pciaddr = print_pcie_addr($id);
385 }
386 } else {
387 my $pci_name = $d->{'legacy-igd'} ? 'legacy-igd' : $id;
388 $pciaddr = print_pci_addr($pci_name, $bridges, $arch, $machine_type);
389 }
390
391 my $pcidevices = $d->{pciid};
392 my $multifunction = @$pcidevices > 1;
393
394 if ($d->{'legacy-igd'}) {
395 die "only one device can be assigned in legacy-igd mode\n"
396 if $legacy_igd;
397 $legacy_igd = 1;
398
399 die "legacy IGD assignment requires VGA mode to be 'none'\n"
400 if !defined($conf->{'vga'}) || $conf->{'vga'} ne 'none';
401 die "legacy IGD assignment requires rombar to be enabled\n"
402 if defined($d->{rombar}) && !$d->{rombar};
403 die "legacy IGD assignment is not compatible with x-vga\n"
404 if $d->{'x-vga'};
405 die "legacy IGD assignment is not compatible with mdev\n"
406 if $d->{mdev};
407 die "legacy IGD assignment is not compatible with q35\n"
408 if $q35;
409 die "legacy IGD assignment is not compatible with multifunction devices\n"
410 if $multifunction;
411 die "legacy IGD assignment only works for devices on host bus 00:02.0\n"
412 if $pcidevices->[0]->{id} !~ m/02\.0$/;
413 }
414
415 my $xvga = '';
416 if ($d->{'x-vga'}) {
417 $xvga = ',x-vga=on' if !($conf->{bios} && $conf->{bios} eq 'ovmf');
418 $kvm_off = 1;
419 $vga->{type} = 'none' if !defined($conf->{vga});
420 $gpu_passthrough = 1;
421 }
422
423 my $sysfspath;
424 if ($d->{mdev} && scalar(@$pcidevices) == 1) {
425 my $pci_id = $pcidevices->[0]->{id};
426 my $uuid = PVE::SysFSTools::generate_mdev_uuid($vmid, $i);
427 $sysfspath = "/sys/bus/pci/devices/$pci_id/$uuid";
428 } elsif ($d->{mdev}) {
429 warn "ignoring mediated device '$id' with multifunction device\n";
430 }
431
432 my $j = 0;
433 foreach my $pcidevice (@$pcidevices) {
434 my $devicestr = "vfio-pci";
435
436 if ($sysfspath) {
437 $devicestr .= ",sysfsdev=$sysfspath";
438 } else {
439 $devicestr .= ",host=$pcidevice->{id}";
440 }
441
442 my $mf_addr = $multifunction ? ".$j" : '';
443 $devicestr .= ",id=${id}${mf_addr}${pciaddr}${mf_addr}";
444
445 if ($j == 0) {
446 $devicestr .= ',rombar=0' if defined($d->{rombar}) && !$d->{rombar};
447 $devicestr .= "$xvga";
448 $devicestr .= ",multifunction=on" if $multifunction;
449 $devicestr .= ",romfile=/usr/share/kvm/$d->{romfile}" if $d->{romfile};
450 $devicestr .= ",bootindex=$bootorder->{$id}" if $bootorder->{$id};
451 }
452
453 push @$devices, '-device', $devicestr;
454 $j++;
455 }
456 }
457
458 return ($kvm_off, $gpu_passthrough, $legacy_igd);
459 }
460
461 1;