- Add some TIMER values to the ddk.
[reactos.git] / reactos / ntoskrnl / ke / dpc.c
1 /* $Id$
2 *
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)
7 *
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)
12 */
13
14 /*
15 * NOTE: See also the higher level support routines in ntoskrnl/io/dpc.c
16 */
17
18 /* INCLUDES ***************************************************************/
19
20 #include <ntoskrnl.h>
21 #define NDEBUG
22 #include <internal/debug.h>
23
24 #if defined (ALLOC_PRAGMA)
25 #pragma alloc_text(INIT, KeInitDpc)
26 #endif
27
28 ULONG KiMaximumDpcQueueDepth = 4;
29 ULONG KiMinimumDpcRate = 3;
30 ULONG KiAdjustDpcThreshold = 20;
31 ULONG KiIdealDpcRate = 20;
32
33 /* TYPES *******************************************************************/
34
35 #define MAX_QUANTUM 0x7F
36
37 /* FUNCTIONS ****************************************************************/
38
39 /*
40 * FUNCTION: Initialize DPC handling
41 */
42 VOID
43 INIT_FUNCTION
44 NTAPI
45 KeInitDpc(PKPRCB Prcb)
46 {
47 InitializeListHead(&Prcb->DpcData[0].DpcListHead);
48 #if 0
49 /*
50 * FIXME:
51 * Prcb->DpcEvent is a NULL pointer.
52 */
53 KeInitializeEvent(Prcb->DpcEvent, 0, 0);
54 #endif
55 KeInitializeSpinLock(&Prcb->DpcData[0].DpcLock);
56 Prcb->MaximumDpcQueueDepth = KiMaximumDpcQueueDepth;
57 Prcb->MinimumDpcRate = KiMinimumDpcRate;
58 Prcb->DpcData[0].DpcQueueDepth = 0;
59 }
60
61 /*
62 * @implemented
63 */
64 VOID
65 STDCALL
66 KeInitializeThreadedDpc(PKDPC Dpc,
67 PKDEFERRED_ROUTINE DeferredRoutine,
68 PVOID DeferredContext)
69 /*
70 * FUNCTION:
71 * Initalizes a Threaded DPC and registers the DeferredRoutine for it.
72 * ARGUMENTS:
73 * Dpc = Pointer to a caller supplied DPC to be initialized. The caller must allocate this memory.
74 * DeferredRoutine = Pointer to associated DPC callback routine.
75 * DeferredContext = Parameter to be passed to the callback routine.
76 * NOTE: Callers can be running at any IRQL.
77 */
78 {
79 DPRINT("Threaded DPC Initializing: %x with Routine: %x\n", Dpc, DeferredRoutine);
80 Dpc->Type = ThreadedDpcObject;
81 Dpc->Number= 0;
82 Dpc->Importance= MediumImportance;
83 Dpc->DeferredRoutine = DeferredRoutine;
84 Dpc->DeferredContext = DeferredContext;
85 Dpc->DpcData = NULL;
86 }
87
88 /*
89 * @implemented
90 *
91 * FUNCTION:
92 * Initalizes a DPC and registers the DeferredRoutine for it.
93 * ARGUMENTS:
94 * Dpc = Pointer to a caller supplied DPC to be initialized. The caller must allocate this memory.
95 * DeferredRoutine = Pointer to associated DPC callback routine.
96 * DeferredContext = Parameter to be passed to the callback routine.
97 * NOTE: Callers can be running at any IRQL.
98 */
99 VOID
100 STDCALL
101 KeInitializeDpc(PKDPC Dpc,
102 PKDEFERRED_ROUTINE DeferredRoutine,
103 PVOID DeferredContext)
104 {
105 DPRINT("DPC Initializing: %x with Routine: %x\n", Dpc, DeferredRoutine);
106 Dpc->Type = DpcObject;
107 Dpc->Number= 0;
108 Dpc->Importance= MediumImportance;
109 Dpc->DeferredRoutine = DeferredRoutine;
110 Dpc->DeferredContext = DeferredContext;
111 Dpc->DpcData = NULL;
112 }
113
114 /*
115 * @implemented
116 *
117 * FUNCTION:
118 * Queues a DPC for execution when the IRQL of a processor
119 * drops below DISPATCH_LEVEL
120 * ARGUMENTS:
121 * Dpc = Pointed to a DPC Object Initalized by KeInitializeDpc.
122 * SystemArgument1 = Driver Determined context data
123 * SystemArgument2 = Driver Determined context data
124 * RETURNS:
125 * TRUE if the DPC object wasn't already in the queue
126 * FALSE otherwise
127 * NOTES:
128 * If there is currently a DPC active on the target processor, or a DPC
129 * interrupt has already been requested on the target processor when a
130 * DPC is queued, then no further action is necessary. The DPC will be
131 * executed on the target processor when its queue entry is processed.
132 *
133 * If there is not a DPC active on the target processor and a DPC interrupt
134 * has not been requested on the target processor, then the exact treatment
135 * of the DPC is dependent on whether the host system is a UP system or an
136 * MP system.
137 *
138 * UP system.
139 * ----------
140 * If the DPC is of medium or high importance, the current DPC queue depth
141 * is greater than the maximum target depth, or current DPC request rate is
142 * less the minimum target rate, then a DPC interrupt is requested on the
143 * host processor and the DPC will be processed when the interrupt occurs.
144 * Otherwise, no DPC interupt is requested and the DPC execution will be
145 * delayed until the DPC queue depth is greater that the target depth or the
146 * minimum DPC rate is less than the target rate.
147 *
148 * MP system.
149 * ----------
150 * If the DPC is being queued to another processor and the depth of the DPC
151 * queue on the target processor is greater than the maximum target depth or
152 * the DPC is of high importance, then a DPC interrupt is requested on the
153 * target processor and the DPC will be processed when the interrupt occurs.
154 * Otherwise, the DPC execution will be delayed on the target processor until
155 * the DPC queue depth on the target processor is greater that the maximum
156 * target depth or the minimum DPC rate on the target processor is less than
157 * the target mimimum rate.
158 *
159 * If the DPC is being queued to the current processor and the DPC is not of
160 * low importance, the current DPC queue depth is greater than the maximum
161 * target depth, or the minimum DPC rate is less than the minimum target rate,
162 * then a DPC interrupt is request on the current processor and the DPV will
163 * be processed whne the interrupt occurs. Otherwise, no DPC interupt is
164 * requested and the DPC execution will be delayed until the DPC queue depth
165 * is greater that the target depth or the minimum DPC rate is less than the
166 * target rate.
167 */
168 BOOLEAN
169 STDCALL
170 KeInsertQueueDpc(PKDPC Dpc,
171 PVOID SystemArgument1,
172 PVOID SystemArgument2)
173 {
174 KIRQL OldIrql;
175 PKPRCB Prcb;
176
177 DPRINT("KeInsertQueueDpc(DPC %x, SystemArgument1 %x, SystemArgument2 %x)\n",
178 Dpc, SystemArgument1, SystemArgument2);
179
180 /* Check IRQL and Raise it to HIGH_LEVEL */
181 ASSERT(KeGetCurrentIrql()>=DISPATCH_LEVEL);
182 KeRaiseIrql(HIGH_LEVEL, &OldIrql);
183
184 /* Check if this is a Thread DPC, which we don't support (yet) */
185 if (Dpc->Type == ThreadedDpcObject) {
186 KeLowerIrql(OldIrql);
187 return FALSE;
188 }
189
190 #ifdef CONFIG_SMP
191 /* Get the right PCR for this CPU */
192 if (Dpc->Number >= MAXIMUM_PROCESSORS) {
193
194 ASSERT (Dpc->Number - MAXIMUM_PROCESSORS < KeNumberProcessors);
195 Prcb = ((PKPCR)((ULONG_PTR)KPCR_BASE + ((Dpc->Number - MAXIMUM_PROCESSORS) * PAGE_SIZE)))->Prcb;
196
197 } else {
198
199 ASSERT (Dpc->Number < KeNumberProcessors);
200 Prcb = KeGetCurrentPrcb();
201 Dpc->Number = KeGetCurrentProcessorNumber();
202 }
203
204 KiAcquireSpinLock(&Prcb->DpcData[0].DpcLock);
205 #else
206 Prcb = ((PKPCR)KPCR_BASE)->Prcb;
207 #endif
208
209 /* Get the DPC Data */
210 if (InterlockedCompareExchangeUL(&Dpc->DpcData, &Prcb->DpcData[0].DpcLock, 0)) {
211
212 DPRINT("DPC Already Inserted\n");
213 #ifdef CONFIG_SMP
214 KiReleaseSpinLock(&Prcb->DpcData[0].DpcLock);
215 #endif
216 KeLowerIrql(OldIrql);
217 return(FALSE);
218 }
219
220 /* Make sure the lists are free if the Queue is 0 */
221 if (Prcb->DpcData[0].DpcQueueDepth == 0) {
222
223 ASSERT(IsListEmpty(&Prcb->DpcData[0].DpcListHead));
224 } else {
225
226 ASSERT(!IsListEmpty(&Prcb->DpcData[0].DpcListHead));
227 }
228
229 /* Now we can play with the DPC safely */
230 Dpc->SystemArgument1=SystemArgument1;
231 Dpc->SystemArgument2=SystemArgument2;
232 Prcb->DpcData[0].DpcQueueDepth++;
233 Prcb->DpcData[0].DpcCount++;
234
235 /* Insert the DPC into the list. HighImportance DPCs go at the beginning */
236 if (Dpc->Importance == HighImportance) {
237
238 InsertHeadList(&Prcb->DpcData[0].DpcListHead, &Dpc->DpcListEntry);
239 } else {
240
241 InsertTailList(&Prcb->DpcData[0].DpcListHead, &Dpc->DpcListEntry);
242 }
243 DPRINT("New DPC Added. Dpc->DpcListEntry.Flink %x\n", Dpc->DpcListEntry.Flink);
244
245 /* Make sure a DPC isn't executing already and respect rules outlined above. */
246 if ((!Prcb->DpcRoutineActive) && (!Prcb->DpcInterruptRequested)) {
247
248 #ifdef CONFIG_SMP
249 /* Check if this is the same CPU */
250 if (Prcb != KeGetCurrentPrcb()) {
251
252 /* Send IPI if High Importance */
253 if ((Dpc->Importance == HighImportance) ||
254 (Prcb->DpcData[0].DpcQueueDepth >= Prcb->MaximumDpcQueueDepth)) {
255
256 if (Dpc->Number >= MAXIMUM_PROCESSORS) {
257
258 KiIpiSendRequest(1 << (Dpc->Number - MAXIMUM_PROCESSORS), IPI_DPC);
259 } else {
260
261 KiIpiSendRequest(1 << Dpc->Number, IPI_DPC);
262 }
263
264 }
265 } else {
266
267 /* Request an Interrupt only if the DPC isn't low priority */
268 if ((Dpc->Importance != LowImportance) ||
269 (Prcb->DpcData[0].DpcQueueDepth >= Prcb->MaximumDpcQueueDepth) ||
270 (Prcb->DpcRequestRate < Prcb->MinimumDpcRate)) {
271
272 /* Request Interrupt */
273 HalRequestSoftwareInterrupt(DISPATCH_LEVEL);
274 Prcb->DpcInterruptRequested = TRUE;
275 }
276 }
277 #else
278 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);
279
280 /* Request an Interrupt only if the DPC isn't low priority */
281 if ((Dpc->Importance != LowImportance) ||
282 (Prcb->DpcData[0].DpcQueueDepth >= Prcb->MaximumDpcQueueDepth) ||
283 (Prcb->DpcRequestRate < Prcb->MinimumDpcRate)) {
284
285 /* Request Interrupt */
286 DPRINT("Requesting Interrupt\n");
287 HalRequestSoftwareInterrupt(DISPATCH_LEVEL);
288 Prcb->DpcInterruptRequested = TRUE;
289 }
290 #endif
291 }
292 #ifdef CONFIG_SMP
293 KiReleaseSpinLock(&Prcb->DpcData[0].DpcLock);
294 #endif
295 /* Lower IRQL */
296 KeLowerIrql(OldIrql);
297 return(TRUE);
298 }
299
300 /*
301 * @implemented
302 *
303 * FUNCTION:
304 * Removes DPC object from the system dpc queue
305 * ARGUMENTS:
306 * Dpc = Pointer to DPC to remove from the queue.
307 * RETURNS:
308 * TRUE if the DPC was in the queue
309 * FALSE otherwise
310 */
311 BOOLEAN
312 STDCALL
313 KeRemoveQueueDpc(PKDPC Dpc)
314 {
315 BOOLEAN WasInQueue;
316 KIRQL OldIrql;
317
318 /* Raise IRQL */
319 DPRINT("Removing DPC: %x\n", Dpc);
320 KeRaiseIrql(HIGH_LEVEL, &OldIrql);
321 #ifdef CONFIG_SMP
322 KiAcquireSpinLock(&((PKDPC_DATA)Dpc->DpcData)->DpcLock);
323 #endif
324
325 /* First make sure the DPC lock isn't being held */
326 WasInQueue = Dpc->DpcData ? TRUE : FALSE;
327 if (Dpc->DpcData) {
328
329 /* Remove the DPC */
330 ((PKDPC_DATA)Dpc->DpcData)->DpcQueueDepth--;
331 RemoveEntryList(&Dpc->DpcListEntry);
332
333 }
334 #ifdef CONFIG_SMP
335 KiReleaseSpinLock(&((PKDPC_DATA)Dpc->DpcData)->DpcLock);
336 #endif
337
338 /* Return if the DPC was in the queue or not */
339 KeLowerIrql(OldIrql);
340 return WasInQueue;
341 }
342
343 /*
344 * @implemented
345 */
346 VOID
347 STDCALL
348 KeFlushQueuedDpcs(VOID)
349 /*
350 * FUNCTION:
351 * Called to Deliver DPCs if any are pending.
352 * NOTES:
353 * Called when deleting a Driver.
354 */
355 {
356 /* Request an interrupt if needed */
357 if (KeGetCurrentPrcb()->DpcData[0].DpcQueueDepth) HalRequestSoftwareInterrupt(DISPATCH_LEVEL);
358 }
359
360 /*
361 * @implemented
362 */
363 BOOLEAN
364 STDCALL
365 KeIsExecutingDpc(
366 VOID
367 )
368 {
369 /* Return if the Dpc Routine is active */
370 return KeGetCurrentPrcb()->DpcRoutineActive;
371 }
372
373 /*
374 * FUNCTION: Specifies the DPCs importance
375 * ARGUMENTS:
376 * Dpc = Initalizes DPC
377 * Importance = DPC importance
378 * RETURNS: None
379 *
380 * @implemented
381 */
382 VOID
383 STDCALL
384 KeSetImportanceDpc (IN PKDPC Dpc,
385 IN KDPC_IMPORTANCE Importance)
386 {
387 /* Set the DPC Importance */
388 Dpc->Importance = Importance;
389 }
390
391 /*
392 * @implemented
393 *
394 * FUNCTION: Specifies on which processor the DPC will run
395 * ARGUMENTS:
396 * Dpc = Initalizes DPC
397 * Number = Processor number
398 * RETURNS: None
399 */
400 VOID
401 STDCALL
402 KeSetTargetProcessorDpc(IN PKDPC Dpc,
403 IN CCHAR Number)
404 {
405 /* Check how many CPUs are on the system */
406 if (Number >= MAXIMUM_PROCESSORS) {
407
408 /* No CPU Number */
409 Dpc->Number = 0;
410
411 } else {
412
413 /* Set the Number Specified */
414 ASSERT(Number < KeNumberProcessors);
415 Dpc->Number = Number + MAXIMUM_PROCESSORS;
416 }
417 }
418
419 /*
420 * FUNCTION:
421 * Called when a quantum end occurs to check if priority should be changed
422 * and wether a new thread should be dispatched.
423 * NOTES:
424 * Called when deleting a Driver.
425 */
426 VOID
427 STDCALL
428 KiQuantumEnd(VOID)
429 {
430 PKPRCB Prcb;
431 PKTHREAD CurrentThread;
432 KIRQL OldIrql;
433 PKPROCESS Process;
434 KPRIORITY OldPriority;
435 KPRIORITY NewPriority;
436
437 /* Lock dispatcher, get current thread */
438 Prcb = KeGetCurrentPrcb();
439 CurrentThread = KeGetCurrentThread();
440 OldIrql = KeRaiseIrqlToSynchLevel();
441
442 /* Get the Thread's Process */
443 Process = CurrentThread->ApcState.Process;
444
445 /* Set DPC Event if requested */
446 if (Prcb->DpcSetEventRequest)
447 {
448 KeSetEvent(&Prcb->DpcEvent, 0, 0);
449 }
450
451 /* Check if Quantum expired */
452 if (CurrentThread->Quantum <= 0) {
453
454 /* Reset the new Quantum */
455 CurrentThread->Quantum = CurrentThread->QuantumReset;
456
457 /* Calculate new priority */
458 OldPriority = CurrentThread->Priority;
459 if (OldPriority < LOW_REALTIME_PRIORITY) {
460
461 /* Set the New Priority and add the Priority Decrement */
462 NewPriority = OldPriority - CurrentThread->PriorityDecrement - 1;
463
464 /* Don't go out of bounds */
465 if (NewPriority < CurrentThread->BasePriority) NewPriority = CurrentThread->BasePriority;
466
467 /* Reset the priority decrement */
468 CurrentThread->PriorityDecrement = 0;
469
470 /* Set a new priority if needed */
471 if (OldPriority != NewPriority) {
472
473 /* Set new Priority */
474 BOOLEAN Dummy; /* <- This is a hack anyways... */
475 KiSetPriorityThread(CurrentThread, NewPriority, &Dummy);
476
477 } else {
478
479 /* Queue new thread if none is already */
480 if (Prcb->NextThread == NULL) {
481
482 /* FIXME: Schedule a New Thread, when ROS will have NT Scheduler */
483
484 } else {
485
486 /* Make the current thread non-premeptive if a new thread is queued */
487 CurrentThread->Preempted = FALSE;
488 }
489 }
490
491
492 } else {
493 /* Set the Quantum back to Maximum */
494 //if (CurrentThread->DisableQuantum) {
495 // CurrentThread->Quantum = MAX_QUANTUM;
496 //}
497 }
498 }
499
500 /* Dispatch the Thread */
501 KeLowerIrql(DISPATCH_LEVEL);
502 KiDispatchThread(Ready);
503 }
504
505 /*
506 * @implemented
507 *
508 * FUNCTION:
509 * Called whenever a system interrupt is generated at DISPATCH_LEVEL.
510 * It delivers queued DPCs and dispatches a new thread if need be.
511 */
512 VOID
513 STDCALL
514 KiDispatchInterrupt(VOID)
515 {
516 PLIST_ENTRY DpcEntry;
517 PKDPC Dpc;
518 KIRQL OldIrql;
519 PKPRCB Prcb;
520
521 DPRINT("Dispatching Interrupts\n");
522 ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);
523
524 /* Set DPC Deliver to Active */
525 Prcb = KeGetCurrentPrcb();
526
527 if (Prcb->DpcData[0].DpcQueueDepth > 0) {
528 /* Raise IRQL */
529 KeRaiseIrql(HIGH_LEVEL, &OldIrql);
530 #ifdef CONFIG_SMP
531 KiAcquireSpinLock(&Prcb->DpcData[0].DpcLock);
532 #endif
533 Prcb->DpcRoutineActive = TRUE;
534
535 DPRINT("&Prcb->DpcData[0].DpcListHead: %x\n", &Prcb->DpcData[0].DpcListHead);
536 /* Loop while we have entries */
537 while (!IsListEmpty(&Prcb->DpcData[0].DpcListHead)) {
538
539 ASSERT(Prcb->DpcData[0].DpcQueueDepth > 0);
540 DPRINT("Queue Depth: %x\n", Prcb->DpcData[0].DpcQueueDepth);
541
542 /* Get the DPC call it */
543 DpcEntry = RemoveHeadList(&Prcb->DpcData[0].DpcListHead);
544 Dpc = CONTAINING_RECORD(DpcEntry, KDPC, DpcListEntry);
545 DPRINT("Dpc->DpcListEntry.Flink %x\n", Dpc->DpcListEntry.Flink);
546 Dpc->DpcData = NULL;
547 Prcb->DpcData[0].DpcQueueDepth--;
548 #ifdef CONFIG_SMP
549 KiReleaseSpinLock(&Prcb->DpcData[0].DpcLock);
550 #endif
551 /* Disable/Enabled Interrupts and Call the DPC */
552 KeLowerIrql(OldIrql);
553 DPRINT("Calling DPC: %x\n", Dpc);
554 Dpc->DeferredRoutine(Dpc,
555 Dpc->DeferredContext,
556 Dpc->SystemArgument1,
557 Dpc->SystemArgument2);
558 KeRaiseIrql(HIGH_LEVEL, &OldIrql);
559
560 #ifdef CONFIG_SMP
561 KiAcquireSpinLock(&Prcb->DpcData[0].DpcLock);
562 /*
563 * If the dpc routine drops the irql below DISPATCH_LEVEL,
564 * a thread switch can occur and after the next thread switch
565 * the execution may start on an other processor.
566 */
567 if (Prcb != KeGetCurrentPrcb()) {
568
569 Prcb->DpcRoutineActive = FALSE;
570 KiReleaseSpinLock(&Prcb->DpcData[0].DpcLock);
571 Prcb = KeGetCurrentPrcb();
572 KiAcquireSpinLock(&Prcb->DpcData[0].DpcLock);
573 Prcb->DpcRoutineActive = TRUE;
574 }
575 #endif
576 }
577 /* Clear DPC Flags */
578 Prcb->DpcRoutineActive = FALSE;
579 Prcb->DpcInterruptRequested = FALSE;
580 #ifdef CONFIG_SMP
581 KiReleaseSpinLock(&Prcb->DpcData[0].DpcLock);
582 #endif
583
584 /* DPC Dispatching Ended, re-enable interrupts */
585 KeLowerIrql(OldIrql);
586 }
587
588 DPRINT("Checking for Quantum End\n");
589
590 /* If we have Quantum End, call the function */
591 if (Prcb->QuantumEnd) {
592
593 Prcb->QuantumEnd = FALSE;
594 KiQuantumEnd();
595 }
596 }
597
598 /* EOF */