]> git.proxmox.com Git - qemu-server.git/blame - PVE/QemuServer/PCI.pm
pci reservation: rework helpers style and readability wise
[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
80 }
81};
82PVE::JSONSchema::register_format('pve-qm-hostpci', $hostpci_fmt);
83
84our $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,
89Map host PCI devices into guest.
90
91NOTE: This option allows direct access to host hardware. So it is no longer
92possible to migrate such machines - use with special care.
93
94CAUTION: Experimental! User reported problems with this option.
95EODESCR
96};
97PVE::JSONSchema::register_standard_option("pve-qm-hostpci", $hostpcidesc);
98
d7d698f6
TL
99my $pci_addr_map;
100sub 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
228my sub generate_mdev_uuid {
229 my ($vmid, $index) = @_;
230 return sprintf("%08d-0000-0000-0000-%012d", $index, $vmid);
231}
232
d7d698f6
TL
233my $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
242sub 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
263my $pcie_addr_map;
264sub 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
306sub 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.
321sub 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
351sub 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
368sub 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 470sub 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
488my $RUNDIR = '/run/qemu-server';
489my $PCIID_RESERVATION_FILE = "${RUNDIR}/pci-id-reservations";
490my $PCIID_RESERVATION_LOCK = "${PCIID_RESERVATION_FILE}.lock";
3bfee796 491
cda95d52 492my $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 507my $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
522sub 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
536sub 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 5761;