/*
 * 5799-CGZ (C) COPYRIGHT IBM CORPORATION 1986,1987,1988
 * LICENSED MATERIALS - PROPERTY OF IBM
 * REFER TO COPYRIGHT INSTRUCTIONS FORM NUMBER G120-2083
 */
/* $Header: trap.c,v 12.1 89/05/09 23:18:56 triha Exp $ */
/* $ACIS:trap.c 9.0$ */
/* $Source: /ibm/acis/usr/sys/ca/RCS/trap.c,v $ */

#if !defined(lint) && !defined(NO_RCS_HDRS)
static char *rcsid = "$Header: trap.c,v 12.1 89/05/09 23:18:56 triha Exp $";
#endif
#if defined(DEBUG) && !defined(SYSCALLTRACE)
#define SYSCALLTRACE
#endif

 /*     trap.c  6.1     83/08/18        */

#include "psp.h"
#include "asy.h"
#include "param.h"
#include "debug.h"
#include "scr.h"
#include "mmu.h"
#include "reg.h"
#include "frame.h"

#include "vmparam.h"
#include "systm.h"
#include "dir.h"
#include "user.h"
#include "proc.h"
#include "seg.h"
#include "acct.h"
#include "kernel.h"
#include "vmmeter.h"
#ifdef SYSCALLTRACE
#include "../sys/syscalls.c"
#endif
#include "io.h"
#ifdef ROMPC
#include "cpu.h"
#include "ioim.h"
#endif ROMPC
#include "fp.h"
#include "sigframe.h"


#define IPAGE_FAULT (PCS_KNOWN+PCS_IADDR)
#define DPAGE_FAULT (PCS_KNOWN+PCS_DADDR)
#define DPAGE_FAULT2 (PCS_UNKNOWN+PCS_DADDR)
#define ILL_OP	(PCS_KNOWN+PCS_BAD_I)
#define PRIV_OP	(PCS_KNOWN+PCS_PRIV_I)

struct sysent sysent[];
int nsysent;
char *mcpcfmt = MCPCFMT;

char *icsfmt = ICSFMT;
int _csr;				  /* the csr contents after trap */
					  /* just ICS without CS */
int addupc_r15 = 1;			  /* if we should use r15 if iar isn't good */

/*
 * Called from the trap handler when a processor trap occurs.
 *   mcs_pcs   = machine check status and program check status
 *   info = specific to type of check (eg mmu exception register)
 *	the low-order 16 bits of info are low-order 16 bits of the SER
 *	the high-order 16 bits of info are the offending page number.
 * Note that some high order bits of the mcs_pcs are used to simulate the
 *   breakpoint and AST conditions of the VAX.
 */
/*ARGSUSED*/
trap(xmcs_pcs, xinfo, ics_cs, regs)
	int xmcs_pcs;			/* force onto stack */
	int xinfo;			/* force onto stack */
	int ics_cs;			  /* must not be a register variable */
	int regs;			  /* must not be a register variable */
{
	register mcs_pcs = xmcs_pcs;
	register info = xinfo;
	register int *locr0 = &regs;
	register int i;
	register struct proc *p;
	struct timeval syst;
	char *epitaph;


	syst = u.u_ru.ru_stime;

	if (ICSCS_PROBSTATE & ics_cs) {
		mcs_pcs |= USER;
		u.u_ar0 = locr0;
	}

	DEBUGF((trdebug & mcs_pcs),
	    prstate("trap", mcs_pcs, info, locr0[IAR], ics_cs, 
		(trdebug & SHOW_TRAP_REGS) ? locr0 : (int *)0));

	switch (mcs_pcs) {
	default:
		if (mcs_pcs & MCS_CHECK) {	/* machine check */
			if (mcs_pcs & USER) {	/* machine check in user mode */
				printf("machine check in user mode ...\n");
				prstate("machine check", mcs_pcs, info, locr0[IAR], ics_cs, locr0);
				klsreset();	/* reinitialize keyboard */
				klswait();	/* and wait until done */
				printf("keyboard init done.\n");
#if NPSP > 0
				pspreset();
				printf("psp init done.\n");
#endif NPSP
				i = SIGBUS;
				break;
			} else {	/* machine check in kernel mode */
				epitaph = "machine check";
				klsreset();	/* reinitialize keyboard */
				spl0();		/* allow interrupts */
				klswait();	/* and wait until done */
			}
		} else if (mcs_pcs & USER) {
			printf("trap: unknown trap (0x%b) in user mode\n",
				mcs_pcs, mcpcfmt);
			i = SIGSEGV;
			break;
		} else {	/* else unknown trap in kernel mode */
			epitaph = "trap";
		}
		goto grave;


#ifdef ROMPC
	case DPAGE_FAULT2 + USER:
		if (locr0[ECR_COUNT] == 0) {
			epitaph = "nothing on exception stack";
			goto grave;
		}
		if (locr0[ECR_COUNT] > 1)
			if (check_apc_bug(locr0)) {
				i = SIGBUS;
				break;
			}
		if (locr0[ECR_COUNT] > 1)
			info = apc_mux(locr0,info);	/* handle multiple exceptions */
		info = (info&(NBPG-1)) | (locr0[EX1_ADDR] & (-NBPG));
		/* fall thru for now */
#endif ROMPC
	case DPAGE_FAULT + USER:	  /* data page fault */
	case IPAGE_FAULT + USER:	  /* inst page fault */
		if (locr0[ECR_COUNT] > 1)
			if (check_apc_bug(locr0)) {
				i = SIGBUS;
				break;
			}
		if (info & MMU_EX_KEYS) {
#ifdef SIM_CHG_BIT
			if (GETSEG(info) == 0xf) {
				if (float_pci(mcs_pcs,info,locr0))
					goto out;	/* handled by float_pci */
			} else if (protection(mcs_pcs, info, locr0) == 0)
				return;		/* was handled protection */
#endif SIM_CHG_BIT
			i = SIGSEGV;
			break;
		}
/*
 * following check against segment f is to distinguish page faults
 * from other program checks.
 * This is necssary because these faults sometimes have the page fault bit on!!!
 */
		if ((info & MMU_EX_FAULT) && GETSEG(info) != 0xf) {
			i = u.u_error;
			if (pagein(info & (-NBPG), 0)) { /* VAX segment fault */
				if (GROWSP(locr0[SP]))
					goto out;
				i = SIGSEGV;
				break;
			}
			u.u_error = i;
			goto out;
		}
		/* check to see if FPA related */
		if (float_pci(mcs_pcs,info,locr0))
			goto out;	/* handled by float_pci */
		/* assume reference to bad address (segment F?) and give SIGBUS */
		i = SIGBUS;		  /* not mmu related */
		break;

#ifdef ROMPC
	case DPAGE_FAULT2:
		if (locr0[ECR_COUNT] == 0) {
			epitaph = "nothing on exception stack";
			goto grave;
		}
		if (locr0[ECR_COUNT] > 1)
			info = apc_mux(locr0,info);	/* handle multiple exceptions */
		info = (info&(NBPG-1)) | (locr0[EX1_ADDR] & (-NBPG));
		/* fall thru for now */
#endif ROMPC
	case DPAGE_FAULT:		  /* allow data page faults in kernel mode */
#ifdef SIM_CHG_BIT
		if (info & MMU_EX_KEYS) {
			if (GETSEG(info) == 0xf) {
				if (float_pci(mcs_pcs,info,locr0))
					goto out;	/* handled by float_pci */
			} else if (protection(mcs_pcs, info, locr0) == 0)
				return;		/* was handled protection */
		}
#endif SIM_CHG_BIT
		if ((info & MMU_EX_FAULT) == 0 || GETSEG(info) == 0xf) {
			/* if it is a data reference then check to see if FPA related */
			if (float_pci(mcs_pcs,info,locr0)) {
				DEBUGF(fpadebug, printf("float_pci returned ics=%x\n",mfsr(SCR_ICS)));
				return;		/* handled by float_pci */
			}
			epitaph = "kernel trap not tlb";
			goto grave;
		}
		i = u.u_error;
		if (pagein(info & (-NBPG), 0)) { /* VAX segment fault */
			if ((info & (-NBPG)) == REDZONE)
				epitaph = "kernel stack overflow";
			else
				epitaph = "kernel trap";
			goto grave;
		}
		u.u_error = i;
		return;


	case IPAGE_FAULT:		/* allow instruction faults in kernel mode for NFL fixups */
		if (nfl_fixup(mcs_pcs,info,locr0))
			return;		/* nfl_fixup handled it */
		epitaph = "trap";
		goto grave;
	case BKPT + USER:		  /* allow breakpoint inst only in user mode */
					  /* integer division-by-zero;  inst */
					  /* must match exactly ldiv.s and   */
					  /* uldiv.s in libc/machine/gen     */
		if ((*(u_short *)locr0[IAR]) == 0xcc23) {
			u.u_code = FP_INT_DIVIDE;
			i = SIGFPE;
			break;
		}
		/* else fall through to step inst. */

	case STEP + USER:		  /* allow inst step only in user mode */
		ics_cs &= ~ICSCS_INSTSTEP; /* probably off already */
		i = SIGTRAP;
		break;

	case VAST + USER:		  /* allow VAX AST only in user mode */
		astoff();		  /* once is enough */
		/*
		 * we attempt to first profile the IAR, if that doesn't
		 * work we try r15. This is primarily so that the generated
		 * floating point code gets attributed to the calling C
		 * code rather than lost.
		 */
		if ((u.u_procp->p_flag & SOWEUPC) && u.u_prof.pr_scale) {
			if (!addupc(locr0[IAR], &u.u_prof, 1) && addupc_r15)
				addupc(locr0[R15], &u.u_prof, 1);
			u.u_procp->p_flag &= ~SOWEUPC;
		}
		goto out;

	case ILL_OP + USER:		  /* illegal instruction */
	case PRIV_OP + USER:		  /* privileged instruction */
		u.u_code = mcs_pcs - USER;
		i = SIGILL;
		break;

	}
	/* end of switch mcs_pcs */
	DEBUGF(sydebug&0x400, printf("trap: psignal(%d)\n", i));
		
	psignal(u.u_procp, i);
out:
	p = u.u_procp;
	if (p->p_cursig || ISSIG(p))
		psig();
	p->p_pri = p->p_usrpri;
	if (runrun) {
		/*
		 * Since we are u.u_procp, clock will normally just change
		 * our priority without moving us from one queue to another
		 * (since the running process is not on a queue.)
		 * If that happened after we setrq ourselves but before we
		 * swtch()'ed, we might not be on the queue indicated by
		 * our priority.
		 */
		(void) splclock();
		setrq(p);
		u.u_ru.ru_nivcsw++;
		swtch();
	}
	if (u.u_prof.pr_scale) {
		register int ticks;
		register struct timeval *tv = &u.u_ru.ru_stime;

		ticks = (tv->tv_sec - syst.tv_sec) * hz +
			(tv->tv_usec - syst.tv_usec) / tick;
		if (ticks)
			addupc(locr0[IAR], &u.u_prof, ticks);
	}
	curpri = p->p_pri;
	return;

grave:
	spl7();
	prstate("trap", mcs_pcs, info, locr0[IAR], ics_cs, locr0);
	panic(epitaph);
}


syscall(mcs_pcs, info, ics_cs, regs)
	register mcs_pcs, info;
	int ics_cs;			  /* must not be a register variable */
	int regs;			  /* must not be a register variable */
{
	register int *locr0 = &regs;
	struct timeval syst;
	register struct sysent *callp;
#ifdef PCC
	register struct user *uptr = &u;
#define u (*uptr)			/* allow faster access to u */
#endif

	DEBUGF(svdebug&0x80,
	    prstate("svc", mcs_pcs, info, locr0[IAR], ics_cs, (int *)0));

	if ((ICSCS_PROBSTATE & ics_cs)  == 0)
		panic("syscall");

	syst = u.u_ru.ru_stime;

	u.u_ar0 = locr0;
	if (info == 139) { /* This undefined syscall is used by */
		sigcleanup(&((struct sigframe *)locr0[SP])->sf_sc); /* the signal trampoline code, to do */
		goto out; /* the return from the user signal   */
	}

	u.u_error = 0;		/* optimistic */
	if (info) {		/* direct */
		callp = ((unsigned) info >= nsysent) ? &sysent[0] : &sysent[info];
#ifdef DUALCALL
		if (u.u_calltype) 
#endif DUALCALL
/****** NCS call has arg 4 in R5 *********/
			switch(callp->sy_narg) {
			default:
				panic("syscall: narg > 6");
			case 6:
				u.u_arg[5] = fuword((caddr_t)locr0[SP] + ARG5_OFFSET + 4);
			case 5:
				u.u_arg[4] = fuword((caddr_t)locr0[SP] + ARG5_OFFSET);
			case 4:
				u.u_arg[3] = locr0[R5];
			case 3:
				u.u_arg[2] = locr0[R4];
			case 2:
				u.u_arg[1] = locr0[R3];
			case 1:
				u.u_arg[0] = locr0[R2];
			case 0:
				;
			}
#ifdef DUALCALL
		else
/****** OCS call has arg 4 in 12(sp) *********/
			switch(callp->sy_narg) {
			default:
				panic("syscall: narg > 6");
			case 6:
				u.u_arg[5] = fuword((caddr_t)locr0[SP] + 5 * sizeof(int));
			case 5:
				u.u_arg[4] = fuword((caddr_t)locr0[SP] + 4 * sizeof(int));
			case 4:
				u.u_arg[3] = fuword((caddr_t)locr0[SP] + 3 * sizeof(int));
			case 3:
				u.u_arg[2] = locr0[R4];
			case 2:
				u.u_arg[1] = locr0[R3];
			case 1:
				u.u_arg[0] = locr0[R2];
			case 0:
				;
			}
#endif DUALCALL
	} else {	  /* indirection */
			info = locr0[R2];
			callp = ((unsigned) info >= nsysent) ? &sysent[0] : &sysent[info];
#ifdef DUALCALL
			if (u.u_calltype) 
#endif DUALCALL
/****** NCS call has arg 4 in R5 *********/
				switch(callp->sy_narg) {
				default:
					panic("syscall: narg > 6");
				case 6:
					u.u_arg[5] = fuword((caddr_t)locr0[SP] + ARG5_OFFSET + 2 * sizeof(int));
				case 5:
					u.u_arg[4] = fuword((caddr_t)locr0[SP] + ARG5_OFFSET + 4);
				case 4:
					u.u_arg[3] = fuword((caddr_t)locr0[SP] + ARG5_OFFSET);
				case 3:
					u.u_arg[2] = locr0[R5];
				case 2:
					u.u_arg[1] = locr0[R4];
				case 1:
					u.u_arg[0] = locr0[R3];
				case 0:
					;
				}
#ifdef DUALCALL
			else
/****** OCS call has arg 4 in 12(sp) *********/
				switch(callp->sy_narg) {
				default:
					panic("syscall: narg > 6");
				case 6:
					u.u_arg[5] = fuword((caddr_t)locr0[SP] + 6 * sizeof(int));
				case 5:
					u.u_arg[4] = fuword((caddr_t)locr0[SP] + 5 * sizeof(int));
				case 4:
					u.u_arg[3] = fuword((caddr_t)locr0[SP] + 4 * sizeof(int));
				case 3:
					u.u_arg[2] = fuword((caddr_t)locr0[SP] + 3 * sizeof(int));
				case 2:
					u.u_arg[1] = locr0[R4];
				case 1:
					u.u_arg[0] = locr0[R3];
				case 0:
					;
				}
#endif DUALCALL
	}
#ifdef DUALCALL
         if (u.u_calltype)
#endif DUALCALL
	 {
#ifdef VFS
           u.u_ap = u.u_arg;
#endif VFS
 	   u.u_r.r_val1 = 0;
 	   u.u_r.r_val2 = locr0[R0];
         }
#ifdef DUALCALL
         else
	 {
#ifdef VFS
           u.u_ap = u.u_arg;
#endif VFS
 	   u.u_r.r_val1 = 0;
 	   u.u_r.r_val2 = locr0[R2];
         }
#endif DUALCALL
	if (setjmp(&u.u_qsave)) {
		if (u.u_error == 0 && u.u_eosys != RESTARTSYS)
			u.u_error = EINTR;
	} else {
		u.u_eosys = NORMALRETURN;
#ifdef SYSCALLTRACE
		if (svdebug || (u.u_procp->p_flag & STRACE)) {
			register int j;
			char *cp;

			if (info >= nsysent)
				Tprintf("0x%x", info);
			else
				Tprintf("%s", syscallnames[info]);
			cp = "(";
			for (j = 0; j < callp->sy_narg; j++) {
				Tprintf("%s%x", cp, u.u_arg[j]);
				cp = ", ";
			}
			if (j)
				Tprintf(")");
			Tprintf(" pid=%d\n", u.u_procp->p_pid);
		}
#endif
#ifndef VFS
		(*(callp->sy_call))();		
#else !VFS
		(*(callp->sy_call))(u.u_ap);
#endif !VFS
#ifdef SYSCALLTRACE
		if ((svdebug || (u.u_procp->p_flag & STRACE)) && (svdebug > 1 || u.u_error))
			Tprintf("%s completed for pid %d error %d returns 0x%x 0x%x\n",
			    syscallnames[info], u.u_procp->p_pid, u.u_error, u.u_r.r_val1, u.u_r.r_val2);
#endif
	}
	if (u.u_eosys == NORMALRETURN) {
		if (u.u_error) {
#ifdef DUALCALL
		    if (u.u_calltype==0)
			locr0[R0] = u.u_error;
		    else 
#endif DUALCALL
			locr0[R2] = u.u_error;
		   ics_cs |= ICSCS_TESTBIT;
		} else {
#ifdef DUALCALL
		    if (u.u_calltype==0) {
		       locr0[R0] = u.u_r.r_val1;
		       locr0[R2] = u.u_r.r_val2;
		    } else
#endif DUALCALL
			   {
		       locr0[R2] = u.u_r.r_val1;
		       locr0[R0] = u.u_r.r_val2;
		    }
		ics_cs &= ~ICSCS_TESTBIT;
		}
	} else if (u.u_eosys == RESTARTSYS)
		locr0[IAR] -= 4; /* backup IAR to re-execute svc */
	/* else if (u.u_eosys == JUSTRETURN) */
		/* nothing to do */
out:
	{
		register struct proc *p = u.u_procp;
		if (p->p_cursig || ISSIG(p))
			psig();
		p->p_pri = p->p_usrpri;
		if (runrun) {
			/*
			 * Since we are u.u_procp, clock will normally just change
			 * our priority without moving us from one queue to another
			 * (since the running process is not on a queue.)
			 * If that happened after we setrq ourselves but before we
			 * swtch()'ed, we might not be on the queue indicated by
			 * our priority.
			 */
			(void) splclock();
			setrq(p);
			u.u_ru.ru_nivcsw++;
			swtch();
		}
		curpri = p->p_pri;
	}
	if (u.u_prof.pr_scale) {
		register int ticks;
		register struct timeval *tv = &u.u_ru.ru_stime;

		ticks = (tv->tv_sec - syst.tv_sec) * hz +
			(tv->tv_usec - syst.tv_usec) / tick;
		if (ticks)
			addupc(locr0[IAR], &u.u_prof, ticks);
	}

}
#undef u		/* prevent confusing following code */


/*
 * the register address map such that regs[regmap[0]] gets r0.
 */

char regmap[] = {
	R0, R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R15 };

/*
 * print the machine state
 * iar, mcs_pcs, ics_cs, general registers
 */
prstate(name, mcs_pcs, info, iar, ics_cs, regs)
	register char *name;
	register int mcs_pcs, info, ics_cs;
	register int *regs;
	short *iar;
{
	register int i;
	printf("%s: mcs_pcs=0x%b, info=0x%b, iar=0x%x, ics_cs=0x%b\n",
	    name, mcs_pcs, mcpcfmt,
	    info,"\20\1DATA\2PROTECTION\3TLB-SPECIFICATION\4PAGE-FAULT\5MULTIPLE\6EXTERNAL-DEVICE\7IPT-SPECIFICATION\10ROS-WRITE\12TLB-RELOAD\13CORRECTABLE-ECC",
	    iar, ics_cs, icsfmt);
#ifdef IBMRTPC
	csr_print(*(int volatile *)CSR);
#endif IBMRTPC
	if (regs) {
		for (i=0; i<16; ) {
			if ((i & 0x03) == 0)
				printf("R%x ... R%x ",i,i+3);
			prhex((char *) (regs + regmap[i]), sizeof (int));
			if ((++i & 0x03) == 0)
				printf("\n");
		}
#ifdef ROMPC
		if (cpu_model == CPU_ROMPC && regs[ECR_COUNT])
			{
			print_ecr(regs[ECR_COUNT], (int) &regs[EX1_CTL]);
#define PRINT(value) if (regs[EX_/**/value]) printf("value=%x ",regs[EX_/**/value])
			PRINT(GSR1);
			PRINT(REP1);
			PRINT(GSR2);
			PRINT(REP2);
			}
#endif ROMPC
	}
	printser();
}

prhex(ptr,n)
	register char * ptr;
	register int n;
{
/*
 * print "n" bytes pointed to by "ptr" in hex
 * we do this because the kernel printf isn't smart
 * enought to print out a specified number of digits.
 */
	while (--n >= 0) {
		putnibble(*ptr >> 4);
		putnibble(*ptr);
		++ptr;
	}
	printf(" ");
}

static
putnibble(n)
	register int n;
{
	printf("%c","0123456789abcdef"[n&0x0f]);
}

/*
 * nonexistent system call-- signal process (may want to handle it)
 * flag error if process won't see signal immediately
 * Q: should we do that all the time ??
 */
nosys()
{
	if (u.u_signal[SIGSYS] == SIG_IGN || u.u_signal[SIGSYS] == SIG_HOLD)
		u.u_error = EINVAL;
	psignal(u.u_procp, SIGSYS);
}


static
csr_print(csr)
register int csr;
{
#ifdef IBMRTPC
	printf("CSR=%b\n", csr,
			"\20\11PIO-PENDING\12PLANAR-BUSY\13CH-RESET-CAPTURED\14DMA-EXECPTION\15I/O-CHECK\16INV-OPERATION\17PROT-VIOLATION\20PIO-DMA\21DMA-ERR-CH8\22DMA-ERR-CH7\23DMA-ERR-CH6\24DMA-ERR-CH5\25DMA-ERR-CH3\26DMA-ERR-CH2\27DMA-ERR-CH1\30DMA-ERR-CH0\31PIO-ERR\33SYSTEM-ATTN\34SOFT-RESET\35POWER\37INTR-PENDING\40EXCEPTION");
	_csr = csr;		/* save it for later */
#endif IBMRTPC
	}


#include "../machine/fp.h"
#include "../machine/softint.h"

void
saveFP(savearea)
	FP_MACH * savearea;
{
#ifdef GPROF		/* following is to reduce interference with blt */
	register int *from = (int *) &machine;
	register int *to = (int *) savearea;
	register int count = 0;
	for (count=0; count <sizeof (FP_MACH)/4; ++count)
		*to++ = *from++;
#else
	*savearea = machine;
#endif
}


void
restoreFP(savearea)
	FP_MACH * savearea;
{
#ifdef GPROF		/* following is to reduce interference with blt */
	register int *from = (int *) savearea;
	register int *to = (int *) &machine;
	register int count = 0;
	for (count=0; count <sizeof (FP_MACH)/4; ++count)
		*to++ = *from++;
#else
	machine = *savearea;
#endif
}


void
slih6(dev, icscs, iar)
	register dev_t dev;
	register int icscs;
	register int iar;
{
	FP_MACH fpregs;

	saveFP(&fpregs);

	while (softlevel) {

		if (softlevel & SOFT_CLOCK) {
			softlevel &= ~SOFT_CLOCK;
			softclock(dev, icscs, iar);
		}
		if (softlevel & SOFT_NET) {
			softlevel &= ~SOFT_NET;
#ifdef INET
			softnet();
#endif
		}
		if (softlevel & SOFT_TTY) {
			softlevel &= ~SOFT_TTY;
			softTTY();
		}
#ifndef ATR
		if (softlevel & SOFT_KLS) {
			softlevel &= ~SOFT_KLS;
			softkls();
		}
#if NPSP > 0
		if (softlevel & SOFT_PSP) {
			softlevel &= ~SOFT_PSP;
			softpsp();
		}
#endif NPSP
#endif ATR 
		if (softlevel & ~SOFT_ALL) {
			printf("slih6: undefined bit(s) set in softlevel: %x\n",
			    softlevel);
			softlevel &= SOFT_ALL;
		}
	}
	restoreFP(&fpregs);
}


#define ON(x) (x) ? "1" : "0"


static
printser()
{
/* cannot use MMU_IOBASE because it doesn't return a value (DUMB!) */
	register int mmubase = MMUBASE; /* get base address */
	extern int ser;		/* use saved value from locore */
	register int reg;

/*	ser = ior((unsigned) mmubase + MMU_SER);	/* pick up the SER */
	printf("SER=%b ", ser,
	    "\20\1DATA\2PROTECTION\3TLB-SPECIFICATION\4PAGE-FAULT\5MULTIPLE\6EXTERNAL-DEVICE\7IPT-SPECIFICATION\10ROS-WRITE\12TLB-RELOAD\13CORRECTABLE-ECC\14STORAGE\15LOAD\16I/O-ADDRESS\17STORAGE-ADDRESS\20PMUC-NAKDN\21PMUC-NAKDA\22SEGMENT-VIOLATION");
	printf("SEAR=%x ", ior((unsigned) mmubase + MMU_SEAR));
	printf("TRAR=%x ", ior((unsigned) mmubase + MMU_TRAR));
	reg = ior((unsigned) mmubase + MMU_TCR);
	printf("TCR=%b HAT/IPT=%x\n", reg,
	    "\20\114K-PAGE\12RESERVED\13INTR-TLB-RELOAD\14INTR-CECC\15TLIPT\16RAS-DIAG\17ISPER\20V=R", reg & 0xff);
}


/*
 * addupc takes an 'iar' in the user text and finds the appropriate
 * cell in the profiling array and increments it. 
 * it replaces a version written in assembler on the vax.
 * it special cases the common values for pr_scale (1, 1/2, 1/4 etc)
 * which are done by shifts. the more general case is done via
 * upcmul which does a 32x32 bit product and drops the bottom 16 bits.
 */
static
addupc(iar, prof, ticks)
	register int iar;
	register struct uprof *prof;
	register int ticks;
{
	register int x = (iar - prof->pr_off) >> 1;
	register short *buf;

	if (x < 0)
		return(0);		/* not in range */
	switch(prof->pr_scale)
		{
	case 0x10000:
		break;
	case 0x8000:
		x >>= 1;
		break;
	case 0x4000:
		x >>= 2;
		break;
	case 0x2000:
		x >>= 3;
		break;
	case 0x1000:
		x >>= 4;
		break;
	case 0x800:
		x >>= 5;
		break;
	default:
		x = upcmul((unsigned) x, prof->pr_scale);
		break;
		}
	if (((unsigned)x<<1) >= prof->pr_size)
		return(0);		/* not in range */
	buf = prof->pr_base + x;
	if (isitok((unsigned) buf, sizeof (short), UWRITE_OK))
		buf[0] += ticks;
	else
		prof->pr_scale = 0;
	return(1);		/* worked (or cannot work) */
}

intzero(info, iar, ics_cs)
register int info;		/* not used */
register short *iar;
register int ics_cs;
{
	panic("<CTRL><ALT><SCROLL-LOCK> pressed; no debugger present");
}

/*
 * service level 2 interrupt.
 * possible causes are:
 * 1. iocc fault 
 * 2. mmu fault 
 * in case of iocc or mmu fault we panic (for now)
 */
inttwo(info, iar, ics_cs)
register int info;		/* not used */
register short *iar;
register int ics_cs;
{
#ifdef IBMRTPC
	register int csr = *(int *)CSR;
#endif IBMRTPC
	register int mcs_pcs = mfsr(SCR_MCSPCS);
	extern int ser;		/* saved ser */

	ser = ior((unsigned) MMUBASE + MMU_SER);	/* pick up the SER */
	iow((unsigned) MMUBASE + MMU_SER, 0);		/* clear the SER */

	prstate("int2", mcs_pcs, info, iar, ics_cs,
		(int *) 0);
#ifdef IBMRTPC
	if (csr & CSR_OHOH)
		panic("IOCC/PMUC exception");
#endif IBMRTPC
	if (ser & MMU_EX_OHOH)
		panic("MMU/PMUC level 2 interrupt");
	else
		panic("unknown level 2 interrupt");
}


/*
 * normally we don't get instruction page faults in kernel mode
 * there is one case where we do, as there is a problem with the
 * kernel floating point linkage, where we really want to generate
 * a	bali	r15, fpget
 * or	bali	r15, fpput
 * but can't because the linker doesn't have the capability to handle
 * such relocation on a global symbol. 
 * therefore we put in a 
 * bala	fpget + 0xff0000
 * which the loader is perfectly happy with. 
 * later when the instruction is executed we end up transfering 
 * control to that address, which is not in the program's
 * address space (this will have to change if we allow program's
 * to have more than 0xff0000 bytes of text space).
 * we then change the bala to an appropriate bali.
 * NOTE: this method requires that fpget and fpput be in the first 0x10000
 * of the kernel's low memory.
 */

int nfl_count;		/* number of nfl_fixups done */

nfl_fixup(mcs_pcs, info, locr0)
register int mcs_pcs;
register int info;
register int *locr0;
{
#ifdef NFL
	register unsigned addr = (locr0[R15]-4);
	register int phys;		/* physical memory address */
	register int offset;

	DEBUGF(nfldebug&0x01,
		prstate("nfl_fixup", mcs_pcs, info, locr0[IAR], locr0[ICSCS],locr0));
	if ((info & NFL_MASK) != NFL_ADDR)
		return(0);		/* not for us */

	/*
	 * check to make sure r15 contains a valid address (paranoia)
	 * and if that it points to a bala of the correct form
	 */
	if ((phys = vtop(addr)) < 0 || *(u_short *) addr != (((unsigned) NFL_INSTN)>>16))
		return(0);		/* oops, not a fixup case */

	/*
	 * now build a bali instruction to replace the original 
	 * bala. Then use a locore routine to store it with 
	 * translation off.
	 */
	offset = ((locr0[IAR] - NFL_ADDR) - (addr - SYSBASE)) >> 1;
	DEBUGF(nfldebug, printf("nfl_fixup: store_instn(%x,%x)\n", phys, BALI_R15 | (offset & BALI_MASK)));
	store_instn(phys, BALI_R15 | (offset & BALI_MASK));
	locr0[IAR] = addr;	/* back up and try it again! */
	++nfl_count;		/* remember how many times we did it */
	return(1);		/* done it! */
#else
	return(0);
#endif NFL
}

#ifdef ROMPC

static char *ec_length[] = { "c", "h", "", "ts" };
static char *ec_ops[] = { "l", "lm", "ior", "la", "st", "stm", "iow", "?" };
static char *ec_cancel[] = { "", "-" };

static
print_ecr(ecr_count,ecr_addr)
{
	if ((unsigned) ecr_count <= 2) {
		printf("exception stack(%d):",ecr_count);
		for (; ecr_count-- > 0; ecr_addr += 16) {
			int reg = * (unsigned short *)(ecr_addr+2);
			
			printf("\t%s%s%s r%d,%x ",
				ec_cancel[reg&1],	/* cancelled? */
				ec_ops[(reg>>3)&0x07],	/* storage operation */
				ec_length[(reg>>6)&0x03],	/* operation length */
				(reg>>8)&0x0f,		/* register number */
				* (int *)(ecr_addr+4)	/* address */
				);
			if (reg&(0x04<<3))
				printf("[%x] ", * (int *)(ecr_addr+8));	/* data value */
		}
		printf("\n");
	}
}

static char ioim[] = {
	1, 0, 0, 0, 		/* 0 1 2 3 */
	1, 0, 0, 0, 		/* 4 5 6 7 */
	0, 0, 0, 0, 		/* 8 9 A B */
	2, 2, 1, 1		/* C D E F */
};

#define ISLOCKED_68881() (ior(IOIM2+IOIM_SUB_PROB) & SEG_C) == 0 /* if 68881 locked */

/*
 * handle sticky cases where the second of certain multiple exceptions must be
 * either simulated or eliminated as the operation will have actually taken
 * place.
 */

static
apc_mux(locr0,info)
register int *locr0;
{
	int addr2 = locr0[EX2_ADDR];
	int ctl2 = locr0[EX2_CTL];
	int addr1 = locr0[EX1_ADDR];
	int ctl1 = locr0[EX1_CTL];

	DEBUGF(trdebug & SHOW_MUX,
		prstate("apc_mux", locr0[MCSPCS], locr0[INFO], locr0[IAR], locr0[ICSCS],locr0));
	if (GETSEG(addr2) == 0xf && ! IS_IOR_IOW(ctl2) &&
			(IS_IOCC(addr2) || IS_68881(addr2)) ) {
	/*
	 * If here, the newest exception (2nd one) is from the IOCC (IOIM1),
	 * or the MC68881 (IOIM2). 
	 * Check to see if the oldest exception was from the same channel
	 * as the newest. If it was, and it was not an IOR/IOW, then the 
	 * newest exception was rejected at the IOIM, and it was totally
	 * restartable.
	 * Note that if the first exception was an IOR, then this does not
	 * serialize the channel, so the 2nd exception would have gone thru.
	 */
		if (!IS_IOR_IOW(ctl1) &&
				ioim[GETSUBSEG(addr1)] == ioim[GETSUBSEG(addr2)]){
			/*
			 * both exceptions from same channel - serialized 
			 */
			DEBUGF(trdebug & SHOW_MUX2,printf("apc_mux: both ioim%d\n",ioim[GETSUBSEG(addr1)]));
			return(info);
		}
		if (IS_68881(addr2)) {
			/*
			 * we control access to the 68881 by "locking" it. This
			 * is done by turning off problem state access it its
			 * subsegments when we want to detect access to it.
			 * any "lock" exception to the 68881 is just an PMUC 
			 * exception and, as the 2nd exception, is totally
			 * restartable.
			 */
			if ((cpu_model == CPU_ROMPC) && !ISLOCKED_68881()) {
			/*
			 * check for Store/Load. Unconditionally discard Loads
			 * as the second exception in the stack for the 68881.
			 * Note that the test for a load as the 2nd exception 
			 * is only against the high order bit of the storage
			 * operation field in the exception control word.
			 * We have already verified that the second exception
			 * is not an IORead, and a LM is a serializing 
			 * instruction, so we should never see it as a second
			 * exception in the stack following a store. This leaves
			 * only Load and LHA which do serialize after a Store.
			 */
				if (IS_STORE(ctl1) && !IS_STORE(ctl2))
					discard_load(locr0);
				else
					/*
					 * 68881 is 2nd exception, 
					 * not Store/Load
					 * abort the 68881 unless the 68881
					 * is reporting a take exception
					 */
#ifdef MC881
					mc881_abort(locr0);
#else
					panic("mc881_abort");
#endif
			}
		} else {
#ifdef IBMRTPC
		/*
		 * the IOCC was the 2nd exception in the stack, and the first
		 * exception was from a different channel or was an IOR
		 */
			if ((_csr = *(int *)CSR) & 0x80000000) {
			/*
			 * bit 0 of the CSR is set indicating the IOCC reported
			 * the program check.
			 */
				* (int *) CSR = 0;	/* reset CSR */
				DEBUGF(trdebug & SHOW_MUX2,{
					printf("apc_mux: IOCC ");
					csr_print(_csr);
					});
			} else if (IS_STORE(ctl1) && !IS_STORE(ctl2) &&
					(locr0[EX_GSR1] & IOIM_PARITY_REC) == 0){
				discard_load(locr0);	/* throw away the load */
			}

#endif IBMRTPC
		}
	}
#ifdef SIM_CHG_BIT
/*
 * look for the case of two stores, which since they are not serialized
 * could show a protection exception which is due to the second packet
 * on the first. We explicitly check for this case and ignore the 
 * protection exception if it isn't on the first packet.
 * TODO: check to see if mode of access was kernel and user and figure 
 *	out what the K bit should have been in the segment register so
 *	that we can distinguish kernel faults from user faults.
 *	currently test_protection just checks for the page being read-only.
 */
	else if (IS_STORE(ctl1) && IS_STORE(ctl2) && (info & MMU_EX_KEYS) &&
			!test_protection(addr1) && test_protection(addr2)) {
	DEBUGF ((padebug & SHOW_PROTMUX), 
		prstate("apc_mux ex_keys", locr0[MCSPCS], locr0[INFO], locr0[IAR], locr0[ICSCS],locr0));
	info &= ~MMU_EX_KEYS;	/* ignore protection exception since it was on
				 * second packet */
	}
#endif SIM_CHG_BIT
	return(info);

}

static
discard_load(locr0)
register int *locr0;
{
	int ctl2 = locr0[EX2_CTL];
	int addr2 = locr0[EX2_ADDR];
	int reply = locr0[ioim[GETSUBSEG(addr2)] == 1 ? EX_REP1 : EX_REP2];	/* get reply */
	DEBUGF(trdebug & SHOW_MUX2,
		prstate("discard_load", locr0[MCSPCS], locr0[INFO], locr0[IAR], locr0[ICSCS],locr0));
	if (IS_CANCELLED(ctl2)) {
		DEBUGF(trdebug & SHOW_MUX2,printf("apc_mux: discard_load - cancelled\n"));
		return;		/* already cancelled */
	}
	/*
	 * normalize the PMUC Reply Register:
	 * for byte and halfword loads, the reply data is not kept right
	 * justified in the register, but rather its location is a
	 * function of the low 2 or 1 bit(2) of the address packets
	 * (respectively).
	 */
	switch(EX_GETLEN(ctl2)) {
	case EX_BYTE:
		reply = (reply >> (((~addr2) & 03) * 8)) & 0xff;
		break;
	case EX_HALF:
		reply = (reply >> (((~addr2) & 02) * 8)) & 0xffff;
		if (EX_GETOP(ctl2) == EX_LHA)
			reply = (reply<<16) >> 16;	/* extend sign */
		break;
	case EX_WORD:
		break;		/* already in correct place */
	case EX_TS:
		return;		/* no action for TSH */
	}
	DEBUGF(trdebug & SHOW_MUX2,printf("apc_mux: discard_load: reply=%x len=%s reg=%d\n",reply,ec_length[EX_GETLEN(ctl2)],EX_GETREG(ctl2)));
	/*
	 * Now, the reply data is normalized, and the Test and Set Halves
	 * have been screened.
	 * Cancel the Load, and update the context save area. 
	 */
	 locr0[R0 + EX_GETREG(ctl2)] = reply;	/* update proper register */
	 --locr0[ECR_COUNT];	/* get rid of this exception */
}

#endif ROMPC

static
check_apc_bug(locr0)
register int *locr0;
{
	int addr2 = locr0[EX2_ADDR];
	int ctl2 = locr0[EX2_CTL];
	int addr1 = locr0[EX1_ADDR];
	int ctl1 = locr0[EX1_CTL];

	if (GETSEG(addr1) == 0xf && GETSEG(addr2) == 0xf &&
		IS_FLOAT(addr1) && IS_FLOAT(addr2))
		if (addr1 == addr2 && ctl1 == ctl2 &&
				locr0[EX1_DATA] == locr0[EX2_DATA])
			{
			DEBUGF(1, printf("possible CPU fault:\n"));
			DEBUGF(1, printf("addr1=addr2=%x\n",addr1));
			DEBUGF(1, printf("ctl1=ctl2=%x\n",ctl1));
			DEBUGF(1, printf("EX1_DATA=EX2_DATA=%x\n",locr0[EX1_DATA]));
			return 1;
			}
	return 0;
}
