hi,

The function xhci_event_xfer_isoc() of sys/dev/usb/xhci.c at line 954
does not work with zero length multi-TRB inbound transfer.

   949          /*
   950           * If we queued two TRBs for a frame and this is the second TRB,
   951           * check if the first TRB needs accounting since it might not 
have
   952           * raised an interrupt in case of full data received.
   953           */
   954          if ((letoh32(xp->ring.trbs[trb_idx].trb_flags) & 
XHCI_TRB_TYPE_MASK) ==
   955              XHCI_TRB_TYPE_NORMAL) {
   956                  frame_idx--;
   957                  if (trb_idx == 0)
   958                          trb0_idx = xp->ring.ntrb - 2;
   959                  else
   960                          trb0_idx = trb_idx - 1;
   961                  if (xfer->frlengths[frame_idx] == 0) {
   962                          xfer->frlengths[frame_idx] = 
XHCI_TRB_LEN(letoh32(
   963                              xp->ring.trbs[trb0_idx].trb_status));
   964                  }
   965          }
   966  
   967          xfer->frlengths[frame_idx] +=
   968              XHCI_TRB_LEN(letoh32(xp->ring.trbs[trb_idx].trb_status)) - 
remain;
   969          xfer->actlen += xfer->frlengths[frame_idx];

When a multi-TRB inbound transfer TD completes with transfer length = 0,
the HC should generate two events: 1st event for ISOCH TRB /w ISP|CHAIN
and 2nd event for NORMAL TRB w/ ISP|IOC.
Transfer Length field (it's remain length, actually) of each event is
same as requested length is, i.e., transferred length is 0.
So when the first event raises the frlengths is set to 0 at line 967.
It's correct.
On second event, as the comment describes, xhci.c tries to calculate
the 1st TRB xfer length at lines 954-965. The requested length of
1st TRB is stored into frlengths -- even though the xfer len is 0.

If frlengths = 0, we cannot distinguish the case the first event is
not raised from the case the transferred length is 0.
The frlengths is already 0 so the requested length of 1st TRB is stored.

For example, I applied debug printf [*1], and run
mplayer tv:// for my webcam.
I see...

#25 remain 1024 type 5 origlen 1024 frlengths[25] 0
#25 (omitted) frlen[25] 1024
#26 remain 2048 type 1 origlen 2048 frlengths[25] 1024

These console logs show a 3072 bytes frame is splitted into
two TRBs and got 0 bytes. The first TRB transfers 0 bytes and
the second TRB transfers 0, too, but it results 1024 bytes.

My proposal patch [*2] adds a flag to xhci_xfer that indicates the
TRB processed by xhci.c previously has CHAIN bit, and updates the
frlengths only when that flag is not set.



[*1]
debug printf.
It shows only splitted isochronous TDs.

--- sys/dev/usb/xhci.c.orig     Sun Apr  5 10:12:37 2020
+++ sys/dev/usb/xhci.c  Fri May 29 04:13:36 2020
@@ -961,12 +961,23 @@ xhci_event_xfer_isoc(struct usbd_xfer *xfer, struct xh
                if (xfer->frlengths[frame_idx] == 0) {
                        xfer->frlengths[frame_idx] = XHCI_TRB_LEN(letoh32(
                            xp->ring.trbs[trb0_idx].trb_status));
+                       printf("#%d (omitted) frlen[%d] %u\n",
+                           trb0_idx, frame_idx, xfer->frlengths[frame_idx]);
                }
        }
 
        xfer->frlengths[frame_idx] +=
            XHCI_TRB_LEN(letoh32(xp->ring.trbs[trb_idx].trb_status)) - remain;
        xfer->actlen += xfer->frlengths[frame_idx];
+       uint32_t trb_flags = letoh32(xp->ring.trbs[trb_idx].trb_flags);
+       if ((trb_flags & XHCI_TRB_CHAIN) ||
+           (trb_flags & XHCI_TRB_TYPE_MASK) == XHCI_TRB_TYPE_NORMAL) {
+               printf("#%d remain %u type %u origlen %u frlengths[%d] %hu\n",
+                   trb_idx, remain,
+                   XHCI_TRB_TYPE(trb_flags),
+                   XHCI_TRB_LEN(le32toh(xp->ring.trbs[trb_idx].trb_status)),
+                   frame_idx, xfer->frlengths[frame_idx]);
+       }
 
        if (xx->index != trb_idx)
                return (1);

[*2]
patch

--- sys/dev/usb/xhcivar.h.orig  Sun Oct  6 21:19:28 2019
+++ sys/dev/usb/xhcivar.h       Fri May 22 04:19:57 2020
@@ -40,6 +40,7 @@ struct xhci_xfer {
        struct usbd_xfer         xfer;
        int                      index;         /* Index of the last TRB */
        size_t                   ntrb;          /* Number of associated TRBs */
+       bool                     trb_chained;
 };
 
 struct xhci_ring {
--- sys/dev/usb/xhci.c.orig     Sun Apr  5 10:12:37 2020
+++ sys/dev/usb/xhci.c  Thu Jun  4 05:39:03 2020
@@ -958,15 +958,23 @@ xhci_event_xfer_isoc(struct usbd_xfer *xfer, struct xh
                        trb0_idx = xp->ring.ntrb - 2;
                else
                        trb0_idx = trb_idx - 1;
-               if (xfer->frlengths[frame_idx] == 0) {
+               if (xfer->frlengths[frame_idx] == 0 && !xx->trb_chained) {
                        xfer->frlengths[frame_idx] = XHCI_TRB_LEN(letoh32(
                            xp->ring.trbs[trb0_idx].trb_status));
                }
        }
 
-       xfer->frlengths[frame_idx] +=
-           XHCI_TRB_LEN(letoh32(xp->ring.trbs[trb_idx].trb_status)) - remain;
-       xfer->actlen += xfer->frlengths[frame_idx];
+       if (!xx->trb_chained) {
+               xfer->frlengths[frame_idx] +=
+                   XHCI_TRB_LEN(letoh32(xp->ring.trbs[trb_idx].trb_status)) -
+                   remain;
+               xfer->actlen += xfer->frlengths[frame_idx];
+       }
+
+       if (letoh32(xp->ring.trbs[trb_idx].trb_flags) & XHCI_TRB_CHAIN)
+               xx->trb_chained = true;
+       else
+               xx->trb_chained = false;
 
        if (xx->index != trb_idx)
                return (1);

Reply via email to