'Why does WinUSB split transfers greater than MAXIMUM_TRANSFER_SIZE and non-multiples of MaximumPacketSize into multiple transfers?

When transferring a superspeed bulk-in transfer greater than the MAXIMIMUM_TRANSFER_SIZE (2MiB), and a non multiple of the maximum packet size (1024B) it will split the transfers into 3 USB transfers rather than 2.

I'm using LibUSB for the PC side code, requesting 2098192 B (2MiB + 1040B) bulk-in bytes. The device is using the WinUSB driver and is capable of sending the full amount.

The pseudocode is:

while (!isDone) {
    // 2098192 B = 2MiB + 1040B
    int ret = libusb_bulk_transfer(handle, ep, buf, 2098192, actual_length, 0);  
}

I would expect to get this back in 1 LibUSB transfer, but instead I get it in two libUSB transfers of:

  1. actual_length = 2098176B (2MiB + 1024B)
  2. actual_length = 16B

Here's a table of the different transfer sizes and how many LibUSB transfers it takes. There appears to be a theshold above which this behavior appears: threshold=MAXIMUM_TRANSFER_SIZE + MaximumPacketSize = 2098176B.

Requested Size Number of LibUSB transfers
transferSize < threshold 1
transferSize > threshold AND (transferSize % maximumPacketSize == 0) 1
transferSize > threshold AND (transferSize % MaximumPacketSize != 0) 2

By using [LogMon USB logging, I was able to log what the WinUSB driver was doing under the hood when requesting 2098192 B (2MiB + 1040B):

  1. WinUSB asks for 2MiB (MAXIMUM_TRANSFER_SIZE). This request is now in a pending state: USBD_STATUS_PENDING.
  2. The device sends 2MiB, the request is successful, and the state of that transfer is now USBD_STATUS_SUCCESS. (This is the first transfer)
  3. WinUSB asks for 1024B (Maximum packet size). This request is now in a pending state: USBD_STATUS_PENDING.
  4. The device sends 1024B, and the state of that transfer is now USBD_STATUS_SUCCESS.
  5. The pseudocode libUSB bulk transfer loop increments and requests 2098192B again.
  6. WinUSB asks for 2MiB (MAXIMUM_TRANSFER_SIZE). This request is now in a pending state: USBD_STATUS_PENDING.
  7. The device sends 16B, and the state of that transfer is now USBD_STATUS_SUCCESS.
  8. Return to 1.

The first Libusb transfer is 1-4 and the second libusb transfer is 6-7.

According to the documentation for ReadPipe:

If the data returned by the device is greater than a maximum transfer length, WinUSB divides the request into smaller requests of maximum transfer length and submits them serially. If the transfer length is not a multiple of the endpoint's maximum packet size (retrievable through the WINUSB_PIPE_INFORMATION structure's MaximumPacketSize member), WinUSB increases the size of the transfer to the next multiple of MaximumPacketSize. https://docs.microsoft.com/en-us/windows/win32/api/winusb/nf-winusb-winusb_readpipe

Reading this I would expect WinUSB to ask for 2MiB, then for 2048 B as it should "increase the size of the transfer to the next multiple of MaximumPacketSize".

If I instead manually round up my libusb_bulk_request to the next multiple of MaximumPacketSize and ask for 2099200 B (2MiB + 2048 B), it comes back in one libusb transfer (2 WinUSB transfers of 2MiB and 1040 B). But it seems strange to ask for 2099200 B (2MiB+2048B), when we really want 2098192 B (2MiB + 1040 B).

To me it seems like WinUSB isn't rounding the requested transfer to the next multiple correctly. I feel like I'm missing something.



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source