Index: /trunk/src/VBox/VMM/PGMSavedState.cpp
===================================================================
--- /trunk/src/VBox/VMM/PGMSavedState.cpp	(revision 23523)
+++ /trunk/src/VBox/VMM/PGMSavedState.cpp	(revision 23524)
@@ -175,68 +175,11 @@
 
 /**
- * Prepare for a live save operation.
- *
- * This will attempt to allocate and initialize the tracking structures.  It
- * will also prepare for write monitoring of pages and initialize PGM::LiveSave.
- * pgmR3SaveDone will do the cleanups.
+ * Prepares the ROM pages for a live save.
  *
  * @returns VBox status code.
- *
- * @param   pVM         The VM handle.
- * @param   pSSM        The SSM handle.
- */
-static DECLCALLBACK(int) pgmR3LivePrep(PVM pVM, PSSMHANDLE pSSM)
-{
-    /*
-     * Indicate that we will be using the write monitoring.
-     */
-    pgmLock(pVM);
-    /** @todo find a way of mediating this when more users are added. */
-    if (pVM->pgm.s.fPhysWriteMonitoringEngaged)
-    {
-        pgmUnlock(pVM);
-        AssertLogRelFailedReturn(VERR_INTERNAL_ERROR_2);
-    }
-    pVM->pgm.s.fPhysWriteMonitoringEngaged = true;
-    pgmUnlock(pVM);
-
-    /*
-     * Initialize the statistics.
-     */
-    pVM->pgm.s.LiveSave.cReadyPages      = 0;
-    pVM->pgm.s.LiveSave.cDirtyPages      = 0;
-    pVM->pgm.s.LiveSave.cIgnoredPages    = 0;
-    pVM->pgm.s.LiveSave.cDirtyMmio2Pages = 0;
-    pVM->pgm.s.LiveSave.fActive          = true;
-
-    /*
-     * Initialize the live save tracking in the MMIO2 ranges.
-     * ASSUME nothing changes here.
-     */
-    pgmLock(pVM);
-    for (PPGMMMIO2RANGE pMmio2 = pVM->pgm.s.pMmio2RangesR3; pMmio2; pMmio2 = pMmio2->pNextR3)
-    {
-        uint32_t const  cPages = pMmio2->RamRange.cb >> PAGE_SHIFT;
-        pgmUnlock(pVM);
-
-        PPGMLIVESAVEMMIO2PAGE paLSPages = (PPGMLIVESAVEMMIO2PAGE)MMR3HeapAllocZ(pVM, MM_TAG_PGM, sizeof(PGMLIVESAVEMMIO2PAGE) * cPages);
-        if (!paLSPages)
-            return VERR_NO_MEMORY;
-        for (uint32_t iPage = 0; iPage < cPages; iPage++)
-        {
-            /* Initialize it as a dirty zero page. */
-            paLSPages[iPage].fDirty          = true;
-            paLSPages[iPage].cUnchangedScans = 0;
-            paLSPages[iPage].fZero           = true;
-            paLSPages[iPage].u32CrcH1        = PGM_STATE_CRC32_ZERO_HALF_PAGE;
-            paLSPages[iPage].u32CrcH2        = PGM_STATE_CRC32_ZERO_HALF_PAGE;
-        }
-
-        pgmLock(pVM);
-        pMmio2->paLSPages = paLSPages;
-        pVM->pgm.s.LiveSave.cDirtyMmio2Pages += cPages;
-    }
-    pgmUnlock(pVM);
-
+ * @param   pVM                 The VM handle.
+ */
+static int pgmR3PrepRomPages(PVM pVM)
+{
     /*
      * Initialize the live save tracking in the ROM page descriptors.
@@ -277,4 +220,752 @@
     }
     pgmUnlock(pVM);
+
+    return VINF_SUCCESS;
+}
+
+
+/**
+ * Assigns IDs to the ROM ranges and saves them.
+ *
+ * @returns VBox status code.
+ * @param   pVM                 The VM handle.
+ * @param   pSSM                Saved state handle.
+ */
+static int pgmR3SaveRomRanges(PVM pVM, PSSMHANDLE pSSM)
+{
+    pgmLock(pVM);
+    uint8_t id = 1;
+    for (PPGMROMRANGE pRom = pVM->pgm.s.pRomRangesR3; pRom; pRom = pRom->pNextR3, id++)
+    {
+        pRom->idSavedState = id;
+        SSMR3PutU8(pSSM, id);
+        SSMR3PutStrZ(pSSM, "");         /* device name */
+        SSMR3PutU32(pSSM, 0);           /* device instance */
+        SSMR3PutU8(pSSM, 0);            /* region */
+        SSMR3PutStrZ(pSSM, pRom->pszDesc);
+        SSMR3PutGCPhys(pSSM, pRom->GCPhys);
+        int rc = SSMR3PutGCPhys(pSSM, pRom->cb);
+        if (RT_FAILURE(rc))
+            break;
+    }
+    pgmUnlock(pVM);
+    return SSMR3PutU8(pSSM, UINT8_MAX);
+}
+
+
+/**
+ * Loads the ROM range ID assignments.
+ *
+ * @returns VBox status code.
+ *
+ * @param   pVM                 The VM handle.
+ * @param   pSSM                The saved state handle.
+ */
+static int pgmR3LoadRomRanges(PVM pVM, PSSMHANDLE pSSM)
+{
+    Assert(PGMIsLockOwner(pVM));
+
+    for (PPGMROMRANGE pRom = pVM->pgm.s.pRomRangesR3; pRom; pRom = pRom->pNextR3)
+        pRom->idSavedState = UINT8_MAX;
+
+    for (;;)
+    {
+        /*
+         * Read the data.
+         */
+        uint8_t id;
+        int rc = SSMR3GetU8(pSSM, &id);
+        if (RT_FAILURE(rc))
+            return rc;
+        if (id == UINT8_MAX)
+        {
+            for (PPGMROMRANGE pRom = pVM->pgm.s.pRomRangesR3; pRom; pRom = pRom->pNextR3)
+                AssertLogRelMsg(pRom->idSavedState != UINT8_MAX, ("%s\n", pRom->pszDesc));
+            return VINF_SUCCESS;        /* the end */
+        }
+        AssertLogRelReturn(id != 0, VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
+
+        char szDevName[RT_SIZEOFMEMB(PDMDEVREG, szDeviceName)];
+        rc = SSMR3GetStrZ(pSSM, szDevName, sizeof(szDevName));
+        AssertLogRelRCReturn(rc, rc);
+
+        uint32_t    uInstance;
+        SSMR3GetU32(pSSM, &uInstance);
+        uint8_t     iRegion;
+        SSMR3GetU8(pSSM, &iRegion);
+
+        char szDesc[64];
+        rc = SSMR3GetStrZ(pSSM, szDesc, sizeof(szDesc));
+        AssertLogRelRCReturn(rc, rc);
+
+        RTGCPHYS GCPhys;
+        SSMR3GetGCPhys(pSSM, &GCPhys);
+        RTGCPHYS cb;
+        rc = SSMR3GetGCPhys(pSSM, &cb);
+        if (RT_FAILURE(rc))
+            return rc;
+        AssertLogRelMsgReturn(!(GCPhys & PAGE_OFFSET_MASK), ("GCPhys=%RGp %s\n", GCPhys, szDesc), VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
+        AssertLogRelMsgReturn(!(cb & PAGE_OFFSET_MASK),     ("cb=%RGp %s\n", cb, szDesc),         VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
+
+        /*
+         * Locate a matching ROM range.
+         */
+        AssertLogRelMsgReturn(   uInstance == 0
+                              && iRegion == 0
+                              && szDevName[0] == '\0',
+                              ("GCPhys=%RGp %s\n", GCPhys, 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))
+            {
+                pRom->idSavedState = id;
+                break;
+            }
+        }
+        AssertLogRelMsgReturn(pRom, ("GCPhys=%RGp %s\n", GCPhys, szDesc), VERR_SSM_LOAD_CONFIG_MISMATCH);
+    } /* forever */
+}
+
+
+/**
+ * Scan ROM pages.
+ *
+ * @param   pVM                 The VM handle.
+ */
+static void pgmR3ScanRomPages(PVM pVM)
+{
+    /*
+     * The shadow ROMs.
+     */
+    pgmLock(pVM);
+    for (PPGMROMRANGE pRom = pVM->pgm.s.pRomRangesR3; pRom; pRom = pRom->pNextR3)
+    {
+        if (pRom->fFlags & PGMPHYS_ROM_FLAGS_SHADOWED)
+        {
+            uint32_t const cPages = pRom->cb >> PAGE_SHIFT;
+            for (uint32_t iPage = 0; iPage < cPages; iPage++)
+            {
+                PPGMROMPAGE pRomPage = &pRom->aPages[iPage];
+                if (pRomPage->LiveSave.fWrittenTo)
+                {
+                    pRomPage->LiveSave.fWrittenTo = false;
+                    if (!pRomPage->LiveSave.fDirty)
+                    {
+                        pRomPage->LiveSave.fDirty = true;
+                        pVM->pgm.s.LiveSave.cReadyPages--;
+                        pVM->pgm.s.LiveSave.cDirtyPages++;
+                    }
+                    pRomPage->LiveSave.fDirtiedRecently = true;
+                }
+                else
+                    pRomPage->LiveSave.fDirtiedRecently = false;
+            }
+        }
+    }
+    pgmUnlock(pVM);
+}
+
+
+/**
+ * Takes care of the virgin ROM pages in the first pass.
+ *
+ * This is an attempt at simplifying the handling of ROM pages a little bit.
+ * This ASSUMES that no new ROM ranges will be added and that they won't be
+ * relinked in any way.
+ *
+ * @param   pVM                 The VM handle.
+ * @param   pSSM                The SSM handle.
+ * @param   fLiveSave           Whether we're in a live save or not.
+ */
+static int pgmR3SaveRomVirginPages(PVM pVM, PSSMHANDLE pSSM, bool fLiveSave)
+{
+    pgmLock(pVM);
+    for (PPGMROMRANGE pRom = pVM->pgm.s.pRomRangesR3; pRom; pRom = pRom->pNextR3)
+    {
+        uint32_t const cPages = pRom->cb >> PAGE_SHIFT;
+        for (uint32_t iPage = 0; iPage < cPages; iPage++)
+        {
+            RTGCPHYS   GCPhys  = pRom->GCPhys + ((RTGCPHYS)iPage << PAGE_SHIFT);
+            PGMROMPROT enmProt = pRom->aPages[iPage].enmProt;
+
+            /* Get the virgin page descriptor. */
+            PPGMPAGE pPage;
+            if (PGMROMPROT_IS_ROM(enmProt))
+                pPage = pgmPhysGetPage(&pVM->pgm.s, GCPhys);
+            else
+                pPage = &pRom->aPages[iPage].Virgin;
+
+            /* Get the page bits. (Cannot use pgmPhysGCPhys2CCPtrInternalReadOnly here!) */
+            int rc = VINF_SUCCESS;
+            char abPage[PAGE_SIZE];
+            if (!PGM_PAGE_IS_ZERO(pPage))
+            {
+                void const *pvPage;
+                rc = pgmPhysPageMapReadOnly(pVM, pPage, GCPhys, &pvPage);
+                if (RT_SUCCESS(rc))
+                    memcpy(abPage, pvPage, PAGE_SIZE);
+            }
+            else
+                ASMMemZeroPage(abPage);
+            pgmUnlock(pVM);
+            AssertLogRelMsgRCReturn(rc, ("rc=%Rrc GCPhys=%RGp\n", rc, GCPhys), rc);
+
+            /* Save it. */
+            if (iPage > 0)
+                SSMR3PutU8(pSSM, PGM_STATE_REC_ROM_VIRGIN);
+            else
+            {
+                SSMR3PutU8(pSSM, PGM_STATE_REC_ROM_VIRGIN | PGM_STATE_REC_FLAG_ADDR);
+                SSMR3PutU8(pSSM, pRom->idSavedState);
+                SSMR3PutU32(pSSM, iPage);
+            }
+            SSMR3PutU8(pSSM, (uint8_t)enmProt);
+            rc = SSMR3PutMem(pSSM, abPage, PAGE_SIZE);
+            if (RT_FAILURE(rc))
+                return rc;
+
+            /* Update state. */
+            pgmLock(pVM);
+            pRom->aPages[iPage].LiveSave.u8Prot = (uint8_t)enmProt;
+            if (fLiveSave)
+            {
+                pVM->pgm.s.LiveSave.cDirtyPages--;
+                pVM->pgm.s.LiveSave.cReadyPages++;
+            }
+        }
+    }
+    pgmUnlock(pVM);
+    return VINF_SUCCESS;
+}
+
+
+/**
+ * Saves dirty pages in the shadowed ROM ranges.
+ *
+ * Used by pgmR3LiveExecPart2 and pgmR3SaveExecMemory.
+ *
+ * @returns VBox status code.
+ * @param   pVM                 The VM handle.
+ * @param   pSSM                The SSM handle.
+ * @param   fLiveSave           Whether it's a live save or not.
+ * @param   fFinalPass          Whether this is the final pass or not.
+ */
+static int pgmR3SaveShadowedRomPages(PVM pVM, PSSMHANDLE pSSM, bool fLiveSave, bool fFinalPass)
+{
+    /*
+     * The Shadowed ROMs.
+     *
+     * ASSUMES that the ROM ranges are fixed.
+     * ASSUMES that all the ROM ranges are mapped.
+     */
+    pgmLock(pVM);
+    for (PPGMROMRANGE pRom = pVM->pgm.s.pRomRangesR3; pRom; pRom = pRom->pNextR3)
+    {
+        if (pRom->fFlags & PGMPHYS_ROM_FLAGS_SHADOWED)
+        {
+            uint32_t const cPages    = pRom->cb >> PAGE_SHIFT;
+            uint32_t       iPrevPage = cPages;
+            for (uint32_t iPage = 0; iPage < cPages; iPage++)
+            {
+                PPGMROMPAGE pRomPage = &pRom->aPages[iPage];
+                if (    !fLiveSave
+                    ||  (   pRomPage->LiveSave.fDirty
+                         && (   (   !pRomPage->LiveSave.fDirtiedRecently
+                                 && !pRomPage->LiveSave.fWrittenTo)
+                             || fFinalPass
+                             )
+                         )
+                    )
+                {
+                    uint8_t     abPage[PAGE_SIZE];
+                    PGMROMPROT  enmProt = pRomPage->enmProt;
+                    RTGCPHYS    GCPhys  = pRom->GCPhys + ((RTGCPHYS)iPage << PAGE_SHIFT);
+                    PPGMPAGE    pPage   = PGMROMPROT_IS_ROM(enmProt) ? &pRomPage->Shadow : pgmPhysGetPage(&pVM->pgm.s, GCPhys);
+                    bool        fZero   = PGM_PAGE_IS_ZERO(pPage);
+                    int         rc      = VINF_SUCCESS;
+                    if (!fZero)
+                    {
+                        void const *pvPage;
+                        rc = pgmPhysPageMapReadOnly(pVM, pPage, GCPhys, &pvPage);
+                        if (RT_SUCCESS(rc))
+                            memcpy(abPage, pvPage, PAGE_SIZE);
+                    }
+                    if (fLiveSave && RT_SUCCESS(rc))
+                    {
+                        pRomPage->LiveSave.u8Prot = (uint8_t)enmProt;
+                        pRomPage->LiveSave.fDirty = false;
+                        pVM->pgm.s.LiveSave.cReadyPages++;
+                        pVM->pgm.s.LiveSave.cDirtyPages--;
+                    }
+                    pgmUnlock(pVM);
+                    AssertLogRelMsgRCReturn(rc, ("rc=%Rrc GCPhys=%RGp\n", rc, GCPhys), rc);
+
+                    if (iPage - 1U == iPrevPage && iPage > 0)
+                        SSMR3PutU8(pSSM, (fZero ? PGM_STATE_REC_ROM_SHW_ZERO : PGM_STATE_REC_ROM_SHW_RAW));
+                    else
+                    {
+                        SSMR3PutU8(pSSM, (fZero ? PGM_STATE_REC_ROM_SHW_ZERO : PGM_STATE_REC_ROM_SHW_RAW) | PGM_STATE_REC_FLAG_ADDR);
+                        SSMR3PutU8(pSSM, pRom->idSavedState);
+                        SSMR3PutU32(pSSM, iPage);
+                    }
+                    rc = SSMR3PutU8(pSSM, (uint8_t)enmProt);
+                    if (!fZero)
+                        rc = SSMR3PutMem(pSSM, abPage, PAGE_SIZE);
+                    if (RT_FAILURE(rc))
+                        return rc;
+
+                    pgmLock(pVM);
+                    iPrevPage = iPage;
+                }
+                /*
+                 * In the final pass, make sure the protection is in sync.
+                 */
+                else if (   fFinalPass
+                         && pRomPage->LiveSave.u8Prot != pRomPage->enmProt)
+                {
+                    PGMROMPROT enmProt = pRomPage->enmProt;
+                    pRomPage->LiveSave.u8Prot = (uint8_t)enmProt;
+                    pgmUnlock(pVM);
+
+                    if (iPage - 1U == iPrevPage && iPage > 0)
+                        SSMR3PutU8(pSSM, PGM_STATE_REC_ROM_PROT);
+                    else
+                    {
+                        SSMR3PutU8(pSSM, PGM_STATE_REC_ROM_PROT | PGM_STATE_REC_FLAG_ADDR);
+                        SSMR3PutU8(pSSM, pRom->idSavedState);
+                        SSMR3PutU32(pSSM, iPage);
+                    }
+                    int rc = SSMR3PutU8(pSSM, (uint8_t)enmProt);
+                    if (RT_FAILURE(rc))
+                        return rc;
+
+                    pgmLock(pVM);
+                    iPrevPage = iPage;
+                }
+            }
+        }
+    }
+    pgmUnlock(pVM);
+    return VINF_SUCCESS;
+}
+
+
+/**
+ * Cleans up ROM pages after a live save.
+ *
+ * @param   pVM                 The VM handle.
+ */
+static void pgmR3DoneRomPages(PVM pVM)
+{
+    NOREF(pVM);
+}
+
+
+/**
+ * Prepares the MMIO2 pages for a live save.
+ *
+ * @returns VBox status code.
+ * @param   pVM                 The VM handle.
+ */
+static int pgmR3PrepMmio2Pages(PVM pVM)
+{
+    /*
+     * Initialize the live save tracking in the MMIO2 ranges.
+     * ASSUME nothing changes here.
+     */
+    pgmLock(pVM);
+    for (PPGMMMIO2RANGE pMmio2 = pVM->pgm.s.pMmio2RangesR3; pMmio2; pMmio2 = pMmio2->pNextR3)
+    {
+        uint32_t const  cPages = pMmio2->RamRange.cb >> PAGE_SHIFT;
+        pgmUnlock(pVM);
+
+        PPGMLIVESAVEMMIO2PAGE paLSPages = (PPGMLIVESAVEMMIO2PAGE)MMR3HeapAllocZ(pVM, MM_TAG_PGM, sizeof(PGMLIVESAVEMMIO2PAGE) * cPages);
+        if (!paLSPages)
+            return VERR_NO_MEMORY;
+        for (uint32_t iPage = 0; iPage < cPages; iPage++)
+        {
+            /* Initialize it as a dirty zero page. */
+            paLSPages[iPage].fDirty          = true;
+            paLSPages[iPage].cUnchangedScans = 0;
+            paLSPages[iPage].fZero           = true;
+            paLSPages[iPage].u32CrcH1        = PGM_STATE_CRC32_ZERO_HALF_PAGE;
+            paLSPages[iPage].u32CrcH2        = PGM_STATE_CRC32_ZERO_HALF_PAGE;
+        }
+
+        pgmLock(pVM);
+        pMmio2->paLSPages = paLSPages;
+        pVM->pgm.s.LiveSave.cDirtyMmio2Pages += cPages;
+    }
+    pgmUnlock(pVM);
+    return VINF_SUCCESS;
+}
+
+
+/**
+ * Assigns IDs to the MMIO2 ranges and saves them.
+ *
+ * @returns VBox status code.
+ * @param   pVM                 The VM handle.
+ * @param   pSSM                Saved state handle.
+ */
+static int pgmR3SaveMmio2Ranges(PVM pVM, PSSMHANDLE pSSM)
+{
+    pgmLock(pVM);
+    uint8_t id = 1;
+    for (PPGMMMIO2RANGE pMmio2 = pVM->pgm.s.pMmio2RangesR3; pMmio2; pMmio2 = pMmio2->pNextR3, id++)
+    {
+        pMmio2->idSavedState = id;
+        SSMR3PutU8(pSSM, id);
+        SSMR3PutStrZ(pSSM, pMmio2->pDevInsR3->pDevReg->szDeviceName);
+        SSMR3PutU32(pSSM, pMmio2->pDevInsR3->iInstance);
+        SSMR3PutU8(pSSM, pMmio2->iRegion);
+        SSMR3PutStrZ(pSSM, pMmio2->RamRange.pszDesc);
+        int rc = SSMR3PutGCPhys(pSSM, pMmio2->RamRange.cb);
+        if (RT_FAILURE(rc))
+            break;
+    }
+    pgmUnlock(pVM);
+    return SSMR3PutU8(pSSM, UINT8_MAX);
+}
+
+
+/**
+ * Loads the MMIO2 range ID assignments.
+ *
+ * @returns VBox status code.
+ *
+ * @param   pVM                 The VM handle.
+ * @param   pSSM                The saved state handle.
+ */
+static int pgmR3LoadMmio2Ranges(PVM pVM, PSSMHANDLE pSSM)
+{
+    Assert(PGMIsLockOwner(pVM));
+
+    for (PPGMMMIO2RANGE pMmio2 = pVM->pgm.s.pMmio2RangesR3; pMmio2; pMmio2 = pMmio2->pNextR3)
+        pMmio2->idSavedState = UINT8_MAX;
+
+    for (;;)
+    {
+        /*
+         * Read the data.
+         */
+        uint8_t id;
+        int rc = SSMR3GetU8(pSSM, &id);
+        if (RT_FAILURE(rc))
+            return rc;
+        if (id == UINT8_MAX)
+        {
+            for (PPGMMMIO2RANGE pMmio2 = pVM->pgm.s.pMmio2RangesR3; pMmio2; pMmio2 = pMmio2->pNextR3)
+                AssertLogRelMsg(pMmio2->idSavedState != UINT8_MAX, ("%s\n", pMmio2->RamRange.pszDesc));
+            return VINF_SUCCESS;        /* the end */
+        }
+        AssertLogRelReturn(id != 0, VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
+
+        char szDevName[RT_SIZEOFMEMB(PDMDEVREG, szDeviceName)];
+        rc = SSMR3GetStrZ(pSSM, szDevName, sizeof(szDevName));
+        AssertLogRelRCReturn(rc, rc);
+
+        uint32_t    uInstance;
+        SSMR3GetU32(pSSM, &uInstance);
+        uint8_t     iRegion;
+        SSMR3GetU8(pSSM, &iRegion);
+
+        char szDesc[64];
+        rc = SSMR3GetStrZ(pSSM, szDesc, sizeof(szDesc));
+        AssertLogRelRCReturn(rc, rc);
+
+        RTGCPHYS cb;
+        rc = SSMR3GetGCPhys(pSSM, &cb);
+        AssertLogRelMsgReturn(!(cb & PAGE_OFFSET_MASK), ("cb=%RGp %s\n", cb, szDesc), VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
+
+        /*
+         * Locate a matching MMIO2 range.
+         */
+        PPGMMMIO2RANGE pMmio2;
+        for (pMmio2 = pVM->pgm.s.pMmio2RangesR3; pMmio2; pMmio2 = pMmio2->pNextR3)
+        {
+            if (    pMmio2->idSavedState == UINT8_MAX
+                &&  pMmio2->iRegion == iRegion
+                &&  pMmio2->pDevInsR3->iInstance == uInstance
+                &&  !strcmp(pMmio2->pDevInsR3->pDevReg->szDeviceName, szDevName))
+            {
+                pMmio2->idSavedState = id;
+                break;
+            }
+        }
+        AssertLogRelMsgReturn(pMmio2, ("%s/%u/%u: %s\n", szDevName, uInstance, iRegion, szDesc), VERR_SSM_LOAD_CONFIG_MISMATCH);
+    } /* forever */
+}
+
+
+/**
+ * Scans one MMIO2 page.
+ *
+ * @returns True if changed, false if unchanged.
+ *
+ * @param   pVM                 The VM handle
+ * @param   pbPage              The page bits.
+ * @param   pLSPage             The live save tracking structure for the page.
+ *
+ */
+DECLINLINE(bool) pgmR3ScanMmio2Page(PVM pVM, uint8_t const *pbPage, PPGMLIVESAVEMMIO2PAGE pLSPage)
+{
+    /*
+     * Special handling of zero pages.
+     */
+    if (pLSPage->fZero)
+    {
+        if (ASMMemIsZeroPage(pbPage))
+        {
+            /* Not modified. */
+            if (pLSPage->fDirty)
+                pLSPage->cUnchangedScans++;
+            return false;
+        }
+
+        pLSPage->fZero    = false;
+        pLSPage->u32CrcH1 = RTCrc32(pbPage, PAGE_SIZE / 2);
+    }
+    else
+    {
+        /*
+         * CRC the first half, if it doesn't match the page is dirty and
+         * we won't check the 2nd half (we'll do that next time).
+         */
+        uint32_t u32CrcH1 = RTCrc32(pbPage, PAGE_SIZE / 2);
+        if (u32CrcH1 == pLSPage->u32CrcH1)
+        {
+            uint32_t u32CrcH2 = RTCrc32(pbPage + PAGE_SIZE / 2, PAGE_SIZE / 2);
+            if (u32CrcH2 == pLSPage->u32CrcH2)
+            {
+                /* Probably not modified. */
+                if (pLSPage->fDirty)
+                    pLSPage->cUnchangedScans++;
+                return false;
+            }
+
+            pLSPage->u32CrcH2 = u32CrcH2;
+        }
+        else
+        {
+            pLSPage->u32CrcH1 = u32CrcH1;
+            if (    u32CrcH1 == PGM_STATE_CRC32_ZERO_HALF_PAGE
+                &&  ASMMemIsZeroPage(pbPage))
+            {
+                pLSPage->u32CrcH2 = PGM_STATE_CRC32_ZERO_HALF_PAGE;
+                pLSPage->fZero    = true;
+            }
+        }
+    }
+
+    /* dirty page path */
+    pLSPage->cUnchangedScans = 0;
+    if (!pLSPage->fDirty)
+    {
+        pLSPage->fDirty = true;
+        pVM->pgm.s.LiveSave.cReadyPages--;
+        pVM->pgm.s.LiveSave.cDirtyMmio2Pages++;
+    }
+    return true;
+}
+
+
+/**
+ * Scan for MMIO2 page modifications.
+ *
+ * @param   pVM                 The VM handle.
+ * @param   uPass               The pass number.
+ */
+static void pgmR3ScanMmio2Pages(PVM pVM, uint32_t uPass)
+{
+    /*
+     * Only do this every 4th time as it's a little bit expensive.
+     */
+    if (    (uPass & 3) != 0
+        ||  uPass == SSM_PASS_FINAL)
+        return;
+
+    pgmLock(pVM);                       /* paranoia */
+    for (PPGMMMIO2RANGE pMmio2 = pVM->pgm.s.pMmio2RangesR3; pMmio2; pMmio2 = pMmio2->pNextR3)
+    {
+        PPGMLIVESAVEMMIO2PAGE paLSPages = pMmio2->paLSPages;
+        uint8_t const  *pbPage = (uint8_t const *)pMmio2->RamRange.pvR3;
+        uint32_t        cPages = pMmio2->RamRange.cb >> PAGE_SHIFT;
+        pgmUnlock(pVM);
+
+        for (uint32_t iPage = 0; iPage < cPages; iPage++, pbPage += PAGE_SIZE)
+        {
+            uint8_t const *pbPage = (uint8_t const *)pMmio2->pvR3 + iPage * PAGE_SIZE;
+            pgmR3ScanMmio2Page(pVM,pbPage, &paLSPages[iPage]);
+        }
+
+        pgmLock(pVM);
+    }
+    pgmUnlock(pVM);
+
+}
+
+
+/**
+ * Save quiescent MMIO2 pages.
+ *
+ * @returns VBox status code.
+ * @param   pVM                 The VM handle.
+ * @param   pSSM                The SSM handle.
+ * @param   fLiveSave           Whether it's a live save or not.
+ * @param   uPass               The pass number.
+ */
+static int pgmR3SaveMmio2Pages(PVM pVM, PSSMHANDLE pSSM, bool fLiveSave, uint32_t uPass)
+{
+    /** @todo implement live saving of MMIO2 pages. (Need some way of telling the
+     *        device that we wish to know about changes.) */
+
+    int rc = VINF_SUCCESS;
+    if (uPass == SSM_PASS_FINAL)
+    {
+        /*
+         * The mop up round.
+         */
+        pgmLock(pVM);
+        for (PPGMMMIO2RANGE pMmio2 = pVM->pgm.s.pMmio2RangesR3;
+             pMmio2 && RT_SUCCESS(rc);
+             pMmio2 = pMmio2->pNextR3)
+        {
+            PPGMLIVESAVEMMIO2PAGE paLSPages = pMmio2->paLSPages;
+            uint8_t const        *pbPage    = (uint8_t const *)pMmio2->RamRange.pvR3;
+            uint32_t              cPages    = pMmio2->RamRange.cb >> PAGE_SHIFT;
+            uint32_t              iPageLast = cPages;
+            for (uint32_t iPage = 0; iPage < cPages; iPage++, pbPage += PAGE_SIZE)
+            {
+                uint8_t u8Type;
+                if (!fLiveSave)
+                    u8Type = ASMMemIsZeroPage(pbPage) ? PGM_STATE_REC_MMIO2_ZERO : PGM_STATE_REC_MMIO2_RAW;
+                else
+                {
+                    /* Try figure if it's a clean page, compare the SHA-1 to be really sure. */
+                    if (   !paLSPages[iPage].fDirty
+                        && !pgmR3ScanMmio2Page(pVM, pbPage, &paLSPages[iPage]))
+                    {
+                        if (paLSPages[iPage].fZero)
+                            continue;
+
+                        uint8_t abSha1Hash[RTSHA1_HASH_SIZE];
+                        RTSha1(pbPage, PAGE_SIZE, abSha1Hash);
+                        if (!memcmp(abSha1Hash, paLSPages[iPage].abSha1Saved, sizeof(abSha1Hash)))
+                            continue;
+                    }
+                    u8Type = paLSPages[iPage].fZero ? PGM_STATE_REC_MMIO2_ZERO : PGM_STATE_REC_MMIO2_RAW;
+                }
+
+                if (iPage != 0 && iPage == iPageLast + 1)
+                    rc = SSMR3PutU8(pSSM, u8Type);
+                else
+                {
+                    SSMR3PutU8(pSSM, u8Type | PGM_STATE_REC_FLAG_ADDR);
+                    SSMR3PutU8(pSSM, pMmio2->idSavedState);
+                    rc = SSMR3PutU32(pSSM, iPage);
+                }
+                if (u8Type == PGM_STATE_REC_MMIO2_RAW)
+                    rc = SSMR3PutMem(pSSM, pbPage, PAGE_SIZE);
+                if (RT_FAILURE(rc))
+                    break;
+                iPageLast = iPage;
+            }
+        }
+        pgmUnlock(pVM);
+    }
+    /*
+     * Only do this every 4rd time since it's kind of expense.
+     * We position it two passes after the scan pass to avoid saving busy pages.
+     */
+    else if ((uPass & 3) == 2)
+    {
+        pgmLock(pVM);
+        for (PPGMMMIO2RANGE pMmio2 = pVM->pgm.s.pMmio2RangesR3;
+             pMmio2 && RT_SUCCESS(rc);
+             pMmio2 = pMmio2->pNextR3)
+        {
+            PPGMLIVESAVEMMIO2PAGE paLSPages = pMmio2->paLSPages;
+            uint8_t const        *pbPage    = (uint8_t const *)pMmio2->RamRange.pvR3;
+            uint32_t              cPages    = pMmio2->RamRange.cb >> PAGE_SHIFT;
+            uint32_t              iPageLast = cPages;
+            pgmUnlock(pVM);
+
+            for (uint32_t iPage = 0; iPage < cPages; iPage++, pbPage += PAGE_SIZE)
+            {
+                /* Skip clean pages and pages which hasn't quiesced. */
+                if (!paLSPages[iPage].fDirty)
+                    continue;
+                if (paLSPages[iPage].cUnchangedScans < 3)
+                    continue;
+                if (pgmR3ScanMmio2Page(pVM, pbPage, &paLSPages[iPage]))
+                    continue;
+
+                /* Save it. */
+                if (!paLSPages[iPage].fZero)
+                    RTSha1(pbPage, PAGE_SIZE, paLSPages[iPage].abSha1Saved);
+                uint8_t u8Type = paLSPages[iPage].fZero ? PGM_STATE_REC_MMIO2_ZERO : PGM_STATE_REC_MMIO2_RAW;
+                if (iPage != 0 && iPage == iPageLast + 1)
+                    rc = SSMR3PutU8(pSSM, u8Type);
+                else
+                {
+                    SSMR3PutU8(pSSM, u8Type | PGM_STATE_REC_FLAG_ADDR);
+                    SSMR3PutU8(pSSM, pMmio2->idSavedState);
+                    rc = SSMR3PutU32(pSSM, iPage);
+                }
+                if (u8Type == PGM_STATE_REC_MMIO2_RAW)
+                    rc = SSMR3PutMem(pSSM, pbPage, PAGE_SIZE);
+                if (RT_FAILURE(rc))
+                    break;
+                iPageLast = iPage;
+            }
+
+            pgmLock(pVM);
+        }
+        pgmUnlock(pVM);
+    }
+
+    return rc;
+}
+
+
+/**
+ * Cleans up MMIO2 pages after a live save.
+ *
+ * @param   pVM                 The VM handle.
+ */
+static void pgmR3DoneMmio2Pages(PVM pVM)
+{
+    /*
+     * Free the tracking structures for the MMIO2 pages.
+     * We do the freeing outside the lock in case the VM is running.
+     */
+    pgmLock(pVM);
+    for (PPGMMMIO2RANGE pMmio2 = pVM->pgm.s.pMmio2RangesR3; pMmio2; pMmio2 = pMmio2->pNextR3)
+    {
+        void *pvMmio2ToFree = pMmio2->paLSPages;
+        if (pvMmio2ToFree)
+        {
+            pMmio2->paLSPages = NULL;
+            pgmUnlock(pVM);
+            MMR3HeapFree(pvMmio2ToFree);
+            pgmLock(pVM);
+        }
+    }
+    pgmUnlock(pVM);
+}
+
+
+/**
+ * Prepares the RAM pages for a live save.
+ *
+ * @returns VBox status code.
+ * @param   pVM                 The VM handle.
+ */
+static int pgmR3PrepRamPages(PVM pVM)
+{
 
     /*
@@ -386,661 +1077,4 @@
 
     return VINF_SUCCESS;
-}
-
-
-/**
- * Assigns IDs to the ROM ranges and saves them.
- *
- * @returns VBox status code.
- * @param   pVM                 The VM handle.
- * @param   pSSM                Saved state handle.
- */
-static int pgmR3SaveRomRanges(PVM pVM, PSSMHANDLE pSSM)
-{
-    pgmLock(pVM);
-    uint8_t id = 1;
-    for (PPGMROMRANGE pRom = pVM->pgm.s.pRomRangesR3; pRom; pRom = pRom->pNextR3, id++)
-    {
-        pRom->idSavedState = id;
-        SSMR3PutU8(pSSM, id);
-        SSMR3PutStrZ(pSSM, "");         /* device name */
-        SSMR3PutU32(pSSM, 0);           /* device instance */
-        SSMR3PutU8(pSSM, 0);            /* region */
-        SSMR3PutStrZ(pSSM, pRom->pszDesc);
-        SSMR3PutGCPhys(pSSM, pRom->GCPhys);
-        int rc = SSMR3PutGCPhys(pSSM, pRom->cb);
-        if (RT_FAILURE(rc))
-            break;
-    }
-    pgmUnlock(pVM);
-    return SSMR3PutU8(pSSM, UINT8_MAX);
-}
-
-
-/**
- * Loads the ROM range ID assignments.
- *
- * @returns VBox status code.
- *
- * @param   pVM                 The VM handle.
- * @param   pSSM                The saved state handle.
- */
-static int pgmR3LoadRomRanges(PVM pVM, PSSMHANDLE pSSM)
-{
-    Assert(PGMIsLockOwner(pVM));
-
-    for (PPGMROMRANGE pRom = pVM->pgm.s.pRomRangesR3; pRom; pRom = pRom->pNextR3)
-        pRom->idSavedState = UINT8_MAX;
-
-    for (;;)
-    {
-        /*
-         * Read the data.
-         */
-        uint8_t id;
-        int rc = SSMR3GetU8(pSSM, &id);
-        if (RT_FAILURE(rc))
-            return rc;
-        if (id == UINT8_MAX)
-        {
-            for (PPGMROMRANGE pRom = pVM->pgm.s.pRomRangesR3; pRom; pRom = pRom->pNextR3)
-                AssertLogRelMsg(pRom->idSavedState != UINT8_MAX, ("%s\n", pRom->pszDesc));
-            return VINF_SUCCESS;        /* the end */
-        }
-        AssertLogRelReturn(id != 0, VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
-
-        char szDevName[RT_SIZEOFMEMB(PDMDEVREG, szDeviceName)];
-        rc = SSMR3GetStrZ(pSSM, szDevName, sizeof(szDevName));
-        AssertLogRelRCReturn(rc, rc);
-
-        uint32_t    uInstance;
-        SSMR3GetU32(pSSM, &uInstance);
-        uint8_t     iRegion;
-        SSMR3GetU8(pSSM, &iRegion);
-
-        char szDesc[64];
-        rc = SSMR3GetStrZ(pSSM, szDesc, sizeof(szDesc));
-        AssertLogRelRCReturn(rc, rc);
-
-        RTGCPHYS GCPhys;
-        SSMR3GetGCPhys(pSSM, &GCPhys);
-        RTGCPHYS cb;
-        rc = SSMR3GetGCPhys(pSSM, &cb);
-        if (RT_FAILURE(rc))
-            return rc;
-        AssertLogRelMsgReturn(!(GCPhys & PAGE_OFFSET_MASK), ("GCPhys=%RGp %s\n", GCPhys, szDesc), VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
-        AssertLogRelMsgReturn(!(cb & PAGE_OFFSET_MASK),     ("cb=%RGp %s\n", cb, szDesc),         VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
-
-        /*
-         * Locate a matching ROM range.
-         */
-        AssertLogRelMsgReturn(   uInstance == 0
-                              && iRegion == 0
-                              && szDevName[0] == '\0',
-                              ("GCPhys=%RGp %s\n", GCPhys, 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))
-            {
-                pRom->idSavedState = id;
-                break;
-            }
-        }
-        AssertLogRelMsgReturn(pRom, ("GCPhys=%RGp %s\n", GCPhys, szDesc), VERR_SSM_LOAD_CONFIG_MISMATCH);
-    } /* forever */
-}
-
-
-/**
- * Scan ROM pages.
- *
- * @param   pVM                 The VM handle.
- */
-static void pgmR3ScanRomPages(PVM pVM)
-{
-    /*
-     * The shadow ROMs.
-     */
-    pgmLock(pVM);
-    for (PPGMROMRANGE pRom = pVM->pgm.s.pRomRangesR3; pRom; pRom = pRom->pNextR3)
-    {
-        if (pRom->fFlags & PGMPHYS_ROM_FLAGS_SHADOWED)
-        {
-            uint32_t const cPages = pRom->cb >> PAGE_SHIFT;
-            for (uint32_t iPage = 0; iPage < cPages; iPage++)
-            {
-                PPGMROMPAGE pRomPage = &pRom->aPages[iPage];
-                if (pRomPage->LiveSave.fWrittenTo)
-                {
-                    pRomPage->LiveSave.fWrittenTo = false;
-                    if (!pRomPage->LiveSave.fDirty)
-                    {
-                        pRomPage->LiveSave.fDirty = true;
-                        pVM->pgm.s.LiveSave.cReadyPages--;
-                        pVM->pgm.s.LiveSave.cDirtyPages++;
-                    }
-                    pRomPage->LiveSave.fDirtiedRecently = true;
-                }
-                else
-                    pRomPage->LiveSave.fDirtiedRecently = false;
-            }
-        }
-    }
-    pgmUnlock(pVM);
-}
-
-
-/**
- * Takes care of the virgin ROM pages in the first pass.
- *
- * This is an attempt at simplifying the handling of ROM pages a little bit.
- * This ASSUMES that no new ROM ranges will be added and that they won't be
- * relinked in any way.
- *
- * @param   pVM                 The VM handle.
- * @param   pSSM                The SSM handle.
- * @param   fLiveSave           Whether we're in a live save or not.
- */
-static int pgmR3SaveRomVirginPages(PVM pVM, PSSMHANDLE pSSM, bool fLiveSave)
-{
-    pgmLock(pVM);
-    for (PPGMROMRANGE pRom = pVM->pgm.s.pRomRangesR3; pRom; pRom = pRom->pNextR3)
-    {
-        uint32_t const cPages = pRom->cb >> PAGE_SHIFT;
-        for (uint32_t iPage = 0; iPage < cPages; iPage++)
-        {
-            RTGCPHYS   GCPhys  = pRom->GCPhys + ((RTGCPHYS)iPage << PAGE_SHIFT);
-            PGMROMPROT enmProt = pRom->aPages[iPage].enmProt;
-
-            /* Get the virgin page descriptor. */
-            PPGMPAGE pPage;
-            if (PGMROMPROT_IS_ROM(enmProt))
-                pPage = pgmPhysGetPage(&pVM->pgm.s, GCPhys);
-            else
-                pPage = &pRom->aPages[iPage].Virgin;
-
-            /* Get the page bits. (Cannot use pgmPhysGCPhys2CCPtrInternalReadOnly here!) */
-            int rc = VINF_SUCCESS;
-            char abPage[PAGE_SIZE];
-            if (!PGM_PAGE_IS_ZERO(pPage))
-            {
-                void const *pvPage;
-                rc = pgmPhysPageMapReadOnly(pVM, pPage, GCPhys, &pvPage);
-                if (RT_SUCCESS(rc))
-                    memcpy(abPage, pvPage, PAGE_SIZE);
-            }
-            else
-                ASMMemZeroPage(abPage);
-            pgmUnlock(pVM);
-            AssertLogRelMsgRCReturn(rc, ("rc=%Rrc GCPhys=%RGp\n", rc, GCPhys), rc);
-
-            /* Save it. */
-            if (iPage > 0)
-                SSMR3PutU8(pSSM, PGM_STATE_REC_ROM_VIRGIN);
-            else
-            {
-                SSMR3PutU8(pSSM, PGM_STATE_REC_ROM_VIRGIN | PGM_STATE_REC_FLAG_ADDR);
-                SSMR3PutU8(pSSM, pRom->idSavedState);
-                SSMR3PutU32(pSSM, iPage);
-            }
-            SSMR3PutU8(pSSM, (uint8_t)enmProt);
-            rc = SSMR3PutMem(pSSM, abPage, PAGE_SIZE);
-            if (RT_FAILURE(rc))
-                return rc;
-
-            /* Update state. */
-            pgmLock(pVM);
-            pRom->aPages[iPage].LiveSave.u8Prot = (uint8_t)enmProt;
-            if (fLiveSave)
-            {
-                pVM->pgm.s.LiveSave.cDirtyPages--;
-                pVM->pgm.s.LiveSave.cReadyPages++;
-            }
-        }
-    }
-    pgmUnlock(pVM);
-    return VINF_SUCCESS;
-}
-
-
-/**
- * Saves dirty pages in the shadowed ROM ranges.
- *
- * Used by pgmR3LiveExecPart2 and pgmR3SaveExecMemory.
- *
- * @returns VBox status code.
- * @param   pVM                 The VM handle.
- * @param   pSSM                The SSM handle.
- * @param   fLiveSave           Whether it's a live save or not.
- * @param   fFinalPass          Whether this is the final pass or not.
- */
-static int pgmR3SaveShadowedRomPages(PVM pVM, PSSMHANDLE pSSM, bool fLiveSave, bool fFinalPass)
-{
-    /*
-     * The Shadowed ROMs.
-     *
-     * ASSUMES that the ROM ranges are fixed.
-     * ASSUMES that all the ROM ranges are mapped.
-     */
-    pgmLock(pVM);
-    for (PPGMROMRANGE pRom = pVM->pgm.s.pRomRangesR3; pRom; pRom = pRom->pNextR3)
-    {
-        if (pRom->fFlags & PGMPHYS_ROM_FLAGS_SHADOWED)
-        {
-            uint32_t const cPages    = pRom->cb >> PAGE_SHIFT;
-            uint32_t       iPrevPage = cPages;
-            for (uint32_t iPage = 0; iPage < cPages; iPage++)
-            {
-                PPGMROMPAGE pRomPage = &pRom->aPages[iPage];
-                if (    !fLiveSave
-                    ||  (   pRomPage->LiveSave.fDirty
-                         && (   (   !pRomPage->LiveSave.fDirtiedRecently
-                                 && !pRomPage->LiveSave.fWrittenTo)
-                             || fFinalPass
-                             )
-                         )
-                    )
-                {
-                    uint8_t     abPage[PAGE_SIZE];
-                    PGMROMPROT  enmProt = pRomPage->enmProt;
-                    RTGCPHYS    GCPhys  = pRom->GCPhys + ((RTGCPHYS)iPage << PAGE_SHIFT);
-                    PPGMPAGE    pPage   = PGMROMPROT_IS_ROM(enmProt) ? &pRomPage->Shadow : pgmPhysGetPage(&pVM->pgm.s, GCPhys);
-                    bool        fZero   = PGM_PAGE_IS_ZERO(pPage);
-                    int         rc      = VINF_SUCCESS;
-                    if (!fZero)
-                    {
-                        void const *pvPage;
-                        rc = pgmPhysPageMapReadOnly(pVM, pPage, GCPhys, &pvPage);
-                        if (RT_SUCCESS(rc))
-                            memcpy(abPage, pvPage, PAGE_SIZE);
-                    }
-                    if (fLiveSave && RT_SUCCESS(rc))
-                    {
-                        pRomPage->LiveSave.u8Prot = (uint8_t)enmProt;
-                        pRomPage->LiveSave.fDirty = false;
-                        pVM->pgm.s.LiveSave.cReadyPages++;
-                        pVM->pgm.s.LiveSave.cDirtyPages--;
-                    }
-                    pgmUnlock(pVM);
-                    AssertLogRelMsgRCReturn(rc, ("rc=%Rrc GCPhys=%RGp\n", rc, GCPhys), rc);
-
-                    if (iPage - 1U == iPrevPage && iPage > 0)
-                        SSMR3PutU8(pSSM, (fZero ? PGM_STATE_REC_ROM_SHW_ZERO : PGM_STATE_REC_ROM_SHW_RAW));
-                    else
-                    {
-                        SSMR3PutU8(pSSM, (fZero ? PGM_STATE_REC_ROM_SHW_ZERO : PGM_STATE_REC_ROM_SHW_RAW) | PGM_STATE_REC_FLAG_ADDR);
-                        SSMR3PutU8(pSSM, pRom->idSavedState);
-                        SSMR3PutU32(pSSM, iPage);
-                    }
-                    rc = SSMR3PutU8(pSSM, (uint8_t)enmProt);
-                    if (!fZero)
-                        rc = SSMR3PutMem(pSSM, abPage, PAGE_SIZE);
-                    if (RT_FAILURE(rc))
-                        return rc;
-
-                    pgmLock(pVM);
-                    iPrevPage = iPage;
-                }
-                /*
-                 * In the final pass, make sure the protection is in sync.
-                 */
-                else if (   fFinalPass
-                         && pRomPage->LiveSave.u8Prot != pRomPage->enmProt)
-                {
-                    PGMROMPROT enmProt = pRomPage->enmProt;
-                    pRomPage->LiveSave.u8Prot = (uint8_t)enmProt;
-                    pgmUnlock(pVM);
-
-                    if (iPage - 1U == iPrevPage && iPage > 0)
-                        SSMR3PutU8(pSSM, PGM_STATE_REC_ROM_PROT);
-                    else
-                    {
-                        SSMR3PutU8(pSSM, PGM_STATE_REC_ROM_PROT | PGM_STATE_REC_FLAG_ADDR);
-                        SSMR3PutU8(pSSM, pRom->idSavedState);
-                        SSMR3PutU32(pSSM, iPage);
-                    }
-                    int rc = SSMR3PutU8(pSSM, (uint8_t)enmProt);
-                    if (RT_FAILURE(rc))
-                        return rc;
-
-                    pgmLock(pVM);
-                    iPrevPage = iPage;
-                }
-            }
-        }
-    }
-    pgmUnlock(pVM);
-    return VINF_SUCCESS;
-}
-
-
-/**
- * Assigns IDs to the MMIO2 ranges and saves them.
- *
- * @returns VBox status code.
- * @param   pVM                 The VM handle.
- * @param   pSSM                Saved state handle.
- */
-static int pgmR3SaveMmio2Ranges(PVM pVM, PSSMHANDLE pSSM)
-{
-    pgmLock(pVM);
-    uint8_t id = 1;
-    for (PPGMMMIO2RANGE pMmio2 = pVM->pgm.s.pMmio2RangesR3; pMmio2; pMmio2 = pMmio2->pNextR3, id++)
-    {
-        pMmio2->idSavedState = id;
-        SSMR3PutU8(pSSM, id);
-        SSMR3PutStrZ(pSSM, pMmio2->pDevInsR3->pDevReg->szDeviceName);
-        SSMR3PutU32(pSSM, pMmio2->pDevInsR3->iInstance);
-        SSMR3PutU8(pSSM, pMmio2->iRegion);
-        SSMR3PutStrZ(pSSM, pMmio2->RamRange.pszDesc);
-        int rc = SSMR3PutGCPhys(pSSM, pMmio2->RamRange.cb);
-        if (RT_FAILURE(rc))
-            break;
-    }
-    pgmUnlock(pVM);
-    return SSMR3PutU8(pSSM, UINT8_MAX);
-}
-
-
-/**
- * Loads the MMIO2 range ID assignments.
- *
- * @returns VBox status code.
- *
- * @param   pVM                 The VM handle.
- * @param   pSSM                The saved state handle.
- */
-static int pgmR3LoadMmio2Ranges(PVM pVM, PSSMHANDLE pSSM)
-{
-    Assert(PGMIsLockOwner(pVM));
-
-    for (PPGMMMIO2RANGE pMmio2 = pVM->pgm.s.pMmio2RangesR3; pMmio2; pMmio2 = pMmio2->pNextR3)
-        pMmio2->idSavedState = UINT8_MAX;
-
-    for (;;)
-    {
-        /*
-         * Read the data.
-         */
-        uint8_t id;
-        int rc = SSMR3GetU8(pSSM, &id);
-        if (RT_FAILURE(rc))
-            return rc;
-        if (id == UINT8_MAX)
-        {
-            for (PPGMMMIO2RANGE pMmio2 = pVM->pgm.s.pMmio2RangesR3; pMmio2; pMmio2 = pMmio2->pNextR3)
-                AssertLogRelMsg(pMmio2->idSavedState != UINT8_MAX, ("%s\n", pMmio2->RamRange.pszDesc));
-            return VINF_SUCCESS;        /* the end */
-        }
-        AssertLogRelReturn(id != 0, VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
-
-        char szDevName[RT_SIZEOFMEMB(PDMDEVREG, szDeviceName)];
-        rc = SSMR3GetStrZ(pSSM, szDevName, sizeof(szDevName));
-        AssertLogRelRCReturn(rc, rc);
-
-        uint32_t    uInstance;
-        SSMR3GetU32(pSSM, &uInstance);
-        uint8_t     iRegion;
-        SSMR3GetU8(pSSM, &iRegion);
-
-        char szDesc[64];
-        rc = SSMR3GetStrZ(pSSM, szDesc, sizeof(szDesc));
-        AssertLogRelRCReturn(rc, rc);
-
-        RTGCPHYS cb;
-        rc = SSMR3GetGCPhys(pSSM, &cb);
-        AssertLogRelMsgReturn(!(cb & PAGE_OFFSET_MASK), ("cb=%RGp %s\n", cb, szDesc), VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
-
-        /*
-         * Locate a matching MMIO2 range.
-         */
-        PPGMMMIO2RANGE pMmio2;
-        for (pMmio2 = pVM->pgm.s.pMmio2RangesR3; pMmio2; pMmio2 = pMmio2->pNextR3)
-        {
-            if (    pMmio2->idSavedState == UINT8_MAX
-                &&  pMmio2->iRegion == iRegion
-                &&  pMmio2->pDevInsR3->iInstance == uInstance
-                &&  !strcmp(pMmio2->pDevInsR3->pDevReg->szDeviceName, szDevName))
-            {
-                pMmio2->idSavedState = id;
-                break;
-            }
-        }
-        AssertLogRelMsgReturn(pMmio2, ("%s/%u/%u: %s\n", szDevName, uInstance, iRegion, szDesc), VERR_SSM_LOAD_CONFIG_MISMATCH);
-    } /* forever */
-}
-
-
-/**
- * Scans one MMIO2 page.
- *
- * @returns True if changed, false if unchanged.
- *
- * @param   pVM                 The VM handle
- * @param   pbPage              The page bits.
- * @param   pLSPage             The live save tracking structure for the page.
- *
- */
-DECLINLINE(bool) pgmR3ScanMmio2Page(PVM pVM, uint8_t const *pbPage, PPGMLIVESAVEMMIO2PAGE pLSPage)
-{
-    /*
-     * Special handling of zero pages.
-     */
-    if (pLSPage->fZero)
-    {
-        if (ASMMemIsZeroPage(pbPage))
-        {
-            /* Not modified. */
-            if (pLSPage->fDirty)
-                pLSPage->cUnchangedScans++;
-            return false;
-        }
-
-        pLSPage->fZero    = false;
-        pLSPage->u32CrcH1 = RTCrc32(pbPage, PAGE_SIZE / 2);
-    }
-    else
-    {
-        /*
-         * CRC the first half, if it doesn't match the page is dirty and
-         * we won't check the 2nd half (we'll do that next time).
-         */
-        uint32_t u32CrcH1 = RTCrc32(pbPage, PAGE_SIZE / 2);
-        if (u32CrcH1 == pLSPage->u32CrcH1)
-        {
-            uint32_t u32CrcH2 = RTCrc32(pbPage + PAGE_SIZE / 2, PAGE_SIZE / 2);
-            if (u32CrcH2 == pLSPage->u32CrcH2)
-            {
-                /* Probably not modified. */
-                if (pLSPage->fDirty)
-                    pLSPage->cUnchangedScans++;
-                return false;
-            }
-
-            pLSPage->u32CrcH2 = u32CrcH2;
-        }
-        else
-        {
-            pLSPage->u32CrcH1 = u32CrcH1;
-            if (    u32CrcH1 == PGM_STATE_CRC32_ZERO_HALF_PAGE
-                &&  ASMMemIsZeroPage(pbPage))
-            {
-                pLSPage->u32CrcH2 = PGM_STATE_CRC32_ZERO_HALF_PAGE;
-                pLSPage->fZero    = true;
-            }
-        }
-    }
-
-    /* dirty page path */
-    pLSPage->cUnchangedScans = 0;
-    if (!pLSPage->fDirty)
-    {
-        pLSPage->fDirty = true;
-        pVM->pgm.s.LiveSave.cReadyPages--;
-        pVM->pgm.s.LiveSave.cDirtyMmio2Pages++;
-    }
-    return true;
-}
-
-
-/**
- * Scan for MMIO2 page modifications.
- *
- * @param   pVM                 The VM handle.
- * @param   uPass               The pass number.
- */
-static void pgmR3ScanMmio2Pages(PVM pVM, uint32_t uPass)
-{
-    /*
-     * Only do this every 4th time as it's a little bit expensive.
-     */
-    if (    (uPass & 3) != 0
-        ||  uPass == SSM_PASS_FINAL)
-        return;
-
-    pgmLock(pVM);                       /* paranoia */
-    for (PPGMMMIO2RANGE pMmio2 = pVM->pgm.s.pMmio2RangesR3; pMmio2; pMmio2 = pMmio2->pNextR3)
-    {
-        PPGMLIVESAVEMMIO2PAGE paLSPages = pMmio2->paLSPages;
-        uint8_t const  *pbPage = (uint8_t const *)pMmio2->RamRange.pvR3;
-        uint32_t        cPages = pMmio2->RamRange.cb >> PAGE_SHIFT;
-        pgmUnlock(pVM);
-
-        for (uint32_t iPage = 0; iPage < cPages; iPage++, pbPage += PAGE_SIZE)
-        {
-            uint8_t const *pbPage = (uint8_t const *)pMmio2->pvR3 + iPage * PAGE_SIZE;
-            pgmR3ScanMmio2Page(pVM,pbPage, &paLSPages[iPage]);
-        }
-
-        pgmLock(pVM);
-    }
-    pgmUnlock(pVM);
-
-}
-
-
-/**
- * Save quiescent MMIO2 pages.
- *
- * @returns VBox status code.
- * @param   pVM                 The VM handle.
- * @param   pSSM                The SSM handle.
- * @param   fLiveSave           Whether it's a live save or not.
- * @param   uPass               The pass number.
- */
-static int pgmR3SaveMmio2Pages(PVM pVM, PSSMHANDLE pSSM, bool fLiveSave, uint32_t uPass)
-{
-    /** @todo implement live saving of MMIO2 pages. (Need some way of telling the
-     *        device that we wish to know about changes.) */
-
-    int rc = VINF_SUCCESS;
-    if (uPass == SSM_PASS_FINAL)
-    {
-        /*
-         * The mop up round.
-         */
-        pgmLock(pVM);
-        for (PPGMMMIO2RANGE pMmio2 = pVM->pgm.s.pMmio2RangesR3;
-             pMmio2 && RT_SUCCESS(rc);
-             pMmio2 = pMmio2->pNextR3)
-        {
-            PPGMLIVESAVEMMIO2PAGE paLSPages = pMmio2->paLSPages;
-            uint8_t const        *pbPage    = (uint8_t const *)pMmio2->RamRange.pvR3;
-            uint32_t              cPages    = pMmio2->RamRange.cb >> PAGE_SHIFT;
-            uint32_t              iPageLast = cPages;
-            for (uint32_t iPage = 0; iPage < cPages; iPage++, pbPage += PAGE_SIZE)
-            {
-                uint8_t u8Type;
-                if (!fLiveSave)
-                    u8Type = ASMMemIsZeroPage(pbPage) ? PGM_STATE_REC_MMIO2_ZERO : PGM_STATE_REC_MMIO2_RAW;
-                else
-                {
-                    /* Try figure if it's a clean page, compare the SHA-1 to be really sure. */
-                    if (   !paLSPages[iPage].fDirty
-                        && !pgmR3ScanMmio2Page(pVM, pbPage, &paLSPages[iPage]))
-                    {
-                        if (paLSPages[iPage].fZero)
-                            continue;
-
-                        uint8_t abSha1Hash[RTSHA1_HASH_SIZE];
-                        RTSha1(pbPage, PAGE_SIZE, abSha1Hash);
-                        if (!memcmp(abSha1Hash, paLSPages[iPage].abSha1Saved, sizeof(abSha1Hash)))
-                            continue;
-                    }
-                    u8Type = paLSPages[iPage].fZero ? PGM_STATE_REC_MMIO2_ZERO : PGM_STATE_REC_MMIO2_RAW;
-                }
-
-                if (iPage != 0 && iPage == iPageLast + 1)
-                    rc = SSMR3PutU8(pSSM, u8Type);
-                else
-                {
-                    SSMR3PutU8(pSSM, u8Type | PGM_STATE_REC_FLAG_ADDR);
-                    SSMR3PutU8(pSSM, pMmio2->idSavedState);
-                    rc = SSMR3PutU32(pSSM, iPage);
-                }
-                if (u8Type == PGM_STATE_REC_MMIO2_RAW)
-                    rc = SSMR3PutMem(pSSM, pbPage, PAGE_SIZE);
-                if (RT_FAILURE(rc))
-                    break;
-                iPageLast = iPage;
-            }
-        }
-        pgmUnlock(pVM);
-    }
-    /*
-     * Only do this every 4rd time since it's kind of expense.
-     * We position it two passes after the scan pass to avoid saving busy pages.
-     */
-    else if ((uPass & 3) == 2)
-    {
-        pgmLock(pVM);
-        for (PPGMMMIO2RANGE pMmio2 = pVM->pgm.s.pMmio2RangesR3;
-             pMmio2 && RT_SUCCESS(rc);
-             pMmio2 = pMmio2->pNextR3)
-        {
-            PPGMLIVESAVEMMIO2PAGE paLSPages = pMmio2->paLSPages;
-            uint8_t const        *pbPage    = (uint8_t const *)pMmio2->RamRange.pvR3;
-            uint32_t              cPages    = pMmio2->RamRange.cb >> PAGE_SHIFT;
-            uint32_t              iPageLast = cPages;
-            pgmUnlock(pVM);
-
-            for (uint32_t iPage = 0; iPage < cPages; iPage++, pbPage += PAGE_SIZE)
-            {
-                /* Skip clean pages and pages which hasn't quiesced. */
-                if (!paLSPages[iPage].fDirty)
-                    continue;
-                if (paLSPages[iPage].cUnchangedScans < 3)
-                    continue;
-                if (pgmR3ScanMmio2Page(pVM, pbPage, &paLSPages[iPage]))
-                    continue;
-
-                /* Save it. */
-                if (!paLSPages[iPage].fZero)
-                    RTSha1(pbPage, PAGE_SIZE, paLSPages[iPage].abSha1Saved);
-                uint8_t u8Type = paLSPages[iPage].fZero ? PGM_STATE_REC_MMIO2_ZERO : PGM_STATE_REC_MMIO2_RAW;
-                if (iPage != 0 && iPage == iPageLast + 1)
-                    rc = SSMR3PutU8(pSSM, u8Type);
-                else
-                {
-                    SSMR3PutU8(pSSM, u8Type | PGM_STATE_REC_FLAG_ADDR);
-                    SSMR3PutU8(pSSM, pMmio2->idSavedState);
-                    rc = SSMR3PutU32(pSSM, iPage);
-                }
-                if (u8Type == PGM_STATE_REC_MMIO2_RAW)
-                    rc = SSMR3PutMem(pSSM, pbPage, PAGE_SIZE);
-                if (RT_FAILURE(rc))
-                    break;
-                iPageLast = iPage;
-            }
-
-            pgmLock(pVM);
-        }
-        pgmUnlock(pVM);
-    }
-
-    return rc;
 }
 
@@ -1375,4 +1409,71 @@
 
 /**
+ * Cleans up RAM pages after a live save.
+ *
+ * @param   pVM                 The VM handle.
+ */
+static void pgmR3DoneRamPages(PVM pVM)
+{
+    /*
+     * Free the tracking arrays and disable write monitoring.
+     *
+     * Play nice with the PGM lock in case we're called while the VM is still
+     * running.  This means we have to delay the freeing since we wish to use
+     * paLSPages as an indicator of which RAM ranges which we need to scan for
+     * write monitored pages.
+     */
+    void *pvToFree = NULL;
+    PPGMRAMRANGE pCur;
+    uint32_t cMonitoredPages = 0;
+    pgmLock(pVM);
+    do
+    {
+        for (pCur = pVM->pgm.s.pRamRangesR3; pCur; pCur = pCur->pNextR3)
+        {
+            if (pCur->paLSPages)
+            {
+                if (pvToFree)
+                {
+                    uint32_t idRamRangesGen = pVM->pgm.s.idRamRangesGen;
+                    pgmUnlock(pVM);
+                    MMR3HeapFree(pvToFree);
+                    pvToFree = NULL;
+                    pgmLock(pVM);
+                    if (idRamRangesGen != pVM->pgm.s.idRamRangesGen)
+                        break;          /* start over again. */
+                }
+
+                pvToFree = pCur->paLSPages;
+                pCur->paLSPages = NULL;
+
+                uint32_t iPage = pCur->cb >> PAGE_SHIFT;
+                while (iPage--)
+                {
+                    PPGMPAGE pPage = &pCur->aPages[iPage];
+                    PGM_PAGE_CLEAR_WRITTEN_TO(pPage);
+                    if (PGM_PAGE_GET_STATE(pPage) == PGM_PAGE_STATE_WRITE_MONITORED)
+                    {
+                        PGM_PAGE_SET_STATE(pPage, PGM_PAGE_STATE_ALLOCATED);
+                        cMonitoredPages++;
+                    }
+                }
+            }
+        }
+    } while (pCur);
+
+    Assert(pVM->pgm.s.cMonitoredPages >= cMonitoredPages);
+    if (pVM->pgm.s.cMonitoredPages < cMonitoredPages)
+        pVM->pgm.s.cMonitoredPages = 0;
+    else
+        pVM->pgm.s.cMonitoredPages -= cMonitoredPages;
+
+    pgmUnlock(pVM);
+
+    MMR3HeapFree(pvToFree);
+    pvToFree = NULL;
+}
+
+
+/**
  * Execute a live save pass.
  *
@@ -1515,4 +1616,53 @@
 #endif /* !VBOX_WITH_LIVE_MIGRATION */
 
+
+/**
+ * Prepare for a live save operation.
+ *
+ * This will attempt to allocate and initialize the tracking structures.  It
+ * will also prepare for write monitoring of pages and initialize PGM::LiveSave.
+ * pgmR3SaveDone will do the cleanups.
+ *
+ * @returns VBox status code.
+ *
+ * @param   pVM         The VM handle.
+ * @param   pSSM        The SSM handle.
+ */
+static DECLCALLBACK(int) pgmR3LivePrep(PVM pVM, PSSMHANDLE pSSM)
+{
+    /*
+     * Indicate that we will be using the write monitoring.
+     */
+    pgmLock(pVM);
+    /** @todo find a way of mediating this when more users are added. */
+    if (pVM->pgm.s.fPhysWriteMonitoringEngaged)
+    {
+        pgmUnlock(pVM);
+        AssertLogRelFailedReturn(VERR_INTERNAL_ERROR_2);
+    }
+    pVM->pgm.s.fPhysWriteMonitoringEngaged = true;
+    pgmUnlock(pVM);
+
+    /*
+     * Initialize the statistics.
+     */
+    pVM->pgm.s.LiveSave.cReadyPages      = 0;
+    pVM->pgm.s.LiveSave.cDirtyPages      = 0;
+    pVM->pgm.s.LiveSave.cIgnoredPages    = 0;
+    pVM->pgm.s.LiveSave.cDirtyMmio2Pages = 0;
+    pVM->pgm.s.LiveSave.fActive          = true;
+
+    /*
+     * Per page type.
+     */
+    int rc = pgmR3PrepRomPages(pVM);
+    if (RT_SUCCESS(rc))
+        rc = pgmR3PrepMmio2Pages(pVM);
+    if (RT_SUCCESS(rc))
+        rc = pgmR3PrepRamPages(pVM);
+    return rc;
+}
+
+
 /**
  * Execute state save operation.
@@ -1658,79 +1808,22 @@
 {
     /*
-     * Free the tracking arrays and disable write monitoring.
-     *
-     * Play nice with the PGM lock in case we're called while the VM is still
-     * running.  This means we have to delay the freeing since we wish to use
-     * paLSPages as an indicator of which RAM ranges which we need to scan for
-     * write monitored pages.
-     */
-    void *pvToFree = NULL;
-    PPGMRAMRANGE pCur;
-    uint32_t cMonitoredPages = 0;
+     * Do per page type cleanups first.
+     */
+    if (pVM->pgm.s.LiveSave.fActive)
+    {
+        pgmR3DoneRomPages(pVM);
+        pgmR3DoneMmio2Pages(pVM);
+        pgmR3DoneRamPages(pVM);
+    }
+
+    /*
+     * Clear the live save indicator and disengage write monitoring.
+     */
     pgmLock(pVM);
-    do
-    {
-        for (pCur = pVM->pgm.s.pRamRangesR3; pCur; pCur = pCur->pNextR3)
-        {
-            if (pCur->paLSPages)
-            {
-                if (pvToFree)
-                {
-                    uint32_t idRamRangesGen = pVM->pgm.s.idRamRangesGen;
-                    pgmUnlock(pVM);
-                    MMR3HeapFree(pvToFree);
-                    pvToFree = NULL;
-                    pgmLock(pVM);
-                    if (idRamRangesGen != pVM->pgm.s.idRamRangesGen)
-                        break;          /* start over again. */
-                }
-
-                pvToFree = pCur->paLSPages;
-                pCur->paLSPages = NULL;
-
-                uint32_t iPage = pCur->cb >> PAGE_SHIFT;
-                while (iPage--)
-                {
-                    PPGMPAGE pPage = &pCur->aPages[iPage];
-                    PGM_PAGE_CLEAR_WRITTEN_TO(pPage);
-                    if (PGM_PAGE_GET_STATE(pPage) == PGM_PAGE_STATE_WRITE_MONITORED)
-                    {
-                        PGM_PAGE_SET_STATE(pPage, PGM_PAGE_STATE_ALLOCATED);
-                        cMonitoredPages++;
-                    }
-                }
-            }
-        }
-    } while (pCur);
-
-    /* Ditto for MMIO2 (ASSUME no runtime MMIO2 changes). */
-    for (PPGMMMIO2RANGE pMmio2 = pVM->pgm.s.pMmio2RangesR3; pMmio2; pMmio2 = pMmio2->pNextR3)
-    {
-        void *pvMmio2ToFree = pMmio2->paLSPages;
-        if (pvMmio2ToFree)
-        {
-            pMmio2->paLSPages = NULL;
-            pgmUnlock(pVM);
-            MMR3HeapFree(pvMmio2ToFree);
-            pgmLock(pVM);
-        }
-    }
-
-    /* Update PGM state. */
     pVM->pgm.s.LiveSave.fActive = false;
-
-    Assert(pVM->pgm.s.cMonitoredPages >= cMonitoredPages);
-    if (pVM->pgm.s.cMonitoredPages < cMonitoredPages)
-        pVM->pgm.s.cMonitoredPages = 0;
-    else
-        pVM->pgm.s.cMonitoredPages -= cMonitoredPages;
-
     /** @todo this is blindly assuming that we're the only user of write
      *        monitoring. Fix this when more users are added. */
     pVM->pgm.s.fPhysWriteMonitoringEngaged = false;
     pgmUnlock(pVM);
-
-    MMR3HeapFree(pvToFree);
-    pvToFree = NULL;
 
     return VINF_SUCCESS;
