mirror of
https://github.com/reactos/reactos.git
synced 2024-11-01 20:32:36 +00:00
991 lines
30 KiB
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 */
|
|
KiReleaseDispatcherLockFromSynchLevel();
|
|
|
|
/* 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 */
|
|
KiAcquireApcLockRaiseToDpc(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 */
|
|
KiAcquireApcLockRaiseToDpc(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 */
|
|
KiAcquireApcLockRaiseToSynch(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 */
|
|
KiReleaseApcLockFromSynchLevel(&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 */
|
|
KiAcquireApcLockRaiseToSynch(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 */
|
|
KiAcquireApcLockRaiseToSynch(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 (this raises IRQL to SYNCH_LEVEL) */
|
|
KiAcquireApcLockRaiseToSynch(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 */
|
|
KiAcquireDispatcherLockAtSynchLevel();
|
|
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 */
|
|
KiReleaseDispatcherLockFromSynchLevel();
|
|
}
|
|
|
|
/* 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;
|
|
}
|