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 *****************************************************************/
34 #include <internal/debug.h>
36 /* GLOBALS *******************************************************************/
39 extern KSPIN_LOCK PiThreadListLock
;
41 VOID
PsTerminateCurrentThread(NTSTATUS ExitStatus
);
43 #define TAG_KAPC TAG('K', 'A', 'P', 'C')
45 /* FUNCTIONS *****************************************************************/
59 VOID
KiRundownThread(VOID
)
66 BOOLEAN
KiTestAlert(VOID
)
68 * FUNCTION: Tests whether there are any pending APCs for the current thread
69 * and if so the APCs will be delivered on exit from kernel mode
74 KeAcquireSpinLock(&PiApcLock
, &oldIrql
);
75 if (KeGetCurrentThread()->ApcState
.UserApcPending
== 0)
77 KeReleaseSpinLock(&PiApcLock
, oldIrql
);
80 KeGetCurrentThread()->Alerted
[0] = 1;
81 KeReleaseSpinLock(&PiApcLock
, oldIrql
);
86 KiDeliverNormalApc(VOID
)
88 PETHREAD Thread
= PsGetCurrentThread();
92 PKNORMAL_ROUTINE NormalRoutine
;
94 PVOID SystemArgument1
;
95 PVOID SystemArgument2
;
97 KeAcquireSpinLock(&PiApcLock
, &oldlvl
);
98 while(!IsListEmpty(&(Thread
->Tcb
.ApcState
.ApcListHead
[0])))
100 current
= RemoveTailList(&Thread
->Tcb
.ApcState
.ApcListHead
[0]);
101 Apc
= CONTAINING_RECORD(current
, KAPC
, ApcListEntry
);
102 if (Apc
->NormalRoutine
== NULL
)
104 DbgPrint("Exiting kernel with kernel APCs pending.\n");
105 KEBUGCHECKEX(KERNEL_APC_PENDING_DURING_EXIT
, (ULONG
)Apc
,
106 Thread
->Tcb
.KernelApcDisable
, oldlvl
, 0);
108 Apc
->Inserted
= FALSE
;
109 Thread
->Tcb
.ApcState
.KernelApcInProgress
++;
110 Thread
->Tcb
.ApcState
.KernelApcPending
--;
112 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
114 NormalRoutine
= Apc
->NormalRoutine
;
115 NormalContext
= Apc
->NormalContext
;
116 SystemArgument1
= Apc
->SystemArgument1
;
117 SystemArgument2
= Apc
->SystemArgument2
;
118 Apc
->KernelRoutine(Apc
,
123 NormalRoutine(NormalContext
, SystemArgument1
, SystemArgument2
);
125 KeAcquireSpinLock(&PiApcLock
, &oldlvl
);
126 Thread
->Tcb
.ApcState
.KernelApcInProgress
--;
128 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
132 KiDeliverUserApc(PKTRAP_FRAME TrapFrame
)
134 * FUNCTION: Tests whether there are any pending APCs for the current thread
135 * and if so the APCs will be delivered on exit from kernel mode.
137 * Thread = Thread to test for alerts
138 * UserContext = The user context saved on entry to kernel mode
141 PLIST_ENTRY current_entry
;
148 DPRINT("KiDeliverUserApc(TrapFrame %x/%x)\n", TrapFrame
,
149 KeGetCurrentThread()->TrapFrame
);
150 Thread
= KeGetCurrentThread();
153 * Check for thread termination
156 KeAcquireSpinLock(&PiApcLock
, &oldlvl
);
158 current_entry
= Thread
->ApcState
.ApcListHead
[1].Flink
;
161 * Shouldn't happen but check anyway.
163 if (current_entry
== &Thread
->ApcState
.ApcListHead
[1])
165 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
166 DbgPrint("KiDeliverUserApc called but no APC was pending\n");
170 while (!IsListEmpty(&Thread
->ApcState
.ApcListHead
[1]))
172 current_entry
= RemoveHeadList(&Thread
->ApcState
.ApcListHead
[1]);
173 Apc
= CONTAINING_RECORD(current_entry
, KAPC
, ApcListEntry
);
174 Apc
->Inserted
= FALSE
;
177 * We've dealt with one pending user-mode APC
179 Thread
->ApcState
.UserApcPending
--;
180 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
183 * Save the thread's current context (in other words the registers
184 * that will be restored when it returns to user mode) so the
185 * APC dispatcher can restore them later
187 Context
= (PCONTEXT
)(((PUCHAR
)TrapFrame
->Esp
) - sizeof(CONTEXT
));
188 memset(Context
, 0, sizeof(CONTEXT
));
189 Context
->ContextFlags
= CONTEXT_CONTROL
| CONTEXT_INTEGER
|
190 CONTEXT_SEGMENTS
| CONTEXT_i386
;
191 Context
->SegGs
= TrapFrame
->Gs
;
192 Context
->SegFs
= TrapFrame
->Fs
;
193 Context
->SegEs
= TrapFrame
->Es
;
194 Context
->SegDs
= TrapFrame
->Ds
;
195 Context
->Edi
= TrapFrame
->Edi
;
196 Context
->Esi
= TrapFrame
->Esi
;
197 Context
->Ebx
= TrapFrame
->Ebx
;
198 Context
->Edx
= TrapFrame
->Edx
;
199 Context
->Ecx
= TrapFrame
->Ecx
;
200 Context
->Eax
= TrapFrame
->Eax
;
201 Context
->Ebp
= TrapFrame
->Ebp
;
202 Context
->Eip
= TrapFrame
->Eip
;
203 Context
->SegCs
= TrapFrame
->Cs
;
204 Context
->EFlags
= TrapFrame
->Eflags
;
205 Context
->Esp
= TrapFrame
->Esp
;
206 Context
->SegSs
= TrapFrame
->Ss
;
209 * Setup the trap frame so the thread will start executing at the
210 * APC Dispatcher when it returns to user-mode
212 Esp
= (PULONG
)(((PUCHAR
)TrapFrame
->Esp
) -
213 (sizeof(CONTEXT
) + (6 * sizeof(ULONG
))));
216 Esp
[1] = (ULONG
)Apc
->NormalRoutine
;
217 Esp
[2] = (ULONG
)Apc
->NormalContext
;
218 Esp
[3] = (ULONG
)Apc
->SystemArgument1
;
219 Esp
[4] = (ULONG
)Apc
->SystemArgument2
;
220 Esp
[5] = (ULONG
)Context
;
221 TrapFrame
->Eip
= (ULONG
)LdrpGetSystemDllApcDispatcher();
222 TrapFrame
->Esp
= (ULONG
)Esp
;
226 * Now call for the kernel routine for the APC, which will free
227 * the APC data structure, we can't do this ourselves because
228 * the APC may be embedded in some larger structure e.g. an IRP
229 * We also give the kernel routine a last chance to modify the
230 * arguments to the user APC routine.
232 Apc
->KernelRoutine(Apc
,
233 (PKNORMAL_ROUTINE
*)&Esp
[1],
238 KeAcquireSpinLock(&PiApcLock
, &oldlvl
);
240 Thread
->Alerted
[0] = 0;
241 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
250 KiDeliverApc(ULONG Unknown1
,
254 * FUNCTION: Deliver an APC to the current thread.
255 * NOTES: This is called from the IRQL switching code if the current thread
256 * is returning from an IRQL greater than or equal to APC_LEVEL to
257 * PASSIVE_LEVEL and there are kernel-mode APCs pending. This means any
258 * pending APCs will be delivered after a thread gets a new quantum and
259 * after it wakes from a wait.
262 PETHREAD Thread
= PsGetCurrentThread();
263 PLIST_ENTRY current_entry
;
267 DPRINT("KiDeliverApc()\n");
268 KeAcquireSpinLock(&PiApcLock
, &oldlvl
);
269 current_entry
= Thread
->Tcb
.ApcState
.ApcListHead
[0].Flink
;
270 while(current_entry
!= &Thread
->Tcb
.ApcState
.ApcListHead
[0])
272 Apc
= CONTAINING_RECORD(current_entry
, KAPC
, ApcListEntry
);
273 if (Apc
->NormalRoutine
== NULL
)
275 Apc
->Inserted
= FALSE
;
276 RemoveEntryList(&Apc
->ApcListEntry
);
277 Thread
->Tcb
.ApcState
.KernelApcInProgress
++;
278 Thread
->Tcb
.ApcState
.KernelApcPending
--;
280 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
282 Apc
->KernelRoutine(Apc
,
285 &Apc
->SystemArgument1
,
286 &Apc
->SystemArgument2
);
288 KeAcquireSpinLock(&PiApcLock
, &oldlvl
);
289 Thread
->Tcb
.ApcState
.KernelApcInProgress
--;
290 current_entry
= Thread
->Tcb
.ApcState
.ApcListHead
[0].Flink
;
294 current_entry
= current_entry
->Flink
;
297 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
304 KeInsertQueueApc (PKAPC Apc
,
305 PVOID SystemArgument1
,
306 PVOID SystemArgument2
,
307 KPRIORITY PriorityBoost
)
309 * FUNCTION: Queues an APC for execution
311 * Apc = APC to be queued
312 * SystemArgument[1-2] = TBD
316 //FIXME: return FALSE if APC can't be queued to target thread (thread has ended)
318 PKTHREAD TargetThread
;
320 DPRINT("KeInsertQueueApc(Apc %x, SystemArgument1 %x, "
321 "SystemArgument2 %x)\n",Apc
,SystemArgument1
,
324 Apc
->SystemArgument1
= SystemArgument1
;
325 Apc
->SystemArgument2
= SystemArgument2
;
330 /* TMN: This code is in error */
331 DbgPrint("KeInsertQueueApc(): multiple APC insertations\n");
338 KeAcquireSpinLock(&PiApcLock
, &oldlvl
);
340 TargetThread
= Apc
->Thread
;
342 if (TargetThread
->State
== THREAD_STATE_TERMINATED_1
||
343 TargetThread
->State
== THREAD_STATE_TERMINATED_2
)
345 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
349 if (Apc
->ApcMode
== KernelMode
)
351 InsertTailList(&TargetThread
->ApcState
.ApcListHead
[0],
353 TargetThread
->ApcState
.KernelApcPending
++;
357 InsertTailList(&TargetThread
->ApcState
.ApcListHead
[1],
359 TargetThread
->ApcState
.UserApcPending
++;
361 Apc
->Inserted
= TRUE
;
364 * If this is a kernel-mode APC for the current thread and we are not
365 * inside a critical section or at APC level then call it, in fact we
366 * rely on the side effects of dropping the IRQL level when we release
369 if (Apc
->ApcMode
== KernelMode
&& TargetThread
== KeGetCurrentThread() &&
370 Apc
->NormalRoutine
== NULL
)
372 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
377 * If this is a kernel-mode APC and it is waiting at PASSIVE_LEVEL and
378 * not inside a critical section then wake it up. Otherwise it will
379 * execute the APC as soon as it returns to PASSIVE_LEVEL.
380 * FIXME: If the thread is running on another processor then send an
382 * FIXME: Check if the thread is terminating.
384 if (Apc
->ApcMode
== KernelMode
&& TargetThread
->WaitIrql
< APC_LEVEL
&&
385 Apc
->NormalRoutine
== NULL
)
387 KeRemoveAllWaitsThread(CONTAINING_RECORD(TargetThread
, ETHREAD
, Tcb
),
388 STATUS_KERNEL_APC
, TRUE
);
392 * If this is a 'funny' user-mode APC then mark the thread as
393 * alerted so it will execute the APC on exit from kernel mode. If it
394 * is waiting alertably then wake it up so it can return to user mode.
396 if (Apc
->ApcMode
== KernelMode
&& Apc
->NormalRoutine
!= NULL
)
398 TargetThread
->Alerted
[1] = 1;
399 if (TargetThread
->Alertable
== TRUE
&&
400 TargetThread
->WaitMode
== UserMode
)
404 Thread
= CONTAINING_RECORD(TargetThread
, ETHREAD
, Tcb
);
405 KeRemoveAllWaitsThread(Thread
, STATUS_USER_APC
, TRUE
);
410 * If the thread is waiting alertably then wake it up and it will
411 * return to to user-mode executing the APC in the process. Otherwise the
412 * thread will execute the APC next time it enters an alertable wait.
414 if (Apc
->ApcMode
== UserMode
&& TargetThread
->Alertable
== TRUE
&&
415 TargetThread
->WaitMode
== UserMode
)
419 DPRINT("Resuming thread for user APC\n");
421 Status
= STATUS_USER_APC
;
422 TargetThread
->Alerted
[0] = 1;
423 KeRemoveAllWaitsThread(CONTAINING_RECORD(TargetThread
, ETHREAD
, Tcb
),
424 STATUS_USER_APC
, TRUE
);
426 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
431 KeRemoveQueueApc (PKAPC Apc
)
433 * FUNCTION: Removes APC object from the apc queue
435 * Apc = APC to remove
436 * RETURNS: TRUE if the APC was in the queue
438 * NOTE: This function is not exported.
442 PKTHREAD TargetThread
;
444 KeRaiseIrql(HIGH_LEVEL
, &oldIrql
);
445 KiAcquireSpinLock(&PiApcLock
);
446 if (Apc
->Inserted
== FALSE
)
448 KiReleaseSpinLock(&PiApcLock
);
449 KeLowerIrql(oldIrql
);
453 TargetThread
= Apc
->Thread
;
454 RemoveEntryList(&Apc
->ApcListEntry
);
455 if (Apc
->ApcMode
== KernelMode
)
457 TargetThread
->ApcState
.KernelApcPending
--;
461 TargetThread
->ApcState
.UserApcPending
--;
463 Apc
->Inserted
= FALSE
;
465 KiReleaseSpinLock(&PiApcLock
);
466 KeLowerIrql(oldIrql
);
478 IN KAPC_ENVIRONMENT TargetEnvironment
,
479 IN PKKERNEL_ROUTINE KernelRoutine
,
480 IN PKRUNDOWN_ROUTINE RundownRoutine OPTIONAL
,
481 IN PKNORMAL_ROUTINE NormalRoutine
,
482 IN KPROCESSOR_MODE Mode
,
485 * FUNCTION: Initialize an APC object
487 * Apc = Pointer to the APC object to initialized
488 * Thread = Thread the APC is to be delivered to
489 * StateIndex = KAPC_ENVIRONMENT
490 * KernelRoutine = Routine to be called for a kernel-mode APC
491 * RundownRoutine = Routine to be called if the thread has exited with
492 * the APC being executed
493 * NormalRoutine = Routine to be called for a user-mode APC
495 * Context = Parameter to be passed to the APC routine
498 DPRINT("KeInitializeApc(Apc %x, Thread %x, Environment %d, "
499 "KernelRoutine %x, RundownRoutine %x, NormalRoutine %x, Mode %d, "
500 "Context %x)\n",Apc
,Thread
,TargetEnvironment
,KernelRoutine
,RundownRoutine
,
501 NormalRoutine
,Mode
,Context
);
503 memset(Apc
, 0, sizeof(KAPC
));
504 Apc
->Thread
= Thread
;
505 Apc
->ApcListEntry
.Flink
= NULL
;
506 Apc
->ApcListEntry
.Blink
= NULL
;
507 Apc
->KernelRoutine
= KernelRoutine
;
508 Apc
->RundownRoutine
= RundownRoutine
;
509 Apc
->NormalRoutine
= NormalRoutine
;
510 Apc
->NormalContext
= Context
;
511 Apc
->Inserted
= FALSE
;
513 if (TargetEnvironment
== CurrentApcEnvironment
)
515 Apc
->ApcStateIndex
= Thread
->ApcStateIndex
;
519 Apc
->ApcStateIndex
= TargetEnvironment
;
522 if (Apc
->NormalRoutine
!= NULL
)
528 Apc
->ApcMode
= KernelMode
;
533 NtQueueApcRundownRoutine(PKAPC Apc
)
539 NtQueueApcKernelRoutine(PKAPC Apc
,
540 PKNORMAL_ROUTINE
* NormalRoutine
,
541 PVOID
* NormalContext
,
542 PVOID
* SystemArgument1
,
543 PVOID
* SystemArgument2
)
549 NtQueueApcThread(HANDLE ThreadHandle
,
550 PKNORMAL_ROUTINE ApcRoutine
,
552 PVOID SystemArgument1
,
553 PVOID SystemArgument2
)
562 Status
= ObReferenceObjectByHandle(ThreadHandle
,
563 THREAD_ALL_ACCESS
, /* FIXME */
568 if (!NT_SUCCESS(Status
))
573 Apc
= ExAllocatePoolWithTag(NonPagedPool
, sizeof(KAPC
), TAG_KAPC
);
576 ObDereferenceObject(Thread
);
577 return(STATUS_NO_MEMORY
);
582 OriginalApcEnvironment
,
583 NtQueueApcKernelRoutine
,
584 NtQueueApcRundownRoutine
,
588 KeInsertQueueApc(Apc
,
593 ObDereferenceObject(Thread
);
594 return(STATUS_SUCCESS
);
598 NTSTATUS STDCALL
NtTestAlert(VOID
)
601 return(STATUS_SUCCESS
);
605 PiInitApcManagement(VOID
)
607 KeInitializeSpinLock(&PiApcLock
);
612 KiSwapApcEnvironment(
614 PKPROCESS NewProcess
)
616 /* FIXME: Grab the process apc lock or the PiApcLock? And why does both exist? */
618 if (Thread
->ApcStateIndex
== AttachedApcEnvironment
)
620 /* NewProcess must be the same as in the original-environment */
621 assert(NewProcess
== Thread
->ApcStatePointer
[OriginalApcEnvironment
]->Process
);
624 FIXME: Deliver any pending apc's queued to the attached environment before
625 switching back to the original environment.
626 This is not crucial thou, since i don't think we'll ever target apc's
627 to attached environments (or?)...
628 Remove the following asserts if implementing this.
632 /* we don't support targeting apc's at attached-environments (yet)... */
633 assert(IsListEmpty(&Thread
->ApcState
.ApcListHead
[KernelMode
]));
634 assert(IsListEmpty(&Thread
->ApcState
.ApcListHead
[UserMode
]));
635 assert(Thread
->ApcState
.KernelApcInProgress
== FALSE
);
636 assert(Thread
->ApcState
.KernelApcPending
== FALSE
);
637 assert(Thread
->ApcState
.UserApcPending
== FALSE
);
639 /* restore backup of original environment */
640 Thread
->ApcState
= Thread
->SavedApcState
;
642 /* update environment pointers */
643 Thread
->ApcStatePointer
[OriginalApcEnvironment
] = &Thread
->ApcState
;
644 Thread
->ApcStatePointer
[AttachedApcEnvironment
] = &Thread
->SavedApcState
;
646 /* update current-environment index */
647 Thread
->ApcStateIndex
= OriginalApcEnvironment
;
649 else if (Thread
->ApcStateIndex
== OriginalApcEnvironment
)
651 /* backup original environment */
652 Thread
->SavedApcState
= Thread
->ApcState
;
655 FIXME: Is it possible to target an apc to an attached environment even if the
656 thread is not currently attached???? If so, then this is bougus since it
657 reinitializes the attached apc environment then located in SavedApcState.
661 /* setup a fresh new attached environment */
662 InitializeListHead(&Thread
->ApcState
.ApcListHead
[KernelMode
]);
663 InitializeListHead(&Thread
->ApcState
.ApcListHead
[UserMode
]);
664 Thread
->ApcState
.Process
= NewProcess
;
665 Thread
->ApcState
.KernelApcInProgress
= FALSE
;
666 Thread
->ApcState
.KernelApcPending
= FALSE
;
667 Thread
->ApcState
.UserApcPending
= FALSE
;
669 /* update environment pointers */
670 Thread
->ApcStatePointer
[OriginalApcEnvironment
] = &Thread
->SavedApcState
;
671 Thread
->ApcStatePointer
[AttachedApcEnvironment
] = &Thread
->ApcState
;
673 /* update current-environment index */
674 Thread
->ApcStateIndex
= AttachedApcEnvironment
;
678 /* FIXME: Is this the correct bug code? */
679 KEBUGCHECK(APC_INDEX_MISMATCH
);