title "Idle Loop" ;++ ; ; Copyright (c) 2000 Microsoft Corporation ; ; Module Name: ; ; idle.asm ; ; Abstract: ; ; This module implements the platform specifid idle loop. ; ; Author: ; ; David N. Cutler (davec) 21-Sep-2000 ; ; Environment: ; ; Kernel mode only. ; ;-- include ksamd64.inc extern KdDebuggerEnabled:byte extern KeAcquireQueuedSpinLockAtDpcLevel:proc extern KeAcquireQueuedSpinLockRaiseToSynch:proc extern KeReleaseQueuedSpinLock:proc extern KeReleaseQueuedSpinLockFromDpcLevel:proc extern KdCheckForDebugBreak:proc ifndef NT_UP extern KiIdleSchedule:proc endif extern KiIdleSummary:qword extern KiRetireDpcList:proc extern SwapContext:proc extern __imp_HalClearSoftwareInterrupt:qword subttl "Idle Loop" ;++ ; VOID ; KiIdleLoop ( ; VOID ; ) ; ; Routine Description: ; ; This routine continuously executes the idle loop and never returns. ; ; Arguments: ; ; None. ; ; Return value: ; ; This routine never returns. ; ;-- IlFrame struct P1Home dq ? ; P2Home dq ? ; P3Home dq ? ; P4Home dq ? ; Fill dq ? ; fill to 8 mod 16 IlFrame ends NESTED_ENTRY KiIdleLoop, _TEXT$00 alloc_stack (sizeof IlFrame) ; allocate stack frame END_PROLOGUE mov rbx, gs:[PcCurrentPrcb] ; get current processor block address xor edi, edi ; reset check breakin counter jmp short KiIL20 ; skip idle processor on first iteration ; ; 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 managment can be ; performed. ; ; N.B. The HAL is called with interrupts disabled. The HAL will return ; with interrupts enabled. ; KiIL10: lea rcx, PbPowerState[rbx] ; set address of power state call qword ptr PpIdleFunction[rcx] ; call idle function ; ; Give the debugger an opportunity to gain control if the kernel debuggger ; is enabled. ; ; N.B. On an MP system the lowest numbered idle processor is the only ; processor that checks for a breakin request. ; KiIL20: cmp KdDebuggerEnabled, 0 ; check if a debugger is enabled je short CheckDpcList ; if e, debugger not enabled ifndef NT_UP mov rax, KiIdleSummary ; get idle summary mov rcx, PbSetMember[rbx] ; get set member dec rcx ; compute right bit mask and rax, rcx ; check if any lower bits set jnz short CheckDpcList ; if nz, not lowest numbered endif dec edi ; decrement check breakin counter jg short CheckDpcList ; if g, not time to check for breakin call KdCheckForDebugBreak ; check if break in requested mov edi, 1000 ; set check breakin interval ; ; Disable interrupts and check if there is any work in the DPC list of the ; current processor or a target processor. ; ; N.B. The following code enables interrupts for a few cycles, then disables ; them again for the subsequent DPC and next thread checks. ; CheckDpcList: ; reference label sti ; enable interrupts nop ; nop ; cli ; disable interrupts ; ; Process the deferred procedure call list for the current processor. ; mov eax, PbDpcQueueDepth[rbx] ; get DPC queue depth or rax, PbTimerRequest[rbx] ; merge timer request value ifndef NT_UP or rax, PbDeferredReadyListHead[rbx] ; merge ready list head endif jz short CheckNextThread ; if z, no DPCs to process mov cl, DISPATCH_LEVEL ; set interrupt level call __imp_HalClearSoftwareInterrupt ; clear software interrupt mov rcx, rbx ; set current PRCB address call KiRetireDpcList ; process the current DPC list xor edi, edi ; clear check breakin interval ; ; Check if a thread has been selected to run on the current processor. ; CheckNextThread: ; cmp qword ptr PbNextThread[rbx], 0 ; check if thread slected ifdef NT_UP je short KiIL10 ; if e, no thread selected else je KiIL50 ; if e, no thread selected endif sti ; enable interrupts mov ecx, SYNCH_LEVEL ; set IRQL to synchronization level RaiseIrql ; ; ; set context swap busy for the idle thread and acquire the PRCB Lock. ; mov rdi, PbCurrentThread[rbx] ; get current thread address ifndef NT_UP mov byte ptr ThSwapBusy[rdi], 1 ; set ocntext swap busy lea r11, PbPrcbLock[rbx] ; set address of current PRCB lock AcquireSpinLock r11 ; acquire current PRCB Lock endif mov rsi, PbNextThread[rbx] ; set next thread address ; ; If a thread had been scheduled for this processor, but was removed from ; eligibility (e.g., an affinity change), then the new thread could be the ; idle thread. ; ifndef NT_UP cmp rsi, rdi ; check if swap from idle to idle je short KiIL40 ; if eq, idle to idle endif and qword ptr PbNextThread[rbx], 0 ; clear next thread address mov PbCurrentThread[rbx], rsi ; set current thread address mov byte ptr ThState[rsi], Running ; set new thread state ; ; Clear idle schedule since a new thread has been selected for execution on ; this processor and release the PRCB lock. ; ifndef NT_UP and byte ptr PbIdleSchedule[rbx], 0 ; clear idle schedule and qword ptr PbPrcbLock[rbx], 0 ; release current PRCB lock endif ; ; Switch context to new thread. ; KiIL30: mov cl, APC_LEVEL ; set APC bypass disable call SwapContext ; swap context to next thread ifndef NT_UP mov ecx, DISPATCH_LEVEL ; set IRQL to dispatch level SetIrql ; endif xor edi, edi ; clear check breakin interval jmp KiIL20 ; loop ; ; The new thread is the Idle thread (same as old thread). This can happen ; rarely when a thread scheduled for this processor is made unable to run ; on this processor. As this processor has again been marked idle, other ; processors may unconditionally assign new threads to this processor. ; ifndef NT_UP KiIL40: and qword ptr PbNextThread[rbx], 0 ; clear next thread and qword ptr PbPrcbLock[rbx], 0 ; release current PRCB lock and byte ptr ThSwapBusy[rdi], 0 ; set context swap idle jmp KiIL20 ; ; ; Call idle schedule if requested. ; KiIL50: cmp byte ptr PbIdleSchedule[rbx], 0 ; check if idle schedule je KiIL10 ; if e, idle schedule not requested sti ; enable interrupts mov rcx, rbx ; pass current PRCB address call KiIdleSchedule ; attempt to schedule thread test rax, rax ; test if new thread schedule mov rsi, rax ; set new thread address mov rdi, PbIdleThread[rbx] ; get idle thread address jnz short KiIL30 ; if nz, new thread scheduled jmp KiIL20 ; endif NESTED_END KiIdleLoop, _TEXT$00 end