# Harry Porter  -  7 August 2023

	.sourcefile	"entry.s", "xv6 - Harry Porter"
	.begin		kernel

	.import		_P_BasicSystem_Boot_Code                    # e.g., "coldboot" = 0x636F6C64626F6F74
	.import		_P_BasicSystem_Boot_Private_Memory_Size     # in bytes
	.import		_P_BasicSystem_Boot_Shared_Memory_Start
	.import		_P_BasicSystem_Boot_Shared_Memory_Size      # in bytes, possibly 0
	.import		_P_BasicSystem_Boot_First_Unused_Addr
	.import		_P_BasicSystem_Boot_Csr_Version

	.import		cpuid

	.import		_kplEntry
	.import		GlobalTrapHandler
	.import		CPUStateSaveArea
	.import		_putString
	.import		CantContinue

	.import		_P_xv6_stack0
	.import		_P_xv6_kerneltrap
	.import		_P_xv6_usertrap

	.export		_entry
	.export		kernelvec
	.export		uservec
	.export		userret
	.export		swtch


CAUSE_STACK_OVERFLOW:	.equ		0x2088
CAUSE_TIMER_INTERRUPT:	.equ		0x2090



# 
# =====================  _entry  =====================
#
# Prepare for xv6 execution.  Initialize the stack pointer and other things.
# Then call the "_kplentry" function, which will call "main". "_kplEntry" is a function
# generated by the compiler for any package containing the "main" function.
#
# CONCERNING STACK USAGE: "_entry" allocates exactly 8 bytes onto the stack. The value
#     of 0 is pushed as the "return address", indicating the bottom of the stack
#     to the debugger. If the framesize changes later on, it will affect any
#     and all stack overflow calculations, since this is a fixed, known overhead.
#     (See also all stack usage related to errors and THROW processing, since that
#     can push additional bytes onto the stack.)
#
#
_entry:
						# Initially in kernel mode & interrupts are disabled
	.function	"_entry",line=0,framesize=8
	movi		gp,0x000010000		# Initialize the Global Pointer register

	stored		_P_BasicSystem_Boot_Code,r1			# Store parameters from boot program
	stored		_P_BasicSystem_Boot_Private_Memory_Size,r2	# Store parameters from boot program
	stored		_P_BasicSystem_Boot_Shared_Memory_Start,r3	# Store parameters from boot program
	stored		_P_BasicSystem_Boot_Shared_Memory_Size,r4	# Store parameters from boot program
	stored		_P_BasicSystem_Boot_First_Unused_Addr,r5	# Store parameters from boot program
	csrread		r1,csr_version					# Store csr_version in "Boot_Csr_Version"
	stored		_P_BasicSystem_Boot_Csr_Version,r1		# Store parameters from boot program

						# Initialize the stack pointer and stack_limit
	movi		r2,_P_xv6_stack0+8	# .  r2 = addr of memory array with 4K per core
	call		cpuid			# .  r1 = core number (0...8)
	slli		r1,r1,12		# .  multiply by 4096
	add		r2,r2,r1		# .  add to r2 to get stack start addr
	slli		r3,r2,28		# .  Shift into upper bits
	ori		r3,r3,0x0001		# .  Set mode=K
	csrwrite	csr_status,r3		# .  Set csr_status
	addi		r2,r2,4096		# .  + 4096 to get to beyond address
	mov		sp,r2			# .  set sp
	addi		sp,sp,-8		# Push a frame (size = 8) with
	stored		0(sp),r0		# . return address of zero, to indicate no caller
	movi		r1,CPUStateSaveArea	# Setup default trap handler (prior to xv6 using kernelvec) 
	csrwrite	csr_ptr,r1		# .  Initialize csr_ptr to point to CPUStateSaveArea
	movi		r1,GlobalTrapHandler	# .  Set csr_trapvec to point to GlobalTrapHandler
	csrwrite	csr_trapvec,r1		# .  .
	movi		tp,0			# Set "tp", the Thread Pointer register to NULL
	movi		r1,0x7fffffffffffffff	# Avoid any timer interrupts before ready
	csrwrite	csr_timer,r1		# . csr_timer = MAX
	call		_kplEntry		# Call "_kplEntry"; no return expected
	movi		r1,mainReturnErr	# Print message and halt
	call		_putString		# .	
	call		CantContinue		# .
	.endfunction
mainReturnErr:
	.string		"\nUnexpected return from main\n\0"
	.align		8


# =====================  kernelvec  =====================
#
# Come here whenever a trap occurs while executing in kernel mode. First, save
# all the registers on the stack of the currently executing kernel thread and then
# call kerneltrap. Kerneltrap will execute the relevant trap handler with interrupts
# disabled the entire time. Eventually, the kernelvec will return here. At that
# time, we'll restore all the registers and do a SYSRET to resume the interrupted thread.
# 
kernelvec:
	.function	"kernelvec",line=0,framesize=128+(7*8)

	# If this is a stack overflow, avoid infinite recursion.
	csrwrite	csr_temp,r1		# save r1
	csrread		r1,csr_cause
	addi		r1,r1,-CAUSE_STACK_OVERFLOW
	beqz		r1,gotProblem		# avoid BEQI, which uses reg t
	csrread		r1,csr_bad
	addi		r1,r1,-CAUSE_STACK_OVERFLOW
	bnez		r1,noProblem
gotProblem:
	movi		r1,0x0001		# clear stack_limit in csr_status
	csrwrite	csr_status,r1
noProblem:
	csrread		r1,csr_temp		# restore r1

	# make room to save registers.
	addi		sp,sp,-128
	# save the registers. (r0, tp, and gp never change so they are not saved)
	store.d		 0(sp),r1
	store.d		 8(sp),r2
	store.d		16(sp),r3
	store.d		24(sp),r4
	store.d		32(sp),r5
	store.d		40(sp),r6
	store.d		48(sp),r7
	store.d		56(sp),t
	store.d		64(sp),s0
	store.d		72(sp),s1
	store.d		80(sp),s2
	store.d		88(sp),lr
	store.d		96(sp),sp

# NOTE: We must leave 3 doublewords between the last used byte and the previous
#       stack top, since KPL sometimes places data beyond the stack top. Since we may
#       be returning to the interrupted code, these must be preserved.
#   88...95  -- lr save area
#   96...103 -- sp save area
#  104...111 -- reserved
#  112...119 -- reserved
#  120...127 -- reserved
#  128       <-- previous stack top

# If the causeCode is an exception (i.e., < CAUSE_TIMER_INTERRUPT), then the kernel has an error
# and we will not be returning. In that case, overwrite offset 120 with a fake return addr, which
# will make the stack look like we were called from the location of the exception. This will allow
# the emulator debugging to show the call stack, facilitating debugging.
	csrread		r1,csr_cause
	bgei		r1,CAUSE_TIMER_INTERRUPT,notAnException
	csrread		r1,csr_prevpc
	store.d		120(sp),r1
notAnException:

	# call the trap handler
	csrread		r4,csr_cause		# pass args (r1,r2,r3,cause,prevpc,bad,addr)
	csrread		r5,csr_prevpc
	csrread		r6,csr_bad
	csrread		r7,csr_addr
	addi		sp,sp,-(7*8)		# Allocate arg-save-area for 7 args to kerneltrap
	call		_P_xv6_kerneltrap
	addi		sp,sp,+(7*8)

	# restore the registers.
	load.d		r1, 0(sp)
	load.d		r2, 8(sp)
	load.d		r3,16(sp)
	load.d		r4,24(sp)
	load.d		r5,32(sp)
	load.d		r6,40(sp)
	load.d		r7,48(sp)
	load.d		 t,56(sp)
	load.d		s0,64(sp)
	load.d		s1,72(sp)
	load.d		s2,80(sp)
	load.d		lr,88(sp)
	load.d		sp,96(sp)

	addi		sp,sp,128

	# Return to whatever we were doing in the kernel.
	sysret
	.endfunction



# =====================  uservec  =====================
#
# Previously, usertrapret set csr_trapvec to point to this code, so any trap
# from user space will jump here, now executing in kernel mode. Save the user's
# registers immediately. While the user process executes, csr_ptr points to this
# process's Trapframe, which is where the regs are to be saved. Then load the
# registers needed for the kernel execution (i.e., csr_status, sp, and gp) and
# jump to usertrap().
#
uservec:
	.function	"uservec",line=0,framesize=0

# debug

# Save user r1 in csr_ptr so r1 can be used to access the Trapframe.
	csrswap		r1,csr_ptr,r1

# Save all other user registers in the Trapframe.
	store.d		8  (r1), r2
	store.d		16 (r1), r3
	store.d		24 (r1), r4
	store.d		32 (r1), r5
	store.d		40 (r1), r6
	store.d		48 (r1), r7
	store.d		56 (r1), t
	store.d		64 (r1), s0
	store.d		72 (r1), s1
	store.d		80 (r1), s2
	store.d		88 (r1), tp
	store.d		96 (r1), gp
	store.d		104(r1), lr
	store.d		112(r1), sp

# Now save r1.
	csrread		r2,csr_ptr
	store.d		0 (r1),r2

# Initialize kernel csr_status, from p->trapframe->kernel_status.
	load.d		r2, 136(r1)
	csrwrite	csr_status,r2

# Initialize kernel stack pointer, from p->trapframe->kernel_sp.
	load.d		sp, 144(r1)

# Initialize gp, and lr.
	movi		gp,0x0_0001_0000
	movi		lr,0		# Note: usertrap does not return

# Jump to usertrap(), which will call usertrapret to "return" to the user mode code.
	jump		_P_xv6_usertrap

	.endfunction



# =====================  userret  =====================
#
# external userret (trapframe: ptr to TrapFrame)
#
# userret is invoked by usertrapret() to switch from kernel into user mode
# and resume executing a user process. It is passed the address of the
# process's Trapframe in r1.
#
userret:
	.function	"userret",line=0,framesize=0

# Invalidate all TLB registers for this process in case changes to the pagetable were made
	tlbclear

# Move user register r1  from the Trapframe into csr_ptr.
	load.d		r2, 0 (r1)
	csrwrite	csr_ptr,r2

# Restore all other user registers from the Trapframe.
	load.d		r2,   8(r1)
	load.d		r3,  16(r1)
	load.d		r4,  24(r1)
	load.d		r5,  32(r1)
	load.d		r6,  40(r1)
	load.d		r7,  48(r1)
	load.d		t,   56(r1)	# Must avoid implicit use of t after this point
	load.d		s0,  64(r1)
	load.d		s1,  72(r1)
	load.d		s2,  80(r1)
	load.d		tp,  88(r1)
	load.d		gp,  96(r1)
	load.d		lr, 104(r1)
	load.d		sp, 112(r1)

# Now restore r1 and make csr_ptr point to the Trapframe.
	csrswap		r1,csr_ptr,r1

# Return to executing in user mode. SYSRET will restore PC and csr_status
# from csr_prevpc and csr_stat2, which usertrapret() has just loaded.
	sysret

	.endfunction



# ====================  swtch  ====================

# Context switch
#
#   external swtch (old_, new_: ptr to Context)
# 
# This function is called to switch from a process's kernel thread to the scheduler
# thread, and back again. Save the critical registers that must be preserved across
# any call in the "old" context and restore the next thread's registers from "new".	
#
swtch:
	.function	"swtch",line=0,framesize=0

	store.d		0(r1), lr
	store.d		8(r1), sp
	csrread		r3,csr_status
	store.d		16(r1), r3

# Since csr_ptr & csr_pgtable are always loaded before entering user mode, we
# don't need to save/restore them here.

	load.d		lr, 0(r2)
	load.d		sp, 8(r2)
	load.d		r3, 16(r2)
	csrwrite	csr_status,r3

	ret
	.endfunction

