
/*
 *  name:             scanpci.c
 *
 *  purpose:          This program will scan for and print details of
 *                    devices on the PCI bus.

 *  author:           Robin Cutshaw (robin@xfree86.org)
 *
 *  supported O/S's:  SVR4, UnixWare, SCO,
 *                    FreeBSD, NetBSD, 386BSD, BSDI BSD/386,
 *                    Linux, Mach/386,
 *                    DOS (WATCOM 9.5 compiler)
 *
 *  compiling:        [g]cc scanpci.c -o scanpci
 *                    for SVR4, UnixWare use:
 *                        [g]cc -DSVR4 scanpci.c -o scanpci
 *                    for DOS, watcom 9.5:
 *                        wcc386p -zq -omaxet -7 -4s -s -w3 -d2 name.c
 *                        and link with PharLap or other dos extender for exe
 *
 */

/*
 *  Copyright (C) 1994, 1995 The XFree86 Project, Inc.  All Rights
 *  Reserved.
 *
 *  Permission is hereby granted, free of charge, to any person
 *  obtaining a copy of this software and associated documentation
 *  files (the "Software"), to deal in the Software without
 *  restriction, including without limitation the rights to use,
 *  copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  copies of the Software, and to permit persons to whom the
 *  Software is furnished to do so, subject to the following
 *  conditions:
 *
 *  The above copyright notice and this permission notice shall be
 *  included in all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 *  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 *  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 *  NONINFRINGEMENT.  IN NO EVENT SHALL THE XFREE86 PROJECT BE LIABLE
 *  FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 *  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 *  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 *  SOFTWARE.
 *
 *  Except as contained in this notice, the name of the XFree86
 *  Project shall not be used in advertising or otherwise to promote
 *  the sale, use or other dealings in this Software without prior
 *  written authorization from the XFree86 Project.
 */

#include <stdio.h>
#include <sys/types.h>
#if defined(SVR4)
#include <sys/proc.h>
#include <sys/tss.h>
#include <sys/sysi86.h>
#include <sys/seg.h>
#include <sys/v86.h>
#endif
#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__386BSD__)
#include <sys/file.h>
#include <machine/console.h>
#define GCCUSESGAS
#endif
#if defined(__bsdi__)
#include <sys/file.h>
#include <sys/ioctl.h>
#include <i386/isa/pcconsioctl.h>
#define GCCUSESGAS
#endif
#if defined(SCO)
#include <sys/console.h>
#endif


#if defined(__WATCOMC__)

#include <stdlib.h>
void outl(unsigned port, unsigned data);
#pragma aux outl =  "out    dx, eax" parm [dx] [eax];
void outb(unsigned port, unsigned data);
#pragma aux outb = "out    dx, al" parm [dx] [eax];
unsigned inl(unsigned port);
#pragma aux inl = "in     eax, dx" parm [dx];
unsigned inb(unsigned port);
#pragma aux inb = "xor    eax,eax" "in     al, dx" parm [dx];

#else /* __WATCOMC__ */

#if defined(__GNUC__)

#if defined(GCCUSESGAS)
#define OUTB_GCC "outb %0,%1"
#define OUTL_GCC "outl %0,%1"
#define INB_GCC  "inb %1,%0"
#define INL_GCC  "inl %1,%0"
#else
#define OUTB_GCC "out%B0 (%1)"
#define OUTL_GCC "out%L0 (%1)"
#define INB_GCC "in%B0 (%1)"
#define INL_GCC "in%L0 (%1)"
#endif /* GCCUSESGAS */

static void outb(short port, char val) {
     __asm__ __volatile__(OUTB_GCC : :"a" (val), "d" (port)); }
static void outl(short port, unsigned long val) {
     __asm__ __volatile__(OUTL_GCC : :"a" (val), "d" (port)); }
static unsigned char inb(short port) { unsigned char ret;
     __asm__ __volatile__(INB_GCC : "=a" (ret) : "d" (port)); return ret; }
static unsigned long inl(short port) { unsigned long ret;
     __asm__ __volatile__(INL_GCC : "=a" (ret) : "d" (port)); return ret; }

#else  /* __GNUC__ */

#if defined(__STDC__) && (__STDC__ == 1)
# ifndef NCR
#  define asm __asm
# endif
#endif

#ifdef SVR4
# ifndef __USLC__
#  define __USLC__
# endif
#endif

#include <sys/inline.h>

#endif /* __GNUC__ */
#endif /* __WATCOMC__ */


struct pci_config_reg {
    /* start of official PCI config space header */
    union {
        unsigned long device_vendor;
	struct {
	    unsigned short vendor;
	    unsigned short device;
	} dv;
    } dv_id;
#define _device_vendor dv_id.device_vendor
#define _vendor dv_id.dv.vendor
#define _device dv_id.dv.device
    union {
        unsigned long status_command;
	struct {
	    unsigned short command;
	    unsigned short status;
	} sc;
    } stat_cmd;
#define _status_command stat_cmd.status_command
#define _command stat_cmd.sc.command
#define _status  stat_cmd.sc.status
    union {
        unsigned long class_revision;
	struct {
	    unsigned char rev_id;
	    unsigned char prog_if;
	    unsigned char sub_class;
	    unsigned char base_class;
	} cr;
    } class_rev;
#define _class_revision class_rev.class_revision
#define _rev_id     class_rev.cr.rev_id
#define _prog_if    class_rev.cr.prog_if
#define _sub_class  class_rev.cr.sub_class
#define _base_class class_rev.cr.base_class
    union {
        unsigned long bist_header_latency_cache;
	struct {
	    unsigned char cache_line_size;
	    unsigned char latency_timer;
	    unsigned char header_type;
	    unsigned char bist;
	} bhlc;
    } bhlc;
#define _bist_header_latency_cache bhlc.bist_header_latency_cache
#define _cache_line_size bhlc.bhlc.cache_line_size
#define _latency_timer   bhlc.bhlc.latency_timer
#define _header_type     bhlc.bhlc.header_type
#define _bist            bhlc.bhlc.bist
    unsigned long _base0;
    unsigned long _base1;
    unsigned long _base2;
    unsigned long _base3;
    unsigned long _base4;
    unsigned long _base5;
    unsigned long rsvd1;
    unsigned long rsvd2;
    unsigned long _baserom;
    unsigned long rsvd3;
    unsigned long rsvd4;
    union {
        unsigned long max_min_ipin_iline;
	struct {
	    unsigned char int_line;
	    unsigned char int_pin;
	    unsigned char min_gnt;
	    unsigned char max_lat;
	} mmii;
    } mmii;
#define _max_min_ipin_iline mmii.max_min_ipin_iline
#define _int_line mmii.mmii.int_line
#define _int_pin  mmii.mmii.int_pin
#define _min_gnt  mmii.mmii.min_gnt
#define _max_lat  mmii.mmii.max_lat
    /* end of official PCI config space header */
    unsigned short _ioaddr; /* config type 1 - private I/O addr    */
    unsigned long _pcibus;  /* config type 2 - private bus id      */
    unsigned long _cardnum; /* config type 2 - private card number */
};

extern void identify_card(struct pci_config_reg *);
extern void print_i128(struct pci_config_reg *);
extern void enable_os_io();
extern void disable_os_io();

#define MAX_DEV_PER_VENDOR 16
#define NF ((void (*)())NULL)

struct pci_vendor_device {
    unsigned short vendor_id;
    char *vendorname;
    struct pci_device {
        unsigned short device_id;
        char *devicename;
	void (*print_func)(struct pci_config_reg *);
    } device[MAX_DEV_PER_VENDOR];
} pvd[] = {
        { 0x1000, "NCR", {
                            { 0x0001, "53C810", NF },
                            { 0x0002, "53C820", NF },
                            { 0x0003, "53C825", NF },
                            { 0x0000, (char *)NULL, NF } } },
        { 0x1002, "ATI", {
                            { 0x4158, "Mach32", NF },
                            { 0x4758, "Mach64", NF },
                            { 0x0000, (char *)NULL, NF } } },
        { 0x100C, "Tseng Labs", {
                            { 0x3202, "ET4000w32p rev A", NF },
                            { 0x3207, "ET4000w32p rev D", NF },
                            { 0x0000, (char *)NULL, NF } } },
        { 0x100E, "Diamond", {
                            { 0x9001, "Viper/PCI", NF },
                            { 0x0000, (char *)NULL, NF } } },
        { 0x1011, "Digital", {
                            { 0x0002, "DC21040 10Mb/s Ethernet", NF },
                            { 0x0009, "DC21140 10/100 Mb/s Ethernet", NF },
                            { 0x000F, "DEFPA (FDDI PCI)", NF },
                            { 0x0000, (char *)NULL, NF } } },
        { 0x1013, "Cirrus Logic", {
                            { 0x00A4, "unknown", NF },
                            { 0x00A8, "5434", NF },
                            { 0x0000, (char *)NULL, NF } } },
        { 0x101A, "NCR", {
                            { 0x0000, (char *)NULL, NF } } },
        { 0x1025, "ALI", {
                            { 0x1435, "M1435", NF },
                            { 0x0000, (char *)NULL, NF } } },
        { 0x102B, "Matrox", {
                            { 0x0000, (char *)NULL, NF } } },
        { 0x1042, "SMC", {
                            { 0x1000, "37C665", NF },
                            { 0x0000, (char *)NULL, NF } } },
        { 0x1045, "Opti", {
                            { 0xC822, "82C822", NF },
                            { 0xC621, "82C621", NF },
                            { 0x0000, (char *)NULL, NF } } },
        { 0x104B, "BusLogic", {
                            { 0x0140, "946C", NF },
                            { 0x0000, (char *)NULL, NF } } },
        { 0x105D, "Number Nine", {
                            { 0x2309, "Imagine-128", print_i128 },
                            { 0x0000, (char *)NULL, NF } } },
        { 0x1060, "UMC", {
                            { 0x0101, "UM8673F", NF },
                            { 0x8881, "UM8881F", NF },
                            { 0x8886, "UM8886F", NF },
                            { 0x0000, (char *)NULL, NF } } },
        { 0x5333, "S3", {
                            { 0x8811, "Trio64", NF },
                            { 0x88B0, "928", NF },
                            { 0x88C0, "864-0", NF },
                            { 0x88C1, "864-1", NF },
                            { 0x88D0, "964", NF },
                            { 0x0000, (char *)NULL, NF } } },
        { 0x8086, "Intel", {
                            { 0x0482, "82375EB pci-eisa bridge", NF },
                            { 0x0483, "82424ZX cache dram controller", NF },
                            { 0x0484, "82378IB pci-isa bridge", NF },
                            { 0x04A3, "82434LX pci cache mem controller", NF },
                            { 0x0000, (char *)NULL, NF } } },
        { 0x9004, "Adaptec", {
                            { 0x7178, "2940", NF },
                            { 0x0000, (char *)NULL, NF } } },
        { 0x0000, (char *)NULL, {
                            { 0x0000, (char *)NULL, NF } } }
};

#define PCI_EN 0x80000000

main(int argc, unsigned char *argv[])
{
    unsigned long tmplong1, tmplong2, config_cmd;
    unsigned char tmp1, tmp2;
    struct pci_config_reg pcr;

    if (argc != 1) {
	printf("Usage: %s\n");
	exit(1);
    }
#ifndef MSDOS
    if (getuid()) {
	printf("This program must be run as root\n");
	exit(1);
    }
#endif

    enable_os_io();

    outb(0xCF8, 0x00);
    outb(0xCFA, 0x00);
    tmp1 = inb(0xCF8);
    tmp2 = inb(0xCFA);
    if ((tmp1 == 0x00) && (tmp2 == 0x00))
        printf("PCI says configuration type 2\n");
    else {
        tmplong1 = inl(0xCF8);
        outl(0xCF8, PCI_EN);
        tmplong2 = inl(0xCF8);
        outl(0xCF8, tmplong1);
        if (tmplong2 == PCI_EN)
            printf("PCI says configuration type 1\n");
	else {
            printf("No PCI !\n");
	    disable_os_io();
	    exit(1);
	}
    }

    /* Try pci config 1 probe first */

    printf("\nPCI probing configuration type 1\n");

    pcr._ioaddr = 0xFFFF;

    for (pcr._pcibus = 0x0; pcr._pcibus < 0x10; pcr._pcibus += 0x1) {
        for (pcr._cardnum = 0x0; pcr._cardnum < 0x20; pcr._cardnum += 0x1) {
	    config_cmd = PCI_EN | (pcr._pcibus<<16) | (pcr._cardnum<<11);

            outl(0xCF8, config_cmd);         /* ioreg 0 */
            pcr._device_vendor = inl(0xCFC);

            if (pcr._vendor == 0xFFFF)   /* nothing there */
                continue;

	    printf("\npci bus 0x%x cardnum 0x%02x, vendor 0x%04x device 0x%04x\n",
	        pcr._pcibus, pcr._cardnum, pcr._vendor, pcr._device);

            outl(0xCF8, config_cmd | 0x04); pcr._status_command  = inl(0xCFC);
            outl(0xCF8, config_cmd | 0x08); pcr._class_revision  = inl(0xCFC);
            outl(0xCF8, config_cmd | 0x0C); pcr._bist_header_latency_cache
								= inl(0xCFC);
            outl(0xCF8, config_cmd | 0x10); pcr._base0  = inl(0xCFC);
            outl(0xCF8, config_cmd | 0x14); pcr._base1  = inl(0xCFC);
            outl(0xCF8, config_cmd | 0x18); pcr._base2  = inl(0xCFC);
            outl(0xCF8, config_cmd | 0x1C); pcr._base3  = inl(0xCFC);
            outl(0xCF8, config_cmd | 0x20); pcr._base4  = inl(0xCFC);
            outl(0xCF8, config_cmd | 0x24); pcr._base5  = inl(0xCFC);
            outl(0xCF8, config_cmd | 0x30); pcr._baserom = inl(0xCFC);
            outl(0xCF8, config_cmd | 0x3C); pcr._max_min_ipin_iline
								= inl(0xCFC);

            identify_card(&pcr);
        }
    }


    /* Now try pci config 2 probe (deprecated) */

    outb(0xCF8, 0xF1);
    outb(0xCFA, 0x00); /* bus 0 for now */

    printf("\nPCI probing configuration type 2\n");

    pcr._pcibus = 0xFFFFFFFF;
    pcr._cardnum = 0xFFFFFFFF;

    for (pcr._ioaddr = 0xC000; pcr._ioaddr < 0xD000; pcr._ioaddr += 0x0100) {
        pcr._device_vendor = inl(pcr._ioaddr);

        if (pcr._vendor == 0xFFFF)   /* nothing there */
            continue;
	/* opti chipsets that use config type 1 look like this on config 2 */
        if ((pcr._vendor == 0xFF00) && (pcr._device == 0xFFFF))
            continue;

	printf("\npci slot at 0x%04x, vendor 0x%04x device 0x%04x\n",
	    pcr._ioaddr, pcr._vendor, pcr._device);

        pcr._status_command = inl(pcr._ioaddr + 0x04);
        pcr._class_revision = inl(pcr._ioaddr + 0x08);
        pcr._bist_header_latency_cache = inl(pcr._ioaddr + 0x0C);
        pcr._base0 = inl(pcr._ioaddr + 0x10);
        pcr._base1 = inl(pcr._ioaddr + 0x14);
        pcr._base2 = inl(pcr._ioaddr + 0x18);
        pcr._base3 = inl(pcr._ioaddr + 0x1C);
        pcr._base4 = inl(pcr._ioaddr + 0x20);
        pcr._base5 = inl(pcr._ioaddr + 0x24);
        pcr._baserom = inl(pcr._ioaddr + 0x30);
        pcr._max_min_ipin_iline = inl(pcr._ioaddr + 0x3C);

        identify_card(&pcr);
    }

    outb(0xCF8, 0x00);

    disable_os_io();
}


void
identify_card(struct pci_config_reg *pcr)
{

	int i = 0, j, foundit = 0;

	while (pvd[i].vendorname != (char *)NULL) {
	    if (pvd[i].vendor_id == pcr->_vendor) {
		j = 0;
		printf(" %s ", pvd[i].vendorname);
		while (pvd[i].device[j].devicename != (char *)NULL) {
		    if (pvd[i].device[j].device_id == pcr->_device) {
	                printf("%s", pvd[i].device[j].devicename);
			foundit = 1;
			break;
		    }
		    j++;
		}
	    }
	    if (foundit)
		break;
	    i++;
	}

	if (!foundit)
		printf(" Device unknown\n");
	else {
	    printf("\n");
	    if (pvd[i].device[j].print_func != (void (*)())NULL) {
                pvd[i].device[j].print_func(pcr);
		return;
	    }
	}

        if (pcr->_status_command)
            printf("  STATUS    0x%04x  COMMAND 0x%04x\n",
                pcr->_status, pcr->_command);
        if (pcr->_class_revision)
            printf("  CLASS     0x%02x 0x%02x 0x%02x  REVISION 0x%02x\n",
                pcr->_base_class, pcr->_sub_class, pcr->_prog_if,
		pcr->_rev_id);
        if (pcr->_bist_header_latency_cache)
            printf("  BIST      0x%02x  HEADER 0x%02x  LATENCY 0x%02x  CACHE 0x%02x\n",
                pcr->_bist, pcr->_header_type, pcr->_latency_timer,
		pcr->_cache_line_size);
        if (pcr->_base0)
            printf("  BASE0     0x%08x  addr 0x%08x  %s\n",
                pcr->_base0, pcr->_base0 & (pcr->_base0 & 0x1 ?
		0xFFFFFFFC : 0xFFFFFFF0), pcr->_base0 & 0x1 ? "I/O" : "MEM");
        if (pcr->_base1)
            printf("  BASE1     0x%08x  addr 0x%08x  %s\n",
                pcr->_base1, pcr->_base1 & (pcr->_base1 & 0x1 ?
		0xFFFFFFFC : 0xFFFFFFF0), pcr->_base1 & 0x1 ? "I/O" : "MEM");
        if (pcr->_base2)
            printf("  BASE2     0x%08x  addr 0x%08x  %s\n",
                pcr->_base2, pcr->_base2 & (pcr->_base2 & 0x1 ?
		0xFFFFFFFC : 0xFFFFFFF0), pcr->_base2 & 0x1 ? "I/O" : "MEM");
        if (pcr->_base3)
            printf("  BASE3     0x%08x  addr 0x%08x  %s\n",
                pcr->_base3, pcr->_base3 & (pcr->_base3 & 0x1 ?
		0xFFFFFFFC : 0xFFFFFFF0), pcr->_base3 & 0x1 ? "I/O" : "MEM");
        if (pcr->_base4)
            printf("  BASE4     0x%08x  addr 0x%08x  %s\n",
                pcr->_base4, pcr->_base4 & (pcr->_base4 & 0x1 ?
		0xFFFFFFFC : 0xFFFFFFF0), pcr->_base4 & 0x1 ? "I/O" : "MEM");
        if (pcr->_base5)
            printf("  BASE5     0x%08x  addr 0x%08x  %s\n",
                pcr->_base5, pcr->_base5 & (pcr->_base5 & 0x1 ?
		0xFFFFFFFC : 0xFFFFFFF0), pcr->_base5 & 0x1 ? "I/O" : "MEM");
        if (pcr->_baserom)
            printf("  BASEROM   0x%08x  addr 0x%08x  %sdecode-enabled\n",
                pcr->_baserom, pcr->_baserom & 0xFFFF8000,
                pcr->_baserom & 0x1 ? "" : "not-");
        if (pcr->_max_min_ipin_iline)
            printf("  MAX_LAT   0x%02x  MIN_GNT 0x%02x  INT_PIN 0x%02x  INT_LINE 0x%02x\n",
                pcr->_max_lat, pcr->_min_gnt, pcr->_int_pin, pcr->_int_line);
}


void
print_i128(struct pci_config_reg *pcr)
{
    if (pcr->_status_command)
        printf("  STATUS    0x%04x  COMMAND 0x%04x\n",
            pcr->_status, pcr->_command);
    if (pcr->_class_revision)
        printf("  CLASS     0x%02x 0x%02x 0x%02x  REVISION 0x%02x\n",
            pcr->_base_class, pcr->_sub_class, pcr->_prog_if, pcr->_rev_id);
    if (pcr->_bist_header_latency_cache)
        printf("  BIST      0x%02x  HEADER 0x%02x  LATENCY 0x%02x  CACHE 0x%02x\n",
            pcr->_bist, pcr->_header_type, pcr->_latency_timer,
            pcr->_cache_line_size);
    printf("  MW0_AD    0x%08x  addr 0x%08x  %spre-fetchable\n",
        pcr->_base0, pcr->_base0 & 0xFFC00000,
        pcr->_base0 & 0x8 ? "" : "not-");
    printf("  MW1_AD    0x%08x  addr 0x%08x  %spre-fetchable\n",
        pcr->_base1, pcr->_base1 & 0xFFC00000,
        pcr->_base1 & 0x8 ? "" : "not-");
    printf("  XYW_AD(A) 0x%08x  addr 0x%08x\n",
        pcr->_base2, pcr->_base2 & 0xFFC00000);
    printf("  XYW_AD(B) 0x%08x  addr 0x%08x\n",
        pcr->_base3, pcr->_base3 & 0xFFC00000);
    printf("  RBASE_G   0x%08x  addr 0x%08x\n",
        pcr->_base4, pcr->_base4 & 0xFFFF0000);
    printf("  IO        0x%08x  addr 0x%08x\n",
        pcr->_base5, pcr->_base5 & 0xFFFFFF00);
    printf("  RBASE_E   0x%08x  addr 0x%08x  %sdecode-enabled\n",
        pcr->_baserom, pcr->_baserom & 0xFFFF8000,
        pcr->_baserom & 0x1 ? "" : "not-");
    if (pcr->_max_min_ipin_iline)
        printf("  MAX_LAT   0x%02x  MIN_GNT 0x%02x  INT_PIN 0x%02x  INT_LINE 0x%02x\n",
            pcr->_max_lat, pcr->_min_gnt, pcr->_int_pin, pcr->_int_line);
}


static int io_fd;

void
enable_os_io()
{
#if defined(SVR4) || defined(SCO)
    sysi86(SI86V86, V86SC_IOPL, PS_IOPL);
#endif
#ifdef linux
    iopl(3);
#endif
#if defined(__FreeBSD__) || defined(__386BSD__) || defined(__bsdi__)
    if ((io_fd = open("/dev/console", O_RDWR, 0)) < 0) {
        perror("/dev/console");
        exit(1);
    }
#if defined(__FreeBSD__) || defined(__386BSD__)
    if (ioctl(io_fd, KDENABIO, 0) < 0) {
        perror("ioctl(KDENABIO)");
        exit(1);
    }
#endif
#if defined(__bsdi__)
    if (ioctl(io_fd, PCCONENABIOPL, 0) < 0) {
        perror("ioctl(PCCONENABIOPL)");
        exit(1);
    }
#endif
#endif
#if defined(__NetBSD__)
    if ((io_fd = open("/dev/io", O_RDWR, 0)) < 0) {
        perror("/dev/iopl");
        exit(1);
    }
#endif
#if defined(MACH386)
    if ((io_fd = open("/dev/iopl", O_RDWR, 0)) < 0) {
        perror("/dev/iopl");
        exit(1);
    }
#endif
}


void
disable_os_io()
{
#if defined(SVR4) || defined(SCO)
    sysi86(SI86V86, V86SC_IOPL, 0);
#endif
#ifdef linux
    iopl(0);
#endif
#if defined(__FreeBSD__) || defined(__386BSD__)
    if (ioctl(io_fd, KDDISABIO, 0) < 0) {
        perror("ioctl(KDDISABIO)");
	close(io_fd);
        exit(1);
    }
    close(io_fd);
#endif
#if defined(__bsdi__)
    if (ioctl(io_fd, PCCONDISABIOPL, 0) < 0) {
        perror("ioctl(PCCONDISABIOPL)");
	close(io_fd);
        exit(1);
    }
    close(io_fd);
#endif
#if defined(MACH386) || defined(__NetBSD__)
    close(io_fd);
#endif
}
