diff options
| author | Dave Wilson <[email protected]> | 2015-03-17 19:50:07 -0700 |
|---|---|---|
| committer | Dave Wilson <[email protected]> | 2015-03-17 19:50:07 -0700 |
| commit | 97cf5197cf5b882b2c689d8dc2b555f2edf8f418 (patch) | |
| tree | 46f3701832d70b420eb0fc0eb93261f9da45db3f /storage/class/classpnp/src/lock.c | |
| parent | ef1905bf1e8825bb31120dfb27e0daf3154d859a (diff) | |
Initial publish
Diffstat (limited to 'storage/class/classpnp/src/lock.c')
| -rw-r--r-- | storage/class/classpnp/src/lock.c | 550 |
1 files changed, 550 insertions, 0 deletions
diff --git a/storage/class/classpnp/src/lock.c b/storage/class/classpnp/src/lock.c new file mode 100644 index 00000000..1b5a8862 --- /dev/null +++ b/storage/class/classpnp/src/lock.c @@ -0,0 +1,550 @@ +/*++ + +Copyright (C) Microsoft Corporation, 1990 - 1998 + +Module Name: + + lock.c + +Abstract: + + This is the NT SCSI port driver. + +Environment: + + kernel mode only + +Notes: + + This module is a driver dll for scsi miniports. + +Revision History: + +--*/ + +#include "classp.h" +#include "debug.h" + +#ifdef DEBUG_USE_WPP +#include "lock.tmh" +#endif + + +LONG LockHighWatermark = 0; +LONG LockLowWatermark = 0; +LONG MaxLockedMinutes = 5; + +// +// Structure used for tracking remove lock allocations in checked builds +// +typedef struct _REMOVE_TRACKING_BLOCK { + PVOID Tag; + LARGE_INTEGER TimeLocked; + PCSTR File; + ULONG Line; +} REMOVE_TRACKING_BLOCK, *PREMOVE_TRACKING_BLOCK; + +/*++//////////////////////////////////////////////////////////////////////////// + +Classpnp RemoveLockRundown + +RemoveLockRundown is a cacheaware rundown protection for the classpnp device object. While this +rundown protection is held successfully, the caller can assume that no pending pnp REMOVE +requests will be completed. + +The RemoveLockRundown is a replacement of the original RemoveLock to improve the scalability. +For backward compatibility, we still keep the RemoveLock field in the device common extension structure. +However, the old RemoveLock is only being used in the DBG build. + +The usage of the RemoveLockRundown is slightly different from the normal rundown protection usage. +The RemoveLockRundown is acquired via ClassAcquireRemoveLockEx() function +and released via ClassReleaseRemoveLock() function. Usually, we bail out when the acquisition +of rundown protection fails (calls to ExAcquireRundownProtectionCacheAware returns FALSE) and +will not release the rundown protection in acquisition failure. For the RemoveLockRundown, +the caller will always call ClassAcquireRemoveLockEx() and ClassReleaseRemoveLock() in a pair no +matter the return value of ClassAcquireRemoveLockEx(). Therefore, a thread may still call +ClassReleaseRemoveLock() even the previous acquisition RemoveLockRundown protection failed. + +To deal with the previous acquisition failure case, we introduced a new field RemoveLockFailAcquire +as a counter for rundown acquisition failures. In the ClassReleaseRemoveLock() function, we only +release the rundown protection when this counter is decremented to zero. Since the change of RemoveLockFailAcquire +and release rundown protection is not protected by a lock as an atomic operation, we use a while loop over +InterlockedCompareExchange operation to make sure when we release the rundown protection, this counter is +actually zero. + +--*/ + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassAcquireRemoveLockEx() + +Routine Description: + + This routine is called to acquire the remove lock on the device object. + While the lock is held, the caller can assume that no pending pnp REMOVE + requests will be completed. + + The lock should be acquired immediately upon entering a dispatch routine. + It should also be acquired before creating any new reference to the + device object if there's a chance of releasing the reference before the + new one is done. + + This routine will return TRUE if the lock was successfully acquired or + FALSE if it cannot be because the device object has already been removed. + +Arguments: + + DeviceObject - the device object to lock + + Tag - Used for tracking lock allocation and release. If an irp is + specified when acquiring the lock then the same Tag must be + used to release the lock before the Tag is completed. + +Return Value: + + The value of the IsRemoved flag in the device extension. If this is + non-zero then the device object has received a Remove irp and non-cleanup + IRP's should fail. + + If the value is REMOVE_COMPLETE, the caller should not even release the + lock. + +--*/ +ULONG +ClassAcquireRemoveLockEx( + _In_ PDEVICE_OBJECT DeviceObject, + _In_ PVOID Tag, + _In_ PCSTR File, + _In_ ULONG Line + ) +// This function implements the acquisition of Tag +#pragma warning(suppress:28104) +{ + PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension; + BOOLEAN rundownAcquired; + PEX_RUNDOWN_REF_CACHE_AWARE removeLockRundown = NULL; + + // + // Grab the remove lock + // + + #if DBG + + LONG lockValue; + + lockValue = InterlockedIncrement(&commonExtension->RemoveLock); + + + TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_LOCK, "ClassAcquireRemoveLock: " + "Acquired for Object %p & irp %p - count is %d\n", + DeviceObject, Tag, lockValue)); + + NT_ASSERTMSG("ClassAcquireRemoveLock - lock value was negative : ", + (lockValue > 0)); + + NT_ASSERTMSG("RemoveLock increased to meet LockHighWatermark", + ((LockHighWatermark == 0) || + (lockValue != LockHighWatermark))); + + if (commonExtension->IsRemoved != REMOVE_COMPLETE) { + PRTL_GENERIC_TABLE removeTrackingList = NULL; + REMOVE_TRACKING_BLOCK trackingBlock; + PREMOVE_TRACKING_BLOCK insertedTrackingBlock = NULL; + BOOLEAN newElement = FALSE; + + KIRQL oldIrql; + + trackingBlock.Tag = Tag; + + trackingBlock.File = File; + trackingBlock.Line = Line; + + KeQueryTickCount((&trackingBlock.TimeLocked)); + + KeAcquireSpinLock(&commonExtension->RemoveTrackingSpinlock, + &oldIrql); + + removeTrackingList = commonExtension->RemoveTrackingList; + + if (removeTrackingList != NULL) { + insertedTrackingBlock = RtlInsertElementGenericTable(removeTrackingList, + &trackingBlock, + sizeof(REMOVE_TRACKING_BLOCK), + &newElement); + } + + if (insertedTrackingBlock != NULL) { + if (!newElement) { + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_LOCK, ">>>>>ClassAcquireRemoveLock: " + "already tracking Tag %p\n", Tag)); + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_LOCK, ">>>>>ClassAcquireRemoveLock: " + "acquired in file %s on line %d\n", + insertedTrackingBlock->File, insertedTrackingBlock->Line)); +// NT_ASSERT(FALSE); + + } + } else { + commonExtension->RemoveTrackingUntrackedCount++; + + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_LOCK, ">>>>>ClassAcquireRemoveLock: " + "Cannot track Tag %p - currently %d untracked requsts\n", + Tag, commonExtension->RemoveTrackingUntrackedCount)); + } + + KeReleaseSpinLock(&commonExtension->RemoveTrackingSpinlock, oldIrql); + } + #else + + UNREFERENCED_PARAMETER(Tag); + UNREFERENCED_PARAMETER(File); + UNREFERENCED_PARAMETER(Line); + + #endif + + removeLockRundown = (PEX_RUNDOWN_REF_CACHE_AWARE) + ((PCHAR)commonExtension->PrivateCommonData + sizeof(CLASS_PRIVATE_COMMON_DATA)); + rundownAcquired = ExAcquireRundownProtectionCacheAware(removeLockRundown); + if (!rundownAcquired) { + InterlockedIncrement((volatile LONG*) &(commonExtension->PrivateCommonData->RemoveLockFailAcquire)); + TracePrint((TRACE_LEVEL_VERBOSE, + TRACE_FLAG_LOCK, + "ClassAcquireRemoveLockEx: RemoveLockRundown acquisition failed" + "RemoveLockFailAcquire = %d\n", + commonExtension->PrivateCommonData->RemoveLockFailAcquire)); + } + + return (commonExtension->IsRemoved); +} + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassReleaseRemoveLock() + +Routine Description: + + This routine is called to release the remove lock on the device object. It + must be called when finished using a previously locked reference to the + device object. If an Tag was specified when acquiring the lock then the + same Tag must be specified when releasing the lock. + + When the lock count reduces to zero, this routine will signal the waiting + remove Tag to delete the device object. As a result the DeviceObject + pointer should not be used again once the lock has been released. + +Arguments: + + DeviceObject - the device object to lock + + Tag - The irp (if any) specified when acquiring the lock. This is used + for lock tracking purposes + +Return Value: + + none + +--*/ +VOID +ClassReleaseRemoveLock( + _In_ PDEVICE_OBJECT DeviceObject, + _In_opt_ PIRP Tag + ) +// This function implements the release of Tag +#pragma warning(suppress:28103) +{ + PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension; + LONG lockValue; + LONG oldValue; + PEX_RUNDOWN_REF_CACHE_AWARE removeLockRundown = NULL; + + #if DBG + PRTL_GENERIC_TABLE removeTrackingList = NULL; + REMOVE_TRACKING_BLOCK searchDataBlock; + + BOOLEAN found = FALSE; + + BOOLEAN isRemoved = (commonExtension->IsRemoved == REMOVE_COMPLETE); + + KIRQL oldIrql; + + if (isRemoved) { + TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_LOCK, "ClassReleaseRemoveLock: REMOVE_COMPLETE set; this should never happen")); + InterlockedDecrement(&(commonExtension->RemoveLock)); + return; + } + + KeAcquireSpinLock(&commonExtension->RemoveTrackingSpinlock, + &oldIrql); + + removeTrackingList = commonExtension->RemoveTrackingList; + + if (removeTrackingList != NULL) { + searchDataBlock.Tag = Tag; + found = RtlDeleteElementGenericTable(removeTrackingList, &searchDataBlock); + } + + if (!found) { + if(commonExtension->RemoveTrackingUntrackedCount == 0) { + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_LOCK, ">>>>>ClassReleaseRemoveLock: " + "Couldn't find Tag %p in the lock tracking list\n", Tag)); + // + // This might happen if the device is being removed and the tracking list + // has already been freed. Don't assert if that is the case. + // + NT_ASSERT((removeTrackingList == NULL) && (commonExtension->IsRemoved != NO_REMOVE)); + } else { + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_LOCK, ">>>>>ClassReleaseRemoveLock: " + "Couldn't find Tag %p in the lock tracking list - " + "may be one of the %d untracked requests still outstanding\n", + Tag, commonExtension->RemoveTrackingUntrackedCount)); + + commonExtension->RemoveTrackingUntrackedCount--; + NT_ASSERT(commonExtension->RemoveTrackingUntrackedCount >= 0); + } + } + + KeReleaseSpinLock(&commonExtension->RemoveTrackingSpinlock, + oldIrql); + + lockValue = InterlockedDecrement(&commonExtension->RemoveLock); + + TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_LOCK, "ClassReleaseRemoveLock: " + "Released for Object %p & irp %p - count is %d\n", + DeviceObject, Tag, lockValue)); + + NT_ASSERT(lockValue >= 0); + + NT_ASSERTMSG("RemoveLock decreased to meet LockLowWatermark", + ((LockLowWatermark == 0) || !(lockValue == LockLowWatermark))); + + if (lockValue == 0) { + + NT_ASSERT(commonExtension->IsRemoved); + + // + // The device needs to be removed. Signal the remove event + // that it's safe to go ahead. + // + + TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_LOCK, "ClassReleaseRemoveLock: " + "Release for object %p & irp %p caused lock to go to zero\n", + DeviceObject, Tag)); + + } + + #else + + UNREFERENCED_PARAMETER(Tag); + + #endif + + // + // Decrement the RemoveLockFailAcquire by 1 when RemoveLockFailAcquire is non-zero. + // Release the RemoveLockRundown only when RemoveLockFailAcquire is zero. + // + + oldValue = 1; + lockValue = commonExtension->PrivateCommonData->RemoveLockFailAcquire; + while (lockValue != 0) { + oldValue = + InterlockedCompareExchange((volatile LONG *) &commonExtension->PrivateCommonData->RemoveLockFailAcquire, + lockValue - 1, + lockValue); + + if (oldValue == lockValue) { + break; + } + + lockValue = oldValue; + } + + if (lockValue == 0) { + removeLockRundown = (PEX_RUNDOWN_REF_CACHE_AWARE) + ((PCHAR)commonExtension->PrivateCommonData + sizeof(CLASS_PRIVATE_COMMON_DATA)); + ExReleaseRundownProtectionCacheAware(removeLockRundown); + } + + return; +} + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassCompleteRequest() + +Routine Description: + + This routine is a wrapper around (and should be used instead of) + IoCompleteRequest. It is used primarily for debugging purposes. + The routine will assert if the Irp being completed is still holding + the release lock. + +Arguments: + + DeviceObject - the device object that was handling this request + + Irp - the irp to be completed by IoCompleteRequest + + PriorityBoost - the priority boost to pass to IoCompleteRequest + +Return Value: + + none + +--*/ +VOID +ClassCompleteRequest( + _In_ PDEVICE_OBJECT DeviceObject, + _In_ PIRP Irp, + _In_ CCHAR PriorityBoost + ) +{ + #if DBG + PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension; + + PRTL_GENERIC_TABLE removeTrackingList = NULL; + REMOVE_TRACKING_BLOCK searchDataBlock; + PREMOVE_TRACKING_BLOCK foundTrackingBlock; + + KIRQL oldIrql; + + KeAcquireSpinLock(&commonExtension->RemoveTrackingSpinlock, &oldIrql); + + removeTrackingList = commonExtension->RemoveTrackingList; + + if (removeTrackingList != NULL) + { + searchDataBlock.Tag = Irp; + + foundTrackingBlock = RtlLookupElementGenericTable(removeTrackingList, &searchDataBlock); + + if(foundTrackingBlock != NULL) { + + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_LOCK, ">>>>>ClassCompleteRequest: " + "Irp %p completed while still holding the remove lock\n", Irp)); + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_LOCK, ">>>>>ClassCompleteRequest: " + "Lock acquired in file %s on line %d\n", + foundTrackingBlock->File, foundTrackingBlock->Line)); + NT_ASSERT(FALSE); + } + } + + KeReleaseSpinLock(&commonExtension->RemoveTrackingSpinlock, oldIrql); + #endif + + + UNREFERENCED_PARAMETER(DeviceObject); + + IoCompleteRequest(Irp, PriorityBoost); + return; +} // end ClassCompleteRequest() + + +RTL_GENERIC_COMPARE_RESULTS +RemoveTrackingCompareRoutine( + PRTL_GENERIC_TABLE Table, + PVOID FirstStruct, + PVOID SecondStruct + ) +{ + PVOID tag1, tag2; + + UNREFERENCED_PARAMETER(Table); + + tag1 = ((PREMOVE_TRACKING_BLOCK)FirstStruct)->Tag; + tag2 = ((PREMOVE_TRACKING_BLOCK)SecondStruct)->Tag; + + if (tag1 < tag2) + { + return GenericLessThan; + } + else if (tag1 > tag2) + { + return GenericGreaterThan; + } + + return GenericEqual; +} + +PVOID +RemoveTrackingAllocateRoutine( + PRTL_GENERIC_TABLE Table, + CLONG ByteSize + ) +{ + UNREFERENCED_PARAMETER(Table); + + return ExAllocatePoolWithTag(NonPagedPoolNx, ByteSize, CLASS_TAG_LOCK_TRACKING); +} + +VOID +RemoveTrackingFreeRoutine( + PRTL_GENERIC_TABLE Table, + PVOID Buffer + ) +{ + UNREFERENCED_PARAMETER(Table); + + FREE_POOL(Buffer); +} + +VOID +ClasspInitializeRemoveTracking( + _In_ PDEVICE_OBJECT DeviceObject + ) +{ + PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension; + + #if DBG + KeInitializeSpinLock(&commonExtension->RemoveTrackingSpinlock); + + commonExtension->RemoveTrackingList = ExAllocatePoolWithTag(NonPagedPoolNx, sizeof(RTL_GENERIC_TABLE), CLASS_TAG_LOCK_TRACKING); + + if (commonExtension->RemoveTrackingList != NULL) + { + RtlInitializeGenericTable(commonExtension->RemoveTrackingList, + RemoveTrackingCompareRoutine, + RemoveTrackingAllocateRoutine, + RemoveTrackingFreeRoutine, + NULL); + } + #else + + UNREFERENCED_PARAMETER(DeviceObject); + + commonExtension->RemoveTrackingSpinlock = (ULONG_PTR) -1; + commonExtension->RemoveTrackingList = NULL; + #endif +} + +VOID +ClasspUninitializeRemoveTracking( + _In_ PDEVICE_OBJECT DeviceObject + ) +{ + #if DBG + PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension; + PRTL_GENERIC_TABLE removeTrackingList = commonExtension->RemoveTrackingList; + + ASSERTMSG("Removing the device while still holding remove locks", + commonExtension->RemoveTrackingUntrackedCount == 0 && + removeTrackingList != NULL ? RtlNumberGenericTableElements(removeTrackingList) == 0 : TRUE); + + if (removeTrackingList != NULL) + { + KIRQL oldIrql; + KeAcquireSpinLock(&commonExtension->RemoveTrackingSpinlock, &oldIrql); + + FREE_POOL(removeTrackingList); + commonExtension->RemoveTrackingList = NULL; + + KeReleaseSpinLock(&commonExtension->RemoveTrackingSpinlock, oldIrql); + } + + #else + + UNREFERENCED_PARAMETER(DeviceObject); + #endif +} + + + + |
