[PATCH] hw/usb/hcd-xhci: Clamp overshot isochronous kick time

dongwon.kim@intel.com posted 1 patch 1 month, 2 weeks ago
hw/usb/hcd-xhci.c | 25 ++++++++++++++++++++++---
1 file changed, 22 insertions(+), 3 deletions(-)
[PATCH] hw/usb/hcd-xhci: Clamp overshot isochronous kick time
Posted by dongwon.kim@intel.com 1 month, 2 weeks ago
From: "Chew, Tong Liang" <tong.liang.chew@intel.com>

When an isochronous transfer's Frame ID is reconstructed from the current
MFINDEX epoch, a delayed I/O thread can make the selected frame appear to
be in the past. The existing recovery advances the kick by one MFINDEX
epoch, which can place the transfer roughly two seconds in the future.

The xHCI specification allows a Frame ID to be scheduled only up to
895 ms (0x1BF8 microframes) ahead of the current MFINDEX. Clamp an
epoch-adjusted kick beyond that range to the current MFINDEX so the
missed transfer is dispatched immediately instead of waiting for the
kick timer.

Signed-off-by: Tong Liang Chew <tong.liang.chew@intel.com>
Signed-off-by: Dongwon Kim <dongwon.kim@intel.com>
---
 hw/usb/hcd-xhci.c | 25 ++++++++++++++++++++++---
 1 file changed, 22 insertions(+), 3 deletions(-)

diff --git a/hw/usb/hcd-xhci.c b/hw/usb/hcd-xhci.c
index d342aa2739..0642b5dc4f 100644
--- a/hw/usb/hcd-xhci.c
+++ b/hw/usb/hcd-xhci.c
@@ -1763,12 +1763,30 @@ static void xhci_calc_iso_kick(XHCIState *xhci, XHCITransfer *xfer,
             xfer->mfindex_kick = asap;
         }
     } else {
-        xfer->mfindex_kick = ((xfer->trbs[0].control >> TRB_TR_FRAMEID_SHIFT)
-                              & TRB_TR_FRAMEID_MASK) << 3;
+        uint32_t frame_id = (xfer->trbs[0].control >> TRB_TR_FRAMEID_SHIFT)
+                            & TRB_TR_FRAMEID_MASK;
+        xfer->mfindex_kick = (uint64_t)frame_id << 3;
         xfer->mfindex_kick |= mfindex & ~0x3fff;
         if (xfer->mfindex_kick + 0x100 < mfindex) {
             xfer->mfindex_kick += 0x4000;
         }
+        /*
+         * Guard against epoch-advance overshoot: when the IO thread is briefly
+         * delayed, mfindex can advance past frame_kick by more than the 0x100
+         * lookahead threshold above. Adding 0x4000 then places mfindex_kick
+         * ~2 seconds into the future, stalling all isochronous transfers until
+         * the kick timer fires (observed as periodic ~2.1 s USB audio/video
+         * stalls).
+         *
+         * Per xHCI specification section 4.11.2.5, a Frame ID is valid only
+         * up to 895 ms (0x1BF8 microframes) ahead of the current MFINDEX. If
+         * epoch reconstruction places the kick beyond that range, clamp it
+         * to the current MFINDEX so the missed transfer is dispatched
+         * immediately instead of stalling.
+         */
+        if (xfer->mfindex_kick > mfindex + 0x1BF8) {
+            xfer->mfindex_kick = mfindex;
+        }
     }
 }
 
@@ -1776,8 +1794,9 @@ static void xhci_check_intr_iso_kick(XHCIState *xhci, XHCITransfer *xfer,
                                      XHCIEPContext *epctx, uint64_t mfindex)
 {
     if (xfer->mfindex_kick > mfindex) {
+        uint64_t delay_mf = xfer->mfindex_kick - mfindex;
         timer_mod(epctx->kick_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
-                       (xfer->mfindex_kick - mfindex) * 125000);
+                       delay_mf * 125000);
         xfer->running_retry = 1;
     } else {
         epctx->mfindex_last = xfer->mfindex_kick;
-- 
2.43.0