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.

Question about TivaWare handling of USB Stall

Other Parts Discussed in Thread: TM4C129XNCZAD

I am using a TM4C129XNCZAD part on a custom board. I am seeing a situation where my USB device (board) is not connecting to a Windows 7 host. I have narrowed it down to the firmware gettng put into a "Stall" state and not ever getting out. If I clear the stall state (i.e. set it to the "Idle" state using the debugger) things work as expected.

I see where the Stall state is set in USBLib\driver\usbdenum.c in the USBDCDStallEP0 () function shown below:

void
USBDCDStallEP0(uint32_t ui32Index)
{
    ASSERT(ui32Index == 0);

    //
    // Stall the endpoint in question.
    //
    MAP_USBDevEndpointStall(USB0_BASE, USB_EP_0, USB_EP_DEV_OUT);

    //
    // Enter the stalled state.
    //
    g_psDCDInst[0].iEP0State = eUSBStateStall;
}

The Stall state is then supposed to be cleared in the USBDeviceEnumHandler function in the same file. the particular case is shown below:

        case eUSBStateStall:
        {
            //
            // If we sent a stall then acknowledge this interrupt.
            //
            if(ui32EPStatus & USB_DEV_EP0_SENT_STALL)
            {
                //
                // Clear the Setup End condition.
                //
                MAP_USBDevEndpointStatusClear(USB0_BASE, USB_EP_0,
                                              USB_DEV_EP0_SENT_STALL);

                //
                // Reset the global end point 0 state to IDLE.
                //
                pDevInstance->iEP0State = eUSBStateIdle;

            }
            break;
        }

Before clearing the Stall state, the code checks the ui32EPStatus variable to see if the USB_DEV_EP0_SENT_STALL bit is set. My question is: how does this bit get set? I see nowhere in the code where this happens. Is this done within the hardware and when the status is read the will be set? If so, is it possible that there is a possible race condition that the status is checked before it is set in hardware? Is there any reason that changing the state to Idle should not take place regardless of the status?


Thanks much,

Jeff
  • Hello Jeff,

    The bit is read from the Endpoint Status register earlier in the code for USBDeviceEnumHandler

    ui32EPStatus = MAP_USBEndpointStatus(USB0_BASE, USB_EP_0);

    The usbdenum.c has the function to stall USBDCDStallEP0 called at multiple places. A code execution trace using print statements may be employed to understand the call of the function viz-a-viz the Device Interrupt Handler.

    Regards,
    Amit
  • Amit,

    Thanks for the quick reply.

    I saw where the status is read, I am asking how the bit gets set in the status register. I am assuming that it is done in ROM/hardware. I also see where/why the call to USBDCDStallEP0 is done. I am questioning the method of how the stall state is cleared. If I move the statement that sets the state to Idle outside of the check for the status bit being set, my problem goes away.

    Thanks,

    Jeff
  • Hello Jeff,

    Yes. When a stall is performed by the CPU, the bits get set in the Status register to indicate that the USB bus has issues a Stall.

    The state is assigned in the USBDCDStallEP0 function call. The only thing I can think of is that while the state is assigned, the actual bit getting set is delayed in HW. If you can add the following statement under case, does it work?

    case eUSBStateStall:
    {
    ui32EPStatus = MAP_USBEndpointStatus(USB0_BASE, USB_EP_0); << ADD THIS>>
    if(ui32EPStatus & USB_DEV_EP0_SENT_STALL)
    {
    //
    // Clear the Setup End condition.
    //
    MAP_USBDevEndpointStatusClear(USB0_BASE, USB_EP_0,
    USB_DEV_EP0_SENT_STALL);

    //
    // Reset the global end point 0 state to IDLE.
    //
    pDevInstance->iEP0State = eUSBStateIdle;

    }

    Regards
    Amit
  • Amit,

    Your change works about half of the time.

    Do you know what triggers the CPU to set the bit? Is it starting the stall state or maybe waiting for an ACK from the host?
    Is there any way to get into the Stall state other then by the firmware? In other words, why are we checking to see if we sent it? Is there any reason not to just change the state back to Idle regardless of how we got there?

    Thanks
  • Hello Jeff,

    Stall is never sent by the host and only the device. There are 2 types of stall

    1. Functional Stall: where the device is unable to send or receive the data and is cleared when the host gets a successful control request rsponse
    2. Protocol stall: When the host sends a unsupported control request like an incorrect Set Configuration.

    It is not clear whether the Stall is being sent during enumeration or during data transfer (highly likely during enumeration). It seems that the status flag is not getting updated w.r.t to the State Machine execution as given by USBLIB. If you add a delay as well, does it change from half of the time to all the time working: which will give us the information that indeed it is a SW vs HW race condition.

    Regards,
    Amit
  • Amit,

    The Stall is indeed happening during Enumeration.

    I added a call to MAP_SysCtlDelay before reading the status. Assuming that a count of 40000 equates to a 1 millisecond delay, I did the following:

    1 mS failed 1st time
    10 mS failed 5th time
    50 mS failed 3rd time
    100 mS failed 1st time
    200 mS failed 2nd time
    1 sec failed first time

    When I change the code to always set the state back to Idle regardless of status (and no delay) it worked 10 consecutive times (I stopped at 10).

    Jeff
  • Hello Jeff,

    And when it fails, you can confirm that STALL is sent on the bus? Also what is the System Frequency the TM4C129 is operating at?

    Regards
    Amit
  • Amit,

    Upon further inspection, the Stall is sent on the bus when an "Out" transaction is sent on the bus during Enumeration when the Host is booting to the BIOS. After that, when Windows starts to boot, the "Get Descriptor" command fails as the device is stuck in the "Stall" state. I have attached a screen shot of the USB Analyzer.

    If you want to download and install the Analyzer software I can send you the capture file and you can look at all of the gory details.

    Jeff

  • Hello Jeff,

    There is a STALL, followed by USB Bus Reset, before the GetDescriptor Device is called. Also this is happening during BIOS boot on the Host.

    (a) If the Host is already booted then does the issue occur?
    (b) What does the host send as the control packet to the EP0 before the STALL is sent bu the device.

    Regards
    Amit
  • Amit,

    a) I assume you are asking if I plug into the Host after it is already booted to Windows, then it works as expected - no issues.

    b) There are a few places where Stall/Stalled status appear. Hopefully you can see what you need from the attachement. If not, please refer to the time stamp of what you want more details on.

    Thanks,

    Jeff

  • Hello Jeff,

    Can you send the file and the instructions to install the software?

    Regards
    Amit
  • Amit,

    You can get the software here: http://www.ellisys.com/products/download/visualusb.msi  Just install it like any other Windows program.

    Once installed, just unzip and open the attached file. There are a number of buttons on the App toolbar to show/hide various items.

    TPI-II E4200 Failed Bootup.zip

  • Hello Jeff,

    I think I understand the issue a little bit better and would like to discuss this with you. I have sent you a PM.

    Regards
    Amit
  • Amit,

    I have attached two more files.

    Jeff

    "TPI-I E4200 Good Bootup" is the older board with the Stellaris part. the other file is the new board with the Tiva part hot-plugged to the Host with Windows already booted.

    Jeff

    USBDebuggerFiles.zip

  • Hello Jeff

    1. The Hot Plug shows that the SETADDRESS is done after the GETDESCRIPTOR_DEVICE and that is why the board works during hot plug
    2. The Good Boot Up shows that the Bus Reset allows the Stellaris device to recover, but not the TM4C device
    2.a. Is the Stellaris device using StellarisWare? There have been changes to the usblib when moving from Stellaris to TM4C device. So it may be a bug in the software as you mentioned earlier. Due to the fact that the SRAM gets cleared but not the state machine, the device continues to be in Stall.

    Regards
    Amit
  • Amit,

    2a. Yes, the device is the LM3S9B90 and it uses Stellarisware release 9453.

    Jeff
  • Hello Jeff,

    I think we would need to debug a bit more here. In the usbdenum.c there is a function called USBDeviceEnumResetHandler which must be called on a Bus Reset.

    What we would need to do is to check if the call of the reset handler initializes the State Info in iEP0State (TM4C12x) and eEP0State (LM3S) when a Bus Reset is invoked.

    Regards
    Amit
  • Amit Ashara said:
    I think we would need to debug a bit more here. In the usbdenum.c there is a function called USBDeviceEnumResetHandler which must be called on a Bus Reset.

    Hi Amit,

    We have discussed on the issue,
    at bus reset event, USBDeviceEnumResetHandler() should reset iEP0State variable into eUSBStateIdle,
    in this topic for TivaWare 2.1.0.12573

    "TivaWare : USB reconnection failure after disconnection in the middle of communication"
    http://e2e.ti.com/support/microcontrollers/tiva_arm/f/908/p/400195/1425501

    Also, we discussed another regular recovery feature of control transfer - "early" SETUP, too.

    Unfortunately, these fixes aren't taken to the newer TivaWare yet.

    Tsuneo

  • Hello Tsuneo

    I think the issue fix got missed being filed into the system. I am filing a bug into the system as well.

    Regards
    Amit
  • Amit,

    I can confirm that adding pDevInstance->iEP0State = eUSBStateIdle; at the end of the USBDeviceEnumResetHandler( ) fixes my issue (it worked 10 out of 10 times). Thanks to Tsuneo and Tomohiko.

    Amit - is there anything you want me to test?

    Jeff
  • Hi Jeff,

    Your log of "TPI-II E4200 Failed Bootup.zip" seems to capture BIOS bootup sequence at PC power up.
     - It is known by Set_Protocol(1) class specific request

    Your HID device sets boot-keyboard flag in the HID interface descriptor.

    Interface_descriptor = {
      .bInterfaceClass    = 0x03,   // HID class
      .bInterfaceSubClass = 0x01,   // boot interface
      .bInterfaceProtocol = 0x01,   // keyboard
    }

    Seeing this flag, BIOS assumes your device as a boot-keyboard.
    And then, BIOS puts Set_Report( output report ), to setup LEDs (shift-lock, caps-lock, etc) on the keyboard.

    If your HID device doesn't have boot-keyboard feature, set above triad to

    Interface_descriptor = {
      .bInterfaceClass    = 0x03,   // HID class
      .bInterfaceSubClass = 0x00,   // no subclass
      .bInterfaceProtocol = 0x00,   // none
    }

    And then, BIOS doesn't put any unexpected request to your device.

    Tsuneo

  • Tsuneo,

    Good catch!

    Thanks!
  • Maybe, your HID device derives from the USB keyboard example.
    The boot-keyboard is defined at these lines of the source.

    \TivaWare_C_Series-2.1.1.71\usblib\device\usbdhidkeyb.c
    
    line 81:
    static uint8_t g_pui8HIDInterface[HIDINTERFACE_SIZE] =
    {
        //
        // HID Device Class Interface Descriptor.
        //
        9,                          // Size of the interface descriptor.
        USB_DTYPE_INTERFACE,        // Type of this descriptor.
        0,                          // The index for this interface.
        0,                          // The alternate setting for this interface.
        1,                          // The number of endpoints used by this
                                    // interface.
        USB_CLASS_HID,              // The interface class
    //    USB_HID_SCLASS_BOOT,        // The interface sub-class.
        USB_HID_SCLASS_NONE
    //    USB_HID_PROTOCOL_KEYB,      // The interface protocol for the sub-class
        USB_HID_PROTOCOL_NONE,
                                    // specified above.
        4,                          // The string index for this interface.
    };

    void *
    USBDHIDKeyboardCompositeInit(uint32_t ui32Index,
                                 tUSBDHIDKeyboardDevice *psHIDKbDevice,
                                 tCompositeEntry *psCompEntry)
    {
        ...
        ...
    line 940:
        psHIDDevice->ui8Subclass = USB_HID_SCLASS_BOOT;   // <-- USB_HID_SCLASS_NONE
        psHIDDevice->ui8Protocol = USB_HID_PROTOCOL_KEYB; // <-- USB_HID_PROTOCOL_NONE
    
    




    Tsuneo

  • Tsuneo,

    My device is actually based on several examples, with the keyboard being one of them. Thanks for the ideas!

    Jeff
  • Amit,

    Do you agree that adding pDevInstance->iEP0State = eUSBStateIdle; at the end of the USBDeviceEnumResetHandler( ) is the best/final fix for this issue?

    Thanks,

    Jeff

  • Hello Jeff,

    Yes, I agree. However based on the testing we will do if any other state variables need to be reset as well, then it would be added.

    Regards
    Amit