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