diff options
Diffstat (limited to 'src/VBox/Devices/VMMDev/VMMDevTesting.cpp')
-rw-r--r-- | src/VBox/Devices/VMMDev/VMMDevTesting.cpp | 1111 |
1 files changed, 1111 insertions, 0 deletions
diff --git a/src/VBox/Devices/VMMDev/VMMDevTesting.cpp b/src/VBox/Devices/VMMDev/VMMDevTesting.cpp new file mode 100644 index 00000000..e7ddc94e --- /dev/null +++ b/src/VBox/Devices/VMMDev/VMMDevTesting.cpp @@ -0,0 +1,1111 @@ +/* $Id: VMMDevTesting.cpp $ */ +/** @file + * VMMDev - Testing Extensions. + * + * To enable: VBoxManage setextradata vmname VBoxInternal/Devices/VMMDev/0/Config/TestingEnabled 1 + */ + +/* + * Copyright (C) 2010-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 <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DEV_VMM +#include <VBox/VMMDev.h> +#include <VBox/vmm/vmapi.h> +#include <VBox/log.h> +#include <VBox/err.h> + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/string.h> +#include <iprt/time.h> +#include <iprt/test.h> + +#ifdef IN_RING3 +# define USING_VMM_COMMON_DEFS /* HACK ALERT! We ONLY want the EMT thread handles, so the common defs doesn't matter. */ +# include <VBox/vmm/vmcc.h> +#endif +#include <VBox/AssertGuest.h> + +#include "VMMDevState.h" +#include "VMMDevTesting.h" + + +#ifndef VBOX_WITHOUT_TESTING_FEATURES + +#define VMMDEV_TESTING_OUTPUT(a) \ + do \ + { \ + LogAlways(a);\ + LogRel(a);\ + } while (0) + +/** + * @callback_method_impl{FNIOMMMIONEWWRITE} + */ +static DECLCALLBACK(VBOXSTRICTRC) vmmdevTestingMmioWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void const *pv, unsigned cb) +{ + RT_NOREF_PV(pvUser); + + switch (off) + { + case VMMDEV_TESTING_MMIO_OFF_NOP_R3: +#ifndef IN_RING3 + return VINF_IOM_R3_MMIO_WRITE; +#endif + case VMMDEV_TESTING_MMIO_OFF_NOP: + return VINF_SUCCESS; + + default: + { + /* + * Readback register (64 bytes wide). + */ + if ( ( off >= VMMDEV_TESTING_MMIO_OFF_READBACK + && off + cb <= VMMDEV_TESTING_MMIO_OFF_READBACK + VMMDEV_TESTING_READBACK_SIZE) +#ifndef IN_RING3 + || ( off >= VMMDEV_TESTING_MMIO_OFF_READBACK_R3 + && off + cb <= VMMDEV_TESTING_MMIO_OFF_READBACK_R3 + VMMDEV_TESTING_READBACK_SIZE) +#endif + ) + { + PVMMDEV pThis = PDMDEVINS_2_DATA(pDevIns, PVMMDEV); + off &= VMMDEV_TESTING_READBACK_SIZE - 1; + switch (cb) + { + case 8: *(uint64_t *)&pThis->TestingData.abReadBack[off] = *(uint64_t const *)pv; break; + case 4: *(uint32_t *)&pThis->TestingData.abReadBack[off] = *(uint32_t const *)pv; break; + case 2: *(uint16_t *)&pThis->TestingData.abReadBack[off] = *(uint16_t const *)pv; break; + case 1: *(uint8_t *)&pThis->TestingData.abReadBack[off] = *(uint8_t const *)pv; break; + default: memcpy(&pThis->TestingData.abReadBack[off], pv, cb); break; + } + return VINF_SUCCESS; + } +#ifndef IN_RING3 + if ( off >= VMMDEV_TESTING_MMIO_OFF_READBACK_R3 + && off + cb <= VMMDEV_TESTING_MMIO_OFF_READBACK_R3 + 64) + return VINF_IOM_R3_MMIO_WRITE; +#endif + + break; + } + + /* + * Odd NOP accesses. + */ + case VMMDEV_TESTING_MMIO_OFF_NOP_R3 + 1: + case VMMDEV_TESTING_MMIO_OFF_NOP_R3 + 2: + case VMMDEV_TESTING_MMIO_OFF_NOP_R3 + 3: + case VMMDEV_TESTING_MMIO_OFF_NOP_R3 + 4: + case VMMDEV_TESTING_MMIO_OFF_NOP_R3 + 5: + case VMMDEV_TESTING_MMIO_OFF_NOP_R3 + 6: + case VMMDEV_TESTING_MMIO_OFF_NOP_R3 + 7: +#ifndef IN_RING3 + return VINF_IOM_R3_MMIO_WRITE; +#endif + case VMMDEV_TESTING_MMIO_OFF_NOP + 1: + case VMMDEV_TESTING_MMIO_OFF_NOP + 2: + case VMMDEV_TESTING_MMIO_OFF_NOP + 3: + case VMMDEV_TESTING_MMIO_OFF_NOP + 4: + case VMMDEV_TESTING_MMIO_OFF_NOP + 5: + case VMMDEV_TESTING_MMIO_OFF_NOP + 6: + case VMMDEV_TESTING_MMIO_OFF_NOP + 7: + return VINF_SUCCESS; + } + return VINF_SUCCESS; +} + + +/** + * @callback_method_impl{FNIOMMMIONEWREAD} + */ +static DECLCALLBACK(VBOXSTRICTRC) vmmdevTestingMmioRead(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void *pv, unsigned cb) +{ + RT_NOREF_PV(pvUser); + + switch (off) + { + case VMMDEV_TESTING_MMIO_OFF_NOP_R3: +#ifndef IN_RING3 + return VINF_IOM_R3_MMIO_READ; +#endif + /* fall thru. */ + case VMMDEV_TESTING_MMIO_OFF_NOP: + switch (cb) + { + case 8: + *(uint64_t *)pv = VMMDEV_TESTING_NOP_RET | ((uint64_t)VMMDEV_TESTING_NOP_RET << 32); + break; + case 4: + *(uint32_t *)pv = VMMDEV_TESTING_NOP_RET; + break; + case 2: + *(uint16_t *)pv = RT_LO_U16(VMMDEV_TESTING_NOP_RET); + break; + case 1: + *(uint8_t *)pv = (uint8_t)(VMMDEV_TESTING_NOP_RET & UINT8_MAX); + break; + default: + AssertFailed(); + return VERR_INTERNAL_ERROR_5; + } + return VINF_SUCCESS; + + + default: + { + /* + * Readback register (64 bytes wide). + */ + if ( ( off >= VMMDEV_TESTING_MMIO_OFF_READBACK + && off + cb <= VMMDEV_TESTING_MMIO_OFF_READBACK + 64) +#ifndef IN_RING3 + || ( off >= VMMDEV_TESTING_MMIO_OFF_READBACK_R3 + && off + cb <= VMMDEV_TESTING_MMIO_OFF_READBACK_R3 + 64) +#endif + ) + { + PVMMDEV pThis = PDMDEVINS_2_DATA(pDevIns, PVMMDEV); + off &= 0x3f; + switch (cb) + { + case 8: *(uint64_t *)pv = *(uint64_t const *)&pThis->TestingData.abReadBack[off]; break; + case 4: *(uint32_t *)pv = *(uint32_t const *)&pThis->TestingData.abReadBack[off]; break; + case 2: *(uint16_t *)pv = *(uint16_t const *)&pThis->TestingData.abReadBack[off]; break; + case 1: *(uint8_t *)pv = *(uint8_t const *)&pThis->TestingData.abReadBack[off]; break; + default: memcpy(pv, &pThis->TestingData.abReadBack[off], cb); break; + } + return VINF_SUCCESS; + } +#ifndef IN_RING3 + if ( off >= VMMDEV_TESTING_MMIO_OFF_READBACK_R3 + && off + cb <= VMMDEV_TESTING_MMIO_OFF_READBACK_R3 + 64) + return VINF_IOM_R3_MMIO_READ; +#endif + break; + } + + /* + * Odd NOP accesses (for 16-bit code mainly). + */ + case VMMDEV_TESTING_MMIO_OFF_NOP_R3 + 1: + case VMMDEV_TESTING_MMIO_OFF_NOP_R3 + 2: + case VMMDEV_TESTING_MMIO_OFF_NOP_R3 + 3: + case VMMDEV_TESTING_MMIO_OFF_NOP_R3 + 4: + case VMMDEV_TESTING_MMIO_OFF_NOP_R3 + 5: + case VMMDEV_TESTING_MMIO_OFF_NOP_R3 + 6: + case VMMDEV_TESTING_MMIO_OFF_NOP_R3 + 7: +#ifndef IN_RING3 + return VINF_IOM_R3_MMIO_READ; +#endif + case VMMDEV_TESTING_MMIO_OFF_NOP + 1: + case VMMDEV_TESTING_MMIO_OFF_NOP + 2: + case VMMDEV_TESTING_MMIO_OFF_NOP + 3: + case VMMDEV_TESTING_MMIO_OFF_NOP + 4: + case VMMDEV_TESTING_MMIO_OFF_NOP + 5: + case VMMDEV_TESTING_MMIO_OFF_NOP + 6: + case VMMDEV_TESTING_MMIO_OFF_NOP + 7: + { + static uint8_t const s_abNopValue[8] = + { + VMMDEV_TESTING_NOP_RET & 0xff, + (VMMDEV_TESTING_NOP_RET >> 8) & 0xff, + (VMMDEV_TESTING_NOP_RET >> 16) & 0xff, + (VMMDEV_TESTING_NOP_RET >> 24) & 0xff, + VMMDEV_TESTING_NOP_RET & 0xff, + (VMMDEV_TESTING_NOP_RET >> 8) & 0xff, + (VMMDEV_TESTING_NOP_RET >> 16) & 0xff, + (VMMDEV_TESTING_NOP_RET >> 24) & 0xff, + }; + + memset(pv, 0xff, cb); + memcpy(pv, &s_abNopValue[off & 7], RT_MIN(8 - (off & 7), cb)); + return VINF_SUCCESS; + } + } + + return VINF_IOM_MMIO_UNUSED_FF; +} + +#ifdef IN_RING3 + +/** + * Executes the VMMDEV_TESTING_CMD_VALUE_REG command when the data is ready. + * + * @param pDevIns The PDM device instance. + * @param pThis The instance VMMDev data. + */ +static void vmmdevTestingCmdExec_ValueReg(PPDMDEVINS pDevIns, PVMMDEV pThis) +{ + char *pszRegNm = strchr(pThis->TestingData.String.sz, ':'); + if (pszRegNm) + { + *pszRegNm++ = '\0'; + pszRegNm = RTStrStrip(pszRegNm); + } + char *pszValueNm = RTStrStrip(pThis->TestingData.String.sz); + size_t const cchValueNm = strlen(pszValueNm); + if (cchValueNm && pszRegNm && *pszRegNm) + { + VMCPUID idCpu = PDMDevHlpGetCurrentCpuId(pDevIns); + uint64_t u64Value; + int rc2 = PDMDevHlpDBGFRegNmQueryU64(pDevIns, idCpu, pszRegNm, &u64Value); + if (RT_SUCCESS(rc2)) + { + const char *pszWarn = rc2 == VINF_DBGF_TRUNCATED_REGISTER ? " truncated" : ""; +#if 1 /*!RTTestValue format*/ + char szFormat[128], szValue[128]; + RTStrPrintf(szFormat, sizeof(szFormat), "%%VR{%s}", pszRegNm); + rc2 = PDMDevHlpDBGFRegPrintf(pDevIns, idCpu, szValue, sizeof(szValue), szFormat); + if (RT_SUCCESS(rc2)) + VMMDEV_TESTING_OUTPUT(("testing: VALUE '%s'%*s: %16s {reg=%s}%s\n", + pszValueNm, + (ssize_t)cchValueNm - 12 > 48 ? 0 : 48 - ((ssize_t)cchValueNm - 12), "", + szValue, pszRegNm, pszWarn)); + else +#endif + VMMDEV_TESTING_OUTPUT(("testing: VALUE '%s'%*s: %'9llu (%#llx) [0] {reg=%s}%s\n", + pszValueNm, + (ssize_t)cchValueNm - 12 > 48 ? 0 : 48 - ((ssize_t)cchValueNm - 12), "", + u64Value, u64Value, pszRegNm, pszWarn)); + } + else + VMMDEV_TESTING_OUTPUT(("testing: error querying register '%s' for value '%s': %Rrc\n", + pszRegNm, pszValueNm, rc2)); + } + else + VMMDEV_TESTING_OUTPUT(("testing: malformed register value '%s'/'%s'\n", pszValueNm, pszRegNm)); +} + +#endif /* IN_RING3 */ + +/** + * @callback_method_impl{FNIOMIOPORTNEWOUT} + */ +static DECLCALLBACK(VBOXSTRICTRC) +vmmdevTestingIoWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t u32, unsigned cb) +{ + PVMMDEV pThis = PDMDEVINS_2_DATA(pDevIns, PVMMDEV); +#ifdef IN_RING3 + PVMMDEVCC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVMMDEVCC); +#endif + RT_NOREF_PV(pvUser); + + switch (offPort) + { + /* + * The NOP I/O ports are used for performance measurements. + */ + case VMMDEV_TESTING_IOPORT_NOP - VMMDEV_TESTING_IOPORT_BASE: + switch (cb) + { + case 4: + case 2: + case 1: + break; + default: + AssertFailed(); + return VERR_INTERNAL_ERROR_2; + } + return VINF_SUCCESS; + + case VMMDEV_TESTING_IOPORT_NOP_R3 - VMMDEV_TESTING_IOPORT_BASE: + switch (cb) + { + case 4: + case 2: + case 1: +#ifndef IN_RING3 + return VINF_IOM_R3_IOPORT_WRITE; +#else + return VINF_SUCCESS; +#endif + default: + AssertFailed(); + return VERR_INTERNAL_ERROR_2; + } + + /* The timestamp I/O ports are read-only. */ + case VMMDEV_TESTING_IOPORT_TS_LOW - VMMDEV_TESTING_IOPORT_BASE: + case VMMDEV_TESTING_IOPORT_TS_HIGH - VMMDEV_TESTING_IOPORT_BASE: + break; + + /* + * The command port (DWORD and WORD write only). + * (We have to allow WORD writes for 286, 186 and 8086 execution modes.) + */ + case VMMDEV_TESTING_IOPORT_CMD - VMMDEV_TESTING_IOPORT_BASE: + if (cb == 2) + { + u32 |= VMMDEV_TESTING_CMD_MAGIC_HI_WORD; + cb = 4; + } + if (cb == 4) + { + pThis->u32TestingCmd = u32; + pThis->offTestingData = 0; + pThis->cbReadableTestingData = 0; + RT_ZERO(pThis->TestingData); + return VINF_SUCCESS; + } + break; + + /* + * The data port. Used of providing data for a command. + */ + case VMMDEV_TESTING_IOPORT_DATA - VMMDEV_TESTING_IOPORT_BASE: + { + uint32_t uCmd = pThis->u32TestingCmd; + uint32_t off = pThis->offTestingData; + switch (uCmd) + { + case VMMDEV_TESTING_CMD_INIT: + case VMMDEV_TESTING_CMD_SUB_NEW: + case VMMDEV_TESTING_CMD_FAILED: + case VMMDEV_TESTING_CMD_SKIPPED: + case VMMDEV_TESTING_CMD_PRINT: + if ( off < sizeof(pThis->TestingData.String.sz) - 1 + && cb == 1) + { + if (u32) + { + pThis->TestingData.String.sz[off] = u32; + pThis->offTestingData = off + 1; + } + else + { +#ifdef IN_RING3 + pThis->TestingData.String.sz[off] = '\0'; + switch (uCmd) + { + case VMMDEV_TESTING_CMD_INIT: + VMMDEV_TESTING_OUTPUT(("testing: INIT '%s'\n", pThis->TestingData.String.sz)); + if (pThisCC->hTestingTest != NIL_RTTEST) + { + RTTestChangeName(pThisCC->hTestingTest, pThis->TestingData.String.sz); + RTTestBanner(pThisCC->hTestingTest); + } + break; + case VMMDEV_TESTING_CMD_SUB_NEW: + VMMDEV_TESTING_OUTPUT(("testing: SUB_NEW '%s'\n", pThis->TestingData.String.sz)); + if (pThisCC->hTestingTest != NIL_RTTEST) + RTTestSub(pThisCC->hTestingTest, pThis->TestingData.String.sz); + break; + case VMMDEV_TESTING_CMD_FAILED: + if (pThisCC->hTestingTest != NIL_RTTEST) + RTTestFailed(pThisCC->hTestingTest, "%s", pThis->TestingData.String.sz); + VMMDEV_TESTING_OUTPUT(("testing: FAILED '%s'\n", pThis->TestingData.String.sz)); + break; + case VMMDEV_TESTING_CMD_SKIPPED: + if (pThisCC->hTestingTest != NIL_RTTEST) + { + if (off) + RTTestSkipped(pThisCC->hTestingTest, "%s", pThis->TestingData.String.sz); + else + RTTestSkipped(pThisCC->hTestingTest, NULL); + } + VMMDEV_TESTING_OUTPUT(("testing: SKIPPED '%s'\n", pThis->TestingData.String.sz)); + break; + case VMMDEV_TESTING_CMD_PRINT: + if (pThisCC->hTestingTest != NIL_RTTEST && off) + RTTestPrintf(pThisCC->hTestingTest, RTTESTLVL_ALWAYS, "%s", pThis->TestingData.String.sz); + VMMDEV_TESTING_OUTPUT(("testing: '%s'\n", pThis->TestingData.String.sz)); + break; + } +#else + return VINF_IOM_R3_IOPORT_WRITE; +#endif + } + return VINF_SUCCESS; + } + break; + + case VMMDEV_TESTING_CMD_TERM: + case VMMDEV_TESTING_CMD_SUB_DONE: + if (cb == 2) + { + if (off == 0) + { + pThis->TestingData.Error.c = u32; + pThis->offTestingData = 2; + break; + } + if (off == 2) + { + u32 <<= 16; + u32 |= pThis->TestingData.Error.c & UINT16_MAX; + cb = 4; + off = 0; + } + else + break; + } + + if ( off == 0 + && cb == 4) + { +#ifdef IN_RING3 + pThis->TestingData.Error.c = u32; + if (uCmd == VMMDEV_TESTING_CMD_TERM) + { + if (pThisCC->hTestingTest != NIL_RTTEST) + { + while (RTTestErrorCount(pThisCC->hTestingTest) < u32) + RTTestErrorInc(pThisCC->hTestingTest); /* A bit stupid, but does the trick. */ + RTTestSubDone(pThisCC->hTestingTest); + RTTestSummaryAndDestroy(pThisCC->hTestingTest); + pThisCC->hTestingTest = NIL_RTTEST; + } + VMMDEV_TESTING_OUTPUT(("testing: TERM - %u errors\n", u32)); + } + else + { + if (pThisCC->hTestingTest != NIL_RTTEST) + { + while (RTTestSubErrorCount(pThisCC->hTestingTest) < u32) + RTTestErrorInc(pThisCC->hTestingTest); /* A bit stupid, but does the trick. */ + RTTestSubDone(pThisCC->hTestingTest); + } + VMMDEV_TESTING_OUTPUT(("testing: SUB_DONE - %u errors\n", u32)); + } + return VINF_SUCCESS; +#else + return VINF_IOM_R3_IOPORT_WRITE; +#endif + } + break; + + case VMMDEV_TESTING_CMD_VALUE: + if (cb == 4) + { + if (off == 0) + pThis->TestingData.Value.u64Value.s.Lo = u32; + else if (off == 4) + pThis->TestingData.Value.u64Value.s.Hi = u32; + else if (off == 8) + pThis->TestingData.Value.u32Unit = u32; + else + break; + pThis->offTestingData = off + 4; + return VINF_SUCCESS; + } + if (cb == 2) + { + if (off == 0) + pThis->TestingData.Value.u64Value.Words.w0 = (uint16_t)u32; + else if (off == 2) + pThis->TestingData.Value.u64Value.Words.w1 = (uint16_t)u32; + else if (off == 4) + pThis->TestingData.Value.u64Value.Words.w2 = (uint16_t)u32; + else if (off == 6) + pThis->TestingData.Value.u64Value.Words.w3 = (uint16_t)u32; + else if (off == 8) + pThis->TestingData.Value.u32Unit = (uint16_t)u32; + else if (off == 10) + pThis->TestingData.Value.u32Unit |= u32 << 16; + else + break; + pThis->offTestingData = off + 2; + return VINF_SUCCESS; + } + + if ( off >= 12 + && cb == 1 + && off - 12 < sizeof(pThis->TestingData.Value.szName) - 1) + { + if (u32) + { + pThis->TestingData.Value.szName[off - 12] = u32; + pThis->offTestingData = off + 1; + } + else + { +#ifdef IN_RING3 + pThis->TestingData.Value.szName[off - 12] = '\0'; + + RTTESTUNIT enmUnit = (RTTESTUNIT)pThis->TestingData.Value.u32Unit; + if (enmUnit <= RTTESTUNIT_INVALID || enmUnit >= RTTESTUNIT_END) + { + VMMDEV_TESTING_OUTPUT(("Invalid log value unit %#x\n", pThis->TestingData.Value.u32Unit)); + enmUnit = RTTESTUNIT_NONE; + } + if (pThisCC->hTestingTest != NIL_RTTEST) + RTTestValue(pThisCC->hTestingTest, pThis->TestingData.Value.szName, + pThis->TestingData.Value.u64Value.u, enmUnit); + + VMMDEV_TESTING_OUTPUT(("testing: VALUE '%s'%*s: %'9llu (%#llx) [%u]\n", + pThis->TestingData.Value.szName, + off - 12 > 48 ? 0 : 48 - (off - 12), "", + pThis->TestingData.Value.u64Value.u, pThis->TestingData.Value.u64Value.u, + pThis->TestingData.Value.u32Unit)); +#else + return VINF_IOM_R3_IOPORT_WRITE; +#endif + } + return VINF_SUCCESS; + } + break; + + + /* + * RTTestValue with the output from DBGFR3RegNmQuery. + */ + case VMMDEV_TESTING_CMD_VALUE_REG: + { + if ( off < sizeof(pThis->TestingData.String.sz) - 1 + && cb == 1) + { + pThis->TestingData.String.sz[off] = u32; + if (u32) + pThis->offTestingData = off + 1; + else +#ifdef IN_RING3 + vmmdevTestingCmdExec_ValueReg(pDevIns, pThis); +#else + return VINF_IOM_R3_IOPORT_WRITE; +#endif + return VINF_SUCCESS; + } + break; + } + + /* + * Query configuration. + */ + case VMMDEV_TESTING_CMD_QUERY_CFG: + { + switch (u32) + { + case VMMDEV_TESTING_CFG_DWORD0: + case VMMDEV_TESTING_CFG_DWORD1: + case VMMDEV_TESTING_CFG_DWORD2: + case VMMDEV_TESTING_CFG_DWORD3: + case VMMDEV_TESTING_CFG_DWORD4: + case VMMDEV_TESTING_CFG_DWORD5: + case VMMDEV_TESTING_CFG_DWORD6: + case VMMDEV_TESTING_CFG_DWORD7: + case VMMDEV_TESTING_CFG_DWORD8: + case VMMDEV_TESTING_CFG_DWORD9: + pThis->cbReadableTestingData = sizeof(pThis->TestingData.u32); + pThis->TestingData.u32 = pThis->au32TestingCfgDwords[u32 - VMMDEV_TESTING_CFG_DWORD0]; + break; + + case VMMDEV_TESTING_CFG_IS_NEM_LINUX: + case VMMDEV_TESTING_CFG_IS_NEM_WINDOWS: + case VMMDEV_TESTING_CFG_IS_NEM_DARWIN: + { + pThis->cbReadableTestingData = sizeof(pThis->TestingData.b); +#if defined(RT_OS_DARWIN) + pThis->TestingData.b = u32 == VMMDEV_TESTING_CFG_IS_NEM_DARWIN + && PDMDevHlpGetMainExecutionEngine(pDevIns) == VM_EXEC_ENGINE_NATIVE_API; +#elif defined(RT_OS_LINUX) + pThis->TestingData.b = u32 == VMMDEV_TESTING_CFG_IS_NEM_LINUX + && PDMDevHlpGetMainExecutionEngine(pDevIns) == VM_EXEC_ENGINE_NATIVE_API; +#elif defined(RT_OS_WINDOWS) + pThis->TestingData.b = u32 == VMMDEV_TESTING_CFG_IS_NEM_WINDOWS + && PDMDevHlpGetMainExecutionEngine(pDevIns) == VM_EXEC_ENGINE_NATIVE_API; +#else + pThis->TestingData.b = false; +#endif + break; + } + } + break; + } + + default: + break; + } + Log(("VMMDEV_TESTING_IOPORT_CMD: bad access; cmd=%#x off=%#x cb=%#x u32=%#x\n", uCmd, off, cb, u32)); + return VINF_SUCCESS; + } + + /* + * Configure the locking contention test. + */ + case VMMDEV_TESTING_IOPORT_LOCKED_HI - VMMDEV_TESTING_IOPORT_BASE: + case VMMDEV_TESTING_IOPORT_LOCKED_LO - VMMDEV_TESTING_IOPORT_BASE: + switch (cb) + { + case 4: + { + bool const fReadWriteSection = pThis->TestingLockControl.s.fReadWriteSection; + int rc; +#ifndef IN_RING3 + if (!pThis->TestingLockControl.s.fMustSucceed) + { + if (!fReadWriteSection) + rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CritSect, VINF_IOM_R3_IOPORT_WRITE); + else + rc = PDMDevHlpCritSectRwEnterExcl(pDevIns, &pThis->CritSectRw, VINF_IOM_R3_IOPORT_WRITE); + if (rc != VINF_SUCCESS) + return rc; + } + else +#endif + { + if (!fReadWriteSection) + rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CritSect, VINF_SUCCESS); + else + rc = PDMDevHlpCritSectRwEnterExcl(pDevIns, &pThis->CritSectRw, VINF_SUCCESS); + AssertRCReturn(rc, rc); + } + + if (offPort == VMMDEV_TESTING_IOPORT_LOCKED_LO - VMMDEV_TESTING_IOPORT_BASE) + { + if (pThis->TestingLockControl.au32[0] != u32) + { + pThis->TestingLockControl.au32[0] = u32; + PDMDevHlpSUPSemEventSignal(pDevIns, pThis->hTestingLockEvt); + } + } + else + { + u32 &= ~VMMDEV_TESTING_LOCKED_HI_MBZ_MASK; + if (pThis->TestingLockControl.au32[1] != u32) + { + pThis->TestingLockControl.au32[1] = u32; + PDMDevHlpSUPSemEventSignal(pDevIns, pThis->hTestingLockEvt); + } + } + + if (!fReadWriteSection) + PDMDevHlpCritSectLeave(pDevIns, &pThis->CritSect); + else + PDMDevHlpCritSectRwLeaveExcl(pDevIns, &pThis->CritSectRw); + return VINF_SUCCESS; + } + + case 2: + case 1: + ASSERT_GUEST_FAILED(); + break; + + default: + AssertFailed(); + return VERR_INTERNAL_ERROR_2; + } + + default: + break; + } + + return VERR_IOM_IOPORT_UNUSED; +} + + +/** + * @callback_method_impl{FNIOMIOPORTNEWIN} + */ +static DECLCALLBACK(VBOXSTRICTRC) +vmmdevTestingIoRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t *pu32, unsigned cb) +{ + PVMMDEV pThis = PDMDEVINS_2_DATA(pDevIns, PVMMDEV); + RT_NOREF_PV(pvUser); + + switch (offPort) + { + /* + * The NOP I/O ports are used for performance measurements. + */ + case VMMDEV_TESTING_IOPORT_NOP - VMMDEV_TESTING_IOPORT_BASE: + switch (cb) + { + case 4: + case 2: + case 1: + break; + default: + AssertFailed(); + return VERR_INTERNAL_ERROR_2; + } + *pu32 = VMMDEV_TESTING_NOP_RET; + return VINF_SUCCESS; + + case VMMDEV_TESTING_IOPORT_NOP_R3 - VMMDEV_TESTING_IOPORT_BASE: + switch (cb) + { + case 4: + case 2: + case 1: +#ifndef IN_RING3 + return VINF_IOM_R3_IOPORT_READ; +#else + *pu32 = VMMDEV_TESTING_NOP_RET; + return VINF_SUCCESS; +#endif + default: + AssertFailed(); + return VERR_INTERNAL_ERROR_2; + } + + /* + * The timestamp I/O ports are obviously used for getting a good fix + * on the current time (as seen by the host?). + * + * The high word is latched when reading the low, so reading low + high + * gives you a 64-bit timestamp value. + */ + case VMMDEV_TESTING_IOPORT_TS_LOW - VMMDEV_TESTING_IOPORT_BASE: + if (cb == 4) + { + uint64_t NowTS = RTTimeNanoTS(); + *pu32 = (uint32_t)NowTS; + pThis->u32TestingHighTimestamp = (uint32_t)(NowTS >> 32); + return VINF_SUCCESS; + } + break; + + case VMMDEV_TESTING_IOPORT_TS_HIGH - VMMDEV_TESTING_IOPORT_BASE: + if (cb == 4) + { + *pu32 = pThis->u32TestingHighTimestamp; + return VINF_SUCCESS; + } + break; + + /* + * Just return the current locking configuration value after first + * acquiring the lock of course. + */ + case VMMDEV_TESTING_IOPORT_LOCKED_LO - VMMDEV_TESTING_IOPORT_BASE: + case VMMDEV_TESTING_IOPORT_LOCKED_HI - VMMDEV_TESTING_IOPORT_BASE: + switch (cb) + { + case 4: + case 2: + case 1: + { + /* + * Check configuration and enter the designation critical + * section in the specific fashion. + */ + bool const fReadWriteSection = pThis->TestingLockControl.s.fReadWriteSection; + bool const fEmtShared = pThis->TestingLockControl.s.fEmtShared; + int rc; +#ifndef IN_RING3 + if (!pThis->TestingLockControl.s.fMustSucceed) + { + if (!fReadWriteSection) + rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CritSect, VINF_IOM_R3_IOPORT_READ); + else if (!fEmtShared) + rc = PDMDevHlpCritSectRwEnterExcl(pDevIns, &pThis->CritSectRw, VINF_IOM_R3_IOPORT_READ); + else + rc = PDMDevHlpCritSectRwEnterShared(pDevIns, &pThis->CritSectRw, VINF_IOM_R3_IOPORT_READ); + if (rc != VINF_SUCCESS) + return rc; + } + else +#endif + { + if (!fReadWriteSection) + rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CritSect, VINF_SUCCESS); + else if (!fEmtShared) + rc = PDMDevHlpCritSectRwEnterExcl(pDevIns, &pThis->CritSectRw, VINF_SUCCESS); + else + rc = PDMDevHlpCritSectRwEnterShared(pDevIns, &pThis->CritSectRw, VINF_SUCCESS); + AssertRCReturn(rc, rc); + } + + /* + * Grab return value and, if requested, hold for a while. + */ + *pu32 = pThis->TestingLockControl.au32[ offPort + - (VMMDEV_TESTING_IOPORT_LOCKED_LO - VMMDEV_TESTING_IOPORT_BASE)]; + uint64_t cTicks = (uint64_t)pThis->TestingLockControl.s.cKiloTicksEmtHold * _1K; + if (cTicks) + { + uint64_t const uStartTick = ASMReadTSC(); + do + { + ASMNopPause(); + ASMNopPause(); + } while (ASMReadTSC() - uStartTick < cTicks); + } + + /* + * Leave. + */ + if (!fReadWriteSection) + PDMDevHlpCritSectLeave(pDevIns, &pThis->CritSect); + else if (!fEmtShared) + PDMDevHlpCritSectRwLeaveExcl(pDevIns, &pThis->CritSectRw); + else + PDMDevHlpCritSectRwLeaveShared(pDevIns, &pThis->CritSectRw); + return VINF_SUCCESS; + } + + default: + AssertFailed(); + return VERR_INTERNAL_ERROR_2; + } + + /* + * The command registers is write-only. + */ + case VMMDEV_TESTING_IOPORT_CMD - VMMDEV_TESTING_IOPORT_BASE: + break; + + /* + * The data register is only readable after a query command, otherwise it + * behaves as an undefined port. Return zeros if the guest reads too much. + */ + case VMMDEV_TESTING_IOPORT_DATA - VMMDEV_TESTING_IOPORT_BASE: + if (pThis->cbReadableTestingData > 0) + { + if (pThis->offTestingData < pThis->cbReadableTestingData) + { + switch (RT_MIN(cb, pThis->cbReadableTestingData - pThis->offTestingData)) + { + case 1: + *pu32 = pThis->TestingData.ab[pThis->offTestingData++]; + break; + case 2: + *pu32 = pThis->TestingData.ab[pThis->offTestingData] + | ((uint32_t)pThis->TestingData.ab[pThis->offTestingData + 1] << 8); + pThis->offTestingData += 2; + break; + case 3: + *pu32 = pThis->TestingData.ab[pThis->offTestingData] + | ((uint32_t)pThis->TestingData.ab[pThis->offTestingData + 1] << 8) + | ((uint32_t)pThis->TestingData.ab[pThis->offTestingData + 2] << 16); + pThis->offTestingData += 3; + break; + case 4: + *pu32 = pThis->TestingData.ab[pThis->offTestingData] + | ((uint32_t)pThis->TestingData.ab[pThis->offTestingData + 1] << 8) + | ((uint32_t)pThis->TestingData.ab[pThis->offTestingData + 2] << 16) + | ((uint32_t)pThis->TestingData.ab[pThis->offTestingData + 3] << 24); + pThis->offTestingData += 4; + break; + } + } + else + *pu32 = 0; + return VINF_SUCCESS; + } + break; + + default: + break; + } + + return VERR_IOM_IOPORT_UNUSED; +} + +#ifdef IN_RING3 + +/** + * @callback_method_impl{FNPDMTHREADDEV} + */ +static DECLCALLBACK(int) vmmdevR3TestingLockingThread(PPDMDEVINS pDevIns, PPDMTHREAD pThread) +{ + PVMMDEV pThis = PDMDEVINS_2_DATA(pDevIns, PVMMDEV); + PVM pVM = PDMDevHlpGetVM(pDevIns); + AssertPtr(pVM); + + while (RT_LIKELY(pThread->enmState == PDMTHREADSTATE_RUNNING)) + { + int rc; + uint32_t cNsNextWait = 0; + uint32_t const fCfgHi = pThis->TestingLockControl.au32[1]; + if (fCfgHi & VMMDEV_TESTING_LOCKED_HI_ENABLED) + { + /* + * take lock + */ + if (!(fCfgHi & VMMDEV_TESTING_LOCKED_HI_TYPE_RW)) + rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CritSect, VINF_SUCCESS); + else if (!(fCfgHi & VMMDEV_TESTING_LOCKED_HI_THREAD_SHARED)) + rc = PDMDevHlpCritSectRwEnterExcl(pDevIns, &pThis->CritSectRw, VINF_SUCCESS); + else + rc = PDMDevHlpCritSectRwEnterShared(pDevIns, &pThis->CritSectRw, VINF_SUCCESS); + AssertLogRelRCReturn(rc, rc); + + /* + * Delay releasing lock. + */ + cNsNextWait = pThis->TestingLockControl.s.cUsBetween * RT_NS_1US; + if (pThis->TestingLockControl.s.cUsHold) + { + PDMDevHlpSUPSemEventWaitNsRelIntr(pDevIns, pThis->hTestingLockEvt, pThis->TestingLockControl.s.cUsHold); + if (pThis->TestingLockControl.s.fPokeBeforeRelease) + VMCC_FOR_EACH_VMCPU_STMT(pVM, RTThreadPoke(pVCpu->hThread)); + } + + /* + * Release lock. + */ + if (!(fCfgHi & VMMDEV_TESTING_LOCKED_HI_TYPE_RW)) + rc = PDMDevHlpCritSectLeave(pDevIns, &pThis->CritSect); + else if (!(fCfgHi & VMMDEV_TESTING_LOCKED_HI_THREAD_SHARED)) + rc = PDMDevHlpCritSectRwLeaveExcl(pDevIns, &pThis->CritSectRw); + else + rc = PDMDevHlpCritSectRwLeaveShared(pDevIns, &pThis->CritSectRw); + AssertLogRelRCReturn(rc, rc); + } + + /* + * Wait for the next iteration. + */ + if (RT_LIKELY(pThread->enmState == PDMTHREADSTATE_RUNNING)) + { /* likely */ } + else + break; + if (cNsNextWait > 0) + PDMDevHlpSUPSemEventWaitNsRelIntr(pDevIns, pThis->hTestingLockEvt, cNsNextWait); + else + PDMDevHlpSUPSemEventWaitNoResume(pDevIns, pThis->hTestingLockEvt, RT_INDEFINITE_WAIT); + } + + return VINF_SUCCESS; +} + + +/** + * @callback_method_impl{FNPDMTHREADWAKEUPDEV} + */ +static DECLCALLBACK(int) vmmdevR3TestingLockingThreadWakeup(PPDMDEVINS pDevIns, PPDMTHREAD pThread) +{ + PVMMDEV pThis = PDMDEVINS_2_DATA(pDevIns, PVMMDEV); + RT_NOREF(pThread); + return PDMDevHlpSUPSemEventSignal(pDevIns, pThis->hTestingLockEvt); +} + + +/** + * Initializes the testing part of the VMMDev if enabled. + * + * @param pDevIns The VMMDev device instance. + */ +void vmmdevR3TestingTerminate(PPDMDEVINS pDevIns) +{ + PVMMDEV pThis = PDMDEVINS_2_DATA(pDevIns, PVMMDEV); + PVMMDEVCC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVMMDEVCC); + if (!pThis->fTestingEnabled) + return; + + if (pThisCC->hTestingTest != NIL_RTTEST) + { + RTTestFailed(pThisCC->hTestingTest, "Still open at vmmdev destruction."); + RTTestSummaryAndDestroy(pThisCC->hTestingTest); + pThisCC->hTestingTest = NIL_RTTEST; + } +} + + +/** + * Initializes the testing part of the VMMDev if enabled. + * + * @returns VBox status code. + * @param pDevIns The VMMDev device instance. + */ +int vmmdevR3TestingInitialize(PPDMDEVINS pDevIns) +{ + PVMMDEV pThis = PDMDEVINS_2_DATA(pDevIns, PVMMDEV); + PVMMDEVCC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVMMDEVCC); + int rc; + + if (!pThis->fTestingEnabled) + return VINF_SUCCESS; + + if (pThis->fTestingMMIO) + { + /* + * Register a chunk of MMIO memory that we'll use for various + * tests interfaces. Optional, needs to be explicitly enabled. + */ + rc = PDMDevHlpMmioCreateAndMap(pDevIns, VMMDEV_TESTING_MMIO_BASE, VMMDEV_TESTING_MMIO_SIZE, + vmmdevTestingMmioWrite, vmmdevTestingMmioRead, + IOMMMIO_FLAGS_READ_PASSTHRU | IOMMMIO_FLAGS_WRITE_PASSTHRU, + "VMMDev Testing", &pThis->hMmioTesting); + AssertRCReturn(rc, rc); + } + + /* + * Register the I/O ports used for testing. + */ + rc = PDMDevHlpIoPortCreateAndMap(pDevIns, VMMDEV_TESTING_IOPORT_BASE, VMMDEV_TESTING_IOPORT_COUNT, + vmmdevTestingIoWrite, vmmdevTestingIoRead, "VMMDev Testing", NULL /*paExtDescs*/, + &pThis->hIoPortTesting); + AssertRCReturn(rc, rc); + + /* + * Initialize the read/write critical section used for the locking tests. + */ + rc = PDMDevHlpCritSectRwInit(pDevIns, &pThis->CritSectRw, RT_SRC_POS, "VMMLockRW"); + AssertRCReturn(rc, rc); + + /* + * Create the locking thread. + */ + rc = PDMDevHlpSUPSemEventCreate(pDevIns, &pThis->hTestingLockEvt); + AssertRCReturn(rc, rc); + rc = PDMDevHlpThreadCreate(pDevIns, &pThisCC->pTestingLockThread, NULL /*pvUser*/, vmmdevR3TestingLockingThread, + vmmdevR3TestingLockingThreadWakeup, 0 /*cbStack*/, RTTHREADTYPE_IO, "VMMLockT"); + AssertRCReturn(rc, rc); + + /* + * Open the XML output file(/pipe/whatever) if specfied. + */ + rc = RTTestCreateEx("VMMDevTesting", RTTEST_C_USE_ENV | RTTEST_C_NO_TLS | RTTEST_C_XML_DELAY_TOP_TEST, + RTTESTLVL_DEBUG, -1 /*iNativeTestPipe*/, pThisCC->pszTestingXmlOutput, &pThisCC->hTestingTest); + if (RT_FAILURE(rc)) + return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, "Error creating testing instance"); + + return VINF_SUCCESS; +} + +#else /* !IN_RING3 */ + +/** + * Does the ring-0/raw-mode initialization of the testing part if enabled. + * + * @returns VBox status code. + * @param pDevIns The VMMDev device instance. + */ +int vmmdevRZTestingInitialize(PPDMDEVINS pDevIns) +{ + PVMMDEV pThis = PDMDEVINS_2_DATA(pDevIns, PVMMDEV); + int rc; + + if (!pThis->fTestingEnabled) + return VINF_SUCCESS; + + if (pThis->fTestingMMIO) + { + rc = PDMDevHlpMmioSetUpContext(pDevIns, pThis->hMmioTesting, vmmdevTestingMmioWrite, vmmdevTestingMmioRead, NULL); + AssertRCReturn(rc, rc); + } + + rc = PDMDevHlpIoPortSetUpContext(pDevIns, pThis->hIoPortTesting, vmmdevTestingIoWrite, vmmdevTestingIoRead, NULL); + AssertRCReturn(rc, rc); + + return VINF_SUCCESS; +} + +#endif /* !IN_RING3 */ +#endif /* !VBOX_WITHOUT_TESTING_FEATURES */ + |