/* very simple set of routines to get back what is stored in /proc/pci in */
/* a programmer friendly fashion. Also tries to map a pci entry into a    */
/* kernel module name to aid autoloading modules during an install        */
/* Michael Fulbright Sept 1997                                            */

#include <stdio.h>
#include <strings.h>
#include <malloc.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>

#include "pciprobe.h"
#include "pci-ids.h"


/* clean up that string */
static void TrimWhitespace( char *s ) {
    char *f, *l, *p, *q;

    if (!(*s))
        return;
    
    for (f=s; *f && isspace(*f); f++) ;

    if (!*f) {
        *s = '\0';
        return;
    }
    
    for (l=f+strlen(f)-1; isspace(*l) ; l--)
    *l = '\0';
        
    q = s, p = f;
    while (*p)
        *q++ = *p++;
    
    *q = '\0';
}


/* create a new clean pciDevice entry */
struct pciDevice *pciNewDevice( void ) {
    struct pciDevice *p;

    p = (struct pciDevice *) malloc( sizeof(struct pciDevice) );
    p->nhits = 0;
    p->name=NULL;
    p->module=NULL;
    p->class=PCI_UNSET;
    return p;
}

/* remove a pciDevice */
void pciFreeDevice( struct pciDevice *p ) {
    int i;

    if (p->name) {
	for (i=0; i<p->nhits; i++)
	    free(p->name[i]);
	free(p->name);
    }

    if (p->module) {
	for (i=0; i<p->nhits; i++)
	    free(p->module[i]);
	free(p->module);
    }

    free(p);
}

    
/* given a class to probe, returns an array of devices found which match */
/* all entries are malloc'd, so caller must free when done. Use          */
/* pciDeviceFree() to free individual pciDevice's, since they have       */
/* internal elements which are malloc'd                                  */
/* returns number cards found. If nothing found returns 0 and            */
/* setsdevs is set to NULL                                               */
/* if system error occurs (like no /proc/pci!) returns -1                */
int pciProbeDevice( enum pciClass type, struct pciDevice ***devs ) {
    int done;
    int found, maxlen, maxidx;
    int ok, first;
    int ndevs, nmatches;
    int bus, device, function;
    int nonvgadev, madeentry;
    int i, len;
    char *devtype, *devmodel, *vendorid, *deviceid, *mapped, *mapname, *eptr;
    int stage;
    unsigned int devnumid, vennumid;
    FILE *f;
    char buf[100];

    struct pci_module_map *pcidb;

    /* open /proc/pci and parse output */
    f=fopen("/proc/pci", "r");
    if (!f) {
	return -1;
    }

    done = 0;
    stage = 0;
    ndevs = 0;
    *devs = NULL;
    while (!done) {
	if (!fgets(buf,100,f))
	    break;

	madeentry = 0; /* havent made nothin yet */

	/* see what we're looking for */
	if (stage == 0) {
	    /* need to find start of list of PCI devices */
	    if (!strncmp(buf, "PCI devices found:", 18))
		stage++;
	    continue;
	} else if (stage == 1) {
	    /* looking for a line starting with 'Bus' */
	    if (strstr(buf, "Bus") && strstr(buf, "device")) {
		/* found it, now parse out stuff we care about */
		sscanf(buf, "  Bus %d, device %d, function %d:\n",
		       &bus, &device, &function);

		/* read the next line for other goodies */
		if (!fgets(buf,100,f))
		    break;

		/* strip out devtype and model of card */
		devmodel = strchr(buf, ':');
		if (!devmodel)
		    continue;
		*devmodel++ = 0;

		/* blow revision number off end of devmodel */
		if ((eptr=strrchr(devmodel, '(')))
		    *eptr = 0;
		devtype=strdup(buf);
		devmodel=strdup(devmodel);
		TrimWhitespace(devtype);
		TrimWhitespace(devmodel);

		/* see if its the class we're looking for */
		/* nonvgadev tells use card didnt set a class */
		/* so we have to see if it happens to match a */
		/* entry in the class db we have              */
		ok = 1;
		nonvgadev = strstr(devtype, CLASS_NONE) != NULL;

		switch (type) {
		  case PCI_ETHERNET:
		    ok = strstr(devtype, CLASS_ETHERNET) != NULL || nonvgadev;
		    break;
		  case PCI_SCSI:
		    ok = strstr(devtype, CLASS_SCSI) != NULL || nonvgadev;
		    break;
		  case PCI_VIDEO:
		    ok = strstr(devtype, CLASS_VIDEO) != NULL ||
			 strstr(devtype, CLASS_VIDEO_OTHER) != NULL ||
			 nonvgadev;
		    break;
		  default:
		    ok = 0;
		    break;
		}

		if (!ok) {
		    free(devtype);
		    free(devmodel);
		    continue;
		}
		
		/* see if its an unknown model */
		deviceid = vendorid = NULL;
		if (strstr(devmodel, "Unknown")) {
		    if (!fgets(buf,100,f))
			break;
		    if (strstr(buf, "Vendor id")) {
			char *s, *t, *v;
			s = strchr(buf, '=');
			if (s) {
			    s++;
			    t = strchr(s, '.');
			    if (t) {
				*t = 0;
				t = strchr(t+1, '=');
				if (t) {
				    t++;
				    v = strchr(t, '.');
				    if (v) {
					*v = 0;
					vendorid = strdup(s);
					deviceid = strdup(t);
					TrimWhitespace(vendorid);
					TrimWhitespace(deviceid);
					devnumid=strtol(deviceid, &eptr, 16);
					vennumid=strtol(vendorid, &eptr, 16);
				    }
				}
			    }
			}
		    }
		}
		    
		/* now find all matches and stick into entry */
		switch (type) {
		  case PCI_ETHERNET:
		    pcidb = eth_pci_ids;
		    len   = eth_num_ids;
		    break;
		  case PCI_SCSI:
		    pcidb = scsi_pci_ids;
		    len   = scsi_num_ids;
		    break;
		  case PCI_VIDEO:
		    pcidb = video_pci_ids;
		    len   = video_num_ids;
		    break;
		  default:
		    pcidb = NULL;
		    len = 0;
		}

		/* gotta find the maximally matching entry(s) */
		maxlen = -1;
		if (!strstr(devmodel,"Unknown")) {
		    for (i=0; i<len; i++) {
			if (strstr(devmodel, pcidb[i].name)) {
			    int l=strlen(pcidb[i].name);
			    if (l > maxlen) {
				maxlen = l;
			    }
			}
		    }
		}
		
		first = 1;
		nmatches = 0;
		i = 0;
		while ( 1 ) {
		    found = 0;
		    if (strstr(devmodel, "Unknown")) {
			for (; i<len && 
				 (vennumid != pcidb[i].vendor ||
				 devnumid != pcidb[i].device); i++);
			found = (i < len);
			if (found)
			    maxidx = i;
		    } else {
			for (; i<len; i++){
			    if (strstr(devmodel, pcidb[i].name)) {
				int l=strlen(pcidb[i].name);
				if (l >= maxlen) {
				    maxidx = i;
				    found = 1;
				    break;
				}
			    }
			}
		    }
		    
		    if (!found) {
			if (!first || nonvgadev) 
			    break;
			else {
			    mapped  = "UNKNOWN";
			    mapname = "UNKNOWN";
			}
		    } else {
			mapped = (char *) pcidb[maxidx].module;
			mapname = (char *) pcidb[maxidx].name;
		    }
		    
		    first = 0;
		    
		    /* insert into current pciDevice */
		    /* we got enough info, let make a device entry */
		    if (!madeentry) {
		      if ((ndevs % 4) == 0) 
			*devs=(struct pciDevice **)
			  realloc(*devs,(ndevs+4)*sizeof(struct pciDevice *));
		      (*devs)[ndevs]=pciNewDevice();
		    }

		    madeentry = 1;

		    (*devs)[ndevs]->name=realloc((*devs)[ndevs]->name,
				       (nmatches+1)*sizeof(char *));
		    (*devs)[ndevs]->module=realloc((*devs)[ndevs]->module,
					 (nmatches+1)*sizeof(char *));
		    (*devs)[ndevs]->name[nmatches]=strdup(mapname);
		    (*devs)[ndevs]->module[nmatches]=strdup(mapped);
		    i++;
		    nmatches++;
		}		    

		if (vendorid)
		    free(vendorid);
		if (deviceid)
		    free(deviceid);
		free(devtype);
		free(devmodel);
		if (madeentry) {
		  (*devs)[ndevs]->nhits=nmatches;
		  (*devs)[ndevs]->class=type;
		  ndevs++;
		}
	    }
	}
    }
    
    fclose(f);
    return ndevs;
}

