]>
Commit | Line | Data |
---|---|---|
de9768f0 DC |
1 | package PVE::QemuServer::PCI; |
2 | ||
41af2dfc TL |
3 | use warnings; |
4 | use strict; | |
5 | ||
74c17b7a SR |
6 | use PVE::JSONSchema; |
7 | use PVE::SysFSTools; | |
3bfee796 | 8 | use PVE::Tools; |
74c17b7a | 9 | |
de9768f0 DC |
10 | use base 'Exporter'; |
11 | ||
12 | our @EXPORT_OK = qw( | |
13 | print_pci_addr | |
14 | print_pcie_addr | |
c4e16381 | 15 | print_pcie_root_port |
74c17b7a | 16 | parse_hostpci |
de9768f0 DC |
17 | ); |
18 | ||
74c17b7a SR |
19 | our $MAX_HOSTPCI_DEVICES = 16; |
20 | ||
a4d5b84c | 21 | my $PCIRE = qr/(?:[a-f0-9]{4}:)?[a-f0-9]{2}:[a-f0-9]{2}(?:\.[a-f0-9])?/; |
74c17b7a SR |
22 | my $hostpci_fmt = { |
23 | host => { | |
24 | default_key => 1, | |
25 | type => 'string', | |
26 | pattern => qr/$PCIRE(;$PCIRE)*/, | |
27 | format_description => 'HOSTPCIID[;HOSTPCIID2...]', | |
28 | description => <<EODESCR, | |
29 | Host PCI device pass through. The PCI ID of a host's PCI device or a list | |
30 | of PCI virtual functions of the host. HOSTPCIID syntax is: | |
31 | ||
32 | 'bus:dev.func' (hexadecimal numbers) | |
33 | ||
34 | You can us the 'lspci' command to list existing PCI devices. | |
35 | EODESCR | |
36 | }, | |
37 | rombar => { | |
38 | type => 'boolean', | |
1fac3a0b TL |
39 | description => "Specify whether or not the device's ROM will be visible in the" |
40 | ." guest's memory map.", | |
74c17b7a SR |
41 | optional => 1, |
42 | default => 1, | |
43 | }, | |
44 | romfile => { | |
1fac3a0b TL |
45 | type => 'string', |
46 | pattern => '[^,;]+', | |
47 | format_description => 'string', | |
48 | description => "Custom pci device rom filename (must be located in /usr/share/kvm/).", | |
49 | optional => 1, | |
74c17b7a SR |
50 | }, |
51 | pcie => { | |
52 | type => 'boolean', | |
1fac3a0b | 53 | description => "Choose the PCI-express bus (needs the 'q35' machine model).", |
74c17b7a SR |
54 | optional => 1, |
55 | default => 0, | |
56 | }, | |
57 | 'x-vga' => { | |
58 | type => 'boolean', | |
1fac3a0b | 59 | description => "Enable vfio-vga device support.", |
74c17b7a SR |
60 | optional => 1, |
61 | default => 0, | |
62 | }, | |
13d68979 SR |
63 | 'legacy-igd' => { |
64 | type => 'boolean', | |
1fac3a0b TL |
65 | description => "Pass this device in legacy IGD mode, making it the primary and exclusive" |
66 | ." graphics device in the VM. Requires 'pc-i440fx' machine type and VGA set to 'none'.", | |
13d68979 SR |
67 | optional => 1, |
68 | default => 0, | |
69 | }, | |
74c17b7a SR |
70 | 'mdev' => { |
71 | type => 'string', | |
1fac3a0b | 72 | format_description => 'string', |
74c17b7a SR |
73 | pattern => '[^/\.:]+', |
74 | optional => 1, | |
75 | description => <<EODESCR | |
76 | The type of mediated device to use. | |
77 | An instance of this type will be created on startup of the VM and | |
78 | will be cleaned up when the VM stops. | |
79 | EODESCR | |
80 | } | |
81 | }; | |
82 | PVE::JSONSchema::register_format('pve-qm-hostpci', $hostpci_fmt); | |
83 | ||
84 | our $hostpcidesc = { | |
1fac3a0b TL |
85 | optional => 1, |
86 | type => 'string', format => 'pve-qm-hostpci', | |
87 | description => "Map host PCI devices into guest.", | |
74c17b7a SR |
88 | verbose_description => <<EODESCR, |
89 | Map host PCI devices into guest. | |
90 | ||
91 | NOTE: This option allows direct access to host hardware. So it is no longer | |
92 | possible to migrate such machines - use with special care. | |
93 | ||
94 | CAUTION: Experimental! User reported problems with this option. | |
95 | EODESCR | |
96 | }; | |
97 | PVE::JSONSchema::register_standard_option("pve-qm-hostpci", $hostpcidesc); | |
98 | ||
d7d698f6 TL |
99 | my $pci_addr_map; |
100 | sub get_pci_addr_map { | |
101 | $pci_addr_map = { | |
102 | piix3 => { bus => 0, addr => 1, conflict_ok => qw(ehci) }, | |
103 | ehci => { bus => 0, addr => 1, conflict_ok => qw(piix3) }, # instead of piix3 on arm | |
13d68979 SR |
104 | vga => { bus => 0, addr => 2, conflict_ok => qw(legacy-igd) }, |
105 | 'legacy-igd' => { bus => 0, addr => 2, conflict_ok => qw(vga) }, # legacy-igd requires vga=none | |
d7d698f6 TL |
106 | balloon0 => { bus => 0, addr => 3 }, |
107 | watchdog => { bus => 0, addr => 4 }, | |
108 | scsihw0 => { bus => 0, addr => 5, conflict_ok => qw(pci.3) }, | |
109 | 'pci.3' => { bus => 0, addr => 5, conflict_ok => qw(scsihw0) }, # also used for virtio-scsi-single bridge | |
110 | scsihw1 => { bus => 0, addr => 6 }, | |
111 | ahci0 => { bus => 0, addr => 7 }, | |
112 | qga0 => { bus => 0, addr => 8 }, | |
113 | spice => { bus => 0, addr => 9 }, | |
114 | virtio0 => { bus => 0, addr => 10 }, | |
115 | virtio1 => { bus => 0, addr => 11 }, | |
116 | virtio2 => { bus => 0, addr => 12 }, | |
117 | virtio3 => { bus => 0, addr => 13 }, | |
118 | virtio4 => { bus => 0, addr => 14 }, | |
119 | virtio5 => { bus => 0, addr => 15 }, | |
120 | hostpci0 => { bus => 0, addr => 16 }, | |
121 | hostpci1 => { bus => 0, addr => 17 }, | |
122 | net0 => { bus => 0, addr => 18 }, | |
123 | net1 => { bus => 0, addr => 19 }, | |
124 | net2 => { bus => 0, addr => 20 }, | |
125 | net3 => { bus => 0, addr => 21 }, | |
126 | net4 => { bus => 0, addr => 22 }, | |
127 | net5 => { bus => 0, addr => 23 }, | |
128 | vga1 => { bus => 0, addr => 24 }, | |
129 | vga2 => { bus => 0, addr => 25 }, | |
130 | vga3 => { bus => 0, addr => 26 }, | |
131 | hostpci2 => { bus => 0, addr => 27 }, | |
132 | hostpci3 => { bus => 0, addr => 28 }, | |
133 | #addr29 : usb-host (pve-usb.cfg) | |
134 | 'pci.1' => { bus => 0, addr => 30 }, | |
135 | 'pci.2' => { bus => 0, addr => 31 }, | |
136 | 'net6' => { bus => 1, addr => 1 }, | |
137 | 'net7' => { bus => 1, addr => 2 }, | |
138 | 'net8' => { bus => 1, addr => 3 }, | |
139 | 'net9' => { bus => 1, addr => 4 }, | |
140 | 'net10' => { bus => 1, addr => 5 }, | |
141 | 'net11' => { bus => 1, addr => 6 }, | |
142 | 'net12' => { bus => 1, addr => 7 }, | |
143 | 'net13' => { bus => 1, addr => 8 }, | |
144 | 'net14' => { bus => 1, addr => 9 }, | |
145 | 'net15' => { bus => 1, addr => 10 }, | |
146 | 'net16' => { bus => 1, addr => 11 }, | |
147 | 'net17' => { bus => 1, addr => 12 }, | |
148 | 'net18' => { bus => 1, addr => 13 }, | |
149 | 'net19' => { bus => 1, addr => 14 }, | |
150 | 'net20' => { bus => 1, addr => 15 }, | |
151 | 'net21' => { bus => 1, addr => 16 }, | |
152 | 'net22' => { bus => 1, addr => 17 }, | |
153 | 'net23' => { bus => 1, addr => 18 }, | |
154 | 'net24' => { bus => 1, addr => 19 }, | |
155 | 'net25' => { bus => 1, addr => 20 }, | |
156 | 'net26' => { bus => 1, addr => 21 }, | |
157 | 'net27' => { bus => 1, addr => 22 }, | |
158 | 'net28' => { bus => 1, addr => 23 }, | |
159 | 'net29' => { bus => 1, addr => 24 }, | |
160 | 'net30' => { bus => 1, addr => 25 }, | |
161 | 'net31' => { bus => 1, addr => 26 }, | |
162 | 'xhci' => { bus => 1, addr => 27 }, | |
2513b862 | 163 | 'pci.4' => { bus => 1, addr => 28 }, |
2cf61f33 | 164 | 'rng0' => { bus => 1, addr => 29 }, |
13d68979 | 165 | 'pci.2-igd' => { bus => 1, addr => 30 }, # replaces pci.2 in case a legacy IGD device is passed through |
d7d698f6 TL |
166 | 'virtio6' => { bus => 2, addr => 1 }, |
167 | 'virtio7' => { bus => 2, addr => 2 }, | |
168 | 'virtio8' => { bus => 2, addr => 3 }, | |
169 | 'virtio9' => { bus => 2, addr => 4 }, | |
170 | 'virtio10' => { bus => 2, addr => 5 }, | |
171 | 'virtio11' => { bus => 2, addr => 6 }, | |
172 | 'virtio12' => { bus => 2, addr => 7 }, | |
173 | 'virtio13' => { bus => 2, addr => 8 }, | |
174 | 'virtio14' => { bus => 2, addr => 9 }, | |
175 | 'virtio15' => { bus => 2, addr => 10 }, | |
176 | 'ivshmem' => { bus => 2, addr => 11 }, | |
177 | 'audio0' => { bus => 2, addr => 12 }, | |
178 | hostpci4 => { bus => 2, addr => 13 }, | |
179 | hostpci5 => { bus => 2, addr => 14 }, | |
180 | hostpci6 => { bus => 2, addr => 15 }, | |
181 | hostpci7 => { bus => 2, addr => 16 }, | |
182 | hostpci8 => { bus => 2, addr => 17 }, | |
183 | hostpci9 => { bus => 2, addr => 18 }, | |
184 | hostpci10 => { bus => 2, addr => 19 }, | |
185 | hostpci11 => { bus => 2, addr => 20 }, | |
186 | hostpci12 => { bus => 2, addr => 21 }, | |
187 | hostpci13 => { bus => 2, addr => 22 }, | |
188 | hostpci14 => { bus => 2, addr => 23 }, | |
189 | hostpci15 => { bus => 2, addr => 24 }, | |
190 | 'virtioscsi0' => { bus => 3, addr => 1 }, | |
191 | 'virtioscsi1' => { bus => 3, addr => 2 }, | |
192 | 'virtioscsi2' => { bus => 3, addr => 3 }, | |
193 | 'virtioscsi3' => { bus => 3, addr => 4 }, | |
194 | 'virtioscsi4' => { bus => 3, addr => 5 }, | |
195 | 'virtioscsi5' => { bus => 3, addr => 6 }, | |
196 | 'virtioscsi6' => { bus => 3, addr => 7 }, | |
197 | 'virtioscsi7' => { bus => 3, addr => 8 }, | |
198 | 'virtioscsi8' => { bus => 3, addr => 9 }, | |
199 | 'virtioscsi9' => { bus => 3, addr => 10 }, | |
200 | 'virtioscsi10' => { bus => 3, addr => 11 }, | |
201 | 'virtioscsi11' => { bus => 3, addr => 12 }, | |
202 | 'virtioscsi12' => { bus => 3, addr => 13 }, | |
203 | 'virtioscsi13' => { bus => 3, addr => 14 }, | |
204 | 'virtioscsi14' => { bus => 3, addr => 15 }, | |
205 | 'virtioscsi15' => { bus => 3, addr => 16 }, | |
206 | 'virtioscsi16' => { bus => 3, addr => 17 }, | |
207 | 'virtioscsi17' => { bus => 3, addr => 18 }, | |
208 | 'virtioscsi18' => { bus => 3, addr => 19 }, | |
209 | 'virtioscsi19' => { bus => 3, addr => 20 }, | |
210 | 'virtioscsi20' => { bus => 3, addr => 21 }, | |
211 | 'virtioscsi21' => { bus => 3, addr => 22 }, | |
212 | 'virtioscsi22' => { bus => 3, addr => 23 }, | |
213 | 'virtioscsi23' => { bus => 3, addr => 24 }, | |
214 | 'virtioscsi24' => { bus => 3, addr => 25 }, | |
215 | 'virtioscsi25' => { bus => 3, addr => 26 }, | |
216 | 'virtioscsi26' => { bus => 3, addr => 27 }, | |
217 | 'virtioscsi27' => { bus => 3, addr => 28 }, | |
218 | 'virtioscsi28' => { bus => 3, addr => 29 }, | |
219 | 'virtioscsi29' => { bus => 3, addr => 30 }, | |
220 | 'virtioscsi30' => { bus => 3, addr => 31 }, | |
2513b862 DC |
221 | 'scsihw2' => { bus => 4, addr => 1 }, |
222 | 'scsihw3' => { bus => 4, addr => 2 }, | |
223 | 'scsihw4' => { bus => 4, addr => 3 }, | |
d7d698f6 TL |
224 | } if !defined($pci_addr_map); |
225 | return $pci_addr_map; | |
226 | } | |
227 | ||
e2b42bee TL |
228 | my sub generate_mdev_uuid { |
229 | my ($vmid, $index) = @_; | |
230 | return sprintf("%08d-0000-0000-0000-%012d", $index, $vmid); | |
231 | } | |
232 | ||
d7d698f6 TL |
233 | my $get_addr_mapping_from_id = sub { |
234 | my ($map, $id) = @_; | |
235 | ||
236 | my $d = $map->{$id}; | |
d1c1af4b | 237 | return if !defined($d) || !defined($d->{bus}) || !defined($d->{addr}); |
d7d698f6 TL |
238 | |
239 | return { bus => $d->{bus}, addr => sprintf("0x%x", $d->{addr}) }; | |
de9768f0 DC |
240 | }; |
241 | ||
242 | sub print_pci_addr { | |
d559309f | 243 | my ($id, $bridges, $arch, $machine) = @_; |
de9768f0 DC |
244 | |
245 | my $res = ''; | |
246 | ||
d7d698f6 | 247 | # using same bus slots on all HW, so we need to check special cases here: |
d559309f WB |
248 | my $busname = 'pci'; |
249 | if ($arch eq 'aarch64' && $machine =~ /^virt/) { | |
d7d698f6 | 250 | die "aarch64/virt cannot use IDE devices\n" if $id =~ /^ide/; |
d559309f WB |
251 | $busname = 'pcie'; |
252 | } | |
253 | ||
d7d698f6 TL |
254 | my $map = get_pci_addr_map(); |
255 | if (my $d = $get_addr_mapping_from_id->($map, $id)) { | |
256 | $res = ",bus=$busname.$d->{bus},addr=$d->{addr}"; | |
257 | $bridges->{$d->{bus}} = 1 if $bridges; | |
de9768f0 | 258 | } |
de9768f0 | 259 | |
d7d698f6 | 260 | return $res; |
de9768f0 DC |
261 | } |
262 | ||
d7d698f6 TL |
263 | my $pcie_addr_map; |
264 | sub get_pcie_addr_map { | |
265 | $pcie_addr_map = { | |
55655ebc | 266 | vga => { bus => 'pcie.0', addr => 1 }, |
de9768f0 DC |
267 | hostpci0 => { bus => "ich9-pcie-port-1", addr => 0 }, |
268 | hostpci1 => { bus => "ich9-pcie-port-2", addr => 0 }, | |
269 | hostpci2 => { bus => "ich9-pcie-port-3", addr => 0 }, | |
270 | hostpci3 => { bus => "ich9-pcie-port-4", addr => 0 }, | |
c4e16381 AL |
271 | hostpci4 => { bus => "ich9-pcie-port-5", addr => 0 }, |
272 | hostpci5 => { bus => "ich9-pcie-port-6", addr => 0 }, | |
273 | hostpci6 => { bus => "ich9-pcie-port-7", addr => 0 }, | |
274 | hostpci7 => { bus => "ich9-pcie-port-8", addr => 0 }, | |
275 | hostpci8 => { bus => "ich9-pcie-port-9", addr => 0 }, | |
276 | hostpci9 => { bus => "ich9-pcie-port-10", addr => 0 }, | |
277 | hostpci10 => { bus => "ich9-pcie-port-11", addr => 0 }, | |
278 | hostpci11 => { bus => "ich9-pcie-port-12", addr => 0 }, | |
279 | hostpci12 => { bus => "ich9-pcie-port-13", addr => 0 }, | |
280 | hostpci13 => { bus => "ich9-pcie-port-14", addr => 0 }, | |
281 | hostpci14 => { bus => "ich9-pcie-port-15", addr => 0 }, | |
282 | hostpci15 => { bus => "ich9-pcie-port-16", addr => 0 }, | |
739ba340 DC |
283 | # win7 is picky about pcie assignments |
284 | hostpci0bus0 => { bus => "pcie.0", addr => 16 }, | |
285 | hostpci1bus0 => { bus => "pcie.0", addr => 17 }, | |
286 | hostpci2bus0 => { bus => "pcie.0", addr => 18 }, | |
287 | hostpci3bus0 => { bus => "pcie.0", addr => 19 }, | |
6dbcb073 | 288 | ivshmem => { bus => 'pcie.0', addr => 20 }, |
c4e16381 AL |
289 | hostpci4bus0 => { bus => "pcie.0", addr => 9 }, |
290 | hostpci5bus0 => { bus => "pcie.0", addr => 10 }, | |
291 | hostpci6bus0 => { bus => "pcie.0", addr => 11 }, | |
292 | hostpci7bus0 => { bus => "pcie.0", addr => 12 }, | |
293 | hostpci8bus0 => { bus => "pcie.0", addr => 13 }, | |
294 | hostpci9bus0 => { bus => "pcie.0", addr => 14 }, | |
295 | hostpci10bus0 => { bus => "pcie.0", addr => 15 }, | |
e2b0d85d TL |
296 | hostpci11bus0 => { bus => "pcie.0", addr => 21 }, |
297 | hostpci12bus0 => { bus => "pcie.0", addr => 22 }, | |
298 | hostpci13bus0 => { bus => "pcie.0", addr => 23 }, | |
299 | hostpci14bus0 => { bus => "pcie.0", addr => 24 }, | |
300 | hostpci15bus0 => { bus => "pcie.0", addr => 25 }, | |
d7d698f6 TL |
301 | } if !defined($pcie_addr_map); |
302 | ||
303 | return $pcie_addr_map; | |
304 | } | |
305 | ||
306 | sub print_pcie_addr { | |
307 | my ($id) = @_; | |
308 | ||
309 | my $res = ''; | |
de9768f0 | 310 | |
d7d698f6 TL |
311 | my $map = get_pcie_addr_map($id); |
312 | if (my $d = $get_addr_mapping_from_id->($map, $id)) { | |
313 | $res = ",bus=$d->{bus},addr=$d->{addr}"; | |
de9768f0 | 314 | } |
de9768f0 | 315 | |
d7d698f6 | 316 | return $res; |
de9768f0 | 317 | } |
b71351a7 | 318 | |
c4e16381 AL |
319 | # Generates the device strings for additional pcie root ports. The first 4 pcie |
320 | # root ports are defined in the pve-q35*.cfg files. | |
321 | sub print_pcie_root_port { | |
322 | my ($i) = @_; | |
323 | my $res = ''; | |
324 | ||
c4e16381 | 325 | my $root_port_addresses = { |
e2b0d85d TL |
326 | 4 => "10.0", |
327 | 5 => "10.1", | |
328 | 6 => "10.2", | |
329 | 7 => "10.3", | |
330 | 8 => "10.4", | |
331 | 9 => "10.5", | |
c4e16381 AL |
332 | 10 => "10.6", |
333 | 11 => "10.7", | |
334 | 12 => "11.0", | |
335 | 13 => "11.1", | |
336 | 14 => "11.2", | |
337 | 15 => "11.3", | |
338 | }; | |
339 | ||
340 | if (defined($root_port_addresses->{$i})) { | |
e2b0d85d | 341 | my $id = $i + 1; |
c4e16381 AL |
342 | $res = "pcie-root-port,id=ich9-pcie-port-${id}"; |
343 | $res .= ",addr=$root_port_addresses->{$i}"; | |
344 | $res .= ",x-speed=16,x-width=32,multifunction=on,bus=pcie.0"; | |
345 | $res .= ",port=${id},chassis=${id}"; | |
346 | } | |
347 | ||
348 | return $res; | |
349 | } | |
350 | ||
74c17b7a SR |
351 | sub parse_hostpci { |
352 | my ($value) = @_; | |
353 | ||
d1c1af4b | 354 | return if !$value; |
74c17b7a SR |
355 | |
356 | my $res = PVE::JSONSchema::parse_property_string($hostpci_fmt, $value); | |
357 | ||
358 | my @idlist = split(/;/, $res->{host}); | |
359 | delete $res->{host}; | |
360 | foreach my $id (@idlist) { | |
361 | my $devs = PVE::SysFSTools::lspci($id); | |
362 | die "no PCI device found for '$id'\n" if !scalar(@$devs); | |
363 | push @{$res->{pciid}}, @$devs; | |
364 | } | |
365 | return $res; | |
366 | } | |
367 | ||
368 | sub print_hostpci_devices { | |
41af2dfc | 369 | my ($vmid, $conf, $devices, $vga, $winversion, $q35, $bridges, $arch, $machine_type, $bootorder) = @_; |
74c17b7a SR |
370 | |
371 | my $kvm_off = 0; | |
372 | my $gpu_passthrough = 0; | |
13d68979 | 373 | my $legacy_igd = 0; |
74c17b7a | 374 | |
f7d1505b | 375 | my $pciaddr; |
74c17b7a SR |
376 | for (my $i = 0; $i < $MAX_HOSTPCI_DEVICES; $i++) { |
377 | my $id = "hostpci$i"; | |
378 | my $d = parse_hostpci($conf->{$id}); | |
379 | next if !$d; | |
380 | ||
381 | if (my $pcie = $d->{pcie}) { | |
382 | die "q35 machine model is not enabled" if !$q35; | |
383 | # win7 wants to have the pcie devices directly on the pcie bus | |
384 | # instead of in the root port | |
385 | if ($winversion == 7) { | |
386 | $pciaddr = print_pcie_addr("${id}bus0"); | |
387 | } else { | |
388 | # add more root ports if needed, 4 are present by default | |
389 | # by pve-q35 cfgs, rest added here on demand. | |
390 | if ($i > 3) { | |
391 | push @$devices, '-device', print_pcie_root_port($i); | |
392 | } | |
393 | $pciaddr = print_pcie_addr($id); | |
394 | } | |
395 | } else { | |
13d68979 SR |
396 | my $pci_name = $d->{'legacy-igd'} ? 'legacy-igd' : $id; |
397 | $pciaddr = print_pci_addr($pci_name, $bridges, $arch, $machine_type); | |
398 | } | |
399 | ||
400 | my $pcidevices = $d->{pciid}; | |
f7d1505b | 401 | my $multifunction = @$pcidevices > 1; |
13d68979 SR |
402 | |
403 | if ($d->{'legacy-igd'}) { | |
404 | die "only one device can be assigned in legacy-igd mode\n" | |
405 | if $legacy_igd; | |
406 | $legacy_igd = 1; | |
407 | ||
408 | die "legacy IGD assignment requires VGA mode to be 'none'\n" | |
409 | if !defined($conf->{'vga'}) || $conf->{'vga'} ne 'none'; | |
410 | die "legacy IGD assignment requires rombar to be enabled\n" | |
411 | if defined($d->{rombar}) && !$d->{rombar}; | |
412 | die "legacy IGD assignment is not compatible with x-vga\n" | |
413 | if $d->{'x-vga'}; | |
414 | die "legacy IGD assignment is not compatible with mdev\n" | |
415 | if $d->{mdev}; | |
416 | die "legacy IGD assignment is not compatible with q35\n" | |
417 | if $q35; | |
418 | die "legacy IGD assignment is not compatible with multifunction devices\n" | |
419 | if $multifunction; | |
420 | die "legacy IGD assignment only works for devices on host bus 00:02.0\n" | |
421 | if $pcidevices->[0]->{id} !~ m/02\.0$/; | |
74c17b7a SR |
422 | } |
423 | ||
424 | my $xvga = ''; | |
425 | if ($d->{'x-vga'}) { | |
426 | $xvga = ',x-vga=on' if !($conf->{bios} && $conf->{bios} eq 'ovmf'); | |
427 | $kvm_off = 1; | |
428 | $vga->{type} = 'none' if !defined($conf->{vga}); | |
429 | $gpu_passthrough = 1; | |
430 | } | |
431 | ||
74c17b7a SR |
432 | my $sysfspath; |
433 | if ($d->{mdev} && scalar(@$pcidevices) == 1) { | |
434 | my $pci_id = $pcidevices->[0]->{id}; | |
e2b42bee | 435 | my $uuid = generate_mdev_uuid($vmid, $i); |
74c17b7a SR |
436 | $sysfspath = "/sys/bus/pci/devices/$pci_id/$uuid"; |
437 | } elsif ($d->{mdev}) { | |
438 | warn "ignoring mediated device '$id' with multifunction device\n"; | |
439 | } | |
440 | ||
1fac3a0b | 441 | my $j = 0; |
74c17b7a SR |
442 | foreach my $pcidevice (@$pcidevices) { |
443 | my $devicestr = "vfio-pci"; | |
444 | ||
445 | if ($sysfspath) { | |
446 | $devicestr .= ",sysfsdev=$sysfspath"; | |
447 | } else { | |
448 | $devicestr .= ",host=$pcidevice->{id}"; | |
449 | } | |
450 | ||
451 | my $mf_addr = $multifunction ? ".$j" : ''; | |
452 | $devicestr .= ",id=${id}${mf_addr}${pciaddr}${mf_addr}"; | |
453 | ||
454 | if ($j == 0) { | |
455 | $devicestr .= ',rombar=0' if defined($d->{rombar}) && !$d->{rombar}; | |
456 | $devicestr .= "$xvga"; | |
457 | $devicestr .= ",multifunction=on" if $multifunction; | |
458 | $devicestr .= ",romfile=/usr/share/kvm/$d->{romfile}" if $d->{romfile}; | |
2141a802 | 459 | $devicestr .= ",bootindex=$bootorder->{$id}" if $bootorder->{$id}; |
74c17b7a SR |
460 | } |
461 | ||
462 | push @$devices, '-device', $devicestr; | |
463 | $j++; | |
464 | } | |
465 | } | |
466 | ||
13d68979 | 467 | return ($kvm_off, $gpu_passthrough, $legacy_igd); |
74c17b7a SR |
468 | } |
469 | ||
acd4b777 | 470 | sub prepare_pci_device { |
82712fcd | 471 | my ($vmid, $pciid, $index, $mdev) = @_; |
acd4b777 DC |
472 | |
473 | my $info = PVE::SysFSTools::pci_device_info("$pciid"); | |
474 | die "IOMMU not present\n" if !PVE::SysFSTools::check_iommu_support(); | |
475 | die "no pci device info for device '$pciid'\n" if !$info; | |
476 | ||
477 | if ($mdev) { | |
e2b42bee | 478 | my $uuid = generate_mdev_uuid($vmid, $index); |
acd4b777 DC |
479 | PVE::SysFSTools::pci_create_mdev_device($pciid, $uuid, $mdev); |
480 | } else { | |
481 | die "can't unbind/bind PCI group to VFIO '$pciid'\n" | |
482 | if !PVE::SysFSTools::pci_dev_group_bind_to_vfio($pciid); | |
483 | die "can't reset PCI device '$pciid'\n" | |
484 | if $info->{has_fl_reset} && !PVE::SysFSTools::pci_dev_reset($info); | |
485 | } | |
acd4b777 DC |
486 | } |
487 | ||
bda0ebff TL |
488 | my $RUNDIR = '/run/qemu-server'; |
489 | my $PCIID_RESERVATION_FILE = "${RUNDIR}/pci-id-reservations"; | |
490 | my $PCIID_RESERVATION_LOCK = "${PCIID_RESERVATION_FILE}.lock"; | |
3bfee796 | 491 | |
cda95d52 | 492 | my $parse_pci_reservation_unlocked = sub { |
3bfee796 | 493 | my $pciids = {}; |
cda95d52 | 494 | if (my $fh = IO::File->new($PCIID_RESERVATION_FILE, "r")) { |
3bfee796 DC |
495 | while (my $line = <$fh>) { |
496 | if ($line =~ m/^($PCIRE)\s(\d+)\s(time|pid)\:(\d+)$/) { | |
497 | $pciids->{$1} = { | |
498 | vmid => $2, | |
499 | "$3" => $4, | |
500 | }; | |
501 | } | |
502 | } | |
503 | } | |
3bfee796 DC |
504 | return $pciids; |
505 | }; | |
506 | ||
cda95d52 | 507 | my $write_pci_reservation_unlocked = sub { |
a0159367 | 508 | my ($reservations) = @_; |
3bfee796 DC |
509 | |
510 | my $data = ""; | |
a0159367 TL |
511 | for my $pci_id (sort keys $reservations->%*) { |
512 | my ($vmid, $pid, $time) = $reservations->{$pci_id}->@{'vmid', 'pid', 'time'}; | |
513 | if (defined($pid)) { | |
514 | $data .= "$pci_id $vmid pid:$pid\n"; | |
3bfee796 | 515 | } else { |
a0159367 | 516 | $data .= "$pci_id $vmid time:$time\n"; |
3bfee796 DC |
517 | } |
518 | } | |
3bfee796 DC |
519 | PVE::Tools::file_set_contents($PCIID_RESERVATION_FILE, $data); |
520 | }; | |
521 | ||
522 | sub remove_pci_reservation { | |
a0159367 | 523 | my ($dropped_ids) = @_; |
3bfee796 | 524 | |
a0159367 TL |
525 | $dropped_ids = [ $dropped_ids ] if !ref($dropped_ids); |
526 | return if !scalar(@$dropped_ids); # do nothing for empty list | |
3bfee796 | 527 | |
a0159367 TL |
528 | PVE::Tools::lock_file($PCIID_RESERVATION_LOCK, 2, sub { |
529 | my $reservation_list = $parse_pci_reservation_unlocked->(); | |
530 | delete $reservation_list->@{$dropped_ids->@*}; | |
531 | $write_pci_reservation_unlocked->($reservation_list); | |
532 | }); | |
3bfee796 | 533 | die $@ if $@; |
3bfee796 DC |
534 | } |
535 | ||
536 | sub reserve_pci_usage { | |
a0159367 | 537 | my ($requested_ids, $vmid, $timeout, $pid) = @_; |
3bfee796 | 538 | |
a0159367 TL |
539 | $requested_ids = [ $requested_ids ] if !ref($requested_ids); |
540 | return if !scalar(@$requested_ids); # do nothing for empty list | |
3bfee796 | 541 | |
a0159367 TL |
542 | PVE::Tools::lock_file($PCIID_RESERVATION_LOCK, 5, sub { |
543 | my $reservation_list = $parse_pci_reservation_unlocked->(); | |
3bfee796 DC |
544 | |
545 | my $ctime = time(); | |
a0159367 TL |
546 | for my $id ($requested_ids->@*) { |
547 | my $reservation = $reservation_list->{$id}; | |
548 | if ($reservation && $reservation->{vmid} != $vmid) { | |
549 | # check time based reservation | |
550 | die "PCI device '$id' is currently reserved for use by VMID '$reservation->{vmid}'\n" | |
551 | if defined($reservation->{time}) && $reservation->{time} > $ctime; | |
552 | ||
553 | if (my $reserved_pid = $reservation->{pid}) { | |
3bfee796 | 554 | # check running vm |
a0159367 TL |
555 | my $running_pid = PVE::QemuServer::Helpers::vm_running_locally($reservation->{vmid}); |
556 | if (defined($running_pid) && $running_pid == $reserved_pid) { | |
557 | die "PCI device '$id' already in use by VMID '$reservation->{vmid}'\n"; | |
558 | } else { | |
559 | warn "leftover PCI reservation found for $id, lets take it...\n"; | |
3bfee796 DC |
560 | } |
561 | } | |
562 | } | |
563 | ||
a0159367 TL |
564 | $reservation_list->{$id} = { vmid => $vmid }; |
565 | if (defined($pid)) { # VM started up, we can reserve now with the actual PID | |
566 | $reservation_list->{$id}->{pid} = $pid; | |
567 | } elsif (defined($timeout)) { # tempoaray reserve as we don't now the PID yet | |
568 | $reservation_list->{$id}->{time} = $ctime + $timeout + 5; | |
3bfee796 | 569 | } |
3bfee796 | 570 | } |
a0159367 | 571 | $write_pci_reservation_unlocked->($reservation_list); |
3bfee796 DC |
572 | }); |
573 | die $@ if $@; | |
574 | } | |
575 | ||
b71351a7 | 576 | 1; |