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 *******************************************************************/
38 VOID
PsTerminateCurrentThread(NTSTATUS ExitStatus
);
40 #define TAG_KAPC TAG('K', 'A', 'P', 'C')
42 /* FUNCTIONS *****************************************************************/
53 return KeGetCurrentThread()->KernelApcDisable
? FALSE
: TRUE
;
56 VOID
KiRundownThread(VOID
)
63 BOOLEAN
KiTestAlert(VOID
)
65 * FUNCTION: Tests whether there are any pending APCs for the current thread
66 * and if so the APCs will be delivered on exit from kernel mode
71 oldIrql
= KeRaiseIrqlToDpcLevel();
72 if (KeGetCurrentThread()->ApcState
.UserApcPending
== 0)
77 KeGetCurrentThread()->Alerted
[0] = 1;
83 KiDeliverNormalApc(VOID
)
85 PETHREAD Thread
= PsGetCurrentThread();
89 PKNORMAL_ROUTINE NormalRoutine
;
91 PVOID SystemArgument1
;
92 PVOID SystemArgument2
;
94 oldlvl
= KeRaiseIrqlToDpcLevel();
95 while(!IsListEmpty(&(Thread
->Tcb
.ApcState
.ApcListHead
[0])))
97 current
= RemoveTailList(&Thread
->Tcb
.ApcState
.ApcListHead
[0]);
98 Apc
= CONTAINING_RECORD(current
, KAPC
, ApcListEntry
);
99 if (Apc
->NormalRoutine
== NULL
)
101 DbgPrint("Exiting kernel with kernel APCs pending.\n");
102 KEBUGCHECKEX(KERNEL_APC_PENDING_DURING_EXIT
, (ULONG
)Apc
,
103 Thread
->Tcb
.KernelApcDisable
, oldlvl
, 0);
105 Apc
->Inserted
= FALSE
;
106 Thread
->Tcb
.ApcState
.KernelApcInProgress
++;
107 Thread
->Tcb
.ApcState
.KernelApcPending
--;
111 ASSERT(Apc
->KernelRoutine
);
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 oldlvl
= KeRaiseIrqlToDpcLevel();
125 Thread
->Tcb
.ApcState
.KernelApcInProgress
--;
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 oldlvl
= KeRaiseIrqlToDpcLevel();
157 current_entry
= Thread
->ApcState
.ApcListHead
[1].Flink
;
160 * Shouldn't happen but check anyway.
162 if (current_entry
== &Thread
->ApcState
.ApcListHead
[1])
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
--;
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 ASSERT(Apc
->KernelRoutine
);
232 Apc
->KernelRoutine(Apc
,
233 (PKNORMAL_ROUTINE
*)&Esp
[1],
238 oldlvl
= KeRaiseIrqlToDpcLevel();
240 Thread
->Alerted
[0] = 0;
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 oldlvl
= KeRaiseIrqlToDpcLevel();
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
--;
282 ASSERT(Apc
->KernelRoutine
);
283 Apc
->KernelRoutine(Apc
,
286 &Apc
->SystemArgument1
,
287 &Apc
->SystemArgument2
);
289 oldlvl
= KeRaiseIrqlToDpcLevel();
290 Thread
->Tcb
.ApcState
.KernelApcInProgress
--;
291 current_entry
= Thread
->Tcb
.ApcState
.ApcListHead
[0].Flink
;
295 current_entry
= current_entry
->Flink
;
305 KeInsertQueueApc (PKAPC Apc
,
306 PVOID SystemArgument1
,
307 PVOID SystemArgument2
,
308 KPRIORITY PriorityBoost
)
310 * FUNCTION: Queues an APC for execution
312 * Apc = APC to be queued
313 * SystemArgument[1-2] = TBD
317 //FIXME: return FALSE if APC can't be queued to target thread (thread has ended)
319 PKTHREAD TargetThread
;
321 DPRINT("KeInsertQueueApc(Apc %x, SystemArgument1 %x, "
322 "SystemArgument2 %x)\n",Apc
,SystemArgument1
,
325 Apc
->SystemArgument1
= SystemArgument1
;
326 Apc
->SystemArgument2
= SystemArgument2
;
331 /* TMN: This code is in error */
332 DbgPrint("KeInsertQueueApc(): multiple APC insertations\n");
339 oldlvl
= KeRaiseIrqlToDpcLevel();
341 TargetThread
= Apc
->Thread
;
343 if (TargetThread
->State
== THREAD_STATE_TERMINATED_1
||
344 TargetThread
->State
== THREAD_STATE_TERMINATED_2
)
350 if (Apc
->ApcMode
== KernelMode
)
352 InsertTailList(&TargetThread
->ApcStatePointer
[(int) Apc
->ApcStateIndex
]->ApcListHead
[0],
354 TargetThread
->ApcStatePointer
[(int) Apc
->ApcStateIndex
]->KernelApcPending
++;
358 InsertTailList(&TargetThread
->ApcStatePointer
[(int) Apc
->ApcStateIndex
]->ApcListHead
[1],
360 TargetThread
->ApcStatePointer
[(int) Apc
->ApcStateIndex
]->UserApcPending
++;
362 Apc
->Inserted
= TRUE
;
365 * If this is a kernel-mode APC for the current thread and we are not
366 * inside a critical section or at APC level then call it, in fact we
367 * rely on the side effects of dropping the IRQL level when we release
370 if (Apc
->ApcMode
== KernelMode
&& TargetThread
== KeGetCurrentThread() &&
371 Apc
->NormalRoutine
== NULL
)
378 * If this is a kernel-mode APC and it is waiting at PASSIVE_LEVEL and
379 * not inside a critical section then wake it up. Otherwise it will
380 * execute the APC as soon as it returns to PASSIVE_LEVEL.
381 * FIXME: If the thread is running on another processor then send an
383 * FIXME: Check if the thread is terminating.
385 if (Apc
->ApcMode
== KernelMode
&& TargetThread
->WaitIrql
< APC_LEVEL
&&
386 Apc
->NormalRoutine
== NULL
)
388 KeRemoveAllWaitsThread(CONTAINING_RECORD(TargetThread
, ETHREAD
, Tcb
),
389 STATUS_KERNEL_APC
, TRUE
);
393 * If this is a 'funny' user-mode APC then mark the thread as
394 * alerted so it will execute the APC on exit from kernel mode. If it
395 * is waiting alertably then wake it up so it can return to user mode.
397 if (Apc
->ApcMode
== KernelMode
&& Apc
->NormalRoutine
!= NULL
)
399 TargetThread
->Alerted
[1] = 1;
400 if (TargetThread
->Alertable
== TRUE
&&
401 TargetThread
->WaitMode
== UserMode
)
405 Thread
= CONTAINING_RECORD(TargetThread
, ETHREAD
, Tcb
);
406 KeRemoveAllWaitsThread(Thread
, STATUS_USER_APC
, TRUE
);
411 * If the thread is waiting alertably then wake it up and it will
412 * return to to user-mode executing the APC in the process. Otherwise the
413 * thread will execute the APC next time it enters an alertable wait.
415 if (Apc
->ApcMode
== UserMode
&& TargetThread
->Alertable
== TRUE
&&
416 TargetThread
->WaitMode
== UserMode
)
420 DPRINT("Resuming thread for user APC\n");
422 Status
= STATUS_USER_APC
;
423 TargetThread
->Alerted
[0] = 1;
424 KeRemoveAllWaitsThread(CONTAINING_RECORD(TargetThread
, ETHREAD
, Tcb
),
425 STATUS_USER_APC
, TRUE
);
432 KeRemoveQueueApc (PKAPC Apc
)
434 * FUNCTION: Removes APC object from the apc queue
436 * Apc = APC to remove
437 * RETURNS: TRUE if the APC was in the queue
439 * NOTE: This function is not exported.
443 PKTHREAD TargetThread
;
445 oldIrql
= KeRaiseIrqlToDpcLevel();
446 if (Apc
->Inserted
== FALSE
)
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 KeLowerIrql(oldIrql
);
476 IN KAPC_ENVIRONMENT TargetEnvironment
,
477 IN PKKERNEL_ROUTINE KernelRoutine
,
478 IN PKRUNDOWN_ROUTINE RundownRoutine OPTIONAL
,
479 IN PKNORMAL_ROUTINE NormalRoutine
,
480 IN KPROCESSOR_MODE Mode
,
483 * FUNCTION: Initialize an APC object
485 * Apc = Pointer to the APC object to initialized
486 * Thread = Thread the APC is to be delivered to
487 * StateIndex = KAPC_ENVIRONMENT
488 * KernelRoutine = Routine to be called for a kernel-mode APC
489 * RundownRoutine = Routine to be called if the thread has exited with
490 * the APC being executed
491 * NormalRoutine = Routine to be called for a user-mode APC
493 * Context = Parameter to be passed to the APC routine
496 DPRINT("KeInitializeApc(Apc %x, Thread %x, Environment %d, "
497 "KernelRoutine %x, RundownRoutine %x, NormalRoutine %x, Mode %d, "
498 "Context %x)\n",Apc
,Thread
,TargetEnvironment
,KernelRoutine
,RundownRoutine
,
499 NormalRoutine
,Mode
,Context
);
501 memset(Apc
, 0, sizeof(KAPC
));
502 Apc
->Thread
= Thread
;
503 Apc
->ApcListEntry
.Flink
= NULL
;
504 Apc
->ApcListEntry
.Blink
= NULL
;
505 Apc
->KernelRoutine
= KernelRoutine
;
506 Apc
->RundownRoutine
= RundownRoutine
;
507 Apc
->NormalRoutine
= NormalRoutine
;
508 Apc
->NormalContext
= Context
;
509 Apc
->Inserted
= FALSE
;
511 if (TargetEnvironment
== CurrentApcEnvironment
)
513 Apc
->ApcStateIndex
= Thread
->ApcStateIndex
;
517 Apc
->ApcStateIndex
= TargetEnvironment
;
520 if (Apc
->NormalRoutine
!= NULL
)
526 Apc
->ApcMode
= KernelMode
;
531 NtQueueApcRundownRoutine(PKAPC Apc
)
537 NtQueueApcKernelRoutine(PKAPC Apc
,
538 PKNORMAL_ROUTINE
* NormalRoutine
,
539 PVOID
* NormalContext
,
540 PVOID
* SystemArgument1
,
541 PVOID
* SystemArgument2
)
547 NtQueueApcThread(HANDLE ThreadHandle
,
548 PKNORMAL_ROUTINE ApcRoutine
,
550 PVOID SystemArgument1
,
551 PVOID SystemArgument2
)
560 Status
= ObReferenceObjectByHandle(ThreadHandle
,
561 THREAD_ALL_ACCESS
, /* FIXME */
566 if (!NT_SUCCESS(Status
))
571 Apc
= ExAllocatePoolWithTag(NonPagedPool
, sizeof(KAPC
), TAG_KAPC
);
574 ObDereferenceObject(Thread
);
575 return(STATUS_NO_MEMORY
);
580 OriginalApcEnvironment
,
581 NtQueueApcKernelRoutine
,
582 NtQueueApcRundownRoutine
,
586 KeInsertQueueApc(Apc
,
591 ObDereferenceObject(Thread
);
592 return(STATUS_SUCCESS
);
596 NTSTATUS STDCALL
NtTestAlert(VOID
)
599 return(STATUS_SUCCESS
);
603 PiInitApcManagement(VOID
)
608 RepairList(PLIST_ENTRY Original
, PLIST_ENTRY Copy
, int Mode
)
610 if (IsListEmpty(&Original
[Mode
]))
612 InitializeListHead(&Copy
[Mode
]);
616 Copy
[Mode
].Flink
->Blink
= &Copy
[Mode
];
617 Copy
[Mode
].Blink
->Flink
= &Copy
[Mode
];
623 KiSwapApcEnvironment(
625 PKPROCESS NewProcess
)
627 if (Thread
->ApcStateIndex
== AttachedApcEnvironment
)
629 /* NewProcess must be the same as in the original-environment */
630 ASSERT(NewProcess
== Thread
->ApcStatePointer
[OriginalApcEnvironment
]->Process
);
633 FIXME: Deliver any pending apc's queued to the attached environment before
634 switching back to the original environment.
635 This is not crucial thou, since i don't think we'll ever target apc's
636 to attached environments (or?)...
637 Remove the following asserts if implementing this.
641 /* we don't support targeting apc's at attached-environments (yet)... */
642 ASSERT(IsListEmpty(&Thread
->ApcState
.ApcListHead
[KernelMode
]));
643 ASSERT(IsListEmpty(&Thread
->ApcState
.ApcListHead
[UserMode
]));
644 ASSERT(Thread
->ApcState
.KernelApcInProgress
== FALSE
);
645 ASSERT(Thread
->ApcState
.KernelApcPending
== FALSE
);
646 ASSERT(Thread
->ApcState
.UserApcPending
== FALSE
);
648 /* restore backup of original environment */
649 Thread
->ApcState
= Thread
->SavedApcState
;
651 RepairList(Thread
->SavedApcState
.ApcListHead
, Thread
->ApcState
.ApcListHead
,
653 RepairList(Thread
->SavedApcState
.ApcListHead
, Thread
->ApcState
.ApcListHead
,
656 /* update environment pointers */
657 Thread
->ApcStatePointer
[OriginalApcEnvironment
] = &Thread
->ApcState
;
658 Thread
->ApcStatePointer
[AttachedApcEnvironment
] = &Thread
->SavedApcState
;
660 /* update current-environment index */
661 Thread
->ApcStateIndex
= OriginalApcEnvironment
;
663 else if (Thread
->ApcStateIndex
== OriginalApcEnvironment
)
665 /* backup original environment */
666 Thread
->SavedApcState
= Thread
->ApcState
;
668 RepairList(Thread
->ApcState
.ApcListHead
, Thread
->SavedApcState
.ApcListHead
,
670 RepairList(Thread
->ApcState
.ApcListHead
, Thread
->SavedApcState
.ApcListHead
,
674 FIXME: Is it possible to target an apc to an attached environment even if the
675 thread is not currently attached???? If so, then this is bougus since it
676 reinitializes the attached apc environment then located in SavedApcState.
680 /* setup a fresh new attached environment */
681 InitializeListHead(&Thread
->ApcState
.ApcListHead
[KernelMode
]);
682 InitializeListHead(&Thread
->ApcState
.ApcListHead
[UserMode
]);
683 Thread
->ApcState
.Process
= NewProcess
;
684 Thread
->ApcState
.KernelApcInProgress
= FALSE
;
685 Thread
->ApcState
.KernelApcPending
= FALSE
;
686 Thread
->ApcState
.UserApcPending
= FALSE
;
688 /* update environment pointers */
689 Thread
->ApcStatePointer
[OriginalApcEnvironment
] = &Thread
->SavedApcState
;
690 Thread
->ApcStatePointer
[AttachedApcEnvironment
] = &Thread
->ApcState
;
692 /* update current-environment index */
693 Thread
->ApcStateIndex
= AttachedApcEnvironment
;
697 /* FIXME: Is this the correct bug code? */
698 KEBUGCHECK(APC_INDEX_MISMATCH
);