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 *****************************************************************/
35 #include <internal/debug.h>
38 /* GLOBALS *******************************************************************/
41 extern KSPIN_LOCK PiThreadListLock
;
43 VOID
PsTerminateCurrentThread(NTSTATUS ExitStatus
);
45 #define TAG_KAPC TAG('K', 'A', 'P', 'C')
47 /* FUNCTIONS *****************************************************************/
49 VOID
KiRundownThread(VOID
)
56 BOOLEAN
KiTestAlert(VOID
)
58 * FUNCTION: Tests whether there are any pending APCs for the current thread
59 * and if so the APCs will be delivered on exit from kernel mode
64 KeAcquireSpinLock(&PiApcLock
, &oldIrql
);
65 if (KeGetCurrentThread()->ApcState
.UserApcPending
== 0)
67 KeReleaseSpinLock(&PiApcLock
, oldIrql
);
70 KeGetCurrentThread()->Alerted
[0] = 1;
71 KeReleaseSpinLock(&PiApcLock
, oldIrql
);
76 KiDeliverNormalApc(VOID
)
78 PETHREAD Thread
= PsGetCurrentThread();
82 PKNORMAL_ROUTINE NormalRoutine
;
84 PVOID SystemArgument1
;
85 PVOID SystemArgument2
;
87 KeAcquireSpinLock(&PiApcLock
, &oldlvl
);
88 while(!IsListEmpty(&(Thread
->Tcb
.ApcState
.ApcListHead
[0])))
90 current
= Thread
->Tcb
.ApcState
.ApcListHead
[0].Blink
;
91 Apc
= CONTAINING_RECORD(current
, KAPC
, ApcListEntry
);
92 if (Apc
->NormalRoutine
== NULL
)
94 DbgPrint("Exiting kernel with kernel APCs pending.\n");
97 (VOID
)RemoveTailList(&Thread
->Tcb
.ApcState
.ApcListHead
[0]);
98 Apc
->Inserted
= FALSE
;
99 Thread
->Tcb
.ApcState
.KernelApcInProgress
++;
100 Thread
->Tcb
.ApcState
.KernelApcPending
--;
102 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
104 NormalRoutine
= Apc
->NormalRoutine
;
105 NormalContext
= Apc
->NormalContext
;
106 SystemArgument1
= Apc
->SystemArgument1
;
107 SystemArgument2
= Apc
->SystemArgument2
;
108 Apc
->KernelRoutine(Apc
,
113 NormalRoutine(NormalContext
, SystemArgument1
, SystemArgument2
);
115 KeAcquireSpinLock(&PiApcLock
, &oldlvl
);
116 Thread
->Tcb
.ApcState
.KernelApcInProgress
--;
118 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
122 KiDeliverUserApc(PKTRAP_FRAME TrapFrame
)
124 * FUNCTION: Tests whether there are any pending APCs for the current thread
125 * and if so the APCs will be delivered on exit from kernel mode.
127 * Thread = Thread to test for alerts
128 * UserContext = The user context saved on entry to kernel mode
131 PLIST_ENTRY current_entry
;
138 DPRINT("KiDeliverUserApc(TrapFrame %x/%x)\n", TrapFrame
,
139 KeGetCurrentThread()->TrapFrame
);
140 Thread
= KeGetCurrentThread();
143 * Check for thread termination
146 KeAcquireSpinLock(&PiApcLock
, &oldlvl
);
148 current_entry
= Thread
->ApcState
.ApcListHead
[1].Flink
;
151 * Shouldn't happen but check anyway.
153 if (current_entry
== &Thread
->ApcState
.ApcListHead
[1])
155 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
156 DbgPrint("KiDeliverUserApc called but no APC was pending\n");
160 while (!IsListEmpty(&Thread
->ApcState
.ApcListHead
[1]))
162 current_entry
= RemoveHeadList(&Thread
->ApcState
.ApcListHead
[1]);
163 Apc
= CONTAINING_RECORD(current_entry
, KAPC
, ApcListEntry
);
164 Apc
->Inserted
= FALSE
;
165 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
168 * Save the thread's current context (in other words the registers
169 * that will be restored when it returns to user mode) so the
170 * APC dispatcher can restore them later
172 Context
= (PCONTEXT
)(((PUCHAR
)TrapFrame
->Esp
) - sizeof(CONTEXT
));
173 memset(Context
, 0, sizeof(CONTEXT
));
174 Context
->ContextFlags
= CONTEXT_CONTROL
| CONTEXT_INTEGER
|
175 CONTEXT_SEGMENTS
| CONTEXT_i386
;
176 Context
->SegGs
= TrapFrame
->Gs
;
177 Context
->SegFs
= TrapFrame
->Fs
;
178 Context
->SegEs
= TrapFrame
->Es
;
179 Context
->SegDs
= TrapFrame
->Ds
;
180 Context
->Edi
= TrapFrame
->Edi
;
181 Context
->Esi
= TrapFrame
->Esi
;
182 Context
->Ebx
= TrapFrame
->Ebx
;
183 Context
->Edx
= TrapFrame
->Edx
;
184 Context
->Ecx
= TrapFrame
->Ecx
;
185 Context
->Eax
= TrapFrame
->Eax
;
186 Context
->Ebp
= TrapFrame
->Ebp
;
187 Context
->Eip
= TrapFrame
->Eip
;
188 Context
->SegCs
= TrapFrame
->Cs
;
189 Context
->EFlags
= TrapFrame
->Eflags
;
190 Context
->Esp
= TrapFrame
->Esp
;
191 Context
->SegSs
= TrapFrame
->Ss
;
194 * Setup the trap frame so the thread will start executing at the
195 * APC Dispatcher when it returns to user-mode
197 Esp
= (PULONG
)(((PUCHAR
)TrapFrame
->Esp
) -
198 (sizeof(CONTEXT
) + (6 * sizeof(ULONG
))));
201 Esp
[1] = (ULONG
)Apc
->NormalRoutine
;
202 Esp
[2] = (ULONG
)Apc
->NormalContext
;
203 Esp
[3] = (ULONG
)Apc
->SystemArgument1
;
204 Esp
[4] = (ULONG
)Apc
->SystemArgument2
;
205 Esp
[5] = (ULONG
)Context
;
206 TrapFrame
->Eip
= (ULONG
)LdrpGetSystemDllApcDispatcher();
207 TrapFrame
->Esp
= (ULONG
)Esp
;
210 * We've dealt with one pending user-mode APC
212 Thread
->ApcState
.UserApcPending
--;
215 * Now call for the kernel routine for the APC, which will free
216 * the APC data structure, we can't do this ourselves because
217 * the APC may be embedded in some larger structure e.g. an IRP
218 * We also give the kernel routine a last chance to modify the
219 * arguments to the user APC routine.
221 Apc
->KernelRoutine(Apc
,
222 (PKNORMAL_ROUTINE
*)&Esp
[1],
227 KeAcquireSpinLock(&PiApcLock
, &oldlvl
);
229 Thread
->Alerted
[0] = 0;
230 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
236 KiDeliverApc(ULONG Unknown1
,
240 * FUNCTION: Deliver an APC to the current thread.
241 * NOTES: This is called from the IRQL switching code if the current thread
242 * is returning from an IRQL greater than or equal to APC_LEVEL to
243 * PASSIVE_LEVEL and there are kernel-mode APCs pending. This means any
244 * pending APCs will be delivered after a thread gets a new quantum and
245 * after it wakes from a wait.
248 PETHREAD Thread
= PsGetCurrentThread();
249 PLIST_ENTRY current_entry
;
253 DPRINT("KiDeliverApc()\n");
254 KeAcquireSpinLock(&PiApcLock
, &oldlvl
);
255 current_entry
= Thread
->Tcb
.ApcState
.ApcListHead
[0].Flink
;
256 while(current_entry
!= &Thread
->Tcb
.ApcState
.ApcListHead
[0])
258 Apc
= CONTAINING_RECORD(current_entry
, KAPC
, ApcListEntry
);
259 if (Apc
->NormalRoutine
== NULL
)
261 current_entry
= current_entry
->Flink
;
262 Apc
->Inserted
= FALSE
;
263 RemoveEntryList(&Apc
->ApcListEntry
);
264 Thread
->Tcb
.ApcState
.KernelApcInProgress
++;
265 Thread
->Tcb
.ApcState
.KernelApcPending
--;
267 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
269 Apc
->KernelRoutine(Apc
,
272 &Apc
->SystemArgument1
,
273 &Apc
->SystemArgument2
);
275 KeAcquireSpinLock(&PiApcLock
, &oldlvl
);
276 Thread
->Tcb
.ApcState
.KernelApcInProgress
--;
280 current_entry
= current_entry
->Flink
;
283 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
287 KeInsertQueueApc (PKAPC Apc
,
288 PVOID SystemArgument1
,
289 PVOID SystemArgument2
,
292 * FUNCTION: Queues an APC for execution
294 * Apc = APC to be queued
295 * SystemArgument[1-2] = TBD
300 PKTHREAD TargetThread
;
302 DPRINT("KeInsertQueueApc(Apc %x, SystemArgument1 %x, "
303 "SystemArgument2 %x, Mode %d)\n",Apc
,SystemArgument1
,
304 SystemArgument2
,Mode
);
306 KeAcquireSpinLock(&PiApcLock
, &oldlvl
);
308 Apc
->SystemArgument1
= SystemArgument1
;
309 Apc
->SystemArgument2
= SystemArgument2
;
313 DbgPrint("KeInsertQueueApc(): multiple APC insertations\n");
317 TargetThread
= Apc
->Thread
;
318 if (Apc
->ApcMode
== KernelMode
)
320 InsertTailList(&TargetThread
->ApcState
.ApcListHead
[0],
322 TargetThread
->ApcState
.KernelApcPending
++;
326 InsertTailList(&TargetThread
->ApcState
.ApcListHead
[1],
328 TargetThread
->ApcState
.UserApcPending
++;
330 Apc
->Inserted
= TRUE
;
333 * If this is a kernel-mode APC for the current thread and we are not
334 * inside a critical section or at APC level then call it, in fact we
335 * rely on the side effects of dropping the IRQL level when we release
338 if (Apc
->ApcMode
== KernelMode
&& TargetThread
== KeGetCurrentThread() &&
339 Apc
->NormalRoutine
== NULL
)
341 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
346 * If this is a kernel-mode APC and it is waiting at PASSIVE_LEVEL and
347 * not inside a critical section then wake it up. Otherwise it will
348 * execute the APC as soon as it returns to PASSIVE_LEVEL.
349 * FIXME: If the thread is running on another processor then send an
351 * FIXME: Check if the thread is terminating.
353 if (Apc
->ApcMode
== KernelMode
&& TargetThread
->WaitIrql
< APC_LEVEL
&&
354 Apc
->NormalRoutine
== NULL
)
356 KeRemoveAllWaitsThread(CONTAINING_RECORD(TargetThread
, ETHREAD
, Tcb
),
361 * If this is a 'funny' user-mode APC then mark the thread as
362 * alerted so it will execute the APC on exit from kernel mode. If it
363 * is waiting alertably then wake it up so it can return to user mode.
365 if (Apc
->ApcMode
== KernelMode
&& Apc
->NormalRoutine
!= NULL
)
367 TargetThread
->Alerted
[1] = 1;
368 if (TargetThread
->Alertable
== TRUE
&&
369 TargetThread
->WaitMode
== UserMode
)
373 Thread
= CONTAINING_RECORD(TargetThread
, ETHREAD
, Tcb
);
374 KeRemoveAllWaitsThread(Thread
, STATUS_USER_APC
);
379 * If the thread is waiting alertably then wake it up and it will
380 * return to to user-mode executing the APC in the process. Otherwise the
381 * thread will execute the APC next time it enters an alertable wait.
383 if (Apc
->ApcMode
== UserMode
&& TargetThread
->Alertable
== TRUE
&&
384 TargetThread
->WaitMode
== UserMode
)
388 DPRINT("Resuming thread for user APC\n");
390 Status
= STATUS_USER_APC
;
391 TargetThread
->Alerted
[0] = 1;
392 KeRemoveAllWaitsThread(CONTAINING_RECORD(TargetThread
, ETHREAD
, Tcb
),
395 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
399 KeRemoveQueueApc (PKAPC Apc
)
401 * FUNCTION: Removes APC object from the apc queue
403 * Apc = APC to remove
404 * RETURNS: TRUE if the APC was in the queue
406 * NOTE: This function is not exported.
410 PKTHREAD TargetThread
;
412 KeAcquireSpinLockAtDpcLevel(&PiApcLock
);
413 KeRaiseIrql(HIGH_LEVEL
, &oldIrql
);
414 if (Apc
->Inserted
== FALSE
)
416 KeReleaseSpinLock(&PiApcLock
, oldIrql
);
420 TargetThread
= Apc
->Thread
;
421 RemoveEntryList(&Apc
->ApcListEntry
);
422 if (Apc
->ApcMode
== KernelMode
)
424 TargetThread
->ApcState
.KernelApcPending
--;
428 TargetThread
->ApcState
.UserApcPending
--;
430 Apc
->Inserted
= FALSE
;
432 KeReleaseSpinLock(&PiApcLock
, oldIrql
);
438 KeInitializeApc(PKAPC Apc
,
441 PKKERNEL_ROUTINE KernelRoutine
,
442 PKRUNDOWN_ROUTINE RundownRoutine
,
443 PKNORMAL_ROUTINE NormalRoutine
,
447 * FUNCTION: Initialize an APC object
449 * Apc = Pointer to the APC object to initialized
450 * Thread = Thread the APC is to be delivered to
452 * KernelRoutine = Routine to be called for a kernel-mode APC
453 * RundownRoutine = Routine to be called if the thread has exited with
454 * the APC being executed
455 * NormalRoutine = Routine to be called for a user-mode APC
457 * Context = Parameter to be passed to the APC routine
460 DPRINT("KeInitializeApc(Apc %x, Thread %x, StateIndex %d, "
461 "KernelRoutine %x, RundownRoutine %x, NormalRoutine %x, Mode %d, "
462 "Context %x)\n",Apc
,Thread
,StateIndex
,KernelRoutine
,RundownRoutine
,
463 NormalRoutine
,Mode
,Context
);
465 memset(Apc
, 0, sizeof(KAPC
));
466 Apc
->Thread
= Thread
;
467 Apc
->ApcListEntry
.Flink
= NULL
;
468 Apc
->ApcListEntry
.Blink
= NULL
;
469 Apc
->KernelRoutine
= KernelRoutine
;
470 Apc
->RundownRoutine
= RundownRoutine
;
471 Apc
->NormalRoutine
= NormalRoutine
;
472 Apc
->NormalContext
= Context
;
473 Apc
->Inserted
= FALSE
;
474 Apc
->ApcStateIndex
= StateIndex
;
475 if (Apc
->NormalRoutine
!= NULL
)
481 Apc
->ApcMode
= KernelMode
;
486 NtQueueApcRundownRoutine(PKAPC Apc
)
492 NtQueueApcKernelRoutine(PKAPC Apc
,
493 PKNORMAL_ROUTINE
* NormalRoutine
,
494 PVOID
* NormalContext
,
495 PVOID
* SystemArgument1
,
496 PVOID
* SystemArgument2
)
502 NtQueueApcThread(HANDLE ThreadHandle
,
503 PKNORMAL_ROUTINE ApcRoutine
,
505 PVOID SystemArgument1
,
506 PVOID SystemArgument2
)
512 Status
= ObReferenceObjectByHandle(ThreadHandle
,
513 THREAD_ALL_ACCESS
, /* FIXME */
518 if (!NT_SUCCESS(Status
))
523 Apc
= ExAllocatePoolWithTag(NonPagedPool
, sizeof(KAPC
), TAG_KAPC
);
526 ObDereferenceObject(Thread
);
527 return(STATUS_NO_MEMORY
);
533 NtQueueApcKernelRoutine
,
534 NtQueueApcRundownRoutine
,
538 KeInsertQueueApc(Apc
,
543 ObDereferenceObject(Thread
);
544 return(STATUS_SUCCESS
);
548 NTSTATUS STDCALL
NtTestAlert(VOID
)
551 return(STATUS_SUCCESS
);
554 VOID
PiInitApcManagement(VOID
)
556 KeInitializeSpinLock(&PiApcLock
);