VirtualBox

source: vbox/trunk/src/VBox/VMM/testcase/tstPDMQueue.cpp@ 103025

Last change on this file since 103025 was 99674, checked in by vboxsync, 19 months ago

VMM/PDMQueue: Fix PDMQueueAllocEx() to respect the multiple of 32 cBits input parameter requirement (mainly to fix the tstPDMQueue on ARM which uses a different ASMBitFirstSet() implementation which is not as forgiving), and a small fix to the testcase itself to not crash when allocating an item failed, which is counted as an error anyway

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 16.1 KB
Line 
1/* $Id: tstPDMQueue.cpp 99674 2023-05-08 13:27:47Z vboxsync $ */
2/** @file
3 * PDM Queue Testcase.
4 */
5
6/*
7 * Copyright (C) 2022-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28
29/*********************************************************************************************************************************
30* Header Files *
31*********************************************************************************************************************************/
32#define LOG_GROUP LOG_GROUP_PDM_QUEUE
33#define VBOX_IN_VMM
34
35#include <VBox/vmm/pdmqueue.h>
36
37#include <VBox/vmm/vm.h>
38#include <VBox/vmm/uvm.h>
39#include <VBox/vmm/vmm.h>
40
41#include <iprt/errcore.h>
42#include <VBox/log.h>
43#include <iprt/assert.h>
44#include <iprt/initterm.h>
45#include <iprt/message.h>
46#include <iprt/rand.h>
47#include <iprt/string.h>
48#include <iprt/thread.h>
49#include <iprt/test.h>
50
51
52/*********************************************************************************************************************************
53* Global Variables *
54*********************************************************************************************************************************/
55static RTTEST g_hTest;
56
57
58/*********************************************************************************************************************************
59* Test #2 - Threading *
60*********************************************************************************************************************************/
61typedef struct TEST2ITEM
62{
63 PDMQUEUEITEMCORE Core;
64 uint32_t iSeqNo;
65 uint32_t iThreadNo;
66 /* Pad it up to two cachelines to reduce noise. */
67 uint8_t abPadding[128 - sizeof(PDMQUEUEITEMCORE) - sizeof(uint32_t) * 2];
68} TEST2ITEM;
69typedef TEST2ITEM *PTEST2ITEM;
70
71static struct TEST2THREAD
72{
73 RTTHREAD hThread;
74 uint32_t iThreadNo;
75 uint32_t cMaxPending;
76 /* Pad one cache line. */
77 uint8_t abPadding1[64];
78 uint32_t volatile cPending;
79 uint32_t volatile iReceiveSeqNo;
80 /* Pad structure size to three cache lines. */
81 uint8_t abPadding2[64 * 2 - sizeof(uint32_t) * 4 - sizeof(RTTHREAD)];
82} g_aTest2Threads[16];
83
84static bool volatile g_fTest2Terminate = false;
85static uint32_t volatile g_cTest2Threads = 0;
86static uint32_t g_cTest2Received = 0;
87static bool volatile g_fTest2PushBack = false;
88static PVM volatile g_pTest2VM = NULL; /**< Volatile to force local copy in thread function. */
89static PDMQUEUEHANDLE volatile g_hTest2Queue = NIL_PDMQUEUEHANDLE; /**< Ditto. */
90
91
92
93/**
94 * @callback_method_impl{FNPDMQUEUEEXT, Consumer callback}
95 */
96static DECLCALLBACK(bool) Test2ConsumerCallback(void *pvUser, PPDMQUEUEITEMCORE pItem)
97{
98 PTEST2ITEM pMyItem = (PTEST2ITEM)pItem;
99 size_t const iThread = pMyItem->iThreadNo;
100 RTTEST_CHECK_RET(g_hTest, iThread < RT_ELEMENTS(g_aTest2Threads), true);
101
102 /*
103 * Start pushing back after the first million or when the
104 * control thread decide it's time for it:
105 */
106 uint32_t cReceived = ++g_cTest2Received;
107 if (g_fTest2PushBack)
108 {
109 if ((cReceived & 3) == 3 && cReceived > _1M)
110 return false;
111 }
112 else if (cReceived < _1M )
113 { /* likely */ }
114 else
115 g_fTest2PushBack = true;
116
117 /*
118 * Process the item:
119 */
120 uint32_t iCallbackNo = ASMAtomicIncU32(&g_aTest2Threads[iThread].iReceiveSeqNo);
121 if (pMyItem->iSeqNo != iCallbackNo)
122 RTTestFailed(g_hTest, "iThread=%#x: iSeqNo=%#x, expected %#x\n", iThread, pMyItem->iSeqNo, iCallbackNo);
123
124 ASMAtomicDecU32(&g_aTest2Threads[iThread].cPending);
125
126 RT_NOREF(pvUser);
127 return true;
128}
129
130
131/**
132 * @callback_method_impl{FNRTTHREAD, Producer thread}
133 */
134static DECLCALLBACK(int) Test2Thread(RTTHREAD hThreadSelf, void *pvUser)
135{
136 PVM const pVM = g_pTest2VM;
137 PDMQUEUEHANDLE const hQueue = g_hTest2Queue;
138 size_t const iThread = (size_t)pvUser;
139 RTTEST_CHECK_RET(g_hTest, iThread < RT_ELEMENTS(g_aTest2Threads), VERR_INVALID_PARAMETER);
140
141 uint32_t iSendSeqNo = 0;
142 uint32_t cSpinLoops = 0;
143 while (!g_fTest2Terminate && iSendSeqNo < _64M)
144 {
145 if (g_aTest2Threads[iThread].cPending < g_aTest2Threads[iThread].cMaxPending)
146 {
147 PTEST2ITEM pMyItem = (PTEST2ITEM)PDMQueueAlloc(pVM, hQueue, pVM);
148 if (pMyItem)
149 {
150 pMyItem->iSeqNo = ++iSendSeqNo;
151 pMyItem->iThreadNo = (uint32_t)iThread;
152 RTTEST_CHECK_RC(g_hTest, PDMQueueInsert(pVM, hQueue, pVM, &pMyItem->Core), VINF_SUCCESS);
153 ASMAtomicIncU32(&g_aTest2Threads[iThread].cPending);
154 }
155 else
156 {
157 RTTestFailed(g_hTest, "iThread=%u: PDMQueueAlloc failed: cPending=%u cMaxPending=%u iSendSeqNo=%u",
158 iThread, g_aTest2Threads[iThread].cPending, g_aTest2Threads[iThread].cMaxPending, iSendSeqNo);
159 ASMAtomicWriteBool(&g_fTest2Terminate, true);
160 break;
161 }
162
163 cSpinLoops = 0;
164 }
165 else if (cSpinLoops++ < 1024)
166 ASMNopPause();
167 else
168 {
169 RTThreadYield();
170 cSpinLoops = 0;
171 }
172 }
173
174 ASMAtomicDecU32(&g_cTest2Threads);
175
176 RT_NOREF(hThreadSelf);
177 return VINF_SUCCESS;
178}
179
180
181/**
182 * @callback_method_impl{FNRTTHREAD, Control thread}
183 */
184static DECLCALLBACK(int) Test2ControlThread(RTTHREAD hThreadSelf, void *pvUser)
185{
186 RT_NOREF(hThreadSelf, pvUser);
187
188 RTThreadSleep(RT_MS_5SEC);
189 ASMAtomicWriteBool(&g_fTest2PushBack, true);
190
191 RTThreadSleep(RT_MS_30SEC);
192 ASMAtomicWriteBool(&g_fTest2Terminate, true);
193
194 ASMAtomicDecU32(&g_cTest2Threads);
195 return VINF_SUCCESS;
196}
197
198
199static DECLCALLBACK(int) Test2Emt(PVM pVM, PUVM pUVM)
200{
201 uint32_t cThreads = 2;
202 RTTestSubF(g_hTest, "%u Threads", cThreads);
203 RTTEST_CHECK_RET(g_hTest, cThreads < RT_ELEMENTS(g_aTest2Threads) /* last entry is control thread*/, VERR_OUT_OF_RANGE);
204
205 PDMQUEUEHANDLE hQueue;
206 RTTEST_CHECK_RC_RET(g_hTest, PDMR3QueueCreateExternal(pVM, sizeof(TEST2ITEM), cThreads * 128 + 16, 0 /*cMilliesInterval*/,
207 Test2ConsumerCallback, pVM /*pvUser*/, "Test2", &hQueue),
208 VINF_SUCCESS, VINF_SUCCESS);
209
210 /* Init the thread data: */
211 g_fTest2Terminate = false;
212 g_pTest2VM = pVM;
213 g_hTest2Queue = hQueue;
214 g_fTest2PushBack = false;
215 g_cTest2Received = 0;
216 for (uint32_t i = 0; i < cThreads; i++)
217 {
218 g_aTest2Threads[i].hThread = NIL_RTTHREAD;
219 g_aTest2Threads[i].iThreadNo = i;
220 g_aTest2Threads[i].cMaxPending = 64 + i % 16;
221 g_aTest2Threads[i].cPending = 0;
222 g_aTest2Threads[i].iReceiveSeqNo = 0;
223 }
224
225 /* Start the threads: */
226 for (uint32_t i = 0; i < cThreads; i++)
227 {
228 RTTEST_CHECK_RC_BREAK(g_hTest, RTThreadCreateF(&g_aTest2Threads[i].hThread, Test2Thread, (void *)(uintptr_t)i, 0,
229 RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "test2-t%u", i),
230 VINF_SUCCESS);
231 ASMAtomicIncU32(&g_cTest2Threads);
232 }
233
234 int rc;
235 RTTEST_CHECK_RC(g_hTest, rc = RTThreadCreate(&g_aTest2Threads[cThreads].hThread, Test2ControlThread, NULL, 0,
236 RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "test2-ctl"),
237 VINF_SUCCESS);
238 if (RT_SUCCESS(rc))
239 ASMAtomicIncU32(&g_cTest2Threads);
240
241 /* Process the queue till all threads have quit or termination is triggered: */
242 while ( ASMAtomicUoReadU32(&g_cTest2Threads) != 0
243 && !g_fTest2Terminate)
244 {
245 PDMR3QueueFlushAll(pVM);
246 }
247
248 /* Wait for the threads. */
249 ASMAtomicWriteBool(&g_fTest2Terminate, true);
250 for (uint32_t i = 0; i <= cThreads; i++)
251 {
252 if (g_aTest2Threads[i].hThread != NIL_RTTHREAD)
253 {
254 int rcThread = VERR_GENERAL_FAILURE;
255 RTTEST_CHECK_RC(g_hTest, RTThreadWait(g_aTest2Threads[i].hThread, RT_MS_30SEC, &rcThread), VINF_SUCCESS);
256 RTTEST_CHECK_RC(g_hTest, rcThread, VINF_SUCCESS);
257 }
258 }
259
260 STAMR3Print(pUVM, "/PDM/Queue/Test2/*");
261
262 /* Cleanup: */
263 RTTEST_CHECK_RC(g_hTest, PDMR3QueueDestroy(pVM, hQueue, pVM), VINF_SUCCESS);
264 RTTestSubDone(g_hTest);
265 return VINF_SUCCESS;
266}
267
268
269/*********************************************************************************************************************************
270* Test #1 - Basics *
271*********************************************************************************************************************************/
272static uint32_t volatile g_cTest1Callbacks = 0;
273static int32_t volatile g_cTest1Pushback = INT32_MAX;
274
275typedef struct TEST1ITEM
276{
277 PDMQUEUEITEMCORE Core;
278 uint32_t iSeqNo;
279} TEST1ITEM;
280typedef TEST1ITEM *PTEST1ITEM;
281
282
283/** @callback_method_impl{FNPDMQUEUEEXT} */
284static DECLCALLBACK(bool) Test1ConsumerCallback(void *pvUser, PPDMQUEUEITEMCORE pItem)
285{
286 if (ASMAtomicDecS32(&g_cTest1Pushback) < 0)
287 return false;
288
289 PTEST1ITEM pMyItem = (PTEST1ITEM)pItem;
290 uint32_t iCallbackNo = ASMAtomicIncU32(&g_cTest1Callbacks);
291 if (pMyItem->iSeqNo != iCallbackNo)
292 RTTestFailed(g_hTest, "iSeqNo=%#x, expected %#x\n", pMyItem->iSeqNo, iCallbackNo);
293
294 RT_NOREF(pvUser);
295 return true;
296}
297
298
299static DECLCALLBACK(int) Test1Emt(PVM pVM)
300{
301 RTTestSub(g_hTest, "Basics");
302
303 PDMQUEUEHANDLE hQueue;
304 RTTEST_CHECK_RC_RET(g_hTest, PDMR3QueueCreateExternal(pVM, sizeof(TEST1ITEM), 16, 0 /*cMilliesInterval*/,
305 Test1ConsumerCallback, pVM /*pvUser*/, "Test1", &hQueue),
306 VINF_SUCCESS, VINF_SUCCESS);
307
308 PDMQUEUEHANDLE const hQueueFirst = hQueue; /* Save the handle value so we can check that it's correctly reused. */
309
310 /*
311 * Single item:
312 */
313 PTEST1ITEM pMyItem = (PTEST1ITEM)PDMQueueAlloc(pVM, hQueue, pVM);
314 RTTEST_CHECK(g_hTest, pMyItem);
315 if (pMyItem)
316 {
317 pMyItem->iSeqNo = 1;
318 RTTEST_CHECK_RC(g_hTest, PDMQueueInsert(pVM, hQueue, pVM, &pMyItem->Core), VINF_SUCCESS);
319
320 PDMR3QueueFlushAll(pVM);
321 RTTEST_CHECK(g_hTest, g_cTest1Callbacks == 1);
322 }
323
324 /*
325 * All items:
326 */
327 for (uint32_t i = 0; i < 16; i++)
328 {
329 pMyItem = (PTEST1ITEM)PDMQueueAlloc(pVM, hQueue, pVM);
330 RTTEST_CHECK_BREAK(g_hTest, pMyItem);
331 pMyItem->iSeqNo = i + 2;
332 RTTEST_CHECK_RC(g_hTest, PDMQueueInsert(pVM, hQueue, pVM, &pMyItem->Core), VINF_SUCCESS);
333 }
334
335 pMyItem = (PTEST1ITEM)PDMQueueAlloc(pVM, hQueue, pVM);
336 RTTEST_CHECK(g_hTest, pMyItem == NULL);
337
338 PDMR3QueueFlushAll(pVM);
339 RTTEST_CHECK(g_hTest, g_cTest1Callbacks == 17);
340
341 /*
342 * Push back.
343 * 1. First queue all items.
344 * 2. Process half of them.
345 * 3. The process one by one.
346 */
347 g_cTest1Callbacks = 0;
348 g_cTest1Pushback = 8;
349
350 for (uint32_t i = 0; i < 16; i++)
351 {
352 pMyItem = (PTEST1ITEM)PDMQueueAlloc(pVM, hQueue, pVM);
353 RTTEST_CHECK_BREAK(g_hTest, pMyItem);
354 pMyItem->iSeqNo = i + 1;
355 RTTEST_CHECK_RC(g_hTest, PDMQueueInsert(pVM, hQueue, pVM, &pMyItem->Core), VINF_SUCCESS);
356 }
357
358 pMyItem = (PTEST1ITEM)PDMQueueAlloc(pVM, hQueue, pVM);
359 RTTEST_CHECK(g_hTest, pMyItem == NULL);
360
361 PDMR3QueueFlushAll(pVM);
362 RTTEST_CHECK(g_hTest, g_cTest1Callbacks == 8);
363
364 for (uint32_t i = 0; i < 8; i++)
365 {
366 g_cTest1Pushback = 1;
367 PDMR3QueueFlushAll(pVM);
368 RTTEST_CHECK(g_hTest, g_cTest1Callbacks == 8 + 1 + i);
369 }
370
371 /*
372 * Cleanup.
373 */
374 RTTEST_CHECK_RC(g_hTest, PDMR3QueueDestroy(pVM, hQueue, pVM), VINF_SUCCESS);
375
376 /*
377 * Do some creation/deletion ordering checks.
378 */
379 RTTestSub(g_hTest, "Cleanup & handle reuse");
380 PDMQUEUEHANDLE ahQueues[168];
381 for (size_t i = 0; i < RT_ELEMENTS(ahQueues); i++)
382 ahQueues[i] = NIL_PDMQUEUEHANDLE;
383 for (uint32_t i = 0; i < RT_ELEMENTS(ahQueues); i++)
384 {
385 char szQueueNm[32];
386 RTStrPrintf(szQueueNm, sizeof(szQueueNm), "Test1b-%u", i);
387 RTTEST_CHECK_RC(g_hTest, PDMR3QueueCreateExternal(pVM, sizeof(TEST1ITEM), i + 1, 0 /*cMilliesInterval*/,
388 Test1ConsumerCallback, pVM /*pvUser*/, szQueueNm, &ahQueues[i]),
389 VINF_SUCCESS);
390 if (i == 0 && ahQueues[0] != hQueueFirst)
391 RTTestFailed(g_hTest, "Queue handle value not reused: %#RX64, expected %#RX64", ahQueues[0], hQueueFirst);
392 }
393
394 /* Delete them in random order. */
395 for (uint32_t i = 0; i < RT_ELEMENTS(ahQueues); i++)
396 {
397 uint32_t iDelete = RTRandU32Ex(0, RT_ELEMENTS(ahQueues) - 1);
398 if (ahQueues[iDelete] != NIL_PDMQUEUEHANDLE)
399 {
400 RTTEST_CHECK_RC(g_hTest, PDMR3QueueDestroy(pVM, ahQueues[iDelete], pVM), VINF_SUCCESS);
401 ahQueues[iDelete] = NIL_PDMQUEUEHANDLE;
402 }
403 }
404
405 /* Delete remainder in ascending order, creating a array shrinking at the end. */
406 for (uint32_t i = 0; i < RT_ELEMENTS(ahQueues); i++)
407 if (ahQueues[i] != NIL_PDMQUEUEHANDLE)
408 {
409 RTTEST_CHECK_RC(g_hTest, PDMR3QueueDestroy(pVM, ahQueues[i], pVM), VINF_SUCCESS);
410 ahQueues[i] = NIL_PDMQUEUEHANDLE;
411 }
412
413 /* Create one more queue and check that we get the first queue handle again. */
414 RTTEST_CHECK_RC(g_hTest, PDMR3QueueCreateExternal(pVM, sizeof(TEST1ITEM), 1, 0 /*cMilliesInterval*/,
415 Test1ConsumerCallback, pVM /*pvUser*/, "Test1c", &hQueue), VINF_SUCCESS);
416 if (hQueue != hQueueFirst)
417 RTTestFailed(g_hTest, "Queue handle value not reused: %#RX64, expected %#RX64", hQueue, hQueueFirst);
418 RTTEST_CHECK_RC(g_hTest, PDMR3QueueDestroy(pVM, hQueue, pVM), VINF_SUCCESS);
419
420 RTTestSubDone(g_hTest);
421 return VINF_SUCCESS;
422}
423
424
425static void DoTests(void)
426{
427 PVM pVM;
428 PUVM pUVM;
429 RTTESTI_CHECK_RC_OK_RETV(VMR3Create(1 /*cCpus*/, NULL, VMCREATE_F_DRIVERLESS, NULL, NULL, NULL, NULL, &pVM, &pUVM));
430
431 /*
432 * Do the tests.
433 */
434 RTTESTI_CHECK_RC(VMR3ReqCallWaitU(pUVM, 0, (PFNRT)Test1Emt, 1, pVM), VINF_SUCCESS);
435 if (RTTestErrorCount(g_hTest) == 0)
436 {
437 RTTESTI_CHECK_RC(VMR3ReqCallWaitU(pUVM, 0, (PFNRT)Test2Emt, 2, pVM, pUVM), VINF_SUCCESS);
438 }
439
440 /*
441 * Clean up.
442 */
443 RTTESTI_CHECK_RC_OK_RETV(VMR3PowerOff(pUVM));
444 RTTESTI_CHECK_RC_OK_RETV(VMR3Destroy(pUVM));
445 VMR3ReleaseUVM(pUVM);
446}
447
448
449int main(int argc, char **argv)
450{
451 /*
452 * We run the VMM in driverless mode to avoid needing to hardened the testcase
453 */
454 RTEXITCODE rcExit;
455 int rc = RTR3InitExe(argc, &argv, SUPR3INIT_F_DRIVERLESS << RTR3INIT_FLAGS_SUPLIB_SHIFT);
456 if (RT_SUCCESS(rc))
457 {
458 rc = RTTestCreate("tstPDMQueue", &g_hTest);
459 if (RT_SUCCESS(rc))
460 {
461 RTTestBanner(g_hTest);
462 DoTests();
463 rcExit = RTTestSummaryAndDestroy(g_hTest);
464 }
465 else
466 rcExit = RTMsgErrorExitFailure("RTTestCreate failed: %Rrc", rc);
467 }
468 else
469 rcExit = RTMsgInitFailure(rc);
470 return rcExit;
471}
472
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette