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
;
329 if (Apc
->ApcMode
== KernelMode
)
331 InsertTailList(&TargetThread
->ApcState
.ApcListHead
[0],
333 TargetThread
->ApcState
.KernelApcPending
++;
337 InsertTailList(&TargetThread
->ApcState
.ApcListHead
[1],
339 TargetThread
->ApcState
.UserApcPending
++;
341 Apc
->Inserted
= TRUE
;
344 * If this is a kernel-mode APC for the current thread and we are not
345 * inside a critical section or at APC level then call it, in fact we
346 * rely on the side effects of dropping the IRQL level when we release
349 if (Apc
->ApcMode
== KernelMode
&& TargetThread
== KeGetCurrentThread() &&
350 Apc
->NormalRoutine
== NULL
)
352 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
357 * If this is a kernel-mode APC and it is waiting at PASSIVE_LEVEL and
358 * not inside a critical section then wake it up. Otherwise it will
359 * execute the APC as soon as it returns to PASSIVE_LEVEL.
360 * FIXME: If the thread is running on another processor then send an
362 * FIXME: Check if the thread is terminating.
364 if (Apc
->ApcMode
== KernelMode
&& TargetThread
->WaitIrql
< APC_LEVEL
&&
365 Apc
->NormalRoutine
== NULL
)
367 KeRemoveAllWaitsThread(CONTAINING_RECORD(TargetThread
, ETHREAD
, Tcb
),
372 * If this is a 'funny' user-mode APC then mark the thread as
373 * alerted so it will execute the APC on exit from kernel mode. If it
374 * is waiting alertably then wake it up so it can return to user mode.
376 if (Apc
->ApcMode
== KernelMode
&& Apc
->NormalRoutine
!= NULL
)
378 TargetThread
->Alerted
[1] = 1;
379 if (TargetThread
->Alertable
== TRUE
&&
380 TargetThread
->WaitMode
== UserMode
)
384 Thread
= CONTAINING_RECORD(TargetThread
, ETHREAD
, Tcb
);
385 KeRemoveAllWaitsThread(Thread
, STATUS_USER_APC
);
390 * If the thread is waiting alertably then wake it up and it will
391 * return to to user-mode executing the APC in the process. Otherwise the
392 * thread will execute the APC next time it enters an alertable wait.
394 if (Apc
->ApcMode
== UserMode
&& TargetThread
->Alertable
== TRUE
&&
395 TargetThread
->WaitMode
== UserMode
)
399 DPRINT("Resuming thread for user APC\n");
401 Status
= STATUS_USER_APC
;
402 TargetThread
->Alerted
[0] = 1;
403 KeRemoveAllWaitsThread(CONTAINING_RECORD(TargetThread
, ETHREAD
, Tcb
),
406 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
411 KeRemoveQueueApc (PKAPC Apc
)
413 * FUNCTION: Removes APC object from the apc queue
415 * Apc = APC to remove
416 * RETURNS: TRUE if the APC was in the queue
418 * NOTE: This function is not exported.
422 PKTHREAD TargetThread
;
424 KeRaiseIrql(HIGH_LEVEL
, &oldIrql
);
425 KeAcquireSpinLockAtDpcLevel(&PiApcLock
);
426 if (Apc
->Inserted
== FALSE
)
428 KeReleaseSpinLock(&PiApcLock
, oldIrql
);
432 TargetThread
= Apc
->Thread
;
433 RemoveEntryList(&Apc
->ApcListEntry
);
434 if (Apc
->ApcMode
== KernelMode
)
436 TargetThread
->ApcState
.KernelApcPending
--;
440 TargetThread
->ApcState
.UserApcPending
--;
442 Apc
->Inserted
= FALSE
;
444 KeReleaseSpinLock(&PiApcLock
, oldIrql
);
457 IN PKKERNEL_ROUTINE KernelRoutine
,
458 IN PKRUNDOWN_ROUTINE RundownRoutine
,
459 IN PKNORMAL_ROUTINE NormalRoutine
,
463 * FUNCTION: Initialize an APC object
465 * Apc = Pointer to the APC object to initialized
466 * Thread = Thread the APC is to be delivered to
467 * StateIndex = KAPC_ENVIRONMENT
468 * KernelRoutine = Routine to be called for a kernel-mode APC
469 * RundownRoutine = Routine to be called if the thread has exited with
470 * the APC being executed
471 * NormalRoutine = Routine to be called for a user-mode APC
473 * Context = Parameter to be passed to the APC routine
476 KAPC_ENVIRONMENT Environment
= (KAPC_ENVIRONMENT
) StateIndex
;
478 DPRINT("KeInitializeApc(Apc %x, Thread %x, Environment %d, "
479 "KernelRoutine %x, RundownRoutine %x, NormalRoutine %x, Mode %d, "
480 "Context %x)\n",Apc
,Thread
,Environment
,KernelRoutine
,RundownRoutine
,
481 NormalRoutine
,Mode
,Context
);
483 memset(Apc
, 0, sizeof(KAPC
));
484 Apc
->Thread
= Thread
;
485 Apc
->ApcListEntry
.Flink
= NULL
;
486 Apc
->ApcListEntry
.Blink
= NULL
;
487 Apc
->KernelRoutine
= KernelRoutine
;
488 Apc
->RundownRoutine
= RundownRoutine
;
489 Apc
->NormalRoutine
= NormalRoutine
;
490 Apc
->NormalContext
= Context
;
491 Apc
->Inserted
= FALSE
;
493 if (Environment
== CurrentApcEnvironment
)
495 Apc
->ApcStateIndex
= Thread
->ApcStateIndex
;
499 Apc
->ApcStateIndex
= Environment
;
502 if (Apc
->NormalRoutine
!= NULL
)
504 Apc
->ApcMode
= (KPROCESSOR_MODE
) Mode
;
508 Apc
->ApcMode
= KernelMode
;
513 NtQueueApcRundownRoutine(PKAPC Apc
)
519 NtQueueApcKernelRoutine(PKAPC Apc
,
520 PKNORMAL_ROUTINE
* NormalRoutine
,
521 PVOID
* NormalContext
,
522 PVOID
* SystemArgument1
,
523 PVOID
* SystemArgument2
)
529 NtQueueApcThread(HANDLE ThreadHandle
,
530 PKNORMAL_ROUTINE ApcRoutine
,
532 PVOID SystemArgument1
,
533 PVOID SystemArgument2
)
539 Status
= ObReferenceObjectByHandle(ThreadHandle
,
540 THREAD_ALL_ACCESS
, /* FIXME */
545 if (!NT_SUCCESS(Status
))
550 Apc
= ExAllocatePoolWithTag(NonPagedPool
, sizeof(KAPC
), TAG_KAPC
);
553 ObDereferenceObject(Thread
);
554 return(STATUS_NO_MEMORY
);
559 OriginalApcEnvironment
,
560 NtQueueApcKernelRoutine
,
561 NtQueueApcRundownRoutine
,
565 KeInsertQueueApc(Apc
,
570 ObDereferenceObject(Thread
);
571 return(STATUS_SUCCESS
);
575 NTSTATUS STDCALL
NtTestAlert(VOID
)
578 return(STATUS_SUCCESS
);
581 VOID
PiInitApcManagement(VOID
)
583 KeInitializeSpinLock(&PiApcLock
);