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
);