AnsweredAssumed Answered

Possible bug re: sending zero-length data packets in control transfers (MQX USB stack)

Question asked by Sam Kearney on Jul 17, 2015
Latest reply on Jul 27, 2015 by isaacavila

I recently ran into an issue trying to bring up a Kinetis processor running MQX and the USB device stack. This processor was failing to get through enumeration; it was not responding properly to the first Get Configuration Descriptor request from the host. The host was reporting timeout errors. This would happen when the configuration descriptor was exactly 64 bytes long (and, I expect, it will happen when it is a multiple of 64 bytes... more on that later).


The processor was an MK20FN1M0VMD12, running MQX 4.2.0 with the USB device stack (not usb-v2). It is using the EHCI interface.


Using a USB sniffer I noticed that the host was sending a Get Configuration Descriptor request, requesting a length of 255. My device responded with one transaction containing my entire configuration descriptor, which is exactly 64 bytes long. Then the host polled continuously for another IN transaction, and my device NAKed continuously, until the host reported a timeout.


I expect this is because of the way control transfers are specified in the USB spec:


"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"

(USB Specification Revision 2.0, §5.5.3)


wMaxPacketSize being 64 for control transfers. The host was waiting for a zero-length packet, which the USB device stack never sent.


The Kinetis USBHS controller has a hardware option to insert zero-length packets when the last packet to transmit equals the maximum packet length. But when initializing the control endpoint, the USB stack turns this option off:


(usb_class.c, line 250)

    (void)_usb_device_init_endpoint(event->handle, &ep_struct, TRUE);

    component = (uint8_t)(ep_struct.ep_num |


    /* set the EndPoint Status as Idle in the device layer */





    ep_struct.direction = USB_SEND;  

    (void)_usb_device_init_endpoint(event->handle, &ep_struct, TRUE);


The third parameter in _usb_device_init_endpoint() is hardcoded to TRUE.


(dev_main.c, line 296)



USB_STATUS _usb_device_init_endpoint


    /* [IN] the USB_USB_dev_initialize state structure */

    _usb_device_handle         handle,



    /* [IN] the endpoint structure, include members such as endpoint number,

     * endpoint type, endpoint direction and the max packet size


    USB_EP_STRUCT_PTR    ep_ptr,


    /* [IN] After all data is transfered, should we terminate the transfer

     * with a zero length packet if the last packet size == MAX_PACKET_SIZE?


      uint8_t                     flag


{ /* Body */

   USB_STATUS                    error = 0;

   USB_DEV_STATE_STRUCT_PTR      usb_dev_ptr;


   if (handle == NULL)


        return USBERR_ERROR;




   usb_dev_ptr = (USB_DEV_STATE_STRUCT_PTR)handle;



   /* Initialize the transfer descriptor */

   usb_dev_ptr->TEMP_XD_PTR->EP_NUM = ep_ptr->ep_num;

   usb_dev_ptr->TEMP_XD_PTR->BDIRECTION = ep_ptr->direction;

   usb_dev_ptr->TEMP_XD_PTR->WMAXPACKETSIZE = (uint16_t)(ep_ptr->size & 0x0000FFFF);

   usb_dev_ptr->TEMP_XD_PTR->EP_TYPE = ep_ptr->type;

   usb_dev_ptr->TEMP_XD_PTR->DONT_ZERO_TERMINATE = flag;



The comment language here is misleading; if TRUE is passed into flag, the stack sets the DONT_ZERO_TERMINATE parameter to TRUE, which causes the ZLT bit in the USBHS endpoint queue head to be set to 1, which DISables zero-length packets.


Changing the hardcoded value to FALSE cause my application to work perfectly; I verified using the sniffer that zero-length packets were now being transmitted.


It seems to me that, since this is straight out of the USB specification, the USB stack should have a provision to deal with this edge case (the case where the length of transmitted data is a multiple of the maximum packet size) without me having to modify the source code, either by turning on hardware ZLP insertion or doing it in software. Perhaps there's a configuration setting I'm missing?