1 /* 2 * PROJECT: ReactOS kernel-mode tests 3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) 4 * PURPOSE: Kernel-Mode Test Suite Spin lock test 5 * COPYRIGHT: Copyright 2011-2023 Thomas Faber (thomas.faber@reactos.org) 6 * COPYRIGHT: Copyright 2021 Jérôme Gardou (jerome.gardou@reactos.org) 7 */ 8 9 #ifndef _WIN64 10 __declspec(dllimport) void __stdcall KeAcquireSpinLock(unsigned long *, unsigned char *); 11 __declspec(dllimport) void __stdcall KeReleaseSpinLock(unsigned long *, unsigned char); 12 __declspec(dllimport) void __stdcall KeAcquireSpinLockAtDpcLevel(unsigned long *); 13 __declspec(dllimport) void __stdcall KeReleaseSpinLockFromDpcLevel(unsigned long *); 14 #endif 15 16 /* this define makes KeInitializeSpinLock not use the inlined version */ 17 #define WIN9X_COMPAT_SPINLOCK 18 #include <kmt_test.h> 19 #include <limits.h> 20 21 //#define NDEBUG 22 #include <debug.h> 23 24 static 25 _Must_inspect_result_ 26 _IRQL_requires_min_(DISPATCH_LEVEL) 27 _Post_satisfies_(return == 1 || return == 0) 28 BOOLEAN 29 (FASTCALL 30 *pKeTryToAcquireSpinLockAtDpcLevel)( 31 _Inout_ _Requires_lock_not_held_(*_Curr_) 32 _When_(return!=0, _Acquires_lock_(*_Curr_)) 33 PKSPIN_LOCK SpinLock); 34 35 static 36 VOID 37 (FASTCALL 38 *pKeAcquireInStackQueuedSpinLockForDpc)( 39 IN OUT PKSPIN_LOCK SpinLock, 40 OUT PKLOCK_QUEUE_HANDLE LockHandle); 41 42 static 43 VOID 44 (FASTCALL 45 *pKeReleaseInStackQueuedSpinLockForDpc)( 46 IN PKLOCK_QUEUE_HANDLE LockHandle); 47 48 static 49 _Must_inspect_result_ 50 BOOLEAN 51 (FASTCALL 52 *pKeTestSpinLock)( 53 _In_ PKSPIN_LOCK SpinLock); 54 55 /* TODO: multiprocessor testing */ 56 57 struct _CHECK_DATA; 58 typedef struct _CHECK_DATA CHECK_DATA, *PCHECK_DATA; 59 60 typedef VOID (*PACQUIRE_FUNCTION)(PKSPIN_LOCK, PCHECK_DATA); 61 typedef VOID (*PRELEASE_FUNCTION)(PKSPIN_LOCK, PCHECK_DATA); 62 typedef BOOLEAN (*PTRY_FUNCTION)(PKSPIN_LOCK, PCHECK_DATA); 63 64 struct _CHECK_DATA 65 { 66 enum 67 { 68 CheckQueueHandle, 69 CheckQueue, 70 CheckLock 71 } Check; 72 KIRQL IrqlWhenAcquired; 73 PACQUIRE_FUNCTION Acquire; 74 PRELEASE_FUNCTION Release; 75 PTRY_FUNCTION TryAcquire; 76 PACQUIRE_FUNCTION AcquireNoRaise; 77 PRELEASE_FUNCTION ReleaseNoLower; 78 PTRY_FUNCTION TryAcquireNoRaise; 79 KSPIN_LOCK_QUEUE_NUMBER QueueNumber; 80 BOOLEAN TryRetOnFailure; 81 KIRQL OriginalIrql; 82 BOOLEAN IsAcquired; 83 _ANONYMOUS_UNION union 84 { 85 KLOCK_QUEUE_HANDLE QueueHandle; 86 PKSPIN_LOCK_QUEUE Queue; 87 KIRQL Irql; 88 } DUMMYUNIONNAME; 89 PVOID UntouchedValue; 90 }; 91 92 #define DEFINE_ACQUIRE(LocalName, SetIsAcquired, DoCall) \ 93 static VOID LocalName(PKSPIN_LOCK SpinLock, PCHECK_DATA CheckData) \ 94 { \ 95 ASSERT(!CheckData->IsAcquired); \ 96 DoCall; \ 97 if (SetIsAcquired) CheckData->IsAcquired = TRUE; \ 98 } 99 100 #define DEFINE_RELEASE(LocalName, SetIsAcquired, DoCall) \ 101 static VOID LocalName(PKSPIN_LOCK SpinLock, PCHECK_DATA CheckData) \ 102 { \ 103 DoCall; \ 104 if (SetIsAcquired) CheckData->IsAcquired = FALSE; \ 105 } 106 107 DEFINE_ACQUIRE(AcquireNormal, TRUE, KeAcquireSpinLock(SpinLock, &CheckData->Irql)) 108 DEFINE_RELEASE(ReleaseNormal, TRUE, KeReleaseSpinLock(SpinLock, CheckData->Irql)) 109 #ifdef _X86_ 110 DEFINE_ACQUIRE(AcquireExp, TRUE, (KeAcquireSpinLock)(SpinLock, &CheckData->Irql)) 111 DEFINE_RELEASE(ReleaseExp, TRUE, (KeReleaseSpinLock)(SpinLock, CheckData->Irql)) 112 #else 113 DEFINE_ACQUIRE(AcquireExp, TRUE, KeAcquireSpinLock(SpinLock, &CheckData->Irql)) 114 DEFINE_RELEASE(ReleaseExp, TRUE, KeReleaseSpinLock(SpinLock, CheckData->Irql)) 115 #endif 116 DEFINE_ACQUIRE(AcquireSynch, TRUE, CheckData->Irql = KeAcquireSpinLockRaiseToSynch(SpinLock)) 117 118 DEFINE_ACQUIRE(AcquireInStackQueued, TRUE, KeAcquireInStackQueuedSpinLock(SpinLock, &CheckData->QueueHandle)) 119 DEFINE_ACQUIRE(AcquireInStackSynch, TRUE, KeAcquireInStackQueuedSpinLockRaiseToSynch(SpinLock, &CheckData->QueueHandle)) 120 DEFINE_RELEASE(ReleaseInStackQueued, TRUE, KeReleaseInStackQueuedSpinLock(&CheckData->QueueHandle)) 121 122 DEFINE_ACQUIRE(AcquireQueued, TRUE, CheckData->Irql = KeAcquireQueuedSpinLock(CheckData->QueueNumber)) 123 DEFINE_ACQUIRE(AcquireQueuedSynch, TRUE, CheckData->Irql = KeAcquireQueuedSpinLockRaiseToSynch(CheckData->QueueNumber)) 124 DEFINE_RELEASE(ReleaseQueued, TRUE, KeReleaseQueuedSpinLock(CheckData->QueueNumber, CheckData->Irql)) 125 126 DEFINE_ACQUIRE(AcquireNoRaise, FALSE, KeAcquireSpinLockAtDpcLevel(SpinLock)) 127 DEFINE_RELEASE(ReleaseNoLower, FALSE, KeReleaseSpinLockFromDpcLevel(SpinLock)) 128 DEFINE_ACQUIRE(AcquireExpNoRaise, FALSE, (KeAcquireSpinLockAtDpcLevel)(SpinLock)) 129 DEFINE_RELEASE(ReleaseExpNoLower, FALSE, (KeReleaseSpinLockFromDpcLevel)(SpinLock)) 130 131 DEFINE_ACQUIRE(AcquireInStackNoRaise, FALSE, KeAcquireInStackQueuedSpinLockAtDpcLevel(SpinLock, &CheckData->QueueHandle)) 132 DEFINE_RELEASE(ReleaseInStackNoRaise, FALSE, KeReleaseInStackQueuedSpinLockFromDpcLevel(&CheckData->QueueHandle)) 133 134 /* TODO: test these functions. They behave weirdly, though */ 135 #if 0 136 DEFINE_ACQUIRE(AcquireForDpc, TRUE, CheckData->Irql = KeAcquireSpinLockForDpc(SpinLock)) 137 DEFINE_RELEASE(ReleaseForDpc, TRUE, KeReleaseSpinLockForDpc(SpinLock, CheckData->Irql)) 138 #endif 139 140 DEFINE_ACQUIRE(AcquireInStackForDpc, FALSE, pKeAcquireInStackQueuedSpinLockForDpc(SpinLock, &CheckData->QueueHandle)) 141 DEFINE_RELEASE(ReleaseInStackForDpc, FALSE, pKeReleaseInStackQueuedSpinLockForDpc(&CheckData->QueueHandle)) 142 143 #ifdef _X86_ 144 DEFINE_ACQUIRE(AcquireInt, FALSE, KiAcquireSpinLock(SpinLock)) 145 DEFINE_RELEASE(ReleaseInt, FALSE, KiReleaseSpinLock(SpinLock)) 146 #else 147 DEFINE_ACQUIRE(AcquireInt, TRUE, KeAcquireSpinLock(SpinLock, &CheckData->Irql)) 148 DEFINE_RELEASE(ReleaseInt, TRUE, KeReleaseSpinLock(SpinLock, CheckData->Irql)) 149 #endif 150 151 BOOLEAN TryQueued(PKSPIN_LOCK SpinLock, PCHECK_DATA CheckData) { 152 LOGICAL Ret = KeTryToAcquireQueuedSpinLock(CheckData->QueueNumber, &CheckData->Irql); 153 CheckData->IsAcquired = TRUE; 154 ASSERT(Ret == FALSE || Ret == TRUE); 155 return (BOOLEAN)Ret; 156 } 157 BOOLEAN TryQueuedSynch(PKSPIN_LOCK SpinLock, PCHECK_DATA CheckData) { 158 BOOLEAN Ret = KeTryToAcquireQueuedSpinLockRaiseToSynch(CheckData->QueueNumber, &CheckData->Irql); 159 CheckData->IsAcquired = TRUE; 160 return Ret; 161 } 162 BOOLEAN TryNoRaise(PKSPIN_LOCK SpinLock, PCHECK_DATA CheckData) { 163 BOOLEAN Ret = pKeTryToAcquireSpinLockAtDpcLevel(SpinLock); 164 return Ret; 165 } 166 167 #define CheckSpinLockLock(SpinLock, CheckData, Value) do \ 168 { \ 169 PKTHREAD Thread = KeGetCurrentThread(); \ 170 (VOID)Thread; \ 171 if (KmtIsMultiProcessorBuild || KmtIsCheckedBuild) \ 172 { \ 173 ok_eq_bool(Ret, (Value) == 0); \ 174 if (SpinLock) \ 175 { \ 176 if (KmtIsCheckedBuild) \ 177 ok_eq_ulongptr(*(SpinLock), (Value) ? (ULONG_PTR)Thread | 1 : 0); \ 178 else \ 179 ok_eq_ulongptr(*(SpinLock), (Value) ? 1 : 0); \ 180 } \ 181 } \ 182 else \ 183 { \ 184 ok_bool_true(Ret, "KeTestSpinLock returned"); \ 185 if (SpinLock) \ 186 ok_eq_ulongptr(*(SpinLock), 0); \ 187 } \ 188 ok_eq_uint((CheckData)->Irql, (CheckData)->OriginalIrql); \ 189 } while (0) 190 191 #define CheckSpinLockQueue(SpinLock, CheckData, Value) do \ 192 { \ 193 ok_eq_pointer((CheckData)->Queue->Next, NULL); \ 194 ok_eq_pointer((CheckData)->Queue->Lock, NULL); \ 195 ok_eq_uint((CheckData)->Irql, (CheckData)->OriginalIrql); \ 196 } while (0) 197 198 #define CheckSpinLockQueueHandle(SpinLock, CheckData, Value) do \ 199 { \ 200 if (KmtIsMultiProcessorBuild || KmtIsCheckedBuild) \ 201 { \ 202 ok_eq_bool(Ret, (Value) == 0); \ 203 if (SpinLock) \ 204 ok_eq_ulongptr(*(SpinLock), \ 205 (Value) ? &(CheckData)->QueueHandle : 0); \ 206 ok_eq_pointer((CheckData)->QueueHandle.LockQueue.Next, NULL); \ 207 ok_eq_pointer((CheckData)->QueueHandle.LockQueue.Lock, \ 208 (PVOID)((ULONG_PTR)SpinLock | ((Value) ? 2 : 0))); \ 209 } \ 210 else \ 211 { \ 212 ok_bool_true(Ret, "KeTestSpinLock returned"); \ 213 if (SpinLock) \ 214 ok_eq_ulongptr(*(SpinLock), 0); \ 215 ok_eq_pointer((CheckData)->QueueHandle.LockQueue.Next, (CheckData)->UntouchedValue); \ 216 ok_eq_pointer((CheckData)->QueueHandle.LockQueue.Lock, (CheckData)->UntouchedValue); \ 217 } \ 218 ok_eq_uint((CheckData)->QueueHandle.OldIrql, (CheckData)->OriginalIrql); \ 219 } while (0) 220 221 #define CheckSpinLock(SpinLock, CheckData, Value) do \ 222 { \ 223 BOOLEAN Ret = SpinLock && pKeTestSpinLock ? pKeTestSpinLock(SpinLock) : TRUE; \ 224 KIRQL ExpectedIrql = (CheckData)->OriginalIrql; \ 225 \ 226 switch ((CheckData)->Check) \ 227 { \ 228 case CheckLock: \ 229 CheckSpinLockLock(SpinLock, CheckData, Value); \ 230 break; \ 231 case CheckQueue: \ 232 CheckSpinLockQueue(SpinLock, CheckData, Value); \ 233 break; \ 234 case CheckQueueHandle: \ 235 CheckSpinLockQueueHandle(SpinLock, CheckData, Value); \ 236 break; \ 237 } \ 238 \ 239 if ((CheckData)->IsAcquired) \ 240 ExpectedIrql = (CheckData)->IrqlWhenAcquired; \ 241 ok_irql(ExpectedIrql); \ 242 ok_bool_false(KeAreApcsDisabled(), "KeAreApcsDisabled returned"); \ 243 ok_bool_true(KmtAreInterruptsEnabled(), "Interrupts enabled:"); \ 244 } while (0) 245 246 static 247 VOID 248 TestSpinLock( 249 PKSPIN_LOCK SpinLock, 250 PCHECK_DATA CheckData) 251 { 252 static INT Run = 0; 253 trace("Test SpinLock run %d\n", Run++); 254 255 ok_irql(CheckData->OriginalIrql); 256 257 if (SpinLock) 258 ok_eq_ulongptr(*SpinLock, 0); 259 CheckData->Acquire(SpinLock, CheckData); 260 CheckSpinLock(SpinLock, CheckData, 1); 261 CheckData->Release(SpinLock, CheckData); 262 CheckSpinLock(SpinLock, CheckData, 0); 263 264 if (CheckData->TryAcquire) 265 { 266 CheckSpinLock(SpinLock, CheckData, 0); 267 ok_bool_true(CheckData->TryAcquire(SpinLock, CheckData), "TryAcquire returned"); 268 CheckSpinLock(SpinLock, CheckData, 1); 269 /* A second TryToAcquire results in SPINLOCK_ALREADY_OWNED on checked builds */ 270 if (!KmtIsCheckedBuild) 271 { 272 if (KmtIsMultiProcessorBuild) 273 { 274 /* In MP, this fails as you would expect */ 275 ok_bool_false(CheckData->TryAcquire(SpinLock, CheckData), "TryAcquire returned"); 276 } 277 else 278 { 279 /* In UP, this always succeeds: recursive acquires are illegal and parallel processors don't exist */ 280 ok_bool_true(CheckData->TryAcquire(SpinLock, CheckData), "TryAcquire returned"); 281 ok_eq_uint(CheckData->Irql, CheckData->IrqlWhenAcquired); 282 CheckData->Irql = CheckData->OriginalIrql; 283 } 284 CheckSpinLock(SpinLock, CheckData, 1); 285 } 286 CheckData->Release(SpinLock, CheckData); 287 CheckSpinLock(SpinLock, CheckData, 0); 288 } 289 290 if (CheckData->AcquireNoRaise && 291 (CheckData->OriginalIrql >= DISPATCH_LEVEL || !KmtIsCheckedBuild) && 292 (CheckData->AcquireNoRaise != AcquireInStackForDpc || 293 !skip(pKeAcquireInStackQueuedSpinLockForDpc && 294 pKeReleaseInStackQueuedSpinLockForDpc, "No DPC spinlock functions\n"))) 295 { 296 /* acquire/release without irql change */ 297 CheckData->AcquireNoRaise(SpinLock, CheckData); 298 CheckSpinLock(SpinLock, CheckData, 1); 299 CheckData->ReleaseNoLower(SpinLock, CheckData); 300 CheckSpinLock(SpinLock, CheckData, 0); 301 302 /* acquire without raise, but normal release */ 303 CheckData->AcquireNoRaise(SpinLock, CheckData); 304 CheckSpinLock(SpinLock, CheckData, 1); 305 CheckData->Release(SpinLock, CheckData); 306 CheckSpinLock(SpinLock, CheckData, 0); 307 308 /* acquire normally but release without lower */ 309 CheckData->Acquire(SpinLock, CheckData); 310 CheckSpinLock(SpinLock, CheckData, 1); 311 CheckData->ReleaseNoLower(SpinLock, CheckData); 312 CheckSpinLock(SpinLock, CheckData, 0); 313 CheckData->IsAcquired = FALSE; 314 KmtSetIrql(CheckData->OriginalIrql); 315 316 if (CheckData->TryAcquireNoRaise && 317 !skip(pKeTryToAcquireSpinLockAtDpcLevel != NULL, "KeTryToAcquireSpinLockAtDpcLevel unavailable\n")) 318 { 319 CheckSpinLock(SpinLock, CheckData, 0); 320 ok_bool_true(CheckData->TryAcquireNoRaise(SpinLock, CheckData), "TryAcquireNoRaise returned"); 321 CheckSpinLock(SpinLock, CheckData, 1); 322 if (!KmtIsCheckedBuild) 323 { 324 if (KmtIsMultiProcessorBuild) 325 { 326 /* In MP, this fails as you would expect */ 327 ok_bool_false(CheckData->TryAcquireNoRaise(SpinLock, CheckData), "TryAcquireNoRaise returned"); 328 } 329 else 330 { 331 /* In UP, this always succeeds: recursive acquires are illegal and parallel processors don't exist */ 332 ok_bool_true(CheckData->TryAcquireNoRaise(SpinLock, CheckData), "TryAcquireNoRaise returned"); 333 } 334 CheckSpinLock(SpinLock, CheckData, 1); 335 } 336 CheckData->ReleaseNoLower(SpinLock, CheckData); 337 CheckSpinLock(SpinLock, CheckData, 0); 338 } 339 } 340 341 ok_irql(CheckData->OriginalIrql); 342 /* make sure we survive this in case of error */ 343 KmtSetIrql(CheckData->OriginalIrql); 344 } 345 346 START_TEST(KeSpinLock) 347 { 348 KSPIN_LOCK SpinLock = (KSPIN_LOCK)0x5555555555555555LL; 349 PKSPIN_LOCK pSpinLock = &SpinLock; 350 KIRQL Irql, SynchIrql = KmtIsMultiProcessorBuild ? IPI_LEVEL - 2 : DISPATCH_LEVEL; 351 KIRQL OriginalIrqls[] = { PASSIVE_LEVEL, APC_LEVEL, DISPATCH_LEVEL, HIGH_LEVEL }; 352 CHECK_DATA TestData[] = 353 { 354 { CheckLock, DISPATCH_LEVEL, AcquireNormal, ReleaseNormal, NULL, AcquireNoRaise, ReleaseNoLower, TryNoRaise }, 355 { CheckLock, DISPATCH_LEVEL, AcquireExp, ReleaseExp, NULL, AcquireExpNoRaise, ReleaseExpNoLower, NULL }, 356 /* TODO: this one is just weird! 357 { CheckLock, DISPATCH_LEVEL, AcquireNormal, ReleaseNormal, NULL, AcquireForDpc, ReleaseForDpc, NULL },*/ 358 { CheckLock, DISPATCH_LEVEL, AcquireNormal, ReleaseNormal, NULL, AcquireInt, ReleaseInt, NULL }, 359 { CheckLock, SynchIrql, AcquireSynch, ReleaseNormal, NULL, NULL, NULL, NULL }, 360 { CheckQueueHandle, DISPATCH_LEVEL, AcquireInStackQueued, ReleaseInStackQueued, NULL, AcquireInStackNoRaise, ReleaseInStackNoRaise, NULL }, 361 { CheckQueueHandle, SynchIrql, AcquireInStackSynch, ReleaseInStackQueued, NULL, NULL, NULL, NULL }, 362 { CheckQueueHandle, DISPATCH_LEVEL, AcquireInStackQueued, ReleaseInStackQueued, NULL, AcquireInStackForDpc, ReleaseInStackForDpc, NULL }, 363 { CheckQueue, DISPATCH_LEVEL, AcquireQueued, ReleaseQueued, TryQueued, NULL, NULL, NULL, LockQueuePfnLock }, 364 { CheckQueue, SynchIrql, AcquireQueuedSynch, ReleaseQueued, TryQueuedSynch, NULL, NULL, NULL, LockQueuePfnLock }, 365 }; 366 int i, iIrql; 367 PKPRCB Prcb; 368 369 pKeTryToAcquireSpinLockAtDpcLevel = KmtGetSystemRoutineAddress(L"KeTryToAcquireSpinLockAtDpcLevel"); 370 pKeAcquireInStackQueuedSpinLockForDpc = KmtGetSystemRoutineAddress(L"KeAcquireInStackQueuedSpinLockForDpc"); 371 pKeReleaseInStackQueuedSpinLockForDpc = KmtGetSystemRoutineAddress(L"KeReleaseInStackQueuedSpinLockForDpc"); 372 pKeTestSpinLock = KmtGetSystemRoutineAddress(L"KeTestSpinLock"); 373 374 Prcb = KeGetCurrentPrcb(); 375 376 /* KeInitializeSpinLock */ 377 memset(&SpinLock, 0x55, sizeof SpinLock); 378 KeInitializeSpinLock(&SpinLock); 379 ok_eq_ulongptr(SpinLock, 0); 380 381 /* KeTestSpinLock */ 382 if (!skip(pKeTestSpinLock != NULL, "KeTestSpinLock unavailable\n")) 383 { 384 ok_bool_true(pKeTestSpinLock(&SpinLock), "KeTestSpinLock returned"); 385 SpinLock = 1; 386 ok_bool_false(pKeTestSpinLock(&SpinLock), "KeTestSpinLock returned"); 387 SpinLock = 2; 388 ok_bool_false(pKeTestSpinLock(&SpinLock), "KeTestSpinLock returned"); 389 SpinLock = (ULONG_PTR)-1; 390 ok_bool_false(pKeTestSpinLock(&SpinLock), "KeTestSpinLock returned"); 391 SpinLock = (ULONG_PTR)1 << (sizeof(ULONG_PTR) * CHAR_BIT - 1); 392 ok_bool_false(pKeTestSpinLock(&SpinLock), "KeTestSpinLock returned"); 393 SpinLock = 0; 394 ok_bool_true(pKeTestSpinLock(&SpinLock), "KeTestSpinLock returned"); 395 } 396 397 /* on UP none of the following functions actually looks at the spinlock! */ 398 if (!KmtIsMultiProcessorBuild && !KmtIsCheckedBuild) 399 pSpinLock = NULL; 400 401 for (i = 0; i < sizeof TestData / sizeof TestData[0]; ++i) 402 { 403 memset(&SpinLock, 0x55, sizeof SpinLock); 404 KeInitializeSpinLock(&SpinLock); 405 if (TestData[i].Check == CheckQueueHandle) 406 memset(&TestData[i].QueueHandle, 0x55, sizeof TestData[i].QueueHandle); 407 if (TestData[i].Check == CheckQueue) 408 { 409 TestData[i].Queue = &Prcb->LockQueue[TestData[i].QueueNumber]; 410 TestData[i].UntouchedValue = NULL; 411 } 412 else 413 TestData[i].UntouchedValue = (PVOID)0x5555555555555555LL; 414 415 for (iIrql = 0; iIrql < sizeof OriginalIrqls / sizeof OriginalIrqls[0]; ++iIrql) 416 { 417 if (KmtIsCheckedBuild && OriginalIrqls[iIrql] > DISPATCH_LEVEL) 418 continue; 419 KeRaiseIrql(OriginalIrqls[iIrql], &Irql); 420 TestData[i].OriginalIrql = OriginalIrqls[iIrql]; 421 TestData[i].IsAcquired = FALSE; 422 TestSpinLock(pSpinLock, &TestData[i]); 423 KeLowerIrql(Irql); 424 } 425 } 426 427 KmtSetIrql(PASSIVE_LEVEL); 428 } 429