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