/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * System call trap handler.
 */
#include <sys/asm_linkage.h>
#include <sys/machpcb.h>
#include <sys/machthread.h>
#include <sys/syscall.h>
#include <sys/trap.h>
#include <sys/machtrap.h>
#include <sys/pcb.h>
#include <sys/machparam.h>

#include "assym.h"

#ifdef TRAPTRACE
#include <sys/traptrace.h>
#endif /* TRAPTRACE */

#if (1 << SYSENT_SHIFT) != SYSENT_SIZE
#error	"SYSENT_SHIFT does not correspond to size of sysent structure"
#endif
	
/*
 * Native System call trap handler.
 *
 * We branch here from sys_trap when a 64-bit system call occurs.
 *
 * Entry:
 *	%o0 = regs
 *
 * Usage:
 *	%l0 = saved return address
 *	%l1 = saved regs
 *	%l2 = lwp
 */
	ENTRY_NP(syscall_trap)
	ldn	[THREAD_REG + T_CPU], %g1	! get cpu pointer
	mov	%o7, %l0			! save return addr
	!
	! If the trapping thread has the address mask bit set, then it's
	!   a 32-bit process, and has no business calling 64-bit syscalls.
	!
	ldx	[%o0 + TSTATE_OFF], %l1		! saved %tstate.am is that
	andcc	%l1, TSTATE_AM, %l1		!   of the trapping proc
	bne,pn	%xcc, _syscall_ill		!
	mov	%o0, %l1			! save reg pointer
	mov	%i0, %o0			! copy 1st arg
	mov	%i1, %o1			! copy 2nd arg
	ldx	[%g1 + CPU_STATS_SYS_SYSCALL], %g2
	inc	%g2				! cpu_stats.sys.syscall++
	stx	%g2, [%g1 + CPU_STATS_SYS_SYSCALL]

	!
	! Set new state for LWP
	!
	ldn	[THREAD_REG + T_LWP], %l2
	mov	LWP_SYS, %g3
	mov	%i2, %o2			! copy 3rd arg
	stb	%g3, [%l2 + LWP_STATE]
	mov	%i3, %o3			! copy 4th arg
	ldx	[%l2 + LWP_RU_SYSC], %g2	! pesky statistics
	mov	%i4, %o4			! copy 5th arg
	addx	%g2, 1, %g2
	stx	%g2, [%l2 + LWP_RU_SYSC]
	mov	%i5, %o5			! copy 6th arg
	! args for direct syscalls now set up

#ifdef TRAPTRACE
	!
	! make trap trace entry - helps in debugging
	!
	rdpr	%pstate, %l3
	andn	%l3, PSTATE_IE | PSTATE_AM, %g3
	wrpr	%g0, %g3, %pstate		! disable interrupt
	TRACE_PTR(%g3, %g2)			! get trace pointer
	GET_TRACE_TICK(%g1, %g2)
	stxa	%g1, [%g3 + TRAP_ENT_TICK]%asi
	ldx	[%l1 + G1_OFF], %g1		! get syscall code
	TRACE_SAVE_TL_VAL(%g3, %g1)
	TRACE_SAVE_GL_VAL(%g3, %g0)
	set	TT_SC_ENTR, %g2
	stha	%g2, [%g3 + TRAP_ENT_TT]%asi
	stxa	%g7, [%g3 + TRAP_ENT_TSTATE]%asi ! save thread in tstate space
	stna	%sp, [%g3 + TRAP_ENT_SP]%asi
	stna	%o0, [%g3 + TRAP_ENT_F1]%asi
	stna	%o1, [%g3 + TRAP_ENT_F2]%asi
	stna	%o2, [%g3 + TRAP_ENT_F3]%asi
	stna	%o3, [%g3 + TRAP_ENT_F4]%asi
	stna	%o4, [%g3 + TRAP_ENT_TPC]%asi
	stna	%o5, [%g3 + TRAP_ENT_TR]%asi
	TRACE_NEXT(%g3, %g2, %g1)		! set new trace pointer
	wrpr	%g0, %l3, %pstate		! enable interrupt
#endif /* TRAPTRACE */

	!
	! Test for pre-system-call handling
	!
	ldub	[THREAD_REG + T_PRE_SYS], %g3	! pre-syscall proc?
#ifdef SYSCALLTRACE
	sethi	%hi(syscalltrace), %g4
	ld	[%g4 + %lo(syscalltrace)], %g4
	orcc	%g3, %g4, %g0			! pre_syscall OR syscalltrace?
#else
	tst	%g3				! is pre_syscall flag set?
#endif /* SYSCALLTRACE */

	bnz,pn	%icc, _syscall_pre
	nop

	! Fast path invocation of new_mstate

	mov	LMS_USER, %o0
	call	syscall_mstate
	mov	LMS_SYSTEM, %o1
	
	ldx	[%l1 + O0_OFF], %o0		! restore %o0
	ldx	[%l1 + O1_OFF], %o1		! restore %o1
	ldx	[%l1 + O2_OFF], %o2
	ldx     [%l1 + O3_OFF], %o3
	ldx     [%l1 + O4_OFF], %o4
	ldx	[%l1 + O5_OFF], %o5
	
	! lwp_arg now set up
3:
	!
	! Call the handler.  The %o's and lwp_arg have been set up.
	!
	ldx	[%l1 + G1_OFF], %g1		! get code
	set	sysent, %g3			! load address of vector table
	cmp	%g1, NSYSCALL			! check range
	sth	%g1, [THREAD_REG + T_SYSNUM]	! save syscall code
	bgeu,pn	%ncc, _syscall_ill
	  sll	%g1, SYSENT_SHIFT, %g4			! delay - get index 
	add	%g3, %g4, %l4
	ldn	[%l4 + SY_CALLC], %g3		! load system call handler

	call	%g3				! call system call handler
	  nop
	!
	! If handler returns two ints, then we need to split the 64-bit
	! return value in %o0 into %o0 and %o1
	!
	lduh	[%l4 + SY_FLAGS], %l4		! load sy_flags
	andcc	%l4, SE_32RVAL2, %g0		! check for 2 x 32-bit
	bz,pt	%xcc, 5f
	  nop
	srl	%o0, 0, %o1			! lower 32-bits into %o1
	srlx	%o0, 32, %o0			! upper 32-bits into %o0
5:

#ifdef TRAPTRACE
	!
	! make trap trace entry for return - helps in debugging
	!
	rdpr	%pstate, %g5
	andn	%g5, PSTATE_IE | PSTATE_AM, %g4
	wrpr	%g0, %g4, %pstate		! disable interrupt
	TRACE_PTR(%g4, %g2)			! get trace pointer
	GET_TRACE_TICK(%g2, %g3)
	stxa	%g2, [%g4 + TRAP_ENT_TICK]%asi
	lduh	[THREAD_REG + T_SYSNUM], %g2
	TRACE_SAVE_TL_VAL(%g4, %g2)
	TRACE_SAVE_GL_VAL(%g4, %g0)
	mov	TT_SC_RET, %g2			! system call return code
	stha	%g2, [%g4 + TRAP_ENT_TT]%asi
	ldn	[%l1 + nPC_OFF], %g2		! get saved npc (new pc)
	stna	%g2, [%g4 + TRAP_ENT_TPC]%asi
	ldx	[%l1 + TSTATE_OFF], %g2		! get saved tstate
	stxa	%g2, [%g4 + TRAP_ENT_TSTATE]%asi
	stna	%sp, [%g4 + TRAP_ENT_SP]%asi
	stna	THREAD_REG, [%g4 + TRAP_ENT_TR]%asi
	stna	%o0, [%g4 + TRAP_ENT_F1]%asi
	stna	%o1, [%g4 + TRAP_ENT_F2]%asi
	stna	%g0, [%g4 + TRAP_ENT_F3]%asi
	stna	%g0, [%g4 + TRAP_ENT_F4]%asi
	TRACE_NEXT(%g4, %g2, %g3)		! set new trace pointer
	wrpr	%g0, %g5, %pstate		! enable interrupt
#endif /* TRAPTRACE */
	!
	! Check for post-syscall processing.
	! This tests all members of the union containing t_astflag, t_post_sys,
	! and t_sig_check with one test.
	!
	ld	[THREAD_REG + T_POST_SYS_AST], %g1
#ifdef SYSCALLTRACE
	sethi	%hi(syscalltrace), %g4
	ld	[%g4 + %lo(syscalltrace)], %g4
	orcc	%g4, %g1, %g0			! OR in syscalltrace
#else
	tst	%g1				! need post-processing?
#endif /* SYSCALLTRACE */
	bnz,pn	%icc, _syscall_post		! yes - post_syscall or AST set
	mov	LWP_USER, %g1
	stb	%g1, [%l2 + LWP_STATE]		! set lwp_state
	stx	%o0, [%l1 + O0_OFF]		! set rp->r_o0
	stx	%o1, [%l1 + O1_OFF]		! set rp->r_o1
	clrh	[THREAD_REG + T_SYSNUM]		! clear syscall code
	ldx	[%l1 + TSTATE_OFF], %g1		! get saved tstate
	ldn	[%l1 + nPC_OFF], %g2		! get saved npc (new pc)
	mov	CCR_IC, %g3
	sllx	%g3, TSTATE_CCR_SHIFT, %g3
	add	%g2, 4, %g4			! calc new npc
	andn	%g1, %g3, %g1			! clear carry bit for no error
	stn	%g2, [%l1 + PC_OFF]
	stn	%g4, [%l1 + nPC_OFF]
	stx	%g1, [%l1 + TSTATE_OFF]

	! Switch mstate back on the way out

	mov	LMS_SYSTEM, %o0
	call	syscall_mstate
	mov	LMS_USER, %o1
	jmp	%l0 + 8
	 nop

_syscall_pre:
	ldx	[%l1 + G1_OFF], %g1
	call	pre_syscall			! abort = pre_syscall(arg0)
	sth	%g1, [THREAD_REG + T_SYSNUM]

	brnz,pn	%o0, _syscall_post		! did it abort?
	nop
	ldx	[%l1 + O0_OFF], %o0		! reload args
	ldx	[%l1 + O1_OFF], %o1
	ldx	[%l1 + O2_OFF], %o2
	ldx	[%l1 + O3_OFF], %o3
	ldx	[%l1 + O4_OFF], %o4
	ba,pt	%xcc, 3b
	ldx	[%l1 + O5_OFF], %o5

	!
	! Floating-point trap was pending at start of system call.
	! Here with:
	!	%l3 = mpcb_flags
	!
_syscall_fp:
	andn	%l3, FP_TRAPPED, %l3
	st	%l3, [%sp + STACK_BIAS + MPCB_FLAGS] 	! clear FP_TRAPPED
	jmp	%l0 + 8				! return to user_rtt
	clrh	[THREAD_REG + T_SYSNUM]		! clear syscall code

	!
	! illegal system call - syscall number out of range
	!
_syscall_ill:
	call	nosys
	nop
	!
	! Post-syscall with special processing needed.
	!
_syscall_post:
	call	post_syscall			! post_syscall(rvals)
	nop
	jmp	%l0 + 8				! return to user_rtt
	nop
	SET_SIZE(syscall_trap)

/*
 * System call trap handler for ILP32 processes.
 *
 * We branch here from sys_trap when a system call occurs.
 *
 * Entry:
 *	%o0 = regs
 *
 * Usage:
 *	%l0 = saved return address
 *	%l1 = saved regs
 *	%l2 = lwp
 */
	ENTRY_NP(syscall_trap32)
	ldx	[THREAD_REG + T_CPU], %g1	! get cpu pointer
	mov	%o7, %l0			! save return addr

	!
	! If the trapping thread has the address mask bit clear, then it's
	!   a 64-bit process, and has no business calling 32-bit syscalls.
	!
	ldx	[%o0 + TSTATE_OFF], %l1		! saved %tstate.am is that
	andcc	%l1, TSTATE_AM, %l1		!   of the trapping proc
	be,pn	%xcc, _syscall_ill32		!
	  mov	%o0, %l1			! save reg pointer
	srl	%i0, 0, %o0			! copy 1st arg, clear high bits
	srl	%i1, 0, %o1			! copy 2nd arg, clear high bits
	ldx	[%g1 + CPU_STATS_SYS_SYSCALL], %g2
	inc	%g2				! cpu_stats.sys.syscall++
	stx	%g2, [%g1 + CPU_STATS_SYS_SYSCALL]

	!
	! Set new state for LWP
	!
	ldx	[THREAD_REG + T_LWP], %l2
	mov	LWP_SYS, %g3
	srl	%i2, 0, %o2			! copy 3rd arg, clear high bits
	stb	%g3, [%l2 + LWP_STATE]
	srl	%i3, 0, %o3			! copy 4th arg, clear high bits
	ldx	[%l2 + LWP_RU_SYSC], %g2	! pesky statistics
	srl	%i4, 0, %o4			! copy 5th arg, clear high bits
	addx	%g2, 1, %g2
	stx	%g2, [%l2 + LWP_RU_SYSC]
	srl	%i5, 0, %o5			! copy 6th arg, clear high bits
	! args for direct syscalls now set up

#ifdef TRAPTRACE
	!
	! make trap trace entry - helps in debugging
	!
	rdpr	%pstate, %l3
	andn	%l3, PSTATE_IE | PSTATE_AM, %g3
	wrpr	%g0, %g3, %pstate		! disable interrupt
	TRACE_PTR(%g3, %g2)			! get trace pointer
	GET_TRACE_TICK(%g1, %g2)
	stxa	%g1, [%g3 + TRAP_ENT_TICK]%asi
	ldx	[%l1 + G1_OFF], %g1		! get syscall code
	TRACE_SAVE_TL_VAL(%g3, %g1)
	TRACE_SAVE_GL_VAL(%g3, %g0)
	set	TT_SC_ENTR, %g2
	stha	%g2, [%g3 + TRAP_ENT_TT]%asi
	stxa	%g7, [%g3 + TRAP_ENT_TSTATE]%asi ! save thread in tstate space
	stna	%sp, [%g3 + TRAP_ENT_SP]%asi
	stna	%o0, [%g3 + TRAP_ENT_F1]%asi
	stna	%o1, [%g3 + TRAP_ENT_F2]%asi
	stna	%o2, [%g3 + TRAP_ENT_F3]%asi
	stna	%o3, [%g3 + TRAP_ENT_F4]%asi
	stna	%o4, [%g3 + TRAP_ENT_TPC]%asi
	stna	%o5, [%g3 + TRAP_ENT_TR]%asi
	TRACE_NEXT(%g3, %g2, %g1)		! set new trace pointer
	wrpr	%g0, %l3, %pstate		! enable interrupt
#endif /* TRAPTRACE */

	!
	! Test for pre-system-call handling
	!
	ldub	[THREAD_REG + T_PRE_SYS], %g3	! pre-syscall proc?
#ifdef SYSCALLTRACE
	sethi	%hi(syscalltrace), %g4
	ld	[%g4 + %lo(syscalltrace)], %g4
	orcc	%g3, %g4, %g0			! pre_syscall OR syscalltrace?
#else
	tst	%g3				! is pre_syscall flag set?
#endif /* SYSCALLTRACE */
	bnz,pn	%icc, _syscall_pre32		! yes - pre_syscall needed
	  nop

	! Fast path invocation of new_mstate
	mov	LMS_USER, %o0
	call 	syscall_mstate
	mov	LMS_SYSTEM, %o1
		
 	lduw	[%l1 + O0_OFF + 4], %o0		! reload 32-bit args
	lduw	[%l1 + O1_OFF + 4], %o1
	lduw	[%l1 + O2_OFF + 4], %o2
	lduw	[%l1 + O3_OFF + 4], %o3
	lduw	[%l1 + O4_OFF + 4], %o4
	lduw	[%l1 + O5_OFF + 4], %o5

	! lwp_arg now set up
3:
	!
	! Call the handler.  The %o's have been set up.
	!
	lduw	[%l1 + G1_OFF + 4], %g1		! get 32-bit code
	set	sysent32, %g3			! load address of vector table
	cmp	%g1, NSYSCALL			! check range
	sth	%g1, [THREAD_REG + T_SYSNUM]	! save syscall code
	bgeu,pn	%ncc, _syscall_ill32
	  sll	%g1, SYSENT_SHIFT, %g4		! delay - get index 
	add	%g3, %g4, %g5			! g5 = addr of sysentry
	ldx	[%g5 + SY_CALLC], %g3		! load system call handler

	brnz,a,pt %g1, 4f			! check for indir()
	mov	%g5, %l4			! save addr of sysentry
	!
	! Yuck.  If %g1 is zero, that means we're doing a syscall() via the
	! indirect system call.  That means we have to check the
	! flags of the targetted system call, not the indirect system call
	! itself.  See return value handling code below.
	!
	set	sysent32, %l4			! load address of vector table
	cmp	%o0, NSYSCALL			! check range
	bgeu,pn	%ncc, 4f			! out of range, let C handle it
	  sll	%o0, SYSENT_SHIFT, %g4		! delay - get index
	add	%g4, %l4, %l4			! compute & save addr of sysent
4:
	call	%g3				! call system call handler
	nop

	!
	! If handler returns long long then we need to split the 64 bit
	! return value in %o0 into %o0 and %o1 for ILP32 clients.
	!	
	lduh    [%l4 + SY_FLAGS], %g4           ! load sy_flags
	andcc	%g4, SE_64RVAL | SE_32RVAL2, %g0 ! check for 64-bit return
	bz,a,pt	%xcc, 5f
	  srl	%o0, 0, %o0			! 32-bit only
	srl	%o0, 0, %o1			! lower 32 bits into %o1
	srlx	%o0, 32, %o0			! upper 32 bits into %o0
5:

#ifdef TRAPTRACE
	!
	! make trap trace entry for return - helps in debugging
	!
	rdpr	%pstate, %g5
	andn	%g5, PSTATE_IE | PSTATE_AM, %g4
	wrpr	%g0, %g4, %pstate		! disable interrupt
	TRACE_PTR(%g4, %g2)			! get trace pointer
	GET_TRACE_TICK(%g2, %g3)
	stxa	%g2, [%g4 + TRAP_ENT_TICK]%asi
	lduh	[THREAD_REG + T_SYSNUM], %g2
	TRACE_SAVE_TL_VAL(%g4, %g2)
	TRACE_SAVE_GL_VAL(%g4, %g0)
	mov	TT_SC_RET, %g2			! system call return code
	stha	%g2, [%g4 + TRAP_ENT_TT]%asi
	ldx	[%l1 + nPC_OFF], %g2		! get saved npc (new pc)
	stna	%g2, [%g4 + TRAP_ENT_TPC]%asi
	ldx	[%l1 + TSTATE_OFF], %g2		! get saved tstate
	stxa	%g2, [%g4 + TRAP_ENT_TSTATE]%asi
	stna	%sp, [%g4 + TRAP_ENT_SP]%asi
	stna	THREAD_REG, [%g4 + TRAP_ENT_TR]%asi
	stna	%o0, [%g4 + TRAP_ENT_F1]%asi
	stna	%o1, [%g4 + TRAP_ENT_F2]%asi
	stna	%g0, [%g4 + TRAP_ENT_F3]%asi
	stna	%g0, [%g4 + TRAP_ENT_F4]%asi
	TRACE_NEXT(%g4, %g2, %g3)		! set new trace pointer
	wrpr	%g0, %g5, %pstate		! enable interrupt
#endif /* TRAPTRACE */
	!
	! Check for post-syscall processing.
	! This tests all members of the union containing t_astflag, t_post_sys,
	! and t_sig_check with one test.
	!
	ld	[THREAD_REG + T_POST_SYS_AST], %g1
#ifdef SYSCALLTRACE
	sethi	%hi(syscalltrace), %g4
	ld	[%g4 + %lo(syscalltrace)], %g4
	orcc	%g4, %g1, %g0			! OR in syscalltrace
#else
	tst	%g1				! need post-processing?
#endif /* SYSCALLTRACE */
	bnz,pn	%icc, _syscall_post32		! yes - post_syscall or AST set
	mov	LWP_USER, %g1
	stb	%g1, [%l2 + LWP_STATE]		! set lwp_state
	stx	%o0, [%l1 + O0_OFF]		! set rp->r_o0
	stx	%o1, [%l1 + O1_OFF]		! set rp->r_o1
	clrh	[THREAD_REG + T_SYSNUM]		! clear syscall code
	ldx	[%l1 + TSTATE_OFF], %g1		! get saved tstate
	ldx	[%l1 + nPC_OFF], %g2		! get saved npc (new pc)
	mov	CCR_IC, %g3
	sllx	%g3, TSTATE_CCR_SHIFT, %g3
	add	%g2, 4, %g4			! calc new npc
	andn	%g1, %g3, %g1			! clear carry bit for no error
	stx	%g2, [%l1 + PC_OFF]
	stx	%g4, [%l1 + nPC_OFF]
	stx	%g1, [%l1 + TSTATE_OFF]

	! fast path outbound microstate accounting call
	mov	LMS_SYSTEM, %o0
	call 	syscall_mstate
	mov	LMS_USER, %o1

	jmp	%l0 + 8
	 nop


_syscall_pre32:
	ldx	[%l1 + G1_OFF], %g1
	call	pre_syscall			! abort = pre_syscall(arg0)
	sth	%g1, [THREAD_REG + T_SYSNUM]

	brnz,pn	%o0, _syscall_post32		! did it abort?
	nop
 	lduw	[%l1 + O0_OFF + 4], %o0		! reload 32-bit args
	lduw	[%l1 + O1_OFF + 4], %o1
	lduw	[%l1 + O2_OFF + 4], %o2
	lduw	[%l1 + O3_OFF + 4], %o3
	lduw	[%l1 + O4_OFF + 4], %o4
	ba,pt	%xcc, 3b
	lduw	[%l1 + O5_OFF + 4], %o5

	!
	! Floating-point trap was pending at start of system call.
	! Here with:
	!	%l3 = mpcb_flags
	!
_syscall_fp32:
	andn	%l3, FP_TRAPPED, %l3
	st	%l3, [%sp + STACK_BIAS + MPCB_FLAGS] 	! clear FP_TRAPPED
	jmp	%l0 + 8				! return to user_rtt
	clrh	[THREAD_REG + T_SYSNUM]		! clear syscall code

	!
	! illegal system call - syscall number out of range
	!
_syscall_ill32:
	call	nosys
	nop
	!
	! Post-syscall with special processing needed.
	!
_syscall_post32:
	call	post_syscall			! post_syscall(rvals)
	nop
	jmp	%l0 + 8				! return to user_rtt
	nop
	SET_SIZE(syscall_trap32)


/*
 * lwp_rtt - start execution in newly created LWP.
 *	Here with t_post_sys set by lwp_create, and lwp_eosys == JUSTRETURN,
 *	so that post_syscall() will run and the registers will
 *	simply be restored.
 *	This must go out through sys_rtt instead of syscall_rtt.
 */
	ENTRY_NP(lwp_rtt_initial)
	ldn	[THREAD_REG + T_STACK], %l7
	call	__dtrace_probe___proc_start
	sub	%l7, STACK_BIAS, %sp
	ba,a,pt	%xcc, 0f

	ENTRY_NP(lwp_rtt)
	ldn	[THREAD_REG + T_STACK], %l7
	sub	%l7, STACK_BIAS, %sp
0:
	call	__dtrace_probe___proc_lwp__start
	nop
	call	dtrace_systrace_rtt
	add	%sp, REGOFF + STACK_BIAS, %l7
	ldx	[%l7 + O0_OFF], %o0
	call	post_syscall
	ldx	[%l7 + O1_OFF], %o1
	ba,a,pt	%xcc, user_rtt
	SET_SIZE(lwp_rtt)
	SET_SIZE(lwp_rtt_initial)

