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 (ekohl@abo.rhein-zeitung.de)
11 /* INCLUDES ******************************************************************/
17 /* GLOBALS *******************************************************************/
19 ULONG KiMaximumDpcQueueDepth
= 4;
20 ULONG KiMinimumDpcRate
= 3;
21 ULONG KiAdjustDpcThreshold
= 20;
22 ULONG KiIdealDpcRate
= 20;
23 BOOLEAN KeThreadDpcEnable
;
24 FAST_MUTEX KiGenericCallDpcMutex
;
26 /* PRIVATE FUNCTIONS *********************************************************/
30 KiTimerExpiration(IN PKDPC Dpc
,
31 IN PVOID DeferredContext
,
32 IN PVOID SystemArgument1
,
33 IN PVOID SystemArgument2
)
35 LARGE_INTEGER SystemTime
, InterruptTime
, Interval
;
37 ULONG Timers
, ActiveTimers
, DpcCalls
;
38 PLIST_ENTRY ListHead
, NextEntry
;
39 PKTIMER_TABLE_ENTRY TimerEntry
;
44 DPC_QUEUE_ENTRY DpcEntry
[MAX_TIMER_DPCS
];
45 PKSPIN_LOCK_QUEUE LockQueue
;
47 /* Disable interrupts */
50 /* Query system and interrupt time */
51 KeQuerySystemTime(&SystemTime
);
52 InterruptTime
.QuadPart
= KeQueryInterruptTime();
53 Limit
= KeTickCount
.LowPart
;
55 /* Bring interrupts back */
58 /* Get the index of the timer and normalize it */
59 Index
= PtrToLong(SystemArgument1
);
60 if ((Limit
- Index
) >= TIMER_TABLE_SIZE
)
63 Limit
= Index
+ TIMER_TABLE_SIZE
- 1;
66 /* Setup index and actual limit */
68 Limit
&= (TIMER_TABLE_SIZE
- 1);
70 /* Setup accounting data */
75 /* Lock the Database and Raise IRQL */
76 OldIrql
= KiAcquireDispatcherLock();
78 /* Start expiration loop */
81 /* Get the current index */
82 Index
= (Index
+ 1) & (TIMER_TABLE_SIZE
- 1);
84 /* Get list pointers and loop the list */
85 ListHead
= &KiTimerTableListHead
[Index
].Entry
;
86 while (ListHead
!= ListHead
->Flink
)
88 /* Lock the timer and go to the next entry */
89 LockQueue
= KiAcquireTimerLock(Index
);
90 NextEntry
= ListHead
->Flink
;
92 /* Get the current timer and check its due time */
94 Timer
= CONTAINING_RECORD(NextEntry
, KTIMER
, TimerListEntry
);
95 if ((NextEntry
!= ListHead
) &&
96 (Timer
->DueTime
.QuadPart
<= InterruptTime
.QuadPart
))
98 /* It's expired, remove it */
100 if (RemoveEntryList(&Timer
->TimerListEntry
))
102 /* Get the entry and check if it's empty */
103 TimerEntry
= &KiTimerTableListHead
[Timer
->Header
.Hand
];
104 if (IsListEmpty(&TimerEntry
->Entry
))
106 /* Clear the time then */
107 TimerEntry
->Time
.HighPart
= 0xFFFFFFFF;
111 /* Make it non-inserted, unlock it, and signal it */
112 Timer
->Header
.Inserted
= FALSE
;
113 KiReleaseTimerLock(LockQueue
);
114 Timer
->Header
.SignalState
= 1;
116 /* Get the DPC and period */
117 TimerDpc
= Timer
->Dpc
;
118 Period
= Timer
->Period
;
120 /* Check if there's any waiters */
121 if (!IsListEmpty(&Timer
->Header
.WaitListHead
))
123 /* Check the type of event */
124 if (Timer
->Header
.Type
== TimerNotificationObject
)
126 /* Unwait the thread */
127 KxUnwaitThread(&Timer
->Header
, IO_NO_INCREMENT
);
131 /* Otherwise unwait the thread and signal the timer */
132 KxUnwaitThreadForEvent((PKEVENT
)Timer
, IO_NO_INCREMENT
);
136 /* Check if we have a period */
139 /* Calculate the interval and insert the timer */
140 Interval
.QuadPart
= Int32x32To64(Period
, -10000);
141 while (!KiInsertTreeTimer(Timer
, Interval
));
144 /* Check if we have a DPC */
147 /* Setup the DPC Entry */
148 DpcEntry
[DpcCalls
].Dpc
= TimerDpc
;
149 DpcEntry
[DpcCalls
].Routine
= TimerDpc
->DeferredRoutine
;
150 DpcEntry
[DpcCalls
].Context
= TimerDpc
->DeferredContext
;
154 /* Check if we're done processing */
155 if (!(ActiveTimers
) || !(Timers
))
157 /* Release the dispatcher while doing DPCs */
158 KiReleaseDispatcherLock(DISPATCH_LEVEL
);
160 /* Start looping all DPC Entries */
161 for (i
= 0; DpcCalls
; DpcCalls
--, i
++)
164 DpcEntry
[i
].Routine(DpcEntry
[i
].Dpc
,
166 UlongToPtr(SystemTime
.LowPart
),
167 UlongToPtr(SystemTime
.HighPart
));
170 /* Reset accounting */
174 /* Lock the dispatcher database */
175 KiAcquireDispatcherLock();
180 /* Check if the timer list is empty */
181 if (NextEntry
!= ListHead
)
184 ASSERT(KiTimerTableListHead
[Index
].Time
.QuadPart
<=
185 Timer
->DueTime
.QuadPart
);
187 /* Update the time */
189 KiTimerTableListHead
[Index
].Time
.QuadPart
=
190 Timer
->DueTime
.QuadPart
;
194 /* Release the lock */
195 KiReleaseTimerLock(LockQueue
);
197 /* Check if we've scanned all the timers we could */
200 /* Release the dispatcher while doing DPCs */
201 KiReleaseDispatcherLock(DISPATCH_LEVEL
);
203 /* Start looping all DPC Entries */
204 for (i
= 0; DpcCalls
; DpcCalls
--, i
++)
207 DpcEntry
[i
].Routine(DpcEntry
[i
].Dpc
,
209 UlongToPtr(SystemTime
.LowPart
),
210 UlongToPtr(SystemTime
.HighPart
));
213 /* Reset accounting */
217 /* Lock the dispatcher database */
218 KiAcquireDispatcherLock();
225 } while (Index
!= Limit
);
227 /* Check if we still have DPC entries */
230 /* Release the dispatcher while doing DPCs */
231 KiReleaseDispatcherLock(DISPATCH_LEVEL
);
233 /* Start looping all DPC Entries */
234 for (i
= 0; DpcCalls
; DpcCalls
--, i
++)
237 DpcEntry
[i
].Routine(DpcEntry
[i
].Dpc
,
239 UlongToPtr(SystemTime
.LowPart
),
240 UlongToPtr(SystemTime
.HighPart
));
243 /* Lower IRQL if we need to */
244 if (OldIrql
!= DISPATCH_LEVEL
) KeLowerIrql(OldIrql
);
248 /* Unlock the dispatcher */
249 KiReleaseDispatcherLock(OldIrql
);
257 PKPRCB Prcb
= KeGetCurrentPrcb();
258 PKTHREAD NextThread
, Thread
= Prcb
->CurrentThread
;
260 /* Check if a DPC Event was requested to be signaled */
261 if (InterlockedExchange(&Prcb
->DpcSetEventRequest
, 0))
264 KeSetEvent(&Prcb
->DpcEvent
, 0, 0);
267 /* Raise to synchronization level and lock the PRCB and thread */
268 KeRaiseIrqlToSynchLevel();
269 KiAcquireThreadLock(Thread
);
270 KiAcquirePrcbLock(Prcb
);
272 /* Check if Quantum expired */
273 if (Thread
->Quantum
<= 0)
275 /* Make sure that we're not real-time or without a quantum */
276 if ((Thread
->Priority
< LOW_REALTIME_PRIORITY
) &&
277 !(Thread
->ApcState
.Process
->DisableQuantum
))
279 /* Reset the new Quantum */
280 Thread
->Quantum
= Thread
->QuantumReset
;
282 /* Calculate new priority */
283 Thread
->Priority
= KiComputeNewPriority(Thread
, 1);
285 /* Check if a new thread is scheduled */
286 if (!Prcb
->NextThread
)
289 /* Get a new ready thread */
290 NextThread
= KiSelectReadyThread(Thread
->Priority
, Prcb
);
293 /* Found one, set it on standby */
294 NextThread
->State
= Standby
;
295 Prcb
->NextThread
= NextThread
;
299 KiReleasePrcbLock(Prcb
);
300 KeLowerIrql(DISPATCH_LEVEL
);
301 KiDispatchThread(Ready
);
307 /* Otherwise, make sure that this thread doesn't get preempted */
308 Thread
->Preempted
= FALSE
;
313 /* Otherwise, set maximum quantum */
314 Thread
->Quantum
= MAX_QUANTUM
;
318 /* Release the thread lock */
319 KiReleaseThreadLock(Thread
);
321 /* Check if there's no thread scheduled */
322 if (!Prcb
->NextThread
)
325 KiReleasePrcbLock(Prcb
);
326 KeLowerIrql(DISPATCH_LEVEL
);
330 /* Get the next thread now */
331 NextThread
= Prcb
->NextThread
;
333 /* Set current thread's swap busy to true */
334 KiSetThreadSwapBusy(Thread
);
336 /* Switch threads in PRCB */
337 Prcb
->NextThread
= NULL
;
338 Prcb
->CurrentThread
= NextThread
;
340 /* Set thread to running and the switch reason to Quantum End */
341 NextThread
->State
= Running
;
342 Thread
->WaitReason
= WrQuantumEnd
;
344 /* Queue it on the ready lists */
345 KxQueueReadyThread(Thread
, Prcb
);
347 /* Set wait IRQL to APC_LEVEL */
348 Thread
->WaitIrql
= APC_LEVEL
;
351 KiSwapContext(Thread
, NextThread
);
353 /* Lower IRQL back to DISPATCH_LEVEL */
354 KeLowerIrql(DISPATCH_LEVEL
);
359 KiRetireDpcList(IN PKPRCB Prcb
)
361 PKDPC_DATA DpcData
= Prcb
->DpcData
;
362 PLIST_ENTRY DpcEntry
;
364 PKDEFERRED_ROUTINE DeferredRoutine
;
365 PVOID DeferredContext
, SystemArgument1
, SystemArgument2
;
368 /* Main outer loop */
371 /* Set us as active */
372 Prcb
->DpcRoutineActive
= TRUE
;
374 /* Check if this is a timer expiration request */
375 if (Prcb
->TimerRequest
)
377 TimerHand
= Prcb
->TimerHand
;
378 Prcb
->TimerRequest
= 0;
380 KiTimerExpiration(NULL
, NULL
, (PVOID
) TimerHand
, NULL
);
384 /* Loop while we have entries in the queue */
385 while (DpcData
->DpcQueueDepth
)
387 /* Lock the DPC data */
388 KefAcquireSpinLockAtDpcLevel(&DpcData
->DpcLock
);
390 /* Make sure we have an entry */
391 if (!IsListEmpty(&DpcData
->DpcListHead
))
393 /* Remove the DPC from the list */
394 DpcEntry
= RemoveHeadList(&DpcData
->DpcListHead
);
395 Dpc
= CONTAINING_RECORD(DpcEntry
, KDPC
, DpcListEntry
);
397 /* Clear its DPC data and save its parameters */
399 DeferredRoutine
= Dpc
->DeferredRoutine
;
400 DeferredContext
= Dpc
->DeferredContext
;
401 SystemArgument1
= Dpc
->SystemArgument1
;
402 SystemArgument2
= Dpc
->SystemArgument2
;
404 /* Decrease the queue depth */
405 DpcData
->DpcQueueDepth
--;
408 Prcb
->DebugDpcTime
= 0;
410 /* Release the lock */
411 KefReleaseSpinLockFromDpcLevel(&DpcData
->DpcLock
);
413 /* Re-enable interrupts */
421 ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL
);
423 /* Disable interrupts and keep looping */
428 /* The queue should be flushed now */
429 ASSERT(DpcData
->DpcQueueDepth
== 0);
431 /* Release DPC Lock */
432 KefReleaseSpinLockFromDpcLevel(&DpcData
->DpcLock
);
437 /* Clear DPC Flags */
438 Prcb
->DpcRoutineActive
= FALSE
;
439 Prcb
->DpcInterruptRequested
= FALSE
;
441 /* Check if we have deferred threads */
442 if (Prcb
->DeferredReadyListHead
.Next
)
444 /* FIXME: 2K3-style scheduling not implemeted */
447 } while (DpcData
->DpcQueueDepth
);
452 KiInitializeDpc(IN PKDPC Dpc
,
453 IN PKDEFERRED_ROUTINE DeferredRoutine
,
454 IN PVOID DeferredContext
,
457 /* Setup the DPC Object */
460 Dpc
->Importance
= MediumImportance
;
461 Dpc
->DeferredRoutine
= DeferredRoutine
;
462 Dpc
->DeferredContext
= DeferredContext
;
466 /* PUBLIC FUNCTIONS **********************************************************/
473 KeInitializeThreadedDpc(IN PKDPC Dpc
,
474 IN PKDEFERRED_ROUTINE DeferredRoutine
,
475 IN PVOID DeferredContext
)
477 /* Call the internal routine */
478 KiInitializeDpc(Dpc
, DeferredRoutine
, DeferredContext
, ThreadedDpcObject
);
486 KeInitializeDpc(IN PKDPC Dpc
,
487 IN PKDEFERRED_ROUTINE DeferredRoutine
,
488 IN PVOID DeferredContext
)
490 /* Call the internal routine */
491 KiInitializeDpc(Dpc
, DeferredRoutine
, DeferredContext
, DpcObject
);
499 KeInsertQueueDpc(IN PKDPC Dpc
,
500 IN PVOID SystemArgument1
,
501 IN PVOID SystemArgument2
)
504 PKPRCB Prcb
, CurrentPrcb
= KeGetCurrentPrcb();
507 BOOLEAN DpcConfigured
= FALSE
, DpcInserted
= FALSE
;
510 /* Check IRQL and Raise it to HIGH_LEVEL */
511 KeRaiseIrql(HIGH_LEVEL
, &OldIrql
);
513 /* Check if the DPC has more then the maximum number of CPUs */
514 if (Dpc
->Number
>= MAXIMUM_PROCESSORS
)
516 /* Then substract the maximum and get that PRCB. */
517 Cpu
= Dpc
->Number
- MAXIMUM_PROCESSORS
;
518 Prcb
= KiProcessorBlock
[Cpu
];
522 /* Use the current one */
527 /* Check if this is a threaded DPC and threaded DPCs are enabled */
528 if ((Dpc
->Type
== ThreadedDpcObject
) && (Prcb
->ThreadDpcEnable
))
530 /* Then use the threaded data */
531 DpcData
= &Prcb
->DpcData
[DPC_THREADED
];
535 /* Otherwise, use the regular data */
536 DpcData
= &Prcb
->DpcData
[DPC_NORMAL
];
539 /* Acquire the DPC lock */
540 KiAcquireSpinLock(&DpcData
->DpcLock
);
542 /* Get the DPC Data */
543 if (!InterlockedCompareExchangePointer(&Dpc
->DpcData
, DpcData
, NULL
))
545 /* Now we can play with the DPC safely */
546 Dpc
->SystemArgument1
= SystemArgument1
;
547 Dpc
->SystemArgument2
= SystemArgument2
;
548 DpcData
->DpcQueueDepth
++;
550 DpcConfigured
= TRUE
;
552 /* Check if this is a high importance DPC */
553 if (Dpc
->Importance
== HighImportance
)
555 /* Pre-empty other DPCs */
556 InsertHeadList(&DpcData
->DpcListHead
, &Dpc
->DpcListEntry
);
560 /* Add it at the end */
561 InsertTailList(&DpcData
->DpcListHead
, &Dpc
->DpcListEntry
);
564 /* Check if this is the DPC on the threaded list */
565 if (&Prcb
->DpcData
[DPC_THREADED
].DpcListHead
== &DpcData
->DpcListHead
)
567 /* Make sure a threaded DPC isn't already active */
568 if (!(Prcb
->DpcThreadActive
) && !(Prcb
->DpcThreadRequested
))
570 /* FIXME: Setup Threaded DPC */
576 /* Make sure a DPC isn't executing already */
577 if (!(Prcb
->DpcRoutineActive
) && !(Prcb
->DpcInterruptRequested
))
579 /* Check if this is the same CPU */
580 if (Prcb
!= CurrentPrcb
)
583 * Check if the DPC is of high importance or above the
584 * maximum depth. If it is, then make sure that the CPU
585 * isn't idle, or that it's sleeping.
587 if (((Dpc
->Importance
== HighImportance
) ||
588 (DpcData
->DpcQueueDepth
>=
589 Prcb
->MaximumDpcQueueDepth
)) &&
590 (!(AFFINITY_MASK(Cpu
) & KiIdleSummary
) ||
593 /* Set interrupt requested */
594 Prcb
->DpcInterruptRequested
= TRUE
;
596 /* Set DPC inserted */
602 /* Check if the DPC is of anything but low importance */
603 if ((Dpc
->Importance
!= LowImportance
) ||
604 (DpcData
->DpcQueueDepth
>=
605 Prcb
->MaximumDpcQueueDepth
) ||
606 (Prcb
->DpcRequestRate
< Prcb
->MinimumDpcRate
))
608 /* Set interrupt requested */
609 Prcb
->DpcInterruptRequested
= TRUE
;
611 /* Set DPC inserted */
619 /* Release the lock */
620 KiReleaseSpinLock(&DpcData
->DpcLock
);
622 /* Check if the DPC was inserted */
625 /* Check if this was SMP */
626 if (Prcb
!= CurrentPrcb
)
628 /* It was, request and IPI */
629 KiIpiSendRequest(AFFINITY_MASK(Cpu
), IPI_DPC
);
633 /* It wasn't, request an interrupt from HAL */
634 HalRequestSoftwareInterrupt(DISPATCH_LEVEL
);
639 KeLowerIrql(OldIrql
);
640 return DpcConfigured
;
648 KeRemoveQueueDpc(IN PKDPC Dpc
)
654 /* Disable interrupts */
657 /* Get DPC data and type */
659 DpcData
= Dpc
->DpcData
;
662 /* Acquire the DPC lock */
663 KiAcquireSpinLock(&DpcData
->DpcLock
);
665 /* Make sure that the data didn't change */
666 if (DpcData
== Dpc
->DpcData
)
669 DpcData
->DpcQueueDepth
--;
670 RemoveEntryList(&Dpc
->DpcListEntry
);
674 /* Release the lock */
675 KiReleaseSpinLock(&DpcData
->DpcLock
);
678 /* Re-enable interrupts */
681 /* Return if the DPC was in the queue or not */
682 return DpcData
? TRUE
: FALSE
;
690 KeFlushQueuedDpcs(VOID
)
694 /* Check if this is an UP machine */
695 if (KeActiveProcessors
== 1)
697 /* Check if there are DPCs on either queues */
698 if ((KeGetCurrentPrcb()->DpcData
[DPC_NORMAL
].DpcQueueDepth
) ||
699 (KeGetCurrentPrcb()->DpcData
[DPC_THREADED
].DpcQueueDepth
))
701 /* Request an interrupt */
702 HalRequestSoftwareInterrupt(DISPATCH_LEVEL
);
707 /* FIXME: SMP support required */
717 KeIsExecutingDpc(VOID
)
719 /* Return if the Dpc Routine is active */
720 return KeGetCurrentPrcb()->DpcRoutineActive
;
728 KeSetImportanceDpc (IN PKDPC Dpc
,
729 IN KDPC_IMPORTANCE Importance
)
731 /* Set the DPC Importance */
733 Dpc
->Importance
= Importance
;
741 KeSetTargetProcessorDpc(IN PKDPC Dpc
,
744 /* Set a target CPU */
746 Dpc
->Number
= Number
+ MAXIMUM_PROCESSORS
;