/* $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
 */

.file "locore.s"
.ident	"$Header: locore.s 1.22 90/10/01 $"
.text

/*
 * locore.s
 *	Machine dependent low-level kernel stuff.
 *
 * Mostly interrupt and trap handling.
 *
 * Very machine dependent.  Intel 80386 version.
 */

/* $Log:	locore.s,v $
 */

#include "assym.h"
#include "sys/asm.h"
#include "sys/cmn_err.h"

/*
 * Hardware interrupt handlers.
 *
 * These are pointed at thru a combination of the per-processor
 * Interrupt Descriptor Table (IDT) and Global Descriptor Table (GDT).
 *
 * There is one handler per SLIC bin.  Bins 1-7 are handled in a common
 * manner (the HW interrupts).  Bin 0 is special cased for SW interrupts.
 *
 * Bins 1-7 handle as follows:
 *	Save scratch registers.
 *	eax = bin#.
 *	Goto "dev_common".
 *
 * Dev_common:
 *	count device interrupt (except bin 7, used for clocks)
 *	Save entry IPL.
 *	Set up new IPL.
 *	Read vector from SLIC.
 *	Tell SLIC ok to accept another interrupt.
 *	Verify vector as valid.
 *	Call interrupt handler thru int_bin_table[].
 *	If returning to user mode, check for and handle redispatches
 *		(via falling into trap handler (T_SWTCH)).
 *	Else, disable interrupts, restore previous IPL, and return.
 *
 * Handlers are called with vector number as argument.  The bin #
 * information is *NOT* passed to the handler.
 *
 * All interrupts enter via interrupt-gates, thus SW must re-enable
 * interrupts at processor.  The main reason for interrupt-gates instead of
 * trap-gates is that the SLIC still yanks on the interrupt line until SW
 * tells SLIC it has the interrupt; thus if enter with trap-gate, it will
 * re-enter constantly and overflow the stack.  Also, other interrupts can
 * occur (eg, FPA) and can mess up %es,%ds assignments (since kernel
 * doesn't save/restore these explicitly, rather by "knowing" context).
 *
 * Interrupts save scratch registers in a "push-all" consistent order;
 * if need to handle re-dispatch, can push remaining registers and
 * behave like a trap.
 *
 * Thus after dev_common saves entry SPL, the stack looks like:
 *
 *	old SS, padded		only if inter-segment (user->kernel)
 *	old SP			only if inter-segment (user->kernel)
 *	flags
 *	CS, padded		sense user-mode entry from RPL field
 *	IP			return context
 *	EAX			scratch registers
 *	ECX			ditto
 *	EDX			more such
 *	old SPL			entry SLIC local mask
 *
 * Bin 0 (SW) interrupts are handled via reading the bin0 message register
 * and looping until we clear it out, calling an appropriate SW interrupt
 * handler for each bit:
 *
 *	Save scratch registers.
 *	spl1().
 *	ldata = SLIC Bin0 message data.
 *	ON processor ints.
 *	loop {
 *		BIT = FFS(ldata).
 *		If no bits set {
 *			OFF processor ints.
 *			spl0().
 *			restore registers.
 *			rett.
 *		}
 *		clear BIT in ldata.
 *		call SW handler(BIT).
 *		ldata |= SLIC Bin0 message data.
 *	}
 *
 * All entries clear "direction" flag, since C environment assumes this.
 *
 * Bin 0 interrupt handler uses an interrupt gate, to turn OFF processor
 * interrupts until ready to accept another bin0 (or higher) interrupt.
 */

#define	INTR_ENTER(bin) \
	pushl	%eax; \
	pushl	%ecx; \
	pushl	%edx; \
	movw	$KERNEL_DS, %ax; \
	movw	%ax, %ds; \
	movw	%ax, %es; \
	movl	$bin, %eax

	.text

ENTRY(bin1int)
	INTR_ENTER(1)			/ bin 1 interrupt.
	incl	VA_PLOCAL+L_CNT+V_INTR	/ count the device interrupt.
	jmp	dev_common		/ common handling.

ENTRY(bin2int)
	INTR_ENTER(2)			/ bin 2 interrupt.
	incl	VA_PLOCAL+L_CNT+V_INTR	/ count the device interrupt.
	jmp	dev_common		/ common handling.

ENTRY(bin3int)
	INTR_ENTER(3)			/ bin 3 interrupt.
	incl	VA_PLOCAL+L_CNT+V_INTR	/ count the device interrupt.
	jmp	dev_common		/ common handling.

ENTRY(bin4int)
	INTR_ENTER(4)			/ bin 4 interrupt.
	incl	VA_PLOCAL+L_CNT+V_INTR	/ count the device interrupt.
	jmp	dev_common		/ common handling.

ENTRY(bin5int)
	INTR_ENTER(5)			/ bin 5 interrupt.
	incl	VA_PLOCAL+L_CNT+V_INTR	/ count the device interrupt.
	jmp	dev_common		/ common handling.

ENTRY(bin6int)
	INTR_ENTER(6)			/ bin 6 interrupt.
	incl	VA_PLOCAL+L_CNT+V_INTR	/ count the device interrupt.
	jmp	dev_common		/ common handling.

ENTRY(bin7int)
	INTR_ENTER(7)			/ bin 7 interrupt.
	/ Don't count clock ticks.
	/ Optimize: clocks fall thru.

/*
 * Common handling for bin 1-7 interrupts.  EAX == bin # on entry.
 * Indexing on int_bin_table assumes the bin_header structure is
 * 8-bytes (quad-word).
 */
dev_common:
	movl	va_slic_lmask, %edx	/ EDX = address of slic mask register
	movb	(%edx), %cl		/ ECX = old SPL.  Save...
	movb	spltab(%eax), %ch	/ new SPL from table...
	movb	%ch, (%edx)		/	...masks bin and lower.
	movb	(%edx), %ch		/	...sync the write
	pushl	%ecx			/	...below scratch regs on stack.
	/ the low byte is the spl the high is our new spl
	movl	va_slic, %edx		/ EDX = address of slic base
	movzbl	SL_BININT(%edx), %ecx	/ ECX = vector # from message data.
	/ the cpu will block until the write complete.
	/ This will inssure the spl mask has been set.
	/ The following write may now take place before or after the "sti".
	/ The current SL_LMASK is the stronger of intended spl and the
	/ spl at the time od the interrupt. This simplefies the concerns when
	/ returning from interrupt later on.
	movb	$0, SL_BININT(%edx)	/ tell SLIC ok for more interrupts.
	sti				/ ok for more interrupts now.
	cld				/ in case intr'd code had it set.
	leal	int_bin_table(,%eax,8), %edx / EDX -> intr table for this bin.
	pushl	%ecx			/ argument = vector #.
/ interrupt table is fully allocated so don't need to check if valid intr
	movl	BH_HDLRTAB(%edx), %eax	/ EAX == base of vectors for this bin.
	CALL	*(%eax,%ecx,4)		/ call handler.
	addl	$4, %esp		/ clear stack.
intdone:
	testb	$RPL_MASK, INTR_SP_CS(%esp)	/ returning to user mode?
	je	intret			/ no -- don't worry about reschedule.
	cmpl	$0, VA_PLOCAL+L_RUNRUN	/ yup -- "my" runrun flag set?
	jne	doresched		/ yup -- involuntary switch.
	/*
	 * Really returning to user.  Must be careful to restore DS,ES
	 * after update SLIC (else can't address it!).
	 */
	cli				/ OFF processor interrupts.
	popl	%eax			/ entry SLIC local mask (SPL).
	movl	va_slic_lmask, %edx	/ EDX = address of slic mask register
	movb	%al, (%edx)		/ restore entry SPL.
	movb	(%edx),%al		/ sync+2
	movw	$USER_DS, %ax		/ restore...
	movw	%ax, %ds		/	...user DS
	movw	%ax, %es		/		...user ES.
intret0:
	popl	%edx			/ restore...
	popl	%ecx			/	...scratch
	popl	%eax			/		...registers.
	iret				/ return to user from interrupt.
/*
 * Interrupt return to kernel.  Don't fuss with DS, ES.
 * Since the SLIC mask is now gaurenteed to be stronger that the saved
 * spl we no loner need to worry when it takes affect with respect to the
 * iret.
 */
intret:
	cli				/ OFF processor interrupts.
	popl	%eax			/ entry SLIC local mask (SPL).
	/ %al is the old mask, %ah is the current mask.
	/ It is possible that the saved mask was set to a higher value than
	/ that we are currently running at. This would happen for example if
	/ we had just written to the slic mask and within 500 ns the interrupt
	/ that we are returning from occured.
	/ In this case we must be careful that we don't hit the iret
	/ without inssuring that the mask has been set.
	/
	cmpb	%al,%ah			/ going back to higher SPL?
	je	intret0			/ same level do nothing
	movl	va_slic_lmask, %edx	/ EDX = address of slic mask register
	jb	intret1			/ going to higher spl
	movb	%al, (%edx)		/ restore entry SPL.
	popl	%edx			/ restore...
	popl	%ecx			/	...scratch
	popl	%eax			/		...registers.
	iret				/ return from interrupt.
intret1:
	movb	%al, (%edx)		/ restore entry SPL.
#ifdef	MODELD
	/ Not real sure the following code actually works.
	/ Believe the race is receiving an interrupt while actually
	/ writing the lmask register.  If an interrupt is coming in at
	/ the time, it doesn't matter what the register value is.
	/ The slic has accepted the interrupt and must now deliver it
	/ to the processor.
	/ In any event, the "cmp, ..., iret" sequence must be long
	/ enough to close the slic interrupt latency window.
	/ For i386, there are 41 cycles between the "movb" and the
	/ iret.  For the i486, this is reduced to 30 cycles.
	/ This means that the following code is OK up to 45Mhz on a i486.
#endif	/* MODELD */
intret2:
	movb	(%edx), %ah
	cmpb	%ah,%al
	jne	intret2
	popl	%edx			/ restore...
	popl	%ecx			/	...scratch
	popl	%eax			/		...registers.
	iret				/ return from interrupt.

/*
 * spltab[]
 *	Maps bin # to IPL value to put in SLIC local-mask register.
 *
 * spltab[i] masks interrupts `i' and lower priority.
 */
	.data
	.align	4
spltab:
	.byte	SPL1			/ [0]
	.byte	SPL2			/ [1]
	.byte	SPL3			/ [2]
	.byte	SPL_HOLE		/ [3]
	.byte	SPL4			/ [4]
	.byte	SPL5			/ [5]
	.byte	SPL6			/ [6]
	.byte	SPL7			/ [7]

/*
 * Undefined SW trap handler.
 */
ENTRY(swt_undef)
	pushl	$swtundef		/ panic message
	pushl	$CE_PANIC
	CALL	cmn_err			/ "Undefined software trap"
	/*
	 *+ An undefined software trap has occurred.
	 *+ This implies a possible hardware or kernel software
	 *+ problem.
	 */
	/addl	$4, %esp		/ not really
	/RETURN				/ not really

	.data
swtundef:
	.string	"Undefined software trap"
	.text

/*
 * Arrange to redispatch (call trap() with simulated T_SWTCH type code).
 * At entry here, stack is "standard" interrupt stack, including saved SPL.
 * Must complete "push-all" and otherwise behave like a trap.
 *
 * Already executing using KERNEL_DS.
 */
	.align	4
doresched:
	addl	$4, %esp		/ pop interrupt SPL (should be SPL0).
	pushl	%ebx			/ save...
	subl	$4, %esp		/		(dummy "SP")
	pushl	%ebp			/	...other
	pushl	%esi			/		...registers
	pushl	%edi			/			...ala "pushall"
	pushl	$T_SWTCH		/ "switch" trap-type.
	movl	va_slic_lmask, %edx
	movb	$SPL0, (%edx)		/ new value == SPL0.
	jmp	trap_common

/*
 * Bin0 (SW) interrupt handler.  Entered thru interrupt gate, thus
 * interrupts masked at processor.
 *
 * Called routines must *not* redispatch; they must behave as interrupts.
 */
ENTRY(bin0int)
	pushl	%eax			/ save...
	pushl	%ecx			/	...scratch
	pushl	%edx			/		...registers.
	movw	$KERNEL_DS, %ax		/ establish...
	movw	%ax, %ds		/	...kernel DS
	movw	%ax, %es		/		... kernel ES.
	/ was SPL_ASM($SPL1,%al) but to set SPL to mask 0.
	/ But slic mask may now be greater tahn spl0 due to sychronsation
	/ slippage, so add spl1 to what it currently is.
	movl	va_slic_lmask, %edx		/ get slic mask address
	movb	(%edx), %al		
	movb	$SPL1, %ah 		/ store the new mask for int_ret
	movb	%ah, (%edx)
#ifdef MODELD
	movb	(%edx), %ah		/ Dummy read-sync write
	movl	plocal_slic_delay, %edx	/ Wait for slic_lmask to be written
	movl	(%edx), %edx
0:	subl	$1, %edx
	jg	0b
#endif /* MODELD */
	pushl	%eax			/ save entry SPL (should be SPL0).
	movl	va_slic, %edx		/ get slic address
	movzbl	SL_B0INT(%edx), %ecx	/ ECX = Bin0 message data (mask).
	sti				/ ON processor interrupts.
#ifdef	MODELD
	/ It's important to delay calling the software interrupt handler
	/ until the lmask latency window has closed.  On a i386, the
	/ above sequence to the call takes a minimium of 44 ticks.
	/ On a i486, the same sequence takes 36 ticks and works for i486's
	/ up 55Mhz.
#endif	/* MODELD */
	cld				/ in case intr'd code had it set.
0:	bsfl	%ecx, %eax		/ Find software trap bit
	je	intdone			/ no bit ==> done.
	btrl	%eax, %ecx		/ clear soft interrupt bit.
	pushl	%ecx			/ save remaining interrupt data.
	CALL	*softvec(,%eax,4)	/ call soft interrupt routine.
	popl	%ecx			/ restore interrupt data
	movl	va_slic, %edx		/ get slic address
	orb	SL_B0INT(%edx), %cl	/ CL |= Bin0 message data (mask)
	jmp	0b			/ repeat until no bits set.

/*
 * Unconditionally configured SW interrupt handlers.
 */

/*
 * reched()
 *	Set self `runrun' to cause re-dispatch.
 */
ENTRY(resched)
	movl	$1, VA_PLOCAL+L_RUNRUN	/ Set runrun flag
	RETURN

/*
 * undef()
 *	No such.  Somebody goofed.
 */
ENTRY(undef)
	pushl	$undefmsg		/ panic message
	pushl	$CE_PANIC
	CALL	cmn_err			/ "Undefined software interrupt"
	/*
	 *+ An undefined software interrupt has occurred.
	 *+ This implies either a hardware or kernel software
	 *+ problem.
	 */
	/addl	$4, %esp		/ not really
	/RETURN				/ not really

	.data
undefmsg:
	.string	"Undefined software interrupt"
	.text

/*
 * splx(newmask)
 *	Lower interrupt priority mask back to previous value.
 *
 * DEBUG version checks that mask is actually "lowering" -- ie, staying the
 * same or enabling more interrupts.  Non-debug version is in-line expanded
 * (see machine/intctl.h).
 *
 * Since 1's in the mask enable interrupts, (oldmask & newmask) == oldmask is ok.
 */

#ifdef	DEBUG
ENTRY(splx)
	pushl	%ebp				/ save old frame pointer
	movl	%esp, %ebp			/ and set up new one
	cmpb	$SYSTYP_S1, arch_type		/ check machine type
	jne	1f
						/ PC/3AT version
	inb	$0x21				/ get old mask
	jmp	.+2				/ for I/O recovery time
	movb	%al, %cl			/ and save it
	movb	BPARG0, %al			/ get new mask
	movzbl	%al, %eax			/ clear upper 3 bytes of %eax
	movb	slic_to_pic(%eax), %al		/ convert SLIC mask to PIC mask
	outb	$0x21				/ write new mask
	jmp	.+2				/ for I/O recovery time
	andb	%al, %cl			/ %cl = (new & old)
	cmpb	%al, %cl			/ ok if (new & old) == new
	jne	9f				/ not ok -- down in flames
	leave
	RETURN
						/ SB8000 version
1:	movl	va_slic_lmask, %ecx		/ get slic mask address
	movb	(%ecx), %ah			/ old mask value from SLIC.
	movb	BPARG0, %al			/ new mask.
	movb	%al, (%ecx)			/ install new mask.
	andb	%ah, %al			/ %al = old & new.
	cmpb	%ah, %al			/ ok if (old & new) == old.
	jne	9f				/ not ok -- down in flames.
	leave					/ reset frame
	RETURN					/ ok -- done.
9:	pushl	$8f				/ bad news.
	pushl	$CE_PANIC
	CALL	cmn_err				/ really bad news.
8:	.string	"splx: bad nesting"
#endif	/* DEBUG */

/*
 * v_lock(laddr, retipl)
 *	lock_t	*laddr;
 *	spl_t	retipl;
 *
 * Low-level release a lock and drop spl to retipl.
 *
 * On a SLIC_GATES kernel, ok to just write on the variable,
 * since only writes on lock are from holding process or behind SLIC gate.
 *
 * On a memory/cache based kernel, must use locked operation since
 * compatibility mode cache (or no cache) uses bus lock line;
 * just writing the variable can race with an attempt to acquire the gate.
 */

#ifdef	DEBUG
ENTRY(v_lock)
	pushl	%ebp				/ save old frame pointer
	movl	%esp, %ebp			/ and set up new one
	cmpb	$SYSTYP_S1, arch_type		/ check machine type
	je	1f
	movl	BPARG0, %ecx			/ get &lock
#if defined(SLIC_GATES)
	movb	$L_UNLOCKED, (%ecx)		/ release the lock
#else
	movb	$L_UNLOCKED, %al		/ get unlock value in register
	xchgb	%al, (%ecx)			/ release the lock
#endif	/* SLIC_GATES */
1:	pushl	BPARG1				/ push argument spl
	CALL	splx
	leave					/ reset frame
	RETURN
#endif	/* DEBUG */

/*
 * Processor Trap Handlers.
 *
 * There are two forms of trap entry -- those that push an error code
 * and those that don't.
 *
 * TRAP_ENTER_NOERR assumes no trap error code, no need to pop it.
 * TRAP_ENTER_ERR assumes HW pushed the error code, thus pops it into
 *	l.trap_err_code.
 *
 * All entries are thru trap gates, thus interrupts are on at entry.
 *
 * No save/restore of SPL -- code is supposed to nest this properly.
 */

#define	TRAP_ENTER_NOERR(type) \
	pushal; \
	movw	$KERNEL_DS, %ax; \
	movw	%ax, %ds; \
	movw	%ax, %es; \
	pushl	$type

#define	TRAP_ENTER_ERR(type) \
	movw	%cs:kernel_ds, %ds; \
	movw	%cs:kernel_ds, %es; \
	popl	VA_PLOCAL+L_TRAP_ERR_CODE; \
	pushal; \
	pushl	$type

/*
 * TRAP_ENTER_ERR must load a value for kernel DS, but need to use
 * it (to pop error code) before can load thru a register.
 */

kernel_ds:.value	KERNEL_DS

/*
 * T_DIVERR -- integer divide error.  No error code.
 */
ENTRY(t_diverr)
	TRAP_ENTER_NOERR(T_DIVERR)
	jmp	trap_common

/*
 * T_DBG -- debug trap/fault, single step, etc.  No error code.
 */
ENTRY(t_dbg)
	TRAP_ENTER_NOERR(T_DBG)
	jmp	trap_common

/*
 * T_NMI -- Non-Maskable Interrupt.  No error code.
 * Entered thru interrupt gate (interrupts disabled).
 *
 * If probe_nmi == NULL, handle as trap (which will panic the system).
 * Else fall through to kp. (80386 disables NMI's until an iret is executed).
 * The "iret" removes the NMI stack frame.
 */
#ifndef	KERNEL_PROFILING

ENTRY(t_nmi)
	TRAP_ENTER_NOERR(T_NMI)
	movl	probe_nmi, %eax		/ probe_nmi procedure, or NULL.
	cmpl	$0, %eax		/ probing?
	je	trap_common		/ no -- a real NMI.
#else	/* KERNEL_PROFILING */
	.globl	kp_nmi
ENTRY(t_nmi)
	/*
	 * TRAP_ENTER_NOERR expanded and rearranged inline to allow
	 * for pushing the ds and es regs.
	 */
	pushal
	pushl	$T_NMI
	pushl	%ds
	pushl	%es
	movw	$KERNEL_DS, %ax
	movw	%ax, %ds
	movw	%ax, %es

	movl	probe_nmi, %eax		/ probe_nmi procedure, or NULL.
	cmpl	$0, %eax		/ probing?
	jne	2f			/ yes

	cld				/ in case trap'd code had it set.
	CALL	kp_nmi			/ assume profiler NMI
	testl	%eax, %eax		/ was it really?
	je	1f			/ yes -- return from trap
	addl	$8, %esp		/ "pop" the pushed ds and es
	jmp	trap_common

1:
	cli				/ intr should be off, but be sure.
	popl	%es
	popl	%ds
	addl	$4, %esp		/ "pop" trap type.
	popal				/ restore interrupted registers.
	iret				/ back from whence we came.
2:
	addl	$8, %esp		/ "pop" the pushed ds and es
#endif	/* KERNEL_PROFILING */
	addl	$4, %esp		/ "pop" trap type.
	movl	%eax, SP_EIP(%esp)	/ alter return IP to probe_nmi function.
	popal				/ restore all regs.
	iret				/ "jump" to probe-NMI handler.

	.data
	.globl	probe_nmi
probe_nmi:
	.long	0
	.text

/*
 * T_INT3 -- single-byte interrupt (breakpoint).  No error code.
 */
ENTRY(t_int3)
	TRAP_ENTER_NOERR(T_INT3)
	jmp	trap_common

/*
 * T_INTO -- interrupt on overflow.  No error code.
 */
ENTRY(t_into)
	TRAP_ENTER_NOERR(T_INTO)
	jmp	trap_common

/*
 * T_CHECK -- array bounds check.  No error code.
 */
ENTRY(t_check)
	TRAP_ENTER_NOERR(T_CHECK)
	jmp	trap_common

/*
 * T_UND -- undefined/illegal op-code.  No error code.
 */
ENTRY(t_und)
	TRAP_ENTER_NOERR(T_UND)
	jmp	trap_common

/*
 * T_DNA -- device not available (FPU).  No error code.
 */
ENTRY(t_dna)
	TRAP_ENTER_NOERR(T_DNA)
	jmp	trap_common

/*
 * T_SYSERR -- System error (double fault).  Zero value error code.
 */
ENTRY(t_syserr)
	TRAP_ENTER_ERR(T_SYSERR)
	jmp	trap_common

/*
 * T_RES -- Reserved trap entry.  Serious Problem.
 *
 * This is entered via interrupt-gate (interrupts masked at processor).
 * Use "splhi()" to insure interrupts can't be turned on; panic printf's
 * will re-enable processor interrupts due to "v_gate()".
 *
 * This is used in all otherwise unused slots in the IDT.  Thus it catches
 * bogus interrupt vectors from the hardware.
 */
ENTRY(t_res)
	TRAP_ENTER_NOERR(T_RES)
	SPL_ASM($SPLHI,%bl)			/ %bl = splhi()
	sti					/ processor now allows int's.
	jmp	trap_common

/*
 * T_BADTSS -- Invalid Task-State Segment.  Selector error code.
 */
ENTRY(t_badtss)
	TRAP_ENTER_ERR(T_BADTSS)
	jmp	trap_common

/*
 * T_NOTPRES -- Segment/gate not present.  Selector error code.
 */
ENTRY(t_notpres)
	TRAP_ENTER_ERR(T_NOTPRES)
	jmp	trap_common

/*
 * T_STKFLT -- Stack fault.  Selector or zero error code.
 */
ENTRY(t_stkflt)
	TRAP_ENTER_ERR(T_STKFLT)
	jmp	trap_common

/*
 * T_GPFLT -- General protection fault.  Selector or zero error code.
 */
ENTRY(t_gpflt)
	TRAP_ENTER_ERR(T_GPFLT)
	jmp	trap_common

/*
 * T_COPERR -- Math co-processor error (FPU).  No error code.
 */
ENTRY(t_coperr)
	TRAP_ENTER_NOERR(T_COPERR)
	jmp	trap_common

/*
 * t_fpa -- FPA exception.  This is actually an interrupt.
 *
 * Enter via interrupt gate so interrupts are disabled.
 * Must read FPA process context register (PCR), then mask all exceptions
 * and insure this is sync'd, then finally enable interrupts and call
 * fpa_trap() to do the dirty work.
 */
ENTRY(t_fpa)
	/*
	 * Enter much like bin0int.
	 */
	pushl	%eax			/ save...
	pushl	%ecx			/	...scratch
	pushl	%edx			/		...registers.
	movw	$KERNEL_DS, %ax		/ establish...
	movw	%ax, %ds		/	...kernel DS
	movw	%ax, %es		/		... kernel ES.
	movl	va_slic_lmask, %ecx	/ get slic mask address
	movb	(%ecx), %al		/ %al = entry SPL.
	movb	$SPL0, %ah		/ for intdone
	pushl	%eax			/ save entry SPL.
	/*
	 * Read FPA PCR, then mask all exceptions.
	 */
	movl	VA_FPA+FPA_STCTX, %ecx			/ %ecx = FPA PCR
	movl	$FPA_PCR_EM_ALL, VA_FPA+FPA_LDCTX	/ mask all exceptions.
	movl	VA_FPA+FPA_STCTX, %edx			/ synch the above write.
	/*
	 * Now can re-enable interrupts and call real FPA trap handler.
	 * Once re-enable processor interrupts, can take SLIC interrupt.
	 * Note that SLIC interrupt goes first if FPA and SLIC arrive
	 * at processor simultaneously.
	 */
	sti				/ interrupts ON again.
	pushl	%ecx			/ call it with nasty PCR.
	CALL	fpa_trap		/ poke at process.
	popl	%ecx			/ clear stack.
	jmp	intdone			/ all done.

/*
 * T_PGFLT -- Page fault.  Page-fault error code.
 *
 * Chip bug (thru at least B1's) makes page-fault error code unreliable.
 * Thus, don't use and optimize by clearing stack and use faster trap entry.
 *
 * If MFG or DEBUG kernel, always use the C trap() code.
 * Otherwise, do it faster if possible.
 */
ENTRY(t_pgflt)
	addl	$4, %esp			/ clear bogus PFEC off stack.
	/*
	 * Try to "fast-path".  Save only scratch registers.
	 * If process is being profiled or should swap itself, or the
	 * faulted address isn't in the user-address-space, use trap().
	 * Else call pagein() directly.
	 */
	pushl	%eax			/ save...
	pushl	%ecx			/	...scratch
	pushl	%edx			/		...registers.
	movw	$KERNEL_DS, %ax		/ establish...
	movw	%ax, %ds		/	...kernel DS
	movw	%ax, %es		/		... kernel ES.
	movl	%cr2, %eax		/ faulted address.
	cld				/ in case trap'd code had it set.
	cmpl	$0, VA_UAREA+U_PROF+PR_SCALE	/ process being profiled?
	jne	pgflt_use_trap		/ yup -- use trap().
	movl	VA_UAREA+U_PROCP, %edx	/ is process...
	testl	$SFSWAP, P_FLAG(%edx)	/	...supposed to swap?
	jnz	pgflt_use_trap		/ yup - use trap().
#if	USOFF != 0
	subl	$USOFF, %eax		/ adjust address to user relative.
#endif
	cmpl	$[USER_SPACE-UBYTES], %eax	/ <= top of user space?
	jnb	pgflt_use_trap2		/ nope -- use trap() (note: unsigned).
	pushl	%eax			/ and pass to pagein().
	CALL	pagein			/ returns true if succeed, else false.
	testl	%eax, %eax		/ pagein() was ok?
	jz	pgflt_use_trap1		/ nope -- use trap().
	addl	$4, %esp		/ pop fault address.
	/*
	 * pagein() was successful.  Restore process priority in case
	 * process blocked inside pagein().  This does:
	 *	p->p_pri = p->p_usrpri;
	 *	l.eng->e_pri = p->p_pri;
	 *	SLICPRI(l.eng->e_pri>>2);
	 * from trap().
	 */
	movl	VA_UAREA+U_PROCP, %edx	/ u.u_procp
	movb	P_USRPRI(%edx), %al	/ u.u_procp->p_usrpri
	movb	%al, P_PRI(%edx)	/ u.u_procp->p_pri = u.u_procp->p_usrpri
	movl	VA_PLOCAL+L_ENG, %edx	/ &engine[me]->e_pri = ...
	movb	%al, E_PRI(%edx)	/		new process' prio.
	shlb	$1, %al			/ (pri / 4) << 3, for SLIC sl_ipl.
	movl	va_slic, %edx		/ get slic base address
	movb	%al, SL_IPL(%edx)	/ new interrupt arbitration prio.
	/*
	 * Bump stats and exit via intdone by pushing "false" SPL0 on stack.
	 * Could optimize a bit more and expand "intdone" here (without the
	 * SPL0 fuss), but prefer to keep the complicated code in one place.
	 */
	incl	VA_PLOCAL+L_CNT+V_FAULTS / count the fault.
	incl	VA_PLOCAL+L_CNT+V_TRAP	/ and the trap.
	movb	$SPL0, %al
	movb	%al, %ah
	pushl	%eax			/ now frame looks like interrupt.
	jmp	intdone			/ and return.
	/*
	 * For some reason, can't use fast path.  Finish a "trap" stack frame
	 * and fall into normal trap code.  Faulting address is in %eax.
	 * Land at pgflt_use_trap1 if pagein() failed.  Recovers user fault
	 * address from stack (pagein() modified to beginning of page).
	 */
pgflt_use_trap1:
	popl	%eax			/ pagein() failed -- recover user addr.
pgflt_use_trap2:
#if	USOFF != 0
	addl	$USOFF, %eax		/ un-user-relocate.
#endif
pgflt_use_trap:
	movl	%eax, VA_PLOCAL+L_TRAP_ERR_CODE	/ save for trap().
	pushl	%ebx			/ save...
	subl	$4, %esp		/		(dummy "SP")
	pushl	%ebp			/	...other
	pushl	%esi			/		...registers
	pushl	%edi			/			...ala "pushall"
	pushl	$T_PGFLT		/ "page-fault" trap-type.
	/jmp	trap_common		/ optimize: fall thru.

/*
 * Common code for processor traps.  Call trap(type).
 * Entry code already pushed trap type.
 */
trap_common:
	cld				/ in case trap'd code had it set.
	CALL	trap			/ trap(type)
	addl	$4, %esp		/ clear off traptype
	testb	$RPL_MASK, SP_CS(%esp)	/ going back to user mode?
	je	9f			/ Nope -- avoid seg-reg fuss.
	cli				/ restoring ds, es can't reenter!
	movw	$USER_DS, %ax		/ restore...
	movw	%ax, %ds		/	...user-mode DS
	movw	%ax, %es		/		...user-mode ES.
9:	popal				/ restore interrupted registers.
	iret				/ back from whence we came.

/*
 * System-call entry points.  Handled much like traps, except call
 * syscall() instead of trap() (to avoid indirection).  Also, various
 * optimizations on exit based on knowledge we 'must' have come from
 * user-mode.
 *
 * Different entry for each number of arguments.
 *
 * SPL is properly nested at syscall exit, thus can avoid re-program of
 * SL_LMASK (eg, "must" have entered at SPL0, and it "must" be that now
 * since about to return to user mode ==> no need to reprogram).
 *
 * Don't check "runrun" for redispatch here, since syscall() already
 * did that before returning.
 */

/*
 * Macros to handle syscall entry, exit.
 *
 * SYS_ENTER saves user general registers, copies arguments as appropriate,
 * invokes syscall handler.  Also clear "direction" flag since user code
 * have set it, and C-environment assumes it's clear.
 *
 * SYS_CALL checks validity of syscall handler encoded in syscall number,
 * and calls the syscall handler.  Assumes %eax is unchanged from entry value
 * (ie, SYS_ENTER and arg-copying doesn't modify it).
 *
 * All entries are thru trap gates, thus interrupts are on at entry.
 *
 * On K20, SYS_EXIT should check SLIC write-buffer to insure NMI taken
 * in user mode isn't really a system panic.  This is ignored for
 * K20 since it's not production.
 *
 * All registers are pushed before calling syscall/trap, thus syscall()
 * and trap() frames may differ.
 */

#define	SYS_ENTER(nargs) \
	pushal; \
	cld; \
	movw	$KERNEL_DS, %bx; \
	movw	%bx, %es; \
	movw	%bx, %ds; \
	SYS_COPY/**/nargs

#define	SYS_CALL \
	shrl	$16, %eax; \
	cmpl	syscall_nhandler, %eax; \
	jae	9f; \
	imull	$SCALLH_ENT_SZ, %eax, %ecx; \
	CALL	*syscall_handler+SCALLH_HDLR(%ecx)

#define	SYS_EXIT \
	movw	$USER_DS, %ax; \
	cli; \
	movw	%ax, %ds; \
	movw	%ax, %es; \
	popal; \
	iret

#define	SYS_BAD \
9:	CALL	nosyscall; \
	int	$3

#define	SYSCALL(nargs)	SYS_ENTER(nargs); SYS_CALL; SYS_EXIT; SYS_BAD

/*
 * Macros to copy appropriate number of parameters.
 * Assume ES and DS set up for kernel data.
 *
 * Single-arg syscall passes value in ECX.  Two arg syscall passes in
 * ECX, EDX.  All others pass pointer to args in ECX (so libc procedure
 * doesn't need to save/restore ESI).
 *
 * SYS_COPY_ARGS() assumes USER_ADDR_MASK is 2**n-1, thus can "and".  If
 * this changes (see machine/mc_vmparam.h, machine/genassym.c), must change
 * SYS_COPY_ARGS().
 */

#define	SYS_COPY_ARGS(n)	\
	movl	$VA_UAREA+U_ARG, %edi; \
	andl	$USER_ADDR_MASK, %ecx; \
	leal	VA_USER(%ecx), %esi; \
	movl	$n, %ecx; \
	rep;	smovl

#define	SYS_COPY0			/* NOP */

#define	SYS_COPY1 \
	movl %ecx, VA_UAREA+U_ARG

#define	SYS_COPY2 \
	movl %ecx, VA_UAREA+U_ARG; \
	movl %edx, VA_UAREA+U_ARG+4

#define	SYS_COPY3 \
	movl	$sys_copy_err, VA_UAREA+U_FLTADDR; \
	SYS_COPY_ARGS(3); \
	movl	$0, VA_UAREA+U_FLTADDR

#define	SYS_COPY4 \
	movl	$sys_copy_err, VA_UAREA+U_FLTADDR; \
	SYS_COPY_ARGS(4); \
	movl	$0, VA_UAREA+U_FLTADDR

#define	SYS_COPY5 \
	movl	$sys_copy_err, VA_UAREA+U_FLTADDR; \
	SYS_COPY_ARGS(5); \
	movl	$0, VA_UAREA+U_FLTADDR

#define	SYS_COPY6 \
	movl	$sys_copy_err, VA_UAREA+U_FLTADDR; \
	SYS_COPY_ARGS(6); \
	movl	$0, VA_UAREA+U_FLTADDR

/*
 * The syscall handlers.
 */

ENTRY(t_svc0)
	SYSCALL(0)			/ 0-argument system call.

ENTRY(t_svc1)
	SYSCALL(1)			/ 1-argument system call.

ENTRY(t_svc2)
	SYSCALL(2)			/ 2-argument system call.

ENTRY(t_svc3)
	SYSCALL(3)			/ 3-argument system call.

ENTRY(t_svc4)
	SYSCALL(4)			/ 4-argument system call.

ENTRY(t_svc5)
	SYSCALL(5)			/ 5-argument system call.

ENTRY(t_svc6)
	SYSCALL(6)			/ 6-argument system call.

/*
 * On copy error (bad fault), come here.
 * Assumes stack is ready to "SYS_EXIT".
 *
 * Could post SIGSEGV to process instead of just fail syscall.
 */
sys_copy_err:
	movl	$EFAULT, SP_EAX(%esp)		/ return EAX = EFAULT.
	orl	$FLAGS_CF, SP_FLAGS(%esp)	/ set carry-flag (error).
	movl	$0, VA_UAREA+U_FLTADDR		/ clear fault recovery address.
	SYS_EXIT				/ return error to user.

/*
 * Call Gate entry for normal system calls (using BCS).
 */

ENTRY(sys_call)
	pushal					/ save all registers

	pushfl					/ write EFLAGS on stack
	popl	%eax				/ bcause `lcall' does not push
	movl	%eax, SP_FLAGS(%esp)		/ flags (segment desc has arg 
						/ cnt set to 1 to allocate it).

	cld					/ zap in case it was set
	movw	$KERNEL_DS, %ax			/ load up kernel segment regs
	movw	%ax, %es
	movw	%ax, %ds

	CALL	systrap				/ handle system call

	movw	$USER_DS, %ax			/ restore user segment regs
	cli					/ ints off
	movw	%ax, %es
	movw	%ax, %ds
	popal					/ restore registers
	iret					/ return from interrupt.

/*
 * Call Gate entry for signal cleanup (using BCS).
 */

ENTRY(sig_clean)
	pushal					/ save all registers

	pushfl					/ write EFLAGS on stack
	popl	%eax				/ bcause `lcall' does not push
	movl	%eax, SP_FLAGS(%esp)		/ flags (segment desc has arg 
						/ cnt set to 1 to allocate it).

	cld					/ zap in case it was set
	movw	$KERNEL_DS, %ax			/ load up kernel segment regs
	movw	%ax, %es
	movw	%ax, %ds

	CALL	sigclean			/ handle system call

	movw	$USER_DS, %ax			/ restore user segment regs
	cli					/ ints off
	movw	%ax, %es
	movw	%ax, %ds
	popal					/ restore registers
	iret					/ return from interrupt.
