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