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