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)
9 /* INCLUDES *****************************************************************/
15 /* DATA **********************************************************************/
17 ULONG ExPushLockSpinCount
= 0;
22 /* PRIVATE FUNCTIONS *********************************************************/
25 #define InterlockedAndPointer(ptr,val) InterlockedAnd64((PLONGLONG)ptr,(LONGLONG)val)
27 #define InterlockedAndPointer(ptr,val) InterlockedAnd((PLONG)ptr,(LONG)val)
31 * @name ExpInitializePushLocks
33 * The ExpInitializePushLocks routine initialized Pushlock support.
39 * @remarks The ExpInitializePushLocks routine sets up the spin on SMP machines.
45 ExpInitializePushLocks(VOID
)
48 /* Initialize an internal 1024-iteration spin for MP CPUs */
49 if (KeNumberProcessors
> 1)
50 ExPushLockSpinCount
= 1024;
55 * @name ExfWakePushLock
57 * The ExfWakePushLock routine wakes a Pushlock that is in the waiting
61 * Pointer to a pushlock that is waiting.
64 * Last known value of the pushlock before this routine was called.
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.
75 ExfWakePushLock(PEX_PUSH_LOCK PushLock
,
76 EX_PUSH_LOCK OldValue
)
78 EX_PUSH_LOCK NewValue
;
79 PEX_PUSH_LOCK_WAIT_BLOCK PreviousWaitBlock
, FirstWaitBlock
, LastWaitBlock
;
80 PEX_PUSH_LOCK_WAIT_BLOCK WaitBlock
;
83 /* Start main wake loop */
87 ASSERT(!OldValue
.MultipleShared
);
89 /* Check if it's locked */
90 while (OldValue
.Locked
)
92 /* It's not waking anymore */
93 NewValue
.Value
= OldValue
.Value
&~ EX_PUSH_LOCK_WAKING
;
96 ASSERT(!NewValue
.Waking
);
97 ASSERT(NewValue
.Locked
);
98 ASSERT(NewValue
.Waiting
);
100 /* Write the New Value */
101 NewValue
.Ptr
= InterlockedCompareExchangePointer(&PushLock
->Ptr
,
104 if (NewValue
.Value
== OldValue
.Value
) return;
106 /* Someone changed the value behind our back, update it*/
110 /* Save the First Block */
111 FirstWaitBlock
= (PEX_PUSH_LOCK_WAIT_BLOCK
)(OldValue
.Value
&
112 ~EX_PUSH_LOCK_PTR_BITS
);
113 WaitBlock
= FirstWaitBlock
;
115 /* Try to find the last block */
118 /* Get the last wait block */
119 LastWaitBlock
= WaitBlock
->Last
;
121 /* Check if we found it */
125 WaitBlock
= LastWaitBlock
;
129 /* Save the previous block */
130 PreviousWaitBlock
= WaitBlock
;
132 /* Move to next block */
133 WaitBlock
= WaitBlock
->Next
;
135 /* Save the previous block */
136 WaitBlock
->Previous
= PreviousWaitBlock
;
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
))
144 /* Destroy the pushlock */
146 ASSERT(!NewValue
.Waking
);
148 /* Write the New Value */
149 NewValue
.Ptr
= InterlockedCompareExchangePointer(&PushLock
->Ptr
,
152 if (NewValue
.Value
== OldValue
.Value
) break;
154 /* Someone changed the value behind our back, update it*/
159 /* Link the wait blocks */
160 FirstWaitBlock
->Last
= PreviousWaitBlock
;
161 WaitBlock
->Previous
= NULL
;
164 ASSERT(FirstWaitBlock
!= WaitBlock
);
165 ASSERT(PushLock
->Waiting
);
167 /* Remove waking bit from pushlock */
168 InterlockedAndPointer(&PushLock
->Value
, ~EX_PUSH_LOCK_WAKING
);
175 /* Check if there's a previous block */
176 OldIrql
= DISPATCH_LEVEL
;
177 if (WaitBlock
->Previous
)
179 /* Raise to Dispatch */
180 KeRaiseIrql(DISPATCH_LEVEL
, &OldIrql
);
186 /* Get the previous Wait block */
187 PreviousWaitBlock
= WaitBlock
->Previous
;
190 ASSERT(!WaitBlock
->Signaled
);
193 /* We are about to get signaled */
194 WaitBlock
->Signaled
= TRUE
;
197 /* Set the Wait Bit in the Wait Block */
198 if (!InterlockedBitTestAndReset(&WaitBlock
->Flags
, 1))
200 /* Nobody signaled us, so do it */
201 KeSignalGateBoostPriority(&WaitBlock
->WakeGate
);
204 /* Set the wait block and check if there still is one to loop*/
205 WaitBlock
= PreviousWaitBlock
;
206 if (!WaitBlock
) break;
209 /* Check if we have to lower back the IRQL */
210 if (OldIrql
!= DISPATCH_LEVEL
) KeLowerIrql(OldIrql
);
214 * @name ExpOptimizePushLockList
216 * The ExpOptimizePushLockList routine optimizes the list of waiters
217 * associated to a pushlock's wait block.
220 * Pointer to a pushlock whose waiter list needs to be optimized.
223 * Last known value of the pushlock before this routine was called.
227 * @remarks At the end of the optimization, the pushlock will also be wakened.
232 ExpOptimizePushLockList(PEX_PUSH_LOCK PushLock
,
233 EX_PUSH_LOCK OldValue
)
235 PEX_PUSH_LOCK_WAIT_BLOCK WaitBlock
, LastWaitBlock
, PreviousWaitBlock
, FirstWaitBlock
;
236 EX_PUSH_LOCK NewValue
;
238 /* Start main loop */
241 /* Check if we've been unlocked */
242 if (!OldValue
.Locked
)
244 /* Wake us up and leave */
245 ExfWakePushLock(PushLock
, OldValue
);
249 /* Get the wait block */
250 WaitBlock
= (PEX_PUSH_LOCK_WAIT_BLOCK
)(OldValue
.Value
&
251 ~EX_PUSH_LOCK_PTR_BITS
);
253 /* Loop the blocks */
254 FirstWaitBlock
= WaitBlock
;
257 /* Get the last wait block */
258 LastWaitBlock
= WaitBlock
->Last
;
261 /* Set this as the new last block, we're done */
262 FirstWaitBlock
->Last
= LastWaitBlock
;
267 PreviousWaitBlock
= WaitBlock
;
269 /* Get the next block */
270 WaitBlock
= WaitBlock
->Next
;
272 /* Save the previous */
273 WaitBlock
->Previous
= PreviousWaitBlock
;
276 /* Remove the wake bit */
277 NewValue
.Value
= OldValue
.Value
&~ EX_PUSH_LOCK_WAKING
;
280 ASSERT(NewValue
.Locked
);
281 ASSERT(!NewValue
.Waking
);
283 /* Update the value */
284 NewValue
.Ptr
= InterlockedCompareExchangePointer(&PushLock
->Ptr
,
288 /* If we updated correctly, leave */
289 if (NewValue
.Value
== OldValue
.Value
) break;
297 * @name ExTimedWaitForUnblockPushLock
299 * The ExTimedWaitForUnblockPushLock routine waits for a pushlock
300 * to be unblocked, for a specified internal.
303 * Pointer to a pushlock whose waiter list needs to be optimized.
306 * Pointer to the pushlock's wait block.
309 * Amount of time to wait for this pushlock to be unblocked.
311 * @return STATUS_SUCCESS is the pushlock is now unblocked, otherwise the error
312 * code returned by KeWaitForSingleObject.
314 * @remarks If the wait fails, then a manual unblock is attempted.
319 ExTimedWaitForUnblockPushLock(IN PEX_PUSH_LOCK PushLock
,
321 IN PLARGE_INTEGER Timeout
)
325 /* Initialize the wait event */
326 KeInitializeEvent(&((PEX_PUSH_LOCK_WAIT_BLOCK
)WaitBlock
)->WakeEvent
,
327 SynchronizationEvent
,
331 /* Spin on the push lock if necessary */
332 if (ExPushLockSpinCount
)
334 ULONG i
= ExPushLockSpinCount
;
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
;
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
))
351 /* Nobody removed it already, let's do a full wait */
352 Status
= KeWaitForSingleObject(&((PEX_PUSH_LOCK_WAIT_BLOCK
)WaitBlock
)->
358 /* Check if the wait was satisfied */
359 if (Status
!= STATUS_SUCCESS
)
361 /* Try unblocking the pushlock if it was not */
362 ExfUnblockPushLock(PushLock
, WaitBlock
);
367 /* Someone beat us to it, no need to wait */
368 Status
= STATUS_SUCCESS
;
376 * @name ExWaitForUnblockPushLock
378 * The ExWaitForUnblockPushLock routine waits for a pushlock
379 * to be unblocked, for a specified internal.
382 * Pointer to a pushlock whose waiter list needs to be optimized.
385 * Pointer to the pushlock's wait block.
387 * @return STATUS_SUCCESS is the pushlock is now unblocked, otherwise the error
388 * code returned by KeWaitForSingleObject.
390 * @remarks If the wait fails, then a manual unblock is attempted.
395 ExWaitForUnblockPushLock(IN PEX_PUSH_LOCK PushLock
,
398 /* Call the timed function with no timeout */
399 ExTimedWaitForUnblockPushLock(PushLock
, WaitBlock
, NULL
);
403 * @name ExBlockPushLock
405 * The ExBlockPushLock routine blocks a pushlock.
408 * Pointer to a pushlock whose waiter list needs to be optimized.
411 * Pointer to the pushlock's wait block.
420 ExBlockPushLock(PEX_PUSH_LOCK PushLock
,
423 PEX_PUSH_LOCK_WAIT_BLOCK WaitBlock
= pWaitBlock
;
424 EX_PUSH_LOCK NewValue
, OldValue
;
426 /* Detect invalid wait block alignment */
427 ASSERT(((ULONG_PTR
)pWaitBlock
& 0xF) == 0);
429 /* Set the waiting bit */
430 WaitBlock
->Flags
= EX_PUSH_LOCK_FLAGS_WAIT
;
432 /* Get the old value */
433 OldValue
= *PushLock
;
435 /* Start block loop */
438 /* Link the wait blocks */
439 WaitBlock
->Next
= OldValue
.Ptr
;
441 /* Set the new wait block value */
442 NewValue
.Ptr
= InterlockedCompareExchangePointer(&PushLock
->Ptr
,
445 if (OldValue
.Ptr
== NewValue
.Ptr
) break;
447 /* Try again with the new value */
452 /* PUBLIC FUNCTIONS **********************************************************/
455 * @name ExAcquirePushLockExclusive
458 * The ExAcquirePushLockExclusive macro exclusively acquires a PushLock.
461 * Pointer to the pushlock which is to be acquired.
465 * @remarks Callers of ExAcquirePushLockShared must be running at IRQL <= APC_LEVEL.
466 * This macro should usually be paired up with KeAcquireCriticalRegion.
471 ExfAcquirePushLockExclusive(PEX_PUSH_LOCK PushLock
)
473 EX_PUSH_LOCK OldValue
= *PushLock
, NewValue
, TempValue
;
475 EX_PUSH_LOCK_WAIT_BLOCK Block
;
476 PEX_PUSH_LOCK_WAIT_BLOCK WaitBlock
= &Block
;
478 /* Start main loop */
481 /* Check if it's unlocked */
482 if (!OldValue
.Locked
)
485 NewValue
.Value
= OldValue
.Value
| EX_PUSH_LOCK_LOCK
;
486 ASSERT(NewValue
.Locked
);
488 /* Set the new value */
489 if (InterlockedCompareExchangePointer(&PushLock
->Ptr
,
491 OldValue
.Ptr
) != OldValue
.Ptr
)
494 OldValue
= *PushLock
;
498 /* Break out of the loop */
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
;
509 /* Check if there is already a waiter */
510 if (OldValue
.Waiting
)
512 /* Nobody is the last waiter yet */
513 WaitBlock
->Last
= NULL
;
515 /* We are an exclusive waiter */
516 WaitBlock
->ShareCount
= 0;
518 /* Set the current Wait Block pointer */
519 WaitBlock
->Next
= (PEX_PUSH_LOCK_WAIT_BLOCK
)(
520 OldValue
.Value
&~ EX_PUSH_LOCK_PTR_BITS
);
523 NewValue
.Value
= (OldValue
.Value
& EX_PUSH_LOCK_MULTIPLE_SHARED
) |
525 EX_PUSH_LOCK_WAKING
|
526 EX_PUSH_LOCK_WAITING
|
527 (ULONG_PTR
)WaitBlock
;
529 /* Check if the pushlock was already waking */
530 if (!OldValue
.Waking
) NeedWake
= TRUE
;
534 /* We are the first waiter, so loop the wait block */
535 WaitBlock
->Last
= WaitBlock
;
537 /* Set the share count */
538 WaitBlock
->ShareCount
= (LONG
)OldValue
.Shared
;
540 /* Check if someone is sharing this pushlock */
541 if (OldValue
.Shared
> 1)
543 /* Point to our wait block */
544 NewValue
.Value
= EX_PUSH_LOCK_MULTIPLE_SHARED
|
546 EX_PUSH_LOCK_WAITING
|
547 (ULONG_PTR
)WaitBlock
;
551 /* No shared count */
552 WaitBlock
->ShareCount
= 0;
554 /* Point to our wait block */
555 NewValue
.Value
= EX_PUSH_LOCK_LOCK
|
556 EX_PUSH_LOCK_WAITING
|
557 (ULONG_PTR
)WaitBlock
;
562 /* Setup the Debug Wait Block */
563 WaitBlock
->Signaled
= 0;
564 WaitBlock
->OldValue
= OldValue
;
565 WaitBlock
->NewValue
= NewValue
;
566 WaitBlock
->PushLock
= PushLock
;
570 ASSERT(NewValue
.Waiting
);
571 ASSERT(NewValue
.Locked
);
573 /* Write the new value */
574 TempValue
= NewValue
;
575 NewValue
.Ptr
= InterlockedCompareExchangePointer(&PushLock
->Ptr
,
578 if (NewValue
.Value
!= OldValue
.Value
)
581 OldValue
= *PushLock
;
585 /* Check if the pushlock needed waking */
588 /* Scan the Waiters and Wake PushLocks */
589 ExpOptimizePushLockList(PushLock
, TempValue
);
592 /* Set up the Wait Gate */
593 KeInitializeGate(&WaitBlock
->WakeGate
);
596 /* Now spin on the push lock if necessary */
597 if (ExPushLockSpinCount
)
599 ULONG i
= ExPushLockSpinCount
;
603 if (!(*(volatile LONG
*)&WaitBlock
->Flags
& EX_PUSH_LOCK_WAITING
))
611 /* Now try to remove the wait bit */
612 if (InterlockedBitTestAndReset(&WaitBlock
->Flags
, 1))
614 /* Nobody removed it already, let's do a full wait */
615 KeWaitForGate(&WaitBlock
->WakeGate
, WrPushLock
, KernelMode
);
616 ASSERT(WaitBlock
->Signaled
);
619 /* We shouldn't be shared anymore */
620 ASSERT((WaitBlock
->ShareCount
== 0));
629 * @name ExAcquirePushLockShared
632 * The ExAcquirePushLockShared routine acquires a shared PushLock.
635 * Pointer to the pushlock which is to be acquired.
639 * @remarks Callers of ExAcquirePushLockShared must be running at IRQL <= APC_LEVEL.
640 * This macro should usually be paired up with KeAcquireCriticalRegion.
645 ExfAcquirePushLockShared(PEX_PUSH_LOCK PushLock
)
647 EX_PUSH_LOCK OldValue
= *PushLock
, NewValue
;
649 EX_PUSH_LOCK_WAIT_BLOCK Block
;
650 PEX_PUSH_LOCK_WAIT_BLOCK WaitBlock
= &Block
;
652 /* Start main loop */
655 /* Check if it's unlocked or if it's waiting without any sharers */
656 if (!(OldValue
.Locked
) || (!(OldValue
.Waiting
) && (OldValue
.Shared
> 0)))
658 /* Check if anyone is waiting on it */
659 if (!OldValue
.Waiting
)
661 /* Increase the share count and lock it */
662 NewValue
.Value
= OldValue
.Value
| EX_PUSH_LOCK_LOCK
;
667 /* Simply set the lock bit */
668 NewValue
.Value
= OldValue
.Value
| EX_PUSH_LOCK_LOCK
;
672 ASSERT(NewValue
.Locked
);
674 /* Set the new value */
675 NewValue
.Ptr
= InterlockedCompareExchangePointer(&PushLock
->Ptr
,
678 if (NewValue
.Value
!= OldValue
.Value
)
681 OldValue
= *PushLock
;
685 /* Break out of the loop */
690 /* We'll have to create a Waitblock */
691 WaitBlock
->Flags
= EX_PUSH_LOCK_FLAGS_WAIT
;
692 WaitBlock
->ShareCount
= 0;
694 WaitBlock
->Previous
= NULL
;
696 /* Check if there is already a waiter */
697 if (OldValue
.Waiting
)
699 /* Set the current Wait Block pointer */
700 WaitBlock
->Next
= (PEX_PUSH_LOCK_WAIT_BLOCK
)(
701 OldValue
.Value
&~ EX_PUSH_LOCK_PTR_BITS
);
703 /* Nobody is the last waiter yet */
704 WaitBlock
->Last
= NULL
;
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
;
713 /* Check if the pushlock was already waking */
714 if (!OldValue
.Waking
) NeedWake
= TRUE
;
718 /* We are the first waiter, so loop the wait block */
719 WaitBlock
->Last
= WaitBlock
;
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
;
728 ASSERT(NewValue
.Waiting
);
731 /* Setup the Debug Wait Block */
732 WaitBlock
->Signaled
= 0;
733 WaitBlock
->OldValue
= OldValue
;
734 WaitBlock
->NewValue
= NewValue
;
735 WaitBlock
->PushLock
= PushLock
;
738 /* Write the new value */
739 NewValue
.Ptr
= InterlockedCompareExchangePointer(&PushLock
->Ptr
,
742 if (NewValue
.Ptr
!= OldValue
.Ptr
)
745 OldValue
= *PushLock
;
749 /* Update the value now */
752 /* Check if the pushlock needed waking */
755 /* Scan the Waiters and Wake PushLocks */
756 ExpOptimizePushLockList(PushLock
, OldValue
);
759 /* Set up the Wait Gate */
760 KeInitializeGate(&WaitBlock
->WakeGate
);
763 /* Now spin on the push lock if necessary */
764 if (ExPushLockSpinCount
)
766 ULONG i
= ExPushLockSpinCount
;
770 if (!(*(volatile LONG
*)&WaitBlock
->Flags
& EX_PUSH_LOCK_WAITING
))
778 /* Now try to remove the wait bit */
779 if (InterlockedBitTestAndReset(&WaitBlock
->Flags
, 1))
781 /* Fast-path did not work, we need to do a full wait */
782 KeWaitForGate(&WaitBlock
->WakeGate
, WrPushLock
, KernelMode
);
783 ASSERT(WaitBlock
->Signaled
);
786 /* We shouldn't be shared anymore */
787 ASSERT((WaitBlock
->ShareCount
== 0));
793 * @name ExfReleasePushLock
796 * The ExReleasePushLock routine releases a previously acquired PushLock.
800 * Pointer to a previously acquired pushlock.
804 * @remarks Callers of ExfReleasePushLock must be running at IRQL <= APC_LEVEL.
805 * This macro should usually be paired up with KeLeaveCriticalRegion.
810 ExfReleasePushLock(PEX_PUSH_LOCK PushLock
)
812 EX_PUSH_LOCK OldValue
= *PushLock
, NewValue
, WakeValue
;
813 PEX_PUSH_LOCK_WAIT_BLOCK WaitBlock
, LastWaitBlock
;
816 ASSERT(OldValue
.Locked
);
818 /* Start main loop */
821 /* Check if someone is waiting on the lock */
822 if (!OldValue
.Waiting
)
824 /* Check if it's shared */
825 if (OldValue
.Shared
> 1)
827 /* Write the Old Value but decrease share count */
833 /* Simply clear the lock */
837 /* Write the New Value */
838 NewValue
.Ptr
= InterlockedCompareExchangePointer(&PushLock
->Ptr
,
841 if (NewValue
.Value
== OldValue
.Value
) return;
843 /* Did it enter a wait state? */
848 /* Ok, we do know someone is waiting on it. Are there more then one? */
849 if (OldValue
.MultipleShared
)
851 /* Get the wait block */
852 WaitBlock
= (PEX_PUSH_LOCK_WAIT_BLOCK
)(OldValue
.Value
&
853 ~EX_PUSH_LOCK_PTR_BITS
);
855 /* Loop until we find the last wait block */
858 /* Get the last wait block */
859 LastWaitBlock
= WaitBlock
->Last
;
865 WaitBlock
= LastWaitBlock
;
870 WaitBlock
= WaitBlock
->Next
;
873 /* Make sure the Share Count is above 0 */
874 if (WaitBlock
->ShareCount
> 0)
876 /* This shouldn't be an exclusive wait block */
877 ASSERT(WaitBlock
->Flags
& EX_PUSH_LOCK_FLAGS_EXCLUSIVE
);
879 /* Do the decrease and check if the lock isn't shared anymore */
880 if (InterlockedDecrement(&WaitBlock
->ShareCount
) > 0) return;
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.
893 /* Now we need to see if it's waking */
896 /* Remove the lock and multiple shared bits */
897 NewValue
.Value
= OldValue
.Value
;
898 NewValue
.MultipleShared
= FALSE
;
899 NewValue
.Locked
= FALSE
;
902 ASSERT(NewValue
.Waking
&& !NewValue
.Locked
&& !NewValue
.MultipleShared
);
904 /* Write the new value */
905 NewValue
.Ptr
= InterlockedCompareExchangePointer(&PushLock
->Ptr
,
908 if (NewValue
.Value
== OldValue
.Value
) return;
912 /* Remove the lock and multiple shared bits */
913 NewValue
.Value
= OldValue
.Value
;
914 NewValue
.MultipleShared
= FALSE
;
915 NewValue
.Locked
= FALSE
;
917 /* It's not already waking, so add the wake bit */
918 NewValue
.Waking
= TRUE
;
921 ASSERT(NewValue
.Waking
&& !NewValue
.Locked
&& !NewValue
.MultipleShared
);
923 /* Write the new value */
924 WakeValue
= NewValue
;
925 NewValue
.Ptr
= InterlockedCompareExchangePointer(&PushLock
->Ptr
,
928 if (NewValue
.Value
!= OldValue
.Value
) continue;
930 /* The write was successful. The pushlock is Unlocked and Waking */
931 ExfWakePushLock(PushLock
, WakeValue
);
940 * @name ExfReleasePushLockShared
943 * The ExfReleasePushLockShared macro releases a previously acquired PushLock.
946 * Pointer to a previously acquired pushlock.
950 * @remarks Callers of ExReleasePushLockShared must be running at IRQL <= APC_LEVEL.
951 * This macro should usually be paired up with KeLeaveCriticalRegion.
956 ExfReleasePushLockShared(PEX_PUSH_LOCK PushLock
)
958 EX_PUSH_LOCK OldValue
= *PushLock
, NewValue
, WakeValue
;
959 PEX_PUSH_LOCK_WAIT_BLOCK WaitBlock
, LastWaitBlock
;
961 /* Check if someone is waiting on the lock */
962 while (!OldValue
.Waiting
)
964 /* Check if it's shared */
965 if (OldValue
.Shared
> 1)
967 /* Write the Old Value but decrease share count */
973 /* Simply clear the lock */
977 /* Write the New Value */
978 NewValue
.Ptr
= InterlockedCompareExchangePointer(&PushLock
->Ptr
,
981 if (NewValue
.Value
== OldValue
.Value
) return;
983 /* Did it enter a wait state? */
987 /* Ok, we do know someone is waiting on it. Are there more then one? */
988 if (OldValue
.MultipleShared
)
990 /* Get the wait block */
991 WaitBlock
= (PEX_PUSH_LOCK_WAIT_BLOCK
)(OldValue
.Value
&
992 ~EX_PUSH_LOCK_PTR_BITS
);
994 /* Loop until we find the last wait block */
997 /* Get the last wait block */
998 LastWaitBlock
= WaitBlock
->Last
;
1004 WaitBlock
= LastWaitBlock
;
1008 /* Keep searching */
1009 WaitBlock
= WaitBlock
->Next
;
1013 ASSERT(WaitBlock
->ShareCount
> 0);
1014 ASSERT(WaitBlock
->Flags
& EX_PUSH_LOCK_FLAGS_EXCLUSIVE
);
1016 /* Do the decrease and check if the lock isn't shared anymore */
1017 if (InterlockedDecrement(&WaitBlock
->ShareCount
) > 0) return;
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.
1029 /* Now we need to see if it's waking */
1030 if (OldValue
.Waking
)
1032 /* Remove the lock and multiple shared bits */
1033 NewValue
.Value
= OldValue
.Value
;
1034 NewValue
.MultipleShared
= FALSE
;
1035 NewValue
.Locked
= FALSE
;
1038 ASSERT(NewValue
.Waking
&& !NewValue
.Locked
&& !NewValue
.MultipleShared
);
1040 /* Write the new value */
1041 NewValue
.Ptr
= InterlockedCompareExchangePointer(&PushLock
->Ptr
,
1044 if (NewValue
.Value
== OldValue
.Value
) return;
1048 /* Remove the lock and multiple shared bits */
1049 NewValue
.Value
= OldValue
.Value
;
1050 NewValue
.MultipleShared
= FALSE
;
1051 NewValue
.Locked
= FALSE
;
1053 /* It's not already waking, so add the wake bit */
1054 NewValue
.Waking
= TRUE
;
1057 ASSERT(NewValue
.Waking
&& !NewValue
.Locked
&& !NewValue
.MultipleShared
);
1059 /* Write the new value */
1060 WakeValue
= NewValue
;
1061 NewValue
.Ptr
= InterlockedCompareExchangePointer(&PushLock
->Ptr
,
1064 if (NewValue
.Value
!= OldValue
.Value
) continue;
1066 /* The write was successful. The pushlock is Unlocked and Waking */
1067 ExfWakePushLock(PushLock
, WakeValue
);
1074 * ExfReleasePushLockExclusive
1075 * @implemented NT5.2
1077 * The ExfReleasePushLockExclusive routine releases a previously
1078 * exclusively acquired PushLock.
1081 * Pointer to a previously acquired pushlock.
1085 * @remarks Callers of ExReleasePushLockExclusive must be running at IRQL <= APC_LEVEL.
1086 * This macro should usually be paired up with KeLeaveCriticalRegion.
1091 ExfReleasePushLockExclusive(PEX_PUSH_LOCK PushLock
)
1093 EX_PUSH_LOCK NewValue
, WakeValue
;
1094 EX_PUSH_LOCK OldValue
= *PushLock
;
1096 /* Loop until we can change */
1100 ASSERT(OldValue
.Locked
);
1101 ASSERT(OldValue
.Waiting
|| OldValue
.Shared
== 0);
1103 /* Check if it's waiting and not yet waking */
1104 if ((OldValue
.Waiting
) && !(OldValue
.Waking
))
1106 /* Remove the lock bit, and add the wake bit */
1107 NewValue
.Value
= (OldValue
.Value
&~ EX_PUSH_LOCK_LOCK
) |
1108 EX_PUSH_LOCK_WAKING
;
1111 ASSERT(NewValue
.Waking
&& !NewValue
.Locked
);
1113 /* Write the New Value. Save our original value for waking */
1114 WakeValue
= NewValue
;
1115 NewValue
.Ptr
= InterlockedCompareExchangePointer(&PushLock
->Ptr
,
1119 /* Check if the value changed behind our back */
1120 if (NewValue
.Value
== OldValue
.Value
)
1122 /* Wake the Pushlock */
1123 ExfWakePushLock(PushLock
, WakeValue
);
1129 /* A simple unlock */
1130 NewValue
.Value
= OldValue
.Value
&~ EX_PUSH_LOCK_LOCK
;
1133 ASSERT(NewValue
.Waking
|| !NewValue
.Waiting
);
1135 /* Write the New Value */
1136 NewValue
.Ptr
= InterlockedCompareExchangePointer(&PushLock
->Ptr
,
1140 /* Check if the value changed behind our back */
1141 if (NewValue
.Value
== OldValue
.Value
) break;
1145 OldValue
= NewValue
;
1150 * @name ExfTryToWakePushLock
1151 * @implemented NT5.2
1153 * The ExfTryToWakePushLock attemps to wake a waiting pushlock.
1156 * Pointer to a PushLock which is in the wait state.
1160 * @remarks The pushlock must be in a wait state and must not be already waking.
1165 ExfTryToWakePushLock(PEX_PUSH_LOCK PushLock
)
1167 EX_PUSH_LOCK OldValue
= *PushLock
, NewValue
;
1170 * If the Pushlock is not waiting on anything, or if it's already waking up
1171 * and locked, don't do anything
1173 if ((OldValue
.Waking
) || (OldValue
.Locked
) || !(OldValue
.Waiting
)) return;
1175 /* Make it Waking */
1176 NewValue
= OldValue
;
1177 NewValue
.Waking
= TRUE
;
1179 /* Write the New Value */
1180 if (InterlockedCompareExchangePointer(&PushLock
->Ptr
,
1182 OldValue
.Ptr
) == OldValue
.Ptr
)
1184 /* Wake the Pushlock */
1185 ExfWakePushLock(PushLock
, NewValue
);
1190 * @name ExfUnblockPushLock
1191 * @implemented NT5.1
1193 * The ExfUnblockPushLock routine unblocks a previously blocked PushLock.
1196 * Pointer to a previously blocked PushLock.
1200 * @remarks Callers of ExfUnblockPushLock can be running at any IRQL.
1205 ExfUnblockPushLock(PEX_PUSH_LOCK PushLock
,
1206 PVOID CurrentWaitBlock
)
1208 PEX_PUSH_LOCK_WAIT_BLOCK WaitBlock
, NextWaitBlock
;
1209 KIRQL OldIrql
= DISPATCH_LEVEL
;
1211 /* Get the wait block and erase the previous one */
1212 WaitBlock
= InterlockedExchangePointer(&PushLock
->Ptr
, NULL
);
1215 /* Check if there is a linked pushlock and raise IRQL appropriately */
1216 if (WaitBlock
->Next
) KeRaiseIrql(DISPATCH_LEVEL
, &OldIrql
);
1218 /* Start block loop */
1221 /* Get the next block */
1222 NextWaitBlock
= WaitBlock
->Next
;
1224 /* Remove the wait flag from the Wait block */
1225 if (!InterlockedBitTestAndReset(&WaitBlock
->Flags
, EX_PUSH_LOCK_FLAGS_WAIT_V
))
1227 /* Nobody removed the flag before us, so signal the event */
1228 KeSetEventBoostPriority(&WaitBlock
->WakeEvent
, NULL
);
1231 /* Try the next one */
1232 WaitBlock
= NextWaitBlock
;
1235 /* Lower IRQL if needed */
1236 if (OldIrql
!= DISPATCH_LEVEL
) KeLowerIrql(OldIrql
);
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
))
1244 /* Wait for the pushlock to be unblocked */
1245 ExWaitForUnblockPushLock(PushLock
, CurrentWaitBlock
);