#include "u.h"
#include "../port/lib.h" 
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"
#include "../port/error.h"

static	char Etoomany[] = 	"too many ports opened";
static	char Enoclient[] =	"no such pci device...";

static void *pcidevbase = 0;
enum {
	Qtopdir = 0,
	Qbase,

	Qpcidir,
	Qinb,	/* reading */
	Qinw,	
	Qinl,	
	Qoutb,	/* writing */
	Qoutw,
	Qoutl,	
	
};

typedef struct Mypci Mypci;
typedef struct Devpci Devpci;

struct Mypci
{
	QLock;
	ulong base;
};

enum {
	Maxpcidev = 128,		/* looks like a good number */
};

struct Devpci 
{
	Mypci *pci[Maxpcidev];
	uint npci;
};

static Devpci devpci;

static Dirtab pcidevdir[] = {
	"inb", {Qinb}, 0, 0440,
	"inw", {Qinw}, 0, 0440,
	"inl", {Qinl}, 0, 0440,
	"outb", {Qoutb}, 0, 0220,
	"outw", {Qoutw}, 0, 0220,
	"outl", {Qoutl}, 0, 0220,
};

static Dirtab pcitopdir[] = {
	"base", {Qbase}, 0, 0220,
};

#define	QSHIFT	4	/* location in qid of client # */

#define	QID(q)		(((q).path&0x0000000F)>>0)
#define	CLIENTPATH(q)	((q&0x07FFFFFF0)>>QSHIFT)
#define	CLIENT(q)	CLIENTPATH((q).path)


Mypci*
pcislotpath(ulong path)
{
	Mypci *cl;
	int slot;

	slot = CLIENTPATH(path);
	if(slot == 0)
		return nil;
	cl = devpci.pci[slot-1];
	if(cl==0 || cl->base==0)
		return nil;
	return cl;
}


Mypci*
pcislot(Chan *c)
{
	Mypci *client;

	client = pcislotpath(c->qid.path);
	if(client == nil)
		error(Enoclient);
	return client;
}


static int
pcidevgen(Chan *c, Dirtab *tab, int x, int s, Dir *dp)
{
	int t;
	Qid q;
	ulong path;
	Mypci *cl;
	char buf[NAMELEN];
	

	USED(tab, x);
	q.vers = 0;

	if(s == DEVDOTDOT){
		switch(QID(c->qid)){
		case Qpcidir:
			cl = pcislot(c);
			sprint(buf, "0x%lux", cl->base);
			devdir(c, (Qid){CHDIR|Qtopdir, 0}, buf, 0, eve, 0500, dp);
			break;
		default:
			panic("pcidevwalk %lux", c->qid.path);
		}
		return 1;
	}


	t = QID(c->qid);
	if(t == Qtopdir){
		if(s == 0){
			q = (Qid){Qbase, 0};
			devdir(c, q, "base", 0, eve, 0600, dp);
		}
		else if(s <= devpci.npci){
			cl = devpci.pci[s-1];
			if(cl == 0)
				return 0;
			sprint(buf, "0x%lux", cl->base);
			q = (Qid){CHDIR|(s<<QSHIFT)|Qpcidir, 0};
			devdir(c, q, buf, 0, eve, 0555, dp);
			return 1;
		}
		else
			return -1;
		return 1;
	}


	path = c->qid.path&~(CHDIR|((1<<QSHIFT)-1));	/* slot component */
	q.vers = c->qid.vers;
	switch(s){
	case 0:
		q = (Qid){path|Qinb, c->qid.vers};
		devdir(c, q, "inb", 0, eve, 0200, dp);
		break;
	case 1:
		q = (Qid){path|Qinw, c->qid.vers};
		devdir(c, q, "inw", 0, eve, 0200, dp);
		break;
	case 2:
		q = (Qid){path|Qinl, c->qid.vers};
		devdir(c, q, "inl", 0, eve, 0200, dp);
		break;
	case 3:
		q = (Qid){path|Qoutb, c->qid.vers};
		devdir(c, q, "outb", 0, eve, 0400, dp);
		break;
	case 4:
		q = (Qid){path|Qoutw, c->qid.vers};
		devdir(c, q, "outw", 0, eve, 0400, dp);
		break;
	case 5:
		q = (Qid){path|Qoutl, c->qid.vers};
		devdir(c, q, "outl", 0, eve, 0400, dp);
		break;
	default:
		return -1;
	}
	return 1;

}
	
static void
pcidevreset(void)
{
}

void
pcidevinit(void)
{

	devinit();

}

static Chan*
pcidevattach(char* spec)
{
	return devattach('Z', spec);
}

int
pcidevwalk(Chan* c, char* name)
{
	return devwalk(c, name, 0,0 , pcidevgen);
}

static void
pcidevstat(Chan* c, char* dp)
{
	devstat(c, dp, pcitopdir, nelem(pcitopdir), devgen);
}

static Chan*
pcidevopen(Chan* c, int omode)
{
	return devopen(c, omode, pcitopdir, nelem(pcitopdir), devgen);
}




static long
pcidevread(Chan* c, void* a, long n, vlong)
{
	char str[16];
	int size = 0;
	ulong o;
	Mypci *cl;




	if(c->qid.path & CHDIR)
		return devdirread(c, a, n, 0, 0, pcidevgen);
	cl = pcislot(c);

	qlock(cl);
	if(waserror()){
		qunlock(cl);
		nexterror();
	}

	/* assume some things about 'a' that we probably shouldn't */
	switch(QID(c->qid)){
	case Qinb:
		size = sprint(str, "0x%2.2ux", inb(cl->base) & 0xFF);
		break;
	case Qinw:
		size = sprint(str, "0x%4.4ux", ins(cl->base) & 0xFFFF);
		break;
	case Qinl:
		size = sprint(str, "0x%8.8lux", inl(cl->base) & 0xFFFFFFFF);
		break;
	}

	qunlock(cl);
	poperror();

	o = c->offset;
	if(o >= size)
		return 0;
	if(o+n > size)
		n = size-c->offset;
	memmove(a, str+o, n);

	return n;
}


static long
pcidevwrite(Chan* c, void* a, long n, vlong off)
{
	Mypci *cl;
	ulong offset = off;


	USED(offset, n);
	if(c->qid.path & CHDIR)
		error(Eisdir);

	if(QID(c->qid) == Qbase) {
		if(devpci.npci < Maxpcidev) {
			devpci.pci[devpci.npci] = (Mypci *)malloc(sizeof(Mypci));
			devpci.pci[devpci.npci]->base = strtol(a, nil, 0);
			devpci.npci++;
			return 1;
		} else {
			error(Etoomany);
			return 0;
		}			
	}

	cl = pcislot(c);
	qlock(cl);
	switch(QID(c->qid)){
	case Qoutb:
		outb(PADDR(cl->base), atol(a) & 0xff);
		break;
	case Qoutw:
		outs(PADDR(cl->base), atol(a) & 0xffff);
		break;
	case Qoutl:
		outl(PADDR(cl->base), atol(a));
		break;
	}
	qunlock(cl);
	return 1;

}

static void
pcidevcreate(Chan *, char*, int, ulong)
{
}

static Chan *
pcidevclone(Chan *c1, Chan *c2)
{
	return devclone(c1, c2);
}

static void
pcidevremove(Chan *c)
{
	int slot;
	Mypci *cl;
	

	slot = CLIENTPATH(c->qid.path);

	if(slot == 0)
		return;	

	slot--;	/* align with pci[] */

	cl = devpci.pci[slot];

	free(cl);
	devpci.npci--;

	while (slot < devpci.npci) {
		devpci.pci[slot] = devpci.pci[slot+1];
		slot++;
	}
}

static void
pcidevclose(Chan *c) 
{
	USED(c);
}

Dev pcidevdevtab = {
	'Z',
	"pcidev",
	pcidevreset,
	devinit,
	pcidevattach,
	pcidevclone,
	pcidevwalk,
	pcidevstat,
	pcidevopen,
	pcidevcreate,
	pcidevclose,
	pcidevread,
	devbread,
	pcidevwrite,
	devbwrite,
	pcidevremove,
	devwstat,

};