X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=src%2FPVE%2FSysFSTools.pm;h=a8d9a7f0e24fc3fd014c140b594d7d5da982128f;hb=HEAD;hp=4b23668538dca39293917e80e623f2a08f9cfbd0;hpb=6eb61ecd4ad9bfa6b1765965a05d7a5ff0d38aff;p=pve-common.git diff --git a/src/PVE/SysFSTools.pm b/src/PVE/SysFSTools.pm index 4b23668..57f0ac8 100644 --- a/src/PVE/SysFSTools.pm +++ b/src/PVE/SysFSTools.pm @@ -8,32 +8,187 @@ use IO::File; use PVE::Tools qw(file_read_firstline dir_glob_foreach); my $pcisysfs = "/sys/bus/pci"; -my $pciregex = "([a-f0-9]{4}):([a-f0-9]{2}):([a-f0-9]{2})\.([a-f0-9])"; +my $domainregex = "[a-f0-9]{4,}"; +my $pciregex = "($domainregex):([a-f0-9]{2}):([a-f0-9]{2})\.([a-f0-9])"; + +my $parse_pci_ids = sub { + my $ids = {}; + + open(my $fh, '<', "/usr/share/misc/pci.ids") + or return $ids; + + my $curvendor; + my $curdevice; + while (my $line = <$fh>) { + if ($line =~ m/^([0-9a-fA-F]{4})\s+(.*)$/) { + $curvendor = ($ids->{"0x$1"} = {}); + $curvendor->{name} = $2; + } elsif ($line =~ m/^\t([0-9a-fA-F]{4})\s+(.*)$/) { + $curdevice = ($curvendor->{devices}->{"0x$1"} = {}); + $curdevice->{name} = $2; + } elsif ($line =~ m/^\t\t([0-9a-fA-F]{4}) ([0-9a-fA-F]{4})\s+(.*)$/) { + $curdevice->{subs}->{"0x$1"}->{"0x$2"} = $3; + } + } + + return $ids; +}; +my sub normalize_pci_id { + my ($id) = @_; + $id = "0000:$id" if $id !~ m/^${domainregex}:/; + return $id; +}; + +# returns a list of pci devices +# +# filter is either a string (then it tries to match to the id) +# or a sub ref (then it adds the device if the sub returns truthy) +# +# verbose also returns iommu groups, subvendor/device and the +# human readable names from /usr/share/misc/pci.ids +# +# return format: +# [ +# { +# id => '00:00.0', +# vendor => '0xabab', +# device => '0xefef', +# class => '0x012345', +# +# # optional +# iommugroup => '14', +# mdev => 1, +# vendor_name => 'Foo Inc.', +# device_name => 'Bar 9000AF', +# subsystem_vendor => '0xacac', +# subsystem_device => '0xfefe', +# subsystem_vendor_name => 'Foo Europe GmbH', +# subsystem_device_name => 'Bar 9001AF OC', +# }, +# ... +# ] +# sub lspci { - my ($filter) = @_; + my ($filter, $verbose) = @_; - my $devices = {}; + my $devices = []; + my $ids = {}; + if ($verbose) { + $ids = $parse_pci_ids->(); + } dir_glob_foreach("$pcisysfs/devices", $pciregex, sub { - my (undef, undef, $bus, $slot, $function) = @_; - my $id = "$bus:$slot"; - return if defined($filter) && $id ne $filter; - my $res = { id => $id, function => $function}; - push @{$devices->{$id}}, $res; + my ($fullid, $domain, $bus, $slot, $function) = @_; + my $id = "$domain:$bus:$slot.$function"; + + if (defined($filter) && !ref($filter) && $id !~ m/^(0000:)?\Q$filter\E/) { + return; # filter ids early + } + + my $devdir = "$pcisysfs/devices/$fullid"; + + my $vendor = file_read_firstline("$devdir/vendor"); + my $device = file_read_firstline("$devdir/device"); + my $class = file_read_firstline("$devdir/class"); + + my $res = { + id => $id, + vendor => $vendor, + device => $device, + class => $class, + }; + + if (defined($filter) && ref($filter) eq 'CODE' && !$filter->($res)) { + return; + } + + if ($verbose) { + $res->{iommugroup} = -1; + if (-e "$devdir/iommu_group") { + my ($iommugroup) = (readlink("$devdir/iommu_group") =~ m/\/(\d+)$/); + $res->{iommugroup} = int($iommugroup); + } + + if (-d "$devdir/mdev_supported_types") { + $res->{mdev} = 1; + } + + my $device_hash = $ids->{$vendor}->{devices}->{$device} // {}; + + my $sub_vendor = file_read_firstline("$devdir/subsystem_vendor"); + my $sub_device = file_read_firstline("$devdir/subsystem_device"); + + my $vendor_name = $ids->{$vendor}->{name}; + my $device_name = $device_hash->{name}; + my $sub_vendor_name = $ids->{$sub_vendor}->{name}; + my $sub_device_name = $device_hash->{subs}->{$sub_vendor}->{$sub_device}; + + $res->{vendor_name} = $vendor_name if defined($vendor_name); + $res->{device_name} = $device_name if defined($device_name); + $res->{subsystem_vendor} = $sub_vendor if defined($sub_vendor); + $res->{subsystem_device} = $sub_device if defined($sub_device); + $res->{subsystem_vendor_name} = $sub_vendor_name if defined($sub_vendor_name); + $res->{subsystem_device_name} = $sub_device_name if defined($sub_device_name); + } + + push @$devices, $res; }); - # Entries should be sorted by functions. - foreach my $id (keys %$devices) { - my $dev = $devices->{$id}; - $devices->{$id} = [ sort { $a->{function} <=> $b->{function} } @$dev ]; - } + # Entries should be sorted by ids + $devices = [ sort { $a->{id} cmp $b->{id} } @$devices ]; return $devices; } +# +# return format: +# [ +# { +# type => 'FooType_1', +# description => "a longer description with custom format\nand newlines", +# available => 5, +# }, +# ... +# ] +# +sub get_mdev_types { + my ($id) = @_; + + $id = normalize_pci_id($id); + + my $types = []; + + my $mdev_path = "$pcisysfs/devices/$id/mdev_supported_types"; + if (!-d $mdev_path) { + return $types; + } + + dir_glob_foreach($mdev_path, '[^\.].*', sub { + my ($type) = @_; + + my $type_path = "$mdev_path/$type"; + + my $available = int(file_read_firstline("$type_path/available_instances")); + my $description = PVE::Tools::file_get_contents("$type_path/description"); + + my $entry = { + type => $type, + description => $description, + available => $available, + }; + + my $name = file_read_firstline("$type_path/name"); + $entry->{name} = $name if defined($name); + + push @$types, $entry; + }); + + return $types; +} + sub check_iommu_support{ - # iommu support if there is anything in /sys/class/iommu besides . or .. + # we have IOMMU support if /sys/class/iommu/ is populated return PVE::Tools::dir_glob_regex('/sys/class/iommu/', "[^\.].*"); } @@ -51,26 +206,28 @@ sub file_write { } sub pci_device_info { - my ($name) = @_; + my ($name, $verbose) = @_; my $res; return undef if $name !~ m/^${pciregex}$/; my ($domain, $bus, $slot, $func) = ($1, $2, $3, $4); - my $irq = file_read_firstline("$pcisysfs/devices/$name/irq"); + my $devdir = "$pcisysfs/devices/$name"; + + my $irq = file_read_firstline("$devdir/irq"); return undef if !defined($irq) || $irq !~ m/^\d+$/; - my $vendor = file_read_firstline("$pcisysfs/devices/$name/vendor"); + my $vendor = file_read_firstline("$devdir/vendor"); return undef if !defined($vendor) || $vendor !~ s/^0x//; - my $product = file_read_firstline("$pcisysfs/devices/$name/device"); + my $product = file_read_firstline("$devdir/device"); return undef if !defined($product) || $product !~ s/^0x//; $res = { name => $name, vendor => $vendor, - product => $product, + device => $product, domain => $domain, bus => $bus, slot => $slot, @@ -79,6 +236,25 @@ sub pci_device_info { has_fl_reset => -f "$pcisysfs/devices/$name/reset" || 0, }; + if ($verbose) { + my $sub_vendor = file_read_firstline("$devdir/subsystem_vendor"); + $sub_vendor =~ s/^0x// if defined($sub_vendor); + my $sub_device = file_read_firstline("$devdir/subsystem_device"); + $sub_device =~ s/^0x// if defined($sub_device); + + $res->{subsystem_vendor} = $sub_vendor if defined($sub_vendor); + $res->{subsystem_device} = $sub_device if defined($sub_device); + + if (-e "$devdir/iommu_group") { + my ($iommugroup) = (readlink("$devdir/iommu_group") =~ m/\/(\d+)$/); + $res->{iommugroup} = int($iommugroup); + } + + if (-d "$devdir/mdev_supported_types") { + $res->{mdev} = 1; + } + } + return $res; } @@ -107,7 +283,7 @@ sub pci_dev_bind_to_vfio { my $testdir = "$vfio_basedir/$name"; return 1 if -d $testdir; - my $data = "$dev->{vendor} $dev->{product}"; + my $data = "$dev->{vendor} $dev->{device}"; return undef if !file_write("$vfio_basedir/new_id", $data); my $fn = "$pcisysfs/devices/$name/driver/unbind"; @@ -133,17 +309,18 @@ sub pci_dev_group_bind_to_vfio { } die "Cannot find vfio-pci module!\n" if !-d $vfio_basedir; + $pciid = normalize_pci_id($pciid); + # get IOMMU group devices - opendir(my $D, "$pcisysfs/devices/0000:$pciid/iommu_group/devices/") || die "Cannot open iommu_group: $!\n"; - my @devs = grep /^0000:/, readdir($D); + opendir(my $D, "$pcisysfs/devices/$pciid/iommu_group/devices/") || die "Cannot open iommu_group: $!\n"; + my @devs = grep /^${domainregex}:/, readdir($D); closedir($D); foreach my $pciid (@devs) { - $pciid =~ m/^([:\.\da-f]+)$/ or die "PCI ID $pciid not valid!\n"; + $pciid =~ m/^([:\.0-9a-f]+)$/ or die "PCI ID $pciid not valid!\n"; - # pci bridges, switches or root ports are not supported - # they have a pci_bus subdirectory so skip them - next if (-e "$pcisysfs/devices/$pciid/pci_bus"); + # PCI bridges, switches or root-ports aren't supported and all have a pci_bus dir we can test + next if (-e "$pcisysfs/devices/$pciid/pci_bus"); my $info = pci_device_info($1); pci_dev_bind_to_vfio($info) || die "Cannot bind $pciid to vfio\n"; @@ -152,6 +329,52 @@ sub pci_dev_group_bind_to_vfio { return 1; } +sub pci_create_mdev_device { + my ($pciid, $uuid, $type) = @_; + + $pciid = normalize_pci_id($pciid); + + my $basedir = "$pcisysfs/devices/$pciid"; + my $mdev_dir = "$basedir/mdev_supported_types"; + + die "pci device '$pciid' does not support mediated devices \n" + if !-d $mdev_dir; + + die "pci device '$pciid' has no type '$type'\n" + if !-d "$mdev_dir/$type"; + + if (-d "$basedir/$uuid") { + # it already exists, checking type + my $typelink = readlink("$basedir/$uuid/mdev_type"); + my ($existingtype) = $typelink =~ m|/([^/]+)$|; + die "mdev instance '$uuid' already exits, but type is not '$type'\n" + if $type ne $existingtype; + + # instance exists, so use it but warn the user + warn "mdev instance '$uuid' already existed, using it.\n"; + return undef; + } + + my $instances = file_read_firstline("$mdev_dir/$type/available_instances"); + my ($avail) = $instances =~ m/^(\d+)$/; + die "pci device '$pciid' has no available instances of '$type'\n" + if $avail < 1; + + die "could not create 'type' for pci devices '$pciid'\n" + if !file_write("$mdev_dir/$type/create", $uuid); + + return undef; +} + +# encode the hostpci index and vmid into the uuid +sub generate_mdev_uuid { + my ($vmid, $index) = @_; + + my $string = sprintf("%08d-0000-0000-0000-%012d", $index, $vmid); + + return $string; +} + # idea is from usbutils package (/usr/bin/usb-devices) script sub __scan_usb_device { my ($res, $devpath, $parent, $level) = @_;