summaryrefslogtreecommitdiffstats
path: root/src/VBox/VMM/VMMR3/PGMSharedPage.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/VMM/VMMR3/PGMSharedPage.cpp')
-rw-r--r--src/VBox/VMM/VMMR3/PGMSharedPage.cpp442
1 files changed, 442 insertions, 0 deletions
diff --git a/src/VBox/VMM/VMMR3/PGMSharedPage.cpp b/src/VBox/VMM/VMMR3/PGMSharedPage.cpp
new file mode 100644
index 00000000..dec43d5d
--- /dev/null
+++ b/src/VBox/VMM/VMMR3/PGMSharedPage.cpp
@@ -0,0 +1,442 @@
+/* $Id: PGMSharedPage.cpp $ */
+/** @file
+ * PGM - Page Manager and Monitor, Shared page handling
+ */
+
+/*
+ * Copyright (C) 2006-2020 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_PGM_SHARED
+#include <VBox/vmm/pgm.h>
+#include <VBox/vmm/stam.h>
+#include <VBox/vmm/uvm.h>
+#include "PGMInternal.h"
+#include <VBox/vmm/vm.h>
+#include <VBox/sup.h>
+#include <VBox/param.h>
+#include <VBox/err.h>
+#include <VBox/log.h>
+#include <VBox/VMMDev.h>
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+#include "PGMInline.h"
+
+
+#ifdef VBOX_WITH_PAGE_SHARING
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+# ifdef VBOX_STRICT
+/** Keep a copy of all registered shared modules for the .pgmcheckduppages debugger command. */
+static PGMMREGISTERSHAREDMODULEREQ g_apSharedModules[512] = {0};
+static unsigned g_cSharedModules = 0;
+# endif /* VBOX_STRICT */
+
+
+/**
+ * Registers a new shared module for the VM
+ *
+ * @returns VBox status code.
+ * @param pVM The cross context VM structure.
+ * @param enmGuestOS Guest OS type.
+ * @param pszModuleName Module name.
+ * @param pszVersion Module version.
+ * @param GCBaseAddr Module base address.
+ * @param cbModule Module size.
+ * @param cRegions Number of shared region descriptors.
+ * @param paRegions Shared region(s).
+ *
+ * @todo This should be a GMMR3 call. No need to involve GMM here.
+ */
+VMMR3DECL(int) PGMR3SharedModuleRegister(PVM pVM, VBOXOSFAMILY enmGuestOS, char *pszModuleName, char *pszVersion,
+ RTGCPTR GCBaseAddr, uint32_t cbModule, uint32_t cRegions,
+ VMMDEVSHAREDREGIONDESC const *paRegions)
+{
+ Log(("PGMR3SharedModuleRegister family=%d name=%s version=%s base=%RGv size=%x cRegions=%d\n",
+ enmGuestOS, pszModuleName, pszVersion, GCBaseAddr, cbModule, cRegions));
+
+ /*
+ * Sanity check.
+ */
+ AssertReturn(cRegions <= VMMDEVSHAREDREGIONDESC_MAX, VERR_INVALID_PARAMETER);
+ if (!pVM->pgm.s.fPageFusionAllowed)
+ return VERR_NOT_SUPPORTED;
+
+ /*
+ * Allocate and initialize a GMM request.
+ */
+ PGMMREGISTERSHAREDMODULEREQ pReq;
+ pReq = (PGMMREGISTERSHAREDMODULEREQ)RTMemAllocZ(RT_UOFFSETOF_DYN(GMMREGISTERSHAREDMODULEREQ, aRegions[cRegions]));
+ AssertReturn(pReq, VERR_NO_MEMORY);
+
+ pReq->enmGuestOS = enmGuestOS;
+ pReq->GCBaseAddr = GCBaseAddr;
+ pReq->cbModule = cbModule;
+ pReq->cRegions = cRegions;
+ for (uint32_t i = 0; i < cRegions; i++)
+ pReq->aRegions[i] = paRegions[i];
+
+ int rc = RTStrCopy(pReq->szName, sizeof(pReq->szName), pszModuleName);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTStrCopy(pReq->szVersion, sizeof(pReq->szVersion), pszVersion);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Issue the request. In strict builds, do some local tracking.
+ */
+ pgmR3PhysAssertSharedPageChecksums(pVM);
+ rc = GMMR3RegisterSharedModule(pVM, pReq);
+ if (RT_SUCCESS(rc))
+ rc = pReq->rc;
+ AssertMsg(rc == VINF_SUCCESS || rc == VINF_GMM_SHARED_MODULE_ALREADY_REGISTERED, ("%Rrc\n", rc));
+
+# ifdef VBOX_STRICT
+ if ( rc == VINF_SUCCESS
+ && g_cSharedModules < RT_ELEMENTS(g_apSharedModules))
+ {
+ unsigned i;
+ for (i = 0; i < RT_ELEMENTS(g_apSharedModules); i++)
+ if (g_apSharedModules[i] == NULL)
+ {
+
+ size_t const cbSharedModule = RT_UOFFSETOF_DYN(GMMREGISTERSHAREDMODULEREQ, aRegions[cRegions]);
+ g_apSharedModules[i] = (PGMMREGISTERSHAREDMODULEREQ)RTMemDup(pReq, cbSharedModule);
+ g_cSharedModules++;
+ break;
+ }
+ Assert(i < RT_ELEMENTS(g_apSharedModules));
+ }
+# endif /* VBOX_STRICT */
+ if (RT_SUCCESS(rc))
+ rc = VINF_SUCCESS;
+ }
+ }
+
+ RTMemFree(pReq);
+ return rc;
+}
+
+
+/**
+ * Unregisters a shared module for the VM
+ *
+ * @returns VBox status code.
+ * @param pVM The cross context VM structure.
+ * @param pszModuleName Module name.
+ * @param pszVersion Module version.
+ * @param GCBaseAddr Module base address.
+ * @param cbModule Module size.
+ *
+ * @todo This should be a GMMR3 call. No need to involve GMM here.
+ */
+VMMR3DECL(int) PGMR3SharedModuleUnregister(PVM pVM, char *pszModuleName, char *pszVersion, RTGCPTR GCBaseAddr, uint32_t cbModule)
+{
+ Log(("PGMR3SharedModuleUnregister name=%s version=%s base=%RGv size=%x\n", pszModuleName, pszVersion, GCBaseAddr, cbModule));
+
+ AssertMsgReturn(cbModule > 0 && cbModule < _1G, ("%u\n", cbModule), VERR_OUT_OF_RANGE);
+ if (!pVM->pgm.s.fPageFusionAllowed)
+ return VERR_NOT_SUPPORTED;
+
+ /*
+ * Forward the request to GMM (ring-0).
+ */
+ PGMMUNREGISTERSHAREDMODULEREQ pReq = (PGMMUNREGISTERSHAREDMODULEREQ)RTMemAlloc(sizeof(*pReq));
+ AssertReturn(pReq, VERR_NO_MEMORY);
+
+ pReq->GCBaseAddr = GCBaseAddr;
+ pReq->u32Alignment = 0;
+ pReq->cbModule = cbModule;
+
+ int rc = RTStrCopy(pReq->szName, sizeof(pReq->szName), pszModuleName);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTStrCopy(pReq->szVersion, sizeof(pReq->szVersion), pszVersion);
+ if (RT_SUCCESS(rc))
+ {
+ pgmR3PhysAssertSharedPageChecksums(pVM);
+ rc = GMMR3UnregisterSharedModule(pVM, pReq);
+ pgmR3PhysAssertSharedPageChecksums(pVM);
+
+# ifdef VBOX_STRICT
+ /*
+ * Update our local tracking.
+ */
+ for (unsigned i = 0; i < g_cSharedModules; i++)
+ {
+ if ( g_apSharedModules[i]
+ && !strcmp(g_apSharedModules[i]->szName, pszModuleName)
+ && !strcmp(g_apSharedModules[i]->szVersion, pszVersion))
+ {
+ RTMemFree(g_apSharedModules[i]);
+ g_apSharedModules[i] = NULL;
+ g_cSharedModules--;
+ break;
+ }
+ }
+# endif /* VBOX_STRICT */
+ }
+ }
+
+ RTMemFree(pReq);
+ return rc;
+}
+
+
+/**
+ * Rendezvous callback that will be called once.
+ *
+ * @returns VBox strict status code.
+ * @param pVM The cross context VM structure.
+ * @param pVCpu The cross context virtual CPU structure of the calling EMT.
+ * @param pvUser Pointer to a VMCPUID with the requester's ID.
+ */
+static DECLCALLBACK(VBOXSTRICTRC) pgmR3SharedModuleRegRendezvous(PVM pVM, PVMCPU pVCpu, void *pvUser)
+{
+ VMCPUID idCpu = *(VMCPUID *)pvUser;
+
+ /* Execute on the VCPU that issued the original request to make sure we're in the right cr3 context. */
+ if (pVCpu->idCpu != idCpu)
+ {
+ Assert(pVM->cCpus > 1);
+ return VINF_SUCCESS;
+ }
+
+
+ /* Flush all pending handy page operations before changing any shared page assignments. */
+ int rc = PGMR3PhysAllocateHandyPages(pVM);
+ AssertRC(rc);
+
+ /*
+ * Lock it here as we can't deal with busy locks in this ring-0 path.
+ */
+ LogFlow(("pgmR3SharedModuleRegRendezvous: start (%d)\n", pVM->pgm.s.cSharedPages));
+
+ pgmLock(pVM);
+ pgmR3PhysAssertSharedPageChecksums(pVM);
+ rc = GMMR3CheckSharedModules(pVM);
+ pgmR3PhysAssertSharedPageChecksums(pVM);
+ pgmUnlock(pVM);
+ AssertLogRelRC(rc);
+
+ LogFlow(("pgmR3SharedModuleRegRendezvous: done (%d)\n", pVM->pgm.s.cSharedPages));
+ return rc;
+}
+
+/**
+ * Shared module check helper (called on the way out).
+ *
+ * @param pVM The cross context VM structure.
+ * @param idCpu VCPU id.
+ */
+static DECLCALLBACK(void) pgmR3CheckSharedModulesHelper(PVM pVM, VMCPUID idCpu)
+{
+ /* We must stall other VCPUs as we'd otherwise have to send IPI flush commands for every single change we make. */
+ STAM_REL_PROFILE_START(&pVM->pgm.s.StatShModCheck, a);
+ int rc = VMMR3EmtRendezvous(pVM, VMMEMTRENDEZVOUS_FLAGS_TYPE_ALL_AT_ONCE, pgmR3SharedModuleRegRendezvous, &idCpu);
+ AssertRCSuccess(rc);
+ STAM_REL_PROFILE_STOP(&pVM->pgm.s.StatShModCheck, a);
+}
+
+
+/**
+ * Check all registered modules for changes.
+ *
+ * @returns VBox status code.
+ * @param pVM The cross context VM structure.
+ */
+VMMR3DECL(int) PGMR3SharedModuleCheckAll(PVM pVM)
+{
+ if (!pVM->pgm.s.fPageFusionAllowed)
+ return VERR_NOT_SUPPORTED;
+
+ /* Queue the actual registration as we are under the IOM lock right now. Perform this operation on the way out. */
+ return VMR3ReqCallNoWait(pVM, VMCPUID_ANY_QUEUE, (PFNRT)pgmR3CheckSharedModulesHelper, 2, pVM, VMMGetCpuId(pVM));
+}
+
+
+# ifdef DEBUG
+/**
+ * Query the state of a page in a shared module
+ *
+ * @returns VBox status code.
+ * @param pVM The cross context VM structure.
+ * @param GCPtrPage Page address.
+ * @param pfShared Shared status (out).
+ * @param pfPageFlags Page flags (out).
+ */
+VMMR3DECL(int) PGMR3SharedModuleGetPageState(PVM pVM, RTGCPTR GCPtrPage, bool *pfShared, uint64_t *pfPageFlags)
+{
+ /* Debug only API for the page fusion testcase. */
+ RTGCPHYS GCPhys;
+ uint64_t fFlags;
+
+ pgmLock(pVM);
+
+ int rc = PGMGstGetPage(VMMGetCpu(pVM), GCPtrPage, &fFlags, &GCPhys);
+ switch (rc)
+ {
+ case VINF_SUCCESS:
+ {
+ PPGMPAGE pPage = pgmPhysGetPage(pVM, GCPhys);
+ if (pPage)
+ {
+ *pfShared = PGM_PAGE_IS_SHARED(pPage);
+ *pfPageFlags = fFlags;
+ }
+ else
+ rc = VERR_PGM_INVALID_GC_PHYSICAL_ADDRESS;
+ break;
+ }
+
+ case VERR_PAGE_NOT_PRESENT:
+ case VERR_PAGE_TABLE_NOT_PRESENT:
+ case VERR_PAGE_MAP_LEVEL4_NOT_PRESENT:
+ case VERR_PAGE_DIRECTORY_PTR_NOT_PRESENT:
+ *pfShared = false;
+ *pfPageFlags = 0;
+ rc = VINF_SUCCESS;
+ break;
+
+ default:
+ break;
+ }
+
+ pgmUnlock(pVM);
+ return rc;
+}
+# endif /* DEBUG */
+
+# ifdef VBOX_STRICT
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The '.pgmcheckduppages' command.}
+ */
+DECLCALLBACK(int) pgmR3CmdCheckDuplicatePages(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ unsigned cBallooned = 0;
+ unsigned cShared = 0;
+ unsigned cZero = 0;
+ unsigned cUnique = 0;
+ unsigned cDuplicate = 0;
+ unsigned cAllocZero = 0;
+ unsigned cPages = 0;
+ NOREF(pCmd); NOREF(paArgs); NOREF(cArgs);
+ PVM pVM = pUVM->pVM;
+ VM_ASSERT_VALID_EXT_RETURN(pVM, VERR_INVALID_VM_HANDLE);
+
+ pgmLock(pVM);
+
+ for (PPGMRAMRANGE pRam = pVM->pgm.s.pRamRangesXR3; pRam; pRam = pRam->pNextR3)
+ {
+ PPGMPAGE pPage = &pRam->aPages[0];
+ RTGCPHYS GCPhys = pRam->GCPhys;
+ uint32_t cLeft = pRam->cb >> PAGE_SHIFT;
+ while (cLeft-- > 0)
+ {
+ if (PGM_PAGE_GET_TYPE(pPage) == PGMPAGETYPE_RAM)
+ {
+ switch (PGM_PAGE_GET_STATE(pPage))
+ {
+ case PGM_PAGE_STATE_ZERO:
+ cZero++;
+ break;
+
+ case PGM_PAGE_STATE_BALLOONED:
+ cBallooned++;
+ break;
+
+ case PGM_PAGE_STATE_SHARED:
+ cShared++;
+ break;
+
+ case PGM_PAGE_STATE_ALLOCATED:
+ case PGM_PAGE_STATE_WRITE_MONITORED:
+ {
+ /* Check if the page was allocated, but completely zero. */
+ PGMPAGEMAPLOCK PgMpLck;
+ const void *pvPage;
+ int rc = pgmPhysGCPhys2CCPtrInternalReadOnly(pVM, pPage, GCPhys, &pvPage, &PgMpLck);
+ if ( RT_SUCCESS(rc)
+ && ASMMemIsZeroPage(pvPage))
+ cAllocZero++;
+ else if (GMMR3IsDuplicatePage(pVM, PGM_PAGE_GET_PAGEID(pPage)))
+ cDuplicate++;
+ else
+ cUnique++;
+ if (RT_SUCCESS(rc))
+ pgmPhysReleaseInternalPageMappingLock(pVM, &PgMpLck);
+ break;
+ }
+
+ default:
+ AssertFailed();
+ break;
+ }
+ }
+
+ /* next */
+ pPage++;
+ GCPhys += PAGE_SIZE;
+ cPages++;
+ /* Give some feedback for every processed megabyte. */
+ if ((cPages & 0x7f) == 0)
+ pCmdHlp->pfnPrintf(pCmdHlp, NULL, ".");
+ }
+ }
+ pgmUnlock(pVM);
+
+ pCmdHlp->pfnPrintf(pCmdHlp, NULL, "\nNumber of zero pages %08x (%d MB)\n", cZero, cZero / 256);
+ pCmdHlp->pfnPrintf(pCmdHlp, NULL, "Number of alloczero pages %08x (%d MB)\n", cAllocZero, cAllocZero / 256);
+ pCmdHlp->pfnPrintf(pCmdHlp, NULL, "Number of ballooned pages %08x (%d MB)\n", cBallooned, cBallooned / 256);
+ pCmdHlp->pfnPrintf(pCmdHlp, NULL, "Number of shared pages %08x (%d MB)\n", cShared, cShared / 256);
+ pCmdHlp->pfnPrintf(pCmdHlp, NULL, "Number of unique pages %08x (%d MB)\n", cUnique, cUnique / 256);
+ pCmdHlp->pfnPrintf(pCmdHlp, NULL, "Number of duplicate pages %08x (%d MB)\n", cDuplicate, cDuplicate / 256);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The '.pgmsharedmodules' command.}
+ */
+DECLCALLBACK(int) pgmR3CmdShowSharedModules(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ NOREF(pCmd); NOREF(paArgs); NOREF(cArgs);
+ PVM pVM = pUVM->pVM;
+ VM_ASSERT_VALID_EXT_RETURN(pVM, VERR_INVALID_VM_HANDLE);
+
+ pgmLock(pVM);
+ for (unsigned i = 0; i < RT_ELEMENTS(g_apSharedModules); i++)
+ {
+ if (g_apSharedModules[i])
+ {
+ pCmdHlp->pfnPrintf(pCmdHlp, NULL, "Shared module %s (%s):\n", g_apSharedModules[i]->szName, g_apSharedModules[i]->szVersion);
+ for (unsigned j = 0; j < g_apSharedModules[i]->cRegions; j++)
+ pCmdHlp->pfnPrintf(pCmdHlp, NULL, "--- Region %d: base %RGv size %x\n", j, g_apSharedModules[i]->aRegions[j].GCRegionAddr, g_apSharedModules[i]->aRegions[j].cbRegion);
+ }
+ }
+ pgmUnlock(pVM);
+
+ return VINF_SUCCESS;
+}
+
+# endif /* VBOX_STRICT*/
+#endif /* VBOX_WITH_PAGE_SHARING */