/* Copyright 1999, 2000 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 <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <sys/utsname.h>

#include "isapnp.h"

/* I am in hell. ISAPnP is the fifth ring of hell. */

static struct isapnpDevice *isapnpDeviceList = NULL;
static int numIsapnpDevices = 0;

static char *soundlist[] = {
	  "sb",
	  "cs4232",
	  "ad1816",
	  "ad1848",
	  "sscape",
	  "gus",
	  "opl3sa2",
	  "mad16",
	  "awe_wave",
	  NULL
};

static char *netlist[] = {
	  "3c509",
	  "3c515",
	  "ne",
	  "sb1000",
	  "smc-ultra",
	  NULL
};
	  
static char *scsilist[] = {
	  "aha1542",
	  "g_NCR5380",
	  NULL
};

static void isapnpFreeDevice(struct isapnpDevice *dev) {
	if (dev->deviceId) free(dev->deviceId);
	if (dev->pdeviceId) free(dev->pdeviceId);
	if (dev->compat) free(dev->compat);
	if (dev->io) free(dev->io);
	if (dev->irq) free(dev->irq);
	if (dev->dma) free(dev->dma);
	if (dev->mem) free(dev->mem);
	freeDevice((struct device *)dev);
}

static void isapnpWriteDevice(FILE *file, struct isapnpDevice *dev) {
	int x;
	
	writeDevice(file, (struct device *)dev);
	if (dev->deviceId)
	  fprintf(file,"deviceId: %s\n",dev->deviceId);
	if (dev->pdeviceId)
	  fprintf(file,"pdeviceId: %s\n", dev->pdeviceId);
	if (dev->compat)
	  fprintf(file,"compat: %s\n",dev->compat);
	fprintf(file,"native: %d\n",dev->native);
	fprintf(file,"active: %d\n",dev->active);
	fprintf(file,"cardnum: %d\n",dev->cardnum);
	fprintf(file,"logdev: %d\n",dev->logdev);
	if (dev->io && dev->io[0]!=-1) {
		fprintf(file,"io: 0x%x", dev->io[0]);
		for (x=1;dev->io[x]!=-1;x++)
		  fprintf(file,",0x%x",dev->io[x]);
		fprintf(file,"\n");
	}
	if (dev->irq && dev->irq[0]!=-1) {
		fprintf(file,"irq: %d", dev->irq[0]);
		for (x=1;dev->irq[x]!=-1;x++)
		  fprintf(file,",%d",dev->irq[x]);
		fprintf(file,"\n");
	}
	if (dev->dma && dev->dma[0]!=-1) {
		fprintf(file,"dma: %d", dev->dma[0]);
		for (x=1;dev->dma[x]!=-1;x++)
		  fprintf(file,",%d",dev->dma[x]);
		fprintf(file,"\n");
	}
	if (dev->mem && dev->mem[0]!=-1) {
		fprintf(file,"mem: 0x%x", dev->mem[0]);
		for (x=1;dev->mem[x]!=-1;x++)
		  fprintf(file,",0x%x",dev->mem[x]);
		fprintf(file,"\n");
	}
}

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

static int isapnpCompareDevice(struct isapnpDevice *dev1, struct isapnpDevice *dev2)
{
	int x=compareDevice((struct device *)dev1,(struct device *)dev2);
	if (x) return x;
	return devCmp( (void *)dev1, (void *)dev2 );
	/* needs finished */
}

static char *demangle(int vendor, int device) {
	static char ret[8];
	
        sprintf(ret, "%c%c%c%x%x%x%x",
		'A' + ((vendor >> 2) & 0x3f) - 1,
		'A' + (((vendor & 3) << 3) | ((vendor >> 13) & 7)) - 1,
		'A' + ((vendor >> 8) & 0x1f) - 1,
		(device >> 4) & 0x0f,
		device & 0x0f,
		(device >> 12) & 0x0f,
		(device >> 8) & 0x0f);
	return ret;
}

int isapnpReadDrivers(char *filename) {
	int fd;
	char path[256];
	struct utsname utsbuf;
	int id1, id2, id3, id4;
	struct isapnpDevice key, *nextDevice;
	char *ident, *pident;
	char *buf, *start, *next, *ptr, *module;
	
	uname(&utsbuf);
	snprintf(path,255,"/lib/modules/%s/modules.isapnpmap",utsbuf.release);
	fd = open(path,O_RDONLY);
	if (fd < 0) {
		fd = open("/etc/modules.isapnpmap", O_RDONLY);
		if (fd < 0) {
			fd = open("/modules/modules.isapnpmap", O_RDONLY);
			if (fd < 0) {
				fd = open("./modules.isapnpmap", O_RDONLY);
				if (fd <0) 
				  return -1;
			}
		}
	}
	
	start = buf = bufFromFd(fd);
	
	nextDevice = isapnpDeviceList + numIsapnpDevices;
	
	while (*buf) {
		next = buf;
		while (*next && *next != '\n') next++;
		if (*next) {
			*next = '\0';
			next++;
		}
		ptr = buf;
		if (*ptr == '#') {
			buf = next;
			continue;
		}
		
		while (*ptr && !isspace(*ptr)) ptr++;
		if (*ptr) {
			*ptr = '\0';
			ptr++;
		}
		while (isspace(*ptr)) ptr++;
		module = strdup(buf);
		buf = ptr;

		while (*ptr && !isspace(*ptr)) ptr++;
		if (*ptr) {
			*ptr = '\0';
			ptr++;
		}
		while (isspace(*ptr)) ptr++;
		id1 = strtoul(buf, NULL, 16);
		buf = ptr;
		
		while (*ptr && !isspace(*ptr)) ptr++;
		if (*ptr) {
			*ptr = '\0';
			ptr++;
		}
		while (isspace(*ptr)) ptr++;
		id2 = strtoul(buf, NULL, 16);
		buf = ptr;
		
		while (*ptr && !isspace(*ptr)) ptr++;
		if (*ptr) {
			*ptr = '\0';
			ptr++;
		}
		while (isspace(*ptr)) ptr++;
		buf = ptr;
		
		while (*ptr && !isspace(*ptr)) ptr++;
		if (*ptr) {
			*ptr = '\0';
			ptr++;
		}
		while (isspace(*ptr)) ptr++;
		id3 = strtoul(buf,NULL,16);
		buf = ptr;
		
		while (*ptr && !isspace(*ptr)) ptr++;
		if (*ptr) {
			*ptr = '\0';
			ptr++;
		}
		while (isspace(*ptr)) ptr++;
		id4 = strtoul(buf,NULL,16);
		
		pident = strdup(demangle(id1, id2));
		ident = strdup(demangle(id3, id4));
		key.deviceId = ident;
		key.pdeviceId = pident;
		
		nextDevice = bsearch(&key, isapnpDeviceList, numIsapnpDevices,
				     sizeof(struct isapnpDevice), devCmp);
		
		if (!nextDevice) {
			isapnpDeviceList = realloc(isapnpDeviceList,
						   (numIsapnpDevices + 1) *
						   sizeof(struct isapnpDevice));
			nextDevice = isapnpDeviceList + numIsapnpDevices;
			memset(nextDevice,'\0',sizeof(struct isapnpDevice));
			nextDevice->driver = module;
			nextDevice->deviceId = ident;
			nextDevice->pdeviceId = pident;
			nextDevice++;
			numIsapnpDevices++;
			qsort(isapnpDeviceList, numIsapnpDevices,
			      sizeof(*isapnpDeviceList), devCmp);
		} else {
			free(ident);
			free(pident);
			free(module);
		}
		buf = next;
	}
	free(start);
	return 0;
}

void isapnpFreeDrivers(void) {
        int x;

        if (isapnpDeviceList) {
		for (x=0;x<numIsapnpDevices;x++) {
			if (isapnpDeviceList[x].deviceId) free (isapnpDeviceList[x].deviceId);
			if (isapnpDeviceList[x].driver) free (isapnpDeviceList[x].driver);
	        }
		free(isapnpDeviceList);
		isapnpDeviceList=NULL;
		numIsapnpDevices=0;
	}
}

struct isapnpDevice * isapnpNewDevice(struct isapnpDevice *dev) {
	struct isapnpDevice *ret;
    
	ret = malloc(sizeof(struct isapnpDevice));
	memset(ret,'\0',sizeof(struct isapnpDevice));
	ret=(struct isapnpDevice *)newDevice((struct device *)dev,(struct device *)ret);
	ret->bus = BUS_ISAPNP;
	if (dev && dev->bus == BUS_ISAPNP) {
		int x=0;
		
		ret->native = dev->native;
		ret->active = dev->active;
		ret->cardnum = dev->cardnum;
		ret->logdev = dev->logdev;
		if (dev->deviceId) ret->deviceId=strdup(dev->deviceId);
		if (dev->pdeviceId) ret->pdeviceId=strdup(dev->pdeviceId);
		if (dev->compat) ret->compat=strdup(dev->compat);
		if (dev->io) {
			for (x=0;dev->io[x]!=-1;x++) {
				ret->io=realloc(ret->io,(x+1)*sizeof(int));
				ret->io[x]=dev->io[x];
			}
			ret->io[x]=-1;
		}
		if (dev->irq) {
			for (x=0;dev->irq[x]!=-1;x++) {
				ret->irq=realloc(ret->irq,(x+1)*sizeof(int));
				ret->irq[x]=dev->irq[x];
			}
			ret->irq[x]=-1;
		}
		if (dev->dma) {
			for (x=0;dev->dma[x]!=-1;x++) {
				ret->dma=realloc(ret->dma,(x+1)*sizeof(int));
				ret->dma[x]=dev->dma[x];
			}
			ret->dma[x]=-1;
		}
		if (dev->mem) {
			for (x=0;dev->mem[x]!=-1;x++) {
				ret->mem=realloc(ret->mem,(x+1)*sizeof(int));
				ret->mem[x]=dev->mem[x];
			}
			ret->mem[x]=-1;
		}
	}
	ret->newDevice = isapnpNewDevice;
	ret->freeDevice = isapnpFreeDevice;
	ret->writeDevice = isapnpWriteDevice;
	ret->compareDevice = isapnpCompareDevice;
	return ret;
}

int *isapnpReadResources(char *line, int base) {
	int *ret=NULL, x=0;
	char *ptr;
	
	do {
		ptr=strstr(line,",");
		if (ptr) *ptr='\0';
		x++;
		ret = realloc(ret,(x+2)*sizeof(int));
		ret[x-1] = strtoul(line,NULL,base);
		ret[x] = -1;
		if (ptr) line = ptr+1;
	} while (ptr);
	return ret;
}

static void setDriverAndClass(struct isapnpDevice *dev) {
	struct isapnpDevice key, *searchdev;
	int x;
	
	key.deviceId = dev->deviceId;
	key.pdeviceId = dev->pdeviceId;
	searchdev = bsearch(&key, isapnpDeviceList, numIsapnpDevices,
			    sizeof(struct isapnpDevice), devCmp);
	if (!searchdev) {
		key.pdeviceId = demangle(0xffff,0xffff);
		searchdev = bsearch(&key, isapnpDeviceList, numIsapnpDevices,
			    sizeof(struct isapnpDevice), devCmp);
	}
	if (!searchdev && dev->compat) {
		key.deviceId = dev->compat;
		searchdev = bsearch(&key, isapnpDeviceList, numIsapnpDevices,
				    sizeof(struct isapnpDevice), devCmp);
	}
	if (searchdev) {
		dev->driver = strdup(searchdev->driver);
		dev->native = 1;
	} else {
		dev->native = 0;
		if (!strstr(dev->desc,"IDE") && !strstr(dev->desc,"ATAPI") &&
		    !strstr(dev->desc,"CD-ROM") && !strstr(dev->desc,"CDROM")) {
			if (!strncmp(dev->pdeviceId, "CSC", 3) && strcmp(dev->pdeviceId, "CSC6040"))
			  dev->driver = strdup("cs4232");
			if (!strncmp(dev->pdeviceId, "ENS", 3)) {
				if (!strstr(dev->desc,"VIVO"))
				  dev->driver = strdup("ad1848");
				else
				  dev->driver = strdup("sscape");
			}
			if (!strncmp(dev->pdeviceId, "GRV", 3))
			  dev->driver = strdup("gus");
			if (!strncmp(dev->pdeviceId, "AZT", 3))
			  dev->driver = strdup("ad1848");
			if (!strncmp(dev->pdeviceId, "OPT", 3))
			  dev->driver = strdup("mad16");
			if (!strncmp(dev->pdeviceId, "CMI", 3))
			  dev->driver = strdup("ad1848");
		}
	}
	for (x=0; soundlist[x]; x++) {
		if (!strcmp(soundlist[x],dev->driver))
		  dev->class = CLASS_AUDIO;
	}
	for (x=0; netlist[x]; x++) {
		if (!strcmp(netlist[x],dev->driver)) {
			dev->class = CLASS_NETWORK;
			dev->device = strdup("eth");
		}
		
	}
	for (x=0; scsilist[x]; x++) {
		if (!strcmp(scsilist[x],dev->driver))
		  dev->class = CLASS_SCSI;
	}
}

int isapnpActivate(struct isapnpDevice **dev) {
	FILE *procfile;
	struct isapnpDevice *newdev, *curdev = *dev;
	
	procfile = fopen("/proc/isapnp","w"); 
	if (!procfile)
	  return 0;
	fprintf(procfile,"card %d %s\n", curdev->cardnum, curdev->pdeviceId);
	fprintf(procfile,"dev 0 %s\n", curdev->deviceId);
	fprintf(procfile,"auto\n");
	fclose(procfile);
	
	newdev = (struct isapnpDevice *)isapnpProbe(CLASS_UNSPEC, PROBE_ALL, NULL);
	
	if (!newdev)
	  return 0;
	for (; newdev; newdev = (struct isapnpDevice *)newdev->next) {
		if (!devCmp(newdev,curdev)) {
			*dev = newdev;
			return 1;
		}
	}
	return 0;
}

struct device * isapnpProbe(enum deviceClass probeClass, int probeFlags, struct device *devlist) {
	char *pnpbuf=NULL;
	char *start, *current, *ptr;
	struct isapnpDevice *dev = NULL;
	char pdev[10], pdesc[64];
	char *pnpdesc;
	int cardnum;
	char buf[4096];
	int fd, bytes = 0;
	int init_list = 0;
	
	if (
	    (probeClass == CLASS_UNSPEC) ||
	    (probeClass == CLASS_OTHER) ||
	    (probeClass == CLASS_NETWORK) ||
	    (probeClass == CLASS_MODEM) ||
	    (probeClass == CLASS_AUDIO)
	    ) {
		
		if (!isapnpDeviceList) {
			isapnpReadDrivers(NULL);
			init_list = 1;
		}
		fd = open("./isapnp",O_RDONLY);
		if (fd==-1) {
			fd = open("/proc/isapnp",O_RDONLY);
			if (fd==-1) {
				return devlist;
			}
		}
	
		memset(buf,'\0',4096);
		while (read(fd,buf,4096) >0) {
			pnpbuf = realloc(pnpbuf,bytes+4096);
			memcpy(pnpbuf+bytes,buf,4096);
			bytes+= 4096;
			memset(buf,'\0',4096);
		}
		close(fd);
		
		start = pnpbuf;
	
		while (start && *start) {
			current = start;
			while (*current && *current != '\n') current++;
			if (*current) {
				*current = '\0';
				current++;
			}
			if (!strncmp("Card ",start,5)) {
				cardnum = atoi(start+5) - 1;
				start+=8;
				if ( (ptr=strstr(start,":")) ) {
					*ptr='\0';
					strncpy(pdev,start,10);
					start=ptr+1;
					if ( (ptr=strstr(start,"'")) ) {
						*ptr='\0';
						strncpy(pdesc,start,64);
						start=ptr+1;
					}
				}
			} else if (!strncmp("  Logical device",start,16)) {
				if (dev) {
					setDriverAndClass(dev);
					if (probeClass == CLASS_UNSPEC ||
					    probeClass == dev->class) {
						if (devlist)
						  dev->next = devlist;
						devlist = (struct device *)dev;
					} else {
						isapnpFreeDevice(dev);
					}
				}
				dev = isapnpNewDevice(NULL);
				dev->cardnum = cardnum;
				dev->pdeviceId = strdup(pdev);
				dev->driver = strdup("unknown");
				dev->logdev = atoi(start+17);
				start += 20;
				if ( (ptr=strstr(start,":")) ) {
					*ptr='\0';
					dev->deviceId = strdup(start);
					start = ptr+1;
					if ( (ptr=strstr(start,"'")) ) {
						*ptr = '\0';
						pnpdesc = strdup(start);
					}
				}
				dev->desc = malloc(strlen(pdesc)+strlen(pnpdesc)+3);
				snprintf(dev->desc,strlen(pdesc)+strlen(pnpdesc)+2,"%s:%s",pdesc,pnpdesc);
				if (pnpdesc)
				  free(pnpdesc);
			} else if (!strncmp("    Device is active", start,20)) {
				dev->active = 1;
			} else if (!strncmp("    Compatible device", start,21)) {
				dev->compat = strdup(start+22);
			} else if (!strncmp("    Active port", start, 15)) {
				dev->io=isapnpReadResources(start+16,16);
			} else if (!strncmp("    Active IRQ", start, 14)) {
				dev->irq=isapnpReadResources(start+15,10);
			} else if (!strncmp("    Active DMA", start, 14)) {
				dev->dma=isapnpReadResources(start+15,10);
			} else if (!strncmp("    Active memory", start, 17)) {
				dev->mem=isapnpReadResources(start+18,16);
			}
			start = current;
		}
		if (dev) {
			setDriverAndClass(dev);
			if (probeClass == CLASS_UNSPEC ||
			    probeClass == dev->class) {
				if (devlist)
				  dev->next = devlist;
				devlist = (struct device *)dev;
			} else {
				isapnpFreeDevice(dev);
			}
		}
		free(pnpbuf);
	}
	if (isapnpDeviceList && init_list)
	  isapnpFreeDrivers();
	return devlist;
}
