windowsusbwinusb

Why can't I get full bandwidth from WinUsb_ReadIsochPipeAsap?


I have multiple WinUsb_ReadIsochPipeAsap in flight at a time and always queue the next one as soon as WinUsb_GetOverlappedResult succeeds for the first. According to the documentation:

WinUsb_ReadIsochPipeAsap allows the USB driver stack to choose the starting frame number for the transfer. If one or more transfers are already pending on the endpoint, the transfer will be scheduled for the frame number immediately following the last frame number of the last currently pending transfer.

(emphasis added)

When I measure the data rate on the host (rate of read completion), I am only seeing a polling rate of 6ms (every 6th frame), even though the descriptor bInterval is 1, and 6ms isn't even a possible polling interval for isochronous endpoints. The documentation makes me think I should see a transfer on every frame, because I always have multiple pending transfers.

If I look at the bus with a USB analyzer, I don't see any IN-NAK from the device, so I suspect Windows is not even making the request.

Why don't I see the full 1000Hz transfer rate?


Solution

  • The arguments to WinUsb_ReadIsochPipeAsap:

    BOOL __stdcall WinUsb_ReadIsochPipeAsap(
      _In_     PWINUSB_ISOCH_BUFFER_HANDLE BufferHandle,
      _In_     ULONG                       Offset,
      _In_     ULONG                       Length,
      _In_     BOOL                        ContinueStream,
      _In_     PULONG                      NumberOfPackets,
               PUSBD_ISO_PACKET_DESCRIPTOR IsoPacketDescriptors,
      _In_opt_ LPOVERLAPPED                Overlapped
    );
    

    According to the documentation, the meaning of ContinueStream is:

    ContinueStream [in]

    Indicates that the transfer should only be submitted if it can be scheduled in the first frame after the last pending transfer.

    (emphasis added)

    And this is true. For example, if your very first call to WinUsb_ReadIsochPipeAsap passes TRUE, you will get ERROR_INVALID_PARAMETER because there is nothing to continue. If you allow more than 1 frame of time (1ms in full speed) and pass TRUE, you will get ERROR_INVALID_PARAMETER because the "first frame after the last pending transfer" is no longer available.

    However, despite the very similar language about "first frame after the last pending transfer" being the default behavior, the only way Windows will actually schedule the transfers to be in sequential frames is if ContinueStream is set to TRUE. It doesn't matter how fast you call WinUsb_ReadIsochPipeAsap or how many overlapped calls you make.

    The solution is to always try to ContinueStream, but fall back to not continuing in case there is a hitch (else you will fail forever after the first failed ContinueStream):

    ContinueStream = FALSE;
    
    while (...)
    {
        if ( !WinUsb_ReadIsochPipeAsap( ..., ContinueStream, ... ) )
        {
            DWORD lastError = GetLastError();
    
            if ( lastError == ERROR_INVALID_PARAMETER && ContinueStream)
            {
                ContinueStream = FALSE;
                continue;
            }
        }
        ContinueStream = TRUE;
        ...
    }
    

    Alternatively, you could rewrite your loop to use WinUsb_ReadIsochPipe (the non ASAP version), but that just requires you to manage the sequential frame numbers yourself.