b490d71aa6b17f55a5419cc72cb4e29dd7e945f4
[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 = 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