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 = ¤tTime; 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 = ¤tTime; 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