3 # this is some experimental code to test pci pass through
9 use Time
::HiRes
qw(usleep);
12 # linux/Documentation/filesystems/sysfs-pci.txt
13 # linux/DocumentationABI/testing/sysfs-bus-pci
17 PCI_CONF_HEADER_LEN
=> 0x40,
18 PCI_STATUS_CAP_LIST
=> 0x10,
19 PCI_CAPABILITY_LIST
=> 0x34,
20 PCI_CAP_ID_PM
=> 0x01,
22 PCI_PM_CTRL_STATE_MASK
=> 0x03,
23 PCI_PM_CTRL_STATE_D0
=> 0x00,
24 PCI_PM_CTRL_STATE_D3hot
=> 0x03,
25 PCI_PM_CTRL_NO_SOFT_RESET
=> 0x08,
28 my $pcisysfs = "/sys/bus/pci";
30 sub file_read_firstline
{
33 my $fh = IO
::File-
>new ($filename, "r");
44 my $fh = IO
::File-
>new ($filename, "r");
47 local $/ = undef; # enable slurp mode
55 my ($filename, $buf) = @_;
57 my $fh = IO
::File-
>new ($filename, "w");
60 my $res = print $fh $buf;
70 return file_read
("$pcisysfs/devices/$name/config");
73 sub pci_config_write
{
74 my ($name, $pos, $buf) = @_;
76 my $filename = "$pcisysfs/devices/$name/config";
78 my $fh = IO
::File-
>new ($filename, "w");
81 if (sysseek($fh, $pos, 0) != $pos) {
82 print "PCI WRITE seek failed\n";
86 my $res = syswrite ($fh, $buf);
87 print "PCI WRITE $res\n";
95 my ($conf, $pos, $fmt) = @_;
100 } elsif ($fmt eq 'S') {
102 } elsif ($fmt eq 'L') {
107 return undef if (($pos < 0) || (($pos + $len) > length($conf)));
109 return unpack($fmt, substr($conf, $pos, $len));
113 sub pci_device_list
{
117 my $dh = IO
::Dir-
>new ("$pcisysfs/devices") || return $res;
122 while (defined(my $name = $dh->read)) {
123 if ($name =~ m/^([a-f0-9]{4}):([a-f0-9]{2}):([a-f0-9]{2})\.([a-f0-9])$/i) {
124 my ($domain, $bus, $slot, $func) = ($1, $2, $3, $4);
126 my $irq = file_read_firstline
("$pcisysfs/devices/$name/irq");
127 next if $irq !~ m/^\d+$/;
129 my $irq_is_shared = defined($used_irqs->{$irq}) || 0;
130 $used_irqs->{$irq} = 1;
132 my $vendor = file_read_firstline
("$pcisysfs/devices/$name/vendor");
133 next if $vendor !~ s/^0x//;
134 my $product = file_read_firstline
("$pcisysfs/devices/$name/device");
135 next if $product !~ s/^0x//;
137 my $conf = read_pci_config
($name);
148 irq_is_shared
=> $irq_is_shared,
149 has_fl_reset
=> -f
"$pcisysfs/devices/$name/reset" || 0,
153 my $status = pci_config_read
($conf, PCI_STATUS
, 'S');
154 next if !defined ($status) || (!($status & PCI_STATUS_CAP_LIST
));
156 my $pos = pci_config_read
($conf, PCI_CAPABILITY_LIST
, 'C');
157 while ($pos && $pos > PCI_CONF_HEADER_LEN
&& $pos != 0xff) {
158 my $capid = pci_config_read
($conf, $pos, 'C');
159 last if !defined ($capid);
160 $res->{$name}->{cap
}->{$capid} = $pos;
161 $pos = pci_config_read
($conf, $pos + 1, 'C');
164 #print Dumper($res->{$name});
165 my $capid = PCI_CAP_ID_PM
;
166 if (my $pm_cap_off = $res->{$name}->{cap
}->{$capid}) {
167 # require the NO_SOFT_RESET bit is clear
168 my $ctl = pci_config_read
($conf, $pm_cap_off + PCI_PM_CTRL
, 'L');
169 if (defined ($ctl) && !($ctl & PCI_PM_CTRL_NO_SOFT_RESET
)) {
170 $res->{$name}->{has_pm_reset
} = 1;
181 my ($list, $name) = @_;
183 print "trying to reset $name\n";
185 my $dev = $list->{$name} || die "no such pci device '$name";
187 my $capid = PCI_CAP_ID_PM
;
188 my $pm_cap_off = $list->{$name}->{cap
}->{$capid};
190 return undef if !defined ($pm_cap_off);
191 return undef if !$dev->{has_pm_reset
};
193 my $conf = read_pci_config
($name) || die "cant read pci config";
195 my $ctl = pci_config_read
($conf, $pm_cap_off + PCI_PM_CTRL
, 'L');
196 return undef if !defined ($ctl);
198 $ctl = $ctl & ~PCI_PM_CTRL_STATE_MASK
;
200 pci_config_write
($name, $pm_cap_off + PCI_PM_CTRL
,
201 pack ('L', $ctl|PCI_PM_CTRL_STATE_D3hot
));
203 usleep
(10000); # 10ms
205 pci_config_write
($name, $pm_cap_off + PCI_PM_CTRL
,
206 pack ('L', $ctl|PCI_PM_CTRL_STATE_D0
));
208 usleep
(10000); # 10ms
210 return pci_config_write
($name, 0, $conf);
214 my ($list, $name) = @_;
216 print "trying to reset $name\n";
218 my $dev = $list->{$name} || die "no such pci device '$name";
220 my $fn = "$pcisysfs/devices/$name/reset";
222 return file_write
($fn, "1");
226 sub pci_dev_bind_to_stub
{
227 my ($list, $name) = @_;
229 my $dev = $list->{$name} || die "no such pci device '$name";
231 #return undef if $dev->{irq_is_shared};
233 my $testdir = "$pcisysfs/drivers/pci-stub/$name";
234 return 1 if -d
$testdir;
236 my $data = "$dev->{vendor} $dev->{product}";
237 return undef if !file_write
("$pcisysfs/drivers/pci-stub/new_id", $data);
239 my $fn = "$pcisysfs/devices/$name/driver/unbind";
240 if (!file_write
($fn, $name)) {
241 return undef if -f
$fn;
244 $fn = "$pcisysfs/drivers/pci-stub/bind";
246 return undef if !file_write
($fn, $name);
252 sub pci_dev_unbind_from_stub
{
253 my ($list, $name) = @_;
255 my $dev = $list->{$name} || die "no such pci device '$name";
257 #return undef if $dev->{irq_is_shared};
259 my $testdir = "$pcisysfs/drivers/pci-stub/$name";
260 return 1 if ! -d
$testdir;
262 my $data = "$dev->{vendor} $dev->{product}";
263 file_write
("$pcisysfs/drivers/pci-stub/remove_id", $data);
265 return undef if !file_write
("$pcisysfs/drivers/pci-stub/unbind", $name);
267 return ! -d
$testdir;
270 my $devlist = pci_device_list
();
271 print Dumper
($devlist);
273 my $name = $ARGV[0] || exit 0;
275 if (!pci_dev_bind_to_stub
($devlist, $name)) {
279 if (!pci_dev_unbind_from_stub
($devlist, $name)) {
284 #pci_pm_reset ($devlist, $name);
286 if (!pci_dev_reset
($devlist, $name)) {
287 print "reset failed\n";