/* $NetBSD: cpuswitch.S,v 1.41 2023/03/01 08:17:24 riastradh Exp $ */

/*-
 * Copyright (c) 2014, 2020 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 <aarch64/asm.h>
#include <aarch64/locore.h>

#include "assym.h"

#include "opt_cpuoptions.h"
#include "opt_ddb.h"
#include "opt_kasan.h"

RCSID("$NetBSD: cpuswitch.S,v 1.41 2023/03/01 08:17:24 riastradh Exp $")

	ARMV8_DEFINE_OPTIONS

/*
 * At IPL_SCHED:
 *	x0 = oldlwp
 *	x1 = newlwp
 *	x2 = returning
 * returns x0-x2 unchanged
 */
ENTRY_NP(cpu_switchto)
	/*
	 * Store the callee saved register on the stack.
	 */
	sub	sp, sp, #TF_SIZE		/* make switchframe */
	stp	x19, x20, [sp, #TF_X19]
	stp	x21, x22, [sp, #TF_X21]
	stp	x23, x24, [sp, #TF_X23]
	stp	x25, x26, [sp, #TF_X25]
	stp	x27, x28, [sp, #TF_X27]
	stp	x29, x30, [sp, #TF_X29]
#ifdef DDB
	/* mark as switchframe for backtrace */
	.if TF_SP + 8 == TF_PC
	stp	xzr, lr, [sp, #TF_SP]
	.else
	str	xzr, [sp, #TF_SP]
	str	lr, [sp, #TF_PC]
	.endif
#endif

	/*
	 * Save the current stack pointer and the CPACR and save them in
	 * old lwp md area.
	 */
	mov	x4, sp
	mrs	x5, cpacr_el1
	str	x5, [x0, #L_MD_CPACR]
	ldr	x6, [x0, #L_PCB]	/* x6 = lwp_getpcb(oldlwp) */
	str	x4, [x6, #PCB_TF]

	/* We are done with the old lwp */
	ldr	x6, [x1, #L_PCB]	/* x6 = lwp_getpcb(newlwp) */
	ldr	x4, [x6, #PCB_TF]	/* get trapframe ptr (aka SP) */
#ifdef DDB
	str	xzr, [x6, #PCB_TF]	/* clear l->l_addr->pcb_tf */
#endif
	ldr	x5, [x1, #L_MD_CPACR]	/* get cpacr_el1 */

	DISABLE_INTERRUPT
	mov	sp, x4			/* restore stack pointer */
	msr	cpacr_el1, x5		/* restore cpacr_el1 */
	isb

#ifdef ARMV83_PAC
	/* Switch the PAC key. */
	adrl	x4, _C_LABEL(aarch64_pac_enabled)
	ldr	w4, [x4]
	cbz	w4, 1f
	ldp	x5, x6, [x1, #L_MD_IA_KERN]
	msr	APIAKeyLo_EL1, x5
	msr	APIAKeyHi_EL1, x6

	/* Other keys only need to be updated when switching to user process */
	ldr	w5, [x1, #L_FLAG]
	and	w5, w5, #LW_SYSTEM	/* (lwp->l_flag & LW_SYSTEM) ? */
	cbnz	w5, 1f

	ldp	x5, x6, [x1, #L_MD_IB_USER]
	msr	APIBKeyLo_EL1, x5
	msr	APIBKeyHi_EL1, x6
	ldp	x5, x6, [x1, #L_MD_DA_USER]
	msr	APDAKeyLo_EL1, x5
	msr	APDAKeyHi_EL1, x6
	ldp	x5, x6, [x1, #L_MD_DB_USER]
	msr	APDBKeyLo_EL1, x5
	msr	APDBKeyHi_EL1, x6
	ldp	x5, x6, [x1, #L_MD_GA_USER]
	msr	APGAKeyLo_EL1, x5
	msr	APGAKeyHi_EL1, x6
1:
	isb
#endif

	msr	tpidr_el1, x1		/* switch curlwp to new lwp */
	ldr	x3, [x1, #L_CPU]

	/*
	 * 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.
	 */
	dmb	ishst			/* store-before-store */
	str	x1, [x3, #CI_CURLWP]	/* switch curlwp to new lwp */
	dmb	ish			/* store-before-load */

	ENABLE_INTERRUPT

	/*
	 * Restore callee save registers.
	 */
	ldp	x19, x20, [sp, #TF_X19]
	ldp	x21, x22, [sp, #TF_X21]
	ldp	x23, x24, [sp, #TF_X23]
	ldp	x25, x26, [sp, #TF_X25]
	ldp	x27, x28, [sp, #TF_X27]
	ldp	x29, lr, [sp, #TF_X29]
	add	sp, sp, #TF_SIZE	/* unwind switchframe */

	ret
END(cpu_switchto)

/*
 * void
 * cpu_switchto_softint(struct lwp *softlwp, int ipl)
 * {
 *	build a switchframe on kernel stack.
 *	craft TF_X30 to have softint_cleanup.
 *	pinned_lwp = curlwp
 *	switch to softlwp context.
 *	call softint_dispatch(pinned_lwp, ipl);
 *	switch back to pinned_lwp context.
 *	unwind switchframe made on kernel stack.
 *	return to caller this time.
 * }
 */
ENTRY_NP(cpu_switchto_softint)
#ifdef DDB
	mov	x7, sp			/* keep original sp for backtrace */
#endif
	stp	x19, x20, [sp, #-16]!	/* save */
	sub	sp, sp, #TF_SIZE	/* make switchframe */
	adr	x2, softint_cleanup	/* return address for cpu_switchto() */
	mrs	x19, daif		/* x19 := original interrupt mask */
	stp	x19, lr, [sp, #TF_X19]	/* x20 := original lr */
	stp	x21, x22, [sp, #TF_X21]
	stp	x23, x24, [sp, #TF_X23]
	stp	x25, x26, [sp, #TF_X25]
	stp	x27, x28, [sp, #TF_X27]
	stp	x29, x2, [sp, #TF_X29]	/* tf->tf_lr = softint_cleanup; */

	mrs	x19, tpidr_el1		/* x19 := curlwp */
	mov	x4, sp

	mrs	x5, cpacr_el1
	ldr	x6, [x19, #L_PCB]	/* x6 = lwp_getpcb(curlwp) */
	str	x4, [x6, #PCB_TF]
	str	x5, [x19, #L_MD_CPACR]

#ifdef KASAN
	/* clear the new stack */
	stp	x0, x1, [sp, #-16]!
#ifdef DDB
	stp	x7, lr, [sp, #-16]!	/* original sp and lr */
#endif
	bl	_C_LABEL(kasan_softint)
#ifdef DDB
	ldp	x7, lr, [sp], #16
#endif
	ldp	x0, x1, [sp], #16
#endif

	ldr	x4, [x0, #L_MD_UTF]

	DISABLE_INTERRUPT
	ldr	x20, [x19, #L_CPU]	/* x20 := curlwp->l_cpu */

	/* onto new stack */
	sub	sp, x4, #TF_SIZE	/* new sp := softlwp->l_md_utf - 1 */
	msr	tpidr_el1, x0		/* curlwp = softlwp; */
	dmb	ishst			/* for mutex_enter; see cpu_switchto */
	str	x0, [x20, #CI_CURLWP]	/* curcpu()->ci_curlwp = softlwp; */
	/*
	 * No need for barrier after ci->ci_curlwp = softlwp -- when we
	 * enter a softint lwp, it can't be holding any mutexes, so it
	 * can't release any until after it has acquired them, so we
	 * need not participate in the protocol with mutex_vector_enter
	 * barriers here.
	 */

	mov	x5, #CPACR_FPEN_NONE
	msr	cpacr_el1, x5		/* cpacr_el1 = CPACR_FPEN_NONE */
	isb

#ifdef ARMV83_PAC
	/* Switch the PAC key. */
	adrl	x4, _C_LABEL(aarch64_pac_enabled)
	ldr	w4, [x4]
	cbz	w4, 1f
	ldp	x5, x6, [x0, #L_MD_IA_KERN]
	msr	APIAKeyLo_EL1, x5
	msr	APIAKeyHi_EL1, x6
	isb
1:
#endif
	ENABLE_INTERRUPT

#ifdef DDB
	/* for db_trace.c:db_sp_trace() */
	stp	x7, lr, [sp, #-16]!	/* push original sp,lr for backtrace info */
#endif

	/* softint_dispatch(pinned_lwp, ipl) */
	mov	x0, x19			/* x0 := pinned_lwp */
	bl	_C_LABEL(softint_dispatch)

#ifdef DDB
	add	sp, sp, #16		/* pop backtrace info */
#endif

	ldr	x6, [x19, #L_PCB]	/* x6 = lwp_getpcb(curlwp) */
	ldr	x4, [x6, #PCB_TF]	/* x4 := pinned_lwp->l_addr->pcb_tf */
#ifdef DDB
	str	xzr, [x6, #PCB_TF]	/* clear l->l_addr->pcb_tf */
#endif
	ldr	x5, [x19, #L_MD_CPACR]	/* x5 := pinned_lwp->l_md_cpacr */

	DISABLE_INTERRUPT
	msr	tpidr_el1, x19		/* curlwp = pinned_lwp */
	ldr	x3, [x19, #L_CPU]	/* x3 = curlwp->l_cpu */
	dmb	ishst			/* for mutex_enter; see cpu_switchto */
	str	x19, [x3, #CI_CURLWP]	/* curlwp->l_cpu->ci_curlwp := x19 */
	dmb	ish			/* for mutex_enter; see cpu_switchto */

	mov	sp, x4			/* restore pinned_lwp sp */
	msr	cpacr_el1, x5		/* restore pinned_lwp cpacr */
	isb

#ifdef ARMV83_PAC
	/* Restore the PAC key. */
	adrl	x4, _C_LABEL(aarch64_pac_enabled)
	ldr	w4, [x4]
	cbz	w4, 1f
	ldp	x5, x6, [x19, #L_MD_IA_KERN]
	msr	APIAKeyLo_EL1, x5
	msr	APIAKeyHi_EL1, x6
	isb
1:
#endif

	ldp	x19, lr, [sp, #TF_X19]	/* restore pinned_lwp lr */
	msr	daif, x19		/* restore interrupt mask */

	add	sp, sp, #TF_SIZE	/* unwind switchframe */
	ldp	x19, x20, [sp], #16	/* restore */
	ret
END(cpu_switchto_softint)

/*
 * void
 * softint_cleanup(struct lwp *softlwp)
 * {
 *	cpu_switchto() bottom half arranges to start this when softlwp.
 *	kernel thread is to yield CPU for the pinned_lwp in the above.
 *	curcpu()->ci_mtx_count += 1;
 *	this returns as if cpu_switchto_softint finished normally.
 * }
 */
ENTRY_NP(softint_cleanup)
	mov	lr, x20			/* restore original lr */

	mrs	x20, tpidr_el1		/* curlwp */
	ldr	x3, [x20, #L_CPU]	/* curcpu */
	ldr	w2, [x3, #CI_MTX_COUNT]	/* ->ci_mtx_count */
	add	w2, w2, #1
	str	w2, [x3, #CI_MTX_COUNT]

	msr	daif, x19		/* restore interrupt mask */
	ldp	x19, x20, [sp], #16	/* restore */
	ret
END(softint_cleanup)

/*
 * Called at IPL_SCHED:
 *	x0 = old lwp (from cpu_switchto)
 *	x1 = new lwp (from cpu_switchto)
 *	x27 = func
 *	x28 = arg
 */
ENTRY_NP(lwp_trampoline)
	stp	x0, x1, [sp, #-16]!	/* save x0, x1 */
	bl	_C_LABEL(lwp_startup)
	ldp	x0, x1, [sp], #16	/* restore x0, x1 */

#ifdef ARMV83_PAC
	/* Generate a new PAC key. */
	adrl	x4, _C_LABEL(aarch64_pac_enabled)
	ldr	w4, [x4]
	cbz	w4, 1f
	mov	x26, x1

	bl	_C_LABEL(cprng_strong64)
	mov	x25, x0
	bl	_C_LABEL(cprng_strong64)
	stp	x25, x0, [x26, #L_MD_IA_KERN]
	msr	APIAKeyLo_EL1, x25
	msr	APIAKeyHi_EL1, x0
	isb
1:
#endif

	/*
	 * When the x27 function returns, it will jump to el0_trap_exit.
	 */
	adr	lr, el0_trap_exit	/* tail call via lr */
	mov	x0, x28			/* mov arg into place */
	mov	x16, x27		/* use x16 as jump register, for BTI */
	br	x16			/* call function with arg */
END(lwp_trampoline)


/*
 * int cpu_set_onfault(struct faultbuf *fb)
 */
ENTRY_NP(cpu_set_onfault)
	mrs	x2, tpidr_el1		/* x2 = curlwp */
	str	x0, [x2, #L_MD_ONFAULT] /* l_md.md_onfault = fb */

	stp	x19, x20, [x0, #(FB_X19 * 8)]
	stp	x21, x22, [x0, #(FB_X21 * 8)]
	stp	x23, x24, [x0, #(FB_X23 * 8)]
	stp	x25, x26, [x0, #(FB_X25 * 8)]
	stp	x27, x28, [x0, #(FB_X27 * 8)]
	stp	x29, x30, [x0, #(FB_X29 * 8)]
	mov	x1, sp
	str	x1, [x0, #(FB_SP * 8)]
	mov	x0, #0
	ret
END(cpu_set_onfault)

/*
 * setjmp(9)
 * int setjmp(label_t *label);
 * void longjmp(label_t *label);
 */
ENTRY_NP(setjmp)
	stp	x19, x20, [x0, #(LBL_X19 * 8)]
	stp	x21, x22, [x0, #(LBL_X21 * 8)]
	stp	x23, x24, [x0, #(LBL_X23 * 8)]
	stp	x25, x26, [x0, #(LBL_X25 * 8)]
	stp	x27, x28, [x0, #(LBL_X27 * 8)]
	stp	x29, x30, [x0, #(LBL_X29 * 8)]
	mov	x1, sp
	str	x1, [x0, #(LBL_SP * 8)]
	mov	x0, #0
	ret
END(setjmp)

ENTRY_NP(longjmp)
	ldp	x19, x20, [x0, #(LBL_X19 * 8)]
	ldp	x21, x22, [x0, #(LBL_X21 * 8)]
	ldp	x23, x24, [x0, #(LBL_X23 * 8)]
	ldp	x25, x26, [x0, #(LBL_X25 * 8)]
	ldp	x27, x28, [x0, #(LBL_X27 * 8)]
	ldp	x29, x30, [x0, #(LBL_X29 * 8)]
	ldr	x1, [x0, #(LBL_SP * 8)]
	mov	sp, x1
	mov	x0, #1
	ret
END(longjmp)

ENTRY_NP(load_fpregs)
	ldp	q0, q1, [x0, #FPREG_Q0]
	ldp	q2, q3, [x0, #FPREG_Q2]
	ldp	q4, q5, [x0, #FPREG_Q4]
	ldp	q6, q7, [x0, #FPREG_Q6]
	ldp	q8, q9, [x0, #FPREG_Q8]
	ldp	q10, q11, [x0, #FPREG_Q10]
	ldp	q12, q13, [x0, #FPREG_Q12]
	ldp	q14, q15, [x0, #FPREG_Q14]
	ldp	q16, q17, [x0, #FPREG_Q16]
	ldp	q18, q19, [x0, #FPREG_Q18]
	ldp	q20, q21, [x0, #FPREG_Q20]
	ldp	q22, q23, [x0, #FPREG_Q22]
	ldp	q24, q25, [x0, #FPREG_Q24]
	ldp	q26, q27, [x0, #FPREG_Q26]
	ldp	q28, q29, [x0, #FPREG_Q28]
	ldp	q30, q31, [x0, #FPREG_Q30]
	ldr	w8, [x0, #FPREG_FPCR]
	ldr	w9, [x0, #FPREG_FPSR]
	msr	fpcr, x8
	msr	fpsr, x9
	ret
END(load_fpregs)

ENTRY_NP(save_fpregs)
	stp	q0, q1, [x0, #FPREG_Q0]
	stp	q2, q3, [x0, #FPREG_Q2]
	stp	q4, q5, [x0, #FPREG_Q4]
	stp	q6, q7, [x0, #FPREG_Q6]
	stp	q8, q9, [x0, #FPREG_Q8]
	stp	q10, q11, [x0, #FPREG_Q10]
	stp	q12, q13, [x0, #FPREG_Q12]
	stp	q14, q15, [x0, #FPREG_Q14]
	stp	q16, q17, [x0, #FPREG_Q16]
	stp	q18, q19, [x0, #FPREG_Q18]
	stp	q20, q21, [x0, #FPREG_Q20]
	stp	q22, q23, [x0, #FPREG_Q22]
	stp	q24, q25, [x0, #FPREG_Q24]
	stp	q26, q27, [x0, #FPREG_Q26]
	stp	q28, q29, [x0, #FPREG_Q28]
	stp	q30, q31, [x0, #FPREG_Q30]
	mrs	x8, fpcr
	mrs	x9, fpsr
	str	w8, [x0, #FPREG_FPCR]
	str	w9, [x0, #FPREG_FPSR]
	ret
END(save_fpregs)
