From f215e02bf85f68d3a6106c2a1f4f7f063f819064 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 11 Apr 2024 10:17:27 +0200 Subject: Adding upstream version 7.0.14-dfsg. Signed-off-by: Daniel Baumann --- src/VBox/VMM/testcase/tstPDMQueue.cpp | 469 ++++++++++++++++++++++++++++++++++ 1 file changed, 469 insertions(+) create mode 100644 src/VBox/VMM/testcase/tstPDMQueue.cpp (limited to 'src/VBox/VMM/testcase/tstPDMQueue.cpp') diff --git a/src/VBox/VMM/testcase/tstPDMQueue.cpp b/src/VBox/VMM/testcase/tstPDMQueue.cpp new file mode 100644 index 00000000..20001b10 --- /dev/null +++ b/src/VBox/VMM/testcase/tstPDMQueue.cpp @@ -0,0 +1,469 @@ +/* $Id: tstPDMQueue.cpp $ */ +/** @file + * PDM Queue Testcase. + */ + +/* + * Copyright (C) 2022-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_PDM_QUEUE +#define VBOX_IN_VMM + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static RTTEST g_hTest; + + +/********************************************************************************************************************************* +* Test #2 - Threading * +*********************************************************************************************************************************/ +typedef struct TEST2ITEM +{ + PDMQUEUEITEMCORE Core; + uint32_t iSeqNo; + uint32_t iThreadNo; + /* Pad it up to two cachelines to reduce noise. */ + uint8_t abPadding[128 - sizeof(PDMQUEUEITEMCORE) - sizeof(uint32_t) * 2]; +} TEST2ITEM; +typedef TEST2ITEM *PTEST2ITEM; + +static struct TEST2THREAD +{ + RTTHREAD hThread; + uint32_t iThreadNo; + uint32_t cMaxPending; + /* Pad one cache line. */ + uint8_t abPadding1[64]; + uint32_t volatile cPending; + uint32_t volatile iReceiveSeqNo; + /* Pad structure size to three cache lines. */ + uint8_t abPadding2[64 * 2 - sizeof(uint32_t) * 4 - sizeof(RTTHREAD)]; +} g_aTest2Threads[16]; + +static bool volatile g_fTest2Terminate = false; +static uint32_t volatile g_cTest2Threads = 0; +static uint32_t g_cTest2Received = 0; +static bool volatile g_fTest2PushBack = false; +static PVM volatile g_pTest2VM = NULL; /**< Volatile to force local copy in thread function. */ +static PDMQUEUEHANDLE volatile g_hTest2Queue = NIL_PDMQUEUEHANDLE; /**< Ditto. */ + + + +/** + * @callback_method_impl{FNPDMQUEUEEXT, Consumer callback} + */ +static DECLCALLBACK(bool) Test2ConsumerCallback(void *pvUser, PPDMQUEUEITEMCORE pItem) +{ + PTEST2ITEM pMyItem = (PTEST2ITEM)pItem; + size_t const iThread = pMyItem->iThreadNo; + RTTEST_CHECK_RET(g_hTest, iThread < RT_ELEMENTS(g_aTest2Threads), true); + + /* + * Start pushing back after the first million or when the + * control thread decide it's time for it: + */ + uint32_t cReceived = ++g_cTest2Received; + if (g_fTest2PushBack) + { + if ((cReceived & 3) == 3 && cReceived > _1M) + return false; + } + else if (cReceived < _1M ) + { /* likely */ } + else + g_fTest2PushBack = true; + + /* + * Process the item: + */ + uint32_t iCallbackNo = ASMAtomicIncU32(&g_aTest2Threads[iThread].iReceiveSeqNo); + if (pMyItem->iSeqNo != iCallbackNo) + RTTestFailed(g_hTest, "iThread=%#x: iSeqNo=%#x, expected %#x\n", iThread, pMyItem->iSeqNo, iCallbackNo); + + ASMAtomicDecU32(&g_aTest2Threads[iThread].cPending); + + RT_NOREF(pvUser); + return true; +} + + +/** + * @callback_method_impl{FNRTTHREAD, Producer thread} + */ +static DECLCALLBACK(int) Test2Thread(RTTHREAD hThreadSelf, void *pvUser) +{ + PVM const pVM = g_pTest2VM; + PDMQUEUEHANDLE const hQueue = g_hTest2Queue; + size_t const iThread = (size_t)pvUser; + RTTEST_CHECK_RET(g_hTest, iThread < RT_ELEMENTS(g_aTest2Threads), VERR_INVALID_PARAMETER); + + uint32_t iSendSeqNo = 0; + uint32_t cSpinLoops = 0; + while (!g_fTest2Terminate && iSendSeqNo < _64M) + { + if (g_aTest2Threads[iThread].cPending < g_aTest2Threads[iThread].cMaxPending) + { + PTEST2ITEM pMyItem = (PTEST2ITEM)PDMQueueAlloc(pVM, hQueue, pVM); + if (pMyItem) + { + pMyItem->iSeqNo = ++iSendSeqNo; + pMyItem->iThreadNo = (uint32_t)iThread; + RTTEST_CHECK_RC(g_hTest, PDMQueueInsert(pVM, hQueue, pVM, &pMyItem->Core), VINF_SUCCESS); + ASMAtomicIncU32(&g_aTest2Threads[iThread].cPending); + } + else + { + RTTestFailed(g_hTest, "iThread=%u: PDMQueueAlloc failed: cPending=%u cMaxPending=%u iSendSeqNo=%u", + iThread, g_aTest2Threads[iThread].cPending, g_aTest2Threads[iThread].cMaxPending, iSendSeqNo); + ASMAtomicWriteBool(&g_fTest2Terminate, true); + break; + } + + cSpinLoops = 0; + } + else if (cSpinLoops++ < 1024) + ASMNopPause(); + else + { + RTThreadYield(); + cSpinLoops = 0; + } + } + + ASMAtomicDecU32(&g_cTest2Threads); + + RT_NOREF(hThreadSelf); + return VINF_SUCCESS; +} + + +/** + * @callback_method_impl{FNRTTHREAD, Control thread} + */ +static DECLCALLBACK(int) Test2ControlThread(RTTHREAD hThreadSelf, void *pvUser) +{ + RT_NOREF(hThreadSelf, pvUser); + + RTThreadSleep(RT_MS_5SEC); + ASMAtomicWriteBool(&g_fTest2PushBack, true); + + RTThreadSleep(RT_MS_30SEC); + ASMAtomicWriteBool(&g_fTest2Terminate, true); + + ASMAtomicDecU32(&g_cTest2Threads); + return VINF_SUCCESS; +} + + +static DECLCALLBACK(int) Test2Emt(PVM pVM, PUVM pUVM) +{ + uint32_t cThreads = 2; + RTTestSubF(g_hTest, "%u Threads", cThreads); + RTTEST_CHECK_RET(g_hTest, cThreads < RT_ELEMENTS(g_aTest2Threads) /* last entry is control thread*/, VERR_OUT_OF_RANGE); + + PDMQUEUEHANDLE hQueue; + RTTEST_CHECK_RC_RET(g_hTest, PDMR3QueueCreateExternal(pVM, sizeof(TEST2ITEM), cThreads * 128 + 16, 0 /*cMilliesInterval*/, + Test2ConsumerCallback, pVM /*pvUser*/, "Test2", &hQueue), + VINF_SUCCESS, VINF_SUCCESS); + + /* Init the thread data: */ + g_fTest2Terminate = false; + g_pTest2VM = pVM; + g_hTest2Queue = hQueue; + g_fTest2PushBack = false; + g_cTest2Received = 0; + for (uint32_t i = 0; i < cThreads; i++) + { + g_aTest2Threads[i].hThread = NIL_RTTHREAD; + g_aTest2Threads[i].iThreadNo = i; + g_aTest2Threads[i].cMaxPending = 64 + i % 16; + g_aTest2Threads[i].cPending = 0; + g_aTest2Threads[i].iReceiveSeqNo = 0; + } + + /* Start the threads: */ + for (uint32_t i = 0; i < cThreads; i++) + { + RTTEST_CHECK_RC_BREAK(g_hTest, RTThreadCreateF(&g_aTest2Threads[i].hThread, Test2Thread, (void *)(uintptr_t)i, 0, + RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "test2-t%u", i), + VINF_SUCCESS); + ASMAtomicIncU32(&g_cTest2Threads); + } + + int rc; + RTTEST_CHECK_RC(g_hTest, rc = RTThreadCreate(&g_aTest2Threads[cThreads].hThread, Test2ControlThread, NULL, 0, + RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "test2-ctl"), + VINF_SUCCESS); + if (RT_SUCCESS(rc)) + ASMAtomicIncU32(&g_cTest2Threads); + + /* Process the queue till all threads have quit or termination is triggered: */ + while ( ASMAtomicUoReadU32(&g_cTest2Threads) != 0 + && !g_fTest2Terminate) + { + PDMR3QueueFlushAll(pVM); + } + + /* Wait for the threads. */ + ASMAtomicWriteBool(&g_fTest2Terminate, true); + for (uint32_t i = 0; i <= cThreads; i++) + { + if (g_aTest2Threads[i].hThread != NIL_RTTHREAD) + { + int rcThread = VERR_GENERAL_FAILURE; + RTTEST_CHECK_RC(g_hTest, RTThreadWait(g_aTest2Threads[i].hThread, RT_MS_30SEC, &rcThread), VINF_SUCCESS); + RTTEST_CHECK_RC(g_hTest, rcThread, VINF_SUCCESS); + } + } + + STAMR3Print(pUVM, "/PDM/Queue/Test2/*"); + + /* Cleanup: */ + RTTEST_CHECK_RC(g_hTest, PDMR3QueueDestroy(pVM, hQueue, pVM), VINF_SUCCESS); + RTTestSubDone(g_hTest); + return VINF_SUCCESS; +} + + +/********************************************************************************************************************************* +* Test #1 - Basics * +*********************************************************************************************************************************/ +static uint32_t volatile g_cTest1Callbacks = 0; +static int32_t volatile g_cTest1Pushback = INT32_MAX; + +typedef struct TEST1ITEM +{ + PDMQUEUEITEMCORE Core; + uint32_t iSeqNo; +} TEST1ITEM; +typedef TEST1ITEM *PTEST1ITEM; + + +/** @callback_method_impl{FNPDMQUEUEEXT} */ +static DECLCALLBACK(bool) Test1ConsumerCallback(void *pvUser, PPDMQUEUEITEMCORE pItem) +{ + if (ASMAtomicDecS32(&g_cTest1Pushback) < 0) + return false; + + PTEST1ITEM pMyItem = (PTEST1ITEM)pItem; + uint32_t iCallbackNo = ASMAtomicIncU32(&g_cTest1Callbacks); + if (pMyItem->iSeqNo != iCallbackNo) + RTTestFailed(g_hTest, "iSeqNo=%#x, expected %#x\n", pMyItem->iSeqNo, iCallbackNo); + + RT_NOREF(pvUser); + return true; +} + + +static DECLCALLBACK(int) Test1Emt(PVM pVM) +{ + RTTestSub(g_hTest, "Basics"); + + PDMQUEUEHANDLE hQueue; + RTTEST_CHECK_RC_RET(g_hTest, PDMR3QueueCreateExternal(pVM, sizeof(TEST1ITEM), 16, 0 /*cMilliesInterval*/, + Test1ConsumerCallback, pVM /*pvUser*/, "Test1", &hQueue), + VINF_SUCCESS, VINF_SUCCESS); + + PDMQUEUEHANDLE const hQueueFirst = hQueue; /* Save the handle value so we can check that it's correctly reused. */ + + /* + * Single item: + */ + PTEST1ITEM pMyItem = (PTEST1ITEM)PDMQueueAlloc(pVM, hQueue, pVM); + RTTEST_CHECK(g_hTest, pMyItem); + pMyItem->iSeqNo = 1; + RTTEST_CHECK_RC(g_hTest, PDMQueueInsert(pVM, hQueue, pVM, &pMyItem->Core), VINF_SUCCESS); + + PDMR3QueueFlushAll(pVM); + RTTEST_CHECK(g_hTest, g_cTest1Callbacks == 1); + + /* + * All items: + */ + for (uint32_t i = 0; i < 16; i++) + { + pMyItem = (PTEST1ITEM)PDMQueueAlloc(pVM, hQueue, pVM); + RTTEST_CHECK_BREAK(g_hTest, pMyItem); + pMyItem->iSeqNo = i + 2; + RTTEST_CHECK_RC(g_hTest, PDMQueueInsert(pVM, hQueue, pVM, &pMyItem->Core), VINF_SUCCESS); + } + + pMyItem = (PTEST1ITEM)PDMQueueAlloc(pVM, hQueue, pVM); + RTTEST_CHECK(g_hTest, pMyItem == NULL); + + PDMR3QueueFlushAll(pVM); + RTTEST_CHECK(g_hTest, g_cTest1Callbacks == 17); + + /* + * Push back. + * 1. First queue all items. + * 2. Process half of them. + * 3. The process one by one. + */ + g_cTest1Callbacks = 0; + g_cTest1Pushback = 8; + + for (uint32_t i = 0; i < 16; i++) + { + pMyItem = (PTEST1ITEM)PDMQueueAlloc(pVM, hQueue, pVM); + RTTEST_CHECK_BREAK(g_hTest, pMyItem); + pMyItem->iSeqNo = i + 1; + RTTEST_CHECK_RC(g_hTest, PDMQueueInsert(pVM, hQueue, pVM, &pMyItem->Core), VINF_SUCCESS); + } + + pMyItem = (PTEST1ITEM)PDMQueueAlloc(pVM, hQueue, pVM); + RTTEST_CHECK(g_hTest, pMyItem == NULL); + + PDMR3QueueFlushAll(pVM); + RTTEST_CHECK(g_hTest, g_cTest1Callbacks == 8); + + for (uint32_t i = 0; i < 8; i++) + { + g_cTest1Pushback = 1; + PDMR3QueueFlushAll(pVM); + RTTEST_CHECK(g_hTest, g_cTest1Callbacks == 8 + 1 + i); + } + + /* + * Cleanup. + */ + RTTEST_CHECK_RC(g_hTest, PDMR3QueueDestroy(pVM, hQueue, pVM), VINF_SUCCESS); + + /* + * Do some creation/deletion ordering checks. + */ + RTTestSub(g_hTest, "Cleanup & handle reuse"); + PDMQUEUEHANDLE ahQueues[168]; + for (size_t i = 0; i < RT_ELEMENTS(ahQueues); i++) + ahQueues[i] = NIL_PDMQUEUEHANDLE; + for (uint32_t i = 0; i < RT_ELEMENTS(ahQueues); i++) + { + char szQueueNm[32]; + RTStrPrintf(szQueueNm, sizeof(szQueueNm), "Test1b-%u", i); + RTTEST_CHECK_RC(g_hTest, PDMR3QueueCreateExternal(pVM, sizeof(TEST1ITEM), i + 1, 0 /*cMilliesInterval*/, + Test1ConsumerCallback, pVM /*pvUser*/, szQueueNm, &ahQueues[i]), + VINF_SUCCESS); + if (i == 0 && ahQueues[0] != hQueueFirst) + RTTestFailed(g_hTest, "Queue handle value not reused: %#RX64, expected %#RX64", ahQueues[0], hQueueFirst); + } + + /* Delete them in random order. */ + for (uint32_t i = 0; i < RT_ELEMENTS(ahQueues); i++) + { + uint32_t iDelete = RTRandU32Ex(0, RT_ELEMENTS(ahQueues) - 1); + if (ahQueues[iDelete] != NIL_PDMQUEUEHANDLE) + { + RTTEST_CHECK_RC(g_hTest, PDMR3QueueDestroy(pVM, ahQueues[iDelete], pVM), VINF_SUCCESS); + ahQueues[iDelete] = NIL_PDMQUEUEHANDLE; + } + } + + /* Delete remainder in ascending order, creating a array shrinking at the end. */ + for (uint32_t i = 0; i < RT_ELEMENTS(ahQueues); i++) + if (ahQueues[i] != NIL_PDMQUEUEHANDLE) + { + RTTEST_CHECK_RC(g_hTest, PDMR3QueueDestroy(pVM, ahQueues[i], pVM), VINF_SUCCESS); + ahQueues[i] = NIL_PDMQUEUEHANDLE; + } + + /* Create one more queue and check that we get the first queue handle again. */ + RTTEST_CHECK_RC(g_hTest, PDMR3QueueCreateExternal(pVM, sizeof(TEST1ITEM), 1, 0 /*cMilliesInterval*/, + Test1ConsumerCallback, pVM /*pvUser*/, "Test1c", &hQueue), VINF_SUCCESS); + if (hQueue != hQueueFirst) + RTTestFailed(g_hTest, "Queue handle value not reused: %#RX64, expected %#RX64", hQueue, hQueueFirst); + RTTEST_CHECK_RC(g_hTest, PDMR3QueueDestroy(pVM, hQueue, pVM), VINF_SUCCESS); + + RTTestSubDone(g_hTest); + return VINF_SUCCESS; +} + + +static void DoTests(void) +{ + PVM pVM; + PUVM pUVM; + RTTESTI_CHECK_RC_OK_RETV(VMR3Create(1 /*cCpus*/, NULL, VMCREATE_F_DRIVERLESS, NULL, NULL, NULL, NULL, &pVM, &pUVM)); + + /* + * Do the tests. + */ + RTTESTI_CHECK_RC(VMR3ReqCallWaitU(pUVM, 0, (PFNRT)Test1Emt, 1, pVM), VINF_SUCCESS); + if (RTTestErrorCount(g_hTest) == 0) + { + RTTESTI_CHECK_RC(VMR3ReqCallWaitU(pUVM, 0, (PFNRT)Test2Emt, 2, pVM, pUVM), VINF_SUCCESS); + } + + /* + * Clean up. + */ + RTTESTI_CHECK_RC_OK_RETV(VMR3PowerOff(pUVM)); + RTTESTI_CHECK_RC_OK_RETV(VMR3Destroy(pUVM)); + VMR3ReleaseUVM(pUVM); +} + + +int main(int argc, char **argv) +{ + /* + * We run the VMM in driverless mode to avoid needing to hardened the testcase + */ + RTEXITCODE rcExit; + int rc = RTR3InitExe(argc, &argv, SUPR3INIT_F_DRIVERLESS << RTR3INIT_FLAGS_SUPLIB_SHIFT); + if (RT_SUCCESS(rc)) + { + rc = RTTestCreate("tstPDMQueue", &g_hTest); + if (RT_SUCCESS(rc)) + { + RTTestBanner(g_hTest); + DoTests(); + rcExit = RTTestSummaryAndDestroy(g_hTest); + } + else + rcExit = RTMsgErrorExitFailure("RTTestCreate failed: %Rrc", rc); + } + else + rcExit = RTMsgInitFailure(rc); + return rcExit; +} + -- cgit v1.2.3