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
;
171 * We've dealt with one pending user-mode APC
173 Thread
->ApcState
.UserApcPending
--;
174 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
177 * Save the thread's current context (in other words the registers
178 * that will be restored when it returns to user mode) so the
179 * APC dispatcher can restore them later
181 Context
= (PCONTEXT
)(((PUCHAR
)TrapFrame
->Esp
) - sizeof(CONTEXT
));
182 memset(Context
, 0, sizeof(CONTEXT
));
183 Context
->ContextFlags
= CONTEXT_CONTROL
| CONTEXT_INTEGER
|
184 CONTEXT_SEGMENTS
| CONTEXT_i386
;
185 Context
->SegGs
= TrapFrame
->Gs
;
186 Context
->SegFs
= TrapFrame
->Fs
;
187 Context
->SegEs
= TrapFrame
->Es
;
188 Context
->SegDs
= TrapFrame
->Ds
;
189 Context
->Edi
= TrapFrame
->Edi
;
190 Context
->Esi
= TrapFrame
->Esi
;
191 Context
->Ebx
= TrapFrame
->Ebx
;
192 Context
->Edx
= TrapFrame
->Edx
;
193 Context
->Ecx
= TrapFrame
->Ecx
;
194 Context
->Eax
= TrapFrame
->Eax
;
195 Context
->Ebp
= TrapFrame
->Ebp
;
196 Context
->Eip
= TrapFrame
->Eip
;
197 Context
->SegCs
= TrapFrame
->Cs
;
198 Context
->EFlags
= TrapFrame
->Eflags
;
199 Context
->Esp
= TrapFrame
->Esp
;
200 Context
->SegSs
= TrapFrame
->Ss
;
203 * Setup the trap frame so the thread will start executing at the
204 * APC Dispatcher when it returns to user-mode
206 Esp
= (PULONG
)(((PUCHAR
)TrapFrame
->Esp
) -
207 (sizeof(CONTEXT
) + (6 * sizeof(ULONG
))));
210 Esp
[1] = (ULONG
)Apc
->NormalRoutine
;
211 Esp
[2] = (ULONG
)Apc
->NormalContext
;
212 Esp
[3] = (ULONG
)Apc
->SystemArgument1
;
213 Esp
[4] = (ULONG
)Apc
->SystemArgument2
;
214 Esp
[5] = (ULONG
)Context
;
215 TrapFrame
->Eip
= (ULONG
)LdrpGetSystemDllApcDispatcher();
216 TrapFrame
->Esp
= (ULONG
)Esp
;
220 * Now call for the kernel routine for the APC, which will free
221 * the APC data structure, we can't do this ourselves because
222 * the APC may be embedded in some larger structure e.g. an IRP
223 * We also give the kernel routine a last chance to modify the
224 * arguments to the user APC routine.
226 Apc
->KernelRoutine(Apc
,
227 (PKNORMAL_ROUTINE
*)&Esp
[1],
232 KeAcquireSpinLock(&PiApcLock
, &oldlvl
);
234 Thread
->Alerted
[0] = 0;
235 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
241 KiDeliverApc(ULONG Unknown1
,
245 * FUNCTION: Deliver an APC to the current thread.
246 * NOTES: This is called from the IRQL switching code if the current thread
247 * is returning from an IRQL greater than or equal to APC_LEVEL to
248 * PASSIVE_LEVEL and there are kernel-mode APCs pending. This means any
249 * pending APCs will be delivered after a thread gets a new quantum and
250 * after it wakes from a wait.
253 PETHREAD Thread
= PsGetCurrentThread();
254 PLIST_ENTRY current_entry
;
258 DPRINT("KiDeliverApc()\n");
259 KeAcquireSpinLock(&PiApcLock
, &oldlvl
);
260 current_entry
= Thread
->Tcb
.ApcState
.ApcListHead
[0].Flink
;
261 while(current_entry
!= &Thread
->Tcb
.ApcState
.ApcListHead
[0])
263 Apc
= CONTAINING_RECORD(current_entry
, KAPC
, ApcListEntry
);
264 if (Apc
->NormalRoutine
== NULL
)
266 current_entry
= current_entry
->Flink
;
267 Apc
->Inserted
= FALSE
;
268 RemoveEntryList(&Apc
->ApcListEntry
);
269 Thread
->Tcb
.ApcState
.KernelApcInProgress
++;
270 Thread
->Tcb
.ApcState
.KernelApcPending
--;
272 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
274 Apc
->KernelRoutine(Apc
,
277 &Apc
->SystemArgument1
,
278 &Apc
->SystemArgument2
);
280 KeAcquireSpinLock(&PiApcLock
, &oldlvl
);
281 Thread
->Tcb
.ApcState
.KernelApcInProgress
--;
285 current_entry
= current_entry
->Flink
;
288 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
292 KeInsertQueueApc (PKAPC Apc
,
293 PVOID SystemArgument1
,
294 PVOID SystemArgument2
,
295 KPRIORITY PriorityBoost
)
297 * FUNCTION: Queues an APC for execution
299 * Apc = APC to be queued
300 * SystemArgument[1-2] = TBD
304 //FIXME: return FALSE if APC can't be queued to target thread (thread has ended)
306 PKTHREAD TargetThread
;
308 DPRINT("KeInsertQueueApc(Apc %x, SystemArgument1 %x, "
309 "SystemArgument2 %x)\n",Apc
,SystemArgument1
,
312 KeAcquireSpinLock(&PiApcLock
, &oldlvl
);
314 Apc
->SystemArgument1
= SystemArgument1
;
315 Apc
->SystemArgument2
= SystemArgument2
;
319 DbgPrint("KeInsertQueueApc(): multiple APC insertations\n");
323 TargetThread
= Apc
->Thread
;
324 if (Apc
->ApcMode
== KernelMode
)
326 InsertTailList(&TargetThread
->ApcState
.ApcListHead
[0],
328 TargetThread
->ApcState
.KernelApcPending
++;
332 InsertTailList(&TargetThread
->ApcState
.ApcListHead
[1],
334 TargetThread
->ApcState
.UserApcPending
++;
336 Apc
->Inserted
= TRUE
;
339 * If this is a kernel-mode APC for the current thread and we are not
340 * inside a critical section or at APC level then call it, in fact we
341 * rely on the side effects of dropping the IRQL level when we release
344 if (Apc
->ApcMode
== KernelMode
&& TargetThread
== KeGetCurrentThread() &&
345 Apc
->NormalRoutine
== NULL
)
347 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
352 * If this is a kernel-mode APC and it is waiting at PASSIVE_LEVEL and
353 * not inside a critical section then wake it up. Otherwise it will
354 * execute the APC as soon as it returns to PASSIVE_LEVEL.
355 * FIXME: If the thread is running on another processor then send an
357 * FIXME: Check if the thread is terminating.
359 if (Apc
->ApcMode
== KernelMode
&& TargetThread
->WaitIrql
< APC_LEVEL
&&
360 Apc
->NormalRoutine
== NULL
)
362 KeRemoveAllWaitsThread(CONTAINING_RECORD(TargetThread
, ETHREAD
, Tcb
),
367 * If this is a 'funny' user-mode APC then mark the thread as
368 * alerted so it will execute the APC on exit from kernel mode. If it
369 * is waiting alertably then wake it up so it can return to user mode.
371 if (Apc
->ApcMode
== KernelMode
&& Apc
->NormalRoutine
!= NULL
)
373 TargetThread
->Alerted
[1] = 1;
374 if (TargetThread
->Alertable
== TRUE
&&
375 TargetThread
->WaitMode
== UserMode
)
379 Thread
= CONTAINING_RECORD(TargetThread
, ETHREAD
, Tcb
);
380 KeRemoveAllWaitsThread(Thread
, STATUS_USER_APC
);
385 * If the thread is waiting alertably then wake it up and it will
386 * return to to user-mode executing the APC in the process. Otherwise the
387 * thread will execute the APC next time it enters an alertable wait.
389 if (Apc
->ApcMode
== UserMode
&& TargetThread
->Alertable
== TRUE
&&
390 TargetThread
->WaitMode
== UserMode
)
394 DPRINT("Resuming thread for user APC\n");
396 Status
= STATUS_USER_APC
;
397 TargetThread
->Alerted
[0] = 1;
398 KeRemoveAllWaitsThread(CONTAINING_RECORD(TargetThread
, ETHREAD
, Tcb
),
401 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
406 KeRemoveQueueApc (PKAPC Apc
)
408 * FUNCTION: Removes APC object from the apc queue
410 * Apc = APC to remove
411 * RETURNS: TRUE if the APC was in the queue
413 * NOTE: This function is not exported.
417 PKTHREAD TargetThread
;
419 KeAcquireSpinLockAtDpcLevel(&PiApcLock
);
420 KeRaiseIrql(HIGH_LEVEL
, &oldIrql
);
421 if (Apc
->Inserted
== FALSE
)
423 KeReleaseSpinLock(&PiApcLock
, oldIrql
);
427 TargetThread
= Apc
->Thread
;
428 RemoveEntryList(&Apc
->ApcListEntry
);
429 if (Apc
->ApcMode
== KernelMode
)
431 TargetThread
->ApcState
.KernelApcPending
--;
435 TargetThread
->ApcState
.UserApcPending
--;
437 Apc
->Inserted
= FALSE
;
439 KeReleaseSpinLock(&PiApcLock
, oldIrql
);
449 IN PKKERNEL_ROUTINE KernelRoutine
,
450 IN PKRUNDOWN_ROUTINE RundownRoutine
,
451 IN PKNORMAL_ROUTINE NormalRoutine
,
455 * FUNCTION: Initialize an APC object
457 * Apc = Pointer to the APC object to initialized
458 * Thread = Thread the APC is to be delivered to
459 * StateIndex = KAPC_ENVIRONMENT
460 * KernelRoutine = Routine to be called for a kernel-mode APC
461 * RundownRoutine = Routine to be called if the thread has exited with
462 * the APC being executed
463 * NormalRoutine = Routine to be called for a user-mode APC
465 * Context = Parameter to be passed to the APC routine
468 KAPC_ENVIRONMENT Environment
= (KAPC_ENVIRONMENT
) StateIndex
;
470 DPRINT("KeInitializeApc(Apc %x, Thread %x, Environment %d, "
471 "KernelRoutine %x, RundownRoutine %x, NormalRoutine %x, Mode %d, "
472 "Context %x)\n",Apc
,Thread
,Environment
,KernelRoutine
,RundownRoutine
,
473 NormalRoutine
,Mode
,Context
);
475 memset(Apc
, 0, sizeof(KAPC
));
476 Apc
->Thread
= Thread
;
477 Apc
->ApcListEntry
.Flink
= NULL
;
478 Apc
->ApcListEntry
.Blink
= NULL
;
479 Apc
->KernelRoutine
= KernelRoutine
;
480 Apc
->RundownRoutine
= RundownRoutine
;
481 Apc
->NormalRoutine
= NormalRoutine
;
482 Apc
->NormalContext
= Context
;
483 Apc
->Inserted
= FALSE
;
485 if (Environment
== CurrentApcEnvironment
)
487 Apc
->ApcStateIndex
= Thread
->ApcStateIndex
;
491 Apc
->ApcStateIndex
= Environment
;
494 if (Apc
->NormalRoutine
!= NULL
)
496 Apc
->ApcMode
= (KPROCESSOR_MODE
) Mode
;
500 Apc
->ApcMode
= KernelMode
;
505 NtQueueApcRundownRoutine(PKAPC Apc
)
511 NtQueueApcKernelRoutine(PKAPC Apc
,
512 PKNORMAL_ROUTINE
* NormalRoutine
,
513 PVOID
* NormalContext
,
514 PVOID
* SystemArgument1
,
515 PVOID
* SystemArgument2
)
521 NtQueueApcThread(HANDLE ThreadHandle
,
522 PKNORMAL_ROUTINE ApcRoutine
,
524 PVOID SystemArgument1
,
525 PVOID SystemArgument2
)
531 Status
= ObReferenceObjectByHandle(ThreadHandle
,
532 THREAD_ALL_ACCESS
, /* FIXME */
537 if (!NT_SUCCESS(Status
))
542 Apc
= ExAllocatePoolWithTag(NonPagedPool
, sizeof(KAPC
), TAG_KAPC
);
545 ObDereferenceObject(Thread
);
546 return(STATUS_NO_MEMORY
);
551 OriginalApcEnvironment
,
552 NtQueueApcKernelRoutine
,
553 NtQueueApcRundownRoutine
,
557 KeInsertQueueApc(Apc
,
562 ObDereferenceObject(Thread
);
563 return(STATUS_SUCCESS
);
567 NTSTATUS STDCALL
NtTestAlert(VOID
)
570 return(STATUS_SUCCESS
);
573 VOID
PiInitApcManagement(VOID
)
575 KeInitializeSpinLock(&PiApcLock
);