]>
Commit | Line | Data |
---|---|---|
1e3baf05 DM |
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; |