3 * COPYRIGHT: See COPYING in the top level directory
4 * PROJECT: ReactOS kernel
5 * FILE: ntoskrnl/ke/apc.c
6 * PURPOSE: NT Implementation of APCs
8 * PROGRAMMERS: Alex Ionescu (alex@relsoft.net)
12 /* INCLUDES *****************************************************************/
16 #include <internal/debug.h>
18 /* GLOBALS *******************************************************************/
20 VOID
PsTerminateCurrentThread(NTSTATUS ExitStatus
);
22 #define TAG_KAPC TAG('K', 'A', 'P', 'C')
24 /* FUNCTIONS *****************************************************************/
34 IN KAPC_ENVIRONMENT TargetEnvironment
,
35 IN PKKERNEL_ROUTINE KernelRoutine
,
36 IN PKRUNDOWN_ROUTINE RundownRoutine OPTIONAL
,
37 IN PKNORMAL_ROUTINE NormalRoutine
,
38 IN KPROCESSOR_MODE Mode
,
41 * FUNCTION: Initialize an APC object
43 * Apc = Pointer to the APC object to initialized
44 * Thread = Thread the APC is to be delivered to
45 * TargetEnvironment = APC environment to use
46 * KernelRoutine = Routine to be called for a kernel-mode APC
47 * RundownRoutine = Routine to be called if the thread has exited with
48 * the APC being executed
49 * NormalRoutine = Routine to be called for a user-mode APC
51 * Context = Parameter to be passed to the APC routine
54 DPRINT ("KeInitializeApc(Apc %x, Thread %x, Environment %d, "
55 "KernelRoutine %x, RundownRoutine %x, NormalRoutine %x, Mode %d, "
56 "Context %x)\n",Apc
,Thread
,TargetEnvironment
,KernelRoutine
,RundownRoutine
,
57 NormalRoutine
,Mode
,Context
);
59 /* Set up the basic APC Structure Data */
60 RtlZeroMemory(Apc
, sizeof(KAPC
));
62 Apc
->Size
= sizeof(KAPC
);
64 /* Set the Environment */
65 if (TargetEnvironment
== CurrentApcEnvironment
) {
66 Apc
->ApcStateIndex
= Thread
->ApcStateIndex
;
68 Apc
->ApcStateIndex
= TargetEnvironment
;
71 /* Set the Thread and Routines */
73 Apc
->KernelRoutine
= KernelRoutine
;
74 Apc
->RundownRoutine
= RundownRoutine
;
75 Apc
->NormalRoutine
= NormalRoutine
;
77 /* Check if this is a Special APC, in which case we use KernelMode and no Context */
78 if (ARGUMENT_PRESENT(NormalRoutine
)) {
80 Apc
->NormalContext
= Context
;
82 Apc
->ApcMode
= KernelMode
;
91 KeInsertQueueApc (PKAPC Apc
,
92 PVOID SystemArgument1
,
93 PVOID SystemArgument2
,
94 KPRIORITY PriorityBoost
)
96 * FUNCTION: Queues an APC for execution
98 * Apc = APC to be queued
99 * SystemArgument[1-2] = Arguments we ignore and simply pass on.
100 * PriorityBoost = Priority Boost to give to the Thread
105 PLIST_ENTRY ApcListEntry
;
108 ASSERT_IRQL_LESS_OR_EQUAL(DISPATCH_LEVEL
);
109 DPRINT ("KeInsertQueueApc(Apc %x, SystemArgument1 %x, "
110 "SystemArgument2 %x)\n",Apc
,SystemArgument1
,
113 OldIrql
= KeAcquireDispatcherDatabaseLock();
115 /* Get the Thread specified in the APC */
116 Thread
= Apc
->Thread
;
118 /* Make sure the thread allows APC Queues.
119 * The thread is not apc queueable, for instance, when it's (about to be) terminated.
121 if (Thread
->ApcQueueable
== FALSE
) {
122 DPRINT("Thread doesn't allow APC Queues\n");
123 KeReleaseDispatcherDatabaseLock(OldIrql
);
127 /* Set the System Arguments */
128 Apc
->SystemArgument1
= SystemArgument1
;
129 Apc
->SystemArgument2
= SystemArgument2
;
131 /* Don't do anything if the APC is already inserted */
133 KeReleaseDispatcherDatabaseLock(OldIrql
);
138 1) Kernel APC with Normal Routine or User APC = Put it at the end of the List
139 2) User APC which is PsExitSpecialApc = Put it at the front of the List
140 3) Kernel APC without Normal Routine = Put it at the end of the No-Normal Routine Kernel APC list
142 if ((Apc
->ApcMode
!= KernelMode
) && (Apc
->KernelRoutine
== (PKKERNEL_ROUTINE
)PsExitSpecialApc
)) {
143 DPRINT ("Inserting the Process Exit APC into the Queue\n");
144 Thread
->ApcStatePointer
[(int)Apc
->ApcStateIndex
]->UserApcPending
= TRUE
;
145 InsertHeadList(&Thread
->ApcStatePointer
[(int)Apc
->ApcStateIndex
]->ApcListHead
[(int)Apc
->ApcMode
],
147 } else if (Apc
->NormalRoutine
== NULL
) {
148 DPRINT ("Inserting Special APC %x into the Queue\n", Apc
);
149 for (ApcListEntry
= Thread
->ApcStatePointer
[(int)Apc
->ApcStateIndex
]->ApcListHead
[(int)Apc
->ApcMode
].Flink
;
150 ApcListEntry
!= &Thread
->ApcStatePointer
[(int)Apc
->ApcStateIndex
]->ApcListHead
[(int)Apc
->ApcMode
];
151 ApcListEntry
= ApcListEntry
->Flink
) {
153 QueuedApc
= CONTAINING_RECORD(ApcListEntry
, KAPC
, ApcListEntry
);
154 if (Apc
->NormalRoutine
!= NULL
) break;
157 /* We found the first "Normal" APC, so write right before it */
158 ApcListEntry
= ApcListEntry
->Blink
;
159 InsertHeadList(ApcListEntry
, &Apc
->ApcListEntry
);
161 DPRINT ("Inserting Normal APC %x into the %x Queue\n", Apc
, Apc
->ApcMode
);
162 InsertTailList(&Thread
->ApcStatePointer
[(int)Apc
->ApcStateIndex
]->ApcListHead
[(int)Apc
->ApcMode
],
166 /* Confirm Insertion */
167 Apc
->Inserted
= TRUE
;
169 /* Three possibilites here again:
170 1) Kernel APC, The thread is Running: Request an Interrupt
171 2) Kernel APC, The Thread is Waiting at PASSIVE_LEVEL and APCs are enabled and not in progress: Unwait the Thread
172 3) User APC, Unwait the Thread if it is alertable
174 if (Apc
->ApcMode
== KernelMode
) {
175 Thread
->ApcState
.KernelApcPending
= TRUE
;
176 if (Thread
->State
== THREAD_STATE_RUNNING
) {
178 DPRINT ("Requesting APC Interrupt for Running Thread \n");
179 HalRequestSoftwareInterrupt(APC_LEVEL
);
180 } else if ((Thread
->State
== THREAD_STATE_BLOCKED
) &&
181 (Thread
->WaitIrql
< APC_LEVEL
) &&
182 (Apc
->NormalRoutine
== NULL
))
184 DPRINT ("Waking up Thread for Kernel-Mode APC Delivery \n");
185 KiAbortWaitThread(Thread
, STATUS_KERNEL_APC
);
187 } else if ((Thread
->State
== THREAD_STATE_BLOCKED
) &&
188 (Thread
->WaitMode
== UserMode
) &&
191 DPRINT ("Waking up Thread for User-Mode APC Delivery \n");
192 Thread
->ApcState
.UserApcPending
= TRUE
;
193 KiAbortWaitThread(Thread
, STATUS_USER_APC
);
196 /* Return Sucess if we are here */
197 KeReleaseDispatcherDatabaseLock(OldIrql
);
202 KeRemoveQueueApc (PKAPC Apc
)
204 * FUNCTION: Removes APC object from the apc queue
206 * Apc = APC to remove
207 * RETURNS: TRUE if the APC was in the queue
209 * NOTE: This function is not exported.
213 PKTHREAD Thread
= Apc
->Thread
;
215 ASSERT_IRQL_LESS_OR_EQUAL(DISPATCH_LEVEL
);
216 DPRINT("KeRemoveQueueApc called for APC: %x \n", Apc
);
218 OldIrql
= KeAcquireDispatcherDatabaseLock();
219 KeAcquireSpinLock(&Thread
->ApcQueueLock
, &OldIrql
);
221 /* Remove it from the Queue if it's inserted */
222 if (!Apc
->Inserted
== FALSE
) {
223 RemoveEntryList(&Apc
->ApcListEntry
);
224 Apc
->Inserted
= FALSE
;
226 /* If the Queue is completely empty, then no more APCs are pending */
227 if (IsListEmpty(&Thread
->ApcStatePointer
[(int)Apc
->ApcStateIndex
]->ApcListHead
[(int)Apc
->ApcMode
])) {
228 if (Apc
->ApcMode
== KernelMode
) {
229 Thread
->ApcStatePointer
[(int)Apc
->ApcStateIndex
]->KernelApcPending
= FALSE
;
231 Thread
->ApcStatePointer
[(int)Apc
->ApcStateIndex
]->UserApcPending
= FALSE
;
235 KeReleaseSpinLock(&Thread
->ApcQueueLock
, OldIrql
);
236 KeReleaseDispatcherDatabaseLock(OldIrql
);
240 /* Restore IRQL and Return */
241 KeReleaseSpinLock(&Thread
->ApcQueueLock
, OldIrql
);
242 KeReleaseDispatcherDatabaseLock(OldIrql
);
252 KiDeliverApc(KPROCESSOR_MODE DeliveryMode
,
254 PKTRAP_FRAME TrapFrame
)
256 * FUNCTION: Deliver an APC to the current thread.
257 * NOTES: This is called from the IRQL switching code if the current thread
258 * is returning from an IRQL greater than or equal to APC_LEVEL to
259 * PASSIVE_LEVEL and there are kernel-mode APCs pending. This means any
260 * pending APCs will be delivered after a thread gets a new quantum and
261 * after it wakes from a wait. Note that the TrapFrame is only valid if
262 * the previous mode is User.
265 PKTHREAD Thread
= KeGetCurrentThread();
266 PLIST_ENTRY ApcListEntry
;
269 PKKERNEL_ROUTINE KernelRoutine
;
271 PKNORMAL_ROUTINE NormalRoutine
;
272 PVOID SystemArgument1
;
273 PVOID SystemArgument2
;
275 ASSERT_IRQL_EQUAL(APC_LEVEL
);
277 /* Lock the APC Queue and Raise IRQL to Synch */
278 KeAcquireSpinLock(&Thread
->ApcQueueLock
, &OldIrql
);
280 /* Clear APC Pending */
281 Thread
->ApcState
.KernelApcPending
= FALSE
;
283 /* Do the Kernel APCs first */
284 while (!IsListEmpty(&Thread
->ApcState
.ApcListHead
[KernelMode
])) {
286 /* Get the next Entry */
287 ApcListEntry
= Thread
->ApcState
.ApcListHead
[KernelMode
].Flink
;
288 Apc
= CONTAINING_RECORD(ApcListEntry
, KAPC
, ApcListEntry
);
290 /* Save Parameters so that it's safe to free the Object in Kernel Routine*/
291 NormalRoutine
= Apc
->NormalRoutine
;
292 KernelRoutine
= Apc
->KernelRoutine
;
293 NormalContext
= Apc
->NormalContext
;
294 SystemArgument1
= Apc
->SystemArgument1
;
295 SystemArgument2
= Apc
->SystemArgument2
;
298 if (NormalRoutine
== NULL
) {
299 /* Remove the APC from the list */
300 Apc
->Inserted
= FALSE
;
301 RemoveEntryList(ApcListEntry
);
303 /* Go back to APC_LEVEL */
304 KeReleaseSpinLock(&Thread
->ApcQueueLock
, OldIrql
);
306 /* Call the Special APC */
307 DPRINT("Delivering a Special APC: %x\n", Apc
);
314 /* Raise IRQL and Lock again */
315 KeAcquireSpinLock(&Thread
->ApcQueueLock
, &OldIrql
);
317 /* Normal Kernel APC */
318 if (Thread
->ApcState
.KernelApcInProgress
|| Thread
->KernelApcDisable
) {
321 * DeliveryMode must be KernelMode in this case, since one may not
322 * return to umode while being inside a critical section or while
323 * a regular kmode apc is running (the latter should be impossible btw).
326 ASSERT(DeliveryMode
== KernelMode
);
328 KeReleaseSpinLock(&Thread
->ApcQueueLock
, OldIrql
);
332 /* Dequeue the APC */
333 RemoveEntryList(ApcListEntry
);
334 Apc
->Inserted
= FALSE
;
336 /* Go back to APC_LEVEL */
337 KeReleaseSpinLock(&Thread
->ApcQueueLock
, OldIrql
);
339 /* Call the Kernel APC */
340 DPRINT("Delivering a Normal APC: %x\n", Apc
);
347 /* If There still is a Normal Routine, then we need to call this at PASSIVE_LEVEL */
348 if (NormalRoutine
!= NULL
) {
349 /* At Passive Level, this APC can be prempted by a Special APC */
350 Thread
->ApcState
.KernelApcInProgress
= TRUE
;
351 KeLowerIrql(PASSIVE_LEVEL
);
353 /* Call and Raise IRQ back to APC_LEVEL */
354 DPRINT("Calling the Normal Routine for a Normal APC: %x\n", Apc
);
355 NormalRoutine(&NormalContext
, &SystemArgument1
, &SystemArgument2
);
356 KeRaiseIrql(APC_LEVEL
, &OldIrql
);
359 /* Raise IRQL and Lock again */
360 KeAcquireSpinLock(&Thread
->ApcQueueLock
, &OldIrql
);
361 Thread
->ApcState
.KernelApcInProgress
= FALSE
;
365 /* Now we do the User APCs */
366 if ((!IsListEmpty(&Thread
->ApcState
.ApcListHead
[UserMode
])) &&
367 (DeliveryMode
== UserMode
) &&
368 (Thread
->ApcState
.UserApcPending
== TRUE
)) {
370 /* It's not pending anymore */
371 Thread
->ApcState
.UserApcPending
= FALSE
;
373 /* Get the APC Object */
374 ApcListEntry
= Thread
->ApcState
.ApcListHead
[UserMode
].Flink
;
375 Apc
= CONTAINING_RECORD(ApcListEntry
, KAPC
, ApcListEntry
);
377 /* Save Parameters so that it's safe to free the Object in Kernel Routine*/
378 NormalRoutine
= Apc
->NormalRoutine
;
379 KernelRoutine
= Apc
->KernelRoutine
;
380 NormalContext
= Apc
->NormalContext
;
381 SystemArgument1
= Apc
->SystemArgument1
;
382 SystemArgument2
= Apc
->SystemArgument2
;
384 /* Remove the APC from Queue, restore IRQL and call the APC */
385 RemoveEntryList(ApcListEntry
);
386 Apc
->Inserted
= FALSE
;
388 KeReleaseSpinLock(&Thread
->ApcQueueLock
, OldIrql
);
389 DPRINT("Calling the Kernel Routine for for a User APC: %x\n", Apc
);
396 if (NormalRoutine
== NULL
) {
397 /* Check if more User APCs are Pending */
398 KeTestAlertThread(UserMode
);
400 /* Set up the Trap Frame and prepare for Execution in NTDLL.DLL */
401 DPRINT("Delivering a User APC: %x\n", Apc
);
402 KiInitializeUserApc(Reserved
,
410 /* Go back to APC_LEVEL */
411 KeReleaseSpinLock(&Thread
->ApcQueueLock
, OldIrql
);
417 KiFreeApcRoutine(PKAPC Apc
,
418 PKNORMAL_ROUTINE
* NormalRoutine
,
419 PVOID
* NormalContext
,
420 PVOID
* SystemArgument1
,
421 PVOID
* SystemArgument2
)
423 /* Free the APC and do nothing else */
428 KiInitializeUserApc(IN PVOID Reserved
,
429 IN PKTRAP_FRAME TrapFrame
,
430 IN PKNORMAL_ROUTINE NormalRoutine
,
431 IN PVOID NormalContext
,
432 IN PVOID SystemArgument1
,
433 IN PVOID SystemArgument2
)
435 * FUNCTION: Prepares the Context for a user mode APC through ntdll.dll
441 DPRINT("KiInitializeUserApc(TrapFrame %x/%x)\n", TrapFrame
, KeGetCurrentThread()->TrapFrame
);
444 * Save the thread's current context (in other words the registers
445 * that will be restored when it returns to user mode) so the
446 * APC dispatcher can restore them later
448 Context
= (PCONTEXT
)(((PUCHAR
)TrapFrame
->Esp
) - sizeof(CONTEXT
));
449 RtlZeroMemory(Context
, sizeof(CONTEXT
));
450 Context
->ContextFlags
= CONTEXT_FULL
;
451 Context
->SegGs
= TrapFrame
->Gs
;
452 Context
->SegFs
= TrapFrame
->Fs
;
453 Context
->SegEs
= TrapFrame
->Es
;
454 Context
->SegDs
= TrapFrame
->Ds
;
455 Context
->Edi
= TrapFrame
->Edi
;
456 Context
->Esi
= TrapFrame
->Esi
;
457 Context
->Ebx
= TrapFrame
->Ebx
;
458 Context
->Edx
= TrapFrame
->Edx
;
459 Context
->Ecx
= TrapFrame
->Ecx
;
460 Context
->Eax
= TrapFrame
->Eax
;
461 Context
->Ebp
= TrapFrame
->Ebp
;
462 Context
->Eip
= TrapFrame
->Eip
;
463 Context
->SegCs
= TrapFrame
->Cs
;
464 Context
->EFlags
= TrapFrame
->Eflags
;
465 Context
->Esp
= TrapFrame
->Esp
;
466 Context
->SegSs
= TrapFrame
->Ss
;
469 * Setup the trap frame so the thread will start executing at the
470 * APC Dispatcher when it returns to user-mode
472 Esp
= (PULONG
)(((PUCHAR
)TrapFrame
->Esp
) - (sizeof(CONTEXT
) + (6 * sizeof(ULONG
))));
474 Esp
[1] = (ULONG
)NormalRoutine
;
475 Esp
[2] = (ULONG
)NormalContext
;
476 Esp
[3] = (ULONG
)SystemArgument1
;
477 Esp
[4] = (ULONG
)SystemArgument2
;
478 Esp
[5] = (ULONG
)Context
;
479 TrapFrame
->Eip
= (ULONG
)LdrpGetSystemDllApcDispatcher();
480 TrapFrame
->Esp
= (ULONG
)Esp
;
492 return KeGetCurrentThread()->KernelApcDisable
? TRUE
: FALSE
;
497 NtQueueApcThread(HANDLE ThreadHandle
,
498 PKNORMAL_ROUTINE ApcRoutine
,
500 PVOID SystemArgument1
,
501 PVOID SystemArgument2
)
504 * This function is used to queue an APC from user-mode for the specified thread.
505 * The thread must enter an alertable wait before the APC will be delivered.
508 * Thread Handle - Handle to the Thread. This handle must have THREAD_SET_CONTEXT privileges.
509 * ApcRoutine - Pointer to the APC Routine to call when the APC executes.
510 * NormalContext - User-defined value to pass to the APC Routine
511 * SystemArgument1 - User-defined value to pass to the APC Routine
512 * SystemArgument2 - User-defined value to pass to the APC Routine
514 * RETURNS: NTSTATUS SUCCESS or Failure Code from included calls.
520 KPROCESSOR_MODE PreviousMode
;
523 PreviousMode
= ExGetPreviousMode();
525 /* Get ETHREAD from Handle */
526 Status
= ObReferenceObjectByHandle(ThreadHandle
,
533 /* Fail if the Handle is invalid for some reason */
534 if (!NT_SUCCESS(Status
)) {
538 /* If this is a Kernel or System Thread, then fail */
539 if (Thread
->Tcb
.Teb
== NULL
) {
540 ObDereferenceObject(Thread
);
541 return STATUS_INVALID_HANDLE
;
544 /* Allocate an APC */
545 Apc
= ExAllocatePoolWithTag(NonPagedPool
, sizeof(KAPC
), TAG_KAPC
);
547 ObDereferenceObject(Thread
);
548 return(STATUS_NO_MEMORY
);
551 /* Initialize and Queue a user mode apc (always!) */
554 OriginalApcEnvironment
,
560 if (!KeInsertQueueApc(Apc
, SystemArgument1
, SystemArgument2
, IO_NO_INCREMENT
)) {
561 Status
= STATUS_UNSUCCESSFUL
;
563 Status
= STATUS_SUCCESS
;
566 /* Dereference Thread and Return */
567 ObDereferenceObject(Thread
);
572 static inline VOID
RepairList(PLIST_ENTRY Original
,
574 KPROCESSOR_MODE Mode
)
576 /* Copy Source to Desination */
577 if (IsListEmpty(&Original
[(int)Mode
])) {
578 InitializeListHead(&Copy
[(int)Mode
]);
580 Copy
[(int)Mode
].Flink
= Original
[(int)Mode
].Flink
;
581 Copy
[(int)Mode
].Blink
= Original
[(int)Mode
].Blink
;
582 Original
[(int)Mode
].Flink
->Blink
= &Copy
[(int)Mode
];
583 Original
[(int)Mode
].Blink
->Flink
= &Copy
[(int)Mode
];
589 KiMoveApcState (PKAPC_STATE OldState
,
590 PKAPC_STATE NewState
)
592 /* Restore backup of Original Environment */
593 *NewState
= *OldState
;
596 RepairList(NewState
->ApcListHead
, OldState
->ApcListHead
, KernelMode
);
597 RepairList(NewState
->ApcListHead
, OldState
->ApcListHead
, UserMode
);