1 /*++ 2 3 Copyright (C) Microsoft Corporation, 1990 - 1998 4 5 Module Name: 6 7 lock.c 8 9 Abstract: 10 11 This is the NT SCSI port driver. 12 13 Environment: 14 15 kernel mode only 16 17 Notes: 18 19 This module is a driver dll for scsi miniports. 20 21 Revision History: 22 23 --*/ 24 25 #include "classp.h" 26 #include "debug.h" 27 28 #ifdef DEBUG_USE_WPP 29 #include "lock.tmh" 30 #endif 31 32 33 LONG LockHighWatermark = 0; 34 LONG LockLowWatermark = 0; 35 LONG MaxLockedMinutes = 5; 36 37 // 38 // Structure used for tracking remove lock allocations in checked builds 39 // 40 typedef struct _REMOVE_TRACKING_BLOCK { 41 PVOID Tag; 42 LARGE_INTEGER TimeLocked; 43 PCSTR File; 44 ULONG Line; 45 } REMOVE_TRACKING_BLOCK, *PREMOVE_TRACKING_BLOCK; 46 47 /*++//////////////////////////////////////////////////////////////////////////// 48 49 Classpnp RemoveLockRundown 50 51 RemoveLockRundown is a cacheaware rundown protection for the classpnp device object. While this 52 rundown protection is held successfully, the caller can assume that no pending pnp REMOVE 53 requests will be completed. 54 55 The RemoveLockRundown is a replacement of the original RemoveLock to improve the scalability. 56 For backward compatibility, we still keep the RemoveLock field in the device common extension structure. 57 However, the old RemoveLock is only being used in the DBG build. 58 59 The usage of the RemoveLockRundown is slightly different from the normal rundown protection usage. 60 The RemoveLockRundown is acquired via ClassAcquireRemoveLockEx() function 61 and released via ClassReleaseRemoveLock() function. Usually, we bail out when the acquisition 62 of rundown protection fails (calls to ExAcquireRundownProtectionCacheAware returns FALSE) and 63 will not release the rundown protection in acquisition failure. For the RemoveLockRundown, 64 the caller will always call ClassAcquireRemoveLockEx() and ClassReleaseRemoveLock() in a pair no 65 matter the return value of ClassAcquireRemoveLockEx(). Therefore, a thread may still call 66 ClassReleaseRemoveLock() even the previous acquisition RemoveLockRundown protection failed. 67 68 To deal with the previous acquisition failure case, we introduced a new field RemoveLockFailAcquire 69 as a counter for rundown acquisition failures. In the ClassReleaseRemoveLock() function, we only 70 release the rundown protection when this counter is decremented to zero. Since the change of RemoveLockFailAcquire 71 and release rundown protection is not protected by a lock as an atomic operation, we use a while loop over 72 InterlockedCompareExchange operation to make sure when we release the rundown protection, this counter is 73 actually zero. 74 75 --*/ 76 77 /*++//////////////////////////////////////////////////////////////////////////// 78 79 ClassAcquireRemoveLockEx() 80 81 Routine Description: 82 83 This routine is called to acquire the remove lock on the device object. 84 While the lock is held, the caller can assume that no pending pnp REMOVE 85 requests will be completed. 86 87 The lock should be acquired immediately upon entering a dispatch routine. 88 It should also be acquired before creating any new reference to the 89 device object if there's a chance of releasing the reference before the 90 new one is done. 91 92 This routine will return TRUE if the lock was successfully acquired or 93 FALSE if it cannot be because the device object has already been removed. 94 95 Arguments: 96 97 DeviceObject - the device object to lock 98 99 Tag - Used for tracking lock allocation and release. If an irp is 100 specified when acquiring the lock then the same Tag must be 101 used to release the lock before the Tag is completed. 102 103 Return Value: 104 105 The value of the IsRemoved flag in the device extension. If this is 106 non-zero then the device object has received a Remove irp and non-cleanup 107 IRP's should fail. 108 109 If the value is REMOVE_COMPLETE, the caller should not even release the 110 lock. 111 112 --*/ 113 ULONG 114 NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */ 115 ClassAcquireRemoveLockEx( 116 _In_ PDEVICE_OBJECT DeviceObject, 117 _In_ PVOID Tag, 118 _In_ PCSTR File, 119 _In_ ULONG Line 120 ) 121 // This function implements the acquisition of Tag 122 #ifdef _MSC_VER 123 #pragma warning(suppress:28104) 124 #endif 125 { 126 PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension; 127 BOOLEAN rundownAcquired; 128 PEX_RUNDOWN_REF_CACHE_AWARE removeLockRundown = NULL; 129 130 // 131 // Grab the remove lock 132 // 133 134 #if DBG 135 136 LONG lockValue; 137 138 lockValue = InterlockedIncrement(&commonExtension->RemoveLock); 139 140 141 TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_LOCK, "ClassAcquireRemoveLock: " 142 "Acquired for Object %p & irp %p - count is %d\n", 143 DeviceObject, Tag, lockValue)); 144 145 NT_ASSERTMSG("ClassAcquireRemoveLock - lock value was negative : ", 146 (lockValue > 0)); 147 148 NT_ASSERTMSG("RemoveLock increased to meet LockHighWatermark", 149 ((LockHighWatermark == 0) || 150 (lockValue != LockHighWatermark))); 151 152 if (commonExtension->IsRemoved != REMOVE_COMPLETE) { 153 PRTL_GENERIC_TABLE removeTrackingList = NULL; 154 REMOVE_TRACKING_BLOCK trackingBlock; 155 PREMOVE_TRACKING_BLOCK insertedTrackingBlock = NULL; 156 BOOLEAN newElement = FALSE; 157 158 KIRQL oldIrql; 159 160 trackingBlock.Tag = Tag; 161 162 trackingBlock.File = File; 163 trackingBlock.Line = Line; 164 165 KeQueryTickCount((&trackingBlock.TimeLocked)); 166 167 KeAcquireSpinLock(&commonExtension->RemoveTrackingSpinlock, 168 &oldIrql); 169 170 removeTrackingList = commonExtension->RemoveTrackingList; 171 172 if (removeTrackingList != NULL) { 173 insertedTrackingBlock = RtlInsertElementGenericTable(removeTrackingList, 174 &trackingBlock, 175 sizeof(REMOVE_TRACKING_BLOCK), 176 &newElement); 177 } 178 179 if (insertedTrackingBlock != NULL) { 180 if (!newElement) { 181 TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_LOCK, ">>>>>ClassAcquireRemoveLock: " 182 "already tracking Tag %p\n", Tag)); 183 TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_LOCK, ">>>>>ClassAcquireRemoveLock: " 184 "acquired in file %s on line %d\n", 185 insertedTrackingBlock->File, insertedTrackingBlock->Line)); 186 // NT_ASSERT(FALSE); 187 188 } 189 } else { 190 commonExtension->RemoveTrackingUntrackedCount++; 191 192 TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_LOCK, ">>>>>ClassAcquireRemoveLock: " 193 "Cannot track Tag %p - currently %d untracked requsts\n", 194 Tag, commonExtension->RemoveTrackingUntrackedCount)); 195 } 196 197 KeReleaseSpinLock(&commonExtension->RemoveTrackingSpinlock, oldIrql); 198 } 199 #else 200 201 UNREFERENCED_PARAMETER(Tag); 202 UNREFERENCED_PARAMETER(File); 203 UNREFERENCED_PARAMETER(Line); 204 205 #endif 206 207 removeLockRundown = (PEX_RUNDOWN_REF_CACHE_AWARE) 208 ((PCHAR)commonExtension->PrivateCommonData + sizeof(CLASS_PRIVATE_COMMON_DATA)); 209 rundownAcquired = ExAcquireRundownProtectionCacheAware(removeLockRundown); 210 if (!rundownAcquired) { 211 InterlockedIncrement((volatile LONG*) &(commonExtension->PrivateCommonData->RemoveLockFailAcquire)); 212 TracePrint((TRACE_LEVEL_VERBOSE, 213 TRACE_FLAG_LOCK, 214 "ClassAcquireRemoveLockEx: RemoveLockRundown acquisition failed" 215 "RemoveLockFailAcquire = %d\n", 216 commonExtension->PrivateCommonData->RemoveLockFailAcquire)); 217 } 218 219 return (commonExtension->IsRemoved); 220 } 221 222 /*++//////////////////////////////////////////////////////////////////////////// 223 224 ClassReleaseRemoveLock() 225 226 Routine Description: 227 228 This routine is called to release the remove lock on the device object. It 229 must be called when finished using a previously locked reference to the 230 device object. If an Tag was specified when acquiring the lock then the 231 same Tag must be specified when releasing the lock. 232 233 When the lock count reduces to zero, this routine will signal the waiting 234 remove Tag to delete the device object. As a result the DeviceObject 235 pointer should not be used again once the lock has been released. 236 237 Arguments: 238 239 DeviceObject - the device object to lock 240 241 Tag - The irp (if any) specified when acquiring the lock. This is used 242 for lock tracking purposes 243 244 Return Value: 245 246 none 247 248 --*/ 249 VOID 250 NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */ 251 ClassReleaseRemoveLock( 252 _In_ PDEVICE_OBJECT DeviceObject, 253 _In_opt_ PIRP Tag 254 ) 255 // This function implements the release of Tag 256 #ifdef _MSC_VER 257 #pragma warning(suppress:28103) 258 #endif 259 { 260 PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension; 261 LONG lockValue; 262 LONG oldValue; 263 PEX_RUNDOWN_REF_CACHE_AWARE removeLockRundown = NULL; 264 265 #if DBG 266 PRTL_GENERIC_TABLE removeTrackingList = NULL; 267 REMOVE_TRACKING_BLOCK searchDataBlock; 268 269 BOOLEAN found = FALSE; 270 271 BOOLEAN isRemoved = (commonExtension->IsRemoved == REMOVE_COMPLETE); 272 273 KIRQL oldIrql; 274 275 if (isRemoved) { 276 TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_LOCK, "ClassReleaseRemoveLock: REMOVE_COMPLETE set; this should never happen")); 277 InterlockedDecrement(&(commonExtension->RemoveLock)); 278 return; 279 } 280 281 KeAcquireSpinLock(&commonExtension->RemoveTrackingSpinlock, 282 &oldIrql); 283 284 removeTrackingList = commonExtension->RemoveTrackingList; 285 286 if (removeTrackingList != NULL) { 287 searchDataBlock.Tag = Tag; 288 found = RtlDeleteElementGenericTable(removeTrackingList, &searchDataBlock); 289 } 290 291 if (!found) { 292 if(commonExtension->RemoveTrackingUntrackedCount == 0) { 293 TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_LOCK, ">>>>>ClassReleaseRemoveLock: " 294 "Couldn't find Tag %p in the lock tracking list\n", Tag)); 295 // 296 // This might happen if the device is being removed and the tracking list 297 // has already been freed. Don't assert if that is the case. 298 // 299 NT_ASSERT((removeTrackingList == NULL) && (commonExtension->IsRemoved != NO_REMOVE)); 300 } else { 301 TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_LOCK, ">>>>>ClassReleaseRemoveLock: " 302 "Couldn't find Tag %p in the lock tracking list - " 303 "may be one of the %d untracked requests still outstanding\n", 304 Tag, commonExtension->RemoveTrackingUntrackedCount)); 305 306 commonExtension->RemoveTrackingUntrackedCount--; 307 NT_ASSERT(commonExtension->RemoveTrackingUntrackedCount >= 0); 308 } 309 } 310 311 KeReleaseSpinLock(&commonExtension->RemoveTrackingSpinlock, 312 oldIrql); 313 314 lockValue = InterlockedDecrement(&commonExtension->RemoveLock); 315 316 TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_LOCK, "ClassReleaseRemoveLock: " 317 "Released for Object %p & irp %p - count is %d\n", 318 DeviceObject, Tag, lockValue)); 319 320 NT_ASSERT(lockValue >= 0); 321 322 NT_ASSERTMSG("RemoveLock decreased to meet LockLowWatermark", 323 ((LockLowWatermark == 0) || !(lockValue == LockLowWatermark))); 324 325 if (lockValue == 0) { 326 327 NT_ASSERT(commonExtension->IsRemoved); 328 329 // 330 // The device needs to be removed. Signal the remove event 331 // that it's safe to go ahead. 332 // 333 334 TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_LOCK, "ClassReleaseRemoveLock: " 335 "Release for object %p & irp %p caused lock to go to zero\n", 336 DeviceObject, Tag)); 337 338 } 339 340 #else 341 342 UNREFERENCED_PARAMETER(Tag); 343 344 #endif 345 346 // 347 // Decrement the RemoveLockFailAcquire by 1 when RemoveLockFailAcquire is non-zero. 348 // Release the RemoveLockRundown only when RemoveLockFailAcquire is zero. 349 // 350 351 oldValue = 1; 352 lockValue = commonExtension->PrivateCommonData->RemoveLockFailAcquire; 353 while (lockValue != 0) { 354 oldValue = 355 InterlockedCompareExchange((volatile LONG *) &commonExtension->PrivateCommonData->RemoveLockFailAcquire, 356 lockValue - 1, 357 lockValue); 358 359 if (oldValue == lockValue) { 360 break; 361 } 362 363 lockValue = oldValue; 364 } 365 366 if (lockValue == 0) { 367 removeLockRundown = (PEX_RUNDOWN_REF_CACHE_AWARE) 368 ((PCHAR)commonExtension->PrivateCommonData + sizeof(CLASS_PRIVATE_COMMON_DATA)); 369 ExReleaseRundownProtectionCacheAware(removeLockRundown); 370 } 371 372 return; 373 } 374 375 /*++//////////////////////////////////////////////////////////////////////////// 376 377 ClassCompleteRequest() 378 379 Routine Description: 380 381 This routine is a wrapper around (and should be used instead of) 382 IoCompleteRequest. It is used primarily for debugging purposes. 383 The routine will assert if the Irp being completed is still holding 384 the release lock. 385 386 Arguments: 387 388 DeviceObject - the device object that was handling this request 389 390 Irp - the irp to be completed by IoCompleteRequest 391 392 PriorityBoost - the priority boost to pass to IoCompleteRequest 393 394 Return Value: 395 396 none 397 398 --*/ 399 VOID 400 NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */ 401 ClassCompleteRequest( 402 _In_ PDEVICE_OBJECT DeviceObject, 403 _In_ PIRP Irp, 404 _In_ CCHAR PriorityBoost 405 ) 406 { 407 #if DBG 408 PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension; 409 410 PRTL_GENERIC_TABLE removeTrackingList = NULL; 411 REMOVE_TRACKING_BLOCK searchDataBlock; 412 PREMOVE_TRACKING_BLOCK foundTrackingBlock; 413 414 KIRQL oldIrql; 415 416 KeAcquireSpinLock(&commonExtension->RemoveTrackingSpinlock, &oldIrql); 417 418 removeTrackingList = commonExtension->RemoveTrackingList; 419 420 if (removeTrackingList != NULL) 421 { 422 searchDataBlock.Tag = Irp; 423 424 foundTrackingBlock = RtlLookupElementGenericTable(removeTrackingList, &searchDataBlock); 425 426 if(foundTrackingBlock != NULL) { 427 428 TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_LOCK, ">>>>>ClassCompleteRequest: " 429 "Irp %p completed while still holding the remove lock\n", Irp)); 430 TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_LOCK, ">>>>>ClassCompleteRequest: " 431 "Lock acquired in file %s on line %d\n", 432 foundTrackingBlock->File, foundTrackingBlock->Line)); 433 NT_ASSERT(FALSE); 434 } 435 } 436 437 KeReleaseSpinLock(&commonExtension->RemoveTrackingSpinlock, oldIrql); 438 #endif 439 440 441 UNREFERENCED_PARAMETER(DeviceObject); 442 443 IoCompleteRequest(Irp, PriorityBoost); 444 return; 445 } // end ClassCompleteRequest() 446 447 448 RTL_GENERIC_COMPARE_RESULTS 449 NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */ 450 RemoveTrackingCompareRoutine( 451 PRTL_GENERIC_TABLE Table, 452 PVOID FirstStruct, 453 PVOID SecondStruct 454 ) 455 { 456 PVOID tag1, tag2; 457 458 UNREFERENCED_PARAMETER(Table); 459 460 tag1 = ((PREMOVE_TRACKING_BLOCK)FirstStruct)->Tag; 461 tag2 = ((PREMOVE_TRACKING_BLOCK)SecondStruct)->Tag; 462 463 if (tag1 < tag2) 464 { 465 return GenericLessThan; 466 } 467 else if (tag1 > tag2) 468 { 469 return GenericGreaterThan; 470 } 471 472 return GenericEqual; 473 } 474 475 PVOID 476 NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */ 477 RemoveTrackingAllocateRoutine( 478 PRTL_GENERIC_TABLE Table, 479 CLONG ByteSize 480 ) 481 { 482 UNREFERENCED_PARAMETER(Table); 483 484 return ExAllocatePoolWithTag(NonPagedPoolNx, ByteSize, CLASS_TAG_LOCK_TRACKING); 485 } 486 487 VOID 488 NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */ 489 RemoveTrackingFreeRoutine( 490 PRTL_GENERIC_TABLE Table, 491 PVOID Buffer 492 ) 493 { 494 UNREFERENCED_PARAMETER(Table); 495 496 FREE_POOL(Buffer); 497 } 498 499 VOID 500 ClasspInitializeRemoveTracking( 501 _In_ PDEVICE_OBJECT DeviceObject 502 ) 503 { 504 PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension; 505 506 #if DBG 507 KeInitializeSpinLock(&commonExtension->RemoveTrackingSpinlock); 508 509 commonExtension->RemoveTrackingList = ExAllocatePoolWithTag(NonPagedPoolNx, sizeof(RTL_GENERIC_TABLE), CLASS_TAG_LOCK_TRACKING); 510 511 if (commonExtension->RemoveTrackingList != NULL) 512 { 513 RtlInitializeGenericTable(commonExtension->RemoveTrackingList, 514 RemoveTrackingCompareRoutine, 515 RemoveTrackingAllocateRoutine, 516 RemoveTrackingFreeRoutine, 517 NULL); 518 } 519 #else 520 521 UNREFERENCED_PARAMETER(DeviceObject); 522 523 commonExtension->RemoveTrackingSpinlock = (ULONG_PTR) -1; 524 commonExtension->RemoveTrackingList = NULL; 525 #endif 526 } 527 528 VOID 529 ClasspUninitializeRemoveTracking( 530 _In_ PDEVICE_OBJECT DeviceObject 531 ) 532 { 533 #if DBG 534 PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension; 535 PRTL_GENERIC_TABLE removeTrackingList = commonExtension->RemoveTrackingList; 536 537 ASSERTMSG("Removing the device while still holding remove locks", 538 commonExtension->RemoveTrackingUntrackedCount == 0 && 539 removeTrackingList != NULL ? RtlNumberGenericTableElements(removeTrackingList) == 0 : TRUE); 540 541 if (removeTrackingList != NULL) 542 { 543 KIRQL oldIrql; 544 KeAcquireSpinLock(&commonExtension->RemoveTrackingSpinlock, &oldIrql); 545 546 FREE_POOL(removeTrackingList); 547 commonExtension->RemoveTrackingList = NULL; 548 549 KeReleaseSpinLock(&commonExtension->RemoveTrackingSpinlock, oldIrql); 550 } 551 552 #else 553 554 UNREFERENCED_PARAMETER(DeviceObject); 555 #endif 556 } 557 558 559 560 561