Index: trunk/src/VBox/Devices/Audio/DevHDA.cpp
===================================================================
--- trunk/src/VBox/Devices/Audio/DevHDA.cpp	(revision 88128)
+++ trunk/src/VBox/Devices/Audio/DevHDA.cpp	(revision 88137)
@@ -250,4 +250,32 @@
 #endif
 } HDADRIVER;
+
+
+/** Internal state of this BDLE.
+ *  Not part of the actual BDLE registers.
+ * @note Only for saved state.  */
+typedef struct HDABDLESTATELEGACY
+{
+    /** Own index within the BDL (Buffer Descriptor List). */
+    uint32_t     u32BDLIndex;
+    /** Number of bytes below the stream's FIFO watermark (SDFIFOW).
+     *  Used to check if we need fill up the FIFO again. */
+    uint32_t     cbBelowFIFOW;
+    /** Current offset in DMA buffer (in bytes).*/
+    uint32_t     u32BufOff;
+    uint32_t     Padding;
+} HDABDLESTATELEGACY;
+
+/**
+ * BDLE and state.
+ * @note Only for saved state.
+ */
+typedef struct HDABDLELEGACY
+{
+    /** The actual BDL description. */
+    HDABDLEDESC         Desc;
+    HDABDLESTATELEGACY  State;
+} HDABDLELEGACY;
+AssertCompileSize(HDABDLELEGACY, 32);
 
 
@@ -469,8 +497,8 @@
 static SSMFIELD const g_aSSMBDLEStateFields6[] =
 {
-    SSMFIELD_ENTRY(HDABDLESTATE, u32BDLIndex),
-    SSMFIELD_ENTRY(HDABDLESTATE, cbBelowFIFOW),
-    SSMFIELD_ENTRY_OLD(FIFO,     HDA_FIFO_MAX), /* Deprecated; now is handled in the stream's circular buffer. */
-    SSMFIELD_ENTRY(HDABDLESTATE, u32BufOff),
+    SSMFIELD_ENTRY(HDABDLESTATELEGACY, u32BDLIndex),
+    SSMFIELD_ENTRY(HDABDLESTATELEGACY, cbBelowFIFOW),
+    SSMFIELD_ENTRY_OLD(FIFO,           HDA_FIFO_MAX), /* Deprecated; now is handled in the stream's circular buffer. */
+    SSMFIELD_ENTRY(HDABDLESTATELEGACY, u32BufOff),
     SSMFIELD_ENTRY_TERM()
 };
@@ -479,7 +507,7 @@
 static SSMFIELD const g_aSSMBDLEStateFields7[] =
 {
-    SSMFIELD_ENTRY(HDABDLESTATE, u32BDLIndex),
-    SSMFIELD_ENTRY(HDABDLESTATE, cbBelowFIFOW),
-    SSMFIELD_ENTRY(HDABDLESTATE, u32BufOff),
+    SSMFIELD_ENTRY(HDABDLESTATELEGACY, u32BDLIndex),
+    SSMFIELD_ENTRY(HDABDLESTATELEGACY, cbBelowFIFOW),
+    SSMFIELD_ENTRY(HDABDLESTATELEGACY, u32BufOff),
     SSMFIELD_ENTRY_TERM()
 };
@@ -489,5 +517,5 @@
 {
     SSMFIELD_ENTRY_OLD(cBDLE,      sizeof(uint16_t)), /* Deprecated. */
-    SSMFIELD_ENTRY(HDASTREAMSTATE, uCurBDLE),
+    SSMFIELD_ENTRY_OLD(uCurBDLE,   sizeof(uint16_t)), /* We figure it out from LPID */
     SSMFIELD_ENTRY_OLD(fStop,      1),                /* Deprecated; see SSMR3PutBool(). */
     SSMFIELD_ENTRY_OLD(fRunning,   1),                /* Deprecated; using the HDA_SDCTL_RUN bit is sufficient. */
@@ -499,5 +527,6 @@
 static SSMFIELD const g_aSSMStreamStateFields7[] =
 {
-    SSMFIELD_ENTRY(HDASTREAMSTATE, uCurBDLE),
+    SSMFIELD_ENTRY(HDASTREAMSTATE, idxCurBdle),         /* For backward compatibility we save this. We use LPIB on restore. */
+    SSMFIELD_ENTRY_OLD(uCurBDLEHi, sizeof(uint8_t)),    /* uCurBDLE was 16-bit for some reason, so store/ignore the zero top byte. */
     SSMFIELD_ENTRY(HDASTREAMSTATE, fInReset),
     SSMFIELD_ENTRY(HDASTREAMSTATE, tsTransferNext),
@@ -518,12 +547,12 @@
 static SSMFIELD const g_aSSMStreamBdleFields1234[] =
 {
-    SSMFIELD_ENTRY(HDABDLE,             Desc.u64BufAddr),       /* u64BdleCviAddr */
-    SSMFIELD_ENTRY_OLD(u32BdleMaxCvi,   sizeof(uint32_t)),      /* u32BdleMaxCvi */
-    SSMFIELD_ENTRY(HDABDLE,             State.u32BDLIndex),     /* u32BdleCvi */
-    SSMFIELD_ENTRY(HDABDLE,             Desc.u32BufSize),       /* u32BdleCviLen */
-    SSMFIELD_ENTRY(HDABDLE,             State.u32BufOff),       /* u32BdleCviPos */
-    SSMFIELD_ENTRY_CALLBACK(HDABDLE,    Desc.fFlags, hdaR3GetPutTrans_HDABDLE_Desc_fFlags_1thru4), /* fBdleCviIoc */
-    SSMFIELD_ENTRY(HDABDLE,             State.cbBelowFIFOW),    /* cbUnderFifoW */
-    SSMFIELD_ENTRY_OLD(au8FIFO,         256),                   /* au8FIFO */
+    SSMFIELD_ENTRY(HDABDLELEGACY,           Desc.u64BufAddr),       /* u64BdleCviAddr */
+    SSMFIELD_ENTRY_OLD(u32BdleMaxCvi,       sizeof(uint32_t)),      /* u32BdleMaxCvi */
+    SSMFIELD_ENTRY(HDABDLELEGACY,           State.u32BDLIndex),     /* u32BdleCvi */
+    SSMFIELD_ENTRY(HDABDLELEGACY,           Desc.u32BufSize),       /* u32BdleCviLen */
+    SSMFIELD_ENTRY(HDABDLELEGACY,           State.u32BufOff),       /* u32BdleCviPos */
+    SSMFIELD_ENTRY_CALLBACK(HDABDLELEGACY,  Desc.fFlags, hdaR3GetPutTrans_HDABDLE_Desc_fFlags_1thru4), /* fBdleCviIoc */
+    SSMFIELD_ENTRY(HDABDLELEGACY,           State.cbBelowFIFOW),    /* cbUnderFifoW */
+    SSMFIELD_ENTRY_OLD(au8FIFO,             256),                   /* au8FIFO */
     SSMFIELD_ENTRY_TERM()
 };
@@ -3354,5 +3383,5 @@
     if (RT_SUCCESS(rc))
     {
-        PHDABDLE pState = (PHDABDLE)pvStruct;
+        HDABDLELEGACY *pState = (HDABDLELEGACY *)pvStruct;
         pState->Desc.fFlags = fIoc ? HDA_BDLE_F_IOC : 0;
     }
@@ -3379,10 +3408,11 @@
     AssertRCReturn(rc, rc);
 
-    rc = pHlp->pfnSSMPutStructEx(pSSM, &pStreamShared->State.BDLE.Desc, sizeof(pStreamShared->State.BDLE.Desc),
-                                 0 /*fFlags*/, g_aSSMBDLEDescFields7, NULL);
+    HDABDLEDESC TmpDesc = *(HDABDLEDESC *)&pStreamShared->State.aBdl[RT_MIN(pStreamShared->State.idxCurBdle,
+                                                                            RT_ELEMENTS(pStreamShared->State.aBdl))];
+    rc = pHlp->pfnSSMPutStructEx(pSSM, &TmpDesc, sizeof(TmpDesc), 0 /*fFlags*/, g_aSSMBDLEDescFields7, NULL);
     AssertRCReturn(rc, rc);
 
-    rc = pHlp->pfnSSMPutStructEx(pSSM, &pStreamShared->State.BDLE.State, sizeof(pStreamShared->State.BDLE.State),
-                                 0 /*fFlags*/,  g_aSSMBDLEStateFields7, NULL);
+    HDABDLESTATELEGACY TmpState = { pStreamShared->State.idxCurBdle, 0, pStreamShared->State.offCurBdle, 0 };
+    rc = pHlp->pfnSSMPutStructEx(pSSM, &TmpState, sizeof(TmpState), 0 /*fFlags*/,  g_aSSMBDLEStateFields7, NULL);
     AssertRCReturn(rc, rc);
 
@@ -3477,11 +3507,14 @@
 
 /**
- * Does required post processing when loading a saved state.
+ * Does tail processing after having loaded our saved state.
  *
- * @param   pDevIns             The device instance.
- * @param   pThis               Pointer to the shared HDA state.
- * @param   pThisCC             Pointer to the ring-3 HDA state.
- */
-static int hdaR3LoadExecPost(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATER3 pThisCC)
+ * @param   pDevIns     The device instance.
+ * @param   pThis       Pointer to the shared HDA state.
+ * @param   pThisCC     Pointer to the ring-3 HDA state.
+ * @param   pSSM        The saved state handle.
+ *
+ * @todo    r=bird: Replace by a post load callback.
+ */
+static int hdaR3LoadExecTail(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATER3 pThisCC, PSSMHANDLE pSSM)
 {
     int rc = VINF_SUCCESS; /** @todo r=bird: Really, what's the point of this? */
@@ -3523,18 +3556,68 @@
             hdaR3StreamRegisterDMAHandlers(pThis, pStreamShared);
 #endif
-            /** @todo r=bird: This can go wrong if the state saving happened exactly when
-             *        the timer expired.  See comparison in
-             *        hdaR3StreamTransferIsScheduled.   I've decided to just do this
-             *        unconditionally as that fixes the issue and simplifies the
-             *        hdaR3StreamMarkStarted.  Also, I don't really get why this check is
-             *        necessary at all. */
-            /*if (hdaR3StreamTransferIsScheduled(pStreamShared, PDMDevHlpTimerGet(pDevIns, pStreamShared->hTimer))) */
+
+            /* Use the LPIB to find the current scheduling position.  If this isn't
+               exactly on a scheduling item adjust LPIB down to the start of the
+               current.  This isn't entirely ideal, but it avoid the IRQ counting
+               issue if we round it upwards. (it is also a lot simpler) */
+            uint32_t uLpib = HDA_STREAM_REG(pThis, LPIB, i);
+            AssertLogRelMsgStmt(uLpib < pStreamShared->u32CBL, ("LPIB=%#RX32 CBL=%#RX32\n", uLpib, pStreamShared->u32CBL),
+                                HDA_STREAM_REG(pThis, LPIB, i) = uLpib = 0);
+
+            uint32_t off = 0;
+            for (uint32_t j = 0; j < pStreamShared->State.cSchedule; j++)
             {
-                /* Avoid going through the timer here by calling the stream's timer function directly.
-                 * Should speed up starting the stream transfers. */
-                uint64_t tsNow = hdaR3StreamTimerMain(pDevIns, pThis, pThisCC, pStreamShared, pStreamR3);
-
-                hdaR3StreamMarkStarted(pDevIns, pThis, pStreamShared, tsNow);
+                AssertReturn(pStreamShared->State.aSchedule[j].cbPeriod >= 1 && pStreamShared->State.aSchedule[j].cLoops >= 1,
+                             pDevIns->pHlpR3->pfnSSMSetLoadError(pSSM, VERR_INTERNAL_ERROR_2, RT_SRC_POS,
+                                                                 "Stream #%u, sched #%u: cbPeriod=%u cLoops=%u\n",
+                                                                 pStreamShared->u8SD, j,
+                                                                 pStreamShared->State.aSchedule[j].cbPeriod,
+                                                                 pStreamShared->State.aSchedule[j].cLoops));
+                uint32_t cbCur = pStreamShared->State.aSchedule[j].cbPeriod
+                               * pStreamShared->State.aSchedule[j].cLoops;
+                if (uLpib >= off + cbCur)
+                    off += cbCur;
+                else
+                {
+                    uint32_t const offDelta = uLpib - off;
+                    uint32_t idxLoop = offDelta / pStreamShared->State.aSchedule[j].cbPeriod;
+                    uint32_t offLoop = offDelta % pStreamShared->State.aSchedule[j].cbPeriod;
+                    if (offLoop)
+                    {
+                        /** @todo somehow bake this into the DMA timer logic.   */
+                        LogFunc(("stream #%u: LPIB=%#RX32; adjusting due to scheduling clash: -%#x (j=%u idxLoop=%u cbPeriod=%#x)\n",
+                                 pStreamShared->u8SD, uLpib, offLoop, j, idxLoop, pStreamShared->State.aSchedule[j].cbPeriod));
+                        uLpib -= offLoop;
+                        HDA_STREAM_REG(pThis, LPIB, i) = uLpib;
+                    }
+                    pStreamShared->State.idxSchedule     = (uint16_t)j;
+                    pStreamShared->State.idxScheduleLoop = (uint16_t)idxLoop;
+                    off = UINT32_MAX;
+                    break;
+                }
             }
+            Assert(off == UINT32_MAX);
+
+            /* Now figure out the current BDLE and the offset within it. */
+            off = 0;
+            for (uint32_t j = 0; j < pStreamShared->State.cBdles; j++)
+                if (uLpib >= off + pStreamShared->State.aBdl[j].cb)
+                    off += pStreamShared->State.aBdl[j].cb;
+                else
+                {
+                    pStreamShared->State.idxCurBdle = j;
+                    pStreamShared->State.offCurBdle = uLpib - off;
+                    off = UINT32_MAX;
+                    break;
+                }
+            AssertReturn(off == UINT32_MAX, pDevIns->pHlpR3->pfnSSMSetLoadError(pSSM, VERR_INTERNAL_ERROR_3, RT_SRC_POS,
+                                                                                "Stream #%u: LPIB=%#RX32 not found in loaded BDL\n",
+                                                                                pStreamShared->u8SD, uLpib));
+
+            /* Avoid going through the timer here by calling the stream's timer function directly.
+             * Should speed up starting the stream transfers. */
+            uint64_t tsNow = hdaR3StreamTimerMain(pDevIns, pThis, pThisCC, pStreamShared, pStreamR3);
+
+            hdaR3StreamMarkStarted(pDevIns, pThis, pStreamShared, tsNow);
 
             /* Also keep track of the currently active streams. */
@@ -3554,5 +3637,5 @@
  * @param   pThis       Pointer to the shared HDA state.
  * @param   pThisCC     Pointer to the ring-3 HDA state.
- * @param   pSSM        Pointer to SSM handle.
+ * @param   pSSM        The saved state handle.
  * @param   uVersion    Saved state version to load.
  */
@@ -3645,12 +3728,14 @@
             /* Note 2: The stream's saving order is/was fixed, so don't touch! */
 
+            HDABDLELEGACY BDLE;
+
             /* Output */
             PHDASTREAM pStreamShared = &pThis->aStreams[4];
             rc = hdaR3StreamSetUp(pDevIns, pThis, pStreamShared, &pThisCC->aStreams[4], 4 /* Stream descriptor, hardcoded */);
             AssertRCReturn(rc, rc);
-            rc = pHlp->pfnSSMGetStructEx(pSSM, &pStreamShared->State.BDLE, sizeof(pStreamShared->State.BDLE),
-                                         0 /* fFlags */, g_aSSMStreamBdleFields1234, pDevIns);
+            RT_ZERO(BDLE);
+            rc = pHlp->pfnSSMGetStructEx(pSSM, &BDLE, sizeof(BDLE), 0 /* fFlags */, g_aSSMStreamBdleFields1234, pDevIns);
             AssertRCReturn(rc, rc);
-            pStreamShared->State.uCurBDLE = pStreamShared->State.BDLE.State.u32BDLIndex;
+            pStreamShared->State.idxCurBdle = (uint8_t)BDLE.State.u32BDLIndex; /* not necessary */
 
             /* Microphone-In */
@@ -3658,8 +3743,7 @@
             rc = hdaR3StreamSetUp(pDevIns, pThis, pStreamShared, &pThisCC->aStreams[2], 2 /* Stream descriptor, hardcoded */);
             AssertRCReturn(rc, rc);
-            rc = pHlp->pfnSSMGetStructEx(pSSM, &pStreamShared->State.BDLE, sizeof(pStreamShared->State.BDLE),
-                                         0 /* fFlags */, g_aSSMStreamBdleFields1234, pDevIns);
+            rc = pHlp->pfnSSMGetStructEx(pSSM, &BDLE, sizeof(BDLE), 0 /* fFlags */, g_aSSMStreamBdleFields1234, pDevIns);
             AssertRCReturn(rc, rc);
-            pStreamShared->State.uCurBDLE = pStreamShared->State.BDLE.State.u32BDLIndex;
+            pStreamShared->State.idxCurBdle = (uint8_t)BDLE.State.u32BDLIndex; /* not necessary */
 
             /* Line-In */
@@ -3667,8 +3751,7 @@
             rc = hdaR3StreamSetUp(pDevIns, pThis, pStreamShared, &pThisCC->aStreams[0], 0 /* Stream descriptor, hardcoded */);
             AssertRCReturn(rc, rc);
-            rc = pHlp->pfnSSMGetStructEx(pSSM, &pStreamShared->State.BDLE, sizeof(pStreamShared->State.BDLE),
-                                         0 /* fFlags */, g_aSSMStreamBdleFields1234, pDevIns);
+            rc = pHlp->pfnSSMGetStructEx(pSSM, &BDLE, sizeof(BDLE), 0 /* fFlags */, g_aSSMStreamBdleFields1234, pDevIns);
             AssertRCReturn(rc, rc);
-            pStreamShared->State.uCurBDLE = pStreamShared->State.BDLE.State.u32BDLIndex;
+            pStreamShared->State.idxCurBdle = (uint8_t)BDLE.State.u32BDLIndex; /* not necessary */
             break;
         }
@@ -3731,5 +3814,5 @@
                     rc = pHlp->pfnSSMGetStructEx(pSSM, &Tmp, sizeof(Tmp), 0 /* fFlags */, g_aV5State1Fields, NULL);
                     AssertRCReturn(rc, rc);
-                    pStreamShared->State.uCurBDLE = Tmp.uCurBDLE;
+                    pStreamShared->State.idxCurBdle = (uint8_t)Tmp.uCurBDLE; /* not necessary */
 
                     for (uint16_t a = 0; a < Tmp.cBLDEs; a++)
@@ -3744,9 +3827,4 @@
                         rc = pHlp->pfnSSMGetStructEx(pSSM, &Tmp, sizeof(Tmp), 0 /* fFlags */, g_aV5State2Fields, NULL);
                         AssertRCReturn(rc, rc);
-                        if (Tmp.u32BDLEIndex == pStreamShared->State.uCurBDLE)
-                        {
-                            pStreamShared->State.BDLE.State.cbBelowFIFOW = Tmp.cbBelowFIFOW;
-                            pStreamShared->State.BDLE.State.u32BufOff    = Tmp.u32BufOff;
-                        }
                     }
                 }
@@ -3757,10 +3835,10 @@
                     AssertRCReturn(rc, rc);
 
-                    rc = pHlp->pfnSSMGetStructEx(pSSM, &pStreamShared->State.BDLE.Desc, sizeof(pStreamShared->State.BDLE.Desc),
-                                                 0 /* fFlags */, g_aSSMBDLEDescFields6, pDevIns);
+                    HDABDLEDESC IgnDesc;
+                    rc = pHlp->pfnSSMGetStructEx(pSSM, &IgnDesc, sizeof(IgnDesc), 0 /* fFlags */, g_aSSMBDLEDescFields6, pDevIns);
                     AssertRCReturn(rc, rc);
 
-                    rc = pHlp->pfnSSMGetStructEx(pSSM, &pStreamShared->State.BDLE.State, sizeof(HDABDLESTATE),
-                                                 0 /* fFlags */, g_aSSMBDLEStateFields6, NULL);
+                    HDABDLESTATELEGACY IgnState;
+                    rc = pHlp->pfnSSMGetStructEx(pSSM, &IgnState, sizeof(IgnState), 0 /* fFlags */, g_aSSMBDLEStateFields6, NULL);
                     AssertRCReturn(rc, rc);
 
@@ -3807,5 +3885,5 @@
         rc = hdaR3LoadExecLegacy(pDevIns, pThis, pThisCC, pSSM, uVersion);
         if (RT_SUCCESS(rc))
-            rc = hdaR3LoadExecPost(pDevIns, pThis, pThisCC);
+            rc = hdaR3LoadExecTail(pDevIns, pThis, pThisCC, pSSM);
         return rc;
     }
@@ -3897,14 +3975,15 @@
         /*
          * Load BDLEs (Buffer Descriptor List Entries) and DMA counters.
+         * Obsolete. Derived from LPID now.
          */
-        rc = pHlp->pfnSSMGetStructEx(pSSM, &pStreamShared->State.BDLE.Desc, sizeof(HDABDLEDESC),
-                                     0 /* fFlags */, g_aSSMBDLEDescFields7, NULL);
+        HDABDLEDESC IgnDesc;
+        rc = pHlp->pfnSSMGetStructEx(pSSM, &IgnDesc, sizeof(IgnDesc), 0 /* fFlags */, g_aSSMBDLEDescFields7, NULL);
         AssertRCReturn(rc, rc);
 
-        rc = pHlp->pfnSSMGetStructEx(pSSM, &pStreamShared->State.BDLE.State, sizeof(HDABDLESTATE),
-                                     0 /* fFlags */, g_aSSMBDLEStateFields7, NULL);
+        HDABDLESTATELEGACY IgnState;
+        rc = pHlp->pfnSSMGetStructEx(pSSM, &IgnState, sizeof(IgnState), 0 /* fFlags */, g_aSSMBDLEStateFields7, NULL);
         AssertRCReturn(rc, rc);
 
-        Log2Func(("[SD%RU8] %R[bdle]\n", pStreamShared->u8SD, &pStreamShared->State.BDLE));
+        Log2Func(("[SD%RU8]\n", pStreamShared->u8SD));
 
         /*
@@ -3977,5 +4056,5 @@
     } /* for cStreams */
 
-    rc = hdaR3LoadExecPost(pDevIns, pThis, pThisCC);
+    rc = hdaR3LoadExecTail(pDevIns, pThis, pThisCC, pSSM);
     AssertRC(rc);
 
@@ -3988,20 +4067,4 @@
 *   IPRT format type handlers                                                                                                    *
 *********************************************************************************************************************************/
-
-/**
- * @callback_method_impl{FNRTSTRFORMATTYPE}
- */
-static DECLCALLBACK(size_t) hdaR3StrFmtBDLE(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput,
-                                            const char *pszType, void const *pvValue,
-                                            int cchWidth, int cchPrecision, unsigned fFlags,
-                                            void *pvUser)
-{
-    RT_NOREF(pszType, cchWidth,  cchPrecision, fFlags, pvUser);
-    PHDABDLE pBDLE = (PHDABDLE)pvValue;
-    return RTStrFormat(pfnOutput,  pvArgOutput, NULL, 0,
-                       "BDLE(idx:%RU32, off:%RU32, fifow:%RU32, IOC:%RTbool, DMA[%RU32 bytes @ 0x%x])",
-                       pBDLE->State.u32BDLIndex, pBDLE->State.u32BufOff, pBDLE->State.cbBelowFIFOW,
-                       pBDLE->Desc.fFlags & HDA_BDLE_F_IOC, pBDLE->Desc.u32BufSize, pBDLE->Desc.u64BufAddr);
-}
 
 /**
@@ -4115,5 +4178,4 @@
 static void hdaR3DbgPrintStream(PHDASTATE pThis, PCDBGFINFOHLP pHlp, int idxStream)
 {
-    const PHDASTREAM pStream = &pThis->aStreams[idxStream];
 
     pHlp->pfnPrintf(pHlp, "Stream #%d:\n", idxStream);
@@ -4122,13 +4184,15 @@
     pHlp->pfnPrintf(pHlp, "  SD%dFIFOS: %R[sdfifos]\n", idxStream, HDA_STREAM_REG(pThis, FIFOS, idxStream));
     pHlp->pfnPrintf(pHlp, "  SD%dFIFOW: %R[sdfifow]\n", idxStream, HDA_STREAM_REG(pThis, FIFOW, idxStream));
-    pHlp->pfnPrintf(pHlp, "  BDLE     : %R[bdle]\n",    &pStream->State.BDLE);
-}
-
-/** Worker for hdaR3DbgInfoBDLE. */
-static void hdaR3DbgPrintBDLE(PPDMDEVINS pDevIns, PHDASTATE pThis, PCDBGFINFOHLP pHlp, int idxStream)
+
+    PHDASTREAM const pStream = &pThis->aStreams[idxStream];
+    pHlp->pfnPrintf(pHlp, "  Current BDLE%02u: %s%#RX64 LB %#x%s - off=%#x\n", pStream->State.idxCurBdle, "%%" /*vboxdbg phys prefix*/,
+                    pStream->State.aBdl[pStream->State.idxCurBdle].GCPhys, pStream->State.aBdl[pStream->State.idxCurBdle].cb,
+                    pStream->State.aBdl[pStream->State.idxCurBdle].fFlags ? " IOC" : "", pStream->State.offCurBdle);
+}
+
+/** Worker for hdaR3DbgInfoBDL. */
+static void hdaR3DbgPrintBDL(PPDMDEVINS pDevIns, PHDASTATE pThis, PCDBGFINFOHLP pHlp, int idxStream)
 {
     const PHDASTREAM pStream = &pThis->aStreams[idxStream];
-
-    pHlp->pfnPrintf(pHlp, "Stream #%d BDLE:\n",      idxStream);
 
     uint64_t const u64BaseDMA = RT_MAKE_U64(HDA_STREAM_REG(pThis, BDPL, idxStream),
@@ -4136,16 +4200,24 @@
     uint16_t const u16LVI     = HDA_STREAM_REG(pThis, LVI, idxStream);
     uint32_t const u32CBL     = HDA_STREAM_REG(pThis, CBL, idxStream);
-
-    pHlp->pfnPrintf(pHlp, "  Current: %R[bdle]\n", &pStream->State.BDLE);
-    pHlp->pfnPrintf(pHlp, "  Memory - %#RX64 LB %#x (LVI=%u):\n", u64BaseDMA, u16LVI * sizeof(HDABDLEDESC), u16LVI);
+    uint8_t const  idxCurBdle = pStream->State.idxCurBdle;
+    pHlp->pfnPrintf(pHlp, "Stream #%d BDL: %s%#011RX64 LB %#x (LVI=%u)\n", idxStream, "%%" /*vboxdbg phys prefix*/,
+                    u64BaseDMA, u16LVI * sizeof(HDABDLEDESC), u16LVI);
+    if (u64BaseDMA || idxCurBdle != 0 || pStream->State.aBdl[idxCurBdle].GCPhys != 0 || pStream->State.aBdl[idxCurBdle].cb != 0)
+        pHlp->pfnPrintf(pHlp, "  Current:     BDLE%03u: %s%#011RX64 LB %#x%s - off=%#x  LPIB=%#RX32\n",
+                        pStream->State.idxCurBdle, "%%" /*vboxdbg phys prefix*/,
+                        pStream->State.aBdl[idxCurBdle].GCPhys, pStream->State.aBdl[idxCurBdle].cb,
+                        pStream->State.aBdl[idxCurBdle].fFlags ? " IOC" : "", pStream->State.offCurBdle,
+                        HDA_STREAM_REG(pThis, LPIB, idxStream));
     if (!u64BaseDMA)
         return;
 
-    uint32_t cbTotal = 0;
+    /*
+     * The BDL:
+     */
+    uint64_t cbTotal = 0;
     for (uint16_t i = 0; i < u16LVI + 1; i++)
     {
-        /** @todo shouldn't this be a PCI read?   */
         HDABDLEDESC bd = {0, 0, 0};
-        PDMDevHlpPhysRead(pDevIns, u64BaseDMA + i * sizeof(HDABDLEDESC), &bd, sizeof(bd));
+        PDMDevHlpPCIPhysRead(pDevIns, u64BaseDMA + i * sizeof(HDABDLEDESC), &bd, sizeof(bd));
 
         char szFlags[64];
@@ -4153,21 +4225,45 @@
         if (bd.fFlags & ~HDA_BDLE_F_IOC)
             RTStrPrintf(szFlags, sizeof(szFlags), " !!fFlags=%#x!!\n", bd.fFlags);
-        pHlp->pfnPrintf(pHlp, "    %sBDLE%03u: %#RX64 LB %#x %s%s\n",
-                        pStream->State.BDLE.State.u32BDLIndex == i ? "=>" : "  ", i, bd.u64BufAddr, bd.u32BufSize,
-                        bd.fFlags & HDA_BDLE_F_IOC ? " IOC=1" : "", szFlags);
+        pHlp->pfnPrintf(pHlp, "    %sBDLE%03u: %s%#011RX64 LB %#06x %s%s\n", idxCurBdle == i ? "=>" : "  ", i, "%%", bd.u64BufAddr,
+                        bd.u32BufSize, bd.fFlags & HDA_BDLE_F_IOC ? " IOC=1" : "", szFlags);
+
+        if (memcmp(&bd, &pStream->State.aBdl[i], sizeof(bd)) != 0)
+        {
+            szFlags[0] = '\0';
+            if (bd.fFlags & ~HDA_BDLE_F_IOC)
+                RTStrPrintf(szFlags, sizeof(szFlags), " !!fFlags=%#x!!\n", bd.fFlags);
+            pHlp->pfnPrintf(pHlp, "    !!!loaded: %s%#011RX64 LB %#06x %s%s\n", "%%", pStream->State.aBdl[i].GCPhys,
+                            pStream->State.aBdl[i].cb, pStream->State.aBdl[i].fFlags & HDA_BDLE_F_IOC ? " IOC=1" : "", szFlags);
+        }
 
         cbTotal += bd.u32BufSize;
     }
-    pHlp->pfnPrintf(pHlp, "  Total: %RU32 bytes\n", cbTotal);
+    pHlp->pfnPrintf(pHlp, "  Total: %#RX64 bytes (%RU64)\n", cbTotal, cbTotal);
     if (cbTotal != u32CBL)
-        pHlp->pfnPrintf(pHlp, "  Warning: %RU32 bytes does not match CBL (%RU32)!\n", cbTotal, u32CBL);
-
+        pHlp->pfnPrintf(pHlp, "  Warning: %#RX64 bytes does not match CBL (%#RX64)!\n", cbTotal, u32CBL);
+
+    /*
+     * The scheduling plan.
+     */
+    uint16_t const idxSchedule = pStream->State.idxSchedule;
+    pHlp->pfnPrintf(pHlp, "  Scheduling: %u items, %u prologue.  Current: %u, loop %u.\n",  pStream->State.cSchedule,
+                    pStream->State.cSchedulePrologue, idxSchedule, pStream->State.idxScheduleLoop);
+    for (uint16_t i = 0; i < pStream->State.cSchedule; i++)
+        pHlp->pfnPrintf(pHlp, "    %s#%02u: %#x bytes, %u loop%s, %RU32 ticks. BDLE%u thru BDLE%u\n",
+                        i == idxSchedule ? "=>" : "  ", i,
+                        pStream->State.aSchedule[i].cbPeriod, pStream->State.aSchedule[i].cLoops,
+                        pStream->State.aSchedule[i].cLoops == 1 ? "" : "s",
+                        pStream->State.aSchedule[i].cPeriodTicks, pStream->State.aSchedule[i].idxFirst,
+                        pStream->State.aSchedule[i].idxFirst + pStream->State.aSchedule[i].cEntries - 1);
+
+    /*
+     * DMA counters:
+     */
     uint64_t const uDPBase = pThis->u64DPBase & DPBASE_ADDR_MASK;
-    pHlp->pfnPrintf(pHlp, "  DMA counters %#RX64 LB %#x, %s:\n", uDPBase, u16LVI * 2 * sizeof(uint32_t),
+    pHlp->pfnPrintf(pHlp, "  DMA counters %#011RX64 LB %#x, %s:\n", uDPBase, u16LVI * 2 * sizeof(uint32_t),
                     pThis->fDMAPosition ? "enabled" : "disabled");
     if (uDPBase)
         for (uint16_t i = 0; i < u16LVI + 1; i++)
         {
-            /** @todo shouldn't this be a PCI read? */
             struct
             {
@@ -4175,5 +4271,5 @@
                 uint32_t uReserved;
             } Buf = { 0 , 0 };
-            PDMDevHlpPhysRead(pDevIns, uDPBase + i * sizeof(Buf), &Buf, sizeof(Buf));
+            PDMDevHlpPCIPhysRead(pDevIns, uDPBase + i * sizeof(Buf), &Buf, sizeof(Buf));
 
             char szReserved[64];
@@ -4187,5 +4283,5 @@
 }
 
-/** Used by hdaR3DbgInfoStream and hdaR3DbgInfoBDLE. */
+/** Used by hdaR3DbgInfoStream and hdaR3DbgInfoBDL. */
 static int hdaR3DbgLookupStrmIdx(PCDBGFINFOHLP pHlp, const char *pszArgs)
 {
@@ -4218,13 +4314,13 @@
  * @callback_method_impl{FNDBGFHANDLERDEV, hdabdle}
  */
-static DECLCALLBACK(void) hdaR3DbgInfoBDLE(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
+static DECLCALLBACK(void) hdaR3DbgInfoBDL(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
 {
     PHDASTATE   pThis     = PDMDEVINS_2_DATA(pDevIns, PHDASTATE);
     int         idxStream = hdaR3DbgLookupStrmIdx(pHlp, pszArgs);
     if (idxStream != -1)
-        hdaR3DbgPrintBDLE(pDevIns, pThis, pHlp, idxStream);
+        hdaR3DbgPrintBDL(pDevIns, pThis, pHlp, idxStream);
     else
         for (idxStream = 0; idxStream < HDA_MAX_STREAMS; ++idxStream)
-            hdaR3DbgPrintBDLE(pDevIns, pThis, pHlp, idxStream);
+            hdaR3DbgPrintBDL(pDevIns, pThis, pHlp, idxStream);
 }
 
@@ -5077,7 +5173,7 @@
      */
     PDMDevHlpDBGFInfoRegister(pDevIns, "hda",         "HDA registers. (hda [register case-insensitive])", hdaR3DbgInfo);
-    PDMDevHlpDBGFInfoRegister(pDevIns, "hdabdle",
-                              "HDA buffer descriptor list (BDLE) and DMA stream positions. (hdabdle [stream number])",
-                              hdaR3DbgInfoBDLE);
+    PDMDevHlpDBGFInfoRegister(pDevIns, "hdabdl",
+                              "HDA buffer descriptor list (BDL) and DMA stream positions. (hdabdl [stream number])",
+                              hdaR3DbgInfoBDL);
     PDMDevHlpDBGFInfoRegister(pDevIns, "hdastream",   "HDA stream info. (hdastream [stream number])",    hdaR3DbgInfoStream);
     PDMDevHlpDBGFInfoRegister(pDevIns, "hdcnodes",    "HDA codec nodes.",                                hdaR3DbgInfoCodecNodes);
@@ -5085,10 +5181,9 @@
     PDMDevHlpDBGFInfoRegister(pDevIns, "hdamixer",    "HDA mixer state.",                                hdaR3DbgInfoMixer);
 
-    rc = RTStrFormatTypeRegister("bdle",    hdaR3StrFmtBDLE,    NULL);
-    AssertMsgReturn(RT_SUCCESS(rc) || rc == VERR_ALREADY_EXISTS, ("%Rrc\n", rc), rc);
     rc = RTStrFormatTypeRegister("sdctl",   hdaR3StrFmtSDCTL,   NULL);
     AssertMsgReturn(RT_SUCCESS(rc) || rc == VERR_ALREADY_EXISTS, ("%Rrc\n", rc), rc);
     rc = RTStrFormatTypeRegister("sdsts",   hdaR3StrFmtSDSTS,   NULL);
     AssertMsgReturn(RT_SUCCESS(rc) || rc == VERR_ALREADY_EXISTS, ("%Rrc\n", rc), rc);
+    /** @todo the next two are rather pointless.   */
     rc = RTStrFormatTypeRegister("sdfifos", hdaR3StrFmtSDFIFOS, NULL);
     AssertMsgReturn(RT_SUCCESS(rc) || rc == VERR_ALREADY_EXISTS, ("%Rrc\n", rc), rc);
Index: trunk/src/VBox/Devices/Audio/DevHDACommon.cpp
===================================================================
--- trunk/src/VBox/Devices/Audio/DevHDACommon.cpp	(revision 88128)
+++ trunk/src/VBox/Devices/Audio/DevHDACommon.cpp	(revision 88137)
@@ -365,146 +365,4 @@
 }
 
-/*
- * Reads DMA data from a given HDA output stream.
- *
- * @return  IPRT status code.
- * @param   pDevIns             The device instance.
- * @param   pThis               The shared HDA device state (for stats).
- * @param   pStreamShared       HDA output stream to read DMA data from - shared bits.
- * @param   pStreamR3           HDA output stream to read DMA data from - shared ring-3.
- * @param   pvBuf               Where to store the read data.
- * @param   cbBuf               How much to read in bytes.
- * @param   pcbRead             Returns read bytes from DMA. Optional.
- */
-int hdaR3DMARead(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3,
-                 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
-{
-    RT_NOREF(pThis);
-    PHDABDLE pBDLE = &pStreamShared->State.BDLE;
-
-    int rc = VINF_SUCCESS;
-
-    uint32_t cbReadTotal = 0;
-    uint32_t cbLeft      = RT_MIN(cbBuf, pBDLE->Desc.u32BufSize - pBDLE->State.u32BufOff);
-
-# ifdef HDA_DEBUG_SILENCE
-    uint64_t   csSilence = 0;
-
-    pStreamR3->Dbg.cSilenceThreshold = 100;
-    pStreamR3->Dbg.cbSilenceReadMin  = _1M;
-# endif
-
-    RTGCPHYS GCPhysChunk = pBDLE->Desc.u64BufAddr + pBDLE->State.u32BufOff;
-
-    while (cbLeft)
-    {
-        uint32_t cbChunk = RT_MIN(cbLeft, pStreamShared->u8FIFOS);
-
-        rc = PDMDevHlpPhysRead(pDevIns, GCPhysChunk, (uint8_t *)pvBuf + cbReadTotal, cbChunk);
-        AssertRCBreak(rc);
-
-# ifdef HDA_DEBUG_SILENCE
-        uint16_t *pu16Buf = (uint16_t *)pvBuf;
-        for (size_t i = 0; i < cbChunk / sizeof(uint16_t); i++)
-        {
-            if (*pu16Buf == 0)
-                csSilence++;
-            else
-                break;
-            pu16Buf++;
-        }
-# endif
-        if (RT_LIKELY(!pStreamR3->Dbg.Runtime.fEnabled))
-        { /* likely */ }
-        else
-            DrvAudioHlpFileWrite(pStreamR3->Dbg.Runtime.pFileDMARaw, (uint8_t *)pvBuf + cbReadTotal, cbChunk, 0 /* fFlags */);
-
-        STAM_COUNTER_ADD(&pThis->StatBytesRead, cbChunk);
-
-        /* advance */
-        Assert(cbLeft    >= cbChunk);
-        GCPhysChunk       = (GCPhysChunk + cbChunk) % pBDLE->Desc.u32BufSize;
-        cbReadTotal      += cbChunk;
-        cbLeft           -= cbChunk;
-    }
-
-# ifdef HDA_DEBUG_SILENCE
-    if (csSilence)
-        pStreamR3->Dbg.csSilence += csSilence;
-
-    if (   csSilence == 0
-        && pStreamR3->Dbg.csSilence   >  pStreamR3->Dbg.cSilenceThreshold
-        && pStreamR3->Dbg.cbReadTotal >= pStreamR3->Dbg.cbSilenceReadMin)
-    {
-        LogFunc(("Silent block detected: %RU64 audio samples\n", pStreamR3->Dbg.csSilence));
-        pStreamR3->Dbg.csSilence = 0;
-    }
-# endif
-
-    if (RT_SUCCESS(rc))
-    {
-        if (pcbRead)
-            *pcbRead = cbReadTotal;
-    }
-
-    return rc;
-}
-
-/**
- * Writes audio data from an HDA input stream's FIFO to its associated DMA area.
- *
- * @return  IPRT status code.
- * @param   pDevIns             The device instance.
- * @param   pThis               The shared HDA device state (for stats).
- * @param   pStreamShared       HDA input stream to write audio data to - shared.
- * @param   pStreamR3           HDA input stream to write audio data to - ring-3.
- * @param   pvBuf               Data to write.
- * @param   cbBuf               How much (in bytes) to write.
- * @param   pcbWritten          Returns written bytes on success. Optional.
- */
-int hdaR3DMAWrite(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3,
-                  const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
-{
-    RT_NOREF(pThis);
-    PHDABDLE    pBDLE          = &pStreamShared->State.BDLE;
-    int         rc             = VINF_SUCCESS;
-    uint32_t    cbWrittenTotal = 0;
-    uint32_t    cbLeft         = RT_MIN(cbBuf, pBDLE->Desc.u32BufSize - pBDLE->State.u32BufOff);
-    RTGCPHYS    GCPhysChunk    = pBDLE->Desc.u64BufAddr + pBDLE->State.u32BufOff;
-    while (cbLeft)
-    {
-        uint32_t cbChunk = RT_MIN(cbLeft, pStreamShared->u8FIFOS);
-
-        /* Sanity checks. */
-        Assert(cbChunk <= pBDLE->Desc.u32BufSize - pBDLE->State.u32BufOff);
-
-        if (RT_LIKELY(!pStreamR3->Dbg.Runtime.fEnabled))
-        { /* likely */ }
-        else
-            DrvAudioHlpFileWrite(pStreamR3->Dbg.Runtime.pFileDMARaw, (uint8_t *)pvBuf + cbWrittenTotal, cbChunk, 0 /* fFlags */);
-
-        rc = PDMDevHlpPCIPhysWrite(pDevIns, GCPhysChunk, (uint8_t *)pvBuf + cbWrittenTotal, cbChunk);
-        AssertRCReturn(rc, rc);
-
-        STAM_COUNTER_ADD(&pThis->StatBytesWritten, cbChunk);
-
-        /* advance */
-        Assert(cbLeft  >= cbChunk);
-        cbWrittenTotal += (uint32_t)cbChunk;
-        GCPhysChunk     = (GCPhysChunk + cbChunk) % pBDLE->Desc.u32BufSize;
-        cbLeft         -= (uint32_t)cbChunk;
-    }
-
-    if (RT_SUCCESS(rc))
-    {
-        if (pcbWritten)
-            *pcbWritten = cbWrittenTotal;
-    }
-    else
-        LogFunc(("Failed with %Rrc\n", rc));
-
-    return rc;
-}
-
 #endif /* IN_RING3 */
 
@@ -673,72 +531,3 @@
 # endif /* LOG_ENABLED */
 
-/**
- * Fetches a Bundle Descriptor List Entry (BDLE) from the DMA engine.
- *
- * @param   pDevIns             The device instance.
- * @param   pBDLE               Where to store the fetched result.
- * @param   u64BaseDMA          Address base of DMA engine to use.
- * @param   u16Entry            BDLE entry to fetch.
- */
-int hdaR3BDLEFetch(PPDMDEVINS pDevIns, PHDABDLE pBDLE, uint64_t u64BaseDMA, uint16_t u16Entry)
-{
-    AssertPtrReturn(pBDLE,   VERR_INVALID_POINTER);
-    AssertReturn(u64BaseDMA, VERR_INVALID_PARAMETER);
-
-    if (!u64BaseDMA)
-    {
-        LogRel2(("HDA: Unable to fetch BDLE #%RU16 - no base DMA address set (yet)\n", u16Entry));
-        return VERR_NOT_FOUND;
-    }
-    /** @todo Compare u16Entry with LVI. */
-
-    int rc = PDMDevHlpPhysRead(pDevIns, u64BaseDMA + (u16Entry * sizeof(HDABDLEDESC)),
-                               &pBDLE->Desc, sizeof(pBDLE->Desc));
-
-    if (RT_SUCCESS(rc))
-    {
-        /* Reset internal state. */
-        RT_ZERO(pBDLE->State);
-        pBDLE->State.u32BDLIndex = u16Entry;
-    }
-
-    Log3Func(("Entry #%d @ 0x%x: %R[bdle], rc=%Rrc\n", u16Entry, u64BaseDMA + (u16Entry * sizeof(HDABDLEDESC)), pBDLE, rc));
-
-
-    return VINF_SUCCESS;
-}
-
-/**
- * Tells whether a given BDLE is complete or not.
- *
- * @return  true if BDLE is complete, false if not.
- * @param   pBDLE               BDLE to retrieve status for.
- */
-bool hdaR3BDLEIsComplete(PHDABDLE pBDLE)
-{
-    bool fIsComplete = false;
-
-    if (   !pBDLE->Desc.u32BufSize /* There can be BDLEs with 0 size. */
-        || (pBDLE->State.u32BufOff >= pBDLE->Desc.u32BufSize))
-    {
-        Assert(pBDLE->State.u32BufOff == pBDLE->Desc.u32BufSize);
-        fIsComplete = true;
-    }
-
-    Log3Func(("%R[bdle] => %s\n", pBDLE, fIsComplete ? "COMPLETE" : "INCOMPLETE"));
-
-    return fIsComplete;
-}
-
-/**
- * Tells whether a given BDLE needs an interrupt or not.
- *
- * @return  true if BDLE needs an interrupt, false if not.
- * @param   pBDLE               BDLE to retrieve status for.
- */
-bool hdaR3BDLENeedsInterrupt(PHDABDLE pBDLE)
-{
-    return (pBDLE->Desc.fFlags & HDA_BDLE_F_IOC);
-}
-
 #endif /* IN_RING3 */
Index: trunk/src/VBox/Devices/Audio/DevHDACommon.h
===================================================================
--- trunk/src/VBox/Devices/Audio/DevHDACommon.h	(revision 88128)
+++ trunk/src/VBox/Devices/Audio/DevHDACommon.h	(revision 88137)
@@ -547,21 +547,4 @@
 
 /**
- * Internal state of a Buffer Descriptor List Entry (BDLE),
- * needed to keep track of the data needed for the actual device
- * emulation.
- */
-typedef struct HDABDLESTATE
-{
-    /** Own index within the BDL (Buffer Descriptor List). */
-    uint32_t     u32BDLIndex;
-    /** Number of bytes below the stream's FIFO watermark (SDFIFOW).
-     *  Used to check if we need fill up the FIFO again. */
-    uint32_t     cbBelowFIFOW;
-    /** Current offset in DMA buffer (in bytes).*/
-    uint32_t     u32BufOff;
-    uint32_t     Padding;
-} HDABDLESTATE, *PHDABDLESTATE;
-
-/**
  * BDL description structure.
  * Do not touch this, as this must match to the HDA specs.
@@ -582,18 +565,4 @@
 AssertCompileSize(HDABDLEDESC, 16); /* Always 16 byte. Also must be aligned on 128-byte boundary. */
 
-/**
- * Buffer Descriptor List Entry (BDLE) (3.6.3).
- */
-typedef struct HDABDLE
-{
-    /** The actual BDL description. */
-    HDABDLEDESC  Desc;
-    /** Internal state of this BDLE.
-     *  Not part of the actual BDLE registers. */
-    HDABDLESTATE State;
-} HDABDLE;
-AssertCompileSizeAlignment(HDABDLE, 8);
-/** Pointer to a buffer descriptor list entry (BDLE). */
-typedef HDABDLE *PHDABDLE;
 
 /** @name Object lookup functions.
@@ -638,15 +607,4 @@
 /** @} */
 
-/** @name DMA utility functions.
- * @{
- */
-#ifdef IN_RING3
-int           hdaR3DMARead(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3,
-                           void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead);
-int           hdaR3DMAWrite(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3,
-                            const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten);
-#endif
-/** @} */
-
 /** @name Register functions.
  * @{
@@ -665,7 +623,4 @@
 void          hdaR3BDLEDumpAll(PPDMDEVINS pDevIns, PHDASTATE pThis, uint64_t u64BDLBase, uint16_t cBDLE);
 # endif
-int           hdaR3BDLEFetch(PPDMDEVINS pDevIns, PHDABDLE pBDLE, uint64_t u64BaseDMA, uint16_t u16Entry);
-bool          hdaR3BDLEIsComplete(PHDABDLE pBDLE);
-bool          hdaR3BDLENeedsInterrupt(PHDABDLE pBDLE);
 #endif /* IN_RING3 */
 /** @} */
Index: trunk/src/VBox/Devices/Audio/HDAStream.cpp
===================================================================
--- trunk/src/VBox/Devices/Audio/HDAStream.cpp	(revision 88128)
+++ trunk/src/VBox/Devices/Audio/HDAStream.cpp	(revision 88137)
@@ -221,4 +221,233 @@
     LogFlowFuncLeave();
 }
+
+
+/**
+ * Appends a item to the scheduler.
+ *
+ * @returns VBox status code.
+ * @param   pStreamShared   The stream which scheduler should be modified.
+ * @param   cbCur           The period length in guest bytes.
+ * @param   cbMaxPeriod     The max period in guest bytes.
+ * @param   idxLastBdle     The last BDLE in the period.
+ * @param   pHostProps      The host PCM properties.
+ * @param   pGuestProps     The guest PCM properties.
+ * @param   pcbBorrow       Where to account for bytes borrowed across buffers
+ *                          to align scheduling items on frame boundraries.
+ */
+static int hdaR3StreamAddScheduleItem(PHDASTREAM pStreamShared, uint32_t cbCur, uint32_t cbMaxPeriod, uint32_t idxLastBdle,
+                                      PCPDMAUDIOPCMPROPS pHostProps, PCPDMAUDIOPCMPROPS pGuestProps, uint32_t *pcbBorrow)
+{
+    /* Check that we've got room (shouldn't ever be a problem). */
+    size_t idx = pStreamShared->State.cSchedule;
+    AssertLogRelReturn(idx + 1 < RT_ELEMENTS(pStreamShared->State.aSchedule), VERR_INTERNAL_ERROR_5);
+
+    /* Figure out the BDLE range for this period. */
+    uint32_t const idxFirstBdle = idx == 0 ? 0
+                                :   pStreamShared->State.aSchedule[idx - 1].idxFirst
+                                  + pStreamShared->State.aSchedule[idx - 1].cEntries;
+
+    pStreamShared->State.aSchedule[idx].idxFirst = (uint8_t)idxFirstBdle;
+    pStreamShared->State.aSchedule[idx].cEntries = idxLastBdle >= idxFirstBdle
+                                                 ? idxLastBdle - idxFirstBdle + 1
+                                                 : pStreamShared->State.cBdles - idxFirstBdle + idxLastBdle + 1;
+
+    /* Deal with borrowing due to unaligned IOC buffers. */
+    uint32_t const cbBorrowed = *pcbBorrow;
+    if (cbBorrowed < cbCur)
+        cbCur -= cbBorrowed;
+    else
+    {
+        /* Note. We can probably gloss over this, but it's not a situation a sane guest would put us, so don't bother for now. */
+        ASSERT_GUEST_MSG_FAILED(("#%u: cbBorrow=%#x cbCur=%#x BDLE[%u..%u]\n",
+                                 pStreamShared->u8SD, cbBorrowed, cbCur, idxFirstBdle, idxLastBdle));
+        LogRelMax(32, ("HDA: Stream #%u has a scheduling error: cbBorrow=%#x cbCur=%#x BDLE[%u..%u]\n",
+                       pStreamShared->u8SD, cbBorrowed, cbCur, idxFirstBdle, idxLastBdle));
+        return VERR_OUT_OF_RANGE;
+    }
+
+    uint32_t cbCurAligned = PDMAudioPropsRoundUpBytesToFrame(pGuestProps, cbCur);
+    *pcbBorrow = cbCurAligned - cbCur;
+
+    /* Do we need to split up the period? */
+    if (cbCurAligned <= cbMaxPeriod)
+    {
+        uint32_t cbHost = PDMAudioPropsFramesToBytes(pHostProps, PDMAudioPropsBytesToFrames(pGuestProps, cbCurAligned));
+        pStreamShared->State.aSchedule[idx].cbPeriod = cbHost;
+        pStreamShared->State.aSchedule[idx].cLoops   = 1;
+    }
+    else
+    {
+        /* Reduce till we've below the threshold. */
+        uint32_t cbLoop = cbCurAligned;
+        do
+            cbLoop = cbCurAligned / 2;
+        while (cbLoop > cbMaxPeriod);
+        cbLoop = PDMAudioPropsRoundUpBytesToFrame(pGuestProps, cbLoop);
+
+        /* Complete the scheduling item. */
+        uint32_t cbHost = PDMAudioPropsFramesToBytes(pHostProps, PDMAudioPropsBytesToFrames(pGuestProps, cbLoop));
+        pStreamShared->State.aSchedule[idx].cbPeriod = cbHost;
+        pStreamShared->State.aSchedule[idx].cLoops   = cbCurAligned / cbLoop;
+
+        /* If there is a remainder, add it as a separate entry (this is
+           why the schedule must be more than twice the size of the BDL).*/
+        cbCurAligned %= cbLoop;
+        if (cbCurAligned)
+        {
+            pStreamShared->State.aSchedule[idx + 1] = pStreamShared->State.aSchedule[idx];
+            idx++;
+            cbHost = PDMAudioPropsFramesToBytes(pHostProps, PDMAudioPropsBytesToFrames(pGuestProps, cbCurAligned));
+            pStreamShared->State.aSchedule[idx].cbPeriod = cbHost;
+            pStreamShared->State.aSchedule[idx].cLoops   = 1;
+        }
+    }
+
+    /* Done. */
+    pStreamShared->State.cSchedule = (uint16_t)(idx + 1);
+
+    return VINF_SUCCESS;
+}
+
+/**
+ * Creates the DMA timer schedule for the stream
+ *
+ * This is called from the stream setup code.
+ *
+ * @returns VBox status code.
+ * @param   pStreamShared       The stream to create a schedule for.  The BDL
+ *                              must be loaded.
+ * @param   cSegments           Number of BDL segments.
+ * @param   cBufferIrqs         Number of the BDLEs with IOC=1.
+ * @param   cbTotal             The total BDL length in guest bytes.
+ * @param   cbMaxPeriod         Max period in guest bytes.   This is in case the
+ *                              guest want to play the whole "Der Ring des
+ *                              Nibelungen" cycle in one go.
+ * @param   cTimerTicksPerSec   The DMA timer frequency.
+ * @param   pHostProps          The host PCM properties.
+ * @param   pGuestProps         The guest PCM properties.
+ */
+static int hdaR3StreamCreateSchedule(PHDASTREAM pStreamShared, uint32_t cSegments, uint32_t cBufferIrqs, uint32_t cbTotal,
+                                     uint32_t cbMaxPeriod, uint64_t cTimerTicksPerSec,
+                                     PCPDMAUDIOPCMPROPS pHostProps, PCPDMAUDIOPCMPROPS pGuestProps)
+{
+    int rc;
+
+    /*
+     * Reset scheduling state.
+     */
+    RT_ZERO(pStreamShared->State.aSchedule);
+    pStreamShared->State.cSchedule         = 0;
+    pStreamShared->State.cSchedulePrologue = 0;
+    pStreamShared->State.idxSchedule       = 0;
+    pStreamShared->State.idxScheduleLoop   = 0;
+
+    /*
+     * Do the basic schedule compilation.
+     */
+    uint32_t cPotentialPrologue = 0;
+    uint32_t cbBorrow           = 0;
+    uint32_t cbCur              = 0;
+    pStreamShared->State.aSchedule[0].idxFirst = 0;
+    for (uint32_t i = 0; i < cSegments; i++)
+    {
+        cbCur += pStreamShared->State.aBdl[i].cb;
+        if (pStreamShared->State.aBdl[i].fFlags & HDA_BDLE_F_IOC)
+        {
+            rc = hdaR3StreamAddScheduleItem(pStreamShared, cbCur, cbMaxPeriod, i, pHostProps, pGuestProps, &cbBorrow);
+            ASSERT_GUEST_RC_RETURN(rc, rc);
+
+            if (cPotentialPrologue == 0)
+                cPotentialPrologue = pStreamShared->State.cSchedule;
+            cbCur = 0;
+        }
+    }
+    AssertLogRelMsgReturn(cbBorrow == 0, ("HDA: Internal scheduling error on stream #%u: cbBorrow=%#x cbTotal=%#x cbCur=%#x\n",
+                                          pStreamShared->u8SD, cbBorrow, cbTotal, cbCur),
+                          VERR_INTERNAL_ERROR_3);
+
+    /*
+     * Deal with any loose ends.
+     */
+    if (cbCur && cBufferIrqs == 0)
+    {
+        /* No IOC. Split the period in two. */
+        Assert(cbCur == cbTotal);
+        cbCur = PDMAudioPropsFloorBytesToFrame(pGuestProps, cbCur / 2);
+        rc = hdaR3StreamAddScheduleItem(pStreamShared, cbCur, cbMaxPeriod, cSegments, pHostProps, pGuestProps, &cbBorrow);
+        ASSERT_GUEST_RC_RETURN(rc, rc);
+
+        rc = hdaR3StreamAddScheduleItem(pStreamShared, cbTotal - cbCur, cbMaxPeriod, cSegments,
+                                        pHostProps, pGuestProps, &cbBorrow);
+        ASSERT_GUEST_RC_RETURN(rc, rc);
+        Assert(cbBorrow == 0);
+    }
+    else if (cbCur)
+    {
+        /* The last BDLE didn't have IOC set, so we must continue processing
+           from the start till we hit one that has.  */
+        uint32_t i;
+        for (i = 0; i < cSegments; i++)
+        {
+            cbCur += pStreamShared->State.aBdl[i].cb;
+            if (pStreamShared->State.aBdl[i].fFlags & HDA_BDLE_F_IOC)
+                break;
+        }
+        rc = hdaR3StreamAddScheduleItem(pStreamShared, cbCur, cbMaxPeriod, i, pHostProps, pGuestProps, &cbBorrow);
+        ASSERT_GUEST_RC_RETURN(rc, rc);
+
+        /* The initial scheduling items covering the wrap around area are
+           considered a prologue and must not repeated later. */
+        Assert(cPotentialPrologue);
+        pStreamShared->State.cSchedulePrologue = (uint8_t)cPotentialPrologue;
+    }
+
+    /*
+     * If there is just one BDLE with IOC set, we have to make sure
+     * we've got at least two periods scheduled, otherwise there is
+     * a very good chance the guest will overwrite the start of the
+     * buffer before we ever get around to reading it.
+     */
+    if (cBufferIrqs == 1)
+    {
+        uint32_t i = pStreamShared->State.cSchedulePrologue;
+        Assert(i < pStreamShared->State.cSchedule);
+        if (   i + 1 == pStreamShared->State.cSchedule
+            && pStreamShared->State.aSchedule[i].cLoops == 1)
+        {
+            uint32_t const cbFirstHalf = PDMAudioPropsFloorBytesToFrame(pHostProps, pStreamShared->State.aSchedule[i].cbPeriod / 2);
+            uint32_t const cbOtherHalf = pStreamShared->State.aSchedule[i].cbPeriod - cbFirstHalf;
+            pStreamShared->State.aSchedule[i].cbPeriod = cbFirstHalf;
+            if (cbFirstHalf == cbOtherHalf)
+                pStreamShared->State.aSchedule[i].cLoops = 2;
+            else
+            {
+                pStreamShared->State.aSchedule[i + 1] = pStreamShared->State.aSchedule[i];
+                pStreamShared->State.aSchedule[i].cbPeriod = cbOtherHalf;
+                pStreamShared->State.cSchedule++;
+            }
+        }
+    }
+
+    /*
+     * Go over the schduling entries and calculate the timer ticks for each period.
+     */
+    LogRel2(("HDA: Stream #%u schedule: %u items, %u prologue\n",
+             pStreamShared->u8SD, pStreamShared->State.cSchedule, pStreamShared->State.cSchedulePrologue));
+    uint64_t const cbHostPerSec = PDMAudioPropsFramesToBytes(pHostProps, pHostProps->uHz);
+    for (uint32_t i = 0; i < pStreamShared->State.cSchedule; i++)
+    {
+        uint64_t const cTicks = ASMMultU64ByU32DivByU32(cTimerTicksPerSec, pStreamShared->State.aSchedule[i].cbPeriod,
+                                                        cbHostPerSec);
+        AssertLogRelMsgReturn((uint32_t)cTicks == cTicks, ("cTicks=%RU64 (%#RX64)\n", cTicks, cTicks), VERR_INTERNAL_ERROR_4);
+        pStreamShared->State.aSchedule[i].cPeriodTicks = RT_MAX((uint32_t)cTicks, 16);
+        LogRel2(("HDA:  #%u: %u ticks / %u bytes, %u loops, BDLE%u L %u\n", i, pStreamShared->State.aSchedule[i].cPeriodTicks,
+                 pStreamShared->State.aSchedule[i].cbPeriod, pStreamShared->State.aSchedule[i].cLoops,
+                 pStreamShared->State.aSchedule[i].idxFirst, pStreamShared->State.aSchedule[i].cEntries));
+    }
+
+    return VINF_SUCCESS;
+}
+
 
 /**
@@ -346,80 +575,83 @@
              PDMAudioPropsBytesToMilli(&pStreamR3->State.Mapping.GuestProps, pStreamShared->u32CBL)));
 
-    /* Figure out how many transfer fragments we're going to use for this stream. */
-    uint32_t cTransferFragments = pStreamShared->u16LVI + 1;
+
+    /*
+     * Load the buffer descriptor list.
+     *
+     * Section 3.6.2 states that "the BDL should not be modified unless the RUN
+     * bit is 0", so it should be within the specs to read it once here and not
+     * re-read any BDLEs later.
+     */
+    /* Reset BDL state. */
+    RT_ZERO(pStreamShared->State.aBdl);
+    pStreamShared->State.offCurBdle = 0;
+    pStreamShared->State.idxCurBdle = 0;
+
+    uint32_t /*const*/ cTransferFragments = (pStreamShared->u16LVI & 0xff) + 1;
     if (cTransferFragments <= 1)
-        LogRel(("HDA: Warning: Stream #%RU8 transfer fragments (%RU16) invalid -- buggy guest audio driver!\n",
-                uSD, pStreamShared->u16LVI));
+        LogRel(("HDA: Warning: Stream #%RU8 transfer buffer count invalid: (%RU16)! Buggy guest audio driver!\n", uSD, pStreamShared->u16LVI));
+    AssertLogRelReturn(cTransferFragments <= RT_ELEMENTS(pStreamShared->State.aBdl), VERR_INTERNAL_ERROR_5);
+    pStreamShared->State.cBdles = cTransferFragments;
+
+    /* Load them. */
+    rc = PDMDevHlpPCIPhysRead(pDevIns, u64BDLBase, pStreamShared->State.aBdl,
+                              sizeof(pStreamShared->State.aBdl[0]) * cTransferFragments);
+    AssertRC(rc);
+
+    /* Check what we just loaded.  Refuse overly large buffer lists. */
+    uint64_t cbTotal     = 0;
+    uint32_t cBufferIrqs = 0;
+    for (uint32_t i = 0; i < cTransferFragments; i++)
+    {
+        if (pStreamShared->State.aBdl[i].fFlags & HDA_BDLE_F_IOC)
+            cBufferIrqs++;
+        cbTotal += pStreamShared->State.aBdl[i].cb;
+    }
+    ASSERT_GUEST_STMT_RETURN(cbTotal < _2G,
+                             LogRelMax(32, ("HDA: Error: Stream #%u is configured with an insane amount of buffer space - refusing do work with it: %RU64 (%#RX64) bytes.\n",
+                                            uSD, cbTotal, cbTotal)),
+                             VERR_NOT_SUPPORTED);
+    ASSERT_GUEST_STMT_RETURN(cbTotal == u32CBL,
+                             LogRelMax(32, ("HDA: Warning: Stream #%u has a mismatch between CBL and configured buffer space: %RU32 (%#RX32) vs %RU64 (%#RX64)\n",
+                                            uSD, u32CBL, u32CBL, cbTotal, cbTotal)),
+                             VERR_NOT_SUPPORTED);
 
     /*
-     * Handle the stream's position adjustment.
-     */
-    uint32_t cfPosAdjust = 0;
-
-    LogFunc(("[SD%RU8] fPosAdjustEnabled=%RTbool, cPosAdjustFrames=%RU16\n",
-             uSD, pThis->fPosAdjustEnabled, pThis->cPosAdjustFrames));
-
-    if (pThis->fPosAdjustEnabled) /* Is the position adjustment enabled at all? */
-    {
-        HDABDLE BDLE;
-        RT_ZERO(BDLE);
-
-        int rc2 = hdaR3BDLEFetch(pDevIns, &BDLE, pStreamShared->u64BDLBase, 0 /* Entry */);
-        AssertRC(rc2);
-
-        /* Note: Do *not* check if this BDLE aligns to the stream's frame size.
-         *       It can happen that this isn't the case on some guests, e.g.
-         *       on Windows with a 5.1 speaker setup.
-         *
-         *       The only thing which counts is that the stream's CBL value
-         *       properly aligns to the stream's frame size.
-         */
-
-        /* If no custom set position adjustment is set, apply some
-         * simple heuristics to detect the appropriate position adjustment. */
-        if (   !pThis->cPosAdjustFrames
-        /* Position adjustmenet buffer *must* have the IOC bit set! */
-            && hdaR3BDLENeedsInterrupt(&BDLE))
-        {
-            /** @todo Implement / use a (dynamic) table once this gets more complicated. */
-#ifdef VBOX_WITH_INTEL_HDA
-            /* Intel ICH / PCH: 1 frame. */
-            if (BDLE.Desc.u32BufSize == (uint32_t)(1 * pStreamR3->State.Mapping.cbGuestFrame))
-            {
-                cfPosAdjust = 1;
-            }
-            /* Intel Baytrail / Braswell: 32 frames. */
-            else if (BDLE.Desc.u32BufSize == (uint32_t)(32 * pStreamR3->State.Mapping.cbGuestFrame))
-            {
-                cfPosAdjust = 32;
-            }
-#endif
-        }
-        else /* Go with the set default. */
-            cfPosAdjust = pThis->cPosAdjustFrames;
-
-        if (cfPosAdjust)
-        {
-            /* Also adjust the number of fragments, as the position adjustment buffer
-             * does not count as an own fragment as such.
-             *
-             * This e.g. can happen on (newer) Ubuntu guests which use
-             * 4 (IOC) + 4408 (IOC) + 4408 (IOC) + 4408 (IOC) + 4404 (= 17632) bytes,
-             * where the first buffer (4) is used as position adjustment.
-             *
-             * Only skip a fragment if the whole buffer fragment is used for
-             * position adjustment.
-             */
-            if ((cfPosAdjust * pStreamR3->State.Mapping.cbGuestFrame) == BDLE.Desc.u32BufSize)
-                cTransferFragments--;
-
-            /* Initialize position adjustment counter. */
-            pStreamShared->State.cfPosAdjustDefault = cfPosAdjust;
-            pStreamShared->State.cfPosAdjustLeft    = cfPosAdjust;
-            LogRel2(("HDA: Position adjustment for stream #%RU8 active (%RU32 frames)\n", uSD, cfPosAdjust));
-        }
-    }
-
-    Log3Func(("[SD%RU8] cfPosAdjust=%RU32, cFragments=%RU32\n", uSD, cfPosAdjust, cTransferFragments));
+     * Create a DMA timer schedule.
+     */
+     rc = hdaR3StreamCreateSchedule(pStreamShared, cTransferFragments, cBufferIrqs, (uint32_t)cbTotal,
+                                   PDMAudioPropsMilliToBytes(&pStreamR3->State.Mapping.GuestProps, 100 /** @todo make configurable */),
+                                   PDMDevHlpTimerGetFreq(pDevIns, pStreamShared->hTimer),
+                                   &HostProps, &pStreamR3->State.Mapping.GuestProps);
+    if (RT_FAILURE(rc))
+        return rc;
+
+    pStreamShared->State.cbTransferSize = pStreamShared->State.aSchedule[0].cbPeriod;
+
+    /*
+     * Calculate the transfer Hz for use in the circular buffer calculation.
+     */
+    uint32_t cbMaxPeriod = 0;
+    uint32_t cbMinPeriod = UINT32_MAX;
+    uint32_t cPeriods    = 0;
+    for (uint32_t i = 0; i < pStreamShared->State.cSchedule; i++)
+    {
+        uint32_t cbPeriod = pStreamShared->State.aSchedule[i].cbPeriod;
+        cbMaxPeriod = RT_MAX(cbMaxPeriod, cbPeriod);
+        cbMinPeriod = RT_MIN(cbMinPeriod, cbPeriod);
+        cPeriods   += pStreamShared->State.aSchedule[i].cLoops;
+    }
+    uint64_t const cbTransferPerSec  = RT_MAX(PDMAudioPropsFramesToBytes(&pCfg->Props, pCfg->Props.uHz),
+                                              4096 /* zero div prevention: min is 6kHz, picked 4k in case I'm mistaken */);
+    unsigned uTransferHz = cbTransferPerSec * 1000 / cbMaxPeriod;
+    LogRel2(("HDA: Stream #%RU8 needs a %u.%03u Hz timer rate (period: %u..%u host bytes)\n",
+             uSD, uTransferHz / 1000, uTransferHz % 1000, cbMinPeriod, cbMaxPeriod));
+    uTransferHz /= 1000;
+
+    if (uTransferHz > 400) /* Anything above 400 Hz looks fishy -- tell the user. */
+        LogRelMax(32, ("HDA: Warning: Calculated transfer Hz rate for stream #%RU8 looks incorrect (%u), please re-run with audio debug mode and report a bug\n",
+                       uSD, uTransferHz));
+
+    pStreamShared->State.cbAvgTransfer = (uint32_t)(cbTotal + cPeriods - 1) / cPeriods;
 
     /*
@@ -429,301 +661,96 @@
     /* Assign the global device rate to the stream I/O timer as default. */
     pStreamShared->State.uTimerIoHz = pThis->uTimerHz;
-
-    /*
-     * Determine the transfer Hz the guest OS expects data transfer at.
-     *
-     * Guests also expect a very extact DMA timing for reading / writing audio data, so we run on a constant
-     * (virtual) rate which we expose to the guest.
-     *
-     * Data rate examples:
-     *   * Windows 10 @ 44,1kHz / 16-bit
-     *      2 channels (stereo):
-     *          * Default mode: 448 audio frames -> ~10.15ms = 1792 byte every ~10ms.
-     *          * Fast mode:    128 audio frames -> ~ 2.90ms =  512 byte every ~3ms.
-     *      6 channels (5.1 surround):
-     *          * Default mode: Same values as above!
-     */
-
-    /* Audio data per second the stream needs. */
-    const uint32_t cbDataPerSec = PDMAudioPropsMilliToBytes(&pStreamR3->State.Mapping.GuestProps, RT_MS_1SEC);
-
-    /* This is used to indicate whether we're done or should the uTimerIoHz as fallback. */
-    rc = VINF_SUCCESS;
-
-    /* The transfer Hz depend on the heuristics above, that is,
-       how often the guest expects to see a new data transfer. */
     ASSERT_GUEST_LOGREL_MSG_STMT(pStreamShared->State.uTimerIoHz,
                                  ("I/O timer Hz rate for stream #%RU8 is invalid\n", uSD),
                                  pStreamShared->State.uTimerIoHz = HDA_TIMER_HZ_DEFAULT);
-    unsigned uTransferHz = pStreamShared->State.uTimerIoHz;
-
-    LogRel2(("HDA: Stream #%RU8 needs %RU32 bytes/s audio data (from the guest).\n", uSD, cbDataPerSec));
-
-    if (pThis->fTransferHeuristicsEnabled) /* Are data transfer heuristics enabled? */
-    {
-        /* Don't take frames (as bytes) into account which are part of the position adjustment. */
-        uint32_t cbTransferHeuristicsPosAdjust = pStreamShared->State.cfPosAdjustDefault * pStreamR3->State.Mapping.cbGuestFrame;
-        uint32_t cbTransferHeuristics          = 0;
-        uint32_t cbTransferHeuristicsCur       = 0;
-        uint32_t cBufferIrqs                   = 0;
-        for (uint32_t i = 0; i < cTransferFragments; i++)
-        {
-            /** @todo wrong read type!   */
-            HDABDLEDESC bd = { 0, 0, 0 };
-            PDMDevHlpPhysRead(pDevIns, u64BDLBase + i * sizeof(HDABDLEDESC), &bd, sizeof(bd));
-
-            LogRel2(("HDA: Stream #%RU8 BDLE%03u: %#RX64 LB %#x %s (%#x)\n", uSD, i,
-                     bd.u64BufAddr, bd.u32BufSize, bd.fFlags & HDA_BDLE_F_IOC ? " IOC=1" : "", bd.fFlags));
-
-            /* Position adjustment (still) needed / active? */
-            if (cbTransferHeuristicsPosAdjust)
-            {
-                const uint32_t cbTransferHeuristicsPosAdjustMin = RT_MIN(cbTransferHeuristicsPosAdjust, bd.u32BufSize);
-
-                bd.u32BufSize                 -= cbTransferHeuristicsPosAdjustMin;
-                cbTransferHeuristicsPosAdjust -= cbTransferHeuristicsPosAdjustMin;
-            }
-
-            /* Anything left to process for the current BDLE after doing the position adjustment? */
-            if (bd.u32BufSize == 0)
-                continue;
-
-            /* Is an interrupt expected for the current BDLE? */
-            if (bd.fFlags & HDA_BDLE_F_IOC)
-            {
-                cbTransferHeuristicsCur += bd.u32BufSize;
-                if (   cbTransferHeuristicsCur == cbTransferHeuristics
-                    || !cbTransferHeuristics)
-                    cbTransferHeuristics = cbTransferHeuristicsCur;
-                else
-                {
-                    /** @todo r=bird: you need to find the smallest common denominator here, not
-                     *        just the minimum.  Ignoring this for now as windows has two equal
-                     *        sized buffers both with IOC set. */
-                    LogRelMax(32, ("HDA: Uneven IRQ buffer config; i=%u cbCur=%#x cbMin=%#x.\n", i, cbTransferHeuristicsCur, cbTransferHeuristics));
-                    cbTransferHeuristics = RT_MIN(cbTransferHeuristicsCur, cbTransferHeuristics);
-                }
-                cbTransferHeuristicsCur = 0;
-                cBufferIrqs++;
-            }
-            else /* No interrupt expected -> add it to the former BDLE size. */
-                cbTransferHeuristicsCur += bd.u32BufSize;
-        }
-
-        /*
-         * If the guest doesn't use buffer IRQs or only has one, just split the total
-         * buffer length in half and use that as timer heuristics.  That gives the
-         * guest half a buffer to fill while we're processing the other half.
-         */
-        if (cBufferIrqs <= 1)
-            cbTransferHeuristics = pStreamShared->u32CBL / 2;
-
-        /* Paranoia (fall back on I/O timer Hz if this happens). */
-        if (cbTransferHeuristics >= 8)
-        {
-            ASSERT_GUEST_LOGREL_MSG(PDMAudioPropsIsSizeAligned(&pStreamR3->State.Mapping.GuestProps, cbTransferHeuristics),
-                                    ("We arrived at a misaligned transfer size for stream #%RU8: %#x (%u)\n",
-                                     uSD, cbTransferHeuristics, cbTransferHeuristics));
-
-            uint64_t const cTimerTicksPerSec = PDMDevHlpTimerGetFreq(pDevIns, pStreamShared->hTimer);
-
-            /* Convert the heuristics value to host side bytes and that's what we're calculating. */
-            cbTransferHeuristics = PDMAudioPropsBytesToFrames(&pStreamR3->State.Mapping.GuestProps, cbTransferHeuristics);
-            cbTransferHeuristics = PDMAudioPropsFramesToBytes(&pCfg->Props, cbTransferHeuristics);
-
-            uint64_t const cbTransferPerSec  = RT_MAX(PDMAudioPropsFramesToBytes(&pCfg->Props, pCfg->Props.uHz),
-                                                      4096 /* zero div prevention: min is 6kHz, picked 4k in case I'm mistaken */);
-
-            /* Make sure the period is 250ms (random value) or less, in case the guest
-               want to play the whole "Der Ring des Nibelungen" cycle in one go.  Just
-               halve the buffer till we get there. */
-            while (cbTransferHeuristics > 1024 && cbTransferHeuristics > cbTransferPerSec / 4)
-                cbTransferHeuristics = PDMAudioPropsFloorBytesToFrame(&pCfg->Props, cbTransferHeuristics / 2);
-
-            /* Set the transfer size per timer callout. (No chunking, so same.) */
-            pStreamShared->State.cbTransferSize  = cbTransferHeuristics;
-            pStreamShared->State.cbTransferChunk = cbTransferHeuristics;
-            ASSERT_GUEST_LOGREL_MSG(PDMAudioPropsIsSizeAligned(&pCfg->Props, cbTransferHeuristics),
-                                    ("We arrived at a misaligned transfer size for stream #%RU8: %#x (%u)\n",
-                                     uSD, cbTransferHeuristics, cbTransferHeuristics));
-
-            /* Convert to timer ticks. */
-            pStreamShared->State.cTicksPerByte = (cTimerTicksPerSec + cbTransferPerSec / 2) / cbTransferPerSec;
-            AssertStmt(pStreamShared->State.cTicksPerByte, pStreamShared->State.cTicksPerByte = 4096);
-
-            pStreamShared->State.cTransferTicks = (cTimerTicksPerSec * cbTransferHeuristics + cbTransferPerSec / 2)
-                                                / cbTransferPerSec;
-
-            /* Estimate timer HZ for the circular buffer setup. */
-            uTransferHz = cbTransferPerSec * 1000 / cbTransferHeuristics;
-            LogRel2(("HDA: Stream #%RU8 needs a data transfer at least every %RU64 ticks / %RU32 bytes / approx %u.%03u Hz\n",
-                     uSD, pStreamShared->State.cTransferTicks, cbTransferHeuristics, uTransferHz / 1000, uTransferHz % 1000));
-            uTransferHz /= 1000;
-
-            /* Indicate that we're done with period calculation. */
-            rc = VINF_ALREADY_INITIALIZED;
-        }
-    }
-
-    if (uTransferHz > 400) /* Anything above 400 Hz looks fishy -- tell the user. */
-        LogRelMax(32, ("HDA: Warning: Calculated transfer Hz rate for stream #%RU8 looks incorrect (%u), please re-run with audio debug mode and report a bug\n",
-                       uSD, uTransferHz));
 
     /* Set I/O scheduling hint for the backends. */
+    /** @todo r=bird: This is in the 'Device' portion, yet it's used by the
+     *        audio driver.  You would think stuff in the 'Device' part is
+     *        private to the device. */
     pCfg->Device.cMsSchedulingHint = RT_MS_1SEC / pStreamShared->State.uTimerIoHz;
     LogRel2(("HDA: Stream #%RU8 set scheduling hint for the backends to %RU32ms\n", uSD, pCfg->Device.cMsSchedulingHint));
 
-    if (rc != VINF_ALREADY_INITIALIZED && RT_SUCCESS(rc))
-    {
-        /*
-         * Transfer heuristics disabled or failed.
-         */
-        Assert(uTransferHz == pStreamShared->State.uTimerIoHz);
-        LogRel2(("HDA: Stream #%RU8 transfer timer and I/O timer rate is %u Hz.\n", uSD, uTransferHz));
-
-        /* Make sure that the chosen transfer Hz rate dividable by the stream's overall data rate. */
-        ASSERT_GUEST_LOGREL_MSG_STMT(cbDataPerSec % uTransferHz == 0,
-                                     ("Transfer data rate (%RU32 bytes/s) for stream #%RU8 does not fit to stream timing (%u Hz)\n",
-                                      cbDataPerSec, uSD, uTransferHz),
-                                     uTransferHz = HDA_TIMER_HZ_DEFAULT);
-
-        pStreamShared->State.cbTransferSize = (pStreamR3->State.Mapping.GuestProps.uHz * pStreamR3->State.Mapping.cbGuestFrame)
-                                            / uTransferHz;
-        ASSERT_GUEST_LOGREL_MSG_STMT(pStreamShared->State.cbTransferSize,
-                                     ("Transfer size for stream #%RU8 is invalid\n", uSD), rc = VERR_INVALID_PARAMETER);
+
+    /* Make sure to also update the stream's DMA counter (based on its current LPIB value). */
+    hdaR3StreamSetPositionAbs(pStreamShared, pDevIns, pThis, HDA_STREAM_REG(pThis, LPIB, uSD));
+
+#ifdef LOG_ENABLED
+    hdaR3BDLEDumpAll(pDevIns, pThis, pStreamShared->u64BDLBase, pStreamShared->u16LVI + 1);
+#endif
+
+    /*
+     * Set up internal ring buffer.
+     */
+
+    /* (Re-)Allocate the stream's internal DMA buffer,
+     * based on the timing *and* PCM properties we just got above. */
+    if (pStreamR3->State.pCircBuf)
+    {
+        RTCircBufDestroy(pStreamR3->State.pCircBuf);
+        pStreamR3->State.pCircBuf = NULL;
+    }
+    pStreamR3->State.offWrite = 0;
+    pStreamR3->State.offRead  = 0;
+
+    /*
+     * The default internal ring buffer size must be:
+     *
+     *      - Large enough for at least three periodic DMA transfers.
+     *
+     *        It is critically important that we don't experience underruns
+     *        in the DMA OUT code, because it will cause the buffer processing
+     *        to get skewed and possibly overlap with what the guest is updating.
+     *        At the time of writing (2021-03-05) there is no code for getting
+     *        back into sync there.
+     *
+     *      - Large enough for at least three I/O scheduling hints.
+     *
+     *        We want to lag behind a DMA period or two, but there must be
+     *        sufficent space for the AIO thread to get schedule and shuffle
+     *        data thru the mixer and onto the host audio hardware.
+     *
+     *      - Both above with plenty to spare.
+     *
+     * So, just take the longest of the two periods and multipling it by 6.
+     * We aren't not talking about very large base buffers heres, so size isn't
+     * an issue.
+     *
+     * Note: Use pCfg->Props as PCM properties here, as we only want to store the
+     *       samples we actually need, in other words, skipping the interleaved
+     *       channels we don't support / need to save space.
+     */
+    uint32_t msCircBuf = RT_MS_1SEC * 6 / RT_MIN(uTransferHz, pStreamShared->State.uTimerIoHz);
+    msCircBuf = RT_MAX(msCircBuf, pThis->msInitialDelay + RT_MS_1SEC * 6 / uTransferHz);
+
+    uint32_t cbCircBuf = PDMAudioPropsMilliToBytes(&pCfg->Props, msCircBuf);
+    LogRel2(("HDA: Stream #%RU8 default ring buffer size is %RU32 bytes / %RU64 ms\n",
+             uSD, cbCircBuf, PDMAudioPropsBytesToMilli(&pCfg->Props, cbCircBuf)));
+
+    uint32_t msCircBufCfg = hdaGetDirFromSD(uSD) == PDMAUDIODIR_IN ? pThis->cbCircBufInMs : pThis->cbCircBufOutMs;
+    if (msCircBufCfg) /* Anything set via CFGM? */
+    {
+        cbCircBuf = PDMAudioPropsMilliToBytes(&pCfg->Props, msCircBufCfg);
+        LogRel2(("HDA: Stream #%RU8 is using a custom ring buffer size of %RU32 bytes / %RU64 ms\n",
+                 uSD, cbCircBuf, PDMAudioPropsBytesToMilli(&pCfg->Props, cbCircBuf)));
+    }
+
+    /* Serious paranoia: */
+    ASSERT_GUEST_LOGREL_MSG_STMT(cbCircBuf % (pCfg->Props.cbSample * pCfg->Props.cChannels) == 0,
+                                 ("Ring buffer size (%RU32) for stream #%RU8 not aligned to the (host) frame size (%RU8)\n",
+                                  cbCircBuf, uSD, pCfg->Props.cbSample * pCfg->Props.cChannels),
+                                 rc = VERR_INVALID_PARAMETER);
+    ASSERT_GUEST_LOGREL_MSG_STMT(cbCircBuf, ("Ring buffer size for stream #%RU8 is invalid\n", uSD),
+                                 rc = VERR_INVALID_PARAMETER);
+    if (RT_SUCCESS(rc))
+    {
+        rc = RTCircBufCreate(&pStreamR3->State.pCircBuf, cbCircBuf);
         if (RT_SUCCESS(rc))
         {
             /*
-             * Calculate the bytes we need to transfer to / from the stream's DMA per iteration.
-             * This is bound to the device's Hz rate and thus to the (virtual) timing the device expects.
-             *
-             * As we don't do chunked transfers the moment, the chunk size equals the overall transfer size.
+             * Forward the timer frequency hint to TM as well for better accuracy on
+             * systems w/o preemption timers (also good for 'info timers').
              */
-            pStreamShared->State.cbTransferChunk = pStreamShared->State.cbTransferSize;
-            ASSERT_GUEST_LOGREL_MSG_STMT(pStreamShared->State.cbTransferChunk,
-                                         ("Transfer chunk for stream #%RU8 is invalid\n", uSD),
-                                         rc = VERR_INVALID_PARAMETER);
-            if (RT_SUCCESS(rc))
-            {
-                /* Make sure that the transfer chunk does not exceed the overall transfer size. */
-                AssertStmt(pStreamShared->State.cbTransferChunk <= pStreamShared->State.cbTransferSize,
-                           pStreamShared->State.cbTransferChunk = pStreamShared->State.cbTransferSize);
-
-                const uint64_t uTimerFreq  = PDMDevHlpTimerGetFreq(pDevIns, pStreamShared->hTimer);
-
-                const double cTicksPerHz   = uTimerFreq  / uTransferHz;
-
-                double       cTicksPerByte = cTicksPerHz / (double)pStreamShared->State.cbTransferChunk;
-                if (uTransferHz < 100)
-                    cTicksPerByte /= 100 / uTransferHz;
-                else
-                    cTicksPerByte *= uTransferHz / 100;
-                Assert(cTicksPerByte);
-
-#define HDA_ROUND_NEAREST(a_X) ((a_X) >= 0 ? (uint32_t)((a_X) + 0.5) : (uint32_t)((a_X) - 0.5))
-
-                /* Calculate the timer ticks per byte for this stream. */
-                pStreamShared->State.cTicksPerByte = HDA_ROUND_NEAREST(cTicksPerByte);
-                Assert(pStreamShared->State.cTicksPerByte);
-
-                const double cTransferTicks = pStreamShared->State.cbTransferChunk * cTicksPerByte;
-
-                /* Calculate timer ticks per transfer. */
-                pStreamShared->State.cTransferTicks = HDA_ROUND_NEAREST(cTransferTicks);
-                Assert(pStreamShared->State.cTransferTicks);
-#undef HDA_ROUND_NEAREST
-
-                LogRel2(("HDA: Stream #%RU8 is using %uHz I/O timer (%RU64 virtual ticks / Hz), stream Hz=%RU32, cTicksPerByte=%RU64, cTransferTicks=%RU64 -> cbTransferChunk=%RU32 (%RU64ms), cbTransferSize=%RU32 (%RU64ms)\n",
-                         uSD, pStreamShared->State.uTimerIoHz, (uint64_t)cTicksPerHz, pStreamR3->State.Mapping.GuestProps.uHz,
-                         pStreamShared->State.cTicksPerByte, pStreamShared->State.cTransferTicks,
-                         pStreamShared->State.cbTransferChunk, PDMAudioPropsBytesToMilli(&pStreamR3->State.Mapping.GuestProps, pStreamShared->State.cbTransferChunk),
-                         pStreamShared->State.cbTransferSize,  PDMAudioPropsBytesToMilli(&pStreamR3->State.Mapping.GuestProps, pStreamShared->State.cbTransferSize)));
-            }
-        }
-    }
-
-    if (RT_SUCCESS(rc))
-    {
-        /* Make sure to also update the stream's DMA counter (based on its current LPIB value). */
-        hdaR3StreamSetPositionAbs(pStreamShared, pDevIns, pThis, HDA_STREAM_REG(pThis, LPIB, uSD));
-
-#ifdef LOG_ENABLED
-        hdaR3BDLEDumpAll(pDevIns, pThis, pStreamShared->u64BDLBase, pStreamShared->u16LVI + 1);
-#endif
-
-        /*
-         * Set up internal ring buffer.
-         */
-
-        /* (Re-)Allocate the stream's internal DMA buffer,
-         * based on the timing *and* PCM properties we just got above. */
-        if (pStreamR3->State.pCircBuf)
-        {
-            RTCircBufDestroy(pStreamR3->State.pCircBuf);
-            pStreamR3->State.pCircBuf = NULL;
-        }
-        pStreamR3->State.offWrite = 0;
-        pStreamR3->State.offRead  = 0;
-
-        /*
-         * The default internal ring buffer size must be:
-         *
-         *      - Large enough for at least three periodic DMA transfers.
-         *
-         *        It is critically important that we don't experience underruns
-         *        in the DMA OUT code, because it will cause the buffer processing
-         *        to get skewed and possibly overlap with what the guest is updating.
-         *        At the time of writing (2021-03-05) there is no code for getting
-         *        back into sync there.
-         *
-         *      - Large enough for at least three I/O scheduling hints.
-         *
-         *        We want to lag behind a DMA period or two, but there must be
-         *        sufficent space for the AIO thread to get schedule and shuffle
-         *        data thru the mixer and onto the host audio hardware.
-         *
-         *      - Both above with plenty to spare.
-         *
-         * So, just take the longest of the two periods and multipling it by 6.
-         * We aren't not talking about very large base buffers heres, so size isn't
-         * an issue.
-         *
-         * Note: Use pCfg->Props as PCM properties here, as we only want to store the
-         *       samples we actually need, in other words, skipping the interleaved
-         *       channels we don't support / need to save space.
-         */
-        uint32_t msCircBuf = RT_MS_1SEC * 6 / RT_MIN(uTransferHz, pStreamShared->State.uTimerIoHz);
-        msCircBuf = RT_MAX(msCircBuf, pThis->msInitialDelay + RT_MS_1SEC * 6 / uTransferHz);
-
-        uint32_t cbCircBuf = PDMAudioPropsMilliToBytes(&pCfg->Props, msCircBuf);
-        LogRel2(("HDA: Stream #%RU8 default ring buffer size is %RU32 bytes / %RU64 ms\n",
-                 uSD, cbCircBuf, PDMAudioPropsBytesToMilli(&pCfg->Props, cbCircBuf)));
-
-        uint32_t msCircBufCfg = hdaGetDirFromSD(uSD) == PDMAUDIODIR_IN ? pThis->cbCircBufInMs : pThis->cbCircBufOutMs;
-        if (msCircBufCfg) /* Anything set via CFGM? */
-        {
-            cbCircBuf = PDMAudioPropsMilliToBytes(&pCfg->Props, msCircBufCfg);
-            LogRel2(("HDA: Stream #%RU8 is using a custom ring buffer size of %RU32 bytes / %RU64 ms\n",
-                     uSD, cbCircBuf, PDMAudioPropsBytesToMilli(&pCfg->Props, cbCircBuf)));
-        }
-
-        /* Serious paranoia: */
-        ASSERT_GUEST_LOGREL_MSG_STMT(cbCircBuf % (pCfg->Props.cbSample * pCfg->Props.cChannels) == 0,
-                                     ("Ring buffer size (%RU32) for stream #%RU8 not aligned to the (host) frame size (%RU8)\n",
-                                      cbCircBuf, uSD, pCfg->Props.cbSample * pCfg->Props.cChannels),
-                                     rc = VERR_INVALID_PARAMETER);
-        ASSERT_GUEST_LOGREL_MSG_STMT(cbCircBuf, ("Ring buffer size for stream #%RU8 is invalid\n", uSD),
-                                     rc = VERR_INVALID_PARAMETER);
-        if (RT_SUCCESS(rc))
-        {
-            rc = RTCircBufCreate(&pStreamR3->State.pCircBuf, cbCircBuf);
-            if (RT_SUCCESS(rc))
-            {
-                /*
-                 * Forward the timer frequency hint to TM as well for better accuracy on
-                 * systems w/o preemption timers (also good for 'info timers').
-                 */
-                PDMDevHlpTimerSetFrequencyHint(pDevIns, pStreamShared->hTimer, uTransferHz);
-            }
+            PDMDevHlpTimerSetFrequencyHint(pDevIns, pStreamShared->hTimer, uTransferHz);
         }
     }
@@ -734,5 +761,6 @@
 #ifdef VBOX_WITH_DTRACE
     VBOXDD_HDA_STREAM_SETUP((uint32_t)uSD, rc, pStreamShared->State.Cfg.Props.uHz,
-                            pStreamShared->State.cTransferTicks, pStreamShared->State.cbTransferSize);
+                            pStreamShared->State.aSchedule[pStreamShared->State.cSchedule - 1].cPeriodTicks,
+                            pStreamShared->State.aSchedule[pStreamShared->State.cSchedule - 1].cbPeriod);
 #endif
     return rc;
@@ -790,7 +818,4 @@
     pStreamR3->pMixSink = hdaR3GetDefaultSink(pThisCC, uSD);
 
-    /* Reset position adjustment counter. */
-    pStreamShared->State.cfPosAdjustLeft = pStreamShared->State.cfPosAdjustDefault;
-
     /* Reset transfer stuff. */
     pStreamShared->State.cTransferPendingInterrupts = 0;
@@ -804,6 +829,13 @@
     pStreamShared->State.tsStart          = 0;
 
-    RT_ZERO(pStreamShared->State.BDLE);
-    pStreamShared->State.uCurBDLE = 0;
+    RT_ZERO(pStreamShared->State.aBdl);
+    RT_ZERO(pStreamShared->State.aSchedule);
+    pStreamShared->State.offCurBdle        = 0;
+    pStreamShared->State.cBdles            = 0;
+    pStreamShared->State.idxCurBdle        = 0;
+    pStreamShared->State.cSchedulePrologue = 0;
+    pStreamShared->State.cSchedule         = 0;
+    pStreamShared->State.idxSchedule       = 0;
+    pStreamShared->State.idxScheduleLoop   = 0;
 
     if (pStreamR3->State.pCircBuf)
@@ -1148,11 +1180,91 @@
 
 /**
- * Transfers data of an HDA stream according to its usage (input / output).
- *
- * For an SDO (output) stream this means reading DMA data from the device to
- * the HDA stream's internal FIFO buffer.
- *
- * For an SDI (input) stream this is reading audio data from the HDA stream's
- * internal FIFO buffer and writing it as DMA data to the device.
+ * Get the current address and number of bytes left in the current BDLE.
+ *
+ * @returns The current physical address.
+ * @param   pStreamShared   The stream to check.
+ * @param   pcbLeft         The number of bytes left at the returned address.
+ */
+DECLINLINE(RTGCPHYS) hdaR3StreamDmaBufGet(PHDASTREAM pStreamShared, uint32_t *pcbLeft)
+{
+    uint8_t        idxBdle    = pStreamShared->State.idxCurBdle;
+    AssertStmt(idxBdle < pStreamShared->State.cBdles, idxBdle = 0);
+
+    uint32_t const cbCurBdl   = pStreamShared->State.aBdl[idxBdle].cb;
+    uint32_t       offCurBdle = pStreamShared->State.offCurBdle;
+    AssertStmt(pStreamShared->State.offCurBdle <= cbCurBdl, offCurBdle = cbCurBdl);
+
+    *pcbLeft = cbCurBdl - offCurBdle;
+    return pStreamShared->State.aBdl[idxBdle].GCPhys + offCurBdle;
+}
+
+/**
+ * Get the size of the current BDLE.
+ *
+ * @returns The size (in bytes).
+ * @param   pStreamShared   The stream to check.
+ */
+DECLINLINE(RTGCPHYS) hdaR3StreamDmaBufGetSize(PHDASTREAM pStreamShared)
+{
+    uint8_t idxBdle = pStreamShared->State.idxCurBdle;
+    AssertStmt(idxBdle < pStreamShared->State.cBdles, idxBdle = 0);
+    return pStreamShared->State.aBdl[idxBdle].cb;
+}
+
+/**
+ * Checks if the current BDLE is completed.
+ *
+ * @retval  true if complete
+ * @retval  false if not.
+ * @param   pStreamShared   The stream to check.
+ */
+DECLINLINE(bool) hdaR3StreamDmaBufIsComplete(PHDASTREAM pStreamShared)
+{
+    uint8_t const  idxBdle    = pStreamShared->State.idxCurBdle;
+    AssertReturn(idxBdle < pStreamShared->State.cBdles, true);
+
+    uint32_t const cbCurBdl   = pStreamShared->State.aBdl[idxBdle].cb;
+    uint32_t const offCurBdle = pStreamShared->State.offCurBdle;
+    Assert(offCurBdle <= cbCurBdl);
+    return offCurBdle >= cbCurBdl;
+}
+
+/**
+ * Checks if the current BDLE needs a completion IRQ.
+ *
+ * @retval  true if IRQ is needed.
+ * @retval  false if not.
+ * @param   pStreamShared   The stream to check.
+ */
+DECLINLINE(bool) hdaR3StreamDmaBufNeedsIrq(PHDASTREAM pStreamShared)
+{
+    uint8_t const idxBdle = pStreamShared->State.idxCurBdle;
+    AssertReturn(idxBdle < pStreamShared->State.cBdles, false);
+    return (pStreamShared->State.aBdl[idxBdle].fFlags & HDA_BDLE_F_IOC) != 0;
+}
+
+/**
+ * Advances the DMA engine to the next BDLE.
+ *
+ * @param   pStreamShared   The stream which DMA engine is to be updated.
+ */
+DECLINLINE(void) hdaR3StreamDmaBufAdvanceToNext(PHDASTREAM pStreamShared)
+{
+    uint8_t idxBdle = pStreamShared->State.idxCurBdle;
+    Assert(pStreamShared->State.offCurBdle == pStreamShared->State.aBdl[idxBdle].cb);
+
+    if (idxBdle < pStreamShared->State.cBdles - 1)
+        idxBdle++;
+    else
+        idxBdle = 0;
+    pStreamShared->State.idxCurBdle = idxBdle;
+    pStreamShared->State.offCurBdle = 0;
+}
+
+/**
+ * Does DMA transfer for an HDA input stream.
+ *
+ * Reads audio data from the HDA stream's internal DMA buffer and writing to
+ * guest memory.
  *
  * @returns IPRT status code.
@@ -1164,9 +1276,10 @@
  * @param   cbToProcessMax      How much data (in bytes) to process as maximum.
  */
-static int hdaR3StreamTransfer(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATER3 pThisCC, PHDASTREAM pStreamShared,
-                               PHDASTREAMR3 pStreamR3, uint32_t cbToProcessMax)
+static int hdaR3StreamDoDmaInput(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATER3 pThisCC, PHDASTREAM pStreamShared,
+                                 PHDASTREAMR3 pStreamR3, uint32_t cbToProcessMax)
 {
     uint8_t const uSD = pStreamShared->u8SD;
     LogFlowFunc(("ENTER - #%u cbToProcessMax=%#x\n", uSD, cbToProcessMax));
+    Assert(hdaGetDirFromSD(uSD) != PDMAUDIODIR_OUT);
 
     if (RT_LIKELY(cbToProcessMax >= pStreamShared->State.cbTransferSize))
@@ -1174,5 +1287,4 @@
     else
     {
-        Assert(hdaGetDirFromSD(uSD) != PDMAUDIODIR_OUT /* Handled by caller */);
         /** @todo account for this or something so we can try get back in sync
          *        later... */
@@ -1243,17 +1355,8 @@
     /* Transfer sanity checks. */
     Assert(pStreamShared->State.cbTransferSize);
-    Assert(pStreamShared->State.cbTransferChunk <= pStreamShared->State.cbTransferSize);
 
     int rc = VINF_SUCCESS;
 
-    /* Fetch first / next BDL entry. */
-    PHDABDLE pBDLE = &pStreamShared->State.BDLE;
-    if (hdaR3BDLEIsComplete(pBDLE))
-    {
-        rc = hdaR3BDLEFetch(pDevIns, pBDLE, pStreamShared->u64BDLBase, pStreamShared->State.uCurBDLE);
-        AssertRC(rc);
-    }
-
-    uint32_t cbToProcess = RT_MIN(pStreamShared->State.cbTransferSize, pStreamShared->State.cbTransferChunk);
+    uint32_t cbToProcess = pStreamShared->State.cbTransferSize;
 
     Assert(cbToProcess);                                                     /* Nothing to process when there should be data. Accounting bug? */
@@ -1287,252 +1390,83 @@
 
         /* Limit the chunk to the remaining data of the current BDLE. */
-        cbChunk = RT_MIN(cbChunk, pBDLE->Desc.u32BufSize - pBDLE->State.u32BufOff);
-
-        /* If there are position adjustment frames left to be processed,
-         * make sure that we process them first as a whole. */
-        if (pStreamShared->State.cfPosAdjustLeft)
-            cbChunk = RT_MIN(cbChunk, (uint32_t)pStreamShared->State.cfPosAdjustLeft * pStreamR3->State.Mapping.cbGuestFrame);
+        uint32_t cbDmaBuf = 0;
+        RTGCPHYS GCPhys = hdaR3StreamDmaBufGet(pStreamShared, &cbDmaBuf);
+        cbChunk = RT_MIN(cbChunk, cbDmaBuf);
 
         if (!cbChunk)
             break;
 
-        uint32_t   cbDMA    = 0;
+        STAM_PROFILE_START(&pThis->StatIn, a);
+
+        /*
+         * Copy out of the internal DMA buffer.
+         * @todo eliminate extra copy.
+         */
         PRTCIRCBUF pCircBuf = pStreamR3->State.pCircBuf;
         uint8_t   *pabFIFO  = pStreamShared->abFIFO;
-
-        if (hdaGetDirFromSD(uSD) == PDMAUDIODIR_IN) /* Input (SDI). */
+        uint32_t   cbDMAWritten = 0;
+        uint32_t   cbDMAToWrite = cbChunk;
+
+        /** @todo Do we need interleaving streams support here as well?
+         *        Never saw anything else besides mono/stereo mics (yet). */
+        while (cbDMAToWrite)
         {
-            STAM_PROFILE_START(&pThis->StatIn, a);
-
-            uint32_t cbDMAWritten = 0;
-            uint32_t cbDMAToWrite = cbChunk;
-
-            /** @todo Do we need interleaving streams support here as well?
-             *        Never saw anything else besides mono/stereo mics (yet). */
-            while (cbDMAToWrite)
-            {
-                void *pvBuf; size_t cbBuf;
-                RTCircBufAcquireReadBlock(pCircBuf, cbDMAToWrite, &pvBuf, &cbBuf);
-
-                if (   !cbBuf
-                    && !RTCircBufUsed(pCircBuf))
-                    break;
-
-                memcpy(pabFIFO + cbDMAWritten, pvBuf, cbBuf);
+            void *pvBuf; size_t cbBuf;
+            RTCircBufAcquireReadBlock(pCircBuf, cbDMAToWrite, &pvBuf, &cbBuf);
+
+            if (   !cbBuf
+                && !RTCircBufUsed(pCircBuf))
+                break;
+
+            memcpy(pabFIFO + cbDMAWritten, pvBuf, cbBuf);
 #ifdef VBOX_WITH_DTRACE
-                VBOXDD_HDA_STREAM_DMA_IN((uint32_t)uSD, (uint32_t)cbBuf, pStreamR3->State.offRead);
+            VBOXDD_HDA_STREAM_DMA_IN((uint32_t)uSD, (uint32_t)cbBuf, pStreamR3->State.offRead);
 #endif
-                pStreamR3->State.offRead += cbBuf;
-
-                RTCircBufReleaseReadBlock(pCircBuf, cbBuf);
-
-                Assert(cbDMAToWrite >= cbBuf);
-                cbDMAToWrite -= (uint32_t)cbBuf;
-                cbDMAWritten += (uint32_t)cbBuf;
-                Assert(cbDMAWritten <= cbChunk);
-            }
-
-            if (cbDMAToWrite)
-            {
-                LogRel2(("HDA: FIFO underflow for stream #%RU8 (%RU32 bytes outstanding)\n", uSD, cbDMAToWrite));
-
-                Assert(cbChunk == cbDMAWritten + cbDMAToWrite);
-                memset((uint8_t *)pabFIFO + cbDMAWritten, 0, cbDMAToWrite);
-                cbDMAWritten = cbChunk;
-            }
-
-            rc = hdaR3DMAWrite(pDevIns, pThis, pStreamShared, pStreamR3, pabFIFO, cbDMAWritten, &cbDMA /* pcbWritten */);
-            if (RT_FAILURE(rc))
-                LogRel(("HDA: Writing to stream #%RU8 DMA failed with %Rrc\n", uSD, rc));
-
-            STAM_PROFILE_STOP(&pThis->StatIn, a);
+            pStreamR3->State.offRead += cbBuf;
+
+            RTCircBufReleaseReadBlock(pCircBuf, cbBuf);
+
+            Assert(cbDMAToWrite >= cbBuf);
+            cbDMAToWrite -= (uint32_t)cbBuf;
+            cbDMAWritten += (uint32_t)cbBuf;
+            Assert(cbDMAWritten <= cbChunk);
         }
-        else if (hdaGetDirFromSD(uSD) == PDMAUDIODIR_OUT) /* Output (SDO). */
+
+        if (cbDMAToWrite)
         {
-            STAM_PROFILE_START(&pThis->StatOut, a);
-
-            rc = hdaR3DMARead(pDevIns, pThis, pStreamShared, pStreamR3, pabFIFO, cbChunk, &cbDMA /* pcbRead */);
-            if (RT_SUCCESS(rc))
-            {
-                const uint32_t cbFree = (uint32_t)RTCircBufFree(pCircBuf);
-
-                /*
-                 * Most guests don't use different stream frame sizes than
-                 * the default one, so save a bit of CPU time and don't go into
-                 * the frame extraction code below.
-                 *
-                 * Only macOS guests need the frame extraction branch below at the moment AFAIK.
-                 */
-                if (pStreamR3->State.Mapping.cbGuestFrame == HDA_FRAME_SIZE_DEFAULT)
-                {
-                    uint32_t cbDMARead = 0;
-                    uint32_t cbDMALeft = RT_MIN(cbDMA, cbFree);
-
-                    while (cbDMALeft)
-                    {
-                        void *pvBuf; size_t cbBuf;
-                        RTCircBufAcquireWriteBlock(pCircBuf, cbDMALeft, &pvBuf, &cbBuf);
-
-                        if (cbBuf)
-                        {
-                            memcpy(pvBuf, pabFIFO + cbDMARead, cbBuf);
-                            cbDMARead += (uint32_t)cbBuf;
-                            cbDMALeft -= (uint32_t)cbBuf;
-#ifdef VBOX_WITH_DTRACE
-                            VBOXDD_HDA_STREAM_DMA_OUT((uint32_t)uSD, (uint32_t)cbBuf, pStreamR3->State.offWrite);
-#endif
-                            pStreamR3->State.offWrite += cbBuf;
-                        }
-
-                        RTCircBufReleaseWriteBlock(pCircBuf, cbBuf);
-                    }
-                }
-                else
-                {
-                    /*
-                     * The following code extracts the required audio stream (channel) data
-                     * of non-interleaved *and* interleaved audio streams.
-                     *
-                     * We by default only support 2 channels with 16-bit samples (HDA_FRAME_SIZE),
-                     * but an HDA audio stream can have interleaved audio data of multiple audio
-                     * channels in such a single stream ("AA,AA,AA vs. AA,BB,AA,BB").
-                     *
-                     * So take this into account by just handling the first channel in such a stream ("A")
-                     * and just discard the other channel's data.
-                     *
-                     * I know, the following code is horribly slow, but seems to work for now.
-                     */
-                    /** @todo Optimize channel data extraction! Use some SSE(3) / intrinsics? */
-                    for (unsigned m = 0; m < pStreamR3->State.Mapping.cMappings; m++)
-                    {
-                        const uint32_t cbFrame  = pStreamR3->State.Mapping.cbGuestFrame;
-
-                        Assert(cbFree >= cbDMA);
-
-                        PPDMAUDIOSTREAMMAP pMap = &pStreamR3->State.Mapping.paMappings[m];
-                        AssertPtr(pMap);
-
-                        Log3Func(("Mapping #%u: Start (cbDMA=%RU32, cbFrame=%RU32, offNext=%RU32)\n",
-                                  m, cbDMA, cbFrame, pMap->offNext));
-
-
-                        /* Skip the current DMA chunk if the chunk is smaller than what the current stream mapping needs to read
-                         * the next associated frame (pointed to at pMap->cbOff).
-                         *
-                         * This can happen if the guest did not come up with enough data within a certain time period, especially
-                         * when using multi-channel speaker (> 2 channels [stereo]) setups. */
-                        if (pMap->offNext > cbChunk)
-                        {
-                            Log2Func(("Mapping #%u: Skipped (cbChunk=%RU32, cbMapOff=%RU32)\n", m, cbChunk, pMap->offNext));
-                            continue;
-                        }
-
-                        uint8_t *pbSrcBuf = pabFIFO;
-                        size_t cbSrcOff   = pMap->offNext;
-
-                        for (unsigned i = 0; i < cbDMA / cbFrame; i++)
-                        {
-                            void *pvDstBuf; size_t cbDstBuf;
-                            RTCircBufAcquireWriteBlock(pCircBuf, pMap->cbStep, &pvDstBuf, &cbDstBuf);
-
-                            Assert(cbDstBuf >= pMap->cbStep);
-
-                            if (cbDstBuf)
-                            {
-                                Log3Func(("Mapping #%u: Frame #%02u:    cbStep=%u, offFirst=%u, offNext=%u, cbDstBuf=%u, cbSrcOff=%u\n",
-                                          m, i, pMap->cbStep, pMap->offFirst, pMap->offNext, cbDstBuf, cbSrcOff));
-
-                                memcpy(pvDstBuf, pbSrcBuf + cbSrcOff, cbDstBuf);
-
-#if 0 /* Too slow, even for release builds, so disabled it. */
-                                if (pStreamR3->Dbg.Runtime.fEnabled)
-                                    DrvAudioHlpFileWrite(pStreamR3->Dbg.Runtime.pFileDMAMapped, pvDstBuf, cbDstBuf,
-                                                         0 /* fFlags */);
-#endif
-                                Assert(cbSrcOff <= cbDMA);
-                                if (cbSrcOff + cbFrame + pMap->offFirst<= cbDMA)
-                                    cbSrcOff += cbFrame + pMap->offFirst;
-
-#ifdef VBOX_WITH_DTRACE
-                                VBOXDD_HDA_STREAM_DMA_OUT((uint32_t)uSD, (uint32_t)cbDstBuf, pStreamR3->State.offWrite);
-#endif
-                                Log3Func(("Mapping #%u: Frame #%02u:    -> cbSrcOff=%zu\n", m, i, cbSrcOff));
-                                pStreamR3->State.offWrite += cbDstBuf;
-                            }
-
-                            RTCircBufReleaseWriteBlock(pCircBuf, cbDstBuf);
-                        }
-
-                        Log3Func(("Mapping #%u: End cbSize=%u, cbDMA=%RU32, cbSrcOff=%zu\n",
-                                  m, pMap->cbStep, cbDMA, cbSrcOff));
-
-                        Assert(cbSrcOff <= cbDMA);
-
-                        const uint32_t cbSrcLeft = cbDMA - (uint32_t)cbSrcOff;
-                        if (cbSrcLeft)
-                        {
-                            Log3Func(("Mapping #%u: cbSrcLeft=%RU32\n", m, cbSrcLeft));
-
-                            if (cbSrcLeft >= pMap->cbStep)
-                            {
-                                void *pvDstBuf; size_t cbDstBuf;
-                                RTCircBufAcquireWriteBlock(pCircBuf, pMap->cbStep, &pvDstBuf, &cbDstBuf);
-
-                                Assert(cbDstBuf >= pMap->cbStep);
-
-                                if (cbDstBuf)
-                                {
-                                    memcpy(pvDstBuf, pbSrcBuf + cbSrcOff, cbDstBuf);
-#ifdef VBOX_WITH_DTRACE
-                                    VBOXDD_HDA_STREAM_DMA_OUT((uint32_t)uSD, (uint32_t)cbDstBuf, pStreamR3->State.offWrite);
-#endif
-                                    pStreamR3->State.offWrite += cbDstBuf;
-                                }
-
-                                RTCircBufReleaseWriteBlock(pCircBuf, cbDstBuf);
-                            }
-
-                            Assert(pMap->cbFrame >= cbSrcLeft);
-                            pMap->offNext = pMap->cbFrame - cbSrcLeft;
-                        }
-                        else
-                            pMap->offNext = 0;
-
-                        Log3Func(("Mapping #%u finish (cbSrcOff=%zu, offNext=%zu)\n", m, cbSrcOff, pMap->offNext));
-                    }
-                }
-            }
-            else
-                LogRel(("HDA: Reading from stream #%RU8 DMA failed with %Rrc\n", uSD, rc));
-
-            STAM_PROFILE_STOP(&pThis->StatOut, a);
+            LogRel2(("HDA: FIFO underflow for stream #%RU8 (%RU32 bytes outstanding)\n", uSD, cbDMAToWrite));
+
+            Assert(cbChunk == cbDMAWritten + cbDMAToWrite);
+            memset((uint8_t *)pabFIFO + cbDMAWritten, 0, cbDMAToWrite);
+            cbDMAWritten = cbChunk;
         }
 
-        else /** @todo Handle duplex streams? */
-            AssertFailed();
-
-        if (cbDMA)
+        /*
+         * Write to the guest.
+         */
+        if (RT_LIKELY(!pStreamR3->Dbg.Runtime.fEnabled))
+        { /* likely */ }
+        else
+            DrvAudioHlpFileWrite(pStreamR3->Dbg.Runtime.pFileDMARaw, pabFIFO, cbDMAWritten, 0 /* fFlags */);
+
+        rc = PDMDevHlpPCIPhysWrite(pDevIns, GCPhys, pabFIFO, cbDMAWritten);
+        AssertLogRelMsgRC(rc, ("HDA: Write at %RGp LB %#x -> %Rrc\n", GCPhys, cbDMAWritten, rc));
+
+        STAM_COUNTER_ADD(&pThis->StatBytesWritten, cbChunk);
+
+        STAM_PROFILE_STOP(&pThis->StatIn, a);
+
+        /* Advance. */
+        pStreamShared->State.offCurBdle += cbDMAWritten;
+        Assert(cbLeft >= cbDMAWritten);
+        cbLeft        -= cbDMAWritten;
+        cbProcessed   += cbDMAWritten;
+
+        if (hdaR3StreamDmaBufIsComplete(pStreamShared))
         {
-            /* We always increment the position of DMA buffer counter because we're always reading
-             * into an intermediate DMA buffer. */
-            pBDLE->State.u32BufOff += (uint32_t)cbDMA;
-            Assert(pBDLE->State.u32BufOff <= pBDLE->Desc.u32BufSize);
-
-            /* Are we done doing the position adjustment?
-             * Only then do the transfer accounting .*/
-            if (pStreamShared->State.cfPosAdjustLeft == 0)
-            {
-                Assert(cbLeft >= cbDMA);
-                cbLeft        -= cbDMA;
-
-                cbProcessed   += cbDMA;
-            }
-
-            Log3Func(("[SD%RU8] cbDMA=%RU32 -> %R[bdle]\n", uSD, cbDMA, pBDLE));
-        }
-
-        if (hdaR3BDLEIsComplete(pBDLE))
-        {
-            Log3Func(("[SD%RU8] Completed %R[bdle]\n", uSD, pBDLE));
+            Log3Func(("[SD%RU8] Completed BDLE%u %#RX64 LB %#RX32 fFlags=%#x (wrote %#x)\n",
+                      uSD, pStreamShared->State.idxCurBdle, pStreamShared->State.aBdl[pStreamShared->State.idxCurBdle].GCPhys,
+                      pStreamShared->State.aBdl[pStreamShared->State.idxCurBdle].cb,
+                      pStreamShared->State.aBdl[pStreamShared->State.idxCurBdle].fFlags, cbDMAWritten));
 
             /* Make sure to also update the wall clock when a BDLE is complete.
@@ -1541,5 +1475,5 @@
                              hdaWalClkGetCurrent(pThis)
                            + hdaR3StreamPeriodFramesToWalClk(pPeriod,
-                                                               pBDLE->Desc.u32BufSize
+                                                               hdaR3StreamDmaBufGetSize(pStreamShared)
                                                              / pStreamR3->State.Mapping.cbGuestFrame),
                            false /* fForce */);
@@ -1553,14 +1487,8 @@
              *                      Not doing this at the right time will result in ugly sound crackles!
              */
-            hdaR3StreamSetPositionAdd(pStreamShared, pDevIns, pThis, pBDLE->Desc.u32BufSize);
-
-                /* Does the current BDLE require an interrupt to be sent? */
-            if (   hdaR3BDLENeedsInterrupt(pBDLE)
-                /* Are we done doing the position adjustment?
-                 * It can happen that a BDLE which is handled while doing the
-                 * position adjustment requires an interrupt on completion (IOC) being set.
-                 *
-                 * In such a case we need to skip such an interrupt and just move on. */
-                && pStreamShared->State.cfPosAdjustLeft == 0)
+            hdaR3StreamSetPositionAdd(pStreamShared, pDevIns, pThis, hdaR3StreamDmaBufGetSize(pStreamShared));
+
+            /* Does the current BDLE require an interrupt to be sent? */
+            if (hdaR3StreamDmaBufNeedsIrq(pStreamShared))
             {
                 /* If the IOCE ("Interrupt On Completion Enable") bit of the SDCTL register is set
@@ -1573,10 +1501,5 @@
                     {
                         pStreamShared->State.cTransferPendingInterrupts = 1;
-
-                        AssertMsg(pStreamShared->State.cTransferPendingInterrupts <= 32,
-                                  ("Too many pending interrupts (%RU8) for stream #%RU8\n",
-                                   pStreamShared->State.cTransferPendingInterrupts, uSD));
-
-                        Log3Func(("[SD%RU8] Scheduling interrupt (now %RU8 total)\n", uSD, pStreamShared->State.cTransferPendingInterrupts));
+                        Log3Func(("[SD%RU8] Scheduling interrupt\n", uSD));
 
                         /*
@@ -1602,21 +1525,11 @@
             }
 
-            if (pStreamShared->State.uCurBDLE == pStreamShared->u16LVI)
-            {
-                pStreamShared->State.uCurBDLE = 0;
-            }
-            else
-                pStreamShared->State.uCurBDLE++;
-
-            /* Fetch the next BDLE entry. */
-            hdaR3BDLEFetch(pDevIns, pBDLE, pStreamShared->u64BDLBase, pStreamShared->State.uCurBDLE);
+            hdaR3StreamDmaBufAdvanceToNext(pStreamShared);
         }
-
-        /* Do the position adjustment accounting. */
-        pStreamShared->State.cfPosAdjustLeft -=
-            RT_MIN(pStreamShared->State.cfPosAdjustLeft, cbDMA / pStreamR3->State.Mapping.cbGuestFrame);
-
-        if (RT_FAILURE(rc))
-            break;
+        else
+            Log3Func(("[SD%RU8] Not completed BDLE%u %#RX64 LB %#RX32 fFlags=%#x: off=%#RX32, wrote %#x\n",
+                      uSD, pStreamShared->State.idxCurBdle, pStreamShared->State.aBdl[pStreamShared->State.idxCurBdle].GCPhys,
+                      pStreamShared->State.aBdl[pStreamShared->State.idxCurBdle].cb,
+                      pStreamShared->State.aBdl[pStreamShared->State.idxCurBdle].fFlags, pStreamShared->State.offCurBdle, cbDMAWritten));
     }
 
@@ -1628,11 +1541,6 @@
     Assert(cbLeft      == 0);
 
-    /* Only do the data accounting if we don't have to do any position
-     * adjustment anymore. */
-    if (pStreamShared->State.cfPosAdjustLeft == 0)
-    {
-        hdaR3StreamPeriodInc(pPeriod, RT_MIN(cbProcessed / pStreamR3->State.Mapping.cbGuestFrame,
-                                             hdaR3StreamPeriodGetRemainingFrames(pPeriod)));
-    }
+    hdaR3StreamPeriodInc(pPeriod, RT_MIN(cbProcessed / pStreamR3->State.Mapping.cbGuestFrame,
+                                         hdaR3StreamPeriodGetRemainingFrames(pPeriod)));
 
     const bool fTransferComplete  = cbLeft == 0;
@@ -1661,18 +1569,11 @@
     }
 
-    /* Set the next transfer timing slot.
-     * This must happen at a constant rate. */
-    pStreamShared->State.tsTransferNext = tsNow + pStreamShared->State.cTransferTicks;
-
     /* Always update this timestamp, no matter what pStreamShared->State.tsTransferNext is. */
     pStreamShared->State.tsTransferLast = tsNow;
 
-    Log3Func(("[SD%RU8] %R[bdle] -- %#RX32/%#RX32 @ %#RX64\n", uSD, pBDLE, cbProcessed, pStreamShared->State.cbTransferSize,
+    Log3Func(("[SD%RU8] %#RX32/%#RX32 @ %#RX64\n", uSD, cbProcessed, pStreamShared->State.cbTransferSize,
               (hdaGetDirFromSD(uSD) == PDMAUDIODIR_OUT ? pStreamR3->State.offWrite : pStreamR3->State.offRead) - cbProcessed));
     Log3Func(("[SD%RU8] fTransferComplete=%RTbool, cTransferPendingInterrupts=%RU8\n",
               uSD, fTransferComplete, pStreamShared->State.cTransferPendingInterrupts));
-    Log3Func(("[SD%RU8] tsNow=%RU64, tsTransferNext=%RU64 (in %RU64 ticks)\n",
-              uSD, tsNow, pStreamShared->State.tsTransferNext,
-              pStreamShared->State.tsTransferNext ? pStreamShared->State.tsTransferNext - tsNow : 0));
 
     LogFlowFuncLeave();
@@ -1697,8 +1598,7 @@
  * @param   pStreamR3           HDA stream to update (ring-3).
  * @param   cbToProduce         The max amount of data to produce (i.e. put into
- *                              the circular buffer).  (Caller should already
- *                              have made sure this is at least the size of one
- *                              DMA timer period, so this function doesn't need
- *                              to do any extra underflow handling.)
+ *                              the circular buffer).  Unless something is going
+ *                              seriously wrong, this will always be transfer
+ *                              size for the current period.
  * @param   tsNowNs             The current RTTimeNano() value.
  *
@@ -1751,8 +1651,4 @@
     Assert(ASMAtomicReadBool(&pStreamShared->State.fRunning));
 
-    /* Transfer sanity checks. */
-    Assert(pStreamShared->State.cbTransferSize);
-    Assert(pStreamShared->State.cbTransferChunk <= pStreamShared->State.cbTransferSize);
-
     /*
      * Some timestamp stuff for logging/debugging.
@@ -1764,63 +1660,6 @@
 
     /*
-     * Fetch the next BDL entry.
-     */
-    int      rc    = VINF_SUCCESS;
-    PHDABDLE pBDLE = &pStreamShared->State.BDLE;
-    if (hdaR3BDLEIsComplete(pBDLE))
-    {
-        rc = hdaR3BDLEFetch(pDevIns, pBDLE, pStreamShared->u64BDLBase, pStreamShared->State.uCurBDLE);
-        AssertRCReturn(rc, rc);
-    }
-
-    /*
-     * The caller should have made sure we've got at least cbTransferSize
-     * of buffer available.   We will not transfer more than that.
-     */
-    uint8_t cSuppressIocs = 0;
-    Assert(pStreamShared->State.cbTransferChunk == pStreamShared->State.cbTransferSize);
-    if (!pStreamShared->State.cfPosAdjustLeft)
-    {
-        Assert(cbToProduce >= pStreamShared->State.cbTransferSize);
-        if (cbToProduce > pStreamShared->State.cbTransferSize)
-            cbToProduce = pStreamShared->State.cbTransferSize;
-    }
-    else
-    {
-        /* We currently process the position adjustment BLDE0 and the whole BLDE1
-           in one DMA timer callout, ignoring BLDE0.IOC. */
-        /** @todo Process the tiny BLDE0 seperatly. */
-        uint32_t const cbPosAdjust = PDMAudioPropsFramesToBytes(&pStreamShared->State.Cfg.Props,
-                                                                pStreamShared->State.cfPosAdjustLeft);
-        Assert(cbToProduce >= pStreamShared->State.cbTransferSize + cbPosAdjust);
-        if (cbToProduce >= pStreamShared->State.cbTransferSize + cbPosAdjust)
-        {
-            cbToProduce = pStreamShared->State.cbTransferSize + cbPosAdjust;
-            pStreamShared->State.cfPosAdjustLeft = 0;
-            cSuppressIocs = pBDLE->Desc.fFlags & HDA_BDLE_F_IOC ? 1 : 0;
-        }
-        else
-            pStreamShared->State.cfPosAdjustLeft -= PDMAudioPropsBytesToFrames(&pStreamShared->State.Cfg.Props, cbToProduce);
-    }
-    uint32_t cbLeft = cbToProduce;
-    Assert(PDMAudioPropsIsSizeAligned(&pStreamShared->State.Cfg.Props, cbLeft));
-
-    /*
-     * Whether an interrupt has been sent (asserted) for this transfer period already or not.
-     *
-     * Note: Windows 10 relies on this, e.g. sending more than one interrupt per transfer period
-     *       confuses the Windows' audio driver and will screw up the audio data. So only send
-     *       one interrupt per transfer period.
-     *
-     * Note! This only applies if the transfer heuristics isn't active.
-     */
-    /** @todo r=bird: Of course the guest gets confused if you bundle interrupts.
-     *        Unless the buffers are really small, this is stuff that won't happen
-     *        on real hardware.  */
-    /** @todo Disallow non-heuristics approach!  It only complicates the code
-     *        and messes with the guest if we cover more than one IOC! */
-    bool fInterruptSent = false;
-
-    /* Set the FIFORDY bit on the stream while doing the transfer. */
+     * Set the FIFORDY bit on the stream while doing the transfer.
+     */
     /** @todo r=bird: I don't get the HDA_SDSTS_FIFORDY logic.  Unless we're
      *        assuming SMP guest and that it can get stream registers while we're
@@ -1838,4 +1677,8 @@
     uint32_t   cbBounce = 0;            /* in case of incomplete frames between buffer segments */
     PRTCIRCBUF pCircBuf = pStreamR3->State.pCircBuf;
+    uint32_t   cbLeft   = cbToProduce;
+    Assert(cbLeft == pStreamShared->State.cbTransferSize);
+    Assert(PDMAudioPropsIsSizeAligned(&pStreamShared->State.Cfg.Props, cbLeft));
+
     while (cbLeft > 0)
     {
@@ -1845,6 +1688,6 @@
          * Figure out how much we can read & write in this iteration.
          */
-        uint32_t cbChunk = pBDLE->Desc.u32BufSize - pBDLE->State.u32BufOff;
-        AssertStmt(cbChunk <= pBDLE->Desc.u32BufSize, cbChunk = 0);
+        uint32_t cbChunk = 0;
+        RTGCPHYS GCPhys  = hdaR3StreamDmaBufGet(pStreamShared, &cbChunk);
 
         /* Need to diverge if the frame format differs.  */
@@ -1860,5 +1703,4 @@
              * Read the guest data directly into the internal DMA buffer.
              */
-            RTGCPHYS GCPhys = pBDLE->Desc.u64BufAddr + pBDLE->State.u32BufOff;
             while (cbChunk > 0)
             {
@@ -1885,9 +1727,8 @@
 
                 /* advance */
-                cbChunk                -= (uint32_t)cbBufDst;
-                GCPhys                 +=           cbBufDst;
-                cbLeft                 -= (uint32_t)cbBufDst;
-                pBDLE->State.u32BufOff += (uint32_t)cbBufDst;
-                Assert(pBDLE->State.u32BufOff <= pBDLE->Desc.u32BufSize);
+                cbChunk                         -= (uint32_t)cbBufDst;
+                GCPhys                          +=           cbBufDst;
+                cbLeft                          -= (uint32_t)cbBufDst;
+                pStreamShared->State.offCurBdle += (uint32_t)cbBufDst;
             }
         }
@@ -1914,5 +1755,4 @@
              * Loop till we've covered the chunk.
              */
-            RTGCPHYS GCPhys = pBDLE->Desc.u64BufAddr + pBDLE->State.u32BufOff;
             Log5Func(("loop0:  GCPhys=%RGp cbChunk=%#x + cbBounce=%#x\n", GCPhys, cbChunk, cbBounce));
             while (cbChunk > 0)
@@ -1969,8 +1809,7 @@
 
                 /* advance */
-                cbChunk                -= cbToRead;
-                GCPhys                 += cbToRead;
-                pBDLE->State.u32BufOff += cbToRead;
-                Assert(pBDLE->State.u32BufOff <= pBDLE->Desc.u32BufSize);
+                cbChunk                         -= cbToRead;
+                GCPhys                          += cbToRead;
+                pStreamShared->State.offCurBdle += cbToRead;
                 if (cbRemainder)
                     memmove(&abBounce[0], &abBounce[cbBounce - cbRemainder], cbRemainder);
@@ -1985,7 +1824,10 @@
          * Is the buffer descriptor complete.
          */
-        if (hdaR3BDLEIsComplete(pBDLE))
+        if (hdaR3StreamDmaBufIsComplete(pStreamShared))
         {
-            Log3Func(("[SD%RU8] Completed %R[bdle]\n", uSD, pBDLE));
+            Log3Func(("[SD%RU8] Completed BDLE%u %#RX64 LB %#RX32 fFlags=%#x\n", uSD, pStreamShared->State.idxCurBdle,
+                      pStreamShared->State.aBdl[pStreamShared->State.idxCurBdle].GCPhys,
+                      pStreamShared->State.aBdl[pStreamShared->State.idxCurBdle].cb,
+                      pStreamShared->State.aBdl[pStreamShared->State.idxCurBdle].fFlags));
 
             /* Make sure to also update the wall clock when a BDLE is complete.
@@ -1995,5 +1837,5 @@
                              hdaWalClkGetCurrent(pThis)
                            + hdaR3StreamPeriodFramesToWalClk(pPeriod,
-                                                               pBDLE->Desc.u32BufSize
+                                                               hdaR3StreamDmaBufGetSize(pStreamShared)
                                                              / pStreamR3->State.Mapping.cbGuestFrame),
                            false /* fForce */);
@@ -2008,32 +1850,23 @@
              *                      Not doing this at the right time will result in ugly sound crackles!
              */
-            hdaR3StreamSetPositionAdd(pStreamShared, pDevIns, pThis, pBDLE->Desc.u32BufSize);
+            hdaR3StreamSetPositionAdd(pStreamShared, pDevIns, pThis, hdaR3StreamDmaBufGetSize(pStreamShared));
 
             /* Does the current BDLE require an interrupt to be sent? */
-            if (hdaR3BDLENeedsInterrupt(pBDLE))
+            if (hdaR3StreamDmaBufNeedsIrq(pStreamShared))
             {
-                if (cSuppressIocs == 0)
+                /* If the IOCE ("Interrupt On Completion Enable") bit of the SDCTL
+                   register is set we need to generate an interrupt. */
+                if (HDA_STREAM_REG(pThis, CTL, uSD) & HDA_SDCTL_IOCE)
                 {
-                    /* If the IOCE ("Interrupt On Completion Enable") bit of the SDCTL
-                       register is set we need to generate an interrupt. */
-                    if (HDA_STREAM_REG(pThis, CTL, uSD) & HDA_SDCTL_IOCE)
-                    {
-                        /* Assert the interrupt before actually fetching the next BDLE below. */
-                        if (!fInterruptSent)
-                        {
-                            pStreamShared->State.cTransferPendingInterrupts = 1;
-                            Log3Func(("[SD%RU8] Scheduling interrupt\n", uSD));
-
-                            /* Trigger an interrupt first and let hdaRegWriteSDSTS() deal with
-                             * ending / beginning of a period. */
-                            /** @todo r=bird: What does the above comment mean? */
-                            HDA_STREAM_REG(pThis, STS, uSD) |= HDA_SDSTS_BCIS;
-                            HDA_PROCESS_INTERRUPT(pDevIns, pThis);
-                            fInterruptSent = true;
-                        }
-                    }
+                    /* Assert the interrupt before actually fetching the next BDLE below. */
+                    pStreamShared->State.cTransferPendingInterrupts = 1;
+                    Log3Func(("[SD%RU8] Scheduling interrupt\n", uSD));
+
+                    /* Trigger an interrupt first and let hdaRegWriteSDSTS() deal with
+                     * ending / beginning of a period. */
+                    /** @todo r=bird: What does the above comment mean? */
+                    HDA_STREAM_REG(pThis, STS, uSD) |= HDA_SDSTS_BCIS;
+                    HDA_PROCESS_INTERRUPT(pDevIns, pThis);
                 }
-                else
-                    cSuppressIocs--;
             }
 
@@ -2041,12 +1874,11 @@
              * Advance to the next BDLE.
              */
-            if (pStreamShared->State.uCurBDLE >= pStreamShared->u16LVI)
-                pStreamShared->State.uCurBDLE = 0;
-            else
-                pStreamShared->State.uCurBDLE++;
-            hdaR3BDLEFetch(pDevIns, pBDLE, pStreamShared->u64BDLBase, pStreamShared->State.uCurBDLE);
+            hdaR3StreamDmaBufAdvanceToNext(pStreamShared);
         }
         else
-            Log3Func(("[SD%RU8] Not complete %R[bdle]\n", uSD, pBDLE));
+            Log3Func(("[SD%RU8] Not completed BDLE%u %#RX64 LB %#RX32 fFlags=%#x: off=%#RX32\n",
+                      uSD, pStreamShared->State.idxCurBdle, pStreamShared->State.aBdl[pStreamShared->State.idxCurBdle].GCPhys,
+                      pStreamShared->State.aBdl[pStreamShared->State.idxCurBdle].cb,
+                      pStreamShared->State.aBdl[pStreamShared->State.idxCurBdle].fFlags, pStreamShared->State.offCurBdle));
     }
 
@@ -2085,6 +1917,6 @@
      * Log and leave.
      */
-    Log3Func(("[SD%RU8] %R[bdle] -- %#RX32/%#RX32 @ %#RX64 - cTransferPendingInterrupts=%RU8\n",
-              uSD, pBDLE, cbToProduce, pStreamShared->State.cbTransferSize, pStreamR3->State.offWrite - cbToProduce,
+    Log3Func(("[SD%RU8] %#RX32/%#RX32 @ %#RX64 - cTransferPendingInterrupts=%RU8\n",
+              uSD, cbToProduce, pStreamShared->State.cbTransferSize, pStreamR3->State.offWrite - cbToProduce,
               pStreamShared->State.cTransferPendingInterrupts));
 
@@ -2153,11 +1985,32 @@
         && AudioMixerSinkIsActive(pStreamR3->pMixSink->pMixSink))
     {
-        uint64_t const tsNow          = PDMDevHlpTimerGet(pDevIns, pStreamShared->hTimer); /* (For virtual sync this remains the same for the whole callout IIRC) */
-        uint64_t const tsTransferNext = tsNow + pStreamShared->State.cTransferTicks;
+        /* Advance the schduling: */
+        uint32_t idxSched = pStreamShared->State.idxSchedule;
+        AssertStmt(idxSched < RT_ELEMENTS(pStreamShared->State.aSchedule), idxSched = 0);
+        uint32_t idxLoop  = pStreamShared->State.idxScheduleLoop + 1;
+        if (idxLoop >= pStreamShared->State.aSchedule[idxSched].cLoops)
+        {
+            idxSched += 1;
+            if (   idxSched > pStreamShared->State.cSchedule
+                || idxSched >= RT_ELEMENTS(pStreamShared->State.aSchedule) /*paranoia^2*/)
+            {
+                idxSched = pStreamShared->State.cSchedulePrologue;
+                AssertStmt(idxSched < RT_ELEMENTS(pStreamShared->State.aSchedule), idxSched = 0);
+            }
+            idxLoop = 0;
+        }
+        pStreamShared->State.idxScheduleLoop = (uint16_t)idxLoop;
+
+        /* Do the arcual timer arming. */
+        uint64_t const tsNow = PDMDevHlpTimerGet(pDevIns, pStreamShared->hTimer); /* (For virtual sync this remains the same for the whole callout IIRC) */
+        uint64_t const tsTransferNext = tsNow + pStreamShared->State.aSchedule[idxSched].cPeriodTicks;
         Log3Func(("[SD%RU8] fSinkActive=true, tsTransferNext=%RU64 (in %RU64)\n",
                   pStreamShared->u8SD, tsTransferNext, tsTransferNext - tsNow));
-        pStreamShared->State.tsTransferNext = tsTransferNext; /* legacy */
         int rc = PDMDevHlpTimerSet(pDevIns, pStreamShared->hTimer, tsTransferNext);
         AssertRC(rc);
+
+        /* Some legacy stuff: */
+        pStreamShared->State.tsTransferNext = tsTransferNext;
+        pStreamShared->State.cbTransferSize = pStreamShared->State.aSchedule[idxSched].cbPeriod;
 
         return tsNow;
@@ -2195,5 +2048,12 @@
                        PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3, bool fInTimer)
 {
-    if (!pStreamShared)
+    int rc2;
+
+    /*
+     * Make sure we're running and got an active mixer sink.
+     */
+    if (RT_LIKELY(pStreamShared->State.fRunning))
+    { /* likely */ }
+    else
         return;
 
@@ -2201,13 +2061,21 @@
     if (pStreamR3->pMixSink)
         pSink = pStreamR3->pMixSink->pMixSink;
-
-    if (!AudioMixerSinkIsActive(pSink)) /* No sink available? Bail out. */
+    if (RT_LIKELY(AudioMixerSinkIsActive(pSink)))
+    { /* likely */ }
+    else
         return;
 
-    const uint64_t tsNowNs = RTTimeNanoTS();
-
-    int rc2;
-
-    if (hdaGetDirFromSD(pStreamShared->u8SD) == PDMAUDIODIR_OUT) /* Output (SDO). */
+    /*
+     * Get scheduling info common to both input and output streams.
+     */
+    const uint64_t tsNowNs  = RTTimeNanoTS();
+    uint32_t       idxSched = pStreamShared->State.idxSchedule;
+    AssertStmt(idxSched < RT_MIN(RT_ELEMENTS(pStreamShared->State.aSchedule), pStreamShared->State.cSchedule), idxSched = 0);
+    uint32_t const cbPeriod = pStreamShared->State.aSchedule[idxSched].cbPeriod;
+
+    /*
+     * Output streams (SDO).
+     */
+    if (hdaGetDirFromSD(pStreamShared->u8SD) == PDMAUDIODIR_OUT)
     {
         bool fDoRead; /* Whether to push data down the driver stack or not.  */
@@ -2221,5 +2089,5 @@
              */
             uint32_t cbStreamFree = hdaR3StreamGetFree(pStreamR3);
-            if (cbStreamFree >= pStreamShared->State.cbTransferSize)
+            if (cbStreamFree >= cbPeriod)
             { /* likely */ }
             else
@@ -2227,5 +2095,5 @@
                 STAM_REL_COUNTER_INC(&pStreamR3->State.StatDmaFlowProblems);
                 Log(("hdaR3StreamUpdate: Warning! Stream #%u has insufficient space free: %u bytes, need %u.  Will try move data out of the buffer...\n",
-                     pStreamShared->u8SD, cbStreamFree, pStreamShared->State.cbTransferSize));
+                     pStreamShared->u8SD, cbStreamFree, cbPeriod));
 # ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO
                 int rc = RTCritSectTryEnter(&pStreamR3->State.AIO.CritSect);
@@ -2243,5 +2111,5 @@
 
                 cbStreamFree = hdaR3StreamGetFree(pStreamR3);
-                if (cbStreamFree < pStreamShared->State.cbTransferSize)
+                if (cbStreamFree < cbPeriod)
                 {
                     /* Unable to make sufficient space.  Drop the whole buffer content.
@@ -2270,5 +2138,5 @@
 
             uint64_t const offWriteBefore = pStreamR3->State.offWrite;
-            rc2 = hdaR3StreamDoDmaOutput(pDevIns, pThis, pThisCC, pStreamShared, pStreamR3, cbStreamFree, tsNowNs);
+            rc2 = hdaR3StreamDoDmaOutput(pDevIns, pThis, pThisCC, pStreamShared, pStreamR3, RT_MIN(cbStreamFree, cbPeriod), tsNowNs);
             AssertRC(rc2);
 
@@ -2291,5 +2159,5 @@
             if (!pStreamShared->State.tsAioDelayEnd)
                 fDoRead = pStreamR3->State.offWrite > offWriteBefore
-                       || hdaR3StreamGetFree(pStreamR3) < pStreamShared->State.cbTransferSize * 2;
+                       || hdaR3StreamGetFree(pStreamR3) < pStreamShared->State.cbAvgTransfer * 2;
             else if (PDMDevHlpTimerGet(pDevIns, pStreamShared->hTimer) >= pStreamShared->State.tsAioDelayEnd)
             {
@@ -2298,5 +2166,5 @@
                 fDoRead = true;
             }
-            else if (hdaR3StreamGetFree(pStreamR3) < pStreamShared->State.cbTransferSize * 2)
+            else if (hdaR3StreamGetFree(pStreamR3) < pStreamShared->State.cbAvgTransfer * 2)
             {
                 Log3Func(("Initial delay done: Passed running short on buffer.\n"));
@@ -2313,5 +2181,5 @@
                       (tsNowNs - pStreamShared->State.tsLastReadNs) / RT_NS_1MS,
                       pStreamShared->State.Cfg.Device.cMsSchedulingHint, cbStreamFree,
-                      pStreamShared->State.cbTransferSize * 2, fDoRead));
+                      pStreamShared->State.cbAvgTransfer * 2, fDoRead));
 
             if (fDoRead)
@@ -2339,6 +2207,11 @@
             hdaR3StreamPushToMixer(pStreamShared, pStreamR3, pSink, tsNowNs);
     }
-    else /* Input (SDI). */
-    {
+    /*
+     * Input stream (SDI).
+     */
+    else
+    {
+        /** @todo Re-org this. Like in the output case, the timer thread should try do
+         *        AIO work if we have an underrun. */
 # ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO
         if (!fInTimer)
@@ -2395,5 +2268,6 @@
         {
 # ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO
-            if (tsNowNs - pStreamShared->State.tsLastReadNs >= pStreamShared->State.Cfg.Device.cMsSchedulingHint * RT_NS_1MS)
+            if (   tsNowNs - pStreamShared->State.tsLastReadNs >= pStreamShared->State.Cfg.Device.cMsSchedulingHint * RT_NS_1MS
+                || hdaR3StreamGetUsed(pStreamR3) < RT_MAX(cbPeriod, pStreamShared->State.cbAvgTransfer))
             {
                 Log5Func(("Notifying AIO thread\n"));
@@ -2407,5 +2281,5 @@
             if (cbStreamUsed)
             {
-                rc2 = hdaR3StreamTransfer(pDevIns, pThis, pThisCC, pStreamShared, pStreamR3, cbStreamUsed);
+                rc2 = hdaR3StreamDoDmaInput(pDevIns, pThis, pThisCC, pStreamShared, pStreamR3, RT_MIN(cbStreamUsed, cbPeriod));
                 AssertRC(rc2);
             }
Index: trunk/src/VBox/Devices/Audio/HDAStream.h
===================================================================
--- trunk/src/VBox/Devices/Audio/HDAStream.h	(revision 88128)
+++ trunk/src/VBox/Devices/Audio/HDAStream.h	(revision 88137)
@@ -114,7 +114,4 @@
 typedef struct HDASTREAMSTATE
 {
-    /** Current BDLE to use. Wraps around to 0 if
-     *  maximum (cBDLE) is reached. */
-    uint16_t                uCurBDLE;
     /** Flag indicating whether this stream currently is
      *  in reset mode and therefore not acccessible by the guest. */
@@ -122,8 +119,11 @@
     /** Flag indicating if the stream is in running state or not. */
     volatile bool           fRunning;
+    /** The stream's I/O timer Hz rate. */
+    uint16_t                uTimerIoHz;
+    /** How many interrupts are pending due to
+     *  BDLE interrupt-on-completion (IOC) bits set. */
+    uint8_t                 cTransferPendingInterrupts;
     /** Unused, padding. */
-    uint8_t                 abPadding0[4];
-    /** Current BDLE (Buffer Descriptor List Entry). */
-    HDABDLE                 BDLE;
+    uint8_t                 abPadding1[3];
     /** Timestamp (absolute, in timer ticks) of the last DMA data transfer. */
     uint64_t                tsTransferLast;
@@ -135,25 +135,6 @@
      * @note This is in host side frames, in case we're doing any mapping. */
     uint32_t                cbTransferSize;
-    /** Unused, same as cbTransferSize. */
-    uint32_t                cbTransferChunk;
-    /** How many interrupts are pending due to
-     *  BDLE interrupt-on-completion (IOC) bits set. */
-    uint8_t                 cTransferPendingInterrupts;
-    uint8_t                 abPadding2[7];
-    /** The stream's I/O timer Hz rate. */
-    uint16_t                uTimerIoHz;
-    /** Number of audio data frames for the position adjustment.
-     *  0 if no position adjustment is needed. */
-    uint16_t                cfPosAdjustDefault;
-    /** How many audio data frames are left to be processed
-     *  for the position adjustment handling.
-     *
-     *  0 if position adjustment handling is done or inactive. */
-    uint16_t                cfPosAdjustLeft;
-    uint16_t                u16Padding3;
-    /** (Virtual) clock ticks per byte. */
-    uint64_t                cTicksPerByte;
-    /** (Virtual) clock ticks per transfer. */
-    uint64_t                cTransferTicks;
+    /** The size of an average transfer. */
+    uint32_t                cbAvgTransfer;
     /** The stream's period. Need for timing. */
     HDASTREAMPERIOD         Period;
@@ -175,6 +156,57 @@
     /** The start time for the playback (on the timer clock). */
     uint64_t                tsStart;
+
+    /** @name DMA engine
+     * @{ */
+    /** The offset into the current BDLE. */
+    uint32_t                offCurBdle;
+    /** LVI + 1 */
+    uint16_t                cBdles;
+    /** The index of the current BDLE.
+     * This is the entry which period is currently "running" on the DMA timer.  */
+    uint8_t                 idxCurBdle;
+    /** The number of prologue scheduling steps.
+     * This is used when the tail BDLEs doesn't have IOC set.  */
+    uint8_t                 cSchedulePrologue;
+    /** Number of scheduling steps. */
+    uint16_t                cSchedule;
+    /** Current scheduling step. */
+    uint16_t                idxSchedule;
+    /** Current loop number within the current scheduling step.  */
+    uint32_t                idxScheduleLoop;
+
+    /** Buffer descriptors and additional timer scheduling state.
+     * (Same as HDABDLEDESC, with more sensible naming.)  */
+    struct
+    {
+        /** The buffer address. */
+        uint64_t            GCPhys;
+        /** The buffer size (guest bytes). */
+        uint32_t            cb;
+        /** The flags (only bit 0 is defined).    */
+        uint32_t            fFlags;
+    }                       aBdl[256];
+    /** Scheduling steps. */
+    struct
+    {
+        /** Number of timer ticks per period.
+         * ASSUMES that we don't need a full second and that the timer resolution
+         * isn't much higher than nanoseconds. */
+        uint32_t            cPeriodTicks;
+        /** The period length in host bytes. */
+        uint32_t            cbPeriod;
+        /** Number of times to repeat the period. */
+        uint32_t            cLoops;
+        /** The BDL index of the first entry.   */
+        uint8_t             idxFirst;
+        /** The number of BDL entries. */
+        uint8_t             cEntries;
+        uint8_t             abPadding[2];
+    }                       aSchedule[512+8];
+    /** @} */
 } HDASTREAMSTATE;
 AssertCompileSizeAlignment(HDASTREAMSTATE, 8);
+AssertCompileMemberAlignment(HDASTREAMSTATE, aBdl, 16);
+AssertCompileMemberAlignment(HDASTREAMSTATE, aSchedule, 16);
 
 /**
