diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm index fa71b251..1c08222e 100644 --- a/PVE/QemuServer.pm +++ b/PVE/QemuServer.pm @@ -48,7 +48,7 @@ use PVE::QemuServer::Drive qw(is_valid_drivename drive_is_cloudinit drive_is_cdr use PVE::QemuServer::Machine; use PVE::QemuServer::Memory; use PVE::QemuServer::Monitor qw(mon_cmd); -use PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr print_pcie_root_port); +use PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr print_pcie_root_port parse_hostpci); use PVE::QemuServer::USB qw(parse_usb_device); my $have_sdn; @@ -768,7 +768,6 @@ while (my ($k, $v) = each %$confdesc) { my $MAX_USB_DEVICES = 5; my $MAX_NETS = 32; -my $MAX_HOSTPCI_DEVICES = 16; my $MAX_SERIAL_PORTS = 4; my $MAX_PARALLEL_PORTS = 3; my $MAX_NUMA = 8; @@ -1011,76 +1010,6 @@ my $usbdesc = { }; PVE::JSONSchema::register_standard_option("pve-qm-usb", $usbdesc); -my $PCIRE = qr/([a-f0-9]{4}:)?[a-f0-9]{2}:[a-f0-9]{2}(?:\.[a-f0-9])?/; -my $hostpci_fmt = { - host => { - default_key => 1, - type => 'string', - pattern => qr/$PCIRE(;$PCIRE)*/, - format_description => 'HOSTPCIID[;HOSTPCIID2...]', - description => < { - type => 'boolean', - description => "Specify whether or not the device's ROM will be visible in the guest's memory map.", - optional => 1, - default => 1, - }, - romfile => { - type => 'string', - pattern => '[^,;]+', - format_description => 'string', - description => "Custom pci device rom filename (must be located in /usr/share/kvm/).", - optional => 1, - }, - pcie => { - type => 'boolean', - description => "Choose the PCI-express bus (needs the 'q35' machine model).", - optional => 1, - default => 0, - }, - 'x-vga' => { - type => 'boolean', - description => "Enable vfio-vga device support.", - optional => 1, - default => 0, - }, - 'mdev' => { - type => 'string', - format_description => 'string', - pattern => '[^/\.:]+', - optional => 1, - description => < 1, - type => 'string', format => 'pve-qm-hostpci', - description => "Map host PCI devices into guest.", - verbose_description => < 1, type => 'string', @@ -1119,8 +1048,8 @@ for (my $i = 0; $i < $MAX_SERIAL_PORTS; $i++) { $confdesc->{"serial$i"} = $serialdesc; } -for (my $i = 0; $i < $MAX_HOSTPCI_DEVICES; $i++) { - $confdesc->{"hostpci$i"} = $hostpcidesc; +for (my $i = 0; $i < $PVE::QemuServer::PCI::MAX_HOSTPCI_DEVICES; $i++) { + $confdesc->{"hostpci$i"} = $PVE::QemuServer::PCI::hostpcidesc; } for my $key (keys %{$PVE::QemuServer::Drive::drivedesc_hash}) { @@ -1756,23 +1685,6 @@ sub parse_numa { return $res; } -sub parse_hostpci { - my ($value) = @_; - - return undef if !$value; - - my $res = PVE::JSONSchema::parse_property_string($hostpci_fmt, $value); - - my @idlist = split(/;/, $res->{host}); - delete $res->{host}; - foreach my $id (@idlist) { - my $devs = PVE::SysFSTools::lspci($id); - die "no PCI device found for '$id'\n" if !scalar(@$devs); - push @{$res->{pciid}}, @$devs; - } - return $res; -} - # netX: e1000=XX:XX:XX:XX:XX:XX,bridge=vmbr0,rate= sub parse_net { my ($data) = @_; @@ -3189,77 +3101,9 @@ sub config_to_command { push @$devices, '-device', $kbd if defined($kbd); } - my $kvm_off = 0; - my $gpu_passthrough; - - # host pci devices - for (my $i = 0; $i < $MAX_HOSTPCI_DEVICES; $i++) { - my $id = "hostpci$i"; - my $d = parse_hostpci($conf->{$id}); - next if !$d; - - if (my $pcie = $d->{pcie}) { - die "q35 machine model is not enabled" if !$q35; - # win7 wants to have the pcie devices directly on the pcie bus - # instead of in the root port - if ($winversion == 7) { - $pciaddr = print_pcie_addr("${id}bus0"); - } else { - # add more root ports if needed, 4 are present by default - # by pve-q35 cfgs, rest added here on demand. - if ($i > 3) { - push @$devices, '-device', print_pcie_root_port($i); - } - $pciaddr = print_pcie_addr($id); - } - } else { - $pciaddr = print_pci_addr($id, $bridges, $arch, $machine_type); - } - - my $xvga = ''; - if ($d->{'x-vga'}) { - $xvga = ',x-vga=on' if !($conf->{bios} && $conf->{bios} eq 'ovmf'); - $kvm_off = 1; - $vga->{type} = 'none' if !defined($conf->{vga}); - $gpu_passthrough = 1; - } - - my $pcidevices = $d->{pciid}; - my $multifunction = 1 if @$pcidevices > 1; - - my $sysfspath; - if ($d->{mdev} && scalar(@$pcidevices) == 1) { - my $pci_id = $pcidevices->[0]->{id}; - my $uuid = PVE::SysFSTools::generate_mdev_uuid($vmid, $i); - $sysfspath = "/sys/bus/pci/devices/$pci_id/$uuid"; - } elsif ($d->{mdev}) { - warn "ignoring mediated device '$id' with multifunction device\n"; - } - - my $j=0; - foreach my $pcidevice (@$pcidevices) { - my $devicestr = "vfio-pci"; - - if ($sysfspath) { - $devicestr .= ",sysfsdev=$sysfspath"; - } else { - $devicestr .= ",host=$pcidevice->{id}"; - } - - my $mf_addr = $multifunction ? ".$j" : ''; - $devicestr .= ",id=${id}${mf_addr}${pciaddr}${mf_addr}"; - - if ($j == 0) { - $devicestr .= ',rombar=0' if defined($d->{rombar}) && !$d->{rombar}; - $devicestr .= "$xvga"; - $devicestr .= ",multifunction=on" if $multifunction; - $devicestr .= ",romfile=/usr/share/kvm/$d->{romfile}" if $d->{romfile}; - } - - push @$devices, '-device', $devicestr; - $j++; - } - } + # host pci device passthrough + my ($kvm_off, $gpu_passthrough) = PVE::QemuServer::PCI::print_hostpci_devices( + $conf, $devices, $winversion, $q35, $bridges, $arch, $machine_type); # usb devices my $usb_dev_features = {}; @@ -5092,7 +4936,7 @@ sub vm_start_nolock { } # host pci devices - for (my $i = 0; $i < $MAX_HOSTPCI_DEVICES; $i++) { + for (my $i = 0; $i < $PVE::QemuServer::PCI::MAX_HOSTPCI_DEVICES; $i++) { my $d = parse_hostpci($conf->{"hostpci$i"}); next if !$d; my $pcidevices = $d->{pciid}; diff --git a/PVE/QemuServer/PCI.pm b/PVE/QemuServer/PCI.pm index 4d9ce264..39f99702 100644 --- a/PVE/QemuServer/PCI.pm +++ b/PVE/QemuServer/PCI.pm @@ -1,13 +1,89 @@ package PVE::QemuServer::PCI; +use PVE::JSONSchema; +use PVE::SysFSTools; + use base 'Exporter'; our @EXPORT_OK = qw( print_pci_addr print_pcie_addr print_pcie_root_port +parse_hostpci ); +our $MAX_HOSTPCI_DEVICES = 16; + +my $PCIRE = qr/([a-f0-9]{4}:)?[a-f0-9]{2}:[a-f0-9]{2}(?:\.[a-f0-9])?/; +my $hostpci_fmt = { + host => { + default_key => 1, + type => 'string', + pattern => qr/$PCIRE(;$PCIRE)*/, + format_description => 'HOSTPCIID[;HOSTPCIID2...]', + description => < { + type => 'boolean', + description => "Specify whether or not the device's ROM will be visible in the guest's memory map.", + optional => 1, + default => 1, + }, + romfile => { + type => 'string', + pattern => '[^,;]+', + format_description => 'string', + description => "Custom pci device rom filename (must be located in /usr/share/kvm/).", + optional => 1, + }, + pcie => { + type => 'boolean', + description => "Choose the PCI-express bus (needs the 'q35' machine model).", + optional => 1, + default => 0, + }, + 'x-vga' => { + type => 'boolean', + description => "Enable vfio-vga device support.", + optional => 1, + default => 0, + }, + 'mdev' => { + type => 'string', + format_description => 'string', + pattern => '[^/\.:]+', + optional => 1, + description => < 1, + type => 'string', format => 'pve-qm-hostpci', + description => "Map host PCI devices into guest.", + verbose_description => <{host}); + delete $res->{host}; + foreach my $id (@idlist) { + my $devs = PVE::SysFSTools::lspci($id); + die "no PCI device found for '$id'\n" if !scalar(@$devs); + push @{$res->{pciid}}, @$devs; + } + return $res; +} + +sub print_hostpci_devices { + my ($conf, $devices, $winversion, $q35, $bridges, $arch, $machine_type) = @_; + + my $kvm_off = 0; + my $gpu_passthrough = 0; + + for (my $i = 0; $i < $MAX_HOSTPCI_DEVICES; $i++) { + my $id = "hostpci$i"; + my $d = parse_hostpci($conf->{$id}); + next if !$d; + + if (my $pcie = $d->{pcie}) { + die "q35 machine model is not enabled" if !$q35; + # win7 wants to have the pcie devices directly on the pcie bus + # instead of in the root port + if ($winversion == 7) { + $pciaddr = print_pcie_addr("${id}bus0"); + } else { + # add more root ports if needed, 4 are present by default + # by pve-q35 cfgs, rest added here on demand. + if ($i > 3) { + push @$devices, '-device', print_pcie_root_port($i); + } + $pciaddr = print_pcie_addr($id); + } + } else { + $pciaddr = print_pci_addr($id, $bridges, $arch, $machine_type); + } + + my $xvga = ''; + if ($d->{'x-vga'}) { + $xvga = ',x-vga=on' if !($conf->{bios} && $conf->{bios} eq 'ovmf'); + $kvm_off = 1; + $vga->{type} = 'none' if !defined($conf->{vga}); + $gpu_passthrough = 1; + } + + my $pcidevices = $d->{pciid}; + my $multifunction = 1 if @$pcidevices > 1; + + my $sysfspath; + if ($d->{mdev} && scalar(@$pcidevices) == 1) { + my $pci_id = $pcidevices->[0]->{id}; + my $uuid = PVE::SysFSTools::generate_mdev_uuid($vmid, $i); + $sysfspath = "/sys/bus/pci/devices/$pci_id/$uuid"; + } elsif ($d->{mdev}) { + warn "ignoring mediated device '$id' with multifunction device\n"; + } + + my $j=0; + foreach my $pcidevice (@$pcidevices) { + my $devicestr = "vfio-pci"; + + if ($sysfspath) { + $devicestr .= ",sysfsdev=$sysfspath"; + } else { + $devicestr .= ",host=$pcidevice->{id}"; + } + + my $mf_addr = $multifunction ? ".$j" : ''; + $devicestr .= ",id=${id}${mf_addr}${pciaddr}${mf_addr}"; + + if ($j == 0) { + $devicestr .= ',rombar=0' if defined($d->{rombar}) && !$d->{rombar}; + $devicestr .= "$xvga"; + $devicestr .= ",multifunction=on" if $multifunction; + $devicestr .= ",romfile=/usr/share/kvm/$d->{romfile}" if $d->{romfile}; + } + + push @$devices, '-device', $devicestr; + $j++; + } + } + + return ($kvm_off, $gpu_passthrough); +} + 1;