/*
 *  This file is part of ixemul.library for the Amiga.
 *  Copyright (C) 1991, 1992  Markus M. Wild
 *  Portions Copyright (C) 1994 Rafael W. Luebbert
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public
 *  License along with this library; if not, write to the Free
 *  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  $Id: trap.s,v 1.1 1994/06/19 15:17:35 rluebbert Exp $
 *
 *  $Log: trap.s,v $
 *  Revision 1.1  1994/06/19  15:17:35  rluebbert
 *  Initial revision
 *
 */

	.globl	_trap_00
	.globl	_restore_00
	.globl	_sup00_do_sigresume
	.globl	_sup00_do_sigreturn
	.globl	_sup00_do_sigreturn_ssp
	.globl	_trap_20
	.globl	_restore_20
	.globl	_sup20_do_sigresume
	.globl	_sup20_do_sigreturn
	.globl	_sup20_do_sigreturn_ssp
	.globl	_supervisor
	.globl	_do_sigreturn
	.globl	_launch_glue
	.globl	_addupc
	.globl	_resetfpu
	.globl	_install_vector
	.globl	_restore_vector
	.globl  _vector_install_count
	.globl	_vector_old_pc
	.globl	_vector_nop

#undef DEBUG

	| This is the trap processing function for the mc68000.
	| Things are quite easy here, just save the general purpose registers
	| and the pc/sr combo, call trap(), then restore the previous
	| context and return
_trap_00:
	movel	a5,sp@-		| need a scratch register
	movel	usp,a5		| get usp
	movel	a5,a5@(-10)	| store usp
	lea	a5@(-10),a5	| make room for sr, pc and usp.
	moveml	d0-d7/a0-a6,a5@-| store registers on usp
	movel	sp@+,a5@(0x34)	| insert the saved a5 into the saveset
	movel	sp@+,d1		| remember trapnumber
	movew	sp@+,a5@(0x44)	| copy SR
	movel	sp@+,d2		| remember and
	movel	d2,a5@(0x40)	| copy (offending?) PC
	movel	a5,a3		| save pointer to registers

	movel	#0,a5@-		| pass NULL pointer to stored fpu registers
	movel	a3,a5@-		| pass pointer to stored registers
	movel	d2,a5@-		| pass offending PC
	addl	d1,d1		| convert the passed trap number into a fake
	addl	d1,d1		| 68020 frame format word
	movel	d1,a5@-		| pass frame format word
	|
	| pass return address and ssp-value on userstack
	| This happens for the same reason as we have a glue_launch entry.
	| trap cleans up these 8 bytes on the user stack itself
	movel	sp,a5@-
	movel	#_restore_00,a5@-
	movel	a5,usp		| and remember current value of usp (a5)

	| fine, now process this trap. This might (doesn't have to) push
	| additional frames. If not, we just return where the exception 
	| took place (and probably will again...)

	jsr	_trap		| to the higher level C trap processor 

_restore_00:
	movel	usp,a5		| get usp
	lea	a5@(16),a5	| skip trap argument
	movel	a5@(0x3c),a1	| get stored usp
	movel	a1,usp
	
	| set up the original supervisor stack frame
	movel	a5@(0x40),sp@-	| copy PC
	movew	a5@(0x44),sp@-	| and SR
	moveml	a5@,d0-d7/a0-a6	| and the other registers
	rte



	| This is the trap processor for the mc68020 and above, paired with
	| an fpu (don't *need* an fpu though).
	| What is done: start is same as with 68000, but then the complete
	| additional exception frame is saved on the usp, together with the
	| fpu state. Then trap() is called, and then the previous context
	| is restored (involves copying back the frame from the usp over to the ssp)
_trap_20:
	movel	a5,sp@		| nuke the trap number, we use the frame format word
	movel	usp,a5		| get usp
	movel	a5,a5@(-10)	| store usp
	lea	a5@(-10),a5	| make room for sr, pc and usp.
	moveml	d0-d7/a0-a6,a5@-| store registers on usp
	movel	sp@+,a5@(0x34)	| insert the saved a5 into the saveset
	movew	sp@+,a5@(0x44)	| copy SR
	movel	sp@+,d2		| remember and
	movel	d2,a5@(0x40)	| copy (offending?) PC
	movel	a5,a3		| save pointer to registers

	| find out more about the frame (according to the MC68030 user manual)
	clrl	d1
	movew	sp@+,d1		| remember frame format word
	movew	d1,d0
	andw	#0xf000,d0
	beq	Lfmt_S0		| S0
	cmpw	#0x1000,d0
	beq	Lfmt_S1		| S1 this (interrupt) frame shouldn't be here...
	cmpw	#0x2000,d0
	beq	Lfmt_S2		| CHK{2},cpTRAPcc,TRAPV,Trace,Div0,MMUcfg,cp post instr
	cmpw	#0x9000,d0
	beq	Lfmt_S9		| cp mid instr,main det prot viol,int during cp instr
	cmpw	#0xa000,d0
	beq	Lfmt_SA_SB	| address or bus error, short and long frame
	cmpw	#0xb000,d0
	bne	Lfmt_S0		| ??? frame, this will probably not fully cleanup sp..

Lfmt_SA_SB:
	| this part (upto Lbe10) inspired by locore.s in sys/hp300/ of BSD4.3-reno
	movew	sp@(2),d0	| grab SSW for fault processing
	btst	#12,d0		| RB set?
	beq	LbeX0		| no, test RC
	bset	#14,d0		| yes, must set FB
	movew	d0,sp@(2)	| for hardware too
LbeX0:
	btst	#13,d0		| RC set?
	beq	LbeX1		| no, skip
	bset	#15,d0		| yes, must set FC
	movew	d0,sp@(2)	| for hardware too
LbeX1:
	btst	#8,d0		| data fault?
	beq	Lbe0		| no, check for hard cases
	movel	sp@(8),d2	| fault address is as given in frame
	bra	Lbe10		| thats it
Lbe0:
	btst	#12,d1		| long (type B) stack frame?
	bne	Lbe4		| yes, go handle
	btst	#14,d0		| no, can use saved PC. FB set?
	beq	Lbe3		| no, try FC
	addql	#4,d2		| yes, adjust address
	bra	Lbe10		| done
Lbe3:
	btst	#15,d0		| FC set?
	beq	Lbe10		| no, done
	addql	#2,d2		| yes, adjust address
	bra	Lbe10		| done
Lbe4:
	movel	sp@(28),d2	| long format, use stage B address
	btst	#15,d0		| FC set?
	beq	Lbe10		| no, all done
	subql	#2,d2		| yes, adjust address
Lbe10:

	| now move the frame over to the usp (6/21 longwords remain)
	
	moveml	sp@+,d3-d7/a0	| may trash as many registers as I like, I saved
	moveml	d3-d7/a0,a5@-	| them already ;-) First copy 6 longs

	btst	#12,d1		| long (type B) stack frame?
	beq	Lfmt_S0		| nope, done

	moveml	sp@+,d3-d7/a0-a2 | first copy 8 longs
	moveml	d3-d7/a0-a2,a5@-
	moveml	sp@+,d3-d7/a0-a1 | plus 7 gives 15, plus already stored 6 is 21
	moveml	d3-d7/a0-a1,a5@-
	bra	Lfmt_S0		| finito

Lfmt_S9:
	movel	sp@+,a5@-	| S9 is an S2 plus 4 internal (word length) registers
	movel	sp@+,a5@-	| so store those registers, and fall into S2

Lfmt_S2:
	movel	sp@+,d2		| S2 contains the offending instruction address
				| and the frame format word
	movel	d2,a5@-		| we have the offending instruction address here

	| fall into

Lfmt_S0:
Lfmt_S1:
	movew	d1,a5@-		| and as the last thing store the frame format word

	|
	| now lets look at the fpu, if there is an fpu in the system
	|

	movel	#0,a4		| clear pointer to fpu registers
	movel   4:w,a0
	btst	#4,a0@(0x129)	| is AFB_68881 set in SysBase->AttnFlags ??
	beq	Lno_fpu
	fsave	a5@-		| dump the fpu state onto the usp
	moveb	a5@,d0		| and get the fpu state identifier
	beq	Lno_fpu		| null frame?

	fmoveml	fpcr/fpsr/fpi,a5@-	| push the fpu control registers and
	fmovemx	fp0-fp7,a5@-		| the fpu data registers
	movel	a5,a4		| store pointer to fpu registers

	movew	#-1,a5@-	| mark that there is fpu stuff on the stack
Lno_fpu:

	|
	| pass return address and ssp-value on userstack
	| This happens for the same reason as we have a glue_launch entry.
	| trap cleans up these 8 bytes on the user stack itself
	movel	a4,a5@-		| pass pointer to stored fpu registers
	movel	a3,a5@-		| pass pointer to stored registers
	movel	d2,a5@-		| pass offending PC
	movel	d1,a5@-		| pass frame format word

	movel	sp,a5@-
	movel	#_restore_20,a5@-

	movel	a5,usp		| set the new value of the usp

	| that's it, phew.. now process this frame, and perhaps throw some
	| frames on it as well to deal with the signal
	
	jsr	_trap		| do distribution in C ;-)

_restore_20:
	
	|
	| restore the saved stack frame from the usp, and copy the necessary
	| parts over to the ssp
	|

	movel	usp,a5
	lea	a5@(16),a5	| skip trap() arguments
	
	| first deal with fpu stuff, if there's an fpu

        movel   4:w,a0
	btst	#4,a0@(0x129)	| is AFB_68881 set in SysBase->AttnFlags ??
	beq	Lno_fpu2
	tstb	a5@
	beq	Lrst_fpu_frame	| there's only the null frame, go and restore it

	lea	a5@(2),a5	| skip fpu indicator
	fmovemx	a5@+,fp0-fp7		| restore fpu data registers and
	fmoveml	a5@+,fpcr/fpsr/fpi	| fpu control registers
	
Lrst_fpu_frame:
	frestore a5@+		| and restore the internal fpu state
Lno_fpu2:
	movew	a5@+,d1		| get frame format word
	movew	d1,d0
	andw	#0xf000,d0
	beq	Lrfmt_S0	| S0
	cmpw	#0x1000,d0
	beq	Lrfmt_S1	| S1
	cmpw	#0x2000,d0
	beq	Lrfmt_S2	| S2
	cmpw	#0x9000,d0
	beq	Lrfmt_S9	| S9
	cmpw	#0xa000,d0
	beq	Lrfmt_SA	| SA
	cmpw	#0xb000,d0
	bne	Lrfmt_S0	| ??? frame

Lrfmt_SB:
	moveml	a5@+,d3-d7/a0-a2
	moveml	d3-d7/a0-a2,sp@-
	moveml	a5@+,d3-d7/a0-a1
	moveml	d3-d7/a0-a1,sp@- | copy 15 longs

Lrfmt_SA:
	movel	a5@+,sp@-	| copy  3 longs
	movel	a5@+,sp@-
	movel	a5@+,sp@-
	
Lrfmt_S9:
	movel	a5@+,sp@-	| copy  2 longs
	movel	a5@+,sp@-

Lrfmt_S2:
	movel	a5@+,sp@-	| copy  1 long

Lrfmt_S1:
Lrfmt_S0:
	movew	d1,sp@-		| insert frame format word
	movel	a5@(0x40),sp@-	| copy PC
	movew	a5@(0x44),sp@-	| and SR
	
	movel	a5@(0x3c),a1	| get the stored usp
	movel	a1,usp
	
	moveml	a5@,d0-d7/a0-a6	| restore the cpu registers
	rte			| that's it (finally) .. 

	|
	| jump to the given argument in supervisor mode
	| does NOT return
	|
_supervisor:
	movel	sp@(4),a5	| where to go in supervisor
	movel	sp@(8),sp@-	| with this usp
	movel	a6,sp@-
	movel	4:w,a6
	jmp	a6@(-0x1e)	| do it (Supervisor() system call)

	|
	| restore signal context
	| argument comes in usp
	|
	| mc68020 entry
_sup20_do_sigresume:
	jsr	_resume_signal_check
	bra	_sup20_do_sigreturn

_sup20_do_sigreturn_ssp:	| entry via jsr from supervisor mode
	movel	sp@(4),sp	| set ssp
_sup20_do_sigreturn:
	lea	sp@(-8),sp	| make room for an exception frame
	movew	#0x20,sp@(6)	| fake format word
	bra	fromsup_sigreturn

	| mc68000 entry
_sup00_do_sigresume:
	jsr	_resume_signal_check
	bra	_sup00_do_sigreturn

_sup00_do_sigreturn_ssp:
	movel	sp@(4),sp	| set ssp
_sup00_do_sigreturn:
	lea	sp@(-6),sp	| make room for an exception frame

fromsup_sigreturn:
	moveml	d0/d1/a0/a1,sp@-

	movel	usp,a0		| get signal context
	bra	resume_sigreturn

	| Supervisor() entry (already comes on exception frame)
_do_sigreturn:
	| make the sigreturn() function preserve all registers
	moveml	d0/d1/a0/a1,sp@-

	movel	usp,a0		| get signal context
	movel	a0@+,a6		| restore the trashed a6 register
	movel	a0@,a0

resume_sigreturn:


	movel	4:w,a1		| SysBase
	movel	a1,d1		| remember for later setting of sc_ap
	movel	a1@(0x114),a1	| ThisTask
	movel	a1@(0x2e),a1	| TrapData -> (struct user *)
	
	| struct user offsets are determined by create_defines
#include "ix_internals.h"
	| ok, now a0:sc, a1:u

	movel	a0@+,a1@(U_ONSTACK_OFFSET)	| u.u_onstack = sc->sc_onstack
	movel	a0@+,a1@(P_SIGMASK_OFFSET)	| u.p_sigmask = sc->sc_mask
	movel	a0@+,a1		| usp = sc->sc_sp
	movel	a1,usp
	movel	a0@+,a5		| fp  = sc->sc_fp

	movel	d1,a1		| get back SysBase
	movel	a1@(0x114),a1	| ThisTask
	lea	a0@(2),a0	| skip unused part of sc_ap
	movew	a0@,a1@(0x10)	| store IDNestCnt,TDNestCnt
	movel	d1,a1
	movew	a0@+,d1		| get IDNestCnt and TDNestCnt
	movew	d1,a1@(0x126)	| store them in SysBase
	tstb	a1@(0x126)
	bmi	Lenable
Ldisabled:
	movew	#0x4000,0xdff09a	| disable interrupts
	bra	Lint_twiddle
Lenable:
	movew	#0xc000,0xdff09a	| enable interrupts
Lint_twiddle:
	| set pc and sr in current exception frame
	movel	a0@+,sp@(2+4*4)	| set PC
	movew	a0@(2),sp@(4*4)	| and SR
	moveml	sp@+,d0/d1/a0/a1
	rte



	|
	| launch_glue is used to invoke the sig_launch handler. We have to care to
	| clean the supervisor stack, if we should call a signal handler from
	| the launch handler.
	| The bad thing is, that doing this right needs information on how
	| the tc_Launch entry is called from the OS. Thus I pass two parameters
	| on the user stack, the value of the `virgin' ssp and the value of
	| a4, which happens to contain the address of the context restore
	| function.
	| It is assumed, that tc_Launch is called via 
	| ... jsr sub
	| ...
	| sub:jsr tc_Launch
	| Thus we have to backup the sp by two jsr's, which is 8.

_launch_glue:
	movel	4:w,a0
	movel	a0@(0x114),a0	| tid = SysBase->ThisTask
	movel	a0@(0x36),a0	| usp isn't setup correctly, do it now from tc_SPReg
	movel	sp,a0@-
	addl	#8,a0@
	movel	a4,a0@-
	movel	a0,usp
	
	jsr	_sig_launch	| sig_launch `(void *pc_ret, *ssp_ret)' (`' on usp)

	| sig_launch() already corrects the usp to pop those two arguments
	rts

/*
 * update profiling information for the user
 * addupc(pc, &u.u_prof, ticks)
 */
_addupc:
	movl	a2,sp@-			| scratch register
	movl	sp@(12),a2		| get &u.u_prof
	movl	sp@(8),d0		| get user pc
	subl	a2@(8),d0		| pc -= pr->pr_off
	jlt	Lauexit			| less than 0, skip it
	movl	a2@(12),d1		| get pr->pr_scale
	lsrl	#1,d0			| pc /= 2
	lsrl	#1,d1			| scale /= 2

	movel	d1,sp@-
	movel	d0,sp@-
	jsr	___mulsi3
	addqw	#8,sp

	moveq	#14,d1
	lsrl	d1,d0			| pc >>= 14
	bclr	#0,d0			| pc &= ~1
	cmpl	a2@(4),d0		| too big for buffer?
	jge	Lauexit			| yes, screw it
	addl	a2@,d0			| no, add base

	movel	d0,a0
	movew	a0@,d0

	addw	sp@(18),d0		| add tick to current value

	movew	d0,a0@

Lauexit:
	movl	sp@+,a2			| restore scratch reg
	rts

Lreset_fpu:
	clrl	sp@-			| prepare 060 fpu null state frame
	clrl	sp@-			| needs 2 additional longs on stack
	clrl	sp@-
	frestore sp@+

	| Note that after using frestore with a null state frame we have
	| to set up the control register to restore rounding to truncation
	| rather than round-to-nearest, as required by the ANSI C standard.

	moveq	#72,d0
	addl	d0,d0
	fmovel	d0,fpcr
	rte

_resetfpu:
	movel	a6,sp@-
	movel	a5,sp@-
	movel	4:w,a6

	btst	#7,a6@(0x129)		| is AFB_68060 set in SysBase->AttnFlags ?
	lea	Lreset_fpu,a5
	jne	L_skip
	addqw	#4,a5
L_skip:
	jsr	a6@(-30)		| Supervisor()

	movel	sp@+,a5
	movel	sp@+,a6
	rts

| See the Enforcer manual for the original assembler code of the
| following code.

| This is the new bus error handler for use with Enforcer & GDB.
| There are two entry points, one for the 68020 and 68030, and one
| for the 68040.

_vector_nop:
	nop

| make sure next data is on a 4 byte boundary!

	.align	2

MyExecBase:
	.long	0			| The local copy
OldVector:
	.long	0			| One long word
_vector_install_count:
	.long	0			| use count
_vector_old_pc:
	.long	0			| store old pc

_new_vector_040:
	cmpl	#4,sp@(20)		| 68040
	jeq	TraceSkip		| If AbsExecBase, OK
	jra	TraceProcess

_new_vector:
	cmpl	#4,sp@(16)
	jeq	TraceSkip		| If AbsExecBase, OK

| Check if the current Task is running under ixemul control and
| is being traced by ixemul.

TraceProcess:
	movel	a0,sp@-			| Save this...
	movel	d0,sp@-

	movel	MyExecBase,a0		| Get ExecBase...
	movel	a0@(276:W),a0		| Get ThisTask
	movel	a0@(46:W),d0		| TrapData -> (struct user *)
	movel	d0,a0
	jeq	NoTrace			| Not ixemul
	movel	a0@,d0			| get u_ixbase
	cmpl	_ixemulbase,d0		| == ixemulbase?
	jne	NoTrace			| Nope, not an ixemul program
	btst	#4,a0@(P_FLAG_OFFSET+3)	| Test STRC flag
	beq	NoTrace
	btst	#7,sp@(8:W)		| Are we already single stepping?
	jne	NoTrace
	bset	#7,sp@(8:W)		| Set trace bit...
	lea	_vector_old_pc,a0
	tstl	a0@			| is this variable already in use?
	jne	NoTrace
	movel	sp@(10:W),a0@		| store old pc
	lea	_vector_nop,a0
	movel	a0,sp@(10:W)		| store new pc

NoTrace:
	movel	sp@+,d0			| Restore...
	movel	sp@+,a0

TraceSkip:
	movel	OldVector,sp@-		| Ready to return
	rts

| Call this function in Supervisor mode

_restore_vector:
	btst	#3,@(4)@(0x129)		| is AFB_68040 set in SysBase->AttnFlags ??
	lea	_new_vector,a0
	jeq	got_vector1
	lea	_new_vector_040,a0

got_vector1:
	lea	_vector_install_count,a1
	tstl	a1@
	beq	restore_exit
	subql	#1,a1@			| decrease use count
	bne	restore_exit
	movec	vbr,a1			| get Vector Base Register
	cmpl	a1@(8:W),a0		| sanity check
	jne	restore_exit
	movel	OldVector,a1@(8:W)

restore_exit:
	rte

| Call this function in Supervisor mode

_install_vector:
	btst	#3,@(4)@(0x129)		| is AFB_68040 set in SysBase->AttnFlags ??
	lea	_new_vector,a0
	jeq	got_vector2
	lea	_new_vector_040,a0

got_vector2:
	addql	#1,_vector_install_count | increase use count
	movec	vbr,a1			| get Vector Base Register
	cmpl	a1@(8:W),a0
	jeq	install_done		| sanity check
	movel	4:W,MyExecBase
	movel	a1@(8:W),OldVector
	movel	a0,a1@(8:W)

install_done:
	rte
