This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

TM4C1237E6PM: USB OUT followed by IN of *very specific* packet sizes fail

Part Number: TM4C1237E6PM

I've written a boot loader for a Tiva TM4C1237.  I use a Python program to test it.  I have found that very specifically-sized combinations of USB OUT followed by IN transactions fail consistently.

I'm using a small Python program and pyusb to find the device by VID/PID and interactively send USB requests to test my Tiva firmware.  I can send IN and OUT requests of various sizes to EP0.  I have found that this combination of transactions fails consistently:

  • An OUT transaction to EP0 of exactly any (1..n) multiple of max_packet_size bytes, followed by
  • An IN transaction to EP0 of more than max_packet_size bytes.

Under those specific conditions, the OUT transaction succeeds, but the IN transaction that follows it will always fail.  Deviating either transaction size by one byte will prevent the failure from occurring.  After the failure occurs, subsequent IN transactions will frequently continue to fail, until at some point, the problem gets cleared and subsequent IN transactions will succeed.

Here are some specific numeric examples (max_packet_size is 64):

  • These will all succeed, regardless of the IN size, because the OUT size is not a multiple of 64:
    • OUT 63 bytes, IN 63 bytes:   SUCCEEDS
    • OUT 63 bytes, IN 64 bytes:   SUCCEEDS
    • OUT 63 bytes, IN 65 bytes:   SUCCEEDS
    • OUT 100 bytes, IN 63 bytes:   SUCCEEDS
    • OUT 100 bytes, IN 64 bytes:   SUCCEEDS
    • OUT 100 bytes, IN 65 bytes:   SUCCEEDS
    • OUT 129 bytes, IN 512 bytes:   SUCCEEDS
  • These will all succeed, regardless of the OUT size, because the IN size is not more than 64:
    • OUT 64 bytes, IN 63 bytes:   SUCCEEDS
    • OUT 64 bytes, IN 64 bytes:   SUCCEEDS
    • OUT 128 bytes, IN 64 bytes:   SUCCEEDS
    • OUT 512 bytes, IN 64 bytes:  SUCCEEDS
  • These all fail, because the OUT is exactly n*64, where n=1,2,3,..., and the IN is more than 64:
    • OUT 64 bytes, IN 65 bytes:   FAILS
    • OUT 64 bytes, IN 512 bytes:   FAILS
    • OUT 128 bytes, IN 100 bytes:   FAILS
    • OUT 512 bytes, IN 65 bytes:   FAILS

Upon further investigation, I found that when the IN failure occurs, it happened because the USB interrupts did not occur.

Since the problem only occurs under very specific conditions of OUT followed by IN, I suspect the problem is not in my firmware.

I have prepared a minimal CCS project and the Python program that demonstrate the problem, but the forum does not allow me to attach those files, so I don't know how to make them available to others.  I'm using CCS version 11, and TivaWare_C_Series-2.2.0.295.

  • Hi,

      You can drag your files into the editing window. I will also forward your question to our USB expert. 

  • I didn't realize that dragging/dropping files was different from the menu command Insert Image/Video/file.

    Here is a zip file with the project, the python program, and a readme file which contains instructions to reproduce the problem and some debug output indicating that the Tx interrupts don't occur when the transaction fails.

    USBProblem.zip

  • Hello Douglas,

    Thank you for the detailed documentation of this issue. I just returned to the office today and due to the complexity of it, I may need a few days to review all these details and come with some feedback - I will try and have some level of feedback by COB Wednesday.

    Couple clarifying questions for as I investigate:

    1. Have you only seen this on EP0 - or have you just not tested it on EP1 or others?
    2. Is the device operating in USB DFU mode?

    Best Regards,

    Ralph Jacobi

  • Hi Ralph,

    I haven't tested it with any other endpoints.  This program only uses endpoint 0, so no other endpoints are configured.

    I'm not sure what you mean by DFU mode.  It's not implementing the USB DFU spec, if that's what you mean, but it does receive a binary image from the host application, write that into flash memory, and jump to it.

  • Hi Douglas,

    It was more to understand which USB layers of the stack are being used. DFU, CDC, Bulk etc.

    Best Regards,

    Ralph Jacobi

  • Hi Douglas,

    Have had some time to dig into your code and I see the bulk of your implementation is not using a specific USB protocol as much as leveraging lower level APIs like USBDCDStallEP0.

    These all fail, because the OUT is exactly n*64, where n=1,2,3,..., and the IN is more than 64

    The very first thing that comes to mind here because of how you've narrowed this down to this exact case and seeing how your Python program and USB example are setup is that you do not have a Zero-Length Packet (ZLP) being sent to indicate the end of the transmission. The typical process for a packet that is exactly 64 bytes or a series of packets that end with a 64-byte packet (so length = 128, 192 etc,) is to send a ZLP that indicate the packet is completed and then prepare the receive the next packet.

    The fact that this case is isolate only to OUT pipe and occurs specifically for n*64 sized packets and using just +/- 1 byte solves it really lends to the idea that this is purely due to the zero-length packet not being sent.

    This E2E thread has some back and forth on this topic including a screenshot of the USB analyzer capture: https://e2e.ti.com/support/microcontrollers/arm-based-microcontrollers-group/arm-based-microcontrollers/f/arm-based-microcontrollers-forum/535829/64-byte-usb-packet

    Meanwhile this is a good explanation of what the ZLP is and why it is important: https://stackoverflow.com/questions/48975323/what-is-zero-length-packet

    This is sort of ZLP handling is part of our USB stack in protocol specific files, but since you abstracted a layer away from those by just using the underlying endpoint APIs, that is now missing from the implementation from what I can see.

    Let me know if this explanation makes sense or if I missed a part of your code that implements the ZLP.

    Best Regards,

    Ralph Jacobi

  • No, that's not the problem.

    > This is sort of ZLP handling is part of our USB stack in protocol specific files, but since you abstracted a layer away from those by just using the underlying endpoint APIs, that is now missing from the implementation from what I can see.

    The TivaWare USB Library User's Guide, at the beginning of section 2.23, says that if an existing USB Device Class Driver is not suitable for my application, I may develop using the lower-level USB Device API instead. The existing drivers are not suitable, so I did what the manual says to do.

    Lack of ZLP handling in my code isn't the problem. The lower-level API is documented in sections 2.23 and 2.24. Those sections never mention zero-length packets. ZLP's are only mentioned four times, in a section about ring buffers. The fact that the driver library manual says to use the lower-level API is an indication that the lower-level API is not considered incomplete, and requiring the programmer add code to send or receive zero-length packets.

    Also, using a USB protocol analyzer, I can see the zero-length packets on all of the IN and OUT transactions that succeed, so ZLP handling is not missing from the implementation.

    After more investigation, what I see now is that IN and OUT transactions will both fail after an IN or an OUT of 64 bytes, so it's not limited to just the OUT then IN conditions that I originally indicated.

    The IN or OUT of n*max bytes doesn't fail, but it sets up the conditions for the next transaction to fail (depending on its size).

    This is what I see in the case of an IN 64 transaction, immediately followed by an IN 65:

    • Status starts at 01, indicating RxRdy.
    • The SETUP packet is received, 64 bytes are copied to FIFO, TxRdy bit is set.
    • The EP0 interrupt occurs. State is Tx. Flags are 01, indicating RxRdy is set, and TxRdy has been cleared, which indicates that the host has taken the 64 bytes sent.
    • While still in the interrupt context, no bytes are copied into FIFO, as expected, to end the Tx since the last packet was full. TxRdy and DataEnd bits are set. At the end of this ISR call, that status has become 1B (SetEnd, DataEnd, TxRdy, RxRdy), indicating that the SetEnd flag is now asserted. I don't think it should be.
    • The next EP0 interrupt occurs. State is Status. Status bits are still 1B (SetEnd, DataEnd, TxRdy, RxRdy), indicating that data has not been taken by the host, clearing the TxRdy and DataEnd flags, and SetEnd is still true. The state is changed to Idle.

    The next transaction, an IN 65, starts off with the flags equal to 19 (SetEnd, DataEnd, RxRdy), indicating that the SetEnd and DataEnd flags are still true. This transaction fails.

    The USB analyzer at this point shows the IN 65 resulted in:

    • An IN token
    •    64 bytes transferred
    • An IN token
    •    Stall

    I think the DateEnd bit, still being true, is causing this transaction to end before it has completed.

    Also, once the SetEnd flag becomes true, it never becomes false until I reset the MCU.

  • Hi Douglas,

    I will review the full details of your post in the AM, but I wanted to quickly reply to ask if you could provide the USB analyzer capture. Since the ZLP is not the root cause, looking at a USB analyzer capture is going to be part of the next steps to try and debug this issue on my end.

    Best Regards,

    Ralph Jacobi

  • Do you want a capture from the USB protocol analyzer?  Or just a screen capture?  The protocol analyzer is a Total Phase Beagle USB 480.  Can you read that type of file?

  • Hi Douglas,

    The full analyzer capture file is what I would like to get - I double checked and it looks like I can get the software for that analyzer to view the file. I am pretty sure I've viewed Total Beagle USB captures before, the GUI looks familiar from something I did a few years back.

    Best Regards,

    Ralph Jacobi

  • OK.  It will take a little while to prepare something for you, since I think you'll need a "decoded" version of my rather cryptic debug output.

  • Maybe?

    I mostly wanted to look at how the packets flow like when you described:

    The next transaction, an IN 65, starts off with the flags equal to 19 (SetEnd, DataEnd, RxRdy), indicating that the SetEnd and DataEnd flags are still true. This transaction fails.

    The USB analyzer at this point shows the IN 65 resulted in:

    • An IN token
    •    64 bytes transferred
    • An IN token
    •    Stall

    I know a good bit about USB but I don't delve into protocol analyzer viewers enough to really visualize your description as well as if I could just poke through the file manually.

    Best Regards,

    Ralph Jacobi

  • I've attached a zip file which contains the USB protocol analyzer data (IN 65, IN 64, IN 65.tdc, from a Total Phase Beagle USB 480), and a long file (USB Problem #2.rtf) which gives all the details of the tests.

    For those who don't want to read all the details, here is a quick summary:

    I performed these USB control transfer tests:

    1. IN 65
    2. IN 64
    3. IN 65

    #1 and #2 succeeded.  #3, although identical to #1, failed.

    The status flags seem to go awry in the IN 64, but the transfer succeeds anyway.  The second IN 65 transfer (#3) starts off with a bad status condition:  SetEnd, DataEnd, RxRdy, in USB control register USBCSRL0).  This transfer fails.

    (End of summary.)

    The protocol analyzer output shows that the zero-length packets which indicate the overall success or failure of the transfer are present in the first two transfers.  The third transfer stalls.

    I think the reason that #3, the second IN 65, fails is that the DataEnd bit is still set from the previous transfer, the IN 64.  I don't understand how the status flags could do what they do during the IN 64 transfer.

    Perhaps the solution is simply to add code to the TI USB library that clears the status bits that I believe are causing the problem.  It would be better, though, to really understand what's causing the problem.

    USB Problem #2.zip

  • Hello Douglas,

    This reported problem has been a very difficult request to investigate because you are really used a stripped down set of commands from the USB library here. This isn't a problem I've seen reported to this point for anyone using the library as is for given layers like CDC / HID etc.

    I reviewed the USB analyzer captures and your code, but really I will need to get a point where I am able to compare what you've uniquely done with your implementation versus how the USB library is supposed to handle the same situation to determine if it is an application code issue or a USB library issue (and if the latter, why hasn't it impacted other applications).

    Unfortunately that level of investigation is something I have not been able to carve out sufficient time to delve into, and I don't really have a current estimate when I will be able to do so as this is above and beyond the level of support we would typically provide for the USB library given the unique implementation.

    Sorry for not replying back sooner, I should have at least given that status report this time last week.

    Best Regards,

    Ralph Jacobi

  • I don't understand what alternatives I had, or how this is such a unique use of the library that you haven't seen it before.

    Unless I am misunderstanding something, anyone using the library as-is for CDC/HID, etc., is making serial-to-USB adapters, mice, and keyboards (or sound devices, or mass storage devices, etc.).  Those are all devices that are so common that they have their own classes defined in the USB specification.  From what I've read in the TivaWare USB Library User's Guide, it seems that my options are to either use an existing class for a device that doesn't fall into any of those categories, or use the low-level API.  Specifically, it says "If an existing USB Device Class Driver is not suitable for your application, you may choose to
    develop your device using the lower-level USB Device API instead."  My only choice, as I see it, was to use the low-level USB API. 

    You must have thousands of customers that are not making a device that already has a class defined, as I and my employer are not.  What did they do?  Wouldn't they also have had to use this stripped-down set of commands because none of the existing class drivers were suitable to their application? or am I the only one?  If there is some alternative that I was supposed to use, what is it?

  • Hello Douglas,

    I have been supporting the USB library since 2017 and this is the first time I've had a customer using APIs like USBDCDRequestDataEP0 / USBDCDSendDataEP0 / USBDCDStallEP0 in the application layer.

    On the few cases I've seen requests for mode that isn't part of the library, it has been for USB defined classes and they took an existing TivaWare class like CDC or HID and adapted it to the omitted USB defined class.

    I haven't once supported a custom class that is absent from USB specification. That's not to say it hasn't been done, but those customers haven't posted requests on E2E and I have no experience on my end with handling such an application.

    Best Regards,

    Ralph Jacobi