3 * Copyright (C) 1998, 1999, 2000, 2001 ReactOS Team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 * PROJECT: ReactOS kernel
21 * FILE: ntoskrnl/ke/apc.c
22 * PURPOSE: NT Implementation of APCs
23 * PROGRAMMER: Alex Ionescu (alex@relsoft.net)
24 * PORTABILITY: Unchecked
27 * 12/11/99: Phillip Susi: Reworked the APC code
28 * 11/11/04: Alex Ionescu - Total Rewrite
31 /* INCLUDES *****************************************************************/
35 #include <internal/debug.h>
37 /* GLOBALS *******************************************************************/
39 VOID
PsTerminateCurrentThread(NTSTATUS ExitStatus
);
41 #define TAG_KAPC TAG('K', 'A', 'P', 'C')
43 /* FUNCTIONS *****************************************************************/
53 IN KAPC_ENVIRONMENT TargetEnvironment
,
54 IN PKKERNEL_ROUTINE KernelRoutine
,
55 IN PKRUNDOWN_ROUTINE RundownRoutine OPTIONAL
,
56 IN PKNORMAL_ROUTINE NormalRoutine
,
57 IN KPROCESSOR_MODE Mode
,
60 * FUNCTION: Initialize an APC object
62 * Apc = Pointer to the APC object to initialized
63 * Thread = Thread the APC is to be delivered to
64 * TargetEnvironment = APC environment to use
65 * KernelRoutine = Routine to be called for a kernel-mode APC
66 * RundownRoutine = Routine to be called if the thread has exited with
67 * the APC being executed
68 * NormalRoutine = Routine to be called for a user-mode APC
70 * Context = Parameter to be passed to the APC routine
73 DPRINT ("KeInitializeApc(Apc %x, Thread %x, Environment %d, "
74 "KernelRoutine %x, RundownRoutine %x, NormalRoutine %x, Mode %d, "
75 "Context %x)\n",Apc
,Thread
,TargetEnvironment
,KernelRoutine
,RundownRoutine
,
76 NormalRoutine
,Mode
,Context
);
78 /* Set up the basic APC Structure Data */
79 RtlZeroMemory(Apc
, sizeof(KAPC
));
81 Apc
->Size
= sizeof(KAPC
);
83 /* Set the Environment */
84 if (TargetEnvironment
== CurrentApcEnvironment
) {
85 Apc
->ApcStateIndex
= Thread
->ApcStateIndex
;
87 Apc
->ApcStateIndex
= TargetEnvironment
;
90 /* Set the Thread and Routines */
92 Apc
->KernelRoutine
= KernelRoutine
;
93 Apc
->RundownRoutine
= RundownRoutine
;
94 Apc
->NormalRoutine
= NormalRoutine
;
96 /* Check if this is a Special APC, in which case we use KernelMode and no Context */
97 if (ARGUMENT_PRESENT(NormalRoutine
)) {
99 Apc
->NormalContext
= Context
;
101 Apc
->ApcMode
= KernelMode
;
110 KeInsertQueueApc (PKAPC Apc
,
111 PVOID SystemArgument1
,
112 PVOID SystemArgument2
,
113 KPRIORITY PriorityBoost
)
115 * FUNCTION: Queues an APC for execution
117 * Apc = APC to be queued
118 * SystemArgument[1-2] = Arguments we ignore and simply pass on.
119 * PriorityBoost = Priority Boost to give to the Thread
124 PLIST_ENTRY ApcListEntry
;
127 ASSERT_IRQL_LESS_OR_EQUAL(DISPATCH_LEVEL
);
128 DPRINT ("KeInsertQueueApc(Apc %x, SystemArgument1 %x, "
129 "SystemArgument2 %x)\n",Apc
,SystemArgument1
,
132 OldIrql
= KeAcquireDispatcherDatabaseLock();
134 /* Get the Thread specified in the APC */
135 Thread
= Apc
->Thread
;
137 /* Make sure the thread allows APC Queues.
138 * The thread is not apc queueable, for instance, when it's (about to be) terminated.
140 if (Thread
->ApcQueueable
== FALSE
) {
141 DPRINT("Thread doesn't allow APC Queues\n");
142 KeReleaseDispatcherDatabaseLock(OldIrql
);
146 /* Set the System Arguments */
147 Apc
->SystemArgument1
= SystemArgument1
;
148 Apc
->SystemArgument2
= SystemArgument2
;
150 /* Don't do anything if the APC is already inserted */
152 KeReleaseDispatcherDatabaseLock(OldIrql
);
157 1) Kernel APC with Normal Routine or User APC = Put it at the end of the List
158 2) User APC which is PsExitSpecialApc = Put it at the front of the List
159 3) Kernel APC without Normal Routine = Put it at the end of the No-Normal Routine Kernel APC list
161 if ((Apc
->ApcMode
!= KernelMode
) && (Apc
->KernelRoutine
== (PKKERNEL_ROUTINE
)PsExitSpecialApc
)) {
162 DPRINT ("Inserting the Process Exit APC into the Queue\n");
163 Thread
->ApcStatePointer
[(int)Apc
->ApcStateIndex
]->UserApcPending
= TRUE
;
164 InsertHeadList(&Thread
->ApcStatePointer
[(int)Apc
->ApcStateIndex
]->ApcListHead
[(int)Apc
->ApcMode
],
166 } else if (Apc
->NormalRoutine
== NULL
) {
167 DPRINT ("Inserting Special APC %x into the Queue\n", Apc
);
168 for (ApcListEntry
= Thread
->ApcStatePointer
[(int)Apc
->ApcStateIndex
]->ApcListHead
[(int)Apc
->ApcMode
].Flink
;
169 ApcListEntry
!= &Thread
->ApcStatePointer
[(int)Apc
->ApcStateIndex
]->ApcListHead
[(int)Apc
->ApcMode
];
170 ApcListEntry
= ApcListEntry
->Flink
) {
172 QueuedApc
= CONTAINING_RECORD(ApcListEntry
, KAPC
, ApcListEntry
);
173 if (Apc
->NormalRoutine
!= NULL
) break;
176 /* We found the first "Normal" APC, so write right before it */
177 ApcListEntry
= ApcListEntry
->Blink
;
178 InsertHeadList(ApcListEntry
, &Apc
->ApcListEntry
);
180 DPRINT ("Inserting Normal APC %x into the %x Queue\n", Apc
, Apc
->ApcMode
);
181 InsertTailList(&Thread
->ApcStatePointer
[(int)Apc
->ApcStateIndex
]->ApcListHead
[(int)Apc
->ApcMode
],
185 /* Confirm Insertion */
186 Apc
->Inserted
= TRUE
;
188 /* Three possibilites here again:
189 1) Kernel APC, The thread is Running: Request an Interrupt
190 2) Kernel APC, The Thread is Waiting at PASSIVE_LEVEL and APCs are enabled and not in progress: Unwait the Thread
191 3) User APC, Unwait the Thread if it is alertable
193 if (Apc
->ApcMode
== KernelMode
) {
194 Thread
->ApcState
.KernelApcPending
= TRUE
;
195 if (Thread
->State
== THREAD_STATE_RUNNING
) {
197 DPRINT ("Requesting APC Interrupt for Running Thread \n");
198 HalRequestSoftwareInterrupt(APC_LEVEL
);
199 } else if ((Thread
->State
== THREAD_STATE_BLOCKED
) &&
200 (Thread
->WaitIrql
< APC_LEVEL
) &&
201 (Apc
->NormalRoutine
== NULL
))
203 DPRINT ("Waking up Thread for Kernel-Mode APC Delivery \n");
204 KiAbortWaitThread(Thread
, STATUS_KERNEL_APC
);
206 } else if ((Thread
->State
== THREAD_STATE_BLOCKED
) &&
207 (Thread
->WaitMode
== UserMode
) &&
210 DPRINT ("Waking up Thread for User-Mode APC Delivery \n");
211 Thread
->ApcState
.UserApcPending
= TRUE
;
212 KiAbortWaitThread(Thread
, STATUS_USER_APC
);
215 /* Return Sucess if we are here */
216 KeReleaseDispatcherDatabaseLock(OldIrql
);
221 KeRemoveQueueApc (PKAPC Apc
)
223 * FUNCTION: Removes APC object from the apc queue
225 * Apc = APC to remove
226 * RETURNS: TRUE if the APC was in the queue
228 * NOTE: This function is not exported.
232 PKTHREAD Thread
= Apc
->Thread
;
234 ASSERT_IRQL_LESS_OR_EQUAL(DISPATCH_LEVEL
);
235 DPRINT("KeRemoveQueueApc called for APC: %x \n", Apc
);
237 OldIrql
= KeAcquireDispatcherDatabaseLock();
238 KeAcquireSpinLock(&Thread
->ApcQueueLock
, &OldIrql
);
240 /* Remove it from the Queue if it's inserted */
241 if (!Apc
->Inserted
== FALSE
) {
242 RemoveEntryList(&Apc
->ApcListEntry
);
243 Apc
->Inserted
= FALSE
;
245 /* If the Queue is completely empty, then no more APCs are pending */
246 if (IsListEmpty(&Thread
->ApcStatePointer
[(int)Apc
->ApcStateIndex
]->ApcListHead
[(int)Apc
->ApcMode
])) {
247 if (Apc
->ApcMode
== KernelMode
) {
248 Thread
->ApcStatePointer
[(int)Apc
->ApcStateIndex
]->KernelApcPending
= FALSE
;
250 Thread
->ApcStatePointer
[(int)Apc
->ApcStateIndex
]->UserApcPending
= FALSE
;
254 KeReleaseSpinLock(&Thread
->ApcQueueLock
, OldIrql
);
255 KeReleaseDispatcherDatabaseLock(OldIrql
);
259 /* Restore IRQL and Return */
260 KeReleaseSpinLock(&Thread
->ApcQueueLock
, OldIrql
);
261 KeReleaseDispatcherDatabaseLock(OldIrql
);
271 KiDeliverApc(KPROCESSOR_MODE DeliveryMode
,
273 PKTRAP_FRAME TrapFrame
)
275 * FUNCTION: Deliver an APC to the current thread.
276 * NOTES: This is called from the IRQL switching code if the current thread
277 * is returning from an IRQL greater than or equal to APC_LEVEL to
278 * PASSIVE_LEVEL and there are kernel-mode APCs pending. This means any
279 * pending APCs will be delivered after a thread gets a new quantum and
280 * after it wakes from a wait. Note that the TrapFrame is only valid if
281 * the previous mode is User.
284 PKTHREAD Thread
= KeGetCurrentThread();
285 PLIST_ENTRY ApcListEntry
;
288 PKKERNEL_ROUTINE KernelRoutine
;
290 PKNORMAL_ROUTINE NormalRoutine
;
291 PVOID SystemArgument1
;
292 PVOID SystemArgument2
;
294 ASSERT_IRQL_EQUAL(APC_LEVEL
);
296 /* Lock the APC Queue and Raise IRQL to Synch */
297 KeAcquireSpinLock(&Thread
->ApcQueueLock
, &OldIrql
);
299 /* Clear APC Pending */
300 Thread
->ApcState
.KernelApcPending
= FALSE
;
302 /* Do the Kernel APCs first */
303 while (!IsListEmpty(&Thread
->ApcState
.ApcListHead
[KernelMode
])) {
305 /* Get the next Entry */
306 ApcListEntry
= Thread
->ApcState
.ApcListHead
[KernelMode
].Flink
;
307 Apc
= CONTAINING_RECORD(ApcListEntry
, KAPC
, ApcListEntry
);
309 /* Save Parameters so that it's safe to free the Object in Kernel Routine*/
310 NormalRoutine
= Apc
->NormalRoutine
;
311 KernelRoutine
= Apc
->KernelRoutine
;
312 NormalContext
= Apc
->NormalContext
;
313 SystemArgument1
= Apc
->SystemArgument1
;
314 SystemArgument2
= Apc
->SystemArgument2
;
317 if (NormalRoutine
== NULL
) {
318 /* Remove the APC from the list */
319 Apc
->Inserted
= FALSE
;
320 RemoveEntryList(ApcListEntry
);
322 /* Go back to APC_LEVEL */
323 KeReleaseSpinLock(&Thread
->ApcQueueLock
, OldIrql
);
325 /* Call the Special APC */
326 DPRINT("Delivering a Special APC: %x\n", Apc
);
333 /* Raise IRQL and Lock again */
334 KeAcquireSpinLock(&Thread
->ApcQueueLock
, &OldIrql
);
336 /* Normal Kernel APC */
337 if (Thread
->ApcState
.KernelApcInProgress
|| Thread
->KernelApcDisable
) {
340 * DeliveryMode must be KernelMode in this case, since one may not
341 * return to umode while being inside a critical section or while
342 * a regular kmode apc is running (the latter should be impossible btw).
345 ASSERT(DeliveryMode
== KernelMode
);
347 KeReleaseSpinLock(&Thread
->ApcQueueLock
, OldIrql
);
351 /* Dequeue the APC */
352 RemoveEntryList(ApcListEntry
);
353 Apc
->Inserted
= FALSE
;
355 /* Go back to APC_LEVEL */
356 KeReleaseSpinLock(&Thread
->ApcQueueLock
, OldIrql
);
358 /* Call the Kernel APC */
359 DPRINT("Delivering a Normal APC: %x\n", Apc
);
366 /* If There still is a Normal Routine, then we need to call this at PASSIVE_LEVEL */
367 if (NormalRoutine
!= NULL
) {
368 /* At Passive Level, this APC can be prempted by a Special APC */
369 Thread
->ApcState
.KernelApcInProgress
= TRUE
;
370 KeLowerIrql(PASSIVE_LEVEL
);
372 /* Call and Raise IRQ back to APC_LEVEL */
373 DPRINT("Calling the Normal Routine for a Normal APC: %x\n", Apc
);
374 NormalRoutine(&NormalContext
, &SystemArgument1
, &SystemArgument2
);
375 KeRaiseIrql(APC_LEVEL
, &OldIrql
);
378 /* Raise IRQL and Lock again */
379 KeAcquireSpinLock(&Thread
->ApcQueueLock
, &OldIrql
);
380 Thread
->ApcState
.KernelApcInProgress
= FALSE
;
384 /* Now we do the User APCs */
385 if ((!IsListEmpty(&Thread
->ApcState
.ApcListHead
[UserMode
])) &&
386 (DeliveryMode
== UserMode
) &&
387 (Thread
->ApcState
.UserApcPending
== TRUE
)) {
389 /* It's not pending anymore */
390 Thread
->ApcState
.UserApcPending
= FALSE
;
392 /* Get the APC Object */
393 ApcListEntry
= Thread
->ApcState
.ApcListHead
[UserMode
].Flink
;
394 Apc
= CONTAINING_RECORD(ApcListEntry
, KAPC
, ApcListEntry
);
396 /* Save Parameters so that it's safe to free the Object in Kernel Routine*/
397 NormalRoutine
= Apc
->NormalRoutine
;
398 KernelRoutine
= Apc
->KernelRoutine
;
399 NormalContext
= Apc
->NormalContext
;
400 SystemArgument1
= Apc
->SystemArgument1
;
401 SystemArgument2
= Apc
->SystemArgument2
;
403 /* Remove the APC from Queue, restore IRQL and call the APC */
404 RemoveEntryList(ApcListEntry
);
405 Apc
->Inserted
= FALSE
;
407 KeReleaseSpinLock(&Thread
->ApcQueueLock
, OldIrql
);
408 DPRINT("Calling the Kernel Routine for for a User APC: %x\n", Apc
);
415 if (NormalRoutine
== NULL
) {
416 /* Check if more User APCs are Pending */
417 KeTestAlertThread(UserMode
);
419 /* Set up the Trap Frame and prepare for Execution in NTDLL.DLL */
420 DPRINT("Delivering a User APC: %x\n", Apc
);
421 KiInitializeUserApc(Reserved
,
429 /* Go back to APC_LEVEL */
430 KeReleaseSpinLock(&Thread
->ApcQueueLock
, OldIrql
);
436 KiFreeApcRoutine(PKAPC Apc
,
437 PKNORMAL_ROUTINE
* NormalRoutine
,
438 PVOID
* NormalContext
,
439 PVOID
* SystemArgument1
,
440 PVOID
* SystemArgument2
)
442 /* Free the APC and do nothing else */
447 KiInitializeUserApc(IN PVOID Reserved
,
448 IN PKTRAP_FRAME TrapFrame
,
449 IN PKNORMAL_ROUTINE NormalRoutine
,
450 IN PVOID NormalContext
,
451 IN PVOID SystemArgument1
,
452 IN PVOID SystemArgument2
)
454 * FUNCTION: Prepares the Context for a user mode APC through ntdll.dll
460 DPRINT("KiInitializeUserApc(TrapFrame %x/%x)\n", TrapFrame
, KeGetCurrentThread()->TrapFrame
);
463 * Save the thread's current context (in other words the registers
464 * that will be restored when it returns to user mode) so the
465 * APC dispatcher can restore them later
467 Context
= (PCONTEXT
)(((PUCHAR
)TrapFrame
->Esp
) - sizeof(CONTEXT
));
468 RtlZeroMemory(Context
, sizeof(CONTEXT
));
469 Context
->ContextFlags
= CONTEXT_FULL
;
470 Context
->SegGs
= TrapFrame
->Gs
;
471 Context
->SegFs
= TrapFrame
->Fs
;
472 Context
->SegEs
= TrapFrame
->Es
;
473 Context
->SegDs
= TrapFrame
->Ds
;
474 Context
->Edi
= TrapFrame
->Edi
;
475 Context
->Esi
= TrapFrame
->Esi
;
476 Context
->Ebx
= TrapFrame
->Ebx
;
477 Context
->Edx
= TrapFrame
->Edx
;
478 Context
->Ecx
= TrapFrame
->Ecx
;
479 Context
->Eax
= TrapFrame
->Eax
;
480 Context
->Ebp
= TrapFrame
->Ebp
;
481 Context
->Eip
= TrapFrame
->Eip
;
482 Context
->SegCs
= TrapFrame
->Cs
;
483 Context
->EFlags
= TrapFrame
->Eflags
;
484 Context
->Esp
= TrapFrame
->Esp
;
485 Context
->SegSs
= TrapFrame
->Ss
;
488 * Setup the trap frame so the thread will start executing at the
489 * APC Dispatcher when it returns to user-mode
491 Esp
= (PULONG
)(((PUCHAR
)TrapFrame
->Esp
) - (sizeof(CONTEXT
) + (6 * sizeof(ULONG
))));
493 Esp
[1] = (ULONG
)NormalRoutine
;
494 Esp
[2] = (ULONG
)NormalContext
;
495 Esp
[3] = (ULONG
)SystemArgument1
;
496 Esp
[4] = (ULONG
)SystemArgument2
;
497 Esp
[5] = (ULONG
)Context
;
498 TrapFrame
->Eip
= (ULONG
)LdrpGetSystemDllApcDispatcher();
499 TrapFrame
->Esp
= (ULONG
)Esp
;
511 return KeGetCurrentThread()->KernelApcDisable
? TRUE
: FALSE
;
516 NtQueueApcThread(HANDLE ThreadHandle
,
517 PKNORMAL_ROUTINE ApcRoutine
,
519 PVOID SystemArgument1
,
520 PVOID SystemArgument2
)
523 * This function is used to queue an APC from user-mode for the specified thread.
524 * The thread must enter an alertable wait before the APC will be delivered.
527 * Thread Handle - Handle to the Thread. This handle must have THREAD_SET_CONTEXT privileges.
528 * ApcRoutine - Pointer to the APC Routine to call when the APC executes.
529 * NormalContext - User-defined value to pass to the APC Routine
530 * SystemArgument1 - User-defined value to pass to the APC Routine
531 * SystemArgument2 - User-defined value to pass to the APC Routine
533 * RETURNS: NTSTATUS SUCCESS or Failure Code from included calls.
541 /* Get ETHREAD from Handle */
542 Status
= ObReferenceObjectByHandle(ThreadHandle
,
549 /* Fail if the Handle is invalid for some reason */
550 if (!NT_SUCCESS(Status
)) {
554 /* If this is a Kernel or System Thread, then fail */
555 if (Thread
->Tcb
.Teb
== NULL
) {
556 ObDereferenceObject(Thread
);
557 return STATUS_INVALID_HANDLE
;
560 /* Allocate an APC */
561 Apc
= ExAllocatePoolWithTag(NonPagedPool
, sizeof(KAPC
), TAG_KAPC
);
563 ObDereferenceObject(Thread
);
564 return(STATUS_NO_MEMORY
);
567 /* Initialize and Queue */
570 OriginalApcEnvironment
,
576 if (!KeInsertQueueApc(Apc
, SystemArgument1
, SystemArgument2
, IO_NO_INCREMENT
)) {
577 Status
= STATUS_UNSUCCESSFUL
;
579 Status
= STATUS_SUCCESS
;
582 /* Dereference Thread and Return */
583 ObDereferenceObject(Thread
);
588 static inline VOID
RepairList(PLIST_ENTRY Original
,
590 KPROCESSOR_MODE Mode
)
592 /* Copy Source to Desination */
593 if (IsListEmpty(&Original
[(int)Mode
])) {
594 InitializeListHead(&Copy
[(int)Mode
]);
596 Copy
[(int)Mode
].Flink
= Original
[(int)Mode
].Flink
;
597 Copy
[(int)Mode
].Blink
= Original
[(int)Mode
].Blink
;
598 Original
[(int)Mode
].Flink
->Blink
= &Copy
[(int)Mode
];
599 Original
[(int)Mode
].Blink
->Flink
= &Copy
[(int)Mode
];
605 KiMoveApcState (PKAPC_STATE OldState
,
606 PKAPC_STATE NewState
)
608 /* Restore backup of Original Environment */
609 *NewState
= *OldState
;
612 RepairList(NewState
->ApcListHead
, OldState
->ApcListHead
, KernelMode
);
613 RepairList(NewState
->ApcListHead
, OldState
->ApcListHead
, UserMode
);