2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS kernel
4 * FILE: ntoskrnl/ke/i386/ctxswitch.S
5 * PURPOSE: Thread Context Switching
7 * PROGRAMMERS: Alex Ionescu (alex@relsoft.net)
8 * Gregor Anich (FPU Code)
11 /* INCLUDES ******************************************************************/
14 .intel_syntax noprefix
18 #define WrDispatchInt 0x1F
20 Dividend: .float 4195835.0
21 Divisor: .float 3145727.0
25 /* FUNCTIONS ****************************************************************/
27 .globl _KiIsNpxErrataPresent@0
28 .func KiIsNpxErrataPresent@0
29 _KiIsNpxErrataPresent@0:
31 /* Disable interrupts */
34 /* Get CR0 and mask out FPU flags */
37 and eax, ~(CR0_MP + CR0_TS + CR0_EM)
40 /* Initialize the FPU */
43 /* Do the divison and inverse multiplication */
44 fld qword ptr Dividend
45 fstp qword ptr Result1
47 fstp qword ptr Result2
49 fdiv qword ptr Result2
50 fmul qword ptr Result2
52 /* Do the compare and check flags */
53 fcomp qword ptr Result1
57 /* Restore CR0 and interrupts */
61 /* Return errata status */
70 .globl _KiIsNpxPresent@0
71 .func KiIsNpxPresent@0
77 /* Get CR0 and mask out FPU flags */
79 and eax, ~(CR0_MP + CR0_TS + CR0_EM + CR0_ET)
81 /* Initialize the FPU and assume FALSE for return */
85 /* Save magic value on stack */
89 /* Setup stack for FPU store */
93 /* Now check if our magic got cleared */
97 /* Enable FPU, set return to TRUE */
101 /* If this is a 486 or higher, enable INT 16 as well */
102 cmp dword ptr fs:KPCR_PRCB_CPU_TYPE, 3
107 /* Set emulation enabled during the first boot phase and set the CR0 */
108 or eax, (CR0_EM + CR0_TS)
115 /* Return true or false */
120 .globl _KiFlushNPXState@4
121 .func KiFlushNPXState@4
124 /* Save volatiles and disable interrupts */
131 /* Save the PCR and get the current thread */
132 mov edi, fs:[KPCR_SELF]
133 mov esi, [edi+KPCR_CURRENT_THREAD]
135 /* Check if we're already loaded */
136 cmp byte ptr [esi+KTHREAD_NPX_STATE], NPX_STATE_LOADED
139 /* Check if we're supposed to get it */
140 cmp dword ptr [esp+20], 0
144 /* Assert Fxsr support */
145 test byte ptr _KeI386FxsrPresent, 1
151 /* Get CR0 and test if it's valid */
153 test bl, CR0_MP + CR0_TS + CR0_EM
156 /* Enable fnsave to work */
157 and ebx, ~(CR0_MP + CR0_TS + CR0_EM)
161 /* Check if we are the NPX Thread */
162 mov eax, [edi+KPCR_NPX_THREAD]
166 /* Check if it's not loaded */
167 cmp byte ptr [eax+KTHREAD_NPX_STATE], NPX_STATE_NOT_LOADED
171 /* We are the NPX Thread with an unloaded NPX State... this isn't normal! */
175 /* Save the NPX State */
176 mov ecx, [eax+KTHREAD_INITIAL_STACK]
177 sub ecx, NPX_FRAME_LENGTH
179 mov byte ptr [eax+KTHREAD_NPX_STATE], NPX_STATE_NOT_LOADED
182 /* Load the NPX State */
183 mov ecx, [esi+KTHREAD_INITIAL_STACK]
184 sub ecx, NPX_FRAME_LENGTH
187 /* Get the CR0 state and destination */
188 mov edx, [ecx+FN_CR0_NPX_STATE]
193 /* We already have a valid state, flush it */
195 test bl, CR0_MP + CR0_TS + CR0_EM
198 /* Enable fnsave to work */
199 and ebx, ~(CR0_MP + CR0_TS + CR0_EM)
203 /* Get the kernel stack */
204 mov ecx, [esi+KTHREAD_INITIAL_STACK]
205 test byte ptr _KeI386FxsrPresent, 1
206 lea ecx, [ecx-NPX_FRAME_LENGTH]
208 /* Set the NPX State */
209 mov byte ptr [esi+KTHREAD_NPX_STATE], NPX_STATE_NOT_LOADED
212 mov edx, [ecx+FN_CR0_NPX_STATE]
215 /* Save the FX State */
218 /* Check if we also have to save it in the parameter */
223 /* Save the Fn state in the parameter we got */
232 or ebx, NPX_STATE_NOT_LOADED
234 /* Clear the NPX thread */
235 mov [edi+KPCR_NPX_THREAD], eax
237 /* Add saved CR0 into NPX State, and set it */
241 /* Re-enable interrupts and return */
254 * The KiThreadStartup routine is the beginning of any thread.
257 * SystemRoutine - Pointer to the System Startup Routine. Either
258 * PspUserThreadStartup or PspSystemThreadStartup
260 * StartRoutine - For Kernel Threads only, specifies the starting execution
261 * point of the new thread.
263 * StartContext - For Kernel Threads only, specifies a pointer to variable
264 * context data to be sent to the StartRoutine above.
266 * UserThread - Indicates whether or not this is a user thread. This tells
267 * us if the thread has a context or not.
269 * TrapFrame - Pointer to the KTHREAD to which the caller wishes to
273 * Should never return for a system thread. Returns through the System Call
274 * Exit Dispatcher for a user thread.
277 * If a return from a system thread is detected, a bug check will occur.
280 .func KiThreadStartup@156
281 .globl _KiThreadStartup@156
282 _KiThreadStartup@156:
285 * Clear all the non-volatile registers, so the thread won't be tempted to
286 * expect any static data (like some badly coded usermode/win9x apps do)
293 /* It's now safe to go to APC */
298 * Call the System Routine which is right on our stack now.
299 * After we pop the pointer, the Start Routine/Context will be on the
300 * stack, as parameters to the System Routine
305 /* The thread returned... was it a user-thread? */
310 /* Yes it was, set our trapframe for the System Call Exit Dispatcher */
313 /* Exit back to user-mode */
318 /* A system thread returned...this is very bad! */
323 * KiSwapContextInternal
325 * The KiSwapContextInternal routine switches context to another thread.
328 * ESI - Pointer to the KTHREAD to which the caller wishes to
330 * EDI - Pointer to the KTHREAD to which the caller wishes to
337 * Absolutely all registers except ESP can be trampled here for maximum code flexibility.
340 .globl @KiSwapContextInternal@0
341 .func @KiSwapContextInternal@0, @KiSwapContextInternal@0
342 @KiSwapContextInternal@0:
349 /* Acquire the swap lock */
350 cmp byte ptr [esi+KTHREAD_SWAP_BUSY], 0
356 /* Increase context switches (use ES for lazy load) */
357 inc dword ptr es:[ebx+KPCR_CONTEXT_SWITCHES]
359 /* Save the Exception list */
360 push [ebx+KPCR_EXCEPTION_LIST]
363 cmp dword ptr [ebx+KPCR_PERF_GLOBAL_GROUP_MASK], 0
369 /* Assert that we're on the right CPU */
370 mov cl, [esi+KTHREAD_NEXT_PROCESSOR]
371 cmp cl, [ebx+KPCR_PROCESSOR_NUMBER]
376 /* Get CR0 and save it */
381 /* Check NPX State */
382 cmp byte ptr [edi+KTHREAD_NPX_STATE], NPX_STATE_LOADED
388 mov [edi+KTHREAD_KERNEL_STACK], esp
390 /* Checking NPX, disable interrupts now */
391 mov eax, [esi+KTHREAD_INITIAL_STACK]
394 /* Get the NPX State */
395 movzx ecx, byte ptr [esi+KTHREAD_NPX_STATE]
397 /* Clear the other bits, merge in CR0, merge in FPU CR0 bits and compare */
398 and edx, ~(CR0_MP + CR0_EM + CR0_TS)
400 or ecx, [eax - (NPX_FRAME_LENGTH - FN_CR0_NPX_STATE)]
405 /* Enable interrupts and set the current stack */
407 mov esp, [esi+KTHREAD_KERNEL_STACK]
409 /* Check if address space switch is needed */
410 mov ebp, [esi+KTHREAD_APCSTATE_PROCESS]
411 mov eax, [edi+KTHREAD_APCSTATE_PROCESS]
416 /* Get the active processors and XOR with the process' */
417 mov ecx, [ebx+KPCR_SET_MEMBER_COPY]
418 lock xor [ebp+KPROCESS_ACTIVE_PROCESSORS], ecx
419 lock xor [eax+KPROCESS_ACTIVE_PROCESSORS], ecx
421 /* Assert change went ok */
423 test [ebp+KPROCESS_ACTIVE_PROCESSORS], ecx
425 test [eax+KPROCESS_ACTIVE_PROCESSORS], ecx
430 /* Check if we need an LDT */
431 mov ecx, [ebp+KPROCESS_LDT_DESCRIPTOR0]
432 or ecx, [eax+KPROCESS_LDT_DESCRIPTOR0]
436 /* Switch address space */
437 mov eax, [ebp+KPROCESS_DIRECTORY_TABLE_BASE]
443 /* Release swap lock */
444 and byte ptr [edi+KTHREAD_SWAP_BUSY], 0
452 mov eax, [esi+KTHREAD_TEB]
453 mov [ebx+KPCR_TEB], eax
454 mov ecx, [ebx+KPCR_GDT]
460 /* Get stack pointer */
461 mov eax, [esi+KTHREAD_INITIAL_STACK]
463 /* Make space for the NPX Frame */
464 sub eax, NPX_FRAME_LENGTH
466 /* Check if this isn't V86 Mode, so we can bias the Esp0 */
467 test dword ptr [eax - KTRAP_FRAME_SIZE + KTRAP_FRAME_EFLAGS], EFLAGS_V86_MASK
471 sub eax, KTRAP_FRAME_V86_GS - KTRAP_FRAME_SS
476 mov ecx, [ebx+KPCR_TSS]
477 mov [ecx+KTSS_ESP0], eax
479 /* Set current IOPM offset in the TSS */
480 mov ax, [ebp+KPROCESS_IOPM_OFFSET]
481 mov [ecx+KTSS_IOMAPBASE], ax
483 /* Increase context switches */
484 inc dword ptr [esi+KTHREAD_CONTEXT_SWITCHES]
486 /* Restore exception list */
487 pop [ebx+KPCR_EXCEPTION_LIST]
492 /* DPC shouldn't be active */
493 cmp byte ptr [ebx+KPCR_PRCB_DPC_ROUTINE_ACTIVE], 0
496 /* Check if kernel APCs are pending */
497 cmp byte ptr [esi+KTHREAD_PENDING_KERNEL_APC], 0
500 /* No APCs, return */
506 /* Check if they're disabled */
507 cmp word ptr [esi+KTHREAD_SPECIAL_APC_DISABLE], 0
512 /* Request APC Delivery */
514 call @HalRequestSoftwareInterrupt@4
519 /* Return with APC pending */
524 /* Check if it's empty */
525 mov eax, [ebp+KPROCESS_LDT_DESCRIPTOR0]
529 /* Write the LDT Selector */
530 mov ecx, [ebx+KPCR_GDT]
531 mov [ecx+KGDT_LDT], eax
532 mov eax, [ebp+KPROCESS_LDT_DESCRIPTOR1]
533 mov [ecx+KGDT_LDT+4], eax
535 /* Write the INT21 handler */
536 mov ecx, [ebx+KPCR_IDT]
537 mov eax, [ebp+KPROCESS_INT21_DESCRIPTOR0]
539 mov eax, [ebp+KPROCESS_INT21_DESCRIPTOR1]
542 /* Save LDT Selector */
552 /* Assert NPX State */
553 test byte ptr [esi+KTHREAD_NPX_STATE], ~(NPX_STATE_NOT_LOADED)
555 test dword ptr [eax - (NPX_FRAME_LENGTH - FN_CR0_NPX_STATE)], ~(CR0_PE + CR0_MP + CR0_EM + CR0_TS)
575 /* No WMI support yet */
583 /* Bugcheck the machine, printing out the threads being switched */
584 mov eax, [edi+KTHREAD_INITIAL_STACK]
589 push ATTEMPTED_SWITCH_FROM_DPC
590 call _KeBugCheckEx@20
605 * The KiSwapContext routine switches context to another thread.
608 * TargetThread - Pointer to the KTHREAD to which the caller wishes to
612 * The WaitStatus of the Target Thread.
615 * This is a wrapper around KiSwapContextInternal which will save all the
616 * non-volatile registers so that the Internal function can use all of
617 * them. It will also save the old current thread and set the new one.
619 * The calling thread does not return after KiSwapContextInternal until
620 * another thread switches to IT.
623 .globl @KiSwapContext@8
624 .func @KiSwapContext@8, @KiSwapContext@8
627 /* Save 4 registers */
630 /* Save all the non-volatile ones */
636 /* Get the current KPCR */
637 mov ebx, fs:[KPCR_SELF]
639 /* Get the Current Thread */
642 /* Get the New Thread */
645 /* Get the wait IRQL */
646 movzx ecx, byte ptr [edi+KTHREAD_WAIT_IRQL]
648 /* Do the swap with the registers correctly setup */
649 call @KiSwapContextInternal@0
651 /* Return the registers */
663 .func @KiIdleLoop@0, @KiIdleLoop@0
667 mov ebx, fs:[KPCR_SELF]
669 /* Jump into mainline code */
673 /* Call the CPU's idle function */
674 lea ecx, [ebx+KPCR_PRCB_POWER_STATE_IDLE_FUNCTION]
678 /* Cycle interrupts for 1 cycle */
684 /* Check if we have to deliver DPCs, timers, or deferred threads */
685 mov eax, [ebx+KPCR_PRCB_DPC_QUEUE_DEPTH]
686 or eax, [ebx+KPCR_PRCB_TIMER_REQUEST]
688 or eax, [ebx+KPCR_PRCB_DEFERRED_READY_LIST_HEAD]
692 mov cl, DISPATCH_LEVEL
693 call @HalClearSoftwareInterrupt@4
695 /* Handle the above */
696 lea ecx, [ebx+KPCR_PRCB_DATA]
697 call @KiRetireDpcList@4
700 /* Check if a next thread is queued */
701 cmp dword ptr [ebx+KPCR_PRCB_NEXT_THREAD], 0
709 /* There is, raise IRQL to synch level */
710 call _KeRaiseIrqlToSynchLevel@0
714 /* Set the current thread to ready */
715 mov edi, [ebx+KPCR_CURRENT_THREAD]
717 mov byte ptr [edi+KTHREAD_SWAP_BUSY], 1
719 /* Acquire the PRCB Lock */
720 lock bts dword ptr [ebx+KPCR_PRCB_PRCB_LOCK], 0
722 lea ecx, [ebx+KPCR_PRCB_PRCB_LOCK]
723 call @KefAcquireSpinLockAtDpcLevel@4
727 /* Check if the next thread is the current */
728 mov esi, [ebx+KPCR_PRCB_NEXT_THREAD]
734 /* Clear the next thread and set this one instead */
735 and dword ptr [ebx+KPCR_PRCB_NEXT_THREAD], 0
736 mov [ebx+KPCR_CURRENT_THREAD], esi
738 /* Set the thread as running */
739 mov byte ptr [esi+KTHREAD_STATE_], Running
742 /* Disable the idle scheduler and release the PRCB lock */
743 and byte ptr [ebx+KPCR_PRCB_IDLE_SCHEDULE], 0
744 and dword ptr [ebx+KPCR_PRCB_PRCB_LOCK], 0
748 /* ReactOS Mm Hack */
750 call @MiSyncForContextSwitch@4
752 /* Swap context at APC_LEVEL */
754 call @KiSwapContextInternal@0
757 /* Lower to DPC level */
758 mov ecx, DISPATCH_LEVEL
765 /* Clear the next thread, and put the thready as ready after lock release */
766 and dword ptr [ebx+KPCR_PRCB_NEXT_THREAD], 0
767 and dword ptr [ebx+KPCR_PRCB_PRCB_LOCK], 0
768 and byte ptr [edi+KTHREAD_STATE_], Ready
772 /* Check if the idle scheduler is enabled */
773 cmp byte ptr [ebx+KPCR_PRCB_IDLE_SCHEDULE], 0
776 /* It is, so call the scheduler */
777 lea ecx, [ebx+KPCR_PRCB_DATA]
778 call @KiIdleSchedule@4
781 /* Get new thread pointers and either swap or idle loop again */
783 mov edi, [ebx+KPCR_PRCB_IDLE_THREAD]
789 .globl _Ki386AdjustEsp0@4
790 .func Ki386AdjustEsp0@4
793 /* Get the current thread */
794 mov eax, [fs:KPCR_CURRENT_THREAD]
796 /* Get trap frame and stack */
798 mov eax, [eax+KTHREAD_INITIAL_STACK]
801 test dword ptr [edx+KTRAP_FRAME_EFLAGS], EFLAGS_V86_MASK
805 sub eax, KTRAP_FRAME_V86_GS - KTRAP_FRAME_SS
808 /* Skip FX Save Area */
809 sub eax, SIZEOF_FX_SAVE_AREA
811 /* Disable interrupts */
816 mov edx, [fs:KPCR_TSS]
817 mov ss:[edx+KTSS_ESP0], eax
819 /* Enable interrupts and return */
824 .globl _KiSwapProcess@8
825 .func KiSwapProcess@8
828 /* Get process pointers */
833 /* Update active processors */
834 mov ecx, fs:[KPCR_SET_MEMBER]
835 lock xor [edx+KPROCESS_ACTIVE_PROCESSORS], ecx
836 lock xor [eax+KPROCESS_ACTIVE_PROCESSORS], ecx
840 test dword ptr [edx+KPROCESS_ACTIVE_PROCESSORS], 0
842 test dword ptr [eax+KPROCESS_ACTIVE_PROCESSORS], 0
847 /* Check if their LDTs changed */
848 mov ecx, [edx+KPROCESS_LDT_DESCRIPTOR0]
849 or ecx, [eax+KPROCESS_LDT_DESCRIPTOR0]
853 mov eax, [edx+KPROCESS_DIRECTORY_TABLE_BASE]
857 mov ecx, fs:[KPCR_TSS]
859 /* Clear GS on process swap */
863 /* Update IOPM offset */
864 mov ax, [edx+KPROCESS_IOPM_OFFSET]
865 mov [ecx+KTSS_IOMAPBASE], ax