Fixed timer code
[reactos.git] / reactos / ntoskrnl / ke / apc.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS kernel
4 * FILE: ntoskrnl/ke/apc.c
5 * PURPOSE: Possible implementation of APCs
6 * PROGRAMMER: David Welch (welch@cwcom.net)
7 * UPDATE HISTORY:
8 * Created 22/05/98
9 * 12/11/99: Phillip Susi: Reworked the APC code
10 */
11
12 /* INCLUDES *****************************************************************/
13
14 #include <ddk/ntddk.h>
15 #include <internal/i386/segment.h>
16 #include <internal/ps.h>
17 #include <internal/ke.h>
18 #include <internal/ldr.h>
19
20 #define NDEBUG
21 #include <internal/debug.h>
22
23 /* NOTES *********************************************************************/
24
25
26
27 /* GLOBALS *******************************************************************/
28
29 KSPIN_LOCK PiApcLock;
30 extern KSPIN_LOCK PiThreadListLock;
31
32 VOID PsTerminateCurrentThread(NTSTATUS ExitStatus);
33
34 /* FUNCTIONS *****************************************************************/
35
36 VOID KiRundownThread(VOID)
37 /*
38 * FUNCTION:
39 */
40 {
41 }
42
43 BOOLEAN KiTestAlert(VOID)
44 /*
45 * FUNCTION: Tests whether there are any pending APCs for the current thread
46 * and if so the APCs will be delivered on exit from kernel mode
47 */
48 {
49 KIRQL oldIrql;
50
51 KeAcquireSpinLock(&PiApcLock, &oldIrql);
52 if (KeGetCurrentThread()->ApcState.UserApcPending == 0)
53 {
54 KeReleaseSpinLock(&PiApcLock, oldIrql);
55 return(FALSE);
56 }
57 KeGetCurrentThread()->Alerted[0] = 1;
58 KeReleaseSpinLock(&PiApcLock, oldIrql);
59 return(TRUE);
60 }
61
62 VOID
63 KiDeliverNormalApc(VOID)
64 {
65 PETHREAD Thread = PsGetCurrentThread();
66 PLIST_ENTRY current;
67 PKAPC Apc;
68 KIRQL oldlvl;
69 PKNORMAL_ROUTINE NormalRoutine;
70 PVOID NormalContext;
71 PVOID SystemArgument1;
72 PVOID SystemArgument2;
73
74 KeAcquireSpinLock(&PiApcLock, &oldlvl);
75 while(!IsListEmpty(&(Thread->Tcb.ApcState.ApcListHead[0])))
76 {
77 current = Thread->Tcb.ApcState.ApcListHead[0].Blink;
78 Apc = CONTAINING_RECORD(current, KAPC, ApcListEntry);
79 if (Apc->NormalRoutine != NULL)
80 {
81 (void)RemoveTailList(&Thread->Tcb.ApcState.ApcListHead[0]);
82 Thread->Tcb.ApcState.KernelApcInProgress++;
83 Thread->Tcb.ApcState.KernelApcPending--;
84
85 KeReleaseSpinLock(&PiApcLock, oldlvl);
86
87 NormalRoutine = Apc->NormalRoutine;
88 NormalContext = Apc->NormalContext;
89 SystemArgument1 = Apc->SystemArgument1;
90 SystemArgument2 = Apc->SystemArgument2;
91 Apc->KernelRoutine(Apc,
92 &NormalRoutine,
93 &NormalContext,
94 &SystemArgument1,
95 &SystemArgument2);
96 NormalRoutine(NormalContext, SystemArgument1, SystemArgument2);
97
98 KeAcquireSpinLock(&PiApcLock, &oldlvl);
99 Thread->Tcb.ApcState.KernelApcInProgress--;
100 }
101 }
102 KeReleaseSpinLock(&PiApcLock, oldlvl);
103 }
104
105 BOOLEAN
106 KiDeliverUserApc(PKTRAP_FRAME TrapFrame)
107 /*
108 * FUNCTION: Tests whether there are any pending APCs for the current thread
109 * and if so the APCs will be delivered on exit from kernel mode.
110 * ARGUMENTS:
111 * Thread = Thread to test for alerts
112 * UserContext = The user context saved on entry to kernel mode
113 */
114 {
115 PLIST_ENTRY current_entry;
116 PKAPC Apc;
117 PULONG Esp;
118 PCONTEXT Context;
119 KIRQL oldlvl;
120 PKTHREAD Thread;
121
122 DPRINT("KiDeliverUserApc(TrapFrame %x/%x)\n", TrapFrame,
123 KeGetCurrentThread()->TrapFrame);
124 Thread = KeGetCurrentThread();
125
126 /*
127 * Check for thread termination
128 */
129
130 KeAcquireSpinLock(&PiApcLock, &oldlvl);
131
132 current_entry = Thread->ApcState.ApcListHead[1].Flink;
133
134 /*
135 * Shouldn't happen but check anyway.
136 */
137 if (current_entry == &Thread->ApcState.ApcListHead[1])
138 {
139 KeReleaseSpinLock(&PiApcLock, oldlvl);
140 DbgPrint("KiDeliverUserApc called but no APC was pending\n");
141 return(FALSE);
142 }
143
144 current_entry = RemoveHeadList(&Thread->ApcState.ApcListHead[1]);
145 Apc = CONTAINING_RECORD(current_entry, KAPC, ApcListEntry);
146
147 /*
148 * Save the thread's current context (in other words the registers
149 * that will be restored when it returns to user mode) so the
150 * APC dispatcher can restore them later
151 */
152 Context = (PCONTEXT)(((PUCHAR)TrapFrame->Esp) - sizeof(CONTEXT));
153 memset(Context, 0, sizeof(CONTEXT));
154 Context->ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER |
155 CONTEXT_SEGMENTS | CONTEXT_i386;
156 Context->SegGs = TrapFrame->Gs;
157 Context->SegFs = TrapFrame->Fs;
158 Context->SegEs = TrapFrame->Es;
159 Context->SegDs = TrapFrame->Ds;
160 Context->Edi = TrapFrame->Edi;
161 Context->Esi = TrapFrame->Esi;
162 Context->Ebx = TrapFrame->Ebx;
163 Context->Edx = TrapFrame->Edx;
164 Context->Ecx = TrapFrame->Ecx;
165 Context->Eax = TrapFrame->Eax;
166 Context->Ebp = TrapFrame->Ebp;
167 Context->Eip = TrapFrame->Eip;
168 Context->SegCs = TrapFrame->Cs;
169 Context->EFlags = TrapFrame->Eflags;
170 Context->Esp = TrapFrame->Esp;
171 Context->SegSs = TrapFrame->Ss;
172
173 /*
174 * Setup the trap frame so the thread will start executing at the
175 * APC Dispatcher when it returns to user-mode
176 */
177 Esp = (PULONG)(((PUCHAR)TrapFrame->Esp) -
178 (sizeof(CONTEXT) + (6 * sizeof(ULONG))));
179
180 Esp[0] = 0xdeadbeef;
181 Esp[1] = (ULONG)Apc->NormalRoutine;
182 Esp[2] = (ULONG)Apc->NormalContext;
183 Esp[3] = (ULONG)Apc->SystemArgument1;
184 Esp[4] = (ULONG)Apc->SystemArgument2;
185 Esp[5] = (ULONG)Context;
186 TrapFrame->Eip = (ULONG)LdrpGetSystemDllApcDispatcher();
187 TrapFrame->Esp = (ULONG)Esp;
188
189 /*
190 * We've dealt with one pending user-mode APC
191 */
192 Thread->ApcState.UserApcPending--;
193
194 /*
195 * FIXME: Give some justification for this
196 */
197 KeReleaseSpinLock(&PiApcLock, oldlvl);
198
199 /*
200 * Now call for the kernel routine for the APC, which will free
201 * the APC data structure, we can't do this ourselves because
202 * the APC may be embedded in some larger structure e.g. an IRP
203 * We also give the kernel routine a last chance to modify the arguments to
204 * the user APC routine.
205 */
206 Apc->KernelRoutine(Apc,
207 (PKNORMAL_ROUTINE*)&Esp[1],
208 (PVOID*)&Esp[2],
209 (PVOID*)&Esp[3],
210 (PVOID*)&Esp[4]);
211
212 Thread->Alerted[0] = 0;
213 return(TRUE);
214 }
215
216 VOID STDCALL KiDeliverApc(ULONG Unknown1,
217 ULONG Unknown2,
218 ULONG Unknown3)
219 /*
220 * FUNCTION: Deliver an APC to the current thread.
221 * NOTES: This is called from the IRQL switching code if the current thread
222 * is returning from an IRQL greater than or equal to APC_LEVEL to
223 * PASSIVE_LEVEL and there are kernel-mode APCs pending. This means any
224 * pending APCs will be delivered after a thread gets a new quantum and
225 * after it wakes from a wait.
226 */
227 {
228 PETHREAD Thread = PsGetCurrentThread();
229 PLIST_ENTRY current;
230 PKAPC Apc;
231 KIRQL oldlvl;
232
233 DPRINT("KiDeliverApc()\n");
234 KeAcquireSpinLock(&PiApcLock, &oldlvl);
235 while(!IsListEmpty(&(Thread->Tcb.ApcState.ApcListHead[0])))
236 {
237 current = Thread->Tcb.ApcState.ApcListHead[0].Blink;
238 Apc = CONTAINING_RECORD(current, KAPC, ApcListEntry);
239 if (Apc->NormalRoutine == NULL)
240 {
241 (void)RemoveTailList(&Thread->Tcb.ApcState.ApcListHead[0]);
242 Thread->Tcb.ApcState.KernelApcInProgress++;
243 Thread->Tcb.ApcState.KernelApcPending--;
244
245 KeReleaseSpinLock(&PiApcLock, oldlvl);
246
247 Apc = CONTAINING_RECORD(current, KAPC, ApcListEntry);
248 Apc->KernelRoutine(Apc,
249 &Apc->NormalRoutine,
250 &Apc->NormalContext,
251 &Apc->SystemArgument1,
252 &Apc->SystemArgument2);
253
254 KeAcquireSpinLock(&PiApcLock, &oldlvl);
255 Thread->Tcb.ApcState.KernelApcInProgress--;
256 }
257 }
258 KeReleaseSpinLock(&PiApcLock, oldlvl);
259 }
260
261 VOID STDCALL
262 KeInsertQueueApc (PKAPC Apc,
263 PVOID SystemArgument1,
264 PVOID SystemArgument2,
265 UCHAR Mode)
266 /*
267 * FUNCTION: Queues an APC for execution
268 * ARGUMENTS:
269 * Apc = APC to be queued
270 * SystemArgument[1-2] = TBD
271 * Mode = TBD
272 */
273 {
274 KIRQL oldlvl;
275 PKTHREAD TargetThread;
276
277 DPRINT("KeInsertQueueApc(Apc %x, SystemArgument1 %x, "
278 "SystemArgument2 %x, Mode %d)\n",Apc,SystemArgument1,
279 SystemArgument2,Mode);
280
281 KeAcquireSpinLock(&PiApcLock, &oldlvl);
282
283 Apc->SystemArgument1 = SystemArgument1;
284 Apc->SystemArgument2 = SystemArgument2;
285
286 if (Apc->Inserted)
287 {
288 DbgPrint("KeInsertQueueApc(): multiple APC insertations\n");
289 KeBugCheck(0);
290 }
291
292 TargetThread = Apc->Thread;
293 if (Apc->ApcMode == KernelMode)
294 {
295 InsertTailList(&TargetThread->ApcState.ApcListHead[0],
296 &Apc->ApcListEntry);
297 TargetThread->ApcState.KernelApcPending++;
298 }
299 else
300 {
301 InsertTailList(&TargetThread->ApcState.ApcListHead[1],
302 &Apc->ApcListEntry);
303 TargetThread->ApcState.UserApcPending++;
304 }
305 Apc->Inserted = TRUE;
306
307 if (Apc->ApcMode == KernelMode && TargetThread->KernelApcDisable >= 1 &&
308 TargetThread->WaitIrql < APC_LEVEL && Apc->NormalRoutine == NULL)
309 {
310 KeRemoveAllWaitsThread(CONTAINING_RECORD(TargetThread, ETHREAD, Tcb),
311 STATUS_KERNEL_APC);
312 }
313 if (Apc->ApcMode == KernelMode && Apc->NormalRoutine != NULL)
314 {
315 TargetThread->Alerted[1] = 1;
316 if (TargetThread->Alertable == TRUE &&
317 TargetThread->WaitMode == UserMode)
318 {
319 PETHREAD Thread;
320
321 Thread = CONTAINING_RECORD(TargetThread, ETHREAD, Tcb);
322 KeRemoveAllWaitsThread(Thread, STATUS_USER_APC);
323 }
324 }
325
326 /*
327 * For user mode APCs if the thread is already waiting then we wait it
328 * up and increment UserApcPending so it will deliver the APC on exit
329 * from kernel mode. If the thread isn't waiting then before it
330 * enters an alertable, user mode wait then it will check for
331 * user mode APCs and if there are any pending then return immediately
332 * and they will be delivered on exit from kernel mode
333 */
334 if (Apc->ApcMode == UserMode && TargetThread->Alertable == TRUE &&
335 TargetThread->WaitMode == UserMode)
336 {
337 NTSTATUS Status;
338
339 DPRINT("Resuming thread for user APC\n");
340
341 Status = STATUS_USER_APC;
342 TargetThread->Alerted[0] = 1;
343 KeRemoveAllWaitsThread(CONTAINING_RECORD(TargetThread, ETHREAD, Tcb),
344 STATUS_USER_APC);
345 }
346 KeReleaseSpinLock(&PiApcLock, oldlvl);
347 }
348
349 BOOLEAN STDCALL
350 KeRemoveQueueApc (PKAPC Apc)
351 /*
352 * FUNCTION: Removes APC object from the apc queue
353 * ARGUMENTS:
354 * Apc = APC to remove
355 * RETURNS: TRUE if the APC was in the queue
356 * FALSE otherwise
357 * NOTE: This function is not exported.
358 */
359 {
360 KIRQL oldIrql;
361 PKTHREAD TargetThread;
362
363 KeAcquireSpinLockAtDpcLevel(&PiApcLock);
364 KeRaiseIrql(HIGH_LEVEL, &oldIrql);
365 if (Apc->Inserted == FALSE)
366 {
367 KeReleaseSpinLock(&PiApcLock, oldIrql);
368 return(FALSE);
369 }
370
371 TargetThread = Apc->Thread;
372 RemoveEntryList(&Apc->ApcListEntry);
373 if (Apc->ApcMode == KernelMode)
374 {
375 TargetThread->ApcState.KernelApcPending--;
376 }
377 else
378 {
379 TargetThread->ApcState.UserApcPending--;
380 }
381 Apc->Inserted = FALSE;
382
383 KeReleaseSpinLock(&PiApcLock, oldIrql);
384 return(TRUE);
385 }
386
387
388 VOID STDCALL
389 KeInitializeApc (PKAPC Apc,
390 PKTHREAD Thread,
391 UCHAR StateIndex,
392 PKKERNEL_ROUTINE KernelRoutine,
393 PKRUNDOWN_ROUTINE RundownRoutine,
394 PKNORMAL_ROUTINE NormalRoutine,
395 UCHAR Mode,
396 PVOID Context)
397 /*
398 * FUNCTION: Initialize an APC object
399 * ARGUMENTS:
400 * Apc = Pointer to the APC object to initialized
401 * Thread = Thread the APC is to be delivered to
402 * StateIndex = TBD
403 * KernelRoutine = Routine to be called for a kernel-mode APC
404 * RundownRoutine = Routine to be called if the thread has exited with
405 * the APC being executed
406 * NormalRoutine = Routine to be called for a user-mode APC
407 * Mode = APC mode
408 * Context = Parameter to be passed to the APC routine
409 */
410 {
411 DPRINT("KeInitializeApc(Apc %x, Thread %x, StateIndex %d, "
412 "KernelRoutine %x, RundownRoutine %x, NormalRoutine %x, Mode %d, "
413 "Context %x)\n",Apc,Thread,StateIndex,KernelRoutine,RundownRoutine,
414 NormalRoutine,Mode,Context);
415 memset(Apc, 0, sizeof(KAPC));
416 Apc->Thread = Thread;
417 Apc->ApcListEntry.Flink = NULL;
418 Apc->ApcListEntry.Blink = NULL;
419 Apc->KernelRoutine = KernelRoutine;
420 Apc->RundownRoutine = RundownRoutine;
421 Apc->NormalRoutine = NormalRoutine;
422 Apc->NormalContext = Context;
423 Apc->Inserted = FALSE;
424 Apc->ApcStateIndex = StateIndex;
425 if (Apc->NormalRoutine != NULL)
426 {
427 Apc->ApcMode = Mode;
428 }
429 else
430 {
431 Apc->ApcMode = KernelMode;
432 }
433 }
434
435 VOID NtQueueApcRundownRoutine(PKAPC Apc)
436 {
437 ExFreePool(Apc);
438 }
439
440 VOID NtQueueApcKernelRoutine(PKAPC Apc,
441 PKNORMAL_ROUTINE* NormalRoutine,
442 PVOID* NormalContext,
443 PVOID* SystemArgument1,
444 PVOID* SystemArgument2)
445 {
446 ExFreePool(Apc);
447 }
448
449 NTSTATUS STDCALL NtQueueApcThread(HANDLE ThreadHandle,
450 PKNORMAL_ROUTINE ApcRoutine,
451 PVOID NormalContext,
452 PVOID SystemArgument1,
453 PVOID SystemArgument2)
454 {
455 PKAPC Apc;
456 PETHREAD Thread;
457 NTSTATUS Status;
458
459 Status = ObReferenceObjectByHandle(ThreadHandle,
460 THREAD_ALL_ACCESS, /* FIXME */
461 PsThreadType,
462 UserMode,
463 (PVOID*)&Thread,
464 NULL);
465 if (!NT_SUCCESS(Status))
466 {
467 return(Status);
468 }
469
470 Apc = ExAllocatePool(NonPagedPool, sizeof(KAPC));
471 if (Apc == NULL)
472 {
473 ObDereferenceObject(Thread);
474 return(STATUS_NO_MEMORY);
475 }
476
477 KeInitializeApc(Apc,
478 &Thread->Tcb,
479 0,
480 NtQueueApcKernelRoutine,
481 NtQueueApcRundownRoutine,
482 ApcRoutine,
483 UserMode,
484 NormalContext);
485 KeInsertQueueApc(Apc,
486 SystemArgument1,
487 SystemArgument2,
488 UserMode);
489
490 ObDereferenceObject(Thread);
491 return(STATUS_SUCCESS);
492 }
493
494
495 NTSTATUS STDCALL NtTestAlert(VOID)
496 {
497 KiTestAlert();
498 return(STATUS_SUCCESS);
499 }
500
501 VOID PiInitApcManagement(VOID)
502 {
503 KeInitializeSpinLock(&PiApcLock);
504 }
505