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