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 }
407