35e5fb60119056c89ee2f3c8992da994980be773
[reactos.git] / kmtests / ntos_ke / KeSpinLock.c
1 /*
2 * PROJECT: ReactOS kernel-mode tests
3 * LICENSE: GPLv2+ - See COPYING in the top level directory
4 * PURPOSE: Kernel-Mode Test Suite Spin lock test
5 * PROGRAMMER: Thomas Faber <thfabba@gmx.de>
6 */
7
8 #ifndef _WIN64
9 __declspec(dllimport) void __stdcall KeAcquireSpinLock(unsigned long *, unsigned char *);
10 __declspec(dllimport) void __stdcall KeReleaseSpinLock(unsigned long *, unsigned char);
11 __declspec(dllimport) void __stdcall KeAcquireSpinLockAtDpcLevel(unsigned long *);
12 __declspec(dllimport) void __stdcall KeReleaseSpinLockFromDpcLevel(unsigned long *);
13 #endif
14
15 /* this define makes KeInitializeSpinLock not use the inlined version */
16 #define WIN9X_COMPAT_SPINLOCK
17 #include <ntddk.h>
18 #include <ntifs.h>
19 #include <ndk/kefuncs.h>
20 #include <kmt_test.h>
21 #include <pseh/pseh2.h>
22 #include <limits.h>
23
24 //#define NDEBUG
25 #include <debug.h>
26
27 /* TODO: these are documented for Vista+ */
28 NTKERNELAPI
29 VOID
30 FASTCALL
31 KeAcquireInStackQueuedSpinLockForDpc(
32 IN OUT PKSPIN_LOCK SpinLock,
33 OUT PKLOCK_QUEUE_HANDLE LockHandle);
34
35 NTKERNELAPI
36 VOID
37 FASTCALL
38 KeReleaseInStackQueuedSpinLockForDpc(
39 IN PKLOCK_QUEUE_HANDLE LockHandle);
40
41 /* TODO: multiprocessor testing */
42
43 struct _CHECK_DATA;
44 typedef struct _CHECK_DATA CHECK_DATA, *PCHECK_DATA;
45
46 typedef VOID (*PACQUIRE_FUNCTION)(PKSPIN_LOCK, PCHECK_DATA);
47 typedef VOID (*PRELEASE_FUNCTION)(PKSPIN_LOCK, PCHECK_DATA);
48 typedef BOOLEAN (*PTRY_FUNCTION)(PKSPIN_LOCK, PCHECK_DATA);
49
50 struct _CHECK_DATA
51 {
52 enum
53 {
54 CheckQueueHandle,
55 CheckQueue,
56 CheckLock
57 } Check;
58 KIRQL IrqlWhenAcquired;
59 PACQUIRE_FUNCTION Acquire;
60 PRELEASE_FUNCTION Release;
61 PTRY_FUNCTION TryAcquire;
62 PACQUIRE_FUNCTION AcquireNoRaise;
63 PRELEASE_FUNCTION ReleaseNoLower;
64 PTRY_FUNCTION TryAcquireNoRaise;
65 KSPIN_LOCK_QUEUE_NUMBER QueueNumber;
66 BOOLEAN TryRetOnFailure;
67 KIRQL OriginalIrql;
68 BOOLEAN IsAcquired;
69 _ANONYMOUS_UNION union
70 {
71 KLOCK_QUEUE_HANDLE QueueHandle;
72 PKSPIN_LOCK_QUEUE Queue;
73 KIRQL Irql;
74 } DUMMYUNIONNAME;
75 PVOID UntouchedValue;
76 };
77
78 #define DEFINE_ACQUIRE(LocalName, SetIsAcquired, DoCall) \
79 static VOID LocalName(PKSPIN_LOCK SpinLock, PCHECK_DATA CheckData) \
80 { \
81 ASSERT(!CheckData->IsAcquired); \
82 DoCall; \
83 if (SetIsAcquired) CheckData->IsAcquired = TRUE; \
84 }
85
86 #define DEFINE_RELEASE(LocalName, SetIsAcquired, DoCall) \
87 static VOID LocalName(PKSPIN_LOCK SpinLock, PCHECK_DATA CheckData) \
88 { \
89 DoCall; \
90 if (SetIsAcquired) CheckData->IsAcquired = FALSE; \
91 }
92
93 DEFINE_ACQUIRE(AcquireNormal, TRUE, KeAcquireSpinLock(SpinLock, &CheckData->Irql))
94 DEFINE_RELEASE(ReleaseNormal, TRUE, KeReleaseSpinLock(SpinLock, CheckData->Irql))
95 DEFINE_ACQUIRE(AcquireExp, TRUE, (KeAcquireSpinLock)(SpinLock, &CheckData->Irql))
96 DEFINE_RELEASE(ReleaseExp, TRUE, (KeReleaseSpinLock)(SpinLock, CheckData->Irql))
97 DEFINE_ACQUIRE(AcquireSynch, TRUE, CheckData->Irql = KeAcquireSpinLockRaiseToSynch(SpinLock))
98
99 DEFINE_ACQUIRE(AcquireInStackQueued, TRUE, KeAcquireInStackQueuedSpinLock(SpinLock, &CheckData->QueueHandle))
100 DEFINE_ACQUIRE(AcquireInStackSynch, TRUE, KeAcquireInStackQueuedSpinLockRaiseToSynch(SpinLock, &CheckData->QueueHandle))
101 DEFINE_RELEASE(ReleaseInStackQueued, TRUE, KeReleaseInStackQueuedSpinLock(&CheckData->QueueHandle))
102
103 DEFINE_ACQUIRE(AcquireQueued, TRUE, CheckData->Irql = KeAcquireQueuedSpinLock(CheckData->QueueNumber))
104 DEFINE_ACQUIRE(AcquireQueuedSynch, TRUE, CheckData->Irql = KeAcquireQueuedSpinLockRaiseToSynch(CheckData->QueueNumber))
105 DEFINE_RELEASE(ReleaseQueued, TRUE, KeReleaseQueuedSpinLock(CheckData->QueueNumber, CheckData->Irql))
106
107 DEFINE_ACQUIRE(AcquireNoRaise, FALSE, KeAcquireSpinLockAtDpcLevel(SpinLock))
108 DEFINE_RELEASE(ReleaseNoLower, FALSE, KeReleaseSpinLockFromDpcLevel(SpinLock))
109 DEFINE_ACQUIRE(AcquireExpNoRaise, FALSE, (KeAcquireSpinLockAtDpcLevel)(SpinLock))
110 DEFINE_RELEASE(ReleaseExpNoLower, FALSE, (KeReleaseSpinLockFromDpcLevel)(SpinLock))
111
112 DEFINE_ACQUIRE(AcquireInStackNoRaise, FALSE, KeAcquireInStackQueuedSpinLockAtDpcLevel(SpinLock, &CheckData->QueueHandle))
113 DEFINE_RELEASE(ReleaseInStackNoRaise, FALSE, KeReleaseInStackQueuedSpinLockFromDpcLevel(&CheckData->QueueHandle))
114
115 DEFINE_ACQUIRE(AcquireForDpc, TRUE, CheckData->Irql = KeAcquireSpinLockForDpc(SpinLock))
116 DEFINE_RELEASE(ReleaseForDpc, TRUE, KeReleaseSpinLockForDpc(SpinLock, CheckData->Irql))
117
118 DEFINE_ACQUIRE(AcquireInStackForDpc, FALSE, KeAcquireInStackQueuedSpinLockForDpc(SpinLock, &CheckData->QueueHandle))
119 DEFINE_RELEASE(ReleaseInStackForDpc, FALSE, KeReleaseInStackQueuedSpinLockForDpc(&CheckData->QueueHandle))
120
121 DEFINE_ACQUIRE(AcquireInt, FALSE, KiAcquireSpinLock(SpinLock))
122 DEFINE_RELEASE(ReleaseInt, FALSE, KiReleaseSpinLock(SpinLock))
123
124 BOOLEAN TryQueued(PKSPIN_LOCK SpinLock, PCHECK_DATA CheckData) {
125 LOGICAL Ret = KeTryToAcquireQueuedSpinLock(CheckData->QueueNumber, &CheckData->Irql);
126 CheckData->IsAcquired = TRUE;
127 ASSERT(Ret == FALSE || Ret == TRUE);
128 return (BOOLEAN)Ret;
129 }
130 BOOLEAN TryQueuedSynch(PKSPIN_LOCK SpinLock, PCHECK_DATA CheckData) {
131 BOOLEAN Ret = KeTryToAcquireQueuedSpinLockRaiseToSynch(CheckData->QueueNumber, &CheckData->Irql);
132 CheckData->IsAcquired = TRUE;
133 return Ret;
134 }
135 BOOLEAN TryNoRaise(PKSPIN_LOCK SpinLock, PCHECK_DATA CheckData) {
136 BOOLEAN Ret = KeTryToAcquireSpinLockAtDpcLevel(SpinLock);
137 return Ret;
138 }
139
140 #define CheckSpinLockLock(SpinLock, CheckData, Value) do \
141 { \
142 PKTHREAD Thread = KeGetCurrentThread(); \
143 if (KmtIsMultiProcessorBuild) \
144 { \
145 ok_eq_bool(Ret, (Value) == 0); \
146 if (SpinLock) \
147 ok_eq_pointer((PVOID)*(SpinLock), \
148 (Value) ? (PVOID)((ULONG_PTR)Thread | 1) : 0); \
149 } \
150 else \
151 { \
152 ok_bool_true(Ret, "KeTestSpinLock returned"); \
153 if (SpinLock) \
154 ok_eq_pointer((PVOID)*(SpinLock), NULL); \
155 } \
156 ok_eq_uint((CheckData)->Irql, (CheckData)->OriginalIrql); \
157 } while (0)
158
159 #define CheckSpinLockQueue(SpinLock, CheckData, Value) do \
160 { \
161 ok_eq_pointer((CheckData)->Queue->Next, NULL); \
162 ok_eq_pointer((CheckData)->Queue->Lock, NULL); \
163 ok_eq_uint((CheckData)->Irql, (CheckData)->OriginalIrql); \
164 } while (0)
165
166 #define CheckSpinLockQueueHandle(SpinLock, CheckData, Value) do \
167 { \
168 if (KmtIsMultiProcessorBuild) \
169 { \
170 ok_eq_bool(Ret, (Value) == 0); \
171 if (SpinLock) \
172 ok_eq_pointer((PVOID)*(SpinLock), \
173 (Value) ? &(CheckData)->QueueHandle : 0); \
174 ok_eq_pointer((CheckData)->QueueHandle.LockQueue.Next, NULL); \
175 ok_eq_pointer((CheckData)->QueueHandle.LockQueue.Lock, \
176 (PVOID)((ULONG_PTR)SpinLock | ((Value) ? 2 : 0))); \
177 } \
178 else \
179 { \
180 ok_bool_true(Ret, "KeTestSpinLock returned"); \
181 if (SpinLock) \
182 ok_eq_pointer((PVOID)*(SpinLock), NULL); \
183 ok_eq_pointer((CheckData)->QueueHandle.LockQueue.Next, (CheckData)->UntouchedValue); \
184 ok_eq_pointer((CheckData)->QueueHandle.LockQueue.Lock, (CheckData)->UntouchedValue); \
185 } \
186 ok_eq_uint((CheckData)->QueueHandle.OldIrql, (CheckData)->OriginalIrql); \
187 } while (0)
188
189 #define CheckSpinLock(SpinLock, CheckData, Value) do \
190 { \
191 BOOLEAN Ret = SpinLock ? KeTestSpinLock(SpinLock) : TRUE; \
192 KIRQL ExpectedIrql = (CheckData)->OriginalIrql; \
193 \
194 switch ((CheckData)->Check) \
195 { \
196 case CheckLock: \
197 CheckSpinLockLock(SpinLock, CheckData, Value); \
198 break; \
199 case CheckQueue: \
200 CheckSpinLockQueue(SpinLock, CheckData, Value); \
201 break; \
202 case CheckQueueHandle: \
203 CheckSpinLockQueueHandle(SpinLock, CheckData, Value); \
204 break; \
205 } \
206 \
207 if ((CheckData)->IsAcquired) \
208 ExpectedIrql = (CheckData)->IrqlWhenAcquired; \
209 ok_irql(ExpectedIrql); \
210 ok_bool_false(KeAreApcsDisabled(), "KeAreApcsDisabled returned"); \
211 ok_bool_true(KmtAreInterruptsEnabled(), "Interrupts enabled:"); \
212 } while (0)
213
214 static
215 VOID
216 TestSpinLock(
217 PKSPIN_LOCK SpinLock,
218 PCHECK_DATA CheckData)
219 {
220 static INT Run = 0;
221 trace("Test SpinLock run %d\n", Run++);
222
223 ok_irql(CheckData->OriginalIrql);
224
225 if (SpinLock)
226 ok_eq_pointer((PVOID)*SpinLock, NULL);
227 CheckData->Acquire(SpinLock, CheckData);
228 CheckSpinLock(SpinLock, CheckData, 1);
229 CheckData->Release(SpinLock, CheckData);
230 CheckSpinLock(SpinLock, CheckData, 0);
231
232 if (CheckData->TryAcquire)
233 {
234 CheckSpinLock(SpinLock, CheckData, 0);
235 ok_bool_true(CheckData->TryAcquire(SpinLock, CheckData), "TryAcquire returned");
236 CheckSpinLock(SpinLock, CheckData, 1);
237 if (!KmtIsCheckedBuild)
238 {
239 /* SPINLOCK_ALREADY_OWNED on checked build */
240 ok_bool_true(CheckData->TryAcquire(SpinLock, CheckData), "TryAcquire returned");
241 /* even a failing acquire sets irql */
242 ok_eq_uint(CheckData->Irql, CheckData->IrqlWhenAcquired);
243 CheckData->Irql = CheckData->OriginalIrql;
244 CheckSpinLock(SpinLock, CheckData, 1);
245 }
246 CheckData->Release(SpinLock, CheckData);
247 CheckSpinLock(SpinLock, CheckData, 0);
248 }
249
250 if (CheckData->AcquireNoRaise &&
251 (CheckData->OriginalIrql >= DISPATCH_LEVEL || !KmtIsCheckedBuild))
252 {
253 /* acquire/release without irql change */
254 CheckData->AcquireNoRaise(SpinLock, CheckData);
255 CheckSpinLock(SpinLock, CheckData, 1);
256 CheckData->ReleaseNoLower(SpinLock, CheckData);
257 CheckSpinLock(SpinLock, CheckData, 0);
258
259 /* acquire without raise, but normal release */
260 CheckData->AcquireNoRaise(SpinLock, CheckData);
261 CheckSpinLock(SpinLock, CheckData, 1);
262 CheckData->Release(SpinLock, CheckData);
263 CheckSpinLock(SpinLock, CheckData, 0);
264
265 /* acquire normally but release without lower */
266 CheckData->Acquire(SpinLock, CheckData);
267 CheckSpinLock(SpinLock, CheckData, 1);
268 CheckData->ReleaseNoLower(SpinLock, CheckData);
269 CheckSpinLock(SpinLock, CheckData, 0);
270 CheckData->IsAcquired = FALSE;
271 KmtSetIrql(CheckData->OriginalIrql);
272
273 if (CheckData->TryAcquireNoRaise)
274 {
275 CheckSpinLock(SpinLock, CheckData, 0);
276 ok_bool_true(CheckData->TryAcquireNoRaise(SpinLock, CheckData), "TryAcquireNoRaise returned");
277 CheckSpinLock(SpinLock, CheckData, 1);
278 if (!KmtIsCheckedBuild)
279 {
280 ok_bool_true(CheckData->TryAcquireNoRaise(SpinLock, CheckData), "TryAcquireNoRaise returned");
281 CheckSpinLock(SpinLock, CheckData, 1);
282 }
283 CheckData->ReleaseNoLower(SpinLock, CheckData);
284 CheckSpinLock(SpinLock, CheckData, 0);
285 }
286 }
287
288 ok_irql(CheckData->OriginalIrql);
289 /* make sure we survive this in case of error */
290 KmtSetIrql(CheckData->OriginalIrql);
291 }
292
293 START_TEST(KeSpinLock)
294 {
295 KSPIN_LOCK SpinLock = (KSPIN_LOCK)0x5555555555555555LL;
296 PKSPIN_LOCK pSpinLock = &SpinLock;
297 KIRQL Irql, SynchIrql = KmtIsMultiProcessorBuild ? IPI_LEVEL - 2 : DISPATCH_LEVEL;
298 KIRQL OriginalIrqls[] = { PASSIVE_LEVEL, APC_LEVEL, DISPATCH_LEVEL, HIGH_LEVEL };
299 CHECK_DATA TestData[] =
300 {
301 { CheckLock, DISPATCH_LEVEL, AcquireNormal, ReleaseNormal, NULL, AcquireNoRaise, ReleaseNoLower, TryNoRaise },
302 { CheckLock, DISPATCH_LEVEL, AcquireExp, ReleaseExp, NULL, AcquireExpNoRaise, ReleaseExpNoLower, NULL },
303 /* TODO: this one is just weird!
304 { CheckLock, DISPATCH_LEVEL, AcquireNormal, ReleaseNormal, NULL, AcquireForDpc, ReleaseForDpc, NULL },*/
305 { CheckLock, DISPATCH_LEVEL, AcquireNormal, ReleaseNormal, NULL, AcquireInt, ReleaseInt, NULL },
306 { CheckLock, SynchIrql, AcquireSynch, ReleaseNormal, NULL, NULL, NULL, NULL },
307 { CheckQueueHandle, DISPATCH_LEVEL, AcquireInStackQueued, ReleaseInStackQueued, NULL, AcquireInStackNoRaise, ReleaseInStackNoRaise, NULL },
308 { CheckQueueHandle, SynchIrql, AcquireInStackSynch, ReleaseInStackQueued, NULL, NULL, NULL, NULL },
309 { CheckQueueHandle, DISPATCH_LEVEL, AcquireInStackQueued, ReleaseInStackQueued, NULL, AcquireInStackForDpc, ReleaseInStackForDpc, NULL },
310 { CheckQueue, DISPATCH_LEVEL, AcquireQueued, ReleaseQueued, TryQueued, NULL, NULL, NULL, LockQueuePfnLock },
311 { CheckQueue, SynchIrql, AcquireQueuedSynch, ReleaseQueued, TryQueuedSynch, NULL, NULL, NULL, LockQueuePfnLock },
312 };
313 int i, iIrql;
314 PKPRCB Prcb = KeGetCurrentPrcb();
315
316 /* KeInitializeSpinLock */
317 memset(&SpinLock, 0x55, sizeof SpinLock);
318 KeInitializeSpinLock(&SpinLock);
319 ok_eq_pointer((PVOID)SpinLock, NULL);
320
321 /* KeTestSpinLock */
322 ok_bool_true(KeTestSpinLock(&SpinLock), "KeTestSpinLock returned");
323 SpinLock = 1;
324 ok_bool_false(KeTestSpinLock(&SpinLock), "KeTestSpinLock returned");
325 SpinLock = 2;
326 ok_bool_false(KeTestSpinLock(&SpinLock), "KeTestSpinLock returned");
327 SpinLock = (ULONG_PTR)-1;
328 ok_bool_false(KeTestSpinLock(&SpinLock), "KeTestSpinLock returned");
329 SpinLock = (ULONG_PTR)1 << (sizeof(ULONG_PTR) * CHAR_BIT - 1);
330 ok_bool_false(KeTestSpinLock(&SpinLock), "KeTestSpinLock returned");
331 SpinLock = 0;
332 ok_bool_true(KeTestSpinLock(&SpinLock), "KeTestSpinLock returned");
333
334 /* on UP none of the following functions actually looks at the spinlock! */
335 if (!KmtIsMultiProcessorBuild && !KmtIsCheckedBuild)
336 pSpinLock = NULL;
337
338 for (i = 0; i < sizeof TestData / sizeof TestData[0]; ++i)
339 {
340 memset(&SpinLock, 0x55, sizeof SpinLock);
341 KeInitializeSpinLock(&SpinLock);
342 if (TestData[i].Check == CheckQueueHandle)
343 memset(&TestData[i].QueueHandle, 0x55, sizeof TestData[i].QueueHandle);
344 if (TestData[i].Check == CheckQueue)
345 {
346 TestData[i].Queue = &Prcb->LockQueue[TestData[i].QueueNumber];
347 TestData[i].UntouchedValue = NULL;
348 }
349 else
350 TestData[i].UntouchedValue = (PVOID)0x5555555555555555LL;
351
352 for (iIrql = 0; iIrql < sizeof OriginalIrqls / sizeof OriginalIrqls[0]; ++iIrql)
353 {
354 if (KmtIsCheckedBuild && OriginalIrqls[iIrql] > DISPATCH_LEVEL)
355 continue;
356 KeRaiseIrql(OriginalIrqls[iIrql], &Irql);
357 TestData[i].OriginalIrql = OriginalIrqls[iIrql];
358 TestData[i].IsAcquired = FALSE;
359 TestSpinLock(pSpinLock, &TestData[i]);
360 KeLowerIrql(Irql);
361 }
362 }
363
364 KmtSetIrql(PASSIVE_LEVEL);
365 }