e18eb0e418d9f20462c53d2b4e29011d3ab50095
[reactos.git] / reactos / ntoskrnl / ke / dpc.c
1 /*
2 * PROJECT: ReactOS Kernel
3 * LICENSE: GPL - See COPYING in the top level directory
4 * FILE: ntoskrnl/ke/dpc.c
5 * PURPOSE: Routines for CPU-level support
6 * PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org)
7 * Philip Susi (phreak@iag.net)
8 * Eric Kohl (ekohl@abo.rhein-zeitung.de)
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
26 /* PRIVATE FUNCTIONS *********************************************************/
27
28 //
29 // This routine executes at the end of a thread's quantum.
30 // If the thread's quantum has expired, then a new thread is attempted
31 // to be scheduled.
32 //
33 // If no candidate thread has been found, the routine will return, otherwise
34 // it will swap contexts to the next scheduled thread.
35 //
36 VOID
37 NTAPI
38 KiQuantumEnd(VOID)
39 {
40 PKPRCB Prcb = KeGetCurrentPrcb();
41 PKTHREAD NextThread, Thread = Prcb->CurrentThread;
42
43 /* Check if a DPC Event was requested to be signaled */
44 if (InterlockedExchange(&Prcb->DpcSetEventRequest, 0))
45 {
46 /* Signal it */
47 KeSetEvent(&Prcb->DpcEvent, 0, 0);
48 }
49
50 /* Raise to synchronization level and lock the PRCB and thread */
51 KeRaiseIrqlToSynchLevel();
52 KiAcquireThreadLock(Thread);
53 KiAcquirePrcbLock(Prcb);
54
55 /* Check if Quantum expired */
56 if (Thread->Quantum <= 0)
57 {
58 /* Make sure that we're not real-time or without a quantum */
59 if ((Thread->Priority < LOW_REALTIME_PRIORITY) &&
60 !(Thread->ApcState.Process->DisableQuantum))
61 {
62 /* Reset the new Quantum */
63 Thread->Quantum = Thread->QuantumReset;
64
65 /* Calculate new priority */
66 Thread->Priority = KiComputeNewPriority(Thread, 1);
67
68 /* Check if a new thread is scheduled */
69 if (!Prcb->NextThread)
70 {
71 #ifdef NEW_SCHEDULER
72 /* Get a new ready thread */
73 NextThread = KiSelectReadyThread(Thread->Priority, Prcb);
74 if (NextThread)
75 {
76 /* Found one, set it on standby */
77 NextThread->State = Standby;
78 Prcb->NextThread = NextThread;
79 }
80 #else
81 /* Just leave now */
82 KiReleasePrcbLock(Prcb);
83 KeLowerIrql(DISPATCH_LEVEL);
84 KiDispatchThread(Ready);
85 return;
86 #endif
87 }
88 else
89 {
90 /* Otherwise, make sure that this thread doesn't get preempted */
91 Thread->Preempted = FALSE;
92 }
93 }
94 else
95 {
96 /* Otherwise, set maximum quantum */
97 Thread->Quantum = MAX_QUANTUM;
98 }
99 }
100
101 /* Release the thread lock */
102 KiReleaseThreadLock(Thread);
103
104 /* Check if there's no thread scheduled */
105 if (!Prcb->NextThread)
106 {
107 /* Just leave now */
108 KiReleasePrcbLock(Prcb);
109 KeLowerIrql(DISPATCH_LEVEL);
110 return;
111 }
112
113 /* Get the next thread now */
114 NextThread = Prcb->NextThread;
115
116 /* Set current thread's swap busy to true */
117 KiSetThreadSwapBusy(Thread);
118
119 /* Switch threads in PRCB */
120 Prcb->NextThread = NULL;
121 Prcb->CurrentThread = NextThread;
122
123 /* Set thread to running and the switch reason to Quantum End */
124 NextThread->State = Running;
125 Thread->WaitReason = WrQuantumEnd;
126
127 /* Queue it on the ready lists */
128 KxQueueReadyThread(Thread, Prcb);
129
130 /* Set wait IRQL to APC_LEVEL */
131 Thread->WaitIrql = APC_LEVEL;
132
133 /* Swap threads */
134 KiSwapContext(Thread, NextThread);
135
136 /* Lower IRQL back to DISPATCH_LEVEL */
137 KeLowerIrql(DISPATCH_LEVEL);
138 }
139
140 VOID
141 FASTCALL
142 KiRetireDpcList(IN PKPRCB Prcb)
143 {
144 PKDPC_DATA DpcData = Prcb->DpcData;
145 PLIST_ENTRY DpcEntry;
146 PKDPC Dpc;
147 PKDEFERRED_ROUTINE DeferredRoutine;
148 PVOID DeferredContext, SystemArgument1, SystemArgument2;
149
150 /* Main outer loop */
151 do
152 {
153 /* Set us as active */
154 Prcb->DpcRoutineActive = TRUE;
155
156 /* Check if this is a timer expiration request */
157 if (Prcb->TimerRequest)
158 {
159 /* FIXME: Not yet implemented */
160 ASSERT(FALSE);
161 }
162
163 /* Loop while we have entries in the queue */
164 while (DpcData->DpcQueueDepth)
165 {
166 /* Lock the DPC data */
167 KefAcquireSpinLockAtDpcLevel(&DpcData->DpcLock);
168
169 /* Make sure we have an entry */
170 if (!IsListEmpty(&DpcData->DpcListHead))
171 {
172 /* Remove the DPC from the list */
173 DpcEntry = RemoveHeadList(&DpcData->DpcListHead);
174 Dpc = CONTAINING_RECORD(DpcEntry, KDPC, DpcListEntry);
175
176 /* Clear its DPC data and save its parameters */
177 Dpc->DpcData = NULL;
178 DeferredRoutine = Dpc->DeferredRoutine;
179 DeferredContext = Dpc->DeferredContext;
180 SystemArgument1 = Dpc->SystemArgument1;
181 SystemArgument2 = Dpc->SystemArgument2;
182
183 /* Decrease the queue depth */
184 DpcData->DpcQueueDepth--;
185
186 /* Clear DPC Time */
187 Prcb->DebugDpcTime = 0;
188
189 /* Release the lock */
190 KefReleaseSpinLockFromDpcLevel(&DpcData->DpcLock);
191
192 /* Re-enable interrupts */
193 _enable();
194
195 /* Call the DPC */
196 DeferredRoutine(Dpc,
197 DeferredContext,
198 SystemArgument1,
199 SystemArgument2);
200 ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);
201
202 /* Disable interrupts and keep looping */
203 _disable();
204 }
205 else
206 {
207 /* The queue should be flushed now */
208 ASSERT(DpcData->DpcQueueDepth == 0);
209
210 /* Release DPC Lock */
211 KefReleaseSpinLockFromDpcLevel(&DpcData->DpcLock);
212 break;
213 }
214 }
215
216 /* Clear DPC Flags */
217 Prcb->DpcRoutineActive = FALSE;
218 Prcb->DpcInterruptRequested = FALSE;
219
220 /* Check if we have deferred threads */
221 if (Prcb->DeferredReadyListHead.Next)
222 {
223 /* FIXME: 2K3-style scheduling not implemeted */
224 ASSERT(FALSE);
225 }
226 } while (DpcData->DpcQueueDepth);
227 }
228
229 VOID
230 NTAPI
231 KiInitializeDpc(IN PKDPC Dpc,
232 IN PKDEFERRED_ROUTINE DeferredRoutine,
233 IN PVOID DeferredContext,
234 IN KOBJECTS Type)
235 {
236 /* Setup the DPC Object */
237 Dpc->Type = Type;
238 Dpc->Number= 0;
239 Dpc->Importance= MediumImportance;
240 Dpc->DeferredRoutine = DeferredRoutine;
241 Dpc->DeferredContext = DeferredContext;
242 Dpc->DpcData = NULL;
243 }
244
245 /* PUBLIC FUNCTIONS **********************************************************/
246
247 /*
248 * @implemented
249 */
250 VOID
251 NTAPI
252 KeInitializeThreadedDpc(IN PKDPC Dpc,
253 IN PKDEFERRED_ROUTINE DeferredRoutine,
254 IN PVOID DeferredContext)
255 {
256 /* Call the internal routine */
257 KiInitializeDpc(Dpc, DeferredRoutine, DeferredContext, ThreadedDpcObject);
258 }
259
260 /*
261 * @implemented
262 */
263 VOID
264 NTAPI
265 KeInitializeDpc(IN PKDPC Dpc,
266 IN PKDEFERRED_ROUTINE DeferredRoutine,
267 IN PVOID DeferredContext)
268 {
269 /* Call the internal routine */
270 KiInitializeDpc(Dpc, DeferredRoutine, DeferredContext, DpcObject);
271 }
272
273 /*
274 * @implemented
275 */
276 BOOLEAN
277 NTAPI
278 KeInsertQueueDpc(IN PKDPC Dpc,
279 IN PVOID SystemArgument1,
280 IN PVOID SystemArgument2)
281 {
282 KIRQL OldIrql;
283 PKPRCB Prcb, CurrentPrcb = KeGetCurrentPrcb();
284 ULONG Cpu;
285 PKDPC_DATA DpcData;
286 BOOLEAN DpcConfigured = FALSE, DpcInserted = FALSE;
287 ASSERT_DPC(Dpc);
288
289 /* Check IRQL and Raise it to HIGH_LEVEL */
290 KeRaiseIrql(HIGH_LEVEL, &OldIrql);
291
292 /* Check if the DPC has more then the maximum number of CPUs */
293 if (Dpc->Number >= MAXIMUM_PROCESSORS)
294 {
295 /* Then substract the maximum and get that PRCB. */
296 Cpu = Dpc->Number - MAXIMUM_PROCESSORS;
297 Prcb = KiProcessorBlock[Cpu];
298 }
299 else
300 {
301 /* Use the current one */
302 Prcb = CurrentPrcb;
303 Cpu = Prcb->Number;
304 }
305
306 /* Check if this is a threaded DPC and threaded DPCs are enabled */
307 if ((Dpc->Type == ThreadedDpcObject) && (Prcb->ThreadDpcEnable))
308 {
309 /* Then use the threaded data */
310 DpcData = &Prcb->DpcData[DPC_THREADED];
311 }
312 else
313 {
314 /* Otherwise, use the regular data */
315 DpcData = &Prcb->DpcData[DPC_NORMAL];
316 }
317
318 /* Acquire the DPC lock */
319 KiAcquireSpinLock(&DpcData->DpcLock);
320
321 /* Get the DPC Data */
322 if (!InterlockedCompareExchangePointer(&Dpc->DpcData, DpcData, NULL))
323 {
324 /* Now we can play with the DPC safely */
325 Dpc->SystemArgument1 = SystemArgument1;
326 Dpc->SystemArgument2 = SystemArgument2;
327 DpcData->DpcQueueDepth++;
328 DpcData->DpcCount++;
329 DpcConfigured = TRUE;
330
331 /* Check if this is a high importance DPC */
332 if (Dpc->Importance == HighImportance)
333 {
334 /* Pre-empty other DPCs */
335 InsertHeadList(&DpcData->DpcListHead, &Dpc->DpcListEntry);
336 }
337 else
338 {
339 /* Add it at the end */
340 InsertTailList(&DpcData->DpcListHead, &Dpc->DpcListEntry);
341 }
342
343 /* Check if this is the DPC on the threaded list */
344 if (&Prcb->DpcData[DPC_THREADED].DpcListHead == &DpcData->DpcListHead)
345 {
346 /* Make sure a threaded DPC isn't already active */
347 if (!(Prcb->DpcThreadActive) && !(Prcb->DpcThreadRequested))
348 {
349 /* FIXME: Setup Threaded DPC */
350 ASSERT(FALSE);
351 }
352 }
353 else
354 {
355 /* Make sure a DPC isn't executing already */
356 if (!(Prcb->DpcRoutineActive) && !(Prcb->DpcInterruptRequested))
357 {
358 /* Check if this is the same CPU */
359 if (Prcb != CurrentPrcb)
360 {
361 /*
362 * Check if the DPC is of high importance or above the
363 * maximum depth. If it is, then make sure that the CPU
364 * isn't idle, or that it's sleeping.
365 */
366 if (((Dpc->Importance == HighImportance) ||
367 (DpcData->DpcQueueDepth >=
368 Prcb->MaximumDpcQueueDepth)) &&
369 (!(AFFINITY_MASK(Cpu) & KiIdleSummary) ||
370 (Prcb->Sleeping)))
371 {
372 /* Set interrupt requested */
373 Prcb->DpcInterruptRequested = TRUE;
374
375 /* Set DPC inserted */
376 DpcInserted = TRUE;
377 }
378 }
379 else
380 {
381 /* Check if the DPC is of anything but low importance */
382 if ((Dpc->Importance != LowImportance) ||
383 (DpcData->DpcQueueDepth >=
384 Prcb->MaximumDpcQueueDepth) ||
385 (Prcb->DpcRequestRate < Prcb->MinimumDpcRate))
386 {
387 /* Set interrupt requested */
388 Prcb->DpcInterruptRequested = TRUE;
389
390 /* Set DPC inserted */
391 DpcInserted = TRUE;
392 }
393 }
394 }
395 }
396 }
397
398 /* Release the lock */
399 KiReleaseSpinLock(&DpcData->DpcLock);
400
401 /* Check if the DPC was inserted */
402 if (DpcInserted)
403 {
404 /* Check if this was SMP */
405 if (Prcb != CurrentPrcb)
406 {
407 /* It was, request and IPI */
408 KiIpiSendRequest(AFFINITY_MASK(Cpu), IPI_DPC);
409 }
410 else
411 {
412 /* It wasn't, request an interrupt from HAL */
413 HalRequestSoftwareInterrupt(DISPATCH_LEVEL);
414 }
415 }
416
417 /* Lower IRQL */
418 KeLowerIrql(OldIrql);
419 return DpcConfigured;
420 }
421
422 /*
423 * @implemented
424 */
425 BOOLEAN
426 NTAPI
427 KeRemoveQueueDpc(IN PKDPC Dpc)
428 {
429 PKDPC_DATA DpcData;
430 UCHAR DpcType;
431 ASSERT_DPC(Dpc);
432
433 /* Disable interrupts */
434 _disable();
435
436 /* Get DPC data and type */
437 DpcType = Dpc->Type;
438 DpcData = Dpc->DpcData;
439 if (DpcData)
440 {
441 /* Acquire the DPC lock */
442 KiAcquireSpinLock(&DpcData->DpcLock);
443
444 /* Make sure that the data didn't change */
445 if (DpcData == Dpc->DpcData)
446 {
447 /* Remove the DPC */
448 DpcData->DpcQueueDepth--;
449 RemoveEntryList(&Dpc->DpcListEntry);
450 Dpc->DpcData = NULL;
451 }
452
453 /* Release the lock */
454 KiReleaseSpinLock(&DpcData->DpcLock);
455 }
456
457 /* Re-enable interrupts */
458 _enable();
459
460 /* Return if the DPC was in the queue or not */
461 return DpcData ? TRUE : FALSE;
462 }
463
464 /*
465 * @implemented
466 */
467 VOID
468 NTAPI
469 KeFlushQueuedDpcs(VOID)
470 {
471 PAGED_CODE();
472
473 /* Check if this is an UP machine */
474 if (KeActiveProcessors == 1)
475 {
476 /* Check if there are DPCs on either queues */
477 if ((KeGetCurrentPrcb()->DpcData[DPC_NORMAL].DpcQueueDepth) ||
478 (KeGetCurrentPrcb()->DpcData[DPC_THREADED].DpcQueueDepth))
479 {
480 /* Request an interrupt */
481 HalRequestSoftwareInterrupt(DISPATCH_LEVEL);
482 }
483 }
484 else
485 {
486 /* FIXME: SMP support required */
487 ASSERT(FALSE);
488 }
489 }
490
491 /*
492 * @implemented
493 */
494 BOOLEAN
495 NTAPI
496 KeIsExecutingDpc(VOID)
497 {
498 /* Return if the Dpc Routine is active */
499 return KeGetCurrentPrcb()->DpcRoutineActive;
500 }
501
502 /*
503 * @implemented
504 */
505 VOID
506 NTAPI
507 KeSetImportanceDpc (IN PKDPC Dpc,
508 IN KDPC_IMPORTANCE Importance)
509 {
510 /* Set the DPC Importance */
511 ASSERT_DPC(Dpc);
512 Dpc->Importance = Importance;
513 }
514
515 /*
516 * @implemented
517 */
518 VOID
519 NTAPI
520 KeSetTargetProcessorDpc(IN PKDPC Dpc,
521 IN CCHAR Number)
522 {
523 /* Set a target CPU */
524 ASSERT_DPC(Dpc);
525 Dpc->Number = Number + MAXIMUM_PROCESSORS;
526 }
527
528 /* EOF */