1498 lines
56 KiB
ArmAsm
Raw Normal View History

2001-01-01 00:00:00 +01:00
// TITLE("Context Swap")
// Copyright (c) 1991 - 1993 Microsoft Corporation
// Module Name:
// x4ctxswap.s
// Abstract:
// This module implements the MIPS machine dependent code necessary to
// field the dispatch interrupt and to perform kernel initiated context
// switching.
// Author:
// David N. Cutler (davec) 1-Apr-1991
// Environment:
// Kernel mode only.
// Revision History:
#include "ksmips.h"
//#define _COLLECT_SWITCH_DATA_ 1
// Define external variables that can be addressed using GP.
.extern KeNumberProcessIds 4
.extern KeTickCount 3 * 4
.extern KiContextSwapLock 4
.extern KiDispatcherLock 4
.extern KiIdleSummary 4
.extern KiReadySummary 4
.extern KiSynchIrql 4
.extern KiWaitInListHead 2 * 4
.extern KiWaitOutListHead 2 * 4
SBTTL("Switch To Thread")
// NTSTATUS
// KiSwitchToThread (
// IN PKTHREAD NextThread
// IN ULONG WaitReason,
// IN ULONG WaitMode,
// IN PKEVENT WaitObject
// )
// Routine Description:
// This function performs an optimal switch to the specified target thread
// if possible. No timeout is associated with the wait, thus the issuing
// thread will wait until the wait event is signaled or an APC is deliverd.
// N.B. This routine is called with the dispatcher database locked.
// N.B. The wait IRQL is assumed to be set for the current thread and the
// wait status is assumed to be set for the target thread.
// N.B. It is assumed that if a queue is associated with the target thread,
// then the concurrency count has been incremented.
// N.B. Control is returned from this function with the dispatcher database
// unlocked.
// Arguments:
// NextThread - Supplies a pointer to a dispatcher object of type thread.
// WaitReason - supplies the reason for the wait operation.
// WaitMode - Supplies the processor wait mode.
// WaitObject - Supplies a pointer to a dispatcher object of type event
// or semaphore.
// Return Value:
// The wait completion status. A value of STATUS_SUCCESS is returned if
// the specified object satisfied the wait. A value of STATUS_USER_APC is
// returned if the wait was aborted to deliver a user APC to the current
// thread.
NESTED_ENTRY(KiSwitchToThread, ExceptionFrameLength, zero)
subu sp,sp,ExceptionFrameLength // allocate context frame
sw ra,ExIntRa(sp) // save return address
sw s0,ExIntS0(sp) // save integer registers s0 - s2
sw s1,ExIntS1(sp) //
sw s2,ExIntS2(sp) //
PROLOGUE_END
// Save the wait reason, the wait mode, and the wait object address.
sw a1,ExceptionFrameLength + (1 * 4)(sp) // save wait reason
sw a2,ExceptionFrameLength + (2 * 4)(sp) // save wait mode
sw a3,ExceptionFrameLength + (3 * 4)(sp) // save wait object address
// If the target thread's kernel stack is resident, the target thread's
// process is in the balance set, the target thread can can run on the
// current processor, and another thread has not already been selected
// to run on the current processor, then do a direct dispatch to the
// target thread bypassing all the general wait logic, thread priorities
// permiting.
lw t9,ThApcState + AsProcess(a0) // get target process address
lbu v0,ThKernelStackResident(a0) // get kernel stack resident
lw s0,KiPcr + PcPrcb(zero) // get address of PRCB
lbu v1,PrState(t9) // get target process state
lw s1,KiPcr + PcCurrentThread(zero) // get current thread address
beq zero,v0,LongWay // if eq, kernel stack not resident
xor v1,v1,ProcessInMemory // check if process in memory
move s2,a0 // set target thread address
bne zero,v1,LongWay // if ne, process not in memory
#if !defined(NT_UP)
lw t0,PbNextThread(s0) // get address of next thread
lbu t1,ThNextProcessor(s1) // get current processor number
lw t2,ThAffinity(s2) // get target thread affinity
lw t3,KiPcr + PcSetMember(zero) // get processor set member
bne zero,t0,LongWay // if ne, next thread selected
and t3,t3,t2 // check if for compatible affinity
beq zero,t3,LongWay // if eq, affinity not compatible
#endif
// Compute the new thread priority.
lbu t4,ThPriority(s1) // get client thread priority
lbu t5,ThPriority(s2) // get server thread priority
sltu v0,t4,LOW_REALTIME_PRIORITY // check if realtime client
sltu v1,t5,LOW_REALTIME_PRIORITY // check if realtime server
beq zero,v0,60f // if eq, realtime client
lbu t6,ThPriorityDecrement(s2) // get priority decrement value
lbu t7,ThBasePriority(s2) // get client base priority
beq zero,v1,50f // if eq, realtime server
addu t8,t7,1 // computed boosted priority
bne zero,t6,30f // if ne, server boost active
// Both the client and the server are not realtime and a priority boost
// is not currently active for the server. Under these conditions an
// optimal switch to the server can be performed if the base priority
// of the server is above a minimum threshold or the boosted priority
// of the server is not less than the client priority.
sltu v0,t8,t4 // check if high enough boost
sltu v1,t8,LOW_REALTIME_PRIORITY // check if less than realtime
bne zero,v0,20f // if ne, boosted priority less
sb t8,ThPriority(s2) // asssume boosted priority is okay
bne zero,v1,70f // if ne, less than realtime
li t8,LOW_REALTIME_PRIORITY - 1 // set high server priority
sb t8,ThPriority(s2) //
b 70f //
// The boosted priority of the server is less than the current priority of
// the client. If the server base priority is above the required threshold,
// then a optimal switch to the server can be performed by temporarily
// raising the priority of the server to that of the client.
20: sltu v0,t7,BASE_PRIORITY_THRESHOLD // check if above threshold
subu t8,t4,t7 // compute priority decrement value
bne zero,v0,LongWay // if ne, priority below threshold
li t7,ROUND_TRIP_DECREMENT_COUNT // get system decrement count value
sb t8,ThPriorityDecrement(s2) // set priority decrement value
sb t4,ThPriority(s2) // set current server priority
sb t7,ThDecrementCount(s2) // set server decrement count
b 70f //
// A server boost has previously been applied to the server thread. Count
// down the decrement count to determine if another optimal server switch
// is allowed.
30: lbu t8,ThDecrementCount(s2) // decrement server count value
subu t8,t8,1 //
sb t8,ThDecrementCount(s2) // store updated decrement count
beq zero,t8,40f // if eq, no more switches allowed
// Another optimal switch to the server is allowed provided that the
// server priority is not less than the client priority.
sltu v0,t5,t4 // check if server lower priority
beq zero,v0,70f // if eq, server not lower priority
b LongWay //
// The server has exhausted the number of times an optimal switch may
// be performed without reducing it priority. Reduce the priority of
// the server to its original unboosted value minus one.
40: sb zero,ThPriorityDecrement(s2) // clear server priority decrement
sb t7,ThPriority(s2) // set server priority to base
b LongWay //
// The client is not realtime and the server is realtime. An optimal switch
// to the server can be performed.
50: lb t8,PrThreadQuantum(t9) // get process quantum value
b 65f //
// The client is realtime. In order for an optimal switch to occur, the
// server must also be realtime and run at a high or equal priority.
60: sltu v0,t5,t4 // check if server is lower priority
lb t8,PrThreadQuantum(t9) // get process quantum value
bne zero,v0,LongWay // if ne, server is lower priority
65: sb t8,ThQuantum(s2) // set server thread quantum
// Set the next processor for the server thread.
70: //
#if !defined(NT_UP)
sb t1,ThNextProcessor(s2) // set server next processor number
#endif
// Set the address of the wait block list in the client thread, initialization
// the event wait block, and insert the wait block in client event wait list.
addu t0,s1,EVENT_WAIT_BLOCK_OFFSET // compute wait block address
sw t0,ThWaitBlockList(s1) // set address of wait block list
sw zero,ThWaitStatus(s1) // set initial wait status
sw a3,WbObject(t0) // set address of wait object
sw t0,WbNextWaitBlock(t0) // set next wait block address
lui t1,WaitAny // get wait type and wait key
sw t1,WbWaitKey(t0) // set wait key and wait type
addu t1,a3,EvWaitListHead // compute wait object listhead address
lw t2,LsBlink(t1) // get backward link of listhead
addu t3,t0,WbWaitListEntry // compute wait block list entry address
sw t3,LsBlink(t1) // set backward link of listhead
sw t3,LsFlink(t2) // set forward link in last entry
sw t1,LsFlink(t3) // set forward link in wait entry
sw t2,LsBlink(t3) // set backward link in wait entry
// Set the client thread wait parameters, set the thread state to Waiting,
// and insert the thread in the proper wait list.
sb zero,ThAlertable(s1) // set alertable FALSE.
sb a1,ThWaitReason(s1) //
sb a2,ThWaitMode(s1) // set the wait mode
lb a3,ThEnableStackSwap(s1) // get kernel stack swap enable
lw t1,KeTickCount + 0 // get low part of tick count
sw t1,ThWaitTime(s1) // set thread wait time
li t0,Waiting // set thread state
sb t0,ThState(s1) //
la t1,KiWaitInListHead // get address of wait in listhead
beq zero,a2,75f // if eq, wait mode is kernel
beq zero,a3,75f // if eq, kernel stack swap disabled
sltu t0,t4,LOW_REALTIME_PRIORITY + 9 // check if priority in range
bne zero,t0,76f // if ne, thread priority in range
75: la t1,KiWaitOutListHead // get address of wait out listhead
76: lw t2,LsBlink(t1) // get backlink of wait listhead
addu t3,s1,ThWaitListEntry // compute wait list entry address
sw t3,LsBlink(t1) // set backward link of listhead
sw t3,LsFlink(t2) // set forward link in last entry
sw t1,LsFlink(t3) // set forward link in wait entry
sw t2,LsBlink(t3) // set backward link in wait entry
// If the current thread is processing a queue entry, then attempt to
// activate another thread that is blocked on the queue object.
// N.B. The next thread address can change if the routine to activate
// a queue waiter is called.
77: lw a0,ThQueue(s1) // get queue object address
beq zero,a0,78f // if eq, no queue object attached
sw s2,PbNextThread(s0) // set next thread address
jal KiActivateWaiterQueue // attempt to activate a blocked thread
lw s2,PbNextThread(s0) // get next thread address
sw zero,PbNextThread(s0) // set next thread address to NULL
78: sw s2,PbCurrentThread(s0) // set address of current thread object
jal SwapContext // swap context
// Lower IRQL to its previous level.
// N.B. SwapContext releases the dispatcher database lock.
// N.B. The register s2 contains the address of the new thread on return.
lw v0,ThWaitStatus(s2) // get wait completion status
lbu a0,ThWaitIrql(s2) // get original IRQL
lbu t0,KiPcr + PcIrqlTable(a0) // get translation table entry value
li t1,~(0xff << PSR_INTMASK) // get interrupt enable mask
sll t0,t0,PSR_INTMASK // shift table entry into position
DISABLE_INTERRUPTS(t2) // disable interrupts
and t2,t2,t1 // clear current interrupt enables
or t2,t2,t0 // set new interrupt enables
sb a0,KiPcr + PcCurrentIrql(zero) // set new IRQL
ENABLE_INTERRUPTS(t2) // enable interrupts
// If the wait was not interrupted to deliver a kernel APC, then return the
// completion status.
xor v1,v0,STATUS_KERNEL_APC // check if awakened for kernel APC
bne zero,v1,90f // if ne, normal wait completion
// Disable interrupts an attempt to acquire the dispatcher database lock.
lw s1,KiPcr + PcCurrentThread(zero) // get current thread address
lbu s2,KiSynchIrql // get new IRQL level
79: DISABLE_INTERRUPTS(t4) // disable interrupts
#if !defined(NT_UP)
80: ll t0,KiDispatcherLock // get current lock value
move t1,s1 // set ownership value
bne zero,t0,85f // if ne, spin lock owned
sc t1,KiDispatcherLock // set spin lock owned
beq zero,t1,80b // if eq, store conditional failure
#endif
// Raise IRQL to synchronization level and save wait IRQL.
// N.B. The raise IRQL code is duplicated here to avoid any extra overhead
// since this is such a common operation.
lbu t1,KiPcr + PcIrqlTable(s2) // get translation table entry value
li t2,~(0xff << PSR_INTMASK) // get interrupt enable mask
sll t1,t1,PSR_INTMASK // shift table entry into position
lbu t3,KiPcr + PcCurrentIrql(zero) // get current IRQL
and t4,t4,t2 // clear current interrupt enables
or t4,t4,t1 // set new interrupt enables
sb s2,KiPcr + PcCurrentIrql(zero) // set new IRQL level
ENABLE_INTERRUPTS(t4) // enable interrupts
sb t3,ThWaitIrql(s1) // set client wait IRQL
b ContinueWait //
#if !defined(NT_UP)
85: ENABLE_INTERRUPTS(t4) // enable interrupts
b 79b // try again
#endif
// Ready the target thread for execution and wait on the specified wait
// object.
LongWay: //
jal KiReadyThread // ready thread for execution
// Continue the and return the wait completion status.
// N.B. The wait continuation routine is called with the dispatcher
// database locked.
ContinueWait: //
lw a0,ExceptionFrameLength + (3 * 4)(sp) // get wait object address
lw a1,ExceptionFrameLength + (1 * 4)(sp) // get wait reason
lw a2,ExceptionFrameLength + (2 * 4)(sp) // get wait mode
jal KiContinueClientWait // continue client wait
90: lw s0,ExIntS0(sp) // restore register s0 - s2
lw s1,ExIntS1(sp) //
lw s2,ExIntS2(sp) //
lw ra,ExIntRa(sp) // get return address
addu sp,sp,ExceptionFrameLength // deallocate context frame
j ra // return
.end KiSwitchToThread
SBTTL("Unlock Dispatcher Database")
// VOID
// KiUnlockDispatcherDatabase (
// IN KIRQL OldIrql
// )
// Routine Description:
// This routine is entered at synchronization level with the dispatcher
// database locked. Its function is to either unlock the dispatcher
// database and return or initiate a context switch if another thread
// has been selected for execution.
// N.B. This code merges with the following swap context code.
// N.B. A context switch CANNOT be initiated if the previous IRQL
// is greater than or equal to DISPATCH_LEVEL.
// N.B. This routine is carefully written to be a leaf function. If,
// however, a context swap should be performed, the routine is
// switched to a nested fucntion.
// Arguments:
// OldIrql (a0) - Supplies the IRQL when the dispatcher database
// lock was acquired.
// Return Value:
// None.
LEAF_ENTRY(KiUnlockDispatcherDatabase)
// Check if a thread has been scheduled to execute on the current processor.
lw t0,KiPcr + PcPrcb(zero) // get address of PRCB
and a0,a0,0xff // isolate old IRQL
sltu t1,a0,DISPATCH_LEVEL // check if IRQL below dispatch level
lw t2,PbNextThread(t0) // get next thread address
bne zero,t2,30f // if ne, a new thread selected
// A new thread has not been selected to run on the current processor.
// Release the dispatcher database lock and restore IRQL to its previous
// level.
10: //
#if !defined(NT_UP)
sw zero,KiDispatcherLock // set spin lock not owned
#endif
lbu t0,KiPcr + PcIrqlTable(a0) // get translation table entry value
li t1,~(0xff << PSR_INTMASK) // get interrupt enable mask
sll t0,t0,PSR_INTMASK // shift table entry into position
DISABLE_INTERRUPTS(t2) // disable interrupts
and t2,t2,t1 // clear current interrupt enables
or t2,t2,t0 // set new interrupt enables
sb a0,KiPcr + PcCurrentIrql(zero) // set new IRQL
ENABLE_INTERRUPTS(t2) // enable interrupts
j ra // return
// A new thread has been selected to run on the current processor, but
// the new IRQL is not below dispatch level. If the current processor is
// not executing a DPC, then request a dispatch interrupt on the current
// processor before releasing the dispatcher lock and restoring IRQL.
20: bne zero,t3,10b // if ne, DPC routine active
#if !defined(NT_UP)
sw zero,KiDispatcherLock // set spin lock not owned
#endif
lbu t0,KiPcr + PcIrqlTable(a0) // get translation table entry value
li t1,~(0xff << PSR_INTMASK) // get interrupt enable mask
sll t0,t0,PSR_INTMASK // shift table entry into position
DISABLE_INTERRUPTS(t2) // disable interrupts
.set noreorder
.set noat
mfc0 t3,cause // get exception cause register
and t2,t2,t1 // clear current interrupt enables
or t2,t2,t0 // set new interrupt enables
or t3,t3,DISPATCH_INTERRUPT // set dispatch interrupt request
mtc0 t3,cause // set exception cause register
sb a0,KiPcr + PcCurrentIrql(zero) // set new IRQL
.set at
.set reorder
ENABLE_INTERRUPTS(t2) // enable interrupts
j ra // return
// A new thread has been selected to run on the current processor.
// If the new IRQL is less than dispatch level, then switch to the new
// thread.
// N.B. the jump to the switch to the next thread is required.
30: lw t3,PbDpcRoutineActive(t0) // get DPC active flag
beq zero,t1,20b // if eq, IRQL not below dispatch
j KxUnlockDispatcherDatabase //
.end KiUnlockDispatcherDataBase
// N.B. This routine is carefully written as a nested function. Control
// drops into this function from above.
NESTED_ENTRY(KxUnlockDispatcherDatabase, ExceptionFrameLength, zero)
subu sp,sp,ExceptionFrameLength // allocate context frame
sw ra,ExIntRa(sp) // save return address
sw s0,ExIntS0(sp) // save integer registers s0 - s2
sw s1,ExIntS1(sp) //
sw s2,ExIntS2(sp) //
PROLOGUE_END
move s0,t0 // set address of PRCB
lw s1,KiPcr + PcCurrentThread(zero) // get current thread address
move s2,t2 // set next thread address
sb a0,ThWaitIrql(s1) // save previous IRQL
sw zero,PbNextThread(s0) // clear next thread address
// Reready current thread for execution and swap context to the selected
// thread.
// N.B. The return from the call to swap context is directly to the swap
// thread exit.
move a0,s1 // set address of previous thread object
sw s2,PbCurrentThread(s0) // set address of current thread object
jal KiReadyThread // reready thread for execution
la ra,KiSwapThreadExit // set return address
j SwapContext // swap context
.end KxUnlockDispatcherDatabase
SBTTL("Swap Thread")
// VOID
// KiSwapThread (
// VOID
// )
// Routine Description:
// This routine is called to select and the next thread to run on the
// current processor and to perform a context switch to the thread.
// Arguments:
// None.
// Return Value:
// Wait completion status (v0).
NESTED_ENTRY(KiSwapThread, ExceptionFrameLength, zero)
subu sp,sp,ExceptionFrameLength // allocate context frame
sw ra,ExIntRa(sp) // save return address
sw s0,ExIntS0(sp) // save integer registers s0 - s2
sw s1,ExIntS1(sp) //
sw s2,ExIntS2(sp) //
PROLOGUE_END
.set noreorder
.set noat
lw s0,KiPcr + PcPrcb(zero) // get address of PRCB
lw t0,KiReadySummary // get ready summary
lw s1,KiPcr + PcCurrentThread(zero) // get current thread address
lw s2,PbNextThread(s0) // get address of next thread
#if !defined(NT_UP)
lw t1,KiPcr + PcSetMember(zero) // get processor affinity mask
lbu v0,PbNumber(s0) // get current processor number
lw v1,KeTickCount + 0 // get low part of tick count
#endif
srl t3,t0,16 // isolate bits <31:16> of summary
li t2,16 // set base bit number
bnel zero,s2,120f // if ne, next thread selected
sw zero,PbNextThread(s0) // zero address of next thread
// Find the highest nibble in the ready summary that contains a set bit
// and left justify so the nibble is in bits <31:28>.
bne zero,t3,10f // if ne, bits <31:16> are nonzero
srl t3,t3,8 // isolate bits <31:24> of summary
li t2,0 // set base bit number
srl t3,t0,8 // isolate bits <15:8> of summary
10: bnel zero,t3,20f // if ne, bits <15:8> are nonzero
addu t2,t2,8 // add bit offset to nonzero byte
20: srl t3,t0,t2 // isolate highest nonzero byte
addu t2,t2,3 // adjust to high bit in nibble
sltu t4,t3,0x10 // check if high nibble nonzero
xor t4,t4,1 // complement less than indicator
sll t4,t4,2 // multiply by nibble width
addu t2,t2,t4 // compute ready queue priority
la t3,KiDispatcherReadyListHead // get ready listhead base address
nor t4,t2,zero // compute left justify shift count
sll t4,t0,t4 // left justify ready summary to nibble
// If the next bit is set in the ready summary, then scan the corresponding
// dispatcher ready queue.
30: bltz t4,50f // if ltz, queue contains an entry
sll t4,t4,1 // position next ready summary bit
bne zero,t4,30b // if ne, more queues to scan
subu t2,t2,1 // decrement ready queue priority
// All ready queues were scanned without finding a runnable thread so
// default to the idle thread and set the appropriate bit in idle summary.
40: //
#if defined(_COLLECT_SWITCH_DATA_)
la t0,KeThreadSwitchCounters // get switch counters address
lw v0,TwSwitchToIdle(t0) // increment switch to idle count
addu v0,v0,1 //
sw v0,TwSwitchToIdle(t0) //
#endif
#if defined(NT_UP)
li t0,1 // get current idle summary
#else
lw t0,KiIdleSummary // get current idle summary
or t0,t0,t1 // set member bit in idle summary
#endif
sw t0,KiIdleSummary // set new idle summary
b 120f //
lw s2,PbIdleThread(s0) // set address of idle thread
// If the thread can execute on the current processor, then remove it from
// the dispatcher ready queue.
50: sll t5,t2,3 // compute ready listhead offset
addu t5,t5,t3 // compute ready queue address
lw t6,LsFlink(t5) // get address of first queue entry
subu s2,t6,ThWaitListEntry // compute address of thread object
#if !defined(NT_UP)
60: lw t7,ThAffinity(s2) // get thread affinity
lw t8,ThWaitTime(s2) // get time of thread ready
lbu t9,ThNextProcessor(s2) // get last processor number
and t7,t7,t1 // check for compatible thread affinity
bne zero,t7,70f // if ne, thread affinity compatible
subu t8,v1,t8 // compute length of wait
lw t6,LsFlink(t6) // get address of next entry
bne t5,t6,60b // if ne, not end of list
subu s2,t6,ThWaitListEntry // compute address of thread object
bne zero,t4,30b // if ne, more queues to scan
subu t2,t2,1 // decrement ready queue priority
b 40b //
nop // fill
// If the thread last ran on the current processor, the processor is the
// ideal processor for the thread, the thread has been waiting for longer
// than a quantum, ot its priority is greater than low realtime plus 9,
// then select the thread. Otherwise, an attempt is made to find a more
// appropriate candidate.
70: lbu a0,ThIdealProcessor(s2) // get ideal processor number
beq v0,t9,110f // if eq, last processor number match
sltu t7,t2,LOW_REALTIME_PRIORITY + 9 // check if priority in range
beq v0,a0,100f // if eq, ideal processor number match
sltu t8,t8,READY_SKIP_QUANTUM + 1 // check if wait time exceeded
and t8,t8,t7 // check if priority and time match
beql zero,t8,110f // if eq, priority or time mismatch
sb v0,ThNextProcessor(s2) // set next processor number
// Search forward in the ready queue until the end of the list is reached
// or a more appropriate thread is found.
lw t7,LsFlink(t6) // get address of next entry
80: beq t5,t7,100f // if eq, end of list
subu a1,t7,ThWaitListEntry // compute address of thread object
lw a2,ThAffinity(a1) // get thread affinity
lw t8,ThWaitTime(a1) // get time of thread ready
lbu t9,ThNextProcessor(a1) // get last processor number
lbu a0,ThIdealProcessor(a1) // get ideal processor number
and a2,a2,t1 // check for compatible thread affinity
subu t8,v1,t8 // compute length of wait
beq zero,a2,85f // if eq, thread affinity not compatible
sltu t8,t8,READY_SKIP_QUANTUM + 1 // check if wait time exceeded
beql v0,t9,90f // if eq, processor number match
move s2,a1 // set thread address
beql v0,a0,90f // if eq, processor number match
move s2,a1 // set thread address
85: bne zero,t8,80b // if ne, wait time not exceeded
lw t7,LsFlink(t7) // get address of next entry
b 110f //
sb v0,ThNextProcessor(s2) // set next processor number
90: move t6,t7 // set list entry address
100: sb v0,ThNextProcessor(s2) // set next processor number
.set at
.set reorder
110: //
#if defined(_COLLECT_SWITCH_DATA_)
la v1,KeThreadSwitchCounters + TwFindIdeal// get counter address
lbu a0,ThIdealProcessor(s2) // get ideal processor number
lbu t9,ThLastprocessor(s2) // get last processor number
beq v0,a0,115f // if eq, processor number match
addu v1,v1,TwFindLast - TwFindIdeal // compute counter address
beq v0,t9,115f // if eq, processor number match
addu v1,v1,TwFindAny - TwFindLast // compute counter address
115: lw v0,0(v1) // increment appropriate counter
addu v0,v0,1 //
sw v0,0(v1) //
#endif
#endif
// Remove the selected thread from the ready queue.
lw t7,LsFlink(t6) // get list entry forward link
lw t8,LsBlink(t6) // get list entry backward link
li t1,1 // set bit for mask generation
sw t7,LsFlink(t8) // set forward link in previous entry
sw t8,LsBlink(t7) // set backward link in next entry
bne t7,t8,120f // if ne, list is not empty
sll t1,t1,t2 // compute ready summary set member
xor t1,t1,t0 // clear ready summary bit
sw t1,KiReadySummary //
// Swap context to the next thread.
.set noreorder
.set noat
120: jal SwapContext // swap context
sw s2,PbCurrentThread(s0) // set address of current thread object
.set at
.set reorder
// Lower IRQL, deallocate context frame, and return wait completion status.
// N.B. SwapContext releases the dispatcher database lock.
// N.B. The register v0 contains the kernel APC pending state on return.
// N.B. The register s2 contains the address of the new thread on return.
ALTERNATE_ENTRY(KiSwapThreadExit)
lw s1,ThWaitStatus(s2) // get wait completion status
lbu a0,ThWaitIrql(s2) // get original wait IRQL
sltu v1,a0,APC_LEVEL // check if wait IRQL is zero
and v1,v1,v0 // check if IRQL and APC pending set
beq zero,v1,10f // if eq, IRQL or pending not set
// Lower IRQL to APC level and dispatch APC interrupt.
.set noreorder
.set noat
li a0,APC_LEVEL // set new IRQL level
lbu t0,KiPcr + PcIrqlTable(a0) // get translation table entry value
li t1,~(0xff << PSR_INTMASK) // get interrupt enable mask
li t2,CU1_ENABLE // get coprocessor 1 enable bits
mfc0 t3,psr // get current PSR
mtc0 t2,psr
sll t0,t0,PSR_INTMASK // shift table entry into position
and t3,t3,t1 // clear current interrupt enables
or t3,t3,t0 // set new interrupt enables
mfc0 t4,cause // get exception cause register
sb a0,KiPcr + PcCurrentIrql(zero) // set new IRQL
and t4,t4,DISPATCH_INTERRUPT // clear APC interrupt pending
mtc0 t4,cause //
mtc0 t3,psr // enable interrupts
.set at
.set reorder
lw t0,KiPcr + PcPrcb(zero) // get current processor block address
lw t1,PbApcBypassCount(t0) // increment the APC bypass count
addu t1,t1,1 //
sw t1,PbApcBypassCount(t0) // store result
move a0,zero // set previous mode to kernel
move a1,zero // set exception frame address
move a2,zero // set trap frame addresss
jal KiDeliverApc // deliver kernel mode APC
move a0,zero // set original wait IRQL
// Lower IRQL to wait level, set return status, restore registers, and
// return.
.set noreorder
.set noat
10: lbu t0,KiPcr + PcIrqlTable(a0) // get translation table entry value
li t1,~(0xff << PSR_INTMASK) // get interrupt enable mask
li t2,CU1_ENABLE // get coprocessor 1 enable bits
mfc0 t3,psr // get current PSR
mtc0 t2,psr
sll t0,t0,PSR_INTMASK // shift table entry into position
and t3,t3,t1 // clear current interrupt enables
or t3,t3,t0 // set new interrupt enables
sb a0,KiPcr + PcCurrentIrql(zero) // set new IRQL
mtc0 t3,psr // enable interrupts
.set at
.set reorder
move v0,s1 // set return status
lw s0,ExIntS0(sp) // restore register s0 - s2
lw s1,ExIntS1(sp) //
lw s2,ExIntS2(sp) //
lw ra,ExIntRa(sp) // get return address
addu sp,sp,ExceptionFrameLength // deallocate context frame
j ra // return
.end KiSwapThread
SBTTL("Dispatch Interrupt")
// Routine Description:
// This routine is entered as the result of a software interrupt generated
// at DISPATCH_LEVEL. Its function is to process the Deferred Procedure Call
// (DPC) list, and then perform a context switch if a new thread has been
// selected for execution on the processor.
// This routine is entered at IRQL DISPATCH_LEVEL with the dispatcher
// database unlocked. When a return to the caller finally occurs, the
// IRQL remains at DISPATCH_LEVEL, and the dispatcher database is still
// unlocked.
// N.B. On entry to this routine all integer registers and the volatile
// floating registers have been saved.
// Arguments:
// s8 - Supplies a pointer to the base of a trap frame.
// Return Value:
// None.
NESTED_ENTRY(KiDispatchInterrupt, ExceptionFrameLength, zero)
subu sp,sp,ExceptionFrameLength // allocate context frame
sw ra,ExIntRa(sp) // save return address
PROLOGUE_END
lw s0,KiPcr + PcPrcb(zero) // get address of PRCB
// Process the deferred procedure call list.
PollDpcList: //
DISABLE_INTERRUPTS(s1) // disable interrupts
.set noreorder
.set noat
mfc0 t0,cause // get exception cause register
and t0,t0,APC_INTERRUPT // clear dispatch interrupt pending
mtc0 t0,cause // set exception cause register
.set at
.set reorder
addu a1,s0,PbDpcListHead // compute DPC listhead address
lw a0,LsFlink(a1) // get address of next entry
beq a0,a1,20f // if eq, DPC list is empty
// Switch to interrupt stack to process the DPC list.
lw t1,KiPcr + PcInterruptStack(zero) // get interrupt stack address stack
subu t2,t1,ExceptionFrameLength // allocate exception frame
sw sp,ExIntS4(t2) // save old stack pointer
sw zero,ExIntRa(t2) // clear return address
sw t1,KiPcr + PcInitialStack(zero) // set initial stack address
subu t1,t1,KERNEL_STACK_SIZE // compute and set stack limit
sw t1,KiPcr + PcStackLimit(zero) //
move sp,t2 // set new stack pointer
sw sp,KiPcr + PcOnInterruptStack(zero) // set stack indicator
move v0,s1 // set previous PSR value
jal KiRetireDpcList // process the DPC list
// Switch back to previous stack and restore the initial stack limit.
lw t1,KiPcr + PcCurrentThread(zero) // get current thread address
lw t2,ThInitialStack(t1) // get initial stack address
lw t3,ThStackLimit(t1) // get stack limit
lw sp,ExIntS4(sp) // restore stack pointer
sw t2,KiPcr + PcInitialStack(zero) // set initial stack address
sw t3,KiPcr + PcStackLimit(zero) // set stack limit
sw zero,KiPcr + PcOnInterruptStack(zero) // clear stack indicator
20: ENABLE_INTERRUPTS(s1) // enable interrupts
// Check to determine if quantum end has occured.
// N.B. If a new thread is selected as a result of processing a quantum
// end request, then the new thread is returned with the dispatcher
// database locked. Otherwise, NULL is returned with the dispatcher
// database unlocked.
lw t0,KiPcr + PcQuantumEnd(zero) // get quantum end indicator
bne zero,t0,70f // if ne, quantum end request
// Check to determine if a new thread has been selected for execution on
// this processor.
lw s2,PbNextThread(s0) // get address of next thread object
beq zero,s2,50f // if eq, no new thread selected
// Disable interrupts and attempt to acquire the dispatcher database lock.
lbu a0,KiSynchIrql // get new IRQL value
DISABLE_INTERRUPTS(t3) // disable interrupts
#if !defined(NT_UP)
30: ll t0,KiDispatcherLock // get current lock value
move t1,s2 // set lock ownership value
bne zero,t0,60f // if ne, spin lock owned
sc t1,KiDispatcherLock // set spin lock owned
beq zero,t1,30b // if eq, store conditional failed
#endif
// Raise IRQL to synchronization level.
// N.B. The raise IRQL code is duplicated here to avoid any extra overhead
// since this is such a common operation.
lbu t0,KiPcr + PcIrqlTable(a0) // get translation table entry value
li t1,~(0xff << PSR_INTMASK) // get interrupt enable mask
sll t0,t0,PSR_INTMASK // shift table entry into position
and t3,t3,t1 // clear current interrupt enables
or t3,t3,t0 // set new interrupt enables
sb a0,KiPcr + PcCurrentIrql(zero) // set new IRQL
ENABLE_INTERRUPTS(t3) // enable interrupts
40: lw s1,KiPcr + PcCurrentThread(zero) // get current thread object address
lw s2,PbNextThread(s0) // get address of next thread object
sw zero,PbNextThread(s0) // clear address of next thread object
// Reready current thread for execution and swap context to the selected thread.
move a0,s1 // set address of previous thread object
sw s2,PbCurrentThread(s0) // set address of current thread object
jal KiReadyThread // reready thread for execution
jal SwapContext // swap context
// Restore saved registers, deallocate stack frame, and return.
50: lw ra,ExIntRa(sp) // get return address
addu sp,sp,ExceptionFrameLength // deallocate context frame
j ra // return
// Enable interrupts and check DPC queue.
#if !defined(NT_UP)
60: ENABLE_INTERRUPTS(t3) // enable interrupts
j PollDpcList //
#endif
// Process quantum end event.
// N.B. If the quantum end code returns a NULL value, then no next thread
// has been selected for execution. Otherwise, a next thread has been
// selected and the dispatcher databased is locked.
70: sw zero,KiPcr + PcQuantumEnd(zero) // clear quantum end indicator
jal KiQuantumEnd // process quantum end request
bne zero,v0,40b // if ne, next thread selected
lw ra,ExIntRa(sp) // get return address
addu sp,sp,ExceptionFrameLength // deallocate context frame
j ra // return
.end KiDispatchInterrupt
SBTTL("Swap Context to Next Thread")
// Routine Description:
// This routine is called to swap context from one thread to the next.
// Arguments:
// s0 - Address of Processor Control Block.
// s1 - Address of previous thread object.
// s2 - Address of next thread object.
// sp - Pointer to a exception frame.
// Return value:
// v0 - Kernel APC pending.
// s0 - Address of Processor Control Block.
// s2 - Address of current thread object.
NESTED_ENTRY(SwapContext, 0, zero)
// Set the thread state to running.
li t0,Running // set thread state to running
sb t0,ThState(s2) //
// Acquire the context swap lock so the address space of the old process
// cannot be deleted and then release the dispatcher database lock.
// N.B. This lock is used to protect the address space until the context
// switch has sufficiently progressed to the point where the address
// space is no longer needed. This lock is also acquired by the reaper
// thread before it finishes thread termination.
#if !defined(NT_UP)
10: ll t0,KiContextSwapLock // get current lock value
move t1,s2 // set ownership value
bne zero,t0,10b // if ne, lock already owned
sc t1,KiContextSwapLock // set lock ownership value
beq zero,t1,10b // if eq, store conditional failed
sw zero,KiDispatcherLock // set lock not owned
#endif
// Save old thread nonvolatile context.
sw ra,ExSwapReturn(sp) // save return address
sw s3,ExIntS3(sp) // save integer registers s3 - s8.
sw s4,ExIntS4(sp) //
sw s5,ExIntS5(sp) //
sw s6,ExIntS6(sp) //
sw s7,ExIntS7(sp) //
sw s8,ExIntS8(sp) //
sdc1 f20,ExFltF20(sp) // save floating registers f20 - f31
sdc1 f22,ExFltF22(sp) //
sdc1 f24,ExFltF24(sp) //
sdc1 f26,ExFltF26(sp) //
sdc1 f28,ExFltF28(sp) //
sdc1 f30,ExFltF30(sp) //
PROLOGUE_END
// Accumlate the total time spent in a thread.
#if defined(PERF_DATA)
addu a0,sp,ExFltF20 // compute address of result
move a1,zero // set address of optional frequency
jal KeQueryPerformanceCounter // query performance counter
lw t0,ExFltF20(sp) // get current cycle count
lw t1,ExFltF20 + 4(sp) //
lw t2,PbStartCount(s0) // get starting cycle count
lw t3,PbStartCount + 4(s0) //
sw t0,PbStartCount(s0) // set starting cycle count
sw t1,PbStartCount + 4(s0) //
lw t4,EtPerformanceCountLow(s1) // get accumulated cycle count
lw t5,EtPerformanceCountHigh(s1) //
subu t6,t0,t2 // subtract low parts
subu t7,t1,t3 // subtract high parts
sltu v0,t0,t2 // generate borrow from high part
subu t7,t7,v0 // subtract borrow
addu t6,t6,t4 // add low parts
addu t7,t7,t5 // add high parts
sltu v0,t6,t4 // generate carry into high part
addu t7,t7,v0 // add carry
sw t6,EtPerformanceCountLow(s1) // set accumulated cycle count
sw t7,EtPerformanceCountHigh(s1) //
#endif
// The following entry point is used to switch from the idle thread to
// another thread.
ALTERNATE_ENTRY(SwapFromIdle)
#if DBG
lw t0,ThInitialStack(s1) // get initial stack address
lw t1,ThStackLimit(s1) // get stack limit
sltu t2,sp,t0 // stack within limits?
sltu t3,sp,t1 //
xor t3,t3,t2 //
bne zero,t3,5f // if ne, stack within limits
li a0,PANIC_STACK_SWITCH // set bug check code
move a1,t0 // set initial stack address
move a2,t1 // set stack limit
move a3,sp // set stack address
jal KeBugCheckEx // bug check
#endif
// Get the old and new process object addresses.
5: lw s3,ThApcState + AsProcess(s2) // get new process address
lw s4,ThApcState + AsProcess(s1) // get old process address
// Save the processor state, swap stack pointers, and set the new stack
// limits.
.set noreorder
.set noat
mfc0 s7,psr // save current PSR
li t1,CU1_ENABLE // disable interrupts
mtc0 t1,psr // 3 cycle hazzard
lw t2,ThInitialStack(s2) // get new initial stack pointer
lw t3,ThStackLimit(s2) // get new stack limit
sw sp,ThKernelStack(s1) // save old kernel stack pointer
lw sp,ThKernelStack(s2) // get new kernel stack pointer
ld t1,ThTeb(s2) // get user TEB and TLS array addresses
sw t2,KiPcr + PcInitialStack(zero) // set initial stack pointer
sw t3,KiPcr + PcStackLimit(zero) // set stack limit
sd t1,KiPcr + PcTeb(zero) // set user TEB and TLS array addresses
// If the new process is not the same as the old process, then swap the
// address space to the new process.
// N.B. The context swap lock cannot be dropped until all references to the
// old process address space are complete. This includes any possible
// TB Misses that could occur referencing the new address space while
// still executing in the old address space.
// N.B. The process address space swap is executed with interrupts disabled.
#if defined(NT_UP)
beq s3,s4,20f // if eq, old and new process match
#else
beql s3,s4,20f // if eq, old and new process match
sw zero,KiContextSwapLock // set spin lock not owned
// Update the processor set masks.
lw t0,KiPcr + PcSetMember(zero) // get processor set member
lw t2,PrActiveProcessors(s3) // get new active processor set
lw t1,PrActiveProcessors(s4) // get old active processor set
or t2,t2,t0 // set processor member in set
xor t1,t1,t0 // clear processor member in set
sw t2,PrActiveProcessors(s3) // set new active processor set
sw t1,PrActiveProcessors(s4) // set old active processor set
sw zero,KiContextSwapLock // set spin lock not owned
#endif
lw s5,PrDirectoryTableBase(s3) // get page directory PDE
lw s6,PrDirectoryTableBase + 4(s3) // get hyper space PDE
.set at
.set reorder
// Allocate a new process PID. If the new PID number is greater than the
// number of PIDs supported on the host processor, then flush the entire
// TB and reset the PID number ot zero.
lw v1,KiPcr + PcCurrentPid(zero) // get current processor PID
lw t2,KeNumberProcessIds // get number of process id's
addu v1,v1,1 << ENTRYHI_PID // increment master system PID
sltu t2,v1,t2 // any more PIDs to allocate
bne zero,t2,10f // if ne, more PIDs to allocate
// Flush the random part of the TB.
jal KiFlushRandomTb // flush random part of TB
move v1,zero // set next PID value
// Swap address space to the specified process.
10: sw v1,KiPcr + PcCurrentPid(zero) // set current processor PID
li t3,PDE_BASE // get virtual address of PDR
or t3,t3,v1 // merge process PID
li t4,PDR_ENTRY << INDEX_INDEX // set entry index for PDR
.set noreorder
.set noat
mtc0 t3,entryhi // set VPN2 and PID of TB entry
mtc0 s5,entrylo0 // set first PDE value
mtc0 s6,entrylo1 // set second PDE value
mtc0 t4,index // set index of PDR entry
nop // 1 cycle hazzard
tlbwi // write system PDR TB entry
nop // 3 cycle hazzard
nop //
nop //
.set at
.set reorder
// If the new thread has a kernel mode APC pending, then request an APC
// interrupt.
.set noreorder
.set noat
20: lbu v0,ThApcState + AsKernelApcPending(s2) // get kernel APC pending
mfc0 t3,cause // get cause register contents
sll t2,v0,(APC_LEVEL + CAUSE_INTPEND - 1) // shift APC pending
or t3,t3,t2 // merge possible APC interrupt request
mtc0 t3,cause // write exception cause register
mtc0 s7,psr // set new PSR
.set at
.set reorder
// Update the number of context switches for the current processor and the
// new thread and save the address of the new thread objhect in the PCR.
lw t0,PbContextSwitches(s0) // increment processor context switches
addu t0,t0,1 //
sw t0,PbContextSwitches(s0) //
lw t1,ThContextSwitches(s2) // increment thread context switches
addu t1,t1,1 //
sw t1,ThContextSwitches(s2) //
sw s2,KiPcr + PcCurrentThread(zero) // set address of new thread
// Restore new thread nonvolatile context.
ldc1 f20,ExFltF20(sp) // restore floating registers f20 - f31
ldc1 f22,ExFltF22(sp) //
ldc1 f24,ExFltF24(sp) //
ldc1 f26,ExFltF26(sp) //
ldc1 f28,ExFltF28(sp) //
ldc1 f30,ExFltF30(sp) //
lw s3,ExIntS3(sp) // restore integer registers s3 - s8.
lw s4,ExIntS4(sp) //
lw s5,ExIntS5(sp) //
lw s6,ExIntS6(sp) //
lw s7,ExIntS7(sp) //
lw s8,ExIntS8(sp) //
// Set address of current thread object and return.
// N.B. The register s2 contains the address of the new thread on return.
lw ra,ExSwapReturn(sp) // get return address
j ra // return
.end SwapContext
SBTTL("Swap Process")
// BOOLEAN
// KiSwapProcess (
// IN PKPROCESS NewProcess,
// IN PKPROCESS OldProcess
// )
// Routine Description:
// This function swaps the address space from one process to another by
// assigning a new process id, if necessary, and loading the fixed entry
// in the TB that maps the process page directory page.
// Arguments:
// NewProcess (a0) - Supplies a pointer to a control object of type process
// which represents the new process that is switched to.
// OldProcess (a1) - Supplies a pointer to a control object of type process
// which represents the old process that is switched from.
// Return Value:
// None.
.struct 0
SpArg: .space 4 * 4 // argument register save area
.space 4 * 3 // fill for alignment
SpRa: .space 4 // saved return address
SpFrameLength: // length of stack frame
SpA0: .space 4 // saved argument register a0
NESTED_ENTRY(KiSwapProcess, SpFrameLength, zero)
subu sp,sp,SpFrameLength // allocate stack frame
sw ra,SpRa(sp) // save return address
PROLOGUE_END
// Acquire the context swap lock, clear the processor set member in he old
// process, set the processor member in the new process, and release the
// context swap lock.
#if !defined(NT_UP)
10: ll t0,KiContextSwapLock // get current lock value
move t1,a0 // set ownership value
bne zero,t0,10b // if ne, lock already owned
sc t1,KiContextSwapLock // set lock ownership value
beq zero,t1,10b // if eq, store conditional failed
lw t0,KiPcr + PcSetMember(zero) // get processor set member
lw t2,PrActiveProcessors(a0) // get new active processor set
lw t1,PrActiveProcessors(a1) // get old active processor set
or t2,t2,t0 // set processor member in set
xor t1,t1,t0 // clear processor member in set
sw t2,PrActiveProcessors(a0) // set new active processor set
sw t1,PrActiveProcessors(a1) // set old active processor set
sw zero,KiContextSwapLock // clear lock value
#endif
// Allocate a new process PID. If the new PID number is greater than the
// number of PIDs supported on the host processor, then flush the entire
// TB and reset the PID number ot zero.
lw v1,KiPcr + PcCurrentPid(zero) // get current processor PID
lw t2,KeNumberProcessIds // get number of process id's
addu v1,v1,1 << ENTRYHI_PID // increment master system PID
sltu t2,v1,t2 // any more PIDs to allocate
bne zero,t2,15f // if ne, more PIDs to allocate
// Flush the random part of the TB.
sw a0,SpA0(sp) // save process object address
jal KiFlushRandomTb // flush random part of TB
lw a0,SpA0(sp) // restore process object address
move v1,zero // set next PID value
// Swap address space to the specified process.
15: sw v1,KiPcr + PcCurrentPid(zero) // set current processor PID
lw t1,PrDirectoryTableBase(a0) // get page directory PDE
lw t2,PrDirectoryTableBase + 4(a0) // get hyper space PDE
li t3,PDE_BASE // get virtual address of PDR
or t3,t3,v1 // merge process PID
li t4,PDR_ENTRY << INDEX_INDEX // set entry index for PDR
DISABLE_INTERRUPTS(t5) // disable interrupts
.set noreorder
.set noat
mtc0 t3,entryhi // set VPN2 and PID of TB entry
mtc0 t1,entrylo0 // set first PDE value
mtc0 t2,entrylo1 // set second PDE value
mtc0 t4,index // set index of PDR entry
nop // 1 cycle hazzard
tlbwi // write system PDR TB entry
nop // 3 cycle hazzard
nop //
nop //
.set at
.set reorder
ENABLE_INTERRUPTS(t5) // enable interrupts
lw ra,SpRa(sp) // restore return address
addu sp,sp,SpFrameLength // deallocate stack frame
j ra // return
.end KiSwapProcess