/*
 * 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 (c) 1993, 2010, Oracle and/or its affiliates. All rights reserved.
 */
 
/*
 * Process switching routines.
 */

#include "assym.h"

#include <sys/param.h>
#include <sys/asm_linkage.h>
#include <sys/mmu.h>
#include <sys/pcb.h>
#include <sys/machthread.h>
#include <sys/machclock.h>
#include <sys/privregs.h>
#include <sys/vtrace.h>
#include <vm/hat_sfmmu.h>

/*
 * resume(kthread_id_t)
 *
 * a thread can only run on one processor at a time. there
 * exists a window on MPs where the current thread on one
 * processor is capable of being dispatched by another processor.
 * some overlap between outgoing and incoming threads can happen
 * when they are the same thread. in this case where the threads
 * are the same, resume() on one processor will spin on the incoming 
 * thread until resume() on the other processor has finished with
 * the outgoing thread.
 *
 * The MMU context changes when the resuming thread resides in a different
 * process.  Kernel threads are known by resume to reside in process 0.
 * The MMU context, therefore, only changes when resuming a thread in
 * a process different from curproc.
 *
 * resume_from_intr() is called when the thread being resumed was not 
 * passivated by resume (e.g. was interrupted).  This means that the
 * resume lock is already held and that a restore context is not needed.
 * Also, the MMU context is not changed on the resume in this case.
 *
 * resume_from_zombie() is the same as resume except the calling thread
 * is a zombie and must be put on the deathrow list after the CPU is
 * off the stack.
 */

	ENTRY(resume)
	save	%sp, -SA(MINFRAME), %sp		! save ins and locals

	call	__dtrace_probe___sched_off__cpu	! DTrace probe
	mov	%i0, %o0			! arg for DTrace probe

	membar	#Sync				! flush writebuffers
	flushw					! flushes all but this window

	stn	%i7, [THREAD_REG + T_PC]	! save return address
	stn	%fp, [THREAD_REG + T_SP]	! save sp

	!
	! Save GSR (Graphics Status Register).
	!
	! Read fprs, call fp_save if FPRS_FEF set.
	! This handles floating-point state saving.
	! The fprs could be turned on by hw bcopy software,
	! *or* by fp_disabled. Handle it either way.
	!
	ldn	[THREAD_REG + T_LWP], %o4	! get lwp pointer
	rd	%fprs, %g4			! read fprs
	brnz,pt	%o4, 0f				! if user thread skip
	  ldn	[THREAD_REG + T_CPU], %i1	! get CPU pointer

	!
	! kernel thread
	!	
	! we save fprs at the beginning the stack so we know
	! where to check at resume time
	ldn	[THREAD_REG + T_STACK], %i2
	ldn	[THREAD_REG + T_CTX], %g3	! get ctx pointer
	andcc	%g4, FPRS_FEF, %g0		! is FPRS_FEF set
	bz,pt	%icc, 1f			! nope, skip
	  st	%g4, [%i2 + SA(MINFRAME) + FPU_FPRS]	! save fprs
	  
	! save kernel fp state in stack
	add	%i2, SA(MINFRAME), %o0		! o0 = kfpu_t ptr
	rd	%gsr, %g5
	call	fp_save
	stx	%g5, [%o0 + FPU_GSR]		! store GSR
	ba,a,pt	%icc, 1f
	  nop

0:
	! user thread
	! o4 = lwp ptr
	! g4 = fprs
	! i1 = CPU ptr
	ldn	[%o4 + LWP_FPU], %o0		! fp pointer
	stn	%fp, [THREAD_REG + T_SP]	! save sp
	andcc	%g4, FPRS_FEF, %g0		! is FPRS_FEF set
	st	%g4, [%o0 + FPU_FPRS]		! store FPRS
#if defined(DEBUG) || defined(NEED_FPU_EXISTS)
	sethi	%hi(fpu_exists), %g5
	ld	[%g5 + %lo(fpu_exists)], %g5
	brz,pn	%g5, 1f
	  ldn	[THREAD_REG + T_CTX], %g3	! get ctx pointer
#endif
	bz,pt	%icc, 1f			! most apps don't use fp
	  ldn	[THREAD_REG + T_CTX], %g3	! get ctx pointer
	ldn	[%o4 + LWP_FPU], %o0		! fp pointer
	rd	%gsr, %g5
	call	fp_save				! doesn't touch globals
	stx	%g5, [%o0 + FPU_GSR]		! store GSR
1:
	!
	! Perform context switch callback if set.
	! This handles coprocessor state saving.
	! i1 = cpu ptr
	! g3 = ctx pointer
	!
	wr	%g0, %g0, %fprs			! disable fpu and clear fprs
	brz,pt	%g3, 2f				! skip call when zero
	ldn	[%i0 + T_PROCP], %i3		! delay slot - get proc pointer
	call	savectx
	mov	THREAD_REG, %o0			! delay - arg = thread pointer
2:
	ldn	[THREAD_REG + T_PROCP], %i2	! load old curproc - for mmu

	!
	! Temporarily switch to idle thread's stack
	!
	ldn	[%i1 + CPU_IDLE_THREAD], %o0	! idle thread pointer
	ldn	[%o0 + T_SP], %o1		! get onto idle thread stack
	sub	%o1, SA(MINFRAME), %sp		! save room for ins and locals
	clr	%fp

	!
	! Set the idle thread as the current thread
	!
	mov	THREAD_REG, %l3			! save %g7 (current thread)
	mov	%o0, THREAD_REG			! set %g7 to idle
	stn	%o0, [%i1 + CPU_THREAD]		! set CPU's thread to idle

	!
	! Clear and unlock previous thread's t_lock
	! to allow it to be dispatched by another processor.
	!
	clrb	[%l3 + T_LOCK]			! clear tp->t_lock

	!
	! IMPORTANT: Registers at this point must be:
	!	%i0 = new thread
	!	%i1 = cpu pointer
	!	%i2 = old proc pointer
	!	%i3 = new proc pointer
	!	
	! Here we are in the idle thread, have dropped the old thread.
	! 
	ALTENTRY(_resume_from_idle)

	! SET_KCONTEXTREG(reg0, reg1, reg2, reg3, reg4, label1, label2, label3)
	SET_KCONTEXTREG(%o0, %g1, %g2, %g3, %o3, l1, l2, l3)

	cmp 	%i2, %i3		! resuming the same process?
	be,pt	%xcc, 5f		! yes.
	  nop

	ldx	[%i3 + P_AS], %o0	! load p->p_as
	ldx	[%o0 + A_HAT], %i5	! %i5 = new proc hat

	!
	! update cpusran field
	!
	ld	[%i1 + CPU_ID], %o4
	add	%i5, SFMMU_CPUSRAN, %o5
	CPU_INDEXTOSET(%o5, %o4, %g1)
	ldx	[%o5], %o2		! %o2 = cpusran field
	mov	1, %g2
	sllx	%g2, %o4, %o4		! %o4 = bit for this cpu
	andcc	%o4, %o2, %g0
	bnz,pn	%xcc, 0f		! bit already set, go to 0
	  nop
3:
	or	%o2, %o4, %o1		! or in this cpu's bit mask
	casx	[%o5], %o2, %o1
	cmp	%o2, %o1
	bne,a,pn %xcc, 3b
	  ldx	[%o5], %o2		! o2 = cpusran field
	membar	#LoadLoad|#StoreLoad

0:
	! 
	! disable interrupts
	!
	! if resume from user to kernel thread
	!	call sfmmu_setctx_sec
	! if resume from kernel (or a different user) thread to user thread
	!	call sfmmu_alloc_ctx
	! sfmmu_load_mmustate
	!
	! enable interrupts
	!
	! %i5 = new proc hat
	!

	sethi	%hi(ksfmmup), %o2
        ldx	[%o2 + %lo(ksfmmup)], %o2

	rdpr	%pstate, %i4
        cmp	%i5, %o2		! new proc hat == ksfmmup ?
	bne,pt	%xcc, 3f		! new proc is not kernel as, go to 3
	  wrpr	%i4, PSTATE_IE, %pstate

	SET_KAS_CTXSEC_ARGS(%i5, %o0, %o1)

	! new proc is kernel as

	call	sfmmu_setctx_sec		! switch to kernel context
	  or	%o0, %o1, %o0
	
	ba,a,pt	%icc, 4f

	!
	! Switch to user address space.
	!
3:
	mov	%i5, %o0			! %o0 = sfmmup
	mov	%i1, %o2			! %o2 = CPU
	set	SFMMU_PRIVATE, %o3		! %o3 = sfmmu private flag
	call	sfmmu_alloc_ctx
	  mov	%g0, %o1			! %o1 = allocate flag = 0

	brz,a,pt %o0, 4f			! %o0 == 0, no private alloc'ed
          nop

        ldn     [%i5 + SFMMU_SCDP], %o0         ! using shared contexts?
        brz,a,pt %o0, 4f
          nop

	ldn   [%o0 + SCD_SFMMUP], %o0		! %o0 = scdp->scd_sfmmup
	mov	%i1, %o2			! %o2 = CPU
	set	SFMMU_SHARED, %o3		! %o3 = sfmmu shared flag
	call	sfmmu_alloc_ctx
	  mov	1, %o1				! %o1 = allocate flag = 1
	
4:
	call	sfmmu_load_mmustate		! program MMU registers
	  mov	%i5, %o0

	wrpr    %g0, %i4, %pstate               ! enable interrupts
	
5:
	!
	! spin until dispatched thread's mutex has
	! been unlocked. this mutex is unlocked when
	! it becomes safe for the thread to run.
	! 
	ldstub	[%i0 + T_LOCK], %o0	! lock curthread's t_lock
6:
	brnz,pn	%o0, 7f			! lock failed
	  ldx	[%i0 + T_PC], %i7	! delay - restore resuming thread's pc

	!
	! Fix CPU structure to indicate new running thread.
	! Set pointer in new thread to the CPU structure.
	! XXX - Move migration statistic out of here
	!
        ldx	[%i0 + T_CPU], %g2	! last CPU to run the new thread
        cmp     %g2, %i1		! test for migration
        be,pt	%xcc, 4f		! no migration
          ldn	[%i0 + T_LWP], %o1	! delay - get associated lwp (if any)
        ldx	[%i1 + CPU_STATS_SYS_CPUMIGRATE], %g2
        inc     %g2
        stx	%g2, [%i1 + CPU_STATS_SYS_CPUMIGRATE]
	stx	%i1, [%i0 + T_CPU]	! set new thread's CPU pointer
4:
	stx	%i0, [%i1 + CPU_THREAD]	! set CPU's thread pointer
	membar	#StoreLoad		! synchronize with mutex_exit()
	mov	%i0, THREAD_REG		! update global thread register
	stx	%o1, [%i1 + CPU_LWP]	! set CPU's lwp ptr
	brz,a,pn %o1, 1f		! if no lwp, branch and clr mpcb
	  stx	%g0, [%i1 + CPU_MPCB]
	!
	! user thread
	! o1 = lwp
	! i0 = new thread
	!
	ldx	[%i0 + T_STACK], %o0
	stx	%o0, [%i1 + CPU_MPCB]	! set CPU's mpcb pointer
#ifdef CPU_MPCB_PA
	ldx	[%o0 + MPCB_PA], %o0
	stx	%o0, [%i1 + CPU_MPCB_PA]
#endif
	! Switch to new thread's stack
	ldx	[%i0 + T_SP], %o0	! restore resuming thread's sp
	sub	%o0, SA(MINFRAME), %sp	! in case of intr or trap before restore
	mov	%o0, %fp
	!
	! Restore resuming thread's GSR reg and floating-point regs
	! Note that the ld to the gsr register ensures that the loading of
	! the floating point saved state has completed without necessity
	! of a membar #Sync.
	!
#if defined(DEBUG) || defined(NEED_FPU_EXISTS)
	sethi	%hi(fpu_exists), %g3
	ld	[%g3 + %lo(fpu_exists)], %g3
	brz,pn	%g3, 2f
	  ldx	[%i0 + T_CTX], %i5	! should resumed thread restorectx?
#endif
	ldx	[%o1 + LWP_FPU], %o0		! fp pointer
	ld	[%o0 + FPU_FPRS], %g5		! get fpu_fprs
	andcc	%g5, FPRS_FEF, %g0		! is FPRS_FEF set?
	bz,a,pt	%icc, 9f			! no, skip fp_restore
	  wr	%g0, FPRS_FEF, %fprs		! enable fprs so fp_zero works

	ldx	[THREAD_REG + T_CPU], %o4	! cpu pointer
	call	fp_restore
	  wr	%g5, %g0, %fprs			! enable fpu and restore fprs

	ldx	[%o0 + FPU_GSR], %g5		! load saved GSR data
	wr	%g5, %g0, %gsr			! restore %gsr data
	ba,pt	%icc,2f
	  ldx	[%i0 + T_CTX], %i5	! should resumed thread restorectx?

9:
	!
	! Zero resuming thread's fp registers, for *all* non-fp program
	! Remove all possibility of using the fp regs as a "covert channel".
	!
	call	fp_zero
	  wr	%g0, %g0, %gsr
	ldx	[%i0 + T_CTX], %i5	! should resumed thread restorectx?
	ba,pt	%icc, 2f
	  wr	%g0, %g0, %fprs			! disable fprs

1:
#ifdef CPU_MPCB_PA
	mov	-1, %o1
	stx	%o1, [%i1 + CPU_MPCB_PA]
#endif
	!
	! kernel thread
	! i0 = new thread
	!
	! Switch to new thread's stack
	!
	ldx	[%i0 + T_SP], %o0	! restore resuming thread's sp
	sub	%o0, SA(MINFRAME), %sp	! in case of intr or trap before restore
	mov	%o0, %fp
	!
	! Restore resuming thread's GSR reg and floating-point regs
	! Note that the ld to the gsr register ensures that the loading of
	! the floating point saved state has completed without necessity
	! of a membar #Sync.
	!
	ldx	[%i0 + T_STACK], %o0
	ld	[%o0 + SA(MINFRAME) + FPU_FPRS], %g5	! load fprs
	ldx	[%i0 + T_CTX], %i5		! should thread restorectx?
	andcc	%g5, FPRS_FEF, %g0		! did we save fp in stack?
	bz,a,pt	%icc, 2f
	  wr	%g0, %g0, %fprs			! clr fprs

	wr	%g5, %g0, %fprs			! enable fpu and restore fprs
	call	fp_restore
	add	%o0, SA(MINFRAME), %o0		! o0 = kpu_t ptr
	ldx	[%o0 + FPU_GSR], %g5		! load saved GSR data
	wr	%g5, %g0, %gsr			! restore %gsr data

2:
	!
	! Restore resuming thread's context
	! i5 = ctx ptr
	!
	brz,a,pt %i5, 8f		! skip restorectx() when zero
	  ld	[%i1 + CPU_BASE_SPL], %o0
	call	restorectx		! thread can not sleep on temp stack
	  mov	THREAD_REG, %o0		! delay slot - arg = thread pointer
	!
	! Set priority as low as possible, blocking all interrupt threads
	! that may be active.
	!
	ld	[%i1 + CPU_BASE_SPL], %o0
8:
	wrpr	%o0, 0, %pil
	wrpr	%g0, WSTATE_KERN, %wstate
	!
	! If we are resuming an interrupt thread, store a starting timestamp
	! in the thread structure.
	!
	lduh	[THREAD_REG + T_FLAGS], %o0
	andcc	%o0, T_INTR_THREAD, %g0
	bnz,pn	%xcc, 0f
	  nop
5:		
	call	__dtrace_probe___sched_on__cpu	! DTrace probe
	nop

	ret				! resume curthread
	restore
0:
	add	THREAD_REG, T_INTR_START, %o2
1:	
	ldx	[%o2], %o1
	RD_CLOCK_TICK(%o0,%o3,%g5,__LINE__)
	casx	[%o2], %o1, %o0
	cmp	%o0, %o1
	be,pt	%xcc, 5b
	  nop
	! If an interrupt occurred while we were attempting to store
	! the timestamp, try again.
	ba,pt	%xcc, 1b
	  nop

	!
	! lock failed - spin with regular load to avoid cache-thrashing.
	!
7:
	brnz,a,pt %o0, 7b		! spin while locked
	  ldub	[%i0 + T_LOCK], %o0
	ba	%xcc, 6b
	  ldstub  [%i0 + T_LOCK], %o0	! delay - lock curthread's mutex
	SET_SIZE(_resume_from_idle)
	SET_SIZE(resume)

	ENTRY(resume_from_zombie)
	save	%sp, -SA(MINFRAME), %sp		! save ins and locals

	call	__dtrace_probe___sched_off__cpu	! DTrace probe
	mov	%i0, %o0			! arg for DTrace probe

	ldn	[THREAD_REG + T_CPU], %i1	! cpu pointer
					
	flushw					! flushes all but this window
	ldn	[THREAD_REG + T_PROCP], %i2	! old procp for mmu ctx

	!
	! Temporarily switch to the idle thread's stack so that
	! the zombie thread's stack can be reclaimed by the reaper.
	!
	ldn	[%i1 + CPU_IDLE_THREAD], %o2	! idle thread pointer
	ldn	[%o2 + T_SP], %o1		! get onto idle thread stack
	sub	%o1, SA(MINFRAME), %sp		! save room for ins and locals
	clr	%fp
	!
	! Set the idle thread as the current thread.
	! Put the zombie on death-row.
	! 	
	mov	THREAD_REG, %o0			! save %g7 = curthread for arg
	mov	%o2, THREAD_REG			! set %g7 to idle
	stn	%g0, [%i1 + CPU_MPCB]		! clear mpcb
#ifdef CPU_MPCB_PA
	mov	-1, %o1
	stx	%o1, [%i1 + CPU_MPCB_PA]
#endif
	call	reapq_add			! reapq_add(old_thread);
	stn	%o2, [%i1 + CPU_THREAD]		! delay - CPU's thread = idle

	!
	! resume_from_idle args:
	!	%i0 = new thread
	!	%i1 = cpu
	!	%i2 = old proc
	!	%i3 = new proc
	!	
	b	_resume_from_idle		! finish job of resume
	ldn	[%i0 + T_PROCP], %i3		! new process
	SET_SIZE(resume_from_zombie)

	ENTRY(resume_from_intr)
	save	%sp, -SA(MINFRAME), %sp		! save ins and locals

	!
	! We read in the fprs and call fp_save if FPRS_FEF is set
	! to save the floating-point state if fprs has been
	! modified by operations such as hw bcopy or fp_disabled.
	! This is to resolve an issue where an interrupting thread
	! doesn't retain their floating-point registers when
	! switching out of the interrupt context.
	!
	rd	%fprs, %g4
	ldn	[THREAD_REG + T_STACK], %i2
	andcc	%g4, FPRS_FEF, %g0		! is FPRS_FEF set
	bz,pt	%icc, 4f
	  st	%g4, [%i2 + SA(MINFRAME) + FPU_FPRS]	! save fprs

	! save kernel fp state in stack
	add	%i2, SA(MINFRAME), %o0		! %o0 = kfpu_t ptr
	rd	%gsr, %g5
	call fp_save
	stx	%g5, [%o0 + FPU_GSR]		! store GSR

4:

	flushw					! flushes all but this window
	stn	%fp, [THREAD_REG + T_SP]	! delay - save sp
	stn	%i7, [THREAD_REG + T_PC]	! save return address

	ldn	[%i0 + T_PC], %i7		! restore resuming thread's pc
	ldn	[THREAD_REG + T_CPU], %i1	! cpu pointer

	!
	! Fix CPU structure to indicate new running thread.
	! The pinned thread we're resuming already has the CPU pointer set.
	!
	mov	THREAD_REG, %l3		! save old thread
	stn	%i0, [%i1 + CPU_THREAD]	! set CPU's thread pointer
	membar	#StoreLoad		! synchronize with mutex_exit()
	mov	%i0, THREAD_REG		! update global thread register

	!
	! Switch to new thread's stack
	!
	ldn	[THREAD_REG + T_SP], %o0	! restore resuming thread's sp
	sub	%o0, SA(MINFRAME), %sp ! in case of intr or trap before restore
	mov	%o0, %fp
	clrb	[%l3 + T_LOCK]		! clear intr thread's tp->t_lock

	!
	! If we are resuming an interrupt thread, store a timestamp in the
	! thread structure.
	!
	lduh	[THREAD_REG + T_FLAGS], %o0
	andcc	%o0, T_INTR_THREAD, %g0
	bnz,pn	%xcc, 0f
	!
	! We're resuming a non-interrupt thread.
	! Clear CPU_INTRCNT and check if cpu_kprunrun set?
	!
	ldub	[%i1 + CPU_KPRUNRUN], %o5	! delay
	brnz,pn	%o5, 3f				! call kpreempt(KPREEMPT_SYNC);
	stub	%g0, [%i1 + CPU_INTRCNT]
1:
	ret				! resume curthread
	restore
0:
	!
	! We're an interrupt thread. Update t_intr_start and cpu_intrcnt
	!
	add	THREAD_REG, T_INTR_START, %o2
2:
	ldx	[%o2], %o1
	RD_CLOCK_TICK(%o0,%o3,%l1,__LINE__)
	casx	[%o2], %o1, %o0
	cmp	%o0, %o1
	bne,pn	%xcc, 2b
	ldn	[THREAD_REG + T_INTR], %l1	! delay
	! Reset cpu_intrcnt if we aren't pinning anyone
	brz,a,pt %l1, 2f
	stub	%g0, [%i1 + CPU_INTRCNT]
2:	
	ba,pt	%xcc, 1b
	nop
3:
	!
	! We're a non-interrupt thread and cpu_kprunrun is set. call kpreempt.
	!
	call	kpreempt
	mov	KPREEMPT_SYNC, %o0
	ba,pt	%xcc, 1b
	nop
	SET_SIZE(resume_from_intr)


/*
 * thread_start()
 *
 * the current register window was crafted by thread_run() to contain
 * an address of a procedure (in register %i7), and its args in registers
 * %i0 through %i5. a stack trace of this thread will show the procedure
 * that thread_start() invoked at the bottom of the stack. an exit routine
 * is stored in %l0 and called when started thread returns from its called
 * procedure.
 */

	ENTRY(thread_start)
	mov	%i0, %o0
	jmpl 	%i7, %o7	! call thread_run()'s start() procedure.
	mov	%i1, %o1

	call	thread_exit	! destroy thread if it returns.
	nop
	unimp 0
	SET_SIZE(thread_start)
