windows-nt/Source/XPSP1/NT/base/ntos/ke/amd64/idle.asm
2020-09-26 16:20:57 +08:00

242 lines
7.8 KiB
NASM

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 KiCheckBreakInRequest:proc
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
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 KiCheckBreakInRequest ; 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 eax, PbTimerHand[rbx] ; merge timer hand value
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 processor block 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
je short KiIL10 ; if e, no thread selected
;
; A thread has been selected for execution on this processor. Acquire the
; context swap lock, get the thread address again (it may have changed),
; and test whether a swap from idle is blocked for the specified thread.
; If swap from idle is blocked, then release the context swap lock and loop.
; Otherwise, clear the address of the next thread in the processor block
; and call swap context to start execution of the selected thread.
;
sti ; enable interrupts
ifndef NT_UP
mov ecx, LockQueueContextSwapLock ; set queued spin lock number
call KeAcquireQueuedSpinLockRaiseToSynch ; acquire queued spin lock
endif
mov rsi, PbNextThread[rbx] ; set next thread address
mov rdi, PbCurrentThread[rbx] ; get current thread address
ifndef NT_UP
cmp byte ptr ThIdleSwapBlock[rsi], 0 ; check if swap from idle blocked
jne short KiIL40 ; if ne, swap from idle blocked
cmp rsi, rdi ; check if swap from idle to idle
je short KiIL60 ; if eq, idle to idle
endif
mov qword ptr PbNextThread[rbx], 0 ; clear next thread address
mov PbCurrentThread[rbx], rsi ; set current thread address
mov cl, APC_LEVEL ; set APC interrupt bypass disable
ifndef NT_UP
mov edx, 1 ; set swap from idle true
endif
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
;
; Swap from idle is blocked while the specified thread clears the context
; code. Release the context swap lock and try again.
;
ifndef NT_UP
KiIL40: mov ecx, LockQueueContextSwapLock ; set queued spin lock number
mov dl, DISPATCH_LEVEL ; set previous IRQL to dispatch level
call KeReleaseQueuedSpinLock ; release context swap lock
jmp KiIL20 ; loop
;
; Under rare conditions, a thread can have been scheduled on this processor
; and subsequently made inelligible to run via a call to set affinity. If no
; other thread was available to run at the time of the call to set affinity,
; then the idle thread will have been rescheduled and this processor marked
; idle. If a new thread becomes available to run on this processor, then the
; net thread field in the prcoessr block may be unconditionally written.
;
; Protect clearing the next thread field by obtaining the dispatcher lock. If
; the next thread filed is no longer the idle thread, then a new thread has
; been scheduled for this processor and the next thread field must not be
; cleared.
;
KiIL60: lea rcx, PbLockQueue + (16 * LockQueueContextSwapLock)[rbx] ; release
call KeReleaseQueuedSpinLockFromDpcLevel ; the context swap lock
lea rcx, PbLockQueue + (16 * LockQueueDispatcherLock)[rbx] ; acquire
call KeAcquireQueuedSpinLockAtDpcLevel ; the dispatcher lock
cmp rsi, PbNextThread[rbx] ; check if the target thread still idle
jne short KiIL65 ; if ne, not idle thread
mov qword ptr PbNextThread[rbx], 0 ; clear next thread address
KiIL65: mov ecx, LockQueueDispatcherLock ; set lock queue number
mov dl, DISPATCH_LEVEL ; set previous IRQL
call KeReleaseQueuedSpinLock ; release dispatcher lock
jmp KiIL20 ; loop
endif
NESTED_END KiIdleLoop, _TEXT$00
end