Reception Using DMA with Lists

Micrium provides an alternate method for executing DMA transfers: DMA with Lists. The goal of this implementation is to reduce the number of controller errors (overrun, underrun, etc.), and increase driver performance. The typical implementation of the DMA descriptor initialization still applies here.

In order to keep the interrupt time as short as possible, you cannot call the µC/TCP-IP module to get a free buffer from within the ISR. In order to manage buffers, you must maintain a list of buffers within the device driver.

To implement the list method, create three lists: the Buffer List, the Ready List and the Free List. The three lists contain nodes which are moved from one list to another. A node is a memory space where you store pointers to the buffer address and the location of the next node.

The device driver data NET_DEV_DATA must contain three pointers which point to the first node of each list. The following is a description of the three lists:

Buffers List

This list contains empty nodes. Once a node is filled with the location of a free buffer, you must add this node to the Free List. If a node cannot be filled with the location of a free buffer, or a buffer ready to be processed, you must move the node back into the Buffers List.

Ready List

This list contains buffers which are ready to be processed (i.e., used by the application). When no resources are available to fill a node because they are occupied by the µC/TCP-IP module or by a DMA descriptor, the node must be moved into the Buffer List.

Free List

This list contains nodes that point to free buffers. When a buffer is no longer in use by the stack, a node from the Buffer List is moved to the Free List and the pointer in that node is set to the free buffer. When a pointer in a node in the Free List is used to replace a pointer to a descriptor buffer, you must move the node from the Free List to the Ready List.

Figure - Buffers in lists


Allocation of buffer list nodes

NetDev_Init() is called to allocate memory for the device DMA's descriptors, among other things. You must reserve some memory for each node within the device driver initialization. Since the device is not yet started after the initialization, and no resources are available for the driver, all created nodes must be assigned to the Free List.

The listing below shows the pseudo code for memory allocation of nodes, and list initialization. These steps should be performed during the device initialization.

Listing - Descriptor List Initialization
pdev_data->RxReadyListPtr  = (LIST_ITEM *)0;                                     (1)
pdev_data->RxBufferListPtr = (LIST_ITEM *)0; 
pdev_data->RxFreeListPtr   = (LIST_ITEM *)0;
cnt = pdev_cfg->RxBufLargeNbr - pdev_cfg->RxDescNbr;                             (2)
for (ix = 0; ix < cnt; ix++) {
    plist = (LIST_ITEM *)Mem_HeapAlloc((CPU_SIZE_T  ) sizeof(LIST_ITEM),         (3)
                                       (CPU_SIZE_T  ) 4,
                                       (CPU_SIZE_T *)&reqd_octets,
                                       (LIB_ERR    *)&lib_err);
    if (plist == (LIST_ITEM *)0) {                                               (4)
       *perr = NET_DEV_ERR_MEM_ALLOC;
        return;
    }
    plist->Buffer = (void *)0;                                                   (5)
    plist->Len    =  0;
    plist->Next   =  pdev_data->RxFreeListPtr;                                   (6)
    pdev_data->RxFreeListPtr = plist;                                            (7)
}

(1)  Initialize  RxReadyListPtrRxBufferListPtr  and  RxFreeListPtr  of  pdev_data  to  (LIST_ITEM *)0 .

(2) The initial number of LIST_ITEMs in RxFreeListPtr is calcuated as the number of .RxBufLargeNbr minus .RxDescNbr. From the pool of Receive buffers, only that number of buffers needs to be placed in the RxFreeListPtr since the rest of the buffers are initially assigned to a descriptor.

(3) Allocate a LIST_ITEM object from the heap.

(4) If an error occurred during the allocation of a LIST_ITEM set *perr to NET_DEV_ERR_MEM_ALLOC to notify that there was an error during memory allocation and then return.

(5) Set the .Buffer field to (void *)0 and the .Len field to 0 since no buffer is associated with the list nodes yet.

(6) Set the .Next field of the list node to the current node of RxFreeListPtr

(7) Insert the newly allocated node to the single ended list of RxFreeListPtr.

Thus after the device initialization, all nodes are added into the Free List. The buffers and the ready lists pointers are null.

Initialization of buffer list nodes

NetDev_Start() is used to initialize the reception buffer list. Nodes in the Free List are used to assign a buffer to each of the Receive descriptors, and then removed from the Free List. Any nodes remaining in the Free List should be moved to the Buffer List. Nodes in the Buffer List will be used to replace a descriptor buffer when the ISR handler signals that a new packet has been received. Reception Using DMA with Lists shows pseudocode for the Buffer list initialization:

Listing - Buffer List initialization
cnt = pdev_cfg->RxBufLargeNbr - pdev_cfg->RxDescNbr;                   (1)
for (i = 0; i < cnt; i++) {
    plist                    = pdev_data->RxFreeListPtr;               (2)
    pdev_data->RxFreeListPtr = plist->Next;
    plist->Buffer = NetBuf_GetDataPtr(perr);
    if (*perr != NET_BUF_ERR_NONE) {
         plist->Next              = pdev_data->RxFreeListPtr;          (3)
         pdev_data->RxFreeListPtr = plist;
         break;
    }
    plist->Next                = pdev_data->RxBufferListPtr;           (4)
    pdev_data->RxBufferListPtr = plist;
}


(1)  Get the number of available Receive buffers to put into the  .RxBufferListPtr . Of the  RxBufLargeNbr  buffers,  RxDescNbr  will be assigned to Receive descriptors; the rest will be put into the  .RxBufferListPtr .

(2) Get the list element pointer from free list.

(3) Return the list element pointer to free list in case of error.

(4) Store the list element pointer on Buffer list.


Thus after the device start, all nodes should be added into the Buffer List. So the Free and the Ready Lists should be null.

Deallocation of buffer list nodes

As with typical DMA implementation, you must remove the DMA descriptor ring and free the buffers. Also, you must move all nodes into the Free List. The listing below shows pseudocode for the node deallocation:

Listing - Descriptor and Buffer List deallocation
plist = pdev_data->RxBufferListPtr;
while (plist != (LIST_ITEM *)0) {                                               (1)
    plist_next = plist->Next;
    pdesc_data = plist->Buffer;
    NetBuf_FreeBufDataAreaRx(pif->Nbr, pdesc_data);                             (2)
    plist->Buffer = (void *)0;
    plist->Len    =  0;
    plist->Next              = pdev_data->RxFreeListPtr;                        (3)
    pdev_data->RxFreeListPtr = plist;
    plist = plist_next;
}
pdev_data->RxBufferListPtr = (LIST_ITEM *)0;                                    (4)
plist = pdev_data->RxReadyListPtr;
while (plist != (LIST_ITEM *)0) {                                               (5)
    plist_next = plist->Next;
    pdesc_data = plist->Buffer;
    NetBuf_FreeBufDataAreaRx(pif->Nbr, pdesc_data);                             (6)
    plist->Buffer = (void *)0;
    plist->Len    =  0;
    plist->Next              = pdev_data->RxFreeListPtr;                        (7)
    pdev_data->RxFreeListPtr = plist;
    plist = plist_next;
}
pdev_data->RxReadyListPtr = (LIST_ITEM *)0;                                     (8)

(1)  Repeat deallocation process for the nodes in  RxBufferListPtr  the until the  .Next  field of the node is null.

(2) Return data area to Receive data area pool.

(3) Remove the node from RxBufferListPtr.

(4) Set .RxBufferListPtr of pdev_data to null.

(5) Repeat deallocation process for the nodes in RxFreeListPtr until the .Next field of the node is null.

(6) Return data area to Rx data area pool.

(7) Remove the node from RxFreeListPtr.

(8) Set .RxFreeListPtr of pdev_data to null.


Buffer node processing during ISR

In order to process received packets, you must call the function NetDev_ISR_Handler().

If there are errors associated with the received packet, the packet must be discarded by returning the control of the descriptor back to the Direct Memory Access Controller. If the Buffer List is empty (meaning that there is no available buffer to exchange with a received DMA buffer) the packet must also be discarded.

On the other hand, if a buffer is available in the Buffer List, you must replace the buffer assigned to the DMAC with the available buffer. You must then move the received buffer from the DMAC to the Ready List in order to be processed by the Receive task. We suggest you to put the ISR Receive task in a separate sub-function. Note that you must call your sub-function for each individual Receive descriptor that is owned by the software, since you might receive only a single interrupt signal for a multiple DMA Receive completions.

Pseudo code of what should be put into the NetDev_ISR_Handler() is described below:


Listing - ISR Handling
if ((interrupt source == Receive) ||                                      (1)
    (interrupt source == Receive error)) {
    valid = DEF_TRUE;
    while (valid == DEF_TRUE) {
        pdesc = (DEV_DESC *)pdev_data->RxBufDescCurPtr;                   (2)
        
        if (pdesc->status indicates desc' is owned by soft.) {            (3)
            valid = NetDev_ISR_Rx(pif, pdesc);                            (4)
            
            pdev_data->RxBufDescCurPtr = pdesc->next;                     (5)
        } else {
            valid = DEF_FALSE;
        }
    }
}

(1)  If the interrupt register indicates a completed reception, or a reception error, proceed with handling of the interrupt.

(2) Obtain the pointer to the next ready descriptor.

(3) The descriptor is ready to be processed (reception is complete and descriptor is owned by the software).

(4) Call NetDev_ISR_Rx() to execute the buffer, and list element manipulation required to exchange the buffer of the descriptor with an available buffer.

(5) Move to the next descriptor in order to repeat the process with that descriptor, if it is owned by the software.


Your sub-function ( NetDev_ISR_Rx() ) must replace the current descriptor buffer with a buffer from a node into the Buffer List, and then signal the µC/TCP-IP module to process received packets and refill the Buffer list. You must also make sure that the Buffer List is not null (i.e., there is a buffer available). If no buffers are available, you must discard the packet.

The pseudo code for the Receive ISR sub-function is described below:

Listing - Rx ISR Handling
static  CPU_BOOLEAN  NetDev_ISR_Rx (NET_IF    *pif,
                                    DEV_DESC  *pdesc)
{
    NET_DEV_DATA  *pdev_data;
    LIST_ITEM     *plist_buf;
    LIST_ITEM     *plist_ready;
    void          *p_buf;
    CPU_BOOLEAN    valid;
    CPU_BOOLEAN    signal;
    NET_ERR        err;
    pdev_data = (NET_DEV_DATA *)pif->Dev_Data;                                 (1)
    valid     =  DEF_TRUE;
    signal    =  DEF_FALSE;
    
    if (Frame error) {                                                         (2)
        valid = DEF_FALSE;
    }
    if (Frame data spans over multiple buffers) {                              (3)
        valid = DEF_FALSE;
    }
    if (pdev_data->RxBufferListPtr == (LIST_ITEM *)0) {                        (4)
        valid  = DEF_FALSE;
        signal = DEF_TRUE;
    }
    Clear Interrupt source;
    if (valid == DEF_TRUE) {
        plist_buf                  = pdev_data->RxBufferListPtr;               (5)
        pdev_data->RxBufferListPtr = plist_buf->Next;
        p_buf                      = plist_buf->Buffer;
        plist_buf->Buffer          = pdesc->p_buf;
        plist_buf->Len             = pdesc->size;
        plist_buf->Next            = (LIST_ITEM *)0;
        if (pdev_data->RxReadyListPtr == (LIST_ITEM *)0) {                     (6)
            pdev_data->RxReadyListPtr  = plist_buf;
        } else {
            plist_ready = pdev_data->RxReadyListPtr;
            while (plist_ready != (LIST_ITEM *)0) {                            (7)
                if (plist_ready->Next == (LIST_ITEM *)0) {
                    break;
                }
                plist_ready = plist_ready->Next;
            }
            plist_ready->Next = plist_buf;
        }
        pdesc->p_buf = p_buf;                                                  (8)
        pdesc->size  = 0;
    }
    
    
    if ((valid  == DEF_TRUE) ||
        (signal == DEF_TRUE)) {
        NetOS_IF_RxTaskSignal(pif->Nbr, &err);                                 (9)
    }
    Reset Descriptor;
    return (valid);
} 

(1)  Obtain pointer to  NET_DEV_DATA  object.

(2) If there is an error with the received frame, discard it.

(3) If the frame doesn't hold in a single buffer, discard it.

(4) If there is no node in RxBufferListPtr it means that there is no buffer to exchange with the descriptor's buffer and the received frame must be discarded.

(5) Remove a node from the RxBufferListPtr. Exchange the buffer of that node with the buffer of the descriptor.

(6) If the RxReadyListPtr is empty, move the node removed from RxBufferListPtr to the RxReadyListPtr.

(7) If the RxReadyListPtr is not empty, move the node removed from RxBufferListPtr to the end of RxReadyListPtr.

(8) Assign the buffer removed from RxBufferListPtr to the descriptor.

(9) Signal the Receive task that there is a new frame available.


Moving the node's buffer to the TCP-IP stack

NetDev_Rx() is called by the Receive task of the µC/TCP-IP module to return a buffer to the application if there is one that is available. This function must return the oldest packet received which should be added into the Ready list by the ISR handler. If the list is empty, you must return an error. If the list is not empty, you must set the p_data pointer argument to the current node buffer, set the node buffer to null and move the node to the Free list. Then you have to try to move Free list node to the Buffer list. To do so, you must get a free buffer from the µC/TCP-IP module, fill a node buffer from the Free List and move the node to the Buffer List.

The following is the pseudo code describing this process:

Listing - Packet Reception
if ((interrupt source == Receive) ||
static  void  NetDev_Rx (NET_IF       *pif,
                         CPU_INT08U  **p_data,
                         CPU_INT16U   *size,
                         NET_ERR      *perr)
{
    NET_DEV_DATA       *pdev_data;
    NET_DEV_CFG_ETHER  *pdev_cfg;
    LIST_ITEM          *plist;
    CPU_INT08U         *pbuf;
    CPU_BOOLEAN         valid;
    NET_ERR             net_err;
    CPU_SR_ALLOC();
    pdev_cfg  = (NET_DEV_CFG_ETHER *)pif->Dev_Cfg;
    pdev_data = (NET_DEV_DATA *)pif->Dev_Data;
    CPU_CRITICAL_ENTER();                                                (1)
    plist = pdev_data->RxReadyListPtr;                                   (2)
    if (plist != (LIST_ITEM *)0) {
        pdev_data->RxReadyListPtr = plist->Next;
       *size   = plist->Len;                                             (3)
       *p_data = (CPU_INT08U *)plist->Buffer;                            (4)
        plist->Len    =  0;
        plist->Buffer = (void *)0;
                                                                         (5)
        plist->Next              = pdev_data->RxFreeListPtr;    
        pdev_data->RxFreeListPtr = plist;
        CPU_CRITICAL_EXIT();                                             (6)
        
       *perr = NET_DEV_ERR_NONE;
    } else {
        CPU_CRITICAL_EXIT();                                             (7)
        
       *size   = (CPU_INT16U  )0;
       *p_data = (CPU_INT08U *)0;
       
       *perr = NET_DEV_ERR_RX;
    }
    valid = DEF_TRUE;
    while (valid == DEF_TRUE) {
        pbuf = NetBuf_GetDataPtr((NET_IF        *)pif,
                                 (NET_TRANSACTION)NET_TRANSACTION_RX,
                                 (NET_ERR       *)&net_err);
        if (net_err != NET_BUF_ERR_NONE) {
            valid = DEF_FALSE;
        } else {
            CPU_CRITICAL_ENTER();                                        (1)
            plist = pdev_data->RxFreeListPtr;
            if (plist != (LIST_ITEM *)0) {
                pdev_data->RxFreeListPtr   = plist->Next;
                plist->Buffer              = pbuf;
                plist->Next                = pdev_data->RxBufferListPtr;
                pdev_data->RxBufferListPtr = plist;
                CPU_CRITICAL_EXIT();                    
            } else {
                CPU_CRITICAL_EXIT();                    
                valid = DEF_FALSE;
                                                        
                NetBuf_FreeBufDataAreaRx(pif->Nbr, pbuf);                (8)
            }
        }
    }
}

(1) Disable interrupts to alter shared data.

(2) Get the next ready buffer.

(3) Return the size of the received frame.

(4) Return a pointer to the received data area.

(5) Move the list header into the free list.

(6) Restore interrupts.

(7) Restore interrupts and mark the received frame as invalid.

(8) Return data to received data area pool.