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.

TM4C123GE6PM: Help with non-standard USB descriptor handling

Part Number: TM4C123GE6PM

Hello,

I've managed to get the tivaware usblib modified to successfully respond to Microsoft's OS string descriptor - which is a non-standard descriptor type.  A screenshot of this USB transaction exchange is shown below.  This descriptor exchange is denoted by the EE in the data0 setup packet.  

This descriptor exchange looks like it matches all the standard descriptor exchanges perfectly.  See a standard one below.

Now, the issue I'm having is that after windows sends an OS string descriptor it sends a compatible ID descriptor.  I respond to this descriptor request the same way and the response fails.  I have been testing different ways to modify the usblib to respond and they all seem to fail in the same way as shown below.

The error I get on the protocol analyzer states "Invalid PID sequence" and elaborates "an invalid sequence of packets has been observed".

I've tried using the following function to respond with no luck.  I do however see that the DATA1 of the "IN" packet has the correct data.  I'm trying to send the following descriptor response.

const uint8_t g_pui8MicrosoftCompatibleIDFeatureDesc[] =
//uint8_t g_pui8MicrosoftCompatibleIDFeatureDesc[] =
{
     0x28, 0x00, 0x00, 0x00,        // Desc Length
     0x00, 0x01,                    // Version '1.0'
     0x04, 0x00,                    // Compatibility ID descriptor index 0x0004
     0x01,                          // Number of Sections
     0x00, 0x00, 0x00, 0x00,        // RESERVED
     0x00, 0x00, 0x00,              // RESERVED
     0x00,                          // Interface Number (Interface #0)

     0x01,                          // RESERVED
     0x57, 0x49, 0x4E, 0x55,        // (8 bytes) Compatibility ID "WINUSB\0\0"
     0x53, 0x42, 0x00, 0x00,        //

     0x00, 0x00, 0x00, 0x00,        // (8 bytes) Sub-Compatible ID (UNUSED)
     0x00, 0x00, 0x00, 0x00,        //

     0x00, 0x00, 0x00, 0x00,        // (6 bytes) RESERVED
     0x00, 0x00,                     //
};

with this function:

USBDCDSendDataEP0(0, g_pui8MicrosoftCompatibleIDFeatureDesc, 40);

Currently for testing I've also tried the following to respond.

            //
            // Need to ACK the data on end point 0 without setting last data as there
            // will be a data phase.
            //
            MAP_USBDevEndpointDataAck(USB0_BASE, USB_EP_0, false);


            //
            //
            // Return the externally specified MS OS string descriptor.
            //
            g_psDCDInst[0].pui8EP0Data =
                (uint8_t *)g_pui8MicrosoftCompatibleIDFeatureDesc;

            //
            // The total size of a string descriptor is in byte 0.
            //
            g_psDCDInst[0].ui32EP0DataRemain =
                    g_pui8MicrosoftCompatibleIDFeatureDesc[0];


            //
            // Save the total size of the data sent.
            //
            g_psDCDInst[0].ui32OUTDataSize =
                    g_pui8MicrosoftCompatibleIDFeatureDesc[0];


            USBDEP0StateTx(0);

These code snippets are found in the following function in the usbdenum.c file.

//*****************************************************************************
//
// This internal function reads a request data packet and dispatches it to
// either a standard request handler or the registered device request
// callback depending upon the request type.
//
// \return None.
//
//*****************************************************************************
static void
USBDReadAndDispatchRequest(uint32_t ui32Index)
{
    uint32_t ui32Size;
    tUSBRequest *psRequest;

    //
    // Cast the buffer to a request structure.
    //
    psRequest = (tUSBRequest *)g_pui8DataBufferIn;

    //
    // Set the buffer size.
    //
    ui32Size = EP0_MAX_PACKET_SIZE;

    //
    // Get the data from the USB controller end point 0.
    //
    MAP_USBEndpointDataGet(USB0_BASE, USB_EP_0, g_pui8DataBufferIn,
                           &ui32Size);

    //
    // If there was a null setup packet then just return.
    //
    if(!ui32Size)
    {
        return;
    }

    //
    // See if this is a standard request or not.
    //
    if((psRequest->bmRequestType & USB_RTYPE_TYPE_M) != USB_RTYPE_STANDARD)
    {
        //
        // Since this is not a standard request, see if there is
        // an external handler present.
        //
        if(g_ppsDevInfo[0]->psCallbacks->pfnRequestHandler)
        {   // !!RV!! maybe pass entire g_psDCDInst instead of just pvCBData to
            // get access to EP0 stuff. - doesn't work due to casting but can
            // add the main g_... pointer to pvCBData, but usbdbulk.c doesn't
            // know about g_psDCDInst data type so can't use it.
            //g_psDCDInst[0].pvCBData = (void*)g_psDCDInst;
            //g_ppsDevInfo[0]->psCallbacks->pfnRequestHandler(
            //                                        g_psDCDInst[0].pvCBData,
            //                                        psRequest);

            //USBDCDSendDataEP0(0, g_pui8MicrosoftCompatibleIDFeatureDesc, 40);
            //USBDCDStallEP0(0);


            //
            // Need to ACK the data on end point 0 without setting last data as there
            // will be a data phase.
            //
            MAP_USBDevEndpointDataAck(USB0_BASE, USB_EP_0, false);


            //
            //
            // Return the externally specified MS OS string descriptor.
            //
            g_psDCDInst[0].pui8EP0Data =
                (uint8_t *)g_pui8MicrosoftCompatibleIDFeatureDesc;

            //
            // The total size of a string descriptor is in byte 0.
            //
            g_psDCDInst[0].ui32EP0DataRemain =
                    g_pui8MicrosoftCompatibleIDFeatureDesc[0];


            //
            // Save the total size of the data sent.
            //
            g_psDCDInst[0].ui32OUTDataSize =
                    g_pui8MicrosoftCompatibleIDFeatureDesc[0];


            USBDEP0StateTx(0);

        }
        else
        {
            //
            // If there is no handler then stall this request.
            //
            USBDCDStallEP0(0);
        }
    }
    else
    {
        //
        // Assure that the jump table is not out of bounds.
        //
        if((psRequest->bRequest <
           (sizeof(g_psUSBDStdRequests) / sizeof(tStdRequest))) &&
           (g_psUSBDStdRequests[psRequest->bRequest] != 0))
        {
            //
            // Jump table to the appropriate handler.
            //
            g_psUSBDStdRequests[psRequest->bRequest](&g_psDCDInst[0],
                                                     psRequest);
        }
        else
        {
            //
            // If there is no handler then stall this request.
            //
            USBDCDStallEP0(0);
        }
    }
}

I've also tried using the handler/callback approach, but because this isn't working I'm simplifying the situation to add the code I need to respond to this descriptor directly in the dispatch handler functinon call area for non-standard descriptors.  Does anyone know why these packet/PID sequence would be out of order with these (rough at the moment) code mods to handle the descriptor requests?

It seem strange that handling the OS descriptor (first one) works fine with the following code.  You can see here I added an if statement to find the 0xEE value and respond to this descriptor in the string handler case towards the bottom.

//*****************************************************************************
//
// This function handles the GET_DESCRIPTOR standard USB request.
//
// \param pvInstance is the USB device controller instance data.
// \param psUSBRequest holds the data for this request.
//
// This function will return most of the descriptors requested by the host
// controller.  The descriptor specified by \e
// pvInstance->psInfo->pui8DeviceDescriptor will be returned when the device
// descriptor is requested.  If a request for a specific configuration
// descriptor is made, then the appropriate descriptor from the \e
// g_pConfigDescriptors will be returned.  When a request for a string
// descriptor is made, the appropriate string from the
// \e pvInstance->psInfo->pStringDescriptors will be returned.  If the
// \e pvInstance->psInfo->psCallbacks->GetDescriptor is specified it will be
// called to handle the request.  In this case it must call the
// USBDCDSendDataEP0() function to send the data to the host controller.  If
// the callback is not specified, and the descriptor request is not for a
// device, configuration, or string descriptor then this function will stall
// the request to indicate that the request was not supported by the device.
//
// \return None.
//
//*****************************************************************************
static void
USBDGetDescriptor(void *pvInstance, tUSBRequest *psUSBRequest)
{
    bool bConfig;
    tDCDInstance *psUSBControl;
    tDeviceInfo *psDevice;
    const tConfigHeader *psConfig;
    const tDeviceDescriptor *psDeviceDesc;
    uint8_t ui8Index;
    int32_t i32Index;

    ASSERT(psUSBRequest != 0);
    ASSERT(pvInstance != 0);

    //
    // Create the device information pointer.
    //
    psUSBControl = (tDCDInstance *)pvInstance;
    psDevice = g_ppsDevInfo[0];

    //
    // Need to ACK the data on end point 0 without setting last data as there
    // will be a data phase.
    //
    MAP_USBDevEndpointDataAck(USB0_BASE, USB_EP_0, false);

    //
    // Assume we are not sending the configuration descriptor until we
    // determine otherwise.
    //
    bConfig = false;

    //
    // Which descriptor are we being asked for?
    //
    switch(psUSBRequest->wValue >> 8)
    {
        //
        // This request was for a device descriptor.
        //
        case USB_DTYPE_DEVICE:
        {
            //
            // Return the externally provided device descriptor.
            //
            psUSBControl->pui8EP0Data =
                                    (uint8_t *)psDevice->pui8DeviceDescriptor;

            //
            // The size of the device descriptor is in the first byte.
            //
            psUSBControl->ui32EP0DataRemain =
                psDevice->pui8DeviceDescriptor[0];

            break;
        }

        //
        // This request was for a configuration descriptor.
        //
        case USB_DTYPE_CONFIGURATION:
        {
            //
            // Which configuration are we being asked for?
            //
            ui8Index = (uint8_t)(psUSBRequest->wValue & 0xFF);

            //
            // Is this valid?
            //
            psDeviceDesc =
                (const tDeviceDescriptor *)psDevice->pui8DeviceDescriptor;

            if(ui8Index >= psDeviceDesc->bNumConfigurations)
            {
                //
                // This is an invalid configuration index.  Stall EP0 to
                // indicate a request error.
                //
                USBDCDStallEP0(0);
                psUSBControl->pui8EP0Data = 0;
                psUSBControl->ui32EP0DataRemain = 0;
            }
            else
            {
                //
                // Return the externally specified configuration descriptor.
                //
                psConfig = psDevice->ppsConfigDescriptors[ui8Index];

                //
                // Start by sending data from the beginning of the first
                // descriptor.
                //
                psUSBControl->ui8ConfigSection = 0;
                psUSBControl->ui16SectionOffset = 0;
                psUSBControl->pui8EP0Data =
                                (uint8_t *)psConfig->psSections[0]->pui8Data;

                //
                // Determine the total size of the configuration descriptor
                // by counting the sizes of the sections comprising it.
                //
                psUSBControl->ui32EP0DataRemain =
                                            USBDCDConfigDescGetSize(psConfig);

                //
                // Remember that we need to send the configuration descriptor
                // and which descriptor we need to send.
                //
                psUSBControl->ui8ConfigIndex = ui8Index;

                bConfig = true;
            }
            break;
        }

        //
        // This request was for a string descriptor.
        //
        case USB_DTYPE_STRING:
        {
            //
            // Determine the correct descriptor index based on the requested
            // language ID and index.
            //
            i32Index = USBDStringIndexFromRequest(psUSBRequest->wIndex,
                                                  psUSBRequest->wValue & 0xFF);

            //
            // If the mapping function returned -1 then stall the request to
            // indicate that the request was not valid.
            //
            if(i32Index == -1)
            {
                USBDCDStallEP0(0);
                break;
            }

            //
            // !!RV!!
            // Check for microsoft OS string descriptor and if found
            // handle.  Break immediatley after handling.
            //
            if(i32Index == 0xEE)
            {

                //
                //
                // Return the externally specified MS OS string descriptor.
                //
                psUSBControl->pui8EP0Data =
                    (uint8_t *)g_pui8MicrosoftOSString;

                //
                // The total size of a string descriptor is in byte 0.
                //
                psUSBControl->ui32EP0DataRemain =
                        g_pui8MicrosoftOSString[0];



                //USBDCDStallEP0(0);
                break;
            }
            //
            //
            // Return the externally specified configuration descriptor.
            //
            psUSBControl->pui8EP0Data =
                (uint8_t *)psDevice->ppui8StringDescriptors[i32Index];

            //
            // The total size of a string descriptor is in byte 0.
            //
            psUSBControl->ui32EP0DataRemain =
                psDevice->ppui8StringDescriptors[i32Index][0];

            break;
        }

        //
        // Any other request is not handled by the default enumeration handler
        // so see if it needs to be passed on to another handler.
        //
        default:
        {
            //
            // If there is a handler for requests that are not handled then
            // call it.
            //
            if(psDevice->psCallbacks->pfnGetDescriptor)
            {
                psDevice->psCallbacks->pfnGetDescriptor(g_psDCDInst[0].pvCBData,
                                                      psUSBRequest);
            }
            else
            {
                //
                // Whatever this was this handler does not understand it so
                // just stall the request.
                //
                USBDCDStallEP0(0);
            }

            return;
        }
    }

    //
    // If this request has data to send, then send it.
    //
    if(psUSBControl->pui8EP0Data)
    {
        //
        // If there is more data to send than is requested then just
        // send the requested amount of data.
        //
        if(psUSBControl->ui32EP0DataRemain > psUSBRequest->wLength)
        {
            psUSBControl->ui32EP0DataRemain = psUSBRequest->wLength;
        }

        //
        // Now in the transmit data state.  Be careful to call the correct
        // function since we need to handle the configuration descriptor
        // differently from the others.
        //
        if(!bConfig)
        {
            USBDEP0StateTx(0);
        }
        else
        {
            USBDEP0StateTxConfig(0);
        }
    }
}

So I'm stuck here trying to debug this issue of the non-standard descriptor handling not working.  Even TI's documentation says the first function should be able to handle this "I think".

Any help is greatly appreciated!

  • Hi Robert,

      I wish I could provide more guidance on what is wrong. Looking at your frame 97 and 101, I don't even know what to expect. Will you be able to do some Google searches for a working ID descriptor exchange example with USB analyzer for a non-standard string descriptor? It does not need to be a Tiva device but any device that shows how the device exchanges the string and ID descriptors with the MS OS host in a non-standard format. I think it will help to understand what is the expected data and sequence to be returned by the device. 

      

  • Hi Charles,

    I have been searching around for a few days.  It is so strange because the non-standard "string" descriptor works fine, but the same approach doesn't work for a "non-standard" descriptor.  It looks like there are basically two approaches to this issue.  

    Approach #1 is to use this per the usblib user's guide:

    //*****************************************************************************
    //
    //! This function requests transfer of data to the host on endpoint zero.
    //!
    //! \param ui32Index is the index of the USB controller which is to be used to
    //! send the data.
    //! \param pui8Data is a pointer to the buffer to send via endpoint zero.
    //! \param ui32Size is the amount of data to send in bytes.
    //!
    //! This function handles sending data to the host when a custom command is
    //! issued or non-standard descriptor has been requested on endpoint zero.  If
    //! the application needs notification when this is complete,
    //! <tt>psCallbacks->pfnDataSent</tt> in the tDeviceInfo structure must
    //! contain a valid function pointer.  This callback could be used to free up
    //! the buffer passed into this function in the \e pui8Data parameter.  The
    //! contents of the \e pui8Data buffer must remain unchanged until the
    //! <tt>pfnDataSent</tt> callback is received.
    //!
    //! \return None.
    //
    //*****************************************************************************
    void
    USBDCDSendDataEP0(uint32_t ui32Index, uint8_t *pui8Data, uint32_t ui32Size)
    {
        ASSERT(ui32Index == 0);
    
        //
        // Return the externally provided device descriptor.
        //
        g_psDCDInst[0].pui8EP0Data = pui8Data;
    
        //
        // The size of the device descriptor is in the first byte.
        //
        g_psDCDInst[0].ui32EP0DataRemain = ui32Size;
    
        //
        // Save the total size of the data sent.
        //
        g_psDCDInst[0].ui32OUTDataSize = ui32Size;
    
        //
        // Now in the transmit data state.
        //
        USBDEP0StateTx(0);
    }

    Approach #2 is to use the pointer/structure as TI does in the enumeration.c file specifically in the same way TI handles standard string descriptors.

    Function call heading:

    //*****************************************************************************
    //
    // This function handles the GET_DESCRIPTOR standard USB request.
    //
    // \param pvInstance is the USB device controller instance data.
    // \param psUSBRequest holds the data for this request.
    //
    // This function will return most of the descriptors requested by the host
    // controller.  The descriptor specified by \e
    // pvInstance->psInfo->pui8DeviceDescriptor will be returned when the device
    // descriptor is requested.  If a request for a specific configuration
    // descriptor is made, then the appropriate descriptor from the \e
    // g_pConfigDescriptors will be returned.  When a request for a string
    // descriptor is made, the appropriate string from the
    // \e pvInstance->psInfo->pStringDescriptors will be returned.  If the
    // \e pvInstance->psInfo->psCallbacks->GetDescriptor is specified it will be
    // called to handle the request.  In this case it must call the
    // USBDCDSendDataEP0() function to send the data to the host controller.  If
    // the callback is not specified, and the descriptor request is not for a
    // device, configuration, or string descriptor then this function will stall
    // the request to indicate that the request was not supported by the device.
    //
    // \return None.
    //
    //*****************************************************************************
    static void
    USBDGetDescriptor(void *pvInstance, tUSBRequest *psUSBRequest)

    In this function case 

            //
            // This request was for a string descriptor.
            //
            case USB_DTYPE_STRING:
            {
                //
                // Determine the correct descriptor index based on the requested
                // language ID and index.
                //
                i32Index = USBDStringIndexFromRequest(psUSBRequest->wIndex,
                                                      psUSBRequest->wValue & 0xFF);
    
                //
                // If the mapping function returned -1 then stall the request to
                // indicate that the request was not valid.
                //
                if(i32Index == -1)
                {
                    USBDCDStallEP0(0);
                    break;
                }
    
                //
                // Return the externally specified configuration descriptor.
                //
                psUSBControl->pui8EP0Data =
                    (uint8_t *)psDevice->ppui8StringDescriptors[i32Index];
    
                //
                // The total size of a string descriptor is in byte 0.
                //
                psUSBControl->ui32EP0DataRemain =
                    psDevice->ppui8StringDescriptors[i32Index][0];
    
                break;
            }

    not forgetting the actual TX function at the end:

    USBDEP0StateTx(0);

    Is there anyone who can verify these are either both good approaches, both bad approaches or one good one bad etc...?  After trudging through this a while the driverlib seems to make pretty good sense and in both cases you end up at the same place once you look through the code "under the hood" for EP0 tx'ing.  The protocol analyzer also shows there is no DATA out transaction as the other string descriptors have.

    I see that the setup transaction AND the DATA IN transaction are all ACK'd correctly as well and the PID values for these transactions all look correct.  I would expect at this point using the following function per TI usblib user's guide would at least work as the base case.

    USBDCDSendDataEP0(uint32_t ui32Index, uint8_t *pui8Data, uint32_t ui32Size)

    Is there someone this post could be forwarded to that works on usb driver code?

  • Hi Robert,

    Is there anyone who can verify these are either both good approaches, both bad approaches or one good one bad etc...?  After trudging through this a while the driverlib seems to make pretty good sense and in both cases you end up at the same place once you look through the code "under the hood" for EP0 tx'ing.  The protocol analyzer also shows there is no DATA out transaction as the other string descriptors have.

    I see that the setup transaction AND the DATA IN transaction are all ACK'd correctly as well and the PID values for these transactions all look correct.  I would expect at this point using the following function per TI usblib user's guide would at least work as the base case.

    Sorry, I don't really know which method is preferred. I will suggest you try approach 2 as I hope you just need to extend the current string descriptor handling rather than starting from scratch.

    Is there someone this post could be forwarded to that works on usb driver code?

      Unfortunately, the USB library driver developers are no longer with us. 

  • Hey Charles,

    That sucks but I understand this usb driver lib was probably written a long time ago.  I actually finally figured out what was going on and it looks like TI's code is working fine after some basic mods.  As posted above I tried a few ways and the issue wasn't the driverlib code, but the approach to handling the Microsoft compaitible id feature descriptors.  It turns out the crux of my problem was that this descriptor handling is two parts, not one part.  Microsoft sends a request for JUST the header of the descriptor and then a request for the whole thing.  You can see more info about this here - I recommend downloading the Microsoft os descriptor spec 1.0 and going through it if others have this issue.

    https://learn.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-os-1-0-descriptors-specification

    I do have one question however now that I'm past enumeration and on to an attempted bulk transfer and I'd like to know if there is a "recommended" approach from you guys.  As you can see in the below code this is the function used to transmit bulk device data back to the host.  The thing to note here is there is no option to choose different endpoints. 

    I am trying to get all the endpoints up and running so I can have multiple pipes.  Upon configuring this device to support all 8x USB endpoints in the Tiva part, how do you tell the driver library to transmit using specific endpoints?  Notice below this code is hardcoded to use the endpoint in psInst object.

        //
        // Copy the data into the USB endpoint FIFO.
        //
        i32Retcode = MAP_USBEndpointDataPut(psInst->ui32USBBase,
                                            psInst->ui8INEndpoint,
                                            pi8Data, ui32Length);

    In the overall function:

    uint32_t
    USBDBulkPacketWrite(void *pvBulkDevice, uint8_t *pi8Data, uint32_t ui32Length,
                        bool bLast)
    {
        tBulkInstance *psInst;
        int32_t i32Retcode;
    
        ASSERT(pvBulkDevice);
    
        //
        // Get a pointer to the bulk device instance data pointer
        //
        psInst = &((tUSBDBulkDevice *)pvBulkDevice)->sPrivateData;
    
        //
        // Can we send the data provided?
        //
        if((ui32Length > g_ui16MaxPacketSize) ||
           (psInst->iBulkTxState != eBulkStateIdle))
        {
            //
            // Either the packet was too big or we are in the middle of sending
            // another packet.  Return 0 to indicate that we can't send this data.
            //
            return(0);
        }
    
        //
        // Copy the data into the USB endpoint FIFO.
        //
        i32Retcode = MAP_USBEndpointDataPut(psInst->ui32USBBase,
                                            psInst->ui8INEndpoint,
                                            pi8Data, ui32Length);
    
        //
        // Did we copy the data successfully?
        //
        if(i32Retcode != -1)
        {
            //
            // Remember how many bytes we sent.
            //
            psInst->ui16LastTxSize += (uint16_t)ui32Length;
    
            //
            // If this is the last call for this packet, schedule transmission.
            //
            if(bLast)
            {
                //
                // Send the packet to the host if we have received all the data we
                // can expect for this packet.
                //
                psInst->iBulkTxState = eBulkStateWaitData;
                i32Retcode = MAP_USBEndpointDataSend(psInst->ui32USBBase,
                                                     psInst->ui8INEndpoint,
                                                     USB_TRANS_IN);
            }
        }
    
        //
        // Did an error occur while trying to send the data?
        //
        if(i32Retcode != -1)
        {
            //
            // No - tell the caller we sent all the bytes provided.
            //
            return(ui32Length);
        }
        else
        {
            //
            // Yes - tell the caller we could not send the data.
            //
            return(0);
        }
    }

    Also note with my previous problem - the protocol analyzer was erroring out on PID sequence because the first descriptor transfer that was supposed to be the header only (16 bytes of header data) was the entire header (40 bytes of data) and this was acting as a kind of buffer over-run.  The bytes in addition to the expected 16 were being interpreted as a new PID and were wrong.  I think you can see this in the protocol analyzer screenshots where the setup transaction data stage is reqeuesting 16 bytes of data and I'm sending back 40.  Hope this is helpful to someone.

    Charles - let me know what you think the "proper" way to send data using different/multiple endpoints using usblib and/or if I need to modify this send function and add an endpoint parameter or if there is a cleaner way to do it I'm missing.

  • Hi Robert,

    I am trying to get all the endpoints up and running so I can have multiple pipes.  Upon configuring this device to support all 8x USB endpoints in the Tiva part, how do you tell the driver library to transmit using specific endpoints?  Notice below this code is hardcoded to use the endpoint in psInst object.

    I don't know for sure if the description below will clarify your question. It seems based on the description, the host decides which pipe to take data from. The number of endpoints you have configured should be part of the enumeration you have already established. I hope the USB library somehow knows which endpoint to send the data based on the requested pipe/endpoint from the host. 

    18.6.1.4 Scheduling
    The Device has no control over the scheduling of transactions as scheduling is determined by the
    Host controller. The USB controller can set up a transaction at any time.
    The USB controller waits for the request from the Host controller and generates an interrupt when
    the transaction is complete or if it was terminated due to some error. If the Host controller makes a
    request and the Device controller is not ready, the USB controller sends a busy response (NAK) to
    all requests until it is ready.

    Also note with my previous problem - the protocol analyzer was erroring out on PID sequence because the first descriptor transfer that was supposed to be the header only (16 bytes of header data) was the entire header (40 bytes of data) and this was acting as a kind of buffer over-run.  The bytes in addition to the expected 16 were being interpreted as a new PID and were wrong.  I think you can see this in the protocol analyzer screenshots where the setup transaction data stage is reqeuesting 16 bytes of data and I'm sending back 40.  Hope this is helpful to someone.

    Thank you! You meant to say that you were supposed to return only 16 bytes for the header but instead you return 40 bytes. Glad you you got the enumeration resolved. I certainly learn something and this will for sure help others developing a custom string descriptor. 

  • Hey Charles,

    Thank you for the response.  Yea this is tricky and debugging the driverlib has been an experience.  Speaking of that do you know where I can find the definitions of the ROM_ functions like the one below?  I'm trying to debug more driverlib code (IN transaction isn't working) and this is the key function I need to understand.  It is in usbdenum.c in the usb event handler.

    MAP_USBIntStatusEndpoint(USB0_BASE);

  • Hi Robert,

      Please refer to the below description on how MAP_ and ROM_ are used. Basically, if the function is available in the ROM, then the function will be run out of the ROM. If not, it will be run from drivelib.lib which is stored in the flash when you link the function. To debug this function, I will suggest you just call USBIntStatusEndpoint directly instead of MAP_USBIntStatusEndpoint. By doing so, you will be able to single step the source code of USBIntStatusEndpoint. If you use MAP_USBIntStatusEndpoint, then it will jump to the ROM and debugging only be done via disassembly window which is much more difficult. 

    7.4 ROM and MAP TivaWare Prefixes

    In order to help minimize Flash space, TM4C microcontrollers have a version of TivaWare’s DriverLib loaded into ROM memory. However, the DriverLib included within the ROM is an older version and any updated functions or new functions must be executed via Flash using the latest TivaWare DriverLib. To minimize the burden of understanding which functions that are loaded in ROM are still up to date and which ones must be executed from Flash, TivaWare includes a mapping file that is used to determine whether to use a ROM function or a Flash function. With this setup, there are three possible function calls for each DriverLib function. The generic call, a ROM prefixed call (functions starting with 'ROM_'), and a MAP prefixed call (functions starting with 'MAP_'). For all ROM prefixed calls, the rom.h header file is required, and it will select the correct function from the ROM memory map to execute. If the function does not exist in ROM, then a compiler error will indicate that function is not available. In some cases, older ROM functions that have errata associated with them are removed from rom.h to avoid misuse, and in those cases the DriverLib function from the latest TivaWare should be used. In other cases, the ROM function may not exist at all. This adds a level of complexity that may be undesirable for programmers. To simplify this process, the MAP prefix is offered as the third option for a function call. All DriverLib function calls have an equivalent defined within the rom_map.h header file based on whether a ROM version exists or not. Therefore, using the MAP prefix takes all the guesswork out of when to use ROM or Flash DriverLib functions while gaining the benefit of minimizing the Flash footprint for DriverLib. Once rom_map.h is included in an application file simply add the MAP prefix to all DriverLib functions to leverage the benefits of all available ROM functions.

  • Hi Charles,

    Thanks this worked great! See attached image.

    I'm now able to see OUT transactions successful using libusb and can transmit data in OUT transactions to the Tiva part.  I however can't get an IN transaction to work.  I am noticing on my packet sniffer the transaction times out.  I also notice that the TI driver library doesn't see the interrupt status bit for my EP1 from the USBTXIS register.  It seems that the RX interrupts work fine, but the TX interrupts don't.  I've tracked this through the usblib all the way to these registers.  

    In usbdenum.c the following code (I think) is the interrupt handler that seems to call the callback for receiving data fine from the host, but nothing when an IN transaction happens.  I can see that when I send an OUT transaction from the host EP1 interrupt status bit is set and therefore "ui32Status" isn't zero calling the callback for the endpoint handler.  So my packet sniffer sees an IN transaction timeout from the host, but the interrupt for EP1 received data isn't thrown.  I also verified all the bits in registers TXIE and RXIE are set to 1.

    void
    USBDeviceIntHandlerInternal(uint32_t ui32Index, uint32_t ui32Status)
    {
        static uint32_t ui32SOFDivide = 0;
        void *pvInstance;
        uint32_t ui32DMAIntStatus;
        uint32_t ui32LPMStatus;
    
        //
        // If device initialization has not been performed then just disconnect
        // from the USB bus and return from the handler.
        //
        if(g_ppsDevInfo[0] == 0)
        {
            MAP_USBDevDisconnect(USB0_BASE);
            return;
        }
    
        pvInstance = g_psDCDInst[0].pvCBData;
    
        //
        // Received a reset from the host.
        //
        if(ui32Status & USB_INTCTRL_RESET)
        {
            USBDeviceEnumResetHandler(&g_psDCDInst[0]);
        }
    
        //
        // Suspend was signaled on the bus.
        //
        if(ui32Status & USB_INTCTRL_SUSPEND)
        {
            //
            // Call the SuspendHandler() if it was specified.
            //
            if(g_ppsDevInfo[0]->psCallbacks->pfnSuspendHandler)
            {
                g_ppsDevInfo[0]->psCallbacks->pfnSuspendHandler(pvInstance);
            }
        }
    
        //
        // Resume was signaled on the bus.
        //
        if(ui32Status & USB_INTCTRL_RESUME)
        {
            //
            // Call the ResumeHandler() if it was specified.
            //
            if(g_ppsDevInfo[0]->psCallbacks->pfnResumeHandler)
            {
                g_ppsDevInfo[0]->psCallbacks->pfnResumeHandler(pvInstance);
            }
        }
    
        //
        // USB device was disconnected.
        //
        if(ui32Status & USB_INTCTRL_DISCONNECT)
        {
            //
            // Call the DisconnectHandler() if it was specified.
            //
            if(g_ppsDevInfo[0]->psCallbacks->pfnDisconnectHandler)
            {
                g_ppsDevInfo[0]->psCallbacks->pfnDisconnectHandler(pvInstance);
            }
        }
    
        //
        // Start of Frame was received.
        //
        if(ui32Status & USB_INTCTRL_SOF)
        {
            //
            // Increment the global Start of Frame counter.
            //
            g_ui32USBSOFCount++;
    
            //
            // Increment our SOF divider.
            //
            ui32SOFDivide++;
    
            //
            // Handle resume signaling if required.
            //
            USBDeviceResumeTickHandler(&g_psDCDInst[0]);
    
            //
            // Have we counted enough SOFs to allow us to call the tick function?
            //
            if(ui32SOFDivide == USB_SOF_TICK_DIVIDE)
            {
                //
                // Yes - reset the divider and call the SOF tick handler.
                //
                ui32SOFDivide = 0;
                InternalUSBStartOfFrameTick(USB_SOF_TICK_DIVIDE);
            }
        }
    
        //
        // Handle LPM interrupts.
        //
        ui32LPMStatus = USBLPMIntStatus(USB0_BASE);
    
        //
        // The host LPM resume request has been acknowledged, allow the device
        // class to handle the sleep state.
        //
        if((g_psDCDInst[0].ui32LPMState == USBLIB_LPM_STATE_SLEEP) &&
           ((ui32LPMStatus & (USB_INTLPM_ACK | USB_INTLPM_RESUME)) ==
            USB_INTLPM_RESUME))
        {
            //
            // Notify the class of the wake from LPM L1.
            //
            if(g_ppsDevInfo[0]->psCallbacks->pfnDeviceHandler)
            {
                g_ppsDevInfo[0]->psCallbacks->pfnDeviceHandler(pvInstance,
                                                    USB_EVENT_LPM_RESUME,
                                                    (void *)0);
            }
    
            //
            // Now back in the awake state.
            //
            g_psDCDInst[0].ui32LPMState = USBLIB_LPM_STATE_AWAKE;
    
            //
            // Enable receiving of LPM packet.
            //
            USBDevLPMEnable(USB0_BASE);
        }
        //
        // The host LPM sleep request has been acknowledged, allow the device
        // class to handle the sleep state.
        //
        else if((g_psDCDInst[0].ui32LPMState == USBLIB_LPM_STATE_AWAKE) &&
                ((ui32LPMStatus & (USB_INTLPM_ACK | USB_INTLPM_RESUME)) ==
                 USB_INTLPM_ACK))
        {
            if(g_ppsDevInfo[0]->psCallbacks->pfnDeviceHandler)
            {
                g_ppsDevInfo[0]->psCallbacks->pfnDeviceHandler(pvInstance,
                                                    USB_EVENT_LPM_SLEEP,
                                                    (void *)0);
            }
    
            //
            // Now back in the sleep state.
            //
            g_psDCDInst[0].ui32LPMState = USBLIB_LPM_STATE_SLEEP;
        }
        else if(ui32LPMStatus & USB_INTLPM_NYET)
        {
            //
            // The device has held off the sleep state because LPM
            // responses are disabled.
            //
            if(g_ppsDevInfo[0]->psCallbacks->pfnDeviceHandler)
            {
                g_ppsDevInfo[0]->psCallbacks->pfnDeviceHandler(pvInstance,
                                                    USB_EVENT_LPM_ERROR,
                                                    (void *)0);
            }
        }
    
        //
        // Get the controller interrupt status.
        //
        //ui32Status = MAP_USBIntStatusEndpoint(USB0_BASE);
        ui32Status = USBIntStatusEndpoint(USB0_BASE);
    
        //
        // Handle end point 0 interrupts.
        //
        if(ui32Status & USB_INTEP_0)
        {
            USBDeviceEnumHandler(&g_psDCDInst[0]);
            ui32Status &= ~USB_INTEP_0;
        }
    
        //
        // Check to see if any DMA transfers are pending
        //
        ui32DMAIntStatus = USBLibDMAIntStatus(g_psDCDInst[0].psDMAInstance);
    
        if(ui32DMAIntStatus)
        {
            //
            // Handle any DMA interrupt processing.
            //
            USBLibDMAIntHandler(g_psDCDInst[0].psDMAInstance, ui32DMAIntStatus);
        }
    
        //
        // Because there is no way to detect if a uDMA interrupt has occurred,
        // check for an endpoint callback and call it if it is available.
        //
        if((g_ppsDevInfo[0]->psCallbacks->pfnEndpointHandler) &&
           ((ui32Status != 0) || (ui32DMAIntStatus != 0)))
        {
            g_ppsDevInfo[0]->psCallbacks->pfnEndpointHandler(pvInstance, ui32Status);
        }
    }

    Endpoint handler callback code

        //
        // Because there is no way to detect if a uDMA interrupt has occurred,
        // check for an endpoint callback and call it if it is available.
        //
        if((g_ppsDevInfo[0]->psCallbacks->pfnEndpointHandler) &&
           ((ui32Status != 0) || (ui32DMAIntStatus != 0)))
        {
            g_ppsDevInfo[0]->psCallbacks->pfnEndpointHandler(pvInstance, ui32Status);
        }

    For some reason I can't upload screenshots...will try again soon.

  • In usbdenum.c the following code (I think) is the interrupt handler that seems to call the callback for receiving data fine from the host, but nothing when an IN transaction happens.  I can see that when I send an OUT transaction from the host EP1 interrupt status bit is set and therefore "ui32Status" isn't zero calling the callback for the endpoint handler.  So my packet sniffer sees an IN transaction timeout from the host, but the interrupt for EP1 received data isn't thrown

    Hi Robert,

      I will suggest you try out a standard descriptor type transaction and see how these register bits and status are getting set and how the callback is getting called in the interrupt handler for both IN and OUT transactions. Hopefully, this way you can compare the subtle differences on how the usb library behaves between the standard and non-standard string descriptors. 

  • Hey Charles,

    Its possible I have a misunderstanding of the driver operation regarding IN transactions.  Two quick questions for this bulk launchpad example.

    1. Should the Tiva part be alerted via RX endpoint interrupt that an IN request has happened so it can fetch the data to send back?

    2. Should the data be loaded into the ring buffer before the host issues an IN transaction?

  • Hi Robert,

    1. Should the Tiva part be alerted via RX endpoint interrupt that an IN request has happened so it can fetch the data to send back?

    Normally, IN and OUT are specified with respect to the host. Therefore, IN means reading/receiving from the USB host. I suppose for IN transactions, it should correspond to a TX transaction from the slave device. 

    2. Should the data be loaded into the ring buffer before the host issues an IN transaction?

    Although the buffer is optional, the usb_dev_example does use it to improve efficiency. 

    #define BULK_BUFFER_SIZE 256

    //*****************************************************************************
    //
    // Transmit buffer (from the USB perspective).
    //
    //*****************************************************************************
    uint8_t g_pui8USBTxBuffer[BULK_BUFFER_SIZE];
    tUSBBuffer g_sTxBuffer =
    {
    true, // This is a transmit buffer.
    TxHandler, // pfnCallback
    (void *)&g_sBulkDevice, // Callback data is our device pointer.
    USBDBulkPacketWrite, // pfnTransfer
    USBDBulkTxPacketAvailable, // pfnAvailable
    (void *)&g_sBulkDevice, // pvHandle
    g_pui8USBTxBuffer, // pi8Buffer
    BULK_BUFFER_SIZE, // ui32BufferSize
    };

  • I understand that - the core of this issue is that the Tiva part isn't being alerted to an IN transaction from the host.  How is the device notified of an IN transaction from the host so it can retrieve data to send and load these buffers?

  • Hi Robert,

      Not sure if this will help. I run the stock usb_dev_bulk example and capture the callstack for both IN and OUT transactions. The USB0DeviceIntHandler() is used for both IN and OUT transactions. The HandleEndpoints() function should divert to either ProcessDataToHost() or ProcessDataFromHost() depending if is a IN or OUT transaction. 

    For OUT transactions:

    For IN transactions. 

  • Ok - this seems to be where my problem lies.  I am not seeing any interrupt activity in any handlers including the "USB0DeviceIntHandler()" when sending an IN transaction from the host.  I will report back if I can figure anything out. 

    I also noticed that this example doesn't seem to be able to keep up with a large OUT data chunk transaction from the host.  A traffic capture shows some of the packets being NAK'd because the device isn't keeping up.

  • Hi Robert,

    I am not seeing any interrupt activity in any handlers including the "USB0DeviceIntHandler()" when sending an IN transaction from the host. 

     I wish I could provide more guidance. Here is some comments and suggestions I have:

     - Earlier you said you created your project with all endpoints enabled. I will suggest you start with a simpler configuration. Perhaps just one IN and one OUT endpoint. This way it is easier to debug and compare with the reference example. 

     - Run your project and capture all the USB register settings and compare with the stock usb_dev_bulk example. Are there any major differences? 

     - The stock example usb_dev_bulk simply echoes back the message based on the host's OUT and then IN requests. Is this what your project is doing also? Or is your host only sending the IN request to the device?

     - I don't know why an IN transaction does not create an interrupt. To me, the interrupt is supposed to generate by the USB controller when the hardware conditions are met.  The USB controller waits for the request from the Host controller and generates an interrupt when the transaction is complete or if it was terminated due to some error.

      - Is the device in some type of SUSPEND? Below is some description about SUSPEND.

    When no activity has occurred on the USB bus for 3 ms, the USB controller automatically enters
    SUSPEND mode. If the SUSPEND interrupt has been enabled in the USB Interrupt Enable (USBIE)
    register, an interrupt is generated at this time. When in SUSPEND mode, the PHY also goes into
    SUSPEND mode. When RESUME signaling is detected, the USB controller exits SUSPEND mode
    and takes the PHY out of SUSPEND. If the RESUME interrupt is enabled, an interrupt is generated.
    The USB controller can also be forced to exit SUSPEND mode by setting the RESUME bit in the USB
    Power (USBPOWER) register. When this bit is set, the USB controller exits SUSPEND mode and
    drives RESUME signaling onto the bus. The RESUME bit must be cleared after 10 ms (a maximum
    of 15 ms) to end RESUME signaling.

      - I wonder if the device detects the SOF? Below is some description about SOF.

    When the USB controller is operating in Device mode, it receives a Start-Of-Frame (SOF) packet
    from the Host once every millisecond. When the SOF packet is received, the 11-bit frame number
    contained in the packet is written into the USB Frame Value (USBFRAME) register, and an SOF
    interrupt is also signaled and can be handled by the application. Once the USB controller has started
    to receive SOF packets, it expects one every millisecond. If no SOF packet is received after 1.00358
    ms, the packet is assumed to have been lost, and the USBFRAME register is not updated. The
    USB controller continues and resynchronizes these pulses to the received SOF packets when these
    packets are successfully received again.

      - Are you seeing any errors detected by the device?

  • Hi Charles,

    I'm working on getting the example project up and running to do what you recommended and view all the traffic with the working example. 

    Per this, I noticed the readme in the example bulk project says there is an application binary with Tivaware, but I don't see it.  I do see the visual studio source, but if there is a binary that would be much better.  Did you use this binary when you ran your test and if so where in the Tivaware directory is it located.  Following the below info from the readme doesn't seem to point me to the binary file.

    Readme Text:

    A sample Windows command-line application, usb_bulk_example, illustrating
    how to connect to and communicate with the bulk device is also provided.
    The application binary is installed as part of the ''Windows-side examples
    for USB kits'' package (SW-USB-win) on the installation CD or via download
    from http://www.ti.com/tivaware . Project files are included to allow
    the examples to be built using Microsoft VisualStudio 2008. Source code
    for this application can be found in directory
    TivaWare-for-C-Series/tools/usb_bulk_example.

    Per your questions, I am testing a few different things here during troubleshooting.  I've tried to send an IN transaction from the host without any other traffic to see if it throws an interrupt and no go.  I've also loaded the tx buffer in the main loop when the device side Tiva part powers up and then send an IN transaction still with no luck.  I will try to catch the 11-bit code for an OUT and IN transaction and see if I can catch the IN transaction there as it should be identifiable via the PID.

    I will also look for errors caught by the registers as I haven't noticed any of those yet either. 

    Thanks for the support I know this is difficult for you to comment on, but you've been very helpful.  I will report back!

  • Hi Robert,

      When you go to the TivaWare download site, you will have the option to download the utility highlighted in yellow below. I'm also attaching the binary here. Please first run it on the stock example to get a feel for it. 

    https://e2e.ti.com/cfs-file/__key/communityserver-discussions-components-files/908/usb_5F00_bulk_5F00_example.exe

  • Hey Charles,

    I've figured out my issues and most of it was due to expecting the IN token to alert the device to load data for the host, but this isn't the case.

    I have noticed the bulk device structure (sPrivateData) has IN and OUT endpoint fields.  How does TI want customers to handle say this bulk example with 3x IN and 3xOUT endpoints?  Should we be modifying the endpoint variables in the sPrivateData structure?

  • Hi Robert,

      Reading the below tBulkInstance, it seems like for bulk device it is currently supporting only one IN and one OUT endpoints. You are free to extend it for multiple endpoints. But honestly I don't have the experience with this level of details as to how complex it would entail to change. Is it possible to create a composite device made up of multiple bulk devices? I wonder if that is a viable solution or not?

      On the performance side, do you really think that a bulk device with multiple endpoints will have better throughput than only one pair of IN/OUT endpoint? One single endpoint for Bulk device is supposed to take up all the bandwidth if there are no other higher priority transfers like Interrupt, isochronous transfers. I don't really think having 3X of IN/OUT endpoints will produce better throughput. You are just interleaving between the endpoints. Bear in mind the overhead you will need to incur when switching from one endpoint to another. I tend to think using one IN/OUT is a better performance/throughput option. You are ultimately limited by the maximum theoretical limit for USB 2.0 full speed. Please see this article for some insights. https://stackoverflow.com/questions/39926448/what-is-the-effective-maximum-payload-throughput-for-usb-in-full-speed

    Also a heads-up, today is US holiday for my company. I will be on vacation until Monday afternoon. Please expect delay in my response. 

    //*****************************************************************************
    //
    // PRIVATE
    //
    // This structure defines the private instance data and state variables for the
    // Bulk only example device.  The memory for this structure is inlcluded in
    // the sPrivateData field in the tUSBDBulkDevice structure passed on
    // USBDBulkInit().
    //
    //*****************************************************************************
    typedef struct
    {
        //
        // Base address for the USB controller.
        //
        uint32_t ui32USBBase;
    
        //
        // The device info to interact with the lower level DCD code.
        //
        tDeviceInfo sDevInfo;
    
        //
        // The state of the bulk receive channel.
        //
        volatile tBulkState iBulkRxState;
    
        //
        // The state of the bulk transmit channel.
        //
        volatile tBulkState iBulkTxState;
    
        //
        // State of any pending operations that could not be handled immediately
        // upon receipt.
        //
        volatile uint16_t ui16DeferredOpFlags;
    
        //
        // Size of the last transmit.
        //
        uint16_t ui16LastTxSize;
    
        //
        // The connection status of the device.
        //
        volatile bool bConnected;
    
        //
        // The IN endpoint number, this is modified in composite devices.
        //
        uint8_t ui8INEndpoint;
    
        //
        // The OUT endpoint number, this is modified in composite devices.
        //
        uint8_t ui8OUTEndpoint;
    
        //
        // The bulk class interface number, this is modified in composite devices.
        //
        uint8_t ui8Interface;
    }
    tBulkInstance;

  • Hey Charles,

    Thats what it looks like to me.  Usblib is only setup to work with a single endpoint IN/OUT.  You do however make a great point and maybe I need to re-think my approach since the throughput doesn't change relative to endpoints for bulk.  I have been so in the weeds with this firmware I didn't think about that.  This approach started because I wanted to "logically" isolate different communication "paths".  So for example EP1 carried data of one type, EP2 another type etc...so they were logically isolated for the host software.  

    After thinking about what you said - I may be better off thinking about using non-bulk interfaces for further logical partitioning so I have something to think about here.  Thanks for bringing that up.

    As I suspected usblib needs to be updated and that would be a pain as well.  I hope you had a great holiday and thanks for the support!