/*++ Copyright (c) 2017 Minoca Corp. This file is licensed under the terms of the GNU General Public License version 3. Alternative licensing terms are available. Contact info@minocacorp.com for details. See the LICENSE file at the root of this project for complete licensing information. Module Name: ctxswap.S Abstract: This module implements context switching on AMD64. Author: Evan Green 11-Jun-2017 Environment: Kernel mode --*/ // // ------------------------------------------------------------------- Includes // #include // // ---------------------------------------------------------------- Definitions // // // ----------------------------------------------------------------------- Code // ASSEMBLY_FILE_HEADER // // VOID // KepContextSwap ( // PVOID *SavedStackLocation, // PVOID NewStack, // PVOID NewThreadPointer, // BOOL FirstTime // ) // /*++ Routine Description: This routine switches context to the given thread. Arguments: SavedStackLocation - Supplies a pointer where the old stack pointer will be saved. NewStack - Supplies the new stack address. NewThreadPointer - Supplies the new thread pointer data. FirstTime - Supplies a boolean indicating whether the thread has never been run before. Return Value: None. --*/ FUNCTION(KepContextSwap) pushq %rbp # Function prologue (also saves rbp). movq %rsp, %rbp # pushq %rbx # Save nonvolatile registers. pushq %r12 pushq %r13 pushq %r14 pushq %r15 pushfq subq $16, %rsp # Grab some extra stack space, aligned. movl $CONTEXT_SWAP_MAGIC, (%rsp) # Save a sentinal on the stack. // // Save the current thread's stack pointer. This effectively freezes the // current thread. When this thread is swapped back in, the stack pointer // will be restored, and execution of this function will continue. It's // crucial that the stack pointer not change between a normal context swap // and a first-time context swap, otherwise the thread that created the // context swap will have an incorrect stack next time it is swapped in. // movq %rsp, (%rdi) # Save stack pointer. // // Switch to the new stack and perform work than can only be done once off // of the old stack. Access the new stack before switching to it to trigger // any PML4 updates needed to see the new stack from old CR3. // movl (%rsi), %eax # Poke the new stack to trigger page faults. movq %rsi, %rsp # Switch to the new stack. xorq %rbp, %rbp # Zero out rbp so the call stack stops here. movq %rcx, %r12 # Move FirstTime parameter to non-volatile. // // Switch to the new thread pointer. The kernel doesn't use FS, so it's no // biggie. // movl $X86_MSR_FSBASE, %ecx # Set the MSR. movl %edx, %eax # Copy the low word into eax. shlq $32, %rdx # Shift rdx into its high word. wrmsr # Write the new FS base for user mode. // // Perform any post-stack switch work needed on the old thread. // call KepPostContextSwapWork # Perform post swap work. // // Determine whether to do a first-time return or a normal one. The only // difference is that a first-time execution has been set up to do an iret, // and a normal context swap doesn't need that because the stack is already // set up correctly. // cmpl $FALSE, %r12d # Compare FirstTime to FALSE. je KepContextSwapRestore # Special case a first run. KepContextSwapFirstTime: movq %rsp, %rdi # 1st argument is the trap frame pointer. call KepPreThreadStartWork # Do any thread setup needed. call ArRestoreTrapFrame # Set up CPU context. iretq # Return from the artificial exception. KepContextSwapRestore: movl (%rsp), %eax # Check the sentinal value. cmpl $CONTEXT_SWAP_MAGIC, %eax # Compare. jne KepContextSwapBadMagic # Jump out of line if it's bad. KepContextSwapReturn: addq $16, %rsp # Undo previous alignment adjustment. popfq # Restore registers. popq %r15 popq %r14 popq %r13 popq %r12 popq %rbx popq %rbp ret KepContextSwapBadMagic: int $0x3 # Break on bad context. jmp KepContextSwapReturn # If we break here, WATCH OUT. END_FUNCTION(KepContextSwap)