3 # this is some experimental code to test pci pass through
8 use Time
::HiRes
qw(usleep);
11 # linux/Documentation/filesystems/sysfs-pci.txt
12 # linux/DocumentationABI/testing/sysfs-bus-pci
16 PCI_CONF_HEADER_LEN
=> 0x40,
17 PCI_STATUS_CAP_LIST
=> 0x10,
18 PCI_CAPABILITY_LIST
=> 0x34,
19 PCI_CAP_ID_PM
=> 0x01,
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,
27 my $pcisysfs = "/sys/bus/pci";
29 sub file_read_firstline
{
32 my $fh = IO
::File-
>new ($filename, "r");
43 my $fh = IO
::File-
>new ($filename, "r");
46 local $/ = undef; # enable slurp mode
54 my ($filename, $buf) = @_;
56 my $fh = IO
::File-
>new ($filename, "w");
59 my $res = print $fh $buf;
69 return file_read
("$pcisysfs/devices/$name/config");
72 sub pci_config_write
{
73 my ($name, $pos, $buf) = @_;
75 my $filename = "$pcisysfs/devices/$name/config";
77 my $fh = IO
::File-
>new ($filename, "w");
80 if (sysseek($fh, $pos, 0) != $pos) {
81 print "PCI WRITE seek failed\n";
85 my $res = syswrite ($fh, $buf);
86 print "PCI WRITE $res\n";
94 my ($conf, $pos, $fmt) = @_;
99 } elsif ($fmt eq 'S') {
101 } elsif ($fmt eq 'L') {
106 return undef if (($pos < 0) || (($pos + $len) > length($conf)));
108 return unpack($fmt, substr($conf, $pos, $len));
112 sub pci_device_list
{
116 my $dh = IO
::Dir-
>new ("$pcisysfs/devices") || return $res;
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);
125 my $irq = file_read_firstline
("$pcisysfs/devices/$name/irq");
126 next if $irq !~ m/^\d+$/;
128 my $irq_is_shared = defined($used_irqs->{$irq}) || 0;
129 $used_irqs->{$irq} = 1;
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//;
136 my $conf = read_pci_config
($name);
147 irq_is_shared
=> $irq_is_shared,
148 has_fl_reset
=> -f
"$pcisysfs/devices/$name/reset" || 0,
152 my $status = pci_config_read
($conf, PCI_STATUS
, 'S');
153 next if !defined ($status) || (!($status & PCI_STATUS_CAP_LIST
));
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');
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;
180 my ($list, $name) = @_;
182 print "trying to reset $name\n";
184 my $dev = $list->{$name} || die "no such pci device '$name";
186 my $capid = PCI_CAP_ID_PM
;
187 my $pm_cap_off = $list->{$name}->{cap
}->{$capid};
189 return undef if !defined ($pm_cap_off);
190 return undef if !$dev->{has_pm_reset
};
192 my $conf = read_pci_config
($name) || die "cant read pci config";
194 my $ctl = pci_config_read
($conf, $pm_cap_off + PCI_PM_CTRL
, 'L');
195 return undef if !defined ($ctl);
197 $ctl = $ctl & ~PCI_PM_CTRL_STATE_MASK
;
199 pci_config_write
($name, $pm_cap_off + PCI_PM_CTRL
,
200 pack ('L', $ctl|PCI_PM_CTRL_STATE_D3hot
));
202 usleep
(10000); # 10ms
204 pci_config_write
($name, $pm_cap_off + PCI_PM_CTRL
,
205 pack ('L', $ctl|PCI_PM_CTRL_STATE_D0
));
207 usleep
(10000); # 10ms
209 return pci_config_write
($name, 0, $conf);
213 my ($list, $name) = @_;
215 print "trying to reset $name\n";
217 my $dev = $list->{$name} || die "no such pci device '$name";
219 my $fn = "$pcisysfs/devices/$name/reset";
221 return file_write
($fn, "1");
225 sub pci_dev_bind_to_stub
{
226 my ($list, $name) = @_;
228 my $dev = $list->{$name} || die "no such pci device '$name";
230 #return undef if $dev->{irq_is_shared};
232 my $testdir = "$pcisysfs/drivers/pci-stub/$name";
233 return 1 if -d
$testdir;
235 my $data = "$dev->{vendor} $dev->{product}";
236 return undef if !file_write
("$pcisysfs/drivers/pci-stub/new_id", $data);
238 my $fn = "$pcisysfs/devices/$name/driver/unbind";
239 if (!file_write
($fn, $name)) {
240 return undef if -f
$fn;
243 $fn = "$pcisysfs/drivers/pci-stub/bind";
245 return undef if !file_write
($fn, $name);
251 sub pci_dev_unbind_from_stub
{
252 my ($list, $name) = @_;
254 my $dev = $list->{$name} || die "no such pci device '$name";
256 #return undef if $dev->{irq_is_shared};
258 my $testdir = "$pcisysfs/drivers/pci-stub/$name";
259 return 1 if ! -d
$testdir;
261 my $data = "$dev->{vendor} $dev->{product}";
262 file_write
("$pcisysfs/drivers/pci-stub/remove_id", $data);
264 return undef if !file_write
("$pcisysfs/drivers/pci-stub/unbind", $name);
266 return ! -d
$testdir;
269 my $devlist = pci_device_list
();
270 print Dumper
($devlist);
272 my $name = $ARGV[0] || exit 0;
274 if (!pci_dev_bind_to_stub
($devlist, $name)) {
278 if (!pci_dev_unbind_from_stub
($devlist, $name)) {
283 #pci_pm_reset ($devlist, $name);
285 if (!pci_dev_reset
($devlist, $name)) {
286 print "reset failed\n";