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

#include "apic.h"
#include "io.h"
#include "adr.h"

#undef DBG
#define	DBG	jehanne_print

enum {						/* Local APIC registers */
	Id		= 0x0020,		/* Identification */
	Ver		= 0x0030,		/* Version */
	Tp		= 0x0080,		/* Task Priority */
	Ap		= 0x0090,		/* Arbitration Priority */
	Pp		= 0x00a0,		/* Processor Priority */
	Eoi		= 0x00b0,		/* EOI */
	Ld		= 0x00d0,		/* Logical Destination */
	Df		= 0x00e0,		/* Destination Format */
	Siv		= 0x00f0,		/* Spurious Interrupt Vector */
	Is		= 0x0100,		/* Interrupt Status (8) */
	Tm		= 0x0180,		/* Trigger Mode (8) */
	Ir		= 0x0200,		/* Interrupt Request (8) */
	Es		= 0x0280,		/* Error Status */
	Iclo		= 0x0300,		/* Interrupt Command */
	Ichi		= 0x0310,		/* Interrupt Command [63:32] */
	Lvt0		= 0x0320,		/* Local Vector Table 0 */
	Lvt5		= 0x0330,		/* Local Vector Table 5 */
	Lvt4		= 0x0340,		/* Local Vector Table 4 */
	Lvt1		= 0x0350,		/* Local Vector Table 1 */
	Lvt2		= 0x0360,		/* Local Vector Table 2 */
	Lvt3		= 0x0370,		/* Local Vector Table 3 */
	Tic		= 0x0380,		/* Timer Initial Count */
	Tcc		= 0x0390,		/* Timer Current Count */
	Tdc		= 0x03e0,		/* Timer Divide Configuration */

	Tlvt		= Lvt0,			/* Timer */
	Lint0		= Lvt1,			/* Local Interrupt 0 */
	Lint1		= Lvt2,			/* Local Interrupt 1 */
	Elvt		= Lvt3,			/* Error */
	Pclvt		= Lvt4,			/* Performance Counter */
	Tslvt		= Lvt5,			/* Thermal Sensor */
};

enum {						/* Siv */
	Swen		= 0x00000100,		/* Software Enable */
	Fdis		= 0x00000200,		/* Focus Disable */
};

enum {						/* Iclo */
	Lassert		= 0x00004000,		/* Assert level */

	DSnone		= 0x00000000,		/* Use Destination Field */
	DSself		= 0x00040000,		/* Self is only destination */
	DSallinc	= 0x00080000,		/* All including self */
	DSallexc	= 0x000c0000,		/* All Excluding self */
};

enum {						/* Tlvt */
	Periodic	= 0x00020000,		/* Periodic Timer Mode */
};

enum {						/* Tdc */
	DivX2		= 0x00000000,		/* Divide by 2 */
	DivX4		= 0x00000001,		/* Divide by 4 */
	DivX8		= 0x00000002,		/* Divide by 8 */
	DivX16		= 0x00000003,		/* Divide by 16 */
	DivX32		= 0x00000008,		/* Divide by 32 */
	DivX64		= 0x00000009,		/* Divide by 64 */
	DivX128		= 0x0000000a,		/* Divide by 128 */
	DivX1		= 0x0000000b,		/* Divide by 1 */
};

static uint32_t* lapicbase;

static	Lapic	xlapic[Napic];

Lapic*
lapiclookup(uint32_t id)
{
	Lapic *a;

	if(id >= nelem(xlapic))
		return nil;
	a = xlapic + id;
	if(a->useable)
		return a;
	return nil;
}

static uint32_t
lapicrget(int r)
{
	return lapicbase[r/4];
}

static void
lapicrput(int r, uint32_t data)
{
	if(lapicbase != nil){
		/* early panics can occur with lapicbase uninitialized
		 * but if we let it fault, we loose the actual panic PC
		 */
		lapicbase[r/4] = data;
	}
}

int
lapiceoi(int vecno)
{
	lapicrput(Eoi, 0);
	return vecno;
}

int
lapicisr(int vecno)
{
	int isr;

	isr = lapicrget(Is + (vecno/32)*16);

	return isr & (1<<(vecno%32));
}

static char*
lapicprint(char *p, char *e, Lapic *a, int i)
{
	char *s;

	s = "proc";
	p = jehanne_seprint(p, e, "%-8s ", s);
	p = jehanne_seprint(p, e, "%8ux ", i);
//	p = jehanne_seprint(p, e, "%.8ux ", a->dest);
//	p = jehanne_seprint(p, e, "%.8ux ", a->mask);
//	p = jehanne_seprint(p, e, "%c", a->flags & PcmpBP? 'b': ' ');
//	p = jehanne_seprint(p, e, "%c ", a->flags & PcmpEN? 'e': ' ');
//	p = jehanne_seprint(p, e, "%8ux %8ux", a->lintr[0], a->lintr[1]);
	p = jehanne_seprint(p, e, "%12d\n", a->machno);
	return p;
}

static long
lapicread(Chan* _1, void *a, long n, int64_t off)
{
	char *s, *e, *p;
	long i, r;

	s = jehanne_malloc(READSTR);
	e = s+READSTR;
	p = s;

	for(i = 0; i < nelem(xlapic); i++)
		if(xlapic[i].useable)
			p = lapicprint(p, e, xlapic + i, i);
	r = -1;
	if(!waserror()){
		r = readstr(off, a, n, s);
		poperror();
	}
	jehanne_free(s);
	return r;
}

void
lapicinit(int lapicno, uintmem pa, int isbp)
{
	Lapic *apic;

	/*
	 * Mark the LAPIC useable if it has a good ID, and the registers can
	 * be mapped.  There is x2LAPIC to be dealt with at some point.
	 */
	DBG("lapicinit: lapicno %d pa %#P isbp %d caller %#p\n", lapicno, pa, isbp, getcallerpc());
	addarchfile("lapic", 0444, lapicread, nil);

	if(lapicno >= Napic){
		panic("lapicinit%d: out of range", lapicno);
		return;
	}
	if((apic = &xlapic[lapicno])->useable){
		jehanne_print("lapicinit%d: already initialised\n", lapicno);
		return;
	}
	if(lapicbase == nil){
		//adrmapck(pa, 1024, Ammio, Mfree, Cnone);
		if((lapicbase = vmap(pa, 1024)) == nil){
			panic("lapicinit%d: can't map lapicbase %#P", lapicno, pa);
			return;
		}
		DBG("lapicinit%d: lapicbase %#P -> %#p\n", lapicno, pa, lapicbase);
	}
	apic->useable = 1;

	/*
	 * Assign a machno to the processor associated with this
	 * LAPIC, it may not be an identity map.
	 * Machno 0 is always the bootstrap processor.
	 */
	if(isbp){
		apic->machno = 0;
		m->apicno = lapicno;
	}
	else
		apic->machno = sys->nmach++;
}

void
lapicsetdom(int lapicno, int dom)
{
	Lapic *apic;

	DBG("lapic%d: setdom: %d\n", lapicno, dom);
	if(lapicno >= Napic){
		panic("lapic%d: lapicsetdom: apic out of range", lapicno);
		return;
	}
	if((apic = &xlapic[lapicno])->useable)
		apic->dom = dom;
	else
		jehanne_print("lapic%d: lapicsetdom: apic not usable\n", lapicno);
}

int
machdom(Mach *mp)
{
	return xlapic[mp->apicno].dom;
}

static void
lapicdump0(Lapic *apic, int i)
{
	if(!apic->useable)
		return;
	DBG("lapic%d: machno %d lint0 %#8.8ux lint1 %#8.8ux\n",
		i, apic->machno, apic->lvt[0], apic->lvt[1]);
	DBG(" tslvt %#8.8ux pclvt %#8.8ux elvt %#8.8ux\n",
		lapicrget(Tslvt), lapicrget(Pclvt), lapicrget(Elvt));
	DBG(" tlvt %#8.8ux lint0 %#8.8ux lint1 %#8.8ux siv %#8.8ux\n",
		lapicrget(Tlvt), lapicrget(Lint0),
		lapicrget(Lint1), lapicrget(Siv));
}

void
lapicdump(void)
{
	int i;

	if(!DBGFLG)
		return;

	DBG("lapicbase %#p\n", lapicbase);
	for(i = 0; i < Napic; i++)
		lapicdump0(xlapic + i, i);
}

int
lapiconline(void)
{
	Lapic *apic;
	uint64_t tsc;
	uint32_t dfr, ver;
	int apicno, nlvt;

	if(lapicbase == nil)
		panic("lapiconline: no lapic base");

	if((apicno = ((lapicrget(Id)>>24) & 0xff)) >= Napic)
		panic("lapic: id too large %d", apicno);
	if(apicno != m->apicno){
		panic("lapic: %d != %d", m->apicno, apicno);
		dfr = lapicrget(Id) & ~(0xff<<24);
		dfr |= m->apicno<<24;
		lapicrput(Id, dfr);
		apicno = m->apicno;
	}
	apic = &xlapic[apicno];
	if(!apic->useable)
		panic("lapiconline: lapic%d: unusable %d", apicno, apic->useable);

	/*
	 * Things that can only be done when on the processor
	 * owning the APIC, apicinit above runs on the bootstrap
	 * processor.
	 */
	ver = lapicrget(Ver);
	nlvt = ((ver>>16) & 0xff) + 1;
	if(nlvt > nelem(apic->lvt)){
		jehanne_print("lapiconline%d: nlvt %d > max (%d)\n",
			apicno, nlvt, nelem(apic->lvt));
		nlvt = nelem(apic->lvt);
	}
	apic->nlvt = nlvt;
	apic->ver = ver & 0xff;

	/*
	 * These don't really matter in Physical mode;
	 * set the defaults anyway.
	 */
//	if(jehanne_memcmp(m->cpuinfo, "AuthenticAMD", 12) == 0)
//		dfr = 0xf0000000;
//	else
		dfr = 0xffffffff;
	lapicrput(Df, dfr);
	lapicrput(Ld, 0x00000000);

	/*
	 * Disable interrupts until ready by setting the Task Priority
	 * register to 0xff.
	 */
	lapicrput(Tp, 0xff);

	/*
	 * Software-enable the APIC in the Spurious Interrupt Vector
	 * register and set the vector number. The vector number must have
	 * bits 3-0 0x0f unless the Extended Spurious Vector Enable bit
	 * is set in the HyperTransport Transaction Control register.
	 */
	lapicrput(Siv, Swen|IdtSPURIOUS);

	/*
	 * Acknowledge any outstanding interrupts.
	 */
	lapicrput(Eoi, 0);

	/*
	 * Use the TSC to determine the lapic timer frequency.
	 * It might be possible to snarf this from a chipset
	 * register instead.
	 */
	lapicrput(Tdc, DivX1);
	lapicrput(Tlvt, Im);
	tsc = rdtsc() + m->cpuhz/10;
	lapicrput(Tic, 0xffffffff);

	while(rdtsc() < tsc)
		;

	apic->hz = (0xffffffff-lapicrget(Tcc))*10;
	apic->max = apic->hz/HZ;
	apic->min = apic->hz/(100*HZ);
	apic->div = ((m->cpuhz/apic->max)+HZ/2)/HZ;

	if(m->machno == 0 || DBGFLG){
		jehanne_print("lapic%d: hz %lld max %lld min %lld div %lld\n", apicno,
			apic->hz, apic->max, apic->min, apic->div);
	}

	/*
	 * Mask interrupts on Performance Counter overflow and
	 * Thermal Sensor if implemented, and on Lintr0 (Legacy INTR),
	 * and Lintr1 (Legacy NMI).
	 * Clear any Error Status (write followed by read) and enable
	 * the Error interrupt.
	 */
	switch(apic->nlvt){
	case 7:
	case 6:
		lapicrput(Tslvt, Im);
		/*FALLTHROUGH*/
	case 5:
		lapicrput(Pclvt, Im);
		/*FALLTHROUGH*/
	default:
		break;
	}
	lapicrput(Lint1, apic->lvt[1]|Im|IdtLINT1);
	lapicrput(Lint0, apic->lvt[0]|Im|IdtLINT0);

	lapicrput(Es, 0);
	lapicrget(Es);
	lapicrput(Elvt, IdtERROR);

	/*
	 * Reload the timer to de-synchronise the processors.
	 * When the caller is ready for the APIC to accept interrupts,
	 * it should call lapicpri to lower the task priority.
	 *
	 * The timer is enabled later by the core-specific startup
	 * i.e. don't start the timer unless the core needs it,
	 * to reduce the likelihood of at least one (spurious) interrupt
	 * from the timer when priority is lowered.
	 */
	microdelay((TK2MS(1)*1000/sys->nmach) * m->machno);
	lapicrput(Tic, apic->max);
	return 1;
}

void
lapictimerenable(void)
{
	/*
	 * Perhaps apictimerenable/apictimerdisable should just
	 * clear/set Im in the existing settings of Tlvt, there may
	 * be a time when the timer is used in a different mode;
	 * if so will need to ensure the mode is set when the timer
	 * is initialised.
	 */
	lapicrput(Tlvt, Periodic|IdtTIMER);
}

void
lapictimerdisable(void)
{
	lapicrput(Tlvt, Im|IdtTIMER);
}

void
lapictimerset(uint64_t next)
{
	Lapic *apic;
	int64_t period;

	apic = &xlapic[(lapicrget(Id)>>24) & 0xff];

	ilock(&m->apictimerlock);

	period = apic->max;
	if(next != 0){
		if(apic->div == 0){
			jehanne_print("lapictimerset: apic not ready, wait for gdb\n");
			waitdebugger();
		}
		period = next - fastticks(nil);	/* fastticks is just rdtsc() */
		period /= apic->div;

		if(period < apic->min)
			period = apic->min;
		else if(period > apic->max - apic->min)
			period = apic->max;
	}
	lapicrput(Tic, period);

	iunlock(&m->apictimerlock);
}

void
lapicsipi(int lapicno, uintmem pa)
{
	int i;
	uint32_t crhi, crlo;

	/*
	 * SIPI - Start-up IPI.
	 * To do: checks on lapic validity.
	 */
	crhi = lapicno<<24;
	lapicrput(Ichi, crhi);
	lapicrput(Iclo, DSnone|TMlevel|Lassert|MTir);
	microdelay(200);
	lapicrput(Iclo, DSnone|TMlevel|MTir);
	delay(10);

	crlo = DSnone|TMedge|MTsipi|((uint32_t)pa/(4*KiB));
	for(i = 0; i < 2; i++){
		lapicrput(Ichi, crhi);
		lapicrput(Iclo, crlo);
		microdelay(200);
	}
}

void
lapicipi(int lapicno)
{
	lapicrput(Ichi, lapicno<<24);
	lapicrput(Iclo, DSnone|TMedge|Lassert|MTf|IdtIPI);
	while(lapicrget(Iclo) & Ds)
		;
}

void
lapicpri(int pri)
{
	lapicrput(Tp, pri);
}
