3 * COPYRIGHT: See COPYING in the top level directory
4 * PROJECT: ReactOS kernel
5 * FILE: ntoskrnl/ke/dpc.c
6 * PURPOSE: Handle DPCs (Delayed Procedure Calls)
8 * PROGRAMMERS: David Welch (welch@mcmail.com)
9 * Philip Susi (phreak@iag.net)
10 * Eric Kohl (ekohl@abo.rhein-zeitung.de)
11 * Alex Ionescu (alex@relsoft.net)
15 * NOTE: See also the higher level support routines in ntoskrnl/io/dpc.c
18 /* INCLUDES ***************************************************************/
22 #include <internal/debug.h>
24 /* TYPES *******************************************************************/
26 #define MAX_QUANTUM 0x7F
28 /* FUNCTIONS ****************************************************************/
31 * FUNCTION: Initialize DPC handling
36 KeInitDpc(PKPRCB Prcb
)
38 InitializeListHead(&Prcb
->DpcData
[0].DpcListHead
);
42 * Prcb->DpcEvent is a NULL pointer.
44 KeInitializeEvent(Prcb
->DpcEvent
, 0, 0);
46 KeInitializeSpinLock(&Prcb
->DpcData
[0].DpcLock
);
47 Prcb
->MaximumDpcQueueDepth
= 4;
48 Prcb
->MinimumDpcRate
= 3;
49 Prcb
->DpcData
[0].DpcQueueDepth
= 0;
57 KeInitializeThreadedDpc(PKDPC Dpc
,
58 PKDEFERRED_ROUTINE DeferredRoutine
,
59 PVOID DeferredContext
)
62 * Initalizes a Threaded DPC and registers the DeferredRoutine for it.
64 * Dpc = Pointer to a caller supplied DPC to be initialized. The caller must allocate this memory.
65 * DeferredRoutine = Pointer to associated DPC callback routine.
66 * DeferredContext = Parameter to be passed to the callback routine.
67 * NOTE: Callers can be running at any IRQL.
70 DPRINT("Threaded DPC Initializing: %x with Routine: %x\n", Dpc
, DeferredRoutine
);
71 Dpc
->Type
= ThreadedDpcObject
;
73 Dpc
->Importance
= MediumImportance
;
74 Dpc
->DeferredRoutine
= DeferredRoutine
;
75 Dpc
->DeferredContext
= DeferredContext
;
83 * Initalizes a DPC and registers the DeferredRoutine for it.
85 * Dpc = Pointer to a caller supplied DPC to be initialized. The caller must allocate this memory.
86 * DeferredRoutine = Pointer to associated DPC callback routine.
87 * DeferredContext = Parameter to be passed to the callback routine.
88 * NOTE: Callers can be running at any IRQL.
92 KeInitializeDpc(PKDPC Dpc
,
93 PKDEFERRED_ROUTINE DeferredRoutine
,
94 PVOID DeferredContext
)
96 DPRINT("DPC Initializing: %x with Routine: %x\n", Dpc
, DeferredRoutine
);
97 Dpc
->Type
= DpcObject
;
99 Dpc
->Importance
= MediumImportance
;
100 Dpc
->DeferredRoutine
= DeferredRoutine
;
101 Dpc
->DeferredContext
= DeferredContext
;
109 * Queues a DPC for execution when the IRQL of a processor
110 * drops below DISPATCH_LEVEL
112 * Dpc = Pointed to a DPC Object Initalized by KeInitializeDpc.
113 * SystemArgument1 = Driver Determined context data
114 * SystemArgument2 = Driver Determined context data
116 * TRUE if the DPC object wasn't already in the queue
119 * If there is currently a DPC active on the target processor, or a DPC
120 * interrupt has already been requested on the target processor when a
121 * DPC is queued, then no further action is necessary. The DPC will be
122 * executed on the target processor when its queue entry is processed.
124 * If there is not a DPC active on the target processor and a DPC interrupt
125 * has not been requested on the target processor, then the exact treatment
126 * of the DPC is dependent on whether the host system is a UP system or an
131 * If the DPC is of medium or high importance, the current DPC queue depth
132 * is greater than the maximum target depth, or current DPC request rate is
133 * less the minimum target rate, then a DPC interrupt is requested on the
134 * host processor and the DPC will be processed when the interrupt occurs.
135 * Otherwise, no DPC interupt is requested and the DPC execution will be
136 * delayed until the DPC queue depth is greater that the target depth or the
137 * minimum DPC rate is less than the target rate.
141 * If the DPC is being queued to another processor and the depth of the DPC
142 * queue on the target processor is greater than the maximum target depth or
143 * the DPC is of high importance, then a DPC interrupt is requested on the
144 * target processor and the DPC will be processed when the interrupt occurs.
145 * Otherwise, the DPC execution will be delayed on the target processor until
146 * the DPC queue depth on the target processor is greater that the maximum
147 * target depth or the minimum DPC rate on the target processor is less than
148 * the target mimimum rate.
150 * If the DPC is being queued to the current processor and the DPC is not of
151 * low importance, the current DPC queue depth is greater than the maximum
152 * target depth, or the minimum DPC rate is less than the minimum target rate,
153 * then a DPC interrupt is request on the current processor and the DPV will
154 * be processed whne the interrupt occurs. Otherwise, no DPC interupt is
155 * requested and the DPC execution will be delayed until the DPC queue depth
156 * is greater that the target depth or the minimum DPC rate is less than the
161 KeInsertQueueDpc(PKDPC Dpc
,
162 PVOID SystemArgument1
,
163 PVOID SystemArgument2
)
168 DPRINT("KeInsertQueueDpc(DPC %x, SystemArgument1 %x, SystemArgument2 %x)\n",
169 Dpc
, SystemArgument1
, SystemArgument2
);
171 /* Check IRQL and Raise it to HIGH_LEVEL */
172 ASSERT(KeGetCurrentIrql()>=DISPATCH_LEVEL
);
173 KeRaiseIrql(HIGH_LEVEL
, &OldIrql
);
175 /* Check if this is a Thread DPC, which we don't support (yet) */
176 if (Dpc
->Type
== ThreadedDpcObject
) {
178 KeLowerIrql(OldIrql
);
182 /* Get the right PCR for this CPU */
183 if (Dpc
->Number
>= MAXIMUM_PROCESSORS
) {
185 ASSERT (Dpc
->Number
- MAXIMUM_PROCESSORS
< KeNumberProcessors
);
186 Prcb
= ((PKPCR
)((ULONG_PTR
)KPCR_BASE
+ ((Dpc
->Number
- MAXIMUM_PROCESSORS
) * PAGE_SIZE
)))->Prcb
;
190 ASSERT (Dpc
->Number
< KeNumberProcessors
);
191 Prcb
= KeGetCurrentPrcb();
192 Dpc
->Number
= KeGetCurrentProcessorNumber();
195 KiAcquireSpinLock(&Prcb
->DpcData
[0].DpcLock
);
197 Prcb
= ((PKPCR
)KPCR_BASE
)->Prcb
;
200 /* Get the DPC Data */
201 if (InterlockedCompareExchangeUL(&Dpc
->DpcData
, &Prcb
->DpcData
[0].DpcLock
, 0)) {
203 DPRINT("DPC Already Inserted\n");
205 KiReleaseSpinLock(&Prcb
->DpcData
[0].DpcLock
);
207 KeLowerIrql(OldIrql
);
211 /* Make sure the lists are free if the Queue is 0 */
212 if (Prcb
->DpcData
[0].DpcQueueDepth
== 0) {
214 ASSERT(IsListEmpty(&Prcb
->DpcData
[0].DpcListHead
));
217 ASSERT(!IsListEmpty(&Prcb
->DpcData
[0].DpcListHead
));
220 /* Now we can play with the DPC safely */
221 Dpc
->SystemArgument1
=SystemArgument1
;
222 Dpc
->SystemArgument2
=SystemArgument2
;
223 Prcb
->DpcData
[0].DpcQueueDepth
++;
224 Prcb
->DpcData
[0].DpcCount
++;
226 /* Insert the DPC into the list. HighImportance DPCs go at the beginning */
227 if (Dpc
->Importance
== HighImportance
) {
229 InsertHeadList(&Prcb
->DpcData
[0].DpcListHead
, &Dpc
->DpcListEntry
);
232 InsertTailList(&Prcb
->DpcData
[0].DpcListHead
, &Dpc
->DpcListEntry
);
234 DPRINT("New DPC Added. Dpc->DpcListEntry.Flink %x\n", Dpc
->DpcListEntry
.Flink
);
236 /* Make sure a DPC isn't executing already and respect rules outlined above. */
237 if ((!Prcb
->DpcRoutineActive
) && (!Prcb
->DpcInterruptRequested
)) {
240 /* Check if this is the same CPU */
241 if (Prcb
!= KeGetCurrentPrcb()) {
243 /* Send IPI if High Importance */
244 if ((Dpc
->Importance
== HighImportance
) ||
245 (Prcb
->DpcData
[0].DpcQueueDepth
>= Prcb
->MaximumDpcQueueDepth
)) {
247 if (Dpc
->Number
>= MAXIMUM_PROCESSORS
) {
249 KiIpiSendRequest(1 << (Dpc
->Number
- MAXIMUM_PROCESSORS
), IPI_DPC
);
252 KiIpiSendRequest(1 << Dpc
->Number
, IPI_DPC
);
258 /* Request an Interrupt only if the DPC isn't low priority */
259 if ((Dpc
->Importance
!= LowImportance
) ||
260 (Prcb
->DpcData
[0].DpcQueueDepth
>= Prcb
->MaximumDpcQueueDepth
) ||
261 (Prcb
->DpcRequestRate
< Prcb
->MinimumDpcRate
)) {
263 /* Request Interrupt */
264 HalRequestSoftwareInterrupt(DISPATCH_LEVEL
);
265 Prcb
->DpcInterruptRequested
= TRUE
;
269 DPRINT("Requesting Interrupt. Importance: %x. QueueDepth: %x. MaxQueue: %x . RequestRate: %x. MinRate:%x \n", Dpc
->Importance
, Prcb
->DpcData
[0].DpcQueueDepth
, Prcb
->MaximumDpcQueueDepth
, Prcb
->DpcRequestRate
, Prcb
->MinimumDpcRate
);
271 /* Request an Interrupt only if the DPC isn't low priority */
272 if ((Dpc
->Importance
!= LowImportance
) ||
273 (Prcb
->DpcData
[0].DpcQueueDepth
>= Prcb
->MaximumDpcQueueDepth
) ||
274 (Prcb
->DpcRequestRate
< Prcb
->MinimumDpcRate
)) {
276 /* Request Interrupt */
277 DPRINT("Requesting Interrupt\n");
278 HalRequestSoftwareInterrupt(DISPATCH_LEVEL
);
279 Prcb
->DpcInterruptRequested
= TRUE
;
284 KiReleaseSpinLock(&Prcb
->DpcData
[0].DpcLock
);
287 KeLowerIrql(OldIrql
);
295 * Removes DPC object from the system dpc queue
297 * Dpc = Pointer to DPC to remove from the queue.
299 * TRUE if the DPC was in the queue
304 KeRemoveQueueDpc(PKDPC Dpc
)
310 DPRINT("Removing DPC: %x\n", Dpc
);
311 KeRaiseIrql(HIGH_LEVEL
, &OldIrql
);
313 KiAcquireSpinLock(&((PKDPC_DATA
)Dpc
->DpcData
)->DpcLock
);
316 /* First make sure the DPC lock isn't being held */
317 WasInQueue
= Dpc
->DpcData
? TRUE
: FALSE
;
321 ((PKDPC_DATA
)Dpc
->DpcData
)->DpcQueueDepth
--;
322 RemoveEntryList(&Dpc
->DpcListEntry
);
326 KiReleaseSpinLock(&((PKDPC_DATA
)Dpc
->DpcData
)->DpcLock
);
329 /* Return if the DPC was in the queue or not */
330 KeLowerIrql(OldIrql
);
339 KeFlushQueuedDpcs(VOID
)
342 * Called to Deliver DPCs if any are pending.
344 * Called when deleting a Driver.
347 /* Request an interrupt if needed */
348 if (KeGetCurrentPrcb()->DpcData
[0].DpcQueueDepth
) HalRequestSoftwareInterrupt(DISPATCH_LEVEL
);
360 /* Return if the Dpc Routine is active */
361 return KeGetCurrentPrcb()->DpcRoutineActive
;
365 * FUNCTION: Specifies the DPCs importance
367 * Dpc = Initalizes DPC
368 * Importance = DPC importance
375 KeSetImportanceDpc (IN PKDPC Dpc
,
376 IN KDPC_IMPORTANCE Importance
)
378 /* Set the DPC Importance */
379 Dpc
->Importance
= Importance
;
385 * FUNCTION: Specifies on which processor the DPC will run
387 * Dpc = Initalizes DPC
388 * Number = Processor number
393 KeSetTargetProcessorDpc(IN PKDPC Dpc
,
396 /* Check how many CPUs are on the system */
397 if (Number
>= MAXIMUM_PROCESSORS
) {
404 /* Set the Number Specified */
405 ASSERT(Number
< KeNumberProcessors
);
406 Dpc
->Number
= Number
+ MAXIMUM_PROCESSORS
;
412 * Called when a quantum end occurs to check if priority should be changed
413 * and wether a new thread should be dispatched.
415 * Called when deleting a Driver.
422 PKTHREAD CurrentThread
;
425 KPRIORITY OldPriority
;
426 KPRIORITY NewPriority
;
428 /* Lock dispatcher, get current thread */
429 Prcb
= KeGetCurrentPrcb();
430 CurrentThread
= KeGetCurrentThread();
431 OldIrql
= KeRaiseIrqlToSynchLevel();
433 /* Get the Thread's Process */
434 Process
= CurrentThread
->ApcState
.Process
;
436 /* Set DPC Event if requested */
437 if (Prcb
->DpcSetEventRequest
) {
440 * Prcb->DpcEvent is not initialized.
443 KeSetEvent(Prcb
->DpcEvent
, 0, 0);
446 /* Check if Quantum expired */
447 if (CurrentThread
->Quantum
<= 0) {
449 /* Reset the new Quantum */
450 CurrentThread
->Quantum
= CurrentThread
->QuantumReset
;
452 /* Calculate new priority */
453 OldPriority
= CurrentThread
->Priority
;
454 if (OldPriority
< LOW_REALTIME_PRIORITY
) {
456 /* Set the New Priority and add the Priority Decrement */
457 NewPriority
= OldPriority
- CurrentThread
->PriorityDecrement
- 1;
459 /* Don't go out of bounds */
460 if (NewPriority
< CurrentThread
->BasePriority
) NewPriority
= CurrentThread
->BasePriority
;
462 /* Reset the priority decrement */
463 CurrentThread
->PriorityDecrement
= 0;
465 /* Set a new priority if needed */
466 if (OldPriority
!= NewPriority
) {
468 /* Set new Priority */
469 BOOLEAN Dummy
; /* <- This is a hack anyways... */
470 KiSetPriorityThread(CurrentThread
, NewPriority
, &Dummy
);
474 /* Queue new thread if none is already */
475 if (Prcb
->NextThread
== NULL
) {
477 /* FIXME: Schedule a New Thread, when ROS will have NT Scheduler */
481 /* Make the current thread non-premeptive if a new thread is queued */
482 CurrentThread
->Preempted
= FALSE
;
488 /* Set the Quantum back to Maximum */
489 //if (CurrentThread->DisableQuantum) {
490 // CurrentThread->Quantum = MAX_QUANTUM;
495 /* Dispatch the Thread */
496 KeLowerIrql(DISPATCH_LEVEL
);
497 KiDispatchThread(Ready
);
504 * Called whenever a system interrupt is generated at DISPATCH_LEVEL.
505 * It delivers queued DPCs and dispatches a new thread if need be.
509 KiDispatchInterrupt(VOID
)
511 PLIST_ENTRY DpcEntry
;
516 DPRINT("Dispatching Interrupts\n");
517 ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL
);
519 /* Set DPC Deliver to Active */
520 Prcb
= KeGetCurrentPrcb();
522 if (Prcb
->DpcData
[0].DpcQueueDepth
> 0) {
524 KeRaiseIrql(HIGH_LEVEL
, &OldIrql
);
526 KiAcquireSpinLock(&Prcb
->DpcData
[0].DpcLock
);
528 Prcb
->DpcRoutineActive
= TRUE
;
530 DPRINT("&Prcb->DpcData[0].DpcListHead: %x\n", &Prcb
->DpcData
[0].DpcListHead
);
531 /* Loop while we have entries */
532 while (!IsListEmpty(&Prcb
->DpcData
[0].DpcListHead
)) {
534 ASSERT(Prcb
->DpcData
[0].DpcQueueDepth
> 0);
535 DPRINT("Queue Depth: %x\n", Prcb
->DpcData
[0].DpcQueueDepth
);
537 /* Get the DPC call it */
538 DpcEntry
= RemoveHeadList(&Prcb
->DpcData
[0].DpcListHead
);
539 Dpc
= CONTAINING_RECORD(DpcEntry
, KDPC
, DpcListEntry
);
540 DPRINT("Dpc->DpcListEntry.Flink %x\n", Dpc
->DpcListEntry
.Flink
);
542 Prcb
->DpcData
[0].DpcQueueDepth
--;
544 KiReleaseSpinLock(&Prcb
->DpcData
[0].DpcLock
);
546 /* Disable/Enabled Interrupts and Call the DPC */
547 KeLowerIrql(OldIrql
);
548 DPRINT("Calling DPC: %x\n", Dpc
);
549 Dpc
->DeferredRoutine(Dpc
,
550 Dpc
->DeferredContext
,
551 Dpc
->SystemArgument1
,
552 Dpc
->SystemArgument2
);
553 KeRaiseIrql(HIGH_LEVEL
, &OldIrql
);
556 KiAcquireSpinLock(&Prcb
->DpcData
[0].DpcLock
);
558 * If the dpc routine drops the irql below DISPATCH_LEVEL,
559 * a thread switch can occur and after the next thread switch
560 * the execution may start on an other processor.
562 if (Prcb
!= KeGetCurrentPrcb()) {
564 Prcb
->DpcRoutineActive
= FALSE
;
565 KiReleaseSpinLock(&Prcb
->DpcData
[0].DpcLock
);
566 Prcb
= KeGetCurrentPrcb();
567 KiAcquireSpinLock(&Prcb
->DpcData
[0].DpcLock
);
568 Prcb
->DpcRoutineActive
= TRUE
;
572 /* Clear DPC Flags */
573 Prcb
->DpcRoutineActive
= FALSE
;
574 Prcb
->DpcInterruptRequested
= FALSE
;
576 KiReleaseSpinLock(&Prcb
->DpcData
[0].DpcLock
);
579 /* DPC Dispatching Ended, re-enable interrupts */
580 KeLowerIrql(OldIrql
);
583 DPRINT("Checking for Quantum End\n");
585 /* If we have Quantum End, call the function */
586 if (Prcb
->QuantumEnd
) {
588 Prcb
->QuantumEnd
= FALSE
;