diff options
Diffstat (limited to '')
-rw-r--r-- | src/VBox/ValidationKit/utils/nt/Makefile.kmk | 63 | ||||
-rw-r--r-- | src/VBox/ValidationKit/utils/nt/ntFlushVirtualMemory.cpp | 498 | ||||
-rw-r--r-- | src/VBox/ValidationKit/utils/nt/ntsetfreq.cpp | 161 | ||||
-rw-r--r-- | src/VBox/ValidationKit/utils/nt/nttimesources.cpp | 246 |
4 files changed, 968 insertions, 0 deletions
diff --git a/src/VBox/ValidationKit/utils/nt/Makefile.kmk b/src/VBox/ValidationKit/utils/nt/Makefile.kmk new file mode 100644 index 00000000..67ab901a --- /dev/null +++ b/src/VBox/ValidationKit/utils/nt/Makefile.kmk @@ -0,0 +1,63 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Windows NT Specific Utilities. +# + +# +# 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>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# Set Clock Frequency Utility. +# +PROGRAMS.win += ntSetFreq +ntSetFreq_TEMPLATE = VBoxValidationKitR3 +ntSetFreq_SOURCES = ntsetfreq.cpp +ntSetFreq_VBOX_IMPORT_CHECKER.win.x86 = nt350 + +# +# Test coherency among NT time sources. +# +PROGRAMS.win += ntTimeSources +ntTimeSources_TEMPLATE = VBoxValidationKitR3 +ntTimeSources_SOURCES = nttimesources.cpp + +# +# Test NtFlushVirtualMemory. +# +PROGRAMS.win += ntFlushVirtualMemory +ntFlushVirtualMemory_TEMPLATE = VBoxValidationKitR3 +ntFlushVirtualMemory_SOURCES = ntFlushVirtualMemory.cpp + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/utils/nt/ntFlushVirtualMemory.cpp b/src/VBox/ValidationKit/utils/nt/ntFlushVirtualMemory.cpp new file mode 100644 index 00000000..cb739498 --- /dev/null +++ b/src/VBox/ValidationKit/utils/nt/ntFlushVirtualMemory.cpp @@ -0,0 +1,498 @@ +/* $Id: ntFlushVirtualMemory.cpp $ */ +/** @file + * Memory mapped files testcase - NT. + */ + +/* + * Copyright (C) 2007-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/nt/nt.h> + +#include <iprt/alloca.h> +#include <iprt/file.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/path.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/thread.h> +#include <iprt/err.h> +#include <iprt/x86.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Create page signature. */ +#define MAKE_PAGE_SIGNATURE(a_iPage) ((a_iPage) | UINT32_C(0x42000000) ) + +/** Number history entries on the page. */ +#define NUM_ROUND_HISTORY 16 + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** How chatty we should be. */ +static uint32_t g_cVerbosity = 0; + + +/** + * Checks if the on-disk file matches our expectations. + * + * @returns IPRT status code, fully bitched. + * @param pszFilename The name of the file. + * @param pu32BufChk Buffer to read the file into + * @param pu32BufOrg Expected file content. + * @param cbBuf The buffer size. + * @param iRound The update round. + */ +static int CheckFile(const char *pszFilename, uint32_t *pu32BufChk, uint32_t const *pu32BufOrg, size_t cbBuf, uint32_t iRound) +{ + /* + * Open and read the file into memory. + */ + HANDLE hFile; + int rc = RTNtPathOpen(pszFilename, + GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_NO_INTERMEDIATE_BUFFERING, + OBJ_CASE_INSENSITIVE, + &hFile, + NULL); + if (RT_SUCCESS(rc)) + { + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NTSTATUS rcNt = NtReadFile(hFile, NULL /*hEvent*/, NULL /*pfnApc*/, NULL /*pvApcCtx*/, + &Ios, pu32BufChk, (ULONG)cbBuf, NULL /*poffFile*/, NULL /*pvKey*/); + if (NT_SUCCESS(rcNt) || Ios.Information != cbBuf) + { + /* + * See if the content of the file matches our expectations. + */ + if (memcmp(pu32BufChk, pu32BufOrg, cbBuf) == 0) + { /* matches - likely */ } + else + { + RTMsgError("Round %u: Buffer mismatch!\n", iRound); + + /* Try figure where the differences are. */ + size_t const cPages = cbBuf / X86_PAGE_SIZE; + size_t const cItemsPerPage = X86_PAGE_SIZE / sizeof(pu32BufOrg[0]); + for (uint32_t iPage = 0; iPage < cPages; iPage++) + for (uint32_t iItem = 0; iItem < cItemsPerPage; iItem++) + { + uint32_t uValue = pu32BufChk[iPage * cItemsPerPage + iItem]; + uint32_t uExpected = pu32BufOrg[iPage * cItemsPerPage + iItem]; + if (uValue != uExpected) + RTMsgError("Round %u: page #%u, index #%u: %#x, expected %#x\n", + iRound, iPage, iItem, uValue, uExpected); + } + + rc = VERR_MISMATCH; + } + } + else if (NT_SUCCESS(rcNt)) + { + RTMsgError("Round %u: NtReadFile return %zu bytes intead of %zu!\n", iRound, Ios.Information, cbBuf); + rc = VERR_READ_ERROR; + } + else + { + RTMsgError("Round %u: NtReadFile(%#x) failed: %#x (%#x)\n", iRound, cbBuf, rcNt, Ios.Status); + rc = RTErrConvertFromNtStatus(rcNt); + } + + /* + * Close the file and return. + */ + rcNt = NtClose(hFile); + if (!NT_SUCCESS(rcNt)) + { + RTMsgError("Round %u: NtCloseFile() failed: %#x\n", iRound, rcNt); + rc = RTErrConvertFromNtStatus(rcNt); + } + } + else + RTMsgError("Round %u: RTNtPathOpen() failed: %Rrc\n", iRound, rc); + return rc; +} + + +/** + * Manually checks whether the buffer matches up to our expectations. + * + * @returns IPRT status code, fully bitched. + * @param pu32Buf The buffer/mapping to check. + * @param cbBuf The buffer size. + * @param iRound The update round. + * @param cFlushesLeft Number of flushes left in the round. + */ +static int CheckBuffer(uint32_t const *pu32Buf, size_t cbBuf, uint32_t iRound, uint32_t cFlushesLeft) +{ + size_t const cPages = cbBuf / X86_PAGE_SIZE; + size_t const cItemsPerPage = X86_PAGE_SIZE / sizeof(pu32Buf[0]); + size_t const offPage = iRound & (NUM_ROUND_HISTORY - 1); + uint32_t const uValue = iRound | (cFlushesLeft << 20); +//RTPrintf("debug: CheckBuffer: %p %u/%u\n", pu32Buf, iRound, cFlushesLeft); + + for (uint32_t iPage = 0; iPage < cPages; iPage++) + { + uint32_t uActual = pu32Buf[iPage * cItemsPerPage + offPage]; + if (uActual != uValue) + { + RTMsgError("Round %u/%u: page #%u: last entry is corrupted: %#x, expected %#x\n", + iRound, cFlushesLeft, iPage, uActual, uValue); + return VERR_MISMATCH; + } + + uActual = pu32Buf[iPage * cItemsPerPage + cItemsPerPage - 1]; + if (uActual != MAKE_PAGE_SIGNATURE(iPage)) + { + RTMsgError("Round %u/%u: page #%u magic corrupted: %#x, expected %#x\n", + iRound, cFlushesLeft, iPage, uActual, MAKE_PAGE_SIGNATURE(iPage)); + return VERR_INVALID_MAGIC; + } + } + + /* + * Check previous rounds. + */ + for (uint32_t cRoundsAgo = 1; cRoundsAgo < NUM_ROUND_HISTORY - 1 && cRoundsAgo <= iRound; cRoundsAgo++) + { + uint32_t iOldRound = iRound - cRoundsAgo; + size_t const offOldPage = iOldRound & (NUM_ROUND_HISTORY - 1); + for (uint32_t iPage = 0; iPage < cPages; iPage++) + { + uint32_t uActual = pu32Buf[iPage * cItemsPerPage + offOldPage]; + if (uActual != iOldRound) + { + RTMsgError("Round %u/%u: page #%u: entry from %u rounds ago is corrupted: %#x, expected %#x\n", + iRound, cFlushesLeft, iPage, cRoundsAgo, uActual, uValue); + return VERR_MISMATCH; + } + } + } + + return VINF_SUCCESS; +} + + +/** + * Updates the buffer. + * + * @param pu32Buf The buffer/mapping to update. + * @param cbBuf The buffer size. + * @param iRound The update round. + * @param cFlushesLeft Number of flushes left in this round. + */ +static void UpdateBuffer(uint32_t *pu32Buf, size_t cbBuf, uint32_t iRound, uint32_t cFlushesLeft) +{ + size_t const cPages = cbBuf / X86_PAGE_SIZE; + size_t const cItemsPerPage = X86_PAGE_SIZE / sizeof(pu32Buf[0]); + size_t const offPage = iRound & (NUM_ROUND_HISTORY - 1); + uint32_t const uValue = iRound | (cFlushesLeft << 20); +//RTPrintf("debug: UpdateBuffer: %p %u/%u\n", pu32Buf, iRound, cFlushesLeft); + + for (uint32_t iPage = 0; iPage < cPages; iPage++) + pu32Buf[iPage * cItemsPerPage + offPage] = uValue; +} + + + +/** + * Modifies the file via memory mapping. + * + * @returns IPRT status code, fully bitched. + * @param pszFilename The file we're using as a test bed. + * @param pu32BufOrg The sane copy of the file that gets updated in + * parallel. + * @param cbBuf The size of the file and bufer. + * @param iRound The current round number. + * @param fCheckFirst Whether to read from the mapping the mapping + * before dirtying it the first time around. + * @param fCheckAfterFlush Whether to read from the mapping the mapping + * before dirtying it after a flush. + * @param cFlushes How many times we modify the mapping and flush + * it before one final modification and unmapping. + * @param fLargePages Whether to use large pages. + */ +static int MakeModifications(const char *pszFilename, uint32_t *pu32BufOrg, size_t cbBuf, uint32_t iRound, + bool fCheckFirst, bool fCheckAfterFlush, uint32_t cFlushes, bool fLargePages) +{ + + HANDLE hFile = RTNT_INVALID_HANDLE_VALUE; + int rc = RTNtPathOpen(pszFilename, + GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_NO_INTERMEDIATE_BUFFERING, + OBJ_CASE_INSENSITIVE, + &hFile, + NULL); + if (RT_SUCCESS(rc)) + { + HANDLE hSection; + NTSTATUS rcNt = NtCreateSection(&hSection, + SECTION_ALL_ACCESS, + NULL, /*pObjAttrs*/ + NULL, /*pcbMax*/ + PAGE_READWRITE, + SEC_COMMIT, + hFile); + NtClose(hFile); + if (NT_SUCCESS(rcNt)) + { + PVOID pvMapping = NULL; + SIZE_T cbMapping = 0; + rcNt = NtMapViewOfSection(hSection, NtCurrentProcess(), + &pvMapping, + 0, /* ZeroBits */ + 0, /* CommitSize */ + NULL, /* SectionOffset */ + &cbMapping, + ViewUnmap, + fLargePages ? MEM_LARGE_PAGES : 0, + PAGE_READWRITE); + if (NT_SUCCESS(rcNt)) + { + /* + * Make the modifications. + */ + if (g_cVerbosity >= 2) + RTPrintf("debug: pvMapping=%p LB %#x\n", pvMapping, cbBuf); + + for (uint32_t iInner = 0;; iInner++) + { + if (iInner ? fCheckAfterFlush : fCheckFirst) + { + if (iInner == 0) + rc = CheckBuffer((uint32_t *)pvMapping, cbBuf, iRound - 1, 0); + else + rc = CheckBuffer((uint32_t *)pvMapping, cbBuf, iRound, cFlushes - iInner + 1); + if (RT_FAILURE(rc)) + { + RTMsgError("Round %u/%u: NtUnmapViewOfSection failed: %#x\n", iRound, rcNt); + break; + } + } + + UpdateBuffer((uint32_t *)pvMapping, cbBuf, iRound, cFlushes - iInner); + UpdateBuffer(pu32BufOrg, cbBuf, iRound, cFlushes - iInner); + + if (iInner >= cFlushes) + break; + + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + SIZE_T cbBuf2 = cbBuf; + PVOID pvMapping2 = pvMapping; + rcNt = NtFlushVirtualMemory(NtCurrentProcess(), &pvMapping2, &cbBuf2, &Ios); + if (!NT_SUCCESS(rcNt)) + { + RTMsgError("Round %u: NtFlushVirtualMemory failed: %#x\n", iRound, rcNt); + rc = RTErrConvertFromNtStatus(rcNt); + break; + } + } + + /* + * Cleanup. + */ + rcNt = NtUnmapViewOfSection(NtCurrentProcess(), pvMapping); + if (!NT_SUCCESS(rcNt)) + { + RTMsgError("Round %u: NtUnmapViewOfSection failed: %#x\n", iRound, rcNt); + rc = RTErrConvertFromNtStatus(rcNt); + } + } + else + { + RTMsgError("Round %u: NtMapViewOfSection failed: %#x\n", iRound, rcNt); + rc = RTErrConvertFromNtStatus(rcNt); + } + + rcNt = NtClose(hSection); + if (!NT_SUCCESS(rcNt)) + { + RTMsgError("Round %u: NtClose(hSection) failed: %#x\n", iRound, rcNt); + rc = RTErrConvertFromNtStatus(rcNt); + } + } + else + { + RTMsgError("Round %u: NtCreateSection failed: %#x\n", iRound, rcNt); + rc = RTErrConvertFromNtStatus(rcNt); + } + } + else + RTMsgError("Round %u: Error opening file '%s' for memory mapping: %Rrc\n", iRound, pszFilename, rc); + return rc; +} + + +int main(int argc, char **argv) +{ + /* + * Init IPRT. + */ + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + /* + * Parse arguments. + */ + const char *pszFilename = NULL; + uint32_t cRounds = 4096; + uint32_t cPages = 128; + bool fLargePages = false; + + static const RTGETOPTDEF s_aOptions[] = + { + { "--rounds", 'r', RTGETOPT_REQ_UINT32 }, + { "--pages", 'p', RTGETOPT_REQ_UINT32 }, + { "--filename", 'f', RTGETOPT_REQ_STRING }, + { "--large-pages", 'l', RTGETOPT_REQ_NOTHING }, + { "--quiet", 'q', RTGETOPT_REQ_NOTHING }, + { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, + }; + + RTGETOPTSTATE State; + RTGetOptInit(&State, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0); + RTGETOPTUNION ValueUnion; + int chOpt; + while ((chOpt = RTGetOpt(&State, &ValueUnion)) != 0) + { + switch (chOpt) + { + case 'r': cRounds = ValueUnion.u32; break; + case 'p': cPages = ValueUnion.u32; break; + case 'f': pszFilename = ValueUnion.psz; break; + case 'l': fLargePages = true; break; + case 'q': g_cVerbosity = 0; break; + case 'v': g_cVerbosity += 1; break; + case 'h': + RTPrintf("usage: ntFlushVirtualMemory [-c <times>] [-p <pages>] [-l|--large-pages] [-f <filename>]\n" + "\n" + "Aims at testing memory mapped files on NT w/ NtFlushVirtualMemory / FlushViewOfFile.\n"); + return 0; + + default: + return RTGetOptPrintError(chOpt, &ValueUnion); + } + } + + /* + * Allocate buffers and initialize the original with page numbers. + * + * We keep a original copy that gets updated in parallel to the memory + * mapping, allowing for simple file initialization and memcpy checking. + * + * The second buffer is for reading the file from disk and check. + */ + size_t const cbBuf = cPages * X86_PAGE_SIZE; + size_t const cItemsPerPage = X86_PAGE_SIZE / sizeof(uint32_t); + uint32_t *pu32BufOrg = (uint32_t *)RTMemPageAllocZ(cbBuf); + uint32_t *pu32BufChk = (uint32_t *)RTMemPageAllocZ(cbBuf); + if (pu32BufOrg == NULL || pu32BufChk == NULL) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to allocate two %zu sized buffers!\n", cbBuf); + + for (uint32_t iPage = 0; iPage < cPages; iPage++) + pu32BufOrg[iPage * cItemsPerPage + cItemsPerPage - 1] = MAKE_PAGE_SIGNATURE(iPage); + + rc = CheckBuffer(pu32BufOrg, cbBuf, 0, 0); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Internal error: CheckBuffer failed on virgin buffer: %Rrc\n", rc); + + /* + * Open the file and write out the orignal one. + */ + RTFILE hFile; + if (!pszFilename) + { + char *pszBuf = (char *)alloca(RTPATH_MAX); + rc = RTFileOpenTemp(&hFile, pszBuf, RTPATH_MAX, RTFILE_O_READWRITE | RTFILE_O_CREATE | RTFILE_O_DENY_NONE); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create temporary file: %Rrc\n", rc); + pszFilename = pszBuf; + } + else + { + rc = RTFileOpen(&hFile, pszFilename, RTFILE_O_READWRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to open '%s': %Rrc\n", rc); + } + + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + + rc = RTFileWrite(hFile, pu32BufOrg, cbBuf, NULL); + if (RT_SUCCESS(rc)) + { + RTFileClose(hFile); + + /* + * Do the rounds. We count from 1 here to make verifying the previous round simpler. + */ + for (uint32_t iRound = 1; iRound <= cRounds; iRound++) + { + rc = MakeModifications(pszFilename, pu32BufOrg, cbBuf, iRound, + ((iRound >> 5) & 1) == 1, ((iRound >> 5) & 3) == 3, (iRound >> 3) & 31, fLargePages); + if (RT_SUCCESS(rc)) + { + rc = CheckBuffer(pu32BufOrg, cbBuf, iRound, 0); + if (RT_SUCCESS(rc)) + { + rc = CheckFile(pszFilename, pu32BufChk, pu32BufOrg, cbBuf, iRound); + if (RT_SUCCESS(rc)) + continue; + } + } + break; + } + } + else + { + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Error writing initial %zu bytes to '%s': %Rrc\n", cbBuf, rc); + RTFileClose(hFile); + } + RTFileDelete(pszFilename); + return rcExit; +} + diff --git a/src/VBox/ValidationKit/utils/nt/ntsetfreq.cpp b/src/VBox/ValidationKit/utils/nt/ntsetfreq.cpp new file mode 100644 index 00000000..c2a936dd --- /dev/null +++ b/src/VBox/ValidationKit/utils/nt/ntsetfreq.cpp @@ -0,0 +1,161 @@ +/* $Id: ntsetfreq.cpp $ */ +/** @file + * Set the NT timer frequency. + */ + +/* + * Copyright (C) 2007-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/nt/nt.h> + +#include <iprt/initterm.h> +#include <iprt/getopt.h> +#include <iprt/message.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/thread.h> +#include <iprt/errcore.h> + + +int main(int argc, char **argv) +{ + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + /* + * Parse arguments. + */ + bool fVerbose = true; + uint32_t u32NewRes = 0; + uint32_t cSecsSleep = UINT32_MAX; + + static const RTGETOPTDEF s_aOptions[] = + { + { "--resolution", 'r', RTGETOPT_REQ_UINT32 }, + { "--sleep", 's', RTGETOPT_REQ_UINT32 }, + { "--quiet", 'q', RTGETOPT_REQ_NOTHING }, + { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, + }; + + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0); + while ((rc = RTGetOpt(&GetState, &ValueUnion))) + { + switch (rc) + { + case 'r': + u32NewRes = ValueUnion.u32; + if (u32NewRes > 16*10000 /* 16 ms */ || u32NewRes < 1000 /* 100 microsec */) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, + "syntax error: the new timer resolution (%RU32) is out of range\n", + u32NewRes); + break; + + case 's': + cSecsSleep = ValueUnion.u32; + break; + + case 'q': + fVerbose = false; + break; + + case 'v': + fVerbose = true; + break; + + case 'h': + RTPrintf("Usage: ntsetfreq [-q|--quiet] [-v|--verbose] [-r|--resolution <100ns>] [-s|--sleep <1s>]\n"); + return RTEXITCODE_SUCCESS; + + default: + return RTGetOptPrintError(rc, &ValueUnion); + } + } + + + /* + * Query the current resolution. + */ + ULONG Cur = UINT32_MAX; + ULONG Min = UINT32_MAX; + ULONG Max = UINT32_MAX; + NTSTATUS rcNt = STATUS_SUCCESS; + if (fVerbose || !u32NewRes) + { + rcNt = NtQueryTimerResolution(&Min, &Max, &Cur); + if (NT_SUCCESS(rcNt)) + RTMsgInfo("cur: %u (%u.%02u Hz) min: %u (%u.%02u Hz) max: %u (%u.%02u Hz)\n", + Cur, 10000000 / Cur, (10000000 / (Cur * 100)) % 100, + Min, 10000000 / Min, (10000000 / (Min * 100)) % 100, + Max, 10000000 / Max, (10000000 / (Max * 100)) % 100); + else + RTMsgError("NTQueryTimerResolution failed with status %#x\n", rcNt); + } + + if (u32NewRes) + { + rcNt = NtSetTimerResolution(u32NewRes, TRUE, &Cur); + if (!NT_SUCCESS(rcNt)) + RTMsgError("NTSetTimerResolution(%RU32,,) failed with status %#x\n", u32NewRes, rcNt); + else if (fVerbose) + { + Cur = Min = Max = UINT32_MAX; + rcNt = NtQueryTimerResolution(&Min, &Max, &Cur); + if (NT_SUCCESS(rcNt)) + RTMsgInfo("new: %u (%u.%02u Hz) requested %RU32 (%u.%02u Hz)\n", + Cur, 10000000 / Cur, (10000000 / (Cur * 100)) % 100, + u32NewRes, 10000000 / u32NewRes, (10000000 / (u32NewRes * 100)) % 100); + else + RTMsgError("NTSetTimerResolution succeeded but the NTQueryTimerResolution call failed with status %#x (ignored)\n", + rcNt); + rcNt = STATUS_SUCCESS; + } + } + + if (u32NewRes && NT_SUCCESS(rcNt)) + { + if (cSecsSleep == UINT32_MAX) + for (;;) + RTThreadSleep(RT_INDEFINITE_WAIT); + else + while (cSecsSleep-- > 0) + RTThreadSleep(1000); + } + + return NT_SUCCESS(rcNt) ? 0 : 1; +} + diff --git a/src/VBox/ValidationKit/utils/nt/nttimesources.cpp b/src/VBox/ValidationKit/utils/nt/nttimesources.cpp new file mode 100644 index 00000000..dfa86b79 --- /dev/null +++ b/src/VBox/ValidationKit/utils/nt/nttimesources.cpp @@ -0,0 +1,246 @@ +/* $Id: nttimesources.cpp $ */ +/** @file + * Check the various time sources on Windows NT. + */ + +/* + * Copyright (C) 2009-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/win/windows.h> + +#include <iprt/asm.h> +#include <iprt/asm-amd64-x86.h> +#include <iprt/errcore.h> +#include <iprt/string.h> +#include <iprt/test.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct _MY_KSYSTEM_TIME +{ + ULONG LowPart; + LONG High1Time; + LONG High2Time; +} MY_KSYSTEM_TIME; + +typedef struct _MY_KUSER_SHARED_DATA +{ + ULONG TickCountLowDeprecated; + ULONG TickCountMultiplier; + volatile MY_KSYSTEM_TIME InterruptTime; + volatile MY_KSYSTEM_TIME SystemTime; + volatile MY_KSYSTEM_TIME TimeZoneBias; + /* The rest is not relevant. */ +} MY_KUSER_SHARED_DATA; + +/** The fixed pointer to the user shared data. */ +#define MY_USER_SHARED_DATA ((MY_KUSER_SHARED_DATA *)0x7ffe0000) + +/** Spins until GetTickCount() changes. */ +static void SpinUntilTick(void) +{ + /* spin till GetTickCount changes. */ + DWORD dwMsTick = GetTickCount(); + while (GetTickCount() == dwMsTick) + /* nothing */; +} + +/** Delay function that tries to return right after GetTickCount changed. */ +static void DelayMillies(DWORD dwMsStart, DWORD cMillies) +{ + /* Delay cMillies - 1. */ + Sleep(cMillies - 1); + while (GetTickCount() - dwMsStart < cMillies - 1U) + Sleep(1); + + SpinUntilTick(); +} + + +int main(int argc, char **argv) +{ + RT_NOREF1(argv); + + /* + * Init, create a test instance and "parse" arguments. + */ + RTTEST hTest; + int rc = RTTestInitAndCreate("nttimesources", &hTest); + if (rc) + return rc; + if (argc > 1) + { + RTTestFailed(hTest, "Syntax error! no arguments expected"); + return RTTestSummaryAndDestroy(hTest); + } + + /* + * Guess MHz using GetTickCount. + */ + RTTestSub(hTest, "Guess MHz"); + DWORD dwTickStart, dwTickEnd, cMsTicks; + uint64_t u64TscStart, u64TscEnd, cTscTicks; + + /* get a good start time. */ + SpinUntilTick(); + do + { + dwTickStart = GetTickCount(); + ASMCompilerBarrier(); + ASMSerializeInstruction(); + u64TscStart = ASMReadTSC(); + ASMCompilerBarrier(); + } while (GetTickCount() != dwTickStart); + + /* delay a good while. */ + DelayMillies(dwTickStart, 256); + + /* get a good end time. */ + do + { + dwTickEnd = GetTickCount(); + ASMCompilerBarrier(); + ASMSerializeInstruction(); + u64TscEnd = ASMReadTSC(); + ASMCompilerBarrier(); + } while (GetTickCount() != dwTickEnd); + cMsTicks = dwTickEnd - dwTickStart; + cTscTicks = u64TscEnd - u64TscStart; + + /* Calc an approximate TSC frequency: + cTscTicks / uTscHz = cMsTicks / 1000 + 1 / uTscHz = (cMsTicks / 1000) / cTscTicks + uTscHz = cTscTicks / (cMsTicks / 1000) */ + uint64_t u64TscHz = (long double)cTscTicks / ((long double)cMsTicks / 1000.0); + if ( u64TscHz > _1M*3 + && u64TscHz < _1T) + RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "u64TscHz=%'llu", u64TscHz); + else + { + RTTestFailed(hTest, "u64TscHz=%'llu - out of range", u64TscHz); + u64TscHz = 0; + } + + + /* + * Pit GetTickCount, InterruptTime, Performance Counters and TSC against each other. + */ + LARGE_INTEGER PrfHz; + LARGE_INTEGER PrfStart, PrfEnd, cPrfTicks; + LARGE_INTEGER IntStart, IntEnd, cIntTicks; + for (uint32_t i = 0; i < 7; i++) + { + RTTestSubF(hTest, "The whole bunch - pass #%u", i + 1); + + if (!QueryPerformanceFrequency(&PrfHz)) + { + RTTestFailed(hTest, "QueryPerformanceFrequency failed (%u)", GetLastError()); + return RTTestSummaryAndDestroy(hTest); + } + + /* get a good start time. */ + SpinUntilTick(); + do + { + IntStart.HighPart = MY_USER_SHARED_DATA->InterruptTime.High1Time; + IntStart.LowPart = MY_USER_SHARED_DATA->InterruptTime.LowPart; + dwTickStart = GetTickCount(); + if (!QueryPerformanceCounter(&PrfStart)) + { + RTTestFailed(hTest, "QueryPerformanceCounter failed (%u)", GetLastError()); + return RTTestSummaryAndDestroy(hTest); + } + ASMCompilerBarrier(); + ASMSerializeInstruction(); + u64TscStart = ASMReadTSC(); + ASMCompilerBarrier(); + } while ( MY_USER_SHARED_DATA->InterruptTime.High2Time != IntStart.HighPart + || MY_USER_SHARED_DATA->InterruptTime.LowPart != IntStart.LowPart + || GetTickCount() != dwTickStart); + + /* delay a good while. */ + DelayMillies(dwTickStart, 256); + + /* get a good end time. */ + do + { + IntEnd.HighPart = MY_USER_SHARED_DATA->InterruptTime.High1Time; + IntEnd.LowPart = MY_USER_SHARED_DATA->InterruptTime.LowPart; + dwTickEnd = GetTickCount(); + if (!QueryPerformanceCounter(&PrfEnd)) + { + RTTestFailed(hTest, "QueryPerformanceCounter failed (%u)", GetLastError()); + return RTTestSummaryAndDestroy(hTest); + } + ASMCompilerBarrier(); + ASMSerializeInstruction(); + u64TscEnd = ASMReadTSC(); + ASMCompilerBarrier(); + } while ( MY_USER_SHARED_DATA->InterruptTime.High2Time != IntEnd.HighPart + || MY_USER_SHARED_DATA->InterruptTime.LowPart != IntEnd.LowPart + || GetTickCount() != dwTickEnd); + + cMsTicks = dwTickEnd - dwTickStart; + cTscTicks = u64TscEnd - u64TscStart; + cIntTicks.QuadPart = IntEnd.QuadPart - IntStart.QuadPart; + cPrfTicks.QuadPart = PrfEnd.QuadPart - PrfStart.QuadPart; + + /* Recalc to micro seconds. */ + uint64_t u64MicroSecMs = (uint64_t)cMsTicks * 1000; + uint64_t u64MicroSecTsc = u64TscHz ? (long double)cTscTicks / (long double)u64TscHz * 1000000 : u64MicroSecMs; + uint64_t u64MicroSecPrf = (long double)cPrfTicks.QuadPart / (long double)PrfHz.QuadPart * 1000000; + uint64_t u64MicroSecInt = cIntTicks.QuadPart / 10; /* 100ns units*/ + + /* check how much they differ using the millisecond tick count as the standard candle. */ + RTTestPrintf(hTest, RTTESTLVL_ALWAYS, " %9llu / %7lld us - GetTickCount\n", u64MicroSecMs, 0); + + int64_t off = u64MicroSecTsc - u64MicroSecMs; + RTTestPrintf(hTest, RTTESTLVL_ALWAYS, " %9llu / %7lld us - TSC\n", u64MicroSecTsc, off); + RTTEST_CHECK(hTest, RT_ABS(off) < 50000 /*us*/); /* some extra uncertainty with TSC. */ + + off = u64MicroSecInt - u64MicroSecMs; + RTTestPrintf(hTest, RTTESTLVL_ALWAYS, " %9llu / %7lld us - InterruptTime\n", u64MicroSecInt, off); + RTTEST_CHECK(hTest, RT_ABS(off) < 25000 /*us*/); + + off = u64MicroSecPrf - u64MicroSecMs; + RTTestPrintf(hTest, RTTESTLVL_ALWAYS, " %9llu / %7lld us - QueryPerformanceCounter\n", u64MicroSecPrf, off); + RTTEST_CHECK(hTest, RT_ABS(off) < 25000 /*us*/); + } + + return RTTestSummaryAndDestroy(hTest); +} + |