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 = RemoveTailList(&Thread->Tcb.ApcState.ApcListHead[0]);
95 Apc = CONTAINING_RECORD(current, KAPC, ApcListEntry);
96 if (Apc->NormalRoutine == NULL)
98 DbgPrint("Exiting kernel with kernel APCs pending.\n");
101 Apc->Inserted = FALSE;
102 Thread->Tcb.ApcState.KernelApcInProgress++;
103 Thread->Tcb.ApcState.KernelApcPending--;
105 KeReleaseSpinLock(&PiApcLock, oldlvl);
107 NormalRoutine = Apc->NormalRoutine;
108 NormalContext = Apc->NormalContext;
109 SystemArgument1 = Apc->SystemArgument1;
110 SystemArgument2 = Apc->SystemArgument2;
111 Apc->KernelRoutine(Apc,
116 NormalRoutine(NormalContext, SystemArgument1, SystemArgument2);
118 KeAcquireSpinLock(&PiApcLock, &oldlvl);
119 Thread->Tcb.ApcState.KernelApcInProgress--;
121 KeReleaseSpinLock(&PiApcLock, oldlvl);
125 KiDeliverUserApc(PKTRAP_FRAME TrapFrame)
127 * FUNCTION: Tests whether there are any pending APCs for the current thread
128 * and if so the APCs will be delivered on exit from kernel mode.
130 * Thread = Thread to test for alerts
131 * UserContext = The user context saved on entry to kernel mode
134 PLIST_ENTRY current_entry;
141 DPRINT("KiDeliverUserApc(TrapFrame %x/%x)\n", TrapFrame,
142 KeGetCurrentThread()->TrapFrame);
143 Thread = KeGetCurrentThread();
146 * Check for thread termination
149 KeAcquireSpinLock(&PiApcLock, &oldlvl);
151 current_entry = Thread->ApcState.ApcListHead[1].Flink;
154 * Shouldn't happen but check anyway.
156 if (current_entry == &Thread->ApcState.ApcListHead[1])
158 KeReleaseSpinLock(&PiApcLock, oldlvl);
159 DbgPrint("KiDeliverUserApc called but no APC was pending\n");
163 while (!IsListEmpty(&Thread->ApcState.ApcListHead[1]))
165 current_entry = RemoveHeadList(&Thread->ApcState.ApcListHead[1]);
166 Apc = CONTAINING_RECORD(current_entry, KAPC, ApcListEntry);
167 Apc->Inserted = FALSE;
170 * We've dealt with one pending user-mode APC
172 Thread->ApcState.UserApcPending--;
173 KeReleaseSpinLock(&PiApcLock, oldlvl);
176 * Save the thread's current context (in other words the registers
177 * that will be restored when it returns to user mode) so the
178 * APC dispatcher can restore them later
180 Context = (PCONTEXT)(((PUCHAR)TrapFrame->Esp) - sizeof(CONTEXT));
181 memset(Context, 0, sizeof(CONTEXT));
182 Context->ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER |
183 CONTEXT_SEGMENTS | CONTEXT_i386;
184 Context->SegGs = TrapFrame->Gs;
185 Context->SegFs = TrapFrame->Fs;
186 Context->SegEs = TrapFrame->Es;
187 Context->SegDs = TrapFrame->Ds;
188 Context->Edi = TrapFrame->Edi;
189 Context->Esi = TrapFrame->Esi;
190 Context->Ebx = TrapFrame->Ebx;
191 Context->Edx = TrapFrame->Edx;
192 Context->Ecx = TrapFrame->Ecx;
193 Context->Eax = TrapFrame->Eax;
194 Context->Ebp = TrapFrame->Ebp;
195 Context->Eip = TrapFrame->Eip;
196 Context->SegCs = TrapFrame->Cs;
197 Context->EFlags = TrapFrame->Eflags;
198 Context->Esp = TrapFrame->Esp;
199 Context->SegSs = TrapFrame->Ss;
202 * Setup the trap frame so the thread will start executing at the
203 * APC Dispatcher when it returns to user-mode
205 Esp = (PULONG)(((PUCHAR)TrapFrame->Esp) -
206 (sizeof(CONTEXT) + (6 * sizeof(ULONG))));
209 Esp[1] = (ULONG)Apc->NormalRoutine;
210 Esp[2] = (ULONG)Apc->NormalContext;
211 Esp[3] = (ULONG)Apc->SystemArgument1;
212 Esp[4] = (ULONG)Apc->SystemArgument2;
213 Esp[5] = (ULONG)Context;
214 TrapFrame->Eip = (ULONG)LdrpGetSystemDllApcDispatcher();
215 TrapFrame->Esp = (ULONG)Esp;
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);
243 KiDeliverApc(ULONG Unknown1,
247 * FUNCTION: Deliver an APC to the current thread.
248 * NOTES: This is called from the IRQL switching code if the current thread
249 * is returning from an IRQL greater than or equal to APC_LEVEL to
250 * PASSIVE_LEVEL and there are kernel-mode APCs pending. This means any
251 * pending APCs will be delivered after a thread gets a new quantum and
252 * after it wakes from a wait.
255 PETHREAD Thread = PsGetCurrentThread();
256 PLIST_ENTRY current_entry;
260 DPRINT("KiDeliverApc()\n");
261 KeAcquireSpinLock(&PiApcLock, &oldlvl);
262 current_entry = Thread->Tcb.ApcState.ApcListHead[0].Flink;
263 while(current_entry != &Thread->Tcb.ApcState.ApcListHead[0])
265 Apc = CONTAINING_RECORD(current_entry, KAPC, ApcListEntry);
266 if (Apc->NormalRoutine == NULL)
268 Apc->Inserted = FALSE;
269 RemoveEntryList(&Apc->ApcListEntry);
270 Thread->Tcb.ApcState.KernelApcInProgress++;
271 Thread->Tcb.ApcState.KernelApcPending--;
273 KeReleaseSpinLock(&PiApcLock, oldlvl);
275 Apc->KernelRoutine(Apc,
278 &Apc->SystemArgument1,
279 &Apc->SystemArgument2);
281 KeAcquireSpinLock(&PiApcLock, &oldlvl);
282 Thread->Tcb.ApcState.KernelApcInProgress--;
283 current_entry = Thread->Tcb.ApcState.ApcListHead[0].Flink;
287 current_entry = current_entry->Flink;
290 KeReleaseSpinLock(&PiApcLock, oldlvl);
297 KeInsertQueueApc (PKAPC Apc,
298 PVOID SystemArgument1,
299 PVOID SystemArgument2,
300 KPRIORITY PriorityBoost)
302 * FUNCTION: Queues an APC for execution
304 * Apc = APC to be queued
305 * SystemArgument[1-2] = TBD
309 //FIXME: return FALSE if APC can't be queued to target thread (thread has ended)
311 PKTHREAD TargetThread;
313 DPRINT("KeInsertQueueApc(Apc %x, SystemArgument1 %x, "
314 "SystemArgument2 %x)\n",Apc,SystemArgument1,
317 KeAcquireSpinLock(&PiApcLock, &oldlvl);
319 Apc->SystemArgument1 = SystemArgument1;
320 Apc->SystemArgument2 = SystemArgument2;
324 DbgPrint("KeInsertQueueApc(): multiple APC insertations\n");
328 TargetThread = Apc->Thread;
330 if (TargetThread->State == THREAD_STATE_TERMINATED_1 ||
331 TargetThread->State == THREAD_STATE_TERMINATED_2)
333 KeReleaseSpinLock(&PiApcLock, oldlvl);
337 if (Apc->ApcMode == KernelMode)
339 InsertTailList(&TargetThread->ApcState.ApcListHead[0],
341 TargetThread->ApcState.KernelApcPending++;
345 InsertTailList(&TargetThread->ApcState.ApcListHead[1],
347 TargetThread->ApcState.UserApcPending++;
349 Apc->Inserted = TRUE;
352 * If this is a kernel-mode APC for the current thread and we are not
353 * inside a critical section or at APC level then call it, in fact we
354 * rely on the side effects of dropping the IRQL level when we release
357 if (Apc->ApcMode == KernelMode && TargetThread == KeGetCurrentThread() &&
358 Apc->NormalRoutine == NULL)
360 KeReleaseSpinLock(&PiApcLock, oldlvl);
365 * If this is a kernel-mode APC and it is waiting at PASSIVE_LEVEL and
366 * not inside a critical section then wake it up. Otherwise it will
367 * execute the APC as soon as it returns to PASSIVE_LEVEL.
368 * FIXME: If the thread is running on another processor then send an
370 * FIXME: Check if the thread is terminating.
372 if (Apc->ApcMode == KernelMode && TargetThread->WaitIrql < APC_LEVEL &&
373 Apc->NormalRoutine == NULL)
375 KeRemoveAllWaitsThread(CONTAINING_RECORD(TargetThread, ETHREAD, Tcb),
376 STATUS_KERNEL_APC, TRUE);
380 * If this is a 'funny' user-mode APC then mark the thread as
381 * alerted so it will execute the APC on exit from kernel mode. If it
382 * is waiting alertably then wake it up so it can return to user mode.
384 if (Apc->ApcMode == KernelMode && Apc->NormalRoutine != NULL)
386 TargetThread->Alerted[1] = 1;
387 if (TargetThread->Alertable == TRUE &&
388 TargetThread->WaitMode == UserMode)
392 Thread = CONTAINING_RECORD(TargetThread, ETHREAD, Tcb);
393 KeRemoveAllWaitsThread(Thread, STATUS_USER_APC, TRUE);
398 * If the thread is waiting alertably then wake it up and it will
399 * return to to user-mode executing the APC in the process. Otherwise the
400 * thread will execute the APC next time it enters an alertable wait.
402 if (Apc->ApcMode == UserMode && TargetThread->Alertable == TRUE &&
403 TargetThread->WaitMode == UserMode)
407 DPRINT("Resuming thread for user APC\n");
409 Status = STATUS_USER_APC;
410 TargetThread->Alerted[0] = 1;
411 KeRemoveAllWaitsThread(CONTAINING_RECORD(TargetThread, ETHREAD, Tcb),
412 STATUS_USER_APC, TRUE);
414 KeReleaseSpinLock(&PiApcLock, oldlvl);
419 KeRemoveQueueApc (PKAPC Apc)
421 * FUNCTION: Removes APC object from the apc queue
423 * Apc = APC to remove
424 * RETURNS: TRUE if the APC was in the queue
426 * NOTE: This function is not exported.
430 PKTHREAD TargetThread;
432 KeRaiseIrql(HIGH_LEVEL, &oldIrql);
433 KeAcquireSpinLockAtDpcLevel(&PiApcLock);
434 if (Apc->Inserted == FALSE)
436 KeReleaseSpinLock(&PiApcLock, oldIrql);
440 TargetThread = Apc->Thread;
441 RemoveEntryList(&Apc->ApcListEntry);
442 if (Apc->ApcMode == KernelMode)
444 TargetThread->ApcState.KernelApcPending--;
448 TargetThread->ApcState.UserApcPending--;
450 Apc->Inserted = FALSE;
452 KeReleaseSpinLock(&PiApcLock, oldIrql);
465 IN PKKERNEL_ROUTINE KernelRoutine,
466 IN PKRUNDOWN_ROUTINE RundownRoutine,
467 IN PKNORMAL_ROUTINE NormalRoutine,
471 * FUNCTION: Initialize an APC object
473 * Apc = Pointer to the APC object to initialized
474 * Thread = Thread the APC is to be delivered to
475 * StateIndex = KAPC_ENVIRONMENT
476 * KernelRoutine = Routine to be called for a kernel-mode APC
477 * RundownRoutine = Routine to be called if the thread has exited with
478 * the APC being executed
479 * NormalRoutine = Routine to be called for a user-mode APC
481 * Context = Parameter to be passed to the APC routine
484 KAPC_ENVIRONMENT Environment = (KAPC_ENVIRONMENT) StateIndex;
486 DPRINT("KeInitializeApc(Apc %x, Thread %x, Environment %d, "
487 "KernelRoutine %x, RundownRoutine %x, NormalRoutine %x, Mode %d, "
488 "Context %x)\n",Apc,Thread,Environment,KernelRoutine,RundownRoutine,
489 NormalRoutine,Mode,Context);
491 memset(Apc, 0, sizeof(KAPC));
492 Apc->Thread = Thread;
493 Apc->ApcListEntry.Flink = NULL;
494 Apc->ApcListEntry.Blink = NULL;
495 Apc->KernelRoutine = KernelRoutine;
496 Apc->RundownRoutine = RundownRoutine;
497 Apc->NormalRoutine = NormalRoutine;
498 Apc->NormalContext = Context;
499 Apc->Inserted = FALSE;
501 if (Environment == CurrentApcEnvironment)
503 Apc->ApcStateIndex = Thread->ApcStateIndex;
507 Apc->ApcStateIndex = Environment;
510 if (Apc->NormalRoutine != NULL)
512 Apc->ApcMode = (KPROCESSOR_MODE) Mode;
516 Apc->ApcMode = KernelMode;
521 NtQueueApcRundownRoutine(PKAPC Apc)
527 NtQueueApcKernelRoutine(PKAPC Apc,
528 PKNORMAL_ROUTINE* NormalRoutine,
529 PVOID* NormalContext,
530 PVOID* SystemArgument1,
531 PVOID* SystemArgument2)
537 NtQueueApcThread(HANDLE ThreadHandle,
538 PKNORMAL_ROUTINE ApcRoutine,
540 PVOID SystemArgument1,
541 PVOID SystemArgument2)
547 Status = ObReferenceObjectByHandle(ThreadHandle,
548 THREAD_ALL_ACCESS, /* FIXME */
553 if (!NT_SUCCESS(Status))
558 Apc = ExAllocatePoolWithTag(NonPagedPool, sizeof(KAPC), TAG_KAPC);
561 ObDereferenceObject(Thread);
562 return(STATUS_NO_MEMORY);
567 OriginalApcEnvironment,
568 NtQueueApcKernelRoutine,
569 NtQueueApcRundownRoutine,
573 KeInsertQueueApc(Apc,
578 ObDereferenceObject(Thread);
579 return(STATUS_SUCCESS);
583 NTSTATUS STDCALL NtTestAlert(VOID)
586 return(STATUS_SUCCESS);
589 VOID PiInitApcManagement(VOID)
591 KeInitializeSpinLock(&PiApcLock);