Files
nt-lite/code/kernel/ke_switch.asm
T

141 lines
4.7 KiB
NASM

; ke_switch.asm - context switch and thread entry stubs (MASM64 / ML64)
;
; Struct offsets:
; sizeof(OBJECT) = 16 (type:4, refcount:4, destroy:8)
; THREAD.SavedContext offset = 88
; THREAD.TssRsp0 offset = 104
; THREAD.Teb offset = 112
; THREAD.FpuContext offset = 168
; TSS64.RSP0 offset = 4
;
THREAD_SavedContext EQU 88
THREAD_TssRsp0 EQU 104
THREAD_Teb EQU 112
THREAD_FpuContext EQU 168
TSS_RSP0_OFFSET EQU 4
EXTERN gTss:QWORD ; address of the single TSS, exported from ke_idt.c
.CODE
; -------------------------------------------------------------------------
; void KeSwitchContext(THREAD* from, THREAD* to)
; RCX = from, RDX = to (Microsoft x64)
; -------------------------------------------------------------------------
KeSwitchContext PROC
mov rax, [rcx + THREAD_FpuContext]
fxsave64 [rax]
; Save non-volatile registers onto the current (outgoing) kernel stack.
push rbx
push rbp
push r12
push r13
push r14
push r15
; from->SavedContext = rsp
mov [rcx + THREAD_SavedContext], rsp
; rsp = to->SavedContext
mov rsp, [rdx + THREAD_SavedContext]
; Update MSR_GS_BASE (0xC0000101) with to->Teb
mov rax, [rdx + THREAD_Teb] ; Fetch the incoming TEB pointer
mov ecx, 0C0000101h ; IA32_GS_BASE MSR
mov r8, rdx ; Save RDX ('to' thread) as WRMSR clobbers EDX
mov rdx, rax ; Copy TEB pointer to RDX
shr rdx, 32 ; EDX = High 32 bits of TEB
; EAX = Low 32 bits of TEB (upper 32 bits ignored by CPU)
wrmsr ; Write to MSR
mov rdx, r8 ; Restore RDX
; Update TSS.RSP0 so that the next interrupt/syscall uses the
; incoming thread's kernel stack top.
mov rax, [rdx + THREAD_TssRsp0]
mov r8, gTss
mov qword ptr [r8 + TSS_RSP0_OFFSET], rax
mov rax, [rdx + THREAD_FpuContext]
fxrstor64 [rax]
; Restore non-volatile registers of the incoming thread.
pop r15
pop r14
pop r13
pop r12
pop rbp
pop rbx
ret
KeSwitchContext ENDP
; -------------------------------------------------------------------------
; KeThreadEntry - landing pad for brand-new threads.
;
; Stack layout when we arrive here (set up by KeCreateThread):
; [rsp+00] = KCONTEXT (already popped by KeSwitchContext ret)
; After ret from KeSwitchContext RSP points here:
; [rsp+00] = user RCX arg
; [rsp+08] = user RIP
; [rsp+16] = user CS (0x1B)
; [rsp+24] = RFLAGS (0x202)
; [rsp+32] = user RSP
; [rsp+40] = user SS (0x23)
;
; We load RCX from the stack (first arg per MS x64), then IRETQ.
; -------------------------------------------------------------------------
EXTERN KeSchedulerPostSwitch : PROC
EXTERN KeGetCurrentThread : PROC
EXTERN KeExitThread : PROC
KeThreadEntry PROC
sub rsp, 32 ; Reserve shadow space
call KeSchedulerPostSwitch
add rsp, 32
pop rcx ; load user arg
iretq ; -> ring 3
KeThreadEntry ENDP
; -------------------------------------------------------------------------
; KeKernelThreadEntry - landing pad for kernel-mode threads created by
; KeCreateKernelThread. Never touches ring 3; no IRETQ.
;
; Stack layout when we arrive here (above the KCONTEXT already popped by
; KeSwitchContext's ret):
; [rsp+00] fn ptr (kernel function: void fn(void*))
; [rsp+08] arg ptr
;
; We pop both into non-volatile registers so KeSchedulerPostSwitch can
; clobber volatile ones freely, then call fn(arg). On return we call
; KeExitThread so the thread is cleanly destroyed.
; -------------------------------------------------------------------------
KeKernelThreadEntry PROC
pop rbx ; rbx = fn ptr (non-volatile, survives the call below)
pop r12 ; r12 = arg (non-volatile)
sub rsp, 32
call KeSchedulerPostSwitch ; release scheduler spinlock, reap dead threads
add rsp, 32
mov rcx, r12 ; first arg = arg
sub rsp, 32
call rbx ; fn(arg) -- still ring 0
add rsp, 32
; Thread function returned -- exit cleanly.
sub rsp, 32
call KeGetCurrentThread ; rax = current THREAD*
add rsp, 32
mov rcx, rax ; arg1 = thread*
xor edx, edx ; arg2 = exitCode 0
sub rsp, 32
call KeExitThread
add rsp, 32
; KeExitThread -> KeYield -> KeSwitchContext never returns here, but
; the assembler needs a defined end.
@@: jmp @B
KeKernelThreadEntry ENDP
END