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 *******************************************************************/
40 VOID
PsTerminateCurrentThread(NTSTATUS ExitStatus
);
42 #define TAG_KAPC TAG('K', 'A', 'P', 'C')
44 /* FUNCTIONS *****************************************************************/
55 return KeGetCurrentThread()->KernelApcDisable
? FALSE
: TRUE
;
58 VOID
KiRundownThread(VOID
)
65 BOOLEAN
KiTestAlert(VOID
)
67 * FUNCTION: Tests whether there are any pending APCs for the current thread
68 * and if so the APCs will be delivered on exit from kernel mode
73 KeAcquireSpinLock(&PiApcLock
, &oldIrql
);
74 if (KeGetCurrentThread()->ApcState
.UserApcPending
== 0)
76 KeReleaseSpinLock(&PiApcLock
, oldIrql
);
79 KeGetCurrentThread()->Alerted
[0] = 1;
80 KeReleaseSpinLock(&PiApcLock
, oldIrql
);
85 KiDeliverNormalApc(VOID
)
87 PETHREAD Thread
= PsGetCurrentThread();
91 PKNORMAL_ROUTINE NormalRoutine
;
93 PVOID SystemArgument1
;
94 PVOID SystemArgument2
;
96 KeAcquireSpinLock(&PiApcLock
, &oldlvl
);
97 while(!IsListEmpty(&(Thread
->Tcb
.ApcState
.ApcListHead
[0])))
99 current
= RemoveTailList(&Thread
->Tcb
.ApcState
.ApcListHead
[0]);
100 Apc
= CONTAINING_RECORD(current
, KAPC
, ApcListEntry
);
101 if (Apc
->NormalRoutine
== NULL
)
103 DbgPrint("Exiting kernel with kernel APCs pending.\n");
104 KEBUGCHECKEX(KERNEL_APC_PENDING_DURING_EXIT
, (ULONG
)Apc
,
105 Thread
->Tcb
.KernelApcDisable
, oldlvl
, 0);
107 Apc
->Inserted
= FALSE
;
108 Thread
->Tcb
.ApcState
.KernelApcInProgress
++;
109 Thread
->Tcb
.ApcState
.KernelApcPending
--;
111 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
113 NormalRoutine
= Apc
->NormalRoutine
;
114 NormalContext
= Apc
->NormalContext
;
115 SystemArgument1
= Apc
->SystemArgument1
;
116 SystemArgument2
= Apc
->SystemArgument2
;
117 Apc
->KernelRoutine(Apc
,
122 NormalRoutine(NormalContext
, SystemArgument1
, SystemArgument2
);
124 KeAcquireSpinLock(&PiApcLock
, &oldlvl
);
125 Thread
->Tcb
.ApcState
.KernelApcInProgress
--;
127 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
131 KiDeliverUserApc(PKTRAP_FRAME TrapFrame
)
133 * FUNCTION: Tests whether there are any pending APCs for the current thread
134 * and if so the APCs will be delivered on exit from kernel mode.
136 * Thread = Thread to test for alerts
137 * UserContext = The user context saved on entry to kernel mode
140 PLIST_ENTRY current_entry
;
147 DPRINT("KiDeliverUserApc(TrapFrame %x/%x)\n", TrapFrame
,
148 KeGetCurrentThread()->TrapFrame
);
149 Thread
= KeGetCurrentThread();
152 * Check for thread termination
155 KeAcquireSpinLock(&PiApcLock
, &oldlvl
);
157 current_entry
= Thread
->ApcState
.ApcListHead
[1].Flink
;
160 * Shouldn't happen but check anyway.
162 if (current_entry
== &Thread
->ApcState
.ApcListHead
[1])
164 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
165 DbgPrint("KiDeliverUserApc called but no APC was pending\n");
169 while (!IsListEmpty(&Thread
->ApcState
.ApcListHead
[1]))
171 current_entry
= RemoveHeadList(&Thread
->ApcState
.ApcListHead
[1]);
172 Apc
= CONTAINING_RECORD(current_entry
, KAPC
, ApcListEntry
);
173 Apc
->Inserted
= FALSE
;
176 * We've dealt with one pending user-mode APC
178 Thread
->ApcState
.UserApcPending
--;
179 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
182 * Save the thread's current context (in other words the registers
183 * that will be restored when it returns to user mode) so the
184 * APC dispatcher can restore them later
186 Context
= (PCONTEXT
)(((PUCHAR
)TrapFrame
->Esp
) - sizeof(CONTEXT
));
187 memset(Context
, 0, sizeof(CONTEXT
));
188 Context
->ContextFlags
= CONTEXT_CONTROL
| CONTEXT_INTEGER
|
189 CONTEXT_SEGMENTS
| CONTEXT_i386
;
190 Context
->SegGs
= TrapFrame
->Gs
;
191 Context
->SegFs
= TrapFrame
->Fs
;
192 Context
->SegEs
= TrapFrame
->Es
;
193 Context
->SegDs
= TrapFrame
->Ds
;
194 Context
->Edi
= TrapFrame
->Edi
;
195 Context
->Esi
= TrapFrame
->Esi
;
196 Context
->Ebx
= TrapFrame
->Ebx
;
197 Context
->Edx
= TrapFrame
->Edx
;
198 Context
->Ecx
= TrapFrame
->Ecx
;
199 Context
->Eax
= TrapFrame
->Eax
;
200 Context
->Ebp
= TrapFrame
->Ebp
;
201 Context
->Eip
= TrapFrame
->Eip
;
202 Context
->SegCs
= TrapFrame
->Cs
;
203 Context
->EFlags
= TrapFrame
->Eflags
;
204 Context
->Esp
= TrapFrame
->Esp
;
205 Context
->SegSs
= TrapFrame
->Ss
;
208 * Setup the trap frame so the thread will start executing at the
209 * APC Dispatcher when it returns to user-mode
211 Esp
= (PULONG
)(((PUCHAR
)TrapFrame
->Esp
) -
212 (sizeof(CONTEXT
) + (6 * sizeof(ULONG
))));
215 Esp
[1] = (ULONG
)Apc
->NormalRoutine
;
216 Esp
[2] = (ULONG
)Apc
->NormalContext
;
217 Esp
[3] = (ULONG
)Apc
->SystemArgument1
;
218 Esp
[4] = (ULONG
)Apc
->SystemArgument2
;
219 Esp
[5] = (ULONG
)Context
;
220 TrapFrame
->Eip
= (ULONG
)LdrpGetSystemDllApcDispatcher();
221 TrapFrame
->Esp
= (ULONG
)Esp
;
225 * Now call for the kernel routine for the APC, which will free
226 * the APC data structure, we can't do this ourselves because
227 * the APC may be embedded in some larger structure e.g. an IRP
228 * We also give the kernel routine a last chance to modify the
229 * arguments to the user APC routine.
231 Apc
->KernelRoutine(Apc
,
232 (PKNORMAL_ROUTINE
*)&Esp
[1],
237 KeAcquireSpinLock(&PiApcLock
, &oldlvl
);
239 Thread
->Alerted
[0] = 0;
240 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
249 KiDeliverApc(ULONG Unknown1
,
253 * FUNCTION: Deliver an APC to the current thread.
254 * NOTES: This is called from the IRQL switching code if the current thread
255 * is returning from an IRQL greater than or equal to APC_LEVEL to
256 * PASSIVE_LEVEL and there are kernel-mode APCs pending. This means any
257 * pending APCs will be delivered after a thread gets a new quantum and
258 * after it wakes from a wait.
261 PETHREAD Thread
= PsGetCurrentThread();
262 PLIST_ENTRY current_entry
;
266 DPRINT("KiDeliverApc()\n");
267 KeAcquireSpinLock(&PiApcLock
, &oldlvl
);
268 current_entry
= Thread
->Tcb
.ApcState
.ApcListHead
[0].Flink
;
269 while(current_entry
!= &Thread
->Tcb
.ApcState
.ApcListHead
[0])
271 Apc
= CONTAINING_RECORD(current_entry
, KAPC
, ApcListEntry
);
272 if (Apc
->NormalRoutine
== NULL
)
274 Apc
->Inserted
= FALSE
;
275 RemoveEntryList(&Apc
->ApcListEntry
);
276 Thread
->Tcb
.ApcState
.KernelApcInProgress
++;
277 Thread
->Tcb
.ApcState
.KernelApcPending
--;
279 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
281 Apc
->KernelRoutine(Apc
,
284 &Apc
->SystemArgument1
,
285 &Apc
->SystemArgument2
);
287 KeAcquireSpinLock(&PiApcLock
, &oldlvl
);
288 Thread
->Tcb
.ApcState
.KernelApcInProgress
--;
289 current_entry
= Thread
->Tcb
.ApcState
.ApcListHead
[0].Flink
;
293 current_entry
= current_entry
->Flink
;
296 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
303 KeInsertQueueApc (PKAPC Apc
,
304 PVOID SystemArgument1
,
305 PVOID SystemArgument2
,
306 KPRIORITY PriorityBoost
)
308 * FUNCTION: Queues an APC for execution
310 * Apc = APC to be queued
311 * SystemArgument[1-2] = TBD
315 //FIXME: return FALSE if APC can't be queued to target thread (thread has ended)
317 PKTHREAD TargetThread
;
319 DPRINT("KeInsertQueueApc(Apc %x, SystemArgument1 %x, "
320 "SystemArgument2 %x)\n",Apc
,SystemArgument1
,
323 Apc
->SystemArgument1
= SystemArgument1
;
324 Apc
->SystemArgument2
= SystemArgument2
;
329 /* TMN: This code is in error */
330 DbgPrint("KeInsertQueueApc(): multiple APC insertations\n");
337 KeAcquireSpinLock(&PiApcLock
, &oldlvl
);
339 TargetThread
= Apc
->Thread
;
341 if (TargetThread
->State
== THREAD_STATE_TERMINATED_1
||
342 TargetThread
->State
== THREAD_STATE_TERMINATED_2
)
344 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
348 if (Apc
->ApcMode
== KernelMode
)
350 InsertTailList(&TargetThread
->ApcStatePointer
[(int) Apc
->ApcStateIndex
]->ApcListHead
[0],
352 TargetThread
->ApcStatePointer
[(int) Apc
->ApcStateIndex
]->KernelApcPending
++;
356 InsertTailList(&TargetThread
->ApcStatePointer
[(int) Apc
->ApcStateIndex
]->ApcListHead
[1],
358 TargetThread
->ApcStatePointer
[(int) Apc
->ApcStateIndex
]->UserApcPending
++;
360 Apc
->Inserted
= TRUE
;
363 * If this is a kernel-mode APC for the current thread and we are not
364 * inside a critical section or at APC level then call it, in fact we
365 * rely on the side effects of dropping the IRQL level when we release
368 if (Apc
->ApcMode
== KernelMode
&& TargetThread
== KeGetCurrentThread() &&
369 Apc
->NormalRoutine
== NULL
)
371 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
376 * If this is a kernel-mode APC and it is waiting at PASSIVE_LEVEL and
377 * not inside a critical section then wake it up. Otherwise it will
378 * execute the APC as soon as it returns to PASSIVE_LEVEL.
379 * FIXME: If the thread is running on another processor then send an
381 * FIXME: Check if the thread is terminating.
383 if (Apc
->ApcMode
== KernelMode
&& TargetThread
->WaitIrql
< APC_LEVEL
&&
384 Apc
->NormalRoutine
== NULL
)
386 KeRemoveAllWaitsThread(CONTAINING_RECORD(TargetThread
, ETHREAD
, Tcb
),
387 STATUS_KERNEL_APC
, TRUE
);
391 * If this is a 'funny' user-mode APC then mark the thread as
392 * alerted so it will execute the APC on exit from kernel mode. If it
393 * is waiting alertably then wake it up so it can return to user mode.
395 if (Apc
->ApcMode
== KernelMode
&& Apc
->NormalRoutine
!= NULL
)
397 TargetThread
->Alerted
[1] = 1;
398 if (TargetThread
->Alertable
== TRUE
&&
399 TargetThread
->WaitMode
== UserMode
)
403 Thread
= CONTAINING_RECORD(TargetThread
, ETHREAD
, Tcb
);
404 KeRemoveAllWaitsThread(Thread
, STATUS_USER_APC
, TRUE
);
409 * If the thread is waiting alertably then wake it up and it will
410 * return to to user-mode executing the APC in the process. Otherwise the
411 * thread will execute the APC next time it enters an alertable wait.
413 if (Apc
->ApcMode
== UserMode
&& TargetThread
->Alertable
== TRUE
&&
414 TargetThread
->WaitMode
== UserMode
)
418 DPRINT("Resuming thread for user APC\n");
420 Status
= STATUS_USER_APC
;
421 TargetThread
->Alerted
[0] = 1;
422 KeRemoveAllWaitsThread(CONTAINING_RECORD(TargetThread
, ETHREAD
, Tcb
),
423 STATUS_USER_APC
, TRUE
);
425 KeReleaseSpinLock(&PiApcLock
, oldlvl
);
430 KeRemoveQueueApc (PKAPC Apc
)
432 * FUNCTION: Removes APC object from the apc queue
434 * Apc = APC to remove
435 * RETURNS: TRUE if the APC was in the queue
437 * NOTE: This function is not exported.
441 PKTHREAD TargetThread
;
443 KeRaiseIrql(HIGH_LEVEL
, &oldIrql
);
444 KiAcquireSpinLock(&PiApcLock
);
445 if (Apc
->Inserted
== FALSE
)
447 KiReleaseSpinLock(&PiApcLock
);
448 KeLowerIrql(oldIrql
);
452 TargetThread
= Apc
->Thread
;
453 RemoveEntryList(&Apc
->ApcListEntry
);
454 if (Apc
->ApcMode
== KernelMode
)
456 TargetThread
->ApcStatePointer
[(int) Apc
->ApcStateIndex
]->KernelApcPending
--;
460 TargetThread
->ApcStatePointer
[(int) Apc
->ApcStateIndex
]->UserApcPending
--;
462 Apc
->Inserted
= FALSE
;
464 KiReleaseSpinLock(&PiApcLock
);
465 KeLowerIrql(oldIrql
);
477 IN KAPC_ENVIRONMENT TargetEnvironment
,
478 IN PKKERNEL_ROUTINE KernelRoutine
,
479 IN PKRUNDOWN_ROUTINE RundownRoutine OPTIONAL
,
480 IN PKNORMAL_ROUTINE NormalRoutine
,
481 IN KPROCESSOR_MODE Mode
,
484 * FUNCTION: Initialize an APC object
486 * Apc = Pointer to the APC object to initialized
487 * Thread = Thread the APC is to be delivered to
488 * StateIndex = KAPC_ENVIRONMENT
489 * KernelRoutine = Routine to be called for a kernel-mode APC
490 * RundownRoutine = Routine to be called if the thread has exited with
491 * the APC being executed
492 * NormalRoutine = Routine to be called for a user-mode APC
494 * Context = Parameter to be passed to the APC routine
497 DPRINT("KeInitializeApc(Apc %x, Thread %x, Environment %d, "
498 "KernelRoutine %x, RundownRoutine %x, NormalRoutine %x, Mode %d, "
499 "Context %x)\n",Apc
,Thread
,TargetEnvironment
,KernelRoutine
,RundownRoutine
,
500 NormalRoutine
,Mode
,Context
);
502 memset(Apc
, 0, sizeof(KAPC
));
503 Apc
->Thread
= Thread
;
504 Apc
->ApcListEntry
.Flink
= NULL
;
505 Apc
->ApcListEntry
.Blink
= NULL
;
506 Apc
->KernelRoutine
= KernelRoutine
;
507 Apc
->RundownRoutine
= RundownRoutine
;
508 Apc
->NormalRoutine
= NormalRoutine
;
509 Apc
->NormalContext
= Context
;
510 Apc
->Inserted
= FALSE
;
512 if (TargetEnvironment
== CurrentApcEnvironment
)
514 Apc
->ApcStateIndex
= Thread
->ApcStateIndex
;
518 Apc
->ApcStateIndex
= TargetEnvironment
;
521 if (Apc
->NormalRoutine
!= NULL
)
527 Apc
->ApcMode
= KernelMode
;
532 NtQueueApcRundownRoutine(PKAPC Apc
)
538 NtQueueApcKernelRoutine(PKAPC Apc
,
539 PKNORMAL_ROUTINE
* NormalRoutine
,
540 PVOID
* NormalContext
,
541 PVOID
* SystemArgument1
,
542 PVOID
* SystemArgument2
)
548 NtQueueApcThread(HANDLE ThreadHandle
,
549 PKNORMAL_ROUTINE ApcRoutine
,
551 PVOID SystemArgument1
,
552 PVOID SystemArgument2
)
561 Status
= ObReferenceObjectByHandle(ThreadHandle
,
562 THREAD_ALL_ACCESS
, /* FIXME */
567 if (!NT_SUCCESS(Status
))
572 Apc
= ExAllocatePoolWithTag(NonPagedPool
, sizeof(KAPC
), TAG_KAPC
);
575 ObDereferenceObject(Thread
);
576 return(STATUS_NO_MEMORY
);
581 OriginalApcEnvironment
,
582 NtQueueApcKernelRoutine
,
583 NtQueueApcRundownRoutine
,
587 KeInsertQueueApc(Apc
,
592 ObDereferenceObject(Thread
);
593 return(STATUS_SUCCESS
);
597 NTSTATUS STDCALL
NtTestAlert(VOID
)
600 return(STATUS_SUCCESS
);
604 PiInitApcManagement(VOID
)
606 KeInitializeSpinLock(&PiApcLock
);
610 RepairList(PLIST_ENTRY Original
, PLIST_ENTRY Copy
, int Mode
)
612 if (IsListEmpty(&Original
[Mode
]))
614 InitializeListHead(&Copy
[Mode
]);
618 Copy
[Mode
].Flink
->Blink
= &Copy
[Mode
];
619 Copy
[Mode
].Blink
->Flink
= &Copy
[Mode
];
625 KiSwapApcEnvironment(
627 PKPROCESS NewProcess
)
629 /* FIXME: Grab the process apc lock or the PiApcLock? And why does both exist? */
631 if (Thread
->ApcStateIndex
== AttachedApcEnvironment
)
633 /* NewProcess must be the same as in the original-environment */
634 assert(NewProcess
== Thread
->ApcStatePointer
[OriginalApcEnvironment
]->Process
);
637 FIXME: Deliver any pending apc's queued to the attached environment before
638 switching back to the original environment.
639 This is not crucial thou, since i don't think we'll ever target apc's
640 to attached environments (or?)...
641 Remove the following asserts if implementing this.
645 /* we don't support targeting apc's at attached-environments (yet)... */
646 assert(IsListEmpty(&Thread
->ApcState
.ApcListHead
[KernelMode
]));
647 assert(IsListEmpty(&Thread
->ApcState
.ApcListHead
[UserMode
]));
648 assert(Thread
->ApcState
.KernelApcInProgress
== FALSE
);
649 assert(Thread
->ApcState
.KernelApcPending
== FALSE
);
650 assert(Thread
->ApcState
.UserApcPending
== FALSE
);
652 /* restore backup of original environment */
653 Thread
->ApcState
= Thread
->SavedApcState
;
655 RepairList(Thread
->SavedApcState
.ApcListHead
, Thread
->ApcState
.ApcListHead
,
657 RepairList(Thread
->SavedApcState
.ApcListHead
, Thread
->ApcState
.ApcListHead
,
660 /* update environment pointers */
661 Thread
->ApcStatePointer
[OriginalApcEnvironment
] = &Thread
->ApcState
;
662 Thread
->ApcStatePointer
[AttachedApcEnvironment
] = &Thread
->SavedApcState
;
664 /* update current-environment index */
665 Thread
->ApcStateIndex
= OriginalApcEnvironment
;
667 else if (Thread
->ApcStateIndex
== OriginalApcEnvironment
)
669 /* backup original environment */
670 Thread
->SavedApcState
= Thread
->ApcState
;
672 RepairList(Thread
->ApcState
.ApcListHead
, Thread
->SavedApcState
.ApcListHead
,
674 RepairList(Thread
->ApcState
.ApcListHead
, Thread
->SavedApcState
.ApcListHead
,
678 FIXME: Is it possible to target an apc to an attached environment even if the
679 thread is not currently attached???? If so, then this is bougus since it
680 reinitializes the attached apc environment then located in SavedApcState.
684 /* setup a fresh new attached environment */
685 InitializeListHead(&Thread
->ApcState
.ApcListHead
[KernelMode
]);
686 InitializeListHead(&Thread
->ApcState
.ApcListHead
[UserMode
]);
687 Thread
->ApcState
.Process
= NewProcess
;
688 Thread
->ApcState
.KernelApcInProgress
= FALSE
;
689 Thread
->ApcState
.KernelApcPending
= FALSE
;
690 Thread
->ApcState
.UserApcPending
= FALSE
;
692 /* update environment pointers */
693 Thread
->ApcStatePointer
[OriginalApcEnvironment
] = &Thread
->SavedApcState
;
694 Thread
->ApcStatePointer
[AttachedApcEnvironment
] = &Thread
->ApcState
;
696 /* update current-environment index */
697 Thread
->ApcStateIndex
= AttachedApcEnvironment
;
701 /* FIXME: Is this the correct bug code? */
702 KEBUGCHECK(APC_INDEX_MISMATCH
);