xref: /reactos/ntoskrnl/ke/dpc.c (revision 9393fc32)
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 _Requires_lock_not_held_(Prcb->PrcbLock)
464 VOID
465 NTAPI
466 KiQuantumEnd(VOID)
467 {
468     PKPRCB Prcb = KeGetCurrentPrcb();
469     PKTHREAD NextThread, Thread = Prcb->CurrentThread;
470 
471     /* Check if a DPC Event was requested to be signaled */
472     if (InterlockedExchange(&Prcb->DpcSetEventRequest, 0))
473     {
474         /* Signal it */
475         KeSetEvent(&Prcb->DpcEvent, 0, 0);
476     }
477 
478     /* Raise to synchronization level and lock the PRCB and thread */
479     KeRaiseIrqlToSynchLevel();
480     KiAcquireThreadLock(Thread);
481     KiAcquirePrcbLock(Prcb);
482 
483     /* Check if Quantum expired */
484     if (Thread->Quantum <= 0)
485     {
486         /* Check if we're real-time and with quantums disabled */
487         if ((Thread->Priority >= LOW_REALTIME_PRIORITY) &&
488             (Thread->ApcState.Process->DisableQuantum))
489         {
490             /* Otherwise, set maximum quantum */
491             Thread->Quantum = MAX_QUANTUM;
492         }
493         else
494         {
495             /* Reset the new Quantum */
496             Thread->Quantum = Thread->QuantumReset;
497 
498             /* Calculate new priority */
499             Thread->Priority = KiComputeNewPriority(Thread, 1);
500 
501             /* Check if a new thread is scheduled */
502             if (!Prcb->NextThread)
503             {
504                 /* Get a new ready thread */
505                 NextThread = KiSelectReadyThread(Thread->Priority, Prcb);
506                 if (NextThread)
507                 {
508                     /* Found one, set it on standby */
509                     NextThread->State = Standby;
510                     Prcb->NextThread = NextThread;
511                 }
512             }
513             else
514             {
515                 /* Otherwise, make sure that this thread doesn't get preempted */
516                 Thread->Preempted = FALSE;
517             }
518         }
519     }
520 
521     /* Release the thread lock */
522     KiReleaseThreadLock(Thread);
523 
524     /* Check if there's no thread scheduled */
525     if (!Prcb->NextThread)
526     {
527         /* Just leave now */
528         KiReleasePrcbLock(Prcb);
529         KeLowerIrql(DISPATCH_LEVEL);
530         return;
531     }
532 
533     /* Get the next thread now */
534     NextThread = Prcb->NextThread;
535 
536     /* Set current thread's swap busy to true */
537     KiSetThreadSwapBusy(Thread);
538 
539     /* Switch threads in PRCB */
540     Prcb->NextThread = NULL;
541     Prcb->CurrentThread = NextThread;
542 
543     /* Set thread to running and the switch reason to Quantum End */
544     NextThread->State = Running;
545     Thread->WaitReason = WrQuantumEnd;
546 
547     /* Queue it on the ready lists */
548     KxQueueReadyThread(Thread, Prcb);
549 
550     /* Set wait IRQL to APC_LEVEL */
551     Thread->WaitIrql = APC_LEVEL;
552 
553     /* Swap threads */
554     KiSwapContext(APC_LEVEL, Thread);
555 
556     /* Lower IRQL back to DISPATCH_LEVEL */
557     KeLowerIrql(DISPATCH_LEVEL);
558 }
559 
560 VOID
561 FASTCALL
562 KiRetireDpcList(IN PKPRCB Prcb)
563 {
564     PKDPC_DATA DpcData;
565     PLIST_ENTRY ListHead, DpcEntry;
566     PKDPC Dpc;
567     PKDEFERRED_ROUTINE DeferredRoutine;
568     PVOID DeferredContext, SystemArgument1, SystemArgument2;
569     ULONG_PTR TimerHand;
570 #ifdef CONFIG_SMP
571     KIRQL OldIrql;
572 #endif
573 
574     /* Get data and list variables before starting anything else */
575     DpcData = &Prcb->DpcData[DPC_NORMAL];
576     ListHead = &DpcData->DpcListHead;
577 
578     /* Main outer loop */
579     do
580     {
581         /* Set us as active */
582         Prcb->DpcRoutineActive = TRUE;
583 
584         /* Check if this is a timer expiration request */
585         if (Prcb->TimerRequest)
586         {
587             /* It is, get the timer hand and disable timer request */
588             TimerHand = Prcb->TimerHand;
589             Prcb->TimerRequest = 0;
590 
591             /* Expire timers with interrups enabled */
592             _enable();
593             KiTimerExpiration(NULL, NULL, (PVOID)TimerHand, NULL);
594             _disable();
595         }
596 
597         /* Loop while we have entries in the queue */
598         while (DpcData->DpcQueueDepth != 0)
599         {
600             /* Lock the DPC data and get the DPC entry*/
601             KeAcquireSpinLockAtDpcLevel(&DpcData->DpcLock);
602             DpcEntry = ListHead->Flink;
603 
604             /* Make sure we have an entry */
605             if (DpcEntry != ListHead)
606             {
607                 /* Remove the DPC from the list */
608                 RemoveEntryList(DpcEntry);
609                 Dpc = CONTAINING_RECORD(DpcEntry, KDPC, DpcListEntry);
610 
611                 /* Clear its DPC data and save its parameters */
612                 Dpc->DpcData = NULL;
613                 DeferredRoutine = Dpc->DeferredRoutine;
614                 DeferredContext = Dpc->DeferredContext;
615                 SystemArgument1 = Dpc->SystemArgument1;
616                 SystemArgument2 = Dpc->SystemArgument2;
617 
618                 /* Decrease the queue depth */
619                 DpcData->DpcQueueDepth--;
620 
621 #if DBG
622                 /* Clear DPC Time */
623                 Prcb->DebugDpcTime = 0;
624 #endif
625 
626                 /* Release the lock */
627                 KeReleaseSpinLockFromDpcLevel(&DpcData->DpcLock);
628 
629                 /* Re-enable interrupts */
630                 _enable();
631 
632                 /* Call the DPC */
633                 DeferredRoutine(Dpc,
634                                 DeferredContext,
635                                 SystemArgument1,
636                                 SystemArgument2);
637                 ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);
638 
639                 /* Disable interrupts and keep looping */
640                 _disable();
641             }
642             else
643             {
644                 /* The queue should be flushed now */
645                 ASSERT(DpcData->DpcQueueDepth == 0);
646 
647                 /* Release DPC Lock */
648                 KeReleaseSpinLockFromDpcLevel(&DpcData->DpcLock);
649             }
650         }
651 
652         /* Clear DPC Flags */
653         Prcb->DpcRoutineActive = FALSE;
654         Prcb->DpcInterruptRequested = FALSE;
655 
656 #ifdef CONFIG_SMP
657         /* Check if we have deferred threads */
658         if (Prcb->DeferredReadyListHead.Next)
659         {
660 
661             /* Re-enable interrupts and raise to synch */
662             _enable();
663             OldIrql = KeRaiseIrqlToSynchLevel();
664 
665             /* Process deferred threads */
666             KiProcessDeferredReadyList(Prcb);
667 
668             /* Lower IRQL back and disable interrupts */
669             KeLowerIrql(OldIrql);
670             _disable();
671         }
672 #endif
673     } while (DpcData->DpcQueueDepth != 0);
674 }
675 
676 VOID
677 NTAPI
678 KiInitializeDpc(IN PKDPC Dpc,
679                 IN PKDEFERRED_ROUTINE DeferredRoutine,
680                 IN PVOID DeferredContext,
681                 IN KOBJECTS Type)
682 {
683     /* Setup the DPC Object */
684     Dpc->Type = Type;
685     Dpc->Number = 0;
686     Dpc->Importance= MediumImportance;
687     Dpc->DeferredRoutine = DeferredRoutine;
688     Dpc->DeferredContext = DeferredContext;
689     Dpc->DpcData = NULL;
690 }
691 
692 /* PUBLIC FUNCTIONS **********************************************************/
693 
694 /*
695  * @implemented
696  */
697 VOID
698 NTAPI
699 KeInitializeThreadedDpc(IN PKDPC Dpc,
700                         IN PKDEFERRED_ROUTINE DeferredRoutine,
701                         IN PVOID DeferredContext)
702 {
703     /* Call the internal routine */
704     KiInitializeDpc(Dpc, DeferredRoutine, DeferredContext, ThreadedDpcObject);
705 }
706 
707 /*
708  * @implemented
709  */
710 VOID
711 NTAPI
712 KeInitializeDpc(IN PKDPC Dpc,
713                 IN PKDEFERRED_ROUTINE DeferredRoutine,
714                 IN PVOID DeferredContext)
715 {
716     /* Call the internal routine */
717     KiInitializeDpc(Dpc, DeferredRoutine, DeferredContext, DpcObject);
718 }
719 
720 /*
721  * @implemented
722  */
723 BOOLEAN
724 NTAPI
725 KeInsertQueueDpc(IN PKDPC Dpc,
726                  IN PVOID SystemArgument1,
727                  IN PVOID SystemArgument2)
728 {
729     KIRQL OldIrql;
730     PKPRCB Prcb, CurrentPrcb;
731     ULONG Cpu;
732     PKDPC_DATA DpcData;
733     BOOLEAN DpcConfigured = FALSE, DpcInserted = FALSE;
734     ASSERT_DPC(Dpc);
735 
736     /* Check IRQL and Raise it to HIGH_LEVEL */
737     KeRaiseIrql(HIGH_LEVEL, &OldIrql);
738     CurrentPrcb = KeGetCurrentPrcb();
739 
740     /* Check if the DPC has more then the maximum number of CPUs */
741     if (Dpc->Number >= MAXIMUM_PROCESSORS)
742     {
743         /* Then substract the maximum and get that PRCB. */
744         Cpu = Dpc->Number - MAXIMUM_PROCESSORS;
745         Prcb = KiProcessorBlock[Cpu];
746     }
747     else
748     {
749         /* Use the current one */
750         Prcb = CurrentPrcb;
751         Cpu = Prcb->Number;
752     }
753 
754     /* ROS Sanity Check */
755     ASSERT(Prcb == CurrentPrcb);
756 
757     /* Check if this is a threaded DPC and threaded DPCs are enabled */
758     if ((Dpc->Type == ThreadedDpcObject) && (Prcb->ThreadDpcEnable))
759     {
760         /* Then use the threaded data */
761         DpcData = &Prcb->DpcData[DPC_THREADED];
762     }
763     else
764     {
765         /* Otherwise, use the regular data */
766         DpcData = &Prcb->DpcData[DPC_NORMAL];
767     }
768 
769     /* Acquire the DPC lock */
770     KiAcquireSpinLock(&DpcData->DpcLock);
771 
772     /* Get the DPC Data */
773     if (!InterlockedCompareExchangePointer(&Dpc->DpcData, DpcData, NULL))
774     {
775         /* Now we can play with the DPC safely */
776         Dpc->SystemArgument1 = SystemArgument1;
777         Dpc->SystemArgument2 = SystemArgument2;
778         DpcData->DpcQueueDepth++;
779         DpcData->DpcCount++;
780         DpcConfigured = TRUE;
781 
782         /* Check if this is a high importance DPC */
783         if (Dpc->Importance == HighImportance)
784         {
785             /* Pre-empty other DPCs */
786             InsertHeadList(&DpcData->DpcListHead, &Dpc->DpcListEntry);
787         }
788         else
789         {
790             /* Add it at the end */
791             InsertTailList(&DpcData->DpcListHead, &Dpc->DpcListEntry);
792         }
793 
794         /* Check if this is the DPC on the threaded list */
795         if (&Prcb->DpcData[DPC_THREADED] == DpcData)
796         {
797             /* Make sure a threaded DPC isn't already active */
798             if (!(Prcb->DpcThreadActive) && !(Prcb->DpcThreadRequested))
799             {
800                 /* FIXME: Setup Threaded DPC */
801                 UNIMPLEMENTED_FATAL("Threaded DPC not supported\n");
802             }
803         }
804         else
805         {
806             /* Make sure a DPC isn't executing already */
807             if (!(Prcb->DpcRoutineActive) && !(Prcb->DpcInterruptRequested))
808             {
809                 /* Check if this is the same CPU */
810                 if (Prcb != CurrentPrcb)
811                 {
812                     /*
813                      * Check if the DPC is of high importance or above the
814                      * maximum depth. If it is, then make sure that the CPU
815                      * isn't idle, or that it's sleeping.
816                      */
817                     if (((Dpc->Importance == HighImportance) ||
818                         (DpcData->DpcQueueDepth >=
819                          Prcb->MaximumDpcQueueDepth)) &&
820                         (!(AFFINITY_MASK(Cpu) & KiIdleSummary) ||
821                          (Prcb->Sleeping)))
822                     {
823                         /* Set interrupt requested */
824                         Prcb->DpcInterruptRequested = TRUE;
825 
826                         /* Set DPC inserted */
827                         DpcInserted = TRUE;
828                     }
829                 }
830                 else
831                 {
832                     /* Check if the DPC is of anything but low importance */
833                     if ((Dpc->Importance != LowImportance) ||
834                         (DpcData->DpcQueueDepth >=
835                          Prcb->MaximumDpcQueueDepth) ||
836                         (Prcb->DpcRequestRate < Prcb->MinimumDpcRate))
837                     {
838                         /* Set interrupt requested */
839                         Prcb->DpcInterruptRequested = TRUE;
840 
841                         /* Set DPC inserted */
842                         DpcInserted = TRUE;
843                     }
844                 }
845             }
846         }
847     }
848 
849     /* Release the lock */
850     KiReleaseSpinLock(&DpcData->DpcLock);
851 
852     /* Check if the DPC was inserted */
853     if (DpcInserted)
854     {
855         /* Check if this was SMP */
856         if (Prcb != CurrentPrcb)
857         {
858             /* It was, request and IPI */
859             KiIpiSend(AFFINITY_MASK(Cpu), IPI_DPC);
860         }
861         else
862         {
863             /* It wasn't, request an interrupt from HAL */
864             HalRequestSoftwareInterrupt(DISPATCH_LEVEL);
865         }
866     }
867 
868     /* Lower IRQL */
869     KeLowerIrql(OldIrql);
870     return DpcConfigured;
871 }
872 
873 /*
874  * @implemented
875  */
876 BOOLEAN
877 NTAPI
878 KeRemoveQueueDpc(IN PKDPC Dpc)
879 {
880     PKDPC_DATA DpcData;
881     BOOLEAN Enable;
882     ASSERT_DPC(Dpc);
883 
884     /* Disable interrupts */
885     Enable = KeDisableInterrupts();
886 
887     /* Get DPC data */
888     DpcData = Dpc->DpcData;
889     if (DpcData)
890     {
891         /* Acquire the DPC lock */
892         KiAcquireSpinLock(&DpcData->DpcLock);
893 
894         /* Make sure that the data didn't change */
895         if (DpcData == Dpc->DpcData)
896         {
897             /* Remove the DPC */
898             DpcData->DpcQueueDepth--;
899             RemoveEntryList(&Dpc->DpcListEntry);
900             Dpc->DpcData = NULL;
901         }
902 
903         /* Release the lock */
904         KiReleaseSpinLock(&DpcData->DpcLock);
905     }
906 
907     /* Re-enable interrupts */
908     if (Enable) _enable();
909 
910     /* Return if the DPC was in the queue or not */
911     return DpcData ? TRUE : FALSE;
912 }
913 
914 /*
915  * @implemented
916  */
917 VOID
918 NTAPI
919 KeFlushQueuedDpcs(VOID)
920 {
921     PKPRCB CurrentPrcb = KeGetCurrentPrcb();
922     PAGED_CODE();
923 
924     /* Check if this is an UP machine */
925     if (KeActiveProcessors == 1)
926     {
927         /* Check if there are DPCs on either queues */
928         if ((CurrentPrcb->DpcData[DPC_NORMAL].DpcQueueDepth > 0) ||
929             (CurrentPrcb->DpcData[DPC_THREADED].DpcQueueDepth > 0))
930         {
931             /* Request an interrupt */
932             HalRequestSoftwareInterrupt(DISPATCH_LEVEL);
933         }
934     }
935     else
936     {
937         /* FIXME: SMP support required */
938         ASSERT(FALSE);
939     }
940 }
941 
942 /*
943  * @implemented
944  */
945 BOOLEAN
946 NTAPI
947 KeIsExecutingDpc(VOID)
948 {
949     /* Return if the Dpc Routine is active */
950     return KeGetCurrentPrcb()->DpcRoutineActive;
951 }
952 
953 /*
954  * @implemented
955  */
956 VOID
957 NTAPI
958 KeSetImportanceDpc (IN PKDPC Dpc,
959                     IN KDPC_IMPORTANCE Importance)
960 {
961     /* Set the DPC Importance */
962     ASSERT_DPC(Dpc);
963     Dpc->Importance = Importance;
964 }
965 
966 /*
967  * @implemented
968  */
969 VOID
970 NTAPI
971 KeSetTargetProcessorDpc(IN PKDPC Dpc,
972                         IN CCHAR Number)
973 {
974     /* Set a target CPU */
975     ASSERT_DPC(Dpc);
976     Dpc->Number = Number + MAXIMUM_PROCESSORS;
977 }
978 
979 /*
980  * @implemented
981  */
982 VOID
983 NTAPI
984 KeGenericCallDpc(IN PKDEFERRED_ROUTINE Routine,
985                  IN PVOID Context)
986 {
987     ULONG Barrier = KeNumberProcessors;
988     KIRQL OldIrql;
989     DEFERRED_REVERSE_BARRIER ReverseBarrier;
990     ASSERT(KeGetCurrentIrql () < DISPATCH_LEVEL);
991 
992     //
993     // The barrier is the number of processors, each processor will decrement it
994     // by one, so when all processors have run the DPC, the barrier reaches zero
995     //
996     ReverseBarrier.Barrier = Barrier;
997     ReverseBarrier.TotalProcessors = Barrier;
998 
999     //
1000     // But we don't need the barrier on UP, since we can simply call the routine
1001     // directly while at DISPATCH_LEVEL and not worry about anything else
1002     //
1003     KeRaiseIrql(DISPATCH_LEVEL, &OldIrql);
1004     Routine(&KeGetCurrentPrcb()->CallDpc, Context, &Barrier, &ReverseBarrier);
1005     KeLowerIrql(OldIrql);
1006 }
1007 
1008 /*
1009  * @implemented
1010  */
1011 VOID
1012 NTAPI
1013 KeSignalCallDpcDone(IN PVOID SystemArgument1)
1014 {
1015     //
1016     // Decrement the barrier, which is actually the processor count
1017     //
1018     InterlockedDecrement((PLONG)SystemArgument1);
1019 }
1020 
1021 /*
1022  * @implemented
1023  */
1024 BOOLEAN
1025 NTAPI
1026 KeSignalCallDpcSynchronize(IN PVOID SystemArgument2)
1027 {
1028     //
1029     // There is nothing to do on UP systems -- the processor calling this wins
1030     //
1031     UNREFERENCED_PARAMETER(SystemArgument2);
1032     return TRUE;
1033 }
1034 
1035 /* EOF */
1036