1 /*++
2 
3 Copyright (C) Microsoft Corporation, 1991 - 1999
4 
5 Module Name:
6 
7     clntirp.c
8 
9 Abstract:
10 
11     Client IRP queuing routines for CLASSPNP
12 
13 Environment:
14 
15     kernel mode only
16 
17 Notes:
18 
19 
20 Revision History:
21 
22 --*/
23 
24 #include "classp.h"
25 #include "debug.h"
26 
27 
28 #ifdef DEBUG_USE_WPP
29 #include "clntirp.tmh"
30 #endif
31 
32 VOID
33 ClasspStartIdleTimer(
34     IN PCLASS_PRIVATE_FDO_DATA FdoData
35     );
36 
37 VOID
38 ClasspStopIdleTimer(
39     PCLASS_PRIVATE_FDO_DATA FdoData
40     );
41 
42 KDEFERRED_ROUTINE ClasspIdleTimerDpc;
43 
44 VOID
45 ClasspServiceIdleRequest(
46     PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
47     BOOLEAN PostToDpc
48     );
49 
50 
51 PIRP
52 ClasspDequeueIdleRequest(
53     PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
54     );
55 
56 
57 /*++
58 
59 EnqueueDeferredClientIrp
60 
61 Routine Description:
62 
63     Insert the deferred irp into the list.
64 
65     Note: we currently do not support Cancel for storage irps.
66 
67 Arguments:
68 
69     Fdo - Pointer to the device object
70     Irp     - Pointer to the I/O request packet
71 
72 Return Value:
73 
74     None
75 
76 --*/
77 VOID
78 EnqueueDeferredClientIrp(
79     PDEVICE_OBJECT Fdo,
80     PIRP Irp
81     )
82 {
83     PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
84     PCLASS_PRIVATE_FDO_DATA fdoData = fdoExtension->PrivateFdoData;
85     KIRQL oldIrql;
86 
87 
88     KeAcquireSpinLock(&fdoData->SpinLock, &oldIrql);
89     InsertTailList(&fdoData->DeferredClientIrpList, &Irp->Tail.Overlay.ListEntry);
90 
91 
92     KeReleaseSpinLock(&fdoData->SpinLock, oldIrql);
93 }
94 
95 /*++
96 
97 DequeueDeferredClientIrp
98 
99 Routine Description:
100 
101     Remove the deferred irp from the list.
102 
103 Arguments:
104 
105     Fdo - Pointer to the device object
106 
107 Return Value:
108 
109     Pointer to removed IRP
110 
111 --*/
112 PIRP
113 DequeueDeferredClientIrp(
114     PDEVICE_OBJECT Fdo
115     )
116 {
117     PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
118     PCLASS_PRIVATE_FDO_DATA fdoData = fdoExtension->PrivateFdoData;
119     PIRP irp;
120 
121     //
122     // The DeferredClientIrpList is almost always empty.
123     // We don't want to grab the spinlock every time we check it (which is on every xfer completion)
124     // so check once first before we grab the spinlock.
125     //
126     if (IsListEmpty(&fdoData->DeferredClientIrpList)){
127         irp = NULL;
128     }
129     else {
130         PLIST_ENTRY listEntry;
131         KIRQL oldIrql;
132 
133         KeAcquireSpinLock(&fdoData->SpinLock, &oldIrql);
134         if (IsListEmpty(&fdoData->DeferredClientIrpList)){
135             listEntry = NULL;
136         }
137         else {
138             listEntry = RemoveHeadList(&fdoData->DeferredClientIrpList);
139         }
140         KeReleaseSpinLock(&fdoData->SpinLock, oldIrql);
141 
142         if (listEntry == NULL) {
143             irp = NULL;
144         }
145         else {
146             irp = CONTAINING_RECORD(listEntry, IRP, Tail.Overlay.ListEntry);
147             NT_ASSERT(irp->Type == IO_TYPE_IRP);
148 
149 
150             InitializeListHead(&irp->Tail.Overlay.ListEntry);
151         }
152     }
153 
154     return irp;
155 }
156 
157 /*++
158 
159 ClasspInitializeIdleTimer
160 
161 Routine Description:
162 
163     Initialize the idle timer for the given device.
164 
165 Arguments:
166 
167     FdoExtension    - Pointer to the device extension
168 
169 Return Value:
170 
171     None
172 
173 --*/
174 VOID
175 ClasspInitializeIdleTimer(
176     PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
177     )
178 {
179     PCLASS_PRIVATE_FDO_DATA fdoData = FdoExtension->PrivateFdoData;
180     ULONG idleInterval = CLASS_IDLE_INTERVAL;
181     ULONG idlePrioritySupported = TRUE;
182     ULONG activeIdleIoMax = 1;
183 
184     ClassGetDeviceParameter(FdoExtension,
185                             CLASSP_REG_SUBKEY_NAME,
186                             CLASSP_REG_IDLE_PRIORITY_SUPPORTED,
187                             &idlePrioritySupported);
188 
189 
190     if (idlePrioritySupported == FALSE) {
191         //
192         // User has set the registry to disable idle priority for this disk.
193         // No need to initialize any further.
194         // Always ensure that none of the other fields used for idle priority
195         // io are ever used without checking if it is supported.
196         //
197         TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_TIMER, "ClasspInitializeIdleTimer: Idle priority not supported for disk %p\n", FdoExtension));
198         fdoData->IdlePrioritySupported = FALSE;
199         fdoData->IdleIoCount = 0;
200         fdoData->ActiveIoCount = 0;
201         return;
202     }
203 
204     ClassGetDeviceParameter(FdoExtension,
205                             CLASSP_REG_SUBKEY_NAME,
206                             CLASSP_REG_IDLE_INTERVAL_NAME,
207                             &idleInterval);
208 
209     if ((idleInterval < CLASS_IDLE_INTERVAL_MIN) || (idleInterval > USHORT_MAX)) {
210         //
211         // If the interval is too low or too high, reset it to the default value.
212         //
213         idleInterval = CLASS_IDLE_INTERVAL;
214     }
215 
216     fdoData->IdlePrioritySupported = TRUE;
217     KeInitializeSpinLock(&fdoData->IdleListLock);
218     KeInitializeTimer(&fdoData->IdleTimer);
219     KeInitializeDpc(&fdoData->IdleDpc, ClasspIdleTimerDpc, FdoExtension);
220     InitializeListHead(&fdoData->IdleIrpList);
221     fdoData->IdleTimerStarted = FALSE;
222     fdoData->StarvationDuration = CLASS_STARVATION_INTERVAL;
223     fdoData->IdleInterval = (USHORT)(idleInterval);
224     fdoData->IdleIoCount = 0;
225     fdoData->ActiveIoCount = 0;
226     fdoData->ActiveIdleIoCount = 0;
227 
228     ClassGetDeviceParameter(FdoExtension,
229                             CLASSP_REG_SUBKEY_NAME,
230                             CLASSP_REG_IDLE_ACTIVE_MAX,
231                             &activeIdleIoMax);
232 
233     activeIdleIoMax = max(activeIdleIoMax, 1);
234     activeIdleIoMax = min(activeIdleIoMax, USHORT_MAX);
235 
236     fdoData->IdleActiveIoMax = (USHORT)activeIdleIoMax;
237 
238     return;
239 }
240 
241 /*++
242 
243 ClasspStartIdleTimer
244 
245 Routine Description:
246 
247     Start the idle timer if not already running. Reset the
248     timer counters before starting the timer. Use the IdleInterval
249     in the private fdo data to setup the timer.
250 
251 Arguments:
252 
253     FdoData - Pointer to the private fdo data
254 
255 Return Value:
256 
257     None
258 
259 --*/
260 VOID
261 ClasspStartIdleTimer(
262     IN PCLASS_PRIVATE_FDO_DATA FdoData
263     )
264 {
265     LARGE_INTEGER dueTime;
266     LONG mstotimer;
267     LONG timerStarted;
268 
269     TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_TIMER, "ClasspStartIdleTimer: Start idle timer\n"));
270 
271     timerStarted = InterlockedCompareExchange(&FdoData->IdleTimerStarted, 1, 0);
272 
273     if (!timerStarted) {
274 
275         //
276         // Reset the anti-starvation start time.
277         //
278         FdoData->AntiStarvationStartTime = ClasspGetCurrentTime();
279 
280         //
281         // convert milliseconds to a relative 100ns
282         //
283         mstotimer = (-10) * 1000;
284 
285         //
286         // multiply the period
287         //
288         dueTime.QuadPart = Int32x32To64(FdoData->IdleInterval, mstotimer);
289 
290         KeSetTimerEx(&FdoData->IdleTimer,
291                      dueTime,
292                      FdoData->IdleInterval,
293                      &FdoData->IdleDpc);
294     }
295     return;
296 }
297 
298 /*++
299 
300 ClasspStopIdleTimer
301 
302 Routine Description:
303 
304     Stop the idle timer if running. Also reset the timer counters.
305 
306 Arguments:
307 
308     FdoData - Pointer to the private fdo data
309 
310 Return Value:
311 
312     None
313 
314 --*/
315 VOID
316 ClasspStopIdleTimer(
317     PCLASS_PRIVATE_FDO_DATA FdoData
318     )
319 {
320     LONG timerStarted;
321 
322     TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_TIMER, "ClasspStopIdleTimer: Stop idle timer\n"));
323 
324     timerStarted = InterlockedCompareExchange(&FdoData->IdleTimerStarted, 0, 1);
325 
326     if (timerStarted) {
327         (VOID)KeCancelTimer(&FdoData->IdleTimer);
328     }
329     return;
330 }
331 
332 /*++
333 
334 ClasspGetIdleTime
335 
336 Routine Description:
337 
338     This routine returns how long it has been since the last non-idle request
339     completed by checking the actual time.
340 
341 Arguments:
342 
343     FdoData - Pointer to the private fdo data
344 
345 Return Value:
346 
347     The idle interval in ms.
348 
349 --*/
350 ULONGLONG
351 ClasspGetIdleTime (
352     IN PCLASS_PRIVATE_FDO_DATA FdoData,
353     IN LARGE_INTEGER CurrentTime
354     )
355 {
356     ULONGLONG idleTime;
357     NTSTATUS status;
358 
359     //
360     // Get the time difference between current time and last I/O
361     // complete time.
362     //
363 
364     status = RtlULongLongSub((ULONGLONG)CurrentTime.QuadPart,
365                              (ULONGLONG)FdoData->LastNonIdleIoTime.QuadPart,
366                              &idleTime);
367 
368     if (NT_SUCCESS(status)) {
369         //
370         // Convert the time to milliseconds.
371         //
372         idleTime = ClasspTimeDiffToMs(idleTime);
373     } else {
374         //
375         // Failed to get time difference, assume enough time passed.
376         //
377         idleTime = FdoData->IdleInterval;
378     }
379 
380     return idleTime;
381 }
382 
383 /*++
384 
385 ClasspIdleDurationSufficient
386 
387 Routine Description:
388 
389     This routine computes whether enough idle duration has elapsed since the
390     completion of the last non-idle request.
391 
392 Arguments:
393 
394     FdoData - Pointer to the private fdo data
395 
396     CurrentTimeIn - If CurrentTimeIn is non-NULL
397         - contents are set to NULL if the time is not updated.
398         - time is updated otherwise
399 
400 Return Value:
401 
402     TRUE if sufficient idle duration has elapsed to issue the next idle request.
403 
404 --*/
405 LOGICAL
406 ClasspIdleDurationSufficient (
407     IN PCLASS_PRIVATE_FDO_DATA FdoData,
408     OUT LARGE_INTEGER** CurrentTimeIn
409     )
410 {
411     ULONGLONG idleInterval;
412     LARGE_INTEGER CurrentTime;
413 
414     //
415     // If there are any outstanding non-idle requests, then there has been no
416     // idle time.
417     //
418     if (FdoData->ActiveIoCount > 0) {
419         if (CurrentTimeIn != NULL) {
420             *CurrentTimeIn = NULL;
421         }
422         return FALSE;
423     }
424 
425     //
426     // Check whether an idle request should be issued now or on the next timer
427     // expiration.
428     //
429 
430     CurrentTime = ClasspGetCurrentTime();
431     idleInterval = ClasspGetIdleTime(FdoData, CurrentTime);
432 
433     if (CurrentTimeIn != NULL) {
434         **CurrentTimeIn = CurrentTime;
435     }
436 
437     if (idleInterval >= FdoData->IdleInterval) {
438         return TRUE;
439     }
440 
441     return FALSE;
442 }
443 
444 /*++
445 
446 ClasspIdleTimerDpc
447 
448 Routine Description:
449 
450     Timer dpc function. This function will be called once every
451     IdleInterval. An idle request will be queued if sufficient idle time
452     has elapsed since the last non-idle request.
453 
454 Arguments:
455 
456     Dpc             - Pointer to DPC object
457     Context         - Pointer to the fdo device extension
458     SystemArgument1 - Not used
459     SystemArgument2 - Not used
460 
461 Return Value:
462 
463     None
464 
465 --*/
466 VOID
467 NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
468 ClasspIdleTimerDpc(
469     IN PKDPC Dpc,
470     IN PVOID Context,
471     IN PVOID SystemArgument1,
472     IN PVOID SystemArgument2
473     )
474 {
475     PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Context;
476     PCLASS_PRIVATE_FDO_DATA fdoData;
477     ULONGLONG idleTime;
478     NTSTATUS status;
479     LARGE_INTEGER currentTime;
480     LARGE_INTEGER* pCurrentTime;
481 
482     UNREFERENCED_PARAMETER(Dpc);
483     UNREFERENCED_PARAMETER(SystemArgument1);
484     UNREFERENCED_PARAMETER(SystemArgument2);
485 
486     if (fdoExtension == NULL) {
487         NT_ASSERT(fdoExtension != NULL);
488         return;
489     }
490 
491     fdoData = fdoExtension->PrivateFdoData;
492 
493     if (fdoData->ActiveIoCount <= 0) {
494 
495         //
496         // If there are max active idle requests, do not issue another one here.
497         //
498         if (fdoData->ActiveIdleIoCount >= fdoData->IdleActiveIoMax) {
499             return;
500         }
501 
502         //
503         // Check whether enough idle time has passed since the last non-idle
504         // request has completed.
505         //
506 
507         pCurrentTime = &currentTime;
508         if (ClasspIdleDurationSufficient(fdoData, &pCurrentTime)) {
509             //
510             // We are going to issue an idle request so reset the anti-starvation
511             // timer counter.
512             // If we are here (Idle duration is sufficient), pCurrentTime is
513             // expected to be set.
514             //
515             NT_ASSERT(pCurrentTime != NULL);
516             fdoData->AntiStarvationStartTime = *pCurrentTime;
517             ClasspServiceIdleRequest(fdoExtension, FALSE);
518         }
519         return;
520     }
521 
522     //
523     // Get the time difference between current time and last I/O
524     // complete time.
525     //
526 
527     currentTime = ClasspGetCurrentTime();
528     status = RtlULongLongSub((ULONGLONG)currentTime.QuadPart,
529                              (ULONGLONG)fdoData->AntiStarvationStartTime.QuadPart,
530                              &idleTime);
531 
532     if (NT_SUCCESS(status)) {
533         //
534         // Convert the time to milliseconds.
535         //
536         idleTime = ClasspTimeDiffToMs(idleTime);
537     } else {
538         //
539         // Failed to get time difference, assume enough time passed.
540         //
541         idleTime = fdoData->StarvationDuration;
542     }
543 
544     //
545     // If the timer is running then there must be at least one idle priority I/O pending
546     //
547     if (idleTime >= fdoData->StarvationDuration) {
548         fdoData->AntiStarvationStartTime = currentTime;
549         TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_TIMER, "ClasspIdleTimerDpc: Starvation timer. Send one idle request\n"));
550         ClasspServiceIdleRequest(fdoExtension, FALSE);
551     }
552     return;
553 }
554 
555 /*++
556 
557 ClasspEnqueueIdleRequest
558 
559 Routine Description:
560 
561     This function will insert the idle request into the list.
562     If the inserted reqeust is the first request then it will
563     start the timer.
564 
565 Arguments:
566 
567     DeviceObject    - Pointer to device object
568     Irp             - Pointer to the idle I/O request packet
569 
570 Return Value:
571 
572     NT status code.
573 
574 --*/
575 NTSTATUS
576 ClasspEnqueueIdleRequest(
577     PDEVICE_OBJECT DeviceObject,
578     PIRP Irp
579     )
580 {
581     PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension;
582     PCLASS_PRIVATE_FDO_DATA fdoData = fdoExtension->PrivateFdoData;
583     KIRQL oldIrql;
584     BOOLEAN issueRequest = TRUE;
585     LARGE_INTEGER currentTime;
586     LARGE_INTEGER* pCurrentTime;
587 
588     TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_TIMER, "ClasspEnqueueIdleRequest: Queue idle request %p\n", Irp));
589 
590     IoMarkIrpPending(Irp);
591 
592     //
593     // Reset issueRequest if the idle duration is not sufficient.
594     //
595     pCurrentTime = &currentTime;
596     if (ClasspIdleDurationSufficient(fdoData, &pCurrentTime) == FALSE) {
597         issueRequest = FALSE;
598     }
599 
600     //
601     // If there are already max active idle requests in the port driver, then
602     // queue this idle request.
603     //
604     if (fdoData->ActiveIdleIoCount >= fdoData->IdleActiveIoMax) {
605         issueRequest = FALSE;
606     }
607 
608 
609     KeAcquireSpinLock(&fdoData->IdleListLock, &oldIrql);
610     if (IsListEmpty(&fdoData->IdleIrpList)) {
611         NT_ASSERT(fdoData->IdleIoCount == 0);
612     }
613     InsertTailList(&fdoData->IdleIrpList, &Irp->Tail.Overlay.ListEntry);
614 
615 
616     fdoData->IdleIoCount++;
617     if (!fdoData->IdleTimerStarted) {
618         ClasspStartIdleTimer(fdoData);
619     }
620 
621     if (fdoData->IdleIoCount != 1) {
622         issueRequest = FALSE;
623     }
624 
625 
626     KeReleaseSpinLock(&fdoData->IdleListLock, oldIrql);
627 
628     if (issueRequest) {
629         ClasspServiceIdleRequest(fdoExtension, FALSE);
630     }
631 
632     return STATUS_PENDING;
633 }
634 
635 /*++
636 
637 ClasspDequeueIdleRequest
638 
639 Routine Description:
640 
641     This function will remove the next idle request from the list.
642     If there are no requests in the queue, then it will return NULL.
643 
644 Arguments:
645 
646     FdoExtension         - Pointer to the functional device extension
647 
648 Return Value:
649 
650     Pointer to removed IRP
651 
652 --*/
653 PIRP
654 ClasspDequeueIdleRequest(
655     PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
656     )
657 {
658     PCLASS_PRIVATE_FDO_DATA fdoData = FdoExtension->PrivateFdoData;
659     PLIST_ENTRY listEntry = NULL;
660     PIRP irp = NULL;
661     KIRQL oldIrql;
662 
663     KeAcquireSpinLock(&fdoData->IdleListLock, &oldIrql);
664 
665     if (fdoData->IdleIoCount > 0) {
666         listEntry = RemoveHeadList(&fdoData->IdleIrpList);
667         //
668         // Make sure we actaully removed a request from the list
669         //
670         NT_ASSERT(listEntry != &fdoData->IdleIrpList);
671         //
672         // Decrement the idle I/O count.
673         //
674         fdoData->IdleIoCount--;
675         //
676         // Stop the timer on last request
677         //
678         if (fdoData->IdleIoCount == 0) {
679             ClasspStopIdleTimer(fdoData);
680         }
681         irp = CONTAINING_RECORD(listEntry, IRP, Tail.Overlay.ListEntry);
682         NT_ASSERT(irp->Type == IO_TYPE_IRP);
683 
684 
685         InitializeListHead(&irp->Tail.Overlay.ListEntry);
686     }
687 
688     KeReleaseSpinLock(&fdoData->IdleListLock, oldIrql);
689 
690 
691     return irp;
692 }
693 
694 /*++
695 
696 ClasspCompleteIdleRequest
697 
698 Routine Description:
699 
700     This function will be called every time an idle request is completed.
701     This will call ClasspServiceIdleRequest to process any other pending idle requests.
702 
703 Arguments:
704 
705     FdoExtension    - Pointer to the device extension
706 
707 Return Value:
708 
709     None
710 
711 --*/
712 VOID
713 ClasspCompleteIdleRequest(
714     PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
715     )
716 {
717     PCLASS_PRIVATE_FDO_DATA fdoData = FdoExtension->PrivateFdoData;
718 
719     //
720     // Issue the next idle request if there are any left in the queue, there are
721     // no non-idle requests outstanding, there are less than max idle requests
722     // outstanding, and it has been long enough since the completion of the last
723     // non-idle request.
724     //
725     if ((fdoData->IdleIoCount > 0) &&
726         (fdoData->ActiveIdleIoCount < fdoData->IdleActiveIoMax) &&
727         (fdoData->ActiveIoCount <= 0) &&
728         (ClasspIdleDurationSufficient(fdoData, NULL))) {
729         TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_TIMER, "ClasspCompleteIdleRequest: Service next idle reqeusts\n"));
730         ClasspServiceIdleRequest(FdoExtension, TRUE);
731     }
732 
733     return;
734 }
735 
736 /*++
737 
738 ClasspServiceIdleRequest
739 
740 Routine Description:
741 
742     Remove the next pending idle request from the queue and process it.
743     If a request was removed then it will be processed otherwise it will
744     just return.
745 
746 Arguments:
747 
748     FdoExtension    - Pointer to the device extension
749     PostToDpc       - Flag to pass to ServiceTransferRequest to indicate if request must be posted to a DPC
750 
751 Return Value:
752 
753     None
754 
755 --*/
756 VOID
757 ClasspServiceIdleRequest(
758     PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
759     BOOLEAN PostToDpc
760     )
761 {
762     PIRP irp;
763 
764     irp = ClasspDequeueIdleRequest(FdoExtension);
765     if (irp != NULL) {
766         ServiceTransferRequest(FdoExtension->DeviceObject, irp, PostToDpc);
767     }
768     return;
769 }
770 
771