/* $NetBSD: cpu_switch.S,v 1.2.2.1 2023/07/31 13:36:32 martin Exp $ */

/*-
 * Copyright (c) 2014 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Matt Thomas of 3am Software Foundry.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <machine/asm.h>
#include "assym.h"

/*
 * struct lwp *
 * cpu_switchto(struct lwp *oldl, struct lwp *newl, bool returning);
 */
ENTRY_NP(cpu_switchto)
	addi	sp, sp, -TF_LEN		// allocate trapframe

	REG_S	ra, TF_RA(sp)		// save return address
	REG_S	s0, TF_S0(sp)		// save callee saved address
	REG_S	s1, TF_S1(sp)		// save callee saved address
	REG_S	s2, TF_S2(sp)		// save callee saved address
	REG_S	s3, TF_S3(sp)		// save callee saved address
	REG_S	s4, TF_S4(sp)		// save callee saved address
	REG_S	s5, TF_S5(sp)		// save callee saved address
	REG_S	s6, TF_S6(sp)		// save callee saved address
	REG_S	s7, TF_S7(sp)		// save callee saved address
	REG_S	s8, TF_S8(sp)		// save callee saved address
	REG_S	s9, TF_S9(sp)		// save callee saved address
	REG_S	s10, TF_S10(sp)		// save callee saved address
	REG_S	s11, TF_S11(sp)		// save callee saved address
	csrr	t4, sstatus		// get status for intr state
	REG_S	t4, TF_SR(sp)		// save it

	REG_S	sp, L_MD_KTF(a0)	// record trapframe pointer

	csrrci	t0, sstatus, SR_SIE	// # disable interrupts

	mv	tp, a1			// # put the new lwp in thread pointer

	PTR_L	t1, L_CPU(tp)		// # get curcpu

	/*
	 * Issue barriers to coordinate mutex_exit on this CPU with
	 * mutex_vector_enter on another CPU.
	 *
	 * 1. Any prior mutex_exit by oldlwp must be visible to other
	 *    CPUs before we set ci_curlwp := newlwp on this one,
	 *    requiring a store-before-store barrier.
	 *
	 * 2. ci_curlwp := newlwp must be visible on all other CPUs
	 *    before any subsequent mutex_exit by newlwp can even test
	 *    whether there might be waiters, requiring a
	 *    store-before-load barrier.
	 *
	 * See kern_mutex.c for details -- this is necessary for
	 * adaptive mutexes to detect whether the lwp is on the CPU in
	 * order to safely block without requiring atomic r/m/w in
	 * mutex_exit.
	 */
	fence	w,w
	PTR_S	tp, CI_CURLWP(t1)	// # update curcpu with the new curlwp
	fence	w,r

	REG_L	sp, L_MD_KTF(tp)	// # load its kernel stack pointer
	REG_L	t4, TF_SR(sp)		// # fetch status register
	csrw	sstatus, t4		// # restore it (and interrupts?)

	REG_L	s0, TF_S0(sp)		// restore callee saved
	REG_L	s1, TF_S1(sp)		// restore callee saved
	REG_L	s2, TF_S2(sp)		// restore callee saved
	REG_L	s3, TF_S3(sp)		// restore callee saved
	REG_L	s4, TF_S4(sp)		// restore callee saved
	REG_L	s5, TF_S5(sp)		// restore callee saved
	REG_L	s6, TF_S6(sp)		// restore callee saved
	REG_L	s7, TF_S7(sp)		// restore callee saved
	REG_L	s8, TF_S8(sp)		// restore callee saved
	REG_L	s9, TF_S9(sp)		// restore callee saved
	REG_L	s10, TF_S10(sp)		// restore callee saved
	REG_L	s11, TF_S11(sp)		// restore callee saved

	REG_L	ra, TF_RA(sp)		// restore return address

	addi	sp, sp, TF_LEN		// remove trapframe

	//	a0 = oldl
	//	a1 = curcpu()
	//	tp = newl

	ret
END(cpu_switchto)


ENTRY_NP(cpu_lwp_trampoline)
	mv	a1, tp			// get new lwp
	call	_C_LABEL(lwp_startup)	// call lwp startup

	mv	a0, s1			// get saved arg
	jalr	s0			// call saved func

	// If the saved func returns, we are returning to user land.
	j	_C_LABEL(exception_userexit)
END(cpu_lwp_trampoline)


ENTRY_NP(cpu_fast_switchto_cleanup)
//	PTR_L	t0, L_CPU(tp)		// Get curcpu()
//	INT_L	t1, CI_MTX_COUNT(t0)	// get mutex count
//	addi	t1, t1, 1		// increment mutex count
//	INT_S	t1, CI_MTX_COUNT(t0)	// save it
	mv	ra, a1			// Restore real RA
#if IPL_SCHED != IPL_HIGH
	tail	_C_LABEL(splhigh)	// go back to IPL HIGH
#else
	ret				// just return
#endif
END(cpu_fast_switchto_cleanup)


/*
 * void
 * cpu_fast_switchto(struct lwp *, int s);
 */
ENTRY_NP(cpu_fast_switchto)
	addi	sp, sp, -(TF_LEN + CALLFRAME_SIZ)
	REG_S	a0, (TF_LEN + CALLFRAME_S0)(sp)
	REG_S	ra, (TF_LEN + CALLFRAME_RA)(sp)

	PTR_LA	t2, _C_LABEL(cpu_fast_switchto_cleanup)

	REG_S	t2, TF_RA(sp)		// return to someplace else
	REG_S	s0, TF_S0(sp)		// save callee saved register
	REG_S	s1, TF_S1(sp)		// save callee saved register
	REG_S	s2, TF_S2(sp)		// save callee saved register
	REG_S	s3, TF_S3(sp)		// save callee saved register
	REG_S	s4, TF_S4(sp)		// save callee saved register
	REG_S	s5, TF_S5(sp)		// save callee saved register
	REG_S	s6, TF_S6(sp)		// save callee saved register
	REG_S	s7, TF_S7(sp)		// save callee saved register
	REG_S	s8, TF_S8(sp)		// save callee saved register
	REG_S	s9, TF_S9(sp)		// save callee saved register
	REG_S	s10, TF_S10(sp)		// save callee saved register
	REG_S	s11, TF_S11(sp)		// save callee saved register
	csrr	t4, sstatus		// get status register (for intr state)
	REG_S	t4, TF_SR(sp)		// save it

	mv	s0, tp			// remember curlwp
	mv	s1, sp			// remember kernel stack

	csrrci	t0, sstatus, SR_SIE	// disable interrupts
	PTR_L	t1, L_CPU(tp)		// get curcpu()

	PTR_S	sp, L_MD_KTF(tp)	// save trapframe ptr in oldlwp
	mv	tp, a0			// set thread pointer to newlwp
	fence	w,w			// for mutex_enter; see cpu_switchto
	PTR_S	tp, CI_CURLWP(t1)	// update curlwp
	fence	w,r			// for mutex_enter; see cpu_switchto
	PTR_L	sp, L_MD_KTF(tp)	// switch to its stack
	csrw	sstatus, t0		// reenable interrupts
	call	_C_LABEL(softint_dispatch)
	csrrci	t0, sstatus, SR_SIE	// disable interrupts
	PTR_L	t1, L_CPU(tp)		// get curcpu() again
	mv	tp, s0			// return to pinned lwp
	fence	w,w			// for mutex_enter; see cpu_switchto
	PTR_S	tp, CI_CURLWP(t1)	// restore curlwp
	fence	w,r			// for mutex_enter; see cpu_switchto
	csrw	sstatus, t0		// reenable interrupts
	mv	sp, s1			// restore stack pointer

	REG_L	ra, TF_RA(sp)		// get return address
	REG_L	s0, TF_S0(sp)		// restore register we used
	REG_L	s1, TF_S1(sp)		// restore register we used

	REG_L	a0, (TF_LEN + CALLFRAME_S0)(sp)	// Pass the softlwp
	REG_L	a1, (TF_LEN + CALLFRAME_RA)(sp)	// Pass the real RA

	addi	sp, sp, TF_LEN+CALLFRAME_SIZ	// drop trapframe/callframe
	ret				// return
END(cpu_fast_switchto)


/*
 * RISC-V only has a simple exception handler handles both synchronous traps
 * and interrupts.
 */
ENTRY_NP(cpu_exception_handler)
	csrrw	tp, sscratch, tp	// swap scratch and thread pointer
	beqz	tp, .Lexception_kernel	//   tp == 0, already on kernel stack
	//
	// The exception happened while user code was executing.  We need to
	// get the pointer to the user trapframe from the LWP md area.  Then we
	// save t1 and tp so we have a register to work with and to get curlwp
	// into tp.  We also save the saved SP into the trapframe.
	// Upon entry on an exception from user, sscratch will contain curlwp.
	//
	REG_S	sp, L_MD_USP(tp)	// save user stack pointer temporarily
	PTR_L	sp, L_MD_UTF(sp)	// trapframe pointer loaded
	REG_S	t1, TF_T1(sp)		// save t1
	REG_L	t1, L_MD_USP(tp)	// get user stack pointer
	REG_S	t1, TF_SP(sp)		// save thread pointer in trapframe
	csrrw	t1, sscratch, zero	// swap saved thread pointer with 0
	REG_L	t1, TF_TP(sp)		// save thread pointer in trapframe
	li	t1, 0			// indicate user exception
	j	.Lexception_common

	//
	// The exception happened while we were already in the kernel.  That
	// means tp already has curlwp and sp has the kernel stack pointer so
	// just need to restore it and then adjust it down for space for the
	// trap frame.  We save t1 so we can use it the original sp into the
	// trapframe for use by the exception exiting code.
	//
.Lexception_kernel:
	csrrw	tp, sscratch, zero	// get back our thread pointer
	addi	sp, sp, -TF_LEN		// allocate stack frame
	REG_S	t1, TF_T1(sp)		// save t1
	addi	t1, sp, TF_LEN
	REG_S	t1, TF_SP(sp)		// save SP
	li	t1, 1			// indicate kernel exception

.Lexception_common:
	// Now we save all the temporary registers into the trapframe since
	// they will most certainly be changed.
	REG_S	ra, TF_RA(sp)		// save return address
	REG_S	gp, TF_GP(sp)		// save gp
	REG_S	a0, TF_A0(sp)		// save a0
	REG_S	a1, TF_A1(sp)		// save a1
	REG_S	a2, TF_A2(sp)		// save a2
	REG_S	a3, TF_A3(sp)		// save a3
	REG_S	a4, TF_A4(sp)		// save a4
	REG_S	a5, TF_A5(sp)		// save a5
	REG_S	a6, TF_A6(sp)		// save a6
	REG_S	a7, TF_A7(sp)		// save a7
	REG_S	t0, TF_T0(sp)		// save t0
					// t1 is already saved
	REG_S	t2, TF_T2(sp)		// save t2
	REG_S	t3, TF_T3(sp)		// save t3
	REG_S	t4, TF_T4(sp)		// save t4
	REG_S	t5, TF_T5(sp)		// save t5
	REG_S	t6, TF_T6(sp)		// save t6

	// Now we get the
	mv	a0, sp			// trapframe pointer
	csrr	a1, sepc		// get exception pc
	csrr	a2, sstatus		// get status
	csrr	a3, scause		// get cause

	REG_S	a1, TF_PC(sp)
	REG_S	a2, TF_SR(sp)
	REG_S	a3, TF_CAUSE(sp)	// save cause

	// Now we've saved the trapfame, the cause is still in a3.

	bltz	a3, intr_handler	// MSB is set if interrupt

	// stval is only relevant for non-interrupts
	csrr	a4, stval		// get stval
	REG_S	a4, TF_TVAL(sp)

	beqz	t1, trap_user		// this was a user trap
	// This was a kernel exception
	call	_C_LABEL(cpu_trap)	// just call trap to handle it
exception_kernexit:
	// If we got here, we are returning from a kernel exception (either a
	// trap or interrupt).  Simply return the volatile registers and the
	// exception PC and status, load the saved SP from the trapframe, and
	// return from the exception
	csrrci	zero, sstatus, SR_SIE	// disable interrupts

	REG_L	ra, TF_RA(sp)		// restore return address
	REG_L	gp, TF_GP(sp)		// restore gp
	REG_L	a0, TF_A0(sp)		// restore a0
	REG_L	a1, TF_A1(sp)		// restore a1
	REG_L	a2, TF_A2(sp)		// restore a2
	REG_L	a3, TF_A3(sp)		// restore a3
	REG_L	a4, TF_A4(sp)		// restore a4
	REG_L	a5, TF_A5(sp)		// restore a5
	REG_L	a6, TF_A6(sp)		// restore a6
	REG_L	a7, TF_A7(sp)		// restore a7
	REG_L	t2, TF_T2(sp)		// restore t2
	REG_L	t3, TF_T3(sp)		// restore t3
	REG_L	t4, TF_T4(sp)		// restore t4
	REG_L	t5, TF_T5(sp)		// restore t5
	REG_L	t6, TF_T6(sp)		// restore t6

	REG_L	t0, TF_PC(sp)		// fetch exception PC
	REG_L	t1, TF_SR(sp)		// fetch status

	csrw	sepc, t0		// restore exception PC
	csrw	sstatus, t1		// restore status

	REG_L	t0, TF_T0(sp)		// restore t0
	REG_L	t1, TF_T1(sp)		// restore t1
	REG_L	sp, TF_SP(sp)		// restore SP
	sret				// and we're done

trap_user:
	REG_S	s0, TF_S0(sp)		// only save from userland
	REG_S	s1, TF_S1(sp)		// only save from userland
	REG_S	s2, TF_S2(sp)		// only save from userland
	REG_S	s3, TF_S3(sp)		// only save from userland
	REG_S	s4, TF_S4(sp)		// only save from userland
	REG_S	s5, TF_S5(sp)		// only save from userland
	REG_S	s6, TF_S6(sp)		// only save from userland
	REG_S	s7, TF_S7(sp)		// only save from userland
	REG_S	s8, TF_S8(sp)		// only save from userland
	REG_S	s9, TF_S9(sp)		// only save from userland
	REG_S	s10, TF_S10(sp)		// only save from userland
	REG_S	s11, TF_S11(sp)		// only save from userland

	csrsi	sstatus, SR_SIE		// reenable interrupts

	li	t0, CAUSE_SYSCALL	// let's see if this was a syscall
	beq	a3, t0, trap_syscall	//   yes it was

	call	_C_LABEL(cpu_trap)	// nope, just a regular trap
_C_LABEL(exception_userexit):
	INT_L	t0, L_MD_ASTPENDING(tp)	// ast pending?
	bnez	t0, trap_doast		//   yes, handle it.
	csrrci	zero, sstatus, SR_SIE	// disable interrupts
	csrw	sscratch, tp		// show we are coming from userland
	REG_L	tp, TF_TP(sp)		// only restore from userland
	REG_L	s0, TF_S0(sp)		// only restore from userland
	REG_L	s1, TF_S1(sp)		// only restore from userland
	REG_L	s2, TF_S2(sp)		// only restore from userland
	REG_L	s3, TF_S3(sp)		// only restore from userland
	REG_L	s4, TF_S4(sp)		// only restore from userland
	REG_L	s5, TF_S5(sp)		// only restore from userland
	REG_L	s6, TF_S6(sp)		// only restore from userland
	REG_L	s7, TF_S7(sp)		// only restore from userland
	REG_L	s8, TF_S8(sp)		// only restore from userland
	REG_L	s9, TF_S9(sp)		// only restore from userland
	REG_L	s10, TF_S10(sp)		// only restore from userland
	REG_L	s11, TF_S11(sp)		// only restore from userland
	j	exception_kernexit

trap_syscall:
	PTR_LA	ra, exception_userexit
	PTR_L	t0, L_PROC(tp)		// get proc struct
	PTR_L	t0, P_MD_SYSCALL(t0)	// get syscall address from proc
	jr	t0			// and jump to it

intr_usersave:
	REG_S	s0, TF_S0(sp)		// only save from userland
	REG_S	s1, TF_S1(sp)		// only save from userland
	REG_S	s2, TF_S2(sp)		// only save from userland
	REG_S	s3, TF_S3(sp)		// only save from userland
	REG_S	s4, TF_S4(sp)		// only save from userland
	REG_S	s5, TF_S5(sp)		// only save from userland
	REG_S	s6, TF_S6(sp)		// only save from userland
	REG_S	s7, TF_S7(sp)		// only save from userland
	REG_S	s8, TF_S8(sp)		// only save from userland
	REG_S	s9, TF_S9(sp)		// only save from userland
	REG_S	s10, TF_S10(sp)		// only save from userland
	REG_S	s11, TF_S11(sp)		// only save from userland
	PTR_LA	ra, exception_userexit
trap_doast:
	mv	a0, sp			// only argument is trapframe
	tail	_C_LABEL(cpu_ast)

intr_user:
	call	_C_LABEL(cpu_intr)	// handle interrupt
	INT_L	t0, L_MD_ASTPENDING(tp)	// get astpending
	bnez	t0, intr_usersave	//    if one is pending, deal with in

	csrw	sscratch, tp		// show we are coming from userland
	REG_L	tp, TF_TP(sp)		// restore thread pointer
	j	exception_kernexit	// do standard exception exit

intr_handler:
	beqz	t1, intr_user
	call	_C_LABEL(cpu_intr)
	j	exception_kernexit
END(cpu_exception_handler)


/*
 * int
 * cpu_set_onfault(struct faultbuf *fb, register_t retval)
 */
ENTRY_NP(cpu_set_onfault)
	REG_S	ra, FB_RA(a0)
	REG_S	s0, FB_S0(a0)
	REG_S	s1, FB_S1(a0)
	REG_S	s2, FB_S2(a0)
	REG_S	s3, FB_S3(a0)
	REG_S	s4, FB_S4(a0)
	REG_S	s5, FB_S5(a0)
	REG_S	s6, FB_S6(a0)
	REG_S	s7, FB_S7(a0)
	REG_S	s8, FB_S8(a0)
	REG_S	s9, FB_S9(a0)
	REG_S	s10, FB_S10(a0)
	REG_S	s11, FB_S11(a0)
	REG_S	sp, FB_SP(a0)
	REG_S	a1, FB_A0(a0)
	PTR_S	a0, L_MD_ONFAULT(tp)
	li	a0, 0
	ret
END(cpu_set_onfault)


ENTRY_NP(setjmp)
	REG_S	ra, FB_RA(a0)
	REG_S	s0, FB_S0(a0)
	REG_S	s1, FB_S1(a0)
	REG_S	s2, FB_S2(a0)
	REG_S	s3, FB_S3(a0)
	REG_S	s4, FB_S4(a0)
	REG_S	s5, FB_S5(a0)
	REG_S	s6, FB_S6(a0)
	REG_S	s7, FB_S7(a0)
	REG_S	s8, FB_S8(a0)
	REG_S	s9, FB_S9(a0)
	REG_S	s10, FB_S10(a0)
	REG_S	s11, FB_S11(a0)
	REG_S	sp, FB_SP(a0)
	li	a0, 0
	ret
END(setjmp)


ENTRY_NP(longjmp)
	REG_L	ra, FB_RA(a0)
	REG_L	s0, FB_S0(a0)
	REG_L	s1, FB_S1(a0)
	REG_L	s2, FB_S2(a0)
	REG_L	s3, FB_S3(a0)
	REG_L	s4, FB_S4(a0)
	REG_L	s5, FB_S5(a0)
	REG_L	s6, FB_S6(a0)
	REG_L	s7, FB_S7(a0)
	REG_L	s8, FB_S8(a0)
	REG_L	s9, FB_S9(a0)
	REG_L	s10, FB_S10(a0)
	REG_L	s11, FB_S11(a0)
	REG_L	sp, FB_SP(a0)
	mv	a0, a1
	ret
END(longjmp)
