I believe I have found an issue with ZLT (Zero Length Termination) in MQX for USBHS. I am currently using MQX v4.1.0 and I believe that the issue also exists in the USB Stack v4.1.1. My design is for a K70 microprocessor.
ZLT is only configured for an endpoint at initialization in _usb_dci_usbhs_init_endpoint(). This function configures ZLT into the Endpoint Queue Head and is not modified during run time for any reason. For the control endpoint (Endpoint 0) the value of ZLT is set to 0 (Enable the zero length packet). My example defines this issue for the control endpoint but I believe the issue could occur on any endpoint that behaves in a similar manner.
My device configuration descriptor happens to be 64 bytes in length (which is a multiple of the control endpoint maximum packet size of 64) so responding to a Get_Descriptor request for the full configuration descriptor will result in the hardware automatically appending a zero length packet. This works as expected when the Setup packet makes this request with the data length field (wLength) set to a value not equal to 64. Either a subset of the config descriptor is sent if wLength is less than 64 or the zero length packet is sent to terminate the response when wLength is greater than 64. The issue occurs when the request is made with a wLength of 64. The host is not expecting a zero length packet in this case, however because ZLT is only configured at initialization, it is attempted to be sent anyway.
This results in a whole host of problems. Although the usb packet is sent, the device transfer descriptor (dTD) that contains this 64 byte packet never retires and an interrupt for its completion is never seen. When another request is made on the control endpoint, the linked list of dTDs is not empty and the response is appended on to the 64 byte dTD that was never freed. This new dTD will retire and a subsequent interrupt will occur. However in _usb_dci_usbhs_process_tr_complete() the following code is hit:
if (EHCI_MEM_READ(dTD_ptr->SIZE_IOC_STS) & USBHS_TD_STATUS_ACTIVE)
//*No more dTDs to process. Next one is owned by VUSB*/
Because dTD_ptr is pointing to the beginning of the list where the 64-byte dTD remains, the active bit is still set and code will exit here without freeing any of the linked dTDs. This process continues until the driver runs out of dTD resources.
I believe this to be a bug within the MQX USB driver.
There are two solutions that I can see
1. Free all inactive dTDs. The code stated above in _usb_dci_usbhs_process_tr_complete() makes an incorrect assumption that when the current dTD is active, all the next dTDs are also active. When the current dTD is active, the if conditional above should also check if any other dTDs in the list are inactive and then free them. In the example this would allow dTDs other than the 64 byte one to be freed. One could assume that if dTDs further down the list are inactive, dTDs at the start of the list should be freed up too. This would have the added bonus of cleaning up all the dTDs including the 64 byte one. This solution seems the least intrusive to the USBHS driver though it does not get to the heart of the issue.
2. ZLT state needs to be decided at the packet level instead of at initialization. The function _usb_dci_usbhs_add_dTD should be where the ZLT state is determined on a packet by packet basis. In order for this to happen on the control endpoint, it needs to be determined if wLength is equal to a multiple of the max packet size for the endpoint. The last place in the call stack that wLength is available is USB_Control_Service_Handler(). Somehow this information would need to be available at the lower levels of the driver. This would be very intrusive and have not spent the time to see how this could be done cleanly.
Any thoughts? Are there any other solutions to this issue? In searching the forums I have seen others have had similar issues but I have heard of no other solutions other than don't send packets equal to the max packet size. Hopefully there is another solution.