1 /*
2  * PROJECT:         ReactOS kernel-mode tests
3  * LICENSE:         GPLv2+ - See COPYING in the top level directory
4  * PURPOSE:         Kernel-Mode Test Suite Fast Mutex test
5  * PROGRAMMER:      Thomas Faber <thomas.faber@reactos.org>
6  */
7 
8 #include <kmt_test.h>
9 
10 //#define NDEBUG
11 #include <debug.h>
12 
13 static
14 VOID
15 (FASTCALL
16 *pExEnterCriticalRegionAndAcquireFastMutexUnsafe)(
17     _Inout_ PFAST_MUTEX FastMutex
18 );
19 
20 static
21 VOID
22 (FASTCALL
23 *pExReleaseFastMutexUnsafeAndLeaveCriticalRegion)(
24     _Inout_ PFAST_MUTEX FastMutex
25 );
26 
27 static VOID    (FASTCALL *pExiAcquireFastMutex)(IN OUT PFAST_MUTEX FastMutex);
28 static VOID    (FASTCALL *pExiReleaseFastMutex)(IN OUT PFAST_MUTEX FastMutex);
29 static BOOLEAN (FASTCALL *pExiTryToAcquireFastMutex)(IN OUT PFAST_MUTEX FastMutex);
30 
31 #define CheckMutex(Mutex, ExpectedCount, ExpectedOwner,                 \
32                    ExpectedContention, ExpectedOldIrql,                 \
33                    ExpectedIrql) do                                     \
34 {                                                                       \
35     ok_eq_long((Mutex)->Count, ExpectedCount);                          \
36     ok_eq_pointer((Mutex)->Owner, ExpectedOwner);                       \
37     ok_eq_ulong((Mutex)->Contention, ExpectedContention);               \
38     ok_eq_ulong((Mutex)->OldIrql, (ULONG)ExpectedOldIrql);              \
39     ok_bool_false(KeAreApcsDisabled(), "KeAreApcsDisabled returned");   \
40     ok_irql(ExpectedIrql);                                              \
41 } while (0)
42 
43 static
44 VOID
45 TestFastMutex(
46     PFAST_MUTEX Mutex,
47     KIRQL OriginalIrql)
48 {
49     PKTHREAD Thread = KeGetCurrentThread();
50 
51     ok_irql(OriginalIrql);
52 
53     /* acquire/release normally */
54     ExAcquireFastMutex(Mutex);
55     CheckMutex(Mutex, 0L, Thread, 0LU, OriginalIrql, APC_LEVEL);
56     ok_bool_false(ExTryToAcquireFastMutex(Mutex), "ExTryToAcquireFastMutex returned");
57     CheckMutex(Mutex, 0L, Thread, 0LU, OriginalIrql, APC_LEVEL);
58     ExReleaseFastMutex(Mutex);
59     CheckMutex(Mutex, 1L, NULL, 0LU, OriginalIrql, OriginalIrql);
60 
61     /* ntoskrnl's fastcall version */
62     if (!skip(pExiAcquireFastMutex &&
63               pExiReleaseFastMutex &&
64               pExiTryToAcquireFastMutex, "No fastcall fast mutex functions\n"))
65     {
66         pExiAcquireFastMutex(Mutex);
67         CheckMutex(Mutex, 0L, Thread, 0LU, OriginalIrql, APC_LEVEL);
68         ok_bool_false(pExiTryToAcquireFastMutex(Mutex), "ExiTryToAcquireFastMutex returned");
69         CheckMutex(Mutex, 0L, Thread, 0LU, OriginalIrql, APC_LEVEL);
70         pExiReleaseFastMutex(Mutex);
71         CheckMutex(Mutex, 1L, NULL, 0LU, OriginalIrql, OriginalIrql);
72     }
73 
74     /* try to acquire */
75     ok_bool_true(ExTryToAcquireFastMutex(Mutex), "ExTryToAcquireFastMutex returned");
76     CheckMutex(Mutex, 0L, Thread, 0LU, OriginalIrql, APC_LEVEL);
77     ExReleaseFastMutex(Mutex);
78     CheckMutex(Mutex, 1L, NULL, 0LU, OriginalIrql, OriginalIrql);
79 
80     /* shortcut functions with critical region */
81     if (!skip(pExEnterCriticalRegionAndAcquireFastMutexUnsafe &&
82               pExReleaseFastMutexUnsafeAndLeaveCriticalRegion,
83               "Shortcut functions not available"))
84     {
85         pExEnterCriticalRegionAndAcquireFastMutexUnsafe(Mutex);
86         ok_bool_true(KeAreApcsDisabled(), "KeAreApcsDisabled returned");
87         pExReleaseFastMutexUnsafeAndLeaveCriticalRegion(Mutex);
88     }
89 
90     /* acquire/release unsafe */
91     if (!KmtIsCheckedBuild || OriginalIrql == APC_LEVEL)
92     {
93         ExAcquireFastMutexUnsafe(Mutex);
94         CheckMutex(Mutex, 0L, Thread, 0LU, OriginalIrql, OriginalIrql);
95         ExReleaseFastMutexUnsafe(Mutex);
96         CheckMutex(Mutex, 1L, NULL, 0LU, OriginalIrql, OriginalIrql);
97 
98         /* mismatched acquire/release */
99         ExAcquireFastMutex(Mutex);
100         CheckMutex(Mutex, 0L, Thread, 0LU, OriginalIrql, APC_LEVEL);
101         ExReleaseFastMutexUnsafe(Mutex);
102         CheckMutex(Mutex, 1L, NULL, 0LU, OriginalIrql, APC_LEVEL);
103         KmtSetIrql(OriginalIrql);
104         CheckMutex(Mutex, 1L, NULL, 0LU, OriginalIrql, OriginalIrql);
105 
106         Mutex->OldIrql = 0x55555555LU;
107         ExAcquireFastMutexUnsafe(Mutex);
108         CheckMutex(Mutex, 0L, Thread, 0LU, 0x55555555LU, OriginalIrql);
109         Mutex->OldIrql = PASSIVE_LEVEL;
110         ExReleaseFastMutex(Mutex);
111         CheckMutex(Mutex, 1L, NULL, 0LU, PASSIVE_LEVEL, PASSIVE_LEVEL);
112         KmtSetIrql(OriginalIrql);
113         CheckMutex(Mutex, 1L, NULL, 0LU, PASSIVE_LEVEL, OriginalIrql);
114     }
115 
116     if (!KmtIsCheckedBuild)
117     {
118         /* release without acquire */
119         ExReleaseFastMutexUnsafe(Mutex);
120         CheckMutex(Mutex, 2L, NULL, 0LU, PASSIVE_LEVEL, OriginalIrql);
121         --Mutex->Count;
122         Mutex->OldIrql = OriginalIrql;
123         ExReleaseFastMutex(Mutex);
124         CheckMutex(Mutex, 2L, NULL, 0LU, OriginalIrql, OriginalIrql);
125         ExReleaseFastMutex(Mutex);
126         CheckMutex(Mutex, 3L, NULL, 0LU, OriginalIrql, OriginalIrql);
127         Mutex->Count -= 2;
128     }
129 
130     /* make sure we survive this in case of error */
131     ok_eq_long(Mutex->Count, 1L);
132     Mutex->Count = 1;
133     ok_irql(OriginalIrql);
134     KmtSetIrql(OriginalIrql);
135 }
136 
137 typedef VOID (FASTCALL *PMUTEX_FUNCTION)(PFAST_MUTEX);
138 typedef BOOLEAN (FASTCALL *PMUTEX_TRY_FUNCTION)(PFAST_MUTEX);
139 
140 typedef struct
141 {
142     HANDLE Handle;
143     PKTHREAD Thread;
144     KIRQL Irql;
145     PFAST_MUTEX Mutex;
146     PMUTEX_FUNCTION Acquire;
147     PMUTEX_TRY_FUNCTION TryAcquire;
148     PMUTEX_FUNCTION Release;
149     BOOLEAN Try;
150     BOOLEAN RetExpected;
151     KEVENT InEvent;
152     KEVENT OutEvent;
153 } THREAD_DATA, *PTHREAD_DATA;
154 
155 static
156 VOID
157 NTAPI
158 AcquireMutexThread(
159     PVOID Parameter)
160 {
161     PTHREAD_DATA ThreadData = Parameter;
162     KIRQL Irql;
163     BOOLEAN Ret = FALSE;
164     NTSTATUS Status;
165 
166     KeRaiseIrql(ThreadData->Irql, &Irql);
167 
168     if (ThreadData->Try)
169     {
170         Ret = ThreadData->TryAcquire(ThreadData->Mutex);
171         ok_eq_bool(Ret, ThreadData->RetExpected);
172     }
173     else
174         ThreadData->Acquire(ThreadData->Mutex);
175 
176     ok_bool_false(KeSetEvent(&ThreadData->OutEvent, 0, TRUE), "KeSetEvent returned");
177     Status = KeWaitForSingleObject(&ThreadData->InEvent, Executive, KernelMode, FALSE, NULL);
178     ok_eq_hex(Status, STATUS_SUCCESS);
179 
180     if (!ThreadData->Try || Ret)
181         ThreadData->Release(ThreadData->Mutex);
182 
183     KeLowerIrql(Irql);
184 }
185 
186 static
187 VOID
188 InitThreadData(
189     PTHREAD_DATA ThreadData,
190     PFAST_MUTEX Mutex,
191     PMUTEX_FUNCTION Acquire,
192     PMUTEX_TRY_FUNCTION TryAcquire,
193     PMUTEX_FUNCTION Release)
194 {
195     ThreadData->Mutex = Mutex;
196     KeInitializeEvent(&ThreadData->InEvent, NotificationEvent, FALSE);
197     KeInitializeEvent(&ThreadData->OutEvent, NotificationEvent, FALSE);
198     ThreadData->Acquire = Acquire;
199     ThreadData->TryAcquire = TryAcquire;
200     ThreadData->Release = Release;
201 }
202 
203 static
204 NTSTATUS
205 StartThread(
206     PTHREAD_DATA ThreadData,
207     PLARGE_INTEGER Timeout,
208     KIRQL Irql,
209     BOOLEAN Try,
210     BOOLEAN RetExpected)
211 {
212     NTSTATUS Status = STATUS_SUCCESS;
213     OBJECT_ATTRIBUTES Attributes;
214 
215     ThreadData->Try = Try;
216     ThreadData->Irql = Irql;
217     ThreadData->RetExpected = RetExpected;
218     InitializeObjectAttributes(&Attributes, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);
219     Status = PsCreateSystemThread(&ThreadData->Handle, GENERIC_ALL, &Attributes, NULL, NULL, AcquireMutexThread, ThreadData);
220     ok_eq_hex(Status, STATUS_SUCCESS);
221     Status = ObReferenceObjectByHandle(ThreadData->Handle, SYNCHRONIZE, *PsThreadType, KernelMode, (PVOID *)&ThreadData->Thread, NULL);
222     ok_eq_hex(Status, STATUS_SUCCESS);
223 
224     return KeWaitForSingleObject(&ThreadData->OutEvent, Executive, KernelMode, FALSE, Timeout);
225 }
226 
227 static
228 VOID
229 FinishThread(
230     PTHREAD_DATA ThreadData)
231 {
232     NTSTATUS Status = STATUS_SUCCESS;
233 
234     KeSetEvent(&ThreadData->InEvent, 0, TRUE);
235     Status = KeWaitForSingleObject(ThreadData->Thread, Executive, KernelMode, FALSE, NULL);
236     ok_eq_hex(Status, STATUS_SUCCESS);
237 
238     ObDereferenceObject(ThreadData->Thread);
239     Status = ZwClose(ThreadData->Handle);
240     ok_eq_hex(Status, STATUS_SUCCESS);
241     KeClearEvent(&ThreadData->InEvent);
242     KeClearEvent(&ThreadData->OutEvent);
243 }
244 
245 static
246 VOID
247 TestFastMutexConcurrent(
248     PFAST_MUTEX Mutex)
249 {
250     NTSTATUS Status;
251     THREAD_DATA ThreadData;
252     THREAD_DATA ThreadData2;
253     THREAD_DATA ThreadDataUnsafe;
254     THREAD_DATA ThreadDataTry;
255     LARGE_INTEGER Timeout;
256     Timeout.QuadPart = -50 * MILLISECOND;
257 
258     InitThreadData(&ThreadData, Mutex, ExAcquireFastMutex, NULL, ExReleaseFastMutex);
259     InitThreadData(&ThreadData2, Mutex, ExAcquireFastMutex, NULL, ExReleaseFastMutex);
260     InitThreadData(&ThreadDataUnsafe, Mutex, ExAcquireFastMutexUnsafe, NULL, ExReleaseFastMutexUnsafe);
261     InitThreadData(&ThreadDataTry, Mutex, NULL, ExTryToAcquireFastMutex, ExReleaseFastMutex);
262 
263     /* have a thread acquire the mutex */
264     Status = StartThread(&ThreadData, NULL, PASSIVE_LEVEL, FALSE, FALSE);
265     ok_eq_hex(Status, STATUS_SUCCESS);
266     CheckMutex(Mutex, 0L, ThreadData.Thread, 0LU, PASSIVE_LEVEL, PASSIVE_LEVEL);
267     /* have a second thread try to acquire it -- should fail */
268     Status = StartThread(&ThreadDataTry, NULL, PASSIVE_LEVEL, TRUE, FALSE);
269     ok_eq_hex(Status, STATUS_SUCCESS);
270     CheckMutex(Mutex, 0L, ThreadData.Thread, 0LU, PASSIVE_LEVEL, PASSIVE_LEVEL);
271     FinishThread(&ThreadDataTry);
272 
273     /* have another thread acquire it -- should block */
274     Status = StartThread(&ThreadData2, &Timeout, APC_LEVEL, FALSE, FALSE);
275     ok_eq_hex(Status, STATUS_TIMEOUT);
276     CheckMutex(Mutex, -1L, ThreadData.Thread, 1LU, PASSIVE_LEVEL, PASSIVE_LEVEL);
277 
278     /* finish the first thread -- now the second should become available */
279     FinishThread(&ThreadData);
280     Status = KeWaitForSingleObject(&ThreadData2.OutEvent, Executive, KernelMode, FALSE, NULL);
281     ok_eq_hex(Status, STATUS_SUCCESS);
282     CheckMutex(Mutex, 0L, ThreadData2.Thread, 1LU, APC_LEVEL, PASSIVE_LEVEL);
283 
284     /* block two more threads */
285     Status = StartThread(&ThreadDataUnsafe, &Timeout, APC_LEVEL, FALSE, FALSE);
286     ok_eq_hex(Status, STATUS_TIMEOUT);
287     CheckMutex(Mutex, -1L, ThreadData2.Thread, 2LU, APC_LEVEL, PASSIVE_LEVEL);
288 
289     Status = StartThread(&ThreadData, &Timeout, PASSIVE_LEVEL, FALSE, FALSE);
290     ok_eq_hex(Status, STATUS_TIMEOUT);
291     CheckMutex(Mutex, -2L, ThreadData2.Thread, 3LU, APC_LEVEL, PASSIVE_LEVEL);
292 
293     /* finish 1 */
294     FinishThread(&ThreadData2);
295     Status = KeWaitForSingleObject(&ThreadDataUnsafe.OutEvent, Executive, KernelMode, FALSE, NULL);
296     ok_eq_hex(Status, STATUS_SUCCESS);
297     CheckMutex(Mutex, -1L, ThreadDataUnsafe.Thread, 3LU, APC_LEVEL, PASSIVE_LEVEL);
298 
299     /* finish 2 */
300     FinishThread(&ThreadDataUnsafe);
301     Status = KeWaitForSingleObject(&ThreadData.OutEvent, Executive, KernelMode, FALSE, NULL);
302     ok_eq_hex(Status, STATUS_SUCCESS);
303     CheckMutex(Mutex, 0L, ThreadData.Thread, 3LU, PASSIVE_LEVEL, PASSIVE_LEVEL);
304 
305     /* finish 3 */
306     FinishThread(&ThreadData);
307 
308     CheckMutex(Mutex, 1L, NULL, 3LU, PASSIVE_LEVEL, PASSIVE_LEVEL);
309 }
310 
311 START_TEST(ExFastMutex)
312 {
313 #if defined(_M_AMD64)
314     if (TRUE)
315     {
316         skip(FALSE, "ROSTESTS-367: Skipping kmtest:ExFastMutex because it hangs on Windows Server 2003 x64-Testbot.\n");
317         return;
318     }
319 #endif
320 
321     FAST_MUTEX Mutex;
322     KIRQL Irql;
323 
324     pExEnterCriticalRegionAndAcquireFastMutexUnsafe = KmtGetSystemRoutineAddress(L"ExEnterCriticalRegionAndAcquireFastMutexUnsafe");
325     pExReleaseFastMutexUnsafeAndLeaveCriticalRegion = KmtGetSystemRoutineAddress(L"ExReleaseFastMutexUnsafeAndLeaveCriticalRegion");
326 
327     pExiAcquireFastMutex = KmtGetSystemRoutineAddress(L"ExiAcquireFastMutex");
328     pExiReleaseFastMutex = KmtGetSystemRoutineAddress(L"ExiReleaseFastMutex");
329     pExiTryToAcquireFastMutex = KmtGetSystemRoutineAddress(L"ExiTryToAcquireFastMutex");
330 
331     memset(&Mutex, 0x55, sizeof Mutex);
332     ExInitializeFastMutex(&Mutex);
333     CheckMutex(&Mutex, 1L, NULL, 0LU, 0x55555555LU, PASSIVE_LEVEL);
334 
335     TestFastMutex(&Mutex, PASSIVE_LEVEL);
336     KeRaiseIrql(APC_LEVEL, &Irql);
337     TestFastMutex(&Mutex, APC_LEVEL);
338     if (!KmtIsCheckedBuild)
339     {
340         KeRaiseIrql(DISPATCH_LEVEL, &Irql);
341         TestFastMutex(&Mutex, DISPATCH_LEVEL);
342         KeRaiseIrql(HIGH_LEVEL, &Irql);
343         TestFastMutex(&Mutex, HIGH_LEVEL);
344     }
345     KeLowerIrql(PASSIVE_LEVEL);
346 
347     TestFastMutexConcurrent(&Mutex);
348 }
349