/*	@(#)underflow.s 1.1 94/10/31 SMI	*/

/*
 *	Copyright (c) 1989 by Sun Microsystems, Inc.
 */

#include <machine/asm_linkage.h>
#include <machine/param.h>
#include <machine/psl.h>
#include <machine/trap.h>
#include <machine/mmu.h>
#include <machine/pte.h>
#include <machine/reg.h>
#include <machine/buserr.h>
#include <machine/pcb.h>

#include "assym.s"

	.seg	"text"
	.align	4

PROT_INVAL = (PG_V) >> PG_S_BIT
VALID = (PG_V) >> PG_S_BIT
USER_INVAL = (PG_S) >> PG_S_BIT

/*
 * Window underflow trap handler.
 */
	.global	window_underflow
window_underflow:

#ifdef PERFMETER
	sethi	%hi(_underflowcnt), %l5
	ld	[%l5 + %lo(_underflowcnt)], %l7
	inc	%l7
	st	%l7, [%l5 + %lo(_underflowcnt)]
#endif PERFMETER

	! wim stored into %l3 by trap vector
	sll	%l3, 1, %l4		! next WIM = rol(WIM, 1, NW)
	srl	%l3, %l6, %l5		! trap vector set %l6 = NW-1
	or	%l5, %l4, %l5
	mov	%l5, %wim		! install it
	btst	PSR_PS, %l0		! (wim delay 1) test for user trap
	bz	wu_user			! (wim delay 2)
	restore				! delay slot

	!
	! Supervisor underflow.
	! We do one more restore to get into the window to be restored.
	! The first one was done in the delay slot coming here.
	! We then restore from the stack.
	!
	restore				! get into window to be restored
wu_stack_res:
	RESTORE_WINDOW(%sp)
	save				! get back to original window
	save
	mov	%l0, %psr		! reinstall sup PSR_CC
	nop				! psr delay
	jmp	%l1			! reexecute restore
	rett	%l2

wu_user:
	!
	! User underflow. Window to be restored is a user window.
	! We must check whether the user stack is resident where the window
	! will be restored from, which is pointed to by the windows sp.
	! The sp is the fp of the window which tried to do the restore,
	! so that it is still valid.
	!
	restore				! get into window to be restored
	!
	! Normally, we would check the alignment, and then probe the top
	! and bottom of the save area on the stack. However we optimize
	! this by checking that both ends of the save area are within a
	! 4k unit (the biggest mask we can generate in one cycle), and
	! the alignment in one shot. This allows us to do one probe to
	! the page map. NOTE: this assumes a page size of at least 4k.
	!
	and	%sp, 0xfff, %l0
#ifdef VA_HOLE
	! check if the sp points into the hole in the address space
	sethi	%hi(_hole_shift), %l5	! hole shift address
	ld	[%l5 + %lo(_hole_shift)], %l7
	add	%l0, (14*4), %l0	! interlock, bottom of save area
	sra	%sp, %l7, %l5
	inc	%l5
	andncc	%l5, 1, %l5
	bz	1f
	andncc	%l0, 0xff8, %g0
	b,a	wu_stack_not_res	! sp is in the hole
1:
#else
	add	%l0, (14*4), %l0
	andncc	%l0, 0xff8, %g0
#endif VA_HOLE
	bz,a	wu_sp_bot
	lda	[%sp]ASI_PM, %l1	! check for stack page resident
	!
	! Stack is either misaligned or crosses a 4k boundary.
	!
	btst	0x7, %sp		! test sp alignment
	bz	wu_sp_top
	add	%sp, (14*4), %l0	! delay slot, check top of save area

	!
	! A user underflow trap has happened with a misaligned sp.
	! Fake a memory alignment trap.
	!
	save				! get back to orig window
	save
	mov	%l3, %wim		! restore old wim, so regs are dumped
	b	sys_trap
	mov	T_ALIGNMENT, %l4	! delay slot, fake alignment trap

wu_sp_top:
#ifdef VA_HOLE
	sra	%l0, %l7, %l5
	inc	%l5
	andncc	%l5, 1, %l5
	bz,a	1f
	lda	[%l0]ASI_PM, %l1	! get pme for this address
	b,a	wu_stack_not_res	! address is in the hole
1:
	srl	%l1, PG_S_BIT, %l1	! get vws bits
	sra	%sp, %l7, %l5
	inc	%l5
	andncc	%l5, 1, %l5
	bz	1f
	andcc	%l1, VALID, %g0		! look for valid bit
	b,a	wu_stack_not_res	! stack page can never be resident
1:
#else
	lda	[%l0]ASI_PM, %l1	! get pme for this address
	srl	%l1, PG_S_BIT, %l1	! get vws bits
	andcc	%l1, VALID, %g0		! look for valid bit
#endif VA_HOLE

	bnz,a	wu_sp_bot
	lda	[%sp]ASI_PM, %l1	! delay slot, check bottom of save area

	b,a	wu_stack_not_res	! stack page not resident

wu_sp_bot:
	srl	%l1, PG_S_BIT, %l1	! get vws bits

!!! SECURITY NOTE: check for user access to kernel space
!!! XXX - assumes that kernel space is always resident

	andcc	%l1, USER_INVAL, %g0	! look for supervisor bit
	bnz	wu_stack_not_user
	nop

	andcc	%l1, VALID, %g0		! look for valid bit
	bnz	wu_ustack_res
	nop

	mov	%sp, %l0		! save fault address

wu_stack_not_res:
	!
	! Restore area on user stack is not resident.
	! We punt and fake a page fault so that trap can bring the page in.
	! If the page fault is successful we will reexecute the restore,
	! and underflow with the page now resident.
	!
	sethi	%hi(_masterprocp), %l6
	ld	[%l6 + %lo(_masterprocp)], %l6
	ld	[%l6 + P_STACK], %l7	! setup kernel stack
	SAVE_GLOBALS(%l7 + MINFRAME)
	mov	%l7, %g1		! save computed sp
	mov	%l0, %g2		! save fault address
	mov	GENERIC_INVALID, %g3	! compute bus error reg code
	btst	PROT_INVAL, %l1
	bnz,a	1f
	mov	GENERIC_PROTERR, %g3
1:
	save				! back to last user window
	mov	%psr, %g4		! get CWP
	save				! back to trap window

	!
	! save remaining user state
	!
	mov	%g1, %sp		! setup kernel stack
	SAVE_OUTS(%sp + MINFRAME)
	st	%l0, [%sp + MINFRAME + PSR*4] ! psr
	st	%l1, [%sp + MINFRAME + PC*4] ! pc
	st	%l2, [%sp + MINFRAME + nPC*4] ! npc

	mov	%l3, %wim		! reinstall old wim
	mov	1, %g1			! UWM = 0x01 << CWP
	sll	%g1, %g4, %g1
	sethi	%hi(_uunix), %l6
	ld	[%l6 + %lo(_uunix)], %l6
	st	%g1, [%l6 + PCB_UWM]	! setup u.u_pcb.pcb_uwm
	clr	[%l6 + PCB_WBCNT]

	wr	%l0, PSR_ET, %psr	! enable traps
	mov	T_DATA_FAULT, %o0
	add	%sp, MINFRAME, %o1
	mov	%g2, %o2
	mov	%g3, %o3
	call	_trap			! trap(T_DATA_FAULT,
	mov	S_READ, %o4		!	rp, addr, be, S_READ)

	b,a	sys_rtt

	!
	! The user's save area is resident. Restore the window.
	!
wu_ustack_res:
	RESTORE_WINDOW(%sp)
	save				! get back to original window
	save
wu_out:
	sethi	%hi(_uunix), %l3
	ld	[%l3 + %lo(_uunix)], %l3
	ld	[%l3+PCB_FLAGS], %l3
	clr	%l5			! interlock slot, clear a used reg
	btst	CLEAN_WINDOWS, %l3	! check for clean window maint.
	bz	1f
	mov	%l0, %psr		! reinstall system PSR_CC

	!
	! Maintain clean windows. We only need to clean the registers
	! used in underflow as we know this is a user window.
	!
	mov	%l1, %o6		! put pc, npc in an unobtrusive place
	mov	%l2, %o7
	clr	%l0			! clean the used ones
	clr	%l1
	clr	%l2
	clr	%l3
	clr	%l4
	jmp	%o6			! reexecute restore
	rett	%o7
1:
	nop
	jmp	%l1			! reexecute restore
	rett	%l2

wu_stack_not_user:
	mov	PROT_INVAL, %l1
	b	wu_stack_not_res
	mov	%sp, %l0		!   [fake up SEVAR]
