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