#include	"u.h"
#include	"../port/lib.h"
#include	"mem.h"
#include	"dat.h"
#include	"fns.h"
#include	"ureg.h"
#include	"io.h"
#include	"../port/pci.h"
#include	"tos.h"
#include	"../port/error.h"

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

void
hwintrinit(void)
{
	i8259init();
	mpicenable(0, nil);	/* 8259 interrupts are routed through MPIC intr 0 */
}

static int
hwintrenable(Vctl *v)
{
	int vec, irq;

	irq = v->irq;
	if(BUSTYPE(v->tbdf) == BusPCI) {	/* MPIC? */
		if(irq > 15) {
			print("intrenable: pci irq %d out of range\n", v->irq);
			return -1;
		}
		vec = irq;
		mpicenable(vec, v);
	}
	else {
		if(irq > MaxIrqPIC) {
			print("intrenable: irq %d out of range\n", v->irq);
			return -1;
		}
		vec = irq+VectorPIC;
		if(i8259enable(v) == -1)
			return -1;
	}
	return vec;
}

static int
hwintrdisable(Vctl *v)
{
	int vec, irq;

	irq = v->irq;
	if(BUSTYPE(v->tbdf) == BusPCI) {	/* MPIC? */
		if(irq > 15) {
			print("intrdisable: pci irq %d out of range\n", v->irq);
			return -1;
		}
		vec = irq;
		mpicdisable(vec);
	}
	else {
		if(irq > MaxIrqPIC) {
			print("intrdisable: irq %d out of range\n", v->irq);
			return -1;
		}
		vec = irq+VectorPIC;
		if(i8259disable(irq) == -1)
			return -1;
	}
	return vec;
}

static int
hwvecno(int irq, int tbdf)
{
	if(BUSTYPE(tbdf) == BusPCI) 	/* MPIC? */
		return irq;
	else
		return irq+VectorPIC;
}

void
intrenable(int irq, void (*f)(Ureg*, void*), void* a, int tbdf, char *name)
{
	int vno;
	Vctl *v;

	if(f == nil){
		print("intrenable: nil handler for %d, tbdf 0x%uX for %s\n",
			irq, tbdf, name);
		return;
	}

	v = xalloc(sizeof(Vctl));
	v->isintr = 1;
	v->irq = irq;
	v->tbdf = tbdf;
	v->f = f;
	v->a = a;
	strncpy(v->name, name, KNAMELEN-1);
	v->name[KNAMELEN-1] = 0;

	ilock(&vctllock);
	vno = hwintrenable(v);
	if(vno == -1){
		iunlock(&vctllock);
		print("intrenable: couldn't enable irq %d, tbdf 0x%uX for %s\n",
			irq, tbdf, v->name);
		xfree(v);
		return;
	}
	if(vctl[vno]){
		if(vctl[vno]->isr != v->isr || vctl[vno]->eoi != v->eoi)
			panic("intrenable: handler: %s %s %#p %#p %#p %#p",
				vctl[vno]->name, v->name,
				vctl[vno]->isr, v->isr, vctl[vno]->eoi, v->eoi);
		v->next = vctl[vno];
	}
	vctl[vno] = v;
	iunlock(&vctllock);
}

void
intrdisable(int irq, void (*f)(Ureg *, void *), void *a, int tbdf, char *name)
{
	Vctl **pv, *v;
	int vno;

	vno = hwvecno(irq, tbdf);
	ilock(&vctllock);
	pv = &vctl[vno];
	while (*pv && 
		  ((*pv)->irq != irq || (*pv)->tbdf != tbdf || (*pv)->f != f || (*pv)->a != a ||
		   strcmp((*pv)->name, name)))
		pv = &((*pv)->next);
	assert(*pv);

	v = *pv;
	*pv = (*pv)->next;	/* Link out the entry */
	
	if(vctl[vno] == nil)
		hwintrdisable(v);
	iunlock(&vctllock);
	xfree(v);
}

void	syscall(Ureg*);
static void _dumpstack(Ureg*);

char *excname[] =
{
	"reserved 0",
	"system reset",
	"machine check",
	"data access",
	"instruction access",
	"external interrupt",
	"alignment",
	"program exception",
	"floating-point unavailable",
	"decrementer",
	"reserved A",
	"reserved B",
	"system call",
	"trace trap",
	"floating point assist",
	"reserved F",
	"reserved 10",
	"reserved 11",
	"reserved 12",
	"instruction address breakpoint",
	"system management interrupt",
};

char *fpcause[] =
{
	"inexact operation",
	"division by zero",
	"underflow",
	"overflow",
	"invalid operation",
};
char	*fpexcname(Ureg*, ulong, char*);
#define FPEXPMASK	0xfff80300		/* Floating exception bits in fpscr */


char *regname[]={
	"CAUSE",	"SRR1",
	"PC",		"GOK",
	"LR",		"CR",
	"XER",	"CTR",
	"R0",		"R1",
	"R2",		"R3",
	"R4",		"R5",
	"R6",		"R7",
	"R8",		"R9",
	"R10",	"R11",
	"R12",	"R13",
	"R14",	"R15",
	"R16",	"R17",
	"R18",	"R19",
	"R20",	"R21",
	"R22",	"R23",
	"R24",	"R25",
	"R26",	"R27",
	"R28",	"R29",
	"R30",	"R31",
};

void
trap(Ureg *ureg)
{
	ulong dsisr;
	int ecode, user;
	char buf[ERRMAX], *s;

	user = kenter(ureg);
	ecode = (ureg->cause >> 8) & 0xff;
	if((ureg->status & MSR_RI) == 0)
		print("double fault?: ecode = %d\n", ecode);

	switch(ecode) {
	case CEI:
		intr(ureg);
		preempted(0);
		break;
	case CDEC:
		clockintr(ureg);
		preempted(1);
		break;
	case CSYSCALL:
		if(!user)
			panic("syscall in kernel: srr1 0x%4.4luX", ureg->srr1);
		syscall(ureg);
		return;		/* syscall() calls notify itself, don't do it again */
	case CFPU:
		if(!user || up == nil) {
			dumpregs(ureg);
			panic("floating point in kernel");
		}
		switch(up->fpstate){
		case FPinit:
			fprestore(&initfp);
			up->fpstate = FPactive;
			break;
		case FPinactive:
			fprestore(up->fpsave);
			up->fpstate = FPactive;
			break;
		default:
			panic("fpstate");
		}
		ureg->srr1 |= MSR_FP;
		break;
	case CISI:
		faultpower(ureg, ureg->pc, 1);
		break;
	case CDSI:
		dsisr = getdsisr();
		if(dsisr & BIT(6))
			faultpower(ureg, getdar(), 0);
		else
			faultpower(ureg, getdar(), 1);
		break;
	case CPROG:
		if(ureg->status & (1<<19))
			s = "floating point exception";
		else if(ureg->status & (1<<18))
			s = "illegal instruction";
		else if(ureg->status & (1<<17))
			s = "privileged instruction";
		else
			s = "undefined program exception";
		if(user){
			spllo();
			sprint(buf, "sys: trap: %s", s);
			postnote(up, 1, buf, NDebug);
			break;
		}
		dumpregs(ureg);
		panic(s);
		break;
	default:
		if(ecode < nelem(excname) && user){
			spllo();
			sprint(buf, "sys: trap: %s", excname[ecode]);
			postnote(up, 1, buf, NDebug);
			break;
		}
		dumpregs(ureg);
		if(ecode < nelem(excname))
			panic("%s", excname[ecode]);
		panic("unknown trap/intr: %d", ecode);
	}

	/* restoreureg must execute at high IPL */
	splhi();

	if(user) {
		notify(ureg);
		if(up->fpstate == FPinactive)
			ureg->srr1 &= ~MSR_FP;
		kexit(ureg);
	}
}

void
faultpower(Ureg *ureg, ulong addr, int read)
{
	if(fault(addr, ureg->pc, read) < 0){
		if(!userureg(ureg)){
			dumpregs(ureg);
			panic("kernel fault: 0x%lux", addr);
		}
		faultnote("fault",  read? "read": "write", addr);
	}
}

void
sethvec(int v, void (*r)(void))
{
	ulong *vp, pa, o;

	vp = KADDR(v);
	vp[0] = 0x7c1043a6;	/* MOVW R0, SPR(SPRG0) */
	vp[1] = 0x7c0802a6;	/* MOVW LR, R0 */
	vp[2] = 0x7c1243a6;	/* MOVW R0, SPR(SPRG2) */
	pa = PADDR(r);
	o = pa >> 25;
	if(o != 0 && o != 0x7F){
		/* a branch too far */
		vp[3] = (15<<26)|(pa>>16);	/* MOVW $r&~0xFFFF, R0 */
		vp[4] = (24<<26)|(pa&0xFFFF);	/* OR $r&0xFFFF, R0 */
		vp[5] = 0x7c0803a6;	/* MOVW	R0, LR */
		vp[6] = 0x4e800021;	/* BL (LR) */
	}else
		vp[3] = (18<<26)|(pa&0x3FFFFFC)|3;	/* bla */
	dcflush(vp, 8*sizeof(ulong));
}

void
trapinit(void)
{
	int i;

	/*
	 * set all exceptions to trap
	 */
	for(i = 0; i < 0x2000; i += 0x100)
		sethvec(i, trapvec);

	putmsr(getmsr() & ~MSR_IP);
}

void
intr(Ureg *ureg)
{
	int vno;
	Vctl *ctl, *v;

	vno = mpicintack();
	if(vno == 0) {			/* 8259, wired through MPIC vec 0 */
		vno = i8259intack();
		mpiceoi(0);
	}

	if(vno > nelem(vctl) || (ctl = vctl[vno]) == 0)
		panic("spurious intr %d", vno);

	if(ctl->isr)
		ctl->isr(vno);
	for(v = ctl; v != nil; v = v->next){
		if(v->f)
			v->f(ureg, v->a);
	}
	if(ctl->eoi)
		ctl->eoi(vno);
}

char*
fpexcname(Ureg *ur, ulong fpscr, char *buf)
{
	int i;
	char *s;
	ulong fppc;

	fppc = ur->pc;
	s = 0;
	fpscr >>= 3;		/* trap enable bits */
	fpscr &= (fpscr>>22);	/* anded with exceptions */
	for(i=0; i<5; i++)
		if(fpscr & (1<<i))
			s = fpcause[i];
	if(s == 0)
		return "no floating point exception";
	sprint(buf, "%s fppc=0x%lux", s, fppc);
	return buf;
}

/*
 * 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.lr = getcallerpc(&fn);
	fn(&ureg);
}

static void
_dumpstack(Ureg *ureg)
{
	ulong l, sl, el, v;
	int i;

	l = (ulong)&l;
	if(up == 0){
		el = (ulong)m+BY2PG;
		sl = el-KSTACK;
	}
	else{
		el = (ulong)up;
		sl = el-KSTACK;
	}
	if(l > el || l < sl){
		el = (ulong)m+BY2PG;
		sl = el-KSTACK;
	}
	if(l > el || l < sl)
		return;
	print("ktrace /kernel/path %.8lux %.8lux %.8lux\n", ureg->pc, ureg->sp, ureg->lr);
	i = 0;
	for(; l < el; l += 4){
		v = *(ulong*)l;
		if(KTZERO < v && v < (ulong)etext){
			print("%.8lux=%.8lux ", l, v);
			if(i++ == 4){
				print("\n");
				i = 0;
			}
		}
	}
}

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

void
dumpregs(Ureg *ur)
{
	int i;
	ulong *l;

	if(up) {
		print("registers for %s %ld\n", up->text, up->pid);
		if((ur->srr1 & MSR_PR) == 0)
		if(ur->usp < (ulong)up - KSTACK || ur->usp > (ulong)up)
			print("invalid stack ptr\n");
	}
	else
		print("registers for kernel\n");

	print("dsisr\t%.8lux\tdar\t%.8lux\n", getdsisr(), getdar());
	l = &ur->cause;
	for(i=0; i<sizeof regname/sizeof(char*); i+=2, l+=2)
		print("%s\t%.8lux\t%s\t%.8lux\n", regname[i], l[0], regname[i+1], l[1]);
}

void
kprocchild(Proc *p, void (*entry)(void))
{
	p->sched.pc = (ulong)entry;
	p->sched.sp = (ulong)p;
}

/*
 * called in sysfile.c
 */
void
evenaddr(ulong addr)
{
	if(addr & 3){
		postnote(up, 1, "sys: odd address", NDebug);
		error(Ebadarg);
	}
}

uintptr
execregs(uintptr entry, ulong ssize, ulong nargs)
{
	ulong *sp;
	Ureg *ureg;

	sp = (ulong*)(USTKTOP - ssize);
	*--sp = nargs;

	ureg = up->dbgreg;
	ureg->usp = (ulong)sp;
	ureg->pc = entry;
	ureg->srr1 &= ~MSR_FP;
	return USTKTOP-sizeof(Tos);		/* address of kernel/user shared data */
}

void
forkchild(Proc *p, Ureg *ur)
{
	Ureg *cur;

	p->sched.sp = (ulong)p - UREGSIZE;
	p->sched.pc = (ulong)forkret;

	cur = (Ureg*)(p->sched.sp+2*BY2WD);
	memmove(cur, ur, sizeof(Ureg));
	cur->r3 = 0;
}

uintptr
userpc(void)
{
	Ureg *ureg;

	ureg = (Ureg*)up->dbgreg;
	return ureg->pc;
}


/* This routine must save the values of registers the user is not 
 * permitted to write from devproc and then restore the saved values 
 * before returning
 */
void
setregisters(Ureg *xp, char *pureg, char *uva, int n)
{
	ulong status;

	status = xp->status;
	memmove(pureg, uva, n);
	xp->status = status;
}

/* Give enough context in the ureg to produce a kernel stack for
 * a sleeping process
 */
void
setkernur(Ureg* ureg, Proc* p)
{
	ureg->pc = p->sched.pc;
	ureg->sp = p->sched.sp+4;
}

uintptr
dbgpc(Proc *p)
{
	Ureg *ureg;

	ureg = p->dbgreg;
	if(ureg == 0)
		return 0;

	return ureg->pc;
}

/*
 *  system calls
 */

/* TODO: make this trap a separate asm entry point, like on other RISC architectures */
void
syscall(Ureg* ureg)
{
	ulong scallnr;

	if(!kenter(ureg))
		panic("syscall from kernel");
	if (up->fpstate == FPactive && (ureg->srr1 & MSR_FP) == 0){
		print("fpstate check, entry syscall\n");
		delay(200);
		dumpregs(ureg);
		print("fpstate check, entry syscall\n");
	}
	scallnr = ureg->r3;
	dosyscall(scallnr, (Sargs*)(ureg->usp+BY2WD), &ureg->r3);
	if(up->procctl || up->nnote)
		notify(ureg);
	/* if we delayed sched because we held a lock, sched now */
	if(up->delaysched)
		sched();
	kexit(ureg);
	if (up->fpstate == FPactive && (ureg->srr1 & MSR_FP) == 0){
		print("fpstate check, exit syscall nr %lud, pid %lud\n", scallnr, up->pid);
		dumpregs(ureg);
	}
	if(up->fpstate != FPactive)
		ureg->srr1 &= ~MSR_FP;
}

/*
 *  Call user, if necessary, with note.
 *  Pass user the Ureg struct and the note on his stack.
 */
Ureg*
notify(Ureg* ur, char *msg)
{
	Ureg *nur;
	ulong s, sp;

	sp = ur->usp & ~(BY2V-1);
	sp -= sizeof(Ureg);
	if(!okaddr(sp-ERRMAX-4*BY2WD, sizeof(Ureg)+ERRMAX+4*BY2WD, 1))
		return nil;
	nur = (Ureg*)sp;
	memmove(nur, ur, sizeof(Ureg));
	sp -= BY2WD+ERRMAX;
	memmove((char*)sp, msg, ERRMAX);
	sp -= 3*BY2WD;
	*(ulong*)(sp+2*BY2WD) = sp+3*BY2WD;	/* arg 2 is string */
	ur->r1 = (long)nur;			/* arg 1 is ureg* */
	((ulong*)sp)[1] = (ulong)nur;		/* arg 1 0(FP) is ureg* */
	((ulong*)sp)[0] = 0;			/* arg 0 is pc */
	ur->usp = sp;
	ur->pc = (ulong)up->notify;
	return nur;
}


/*
 *   Return user to state before notify()
 */
int
noted(Ureg *ureg, Ureg *nureg, int arg0)
{
	ulong oureg, sp;

	oureg = (ulong)nureg;

	setregisters(ureg, (char*)ureg, (char*)nureg, sizeof(Ureg));

	switch(arg0){
	case NCONT:
	case NRSTR:
		if(!okaddr(nureg->pc, 1, 0) || !okaddr(nureg->usp, BY2WD, 0))
			return -1;
		break;
	case NSAVE:
		if(!okaddr(nureg->pc, BY2WD, 0)
		|| !okaddr(nureg->usp, BY2WD, 0))
			return -1;
		sp = oureg-4*BY2WD-ERRMAX;
		ureg->sp = sp;
		((ulong*)sp)[1] = oureg;	/* arg 1 0(FP) is ureg* */
		((ulong*)sp)[0] = 0;		/* arg 0 is pc */
		break;
	}
	return 0;
}
