/*
 *  Performance counters
 */

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

enum{
	Qdir		= 0,
	Qgctl,
	Qcore,

	Qctr,
	Qdata,
	Qctl,

	PmcCtlRdStr = 4*1024,
};

#define PMCTYPE(x)	(((unsigned)x)&0xffful)
#define PMCID(x)	(((unsigned)x)>>12)
#define PMCQID(i, t)	((((unsigned)i)<<12)|(t))

Dirtab *pmctab;
static int npmctab;
Dirtab *toptab;
static int ntoptab;
int pmcdebug;

static void
topdirinit(int ncores)
{
	int i;
	Dirtab *d;

	ntoptab = 2 + ncores;
	toptab = malloc(ntoptab * sizeof(Dirtab));
	if (toptab == nil)
		return;
	d = toptab;
	strncpy(d->name, ".", KNAMELEN);
	mkqid(&d->qid, Qdir, 0, QTDIR);
	d->perm = DMDIR|0555;
	d++;
	strncpy(d->name, "ctrdesc", KNAMELEN);
	mkqid(&d->qid, Qgctl, 0, 0);
	d->perm = 0444;
	for (i = 2; i < ncores + 2; i++) {
		d = &toptab[i];
		snprint(d->name, KNAMELEN, "core%4.4ud", i - 2);
		mkqid(&d->qid, PMCQID(i - 2, Qcore), 0, QTDIR);
		d->perm = DMDIR|0555;
	}

}

static void
ctrdirinit(void)
{
	int nr, i;
	Dirtab *d;

	nr = pmcnregs();
		
	npmctab = 1 + 2*nr;
	pmctab = malloc(npmctab * sizeof(Dirtab));
	if (pmctab == nil){
		free(toptab);
		toptab = nil;
		return;
	}

	d = pmctab;
	strncpy(d->name, ".", KNAMELEN);
	mkqid(&d->qid, Qctr, 0, QTDIR);
	d->perm = DMDIR|0555;
	for (i = 1; i < nr + 1; i++) {
		d = &pmctab[i];
		snprint(d->name, KNAMELEN, "ctr%2.2ud", i - 1);
		mkqid(&d->qid, PMCQID(i - 1, Qdata), 0, 0);
		d->perm = 0600;

		d = &pmctab[nr + i];
		snprint(d->name, KNAMELEN, "ctr%2.2udctl", i - 1);
		mkqid(&d->qid, PMCQID(i - 1, Qctl), 0, 0);
		d->perm = 0600;
	}	
	
}

static void
pmcnull(PmcCtl *p)
{
	memset(p, 0xff, sizeof(PmcCtl));
	p->enab = PmcCtlNullval;
	p->user = PmcCtlNullval;
	p->os = PmcCtlNullval;
	p->reset = PmcCtlNullval;
	p->nodesc = 1;
}

static void
pmcinit(void)
{
	int i, j, ncores, nr;
	Mach *mp;

	_pmcupdate = pmcupdate;
	ncores = 0;
	nr = pmcnregs();
	for(i = 0; i < MACHMAX; i++)
		if((mp = sys->machptr[i]) != nil && mp->online){
			ncores++;
			for(j = 0; j < nr; j++)
				pmcnull(&mp->pmc[j]);
		}
	topdirinit(ncores);
	ctrdirinit();
}

static Chan *
pmcattach(char *spec)
{
	if (pmctab == nil)
		error(Enomem);
	return devattach(L'ε', spec);
}
int
pmcgen(Chan *c, char *name, Dirtab*, int, int s, Dir *dp)
{
	int t, i, n;
	Dirtab *l, *d;

	if(s == DEVDOTDOT){
		devdir(c, (Qid){Qdir, 0, QTDIR}, "#ε", 0, eve, 0555, dp);
		c->aux = nil;
		return 1;
	}
	/* first, for directories, generate children */
	switch((int)PMCTYPE(c->qid.path)){
	case Qdir:
		return devgen(c, name, toptab, ntoptab, s, dp);
	case Qctr:
		return devgen(c, name, pmctab, npmctab, s, dp);
	case Qcore:
		c->aux = (void *)PMCID(c->qid.path);		/* core no */
		return devgen(c, name, pmctab, npmctab, s, dp);
	default:
		if(s != 0)
			return -1;

		t = PMCTYPE(c->qid.path);
		if(t < Qctr){
			i = t;
			l = toptab;
			n = ntoptab;
		}else{
			i = PMCID(t);
			if (t == Qctl)
				i += (npmctab - 1)/2;
			l = pmctab;
			n = npmctab;
		}
		if(i >=n)
			return -1;

		d = &l[i];
		
		devdir(c, d->qid, d->name, d->length, eve, d->perm, dp);
		return 1;
	}
}

static Walkqid*
pmcwalk(Chan *c, Chan *nc, char **name, int nname)
{
	return devwalk(c, nc, name, nname, nil, 0, pmcgen);
}

static long
pmcstat(Chan *c, uchar *dp, long n)
{
	return devstat(c, dp, n, nil, 0, pmcgen);
}

static Chan*
pmcopen(Chan *c, int omode)
{
	if (!iseve())
		error(Eperm);
	return devopen(c, omode, nil, 0, pmcgen);
}

static void
pmcclose(Chan *)
{
}



static long
pmcread(Chan *c, void *a, long n, vlong offset)
{
	ulong type, id;
	PmcCtl p;
	char *s;
	u64int v;
	u64int coreno;

	type = PMCTYPE(c->qid.path);
	id = PMCID(c->qid.path);

	switch(type){
	case Qcore:
	case Qdir:
	case Qctr:
		return devdirread(c, a, n, nil, 0, pmcgen);
	}

	s = malloc(PmcCtlRdStr);
	if(waserror()){
		free(s);
		nexterror();
	}
	coreno = (u64int)c->aux;
	p.coreno = coreno;
	switch(type){
	case Qdata:
		v = pmcgetctr(coreno, id);
		snprint(s, PmcCtlRdStr, "%#ullx", v);
		break;
	case Qctl:
		if (pmcgetctl(coreno, &p, id) < 0)
			error("bad ctr");
		if (pmcctlstr(s, PmcCtlRdStr, &p) < 0)
			error("bad pmc");
		break;
	case Qgctl:
		if (pmcdescstr(s, PmcCtlRdStr) < 0)
			error("bad pmc");
		break;
	default:
		error(Eperm);
	}
	n = readstr(offset, a, n, s);
	free(s);
	poperror();
	return n;
}

enum{
	Enable,
	Disable,
	User,
	Os,
	NoUser,
	NoOs,
	Reset,
	Debug,
};

static Cmdtab pmcctlmsg[] =
{
	Enable,		"enable",	0,
	Disable,	"disable",	0,
	User,		"user",		0,
	Os,		"os",		0,
	NoUser,		"nouser",		0,
	NoOs,		"noos",		0,
	Reset,		"reset",	0,
	Debug, 		"debug",	0,
};

typedef void (*APfunc)(void);

typedef struct AcPmcArg AcPmcArg;
struct AcPmcArg {
	int regno;
	int coreno;
	PmcCtl;
};

typedef struct AcCtrArg AcCtrArg;
struct AcCtrArg {
	int regno;
	int coreno;
	u64int v;
};

void
acpmcsetctl(void)
{
	AcPmcArg p;
	Mach *mp;

	mp = up->ac;
	memmove(&p, mp->icc->data, sizeof(AcPmcArg));
	
	mp->icc->rc = pmcsetctl(p.coreno, &p, p.regno);
	return;
}

void
acpmcsetctr(void)
{
	AcCtrArg ctr;
	Mach *mp;

	mp = up->ac;
	memmove(&ctr, mp->icc->data, sizeof(AcCtrArg));
	
	mp->icc->rc = pmcsetctr(ctr.coreno, ctr.v, ctr.regno);
	return;
}


static long
pmcwrite(Chan *c, void *a, long n, vlong)
{
	Cmdbuf *cb;
	Cmdtab *ct;
	ulong type;
	char str[64];	/* 0x0000000000000000\0 */
	AcPmcArg p;
	AcCtrArg ctr;
	u64int coreno;
	Mach *mp;

	if (c->qid.type == QTDIR)
		error(Eperm);
	if (c->qid.path == Qgctl)
		error(Eperm);
	if (n >= sizeof(str))
		error(Ebadctl);

	pmcnull(&p);
	coreno = (u64int)c->aux;
	p.coreno = coreno;
	type = PMCTYPE(c->qid.path);
	p.regno = PMCID(c->qid.path);
	memmove(str, a, n);
	str[n] = '\0';
	mp = up->ac;

	ctr.coreno = coreno;
	ctr.regno = p.regno;
	if (type == Qdata) {
		/* I am a handler for a proc in the core, run an RPC*/
		if (mp != nil && mp->machno == coreno) {
			if (runac(mp, acpmcsetctr, 0, &ctr, sizeof(AcCtrArg)) < 0)
				n = -1;
		} else {
		if (pmcsetctr(coreno, strtoull(str, 0, 0), p.regno) < 0)
			n = -1;
		}
		return n;
	}


	/* TODO: should iterate through multiple lines */
	if (strncmp(str, "set ", 4) == 0){
		memmove(p.descstr, (char *)str + 4, n - 4);
		p.descstr[n - 4] = '\0';
		p.nodesc = 0;
	} else {
		cb = parsecmd(a, n);
		if(waserror()){
			free(cb);
			nexterror();
		}
		ct = lookupcmd(cb, pmcctlmsg, nelem(pmcctlmsg));
		switch(ct->index){
		case Enable:
			p.enab = 1;
			break;
		case Disable:
			p.enab = 0;
			break;
		case User:
			p.user = 1;
			break;
		case Os:
			p.os = 1;
			break;
		case NoUser:
			p.user = 0;
			break;
		case NoOs:
			p.os = 0;
			break;
		case Reset:
			p.reset = 1;
			break;
		case Debug:
			pmcdebug = ~pmcdebug;
			break;
		default:
			cmderror(cb, "invalid ctl");
		break;
		}
		free(cb);
		poperror();
	}
	/* I am a handler for a proc in the core, run an RPC*/
	if (mp != nil && mp->machno == coreno) {
		if (runac(mp, acpmcsetctl, 0, &p, sizeof(AcPmcArg)) < 0)
			n = -1;
	} else {
		if (pmcsetctl(coreno, &p, p.regno) < 0)
			n = -1;
	}
	return n;
}


Dev pmcdevtab = {
	L'ε',
	"pmc",

	pmcinit,
	devinit,
	devshutdown,
	pmcattach,
	pmcwalk,
	pmcstat,
	pmcopen,
	devcreate,
	pmcclose,
	pmcread,
	devbread,
	pmcwrite,
	devbwrite,
	devremove,
	devwstat,
};
