#!/usr/bin/perl
#
# dump_pirq 1.7 2000/04/03 17:59:51
#
# A utility to parse the BIOS PCI IRQ Routing Table
#
# Copyright (C) 2000 David A. Hinds -- dhinds@pcmcia.sourceforge.org
#

# Grab the BIOS from 0xf0000-0xfffff
open(IN, "/dev/mem") || die "cannot open /dev/mem\n";
sysseek(IN, 0xf0000, 0) || die;
die if (sysread(IN, $buf, 0x10000) != 0x10000);
close(IN);

# Look for PIR signature string
$p = index($buf, "\$PIR");
if ($p < 0) {
    print "No interrupt routing information found.\n";
    exit;
}

# Parse the interrupt table header
($minor,$major,$size,$rbus,$rdev,$mask,$cvd,$mini) =
    unpack "CCSCCSLL", substr($buf, $p+4, 16);

sub dev {
    my($devfn) = @_;
    sprintf "%02x.%d", ($devfn>>3), ($devfn&7);
}

printf "Interrupt routing table found at address 0xf%04x:\n", $p;
printf "  Version $major.$minor, size 0x%04x\n", $size;
printf "  Interrupt router is device %02x:%s\n", $rbus, dev($rdev);
printf "  PCI exclusive interrupt mask: 0x%04x\n", $mask;
if ($cvd) {
    printf("  Compatible router: vendor 0x%04x device 0x%04x\n",
	   ($cvd & 0xffff), ($cvd >> 16));
}

sub row {
    my($tag, $link, $mask) = @_;
    if ($link != 0) {
	printf "  INT$tag: link 0x%02x, irq mask 0x%04x\n", $link, $mask;
    }
}

$ofs = 32;
while ($ofs < $size) {
    # Parse a table entry describing a single PCI device
    ($bus, $devfn, $la, $ma, $lb, $mb, $lc, $mc, $ld, $md, $slot) =
	unpack "CCCSCSCSCSC", substr($buf, $p+$ofs, 15);
    printf "\nDevice %02x:%s (slot $slot):\n", $bus, dev($devfn);
    row("A", $la, $ma); row("B", $lb, $mb);
    row("C", $lc, $mc); row("D", $ld, $md);
    $ofs += 16;
}

# The link values in the interrupt routing table are implementation
# dependent.  Here, we'll try to interpret the link values for some
# known PCI bridge types.

$rf = sprintf "/proc/bus/pci/%02x/%s", $rbus, dev($rdev);
open(IN, $rf);
if (sysread(IN, $buf, 0x100) != 0x100) {
    print "\nCould not read router info from $rf.\n";
    exit;
}
close(IN);

print "\nInterrupt router: ";
$vd = unpack "L", substr($buf, 0, 4);

%pIIx = (0x122e8086, "82371FB PIIX",
	 0x70008086, "82371SB PIIX3",
	 0x71108086, "82371AB PIIX4/PIIX4E",
	 0x71988086, "82440MX");

%via = (0x05961106, "82C596",
	0x06861106, "82C686");

%opti = (0xc7001045, "82C700");

%pico = (0x00021066, "PT86C523");

%ali = (0x153310b9, "Aladdin M1533");

sub outb
{
    my($data,$port) = @_;
    open(IO, ">/dev/port") || die;
    sysseek(IO, $port, 0) || die;
    my $x = pack "C", $data;
    syswrite(IO, $x, 1);
    close(IO);
}

sub inb
{
    my($port) = @_;
    my($data);
    open(IO, "/dev/port") || die;
    sysseek(IO, $port, 0) || die;
    sysread(IO, $data, 1);
    close(IO);
    return unpack "C", $data;
}

if ((defined $pIIx{$vd}) || (defined $pIIx{$cvd})) {

    $name = (defined $pIIx{$vd}) ? $pIIx{$vd} : $pIIx{$cvd};
    printf "Intel $name PCI-to-ISA bridge\n";
    @p = unpack "CCCCC", substr($buf, 0x60, 5);
    for ($i = 0; $i < 4; $i++) {
	printf "  PIRQ%d (link 0x%02x): ", $i+1, 0x60+$i;
	print (($p[$i] < 16) ? "irq $p[$i]\n" : "unrouted\n");
    }
    print "  Serial IRQ:";
    print (($p[4] & 0x80) ? " [enabled]" : " [disabled]");
    print (($p[4] & 0x40) ? " [continuous]" : " [quiet]");
    print " [frame=", (($p[4] >> 2) & 15) + 17, "]";
    print " [pulse=", (($p[4] & 3) * 4 + 4), "]\n";

} elsif ((defined $via{$vd}) || (defined $via{$cvd})) {

    $name = (defined $via{$vd}) ? $via{$vd} : $via{$cvd};
    printf "VIA $name PCI-to-ISA bridge\n";
    $p = unpack "L", substr($buf, 0x55, 4);
    %tag = (1, "A", 2, "B", 3, "C", 5, "D");
    foreach $link (1,2,3,5) {
	$irq = ($p >> ($link * 4)) & 15;
	print "  PIRQ$tag{$link} (link 0x0$link): ";
	print ($irq ? "irq $irq\n" : "unrouted\n");
    }

} elsif ((defined $opti{$vd}) || (defined $opti{$cvd})) {

    $name = (defined $opti{$vd}) ? $opti{$vd} : $opti{$cvd};
    printf "OPTi $name PCI-to-ISA bridge\n";
    $p = unpack "S", substr($buf, 0xb8, 2);
    for ($i = 0; $i < 4; $i++) {
	$irq = ($p >> ($i * 4)) & 15;
	printf "  PCIRQ$i (link 0x%02x): ", ($i<<4)+0x02;
	print ($irq ? "irq $irq\n" : "unrouted\n");
    }

} elsif ((defined $pico{$vd} || defined $pico{$cvd})) {

    $name = (defined $pico{$vd}) ? $pico{$vd} : $pico{$cvd};
    printf "PicoPower $name PCI-to-ISA bridge\n";
    outb(0x10, 0x24); $p = inb(0x26);
    outb(0x11, 0x24); $p += inb(0x26)<<8;
    @tag = ("A", "B", "C", "D");
    for ($i = 0; $i < 4; $i++) {
	$irq = ($p >> ($i * 4)) & 15;
	print "  INT$tag[$i] (link 0x0", $i+1, "): ";
	print ($irq ? "irq $irq\n" : "unrouted\n");
    }

} elsif ((defined $ali{$vd} || defined $ali{$cvd})) {

    $name = (defined $ali{$vd}) ? $ali{$vd} : $ali{$cvd};
    printf "AcerLabs $name PCI-to-ISA bridge\n";
    $p = unpack "L", substr($buf, 0x48, 4);
    # This mapping is insane!
    @map = (0, 9, 3, 10, 4, 5, 7, 6, 1, 11, 0, 12, 0, 14, 0, 15);
    for ($i = 0; $i < 8; $i++) {
	$irq = ($p >> ($i*4)) & 15;
	print "  INT", $i+1, " (link ", $i+1, "): ";
	print ($map[$irq] ? "irq $map[$irq]\n" : "unrouted\n");
    }
    $s = unpack "C", substr($buf, 0x70, 1);
    print "  Serial IRQ:";
    print (($s & 0x80) ? " [enabled]" : " [disabled]");
    print (($s & 0x40) ? " [continuous]" : " [quiet]");
    print " [frame=", (($s >> 2) & 15) + 17, "]";
    print " [pulse=", (($s & 3) * 4 + 4), "]\n";

} else {

    printf("unknown vendor 0x%04x device 0x%04x\n",
	   ($vd & 0xffff), ($vd >> 16));

}
