From e611b132f9b8abe35b362e5870b74bce94a1e58e Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 16 May 2020 20:51:50 -0700 Subject: initial commit --- private/ntos/ke/mips/x4ctxsw.s | 1497 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1497 insertions(+) create mode 100644 private/ntos/ke/mips/x4ctxsw.s (limited to 'private/ntos/ke/mips/x4ctxsw.s') diff --git a/private/ntos/ke/mips/x4ctxsw.s b/private/ntos/ke/mips/x4ctxsw.s new file mode 100644 index 000000000..fcda60c50 --- /dev/null +++ b/private/ntos/ke/mips/x4ctxsw.s @@ -0,0 +1,1497 @@ +// 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,1 << PSR_CU1 // get coprocessor 1 enable bit + 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,1 << PSR_CU1 // get coprocessor 1 enable bit + 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,1 << PSR_CU1 // 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 -- cgit v1.2.3