You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
553 lines
19 KiB
553 lines
19 KiB
title "Context Swap"
|
|
;++
|
|
;
|
|
; Copyright (c) 2000 Microsoft Corporation
|
|
;
|
|
; Module Name:
|
|
;
|
|
; ctxswap.asm
|
|
;
|
|
; Abstract:
|
|
;
|
|
; This module implements the code necessary to field the dispatch interrupt
|
|
; and perform context switching.
|
|
;
|
|
; Author:
|
|
;
|
|
; David N. Cutler (davec) 26-Aug-2000
|
|
;
|
|
; Environment:
|
|
;
|
|
; Kernel mode only.
|
|
;
|
|
;--
|
|
|
|
include ksamd64.inc
|
|
|
|
extern KeBugCheckEx:proc
|
|
extern KiDeliverApc:proc
|
|
extern KeRaiseIrqlToSynchLevel:proc
|
|
extern KiQuantumEnd:proc
|
|
extern KiQueueReadyThread:proc
|
|
extern KiRetireDpcList:proc
|
|
extern WmiTraceContextSwap:proc
|
|
extern __imp_HalRequestSoftwareInterrupt:qword
|
|
|
|
subttl "Swap Context"
|
|
;++
|
|
;
|
|
; BOOLEAN
|
|
; KiSwapContext (
|
|
; IN PKTHREAD OldThread,
|
|
; IN PKTHREAD NewThread
|
|
; )
|
|
;
|
|
; Routine Description:
|
|
;
|
|
; This function is a small wrapper that marshalls arguments and calls the
|
|
; actual swap context routine.
|
|
;
|
|
; N.B. The old thread lock has been acquired and the dispatcher lock dropped
|
|
; before this routine is called.
|
|
;
|
|
; N.B. The current thread address and the new thread state has been set
|
|
; before this routine is called.
|
|
;
|
|
; Arguments:
|
|
;
|
|
; OldThread (rcx) - Supplies the address of the old thread.
|
|
;
|
|
; NewThread (rdx) - Supplies the address of the old thread.
|
|
;
|
|
; Return Value:
|
|
;
|
|
; If a kernel APC is pending, then a value of TRUE is returned. Otherwise,
|
|
; a value of FALSE is returned.
|
|
;
|
|
;--
|
|
|
|
NESTED_ENTRY KiSwapContext, _TEXT$00
|
|
|
|
GENERATE_EXCEPTION_FRAME ; generate exception frame
|
|
|
|
mov rbx, gs:[PcCurrentPrcb] ; get current PRCB address
|
|
mov rdi, rcx ; set old thread address
|
|
mov rsi, rdx ; set new thread address
|
|
mov cl, ThWaitIrql[rdi] ; set APC interrupt bypass disable
|
|
call SwapContext ; swap context
|
|
|
|
RESTORE_EXCEPTION_STATE ; restore exception state/deallocate
|
|
|
|
ret ; return
|
|
|
|
NESTED_END KiSwapContext, _TEXT$00
|
|
|
|
subttl "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 DPC list, and then
|
|
; perform a context switch if a new thread has been selected for execution
|
|
; on the current processor.
|
|
;
|
|
; This routine is entered at DISPATCH_LEVEL with the dispatcher database
|
|
; unlocked.
|
|
;
|
|
; Arguments:
|
|
;
|
|
; None
|
|
;
|
|
; Return Value:
|
|
;
|
|
; None.
|
|
;
|
|
;--
|
|
|
|
DiFrame struct
|
|
P1Home dq ? ; PRCB address parameter
|
|
Fill dq ? ; fill to 8 mod 16
|
|
SavedRbx dq ? ; saved RBX
|
|
DiFrame ends
|
|
|
|
NESTED_ENTRY KiDispatchInterrupt, _TEXT$00
|
|
|
|
push_reg rbx ; save nonvolatile register
|
|
alloc_stack (sizeof DiFrame - 8) ; allocate stack frame
|
|
|
|
END_PROLOGUE
|
|
|
|
mov rbx, gs:[PcCurrentPrcb] ; get current PRCB address
|
|
and byte ptr PbDpcInterruptRequested[rbx], 0 ; clear request
|
|
|
|
;
|
|
; Check if the DPC queue has any entries to process.
|
|
;
|
|
|
|
KiDI10: cli ; disable interrupts
|
|
mov eax, PbDpcQueueDepth[rbx] ; get DPC queue depth
|
|
or rax, PbTimerRequest[rbx] ; merge timer request value
|
|
|
|
ifndef NT_UP
|
|
|
|
or rax, PbDeferredReadyListHead[rbx] ; merge deferred ready list
|
|
|
|
endif
|
|
|
|
jz short KiDI20 ; if z, no DPCs to process
|
|
mov PbSavedRsp[rbx], rsp ; save current stack pointer
|
|
mov rsp, PbDpcStack[rbx] ; set DPC stack pointer
|
|
mov rcx, rbx ; set PRCB address parameter
|
|
call KiRetireDpcList ; process the DPC list
|
|
mov rsp, PbSavedRsp[rbx] ; restore current stack pointer
|
|
|
|
;
|
|
; Check to determine if quantum end is requested.
|
|
;
|
|
|
|
KiDI20: sti ; enable interrupts
|
|
cmp byte ptr PbQuantumEnd[rbx], 0 ; check if quantum end request
|
|
je short KiDI40 ; if e, quantum end not requested
|
|
and byte ptr PbQuantumEnd[rbx], 0 ; clear quantum end indicator
|
|
call KiQuantumEnd ; process quantum end
|
|
|
|
;
|
|
; Restore nonvolatile registers, deallocate stack frame, and return.
|
|
;
|
|
|
|
KiDI30: add rsp, sizeof DiFrame - 8 ; deallocate stack frame
|
|
pop rbx ; restore nonvolatile register
|
|
ret ; return
|
|
|
|
;
|
|
; Check to determine if a new thread has been selected for execution on this
|
|
; processor.
|
|
;
|
|
|
|
KiDI40: cmp qword ptr PbNextThread[rbx], 0 ; check if new thread selected
|
|
je short KiDI30 ; if eq, then no new thread
|
|
|
|
;
|
|
; Swap context to a new thread as the result of new thread being scheduled
|
|
; by the dispatch interrupt.
|
|
;
|
|
|
|
add rsp, sizeof DiFrame - 8 ; deallocate stack frame
|
|
pop rbx ; restore nonvolatile register
|
|
jmp short KxDispatchInterrupt ;
|
|
|
|
NESTED_END KiDispatchInterrupt, _TEXT$00
|
|
|
|
;
|
|
; There is a new thread scheduled for execution and the dispatcher lock
|
|
; has been acquired. Context switch to the new thread immediately.
|
|
;
|
|
; N.B. The following routine is entered by falling through from the above
|
|
; routine.
|
|
;
|
|
; N.B. The following routine is carefully written as a nested function that
|
|
; appears to have been called directly by the caller of the above
|
|
; function which processes the dispatch interrupt.
|
|
;
|
|
; Arguments:
|
|
;
|
|
; None.
|
|
;
|
|
|
|
NESTED_ENTRY KxDispatchInterrupt, _TEXT$00
|
|
|
|
GENERATE_EXCEPTION_FRAME ; generate exception frame
|
|
|
|
mov rbx, gs:[PcCurrentPrcb] ; get current PRCB address
|
|
mov rdi, PbCurrentThread[rbx] ; get old thread address
|
|
|
|
;
|
|
; Raise IRQL to SYNCH level, set context swap busy for the old thread, and
|
|
; acquire the current PRCB lock.
|
|
;
|
|
|
|
ifndef NT_UP
|
|
|
|
call KeRaiseIrqlToSynchLevel ; raise IRQL to SYNCH Level
|
|
mov byte ptr ThSwapbusy[rdi], 1 ; set context swap busy
|
|
lea r11, PbPrcbLock[rbx] ; set address of current PRCB
|
|
|
|
AcquireSpinLock r11 ; acquire current PRCB lock
|
|
|
|
endif
|
|
|
|
;
|
|
; Get the next thread address, set the thread state to running, queue the old
|
|
; running thread, and swap context to the next thread.
|
|
;
|
|
|
|
mov rsi, PbNextThread[rbx] ; get next thread address
|
|
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
|
|
mov byte ptr ThWaitReason[rdi], WrDispatchInt ; set wait reason
|
|
mov rcx, rdi ; set address of old thread
|
|
mov rdx, rbx ; set address of current PRCB
|
|
call KiQueueReadyThread ; queue ready thread for execution
|
|
mov cl, APC_LEVEL ; set APC interrupt bypass disable
|
|
call SwapContext ; call context swap routine
|
|
|
|
RESTORE_EXCEPTION_STATE ; restore exception state/deallocate
|
|
|
|
ret ; return
|
|
|
|
NESTED_END KxDispatchInterrupt, _TEXT$00
|
|
|
|
subttl "Swap Context"
|
|
;++
|
|
;
|
|
; Routine Description:
|
|
;
|
|
; This routine is called to swap context from one thread to the next. It
|
|
; swaps context, flushes the translation buffer, swaps the process address
|
|
; space if necessary, and returns to its caller.
|
|
;
|
|
; N.B. This routine is only called by code within this module and the idle
|
|
; thread code and uses special register calling conventions.
|
|
;
|
|
; Arguments:
|
|
;
|
|
; al - Supplies a boolean value that determines whether the full legacy
|
|
; floating state needs to be saved.
|
|
;
|
|
; cl - Supplies the APC interrupt bypass disable IRQL value.
|
|
;
|
|
; rbx - Supplies the address of the current PRCB.
|
|
;
|
|
; rdi - Supplies the address of previous thread.
|
|
;
|
|
; rsi - Supplies the address of next thread.
|
|
;
|
|
; Return value:
|
|
;
|
|
; al - Supplies the kernel APC pending flag.
|
|
;
|
|
; rbx - Supplies the address of the current PRCB.
|
|
;
|
|
; rsi - Supplies the address of current thread.
|
|
;
|
|
;--
|
|
|
|
NESTED_ENTRY SwapContext, _TEXT$00
|
|
|
|
push_reg rbp ; save nonvolatile register
|
|
alloc_stack (KSWITCH_FRAME_LENGTH - (2 * 8)) ; allocate stack frame
|
|
|
|
END_PROLOGUE
|
|
|
|
mov SwApcBypass[rsp], cl ; save APC bypass disable
|
|
|
|
;
|
|
; Wait for new thread lock to be dropped.
|
|
;
|
|
; N.B. It is necessary to wait for the new thread context to be swapped so
|
|
; that any context switch away from the new thread on another processor
|
|
; is completed before attempting to swap context context to the thread.
|
|
;
|
|
|
|
ifndef NT_UP
|
|
|
|
KiSC00: cmp byte ptr ThSwapBusy[rsi], 0 ; check if swap busy for new thread
|
|
jne short KiSC00 ; if ne, context busy for new thread
|
|
|
|
endif
|
|
|
|
;
|
|
; Increment the number of context switches on this processor.
|
|
;
|
|
; N.B. This increment is done here is force the cache block containing the
|
|
; context switch count into the cache as write exclusive. There are
|
|
; several other references to this cache block in the following code.
|
|
;
|
|
|
|
inc dword ptr (PcContextSwitches - PcPrcb)[rbx] ; processor count
|
|
|
|
;
|
|
; Accumulate the total time spent in a thread.
|
|
;
|
|
|
|
ifdef PERF_DATA
|
|
|
|
rdtsc ; read cycle counter
|
|
sub eax, PbThreadStartCount + 0[rbx] ; sub out thread start time
|
|
sbb edx, PbThreadStartCount + 4[rbx] ;
|
|
add EtPerformanceCountLow[rdi], eax ; accumlate thread run time
|
|
adc EtPerformanceCountHigh[rdi], edx ;
|
|
add PbThreadStartCount + 4[rbx], eax ; set new thread start time
|
|
adc PbThreadStartCount + 8[rbx], edx ;
|
|
|
|
endif
|
|
|
|
;
|
|
; Check for context swap logging.
|
|
;
|
|
|
|
cmp qword ptr (PcPerfGlobalGroupMask - PcPrcb)[rbx], 0 ; check if logging enable
|
|
je short KiSC05 ; if eq, logging not enabled
|
|
mov rax, (PcPerfGlobalGroupMask - PcPrcb)[rbx] ; get global mask address
|
|
mov rdx, rdi ; set address of old thread
|
|
mov rcx, rsi ; set address of new thread
|
|
test dword ptr PERF_CONTEXTSWAP_OFFSET[rax], PERF_CONTEXTSWAP_FLAG ; check flag
|
|
jz short KiSC05 ; if z, context swap events not enabled
|
|
call WmiTraceContextSwap ; call trace routine
|
|
|
|
;
|
|
; Save the kernel mode XMM control/status register. If the current thread
|
|
; NPX state is switch, then save the legacy floating point state.
|
|
;
|
|
|
|
KiSC05: stmxcsr SwMxCsr[rsp] ; save kernel mode XMM control/status
|
|
cmp byte ptr ThNpxState[rdi], LEGACY_STATE_SWITCH ; check if switched
|
|
jne short KiSC10 ; if ne, legacy state not switched
|
|
mov rbp, ThInitialStack[rdi] ; get previous thread initial stack
|
|
fnsaved [rbp] ; save full legacy floating point state
|
|
|
|
;
|
|
; Switch kernel stacks.
|
|
;
|
|
|
|
KiSC10: mov ThKernelStack[rdi], rsp ; save old kernel stack pointer
|
|
mov rsp, ThKernelStack[rsi] ; get new kernel stack pointer
|
|
|
|
;
|
|
; Swap the process address space if the new process is not the same as the
|
|
; previous process.
|
|
;
|
|
|
|
mov r14, ThApcState + AsProcess[rsi] ; get new process address
|
|
cmp r14, ThApcState + AsProcess[rdi] ; check if process match
|
|
je short KiSC20 ; if e, process addresses match
|
|
|
|
;
|
|
; Clear the processor bit in the old process.
|
|
;
|
|
|
|
ifndef NT_UP
|
|
|
|
mov rax, ThApcState + AsProcess[rdi] ; get old process address
|
|
mov rcx, (PcSetMember - PcPrcb)[rbx] ; get processor set member
|
|
lock xor PrActiveProcessors[rax], rcx ; clear bit in previous set
|
|
|
|
if DBG
|
|
|
|
test PrActiveProcessors[rax], rcx ; test if bit clear in previous set
|
|
jz short @f ; if z, bit clear in previous set
|
|
int 3 ; debug break - incorrect active mask
|
|
@@: ; reference label
|
|
|
|
endif
|
|
|
|
endif
|
|
|
|
|
|
;
|
|
; Set the processor bit in the new process.
|
|
;
|
|
|
|
ifndef NT_UP
|
|
|
|
lock xor PrActiveProcessors[r14], rcx ; set bit in new set
|
|
|
|
if DBG
|
|
|
|
test PrActiveProcessors[r14], rcx ; test if bit set in new set
|
|
jnz short @f ; if nz, bit set in new set
|
|
int 3 ; debug break - incorrect active mask
|
|
@@: ; reference label
|
|
|
|
endif
|
|
|
|
endif
|
|
|
|
;
|
|
; Load new CR3 value which will flush the TB.
|
|
;
|
|
|
|
mov rax, PrDirectoryTableBase[r14] ; get new directory base
|
|
mov cr3, rax ; flush TLB and set new directory base
|
|
|
|
;
|
|
; Set context swap idle for the old thread lock.
|
|
;
|
|
|
|
KiSc20: ;
|
|
|
|
ifndef NT_UP
|
|
|
|
and byte ptr ThSwapBusy[rdi], 0 ; set context swap idle
|
|
|
|
endif
|
|
|
|
;
|
|
; Set the new kernel stack base in the TSS.
|
|
;
|
|
|
|
mov r15, (PcTss - PcPrcb)[rbx] ; get processor TSS address
|
|
mov rbp, ThInitialStack[rsi] ; get new stack base address
|
|
mov TssRsp0[r15], rbp ; set stack base address in TSS
|
|
|
|
;
|
|
; If the new thread executes in user mode, then restore the legacy floating
|
|
; state, load the compatibility mode TEB address, load the native user mode
|
|
; TEB address, and reload the segment registers if needed.
|
|
;
|
|
; N.B. The upper 32-bits of the compatibility mode TEB address are always
|
|
; zero.
|
|
;
|
|
|
|
cmp byte ptr ThNpxState[rsi], LEGACY_STATE_UNUSED ; check if kernel thread
|
|
je KiSC30 ; if e, kernel thread
|
|
mov cx, LfControlWord[rbp] ; save current control word
|
|
mov word ptr LfControlWord[rbp], 03fh ; set to mask all exceptions
|
|
frstord [rbp] ; restore legacy floating point state
|
|
mov LfControlWord[rbp], cx ; restore control word
|
|
fldcw word ptr LfControlWord[rbp] ; load legacy control word
|
|
|
|
;
|
|
; Set base of compatibility mode TEB.
|
|
;
|
|
|
|
mov eax, ThTeb[rsi] ; compute compatibility mode TEB address
|
|
add eax, CmThreadEnvironmentBlockOffset ;
|
|
mov rcx, (PcGdt - PcPrcb)[rbx] ; get GDT base address
|
|
mov KgdtBaseLow + KGDT64_R3_CMTEB[rcx], ax ; set CMTEB base address
|
|
shr eax, 16 ;
|
|
mov KgdtBaseMiddle + KGDT64_R3_CMTEB[rcx], al ;
|
|
mov KgdtBaseHigh + KGDT64_R3_CMTEB[rcx], ah ;
|
|
|
|
;
|
|
; If the user segment selectors have been changed, then reload them with
|
|
; their cannonical values.
|
|
;
|
|
; N.B. The following code depends on the values defined in ntamd64.w that
|
|
; can be loaded in ds, es, fs, and gs. In particular an "and" operation
|
|
; is used for the below comparison.
|
|
;
|
|
|
|
mov ax, ds ; compute sum of segment selectors
|
|
mov cx, es ;
|
|
and ax, cx ;
|
|
mov cx, gs ;
|
|
and ax, cx ;
|
|
cmp ax, (KGDT64_R3_DATA or RPL_MASK) ; check if sum matches
|
|
je short KiSC25 ; if e, sum matches expected value
|
|
mov cx, KGDT64_R3_DATA or RPL_MASK ; reload user segment selectors
|
|
mov ds, cx ;
|
|
mov es, cx ;
|
|
|
|
;
|
|
; N.B. The following reload of the GS selector destroys the system MSR_GS_BASE
|
|
; register. Thus this sequence must be done with interrupt off.
|
|
;
|
|
|
|
mov eax, (PcSelf - PcPrcb)[rbx] ; get current PCR address
|
|
mov edx, (PcSelf - PcPrcb + 4)[rbx] ;
|
|
cli ; disable interrupts
|
|
mov gs, cx ; reload GS segment selector
|
|
mov ecx, MSR_GS_BASE ; get GS base MSR number
|
|
wrmsr ; write system PCR base address
|
|
sti ; enable interrupts
|
|
KiSC25: mov ax, KGDT64_R3_CMTEB or RPL_MASK ; reload FS segment selector
|
|
mov fs, ax ;
|
|
mov eax, ThTeb[rsi] ; get low part of user TEB address
|
|
mov edx, ThTeb + 4[rsi] ; get high part of user TEB address
|
|
mov (PcTeb - PcPrcb)[rbx], eax ; set user TEB address in PCR
|
|
mov (PcTeb - PcPrcb + 4)[rbx], edx ;
|
|
mov ecx, MSR_GS_SWAP ; get GS base swap MSR number
|
|
wrmsr ; write user TEB base address
|
|
|
|
;
|
|
; Restore kernel mode XMM control/status and update context switch counters.
|
|
;
|
|
|
|
KiSC30: ldmxcsr SwMxCsr[rsp] ; kernel mode XMM control/status
|
|
inc dword ptr ThContextSwitches[rsi] ; thread count
|
|
|
|
;
|
|
; Check if an attempt is being made to context switch while in a DPC routine.
|
|
;
|
|
|
|
cmp word ptr PbDpcRoutineActive[rbx], 0 ; check if DPC active
|
|
jne short KiSC50 ; if ne, DPC is active
|
|
|
|
;
|
|
; If the new thread has a kernel mode APC pending, then request an APC
|
|
; interrupt if APC bypass is disabled.
|
|
;
|
|
|
|
cmp byte ptr ThApcState + AsKernelApcPending[rsi], TRUE ; check if APC pending
|
|
jne short KiSC40 ; if ne, kernel APC not pending
|
|
cmp word ptr ThSpecialApcDisable[rsi], 0 ; check if special APC disable
|
|
jne short KiSC40 ; if ne, special APC disable
|
|
cmp byte ptr SwApcBypass[rsp], PASSIVE_LEVEL ; check if APC bypass enabled
|
|
je short KiSC40 ; if e, APC bypass enabled
|
|
mov cl, APC_LEVEL ; request APC interrupt
|
|
call __imp_HalRequestSoftwareInterrupt ;
|
|
or rax, rsp ; clear ZF flag
|
|
KiSC40: setz al ; set return value
|
|
add rsp, KSWITCH_FRAME_LENGTH - (2 * 8) ; deallocate stack frame
|
|
pop rbp ; restore nonvolatile register
|
|
ret ; return
|
|
|
|
;
|
|
; An attempt is being made to context switch while in a DPC routine. This is
|
|
; most likely caused by a DPC routine calling one of the wait functions.
|
|
;
|
|
|
|
KiSC50: xor r9, r9 ; clear register
|
|
mov SwP5Home[rsp], r9 ; set parameter 5
|
|
mov r8, rsi ; set new thread address
|
|
mov rdx, rdi ; set old thread address
|
|
mov ecx, ATTEMPTED_SWITCH_FROM_DPC ; set bug check code
|
|
call KeBugCheckEx ; bug check system - no return
|
|
ret ; return
|
|
|
|
NESTED_END SwapContext, _TEXT$00
|
|
|
|
end
|