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