// TITLE( "Start System" ) // Copyright (c) 1992 Digital Equipment Corporation // Module: // start.s // Abstract: // This module implements the code necessary to iniitially start NT // on an alpha - it includes the routine that first receives control // when the loader executes the kernel. // Author: // Joe Notarangelo 02-Apr-1992 // Environment: // Kernel Mode only. // Revision History: #include "ksalpha.h" #define TotalFrameLength (KERNEL_STACK_SIZE - (TrapFrameLength + \ ExceptionFrameLength) ) // Global Variables .data #ifdef NT_UP // These global variables are useful only for uni-processor systems // as they are per-processor values on MP systems. .globl KiPcrBaseAddress KiPcrBaseAddress: .long 0 : 1 .globl KiCurrentThread KiCurrentThread: .long 0 : 1 #endif //NT_UP SBTTL( "System Startup" ) // Routine Description: // This routine represents the final stage of the loader. It is // responsible for installing the loaded PALcode image and transfering // control to the startup code in the kernel. // KiSystemStartupContinue is the routine called when NT begins execution. // The first code that must be executed is the PALcode, it must be entered // in PAL mode. The PALcode will return to the address in the return // address register (ra). This function sets ra to the beginning of the // native system code that normally executes to setup the NT operating // environment - so that the PAL "returns" to the normal system start code. // N.B. This code assumes that the I-cache is coherent. // N.B. This routine does not execute in the context of the operating // system but instead executes in the context of the firmware // PAL environment. This routine can only use those services // guaranteed to exist in the firmware. The only PAL services // that can be counted on are: swppal, imb, and halt. // Arguments: // LoaderBlock (a0) - Supplies pointer to Loader Parameter Block. // Return Value: // None. .struct 0 SsRa: .space 8 // Save ra .space 8 // for stack alignment SsFrameLength: NESTED_ENTRY(KiSystemStartup, SsFrameLength, ra) ALTERNATE_ENTRY( KiStartProcessor ) lda sp, -SsFrameLength(sp) // allocate stack frame stq ra, SsRa(sp) // save ra PROLOGUE_END // // Prepare arguments for SWPPAL and Kernel. This assumes that // the SWPPAL does not destroy any of the argument registers. // // a0 = Physical base address of PAL. // a1 = PCR page frame number. // a2 = Pointer to loader paramter block. // ra = Address to return to from pal. // Equals kernel start address. // bis a0, zero, a2 // copy Loader Block to a2 ldl a1, LpbPcrPage(a2) // get pcr page number ldl a0, LpbPalBaseAddress(a2) // get PAL base address sll a0, 32+3, a0 // strip off top bits srl a0, 32+3, a0 // clear upper lw and kseg bits lda ra, KiSystemStartupContinue // store OS start address in ra // // Jump to PAL via SWPPAL. Then return to continuation address in OS. // // a0 = new PAL base address // ra = continuation address SWPPAL // swap PAL images // // We should never get here! // ldq ra, SsRa(sp) // Restore ra lda sp, SsFrameLength(sp) // Restore stack pointer ret zero, (ra) // shouldn't get here .end KiSystemStartup SBTTL( "System Startup Continue" ) // Routine Description: // KiSystemStartupContinue is the routine called when NT begins execution // after loading the Kernel environment from the PAL. // It's function is to register exception routines and system values // with the pal code, call kernel initialization and fall into the idle // thread code // Arguments: // PalBaseAddress(a0) - Supplies base address of the operating system // PALcode. // PcrPage(a1) - Supplies the PFN of the PCR page. // LoaderBlock(a2) - Supplies a pointer to the loader parameter block. // Return Value: // None. .struct 0 SscRa: .space 8 // return address Fill: .space 8 // filler for alignment SscFrameLength: // size of stack frame NESTED_ENTRY( KiSystemStartupContinue, SscFrameLength, ra ) lda sp, -SscFrameLength(sp) // allocate stack frame stq ra, SscRa(sp) // save ra PROLOGUE_END // Establish kernel stack pointer and kernel global pointer from // parameter block. ldl sp, LpbKernelStack(a2) // establish kernel sp ldl gp, LpbGpBase(a2) // establish kernel gp // Initialize PAL values, sp, gp, pcr, pdr, initial thread bis a2, zero, s2 // save pointer to loader block ldl s0, LpbPcrPage(s2) // get pcr page number ldl a0, LpbPdrPage(s2) // get pdr page number ldl a1, LpbThread(s2) // get idle thread address ldil t0, KSEG0_BASE // kseg0 base address bis a0, zero, s3 // save copy of pdr page number sll a0, PAGE_SHIFT, a0 // physical address of pdr sll s0, PAGE_SHIFT, s0 // physical address of pcr bis a0, t0, a0 // kseg0 address of pdr bis s0, t0, s0 // kseg0 address of pcr bis a0, zero, s1 // save copy of pdr address bis zero, zero, a2 // zero Teb for initial thread ldl a3, LpbPanicStack(s2) // get Interrupt stack base ldil a4, TotalFrameLength // set maximum kernel stack size // sp - initial kernel sp // gp - system gp // a0 - pdr kseg0 address // a1 - thread kseg0 address // a2 - Teb address for initial thread // a3 - Interrupt stack base // a4 - Maximum kernel stack size INITIALIZE_PAL #ifdef NT_UP // Save copies of the per-processor values in global variables for // uni-processor systems. lda t0, KiPcrBaseAddress // get address of PCR address stl s0, 0(t0) // save PCR address ldl t1, LpbThread(s2) // get address of idle thread lda t0, KiCurrentThread // get address of thread address stl t1, 0(t0) // save idle address as thread #endif //NT_UP // Establish recursive mapping of pde for ptes and hyperspace // N.B. - page table page for hyperspace is page after pdr page ldil t0, PTE_BASE // get pte base sll t0, 32, t0 // clean upper bits srl t0, 32+PDI_SHIFT-2, t0 // get offset of pde bic t0, 3, t0 // longword aligned, clear low bits addq t0, s1, t0 // kseg0 addr of pde sll s3, PTE_PFN, t1 // shift pfn into place bis t1, PTE_VALID_MASK, t1 // set valid bit // bis t1, PTE_DIRTY_MASK, t1 // set dirty bit stl t1, 0(t0) // store pde for pdr ldil t2, (1 << PTE_PFN) // increment pfn by 1 addq t1, t2, t1 // stl t1, 4(t0) // store hyperspace pde // Establish mapping for special user data page. // N.B. - page table page for this is page after hyperspace page table page // actual data page is the next page. ldil t0, SharedUserData // get shared data base zap t0, 0xf0, t3 // clean upper bits srl t3, PDI_SHIFT-2, t0 // get offset of pde bic t0, 3, t0 // longword aligned, clear low bits addq t0, s1, t0 // kseg0 addr of pde addq t1, t2, t1 // increment pfn by 1 stl t1, 0(t0) // store user data page pde zap t3, 0xf8, t3 // clean upper bits srl t3, PTI_SHIFT-2, t3 // get offset of pte bic t3, 3, t3 // longword aligned, clear low bits addq t3, s1, t3 ldil t4, 2*PAGE_SIZE addq t3, t4, t3 // kseg0 addr of pte addq t1, t2, t1 // increment pfn by 1 stl t1, 0(t3) // Register kernel exception entry points with the PALcode lda a0, KiPanicException // bugcheck entry point ldil a1, entryBugCheck // WRITE_KERNEL_ENTRY_POINT // lda a0, KiGeneralException // general exception entry point ldil a1, entryGeneral // WRITE_KERNEL_ENTRY_POINT // lda a0, KiMemoryManagementException // memory mgmt exception entry ldil a1, entryMM // WRITE_KERNEL_ENTRY_POINT // lda a0, KiInterruptException // interrupt exception entry point ldil a1, entryInterrupt // WRITE_KERNEL_ENTRY_POINT // lda a0, KiSystemServiceException // syscall entry point ldil a1, entrySyscall // WRITE_KERNEL_ENTRY_POINT // // Initialize fields in the pcr ldil t1, PCR_MINOR_VERSION // get minor version ldil t2, PCR_MAJOR_VERSION // get major version stl t1, PcMinorVersion(s0) // store minor version number stl t2, PcMajorVersion(s0) // store major version number ldl t0, LpbThread(s2) // save idle thread in pcr stl t0, PcIdleThread(s0) // ldl t0, LpbPanicStack(s2) // save panic stack in pcr stl t0, PcPanicStack(s0) // ldl t0, LpbProcessorType(s2) // save processor type in pcr stl t0, PcProcessorType(s0) // ldl t0, LpbProcessorRevision(s2) // save processor revision stl t0, PcProcessorRevision(s0) // ldl t0, LpbPhysicalAddressBits(s2) // save physical address bits stl t0, PcPhysicalAddressBits(s0) // ldl t0, LpbMaximumAddressSpaceNumber(s2) // save max asn stl t0, PcMaximumAddressSpaceNumber(s0) // ldl t0, LpbFirstLevelDcacheSize(s2) // save first level dcache size stl t0, PcFirstLevelDcacheSize(s0) // ldl t0, LpbFirstLevelDcacheFillSize(s2) // save dcache fill size stl t0, PcFirstLevelDcacheFillSize(s0) // ldl t0, LpbFirstLevelIcacheSize(s2) // save first level icache size stl t0, PcFirstLevelIcacheSize(s0) // ldl t0, LpbFirstLevelIcacheFillSize(s2) // save icache fill size stl t0, PcFirstLevelIcacheFillSize(s0) // ldl t0, LpbSystemType(s2) // save system type stl t0, PcSystemType(s0) // ldl t0, LpbSystemType+4(s2) // stl t0, PcSystemType+4(s0) // ldl t0, LpbSystemVariant(s2) // save system variant stl t0, PcSystemVariant(s0) // ldl t0, LpbSystemRevision(s2) // save system revision stl t0, PcSystemRevision(s0) // ldl t0, LpbSystemSerialNumber(s2) // save system serial number stl t0, PcSystemSerialNumber(s0) // ldl t0, LpbSystemSerialNumber+4(s2) // stl t0, PcSystemSerialNumber+4(s0) // ldl t0, LpbSystemSerialNumber+8(s2) // stl t0, PcSystemSerialNumber+8(s0) // ldl t0, LpbSystemSerialNumber+12(s2) // stl t0, PcSystemSerialNumber+12(s0) // ldl t0, LpbCycleClockPeriod(s2) // save cycle counter period stl t0, PcCycleClockPeriod(s0) // ldl t0, LpbRestartBlock(s2) // save Restart Block address stl t0, PcRestartBlock(s0) // ldq t0, LpbFirmwareRestartAddress(s2) // save firmware restart stq t0, PcFirmwareRestartAddress(s0) // ldq t0, LpbFirmwareRevisionId(s2) // save firmware revision stq t0, PcFirmwareRevisionId(s0) // ldl t0, LpbDpcStack(s2) // save Dpc Stack stl t0, PcDpcStack(s0) // ldl t0, LpbPrcb(s2) // save Prcb stl t0, PcPrcb(s0) // stl zero, PbDpcRoutineActive(t0) // clear DPC Active flag stl zero, PcMachineCheckError(s0) // indicate no HAL mchk handler // Set system service dispatch address limits used by get and set context. lda t0, KiSystemServiceDispatchStart // set start address of range stl t0, PcSystemServiceDispatchStart(s0) // lda t0, KiSystemServiceDispatchEnd // set end address of range stl t0, PcSystemServiceDispatchEnd(s0) // // Setup arguments and call kernel initialization routine. ldl s0, LpbProcess(s2) // get idle process address ldl s1, LpbThread(s2) // get idle thread address bis s0, zero, a0 // a0 = idle process address bis s1, zero, a1 // a1 = idle thread address ldl a2, LpbKernelStack(s2) // a2 = idle thread stack ldl a3, LpbPrcb(s2) // a3 = processor block address LoadByte(a4, PbNumber(a3)) // a4 = processor number bis s2, zero, a5 // a5 = loader parameter block bsr ra, KiInitializeKernel // initialize system data // Control is returned to the idle thread with IRQL at HIGH_LEVEL. // Lower IRQL level to DISPATCH_LEVEL and set wait IREQL of idle thread. GET_PROCESSOR_CONTROL_BLOCK_BASE // get prcb bis v0, zero, s0 // s0 = prcb address lda s3, PbDpcListHead(s0) // get DPC listhead address #if !defined(NT_UP) lda s5, KiDispatcherLock // get address of dispatcher lock #endif ldil a0, DISPATCH_LEVEL // get dispatch level IRQL StoreByte( a0, ThWaitIrql(s1) ) // set wait IRQL of idle thread bsr ra, KeLowerIrql // lower IRQL ENABLE_INTERRUPTS // enable interrupts bis zero, zero, s2 // clear breakin loop counter bis zero, zero, ra // set bogus RA to stop debugger br zero, KiIdleLoop .end KiSystemStartupContinue // 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 initialization routine or as the result of starting up // another processor in a multiprocessor configuration. NESTED_ENTRY(KiIdleLoop, ExceptionFrameLength, zero) lda sp, -ExceptionFrameLength(sp) // allocate context frame stq ra, ExIntRa(sp) // set bogus RA to stop debugger stq s0, ExIntS0(sp) // save integer registers s0 - s5 stq s1, ExIntS1(sp) // stq s2, ExIntS2(sp) // stq s3, ExIntS3(sp) // #if !defined(NT_UP) stq s5, ExIntS5(sp) // #endif PROLOGUE_END lda t0, KiIdleReturn // set return address from SwapContext stq t0, ExSwapReturn(sp) // directly into exception frame bsr ra, KiSaveNonVolatileFloatState // restore registers we need after swap context KiIdleReturn: // Lower IRQL back to DISPATCH_LEVEL ldil a0, DISPATCH_LEVEL SWAP_IRQL #if DBG bis zero, zero, s2 // reset breakin loop counter #endif // N.B. The address of the current processor block (s0) is preserved across // the switch from idle call. ldq s3, ExIntS3(sp) // restore address of DPC listhead #if !defined(NT_UP) ldl t2, KeNumberProcessors // get number of processors stq t2, ExIntS0(sp) // store number of processors ldq s5, ExIntS5(sp) // restore address of dispatcher lock #endif IdleLoop: #if DBG subl s2, 1, s2 // decrement breakin loop counter bge s2, 5f // if ge, not time for breakin check ldil s2, 200 * 1000 // set breakin loop counter bsr ra, KdPollBreakIn // check if breakin is requested beq v0, 5f // if eq, then no breakin requested lda a0, DBG_STATUS_CONTROL_C bsr ra, DbgBreakPointWithStatus 5: #endif //DBG // Disable interrupts and check if there is any work in the DPC list // of the current processor or a target processor. CheckDpcList: ENABLE_INTERRUPTS // give interrupts a chance DISABLE_INTERRUPTS // to interrupt spinning // Process the deferred procedure call list for the current processor. ldl t0, PbDpcQueueDepth(s0) // get current queue depth beq t0, CheckNextThread // if eq, DPC list is empty // Clear dispatch interrupt. ldil a0, DISPATCH_LEVEL ldl t0, PbSoftwareInterrupts(s0) // clear any pending SW interrupts. bic t0, a0, t1 stl t1, PbSoftwareInterrupts(s0) DEASSERT_SOFTWARE_INTERRUPT // clear any PAL-requested interrupts. bis zero, zero, s2 // clear breakin loop counter bsr ra, KiRetireDpcList // Check if a thread has been selected to run on this processor. CheckNextThread: ldl a0, PbNextThread(s0) // get address of next thread object beq a0, IdleProcessor // 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. #if !defined(NT_UP) 130: ldl_l t0, 0(s5) // get current lock value bis s5, zero, t1 // set lock ownership value bne t0, CheckDpcList // if ne, spin lock owned, go try the DPC list again stl_c t1, 0(s5) // set spin lock owned beq t1, 135f // if eq, store conditional failed mb // synchronize subsequent reads after // the spinlock is acquired #endif // Raise IRQL to sync level and re-enable interrupts ldl a0, KiSynchIrql SWAP_IRQL ENABLE_INTERRUPTS ldl s2, PbNextThread(s0) // get address of next thread object ldl s1, PbIdleThread(s0) // get address of current thread stl zero, PbNextThread(s0) // clear next thread address stl s2, PbCurrentThread(s0) // set address of current thread object // Set new thread's state to running. Note this must be done // under the dispatcher lock so that KiSetPriorityThread sees // the correct state. ldil t0, Running StoreByte( t0, ThState(s2) ) #if !defined(NT_UP) // Acquire the context swap lock so the address space of the old thread // cannot be deleted and then release the dispatcher database lock. In // this case the old thread is the idle thread, 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. lda t0, KiContextSwapLock // get context swap lock address 140: ldl_l t1, 0(t0) // get current lock value bis t0, zero, t2 // set ownership value bne t1, 145f // if ne, lock already owned stl_c t2, 0(t0) // set lock ownership value beq t2, 145f // if eq, store conditional failed mb // synchronize reads and writes stl zero, 0(s5) // set lock not owned #endif bsr ra, SwapFromIdle // swap context to new thread // Note control returns directly from SwapFromIdle to the top // of the loop (KiIdleReturn) since SwapContext gets ra directly from ExSwapReturn(sp) // which was explicitly set when the idle loop was originally entered. IdleProcessor: // There are no entries in the DPC list and a thread has not been selected // for execution on this processor. Call the HAL so power management can // be performed. // N.B. The HAL is called with interrupts disabled. The HAL will return // with interrupts enabled. bsr ra, HalProcessorIdle // notify HAL of idle state br zero, IdleLoop // restart idle loop #if !defined(NT_UP) 135: // Conditional store of dispatcher lock failed. Retry. Do not // spin in cache here. If the lock is owned, we want to check // the DPC list again. ENABLE_INTERRUPTS DISABLE_INTERRUPTS br zero, 130b 145: ldl t1, 0(t0) // spin in cache until lock free beq t1, 140b // retry spin lock br zero, 145b #endif .end KiSwapThread 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 and the DPC list lock held on entry // to this routine. Control is returned to the caller with the same // conditions true. // Arguments: // s0 - Address of the processor control block. // Return value: // None. .struct 0 DpRa: .space 8 // return address .space 8 // fill #if DBG DpStart:.space 8 // DPC start time in ticks DpFunct:.space 8 // DPC function address DpCount:.space 8 // interrupt count at start of DPC DpTime: .space 8 // interrupt time at start of DPC #endif DpcFrameLength: // DPC frame length NESTED_ENTRY(KiRetireDpcList, DpcFrameLength, zero) lda sp, -DpcFrameLength(sp) // allocate stack frame stq ra, DpRa(sp) // save return address PROLOGUE_END 5: stl sp, PbDpcRoutineActive(s0) // set DPC routine active // Process the DPC list. 10: ldl t0, PbDpcQueueDepth(s0) // get current DPC queue depth beq t0, 60f // if eq, list is empty lda t2, PbDpcListHead(s0) // compute DPC list head address 20: #if !defined(NT_UP) ldl_l t1, PbDpcLock(s0) // get current lock value bis s0, zero, t3 // set lock ownership value bne t1, 25f // if ne, spin lock owned stl_c t3, PbDpcLock(s0) // set spin lock owned beq t3, 25f // if eq, store conditional failed mb ldl t0, PbDpcQueueDepth(s0) // get current DPC queue depth beq t0, 50f // if eq, DPC list is empty #endif ldl a0, LsFlink(t2) // get address of next entry ldl t1, LsFlink(a0) // get address of next entry lda a0, -DpDpcListEntry(a0) // compute address of DPC object stl t1, LsFlink(t2) // set address of next in header stl t2, LsBlink(t1) // set address of previous in next ldl a1, DpDeferredContext(a0) // get deferred context argument ldl a2, DpSystemArgument1(a0) // get first system argument ldl a3, DpSystemArgument2(a0) // get second system argument ldl t1, DpDeferredRoutine(a0) // get deferred routine address stl zero, DpLock(a0) // clear DPC inserted state subl t0, 1, t0 // decrement DPC queue depth stl t0, PbDpcQueueDepth(s0) // update DPC queue depth #if !defined(NT_UP) mb // synchronize previous writes stl zero, PbDpcLock(s0) // set spinlock not owned #endif ENABLE_INTERRUPTS // enable interrupts jsr ra, (t1) DISABLE_INTERRUPTS br zero, 10b // Unlock DPC list and clear DPC active. 50: #if !defined(NT_UP) mb // synchronize previous writes stl zero, PbDpcLock(s0) // set spin lock not owned #endif 60: stl zero, PbDpcRoutineActive(s0) // clear DPC routine active stl 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. #if !defined(NT_UP) mb #endif ldl t0, PbDpcQueueDepth(s0) // get current DPC queue depth beq t0, 70f // if eq, DPC list is still empty stl sp, PbDpcRoutineActive(s0) // set DPC routine active lda t2, PbDpcListHead(s0) // compute DPC list head address br zero, 20b 70: ldq ra, DpRa(sp) // restore RA lda sp, DpcFrameLength(sp) // deallocate stack frame ret zero, (ra) // return #if !defined(NT_UP) 25: ldl t1, PbDpcLock(s0) // spin in cache until lock free beq t1, 20b // retry spinlock br zero, 25b #endif .end KiRetireDpcList