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