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