'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:
- actual_length = 2098176B (2MiB + 1024B)
- 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):
- WinUSB asks for 2MiB (MAXIMUM_TRANSFER_SIZE). This request is now in a pending state: USBD_STATUS_PENDING.
- The device sends 2MiB, the request is successful, and the state of that transfer is now USBD_STATUS_SUCCESS. (This is the first transfer)
- WinUSB asks for 1024B (Maximum packet size). This request is now in a pending state: USBD_STATUS_PENDING.
- The device sends 1024B, and the state of that transfer is now USBD_STATUS_SUCCESS.
- The pseudocode libUSB bulk transfer loop increments and requests 2098192B again.
- WinUSB asks for 2MiB (MAXIMUM_TRANSFER_SIZE). This request is now in a pending state: USBD_STATUS_PENDING.
- The device sends 16B, and the state of that transfer is now USBD_STATUS_SUCCESS.
- 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 |
---|