/*
 * 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"

#define INTREGS		(VIRTIO+0xB200)
#define	LOCALREGS	(VIRTIO+IOSIZE)

typedef struct Intregs Intregs;
typedef struct Vctl Vctl;

enum {
	Debug = 0,

	Nvec = 8,		/* # of vectors at start of lexception.s */
	Fiqenable = 1<<7,

	Localtimerint	= 0x40,
	Localmboxint	= 0x50,
	Localintpending	= 0x60,
};

/*
 *   Layout at virtual address KZERO (double mapped at HVECTORS).
 */
typedef struct Vpage0 {
	void	(*vectors[Nvec])(void);
	u32int	vtable[Nvec];
} Vpage0;

/*
 * interrupt control registers
 */
struct Intregs {
	u32int	ARMpending;
	u32int	GPUpending[2];
	u32int	FIQctl;
	u32int	GPUenable[2];
	u32int	ARMenable;
	u32int	GPUdisable[2];
	u32int	ARMdisable;
};

struct Vctl {
	Vctl	*next;
	int	irq;
	int	cpu;
	u32int	*reg;
	u32int	mask;
	void	(*f)(Ureg*, void*);
	void	*a;
};

static Lock vctllock;
static Vctl *vctl, *vfiq;

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",
};

extern int notify(Ureg*);

/*
 *  set up for exceptions
 */
void
trapinit(void)
{
	Vpage0 *vpage0;

	if (m->machno == 0) {
		/* disable everything */
		intrsoff();
		/* set up the exception vectors */
		vpage0 = (Vpage0*)HVECTORS;
		memmove(vpage0->vectors, vectors, sizeof(vpage0->vectors));
		memmove(vpage0->vtable, vtable, sizeof(vpage0->vtable));
		cacheuwbinv();
		l2cacheuwbinv();
	}

	/* set up the stacks for the interrupt modes */
	setr13(PsrMfiq, (u32int*)(FIQSTKTOP));
	setr13(PsrMirq, m->sirq);
	setr13(PsrMabt, m->sabt);
	setr13(PsrMund, m->sund);
	setr13(PsrMsys, m->ssys);

	coherence();
}

void
intrcpushutdown(void)
{
	u32int *enable;

	if(soc.armlocal == 0)
		return;
	enable = (u32int*)(LOCALREGS + Localtimerint) + m->machno;
	*enable = 0;
	if(m->machno){
		enable = (u32int*)(LOCALREGS + Localmboxint) + m->machno;
		*enable = 1;
	}
}

void
intrsoff(void)
{
	Intregs *ip;
	int disable;

	ip = (Intregs*)INTREGS;
	disable = ~0;
	ip->GPUdisable[0] = disable;
	ip->GPUdisable[1] = disable;
	ip->ARMdisable = disable;
	ip->FIQctl = 0;
}

/* called from cpu0 after other cpus are shutdown */
void
intrshutdown(void)
{
	intrsoff();
	intrcpushutdown();
}

static void
intrtime(void)
{
	ulong diff;
	ulong x;

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

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


/*
 *  called by trap to handle irq interrupts.
 *  returns true iff a clock interrupt, thus maybe reschedule.
 */
static int
irq(Ureg* ureg)
{
	Vctl *v;
	int clockintr;
	int found;

	m->perf.intrts = perfticks();
	clockintr = 0;
	found = 0;
	for(v = vctl; v; v = v->next)
		if(v->cpu == m->machno && (*v->reg & v->mask) != 0){
			found = 1;
			coherence();
			v->f(ureg, v->a);
			coherence();
			if(v->irq == IRQclock || v->irq == IRQcntps || v->irq == IRQcntpns)
				clockintr = 1;
		}
	if(!found)
		m->spuriousintr++;
	intrtime();
	return clockintr;
}

/*
 * called direct from lexception.s to handle fiq interrupt.
 */
void
fiq(Ureg *ureg)
{
	Vctl *v;
	int inintr;

	if(m->perf.intrts)
		inintr = 1;
	else{
		inintr = 0;
		m->perf.intrts = perfticks();
	}
	v = vfiq;
	if(v == nil)
		panic("cpu%d: unexpected item in bagging area", m->machno);
	m->intr++;
	ureg->pc -= 4;
	coherence();
	v->f(ureg, v->a);
	coherence();
	if(!inintr)
		intrtime();
}

void
irqenable(int irq, void (*f)(Ureg*, void*), void* a)
{
	Vctl *v;
	Intregs *ip;
	u32int *enable;

	ip = (Intregs*)INTREGS;
	v = (Vctl*)malloc(sizeof(Vctl));
	if(v == nil)
		panic("irqenable: no mem");
	v->irq = irq;
	v->cpu = 0;
	if(irq >= IRQlocal){
		v->reg = (u32int*)(LOCALREGS + Localintpending) + m->machno;
		if(irq >= IRQmbox0)
			enable = (u32int*)(LOCALREGS + Localmboxint) + m->machno;
		else
			enable = (u32int*)(LOCALREGS + Localtimerint) + m->machno;
		v->mask = 1 << (irq - IRQlocal);
		v->cpu = m->machno;
	}else if(irq >= IRQbasic){
		enable = &ip->ARMenable;
		v->reg = &ip->ARMpending;
		v->mask = 1 << (irq - IRQbasic);
	}else{
		enable = &ip->GPUenable[irq/32];
		v->reg = &ip->GPUpending[irq/32];
		v->mask = 1 << (irq % 32);
	}
	v->f = f;
	v->a = a;
	lock(&vctllock);
	if(irq == IRQfiq){
		assert((ip->FIQctl & Fiqenable) == 0);
		assert((*enable & v->mask) == 0);
		vfiq = v;
		ip->FIQctl = Fiqenable | irq;
	}else{
		v->next = vctl;
		vctl = v;
		if(irq >= IRQmbox0){
			if(irq <= IRQmbox3)
				*enable |= 1 << (irq - IRQmbox0);
		}else if(irq >= IRQlocal)
			*enable |= 1 << (irq - IRQlocal);
		else
			*enable = v->mask;
	}
	unlock(&vctllock);
}

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

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

/* this is quite helpful during mmu and cache debugging */
static void
ckfaultstuck(uintptr va)
{
	static int cnt, lastpid;
	static uintptr lastva;

	if (va == lastva && up->pid == lastpid) {
		++cnt;
		if (cnt >= 2)
			/* fault() isn't fixing the underlying cause */
			panic("fault: %d consecutive faults for va %#p",
				cnt+1, va);
	} else {
		cnt = 0;
		lastva = va;
		lastpid = up->pid;
	}
}

/*
 *  called by trap to handle access faults
 */
static void
faultarm(Ureg *ureg, uintptr va, int user, int read)
{
	int n, insyscall;
	char buf[ERRMAX];

	if(up == nil) {
		//dumpregs(ureg);
		panic("fault: nil up in faultarm, pc %#p accessing %#p", ureg->pc, va);
	}
	insyscall = up->insyscall;
	up->insyscall = 1;
	if (Debug)
		ckfaultstuck(va);

	n = fault(va, read);
	if(n < 0){
		if(!user){
			dumpregs(ureg);
			panic("fault: kernel accessing %#p", va);
		}
		/* don't dump registers; programs suicide all the time */
		snprint(buf, sizeof buf, "sys: trap: fault %s va=%#p",
			read? "read": "write", va);
		postnote(up, 1, buf, NDebug);
	}
	up->insyscall = insyscall;
}

/*
 *  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;
}

/*
 *  here on all exceptions other than syscall (SWI) and fiq
 */
void
trap(Ureg *ureg)
{
	int clockintr, user, x, rv, rem;
	ulong inst, fsr;
	uintptr va;
	char buf[ERRMAX];

	assert(!islo());
	if(up != nil)
		rem = ((char*)ureg)-up->kstack;
	else
		rem = ((char*)ureg)-((char*)m+sizeof(Mach));
	if(rem < 256) {
		iprint("trap: %d stack bytes left, up %#p ureg %#p at pc %#lux\n",
			rem, up, ureg, ureg->pc);
		delay(1000);
		dumpstack();
		panic("trap: %d stack bytes left, up %#p ureg %#p at pc %#lux",
			rem, up, ureg, ureg->pc);
	}

	user = (ureg->psr & PsrMask) == PsrMusr;
	if(user){
		up->dbgreg = ureg;
		cycles(&up->kentry);
	}

	/*
	 * All interrupts/exceptions should be resumed at ureg->pc-4,
	 * except for Data Abort which resumes at ureg->pc-8.
	 */
	if(ureg->type == (PsrMabt+1))
		ureg->pc -= 8;
	else
		ureg->pc -= 4;

	clockintr = 0;		/* if set, may call sched() before return */
	switch(ureg->type){
	default:
		panic("unknown trap; type %#lux, psr mode %#lux pc %lux", ureg->type,
			ureg->psr & PsrMask, ureg->pc);
		break;
	case PsrMirq:
		clockintr = irq(ureg);
		m->intr++;
		break;
	case PsrMabt:			/* prefetch fault */
		x = ifsrget();
		fsr = (x>>7) & 0x8 | x & 0x7;
		switch(fsr){
		case 0x02:		/* instruction debug event (BKPT) */
			if(user)
				postnote(up, 1, "sys: breakpoint", NDebug);
			else{
				iprint("kernel bkpt: pc %#lux inst %#ux\n",
					ureg->pc, *(u32int*)ureg->pc);
				panic("kernel bkpt");
			}
			break;
		default:
			faultarm(ureg, ureg->pc, user, 1);
			break;
		}
		break;
	case PsrMabt+1:			/* data fault */
		va = farget();
		inst = *(ulong*)(ureg->pc);
		/* bits 12 and 10 have to be concatenated with status */
		x = fsrget();
		fsr = (x>>7) & 0x20 | (x>>6) & 0x10 | x & 0xf;
		switch(fsr){
		default:
		case 0xa:		/* ? was under external abort */
			panic("unknown data fault, 6b fsr %#lux", fsr);
			break;
		case 0x0:
			panic("vector exception at %#lux", ureg->pc);
			break;
		case 0x1:		/* alignment fault */
		case 0x3:		/* access flag fault (section) */
			if(user){
				snprint(buf, sizeof buf,
					"sys: alignment: va %#p", 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:		/* icache maint fault */
		case 0x8:		/* precise external abort, non-xlat'n */
		case 0x28:
		case 0xc:		/* l1 translation, precise ext. abort */
		case 0x2c:
		case 0xe:		/* l2 translation, precise ext. abort */
		case 0x2e:
		case 0x16:		/* imprecise ext. abort, non-xlt'n */
		case 0x36:
			panic("external abort %#lux pc %#lux addr %#p",
				fsr, ureg->pc, va);
			break;
		case 0x1c:		/* l1 translation, precise parity err */
		case 0x1e:		/* l2 translation, precise parity err */
		case 0x18:		/* imprecise parity or ecc err */
			panic("translation parity error %#lux pc %#lux addr %#p",
				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: va %#p", va);
				postnote(up, 1, buf, NDebug);
			} else
				panic("kernel access violation: pc %#lux va %#p",
					ureg->pc, va);
			break;
		case 0x6:		/* access flag fault (page) */
		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 == 0xD1200070)
				postnote(up, 1, "sys: breakpoint", NDebug);
			else{
				/* look for floating point instructions to interpret */
				rv = fpuemu(ureg);
				if(rv == 0)
					postnote(up, 1, "sys: undefined instruction", NDebug);
			}
		}else{
			if (ureg->pc & 3) {
				iprint("rounding fault pc %#lux down to word\n",
					ureg->pc);
				ureg->pc &= ~3;
			}
			iprint("undefined instruction: pc %#lux inst %#ux\n",
				ureg->pc, *(u32int*)ureg->pc);
			panic("undefined instruction");
		}
		break;
	}
	splhi();

	/* delaysched set because we held a lock or because our quantum ended */
	if(up && up->delaysched && clockintr){
		sched();		/* can cause more traps */
		splhi();
	}

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

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

static 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;
	char *s;

	if((s = getconf("*nodumpstack")) != nil && strcmp(s, "0") != 0){
		iprint("dumpstack disabled\n");
		return;
	}
	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->kstack+KSTACK)
		estack = (uintptr)up->kstack+KSTACK;
	else if((uintptr)&l >= (uintptr)m->stack
	     && (uintptr)&l <= (uintptr)m+MACHSIZE)
		estack = (uintptr)m+MACHSIZE;
	else{
		if(up != nil)
			iprint("&up->kstack %#p &l %#p\n", up->kstack, &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.
 */

static void
getpcsp(ulong *pc, ulong *sp)
{
	*pc = getcallerpc(&pc);
	*sp = (ulong)&pc-4;
}

void
callwithureg(void (*fn)(Ureg*))
{
	Ureg ureg;

	getpcsp((ulong*)&ureg.pc, (ulong*)&ureg.sp);
	ureg.r14 = getcallerpc(&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", up->kstack, up->kstack+KSTACK-4);
	else
		iprint("kernel stack: %8.8lux-%8.8lux\n",
			(ulong)(m+1), (ulong)m+BY2PG-4);
	dumplongs("stack", (ulong *)(ureg + 1), 16);
	delay(2000);
	dumpstack();
	splx(s);
}
