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