]> git.proxmox.com Git - qemu-server.git/blame - PVE/QemuServer/PCI.pm
pci: allow override of PCI vendor/device ids
[qemu-server.git] / PVE / QemuServer / PCI.pm
CommitLineData
de9768f0
DC
1package PVE::QemuServer::PCI;
2
41af2dfc
TL
3use warnings;
4use strict;
5
74c17b7a
SR
6use PVE::JSONSchema;
7use PVE::SysFSTools;
3bfee796 8use PVE::Tools;
74c17b7a 9
de9768f0
DC
10use base 'Exporter';
11
12our @EXPORT_OK = qw(
13print_pci_addr
14print_pcie_addr
c4e16381 15print_pcie_root_port
74c17b7a 16parse_hostpci
de9768f0
DC
17);
18
74c17b7a
SR
19our $MAX_HOSTPCI_DEVICES = 16;
20
a4d5b84c 21my $PCIRE = qr/(?:[a-f0-9]{4}:)?[a-f0-9]{2}:[a-f0-9]{2}(?:\.[a-f0-9])?/;
74c17b7a
SR
22my $hostpci_fmt = {
23 host => {
24 default_key => 1,
25 type => 'string',
26 pattern => qr/$PCIRE(;$PCIRE)*/,
27 format_description => 'HOSTPCIID[;HOSTPCIID2...]',
28 description => <<EODESCR,
29Host PCI device pass through. The PCI ID of a host's PCI device or a list
30of PCI virtual functions of the host. HOSTPCIID syntax is:
31
32'bus:dev.func' (hexadecimal numbers)
33
34You can us the 'lspci' command to list existing PCI devices.
35EODESCR
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
76The type of mediated device to use.
77An instance of this type will be created on startup of the VM and
78will be cleaned up when the VM stops.
79EODESCR
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};
110PVE::JSONSchema::register_format('pve-qm-hostpci', $hostpci_fmt);
111
112our $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,
117Map host PCI devices into guest.
118
119NOTE: This option allows direct access to host hardware. So it is no longer
120possible to migrate such machines - use with special care.
121
122CAUTION: Experimental! User reported problems with this option.
123EODESCR
124};
125PVE::JSONSchema::register_standard_option("pve-qm-hostpci", $hostpcidesc);
126
d7d698f6
TL
127my $pci_addr_map;
128sub 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
256my sub generate_mdev_uuid {
257 my ($vmid, $index) = @_;
258 return sprintf("%08d-0000-0000-0000-%012d", $index, $vmid);
259}
260
d7d698f6
TL
261my $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
270sub 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
291my $pcie_addr_map;
292sub 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
334sub 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.
349sub 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
379sub 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
396sub 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 501sub 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
519my $RUNDIR = '/run/qemu-server';
520my $PCIID_RESERVATION_FILE = "${RUNDIR}/pci-id-reservations";
521my $PCIID_RESERVATION_LOCK = "${PCIID_RESERVATION_FILE}.lock";
3bfee796 522
cda95d52 523my $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 538my $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
553sub 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
567sub 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 6071;