xref: /reactos/ntoskrnl/io/iomgr/remlock.c (revision c2c66aff)
1 /*
2  * PROJECT:         ReactOS Kernel
3  * LICENSE:         GPL - See COPYING in the top level directory
4  * FILE:            ntoskrnl/io/iomgr/remlock.c
5  * PURPOSE:         Remove Lock Support
6  * PROGRAMMERS:     Alex Ionescu (alex.ionescu@reactos.org)
7  *                  Filip Navara (navaraf@reactos.org)
8  *                  Pierre Schweitzer (pierre.schweitzer@reactos.org)
9  */
10 
11 /* INCLUDES ******************************************************************/
12 
13 #include <ntoskrnl.h>
14 #define NDEBUG
15 #include <debug.h>
16 
17 typedef struct _IO_REMOVE_LOCK_TRACKING_BLOCK
18 {
19     PIO_REMOVE_LOCK_TRACKING_BLOCK Next;
20     PVOID Tag;
21     LARGE_INTEGER LockMoment;
22     LPCSTR File;
23     ULONG Line;
24 } IO_REMOVE_LOCK_TRACKING_BLOCK;
25 
26 /* FUNCTIONS *****************************************************************/
27 
28 /*
29  * @implemented
30  */
31 VOID
32 NTAPI
IoInitializeRemoveLockEx(IN PIO_REMOVE_LOCK RemoveLock,IN ULONG AllocateTag,IN ULONG MaxLockedMinutes,IN ULONG HighWatermark,IN ULONG RemlockSize)33 IoInitializeRemoveLockEx(IN PIO_REMOVE_LOCK RemoveLock,
34                          IN ULONG AllocateTag,
35                          IN ULONG MaxLockedMinutes,
36                          IN ULONG HighWatermark,
37                          IN ULONG RemlockSize)
38 {
39     PEXTENDED_IO_REMOVE_LOCK Lock = (PEXTENDED_IO_REMOVE_LOCK)RemoveLock;
40     PAGED_CODE();
41 
42     DPRINT("%s(%p 0x%08x %u %u %u)\n", __FUNCTION__, RemoveLock, AllocateTag, MaxLockedMinutes, HighWatermark, RemlockSize);
43 
44     ASSERT(HighWatermark < MAXLONG);
45 
46     /* If no lock given, nothing to do */
47     if (!Lock)
48     {
49         return;
50     }
51 
52     switch (RemlockSize)
53     {
54         /* Check if this is a debug lock */
55         case (sizeof(IO_REMOVE_LOCK_DBG_BLOCK) + sizeof(IO_REMOVE_LOCK_COMMON_BLOCK)):
56             /* Setup debug parameters */
57             Lock->Dbg.Signature = 'COLR';
58             Lock->Dbg.HighWatermark = HighWatermark;
59             Lock->Dbg.MaxLockedTicks = KeQueryTimeIncrement() * MaxLockedMinutes * 600000000;
60             Lock->Dbg.AllocateTag = AllocateTag;
61             KeInitializeSpinLock(&(Lock->Dbg.Spin));
62             Lock->Dbg.LowMemoryCount = 0;
63             Lock->Dbg.Blocks = NULL;
64 
65         case sizeof(IO_REMOVE_LOCK_COMMON_BLOCK):
66             /* Setup a free block */
67             Lock->Common.Removed = FALSE;
68             Lock->Common.IoCount = 1;
69             KeInitializeEvent(&Lock->Common.RemoveEvent,
70                               SynchronizationEvent,
71                               FALSE);
72     }
73 }
74 
75 /*
76  * @implemented
77  */
78 NTSTATUS
79 NTAPI
IoAcquireRemoveLockEx(IN PIO_REMOVE_LOCK RemoveLock,IN OPTIONAL PVOID Tag,IN LPCSTR File,IN ULONG Line,IN ULONG RemlockSize)80 IoAcquireRemoveLockEx(IN PIO_REMOVE_LOCK RemoveLock,
81                       IN OPTIONAL PVOID Tag,
82                       IN LPCSTR File,
83                       IN ULONG Line,
84                       IN ULONG RemlockSize)
85 {
86     KIRQL OldIrql;
87     LONG LockValue;
88     PIO_REMOVE_LOCK_TRACKING_BLOCK TrackingBlock;
89     PEXTENDED_IO_REMOVE_LOCK Lock = (PEXTENDED_IO_REMOVE_LOCK)RemoveLock;
90 
91     DPRINT("%s(%p %p %s %u %u)\n", __FUNCTION__, RemoveLock, Tag, File, Line, RemlockSize);
92 
93     /* Increase the lock count */
94     LockValue = InterlockedIncrement(&(Lock->Common.IoCount));
95     ASSERT(LockValue > 0);
96     if (!Lock->Common.Removed)
97     {
98         /* Check what kind of lock this is */
99         if (RemlockSize == (sizeof(IO_REMOVE_LOCK_DBG_BLOCK) + sizeof(IO_REMOVE_LOCK_COMMON_BLOCK)))
100         {
101             ASSERT(Lock->Dbg.HighWatermark == 0 || LockValue <= Lock->Dbg.HighWatermark);
102 
103             /* Allocate a tracking block */
104             TrackingBlock = ExAllocatePoolWithTag(NonPagedPool, sizeof(IO_REMOVE_LOCK_TRACKING_BLOCK), Lock->Dbg.AllocateTag);
105             if (!TrackingBlock)
106             {
107                 /* Keep count of failures for lock release and missing tags */
108                 InterlockedIncrement(&(Lock->Dbg.LowMemoryCount));
109             }
110             else
111             {
112                 /* Initialize block */
113                 RtlZeroMemory(TrackingBlock, sizeof(IO_REMOVE_LOCK_TRACKING_BLOCK));
114                 TrackingBlock->Tag = Tag;
115                 TrackingBlock->File = File;
116                 TrackingBlock->Line = Line;
117                 KeQueryTickCount(&(TrackingBlock->LockMoment));
118 
119                 /* Queue the block */
120                 KeAcquireSpinLock(&(Lock->Dbg.Spin), &OldIrql);
121                 TrackingBlock->Next = Lock->Dbg.Blocks;
122                 Lock->Dbg.Blocks = TrackingBlock;
123                 KeReleaseSpinLock(&(Lock->Dbg.Spin), OldIrql);
124             }
125         }
126     }
127     else
128     {
129         /* Otherwise, decrement the count and check if it's gone */
130         if (!InterlockedDecrement(&(Lock->Common.IoCount)))
131         {
132             /* Signal the event */
133             KeSetEvent(&(Lock->Common.RemoveEvent), IO_NO_INCREMENT, FALSE);
134         }
135 
136         /* Return pending delete */
137         return STATUS_DELETE_PENDING;
138     }
139 
140     /* Otherwise, return success */
141     return STATUS_SUCCESS;
142 }
143 
144 /*
145  * @implemented
146  */
147 VOID
148 NTAPI
IoReleaseRemoveLockEx(IN PIO_REMOVE_LOCK RemoveLock,IN PVOID Tag,IN ULONG RemlockSize)149 IoReleaseRemoveLockEx(IN PIO_REMOVE_LOCK RemoveLock,
150                       IN PVOID Tag,
151                       IN ULONG RemlockSize)
152 {
153     KIRQL OldIrql;
154     LONG LockValue;
155     BOOLEAN TagFound;
156     LARGE_INTEGER CurrentMoment;
157     PIO_REMOVE_LOCK_TRACKING_BLOCK TrackingBlock;
158     PIO_REMOVE_LOCK_TRACKING_BLOCK *TrackingBlockLink;
159     PEXTENDED_IO_REMOVE_LOCK Lock = (PEXTENDED_IO_REMOVE_LOCK)RemoveLock;
160 
161     DPRINT("%s(%p %p %u)\n", __FUNCTION__, RemoveLock, Tag, RemlockSize);
162 
163     /* Check what kind of lock this is */
164     if (RemlockSize == (sizeof(IO_REMOVE_LOCK_DBG_BLOCK) + sizeof(IO_REMOVE_LOCK_COMMON_BLOCK)))
165     {
166         /* Acquire blocks queue */
167         KeAcquireSpinLock(&(Lock->Dbg.Spin), &OldIrql);
168 
169         /* Get the release moment */
170         KeQueryTickCount(&CurrentMoment);
171 
172         /* Start browsing tracking blocks to find a block that would match given tag */
173         TagFound = FALSE;
174         TrackingBlock = Lock->Dbg.Blocks;
175         TrackingBlockLink = &(Lock->Dbg.Blocks);
176         while (TrackingBlock != NULL)
177         {
178             /* First of all, check if the lock was locked for too long */
179             if (Lock->Dbg.MaxLockedTicks &&
180                 CurrentMoment.QuadPart - TrackingBlock->LockMoment.QuadPart > Lock->Dbg.MaxLockedTicks)
181             {
182                 DPRINT("Lock %#08lx (with tag %#08lx) was supposed to be held at max %I64d ticks but lasted longer\n",
183                        Lock, TrackingBlock->Tag, Lock->Dbg.MaxLockedTicks);
184                 DPRINT("Lock was acquired in file %s at line %lu\n", TrackingBlock->File, TrackingBlock->Line);
185                 ASSERT(FALSE);
186             }
187 
188             /* Check if this is the first matching tracking block */
189             if ((TagFound == FALSE) && (TrackingBlock->Tag == Tag))
190             {
191                 /* Unlink this tracking block, and free it */
192                 TagFound = TRUE;
193                 *TrackingBlockLink = TrackingBlock->Next;
194                 ExFreePoolWithTag(TrackingBlock, Lock->Dbg.AllocateTag);
195                 TrackingBlock = *TrackingBlockLink;
196             }
197             else
198             {
199                 /* Go to the next tracking block */
200                 TrackingBlockLink = &(TrackingBlock->Next);
201                 TrackingBlock = TrackingBlock->Next;
202             }
203         }
204 
205         /* We're done, release queue lock */
206         KeReleaseSpinLock(&(Lock->Dbg.Spin), OldIrql);
207 
208         /* If we didn't find any matching block */
209         if (TagFound == FALSE)
210         {
211             /* Check if it was because we were low in memory
212              * If yes, then ignore, that's normal
213              */
214             if (InterlockedDecrement(&(Lock->Dbg.LowMemoryCount)) < 0)
215             {
216                 /* Otherwise signal the issue, it shouldn't happen */
217                 InterlockedIncrement(&(Lock->Dbg.LowMemoryCount));
218                 DPRINT("Failed finding block for tag: %#08lx\n", Tag);
219                 ASSERT(FALSE);
220             }
221         }
222     }
223 
224     /* Decrement the lock count */
225     LockValue = InterlockedDecrement(&(Lock->Common.IoCount));
226     ASSERT(LockValue >= 0);
227 
228     if (!LockValue)
229     {
230         /* Someone should be waiting... */
231         ASSERT(Lock->Common.Removed);
232 
233         /* Signal the event */
234         KeSetEvent(&(Lock->Common.RemoveEvent), IO_NO_INCREMENT, FALSE);
235     }
236 }
237 
238 /*
239  * @implemented
240  */
241 VOID
242 NTAPI
IoReleaseRemoveLockAndWaitEx(IN PIO_REMOVE_LOCK RemoveLock,IN PVOID Tag,IN ULONG RemlockSize)243 IoReleaseRemoveLockAndWaitEx(IN PIO_REMOVE_LOCK RemoveLock,
244                              IN PVOID Tag,
245                              IN ULONG RemlockSize)
246 {
247     LONG LockValue;
248     PIO_REMOVE_LOCK_TRACKING_BLOCK TrackingBlock;
249     PEXTENDED_IO_REMOVE_LOCK Lock = (PEXTENDED_IO_REMOVE_LOCK)RemoveLock;
250     PAGED_CODE();
251 
252     DPRINT("%s(%p %p %u)\n", __FUNCTION__, RemoveLock, Tag, RemlockSize);
253 
254     /* Remove the lock and decrement the count */
255     Lock->Common.Removed = TRUE;
256     LockValue = InterlockedDecrement(&Lock->Common.IoCount);
257     ASSERT(LockValue > 0);
258 
259     /* If we are still > 0, then wait for the others to remove lock */
260     if (InterlockedDecrement(&Lock->Common.IoCount) > 0)
261     {
262         /* Wait for it */
263         KeWaitForSingleObject(&(Lock->Common.RemoveEvent),
264                               Executive,
265                               KernelMode,
266                               FALSE,
267                               NULL);
268     }
269 
270     /* Check what kind of lock this is */
271     if (RemlockSize == (sizeof(IO_REMOVE_LOCK_DBG_BLOCK) + sizeof(IO_REMOVE_LOCK_COMMON_BLOCK)))
272     {
273         /* A block must be remaining */
274         ASSERT(Lock->Dbg.Blocks);
275 
276         /* Get it */
277         TrackingBlock = Lock->Dbg.Blocks;
278         /* Tag should match */
279         if (TrackingBlock->Tag != Tag)
280         {
281             DPRINT("Last tracking block tag invalid! Expected: %p, having: %p\n", Tag, TrackingBlock->Tag);
282             ASSERT(TrackingBlock->Tag != Tag);
283         }
284 
285         /* Release block */
286         ExFreePoolWithTag(Lock->Dbg.Blocks, Lock->Dbg.AllocateTag);
287     }
288 }
289 
290 /* EOF */
291