reactos/ntoskrnl/ke/apc.c

991 lines
30 KiB
C

/*
* PROJECT: ReactOS Kernel
* LICENSE: GPL - See COPYING in the top level directory
* FILE: ntoskrnl/ke/apc.c
* PURPOSE: Implements the Asynchronous Procedure Call mechanism
* PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org)
*/
/* INCLUDES *****************************************************************/
#include <ntoskrnl.h>
#define NDEBUG
#include <debug.h>
/* PRIVATE FUNCTIONS *********************************************************/
/*++
* @name KiCheckForKernelApcDelivery
* @implemented NT 5.2
*
* The KiCheckForKernelApcDelivery routine is called whenever APCs have
* just been re-enabled in Kernel Mode, such as after leaving a Critical or
* Guarded Region. It delivers APCs if the environment is right.
*
* @param None.
*
* @return None.
*
* @remarks This routine allows KeLeave/EnterCritical/GuardedRegion to be used
* as macros from inside WIN32K or other Drivers, which will then only
* have to do an Import API call in the case where APCs are enabled again.
*
*--*/
VOID
NTAPI
KiCheckForKernelApcDelivery(VOID)
{
KIRQL OldIrql;
/* We should only deliver at passive */
if (KeGetCurrentIrql() == PASSIVE_LEVEL)
{
/* Raise to APC and Deliver APCs, then lower back to Passive */
KeRaiseIrql(APC_LEVEL, &OldIrql);
KiDeliverApc(KernelMode, 0, 0);
KeLowerIrql(PASSIVE_LEVEL);
}
else
{
/*
* If we're not at passive level it means someone raised IRQL
* to APC level before the critical or guarded section was entered
* (e.g) by a fast mutex). This implies that the APCs shouldn't
* be delivered now, but after the IRQL is lowered to passive
* level again.
*/
KeGetCurrentThread()->ApcState.KernelApcPending = TRUE;
HalRequestSoftwareInterrupt(APC_LEVEL);
}
}
/*++
* @name KiInsertQueueApc
*
* The KiInsertQueueApc routine queues a APC for execution when the right
* scheduler environment exists.
*
* @param Apc
* Pointer to an initialized control object of type APC for which the
* caller provides the storage.
*
* @param PriorityBoost
* Priority Boost to apply to the Thread.
*
* @return None
*
* @remarks The APC will execute at APC_LEVEL for the KernelRoutine registered,
* and at PASSIVE_LEVEL for the NormalRoutine registered.
*
* Callers of this routine must have locked the dipatcher database.
*
*--*/
VOID
FASTCALL
KiInsertQueueApc(IN PKAPC Apc,
IN KPRIORITY PriorityBoost)
{
PKTHREAD Thread = Apc->Thread;
PKAPC_STATE ApcState;
KPROCESSOR_MODE ApcMode;
PLIST_ENTRY ListHead, NextEntry;
PKAPC QueuedApc;
PKGATE Gate;
NTSTATUS Status;
BOOLEAN RequestInterrupt = FALSE;
/*
* Check if the caller wanted this APC to use the thread's environment at
* insertion time.
*/
if (Apc->ApcStateIndex == InsertApcEnvironment)
{
/* Copy it over */
Apc->ApcStateIndex = Thread->ApcStateIndex;
}
/* Get the APC State for this Index, and the mode too */
ApcState = Thread->ApcStatePointer[(UCHAR)Apc->ApcStateIndex];
ApcMode = Apc->ApcMode;
/* The APC must be "inserted" already */
ASSERT(Apc->Inserted == TRUE);
/* Three scenarios:
* 1) Kernel APC with Normal Routine or User APC = Put it at the end of the List
* 2) User APC which is PsExitSpecialApc = Put it at the front of the List
* 3) Kernel APC without Normal Routine = Put it at the end of the No-Normal Routine Kernel APC list
*/
if (Apc->NormalRoutine)
{
/* Normal APC; is it the Thread Termination APC? */
if ((ApcMode != KernelMode) &&
(Apc->KernelRoutine == PsExitSpecialApc))
{
/* Set User APC pending to true */
Thread->ApcState.UserApcPending = TRUE;
/* Insert it at the top of the list */
InsertHeadList(&ApcState->ApcListHead[ApcMode],
&Apc->ApcListEntry);
}
else
{
/* Regular user or kernel Normal APC */
InsertTailList(&ApcState->ApcListHead[ApcMode],
&Apc->ApcListEntry);
}
}
else
{
/* Special APC, find the last one in the list */
ListHead = &ApcState->ApcListHead[ApcMode];
NextEntry = ListHead->Blink;
while (NextEntry != ListHead)
{
/* Get the APC */
QueuedApc = CONTAINING_RECORD(NextEntry, KAPC, ApcListEntry);
/* Is this a No-Normal APC? If so, break */
if (!QueuedApc->NormalRoutine) break;
/* Move to the previous APC in the Queue */
NextEntry = NextEntry->Blink;
}
/* Insert us here */
InsertHeadList(NextEntry, &Apc->ApcListEntry);
}
/* Now check if the Apc State Indexes match */
if (Thread->ApcStateIndex == Apc->ApcStateIndex)
{
/* Check that the thread matches */
if (Thread == KeGetCurrentThread())
{
/* Sanity check */
ASSERT(Thread->State == Running);
/* Check if this is kernel mode */
if (ApcMode == KernelMode)
{
/* All valid, a Kernel APC is pending now */
Thread->ApcState.KernelApcPending = TRUE;
/* Check if Special APCs are disabled */
if (!Thread->SpecialApcDisable)
{
/* They're not, so request the interrupt */
HalRequestSoftwareInterrupt(APC_LEVEL);
}
}
}
else
{
/* Acquire the dispatcher lock */
KiAcquireDispatcherLock();
/* Check if this is a kernel-mode APC */
if (ApcMode == KernelMode)
{
/* Kernel-mode APC, set us pending */
Thread->ApcState.KernelApcPending = TRUE;
/* Are we currently running? */
if (Thread->State == Running)
{
/* The thread is running, so remember to send a request */
RequestInterrupt = TRUE;
}
else if ((Thread->State == Waiting) &&
(Thread->WaitIrql == PASSIVE_LEVEL) &&
!(Thread->SpecialApcDisable) &&
(!(Apc->NormalRoutine) ||
(!(Thread->KernelApcDisable) &&
!(Thread->ApcState.KernelApcInProgress))))
{
/* We'll unwait with this status */
Status = STATUS_KERNEL_APC;
/* Wake up the thread */
KiUnwaitThread(Thread, Status, PriorityBoost);
}
else if (Thread->State == GateWait)
{
/* Lock the thread */
KiAcquireThreadLock(Thread);
/* Essentially do the same check as above */
if ((Thread->State == GateWait) &&
(Thread->WaitIrql == PASSIVE_LEVEL) &&
!(Thread->SpecialApcDisable) &&
(!(Apc->NormalRoutine) ||
(!(Thread->KernelApcDisable) &&
!(Thread->ApcState.KernelApcInProgress))))
{
/* We were in a gate wait. Handle this. */
DPRINT1("A thread was in a gate wait\n");
/* Get the gate */
Gate = Thread->GateObject;
/* Lock the gate */
KiAcquireDispatcherObject(&Gate->Header);
/* Remove it from the waiters list */
RemoveEntryList(&Thread->WaitBlock[0].WaitListEntry);
/* Unlock the gate */
KiReleaseDispatcherObject(&Gate->Header);
/* Increase the queue counter if needed */
if (Thread->Queue) Thread->Queue->CurrentCount++;
/* Put into deferred ready list with this status */
Thread->WaitStatus = STATUS_KERNEL_APC;
KiInsertDeferredReadyList(Thread);
}
/* Release the thread lock */
KiReleaseThreadLock(Thread);
}
}
else if ((Thread->State == Waiting) &&
(Thread->WaitMode == UserMode) &&
((Thread->Alertable) ||
(Thread->ApcState.UserApcPending)))
{
/* Set user-mode APC pending */
Thread->ApcState.UserApcPending = TRUE;
Status = STATUS_USER_APC;
/* Wake up the thread */
KiUnwaitThread(Thread, Status, PriorityBoost);
}
/* Release dispatcher lock */
KiReleaseDispatcherLockFromDpcLevel();
/* Check if an interrupt was requested */
KiRequestApcInterrupt(RequestInterrupt, Thread->NextProcessor);
}
}
}
/*++
* @name KiDeliverApc
* @implemented @NT4
*
* The KiDeliverApc routine is called from IRQL switching code if the
* thread is returning from an IRQL >= APC_LEVEL and Kernel-Mode APCs are
* pending.
*
* @param DeliveryMode
* Specifies the current processor mode.
*
* @param ExceptionFrame
* Pointer to the Exception Frame on non-i386 builds.
*
* @param TrapFrame
* Pointer to the Trap Frame.
*
* @return None.
*
* @remarks First, Special APCs are delivered, followed by Kernel-Mode APCs and
* User-Mode APCs. Note that the TrapFrame is only valid if the
* delivery mode is User-Mode.
* Upon entry, this routine executes at APC_LEVEL.
*
*--*/
VOID
NTAPI
KiDeliverApc(IN KPROCESSOR_MODE DeliveryMode,
IN PKEXCEPTION_FRAME ExceptionFrame,
IN PKTRAP_FRAME TrapFrame)
{
PKTHREAD Thread = KeGetCurrentThread();
PKPROCESS Process = Thread->ApcState.Process;
PKTRAP_FRAME OldTrapFrame;
PLIST_ENTRY ApcListEntry;
PKAPC Apc;
KLOCK_QUEUE_HANDLE ApcLock;
PKKERNEL_ROUTINE KernelRoutine;
PVOID NormalContext;
PKNORMAL_ROUTINE NormalRoutine;
PVOID SystemArgument1;
PVOID SystemArgument2;
ASSERT_IRQL_EQUAL(APC_LEVEL);
/* Save the old trap frame and set current one */
OldTrapFrame = Thread->TrapFrame;
Thread->TrapFrame = TrapFrame;
/* Clear Kernel APC Pending */
Thread->ApcState.KernelApcPending = FALSE;
/* Check if Special APCs are disabled */
if (Thread->SpecialApcDisable) goto Quickie;
/* Do the Kernel APCs first */
while (!IsListEmpty(&Thread->ApcState.ApcListHead[KernelMode]))
{
/* Lock the APC Queue */
KiAcquireApcLockAtApcLevel(Thread, &ApcLock);
/* Check if the list became empty now */
if (IsListEmpty(&Thread->ApcState.ApcListHead[KernelMode]))
{
/* It is, release the lock and break out */
KiReleaseApcLock(&ApcLock);
break;
}
/* Kernel APC is not pending anymore */
Thread->ApcState.KernelApcPending = FALSE;
/* Get the next Entry */
ApcListEntry = Thread->ApcState.ApcListHead[KernelMode].Flink;
Apc = CONTAINING_RECORD(ApcListEntry, KAPC, ApcListEntry);
/* Save Parameters so that it's safe to free the Object in the Kernel Routine*/
NormalRoutine = Apc->NormalRoutine;
KernelRoutine = Apc->KernelRoutine;
NormalContext = Apc->NormalContext;
SystemArgument1 = Apc->SystemArgument1;
SystemArgument2 = Apc->SystemArgument2;
/* Special APC */
if (!NormalRoutine)
{
/* Remove the APC from the list */
RemoveEntryList(ApcListEntry);
Apc->Inserted = FALSE;
/* Release the APC lock */
KiReleaseApcLock(&ApcLock);
/* Call the Special APC */
KernelRoutine(Apc,
&NormalRoutine,
&NormalContext,
&SystemArgument1,
&SystemArgument2);
/* Make sure it returned correctly */
if (KeGetCurrentIrql() != ApcLock.OldIrql)
{
KeBugCheckEx(IRQL_UNEXPECTED_VALUE,
(KeGetCurrentIrql() << 16) |
(ApcLock.OldIrql << 8),
(ULONG_PTR)KernelRoutine,
(ULONG_PTR)Apc,
(ULONG_PTR)NormalRoutine);
}
}
else
{
/* Normal Kernel APC, make sure it's safe to deliver */
if ((Thread->ApcState.KernelApcInProgress) ||
(Thread->KernelApcDisable))
{
/* Release lock and return */
KiReleaseApcLock(&ApcLock);
goto Quickie;
}
/* Dequeue the APC */
RemoveEntryList(ApcListEntry);
Apc->Inserted = FALSE;
/* Go back to APC_LEVEL */
KiReleaseApcLock(&ApcLock);
/* Call the Kernel APC */
KernelRoutine(Apc,
&NormalRoutine,
&NormalContext,
&SystemArgument1,
&SystemArgument2);
/* Make sure it returned correctly */
if (KeGetCurrentIrql() != ApcLock.OldIrql)
{
KeBugCheckEx(IRQL_UNEXPECTED_VALUE,
(KeGetCurrentIrql() << 16) |
(ApcLock.OldIrql << 8),
(ULONG_PTR)KernelRoutine,
(ULONG_PTR)Apc,
(ULONG_PTR)NormalRoutine);
}
/* Check if there still is a Normal Routine */
if (NormalRoutine)
{
/* At Passive Level, an APC can be prempted by a Special APC */
Thread->ApcState.KernelApcInProgress = TRUE;
KeLowerIrql(PASSIVE_LEVEL);
/* Call and Raise IRQL back to APC_LEVEL */
NormalRoutine(NormalContext, SystemArgument1, SystemArgument2);
KeRaiseIrql(APC_LEVEL, &ApcLock.OldIrql);
}
/* Set Kernel APC in progress to false and loop again */
Thread->ApcState.KernelApcInProgress = FALSE;
}
}
/* Now we do the User APCs */
if ((DeliveryMode == UserMode) &&
!(IsListEmpty(&Thread->ApcState.ApcListHead[UserMode])) &&
(Thread->ApcState.UserApcPending))
{
/* Lock the APC Queue */
KiAcquireApcLockAtApcLevel(Thread, &ApcLock);
/* It's not pending anymore */
Thread->ApcState.UserApcPending = FALSE;
/* Check if the list became empty now */
if (IsListEmpty(&Thread->ApcState.ApcListHead[UserMode]))
{
/* It is, release the lock and break out */
KiReleaseApcLock(&ApcLock);
goto Quickie;
}
/* Get the actual APC object */
ApcListEntry = Thread->ApcState.ApcListHead[UserMode].Flink;
Apc = CONTAINING_RECORD(ApcListEntry, KAPC, ApcListEntry);
/* Save Parameters so that it's safe to free the Object in the Kernel Routine*/
NormalRoutine = Apc->NormalRoutine;
KernelRoutine = Apc->KernelRoutine;
NormalContext = Apc->NormalContext;
SystemArgument1 = Apc->SystemArgument1;
SystemArgument2 = Apc->SystemArgument2;
/* Remove the APC from Queue, and release the lock */
RemoveEntryList(ApcListEntry);
Apc->Inserted = FALSE;
KiReleaseApcLock(&ApcLock);
/* Call the kernel routine */
KernelRoutine(Apc,
&NormalRoutine,
&NormalContext,
&SystemArgument1,
&SystemArgument2);
/* Check if there's no normal routine */
if (!NormalRoutine)
{
/* Check if more User APCs are Pending */
KeTestAlertThread(UserMode);
}
else
{
/* Set up the Trap Frame and prepare for Execution in NTDLL.DLL */
KiInitializeUserApc(ExceptionFrame,
TrapFrame,
NormalRoutine,
NormalContext,
SystemArgument1,
SystemArgument2);
}
}
Quickie:
/* Make sure we're still in the same process */
if (Process != Thread->ApcState.Process)
{
/* Erm, we got attached or something! BAD! */
KeBugCheckEx(INVALID_PROCESS_ATTACH_ATTEMPT,
(ULONG_PTR)Process,
(ULONG_PTR)Thread->ApcState.Process,
Thread->ApcStateIndex,
KeGetCurrentPrcb()->DpcRoutineActive);
}
/* Restore the trap frame */
Thread->TrapFrame = OldTrapFrame;
}
FORCEINLINE
VOID
RepairList(IN PLIST_ENTRY Original,
IN PLIST_ENTRY Copy,
IN KPROCESSOR_MODE Mode)
{
/* Check if the list for this mode is empty */
if (IsListEmpty(&Original[Mode]))
{
/* It is, all we need to do is initialize it */
InitializeListHead(&Copy[Mode]);
}
else
{
/* Copy the lists */
Copy[Mode].Flink = Original[Mode].Flink;
Copy[Mode].Blink = Original[Mode].Blink;
Original[Mode].Flink->Blink = &Copy[Mode];
Original[Mode].Blink->Flink = &Copy[Mode];
}
}
VOID
NTAPI
KiMoveApcState(PKAPC_STATE OldState,
PKAPC_STATE NewState)
{
/* Restore backup of Original Environment */
RtlCopyMemory(NewState, OldState, KAPC_STATE_ACTUAL_LENGTH);
/* Repair Lists */
RepairList(OldState->ApcListHead, NewState->ApcListHead, KernelMode);
RepairList(OldState->ApcListHead, NewState->ApcListHead, UserMode);
}
/* PUBLIC FUNCTIONS **********************************************************/
/*++
* @name KeEnterCriticalRegion
* @implemented NT4
*
* The KeEnterCriticalRegion routine temporarily disables the delivery of
* normal kernel APCs; special kernel-mode APCs are still delivered.
*
* @param None.
*
* @return None.
*
* @remarks Highest-level drivers can call this routine while running in the
* context of the thread that requested the current I/O operation.
* Any caller of this routine should call KeLeaveCriticalRegion as
* quickly as possible.
*
* Callers of KeEnterCriticalRegion must be running at IRQL <=
* APC_LEVEL.
*
*--*/
VOID
NTAPI
_KeEnterCriticalRegion(VOID)
{
/* Use inlined function */
KeEnterCriticalRegion();
}
/*++
* KeLeaveCriticalRegion
* @implemented NT4
*
* The KeLeaveCriticalRegion routine reenables the delivery of normal
* kernel-mode APCs that were disabled by a call to KeEnterCriticalRegion.
*
* @param None.
*
* @return None.
*
* @remarks Highest-level drivers can call this routine while running in the
* context of the thread that requested the current I/O operation.
*
* Callers of KeLeaveCriticalRegion must be running at IRQL <=
* DISPATCH_LEVEL.
*
*--*/
VOID
NTAPI
_KeLeaveCriticalRegion(VOID)
{
/* Use inlined version */
KeLeaveCriticalRegion();
}
/*++
* KeInitializeApc
* @implemented NT4
*
* The KeInitializeApc routine initializes an APC object, and registers
* the Kernel, Rundown and Normal routines for that object.
*
* @param Apc
* Pointer to a KAPC structure that represents the APC object to
* initialize. The caller must allocate storage for the structure
* from resident memory.
*
* @param Thread
* Thread to which to deliver the APC.
*
* @param TargetEnvironment
* APC Environment to be used.
*
* @param KernelRoutine
* Points to the KernelRoutine to associate with the APC.
* This routine is executed for all APCs.
*
* @param RundownRoutine
* Points to the RundownRoutine to associate with the APC.
* This routine is executed when the Thread exits during APC execution.
*
* @param NormalRoutine
* Points to the NormalRoutine to associate with the APC.
* This routine is executed at PASSIVE_LEVEL. If this is not specifed,
* the APC becomes a Special APC and the Mode and Context parameters are
* ignored.
*
* @param Mode
* Specifies the processor mode at which to run the Normal Routine.
*
* @param Context
* Specifices the value to pass as Context parameter to the registered
* routines.
*
* @return None.
*
* @remarks The caller can queue an initialized APC with KeInsertQueueApc.
*
*--*/
VOID
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)
{
/* Sanity check */
ASSERT(TargetEnvironment <= InsertApcEnvironment);
/* Set up the basic APC Structure Data */
Apc->Type = ApcObject;
Apc->Size = sizeof(KAPC);
/* Set the Environment */
if (TargetEnvironment == CurrentApcEnvironment)
{
/* Use the current one for the thread */
Apc->ApcStateIndex = Thread->ApcStateIndex;
}
else
{
/* Sanity check */
ASSERT((TargetEnvironment <= Thread->ApcStateIndex) ||
(TargetEnvironment == InsertApcEnvironment));
/* Use the one that was given */
Apc->ApcStateIndex = TargetEnvironment;
}
/* Set the Thread and Routines */
Apc->Thread = Thread;
Apc->KernelRoutine = KernelRoutine;
Apc->RundownRoutine = RundownRoutine;
Apc->NormalRoutine = NormalRoutine;
/* Check if this is a special APC */
if (NormalRoutine)
{
/* It's a normal one. Set the context and mode */
Apc->ApcMode = Mode;
Apc->NormalContext = Context;
}
else
{
/* It's a special APC, which can only be kernel mode */
Apc->ApcMode = KernelMode;
Apc->NormalContext = NULL;
}
/* The APC is not inserted */
Apc->Inserted = FALSE;
}
/*++
* @name KeInsertQueueApc
* @implemented NT4
*
* The KeInsertQueueApc routine queues a APC for execution when the right
* scheduler environment exists.
*
* @param Apc
* Pointer to an initialized control object of type APC for which the
* caller provides the storage.
*
* @param SystemArgument[1,2]
* Pointer to a set of two parameters that contain untyped data.
*
* @param PriorityBoost
* Priority Boost to apply to the Thread.
*
* @return If the APC is already inserted or APC queueing is disabled, FALSE.
* Otherwise, TRUE.
*
* @remarks The APC will execute at APC_LEVEL for the KernelRoutine registered,
* and at PASSIVE_LEVEL for the NormalRoutine registered.
*
* Callers of this routine must be running at IRQL <= DISPATCH_LEVEL.
*
*--*/
BOOLEAN
NTAPI
KeInsertQueueApc(IN PKAPC Apc,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2,
IN KPRIORITY PriorityBoost)
{
PKTHREAD Thread = Apc->Thread;
KLOCK_QUEUE_HANDLE ApcLock;
BOOLEAN State = TRUE;
ASSERT_APC(Apc);
ASSERT_IRQL_LESS_OR_EQUAL(DISPATCH_LEVEL);
/* Get the APC lock */
KiAcquireApcLock(Thread, &ApcLock);
/* Make sure we can Queue APCs and that this one isn't already inserted */
if (!(Thread->ApcQueueable) || (Apc->Inserted))
{
/* Fail */
State = FALSE;
}
else
{
/* Set the System Arguments and set it as inserted */
Apc->SystemArgument1 = SystemArgument1;
Apc->SystemArgument2 = SystemArgument2;
Apc->Inserted = TRUE;
/* Call the Internal Function */
KiInsertQueueApc(Apc, PriorityBoost);
}
/* Release the APC lock and return success */
KiReleaseApcLockFromDpcLevel(&ApcLock);
KiExitDispatcher(ApcLock.OldIrql);
return State;
}
/*++
* @name KeFlushQueueApc
* @implemented NT4
*
* The KeFlushQueueApc routine flushes all APCs of the given processor mode
* from the specified Thread's APC queue.
*
* @param Thread
* Pointer to the thread whose APC queue will be flushed.
*
* @param PreviousMode
* Specifies which APC Queue to flush.
*
* @return A pointer to the first entry in the flushed APC queue.
*
* @remarks If the routine returns NULL, it means that no APCs were flushed.
* Callers of this routine must be running at DISPATCH_LEVEL or lower.
*
*--*/
PLIST_ENTRY
NTAPI
KeFlushQueueApc(IN PKTHREAD Thread,
IN KPROCESSOR_MODE PreviousMode)
{
PKAPC Apc;
PLIST_ENTRY FirstEntry, CurrentEntry;
KLOCK_QUEUE_HANDLE ApcLock;
ASSERT_IRQL_LESS_OR_EQUAL(DISPATCH_LEVEL);
/* Check if this was user mode */
if (PreviousMode == UserMode)
{
/* Get the APC lock */
KiAcquireApcLock(Thread, &ApcLock);
/* Select user list and check if it's empty */
if (IsListEmpty(&Thread->ApcState.ApcListHead[UserMode]))
{
/* Don't return anything */
FirstEntry = NULL;
goto FlushDone;
}
}
else
{
/* Select kernel list and check if it's empty */
if (IsListEmpty( &Thread->ApcState.ApcListHead[KernelMode]))
{
/* Don't return anything */
return NULL;
}
/* Otherwise, acquire the APC lock */
KiAcquireApcLock(Thread, &ApcLock);
}
/* Get the first entry and check if the list is empty now */
FirstEntry = Thread->ApcState.ApcListHead[PreviousMode].Flink;
if (FirstEntry == &Thread->ApcState.ApcListHead[PreviousMode])
{
/* It is, clear the returned entry */
FirstEntry = NULL;
}
else
{
/* It's not, remove the first entry */
RemoveEntryList(&Thread->ApcState.ApcListHead[PreviousMode]);
/* Loop all the entries */
CurrentEntry = FirstEntry;
do
{
/* Get the APC and make it un-inserted */
Apc = CONTAINING_RECORD(CurrentEntry, KAPC, ApcListEntry);
Apc->Inserted = FALSE;
/* Get the next entry */
CurrentEntry = CurrentEntry->Flink;
} while (CurrentEntry != FirstEntry);
/* Re-initialize the list */
InitializeListHead(&Thread->ApcState.ApcListHead[PreviousMode]);
}
/* Release the lock */
FlushDone:
KiReleaseApcLock(&ApcLock);
/* Return the first entry */
return FirstEntry;
}
/*++
* @name KeRemoveQueueApc
* @implemented NT4
*
* The KeRemoveQueueApc routine removes a given APC object from the system
* APC queue.
*
* @param Apc
* Pointer to an initialized APC object that was queued by calling
* KeInsertQueueApc.
*
* @return TRUE if the APC Object is in the APC Queue. Otherwise, no operation
* is performed and FALSE is returned.
*
* @remarks If the given APC Object is currently queued, it is removed from the
* queue and any calls to the registered routines are cancelled.
*
* Callers of this routine must be running at IRQL <= DISPATCH_LEVEL.
*
*--*/
BOOLEAN
NTAPI
KeRemoveQueueApc(IN PKAPC Apc)
{
PKTHREAD Thread = Apc->Thread;
PKAPC_STATE ApcState;
BOOLEAN Inserted;
KLOCK_QUEUE_HANDLE ApcLock;
ASSERT_APC(Apc);
ASSERT_IRQL_LESS_OR_EQUAL(DISPATCH_LEVEL);
/* Get the APC lock */
KiAcquireApcLock(Thread, &ApcLock);
/* Check if it's inserted */
Inserted = Apc->Inserted;
if (Inserted)
{
/* Set it as non-inserted and get the APC state */
Apc->Inserted = FALSE;
ApcState = Thread->ApcStatePointer[(UCHAR)Apc->ApcStateIndex];
/* Acquire the dispatcher lock and remove it from the list */
KiAcquireDispatcherLockAtDpcLevel();
if (RemoveEntryList(&Apc->ApcListEntry))
{
/* Set the correct state based on the APC Mode */
if (Apc->ApcMode == KernelMode)
{
/* No more pending kernel APCs */
ApcState->KernelApcPending = FALSE;
}
else
{
/* No more pending user APCs */
ApcState->UserApcPending = FALSE;
}
}
/* Release dispatcher lock */
KiReleaseDispatcherLockFromDpcLevel();
}
/* Release the lock and return */
KiReleaseApcLock(&ApcLock);
return Inserted;
}
/*++
* @name KeAreApcsDisabled
* @implemented NT4
*
* The KeAreApcsDisabled routine returns whether kernel APC delivery is
* disabled for the current thread.
*
* @param None.
*
* @return KeAreApcsDisabled returns TRUE if the thread is within a critical
* region or a guarded region, and FALSE otherwise.
*
* @remarks A thread running at IRQL = PASSIVE_LEVEL can use KeAreApcsDisabled
* to determine if normal kernel APCs are disabled.
*
* A thread that is inside critical region has both user APCs and
* normal kernel APCs disabled, but not special kernel APCs.
*
* A thread that is inside a guarded region has all APCs disabled,
* including special kernel APCs.
*
* Callers of this routine must be running at IRQL <= DISPATCH_LEVEL.
*
*--*/
BOOLEAN
NTAPI
KeAreApcsDisabled(VOID)
{
/* Return the Kernel APC State */
return KeGetCurrentThread()->CombinedApcDisable ? TRUE : FALSE;
}
/*++
* @name KeAreAllApcsDisabled
* @implemented NT5.1
*
* The KeAreAllApcsDisabled routine returns whether the calling thread is
* inside a guarded region or running at IRQL >= APC_LEVEL, which disables
* all APC delivery.
*
* @param None.
*
* @return KeAreAllApcsDisabled returns TRUE if the thread is within a guarded
* guarded region or running at IRQL >= APC_LEVEL, and FALSE otherwise.
*
* @remarks A thread running at IRQL = PASSIVE_LEVEL can use this routine to
* determine if all APC delivery is disabled.
*
* Callers of this routine must be running at IRQL <= DISPATCH_LEVEL.
*
*--*/
BOOLEAN
NTAPI
KeAreAllApcsDisabled(VOID)
{
/* Return the Special APC State */
return ((KeGetCurrentThread()->SpecialApcDisable) ||
(KeGetCurrentIrql() >= APC_LEVEL)) ? TRUE : FALSE;
}