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     (VOID)Thread;                                                                   \
170     if (KmtIsMultiProcessorBuild || KmtIsCheckedBuild)                              \
171     {                                                                               \
172         ok_eq_bool(Ret, (Value) == 0);                                              \
173         if (SpinLock)                                                               \
174         {                                                                           \
175             if (KmtIsCheckedBuild)                                                  \
176                 ok_eq_ulongptr(*(SpinLock), (Value) ? (ULONG_PTR)Thread | 1 : 0);   \
177             else                                                                    \
178                 ok_eq_ulongptr(*(SpinLock), (Value) ? 1 : 0);                       \
179         }                                                                           \
180     }                                                                               \
181     else                                                                            \
182     {                                                                               \
183         ok_bool_true(Ret, "KeTestSpinLock returned");                               \
184         if (SpinLock)                                                               \
185             ok_eq_ulongptr(*(SpinLock), 0);                                         \
186     }                                                                               \
187     ok_eq_uint((CheckData)->Irql, (CheckData)->OriginalIrql);                       \
188 } while (0)
189 
190 #define CheckSpinLockQueue(SpinLock, CheckData, Value) do                           \
191 {                                                                                   \
192     ok_eq_pointer((CheckData)->Queue->Next, NULL);                                  \
193     ok_eq_pointer((CheckData)->Queue->Lock, NULL);                                  \
194     ok_eq_uint((CheckData)->Irql, (CheckData)->OriginalIrql);                       \
195 } while (0)
196 
197 #define CheckSpinLockQueueHandle(SpinLock, CheckData, Value) do                     \
198 {                                                                                   \
199     if (KmtIsMultiProcessorBuild || KmtIsCheckedBuild)                              \
200     {                                                                               \
201         ok_eq_bool(Ret, (Value) == 0);                                              \
202         if (SpinLock)                                                               \
203             ok_eq_ulongptr(*(SpinLock),                                             \
204                         (Value) ? &(CheckData)->QueueHandle : 0);                   \
205         ok_eq_pointer((CheckData)->QueueHandle.LockQueue.Next, NULL);               \
206         ok_eq_pointer((CheckData)->QueueHandle.LockQueue.Lock,                      \
207                 (PVOID)((ULONG_PTR)SpinLock | ((Value) ? 2 : 0)));                  \
208     }                                                                               \
209     else                                                                            \
210     {                                                                               \
211         ok_bool_true(Ret, "KeTestSpinLock returned");                               \
212         if (SpinLock)                                                               \
213             ok_eq_ulongptr(*(SpinLock), 0);                                         \
214         ok_eq_pointer((CheckData)->QueueHandle.LockQueue.Next, (CheckData)->UntouchedValue);                \
215         ok_eq_pointer((CheckData)->QueueHandle.LockQueue.Lock, (CheckData)->UntouchedValue);                \
216     }                                                                               \
217     ok_eq_uint((CheckData)->QueueHandle.OldIrql, (CheckData)->OriginalIrql);        \
218 } while (0)
219 
220 #define CheckSpinLock(SpinLock, CheckData, Value) do                                \
221 {                                                                                   \
222     BOOLEAN Ret = SpinLock && pKeTestSpinLock ? pKeTestSpinLock(SpinLock) : TRUE;   \
223     KIRQL ExpectedIrql = (CheckData)->OriginalIrql;                                 \
224                                                                                     \
225     switch ((CheckData)->Check)                                                     \
226     {                                                                               \
227         case CheckLock:                                                             \
228             CheckSpinLockLock(SpinLock, CheckData, Value);                          \
229             break;                                                                  \
230         case CheckQueue:                                                            \
231             CheckSpinLockQueue(SpinLock, CheckData, Value);                         \
232             break;                                                                  \
233         case CheckQueueHandle:                                                      \
234             CheckSpinLockQueueHandle(SpinLock, CheckData, Value);                   \
235             break;                                                                  \
236     }                                                                               \
237                                                                                     \
238     if ((CheckData)->IsAcquired)                                                    \
239         ExpectedIrql = (CheckData)->IrqlWhenAcquired;                               \
240     ok_irql(ExpectedIrql);                                                          \
241     ok_bool_false(KeAreApcsDisabled(), "KeAreApcsDisabled returned");               \
242     ok_bool_true(KmtAreInterruptsEnabled(), "Interrupts enabled:");                 \
243 } while (0)
244 
245 static
246 VOID
247 TestSpinLock(
248     PKSPIN_LOCK SpinLock,
249     PCHECK_DATA CheckData)
250 {
251     static INT Run = 0;
252     trace("Test SpinLock run %d\n", Run++);
253 
254     ok_irql(CheckData->OriginalIrql);
255 
256     if (SpinLock)
257         ok_eq_ulongptr(*SpinLock, 0);
258     CheckData->Acquire(SpinLock, CheckData);
259     CheckSpinLock(SpinLock, CheckData, 1);
260     CheckData->Release(SpinLock, CheckData);
261     CheckSpinLock(SpinLock, CheckData, 0);
262 
263     if (CheckData->TryAcquire)
264     {
265         CheckSpinLock(SpinLock, CheckData, 0);
266         ok_bool_true(CheckData->TryAcquire(SpinLock, CheckData), "TryAcquire returned");
267         CheckSpinLock(SpinLock, CheckData, 1);
268         if (!KmtIsCheckedBuild)
269         {
270             /* SPINLOCK_ALREADY_OWNED on checked build */
271             ok_bool_true(CheckData->TryAcquire(SpinLock, CheckData), "TryAcquire returned");
272             /* even a failing acquire sets irql */
273             ok_eq_uint(CheckData->Irql, CheckData->IrqlWhenAcquired);
274             CheckData->Irql = CheckData->OriginalIrql;
275             CheckSpinLock(SpinLock, CheckData, 1);
276         }
277         CheckData->Release(SpinLock, CheckData);
278         CheckSpinLock(SpinLock, CheckData, 0);
279     }
280 
281     if (CheckData->AcquireNoRaise &&
282         (CheckData->OriginalIrql >= DISPATCH_LEVEL || !KmtIsCheckedBuild) &&
283         (CheckData->AcquireNoRaise != AcquireInStackForDpc ||
284          !skip(pKeAcquireInStackQueuedSpinLockForDpc &&
285                pKeReleaseInStackQueuedSpinLockForDpc, "No DPC spinlock functions\n")))
286     {
287         /* acquire/release without irql change */
288         CheckData->AcquireNoRaise(SpinLock, CheckData);
289         CheckSpinLock(SpinLock, CheckData, 1);
290         CheckData->ReleaseNoLower(SpinLock, CheckData);
291         CheckSpinLock(SpinLock, CheckData, 0);
292 
293         /* acquire without raise, but normal release */
294         CheckData->AcquireNoRaise(SpinLock, CheckData);
295         CheckSpinLock(SpinLock, CheckData, 1);
296         CheckData->Release(SpinLock, CheckData);
297         CheckSpinLock(SpinLock, CheckData, 0);
298 
299         /* acquire normally but release without lower */
300         CheckData->Acquire(SpinLock, CheckData);
301         CheckSpinLock(SpinLock, CheckData, 1);
302         CheckData->ReleaseNoLower(SpinLock, CheckData);
303         CheckSpinLock(SpinLock, CheckData, 0);
304         CheckData->IsAcquired = FALSE;
305         KmtSetIrql(CheckData->OriginalIrql);
306 
307         if (CheckData->TryAcquireNoRaise &&
308             !skip(pKeTryToAcquireSpinLockAtDpcLevel != NULL, "KeTryToAcquireSpinLockAtDpcLevel unavailable\n"))
309         {
310             CheckSpinLock(SpinLock, CheckData, 0);
311             ok_bool_true(CheckData->TryAcquireNoRaise(SpinLock, CheckData), "TryAcquireNoRaise returned");
312             CheckSpinLock(SpinLock, CheckData, 1);
313             if (!KmtIsCheckedBuild)
314             {
315                 ok_bool_true(CheckData->TryAcquireNoRaise(SpinLock, CheckData), "TryAcquireNoRaise returned");
316                 CheckSpinLock(SpinLock, CheckData, 1);
317             }
318             CheckData->ReleaseNoLower(SpinLock, CheckData);
319             CheckSpinLock(SpinLock, CheckData, 0);
320         }
321     }
322 
323     ok_irql(CheckData->OriginalIrql);
324     /* make sure we survive this in case of error */
325     KmtSetIrql(CheckData->OriginalIrql);
326 }
327 
328 START_TEST(KeSpinLock)
329 {
330     KSPIN_LOCK SpinLock = (KSPIN_LOCK)0x5555555555555555LL;
331     PKSPIN_LOCK pSpinLock = &SpinLock;
332     KIRQL Irql, SynchIrql = KmtIsMultiProcessorBuild ? IPI_LEVEL - 2 : DISPATCH_LEVEL;
333     KIRQL OriginalIrqls[] = { PASSIVE_LEVEL, APC_LEVEL, DISPATCH_LEVEL, HIGH_LEVEL };
334     CHECK_DATA TestData[] =
335     {
336         { CheckLock,        DISPATCH_LEVEL, AcquireNormal,        ReleaseNormal,        NULL,           AcquireNoRaise,        ReleaseNoLower,        TryNoRaise },
337         { CheckLock,        DISPATCH_LEVEL, AcquireExp,           ReleaseExp,           NULL,           AcquireExpNoRaise,     ReleaseExpNoLower,     NULL },
338         /* TODO: this one is just weird!
339         { CheckLock,        DISPATCH_LEVEL, AcquireNormal,        ReleaseNormal,        NULL,           AcquireForDpc,         ReleaseForDpc,         NULL },*/
340         { CheckLock,        DISPATCH_LEVEL, AcquireNormal,        ReleaseNormal,        NULL,           AcquireInt,            ReleaseInt,            NULL },
341         { CheckLock,        SynchIrql,      AcquireSynch,         ReleaseNormal,        NULL,           NULL,                  NULL,                  NULL },
342         { CheckQueueHandle, DISPATCH_LEVEL, AcquireInStackQueued, ReleaseInStackQueued, NULL,           AcquireInStackNoRaise, ReleaseInStackNoRaise, NULL },
343         { CheckQueueHandle, SynchIrql,      AcquireInStackSynch,  ReleaseInStackQueued, NULL,           NULL,                  NULL,                  NULL },
344         { CheckQueueHandle, DISPATCH_LEVEL, AcquireInStackQueued, ReleaseInStackQueued, NULL,           AcquireInStackForDpc,  ReleaseInStackForDpc,  NULL },
345         { CheckQueue,       DISPATCH_LEVEL, AcquireQueued,        ReleaseQueued,        TryQueued,      NULL,                  NULL,                  NULL,       LockQueuePfnLock },
346         { CheckQueue,       SynchIrql,      AcquireQueuedSynch,   ReleaseQueued,        TryQueuedSynch, NULL,                  NULL,                  NULL,       LockQueuePfnLock },
347     };
348     int i, iIrql;
349     PKPRCB Prcb;
350 
351     pKeTryToAcquireSpinLockAtDpcLevel = KmtGetSystemRoutineAddress(L"KeTryToAcquireSpinLockAtDpcLevel");
352     pKeAcquireInStackQueuedSpinLockForDpc = KmtGetSystemRoutineAddress(L"KeAcquireInStackQueuedSpinLockForDpc");
353     pKeReleaseInStackQueuedSpinLockForDpc = KmtGetSystemRoutineAddress(L"KeReleaseInStackQueuedSpinLockForDpc");
354     pKeTestSpinLock = KmtGetSystemRoutineAddress(L"KeTestSpinLock");
355 
356     Prcb = KeGetCurrentPrcb();
357 
358     /* KeInitializeSpinLock */
359     memset(&SpinLock, 0x55, sizeof SpinLock);
360     KeInitializeSpinLock(&SpinLock);
361     ok_eq_ulongptr(SpinLock, 0);
362 
363     /* KeTestSpinLock */
364     if (!skip(pKeTestSpinLock != NULL, "KeTestSpinLock unavailable\n"))
365     {
366         ok_bool_true(pKeTestSpinLock(&SpinLock), "KeTestSpinLock returned");
367         SpinLock = 1;
368         ok_bool_false(pKeTestSpinLock(&SpinLock), "KeTestSpinLock returned");
369         SpinLock = 2;
370         ok_bool_false(pKeTestSpinLock(&SpinLock), "KeTestSpinLock returned");
371         SpinLock = (ULONG_PTR)-1;
372         ok_bool_false(pKeTestSpinLock(&SpinLock), "KeTestSpinLock returned");
373         SpinLock = (ULONG_PTR)1 << (sizeof(ULONG_PTR) * CHAR_BIT - 1);
374         ok_bool_false(pKeTestSpinLock(&SpinLock), "KeTestSpinLock returned");
375         SpinLock = 0;
376         ok_bool_true(pKeTestSpinLock(&SpinLock), "KeTestSpinLock returned");
377     }
378 
379     /* on UP none of the following functions actually looks at the spinlock! */
380     if (!KmtIsMultiProcessorBuild && !KmtIsCheckedBuild)
381         pSpinLock = NULL;
382 
383     for (i = 0; i < sizeof TestData / sizeof TestData[0]; ++i)
384     {
385         memset(&SpinLock, 0x55, sizeof SpinLock);
386         KeInitializeSpinLock(&SpinLock);
387         if (TestData[i].Check == CheckQueueHandle)
388             memset(&TestData[i].QueueHandle, 0x55, sizeof TestData[i].QueueHandle);
389         if (TestData[i].Check == CheckQueue)
390         {
391             TestData[i].Queue = &Prcb->LockQueue[TestData[i].QueueNumber];
392             TestData[i].UntouchedValue = NULL;
393         }
394         else
395             TestData[i].UntouchedValue = (PVOID)0x5555555555555555LL;
396 
397         for (iIrql = 0; iIrql < sizeof OriginalIrqls / sizeof OriginalIrqls[0]; ++iIrql)
398         {
399             if (KmtIsCheckedBuild && OriginalIrqls[iIrql] > DISPATCH_LEVEL)
400                 continue;
401             KeRaiseIrql(OriginalIrqls[iIrql], &Irql);
402             TestData[i].OriginalIrql = OriginalIrqls[iIrql];
403             TestData[i].IsAcquired = FALSE;
404             TestSpinLock(pSpinLock, &TestData[i]);
405             KeLowerIrql(Irql);
406         }
407     }
408 
409     KmtSetIrql(PASSIVE_LEVEL);
410 }
411