On 2020/06/24 11:57, Patrick Wildt wrote:
> On Tue, Jun 16, 2020 at 06:55:27AM +0000, [email protected] wrote:
>> 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.
> 
> That's a really good find!  I actually do wonder if we could have the
> same issue with the non-isoc transfers, when the first TRB throws a
> short and then we get another event for the last TRB.

It may occur on non-isoc pipes and should be fixed.
I have not observed problems on non-isoc pipes, but it's because
the devices I saw did not do zero-length xfer on non-isoc pipes,
I guess.

> 
> Maybe it would make sense to record the idx of the last TRB that we have
> received an event for?  Then we could check if we already processed that
> TRB.

At first I recorded such an index and compared with current idx, but
I noticed it would be expressed as a bit.
Do you have any plan to use the software's dequeue pointer for other
purposes?

> 
> Patrick
> 
>> 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