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
DEFINE_ACQUIRE(AcquireNoRaise,FALSE,KeAcquireSpinLockAtDpcLevel (SpinLock))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 }
TryQueuedSynch(PKSPIN_LOCK SpinLock,PCHECK_DATA CheckData)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 }
TryNoRaise(PKSPIN_LOCK SpinLock,PCHECK_DATA CheckData)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
TestSpinLock(PKSPIN_LOCK SpinLock,PCHECK_DATA CheckData)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
START_TEST(KeSpinLock)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