/*
 * sheevaplug traps, exceptions, interrupts, system calls.
 */
#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"
#include "ureg.h"
#include "../port/error.h"

#include "arm.h"

enum {
	Ntimevec = 20,			/* # of time buckets for each intr */
	Nvecs = 256,
};

extern int ldrexvalid;

typedef struct Vctl Vctl;
typedef struct Vctl {
	Vctl*	next;		/* handlers on this vector */
	char	*name;		/* of driver, xallocated */
	void	(*f)(Ureg*, void*);	/* handler to call */
	void*	a;		/* argument to call it with */
} Vctl;

static Lock vctllock;
static Vctl* vctl[32];

uvlong ninterrupt;
uvlong ninterruptticks;
ulong intrtimes[Nvecs][Ntimevec];

typedef struct Handler Handler;
struct Handler {
	void	(*r)(Ureg*, void*);
	void	*a;
	char	name[KNAMELEN];
};

static Handler irqlo[32];
static Handler irqhi[32];
static Handler irqbridge[32];
static Lock irqlock;
static int probing, trapped;

typedef struct Irq Irq;
struct Irq {
	ulong	*irq;
	ulong	*irqmask;
	Handler	*irqvec;
	int	nirqvec;
	char	*name;
};
/* irq and irqmask are filled in by trapinit */
static Irq irqs[] = {
[Irqlo]		{nil, nil, irqlo,	nelem(irqlo),	"lo"},
[Irqhi]		{nil, nil, irqhi,	nelem(irqhi),	"hi"},
[Irqbridge]	{nil, nil, irqbridge,	nelem(irqbridge), "bridge"},
};

/*
 *  keep histogram of interrupt service times
 */
void
intrtime(Mach*, int vno)
{
	ulong diff, x;

	if (m == nil)
		return;
	x = perfticks();
	diff = x - m->perf.intrts;
	m->perf.intrts = x;

	m->perf.inintr += diff;
	if(up == nil && m->perf.inidle > diff)
		m->perf.inidle -= diff;

	if (m->cpuhz == 0)			/* not set yet? */
		return;
	diff /= (m->cpuhz/1000000)*100;		/* quantum = 100µsec */
	if(diff >= Ntimevec)
		diff = Ntimevec-1;
	assert(vno >= 0 && vno < Nvecs);
	intrtimes[vno][diff]++;
}

void
intrfmtcounts(char *s, char *se)
{
	USED(s, se);
}

static void
dumpcounts(void)
{
}

void
intrclear(int sort, int v)
{
	*irqs[sort].irq = ~(1 << v);
}

void
intrmask(int sort, int v)
{
	*irqs[sort].irqmask &= ~(1 << v);
}

void
intrunmask(int sort, int v)
{
	*irqs[sort].irqmask |= 1 << v;
}

static void
maskallints(void)
{
	CpucsReg *cpu = (CpucsReg *)soc.cpu;
	IntrReg *intr;

	/* no fiq or ep in use */
	intr = (IntrReg *)soc.intr;
	intr->lo.irqmask = 0;
	intr->hi.irqmask = 0;
	cpu->irqmask = 0;
	coherence();
}

void
intrset(Handler *h, void (*f)(Ureg*, void*), void *a, char *name)
{
	if(h->r != nil) {
//		iprint("duplicate irq: %s (%#p)\n", h->name, h->r);
		return;
	}
	h->r = f;
	h->a = a;
	strncpy(h->name, name, KNAMELEN-1);
	h->name[KNAMELEN-1] = 0;
}

void
intrunset(Handler *h)
{
	h->r = nil;
	h->a = nil;
	h->name[0] = 0;
}

void
intrdel(Handler *h, void (*f)(Ureg*, void*), void *a, char *name)
{
	if(h->r != f || h->a != a || strcmp(h->name, name) != 0)
		return;
	intrunset(h);
}

void
intrenable(int sort, int v, void (*f)(Ureg*, void*), void *a, char *name)
{
//iprint("enabling intr %d vec %d for %s\n", sort, v, name);
	ilock(&irqlock);
	intrset(&irqs[sort].irqvec[v], f, a, name);
	intrunmask(sort, v);
	iunlock(&irqlock);
}

void
intrdisable(int sort, int v, void (*f)(Ureg*, void*), void* a, char *name)
{
	ilock(&irqlock);
	intrdel(&irqs[sort].irqvec[v], f, a, name);
	intrmask(sort, v);
	iunlock(&irqlock);
}

/*
 *  called by trap to handle interrupts
 */
static int
intrs(Ureg *ur, int sort)
{
	int i, s, clockintr;
	ulong ibits;
	Handler *h;
	Irq irq;

	m->perf.intrts = perfticks();
	clockintr = 0;

	assert(sort >= 0 && sort < nelem(irqs));
	irq = irqs[sort];
	ibits = *irq.irq;
	ibits &= *irq.irqmask;

	for(i = 0; i < irq.nirqvec && ibits; i++)
		if(ibits & (1<<i)){
			h = &irq.irqvec[i];
			if(h->r != nil){
				h->r(ur, h->a);
				splhi();
				intrtime(m, sort*32 + i);
				if (sort == Irqbridge && i == IRQcputimer0)
					clockintr = 1;
				ibits &= ~(1<<i);
			}
		}
	if(ibits != 0) {
		iprint("spurious irq%s interrupt: %8.8lux\n", irq.name, ibits);
		s = splfhi();
		*irq.irq &= ibits;
		*irq.irqmask &= ~ibits;
		splx(s);
	}

	return clockintr;
}

void
intrhi(Ureg *ureg, void*)
{
	intrs(ureg, Irqhi);
}

void
intrbridge(Ureg *ureg, void*)
{
	intrs(ureg, Irqbridge);
	intrclear(Irqlo, IRQ0bridge);
}

void
trapinit(void)
{
	int i;
	CpucsReg *cpu;
	IntrReg *intr;
	Vectorpage *page0 = (Vectorpage*)HVECTORS;

	intr = (IntrReg *)soc.intr;
	cpu = (CpucsReg *)soc.cpu;
	irqs[Irqlo].irq = &intr->lo.irq;
	irqs[Irqlo].irqmask = &intr->lo.irqmask;
	irqs[Irqhi].irq = &intr->hi.irq;
	irqs[Irqhi].irqmask = &intr->hi.irqmask;
	irqs[Irqbridge].irq = &cpu->irq;
	irqs[Irqbridge].irqmask = &cpu->irqmask;
	coherence();

	setr13(PsrMfiq, m->fiqstack + nelem(m->fiqstack));
	setr13(PsrMirq, m->irqstack + nelem(m->irqstack));
	setr13(PsrMabt, m->abtstack + nelem(m->abtstack));
	setr13(PsrMund, m->undstack + nelem(m->undstack));

	memmove(page0->vectors, vectors, sizeof page0->vectors);
	memmove(page0->vtable,  vtable,  sizeof page0->vtable);
	cacheuwbinv();
	l2cacheuwbinv();

	cpu->cpucfg &= ~Cfgvecinithi;

	for(i = 0; i < nelem(irqlo); i++)
		intrunset(&irqlo[i]);
	for(i = 0; i < nelem(irqhi); i++)
		intrunset(&irqhi[i]);
	for(i = 0; i < nelem(irqbridge); i++)
		intrunset(&irqbridge[i]);

	/* disable all interrupts */
	intr->lo.fiqmask = intr->hi.fiqmask = 0;
	intr->lo.irqmask = intr->hi.irqmask = 0;
	intr->lo.epmask =  intr->hi.epmask = 0;
	cpu->irqmask = 0;
	coherence();

	/* clear interrupts */
	intr->lo.irq = intr->hi.irq = ~0;
	cpu->irq = ~0;
	coherence();

	intrenable(Irqlo, IRQ0hisum, intrhi, nil, "hi");
	intrenable(Irqlo, IRQ0bridge, intrbridge, nil, "bridge");

	/* enable watchdog & access-error interrupts */
	cpu->irqmask |= 1 << IRQcputimerwd | 1 << IRQaccesserr;
	coherence();
}

static char *trapnames[PsrMask+1] = {
	[ PsrMusr ] "user mode",
	[ PsrMfiq ] "fiq interrupt",
	[ PsrMirq ] "irq interrupt",
	[ PsrMsvc ] "svc/swi exception",
	[ PsrMabt ] "prefetch abort/data abort",
	[ PsrMabt+1 ] "data abort",
	[ PsrMund ] "undefined instruction",
	[ PsrMsys ] "sys trap",
};

static char *
trapname(int psr)
{
	char *s;

	s = trapnames[psr & PsrMask];
	if(s == nil)
		s = "unknown trap number in psr";
	return s;
}

/*
 *  called by trap to handle access faults
 */
static void
faultarm(Ureg *ureg, uintptr va, int user, int read)
{
	if(fault(va, ureg->pc, read) < 0){
		if(!user){
			dumpregs(ureg);
			panic("kernel fault: %s addr=%#p", read? "read": "write", va);
		}
		faultnote("fault", read? "read": "write", va);
	}
}

/*
 *  returns 1 if the instruction writes memory, 0 otherwise
 */
int
writetomem(ulong inst)
{
	/* swap always write memory */
	if((inst & 0x0FC00000) == 0x01000000)
		return 1;

	/* loads and stores are distinguished by bit 20 */
	if(inst & (1<<20))
		return 0;

	return 1;
}

void
trap(Ureg *ureg)
{
	int user, x, rv;
	ulong inst;
	u32int fsr;
	uintptr va;
	char buf[ERRMAX];

	user = kenter(ureg);
	if(ureg->type == PsrMabt+1)
		ureg->pc -= 8;
	else
		ureg->pc -= 4;

	switch(ureg->type) {
	default:
		panic("unknown trap %ld", ureg->type);
		break;
	case PsrMirq:
		m->intr++;
		ldrexvalid = 0;
		preempted(intrs(ureg, Irqlo));
		break;
	case PsrMabt:			/* prefetch fault */
		ldrexvalid = 0;
		faultarm(ureg, ureg->pc, user, 1);
		if(up->nnote == 0 &&
		   (*(u32int*)ureg->pc & ~(0xF<<28)) == 0x01200070)
			postnote(up, 1, "sys: breakpoint", NDebug);
		break;
	case PsrMabt+1:			/* data fault */
		ldrexvalid = 0;
		va = farget();
		inst = *(ulong*)(ureg->pc);
		fsr = fsrget() & 0xf;
		if (probing && !user) {
			if (trapped++ > 0)
				panic("trap: recursive probe %#lux", va);
			ureg->pc += 4;	/* continue at next instruction */
			break;
		}
		switch(fsr){
		case 0x0:
			panic("vector exception at %#lux", ureg->pc);
			break;
		case 0x1:
		case 0x3:
			if(user){
				snprint(buf, sizeof buf,
					"sys: alignment: pc %#lux va %#p\n",
					ureg->pc, va);
				postnote(up, 1, buf, NDebug);
			} else
				panic("kernel alignment: pc %#lux va %#p", ureg->pc, va);
			break;
		case 0x2:
			panic("terminal exception at %#lux", ureg->pc);
			break;
		case 0x4:
		case 0x6:
		case 0x8:
		case 0xa:
		case 0xc:
		case 0xe:
			panic("external abort %#ux pc %#lux addr %#px",
				fsr, ureg->pc, va);
			break;
		case 0x5:		/* translation fault, no section entry */
		case 0x7:		/* translation fault, no page entry */
			faultarm(ureg, va, user, !writetomem(inst));
			break;
		case 0x9:
		case 0xb:
			/* domain fault, accessing something we shouldn't */
			if(user){
				snprint(buf, sizeof buf,
					"sys: access violation: pc %#lux va %#p\n",
					ureg->pc, va);
				postnote(up, 1, buf, NDebug);
			} else
				panic("kernel access violation: pc %#lux va %#p",
					ureg->pc, va);
			break;
		case 0xd:
		case 0xf:
			/* permission error, copy on write or real permission error */
			faultarm(ureg, va, user, !writetomem(inst));
			break;
		}
		break;
	case PsrMund:	/* undefined instruction */
		if(user){
			if(seg(up, ureg->pc, 0) != nil &&
			   (*(u32int*)ureg->pc & ~(0xF<<28)) == 0x01200070)
				postnote(up, 1, "sys: breakpoint", NDebug);
			else{
				/* look for floating point instructions to interpret */
				x = spllo();
				rv = fpiarm(ureg);
				splx(x);
				if(rv == 0){
					ldrexvalid = 0;
					snprint(buf, sizeof buf,
						"undefined instruction: pc %#lux",
						ureg->pc);
					postnote(up, 1, buf, NDebug);
				}
			}
		}else{
			iprint("undefined instruction: pc %#lux inst %#ux\n",
				ureg->pc, ((u32int*)ureg->pc)[-2]);
			panic("undefined instruction");
		}
		break;
	}
	splhi();

	if(user){
		if(up->procctl || up->nnote)
			donotify(ureg);
		kexit(ureg);
	}
}

int
isvalidaddr(void *v)
{
	return (uintptr)v >= KZERO;
}

void
dumplongs(char *msg, ulong *v, int n)
{
	int i, l;

	l = 0;
	iprint("%s at %.8p: ", msg, v);
	for(i=0; i<n; i++){
		if(l >= 4){
			iprint("\n    %.8p: ", v);
			l = 0;
		}
		if(isvalidaddr(v)){
			iprint(" %.8lux", *v++);
			l++;
		}else{
			iprint(" invalid");
			break;
		}
	}
	iprint("\n");
}

static void
dumpstackwithureg(Ureg *ureg)
{
	uintptr l, i, v, estack;
	u32int *p;

	iprint("ktrace /kernel/path %#.8lux %#.8lux %#.8lux # pc, sp, link\n",
		ureg->pc, ureg->sp, ureg->r14);
	delay(2000);
	i = 0;
	if(up != nil && (uintptr)&l <= (uintptr)up)
		estack = (uintptr)up;
	else if((uintptr)&l >= (uintptr)m->stack
	     && (uintptr)&l <= (uintptr)m+MACHSIZE)
		estack = (uintptr)m+MACHSIZE;
	else{
		if(up != nil)
			iprint("&up %#p &l %#p\n", up, &l);
		else
			iprint("&m %#p &l %#p\n", m, &l);
		return;
	}
	for(l = (uintptr)&l; l < estack; l += sizeof(uintptr)){
		v = *(uintptr*)l;
		if(KTZERO < v && v < (uintptr)etext && !(v & 3)){
			v -= sizeof(u32int);		/* back up an instr */
			p = (u32int*)v;
			if((*p & 0x0f000000) == 0x0b000000){	/* BL instr? */
				iprint("%#8.8lux=%#8.8lux ", l, v);
				i++;
			}
		}
		if(i == 4){
			i = 0;
			iprint("\n");
		}
	}
	if(i)
		iprint("\n");
}

/*
 * Fill in enough of Ureg to get a stack trace, and call a function.
 * Used by debugging interface rdb.
 */
void
callwithureg(void (*fn)(Ureg*))
{
	Ureg ureg;

	ureg.pc = getcallerpc(&fn);
	ureg.sp = (uintptr)&fn;
	fn(&ureg);
}

void
dumpstack(void)
{
	callwithureg(dumpstackwithureg);
}

void
dumpregs(Ureg* ureg)
{
	int s;

	if (ureg == nil) {
		iprint("trap: no user process\n");
		return;
	}
	s = splhi();
	iprint("trap: %s", trapname(ureg->type));
	if(ureg != nil && (ureg->psr & PsrMask) != PsrMsvc)
		iprint(" in %s", trapname(ureg->psr));
	iprint("\n");
	iprint("psr %8.8lux type %2.2lux pc %8.8lux link %8.8lux\n",
		ureg->psr, ureg->type, ureg->pc, ureg->link);
	iprint("R14 %8.8lux R13 %8.8lux R12 %8.8lux R11 %8.8lux R10 %8.8lux\n",
		ureg->r14, ureg->r13, ureg->r12, ureg->r11, ureg->r10);
	iprint("R9  %8.8lux R8  %8.8lux R7  %8.8lux R6  %8.8lux R5  %8.8lux\n",
		ureg->r9, ureg->r8, ureg->r7, ureg->r6, ureg->r5);
	iprint("R4  %8.8lux R3  %8.8lux R2  %8.8lux R1  %8.8lux R0  %8.8lux\n",
		ureg->r4, ureg->r3, ureg->r2, ureg->r1, ureg->r0);
	iprint("stack is at %#p\n", ureg);
	iprint("pc %#lux link %#lux\n", ureg->pc, ureg->link);

	if(up)
		iprint("user stack: %#p-%#p\n", (char*)up - KSTACK, up);
	else
		iprint("kernel stack: %8.8lux-%8.8lux\n", (ulong)(m+1), (ulong)m+BY2PG);
	dumplongs("stack", (ulong *)(ureg + 1), 16);
	delay(2000);
	dumpstack();
	splx(s);
}

void
idlehands(void)
{
	extern void _idlehands(void);

	_idlehands();
}

vlong
probeaddr(uintptr addr)
{
	vlong v;
	static Lock fltlck;

	ilock(&fltlck);
	trapped = 0;
	probing = 1;
	coherence();

	v = *(ulong *)addr;	/* this may cause a fault */
	USED(probing);
	coherence();

	probing = 0;
	coherence();
	if (trapped)
		v = -1;
	iunlock(&fltlck);
	return v;
}
