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.

How do I support USB DMA on Beaglebone for custom USB host class?

I have a custom USB HOST class driver that is working great with DMA_MODE not defined.  I have been looking at usb_host_msc.c to try to figure out how to get DMA_MODE working with my app.

From looking at usb_host_msc.c, it appears I need to:

  • #define DMA_MODE in all the right places
  • Create an endpointInfo epInfo[] structure (see below)
  • Create function CPDMAAINTCConfigure() and call it from USBInterruptEnable()
  • I think I can reuse CPDMAAINTCConfigure() code from usb_host_msc.c as-is
  • Call Cppi41DmaInit(USB_INSTANCE,epInfo,NUMBER_OF_ENDPOINTS) in my main()
What is tripping me up is exactly what I need to fill in for the entpointInfo structure.  The usb_host_msc.c contains this:
endpointInfo epInfo[]=
{
    {
        USB_EP_TO_INDEX(USB_EP_1),
        CPDMA_DIR_RX,
        CPDMA_MODE_SET_TRANSPARENT,
    },
    {
        USB_EP_TO_INDEX(USB_EP_1),
        CPDMA_DIR_TX,
        CPDMA_MODE_SET_GRNDIS,
    },
    {
        USB_EP_TO_INDEX(USB_EP_2),
        CPDMA_DIR_RX,
        CPDMA_MODE_SET_TRANSPARENT,
    },
    {
        USB_EP_TO_INDEX(USB_EP_2),
        CPDMA_DIR_TX,
        CPDMA_MODE_SET_GRNDIS,
    }
};

This appears to be setting up DMA for both Tx and Rx on both endpoints 1 and 2.  Also, I thought that GRNDIS didn't work on the Beaglebone and only TRANSPARENT could be used.

My custom USB device uses two BULK endpoints (1 = IN, 2 = OUT).  Would the following structure properly support USB DMA for my remote peripheral device (BB is in USB HOST mode):

endpointInfo epInfo[]=
{
    {
        USB_EP_TO_INDEX(USB_EP_1),
        CPDMA_DIR_RX,
        CPDMA_MODE_SET_TRANSPARENT,
    },
    {
        USB_EP_TO_INDEX(USB_EP_2),
        CPDMA_DIR_TX,
        CPDMA_MODE_SET_TRANSPARENT,
    }
};

I've set both as TRANSPARENT DMA and EP1 as IN and EP2 as out.  I'm not sure if this is correct.

  • This is for StarterWare v2.0.1.1 (not v2.0.0.7).

    I needed to undefine DMA_MODE for usblib to get my host class driver working.  It works when DMA_MODE is undefined but I have not yet worked on getting DMA mode working (that's another discussion).

    Anyways, usbhostenum.c will generate a compiler error if DMA_MODE is not defined.  The problem is with an "else" clause that has no matching "if" clause in the function, USBHCDRxAbort().

    I fixed it locally, but now I'm wonder what the correct fix really is.

     Firstly, if DMA mode is not defined, then I believe all the code in the "else" clause must ALWAYS be executed regardless of if endPoint is USB_EP_0 or not.

    Second, if DMA mode IS defined, then I assume that the "if" clause code would be executed for NON-EP0 endPoint, and the else clause code would be executed for EP0 endPoint.  Is this correct assumption?

    If so, would this code fix be correct for both cases of DMA defined or DMA not defined?

    static unsigned int USBHCDRxAbort(unsigned int ulIndex,  unsigned int endPoint) 
    {   
        USBHostAutoReqClear(g_USBInstance[ulIndex].uiBaseAddr, endPoint);
    
        if (endPoint != USB_EP_0)
        { 
            #ifdef DMA_MODE
            // Initite Rx channel Teardown 
            // : broadly there are 2 sets of actions - On the musb Controller side
            // and then on the CPPI 4.1 DMA side 
            
            /*	CPPI DMA issue  Teardown hang: Frequent teardowns 
            cause controller to hang. Solution/workaround: 250 micro seconds delay  
            to be added to RxDMA teardown path.*/	
            /* 1ms delay*/         
            delay(1);
            Cppi41DmaInitTddPool( ulIndex );
            USBRxChAbort(g_USBInstance[ulIndex].uiBaseAddr, endPoint);
    
            Cppi41DmaRxChTeardown(ulIndex, endPoint);
            /* Clear auto request register once done */
            // clear auto request register
    
            // if( host ) 
            // HWREG(usbInstance->otgBaseAddress + USB_1_RX_MODE_AUTO_REQ_REG_OFFSET)|= 0x00000000;
            USBHostAutoReqClear(g_USBInstance[ulIndex].uiBaseAddr, endPoint);
            return 1;
            #endif
        }    
    
        USBHostRequestINClear(g_USBInstance[ulIndex].uiBaseAddr, endPoint);
    
        USBFIFOFlush(g_USBInstance[ulIndex].uiBaseAddr, endPoint, USB_EP_HOST_IN);
        USBFIFOFlush(g_USBInstance[ulIndex].uiBaseAddr, endPoint, USB_EP_HOST_IN);
    
        USBHostAutoReqSet(g_USBInstance[ulIndex].uiBaseAddr, endPoint);
    
        return 1;
    }
    

    I've reduced some whitespace for readability.  Basically, I've removed the "else" clause and added "return 1" at the end of the DMA_MODE code.  Therefore, if DMA_MODE is defined only DMA code is executed for NON EP0 endPoint.  The non-DMA code will be executed for EP0 endPoint or if DMA_MODE is undefined.

  • I got DMA working, but with some nasty issues.

    I had to call USB0ModuleClkConfig() before Cppi41DmaInit() or else nothing worked.  The function USBHCDInit() also calls USB0ModuleClkConfig(), but that occurs after the call to Cppi41DmaInit().

    There is an issue with USBHCDPipeRead() in that if the read times out, the DMA code in USBHCDRxAbort() seems to kill the USB interface.  That is, any subsequent USB reads or writes now do not work at all; it's as if USBHCDRxAbort() did too much aborting and subsequently prevents any further USB activity from working.  In PIO mode (non-DMA) the function USBHCDRxAbort() does not kill the USB interface - it works afterward.

    Also, USBHCDPipeRead() in PIO mode would return the number of actual bytes received if less than the request size.  If I ask for 512 bytes and receive 16, the function returns 16.  In DMA mode, this function always returns 512.  I cannot figure out (yet) a way to return the actual number of bytes received in DMA mode.

  • Hello Doug,

    The endpoint info structure sets up the dma channels (one RX and one TX channel per endpoint) for the CPPI DMA. And depending on the descriptors read during enumeration either channel is used. So I would recommend you use the configuration mentioned in the usb_host_msc.c file (the one with 4 entries). 

    Currently only transparent DMA mode is supported due to Silicon bugs and the latest code version has changes reflecting the same

    Here is the current configuration

    endpointInfo epInfo[]=
    {
    {
    USB_EP_TO_INDEX(USB_EP_1),
    CPDMA_DIR_RX,
    CPDMA_MODE_SET_TRANSPARENT,
    },

    {
    USB_EP_TO_INDEX(USB_EP_1),
    CPDMA_DIR_TX,
    CPDMA_MODE_SET_TRANSPARENT,
    },

    {
    USB_EP_TO_INDEX(USB_EP_2),
    CPDMA_DIR_RX,
    CPDMA_MODE_SET_TRANSPARENT,
    },

    {
    USB_EP_TO_INDEX(USB_EP_2),
    CPDMA_DIR_TX,
    CPDMA_MODE_SET_TRANSPARENT,
    }

    };

    Also please use the later versions of StarterWare USB stack 02.00.01.01 or 02.00.00.07. A cursory glance at the code indicates that you are using 02.00.00.06 or older. 

     Question regarding USB0ModuleClkConfig ? Yes the clock for the USB module need to be turned on before accessing any associated peripheral including CPPI dma. This is being done in the later StarterWare releases ( 02.00.00.07 mentioned above) in usb_host_msc.c before call to Cppi41DmaInit ().

    Question Regarding  USBHCDPipeRead()

    Channel Aborts: this could be due to a wrong endpoint info configuration . You would need to make the change I mentioned above ( all channels need to be configured for the endpoint) .

    Pipe read returning 512 bytes:-

    If you see the dma request setup  - with refernce to USBHCDPipeRead()

    if(ulSize <=
    (g_sUSBHCD[ulIndex].USBINPipes[ulPipeIdx].ulEpMaxPacketSize))
    {
    ulLength = ulSize;
    }
    else
    {
    ulLength =
    (g_sUSBHCD[ulIndex].USBINPipes[ulPipeIdx].ulEpMaxPacketSize);
    }

    ulength is being initialised to 512bytes (which is the endpoint maxpacket size) if the total size of data to be obtained is greater than 512. Hence this DMA packet completes after 512 bytes or the device sends a value lesser than 512 bytes indicating an end of transfer.

    So if dma set up is proper, and the device gives you say 16 bytes of data over the bulk pipe, the dma packet should complete .

  • Hi Vineeth,

    Thank you for reply!

    I am indeed using the v2.0.1.1 sources.  The code I posted above was from AM335X_StarterWare_02_00_01_01/examples/beaglebone/usb_host_msc/usb_host_msc.c

    I noticed in AM335X_StarterWare_02_00_01_01/examples/evmAM335x/usb_host_msc/usb_host_msc.c the epInfo structure has been changed from GRNDIS to TRANSPARENT, but this change has not been made in the Beaglebone tree for v2.0.1.1 which still uses GRNDIS for Tx pipes.

    The DMA problem I get is when PipeRead timesout.  I request to read a pipe with a timeout of 100ms.  In my host class, it is acceptable for the remote USB device to NOT have any data so it is expected to timeout when no data is ready on the USB device.  In PIO mode (DMA disable), the timeout occurs, and my main code handles that there is no data ready.  In subsequent pipe read requests if data becomes available on the USB device it is correctly received and returned to main.  When DMA mode is enabled, once a single read timeout occurs, all USB activity seems to fail from that point on.  It's as if the DMA teardown code may be too brutal with the DMA/USB hardware settings or state machine and that perhaps something needs to be done from main (after a pipe read timeout) to get the USB stack functioning again.

    As for the returned size from PipeRead in DMA mode, I am wondering, is there anyway to query the hardware of the exact size of a "fractional" packet; that is, a packet received that is < 512 bytes?  In PIO mode PipeRead correctly returns the actual size of the received packet but in DMA mode it always returns 512.  If there was a way for me to modify the DMA code to grab say the FIFO/DMA data size that would be great.

    Thanks,
    Doug.