xref: /reactos/ntoskrnl/ke/apc.c (revision 34593d93)
1 /*
2  * PROJECT:         ReactOS Kernel
3  * LICENSE:         GPL - See COPYING in the top level directory
4  * FILE:            ntoskrnl/ke/apc.c
5  * PURPOSE:         Implements the Asynchronous Procedure Call mechanism
6  * PROGRAMMERS:     Alex Ionescu (alex.ionescu@reactos.org)
7  */
8 
9 /* INCLUDES *****************************************************************/
10 
11 #include <ntoskrnl.h>
12 #define NDEBUG
13 #include <debug.h>
14 
15 /* PRIVATE FUNCTIONS *********************************************************/
16 
17 /*++
18  * @name KiCheckForKernelApcDelivery
19  * @implemented NT 5.2
20  *
21  *     The KiCheckForKernelApcDelivery routine is called whenever APCs have
22  *     just been re-enabled in Kernel Mode, such as after leaving a Critical or
23  *     Guarded Region. It delivers APCs if the environment is right.
24  *
25  * @param None.
26  *
27  * @return None.
28  *
29  * @remarks This routine allows KeLeave/EnterCritical/GuardedRegion to be used
30  *          as macros from inside WIN32K or other Drivers, which will then only
31  *          have to do an Import API call in the case where APCs are enabled again.
32  *
33  *--*/
34 VOID
35 NTAPI
KiCheckForKernelApcDelivery(VOID)36 KiCheckForKernelApcDelivery(VOID)
37 {
38     KIRQL OldIrql;
39 
40     /* We should only deliver at passive */
41     if (KeGetCurrentIrql() == PASSIVE_LEVEL)
42     {
43         /* Raise to APC and Deliver APCs, then lower back to Passive */
44         KeRaiseIrql(APC_LEVEL, &OldIrql);
45         KiDeliverApc(KernelMode, 0, 0);
46         KeLowerIrql(PASSIVE_LEVEL);
47     }
48     else
49     {
50         /*
51          * If we're not at passive level it means someone raised IRQL
52          * to APC level before the critical or guarded section was entered
53          * (e.g) by a fast mutex). This implies that the APCs shouldn't
54          * be delivered now, but after the IRQL is lowered to passive
55          * level again.
56          */
57         KeGetCurrentThread()->ApcState.KernelApcPending = TRUE;
58         HalRequestSoftwareInterrupt(APC_LEVEL);
59     }
60 }
61 
62 /*++
63  * @name KiInsertQueueApc
64  *
65  *     The KiInsertQueueApc routine queues a APC for execution when the right
66  *     scheduler environment exists.
67  *
68  * @param Apc
69  *        Pointer to an initialized control object of type APC for which the
70  *        caller provides the storage.
71  *
72  * @param PriorityBoost
73  *        Priority Boost to apply to the Thread.
74  *
75  * @return None
76  *
77  * @remarks The APC will execute at APC_LEVEL for the KernelRoutine registered,
78  *          and at PASSIVE_LEVEL for the NormalRoutine registered.
79  *
80  *          Callers of this routine must have locked the dipatcher database.
81  *
82  *--*/
83 VOID
84 FASTCALL
KiInsertQueueApc(IN PKAPC Apc,IN KPRIORITY PriorityBoost)85 KiInsertQueueApc(IN PKAPC Apc,
86                  IN KPRIORITY PriorityBoost)
87 {
88     PKTHREAD Thread = Apc->Thread;
89     PKAPC_STATE ApcState;
90     KPROCESSOR_MODE ApcMode;
91     PLIST_ENTRY ListHead, NextEntry;
92     PKAPC QueuedApc;
93     PKGATE Gate;
94     NTSTATUS Status;
95     BOOLEAN RequestInterrupt = FALSE;
96 
97     /*
98      * Check if the caller wanted this APC to use the thread's environment at
99      * insertion time.
100      */
101     if (Apc->ApcStateIndex == InsertApcEnvironment)
102     {
103         /* Copy it over */
104         Apc->ApcStateIndex = Thread->ApcStateIndex;
105     }
106 
107     /* Get the APC State for this Index, and the mode too */
108     ApcState = Thread->ApcStatePointer[(UCHAR)Apc->ApcStateIndex];
109     ApcMode = Apc->ApcMode;
110 
111     /* The APC must be "inserted" already */
112     ASSERT(Apc->Inserted == TRUE);
113 
114     /* Three scenarios:
115      * 1) Kernel APC with Normal Routine or User APC = Put it at the end of the List
116      * 2) User APC which is PsExitSpecialApc = Put it at the front of the List
117      * 3) Kernel APC without Normal Routine = Put it at the end of the No-Normal Routine Kernel APC list
118      */
119     if (Apc->NormalRoutine)
120     {
121         /* Normal APC; is it the Thread Termination APC? */
122         if ((ApcMode != KernelMode) &&
123             (Apc->KernelRoutine == PsExitSpecialApc))
124         {
125             /* Set User APC pending to true */
126             Thread->ApcState.UserApcPending = TRUE;
127 
128             /* Insert it at the top of the list */
129             InsertHeadList(&ApcState->ApcListHead[ApcMode],
130                            &Apc->ApcListEntry);
131         }
132         else
133         {
134             /* Regular user or kernel Normal APC */
135             InsertTailList(&ApcState->ApcListHead[ApcMode],
136                            &Apc->ApcListEntry);
137         }
138     }
139     else
140     {
141         /* Special APC, find the last one in the list */
142         ListHead = &ApcState->ApcListHead[ApcMode];
143         NextEntry = ListHead->Blink;
144         while (NextEntry != ListHead)
145         {
146             /* Get the APC */
147             QueuedApc = CONTAINING_RECORD(NextEntry, KAPC, ApcListEntry);
148 
149             /* Is this a No-Normal APC? If so, break */
150             if (!QueuedApc->NormalRoutine) break;
151 
152             /* Move to the previous APC in the Queue */
153             NextEntry = NextEntry->Blink;
154         }
155 
156         /* Insert us here */
157         InsertHeadList(NextEntry, &Apc->ApcListEntry);
158     }
159 
160     /* Now check if the Apc State Indexes match */
161     if (Thread->ApcStateIndex == Apc->ApcStateIndex)
162     {
163         /* Check that the thread matches */
164         if (Thread == KeGetCurrentThread())
165         {
166             /* Sanity check */
167             ASSERT(Thread->State == Running);
168 
169             /* Check if this is kernel mode */
170             if (ApcMode == KernelMode)
171             {
172                 /* All valid, a Kernel APC is pending now */
173                 Thread->ApcState.KernelApcPending = TRUE;
174 
175                 /* Check if Special APCs are disabled */
176                 if (!Thread->SpecialApcDisable)
177                 {
178                     /* They're not, so request the interrupt */
179                     HalRequestSoftwareInterrupt(APC_LEVEL);
180                 }
181             }
182         }
183         else
184         {
185             /* Acquire the dispatcher lock */
186             KiAcquireDispatcherLock();
187 
188             /* Check if this is a kernel-mode APC */
189             if (ApcMode == KernelMode)
190             {
191                 /* Kernel-mode APC, set us pending */
192                 Thread->ApcState.KernelApcPending = TRUE;
193 
194                 /* Are we currently running? */
195                 if (Thread->State == Running)
196                 {
197                     /* The thread is running, so remember to send a request */
198                     RequestInterrupt = TRUE;
199                 }
200                 else if ((Thread->State == Waiting) &&
201                          (Thread->WaitIrql == PASSIVE_LEVEL) &&
202                          !(Thread->SpecialApcDisable) &&
203                          (!(Apc->NormalRoutine) ||
204                           (!(Thread->KernelApcDisable) &&
205                            !(Thread->ApcState.KernelApcInProgress))))
206                 {
207                     /* We'll unwait with this status */
208                     Status = STATUS_KERNEL_APC;
209 
210                     /* Wake up the thread */
211                     KiUnwaitThread(Thread, Status, PriorityBoost);
212                 }
213                 else if (Thread->State == GateWait)
214                 {
215                     /* Lock the thread */
216                     KiAcquireThreadLock(Thread);
217 
218                     /* Essentially do the same check as above */
219                     if ((Thread->State == GateWait) &&
220                         (Thread->WaitIrql == PASSIVE_LEVEL) &&
221                         !(Thread->SpecialApcDisable) &&
222                         (!(Apc->NormalRoutine) ||
223                          (!(Thread->KernelApcDisable) &&
224                           !(Thread->ApcState.KernelApcInProgress))))
225                     {
226                         /* We were in a gate wait. Handle this. */
227                         DPRINT1("A thread was in a gate wait\n");
228 
229                         /* Get the gate */
230                         Gate = Thread->GateObject;
231 
232                         /* Lock the gate */
233                         KiAcquireDispatcherObject(&Gate->Header);
234 
235                         /* Remove it from the waiters list */
236                         RemoveEntryList(&Thread->WaitBlock[0].WaitListEntry);
237 
238                         /* Unlock the gate */
239                         KiReleaseDispatcherObject(&Gate->Header);
240 
241                         /* Increase the queue counter if needed */
242                         if (Thread->Queue) Thread->Queue->CurrentCount++;
243 
244                         /* Put into deferred ready list with this status */
245                         Thread->WaitStatus = STATUS_KERNEL_APC;
246                         KiInsertDeferredReadyList(Thread);
247                     }
248 
249                     /* Release the thread lock */
250                     KiReleaseThreadLock(Thread);
251                 }
252             }
253             else if ((Thread->State == Waiting) &&
254                      (Thread->WaitMode == UserMode) &&
255                      ((Thread->Alertable) ||
256                       (Thread->ApcState.UserApcPending)))
257             {
258                 /* Set user-mode APC pending */
259                 Thread->ApcState.UserApcPending = TRUE;
260                 Status = STATUS_USER_APC;
261 
262                 /* Wake up the thread */
263                 KiUnwaitThread(Thread, Status, PriorityBoost);
264             }
265 
266             /* Release dispatcher lock */
267             KiReleaseDispatcherLockFromSynchLevel();
268 
269             /* Check if an interrupt was requested */
270             KiRequestApcInterrupt(RequestInterrupt, Thread->NextProcessor);
271         }
272     }
273 }
274 
275 /*++
276  * @name KiDeliverApc
277  * @implemented @NT4
278  *
279  *     The KiDeliverApc routine is called from IRQL switching code if the
280  *     thread is returning from an IRQL >= APC_LEVEL and Kernel-Mode APCs are
281  *     pending.
282  *
283  * @param DeliveryMode
284  *        Specifies the current processor mode.
285  *
286  * @param ExceptionFrame
287  *        Pointer to the Exception Frame on non-i386 builds.
288  *
289  * @param TrapFrame
290  *        Pointer to the Trap Frame.
291  *
292  * @return None.
293  *
294  * @remarks First, Special APCs are delivered, followed by Kernel-Mode APCs and
295  *          User-Mode APCs. Note that the TrapFrame is only valid if the
296  *          delivery mode is User-Mode.
297  *          Upon entry, this routine executes at APC_LEVEL.
298  *
299  *--*/
300 VOID
301 NTAPI
KiDeliverApc(IN KPROCESSOR_MODE DeliveryMode,IN PKEXCEPTION_FRAME ExceptionFrame,IN PKTRAP_FRAME TrapFrame)302 KiDeliverApc(IN KPROCESSOR_MODE DeliveryMode,
303              IN PKEXCEPTION_FRAME ExceptionFrame,
304              IN PKTRAP_FRAME TrapFrame)
305 {
306     PKTHREAD Thread = KeGetCurrentThread();
307     PKPROCESS Process = Thread->ApcState.Process;
308     PKTRAP_FRAME OldTrapFrame;
309     PLIST_ENTRY ApcListEntry;
310     PKAPC Apc;
311     KLOCK_QUEUE_HANDLE ApcLock;
312     PKKERNEL_ROUTINE KernelRoutine;
313     PVOID NormalContext;
314     PKNORMAL_ROUTINE NormalRoutine;
315     PVOID SystemArgument1;
316     PVOID SystemArgument2;
317     ASSERT_IRQL_EQUAL(APC_LEVEL);
318 
319     /* Save the old trap frame and set current one */
320     OldTrapFrame = Thread->TrapFrame;
321     Thread->TrapFrame = TrapFrame;
322 
323     /* Clear Kernel APC Pending */
324     Thread->ApcState.KernelApcPending = FALSE;
325 
326     /* Check if Special APCs are disabled */
327     if (Thread->SpecialApcDisable) goto Quickie;
328 
329     /* Do the Kernel APCs first */
330     while (!IsListEmpty(&Thread->ApcState.ApcListHead[KernelMode]))
331     {
332         /* Lock the APC Queue */
333         KiAcquireApcLockRaiseToDpc(Thread, &ApcLock);
334 
335         /* Check if the list became empty now */
336         if (IsListEmpty(&Thread->ApcState.ApcListHead[KernelMode]))
337         {
338             /* It is, release the lock and break out */
339             KiReleaseApcLock(&ApcLock);
340             break;
341         }
342 
343         /* Kernel APC is not pending anymore */
344         Thread->ApcState.KernelApcPending = FALSE;
345 
346         /* Get the next Entry */
347         ApcListEntry = Thread->ApcState.ApcListHead[KernelMode].Flink;
348         Apc = CONTAINING_RECORD(ApcListEntry, KAPC, ApcListEntry);
349 
350         /* Save Parameters so that it's safe to free the Object in the Kernel Routine*/
351         NormalRoutine = Apc->NormalRoutine;
352         KernelRoutine = Apc->KernelRoutine;
353         NormalContext = Apc->NormalContext;
354         SystemArgument1 = Apc->SystemArgument1;
355         SystemArgument2 = Apc->SystemArgument2;
356 
357         /* Special APC */
358         if (!NormalRoutine)
359         {
360             /* Remove the APC from the list */
361             RemoveEntryList(ApcListEntry);
362             Apc->Inserted = FALSE;
363 
364             /* Release the APC lock */
365             KiReleaseApcLock(&ApcLock);
366 
367             /* Call the Special APC */
368             KernelRoutine(Apc,
369                           &NormalRoutine,
370                           &NormalContext,
371                           &SystemArgument1,
372                           &SystemArgument2);
373 
374             /* Make sure it returned correctly */
375             if (KeGetCurrentIrql() != ApcLock.OldIrql)
376             {
377                 KeBugCheckEx(IRQL_UNEXPECTED_VALUE,
378                              (KeGetCurrentIrql() << 16) |
379                              (ApcLock.OldIrql << 8),
380                              (ULONG_PTR)KernelRoutine,
381                              (ULONG_PTR)Apc,
382                              (ULONG_PTR)NormalRoutine);
383             }
384         }
385         else
386         {
387             /* Normal Kernel APC, make sure it's safe to deliver */
388             if ((Thread->ApcState.KernelApcInProgress) ||
389                 (Thread->KernelApcDisable))
390             {
391                 /* Release lock and return */
392                 KiReleaseApcLock(&ApcLock);
393                 goto Quickie;
394             }
395 
396             /* Dequeue the APC */
397             RemoveEntryList(ApcListEntry);
398             Apc->Inserted = FALSE;
399 
400             /* Go back to APC_LEVEL */
401             KiReleaseApcLock(&ApcLock);
402 
403             /* Call the Kernel APC */
404             KernelRoutine(Apc,
405                           &NormalRoutine,
406                           &NormalContext,
407                           &SystemArgument1,
408                           &SystemArgument2);
409 
410             /* Make sure it returned correctly */
411             if (KeGetCurrentIrql() != ApcLock.OldIrql)
412             {
413                 KeBugCheckEx(IRQL_UNEXPECTED_VALUE,
414                              (KeGetCurrentIrql() << 16) |
415                              (ApcLock.OldIrql << 8),
416                              (ULONG_PTR)KernelRoutine,
417                              (ULONG_PTR)Apc,
418                              (ULONG_PTR)NormalRoutine);
419             }
420 
421             /* Check if there still is a Normal Routine */
422             if (NormalRoutine)
423             {
424                 /* At Passive Level, an APC can be prempted by a Special APC */
425                 Thread->ApcState.KernelApcInProgress = TRUE;
426                 KeLowerIrql(PASSIVE_LEVEL);
427 
428                 /* Call and Raise IRQL back to APC_LEVEL */
429                 NormalRoutine(NormalContext, SystemArgument1, SystemArgument2);
430                 KeRaiseIrql(APC_LEVEL, &ApcLock.OldIrql);
431             }
432 
433             /* Set Kernel APC in progress to false and loop again */
434             Thread->ApcState.KernelApcInProgress = FALSE;
435         }
436     }
437 
438     /* Now we do the User APCs */
439     if ((DeliveryMode == UserMode) &&
440         !(IsListEmpty(&Thread->ApcState.ApcListHead[UserMode])) &&
441          (Thread->ApcState.UserApcPending))
442     {
443         /* Lock the APC Queue */
444         KiAcquireApcLockRaiseToDpc(Thread, &ApcLock);
445 
446         /* It's not pending anymore */
447         Thread->ApcState.UserApcPending = FALSE;
448 
449         /* Check if the list became empty now */
450         if (IsListEmpty(&Thread->ApcState.ApcListHead[UserMode]))
451         {
452             /* It is, release the lock and break out */
453             KiReleaseApcLock(&ApcLock);
454             goto Quickie;
455         }
456 
457         /* Get the actual APC object */
458         ApcListEntry = Thread->ApcState.ApcListHead[UserMode].Flink;
459         Apc = CONTAINING_RECORD(ApcListEntry, KAPC, ApcListEntry);
460 
461         /* Save Parameters so that it's safe to free the Object in the Kernel Routine*/
462         NormalRoutine = Apc->NormalRoutine;
463         KernelRoutine = Apc->KernelRoutine;
464         NormalContext = Apc->NormalContext;
465         SystemArgument1 = Apc->SystemArgument1;
466         SystemArgument2 = Apc->SystemArgument2;
467 
468         /* Remove the APC from Queue, and release the lock */
469         RemoveEntryList(ApcListEntry);
470         Apc->Inserted = FALSE;
471         KiReleaseApcLock(&ApcLock);
472 
473         /* Call the kernel routine */
474         KernelRoutine(Apc,
475                       &NormalRoutine,
476                       &NormalContext,
477                       &SystemArgument1,
478                       &SystemArgument2);
479 
480         /* Check if there's no normal routine */
481         if (!NormalRoutine)
482         {
483             /* Check if more User APCs are Pending */
484             KeTestAlertThread(UserMode);
485         }
486         else
487         {
488             /* Set up the Trap Frame and prepare for Execution in NTDLL.DLL */
489             KiInitializeUserApc(ExceptionFrame,
490                                 TrapFrame,
491                                 NormalRoutine,
492                                 NormalContext,
493                                 SystemArgument1,
494                                 SystemArgument2);
495         }
496     }
497 
498 Quickie:
499     /* Make sure we're still in the same process */
500     if (Process != Thread->ApcState.Process)
501     {
502         /* Erm, we got attached or something! BAD! */
503         KeBugCheckEx(INVALID_PROCESS_ATTACH_ATTEMPT,
504                      (ULONG_PTR)Process,
505                      (ULONG_PTR)Thread->ApcState.Process,
506                      Thread->ApcStateIndex,
507                      KeGetCurrentPrcb()->DpcRoutineActive);
508     }
509 
510     /* Restore the trap frame */
511     Thread->TrapFrame = OldTrapFrame;
512 }
513 
514 FORCEINLINE
515 VOID
RepairList(IN PLIST_ENTRY Original,IN PLIST_ENTRY Copy,IN KPROCESSOR_MODE Mode)516 RepairList(IN PLIST_ENTRY Original,
517            IN PLIST_ENTRY Copy,
518            IN KPROCESSOR_MODE Mode)
519 {
520     /* Check if the list for this mode is empty */
521     if (IsListEmpty(&Original[Mode]))
522     {
523         /* It is, all we need to do is initialize it */
524         InitializeListHead(&Copy[Mode]);
525     }
526     else
527     {
528         /* Copy the lists */
529         Copy[Mode].Flink = Original[Mode].Flink;
530         Copy[Mode].Blink = Original[Mode].Blink;
531         Original[Mode].Flink->Blink = &Copy[Mode];
532         Original[Mode].Blink->Flink = &Copy[Mode];
533     }
534 }
535 
536 VOID
537 NTAPI
KiMoveApcState(PKAPC_STATE OldState,PKAPC_STATE NewState)538 KiMoveApcState(PKAPC_STATE OldState,
539                PKAPC_STATE NewState)
540 {
541     /* Restore backup of Original Environment */
542     RtlCopyMemory(NewState, OldState, KAPC_STATE_ACTUAL_LENGTH);
543 
544     /* Repair Lists */
545     RepairList(OldState->ApcListHead, NewState->ApcListHead, KernelMode);
546     RepairList(OldState->ApcListHead, NewState->ApcListHead, UserMode);
547 }
548 
549 /* PUBLIC FUNCTIONS **********************************************************/
550 
551 /*++
552  * @name KeEnterCriticalRegion
553  * @implemented NT4
554  *
555  *     The KeEnterCriticalRegion routine temporarily disables the delivery of
556  *     normal kernel APCs; special kernel-mode APCs are still delivered.
557  *
558  * @param None.
559  *
560  * @return None.
561  *
562  * @remarks Highest-level drivers can call this routine while running in the
563  *          context of the thread that requested the current I/O operation.
564  *          Any caller of this routine should call KeLeaveCriticalRegion as
565  *          quickly as possible.
566  *
567  *          Callers of KeEnterCriticalRegion must be running at IRQL <=
568  *          APC_LEVEL.
569  *
570  *--*/
571 VOID
572 NTAPI
_KeEnterCriticalRegion(VOID)573 _KeEnterCriticalRegion(VOID)
574 {
575     /* Use inlined function */
576     KeEnterCriticalRegion();
577 }
578 
579 /*++
580  * KeLeaveCriticalRegion
581  * @implemented NT4
582  *
583  *     The KeLeaveCriticalRegion routine reenables the delivery of normal
584  *     kernel-mode APCs that were disabled by a call to KeEnterCriticalRegion.
585  *
586  * @param None.
587  *
588  * @return None.
589  *
590  * @remarks Highest-level drivers can call this routine while running in the
591  *          context of the thread that requested the current I/O operation.
592  *
593  *          Callers of KeLeaveCriticalRegion must be running at IRQL <=
594  *          DISPATCH_LEVEL.
595  *
596  *--*/
597 VOID
598 NTAPI
_KeLeaveCriticalRegion(VOID)599 _KeLeaveCriticalRegion(VOID)
600 {
601     /* Use inlined version */
602     KeLeaveCriticalRegion();
603 }
604 
605 /*++
606  * KeInitializeApc
607  * @implemented NT4
608  *
609  *     The KeInitializeApc routine initializes an APC object, and registers
610  *     the Kernel, Rundown and Normal routines for that object.
611  *
612  * @param Apc
613  *        Pointer to a KAPC structure that represents the APC object to
614  *        initialize. The caller must allocate storage for the structure
615  *        from resident memory.
616  *
617  * @param Thread
618  *        Thread to which to deliver the APC.
619  *
620  * @param TargetEnvironment
621  *        APC Environment to be used.
622  *
623  * @param KernelRoutine
624  *        Points to the KernelRoutine to associate with the APC.
625  *        This routine is executed for all APCs.
626  *
627  * @param RundownRoutine
628  *        Points to the RundownRoutine to associate with the APC.
629  *        This routine is executed when the Thread exits during APC execution.
630  *
631  * @param NormalRoutine
632  *        Points to the NormalRoutine to associate with the APC.
633  *        This routine is executed at PASSIVE_LEVEL. If this is not specifed,
634  *        the APC becomes a Special APC and the Mode and Context parameters are
635  *        ignored.
636  *
637  * @param Mode
638  *        Specifies the processor mode at which to run the Normal Routine.
639  *
640  * @param Context
641  *        Specifices the value to pass as Context parameter to the registered
642  *        routines.
643  *
644  * @return None.
645  *
646  * @remarks The caller can queue an initialized APC with KeInsertQueueApc.
647  *
648  *--*/
649 VOID
650 NTAPI
KeInitializeApc(IN PKAPC Apc,IN PKTHREAD Thread,IN KAPC_ENVIRONMENT TargetEnvironment,IN PKKERNEL_ROUTINE KernelRoutine,IN PKRUNDOWN_ROUTINE RundownRoutine OPTIONAL,IN PKNORMAL_ROUTINE NormalRoutine,IN KPROCESSOR_MODE Mode,IN PVOID Context)651 KeInitializeApc(IN PKAPC Apc,
652                 IN PKTHREAD Thread,
653                 IN KAPC_ENVIRONMENT TargetEnvironment,
654                 IN PKKERNEL_ROUTINE KernelRoutine,
655                 IN PKRUNDOWN_ROUTINE RundownRoutine OPTIONAL,
656                 IN PKNORMAL_ROUTINE NormalRoutine,
657                 IN KPROCESSOR_MODE Mode,
658                 IN PVOID Context)
659 {
660     /* Sanity check */
661     ASSERT(TargetEnvironment <= InsertApcEnvironment);
662 
663     /* Set up the basic APC Structure Data */
664     Apc->Type = ApcObject;
665     Apc->Size = sizeof(KAPC);
666 
667     /* Set the Environment */
668     if (TargetEnvironment == CurrentApcEnvironment)
669     {
670         /* Use the current one for the thread */
671         Apc->ApcStateIndex = Thread->ApcStateIndex;
672     }
673     else
674     {
675         /* Sanity check */
676         ASSERT((TargetEnvironment <= Thread->ApcStateIndex) ||
677                (TargetEnvironment == InsertApcEnvironment));
678 
679         /* Use the one that was given */
680         Apc->ApcStateIndex = TargetEnvironment;
681     }
682 
683     /* Set the Thread and Routines */
684     Apc->Thread = Thread;
685     Apc->KernelRoutine = KernelRoutine;
686     Apc->RundownRoutine = RundownRoutine;
687     Apc->NormalRoutine = NormalRoutine;
688 
689     /* Check if this is a special APC */
690     if (NormalRoutine)
691     {
692         /* It's a normal one. Set the context and mode */
693         Apc->ApcMode = Mode;
694         Apc->NormalContext = Context;
695     }
696     else
697     {
698         /* It's a special APC, which can only be kernel mode */
699         Apc->ApcMode = KernelMode;
700         Apc->NormalContext = NULL;
701     }
702 
703     /* The APC is not inserted */
704     Apc->Inserted = FALSE;
705 }
706 
707 /*++
708  * @name KeInsertQueueApc
709  * @implemented NT4
710  *
711  *     The KeInsertQueueApc routine queues a APC for execution when the right
712  *     scheduler environment exists.
713  *
714  * @param Apc
715  *        Pointer to an initialized control object of type APC for which the
716  *        caller provides the storage.
717  *
718  * @param SystemArgument[1,2]
719  *        Pointer to a set of two parameters that contain untyped data.
720  *
721  * @param PriorityBoost
722  *        Priority Boost to apply to the Thread.
723  *
724  * @return If the APC is already inserted or APC queueing is disabled, FALSE.
725  *         Otherwise, TRUE.
726  *
727  * @remarks The APC will execute at APC_LEVEL for the KernelRoutine registered,
728  *          and at PASSIVE_LEVEL for the NormalRoutine registered.
729  *
730  *          Callers of this routine must be running at IRQL <= DISPATCH_LEVEL.
731  *
732  *--*/
733 BOOLEAN
734 NTAPI
KeInsertQueueApc(IN PKAPC Apc,IN PVOID SystemArgument1,IN PVOID SystemArgument2,IN KPRIORITY PriorityBoost)735 KeInsertQueueApc(IN PKAPC Apc,
736                  IN PVOID SystemArgument1,
737                  IN PVOID SystemArgument2,
738                  IN KPRIORITY PriorityBoost)
739 {
740     PKTHREAD Thread = Apc->Thread;
741     KLOCK_QUEUE_HANDLE ApcLock;
742     BOOLEAN State = TRUE;
743     ASSERT_APC(Apc);
744     ASSERT_IRQL_LESS_OR_EQUAL(DISPATCH_LEVEL);
745 
746     /* Get the APC lock */
747     KiAcquireApcLockRaiseToSynch(Thread, &ApcLock);
748 
749     /* Make sure we can Queue APCs and that this one isn't already inserted */
750     if (!(Thread->ApcQueueable) || (Apc->Inserted))
751     {
752         /* Fail */
753         State = FALSE;
754     }
755     else
756     {
757         /* Set the System Arguments and set it as inserted */
758         Apc->SystemArgument1 = SystemArgument1;
759         Apc->SystemArgument2 = SystemArgument2;
760         Apc->Inserted = TRUE;
761 
762         /* Call the Internal Function */
763         KiInsertQueueApc(Apc, PriorityBoost);
764     }
765 
766     /* Release the APC lock and return success */
767     KiReleaseApcLockFromSynchLevel(&ApcLock);
768     KiExitDispatcher(ApcLock.OldIrql);
769     return State;
770 }
771 
772 /*++
773  * @name KeFlushQueueApc
774  * @implemented NT4
775  *
776  *     The KeFlushQueueApc routine flushes all APCs of the given processor mode
777  *     from the specified Thread's APC queue.
778  *
779  * @param Thread
780  *        Pointer to the thread whose APC queue will be flushed.
781  *
782  * @param PreviousMode
783  *         Specifies which APC Queue to flush.
784  *
785  * @return A pointer to the first entry in the flushed APC queue.
786  *
787  * @remarks If the routine returns NULL, it means that no APCs were flushed.
788  *          Callers of this routine must be running at DISPATCH_LEVEL or lower.
789  *
790  *--*/
791 PLIST_ENTRY
792 NTAPI
KeFlushQueueApc(IN PKTHREAD Thread,IN KPROCESSOR_MODE PreviousMode)793 KeFlushQueueApc(IN PKTHREAD Thread,
794                 IN KPROCESSOR_MODE PreviousMode)
795 {
796     PKAPC Apc;
797     PLIST_ENTRY FirstEntry, CurrentEntry;
798     KLOCK_QUEUE_HANDLE ApcLock;
799     ASSERT_IRQL_LESS_OR_EQUAL(DISPATCH_LEVEL);
800 
801     /* Check if this was user mode */
802     if (PreviousMode == UserMode)
803     {
804         /* Get the APC lock */
805         KiAcquireApcLockRaiseToSynch(Thread, &ApcLock);
806 
807         /* Select user list and check if it's empty */
808         if (IsListEmpty(&Thread->ApcState.ApcListHead[UserMode]))
809         {
810             /* Don't return anything */
811             FirstEntry = NULL;
812             goto FlushDone;
813         }
814     }
815     else
816     {
817         /* Select kernel list and check if it's empty */
818         if (IsListEmpty( &Thread->ApcState.ApcListHead[KernelMode]))
819         {
820             /* Don't return anything */
821             return NULL;
822         }
823 
824         /* Otherwise, acquire the APC lock */
825         KiAcquireApcLockRaiseToSynch(Thread, &ApcLock);
826     }
827 
828     /* Get the first entry and check if the list is empty now */
829     FirstEntry = Thread->ApcState.ApcListHead[PreviousMode].Flink;
830     if (FirstEntry == &Thread->ApcState.ApcListHead[PreviousMode])
831     {
832         /* It is, clear the returned entry */
833         FirstEntry = NULL;
834     }
835     else
836     {
837         /* It's not, remove the first entry */
838         RemoveEntryList(&Thread->ApcState.ApcListHead[PreviousMode]);
839 
840         /* Loop all the entries */
841         CurrentEntry = FirstEntry;
842         do
843         {
844             /* Get the APC and make it un-inserted */
845             Apc = CONTAINING_RECORD(CurrentEntry, KAPC, ApcListEntry);
846             Apc->Inserted = FALSE;
847 
848             /* Get the next entry */
849             CurrentEntry = CurrentEntry->Flink;
850         } while (CurrentEntry != FirstEntry);
851 
852         /* Re-initialize the list */
853         InitializeListHead(&Thread->ApcState.ApcListHead[PreviousMode]);
854     }
855 
856     /* Release the lock */
857 FlushDone:
858     KiReleaseApcLock(&ApcLock);
859 
860     /* Return the first entry */
861     return FirstEntry;
862 }
863 
864 /*++
865  * @name KeRemoveQueueApc
866  * @implemented NT4
867  *
868  *     The KeRemoveQueueApc routine removes a given APC object from the system
869  *     APC queue.
870  *
871  * @param Apc
872  *         Pointer to an initialized APC object that was queued by calling
873  *         KeInsertQueueApc.
874  *
875  * @return TRUE if the APC Object is in the APC Queue. Otherwise, no operation
876  *         is performed and FALSE is returned.
877  *
878  * @remarks If the given APC Object is currently queued, it is removed from the
879  *          queue and any calls to the registered routines are cancelled.
880  *
881  *          Callers of this routine must be running at IRQL <= DISPATCH_LEVEL.
882  *
883  *--*/
884 BOOLEAN
885 NTAPI
KeRemoveQueueApc(IN PKAPC Apc)886 KeRemoveQueueApc(IN PKAPC Apc)
887 {
888     PKTHREAD Thread = Apc->Thread;
889     PKAPC_STATE ApcState;
890     BOOLEAN Inserted;
891     KLOCK_QUEUE_HANDLE ApcLock;
892     ASSERT_APC(Apc);
893     ASSERT_IRQL_LESS_OR_EQUAL(DISPATCH_LEVEL);
894 
895     /* Get the APC lock (this raises IRQL to SYNCH_LEVEL) */
896     KiAcquireApcLockRaiseToSynch(Thread, &ApcLock);
897 
898     /* Check if it's inserted */
899     Inserted = Apc->Inserted;
900     if (Inserted)
901     {
902         /* Set it as non-inserted and get the APC state */
903         Apc->Inserted = FALSE;
904         ApcState = Thread->ApcStatePointer[(UCHAR)Apc->ApcStateIndex];
905 
906         /* Acquire the dispatcher lock and remove it from the list */
907         KiAcquireDispatcherLockAtSynchLevel();
908         if (RemoveEntryList(&Apc->ApcListEntry))
909         {
910             /* Set the correct state based on the APC Mode */
911             if (Apc->ApcMode == KernelMode)
912             {
913                 /* No more pending kernel APCs */
914                 ApcState->KernelApcPending = FALSE;
915             }
916             else
917             {
918                 /* No more pending user APCs */
919                 ApcState->UserApcPending = FALSE;
920             }
921         }
922 
923         /* Release dispatcher lock */
924         KiReleaseDispatcherLockFromSynchLevel();
925     }
926 
927     /* Release the lock and return */
928     KiReleaseApcLock(&ApcLock);
929     return Inserted;
930 }
931 
932 /*++
933  * @name KeAreApcsDisabled
934  * @implemented NT4
935  *
936  *     The KeAreApcsDisabled routine returns whether kernel APC delivery is
937  *     disabled for the current thread.
938  *
939  * @param None.
940  *
941  * @return KeAreApcsDisabled returns TRUE if the thread is within a critical
942  *         region or a guarded region, and FALSE otherwise.
943  *
944  * @remarks A thread running at IRQL = PASSIVE_LEVEL can use KeAreApcsDisabled
945  *          to determine if normal kernel APCs are disabled.
946  *
947  *          A thread that is inside critical region has both user APCs and
948  *          normal kernel APCs disabled, but not special kernel APCs.
949  *
950  *          A thread that is inside a guarded region has all APCs disabled,
951  *          including special kernel APCs.
952  *
953  *          Callers of this routine must be running at IRQL <= DISPATCH_LEVEL.
954  *
955  *--*/
956 BOOLEAN
957 NTAPI
KeAreApcsDisabled(VOID)958 KeAreApcsDisabled(VOID)
959 {
960     /* Return the Kernel APC State */
961     return KeGetCurrentThread()->CombinedApcDisable ? TRUE : FALSE;
962 }
963 
964 /*++
965  * @name KeAreAllApcsDisabled
966  * @implemented NT5.1
967  *
968  *    The KeAreAllApcsDisabled routine returns whether the calling thread is
969  *    inside a guarded region or running at IRQL >= APC_LEVEL, which disables
970  *    all APC delivery.
971  *
972  * @param None.
973  *
974  * @return KeAreAllApcsDisabled returns TRUE if the thread is within a guarded
975  *         guarded region or running at IRQL >= APC_LEVEL, and FALSE otherwise.
976  *
977  * @remarks A thread running at IRQL = PASSIVE_LEVEL can use this routine to
978  *          determine if all APC delivery is disabled.
979  *
980  *          Callers of this routine must be running at IRQL <= DISPATCH_LEVEL.
981  *
982  *--*/
983 BOOLEAN
984 NTAPI
KeAreAllApcsDisabled(VOID)985 KeAreAllApcsDisabled(VOID)
986 {
987     /* Return the Special APC State */
988     return ((KeGetCurrentThread()->SpecialApcDisable) ||
989             (KeGetCurrentIrql() >= APC_LEVEL)) ? TRUE : FALSE;
990 }
991