#define DIAGNOSTIC
/*-
 * Copyright (c) 1990 The Regents of the University of California.
 * All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * William Jolitz.
 *
 * 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.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
 *
 *	$Id: swtch.s,v 1.75 1998/07/28 17:35:09 bde Exp $
 */

#include "npx.h"
#include "opt_user_ldt.h"
#include "opt_vm86.h"

#include <sys/rtprio.h>

#include <machine/asmacros.h>

#ifdef SMP
#include <machine/pmap.h>
#include <machine/apic.h>
#include <machine/smptests.h>		/** GRAB_LOPRIO */
#include <machine/ipl.h>
#include <machine/lock.h>
#endif /* SMP */

#include "assym.s"


/*****************************************************************************/
/* Scheduling                                                                */
/*****************************************************************************/

/*
 * The following primitives manipulate the run queues.
 * _whichqs tells which of the 32 queues _qs
 * have processes in them.  setrunqueue puts processes into queues, Remrq
 * removes them from queues.  The running process is on no queue,
 * other processes are on a queue related to p->p_priority, divided by 4
 * actually to shrink the 0-127 range of priorities into the 32 available
 * queues.
 */
	.data

	.p2align 4
	.globl	_whichqs, _whichrtqs, _whichidqs

/*
 * The below 3 queue bit masks MUST be in this order
 */
_whichrtqs:	.long	0		/* which realtime run qs have data */
_whichqs:	.long	0		/* which run queues have data */
_whichidqs:	.long	0		/* which idletime run qs have data */

	.globl	_hlt_vector
_hlt_vector:	.long	_default_halt	/* pointer to halt routine */

	.globl	_qs,_cnt,_panic

	.globl	_want_resched
_want_resched:	.long	0		/* we need to re-run the scheduler */
#if defined(SWTCH_OPTIM_STATS)
	.globl	_swtch_optim_stats, _tlb_flush_count
_swtch_optim_stats:	.long	0	/* number of _swtch_optims */
_tlb_flush_count:	.long	0
#endif

rem3:	.asciz	"remrq pid: %d, q: %d, qi: 0x%x, bits: 0x%x, oncpu: 0x%x, qh: 0x%x"
set2:	.asciz	"setrunqueue"
#if defined(DIAGNOSTIC)
sw0_1:	.asciz	"cpu_switch: has wchan (pid: %d, type: %d, pri: %d)"
sw0_2:	.asciz	"cpu_switch: not SRUN (pid: %d, type: %d, pri: %d)"
#if defined(SMP)
sw0_4:	.asciz	"cpu_switch: do not have lock"
#endif
#endif
	.p2align 4

	.text

/*
 * setrunqueue(p)
 *
 * Call should be made at spl6(), and p->p_stat should be SRUN
 */
	SUPERALIGN_TEXT
ENTRY(setrunqueue)
	pushl	%ebp
	movl	%esp,%ebp
	movl	8(%esp),%eax	/* We use esp here instead of ebp for scheduling */

	pushl	%edi
	leal	P_FORW(%eax),%edi
#if defined(SMP)
	movb	$0xff,P_ONCPU(%eax)
#endif

	pushl	%esi
	movzbl	P_PRI(%eax),%edx
	movzwl	P_RTPRIO_TYPE(%eax),%ecx

	pushl	%ebx
	andl	$0x7f,%edx
	movl	$_whichqs,%ebx
	shrl	$2,%edx

#ifdef DIAGNOSTIC
	cmpb	$SRUN,P_STAT(%eax)
	je	set1
	pushl	$set2
	call	_panic
set1:
#endif

	cmpl	$RTP_PRIO_NORMAL,%ecx	/* normal priority process? */
	movl	$_qs,%esi
	je	1f

	movzwl	P_RTPRIO_PRIO(%eax),%edx
	movl	$_rtqs,%esi
	andl	$0x1f,%edx

	cmpl	$RTP_PRIO_REALTIME,%ecx	/* RR realtime priority? */
	movl	$_whichrtqs,%ebx 
	je	1f			/* RT priority */

	cmpl	$RTP_PRIO_FIFO,%ecx	/* FIFO realtime priority? */
	je	1f

	movl	$_whichidqs,%ebx
	movl	$_idqs,%esi
	
1:	btsl	%edx,(%ebx)		/* set q full bit */
	popl	%ebx
	leal	4(%esi,%edx,8),%edx	/* Point to the last pointer on the head */
	popl	%esi
	movl	$0,(%edi)		/* Clear tqe_next pointer for proc */
	movl	(%edx),%ecx		/* head->tqh_last */
	movl	%ecx,P_BACK(%eax)	/* Point reverse pointer to last pointer */
	movl	%eax,(%ecx)		/* *head->tqh_last = elm */
	movl	%edi,(%edx)		/* head->tqh_last = &elm->tqe_next */
	popl	%edi
	movl	%ebp,%esp
	popl	%ebp
	ret

/*
 * Remrq(p)
 *
 * Call should be made at spl6().
 */
	SUPERALIGN_TEXT
ENTRY(remrq)
	pushl	%ebp
	movl	%esp,%ebp
	movl	8(%esp),%eax	/* Use %esp instead of %ebp for scheduling */

	pushl	%ebx
	movzwl	P_RTPRIO_TYPE(%eax),%ecx
	movzbl	P_PRI(%eax),%edx
#if defined(SMP)
	movb	$0xff,P_ONCPU(%eax) 
#endif
	andl	$0x7f,%edx

	pushl	%edi
	shrl	$2,%edx
	movl	$_whichqs,%ebx

	cmpl	$RTP_PRIO_NORMAL,%ecx	/* normal priority process? */
	leal	_qs(,%edx,8),%edi
	je	1f

	movzwl	P_RTPRIO_PRIO(%eax),%edx
	movl	$_whichrtqs,%ebx
	andl	$0x1f,%edx

	cmpl	$RTP_PRIO_REALTIME,%ecx	/* realtime priority process? */
	leal	_rtqs(,%edx,8),%edi
	je	1f

	cmpl	$RTP_PRIO_FIFO,%ecx	/* FIFO realtime priority process? */
	je	1f

	movl	$_whichidqs,%ebx
	leal	_idqs(,%edx,8),%edi

1:
	pushl	%esi
#if defined(DIAGNOSTIC)
	btl	%edx,(%ebx)		/* test full bit */
	jb	2f
	pushl	(%edi)
	movzbl	P_ONCPU(%eax),%ecx
	pushl	%ecx
	pushl	(%ebx)
	pushl	%ebx
	pushl	%edx
	pushl	P_PID(%eax)
	pushl	$rem3
	call	_panic
2:
#endif
	movl	P_FORW(%eax),%ecx	/* unlink process */
	movl	P_BACK(%eax),%esi
	testl	%ecx,%ecx
	je	5f
	movl	%esi,P_BACK(%ecx)
	jmp	6f
5:	movl	%esi,4(%edi)		/* Move into last pointer */
6:	movl	%ecx,(%esi)
	popl	%esi
	cmpl	$0,(%edi)		/* Compare NULL with first pointer */
	popl	%edi
	jne	7f
	btrl	%edx,(%ebx)
7:	popl	%ebx
	movl	%ebp,%esp
	xorl	%ecx,%ecx		/* Null out the pointers in the proc */
	movl	%ecx,P_BACK(%eax)
	movl	%ecx,P_FORW(%eax)
	popl	%ebp
	ret

/*
 * When no processes are on the runq, cpu_switch() branches to _idle
 * to wait for something to come ready.
 */
	SUPERALIGN_TEXT
_idle:
	xorl	%ebp,%ebp
	movl	%ebp,_switchtime

#ifdef SMP

	/* when called, we have the mplock, intr disabled */
	/* use our idleproc's "context" */
	movl	_my_idlePTD,%ecx
	movl	%ecx,%cr3
#if defined(SWTCH_OPTIM_STATS)
	incl	_tlb_flush_count
#endif
	/* Keep space for nonexisting return addr, or profiling bombs */
	movl	$_idlestack_top-4,%eax
	movl	%eax,%esp

	/* update common_tss.tss_esp0 pointer */
#ifdef VM86
	movl	_my_tr, %esi
	movl	%eax, _common_tss + TSS_ESP0
	btrl	%esi, _private_tss
	je	1f
	movl	$_common_tssd, %edi

	/* move correct tss descriptor into GDT slot, then reload tr */
	leal	_gdt(,%esi,8), %ebx	/* entry in GDT */
	movl	0(%edi), %eax
	movl	%eax, 0(%ebx)
	movl	4(%edi), %eax
	movl	%eax, 4(%ebx)
	shll	$3, %esi		/* GSEL(entry, SEL_KPL) */
	ltr	%si
1:
#else
	movl	%eax, _common_tss + TSS_ESP0
#endif /* VM86 */

	sti

	/*
	 * XXX callers of cpu_switch() do a bogus splclock().  Locking should
	 * be left to cpu_switch().
	 */
	call	_spl0

	cli

	/*
	 * _REALLY_ free the lock, no matter how deep the prior nesting.
	 * We will recover the nesting on the way out when we have a new
	 * proc to load.
	 *
	 * XXX: we had damn well better be sure we had it before doing this!
	 */
	CPL_LOCK			/* XXX */
	andl	$~SWI_AST_MASK, _ipending 	/* XXX */
	movl	%ebp, _cpl		/* XXX Allow ASTs on other CPU */
	CPL_UNLOCK			/* XXX */
	movl	$FREE_LOCK, _mp_lock

	/* do NOT have lock, intrs disabled */
	.globl	idle_loop
idle_loop:

#if defined(SWTCH_OPTIM_STATS)
	incl	_tlb_flush_count
#endif

	movl	%cr3,%eax		/* ouch! */

	cmpl	%ebp,_smp_active
	movl	%eax,%cr3
	movl	$_whichrtqs,%ebx	/* Schedule this in the middle */
	jne	1f
	cmpl	%ebp,_cpuid
	jne	2f

	SUPERALIGN_TEXT
1:
	movl	(%ebx),%eax
	orl	4(%ebx),%eax
	orl	8(%ebx),%eax
	jne	3f

2:
	/* enable intrs for a halt */
	movl	$0, lapic_tpr		/* 1st candidate for an INT */
	movl	%ebp, lapic_tpr		/* 1st candidate for an INT */
	sti
	call	*_hlt_vector		/* wait for interrupt */
	cli
	jmp	idle_loop

	SUPERALIGN_TEXT
3:
#if defined(I386_CPU) || defined(I486_CPU) || defined(I586_CPU)
	movl	$LOPRIO_LEVEL,lapic_tpr	/* arbitrate for INTs */
	movl	$_rtqs,%esi
	call	_get_mplock
	CPL_LOCK
	movl	$HWI_MASK|SWI_MASK,_cpl	/* XXX Disallow ASTs on other CPU */
	CPL_UNLOCK
	movl	(%ebx),%edi
	testl	%edi,%edi
	CROSSJUMP(jne, qr, je)
	addl	$4,%ebx
	movl	$_qs,%esi
	movl	(%ebx),%edi
	testl	%edi,%edi
	CROSSJUMP(jne, qr, je)
	addl	$4,%ebx
	movl	$_idqs,%esi
	movl	(%ebx),%edi
	testl	%edi,%edi
	CROSSJUMP(jne, qr, je)
#else /* I686_CPU */
	movl	$LOPRIO_LEVEL,lapic_tpr	/* arbitrate for INTs */
	movl	$_rtqs,%esi
	call	_get_mplock
	CPL_LOCK
	movl	$HWI_MASK|SWI_MASK,_cpl	/* XXX Disallow ASTs on other CPU */
	CPL_UNLOCK
	bsfl	(%ebx),%edi
	CROSSJUMP(jne, qr, je)
	addl	$4,%ebx
	movl	$_qs,%esi
	bsfl	(%ebx),%edi
	CROSSJUMP(jne, qr, je)
	addl	$4,%ebx
	movl	$_idqs,%esi
	bsfl	(%ebx),%edi
	CROSSJUMP(jne, qr, je)
#endif
	jmp	_idle
#else /* !SMP */

	movl	$HIDENAME(tmpstk),%esp
#if defined(SWTCH_OPTIM_STATS)
	incl	_swtch_optim_stats
#endif
	movl	_IdlePTD, %ecx
	movl	%cr3, %eax
	cmpl	%ecx, %eax
	je	2f
#if defined(SWTCH_OPTIM_STATS)
	decl	_swtch_optim_stats
	incl	_tlb_flush_count
#endif
	movl	%ecx, %cr3
2:

	/* update common_tss.tss_esp0 pointer */
#ifdef VM86
	movl	_my_tr, %esi
	movl	%esp, _common_tss + TSS_ESP0

	btrl	%esi, _private_tss
	je	1f
	movl	$_common_tssd, %edi

	/* move correct tss descriptor into GDT slot, then reload tr */
	leal	_gdt(,%esi,8), %ebx	/* entry in GDT */
	movl	0(%edi), %eax
	movl	%eax, 0(%ebx)
	movl	4(%edi), %eax
	movl	%eax, 4(%ebx)
	shll	$3, %esi		/* GSEL(entry, SEL_KPL) */
	ltr	%si
1:
#else
	movl	%esp, _common_tss + TSS_ESP0
#endif /* VM86 */

	sti

	/*
	 * XXX callers of cpu_switch() do a bogus splclock().  Locking should
	 * be left to cpu_switch().
	 */
	call	_spl0

	ALIGN_TEXT
#if defined(I386_CPU) || defined(I486_CPU) || defined(I586_CPU)
idle_loop:
	movl	$_whichrtqs,%ebx
	movl	$_rtqs,%esi
	cli
	movl	(%ebx),%edi
	testl	%edi,%edi
	CROSSJUMP(jne, qr, je)
	addl	$4,%ebx
	movl	$_qs,%esi
	movl	(%ebx),%edi
	testl	%edi,%edi
	CROSSJUMP(jne, qr, je)
	addl	$4,%ebx
	movl	$_idqs,%esi
	movl	(%ebx),%edi
	testl	%edi,%edi
	CROSSJUMP(jne, qr, je)
	sti
	call	*_hlt_vector		/* wait for interrupt */
	jmp	idle_loop
#else
idle_loop:
	movl	$_whichrtqs,%ebx
	movl	$_rtqs,%esi
	cli
	bsfl	(%ebx),%edi
	CROSSJUMP(jne, qr, je)
	addl	$4,%ebx
	movl	$_qs,%esi
	bsfl	(%ebx),%edi
	CROSSJUMP(jne, qr, je)
	addl	$4,%ebx
	movl	$_idqs,%esi
	bsfl	(%ebx),%edi
	CROSSJUMP(jne, qr, je)
	sti
	call	*_hlt_vector		/* wait for interrupt */
	jmp	idle_loop

#endif
#endif /* SMP */

CROSSJUMPTARGET(_idle)
CROSSJUMPTARGET(qr)

ENTRY(default_halt)
#ifndef SMP
	hlt				/* XXX:	 until a wakeup IPI */
#endif
	nop
	nop
	nop
	ret

/*
 * cpu_switch()
 */
	SUPERALIGN_TEXT
ENTRY(cpu_switch)
	/* switch to new process. first, save context as needed */
	movl	_curproc,%edx

	/* if no process to save, don't bother */
	testl	%edx,%edx
	je	sw1

	movl	P_VMSPACE(%edx),%eax
	movl	P_ADDR(%edx),%ecx

#ifdef SMP
	movl	_cpuid,%edi
	movb	$0xff,P_ONCPU(%edx)	/* "leave" the cpu */
	cmpb	$0xff,P_BIAS(%edx)
	jne	1f
	movb	$2,P_BIAS(%edx)
1:	cmpl	$0xffffffff,VM_PMAP+PM_PACTIVE(%eax)
	je	2f
	btrl	%edi,VM_PMAP+PM_PACTIVE(%eax)
#else
	movb	$0x00,VM_PMAP+PM_PACTIVE(%eax)
#endif

2:	movl	(%esp),%eax		/* Hardware registers */
	movl	%edi,PCB_EDI(%ecx)
	movl	%esi,PCB_ESI(%ecx)
	movl	%ebp,PCB_EBP(%ecx)
	movl	%esp,PCB_ESP(%ecx)
	movl	%ebx,PCB_EBX(%ecx)
	movl	%eax,PCB_EIP(%ecx)
	movl	%fs,PCB_FS(%ecx)
	movl	%gs,PCB_GS(%ecx)

#ifdef SMP
	movl	_mp_lock, %eax
	/* XXX FIXME: we should be saving the local APIC TPR */
#ifdef DIAGNOSTIC
	cmpl	$FREE_LOCK, %eax	/* is it free? */
	je	badsw4			/* yes, bad medicine! */
#endif /* DIAGNOSTIC */
	andl	$COUNT_FIELD, %eax	/* clear CPU portion */
	movl	%eax, PCB_MPNEST(%ecx)	/* store it */
#endif /* SMP */

#if NNPX > 0
	/* have we used fp, and need a save? */
	movl	_curproc,%eax
	cmpl	%eax,_npxproc
	jne	1f
	addl	$PCB_SAVEFPU,%ecx	/* h/w bugs make saving complicated */
	pushl	%ecx
	call	_npxsave		/* do it in a big C function */
	popl	%eax
1:
#endif	/* NNPX > 0 */

	movl	$0,_curproc		/* out of process */
sw1:
	xorl	%ebp,%ebp

	/* save is done, now choose a new process or idle */
	cli

#ifdef SMP
	/* Stop scheduling if smp_active goes zero and we are not BSP */
	cmpl	%ebp,_smp_active
	jne	1f
	cmpl	%ebp,_cpuid
	je	1f
	CROSSJUMP(je, _idle, jne)	/* wind down */
1:
#endif


#if defined(I386_CPU) || defined(I486_CPU) || defined(I586_CPU)
	movl	$_whichrtqs,%ebx
	movl	$_rtqs,%esi
	movl    (%ebx),%edi		/* pick next p. from rtqs */
	testl	%edi,%edi
	jne	qr			/* realtime procs */

	addl	$4,%ebx
	movl	$_qs,%esi
	movl	(%ebx),%edi
	testl	%edi,%edi
	jne	qr			/* normal proc */

	addl	$4,%ebx
	movl	$_idqs,%esi
	movl	(%ebx),%edi
	testl	%edi,%edi
	CROSSJUMP(je, _idle, jne)	/* if no proc, idle */
#else /* I686 */
	movl	$_whichrtqs,%ebx
	movl	$_rtqs,%esi
	bsfl	(%ebx),%edi
	jne	qr			/* realtime procs */

	addl	$4,%ebx
	movl	$_qs,%esi
	bsfl	(%ebx),%edi
	jne	qr			/* normal proc */

	addl	$4,%ebx
	movl	$_idqs,%esi
	bsfl	(%ebx),%edi
	CROSSJUMP(je, _idle, jne)	/* if no proc, idle */
#endif /* I386 --> I586, I686 */

qr:
#if defined(I386_CPU) || defined(I486_CPU) || defined(I586_CPU)
	bsfl	%edi,%edi		/* find a full q */
#endif

/*
 * If we are non-SMP, just grab the first entry, otherwise
 * we should scan for a CPU affinity match.  We don't go
 * very far out-of-our-way for CPU affinity, by only scanning
 * a single queue for a match.
 */
#if !defined(SMP)
	leal	(%esi,%edi,8),%eax	/* select q */
	movl	P_FORW(%eax),%ecx	/* unlink from front of process q */
	testl	%ecx,%ecx
	jz	_idle
	movl	P_FORW(%ecx),%edx	/* get next */
	movl	P_BACK(%ecx),%esi	/* prev */
	testl	%edx,%edx
	jz	1f
	movl	%esi,P_BACK(%edx)
	jmp	2f
1:	movl	%esi,P_BACK(%eax)
	btrl	%edi,(%ebx)		/* clear q full status */
2:	movl	%edx,(%esi)
#else
1:	leal	(%esi,%edi,8),%ecx

	/* If we get to the end of the queue, then we restart */
2:	movl	P_FORW(%ecx),%eax
	cmpl	%ebp,%eax
	je	5f

	/*
	 * This is a very primitive affinity calculation.
	 */
	movb	P_LASTCPU(%eax),%dl
	movb	P_BIAS(%eax),%dh
	cmpb	_cpuid,%dl
	je	3f
	cmpb	$0,%dh
	je	3f
	cmpb	$0xff,%dh
	je	3f

	decb	%dh
	movl	%eax,%ecx
	movb	%dh,P_BIAS(%eax)
	jmp	2b

	ALIGN_TEXT
5:	incl	%edi
	cmpl	$32,%edi
	jne	1b
	jmp	_idle
	
	
	SUPERALIGN_TEXT
/*
 * Unlink chosen process from the queue.
 */
3:	movl	_cpuid,%ecx
	movb	$0xff,P_BIAS(%eax)
	movb	%cl,P_ONCPU(%eax)
	movb	%cl,P_LASTCPU(%eax)
	leal	(%esi,%edi,8),%ecx
	movl	P_FORW(%eax),%esi	/* unlink process */
	movl	P_BACK(%eax),%edx
	testl	%esi,%esi
	je	5f
	movl	%edx,P_BACK(%esi)
	jmp	6f
5:	movl	%edx,P_BACK(%ecx)
6:	movl	%esi,(%edx)
	cmpl	%ebp,P_FORW(%ecx)
	jne	7f
	btrl	%edi,(%ebx)		/* clear q full status */
7:	movl	%eax,%ecx
#endif

	movl	%ebp,_want_resched

#ifdef	DIAGNOSTIC
	cmpl	%ebp,P_WCHAN(%ecx)
	jne	badsw1
	cmpb	$SRUN,P_STAT(%ecx)
	jne	badsw2
#endif

	movl	%ebp,P_BACK(%ecx) 	/* isolate process to run */
	movl	%ebp,P_FORW(%ecx)

#if defined(SWTCH_OPTIM_STATS)
	incl	_swtch_optim_stats
#endif

#if defined(SMP)

/*
 * Do the VM space context switch, and minimize the
 * TLB flushes.
 *
 * Invariants:
 *	%ecx -- Process
 *	%edx -- PCB
 *	%edi -- PTD (either mapped by PTD, or kernel mapped)
 */
	movl	_cpuid,%edi
	movl	P_VMSPACE(%ecx),%eax	/* %eax -- VMSPACE */
	movl	P_ADDR(%ecx),%edx	/* %edx -- PCB address */
	movl	_SMP_prvpte(,%edi,4),%esi /* Private per-CPU page PTE */
	movl	%cr3,%ebx		/* See if mapping change needed */
	btsl	%edi,VM_PMAP+PM_PACTIVE(%eax) /* Set pmap CPU active flag */
	movl	VM_PMAP+PM_PDIR(%eax),%edi /* Get PDIR */
	movl	PCB_CR3(%edx),%eax	/* Get CR3 for process */
	cmpl	%ebx,%eax		/* Compare CR3 for process with actual CR3 */
	jne	5f			/* If not equal, must flush */
	cmpl	%esi,4*MPPTDI(%edi)	/* See if Private change needed */
	je	6f			/* Private change not needed, no need to flush */
5:

#if defined(SWTCH_OPTIM_STATS)
	decl	_swtch_optim_stats
	incl	_tlb_flush_count
#endif
	movl	%esi,4*MPPTDI(%edi)	/* Load per-CPU private mapping */
	movl	%eax,%cr3		/* And map new process */

#else /* SMP */

	movl	P_ADDR(%ecx),%edx
#if defined(SWTCH_OPTIM_STATS)
	incl	_swtch_optim_stats
#endif
	/* switch address space */
	movl	%cr3,%ebx		/* Get old %cr3 */
	movl	PCB_CR3(%edx),%eax	/* Get new required mapping */
	movl	P_VMSPACE(%ecx),%esi	/* Get new active pmap (vmspace) address */
	cmpl	%eax,%ebx		/* If old matches new, don't have to flush */
	je	4f
#if defined(SWTCH_OPTIM_STATS)
	decl	_swtch_optim_stats
	incl	_tlb_flush_count
#endif
	movl	%eax,%cr3		/* Load new mapping */
4:	movb	$1,VM_PMAP+PM_PACTIVE(%esi)
#endif /* SMP */

6:
#ifdef VM86
	movl	_my_tr, %esi
	cmpl	%ebp, PCB_EXT(%edx)	/* has pcb extension? */
	je	1f
	btsl	%esi, _private_tss	/* mark use of private tss */
	movl	PCB_EXT(%edx), %edi	/* new tss descriptor */
	jmp	2f
1:
#endif

	/* update common_tss.tss_esp0 pointer */
#ifdef VM86
	leal	(UPAGES * PAGE_SIZE - 16)(%edx), %ebx
#else
	leal	(UPAGES * PAGE_SIZE)(%edx), %ebx
#endif /* VM86 */
	movl	%ebx, _common_tss + TSS_ESP0

#ifdef VM86
	btrl	%esi, _private_tss
	je	3f
	movl	$_common_tssd, %edi
2:
	/* move correct tss descriptor into GDT slot, then reload tr */
	leal	_gdt(,%esi,8), %ebx	/* entry in GDT */
	movl	0(%edi), %eax
	movl	%eax, 0(%ebx)
	movl	4(%edi), %eax
	movl	%eax, 4(%ebx)
	shll	$3, %esi		/* GSEL(entry, SEL_KPL) */
	ltr	%si
3:
#endif /* VM86 */

	/* restore context */
	movl	PCB_EDI(%edx),%edi
	movl	PCB_EBP(%edx),%ebp
	movl	PCB_ESI(%edx),%esi
	movl	PCB_EBX(%edx),%ebx
	movl	PCB_ESP(%edx),%esp
	movl	PCB_EIP(%edx),%eax
	movl	%eax,(%esp)

#ifdef SMP
#ifdef GRAB_LOPRIO			/* hold LOPRIO for INTs */
#ifdef CHEAP_TPR
	movl	$0, lapic_tpr
#else
	andl	$~APIC_TPR_PRIO, lapic_tpr
#endif /** CHEAP_TPR */
#endif /** GRAB_LOPRIO */
	movl	_cpuid,%eax
	movb	$0xff, P_BIAS(%ecx)
	movb	%al, P_ONCPU(%ecx)
	movb	%al, P_LASTCPU(%ecx)
#endif /* SMP */
	movl	%edx, _curpcb
	movl	%ecx, _curproc		/* into next process */

#ifdef SMP
	movl	_cpu_lockid, %eax
	orl	PCB_MPNEST(%edx), %eax	/* add next count from PROC */
	movl	%eax, _mp_lock		/* load the mp_lock */
	/* XXX FIXME: we should be restoring the local APIC TPR */
#endif /* SMP */

#ifdef	USER_LDT
	cmpl	$0, PCB_USERLDT(%edx)
	jnz	1f
	movl	__default_ldt,%eax
	cmpl	_currentldt,%eax
	je	2f
	lldt	__default_ldt
	movl	%eax,_currentldt
	jmp	2f
1:	pushl	%edx
	call	_set_user_ldt
	popl	%edx
2:
#endif

	/* This must be done after loading the user LDT. */
	.globl	cpu_switch_load_fs
cpu_switch_load_fs:
	movl	PCB_FS(%edx),%fs
	.globl	cpu_switch_load_gs
cpu_switch_load_gs:
	movl	PCB_GS(%edx),%gs

	sti
	ret

#ifdef DIAGNOSTIC
badsw1:
	movzbl	P_PRI(%ecx),%eax
	pushl	%eax
	movzwl	P_RTPRIO_TYPE(%ecx),%eax
	pushl	%eax
	pushl	P_PID(%ecx)
	pushl	$sw0_1
	call	_panic

badsw2:
	movzbl	P_PRI(%ecx),%eax
	pushl	%eax
	movzwl	P_RTPRIO_TYPE(%ecx),%eax
	pushl	%eax
	pushl	P_PID(%ecx)
	pushl	$sw0_2
	call	_panic

#endif

#if defined(SMP) && defined(DIAGNOSTIC)
badsw4:
	pushl	$sw0_4
	call	_panic

#endif /* SMP && DIAGNOSTIC */

/*
 * savectx(pcb)
 * Update pcb, saving current processor state.
 */
ENTRY(savectx)
	/* fetch PCB */
	movl	4(%esp),%ecx

	/* caller's return address - child won't execute this routine */
	movl	(%esp),%eax
	movl	%edi,PCB_EDI(%ecx)
	movl	%ebp,PCB_EBP(%ecx)
	movl	%esi,PCB_ESI(%ecx)
	movl	%ebx,PCB_EBX(%ecx)
	movl	%esp,PCB_ESP(%ecx)
	movl	%eax,PCB_EIP(%ecx)

	movl	%fs,PCB_FS(%ecx)
	movl	%gs,PCB_GS(%ecx)

#if NNPX > 0
	/*
	 * If npxproc == NULL, then the npx h/w state is irrelevant and the
	 * state had better already be in the pcb.  This is true for forks
	 * but not for dumps (the old book-keeping with FP flags in the pcb
	 * always lost for dumps because the dump pcb has 0 flags).
	 *
	 * If npxproc != NULL, then we have to save the npx h/w state to
	 * npxproc's pcb and copy it to the requested pcb, or save to the
	 * requested pcb and reload.  Copying is easier because we would
	 * have to handle h/w bugs for reloading.  We used to lose the
	 * parent's npx state for forks by forgetting to reload.
	 */
	movl	_npxproc,%eax
	testl	%eax,%eax
	je	1f

	pushl	%ecx
	movl	P_ADDR(%eax),%eax
	leal	PCB_SAVEFPU(%eax),%eax
	pushl	%eax
	pushl	%eax
	call	_npxsave
	addl	$4,%esp
	popl	%eax
	popl	%ecx

	pushl	$PCB_SAVEFPU_SIZE
	leal	PCB_SAVEFPU(%ecx),%ecx
	pushl	%ecx
	pushl	%eax
	call	_bcopy
	addl	$12,%esp
#endif	/* NNPX > 0 */

1:
	ret
