/*
 *  Performance counters non port part
 */

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


/* non portable, for intel will be CPUID.0AH.EDX
 */

enum {
	PeNreg		= 4,	/* Number of Pe/Pct regs */
};

int
pmcnregs(void)
{
	/* could run CPUID to see if there are registers,
	 * PmcMaxCtrs
 	*/
	return PeNreg;
}

//PeHo|PeGo
#define PeAll	(PeOS|PeUsr)
#define SetEvMsk(v, e) ((v)|(((e)&PeEvMskL)|(((e)<<(PeEvMsksh-8))&PeEvMskH)))
#define SetUMsk(v, u) ((v)|(((u)<<8ull)&PeUnMsk))

#define GetEvMsk(e) (((e)&PeEvMskL)|(((e)&PeEvMskH)>>(PeEvMsksh-8)))
#define GetUMsk(u) (((u)&PeUnMsk)>>8ull)

static int
pmcuserenab(int enable)
{
	u64int cr4;

	cr4 = cr4get();
	if (enable){
		cr4 |= Pce;
	} else
		cr4 &=  ~Pce;
	cr4put(cr4);
	return cr4&Pce;
}

PmcCtlCtrId pmcids[] = {
	{"locked instr", "0x024 0x1"},
	{"locked cycles nonspec", "0x024 0x4"},	// cycles
	{"SMI intr", "0x02b 0x0"},
	{"DC access", "0x040 0x0"},
	{"DC miss", "0x041 0x0"},
	{"DC refills", "0x042 0x1f"},
	{"DC evicted", "0x042 0x3f"},
	{"L1 DTLB miss", "0x045 0x7"},		//DTLB L2 hit
	{"L2 DTLB miss", "0x046 0x7"},
	{"L1 DTLB hit", "0x04d 0x3"},
	{"global TLB flush", "0x054 0x0"},
	{"L2 hit", "0x07d 0x3f"},
	{"L2 miss", "0x07e 0xf"},
	{"IC miss", "0x081 0x0"},
	{"IC refill from L2", "0x082 0x0"},
	{"IC refill from system", "0x083 0x0"},
	{"L1 ITLB miss", "0x084 0x0"},			//L2 ITLB hit
	{"L2 ITLB miss", "0x085 0x3"},
	{"DRAM access", "0x0e0 0x3f"},
	{"L3 miss core 0", "0x4e1 0x13"},		//core 0 only
	{"L3 miss core 1", "0x4e1 0x23"},
	{"L3 miss core 2", "0x4e1 0x43"},
	{"L3 miss core 3", "0x4e1 0x83"},
	{"L3 miss socket", "0x4e1 0xf3"},		//all cores in the socket
	{"", ""},
};

int
pmctrans(PmcCtl *p)
{
	PmcCtlCtrId *pi;

	for (pi = &pmcids[0]; pi->portdesc[0] != '\0'; pi++){
		if ( strncmp(p->descstr, pi->portdesc, strlen(pi->portdesc)) == 0){
			strncpy(p->descstr, pi->archdesc, strlen(pi->archdesc) + 1);
			return 0;
		}
	}
	return 1;
}

static int
getctl(PmcCtl *p, u32int regno)
{
	u64int r, e, u;

	r = rdmsr(regno + PerfEvtbase);
	p->enab = (r&PeCtEna) != 0;
	p->user = (r&PeUsr) != 0;
	p->os = (r&PeOS) != 0;
	e = GetEvMsk(r);
	u = GetUMsk(r);
	//TODO inverse translation
	snprint(p->descstr, KNAMELEN, "%#ullx %#ullx", e, u);
	p->nodesc = 0;
	return 0;
}

int
pmcanyenab(void)
{
	int i;
	PmcCtl p;

	for (i = 0; i < pmcnregs(); i++) {
		if (getctl(&p, i) < 0)
			return -1;
		if (p.enab)
			return 1;
	}

	return 0;
}

extern int pmcdebug;

static int
setctl(PmcCtl *p, int regno)
{
	u64int v, e, u;
	char *toks[2];
	char str[KNAMELEN];

	if (regno >= pmcnregs())
		error("invalid reg");

	v = rdmsr(regno + PerfEvtbase);
	v &= PeEvMskH|PeEvMskL|PeCtEna|PeOS|PeUsr|PeUnMsk;
	if (p->enab != PmcCtlNullval)
		if (p->enab)
			v |= PeCtEna;
		else
			v &= ~PeCtEna;

	if (p->user != PmcCtlNullval)
		if (p->user)
			v |= PeUsr;
		else
			v &= ~PeUsr;

	if (p->os != PmcCtlNullval)
		if (p->os)
			v |= PeOS;
		else
			v &= ~PeOS;

	if (pmctrans(p) < 0)
		return -1;

	if (p->nodesc == 0) {
		memmove(str, p->descstr, KNAMELEN);
		if (tokenize(str, toks, 2) != 2)
			return -1;
		e = atoi(toks[0]);
		u = atoi(toks[1]);
		v &= ~(PeEvMskL|PeEvMskH|PeUnMsk);
		v |= SetEvMsk(v, e);
		v |= SetUMsk(v, u);
	}
	if (p->reset != PmcCtlNullval && p->reset) {
		v = 0;
		wrmsr(regno+ PerfCtrbase, 0);
		p->reset = PmcCtlNullval; /* only reset once */
	}
	wrmsr(regno+ PerfEvtbase, v);
	pmcuserenab(pmcanyenab());
	if (pmcdebug) {
		v = rdmsr(regno+ PerfEvtbase);
		print("conf pmc[%#ux]: %#llux\n", regno, v);
	}
	return 0;
}

int
pmcctlstr(char *str, int nstr, PmcCtl *p)
{
	int ns;

	ns = 0;
	if (p->enab && p->enab != PmcCtlNullval)
		ns += snprint(str + ns, nstr - ns, "enable\n");
	else
		ns += snprint(str + ns, nstr - ns, "disable\n");

	if (p->user && p->user != PmcCtlNullval)
		ns += snprint(str + ns, nstr - ns, "user\n");
	if (p->os && p->user != PmcCtlNullval)
		ns += snprint(str + ns, nstr - ns, "os\n");

	//TODO, inverse pmctrans?
	if(!p->nodesc)
		ns += snprint(str + ns, nstr - ns, "%s\n", p->descstr);
	else
		ns += snprint(str + ns, nstr - ns, "no desc\n");
	return ns;
}

int
pmcdescstr(char *str, int nstr)
{
	PmcCtlCtrId *pi;
	int ns;

	ns = 0;

	for (pi = &pmcids[0]; pi->portdesc[0] != '\0'; pi++)
		ns += snprint(str + ns, nstr - ns, "%s\n",pi->portdesc);
	return ns;
}

static u64int
getctr(u32int regno)
{
	return rdmsr(regno + PerfCtrbase);
}

static int
setctr(u64int v, u32int regno)
{
	wrmsr(regno + PerfCtrbase, v);
	return 0;
}

static int
notstale(void *x)
{
	PmcCtr *p;
	p = (PmcCtr *)x;
	return !p->stale;
}

static PmcWait*
newpmcw(void)
{
	PmcWait *w;

	w = malloc(sizeof (PmcWait));
	w->ref = 1;
	return w;
}

static void
pmcwclose(PmcWait *w)
{
	if(decref(w))
		return;

	free(w);
}

/*
 *	As it is now, it sends an IPI if the processor is otherwise
 *	ocuppied for it to update the counter.  Probably not needed
 *	for TC/XC as it will be updated every time we cross the kernel
 *	boundary, but we are doing it now just in case it is idle or
 *	not being updated NB: this function releases the ilock
 */

static void
waitnotstale(Mach *mp, PmcCtr *p)
{
	PmcWait *w;

	p->stale = 1;
	w = newpmcw();
	w->next = p->wq;
	p->wq = w;
	incref(w);
	iunlock(&mp->pmclock);
	apicipi(mp->apicno);
	if(waserror()){
		pmcwclose(w);
		nexterror();
	}
	sleep(&w->r, notstale, p);
	poperror();
	pmcwclose(w);
}

/*
 *	The reason this is not racy is subtle.
 *
 *	If the processor suddenly changes state to busy once I have
 *	decided not to IPI it, I don't wait for it.
 *
 *	In the other case, I have decided to IPI it and hence, wait.
 *	The problem then is that it switches to idle (not
 *	interruptible) and I wait forever but this switch crosses
 *	kernel boundaries and gets the pmclock.  One of us gets there
 *	first and either I never sleep (p->stale iscleared) or I sleep
 *	and get waken after.  pmclock + rendez locks make sure this is
 *	the case.
 */
static int
shouldipi(Mach *mp)
{
	if(!mp->online)
		return 0;

	if(mp->proc == nil && mp->nixtype == NIXAC)
		return 0;

	return 1;
}

u64int
pmcgetctr(u32int coreno, u32int regno)
{
	PmcCtr *p;
	Mach *mp;
	u64int v;

	if(coreno == m->machno){
		v = getctr(regno);
		if (pmcdebug) {
			print("int getctr[%#ux, %#ux] = %#llux\n", regno, coreno, v);
		}
		return v;
	}

	mp = sys->machptr[coreno];
	p = &mp->pmc[regno];
	ilock(&mp->pmclock);
	p->ctrset |= PmcGet;
	if(shouldipi(mp)){
		waitnotstale(mp, p);
		ilock(&mp->pmclock);
	}
	v = p->ctr;
	iunlock(&mp->pmclock);
	if (pmcdebug) {
		print("ext getctr[%#ux, %#ux] = %#llux\n", regno, coreno, v);
	}
	return v;
}

int
pmcsetctr(u32int coreno, u64int v, u32int regno)
{
	PmcCtr *p;
	Mach *mp;

	if(coreno == m->machno){
		if (pmcdebug) {
			print("int getctr[%#ux, %#ux] = %#llux\n", regno, coreno, v);
		}
		return setctr(v, regno);
	}

	mp = sys->machptr[coreno];
	p = &mp->pmc[regno];
	if (pmcdebug) {
		print("ext setctr[%#ux, %#ux] = %#llux\n", regno, coreno, v);
	}
	ilock(&mp->pmclock);
	p->ctr = v;
	p->ctrset |= PmcSet;
	if(shouldipi(mp))
		waitnotstale(mp, p);
	else
		iunlock(&mp->pmclock);
	return 0;
}

static void
ctl2ctl(PmcCtl *dctl, PmcCtl *sctl)
{
	if(sctl->enab != PmcCtlNullval)
		dctl->enab = sctl->enab;
	if(sctl->user != PmcCtlNullval)
		dctl->user = sctl->user;
	if(sctl->os != PmcCtlNullval)
		dctl->os = sctl->os;
	if(sctl->nodesc == 0) {
		memmove(dctl->descstr, sctl->descstr, KNAMELEN);
		dctl->nodesc = 0;
	}
}

int
pmcsetctl(u32int coreno, PmcCtl *pctl, u32int regno)
{
	PmcCtr *p;
	Mach *mp;

	if(coreno == m->machno)
		return setctl(pctl, regno);

	mp = sys->machptr[coreno];
	p = &mp->pmc[regno];
	ilock(&mp->pmclock);
	ctl2ctl(&p->PmcCtl, pctl);
	p->ctlset |= PmcSet;
	if(shouldipi(mp))
		waitnotstale(mp, p);
	else
		iunlock(&mp->pmclock);
	return 0;
}

int
pmcgetctl(u32int coreno, PmcCtl *pctl, u32int regno)
{
	PmcCtr *p;
	Mach *mp;

	if(coreno == m->machno)
		return getctl(pctl, regno);

	mp = sys->machptr[coreno];
	p = &mp->pmc[regno];

	ilock(&mp->pmclock);
	p->ctlset |= PmcGet;
	if(shouldipi(mp)){
		waitnotstale(mp, p);
		ilock(&mp->pmclock);
	}
	memmove(pctl, &p->PmcCtl, sizeof(PmcCtl));
	iunlock(&mp->pmclock);
	return 0;
}

void
pmcupdate(Mach *m)
{
	PmcCtr *p;
	int i, maxct, wk;
	PmcWait *w;

	maxct = pmcnregs();
	for (i = 0; i < maxct; i++) {
		p = &m->pmc[i];
		ilock(&m->pmclock);
		if(p->ctrset & PmcSet)
			setctr(p->ctr, i);
		if(p->ctlset & PmcSet)
			setctl(p, i);
		p->ctr = getctr(i);
		getctl(p, i);
		p->ctrset = PmcIgn;
		p->ctlset = PmcIgn;
		wk = p->stale;
		p->stale = 0;
		if(wk){
			for(w = p->wq; w != nil; w = w->next){
				p->wq = w->next;
				wakeup(&w->r);
				pmcwclose(w);
			}
		}
		iunlock(&m->pmclock);
	}
}

