MQX USBHS - Control Endpoint Zero Length Termination

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

MQX USBHS - Control Endpoint Zero Length Termination

1,415 Views
lh_dan
Contributor III

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. 

Example:

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*/

               break;

          }

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.

Labels (1)
0 Kudos
4 Replies

911 Views
Kan_Li
NXP TechSupport
NXP TechSupport

Hi Daniel,

The RM says "

Zero length termination select. This bit is ignored in isochronous transfers.

Clearing this bit enables the hardware to automatically append a zero length packet when the

following conditions are true:

• The packet transmitted equals maximum packet length

• The dTD has exhausted the field Total Bytes

After this the dTD retires. When the device is receiving, if the last packet length received equals the

maximum packet length and the total bytes is zero, it waits for a zero length packet from the host to

retire the current dTD.

"

So I am wondering what you mean by "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. " Did the device just send a zero length packet without sending the 64bytes configuration descriptor firstly?

Please kindly help to clarify!

Thanks for your patience!

Best Regards,

Kan

0 Kudos

911 Views
lh_dan
Contributor III

Hi Kan,

What is seen on the bus in this situation is the 64 byte configuration descriptor is sent followed by the zero length packet.  Exactly what the zero length termination select is configured to do.  The host (Windows PC in my case) has an issue with this because it requested exactly 64 bytes.  The zero length packet that is sent confuses the host and the dTD is never retired.

-Dan

0 Kudos

911 Views
Kan_Li
NXP TechSupport
NXP TechSupport

Hi Daniel,

Thanks for the information! I also checked USB spec, and it says:

The Data stage of a control transfer from an endpoint to the host is complete when the endpoint does one of

the following:

• Has transferred exactly the amount of data specified during the Setup stage

• Transfers a packet with a payload size less than wMaxPacketSize or transfers a zero-length packet

so I think your second solution should be ok, you may disable ZLT from the beginning, in _usb_dci_usbhs_init_endpoint(), you may do the following change:

EHCI_MEM_WRITE(ep_queue_head_ptr->MAX_PKT_LENGTH, (uint32_t)

((xd_ptr->G.WMAXPACKETSIZE << 16) | VUSB_EP_QUEUE_HEAD_IOS | VUSB_EP_QUEUE_HEAD_ZERO_LEN_TER_SEL));

and in fact, there is very few cases where a ZLT is needed,  so you may use _usb_dci_usbhs_add_dTD() to add ZLT to the tranfer queue in the special cases which require to have a ZLT.

Hope that helps,


Have a great day,
Kan

-----------------------------------------------------------------------------------------------------------------------
Note: If this post answers your question, please click the Correct Answer button. Thank you!
-----------------------------------------------------------------------------------------------------------------------

0 Kudos

911 Views
lh_dan
Contributor III

Kan,

Thank you for responding.

I actually used solution 1 in my case.  It works and was less intrusive to the USB stack.   Solution 2 is probably the more 'correct' solution or as you commented one could send the zero length packet themselves.  Either way, the length requested in the Setup stage would still need to be passed into the lower levels of the USB stack.

The cases for ZLT are very low but if the size of the descriptors (like mine) happen fall on a boundary, it is a condition that occurs with every enumeration.  In searching the forums I found that others had also had issues with the ZLT bit but I hadn't seen anyone with a solution yet.  I hoped that creating this post would help out others that may find themselves in a similar situation.

Thanks,

Dan 

0 Kudos