/* switch.s 
 *   	Machine dependent context switch routines.  DO NOT MODIFY THESE! 
 *
 *	Context switching is inherently machine dependent, since
 *	the registers to be saved, how to set up an initial
 *	call frame, etc, are all specific to a processor architecture.
 *
 * 	This file currently supports the following architectures:
 *	    DEC MIPS
 *	    SUN SPARC
 *	    HP PA-RISC
 *	    Intel 386
 *
 * We define two routines for each architecture:
 *
 * ThreadRoot(InitialPC, InitialArg, WhenDonePC, StartupPC)
 *	InitialPC  - The program counter of the procedure to run
 *			in this thread.
 *      InitialArg - The single argument to the thread.
 *	WhenDonePC - The routine to call when the thread returns.
 *	StartupPC  - Routine to call when the thread is started.
 *
 *	ThreadRoot is called from the SWITCH() routine to start
 *	a thread for the first time. 
 *
 * SWITCH(oldThread, newThread)
 * 	oldThread  - The current thread that was running, where the
 *		CPU register state is to be saved.
 * 	newThread  - The new thread to be run, where the CPU register
 *		state is to be loaded from.
 */

/*
 Copyright (c) 1992-1993 The Regents of the University of California.
 All rights reserved.  See copyright.h for copyright notice and limitation 
 of liability and disclaimer of warranty provisions.
 */

#include "copyright.h"
#include "switch.h"

#ifdef HOST_MIPS

/* Symbolic register names */
#define z       $0      /* zero register */
#define a0      $4      /* argument registers */
#define a1      $5
#define s0      $16     /* callee saved */
#define s1      $17
#define s2      $18
#define s3      $19
#define s4      $20
#define s5      $21
#define s6      $22
#define s7      $23
#define sp      $29     /* stack pointer */
#define fp      $30     /* frame pointer */
#define ra      $31     /* return address */

        .text   
        .align  2

	.globl ThreadRoot
	.ent	ThreadRoot,0
ThreadRoot:
	or	fp,z,z		# Clearing the frame pointer here
				# makes gdb backtraces of thread stacks 
				# end here (I hope!)

	jal	StartupPC	# call startup procedure
	move	a0, InitialArg
	jal	InitialPC	# call main procedure
	jal 	WhenDonePC	# when we're done, call clean up procedure

	# NEVER REACHED
	.end ThreadRoot

	# a0 -- pointer to old Thread
	# a1 -- pointer to new Thread
	.globl SWITCH
	.ent	SWITCH,0
SWITCH:
	sw	sp, SP(a0)		# save new stack pointer
	sw	s0, S0(a0)		# save all the callee-save registers
	sw	s1, S1(a0)
	sw	s2, S2(a0)
	sw	s3, S3(a0)
	sw	s4, S4(a0)
	sw	s5, S5(a0)
	sw	s6, S6(a0)
	sw	s7, S7(a0)
	sw	fp, FP(a0)		# save frame pointer
	sw	ra, PC(a0)		# save return address
	
	lw	sp, SP(a1)		# load the new stack pointer
	lw	s0, S0(a1)		# load the callee-save registers
	lw	s1, S1(a1)
	lw	s2, S2(a1)
	lw	s3, S3(a1)
	lw	s4, S4(a1)
	lw	s5, S5(a1)
	lw	s6, S6(a1)
	lw	s7, S7(a1)
	lw	fp, FP(a1)
	lw	ra, PC(a1)		# load the return address	

	j	ra
	.end SWITCH
#endif HOST_MIPS

#ifdef HOST_SPARC

/* NOTE!  These files appear not to exist on Solaris --
 *  you need to find where (the SPARC-specific) MINFRAME, ST_FLUSH_WINDOWS, ...
 *  are defined.  (I don't have a Solaris machine, so I have no way to tell.)
 */
#ifdef        HOST_SunOS5
#include      <sys/trap.h>
#include      <sys/asm_linkage.h>
#else
#include <sun4/trap.h>
#include <sun4/asm_linkage.h>
#endif        /* HOST_SUNOS5 */

.seg    "text"

/* SPECIAL to the SPARC:
 *	The first two instruction of ThreadRoot are skipped because
 *	the address of ThreadRoot is made the return address of SWITCH()
 *	by the routine Thread::StackAllocate.  SWITCH() jumps here on the
 *	"ret" instruction which is really at "jmp %o7+8".  The 8 skips the
 *	two nops at the beginning of the routine.
 */

#ifdef        HOST_SunOS5
.globl        ThreadRoot
ThreadRoot:
#else
.globl	_ThreadRoot
_ThreadRoot:
#endif
	nop  ; nop         /* These 2 nops are skipped because we are called
			    * with a jmp+8 instruction. */
	clr	%fp        /* Clearing the frame pointer makes gdb backtraces 
	                    * of thread stacks end here. */
			   /* Currently the arguments are in out registers we
			    * save them into local registers so they won't be 
			    * trashed during the calls we make. */
	mov	InitialPC, %l0  
	mov	InitialArg, %l1
	mov	WhenDonePC, %l2
			   /* Execute the code:
			   *	call StartupPC();
			   *	call InitialPC(InitialArg);
			   *	call WhenDonePC();
			   */
	call	StartupPC,0
	nop
	call	%l0, 1	
	mov	%l1, %o0   /* Using delay slot to setup argument to InitialPC */
	call	%l2, 0
	nop
			   /* WhenDonePC call should never return.  If it does
			    * we execute a trap into the debugger.  */
	ta	ST_BREAKPOINT


#ifdef        HOST_SunOS5
.globl        SWITCH
SWITCH:
#else
.globl	_SWITCH
_SWITCH:
#endif
	save	%sp, -SA(MINFRAME), %sp
	st	%fp, [%i0]
	st	%i0, [%i0+I0]
	st	%i1, [%i0+I1]
	st	%i2, [%i0+I2]
	st	%i3, [%i0+I3]
	st	%i4, [%i0+I4]
	st	%i5, [%i0+I5]
	st	%i7, [%i0+I7]
	ta	ST_FLUSH_WINDOWS
	nop
	mov	%i1, %l0
	ld	[%l0+I0], %i0
	ld	[%l0+I1], %i1
	ld	[%l0+I2], %i2
	ld	[%l0+I3], %i3
	ld	[%l0+I4], %i4
	ld	[%l0+I5], %i5
	ld	[%l0+I7], %i7
	ld	[%l0], %i6
	ret
	restore

#endif HOST_SPARC

#ifdef HOST_SNAKE

    ;rp = r2,   sp = r30
    ;arg0 = r26,  arg1 = r25,  arg2 = r24,  arg3 = r23

	.SPACE  $TEXT$
	.SUBSPA $CODE$
ThreadRoot
	.PROC
	.CALLINFO CALLER,FRAME=0
	.ENTRY

	.CALL
	ble  0(%r6)		;call StartupPC
	or   %r31, 0, %rp	;put return address in proper register
	or   %r4, 0, %arg0	;load InitialArg
	.CALL	;in=26
	ble  0(%r3)		;call InitialPC
	or   %r31, 0, %rp	;put return address in proper register
	.CALL
	ble  0(%r5)		;call WhenDonePC
	.EXIT
	or   %r31, 0, %rp	;shouldn't really matter - doesn't return

	.PROCEND


SWITCH
	.PROC
	.CALLINFO CALLER,FRAME=0
	.ENTRY

    ; save process state of oldThread
	stw  %sp, SP(%arg0)	;save stack pointer
	stw  %r3, S0(%arg0)	;save callee-save registers
	stw  %r4, S1(%arg0)
	stw  %r5, S2(%arg0)
	stw  %r6, S3(%arg0)
	stw  %r7, S4(%arg0)
	stw  %r8, S5(%arg0)
	stw  %r9, S6(%arg0)
	stw  %r10, S7(%arg0)
	stw  %r11, S8(%arg0)
	stw  %r12, S9(%arg0)
	stw  %r13, S10(%arg0)
	stw  %r14, S11(%arg0)
	stw  %r15, S12(%arg0)
	stw  %r16, S13(%arg0)
	stw  %r17, S14(%arg0)
	stw  %r18, S15(%arg0)
	stw  %rp, PC(%arg0)	;save program counter

    ; restore process state of nextThread
	ldw  SP(%arg1), %sp	;restore stack pointer
	ldw  S0(%arg1), %r3	;restore callee-save registers
	ldw  S1(%arg1), %r4
	ldw  S2(%arg1), %r5
	ldw  S3(%arg1), %r6
	ldw  S4(%arg1), %r7
	ldw  S5(%arg1), %r8
	ldw  S6(%arg1), %r9
	ldw  S7(%arg1), %r10
	ldw  S8(%arg1), %r11
	ldw  S9(%arg1), %r12
	ldw  S10(%arg1), %r13
	ldw  S11(%arg1), %r14
	ldw  S12(%arg1), %r15
	ldw  S13(%arg1), %r16
	ldw  S14(%arg1), %r17
	ldw  PC(%arg1), %rp	;save program counter
	bv   0(%rp)
	.EXIT
	ldw  S15(%arg1), %r18

	.PROCEND

	.EXPORT SWITCH,ENTRY,PRIV_LEV=3,RTNVAL=GR
	.EXPORT ThreadRoot,ENTRY,PRIV_LEV=3,RTNVAL=GR

#endif

#ifdef HOST_i386

        .text
        .align  2

        .globl  _ThreadRoot

/* void ThreadRoot( void )
**
** expects the following registers to be initialized:
**      eax     points to startup function (interrupt enable)
**      edx     contains inital argument to thread function
**      esi     points to thread function
**      edi     point to Thread::Finish()
*/
_ThreadRoot:
        pushl   %ebp
        movl    %esp,%ebp
        pushl   InitialArg
        call    StartupPC
        call    InitialPC
        call    WhenDonePC

        # NOT REACHED
        movl    %ebp,%esp
        popl    %ebp
        ret



/* void SWITCH( thread *t1, thread *t2 )
**
** on entry, stack looks like this:
**      8(esp)  ->              thread *t2
**      4(esp)  ->              thread *t1
**       (esp)  ->              return address
**
** we push the current eax on the stack so that we can use it as
** a pointer to t1, this decrements esp by 4, so when we use it
** to reference stuff on the stack, we add 4 to the offset.
*/
        .comm   _eax_save,4

        .globl  _SWITCH
_SWITCH:
        movl    %eax,_eax_save          # save the value of eax
        movl    4(%esp),%eax            # move pointer to t1 into eax
        movl    %ebx,_EBX(%eax)         # save registers
        movl    %ecx,_ECX(%eax)
        movl    %edx,_EDX(%eax)
        movl    %esi,_ESI(%eax)
        movl    %edi,_EDI(%eax)
        movl    %ebp,_EBP(%eax)
        movl    %esp,_ESP(%eax)         # save stack pointer
        movl    _eax_save,%ebx          # get the saved value of eax
        movl    %ebx,_EAX(%eax)         # store it
        movl    0(%esp),%ebx            # get return address from stack into ebx
        movl    %ebx,_PC(%eax)          # save it into the pc storage

        movl    8(%esp),%eax            # move pointer to t2 into eax

        movl    _EAX(%eax),%ebx         # get new value for eax into ebx
        movl    %ebx,_eax_save          # save it
        movl    _EBX(%eax),%ebx         # retore old registers
        movl    _ECX(%eax),%ecx
        movl    _EDX(%eax),%edx
        movl    _ESI(%eax),%esi
        movl    _EDI(%eax),%edi
        movl    _EBP(%eax),%ebp
        movl    _ESP(%eax),%esp         # restore stack pointer
        movl    _PC(%eax),%eax          # restore return address into eax
        movl    %eax,4(%esp)            # copy over the ret address on the stack
        movl    _eax_save,%eax

        ret

#endif
