974 lines
38 KiB
ArmAsm
974 lines
38 KiB
ArmAsm
|
// TITLE("System Initialization")
|
|||
|
//++
|
|||
|
//
|
|||
|
// Copyright (c) 1991 Microsoft Corporation
|
|||
|
//
|
|||
|
// Module Name:
|
|||
|
//
|
|||
|
// x4start.s
|
|||
|
//
|
|||
|
// Abstract:
|
|||
|
//
|
|||
|
// This module implements the code necessary to initially startup the
|
|||
|
// NT system.
|
|||
|
//
|
|||
|
// Author:
|
|||
|
//
|
|||
|
// David N. Cutler (davec) 5-Apr-1991
|
|||
|
//
|
|||
|
// Environment:
|
|||
|
//
|
|||
|
// Kernel mode only.
|
|||
|
//
|
|||
|
// Revision History:
|
|||
|
//
|
|||
|
//--
|
|||
|
|
|||
|
#include "ksmips.h"
|
|||
|
|
|||
|
//
|
|||
|
// Define external variables that can be addressed using GP.
|
|||
|
//
|
|||
|
|
|||
|
.extern KdDebuggerEnabled 1
|
|||
|
.extern KeNumberProcessIds 4
|
|||
|
.extern KeNumberProcessors 1
|
|||
|
.extern KeNumberTbEntries 4
|
|||
|
.extern KiBarrierWait 4
|
|||
|
.extern KiContextSwapLock 4
|
|||
|
.extern KiDispatcherLock 4
|
|||
|
.extern KiSynchIrql 4
|
|||
|
|
|||
|
SBTTL("System Initialization")
|
|||
|
//++
|
|||
|
//
|
|||
|
// Routine Description:
|
|||
|
//
|
|||
|
// This routine is called when the NT system begins execution.
|
|||
|
// Its function is to initialize system hardware state, call the
|
|||
|
// kernel initialization routine, and then fall into code that
|
|||
|
// represents the idle thread for all processors.
|
|||
|
//
|
|||
|
// Arguments:
|
|||
|
//
|
|||
|
// a0 - Supplies a pointer to the loader parameter block.
|
|||
|
//
|
|||
|
// Return Value:
|
|||
|
//
|
|||
|
// None.
|
|||
|
//
|
|||
|
//--
|
|||
|
|
|||
|
.struct 0
|
|||
|
SsArgA0:.space 4 // process address argument (a0)
|
|||
|
SsArgA1:.space 4 // thread address argument (a1)
|
|||
|
SsArgA2:.space 4 // idle stack argument (a2)
|
|||
|
SsArgA3:.space 4 // processor block address argument (a3)
|
|||
|
SsPrNum:.space 4 // processor number argument
|
|||
|
SsLdPrm:.space 4 // loader parameter block address
|
|||
|
SsPte: .space 2 * 4 // Pte values
|
|||
|
.space 4 // fill
|
|||
|
SsRa: .space 4 // saved return address
|
|||
|
|
|||
|
SsFrameLength: // length of stack frame
|
|||
|
|
|||
|
NESTED_ENTRY_S(KiSystemBegin, SsFrameLength, zero, INIT)
|
|||
|
|
|||
|
subu sp,sp,SsFrameLength // allocate stack frame
|
|||
|
sw ra,SsRa(sp) // save return address
|
|||
|
|
|||
|
PROLOGUE_END
|
|||
|
|
|||
|
ALTERNATE_ENTRY(KiInitializeSystem)
|
|||
|
|
|||
|
lw sp,LpbKernelStack(a0) // get address of idle thread stack
|
|||
|
subu sp,sp,SsFrameLength // allocate stack frame
|
|||
|
lw gp,LpbGpBase(a0) // get global pointer base address
|
|||
|
sw zero,SsRa(sp) // zero return address
|
|||
|
|
|||
|
DISABLE_INTERRUPTS(t0) // disable interrupts
|
|||
|
|
|||
|
//
|
|||
|
// Get page frame numbers for the PCR and PDR pages that were allocated by
|
|||
|
// the OS loader.
|
|||
|
//
|
|||
|
|
|||
|
lw s0,LpbPdrPage(a0) // set PDR page number
|
|||
|
lw s1,LpbPcrPage(a0) // set PCR page number
|
|||
|
move s2,a0 // save loader parameter block address
|
|||
|
lw s3,LpbPrcb(s2) // get processor block address
|
|||
|
lbu s3,PbNumber(s3) // get processor number
|
|||
|
lw s6,LpbPcrPage2(a0) // set second PCR page
|
|||
|
|
|||
|
//
|
|||
|
// Initialize the configuration, context, page mask, watch, and wired
|
|||
|
// registers.
|
|||
|
//
|
|||
|
// N.B. The base virtual address of the page table pages is left shift by
|
|||
|
// one because of the way VPN2 in inserted into the context register
|
|||
|
// when a TB miss occurs. The TB miss routine right arithmetic shifts
|
|||
|
// the address by one to obtain the real virtual address. Note that it
|
|||
|
// is assumed that bits <31:30> of PTE_BASE are set.
|
|||
|
//
|
|||
|
|
|||
|
li t0,PTE_BASE << 1 // set base virtual page table address
|
|||
|
li t1,FIXED_ENTRIES // set number of fixed TB entries
|
|||
|
li t2,0xf000 // set frame mask register value
|
|||
|
|
|||
|
.set noreorder
|
|||
|
.set noat
|
|||
|
mfc0 s7,config // get processor configuration
|
|||
|
mfc0 s8,prid // get processor id
|
|||
|
mtc0 t0,context // initialize the context register
|
|||
|
mtc0 zero,pagemask // initialize the page mask register
|
|||
|
mtc0 zero,taglo // initialize the tag low register
|
|||
|
mtc0 zero,watchlo // initialize the watch address register
|
|||
|
mtc0 zero,watchhi //
|
|||
|
mtc0 t1,wired // initialize the wired register
|
|||
|
and s4,s7,0x7 // isolate KSEG0 cache policy
|
|||
|
and t3,s8,0xff00 // isolate processor id
|
|||
|
xor t3,t3,0x900 // check if r10000 processor
|
|||
|
bne zero,t3,5f // if ne, not r10000 processor
|
|||
|
sll s5,s4,ENTRYLO_C // shift cache policy into position
|
|||
|
mtc0 t2,framemask // set frame mask register
|
|||
|
.set at
|
|||
|
.set reorder
|
|||
|
|
|||
|
//
|
|||
|
// Clear the translation buffer.
|
|||
|
//
|
|||
|
|
|||
|
5: bne zero,s3,20f // if ne, not processor zero
|
|||
|
li t0,48 // set number of TB entries for r4x00
|
|||
|
and t1,s8,0xff00 // isolate processor id
|
|||
|
xor t1,t1,0x900 // check if r10000 processor
|
|||
|
bne zero,t1,10f // if ne, not r10000 processor
|
|||
|
li t0,64 // set number of TB entries for r10000
|
|||
|
10: sw t0,KeNumberTbEntries // store number of TB entries
|
|||
|
li t0,256 // set number of process id's
|
|||
|
sw t0,KeNumberProcessIds //
|
|||
|
20: jal KiFlushFixedTb // flush fixed TB entries
|
|||
|
jal KiFlushRandomTb // flush random TB entries
|
|||
|
|
|||
|
//
|
|||
|
// Initialize fixed entries that map the PCR into system and user space.
|
|||
|
//
|
|||
|
|
|||
|
sll t0,s6,ENTRYLO_PFN // shift PFN into position
|
|||
|
or t0,t0,1 << ENTRYLO_G // Set G, V, D, and the cache policy
|
|||
|
or t0,t0,1 << ENTRYLO_V //
|
|||
|
or t0,t0,1 << ENTRYLO_D //
|
|||
|
or t0,t0,s5 //
|
|||
|
sll t1,s1,ENTRYLO_PFN // shift PFN into position
|
|||
|
or t1,t1,1 << ENTRYLO_G // Set G, V, D, and the cache policy
|
|||
|
or t1,t1,1 << ENTRYLO_V //
|
|||
|
or t1,t1,1 << ENTRYLO_D //
|
|||
|
or t1,t1,s5 //
|
|||
|
sw t0,SsPte(sp) // set first PTE value
|
|||
|
sw t1,SsPte + 4(sp) // set second PTE value
|
|||
|
addu a0,sp,SsPte // compute address of PTE values
|
|||
|
li a1,KiPcr & ~(1 << PAGE_SHIFT) // set virtual address/2 of PCR
|
|||
|
li a2,PCR_ENTRY // set index of system PCR entry
|
|||
|
jal KeFillFixedEntryTb // fill fixed TB entry
|
|||
|
|
|||
|
sll t0,s6,ENTRYLO_PFN // shift PFN into position
|
|||
|
or t0,t0,1 << ENTRYLO_G // Set G, V, D, and the cache policy
|
|||
|
or t0,t0,1 << ENTRYLO_V //
|
|||
|
or t0,t0,s5 //
|
|||
|
sll t1,s1,ENTRYLO_PFN // shift PFN into position
|
|||
|
or t1,t1,1 << ENTRYLO_G // set G, V, and cache policy
|
|||
|
or t1,t1,1 << ENTRYLO_V //
|
|||
|
or t1,t1,s5 //
|
|||
|
sw t0,SsPte(sp) // set first PTE value
|
|||
|
sw t1,SsPte + 4(sp) // set second PTE value
|
|||
|
addu a0,sp,SsPte // compute address of PTE values
|
|||
|
li a1,UsPcr & ~(1 << PAGE_SHIFT) // set virtual address/2 of PCR
|
|||
|
li a2,PCR_ENTRY + 1 // set index of user PCR entry
|
|||
|
jal KeFillFixedEntryTb // fill fixed TB entry
|
|||
|
|
|||
|
//
|
|||
|
// Set the cache policy for cached memory.
|
|||
|
//
|
|||
|
|
|||
|
li t1,KiPcr // get PCR address
|
|||
|
sw s4,PcCachePolicy(t1) // set cache policy for cached memory
|
|||
|
sw s5,PcAlignedCachePolicy(t1) //
|
|||
|
|
|||
|
//
|
|||
|
// Set the first level data and instruction cache fill size and size.
|
|||
|
//
|
|||
|
|
|||
|
lw t2,LpbFirstLevelDcacheSize(s2) //
|
|||
|
sw t2,PcFirstLevelDcacheSize(t1) //
|
|||
|
lw t2,LpbFirstLevelDcacheFillSize(s2) //
|
|||
|
sw t2,PcFirstLevelDcacheFillSize(t1) //
|
|||
|
lw t2,LpbFirstLevelIcacheSize(s2) //
|
|||
|
sw t2,PcFirstLevelIcacheSize(t1) //
|
|||
|
lw t2,LpbFirstLevelIcacheFillSize(s2) //
|
|||
|
sw t2,PcFirstLevelIcacheFillSize(t1) //
|
|||
|
|
|||
|
//
|
|||
|
// Set the second level data and instruction cache fill size and size.
|
|||
|
//
|
|||
|
|
|||
|
lw t2,LpbSecondLevelDcacheSize(s2) //
|
|||
|
sw t2,PcSecondLevelDcacheSize(t1) //
|
|||
|
lw t2,LpbSecondLevelDcacheFillSize(s2) //
|
|||
|
sw t2,PcSecondLevelDcacheFillSize(t1) //
|
|||
|
lw t2,LpbSecondLevelIcacheSize(s2) //
|
|||
|
sw t2,PcSecondLevelIcacheSize(t1) //
|
|||
|
lw t2,LpbSecondLevelIcacheFillSize(s2) //
|
|||
|
sw t2,PcSecondLevelIcacheFillSize(t1) //
|
|||
|
|
|||
|
//
|
|||
|
// Set the data cache fill size and alignment values.
|
|||
|
//
|
|||
|
|
|||
|
lw t2,PcSecondLevelDcacheSize(t1) // get second level dcache size
|
|||
|
lw t3,PcSecondLevelDcacheFillSize(t1) // get second level fill size
|
|||
|
bne zero,t2,30f // if ne, second level cache present
|
|||
|
lw t3,PcFirstLevelDcacheFillSize(t1) // get first level fill size
|
|||
|
30: subu t4,t3,1 // compute dcache alignment value
|
|||
|
sw t3,PcDcacheFillSize(t1) // set dcache fill size
|
|||
|
sw t4,PcDcacheAlignment(t1) // set dcache alignment value
|
|||
|
|
|||
|
//
|
|||
|
// Set the instruction cache fill size and alignment values.
|
|||
|
//
|
|||
|
|
|||
|
lw t2,PcSecondLevelIcacheSize(t1) // get second level icache size
|
|||
|
lw t3,PcSecondLevelIcacheFillSize(t1) // get second level fill size
|
|||
|
bne zero,t2,40f // if ne, second level cache present
|
|||
|
lw t3,PcFirstLevelIcacheFillSize(t1) // get first level fill size
|
|||
|
40: subu t4,t3,1 // compute icache alignment value
|
|||
|
sw t3,PcIcacheFillSize(t1) // set icache fill size
|
|||
|
sw t4,PcIcacheAlignment(t1) // set icache alignment value
|
|||
|
|
|||
|
//
|
|||
|
// Sweep the data and instruction caches.
|
|||
|
//
|
|||
|
|
|||
|
lw t0,__imp_HalSweepIcache // sweep the instruction cache
|
|||
|
jal t0 //
|
|||
|
lw t0,__imp_HalSweepDcache // sweep the data cache
|
|||
|
jal t0 //
|
|||
|
|
|||
|
//
|
|||
|
// Initialize the fixed entries that map the PDR pages.
|
|||
|
//
|
|||
|
|
|||
|
sll t0,s0,ENTRYLO_PFN // shift PFN into position
|
|||
|
or t0,t0,1 << ENTRYLO_V // set V, D, and cache policy
|
|||
|
or t0,t0,1 << ENTRYLO_D //
|
|||
|
or t0,t0,s5 //
|
|||
|
addu t1,t0,1 << ENTRYLO_PFN // compute PTE for second PDR page
|
|||
|
sw t0,SsPte(sp) // set first PTE value
|
|||
|
sw t1,SsPte + 4(sp) // set second PTE value
|
|||
|
addu a0,sp,SsPte // compute address of PTE values
|
|||
|
li a1,PDE_BASE // set system virtual address/2 of PDR
|
|||
|
li a2,PDR_ENTRY // set index of system PCR entry
|
|||
|
jal KeFillFixedEntryTb // fill fixed TB entry
|
|||
|
li t2,PDE_BASE // set virtual address of PDR
|
|||
|
lw t0,SsPte(sp) // get first PTE value
|
|||
|
lw t1,SsPte + 4(sp) // get second PTE value
|
|||
|
sw t0,((PDE_BASE >> (PDI_SHIFT - 2)) & 0xffc)(t2) // set recursive PDE
|
|||
|
sw t1,((PDE_BASE >> (PDI_SHIFT - 2)) & 0xffc) + 4(t2) // set hyper PDE
|
|||
|
|
|||
|
//
|
|||
|
// Initialize the Processor Control Registers (PCR).
|
|||
|
//
|
|||
|
|
|||
|
li t1,KiPcr // get PCR address
|
|||
|
|
|||
|
//
|
|||
|
// Initialize the minor and major version numbers.
|
|||
|
//
|
|||
|
|
|||
|
li t2,PCR_MINOR_VERSION // set minor version number
|
|||
|
sh t2,PcMinorVersion(t1) //
|
|||
|
li t2,PCR_MAJOR_VERSION // set major version number
|
|||
|
sh t2,PcMajorVersion(t1) //
|
|||
|
|
|||
|
//
|
|||
|
// Set address of processor block.
|
|||
|
//
|
|||
|
|
|||
|
lw t2,LpbPrcb(s2) // set processor block address
|
|||
|
sw t2,PcPrcb(t1) //
|
|||
|
|
|||
|
//
|
|||
|
// Initialize the routine addresses in the exception dispatch table.
|
|||
|
//
|
|||
|
|
|||
|
la t2,KiInvalidException // set address of invalid exception
|
|||
|
li t3,XCODE_VECTOR_LENGTH // set length of dispatch vector
|
|||
|
la t4,PcXcodeDispatch(t1) // compute address of dispatch vector
|
|||
|
50: sw t2,0(t4) // fill dispatch vector
|
|||
|
subu t3,t3,1 // decrement number of entries
|
|||
|
addu t4,t4,4 // advance to next vector entry
|
|||
|
bgtz t3,50b // if gtz, more to fill
|
|||
|
|
|||
|
la t2,KiInterruptException // Initialize exception dispatch table
|
|||
|
sw t2,PcXcodeDispatch + XCODE_INTERRUPT(t1) //
|
|||
|
la t2,KiModifyException //
|
|||
|
sw t2,PcXcodeDispatch + XCODE_MODIFY(t1) //
|
|||
|
la t2,KiReadMissException // set read miss address for r4x00
|
|||
|
and t3,s8,0xff00 // isolate processor id
|
|||
|
xor t3,t3,0x900 // check if r10000 processor
|
|||
|
bne zero,t3,55f // if ne, not r10000 processor
|
|||
|
la t2,KiReadMissException9.x // set read miss address for r10000
|
|||
|
55: sw t2,PcXcodeDispatch + XCODE_READ_MISS(t1) //
|
|||
|
la t2,KiWriteMissException //
|
|||
|
sw t2,PcXcodeDispatch + XCODE_WRITE_MISS(t1) //
|
|||
|
la t2,KiReadAddressErrorException //
|
|||
|
sw t2,PcXcodeDispatch + XCODE_READ_ADDRESS_ERROR(t1) //
|
|||
|
la t2,KiWriteAddressErrorException //
|
|||
|
sw t2,PcXcodeDispatch + XCODE_WRITE_ADDRESS_ERROR(t1) //
|
|||
|
la t2,KiInstructionBusErrorException //
|
|||
|
sw t2,PcXcodeDispatch + XCODE_INSTRUCTION_BUS_ERROR(t1) //
|
|||
|
la t2,KiDataBusErrorException //
|
|||
|
sw t2,PcXcodeDispatch + XCODE_DATA_BUS_ERROR(t1) //
|
|||
|
la t2,KiSystemServiceException //
|
|||
|
sw t2,PcXcodeDispatch + XCODE_SYSTEM_CALL(t1) //
|
|||
|
la t2,KiBreakpointException //
|
|||
|
sw t2,PcXcodeDispatch + XCODE_BREAKPOINT(t1) //
|
|||
|
la t2,KiIllegalInstructionException //
|
|||
|
sw t2,PcXcodeDispatch + XCODE_ILLEGAL_INSTRUCTION(t1) //
|
|||
|
la t2,KiCoprocessorUnusableException //
|
|||
|
sw t2,PcXcodeDispatch + XCODE_COPROCESSOR_UNUSABLE(t1) //
|
|||
|
la t2,KiIntegerOverflowException //
|
|||
|
sw t2,PcXcodeDispatch + XCODE_INTEGER_OVERFLOW(t1) //
|
|||
|
la t2,KiTrapException //
|
|||
|
sw t2,PcXcodeDispatch + XCODE_TRAP(t1) //
|
|||
|
la t2,KiInstructionCoherencyException //
|
|||
|
sw t2,PcXcodeDispatch + XCODE_VIRTUAL_INSTRUCTION(t1) //
|
|||
|
la t2,KiFloatingException //
|
|||
|
sw t2,PcXcodeDispatch + XCODE_FLOATING_EXCEPTION(t1) //
|
|||
|
la t2,KiUserAddressErrorException //
|
|||
|
sw t2,PcXcodeDispatch + XCODE_INVALID_USER_ADDRESS(t1)
|
|||
|
la t2,KiPanicException //
|
|||
|
sw t2,PcXcodeDispatch + XCODE_PANIC(t1) //
|
|||
|
la t2,KiDataCoherencyException //
|
|||
|
sw t2,PcXcodeDispatch + XCODE_VIRTUAL_DATA(t1) //
|
|||
|
|
|||
|
//
|
|||
|
// Initialize the addresses of various data structures that are referenced
|
|||
|
// from the exception and interrupt handling code.
|
|||
|
//
|
|||
|
// N.B. The panic stack is a separate stack that is used when the current
|
|||
|
// kernel stack overlfows.
|
|||
|
//
|
|||
|
// N.B. The interrupt stack is a separate stack and is used to process all
|
|||
|
// interrupts that run at IRQL 3 and above.
|
|||
|
//
|
|||
|
|
|||
|
lw t2,LpbKernelStack(s2) // set initial stack address
|
|||
|
sw t2,PcInitialStack(t1) //
|
|||
|
lw t2,LpbPanicStack(s2) // set panic stack address
|
|||
|
sw t2,PcPanicStack(t1) //
|
|||
|
lw t2,LpbInterruptStack(s2) // set interrupt stack address
|
|||
|
sw t2,PcInterruptStack(t1) //
|
|||
|
sw gp,PcSystemGp(t1) // set system global pointer address
|
|||
|
lw t2,LpbThread(s2) // set current thread address
|
|||
|
sw t2,PcCurrentThread(t1) //
|
|||
|
|
|||
|
//
|
|||
|
// Set current IRQL to highest value.
|
|||
|
//
|
|||
|
|
|||
|
li t2,HIGH_LEVEL // set current IRQL
|
|||
|
sb t2,PcCurrentIrql(t1) //
|
|||
|
|
|||
|
//
|
|||
|
// Set processor id and configuration.
|
|||
|
//
|
|||
|
|
|||
|
sw s7,PcSystemReserved(t1) // save processor configuration
|
|||
|
sw s8,PcProcessorId(t1) // save processor id
|
|||
|
|
|||
|
//
|
|||
|
// Clear floating status and zero the count and compare registers.
|
|||
|
//
|
|||
|
|
|||
|
.set noreorder
|
|||
|
.set noat
|
|||
|
ctc1 zero,fsr // clear floating status
|
|||
|
mtc0 zero,count // initialize the count register
|
|||
|
mtc0 zero,compare // initialize the compare register
|
|||
|
.set at
|
|||
|
.set reorder
|
|||
|
|
|||
|
//
|
|||
|
// Set system dispatch address limits used by get and set context.
|
|||
|
//
|
|||
|
|
|||
|
la t2,KiSystemServiceDispatchStart // set starting address of range
|
|||
|
sw t2,PcSystemServiceDispatchStart(t1) //
|
|||
|
la t2,KiSystemServiceDispatchEnd // set ending address of range
|
|||
|
sw t2,PcSystemServiceDispatchEnd(t1) //
|
|||
|
|
|||
|
//
|
|||
|
// Copy the TB miss, XTB miss, cache parity, and general exception handlers to
|
|||
|
// low memory.
|
|||
|
//
|
|||
|
|
|||
|
bne zero,s3,100f // if ne, not processor zero
|
|||
|
|
|||
|
//
|
|||
|
// Copy TB Miss Handler.
|
|||
|
//
|
|||
|
|
|||
|
la t2,KiTbMissStartAddress2.x // get user TB miss start address
|
|||
|
la t3,KiTbMissEndAddress3.x // get user TB miss end address
|
|||
|
and a0,s8,0xfff0 // isolate id and major chip version
|
|||
|
xor a0,a0,0x420 // test if id 4 and version 2.0 chip
|
|||
|
beq zero,a0,60f // if eq, version 2.0 chip
|
|||
|
la t2,KiTbMissStartAddress3.x // get user TB miss start address
|
|||
|
and a0,s8,0xff00 // isolate processor id
|
|||
|
xor a0,a0,0x900 // check if r10000 processor
|
|||
|
bne zero,a0,60f // if ne, not r10000 processor
|
|||
|
la t2,KiTbMissStartAddress9.x // get user TB miss start address
|
|||
|
la t3,KiTbMissEndAddress9.x // get user TB miss end address
|
|||
|
60: li t4,KSEG0_BASE // get copy address
|
|||
|
70: lw t5,0(t2) // copy code to low memory
|
|||
|
sw t5,0(t4) //
|
|||
|
addu t2,t2,4 // advance copy pointers
|
|||
|
addu t4,t4,4 //
|
|||
|
bne t2,t3,70b // if ne, more to copy
|
|||
|
|
|||
|
//
|
|||
|
// Copy XTB Miss Handler.
|
|||
|
//
|
|||
|
|
|||
|
la t2,KiXTbMissStartAddress2.x // get user TB miss start address
|
|||
|
la t3,KiXTbMissEndAddress3.x // get user TB miss end address
|
|||
|
and a0,s8,0xfff0 // isolate id and major chip version
|
|||
|
xor a0,a0,0x420 // test if id 4 and version 2.0 chip
|
|||
|
beq zero,a0,73f // if eq, version 2.0 chip
|
|||
|
la t2,KiXTbMissStartAddress3.x // get user TB miss start address
|
|||
|
and a0,s8,0xff00 // isolate processor id
|
|||
|
xor a0,a0,0x900 // check if r10000 processor
|
|||
|
bne zero,a0,73f // if ne, not r10000 processor
|
|||
|
la t2,KiXTbMissStartAddress9.x // get user TB miss start address
|
|||
|
la t3,KiXTbMissEndAddress9.x // get user TB miss end address
|
|||
|
73: li t4,KSEG0_BASE + 0x80 // get copy address
|
|||
|
77: lw t5,0(t2) // copy code to low memory
|
|||
|
sw t5,0(t4) //
|
|||
|
addu t2,t2,4 // advance copy pointers
|
|||
|
addu t4,t4,4 //
|
|||
|
bne t2,t3,77b // if ne, more to copy
|
|||
|
|
|||
|
//
|
|||
|
// Copy Cache Error Handler.
|
|||
|
//
|
|||
|
|
|||
|
la t2,KiCacheErrorStartAddress // get cache error start address
|
|||
|
la t3,KiCacheErrorEndAddress // get cache error end address
|
|||
|
li t4,KSEG1_BASE + 0x100 // get copy address
|
|||
|
80: lw t5,0(t2) // copy code to low memory
|
|||
|
sw t5,0(t4) //
|
|||
|
addu t2,t2,4 // advance copy pointers
|
|||
|
addu t4,t4,4 //
|
|||
|
bne t2,t3,80b // if ne, more to copy
|
|||
|
|
|||
|
//
|
|||
|
// Copy General Exception Handler.
|
|||
|
//
|
|||
|
|
|||
|
la t2,KiGeneralExceptionStartAddress // get general exception start address
|
|||
|
la t3,KiGeneralExceptionEndAddress // get general exception end address
|
|||
|
li t4,KSEG0_BASE + 0x180 // get copy address
|
|||
|
90: lw t5,0(t2) // copy code to low memory
|
|||
|
sw t5,0(t4) //
|
|||
|
addu t2,t2,4 // advance copy pointers
|
|||
|
addu t4,t4,4 //
|
|||
|
bne t2,t3,90b // if ne, more to copy
|
|||
|
|
|||
|
//
|
|||
|
// Set the default cache error routine address.
|
|||
|
//
|
|||
|
|
|||
|
la t0,SOFT_RESET_VECTOR // get soft reset vector address
|
|||
|
la t1,CACHE_ERROR_VECTOR // get cache error vector address
|
|||
|
sw t0,0(t1) // set default cache error routine
|
|||
|
|
|||
|
//
|
|||
|
// Sweep the data and instruction caches.
|
|||
|
//
|
|||
|
|
|||
|
100: lw t0,__imp_HalSweepIcache // sweep the instruction cache
|
|||
|
jal t0 //
|
|||
|
lw t0,__imp_HalSweepDcache // sweep the data cache
|
|||
|
jal t0 //
|
|||
|
|
|||
|
// ****** temp ******
|
|||
|
//
|
|||
|
// Setup watch registers to catch write to location 0.
|
|||
|
//
|
|||
|
// ****** temp ******
|
|||
|
|
|||
|
// .set noreorder
|
|||
|
// .set noat
|
|||
|
// li t0,1 // set to watch writes to location 0
|
|||
|
// mtc0 t0,watchlo //
|
|||
|
// mtc0 zero,watchhi //
|
|||
|
// .set at
|
|||
|
// .set reorder
|
|||
|
|
|||
|
//
|
|||
|
// Setup arguments and call kernel initialization routine.
|
|||
|
//
|
|||
|
|
|||
|
lw s0,LpbProcess(s2) // get idle process address
|
|||
|
lw s1,LpbThread(s2) // get idle thread address
|
|||
|
move a0,s0 // set idle process address
|
|||
|
move a1,s1 // set idle thread address
|
|||
|
lw a2,LpbKernelStack(s2) // set idle thread stack address
|
|||
|
lw a3,LpbPrcb(s2) // get processor block address
|
|||
|
sw s3,SsPrNum(sp) // set processor number
|
|||
|
sw s2,SsLdPrm(sp) // set loader parameter block address
|
|||
|
jal KiInitializeKernel // initialize system data structures
|
|||
|
|
|||
|
//
|
|||
|
// Control is returned to the idle thread with IRQL at HIGH_LEVEL. Lower IRQL
|
|||
|
// to DISPATCH_LEVEL, set wait IRQL of idle thread, load global register values,
|
|||
|
// and enter idle loop.
|
|||
|
//
|
|||
|
|
|||
|
move s7,s3 // set processor number
|
|||
|
lw s0,KiPcr + PcPrcb(zero) // get processor control block address
|
|||
|
addu s3,s0,PbDpcListHead // compute DPC listhead address
|
|||
|
li a0,DISPATCH_LEVEL // get dispatch level IRQL
|
|||
|
sb a0,ThWaitIrql(s1) // set wait IRQL of idle thread
|
|||
|
jal KeLowerIrql // lower IRQL
|
|||
|
|
|||
|
DISABLE_INTERRUPTS(s8) // disable interrupts
|
|||
|
|
|||
|
or s8,s8,1 << PSR_IE // set interrupt enable bit set
|
|||
|
subu s6,s8,1 << PSR_IE // clear interrupt enable bit
|
|||
|
|
|||
|
ENABLE_INTERRUPTS(s8) // enable interrupts
|
|||
|
|
|||
|
move s4,zero // clear breakin loop counter
|
|||
|
lbu a0,KiSynchIrql // get new IRQL value
|
|||
|
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 s5,s8,t1 // clear current interrupt enables
|
|||
|
or s5,s5,t0 // set new interrupt enables
|
|||
|
|
|||
|
//
|
|||
|
// In a multiprocessor system the boot processor proceeds directly into
|
|||
|
// the idle loop. As other processors start executing, however, they do
|
|||
|
// not directly enter the idle loop and spin until all processors have
|
|||
|
// been started and the boot master allows them to proceed.
|
|||
|
//
|
|||
|
|
|||
|
#if !defined(NT_UP)
|
|||
|
|
|||
|
110: lw t0,KiBarrierWait // get the current barrier wait value
|
|||
|
bne zero,t0,110b // if ne, spin until allowed to proceed
|
|||
|
lbu t1,KiPcr + PcNumber(zero) // get current processor number
|
|||
|
beq zero,t1,120f // if eq, processor zero
|
|||
|
jal HalAllProcessorsStarted // perform platform specific operations
|
|||
|
bne zero,v0,120f // if ne, initialization succeeded
|
|||
|
li a0,HAL1_INITIALIZATION_FAILED // set bug check reason
|
|||
|
jal KeBugCheck // bug check
|
|||
|
|
|||
|
#endif
|
|||
|
|
|||
|
//
|
|||
|
// Allocate an exception frame and store the nonvolatile register and
|
|||
|
// return address in the frame so when a context switch from the idle
|
|||
|
// thread to another thread occurs, context does not have to be saved
|
|||
|
// and the special swtich from idle entry pointer in the context swap
|
|||
|
// code can be called.
|
|||
|
//
|
|||
|
// Registers s0 - s8 have the following contents:
|
|||
|
//
|
|||
|
// s0 - Address of the current processor block.
|
|||
|
// s1 - Not used.
|
|||
|
// s2 - Not used.
|
|||
|
// s3 - Address of DPC listhead for current processor.
|
|||
|
// s4 - Debugger breakin poll counter.
|
|||
|
// s5 - Saved PSR with interrupt enabled and IRQL of synchronization level.
|
|||
|
// s6 - Saved PSR with interrupts disabled and an IRQL of DISPATCH_LEVEL.
|
|||
|
// s7 - Number of the current processor.
|
|||
|
// s8 - Saved PSR with interrupt enabled and IRQL of DISPATCH_LEVEL.
|
|||
|
//
|
|||
|
|
|||
|
120: subu sp,sp,ExceptionFrameLength // allocate exception frame
|
|||
|
sw s3,ExIntS3(sp) // save register s3 - s8
|
|||
|
sw s4,ExIntS4(sp) //
|
|||
|
sw s5,ExIntS5(sp) //
|
|||
|
sw s6,ExIntS6(sp) //
|
|||
|
sw s7,ExIntS7(sp) //
|
|||
|
sw s8,ExIntS8(sp) //
|
|||
|
la ra,KiIdleLoop // set address of swap return
|
|||
|
sw ra,ExSwapReturn(sp) //
|
|||
|
j KiIdleLoop //
|
|||
|
|
|||
|
.end KiSystemBegin
|
|||
|
|
|||
|
//
|
|||
|
// The following code represents the idle thread for a processor. The idle
|
|||
|
// thread executes at IRQL DISPATCH_LEVEL and continually polls for work to
|
|||
|
// do. Control may be given to this loop either as a result of a return from
|
|||
|
// the system initialize routine or as the result of starting up another
|
|||
|
// processor in a multiprocessor configuration.
|
|||
|
//
|
|||
|
|
|||
|
LEAF_ENTRY(KiIdleLoop)
|
|||
|
|
|||
|
#if DBG
|
|||
|
|
|||
|
move s4,zero // clear breakin loop counter
|
|||
|
|
|||
|
#endif
|
|||
|
|
|||
|
//
|
|||
|
// Lower IRQL to DISPATCH_LEVEL and enable interrupts.
|
|||
|
//
|
|||
|
|
|||
|
DISABLE_INTERRUPTS(t0) // disable interrupts
|
|||
|
|
|||
|
li a0,DISPATCH_LEVEL // get new IRQL value
|
|||
|
sb a0,KiPcr + PcCurrentIrql(zero) // set new IRQL
|
|||
|
|
|||
|
ENABLE_INTERRUPTS(s8) // enable interrupts
|
|||
|
|
|||
|
//
|
|||
|
// Check if the debugger is enabled, the current processor is zero, and
|
|||
|
// whether it is time to poll for a debugger breakin.
|
|||
|
//
|
|||
|
|
|||
|
KiIdleTop: //
|
|||
|
|
|||
|
#if DBG
|
|||
|
|
|||
|
#if !defined(NT_UP)
|
|||
|
|
|||
|
bne zero,s7,CheckDpcList // if ne, not processor zero
|
|||
|
|
|||
|
#endif
|
|||
|
|
|||
|
subu s4,s4,1 // decrement poll counter
|
|||
|
bgtz s4,CheckDpcList // if gtz, then not time to poll
|
|||
|
lbu t0,KdDebuggerEnabled // check if debugger is enabled
|
|||
|
li s4,200 * 1000 // set breakin loop counter
|
|||
|
beq zero,t0,CheckDpcList // if eq, debugger not enabled
|
|||
|
jal KdPollBreakIn // check if breakin is requested
|
|||
|
beq zero,v0,CheckDpcList // if eq, no breakin requested
|
|||
|
li a0,DBG_STATUS_CONTROL_C // break in and send
|
|||
|
jal DbgBreakPointWithStatus // status to the debugger
|
|||
|
|
|||
|
#endif
|
|||
|
|
|||
|
//
|
|||
|
// Enable interrupts to allow any outstanding interrupts to occur, then
|
|||
|
// disable interrupts and check if there is any work in the DPC list of
|
|||
|
// the current processor.
|
|||
|
//
|
|||
|
|
|||
|
CheckDpcList: //
|
|||
|
|
|||
|
//
|
|||
|
// N.B. The following code enables interrupts for a few cycles, then
|
|||
|
// disables them again for the subsequent DPC and next thread
|
|||
|
// checks.
|
|||
|
//
|
|||
|
|
|||
|
.set noreorder
|
|||
|
.set noat
|
|||
|
mtc0 s8,psr // enable interrupts
|
|||
|
nop //
|
|||
|
nop //
|
|||
|
nop //
|
|||
|
nop // allow interrupts to occur
|
|||
|
nop //
|
|||
|
mtc0 s6,psr // disable interrupts
|
|||
|
nop // 3 cycle hazzard
|
|||
|
nop //
|
|||
|
nop //
|
|||
|
.set at
|
|||
|
.set reorder
|
|||
|
|
|||
|
//
|
|||
|
// Process the deferred procedure call list for the current processor.
|
|||
|
//
|
|||
|
|
|||
|
lw a0,LsFlink(s3) // get address of next entry
|
|||
|
beq a0,s3,CheckNextThread // if eq, DPC list is empty
|
|||
|
|
|||
|
.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
|
|||
|
|
|||
|
move v0,s8 // set previous PSR value
|
|||
|
jal KiRetireDpcList // process the DPC list
|
|||
|
|
|||
|
#if DBG
|
|||
|
|
|||
|
move s4,zero // clear breakin loop counter
|
|||
|
|
|||
|
#endif
|
|||
|
|
|||
|
//
|
|||
|
// Check if a thread has been selected to run on the current processor.
|
|||
|
//
|
|||
|
|
|||
|
CheckNextThread: //
|
|||
|
lw s2,PbNextThread(s0) // get address of next thread object
|
|||
|
beq zero,s2,20f // if eq, no thread selected
|
|||
|
|
|||
|
//
|
|||
|
// A thread has been selected for execution on this processor. Acquire
|
|||
|
// dispatcher database lock, get the thread address again (it may have
|
|||
|
// changed), clear the address of the next thread in the processor block,
|
|||
|
// and call swap context to start execution of the selected thread.
|
|||
|
//
|
|||
|
// N.B. If the dispatcher database lock cannot be obtained immediately,
|
|||
|
// then attempt to process another DPC rather than spinning on the
|
|||
|
// dispatcher database lock.
|
|||
|
//
|
|||
|
|
|||
|
lbu a0,KiSynchIrql // get new IRQL value
|
|||
|
|
|||
|
#if !defined(NT_UP)
|
|||
|
|
|||
|
10: ll t0,KiDispatcherLock // get current lock value
|
|||
|
move t1,s2 // set lock ownership value
|
|||
|
bne zero,t0,CheckDpcList // if ne, spin lock owned
|
|||
|
sc t1,KiDispatcherLock // set spin lock owned
|
|||
|
beq zero,t1,10b // if eq, store conditional failed
|
|||
|
|
|||
|
#endif
|
|||
|
|
|||
|
//
|
|||
|
// Raise IRQL to synchronization level and enable interrupts.
|
|||
|
//
|
|||
|
|
|||
|
sb a0,KiPcr + PcCurrentIrql(zero) // set new IRQL
|
|||
|
|
|||
|
ENABLE_INTERRUPTS(s5) // enable interrupts
|
|||
|
|
|||
|
lw s1,PbCurrentThread(s0) // get address of current thread
|
|||
|
lw s2,PbNextThread(s0) // get address of next thread object
|
|||
|
sw zero,PbNextThread(s0) // clear next thread address
|
|||
|
sw s2,PbCurrentThread(s0) // set address of current thread object
|
|||
|
|
|||
|
//
|
|||
|
// 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. In
|
|||
|
// this case the old process is the system process, but the context swap
|
|||
|
// code releases the context swap lock so it must be acquired.
|
|||
|
//
|
|||
|
// 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)
|
|||
|
|
|||
|
15: ll t0,KiContextSwapLock // get current lock value
|
|||
|
move t1,s2 // set ownership value
|
|||
|
bne zero,t0,15b // if ne, lock already owned
|
|||
|
sc t1,KiContextSwapLock // set lock ownership value
|
|||
|
beq zero,t1,15b // if eq, store conditional failed
|
|||
|
sw zero,KiDispatcherLock // set lock not owned
|
|||
|
|
|||
|
#endif
|
|||
|
|
|||
|
j SwapFromIdle // swap context to new thread
|
|||
|
|
|||
|
//
|
|||
|
// There are no entries in the DPC list and a thread has not been selected
|
|||
|
// for excuttion on this processor. Call the HAL so power managment can be
|
|||
|
// performed.
|
|||
|
//
|
|||
|
// N.B. The HAL is called with interrupts disabled. The HAL will return
|
|||
|
// with interrupts enabled.
|
|||
|
//
|
|||
|
|
|||
|
20: la ra,KiIdleTop // set return address
|
|||
|
lw t0,__imp_HalProcessorIdle // notify HAL of idle state
|
|||
|
j t0 //
|
|||
|
|
|||
|
.end KiIdleLoop
|
|||
|
|
|||
|
SBTTL("Retire Deferred Procedure Call List")
|
|||
|
//++
|
|||
|
//
|
|||
|
// Routine Description:
|
|||
|
//
|
|||
|
// This routine is called to retire the specified deferred procedure
|
|||
|
// call list. DPC routines are called using the idle thread (current)
|
|||
|
// stack.
|
|||
|
//
|
|||
|
// N.B. Interrupts must be disabled on entry to this routine. Control
|
|||
|
// is returned to the caller with the same conditions true.
|
|||
|
//
|
|||
|
// Arguments:
|
|||
|
//
|
|||
|
// v0 - Previous PSR value.
|
|||
|
// s0 - Address of the current PRCB.
|
|||
|
//
|
|||
|
// Return value:
|
|||
|
//
|
|||
|
// None.
|
|||
|
//
|
|||
|
//--
|
|||
|
|
|||
|
.struct 0
|
|||
|
.space 4 * 4 // argument save area
|
|||
|
DpRa: .space 4 // return address
|
|||
|
.space 4 // fill
|
|||
|
|
|||
|
#if DBG
|
|||
|
|
|||
|
DpStart:.space 4 // DPC start time in ticks
|
|||
|
DpFunct:.space 4 // DPC function address
|
|||
|
DpCount:.space 4 // interrupt count at start of DPC
|
|||
|
DpTime: .space 4 // interrupt time at start of DPC
|
|||
|
|
|||
|
#endif
|
|||
|
|
|||
|
DpcFrameLength: // DPC frame length
|
|||
|
|
|||
|
NESTED_ENTRY(KiRetireDpcList, DpcFrameLength, zero)
|
|||
|
|
|||
|
subu sp,sp,DpcFrameLength // allocate stack frame
|
|||
|
sw ra,DpRa(sp) // save return address
|
|||
|
|
|||
|
PROLOGUE_END
|
|||
|
|
|||
|
5: sw sp,PbDpcRoutineActive(s0) // set DPC routine active
|
|||
|
sw sp,KiPcr + PcDpcRoutineActive(zero) //
|
|||
|
|
|||
|
//
|
|||
|
// Process the DPC list.
|
|||
|
//
|
|||
|
|
|||
|
10: addu a1,s0,PbDpcListHead // compute DPC listhead address
|
|||
|
lw a0,LsFlink(a1) // get address of next entry
|
|||
|
beq a0,a1,60f // if eq, DPC list is empty
|
|||
|
|
|||
|
#if !defined(NT_UP)
|
|||
|
|
|||
|
20: ll t1,PbDpcLock(s0) // get current lock value
|
|||
|
move t2,s0 // set lock ownership value
|
|||
|
bne zero,t1,20b // if ne, spin lock owned
|
|||
|
sc t2,PbDpcLock(s0) // set spin lock owned
|
|||
|
beq zero,t2,20b // if eq, store conditional failed
|
|||
|
lw a0,LsFlink(a1) // get address of next entry
|
|||
|
beq a0,a1,50f // if eq, DPC list is empty
|
|||
|
|
|||
|
#endif
|
|||
|
|
|||
|
lw t1,LsFlink(a0) // get address of next entry
|
|||
|
subu a0,a0,DpDpcListEntry // compute address of DPC Object
|
|||
|
sw t1,LsFlink(a1) // set address of next in header
|
|||
|
sw a1,LsBlink(t1) // set address of previous in next
|
|||
|
lw a1,DpDeferredContext(a0) // get deferred context argument
|
|||
|
lw a2,DpSystemArgument1(a0) // get first system argument
|
|||
|
lw a3,DpSystemArgument2(a0) // get second system argument
|
|||
|
lw t1,DpDeferredRoutine(a0) // get deferred routine address
|
|||
|
sw zero,DpLock(a0) // clear DPC inserted state
|
|||
|
lw t2,PbDpcQueueDepth(s0) // decrement the DPC queue depth
|
|||
|
subu t2,t2,1 //
|
|||
|
sw t2,PbDpcQueueDepth(s0) //
|
|||
|
|
|||
|
#if !defined(NT_UP)
|
|||
|
|
|||
|
sw zero,PbDpcLock(s0) // set spin lock not owned
|
|||
|
|
|||
|
#endif
|
|||
|
|
|||
|
ENABLE_INTERRUPTS(v0) // enable interrupts
|
|||
|
|
|||
|
#if DBG
|
|||
|
|
|||
|
sw t1,DpFunct(sp) // save DPC function address
|
|||
|
lw t2,KeTickCount // save current tick count
|
|||
|
sw t2,DpStart(sp) //
|
|||
|
lw t3,PbInterruptCount(s0) // get current interrupt count
|
|||
|
lw t4,PbInterruptTime(s0) // get current interrupt time
|
|||
|
sw t3,DpCount(sp) // save interrupt count at start of DPC
|
|||
|
sw t4,DpTime(sp) // save interrupt time at start of DPC
|
|||
|
|
|||
|
#endif
|
|||
|
|
|||
|
jal t1 // call DPC routine
|
|||
|
|
|||
|
#if DBG
|
|||
|
|
|||
|
lbu t0,KiPcr + PcCurrentIrql(zero) // get current IRQL
|
|||
|
sltu t1,t0,DISPATCH_LEVEL // check if less than dispatch level
|
|||
|
beq zero,t1,30f // if eq, not less than dispatch level
|
|||
|
lw t1,DpFunct(sp) // get DPC function address
|
|||
|
jal DbgBreakPoint // execute debug breakpoint
|
|||
|
30: lw t0,KeTickCount // get current tick count
|
|||
|
lw t1,DpStart(sp) // get starting tick count
|
|||
|
lw t2,DpFunct(sp) // get DPC function address
|
|||
|
subu t3,t0,t1 // compute time in DPC function
|
|||
|
sltu t3,t3,100 // check if less than one second
|
|||
|
bne zero,t3,40f // if ne, less than one second
|
|||
|
lw t3,PbInterruptCount(s0) // get current interrupt count
|
|||
|
lw t4,PbInterruptTime(s0) // get current interrupt time
|
|||
|
lw t5,DpCount(sp) // get starting interrupt count
|
|||
|
lw t6,DpTime(sp) // get starting interrupt time
|
|||
|
subu t3,t3,t5 // compute number of interrupts
|
|||
|
subu t4,t4,t6 // compute time of interrupts
|
|||
|
jal DbgBreakPoint // execute debug breakpoint
|
|||
|
|
|||
|
#endif
|
|||
|
|
|||
|
40: DISABLE_INTERRUPTS(v0) // disable interrupts
|
|||
|
|
|||
|
b 10b //
|
|||
|
|
|||
|
//
|
|||
|
// Unlock DPC list and clear DPC active.
|
|||
|
//
|
|||
|
|
|||
|
50:
|
|||
|
|
|||
|
#if !defined(NT_UP)
|
|||
|
|
|||
|
sw zero,PbDpcLock(s0) // set spin lock not owned
|
|||
|
|
|||
|
#endif
|
|||
|
|
|||
|
60: sw zero,PbDpcRoutineActive(s0) // clear DPC routine active
|
|||
|
sw zero,KiPcr + PcDpcRoutineActive(zero) //
|
|||
|
sw zero,PbDpcInterruptRequested(s0) // clear DPC interrupt requested
|
|||
|
|
|||
|
//
|
|||
|
// Check one last time that the DPC list is empty. This is required to
|
|||
|
// close a race condition with the DPC queuing code where it appears that
|
|||
|
// a DPC routine is active (and thus an interrupt is not requested), but
|
|||
|
// this code has decided the DPC list is empty and is clearing the DPC
|
|||
|
// active flag.
|
|||
|
//
|
|||
|
|
|||
|
addu a1,s0,PbDpcListHead // compute DPC listhead address
|
|||
|
lw a0,LsFlink(a1) // get address of next entry
|
|||
|
bne a0,a1,5b // if ne, DPC list is not empty
|
|||
|
lw ra,DpRa(sp) // restore return address
|
|||
|
addu sp,sp,DpcFrameLength // deallocate stack frame
|
|||
|
j ra // return
|
|||
|
|
|||
|
.end KiRetireDpcList
|