]> git.proxmox.com Git - qemu-server.git/blob - pcitest.pl
imported from svn 'qemu-server/pve2'
[qemu-server.git] / pcitest.pl
1 #!/usr/bin/perl -w
2
3 # this is some experimental code to test pci pass through
4
5 use strict;
6 use IO::Dir;
7 use IO::File;
8 use Time::HiRes qw(usleep);
9 use Data::Dumper;
10
11 # linux/Documentation/filesystems/sysfs-pci.txt
12 # linux/DocumentationABI/testing/sysfs-bus-pci
13
14 use constant {
15 PCI_STATUS => 0x06,
16 PCI_CONF_HEADER_LEN => 0x40,
17 PCI_STATUS_CAP_LIST => 0x10,
18 PCI_CAPABILITY_LIST => 0x34,
19 PCI_CAP_ID_PM => 0x01,
20 PCI_PM_CTRL => 0x04,
21 PCI_PM_CTRL_STATE_MASK => 0x03,
22 PCI_PM_CTRL_STATE_D0 => 0x00,
23 PCI_PM_CTRL_STATE_D3hot => 0x03,
24 PCI_PM_CTRL_NO_SOFT_RESET => 0x08,
25 };
26
27 my $pcisysfs = "/sys/bus/pci";
28
29 sub file_read_firstline {
30 my ($filename) = @_;
31
32 my $fh = IO::File->new ($filename, "r");
33 return undef if !$fh;
34 my $res = <$fh>;
35 chomp $res;
36 $fh->close;
37 return $res;
38 }
39
40 sub file_read {
41 my ($filename) = @_;
42
43 my $fh = IO::File->new ($filename, "r");
44 return undef if !$fh;
45
46 local $/ = undef; # enable slurp mode
47 my $content = <$fh>;
48 $fh->close();
49
50 return $content;
51 }
52
53 sub file_write {
54 my ($filename, $buf) = @_;
55
56 my $fh = IO::File->new ($filename, "w");
57 return undef if !$fh;
58
59 my $res = print $fh $buf;
60
61 $fh->close();
62
63 return $res;
64 }
65
66 sub read_pci_config {
67 my $name = shift;
68
69 return file_read ("$pcisysfs/devices/$name/config");
70 }
71
72 sub pci_config_write {
73 my ($name, $pos, $buf) = @_;
74
75 my $filename = "$pcisysfs/devices/$name/config";
76
77 my $fh = IO::File->new ($filename, "w");
78 return undef if !$fh;
79
80 if (sysseek($fh, $pos, 0) != $pos) {
81 print "PCI WRITE seek failed\n";
82 return undef;
83 }
84
85 my $res = syswrite ($fh, $buf);
86 print "PCI WRITE $res\n";
87
88 $fh->close();
89
90 return $res;
91 }
92
93 sub pci_config_read {
94 my ($conf, $pos, $fmt) = @_;
95
96 my $len;
97 if ($fmt eq 'C') {
98 $len = 1;
99 } elsif ($fmt eq 'S') {
100 $len = 2;
101 } elsif ($fmt eq 'L') {
102 $len = 4;
103 } else {
104 return undef;
105 }
106 return undef if (($pos < 0) || (($pos + $len) > length($conf)));
107
108 return unpack($fmt, substr($conf, $pos, $len));
109 }
110
111
112 sub pci_device_list {
113
114 my $res = {};
115
116 my $dh = IO::Dir->new ("$pcisysfs/devices") || return $res;
117
118 my $used_irqs;
119
120 if ($dh) {
121 while (defined(my $name = $dh->read)) {
122 if ($name =~ m/^([a-f0-9]{4}):([a-f0-9]{2}):([a-f0-9]{2})\.([a-f0-9])$/i) {
123 my ($domain, $bus, $slot, $func) = ($1, $2, $3, $4);
124
125 my $irq = file_read_firstline("$pcisysfs/devices/$name/irq");
126 next if $irq !~ m/^\d+$/;
127
128 my $irq_is_shared = defined($used_irqs->{$irq}) || 0;
129 $used_irqs->{$irq} = 1;
130
131 my $vendor = file_read_firstline("$pcisysfs/devices/$name/vendor");
132 next if $vendor !~ s/^0x//;
133 my $product = file_read_firstline("$pcisysfs/devices/$name/device");
134 next if $product !~ s/^0x//;
135
136 my $conf = read_pci_config ($name);
137 next if !$conf;
138
139 $res->{$name} = {
140 vendor => $vendor,
141 product => $product,
142 domain => $domain,
143 bus => $bus,
144 slot => $slot,
145 func => $func,
146 irq => $irq,
147 irq_is_shared => $irq_is_shared,
148 has_fl_reset => -f "$pcisysfs/devices/$name/reset" || 0,
149 };
150
151
152 my $status = pci_config_read ($conf, PCI_STATUS, 'S');
153 next if !defined ($status) || (!($status & PCI_STATUS_CAP_LIST));
154
155 my $pos = pci_config_read ($conf, PCI_CAPABILITY_LIST, 'C');
156 while ($pos && $pos > PCI_CONF_HEADER_LEN && $pos != 0xff) {
157 my $capid = pci_config_read ($conf, $pos, 'C');
158 last if !defined ($capid);
159 $res->{$name}->{cap}->{$capid} = $pos;
160 $pos = pci_config_read ($conf, $pos + 1, 'C');
161 }
162
163 #print Dumper($res->{$name});
164 my $capid = PCI_CAP_ID_PM;
165 if (my $pm_cap_off = $res->{$name}->{cap}->{$capid}) {
166 # require the NO_SOFT_RESET bit is clear
167 my $ctl = pci_config_read ($conf, $pm_cap_off + PCI_PM_CTRL, 'L');
168 if (defined ($ctl) && !($ctl & PCI_PM_CTRL_NO_SOFT_RESET)) {
169 $res->{$name}->{has_pm_reset} = 1;
170 }
171 }
172 }
173 }
174 }
175
176 return $res;
177 }
178
179 sub pci_pm_reset {
180 my ($list, $name) = @_;
181
182 print "trying to reset $name\n";
183
184 my $dev = $list->{$name} || die "no such pci device '$name";
185
186 my $capid = PCI_CAP_ID_PM;
187 my $pm_cap_off = $list->{$name}->{cap}->{$capid};
188
189 return undef if !defined ($pm_cap_off);
190 return undef if !$dev->{has_pm_reset};
191
192 my $conf = read_pci_config ($name) || die "cant read pci config";
193
194 my $ctl = pci_config_read ($conf, $pm_cap_off + PCI_PM_CTRL, 'L');
195 return undef if !defined ($ctl);
196
197 $ctl = $ctl & ~PCI_PM_CTRL_STATE_MASK;
198
199 pci_config_write($name, $pm_cap_off + PCI_PM_CTRL,
200 pack ('L', $ctl|PCI_PM_CTRL_STATE_D3hot));
201
202 usleep(10000); # 10ms
203
204 pci_config_write($name, $pm_cap_off + PCI_PM_CTRL,
205 pack ('L', $ctl|PCI_PM_CTRL_STATE_D0));
206
207 usleep(10000); # 10ms
208
209 return pci_config_write($name, 0, $conf);
210 }
211
212 sub pci_dev_reset {
213 my ($list, $name) = @_;
214
215 print "trying to reset $name\n";
216
217 my $dev = $list->{$name} || die "no such pci device '$name";
218
219 my $fn = "$pcisysfs/devices/$name/reset";
220
221 return file_write ($fn, "1");
222 }
223
224
225 sub pci_dev_bind_to_stub {
226 my ($list, $name) = @_;
227
228 my $dev = $list->{$name} || die "no such pci device '$name";
229
230 #return undef if $dev->{irq_is_shared};
231
232 my $testdir = "$pcisysfs/drivers/pci-stub/$name";
233 return 1 if -d $testdir;
234
235 my $data = "$dev->{vendor} $dev->{product}";
236 return undef if !file_write ("$pcisysfs/drivers/pci-stub/new_id", $data);
237
238 my $fn = "$pcisysfs/devices/$name/driver/unbind";
239 if (!file_write ($fn, $name)) {
240 return undef if -f $fn;
241 }
242
243 $fn = "$pcisysfs/drivers/pci-stub/bind";
244 if (! -d $testdir) {
245 return undef if !file_write ($fn, $name);
246 }
247
248 return -d $testdir;
249 }
250
251 sub pci_dev_unbind_from_stub {
252 my ($list, $name) = @_;
253
254 my $dev = $list->{$name} || die "no such pci device '$name";
255
256 #return undef if $dev->{irq_is_shared};
257
258 my $testdir = "$pcisysfs/drivers/pci-stub/$name";
259 return 1 if ! -d $testdir;
260
261 my $data = "$dev->{vendor} $dev->{product}";
262 file_write ("$pcisysfs/drivers/pci-stub/remove_id", $data);
263
264 return undef if !file_write ("$pcisysfs/drivers/pci-stub/unbind", $name);
265
266 return ! -d $testdir;
267 }
268
269 my $devlist = pci_device_list();
270 print Dumper($devlist);
271
272 my $name = $ARGV[0] || exit 0;
273
274 if (!pci_dev_bind_to_stub($devlist, $name)) {
275 print "failed\n";
276 exit (-1);
277 }
278 if (!pci_dev_unbind_from_stub($devlist, $name)) {
279 print "failed\n";
280 exit (-1);
281 }
282
283 #pci_pm_reset ($devlist, $name);
284
285 if (!pci_dev_reset ($devlist, $name)) {
286 print "reset failed\n";
287 exit (-1);
288 }
289
290
291 exit 0;