/* $Id: tstDevice.cpp $ */ /** @file * tstDevice - Test framework for PDM devices/drivers */ /* * Copyright (C) 2017-2022 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_DEFAULT /** @todo */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tstDeviceInternal.h" #include "tstDeviceCfg.h" #include "tstDeviceBuiltin.h" /********************************************************************************************************************************* * Defined Constants And Macros * *********************************************************************************************************************************/ #define PDM_MAX_DEVICE_INSTANCE_SIZE _4M /********************************************************************************************************************************* * Structures and Typedefs * *********************************************************************************************************************************/ /** * Testcase plugin descriptor. */ typedef struct TSTDEVPLUGIN { /** Node for the plugin list. */ RTLISTNODE NdPlugins; /** Copy of the filename. */ char *pszFilename; /** Loader handle. */ RTLDRMOD hMod; /** Number of references to this plugin. */ volatile uint32_t cRefs; } TSTDEVPLUGIN; /** Pointer to a device plugin descriptor. */ typedef TSTDEVPLUGIN *PTSTDEVPLUGIN; /** Pointer to a const plugin descriptor. */ typedef const TSTDEVPLUGIN *PCTSTDEVPLUGIN; /** * Testcase descriptor. */ typedef struct TSTDEVTESTCASE { /** Node for the list of registered testcases. */ RTLISTNODE NdTestcases; /** Pointer to the plugin the testcase belongs to. */ PCTSTDEVPLUGIN pPlugin; /** Pointer to the testcase descriptor. */ PCTSTDEVTESTCASEREG pTestcaseReg; } TSTDEVTESTCASE; /** Pointer to a testcase descriptor. */ typedef TSTDEVTESTCASE *PTSTDEVTESTCASE; /** Pointer to a constant testcase descriptor. */ typedef const TSTDEVTESTCASE *PCTSTDEVTESTCASE; /** * PDM R0/RC module trampoline descriptor. */ #pragma pack(1) typedef struct TSTDEVPDMMODTRAMPOLINE { /** Jump instruction. */ uint8_t abJmp[6]; /** Address to jump to. */ uintptr_t AddrTarget; /** Padding to get a 16byte aligned structure. */ uint8_t abPadding[HC_ARCH_BITS == 64 ? 2 : 5]; } TSTDEVPDMMODTRAMPOLINE; #pragma pack() AssertCompileSize(TSTDEVPDMMODTRAMPOLINE, 16); /** Pointer to a trampoline descriptor. */ typedef TSTDEVPDMMODTRAMPOLINE *PTSTDEVPDMMODTRAMPOLINE; /** * PDM module descriptor. */ typedef struct TSTDEVPDMMOD { /** Node for the module list. */ RTLISTNODE NdPdmMods; /** Type of module (R3/R0/RC). */ TSTDEVPDMMODTYPE enmType; /** Copy of the filename. */ char *pszFilename; /** Loader handle. */ RTLDRMOD hLdrMod; /** Number of references to this plugin. */ volatile uint32_t cRefs; /** R0/RC Module type dependent data. */ struct { /** The exectuable image bits. */ void *pvBits; /** Size of the memory buffer. */ size_t cbBits; /** Pointer to the executable memory containing the trampoline code. */ uint8_t *pbTrampoline; /** Number of trampoline entries supported. */ uint32_t cTrampolinesMax; /** Number of trampoline entries used. */ uint32_t cTrampolines; /** Pointer to the next unused trampoline entry. */ PTSTDEVPDMMODTRAMPOLINE pTrampolineNext; } R0Rc; } TSTDEVPDMMOD; /** Pointer to a PDM module descriptor. */ typedef TSTDEVPDMMOD *PTSTDEVPDMMOD; /** * Internal callback structure pointer. * The main purpose is to define the extra data we associate * with PDMDEVREGCB so we can find the plugin the device is associated with etc. */ typedef struct TSTDEVPDMDEVREGCBINT { /** The callback structure. */ PDMDEVREGCB Core; /** A bit of padding. */ uint32_t u32[4]; /** Pointer to plugin. */ PTSTDEVPDMMOD pMod; } TSTDEVPDMDEVREGCBINT; /** Pointer to a PDMDEVREGCBINT structure. */ typedef TSTDEVPDMDEVREGCBINT *PTSTDEVPDMDEVREGCBINT; /** Pointer to a const PDMDEVREGCBINT structure. */ typedef const TSTDEVPDMDEVREGCBINT *PCTSTDEVPDMDEVREGCBINT; typedef struct TSTDEVPDMR0IMPORTS { /** The symbol name. */ const char *pszSymbol; /** The pointer. */ PFNRT pfn; } TSTDEVPDMR0IMPORTS; typedef const TSTDEVPDMR0IMPORTS *PCTSTDEVPDMR0IMPORTS; typedef DECLCALLBACKTYPE(int, FNR0MODULEINIT,(void *hMod)); typedef FNR0MODULEINIT *PFNR0MODULEINIT; /********************************************************************************************************************************* * Global Variables * *********************************************************************************************************************************/ /** List of registered testcase plugins. */ RTLISTANCHOR g_LstPlugins; /** List of registered testcases. */ RTLISTANCHOR g_LstTestcases; /** List of registered PDM modules. */ RTLISTANCHOR g_LstPdmMods; /** List of registered PDM R0 modules. */ RTLISTANCHOR g_LstPdmR0Mods; /** List of registered PDM devices. */ RTLISTANCHOR g_LstPdmDevs; static int tstDevPdmR0RegisterModule(void *hMod, PPDMDEVMODREGR0 pModReg); /** * PDM R0 imports we implement. */ static const TSTDEVPDMR0IMPORTS g_aPdmR0Imports[] = { {"SUPR0TracerFireProbe", (PFNRT)NULL}, {"SUPSemEventSignal", (PFNRT)NULL}, {"PDMR0DeviceRegisterModule", (PFNRT)tstDevPdmR0RegisterModule}, {"PDMR0DeviceDeregisterModule", (PFNRT)NULL}, {"PGMShwMakePageWritable", (PFNRT)NULL}, {"IntNetR0IfSend", (PFNRT)/*IntNetR0IfSend*/NULL}, {"IntNetR0IfSetPromiscuousMode", (PFNRT)/*IntNetR0IfSetPromiscuousMode*/NULL}, {"RTAssertMsg1Weak", (PFNRT)RTAssertMsg1Weak}, {"RTAssertMsg2Weak", (PFNRT)RTAssertMsg2Weak}, {"RTAssertShouldPanic", (PFNRT)RTAssertShouldPanic}, {"RTLogDefaultInstanceEx", (PFNRT)RTLogDefaultInstanceEx}, {"RTLogLoggerEx", (PFNRT)RTLogLoggerEx}, {"RTLogRelGetDefaultInstanceEx", (PFNRT)RTLogRelGetDefaultInstanceEx}, {"RTOnceSlow", (PFNRT)RTOnceSlow}, {"RTR0AssertPanicSystem", (PFNRT)0x10101010}, {"RTThreadSleep", (PFNRT)RTThreadSleep}, {"RTTimeMilliTS", (PFNRT)RTTimeMilliTS}, {"RTTimeNanoTS", (PFNRT)RTTimeNanoTS}, {"RTTraceBufAddMsgF", (PFNRT)RTTraceBufAddMsgF}, {"RTMemAllocZTag", (PFNRT)RTMemAllocZTag}, {"RTMemFree", (PFNRT)RTMemFree}, {"RTStrPrintf", (PFNRT)RTStrPrintf}, {"nocrt_memcmp", (PFNRT)memcmp}, {"nocrt_memcpy", (PFNRT)memcpy}, {"nocrt_memmove", (PFNRT)memmove}, {"nocrt_memset", (PFNRT)memset}, {"nocrt_strlen", (PFNRT)strlen}, }; /********************************************************************************************************************************* * Internal Functions * *********************************************************************************************************************************/ #if 0 /** * Parses the options given to the testcase. * * @returns Process status code. * @param cArgs Number of arguments given. * @param paszArgs Pointer to the argument vector. */ static RTEXITCODE tstDevParseOptions(int cArgs, char *paszArgs[]) { static RTGETOPTDEF const s_aOptions[] = { }; int ch; RTGETOPTUNION Value; RTGETOPTSTATE GetState; RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0 /* fFlags */); while ((ch = RTGetOpt(&GetState, &Value))) { switch (ch) { default: return RTGetOptPrintError(ch, &Value); } } } #endif /** * Checks whether the given testcase name is already existing. * * @returns Pointer to existing testcase, NULL if not found. * @param pszFilename The filename to check. */ static PCTSTDEVTESTCASE tstDevTestcaseFind(const char *pszName) { PCTSTDEVTESTCASE pIt; RTListForEach(&g_LstTestcases, pIt, TSTDEVTESTCASE, NdTestcases) { if (!RTStrCmp(pIt->pTestcaseReg->szName, pszName)) return pIt; } return NULL; } /** * @interface_method_impl{TSTDEVPLUGINREGISTER,pfnRegisterTestcase} */ static DECLCALLBACK(int) tstDevRegisterTestcase(void *pvUser, PCTSTDEVTESTCASEREG pTestcaseReg) { int rc = VINF_SUCCESS; PTSTDEVPLUGIN pPlugin = (PTSTDEVPLUGIN)pvUser; /* Try to find a testcase with the name first. */ if (!tstDevTestcaseFind(pTestcaseReg->szName)) { PTSTDEVTESTCASE pTestcase = (PTSTDEVTESTCASE)RTMemAllocZ(sizeof(TSTDEVPLUGIN)); if (RT_LIKELY(pTestcase)) { pTestcase->pPlugin = pPlugin; if (pPlugin) pPlugin->cRefs++; pTestcase->pTestcaseReg = pTestcaseReg; RTListAppend(&g_LstTestcases, &pTestcase->NdTestcases); return VINF_SUCCESS; } else rc = VERR_NO_MEMORY; } else rc = VERR_ALREADY_EXISTS; return rc; } /** * Checks whether the given plugin filename was already loaded. * * @returns Pointer to already loaded plugin, NULL if not found. * @param pszFilename The filename to check. */ static PCTSTDEVPLUGIN tstDevPluginFind(const char *pszFilename) { PCTSTDEVPLUGIN pIt; RTListForEach(&g_LstPlugins, pIt, TSTDEVPLUGIN, NdPlugins) { if (!RTStrCmp(pIt->pszFilename, pszFilename)) return pIt; } return NULL; } /** * Tries to loads the given plugin. * * @returns VBox status code. * @param pszFilename The filename to load. */ static int tstDevLoadPlugin(const char *pszFilename) { int rc = VINF_SUCCESS; /* Check whether the plugin is loaded first. */ if (!tstDevPluginFind(pszFilename)) { PTSTDEVPLUGIN pPlugin = (PTSTDEVPLUGIN)RTMemAllocZ(sizeof(TSTDEVPLUGIN)); if (RT_LIKELY(pPlugin)) { pPlugin->pszFilename = RTStrDup(pszFilename); pPlugin->cRefs = 1; rc = RTLdrLoad(pszFilename, &pPlugin->hMod); if (RT_SUCCESS(rc)) { TSTDEVPLUGINREGISTER TestcaseRegister; PFNTSTDEVPLUGINLOAD pfnPluginLoad = NULL; TestcaseRegister.pfnRegisterTestcase = tstDevRegisterTestcase; rc = RTLdrGetSymbol(pPlugin->hMod, TSTDEV_PLUGIN_LOAD_NAME, (void**)&pfnPluginLoad); if (RT_FAILURE(rc) || !pfnPluginLoad) { LogFunc(("error resolving the entry point %s in plugin %s, rc=%Rrc, pfnPluginLoad=%#p\n", TSTDEV_PLUGIN_LOAD_NAME, pszFilename, rc, pfnPluginLoad)); if (RT_SUCCESS(rc)) rc = VERR_SYMBOL_NOT_FOUND; } if (RT_SUCCESS(rc)) { /* Get the function table. */ rc = pfnPluginLoad(pPlugin, &TestcaseRegister); } else LogFunc(("ignored plugin '%s': rc=%Rrc\n", pszFilename, rc)); /* Create a plugin entry on success. */ if (RT_SUCCESS(rc)) { RTListAppend(&g_LstPlugins, &pPlugin->NdPlugins); return VINF_SUCCESS; } else RTLdrClose(pPlugin->hMod); } RTMemFree(pPlugin); } else rc = VERR_NO_MEMORY; } return rc; } /** * Checks whether the given testcase name is already existing. * * @returns Pointer to already loaded plugin, NULL if not found. * @param pszFilename The filename to check. * @param ppR0Reg Where to store the pointer to the R0 registration record * if existing, optional. */ DECLHIDDEN(PCTSTDEVPDMDEV) tstDevPdmDeviceFind(const char *pszName, PCPDMDEVREGR0 *ppR0Reg) { PCTSTDEVPDMDEV pIt; RTListForEach(&g_LstPdmDevs, pIt, TSTDEVPDMDEV, NdPdmDevs) { if (!RTStrCmp(pIt->pReg->szName, pszName)) { if (ppR0Reg) { *ppR0Reg = NULL; PPDMDEVMODREGR0 pItR0; RTListForEach(&g_LstPdmR0Mods, pItR0, PDMDEVMODREGR0, ListEntry) { for (uint32_t i = 0; i < pItR0->cDevRegs; i++) { PCPDMDEVREGR0 pReg = pItR0->papDevRegs[i]; if (!RTStrCmp(pReg->szName, pszName)) { *ppR0Reg = pReg; return pIt; } } } } return pIt; } } return NULL; } /** * Checks that a PDMDRVREG::szName, PDMDEVREG::szName or PDMUSBREG::szName * field contains only a limited set of ASCII characters. * * @returns true / false. * @param pszName The name to validate. */ bool tstDevPdmR3IsValidName(const char *pszName) { char ch; while ( (ch = *pszName) != '\0' && ( RT_C_IS_ALNUM(ch) || ch == '-' || ch == ' ' /** @todo disallow this! */ || ch == '_') ) pszName++; return ch == '\0'; } /** * @interface_method_impl{PDMDEVREGCB,pfnRegister} */ static DECLCALLBACK(int) tstDevPdmR3DevReg_Register(PPDMDEVREGCB pCallbacks, PCPDMDEVREG pReg) { /* * Validate the registration structure (mostly copy and paste from PDMDevice.cpp). */ Assert(pReg); AssertMsgReturn(pReg->u32Version == PDM_DEVREG_VERSION, ("Unknown struct version %#x!\n", pReg->u32Version), VERR_PDM_UNKNOWN_DEVREG_VERSION); AssertMsgReturn( pReg->szName[0] && strlen(pReg->szName) < sizeof(pReg->szName) && tstDevPdmR3IsValidName(pReg->szName), ("Invalid name '%.*s'\n", sizeof(pReg->szName), pReg->szName), VERR_PDM_INVALID_DEVICE_REGISTRATION); AssertMsgReturn((pReg->fFlags & PDM_DEVREG_FLAGS_HOST_BITS_MASK) == PDM_DEVREG_FLAGS_HOST_BITS_DEFAULT, ("Invalid host bits flags! fFlags=%#x (Device %s)\n", pReg->fFlags, pReg->szName), VERR_PDM_INVALID_DEVICE_HOST_BITS); AssertMsgReturn((pReg->fFlags & PDM_DEVREG_FLAGS_GUEST_BITS_MASK), ("Invalid guest bits flags! fFlags=%#x (Device %s)\n", pReg->fFlags, pReg->szName), VERR_PDM_INVALID_DEVICE_REGISTRATION); AssertMsgReturn(pReg->fClass, ("No class! (Device %s)\n", pReg->szName), VERR_PDM_INVALID_DEVICE_REGISTRATION); AssertMsgReturn(pReg->cMaxInstances > 0, ("Max instances %u! (Device %s)\n", pReg->cMaxInstances, pReg->szName), VERR_PDM_INVALID_DEVICE_REGISTRATION); AssertMsgReturn(pReg->cbInstanceCC <= (uint32_t)(pReg->fFlags & (PDM_DEVREG_FLAGS_RC | PDM_DEVREG_FLAGS_R0) ? 96 * _1K : _1M), ("Instance size %d bytes! (Device %s)\n", pReg->cbInstanceCC, pReg->szName), VERR_PDM_INVALID_DEVICE_REGISTRATION); AssertMsgReturn(pReg->pfnConstruct, ("No constructor! (Device %s)\n", pReg->szName), VERR_PDM_INVALID_DEVICE_REGISTRATION); AssertLogRelMsgReturn((pReg->fFlags & PDM_DEVREG_FLAGS_GUEST_BITS_MASK) == PDM_DEVREG_FLAGS_GUEST_BITS_DEFAULT, ("PDM: Rejected device '%s' because it didn't match the guest bits.\n", pReg->szName), VERR_PDM_INVALID_DEVICE_GUEST_BITS); AssertLogRelMsg(pReg->u32VersionEnd == PDM_DEVREG_VERSION, ("u32VersionEnd=%#x, expected %#x. (szName=%s)\n", pReg->u32VersionEnd, PDM_DEVREG_VERSION, pReg->szName)); /* * Check for duplicates. */ int rc = VINF_SUCCESS; PCTSTDEVPDMDEVREGCBINT pRegCB = (PCTSTDEVPDMDEVREGCBINT)pCallbacks; if (!tstDevPdmDeviceFind(pReg->szName, NULL /*ppR0Reg*/)) { PTSTDEVPDMDEV pPdmDev = (PTSTDEVPDMDEV)RTMemAllocZ(sizeof(TSTDEVPDMDEV)); if (RT_LIKELY(pPdmDev)) { pPdmDev->pPdmMod = pRegCB->pMod; pRegCB->pMod->cRefs++; pPdmDev->pReg = pReg; RTListAppend(&g_LstPdmDevs, &pPdmDev->NdPdmDevs); return VINF_SUCCESS; } else rc = VERR_NO_MEMORY; } else rc = VERR_PDM_DEVICE_NAME_CLASH; return rc; } /** * Checks whether the given PDM module filename was already loaded. * * @returns Pointer to already loaded plugin, NULL if not found. * @param pszFilename The filename to check. */ static PCTSTDEVPDMMOD tstDevPdmModFind(const char *pszFilename) { PCTSTDEVPDMMOD pIt; RTListForEach(&g_LstPdmMods, pIt, TSTDEVPDMMOD, NdPdmMods) { if (!RTStrCmp(pIt->pszFilename, pszFilename)) return pIt; } return NULL; } /** * Resolve an external symbol during RTLdrGetBits(). * * @returns iprt status code. * @param hLdrMod The loader module handle. * @param pszModule Module name. * @param pszSymbol Symbol name, NULL if uSymbol should be used. * @param uSymbol Symbol ordinal, ~0 if pszSymbol should be used. * @param pValue Where to store the symbol value (address). * @param pvUser User argument. */ static DECLCALLBACK(int) tstDevPdmLoadR0RcModGetImport(RTLDRMOD hLdrMod, const char *pszModule, const char *pszSymbol, unsigned uSymbol, RTUINTPTR *pValue, void *pvUser) { RT_NOREF(hLdrMod, uSymbol, pszModule); PTSTDEVPDMMOD pMod = (PTSTDEVPDMMOD)pvUser; RTPrintf("Looking for %s\n", pszSymbol); /* Resolve the import. */ PCTSTDEVPDMR0IMPORTS pImpDesc = NULL; bool fFound = false; for (uint32_t i = 0; i < RT_ELEMENTS(g_aPdmR0Imports); i++) { pImpDesc = &g_aPdmR0Imports[i]; if (!strcmp(pszSymbol, pImpDesc->pszSymbol)) { fFound = true; break; } } int rc = VERR_SYMBOL_NOT_FOUND; if (fFound) { /* Check whether the symbol has a trampoline already. */ PTSTDEVPDMMODTRAMPOLINE pTrampoline = (PTSTDEVPDMMODTRAMPOLINE)pMod->R0Rc.pbTrampoline; for (uint32_t i = 0; i < pMod->R0Rc.cTrampolines; i++) { if (pTrampoline->AddrTarget == (uintptr_t)pImpDesc->pfn) break; pTrampoline++; } /* Create new trampoline if not found. */ if (pTrampoline->AddrTarget != (uintptr_t)pImpDesc->pfn) { if (pMod->R0Rc.cTrampolines < pMod->R0Rc.cTrampolinesMax) { pTrampoline = pMod->R0Rc.pTrampolineNext; pMod->R0Rc.pTrampolineNext++; pMod->R0Rc.cTrampolines++; pTrampoline->abJmp[0] = 0xff; /* jmp */ pTrampoline->abJmp[1] = 0x25; /* rip */ pTrampoline->abJmp[2] = 0x00; /* offset */ pTrampoline->abJmp[3] = 0x00; pTrampoline->abJmp[4] = 0x00; pTrampoline->abJmp[5] = 0x00; pTrampoline->AddrTarget = (uintptr_t)pImpDesc->pfn; rc = VINF_SUCCESS; } else { rc = VERR_SYMBOL_NOT_FOUND; AssertFailed(); } } else rc = VINF_SUCCESS; if (RT_SUCCESS(rc)) *pValue = (RTUINTPTR)pTrampoline; } else AssertFailed(); return rc; } /** * The PDMR0RegisterModule() export called by loaded R0 modules. * * @returns VBox status code. * @param hMod The module handle. * @param pModReg The module registration structure. */ static int tstDevPdmR0RegisterModule(void *hMod, PPDMDEVMODREGR0 pModReg) { RT_NOREF(hMod); RTListAppend(&g_LstPdmR0Mods, &pModReg->ListEntry); return VINF_SUCCESS; } /** * Loads a new R0 modules given by the filename. * * @returns VBox status code. * @param pMod Pointer to module structure. */ static int tstDevPdmLoadR0RcMod(PTSTDEVPDMMOD pMod) { int rc = VINF_SUCCESS; const char *pszFile = RTPathFilename(pMod->pszFilename); /* Check whether the plugin is loaded first. */ if (!tstDevPdmModFind(pszFile)) { /* * R0 modules need special treatment as these are relocatable images * which are supposed to run in ring 0. */ rc = RTLdrOpen(pMod->pszFilename, 0, RTLDRARCH_HOST, &pMod->hLdrMod); if (RT_SUCCESS(rc)) { size_t cb = RTLdrSize(pMod->hLdrMod) + 1024 * sizeof(TSTDEVPDMMODTRAMPOLINE); /* Allocate bits. */ uint32_t fFlags = RTMEMALLOCEX_FLAGS_EXEC; #ifdef RT_OS_LINUX /* * amd64 ELF binaries support only a 2GB code segment everything must be in * (X86_64_PC32 relocation) so we have to use a trampoline to the final destination * which is kept close to the imported module. */ fFlags |= RTMEMALLOCEX_FLAGS_32BIT_REACH; #endif rc = RTMemAllocEx(cb, 0, fFlags, (void **)&pMod->R0Rc.pbTrampoline); pMod->R0Rc.cbBits = cb; if (RT_SUCCESS(rc)) { pMod->R0Rc.pvBits = pMod->R0Rc.pbTrampoline + 1024 * sizeof(TSTDEVPDMMODTRAMPOLINE); pMod->R0Rc.cTrampolinesMax = 1024; pMod->R0Rc.cTrampolines = 0; pMod->R0Rc.pTrampolineNext = (PTSTDEVPDMMODTRAMPOLINE)pMod->R0Rc.pbTrampoline; /* Get the bits. */ rc = RTLdrGetBits(pMod->hLdrMod, pMod->R0Rc.pvBits, (uintptr_t)pMod->R0Rc.pvBits, tstDevPdmLoadR0RcModGetImport, pMod); if (RT_SUCCESS(rc)) { /* Resolve module init entry and call it. */ PFNR0MODULEINIT pfnR0ModuleInit; rc = RTLdrGetSymbolEx(pMod->hLdrMod, pMod->R0Rc.pvBits, (uintptr_t)pMod->R0Rc.pvBits, UINT32_MAX, "ModuleInit", (PRTLDRADDR)&pfnR0ModuleInit); if (RT_SUCCESS(rc)) rc = pfnR0ModuleInit(pMod); } else RTMemFreeEx(pMod->R0Rc.pbTrampoline, pMod->R0Rc.cbBits); } if (RT_FAILURE(rc)) RTLdrClose(pMod->hLdrMod); } } return rc; } /** * Loads the given */ static int tstDevPdmLoadR3Mod(PTSTDEVPDMMOD pMod) { int rc = RTLdrLoad(pMod->pszFilename, &pMod->hLdrMod); if (RT_SUCCESS(rc)) { FNPDMVBOXDEVICESREGISTER *pfnVBoxDevicesRegister; rc = RTLdrGetSymbol(pMod->hLdrMod, "VBoxDevicesRegister", (void**)&pfnVBoxDevicesRegister); if (RT_FAILURE(rc) || !pfnVBoxDevicesRegister) { LogFunc(("error resolving the entry point %s in plugin %s, rc=%Rrc, pfnPluginLoad=%#p\n", "VBoxDevicesRegister", pMod->pszFilename, rc, pfnVBoxDevicesRegister)); if (RT_SUCCESS(rc)) rc = VERR_SYMBOL_NOT_FOUND; } if (RT_SUCCESS(rc)) { TSTDEVPDMDEVREGCBINT RegCB; RegCB.Core.u32Version = PDM_DEVREG_CB_VERSION; RegCB.Core.pfnRegister = tstDevPdmR3DevReg_Register; RegCB.pMod = pMod; rc = pfnVBoxDevicesRegister(&RegCB.Core, VBOX_VERSION); } else LogFunc(("ignored plugin '%s': rc=%Rrc\n", pMod->pszFilename, rc)); if (RT_FAILURE(rc)) RTLdrClose(pMod->hLdrMod); } return rc; } /** * Tries to loads the given PDM module. * * @returns VBox status code. * @param pszFilename The filename to load. * @param enmModType The module type. */ static int tstDevPdmLoadMod(const char *pszFilename, TSTDEVPDMMODTYPE enmModType) { int rc = VINF_SUCCESS; /* Check whether the plugin is loaded first. */ if (!tstDevPdmModFind(pszFilename)) { PTSTDEVPDMMOD pMod = (PTSTDEVPDMMOD)RTMemAllocZ(sizeof(TSTDEVPDMMOD)); if (RT_LIKELY(pMod)) { pMod->pszFilename = RTStrDup(pszFilename); pMod->cRefs = 1; pMod->enmType = enmModType; if (enmModType == TSTDEVPDMMODTYPE_R3) rc = tstDevPdmLoadR3Mod(pMod); else if (enmModType == TSTDEVPDMMODTYPE_RC || enmModType == TSTDEVPDMMODTYPE_R0) rc = tstDevPdmLoadR0RcMod(pMod); if (RT_SUCCESS(rc)) RTListAppend(&g_LstPdmMods, &pMod->NdPdmMods); else RTMemFree(pMod); } else rc = VERR_NO_MEMORY; } return rc; } /** * Tries to resolve the given symbol from the module given. * * @returns VBox status code. * @param pThis The device under test instance. * @param pszMod The module name. * @param enmModType The module type if the module needs to be loaded. * @param pszSymbol The symbol to resolve. * @param ppfn Where to store the value on success. */ DECLHIDDEN(int) tstDevPdmLdrGetSymbol(PTSTDEVDUTINT pThis, const char *pszMod, TSTDEVPDMMODTYPE enmModType, const char *pszSymbol, PFNRT *ppfn) { RT_NOREF(pThis); int rc = VINF_SUCCESS; PCTSTDEVPDMMOD pMod = tstDevPdmModFind(pszMod); if (!pMod) { /* Try to load the module. */ rc = tstDevPdmLoadMod(pszMod, enmModType); if (RT_SUCCESS(rc)) { pMod = tstDevPdmModFind(pszMod); AssertPtr(pMod); } } if (RT_SUCCESS(rc)) { if (pMod->enmType == TSTDEVPDMMODTYPE_R0 || pMod->enmType == TSTDEVPDMMODTYPE_RC) rc = RTLdrGetSymbolEx(pMod->hLdrMod, pMod->R0Rc.pvBits, (uintptr_t)pMod->R0Rc.pvBits, UINT32_MAX, pszSymbol, (PRTLDRADDR)ppfn); else rc = RTLdrGetSymbol(pMod->hLdrMod, pszSymbol, (void **)ppfn); } return rc; } /** * Create a new PDM device with default config. * * @returns VBox status code. * @param pszName Name of the device to create. * @param pDut The device under test structure the created PDM device instance is exercised under. */ static int tstDevPdmDevR3Create(const char *pszName, PTSTDEVDUTINT pDut) { int rc = VINF_SUCCESS; PCTSTDEVPDMDEV pPdmDev = tstDevPdmDeviceFind(pszName, NULL); if (RT_LIKELY(pPdmDev)) { PPDMCRITSECT pCritSect; /* Figure out how much we need. */ uint32_t cb = RT_UOFFSETOF_DYN(PDMDEVINS, achInstanceData[pPdmDev->pReg->cbInstanceCC]); cb = RT_ALIGN_32(cb, 64); uint32_t const offShared = cb; cb += RT_ALIGN_32(pPdmDev->pReg->cbInstanceShared, 64); uint32_t const cbCritSect = RT_ALIGN_32(sizeof(*pCritSect), 64); cb += cbCritSect; uint32_t const cbMsixState = RT_ALIGN_32(pPdmDev->pReg->cMaxMsixVectors * 16 + (pPdmDev->pReg->cMaxMsixVectors + 7) / 8, _4K); uint32_t const cbPciDev = RT_ALIGN_32(RT_UOFFSETOF_DYN(PDMPCIDEV, abMsixState[cbMsixState]), 64); uint32_t const cPciDevs = RT_MIN(pPdmDev->pReg->cMaxPciDevices, 1024); uint32_t const cbPciDevs = cbPciDev * cPciDevs; cb += cbPciDevs; PPDMDEVINS pDevIns = (PPDMDEVINS)RTMemAllocZ(cb); pDevIns->u32Version = PDM_DEVINS_VERSION; pDevIns->iInstance = 0; pDevIns->pReg = pPdmDev->pReg; pDevIns->pvInstanceDataR3 = &pDevIns->achInstanceData[0]; pDevIns->pHlpR3 = &g_tstDevPdmDevHlpR3; pDevIns->pCfg = &pDut->Cfg; pDevIns->Internal.s.pDut = pDut; pDevIns->cbRing3 = cb; pDevIns->fR0Enabled = false; pDevIns->fRCEnabled = false; pDevIns->pvInstanceDataR3 = (uint8_t *)pDevIns + offShared; pDevIns->pvInstanceDataForR3 = &pDevIns->achInstanceData[0]; pCritSect = (PPDMCRITSECT)((uint8_t *)pDevIns + offShared + RT_ALIGN_32(pPdmDev->pReg->cbInstanceShared, 64)); pDevIns->pCritSectRoR3 = pCritSect; pDevIns->cbPciDev = cbPciDev; pDevIns->cPciDevs = cPciDevs; for (uint32_t iPciDev = 0; iPciDev < cPciDevs; iPciDev++) { PPDMPCIDEV pPciDev = (PPDMPCIDEV)((uint8_t *)pDevIns->pCritSectRoR3 + cbCritSect + cbPciDev * iPciDev); if (iPciDev < RT_ELEMENTS(pDevIns->apPciDevs)) pDevIns->apPciDevs[iPciDev] = pPciDev; pPciDev->cbConfig = _4K; pPciDev->cbMsixState = cbMsixState; pPciDev->idxSubDev = (uint16_t)iPciDev; pPciDev->u32Magic = PDMPCIDEV_MAGIC; } RTCritSectInit(&pCritSect->s.CritSect); rc = pPdmDev->pReg->pfnConstruct(pDevIns, 0, pDevIns->pCfg); if (RT_SUCCESS(rc)) pDut->pDevIns = pDevIns; else { rc = pPdmDev->pReg->pfnDestruct(pDevIns); RTMemFree(pDevIns); } } else rc = VERR_NOT_FOUND; return rc; } DECLHIDDEN(int) tstDevPdmDeviceR3Construct(PTSTDEVDUTINT pDut) { PPDMDEVINS pDevInsR3 = pDut->pDevIns; pDevInsR3->pReg = pDut->pPdmDev->pReg; pDevInsR3->pHlpR3 = &g_tstDevPdmDevHlpR3; pDevInsR3->pCfg = &pDut->Cfg; pDevInsR3->Internal.s.pDut = pDut; return pDevInsR3->pReg->pfnConstruct(pDevInsR3, 0, &pDut->Cfg); } DECLCALLBACK(void *) tstDevTestsRun_QueryInterface(PPDMIBASE pInterface, const char *pszIID) { RT_NOREF(pInterface, pszIID); #if 0 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); PDMIBASE_RETURN_INTERFACE(pszIID, PDMILEDCONNECTORS, &pThis->ILedConnectors); PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIANOTIFY, &pThis->IMediaNotify); #endif return NULL; } /** * Run a given test config. * * @returns VBox status code. * @param pDevTstCfg The test config to run. */ static int tstDevTestsRun(PCTSTDEVCFG pDevTstCfg) { int rc = VINF_SUCCESS; for (uint32_t i = 0; i < pDevTstCfg->cTests; i++) { PCTSTDEVTEST pTest = &pDevTstCfg->aTests[i]; TSTDEVDUTINT Dut; Dut.pTest = pTest; Dut.enmCtx = TSTDEVDUTCTX_R3; Dut.pVm = (PVM)0x1000; Dut.SupSession.pDut = &Dut; Dut.Cfg.pDut = &Dut; Dut.pPdmDev = tstDevPdmDeviceFind(pDevTstCfg->pszDevName, NULL /*ppPdmDevR0*/); Dut.IBaseSts.pfnQueryInterface = tstDevTestsRun_QueryInterface; RTListInit(&Dut.LstIoPorts); RTListInit(&Dut.LstMmio); RTListInit(&Dut.LstTimers); RTListInit(&Dut.LstMmHeap); RTListInit(&Dut.LstPdmThreads); RTListInit(&Dut.LstSsmHandlers); RTListInit(&Dut.SupSession.LstSupSem); rc = RTCritSectRwInit(&Dut.CritSectLists); AssertRC(rc); rc = RTCritSectInitEx(&Dut.CritSectNop.s.CritSect, RTCRITSECT_FLAGS_NOP, NIL_RTLOCKVALCLASS, RTLOCKVAL_SUB_CLASS_NONE, "DutNop"); AssertRC(rc); if (!pTest->fR0Enabled) rc = tstDevPdmDevR3Create(pDevTstCfg->pszDevName, &Dut); else rc = tstDevPdmDevR0R3Create(pDevTstCfg->pszDevName, pTest->fRCEnabled, &Dut); if (RT_SUCCESS(rc)) { PCTSTDEVTESTCASE pTestcase = tstDevTestcaseFind(pTest->papszTestcaseIds[i]); if (pTestcase) rc = pTestcase->pTestcaseReg->pfnTestEntry(&Dut, pTest->papTestcaseCfg[i], pTest->pacTestcaseCfgItems[i]); else rc = VERR_NOT_FOUND; } } return rc; } int main(int argc, char *argv[]) { /* * Init the runtime and parse the arguments. */ RTEXITCODE rcExit = RTEXITCODE_SUCCESS; int rc = RTR3InitExe(argc, &argv, 0); if (RT_SUCCESS(rc)) { RTListInit(&g_LstPlugins); RTListInit(&g_LstTestcases); RTListInit(&g_LstPdmMods); RTListInit(&g_LstPdmR0Mods); RTListInit(&g_LstPdmDevs); /* Register builtin tests. */ tstDevRegisterTestcase(NULL, &g_TestcaseSsmFuzz); tstDevRegisterTestcase(NULL, &g_TestcaseSsmLoadDbg); tstDevRegisterTestcase(NULL, &g_TestcaseIoFuzz); PCTSTDEVCFG pDevTstCfg = NULL; rc = tstDevCfgLoad(argv[1], NULL, &pDevTstCfg); if (RT_SUCCESS(rc)) { if (pDevTstCfg->pszTstDevMod) rc = tstDevLoadPlugin(pDevTstCfg->pszTstDevMod); if (RT_SUCCESS(rc)) { rc = tstDevPdmLoadMod(pDevTstCfg->pszPdmR3Mod, TSTDEVPDMMODTYPE_R3); if ( RT_SUCCESS(rc) && pDevTstCfg->pszPdmR0Mod) rc = tstDevPdmLoadMod(pDevTstCfg->pszPdmR0Mod, TSTDEVPDMMODTYPE_R0); if ( RT_SUCCESS(rc) && pDevTstCfg->pszPdmRCMod) rc = tstDevPdmLoadMod(pDevTstCfg->pszPdmRCMod, TSTDEVPDMMODTYPE_RC); if (RT_SUCCESS(rc)) rc = tstDevTestsRun(pDevTstCfg); else rcExit = RTEXITCODE_FAILURE; } else rcExit = RTEXITCODE_FAILURE; } tstDevCfgDestroy(pDevTstCfg); } else rcExit = RTEXITCODE_FAILURE; return rcExit; }