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