3 * Copyright (C) 1998, 1999, 2000, 2001 ReactOS Team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 * PROJECT: ReactOS kernel
21 * FILE: ntoskrnl/ke/apc.c
22 * PURPOSE: Possible implementation of APCs
23 * PROGRAMMER: David Welch (welch@cwcom.net)
24 * PORTABILITY: Unchecked
27 * 12/11/99: Phillip Susi: Reworked the APC code
30 /* INCLUDES *****************************************************************/
32 #include <ddk/ntddk.h>
33 #include <internal/i386/segment.h>
34 #include <internal/ps.h>
35 #include <internal/ke.h>
36 #include <internal/ldr.h>
37 #include <internal/pool.h>
40 #include <internal/debug.h>
42 /* GLOBALS *******************************************************************/
45 extern KSPIN_LOCK PiThreadListLock;
47 VOID PsTerminateCurrentThread(NTSTATUS ExitStatus);
49 #define TAG_KAPC TAG('K', 'A', 'P', 'C')
51 /* FUNCTIONS *****************************************************************/
53 VOID KiRundownThread(VOID)
60 BOOLEAN KiTestAlert(VOID)
62 * FUNCTION: Tests whether there are any pending APCs for the current thread
63 * and if so the APCs will be delivered on exit from kernel mode
68 KeAcquireSpinLock(&PiApcLock, &oldIrql);
69 if (KeGetCurrentThread()->ApcState.UserApcPending == 0)
71 KeReleaseSpinLock(&PiApcLock, oldIrql);
74 KeGetCurrentThread()->Alerted[0] = 1;
75 KeReleaseSpinLock(&PiApcLock, oldIrql);
80 KiDeliverNormalApc(VOID)
82 PETHREAD Thread = PsGetCurrentThread();
86 PKNORMAL_ROUTINE NormalRoutine;
88 PVOID SystemArgument1;
89 PVOID SystemArgument2;
91 KeAcquireSpinLock(&PiApcLock, &oldlvl);
92 while(!IsListEmpty(&(Thread->Tcb.ApcState.ApcListHead[0])))
94 current = Thread->Tcb.ApcState.ApcListHead[0].Blink;
95 Apc = CONTAINING_RECORD(current, KAPC, ApcListEntry);
96 if (Apc->NormalRoutine == NULL)
98 DbgPrint("Exiting kernel with kernel APCs pending.\n");
101 (VOID)RemoveTailList(&Thread->Tcb.ApcState.ApcListHead[0]);
102 Apc->Inserted = FALSE;
103 Thread->Tcb.ApcState.KernelApcInProgress++;
104 Thread->Tcb.ApcState.KernelApcPending--;
106 KeReleaseSpinLock(&PiApcLock, oldlvl);
108 NormalRoutine = Apc->NormalRoutine;
109 NormalContext = Apc->NormalContext;
110 SystemArgument1 = Apc->SystemArgument1;
111 SystemArgument2 = Apc->SystemArgument2;
112 Apc->KernelRoutine(Apc,
117 NormalRoutine(NormalContext, SystemArgument1, SystemArgument2);
119 KeAcquireSpinLock(&PiApcLock, &oldlvl);
120 Thread->Tcb.ApcState.KernelApcInProgress--;
122 KeReleaseSpinLock(&PiApcLock, oldlvl);
126 KiDeliverUserApc(PKTRAP_FRAME TrapFrame)
128 * FUNCTION: Tests whether there are any pending APCs for the current thread
129 * and if so the APCs will be delivered on exit from kernel mode.
131 * Thread = Thread to test for alerts
132 * UserContext = The user context saved on entry to kernel mode
135 PLIST_ENTRY current_entry;
142 DPRINT("KiDeliverUserApc(TrapFrame %x/%x)\n", TrapFrame,
143 KeGetCurrentThread()->TrapFrame);
144 Thread = KeGetCurrentThread();
147 * Check for thread termination
150 KeAcquireSpinLock(&PiApcLock, &oldlvl);
152 current_entry = Thread->ApcState.ApcListHead[1].Flink;
155 * Shouldn't happen but check anyway.
157 if (current_entry == &Thread->ApcState.ApcListHead[1])
159 KeReleaseSpinLock(&PiApcLock, oldlvl);
160 DbgPrint("KiDeliverUserApc called but no APC was pending\n");
164 while (!IsListEmpty(&Thread->ApcState.ApcListHead[1]))
166 current_entry = RemoveHeadList(&Thread->ApcState.ApcListHead[1]);
167 Apc = CONTAINING_RECORD(current_entry, KAPC, ApcListEntry);
168 Apc->Inserted = FALSE;
169 KeReleaseSpinLock(&PiApcLock, oldlvl);
172 * Save the thread's current context (in other words the registers
173 * that will be restored when it returns to user mode) so the
174 * APC dispatcher can restore them later
176 Context = (PCONTEXT)(((PUCHAR)TrapFrame->Esp) - sizeof(CONTEXT));
177 memset(Context, 0, sizeof(CONTEXT));
178 Context->ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER |
179 CONTEXT_SEGMENTS | CONTEXT_i386;
180 Context->SegGs = TrapFrame->Gs;
181 Context->SegFs = TrapFrame->Fs;
182 Context->SegEs = TrapFrame->Es;
183 Context->SegDs = TrapFrame->Ds;
184 Context->Edi = TrapFrame->Edi;
185 Context->Esi = TrapFrame->Esi;
186 Context->Ebx = TrapFrame->Ebx;
187 Context->Edx = TrapFrame->Edx;
188 Context->Ecx = TrapFrame->Ecx;
189 Context->Eax = TrapFrame->Eax;
190 Context->Ebp = TrapFrame->Ebp;
191 Context->Eip = TrapFrame->Eip;
192 Context->SegCs = TrapFrame->Cs;
193 Context->EFlags = TrapFrame->Eflags;
194 Context->Esp = TrapFrame->Esp;
195 Context->SegSs = TrapFrame->Ss;
198 * Setup the trap frame so the thread will start executing at the
199 * APC Dispatcher when it returns to user-mode
201 Esp = (PULONG)(((PUCHAR)TrapFrame->Esp) -
202 (sizeof(CONTEXT) + (6 * sizeof(ULONG))));
205 Esp[1] = (ULONG)Apc->NormalRoutine;
206 Esp[2] = (ULONG)Apc->NormalContext;
207 Esp[3] = (ULONG)Apc->SystemArgument1;
208 Esp[4] = (ULONG)Apc->SystemArgument2;
209 Esp[5] = (ULONG)Context;
210 TrapFrame->Eip = (ULONG)LdrpGetSystemDllApcDispatcher();
211 TrapFrame->Esp = (ULONG)Esp;
214 * We've dealt with one pending user-mode APC
216 Thread->ApcState.UserApcPending--;
219 * Now call for the kernel routine for the APC, which will free
220 * the APC data structure, we can't do this ourselves because
221 * the APC may be embedded in some larger structure e.g. an IRP
222 * We also give the kernel routine a last chance to modify the
223 * arguments to the user APC routine.
225 Apc->KernelRoutine(Apc,
226 (PKNORMAL_ROUTINE*)&Esp[1],
231 KeAcquireSpinLock(&PiApcLock, &oldlvl);
233 Thread->Alerted[0] = 0;
234 KeReleaseSpinLock(&PiApcLock, oldlvl);
240 KiDeliverApc(ULONG Unknown1,
244 * FUNCTION: Deliver an APC to the current thread.
245 * NOTES: This is called from the IRQL switching code if the current thread
246 * is returning from an IRQL greater than or equal to APC_LEVEL to
247 * PASSIVE_LEVEL and there are kernel-mode APCs pending. This means any
248 * pending APCs will be delivered after a thread gets a new quantum and
249 * after it wakes from a wait.
252 PETHREAD Thread = PsGetCurrentThread();
253 PLIST_ENTRY current_entry;
257 DPRINT("KiDeliverApc()\n");
258 KeAcquireSpinLock(&PiApcLock, &oldlvl);
259 current_entry = Thread->Tcb.ApcState.ApcListHead[0].Flink;
260 while(current_entry != &Thread->Tcb.ApcState.ApcListHead[0])
262 Apc = CONTAINING_RECORD(current_entry, KAPC, ApcListEntry);
263 if (Apc->NormalRoutine == NULL)
265 current_entry = current_entry->Flink;
266 Apc->Inserted = FALSE;
267 RemoveEntryList(&Apc->ApcListEntry);
268 Thread->Tcb.ApcState.KernelApcInProgress++;
269 Thread->Tcb.ApcState.KernelApcPending--;
271 KeReleaseSpinLock(&PiApcLock, oldlvl);
273 Apc->KernelRoutine(Apc,
276 &Apc->SystemArgument1,
277 &Apc->SystemArgument2);
279 KeAcquireSpinLock(&PiApcLock, &oldlvl);
280 Thread->Tcb.ApcState.KernelApcInProgress--;
284 current_entry = current_entry->Flink;
287 KeReleaseSpinLock(&PiApcLock, oldlvl);
291 KeInsertQueueApc (PKAPC Apc,
292 PVOID SystemArgument1,
293 PVOID SystemArgument2,
296 * FUNCTION: Queues an APC for execution
298 * Apc = APC to be queued
299 * SystemArgument[1-2] = TBD
304 PKTHREAD TargetThread;
306 DPRINT("KeInsertQueueApc(Apc %x, SystemArgument1 %x, "
307 "SystemArgument2 %x, Mode %d)\n",Apc,SystemArgument1,
308 SystemArgument2,Mode);
310 KeAcquireSpinLock(&PiApcLock, &oldlvl);
312 Apc->SystemArgument1 = SystemArgument1;
313 Apc->SystemArgument2 = SystemArgument2;
317 DbgPrint("KeInsertQueueApc(): multiple APC insertations\n");
321 TargetThread = Apc->Thread;
322 if (Apc->ApcMode == KernelMode)
324 InsertTailList(&TargetThread->ApcState.ApcListHead[0],
326 TargetThread->ApcState.KernelApcPending++;
330 InsertTailList(&TargetThread->ApcState.ApcListHead[1],
332 TargetThread->ApcState.UserApcPending++;
334 Apc->Inserted = TRUE;
337 * If this is a kernel-mode APC for the current thread and we are not
338 * inside a critical section or at APC level then call it, in fact we
339 * rely on the side effects of dropping the IRQL level when we release
342 if (Apc->ApcMode == KernelMode && TargetThread == KeGetCurrentThread() &&
343 Apc->NormalRoutine == NULL)
345 KeReleaseSpinLock(&PiApcLock, oldlvl);
350 * If this is a kernel-mode APC and it is waiting at PASSIVE_LEVEL and
351 * not inside a critical section then wake it up. Otherwise it will
352 * execute the APC as soon as it returns to PASSIVE_LEVEL.
353 * FIXME: If the thread is running on another processor then send an
355 * FIXME: Check if the thread is terminating.
357 if (Apc->ApcMode == KernelMode && TargetThread->WaitIrql < APC_LEVEL &&
358 Apc->NormalRoutine == NULL)
360 KeRemoveAllWaitsThread(CONTAINING_RECORD(TargetThread, ETHREAD, Tcb),
365 * If this is a 'funny' user-mode APC then mark the thread as
366 * alerted so it will execute the APC on exit from kernel mode. If it
367 * is waiting alertably then wake it up so it can return to user mode.
369 if (Apc->ApcMode == KernelMode && Apc->NormalRoutine != NULL)
371 TargetThread->Alerted[1] = 1;
372 if (TargetThread->Alertable == TRUE &&
373 TargetThread->WaitMode == UserMode)
377 Thread = CONTAINING_RECORD(TargetThread, ETHREAD, Tcb);
378 KeRemoveAllWaitsThread(Thread, STATUS_USER_APC);
383 * If the thread is waiting alertably then wake it up and it will
384 * return to to user-mode executing the APC in the process. Otherwise the
385 * thread will execute the APC next time it enters an alertable wait.
387 if (Apc->ApcMode == UserMode && TargetThread->Alertable == TRUE &&
388 TargetThread->WaitMode == UserMode)
392 DPRINT("Resuming thread for user APC\n");
394 Status = STATUS_USER_APC;
395 TargetThread->Alerted[0] = 1;
396 KeRemoveAllWaitsThread(CONTAINING_RECORD(TargetThread, ETHREAD, Tcb),
399 KeReleaseSpinLock(&PiApcLock, oldlvl);
403 KeRemoveQueueApc (PKAPC Apc)
405 * FUNCTION: Removes APC object from the apc queue
407 * Apc = APC to remove
408 * RETURNS: TRUE if the APC was in the queue
410 * NOTE: This function is not exported.
414 PKTHREAD TargetThread;
416 KeAcquireSpinLockAtDpcLevel(&PiApcLock);
417 KeRaiseIrql(HIGH_LEVEL, &oldIrql);
418 if (Apc->Inserted == FALSE)
420 KeReleaseSpinLock(&PiApcLock, oldIrql);
424 TargetThread = Apc->Thread;
425 RemoveEntryList(&Apc->ApcListEntry);
426 if (Apc->ApcMode == KernelMode)
428 TargetThread->ApcState.KernelApcPending--;
432 TargetThread->ApcState.UserApcPending--;
434 Apc->Inserted = FALSE;
436 KeReleaseSpinLock(&PiApcLock, oldIrql);
442 KeInitializeApc(PKAPC Apc,
445 PKKERNEL_ROUTINE KernelRoutine,
446 PKRUNDOWN_ROUTINE RundownRoutine,
447 PKNORMAL_ROUTINE NormalRoutine,
451 * FUNCTION: Initialize an APC object
453 * Apc = Pointer to the APC object to initialized
454 * Thread = Thread the APC is to be delivered to
456 * KernelRoutine = Routine to be called for a kernel-mode APC
457 * RundownRoutine = Routine to be called if the thread has exited with
458 * the APC being executed
459 * NormalRoutine = Routine to be called for a user-mode APC
461 * Context = Parameter to be passed to the APC routine
464 DPRINT("KeInitializeApc(Apc %x, Thread %x, StateIndex %d, "
465 "KernelRoutine %x, RundownRoutine %x, NormalRoutine %x, Mode %d, "
466 "Context %x)\n",Apc,Thread,StateIndex,KernelRoutine,RundownRoutine,
467 NormalRoutine,Mode,Context);
469 memset(Apc, 0, sizeof(KAPC));
470 Apc->Thread = Thread;
471 Apc->ApcListEntry.Flink = NULL;
472 Apc->ApcListEntry.Blink = NULL;
473 Apc->KernelRoutine = KernelRoutine;
474 Apc->RundownRoutine = RundownRoutine;
475 Apc->NormalRoutine = NormalRoutine;
476 Apc->NormalContext = Context;
477 Apc->Inserted = FALSE;
478 Apc->ApcStateIndex = StateIndex;
479 if (Apc->NormalRoutine != NULL)
485 Apc->ApcMode = KernelMode;
490 NtQueueApcRundownRoutine(PKAPC Apc)
496 NtQueueApcKernelRoutine(PKAPC Apc,
497 PKNORMAL_ROUTINE* NormalRoutine,
498 PVOID* NormalContext,
499 PVOID* SystemArgument1,
500 PVOID* SystemArgument2)
506 NtQueueApcThread(HANDLE ThreadHandle,
507 PKNORMAL_ROUTINE ApcRoutine,
509 PVOID SystemArgument1,
510 PVOID SystemArgument2)
516 Status = ObReferenceObjectByHandle(ThreadHandle,
517 THREAD_ALL_ACCESS, /* FIXME */
522 if (!NT_SUCCESS(Status))
527 Apc = ExAllocatePoolWithTag(NonPagedPool, sizeof(KAPC), TAG_KAPC);
530 ObDereferenceObject(Thread);
531 return(STATUS_NO_MEMORY);
537 NtQueueApcKernelRoutine,
538 NtQueueApcRundownRoutine,
542 KeInsertQueueApc(Apc,
547 ObDereferenceObject(Thread);
548 return(STATUS_SUCCESS);
552 NTSTATUS STDCALL NtTestAlert(VOID)
555 return(STATUS_SUCCESS);
558 VOID PiInitApcManagement(VOID)
560 KeInitializeSpinLock(&PiApcLock);