]> git.proxmox.com Git - pve-common.git/blob - src/PVE/SysFSTools.pm
SysFSTools: implement IOMMU check
[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 # iommu support if there is anything in /sys/class/iommu besides . or ..
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 1;