xref: /reactos/ntoskrnl/ke/dpc.c (revision 8a978a17)
1 /*
2  * PROJECT:         ReactOS Kernel
3  * LICENSE:         GPL - See COPYING in the top level directory
4  * FILE:            ntoskrnl/ke/dpc.c
5  * PURPOSE:         Deferred Procedure Call (DPC) Support
6  * PROGRAMMERS:     Alex Ionescu (alex.ionescu@reactos.org)
7  *                  Philip Susi (phreak@iag.net)
8  *                  Eric Kohl
9  */
10 
11 /* INCLUDES ******************************************************************/
12 
13 #include <ntoskrnl.h>
14 #define NDEBUG
15 #include <debug.h>
16 
17 /* GLOBALS *******************************************************************/
18 
19 ULONG KiMaximumDpcQueueDepth = 4;
20 ULONG KiMinimumDpcRate = 3;
21 ULONG KiAdjustDpcThreshold = 20;
22 ULONG KiIdealDpcRate = 20;
23 BOOLEAN KeThreadDpcEnable;
24 FAST_MUTEX KiGenericCallDpcMutex;
25 KDPC KiTimerExpireDpc;
26 ULONG KiTimeLimitIsrMicroseconds;
27 ULONG KiDPCTimeout = 110;
28 
29 /* PRIVATE FUNCTIONS *********************************************************/
30 
31 VOID
32 NTAPI
33 KiCheckTimerTable(IN ULARGE_INTEGER CurrentTime)
34 {
35 #if DBG
36     ULONG i = 0;
37     PLIST_ENTRY ListHead, NextEntry;
38     KIRQL OldIrql;
39     PKTIMER Timer;
40 
41     /* Raise IRQL to high and loop timers */
42     KeRaiseIrql(HIGH_LEVEL, &OldIrql);
43     do
44     {
45         /* Loop the current list */
46         ListHead = &KiTimerTableListHead[i].Entry;
47         NextEntry = ListHead->Flink;
48         while (NextEntry != ListHead)
49         {
50             /* Get the timer and move to the next one */
51             Timer = CONTAINING_RECORD(NextEntry, KTIMER, TimerListEntry);
52             NextEntry = NextEntry->Flink;
53 
54             /* Check if it expired */
55             if (Timer->DueTime.QuadPart <= CurrentTime.QuadPart)
56             {
57                 /* Check if the DPC was queued, but didn't run */
58                 if (!(KeGetCurrentPrcb()->TimerRequest) &&
59                     !(*((volatile PULONG*)(&KiTimerExpireDpc.DpcData))))
60                 {
61                     /* This is bad, breakpoint! */
62                     DPRINT1("Invalid timer state!\n");
63                     DbgBreakPoint();
64                 }
65             }
66         }
67 
68         /* Move to the next timer */
69         i++;
70     } while(i < TIMER_TABLE_SIZE);
71 
72     /* Lower IRQL and return */
73     KeLowerIrql(OldIrql);
74 #endif
75 }
76 
77 VOID
78 NTAPI
79 KiTimerExpiration(IN PKDPC Dpc,
80                   IN PVOID DeferredContext,
81                   IN PVOID SystemArgument1,
82                   IN PVOID SystemArgument2)
83 {
84     ULARGE_INTEGER SystemTime, InterruptTime;
85     LARGE_INTEGER Interval;
86     LONG Limit, Index, i;
87     ULONG Timers, ActiveTimers, DpcCalls;
88     PLIST_ENTRY ListHead, NextEntry;
89     KIRQL OldIrql;
90     PKTIMER Timer;
91     PKDPC TimerDpc;
92     ULONG Period;
93     DPC_QUEUE_ENTRY DpcEntry[MAX_TIMER_DPCS];
94     PKSPIN_LOCK_QUEUE LockQueue;
95     PKPRCB Prcb = KeGetCurrentPrcb();
96 
97     /* Disable interrupts */
98     _disable();
99 
100     /* Query system and interrupt time */
101     KeQuerySystemTime((PLARGE_INTEGER)&SystemTime);
102     InterruptTime.QuadPart = KeQueryInterruptTime();
103     Limit = KeTickCount.LowPart;
104 
105     /* Bring interrupts back */
106     _enable();
107 
108     /* Get the index of the timer and normalize it */
109     Index = PtrToLong(SystemArgument1);
110     if ((Limit - Index) >= TIMER_TABLE_SIZE)
111     {
112         /* Normalize it */
113         Limit = Index + TIMER_TABLE_SIZE - 1;
114     }
115 
116     /* Setup index and actual limit */
117     Index--;
118     Limit &= (TIMER_TABLE_SIZE - 1);
119 
120     /* Setup accounting data */
121     DpcCalls = 0;
122     Timers = 24;
123     ActiveTimers = 4;
124 
125     /* Lock the Database and Raise IRQL */
126     OldIrql = KiAcquireDispatcherLock();
127 
128     /* Start expiration loop */
129     do
130     {
131         /* Get the current index */
132         Index = (Index + 1) & (TIMER_TABLE_SIZE - 1);
133 
134         /* Get list pointers and loop the list */
135         ListHead = &KiTimerTableListHead[Index].Entry;
136         while (ListHead != ListHead->Flink)
137         {
138             /* Lock the timer and go to the next entry */
139             LockQueue = KiAcquireTimerLock(Index);
140             NextEntry = ListHead->Flink;
141 
142             /* Get the current timer and check its due time */
143             Timers--;
144             Timer = CONTAINING_RECORD(NextEntry, KTIMER, TimerListEntry);
145             if ((NextEntry != ListHead) &&
146                 (Timer->DueTime.QuadPart <= InterruptTime.QuadPart))
147             {
148                 /* It's expired, remove it */
149                 ActiveTimers--;
150                 KiRemoveEntryTimer(Timer);
151 
152                 /* Make it non-inserted, unlock it, and signal it */
153                 Timer->Header.Inserted = FALSE;
154                 KiReleaseTimerLock(LockQueue);
155                 Timer->Header.SignalState = 1;
156 
157                 /* Get the DPC and period */
158                 TimerDpc = Timer->Dpc;
159                 Period = Timer->Period;
160 
161                 /* Check if there's any waiters */
162                 if (!IsListEmpty(&Timer->Header.WaitListHead))
163                 {
164                     /* Check the type of event */
165                     if (Timer->Header.Type == TimerNotificationObject)
166                     {
167                         /* Unwait the thread */
168                         KxUnwaitThread(&Timer->Header, IO_NO_INCREMENT);
169                     }
170                     else
171                     {
172                         /* Otherwise unwait the thread and signal the timer */
173                         KxUnwaitThreadForEvent((PKEVENT)Timer, IO_NO_INCREMENT);
174                     }
175                 }
176 
177                 /* Check if we have a period */
178                 if (Period)
179                 {
180                     /* Calculate the interval and insert the timer */
181                     Interval.QuadPart = Int32x32To64(Period, -10000);
182                     while (!KiInsertTreeTimer(Timer, Interval));
183                 }
184 
185                 /* Check if we have a DPC */
186                 if (TimerDpc)
187                 {
188 #ifdef CONFIG_SMP
189                     /*
190                      * If the DPC is targeted to another processor,
191                      * then insert it into that processor's DPC queue
192                      * instead of delivering it now.
193                      * If the DPC is a threaded DPC, and the current CPU
194                      * has threaded DPCs enabled (KiExecuteDpc is actively parsing DPCs),
195                      * then also insert it into the DPC queue for threaded delivery,
196                      * instead of doing it here.
197                      */
198                     if (((TimerDpc->Number >= MAXIMUM_PROCESSORS) &&
199                         ((TimerDpc->Number - MAXIMUM_PROCESSORS) != Prcb->Number)) ||
200                         ((TimerDpc->Type == ThreadedDpcObject) && (Prcb->ThreadDpcEnable)))
201                     {
202                         /* Queue it */
203                         KeInsertQueueDpc(TimerDpc,
204                                          UlongToPtr(SystemTime.LowPart),
205                                          UlongToPtr(SystemTime.HighPart));
206                     }
207                     else
208 #endif
209                     {
210                         /* Setup the DPC Entry */
211                         DpcEntry[DpcCalls].Dpc = TimerDpc;
212                         DpcEntry[DpcCalls].Routine = TimerDpc->DeferredRoutine;
213                         DpcEntry[DpcCalls].Context = TimerDpc->DeferredContext;
214                         DpcCalls++;
215                         ASSERT(DpcCalls < MAX_TIMER_DPCS);
216                     }
217                 }
218 
219                 /* Check if we're done processing */
220                 if (!(ActiveTimers) || !(Timers))
221                 {
222                     /* Release the dispatcher while doing DPCs */
223                     KiReleaseDispatcherLock(DISPATCH_LEVEL);
224 
225                     /* Start looping all DPC Entries */
226                     for (i = 0; DpcCalls; DpcCalls--, i++)
227                     {
228 #if DBG
229                         /* Clear DPC Time */
230                         Prcb->DebugDpcTime = 0;
231 #endif
232 
233                         /* Call the DPC */
234                         DpcEntry[i].Routine(DpcEntry[i].Dpc,
235                                             DpcEntry[i].Context,
236                                             UlongToPtr(SystemTime.LowPart),
237                                             UlongToPtr(SystemTime.HighPart));
238                     }
239 
240                     /* Reset accounting */
241                     Timers = 24;
242                     ActiveTimers = 4;
243 
244                     /* Lock the dispatcher database */
245                     KiAcquireDispatcherLock();
246                 }
247             }
248             else
249             {
250                 /* Check if the timer list is empty */
251                 if (NextEntry != ListHead)
252                 {
253                     /* Sanity check */
254                     ASSERT(KiTimerTableListHead[Index].Time.QuadPart <=
255                            Timer->DueTime.QuadPart);
256 
257                     /* Update the time */
258                     _disable();
259                     KiTimerTableListHead[Index].Time.QuadPart =
260                         Timer->DueTime.QuadPart;
261                     _enable();
262                 }
263 
264                 /* Release the lock */
265                 KiReleaseTimerLock(LockQueue);
266 
267                 /* Check if we've scanned all the timers we could */
268                 if (!Timers)
269                 {
270                     /* Release the dispatcher while doing DPCs */
271                     KiReleaseDispatcherLock(DISPATCH_LEVEL);
272 
273                     /* Start looping all DPC Entries */
274                     for (i = 0; DpcCalls; DpcCalls--, i++)
275                     {
276 #if DBG
277                         /* Clear DPC Time */
278                         Prcb->DebugDpcTime = 0;
279 #endif
280 
281                         /* Call the DPC */
282                         DpcEntry[i].Routine(DpcEntry[i].Dpc,
283                                             DpcEntry[i].Context,
284                                             UlongToPtr(SystemTime.LowPart),
285                                             UlongToPtr(SystemTime.HighPart));
286                     }
287 
288                     /* Reset accounting */
289                     Timers = 24;
290                     ActiveTimers = 4;
291 
292                     /* Lock the dispatcher database */
293                     KiAcquireDispatcherLock();
294                 }
295 
296                 /* Done looping */
297                 break;
298             }
299         }
300     } while (Index != Limit);
301 
302     /* Verify the timer table, on debug builds */
303     if (KeNumberProcessors == 1) KiCheckTimerTable(InterruptTime);
304 
305     /* Check if we still have DPC entries */
306     if (DpcCalls)
307     {
308         /* Release the dispatcher while doing DPCs */
309         KiReleaseDispatcherLock(DISPATCH_LEVEL);
310 
311         /* Start looping all DPC Entries */
312         for (i = 0; DpcCalls; DpcCalls--, i++)
313         {
314 #if DBG
315             /* Clear DPC Time */
316             Prcb->DebugDpcTime = 0;
317 #endif
318 
319             /* Call the DPC */
320             DpcEntry[i].Routine(DpcEntry[i].Dpc,
321                                 DpcEntry[i].Context,
322                                 UlongToPtr(SystemTime.LowPart),
323                                 UlongToPtr(SystemTime.HighPart));
324         }
325 
326         /* Lower IRQL if we need to */
327         if (OldIrql != DISPATCH_LEVEL) KeLowerIrql(OldIrql);
328     }
329     else
330     {
331         /* Unlock the dispatcher */
332         KiReleaseDispatcherLock(OldIrql);
333     }
334 }
335 
336 VOID
337 FASTCALL
338 KiTimerListExpire(IN PLIST_ENTRY ExpiredListHead,
339                   IN KIRQL OldIrql)
340 {
341     ULARGE_INTEGER SystemTime;
342     LARGE_INTEGER Interval;
343     LONG i;
344     ULONG DpcCalls = 0;
345     PKTIMER Timer;
346     PKDPC TimerDpc;
347     ULONG Period;
348     DPC_QUEUE_ENTRY DpcEntry[MAX_TIMER_DPCS];
349     PKPRCB Prcb = KeGetCurrentPrcb();
350 
351     /* Query system */
352     KeQuerySystemTime((PLARGE_INTEGER)&SystemTime);
353 
354     /* Loop expired list */
355     while (ExpiredListHead->Flink != ExpiredListHead)
356     {
357         /* Get the current timer */
358         Timer = CONTAINING_RECORD(ExpiredListHead->Flink, KTIMER, TimerListEntry);
359 
360         /* Remove it */
361         RemoveEntryList(&Timer->TimerListEntry);
362 
363         /* Not inserted */
364         Timer->Header.Inserted = FALSE;
365 
366         /* Signal it */
367         Timer->Header.SignalState = 1;
368 
369         /* Get the DPC and period */
370         TimerDpc = Timer->Dpc;
371         Period = Timer->Period;
372 
373         /* Check if there's any waiters */
374         if (!IsListEmpty(&Timer->Header.WaitListHead))
375         {
376             /* Check the type of event */
377             if (Timer->Header.Type == TimerNotificationObject)
378             {
379                 /* Unwait the thread */
380                 KxUnwaitThread(&Timer->Header, IO_NO_INCREMENT);
381             }
382             else
383             {
384                 /* Otherwise unwait the thread and signal the timer */
385                 KxUnwaitThreadForEvent((PKEVENT)Timer, IO_NO_INCREMENT);
386             }
387         }
388 
389         /* Check if we have a period */
390         if (Period)
391         {
392             /* Calculate the interval and insert the timer */
393             Interval.QuadPart = Int32x32To64(Period, -10000);
394             while (!KiInsertTreeTimer(Timer, Interval));
395         }
396 
397         /* Check if we have a DPC */
398         if (TimerDpc)
399         {
400 #ifdef CONFIG_SMP
401             /*
402              * If the DPC is targeted to another processor,
403              * then insert it into that processor's DPC queue
404              * instead of delivering it now.
405              * If the DPC is a threaded DPC, and the current CPU
406              * has threaded DPCs enabled (KiExecuteDpc is actively parsing DPCs),
407              * then also insert it into the DPC queue for threaded delivery,
408              * instead of doing it here.
409              */
410             if (((TimerDpc->Number >= MAXIMUM_PROCESSORS) &&
411                 ((TimerDpc->Number - MAXIMUM_PROCESSORS) != Prcb->Number)) ||
412                 ((TimerDpc->Type == ThreadedDpcObject) && (Prcb->ThreadDpcEnable)))
413             {
414                 /* Queue it */
415                 KeInsertQueueDpc(TimerDpc,
416                                  UlongToPtr(SystemTime.LowPart),
417                                  UlongToPtr(SystemTime.HighPart));
418             }
419             else
420 #endif
421             {
422                 /* Setup the DPC Entry */
423                 DpcEntry[DpcCalls].Dpc = TimerDpc;
424                 DpcEntry[DpcCalls].Routine = TimerDpc->DeferredRoutine;
425                 DpcEntry[DpcCalls].Context = TimerDpc->DeferredContext;
426                 DpcCalls++;
427                 ASSERT(DpcCalls < MAX_TIMER_DPCS);
428             }
429         }
430     }
431 
432     /* Check if we still have DPC entries */
433     if (DpcCalls)
434     {
435         /* Release the dispatcher while doing DPCs */
436         KiReleaseDispatcherLock(DISPATCH_LEVEL);
437 
438         /* Start looping all DPC Entries */
439         for (i = 0; DpcCalls; DpcCalls--, i++)
440         {
441 #if DBG
442             /* Clear DPC Time */
443             Prcb->DebugDpcTime = 0;
444 #endif
445 
446             /* Call the DPC */
447             DpcEntry[i].Routine(DpcEntry[i].Dpc,
448                                 DpcEntry[i].Context,
449                                 UlongToPtr(SystemTime.LowPart),
450                                 UlongToPtr(SystemTime.HighPart));
451         }
452 
453         /* Lower IRQL */
454         KeLowerIrql(OldIrql);
455     }
456     else
457     {
458         /* Unlock the dispatcher */
459         KiReleaseDispatcherLock(OldIrql);
460     }
461 }
462 
463 VOID
464 NTAPI
465 KiQuantumEnd(VOID)
466 {
467     PKPRCB Prcb = KeGetCurrentPrcb();
468     PKTHREAD NextThread, Thread = Prcb->CurrentThread;
469 
470     /* Check if a DPC Event was requested to be signaled */
471     if (InterlockedExchange(&Prcb->DpcSetEventRequest, 0))
472     {
473         /* Signal it */
474         KeSetEvent(&Prcb->DpcEvent, 0, 0);
475     }
476 
477     /* Raise to synchronization level and lock the PRCB and thread */
478     KeRaiseIrqlToSynchLevel();
479     KiAcquireThreadLock(Thread);
480     KiAcquirePrcbLock(Prcb);
481 
482     /* Check if Quantum expired */
483     if (Thread->Quantum <= 0)
484     {
485         /* Check if we're real-time and with quantums disabled */
486         if ((Thread->Priority >= LOW_REALTIME_PRIORITY) &&
487             (Thread->ApcState.Process->DisableQuantum))
488         {
489             /* Otherwise, set maximum quantum */
490             Thread->Quantum = MAX_QUANTUM;
491         }
492         else
493         {
494             /* Reset the new Quantum */
495             Thread->Quantum = Thread->QuantumReset;
496 
497             /* Calculate new priority */
498             Thread->Priority = KiComputeNewPriority(Thread, 1);
499 
500             /* Check if a new thread is scheduled */
501             if (!Prcb->NextThread)
502             {
503                 /* Get a new ready thread */
504                 NextThread = KiSelectReadyThread(Thread->Priority, Prcb);
505                 if (NextThread)
506                 {
507                     /* Found one, set it on standby */
508                     NextThread->State = Standby;
509                     Prcb->NextThread = NextThread;
510                 }
511             }
512             else
513             {
514                 /* Otherwise, make sure that this thread doesn't get preempted */
515                 Thread->Preempted = FALSE;
516             }
517         }
518     }
519 
520     /* Release the thread lock */
521     KiReleaseThreadLock(Thread);
522 
523     /* Check if there's no thread scheduled */
524     if (!Prcb->NextThread)
525     {
526         /* Just leave now */
527         KiReleasePrcbLock(Prcb);
528         KeLowerIrql(DISPATCH_LEVEL);
529         return;
530     }
531 
532     /* Get the next thread now */
533     NextThread = Prcb->NextThread;
534 
535     /* Set current thread's swap busy to true */
536     KiSetThreadSwapBusy(Thread);
537 
538     /* Switch threads in PRCB */
539     Prcb->NextThread = NULL;
540     Prcb->CurrentThread = NextThread;
541 
542     /* Set thread to running and the switch reason to Quantum End */
543     NextThread->State = Running;
544     Thread->WaitReason = WrQuantumEnd;
545 
546     /* Queue it on the ready lists */
547     KxQueueReadyThread(Thread, Prcb);
548 
549     /* Set wait IRQL to APC_LEVEL */
550     Thread->WaitIrql = APC_LEVEL;
551 
552     /* Swap threads */
553     KiSwapContext(APC_LEVEL, Thread);
554 
555     /* Lower IRQL back to DISPATCH_LEVEL */
556     KeLowerIrql(DISPATCH_LEVEL);
557 }
558 
559 VOID
560 FASTCALL
561 KiRetireDpcList(IN PKPRCB Prcb)
562 {
563     PKDPC_DATA DpcData;
564     PLIST_ENTRY ListHead, DpcEntry;
565     PKDPC Dpc;
566     PKDEFERRED_ROUTINE DeferredRoutine;
567     PVOID DeferredContext, SystemArgument1, SystemArgument2;
568     ULONG_PTR TimerHand;
569 #ifdef CONFIG_SMP
570     KIRQL OldIrql;
571 #endif
572 
573     /* Get data and list variables before starting anything else */
574     DpcData = &Prcb->DpcData[DPC_NORMAL];
575     ListHead = &DpcData->DpcListHead;
576 
577     /* Main outer loop */
578     do
579     {
580         /* Set us as active */
581         Prcb->DpcRoutineActive = TRUE;
582 
583         /* Check if this is a timer expiration request */
584         if (Prcb->TimerRequest)
585         {
586             /* It is, get the timer hand and disable timer request */
587             TimerHand = Prcb->TimerHand;
588             Prcb->TimerRequest = 0;
589 
590             /* Expire timers with interrups enabled */
591             _enable();
592             KiTimerExpiration(NULL, NULL, (PVOID)TimerHand, NULL);
593             _disable();
594         }
595 
596         /* Loop while we have entries in the queue */
597         while (DpcData->DpcQueueDepth != 0)
598         {
599             /* Lock the DPC data and get the DPC entry*/
600             KeAcquireSpinLockAtDpcLevel(&DpcData->DpcLock);
601             DpcEntry = ListHead->Flink;
602 
603             /* Make sure we have an entry */
604             if (DpcEntry != ListHead)
605             {
606                 /* Remove the DPC from the list */
607                 RemoveEntryList(DpcEntry);
608                 Dpc = CONTAINING_RECORD(DpcEntry, KDPC, DpcListEntry);
609 
610                 /* Clear its DPC data and save its parameters */
611                 Dpc->DpcData = NULL;
612                 DeferredRoutine = Dpc->DeferredRoutine;
613                 DeferredContext = Dpc->DeferredContext;
614                 SystemArgument1 = Dpc->SystemArgument1;
615                 SystemArgument2 = Dpc->SystemArgument2;
616 
617                 /* Decrease the queue depth */
618                 DpcData->DpcQueueDepth--;
619 
620 #if DBG
621                 /* Clear DPC Time */
622                 Prcb->DebugDpcTime = 0;
623 #endif
624 
625                 /* Release the lock */
626                 KeReleaseSpinLockFromDpcLevel(&DpcData->DpcLock);
627 
628                 /* Re-enable interrupts */
629                 _enable();
630 
631                 /* Call the DPC */
632                 DeferredRoutine(Dpc,
633                                 DeferredContext,
634                                 SystemArgument1,
635                                 SystemArgument2);
636                 ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);
637 
638                 /* Disable interrupts and keep looping */
639                 _disable();
640             }
641             else
642             {
643                 /* The queue should be flushed now */
644                 ASSERT(DpcData->DpcQueueDepth == 0);
645 
646                 /* Release DPC Lock */
647                 KeReleaseSpinLockFromDpcLevel(&DpcData->DpcLock);
648             }
649         }
650 
651         /* Clear DPC Flags */
652         Prcb->DpcRoutineActive = FALSE;
653         Prcb->DpcInterruptRequested = FALSE;
654 
655 #ifdef CONFIG_SMP
656         /* Check if we have deferred threads */
657         if (Prcb->DeferredReadyListHead.Next)
658         {
659 
660             /* Re-enable interrupts and raise to synch */
661             _enable();
662             OldIrql = KeRaiseIrqlToSynchLevel();
663 
664             /* Process deferred threads */
665             KiProcessDeferredReadyList(Prcb);
666 
667             /* Lower IRQL back and disable interrupts */
668             KeLowerIrql(OldIrql);
669             _disable();
670         }
671 #endif
672     } while (DpcData->DpcQueueDepth != 0);
673 }
674 
675 VOID
676 NTAPI
677 KiInitializeDpc(IN PKDPC Dpc,
678                 IN PKDEFERRED_ROUTINE DeferredRoutine,
679                 IN PVOID DeferredContext,
680                 IN KOBJECTS Type)
681 {
682     /* Setup the DPC Object */
683     Dpc->Type = Type;
684     Dpc->Number = 0;
685     Dpc->Importance= MediumImportance;
686     Dpc->DeferredRoutine = DeferredRoutine;
687     Dpc->DeferredContext = DeferredContext;
688     Dpc->DpcData = NULL;
689 }
690 
691 /* PUBLIC FUNCTIONS **********************************************************/
692 
693 /*
694  * @implemented
695  */
696 VOID
697 NTAPI
698 KeInitializeThreadedDpc(IN PKDPC Dpc,
699                         IN PKDEFERRED_ROUTINE DeferredRoutine,
700                         IN PVOID DeferredContext)
701 {
702     /* Call the internal routine */
703     KiInitializeDpc(Dpc, DeferredRoutine, DeferredContext, ThreadedDpcObject);
704 }
705 
706 /*
707  * @implemented
708  */
709 VOID
710 NTAPI
711 KeInitializeDpc(IN PKDPC Dpc,
712                 IN PKDEFERRED_ROUTINE DeferredRoutine,
713                 IN PVOID DeferredContext)
714 {
715     /* Call the internal routine */
716     KiInitializeDpc(Dpc, DeferredRoutine, DeferredContext, DpcObject);
717 }
718 
719 /*
720  * @implemented
721  */
722 BOOLEAN
723 NTAPI
724 KeInsertQueueDpc(IN PKDPC Dpc,
725                  IN PVOID SystemArgument1,
726                  IN PVOID SystemArgument2)
727 {
728     KIRQL OldIrql;
729     PKPRCB Prcb, CurrentPrcb;
730     ULONG Cpu;
731     PKDPC_DATA DpcData;
732     BOOLEAN DpcConfigured = FALSE, DpcInserted = FALSE;
733     ASSERT_DPC(Dpc);
734 
735     /* Check IRQL and Raise it to HIGH_LEVEL */
736     KeRaiseIrql(HIGH_LEVEL, &OldIrql);
737     CurrentPrcb = KeGetCurrentPrcb();
738 
739     /* Check if the DPC has more then the maximum number of CPUs */
740     if (Dpc->Number >= MAXIMUM_PROCESSORS)
741     {
742         /* Then substract the maximum and get that PRCB. */
743         Cpu = Dpc->Number - MAXIMUM_PROCESSORS;
744         Prcb = KiProcessorBlock[Cpu];
745     }
746     else
747     {
748         /* Use the current one */
749         Prcb = CurrentPrcb;
750         Cpu = Prcb->Number;
751     }
752 
753     /* ROS Sanity Check */
754     ASSERT(Prcb == CurrentPrcb);
755 
756     /* Check if this is a threaded DPC and threaded DPCs are enabled */
757     if ((Dpc->Type == ThreadedDpcObject) && (Prcb->ThreadDpcEnable))
758     {
759         /* Then use the threaded data */
760         DpcData = &Prcb->DpcData[DPC_THREADED];
761     }
762     else
763     {
764         /* Otherwise, use the regular data */
765         DpcData = &Prcb->DpcData[DPC_NORMAL];
766     }
767 
768     /* Acquire the DPC lock */
769     KiAcquireSpinLock(&DpcData->DpcLock);
770 
771     /* Get the DPC Data */
772     if (!InterlockedCompareExchangePointer(&Dpc->DpcData, DpcData, NULL))
773     {
774         /* Now we can play with the DPC safely */
775         Dpc->SystemArgument1 = SystemArgument1;
776         Dpc->SystemArgument2 = SystemArgument2;
777         DpcData->DpcQueueDepth++;
778         DpcData->DpcCount++;
779         DpcConfigured = TRUE;
780 
781         /* Check if this is a high importance DPC */
782         if (Dpc->Importance == HighImportance)
783         {
784             /* Pre-empty other DPCs */
785             InsertHeadList(&DpcData->DpcListHead, &Dpc->DpcListEntry);
786         }
787         else
788         {
789             /* Add it at the end */
790             InsertTailList(&DpcData->DpcListHead, &Dpc->DpcListEntry);
791         }
792 
793         /* Check if this is the DPC on the threaded list */
794         if (&Prcb->DpcData[DPC_THREADED] == DpcData)
795         {
796             /* Make sure a threaded DPC isn't already active */
797             if (!(Prcb->DpcThreadActive) && !(Prcb->DpcThreadRequested))
798             {
799                 /* FIXME: Setup Threaded DPC */
800                 UNIMPLEMENTED_FATAL("Threaded DPC not supported\n");
801             }
802         }
803         else
804         {
805             /* Make sure a DPC isn't executing already */
806             if (!(Prcb->DpcRoutineActive) && !(Prcb->DpcInterruptRequested))
807             {
808                 /* Check if this is the same CPU */
809                 if (Prcb != CurrentPrcb)
810                 {
811                     /*
812                      * Check if the DPC is of high importance or above the
813                      * maximum depth. If it is, then make sure that the CPU
814                      * isn't idle, or that it's sleeping.
815                      */
816                     if (((Dpc->Importance == HighImportance) ||
817                         (DpcData->DpcQueueDepth >=
818                          Prcb->MaximumDpcQueueDepth)) &&
819                         (!(AFFINITY_MASK(Cpu) & KiIdleSummary) ||
820                          (Prcb->Sleeping)))
821                     {
822                         /* Set interrupt requested */
823                         Prcb->DpcInterruptRequested = TRUE;
824 
825                         /* Set DPC inserted */
826                         DpcInserted = TRUE;
827                     }
828                 }
829                 else
830                 {
831                     /* Check if the DPC is of anything but low importance */
832                     if ((Dpc->Importance != LowImportance) ||
833                         (DpcData->DpcQueueDepth >=
834                          Prcb->MaximumDpcQueueDepth) ||
835                         (Prcb->DpcRequestRate < Prcb->MinimumDpcRate))
836                     {
837                         /* Set interrupt requested */
838                         Prcb->DpcInterruptRequested = TRUE;
839 
840                         /* Set DPC inserted */
841                         DpcInserted = TRUE;
842                     }
843                 }
844             }
845         }
846     }
847 
848     /* Release the lock */
849     KiReleaseSpinLock(&DpcData->DpcLock);
850 
851     /* Check if the DPC was inserted */
852     if (DpcInserted)
853     {
854         /* Check if this was SMP */
855         if (Prcb != CurrentPrcb)
856         {
857             /* It was, request and IPI */
858             KiIpiSend(AFFINITY_MASK(Cpu), IPI_DPC);
859         }
860         else
861         {
862             /* It wasn't, request an interrupt from HAL */
863             HalRequestSoftwareInterrupt(DISPATCH_LEVEL);
864         }
865     }
866 
867     /* Lower IRQL */
868     KeLowerIrql(OldIrql);
869     return DpcConfigured;
870 }
871 
872 /*
873  * @implemented
874  */
875 BOOLEAN
876 NTAPI
877 KeRemoveQueueDpc(IN PKDPC Dpc)
878 {
879     PKDPC_DATA DpcData;
880     BOOLEAN Enable;
881     ASSERT_DPC(Dpc);
882 
883     /* Disable interrupts */
884     Enable = KeDisableInterrupts();
885 
886     /* Get DPC data */
887     DpcData = Dpc->DpcData;
888     if (DpcData)
889     {
890         /* Acquire the DPC lock */
891         KiAcquireSpinLock(&DpcData->DpcLock);
892 
893         /* Make sure that the data didn't change */
894         if (DpcData == Dpc->DpcData)
895         {
896             /* Remove the DPC */
897             DpcData->DpcQueueDepth--;
898             RemoveEntryList(&Dpc->DpcListEntry);
899             Dpc->DpcData = NULL;
900         }
901 
902         /* Release the lock */
903         KiReleaseSpinLock(&DpcData->DpcLock);
904     }
905 
906     /* Re-enable interrupts */
907     if (Enable) _enable();
908 
909     /* Return if the DPC was in the queue or not */
910     return DpcData ? TRUE : FALSE;
911 }
912 
913 /*
914  * @implemented
915  */
916 VOID
917 NTAPI
918 KeFlushQueuedDpcs(VOID)
919 {
920     PKPRCB CurrentPrcb = KeGetCurrentPrcb();
921     PAGED_CODE();
922 
923     /* Check if this is an UP machine */
924     if (KeActiveProcessors == 1)
925     {
926         /* Check if there are DPCs on either queues */
927         if ((CurrentPrcb->DpcData[DPC_NORMAL].DpcQueueDepth > 0) ||
928             (CurrentPrcb->DpcData[DPC_THREADED].DpcQueueDepth > 0))
929         {
930             /* Request an interrupt */
931             HalRequestSoftwareInterrupt(DISPATCH_LEVEL);
932         }
933     }
934     else
935     {
936         /* FIXME: SMP support required */
937         ASSERT(FALSE);
938     }
939 }
940 
941 /*
942  * @implemented
943  */
944 BOOLEAN
945 NTAPI
946 KeIsExecutingDpc(VOID)
947 {
948     /* Return if the Dpc Routine is active */
949     return KeGetCurrentPrcb()->DpcRoutineActive;
950 }
951 
952 /*
953  * @implemented
954  */
955 VOID
956 NTAPI
957 KeSetImportanceDpc (IN PKDPC Dpc,
958                     IN KDPC_IMPORTANCE Importance)
959 {
960     /* Set the DPC Importance */
961     ASSERT_DPC(Dpc);
962     Dpc->Importance = Importance;
963 }
964 
965 /*
966  * @implemented
967  */
968 VOID
969 NTAPI
970 KeSetTargetProcessorDpc(IN PKDPC Dpc,
971                         IN CCHAR Number)
972 {
973     /* Set a target CPU */
974     ASSERT_DPC(Dpc);
975     Dpc->Number = Number + MAXIMUM_PROCESSORS;
976 }
977 
978 /*
979  * @implemented
980  */
981 VOID
982 NTAPI
983 KeGenericCallDpc(IN PKDEFERRED_ROUTINE Routine,
984                  IN PVOID Context)
985 {
986     ULONG Barrier = KeNumberProcessors;
987     KIRQL OldIrql;
988     DEFERRED_REVERSE_BARRIER ReverseBarrier;
989     ASSERT(KeGetCurrentIrql () < DISPATCH_LEVEL);
990 
991     //
992     // The barrier is the number of processors, each processor will decrement it
993     // by one, so when all processors have run the DPC, the barrier reaches zero
994     //
995     ReverseBarrier.Barrier = Barrier;
996     ReverseBarrier.TotalProcessors = Barrier;
997 
998     //
999     // But we don't need the barrier on UP, since we can simply call the routine
1000     // directly while at DISPATCH_LEVEL and not worry about anything else
1001     //
1002     KeRaiseIrql(DISPATCH_LEVEL, &OldIrql);
1003     Routine(&KeGetCurrentPrcb()->CallDpc, Context, &Barrier, &ReverseBarrier);
1004     KeLowerIrql(OldIrql);
1005 }
1006 
1007 /*
1008  * @implemented
1009  */
1010 VOID
1011 NTAPI
1012 KeSignalCallDpcDone(IN PVOID SystemArgument1)
1013 {
1014     //
1015     // Decrement the barrier, which is actually the processor count
1016     //
1017     InterlockedDecrement((PLONG)SystemArgument1);
1018 }
1019 
1020 /*
1021  * @implemented
1022  */
1023 BOOLEAN
1024 NTAPI
1025 KeSignalCallDpcSynchronize(IN PVOID SystemArgument2)
1026 {
1027     //
1028     // There is nothing to do on UP systems -- the processor calling this wins
1029     //
1030     UNREFERENCED_PARAMETER(SystemArgument2);
1031     return TRUE;
1032 }
1033 
1034 /* EOF */
1035