Index: /trunk/src/VBox/Devices/USB/DevOHCI.cpp
===================================================================
--- /trunk/src/VBox/Devices/USB/DevOHCI.cpp	(revision 62000)
+++ /trunk/src/VBox/Devices/USB/DevOHCI.cpp	(revision 62001)
@@ -93,4 +93,5 @@
 #include <iprt/semaphore.h>
 #include <iprt/critsect.h>
+#include <iprt/param.h>
 #ifdef IN_RING3
 # include <iprt/alloca.h>
@@ -102,4 +103,7 @@
 #include "VBoxDD.h"
 
+
+#define VBOX_WITH_OHCI_PHYS_READ_CACHE
+//#define VBOX_WITH_OHCI_PHYS_READ_STATS
 
 /*********************************************************************************************************************************
@@ -243,4 +247,13 @@
 typedef OHCILOAD *POHCILOAD;
 
+#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+typedef struct OHCIPAGECACHE
+{
+    /** Last read physical page address. */
+    RTGCPHYS            GCPhysReadCacheAddr;
+    /** Copy of last read physical page. */
+    uint8_t             au8PhysReadCache[PAGE_SIZE];
+} OHCIPAGECACHE, *POHCIPAGECACHE;
+#endif
 
 /**
@@ -398,4 +411,10 @@
     /** Critical section to synchronize the framer and URB completion handler. */
     RTCRITSECT                 CritSect;
+#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+    /** Last read physical page for caching ED reads in the framer thread. */
+    R3PTRTYPE(POHCIPAGECACHE)  pCacheED;
+    /** Last read physical page for caching TD reads in the framer thread. */
+    R3PTRTYPE(POHCIPAGECACHE)  pCacheTD;
+#endif
 
 } OHCI;
@@ -815,4 +834,7 @@
 static void                 ohciBusResume(POHCI ohci, bool fHardware);
 static void                 ohciBusStop(POHCI pThis);
+#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+static void                 ohciPhysReadCacheClear(POHCIPAGECACHE pPageCache);
+#endif
 
 static DECLCALLBACK(void)   ohciRhXferCompletion(PVUSBIROOTHUBPORT pInterface, PVUSBURB pUrb);
@@ -1178,4 +1200,9 @@
     pThis->fno = 0;
 
+#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+    ohciPhysReadCacheClear(pThis->pCacheED);
+    ohciPhysReadCacheClear(pThis->pCacheTD);
+#endif
+
     /*
      * If this is a hardware reset, we will initialize the root hub too.
@@ -1238,4 +1265,165 @@
 #ifdef IN_RING3
 
+#ifdef VBOX_WITH_OHCI_PHYS_READ_STATS
+struct DescReadStats
+{
+    uint32_t cReads;
+    uint32_t cPageChange;
+    uint32_t cMinReadsPerPage;
+    uint32_t cMaxReadsPerPage;
+
+    uint32_t cReadsLastPage;
+    uint32_t u32LastPageAddr;
+};
+
+struct PhysReadStats
+{
+    struct DescReadStats ed;
+    struct DescReadStats td;
+    struct DescReadStats all;
+
+    uint32_t cCrossReads;
+    uint32_t cCacheReads;
+    uint32_t cPageReads;
+};
+
+static struct PhysReadStats physReadStats;
+
+static void descReadStatsReset(struct DescReadStats *p)
+{
+    p->cReads = 0;
+    p->cPageChange = 0;
+    p->cMinReadsPerPage = UINT32_MAX;
+    p->cMaxReadsPerPage = 0;
+
+    p->cReadsLastPage = 0;
+    p->u32LastPageAddr = 0;
+}
+
+static void physReadStatsReset(struct PhysReadStats *p)
+{
+    descReadStatsReset(&p->ed);
+    descReadStatsReset(&p->td);
+    descReadStatsReset(&p->all);
+
+    p->cCrossReads = 0;
+    p->cCacheReads = 0;
+    p->cPageReads = 0;
+}
+
+static void physReadStatsUpdateDesc(struct DescReadStats *p, uint32_t u32Addr)
+{
+    const uint32_t u32PageAddr = u32Addr & ~UINT32_C(0xFFF);
+
+    ++p->cReads;
+
+    if (p->u32LastPageAddr == 0)
+    {
+       /* First call. */
+       ++p->cReadsLastPage;
+       p->u32LastPageAddr = u32PageAddr;
+    }
+    else if (u32PageAddr != p->u32LastPageAddr)
+    {
+       /* New page. */
+       ++p->cPageChange;
+
+       p->cMinReadsPerPage = RT_MIN(p->cMinReadsPerPage, p->cReadsLastPage);
+       p->cMaxReadsPerPage = RT_MAX(p->cMaxReadsPerPage, p->cReadsLastPage);;
+
+       p->cReadsLastPage = 1;
+       p->u32LastPageAddr = u32PageAddr;
+    }
+    else
+    {
+        /* Read on the same page. */
+       ++p->cReadsLastPage;
+    }
+}
+
+static void physReadStatsPrint(struct PhysReadStats *p)
+{
+    p->ed.cMinReadsPerPage = RT_MIN(p->ed.cMinReadsPerPage, p->ed.cReadsLastPage);
+    p->ed.cMaxReadsPerPage = RT_MAX(p->ed.cMaxReadsPerPage, p->ed.cReadsLastPage);;
+    
+    p->td.cMinReadsPerPage = RT_MIN(p->td.cMinReadsPerPage, p->td.cReadsLastPage);
+    p->td.cMaxReadsPerPage = RT_MAX(p->td.cMaxReadsPerPage, p->td.cReadsLastPage);;
+    
+    p->all.cMinReadsPerPage = RT_MIN(p->all.cMinReadsPerPage, p->all.cReadsLastPage);
+    p->all.cMaxReadsPerPage = RT_MAX(p->all.cMaxReadsPerPage, p->all.cReadsLastPage);;
+
+    LogRel(("PHYSREAD:\n"
+            "  ED: %d, %d, %d/%d\n"
+            "  TD: %d, %d, %d/%d\n"
+            " ALL: %d, %d, %d/%d\n"
+            "   C: %d, %d, %d\n"
+            "",
+            p->ed.cReads, p->ed.cPageChange, p->ed.cMinReadsPerPage, p->ed.cMaxReadsPerPage,
+            p->td.cReads, p->td.cPageChange, p->td.cMinReadsPerPage, p->td.cMaxReadsPerPage,
+            p->all.cReads, p->all.cPageChange, p->all.cMinReadsPerPage, p->all.cMaxReadsPerPage,
+            p->cCrossReads, p->cCacheReads, p->cPageReads
+          ));
+
+    physReadStatsReset(p);
+}
+#endif /* VBOX_WITH_OHCI_PHYS_READ_STATS */
+
+#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+static POHCIPAGECACHE ohciPhysReadCacheAlloc(void)
+{
+    return (POHCIPAGECACHE)RTMemAlloc(sizeof(OHCIPAGECACHE));
+}
+
+static void ohciPhysReadCacheFree(POHCIPAGECACHE pPageCache)
+{
+    RTMemFree(pPageCache);
+}
+
+static void ohciPhysReadCacheClear(POHCIPAGECACHE pPageCache)
+{
+    pPageCache->GCPhysReadCacheAddr = NIL_RTGCPHYS;
+}
+
+static void ohciPhysReadCacheRead(POHCI pThis, POHCIPAGECACHE pPageCache, RTGCPHYS GCPhys, void *pvBuf, size_t cbBuf)
+{
+    const RTGCPHYS PageAddr = PAGE_ADDRESS(GCPhys);
+
+    if (PageAddr == PAGE_ADDRESS(GCPhys + cbBuf))
+    {
+        if (PageAddr != pPageCache->GCPhysReadCacheAddr)
+        {
+            PDMDevHlpPhysRead(pThis->pDevInsR3, PageAddr,
+                              pPageCache->au8PhysReadCache, sizeof(pPageCache->au8PhysReadCache));
+            pPageCache->GCPhysReadCacheAddr = PageAddr;
+#ifdef VBOX_WITH_OHCI_PHYS_READ_STATS
+            ++physReadStats.cPageReads;
+#endif
+        }
+
+        memcpy(pvBuf, &pPageCache->au8PhysReadCache[GCPhys & PAGE_OFFSET_MASK], cbBuf);
+#ifdef VBOX_WITH_OHCI_PHYS_READ_STATS
+        ++physReadStats.cCacheReads;
+#endif
+    }
+    else
+    {
+        PDMDevHlpPhysRead(pThis->pDevInsR3, GCPhys, pvBuf, cbBuf);
+#ifdef VBOX_WITH_OHCI_PHYS_READ_STATS
+        ++physReadStats.cCrossReads;
+#endif
+    }
+}
+
+static void ohciReadEdCached(POHCI pThis, uint32_t EdAddr, POHCIED pEd)
+{
+    ohciPhysReadCacheRead(pThis, pThis->pCacheED, EdAddr, pEd, sizeof(*pEd));
+}
+
+static void ohciReadTdCached(POHCI pThis, uint32_t TdAddr, POHCITD pTd)
+{
+    ohciPhysReadCacheRead(pThis, pThis->pCacheTD, TdAddr, pTd, sizeof(*pTd));
+}
+#endif /* VBOX_WITH_OHCI_PHYS_READ_CACHE */
+
 /**
  * Reads an OHCIED.
@@ -1243,4 +1431,8 @@
 DECLINLINE(void) ohciReadEd(POHCI pThis, uint32_t EdAddr, POHCIED pEd)
 {
+#ifdef VBOX_WITH_OHCI_PHYS_READ_STATS
+    physReadStatsUpdateDesc(&physReadStats.ed, EdAddr);
+    physReadStatsUpdateDesc(&physReadStats.all, EdAddr);
+#endif
     ohciGetDWords(pThis, EdAddr, (uint32_t *)pEd, sizeof(*pEd) >> 2);
 }
@@ -1251,4 +1443,8 @@
 DECLINLINE(void) ohciReadTd(POHCI pThis, uint32_t TdAddr, POHCITD pTd)
 {
+#ifdef VBOX_WITH_OHCI_PHYS_READ_STATS
+    physReadStatsUpdateDesc(&physReadStats.td, TdAddr);
+    physReadStatsUpdateDesc(&physReadStats.all, TdAddr);
+#endif
     ohciGetDWords(pThis, TdAddr, (uint32_t *)pTd, sizeof(*pTd) >> 2);
 #ifdef LOG_ENABLED
@@ -2761,6 +2957,14 @@
     }   Head;
 
+#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+    ohciPhysReadCacheClear(pThis->pCacheTD);
+#endif
+
     /* read the head */
+#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+    ohciReadTdCached(pThis, TdAddr, &Head.Td);
+#else
     ohciReadTd(pThis, TdAddr, &Head.Td);
+#endif
     ohciBufInit(&Head.Buf, Head.Td.cbp, Head.Td.be);
     Head.TdAddr = TdAddr;
@@ -2780,5 +2984,9 @@
         pCur->pNext = NULL;
         pCur->TdAddr = pTail->Td.NextTD & ED_PTR_MASK;
+#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+        ohciReadTdCached(pThis, pCur->TdAddr, &pCur->Td);
+#else
         ohciReadTd(pThis, pCur->TdAddr, &pCur->Td);
+#endif
         ohciBufInit(&pCur->Buf, pCur->Td.cbp, pCur->Td.be);
 
@@ -3280,5 +3488,9 @@
     {
         OHCIED Ed;
+#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+        ohciReadEdCached(pThis, EdAddr, &Ed);
+#else
         ohciReadEd(pThis, EdAddr, &Ed);
+#endif
         Assert(!(Ed.hwinfo & ED_HWINFO_ISO)); /* the guest is screwing us */
         if (ohciIsEdReady(&Ed))
@@ -3374,5 +3586,9 @@
     {
         OHCIED Ed;
+#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+        ohciReadEdCached(pThis, EdAddr, &Ed);
+#else
         ohciReadEd(pThis, EdAddr, &Ed);
+#endif
         Assert(!(Ed.hwinfo & ED_HWINFO_ISO)); /* the guest is screwing us */
         if (ohciIsEdPresent(&Ed))
@@ -3491,5 +3707,9 @@
     {
         OHCIED Ed;
+#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+        ohciReadEdCached(pThis, EdAddr, &Ed);
+#else
         ohciReadEd(pThis, EdAddr, &Ed);
+#endif
 
         if (ohciIsEdReady(&Ed))
@@ -3623,4 +3843,10 @@
     Assert(cLeft == 0);
 
+#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+    /* Get hcca data to minimize calls to ohciGetDWords/PDMDevHlpPhysRead. */
+    uint32_t au32HCCA[OHCI_HCCA_NUM_INTR];
+    ohciGetDWords(pThis, pThis->hcca, au32HCCA, OHCI_HCCA_NUM_INTR);
+#endif
+
     /* Go over all bulk/control/interrupt endpoint lists; any URB found in these lists
      * is marked as active again.
@@ -3637,5 +3863,9 @@
             break;
         default:
+#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+            EdAddr = au32HCCA[i];
+#else
             ohciGetDWords(pThis, pThis->hcca + i * sizeof(EdAddr), &EdAddr, 1);
+#endif
             break;
         }
@@ -3644,5 +3874,9 @@
             OHCIED Ed;
             OHCITD Td;
+#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+            ohciReadEdCached(pThis, EdAddr, &Ed);
+#else
             ohciReadEd(pThis, EdAddr, &Ed);
+#endif
             uint32_t TdAddr = Ed.HeadP & ED_PTR_MASK;
             uint32_t TailP  = Ed.TailP & ED_PTR_MASK;
@@ -3651,11 +3885,27 @@
                 && (TdAddr != TailP))
             {
+#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+                ohciPhysReadCacheClear(pThis->pCacheTD);
+#endif
                 do
                 {
+#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+                    ohciReadTdCached(pThis, TdAddr, &Td);
+#else
                     ohciReadTd(pThis, TdAddr, &Td);
+#endif
                     j = ohci_in_flight_find(pThis, TdAddr);
                     if (j > -1)
                         pThis->aInFlight[j].fInactive = false;
                     TdAddr = Td.NextTD & ED_PTR_MASK;
+                    /* See #8125.
+                     * Sometimes the ED is changed by the guest between ohciReadEd above and here.
+                     * Then the code reads TD pointed by the new TailP, which is not allowed.
+                     * Luckily Windows guests have Td.NextTD = 0 in the tail TD.
+                     * Also having a real TD at 0 is very unlikely.
+                     * So do not continue.
+                     */
+                    if (TdAddr == 0)
+                        break;
                     /* Failsafe for temporarily looped lists. */
                     if (++k == 128)
@@ -3811,4 +4061,13 @@
     pThis->fIdle = true;
 
+#ifdef VBOX_WITH_OHCI_PHYS_READ_STATS
+    physReadStatsReset(&physReadStats);
+#endif
+
+#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+    ohciPhysReadCacheClear(pThis->pCacheED);
+    ohciPhysReadCacheClear(pThis->pCacheTD);
+#endif
+
     /* Frame boundary, so do EOF stuff here. */
     bump_frame_number(pThis);
@@ -3821,4 +4080,8 @@
     /* Start the next frame. */
     ohciStartOfFrame(pThis);
+
+#ifdef VBOX_WITH_OHCI_PHYS_READ_STATS
+    physReadStatsPrint(&physReadStats);
+#endif
 
     RTCritSectLeave(&pThis->CritSect);
@@ -5592,4 +5855,11 @@
     PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns);
 
+#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+    ohciPhysReadCacheFree(pThis->pCacheED);
+    pThis->pCacheED = NULL;
+    ohciPhysReadCacheFree(pThis->pCacheTD);
+    pThis->pCacheTD = NULL;
+#endif
+
     if (RTCritSectIsInitialized(&pThis->CritSect))
         RTCritSectDelete(&pThis->CritSect);
@@ -5767,4 +6037,12 @@
         return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS,
                                    N_("OHCI: Failed to create critical section"));
+
+#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+    pThis->pCacheED = ohciPhysReadCacheAlloc();
+    pThis->pCacheTD = ohciPhysReadCacheAlloc();
+    if (pThis->pCacheED == NULL || pThis->pCacheTD == NULL)
+        return PDMDevHlpVMSetError(pDevIns, VERR_NO_MEMORY, RT_SRC_POS,
+                                   N_("OHCI: Failed to allocate PhysRead cache"));
+#endif
 
     /*
