/*=============================================================================
//
//	context.S
//
//	SPARClite context switch code
//
//=============================================================================
//####COPYRIGHTBEGIN####
//
// -------------------------------------------
// The contents of this file are subject to the Cygnus eCos Public License
// Version 1.0 (the "License"); you may not use this file except in
// compliance with the License.  You may obtain a copy of the License at
// http://sourceware.cygnus.com/ecos
// 
// Software distributed under the License is distributed on an "AS IS"
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See the
// License for the specific language governing rights and limitations under
// the License.
// 
// The Original Code is eCos - Embedded Cygnus Operating System, released
// September 30, 1998.
// 
// The Initial Developer of the Original Code is Cygnus.  Portions created
// by Cygnus are Copyright (C) 1998,1999 Cygnus Solutions.  All Rights Reserved.
// -------------------------------------------
//
//###COPYRIGHTEND####
//=============================================================================
//#####DESCRIPTIONBEGIN####
//
// Author(s): 	nickg, gthomas, hmt
// Contributors:	nickg, gthomas, hmt
// Date:	1998-12-15
// Purpose:	SPARClite context switch code
// Description:	This file contains implementations of the thread context 
//		switch routines. It also contains the longjmp() and setjmp()
//              routines.
//
//####DESCRIPTIONEND####
//
//===========================================================================*/

#include <pkgconf/hal.h>

#include <cyg/hal/vectors.h>
	
#define DELAYS_AFTER_WRPSR_SAME_WINDOW
#define DELAYS_AFTER_WRWIM

	.text

! ------------------------------------------------------------------------------
!  hal_thread_switch_context
!  Switch thread contexts
!  %o0 = address of sp of next thread to execute
!  %o1 = address of sp save location of current thread

	.global	hal_thread_switch_context
hal_thread_switch_context:	

	! First take the stack down to make room for the saved register
	! state, including a window save area at the base.  Leave the
	! current window save area undisturbed.  It is unused within the
	! save but will become current again when we continue in this
	! context.  This lets us do this whole piece of work without
	! diabling interrupts for too long, since, for example, we can
	! lower the stack atomically with one instruction:
	sub	%sp, SAVE_REGS_SIZE, %sp
	st	%sp, [ %o1 ]		! return SP for this thread
			
	std	%l0, [%sp + 0 * 4]	! save L & I registers
	std	%l2, [%sp + 2 * 4]
	std	%l4, [%sp + 4 * 4]
	std	%l6, [%sp + 6 * 4]

	std	%i0, [%sp + 8 * 4]
	std	%i2, [%sp + 10 * 4]
	std	%i4, [%sp + 12 * 4]
	std	%i6, [%sp + 14 * 4]
		
#ifdef CYGDBG_HAL_COMMON_CONTEXT_SAVE_MINIMUM
	st	%o7, [%sp + 31 * 4]	! save only the return address
	! and no need to preserve %o0 even though it is restored
#else // save a maximal context
	st	%g1, [%sp + 17 * 4]	! save G & O registers
	std	%g2, [%sp + 18 * 4]
	std	%g4, [%sp + 20 * 4]
	std	%g6, [%sp + 22 * 4]

	std	%o0, [%sp + 24 * 4]
	std	%o2, [%sp + 26 * 4]
	std	%o4, [%sp + 28 * 4]
	std	%o6, [%sp + 30 * 4]
#endif // !CYGDBG_HAL_COMMON_CONTEXT_SAVE_MINIMUM
	! and save the CWP in %g0 save place
	rd	%psr, %g7
	st	%g7, [%sp + 16 * 4]
	
	! Now load the destination thread by dropping through
	! to hal_thread_load_context
	
! ------------------------------------------------------------------------------
!  hal_thread_load_context
!  Load thread context
!  %o0 = address of sp of next thread to execute
!  Note that this function is also the second half of hal_thread_switch_context
!  and is simply dropped into from it.

	.global hal_thread_load_context
hal_thread_load_context:

	! Here, we are a leaf routine but with slightly odd properties.
	! The stack is still the callers at this point but the register
	! set is up for grabs.  So we can use globals:
	
	ld	[ %o0 ], %g7		! Get the next saved SP
	
	! DISABLE INTERRUPTS *ONLY* NOT TRAPS
	rd	%psr, %g6
	or	%g6, 0xfe0, %g5		! PIL up to 15 leave traps enabled
	wr	%g5, %psr
	
	! force out all our callers register sets onto the stack
	! if necessary:	the system will handily take care of this for
	! us as follows:
	save	%sp, -16 * 4, %sp	! need all these to preserve
	save	%sp, -16 * 4, %sp	! the linked list property...
	save	%sp, -16 * 4, %sp
	save	%sp, -16 * 4, %sp
	save	%sp, -16 * 4, %sp
	save	%sp, -16 * 4, %sp
	! six of these is correct; a seventh would force out the current
	! set that was already saved manually above.  Note that minimal
	! space is allowed on stack for locals and ins in case this
	! sequence itself gets interrupted and recurses too deep.
	
	! now select the new window with traps disabled...

	! get the new PSR and CWP that we will ultimately restore
	! from the %g0 save place...
	ld	[%g7 + 16 * 4], %g6	! %g7 holds the new stack pointer
	andn	%g6, 0x20, %g5		! clear ET into %g5
	and	%g6, __WINBITS, %g4	! CWP bits only in %g4
		
	! calculate a new WIM...
	add	%g4, 1, %g3		! required invalid window number
	and	%g3, __WINBITS, %g3	! modulo 8
	mov	1, %g2
	sll	%g2, %g3, %g2		! converted to a mask for the WIM

	! DISABLE INTERRUPTS (TRAPS)
	wr	%g5, %psr		! set CWP to new window, disable traps
	wr	%g2, %wim		! and WIM to new value
	nop
	nop
	nop
		
	! Must do this atomically so that the registers match the stack.
	! After locals and ins are loaded, we are conformant to the PCS
	! so can re-enable interrupts.
	mov	%g7, %sp		! target sp in situ (%sp = %o6)

	ldd	[%sp + 0 * 4], %l0	! restore L & I registers
	ldd	[%sp + 2 * 4], %l2
	ldd	[%sp + 4 * 4], %l4
	ldd	[%sp + 6 * 4], %l6

	ldd	[%sp + 8 * 4], %i0
	ldd	[%sp + 10 * 4], %i2
	ldd	[%sp + 12 * 4], %i4
	ldd	[%sp + 14 * 4], %i6

	! RESTORE INTERRUPTS to saved state
	wr	%g6, %psr		! set new CWP and old ET and PIL
	nop
	nop
	nop
	
	! now load the rest of the context; we can be interrupted here
	! (if the saved context was a voluntary yield or threadstart)
	! but that is OK, other state will be preserved in that case...

#ifdef CYGDBG_HAL_COMMON_CONTEXT_SAVE_MINIMUM
	ld	[%sp + 24 * 4], %o0	! must load the initial argument
#else // restore a maximal context
	ld	[%sp + 17 * 4], %g1
	ldd	[%sp + 18 * 4], %g2
	ldd	[%sp + 20 * 4], %g4
	ldd	[%sp + 22 * 4], %g6

	ldd	[%sp + 24 * 4], %o0
	ldd	[%sp + 26 * 4], %o2
	ldd	[%sp + 28 * 4], %o4
#endif // !CYGDBG_HAL_COMMON_CONTEXT_SAVE_MINIMUM
					! %o6 = %sp, already set up
	ld	[%sp + 31 * 4], %o7	! "return" address

	retl
	add	%sp, SAVE_REGS_SIZE, %sp ! and set the stack back
					! to its entrant value

! ------------------------------------------------------------------------------
!  HAL longjmp, setjmp implementations

!FUNC_START(hal_setjmp)
	.global	hal_setjmp
hal_setjmp:
	! Treat this as a leaf routine, may as well.
	! %o0 is the address of the buffer.

	std	%l0, [%o0 + 0 * 4]	! save L & I registers
	std	%l2, [%o0 + 2 * 4]
	std	%l4, [%o0 + 4 * 4]
	std	%l6, [%o0 + 6 * 4]

	std	%i0, [%o0 + 8 * 4]
	std	%i2, [%o0 + 10 * 4]
	std	%i4, [%o0 + 12 * 4]
	std	%i6, [%o0 + 14 * 4]
		
#ifdef CYGDBG_HAL_COMMON_CONTEXT_SAVE_MINIMUM
	std	%o6, [%o0 + 30 * 4]	! just save %sp and return address
#else // save a maximal context
	st	%g1, [%o0 + 17 * 4]	! save G & O registers
	std	%g2, [%o0 + 18 * 4]
	std	%g4, [%o0 + 20 * 4]
	std	%g6, [%o0 + 22 * 4]

	std	%o0, [%o0 + 24 * 4]
	std	%o2, [%o0 + 26 * 4]
	std	%o4, [%o0 + 28 * 4]
	std	%o6, [%o0 + 30 * 4]
#endif // !CYGDBG_HAL_COMMON_CONTEXT_SAVE_MINIMUM
	
	! and save the CWP in %g0 save place
	rd	%psr, %g7
	st	%g7, [%o0 + 16 * 4]

	! DISABLE INTERRUPTS *ONLY* NOT TRAPS
	or	%g7, 0xfe0, %g6		! PIL up to 15 leave traps enabled
	wr	%g6, %psr
	
	! force out all our callers register sets onto the stack
	! if necessary:	the system will handily take care of this for
	! us as follows:
	save	%sp, -16 * 4, %sp	! need all these to preserve
	save	%sp, -16 * 4, %sp	! the linked list property...
	save	%sp, -16 * 4, %sp
	save	%sp, -16 * 4, %sp
	save	%sp, -16 * 4, %sp
	save	%sp, -16 * 4, %sp
	! six of these is correct; a seventh would force out the current
	! set that was already saved manually above.  Note that minimal
	! space is allowed on stack for locals and ins in case this
	! sequence itself gets interrupted and recurses too deep.

	! (after all, we are about to call deeper not shallower, otherwise
	!  using setjmp is inappropriate)

	! ENABLE INTERRUPTS
	wr	%g7, %psr		! set CWP back to as-was
	nop
	nop
	nop
	
#ifndef CYGDBG_HAL_COMMON_CONTEXT_SAVE_MINIMUM
	ldd	[%o0 + 22 * 4], %g6	! preserve %g7 and %g6
#endif
	
	retl				! ret and return zero to indicate
	mov	0, %o0			! not longjumped-to
	
!  hal_longjmp loads state from arg0 and returns arg1
!FUNC_START(hal_longjmp)
	.global	hal_longjmp
hal_longjmp:
	! This is kind of a leaf routine, it returns elsewhere
	! %o0 is the address of the buffer.
	! %o1 is the value to return in %o0 (since we are a leaf)

	mov	%o0, %g7		! keep the pointer handy
	mov	%o1, %g1		! and the return value
	! now select the new window with traps disabled...
	rd	%psr, %g6
					! preserve ET, clear CWP
	andn	%g6, __WINBITS_MAXIMAL, %g6
	andn	%g6, 0x20, %g5		! clear ET also into %g5

	! get new CWP from %g0 save place...
	ld	[%g7 + 16 * 4], %g4	! %g7 holds the new stack pointer
	and	%g4, __WINBITS, %g4	! preserve CWP bits
		
	! calculate a new WIM...
	add	%g4, 1, %g3		! required invalid window number
	and	%g3, __WINBITS, %g3	! modulo 8
	mov	1, %g2
	sll	%g2, %g3, %g2		! converted to a mask for the WIM

	! DISABLE INTERRUPTS
	wr	%g5, %g4, %psr		! set CWP to new window, disable traps
	wr	%g2, %wim		! and WIM to new value
	nop
	nop
	nop
		
	! Must do this atomically so that the registers match the stack.
	! After locals and ins are loaded, we are conformant to the PCS
	! so can re-enable interrupts.

	ldd	[%g7 + 0 * 4], %l0	! restore L & I registers
	ldd	[%g7 + 2 * 4], %l2
	ldd	[%g7 + 4 * 4], %l4
	ldd	[%g7 + 6 * 4], %l6

	ldd	[%g7 + 8 * 4], %i0
	ldd	[%g7 + 10 * 4], %i2
	ldd	[%g7 + 12 * 4], %i4
	ldd	[%g7 + 14 * 4], %i6

	ld	[%g7 + 30 * 4], %sp	! %o6 = %sp, set up now so as to conform
					! to PCS and so be interruptible
	! ENABLE INTERRUPTS
	wr	%g6, %g4, %psr		! set new CWP and old ET
	nop
	nop
	nop
	
	! now load the rest of the context; we can be interrupted here, but
	! that is OK, other state will be preserved in that case...
#ifdef CYGDBG_HAL_COMMON_CONTEXT_SAVE_MINIMUM
	! we are not preserving globals...
					! %o6 = %sp, already set up
	ld	[%g7 + 31 * 4], %o7	! "return" address
	retl				! %g1 still holds the return value
	mov	%g1, %o0

#else // load a maximal context
	mov	%g7, %o0		! original pointer was in %o0 anyway

	ldd	[%o0 + 18 * 4], %g2
	ldd	[%o0 + 20 * 4], %g4
	ldd	[%o0 + 22 * 4], %g6

	ld	[%o0 + 25 * 4], %o1	! %o0 = original pointer
	ldd	[%o0 + 26 * 4], %o2
	ldd	[%o0 + 28 * 4], %o4
					! %o6 = %sp, already set up
	ld	[%o0 + 31 * 4], %o7	! "return" address

	! %g1 still holds the return value; want to get this into %o0
	! and restore %g1 from the saved state;  %o0 is the state pointer:
					! g1 = R,   o0 = P
	xor	%o0, %g1, %g1		! g1 = R^P, o0 = P
	xor	%o0, %g1, %o0		! g1 = R^P, o0 = R
	xor	%o0, %g1, %g1		! g1 = P,   o0 = R all done
		
	retl
	ld	[%g1 + 17 * 4], %g1	! and finally restore %g1
#endif // !CYGDBG_HAL_COMMON_CONTEXT_SAVE_MINIMUM
	
! ------------------------------------------------------------------------------
! end of context.S



