1 /* 2 * COPYRIGHT: See COPYING in the top level directory 3 * PROJECT: ReactOS Kernel 4 * FILE: ntoskrnl/ex/pushlock.c 5 * PURPOSE: Pushlock and Cache-Aware Pushlock Implementation 6 * PROGRAMMER: Alex Ionescu (alex.ionescu@reactos.com) 7 */ 8 9 /* INCLUDES *****************************************************************/ 10 11 #include <ntoskrnl.h> 12 #define NDEBUG 13 #include <debug.h> 14 15 /* DATA **********************************************************************/ 16 17 ULONG ExPushLockSpinCount = 0; 18 19 #undef EX_PUSH_LOCK 20 #undef PEX_PUSH_LOCK 21 22 /* PRIVATE FUNCTIONS *********************************************************/ 23 24 #ifdef _WIN64 25 #define InterlockedAndPointer(ptr,val) InterlockedAnd64((PLONGLONG)ptr,(LONGLONG)val) 26 #else 27 #define InterlockedAndPointer(ptr,val) InterlockedAnd((PLONG)ptr,(LONG)val) 28 #endif 29 30 /*++ 31 * @name ExpInitializePushLocks 32 * 33 * The ExpInitializePushLocks routine initialized Pushlock support. 34 * 35 * @param None. 36 * 37 * @return None. 38 * 39 * @remarks The ExpInitializePushLocks routine sets up the spin on SMP machines. 40 * 41 *--*/ 42 INIT_FUNCTION 43 VOID 44 NTAPI 45 ExpInitializePushLocks(VOID) 46 { 47 #ifdef CONFIG_SMP 48 /* Initialize an internal 1024-iteration spin for MP CPUs */ 49 if (KeNumberProcessors > 1) 50 ExPushLockSpinCount = 1024; 51 #endif 52 } 53 54 /*++ 55 * @name ExfWakePushLock 56 * 57 * The ExfWakePushLock routine wakes a Pushlock that is in the waiting 58 * state. 59 * 60 * @param PushLock 61 * Pointer to a pushlock that is waiting. 62 * 63 * @param OldValue 64 * Last known value of the pushlock before this routine was called. 65 * 66 * @return None. 67 * 68 * @remarks This is an internal routine; do not call it manually. Only the system 69 * can properly know if the pushlock is ready to be awakened or not. 70 * External callers should use ExfTrytoWakePushLock. 71 * 72 *--*/ 73 VOID 74 FASTCALL 75 ExfWakePushLock(PEX_PUSH_LOCK PushLock, 76 EX_PUSH_LOCK OldValue) 77 { 78 EX_PUSH_LOCK NewValue; 79 PEX_PUSH_LOCK_WAIT_BLOCK PreviousWaitBlock, FirstWaitBlock, LastWaitBlock; 80 PEX_PUSH_LOCK_WAIT_BLOCK WaitBlock; 81 KIRQL OldIrql; 82 83 /* Start main wake loop */ 84 for (;;) 85 { 86 /* Sanity checks */ 87 ASSERT(!OldValue.MultipleShared); 88 89 /* Check if it's locked */ 90 while (OldValue.Locked) 91 { 92 /* It's not waking anymore */ 93 NewValue.Value = OldValue.Value &~ EX_PUSH_LOCK_WAKING; 94 95 /* Sanity checks */ 96 ASSERT(!NewValue.Waking); 97 ASSERT(NewValue.Locked); 98 ASSERT(NewValue.Waiting); 99 100 /* Write the New Value */ 101 NewValue.Ptr = InterlockedCompareExchangePointer(&PushLock->Ptr, 102 NewValue.Ptr, 103 OldValue.Ptr); 104 if (NewValue.Value == OldValue.Value) return; 105 106 /* Someone changed the value behind our back, update it*/ 107 OldValue = NewValue; 108 } 109 110 /* Save the First Block */ 111 FirstWaitBlock = (PEX_PUSH_LOCK_WAIT_BLOCK)(OldValue.Value & 112 ~EX_PUSH_LOCK_PTR_BITS); 113 WaitBlock = FirstWaitBlock; 114 115 /* Try to find the last block */ 116 while (TRUE) 117 { 118 /* Get the last wait block */ 119 LastWaitBlock = WaitBlock->Last; 120 121 /* Check if we found it */ 122 if (LastWaitBlock) 123 { 124 /* Use it */ 125 WaitBlock = LastWaitBlock; 126 break; 127 } 128 129 /* Save the previous block */ 130 PreviousWaitBlock = WaitBlock; 131 132 /* Move to next block */ 133 WaitBlock = WaitBlock->Next; 134 135 /* Save the previous block */ 136 WaitBlock->Previous = PreviousWaitBlock; 137 } 138 139 /* Check if the last Wait Block is not Exclusive or if it's the only one */ 140 PreviousWaitBlock = WaitBlock->Previous; 141 if (!(WaitBlock->Flags & EX_PUSH_LOCK_FLAGS_EXCLUSIVE) || 142 !(PreviousWaitBlock)) 143 { 144 /* Destroy the pushlock */ 145 NewValue.Value = 0; 146 ASSERT(!NewValue.Waking); 147 148 /* Write the New Value */ 149 NewValue.Ptr = InterlockedCompareExchangePointer(&PushLock->Ptr, 150 NewValue.Ptr, 151 OldValue.Ptr); 152 if (NewValue.Value == OldValue.Value) break; 153 154 /* Someone changed the value behind our back, update it*/ 155 OldValue = NewValue; 156 } 157 else 158 { 159 /* Link the wait blocks */ 160 FirstWaitBlock->Last = PreviousWaitBlock; 161 WaitBlock->Previous = NULL; 162 163 /* Sanity checks */ 164 ASSERT(FirstWaitBlock != WaitBlock); 165 ASSERT(PushLock->Waiting); 166 167 /* Remove waking bit from pushlock */ 168 InterlockedAndPointer(&PushLock->Value, ~EX_PUSH_LOCK_WAKING); 169 170 /* Leave the loop */ 171 break; 172 } 173 } 174 175 /* Check if there's a previous block */ 176 OldIrql = DISPATCH_LEVEL; 177 if (WaitBlock->Previous) 178 { 179 /* Raise to Dispatch */ 180 KeRaiseIrql(DISPATCH_LEVEL, &OldIrql); 181 } 182 183 /* Signaling loop */ 184 for (;;) 185 { 186 /* Get the previous Wait block */ 187 PreviousWaitBlock = WaitBlock->Previous; 188 189 /* Sanity check */ 190 ASSERT(!WaitBlock->Signaled); 191 192 #if DBG 193 /* We are about to get signaled */ 194 WaitBlock->Signaled = TRUE; 195 #endif 196 197 /* Set the Wait Bit in the Wait Block */ 198 if (!InterlockedBitTestAndReset(&WaitBlock->Flags, 1)) 199 { 200 /* Nobody signaled us, so do it */ 201 KeSignalGateBoostPriority(&WaitBlock->WakeGate); 202 } 203 204 /* Set the wait block and check if there still is one to loop*/ 205 WaitBlock = PreviousWaitBlock; 206 if (!WaitBlock) break; 207 } 208 209 /* Check if we have to lower back the IRQL */ 210 if (OldIrql != DISPATCH_LEVEL) KeLowerIrql(OldIrql); 211 } 212 213 /*++ 214 * @name ExpOptimizePushLockList 215 * 216 * The ExpOptimizePushLockList routine optimizes the list of waiters 217 * associated to a pushlock's wait block. 218 * 219 * @param PushLock 220 * Pointer to a pushlock whose waiter list needs to be optimized. 221 * 222 * @param OldValue 223 * Last known value of the pushlock before this routine was called. 224 * 225 * @return None. 226 * 227 * @remarks At the end of the optimization, the pushlock will also be wakened. 228 * 229 *--*/ 230 VOID 231 FASTCALL 232 ExpOptimizePushLockList(PEX_PUSH_LOCK PushLock, 233 EX_PUSH_LOCK OldValue) 234 { 235 PEX_PUSH_LOCK_WAIT_BLOCK WaitBlock, LastWaitBlock, PreviousWaitBlock, FirstWaitBlock; 236 EX_PUSH_LOCK NewValue; 237 238 /* Start main loop */ 239 for (;;) 240 { 241 /* Check if we've been unlocked */ 242 if (!OldValue.Locked) 243 { 244 /* Wake us up and leave */ 245 ExfWakePushLock(PushLock, OldValue); 246 break; 247 } 248 249 /* Get the wait block */ 250 WaitBlock = (PEX_PUSH_LOCK_WAIT_BLOCK)(OldValue.Value & 251 ~EX_PUSH_LOCK_PTR_BITS); 252 253 /* Loop the blocks */ 254 FirstWaitBlock = WaitBlock; 255 while (TRUE) 256 { 257 /* Get the last wait block */ 258 LastWaitBlock = WaitBlock->Last; 259 if (LastWaitBlock) 260 { 261 /* Set this as the new last block, we're done */ 262 FirstWaitBlock->Last = LastWaitBlock; 263 break; 264 } 265 266 /* Save the block */ 267 PreviousWaitBlock = WaitBlock; 268 269 /* Get the next block */ 270 WaitBlock = WaitBlock->Next; 271 272 /* Save the previous */ 273 WaitBlock->Previous = PreviousWaitBlock; 274 } 275 276 /* Remove the wake bit */ 277 NewValue.Value = OldValue.Value &~ EX_PUSH_LOCK_WAKING; 278 279 /* Sanity checks */ 280 ASSERT(NewValue.Locked); 281 ASSERT(!NewValue.Waking); 282 283 /* Update the value */ 284 NewValue.Ptr = InterlockedCompareExchangePointer(&PushLock->Ptr, 285 NewValue.Ptr, 286 OldValue.Ptr); 287 288 /* If we updated correctly, leave */ 289 if (NewValue.Value == OldValue.Value) break; 290 291 /* Update value */ 292 OldValue = NewValue; 293 } 294 } 295 296 /*++ 297 * @name ExTimedWaitForUnblockPushLock 298 * 299 * The ExTimedWaitForUnblockPushLock routine waits for a pushlock 300 * to be unblocked, for a specified internal. 301 * 302 * @param PushLock 303 * Pointer to a pushlock whose waiter list needs to be optimized. 304 * 305 * @param WaitBlock 306 * Pointer to the pushlock's wait block. 307 * 308 * @param Timeout 309 * Amount of time to wait for this pushlock to be unblocked. 310 * 311 * @return STATUS_SUCCESS is the pushlock is now unblocked, otherwise the error 312 * code returned by KeWaitForSingleObject. 313 * 314 * @remarks If the wait fails, then a manual unblock is attempted. 315 * 316 *--*/ 317 NTSTATUS 318 FASTCALL 319 ExTimedWaitForUnblockPushLock(IN PEX_PUSH_LOCK PushLock, 320 IN PVOID WaitBlock, 321 IN PLARGE_INTEGER Timeout) 322 { 323 NTSTATUS Status; 324 325 /* Initialize the wait event */ 326 KeInitializeEvent(&((PEX_PUSH_LOCK_WAIT_BLOCK)WaitBlock)->WakeEvent, 327 SynchronizationEvent, 328 FALSE); 329 330 #ifdef CONFIG_SMP 331 /* Spin on the push lock if necessary */ 332 if (ExPushLockSpinCount) 333 { 334 ULONG i = ExPushLockSpinCount; 335 336 do 337 { 338 /* Check if we got lucky and can leave early */ 339 if (!(*(volatile LONG *)&((PEX_PUSH_LOCK_WAIT_BLOCK)WaitBlock)->Flags & EX_PUSH_LOCK_WAITING)) 340 return STATUS_SUCCESS; 341 342 YieldProcessor(); 343 } while (--i); 344 } 345 #endif 346 347 /* Now try to remove the wait bit */ 348 if (InterlockedBitTestAndReset(&((PEX_PUSH_LOCK_WAIT_BLOCK)WaitBlock)->Flags, 349 EX_PUSH_LOCK_FLAGS_WAIT_V)) 350 { 351 /* Nobody removed it already, let's do a full wait */ 352 Status = KeWaitForSingleObject(&((PEX_PUSH_LOCK_WAIT_BLOCK)WaitBlock)-> 353 WakeEvent, 354 WrPushLock, 355 KernelMode, 356 FALSE, 357 Timeout); 358 /* Check if the wait was satisfied */ 359 if (Status != STATUS_SUCCESS) 360 { 361 /* Try unblocking the pushlock if it was not */ 362 ExfUnblockPushLock(PushLock, WaitBlock); 363 } 364 } 365 else 366 { 367 /* Someone beat us to it, no need to wait */ 368 Status = STATUS_SUCCESS; 369 } 370 371 /* Return status */ 372 return Status; 373 } 374 375 /*++ 376 * @name ExWaitForUnblockPushLock 377 * 378 * The ExWaitForUnblockPushLock routine waits for a pushlock 379 * to be unblocked, for a specified internal. 380 * 381 * @param PushLock 382 * Pointer to a pushlock whose waiter list needs to be optimized. 383 * 384 * @param WaitBlock 385 * Pointer to the pushlock's wait block. 386 * 387 * @return STATUS_SUCCESS is the pushlock is now unblocked, otherwise the error 388 * code returned by KeWaitForSingleObject. 389 * 390 * @remarks If the wait fails, then a manual unblock is attempted. 391 * 392 *--*/ 393 VOID 394 FASTCALL 395 ExWaitForUnblockPushLock(IN PEX_PUSH_LOCK PushLock, 396 IN PVOID WaitBlock) 397 { 398 /* Call the timed function with no timeout */ 399 ExTimedWaitForUnblockPushLock(PushLock, WaitBlock, NULL); 400 } 401 402 /*++ 403 * @name ExBlockPushLock 404 * 405 * The ExBlockPushLock routine blocks a pushlock. 406 * 407 * @param PushLock 408 * Pointer to a pushlock whose waiter list needs to be optimized. 409 * 410 * @param WaitBlock 411 * Pointer to the pushlock's wait block. 412 * 413 * @return None. 414 * 415 * @remarks None. 416 * 417 *--*/ 418 VOID 419 FASTCALL 420 ExBlockPushLock(PEX_PUSH_LOCK PushLock, 421 PVOID pWaitBlock) 422 { 423 PEX_PUSH_LOCK_WAIT_BLOCK WaitBlock = pWaitBlock; 424 EX_PUSH_LOCK NewValue, OldValue; 425 426 /* Detect invalid wait block alignment */ 427 ASSERT(((ULONG_PTR)pWaitBlock & 0xF) == 0); 428 429 /* Set the waiting bit */ 430 WaitBlock->Flags = EX_PUSH_LOCK_FLAGS_WAIT; 431 432 /* Get the old value */ 433 OldValue = *PushLock; 434 435 /* Start block loop */ 436 for (;;) 437 { 438 /* Link the wait blocks */ 439 WaitBlock->Next = OldValue.Ptr; 440 441 /* Set the new wait block value */ 442 NewValue.Ptr = InterlockedCompareExchangePointer(&PushLock->Ptr, 443 WaitBlock, 444 OldValue.Ptr); 445 if (OldValue.Ptr == NewValue.Ptr) break; 446 447 /* Try again with the new value */ 448 OldValue = NewValue; 449 } 450 } 451 452 /* PUBLIC FUNCTIONS **********************************************************/ 453 454 /*++ 455 * @name ExAcquirePushLockExclusive 456 * @implemented NT5.1 457 * 458 * The ExAcquirePushLockExclusive macro exclusively acquires a PushLock. 459 * 460 * @params PushLock 461 * Pointer to the pushlock which is to be acquired. 462 * 463 * @return None. 464 * 465 * @remarks Callers of ExAcquirePushLockShared must be running at IRQL <= APC_LEVEL. 466 * This macro should usually be paired up with KeAcquireCriticalRegion. 467 * 468 *--*/ 469 VOID 470 FASTCALL 471 ExfAcquirePushLockExclusive(PEX_PUSH_LOCK PushLock) 472 { 473 EX_PUSH_LOCK OldValue = *PushLock, NewValue, TempValue; 474 BOOLEAN NeedWake; 475 EX_PUSH_LOCK_WAIT_BLOCK Block; 476 PEX_PUSH_LOCK_WAIT_BLOCK WaitBlock = &Block; 477 478 /* Start main loop */ 479 for (;;) 480 { 481 /* Check if it's unlocked */ 482 if (!OldValue.Locked) 483 { 484 /* Lock it */ 485 NewValue.Value = OldValue.Value | EX_PUSH_LOCK_LOCK; 486 ASSERT(NewValue.Locked); 487 488 /* Set the new value */ 489 if (InterlockedCompareExchangePointer(&PushLock->Ptr, 490 NewValue.Ptr, 491 OldValue.Ptr) != OldValue.Ptr) 492 { 493 /* Retry */ 494 OldValue = *PushLock; 495 continue; 496 } 497 498 /* Break out of the loop */ 499 break; 500 } 501 else 502 { 503 /* We'll have to create a Waitblock */ 504 WaitBlock->Flags = EX_PUSH_LOCK_FLAGS_EXCLUSIVE | 505 EX_PUSH_LOCK_FLAGS_WAIT; 506 WaitBlock->Previous = NULL; 507 NeedWake = FALSE; 508 509 /* Check if there is already a waiter */ 510 if (OldValue.Waiting) 511 { 512 /* Nobody is the last waiter yet */ 513 WaitBlock->Last = NULL; 514 515 /* We are an exclusive waiter */ 516 WaitBlock->ShareCount = 0; 517 518 /* Set the current Wait Block pointer */ 519 WaitBlock->Next = (PEX_PUSH_LOCK_WAIT_BLOCK)( 520 OldValue.Value &~ EX_PUSH_LOCK_PTR_BITS); 521 522 /* Point to ours */ 523 NewValue.Value = (OldValue.Value & EX_PUSH_LOCK_MULTIPLE_SHARED) | 524 EX_PUSH_LOCK_LOCK | 525 EX_PUSH_LOCK_WAKING | 526 EX_PUSH_LOCK_WAITING | 527 (ULONG_PTR)WaitBlock; 528 529 /* Check if the pushlock was already waking */ 530 if (!OldValue.Waking) NeedWake = TRUE; 531 } 532 else 533 { 534 /* We are the first waiter, so loop the wait block */ 535 WaitBlock->Last = WaitBlock; 536 537 /* Set the share count */ 538 WaitBlock->ShareCount = (LONG)OldValue.Shared; 539 540 /* Check if someone is sharing this pushlock */ 541 if (OldValue.Shared > 1) 542 { 543 /* Point to our wait block */ 544 NewValue.Value = EX_PUSH_LOCK_MULTIPLE_SHARED | 545 EX_PUSH_LOCK_LOCK | 546 EX_PUSH_LOCK_WAITING | 547 (ULONG_PTR)WaitBlock; 548 } 549 else 550 { 551 /* No shared count */ 552 WaitBlock->ShareCount = 0; 553 554 /* Point to our wait block */ 555 NewValue.Value = EX_PUSH_LOCK_LOCK | 556 EX_PUSH_LOCK_WAITING | 557 (ULONG_PTR)WaitBlock; 558 } 559 } 560 561 #if DBG 562 /* Setup the Debug Wait Block */ 563 WaitBlock->Signaled = 0; 564 WaitBlock->OldValue = OldValue; 565 WaitBlock->NewValue = NewValue; 566 WaitBlock->PushLock = PushLock; 567 #endif 568 569 /* Sanity check */ 570 ASSERT(NewValue.Waiting); 571 ASSERT(NewValue.Locked); 572 573 /* Write the new value */ 574 TempValue = NewValue; 575 NewValue.Ptr = InterlockedCompareExchangePointer(&PushLock->Ptr, 576 NewValue.Ptr, 577 OldValue.Ptr); 578 if (NewValue.Value != OldValue.Value) 579 { 580 /* Retry */ 581 OldValue = *PushLock; 582 continue; 583 } 584 585 /* Check if the pushlock needed waking */ 586 if (NeedWake) 587 { 588 /* Scan the Waiters and Wake PushLocks */ 589 ExpOptimizePushLockList(PushLock, TempValue); 590 } 591 592 /* Set up the Wait Gate */ 593 KeInitializeGate(&WaitBlock->WakeGate); 594 595 #ifdef CONFIG_SMP 596 /* Now spin on the push lock if necessary */ 597 if (ExPushLockSpinCount) 598 { 599 ULONG i = ExPushLockSpinCount; 600 601 do 602 { 603 if (!(*(volatile LONG *)&WaitBlock->Flags & EX_PUSH_LOCK_WAITING)) 604 break; 605 606 YieldProcessor(); 607 } while (--i); 608 } 609 #endif 610 611 /* Now try to remove the wait bit */ 612 if (InterlockedBitTestAndReset(&WaitBlock->Flags, 1)) 613 { 614 /* Nobody removed it already, let's do a full wait */ 615 KeWaitForGate(&WaitBlock->WakeGate, WrPushLock, KernelMode); 616 ASSERT(WaitBlock->Signaled); 617 } 618 619 /* We shouldn't be shared anymore */ 620 ASSERT((WaitBlock->ShareCount == 0)); 621 622 /* Loop again */ 623 OldValue = NewValue; 624 } 625 } 626 } 627 628 /*++ 629 * @name ExAcquirePushLockShared 630 * @implemented NT5.1 631 * 632 * The ExAcquirePushLockShared routine acquires a shared PushLock. 633 * 634 * @params PushLock 635 * Pointer to the pushlock which is to be acquired. 636 * 637 * @return None. 638 * 639 * @remarks Callers of ExAcquirePushLockShared must be running at IRQL <= APC_LEVEL. 640 * This macro should usually be paired up with KeAcquireCriticalRegion. 641 * 642 *--*/ 643 VOID 644 FASTCALL 645 ExfAcquirePushLockShared(PEX_PUSH_LOCK PushLock) 646 { 647 EX_PUSH_LOCK OldValue = *PushLock, NewValue; 648 BOOLEAN NeedWake; 649 EX_PUSH_LOCK_WAIT_BLOCK Block; 650 PEX_PUSH_LOCK_WAIT_BLOCK WaitBlock = &Block; 651 652 /* Start main loop */ 653 for (;;) 654 { 655 /* Check if it's unlocked or if it's waiting without any sharers */ 656 if (!(OldValue.Locked) || (!(OldValue.Waiting) && (OldValue.Shared > 0))) 657 { 658 /* Check if anyone is waiting on it */ 659 if (!OldValue.Waiting) 660 { 661 /* Increase the share count and lock it */ 662 NewValue.Value = OldValue.Value | EX_PUSH_LOCK_LOCK; 663 NewValue.Shared++; 664 } 665 else 666 { 667 /* Simply set the lock bit */ 668 NewValue.Value = OldValue.Value | EX_PUSH_LOCK_LOCK; 669 } 670 671 /* Sanity check */ 672 ASSERT(NewValue.Locked); 673 674 /* Set the new value */ 675 NewValue.Ptr = InterlockedCompareExchangePointer(&PushLock->Ptr, 676 NewValue.Ptr, 677 OldValue.Ptr); 678 if (NewValue.Value != OldValue.Value) 679 { 680 /* Retry */ 681 OldValue = *PushLock; 682 continue; 683 } 684 685 /* Break out of the loop */ 686 break; 687 } 688 else 689 { 690 /* We'll have to create a Waitblock */ 691 WaitBlock->Flags = EX_PUSH_LOCK_FLAGS_WAIT; 692 WaitBlock->ShareCount = 0; 693 NeedWake = FALSE; 694 WaitBlock->Previous = NULL; 695 696 /* Check if there is already a waiter */ 697 if (OldValue.Waiting) 698 { 699 /* Set the current Wait Block pointer */ 700 WaitBlock->Next = (PEX_PUSH_LOCK_WAIT_BLOCK)( 701 OldValue.Value &~ EX_PUSH_LOCK_PTR_BITS); 702 703 /* Nobody is the last waiter yet */ 704 WaitBlock->Last = NULL; 705 706 /* Point to ours */ 707 NewValue.Value = (OldValue.Value & (EX_PUSH_LOCK_MULTIPLE_SHARED | 708 EX_PUSH_LOCK_LOCK)) | 709 EX_PUSH_LOCK_WAKING | 710 EX_PUSH_LOCK_WAITING | 711 (ULONG_PTR)WaitBlock; 712 713 /* Check if the pushlock was already waking */ 714 if (!OldValue.Waking) NeedWake = TRUE; 715 } 716 else 717 { 718 /* We are the first waiter, so loop the wait block */ 719 WaitBlock->Last = WaitBlock; 720 721 /* Point to our wait block */ 722 NewValue.Value = (OldValue.Value & EX_PUSH_LOCK_PTR_BITS) | 723 EX_PUSH_LOCK_WAITING | 724 (ULONG_PTR)WaitBlock; 725 } 726 727 /* Sanity check */ 728 ASSERT(NewValue.Waiting); 729 730 #if DBG 731 /* Setup the Debug Wait Block */ 732 WaitBlock->Signaled = 0; 733 WaitBlock->OldValue = OldValue; 734 WaitBlock->NewValue = NewValue; 735 WaitBlock->PushLock = PushLock; 736 #endif 737 738 /* Write the new value */ 739 NewValue.Ptr = InterlockedCompareExchangePointer(&PushLock->Ptr, 740 NewValue.Ptr, 741 OldValue.Ptr); 742 if (NewValue.Ptr != OldValue.Ptr) 743 { 744 /* Retry */ 745 OldValue = *PushLock; 746 continue; 747 } 748 749 /* Update the value now */ 750 OldValue = NewValue; 751 752 /* Check if the pushlock needed waking */ 753 if (NeedWake) 754 { 755 /* Scan the Waiters and Wake PushLocks */ 756 ExpOptimizePushLockList(PushLock, OldValue); 757 } 758 759 /* Set up the Wait Gate */ 760 KeInitializeGate(&WaitBlock->WakeGate); 761 762 #ifdef CONFIG_SMP 763 /* Now spin on the push lock if necessary */ 764 if (ExPushLockSpinCount) 765 { 766 ULONG i = ExPushLockSpinCount; 767 768 do 769 { 770 if (!(*(volatile LONG *)&WaitBlock->Flags & EX_PUSH_LOCK_WAITING)) 771 break; 772 773 YieldProcessor(); 774 } while (--i); 775 } 776 #endif 777 778 /* Now try to remove the wait bit */ 779 if (InterlockedBitTestAndReset(&WaitBlock->Flags, 1)) 780 { 781 /* Fast-path did not work, we need to do a full wait */ 782 KeWaitForGate(&WaitBlock->WakeGate, WrPushLock, KernelMode); 783 ASSERT(WaitBlock->Signaled); 784 } 785 786 /* We shouldn't be shared anymore */ 787 ASSERT((WaitBlock->ShareCount == 0)); 788 } 789 } 790 } 791 792 /*++ 793 * @name ExfReleasePushLock 794 * @implemented NT5.1 795 * 796 * The ExReleasePushLock routine releases a previously acquired PushLock. 797 * 798 * 799 * @params PushLock 800 * Pointer to a previously acquired pushlock. 801 * 802 * @return None. 803 * 804 * @remarks Callers of ExfReleasePushLock must be running at IRQL <= APC_LEVEL. 805 * This macro should usually be paired up with KeLeaveCriticalRegion. 806 * 807 *--*/ 808 VOID 809 FASTCALL 810 ExfReleasePushLock(PEX_PUSH_LOCK PushLock) 811 { 812 EX_PUSH_LOCK OldValue = *PushLock, NewValue, WakeValue; 813 PEX_PUSH_LOCK_WAIT_BLOCK WaitBlock, LastWaitBlock; 814 815 /* Sanity check */ 816 ASSERT(OldValue.Locked); 817 818 /* Start main loop */ 819 while (TRUE) 820 { 821 /* Check if someone is waiting on the lock */ 822 if (!OldValue.Waiting) 823 { 824 /* Check if it's shared */ 825 if (OldValue.Shared > 1) 826 { 827 /* Write the Old Value but decrease share count */ 828 NewValue = OldValue; 829 NewValue.Shared--; 830 } 831 else 832 { 833 /* Simply clear the lock */ 834 NewValue.Value = 0; 835 } 836 837 /* Write the New Value */ 838 NewValue.Ptr = InterlockedCompareExchangePointer(&PushLock->Ptr, 839 NewValue.Ptr, 840 OldValue.Ptr); 841 if (NewValue.Value == OldValue.Value) return; 842 843 /* Did it enter a wait state? */ 844 OldValue = NewValue; 845 } 846 else 847 { 848 /* Ok, we do know someone is waiting on it. Are there more then one? */ 849 if (OldValue.MultipleShared) 850 { 851 /* Get the wait block */ 852 WaitBlock = (PEX_PUSH_LOCK_WAIT_BLOCK)(OldValue.Value & 853 ~EX_PUSH_LOCK_PTR_BITS); 854 855 /* Loop until we find the last wait block */ 856 while (TRUE) 857 { 858 /* Get the last wait block */ 859 LastWaitBlock = WaitBlock->Last; 860 861 /* Did it exist? */ 862 if (LastWaitBlock) 863 { 864 /* Choose it */ 865 WaitBlock = LastWaitBlock; 866 break; 867 } 868 869 /* Keep searching */ 870 WaitBlock = WaitBlock->Next; 871 } 872 873 /* Make sure the Share Count is above 0 */ 874 if (WaitBlock->ShareCount > 0) 875 { 876 /* This shouldn't be an exclusive wait block */ 877 ASSERT(WaitBlock->Flags & EX_PUSH_LOCK_FLAGS_EXCLUSIVE); 878 879 /* Do the decrease and check if the lock isn't shared anymore */ 880 if (InterlockedDecrement(&WaitBlock->ShareCount) > 0) return; 881 } 882 } 883 884 /* 885 * If nobody was waiting on the block, then we possibly reduced the number 886 * of times the pushlock was shared, and we unlocked it. 887 * If someone was waiting, and more then one person is waiting, then we 888 * reduced the number of times the pushlock is shared in the wait block. 889 * Therefore, at this point, we can now 'satisfy' the wait. 890 */ 891 for (;;) 892 { 893 /* Now we need to see if it's waking */ 894 if (OldValue.Waking) 895 { 896 /* Remove the lock and multiple shared bits */ 897 NewValue.Value = OldValue.Value; 898 NewValue.MultipleShared = FALSE; 899 NewValue.Locked = FALSE; 900 901 /* Sanity check */ 902 ASSERT(NewValue.Waking && !NewValue.Locked && !NewValue.MultipleShared); 903 904 /* Write the new value */ 905 NewValue.Ptr = InterlockedCompareExchangePointer(&PushLock->Ptr, 906 NewValue.Ptr, 907 OldValue.Ptr); 908 if (NewValue.Value == OldValue.Value) return; 909 } 910 else 911 { 912 /* Remove the lock and multiple shared bits */ 913 NewValue.Value = OldValue.Value; 914 NewValue.MultipleShared = FALSE; 915 NewValue.Locked = FALSE; 916 917 /* It's not already waking, so add the wake bit */ 918 NewValue.Waking = TRUE; 919 920 /* Sanity check */ 921 ASSERT(NewValue.Waking && !NewValue.Locked && !NewValue.MultipleShared); 922 923 /* Write the new value */ 924 WakeValue = NewValue; 925 NewValue.Ptr = InterlockedCompareExchangePointer(&PushLock->Ptr, 926 NewValue.Ptr, 927 OldValue.Ptr); 928 if (NewValue.Value != OldValue.Value) continue; 929 930 /* The write was successful. The pushlock is Unlocked and Waking */ 931 ExfWakePushLock(PushLock, WakeValue); 932 return; 933 } 934 } 935 } 936 } 937 } 938 939 /*++ 940 * @name ExfReleasePushLockShared 941 * @implemented NT5.2 942 * 943 * The ExfReleasePushLockShared macro releases a previously acquired PushLock. 944 * 945 * @params PushLock 946 * Pointer to a previously acquired pushlock. 947 * 948 * @return None. 949 * 950 * @remarks Callers of ExReleasePushLockShared must be running at IRQL <= APC_LEVEL. 951 * This macro should usually be paired up with KeLeaveCriticalRegion. 952 * 953 *--*/ 954 VOID 955 FASTCALL 956 ExfReleasePushLockShared(PEX_PUSH_LOCK PushLock) 957 { 958 EX_PUSH_LOCK OldValue = *PushLock, NewValue, WakeValue; 959 PEX_PUSH_LOCK_WAIT_BLOCK WaitBlock, LastWaitBlock; 960 961 /* Check if someone is waiting on the lock */ 962 while (!OldValue.Waiting) 963 { 964 /* Check if it's shared */ 965 if (OldValue.Shared > 1) 966 { 967 /* Write the Old Value but decrease share count */ 968 NewValue = OldValue; 969 NewValue.Shared--; 970 } 971 else 972 { 973 /* Simply clear the lock */ 974 NewValue.Value = 0; 975 } 976 977 /* Write the New Value */ 978 NewValue.Ptr = InterlockedCompareExchangePointer(&PushLock->Ptr, 979 NewValue.Ptr, 980 OldValue.Ptr); 981 if (NewValue.Value == OldValue.Value) return; 982 983 /* Did it enter a wait state? */ 984 OldValue = NewValue; 985 } 986 987 /* Ok, we do know someone is waiting on it. Are there more then one? */ 988 if (OldValue.MultipleShared) 989 { 990 /* Get the wait block */ 991 WaitBlock = (PEX_PUSH_LOCK_WAIT_BLOCK)(OldValue.Value & 992 ~EX_PUSH_LOCK_PTR_BITS); 993 994 /* Loop until we find the last wait block */ 995 while (TRUE) 996 { 997 /* Get the last wait block */ 998 LastWaitBlock = WaitBlock->Last; 999 1000 /* Did it exist? */ 1001 if (LastWaitBlock) 1002 { 1003 /* Choose it */ 1004 WaitBlock = LastWaitBlock; 1005 break; 1006 } 1007 1008 /* Keep searching */ 1009 WaitBlock = WaitBlock->Next; 1010 } 1011 1012 /* Sanity checks */ 1013 ASSERT(WaitBlock->ShareCount > 0); 1014 ASSERT(WaitBlock->Flags & EX_PUSH_LOCK_FLAGS_EXCLUSIVE); 1015 1016 /* Do the decrease and check if the lock isn't shared anymore */ 1017 if (InterlockedDecrement(&WaitBlock->ShareCount) > 0) return; 1018 } 1019 1020 /* 1021 * If nobody was waiting on the block, then we possibly reduced the number 1022 * of times the pushlock was shared, and we unlocked it. 1023 * If someone was waiting, and more then one person is waiting, then we 1024 * reduced the number of times the pushlock is shared in the wait block. 1025 * Therefore, at this point, we can now 'satisfy' the wait. 1026 */ 1027 for (;;) 1028 { 1029 /* Now we need to see if it's waking */ 1030 if (OldValue.Waking) 1031 { 1032 /* Remove the lock and multiple shared bits */ 1033 NewValue.Value = OldValue.Value; 1034 NewValue.MultipleShared = FALSE; 1035 NewValue.Locked = FALSE; 1036 1037 /* Sanity check */ 1038 ASSERT(NewValue.Waking && !NewValue.Locked && !NewValue.MultipleShared); 1039 1040 /* Write the new value */ 1041 NewValue.Ptr = InterlockedCompareExchangePointer(&PushLock->Ptr, 1042 NewValue.Ptr, 1043 OldValue.Ptr); 1044 if (NewValue.Value == OldValue.Value) return; 1045 } 1046 else 1047 { 1048 /* Remove the lock and multiple shared bits */ 1049 NewValue.Value = OldValue.Value; 1050 NewValue.MultipleShared = FALSE; 1051 NewValue.Locked = FALSE; 1052 1053 /* It's not already waking, so add the wake bit */ 1054 NewValue.Waking = TRUE; 1055 1056 /* Sanity check */ 1057 ASSERT(NewValue.Waking && !NewValue.Locked && !NewValue.MultipleShared); 1058 1059 /* Write the new value */ 1060 WakeValue = NewValue; 1061 NewValue.Ptr = InterlockedCompareExchangePointer(&PushLock->Ptr, 1062 NewValue.Ptr, 1063 OldValue.Ptr); 1064 if (NewValue.Value != OldValue.Value) continue; 1065 1066 /* The write was successful. The pushlock is Unlocked and Waking */ 1067 ExfWakePushLock(PushLock, WakeValue); 1068 return; 1069 } 1070 } 1071 } 1072 1073 /*++ 1074 * ExfReleasePushLockExclusive 1075 * @implemented NT5.2 1076 * 1077 * The ExfReleasePushLockExclusive routine releases a previously 1078 * exclusively acquired PushLock. 1079 * 1080 * @params PushLock 1081 * Pointer to a previously acquired pushlock. 1082 * 1083 * @return None. 1084 * 1085 * @remarks Callers of ExReleasePushLockExclusive must be running at IRQL <= APC_LEVEL. 1086 * This macro should usually be paired up with KeLeaveCriticalRegion. 1087 * 1088 *--*/ 1089 VOID 1090 FASTCALL 1091 ExfReleasePushLockExclusive(PEX_PUSH_LOCK PushLock) 1092 { 1093 EX_PUSH_LOCK NewValue, WakeValue; 1094 EX_PUSH_LOCK OldValue = *PushLock; 1095 1096 /* Loop until we can change */ 1097 for (;;) 1098 { 1099 /* Sanity checks */ 1100 ASSERT(OldValue.Locked); 1101 ASSERT(OldValue.Waiting || OldValue.Shared == 0); 1102 1103 /* Check if it's waiting and not yet waking */ 1104 if ((OldValue.Waiting) && !(OldValue.Waking)) 1105 { 1106 /* Remove the lock bit, and add the wake bit */ 1107 NewValue.Value = (OldValue.Value &~ EX_PUSH_LOCK_LOCK) | 1108 EX_PUSH_LOCK_WAKING; 1109 1110 /* Sanity check */ 1111 ASSERT(NewValue.Waking && !NewValue.Locked); 1112 1113 /* Write the New Value. Save our original value for waking */ 1114 WakeValue = NewValue; 1115 NewValue.Ptr = InterlockedCompareExchangePointer(&PushLock->Ptr, 1116 NewValue.Ptr, 1117 OldValue.Ptr); 1118 1119 /* Check if the value changed behind our back */ 1120 if (NewValue.Value == OldValue.Value) 1121 { 1122 /* Wake the Pushlock */ 1123 ExfWakePushLock(PushLock, WakeValue); 1124 break; 1125 } 1126 } 1127 else 1128 { 1129 /* A simple unlock */ 1130 NewValue.Value = OldValue.Value &~ EX_PUSH_LOCK_LOCK; 1131 1132 /* Sanity check */ 1133 ASSERT(NewValue.Waking || !NewValue.Waiting); 1134 1135 /* Write the New Value */ 1136 NewValue.Ptr = InterlockedCompareExchangePointer(&PushLock->Ptr, 1137 NewValue.Ptr, 1138 OldValue.Ptr); 1139 1140 /* Check if the value changed behind our back */ 1141 if (NewValue.Value == OldValue.Value) break; 1142 } 1143 1144 /* Loop again */ 1145 OldValue = NewValue; 1146 } 1147 } 1148 1149 /*++ 1150 * @name ExfTryToWakePushLock 1151 * @implemented NT5.2 1152 * 1153 * The ExfTryToWakePushLock attemps to wake a waiting pushlock. 1154 * 1155 * @param PushLock 1156 * Pointer to a PushLock which is in the wait state. 1157 * 1158 * @return None. 1159 * 1160 * @remarks The pushlock must be in a wait state and must not be already waking. 1161 * 1162 *--*/ 1163 VOID 1164 FASTCALL 1165 ExfTryToWakePushLock(PEX_PUSH_LOCK PushLock) 1166 { 1167 EX_PUSH_LOCK OldValue = *PushLock, NewValue; 1168 1169 /* 1170 * If the Pushlock is not waiting on anything, or if it's already waking up 1171 * and locked, don't do anything 1172 */ 1173 if ((OldValue.Waking) || (OldValue.Locked) || !(OldValue.Waiting)) return; 1174 1175 /* Make it Waking */ 1176 NewValue = OldValue; 1177 NewValue.Waking = TRUE; 1178 1179 /* Write the New Value */ 1180 if (InterlockedCompareExchangePointer(&PushLock->Ptr, 1181 NewValue.Ptr, 1182 OldValue.Ptr) == OldValue.Ptr) 1183 { 1184 /* Wake the Pushlock */ 1185 ExfWakePushLock(PushLock, NewValue); 1186 } 1187 } 1188 1189 /*++ 1190 * @name ExfUnblockPushLock 1191 * @implemented NT5.1 1192 * 1193 * The ExfUnblockPushLock routine unblocks a previously blocked PushLock. 1194 * 1195 * @param PushLock 1196 * Pointer to a previously blocked PushLock. 1197 * 1198 * @return None. 1199 * 1200 * @remarks Callers of ExfUnblockPushLock can be running at any IRQL. 1201 * 1202 *--*/ 1203 VOID 1204 FASTCALL 1205 ExfUnblockPushLock(PEX_PUSH_LOCK PushLock, 1206 PVOID CurrentWaitBlock) 1207 { 1208 PEX_PUSH_LOCK_WAIT_BLOCK WaitBlock, NextWaitBlock; 1209 KIRQL OldIrql = DISPATCH_LEVEL; 1210 1211 /* Get the wait block and erase the previous one */ 1212 WaitBlock = InterlockedExchangePointer(&PushLock->Ptr, NULL); 1213 if (WaitBlock) 1214 { 1215 /* Check if there is a linked pushlock and raise IRQL appropriately */ 1216 if (WaitBlock->Next) KeRaiseIrql(DISPATCH_LEVEL, &OldIrql); 1217 1218 /* Start block loop */ 1219 while (WaitBlock) 1220 { 1221 /* Get the next block */ 1222 NextWaitBlock = WaitBlock->Next; 1223 1224 /* Remove the wait flag from the Wait block */ 1225 if (!InterlockedBitTestAndReset(&WaitBlock->Flags, EX_PUSH_LOCK_FLAGS_WAIT_V)) 1226 { 1227 /* Nobody removed the flag before us, so signal the event */ 1228 KeSetEventBoostPriority(&WaitBlock->WakeEvent, NULL); 1229 } 1230 1231 /* Try the next one */ 1232 WaitBlock = NextWaitBlock; 1233 } 1234 1235 /* Lower IRQL if needed */ 1236 if (OldIrql != DISPATCH_LEVEL) KeLowerIrql(OldIrql); 1237 } 1238 1239 /* Check if we got a wait block that's pending */ 1240 if ((CurrentWaitBlock) && 1241 (((PEX_PUSH_LOCK_WAIT_BLOCK)CurrentWaitBlock)->Flags & 1242 EX_PUSH_LOCK_FLAGS_WAIT)) 1243 { 1244 /* Wait for the pushlock to be unblocked */ 1245 ExWaitForUnblockPushLock(PushLock, CurrentWaitBlock); 1246 } 1247 } 1248