Index: trunk/src/VBox/VMM/VMMAll/PGMAllBth.h
===================================================================
--- trunk/src/VBox/VMM/VMMAll/PGMAllBth.h	(revision 104839)
+++ trunk/src/VBox/VMM/VMMAll/PGMAllBth.h	(revision 104840)
@@ -1741,8 +1741,10 @@
     LogFlow(("SyncPageWorkerTrackDeref(%d,%d): Damn HCPhys=%RHp pShwPage->idx=%#x!!!\n",
              PGM_SHW_TYPE, PGM_GST_TYPE, HCPhys, pShwPage->idx));
-    for (PPGMRAMRANGE pRam = pVM->pgm.s.CTX_SUFF(pRamRangesX);
-         pRam;
-         pRam = pRam->CTX_SUFF(pNext))
-    {
+    uint32_t const idRamRangeMax = RT_MIN(pVM->pgm.s.idRamRangeMax, RT_ELEMENTS(pVM->pgm.s.apRamRanges) - 1U);
+    Assert(pVM->pgm.s.apRamRanges[0] == NULL);
+    for (uint32_t idx = 1; idx <= idRamRangeMax; idx++)
+    {
+        PPGMRAMRANGE const pRam = pVM->CTX_EXPR(pgm, pgmr0, pgm).s.apRamRanges[idx];
+        AssertContinue(pRam);
         unsigned iPage = pRam->cb >> GUEST_PAGE_SHIFT;
         while (iPage-- > 0)
@@ -3675,9 +3677,9 @@
                   GCPtrPage, PdeSrc.u & X86_PDE_P, !!(PdeSrc.u & X86_PDE_RW), !!(PdeSrc.u & X86_PDE_US), (uint64_t)PdeSrc.u, GCPtr,
                   GCPhys, PdeDst.u & PGM_PDFLAGS_TRACK_DIRTY ? " Track-Dirty" : ""));
-            PPGMRAMRANGE    pRam   = pgmPhysGetRangeAtOrAbove(pVM, GCPhys);
             unsigned        iPTDst = 0;
             while (     iPTDst < RT_ELEMENTS(pPTDst->a)
                    &&   !VM_FF_IS_SET(pVM, VM_FF_PGM_NO_MEMORY))
             {
+                PPGMRAMRANGE const pRam   = pgmPhysGetRangeAtOrAbove(pVM, GCPhys);
                 if (pRam && GCPhys >= pRam->GCPhys)
                 {
@@ -3755,8 +3757,4 @@
                     } while (   iPTDst < RT_ELEMENTS(pPTDst->a)
                              && GCPhys <= pRam->GCPhysLast);
-
-                    /* Advance ram range list. */
-                    while (pRam && GCPhys > pRam->GCPhysLast)
-                        pRam = pRam->CTX_SUFF(pNext);
                 }
                 else if (pRam)
Index: trunk/src/VBox/VMM/VMMAll/PGMAllHandler.cpp
===================================================================
--- trunk/src/VBox/VMM/VMMAll/PGMAllHandler.cpp	(revision 104839)
+++ trunk/src/VBox/VMM/VMMAll/PGMAllHandler.cpp	(revision 104840)
@@ -1648,33 +1648,45 @@
     /* Only works if the handle is in the handle table! */
     AssertReturn(hMmio2 != 0, NULL);
-    hMmio2--;
+    uint32_t const cMmio2Ranges = RT_MIN(pVM->pgm.s.cMmio2Ranges, RT_ELEMENTS(pVM->pgm.s.aMmio2Ranges));
+    AssertReturn(hMmio2 <= cMmio2Ranges, NULL);
+    AssertCompile(RT_ELEMENTS(pVM->pgm.s.apMmio2RamRanges)    == RT_ELEMENTS(pVM->pgm.s.aMmio2Ranges));
+#ifdef IN_RING0
+    AssertCompile(RT_ELEMENTS(pVM->pgmr0.s.apMmio2RamRanges)  == RT_ELEMENTS(pVM->pgm.s.aMmio2Ranges));
+    AssertCompile(RT_ELEMENTS(pVM->pgmr0.s.acMmio2RangePages) == RT_ELEMENTS(pVM->pgm.s.aMmio2Ranges));
+#endif
+    uint32_t const idxFirst = hMmio2 - 1U;
 
     /* Must check the first one for PGMREGMMIO2RANGE_F_FIRST_CHUNK. */
-    AssertReturn(hMmio2 < RT_ELEMENTS(pVM->pgm.s.apMmio2RangesR3), NULL);
-    PPGMREGMMIO2RANGE pCur = pVM->pgm.s.CTX_SUFF(apMmio2Ranges)[hMmio2];
-    AssertReturn(pCur, NULL);
-    AssertReturn(pCur->fFlags & PGMREGMMIO2RANGE_F_FIRST_CHUNK, NULL);
+    AssertReturn(pVM->pgm.s.aMmio2Ranges[idxFirst].fFlags & PGMREGMMIO2RANGE_F_FIRST_CHUNK, NULL);
+#ifdef IN_RING0
+    AssertReturn(pVM->pgmr0.s.ahMmio2MapObjs[idxFirst] != NIL_RTR0MEMOBJ, NULL); /* Only the first chunk has a backing object. */
+#endif
 
     /* Loop thru the sub-ranges till we find the one covering offMmio2. */
-    for (;;)
+    for (uint32_t idx = idxFirst; idx < cMmio2Ranges; idx++)
     {
 #ifdef IN_RING3
-        AssertReturn(pCur->pDevInsR3 == pDevIns, NULL);
+        AssertReturn(pVM->pgm.s.aMmio2Ranges[idx].pDevInsR3 == pDevIns, NULL);
 #else
-        AssertReturn(pCur->pDevInsR3 == pDevIns->pDevInsForR3, NULL);
+        AssertReturn(pVM->pgm.s.aMmio2Ranges[idx].pDevInsR3 == pDevIns->pDevInsForR3, NULL);
 #endif
 
         /* Does it match the offset? */
-        if (offMmio2Page < pCur->cbReal)
-            return &pCur->RamRange.aPages[offMmio2Page >> GUEST_PAGE_SHIFT];
-
-        /* Advance if we can. */
-        AssertReturn(!(pCur->fFlags & PGMREGMMIO2RANGE_F_LAST_CHUNK), NULL);
-        offMmio2Page -= pCur->cbReal;
-        hMmio2++;
-        AssertReturn(hMmio2 < RT_ELEMENTS(pVM->pgm.s.apMmio2RangesR3), NULL);
-        pCur = pVM->pgm.s.CTX_SUFF(apMmio2Ranges)[hMmio2];
-        AssertReturn(pCur, NULL);
-    }
+        PPGMRAMRANGE const pRamRange = pVM->CTX_EXPR(pgm, pgmr0, pgm).s.apMmio2RamRanges[idx];
+        AssertReturn(pRamRange, NULL);
+#ifdef IN_RING3
+        RTGCPHYS const     cbRange   = RT_MIN(pRamRange->cb, pVM->pgm.s.aMmio2Ranges[idx].cbReal);
+#else
+        RTGCPHYS const     cbRange   = RT_MIN(pRamRange->cb, (RTGCPHYS)pVM->pgmr0.s.acMmio2RangePages[idx] << GUEST_PAGE_SHIFT);
+#endif
+        if (offMmio2Page < cbRange)
+            return &pRamRange->aPages[offMmio2Page >> GUEST_PAGE_SHIFT];
+
+        /* Advance. */
+        AssertReturn(!(pVM->pgm.s.aMmio2Ranges[idx].fFlags & PGMREGMMIO2RANGE_F_LAST_CHUNK), NULL);
+        offMmio2Page -= cbRange;
+    }
+    AssertFailed();
+    return NULL;
 }
 
@@ -2082,5 +2094,4 @@
 VMMDECL(unsigned) PGMAssertHandlerAndFlagsInSync(PVMCC pVM)
 {
-    PPGM        pPGM = &pVM->pgm.s;
     PGMAHAFIS   State;
     State.GCPhys  = 0;
@@ -2094,6 +2105,12 @@
      */
     PPGMPHYSHANDLERTREE const pPhysHandlerTree = pVM->VMCC_CTX(pgm).s.pPhysHandlerTree;
-    for (PPGMRAMRANGE pRam = pPGM->CTX_SUFF(pRamRangesX); pRam; pRam = pRam->CTX_SUFF(pNext))
-    {
+    uint32_t const            cLookupEntries   = RT_MIN(pVM->pgm.s.RamRangeUnion.cLookupEntries,
+                                                        RT_ELEMENTS(pVM->pgm.s.aRamRangeLookup));
+    for (uint32_t idxLookup = 0; idxLookup < cLookupEntries; idxLookup++)
+    {
+        uint32_t const idRamRange = PGMRAMRANGELOOKUPENTRY_GET_ID(pVM->pgm.s.aRamRangeLookup[idxLookup]);
+        AssertContinue(idRamRange < RT_ELEMENTS(pVM->pgm.s.apRamRanges));
+        PPGMRAMRANGE const pRam = pVM->CTX_EXPR(pgm, pgmr0, pgm).s.apRamRanges[idRamRange];
+        AssertContinue(pRam);
         const uint32_t cPages = pRam->cb >> GUEST_PAGE_SHIFT;
         for (uint32_t iPage = 0; iPage < cPages; iPage++)
Index: trunk/src/VBox/VMM/VMMAll/PGMAllPhys.cpp
===================================================================
--- trunk/src/VBox/VMM/VMMAll/PGMAllPhys.cpp	(revision 104839)
+++ trunk/src/VBox/VMM/VMMAll/PGMAllPhys.cpp	(revision 104840)
@@ -49,4 +49,7 @@
 #ifdef IN_RING3
 # include <iprt/thread.h>
+#elif defined(IN_RING0)
+# include <iprt/mem.h>
+# include <iprt/memobj.h>
 #endif
 
@@ -181,14 +184,8 @@
 
 
-/**
- * Looks up a ROM range by its PGMROMRANGE::GCPhys value.
- */
-DECLINLINE(PPGMROMRANGE) pgmPhysRomLookupByBase(PVMCC pVM, RTGCPHYS GCPhys)
-{
-    for (PPGMROMRANGE pRom = pVM->pgm.s.CTX_SUFF(pRomRanges); pRom; pRom = pRom->CTX_SUFF(pNext))
-        if (pRom->GCPhys == GCPhys)
-            return pRom;
-    return NULL;
-}
+
+/*********************************************************************************************************************************
+*   Access Handlers for ROM and MMIO2                                                                                            *
+*********************************************************************************************************************************/
 
 #ifndef IN_RING3
@@ -204,13 +201,18 @@
 
 {
-    PPGMROMRANGE const  pRom  = pgmPhysRomLookupByBase(pVM, uUser);
+    AssertReturn(uUser < RT_ELEMENTS(pVM->pgmr0.s.apRomRanges), VINF_EM_RAW_EMULATE_INSTR);
+    PPGMROMRANGE const  pRom  = pVM->pgmr0.s.apRomRanges[uUser];
     AssertReturn(pRom, VINF_EM_RAW_EMULATE_INSTR);
+
     uint32_t const      iPage = (GCPhysFault - pRom->GCPhys) >> GUEST_PAGE_SHIFT;
-    int                 rc;
+    AssertReturn(iPage < (pRom->cb >> GUEST_PAGE_SHIFT), VERR_INTERNAL_ERROR_3);
+#ifdef IN_RING0
+    AssertReturn(iPage < pVM->pgmr0.s.acRomRangePages[uUser], VERR_INTERNAL_ERROR_2);
+#endif
+
     RT_NOREF(uErrorCode, pvFault);
-
     Assert(uErrorCode & X86_TRAP_PF_RW); /* This shall not be used for read access! */
 
-    Assert(iPage < (pRom->cb >> GUEST_PAGE_SHIFT));
+    int rc;
     switch (pRom->aPages[iPage].enmProt)
     {
@@ -244,5 +246,5 @@
         case PGMROMPROT_READ_RAM_WRITE_RAM:
             pRom->aPages[iPage].LiveSave.fWrittenTo = true;
-            rc = PGMHandlerPhysicalPageTempOff(pVM, pRom->GCPhys, GCPhysFault & X86_PTE_PG_MASK);
+            rc = PGMHandlerPhysicalPageTempOff(pVM, pRom->GCPhys, GCPhysFault & ~(RTGCPHYS)GUEST_PAGE_OFFSET_MASK);
             AssertRC(rc);
             break; /** @todo Must edit the shadow PT and restart the instruction, not use the interpreter! */
@@ -276,8 +278,13 @@
                        PGMACCESSTYPE enmAccessType, PGMACCESSORIGIN enmOrigin, uint64_t uUser)
 {
-    PPGMROMRANGE const  pRom  = pgmPhysRomLookupByBase(pVM, uUser);
+    AssertReturn(uUser < RT_ELEMENTS(pVM->CTX_EXPR(pgm, pgmr0, pgm).s.apRomRanges), VERR_INTERNAL_ERROR_3);
+    PPGMROMRANGE const  pRom  = pVM->CTX_EXPR(pgm, pgmr0, pgm).s.apRomRanges[uUser];
     AssertReturn(pRom, VERR_INTERNAL_ERROR_3);
+
     uint32_t const      iPage    = (GCPhys - pRom->GCPhys) >> GUEST_PAGE_SHIFT;
-    Assert(iPage < (pRom->cb >> GUEST_PAGE_SHIFT));
+    AssertReturn(iPage < (pRom->cb >> GUEST_PAGE_SHIFT), VERR_INTERNAL_ERROR_2);
+#ifdef IN_RING0
+    AssertReturn(iPage < pVM->pgmr0.s.acRomRangePages[uUser], VERR_INTERNAL_ERROR_2);
+#endif
     PPGMROMPAGE const   pRomPage = &pRom->aPages[iPage];
 
@@ -352,5 +359,5 @@
 #endif
                 {
-                    rc = pgmPhysPageMakeWritableAndMap(pVM, pShadowPage, GCPhys & X86_PTE_PG_MASK, &pvDstPage);
+                    rc = pgmPhysPageMakeWritableAndMap(pVM, pShadowPage, GCPhys & ~(RTGCPHYS)GUEST_PAGE_OFFSET_MASK, &pvDstPage);
                     if (RT_SUCCESS(rc))
                         pvDstPage = (uint8_t *)pvDstPage + (GCPhys & GUEST_PAGE_OFFSET_MASK);
@@ -389,7 +396,7 @@
      * Get the MMIO2 range.
      */
-    AssertReturn(hMmio2 < RT_ELEMENTS(pVM->pgm.s.apMmio2RangesR3), VERR_INTERNAL_ERROR_3);
+    AssertReturn(hMmio2 < RT_ELEMENTS(pVM->pgm.s.aMmio2Ranges), VERR_INTERNAL_ERROR_3);
     AssertReturn(hMmio2 != 0, VERR_INTERNAL_ERROR_3);
-    PPGMREGMMIO2RANGE pMmio2 = pVM->pgm.s.CTX_SUFF(apMmio2Ranges)[hMmio2 - 1];
+    PPGMREGMMIO2RANGE const pMmio2 = &pVM->pgm.s.aMmio2Ranges[hMmio2 - 1];
     Assert(pMmio2->idMmio2 == hMmio2);
     AssertReturn((pMmio2->fFlags & PGMREGMMIO2RANGE_F_TRACK_DIRTY_PAGES) == PGMREGMMIO2RANGE_F_TRACK_DIRTY_PAGES,
@@ -413,5 +420,5 @@
      * Disable the handler for this page.
      */
-    int rc = PGMHandlerPhysicalPageTempOff(pVM, pMmio2->RamRange.GCPhys, GCPhys & X86_PTE_PG_MASK);
+    int rc = PGMHandlerPhysicalPageTempOff(pVM, pMmio2->GCPhys, GCPhys & ~(RTGCPHYS)GUEST_PAGE_OFFSET_MASK);
     AssertRC(rc);
 #ifndef IN_RING3
@@ -474,4 +481,104 @@
 
 
+
+/*********************************************************************************************************************************
+*   RAM Ranges                                                                                                                   *
+*********************************************************************************************************************************/
+
+#ifdef VBOX_STRICT
+/**
+ * Asserts that the RAM range structures are sane.
+ */
+DECLHIDDEN(bool) pgmPhysAssertRamRangesLocked(PVMCC pVM, bool fInUpdate, bool fRamRelaxed)
+{
+    bool fRet = true;
+
+    /*
+     * Check the generation ID.  This is stable since we own the PGM lock.
+     */
+    AssertStmt((pVM->pgm.s.RamRangeUnion.idGeneration & 1U) == (unsigned)fInUpdate, fRet = false);
+
+    /*
+     * Check the entry count and max ID.
+     */
+    uint32_t const idRamRangeMax  = pVM->pgm.s.idRamRangeMax;
+    /* Since this is set to the highest ID, it cannot be the same as the table size. */
+    AssertStmt(idRamRangeMax  < RT_ELEMENTS(pVM->pgm.s.apRamRanges), fRet = false);
+
+    /* Because ID=0 is reserved, it's one less than the table size and at most the
+       same as the max ID. */
+    uint32_t const cLookupEntries = pVM->pgm.s.RamRangeUnion.cLookupEntries;
+    AssertStmt(cLookupEntries < RT_ELEMENTS(pVM->pgm.s.aRamRangeLookup), fRet = false);
+    AssertStmt(cLookupEntries <= idRamRangeMax, fRet = false);
+
+    /*
+     * Check the pointer table(s).
+     */
+    /* The first entry shall be empty. */
+    AssertStmt(pVM->pgm.s.apRamRanges[0] == NULL, fRet = false);
+# ifdef IN_RING0
+    AssertStmt(pVM->pgmr0.s.apRamRanges[0] == NULL, fRet = false);
+    AssertStmt(pVM->pgmr0.s.acRamRangePages[0] == 0, fRet = false);
+# endif
+
+    uint32_t cMappedRanges = 0;
+    for (uint32_t idRamRange = 1; idRamRange <= idRamRangeMax; idRamRange++)
+    {
+# ifdef IN_RING0
+        PPGMRAMRANGE const pRamRange = pVM->pgmr0.s.apRamRanges[idRamRange];
+        AssertContinueStmt(pRamRange, fRet = false);
+        AssertStmt(pVM->pgm.s.apRamRanges[idRamRange] != NIL_RTR3PTR, fRet = false);
+        AssertStmt(   (pRamRange->cb >> GUEST_PAGE_SHIFT) == pVM->pgmr0.s.acRamRangePages[idRamRange]
+                   || (   (pRamRange->cb >> GUEST_PAGE_SHIFT) < pVM->pgmr0.s.acRamRangePages[idRamRange]
+                       && !(pRamRange->fFlags & PGM_RAM_RANGE_FLAGS_AD_HOC_MMIO_EX)),
+                   fRet = false);
+# else
+        PPGMRAMRANGE const pRamRange = pVM->pgm.s.apRamRanges[idRamRange];
+        AssertContinueStmt(pRamRange, fRet = false);
+# endif
+        AssertStmt(pRamRange->idRange == idRamRange, fRet = false);
+        if (pRamRange->GCPhys != NIL_RTGCPHYS)
+        {
+            cMappedRanges++;
+            AssertStmt((pRamRange->GCPhys & GUEST_PAGE_OFFSET_MASK) == 0, fRet = false);
+            AssertStmt((pRamRange->GCPhysLast & GUEST_PAGE_OFFSET_MASK) == GUEST_PAGE_OFFSET_MASK, fRet = false);
+            AssertStmt(pRamRange->GCPhysLast > pRamRange->GCPhys, fRet = false);
+            AssertStmt(pRamRange->GCPhysLast - pRamRange->GCPhys + 1U == pRamRange->cb, fRet = false);
+        }
+        else
+        {
+            AssertStmt(pRamRange->GCPhysLast == NIL_RTGCPHYS, fRet = false);
+            AssertStmt(PGM_RAM_RANGE_IS_AD_HOC(pRamRange) || fRamRelaxed, fRet = false);
+        }
+    }
+
+    /*
+     * Check that the lookup table is sorted and contains the right information.
+     */
+    AssertMsgStmt(cMappedRanges == cLookupEntries,
+                  ("cMappedRanges=%#x cLookupEntries=%#x\n", cMappedRanges, cLookupEntries),
+                  fRet = false);
+    RTGCPHYS GCPhysPrev = ~(RTGCPHYS)0;
+    for (uint32_t idxLookup = 0; idxLookup < cLookupEntries; idxLookup++)
+    {
+        uint32_t const idRamRange = PGMRAMRANGELOOKUPENTRY_GET_ID(pVM->pgm.s.aRamRangeLookup[idxLookup]);
+        AssertContinueStmt(idRamRange > 0 && idRamRange <= idRamRangeMax, fRet = false);
+        PPGMRAMRANGE const pRamRange = pVM->CTX_EXPR(pgm,pgmr0,pgmrc).s.apRamRanges[idRamRange];
+        AssertContinueStmt(pRamRange, fRet = false);
+
+        AssertStmt(pRamRange->idRange == idRamRange, fRet = false);
+        AssertStmt(pRamRange->GCPhys == PGMRAMRANGELOOKUPENTRY_GET_FIRST(pVM->pgm.s.aRamRangeLookup[idxLookup]),
+                   fRet = false);
+        AssertStmt(pRamRange->GCPhysLast == pVM->pgm.s.aRamRangeLookup[idxLookup].GCPhysLast, fRet = false);
+
+        AssertStmt(pRamRange->GCPhys >= GCPhysPrev + 1U, fRet = false);
+        GCPhysPrev = pRamRange->GCPhysLast;
+    }
+
+    return fRet;
+}
+#endif /* VBOX_STRICT */
+
+
 /**
  * Invalidates the RAM range TLBs.
@@ -503,22 +610,42 @@
  *
  * @copydoc pgmPhysGetRange
- */
-PPGMRAMRANGE pgmPhysGetRangeSlow(PVM pVM, RTGCPHYS GCPhys)
+ * @note    Caller owns the PGM lock.
+ */
+PPGMRAMRANGE pgmPhysGetRangeSlow(PVMCC pVM, RTGCPHYS GCPhys)
 {
     STAM_COUNTER_INC(&pVM->pgm.s.Stats.CTX_MID_Z(Stat,RamRangeTlbMisses));
 
-    PPGMRAMRANGE pRam = pVM->pgm.s.CTX_SUFF(pRamRangeTree);
-    while (pRam)
-    {
-        RTGCPHYS off = GCPhys - pRam->GCPhys;
-        if (off < pRam->cb)
-        {
-            pVM->pgm.s.CTX_SUFF(apRamRangesTlb)[PGM_RAMRANGE_TLB_IDX(GCPhys)] = pRam;
-            return pRam;
+    uint32_t idxEnd   = RT_MIN(pVM->pgm.s.RamRangeUnion.cLookupEntries, RT_ELEMENTS(pVM->pgm.s.aRamRangeLookup));
+    uint32_t idxStart = 0;
+    for (;;)
+    {
+        uint32_t       idxLookup        = idxStart + (idxEnd - idxStart) / 2;
+        RTGCPHYS const GCPhysEntryFirst = PGMRAMRANGELOOKUPENTRY_GET_FIRST(pVM->pgm.s.aRamRangeLookup[idxLookup]);
+        RTGCPHYS const cbEntryMinus1    = pVM->pgm.s.aRamRangeLookup[idxLookup].GCPhysLast - GCPhysEntryFirst;
+        RTGCPHYS const off              = GCPhys - GCPhysEntryFirst;
+        if (off <= cbEntryMinus1)
+        {
+            uint32_t const     idRamRange = PGMRAMRANGELOOKUPENTRY_GET_ID(pVM->pgm.s.aRamRangeLookup[idxLookup]);
+            AssertReturn(idRamRange < RT_ELEMENTS(pVM->CTX_EXPR(pgm, pgmr0, pgm).s.apRamRanges), NULL);
+            PPGMRAMRANGE const pRamRange  = pVM->CTX_EXPR(pgm, pgmr0, pgm).s.apRamRanges[idRamRange];
+            Assert(pRamRange);
+            pVM->pgm.s.CTX_SUFF(apRamRangesTlb)[PGM_RAMRANGE_TLB_IDX(GCPhys)] = pRamRange;
+            return pRamRange;
         }
         if (RTGCPHYS_IS_NEGATIVE(off))
-            pRam = pRam->CTX_SUFF(pLeft);
+        {
+            if (idxStart < idxLookup)
+                idxEnd = idxLookup;
+            else
+                break;
+        }
         else
-            pRam = pRam->CTX_SUFF(pRight);
+        {
+            idxLookup += 1;
+            if (idxLookup < idxEnd)
+                idxStart = idxLookup;
+            else
+                break;
+        }
     }
     return NULL;
@@ -531,27 +658,51 @@
  * @copydoc pgmPhysGetRangeAtOrAbove
  */
-PPGMRAMRANGE pgmPhysGetRangeAtOrAboveSlow(PVM pVM, RTGCPHYS GCPhys)
+PPGMRAMRANGE pgmPhysGetRangeAtOrAboveSlow(PVMCC pVM, RTGCPHYS GCPhys)
 {
     STAM_COUNTER_INC(&pVM->pgm.s.Stats.CTX_MID_Z(Stat,RamRangeTlbMisses));
 
-    PPGMRAMRANGE pLastLeft = NULL;
-    PPGMRAMRANGE pRam = pVM->pgm.s.CTX_SUFF(pRamRangeTree);
-    while (pRam)
-    {
-        RTGCPHYS off = GCPhys - pRam->GCPhys;
-        if (off < pRam->cb)
-        {
-            pVM->pgm.s.CTX_SUFF(apRamRangesTlb)[PGM_RAMRANGE_TLB_IDX(GCPhys)] = pRam;
-            return pRam;
+    uint32_t idRamRangeLastLeft = UINT32_MAX;
+    uint32_t idxEnd             = RT_MIN(pVM->pgm.s.RamRangeUnion.cLookupEntries, RT_ELEMENTS(pVM->pgm.s.aRamRangeLookup));
+    uint32_t idxStart           = 0;
+    for (;;)
+    {
+        uint32_t       idxLookup        = idxStart + (idxEnd - idxStart) / 2;
+        RTGCPHYS const GCPhysEntryFirst = PGMRAMRANGELOOKUPENTRY_GET_FIRST(pVM->pgm.s.aRamRangeLookup[idxLookup]);
+        RTGCPHYS const cbEntryMinus1    = pVM->pgm.s.aRamRangeLookup[idxLookup].GCPhysLast - GCPhysEntryFirst;
+        RTGCPHYS const off              = GCPhys - GCPhysEntryFirst;
+        if (off <= cbEntryMinus1)
+        {
+            uint32_t const     idRamRange = PGMRAMRANGELOOKUPENTRY_GET_ID(pVM->pgm.s.aRamRangeLookup[idxLookup]);
+            AssertReturn(idRamRange < RT_ELEMENTS(pVM->CTX_EXPR(pgm, pgmr0, pgm).s.apRamRanges), NULL);
+            PPGMRAMRANGE const pRamRange  = pVM->CTX_EXPR(pgm, pgmr0, pgm).s.apRamRanges[idRamRange];
+            Assert(pRamRange);
+            pVM->pgm.s.CTX_SUFF(apRamRangesTlb)[PGM_RAMRANGE_TLB_IDX(GCPhys)] = pRamRange;
+            return pRamRange;
         }
         if (RTGCPHYS_IS_NEGATIVE(off))
         {
-            pLastLeft = pRam;
-            pRam = pRam->CTX_SUFF(pLeft);
+            idRamRangeLastLeft = PGMRAMRANGELOOKUPENTRY_GET_ID(pVM->pgm.s.aRamRangeLookup[idxLookup]);
+            if (idxStart < idxLookup)
+                idxEnd = idxLookup;
+            else
+                break;
         }
         else
-            pRam = pRam->CTX_SUFF(pRight);
-    }
-    return pLastLeft;
+        {
+            idxLookup += 1;
+            if (idxLookup < idxEnd)
+                idxStart = idxLookup;
+            else
+                break;
+        }
+    }
+    if (idRamRangeLastLeft != UINT32_MAX)
+    {
+        AssertReturn(idRamRangeLastLeft < RT_ELEMENTS(pVM->CTX_EXPR(pgm, pgmr0, pgm).s.apRamRanges), NULL);
+        PPGMRAMRANGE const pRamRange = pVM->CTX_EXPR(pgm, pgmr0, pgm).s.apRamRanges[idRamRangeLastLeft];
+        Assert(pRamRange);
+        return pRamRange;
+    }
+    return NULL;
 }
 
@@ -562,22 +713,47 @@
  * @copydoc pgmPhysGetPage
  */
-PPGMPAGE pgmPhysGetPageSlow(PVM pVM, RTGCPHYS GCPhys)
+PPGMPAGE pgmPhysGetPageSlow(PVMCC pVM, RTGCPHYS GCPhys)
 {
     STAM_COUNTER_INC(&pVM->pgm.s.Stats.CTX_MID_Z(Stat,RamRangeTlbMisses));
 
-    PPGMRAMRANGE pRam = pVM->pgm.s.CTX_SUFF(pRamRangeTree);
-    while (pRam)
-    {
-        RTGCPHYS off = GCPhys - pRam->GCPhys;
-        if (off < pRam->cb)
-        {
-            pVM->pgm.s.CTX_SUFF(apRamRangesTlb)[PGM_RAMRANGE_TLB_IDX(GCPhys)] = pRam;
-            return &pRam->aPages[off >> GUEST_PAGE_SHIFT];
-        }
-
+    uint32_t idxEnd   = RT_MIN(pVM->pgm.s.RamRangeUnion.cLookupEntries, RT_ELEMENTS(pVM->pgm.s.aRamRangeLookup));
+    uint32_t idxStart = 0;
+    for (;;)
+    {
+        uint32_t       idxLookup        = idxStart + (idxEnd - idxStart) / 2;
+        RTGCPHYS const GCPhysEntryFirst = PGMRAMRANGELOOKUPENTRY_GET_FIRST(pVM->pgm.s.aRamRangeLookup[idxLookup]);
+        RTGCPHYS const cbEntryMinus1    = pVM->pgm.s.aRamRangeLookup[idxLookup].GCPhysLast - GCPhysEntryFirst;
+        RTGCPHYS const off              = GCPhys - GCPhysEntryFirst;
+        if (off <= cbEntryMinus1)
+        {
+            uint32_t const     idRamRange = PGMRAMRANGELOOKUPENTRY_GET_ID(pVM->pgm.s.aRamRangeLookup[idxLookup]);
+            AssertReturn(idRamRange < RT_ELEMENTS(pVM->CTX_EXPR(pgm, pgmr0, pgm).s.apRamRanges), NULL);
+            PPGMRAMRANGE const pRamRange  = pVM->CTX_EXPR(pgm, pgmr0, pgm).s.apRamRanges[idRamRange];
+            AssertReturn(pRamRange, NULL);
+            pVM->pgm.s.CTX_SUFF(apRamRangesTlb)[PGM_RAMRANGE_TLB_IDX(GCPhys)] = pRamRange;
+
+            /* Get the page. */
+            Assert(off < pRamRange->cb);
+            RTGCPHYS const idxPage = off >> GUEST_PAGE_SHIFT;
+#ifdef IN_RING0
+            AssertReturn(idxPage < pVM->pgmr0.s.acRamRangePages[idRamRange], NULL);
+#endif
+            return &pRamRange->aPages[idxPage];
+        }
         if (RTGCPHYS_IS_NEGATIVE(off))
-            pRam = pRam->CTX_SUFF(pLeft);
+        {
+            if (idxStart < idxLookup)
+                idxEnd = idxLookup;
+            else
+                break;
+        }
         else
-            pRam = pRam->CTX_SUFF(pRight);
+        {
+            idxLookup += 1;
+            if (idxLookup < idxEnd)
+                idxStart = idxLookup;
+            else
+                break;
+        }
     }
     return NULL;
@@ -590,23 +766,48 @@
  * @copydoc pgmPhysGetPageEx
  */
-int pgmPhysGetPageExSlow(PVM pVM, RTGCPHYS GCPhys, PPPGMPAGE ppPage)
+int pgmPhysGetPageExSlow(PVMCC pVM, RTGCPHYS GCPhys, PPPGMPAGE ppPage)
 {
     STAM_COUNTER_INC(&pVM->pgm.s.Stats.CTX_MID_Z(Stat,RamRangeTlbMisses));
 
-    PPGMRAMRANGE pRam = pVM->pgm.s.CTX_SUFF(pRamRangeTree);
-    while (pRam)
-    {
-        RTGCPHYS off = GCPhys - pRam->GCPhys;
-        if (off < pRam->cb)
-        {
-            pVM->pgm.s.CTX_SUFF(apRamRangesTlb)[PGM_RAMRANGE_TLB_IDX(GCPhys)] = pRam;
-            *ppPage = &pRam->aPages[off >> GUEST_PAGE_SHIFT];
+    uint32_t idxEnd   = RT_MIN(pVM->pgm.s.RamRangeUnion.cLookupEntries, RT_ELEMENTS(pVM->pgm.s.aRamRangeLookup));
+    uint32_t idxStart = 0;
+    for (;;)
+    {
+        uint32_t       idxLookup        = idxStart + (idxEnd - idxStart) / 2;
+        RTGCPHYS const GCPhysEntryFirst = PGMRAMRANGELOOKUPENTRY_GET_FIRST(pVM->pgm.s.aRamRangeLookup[idxLookup]);
+        RTGCPHYS const cbEntryMinus1    = pVM->pgm.s.aRamRangeLookup[idxLookup].GCPhysLast - GCPhysEntryFirst;
+        RTGCPHYS const off              = GCPhys - GCPhysEntryFirst;
+        if (off <= cbEntryMinus1)
+        {
+            uint32_t const     idRamRange = PGMRAMRANGELOOKUPENTRY_GET_ID(pVM->pgm.s.aRamRangeLookup[idxLookup]);
+            AssertReturn(idRamRange < RT_ELEMENTS(pVM->CTX_EXPR(pgm, pgmr0, pgm).s.apRamRanges), VERR_PGM_PHYS_RAM_LOOKUP_IPE);
+            PPGMRAMRANGE const pRamRange  = pVM->CTX_EXPR(pgm, pgmr0, pgm).s.apRamRanges[idRamRange];
+            AssertReturn(pRamRange, VERR_PGM_PHYS_RAM_LOOKUP_IPE);
+            pVM->pgm.s.CTX_SUFF(apRamRangesTlb)[PGM_RAMRANGE_TLB_IDX(GCPhys)] = pRamRange;
+
+            /* Get the page. */
+            Assert(off < pRamRange->cb);
+            RTGCPHYS const idxPage = off >> GUEST_PAGE_SHIFT;
+#ifdef IN_RING0
+            AssertReturn(idxPage < pVM->pgmr0.s.acRamRangePages[idRamRange], VERR_PGM_PHYS_RAM_LOOKUP_IPE);
+#endif
+            *ppPage = &pRamRange->aPages[idxPage];
             return VINF_SUCCESS;
         }
-
         if (RTGCPHYS_IS_NEGATIVE(off))
-            pRam = pRam->CTX_SUFF(pLeft);
+        {
+            if (idxStart < idxLookup)
+                idxEnd = idxLookup;
+            else
+                break;
+        }
         else
-            pRam = pRam->CTX_SUFF(pRight);
+        {
+            idxLookup += 1;
+            if (idxLookup < idxEnd)
+                idxStart = idxLookup;
+            else
+                break;
+        }
     }
 
@@ -621,24 +822,49 @@
  * @copydoc pgmPhysGetPageAndRangeEx
  */
-int pgmPhysGetPageAndRangeExSlow(PVM pVM, RTGCPHYS GCPhys, PPPGMPAGE ppPage, PPGMRAMRANGE *ppRam)
+int pgmPhysGetPageAndRangeExSlow(PVMCC pVM, RTGCPHYS GCPhys, PPPGMPAGE ppPage, PPGMRAMRANGE *ppRam)
 {
     STAM_COUNTER_INC(&pVM->pgm.s.Stats.CTX_MID_Z(Stat,RamRangeTlbMisses));
 
-    PPGMRAMRANGE pRam = pVM->pgm.s.CTX_SUFF(pRamRangeTree);
-    while (pRam)
-    {
-        RTGCPHYS off = GCPhys - pRam->GCPhys;
-        if (off < pRam->cb)
-        {
-            pVM->pgm.s.CTX_SUFF(apRamRangesTlb)[PGM_RAMRANGE_TLB_IDX(GCPhys)] = pRam;
-            *ppRam  = pRam;
-            *ppPage = &pRam->aPages[off >> GUEST_PAGE_SHIFT];
+    uint32_t idxEnd   = RT_MIN(pVM->pgm.s.RamRangeUnion.cLookupEntries, RT_ELEMENTS(pVM->pgm.s.aRamRangeLookup));
+    uint32_t idxStart = 0;
+    for (;;)
+    {
+        uint32_t       idxLookup        = idxStart + (idxEnd - idxStart) / 2;
+        RTGCPHYS const GCPhysEntryFirst = PGMRAMRANGELOOKUPENTRY_GET_FIRST(pVM->pgm.s.aRamRangeLookup[idxLookup]);
+        RTGCPHYS const cbEntryMinus1    = pVM->pgm.s.aRamRangeLookup[idxLookup].GCPhysLast - GCPhysEntryFirst;
+        RTGCPHYS const off              = GCPhys - GCPhysEntryFirst;
+        if (off <= cbEntryMinus1)
+        {
+            uint32_t const     idRamRange = PGMRAMRANGELOOKUPENTRY_GET_ID(pVM->pgm.s.aRamRangeLookup[idxLookup]);
+            AssertReturn(idRamRange < RT_ELEMENTS(pVM->CTX_EXPR(pgm, pgmr0, pgm).s.apRamRanges), VERR_PGM_PHYS_RAM_LOOKUP_IPE);
+            PPGMRAMRANGE const pRamRange  = pVM->CTX_EXPR(pgm, pgmr0, pgm).s.apRamRanges[idRamRange];
+            AssertReturn(pRamRange, VERR_PGM_PHYS_RAM_LOOKUP_IPE);
+            pVM->pgm.s.CTX_SUFF(apRamRangesTlb)[PGM_RAMRANGE_TLB_IDX(GCPhys)] = pRamRange;
+
+            /* Get the page. */
+            Assert(off < pRamRange->cb);
+            RTGCPHYS const idxPage = off >> GUEST_PAGE_SHIFT;
+#ifdef IN_RING0
+            AssertReturn(idxPage < pVM->pgmr0.s.acRamRangePages[idRamRange], VERR_PGM_PHYS_RAM_LOOKUP_IPE);
+#endif
+            *ppRam  = pRamRange;
+            *ppPage = &pRamRange->aPages[idxPage];
             return VINF_SUCCESS;
         }
-
         if (RTGCPHYS_IS_NEGATIVE(off))
-            pRam = pRam->CTX_SUFF(pLeft);
+        {
+            if (idxStart < idxLookup)
+                idxEnd = idxLookup;
+            else
+                break;
+        }
         else
-            pRam = pRam->CTX_SUFF(pRight);
+        {
+            idxLookup += 1;
+            if (idxLookup < idxEnd)
+                idxStart = idxLookup;
+            else
+                break;
+        }
     }
 
@@ -647,4 +873,930 @@
     return VERR_PGM_INVALID_GC_PHYSICAL_ADDRESS;
 }
+
+
+/**
+ * Common worker for pgmR3PhysAllocateRamRange, PGMR0PhysAllocateRamRangeReq,
+ * and pgmPhysMmio2RegisterWorker2.
+ */
+DECLHIDDEN(int) pgmPhysRamRangeAllocCommon(PVMCC pVM, uint32_t cPages, uint32_t fFlags, uint32_t *pidNewRange)
+{
+
+    /*
+     * Allocate the RAM range structure and map it into ring-3.
+     */
+    size_t const cbRamRange = RT_ALIGN_Z(RT_UOFFSETOF_DYN(PGMRAMRANGE, aPages[cPages]), HOST_PAGE_SIZE);
+#ifdef IN_RING0
+    RTR0MEMOBJ   hMemObj    = NIL_RTR0MEMOBJ;
+    int rc = RTR0MemObjAllocPage(&hMemObj, cbRamRange, false /*fExecutable*/);
+#else
+    PPGMRAMRANGE pRamRange;
+    int rc = SUPR3PageAlloc(cbRamRange >> HOST_PAGE_SHIFT, 0 /*fFlags*/, (void **)&pRamRange);
+#endif
+    if (RT_SUCCESS(rc))
+    {
+        /* Zero the memory and do basic range init before mapping it into userland. */
+#ifdef IN_RING0
+        PPGMRAMRANGE const pRamRange = (PPGMRAMRANGE)RTR0MemObjAddress(hMemObj);
+        if (!RTR0MemObjWasZeroInitialized(hMemObj))
+#endif
+            RT_BZERO(pRamRange, cbRamRange);
+
+        pRamRange->GCPhys       = NIL_RTGCPHYS;
+        pRamRange->cb           = (RTGCPHYS)cPages << GUEST_PAGE_SHIFT;
+        pRamRange->GCPhysLast   = NIL_RTGCPHYS;
+        pRamRange->fFlags       = fFlags;
+        pRamRange->idRange      = UINT32_MAX / 2;
+
+#ifdef IN_RING0
+        /* Map it into userland. */
+        RTR0MEMOBJ hMapObj  = NIL_RTR0MEMOBJ;
+        rc = RTR0MemObjMapUser(&hMapObj, hMemObj, (RTR3PTR)-1, 0 /*uAlignment*/,
+                               RTMEM_PROT_READ | RTMEM_PROT_WRITE, NIL_RTR0PROCESS);
+        if (RT_SUCCESS(rc))
+#endif
+        {
+            /*
+             * Grab the lock (unlikely to fail or block as caller typically owns it already).
+             */
+            rc = PGM_LOCK(pVM);
+            if (RT_SUCCESS(rc))
+            {
+                /*
+                 * Allocate a range ID.
+                 */
+                uint32_t idRamRange = pVM->CTX_EXPR(pgm, pgmr0, pgm).s.idRamRangeMax + 1;
+                if (idRamRange != 0 && idRamRange < RT_ELEMENTS(pVM->CTX_EXPR(pgm, pgmr0, pgm).s.apRamRanges))
+                {
+#ifdef IN_RING0
+                    if (pVM->pgmr0.s.apRamRanges[idRamRange] == NULL)
+#endif
+                    {
+                        if (pVM->pgm.s.apRamRanges[idRamRange] == NIL_RTR3PTR)
+                        {
+                            /*
+                             * Commit it.
+                             */
+#ifdef IN_RING0
+                            pVM->pgmr0.s.apRamRanges[idRamRange]       = pRamRange;
+                            pVM->pgmr0.s.acRamRangePages[idRamRange]   = cPages;
+                            pVM->pgmr0.s.ahRamRangeMemObjs[idRamRange] = hMemObj;
+                            pVM->pgmr0.s.ahRamRangeMapObjs[idRamRange] = hMapObj;
+                            pVM->pgmr0.s.idRamRangeMax                 = idRamRange;
+#endif
+
+                            pVM->pgm.s.idRamRangeMax                   = idRamRange;
+#ifdef IN_RING0
+                            pVM->pgm.s.apRamRanges[idRamRange]         = RTR0MemObjAddressR3(hMapObj);
+#else
+                            pVM->pgm.s.apRamRanges[idRamRange]         = pRamRange;
+#endif
+
+                            pRamRange->idRange                          = idRamRange;
+                            *pidNewRange                                = idRamRange;
+
+                            PGM_UNLOCK(pVM);
+                            return VINF_SUCCESS;
+                        }
+                    }
+
+                    /*
+                     * Bail out.
+                     */
+                    rc = VERR_INTERNAL_ERROR_5;
+                }
+                else
+                    rc = VERR_PGM_TOO_MANY_RAM_RANGES;
+                PGM_UNLOCK(pVM);
+            }
+#ifdef IN_RING0
+            RTR0MemObjFree(hMapObj, false /*fFreeMappings*/);
+#endif
+        }
+#ifdef IN_RING0
+        RTR0MemObjFree(hMemObj, true /*fFreeMappings*/);
+#else
+        SUPR3PageFree(pRamRange, cbRamRange >> HOST_PAGE_SHIFT);
+#endif
+    }
+    *pidNewRange = UINT32_MAX;
+    return rc;
+}
+
+
+#ifdef IN_RING0
+/**
+ * This is called during VM initialization to allocate a RAM range.
+ *
+ * The range is not entered into the lookup table, that is something the caller
+ * has to do.  The PGMPAGE entries are zero'ed, but otherwise uninitialized.
+ *
+ * @returns VBox status code.
+ * @param   pGVM    Pointer to the global VM structure.
+ * @param   pReq    Where to get the parameters and return the range ID.
+ * @thread  EMT(0)
+ */
+VMMR0_INT_DECL(int) PGMR0PhysAllocateRamRangeReq(PGVM pGVM, PPGMPHYSALLOCATERAMRANGEREQ pReq)
+{
+    /*
+     * Validate input (ASSUME pReq is a copy and can't be modified by ring-3
+     * while we're here).
+     */
+    AssertPtrReturn(pReq, VERR_INVALID_POINTER);
+    AssertMsgReturn(pReq->Hdr.cbReq == sizeof(*pReq), ("%#x < %#zx\n", pReq->Hdr.cbReq, sizeof(*pReq)), VERR_INVALID_PARAMETER);
+
+    AssertReturn(pReq->cbGuestPage == GUEST_PAGE_SIZE, VERR_INCOMPATIBLE_CONFIG);
+
+    AssertReturn(pReq->cGuestPages > 0, VERR_OUT_OF_RANGE);
+    AssertReturn(pReq->cGuestPages <= PGM_MAX_PAGES_PER_RAM_RANGE, VERR_OUT_OF_RANGE);
+
+    AssertMsgReturn(!(pReq->fFlags & ~(uint32_t)PGM_RAM_RANGE_FLAGS_VALID_MASK), ("fFlags=%#RX32\n", pReq->fFlags),
+                    VERR_INVALID_FLAGS);
+
+    /** @todo better VM state guard, enmVMState is ring-3 writable. */
+    VMSTATE const enmState = pGVM->enmVMState;
+    AssertMsgReturn(enmState == VMSTATE_CREATING, ("enmState=%d\n", enmState), VERR_VM_INVALID_VM_STATE);
+    VM_ASSERT_EMT0_RETURN(pGVM, VERR_VM_THREAD_NOT_EMT);
+
+    /*
+     * Call common worker.
+     */
+    return pgmPhysRamRangeAllocCommon(pGVM, pReq->cGuestPages, pReq->fFlags, &pReq->idNewRange);
+}
+#endif /* IN_RING0 */
+
+
+/**
+ * Frees a RAM range.
+ *
+ * This is not a typical occurence.  Currently only used for a special MMIO2
+ * saved state compatibility scenario involving PCNet and state saved before
+ * VBox v4.3.6.
+ */
+static int pgmPhysRamRangeFree(PVMCC pVM, PPGMRAMRANGE pRamRange)
+{
+    /*
+     * Some basic input validation.
+     */
+    AssertPtrReturn(pRamRange, VERR_INVALID_PARAMETER);
+    uint32_t const idRamRange = ASMAtomicReadU32(&pRamRange->idRange);
+    ASMCompilerBarrier();
+    AssertReturn(idRamRange < RT_ELEMENTS(pVM->CTX_EXPR(pgm, pgmr0, pgm).s.apRamRanges), VERR_INVALID_PARAMETER);
+    AssertReturn(pRamRange == pVM->CTX_EXPR(pgm, pgmr0, pgm).s.apRamRanges[idRamRange], VERR_INVALID_PARAMETER);
+    AssertReturn(pRamRange->GCPhys == NIL_RTGCPHYS, VERR_RESOURCE_BUSY);
+
+    /*
+     * Kill the range pointers and associated data.
+     */
+    pVM->pgm.s.apRamRanges[idRamRange]   = NIL_RTR3PTR;
+#ifdef IN_RING0
+    pVM->pgmr0.s.apRamRanges[idRamRange] = NULL;
+#endif
+
+    /*
+     * Zap the pages and other RAM ranges properties to ensure there aren't any
+     * stale references to anything hanging around should the freeing go awry.
+     */
+#ifdef IN_RING0
+    uint32_t const cPages = pVM->pgmr0.s.acRamRangePages[idRamRange];
+    pVM->pgmr0.s.acRamRangePages[idRamRange] = 0;
+#else
+    uint32_t const cPages = pRamRange->cb >> GUEST_PAGE_SHIFT;
+#endif
+    RT_BZERO(pRamRange->aPages, cPages * sizeof(pRamRange->aPages[0]));
+
+    pRamRange->fFlags    = UINT32_MAX;
+    pRamRange->cb        = NIL_RTGCPHYS;
+    pRamRange->pbR3      = NIL_RTR3PTR;
+    pRamRange->pszDesc   = NIL_RTR3PTR;
+    pRamRange->paLSPages = NIL_RTR3PTR;
+    pRamRange->idRange   = UINT32_MAX / 8;
+
+    /*
+     * Free the RAM range itself.
+     */
+#ifdef IN_RING0
+    Assert(pVM->pgmr0.s.ahRamRangeMapObjs[idRamRange] != NIL_RTR0MEMOBJ);
+    int rc = RTR0MemObjFree(pVM->pgmr0.s.ahRamRangeMapObjs[idRamRange], true /*fFreeMappings*/);
+    if (RT_SUCCESS(rc))
+    {
+        pVM->pgmr0.s.ahRamRangeMapObjs[idRamRange] = NIL_RTR0MEMOBJ;
+        rc = RTR0MemObjFree(pVM->pgmr0.s.ahRamRangeMemObjs[idRamRange], true /*fFreeMappings*/);
+        if (RT_SUCCESS(rc))
+            pVM->pgmr0.s.ahRamRangeMemObjs[idRamRange] = NIL_RTR0MEMOBJ;
+    }
+#else
+    size_t const cbRamRange = RT_ALIGN_Z(RT_UOFFSETOF_DYN(PGMRAMRANGE, aPages[cPages]), HOST_PAGE_SIZE);
+    int rc = SUPR3PageFree(pRamRange, cbRamRange >> HOST_PAGE_SHIFT);
+#endif
+
+    /*
+     * Decrease the max ID if removal was successful and this was the final
+     * RAM range entry.
+     */
+    if (   RT_SUCCESS(rc)
+        && idRamRange == pVM->CTX_EXPR(pgm, pgmr0, pgm).s.idRamRangeMax)
+    {
+        pVM->pgm.s.idRamRangeMax   = idRamRange - 1;
+#ifdef IN_RING0
+        pVM->pgmr0.s.idRamRangeMax = idRamRange - 1;
+#endif
+    }
+
+    return rc;
+}
+
+
+
+/*********************************************************************************************************************************
+*   MMIO2                                                                                                                        *
+*********************************************************************************************************************************/
+
+/**
+ * Calculates the number of chunks
+ *
+ * @returns Number of registration chunk needed.
+ * @param   cb              The size of the MMIO/MMIO2 range.
+ * @param   pcPagesPerChunk Where to return the number of guest pages tracked by
+ *                          each chunk.  Optional.
+ */
+DECLHIDDEN(uint16_t) pgmPhysMmio2CalcChunkCount(RTGCPHYS cb, uint32_t *pcPagesPerChunk)
+{
+    /*
+     * This is the same calculation as PGMR3PhysRegisterRam does, except we'll be
+     * needing a few bytes extra the PGMREGMMIO2RANGE structure.
+     *
+     * Note! In additions, we've got a 24 bit sub-page range for MMIO2 ranges, leaving
+     *       us with an absolute maximum of 16777215 pages per chunk (close to 64 GB).
+     */
+    AssertCompile(PGM_MAX_PAGES_PER_RAM_RANGE < _16M);
+    uint32_t const cPagesPerChunk = PGM_MAX_PAGES_PER_RAM_RANGE;
+
+    if (pcPagesPerChunk)
+        *pcPagesPerChunk = cPagesPerChunk;
+
+    /* Calc the number of chunks we need. */
+    RTGCPHYS const cGuestPages = cb >> GUEST_PAGE_SHIFT;
+    uint16_t cChunks = (uint16_t)((cGuestPages + cPagesPerChunk - 1) / cPagesPerChunk);
+#ifdef IN_RING3
+    AssertRelease((RTGCPHYS)cChunks * cPagesPerChunk >= cGuestPages);
+#else
+    AssertReturn((RTGCPHYS)cChunks * cPagesPerChunk >= cGuestPages, 0);
+#endif
+    return cChunks;
+}
+
+
+/**
+ * Worker for PGMR3PhysMmio2Register and PGMR0PhysMmio2RegisterReq.
+ *
+ * (The caller already know which MMIO2 region ID will be assigned and how many
+ * chunks will be used, so no output parameters required.)
+ */
+DECLHIDDEN(int) pgmPhysMmio2RegisterWorker(PVMCC pVM, uint32_t const cGuestPages, uint8_t const idMmio2,
+                                           const uint8_t cChunks, PPDMDEVINSR3 const pDevIns, uint8_t
+                                           const iSubDev, uint8_t const iRegion, uint32_t const fFlags)
+{
+    /*
+     * Get the number of pages per chunk.
+     */
+    uint32_t cGuestPagesPerChunk;
+    AssertReturn(pgmPhysMmio2CalcChunkCount((RTGCPHYS)cGuestPages << GUEST_PAGE_SHIFT, &cGuestPagesPerChunk) == cChunks,
+                 VERR_PGM_PHYS_MMIO_EX_IPE);
+    Assert(idMmio2 != 0);
+
+    /*
+     * The first thing we need to do is the allocate the memory that will be
+     * backing the whole range.
+     */
+    RTGCPHYS const          cbMmio2Backing   = (RTGCPHYS)cGuestPages << GUEST_PAGE_SHIFT;
+    uint32_t const          cHostPages       = (cbMmio2Backing + HOST_PAGE_SIZE - 1U) >> HOST_PAGE_SHIFT;
+    size_t const            cbMmio2Aligned   = cHostPages << HOST_PAGE_SHIFT;
+    R3PTRTYPE(uint8_t *)    pbMmio2BackingR3 = NIL_RTR3PTR;
+#ifdef IN_RING0
+    RTR0MEMOBJ              hMemObj          = NIL_RTR0MEMOBJ;
+# ifndef VBOX_WITH_LINEAR_HOST_PHYS_MEM
+    int rc = RTR0MemObjAllocPage(&hMemObj, cbMmio2Aligned, false /*fExecutable*/);
+# else
+    int rc = RTR0MemObjAllocPhysNC(&hMemObj, cbMmio2Aligned, NIL_RTHCPHYS);
+# endif
+#else  /* !IN_RING0 */
+    AssertReturn(PGM_IS_IN_NEM_MODE(pVM), VERR_INTERNAL_ERROR_4);
+    int rc = SUPR3PageAlloc(cHostPages, pVM->pgm.s.fUseLargePages ? SUP_PAGE_ALLOC_F_LARGE_PAGES : 0, (void **)&pbMmio2BackingR3);
+#endif /* !IN_RING0 */
+    if (RT_SUCCESS(rc))
+    {
+        /*
+         * Make sure it's is initialized to zeros before it's mapped to userland.
+         */
+#ifdef IN_RING0
+# ifndef VBOX_WITH_LINEAR_HOST_PHYS_MEM
+        uint8_t *pbMmio2BackingR0 = (uint8_t *)RTR0MemObjAddress(hMemObj);
+        AssertPtr(pbMmio2BackingR0);
+# endif
+        if (!RTR0MemObjWasZeroInitialized(hMemObj))
+        {
+            void *pv = RTR0MemObjAddress(hMemObj);
+            AssertReturnStmt(pv, RTR0MemObjFree(hMemObj, true /*fFreeMappings*/),  VERR_INTERNAL_ERROR_4);
+            RT_BZERO(pv, cbMmio2Aligned);
+        }
+#else
+        RT_BZERO(pbMmio2BackingR3, cbMmio2Aligned);
+#endif
+
+#ifdef IN_RING0
+        /*
+         * Map it into ring-3.
+         */
+        RTR0MEMOBJ hMapObj = NIL_RTR0MEMOBJ;
+        rc = RTR0MemObjMapUser(&hMapObj, hMemObj, (RTR3PTR)-1, 0, RTMEM_PROT_READ | RTMEM_PROT_WRITE, NIL_RTR0PROCESS);
+        if (RT_SUCCESS(rc))
+        {
+            pbMmio2BackingR3 = RTR0MemObjAddressR3(hMapObj);
+#endif
+
+            /*
+             * Create the MMIO2 registration records and associated RAM ranges.
+             * The RAM range allocation may fail here.
+             */
+            RTGCPHYS offMmio2Backing = 0;
+            uint32_t cGuestPagesLeft = cGuestPages;
+            for (uint32_t iChunk = 0, idx = idMmio2 - 1; iChunk < cChunks; iChunk++, idx++)
+            {
+                uint32_t const cPagesTrackedByChunk = RT_MIN(cGuestPagesLeft, cGuestPagesPerChunk);
+
+                /*
+                 * Allocate the RAM range for this chunk.
+                 */
+                uint32_t idRamRange = UINT32_MAX;
+                rc = pgmPhysRamRangeAllocCommon(pVM, cPagesTrackedByChunk, PGM_RAM_RANGE_FLAGS_AD_HOC_MMIO_EX, &idRamRange);
+                if (RT_FAILURE(rc))
+                {
+                    /* We only zap the pointers to the backing storage.
+                       PGMR3Term and friends will clean up the RAM ranges and stuff. */
+                    while (iChunk-- > 0)
+                    {
+                        idx--;
+#ifdef IN_RING0
+                        pVM->pgmr0.s.acMmio2RangePages[idx] = 0;
+# ifndef VBOX_WITH_LINEAR_HOST_PHYS_MEM
+                        pVM->pgmr0.s.apbMmio2Backing[idx]   = NULL;
+# endif
+#endif
+
+                        PPGMREGMMIO2RANGE const pMmio2 = &pVM->pgm.s.aMmio2Ranges[idx];
+                        pMmio2->pbR3 = NIL_RTR3PTR;
+
+                        PPGMRAMRANGE const      pRamRange = pVM->CTX_EXPR(pgm, pgmr0, pgm).s.apMmio2RamRanges[idx];
+                        pRamRange->pbR3 = NIL_RTR3PTR;
+                        RT_BZERO(&pRamRange->aPages[0], sizeof(pRamRange->aPages) * cGuestPagesPerChunk);
+                    }
+                    break;
+                }
+
+                pVM->pgm.s.apMmio2RamRanges[idx]    = pVM->pgm.s.apRamRanges[idRamRange];
+#ifdef IN_RING0
+                pVM->pgmr0.s.apMmio2RamRanges[idx]  = pVM->pgmr0.s.apRamRanges[idRamRange];
+                pVM->pgmr0.s.acMmio2RangePages[idx] = cPagesTrackedByChunk;
+#endif
+
+                /* Initialize the RAM range. */
+                PPGMRAMRANGE const pRamRange       = pVM->CTX_EXPR(pgm, pgmr0, pgm).s.apRamRanges[idRamRange];
+                pRamRange->pbR3 = pbMmio2BackingR3 + offMmio2Backing;
+                uint32_t iDstPage = cPagesTrackedByChunk;
+#ifdef IN_RING0
+                AssertRelease(HOST_PAGE_SHIFT == GUEST_PAGE_SHIFT);
+                while (iDstPage-- > 0)
+                {
+                    RTHCPHYS HCPhys = RTR0MemObjGetPagePhysAddr(hMemObj, iDstPage + (offMmio2Backing >> HOST_PAGE_SHIFT));
+                    Assert(HCPhys != NIL_RTHCPHYS);
+                    PGM_PAGE_INIT(&pRamRange->aPages[iDstPage], HCPhys, PGM_MMIO2_PAGEID_MAKE(idMmio2, iDstPage),
+                                  PGMPAGETYPE_MMIO2, PGM_PAGE_STATE_ALLOCATED);
+                }
+#else
+                Assert(PGM_IS_IN_NEM_MODE(pVM));
+                while (iDstPage-- > 0)
+                    PGM_PAGE_INIT(&pRamRange->aPages[iDstPage], UINT64_C(0x0000ffffffff0000),
+                                  PGM_MMIO2_PAGEID_MAKE(idMmio2, iDstPage),
+                                  PGMPAGETYPE_MMIO2, PGM_PAGE_STATE_ALLOCATED);
+#endif
+
+                /*
+                 * Initialize the MMIO2 registration structure.
+                 */
+                PPGMREGMMIO2RANGE const pMmio2 = &pVM->pgm.s.aMmio2Ranges[idx];
+                pMmio2->pDevInsR3       = pDevIns;
+                pMmio2->pbR3            = pbMmio2BackingR3 + offMmio2Backing;
+                pMmio2->fFlags          = 0;
+                if (iChunk == 0)
+                    pMmio2->fFlags     |= PGMREGMMIO2RANGE_F_FIRST_CHUNK;
+                if (iChunk + 1 == cChunks)
+                    pMmio2->fFlags     |= PGMREGMMIO2RANGE_F_LAST_CHUNK;
+                if (fFlags & PGMPHYS_MMIO2_FLAGS_TRACK_DIRTY_PAGES)
+                    pMmio2->fFlags     |= PGMREGMMIO2RANGE_F_TRACK_DIRTY_PAGES;
+
+                pMmio2->iSubDev         = iSubDev;
+                pMmio2->iRegion         = iRegion;
+                pMmio2->idSavedState    = UINT8_MAX;
+                pMmio2->idMmio2         = idMmio2 + iChunk;
+                pMmio2->idRamRange      = idRamRange;
+                Assert(pMmio2->idRamRange == idRamRange);
+                pMmio2->GCPhys          = NIL_RTGCPHYS;
+                pMmio2->cbReal          = (RTGCPHYS)cPagesTrackedByChunk << GUEST_PAGE_SHIFT;
+                pMmio2->pPhysHandlerR3  = NIL_RTR3PTR; /* Pre-alloc is done by ring-3 caller. */
+                pMmio2->paLSPages       = NIL_RTR3PTR;
+
+#if defined(IN_RING0) && !defined(VBOX_WITH_LINEAR_HOST_PHYS_MEM)
+                pVM->pgmr0.s.apbMmio2Backing[idx] = &pbMmio2BackingR0[offMmio2Backing];
+#endif
+
+                /* Advance */
+                cGuestPagesLeft -= cPagesTrackedByChunk;
+                offMmio2Backing += (RTGCPHYS)cPagesTrackedByChunk << GUEST_PAGE_SHIFT;
+            } /* chunk alloc loop */
+            Assert(cGuestPagesLeft == 0 || RT_FAILURE_NP(rc));
+            if (RT_SUCCESS(rc))
+            {
+                /*
+                 * Account for pages and ring-0 memory objects.
+                 */
+                pVM->pgm.s.cAllPages     += cGuestPages;
+                pVM->pgm.s.cPrivatePages += cGuestPages;
+#ifdef IN_RING0
+                pVM->pgmr0.s.ahMmio2MemObjs[idMmio2 - 1] = hMemObj;
+                pVM->pgmr0.s.ahMmio2MapObjs[idMmio2 - 1] = hMapObj;
+#endif
+                pVM->pgm.s.cMmio2Ranges = idMmio2 + cChunks - 1U;
+
+                /*
+                 * Done!.
+                 */
+                return VINF_SUCCESS;
+            }
+
+            /*
+             * Bail.
+             */
+#ifdef IN_RING0
+            RTR0MemObjFree(hMapObj, true /*fFreeMappings*/);
+        }
+        RTR0MemObjFree(hMemObj, true /*fFreeMappings*/);
+#else
+        SUPR3PageFree(pbMmio2BackingR3, cHostPages);
+#endif
+    }
+    else
+        LogRel(("pgmPhysMmio2RegisterWorker: Failed to allocate %RGp bytes of MMIO2 backing memory: %Rrc\n", cbMmio2Aligned, rc));
+    return rc;
+}
+
+
+#ifdef IN_RING0
+/**
+ * This is called during VM initialization to create an MMIO2 range.
+ *
+ * This does everything except setting the PGMRAMRANGE::pszDesc to a non-zero
+ * value and preallocating the access handler for dirty bitmap tracking.
+ *
+ * The caller already knows which MMIO2 ID will be assigned to the registration
+ * and how many chunks it requires, so there are no output fields in the request
+ * structure.
+ *
+ * @returns VBox status code.
+ * @param   pGVM    Pointer to the global VM structure.
+ * @param   pReq    Where to get the parameters.
+ * @thread  EMT(0)
+ */
+VMMR0_INT_DECL(int) PGMR0PhysMmio2RegisterReq(PGVM pGVM, PPGMPHYSMMIO2REGISTERREQ pReq)
+{
+    /*
+     * Validate input (ASSUME pReq is a copy and can't be modified by ring-3
+     * while we're here).
+     */
+    AssertPtrReturn(pReq, VERR_INVALID_POINTER);
+    AssertMsgReturn(pReq->Hdr.cbReq == sizeof(*pReq), ("%#x < %#zx\n", pReq->Hdr.cbReq, sizeof(*pReq)), VERR_INVALID_PARAMETER);
+
+    /** @todo better VM state guard, enmVMState is ring-3 writable. */
+    VMSTATE const enmState = pGVM->enmVMState;
+    AssertMsgReturn(   enmState == VMSTATE_CREATING
+                    || enmState == VMSTATE_LOADING /* pre 4.3.6 state loading needs to ignore a MMIO2 region in PCNet. */
+                    , ("enmState=%d\n", enmState), VERR_VM_INVALID_VM_STATE);
+    VM_ASSERT_EMT0_RETURN(pGVM, VERR_VM_THREAD_NOT_EMT);
+
+    AssertReturn(pReq->cbGuestPage == GUEST_PAGE_SIZE, VERR_INCOMPATIBLE_CONFIG);
+    AssertReturn(GUEST_PAGE_SIZE == HOST_PAGE_SIZE, VERR_INCOMPATIBLE_CONFIG);
+
+    AssertReturn(pReq->cGuestPages > 0, VERR_OUT_OF_RANGE);
+    AssertReturn(pReq->cGuestPages <= PGM_MAX_PAGES_PER_MMIO2_REGION, VERR_OUT_OF_RANGE);
+    AssertReturn(pReq->cGuestPages <= (MM_MMIO_64_MAX >> GUEST_PAGE_SHIFT), VERR_OUT_OF_RANGE);
+
+    AssertMsgReturn(!(pReq->fFlags & ~PGMPHYS_MMIO2_FLAGS_VALID_MASK), ("fFlags=%#x\n", pReq->fFlags), VERR_INVALID_FLAGS);
+
+    AssertMsgReturn(   pReq->cChunks >  0
+                    && pReq->cChunks <  PGM_MAX_MMIO2_RANGES
+                    && pReq->cChunks == pgmPhysMmio2CalcChunkCount((RTGCPHYS)pReq->cGuestPages << GUEST_PAGE_SHIFT, NULL),
+                    ("cChunks=%#x cGuestPages=%#x\n", pReq->cChunks, pReq->cGuestPages),
+                    VERR_INVALID_PARAMETER);
+
+    AssertMsgReturn(   pReq->idMmio2 != 0
+                    && pReq->idMmio2 <= PGM_MAX_MMIO2_RANGES
+                    && (unsigned)pReq->idMmio2 + pReq->cChunks - 1U <= PGM_MAX_MMIO2_RANGES,
+                    ("idMmio2=%#x cChunks=%#x\n", pReq->idMmio2, pReq->cChunks),
+                    VERR_INVALID_PARAMETER);
+
+    for (uint32_t iChunk = 0, idx = pReq->idMmio2 - 1; iChunk < pReq->cChunks; iChunk++, idx++)
+    {
+        AssertReturn(pGVM->pgmr0.s.ahMmio2MapObjs[idx] == NIL_RTR0MEMOBJ, VERR_INVALID_STATE);
+        AssertReturn(pGVM->pgmr0.s.ahMmio2MemObjs[idx] == NIL_RTR0MEMOBJ, VERR_INVALID_STATE);
+        AssertReturn(pGVM->pgmr0.s.apMmio2RamRanges[idx] == NULL, VERR_INVALID_STATE);
+    }
+
+    /*
+     * Make sure we're owning the PGM lock (caller should be), recheck idMmio2
+     * and call the worker function we share with ring-3.
+     */
+    int rc = PGM_LOCK(pGVM);
+    AssertRCReturn(rc, rc);
+
+    AssertReturnStmt(pGVM->pgm.s.cMmio2Ranges + 1U == pReq->idMmio2,
+                     PGM_UNLOCK(pGVM), VERR_INVALID_PARAMETER);
+    AssertReturnStmt(pGVM->pgmr0.s.idRamRangeMax + 1U + pReq->cChunks <= RT_ELEMENTS(pGVM->pgmr0.s.apRamRanges),
+                     PGM_UNLOCK(pGVM), VERR_PGM_TOO_MANY_RAM_RANGES);
+
+    rc = pgmPhysMmio2RegisterWorker(pGVM, pReq->cGuestPages, pReq->idMmio2, pReq->cChunks,
+                                    pReq->pDevIns, pReq->iSubDev, pReq->iRegion, pReq->fFlags);
+
+    PGM_UNLOCK(pGVM);
+    return rc;
+}
+#endif /* IN_RING0 */
+
+
+
+/**
+ * Worker for PGMR3PhysMmio2Deregister & PGMR0PhysMmio2DeregisterReq.
+ */
+DECLHIDDEN(int) pgmPhysMmio2DeregisterWorker(PVMCC pVM, uint8_t idMmio2, uint8_t cChunks, PPDMDEVINSR3 pDevIns)
+{
+    /*
+     * The caller shall have made sure all this is true, but we check again
+     * since we're paranoid.
+     */
+    AssertReturn(idMmio2 > 0 && idMmio2 <= RT_ELEMENTS(pVM->pgm.s.aMmio2Ranges), VERR_INTERNAL_ERROR_2);
+    AssertReturn(cChunks >= 1, VERR_INTERNAL_ERROR_2);
+    uint8_t const idxFirst = idMmio2 - 1U;
+    AssertReturn(idxFirst + cChunks <= pVM->pgm.s.cMmio2Ranges, VERR_INTERNAL_ERROR_2);
+    uint32_t      cGuestPages = 0; /* (For accounting and calulating backing memory size) */
+    for (uint32_t iChunk = 0, idx = idxFirst; iChunk < cChunks; iChunk++, idx++)
+    {
+        AssertReturn(pVM->pgm.s.aMmio2Ranges[idx].pDevInsR3 == pDevIns, VERR_NOT_OWNER);
+        AssertReturn(!(pVM->pgm.s.aMmio2Ranges[idx].fFlags & PGMREGMMIO2RANGE_F_MAPPED), VERR_RESOURCE_BUSY);
+        AssertReturn(pVM->pgm.s.aMmio2Ranges[idx].GCPhys == NIL_RTGCPHYS, VERR_INVALID_STATE);
+        if (iChunk == 0)
+            AssertReturn(pVM->pgm.s.aMmio2Ranges[idx].fFlags & PGMREGMMIO2RANGE_F_FIRST_CHUNK, VERR_INVALID_PARAMETER);
+        else
+            AssertReturn(!(pVM->pgm.s.aMmio2Ranges[idx].fFlags & PGMREGMMIO2RANGE_F_FIRST_CHUNK), VERR_INVALID_PARAMETER);
+        if (iChunk + 1 == cChunks)
+            AssertReturn(pVM->pgm.s.aMmio2Ranges[idx].fFlags & PGMREGMMIO2RANGE_F_LAST_CHUNK, VERR_INVALID_PARAMETER);
+        else
+            AssertReturn(!(pVM->pgm.s.aMmio2Ranges[idx].fFlags & PGMREGMMIO2RANGE_F_LAST_CHUNK), VERR_INVALID_PARAMETER);
+        AssertReturn(pVM->pgm.s.aMmio2Ranges[idx].pPhysHandlerR3 == NIL_RTR3PTR, VERR_INVALID_STATE); /* caller shall free this */
+
+#ifdef IN_RING0
+        cGuestPages += pVM->pgmr0.s.acMmio2RangePages[idx];
+#else
+        cGuestPages += pVM->pgm.s.aMmio2Ranges[idx].cbReal >> GUEST_PAGE_SHIFT;
+#endif
+
+        PPGMRAMRANGE const pRamRange = pVM->CTX_EXPR(pgm, pgmr0, pgm).s.apMmio2RamRanges[idx];
+        AssertPtrReturn(pRamRange, VERR_INVALID_STATE);
+        AssertReturn(pRamRange->fFlags & PGM_RAM_RANGE_FLAGS_AD_HOC_MMIO_EX, VERR_INVALID_STATE);
+        AssertReturn(pRamRange->GCPhys     == NIL_RTGCPHYS, VERR_INVALID_STATE);
+        AssertReturn(pRamRange->GCPhysLast == NIL_RTGCPHYS, VERR_INVALID_STATE);
+    }
+
+    /*
+     * Remove everything except the backing memory first.  We work the ranges
+     * in reverse so that we can reduce the max RAM range ID when possible.
+     */
+#ifdef IN_RING3
+    uint8_t * const pbMmio2Backing = pVM->pgm.s.aMmio2Ranges[idxFirst].pbR3;
+    RTGCPHYS const  cbMmio2Backing = RT_ALIGN_T((RTGCPHYS)cGuestPages << GUEST_PAGE_SHIFT, HOST_PAGE_SIZE, RTGCPHYS);
+#endif
+
+    int      rc     = VINF_SUCCESS;
+    uint32_t iChunk = cChunks;
+    while (iChunk-- > 0)
+    {
+        uint32_t const     idx       = idxFirst + iChunk;
+        PPGMRAMRANGE const pRamRange = pVM->CTX_EXPR(pgm, pgmr0, pgm).s.apMmio2RamRanges[idx];
+
+        /* Zap the MMIO2 region data. */
+        pVM->pgm.s.apMmio2RamRanges[idx]    = NIL_RTR3PTR;
+#ifdef IN_RING0
+        pVM->pgmr0.s.apMmio2RamRanges[idx]  = NULL;
+        pVM->pgmr0.s.acMmio2RangePages[idx] = 0;
+#endif
+        pVM->pgm.s.aMmio2Ranges[idx].pDevInsR3      = NIL_RTR3PTR;
+        pVM->pgm.s.aMmio2Ranges[idx].pbR3           = NIL_RTR3PTR;
+        pVM->pgm.s.aMmio2Ranges[idx].fFlags         = 0;
+        pVM->pgm.s.aMmio2Ranges[idx].iSubDev        = UINT8_MAX;
+        pVM->pgm.s.aMmio2Ranges[idx].iRegion        = UINT8_MAX;
+        pVM->pgm.s.aMmio2Ranges[idx].idSavedState   = UINT8_MAX;
+        pVM->pgm.s.aMmio2Ranges[idx].idMmio2        = UINT8_MAX;
+        pVM->pgm.s.aMmio2Ranges[idx].idRamRange     = UINT16_MAX;
+        pVM->pgm.s.aMmio2Ranges[idx].GCPhys         = NIL_RTGCPHYS;
+        pVM->pgm.s.aMmio2Ranges[idx].cbReal         = 0;
+        pVM->pgm.s.aMmio2Ranges[idx].pPhysHandlerR3 = NIL_RTR3PTR;
+        pVM->pgm.s.aMmio2Ranges[idx].paLSPages      = NIL_RTR3PTR;
+
+        /* Free the RAM range. */
+        int rc2 = pgmPhysRamRangeFree(pVM, pRamRange);
+        AssertLogRelMsgStmt(RT_SUCCESS(rc2), ("rc=%Rrc idx=%u chunk=%u/%u\n", rc, idx, iChunk + 1, cChunks),
+                            rc = RT_SUCCESS(rc) ? rc2 : rc);
+    }
+
+    /*
+     * Final removal frees up the backing memory.
+     */
+#ifdef IN_RING3
+    int const rcBacking = SUPR3PageFree(pbMmio2Backing, cbMmio2Backing >> HOST_PAGE_SHIFT);
+    AssertLogRelMsgStmt(RT_SUCCESS(rcBacking), ("rc=%Rrc %p LB %#zx\n", rcBacking, pbMmio2Backing, cbMmio2Backing),
+                        rc = RT_SUCCESS(rc) ? rcBacking : rc);
+#else
+    int rcBacking = RTR0MemObjFree(pVM->pgmr0.s.ahMmio2MapObjs[idxFirst], true /*fFreeMappings*/);
+    AssertLogRelMsgStmt(RT_SUCCESS(rcBacking),
+                        ("rc=%Rrc ahMmio2MapObjs[%u]=%p\n", rcBacking, pVM->pgmr0.s.ahMmio2MapObjs[idxFirst], idxFirst),
+                        rc = RT_SUCCESS(rc) ? rcBacking : rc);
+    if (RT_SUCCESS(rcBacking))
+    {
+        pVM->pgmr0.s.ahMmio2MapObjs[idxFirst] = NIL_RTR0MEMOBJ;
+
+        rcBacking = RTR0MemObjFree(pVM->pgmr0.s.ahMmio2MemObjs[idxFirst], true /*fFreeMappings*/);
+        AssertLogRelMsgStmt(RT_SUCCESS(rcBacking),
+                            ("rc=%Rrc ahMmio2MemObjs[%u]=%p\n", rcBacking, pVM->pgmr0.s.ahMmio2MemObjs[idxFirst], idxFirst),
+                            rc = RT_SUCCESS(rc) ? rcBacking : rc);
+        if (RT_SUCCESS(rcBacking))
+            pVM->pgmr0.s.ahMmio2MemObjs[idxFirst] = NIL_RTR0MEMOBJ;
+    }
+#endif
+
+    /*
+     * Decrease the MMIO2 count if these were the last ones.
+     */
+    if (idxFirst + cChunks == pVM->pgm.s.cMmio2Ranges)
+        pVM->pgm.s.cMmio2Ranges = idxFirst;
+
+    /*
+     * Update page count stats.
+     */
+    pVM->pgm.s.cAllPages     -= cGuestPages;
+    pVM->pgm.s.cPrivatePages -= cGuestPages;
+
+    return rc;
+}
+
+
+#ifdef IN_RING0
+/**
+ * This is called during VM state loading to deregister an obsolete MMIO2 range.
+ *
+ * This does everything except TLB flushing and releasing the access handler.
+ * The ranges must be unmapped and wihtout preallocated access handlers.
+ *
+ * @returns VBox status code.
+ * @param   pGVM    Pointer to the global VM structure.
+ * @param   pReq    Where to get the parameters.
+ * @thread  EMT(0)
+ */
+VMMR0_INT_DECL(int) PGMR0PhysMmio2DeregisterReq(PGVM pGVM, PPGMPHYSMMIO2DEREGISTERREQ pReq)
+{
+    /*
+     * Validate input (ASSUME pReq is a copy and can't be modified by ring-3
+     * while we're here).
+     */
+    AssertPtrReturn(pReq, VERR_INVALID_POINTER);
+    AssertMsgReturn(pReq->Hdr.cbReq == sizeof(*pReq), ("%#x < %#zx\n", pReq->Hdr.cbReq, sizeof(*pReq)), VERR_INVALID_PARAMETER);
+
+    /** @todo better VM state guard, enmVMState is ring-3 writable. */
+    /* Only LOADING, as this is special purpose for removing an unwanted PCNet MMIO2 region. */
+    VMSTATE const enmState = pGVM->enmVMState;
+    AssertMsgReturn(enmState == VMSTATE_LOADING, ("enmState=%d\n", enmState), VERR_VM_INVALID_VM_STATE);
+    VM_ASSERT_EMT0_RETURN(pGVM, VERR_VM_THREAD_NOT_EMT);
+
+    AssertMsgReturn(   pReq->cChunks > 0
+                    && pReq->cChunks < PGM_MAX_MMIO2_RANGES,
+                    ("idMmio2=%#x cChunks=%#x\n", pReq->idMmio2, pReq->cChunks),
+                    VERR_INVALID_PARAMETER);
+
+    AssertMsgReturn(   pReq->idMmio2 != 0
+                    && pReq->idMmio2 <= PGM_MAX_MMIO2_RANGES
+                    && (unsigned)pReq->idMmio2 + pReq->cChunks - 1U <= PGM_MAX_MMIO2_RANGES,
+                    ("idMmio2=%#x cChunks=%#x\n", pReq->idMmio2, pReq->cChunks),
+                    VERR_INVALID_PARAMETER);
+
+    /*
+     * Validate that the requested range is for exactly one MMIO2 registration.
+     *
+     * This is safe to do w/o the lock because registration and deregistration
+     * is restricted to EMT0, and we're on EMT0 so can't race ourselves.
+     */
+
+    /* Check that the first entry is valid and has a memory object for the backing memory. */
+    uint32_t idx = pReq->idMmio2 - 1;
+    AssertReturn(pGVM->pgmr0.s.apMmio2RamRanges[idx]     != NULL, VERR_INVALID_STATE);
+    AssertReturn(pGVM->pgmr0.s.ahMmio2MemObjs[idx]       != NIL_RTR0MEMOBJ, VERR_INVALID_STATE);
+
+    /* Any additional regions must also have RAM ranges, but shall not have any backing memory. */
+    idx++;
+    for (uint32_t iChunk = 1; iChunk < pReq->cChunks; iChunk++, idx++)
+    {
+        AssertReturn(pGVM->pgmr0.s.apMmio2RamRanges[idx]     != NULL, VERR_INVALID_STATE);
+        AssertReturn(pGVM->pgmr0.s.ahMmio2MemObjs[idx]       == NIL_RTR0MEMOBJ, VERR_INVALID_STATE);
+    }
+
+    /* Check that the next entry is for a different region. */
+    AssertReturn(   idx >= RT_ELEMENTS(pGVM->pgmr0.s.apMmio2RamRanges)
+                 || pGVM->pgmr0.s.apMmio2RamRanges[idx] == NULL
+                 || pGVM->pgmr0.s.ahMmio2MemObjs[idx]   != NIL_RTR0MEMOBJ,
+                 VERR_INVALID_PARAMETER);
+
+    /*
+     * Make sure we're owning the PGM lock (caller should be) and call the
+     * common worker code.
+     */
+    int rc = PGM_LOCK(pGVM);
+    AssertRCReturn(rc, rc);
+
+    rc = pgmPhysMmio2DeregisterWorker(pGVM, pReq->idMmio2, pReq->cChunks, pReq->pDevIns);
+
+    PGM_UNLOCK(pGVM);
+    return rc;
+}
+#endif /* IN_RING0 */
+
+
+
+
+/*********************************************************************************************************************************
+*   ROM                                                                                                                          *
+*********************************************************************************************************************************/
+
+
+/**
+ * Common worker for pgmR3PhysRomRegisterLocked and
+ * PGMR0PhysRomAllocateRangeReq.
+ */
+DECLHIDDEN(int) pgmPhysRomRangeAllocCommon(PVMCC pVM, uint32_t cPages, uint8_t idRomRange, uint32_t fFlags)
+{
+    /*
+     * Allocate the ROM range structure and map it into ring-3.
+     */
+    size_t const cbRomRange = RT_ALIGN_Z(RT_UOFFSETOF_DYN(PGMROMRANGE, aPages[cPages]), HOST_PAGE_SIZE);
+#ifdef IN_RING0
+    RTR0MEMOBJ   hMemObj    = NIL_RTR0MEMOBJ;
+    int rc = RTR0MemObjAllocPage(&hMemObj, cbRomRange, false /*fExecutable*/);
+#else
+    PPGMROMRANGE pRomRange;
+    int rc = SUPR3PageAlloc(cbRomRange >> HOST_PAGE_SHIFT, 0 /*fFlags*/, (void **)&pRomRange);
+#endif
+    if (RT_SUCCESS(rc))
+    {
+        /* Zero the memory and do basic range init before mapping it into userland. */
+#ifdef IN_RING0
+        PPGMROMRANGE const pRomRange = (PPGMROMRANGE)RTR0MemObjAddress(hMemObj);
+        if (!RTR0MemObjWasZeroInitialized(hMemObj))
+#endif
+            RT_BZERO(pRomRange, cbRomRange);
+
+        pRomRange->GCPhys       = NIL_RTGCPHYS;
+        pRomRange->GCPhysLast   = NIL_RTGCPHYS;
+        pRomRange->cb           = (RTGCPHYS)cPages << GUEST_PAGE_SHIFT;
+        pRomRange->fFlags       = fFlags;
+        pRomRange->idSavedState = UINT8_MAX;
+        pRomRange->idRamRange   = UINT16_MAX;
+        pRomRange->cbOriginal   = 0;
+        pRomRange->pvOriginal   = NIL_RTR3PTR;
+        pRomRange->pszDesc      = NIL_RTR3PTR;
+
+#ifdef IN_RING0
+        /* Map it into userland. */
+        RTR0MEMOBJ hMapObj  = NIL_RTR0MEMOBJ;
+        rc = RTR0MemObjMapUser(&hMapObj, hMemObj, (RTR3PTR)-1, 0 /*uAlignment*/,
+                               RTMEM_PROT_READ | RTMEM_PROT_WRITE, NIL_RTR0PROCESS);
+        if (RT_SUCCESS(rc))
+#endif
+        {
+            /*
+             * Grab the lock (unlikely to fail or block as caller typically owns it already).
+             */
+            rc = PGM_LOCK(pVM);
+            if (RT_SUCCESS(rc))
+            {
+                /*
+                 * Check that idRomRange is still free.
+                 */
+                if (idRomRange < RT_ELEMENTS(pVM->CTX_EXPR(pgm, pgmr0, pgm).s.apRomRanges))
+                {
+#ifdef IN_RING0
+                    if (pVM->pgmr0.s.apRomRanges[idRomRange] == NULL)
+#endif
+                    {
+                        if (   pVM->pgm.s.apRomRanges[idRomRange] == NIL_RTR3PTR
+                            && pVM->pgm.s.cRomRanges              == idRomRange)
+                        {
+                            /*
+                             * Commit it.
+                             */
+#ifdef IN_RING0
+                            pVM->pgmr0.s.apRomRanges[idRomRange]       = pRomRange;
+                            pVM->pgmr0.s.acRomRangePages[idRomRange]   = cPages;
+                            pVM->pgmr0.s.ahRomRangeMemObjs[idRomRange] = hMemObj;
+                            pVM->pgmr0.s.ahRomRangeMapObjs[idRomRange] = hMapObj;
+#endif
+
+                            pVM->pgm.s.cRomRanges                      = idRomRange + 1;
+#ifdef IN_RING0
+                            pVM->pgm.s.apRomRanges[idRomRange]         = RTR0MemObjAddressR3(hMapObj);
+#else
+                            pVM->pgm.s.apRomRanges[idRomRange]         = pRomRange;
+#endif
+
+                            PGM_UNLOCK(pVM);
+                            return VINF_SUCCESS;
+                        }
+                    }
+
+                    /*
+                     * Bail out.
+                     */
+                    rc = VERR_INTERNAL_ERROR_5;
+                }
+                else
+                    rc = VERR_PGM_TOO_MANY_ROM_RANGES;
+                PGM_UNLOCK(pVM);
+            }
+#ifdef IN_RING0
+            RTR0MemObjFree(hMapObj, false /*fFreeMappings*/);
+#endif
+        }
+#ifdef IN_RING0
+        RTR0MemObjFree(hMemObj, true /*fFreeMappings*/);
+#else
+        SUPR3PageFree(pRomRange, cbRomRange >> HOST_PAGE_SHIFT);
+#endif
+    }
+    return rc;
+}
+
+
+#ifdef IN_RING0
+/**
+ * This is called during VM initialization to allocate a ROM range.
+ *
+ * The page array is zeroed, the rest is initialized as best we can based on the
+ * information in @a pReq.
+ *
+ * @returns VBox status code.
+ * @param   pGVM    Pointer to the global VM structure.
+ * @param   pReq    Where to get the parameters and return the range ID.
+ * @thread  EMT(0)
+ */
+VMMR0_INT_DECL(int) PGMR0PhysRomAllocateRangeReq(PGVM pGVM, PPGMPHYSROMALLOCATERANGEREQ pReq)
+{
+    /*
+     * Validate input (ASSUME pReq is a copy and can't be modified by ring-3
+     * while we're here).
+     */
+    AssertPtrReturn(pReq, VERR_INVALID_POINTER);
+    AssertMsgReturn(pReq->Hdr.cbReq == sizeof(*pReq), ("%#x < %#zx\n", pReq->Hdr.cbReq, sizeof(*pReq)), VERR_INVALID_PARAMETER);
+
+    AssertReturn(pReq->cbGuestPage == GUEST_PAGE_SIZE, VERR_INCOMPATIBLE_CONFIG);
+
+    AssertReturn(pReq->cGuestPages > 0, VERR_OUT_OF_RANGE);
+    AssertReturn(pReq->cGuestPages <= PGM_MAX_PAGES_PER_ROM_RANGE, VERR_OUT_OF_RANGE);
+
+    AssertMsgReturn(!(pReq->fFlags & ~(uint32_t)PGMPHYS_ROM_FLAGS_VALID_MASK), ("fFlags=%#RX32\n", pReq->fFlags),
+                    VERR_INVALID_FLAGS);
+
+    AssertReturn(pReq->idRomRange < RT_ELEMENTS(pGVM->pgmr0.s.apRomRanges), VERR_OUT_OF_RANGE);
+    AssertReturn(pReq->idRomRange == pGVM->pgm.s.cRomRanges, VERR_OUT_OF_RANGE);
+
+    /** @todo better VM state guard, enmVMState is ring-3 writable. */
+    VMSTATE const enmState = pGVM->enmVMState;
+    AssertMsgReturn(enmState == VMSTATE_CREATING, ("enmState=%d\n", enmState), VERR_VM_INVALID_VM_STATE);
+    VM_ASSERT_EMT0_RETURN(pGVM, VERR_VM_THREAD_NOT_EMT);
+
+    /*
+     * Call common worker.
+     */
+    return pgmPhysRomRangeAllocCommon(pGVM, pReq->cGuestPages, pReq->idRomRange, pReq->fFlags);
+}
+#endif /* IN_RING0 */
+
+
+/*********************************************************************************************************************************
+*   Other stuff
+*********************************************************************************************************************************/
+
 
 
@@ -1403,37 +2555,47 @@
 
     /*
-     * Special cases: MMIO2, ZERO and specially aliased MMIO pages.
+     * Special cases: MMIO2 and specially aliased MMIO pages.
      */
     if (   PGM_PAGE_GET_TYPE(pPage) == PGMPAGETYPE_MMIO2
         || PGM_PAGE_GET_TYPE(pPage) == PGMPAGETYPE_MMIO2_ALIAS_MMIO)
     {
+        *ppMap = NULL;
+
         /* Decode the page id to a page in a MMIO2 ram range. */
-        uint8_t  idMmio2 = PGM_MMIO2_PAGEID_GET_MMIO2_ID(PGM_PAGE_GET_PAGEID(pPage));
-        uint32_t iPage   = PGM_MMIO2_PAGEID_GET_IDX(PGM_PAGE_GET_PAGEID(pPage));
-        AssertLogRelMsgReturn((uint8_t)(idMmio2 - 1U) < RT_ELEMENTS(pVM->pgm.s.CTX_SUFF(apMmio2Ranges)),
+        uint8_t const  idMmio2 = PGM_MMIO2_PAGEID_GET_MMIO2_ID(PGM_PAGE_GET_PAGEID(pPage));
+        uint32_t const iPage   = PGM_MMIO2_PAGEID_GET_IDX(PGM_PAGE_GET_PAGEID(pPage));
+        AssertLogRelMsgReturn((uint8_t)(idMmio2 - 1U) < RT_ELEMENTS(pVM->pgm.s.aMmio2Ranges),
                               ("idMmio2=%u size=%u type=%u GCPHys=%#RGp Id=%u State=%u", idMmio2,
-                               RT_ELEMENTS(pVM->pgm.s.CTX_SUFF(apMmio2Ranges)), PGM_PAGE_GET_TYPE(pPage), GCPhys,
+                               RT_ELEMENTS(pVM->pgm.s.aMmio2Ranges), PGM_PAGE_GET_TYPE(pPage), GCPhys,
                                pPage->s.idPage, pPage->s.uStateY),
                               VERR_PGM_PHYS_PAGE_MAP_MMIO2_IPE);
-        PPGMREGMMIO2RANGE pMmio2Range = pVM->pgm.s.CTX_SUFF(apMmio2Ranges)[idMmio2 - 1];
+        PPGMREGMMIO2RANGE const pMmio2Range = &pVM->pgm.s.aMmio2Ranges[idMmio2 - 1];
         AssertLogRelReturn(pMmio2Range, VERR_PGM_PHYS_PAGE_MAP_MMIO2_IPE);
         AssertLogRelReturn(pMmio2Range->idMmio2 == idMmio2, VERR_PGM_PHYS_PAGE_MAP_MMIO2_IPE);
-        AssertLogRelReturn(iPage < (pMmio2Range->RamRange.cb >> GUEST_PAGE_SHIFT), VERR_PGM_PHYS_PAGE_MAP_MMIO2_IPE);
-        *ppMap = NULL;
-# if   defined(IN_RING0) && defined(VBOX_WITH_LINEAR_HOST_PHYS_MEM)
+#ifndef IN_RING0
+        uint32_t const          idRamRange  = pMmio2Range->idRamRange;
+        AssertLogRelReturn(idRamRange < RT_ELEMENTS(pVM->pgm.s.apRamRanges), VERR_PGM_PHYS_PAGE_MAP_MMIO2_IPE);
+        PPGMRAMRANGE const      pRamRange   = pVM->CTX_EXPR(pgm, pgmr0, pgm).s.apRamRanges[idRamRange];
+        AssertLogRelReturn(pRamRange, VERR_PGM_PHYS_PAGE_MAP_MMIO2_IPE);
+        AssertLogRelReturn(iPage < (pRamRange->cb >> GUEST_PAGE_SHIFT), VERR_PGM_PHYS_PAGE_MAP_MMIO2_IPE);
+        *ppv = pMmio2Range->pbR3 + ((uintptr_t)iPage << GUEST_PAGE_SHIFT);
+        return VINF_SUCCESS;
+
+#else /* IN_RING0 */
+        AssertLogRelReturn(iPage < pVM->pgmr0.s.acMmio2RangePages[idMmio2 - 1], VERR_PGM_PHYS_PAGE_MAP_MMIO2_IPE);
+# ifdef VBOX_WITH_LINEAR_HOST_PHYS_MEM
         return SUPR0HCPhysToVirt(PGM_PAGE_GET_HCPHYS(pPage), ppv);
-# elif defined(IN_RING0)
-        *ppv = (uint8_t *)pMmio2Range->pvR0 + ((uintptr_t)iPage << GUEST_PAGE_SHIFT);
-        return VINF_SUCCESS;
 # else
-        *ppv = (uint8_t *)pMmio2Range->RamRange.pvR3 + ((uintptr_t)iPage << GUEST_PAGE_SHIFT);
+        AssertPtr(pVM->pgmr0.s.apbMmio2Backing[idMmio2 - 1]);
+        *ppv = pVM->pgmr0.s.apbMmio2Backing[idMmio2 - 1] + ((uintptr_t)iPage << GUEST_PAGE_SHIFT);
         return VINF_SUCCESS;
 # endif
-    }
-
-# ifdef VBOX_WITH_PGM_NEM_MODE
+#endif
+    }
+
+#ifdef VBOX_WITH_PGM_NEM_MODE
     if (pVM->pgm.s.fNemMode)
     {
-#  ifdef IN_RING3
+# ifdef IN_RING3
         /*
          * Find the corresponding RAM range and use that to locate the mapping address.
@@ -1445,11 +2607,11 @@
         Assert(pPage == &pRam->aPages[idxPage]);
         *ppMap = NULL;
-        *ppv   = (uint8_t *)pRam->pvR3 + (idxPage << GUEST_PAGE_SHIFT);
+        *ppv   = (uint8_t *)pRam->pbR3 + (idxPage << GUEST_PAGE_SHIFT);
         return VINF_SUCCESS;
-#  else
+# else
         AssertFailedReturn(VERR_INTERNAL_ERROR_2);
-#  endif
-    }
 # endif
+    }
+#endif /* VBOX_WITH_PGM_NEM_MODE */
 
     const uint32_t idChunk = PGM_PAGE_GET_CHUNKID(pPage);
@@ -1699,5 +2861,5 @@
     if (    PGM_PAGE_GET_TYPE(pPage) < PGMPAGETYPE_ROM_SHADOW
         ||  PGM_PAGE_GET_TYPE(pPage) > PGMPAGETYPE_ROM)
-        pTlbe->GCPhys = GCPhys & X86_PTE_PAE_PG_MASK;
+        pTlbe->GCPhys = GCPhys & ~(RTGCPHYS)GUEST_PAGE_OFFSET_MASK;
     else
         pTlbe->GCPhys = NIL_RTGCPHYS; /* ROM: Problematic because of the two pages. :-/ */
@@ -2606,7 +3768,8 @@
      */
     VBOXSTRICTRC rcStrict = VINF_SUCCESS;
-    PPGMRAMRANGE pRam = pgmPhysGetRangeAtOrAbove(pVM, GCPhys);
     for (;;)
     {
+        PPGMRAMRANGE const pRam = pgmPhysGetRangeAtOrAbove(pVM, GCPhys);
+
         /* Inside range or not? */
         if (pRam && GCPhys >= pRam->GCPhys)
@@ -2698,7 +3861,4 @@
         }
 
-        /* Advance range if necessary. */
-        while (pRam && GCPhys > pRam->GCPhysLast)
-            pRam = pRam->CTX_SUFF(pNext);
     } /* Ram range walk */
 
@@ -3013,7 +4173,8 @@
      */
     VBOXSTRICTRC rcStrict = VINF_SUCCESS;
-    PPGMRAMRANGE pRam = pgmPhysGetRangeAtOrAbove(pVM, GCPhys);
     for (;;)
     {
+        PPGMRAMRANGE const pRam = pgmPhysGetRangeAtOrAbove(pVM, GCPhys);
+
         /* Inside range or not? */
         if (pRam && GCPhys >= pRam->GCPhys)
@@ -3095,7 +4256,4 @@
         }
 
-        /* Advance range if necessary. */
-        while (pRam && GCPhys > pRam->GCPhysLast)
-            pRam = pRam->CTX_SUFF(pNext);
     } /* Ram range walk */
 
@@ -4139,8 +5297,19 @@
      */
     PGM_LOCK_VOID(pVM);
-    int rc = VINF_SUCCESS;
-    for (PPGMRAMRANGE pRam = pVM->pgm.s.CTX_SUFF(pRamRangesX); pRam; pRam = pRam->CTX_SUFF(pNext))
-    {
+    int            rc             = VINF_SUCCESS;
+    uint32_t const cLookupEntries = RT_MIN(pVM->pgm.s.RamRangeUnion.cLookupEntries, RT_ELEMENTS(pVM->pgm.s.aRamRangeLookup));
+    for (uint32_t idxLookup = 0; idxLookup < cLookupEntries && RT_SUCCESS(rc); idxLookup++)
+    {
+        uint32_t const idRamRange = PGMRAMRANGELOOKUPENTRY_GET_ID(pVM->pgm.s.aRamRangeLookup[idxLookup]);
+        AssertContinue(idRamRange < RT_ELEMENTS(pVM->CTX_EXPR(pgm, pgmr0, pgm).s.apRamRanges));
+        PPGMRAMRANGE const pRam = pVM->CTX_EXPR(pgm, pgmr0, pgm).s.apRamRanges[idRamRange];
+        AssertContinue(pRam);
+        Assert(pRam->GCPhys == PGMRAMRANGELOOKUPENTRY_GET_FIRST(pVM->pgm.s.aRamRangeLookup[idxLookup]));
+
+#ifdef IN_RING0
+        uint32_t const cPages = RT_MIN(pRam->cb >> X86_PAGE_SHIFT, pVM->pgmr0.s.acRamRangePages[idRamRange]);
+#else
         uint32_t const cPages = pRam->cb >> X86_PAGE_SHIFT;
+#endif
         for (uint32_t iPage = 0; iPage < cPages; iPage++)
         {
Index: trunk/src/VBox/VMM/VMMAll/PGMAllPool.cpp
===================================================================
--- trunk/src/VBox/VMM/VMMAll/PGMAllPool.cpp	(revision 104839)
+++ trunk/src/VBox/VMM/VMMAll/PGMAllPool.cpp	(revision 104840)
@@ -4510,10 +4510,12 @@
      */
     STAM_COUNTER_INC(&pPool->StatTrackLinearRamSearches);
-    PPGMRAMRANGE pRam = pPool->CTX_SUFF(pVM)->pgm.s.CTX_SUFF(pRamRangesX);
-    while (pRam)
-    {
+    uint32_t const idRamRangeMax = RT_MIN(pVM->pgm.s.idRamRangeMax, RT_ELEMENTS(pVM->pgm.s.apRamRanges) - 1U);
+    Assert(pVM->pgm.s.apRamRanges[0] == NULL);
+    for (uint32_t idx = 1; idx <= idRamRangeMax; idx++)
+    {
+        PPGMRAMRANGE const pRam = pVM->CTX_EXPR(pgm, pgmr0, pgm).s.apRamRanges[idx];
+        AssertContinue(pRam);
         unsigned iPage = pRam->cb >> PAGE_SHIFT;
         while (iPage-- > 0)
-        {
             if (PGM_PAGE_GET_HCPHYS(&pRam->aPages[iPage]) == HCPhys)
             {
@@ -4527,6 +4529,4 @@
                 return;
             }
-        }
-        pRam = pRam->CTX_SUFF(pNext);
     }
 
@@ -5795,8 +5795,10 @@
      * Clear all the GCPhys links and rebuild the phys ext free list.
      */
-    for (PPGMRAMRANGE pRam = pVM->pgm.s.CTX_SUFF(pRamRangesX);
-         pRam;
-         pRam = pRam->CTX_SUFF(pNext))
-    {
+    uint32_t const idRamRangeMax = RT_MIN(pVM->pgm.s.idRamRangeMax, RT_ELEMENTS(pVM->pgm.s.apRamRanges) - 1U);
+    Assert(pVM->pgm.s.apRamRanges[0] == NULL);
+    for (uint32_t idx = 1; idx <= idRamRangeMax; idx++)
+    {
+        PPGMRAMRANGE const pRam = pVM->CTX_EXPR(pgm, pgmr0, pgm).s.apRamRanges[idx];
+        AssertContinue(pRam);
         unsigned iPage = pRam->cb >> PAGE_SHIFT;
         while (iPage-- > 0)
Index: trunk/src/VBox/VMM/VMMR0/PGMR0.cpp
===================================================================
--- trunk/src/VBox/VMM/VMMR0/PGMR0.cpp	(revision 104839)
+++ trunk/src/VBox/VMM/VMMR0/PGMR0.cpp	(revision 104840)
@@ -88,4 +88,34 @@
     AssertCompile(sizeof(pGVM->pgmr0.s) <= sizeof(pGVM->pgmr0.padding));
 
+    /* Set the RAM range memory handles to NIL. */
+    AssertCompile(RT_ELEMENTS(pGVM->pgmr0.s.acRamRangePages)   == RT_ELEMENTS(pGVM->pgmr0.s.apRamRanges));
+    AssertCompile(RT_ELEMENTS(pGVM->pgmr0.s.ahRamRangeMemObjs) == RT_ELEMENTS(pGVM->pgmr0.s.apRamRanges));
+    AssertCompile(RT_ELEMENTS(pGVM->pgmr0.s.ahRamRangeMapObjs) == RT_ELEMENTS(pGVM->pgmr0.s.apRamRanges));
+    for (uint32_t i = 0; i < RT_ELEMENTS(pGVM->pgmr0.s.ahRamRangeMemObjs); i++)
+    {
+        pGVM->pgmr0.s.ahRamRangeMemObjs[i] = NIL_RTR0MEMOBJ;
+        pGVM->pgmr0.s.ahRamRangeMapObjs[i] = NIL_RTR0MEMOBJ;
+    }
+    Assert(pGVM->pgmr0.s.idRamRangeMax == 0); /* the structure is ZERO'ed */
+
+    /* Set the MMIO2 range memory handles to NIL. */
+    AssertCompile(RT_ELEMENTS(pGVM->pgmr0.s.ahMmio2MemObjs) == RT_ELEMENTS(pGVM->pgmr0.s.apMmio2RamRanges));
+    AssertCompile(RT_ELEMENTS(pGVM->pgmr0.s.ahMmio2MapObjs) == RT_ELEMENTS(pGVM->pgmr0.s.apMmio2RamRanges));
+    for (uint32_t i = 0; i < RT_ELEMENTS(pGVM->pgmr0.s.ahMmio2MemObjs); i++)
+    {
+        pGVM->pgmr0.s.ahMmio2MemObjs[i] = NIL_RTR0MEMOBJ;
+        pGVM->pgmr0.s.ahMmio2MapObjs[i] = NIL_RTR0MEMOBJ;
+    }
+
+    /* Set the ROM range memory handles to NIL. */
+    AssertCompile(RT_ELEMENTS(pGVM->pgmr0.s.ahRomRangeMemObjs) == RT_ELEMENTS(pGVM->pgmr0.s.apRomRanges));
+    AssertCompile(RT_ELEMENTS(pGVM->pgmr0.s.ahRomRangeMapObjs) == RT_ELEMENTS(pGVM->pgmr0.s.apRomRanges));
+    for (uint32_t i = 0; i < RT_ELEMENTS(pGVM->pgmr0.s.ahRomRangeMemObjs); i++)
+    {
+        pGVM->pgmr0.s.ahRomRangeMemObjs[i] = NIL_RTR0MEMOBJ;
+        pGVM->pgmr0.s.ahRomRangeMapObjs[i] = NIL_RTR0MEMOBJ;
+    }
+
+    /* Set the physical handler related memory handles to NIL. */
     AssertCompile(RT_ELEMENTS(pGVM->pgmr0.s.ahPoolMemObjs) == RT_ELEMENTS(pGVM->pgmr0.s.ahPoolMapObjs));
     for (uint32_t i = 0; i < RT_ELEMENTS(pGVM->pgmr0.s.ahPoolMemObjs); i++)
@@ -278,4 +308,56 @@
         AssertRC(rc);
         pGVM->pgmr0.s.hPhysHandlerMemObj = NIL_RTR0MEMOBJ;
+    }
+
+    for (uint32_t i = 0; i < RT_ELEMENTS(pGVM->pgmr0.s.ahRomRangeMemObjs); i++)
+    {
+        if (pGVM->pgmr0.s.ahRomRangeMapObjs[i] != NIL_RTR0MEMOBJ)
+        {
+            int rc = RTR0MemObjFree(pGVM->pgmr0.s.ahRomRangeMapObjs[i], true /*fFreeMappings*/);
+            AssertRC(rc);
+            pGVM->pgmr0.s.ahRomRangeMapObjs[i] = NIL_RTR0MEMOBJ;
+        }
+
+        if (pGVM->pgmr0.s.ahRomRangeMemObjs[i] != NIL_RTR0MEMOBJ)
+        {
+            int rc = RTR0MemObjFree(pGVM->pgmr0.s.ahRomRangeMemObjs[i], true /*fFreeMappings*/);
+            AssertRC(rc);
+            pGVM->pgmr0.s.ahRomRangeMemObjs[i] = NIL_RTR0MEMOBJ;
+        }
+    }
+
+    for (uint32_t i = 0; i < RT_ELEMENTS(pGVM->pgmr0.s.ahMmio2MemObjs); i++)
+    {
+        if (pGVM->pgmr0.s.ahMmio2MapObjs[i] != NIL_RTR0MEMOBJ)
+        {
+            int rc = RTR0MemObjFree(pGVM->pgmr0.s.ahMmio2MapObjs[i], true /*fFreeMappings*/);
+            AssertRC(rc);
+            pGVM->pgmr0.s.ahMmio2MapObjs[i] = NIL_RTR0MEMOBJ;
+        }
+
+        if (pGVM->pgmr0.s.ahMmio2MemObjs[i] != NIL_RTR0MEMOBJ)
+        {
+            int rc = RTR0MemObjFree(pGVM->pgmr0.s.ahMmio2MemObjs[i], true /*fFreeMappings*/);
+            AssertRC(rc);
+            pGVM->pgmr0.s.ahMmio2MemObjs[i] = NIL_RTR0MEMOBJ;
+        }
+    }
+
+    uint32_t const cRangesMax = RT_MIN(pGVM->pgmr0.s.idRamRangeMax, RT_ELEMENTS(pGVM->pgmr0.s.ahRamRangeMemObjs) - 1U) + 1U;
+    for (uint32_t i = 0; i < cRangesMax; i++)
+    {
+        if (pGVM->pgmr0.s.ahRamRangeMapObjs[i] != NIL_RTR0MEMOBJ)
+        {
+            int rc = RTR0MemObjFree(pGVM->pgmr0.s.ahRamRangeMapObjs[i], true /*fFreeMappings*/);
+            AssertRC(rc);
+            pGVM->pgmr0.s.ahRamRangeMapObjs[i] = NIL_RTR0MEMOBJ;
+        }
+
+        if (pGVM->pgmr0.s.ahRamRangeMemObjs[i] != NIL_RTR0MEMOBJ)
+        {
+            int rc = RTR0MemObjFree(pGVM->pgmr0.s.ahRamRangeMemObjs[i], true /*fFreeMappings*/);
+            AssertRC(rc);
+            pGVM->pgmr0.s.ahRamRangeMemObjs[i] = NIL_RTR0MEMOBJ;
+        }
     }
 
@@ -710,5 +792,5 @@
  * @param   hMmio2      Handle to look up.
  */
-DECLINLINE(PPGMREGMMIO2RANGE) pgmR0PhysMmio2Find(PGVM pGVM, PPDMDEVINS pDevIns, PGMMMIO2HANDLE hMmio2)
+DECLINLINE(int32_t) pgmR0PhysMmio2ValidateHandle(PGVM pGVM, PPDMDEVINS pDevIns, PGMMMIO2HANDLE hMmio2)
 {
     /*
@@ -716,15 +798,11 @@
      * ring-3 pointers and this probably will require some kind of refactoring anyway.
      */
-    if (hMmio2 <= RT_ELEMENTS(pGVM->pgm.s.apMmio2RangesR0) && hMmio2 != 0)
-    {
-        PPGMREGMMIO2RANGE pCur = pGVM->pgm.s.apMmio2RangesR0[hMmio2 - 1];
-        if (pCur && pCur->pDevInsR3 == pDevIns->pDevInsForR3)
-        {
-            Assert(pCur->idMmio2 == hMmio2);
-            return pCur;
-        }
-        Assert(!pCur);
-    }
-    return NULL;
+    AssertReturn(hMmio2 <= RT_ELEMENTS(pGVM->pgm.s.aMmio2Ranges) && hMmio2 != 0, VERR_INVALID_HANDLE);
+    uint32_t const idx = hMmio2 - 1U;
+    AssertReturn(pGVM->pgm.s.aMmio2Ranges[idx].pDevInsR3 == pDevIns->pDevInsForR3, VERR_NOT_OWNER);
+    AssertReturn(pGVM->pgm.s.aMmio2Ranges[idx].idMmio2 == hMmio2, VERR_INVALID_HANDLE);
+    AssertReturn(pGVM->pgmr0.s.ahMmio2MapObjs[idx] != NIL_RTR0MEMOBJ, VERR_INVALID_HANDLE);
+    AssertReturn(pGVM->pgmr0.s.acMmio2RangePages[idx] != 0, VERR_INVALID_HANDLE);
+    return idx;
 }
 
@@ -744,19 +822,20 @@
                                             size_t offSub, size_t cbSub, void **ppvMapping)
 {
+    *ppvMapping = NULL;
     AssertReturn(!(offSub & HOST_PAGE_OFFSET_MASK), VERR_UNSUPPORTED_ALIGNMENT);
     AssertReturn(!(cbSub  & HOST_PAGE_OFFSET_MASK), VERR_UNSUPPORTED_ALIGNMENT);
 
     /*
-     * Translate hRegion into a range pointer.
-     */
-    PPGMREGMMIO2RANGE pFirstRegMmio = pgmR0PhysMmio2Find(pGVM, pDevIns, hMmio2);
-    AssertReturn(pFirstRegMmio, VERR_NOT_FOUND);
+     * Validate and translate hMmio2 into an MMIO2 index.
+     */
+    uint32_t const   idxFirst = pgmR0PhysMmio2ValidateHandle(pGVM, pDevIns, hMmio2);
+    AssertReturn((int32_t)idxFirst >= 0, (int32_t)idxFirst);
+
 #ifndef VBOX_WITH_LINEAR_HOST_PHYS_MEM
-    uint8_t * const pvR0  = (uint8_t *)pFirstRegMmio->pvR0;
+    uint8_t * const  pbR0     = pGVM->pgmr0.s.apbMmio2Backing[idxFirst];
 #else
-    RTR3PTR const  pvR3   = pFirstRegMmio->pvR3;
+    RTR0MEMOBJ const hMemObj  = pGVM->pgmr0.s.ahMmio2MemObjs[idxFirst];
 #endif
-    RTGCPHYS const cbReal = pFirstRegMmio->cbReal;
-    pFirstRegMmio = NULL;
+    RTGCPHYS const   cbReal   = (RTGCPHYS)pGVM->pgmr0.s.acMmio2RangePages[idxFirst] << GUEST_PAGE_SHIFT;
     ASMCompilerBarrier();
 
@@ -767,13 +846,22 @@
         AssertReturn(cbSub < cbReal && cbSub + offSub <= cbReal, VERR_OUT_OF_RANGE);
 
-    /*
-     * Do the mapping.
-     */
 #ifndef VBOX_WITH_LINEAR_HOST_PHYS_MEM
-    AssertPtr(pvR0);
-    *ppvMapping = pvR0 + offSub;
+    /*
+     * Just return the address of the existing ring-0 mapping.
+     */
+    AssertPtrReturn(pbR0, VERR_INTERNAL_ERROR_4);
+    *ppvMapping = &pbR0[offSub];
     return VINF_SUCCESS;
 #else
-    return SUPR0PageMapKernel(pGVM->pSession, pvR3, (uint32_t)offSub, (uint32_t)cbSub, 0 /*fFlags*/, ppvMapping);
+    /*
+     * Call IPRT to do the mapping.  Cleanup is done indirectly by telling
+     * RTR0MemObjFree to include mappings.  It can only be done once, so no
+     * risk of excessive mapping leaks.
+     */
+    RTR0MEMOBJ hMapObj;
+    int rc = RTR0MemObjMapKernelEx(&hMapObj, hMemObj, (void *)-1, 0, RTMEM_PROT_READ | RTMEM_PROT_WRITE, offSub, cbSub);
+    if (RT_SUCCESS(rc))
+        *ppvMapping = RTR0MemObjAddress(hMapObj);
+    return rc;
 #endif
 }
@@ -1058,4 +1146,5 @@
 
 #ifdef VBOX_WITH_PCI_PASSTHROUGH
+# error fixme
     if (pGVM->pgm.s.fPciPassthrough)
     {
Index: trunk/src/VBox/VMM/VMMR0/VMMR0.cpp
===================================================================
--- trunk/src/VBox/VMM/VMMR0/VMMR0.cpp	(revision 104839)
+++ trunk/src/VBox/VMM/VMMR0/VMMR0.cpp	(revision 104840)
@@ -1920,4 +1920,28 @@
             break;
 
+        case VMMR0_DO_PGM_PHYS_ALLOCATE_RAM_RANGE:
+            if (idCpu != 0 || u64Arg)
+                return VERR_INVALID_PARAMETER;
+            rc = PGMR0PhysAllocateRamRangeReq(pGVM, (PPGMPHYSALLOCATERAMRANGEREQ)pReqHdr);
+            break;
+
+        case VMMR0_DO_PGM_PHYS_MMIO2_REGISTER:
+            if (idCpu != 0 || u64Arg)
+                return VERR_INVALID_PARAMETER;
+            rc = PGMR0PhysMmio2RegisterReq(pGVM, (PPGMPHYSMMIO2REGISTERREQ)pReqHdr);
+            break;
+
+        case VMMR0_DO_PGM_PHYS_MMIO2_DEREGISTER:
+            if (idCpu != 0 || u64Arg)
+                return VERR_INVALID_PARAMETER;
+            rc = PGMR0PhysMmio2DeregisterReq(pGVM, (PPGMPHYSMMIO2DEREGISTERREQ)pReqHdr);
+            break;
+
+        case VMMR0_DO_PGM_PHYS_ROM_ALLOCATE_RANGE:
+            if (idCpu != 0 || u64Arg)
+                return VERR_INVALID_PARAMETER;
+            rc = PGMR0PhysRomAllocateRangeReq(pGVM, (PPGMPHYSROMALLOCATERANGEREQ)pReqHdr);
+            break;
+
         /*
          * GMM wrappers.
Index: trunk/src/VBox/VMM/VMMR3/IOMR3Mmio.cpp
===================================================================
--- trunk/src/VBox/VMM/VMMR3/IOMR3Mmio.cpp	(revision 104839)
+++ trunk/src/VBox/VMM/VMMR3/IOMR3Mmio.cpp	(revision 104840)
@@ -275,5 +275,6 @@
     AssertPtrReturn(phRegion, VERR_INVALID_POINTER);
     *phRegion = UINT32_MAX;
-    VM_ASSERT_EMT0_RETURN(pVM, VERR_VM_THREAD_NOT_EMT);
+    PVMCPU const pVCpu = VMMGetCpu(pVM);
+    AssertReturn(pVCpu && pVCpu->idCpu == 0, VERR_VM_THREAD_NOT_EMT);
     VM_ASSERT_STATE_RETURN(pVM, VMSTATE_CREATING, VERR_VM_INVALID_VM_STATE);
     AssertReturn(!pVM->iom.s.fMmioFrozen, VERR_WRONG_ORDER);
@@ -324,4 +325,11 @@
         AssertReturn(idx == pVM->iom.s.cMmioRegs, VERR_IOM_MMIO_IPE_1);
     }
+
+    /*
+     * Create a matching ad-hoc RAM range for this MMIO region.
+     */
+    uint16_t idRamRange = 0;
+    int rc = PGMR3PhysMmioRegister(pVM, pVCpu, cbRegion, pszDesc, &idRamRange);
+    AssertRCReturn(rc,  rc);
 
     /*
@@ -341,4 +349,5 @@
     pVM->iom.s.paMmioRegs[idx].fMapped            = false;
     pVM->iom.s.paMmioRegs[idx].fFlags             = fFlags;
+    pVM->iom.s.paMmioRegs[idx].idRamRange         = idRamRange;
     pVM->iom.s.paMmioRegs[idx].idxSelf            = idx;
 
@@ -409,6 +418,6 @@
                         /* Register with PGM before we shuffle the array: */
                         ASMAtomicWriteU64(&pRegEntry->GCPhysMapping, GCPhys);
-                        rc = PGMR3PhysMmioRegister(pVM, pVCpu, GCPhys, cbRegion, pVM->iom.s.hNewMmioHandlerType,
-                                                   hRegion, pRegEntry->pszDesc);
+                        rc = PGMR3PhysMmioMap(pVM, pVCpu, GCPhys, cbRegion, pRegEntry->idRamRange,
+                                              pVM->iom.s.hNewMmioHandlerType, hRegion);
                         AssertRCReturnStmt(rc, ASMAtomicWriteU64(&pRegEntry->GCPhysMapping, NIL_RTGCPHYS); IOM_UNLOCK_EXCL(pVM), rc);
 
@@ -428,6 +437,6 @@
                         /* Register with PGM before we shuffle the array: */
                         ASMAtomicWriteU64(&pRegEntry->GCPhysMapping, GCPhys);
-                        rc = PGMR3PhysMmioRegister(pVM, pVCpu, GCPhys, cbRegion, pVM->iom.s.hNewMmioHandlerType,
-                                                   hRegion, pRegEntry->pszDesc);
+                        rc = PGMR3PhysMmioMap(pVM, pVCpu, GCPhys, cbRegion, pRegEntry->idRamRange,
+                                              pVM->iom.s.hNewMmioHandlerType, hRegion);
                         AssertRCReturnStmt(rc, ASMAtomicWriteU64(&pRegEntry->GCPhysMapping, NIL_RTGCPHYS); IOM_UNLOCK_EXCL(pVM), rc);
 
@@ -455,5 +464,6 @@
             /* First entry in the lookup table: */
             ASMAtomicWriteU64(&pRegEntry->GCPhysMapping, GCPhys);
-            rc = PGMR3PhysMmioRegister(pVM, pVCpu, GCPhys, cbRegion, pVM->iom.s.hNewMmioHandlerType, hRegion, pRegEntry->pszDesc);
+            rc = PGMR3PhysMmioMap(pVM, pVCpu, GCPhys, cbRegion, pRegEntry->idRamRange,
+                                  pVM->iom.s.hNewMmioHandlerType, hRegion);
             AssertRCReturnStmt(rc, ASMAtomicWriteU64(&pRegEntry->GCPhysMapping, NIL_RTGCPHYS); IOM_UNLOCK_EXCL(pVM), rc);
 
@@ -572,5 +582,5 @@
                 pVM->iom.s.cMmioLookupEntries = cEntries - 1;
 
-                rc = PGMR3PhysMmioDeregister(pVM, pVCpu, GCPhys, pRegEntry->cbRegion);
+                rc = PGMR3PhysMmioUnmap(pVM, pVCpu, GCPhys, pRegEntry->cbRegion, pRegEntry->idRamRange);
                 AssertRC(rc);
 
@@ -622,4 +632,5 @@
 {
     RT_NOREF(pVM, pDevIns, hRegion, cbRegion);
+    AssertFailed();
     return VERR_NOT_IMPLEMENTED;
 }
Index: trunk/src/VBox/VMM/VMMR3/NEMR3NativeTemplate-linux.cpp.h
===================================================================
--- trunk/src/VBox/VMM/VMMR3/NEMR3NativeTemplate-linux.cpp.h	(revision 104839)
+++ trunk/src/VBox/VMM/VMMR3/NEMR3NativeTemplate-linux.cpp.h	(revision 104840)
@@ -927,4 +927,6 @@
      * notification, unless we're replacing RAM).
      */
+    /** @todo r=bird: if it's overlapping RAM, we shouldn't need an additional
+     *        registration, should we? */
     struct kvm_userspace_memory_region Region;
     Region.slot             = idSlot;
Index: trunk/src/VBox/VMM/VMMR3/PDM.cpp
===================================================================
--- trunk/src/VBox/VMM/VMMR3/PDM.cpp	(revision 104839)
+++ trunk/src/VBox/VMM/VMMR3/PDM.cpp	(revision 104840)
@@ -857,5 +857,7 @@
         pdmR3ThreadDestroyDevice(pVM, pDevIns);
         PDMR3QueueDestroyDevice(pVM, pDevIns);
+#if 0
         PGMR3PhysMmio2Deregister(pVM, pDevIns, NIL_PGMMMIO2HANDLE);
+#endif
 #ifdef VBOX_WITH_PDM_ASYNC_COMPLETION
         pdmR3AsyncCompletionTemplateDestroyDevice(pVM, pDevIns);
Index: trunk/src/VBox/VMM/VMMR3/PDMDevHlp.cpp
===================================================================
--- trunk/src/VBox/VMM/VMMR3/PDMDevHlp.cpp	(revision 104839)
+++ trunk/src/VBox/VMM/VMMR3/PDMDevHlp.cpp	(revision 104840)
@@ -289,9 +289,5 @@
     AssertReturn(!pPciDev || pPciDev->Int.s.pDevInsR3 == pDevIns, VERR_INVALID_PARAMETER);
 
-    PVM pVM = pDevIns->Internal.s.pVMR3;
-    VM_ASSERT_EMT0_RETURN(pVM, VERR_VM_THREAD_NOT_EMT);
-    AssertMsgReturn(   pVM->enmVMState == VMSTATE_CREATING
-                    || pVM->enmVMState == VMSTATE_LOADING,
-                    ("state %s, expected CREATING or LOADING\n", VMGetStateName(pVM->enmVMState)), VERR_VM_INVALID_VM_STATE);
+    PVM const pVM = pDevIns->Internal.s.pVMR3;
 
     AssertReturn(!(iPciRegion & UINT16_MAX), VERR_INVALID_PARAMETER); /* not implemented. */
@@ -314,10 +310,4 @@
     VM_ASSERT_EMT(pDevIns->Internal.s.pVMR3);
     LogFlow(("pdmR3DevHlp_Mmio2Destroy: caller='%s'/%d: hRegion=%#RX64\n", pDevIns->pReg->szName, pDevIns->iInstance, hRegion));
-
-    PVM pVM = pDevIns->Internal.s.pVMR3;
-    VM_ASSERT_EMT_RETURN(pVM, VERR_VM_THREAD_NOT_EMT);
-    AssertMsgReturn(   pVM->enmVMState == VMSTATE_DESTROYING
-                    || pVM->enmVMState == VMSTATE_LOADING,
-                    ("state %s, expected DESTROYING or LOADING\n", VMGetStateName(pVM->enmVMState)), VERR_VM_INVALID_VM_STATE);
 
     int rc = PGMR3PhysMmio2Deregister(pDevIns->Internal.s.pVMR3, pDevIns, hRegion);
Index: trunk/src/VBox/VMM/VMMR3/PGM.cpp
===================================================================
--- trunk/src/VBox/VMM/VMMR3/PGM.cpp	(revision 104839)
+++ trunk/src/VBox/VMM/VMMR3/PGM.cpp	(revision 104840)
@@ -1845,10 +1845,4 @@
 
     /*
-     * Ram ranges.
-     */
-    if (pVM->pgm.s.pRamRangesXR3)
-        pgmR3PhysRelinkRamRanges(pVM);
-
-    /*
      * The page pool.
      */
@@ -2121,5 +2115,5 @@
 
 /**
- * Dump registered MMIO ranges to the log.
+ * Display the RAM range info.
  *
  * @param   pVM         The cross context VM structure.
@@ -2136,13 +2130,24 @@
                     pVM,
                     sizeof(RTGCPHYS) * 4 + 1, "GC Phys Range                    ",
-                    sizeof(RTHCPTR) * 2,      "pvHC            ");
-
-    for (PPGMRAMRANGE pCur = pVM->pgm.s.pRamRangesXR3; pCur; pCur = pCur->pNextR3)
-    {
+                    sizeof(RTHCPTR) * 2,      "pbR3            ");
+
+    /*
+     * Traverse the lookup table so we only display mapped MMIO and get it in sorted order.
+     */
+    uint32_t const cRamRangeLookupEntries = RT_MIN(pVM->pgm.s.RamRangeUnion.cLookupEntries,
+                                                   RT_ELEMENTS(pVM->pgm.s.aRamRangeLookup));
+    for (uint32_t idxLookup = 0; idxLookup < cRamRangeLookupEntries; idxLookup++)
+    {
+        uint32_t const idRamRange = PGMRAMRANGELOOKUPENTRY_GET_ID(pVM->pgm.s.aRamRangeLookup[idxLookup]);
+        AssertContinue(idRamRange < RT_ELEMENTS(pVM->pgm.s.apRamRanges));
+        PPGMRAMRANGE const pCur = pVM->pgm.s.apRamRanges[idRamRange];
+        if (pCur != NULL)   { /*likely*/ }
+        else                continue;
+
         pHlp->pfnPrintf(pHlp,
                         "%RGp-%RGp %RHv %s\n",
                         pCur->GCPhys,
                         pCur->GCPhysLast,
-                        pCur->pvR3,
+                        pCur->pbR3,
                         pCur->pszDesc);
         if (fVerbose)
@@ -2182,10 +2187,17 @@
                         pszType = enmType == PGMPAGETYPE_ROM_SHADOW ? "ROM-shadowed" : "ROM";
 
-                        RTGCPHYS const  GCPhysFirstPg = iFirstPage * X86_PAGE_SIZE;
-                        PPGMROMRANGE    pRom          = pVM->pgm.s.pRomRangesR3;
-                        while (pRom && GCPhysFirstPg > pRom->GCPhysLast)
-                            pRom = pRom->pNextR3;
-                        if (pRom && GCPhysFirstPg - pRom->GCPhys < pRom->cb)
-                            pszMore = pRom->pszDesc;
+                        RTGCPHYS const GCPhysFirstPg = iFirstPage << GUEST_PAGE_SHIFT;
+                        uint32_t const cRomRanges    = RT_MIN(pVM->pgm.s.cRomRanges, RT_ELEMENTS(pVM->pgm.s.apRomRanges));
+                        for (uint32_t idxRom = 0; idxRom < cRomRanges; idxRom++)
+                        {
+                            PPGMROMRANGE const pRomRange = pVM->pgm.s.apRomRanges[idxRom];
+                            if (   pRomRange
+                                && GCPhysFirstPg <  pRomRange->GCPhysLast
+                                && GCPhysFirstPg >= pRomRange->GCPhys)
+                            {
+                                pszMore = pRomRange->pszDesc;
+                                break;
+                            }
+                        }
                         break;
                     }
@@ -2540,8 +2552,17 @@
 
     PGM_LOCK_VOID(pVM);
-    for (PPGMRAMRANGE pRam = pVM->pgm.s.pRamRangesXR3;
-         pRam && pRam->GCPhys < GCPhysEnd && RT_SUCCESS(rc);
-         pRam = pRam->pNextR3)
-    {
+
+    uint32_t const cRamRangeLookupEntries = RT_MIN(pVM->pgm.s.RamRangeUnion.cLookupEntries,
+                                                   RT_ELEMENTS(pVM->pgm.s.aRamRangeLookup));
+    for (uint32_t idxLookup = 0; idxLookup < cRamRangeLookupEntries && RT_SUCCESS(rc); idxLookup++)
+    {
+        if (PGMRAMRANGELOOKUPENTRY_GET_FIRST(pVM->pgm.s.aRamRangeLookup[idxLookup]) >= GCPhysEnd)
+            break;
+        uint32_t const idRamRange = PGMRAMRANGELOOKUPENTRY_GET_ID(pVM->pgm.s.aRamRangeLookup[idxLookup]);
+        AssertContinue(idRamRange < RT_ELEMENTS(pVM->pgm.s.apRamRanges));
+        PPGMRAMRANGE const pRam = pVM->pgm.s.apRamRanges[idRamRange];
+        AssertContinue(pRam);
+        Assert(pRam->GCPhys == PGMRAMRANGELOOKUPENTRY_GET_FIRST(pVM->pgm.s.aRamRangeLookup[idxLookup]));
+
         /* fill the gap */
         if (pRam->GCPhys > GCPhys && fIncZeroPgs)
Index: trunk/src/VBox/VMM/VMMR3/PGMDbg.cpp
===================================================================
--- trunk/src/VBox/VMM/VMMR3/PGMDbg.cpp	(revision 104839)
+++ trunk/src/VBox/VMM/VMMR3/PGMDbg.cpp	(revision 104840)
@@ -204,5 +204,6 @@
 {
     UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE);
-    VM_ASSERT_VALID_EXT_RETURN(pUVM->pVM, VERR_INVALID_VM_HANDLE);
+    PVM const pVM = pUVM->pVM;
+    VM_ASSERT_VALID_EXT_RETURN(pVM, VERR_INVALID_VM_HANDLE);
 
     /*
@@ -216,8 +217,13 @@
         return VERR_INVALID_POINTER;
 
-    for (PPGMRAMRANGE pRam = pUVM->pVM->pgm.s.CTX_SUFF(pRamRangesX);
-         pRam;
-         pRam = pRam->CTX_SUFF(pNext))
-    {
+    uint32_t const cLookupEntries = RT_MIN(pVM->pgm.s.RamRangeUnion.cLookupEntries, RT_ELEMENTS(pVM->pgm.s.aRamRangeLookup));
+    for (uint32_t idxLookup = 0; idxLookup < cLookupEntries; idxLookup++)
+    {
+        uint32_t const idRamRange = PGMRAMRANGELOOKUPENTRY_GET_ID(pVM->pgm.s.aRamRangeLookup[idxLookup]);
+        AssertContinue(idRamRange < RT_ELEMENTS(pVM->pgm.s.apRamRanges));
+        PPGMRAMRANGE const pRam = pVM->pgm.s.apRamRanges[idRamRange];
+        if (pRam != NULL)   { /*likely*/ }
+        else                continue;
+
         uint32_t iPage = pRam->cb >> GUEST_PAGE_SHIFT;
         while (iPage-- > 0)
@@ -830,8 +836,14 @@
      */
     PGM_LOCK_VOID(pVM);
-    for (PPGMRAMRANGE pRam = pVM->pgm.s.CTX_SUFF(pRamRangesX);
-         pRam;
-         pRam = pRam->CTX_SUFF(pNext))
-    {
+    uint32_t const cLookupEntries = RT_MIN(pVM->pgm.s.RamRangeUnion.cLookupEntries, RT_ELEMENTS(pVM->pgm.s.aRamRangeLookup));
+/** @todo binary search the start address.   */
+    for (uint32_t idxLookup = 0; idxLookup < cLookupEntries; idxLookup++)
+    {
+        uint32_t const idRamRange = PGMRAMRANGELOOKUPENTRY_GET_ID(pVM->pgm.s.aRamRangeLookup[idxLookup]);
+        AssertContinue(idRamRange < RT_ELEMENTS(pVM->pgm.s.apRamRanges));
+        PPGMRAMRANGE const pRam = pVM->pgm.s.apRamRanges[idRamRange];
+        if (pRam != NULL)   { /*likely*/ }
+        else                continue;
+
         /*
          * If the search range starts prior to the current ram range record,
@@ -3274,5 +3286,5 @@
     LOG_PGM_MEMBER("RTbool",        fPageFusionAllowed);
     LOG_PGM_MEMBER("RTbool",        fPciPassthrough);
-    LOG_PGM_MEMBER("#x",            cMmio2Regions);
+    LOG_PGM_MEMBER("#x",            cMmio2Ranges);
     LOG_PGM_MEMBER("RTbool",        fRestoreRomPagesOnReset);
     LOG_PGM_MEMBER("RTbool",        fZeroRamPagesOnReset);
Index: trunk/src/VBox/VMM/VMMR3/PGMPhys.cpp
===================================================================
--- trunk/src/VBox/VMM/VMMR3/PGMPhys.cpp	(revision 104839)
+++ trunk/src/VBox/VMM/VMMR3/PGMPhys.cpp	(revision 104840)
@@ -138,7 +138,8 @@
      * Copy loop on ram ranges.
      */
-    PPGMRAMRANGE pRam = pgmPhysGetRangeAtOrAbove(pVM, GCPhys);
     for (;;)
     {
+        PPGMRAMRANGE pRam = pgmPhysGetRangeAtOrAbove(pVM, GCPhys);
+
         /* Inside range or not? */
         if (pRam && GCPhys >= pRam->GCPhys)
@@ -219,8 +220,4 @@
             GCPhys += cb;
         }
-
-        /* Advance range if necessary. */
-        while (pRam && GCPhys > pRam->GCPhysLast)
-            pRam = pRam->CTX_SUFF(pNext);
     } /* Ram range walk */
 
@@ -274,7 +271,8 @@
      * Copy loop on ram ranges, stop when we hit something difficult.
      */
-    PPGMRAMRANGE pRam = pgmPhysGetRangeAtOrAbove(pVM, GCPhys);
     for (;;)
     {
+        PPGMRAMRANGE const pRam = pgmPhysGetRangeAtOrAbove(pVM, GCPhys);
+
         /* Inside range or not? */
         if (pRam && GCPhys >= pRam->GCPhys)
@@ -358,8 +356,4 @@
             GCPhys += cb;
         }
-
-        /* Advance range if necessary. */
-        while (pRam && GCPhys > pRam->GCPhysLast)
-            pRam = pRam->CTX_SUFF(pNext);
     } /* Ram range walk */
 
@@ -1004,271 +998,443 @@
 *********************************************************************************************************************************/
 
-#define MAKE_LEAF(a_pNode) \
-    do { \
-        (a_pNode)->pLeftR3  = NIL_RTR3PTR; \
-        (a_pNode)->pRightR3 = NIL_RTR3PTR; \
-        (a_pNode)->pLeftR0  = NIL_RTR0PTR; \
-        (a_pNode)->pRightR0 = NIL_RTR0PTR; \
-    } while (0)
-
-#define INSERT_LEFT(a_pParent, a_pNode) \
-    do { \
-        (a_pParent)->pLeftR3 = (a_pNode); \
-        (a_pParent)->pLeftR0 = (a_pNode)->pSelfR0; \
-    } while (0)
-#define INSERT_RIGHT(a_pParent, a_pNode) \
-    do { \
-        (a_pParent)->pRightR3 = (a_pNode); \
-        (a_pParent)->pRightR0 = (a_pNode)->pSelfR0; \
-    } while (0)
-
-
-/**
- * Recursive tree builder.
- *
- * @param   ppRam           Pointer to the iterator variable.
- * @param   iDepth          The current depth.  Inserts a leaf node if 0.
- */
-static PPGMRAMRANGE pgmR3PhysRebuildRamRangeSearchTreesRecursively(PPGMRAMRANGE *ppRam, int iDepth)
-{
-    PPGMRAMRANGE pRam;
-    if (iDepth <= 0)
-    {
-        /*
-         * Leaf node.
-         */
-        pRam = *ppRam;
-        if (pRam)
-        {
-            *ppRam = pRam->pNextR3;
-            MAKE_LEAF(pRam);
-        }
-    }
+/**
+ * Given the range @a GCPhys thru @a GCPhysLast, find overlapping RAM range or
+ * the correct insertion point.
+ *
+ * @returns Pointer to overlapping RAM range if found, NULL if not.
+ * @param   pVM         The cross context VM structure.
+ * @param   GCPhys      The address of the first byte in the range.
+ * @param   GCPhysLast  The address of the last byte in the range.
+ * @param   pidxInsert  Where to return the lookup table index to insert the
+ *                      range at when returning NULL.  Set to UINT32_MAX when
+ *                      returning the pointer to an overlapping range.
+ * @note    Caller must own the PGM lock.
+ */
+static PPGMRAMRANGE pgmR3PhysRamRangeFindOverlapping(PVM pVM, RTGCPHYS GCPhys, RTGCPHYS GCPhysLast, uint32_t *pidxInsert)
+{
+    PGM_LOCK_ASSERT_OWNER(pVM);
+    uint32_t iStart = 0;
+    uint32_t iEnd   = pVM->pgm.s.RamRangeUnion.cLookupEntries;
+    for (;;)
+    {
+        uint32_t idxLookup = iStart + (iEnd - iStart) / 2;
+        RTGCPHYS const GCPhysEntryFirst = PGMRAMRANGELOOKUPENTRY_GET_FIRST(pVM->pgm.s.aRamRangeLookup[idxLookup]);
+        if (GCPhysLast < GCPhysEntryFirst)
+        {
+            if (idxLookup > iStart)
+                iEnd = idxLookup;
+            else
+            {
+                *pidxInsert = idxLookup;
+                return NULL;
+            }
+        }
+        else
+        {
+            RTGCPHYS const GCPhysEntryLast = pVM->pgm.s.aRamRangeLookup[idxLookup].GCPhysLast;
+            if (GCPhys > GCPhysEntryLast)
+            {
+                idxLookup += 1;
+                if (idxLookup < iEnd)
+                    iStart = idxLookup;
+                else
+                {
+                    *pidxInsert = idxLookup;
+                    return NULL;
+                }
+            }
+            else
+            {
+                /* overlap */
+                Assert(GCPhysEntryLast > GCPhys && GCPhysEntryFirst < GCPhysLast);
+                *pidxInsert = UINT32_MAX;
+                return pVM->pgm.s.apRamRanges[PGMRAMRANGELOOKUPENTRY_GET_ID(pVM->pgm.s.aRamRangeLookup[idxLookup])];
+            }
+        }
+    }
+}
+
+
+/**
+ * Given the range @a GCPhys thru @a GCPhysLast, find the lookup table entry
+ * that's overlapping it.
+ *
+ * @returns The lookup table index of the overlapping entry, UINT32_MAX if not
+ *          found.
+ * @param   pVM         The cross context VM structure.
+ * @param   GCPhys      The address of the first byte in the range.
+ * @param   GCPhysLast  The address of the last byte in the range.
+ * @note    Caller must own the PGM lock.
+ */
+static uint32_t pgmR3PhysRamRangeFindOverlappingIndex(PVM pVM, RTGCPHYS GCPhys, RTGCPHYS GCPhysLast)
+{
+    PGM_LOCK_ASSERT_OWNER(pVM);
+    uint32_t iStart = 0;
+    uint32_t iEnd   = pVM->pgm.s.RamRangeUnion.cLookupEntries;
+    for (;;)
+    {
+        uint32_t idxLookup = iStart + (iEnd - iStart) / 2;
+        RTGCPHYS const GCPhysEntryFirst = PGMRAMRANGELOOKUPENTRY_GET_FIRST(pVM->pgm.s.aRamRangeLookup[idxLookup]);
+        if (GCPhysLast < GCPhysEntryFirst)
+        {
+            if (idxLookup > iStart)
+                iEnd = idxLookup;
+            else
+                return UINT32_MAX;
+        }
+        else
+        {
+            RTGCPHYS const GCPhysEntryLast = pVM->pgm.s.aRamRangeLookup[idxLookup].GCPhysLast;
+            if (GCPhys > GCPhysEntryLast)
+            {
+                idxLookup += 1;
+                if (idxLookup < iEnd)
+                    iStart = idxLookup;
+                else
+                    return UINT32_MAX;
+            }
+            else
+            {
+                /* overlap */
+                Assert(GCPhysEntryLast > GCPhys && GCPhysEntryFirst < GCPhysLast);
+                return idxLookup;
+            }
+        }
+    }
+}
+
+
+/**
+ * Insert @a pRam into the lookup table.
+ *
+ * @returns VBox status code.
+ * @param   pVM         The cross context VM structure.
+ * @param   pRam        The RAM range to insert into the lookup table.
+ * @param   pidxLookup  Optional lookup table hint. This is updated.
+ * @note    Caller must own PGM lock.
+ */
+static int pgmR3PhysRamRangeInsertLookup(PVM pVM, PPGMRAMRANGE pRam, RTGCPHYS GCPhys, uint32_t *pidxLookup)
+{
+    PGM_LOCK_ASSERT_OWNER(pVM);
+#ifdef DEBUG_bird
+    pgmPhysAssertRamRangesLocked(pVM, false /*fInUpdate*/, true /*fRamRelaxed*/);
+#endif
+    AssertMsg(pRam->pszDesc, ("%RGp-%RGp\n", pRam->GCPhys, pRam->GCPhysLast));
+    AssertLogRelMsgReturn(   pRam->GCPhys     == NIL_RTGCPHYS
+                          && pRam->GCPhysLast == NIL_RTGCPHYS,
+                          ("GCPhys=%RGp; range: GCPhys=%RGp LB %RGp GCPhysLast=%RGp %s\n",
+                           GCPhys, pRam->GCPhys, pRam->cb, pRam->GCPhysLast, pRam->pszDesc),
+                          VERR_ALREADY_EXISTS);
+    uint32_t const idRamRange = pRam->idRange;
+    AssertReturn(pVM->pgm.s.apRamRanges[idRamRange] == pRam, VERR_INTERNAL_ERROR_2);
+
+    AssertReturn(!(GCPhys & GUEST_PAGE_OFFSET_MASK), VERR_INTERNAL_ERROR_3);
+    RTGCPHYS const GCPhysLast = GCPhys + pRam->cb - 1U;
+    AssertReturn(GCPhysLast > GCPhys, VERR_INTERNAL_ERROR_4);
+    LogFlowFunc(("GCPhys=%RGp LB %RGp GCPhysLast=%RGp id=%#x %s\n", GCPhys, pRam->cb, GCPhysLast, idRamRange, pRam->pszDesc));
+
+    /*
+     * Find the lookup table location if necessary.
+     */
+    uint32_t const cLookupEntries = pVM->pgm.s.RamRangeUnion.cLookupEntries;
+    AssertLogRelMsgReturn(cLookupEntries + 1 < RT_ELEMENTS(pVM->pgm.s.aRamRangeLookup), /* id=0 is unused, so < is correct. */
+                          ("%#x\n", cLookupEntries), VERR_INTERNAL_ERROR_3);
+
+    uint32_t idxLookup = pidxLookup ? *pidxLookup : UINT32_MAX;
+    if (cLookupEntries == 0)
+        idxLookup = 0; /* special case: empty table */
     else
     {
-
-        /*
-         * Intermediate node.
-         */
-        PPGMRAMRANGE pLeft = pgmR3PhysRebuildRamRangeSearchTreesRecursively(ppRam, iDepth - 1);
-
-        pRam = *ppRam;
-        if (!pRam)
-            return pLeft;
-        *ppRam = pRam->pNextR3;
-        MAKE_LEAF(pRam);
-        INSERT_LEFT(pRam, pLeft);
-
-        PPGMRAMRANGE pRight = pgmR3PhysRebuildRamRangeSearchTreesRecursively(ppRam, iDepth - 1);
-        if (pRight)
-            INSERT_RIGHT(pRam, pRight);
-    }
-    return pRam;
-}
-
-
-/**
- * Rebuilds the RAM range search trees.
- *
+        if (   idxLookup > cLookupEntries
+             || (   idxLookup != 0
+                 && pVM->pgm.s.aRamRangeLookup[idxLookup - 1].GCPhysLast >= GCPhys)
+             || (   idxLookup < cLookupEntries
+                 && PGMRAMRANGELOOKUPENTRY_GET_FIRST(pVM->pgm.s.aRamRangeLookup[idxLookup]) < GCPhysLast))
+        {
+            PPGMRAMRANGE pOverlapping = pgmR3PhysRamRangeFindOverlapping(pVM, GCPhys, GCPhysLast, &idxLookup);
+            AssertLogRelMsgReturn(!pOverlapping,
+                                  ("GCPhys=%RGp; GCPhysLast=%RGp %s - overlaps %RGp...%RGp %s\n",
+                                   GCPhys, GCPhysLast, pRam->pszDesc,
+                                   pOverlapping->GCPhys, pOverlapping->GCPhysLast, pOverlapping->pszDesc),
+                                  VERR_PGM_RAM_CONFLICT);
+            AssertLogRelMsgReturn(idxLookup <= cLookupEntries, ("%#x vs %#x\n", idxLookup, cLookupEntries), VERR_INTERNAL_ERROR_5);
+        }
+        /* else we've got a good hint. */
+    }
+
+    /*
+     * Do the actual job.
+     *
+     * The moving of existing table entries is done in a way that allows other
+     * EMTs to perform concurrent lookups with the updating.
+     */
+    bool const fUseAtomic = pVM->enmVMState != VMSTATE_CREATING
+                         && pVM->cCpus > 1
+#ifdef RT_ARCH_AMD64
+                         && g_CpumHostFeatures.s.fCmpXchg16b
+#endif
+                          ;
+
+    /* Signal that we're modifying the lookup table: */
+    uint32_t const idGeneration = (pVM->pgm.s.RamRangeUnion.idGeneration + 1) | 1; /* paranoia^3 */
+    ASMAtomicWriteU32(&pVM->pgm.s.RamRangeUnion.idGeneration, idGeneration);
+
+    /* Update the RAM range entry. */
+    pRam->GCPhys     = GCPhys;
+    pRam->GCPhysLast = GCPhysLast;
+
+    /* Do we need to shift any lookup table entries? */
+    if (idxLookup != cLookupEntries)
+    {
+        /* We do.  Make a copy of the final entry first. */
+        uint32_t                cToMove = cLookupEntries - idxLookup;
+        PGMRAMRANGELOOKUPENTRY *pCur = &pVM->pgm.s.aRamRangeLookup[cLookupEntries];
+        pCur->GCPhysFirstAndId = pCur[-1].GCPhysFirstAndId;
+        pCur->GCPhysLast       = pCur[-1].GCPhysLast;
+
+        /* Then increase the table size.  This will ensure that anyone starting
+           a search from here on should have consistent data. */
+        ASMAtomicWriteU32(&pVM->pgm.s.RamRangeUnion.cLookupEntries, cLookupEntries + 1);
+
+        /* Transfer the rest of the entries. */
+        cToMove -= 1;
+        if (cToMove > 0)
+        {
+            if (!fUseAtomic)
+                do
+                {
+                    pCur    -= 1;
+                    pCur->GCPhysFirstAndId = pCur[-1].GCPhysFirstAndId;
+                    pCur->GCPhysLast       = pCur[-1].GCPhysLast;
+                    cToMove -= 1;
+                } while (cToMove > 0);
+            else
+            {
+#if RTASM_HAVE_WRITE_U128 >= 2
+                do
+                {
+                    pCur    -= 1;
+                    ASMAtomicWriteU128U(&pCur->u128Volatile, pCur[-1].u128Normal);
+                    cToMove -= 1;
+                } while (cToMove > 0);
+
+#else
+                uint64_t u64PrevLo = pCur[-1].u128Normal.s.Lo;
+                uint64_t u64PrevHi = pCur[-1].u128Normal.s.Hi;
+                do
+                {
+                    pCur    -= 1;
+                    uint64_t const u64CurLo = pCur[-1].u128Normal.s.Lo;
+                    uint64_t const u64CurHi = pCur[-1].u128Normal.s.Hi;
+                    uint128_t      uOldIgn;
+                    AssertStmt(ASMAtomicCmpXchgU128v2(&pCur->u128Volatile.u, u64CurHi, u64CurLo, u64PrevHi, u64PrevLo, &uOldIgn),
+                               (pCur->u128Volatile.s.Lo = u64CurLo, pCur->u128Volatile.s.Hi = u64CurHi));
+                    u64PrevLo = u64CurLo;
+                    u64PrevHi = u64CurHi;
+                    cToMove -= 1;
+                } while (cToMove > 0);
+#endif
+            }
+        }
+    }
+
+    /*
+     * Write the new entry.
+     */
+    PGMRAMRANGELOOKUPENTRY *pInsert = &pVM->pgm.s.aRamRangeLookup[idxLookup];
+    if (!fUseAtomic)
+    {
+        pInsert->GCPhysFirstAndId = idRamRange | GCPhys;
+        pInsert->GCPhysLast       = GCPhysLast;
+    }
+    else
+    {
+        PGMRAMRANGELOOKUPENTRY NewEntry;
+        NewEntry.GCPhysFirstAndId = idRamRange | GCPhys;
+        NewEntry.GCPhysLast       = GCPhysLast;
+        ASMAtomicWriteU128v2(&pInsert->u128Volatile.u, NewEntry.u128Normal.s.Hi, NewEntry.u128Normal.s.Lo);
+    }
+
+    /*
+     * Update the generation and count in one go, signaling the end of the updating.
+     */
+    PGM::PGMRAMRANGEGENANDLOOKUPCOUNT GenAndCount;
+    GenAndCount.cLookupEntries = cLookupEntries + 1;
+    GenAndCount.idGeneration   = idGeneration   + 1;
+    ASMAtomicWriteU64(&pVM->pgm.s.RamRangeUnion.u64Combined, GenAndCount.u64Combined);
+
+    if (pidxLookup)
+        *pidxLookup = idxLookup + 1;
+
+#ifdef DEBUG_bird
+    pgmPhysAssertRamRangesLocked(pVM, false /*fInUpdate*/, false /*fRamRelaxed*/);
+#endif
+    return VINF_SUCCESS;
+}
+
+
+/**
+ * Removes @a pRam from the lookup table.
+ *
+ * @returns VBox status code.
  * @param   pVM         The cross context VM structure.
- */
-static void pgmR3PhysRebuildRamRangeSearchTrees(PVM pVM)
-{
-
-    /*
-     * Create the reasonably balanced tree in a sequential fashion.
-     * For simplicity (laziness) we use standard recursion here.
-     */
-    int             iDepth = 0;
-    PPGMRAMRANGE    pRam   = pVM->pgm.s.pRamRangesXR3;
-    PPGMRAMRANGE    pRoot  = pgmR3PhysRebuildRamRangeSearchTreesRecursively(&pRam, 0);
-    while (pRam)
-    {
-        PPGMRAMRANGE pLeft = pRoot;
-
-        pRoot = pRam;
-        pRam = pRam->pNextR3;
-        MAKE_LEAF(pRoot);
-        INSERT_LEFT(pRoot, pLeft);
-
-        PPGMRAMRANGE pRight = pgmR3PhysRebuildRamRangeSearchTreesRecursively(&pRam, iDepth);
-        if (pRight)
-            INSERT_RIGHT(pRoot, pRight);
-        /** @todo else: rotate the tree. */
-
-        iDepth++;
-    }
-
-    pVM->pgm.s.pRamRangeTreeR3 = pRoot;
-    pVM->pgm.s.pRamRangeTreeR0 = pRoot ? pRoot->pSelfR0 : NIL_RTR0PTR;
-
-#ifdef VBOX_STRICT
-    /*
-     * Verify that the above code works.
-     */
-    unsigned cRanges = 0;
-    for (pRam = pVM->pgm.s.pRamRangesXR3; pRam; pRam = pRam->pNextR3)
-        cRanges++;
-    Assert(cRanges > 0);
-
-    unsigned cMaxDepth = ASMBitLastSetU32(cRanges);
-    if ((1U << cMaxDepth) < cRanges)
-        cMaxDepth++;
-
-    for (pRam = pVM->pgm.s.pRamRangesXR3; pRam; pRam = pRam->pNextR3)
-    {
-        unsigned     cDepth = 0;
-        PPGMRAMRANGE pRam2 = pVM->pgm.s.pRamRangeTreeR3;
+ * @param   pRam        The RAM range to insert into the lookup table.
+ * @param   pidxLookup  Optional lookup table hint. This is updated.
+ * @note    Caller must own PGM lock.
+ */
+static int pgmR3PhysRamRangeRemoveLookup(PVM pVM, PPGMRAMRANGE pRam, uint32_t *pidxLookup)
+{
+    PGM_LOCK_ASSERT_OWNER(pVM);
+    AssertMsg(pRam->pszDesc, ("%RGp-%RGp\n", pRam->GCPhys, pRam->GCPhysLast));
+
+    RTGCPHYS const GCPhys     = pRam->GCPhys;
+    RTGCPHYS const GCPhysLast = pRam->GCPhysLast;
+    AssertLogRelMsgReturn(   GCPhys     != NIL_RTGCPHYS
+                          || GCPhysLast != NIL_RTGCPHYS,
+                          ("range: GCPhys=%RGp LB %RGp GCPhysLast=%RGp %s\n", GCPhys, pRam->cb, GCPhysLast, pRam->pszDesc),
+                          VERR_NOT_FOUND);
+    AssertLogRelMsgReturn(   GCPhys     != NIL_RTGCPHYS
+                          && GCPhysLast == GCPhys + pRam->cb - 1U
+                          && (GCPhys     & GUEST_PAGE_OFFSET_MASK) == 0
+                          && (GCPhysLast & GUEST_PAGE_OFFSET_MASK) == GUEST_PAGE_OFFSET_MASK
+                          && GCPhysLast > GCPhys,
+                             ("range: GCPhys=%RGp LB %RGp GCPhysLast=%RGp %s\n", GCPhys, pRam->cb, GCPhysLast, pRam->pszDesc),
+                          VERR_INTERNAL_ERROR_5);
+    uint32_t const idRamRange = pRam->idRange;
+    AssertReturn(pVM->pgm.s.apRamRanges[idRamRange] == pRam, VERR_INTERNAL_ERROR_4);
+    LogFlowFunc(("GCPhys=%RGp LB %RGp GCPhysLast=%RGp id=%#x %s\n", GCPhys, pRam->cb, GCPhysLast, idRamRange, pRam->pszDesc));
+
+    /*
+     * Find the lookup table location.
+     */
+    uint32_t const cLookupEntries = pVM->pgm.s.RamRangeUnion.cLookupEntries;
+    AssertLogRelMsgReturn(   cLookupEntries > 0
+                          && cLookupEntries < RT_ELEMENTS(pVM->pgm.s.aRamRangeLookup), /* id=0 is unused, so < is correct. */
+                          ("%#x\n", cLookupEntries), VERR_INTERNAL_ERROR_3);
+
+    uint32_t idxLookup = pidxLookup ? *pidxLookup : UINT32_MAX;
+    if (   idxLookup >= cLookupEntries
+        || pVM->pgm.s.aRamRangeLookup[idxLookup].GCPhysLast       != GCPhysLast
+        || pVM->pgm.s.aRamRangeLookup[idxLookup].GCPhysFirstAndId != (GCPhys | idRamRange))
+    {
+        uint32_t iStart = 0;
+        uint32_t iEnd   = cLookupEntries;
         for (;;)
         {
-            if (pRam == pRam2)
-                break;
-            Assert(pRam2);
-            if (pRam->GCPhys < pRam2->GCPhys)
-                pRam2 = pRam2->pLeftR3;
+            idxLookup = iStart + (iEnd - iStart) / 2;
+            RTGCPHYS const GCPhysEntryFirst = PGMRAMRANGELOOKUPENTRY_GET_FIRST(pVM->pgm.s.aRamRangeLookup[idxLookup]);
+            if (GCPhysLast < GCPhysEntryFirst)
+            {
+                AssertLogRelMsgReturn(idxLookup > iStart,
+                                      ("range: GCPhys=%RGp LB %RGp GCPhysLast=%RGp %s\n",
+                                       GCPhys, pRam->cb, GCPhysLast, pRam->pszDesc),
+                                      VERR_NOT_FOUND);
+                iEnd = idxLookup;
+            }
             else
-                pRam2 = pRam2->pRightR3;
-        }
-        AssertMsg(cDepth <= cMaxDepth, ("cDepth=%d cMaxDepth=%d\n", cDepth, cMaxDepth));
-    }
-#endif /* VBOX_STRICT */
-}
-
-#undef MAKE_LEAF
-#undef INSERT_LEFT
-#undef INSERT_RIGHT
-
-/**
- * Relinks the RAM ranges using the pSelfRC and pSelfR0 pointers.
- *
- * Called when anything was relocated.
- *
- * @param   pVM         The cross context VM structure.
- */
-void pgmR3PhysRelinkRamRanges(PVM pVM)
-{
-    PPGMRAMRANGE pCur;
-
-#ifdef VBOX_STRICT
-    for (pCur = pVM->pgm.s.pRamRangesXR3; pCur; pCur = pCur->pNextR3)
-    {
-        Assert((pCur->GCPhys     & GUEST_PAGE_OFFSET_MASK) == 0);
-        Assert((pCur->GCPhysLast & GUEST_PAGE_OFFSET_MASK) == GUEST_PAGE_OFFSET_MASK);
-        Assert((pCur->cb         & GUEST_PAGE_OFFSET_MASK) == 0);
-        Assert(pCur->cb == pCur->GCPhysLast - pCur->GCPhys + 1);
-        for (PPGMRAMRANGE pCur2 = pVM->pgm.s.pRamRangesXR3; pCur2; pCur2 = pCur2->pNextR3)
-            Assert(   pCur2 == pCur
-                   || strcmp(pCur2->pszDesc, pCur->pszDesc)); /** @todo fix MMIO ranges!! */
-    }
-#endif
-
-    pCur = pVM->pgm.s.pRamRangesXR3;
-    if (pCur)
-    {
-        pVM->pgm.s.pRamRangesXR0 = pCur->pSelfR0;
-
-        for (; pCur->pNextR3; pCur = pCur->pNextR3)
-            pCur->pNextR0 = pCur->pNextR3->pSelfR0;
-
-        Assert(pCur->pNextR0 == NIL_RTR0PTR);
-    }
-    else
-    {
-        Assert(pVM->pgm.s.pRamRangesXR0 == NIL_RTR0PTR);
-    }
-    ASMAtomicIncU32(&pVM->pgm.s.idRamRangesGen);
-
-    pgmR3PhysRebuildRamRangeSearchTrees(pVM);
-}
-
-
-/**
- * Links a new RAM range into the list.
- *
- * @param   pVM         The cross context VM structure.
- * @param   pNew        Pointer to the new list entry.
- * @param   pPrev       Pointer to the previous list entry. If NULL, insert as head.
- */
-static void pgmR3PhysLinkRamRange(PVM pVM, PPGMRAMRANGE pNew, PPGMRAMRANGE pPrev)
-{
-    AssertMsg(pNew->pszDesc, ("%RGp-%RGp\n", pNew->GCPhys, pNew->GCPhysLast));
-
-    PGM_LOCK_VOID(pVM);
-
-    PPGMRAMRANGE pRam = pPrev ? pPrev->pNextR3 : pVM->pgm.s.pRamRangesXR3;
-    pNew->pNextR3 = pRam;
-    pNew->pNextR0 = pRam ? pRam->pSelfR0 : NIL_RTR0PTR;
-
-    if (pPrev)
-    {
-        pPrev->pNextR3 = pNew;
-        pPrev->pNextR0 = pNew->pSelfR0;
-    }
-    else
-    {
-        pVM->pgm.s.pRamRangesXR3 = pNew;
-        pVM->pgm.s.pRamRangesXR0 = pNew->pSelfR0;
-    }
-    ASMAtomicIncU32(&pVM->pgm.s.idRamRangesGen);
-
-    pgmR3PhysRebuildRamRangeSearchTrees(pVM);
-    PGM_UNLOCK(pVM);
-}
-
-
-/**
- * Unlink an existing RAM range from the list.
- *
- * @param   pVM         The cross context VM structure.
- * @param   pRam        Pointer to the new list entry.
- * @param   pPrev       Pointer to the previous list entry. If NULL, insert as head.
- */
-static void pgmR3PhysUnlinkRamRange2(PVM pVM, PPGMRAMRANGE pRam, PPGMRAMRANGE pPrev)
-{
-    Assert(pPrev ? pPrev->pNextR3 == pRam : pVM->pgm.s.pRamRangesXR3 == pRam);
-
-    PGM_LOCK_VOID(pVM);
-
-    PPGMRAMRANGE pNext = pRam->pNextR3;
-    if (pPrev)
-    {
-        pPrev->pNextR3 = pNext;
-        pPrev->pNextR0 = pNext ? pNext->pSelfR0 : NIL_RTR0PTR;
-    }
-    else
-    {
-        Assert(pVM->pgm.s.pRamRangesXR3 == pRam);
-        pVM->pgm.s.pRamRangesXR3 = pNext;
-        pVM->pgm.s.pRamRangesXR0 = pNext ? pNext->pSelfR0 : NIL_RTR0PTR;
-    }
-    ASMAtomicIncU32(&pVM->pgm.s.idRamRangesGen);
-
-    pgmR3PhysRebuildRamRangeSearchTrees(pVM);
-    PGM_UNLOCK(pVM);
-}
-
-
-/**
- * Unlink an existing RAM range from the list.
- *
- * @param   pVM         The cross context VM structure.
- * @param   pRam        Pointer to the new list entry.
- */
-static void pgmR3PhysUnlinkRamRange(PVM pVM, PPGMRAMRANGE pRam)
-{
-    PGM_LOCK_VOID(pVM);
-
-    /* find prev. */
-    PPGMRAMRANGE pPrev = NULL;
-    PPGMRAMRANGE pCur = pVM->pgm.s.pRamRangesXR3;
-    while (pCur != pRam)
-    {
-        pPrev = pCur;
-        pCur = pCur->pNextR3;
-    }
-    AssertFatal(pCur);
-
-    pgmR3PhysUnlinkRamRange2(pVM, pRam, pPrev);
-    PGM_UNLOCK(pVM);
+            {
+                RTGCPHYS const GCPhysEntryLast = pVM->pgm.s.aRamRangeLookup[idxLookup].GCPhysLast;
+                if (GCPhys > GCPhysEntryLast)
+                {
+                    idxLookup += 1;
+                    AssertLogRelMsgReturn(idxLookup < iEnd,
+                                          ("range: GCPhys=%RGp LB %RGp GCPhysLast=%RGp %s\n",
+                                           GCPhys, pRam->cb, GCPhysLast, pRam->pszDesc),
+                                          VERR_NOT_FOUND);
+                    iStart = idxLookup;
+                }
+                else
+                {
+                    uint32_t const idEntry = PGMRAMRANGELOOKUPENTRY_GET_ID(pVM->pgm.s.aRamRangeLookup[idxLookup]);
+                    AssertLogRelMsgReturn(   GCPhysEntryFirst == GCPhys
+                                          && GCPhysEntryLast  == GCPhysLast
+                                          && idEntry          == idRamRange,
+                                          ("Found: %RGp..%RGp id=%#x;  Wanted: GCPhys=%RGp LB %RGp GCPhysLast=%RGp id=%#x %s\n",
+                                           GCPhysEntryFirst, GCPhysEntryLast, idEntry,
+                                           GCPhys, pRam->cb, GCPhysLast, pRam->idRange, pRam->pszDesc),
+                                          VERR_NOT_FOUND);
+                    break;
+                }
+            }
+        }
+    }
+    /* else we've got a good hint. */
+
+    /*
+     * Do the actual job.
+     *
+     * The moving of existing table entries is done in a way that allows other
+     * EMTs to perform concurrent lookups with the updating.
+     */
+    bool const fUseAtomic = pVM->enmVMState != VMSTATE_CREATING
+                         && pVM->cCpus > 1
+#ifdef RT_ARCH_AMD64
+                         && g_CpumHostFeatures.s.fCmpXchg16b
+#endif
+                          ;
+
+    /* Signal that we're modifying the lookup table: */
+    uint32_t const idGeneration = (pVM->pgm.s.RamRangeUnion.idGeneration + 1) | 1; /* paranoia^3 */
+    ASMAtomicWriteU32(&pVM->pgm.s.RamRangeUnion.idGeneration, idGeneration);
+
+    /* Do we need to shift any lookup table entries? (This is a lot simpler
+       than insertion.) */
+    if (idxLookup + 1U < cLookupEntries)
+    {
+        uint32_t                cToMove = cLookupEntries - idxLookup - 1U;
+        PGMRAMRANGELOOKUPENTRY *pCur    = &pVM->pgm.s.aRamRangeLookup[idxLookup];
+        if (!fUseAtomic)
+            do
+            {
+                pCur->GCPhysFirstAndId = pCur[1].GCPhysFirstAndId;
+                pCur->GCPhysLast       = pCur[1].GCPhysLast;
+                pCur    += 1;
+                cToMove -= 1;
+            } while (cToMove > 0);
+        else
+        {
+#if RTASM_HAVE_WRITE_U128 >= 2
+            do
+            {
+                ASMAtomicWriteU128U(&pCur->u128Volatile, pCur[1].u128Normal);
+                pCur    += 1;
+                cToMove -= 1;
+            } while (cToMove > 0);
+
+#else
+            uint64_t u64PrevLo = pCur->u128Normal.s.Lo;
+            uint64_t u64PrevHi = pCur->u128Normal.s.Hi;
+            do
+            {
+                uint64_t const u64CurLo = pCur[1].u128Normal.s.Lo;
+                uint64_t const u64CurHi = pCur[1].u128Normal.s.Hi;
+                uint128_t      uOldIgn;
+                AssertStmt(ASMAtomicCmpXchgU128v2(&pCur->u128Volatile.u, u64CurHi, u64CurLo, u64PrevHi, u64PrevLo, &uOldIgn),
+                           (pCur->u128Volatile.s.Lo = u64CurLo, pCur->u128Volatile.s.Hi = u64CurHi));
+                u64PrevLo = u64CurLo;
+                u64PrevHi = u64CurHi;
+                pCur    += 1;
+                cToMove -= 1;
+            } while (cToMove > 0);
+#endif
+        }
+    }
+
+    /* Update the RAM range entry to indicate that it is no longer mapped. */
+    pRam->GCPhys     = NIL_RTGCPHYS;
+    pRam->GCPhysLast = NIL_RTGCPHYS;
+
+    /*
+     * Update the generation and count in one go, signaling the end of the updating.
+     */
+    PGM::PGMRAMRANGEGENANDLOOKUPCOUNT GenAndCount;
+    GenAndCount.cLookupEntries = cLookupEntries - 1;
+    GenAndCount.idGeneration   = idGeneration   + 1;
+    ASMAtomicWriteU64(&pVM->pgm.s.RamRangeUnion.u64Combined, GenAndCount.u64Combined);
+
+    if (pidxLookup)
+        *pidxLookup = idxLookup + 1;
+
+    return VINF_SUCCESS;
 }
 
@@ -1285,7 +1451,5 @@
 
     PGM_LOCK_VOID(pVM);
-    uint32_t cRamRanges = 0;
-    for (PPGMRAMRANGE pCur = pVM->pgm.s.CTX_SUFF(pRamRangesX); pCur; pCur = pCur->CTX_SUFF(pNext))
-        cRamRanges++;
+    uint32_t const cRamRanges = RT_MIN(pVM->pgm.s.RamRangeUnion.cLookupEntries, RT_ELEMENTS(pVM->pgm.s.aRamRangeLookup));
     PGM_UNLOCK(pVM);
     return cRamRanges;
@@ -1312,20 +1476,24 @@
 
     PGM_LOCK_VOID(pVM);
-    uint32_t iCurRange = 0;
-    for (PPGMRAMRANGE pCur = pVM->pgm.s.CTX_SUFF(pRamRangesX); pCur; pCur = pCur->CTX_SUFF(pNext), iCurRange++)
-        if (iCurRange == iRange)
-        {
-            if (pGCPhysStart)
-                *pGCPhysStart = pCur->GCPhys;
-            if (pGCPhysLast)
-                *pGCPhysLast  = pCur->GCPhysLast;
-            if (ppszDesc)
-                *ppszDesc     = pCur->pszDesc;
-            if (pfIsMmio)
-                *pfIsMmio     = !!(pCur->fFlags & PGM_RAM_RANGE_FLAGS_AD_HOC_MMIO);
-
-            PGM_UNLOCK(pVM);
-            return VINF_SUCCESS;
-        }
+    uint32_t const cLookupEntries = RT_MIN(pVM->pgm.s.RamRangeUnion.cLookupEntries, RT_ELEMENTS(pVM->pgm.s.aRamRangeLookup));
+    if (iRange < cLookupEntries)
+    {
+        uint32_t const            idRamRange = PGMRAMRANGELOOKUPENTRY_GET_ID(pVM->pgm.s.aRamRangeLookup[iRange]);
+        Assert(idRamRange && idRamRange <= pVM->pgm.s.idRamRangeMax);
+        PGMRAMRANGE const * const pRamRange  = pVM->pgm.s.apRamRanges[idRamRange];
+        AssertPtr(pRamRange);
+
+        if (pGCPhysStart)
+            *pGCPhysStart = pRamRange->GCPhys;
+        if (pGCPhysLast)
+            *pGCPhysLast  = pRamRange->GCPhysLast;
+        if (ppszDesc)
+            *ppszDesc     = pRamRange->pszDesc;
+        if (pfIsMmio)
+            *pfIsMmio     = !!(pRamRange->fFlags & PGM_RAM_RANGE_FLAGS_AD_HOC_MMIO);
+
+        PGM_UNLOCK(pVM);
+        return VINF_SUCCESS;
+    }
     PGM_UNLOCK(pVM);
     return VERR_OUT_OF_RANGE;
@@ -1516,5 +1684,5 @@
             uint32_t const  fNemNotify = (pvMmio2 ? NEM_NOTIFY_PHYS_MMIO_EX_F_MMIO2 : 0) | NEM_NOTIFY_PHYS_MMIO_EX_F_REPLACE;
             int rc = NEMR3NotifyPhysMmioExMapEarly(pVM, GCPhys, GCPhysLast - GCPhys + 1, fNemNotify,
-                                                   pRam->pvR3 ? (uint8_t *)pRam->pvR3 + GCPhys - pRam->GCPhys : NULL,
+                                                   pRam->pbR3 ? pRam->pbR3 + GCPhys - pRam->GCPhys : NULL,
                                                    pvMmio2, &u2State, NULL /*puNemRange*/);
             AssertLogRelRCReturn(rc, rc);
@@ -1593,4 +1761,37 @@
 
 /**
+ * Wrapper around VMMR0_DO_PGM_PHYS_ALLOCATE_RAM_RANGE.
+ */
+static int pgmR3PhysAllocateRamRange(PVM pVM, PVMCPU pVCpu, uint32_t cGuestPages, uint32_t fFlags, PPGMRAMRANGE *ppRamRange)
+{
+    int                        rc;
+    PGMPHYSALLOCATERAMRANGEREQ AllocRangeReq;
+    AllocRangeReq.idNewRange   = UINT32_MAX / 4;
+    if (SUPR3IsDriverless())
+        rc = pgmPhysRamRangeAllocCommon(pVM, cGuestPages, fFlags, &AllocRangeReq.idNewRange);
+    else
+    {
+        AllocRangeReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC;
+        AllocRangeReq.Hdr.cbReq    = sizeof(AllocRangeReq);
+        AllocRangeReq.cbGuestPage  = GUEST_PAGE_SIZE;
+        AllocRangeReq.cGuestPages  = cGuestPages;
+        AllocRangeReq.fFlags       = fFlags;
+        rc = VMMR3CallR0Emt(pVM, pVCpu, VMMR0_DO_PGM_PHYS_ALLOCATE_RAM_RANGE, 0 /*u64Arg*/, &AllocRangeReq.Hdr);
+    }
+    if (RT_SUCCESS(rc))
+    {
+        Assert(AllocRangeReq.idNewRange != 0);
+        Assert(AllocRangeReq.idNewRange < RT_ELEMENTS(pVM->pgm.s.apRamRanges));
+        AssertPtr(pVM->pgm.s.apRamRanges[AllocRangeReq.idNewRange]);
+        *ppRamRange = pVM->pgm.s.apRamRanges[AllocRangeReq.idNewRange];
+        return VINF_SUCCESS;
+    }
+
+    *ppRamRange = NULL;
+    return rc;
+}
+
+
+/**
  * PGMR3PhysRegisterRam worker that initializes and links a RAM range.
  *
@@ -1603,23 +1804,17 @@
  * @param   GCPhys          The address of the RAM range.
  * @param   GCPhysLast      The last address of the RAM range.
- * @param   R0PtrNew        Ditto for R0.
- * @param   fFlags          PGM_RAM_RANGE_FLAGS_FLOATING or zero.
  * @param   pszDesc         The description.
- * @param   pPrev           The previous RAM range (for linking).
+ * @param   pidxLookup      The lookup table insertion point.
  */
 static int pgmR3PhysInitAndLinkRamRange(PVM pVM, PPGMRAMRANGE pNew, RTGCPHYS GCPhys, RTGCPHYS GCPhysLast,
-                                        RTR0PTR R0PtrNew, uint32_t fFlags, const char *pszDesc, PPGMRAMRANGE pPrev)
+                                        const char *pszDesc, uint32_t *pidxLookup)
 {
     /*
      * Initialize the range.
      */
-    pNew->pSelfR0       = R0PtrNew;
-    pNew->GCPhys        = GCPhys;
-    pNew->GCPhysLast    = GCPhysLast;
-    pNew->cb            = GCPhysLast - GCPhys + 1;
+    Assert(pNew->cb == GCPhysLast - GCPhys + 1U);
     pNew->pszDesc       = pszDesc;
-    pNew->fFlags        = fFlags;
     pNew->uNemRange     = UINT32_MAX;
-    pNew->pvR3          = NULL;
+    pNew->pbR3          = NULL;
     pNew->paLSPages     = NULL;
 
@@ -1641,5 +1836,5 @@
     {
         int rc = SUPR3PageAlloc(RT_ALIGN_Z(pNew->cb, HOST_PAGE_SIZE) >> HOST_PAGE_SHIFT,
-                                pVM->pgm.s.fUseLargePages ? SUP_PAGE_ALLOC_F_LARGE_PAGES : 0, &pNew->pvR3);
+                                pVM->pgm.s.fUseLargePages ? SUP_PAGE_ALLOC_F_LARGE_PAGES : 0, (void **)&pNew->pbR3);
         if (RT_FAILURE(rc))
             return rc;
@@ -1657,23 +1852,22 @@
 
     /*
-     * Link it.
-     */
-    pgmR3PhysLinkRamRange(pVM, pNew, pPrev);
+     * Insert it into the lookup table.
+     */
+    int rc = pgmR3PhysRamRangeInsertLookup(pVM, pNew, GCPhys, pidxLookup);
+    AssertRCReturn(rc, rc);
 
 #ifdef VBOX_WITH_NATIVE_NEM
     /*
      * Notify NEM now that it has been linked.
+     *
+     * As above, it is assumed that on failure the VM creation will fail, so
+     * no extra cleanup is needed here.
      */
     if (VM_IS_NEM_ENABLED(pVM))
     {
         uint8_t u2State = UINT8_MAX;
-        int rc = NEMR3NotifyPhysRamRegister(pVM, GCPhys, pNew->cb, pNew->pvR3, &u2State, &pNew->uNemRange);
-        if (RT_SUCCESS(rc))
-        {
-            if (u2State != UINT8_MAX)
-                pgmPhysSetNemStateForPages(&pNew->aPages[0], cPages, u2State);
-        }
-        else
-            pgmR3PhysUnlinkRamRange2(pVM, pNew, pPrev);
+        rc = NEMR3NotifyPhysRamRegister(pVM, GCPhys, pNew->cb, pNew->pbR3, &u2State, &pNew->uNemRange);
+        if (RT_SUCCESS(rc) && u2State != UINT8_MAX)
+            pgmPhysSetNemStateForPages(&pNew->aPages[0], cPages, u2State);
         return rc;
     }
@@ -1684,51 +1878,94 @@
 
 /**
- * PGMR3PhysRegisterRam worker that registers a high chunk.
- *
- * @returns VBox status code.
- * @param   pVM             The cross context VM structure.
- * @param   GCPhys          The address of the RAM.
- * @param   cRamPages       The number of RAM pages to register.
- * @param   iChunk          The chunk number.
- * @param   pszDesc         The RAM range description.
- * @param   ppPrev          Previous RAM range pointer. In/Out.
- */
-static int pgmR3PhysRegisterHighRamChunk(PVM pVM, RTGCPHYS GCPhys, uint32_t cRamPages, uint32_t iChunk,
-                                         const char *pszDesc, PPGMRAMRANGE *ppPrev)
-{
-    const char *pszDescChunk = iChunk == 0
-                             ? pszDesc
-                             : MMR3HeapAPrintf(pVM, MM_TAG_PGM_PHYS, "%s (#%u)", pszDesc, iChunk + 1);
-    AssertReturn(pszDescChunk, VERR_NO_MEMORY);
-
-    /*
-     * Allocate memory for the new chunk.
-     */
-    size_t const cChunkPages  = RT_ALIGN_Z(RT_UOFFSETOF_DYN(PGMRAMRANGE, aPages[cRamPages]), HOST_PAGE_SIZE) >> HOST_PAGE_SHIFT;
-    PSUPPAGE     paChunkPages = (PSUPPAGE)RTMemTmpAllocZ(sizeof(SUPPAGE) * cChunkPages);
-    AssertReturn(paChunkPages, VERR_NO_TMP_MEMORY);
-    RTR0PTR      R0PtrChunk   = NIL_RTR0PTR;
-    void        *pvChunk      = NULL;
-    int rc = SUPR3PageAllocEx(cChunkPages, 0 /*fFlags*/, &pvChunk, &R0PtrChunk, paChunkPages);
-    if (RT_SUCCESS(rc))
-    {
-        Assert(R0PtrChunk != NIL_RTR0PTR || PGM_IS_IN_NEM_MODE(pVM));
-        memset(pvChunk, 0, cChunkPages << HOST_PAGE_SHIFT);
-
-        PPGMRAMRANGE pNew = (PPGMRAMRANGE)pvChunk;
+ * Worker for PGMR3PhysRegisterRam called with the PGM lock.
+ *
+ * The caller releases the lock.
+ */
+static int pgmR3PhysRegisterRamWorker(PVM pVM, PVMCPU pVCpu, RTGCPHYS GCPhys, RTGCPHYS cb, const char *pszDesc,
+                                      uint32_t const cRamRanges, RTGCPHYS const GCPhysLast)
+{
+#ifdef VBOX_STRICT
+    pgmPhysAssertRamRangesLocked(pVM, false /*fInUpdate*/, false /*fRamRelaxed*/);
+#endif
+
+    /*
+     * Check that we've got enough free RAM ranges.
+     */
+    AssertLogRelMsgReturn((uint64_t)pVM->pgm.s.idRamRangeMax + cRamRanges + 1 <= RT_ELEMENTS(pVM->pgm.s.aRamRangeLookup),
+                          ("idRamRangeMax=%#RX32 vs GCPhys=%RGp cb=%RGp / %#RX32 ranges (%s)\n",
+                           pVM->pgm.s.idRamRangeMax, GCPhys, cb, cRamRanges, pszDesc),
+                          VERR_PGM_TOO_MANY_RAM_RANGES);
+
+    /*
+     * Check for conflicts via the lookup table.  We search it backwards,
+     * assuming that memory is added in ascending order by address.
+     */
+    uint32_t idxLookup = pVM->pgm.s.RamRangeUnion.cLookupEntries;
+    while (idxLookup)
+    {
+        if (GCPhys > pVM->pgm.s.aRamRangeLookup[idxLookup - 1].GCPhysLast)
+            break;
+        idxLookup--;
+        RTGCPHYS const GCPhysCur = PGMRAMRANGELOOKUPENTRY_GET_FIRST(pVM->pgm.s.aRamRangeLookup[idxLookup]);
+        AssertLogRelMsgReturn(   GCPhysLast < GCPhysCur
+                              || GCPhys     > pVM->pgm.s.aRamRangeLookup[idxLookup].GCPhysLast,
+                              ("%RGp-%RGp (%s) conflicts with existing %RGp-%RGp (%s)\n",
+                               GCPhys, GCPhysLast, pszDesc, GCPhysCur, pVM->pgm.s.aRamRangeLookup[idxLookup].GCPhysLast,
+                               pVM->pgm.s.apRamRanges[PGMRAMRANGELOOKUPENTRY_GET_ID(pVM->pgm.s.aRamRangeLookup[idxLookup])]->pszDesc),
+                               VERR_PGM_RAM_CONFLICT);
+    }
+
+    /*
+     * Register it with GMM (the API bitches).
+     */
+    const RTGCPHYS cPages = cb >> GUEST_PAGE_SHIFT;
+    int rc = MMR3IncreaseBaseReservation(pVM, cPages);
+    if (RT_FAILURE(rc))
+        return rc;
+
+    /*
+     * Create the required chunks.
+     */
+    RTGCPHYS cPagesLeft  = cPages;
+    RTGCPHYS GCPhysChunk = GCPhys;
+    uint32_t idxChunk    = 0;
+    while (cPagesLeft > 0)
+    {
+        uint32_t cPagesInChunk = cPagesLeft;
+        if (cPagesInChunk > PGM_MAX_PAGES_PER_RAM_RANGE)
+            cPagesInChunk = PGM_MAX_PAGES_PER_RAM_RANGE;
+
+        const char *pszDescChunk = idxChunk == 0
+                                 ? pszDesc
+                                 : MMR3HeapAPrintf(pVM, MM_TAG_PGM_PHYS, "%s (#%u)", pszDesc, idxChunk + 1);
+        AssertReturn(pszDescChunk, VERR_NO_MEMORY);
+
+        /*
+         * Allocate a RAM range.
+         */
+        PPGMRAMRANGE pNew = NULL;
+        rc = pgmR3PhysAllocateRamRange(pVM, pVCpu, cPagesInChunk, 0 /*fFlags*/, &pNew);
+        AssertLogRelMsgReturn(RT_SUCCESS(rc),
+                              ("pgmR3PhysAllocateRamRange failed: GCPhysChunk=%RGp cPagesInChunk=%#RX32 (%s): %Rrc\n",
+                               GCPhysChunk, cPagesInChunk, pszDescChunk, rc),
+                              rc);
 
         /*
          * Ok, init and link the range.
          */
-        rc = pgmR3PhysInitAndLinkRamRange(pVM, pNew, GCPhys, GCPhys + ((RTGCPHYS)cRamPages << GUEST_PAGE_SHIFT) - 1,
-                                          R0PtrChunk, PGM_RAM_RANGE_FLAGS_FLOATING, pszDescChunk, *ppPrev);
-        if (RT_SUCCESS(rc))
-            *ppPrev = pNew;
-
-        if (RT_FAILURE(rc))
-            SUPR3PageFreeEx(pvChunk, cChunkPages);
-    }
-
-    RTMemTmpFree(paChunkPages);
+        rc = pgmR3PhysInitAndLinkRamRange(pVM, pNew, GCPhysChunk,
+                                          GCPhysChunk + ((RTGCPHYS)cPagesInChunk << GUEST_PAGE_SHIFT) - 1U,
+                                          pszDescChunk, &idxLookup);
+        AssertLogRelMsgReturn(RT_SUCCESS(rc),
+                              ("pgmR3PhysInitAndLinkRamRange failed: GCPhysChunk=%RGp cPagesInChunk=%#RX32 (%s): %Rrc\n",
+                               GCPhysChunk, cPagesInChunk, pszDescChunk, rc),
+                              rc);
+
+        /* advance */
+        GCPhysChunk += (RTGCPHYS)cPagesInChunk << GUEST_PAGE_SHIFT;
+        cPagesLeft  -= cPagesInChunk;
+        idxChunk++;
+    }
+
     return rc;
 }
@@ -1738,7 +1975,7 @@
  * Sets up a range RAM.
  *
- * This will check for conflicting registrations, make a resource
- * reservation for the memory (with GMM), and setup the per-page
- * tracking structures (PGMPAGE).
+ * This will check for conflicting registrations, make a resource reservation
+ * for the memory (with GMM), and setup the per-page tracking structures
+ * (PGMPAGE).
  *
  * @returns VBox status code.
@@ -1750,97 +1987,34 @@
 VMMR3DECL(int) PGMR3PhysRegisterRam(PVM pVM, RTGCPHYS GCPhys, RTGCPHYS cb, const char *pszDesc)
 {
-   /*
+    /*
      * Validate input.
      */
     Log(("PGMR3PhysRegisterRam: GCPhys=%RGp cb=%RGp pszDesc=%s\n", GCPhys, cb, pszDesc));
     AssertReturn(RT_ALIGN_T(GCPhys, GUEST_PAGE_SIZE, RTGCPHYS) == GCPhys, VERR_INVALID_PARAMETER);
-    AssertReturn(RT_ALIGN_T(cb, GUEST_PAGE_SIZE, RTGCPHYS) == cb, VERR_INVALID_PARAMETER);
+    AssertReturn(RT_ALIGN_T(cb,     GUEST_PAGE_SIZE, RTGCPHYS) == cb,     VERR_INVALID_PARAMETER);
     AssertReturn(cb > 0, VERR_INVALID_PARAMETER);
-    RTGCPHYS GCPhysLast = GCPhys + (cb - 1);
+    RTGCPHYS const GCPhysLast = GCPhys + (cb - 1);
     AssertMsgReturn(GCPhysLast > GCPhys, ("The range wraps! GCPhys=%RGp cb=%RGp\n", GCPhys, cb), VERR_INVALID_PARAMETER);
     AssertPtrReturn(pszDesc, VERR_INVALID_POINTER);
-    VM_ASSERT_EMT_RETURN(pVM, VERR_VM_THREAD_NOT_EMT);
+    PVMCPU const pVCpu = VMMGetCpu(pVM);
+    AssertReturn(pVCpu, VERR_VM_THREAD_NOT_EMT);
+    AssertReturn(pVCpu->idCpu == 0, VERR_VM_THREAD_NOT_EMT);
+
+    /*
+     * Calculate the number of RAM ranges required.
+     * See also pgmPhysMmio2CalcChunkCount.
+     */
+    uint32_t const cPagesPerChunk = PGM_MAX_PAGES_PER_RAM_RANGE;
+    uint32_t const cRamRanges     = (uint32_t)(((cb >> GUEST_PAGE_SHIFT) + cPagesPerChunk - 1) / cPagesPerChunk);
+    AssertLogRelMsgReturn(cRamRanges * (RTGCPHYS)cPagesPerChunk * GUEST_PAGE_SIZE >= cb,
+                          ("cb=%RGp cRamRanges=%#RX32 cPagesPerChunk=%#RX32\n", cb, cRamRanges, cPagesPerChunk),
+                          VERR_OUT_OF_RANGE);
 
     PGM_LOCK_VOID(pVM);
 
-    /*
-     * Find range location and check for conflicts.
-     */
-    PPGMRAMRANGE    pPrev = NULL;
-    PPGMRAMRANGE    pRam = pVM->pgm.s.pRamRangesXR3;
-    while (pRam && GCPhysLast >= pRam->GCPhys)
-    {
-        AssertLogRelMsgReturnStmt(   GCPhysLast < pRam->GCPhys
-                                  || GCPhys     > pRam->GCPhysLast,
-                                  ("%RGp-%RGp (%s) conflicts with existing %RGp-%RGp (%s)\n",
-                                   GCPhys, GCPhysLast, pszDesc, pRam->GCPhys, pRam->GCPhysLast, pRam->pszDesc),
-                                  PGM_UNLOCK(pVM), VERR_PGM_RAM_CONFLICT);
-
-        /* next */
-        pPrev = pRam;
-        pRam = pRam->pNextR3;
-    }
-
-    /*
-     * Register it with GMM (the API bitches).
-     */
-    const RTGCPHYS cPages = cb >> GUEST_PAGE_SHIFT;
-    int rc = MMR3IncreaseBaseReservation(pVM, cPages);
-    if (RT_FAILURE(rc))
-    {
-        PGM_UNLOCK(pVM);
-        return rc;
-    }
-
-    if (    GCPhys >= _4G
-        &&  cPages > 256)
-    {
-        /*
-         * The PGMRAMRANGE structures for the high memory can get very big.
-         * There used to be some limitations on SUPR3PageAllocEx allocation
-         * sizes, so traditionally we limited this to 16MB chunks. These days
-         * we do ~64 MB chunks each covering 16GB of guest RAM, making sure
-         * each range is a multiple of 1GB to enable eager hosts to use 1GB
-         * pages in NEM mode.
-         *
-         * See also pgmR3PhysMmio2CalcChunkCount.
-         */
-        uint32_t const cPagesPerChunk = _4M;
-        Assert(RT_ALIGN_32(cPagesPerChunk, X86_PD_PAE_SHIFT - X86_PAGE_SHIFT)); /* NEM large page requirement: 1GB pages. */
-
-        RTGCPHYS cPagesLeft  = cPages;
-        RTGCPHYS GCPhysChunk = GCPhys;
-        uint32_t iChunk      = 0;
-        while (cPagesLeft > 0)
-        {
-            uint32_t cPagesInChunk = cPagesLeft;
-            if (cPagesInChunk > cPagesPerChunk)
-                cPagesInChunk = cPagesPerChunk;
-
-            rc = pgmR3PhysRegisterHighRamChunk(pVM, GCPhysChunk, cPagesInChunk, iChunk, pszDesc, &pPrev);
-            AssertRCReturn(rc, rc);
-
-            /* advance */
-            GCPhysChunk += (RTGCPHYS)cPagesInChunk << GUEST_PAGE_SHIFT;
-            cPagesLeft  -= cPagesInChunk;
-            iChunk++;
-        }
-    }
-    else
-    {
-        /*
-         * Allocate, initialize and link the new RAM range.
-         */
-        const size_t cbRamRange = RT_UOFFSETOF_DYN(PGMRAMRANGE, aPages[cPages]);
-        PPGMRAMRANGE pNew = NULL;
-        RTR0PTR      pNewR0 = NIL_RTR0PTR;
-        rc = SUPR3PageAllocEx(RT_ALIGN_Z(cbRamRange, HOST_PAGE_SIZE) >> HOST_PAGE_SHIFT, 0 /*fFlags*/,
-                              (void **)&pNew, &pNewR0, NULL /*paPages*/);
-        AssertLogRelMsgRCReturn(rc, ("rc=%Rrc cbRamRange=%zu\n", rc, cbRamRange), rc);
-
-        rc = pgmR3PhysInitAndLinkRamRange(pVM, pNew, GCPhys, GCPhysLast, pNewR0, 0 /*fFlags*/, pszDesc, pPrev);
-        AssertLogRelMsgRCReturn(rc, ("rc=%Rrc cbRamRange=%zu\n", rc, cbRamRange), rc);
-    }
-    pgmPhysInvalidatePageMapTLB(pVM);
+    int rc = pgmR3PhysRegisterRamWorker(pVM, pVCpu, GCPhys, cb, pszDesc, cRamRanges, GCPhysLast);
+#ifdef VBOX_STRICT
+    pgmPhysAssertRamRangesLocked(pVM, false /*fInUpdate*/, false /*fRamRelaxed*/);
+#endif
 
     PGM_UNLOCK(pVM);
@@ -1874,6 +2048,12 @@
     uint64_t NanoTS = RTTimeNanoTS();
     PGM_LOCK_VOID(pVM);
-    for (PPGMRAMRANGE pRam = pVM->pgm.s.pRamRangesXR3; pRam; pRam = pRam->pNextR3)
-    {
+    uint32_t const cLookupEntries = RT_MIN(pVM->pgm.s.RamRangeUnion.cLookupEntries, RT_ELEMENTS(pVM->pgm.s.aRamRangeLookup));
+    for (uint32_t idxLookup = 0; idxLookup < cLookupEntries; idxLookup++)
+    {
+        uint32_t const idRamRange = PGMRAMRANGELOOKUPENTRY_GET_ID(pVM->pgm.s.aRamRangeLookup[idxLookup]);
+        AssertContinue(idRamRange < RT_ELEMENTS(pVM->pgm.s.apRamRanges));
+        PPGMRAMRANGE const pRam = pVM->pgm.s.apRamRanges[idRamRange];
+        AssertContinue(pRam);
+
         PPGMPAGE    pPage  = &pRam->aPages[0];
         RTGCPHYS    GCPhys = pRam->GCPhys;
@@ -1936,6 +2116,12 @@
          * Walk the ram ranges.
          */
-        for (PPGMRAMRANGE pRam = pVM->pgm.s.pRamRangesXR3; pRam; pRam = pRam->pNextR3)
-        {
+        uint32_t const cLookupEntries = RT_MIN(pVM->pgm.s.RamRangeUnion.cLookupEntries, RT_ELEMENTS(pVM->pgm.s.aRamRangeLookup));
+        for (uint32_t idxLookup = 0; idxLookup < cLookupEntries; idxLookup++)
+        {
+            uint32_t const idRamRange = PGMRAMRANGELOOKUPENTRY_GET_ID(pVM->pgm.s.aRamRangeLookup[idxLookup]);
+            AssertContinue(idRamRange < RT_ELEMENTS(pVM->pgm.s.apRamRanges));
+            PPGMRAMRANGE const pRam = pVM->pgm.s.apRamRanges[idRamRange];
+            AssertContinue(pRam);
+
             uint32_t iPage = pRam->cb >> GUEST_PAGE_SHIFT;
             AssertMsg(((RTGCPHYS)iPage << GUEST_PAGE_SHIFT) == pRam->cb,
@@ -2035,6 +2221,12 @@
      * Walk the ram ranges.
      */
-    for (PPGMRAMRANGE pRam = pVM->pgm.s.pRamRangesXR3; pRam; pRam = pRam->pNextR3)
-    {
+    uint32_t const idRamRangeMax = RT_MIN(pVM->pgm.s.idRamRangeMax, RT_ELEMENTS(pVM->pgm.s.apRamRanges) - 1U);
+    for (uint32_t idRamRange = 0; idRamRange <= idRamRangeMax; idRamRange++)
+    {
+        PPGMRAMRANGE const pRam = pVM->pgm.s.apRamRanges[idRamRange];
+        Assert(pRam || idRamRange == 0);
+        if (!pRam) continue;
+        Assert(pRam->idRange == idRamRange);
+
         uint32_t iPage = pRam->cb >> GUEST_PAGE_SHIFT;
         AssertMsg(((RTGCPHYS)iPage << GUEST_PAGE_SHIFT) == pRam->cb, ("%RGp %RGp\n", (RTGCPHYS)iPage << GUEST_PAGE_SHIFT, pRam->cb));
@@ -2146,5 +2338,4 @@
             } /* for each page */
         }
-
     }
 
@@ -2214,6 +2405,12 @@
      * Walk the ram ranges.
      */
-    for (PPGMRAMRANGE pRam = pVM->pgm.s.pRamRangesXR3; pRam; pRam = pRam->pNextR3)
-    {
+    uint32_t const idRamRangeMax = RT_MIN(pVM->pgm.s.idRamRangeMax, RT_ELEMENTS(pVM->pgm.s.apRamRanges) - 1U);
+    for (uint32_t idRamRange = 0; idRamRange <= idRamRangeMax; idRamRange++)
+    {
+        PPGMRAMRANGE const pRam = pVM->pgm.s.apRamRanges[idRamRange];
+        Assert(pRam || idRamRange == 0);
+        if (!pRam) continue;
+        Assert(pRam->idRange == idRamRange);
+
         uint32_t iPage = pRam->cb >> GUEST_PAGE_SHIFT;
         AssertMsg(((RTGCPHYS)iPage << GUEST_PAGE_SHIFT) == pRam->cb, ("%RGp %RGp\n", (RTGCPHYS)iPage << GUEST_PAGE_SHIFT, pRam->cb));
@@ -2267,9 +2464,6 @@
 
 /**
- * This is the interface IOM is using to register an MMIO region.
- *
- * It will check for conflicts and ensure that a RAM range structure
- * is present before calling the PGMR3HandlerPhysicalRegister API to
- * register the callbacks.
+ * This is the interface IOM is using to register an MMIO region (unmapped).
+ *
  *
  * @returns VBox status code.
@@ -2277,79 +2471,113 @@
  * @param   pVM             The cross context VM structure.
  * @param   pVCpu           The cross context virtual CPU structure of the calling EMT.
- * @param   GCPhys          The start of the MMIO region.
  * @param   cb              The size of the MMIO region.
- * @param   hType           The physical access handler type registration.
- * @param   uUser           The user argument.
  * @param   pszDesc         The description of the MMIO region.
- * @thread  EMT(pVCpu)
- */
-VMMR3_INT_DECL(int) PGMR3PhysMmioRegister(PVM pVM, PVMCPU pVCpu, RTGCPHYS GCPhys, RTGCPHYS cb, PGMPHYSHANDLERTYPE hType,
-                                          uint64_t uUser, const char *pszDesc)
-{
-    /*
-     * Assert on some assumption.
-     */
-    VMCPU_ASSERT_EMT(pVCpu);
+ * @param   pidRamRange     Where to return the RAM range ID for the MMIO region
+ *                          on success.
+ * @thread  EMT(0)
+ */
+VMMR3_INT_DECL(int) PGMR3PhysMmioRegister(PVM pVM, PVMCPU pVCpu, RTGCPHYS cb, const char *pszDesc, uint16_t *pidRamRange)
+{
+    /*
+     * Assert assumptions.
+     */
+    AssertPtrReturn(pidRamRange, VERR_INVALID_POINTER);
+    *pidRamRange = UINT16_MAX;
+    AssertReturn(pVCpu == VMMGetCpu(pVM) && pVCpu->idCpu == 0, VERR_VM_THREAD_NOT_EMT);
+    VM_ASSERT_STATE_RETURN(pVM, VMSTATE_CREATING, VERR_VM_INVALID_VM_STATE);
+    /// @todo AssertReturn(!pVM->pgm.s.fRamRangesFrozen, VERR_WRONG_ORDER);
+    AssertReturn(cb <= ((RTGCPHYS)PGM_MAX_PAGES_PER_RAM_RANGE << GUEST_PAGE_SHIFT), VERR_OUT_OF_RANGE);
     AssertReturn(!(cb & GUEST_PAGE_OFFSET_MASK), VERR_INVALID_PARAMETER);
-    AssertReturn(!(GCPhys & GUEST_PAGE_OFFSET_MASK), VERR_INVALID_PARAMETER);
     AssertPtrReturn(pszDesc, VERR_INVALID_POINTER);
-    AssertReturn(*pszDesc, VERR_INVALID_PARAMETER);
-#ifdef VBOX_STRICT
-    PCPGMPHYSHANDLERTYPEINT pType = pgmHandlerPhysicalTypeHandleToPtr(pVM, hType);
-    Assert(pType);
-    Assert(pType->enmKind == PGMPHYSHANDLERKIND_MMIO);
-#endif
-
+    AssertReturn(*pszDesc != '\0', VERR_INVALID_POINTER);
+
+    /*
+     * Take the PGM lock and allocate an ad-hoc MMIO RAM range.
+     */
     int rc = PGM_LOCK(pVM);
     AssertRCReturn(rc, rc);
 
-    /*
-     * Make sure there's a RAM range structure for the region.
-     */
-    RTGCPHYS GCPhysLast = GCPhys + (cb - 1);
-    bool fRamExists = false;
-    PPGMRAMRANGE pRamPrev = NULL;
-    PPGMRAMRANGE pRam = pVM->pgm.s.pRamRangesXR3;
-    while (pRam && GCPhysLast >= pRam->GCPhys)
-    {
-        if (    GCPhysLast >= pRam->GCPhys
-            &&  GCPhys     <= pRam->GCPhysLast)
-        {
-            /* Simplification: all within the same range. */
-            AssertLogRelMsgReturnStmt(   GCPhys     >= pRam->GCPhys
-                                      && GCPhysLast <= pRam->GCPhysLast,
-                                      ("%RGp-%RGp (MMIO/%s) falls partly outside %RGp-%RGp (%s)\n",
-                                       GCPhys, GCPhysLast, pszDesc,
-                                       pRam->GCPhys, pRam->GCPhysLast, pRam->pszDesc),
-                                      PGM_UNLOCK(pVM),
-                                      VERR_PGM_RAM_CONFLICT);
-
-            /* Check that it's all RAM or MMIO pages. */
-            PCPGMPAGE pPage = &pRam->aPages[(GCPhys - pRam->GCPhys) >> GUEST_PAGE_SHIFT];
-            uint32_t  cLeft = cb >> GUEST_PAGE_SHIFT;
-            while (cLeft-- > 0)
-            {
-                AssertLogRelMsgReturnStmt(   PGM_PAGE_GET_TYPE(pPage) == PGMPAGETYPE_RAM
-                                          || PGM_PAGE_GET_TYPE(pPage) == PGMPAGETYPE_MMIO,
-                                          ("%RGp-%RGp (MMIO/%s): %RGp is not a RAM or MMIO page - type=%d desc=%s\n",
-                                           GCPhys, GCPhysLast, pszDesc, pRam->GCPhys, PGM_PAGE_GET_TYPE(pPage), pRam->pszDesc),
-                                          PGM_UNLOCK(pVM),
-                                          VERR_PGM_RAM_CONFLICT);
-                pPage++;
-            }
-
-            /* Looks good. */
-            fRamExists = true;
-            break;
-        }
-
-        /* next */
-        pRamPrev = pRam;
-        pRam = pRam->pNextR3;
-    }
-    PPGMRAMRANGE pNew;
-    if (fRamExists)
-    {
-        pNew = NULL;
+    uint32_t const cPages = cb >> GUEST_PAGE_SHIFT;
+    PPGMRAMRANGE   pNew   = NULL;
+    rc = pgmR3PhysAllocateRamRange(pVM, pVCpu, cPages, PGM_RAM_RANGE_FLAGS_AD_HOC_MMIO, &pNew);
+    AssertLogRelMsg(RT_SUCCESS(rc), ("pgmR3PhysAllocateRamRange failed: cPages=%#RX32 (%s): %Rrc\n", cPages, pszDesc, rc));
+    if (RT_SUCCESS(rc))
+    {
+        /* Initialize the range. */
+        pNew->pszDesc       = pszDesc;
+        pNew->uNemRange     = UINT32_MAX;
+        pNew->pbR3          = NULL;
+        pNew->paLSPages     = NULL;
+        Assert(pNew->fFlags == PGM_RAM_RANGE_FLAGS_AD_HOC_MMIO && pNew->cb == cb);
+
+        uint32_t iPage = cPages;
+        while (iPage-- > 0)
+            PGM_PAGE_INIT_ZERO(&pNew->aPages[iPage], pVM, PGMPAGETYPE_MMIO);
+        Assert(PGM_PAGE_GET_TYPE(&pNew->aPages[0]) == PGMPAGETYPE_MMIO);
+
+        /* update the page count stats. */
+        pVM->pgm.s.cPureMmioPages += cPages;
+        pVM->pgm.s.cAllPages      += cPages;
+
+        /*
+         * Set the return value, release lock and return to IOM.
+         */
+        *pidRamRange = pNew->idRange;
+    }
+
+    PGM_UNLOCK(pVM);
+    return rc;
+}
+
+
+/**
+ * Worker for PGMR3PhysMmioMap that's called owning the lock.
+ */
+static int pgmR3PhysMmioMapLocked(PVM pVM, PVMCPU pVCpu, RTGCPHYS const GCPhys, RTGCPHYS const cb, RTGCPHYS const GCPhysLast,
+                                  PPGMRAMRANGE const pMmioRamRange, PGMPHYSHANDLERTYPE const hType, uint64_t const uUser)
+{
+    /* Check that the range isn't mapped already. */
+    AssertLogRelMsgReturn(pMmioRamRange->GCPhys == NIL_RTGCPHYS,
+                          ("desired %RGp mapping for '%s' - already mapped at %RGp!\n",
+                           GCPhys, pMmioRamRange->pszDesc, pMmioRamRange->GCPhys),
+                          VERR_ALREADY_EXISTS);
+
+    /*
+     * Now, check if this falls into a regular RAM range or if we should use
+     * the ad-hoc one (idRamRange).
+     */
+    int                rc;
+    uint32_t           idxInsert         = UINT32_MAX;
+    PPGMRAMRANGE const pOverlappingRange = pgmR3PhysRamRangeFindOverlapping(pVM, GCPhys, GCPhysLast, &idxInsert);
+    if (pOverlappingRange)
+    {
+        /* Simplification: all within the same range. */
+        AssertLogRelMsgReturn(   GCPhys     >= pOverlappingRange->GCPhys
+                              && GCPhysLast <= pOverlappingRange->GCPhysLast,
+                              ("%RGp-%RGp (MMIO/%s) falls partly outside %RGp-%RGp (%s)\n",
+                               GCPhys, GCPhysLast, pMmioRamRange->pszDesc,
+                               pOverlappingRange->GCPhys, pOverlappingRange->GCPhysLast, pOverlappingRange->pszDesc),
+                              VERR_PGM_RAM_CONFLICT);
+
+        /* Check that is isn't an ad hoc range, but a real RAM range. */
+        AssertLogRelMsgReturn(!PGM_RAM_RANGE_IS_AD_HOC(pOverlappingRange),
+                              ("%RGp-%RGp (MMIO/%s) mapping attempt in non-RAM range: %RGp-%RGp (%s)\n",
+                               GCPhys, GCPhysLast, pMmioRamRange->pszDesc,
+                               pOverlappingRange->GCPhys, pOverlappingRange->GCPhysLast, pOverlappingRange->pszDesc),
+                              VERR_PGM_RAM_CONFLICT);
+
+        /* Check that it's all RAM or MMIO pages. */
+        PCPGMPAGE pPage = &pOverlappingRange->aPages[(GCPhys - pOverlappingRange->GCPhys) >> GUEST_PAGE_SHIFT];
+        uint32_t  cLeft = cb >> GUEST_PAGE_SHIFT;
+        while (cLeft-- > 0)
+        {
+            AssertLogRelMsgReturn(   PGM_PAGE_GET_TYPE(pPage) == PGMPAGETYPE_RAM
+                                  || PGM_PAGE_GET_TYPE(pPage) == PGMPAGETYPE_MMIO, /** @todo MMIO type isn't right */
+                                  ("%RGp-%RGp (MMIO/%s): %RGp is not a RAM or MMIO page - type=%d desc=%s\n",
+                                   GCPhys, GCPhysLast, pMmioRamRange->pszDesc, pOverlappingRange->GCPhys,
+                                   PGM_PAGE_GET_TYPE(pPage), pOverlappingRange->pszDesc),
+                                  VERR_PGM_RAM_CONFLICT);
+            pPage++;
+        }
 
         /*
@@ -2358,6 +2586,6 @@
          * for PCI memory, but we're doing the same thing for MMIO2 pages.
          */
-        rc = pgmR3PhysFreePageRange(pVM, pRam, GCPhys, GCPhysLast, NULL);
-        AssertRCReturnStmt(rc, PGM_UNLOCK(pVM), rc);
+        rc = pgmR3PhysFreePageRange(pVM, pOverlappingRange, GCPhys, GCPhysLast, NULL);
+        AssertRCReturn(rc, rc);
 
         /* Force a PGM pool flush as guest ram references have been changed. */
@@ -2371,91 +2599,258 @@
     {
         /*
-         * No RAM range, insert an ad hoc one.
+         * No RAM range, use the ad hoc one (idRamRange).
          *
          * Note that we don't have to tell REM about this range because
          * PGMHandlerPhysicalRegisterEx will do that for us.
          */
-        Log(("PGMR3PhysMmioRegister: Adding ad hoc MMIO range for %RGp-%RGp %s\n", GCPhys, GCPhysLast, pszDesc));
-
-        /* Alloc. */
-        const uint32_t cPages      = cb >> GUEST_PAGE_SHIFT;
-        const size_t   cbRamRange  = RT_UOFFSETOF_DYN(PGMRAMRANGE, aPages[cPages]);
-        const size_t   cRangePages = RT_ALIGN_Z(cbRamRange, HOST_PAGE_SIZE) >> HOST_PAGE_SHIFT;
-        RTR0PTR        pNewR0      = NIL_RTR0PTR;
-        rc = SUPR3PageAllocEx(cRangePages, 0 /*fFlags*/, (void **)&pNew, &pNewR0, NULL /*paPages*/);
-        AssertLogRelMsgRCReturnStmt(rc, ("cbRamRange=%zu\n", cbRamRange), PGM_UNLOCK(pVM), rc);
+        AssertLogRelReturn(idxInsert <= pVM->pgm.s.RamRangeUnion.cLookupEntries, VERR_INTERNAL_ERROR_4);
+        Log(("PGMR3PhysMmioMap: Inserting ad hoc MMIO range #%x for %RGp-%RGp %s\n",
+             pMmioRamRange->idRange, GCPhys, GCPhysLast, pMmioRamRange->pszDesc));
+
+        Assert(PGM_PAGE_GET_TYPE(&pMmioRamRange->aPages[0]) == PGMPAGETYPE_MMIO);
+
+        /* We ASSUME that all the pages in the ad-hoc range are in the proper
+           state and all that and that we don't need to re-initialize them here. */
 
 #ifdef VBOX_WITH_NATIVE_NEM
         /* Notify NEM. */
-        uint8_t u2State = 0; /* (must have valid state as there can't be anything to preserve) */
         if (VM_IS_NEM_ENABLED(pVM))
         {
-            rc = NEMR3NotifyPhysMmioExMapEarly(pVM, GCPhys, cPages << GUEST_PAGE_SHIFT, 0 /*fFlags*/, NULL, NULL,
-                                               &u2State, &pNew->uNemRange);
-            AssertLogRelRCReturnStmt(rc, SUPR3PageFreeEx(pNew, cRangePages), rc);
-        }
-#endif
-
-        /* Initialize the range. */
-        pNew->pSelfR0       = pNewR0;
-        pNew->GCPhys        = GCPhys;
-        pNew->GCPhysLast    = GCPhysLast;
-        pNew->cb            = cb;
-        pNew->pszDesc       = pszDesc;
-        pNew->fFlags        = PGM_RAM_RANGE_FLAGS_AD_HOC_MMIO;
-        pNew->pvR3          = NULL;
-        pNew->paLSPages     = NULL;
-
-        uint32_t iPage = cPages;
-        while (iPage-- > 0)
-        {
-            PGM_PAGE_INIT_ZERO(&pNew->aPages[iPage], pVM, PGMPAGETYPE_MMIO);
+            uint8_t u2State = 0; /* (must have valid state as there can't be anything to preserve) */
+            rc = NEMR3NotifyPhysMmioExMapEarly(pVM, GCPhys, cb, 0 /*fFlags*/, NULL, NULL, &u2State, &pMmioRamRange->uNemRange);
+            AssertLogRelRCReturn(rc, rc);
+
+            uint32_t iPage = cb >> GUEST_PAGE_SHIFT;
+            while (iPage-- > 0)
+                PGM_PAGE_SET_NEM_STATE(&pMmioRamRange->aPages[iPage], u2State);
+        }
+#endif
+        /* Insert it into the lookup table (may in theory fail). */
+        rc = pgmR3PhysRamRangeInsertLookup(pVM, pMmioRamRange, GCPhys, &idxInsert);
+    }
+    if (RT_SUCCESS(rc))
+    {
+        /*
+         * Register the access handler.
+         */
+        rc = PGMHandlerPhysicalRegister(pVM, GCPhys, GCPhysLast, hType, uUser, pMmioRamRange->pszDesc);
+        if (RT_SUCCESS(rc))
+        {
 #ifdef VBOX_WITH_NATIVE_NEM
-            PGM_PAGE_SET_NEM_STATE(&pNew->aPages[iPage], u2State);
-#endif
-        }
-        Assert(PGM_PAGE_GET_TYPE(&pNew->aPages[0]) == PGMPAGETYPE_MMIO);
-
-        /* update the page count stats. */
-        pVM->pgm.s.cPureMmioPages += cPages;
-        pVM->pgm.s.cAllPages      += cPages;
-
-        /* link it */
-        pgmR3PhysLinkRamRange(pVM, pNew, pRamPrev);
-    }
-
-    /*
-     * Register the access handler.
-     */
-    rc = PGMHandlerPhysicalRegister(pVM, GCPhys, GCPhysLast, hType, uUser, pszDesc);
+            /* Late NEM notification (currently not used by anyone). */
+            if (VM_IS_NEM_ENABLED(pVM))
+            {
+                if (pOverlappingRange)
+                    rc = NEMR3NotifyPhysMmioExMapLate(pVM, GCPhys, cb, NEM_NOTIFY_PHYS_MMIO_EX_F_REPLACE,
+                                                      pOverlappingRange->pbR3 + (uintptr_t)(GCPhys - pOverlappingRange->GCPhys),
+                                                      NULL /*pvMmio2*/, NULL /*puNemRange*/);
+                else
+                    rc = NEMR3NotifyPhysMmioExMapLate(pVM, GCPhys, cb, 0 /*fFlags*/, NULL /*pvRam*/, NULL /*pvMmio2*/,
+                                                      &pMmioRamRange->uNemRange);
+                AssertLogRelRC(rc);
+            }
+            if (RT_SUCCESS(rc))
+#endif
+            {
+                pgmPhysInvalidatePageMapTLB(pVM);
+                return VINF_SUCCESS;
+            }
+
+            /*
+             * Failed, so revert it all as best as we can (the memory content in
+             * the overlapping case is gone).
+             */
+            PGMHandlerPhysicalDeregister(pVM, GCPhys);
+        }
+    }
+
+    if (!pOverlappingRange)
+    {
+#ifdef VBOX_WITH_NATIVE_NEM
+        /* Notify NEM about the sudden removal of the RAM range we just told it about. */
+        NEMR3NotifyPhysMmioExUnmap(pVM, GCPhys, cb, 0 /*fFlags*/, NULL /*pvRam*/, NULL /*pvMmio2*/,
+                                   NULL /*pu2State*/, &pMmioRamRange->uNemRange);
+#endif
+
+        /* Remove the ad hoc range from the lookup table. */
+        idxInsert -= 1;
+        pgmR3PhysRamRangeRemoveLookup(pVM, pMmioRamRange, &idxInsert);
+    }
+
+    pgmPhysInvalidatePageMapTLB(pVM);
+    return rc;
+}
+
+
+/**
+ * This is the interface IOM is using to map an MMIO region.
+ *
+ * It will check for conflicts and ensure that a RAM range structure
+ * is present before calling the PGMR3HandlerPhysicalRegister API to
+ * register the callbacks.
+ *
+ * @returns VBox status code.
+ *
+ * @param   pVM             The cross context VM structure.
+ * @param   pVCpu           The cross context virtual CPU structure of the calling EMT.
+ * @param   GCPhys          The start of the MMIO region.
+ * @param   cb              The size of the MMIO region.
+ * @param   idRamRange      The RAM range ID for the MMIO region as returned by
+ *                          PGMR3PhysMmioRegister().
+ * @param   hType           The physical access handler type registration.
+ * @param   uUser           The user argument.
+ * @thread  EMT(pVCpu)
+ */
+VMMR3_INT_DECL(int) PGMR3PhysMmioMap(PVM pVM, PVMCPU pVCpu, RTGCPHYS GCPhys, RTGCPHYS cb, uint16_t idRamRange,
+                                     PGMPHYSHANDLERTYPE hType, uint64_t uUser)
+{
+    /*
+     * Assert on some assumption.
+     */
+    VMCPU_ASSERT_EMT(pVCpu);
+    AssertReturn(!(cb & GUEST_PAGE_OFFSET_MASK), VERR_INVALID_PARAMETER);
+    AssertReturn(!(GCPhys & GUEST_PAGE_OFFSET_MASK), VERR_INVALID_PARAMETER);
+    RTGCPHYS const GCPhysLast = GCPhys + cb - 1U;
+    AssertReturn(GCPhysLast > GCPhys, VERR_INVALID_PARAMETER);
+#ifdef VBOX_STRICT
+    PCPGMPHYSHANDLERTYPEINT pType = pgmHandlerPhysicalTypeHandleToPtr(pVM, hType);
+    Assert(pType);
+    Assert(pType->enmKind == PGMPHYSHANDLERKIND_MMIO);
+#endif
+    AssertReturn(idRamRange <= pVM->pgm.s.idRamRangeMax && idRamRange > 0, VERR_INVALID_HANDLE);
+    PPGMRAMRANGE const pMmioRamRange = pVM->pgm.s.apRamRanges[idRamRange];
+    AssertReturn(pMmioRamRange, VERR_INVALID_HANDLE);
+    AssertReturn(pMmioRamRange->fFlags & PGM_RAM_RANGE_FLAGS_AD_HOC_MMIO, VERR_INVALID_HANDLE);
+    AssertReturn(pMmioRamRange->cb == cb, VERR_OUT_OF_RANGE);
+
+    /*
+     * Take the PGM lock and do the work.
+     */
+    int rc = PGM_LOCK(pVM);
+    AssertRCReturn(rc, rc);
+
+    rc = pgmR3PhysMmioMapLocked(pVM, pVCpu, GCPhys, cb, GCPhysLast, pMmioRamRange, hType, uUser);
+#ifdef VBOX_STRICT
+    pgmPhysAssertRamRangesLocked(pVM, false /*fInUpdate*/, false /*fRamRelaxed*/);
+#endif
+
+    PGM_UNLOCK(pVM);
+    return rc;
+}
+
+
+/**
+ * Worker for PGMR3PhysMmioUnmap that's called with the PGM lock held.
+ */
+static int pgmR3PhysMmioUnmapLocked(PVM pVM, PVMCPU pVCpu, RTGCPHYS const GCPhys, RTGCPHYS const cb,
+                                    RTGCPHYS const GCPhysLast, PPGMRAMRANGE const pMmioRamRange)
+{
+    /*
+     * Lookup the RAM range containing the region to make sure it is actually mapped.
+     */
+    uint32_t idxLookup = pgmR3PhysRamRangeFindOverlappingIndex(pVM, GCPhys, GCPhysLast);
+    AssertLogRelMsgReturn(idxLookup < pVM->pgm.s.RamRangeUnion.cLookupEntries,
+                          ("MMIO range not found at %RGp LB %RGp! (%s)\n", GCPhys, cb, pMmioRamRange->pszDesc),
+                          VERR_NOT_FOUND);
+
+    uint32_t const     idLookupRange = PGMRAMRANGELOOKUPENTRY_GET_ID(pVM->pgm.s.aRamRangeLookup[idxLookup]);
+    AssertLogRelReturn(idLookupRange != 0 && idLookupRange <= pVM->pgm.s.idRamRangeMax, VERR_INTERNAL_ERROR_5);
+    PPGMRAMRANGE const pLookupRange  = pVM->pgm.s.apRamRanges[idLookupRange];
+    AssertLogRelReturn(pLookupRange, VERR_INTERNAL_ERROR_4);
+
+    AssertLogRelMsgReturn(pLookupRange == pMmioRamRange || !PGM_RAM_RANGE_IS_AD_HOC(pLookupRange),
+                          ("MMIO unmap mixup at %RGp LB %RGp (%s) vs %RGp LB %RGp (%s)\n",
+                           GCPhys, cb, pMmioRamRange->pszDesc, pLookupRange->GCPhys, pLookupRange->cb, pLookupRange->pszDesc),
+                          VERR_NOT_FOUND);
+
+    /*
+     * Deregister the handler.  This should reset any aliases, so an ad hoc
+     * range will only contain MMIO type pages afterwards.
+     */
+    int rc = PGMHandlerPhysicalDeregister(pVM, GCPhys);
     if (RT_SUCCESS(rc))
     {
+        if (pLookupRange != pMmioRamRange)
+        {
+            /*
+             * Turn the pages back into RAM pages.
+             */
+            Log(("pgmR3PhysMmioUnmapLocked: Reverting MMIO range %RGp-%RGp (%s) in %RGp-%RGp (%s) to RAM.\n",
+                 GCPhys, GCPhysLast, pMmioRamRange->pszDesc,
+                 pLookupRange->GCPhys, pLookupRange->GCPhysLast, pLookupRange->pszDesc));
+
+            RTGCPHYS const offRange = GCPhys - pLookupRange->GCPhys;
+            uint32_t       iPage    = offRange >> GUEST_PAGE_SHIFT;
+            uint32_t       cLeft    = cb >> GUEST_PAGE_SHIFT;
+            while (cLeft--)
+            {
+                PPGMPAGE pPage = &pLookupRange->aPages[iPage];
+                AssertMsg(   (PGM_PAGE_IS_MMIO(pPage) && PGM_PAGE_IS_ZERO(pPage))
+                          //|| PGM_PAGE_GET_TYPE(pPage) == PGMPAGETYPE_MMIO2_ALIAS_MMIO
+                          //|| PGM_PAGE_GET_TYPE(pPage) == PGMPAGETYPE_SPECIAL_ALIAS_MMIO
+                          , ("%RGp %R[pgmpage]\n", pLookupRange->GCPhys + ((RTGCPHYS)iPage << GUEST_PAGE_SHIFT), pPage));
+/** @todo this isn't entirely correct, is it now... aliases must be converted
+ * to zero pages as they won't be.  however, shouldn't
+ * PGMHandlerPhysicalDeregister deal with this already? */
+                if (PGM_PAGE_IS_MMIO_OR_ALIAS(pPage))
+                    PGM_PAGE_SET_TYPE(pVM, pPage, PGMPAGETYPE_RAM);
+                iPage++;
+            }
+
 #ifdef VBOX_WITH_NATIVE_NEM
-        /* Late NEM notification. */
-        if (VM_IS_NEM_ENABLED(pVM))
-        {
-            uint32_t const fNemNotify = (fRamExists ? NEM_NOTIFY_PHYS_MMIO_EX_F_REPLACE : 0);
-            rc = NEMR3NotifyPhysMmioExMapLate(pVM, GCPhys, GCPhysLast - GCPhys + 1, fNemNotify,
-                                              fRamExists ? (uint8_t *)pRam->pvR3 + (uintptr_t)(GCPhys - pRam->GCPhys) : NULL,
-                                              NULL, !fRamExists ? &pRam->uNemRange : NULL);
-            AssertLogRelRCReturn(rc, rc);
-        }
-#endif
-    }
-    /** @todo the phys handler failure handling isn't complete, esp. wrt NEM. */
-    else if (!fRamExists)
-    {
-        pVM->pgm.s.cPureMmioPages -= cb >> GUEST_PAGE_SHIFT;
-        pVM->pgm.s.cAllPages      -= cb >> GUEST_PAGE_SHIFT;
-
-        /* remove the ad hoc range. */
-        pgmR3PhysUnlinkRamRange2(pVM, pNew, pRamPrev);
-        pNew->cb = pNew->GCPhys = pNew->GCPhysLast = NIL_RTGCPHYS;
-        SUPR3PageFreeEx(pRam, RT_ALIGN_Z(RT_UOFFSETOF_DYN(PGMRAMRANGE, aPages[cb >> GUEST_PAGE_SHIFT]),
-                                         HOST_PAGE_SIZE) >> HOST_PAGE_SHIFT);
-    }
+            /* Notify REM (failure will probably leave things in a non-working state). */
+            if (VM_IS_NEM_ENABLED(pVM))
+            {
+                uint8_t u2State = UINT8_MAX;
+                rc = NEMR3NotifyPhysMmioExUnmap(pVM, GCPhys, GCPhysLast - GCPhys + 1, NEM_NOTIFY_PHYS_MMIO_EX_F_REPLACE,
+                                                pLookupRange->pbR3 ? pLookupRange->pbR3 + GCPhys - pLookupRange->GCPhys : NULL,
+                                                NULL, &u2State, &pLookupRange->uNemRange);
+                AssertLogRelRC(rc);
+                /** @todo status code propagation here... This is likely fatal, right?  */
+                if (u2State != UINT8_MAX)
+                    pgmPhysSetNemStateForPages(&pLookupRange->aPages[(GCPhys - pLookupRange->GCPhys) >> GUEST_PAGE_SHIFT],
+                                               cb >> GUEST_PAGE_SHIFT, u2State);
+            }
+#endif
+        }
+        else
+        {
+            /*
+             * Unlink the ad hoc range.
+             */
+#ifdef VBOX_STRICT
+            uint32_t iPage = cb >> GUEST_PAGE_SHIFT;
+            while (iPage-- > 0)
+            {
+                PPGMPAGE const pPage = &pMmioRamRange->aPages[iPage];
+                Assert(PGM_PAGE_IS_MMIO(pPage));
+            }
+#endif
+
+            Log(("pgmR3PhysMmioUnmapLocked: Unmapping ad hoc MMIO range for %RGp-%RGp %s\n",
+                 GCPhys, GCPhysLast, pMmioRamRange->pszDesc));
+
+#ifdef VBOX_WITH_NATIVE_NEM
+            if (VM_IS_NEM_ENABLED(pVM)) /* Notify REM before we unlink the range. */
+            {
+                rc = NEMR3NotifyPhysMmioExUnmap(pVM, GCPhys, GCPhysLast - GCPhys + 1, 0 /*fFlags*/,
+                                                NULL, NULL, NULL, &pMmioRamRange->uNemRange);
+                AssertLogRelRCReturn(rc, rc); /* we're up the creek if this hits. */
+            }
+#endif
+
+            pgmR3PhysRamRangeRemoveLookup(pVM, pMmioRamRange, &idxLookup);
+        }
+    }
+
+    /* Force a PGM pool flush as guest ram references have been changed. */
+    /** @todo Not entirely SMP safe; assuming for now the guest takes care of
+     *       this internally (not touch mapped mmio while changing the mapping). */
+    pVCpu->pgm.s.fSyncFlags |= PGM_SYNC_CLEAR_PGM_POOL;
+    VMCPU_FF_SET(pVCpu, VMCPU_FF_PGM_SYNC_CR3);
+
     pgmPhysInvalidatePageMapTLB(pVM);
-
-    PGM_UNLOCK(pVM);
+    pgmPhysInvalidRamRangeTlbs(pVM);
+
     return rc;
 }
@@ -2473,139 +2868,35 @@
  * @param   GCPhys          The start of the MMIO region.
  * @param   cb              The size of the MMIO region.
+ * @param   idRamRange      The RAM range ID for the MMIO region as returned by
+ *                          PGMR3PhysMmioRegister().
  * @thread  EMT(pVCpu)
  */
-VMMR3_INT_DECL(int) PGMR3PhysMmioDeregister(PVM pVM, PVMCPU pVCpu, RTGCPHYS GCPhys, RTGCPHYS cb)
-{
+VMMR3_INT_DECL(int) PGMR3PhysMmioUnmap(PVM pVM, PVMCPU pVCpu, RTGCPHYS GCPhys, RTGCPHYS cb, uint16_t idRamRange)
+{
+    /*
+     * Input validation.
+     */
     VMCPU_ASSERT_EMT(pVCpu);
-
+    AssertReturn(!(cb & GUEST_PAGE_OFFSET_MASK), VERR_INVALID_PARAMETER);
+    AssertReturn(!(GCPhys & GUEST_PAGE_OFFSET_MASK), VERR_INVALID_PARAMETER);
+    RTGCPHYS const GCPhysLast = GCPhys + cb - 1U;
+    AssertReturn(GCPhysLast > GCPhys, VERR_INVALID_PARAMETER);
+    AssertReturn(idRamRange <= pVM->pgm.s.idRamRangeMax && idRamRange > 0, VERR_INVALID_HANDLE);
+    PPGMRAMRANGE const pMmioRamRange = pVM->pgm.s.apRamRanges[idRamRange];
+    AssertReturn(pMmioRamRange, VERR_INVALID_HANDLE);
+    AssertReturn(pMmioRamRange->fFlags & PGM_RAM_RANGE_FLAGS_AD_HOC_MMIO, VERR_INVALID_HANDLE);
+    AssertReturn(pMmioRamRange->cb == cb, VERR_OUT_OF_RANGE);
+
+    /*
+     * Take the PGM lock and do what's asked.
+     */
     int rc = PGM_LOCK(pVM);
     AssertRCReturn(rc, rc);
 
-    /*
-     * First deregister the handler, then check if we should remove the ram range.
-     */
-    rc = PGMHandlerPhysicalDeregister(pVM, GCPhys);
-    if (RT_SUCCESS(rc))
-    {
-        RTGCPHYS        GCPhysLast  = GCPhys + (cb - 1);
-        PPGMRAMRANGE    pRamPrev    = NULL;
-        PPGMRAMRANGE    pRam        = pVM->pgm.s.pRamRangesXR3;
-        while (pRam && GCPhysLast >= pRam->GCPhys)
-        {
-            /** @todo We're being a bit too careful here. rewrite. */
-            if (    GCPhysLast == pRam->GCPhysLast
-                &&  GCPhys     == pRam->GCPhys)
-            {
-                Assert(pRam->cb == cb);
-
-                /*
-                 * See if all the pages are dead MMIO pages.
-                 */
-                uint32_t const  cGuestPages = cb >> GUEST_PAGE_SHIFT;
-                bool            fAllMMIO    = true;
-                uint32_t        iPage       = 0;
-                uint32_t        cLeft       = cGuestPages;
-                while (cLeft-- > 0)
-                {
-                    PPGMPAGE    pPage       = &pRam->aPages[iPage];
-                    if (   !PGM_PAGE_IS_MMIO_OR_ALIAS(pPage)
-                        /*|| not-out-of-action later */)
-                    {
-                        fAllMMIO = false;
-                        AssertMsgFailed(("%RGp %R[pgmpage]\n", pRam->GCPhys + ((RTGCPHYS)iPage << GUEST_PAGE_SHIFT), pPage));
-                        break;
-                    }
-                    Assert(   PGM_PAGE_IS_ZERO(pPage)
-                           || PGM_PAGE_GET_TYPE(pPage) == PGMPAGETYPE_MMIO2_ALIAS_MMIO
-                           || PGM_PAGE_GET_TYPE(pPage) == PGMPAGETYPE_SPECIAL_ALIAS_MMIO);
-                    iPage++;
-                }
-                if (fAllMMIO)
-                {
-                    /*
-                     * Ad-hoc range, unlink and free it.
-                     */
-                    Log(("PGMR3PhysMmioDeregister: Freeing ad hoc MMIO range for %RGp-%RGp %s\n",
-                         GCPhys, GCPhysLast, pRam->pszDesc));
-                    /** @todo check the ad-hoc flags? */
-
-#ifdef VBOX_WITH_NATIVE_NEM
-                    if (VM_IS_NEM_ENABLED(pVM)) /* Notify REM before we unlink the range. */
-                    {
-                        rc = NEMR3NotifyPhysMmioExUnmap(pVM, GCPhys, GCPhysLast - GCPhys + 1, 0 /*fFlags*/,
-                                                        NULL, NULL, NULL, &pRam->uNemRange);
-                        AssertLogRelRCReturn(rc, rc);
-                    }
-#endif
-
-                    pVM->pgm.s.cAllPages      -= cGuestPages;
-                    pVM->pgm.s.cPureMmioPages -= cGuestPages;
-
-                    pgmR3PhysUnlinkRamRange2(pVM, pRam, pRamPrev);
-                    const uint32_t cPages      = pRam->cb >> GUEST_PAGE_SHIFT;
-                    const size_t   cbRamRange  = RT_UOFFSETOF_DYN(PGMRAMRANGE, aPages[cPages]);
-                    pRam->cb = pRam->GCPhys = pRam->GCPhysLast = NIL_RTGCPHYS;
-                    SUPR3PageFreeEx(pRam, RT_ALIGN_Z(cbRamRange, HOST_PAGE_SIZE) >> HOST_PAGE_SHIFT);
-                    break;
-                }
-            }
-
-            /*
-             * Range match? It will all be within one range (see PGMAllHandler.cpp).
-             */
-            if (    GCPhysLast >= pRam->GCPhys
-                &&  GCPhys     <= pRam->GCPhysLast)
-            {
-                Assert(GCPhys     >= pRam->GCPhys);
-                Assert(GCPhysLast <= pRam->GCPhysLast);
-
-                /*
-                 * Turn the pages back into RAM pages.
-                 */
-                uint32_t iPage = (GCPhys - pRam->GCPhys) >> GUEST_PAGE_SHIFT;
-                uint32_t cLeft = cb >> GUEST_PAGE_SHIFT;
-                while (cLeft--)
-                {
-                    PPGMPAGE pPage = &pRam->aPages[iPage];
-                    AssertMsg(   (PGM_PAGE_IS_MMIO(pPage) && PGM_PAGE_IS_ZERO(pPage))
-                              || PGM_PAGE_GET_TYPE(pPage) == PGMPAGETYPE_MMIO2_ALIAS_MMIO
-                              || PGM_PAGE_GET_TYPE(pPage) == PGMPAGETYPE_SPECIAL_ALIAS_MMIO,
-                              ("%RGp %R[pgmpage]\n", pRam->GCPhys + ((RTGCPHYS)iPage << GUEST_PAGE_SHIFT), pPage));
-                    if (PGM_PAGE_IS_MMIO_OR_ALIAS(pPage))
-                        PGM_PAGE_SET_TYPE(pVM, pPage, PGMPAGETYPE_RAM);
-                    iPage++;
-                }
-
-#ifdef VBOX_WITH_NATIVE_NEM
-                /* Notify REM (failure will probably leave things in a non-working state). */
-                if (VM_IS_NEM_ENABLED(pVM))
-                {
-                    uint8_t u2State = UINT8_MAX;
-                    rc = NEMR3NotifyPhysMmioExUnmap(pVM, GCPhys, GCPhysLast - GCPhys + 1, NEM_NOTIFY_PHYS_MMIO_EX_F_REPLACE,
-                                                    pRam->pvR3 ? (uint8_t *)pRam->pvR3 + GCPhys - pRam->GCPhys : NULL,
-                                                    NULL, &u2State, &pRam->uNemRange);
-                    AssertLogRelRCReturn(rc, rc);
-                    if (u2State != UINT8_MAX)
-                        pgmPhysSetNemStateForPages(&pRam->aPages[(GCPhys - pRam->GCPhys) >> GUEST_PAGE_SHIFT],
-                                                   cb >> GUEST_PAGE_SHIFT, u2State);
-                }
-#endif
-                break;
-            }
-
-            /* next */
-            pRamPrev = pRam;
-            pRam = pRam->pNextR3;
-        }
-    }
-
-    /* Force a PGM pool flush as guest ram references have been changed. */
-    /** @todo Not entirely SMP safe; assuming for now the guest takes care of
-     *       this internally (not touch mapped mmio while changing the mapping). */
-    pVCpu->pgm.s.fSyncFlags |= PGM_SYNC_CLEAR_PGM_POOL;
-    VMCPU_FF_SET(pVCpu, VMCPU_FF_PGM_SYNC_CR3);
-
-    pgmPhysInvalidatePageMapTLB(pVM);
-    pgmPhysInvalidRamRangeTlbs(pVM);
+    rc = pgmR3PhysMmioUnmapLocked(pVM, pVCpu, GCPhys, cb, GCPhysLast, pMmioRamRange);
+#ifdef VBOX_STRICT
+    pgmPhysAssertRamRangesLocked(pVM, false /*fInUpdate*/, false /*fRamRelaxed*/);
+#endif
+
     PGM_UNLOCK(pVM);
     return rc;
@@ -2619,76 +2910,104 @@
 
 /**
- * Locate a MMIO2 range.
- *
- * @returns Pointer to the MMIO2 range.
+ * Validates the claim to an MMIO2 range and returns the pointer to it.
+ *
+ * @returns The MMIO2 entry index on success, negative error status on failure.
+ * @param   pVM             The cross context VM structure.
+ * @param   pDevIns         The device instance owning the region.
+ * @param   hMmio2          Handle to look up.
+ * @param   pcChunks        Where to return the number of chunks associated with
+ *                          this handle.
+ */
+static int32_t pgmR3PhysMmio2ResolveHandle(PVM pVM, PPDMDEVINS pDevIns, PGMMMIO2HANDLE hMmio2, uint32_t *pcChunks)
+{
+    *pcChunks = 0;
+    uint32_t const idxFirst     = hMmio2 - 1U;
+    uint32_t const cMmio2Ranges = RT_MIN(pVM->pgm.s.cMmio2Ranges, RT_ELEMENTS(pVM->pgm.s.aMmio2Ranges));
+    AssertReturn(idxFirst < cMmio2Ranges, VERR_INVALID_HANDLE);
+
+    PPGMREGMMIO2RANGE const pFirst = &pVM->pgm.s.aMmio2Ranges[idxFirst];
+    AssertReturn(pFirst->idMmio2   == hMmio2, VERR_INVALID_HANDLE);
+    AssertReturn((pFirst->fFlags & PGMREGMMIO2RANGE_F_FIRST_CHUNK), VERR_INVALID_HANDLE);
+    AssertReturn(pFirst->pDevInsR3 == pDevIns && RT_VALID_PTR(pDevIns), VERR_NOT_OWNER);
+
+    /* Figure out how many chunks this handle spans. */
+    if (pFirst->fFlags & PGMREGMMIO2RANGE_F_LAST_CHUNK)
+        *pcChunks = 1;
+    else
+    {
+        uint32_t cChunks = 1;
+        for (uint32_t idx = idxFirst + 1;; idx++)
+        {
+            cChunks++;
+            AssertReturn(idx < cMmio2Ranges, VERR_INTERNAL_ERROR_2);
+            PPGMREGMMIO2RANGE const pCur = &pVM->pgm.s.aMmio2Ranges[idx];
+            AssertLogRelMsgReturn(   pCur->pDevInsR3 == pDevIns
+                                  && pCur->idMmio2   == idx + 1
+                                  && pCur->iSubDev   == pFirst->iSubDev
+                                  && pCur->iRegion   == pFirst->iRegion
+                                  && !(pCur->fFlags & PGMREGMMIO2RANGE_F_FIRST_CHUNK),
+                                  ("cur: %p/%#x/%#x/%#x/%#x/%s;  first: %p/%#x/%#x/%#x/%#x/%s\n",
+                                   pCur->pDevInsR3, pCur->idMmio2, pCur->iSubDev, pCur->iRegion, pCur->fFlags,
+                                   pVM->pgm.s.apMmio2RamRanges[idx]->pszDesc,
+                                   pDevIns, idx + 1, pFirst->iSubDev, pFirst->iRegion, pFirst->fFlags,
+                                   pVM->pgm.s.apMmio2RamRanges[idxFirst]->pszDesc),
+                                  VERR_INTERNAL_ERROR_3);
+            if (pCur->fFlags & PGMREGMMIO2RANGE_F_LAST_CHUNK)
+                break;
+        }
+        *pcChunks = cChunks;
+    }
+
+    return (int32_t)idxFirst;
+}
+
+
+/**
+ * Check if a device has already registered a MMIO2 region.
+ *
+ * @returns NULL if not registered, otherwise pointer to the MMIO2.
  * @param   pVM             The cross context VM structure.
  * @param   pDevIns         The device instance owning the region.
  * @param   iSubDev         The sub-device number.
  * @param   iRegion         The region.
- * @param   hMmio2          Handle to look up.  If NIL, use the @a iSubDev and
- *                          @a iRegion.
- */
-DECLINLINE(PPGMREGMMIO2RANGE) pgmR3PhysMmio2Find(PVM pVM, PPDMDEVINS pDevIns, uint32_t iSubDev,
-                                                 uint32_t iRegion, PGMMMIO2HANDLE hMmio2)
-{
-    if (hMmio2 != NIL_PGMMMIO2HANDLE)
-    {
-        if (hMmio2 <= RT_ELEMENTS(pVM->pgm.s.apMmio2RangesR3) && hMmio2 != 0)
-        {
-            PPGMREGMMIO2RANGE pCur = pVM->pgm.s.apMmio2RangesR3[hMmio2 - 1];
-            if (pCur && pCur->pDevInsR3 == pDevIns)
-            {
-                Assert(pCur->idMmio2 == hMmio2);
-                AssertReturn(pCur->fFlags & PGMREGMMIO2RANGE_F_FIRST_CHUNK, NULL);
-                return pCur;
-            }
-            Assert(!pCur);
-        }
-        for (PPGMREGMMIO2RANGE pCur = pVM->pgm.s.pRegMmioRangesR3; pCur; pCur = pCur->pNextR3)
-            if (pCur->idMmio2 == hMmio2)
-            {
-                AssertBreak(pCur->pDevInsR3 == pDevIns);
-                AssertReturn(pCur->fFlags & PGMREGMMIO2RANGE_F_FIRST_CHUNK, NULL);
-                return pCur;
-            }
-    }
-    else
-    {
-        /*
-         * Search the list.  There shouldn't be many entries.
-         */
-        /** @todo Optimize this lookup! There may now be many entries and it'll
-         *        become really slow when doing MMR3HyperMapMMIO2 and similar. */
-        for (PPGMREGMMIO2RANGE pCur = pVM->pgm.s.pRegMmioRangesR3; pCur; pCur = pCur->pNextR3)
-            if (   pCur->pDevInsR3 == pDevIns
-                && pCur->iRegion == iRegion
-                && pCur->iSubDev == iSubDev)
-                return pCur;
-    }
+ */
+DECLINLINE(PPGMREGMMIO2RANGE) pgmR3PhysMmio2Find(PVM pVM, PPDMDEVINS pDevIns, uint32_t iSubDev, uint32_t iRegion)
+{
+    /*
+     * Search the array.  There shouldn't be many entries.
+     */
+    uint32_t idx = RT_MIN(pVM->pgm.s.cMmio2Ranges, RT_ELEMENTS(pVM->pgm.s.aMmio2Ranges));
+    while (idx-- > 0)
+        if (RT_LIKELY(   pVM->pgm.s.aMmio2Ranges[idx].pDevInsR3 != pDevIns
+                      || pVM->pgm.s.aMmio2Ranges[idx].iRegion   != iRegion
+                      || pVM->pgm.s.aMmio2Ranges[idx].iSubDev   != iSubDev))
+        { /* likely */ }
+        else
+            return &pVM->pgm.s.aMmio2Ranges[idx];
     return NULL;
 }
 
-
 /**
  * Worker for PGMR3PhysMmio2ControlDirtyPageTracking and PGMR3PhysMmio2Map.
  */
-static int pgmR3PhysMmio2EnableDirtyPageTracing(PVM pVM, PPGMREGMMIO2RANGE pFirstMmio2)
+static int pgmR3PhysMmio2EnableDirtyPageTracing(PVM pVM, uint32_t idx, uint32_t cChunks)
 {
     int rc = VINF_SUCCESS;
-    for (PPGMREGMMIO2RANGE pCurMmio2 = pFirstMmio2; pCurMmio2; pCurMmio2 = pCurMmio2->pNextR3)
-    {
-        Assert(!(pCurMmio2->fFlags & PGMREGMMIO2RANGE_F_IS_TRACKING));
-        int rc2 = pgmHandlerPhysicalExRegister(pVM, pCurMmio2->pPhysHandlerR3, pCurMmio2->RamRange.GCPhys,
-                                               pCurMmio2->RamRange.GCPhysLast);
-        AssertLogRelMsgRC(rc2, ("%#RGp-%#RGp %s failed -> %Rrc\n", pCurMmio2->RamRange.GCPhys, pCurMmio2->RamRange.GCPhysLast,
-                                pCurMmio2->RamRange.pszDesc, rc2));
+    while (cChunks-- > 0)
+    {
+        PPGMREGMMIO2RANGE const pMmio2    = &pVM->pgm.s.aMmio2Ranges[idx];
+        PPGMRAMRANGE const      pRamRange = pVM->pgm.s.apMmio2RamRanges[idx];
+
+        Assert(!(pMmio2->fFlags & PGMREGMMIO2RANGE_F_IS_TRACKING));
+        int rc2 = pgmHandlerPhysicalExRegister(pVM, pMmio2->pPhysHandlerR3, pRamRange->GCPhys, pRamRange->GCPhysLast);
         if (RT_SUCCESS(rc2))
-            pCurMmio2->fFlags |= PGMREGMMIO2RANGE_F_IS_TRACKING;
-        else if (RT_SUCCESS(rc))
-            rc = rc2;
-        if (pCurMmio2->fFlags & PGMREGMMIO2RANGE_F_LAST_CHUNK)
-            return rc;
-    }
-    AssertFailed();
+            pMmio2->fFlags |= PGMREGMMIO2RANGE_F_IS_TRACKING;
+        else
+            AssertLogRelMsgFailedStmt(("%#RGp-%#RGp %s failed -> %Rrc\n",
+                                       pRamRange->GCPhys, pRamRange->GCPhysLast, pRamRange->pszDesc, rc2),
+                                      rc = RT_SUCCESS(rc) ? rc2 : rc);
+
+        idx++;
+    }
     return rc;
 }
@@ -2698,217 +3017,26 @@
  * Worker for PGMR3PhysMmio2ControlDirtyPageTracking and PGMR3PhysMmio2Unmap.
  */
-static int pgmR3PhysMmio2DisableDirtyPageTracing(PVM pVM, PPGMREGMMIO2RANGE pFirstMmio2)
-{
-    for (PPGMREGMMIO2RANGE pCurMmio2 = pFirstMmio2; pCurMmio2; pCurMmio2 = pCurMmio2->pNextR3)
-    {
-        if (pCurMmio2->fFlags & PGMREGMMIO2RANGE_F_IS_TRACKING)
-        {
-            int rc2 = pgmHandlerPhysicalExDeregister(pVM, pCurMmio2->pPhysHandlerR3);
-            AssertLogRelMsgRC(rc2, ("%#RGp-%#RGp %s failed -> %Rrc\n", pCurMmio2->RamRange.GCPhys, pCurMmio2->RamRange.GCPhysLast,
-                                    pCurMmio2->RamRange.pszDesc, rc2));
-            pCurMmio2->fFlags &= ~PGMREGMMIO2RANGE_F_IS_TRACKING;
-        }
-        if (pCurMmio2->fFlags & PGMREGMMIO2RANGE_F_LAST_CHUNK)
-            return VINF_SUCCESS;
-    }
-    AssertFailed();
-    return VINF_SUCCESS;
-
-}
-
-
-/**
- * Calculates the number of chunks
- *
- * @returns Number of registration chunk needed.
- * @param   pVM             The cross context VM structure.
- * @param   cb              The size of the MMIO/MMIO2 range.
- * @param   pcPagesPerChunk Where to return the number of pages tracked by each
- *                          chunk.  Optional.
- * @param   pcbChunk        Where to return the guest mapping size for a chunk.
- */
-static uint16_t pgmR3PhysMmio2CalcChunkCount(PVM pVM, RTGCPHYS cb, uint32_t *pcPagesPerChunk, uint32_t *pcbChunk)
-{
-    RT_NOREF_PV(pVM); /* without raw mode */
-
-    /*
-     * This is the same calculation as PGMR3PhysRegisterRam does, except we'll be
-     * needing a few bytes extra the PGMREGMMIO2RANGE structure.
-     *
-     * Note! In additions, we've got a 24 bit sub-page range for MMIO2 ranges, leaving
-     *       us with an absolute maximum of 16777215 pages per chunk (close to 64 GB).
-     */
-    uint32_t const cPagesPerChunk = _4M;
-    Assert(RT_ALIGN_32(cPagesPerChunk, X86_PD_PAE_SHIFT - X86_PAGE_SHIFT)); /* NEM large page requirement: 1GB pages. */
-    uint32_t const cbChunk       = RT_UOFFSETOF_DYN(PGMREGMMIO2RANGE, RamRange.aPages[cPagesPerChunk]);
-    AssertRelease(cPagesPerChunk < _16M);
-
-    if (pcbChunk)
-        *pcbChunk = cbChunk;
-    if (pcPagesPerChunk)
-        *pcPagesPerChunk = cPagesPerChunk;
-
-    /* Calc the number of chunks we need. */
-    RTGCPHYS const cGuestPages = cb >> GUEST_PAGE_SHIFT;
-    uint16_t cChunks = (uint16_t)((cGuestPages + cPagesPerChunk - 1) / cPagesPerChunk);
-    AssertRelease((RTGCPHYS)cChunks * cPagesPerChunk >= cGuestPages);
-    return cChunks;
-}
-
-
-/**
- * Worker for PGMR3PhysMMIO2Register that allocates and the PGMREGMMIO2RANGE
- * structures and does basic initialization.
- *
- * Caller must set type specfic members and initialize the PGMPAGE structures.
- *
- * This was previously also used by PGMR3PhysMmio2PreRegister, a function for
- * pre-registering MMIO that was later (6.1) replaced by a new handle based IOM
- * interface.  The reference to caller and type above is purely historical.
- *
- * @returns VBox status code.
- * @param   pVM             The cross context VM structure.
- * @param   pDevIns         The device instance owning the region.
- * @param   iSubDev         The sub-device number (internal PCI config number).
- * @param   iRegion         The region number.  If the MMIO2 memory is a PCI
- *                          I/O region this number has to be the number of that
- *                          region. Otherwise it can be any number safe
- *                          UINT8_MAX.
- * @param   cb              The size of the region.  Must be page aligned.
- * @param   fFlags          PGMPHYS_MMIO2_FLAGS_XXX.
- * @param   idMmio2         The MMIO2 ID for the first chunk.
- * @param   pszDesc         The description.
- * @param   ppHeadRet       Where to return the pointer to the first
- *                          registration chunk.
- *
- * @thread  EMT
- */
-static int pgmR3PhysMmio2Create(PVM pVM, PPDMDEVINS pDevIns, uint32_t iSubDev, uint32_t iRegion, RTGCPHYS cb, uint32_t fFlags,
-                                uint8_t idMmio2, const char *pszDesc, PPGMREGMMIO2RANGE *ppHeadRet)
-{
-    /*
-     * Figure out how many chunks we need and of which size.
-     */
-    uint32_t cPagesPerChunk;
-    uint16_t cChunks = pgmR3PhysMmio2CalcChunkCount(pVM, cb, &cPagesPerChunk, NULL);
-    AssertReturn(cChunks, VERR_PGM_PHYS_MMIO_EX_IPE);
-
-    /*
-     * Allocate the chunks.
-     */
-    PPGMREGMMIO2RANGE *ppNext = ppHeadRet;
-    *ppNext = NULL;
-
+static int pgmR3PhysMmio2DisableDirtyPageTracing(PVM pVM, uint32_t idx, uint32_t cChunks)
+{
     int rc = VINF_SUCCESS;
-    uint32_t cPagesLeft = cb >> GUEST_PAGE_SHIFT;
-    for (uint16_t iChunk = 0; iChunk < cChunks && RT_SUCCESS(rc); iChunk++, idMmio2++)
-    {
-        /*
-         * We currently do a single RAM range for the whole thing.  This will
-         * probably have to change once someone needs really large MMIO regions,
-         * as we will be running into SUPR3PageAllocEx limitations and such.
-         */
-        const uint32_t   cPagesTrackedByChunk = RT_MIN(cPagesLeft, cPagesPerChunk);
-        const size_t     cbRange = RT_UOFFSETOF_DYN(PGMREGMMIO2RANGE, RamRange.aPages[cPagesTrackedByChunk]);
-        PPGMREGMMIO2RANGE pNew    = NULL;
-
-        /*
-         * Allocate memory for the registration structure.
-         */
-        size_t const cChunkPages  = RT_ALIGN_Z(cbRange, HOST_PAGE_SIZE) >> HOST_PAGE_SHIFT;
-        size_t const cbChunk      = (1 + cChunkPages + 1) << HOST_PAGE_SHIFT;
-        AssertLogRelBreakStmt(cbChunk == (uint32_t)cbChunk, rc = VERR_OUT_OF_RANGE);
-        RTR0PTR      R0PtrChunk   = NIL_RTR0PTR;
-        void        *pvChunk      = NULL;
-        rc = SUPR3PageAllocEx(cChunkPages, 0 /*fFlags*/, &pvChunk, &R0PtrChunk, NULL /*paPages*/);
-        AssertLogRelMsgRCBreak(rc, ("rc=%Rrc, cChunkPages=%#zx\n", rc, cChunkPages));
-
-        Assert(R0PtrChunk != NIL_RTR0PTR || PGM_IS_IN_NEM_MODE(pVM));
-        RT_BZERO(pvChunk, cChunkPages << HOST_PAGE_SHIFT);
-
-        pNew = (PPGMREGMMIO2RANGE)pvChunk;
-        pNew->RamRange.fFlags   = PGM_RAM_RANGE_FLAGS_FLOATING;
-        pNew->RamRange.pSelfR0  = R0PtrChunk + RT_UOFFSETOF(PGMREGMMIO2RANGE, RamRange);
-
-        /*
-         * Initialize the registration structure (caller does specific bits).
-         */
-        pNew->pDevInsR3             = pDevIns;
-        //pNew->pvR3                = NULL;
-        //pNew->pNext               = NULL;
-        if (iChunk == 0)
-            pNew->fFlags |= PGMREGMMIO2RANGE_F_FIRST_CHUNK;
-        if (iChunk + 1 == cChunks)
-            pNew->fFlags |= PGMREGMMIO2RANGE_F_LAST_CHUNK;
-        if (fFlags & PGMPHYS_MMIO2_FLAGS_TRACK_DIRTY_PAGES)
-            pNew->fFlags |= PGMREGMMIO2RANGE_F_TRACK_DIRTY_PAGES;
-        pNew->iSubDev               = iSubDev;
-        pNew->iRegion               = iRegion;
-        pNew->idSavedState          = UINT8_MAX;
-        pNew->idMmio2               = idMmio2;
-        //pNew->pPhysHandlerR3      = NULL;
-        //pNew->paLSPages           = NULL;
-        pNew->RamRange.GCPhys       = NIL_RTGCPHYS;
-        pNew->RamRange.GCPhysLast   = NIL_RTGCPHYS;
-        pNew->RamRange.pszDesc      = pszDesc;
-        pNew->RamRange.cb           = pNew->cbReal = (RTGCPHYS)cPagesTrackedByChunk << X86_PAGE_SHIFT;
-        pNew->RamRange.fFlags      |= PGM_RAM_RANGE_FLAGS_AD_HOC_MMIO_EX;
-        pNew->RamRange.uNemRange    = UINT32_MAX;
-        //pNew->RamRange.pvR3       = NULL;
-        //pNew->RamRange.paLSPages  = NULL;
-
-        *ppNext = pNew;
-        ASMCompilerBarrier();
-        cPagesLeft -= cPagesTrackedByChunk;
-        ppNext = &pNew->pNextR3;
-
-        /*
-         * Pre-allocate a handler if we're tracking dirty pages, unless NEM takes care of this.
-         */
-        if (   (fFlags & PGMPHYS_MMIO2_FLAGS_TRACK_DIRTY_PAGES)
-#ifdef VBOX_WITH_PGM_NEM_MODE
-            && (!VM_IS_NEM_ENABLED(pVM) || !NEMR3IsMmio2DirtyPageTrackingSupported(pVM))
-#endif
-            )
-
-        {
-            rc = pgmHandlerPhysicalExCreate(pVM, pVM->pgm.s.hMmio2DirtyPhysHandlerType, idMmio2, pszDesc, &pNew->pPhysHandlerR3);
-            AssertLogRelMsgRCBreak(rc, ("idMmio2=%zu\n", idMmio2));
-        }
-    }
-    Assert(cPagesLeft == 0);
-
-    if (RT_SUCCESS(rc))
-    {
-        Assert((*ppHeadRet)->fFlags & PGMREGMMIO2RANGE_F_FIRST_CHUNK);
-        return VINF_SUCCESS;
-    }
-
-    /*
-     * Free floating ranges.
-     */
-    while (*ppHeadRet)
-    {
-        PPGMREGMMIO2RANGE pFree = *ppHeadRet;
-        *ppHeadRet = pFree->pNextR3;
-
-        if (pFree->pPhysHandlerR3)
-        {
-            pgmHandlerPhysicalExDestroy(pVM, pFree->pPhysHandlerR3);
-            pFree->pPhysHandlerR3 = NULL;
-        }
-
-        if (pFree->RamRange.fFlags & PGM_RAM_RANGE_FLAGS_FLOATING)
-        {
-            const size_t    cbRange     = RT_UOFFSETOF_DYN(PGMREGMMIO2RANGE,
-                                                           RamRange.aPages[pFree->RamRange.cb >> X86_PAGE_SHIFT]);
-            size_t const    cChunkPages = RT_ALIGN_Z(cbRange, HOST_PAGE_SIZE) >> HOST_PAGE_SHIFT;
-            SUPR3PageFreeEx(pFree, cChunkPages);
-        }
-    }
-
+    while (cChunks-- > 0)
+    {
+        PPGMREGMMIO2RANGE const pMmio2    = &pVM->pgm.s.aMmio2Ranges[idx];
+        PPGMRAMRANGE const      pRamRange = pVM->pgm.s.apMmio2RamRanges[idx];
+        if (pMmio2->fFlags & PGMREGMMIO2RANGE_F_IS_TRACKING)
+        {
+            int rc2 = pgmHandlerPhysicalExDeregister(pVM, pMmio2->pPhysHandlerR3);
+            AssertLogRelMsgStmt(RT_SUCCESS(rc2),
+                                ("%#RGp-%#RGp %s failed -> %Rrc\n",
+                                 pRamRange->GCPhys, pRamRange->GCPhysLast, pRamRange->pszDesc, rc2),
+                                rc = RT_SUCCESS(rc) ? rc2 : rc);
+            pMmio2->fFlags &= ~PGMREGMMIO2RANGE_F_IS_TRACKING;
+        }
+        idx++;
+    }
     return rc;
 }
 
+#if 0 // temp
 
 /**
@@ -2964,4 +3092,5 @@
     PGM_UNLOCK(pVM);
 }
+#endif
 
 
@@ -2997,5 +3126,9 @@
  *                          the memory.
  * @param   phRegion        Where to return the MMIO2 region handle.  Optional.
- * @thread  EMT
+ * @thread  EMT(0)
+ *
+ * @note    Only callable at VM creation time and during VM state loading.
+ *          The latter is for PCNet saved state compatibility with pre 4.3.6
+ *          state.
  */
 VMMR3_INT_DECL(int) PGMR3PhysMmio2Register(PVM pVM, PPDMDEVINS pDevIns, uint32_t iSubDev, uint32_t iRegion, RTGCPHYS cb,
@@ -3012,11 +3145,18 @@
         *phRegion = NIL_PGMMMIO2HANDLE;
     }
-    VM_ASSERT_EMT_RETURN(pVM, VERR_VM_THREAD_NOT_EMT);
+    PVMCPU const  pVCpu      = VMMGetCpu(pVM);
+    AssertReturn(pVCpu && pVCpu->idCpu == 0, VERR_VM_THREAD_NOT_EMT);
+    VMSTATE const enmVMState = VMR3GetState(pVM);
+    AssertMsgReturn(enmVMState == VMSTATE_CREATING || enmVMState == VMSTATE_LOADING,
+                    ("state %s, expected CREATING or LOADING\n", VMGetStateName(enmVMState)),
+                    VERR_VM_INVALID_VM_STATE);
+
     AssertPtrReturn(pDevIns, VERR_INVALID_PARAMETER);
     AssertReturn(iSubDev <= UINT8_MAX, VERR_INVALID_PARAMETER);
     AssertReturn(iRegion <= UINT8_MAX, VERR_INVALID_PARAMETER);
+
     AssertPtrReturn(pszDesc, VERR_INVALID_POINTER);
     AssertReturn(*pszDesc, VERR_INVALID_PARAMETER);
-    AssertReturn(pgmR3PhysMmio2Find(pVM, pDevIns, iSubDev, iRegion, NIL_PGMMMIO2HANDLE) == NULL, VERR_ALREADY_EXISTS);
+
     AssertReturn(!(cb & GUEST_PAGE_OFFSET_MASK), VERR_INVALID_PARAMETER);
     AssertReturn(cb, VERR_INVALID_PARAMETER);
@@ -3025,6 +3165,8 @@
     const uint32_t cGuestPages = cb >> GUEST_PAGE_SHIFT;
     AssertLogRelReturn(((RTGCPHYS)cGuestPages << GUEST_PAGE_SHIFT) == cb, VERR_INVALID_PARAMETER);
-    AssertLogRelReturn(cGuestPages <= (MM_MMIO_64_MAX >> X86_PAGE_SHIFT), VERR_OUT_OF_RANGE);
-    AssertLogRelReturn(cGuestPages <= PGM_MMIO2_MAX_PAGE_COUNT, VERR_OUT_OF_RANGE);
+    AssertLogRelReturn(cGuestPages <= PGM_MAX_PAGES_PER_MMIO2_REGION, VERR_OUT_OF_RANGE);
+    AssertLogRelReturn(cGuestPages <= (MM_MMIO_64_MAX >> GUEST_PAGE_SHIFT), VERR_OUT_OF_RANGE);
+
+    AssertReturn(pgmR3PhysMmio2Find(pVM, pDevIns, iSubDev, iRegion) == NULL, VERR_ALREADY_EXISTS);
 
     /*
@@ -3039,22 +3181,19 @@
 
     /*
-     * Allocate an MMIO2 range ID (not freed on failure).
+     * Check that we've got sufficient MMIO2 ID space for this request (the
+     * allocation will be done later once we've got the backing memory secured,
+     * but given the EMT0 restriction, that's not going to be a problem).
      *
      * The zero ID is not used as it could be confused with NIL_GMM_PAGEID, so
-     * the IDs goes from 1 thru PGM_MMIO2_MAX_RANGES.
-     */
-    unsigned cChunks = pgmR3PhysMmio2CalcChunkCount(pVM, cb, NULL, NULL);
-
-    PGM_LOCK_VOID(pVM);
-    AssertCompile(PGM_MMIO2_MAX_RANGES < 255);
-    uint8_t const  idMmio2          = pVM->pgm.s.cMmio2Regions + 1;
-    unsigned const cNewMmio2Regions = pVM->pgm.s.cMmio2Regions + cChunks;
-    if (cNewMmio2Regions > PGM_MMIO2_MAX_RANGES)
-    {
-        PGM_UNLOCK(pVM);
-        AssertLogRelFailedReturn(VERR_PGM_TOO_MANY_MMIO2_RANGES);
-    }
-    pVM->pgm.s.cMmio2Regions = cNewMmio2Regions;
-    PGM_UNLOCK(pVM);
+     * the IDs goes from 1 thru PGM_MAX_MMIO2_RANGES.
+     */
+    unsigned const cChunks = pgmPhysMmio2CalcChunkCount(cb, NULL);
+
+    int rc = PGM_LOCK(pVM);
+    AssertRCReturn(rc, rc);
+
+    AssertCompile(PGM_MAX_MMIO2_RANGES < 255);
+    uint8_t const idMmio2 = pVM->pgm.s.cMmio2Ranges + 1;
+    AssertLogRelReturnStmt(idMmio2 + cChunks <= PGM_MAX_MMIO2_RANGES, PGM_UNLOCK(pVM), VERR_PGM_TOO_MANY_MMIO2_RANGES);
 
     /*
@@ -3062,91 +3201,74 @@
      * most likely to fail.
      */
-    int rc = MMR3AdjustFixedReservation(pVM, cGuestPages, pszDesc);
+    rc = MMR3AdjustFixedReservation(pVM, cGuestPages, pszDesc);
     if (RT_SUCCESS(rc))
     {
-        const uint32_t cHostPages = RT_ALIGN_T(cb, HOST_PAGE_SIZE, RTGCPHYS) >> HOST_PAGE_SHIFT;
-        PSUPPAGE paPages = (PSUPPAGE)RTMemTmpAlloc(cHostPages * sizeof(SUPPAGE));
+        /*
+         * If we're in driverless we'll be doing the work here, otherwise we
+         * must call ring-0 to do the job as we'll need physical addresses
+         * and maybe a ring-0 mapping address for it all.
+         */
+        if (SUPR3IsDriverless())
+            rc = pgmPhysMmio2RegisterWorker(pVM, cGuestPages, idMmio2, cChunks, pDevIns, iSubDev, iRegion, fFlags);
+        else
+        {
+            PGMPHYSMMIO2REGISTERREQ Mmio2RegReq;
+            Mmio2RegReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC;
+            Mmio2RegReq.Hdr.cbReq    = sizeof(Mmio2RegReq);
+            Mmio2RegReq.cbGuestPage  = GUEST_PAGE_SIZE;
+            Mmio2RegReq.cGuestPages  = cGuestPages;
+            Mmio2RegReq.idMmio2      = idMmio2;
+            Mmio2RegReq.cChunks      = cChunks;
+            Mmio2RegReq.iSubDev      = (uint8_t)iSubDev;
+            Mmio2RegReq.iRegion      = (uint8_t)iRegion;
+            Mmio2RegReq.fFlags       = fFlags;
+            Mmio2RegReq.pDevIns      = pDevIns;
+            rc = VMMR3CallR0Emt(pVM, pVCpu, VMMR0_DO_PGM_PHYS_MMIO2_REGISTER, 0 /*u64Arg*/, &Mmio2RegReq.Hdr);
+        }
         if (RT_SUCCESS(rc))
         {
-            void   *pvPages   = NULL;
-#ifndef VBOX_WITH_LINEAR_HOST_PHYS_MEM
-            RTR0PTR pvPagesR0 = NIL_RTR0PTR;
-#endif
+            Assert(idMmio2 + cChunks - 1 == pVM->pgm.s.cMmio2Ranges);
+
+            /*
+             * There are two things left to do:
+             *      1. Add the description to the associated RAM ranges.
+             *      2. Pre-allocate access handlers for dirty bit tracking if necessary.
+             */
+            bool const fNeedHandler = (fFlags & PGMPHYS_MMIO2_FLAGS_TRACK_DIRTY_PAGES)
 #ifdef VBOX_WITH_PGM_NEM_MODE
-            if (PGM_IS_IN_NEM_MODE(pVM))
-                rc = SUPR3PageAlloc(cHostPages, pVM->pgm.s.fUseLargePages ? SUP_PAGE_ALLOC_F_LARGE_PAGES : 0, &pvPages);
-            else
-#endif
-            {
-#ifndef VBOX_WITH_LINEAR_HOST_PHYS_MEM
-                rc = SUPR3PageAllocEx(cHostPages, 0 /*fFlags*/, &pvPages, &pvPagesR0, paPages);
-#else
-                rc = SUPR3PageAllocEx(cHostPages, 0 /*fFlags*/, &pvPages, NULL /*pR0Ptr*/, paPages);
-#endif
+                                   && (!VM_IS_NEM_ENABLED(pVM) || !NEMR3IsMmio2DirtyPageTrackingSupported(pVM))
+#endif
+                                    ;
+            for (uint32_t idxChunk = 0; idxChunk < cChunks; idxChunk++)
+            {
+                PPGMREGMMIO2RANGE const pMmio2    = &pVM->pgm.s.aMmio2Ranges[idxChunk + idMmio2 - 1];
+                Assert(pMmio2->idRamRange < RT_ELEMENTS(pVM->pgm.s.apRamRanges));
+                PPGMRAMRANGE const      pRamRange = pVM->pgm.s.apRamRanges[pMmio2->idRamRange];
+                Assert(pRamRange->pbR3 == pMmio2->pbR3);
+                Assert(pRamRange->cb   == pMmio2->cbReal);
+
+                pRamRange->pszDesc = pszDesc; /** @todo mangle this if we got more than one chunk */
+                if (fNeedHandler)
+                {
+                    rc = pgmHandlerPhysicalExCreate(pVM, pVM->pgm.s.hMmio2DirtyPhysHandlerType, pMmio2->idMmio2,
+                                                    pszDesc, &pMmio2->pPhysHandlerR3);
+                    AssertLogRelMsgReturnStmt(RT_SUCCESS(rc),
+                                              ("idMmio2=%#x idxChunk=%#x rc=%Rc\n", idMmio2, idxChunk, rc),
+                                              PGM_UNLOCK(pVM),
+                                              rc); /* PGMR3Term will take care of it all. */
+                }
             }
-            if (RT_SUCCESS(rc))
-            {
-                memset(pvPages, 0, cGuestPages * GUEST_PAGE_SIZE);
-
-                /*
-                 * Create the registered MMIO range record for it.
-                 */
-                PPGMREGMMIO2RANGE pNew;
-                rc = pgmR3PhysMmio2Create(pVM, pDevIns, iSubDev, iRegion, cb, fFlags, idMmio2, pszDesc, &pNew);
-                if (RT_SUCCESS(rc))
-                {
-                    if (phRegion)
-                        *phRegion = idMmio2;    /* The ID of the first chunk. */
-
-                    uint32_t iSrcPage   = 0;
-                    uint8_t *pbCurPages = (uint8_t *)pvPages;
-                    for (PPGMREGMMIO2RANGE pCur = pNew; pCur; pCur = pCur->pNextR3)
-                    {
-                        pCur->pvR3          = pbCurPages;
-#ifndef VBOX_WITH_LINEAR_HOST_PHYS_MEM
-                        pCur->pvR0          = pvPagesR0 + (iSrcPage << GUEST_PAGE_SHIFT);
-#endif
-                        pCur->RamRange.pvR3 = pbCurPages;
-
-                        uint32_t iDstPage = pCur->RamRange.cb >> GUEST_PAGE_SHIFT;
-#ifdef VBOX_WITH_PGM_NEM_MODE
-                        if (PGM_IS_IN_NEM_MODE(pVM))
-                            while (iDstPage-- > 0)
-                                PGM_PAGE_INIT(&pNew->RamRange.aPages[iDstPage], UINT64_C(0x0000ffffffff0000),
-                                              PGM_MMIO2_PAGEID_MAKE(idMmio2, iDstPage),
-                                              PGMPAGETYPE_MMIO2, PGM_PAGE_STATE_ALLOCATED);
-                        else
-#endif
-                        {
-                            AssertRelease(HOST_PAGE_SHIFT == GUEST_PAGE_SHIFT);
-                            while (iDstPage-- > 0)
-                                PGM_PAGE_INIT(&pNew->RamRange.aPages[iDstPage], paPages[iDstPage + iSrcPage].Phys,
-                                              PGM_MMIO2_PAGEID_MAKE(idMmio2, iDstPage),
-                                              PGMPAGETYPE_MMIO2, PGM_PAGE_STATE_ALLOCATED);
-                        }
-
-                        /* advance. */
-                        iSrcPage   += pCur->RamRange.cb >> GUEST_PAGE_SHIFT;
-                        pbCurPages += pCur->RamRange.cb;
-                    }
-
-                    RTMemTmpFree(paPages);
-
-                    /*
-                     * Update the page count stats, link the registration and we're done.
-                     */
-                    pVM->pgm.s.cAllPages += cGuestPages;
-                    pVM->pgm.s.cPrivatePages += cGuestPages;
-
-                    pgmR3PhysMmio2Link(pVM, pNew);
-
-                    *ppv = pvPages;
-                    return VINF_SUCCESS;
-                }
-
-                SUPR3PageFreeEx(pvPages, cHostPages);
-            }
-        }
-        RTMemTmpFree(paPages);
+
+            /*
+             * Done!
+             */
+            if (phRegion)
+                *phRegion = idMmio2;
+            *ppv = pVM->pgm.s.aMmio2Ranges[idMmio2 - 1].pbR3;
+
+            PGM_UNLOCK(pVM);
+            return VINF_SUCCESS;
+        }
+
         MMR3AdjustFixedReservation(pVM, -(int32_t)cGuestPages, pszDesc);
     }
@@ -3155,5 +3277,4 @@
     return rc;
 }
-
 
 /**
@@ -3168,4 +3289,8 @@
  * @param   hMmio2          The MMIO2 handle to deregister, or NIL if all
  *                          regions for the given device is to be deregistered.
+ * @thread  EMT(0)
+ *
+ * @note    Only callable during VM state loading. This is to jettison an unused
+ *          MMIO2 section present in PCNet saved state prior to VBox v4.3.6.
  */
 VMMR3_INT_DECL(int) PGMR3PhysMmio2Deregister(PVM pVM, PPDMDEVINS pDevIns, PGMMMIO2HANDLE hMmio2)
@@ -3174,31 +3299,65 @@
      * Validate input.
      */
-    VM_ASSERT_EMT_RETURN(pVM, VERR_VM_THREAD_NOT_EMT);
+    PVMCPU const  pVCpu      = VMMGetCpu(pVM);
+    AssertReturn(pVCpu && pVCpu->idCpu == 0, VERR_VM_THREAD_NOT_EMT);
+    VMSTATE const enmVMState = VMR3GetState(pVM);
+    AssertMsgReturn(enmVMState == VMSTATE_LOADING,
+                    ("state %s, expected LOADING\n", VMGetStateName(enmVMState)),
+                    VERR_VM_INVALID_VM_STATE);
+
     AssertPtrReturn(pDevIns, VERR_INVALID_PARAMETER);
 
     /*
-     * The loop here scanning all registrations will make sure that multi-chunk ranges
-     * get properly deregistered, though it's original purpose was the wildcard iRegion.
+     * Take the PGM lock and scan for registrations matching the requirements.
+     * We do this backwards to more easily reduce the cMmio2Ranges count when
+     * stuff is removed.
      */
     PGM_LOCK_VOID(pVM);
-    int rc = VINF_SUCCESS;
-    unsigned cFound = 0;
-    PPGMREGMMIO2RANGE pPrev = NULL;
-    PPGMREGMMIO2RANGE pCur = pVM->pgm.s.pRegMmioRangesR3;
-    while (pCur)
-    {
-        uint32_t const fFlags = pCur->fFlags;
-        if (    pCur->pDevInsR3 == pDevIns
-            &&  (   hMmio2 == NIL_PGMMMIO2HANDLE
-                 || pCur->idMmio2 == hMmio2))
+
+    int            rc           = VINF_SUCCESS;
+    unsigned       cFound       = 0;
+    uint32_t const cMmio2Ranges = RT_MIN(pVM->pgm.s.cMmio2Ranges, RT_ELEMENTS(pVM->pgm.s.aMmio2Ranges));
+    uint32_t       idx          = cMmio2Ranges;
+    while (idx-- > 0)
+    {
+        PPGMREGMMIO2RANGE pCur = &pVM->pgm.s.aMmio2Ranges[idx];
+        if (   pCur->pDevInsR3 == pDevIns
+            && (   hMmio2 == NIL_PGMMMIO2HANDLE
+                || pCur->idMmio2 == hMmio2))
         {
             cFound++;
+
+            /*
+             * Wind back the first chunk for this registration.
+             */
+            AssertLogRelMsgReturnStmt(pCur->fFlags & PGMREGMMIO2RANGE_F_LAST_CHUNK, ("idx=%u fFlags=%#x\n", idx, pCur->fFlags),
+                                      PGM_UNLOCK(pVM), VERR_INTERNAL_ERROR_3);
+            uint32_t cGuestPages = pCur->cbReal >> GUEST_PAGE_SHIFT;
+            uint32_t cChunks = 1;
+            while (   idx > 0
+                   && !(pCur->fFlags & PGMREGMMIO2RANGE_F_FIRST_CHUNK))
+            {
+                AssertLogRelMsgReturnStmt(   pCur[-1].pDevInsR3 == pDevIns
+                                          && pCur[-1].iRegion   == pCur->iRegion
+                                          && pCur[-1].iSubDev   == pCur->iSubDev,
+                                          ("[%u]: %p/%#x/%#x/fl=%#x; [%u]: %p/%#x/%#x/fl=%#x; cChunks=%#x\n",
+                                           idx - 1, pCur[-1].pDevInsR3, pCur[-1].iRegion, pCur[-1].iSubDev, pCur[-1].fFlags,
+                                           idx, pCur->pDevInsR3, pCur->iRegion, pCur->iSubDev, pCur->fFlags, cChunks),
+                                          PGM_UNLOCK(pVM), VERR_INTERNAL_ERROR_3);
+                cChunks++;
+                pCur--;
+                idx--;
+                cGuestPages += pCur->cbReal >> GUEST_PAGE_SHIFT;
+            }
+            AssertLogRelMsgReturnStmt(pCur->fFlags & PGMREGMMIO2RANGE_F_FIRST_CHUNK,
+                                      ("idx=%u fFlags=%#x cChunks=%#x\n", idx, pCur->fFlags, cChunks),
+                                      PGM_UNLOCK(pVM), VERR_INTERNAL_ERROR_3);
 
             /*
              * Unmap it if it's mapped.
              */
-            if (fFlags & PGMREGMMIO2RANGE_F_MAPPED)
-            {
-                int rc2 = PGMR3PhysMmio2Unmap(pVM, pCur->pDevInsR3, pCur->idMmio2, pCur->RamRange.GCPhys);
+            if (pCur->fFlags & PGMREGMMIO2RANGE_F_MAPPED)
+            {
+                int rc2 = PGMR3PhysMmio2Unmap(pVM, pCur->pDevInsR3, idx + 1, pCur->GCPhys);
                 AssertRC(rc2);
                 if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
@@ -3207,263 +3366,176 @@
 
             /*
-             * Unlink it
+             * Destroy access handlers.
              */
-            PPGMREGMMIO2RANGE pNext = pCur->pNextR3;
-            if (pPrev)
-                pPrev->pNextR3 = pNext;
+            for (uint32_t iChunk = 0; iChunk < cChunks; iChunk++)
+                if (pCur[iChunk].pPhysHandlerR3)
+                {
+                    pgmHandlerPhysicalExDestroy(pVM, pCur[iChunk].pPhysHandlerR3);
+                    pCur[iChunk].pPhysHandlerR3 = NULL;
+                }
+
+            /*
+             * Call kernel mode / worker to do the actual deregistration.
+             */
+            const char * const pszDesc = pVM->pgm.s.apMmio2RamRanges[idx] ? pVM->pgm.s.apMmio2RamRanges[idx]->pszDesc : NULL;
+            int rc2;
+            if (SUPR3IsDriverless())
+            {
+                Assert(PGM_IS_IN_NEM_MODE(pVM));
+                rc2 = pgmPhysMmio2DeregisterWorker(pVM, idx, cChunks, pDevIns);
+                AssertLogRelMsgStmt(RT_SUCCESS(rc2),
+                                    ("pgmPhysMmio2DeregisterWorker: rc=%Rrc idx=%#x cChunks=%#x %s\n",
+                                     rc2, idx, cChunks, pszDesc),
+                                    rc = RT_SUCCESS(rc) ? rc2 : rc);
+            }
             else
-                pVM->pgm.s.pRegMmioRangesR3 = pNext;
-            pCur->pNextR3 = NULL;
-
-            uint8_t idMmio2 = pCur->idMmio2;
-            Assert(idMmio2 <= RT_ELEMENTS(pVM->pgm.s.apMmio2RangesR3));
-            if (idMmio2 <= RT_ELEMENTS(pVM->pgm.s.apMmio2RangesR3))
-            {
-                Assert(pVM->pgm.s.apMmio2RangesR3[idMmio2 - 1] == pCur);
-                pVM->pgm.s.apMmio2RangesR3[idMmio2 - 1] = NULL;
-                pVM->pgm.s.apMmio2RangesR0[idMmio2 - 1] = NIL_RTR0PTR;
+            {
+                PGMPHYSMMIO2DEREGISTERREQ Mmio2DeregReq;
+                Mmio2DeregReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC;
+                Mmio2DeregReq.Hdr.cbReq    = sizeof(Mmio2DeregReq);
+                Mmio2DeregReq.idMmio2      = idx + 1;
+                Mmio2DeregReq.cChunks      = cChunks;
+                Mmio2DeregReq.pDevIns      = pDevIns;
+                rc2 = VMMR3CallR0Emt(pVM, pVCpu, VMMR0_DO_PGM_PHYS_MMIO2_DEREGISTER, 0 /*u64Arg*/, &Mmio2DeregReq.Hdr);
+                AssertLogRelMsgStmt(RT_SUCCESS(rc2),
+                                    ("VMMR0_DO_PGM_PHYS_MMIO2_DEREGISTER: rc=%Rrc idx=%#x cChunks=%#x %s\n",
+                                     rc2, idx, cChunks, pszDesc),
+                                    rc = RT_SUCCESS(rc) ? rc2 : rc);
             }
-
-            /*
-             * Free the memory.
-             */
-            uint32_t const cGuestPages = pCur->cbReal >> GUEST_PAGE_SHIFT;
-            uint32_t const cHostPages  = RT_ALIGN_T(pCur->cbReal, HOST_PAGE_SIZE, RTGCPHYS) >> HOST_PAGE_SHIFT;
-#ifdef VBOX_WITH_PGM_NEM_MODE
-            if (!pVM->pgm.s.fNemMode)
-#endif
-            {
-                int rc2 = SUPR3PageFreeEx(pCur->pvR3, cHostPages);
-                AssertRC(rc2);
-                if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
-                    rc = rc2;
-
-                rc2 = MMR3AdjustFixedReservation(pVM, -(int32_t)cGuestPages, pCur->RamRange.pszDesc);
-                AssertRC(rc2);
-                if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
+            if (RT_FAILURE(rc2))
+            {
+                LogRel(("PGMR3PhysMmio2Deregister: Deregistration failed: %Rrc; cChunks=%u %s\n", rc, cChunks, pszDesc));
+                if (RT_SUCCESS(rc))
                     rc = rc2;
             }
-#ifdef VBOX_WITH_PGM_NEM_MODE
-            else
-            {
-                int rc2 = SUPR3PageFreeEx(pCur->pvR3, cHostPages);
-                AssertRC(rc2);
-                if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
-                    rc = rc2;
+
+            /*
+             * Adjust the memory reservation.
+             */
+            if (!PGM_IS_IN_NEM_MODE(pVM) && RT_SUCCESS(rc2))
+            {
+                rc2 = MMR3AdjustFixedReservation(pVM, -(int32_t)cGuestPages, pszDesc);
+                AssertLogRelMsgStmt(RT_SUCCESS(rc2), ("rc=%Rrc cGuestPages=%#x\n", rc, cGuestPages),
+                                    rc = RT_SUCCESS(rc) ? rc2 : rc);
             }
-#endif
-
-            if (pCur->pPhysHandlerR3)
-            {
-                pgmHandlerPhysicalExDestroy(pVM, pCur->pPhysHandlerR3);
-                pCur->pPhysHandlerR3 = NULL;
-            }
-
-            /* we're leaking hyper memory here if done at runtime. */
-#ifdef VBOX_STRICT
-            VMSTATE const enmState = VMR3GetState(pVM);
-            AssertMsg(   enmState == VMSTATE_POWERING_OFF
-                      || enmState == VMSTATE_POWERING_OFF_LS
-                      || enmState == VMSTATE_OFF
-                      || enmState == VMSTATE_OFF_LS
-                      || enmState == VMSTATE_DESTROYING
-                      || enmState == VMSTATE_TERMINATED
-                      || enmState == VMSTATE_CREATING
-                      , ("%s\n", VMR3GetStateName(enmState)));
-#endif
-
-            if (pCur->RamRange.fFlags & PGM_RAM_RANGE_FLAGS_FLOATING)
-            {
-                const size_t    cbRange     = RT_UOFFSETOF_DYN(PGMREGMMIO2RANGE, RamRange.aPages[cGuestPages]);
-                size_t const    cChunkPages = RT_ALIGN_Z(cbRange, HOST_PAGE_SIZE) >> HOST_PAGE_SHIFT;
-                SUPR3PageFreeEx(pCur, cChunkPages);
-            }
-            /*else
-            {
-                rc = MMHyperFree(pVM, pCur); - does not work, see the alloc call.
-                AssertRCReturn(rc, rc);
-            } */
-
-
-            /* update page count stats */
-            pVM->pgm.s.cAllPages     -= cGuestPages;
-            pVM->pgm.s.cPrivatePages -= cGuestPages;
-
-            /* next */
-            pCur = pNext;
+
+            /* Are we done? */
             if (hMmio2 != NIL_PGMMMIO2HANDLE)
-            {
-                if (fFlags & PGMREGMMIO2RANGE_F_LAST_CHUNK)
-                    break;
-                hMmio2++;
-                Assert(pCur->idMmio2 == hMmio2);
-                Assert(pCur->pDevInsR3 == pDevIns);
-                Assert(!(pCur->fFlags & PGMREGMMIO2RANGE_F_FIRST_CHUNK));
-            }
-        }
-        else
-        {
-            pPrev = pCur;
-            pCur = pCur->pNextR3;
+                break;
         }
     }
     pgmPhysInvalidatePageMapTLB(pVM);
     PGM_UNLOCK(pVM);
-    return !cFound && hMmio2 != NIL_PGMMMIO2HANDLE  ? VERR_NOT_FOUND : rc;
-}
-
-
-/**
- * Maps a MMIO2 region.
- *
- * This is typically done when a guest / the bios / state loading changes the
- * PCI config.  The replacing of base memory has the same restrictions as during
- * registration, of course.
- *
- * @returns VBox status code.
- *
- * @param   pVM             The cross context VM structure.
- * @param   pDevIns         The device instance owning the region.
- * @param   hMmio2          The handle of the region to map.
- * @param   GCPhys          The guest-physical address to be remapped.
- */
-VMMR3_INT_DECL(int) PGMR3PhysMmio2Map(PVM pVM, PPDMDEVINS pDevIns, PGMMMIO2HANDLE hMmio2, RTGCPHYS GCPhys)
-{
-    /*
-     * Validate input.
+    return !cFound && hMmio2 != NIL_PGMMMIO2HANDLE ? VERR_NOT_FOUND : rc;
+}
+
+
+/**
+ * Worker form PGMR3PhysMmio2Map.
+ */
+static int pgmR3PhysMmio2MapLocked(PVM pVM, uint32_t const idxFirst, uint32_t const cChunks,
+                                   RTGCPHYS const GCPhys, RTGCPHYS const GCPhysLast)
+{
+    /*
+     * Validate the mapped status now that we've got the lock.
+     */
+    for (uint32_t iChunk = 0, idx = idxFirst; iChunk < cChunks; iChunk++, idx++)
+    {
+        AssertReturn(   pVM->pgm.s.aMmio2Ranges[idx].GCPhys == NIL_RTGCPHYS
+                     && !(pVM->pgm.s.aMmio2Ranges[idx].fFlags & PGMREGMMIO2RANGE_F_MAPPED),
+                     VERR_WRONG_ORDER);
+        PPGMRAMRANGE const pRamRange = pVM->pgm.s.apMmio2RamRanges[idx];
+        AssertReturn(pRamRange->GCPhys     == NIL_RTGCPHYS, VERR_INTERNAL_ERROR_3);
+        AssertReturn(pRamRange->GCPhysLast == NIL_RTGCPHYS, VERR_INTERNAL_ERROR_3);
+        Assert(pRamRange->pbR3    == pVM->pgm.s.aMmio2Ranges[idx].pbR3);
+        Assert(pRamRange->idRange == pVM->pgm.s.aMmio2Ranges[idx].idRamRange);
+    }
+
+    const char * const pszDesc   = pVM->pgm.s.apMmio2RamRanges[idxFirst]->pszDesc;
+#ifdef VBOX_WITH_NATIVE_NEM
+    uint32_t const     fNemFlags = NEM_NOTIFY_PHYS_MMIO_EX_F_MMIO2
+                                 | (pVM->pgm.s.aMmio2Ranges[idxFirst].fFlags & PGMREGMMIO2RANGE_F_TRACK_DIRTY_PAGES
+                                    ? NEM_NOTIFY_PHYS_MMIO_EX_F_TRACK_DIRTY_PAGES : 0);
+#endif
+
+    /*
+     * Now, check if this falls into a regular RAM range or if we should use
+     * the ad-hoc one.
      *
-     * Note! It's safe to walk the MMIO/MMIO2 list since registrations only
-     *       happens during VM construction.
-     */
-    VM_ASSERT_EMT_RETURN(pVM, VERR_VM_THREAD_NOT_EMT);
-    AssertPtrReturn(pDevIns, VERR_INVALID_PARAMETER);
-    AssertReturn(GCPhys != NIL_RTGCPHYS, VERR_INVALID_PARAMETER);
-    AssertReturn(GCPhys != 0, VERR_INVALID_PARAMETER);
-    AssertReturn(!(GCPhys & GUEST_PAGE_OFFSET_MASK), VERR_INVALID_PARAMETER);
-    AssertReturn(hMmio2 != NIL_PGMMMIO2HANDLE, VERR_INVALID_HANDLE);
-
-    PPGMREGMMIO2RANGE pFirstMmio = pgmR3PhysMmio2Find(pVM, pDevIns, UINT32_MAX, UINT32_MAX, hMmio2);
-    AssertReturn(pFirstMmio, VERR_NOT_FOUND);
-    Assert(pFirstMmio->fFlags & PGMREGMMIO2RANGE_F_FIRST_CHUNK);
-
-    PPGMREGMMIO2RANGE pLastMmio = pFirstMmio;
-    RTGCPHYS          cbRange   = 0;
-    for (;;)
-    {
-        AssertReturn(!(pLastMmio->fFlags & PGMREGMMIO2RANGE_F_MAPPED), VERR_WRONG_ORDER);
-        Assert(pLastMmio->RamRange.GCPhys == NIL_RTGCPHYS);
-        Assert(pLastMmio->RamRange.GCPhysLast == NIL_RTGCPHYS);
-        Assert(pLastMmio->pDevInsR3 == pFirstMmio->pDevInsR3);
-        Assert(pLastMmio->iSubDev   == pFirstMmio->iSubDev);
-        Assert(pLastMmio->iRegion   == pFirstMmio->iRegion);
-        cbRange += pLastMmio->RamRange.cb;
-        if (pLastMmio->fFlags & PGMREGMMIO2RANGE_F_LAST_CHUNK)
-            break;
-        pLastMmio = pLastMmio->pNextR3;
-    }
-
-    RTGCPHYS GCPhysLast = GCPhys + cbRange - 1;
-    AssertLogRelReturn(GCPhysLast > GCPhys, VERR_INVALID_PARAMETER);
-
-    /*
-     * Find our location in the ram range list, checking for restriction
-     * we don't bother implementing yet (partially overlapping, multiple
-     * ram ranges).
-     */
-    PGM_LOCK_VOID(pVM);
-
-    AssertReturnStmt(!(pFirstMmio->fFlags & PGMREGMMIO2RANGE_F_MAPPED), PGM_UNLOCK(pVM), VERR_WRONG_ORDER);
-
-    bool fRamExists = false;
-    PPGMRAMRANGE pRamPrev = NULL;
-    PPGMRAMRANGE pRam = pVM->pgm.s.pRamRangesXR3;
-    while (pRam && GCPhysLast >= pRam->GCPhys)
-    {
-        if (    GCPhys     <= pRam->GCPhysLast
-            &&  GCPhysLast >= pRam->GCPhys)
-        {
-            /* Completely within? */
-            AssertLogRelMsgReturnStmt(   GCPhys     >= pRam->GCPhys
-                                      && GCPhysLast <= pRam->GCPhysLast,
-                                      ("%RGp-%RGp (MMIOEx/%s) falls partly outside %RGp-%RGp (%s)\n",
-                                       GCPhys, GCPhysLast, pFirstMmio->RamRange.pszDesc,
-                                       pRam->GCPhys, pRam->GCPhysLast, pRam->pszDesc),
-                                      PGM_UNLOCK(pVM),
-                                      VERR_PGM_RAM_CONFLICT);
-
-            /* Check that all the pages are RAM pages. */
-            PPGMPAGE pPage      = &pRam->aPages[(GCPhys - pRam->GCPhys) >> GUEST_PAGE_SHIFT];
-            uint32_t cPagesLeft = cbRange >> GUEST_PAGE_SHIFT;
-            while (cPagesLeft-- > 0)
-            {
-                AssertLogRelMsgReturnStmt(PGM_PAGE_GET_TYPE(pPage) == PGMPAGETYPE_RAM,
-                                          ("%RGp isn't a RAM page (%d) - mapping %RGp-%RGp (MMIO2/%s).\n",
-                                           GCPhys, PGM_PAGE_GET_TYPE(pPage), GCPhys, GCPhysLast, pFirstMmio->RamRange.pszDesc),
-                                          PGM_UNLOCK(pVM),
-                                          VERR_PGM_RAM_CONFLICT);
-                pPage++;
-            }
-
-            /* There can only be one MMIO/MMIO2 chunk matching here! */
-            AssertLogRelMsgReturnStmt(pFirstMmio->fFlags & PGMREGMMIO2RANGE_F_LAST_CHUNK,
-                                      ("%RGp-%RGp (MMIOEx/%s, flags %#X) consists of multiple chunks whereas the RAM somehow doesn't!\n",
-                                       GCPhys, GCPhysLast, pFirstMmio->RamRange.pszDesc, pFirstMmio->fFlags),
-                                      PGM_UNLOCK(pVM),
-                                      VERR_PGM_PHYS_MMIO_EX_IPE);
-
-            fRamExists = true;
-            break;
-        }
-
-        /* next */
-        pRamPrev = pRam;
-        pRam = pRam->pNextR3;
-    }
-    Log(("PGMR3PhysMmio2Map: %RGp-%RGp fRamExists=%RTbool %s\n", GCPhys, GCPhysLast, fRamExists, pFirstMmio->RamRange.pszDesc));
-
-
-    /*
-     * Make the changes.
-     */
-    RTGCPHYS GCPhysCur = GCPhys;
-    for (PPGMREGMMIO2RANGE pCurMmio = pFirstMmio; ; pCurMmio = pCurMmio->pNextR3)
-    {
-        pCurMmio->RamRange.GCPhys = GCPhysCur;
-        pCurMmio->RamRange.GCPhysLast = GCPhysCur + pCurMmio->RamRange.cb - 1;
-        if (pCurMmio->fFlags & PGMREGMMIO2RANGE_F_LAST_CHUNK)
-        {
-            Assert(pCurMmio->RamRange.GCPhysLast == GCPhysLast);
-            break;
-        }
-        GCPhysCur += pCurMmio->RamRange.cb;
-    }
-
-    if (fRamExists)
-    {
+     * Note! For reasons of simplictly, we're considering the whole MMIO2 area
+     *       here rather than individual chunks.
+     */
+    int                rc                = VINF_SUCCESS;
+    uint32_t           idxInsert         = UINT32_MAX;
+    PPGMRAMRANGE const pOverlappingRange = pgmR3PhysRamRangeFindOverlapping(pVM, GCPhys, GCPhysLast, &idxInsert);
+    if (pOverlappingRange)
+    {
+        /* Simplification: all within the same range. */
+        AssertLogRelMsgReturn(   GCPhys     >= pOverlappingRange->GCPhys
+                              && GCPhysLast <= pOverlappingRange->GCPhysLast,
+                              ("%RGp-%RGp (MMIO2/%s) falls partly outside %RGp-%RGp (%s)\n",
+                               GCPhys, GCPhysLast, pszDesc,
+                               pOverlappingRange->GCPhys, pOverlappingRange->GCPhysLast, pOverlappingRange->pszDesc),
+                              VERR_PGM_RAM_CONFLICT);
+
+        /* Check that is isn't an ad hoc range, but a real RAM range. */
+        AssertLogRelMsgReturn(!PGM_RAM_RANGE_IS_AD_HOC(pOverlappingRange),
+                              ("%RGp-%RGp (MMIO2/%s) mapping attempt in non-RAM range: %RGp-%RGp (%s)\n",
+                               GCPhys, GCPhysLast, pszDesc,
+                               pOverlappingRange->GCPhys, pOverlappingRange->GCPhysLast, pOverlappingRange->pszDesc),
+                              VERR_PGM_RAM_CONFLICT);
+
+        /* There can only be one MMIO2 chunk matching here! */
+        AssertLogRelMsgReturn(cChunks == 1,
+                              ("%RGp-%RGp (MMIO2/%s) consists of %u chunks whereas the RAM (%s) somehow doesn't!\n",
+                               GCPhys, GCPhysLast, pszDesc, cChunks, pOverlappingRange->pszDesc),
+                              VERR_PGM_PHYS_MMIO_EX_IPE);
+
+        /* Check that it's all RAM pages. */
+        PCPGMPAGE      pPage       = &pOverlappingRange->aPages[(GCPhys - pOverlappingRange->GCPhys) >> GUEST_PAGE_SHIFT];
+        uint32_t const cMmio2Pages = pVM->pgm.s.apMmio2RamRanges[idxFirst]->cb >> GUEST_PAGE_SHIFT;
+        uint32_t       cPagesLeft  = cMmio2Pages;
+        while (cPagesLeft-- > 0)
+        {
+            AssertLogRelMsgReturn(PGM_PAGE_GET_TYPE(pPage) == PGMPAGETYPE_RAM,
+                                  ("%RGp-%RGp (MMIO2/%s): %RGp is not a RAM page - type=%d desc=%s\n", GCPhys, GCPhysLast,
+                                   pszDesc, pOverlappingRange->GCPhys, PGM_PAGE_GET_TYPE(pPage), pOverlappingRange->pszDesc),
+                                  VERR_PGM_RAM_CONFLICT);
+            pPage++;
+        }
+
+#ifdef VBOX_WITH_PGM_NEM_MODE
+        /* We cannot mix MMIO2 into a RAM range in simplified memory mode because pOverlappingRange->pbR3 can't point
+           both at the RAM and MMIO2, so we won't ever write & read from the actual MMIO2 memory if we try. */
+        AssertLogRelMsgReturn(!VM_IS_NEM_ENABLED(pVM),
+                              ("Putting %s at %RGp-%RGp is not possible in NEM mode because existing %RGp-%RGp (%s) mapping\n",
+                               pszDesc, GCPhys, GCPhysLast,
+                               pOverlappingRange->GCPhys, pOverlappingRange->GCPhysLast, pOverlappingRange->pszDesc),
+                              VERR_PGM_NOT_SUPPORTED_FOR_NEM_MODE);
+#endif
+
         /*
          * Make all the pages in the range MMIO/ZERO pages, freeing any
-         * RAM pages currently mapped here. This might not be 100% correct
-         * for PCI memory, but we're doing the same thing for MMIO2 pages.
-         *
-         * We replace these MMIO/ZERO pages with real pages in the MMIO2 case.
+         * RAM pages currently mapped here. This might not be 100% correct,
+         * but so what, we do the same from MMIO...
          */
-        Assert(pFirstMmio->fFlags & PGMREGMMIO2RANGE_F_LAST_CHUNK); /* Only one chunk */
-        Assert(pFirstMmio->pvR3 == pFirstMmio->RamRange.pvR3);
-        Assert(pFirstMmio->RamRange.pvR3 != NULL);
-
-#ifdef VBOX_WITH_PGM_NEM_MODE
-        /* We cannot mix MMIO2 into a RAM range in simplified memory mode because pRam->pvR3 can't point
-           both at the RAM and MMIO2, so we won't ever write & read from the actual MMIO2 memory if we try. */
-        AssertLogRelMsgReturn(!pVM->pgm.s.fNemMode, ("%s at %RGp-%RGp\n", pFirstMmio->RamRange.pszDesc, GCPhys, GCPhysLast),
-                              VERR_PGM_NOT_SUPPORTED_FOR_NEM_MODE);
-#endif
-
-        int rc = pgmR3PhysFreePageRange(pVM, pRam, GCPhys, GCPhysLast, pFirstMmio->RamRange.pvR3);
-        AssertRCReturnStmt(rc, PGM_UNLOCK(pVM), rc);
-
-        /* Replace the pages, freeing all present RAM pages. */
-        PPGMPAGE pPageSrc   = &pFirstMmio->RamRange.aPages[0];
-        PPGMPAGE pPageDst   = &pRam->aPages[(GCPhys - pRam->GCPhys) >> GUEST_PAGE_SHIFT];
-        uint32_t cPagesLeft = pFirstMmio->RamRange.cb >> GUEST_PAGE_SHIFT;
+        rc = pgmR3PhysFreePageRange(pVM, pOverlappingRange, GCPhys, GCPhysLast, NULL);
+        AssertRCReturn(rc, rc);
+
+        Log(("PGMR3PhysMmio2Map: %RGp-%RGp %s - inside %RGp-%RGp %s\n", GCPhys, GCPhysLast, pszDesc,
+             pOverlappingRange->GCPhys, pOverlappingRange->GCPhysLast, pOverlappingRange->pszDesc));
+
+        /*
+         * We're all in for mapping it now. Update the MMIO2 range to reflect it.
+         */
+        pVM->pgm.s.aMmio2Ranges[idxFirst].GCPhys  = GCPhys;
+        pVM->pgm.s.aMmio2Ranges[idxFirst].fFlags |= PGMREGMMIO2RANGE_F_OVERLAPPING | PGMREGMMIO2RANGE_F_MAPPED;
+
+        /*
+         * Replace the pages in the range.
+         */
+        PPGMPAGE pPageSrc   = &pVM->pgm.s.apMmio2RamRanges[idxFirst]->aPages[0];
+        PPGMPAGE pPageDst   = &pOverlappingRange->aPages[(GCPhys - pOverlappingRange->GCPhys) >> GUEST_PAGE_SHIFT];
+        cPagesLeft = cMmio2Pages;
         while (cPagesLeft-- > 0)
         {
@@ -3479,18 +3551,15 @@
             PGM_PAGE_SET_PTE_INDEX(pVM, pPageDst, 0);
             PGM_PAGE_SET_TRACKING(pVM, pPageDst, 0);
-            /* NEM state is set by pgmR3PhysFreePageRange. */
+            /* NEM state is not relevant, see VERR_PGM_NOT_SUPPORTED_FOR_NEM_MODE above. */
 
             pVM->pgm.s.cZeroPages--;
-            GCPhys += GUEST_PAGE_SIZE;
             pPageSrc++;
             pPageDst++;
         }
 
-        /* Flush physical page map TLB. */
-        pgmPhysInvalidatePageMapTLB(pVM);
-
         /* Force a PGM pool flush as guest ram references have been changed. */
-        /** @todo not entirely SMP safe; assuming for now the guest takes care of
-         *  this internally (not touch mapped mmio while changing the mapping). */
+        /** @todo not entirely SMP safe; assuming for now the guest takes
+         *   care of this internally (not touch mapped mmio while changing the
+         *   mapping). */
         PVMCPU pVCpu = VMMGetCpu(pVM);
         pVCpu->pgm.s.fSyncFlags |= PGM_SYNC_CLEAR_PGM_POOL;
@@ -3502,6 +3571,15 @@
          * No RAM range, insert the ones prepared during registration.
          */
-        for (PPGMREGMMIO2RANGE pCurMmio = pFirstMmio; ; pCurMmio = pCurMmio->pNextR3)
-        {
+        Log(("PGMR3PhysMmio2Map: %RGp-%RGp %s - no RAM overlap\n", GCPhys, GCPhysLast, pszDesc));
+        RTGCPHYS GCPhysCur = GCPhys;
+        uint32_t iChunk    = 0;
+        uint32_t idx       = idxFirst;
+        for (; iChunk < cChunks; iChunk++, idx++)
+        {
+            PPGMREGMMIO2RANGE const pMmio2    = &pVM->pgm.s.aMmio2Ranges[idx];
+            PPGMRAMRANGE const      pRamRange = pVM->pgm.s.apMmio2RamRanges[idx];
+            Assert(pRamRange->idRange == pMmio2->idRamRange);
+            Assert(pMmio2->GCPhys     == NIL_RTGCPHYS);
+
 #ifdef VBOX_WITH_NATIVE_NEM
             /* Tell NEM and get the new NEM state for the pages. */
@@ -3509,18 +3587,16 @@
             if (VM_IS_NEM_ENABLED(pVM))
             {
-                int rc = NEMR3NotifyPhysMmioExMapEarly(pVM, pCurMmio->RamRange.GCPhys,
-                                                       pCurMmio->RamRange.GCPhysLast - pCurMmio->RamRange.GCPhys + 1,
-                                                       NEM_NOTIFY_PHYS_MMIO_EX_F_MMIO2
-                                                       | (pCurMmio->fFlags & PGMREGMMIO2RANGE_F_TRACK_DIRTY_PAGES
-                                                          ? NEM_NOTIFY_PHYS_MMIO_EX_F_TRACK_DIRTY_PAGES : 0),
-                                                       NULL /*pvRam*/, pCurMmio->RamRange.pvR3,
-                                                       &u2NemState, &pCurMmio->RamRange.uNemRange);
-                AssertLogRelRCReturnStmt(rc, PGM_UNLOCK(pVM), rc);
+                rc = NEMR3NotifyPhysMmioExMapEarly(pVM, GCPhysCur, pRamRange->cb, fNemFlags, NULL /*pvRam*/, pRamRange->pbR3,
+                                                   &u2NemState, &pRamRange->uNemRange);
+                AssertLogRelMsgBreak(RT_SUCCESS(rc),
+                                     ("%RGp LB %RGp fFlags=%#x (%s)\n",
+                                      GCPhysCur, pRamRange->cb, pMmio2->fFlags, pRamRange->pszDesc));
+                pMmio2->fFlags |= PGMREGMMIO2RANGE_F_MAPPED; /* Set this early to indicate that NEM has been notified. */
             }
 #endif
 
             /* Clear the tracking data of pages we're going to reactivate. */
-            PPGMPAGE pPageSrc   = &pCurMmio->RamRange.aPages[0];
-            uint32_t cPagesLeft = pCurMmio->RamRange.cb >> GUEST_PAGE_SHIFT;
+            PPGMPAGE pPageSrc   = &pRamRange->aPages[0];
+            uint32_t cPagesLeft = pRamRange->cb >> GUEST_PAGE_SHIFT;
             while (cPagesLeft-- > 0)
             {
@@ -3533,13 +3609,47 @@
             }
 
-            /* link in the ram range */
-            pgmR3PhysLinkRamRange(pVM, &pCurMmio->RamRange, pRamPrev);
-
-            if (pCurMmio->fFlags & PGMREGMMIO2RANGE_F_LAST_CHUNK)
-            {
-                Assert(pCurMmio->RamRange.GCPhysLast == GCPhysLast);
-                break;
-            }
-            pRamPrev = &pCurMmio->RamRange;
+            /* Insert the RAM range into the lookup table. */
+            rc = pgmR3PhysRamRangeInsertLookup(pVM, pRamRange, GCPhysCur, &idxInsert);
+            AssertRCBreak(rc);
+
+            /* Mark the range as fully mapped. */
+            pMmio2->fFlags &= ~PGMREGMMIO2RANGE_F_OVERLAPPING;
+            pMmio2->fFlags |= PGMREGMMIO2RANGE_F_MAPPED;
+            pMmio2->GCPhys  = GCPhysCur;
+
+            /* Advance. */
+            GCPhysCur += pRamRange->cb;
+        }
+        if (RT_FAILURE(rc))
+        {
+            /*
+             * Bail out anything we've done so far.
+             */
+            idxInsert -= 1;
+            do
+            {
+                PPGMREGMMIO2RANGE const pMmio2    = &pVM->pgm.s.aMmio2Ranges[idx];
+                PPGMRAMRANGE const      pRamRange = pVM->pgm.s.apMmio2RamRanges[idx];
+
+#ifdef VBOX_WITH_NATIVE_NEM
+                if (   VM_IS_NEM_ENABLED(pVM)
+                    && (pVM->pgm.s.aMmio2Ranges[idx].fFlags & PGMREGMMIO2RANGE_F_MAPPED))
+                {
+                    uint8_t u2NemState = UINT8_MAX;
+                    NEMR3NotifyPhysMmioExUnmap(pVM, GCPhysCur, pRamRange->cb, fNemFlags, NULL, pRamRange->pbR3,
+                                               &u2NemState, &pRamRange->uNemRange);
+                    if (u2NemState != UINT8_MAX)
+                        pgmPhysSetNemStateForPages(pRamRange->aPages, pRamRange->cb >> GUEST_PAGE_SHIFT, u2NemState);
+                }
+#endif
+                if (pMmio2->GCPhys != NIL_RTGCPHYS)
+                    pgmR3PhysRamRangeRemoveLookup(pVM, pRamRange, &idxInsert);
+
+                pMmio2->GCPhys = NIL_RTGCPHYS;
+                pMmio2->fFlags &= ~PGMREGMMIO2RANGE_F_MAPPED;
+
+                idx--;
+            } while (iChunk-- > 0);
+            return rc;
         }
     }
@@ -3554,53 +3664,42 @@
      * release log.
      */
-    if (   pFirstMmio->pPhysHandlerR3
-        && (pFirstMmio->fFlags & PGMREGMMIO2RANGE_F_TRACKING_ENABLED))
-        pgmR3PhysMmio2EnableDirtyPageTracing(pVM, pFirstMmio);
-
-    /*
-     * We're good, set the flags and invalid the mapping TLB.
-     */
-    for (PPGMREGMMIO2RANGE pCurMmio = pFirstMmio; ; pCurMmio = pCurMmio->pNextR3)
-    {
-        pCurMmio->fFlags |= PGMREGMMIO2RANGE_F_MAPPED;
-        if (fRamExists)
-            pCurMmio->fFlags |= PGMREGMMIO2RANGE_F_OVERLAPPING;
+    if (   pVM->pgm.s.aMmio2Ranges[idxFirst].pPhysHandlerR3
+        && (pVM->pgm.s.aMmio2Ranges[idxFirst].fFlags & PGMREGMMIO2RANGE_F_TRACKING_ENABLED))
+        pgmR3PhysMmio2EnableDirtyPageTracing(pVM, idxFirst, cChunks);
+
+    /* Flush physical page map TLB. */
+    pgmPhysInvalidatePageMapTLB(pVM);
+
+#ifdef VBOX_WITH_NATIVE_NEM
+    /*
+     * Late NEM notification (currently unused).
+     */
+    if (VM_IS_NEM_ENABLED(pVM))
+    {
+        if (pOverlappingRange)
+        {
+            uint8_t * const pbRam = pOverlappingRange->pbR3 ? &pOverlappingRange->pbR3[GCPhys - pOverlappingRange->GCPhys] : NULL;
+            rc = NEMR3NotifyPhysMmioExMapLate(pVM, GCPhys, GCPhysLast - GCPhys + 1U,
+                                              fNemFlags | NEM_NOTIFY_PHYS_MMIO_EX_F_REPLACE, pbRam,
+                                              pVM->pgm.s.aMmio2Ranges[idxFirst].pbR3, NULL /*puNemRange*/);
+        }
         else
-            pCurMmio->fFlags &= ~PGMREGMMIO2RANGE_F_OVERLAPPING;
-        if (pCurMmio->fFlags & PGMREGMMIO2RANGE_F_LAST_CHUNK)
-            break;
-    }
-    pgmPhysInvalidatePageMapTLB(pVM);
-
-#ifdef VBOX_WITH_NATIVE_NEM
-    /*
-     * Late NEM notification.
-     */
-    if (VM_IS_NEM_ENABLED(pVM))
-    {
-        int rc;
-        uint32_t fNemFlags = NEM_NOTIFY_PHYS_MMIO_EX_F_MMIO2;
-        if (pFirstMmio->fFlags & PGMREGMMIO2RANGE_F_TRACK_DIRTY_PAGES)
-            fNemFlags |= NEM_NOTIFY_PHYS_MMIO_EX_F_TRACK_DIRTY_PAGES;
-        if (fRamExists)
-            rc = NEMR3NotifyPhysMmioExMapLate(pVM, GCPhys, GCPhysLast - GCPhys + 1, fNemFlags | NEM_NOTIFY_PHYS_MMIO_EX_F_REPLACE,
-                                              pRam->pvR3 ? (uint8_t *)pRam->pvR3 + GCPhys - pRam->GCPhys : NULL, pFirstMmio->pvR3,
-                                              NULL /*puNemRange*/);
-        else
-        {
-            rc = VINF_SUCCESS;
-            for (PPGMREGMMIO2RANGE pCurMmio = pFirstMmio; ; pCurMmio = pCurMmio->pNextR3)
-            {
-                rc = NEMR3NotifyPhysMmioExMapLate(pVM, pCurMmio->RamRange.GCPhys, pCurMmio->RamRange.cb, fNemFlags,
-                                                  NULL, pCurMmio->RamRange.pvR3, &pCurMmio->RamRange.uNemRange);
-                if ((pCurMmio->fFlags & PGMREGMMIO2RANGE_F_LAST_CHUNK) || RT_FAILURE(rc))
-                    break;
+        {
+            for (uint32_t iChunk = 0, idx = idxFirst; iChunk < cChunks; iChunk++, idx++)
+            {
+                PPGMREGMMIO2RANGE const pMmio2    = &pVM->pgm.s.aMmio2Ranges[idx];
+                PPGMRAMRANGE const      pRamRange = pVM->pgm.s.apMmio2RamRanges[idx];
+                Assert(pMmio2->GCPhys == pRamRange->GCPhys);
+
+                rc = NEMR3NotifyPhysMmioExMapLate(pVM, pRamRange->GCPhys, pRamRange->cb, fNemFlags, NULL /*pvRam*/,
+                                                  pRamRange->pbR3, &pRamRange->uNemRange);
+                AssertRCBreak(rc);
             }
         }
-        AssertLogRelRCReturnStmt(rc, PGMR3PhysMmio2Unmap(pVM, pDevIns, hMmio2, GCPhys); PGM_UNLOCK(pVM), rc);
-    }
-#endif
-
-    PGM_UNLOCK(pVM);
+        AssertLogRelRCReturnStmt(rc,
+                                 PGMR3PhysMmio2Unmap(pVM, pVM->pgm.s.aMmio2Ranges[idxFirst].pDevInsR3, idxFirst + 1, GCPhys),
+                                 rc);
+    }
+#endif
 
     return VINF_SUCCESS;
@@ -3609,58 +3708,112 @@
 
 /**
- * Unmaps an MMIO2 region.
+ * Maps a MMIO2 region.
  *
  * This is typically done when a guest / the bios / state loading changes the
- * PCI config. The replacing of base memory has the same restrictions as during
+ * PCI config.  The replacing of base memory has the same restrictions as during
  * registration, of course.
- */
-VMMR3_INT_DECL(int) PGMR3PhysMmio2Unmap(PVM pVM, PPDMDEVINS pDevIns, PGMMMIO2HANDLE hMmio2, RTGCPHYS GCPhys)
-{
-    /*
-     * Validate input
+ *
+ * @returns VBox status code.
+ *
+ * @param   pVM             The cross context VM structure.
+ * @param   pDevIns         The device instance owning the region.
+ * @param   hMmio2          The handle of the region to map.
+ * @param   GCPhys          The guest-physical address to be remapped.
+ */
+VMMR3_INT_DECL(int) PGMR3PhysMmio2Map(PVM pVM, PPDMDEVINS pDevIns, PGMMMIO2HANDLE hMmio2, RTGCPHYS GCPhys)
+{
+    /*
+     * Validate input.
      */
     VM_ASSERT_EMT_RETURN(pVM, VERR_VM_THREAD_NOT_EMT);
     AssertPtrReturn(pDevIns, VERR_INVALID_PARAMETER);
+    AssertReturn(GCPhys != NIL_RTGCPHYS, VERR_INVALID_PARAMETER);
+    AssertReturn(GCPhys != 0, VERR_INVALID_PARAMETER);
+    AssertReturn(!(GCPhys & GUEST_PAGE_OFFSET_MASK), VERR_INVALID_PARAMETER);
     AssertReturn(hMmio2 != NIL_PGMMMIO2HANDLE, VERR_INVALID_HANDLE);
-    if (GCPhys != NIL_RTGCPHYS)
-    {
-        AssertReturn(GCPhys != 0, VERR_INVALID_PARAMETER);
-        AssertReturn(!(GCPhys & GUEST_PAGE_OFFSET_MASK), VERR_INVALID_PARAMETER);
-    }
-
-    PPGMREGMMIO2RANGE pFirstMmio = pgmR3PhysMmio2Find(pVM, pDevIns, UINT32_MAX, UINT32_MAX, hMmio2);
-    AssertReturn(pFirstMmio, VERR_NOT_FOUND);
-    Assert(pFirstMmio->fFlags & PGMREGMMIO2RANGE_F_FIRST_CHUNK);
-
+
+    uint32_t       cChunks  = 0;
+    uint32_t const idxFirst = pgmR3PhysMmio2ResolveHandle(pVM, pDevIns, hMmio2, &cChunks);
+    AssertReturn((int32_t)idxFirst >= 0, (int32_t)idxFirst);
+
+    /* Gather the full range size so we can validate the mapping address properly. */
+    RTGCPHYS cbRange = 0;
+    for (uint32_t iChunk = 0, idx = idxFirst; iChunk < cChunks; iChunk++, idx++)
+        cbRange += pVM->pgm.s.apMmio2RamRanges[idx]->cb;
+
+    RTGCPHYS const GCPhysLast = GCPhys + cbRange - 1;
+    AssertLogRelReturn(GCPhysLast > GCPhys, VERR_INVALID_PARAMETER);
+
+    /*
+     * Take the PGM lock and call worker.
+     */
     int rc = PGM_LOCK(pVM);
     AssertRCReturn(rc, rc);
 
-    PPGMREGMMIO2RANGE pLastMmio = pFirstMmio;
-    RTGCPHYS          cbRange   = 0;
-    for (;;)
-    {
-        AssertReturnStmt(pLastMmio->fFlags & PGMREGMMIO2RANGE_F_MAPPED, PGM_UNLOCK(pVM), VERR_WRONG_ORDER);
-        AssertReturnStmt(pLastMmio->RamRange.GCPhys == GCPhys + cbRange || GCPhys == NIL_RTGCPHYS, PGM_UNLOCK(pVM), VERR_INVALID_PARAMETER);
-        Assert(pLastMmio->pDevInsR3 == pFirstMmio->pDevInsR3);
-        Assert(pLastMmio->iSubDev   == pFirstMmio->iSubDev);
-        Assert(pLastMmio->iRegion   == pFirstMmio->iRegion);
-        cbRange += pLastMmio->RamRange.cb;
-        if (pLastMmio->fFlags & PGMREGMMIO2RANGE_F_LAST_CHUNK)
-            break;
-        pLastMmio = pLastMmio->pNextR3;
-    }
-
-    Log(("PGMR3PhysMmio2Unmap: %RGp-%RGp %s\n",
-         pFirstMmio->RamRange.GCPhys, pLastMmio->RamRange.GCPhysLast, pFirstMmio->RamRange.pszDesc));
-
-    uint16_t const fOldFlags = pFirstMmio->fFlags;
-    AssertReturnStmt(fOldFlags & PGMREGMMIO2RANGE_F_MAPPED, PGM_UNLOCK(pVM), VERR_WRONG_ORDER);
+    rc = pgmR3PhysMmio2MapLocked(pVM, idxFirst, cChunks, GCPhys, GCPhysLast);
+#ifdef VBOX_STRICT
+    pgmPhysAssertRamRangesLocked(pVM, false /*fInUpdate*/, false /*fRamRelaxed*/);
+#endif
+
+    PGM_UNLOCK(pVM);
+    return rc;
+}
+
+
+/**
+ * Worker form PGMR3PhysMmio2Map.
+ */
+static int pgmR3PhysMmio2UnmapLocked(PVM pVM, uint32_t const idxFirst, uint32_t const cChunks, RTGCPHYS const GCPhysIn)
+{
+    /*
+     * Validate input.
+     */
+    RTGCPHYS cbRange = 0;
+    for (uint32_t iChunk = 0, idx = idxFirst; iChunk < cChunks; iChunk++, idx++)
+    {
+        PPGMREGMMIO2RANGE const pMmio2    = &pVM->pgm.s.aMmio2Ranges[idx];
+        PPGMRAMRANGE const      pRamRange = pVM->pgm.s.apMmio2RamRanges[idx];
+        AssertReturn(pMmio2->idRamRange == pRamRange->idRange, VERR_INTERNAL_ERROR_3);
+        AssertReturn(pMmio2->fFlags & PGMREGMMIO2RANGE_F_MAPPED, VERR_WRONG_ORDER);
+        AssertReturn(pMmio2->GCPhys != NIL_RTGCPHYS, VERR_WRONG_ORDER);
+        cbRange += pRamRange->cb;
+    }
+
+    PPGMREGMMIO2RANGE const pFirstMmio2    = &pVM->pgm.s.aMmio2Ranges[idxFirst];
+    PPGMRAMRANGE const      pFirstRamRange = pVM->pgm.s.apMmio2RamRanges[idxFirst];
+    const char * const      pszDesc        = pFirstRamRange->pszDesc;
+    AssertLogRelMsgReturn(GCPhysIn == pFirstMmio2->GCPhys || GCPhysIn == NIL_RTGCPHYS,
+                          ("GCPhys=%RGp, actual address is %RGp\n", GCPhysIn, pFirstMmio2->GCPhys),
+                          VERR_MISMATCH);
+    RTGCPHYS const          GCPhys         = pFirstMmio2->GCPhys; /* (it's always NIL_RTGCPHYS) */
+    Log(("PGMR3PhysMmio2Unmap: %RGp-%RGp %s\n", GCPhys, GCPhys + cbRange - 1U, pszDesc));
+
+    uint16_t const fOldFlags = pFirstMmio2->fFlags;
+    Assert(fOldFlags & PGMREGMMIO2RANGE_F_MAPPED);
+
+    /* Find the first entry in the lookup table and verify the overlapping flag. */
+    uint32_t idxLookup = pgmR3PhysRamRangeFindOverlappingIndex(pVM, GCPhys, GCPhys + pFirstRamRange->cb - 1U);
+    AssertLogRelMsgReturn(idxLookup < pVM->pgm.s.RamRangeUnion.cLookupEntries,
+                          ("MMIO2 range not found at %RGp LB %RGp in the lookup table! (%s)\n",
+                           GCPhys, pFirstRamRange->cb, pszDesc),
+                          VERR_INTERNAL_ERROR_2);
+
+    uint32_t const     idLookupRange = PGMRAMRANGELOOKUPENTRY_GET_ID(pVM->pgm.s.aRamRangeLookup[idxLookup]);
+    AssertLogRelReturn(idLookupRange != 0 && idLookupRange <= pVM->pgm.s.idRamRangeMax, VERR_INTERNAL_ERROR_5);
+    PPGMRAMRANGE const pLookupRange  = pVM->pgm.s.apRamRanges[idLookupRange];
+    AssertLogRelReturn(pLookupRange, VERR_INTERNAL_ERROR_3);
+
+    AssertLogRelMsgReturn(fOldFlags & PGMREGMMIO2RANGE_F_OVERLAPPING
+                          ? pLookupRange != pFirstRamRange : pLookupRange == pFirstRamRange,
+                          ("MMIO2 unmap mixup at %RGp LB %RGp fl=%#x (%s) vs %RGp LB %RGp (%s)\n",
+                           GCPhys, cbRange, fOldFlags, pszDesc, pLookupRange->GCPhys, pLookupRange->cb, pLookupRange->pszDesc),
+                          VERR_INTERNAL_ERROR_4);
 
     /*
      * If monitoring dirty pages, we must deregister the handlers first.
      */
-    if (   pFirstMmio->pPhysHandlerR3
+    if (   pFirstMmio2->pPhysHandlerR3
         && (fOldFlags & PGMREGMMIO2RANGE_F_TRACKING_ENABLED))
-        pgmR3PhysMmio2DisableDirtyPageTracing(pVM, pFirstMmio);
+        pgmR3PhysMmio2DisableDirtyPageTracing(pVM, idxFirst, cChunks);
 
     /*
@@ -3680,33 +3833,15 @@
          * Note! This is where we might differ a little from a real system, because
          *       it's likely to just show the RAM pages as they were before the
-         *       MMIO/MMIO2 region was mapped here.
+         *       MMIO2 region was mapped here.
          */
         /* Only one chunk allowed when overlapping! */
-        Assert(fOldFlags & PGMREGMMIO2RANGE_F_LAST_CHUNK);
+        Assert(cChunks == 1);
+        /* No NEM stuff should ever get here, see assertion in the mapping function. */
+        AssertReturn(!VM_IS_NEM_ENABLED(pVM), VERR_INTERNAL_ERROR_4);
 
         /* Restore the RAM pages we've replaced. */
-        PPGMRAMRANGE pRam = pVM->pgm.s.pRamRangesXR3;
-        while (pRam->GCPhys > pFirstMmio->RamRange.GCPhysLast)
-            pRam = pRam->pNextR3;
-
-        PPGMPAGE pPageDst   = &pRam->aPages[(pFirstMmio->RamRange.GCPhys - pRam->GCPhys) >> GUEST_PAGE_SHIFT];
-        uint32_t cPagesLeft = pFirstMmio->RamRange.cb >> GUEST_PAGE_SHIFT;
-        pVM->pgm.s.cZeroPages += cPagesLeft; /** @todo not correct for NEM mode  */
-
-#ifdef VBOX_WITH_NATIVE_NEM
-        if (VM_IS_NEM_ENABLED(pVM)) /* Notify NEM. Note! we cannot be here in simple memory mode, see mapping function. */
-        {
-            uint8_t u2State = UINT8_MAX;
-            rc = NEMR3NotifyPhysMmioExUnmap(pVM, pFirstMmio->RamRange.GCPhys, pFirstMmio->RamRange.cb,
-                                            fNemFlags | NEM_NOTIFY_PHYS_MMIO_EX_F_REPLACE,
-                                            pRam->pvR3
-                                            ? (uint8_t *)pRam->pvR3 + pFirstMmio->RamRange.GCPhys - pRam->GCPhys : NULL,
-                                            pFirstMmio->pvR3, &u2State, &pRam->uNemRange);
-            AssertRCStmt(rc, rcRet = rc);
-            if (u2State != UINT8_MAX)
-                pgmPhysSetNemStateForPages(pPageDst, cPagesLeft, u2State);
-        }
-#endif
-
+        PPGMPAGE pPageDst   = &pLookupRange->aPages[(pFirstRamRange->GCPhys - pLookupRange->GCPhys) >> GUEST_PAGE_SHIFT];
+        uint32_t cPagesLeft = pFirstRamRange->cb >> GUEST_PAGE_SHIFT;
+        pVM->pgm.s.cZeroPages += cPagesLeft;
         while (cPagesLeft-- > 0)
         {
@@ -3715,11 +3850,9 @@
         }
 
-        /* Flush physical page map TLB. */
-        pgmPhysInvalidatePageMapTLB(pVM);
-
         /* Update range state. */
-        pFirstMmio->RamRange.GCPhys = NIL_RTGCPHYS;
-        pFirstMmio->RamRange.GCPhysLast = NIL_RTGCPHYS;
-        pFirstMmio->fFlags &= ~(PGMREGMMIO2RANGE_F_OVERLAPPING | PGMREGMMIO2RANGE_F_MAPPED);
+        pFirstMmio2->fFlags &= ~(PGMREGMMIO2RANGE_F_OVERLAPPING | PGMREGMMIO2RANGE_F_MAPPED);
+        pFirstMmio2->GCPhys = NIL_RTGCPHYS;
+        Assert(pFirstRamRange->GCPhys == NIL_RTGCPHYS);
+        Assert(pFirstRamRange->GCPhysLast == NIL_RTGCPHYS);
     }
     else
@@ -3728,23 +3861,36 @@
          * Unlink the chunks related to the MMIO/MMIO2 region.
          */
-        for (PPGMREGMMIO2RANGE pCurMmio = pFirstMmio; ; pCurMmio = pCurMmio->pNextR3)
-        {
+        for (uint32_t iChunk = 0, idx = idxFirst; iChunk < cChunks; iChunk++, idx++)
+        {
+            PPGMREGMMIO2RANGE const pMmio2    = &pVM->pgm.s.aMmio2Ranges[idx];
+            PPGMRAMRANGE const      pRamRange = pVM->pgm.s.apMmio2RamRanges[idx];
+            Assert(pMmio2->idRamRange == pRamRange->idRange);
+            Assert(pMmio2->GCPhys     == pRamRange->GCPhys);
+
 #ifdef VBOX_WITH_NATIVE_NEM
             if (VM_IS_NEM_ENABLED(pVM)) /* Notify NEM. */
             {
                 uint8_t u2State = UINT8_MAX;
-                rc = NEMR3NotifyPhysMmioExUnmap(pVM, pCurMmio->RamRange.GCPhys, pCurMmio->RamRange.cb, fNemFlags,
-                                                NULL, pCurMmio->pvR3, &u2State, &pCurMmio->RamRange.uNemRange);
-                AssertRCStmt(rc, rcRet = rc);
+                int rc = NEMR3NotifyPhysMmioExUnmap(pVM, pRamRange->GCPhys, pRamRange->cb, fNemFlags,
+                                                    NULL, pMmio2->pbR3, &u2State, &pRamRange->uNemRange);
+                AssertLogRelMsgStmt(RT_SUCCESS(rc),
+                                    ("NEMR3NotifyPhysMmioExUnmap failed: %Rrc - GCPhys=RGp LB %RGp fNemFlags=%#x pbR3=%p %s\n",
+                                     rc, pRamRange->GCPhys, pRamRange->cb, fNemFlags, pMmio2->pbR3, pRamRange->pszDesc),
+                                    rcRet = rc);
                 if (u2State != UINT8_MAX)
-                    pgmPhysSetNemStateForPages(pCurMmio->RamRange.aPages, pCurMmio->RamRange.cb >> GUEST_PAGE_SHIFT, u2State);
+                    pgmPhysSetNemStateForPages(pRamRange->aPages, pRamRange->cb >> GUEST_PAGE_SHIFT, u2State);
             }
 #endif
-            pgmR3PhysUnlinkRamRange(pVM, &pCurMmio->RamRange);
-            pCurMmio->RamRange.GCPhys = NIL_RTGCPHYS;
-            pCurMmio->RamRange.GCPhysLast = NIL_RTGCPHYS;
-            pCurMmio->fFlags &= ~(PGMREGMMIO2RANGE_F_OVERLAPPING | PGMREGMMIO2RANGE_F_MAPPED);
-            if (pCurMmio->fFlags & PGMREGMMIO2RANGE_F_LAST_CHUNK)
-                break;
+
+            int rc = pgmR3PhysRamRangeRemoveLookup(pVM, pRamRange, &idxLookup);
+            AssertLogRelMsgStmt(RT_SUCCESS(rc),
+                                ("pgmR3PhysRamRangeRemoveLookup failed: %Rrc - GCPhys=%RGp LB %RGp %s\n",
+                                 rc, pRamRange->GCPhys, pRamRange->cb, pRamRange->pszDesc),
+                                rcRet = rc);
+
+            pMmio2->GCPhys  = NIL_RTGCPHYS;
+            pMmio2->fFlags &= ~(PGMREGMMIO2RANGE_F_OVERLAPPING | PGMREGMMIO2RANGE_F_MAPPED);
+            Assert(pRamRange->GCPhys     == NIL_RTGCPHYS);
+            Assert(pRamRange->GCPhysLast == NIL_RTGCPHYS);
         }
     }
@@ -3761,6 +3907,47 @@
     pgmPhysInvalidRamRangeTlbs(pVM);
 
+    return rcRet;
+}
+
+
+/**
+ * Unmaps an MMIO2 region.
+ *
+ * This is typically done when a guest / the bios / state loading changes the
+ * PCI config. The replacing of base memory has the same restrictions as during
+ * registration, of course.
+ */
+VMMR3_INT_DECL(int) PGMR3PhysMmio2Unmap(PVM pVM, PPDMDEVINS pDevIns, PGMMMIO2HANDLE hMmio2, RTGCPHYS GCPhys)
+{
+    /*
+     * Validate input
+     */
+    VM_ASSERT_EMT_RETURN(pVM, VERR_VM_THREAD_NOT_EMT);
+    AssertPtrReturn(pDevIns, VERR_INVALID_PARAMETER);
+    AssertReturn(hMmio2 != NIL_PGMMMIO2HANDLE, VERR_INVALID_HANDLE);
+    if (GCPhys != NIL_RTGCPHYS)
+    {
+        AssertReturn(GCPhys != 0, VERR_INVALID_PARAMETER);
+        AssertReturn(!(GCPhys & GUEST_PAGE_OFFSET_MASK), VERR_INVALID_PARAMETER);
+    }
+
+    uint32_t       cChunks  = 0;
+    uint32_t const idxFirst = pgmR3PhysMmio2ResolveHandle(pVM, pDevIns, hMmio2, &cChunks);
+    AssertReturn((int32_t)idxFirst >= 0, (int32_t)idxFirst);
+
+
+    /*
+     * Take the PGM lock and call worker.
+     */
+    int rc = PGM_LOCK(pVM);
+    AssertRCReturn(rc, rc);
+
+    rc = pgmR3PhysMmio2UnmapLocked(pVM, idxFirst, cChunks, GCPhys);
+#ifdef VBOX_STRICT
+    pgmPhysAssertRamRangesLocked(pVM, false /*fInUpdate*/, false /*fRamRelaxed*/);
+#endif
+
     PGM_UNLOCK(pVM);
-    return rcRet;
+    return rc;
 }
 
@@ -3770,5 +3957,5 @@
  *
  * This is mainly for dealing with old saved states after changing the default
- * size of a mapping region.  See PGMDevHlpMMIOExReduce and
+ * size of a mapping region.  See PDMDevHlpMmio2Reduce and
  * PDMPCIDEV::pfnRegionLoadChangeHookR3.
  *
@@ -3787,10 +3974,14 @@
      * Validate input
      */
-    VM_ASSERT_EMT_RETURN(pVM, VERR_VM_THREAD_NOT_EMT);
     AssertPtrReturn(pDevIns, VERR_INVALID_PARAMETER);
-    AssertReturn(hMmio2 != NIL_PGMMMIO2HANDLE, VERR_INVALID_HANDLE);
-    AssertReturn(cbRegion >= X86_PAGE_SIZE, VERR_INVALID_PARAMETER);
-    AssertReturn(!(cbRegion & X86_PAGE_OFFSET_MASK), VERR_UNSUPPORTED_ALIGNMENT);
-    VMSTATE enmVmState = VMR3GetState(pVM);
+    AssertReturn(hMmio2 != NIL_PGMMMIO2HANDLE && hMmio2 != 0 && hMmio2 <= RT_ELEMENTS(pVM->pgm.s.aMmio2Ranges),
+                 VERR_INVALID_HANDLE);
+    AssertReturn(cbRegion >= GUEST_PAGE_SIZE, VERR_INVALID_PARAMETER);
+    AssertReturn(!(cbRegion & GUEST_PAGE_OFFSET_MASK), VERR_UNSUPPORTED_ALIGNMENT);
+
+    PVMCPU const pVCpu = VMMGetCpu(pVM);
+    AssertReturn(pVCpu && pVCpu->idCpu == 0, VERR_VM_THREAD_NOT_EMT);
+
+    VMSTATE const enmVmState = VMR3GetState(pVM);
     AssertLogRelMsgReturn(   enmVmState == VMSTATE_CREATING
                           || enmVmState == VMSTATE_LOADING,
@@ -3798,12 +3989,18 @@
                           VERR_VM_INVALID_VM_STATE);
 
+    /*
+     * Grab the PGM lock and validate the request properly.
+     */
     int rc = PGM_LOCK(pVM);
     AssertRCReturn(rc, rc);
 
-    PPGMREGMMIO2RANGE pFirstMmio = pgmR3PhysMmio2Find(pVM, pDevIns, UINT32_MAX, UINT32_MAX, hMmio2);
-    if (pFirstMmio)
-    {
-        Assert(pFirstMmio->fFlags & PGMREGMMIO2RANGE_F_FIRST_CHUNK);
-        if (!(pFirstMmio->fFlags & PGMREGMMIO2RANGE_F_MAPPED))
+    uint32_t       cChunks  = 0;
+    uint32_t const idxFirst = pgmR3PhysMmio2ResolveHandle(pVM, pDevIns, hMmio2, &cChunks);
+    if ((int32_t)idxFirst >= 0)
+    {
+        PPGMREGMMIO2RANGE const pFirstMmio2    = &pVM->pgm.s.aMmio2Ranges[idxFirst];
+        PPGMRAMRANGE const      pFirstRamRange = pVM->pgm.s.apMmio2RamRanges[idxFirst];
+        if (   !(pFirstMmio2->fFlags & PGMREGMMIO2RANGE_F_MAPPED)
+            && pFirstMmio2->GCPhys == NIL_RTGCPHYS)
         {
             /*
@@ -3811,29 +4008,44 @@
              *       Implement when there is a real world need and thus a testcase.
              */
-            AssertLogRelMsgStmt(pFirstMmio->fFlags & PGMREGMMIO2RANGE_F_LAST_CHUNK,
-                                ("%s: %#x\n", pFirstMmio->RamRange.pszDesc, pFirstMmio->fFlags),
-                                rc = VERR_NOT_SUPPORTED);
-            if (RT_SUCCESS(rc))
+            if (cChunks == 1)
             {
                 /*
-                 * Make the change.
+                 * The request has to be within the initial size.
                  */
-                Log(("PGMR3PhysMmio2Reduce: %s changes from %RGp bytes (%RGp) to %RGp bytes.\n",
-                     pFirstMmio->RamRange.pszDesc, pFirstMmio->RamRange.cb, pFirstMmio->cbReal, cbRegion));
-
-                AssertLogRelMsgStmt(cbRegion <= pFirstMmio->cbReal,
-                                    ("%s: cbRegion=%#RGp cbReal=%#RGp\n", pFirstMmio->RamRange.pszDesc, cbRegion, pFirstMmio->cbReal),
-                                    rc = VERR_OUT_OF_RANGE);
-                if (RT_SUCCESS(rc))
+                if (cbRegion <= pFirstMmio2->cbReal)
                 {
-                    pFirstMmio->RamRange.cb = cbRegion;
+                    /*
+                     * All we have to do is modify the size stored in the RAM range,
+                     * as it is the one used when mapping it and such.
+                     * The two page counts stored in PGMR0PERVM remain unchanged.
+                     */
+                    Log(("PGMR3PhysMmio2Reduce: %s changes from %#RGp bytes (%#RGp) to %#RGp bytes.\n",
+                         pFirstRamRange->pszDesc, pFirstRamRange->cb, pFirstMmio2->cbReal, cbRegion));
+                    pFirstRamRange->cb = cbRegion;
+                    rc = VINF_SUCCESS;
+                }
+                else
+                {
+                    AssertLogRelMsgFailed(("MMIO2/%s: cbRegion=%#RGp > cbReal=%#RGp\n",
+                                           pFirstRamRange->pszDesc, cbRegion, pFirstMmio2->cbReal));
+                    rc = VERR_OUT_OF_RANGE;
                 }
             }
+            else
+            {
+                AssertLogRelMsgFailed(("MMIO2/%s: more than one chunk: %d (flags=%#x)\n",
+                                       pFirstRamRange->pszDesc, cChunks, pFirstMmio2->fFlags));
+                rc = VERR_NOT_SUPPORTED;
+            }
         }
         else
+        {
+            AssertLogRelMsgFailed(("MMIO2/%s: cannot change size of mapped range: %RGp..%RGp\n", pFirstRamRange->pszDesc,
+                                   pFirstMmio2->GCPhys, pFirstMmio2->GCPhys + pFirstRamRange->cb - 1U));
             rc = VERR_WRONG_ORDER;
+        }
     }
     else
-        rc = VERR_NOT_FOUND;
+        rc = (int32_t)idxFirst;
 
     PGM_UNLOCK(pVM);
@@ -3859,11 +4071,12 @@
 
     /*
-     * Just do this the simple way.  No need for locking as this is only taken at
-     */
-    PGM_LOCK_VOID(pVM);
-    PPGMREGMMIO2RANGE pFirstMmio = pgmR3PhysMmio2Find(pVM, pDevIns, UINT32_MAX, UINT32_MAX, hMmio2);
+     * Just do this the simple way.
+     */
+    int rc = PGM_LOCK_VOID(pVM);
+    AssertRCReturn(rc, rc);
+    uint32_t       cChunks;
+    uint32_t const idxFirst = pgmR3PhysMmio2ResolveHandle(pVM, pDevIns, hMmio2, &cChunks);
     PGM_UNLOCK(pVM);
-    AssertReturn(pFirstMmio, VERR_INVALID_HANDLE);
-    AssertReturn(pFirstMmio->fFlags & PGMREGMMIO2RANGE_F_FIRST_CHUNK, VERR_INVALID_HANDLE);
+    AssertReturn((int32_t)idxFirst >= 0, (int32_t)idxFirst);
     return VINF_SUCCESS;
 }
@@ -3881,11 +4094,15 @@
 VMMR3_INT_DECL(RTGCPHYS) PGMR3PhysMmio2GetMappingAddress(PVM pVM, PPDMDEVINS pDevIns, PGMMMIO2HANDLE hMmio2)
 {
-    AssertPtrReturn(pDevIns, NIL_RTGCPHYS);
-
-    PPGMREGMMIO2RANGE pFirstRegMmio = pgmR3PhysMmio2Find(pVM, pDevIns, UINT32_MAX, UINT32_MAX, hMmio2);
-    AssertReturn(pFirstRegMmio, NIL_RTGCPHYS);
-
-    if (pFirstRegMmio->fFlags & PGMREGMMIO2RANGE_F_MAPPED)
-        return pFirstRegMmio->RamRange.GCPhys;
+    RTGCPHYS GCPhysRet = NIL_RTGCPHYS;
+
+    int rc = PGM_LOCK_VOID(pVM);
+    AssertRCReturn(rc, NIL_RTGCPHYS);
+
+    uint32_t       cChunks;
+    uint32_t const idxFirst = pgmR3PhysMmio2ResolveHandle(pVM, pDevIns, hMmio2, &cChunks);
+    if ((int32_t)idxFirst >= 0)
+        GCPhysRet = pVM->pgm.s.aMmio2Ranges[idxFirst].GCPhys;
+
+    PGM_UNLOCK(pVM);
     return NIL_RTGCPHYS;
 }
@@ -3903,42 +4120,36 @@
      * Continue validation.
      */
-    PPGMREGMMIO2RANGE pFirstRegMmio = pgmR3PhysMmio2Find(pVM, pDevIns, UINT32_MAX, UINT32_MAX, hMmio2);
-    AssertReturn(pFirstRegMmio, VERR_INVALID_HANDLE);
-    AssertReturn(   (pFirstRegMmio->fFlags & (PGMREGMMIO2RANGE_F_TRACK_DIRTY_PAGES | PGMREGMMIO2RANGE_F_FIRST_CHUNK))
-                 ==                          (PGMREGMMIO2RANGE_F_TRACK_DIRTY_PAGES | PGMREGMMIO2RANGE_F_FIRST_CHUNK),
-                 VERR_INVALID_FUNCTION);
-    AssertReturn(pDevIns == pFirstRegMmio->pDevInsR3, VERR_NOT_OWNER);
-
-    RTGCPHYS cbTotal     = 0;
-    uint16_t fTotalDirty = 0;
-    for (PPGMREGMMIO2RANGE pCur = pFirstRegMmio;;)
-    {
-        cbTotal     += pCur->RamRange.cb; /* Not using cbReal here, because NEM is not in on the creating, only the mapping. */
-        fTotalDirty |= pCur->fFlags;
-        if (pCur->fFlags & PGMREGMMIO2RANGE_F_LAST_CHUNK)
-            break;
-        pCur = pCur->pNextR3;
-        AssertPtrReturn(pCur, VERR_INTERNAL_ERROR_5);
-        AssertReturn(   (pCur->fFlags & (PGMREGMMIO2RANGE_F_TRACK_DIRTY_PAGES | PGMREGMMIO2RANGE_F_FIRST_CHUNK))
-                     ==                  PGMREGMMIO2RANGE_F_TRACK_DIRTY_PAGES,
-                     VERR_INTERNAL_ERROR_4);
-    }
-    size_t const cbTotalBitmap = RT_ALIGN_T(cbTotal, GUEST_PAGE_SIZE * 64, RTGCPHYS) / GUEST_PAGE_SIZE / 8;
-
-    if (cbBitmap)
-    {
+    uint32_t                cChunks;
+    uint32_t const          idxFirst    = pgmR3PhysMmio2ResolveHandle(pVM, pDevIns, hMmio2, &cChunks);
+    AssertReturn((int32_t)idxFirst >= 0, (int32_t)idxFirst);
+    PPGMREGMMIO2RANGE const pFirstMmio2 = &pVM->pgm.s.aMmio2Ranges[idxFirst];
+    AssertReturn(pFirstMmio2->fFlags & PGMREGMMIO2RANGE_F_TRACK_DIRTY_PAGES, VERR_INVALID_FUNCTION);
+
+    int rc = VINF_SUCCESS;
+    if (cbBitmap || pvBitmap)
+    {
+        /*
+         * Check the bitmap size and collect all the dirty flags.
+         */
+        RTGCPHYS cbTotal     = 0;
+        uint16_t fTotalDirty = 0;
+        for (uint32_t iChunk = 0, idx = idxFirst; iChunk < cChunks; iChunk++, idx++)
+        {
+            /* Not using cbReal here, because NEM is not in on the creating, only the mapping. */
+            cbTotal     += pVM->pgm.s.apMmio2RamRanges[idx]->cb;
+            fTotalDirty |= pVM->pgm.s.aMmio2Ranges[idx].fFlags;
+        }
+        size_t const cbTotalBitmap = RT_ALIGN_T(cbTotal, GUEST_PAGE_SIZE * 64, RTGCPHYS) / GUEST_PAGE_SIZE / 8;
+
         AssertPtrReturn(pvBitmap, VERR_INVALID_POINTER);
         AssertReturn(RT_ALIGN_P(pvBitmap, sizeof(uint64_t)) == pvBitmap, VERR_INVALID_POINTER);
         AssertReturn(cbBitmap == cbTotalBitmap, VERR_INVALID_PARAMETER);
-    }
-
-    /*
-     * Do the work.
-     */
-    int rc = VINF_SUCCESS;
-    if (pvBitmap)
-    {
+
 #ifdef VBOX_WITH_PGM_NEM_MODE
-        if (pFirstRegMmio->pPhysHandlerR3 == NULL)
+        /*
+         * If there is no physical handler we must be in NEM mode and NEM
+         * taking care of the dirty bit collecting.
+         */
+        if (pFirstMmio2->pPhysHandlerR3 == NULL)
         {
 /** @todo This does not integrate at all with --execute-all-in-iem, leaving the
@@ -3948,23 +4159,23 @@
             AssertReturn(VM_IS_NEM_ENABLED(pVM), VERR_INTERNAL_ERROR_4);
             uint8_t *pbBitmap = (uint8_t *)pvBitmap;
-            for (PPGMREGMMIO2RANGE pCur = pFirstRegMmio; pCur; pCur = pCur->pNextR3)
-            {
-                size_t const cbBitmapChunk = pCur->RamRange.cb / GUEST_PAGE_SIZE / 8;
-                Assert((RTGCPHYS)cbBitmapChunk * GUEST_PAGE_SIZE * 8 == pCur->RamRange.cb);
-                int rc2 = NEMR3PhysMmio2QueryAndResetDirtyBitmap(pVM, pCur->RamRange.GCPhys, pCur->RamRange.cb,
-                                                                 pCur->RamRange.uNemRange, pbBitmap, cbBitmapChunk);
+            for (uint32_t iChunk = 0, idx = idxFirst; iChunk < cChunks; iChunk++, idx++)
+            {
+                PPGMRAMRANGE const pRamRange     = pVM->pgm.s.apMmio2RamRanges[idx];
+                size_t const       cbBitmapChunk = (pRamRange->cb / GUEST_PAGE_SIZE + 7) / 8;
+                Assert((RTGCPHYS)cbBitmapChunk * GUEST_PAGE_SIZE * 8 == pRamRange->cb);
+                Assert(pRamRange->GCPhys == pVM->pgm.s.aMmio2Ranges[idx].GCPhys); /* (No MMIO2 inside RAM in NEM mode!)*/
+                int rc2 = NEMR3PhysMmio2QueryAndResetDirtyBitmap(pVM, pRamRange->GCPhys, pRamRange->cb,
+                                                                 pRamRange->uNemRange, pbBitmap, cbBitmapChunk);
                 if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
                     rc = rc2;
-                if (pCur->fFlags & PGMREGMMIO2RANGE_F_LAST_CHUNK)
-                    break;
-                pbBitmap += pCur->RamRange.cb / GUEST_PAGE_SIZE / 8;
+                pbBitmap += pRamRange->cb / GUEST_PAGE_SIZE / 8;
             }
         }
         else
 #endif
-        if (fTotalDirty & PGMREGMMIO2RANGE_F_IS_DIRTY)
-        {
-            if (   (pFirstRegMmio->fFlags & (PGMREGMMIO2RANGE_F_MAPPED | PGMREGMMIO2RANGE_F_TRACKING_ENABLED))
-                ==                          (PGMREGMMIO2RANGE_F_MAPPED | PGMREGMMIO2RANGE_F_TRACKING_ENABLED))
+             if (fTotalDirty & PGMREGMMIO2RANGE_F_IS_DIRTY)
+        {
+            if (   (pFirstMmio2->fFlags & (PGMREGMMIO2RANGE_F_MAPPED | PGMREGMMIO2RANGE_F_TRACKING_ENABLED))
+                ==                        (PGMREGMMIO2RANGE_F_MAPPED | PGMREGMMIO2RANGE_F_TRACKING_ENABLED))
             {
                 /*
@@ -3972,17 +4183,15 @@
                  */
                 RT_BZERO(pvBitmap, cbBitmap); /* simpler for now. */
-                uint32_t iPageNo = 0;
-                for (PPGMREGMMIO2RANGE pCur = pFirstRegMmio; pCur; pCur = pCur->pNextR3)
+                for (uint32_t iChunk = 0, idx = idxFirst, iPageNo = 0; iChunk < cChunks; iChunk++, idx++)
                 {
-                    if (pCur->fFlags & PGMREGMMIO2RANGE_F_IS_DIRTY)
+                    PPGMREGMMIO2RANGE const pMmio2 = &pVM->pgm.s.aMmio2Ranges[idx];
+                    if (pMmio2->fFlags & PGMREGMMIO2RANGE_F_IS_DIRTY)
                     {
-                        int rc2 = pgmHandlerPhysicalResetMmio2WithBitmap(pVM, pCur->RamRange.GCPhys, pvBitmap, iPageNo);
+                        int rc2 = pgmHandlerPhysicalResetMmio2WithBitmap(pVM, pMmio2->GCPhys, pvBitmap, iPageNo);
                         if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
                             rc = rc2;
-                        pCur->fFlags &= ~PGMREGMMIO2RANGE_F_IS_DIRTY;
+                        pMmio2->fFlags &= ~PGMREGMMIO2RANGE_F_IS_DIRTY;
                     }
-                    if (pCur->fFlags & PGMREGMMIO2RANGE_F_LAST_CHUNK)
-                        break;
-                    iPageNo += pCur->RamRange.cb >> GUEST_PAGE_SHIFT;
+                    iPageNo += pVM->pgm.s.apMmio2RamRanges[idx]->cb >> GUEST_PAGE_SHIFT;
                 }
             }
@@ -3995,15 +4204,14 @@
                  */
                 RT_BZERO(pvBitmap, cbBitmap);
-                uint32_t iPageNo = 0;
-                for (PPGMREGMMIO2RANGE pCur = pFirstRegMmio; pCur; pCur = pCur->pNextR3)
+                for (uint32_t iChunk = 0, idx = idxFirst, iPageNo = 0; iChunk < cChunks; iChunk++, idx++)
                 {
-                    if (pCur->fFlags & PGMREGMMIO2RANGE_F_IS_DIRTY)
+                    PPGMRAMRANGE const      pRamRange = pVM->pgm.s.apMmio2RamRanges[idx];
+                    PPGMREGMMIO2RANGE const pMmio2    = &pVM->pgm.s.aMmio2Ranges[idx];
+                    if (pMmio2->fFlags & PGMREGMMIO2RANGE_F_IS_DIRTY)
                     {
-                        ASMBitSetRange(pvBitmap, iPageNo, iPageNo + (pCur->RamRange.cb >> GUEST_PAGE_SHIFT));
-                        pCur->fFlags &= ~PGMREGMMIO2RANGE_F_IS_DIRTY;
+                        ASMBitSetRange(pvBitmap, iPageNo, iPageNo + (pRamRange->cb >> GUEST_PAGE_SHIFT));
+                        pMmio2->fFlags &= ~PGMREGMMIO2RANGE_F_IS_DIRTY;
                     }
-                    if (pCur->fFlags & PGMREGMMIO2RANGE_F_LAST_CHUNK)
-                        break;
-                    iPageNo += pCur->RamRange.cb >> GUEST_PAGE_SHIFT;
+                    iPageNo += pRamRange->cb >> GUEST_PAGE_SHIFT;
                 }
             }
@@ -4018,19 +4226,19 @@
      * No bitmap. Reset the region if tracking is currently enabled.
      */
-    else if (   (pFirstRegMmio->fFlags & (PGMREGMMIO2RANGE_F_MAPPED | PGMREGMMIO2RANGE_F_TRACKING_ENABLED))
-             ==                          (PGMREGMMIO2RANGE_F_MAPPED | PGMREGMMIO2RANGE_F_TRACKING_ENABLED))
+    else if (   (pFirstMmio2->fFlags & (PGMREGMMIO2RANGE_F_MAPPED | PGMREGMMIO2RANGE_F_TRACKING_ENABLED))
+             ==                        (PGMREGMMIO2RANGE_F_MAPPED | PGMREGMMIO2RANGE_F_TRACKING_ENABLED))
     {
 #ifdef VBOX_WITH_PGM_NEM_MODE
-        if (pFirstRegMmio->pPhysHandlerR3 == NULL)
+        if (pFirstMmio2->pPhysHandlerR3 == NULL)
         {
             AssertReturn(VM_IS_NEM_ENABLED(pVM), VERR_INTERNAL_ERROR_4);
-            for (PPGMREGMMIO2RANGE pCur = pFirstRegMmio; pCur; pCur = pCur->pNextR3)
-            {
-                int rc2 = NEMR3PhysMmio2QueryAndResetDirtyBitmap(pVM, pCur->RamRange.GCPhys, pCur->RamRange.cb,
-                                                                 pCur->RamRange.uNemRange, NULL, 0);
+            for (uint32_t iChunk = 0, idx = idxFirst; iChunk < cChunks; iChunk++, idx++)
+            {
+                PPGMRAMRANGE const pRamRange = pVM->pgm.s.apMmio2RamRanges[idx];
+                Assert(pRamRange->GCPhys == pVM->pgm.s.aMmio2Ranges[idx].GCPhys); /* (No MMIO2 inside RAM in NEM mode!)*/
+                int rc2 = NEMR3PhysMmio2QueryAndResetDirtyBitmap(pVM, pRamRange->GCPhys, pRamRange->cb,
+                                                                 pRamRange->uNemRange, NULL, 0);
                 if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
                     rc = rc2;
-                if (pCur->fFlags & PGMREGMMIO2RANGE_F_LAST_CHUNK)
-                    break;
             }
         }
@@ -4038,12 +4246,10 @@
 #endif
         {
-            for (PPGMREGMMIO2RANGE pCur = pFirstRegMmio; pCur; pCur = pCur->pNextR3)
-            {
-                pCur->fFlags &= ~PGMREGMMIO2RANGE_F_IS_DIRTY;
-                int rc2 = PGMHandlerPhysicalReset(pVM, pCur->RamRange.GCPhys);
+            for (uint32_t iChunk = 0, idx = idxFirst; iChunk < cChunks; iChunk++, idx++)
+            {
+                pVM->pgm.s.aMmio2Ranges[idx].fFlags &= ~PGMREGMMIO2RANGE_F_IS_DIRTY;
+                int rc2 = PGMHandlerPhysicalReset(pVM, pVM->pgm.s.aMmio2Ranges[idx].GCPhys);
                 if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
                     rc = rc2;
-                if (pCur->fFlags & PGMREGMMIO2RANGE_F_LAST_CHUNK)
-                    break;
             }
         }
@@ -4102,10 +4308,9 @@
      * Continue validation.
      */
-    PPGMREGMMIO2RANGE pFirstRegMmio = pgmR3PhysMmio2Find(pVM, pDevIns, UINT32_MAX, UINT32_MAX, hMmio2);
-    AssertReturn(pFirstRegMmio, VERR_INVALID_HANDLE);
-    AssertReturn(   (pFirstRegMmio->fFlags & (PGMREGMMIO2RANGE_F_TRACK_DIRTY_PAGES | PGMREGMMIO2RANGE_F_FIRST_CHUNK))
-                 ==                          (PGMREGMMIO2RANGE_F_TRACK_DIRTY_PAGES | PGMREGMMIO2RANGE_F_FIRST_CHUNK)
-                 , VERR_INVALID_FUNCTION);
-    AssertReturn(pDevIns == pFirstRegMmio->pDevInsR3, VERR_NOT_OWNER);
+    uint32_t                cChunks;
+    uint32_t const          idxFirst    = pgmR3PhysMmio2ResolveHandle(pVM, pDevIns, hMmio2, &cChunks);
+    AssertReturn((int32_t)idxFirst >= 0, (int32_t)idxFirst);
+    PPGMREGMMIO2RANGE const pFirstMmio2 = &pVM->pgm.s.aMmio2Ranges[idxFirst];
+    AssertReturn(pFirstMmio2->fFlags & PGMREGMMIO2RANGE_F_TRACK_DIRTY_PAGES, VERR_INVALID_FUNCTION);
 
 #ifdef VBOX_WITH_PGM_NEM_MODE
@@ -4114,5 +4319,5 @@
      * leave the tracking on all the time there.
      */
-    if (pFirstRegMmio->pPhysHandlerR3 == NULL)
+    if (pFirstMmio2->pPhysHandlerR3 == NULL)
     {
         AssertReturn(VM_IS_NEM_ENABLED(pVM), VERR_INTERNAL_ERROR_4);
@@ -4122,27 +4327,18 @@
 
     /*
-     * Anyting needing doing?
-     */
-    if (fEnabled != RT_BOOL(pFirstRegMmio->fFlags & PGMREGMMIO2RANGE_F_TRACKING_ENABLED))
-    {
-        LogFlowFunc(("fEnabled=%RTbool %s\n", fEnabled, pFirstRegMmio->RamRange.pszDesc));
+     * Anything needing doing?
+     */
+    if (fEnabled != RT_BOOL(pFirstMmio2->fFlags & PGMREGMMIO2RANGE_F_TRACKING_ENABLED))
+    {
+        LogFlowFunc(("fEnabled=%RTbool %s\n", fEnabled, pVM->pgm.s.apMmio2RamRanges[idxFirst]->pszDesc));
 
         /*
          * Update the PGMREGMMIO2RANGE_F_TRACKING_ENABLED flag.
          */
-        for (PPGMREGMMIO2RANGE pCur = pFirstRegMmio;;)
-        {
+        for (uint32_t iChunk = 0, idx = idxFirst; iChunk < cChunks; iChunk++, idx++)
             if (fEnabled)
-                pCur->fFlags |= PGMREGMMIO2RANGE_F_TRACKING_ENABLED;
+                pVM->pgm.s.aMmio2Ranges[idx].fFlags |= PGMREGMMIO2RANGE_F_TRACKING_ENABLED;
             else
-                pCur->fFlags &= ~PGMREGMMIO2RANGE_F_TRACKING_ENABLED;
-            if (pCur->fFlags & PGMREGMMIO2RANGE_F_LAST_CHUNK)
-                break;
-            pCur = pCur->pNextR3;
-            AssertPtrReturn(pCur, VERR_INTERNAL_ERROR_5);
-            AssertReturn(   (pCur->fFlags & (PGMREGMMIO2RANGE_F_TRACK_DIRTY_PAGES | PGMREGMMIO2RANGE_F_FIRST_CHUNK))
-                         ==                  PGMREGMMIO2RANGE_F_TRACK_DIRTY_PAGES
-                         , VERR_INTERNAL_ERROR_4);
-        }
+                pVM->pgm.s.aMmio2Ranges[idx].fFlags &= ~PGMREGMMIO2RANGE_F_TRACKING_ENABLED;
 
         /*
@@ -4154,14 +4350,14 @@
          * it's in the release log.
          */
-        if (pFirstRegMmio->fFlags & PGMREGMMIO2RANGE_F_MAPPED)
+        if (pFirstMmio2->fFlags & PGMREGMMIO2RANGE_F_MAPPED)
         {
             if (fEnabled)
-                pgmR3PhysMmio2EnableDirtyPageTracing(pVM, pFirstRegMmio);
+                pgmR3PhysMmio2EnableDirtyPageTracing(pVM, idxFirst, cChunks);
             else
-                pgmR3PhysMmio2DisableDirtyPageTracing(pVM, pFirstRegMmio);
+                pgmR3PhysMmio2DisableDirtyPageTracing(pVM, idxFirst, cChunks);
         }
     }
     else
-        LogFlowFunc(("fEnabled=%RTbool %s - no change\n", fEnabled, pFirstRegMmio->RamRange.pszDesc));
+        LogFlowFunc(("fEnabled=%RTbool %s - no change\n", fEnabled, pVM->pgm.s.apMmio2RamRanges[idxFirst]->pszDesc));
 
     return VINF_SUCCESS;
@@ -4216,25 +4412,38 @@
     VM_ASSERT_EMT0_RETURN(pVM, VERR_VM_THREAD_NOT_EMT);
     VM_ASSERT_STATE_RETURN(pVM, VMSTATE_LOADING, VERR_VM_INVALID_VM_STATE);
-    AssertPtrReturn(pDevIns, VERR_INVALID_PARAMETER);
-    AssertReturn(hMmio2 != NIL_PGMMMIO2HANDLE, VERR_INVALID_HANDLE);
     AssertReturn(iNewRegion <= UINT8_MAX, VERR_INVALID_PARAMETER);
-
-    AssertReturn(pVM->enmVMState == VMSTATE_LOADING, VERR_INVALID_STATE);
 
     int rc = PGM_LOCK(pVM);
     AssertRCReturn(rc, rc);
 
-    PPGMREGMMIO2RANGE pFirstRegMmio = pgmR3PhysMmio2Find(pVM, pDevIns, UINT32_MAX, UINT32_MAX, hMmio2);
-    AssertReturnStmt(pFirstRegMmio, PGM_UNLOCK(pVM), VERR_NOT_FOUND);
-    AssertReturnStmt(pgmR3PhysMmio2Find(pVM, pDevIns, pFirstRegMmio->iSubDev, iNewRegion, NIL_PGMMMIO2HANDLE) == NULL,
-                     PGM_UNLOCK(pVM), VERR_RESOURCE_IN_USE);
-
-    /*
-     * Make the change.
-     */
-    pFirstRegMmio->iRegion = (uint8_t)iNewRegion;
+    /* Validate and resolve the handle. */
+    uint32_t       cChunks;
+    uint32_t const idxFirst = pgmR3PhysMmio2ResolveHandle(pVM, pDevIns, hMmio2, &cChunks);
+    if ((int32_t)idxFirst >= 0)
+    {
+        /* Check that the new range number is unused. */
+        PPGMREGMMIO2RANGE const pConflict = pgmR3PhysMmio2Find(pVM, pDevIns, pVM->pgm.s.aMmio2Ranges[idxFirst].iSubDev,
+                                                               iNewRegion);
+        if (!pConflict)
+        {
+            /*
+             * Make the change.
+             */
+            for (uint32_t iChunk = 0, idx = idxFirst; iChunk < cChunks; iChunk++, idx++)
+                pVM->pgm.s.aMmio2Ranges[idx].iRegion = (uint8_t)iNewRegion;
+            rc = VINF_SUCCESS;
+        }
+        else
+        {
+            AssertLogRelMsgFailed(("MMIO2/%s: iNewRegion=%d conflicts with %s\n", pVM->pgm.s.apMmio2RamRanges[idxFirst]->pszDesc,
+                                   iNewRegion, pVM->pgm.s.apMmio2RamRanges[pConflict->idRamRange]->pszDesc));
+            rc = VERR_RESOURCE_IN_USE;
+        }
+    }
+    else
+        rc = (int32_t)idxFirst;
 
     PGM_UNLOCK(pVM);
-    return VINF_SUCCESS;
+    return rc;
 }
 
@@ -4275,12 +4484,17 @@
     AssertReturn(RT_ALIGN_T(GCPhys, GUEST_PAGE_SIZE, RTGCPHYS) == GCPhys, VERR_INVALID_PARAMETER);
     AssertReturn(RT_ALIGN_T(cb, GUEST_PAGE_SIZE, RTGCPHYS) == cb, VERR_INVALID_PARAMETER);
-    RTGCPHYS GCPhysLast = GCPhys + (cb - 1);
+    RTGCPHYS const GCPhysLast = GCPhys + (cb - 1);
     AssertReturn(GCPhysLast > GCPhys, VERR_INVALID_PARAMETER);
     AssertPtrReturn(pvBinary, VERR_INVALID_PARAMETER);
     AssertPtrReturn(pszDesc, VERR_INVALID_POINTER);
     AssertReturn(!(fFlags & ~PGMPHYS_ROM_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER);
+
+    PVMCPU const pVCpu = VMMGetCpu(pVM);
+    AssertReturn(pVCpu && pVCpu->idCpu == 0, VERR_VM_THREAD_NOT_EMT);
     VM_ASSERT_STATE_RETURN(pVM, VMSTATE_CREATING, VERR_VM_INVALID_VM_STATE);
 
     const uint32_t cGuestPages = cb >> GUEST_PAGE_SHIFT;
+    AssertReturn(cGuestPages <= PGM_MAX_PAGES_PER_ROM_RANGE, VERR_OUT_OF_RANGE);
+
 #ifdef VBOX_WITH_PGM_NEM_MODE
     const uint32_t cHostPages  = RT_ALIGN_T(cb, HOST_PAGE_SIZE, RTGCPHYS) >> HOST_PAGE_SHIFT;
@@ -4288,19 +4502,23 @@
 
     /*
-     * Find the ROM location in the ROM list first.
-     */
-    PPGMROMRANGE    pRomPrev = NULL;
-    PPGMROMRANGE    pRom = pVM->pgm.s.pRomRangesR3;
-    while (pRom && GCPhysLast >= pRom->GCPhys)
-    {
-        if (    GCPhys     <= pRom->GCPhysLast
-            &&  GCPhysLast >= pRom->GCPhys)
-            AssertLogRelMsgFailedReturn(("%RGp-%RGp (%s) conflicts with existing %RGp-%RGp (%s)\n",
-                                         GCPhys, GCPhysLast, pszDesc,
-                                         pRom->GCPhys, pRom->GCPhysLast, pRom->pszDesc),
-                                        VERR_PGM_RAM_CONFLICT);
-        /* next */
-        pRomPrev = pRom;
-        pRom = pRom->pNextR3;
+     * Make sure we've got a free ROM range.
+     */
+    uint8_t const idRomRange = pVM->pgm.s.cRomRanges;
+    AssertLogRelReturn(idRomRange < RT_ELEMENTS(pVM->pgm.s.apRomRanges), VERR_PGM_TOO_MANY_ROM_RANGES);
+
+    /*
+     * Look thru the existing ROM range and make sure there aren't any
+     * overlapping registration.
+     */
+    uint32_t const cRomRanges = RT_MIN(pVM->pgm.s.cRomRanges, RT_ELEMENTS(pVM->pgm.s.apRomRanges));
+    for (uint32_t idx = 0; idx < cRomRanges; idx++)
+    {
+        PPGMROMRANGE const pRom = pVM->pgm.s.apRomRanges[idx];
+        AssertLogRelMsgReturn(   GCPhys     > pRom->GCPhysLast
+                              || GCPhysLast < pRom->GCPhys,
+                              ("%RGp-%RGp (%s) conflicts with existing %RGp-%RGp (%s)\n",
+                               GCPhys, GCPhysLast, pszDesc,
+                               pRom->GCPhys, pRom->GCPhysLast, pRom->pszDesc),
+                              VERR_PGM_RAM_CONFLICT);
     }
 
@@ -4313,30 +4531,25 @@
      * than one RAM range (lazy).
      */
-    bool            fRamExists = false;
-    PPGMRAMRANGE    pRamPrev = NULL;
-    PPGMRAMRANGE    pRam = pVM->pgm.s.pRamRangesXR3;
-    while (pRam && GCPhysLast >= pRam->GCPhys)
-    {
-        if (    GCPhys     <= pRam->GCPhysLast
-            &&  GCPhysLast >= pRam->GCPhys)
-        {
-            /* completely within? */
-            AssertLogRelMsgReturn(   GCPhys     >= pRam->GCPhys
-                                  && GCPhysLast <= pRam->GCPhysLast,
-                                  ("%RGp-%RGp (%s) falls partly outside %RGp-%RGp (%s)\n",
-                                   GCPhys, GCPhysLast, pszDesc,
-                                   pRam->GCPhys, pRam->GCPhysLast, pRam->pszDesc),
-                                  VERR_PGM_RAM_CONFLICT);
-            fRamExists = true;
-            break;
-        }
-
-        /* next */
-        pRamPrev = pRam;
-        pRam = pRam->pNextR3;
-    }
-    if (fRamExists)
-    {
-        PPGMPAGE pPage = &pRam->aPages[(GCPhys - pRam->GCPhys) >> GUEST_PAGE_SHIFT];
+    uint32_t           idxInsert         = UINT32_MAX;
+    PPGMRAMRANGE const pOverlappingRange = pgmR3PhysRamRangeFindOverlapping(pVM, GCPhys, GCPhysLast, &idxInsert);
+    if (pOverlappingRange)
+    {
+        /* completely within? */
+        AssertLogRelMsgReturn(   GCPhys     >= pOverlappingRange->GCPhys
+                              && GCPhysLast <= pOverlappingRange->GCPhysLast,
+                              ("%RGp-%RGp (%s) falls partly outside %RGp-%RGp (%s)\n",
+                               GCPhys, GCPhysLast, pszDesc,
+                               pOverlappingRange->GCPhys, pOverlappingRange->GCPhysLast, pOverlappingRange->pszDesc),
+                              VERR_PGM_RAM_CONFLICT);
+
+        /* Check that is isn't an ad hoc range, but a real RAM range. */
+        AssertLogRelMsgReturn(!PGM_RAM_RANGE_IS_AD_HOC(pOverlappingRange),
+                              ("%RGp-%RGp (ROM/%s) mapping attempt in non-RAM range: %RGp-%RGp (%s)\n",
+                               GCPhys, GCPhysLast, pszDesc,
+                               pOverlappingRange->GCPhys, pOverlappingRange->GCPhysLast, pOverlappingRange->pszDesc),
+                              VERR_PGM_RAM_CONFLICT);
+
+        /* All the pages must be RAM pages. */
+        PPGMPAGE pPage      = &pOverlappingRange->aPages[(GCPhys - pOverlappingRange->GCPhys) >> GUEST_PAGE_SHIFT];
         uint32_t cPagesLeft = cGuestPages;
         while (cPagesLeft-- > 0)
@@ -4344,7 +4557,10 @@
             AssertLogRelMsgReturn(PGM_PAGE_GET_TYPE(pPage) == PGMPAGETYPE_RAM,
                                   ("%RGp (%R[pgmpage]) isn't a RAM page - registering %RGp-%RGp (%s).\n",
-                                   pRam->GCPhys + ((RTGCPHYS)(uintptr_t)(pPage - &pRam->aPages[0]) << GUEST_PAGE_SHIFT),
-                                   pPage, GCPhys, GCPhysLast, pszDesc), VERR_PGM_RAM_CONFLICT);
-            Assert(PGM_PAGE_IS_ZERO(pPage) || PGM_IS_IN_NEM_MODE(pVM));
+                                   GCPhys + ((RTGCPHYS)cPagesLeft << GUEST_PAGE_SHIFT), pPage, GCPhys, GCPhysLast, pszDesc),
+                                  VERR_PGM_RAM_CONFLICT);
+            AssertLogRelMsgReturn(PGM_PAGE_IS_ZERO(pPage) || PGM_IS_IN_NEM_MODE(pVM),
+                                  ("%RGp (%R[pgmpage]) is not a ZERO page - registering %RGp-%RGp (%s).\n",
+                                   GCPhys + ((RTGCPHYS)cPagesLeft << GUEST_PAGE_SHIFT), pPage, GCPhys, GCPhysLast, pszDesc),
+                                  VERR_PGM_UNEXPECTED_PAGE_STATE);
             pPage++;
         }
@@ -4354,12 +4570,10 @@
      * Update the base memory reservation if necessary.
      */
-    uint32_t cExtraBaseCost = fRamExists ? 0 : cGuestPages;
-    if (fFlags & PGMPHYS_ROM_FLAGS_SHADOWED)
-        cExtraBaseCost += cGuestPages;
+    uint32_t const cExtraBaseCost = (pOverlappingRange ? 0 : cGuestPages)
+                                  + (fFlags & PGMPHYS_ROM_FLAGS_SHADOWED ? cGuestPages : 0);
     if (cExtraBaseCost)
     {
         int rc = MMR3IncreaseBaseReservation(pVM, cExtraBaseCost);
-        if (RT_FAILURE(rc))
-            return rc;
+        AssertRCReturn(rc, rc);
     }
 
@@ -4368,5 +4582,5 @@
      * Early NEM notification before we've made any changes or anything.
      */
-    uint32_t const fNemNotify = (fRamExists ? NEM_NOTIFY_PHYS_ROM_F_REPLACE : 0)
+    uint32_t const fNemNotify = (pOverlappingRange ? NEM_NOTIFY_PHYS_ROM_F_REPLACE : 0)
                               | (fFlags & PGMPHYS_ROM_FLAGS_SHADOWED ? NEM_NOTIFY_PHYS_ROM_F_SHADOW : 0);
     uint8_t        u2NemState = UINT8_MAX;
@@ -4375,6 +4589,8 @@
     {
         int rc = NEMR3NotifyPhysRomRegisterEarly(pVM, GCPhys, cGuestPages << GUEST_PAGE_SHIFT,
-                                                fRamExists ? PGM_RAMRANGE_CALC_PAGE_R3PTR(pRam, GCPhys) : NULL,
-                                                fNemNotify, &u2NemState, fRamExists ? &pRam->uNemRange : &uNemRange);
+                                                 pOverlappingRange
+                                                 ? PGM_RAMRANGE_CALC_PAGE_R3PTR(pOverlappingRange, GCPhys) : NULL,
+                                                 fNemNotify, &u2NemState,
+                                                 pOverlappingRange ? &pOverlappingRange->uNemRange : &uNemRange);
         AssertLogRelRCReturn(rc, rc);
     }
@@ -4382,16 +4598,17 @@
 
     /*
-     * Allocate memory for the virgin copy of the RAM.  In simplified memory mode,
-     * we allocate memory for any ad-hoc RAM range and for shadow pages.
-     */
+     * Allocate memory for the virgin copy of the RAM.  In simplified memory
+     * mode, we allocate memory for any ad-hoc RAM range and for shadow pages.
+     */
+    int                  rc;
     PGMMALLOCATEPAGESREQ pReq  = NULL;
 #ifdef VBOX_WITH_PGM_NEM_MODE
     void                *pvRam = NULL;
     void                *pvAlt = NULL;
-    if (pVM->pgm.s.fNemMode)
-    {
-        if (!fRamExists)
-        {
-            int rc = SUPR3PageAlloc(cHostPages, 0, &pvRam);
+    if (PGM_IS_IN_NEM_MODE(pVM))
+    {
+        if (!pOverlappingRange)
+        {
+            rc = SUPR3PageAlloc(cHostPages, 0, &pvRam);
             if (RT_FAILURE(rc))
                 return rc;
@@ -4399,5 +4616,5 @@
         if (fFlags & PGMPHYS_ROM_FLAGS_SHADOWED)
         {
-            int rc = SUPR3PageAlloc(cHostPages, 0, &pvAlt);
+            rc = SUPR3PageAlloc(cHostPages, 0, &pvAlt);
             if (RT_FAILURE(rc))
             {
@@ -4411,5 +4628,5 @@
 #endif
     {
-        int rc = GMMR3AllocatePagesPrepare(pVM, &pReq, cGuestPages, GMMACCOUNT_BASE);
+        rc = GMMR3AllocatePagesPrepare(pVM, &pReq, cGuestPages, GMMACCOUNT_BASE);
         AssertRCReturn(rc, rc);
 
@@ -4431,43 +4648,54 @@
 
     /*
-     * Allocate the new ROM range and RAM range (if necessary).
-     */
-    PPGMROMRANGE pRomNew     = NULL;
-    RTR0PTR      pRomNewR0   = NIL_RTR0PTR;
-    size_t const cbRomRange  = RT_ALIGN_Z(RT_UOFFSETOF_DYN(PGMROMRANGE, aPages[cGuestPages]), 128);
-    size_t const cbRamRange  = fRamExists ? 0 : RT_UOFFSETOF_DYN(PGMROMRANGE, aPages[cGuestPages]);
-    size_t const cRangePages = RT_ALIGN_Z(cbRomRange + cbRamRange, HOST_PAGE_SIZE) >> HOST_PAGE_SHIFT;
-    int rc = SUPR3PageAllocEx(cRangePages, 0 /*fFlags*/, (void **)&pRomNew, &pRomNewR0, NULL /*paPages*/);
+     * Allocate a RAM range if required.
+     * Note! We don't clean up the RAM range here on failure, VM destruction does that.
+     */
+    rc = VINF_SUCCESS;
+    PPGMRAMRANGE pRamRange = NULL;
+    if (!pOverlappingRange)
+        rc = pgmR3PhysAllocateRamRange(pVM, pVCpu, cGuestPages, PGM_RAM_RANGE_FLAGS_AD_HOC_ROM, &pRamRange);
     if (RT_SUCCESS(rc))
     {
-
         /*
-         * Initialize and insert the RAM range (if required).
+         * Allocate a ROM range.
+         * Note! We don't clean up the ROM range here on failure, VM destruction does that.
          */
-        PPGMRAMRANGE       pRamNew;
-        uint32_t const     idxFirstRamPage = fRamExists ? (GCPhys - pRam->GCPhys) >> GUEST_PAGE_SHIFT : 0;
-        PPGMROMPAGE        pRomPage        = &pRomNew->aPages[0];
-        if (!fRamExists)
-        {
-            /* New RAM range. */
-            pRamNew = (PPGMRAMRANGE)((uintptr_t)pRomNew + cbRomRange);
-            pRamNew->pSelfR0       = !pRomNewR0 ? NIL_RTR0PTR : pRomNewR0 + cbRomRange;
-            pRamNew->GCPhys        = GCPhys;
-            pRamNew->GCPhysLast    = GCPhysLast;
-            pRamNew->cb            = cb;
-            pRamNew->pszDesc       = pszDesc;
-            pRamNew->fFlags        = PGM_RAM_RANGE_FLAGS_AD_HOC_ROM;
-            pRamNew->pvR3          = NULL;
-            pRamNew->paLSPages     = NULL;
+        if (SUPR3IsDriverless())
+            rc = pgmPhysRomRangeAllocCommon(pVM, cGuestPages, idRomRange, fFlags);
+        else
+        {
+            PGMPHYSROMALLOCATERANGEREQ RomRangeReq;
+            RomRangeReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC;
+            RomRangeReq.Hdr.cbReq    = sizeof(RomRangeReq);
+            RomRangeReq.cbGuestPage  = GUEST_PAGE_SIZE;
+            RomRangeReq.cGuestPages  = cGuestPages;
+            RomRangeReq.idRomRange   = idRomRange;
+            RomRangeReq.fFlags       = fFlags;
+            rc = VMMR3CallR0Emt(pVM, pVCpu, VMMR0_DO_PGM_PHYS_ROM_ALLOCATE_RANGE, 0 /*u64Arg*/, &RomRangeReq.Hdr);
+        }
+    }
+    if (RT_SUCCESS(rc))
+    {
+        /*
+         * Initialize and map the RAM range (if required).
+         */
+        PPGMROMRANGE const pRomRange       = pVM->pgm.s.apRomRanges[idRomRange];
+        AssertPtr(pRomRange);
+        uint32_t const     idxFirstRamPage = pOverlappingRange ? (GCPhys - pOverlappingRange->GCPhys) >> GUEST_PAGE_SHIFT : 0;
+        PPGMROMPAGE        pRomPage        = &pRomRange->aPages[0];
+        if (!pOverlappingRange)
+        {
+            /* Initialize the new RAM range and insert it into the lookup table. */
+            pRamRange->pszDesc   = pszDesc;
 #ifdef VBOX_WITH_NATIVE_NEM
-            pRamNew->uNemRange     = uNemRange;
-#endif
-
-            PPGMPAGE pRamPage = &pRamNew->aPages[idxFirstRamPage];
+            pRamRange->uNemRange = uNemRange;
+#endif
+
+            PPGMPAGE pRamPage = &pRamRange->aPages[idxFirstRamPage];
 #ifdef VBOX_WITH_PGM_NEM_MODE
-            if (pVM->pgm.s.fNemMode)
+            if (PGM_IS_IN_NEM_MODE(pVM))
             {
                 AssertPtr(pvRam); Assert(pReq == NULL);
-                pRamNew->pvR3 = pvRam;
+                pRamRange->pbR3 = (uint8_t *)pvRam;
                 for (uint32_t iPage = 0; iPage < cGuestPages; iPage++, pRamPage++, pRomPage++)
                 {
@@ -4479,4 +4707,6 @@
             else
 #endif
+            {
+                Assert(!pRamRange->pbR3); Assert(!pvRam);
                 for (uint32_t iPage = 0; iPage < cGuestPages; iPage++, pRamPage++, pRomPage++)
                 {
@@ -4489,15 +4719,17 @@
                     pRomPage->Virgin = *pRamPage;
                 }
+            }
 
             pVM->pgm.s.cAllPages     += cGuestPages;
             pVM->pgm.s.cPrivatePages += cGuestPages;
-            pgmR3PhysLinkRamRange(pVM, pRamNew, pRamPrev);
+
+            rc = pgmR3PhysRamRangeInsertLookup(pVM, pRamRange, GCPhys, &idxInsert);
         }
         else
         {
-            /* Existing RAM range. */
-            PPGMPAGE pRamPage = &pRam->aPages[idxFirstRamPage];
+            /* Insert the ROM into an existing RAM range. */
+            PPGMPAGE pRamPage = &pOverlappingRange->aPages[idxFirstRamPage];
 #ifdef VBOX_WITH_PGM_NEM_MODE
-            if (pVM->pgm.s.fNemMode)
+            if (PGM_IS_IN_NEM_MODE(pVM))
             {
                 Assert(pvRam == NULL); Assert(pReq == NULL);
@@ -4534,183 +4766,154 @@
                 pVM->pgm.s.cPrivatePages += cGuestPages;
             }
-            pRamNew = pRam;
-        }
-
+            pRamRange = pOverlappingRange;
+        }
+
+        if (RT_SUCCESS(rc))
+        {
 #ifdef VBOX_WITH_NATIVE_NEM
-        /* Set the NEM state of the pages if needed. */
-        if (u2NemState != UINT8_MAX)
-            pgmPhysSetNemStateForPages(&pRamNew->aPages[idxFirstRamPage], cGuestPages, u2NemState);
-#endif
-
-        /* Flush physical page map TLB. */
-        pgmPhysInvalidatePageMapTLB(pVM);
-
-        /*
-         * Register the ROM access handler.
-         */
-        rc = PGMHandlerPhysicalRegister(pVM, GCPhys, GCPhysLast, pVM->pgm.s.hRomPhysHandlerType, GCPhys, pszDesc);
-        if (RT_SUCCESS(rc))
-        {
+            /* Set the NEM state of the pages if needed. */
+            if (u2NemState != UINT8_MAX)
+                pgmPhysSetNemStateForPages(&pRamRange->aPages[idxFirstRamPage], cGuestPages, u2NemState);
+#endif
+
+            /* Flush physical page map TLB. */
+            pgmPhysInvalidatePageMapTLB(pVM);
+
             /*
-             * Copy the image over to the virgin pages.
-             * This must be done after linking in the RAM range.
+             * Register the ROM access handler.
              */
-            size_t          cbBinaryLeft = cbBinary;
-            PPGMPAGE        pRamPage     = &pRamNew->aPages[idxFirstRamPage];
-            for (uint32_t iPage = 0; iPage < cGuestPages; iPage++, pRamPage++)
-            {
-                void *pvDstPage;
-                rc = pgmPhysPageMap(pVM, pRamPage, GCPhys + (iPage << GUEST_PAGE_SHIFT), &pvDstPage);
-                if (RT_FAILURE(rc))
+            rc = PGMHandlerPhysicalRegister(pVM, GCPhys, GCPhysLast, pVM->pgm.s.hRomPhysHandlerType, idRomRange, pszDesc);
+            if (RT_SUCCESS(rc))
+            {
+                /*
+                 * Copy the image over to the virgin pages.
+                 * This must be done after linking in the RAM range.
+                 */
+                size_t          cbBinaryLeft = cbBinary;
+                PPGMPAGE        pRamPage     = &pRamRange->aPages[idxFirstRamPage];
+                for (uint32_t iPage = 0; iPage < cGuestPages; iPage++, pRamPage++)
                 {
-                    VMSetError(pVM, rc, RT_SRC_POS, "Failed to map virgin ROM page at %RGp", GCPhys);
-                    break;
-                }
-                if (cbBinaryLeft >= GUEST_PAGE_SIZE)
-                {
-                    memcpy(pvDstPage, (uint8_t const *)pvBinary + ((size_t)iPage << GUEST_PAGE_SHIFT), GUEST_PAGE_SIZE);
-                    cbBinaryLeft -= GUEST_PAGE_SIZE;
-                }
-                else
-                {
-                    RT_BZERO(pvDstPage, GUEST_PAGE_SIZE); /* (shouldn't be necessary, but can't hurt either) */
-                    if (cbBinaryLeft > 0)
+                    void *pvDstPage;
+                    rc = pgmPhysPageMap(pVM, pRamPage, GCPhys + (iPage << GUEST_PAGE_SHIFT), &pvDstPage);
+                    if (RT_FAILURE(rc))
                     {
-                        memcpy(pvDstPage, (uint8_t const *)pvBinary + ((size_t)iPage << GUEST_PAGE_SHIFT), cbBinaryLeft);
-                        cbBinaryLeft = 0;
+                        VMSetError(pVM, rc, RT_SRC_POS, "Failed to map virgin ROM page at %RGp", GCPhys);
+                        break;
                     }
-                }
-            }
-            if (RT_SUCCESS(rc))
-            {
-                /*
-                 * Initialize the ROM range.
-                 * Note that the Virgin member of the pages has already been initialized above.
-                 */
-                pRomNew->pSelfR0    = pRomNewR0;
-                pRomNew->GCPhys     = GCPhys;
-                pRomNew->GCPhysLast = GCPhysLast;
-                pRomNew->cb         = cb;
-                pRomNew->fFlags     = fFlags;
-                pRomNew->idSavedState = UINT8_MAX;
-                pRomNew->cbOriginal = cbBinary;
-                pRomNew->pszDesc    = pszDesc;
-#ifdef VBOX_WITH_PGM_NEM_MODE
-                pRomNew->pbR3Alternate = (uint8_t *)pvAlt;
-#endif
-                pRomNew->pvOriginal = fFlags & PGMPHYS_ROM_FLAGS_PERMANENT_BINARY
-                                    ? pvBinary : RTMemDup(pvBinary, cbBinary);
-                if (pRomNew->pvOriginal)
-                {
-                    for (unsigned iPage = 0; iPage < cGuestPages; iPage++)
+                    if (cbBinaryLeft >= GUEST_PAGE_SIZE)
                     {
-                        PPGMROMPAGE pPage = &pRomNew->aPages[iPage];
-                        pPage->enmProt = PGMROMPROT_READ_ROM_WRITE_IGNORE;
-#ifdef VBOX_WITH_PGM_NEM_MODE
-                        if (pVM->pgm.s.fNemMode)
-                            PGM_PAGE_INIT(&pPage->Shadow, UINT64_C(0x0000fffffffff000), NIL_GMM_PAGEID,
-                                          PGMPAGETYPE_ROM_SHADOW, PGM_PAGE_STATE_ALLOCATED);
-                        else
-#endif
-                            PGM_PAGE_INIT_ZERO(&pPage->Shadow, pVM, PGMPAGETYPE_ROM_SHADOW);
-                    }
-
-                    /* update the page count stats for the shadow pages. */
-                    if (fFlags & PGMPHYS_ROM_FLAGS_SHADOWED)
-                    {
-#ifdef VBOX_WITH_PGM_NEM_MODE
-                        if (pVM->pgm.s.fNemMode)
-                            pVM->pgm.s.cPrivatePages += cGuestPages;
-                        else
-#endif
-                            pVM->pgm.s.cZeroPages += cGuestPages;
-                        pVM->pgm.s.cAllPages += cGuestPages;
-                    }
-
-                    /*
-                     * Insert the ROM range, tell REM and return successfully.
-                     */
-                    pRomNew->pNextR3 = pRom;
-                    pRomNew->pNextR0 = pRom ? pRom->pSelfR0 : NIL_RTR0PTR;
-
-                    if (pRomPrev)
-                    {
-                        pRomPrev->pNextR3 = pRomNew;
-                        pRomPrev->pNextR0 = pRomNew->pSelfR0;
+                        memcpy(pvDstPage, (uint8_t const *)pvBinary + ((size_t)iPage << GUEST_PAGE_SHIFT), GUEST_PAGE_SIZE);
+                        cbBinaryLeft -= GUEST_PAGE_SIZE;
                     }
                     else
                     {
-                        pVM->pgm.s.pRomRangesR3 = pRomNew;
-                        pVM->pgm.s.pRomRangesR0 = pRomNew->pSelfR0;
+                        RT_BZERO(pvDstPage, GUEST_PAGE_SIZE); /* (shouldn't be necessary, but can't hurt either) */
+                        if (cbBinaryLeft > 0)
+                        {
+                            memcpy(pvDstPage, (uint8_t const *)pvBinary + ((size_t)iPage << GUEST_PAGE_SHIFT), cbBinaryLeft);
+                            cbBinaryLeft = 0;
+                        }
                     }
-
-                    pgmPhysInvalidatePageMapTLB(pVM);
+                }
+                if (RT_SUCCESS(rc))
+                {
+                    /*
+                     * Initialize the ROM range.
+                     * Note that the Virgin member of the pages has already been initialized above.
+                     */
+                    Assert(pRomRange->cb           == cb);
+                    Assert(pRomRange->fFlags       == fFlags);
+                    Assert(pRomRange->idSavedState == UINT8_MAX);
+                    pRomRange->GCPhys        = GCPhys;
+                    pRomRange->GCPhysLast    = GCPhysLast;
+                    pRomRange->cbOriginal    = cbBinary;
+                    pRomRange->pszDesc       = pszDesc;
 #ifdef VBOX_WITH_PGM_NEM_MODE
-                    if (!pVM->pgm.s.fNemMode)
-#endif
-                        GMMR3AllocatePagesCleanup(pReq);
+                    pRomRange->pbR3Alternate = (uint8_t *)pvAlt;
+#endif
+                    pRomRange->pvOriginal    = fFlags & PGMPHYS_ROM_FLAGS_PERMANENT_BINARY
+                                             ? pvBinary : RTMemDup(pvBinary, cbBinary);
+                    if (pRomRange->pvOriginal)
+                    {
+                        for (unsigned iPage = 0; iPage < cGuestPages; iPage++)
+                        {
+                            PPGMROMPAGE const pPage = &pRomRange->aPages[iPage];
+                            pPage->enmProt = PGMROMPROT_READ_ROM_WRITE_IGNORE;
+#ifdef VBOX_WITH_PGM_NEM_MODE
+                            if (PGM_IS_IN_NEM_MODE(pVM))
+                                PGM_PAGE_INIT(&pPage->Shadow, UINT64_C(0x0000fffffffff000), NIL_GMM_PAGEID,
+                                              PGMPAGETYPE_ROM_SHADOW, PGM_PAGE_STATE_ALLOCATED);
+                            else
+#endif
+                                PGM_PAGE_INIT_ZERO(&pPage->Shadow, pVM, PGMPAGETYPE_ROM_SHADOW);
+                        }
+
+                        /* update the page count stats for the shadow pages. */
+                        if (fFlags & PGMPHYS_ROM_FLAGS_SHADOWED)
+                        {
+                            if (PGM_IS_IN_NEM_MODE(pVM))
+                                pVM->pgm.s.cPrivatePages += cGuestPages;
+                            else
+                                pVM->pgm.s.cZeroPages    += cGuestPages;
+                            pVM->pgm.s.cAllPages         += cGuestPages;
+                        }
 
 #ifdef VBOX_WITH_NATIVE_NEM
-                    /*
-                     * Notify NEM again.
-                     */
-                    if (VM_IS_NEM_ENABLED(pVM))
-                    {
-                        u2NemState = UINT8_MAX;
-                        rc = NEMR3NotifyPhysRomRegisterLate(pVM, GCPhys, cb, PGM_RAMRANGE_CALC_PAGE_R3PTR(pRamNew, GCPhys),
-                                                            fNemNotify, &u2NemState,
-                                                            fRamExists ? &pRam->uNemRange : &pRamNew->uNemRange);
-                        if (u2NemState != UINT8_MAX)
-                            pgmPhysSetNemStateForPages(&pRamNew->aPages[idxFirstRamPage], cGuestPages, u2NemState);
+                        /*
+                         * Notify NEM again.
+                         */
+                        if (VM_IS_NEM_ENABLED(pVM))
+                        {
+                            u2NemState = UINT8_MAX;
+                            rc = NEMR3NotifyPhysRomRegisterLate(pVM, GCPhys, cb, PGM_RAMRANGE_CALC_PAGE_R3PTR(pRamRange, GCPhys),
+                                                                fNemNotify, &u2NemState, &pRamRange->uNemRange);
+                            if (u2NemState != UINT8_MAX)
+                                pgmPhysSetNemStateForPages(&pRamRange->aPages[idxFirstRamPage], cGuestPages, u2NemState);
+                        }
+                        else
+#endif
+                            GMMR3AllocatePagesCleanup(pReq);
                         if (RT_SUCCESS(rc))
+                        {
+                            /*
+                             * Done!
+                             */
+#ifdef VBOX_STRICT
+                            pgmPhysAssertRamRangesLocked(pVM, false /*fInUpdate*/, false /*fRamRelaxed*/);
+#endif
                             return rc;
+                        }
+
+                        /*
+                         * bail out
+                         */
+#ifdef VBOX_WITH_NATIVE_NEM
+                        if (fFlags & PGMPHYS_ROM_FLAGS_SHADOWED)
+                        {
+                            Assert(VM_IS_NEM_ENABLED(pVM));
+                            pVM->pgm.s.cPrivatePages -= cGuestPages;
+                            pVM->pgm.s.cAllPages     -= cGuestPages;
+                        }
+#endif
                     }
                     else
-#endif
-                        return rc;
-
-                    /*
-                     * bail out
-                     */
-#ifdef VBOX_WITH_NATIVE_NEM
-                    /* unlink */
-                    if (pRomPrev)
-                    {
-                        pRomPrev->pNextR3 = pRom;
-                        pRomPrev->pNextR0 = pRom ? pRom->pSelfR0 : NIL_RTR0PTR;
-                    }
-                    else
-                    {
-                        pVM->pgm.s.pRomRangesR3 = pRom;
-                        pVM->pgm.s.pRomRangesR0 = pRom ? pRom->pSelfR0 : NIL_RTR0PTR;
-                    }
-
-                    if (fFlags & PGMPHYS_ROM_FLAGS_SHADOWED)
-                    {
-# ifdef VBOX_WITH_PGM_NEM_MODE
-                        if (pVM->pgm.s.fNemMode)
-                            pVM->pgm.s.cPrivatePages -= cGuestPages;
-                        else
-# endif
-                            pVM->pgm.s.cZeroPages -= cGuestPages;
-                        pVM->pgm.s.cAllPages -= cGuestPages;
-                    }
-#endif
+                        rc = VERR_NO_MEMORY;
                 }
-                else
-                    rc = VERR_NO_MEMORY;
+
+                int rc2 = PGMHandlerPhysicalDeregister(pVM, GCPhys);
+                AssertRC(rc2);
             }
 
-            int rc2 = PGMHandlerPhysicalDeregister(pVM, GCPhys);
-            AssertRC(rc2);
-        }
-
-        if (!fRamExists)
-            pgmR3PhysUnlinkRamRange2(pVM, pRamNew, pRamPrev);
-        else
-        {
-            PPGMPAGE pRamPage = &pRam->aPages[idxFirstRamPage];
+            idxInsert -= 1;
+            if (!pOverlappingRange)
+                pgmR3PhysRamRangeRemoveLookup(pVM, pRamRange, &idxInsert);
+        }
+        /* else: lookup insertion failed. */
+
+        if (pOverlappingRange)
+        {
+            PPGMPAGE pRamPage = &pOverlappingRange->aPages[idxFirstRamPage];
 #ifdef VBOX_WITH_PGM_NEM_MODE
-            if (pVM->pgm.s.fNemMode)
+            if (PGM_IS_IN_NEM_MODE(pVM))
             {
                 Assert(pvRam == NULL); Assert(pReq == NULL);
@@ -4733,11 +4936,10 @@
             }
         }
-
-        SUPR3PageFreeEx(pRomNew, cRangePages);
-    }
-
-    /** @todo Purge the mapping cache or something... */
+    }
+    pgmPhysInvalidatePageMapTLB(pVM);
+    pgmPhysInvalidRamRangeTlbs(pVM);
+
 #ifdef VBOX_WITH_PGM_NEM_MODE
-    if (pVM->pgm.s.fNemMode)
+    if (PGM_IS_IN_NEM_MODE(pVM))
     {
         Assert(!pReq);
@@ -4753,4 +4955,8 @@
         GMMR3AllocatePagesCleanup(pReq);
     }
+
+    /* We don't bother to actually free either the ROM nor the RAM ranges
+       themselves, as already mentioned above, we'll leave that to the VM
+       termination cleanup code. */
     return rc;
 }
@@ -4764,5 +4970,5 @@
  * where we can select where the reads go and where the writes go. On real
  * hardware the chipset provides means to configure this. We provide
- * PGMR3PhysProtectROM() for this purpose.
+ * PGMR3PhysRomProtect() for this purpose.
  *
  * A read-only copy of the ROM image will always be kept around while we
@@ -4793,5 +4999,7 @@
          pDevIns, GCPhys, GCPhys + cb, cb, pvBinary, cbBinary, fFlags, pszDesc));
     PGM_LOCK_VOID(pVM);
+
     int rc = pgmR3PhysRomRegisterLocked(pVM, pDevIns, GCPhys, cb, pvBinary, cbBinary, fFlags, pszDesc);
+
     PGM_UNLOCK(pVM);
     return rc;
@@ -4812,7 +5020,9 @@
 {
     PGM_LOCK_ASSERT_OWNER(pVM);
-    for (PPGMROMRANGE pRom = pVM->pgm.s.pRomRangesR3; pRom; pRom = pRom->pNextR3)
-    {
-        const uint32_t cGuestPages = pRom->cb >> GUEST_PAGE_SHIFT;
+    uint32_t const cRomRanges = RT_MIN(pVM->pgm.s.cRomRanges, RT_ELEMENTS(pVM->pgm.s.apRomRanges));
+    for (uint32_t idx = 0; idx < cRomRanges; idx++)
+    {
+        PPGMROMRANGE const pRom        = pVM->pgm.s.apRomRanges[idx];
+        uint32_t const     cGuestPages = pRom->cb >> GUEST_PAGE_SHIFT;
 
         if (pRom->fFlags & PGMPHYS_ROM_FLAGS_SHADOWED)
@@ -4830,5 +5040,5 @@
              */
 #ifdef VBOX_WITH_PGM_NEM_MODE
-            if (pVM->pgm.s.fNemMode)
+            if (PGM_IS_IN_NEM_MODE(pVM))
             {
                 /* Clear all the shadow pages (currently using alternate backing). */
@@ -4871,6 +5081,6 @@
                         continue;
                     Assert(!PGM_PAGE_IS_BALLOONED(&pRom->aPages[iPage].Shadow));
-                    void *pvDstPage;
-                    const RTGCPHYS GCPhys = pRom->GCPhys + (iPage << GUEST_PAGE_SHIFT);
+                    void          *pvDstPage;
+                    RTGCPHYS const GCPhys = pRom->GCPhys + (iPage << GUEST_PAGE_SHIFT);
                     rc = pgmPhysPageMakeWritableAndMap(pVM, &pRom->aPages[iPage].Shadow, GCPhys, &pvDstPage);
                     if (RT_FAILURE(rc))
@@ -4895,5 +5105,5 @@
             for (uint32_t iPage = 0; iPage < cGuestPages && cbSrcLeft > 0; iPage++, pbSrcPage += GUEST_PAGE_SIZE)
             {
-                const RTGCPHYS GCPhys    = pRom->GCPhys + (iPage << GUEST_PAGE_SHIFT);
+                RTGCPHYS const GCPhys    = pRom->GCPhys + (iPage << GUEST_PAGE_SHIFT);
                 PPGMPAGE const pPage     = pgmPhysGetPage(pVM, GCPhys);
                 void const    *pvDstPage = NULL;
@@ -4942,6 +5152,8 @@
      * Free the heap copy of the original bits.
      */
-    for (PPGMROMRANGE pRom = pVM->pgm.s.pRomRangesR3; pRom; pRom = pRom->pNextR3)
-    {
+    uint32_t const cRomRanges = RT_MIN(pVM->pgm.s.cRomRanges, RT_ELEMENTS(pVM->pgm.s.apRomRanges));
+    for (uint32_t idx = 0; idx < cRomRanges; idx++)
+    {
+        PPGMROMRANGE const pRom = pVM->pgm.s.apRomRanges[idx];
         if (   pRom->pvOriginal
             && !(pRom->fFlags & PGMPHYS_ROM_FLAGS_PERMANENT_BINARY))
@@ -4990,6 +5202,8 @@
     int  rc = VINF_SUCCESS;
     bool fFlushTLB = false;
-    for (PPGMROMRANGE pRom = pVM->pgm.s.pRomRangesR3; pRom; pRom = pRom->pNextR3)
-    {
+    uint32_t const cRomRanges = RT_MIN(pVM->pgm.s.cRomRanges, RT_ELEMENTS(pVM->pgm.s.apRomRanges));
+    for (uint32_t idx = 0; idx < cRomRanges; idx++)
+    {
+        PPGMROMRANGE const pRom = pVM->pgm.s.apRomRanges[idx];
         if (   GCPhys     <= pRom->GCPhysLast
             && GCPhysLast >= pRom->GCPhys
@@ -5036,5 +5250,5 @@
 # ifdef VBOX_WITH_PGM_NEM_MODE
                     /* In simplified mode we have to switch the page data around too. */
-                    if (pVM->pgm.s.fNemMode)
+                    if (PGM_IS_IN_NEM_MODE(pVM))
                     {
                         uint8_t         abPage[GUEST_PAGE_SIZE];
@@ -5291,4 +5505,5 @@
 
 
+
 /*********************************************************************************************************************************
 *   Write Monitoring                                                                                                             *
@@ -5317,15 +5532,17 @@
 #endif
 
-    /** @todo pointless to write protect the physical page pointed to by RSP. */
-
-    for (PPGMRAMRANGE pRam = pVM->pgm.s.CTX_SUFF(pRamRangesX);
-         pRam;
-         pRam = pRam->CTX_SUFF(pNext))
-    {
+    uint32_t const cLookupEntries = RT_MIN(pVM->pgm.s.RamRangeUnion.cLookupEntries, RT_ELEMENTS(pVM->pgm.s.aRamRangeLookup));
+    for (uint32_t idxLookup = 0; idxLookup < cLookupEntries; idxLookup++)
+    {
+        uint32_t const idRamRange = PGMRAMRANGELOOKUPENTRY_GET_ID(pVM->pgm.s.aRamRangeLookup[idxLookup]);
+        AssertContinue(idRamRange < RT_ELEMENTS(pVM->pgm.s.apRamRanges));
+        PPGMRAMRANGE const pRam = pVM->pgm.s.apRamRanges[idRamRange];
+        AssertContinue(pRam);
+
         uint32_t cPages = pRam->cb >> GUEST_PAGE_SHIFT;
         for (uint32_t iPage = 0; iPage < cPages; iPage++)
         {
-            PPGMPAGE    pPage = &pRam->aPages[iPage];
-            PGMPAGETYPE enmPageType = (PGMPAGETYPE)PGM_PAGE_GET_TYPE(pPage);
+            PPGMPAGE const    pPage       = &pRam->aPages[iPage];
+            PGMPAGETYPE const enmPageType = (PGMPAGETYPE)PGM_PAGE_GET_TYPE(pPage);
 
             if (    RT_LIKELY(enmPageType == PGMPAGETYPE_RAM)
@@ -5927,8 +6144,12 @@
                 if (idPage != NIL_GMM_PAGEID)
                 {
-                    for (PPGMRAMRANGE pRam = pVM->pgm.s.pRamRangesXR3;
-                         pRam;
-                         pRam = pRam->pNextR3)
+                    uint32_t const idRamRangeMax = RT_MIN(pVM->pgm.s.idRamRangeMax, RT_ELEMENTS(pVM->pgm.s.apRamRanges) - 1U);
+                    for (uint32_t idRamRange = 0; idRamRange <= idRamRangeMax; idRamRange++)
                     {
+                        PPGMRAMRANGE const pRam = pVM->pgm.s.apRamRanges[idRamRange];
+                        Assert(pRam || idRamRange == 0);
+                        if (!pRam) continue;
+                        Assert(pRam->idRange == idRamRange);
+
                         uint32_t const cPages = pRam->cb >> GUEST_PAGE_SHIFT;
                         for (uint32_t iPage = 0; iPage < cPages; iPage++)
Index: trunk/src/VBox/VMM/VMMR3/PGMPool.cpp
===================================================================
--- trunk/src/VBox/VMM/VMMR3/PGMPool.cpp	(revision 104839)
+++ trunk/src/VBox/VMM/VMMR3/PGMPool.cpp	(revision 104840)
@@ -715,8 +715,9 @@
      * Clear all the GCPhys links and rebuild the phys ext free list.
      */
-    for (PPGMRAMRANGE pRam = pPool->CTX_SUFF(pVM)->pgm.s.CTX_SUFF(pRamRangesX);
-         pRam;
-         pRam = pRam->CTX_SUFF(pNext))
+    uint32_t const idRamRangeMax = RT_MIN(pVM->pgm.s.idRamRangeMax, RT_ELEMENTS(pVM->pgm.s.apRamRanges) - 1U);
+    Assert(pVM->pgm.s.apRamRanges[0] == NULL);
+    for (uint32_t idx = 1; idx <= idRamRangeMax; idx++)
     {
+        PPGMRAMRANGE const pRam = pVM->pgm.s.apRamRanges[idx];
         iPage = pRam->cb >> GUEST_PAGE_SHIFT;
         while (iPage-- > 0)
Index: trunk/src/VBox/VMM/VMMR3/PGMSavedState.cpp
===================================================================
--- trunk/src/VBox/VMM/VMMR3/PGMSavedState.cpp	(revision 104839)
+++ trunk/src/VBox/VMM/VMMR3/PGMSavedState.cpp	(revision 104840)
@@ -224,9 +224,9 @@
 static PPGMROMPAGE pgmR3GetRomPage(PVM pVM, RTGCPHYS GCPhys) /** @todo change this to take a hint. */
 {
-    for (PPGMROMRANGE pRomRange = pVM->pgm.s.CTX_SUFF(pRomRanges);
-         pRomRange;
-         pRomRange = pRomRange->CTX_SUFF(pNext))
-    {
-        RTGCPHYS off = GCPhys - pRomRange->GCPhys;
+    uint32_t const cRomRanges = RT_MIN(pVM->pgm.s.cRomRanges, RT_ELEMENTS(pVM->pgm.s.apRomRanges));
+    for (uint32_t idx = 0; idx < cRomRanges; idx++)
+    {
+        PPGMROMRANGE const pRomRange = pVM->pgm.s.apRomRanges[idx];
+        RTGCPHYS const     off       = GCPhys - pRomRange->GCPhys;
         if (GCPhys - pRomRange->GCPhys < pRomRange->cb)
             return &pRomRange->aPages[off >> GUEST_PAGE_SHIFT];
@@ -248,8 +248,10 @@
      */
     PGM_LOCK_VOID(pVM);
-    for (PPGMROMRANGE pRom = pVM->pgm.s.pRomRangesR3; pRom; pRom = pRom->pNextR3)
-    {
-        PPGMRAMRANGE    pRamHint = NULL;;
-        uint32_t const  cPages   = pRom->cb >> GUEST_PAGE_SHIFT;
+    uint32_t const cRomRanges = RT_MIN(pVM->pgm.s.cRomRanges, RT_ELEMENTS(pVM->pgm.s.apRomRanges));
+    for (uint32_t idx = 0; idx < cRomRanges; idx++)
+    {
+        PPGMROMRANGE const pRom     = pVM->pgm.s.apRomRanges[idx];
+        uint32_t const     cPages   = pRom->cb >> GUEST_PAGE_SHIFT;
+        PPGMRAMRANGE       pRamHint = NULL;
 
         for (uint32_t iPage = 0; iPage < cPages; iPage++)
@@ -297,9 +299,11 @@
 {
     PGM_LOCK_VOID(pVM);
-    uint8_t id = 1;
-    for (PPGMROMRANGE pRom = pVM->pgm.s.pRomRangesR3; pRom; pRom = pRom->pNextR3, id++)
-    {
-        pRom->idSavedState = id;
-        SSMR3PutU8(pSSM, id);
+    uint32_t const cRomRanges = RT_MIN(pVM->pgm.s.cRomRanges, RT_ELEMENTS(pVM->pgm.s.apRomRanges));
+    for (uint32_t idx = 0; idx < cRomRanges; idx++)
+    {
+        PPGMROMRANGE const pRom         = pVM->pgm.s.apRomRanges[idx];
+        uint8_t const      idSavedState = (uint8_t)(idx + 1);
+        pRom->idSavedState = idSavedState;
+        SSMR3PutU8(pSSM, idSavedState);
         SSMR3PutStrZ(pSSM, "");         /* device name */
         SSMR3PutU32(pSSM, 0);           /* device instance */
@@ -328,6 +332,7 @@
     PGM_LOCK_ASSERT_OWNER(pVM);
 
-    for (PPGMROMRANGE pRom = pVM->pgm.s.pRomRangesR3; pRom; pRom = pRom->pNextR3)
-        pRom->idSavedState = UINT8_MAX;
+    uint32_t const cRomRanges = RT_MIN(pVM->pgm.s.cRomRanges, RT_ELEMENTS(pVM->pgm.s.apRomRanges));
+    for (uint32_t idx = 0; idx < cRomRanges; idx++)
+        pVM->pgm.s.apRomRanges[idx]->idSavedState = UINT8_MAX;
 
     for (;;)
@@ -342,5 +347,10 @@
         if (id == UINT8_MAX)
         {
-            for (PPGMROMRANGE pRom = pVM->pgm.s.pRomRangesR3; pRom; pRom = pRom->pNextR3)
+            /*
+             * End of ROM ranges. Check that all are accounted for.
+             */
+            for (uint32_t idx = 0; idx < cRomRanges; idx++)
+            {
+                PPGMROMRANGE const pRom = pVM->pgm.s.apRomRanges[idx];
                 if (pRom->idSavedState != UINT8_MAX)
                 { /* likely */ }
@@ -352,4 +362,5 @@
                                     ("The '%s' ROM was not found in the saved state. Probably due to some misconfiguration\n",
                                      pRom->pszDesc));
+            }
             return VINF_SUCCESS;        /* the end */
         }
@@ -384,11 +395,12 @@
                               && iRegion == 0
                               && szDevName[0] == '\0',
-                              ("GCPhys=%RGp %s\n", GCPhys, szDesc),
+                              ("GCPhys=%RGp LB %RGp %s\n", GCPhys, cb, szDesc),
                               VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
-        PPGMROMRANGE pRom;
-        for (pRom = pVM->pgm.s.pRomRangesR3; pRom; pRom = pRom->pNextR3)
-        {
-            if (    pRom->idSavedState == UINT8_MAX
-                &&  !strcmp(pRom->pszDesc, szDesc))
+        uint32_t idx;
+        for (idx = 0; idx < cRomRanges; idx++)
+        {
+            PPGMROMRANGE const pRom = pVM->pgm.s.apRomRanges[idx];
+            if (   pRom->idSavedState == UINT8_MAX
+                && !strcmp(pRom->pszDesc, szDesc))
             {
                 pRom->idSavedState = id;
@@ -396,6 +408,7 @@
             }
         }
-        if (!pRom)
-            return SSMR3SetCfgError(pSSM, RT_SRC_POS, N_("ROM at %RGp by the name '%s' was not found"), GCPhys, szDesc);
+        if (idx >= cRomRanges)
+            return SSMR3SetCfgError(pSSM, RT_SRC_POS, N_("ROM at %RGp LB %RGp by the name '%s' was not found"),
+                                    GCPhys, cb, szDesc);
     } /* forever */
 }
@@ -413,6 +426,8 @@
      */
     PGM_LOCK_VOID(pVM);
-    for (PPGMROMRANGE pRom = pVM->pgm.s.pRomRangesR3; pRom; pRom = pRom->pNextR3)
-    {
+    uint32_t const cRomRanges = RT_MIN(pVM->pgm.s.cRomRanges, RT_ELEMENTS(pVM->pgm.s.apRomRanges));
+    for (uint32_t idx = 0; idx < cRomRanges; idx++)
+    {
+        PPGMROMRANGE const pRom = pVM->pgm.s.apRomRanges[idx];
         if (pRom->fFlags & PGMPHYS_ROM_FLAGS_SHADOWED)
         {
@@ -455,7 +470,9 @@
 {
     PGM_LOCK_VOID(pVM);
-    for (PPGMROMRANGE pRom = pVM->pgm.s.pRomRangesR3; pRom; pRom = pRom->pNextR3)
-    {
-        uint32_t const cPages = pRom->cb >> GUEST_PAGE_SHIFT;
+    uint32_t const cRomRanges = RT_MIN(pVM->pgm.s.cRomRanges, RT_ELEMENTS(pVM->pgm.s.apRomRanges));
+    for (uint32_t idx = 0; idx < cRomRanges; idx++)
+    {
+        PPGMROMRANGE const pRom   = pVM->pgm.s.apRomRanges[idx];
+        uint32_t const     cPages = pRom->cb >> GUEST_PAGE_SHIFT;
         for (uint32_t iPage = 0; iPage < cPages; iPage++)
         {
@@ -478,5 +495,5 @@
                 void const *pvPage;
 #ifdef VBOX_WITH_PGM_NEM_MODE
-                if (!PGMROMPROT_IS_ROM(enmProt) && pVM->pgm.s.fNemMode)
+                if (!PGMROMPROT_IS_ROM(enmProt) && PGM_IS_IN_NEM_MODE(pVM))
                     pvPage = &pRom->pbR3Alternate[iPage << GUEST_PAGE_SHIFT];
                 else
@@ -541,6 +558,8 @@
      */
     PGM_LOCK_VOID(pVM);
-    for (PPGMROMRANGE pRom = pVM->pgm.s.pRomRangesR3; pRom; pRom = pRom->pNextR3)
-    {
+    uint32_t const cRomRanges = RT_MIN(pVM->pgm.s.cRomRanges, RT_ELEMENTS(pVM->pgm.s.apRomRanges));
+    for (uint32_t idx = 0; idx < cRomRanges; idx++)
+    {
+        PPGMROMRANGE const pRom = pVM->pgm.s.apRomRanges[idx];
         if (pRom->fFlags & PGMPHYS_ROM_FLAGS_SHADOWED)
         {
@@ -569,5 +588,5 @@
                         void const *pvPage;
 #ifdef VBOX_WITH_PGM_NEM_MODE
-                        if (PGMROMPROT_IS_ROM(enmProt) && pVM->pgm.s.fNemMode)
+                        if (PGMROMPROT_IS_ROM(enmProt) && PGM_IS_IN_NEM_MODE(pVM))
                             pvPage = &pRom->pbR3Alternate[iPage << GUEST_PAGE_SHIFT];
                         else
@@ -662,7 +681,10 @@
      */
     PGM_LOCK_VOID(pVM);
-    for (PPGMREGMMIO2RANGE pRegMmio = pVM->pgm.s.pRegMmioRangesR3; pRegMmio; pRegMmio = pRegMmio->pNextR3)
-    {
-        uint32_t const  cPages = pRegMmio->RamRange.cb >> GUEST_PAGE_SHIFT;
+    uint32_t const cMmio2Ranges = RT_MIN(pVM->pgm.s.cMmio2Ranges, RT_ELEMENTS(pVM->pgm.s.aMmio2Ranges));
+    for (uint32_t idx = 0; idx < cMmio2Ranges; idx++)
+    {
+        PPGMREGMMIO2RANGE const pRegMmio2 = &pVM->pgm.s.aMmio2Ranges[idx];
+        PPGMRAMRANGE const      pRamRange = pVM->pgm.s.apMmio2RamRanges[idx];
+        uint32_t const          cPages    = pRamRange->cb >> GUEST_PAGE_SHIFT;
         PGM_UNLOCK(pVM);
 
@@ -682,5 +704,5 @@
 
         PGM_LOCK_VOID(pVM);
-        pRegMmio->paLSPages = paLSPages;
+        pRegMmio2->paLSPages = paLSPages;
         pVM->pgm.s.LiveSave.Mmio2.cDirtyPages += cPages;
     }
@@ -700,17 +722,19 @@
 {
     PGM_LOCK_VOID(pVM);
-    uint8_t id = 1;
-    for (PPGMREGMMIO2RANGE pRegMmio = pVM->pgm.s.pRegMmioRangesR3; pRegMmio; pRegMmio = pRegMmio->pNextR3)
-    {
-        pRegMmio->idSavedState = id;
-        SSMR3PutU8(pSSM, id);
-        SSMR3PutStrZ(pSSM, pRegMmio->pDevInsR3->pReg->szName);
-        SSMR3PutU32(pSSM, pRegMmio->pDevInsR3->iInstance);
-        SSMR3PutU8(pSSM, pRegMmio->iRegion);
-        SSMR3PutStrZ(pSSM, pRegMmio->RamRange.pszDesc);
-        int rc = SSMR3PutGCPhys(pSSM, pRegMmio->RamRange.cb);
+    uint32_t const cMmio2Ranges = RT_MIN(pVM->pgm.s.cMmio2Ranges, RT_ELEMENTS(pVM->pgm.s.aMmio2Ranges));
+    for (uint32_t idx = 0; idx < cMmio2Ranges; idx++)
+    {
+        PPGMREGMMIO2RANGE const pRegMmio2    = &pVM->pgm.s.aMmio2Ranges[idx];
+        PPGMRAMRANGE const      pRamRange    = pVM->pgm.s.apMmio2RamRanges[idx];
+        uint8_t const           idSavedState = (uint8_t)(idx + 1);
+        pRegMmio2->idSavedState = idSavedState;
+        SSMR3PutU8(pSSM, idSavedState);
+        SSMR3PutStrZ(pSSM, pRegMmio2->pDevInsR3->pReg->szName);
+        SSMR3PutU32(pSSM, pRegMmio2->pDevInsR3->iInstance);
+        SSMR3PutU8(pSSM, pRegMmio2->iRegion);
+        SSMR3PutStrZ(pSSM, pRamRange->pszDesc);
+        int rc = SSMR3PutGCPhys(pSSM, pRamRange->cb);
         if (RT_FAILURE(rc))
             break;
-        id++;
     }
     PGM_UNLOCK(pVM);
@@ -731,6 +755,7 @@
     PGM_LOCK_ASSERT_OWNER(pVM);
 
-    for (PPGMREGMMIO2RANGE pRegMmio = pVM->pgm.s.pRegMmioRangesR3; pRegMmio; pRegMmio = pRegMmio->pNextR3)
-        pRegMmio->idSavedState = UINT8_MAX;
+    uint32_t const cMmio2Ranges = RT_MIN(pVM->pgm.s.cMmio2Ranges, RT_ELEMENTS(pVM->pgm.s.aMmio2Ranges));
+    for (uint32_t idx = 0; idx < cMmio2Ranges; idx++)
+        pVM->pgm.s.aMmio2Ranges[idx].idSavedState = UINT8_MAX;
 
     for (;;)
@@ -745,6 +770,10 @@
         if (id == UINT8_MAX)
         {
-            for (PPGMREGMMIO2RANGE pRegMmio = pVM->pgm.s.pRegMmioRangesR3; pRegMmio; pRegMmio = pRegMmio->pNextR3)
-                AssertLogRelMsg(pRegMmio->idSavedState != UINT8_MAX, ("%s\n", pRegMmio->RamRange.pszDesc));
+            /*
+             * End of MMIO2 ranges. Check that all are accounted for.
+             */
+            for (uint32_t idx = 0; idx < cMmio2Ranges; idx++)
+                AssertLogRelMsg(pVM->pgm.s.aMmio2Ranges[idx].idSavedState != UINT8_MAX,
+                                ("%s\n", pVM->pgm.s.apMmio2RamRanges[idx]->pszDesc));
             return VINF_SUCCESS;        /* the end */
         }
@@ -772,17 +801,18 @@
          * Locate a matching MMIO2 range.
          */
-        PPGMREGMMIO2RANGE pRegMmio;
-        for (pRegMmio = pVM->pgm.s.pRegMmioRangesR3; pRegMmio; pRegMmio = pRegMmio->pNextR3)
-        {
-            if (    pRegMmio->idSavedState == UINT8_MAX
-                &&  pRegMmio->iRegion == iRegion
-                &&  pRegMmio->pDevInsR3->iInstance == uInstance
-                &&  !strcmp(pRegMmio->pDevInsR3->pReg->szName, szDevName))
+        uint32_t idx;
+        for (idx = 0; idx < cMmio2Ranges; idx++)
+        {
+            PPGMREGMMIO2RANGE const pRegMmio2 = &pVM->pgm.s.aMmio2Ranges[idx];
+            if (    pRegMmio2->idSavedState == UINT8_MAX
+                &&  pRegMmio2->iRegion == iRegion
+                &&  pRegMmio2->pDevInsR3->iInstance == uInstance
+                &&  !strcmp(pRegMmio2->pDevInsR3->pReg->szName, szDevName))
             {
-                pRegMmio->idSavedState = id;
+                pRegMmio2->idSavedState = id;
                 break;
             }
         }
-        if (!pRegMmio)
+        if (idx >= cMmio2Ranges)
             return SSMR3SetCfgError(pSSM, RT_SRC_POS, N_("Failed to locate a MMIO2 range called '%s' owned by %s/%u, region %d"),
                                     szDesc, szDevName, uInstance, iRegion);
@@ -792,11 +822,11 @@
          * the same.
          */
-        if (cb != pRegMmio->RamRange.cb)
-        {
-            LogRel(("PGM: MMIO2 region \"%s\" size mismatch: saved=%RGp config=%RGp\n",
-                    pRegMmio->RamRange.pszDesc, cb, pRegMmio->RamRange.cb));
-            if (cb > pRegMmio->RamRange.cb) /* bad idea? */
+        PPGMRAMRANGE const pRamRange = pVM->pgm.s.apMmio2RamRanges[idx];
+        if (cb != pRamRange->cb)
+        {
+            LogRel(("PGM: MMIO2 region \"%s\" size mismatch: saved=%RGp config=%RGp\n", pRamRange->pszDesc, cb, pRamRange->cb));
+            if (cb > pRamRange->cb) /* bad idea? */
                 return SSMR3SetCfgError(pSSM, RT_SRC_POS, N_("MMIO2 region \"%s\" size mismatch: saved=%RGp config=%RGp"),
-                                        pRegMmio->RamRange.pszDesc, cb, pRegMmio->RamRange.cb);
+                                        pRamRange->pszDesc, cb, pRamRange->cb);
         }
     } /* forever */
@@ -896,13 +926,15 @@
 
     PGM_LOCK_VOID(pVM);                 /* paranoia */
-    for (PPGMREGMMIO2RANGE pRegMmio = pVM->pgm.s.pRegMmioRangesR3; pRegMmio; pRegMmio = pRegMmio->pNextR3)
-    {
-        PPGMLIVESAVEMMIO2PAGE paLSPages = pRegMmio->paLSPages;
-        uint32_t              cPages    = pRegMmio->RamRange.cb >> GUEST_PAGE_SHIFT;
+    uint32_t const cMmio2Ranges = RT_MIN(pVM->pgm.s.cMmio2Ranges, RT_ELEMENTS(pVM->pgm.s.aMmio2Ranges));
+    for (uint32_t idx = 0; idx < cMmio2Ranges; idx++)
+    {
+        PPGMREGMMIO2RANGE const pRegMmio2 = &pVM->pgm.s.aMmio2Ranges[idx];
+        PPGMLIVESAVEMMIO2PAGE   paLSPages = pRegMmio2->paLSPages;
+        uint32_t                cPages    = pVM->pgm.s.apMmio2RamRanges[idx]->cb >> GUEST_PAGE_SHIFT;
         PGM_UNLOCK(pVM);
 
         for (uint32_t iPage = 0; iPage < cPages; iPage++)
         {
-            uint8_t const *pbPage = (uint8_t const *)pRegMmio->pvR3 + iPage * GUEST_PAGE_SIZE;
+            uint8_t const *pbPage = &pRegMmio2->pbR3[iPage * GUEST_PAGE_SIZE];
             pgmR3ScanMmio2Page(pVM, pbPage, &paLSPages[iPage]);
         }
@@ -936,12 +968,13 @@
          */
         PGM_LOCK_VOID(pVM);
-        for (PPGMREGMMIO2RANGE pRegMmio = pVM->pgm.s.pRegMmioRangesR3;
-             pRegMmio && RT_SUCCESS(rc);
-             pRegMmio = pRegMmio->pNextR3)
-        {
-            PPGMLIVESAVEMMIO2PAGE paLSPages = pRegMmio->paLSPages;
-            uint8_t const        *pbPage    = (uint8_t const *)pRegMmio->RamRange.pvR3;
-            uint32_t              cPages    = pRegMmio->RamRange.cb >> GUEST_PAGE_SHIFT;
-            uint32_t              iPageLast = cPages;
+        uint32_t const cMmio2Ranges = RT_MIN(pVM->pgm.s.cMmio2Ranges, RT_ELEMENTS(pVM->pgm.s.aMmio2Ranges));
+        for (uint32_t idx = 0; idx < cMmio2Ranges && RT_SUCCESS(rc); idx++)
+        {
+            PPGMREGMMIO2RANGE const     pRegMmio2 = &pVM->pgm.s.aMmio2Ranges[idx];
+            PPGMRAMRANGE const          pRamRange = pVM->pgm.s.apMmio2RamRanges[idx];
+            PPGMLIVESAVEMMIO2PAGE const paLSPages = pRegMmio2->paLSPages;
+            uint32_t const              cPages    = pRamRange->cb >> GUEST_PAGE_SHIFT;
+            uint8_t const              *pbPage    = pRamRange->pbR3;
+            uint32_t                    iPageLast = cPages;
             for (uint32_t iPage = 0; iPage < cPages; iPage++, pbPage += GUEST_PAGE_SIZE)
             {
@@ -972,5 +1005,5 @@
                 {
                     SSMR3PutU8(pSSM, u8Type | PGM_STATE_REC_FLAG_ADDR);
-                    SSMR3PutU8(pSSM, pRegMmio->idSavedState);
+                    SSMR3PutU8(pSSM, pRegMmio2->idSavedState);
                     rc = SSMR3PutU32(pSSM, iPage);
                 }
@@ -993,12 +1026,13 @@
     {
         PGM_LOCK_VOID(pVM);
-        for (PPGMREGMMIO2RANGE pRegMmio = pVM->pgm.s.pRegMmioRangesR3;
-             pRegMmio && RT_SUCCESS(rc);
-             pRegMmio = pRegMmio->pNextR3)
-        {
-            PPGMLIVESAVEMMIO2PAGE paLSPages = pRegMmio->paLSPages;
-            uint8_t const        *pbPage    = (uint8_t const *)pRegMmio->RamRange.pvR3;
-            uint32_t              cPages    = pRegMmio->RamRange.cb >> GUEST_PAGE_SHIFT;
-            uint32_t              iPageLast = cPages;
+        uint32_t const cMmio2Ranges = RT_MIN(pVM->pgm.s.cMmio2Ranges, RT_ELEMENTS(pVM->pgm.s.aMmio2Ranges));
+        for (uint32_t idx = 0; idx < cMmio2Ranges && RT_SUCCESS(rc); idx++)
+        {
+            PPGMREGMMIO2RANGE const     pRegMmio2 = &pVM->pgm.s.aMmio2Ranges[idx];
+            PPGMRAMRANGE const          pRamRange = pVM->pgm.s.apMmio2RamRanges[idx];
+            PPGMLIVESAVEMMIO2PAGE const paLSPages = pRegMmio2->paLSPages;
+            uint32_t const              cPages    = pRamRange->cb >> GUEST_PAGE_SHIFT;
+            uint8_t const              *pbPage    = pRamRange->pbR3;
+            uint32_t                    iPageLast = cPages;
             PGM_UNLOCK(pVM);
 
@@ -1028,5 +1062,5 @@
                 {
                     SSMR3PutU8(pSSM, u8Type | PGM_STATE_REC_FLAG_ADDR);
-                    SSMR3PutU8(pSSM, pRegMmio->idSavedState);
+                    SSMR3PutU8(pSSM, pRegMmio2->idSavedState);
                     rc = SSMR3PutU32(pSSM, iPage);
                 }
@@ -1067,10 +1101,12 @@
      */
     PGM_LOCK_VOID(pVM);
-    for (PPGMREGMMIO2RANGE pRegMmio = pVM->pgm.s.pRegMmioRangesR3; pRegMmio; pRegMmio = pRegMmio->pNextR3)
-    {
-        void *pvMmio2ToFree = pRegMmio->paLSPages;
+    uint32_t const cMmio2Ranges = RT_MIN(pVM->pgm.s.cMmio2Ranges, RT_ELEMENTS(pVM->pgm.s.aMmio2Ranges));
+    for (uint32_t idx = 0; idx < cMmio2Ranges; idx++)
+    {
+        PPGMREGMMIO2RANGE const pRegMmio2     = &pVM->pgm.s.aMmio2Ranges[idx];
+        void                   *pvMmio2ToFree = pRegMmio2->paLSPages;
         if (pvMmio2ToFree)
         {
-            pRegMmio->paLSPages = NULL;
+            pRegMmio2->paLSPages = NULL;
             PGM_UNLOCK(pVM);
             MMR3HeapFree(pvMmio2ToFree);
@@ -1101,14 +1137,20 @@
      *       for cleaning up.
      */
-    PPGMRAMRANGE pCur;
     PGM_LOCK_VOID(pVM);
+    uint32_t idRamRange;
     do
     {
-        for (pCur = pVM->pgm.s.pRamRangesXR3; pCur; pCur = pCur->pNextR3)
-        {
+        uint32_t const idRamRangeMax = RT_MIN(pVM->pgm.s.idRamRangeMax, RT_ELEMENTS(pVM->pgm.s.apRamRanges) - 1U);
+        for (idRamRange = 0; idRamRange <= idRamRangeMax; idRamRange++)
+        {
+            PPGMRAMRANGE const pCur = pVM->pgm.s.apRamRanges[idRamRange];
+            Assert(pCur || idRamRange == 0);
+            if (!pCur) continue;
+            Assert(pCur->idRange == idRamRange);
+
             if (   !pCur->paLSPages
                 && !PGM_RAM_RANGE_IS_AD_HOC(pCur))
             {
-                uint32_t const  idRamRangesGen = pVM->pgm.s.idRamRangesGen;
+                uint32_t const  idRamRangesGen = pVM->pgm.s.RamRangeUnion.idGeneration;
                 uint32_t const  cPages = pCur->cb >> GUEST_PAGE_SHIFT;
                 PGM_UNLOCK(pVM);
@@ -1117,5 +1159,5 @@
                     return VERR_NO_MEMORY;
                 PGM_LOCK_VOID(pVM);
-                if (pVM->pgm.s.idRamRangesGen != idRamRangesGen)
+                if (pVM->pgm.s.RamRangeUnion.idGeneration != idRamRangesGen)
                 {
                     PGM_UNLOCK(pVM);
@@ -1215,5 +1257,5 @@
             }
         }
-    } while (pCur);
+    } while (idRamRange <= RT_MIN(pVM->pgm.s.idRamRangeMax, RT_ELEMENTS(pVM->pgm.s.apRamRanges) - 1U));
     PGM_UNLOCK(pVM);
 
@@ -1366,11 +1408,20 @@
      */
     RTGCPHYS GCPhysCur = 0;
-    PPGMRAMRANGE pCur;
+    uint32_t idxLookup;
+    uint32_t cLookupEntries;
     PGM_LOCK_VOID(pVM);
     do
     {
-        uint32_t const  idRamRangesGen = pVM->pgm.s.idRamRangesGen;
-        for (pCur = pVM->pgm.s.pRamRangesXR3; pCur; pCur = pCur->pNextR3)
-        {
+        PGM::PGMRAMRANGEGENANDLOOKUPCOUNT const RamRangeUnion = pVM->pgm.s.RamRangeUnion;
+        Assert(pVM->pgm.s.RamRangeUnion.cLookupEntries < RT_ELEMENTS(pVM->pgm.s.aRamRangeLookup));
+        cLookupEntries = pVM->pgm.s.RamRangeUnion.cLookupEntries;
+        for (idxLookup = 0; idxLookup < cLookupEntries; idxLookup++)
+        {
+            uint32_t const idRamRange = PGMRAMRANGELOOKUPENTRY_GET_ID(pVM->pgm.s.aRamRangeLookup[idxLookup]);
+            AssertContinue(idRamRange < RT_ELEMENTS(pVM->pgm.s.apRamRanges));
+            PPGMRAMRANGE const pCur = pVM->pgm.s.apRamRanges[idRamRange];
+            AssertContinue(pCur);
+            Assert(pCur->GCPhys == PGMRAMRANGELOOKUPENTRY_GET_FIRST(pVM->pgm.s.aRamRangeLookup[idxLookup]));
+
             if (    pCur->GCPhysLast > GCPhysCur
                 && !PGM_RAM_RANGE_IS_AD_HOC(pCur))
@@ -1388,5 +1439,5 @@
 #endif
                         && PDMR3CritSectYield(pVM, &pVM->pgm.s.CritSectX)
-                        && pVM->pgm.s.idRamRangesGen != idRamRangesGen)
+                        && pVM->pgm.s.RamRangeUnion.u64Combined != RamRangeUnion.u64Combined)
                     {
                         GCPhysCur = pCur->GCPhys + ((RTGCPHYS)iPage << GUEST_PAGE_SHIFT);
@@ -1556,5 +1607,9 @@
             }
         } /* for each range */
-    } while (pCur);
+
+        /* We must use the starting lookup count here to determine whether we've
+           been thru all or not, since using the current count could lead us to
+           skip the final range if one was umapped while we yielded the lock. */
+    } while (idxLookup < cLookupEntries);
     PGM_UNLOCK(pVM);
 }
@@ -1579,12 +1634,20 @@
     RTGCPHYS GCPhysLast = NIL_RTGCPHYS;
     RTGCPHYS GCPhysCur = 0;
-    PPGMRAMRANGE pCur;
+    uint32_t idxLookup;
+    uint32_t cRamRangeLookupEntries;
 
     PGM_LOCK_VOID(pVM);
     do
     {
-        uint32_t const  idRamRangesGen = pVM->pgm.s.idRamRangesGen;
-        for (pCur = pVM->pgm.s.pRamRangesXR3; pCur; pCur = pCur->pNextR3)
-        {
+        uint32_t const  idRamRangesGen = pVM->pgm.s.RamRangeUnion.idGeneration;
+        cRamRangeLookupEntries = RT_MIN(pVM->pgm.s.RamRangeUnion.cLookupEntries, RT_ELEMENTS(pVM->pgm.s.aRamRangeLookup));
+        for (idxLookup = 0; idxLookup < cRamRangeLookupEntries; idxLookup++)
+        {
+            uint32_t const idRamRange = PGMRAMRANGELOOKUPENTRY_GET_ID(pVM->pgm.s.aRamRangeLookup[idxLookup]);
+            AssertContinue(idRamRange < RT_ELEMENTS(pVM->pgm.s.apRamRanges));
+            PPGMRAMRANGE const pCur = pVM->pgm.s.apRamRanges[idRamRange];
+            AssertContinue(pCur);
+            Assert(pCur->GCPhys == PGMRAMRANGELOOKUPENTRY_GET_FIRST(pVM->pgm.s.aRamRangeLookup[idxLookup]));
+
             if (   pCur->GCPhysLast > GCPhysCur
                 && !PGM_RAM_RANGE_IS_AD_HOC(pCur))
@@ -1600,5 +1663,5 @@
                         && (iPage & 0x7ff) == 0x100
                         && PDMR3CritSectYield(pVM, &pVM->pgm.s.CritSectX)
-                        && pVM->pgm.s.idRamRangesGen != idRamRangesGen)
+                        && pVM->pgm.s.RamRangeUnion.idGeneration != idRamRangesGen)
                     {
                         GCPhysCur = pCur->GCPhys + ((RTGCPHYS)iPage << GUEST_PAGE_SHIFT);
@@ -1737,5 +1800,5 @@
                         pVM->pgm.s.LiveSave.cSavedPages++;
                     }
-                    if (idRamRangesGen != pVM->pgm.s.idRamRangesGen)
+                    if (idRamRangesGen != pVM->pgm.s.RamRangeUnion.idGeneration)
                     {
                         GCPhysCur = GCPhys | GUEST_PAGE_OFFSET_MASK;
@@ -1750,5 +1813,9 @@
             }
         } /* for each range */
-    } while (pCur);
+
+        /* We must use the starting lookup count here to determine whether we've
+           been thru all or not, since using the current count could lead us to
+           skip the final range if one was umapped while we yielded the lock. */
+    } while (idxLookup < cRamRangeLookupEntries);
 
     PGM_UNLOCK(pVM);
@@ -1773,22 +1840,29 @@
      * write monitored pages.
      */
-    void *pvToFree = NULL;
-    PPGMRAMRANGE pCur;
+    void    *pvToFree        = NULL;
     uint32_t cMonitoredPages = 0;
+    uint32_t idRamRangeMax;
+    uint32_t idRamRange;
     PGM_LOCK_VOID(pVM);
     do
     {
-        for (pCur = pVM->pgm.s.pRamRangesXR3; pCur; pCur = pCur->pNextR3)
-        {
+        idRamRangeMax = RT_MIN(pVM->pgm.s.idRamRangeMax, RT_ELEMENTS(pVM->pgm.s.apRamRanges) - 1U);
+        for (idRamRange = 0; idRamRange <= idRamRangeMax; idRamRange++)
+        {
+            PPGMRAMRANGE const pCur = pVM->pgm.s.apRamRanges[idRamRange];
+            Assert(pCur || idRamRange == 0);
+            if (!pCur) continue;
+            Assert(pCur->idRange == idRamRange);
+
             if (pCur->paLSPages)
             {
                 if (pvToFree)
                 {
-                    uint32_t idRamRangesGen = pVM->pgm.s.idRamRangesGen;
+                    uint32_t const idRamRangesGen = pVM->pgm.s.RamRangeUnion.idGeneration;
                     PGM_UNLOCK(pVM);
                     MMR3HeapFree(pvToFree);
                     pvToFree = NULL;
                     PGM_LOCK_VOID(pVM);
-                    if (idRamRangesGen != pVM->pgm.s.idRamRangesGen)
+                    if (idRamRangesGen != pVM->pgm.s.RamRangeUnion.idGeneration)
                         break;          /* start over again. */
                 }
@@ -1810,5 +1884,5 @@
             }
         }
-    } while (pCur);
+    } while (idRamRange <= idRamRangeMax);
 
     Assert(pVM->pgm.s.cMonitoredPages >= cMonitoredPages);
@@ -2346,4 +2420,5 @@
 }
 
+
 /**
  * Ram range flags and bits for older versions of the saved state.
@@ -2357,12 +2432,17 @@
 static int pgmR3LoadMemoryOld(PVM pVM, PSSMHANDLE pSSM, uint32_t uVersion)
 {
-    PPGM pPGM = &pVM->pgm.s;
-
     /*
      * Ram range flags and bits.
      */
-    uint32_t i = 0;
-    for (PPGMRAMRANGE pRam = pPGM->pRamRangesXR3; ; pRam = pRam->pNextR3, i++)
-    {
+    uint32_t       iSeqNo                 = 0;
+    uint32_t const cRamRangeLookupEntries = RT_MIN(pVM->pgm.s.RamRangeUnion.cLookupEntries,
+                                                   RT_ELEMENTS(pVM->pgm.s.aRamRangeLookup));
+    for (uint32_t idxLookup = 0; idxLookup < cRamRangeLookupEntries; idxLookup++)
+    {
+        uint32_t const idRamRange = PGMRAMRANGELOOKUPENTRY_GET_ID(pVM->pgm.s.aRamRangeLookup[idxLookup]);
+        AssertContinue(idRamRange < RT_ELEMENTS(pVM->pgm.s.apRamRanges));
+        PPGMRAMRANGE const pRam = pVM->pgm.s.apRamRanges[idRamRange];
+        AssertContinue(pRam);
+
         /* Check the sequence number / separator. */
         uint32_t u32Sep;
@@ -2372,5 +2452,5 @@
         if (u32Sep == ~0U)
             break;
-        if (u32Sep != i)
+        if (u32Sep != iSeqNo)
         {
             AssertMsgFailed(("u32Sep=%#x (last)\n", u32Sep));
@@ -2432,5 +2512,5 @@
             LogRel(("Ram range: %RGp-%RGp %RGp bytes %s %s\n"
                     "State    : %RGp-%RGp %RGp bytes %s %s\n",
-                    pRam->GCPhys, pRam->GCPhysLast, pRam->cb, pRam->pvR3 ? "bits" : "nobits", pRam->pszDesc,
+                    pRam->GCPhys, pRam->GCPhysLast, pRam->cb, pRam->pbR3 ? "bits" : "nobits", pRam->pszDesc,
                     GCPhys, GCPhysLast, cb, fHaveBits ? "bits" : "nobits", szDesc));
             /*
@@ -2443,7 +2523,8 @@
                                         N_("RAM range mismatch; saved={%RGp-%RGp %RGp bytes %s %s} config={%RGp-%RGp %RGp bytes %s %s}"),
                                         GCPhys, GCPhysLast, cb, fHaveBits ? "bits" : "nobits", szDesc,
-                                        pRam->GCPhys, pRam->GCPhysLast, pRam->cb, pRam->pvR3 ? "bits" : "nobits", pRam->pszDesc);
+                                        pRam->GCPhys, pRam->GCPhysLast, pRam->cb, pRam->pbR3 ? "bits" : "nobits", pRam->pszDesc);
 
             AssertMsgFailed(("debug skipping not implemented, sorry\n"));
+            iSeqNo++;
             continue;
         }
@@ -2525,5 +2606,5 @@
                 }
             }
-            else if (pRam->pvR3)
+            else if (pRam->pbR3)
             {
                 /*
@@ -2533,9 +2614,9 @@
                                       ("fFlags=%#x GCPhys=%#x %s\n", fFlags, pRam->GCPhys, pRam->pszDesc),
                                       VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
-                AssertLogRelMsgReturn(pRam->pvR3,
+                AssertLogRelMsgReturn(pRam->pbR3,
                                       ("GCPhys=%#x %s\n", pRam->GCPhys, pRam->pszDesc),
                                       VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
 
-                rc = SSMR3GetMem(pSSM, pRam->pvR3, pRam->cb);
+                rc = SSMR3GetMem(pSSM, pRam->pbR3, pRam->cb);
                 AssertLogRelMsgRCReturn(rc, ("GCPhys=%#x %s\n", pRam->GCPhys, pRam->pszDesc), rc);
             }
@@ -2584,4 +2665,6 @@
             }
         }
+
+        iSeqNo++;
     }
 
@@ -2610,10 +2693,11 @@
      * Process page records until we hit the terminator.
      */
-    RTGCPHYS            GCPhys   = NIL_RTGCPHYS;
-    PPGMRAMRANGE        pRamHint = NULL;
-    uint8_t             id       = UINT8_MAX;
-    uint32_t            iPage    = UINT32_MAX - 10;
-    PPGMROMRANGE        pRom     = NULL;
-    PPGMREGMMIO2RANGE    pRegMmio = NULL;
+    RTGCPHYS            GCPhys         = NIL_RTGCPHYS;
+    PPGMRAMRANGE        pRamHint       = NULL;
+    uint8_t             id             = UINT8_MAX;
+    uint32_t            iPage          = UINT32_MAX - 10;
+    PPGMROMRANGE        pRom           = NULL;
+    PPGMREGMMIO2RANGE   pRegMmio2      = NULL;
+    PPGMRAMRANGE        pMmio2RamRange = NULL;
 
     /*
@@ -2675,5 +2759,17 @@
                 PPGMPAGE pPage;
                 rc = pgmPhysGetPageWithHintEx(pVM, GCPhys, &pPage, &pRamHint);
-                AssertLogRelMsgRCReturn(rc, ("rc=%Rrc %RGp\n", rc, GCPhys), rc);
+                if (RT_SUCCESS(rc))
+                { /* likely */ }
+                else if (   rc == VERR_PGM_INVALID_GC_PHYSICAL_ADDRESS
+                         && GCPhys < _1M
+                         && GCPhys >= 640U*_1K
+                         && (u8 & ~PGM_STATE_REC_FLAG_ADDR) == PGM_STATE_REC_RAM_ZERO)
+                {
+                    rc = VINF_SUCCESS; /* We've kicked out unused pages between 640K and 1MB, but older states may include them. */
+                    id = UINT8_MAX;
+                    break;
+                }
+                else
+                    AssertLogRelMsgFailedReturn(("rc=%Rrc %RGp u8=%#x\n", rc, GCPhys, u8), rc);
 
                 /*
@@ -2705,7 +2801,5 @@
                         if (   PGM_PAGE_GET_TYPE(pPage) == PGMPAGETYPE_ROM
                             || PGM_PAGE_GET_TYPE(pPage) == PGMPAGETYPE_ROM_SHADOW
-#ifdef VBOX_WITH_PGM_NEM_MODE
-                            || pVM->pgm.s.fNemMode
-#endif
+                            || PGM_IS_IN_NEM_MODE(pVM)
                             || pVM->pgm.s.fRamPreAlloc)
                         {
@@ -2792,16 +2886,23 @@
                         return rc;
                 }
-                if (   !pRegMmio
-                    || pRegMmio->idSavedState != id)
+                if (   !pRegMmio2
+                    || pRegMmio2->idSavedState != id)
                 {
-                    for (pRegMmio = pVM->pgm.s.pRegMmioRangesR3; pRegMmio; pRegMmio = pRegMmio->pNextR3)
-                        if (pRegMmio->idSavedState == id)
+                    pMmio2RamRange = NULL;
+                    uint32_t const cMmio2Ranges = RT_MIN(pVM->pgm.s.cMmio2Ranges, RT_ELEMENTS(pVM->pgm.s.aMmio2Ranges));
+                    for (uint32_t idx = 0; idx < cMmio2Ranges; idx++)
+                        if (pVM->pgm.s.aMmio2Ranges[idx].idSavedState == id)
+                        {
+                            pRegMmio2 = &pVM->pgm.s.aMmio2Ranges[idx];
+                            pMmio2RamRange = pVM->pgm.s.apMmio2RamRanges[idx];
                             break;
-                    AssertLogRelMsgReturn(pRegMmio, ("id=%#u iPage=%#x\n", id, iPage), VERR_PGM_SAVED_MMIO2_RANGE_NOT_FOUND);
+                        }
+                    AssertLogRelMsgReturn(pRegMmio2 && pMmio2RamRange, ("id=%#u iPage=%#x\n", id, iPage),
+                                          VERR_PGM_SAVED_MMIO2_RANGE_NOT_FOUND);
                 }
-                AssertLogRelMsgReturn(iPage < (pRegMmio->RamRange.cb >> GUEST_PAGE_SHIFT),
-                                      ("iPage=%#x cb=%RGp %s\n", iPage, pRegMmio->RamRange.cb, pRegMmio->RamRange.pszDesc),
+                AssertLogRelMsgReturn(iPage < (pMmio2RamRange->cb >> GUEST_PAGE_SHIFT),
+                                      ("iPage=%#x cb=%RGp %s\n", iPage, pMmio2RamRange->cb, pMmio2RamRange->pszDesc),
                                       VERR_PGM_SAVED_MMIO2_PAGE_NOT_FOUND);
-                void *pvDstPage = (uint8_t *)pRegMmio->RamRange.pvR3 + ((size_t)iPage << GUEST_PAGE_SHIFT);
+                void * const pvDstPage = &pMmio2RamRange->pbR3[(size_t)iPage << GUEST_PAGE_SHIFT];
 
                 /*
@@ -2840,11 +2941,16 @@
                         return rc;
                 }
-                if (    !pRom
-                    ||  pRom->idSavedState != id)
+                if (   !pRom
+                    || pRom->idSavedState != id)
                 {
-                    for (pRom = pVM->pgm.s.pRomRangesR3; pRom; pRom = pRom->pNextR3)
+                    uint32_t const cRomRanges = RT_MIN(pVM->pgm.s.cRomRanges, RT_ELEMENTS(pVM->pgm.s.apRomRanges));
+                    uint32_t       idx;
+                    for (idx = 0; idx < cRomRanges; idx++)
+                    {
+                        pRom = pVM->pgm.s.apRomRanges[idx];
                         if (pRom->idSavedState == id)
                             break;
-                    AssertLogRelMsgReturn(pRom, ("id=%#u iPage=%#x\n", id, iPage), VERR_PGM_SAVED_ROM_RANGE_NOT_FOUND);
+                    }
+                    AssertLogRelMsgReturn(idx < cRomRanges, ("id=%#u iPage=%#x\n", id, iPage), VERR_PGM_SAVED_ROM_RANGE_NOT_FOUND);
                 }
                 AssertLogRelMsgReturn(iPage < (pRom->cb >> GUEST_PAGE_SHIFT),
@@ -2928,5 +3034,5 @@
                     case PGM_STATE_REC_ROM_SHW_RAW:
 #ifdef VBOX_WITH_PGM_NEM_MODE
-                        if (fAltPage && pVM->pgm.s.fNemMode)
+                        if (fAltPage && PGM_IS_IN_NEM_MODE(pVM))
                             pvDstPage = &pRom->pbR3Alternate[iPage << GUEST_PAGE_SHIFT];
                         else
@@ -2999,8 +3105,8 @@
         for (VMCPUID i = 0; i < pVM->cCpus; i++)
         {
-            if (uVersion <= PGM_SAVED_STATE_VERSION_PRE_PAE)
+            if (uVersion > PGM_SAVED_STATE_VERSION_PRE_PAE)
+                rc = SSMR3GetStruct(pSSM, &pVM->apCpusR3[i]->pgm.s, &s_aPGMCpuFields[0]);
+            else
                 rc = SSMR3GetStruct(pSSM, &pVM->apCpusR3[i]->pgm.s, &s_aPGMCpuFieldsPrePae[0]);
-            else
-                rc = SSMR3GetStruct(pSSM, &pVM->apCpusR3[i]->pgm.s, &s_aPGMCpuFields[0]);
             AssertLogRelRCReturn(rc, rc);
         }
Index: trunk/src/VBox/VMM/VMMR3/PGMSharedPage.cpp
===================================================================
--- trunk/src/VBox/VMM/VMMR3/PGMSharedPage.cpp	(revision 104839)
+++ trunk/src/VBox/VMM/VMMR3/PGMSharedPage.cpp	(revision 104840)
@@ -356,6 +356,12 @@
     PGM_LOCK_VOID(pVM);
 
-    for (PPGMRAMRANGE pRam = pVM->pgm.s.pRamRangesXR3; pRam; pRam = pRam->pNextR3)
-    {
+    uint32_t const idRamRangeMax = RT_MIN(pVM->pgm.s.idRamRangeMax, RT_ELEMENTS(pVM->pgm.s.apRamRanges) - 1U);
+    for (uint32_t idRamRange = 0; idRamRange <= idRamRangeMax; idRamRange++)
+    {
+        PPGMRAMRANGE const pRam = pVM->pgm.s.apRamRanges[idRamRange];
+        Assert(pRam || idRamRange == 0);
+        if (!pRam) continue;
+        Assert(pRam->idRange == idRamRange);
+
         PPGMPAGE    pPage  = &pRam->aPages[0];
         RTGCPHYS    GCPhys = pRam->GCPhys;
@@ -413,4 +419,5 @@
         }
     }
+
     PGM_UNLOCK(pVM);
 
Index: trunk/src/VBox/VMM/include/IOMInternal.h
===================================================================
--- trunk/src/VBox/VMM/include/IOMInternal.h	(revision 104839)
+++ trunk/src/VBox/VMM/include/IOMInternal.h	(revision 104840)
@@ -275,8 +275,10 @@
     bool volatile                       fMapped;
     /** Set if there is an ring-0 entry too. */
-    bool                                fRing0;
+    bool                                fRing0 : 1;
     /** Set if there is an raw-mode entry too. */
-    bool                                fRawMode;
-    uint8_t                             bPadding;
+    bool                                fRawMode : 1;
+    bool                                fPadding : 6;
+    /** Pre-registered ad-hoc RAM range ID. */
+    uint16_t                            idRamRange;
     /** Same as the handle index. */
     uint16_t                            idxSelf;
Index: trunk/src/VBox/VMM/include/PGMInternal.h
===================================================================
--- trunk/src/VBox/VMM/include/PGMInternal.h	(revision 104839)
+++ trunk/src/VBox/VMM/include/PGMInternal.h	(revision 104840)
@@ -114,4 +114,41 @@
 # define PGM_SYNC_NR_PAGES              8
 #endif
+
+/** Maximum number of RAM ranges.
+ * @note This can be increased to 4096 (at least when targeting x86). */
+#define PGM_MAX_RAM_RANGES              3072
+
+/** Maximum pages per RAM range.
+ *
+ * The PGMRAMRANGE structures for the high memory can get very big. There
+ * used to be some limitations on SUPR3PageAllocEx allocation sizes, so
+ * traditionally we limited this to 16MB chunks.  These days we do ~64 MB
+ * chunks each covering 16GB of guest RAM, making sure each range is a
+ * multiple of 1GB to enable eager hosts to use 1GB pages for NEM mode.
+ *
+ * See also pgmPhysMmio2CalcChunkCount.
+ */
+#define PGM_MAX_PAGES_PER_RAM_RANGE     _4M
+#if defined(X86_PD_PAE_SHIFT) && defined(AssertCompile)
+AssertCompile(RT_ALIGN_32(PGM_MAX_PAGES_PER_RAM_RANGE, X86_PD_PAE_SHIFT - X86_PAGE_SHIFT)); /* NEM large page requirement: 1GB pages. */
+#endif
+
+/** The maximum number of MMIO2 ranges. */
+#define PGM_MAX_MMIO2_RANGES            32
+/** The maximum number of pages in a MMIO2 PCI region.
+ *
+ * The memory for a MMIO2 PCI region is a single chunk of host virtual memory,
+ * but may be handled internally by PGM as a set of multiple MMIO2/RAM ranges,
+ * since PGM_MAX_PAGES_PER_RAM_RANGE is currently lower than this value (4 GiB
+ * vs 16 GiB).
+ */
+#define PGM_MAX_PAGES_PER_MMIO2_REGION  _16M
+
+/** Maximum number of ROM ranges. */
+#define PGM_MAX_ROM_RANGES              16
+/** The maximum pages per ROM range.
+ * Currently 512K pages, or 2GB with 4K pages.  */
+#define PGM_MAX_PAGES_PER_ROM_RANGE     _512K
+AssertCompile(PGM_MAX_PAGES_PER_ROM_RANGE <= PGM_MAX_PAGES_PER_RAM_RANGE);
 
 /**
@@ -1289,10 +1326,34 @@
 
 /**
- * RAM range for GC Phys to HC Phys conversion.
- *
- * Can be used for HC Virt to GC Phys and HC Virt to HC Phys
- * conversions too, but we'll let MM handle that for now.
- *
- * This structure is used by linked lists in both GC and HC.
+ * RAM range lookup table entry.
+ */
+typedef union PGMRAMRANGELOOKUPENTRY
+{
+    RT_GCC_EXTENSION struct
+    {
+        /** Page aligned start address of the range, with page offset holding the ID. */
+        RTGCPHYS                        GCPhysFirstAndId;
+        /** The last address in the range (inclusive). Page aligned (-1). */
+        RTGCPHYS                        GCPhysLast;
+    };
+    /** Alternative 128-bit view for atomic updating. */
+    RTUINT128U volatile                 u128Volatile;
+    /** Alternative 128-bit view for atomic updating. */
+    RTUINT128U                          u128Normal;
+} PGMRAMRANGELOOKUPENTRY;
+/** Pointer to a lookup table entry. */
+typedef PGMRAMRANGELOOKUPENTRY *PPGMRAMRANGELOOKUPENTRY;
+
+/** Extracts the ID from  PGMRAMRANGELOOKUPENTRY::GCPhysFirstAndId. */
+#define PGMRAMRANGELOOKUPENTRY_GET_ID(a_LookupEntry)    ((uint32_t)((a_LookupEntry).GCPhysFirstAndId & GUEST_PAGE_OFFSET_MASK))
+/** Extracts the GCPhysFirst from PGMRAMRANGELOOKUPENTRY::GCPhysFirstAndId. */
+#define PGMRAMRANGELOOKUPENTRY_GET_FIRST(a_LookupEntry) (((a_LookupEntry).GCPhysFirstAndId) & ~(RTGCPHYS)GUEST_PAGE_OFFSET_MASK)
+
+
+/**
+ * RAM range for GC Phys to HC Phys & R3 Ptr conversion.
+ *
+ * This structure is addressed via context specific pointer tables.  Lookup is
+ * organized via the lookup table (PGMRAMRANGELOOKUPENTRY).
  */
 typedef struct PGMRAMRANGE
@@ -1300,10 +1361,8 @@
     /** Start of the range. Page aligned. */
     RTGCPHYS                            GCPhys;
-    /** Size of the range. (Page aligned of course). */
+    /** Size of the range. (Page aligned of course).
+     * Ring-0 duplicates this in a PGMR0PERVM::acRamRangePages (shifted by
+     * guest page size). */
     RTGCPHYS                            cb;
-    /** Pointer to the next RAM range - for R3. */
-    R3PTRTYPE(struct PGMRAMRANGE *)     pNextR3;
-    /** Pointer to the next RAM range - for R0. */
-    R0PTRTYPE(struct PGMRAMRANGE *)     pNextR0;
     /** PGM_RAM_RANGE_FLAGS_* flags. */
     uint32_t                            fFlags;
@@ -1313,29 +1372,21 @@
     RTGCPHYS                            GCPhysLast;
     /** Start of the HC mapping of the range. This is only used for MMIO2 and in NEM mode. */
-    R3PTRTYPE(void *)                   pvR3;
+    R3PTRTYPE(uint8_t *)                pbR3;
+    /** The RAM range identifier (index into the pointer table). */
+    uint32_t                            idRange;
+#if HC_ARCH_BITS != 32
+    /** Padding to make aPage aligned on sizeof(PGMPAGE). */
+    uint32_t                            au32Alignment2[HC_ARCH_BITS == 32 ? 0 : 1];
+#endif
     /** Live save per page tracking data. */
     R3PTRTYPE(PPGMLIVESAVERAMPAGE)      paLSPages;
     /** The range description. */
     R3PTRTYPE(const char *)             pszDesc;
-    /** Pointer to self - R0 pointer. */
-    R0PTRTYPE(struct PGMRAMRANGE *)     pSelfR0;
-
-    /** Pointer to the left search three node - ring-3 context. */
-    R3PTRTYPE(struct PGMRAMRANGE *)     pLeftR3;
-    /** Pointer to the right search three node - ring-3 context. */
-    R3PTRTYPE(struct PGMRAMRANGE *)     pRightR3;
-    /** Pointer to the left search three node - ring-0 context. */
-    R0PTRTYPE(struct PGMRAMRANGE *)     pLeftR0;
-    /** Pointer to the right search three node - ring-0 context. */
-    R0PTRTYPE(struct PGMRAMRANGE *)     pRightR0;
-
-    /** Padding to make aPage aligned on sizeof(PGMPAGE). */
-#if HC_ARCH_BITS == 32
-    uint32_t                            au32Alignment2[HC_ARCH_BITS == 32 ? 2 : 0];
-#endif
+
     /** Array of physical guest page tracking structures.
      * @note Number of entries is PGMRAMRANGE::cb / GUEST_PAGE_SIZE. */
     PGMPAGE                             aPages[1];
 } PGMRAMRANGE;
+AssertCompileMemberAlignment(PGMRAMRANGE, aPages, 16);
 /** Pointer to RAM range for GC Phys to HC Phys conversion. */
 typedef PGMRAMRANGE *PPGMRAMRANGE;
@@ -1343,6 +1394,4 @@
 /** @name PGMRAMRANGE::fFlags
  * @{ */
-/** The RAM range is floating around as an independent guest mapping. */
-#define PGM_RAM_RANGE_FLAGS_FLOATING        RT_BIT(20)
 /** Ad hoc RAM range for an ROM mapping. */
 #define PGM_RAM_RANGE_FLAGS_AD_HOC_ROM      RT_BIT(21)
@@ -1351,4 +1400,6 @@
 /** Ad hoc RAM range for an MMIO2 or pre-registered MMIO mapping. */
 #define PGM_RAM_RANGE_FLAGS_AD_HOC_MMIO_EX  RT_BIT(23)
+/** Valid RAM range flags.  */
+#define PGM_RAM_RANGE_FLAGS_VALID_MASK      (PGM_RAM_RANGE_FLAGS_AD_HOC_ROM | PGM_RAM_RANGE_FLAGS_AD_HOC_MMIO | PGM_RAM_RANGE_FLAGS_AD_HOC_MMIO_EX)
 /** @} */
 
@@ -1377,5 +1428,5 @@
  */
 #define PGM_RAMRANGE_CALC_PAGE_R3PTR(a_pRam, a_GCPhysPage) \
-    ( (a_pRam)->pvR3 ? (R3PTRTYPE(uint8_t *))(a_pRam)->pvR3 + (a_GCPhysPage) - (a_pRam)->GCPhys : NULL )
+    ( (a_pRam)->pbR3 ? (a_pRam)->pbR3 + (a_GCPhysPage) - (a_pRam)->GCPhys : NULL )
 
 
@@ -1427,10 +1478,4 @@
 typedef struct PGMROMRANGE
 {
-    /** Pointer to the next range - R3. */
-    R3PTRTYPE(struct PGMROMRANGE *)     pNextR3;
-    /** Pointer to the next range - R0. */
-    R0PTRTYPE(struct PGMROMRANGE *)     pNextR0;
-    /** Pointer to the this range - R0. */
-    R0PTRTYPE(struct PGMROMRANGE *)     pSelfR0;
     /** Address of the range. */
     RTGCPHYS                            GCPhys;
@@ -1443,6 +1488,9 @@
     /** The saved state range ID. */
     uint8_t                             idSavedState;
-    /** Alignment padding. */
-    uint8_t                             au8Alignment[2];
+    /** The ID of the associated RAM range. */
+#ifdef IN_RING0
+    volatile
+#endif
+    uint16_t                            idRamRange;
     /** The size bits pvOriginal points to. */
     uint32_t                            cbOriginal;
@@ -1460,4 +1508,6 @@
     R3PTRTYPE(uint8_t *)                pbR3Alternate;
     RTR3PTR                             pvAlignment2;
+#else
+    RTR3PTR                             apvUnused[2];
 #endif
     /** The per page tracking structures. */
@@ -1524,14 +1574,8 @@
 typedef struct PGMREGMMIO2RANGE
 {
-    /** The owner of the range. (a device) */
+    /** The owner of the range (a device). */
     PPDMDEVINSR3                        pDevInsR3;
     /** Pointer to the ring-3 mapping of the allocation. */
-    RTR3PTR                             pvR3;
-#ifndef VBOX_WITH_LINEAR_HOST_PHYS_MEM
-    /** Pointer to the ring-0 mapping of the allocation. */
-    RTR0PTR                             pvR0;
-#endif
-    /** Pointer to the next range - R3. */
-    R3PTRTYPE(struct PGMREGMMIO2RANGE *) pNextR3;
+    R3PTRTYPE(uint8_t *)                pbR3;
     /** Flags (PGMREGMMIO2RANGE_F_XXX). */
     uint16_t                            fFlags;
@@ -1544,10 +1588,11 @@
     /** MMIO2 range identifier, for page IDs (PGMPAGE::s.idPage). */
     uint8_t                             idMmio2;
-    /** Alignment padding for putting the ram range on a PGMPAGE alignment boundary. */
-#ifndef VBOX_WITH_LINEAR_HOST_PHYS_MEM
-    uint8_t                             abAlignment[HC_ARCH_BITS == 32 ? 6 + 4 : 2];
-#else
-    uint8_t                             abAlignment[HC_ARCH_BITS == 32 ? 6 + 8 : 2 + 8];
-#endif
+    /** The ID of the associated RAM range. */
+#ifdef IN_RING0
+    volatile
+#endif
+    uint16_t                            idRamRange;
+    /** The mapping address if mapped, NIL_RTGCPHYS if not.  */
+    RTGCPHYS                            GCPhys;
     /** The real size.
      * This may be larger than indicated by RamRange.cb if the range has been
@@ -1560,8 +1605,6 @@
     /** Live save per page tracking data for MMIO2. */
     R3PTRTYPE(PPGMLIVESAVEMMIO2PAGE)    paLSPages;
-    /** The associated RAM range. */
-    PGMRAMRANGE                         RamRange;
+    RTR3PTR                             R3PtrPadding;
 } PGMREGMMIO2RANGE;
-AssertCompileMemberAlignment(PGMREGMMIO2RANGE, RamRange, 16);
 /** Pointer to a MMIO2 or pre-registered MMIO range. */
 typedef PGMREGMMIO2RANGE *PPGMREGMMIO2RANGE;
@@ -1588,10 +1631,6 @@
 
 
-/** @name Internal MMIO2 constants.
+/** @name Internal MMIO2 macros.
  * @{ */
-/** The maximum number of MMIO2 ranges. */
-#define PGM_MMIO2_MAX_RANGES                        32
-/** The maximum number of pages in a MMIO2 range. */
-#define PGM_MMIO2_MAX_PAGE_COUNT                    UINT32_C(0x01000000)
 /** Makes a MMIO2 page ID out of a MMIO2 range ID and page index number. */
 #define PGM_MMIO2_PAGEID_MAKE(a_idMmio2, a_iPage)   ( ((uint32_t)(a_idMmio2) << 24) | (uint32_t)(a_iPage) )
@@ -2948,4 +2987,41 @@
     uint8_t                         abMmioPg[RT_MAX(HOST_PAGE_SIZE, GUEST_PAGE_SIZE)];
 
+    /** @name   RAM, MMIO2 and ROM ranges
+     * @{ */
+    /** The RAM range lookup table. */
+    PGMRAMRANGELOOKUPENTRY          aRamRangeLookup[PGM_MAX_RAM_RANGES];
+    /** The ring-3 RAM range pointer table. */
+    R3PTRTYPE(PPGMRAMRANGE)         apRamRanges[PGM_MAX_RAM_RANGES];
+    /** MMIO2 ranges.  Indexed by idMmio2 minus 1. */
+    PGMREGMMIO2RANGE                aMmio2Ranges[PGM_MAX_MMIO2_RANGES];
+    /** The ring-3 RAM range pointer table running parallel to aMmio2Ranges. */
+    R3PTRTYPE(PPGMRAMRANGE)         apMmio2RamRanges[PGM_MAX_MMIO2_RANGES];
+    /** The ring-3 ROM range pointer table. */
+    R3PTRTYPE(PPGMROMRANGE)         apRomRanges[PGM_MAX_ROM_RANGES];
+    /** Union of generation ID and lookup count. */
+    union PGMRAMRANGEGENANDLOOKUPCOUNT
+    {
+        /* Combined view of both the generation ID and the count for atomic updating/reading. */
+        uint64_t volatile           u64Combined;
+        RT_GCC_EXTENSION struct
+        {
+            /** Generation ID for the RAM ranges.
+             * This member is incremented twice everytime a RAM range is mapped or
+             * unmapped, so odd numbers means aRamRangeLookup is being modified and even
+             * means the update has completed. */
+            uint32_t volatile       idGeneration;
+            /** The number of active entries in aRamRangeLookup. */
+            uint32_t volatile       cLookupEntries;
+        };
+    } RamRangeUnion;
+    /** The max RAM range ID (mirroring PGMR0PERVM::idRamRangeMax). */
+    uint32_t                        idRamRangeMax;
+    /** The number of MMIO2 ranges (serves as the next MMIO2 ID). */
+    uint8_t                         cMmio2Ranges;
+    /** The number of ROM ranges. */
+    uint8_t                         cRomRanges;
+    uint8_t                         abAlignment1[2];
+    /** @}  */
+
     /** @name   The zero page (abPagePg).
      * @{ */
@@ -3005,6 +3081,4 @@
      * Whether PCI passthrough is enabled. */
     bool                            fPciPassthrough;
-    /** The number of MMIO2 regions (serves as the next MMIO2 ID). */
-    uint8_t                         cMmio2Regions;
     /** Restore original ROM page content when resetting after loading state.
      * The flag is set by pgmR3LoadRomRanges and cleared at reset.  This
@@ -3018,13 +3092,10 @@
     /** Alignment padding. */
 #ifndef VBOX_WITH_PGM_NEM_MODE
-    bool                            afAlignment3[1];
+    bool                            afAlignment2[2];
+#else
+    bool                            afAlignment2[1];
 #endif
     /** The host paging mode. (This is what SUPLib reports.) */
     SUPPAGINGMODE                   enmHostMode;
-    bool                            afAlignment3b[2];
-
-    /** Generation ID for the RAM ranges. This member is incremented everytime
-     * a RAM range is linked or unlinked. */
-    uint32_t volatile               idRamRangesGen;
 
     /** Physical access handler type for ROM protection. */
@@ -3042,9 +3113,4 @@
     /** RAM range TLB for R3. */
     R3PTRTYPE(PPGMRAMRANGE)         apRamRangesTlbR3[PGM_RAMRANGE_TLB_ENTRIES];
-    /** Pointer to the list of RAM ranges (Phys GC -> Phys HC conversion) - for R3.
-     * This is sorted by physical address and contains no overlapping ranges. */
-    R3PTRTYPE(PPGMRAMRANGE)         pRamRangesXR3;
-    /** Root of the RAM range search tree for ring-3. */
-    R3PTRTYPE(PPGMRAMRANGE)         pRamRangeTreeR3;
     /** Shadow Page Pool - R3 Ptr. */
     R3PTRTYPE(PPGMPOOL)             pPoolR3;
@@ -3052,22 +3118,11 @@
      * This is sorted by physical address and contains no overlapping ranges. */
     R3PTRTYPE(PPGMROMRANGE)         pRomRangesR3;
-    /** Pointer to the list of MMIO2 ranges - for R3.
-     * Registration order. */
-    R3PTRTYPE(PPGMREGMMIO2RANGE)    pRegMmioRangesR3;
-    /** MMIO2 lookup array for ring-3.  Indexed by idMmio2 minus 1. */
-    R3PTRTYPE(PPGMREGMMIO2RANGE)    apMmio2RangesR3[PGM_MMIO2_MAX_RANGES];
 
     /** RAM range TLB for R0. */
     R0PTRTYPE(PPGMRAMRANGE)         apRamRangesTlbR0[PGM_RAMRANGE_TLB_ENTRIES];
-    /** R0 pointer corresponding to PGM::pRamRangesXR3. */
-    R0PTRTYPE(PPGMRAMRANGE)         pRamRangesXR0;
-    /** Root of the RAM range search tree for ring-0. */
-    R0PTRTYPE(PPGMRAMRANGE)         pRamRangeTreeR0;
     /** Shadow Page Pool - R0 Ptr. */
     R0PTRTYPE(PPGMPOOL)             pPoolR0;
     /** R0 pointer corresponding to PGM::pRomRangesR3. */
     R0PTRTYPE(PPGMROMRANGE)         pRomRangesR0;
-    /** MMIO2 lookup array for ring-0.  Indexed by idMmio2 minus 1. */
-    R0PTRTYPE(PPGMREGMMIO2RANGE)    apMmio2RangesR0[PGM_MMIO2_MAX_RANGES];
 
     /** Hack: Number of deprecated page mapping locks taken by the current lock
@@ -3158,5 +3213,5 @@
     /** Number of repeated long allocation times.   */
     uint32_t                        cLargePageLongAllocRepeats;
-    uint32_t                        uPadding5;
+    uint32_t                        uPadding4;
 
     /**
@@ -3254,6 +3309,11 @@
 #ifndef IN_TSTVMSTRUCTGC /* HACK */
 AssertCompileMemberAlignment(PGM, CritSectX, 8);
+AssertCompileMemberAlignment(PGM, CritSectX, 16);
+AssertCompileMemberAlignment(PGM, CritSectX, 32);
+AssertCompileMemberAlignment(PGM, CritSectX, 64);
 AssertCompileMemberAlignment(PGM, ChunkR3Map, 16);
-AssertCompileMemberAlignment(PGM, PhysTlbR3, 32); /** @todo 32 byte alignment! */
+AssertCompileMemberAlignment(PGM, PhysTlbR3, 8);
+AssertCompileMemberAlignment(PGM, PhysTlbR3, 16);
+AssertCompileMemberAlignment(PGM, PhysTlbR3, 32);
 AssertCompileMemberAlignment(PGM, PhysTlbR0, 32);
 AssertCompileMemberAlignment(PGM, HCPhysZeroPg, 8);
@@ -3688,4 +3748,55 @@
 typedef struct PGMR0PERVM
 {
+    /** @name RAM ranges
+     * @{  */
+    /** The ring-0 RAM range pointer table. */
+    R0PTRTYPE(PPGMRAMRANGE)         apRamRanges[PGM_MAX_RAM_RANGES];
+    /** Trusted RAM range page counts running parallel to apRamRanges.
+     * This keeps the original page count when a range is reduced,
+     * only the PGMRAMRANGE::cb member is changed then. */
+    uint32_t                        acRamRangePages[PGM_MAX_RAM_RANGES];
+    /** The memory objects for the RAM ranges (parallel to apRamRanges). */
+    RTR0MEMOBJ                      ahRamRangeMemObjs[PGM_MAX_RAM_RANGES];
+    /** The ring-3 mapping objects for the RAM ranges (parallel to apRamRanges). */
+    RTR0MEMOBJ                      ahRamRangeMapObjs[PGM_MAX_RAM_RANGES];
+    /** The max RAM range ID (safe).   */
+    uint32_t                        idRamRangeMax;
+    uint8_t                         abAlignment1[64 - sizeof(uint32_t)];
+    /** @} */
+
+    /** @name MMIO2 ranges
+     *  @{ */
+    /** The ring-0 RAM range pointer table running parallel to aMmio2Ranges. */
+    R0PTRTYPE(PPGMRAMRANGE)         apMmio2RamRanges[PGM_MAX_MMIO2_RANGES];
+    /** The memory objects for the MMIO2 backing memory (parallel to
+     *  apMmio2RamRanges). */
+    RTR0MEMOBJ                      ahMmio2MemObjs[PGM_MAX_MMIO2_RANGES];
+    /** The ring-3 mapping objects for the MMIO2 backing memory (parallel
+     *  to apMmio2RamRanges & ahMmio2MemObjs). */
+    RTR0MEMOBJ                      ahMmio2MapObjs[PGM_MAX_MMIO2_RANGES];
+    /** Trusted MMIO2 range sizes (count of guest pages).
+     * This keeps the original page count when a range is reduced,
+     * only the PGMRAMRANGE::cb member is changed then. */
+    uint32_t                        acMmio2RangePages[PGM_MAX_MMIO2_RANGES];
+#ifndef VBOX_WITH_LINEAR_HOST_PHYS_MEM
+    /** Pointer to the ring-0 mapping of the MMIO2 backings (parallel to
+     *  apMmio2RamRanges). */
+    R0PTRTYPE(uint8_t *)            apbMmio2Backing[PGM_MAX_MMIO2_RANGES];
+#endif
+    /** @} */
+
+    /** @name ROM ranges
+     *  @{ */
+    /** The ring-0 ROM range pointer table. */
+    R0PTRTYPE(PPGMROMRANGE)         apRomRanges[PGM_MAX_ROM_RANGES];
+    /** The memory objects for each ROM range (parallel to apRomRanges). */
+    RTR0MEMOBJ                      ahRomRangeMemObjs[PGM_MAX_ROM_RANGES];
+    /** The ring-3 mapping objects for each ROM range (parallel to apRomRanges
+     *  & ahRamRangeMemObjs). */
+    RTR0MEMOBJ                      ahRomRangeMapObjs[PGM_MAX_ROM_RANGES];
+    /** Trusted ROM range sizes (count of guest pages). */
+    uint32_t                        acRomRangePages[PGM_MAX_ROM_RANGES];
+    /** @} */
+
     /** @name PGM Pool related stuff.
      * @{ */
@@ -3788,14 +3899,24 @@
 DECLCALLBACK(FNPGMRZPHYSPFHANDLER)  pgmPhysMmio2WritePfHandler;
 #endif
+DECLHIDDEN(uint16_t) pgmPhysMmio2CalcChunkCount(RTGCPHYS cb, uint32_t *pcPagesPerChunk);
+DECLHIDDEN(int) pgmPhysMmio2RegisterWorker(PVMCC pVM, uint32_t const cGuestPages, uint8_t const idMmio2,
+                                           const uint8_t cChunks, PPDMDEVINSR3 const pDevIns, uint8_t
+                                           const iSubDev, uint8_t const iRegion, uint32_t const fFlags);
+DECLHIDDEN(int) pgmPhysMmio2DeregisterWorker(PVMCC pVM, uint8_t idMmio2, uint8_t cChunks, PPDMDEVINSR3 pDevIns);
 int             pgmPhysFreePage(PVM pVM, PGMMFREEPAGESREQ pReq, uint32_t *pcPendingPages, PPGMPAGE pPage, RTGCPHYS GCPhys,
                                 PGMPAGETYPE enmNewType);
+#ifdef VBOX_STRICT
+DECLHIDDEN(bool) pgmPhysAssertRamRangesLocked(PVMCC pVM, bool fInUpdate, bool fRamRelaxed);
+#endif
 void            pgmPhysInvalidRamRangeTlbs(PVMCC pVM);
 void            pgmPhysInvalidatePageMapTLB(PVMCC pVM);
 void            pgmPhysInvalidatePageMapTLBEntry(PVMCC pVM, RTGCPHYS GCPhys);
-PPGMRAMRANGE    pgmPhysGetRangeSlow(PVM pVM, RTGCPHYS GCPhys);
-PPGMRAMRANGE    pgmPhysGetRangeAtOrAboveSlow(PVM pVM, RTGCPHYS GCPhys);
-PPGMPAGE        pgmPhysGetPageSlow(PVM pVM, RTGCPHYS GCPhys);
-int             pgmPhysGetPageExSlow(PVM pVM, RTGCPHYS GCPhys, PPPGMPAGE ppPage);
-int             pgmPhysGetPageAndRangeExSlow(PVM pVM, RTGCPHYS GCPhys, PPPGMPAGE ppPage, PPGMRAMRANGE *ppRam);
+PPGMRAMRANGE    pgmPhysGetRangeSlow(PVMCC pVM, RTGCPHYS GCPhys);
+PPGMRAMRANGE    pgmPhysGetRangeAtOrAboveSlow(PVMCC pVM, RTGCPHYS GCPhys);
+PPGMPAGE        pgmPhysGetPageSlow(PVMCC pVM, RTGCPHYS GCPhys);
+int             pgmPhysGetPageExSlow(PVMCC pVM, RTGCPHYS GCPhys, PPPGMPAGE ppPage);
+int             pgmPhysGetPageAndRangeExSlow(PVMCC pVM, RTGCPHYS GCPhys, PPPGMPAGE ppPage, PPGMRAMRANGE *ppRam);
+DECLHIDDEN(int) pgmPhysRamRangeAllocCommon(PVMCC pVM, uint32_t cPages, uint32_t fFlags, uint32_t *pidNewRange);
+DECLHIDDEN(int) pgmPhysRomRangeAllocCommon(PVMCC pVM, uint32_t cPages, uint8_t idRomRange, uint32_t fFlags);
 #ifdef VBOX_WITH_NATIVE_NEM
 void            pgmPhysSetNemStateForPages(PPGMPAGE paPages, RTGCPHYS cPages, uint8_t u2State);
@@ -3803,5 +3924,4 @@
 
 #ifdef IN_RING3
-void            pgmR3PhysRelinkRamRanges(PVM pVM);
 int             pgmR3PhysRamPreAllocate(PVM pVM);
 int             pgmR3PhysRamReset(PVM pVM);
Index: trunk/src/VBox/VMM/testcase/tstVMStructSize.cpp
===================================================================
--- trunk/src/VBox/VMM/testcase/tstVMStructSize.cpp	(revision 104839)
+++ trunk/src/VBox/VMM/testcase/tstVMStructSize.cpp	(revision 104840)
@@ -325,5 +325,6 @@
     CHECK_SIZE(PGMPAGE, 16);
     CHECK_MEMBER_ALIGNMENT(PGMRAMRANGE, aPages, 16);
-    CHECK_MEMBER_ALIGNMENT(PGMREGMMIO2RANGE, RamRange, 16);
+    CHECK_SIZE_ALIGNMENT(PGMREGMMIO2RANGE, 16);
+    CHECK_MEMBER_ALIGNMENT(PGMROMRANGE, aPages, 16);
 
     /* TM */
