- Fixed the processor number at the call to KiIpiSendRequest.
[reactos.git] / reactos / ntoskrnl / ke / dpc.c
1 /*
2 * ReactOS kernel
3 * Copyright (C) 2000, 1999, 1998 David Welch <welch@cwcom.net>,
4 * Philip Susi <phreak@iag.net>,
5 * Eric Kohl <ekohl@abo.rhein-zeitung.de>
6 * Alex Ionescu <alex@relsoft.net>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 */
22 /* $Id: dpc.c,v 1.49 2004/11/27 19:27:31 hbirr Exp $
23 *
24 * COPYRIGHT: See COPYING in the top level directory
25 * PROJECT: ReactOS kernel
26 * FILE: ntoskrnl/ke/dpc.c
27 * PURPOSE: Handle DPCs (Delayed Procedure Calls)
28 * PROGRAMMER: David Welch (welch@mcmail.com)
29 * UPDATE HISTORY:
30 * 28/05/98: Created
31 * 12/3/99: Phillip Susi: Fixed IRQL problem
32 * 12/11/04: Alex Ionescu - Major rewrite.
33 */
34
35 /*
36 * NOTE: See also the higher level support routines in ntoskrnl/io/dpc.c
37 */
38
39 /* INCLUDES ***************************************************************/
40
41 #include <ntoskrnl.h>
42 #define NDEBUG
43 #include <internal/debug.h>
44
45 /* TYPES *******************************************************************/
46
47 #define MAX_QUANTUM 0x7F
48 /* GLOBALS ******************************************************************/
49
50 /* FUNCTIONS ****************************************************************/
51
52 VOID INIT_FUNCTION
53 KeInitDpc(PKPCR Pcr)
54 /*
55 * FUNCTION: Initialize DPC handling
56 */
57 {
58 InitializeListHead(&Pcr->PrcbData.DpcData[0].DpcListHead);
59 KeInitializeEvent(Pcr->PrcbData.DpcEvent, 0, 0);
60 KeInitializeSpinLock(&Pcr->PrcbData.DpcData[0].DpcLock);
61 Pcr->PrcbData.MaximumDpcQueueDepth = 4;
62 Pcr->PrcbData.MinimumDpcRate = 3;
63 Pcr->PrcbData.DpcData[0].DpcQueueDepth = 0;
64 }
65
66 /*
67 * @implemented
68 */
69 VOID
70 STDCALL
71 KeInitializeThreadedDpc(PKDPC Dpc,
72 PKDEFERRED_ROUTINE DeferredRoutine,
73 PVOID DeferredContext)
74 /*
75 * FUNCTION:
76 * Initalizes a Threaded 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 {
84 DPRINT("Threaded DPC Initializing: %x with Routine: %x\n", Dpc, DeferredRoutine);
85 //Dpc->Type = KThreadedDpc;
86 Dpc->Number= 0;
87 Dpc->Importance= MediumImportance;
88 Dpc->DeferredRoutine = DeferredRoutine;
89 Dpc->DeferredContext = DeferredContext;
90 Dpc->DpcData = NULL;
91 }
92
93 /*
94 * @implemented
95 */
96 VOID
97 STDCALL
98 KeInitializeDpc (PKDPC Dpc,
99 PKDEFERRED_ROUTINE DeferredRoutine,
100 PVOID DeferredContext)
101 /*
102 * FUNCTION:
103 * Initalizes a DPC and registers the DeferredRoutine for it.
104 * ARGUMENTS:
105 * Dpc = Pointer to a caller supplied DPC to be initialized. The caller must allocate this memory.
106 * DeferredRoutine = Pointer to associated DPC callback routine.
107 * DeferredContext = Parameter to be passed to the callback routine.
108 * NOTE: Callers can be running at any IRQL.
109 */
110 {
111 DPRINT("DPC Initializing: %x with Routine: %x\n", Dpc, DeferredRoutine);
112 Dpc->Type = KDpc;
113 Dpc->Number= 0;
114 Dpc->Importance= MediumImportance;
115 Dpc->DeferredRoutine = DeferredRoutine;
116 Dpc->DeferredContext = DeferredContext;
117 Dpc->DpcData = NULL;
118 }
119
120 /*
121 * @implemented
122 */
123 BOOLEAN STDCALL
124 KeInsertQueueDpc (PKDPC Dpc,
125 PVOID SystemArgument1,
126 PVOID SystemArgument2)
127 /*
128 * FUNCTION:
129 * Queues a DPC for execution when the IRQL of a processor
130 * drops below DISPATCH_LEVEL
131 * ARGUMENTS:
132 * Dpc = Pointed to a DPC Object Initalized by KeInitializeDpc.
133 * SystemArgument1 = Driver Determined context data
134 * SystemArgument2 = Driver Determined context data
135 * RETURNS:
136 * TRUE if the DPC object wasn't already in the queue
137 * FALSE otherwise
138 * NOTES:
139 * If there is currently a DPC active on the target processor, or a DPC
140 * interrupt has already been requested on the target processor when a
141 * DPC is queued, then no further action is necessary. The DPC will be
142 * executed on the target processor when its queue entry is processed.
143 *
144 * If there is not a DPC active on the target processor and a DPC interrupt
145 * has not been requested on the target processor, then the exact treatment
146 * of the DPC is dependent on whether the host system is a UP system or an
147 * MP system.
148 *
149 * UP system.
150 * ----------
151 * If the DPC is of medium or high importance, the current DPC queue depth
152 * is greater than the maximum target depth, or current DPC request rate is
153 * less the minimum target rate, then a DPC interrupt is requested on the
154 * host processor and the DPC will be processed when the interrupt occurs.
155 * Otherwise, no DPC interupt is requested and the DPC execution will be
156 * delayed until the DPC queue depth is greater that the target depth or the
157 * minimum DPC rate is less than the target rate.
158 *
159 * MP system.
160 * ----------
161 * If the DPC is being queued to another processor and the depth of the DPC
162 * queue on the target processor is greater than the maximum target depth or
163 * the DPC is of high importance, then a DPC interrupt is requested on the
164 * target processor and the DPC will be processed when the interrupt occurs.
165 * Otherwise, the DPC execution will be delayed on the target processor until
166 * the DPC queue depth on the target processor is greater that the maximum
167 * target depth or the minimum DPC rate on the target processor is less than
168 * the target mimimum rate.
169 *
170 * If the DPC is being queued to the current processor and the DPC is not of
171 * low importance, the current DPC queue depth is greater than the maximum
172 * target depth, or the minimum DPC rate is less than the minimum target rate,
173 * then a DPC interrupt is request on the current processor and the DPV will
174 * be processed whne the interrupt occurs. Otherwise, no DPC interupt is
175 * requested and the DPC execution will be delayed until the DPC queue depth
176 * is greater that the target depth or the minimum DPC rate is less than the
177 * target rate.
178 */
179 {
180 KIRQL OldIrql;
181 PKPCR Pcr;
182
183 DPRINT("KeInsertQueueDpc(DPC %x, SystemArgument1 %x, SystemArgument2 %x)\n",
184 Dpc, SystemArgument1, SystemArgument2);
185
186 /* Check IRQL and Raise it to HIGH_LEVEL */
187 ASSERT(KeGetCurrentIrql()>=DISPATCH_LEVEL);
188 KeRaiseIrql(HIGH_LEVEL, &OldIrql);
189
190 /* Check if this is a Thread DPC, which we don't support (yet) */
191 //if (Dpc->Type == KThreadedDpc) {
192 // return FALSE;
193 // KeLowerIrql(OldIrql);
194 //}
195
196 #ifdef MP
197 /* Get the right PCR for this CPU */
198 if (Dpc->Number >= MAXIMUM_PROCESSORS) {
199 ASSERT (Dpc->Number - MAXIMUM_PROCESSORS < KeNumberProcessors);
200 Pcr = (PKPCR)(KPCR_BASE + (Dpc->Number - MAXIMUM_PROCESSORS) * PAGE_SIZE);
201 } else {
202 ASSERT (Dpc->Number < KeNumberProcessors);
203 Pcr = KeGetCurrentKPCR();
204 Dpc->Number = KeGetCurrentProcessorNumber();
205 }
206 KiAcquireSpinLock(&Pcr->PrcbData.DpcData[0].DpcLock);
207 #else
208 Pcr = (PKPCR)KPCR_BASE;
209 #endif
210
211 /* Get the DPC Data */
212 if (InterlockedCompareExchange((LONG*)&Dpc->DpcData, (LONG)&Pcr->PrcbData.DpcData[0].DpcLock, 0)) {
213 DPRINT("DPC Already Inserted");
214 #ifdef MP
215 KiReleaseSpinLock(&Pcr->PrcbData.DpcData[0].DpcLock);
216 #endif
217 KeLowerIrql(OldIrql);
218 return(FALSE);
219 }
220
221 /* Make sure the lists are free if the Queue is 0 */
222 if (Pcr->PrcbData.DpcData[0].DpcQueueDepth == 0) {
223 ASSERT(IsListEmpty(&Pcr->PrcbData.DpcData[0].DpcListHead));
224 } else {
225 ASSERT(!IsListEmpty(&Pcr->PrcbData.DpcData[0].DpcListHead));
226 }
227
228 /* Now we can play with the DPC safely */
229 Dpc->SystemArgument1=SystemArgument1;
230 Dpc->SystemArgument2=SystemArgument2;
231 Pcr->PrcbData.DpcData[0].DpcQueueDepth++;
232 Pcr->PrcbData.DpcData[0].DpcCount++;
233
234 /* Insert the DPC into the list. HighImportance DPCs go at the beginning */
235 if (Dpc->Importance == HighImportance) {
236 InsertHeadList(&Pcr->PrcbData.DpcData[0].DpcListHead, &Dpc->DpcListEntry);
237 } else {
238 InsertTailList(&Pcr->PrcbData.DpcData[0].DpcListHead, &Dpc->DpcListEntry);
239 }
240 DPRINT("New DPC Added. Dpc->DpcListEntry.Flink %x\n", Dpc->DpcListEntry.Flink);
241
242 /* Make sure a DPC isn't executing already and respect rules outlined above. */
243 if ((!Pcr->PrcbData.DpcRoutineActive) && (!Pcr->PrcbData.DpcInterruptRequested)) {
244
245 #ifdef MP
246 /* Check if this is the same CPU */
247 if (Pcr != KeGetCurrentKPCR()) {
248 /* Send IPI if High Importance */
249 if ((Dpc->Importance == HighImportance) ||
250 (Pcr->PrcbData.DpcData[0].DpcQueueDepth >= Pcr->PrcbData.MaximumDpcQueueDepth)) {
251 if (Dpc->Number >= MAXIMUM_PROCESSORS) {
252 KiIpiSendRequest(1 << (Dpc->Number - MAXIMUM_PROCESSORS), IPI_REQUEST_DPC);
253 } else {
254 KiIpiSendRequest(1 << Dpc->Number, IPI_REQUEST_DPC);
255 }
256
257 }
258 } else {
259 /* Request an Interrupt only if the DPC isn't low priority */
260 if ((Dpc->Importance != LowImportance) ||
261 (Pcr->PrcbData.DpcData[0].DpcQueueDepth >= Pcr->PrcbData.MaximumDpcQueueDepth) ||
262 (Pcr->PrcbData.DpcRequestRate < Pcr->PrcbData.MinimumDpcRate)) {
263
264 /* Request Interrupt */
265 HalRequestSoftwareInterrupt(DISPATCH_LEVEL);
266 Pcr->PrcbData.DpcInterruptRequested = TRUE;
267 }
268 }
269 #else
270 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);
271 /* Request an Interrupt only if the DPC isn't low priority */
272 if ((Dpc->Importance != LowImportance) ||
273 (Pcr->PrcbData.DpcData[0].DpcQueueDepth >= Pcr->PrcbData.MaximumDpcQueueDepth) ||
274 (Pcr->PrcbData.DpcRequestRate < Pcr->PrcbData.MinimumDpcRate)) {
275
276 /* Request Interrupt */
277 DPRINT("Requesting Interrupt\n");
278 HalRequestSoftwareInterrupt(DISPATCH_LEVEL);
279 Pcr->PrcbData.DpcInterruptRequested = TRUE;
280 }
281 #endif
282 }
283 #ifdef MP
284 KiReleaseSpinLock(&Pcr->PrcbData.DpcData[0].DpcLock);
285 #endif
286 /* Lower IRQL */
287 KeLowerIrql(OldIrql);
288 return(TRUE);
289 }
290
291 /*
292 * @implemented
293 */
294 BOOLEAN STDCALL
295 KeRemoveQueueDpc (PKDPC Dpc)
296 /*
297 * FUNCTION:
298 * Removes DPC object from the system dpc queue
299 * ARGUMENTS:
300 * Dpc = Pointer to DPC to remove from the queue.
301 * RETURNS:
302 * TRUE if the DPC was in the queue
303 * FALSE otherwise
304 */
305 {
306 BOOLEAN WasInQueue;
307 KIRQL OldIrql;
308
309 /* Raise IRQL */
310 DPRINT("Removing DPC: %x\n", Dpc);
311 KeRaiseIrql(HIGH_LEVEL, &OldIrql);
312 #ifdef MP
313 KiAcquireSpinLock(&((PKDPC_DATA)Dpc->DpcData)->DpcLock);
314 #endif
315
316 /* First make sure the DPC lock isn't being held */
317 WasInQueue = Dpc->DpcData ? TRUE : FALSE;
318 if (Dpc->DpcData) {
319
320 /* Remove the DPC */
321 ((PKDPC_DATA)Dpc->DpcData)->DpcQueueDepth--;
322 RemoveEntryList(&Dpc->DpcListEntry);
323
324 }
325 #ifdef MP
326 KiReleaseSpinLock(&((PKDPC_DATA)Dpc->DpcData)->DpcLock);
327 #endif
328
329 /* Return if the DPC was in the queue or not */
330 KeLowerIrql(OldIrql);
331 return WasInQueue;
332 }
333
334 /*
335 * @implemented
336 */
337 VOID
338 STDCALL
339 KeFlushQueuedDpcs(VOID)
340 /*
341 * FUNCTION:
342 * Called to Deliver DPCs if any are pending.
343 * NOTES:
344 * Called when deleting a Driver.
345 */
346 {
347 if (KeGetCurrentKPCR()->PrcbData.DpcData[0].DpcQueueDepth) HalRequestSoftwareInterrupt(DISPATCH_LEVEL);
348 }
349
350 /*
351 * @implemented
352 */
353 BOOLEAN
354 STDCALL
355 KeIsExecutingDpc(
356 VOID
357 )
358 {
359 return KeGetCurrentKPCR()->PrcbData.DpcRoutineActive;
360 }
361
362 /*
363 * FUNCTION: Specifies the DPCs importance
364 * ARGUMENTS:
365 * Dpc = Initalizes DPC
366 * Importance = DPC importance
367 * RETURNS: None
368 *
369 * @implemented
370 */
371 VOID
372 STDCALL
373 KeSetImportanceDpc (IN PKDPC Dpc,
374 IN KDPC_IMPORTANCE Importance)
375 {
376 Dpc->Importance = Importance;
377 }
378
379 /*
380 * FUNCTION: Specifies on which processor the DPC will run
381 * ARGUMENTS:
382 * Dpc = Initalizes DPC
383 * Number = Processor number
384 * RETURNS: None
385 *
386 * @implemented
387 */
388 VOID STDCALL
389 KeSetTargetProcessorDpc (IN PKDPC Dpc,
390 IN CCHAR Number)
391 {
392 if (Number >= MAXIMUM_PROCESSORS)
393 {
394 Dpc->Number = 0;
395 }
396 else
397 {
398 ASSERT(Number < KeNumberProcessors);
399 Dpc->Number = Number + MAXIMUM_PROCESSORS;
400 }
401 }
402
403 VOID
404 STDCALL
405 KiQuantumEnd(VOID)
406 /*
407 * FUNCTION:
408 * Called when a quantum end occurs to check if priority should be changed
409 * and wether a new thread should be dispatched.
410 * NOTES:
411 * Called when deleting a Driver.
412 */
413 {
414 KPRCB 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 NewPriority = OldPriority - CurrentThread->PriorityDecrement - 1;
443 if (NewPriority < CurrentThread->BasePriority) {
444 NewPriority = CurrentThread->BasePriority;
445 }
446 CurrentThread->PriorityDecrement = 0;
447 if (OldPriority != NewPriority) {
448 /* Set new Priority */
449 CurrentThread->Priority = NewPriority;
450 } else {
451 /* Queue new thread if none is already */
452 if (Prcb.NextThread == NULL) {
453 /* FIXME: Schedule a New Thread, when ROS will have NT Scheduler */
454 } else {
455 /* Make the current thread non-premeptive if a new thread is queued */
456 CurrentThread->Preempted = FALSE;
457 }
458 }
459 } else {
460 /* Set the Quantum back to Maximum */
461 //if (CurrentThread->DisableQuantum) {
462 // CurrentThread->Quantum = MAX_QUANTUM;
463 //}
464 }
465 }
466 /* Dispatch the Thread */
467 KeLowerIrql(DISPATCH_LEVEL);
468 PsDispatchThread(THREAD_STATE_READY);
469 }
470
471 /*
472 * @implemented
473 */
474 VOID
475 STDCALL
476 KiDispatchInterrupt(VOID)
477 /*
478 * FUNCTION:
479 * Called whenever a system interrupt is generated at DISPATCH_LEVEL.
480 * It delivers queued DPCs and dispatches a new thread if need be.
481 */
482 {
483 PLIST_ENTRY DpcEntry;
484 PKDPC Dpc;
485 KIRQL OldIrql;
486 PKPCR Pcr;
487
488 DPRINT("Dispatching Interrupts\n");
489 ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);
490
491 /* Set DPC Deliver to Active */
492 Pcr = KeGetCurrentKPCR();
493
494 if (Pcr->PrcbData.DpcData[0].DpcQueueDepth > 0) {
495 /* Raise IRQL */
496 KeRaiseIrql(HIGH_LEVEL, &OldIrql);
497 #ifdef MP
498 KiAcquireSpinLock(&Pcr->PrcbData.DpcData[0].DpcLock);
499 #endif
500 Pcr->PrcbData.DpcRoutineActive = TRUE;
501
502 DPRINT("&Pcr->PrcbData.DpcData[0].DpcListHead: %x\n", &Pcr->PrcbData.DpcData[0].DpcListHead);
503 /* Loop while we have entries */
504 while (!IsListEmpty(&Pcr->PrcbData.DpcData[0].DpcListHead)) {
505 ASSERT(Pcr->PrcbData.DpcData[0].DpcQueueDepth > 0);
506 DPRINT("Queue Depth: %x\n", Pcr->PrcbData.DpcData[0].DpcQueueDepth);
507
508 /* Get the DPC call it */
509 DpcEntry = RemoveHeadList(&Pcr->PrcbData.DpcData[0].DpcListHead);
510 Dpc = CONTAINING_RECORD(DpcEntry, KDPC, DpcListEntry);
511 DPRINT("Dpc->DpcListEntry.Flink %x\n", Dpc->DpcListEntry.Flink);
512 Dpc->DpcData = NULL;
513 Pcr->PrcbData.DpcData[0].DpcQueueDepth--;
514 #ifdef MP
515 KiReleaseSpinLock(&Pcr->PrcbData.DpcData[0].DpcLock);
516 #endif
517 /* Disable/Enabled Interrupts and Call the DPC */
518 KeLowerIrql(OldIrql);
519 DPRINT("Calling DPC: %x\n", Dpc);
520 Dpc->DeferredRoutine(Dpc,
521 Dpc->DeferredContext,
522 Dpc->SystemArgument1,
523 Dpc->SystemArgument2);
524 KeRaiseIrql(HIGH_LEVEL, &OldIrql);
525
526 #ifdef MP
527 KiAcquireSpinLock(&Pcr->PrcbData.DpcData[0].DpcLock);
528 /*
529 * If the dpc routine drops the irql below DISPATCH_LEVEL,
530 * a thread switch can occur and after the next thread switch
531 * the execution may start on an other processor.
532 */
533 if (Pcr != KeGetCurrentKPCR()) {
534 Pcr->PrcbData.DpcRoutineActive = FALSE;
535 KiReleaseSpinLock(&Pcr->PrcbData.DpcData[0].DpcLock);
536 Pcr = KeGetCurrentKPCR();
537 KiAcquireSpinLock(&Pcr->PrcbData.DpcData[0].DpcLock);
538 Pcr->PrcbData.DpcRoutineActive = TRUE;
539 }
540 #endif
541 }
542 /* Clear DPC Flags */
543 Pcr->PrcbData.DpcRoutineActive = FALSE;
544 Pcr->PrcbData.DpcInterruptRequested = FALSE;
545 #ifdef MP
546 KiReleaseSpinLock(&Pcr->PrcbData.DpcData[0].DpcLock);
547 #endif
548
549 /* DPC Dispatching Ended, re-enable interrupts */
550 KeLowerIrql(OldIrql);
551 }
552
553 DPRINT("Checking for Quantum End\n");
554 /* If we have Quantum End, call the function */
555 if (Pcr->PrcbData.QuantumEnd) {
556 Pcr->PrcbData.QuantumEnd = FALSE;
557 KiQuantumEnd();
558 }
559 }
560
561 /* EOF */