/* Copyright 1999 Red Hat, Inc.
 *
 * This software may be freely redistributed under the terms of the GNU
 * public license.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include <ctype.h>
#include <fcntl.h>
#include <setjmp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <sys/stat.h>

#include <pci/pci.h>

#include "pci.h"

struct pciDevice * pciDeviceList = NULL;
static int numPciDevices = 0;
static struct pci_access *pacc=NULL;
static jmp_buf pcibuf;

static int devCmp(const void * a, const void * b) {
    const struct pciDevice * one = a;
    const struct pciDevice * two = b;
    int x=0,y=0;
    
    x = (one->vendorId - two->vendorId);
    y = (one->deviceId - two->deviceId);
    if (x)
      return x;
    else
      return y;
}

static void pciFreeDevice(struct pciDevice *dev) {
    freeDevice((struct device *)dev);
}

static void pciWriteDevice(FILE *file, struct pciDevice *dev) {
	writeDevice(file, (struct device *)dev);
	fprintf(file,"vendorId: %04x\ndeviceId: %04x\n",dev->vendorId,dev->deviceId);
}

static int pciCompareDevice(struct pciDevice *dev1, struct pciDevice *dev2)
{
	int x=compareDevice((struct device *)dev1,(struct device *)dev2);
	if (x) return x;
	return devCmp( (void *)dev1, (void *)dev2 );
}
			    
	

struct pciDevice * pciNewDevice(struct pciDevice *dev) {
    struct pciDevice *ret;
    
    ret = malloc(sizeof(struct pciDevice));
    memset(ret,'\0',sizeof(struct pciDevice));
    ret=(struct pciDevice *)newDevice((struct device *)dev,(struct device *)ret);
    ret->bus = BUS_PCI;
    if (dev && dev->bus == BUS_PCI) {
	ret->vendorId = dev->vendorId;
	ret->deviceId = dev->deviceId;
    }
    ret->newDevice = pciNewDevice;
    ret->freeDevice = pciFreeDevice;
    ret->writeDevice = pciWriteDevice;
	ret->compareDevice = pciCompareDevice;
    return ret;
}

static int vendCmp(const void * a, const void * b) {
    const struct pciDevice * one = a;
    const struct pciDevice * two = b;
    
    return (one->vendorId - two->vendorId);
}

static unsigned int kudzuToPci(enum deviceClass class) {
    switch (class) {
     case CLASS_UNSPEC:
	return 0;
     case CLASS_OTHER:
	return 0;
     case CLASS_NETWORK:
	return PCI_BASE_CLASS_NETWORK;
     case CLASS_VIDEO:
	return PCI_BASE_CLASS_DISPLAY;
     case CLASS_AUDIO:
	return PCI_CLASS_MULTIMEDIA_AUDIO;
     case CLASS_SCSI:
	return PCI_CLASS_STORAGE_SCSI;
     case CLASS_FLOPPY:
	return PCI_CLASS_STORAGE_FLOPPY;
     case CLASS_RAID:
	return PCI_CLASS_STORAGE_RAID;
     case CLASS_MOUSE: /* !?!? */
	return PCI_CLASS_INPUT_MOUSE;
     default:
	return 0;
    }
}

static enum deviceClass pciToKudzu(unsigned int class) {
    
    if (!class) return CLASS_UNSPEC;
    switch (class >> 8) {
     case PCI_BASE_CLASS_NETWORK:
	return CLASS_NETWORK;
     case PCI_BASE_CLASS_DISPLAY:
	return CLASS_VIDEO;
     default:
	break;
    }
    switch (class) {
     case PCI_CLASS_STORAGE_SCSI:
	return CLASS_SCSI;
     case PCI_CLASS_STORAGE_FLOPPY:
	return CLASS_FLOPPY;
     case PCI_CLASS_STORAGE_RAID:
	return CLASS_RAID;
     case PCI_CLASS_MULTIMEDIA_AUDIO:
	return CLASS_AUDIO;
     case PCI_CLASS_INPUT_MOUSE:
	return CLASS_MOUSE;
     default:
	return CLASS_OTHER;
    }
}

char *getVendor(unsigned int vendor) {
    struct pciDevice *searchDev, key;
    char *tmpstr;
    
    key.vendorId = vendor;
    
    searchDev = bsearch(&key,pciDeviceList,numPciDevices,
			sizeof(struct pciDevice), vendCmp);
    if (searchDev) {
	int x;
	
	x=strchr(searchDev->desc,'|')-searchDev->desc;
	tmpstr=calloc(x,sizeof(char));
	tmpstr=strncpy(tmpstr,searchDev->desc,x);
	return tmpstr;
    } else {
	return NULL;
    }
}

int pciProbeReadDrivers() {
    int fd;
    struct stat sb;
    char * buf;
    int numDrivers;
    char * start;
    struct pciDevice * nextDevice;
    char module[5000];
    char descrip[5000];

    fd = open("/usr/share/kudzu/pcitable", O_RDONLY);
    if (fd < 0) 
	fd = open("/etc/pcitable", O_RDONLY);
    if (fd < 0)
	fd = open("/modules/pcitable", O_RDONLY);
    if (fd < 0) 
	fd = open("./pcitable", O_RDONLY);
    if (fd < 0)
	return -1;

    fstat(fd, &sb);
    buf = alloca(sb.st_size + 1);
    read(fd, buf, sb.st_size);
    buf[sb.st_size] = '\0';
    close(fd);

    /* upper bound */
    numDrivers = 1;
    start = buf;
    while ((start = strchr(start, '\n'))) {
	numDrivers++;
	start++;
    }

    pciDeviceList = realloc(pciDeviceList, sizeof(*pciDeviceList) *
				(numPciDevices + numDrivers));
    nextDevice = pciDeviceList + numPciDevices;

    start = buf;
    while (start && *start) {
	while (isspace(*start)) start++;
	if (*start != '#' && *start != '\n') {
	    if (sscanf(start, "%x %x \"%[^\"]\" \"%[^\"]", &nextDevice->vendorId,
		       &nextDevice->deviceId, module, descrip ) == 4) {
		numPciDevices++;
		nextDevice->driver = strdup(module);
		nextDevice->desc = strdup(descrip);
		nextDevice->next = NULL;
		nextDevice->device = NULL;
		nextDevice->class = 0;
		nextDevice->bus = BUS_PCI;
		nextDevice++;
	    }
	}

	start = strchr(start, '\n');
	if (start) start++;
    }

    qsort(pciDeviceList, numPciDevices, sizeof(*pciDeviceList), devCmp);

    return 0;
}

struct pciDevice * pciGetDeviceInfo(unsigned int vend, unsigned int dev) {
    struct pciDevice *searchDev, key;
    
    key.vendorId = vend;
    key.deviceId = dev;
    
    searchDev = bsearch(&key,pciDeviceList,numPciDevices,
			sizeof(struct pciDevice), devCmp);
    if (!searchDev) {
	char *namebuf;

	searchDev = pciNewDevice(NULL);
	searchDev->vendorId = vend;
	searchDev->deviceId = dev;
	searchDev->driver = strdup("unknown");
	searchDev->desc = calloc(128, sizeof(char));
	namebuf = getVendor(vend);
	if (!namebuf) {
	    snprintf(searchDev->desc,128,
		     "Unknown vendor|unknown device %04x:%04x",
		     searchDev->vendorId, searchDev->deviceId);
	} else {
	    snprintf(searchDev->desc,128,
		     "%s|unknown device %04x:%04x",
		     namebuf, searchDev->vendorId, searchDev->deviceId);
	}
    } else {
        searchDev = pciNewDevice(searchDev);
    }
    return searchDev;
}

static void pcinull(char * foo, ...)
{
}

static void pcibail(char * foo, ...)
{
    longjmp(pcibuf,1);
}

struct device * pciProbe(enum deviceClass probeClass, int probeFlags, struct device *devlist) {
    struct pci_dev *p;
    unsigned int type = kudzuToPci(probeClass),devtype;
	int order=0;
    
    if ((probeClass==CLASS_UNSPEC) ||
	(probeClass==CLASS_OTHER) ||
	(probeClass==CLASS_NETWORK) ||
	(probeClass==CLASS_SCSI) ||
	(probeClass==CLASS_VIDEO) ||
	(probeClass==CLASS_AUDIO) ||
	(probeClass==CLASS_MODEM) ||
	(probeClass==CLASS_RAID)) {
	pacc = pci_alloc();
	if (!pacc) return devlist;
	pacc->debug=pacc->warning=pcinull;
	pacc->error=pcibail;
	if (!setjmp(pcibuf)) {
	    pci_init(pacc);
	    pci_scan_bus(pacc);
	    
	    for (p = pacc->devices; p; p=p->next) {
		byte config[256];
		int x=64;
		struct pciDevice *dev,*a_dev;
		
		memset(config,'\0',256);
		pci_read_block(p, 0, config, x);
		if (x<128 &&  (config[PCI_HEADER_TYPE] & 0x7f) == PCI_HEADER_TYPE_CARDBUS) {
		    pci_read_block(p, 0, config+64, 64);
		    x=128;
		}
		dev = pciGetDeviceInfo(p->vendor_id,p->device_id);
		devtype = config[PCI_CLASS_DEVICE+1] << 8 | config[PCI_CLASS_DEVICE];
		if ( (probeFlags & PROBE_ALL) || (strcmp(dev->driver,"unknown") && strcmp(dev->driver,"ignore"))) {
		    if (!type || (type<0xff && (type==devtype>>8))
			|| (type==devtype)) {
			a_dev = pciNewDevice(dev);
			a_dev->class = pciToKudzu(devtype);
			a_dev->index = order;
			order++;
			if (devlist) {
			    a_dev->next = devlist;
			}
			devlist = (struct device *)a_dev;
		    } 
		}
		pciFreeDevice(dev);
	    }
	    pci_cleanup(pacc);
	}
    }
    return devlist;
}

