#!/usr/bin/perl use strict; use warnings; use experimental 'smartmatch'; use lib qw(..); use Test::More; use PVE::Tools qw(file_get_contents); use PVE::QemuServer::PCI; my $qemu_cfg_base_path = "../qemu-configs"; # not our format but that what QEMU gets passed with '-readconfig' sub slurp_qemu_config { my ($fn) = @_; my $raw = file_get_contents($fn); my $lineno = 0; my $cfg = {}; my $group; my $skip_to_next_group; while ($raw =~ /^\h*(.*?)\h*$/gm) { my $line = $1; $lineno++; next if !$line || $line =~ /^#/; # tried to follow qemu's qemu_config_parse function if ($line =~ /\[(\S{1,63}) "([^"\]]{1,63})"\]/) { $group = $2; $skip_to_next_group = 0; if ($1 ne 'device') { $group = undef; $skip_to_next_group = 1; } } elsif ($line =~ /\[([^\]]{1,63})\]/) { $group = undef; $skip_to_next_group = 1; } elsif ($group) { if ($line =~ /(\S{1,63}) = "([^\"]{1,1023})"/) { my ($k, $v) = ($1, $2); $cfg->{$group}->{$k} = $v; } else { print "ignoring $fn:$lineno: $line\n"; } } else { warn "ignore $fn:$lineno, currently no group\n" if !$skip_to_next_group; } } return $cfg; } sub extract_qemu_config_addrs { my ($qemu_cfg) = @_; my $addr_map = {}; for my $k (keys %$qemu_cfg) { my $v = $qemu_cfg->{$k}; next if !$v || !defined($v->{bus}) || !defined($v->{addr}); my $bus = $v->{bus}; $bus =~ s/pci\.//; $addr_map->{$k} = { bus => $bus, addr => $v->{addr} }; } return $addr_map; } print "testing PCI(e) address conflicts\n"; # exec tests #FIXME: make cross PCI <-> PCIe check sense at all?? my $addr_map = {}; my ($fail, $ignored) = (0, 0); sub check_conflict { my ($id, $what, $ignore_if_same_key) = @_; my ($bus, $addr) = $what->@{qw(bus addr)}; my $full_addr = "$bus:$addr"; if (defined(my $conflict = $addr_map->{$full_addr})) { if (my @ignores = $what->{conflict_ok}) { if ($conflict ~~ @ignores) { note("OK: ignore conflict for '$full_addr' between '$id' and '$conflict'"); $ignored++; return; } } # this allows to read multiple pve-*.cfg qemu configs, and check them # normally their OK if they conflict is on the same key. Else TODO?? return if $ignore_if_same_key && $id eq $conflict; note("ERR: conflict for '$full_addr' between '$id' and '$conflict'"); $fail++; } else { $addr_map->{$full_addr} = $id; } } my $pci_map = PVE::QemuServer::PCI::get_pci_addr_map(); while (my ($id, $what) = each %$pci_map) { check_conflict($id, $what); } my $pcie_map = PVE::QemuServer::PCI::get_pcie_addr_map(); while (my ($id, $what) = each %$pcie_map) { check_conflict($id, $what); } my $pve_qm_cfg = slurp_qemu_config("$qemu_cfg_base_path/pve-q35.cfg"); my $pve_qm_cfg_map = extract_qemu_config_addrs($pve_qm_cfg); while (my ($id, $what) = each %$pve_qm_cfg_map) { check_conflict($id, $what); } # FIXME: restart with clean conflict $addr_map with only get_pci*_addr_map ones? my $pve_qm4_cfg = slurp_qemu_config("$qemu_cfg_base_path/pve-q35-4.0.cfg"); my $pve_qm4_cfg_map = extract_qemu_config_addrs($pve_qm4_cfg); while (my ($id, $what) = each %$pve_qm4_cfg_map) { check_conflict($id, $what, 1); } my $pve_qm_usb_cfg = slurp_qemu_config("$qemu_cfg_base_path/pve-usb.cfg"); my $pve_qm_usb_cfg_map = extract_qemu_config_addrs($pve_qm_usb_cfg); while (my ($id, $what) = each %$pve_qm_usb_cfg_map) { check_conflict($id, $what, 1); } if ($fail) { fail("PCI(e) address conflict check, ignored: $ignored, conflicts: $fail"); } else { pass("PCI(e) address conflict check, ignored: $ignored"); } done_testing();