]> git.proxmox.com Git - pve-common.git/blob - src/PVE/SysFSTools.pm
c16e7d3720947c7b43684443c4c73650816fa9c2
[pve-common.git] / src / PVE / SysFSTools.pm
1 package PVE::SysFSTools;
2
3 use strict;
4 use warnings;
5
6 use IO::File;
7
8 use PVE::Tools qw(file_read_firstline dir_glob_foreach);
9
10 my $pcisysfs = "/sys/bus/pci";
11 my $pciregex = "([a-f0-9]{4}):([a-f0-9]{2}):([a-f0-9]{2})\.([a-f0-9])";
12
13 sub lspci {
14 my ($filter) = @_;
15
16 my $devices = {};
17
18 dir_glob_foreach("$pcisysfs/devices", $pciregex, sub {
19 my (undef, undef, $bus, $slot, $function) = @_;
20 my $id = "$bus:$slot";
21 return if defined($filter) && $id ne $filter;
22 my $res = { id => $id, function => $function};
23 push @{$devices->{$id}}, $res;
24 });
25
26 # Entries should be sorted by functions.
27 foreach my $id (keys %$devices) {
28 my $dev = $devices->{$id};
29 $devices->{$id} = [ sort { $a->{function} <=> $b->{function} } @$dev ];
30 }
31
32 return $devices;
33 }
34
35 sub check_iommu_support{
36 # we have IOMMU support if /sys/class/iommu/ is populated
37 return PVE::Tools::dir_glob_regex('/sys/class/iommu/', "[^\.].*");
38 }
39
40 sub file_write {
41 my ($filename, $buf) = @_;
42
43 my $fh = IO::File->new($filename, "w");
44 return undef if !$fh;
45
46 my $res = print $fh $buf;
47
48 $fh->close();
49
50 return $res;
51 }
52
53 sub pci_device_info {
54 my ($name) = @_;
55
56 my $res;
57
58 return undef if $name !~ m/^${pciregex}$/;
59 my ($domain, $bus, $slot, $func) = ($1, $2, $3, $4);
60
61 my $irq = file_read_firstline("$pcisysfs/devices/$name/irq");
62 return undef if !defined($irq) || $irq !~ m/^\d+$/;
63
64 my $vendor = file_read_firstline("$pcisysfs/devices/$name/vendor");
65 return undef if !defined($vendor) || $vendor !~ s/^0x//;
66
67 my $product = file_read_firstline("$pcisysfs/devices/$name/device");
68 return undef if !defined($product) || $product !~ s/^0x//;
69
70 $res = {
71 name => $name,
72 vendor => $vendor,
73 product => $product,
74 domain => $domain,
75 bus => $bus,
76 slot => $slot,
77 func => $func,
78 irq => $irq,
79 has_fl_reset => -f "$pcisysfs/devices/$name/reset" || 0,
80 };
81
82 return $res;
83 }
84
85 sub pci_dev_reset {
86 my ($dev) = @_;
87
88 my $name = $dev->{name};
89
90 my $fn = "$pcisysfs/devices/$name/reset";
91
92 return file_write($fn, "1");
93 }
94
95 sub pci_dev_bind_to_vfio {
96 my ($dev) = @_;
97
98 my $name = $dev->{name};
99
100 my $vfio_basedir = "$pcisysfs/drivers/vfio-pci";
101
102 if (!-d $vfio_basedir) {
103 system("/sbin/modprobe vfio-pci >/dev/null 2>/dev/null");
104 }
105 die "Cannot find vfio-pci module!\n" if !-d $vfio_basedir;
106
107 my $testdir = "$vfio_basedir/$name";
108 return 1 if -d $testdir;
109
110 my $data = "$dev->{vendor} $dev->{product}";
111 return undef if !file_write("$vfio_basedir/new_id", $data);
112
113 my $fn = "$pcisysfs/devices/$name/driver/unbind";
114 if (!file_write($fn, $name)) {
115 return undef if -f $fn;
116 }
117
118 $fn = "$vfio_basedir/bind";
119 if (! -d $testdir) {
120 return undef if !file_write($fn, $name);
121 }
122
123 return -d $testdir;
124 }
125
126 sub pci_dev_group_bind_to_vfio {
127 my ($pciid) = @_;
128
129 my $vfio_basedir = "$pcisysfs/drivers/vfio-pci";
130
131 if (!-d $vfio_basedir) {
132 system("/sbin/modprobe vfio-pci >/dev/null 2>/dev/null");
133 }
134 die "Cannot find vfio-pci module!\n" if !-d $vfio_basedir;
135
136 # get IOMMU group devices
137 opendir(my $D, "$pcisysfs/devices/0000:$pciid/iommu_group/devices/") || die "Cannot open iommu_group: $!\n";
138 my @devs = grep /^0000:/, readdir($D);
139 closedir($D);
140
141 foreach my $pciid (@devs) {
142 $pciid =~ m/^([:\.\da-f]+)$/ or die "PCI ID $pciid not valid!\n";
143
144 # pci bridges, switches or root ports are not supported
145 # they have a pci_bus subdirectory so skip them
146 next if (-e "$pcisysfs/devices/$pciid/pci_bus");
147
148 my $info = pci_device_info($1);
149 pci_dev_bind_to_vfio($info) || die "Cannot bind $pciid to vfio\n";
150 }
151
152 return 1;
153 }
154
155 # idea is from usbutils package (/usr/bin/usb-devices) script
156 sub __scan_usb_device {
157 my ($res, $devpath, $parent, $level) = @_;
158
159 return if ! -d $devpath;
160 return if $level && $devpath !~ m/^.*[-.](\d+)$/;
161 my $port = $level ? int($1 - 1) : 0;
162
163 my $busnum = int(file_read_firstline("$devpath/busnum"));
164 my $devnum = int(file_read_firstline("$devpath/devnum"));
165
166 my $d = {
167 port => $port,
168 level => $level,
169 busnum => $busnum,
170 devnum => $devnum,
171 speed => file_read_firstline("$devpath/speed"),
172 class => hex(file_read_firstline("$devpath/bDeviceClass")),
173 vendid => file_read_firstline("$devpath/idVendor"),
174 prodid => file_read_firstline("$devpath/idProduct"),
175 };
176
177 if ($level) {
178 my $usbpath = $devpath;
179 $usbpath =~ s|^.*/\d+\-||;
180 $d->{usbpath} = $usbpath;
181 }
182
183 my $product = file_read_firstline("$devpath/product");
184 $d->{product} = $product if $product;
185
186 my $manu = file_read_firstline("$devpath/manufacturer");
187 $d->{manufacturer} = $manu if $manu;
188
189 my $serial => file_read_firstline("$devpath/serial");
190 $d->{serial} = $serial if $serial;
191
192 push @$res, $d;
193
194 foreach my $subdev (<$devpath/$busnum-*>) {
195 next if $subdev !~ m|/$busnum-[0-9]+(\.[0-9]+)*$|;
196 __scan_usb_device($res, $subdev, $devnum, $level + 1);
197 }
198
199 };
200
201 sub scan_usb {
202
203 my $devlist = [];
204
205 foreach my $device (</sys/bus/usb/devices/usb*>) {
206 __scan_usb_device($devlist, $device, 0, 0);
207 }
208
209 return $devlist;
210 }
211
212 1;