;
; Copyright 1992 Eric R. Smith
; Copyright 1992,1993,1994 Atari Corporation
; All rights reserved.

%include "../magic/magic.i"
;
; routines for saving/restoring user contexts
;
; long build_context(struct context *sav, short fmt):
;	Called from an interrupt handler (such as the trap #1 routine
;	for system calls) saves the context of the interrupted
;	routine. Assumes that no user registers have been changed
;	since the interrupt, and that the PC and status register
;	are still on the stack. Returns the stack pointer being used
;	at the time of the interrupt **in register a1**.
;	The fmt parameter is used on the 68000 to communicate the exception
;	vector number; on >=68010 we use the vector offset from the frame.
;
; long save_context(struct context *sav):
;	Saves the context of the calling routine in the area pointed
;	to by sav. Save_context always returns 0 when initially called;
;	this is so processes can (by suitably manipulating the
;	saved registers) tell when the return from save_context is
;	actually caused by restoring the context, e.g.:
;		if (save_context(sav) == 0) {		<<-- L1
;			/* do some stuff */
;			sav.regs[D0] = 1;	/* for restore context */
;			restore_context(sav);	/* goes back to L1 */
;		}
;		else /* this is the second time through */
;
; void restore_context(struct context *sav):
;	Restores a context previously saved by build_context or save_context.
;	Since the program counter is part of the context, this function
;	will never return (it's like longjmp()). NOTE: this function should
;	be used only to change contexts _within_ the same program, since
;	it does NOT flush the ATC. See change_context
;
; void change_context(struct context *sav):
;	Restores a context previously saved by build_context or save_context
;	for a different process. Unlike restore_context, this one *does*
;	flush the ATC.

	TEXT

	XDEF	_build_context
	XDEF 	_save_context
	XDEF	_restore_context
	XDEF	_change_context

	XREF	_fpu
	XREF	_framesizes
	XREF	_new_trace	; from intr.s
	XREF	_no_mem_prot

	TEXT
_build_context:
	move.l	a0,-(sp)	; save a0; we'll use it for scratch
	move.l	8(sp),a0	; get address of save area

	tst.w	_no_mem_prot	; is there memory protection?
	bne.s	noprot1
%ifdef MMU040
	move.l	d0,-(sp)
	dc.w	$4e7a,$0806	; movec urp,d0
	move.l	d0,C_CRP(a0)
	move.l	(sp)+,d0
%else
	pmove	crp,C_CRP(a0)	; save CRP from MMU
	pmove	tc,C_TC(a0)	; save TC from MMU
%endif
noprot1:
	movem.l	d0-d7/a0-a6,(a0)	; save registers D0-D7/A0-A6
	clr.b	C_PTRACE(a0)	; no pending traces, thanks!
	lea	12(sp),a1	; start of the interesting stack area
	move.w	(a1)+,d0	; 68000 fake frame format

%ifndef ONLY030
	move.w	($59e).w,d7	; get process frame flag
	bne.s	nojunk		; we have some junk on the stack
	move.w	d0,C_SFMT(a0)	; save fake frame format
	subq.w	#$8,d0		; if bus error (note: subq is faster than
	beq.s	group0		; cmp, and we won't need d0 later)
	subq.w	#$4,d0		; or address error ($C==$8+$4)
	bne.s	nojunk
group0:	move.l	(a1)+,C_INTERNAL(a0)	; stash it in the internal area
	move.l	(a1)+,C_INTERNAL+4(a0)	; if a debugger's interested
nojunk:
%endif
	move.w	(a1)+,d0	; get SR of context
	move.w	d0,C_SR(a0)	; save it
	move.l	(a1)+,C_PC(a0)	; save PC of context
%ifndef ONLY030
	tst.w	d7		; test longframe (AKP)
	beq.s	short1		; short
%endif
	tst.w	_fpu		; is there a true FPU in the system
	beq.s	nofpu
	fsave	C_FSTATE(a0)		; save internal state frame
	tst.b	C_FSTATE(a0)		; if NULL frame then the FPU is not in use
	beq.s	nofpu		; skip programmer's model save
	fmovem.x	fp0-fp7,C_FREGS(a0)		; save data registers
	fmovem.l	fpcr/fpsr/fpiar,C_FCTRL(a0)	; and control registers
nofpu:
	lea	C_SFMT(a0),a2
	move.w	(a1)+,d1	; fetch frame format word
	move.w	d1,(a2)+	; and stash it away for later
	lsr.w	#8,d1		; isolate the frame format identifier
	lsr.w	#4,d1
	lea	_framesizes,a3
	move.b	0(a3,d1.w),d1
	beq.s	short1		; if no data to save, skip this
	subq.w	#1,d1		; correct for first time through loop
bcint:	move.w	(a1)+,(a2)+	; copy CPU internal state
bcover:	dbf	d1,bcint
short1:
	move.l	a1,C_SSP(a0)	; a1 now points above the state frame
	move.l	usp,a1		; save user stack pointer
	move.l	a1,C_USP(a0)
	btst	#13,d0		; check for supervisor mode
	beq.s	L_CONT1		; user mode; we already have stack in a1
L_SUPER1:
; moving from the save state buffer
; means not testing longframe again. (AKP)
	move.l	C_SSP(a0),a1	; was using super stack pointer before interrupt
				;
L_CONT1:
	move.l	($408).w,C_TERM(a0) ; save GEMDOS terminate vector
	move.l	(sp)+,C_A0(a0)	; save old register a0
	rts


_save_context:
	move.l	a0,-(sp)	; save a0
	move.l	8(sp),a0	; get address of context save area

	tst.w	_no_mem_prot
	bne.s	noprot2
%ifdef MMU040
	move.l	d0,-(sp)
	dc.w	$4e7a,$0806	; movec urp,d0
	move.l	d0,C_CRP(a0)
	move.l	(sp)+,d0
%else
	pmove	crp,C_CRP(a0)	; save the CRP from the MMU
	pmove	tc,C_TC(a0)	; save the TC from the MMU
%endif
noprot2:

; if running with a true coprocessor we need to save the FPU state
	tst.w	_fpu		; is there a true FPU in the system
	beq.s	nofpu2
	fsave	C_FSTATE(a0)		; save internal state frame
	tst.b	C_FSTATE(a0)		; if NULL frame then the FPU is not in use
	beq.s	nofpu2		; skip programmer's model save
	fmovem.x	fp0-fp7,C_FREGS(a0)		; save data registers
	fmovem.l	fpcr/fpsr/fpiar,C_FCTRL(a0)	; and control registers
nofpu2:
; note: I am somewhat unsure of this assumption, viz that save_context
; can never be called in a situation where a co-processor
; mid-instruction stack frame would be required. I suspect this is a
; valid assumption, in which case the above FPU code is redundant, the
; next line is not however!

	clr.w	C_SFMT(a0)		; mark as a 4 word stack frame
	clr.b	C_PTRACE(a0)		; no pending traces, thanks!

	movem.l	d0-d7/a0-a6,(a0)	; save D0-D7/A0-A6
	lea	8(sp),a1
	move.l	a1,C_SSP(a0)	; save supervisor stack pointer
				; note that it should be pointing above the PC
	move.l	-4(a1),C_PC(a0)	; save PC
	move.l	usp,a1
	move.l	a1,C_USP(a0)	; save user stack pointer
	move.w	sr,C_SR(a0)	; save status register
	move.l	($408).w,C_TERM(a0)	; save GEMDOS terminate vector
	move.l	(sp)+,C_A0(a0)	; save old a0
	moveq.l	#0,d0		; return 0
	rts

_restore_context:
	ori.w	#$0700,sr
	move.l	4(sp),a0	; address of context save area

; Switch stacks now - starting now ssp is in the memory space of
; the process we're switching to. Thus, we can change memory context
; to there.

	move.l	C_SSP(a0),a1	; get supervisor stack pointer
	tst.b	(a1)		; touch the page for virtual memory programs
	tst.b	-63(a1)		; make sure stack can grow
	move.l	a1,sp
	move.l	C_USP(a0),a1
	move.l	a1,usp		; set user stack pointer
	move.l	C_TERM(a0),($408).w	; restore GEMDOS terminate vector

; Set memory context now: actually, this isn't necessary, since
; we're coming back to a context in the same process as is running
; now.
;	tst.w	_no_mem_prot
;	bne.s	noprot3
;	pmove	C_CRP(a0),crp	; restore MMU root pointer
;	pmove	C_TC(a0),tc	; restore MMU control register
noprot3:

%ifndef ONLY030
	tst.w	($59e).w	; test longframe (AKP)
	beq.s	short3
%endif
; was moveq.l #0,d0, but I don't think that's what was desired
;	moveq.l	#0,d1		; neither d0 nor d1 has to be cleared here (Draco)
	lea	C_SFMT(a0),a1
	move.w	(a1)+,d0	; fetch frame format word
	move.w	d0,d1		; copy it for later
	lsr.w	#8,d1		; isolate the frame format identifier
	lsr.w	#4,d1
	lea	_framesizes,a2
	move.b	0(a2,d1.w),d1
	beq.s	rcovernc	; if no data to copy, skip the copy
	sub.w	d1,sp
	sub.w	d1,sp
	move.l	sp,a2
	subq.w	#1,d1		; correct for first time through loop
rcint:	move.w	(a1)+,(a2)+
rcover:	dbf	d1,rcint
rcovernc:
	move.w	d0,-(sp)	; frame format identifier
; if running with a true coprocessor we need to restore the FPU state

	tst.w	_fpu		; is there a true FPU in the system
	beq.s	short3
	tst.b	C_FSTATE(a0)		; if NULL frame then the FPU is not in use
	beq.s	short4		; skip programmer's model restore
	fmovem.l	C_FCTRL(a0),fpcr/fpsr/fpiar	; restore control registers
	fmovem.x	C_FREGS(a0),fp0-fp7		; and data registers
short4:	frestore	C_FSTATE(a0)			; finally the internal state
short3:
	move.l	C_PC(a0),-(sp)	; push the PC
	move.w	C_SR(a0),-(sp)	; push the status register
	tst.b	C_PTRACE(a0)		; check for a pending trace
	movem.l	(a0),d0-d7/a0-a6	; restore registers d0-d7/a0-a6
	beq.s	notrace
	jmp	_new_trace
notrace:
	rte			; jump back to old context


_change_context:
	ori.w	#$0700,sr	; mask interrupts
	move.l	4(sp),a0	; address of context save area

; Switch stacks now - starting now ssp is in the memory space of
; the process we're switching to. Thus, we can change memory context
; to there.

	move.l	C_SSP(a0),a1	; get supervisor stack pointer
	tst.b	(a1)		; touch the page for virtual memory programs
	tst.b	-63(a1)	; make sure stack can grow
	move.l	a1,sp
	move.l	C_USP(a0),a1
	move.l	a1,usp		; set user stack pointer
	move.l	C_TERM(a0),($408).w	; restore GEMDOS terminate vector

; Set memory context now
	tst.w	_no_mem_prot
	bne.s	noprot4
%ifdef MMU040
	move.l	C_CRP(a0),d0
	dc.w	$f518		; pflusha
	dc.w	$4e7b,$0806	; movec d0,urp
	dc.w	$4e7b,$0807	; movec d0,srp
%else
	pflusha
	pmove	C_CRP(a0),crp	; restore MMU root pointer
	pmove	C_TC(a0),tc	; restore MMU control register
%endif
noprot4:
%ifndef ONLY030
	tst.w	($59e).w	; test longframe (AKP)
	beq.s	short6
%endif
; was moveq.l #0,d0, but I don't think that's what was desired
;	moveq.l	#0,d1		; see the same in _restore_context (Draco)
	lea	C_SFMT(a0),a1
	move.w	(a1)+,d0	; fetch frame format word
	move.w	d0,d1		; copy it for later
	lsr.w	#8,d1		; isolate the frame format identifier
	lsr.w	#4,d1
	lea	_framesizes,a2
	move.b	0(a2,d1.w),d1
	beq.s	rcover2nc	; if no data to copy, skip it
	sub.w	d1,sp
	sub.w	d1,sp
	move.l	sp,a2
	subq.w	#1,d1		; correct for first time through loop
rcint2:	move.w	(a1)+,(a2)+
rcover2: dbf	d1,rcint2
rcover2nc:
	move.w	d0,-(sp)	; frame format identifier
; if running with a true coprocessor we need to restore the FPU state

	tst.w	_fpu		; is there a true FPU in the system
	beq.s	short6
	tst.b	C_FSTATE(a0)	; if NULL frame then the FPU is not in use
	beq.s	short5		; skip programmer's model restore
	fmovem.l	C_FCTRL(a0),fpcr/fpsr/fpiar	; restore control registers
	fmovem.x	C_FREGS(a0),fp0-fp7		; and data registers
short5:	frestore	C_FSTATE(a0)			; finally the internal state
short6:
	move.l	C_PC(a0),-(sp)	; push the PC
	move.w	C_SR(a0),-(sp)	; push status register
	tst.b	C_PTRACE(a0)		; check for a pending trace
	movem.l	(a0),d0-d7/a0-a6	; restore registers d0-d7/a0-a6
	beq.s	notrace2
	jmp	_new_trace
notrace2:
	rte			; jump back to old context

	END
