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>
38 #include <reactos/bugcodes.h>
41 #include <internal/debug.h>
43 /* GLOBALS *******************************************************************/
46 extern KSPIN_LOCK PiThreadListLock
;
48 VOID
PsTerminateCurrentThread(NTSTATUS ExitStatus
);
50 #define TAG_KAPC TAG('K', 'A', 'P', 'C')
52 /* FUNCTIONS *****************************************************************/
66 VOID
KiRundownThread(VOID
)
73 BOOLEAN
KiTestAlert(VOID
)
75 * FUNCTION: Tests whether there are any pending APCs for the current thread
76 * and if so the APCs will be delivered on exit from kernel mode
81 KeAcquireSpinLock(&PiApcLock
, &oldIrql
);
82 if (KeGetCurrentThread()->ApcState
.UserApcPending
== 0)
84 KeReleaseSpinLock(&PiApcLock
, oldIrql
);
87 KeGetCurrentThread()->Alerted
[0] = 1;
88 KeReleaseSpinLock(&PiApcLock
, oldIrql
);
93 KiDeliverNormalApc(VOID
)
95 PETHREAD Thread
= PsGetCurrentThread();
99 PKNORMAL_ROUTINE NormalRoutine
;
101 PVOID SystemArgument1
;
102 PVOID SystemArgument2
;
104 KeAcquireSpinLock(&PiApcLock
, &oldlvl
);
105 while(!IsListEmpty(&(Thread
->Tcb
.ApcState
.ApcListHead
[0])))
107 current
= RemoveTailList(&Thread
->Tcb
.ApcState
.ApcListHead
[0]);
108 Apc
= CONTAINING_RECORD(current
, KAPC
, ApcListEntry
);
109 if (Apc
->NormalRoutine
== NULL
)
111 DbgPrint("Exiting kernel with kernel APCs pending.\n");
112 KEBUGCHECKEX(KERNEL_APC_PENDING_DURING_EXIT
, (ULONG
)Apc
,
113 Thread
->Tcb
.KernelApcDisable
, oldlvl
, 0);
115 Apc
->Inserted
= FALSE
;
116 Thread
->Tcb
.ApcState
.KernelApcInProgress
++;
117 Thread
->Tcb
.ApcState
.KernelApcPending
--;
119 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
121 NormalRoutine
= Apc
->NormalRoutine
;
122 NormalContext
= Apc
->NormalContext
;
123 SystemArgument1
= Apc
->SystemArgument1
;
124 SystemArgument2
= Apc
->SystemArgument2
;
125 Apc
->KernelRoutine(Apc
,
130 NormalRoutine(NormalContext
, SystemArgument1
, SystemArgument2
);
132 KeAcquireSpinLock(&PiApcLock
, &oldlvl
);
133 Thread
->Tcb
.ApcState
.KernelApcInProgress
--;
135 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
139 KiDeliverUserApc(PKTRAP_FRAME TrapFrame
)
141 * FUNCTION: Tests whether there are any pending APCs for the current thread
142 * and if so the APCs will be delivered on exit from kernel mode.
144 * Thread = Thread to test for alerts
145 * UserContext = The user context saved on entry to kernel mode
148 PLIST_ENTRY current_entry
;
155 DPRINT("KiDeliverUserApc(TrapFrame %x/%x)\n", TrapFrame
,
156 KeGetCurrentThread()->TrapFrame
);
157 Thread
= KeGetCurrentThread();
160 * Check for thread termination
163 KeAcquireSpinLock(&PiApcLock
, &oldlvl
);
165 current_entry
= Thread
->ApcState
.ApcListHead
[1].Flink
;
168 * Shouldn't happen but check anyway.
170 if (current_entry
== &Thread
->ApcState
.ApcListHead
[1])
172 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
173 DbgPrint("KiDeliverUserApc called but no APC was pending\n");
177 while (!IsListEmpty(&Thread
->ApcState
.ApcListHead
[1]))
179 current_entry
= RemoveHeadList(&Thread
->ApcState
.ApcListHead
[1]);
180 Apc
= CONTAINING_RECORD(current_entry
, KAPC
, ApcListEntry
);
181 Apc
->Inserted
= FALSE
;
184 * We've dealt with one pending user-mode APC
186 Thread
->ApcState
.UserApcPending
--;
187 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
190 * Save the thread's current context (in other words the registers
191 * that will be restored when it returns to user mode) so the
192 * APC dispatcher can restore them later
194 Context
= (PCONTEXT
)(((PUCHAR
)TrapFrame
->Esp
) - sizeof(CONTEXT
));
195 memset(Context
, 0, sizeof(CONTEXT
));
196 Context
->ContextFlags
= CONTEXT_CONTROL
| CONTEXT_INTEGER
|
197 CONTEXT_SEGMENTS
| CONTEXT_i386
;
198 Context
->SegGs
= TrapFrame
->Gs
;
199 Context
->SegFs
= TrapFrame
->Fs
;
200 Context
->SegEs
= TrapFrame
->Es
;
201 Context
->SegDs
= TrapFrame
->Ds
;
202 Context
->Edi
= TrapFrame
->Edi
;
203 Context
->Esi
= TrapFrame
->Esi
;
204 Context
->Ebx
= TrapFrame
->Ebx
;
205 Context
->Edx
= TrapFrame
->Edx
;
206 Context
->Ecx
= TrapFrame
->Ecx
;
207 Context
->Eax
= TrapFrame
->Eax
;
208 Context
->Ebp
= TrapFrame
->Ebp
;
209 Context
->Eip
= TrapFrame
->Eip
;
210 Context
->SegCs
= TrapFrame
->Cs
;
211 Context
->EFlags
= TrapFrame
->Eflags
;
212 Context
->Esp
= TrapFrame
->Esp
;
213 Context
->SegSs
= TrapFrame
->Ss
;
216 * Setup the trap frame so the thread will start executing at the
217 * APC Dispatcher when it returns to user-mode
219 Esp
= (PULONG
)(((PUCHAR
)TrapFrame
->Esp
) -
220 (sizeof(CONTEXT
) + (6 * sizeof(ULONG
))));
223 Esp
[1] = (ULONG
)Apc
->NormalRoutine
;
224 Esp
[2] = (ULONG
)Apc
->NormalContext
;
225 Esp
[3] = (ULONG
)Apc
->SystemArgument1
;
226 Esp
[4] = (ULONG
)Apc
->SystemArgument2
;
227 Esp
[5] = (ULONG
)Context
;
228 TrapFrame
->Eip
= (ULONG
)LdrpGetSystemDllApcDispatcher();
229 TrapFrame
->Esp
= (ULONG
)Esp
;
233 * Now call for the kernel routine for the APC, which will free
234 * the APC data structure, we can't do this ourselves because
235 * the APC may be embedded in some larger structure e.g. an IRP
236 * We also give the kernel routine a last chance to modify the
237 * arguments to the user APC routine.
239 Apc
->KernelRoutine(Apc
,
240 (PKNORMAL_ROUTINE
*)&Esp
[1],
245 KeAcquireSpinLock(&PiApcLock
, &oldlvl
);
247 Thread
->Alerted
[0] = 0;
248 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
257 KiDeliverApc(ULONG Unknown1
,
261 * FUNCTION: Deliver an APC to the current thread.
262 * NOTES: This is called from the IRQL switching code if the current thread
263 * is returning from an IRQL greater than or equal to APC_LEVEL to
264 * PASSIVE_LEVEL and there are kernel-mode APCs pending. This means any
265 * pending APCs will be delivered after a thread gets a new quantum and
266 * after it wakes from a wait.
269 PETHREAD Thread
= PsGetCurrentThread();
270 PLIST_ENTRY current_entry
;
274 DPRINT("KiDeliverApc()\n");
275 KeAcquireSpinLock(&PiApcLock
, &oldlvl
);
276 current_entry
= Thread
->Tcb
.ApcState
.ApcListHead
[0].Flink
;
277 while(current_entry
!= &Thread
->Tcb
.ApcState
.ApcListHead
[0])
279 Apc
= CONTAINING_RECORD(current_entry
, KAPC
, ApcListEntry
);
280 if (Apc
->NormalRoutine
== NULL
)
282 Apc
->Inserted
= FALSE
;
283 RemoveEntryList(&Apc
->ApcListEntry
);
284 Thread
->Tcb
.ApcState
.KernelApcInProgress
++;
285 Thread
->Tcb
.ApcState
.KernelApcPending
--;
287 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
289 Apc
->KernelRoutine(Apc
,
292 &Apc
->SystemArgument1
,
293 &Apc
->SystemArgument2
);
295 KeAcquireSpinLock(&PiApcLock
, &oldlvl
);
296 Thread
->Tcb
.ApcState
.KernelApcInProgress
--;
297 current_entry
= Thread
->Tcb
.ApcState
.ApcListHead
[0].Flink
;
301 current_entry
= current_entry
->Flink
;
304 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
311 KeInsertQueueApc (PKAPC Apc
,
312 PVOID SystemArgument1
,
313 PVOID SystemArgument2
,
314 KPRIORITY PriorityBoost
)
316 * FUNCTION: Queues an APC for execution
318 * Apc = APC to be queued
319 * SystemArgument[1-2] = TBD
323 //FIXME: return FALSE if APC can't be queued to target thread (thread has ended)
325 PKTHREAD TargetThread
;
327 DPRINT("KeInsertQueueApc(Apc %x, SystemArgument1 %x, "
328 "SystemArgument2 %x)\n",Apc
,SystemArgument1
,
331 Apc
->SystemArgument1
= SystemArgument1
;
332 Apc
->SystemArgument2
= SystemArgument2
;
337 /* TMN: This code is in error */
338 DbgPrint("KeInsertQueueApc(): multiple APC insertations\n");
345 KeAcquireSpinLock(&PiApcLock
, &oldlvl
);
347 TargetThread
= Apc
->Thread
;
349 if (TargetThread
->State
== THREAD_STATE_TERMINATED_1
||
350 TargetThread
->State
== THREAD_STATE_TERMINATED_2
)
352 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
356 if (Apc
->ApcMode
== KernelMode
)
358 InsertTailList(&TargetThread
->ApcState
.ApcListHead
[0],
360 TargetThread
->ApcState
.KernelApcPending
++;
364 InsertTailList(&TargetThread
->ApcState
.ApcListHead
[1],
366 TargetThread
->ApcState
.UserApcPending
++;
368 Apc
->Inserted
= TRUE
;
371 * If this is a kernel-mode APC for the current thread and we are not
372 * inside a critical section or at APC level then call it, in fact we
373 * rely on the side effects of dropping the IRQL level when we release
376 if (Apc
->ApcMode
== KernelMode
&& TargetThread
== KeGetCurrentThread() &&
377 Apc
->NormalRoutine
== NULL
)
379 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
384 * If this is a kernel-mode APC and it is waiting at PASSIVE_LEVEL and
385 * not inside a critical section then wake it up. Otherwise it will
386 * execute the APC as soon as it returns to PASSIVE_LEVEL.
387 * FIXME: If the thread is running on another processor then send an
389 * FIXME: Check if the thread is terminating.
391 if (Apc
->ApcMode
== KernelMode
&& TargetThread
->WaitIrql
< APC_LEVEL
&&
392 Apc
->NormalRoutine
== NULL
)
394 KeRemoveAllWaitsThread(CONTAINING_RECORD(TargetThread
, ETHREAD
, Tcb
),
395 STATUS_KERNEL_APC
, TRUE
);
399 * If this is a 'funny' user-mode APC then mark the thread as
400 * alerted so it will execute the APC on exit from kernel mode. If it
401 * is waiting alertably then wake it up so it can return to user mode.
403 if (Apc
->ApcMode
== KernelMode
&& Apc
->NormalRoutine
!= NULL
)
405 TargetThread
->Alerted
[1] = 1;
406 if (TargetThread
->Alertable
== TRUE
&&
407 TargetThread
->WaitMode
== UserMode
)
411 Thread
= CONTAINING_RECORD(TargetThread
, ETHREAD
, Tcb
);
412 KeRemoveAllWaitsThread(Thread
, STATUS_USER_APC
, TRUE
);
417 * If the thread is waiting alertably then wake it up and it will
418 * return to to user-mode executing the APC in the process. Otherwise the
419 * thread will execute the APC next time it enters an alertable wait.
421 if (Apc
->ApcMode
== UserMode
&& TargetThread
->Alertable
== TRUE
&&
422 TargetThread
->WaitMode
== UserMode
)
426 DPRINT("Resuming thread for user APC\n");
428 Status
= STATUS_USER_APC
;
429 TargetThread
->Alerted
[0] = 1;
430 KeRemoveAllWaitsThread(CONTAINING_RECORD(TargetThread
, ETHREAD
, Tcb
),
431 STATUS_USER_APC
, TRUE
);
433 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
438 KeRemoveQueueApc (PKAPC Apc
)
440 * FUNCTION: Removes APC object from the apc queue
442 * Apc = APC to remove
443 * RETURNS: TRUE if the APC was in the queue
445 * NOTE: This function is not exported.
449 PKTHREAD TargetThread
;
451 KeRaiseIrql(HIGH_LEVEL
, &oldIrql
);
452 KiAcquireSpinLock(&PiApcLock
);
453 if (Apc
->Inserted
== FALSE
)
455 KiReleaseSpinLock(&PiApcLock
);
456 KeLowerIrql(oldIrql
);
460 TargetThread
= Apc
->Thread
;
461 RemoveEntryList(&Apc
->ApcListEntry
);
462 if (Apc
->ApcMode
== KernelMode
)
464 TargetThread
->ApcState
.KernelApcPending
--;
468 TargetThread
->ApcState
.UserApcPending
--;
470 Apc
->Inserted
= FALSE
;
472 KiReleaseSpinLock(&PiApcLock
);
473 KeLowerIrql(oldIrql
);
485 IN KAPC_ENVIRONMENT TargetEnvironment
,
486 IN PKKERNEL_ROUTINE KernelRoutine
,
487 IN PKRUNDOWN_ROUTINE RundownRoutine OPTIONAL
,
488 IN PKNORMAL_ROUTINE NormalRoutine
,
489 IN KPROCESSOR_MODE Mode
,
492 * FUNCTION: Initialize an APC object
494 * Apc = Pointer to the APC object to initialized
495 * Thread = Thread the APC is to be delivered to
496 * StateIndex = KAPC_ENVIRONMENT
497 * KernelRoutine = Routine to be called for a kernel-mode APC
498 * RundownRoutine = Routine to be called if the thread has exited with
499 * the APC being executed
500 * NormalRoutine = Routine to be called for a user-mode APC
502 * Context = Parameter to be passed to the APC routine
505 DPRINT("KeInitializeApc(Apc %x, Thread %x, Environment %d, "
506 "KernelRoutine %x, RundownRoutine %x, NormalRoutine %x, Mode %d, "
507 "Context %x)\n",Apc
,Thread
,TargetEnvironment
,KernelRoutine
,RundownRoutine
,
508 NormalRoutine
,Mode
,Context
);
510 memset(Apc
, 0, sizeof(KAPC
));
511 Apc
->Thread
= Thread
;
512 Apc
->ApcListEntry
.Flink
= NULL
;
513 Apc
->ApcListEntry
.Blink
= NULL
;
514 Apc
->KernelRoutine
= KernelRoutine
;
515 Apc
->RundownRoutine
= RundownRoutine
;
516 Apc
->NormalRoutine
= NormalRoutine
;
517 Apc
->NormalContext
= Context
;
518 Apc
->Inserted
= FALSE
;
520 if (TargetEnvironment
== CurrentApcEnvironment
)
522 Apc
->ApcStateIndex
= Thread
->ApcStateIndex
;
526 Apc
->ApcStateIndex
= TargetEnvironment
;
529 if (Apc
->NormalRoutine
!= NULL
)
535 Apc
->ApcMode
= KernelMode
;
540 NtQueueApcRundownRoutine(PKAPC Apc
)
546 NtQueueApcKernelRoutine(PKAPC Apc
,
547 PKNORMAL_ROUTINE
* NormalRoutine
,
548 PVOID
* NormalContext
,
549 PVOID
* SystemArgument1
,
550 PVOID
* SystemArgument2
)
556 NtQueueApcThread(HANDLE ThreadHandle
,
557 PKNORMAL_ROUTINE ApcRoutine
,
559 PVOID SystemArgument1
,
560 PVOID SystemArgument2
)
566 Status
= ObReferenceObjectByHandle(ThreadHandle
,
567 THREAD_ALL_ACCESS
, /* FIXME */
572 if (!NT_SUCCESS(Status
))
577 Apc
= ExAllocatePoolWithTag(NonPagedPool
, sizeof(KAPC
), TAG_KAPC
);
580 ObDereferenceObject(Thread
);
581 return(STATUS_NO_MEMORY
);
586 OriginalApcEnvironment
,
587 NtQueueApcKernelRoutine
,
588 NtQueueApcRundownRoutine
,
592 KeInsertQueueApc(Apc
,
597 ObDereferenceObject(Thread
);
598 return(STATUS_SUCCESS
);
602 NTSTATUS STDCALL
NtTestAlert(VOID
)
605 return(STATUS_SUCCESS
);
609 PiInitApcManagement(VOID
)
611 KeInitializeSpinLock(&PiApcLock
);
616 KiSwapApcEnvironment(
618 PKPROCESS NewProcess
)
620 /* FIXME: Grab the process apc lock or the PiApcLock? And why does both exist? */
622 if (Thread
->ApcStateIndex
== AttachedApcEnvironment
)
624 /* NewProcess must be the same as in the original-environment */
625 assert(NewProcess
== Thread
->ApcStatePointer
[OriginalApcEnvironment
]->Process
);
628 FIXME: Deliver any pending apc's queued to the attached environment before
629 switching back to the original environment.
630 This is not crucial thou, since i don't think we'll ever target apc's
631 to attached environments (or?)...
632 Remove the following asserts if implementing this.
636 /* we don't support targeting apc's at attached-environments (yet)... */
637 assert(IsListEmpty(&Thread
->ApcState
.ApcListHead
[KernelMode
]));
638 assert(IsListEmpty(&Thread
->ApcState
.ApcListHead
[UserMode
]));
639 assert(Thread
->ApcState
.KernelApcInProgress
== FALSE
);
640 assert(Thread
->ApcState
.KernelApcPending
== FALSE
);
641 assert(Thread
->ApcState
.UserApcPending
== FALSE
);
643 /* restore backup of original environment */
644 Thread
->ApcState
= Thread
->SavedApcState
;
646 /* update environment pointers */
647 Thread
->ApcStatePointer
[OriginalApcEnvironment
] = &Thread
->ApcState
;
648 Thread
->ApcStatePointer
[AttachedApcEnvironment
] = &Thread
->SavedApcState
;
650 /* update current-environment index */
651 Thread
->ApcStateIndex
= OriginalApcEnvironment
;
653 else if (Thread
->ApcStateIndex
== OriginalApcEnvironment
)
655 /* backup original environment */
656 Thread
->SavedApcState
= Thread
->ApcState
;
659 FIXME: Is it possible to target an apc to an attached environment even if the
660 thread is not currently attached???? If so, then this is bougus since it
661 reinitializes the attached apc environment then located in SavedApcState.
665 /* setup a fresh new attached environment */
666 InitializeListHead(&Thread
->ApcState
.ApcListHead
[KernelMode
]);
667 InitializeListHead(&Thread
->ApcState
.ApcListHead
[UserMode
]);
668 Thread
->ApcState
.Process
= NewProcess
;
669 Thread
->ApcState
.KernelApcInProgress
= FALSE
;
670 Thread
->ApcState
.KernelApcPending
= FALSE
;
671 Thread
->ApcState
.UserApcPending
= FALSE
;
673 /* update environment pointers */
674 Thread
->ApcStatePointer
[OriginalApcEnvironment
] = &Thread
->SavedApcState
;
675 Thread
->ApcStatePointer
[AttachedApcEnvironment
] = &Thread
->ApcState
;
677 /* update current-environment index */
678 Thread
->ApcStateIndex
= AttachedApcEnvironment
;
682 /* FIXME: Is this the correct bug code? */
683 KEBUGCHECK(APC_INDEX_MISMATCH
);