xref: /reactos/drivers/storage/class/classpnp/lock.c (revision 9393fc32)
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