Transmission using DMA
When µC/TCP-IP has a packet to transmit, it updates an available descriptor in memory and then writes to a DMA register to start the stalled DMA channel. On transmissions, it is simpler to setup the descriptors. The number and length of the packets to transmit is well defined. This information determines the number of transmit descriptors required and the number of bytes to transmit on each descriptor. The transmit descriptor list is often used in a non-circular fashion. The initial descriptors in the descriptor list are setup for transmission, when the transmission is completed they are cleared, and the process starts over in the next transmission.
Initialization
Similarly to the receive descriptors, the Network Device Driver should allocate a memory block for all transmit buffers and descriptors shown in the figure below.
(1) The Network Device Driver must allocate a list of descriptors and configure each address field to point to a null location.
(2) The Network Device Driver can initialize three pointers. One to track the current descriptor which is expected to contain the next buffer to transmit. A second points to the beginning of the descriptor list. The last pointer may point to the last descriptor in the list or depending on the implementation, it can also point to the last descriptor to transmit. Another method, depending on which DMA controller was used, is to configure a parameter containing the number of descriptors to transmit in one of the DMA controller registers.
Finally, the DMA controller is initialized and hardware is informed of the descriptor list's starting address.
Transmission
(1) With each new transmit buffer, the current descriptor address is set to the buffer address.
(2) DMA transfer is enabled.
(3) The current descriptor pointer is set to the next descriptor for the next transmission.
If no descriptor is free an error should be returned to the Network Protocol stack.
ISR Handler
When the ISR handler receives a DMA interrupt after the transmission completion, a list of descriptors for the completed transmit buffers is determined. Each completed transmit buffer address is passed to the Network Transmit De-allocation task, where the corresponding buffer gets released if it is not referenced by any other part of the Network stack. The Network interface is also signaled for each one of the completed transmit buffers to allow the Network stack to continue transmission of subsequent packets. To complete the operation, the transmit descriptors are cleared to make room for subsequent transmissions.
Transmission of packets can also benefit from a DMA implementation. Similar to the reception of packets, the DMA can be used to move the packet data from the application memory space to the memory location of the Ethernet controller. By utilizing the DMA, the CPU can work on other tasks allowing overall driver performance to increase.
Description of the Transmission Pointers
We use three pointers to manage the transmission of buffers:
TxBufDescPtrStart
This pointer points to the first descriptor and should not take any other value.
TxBufDescPtrComp
This pointer tracks the current descriptor which completed its transmission.
TxBufDescPtrCur
This pointer tracks the current descriptor available for transmission.
Initialization of the transmission descriptors
The function NetDev_Start()
initializes the Transmit buffer descriptor pointers and the DMA Transmit descriptors. The sub-function NetDev_TxDescInit()
initializes the Transmit descriptors ring. The descriptors must not be filled with buffers and must be owned by the software. Your code should activate the current Transmit descriptor only when NetDev_Tx()
is called.
The following is the pseudo code for this initialization:
pdesc = (DEV_DESC *)pdev_data->TxBufDescPtrStart; (1) pdev_data->TxBufDescPtrComp = (DEV_DESC *)pdev_data->TxBufDescPtrStart; pdev_data->TxBufDescPtrCur = (DEV_DESC *)pdev_data->TxBufDescPtrStart; for (i = 0; i < pdev_cfg->TxDescNbr; i++) { (2) pdesc->Addr = 0; pdesc->Len = 0; pdesc->Status = (not started) & (owned by software) pdesc->Next = (DEV_DESC *)(pdesc + 1); pdesc++; (3) } pdesc--; (4) pdesc->Next = (DEV_DESC *)pdev_data->TxBufDescPtrStart; (5)
.TxBufDescPtrComp
and .TxBufDescPtrCur
to .TxBufDescPtrStart
of pdev_data
(2) For every .TxDescNbr
in pdev_cfg
: Set the Transmit Buffer address to null, set the Len
field to 0, and set the status to “not started” and “owned by the software”. Then set the current descriptor's next descriptor to the location of the next descriptor (using pointer arithmetic).
(3) Increment descriptor using pointer arithmetic.
(4) Decrement descriptor to compensate for over-incrementation in the while loop.
(5) Set the .Next
field of the descriptor to .TxBufDescPtrStart
.
Moving packets from the TCP-IP stack to the network device
The function NetDev_Tx()
is called by the µC/TCP-IP module when a packet must be transmitted over the network. This function resets and activates a DMA Transmit descriptor for the packet to transmit. It must first make sure that a Transmit descriptor is available to initialize a transmission. Once the buffer has been assigned to the current Transmit descriptor, an interrupt will be generated to signal that the packet has been transmitted.
The following is the pseudo code for the NetDev_Tx()
function.
static void NetDev_Tx (NET_IF *pif, CPU_INT08U *p_data, CPU_INT16U size, NET_ERR *perr) { NET_DEV_CFG_ETHER *pdev_cfg; NET_DEV_DATA *pdev_data; NET_DEV *pdev; DEV_DESC *pdesc; pdev_cfg = (NET_DEV_CFG_ETHER *)pif->Dev_Cfg; pdev_data = (NET_DEV_DATA *)pif->Dev_Data; pdev = (NET_DEV *)pdev_cfg->BaseAddr; pdesc = (DEV_DESC *)pdev_data->TxBufDescPtrCur; if ((pdesc->Status & Hardware) != 0) { (1) *perr = NET_DEV_ERR_TX_BUSY; return; } pdesc->Addr = p_data; (2) pdesc->Len = size; (3) pdesc->Status = Hardware; (4) pdev->REGISTER = Inform harware that a Tx desc has been made avail; pdev_data->TxBufDescPtrCur = pdesc->Next; (5) *perr = NET_DEV_ERR_NONE; }
(1) If current Transmit Descriptor is still owned by the DMA engine, set *perr
to NET_DEV_ERR_TX_BUSY
indicating that the DMA engine is still occupied at transmitting that frame.
(2) Configure the descriptor with the transmit data area address.
(3) Configure the descriptor frame length.
(4) Give the descriptor ownership to hardware.
(5) Move the pointer of the current transmit descriptor to the next one.
Deallocating packets after transmission
NetDev_ISR_Handler()
is called when the transmission is completed. Within the ISR handler, you must signal the µC/TCP-IP module for each packet transmitted successfully.
It is possible that some packets may be transmitted or received during the ISR handler. As a result, sometimes only one ISR is generated for multiple packets transmitted. You must make sure that all descriptors not owned by the hardware and completed have been signaled by the µC/TCP-IP module.
int_status = pdev->REGISTER; (1) clear active int; if ((int_status & TX_INTERRUPT) > 0) { (2) while(p_desc != TxBufDescPtrCur || pdev->REGISTER == Owned by software) { (3) pdesc = pdev_data->TxBufDescPtrComp; pdev_data->TxBufDescPtrComp = pdesc->Next; NetOS_IF_TxDeallocTaskPost(pdesc->Addr, &err); NetOS_Dev_TxRdySignal(pif->Nbr); (4) } pdesc = pdev_data->TxBufDescPtrComp; pdev_data->TxBufDescPtrComp = pdesc->Next; (5) }
(1) Record the current state of the interrupt register.
(2) Verify if there is a transmission interrupt triggered.
(3) Cycle through the Transmit descriptor while the working descriptor is not pointing on .TxBufFescPtrCur
and that the working descriptor pointer is owned by the software (transmission done).
(4) Deallocate the transmission buffer used by the descriptor.
(5) Signal NetIF
that the Transmit resources are now available.
(6) Move the .TxBufDescPtrComp
descriptor pointer to the .Next
one of that descriptor.
Deallocating the transmit buffers
NetDev_Stop()
is called to free the receive descriptors ring and to deallocate all transmit buffers. To do that, a sub-function is called NetDev_TxDescFreeAll()
where each descriptor’s buffer is freed:
pdesc = pdev_data->TxBufDescPtrStart; (1) for (i = 0; i < pdev_cfg->TxDescNbr; i++) { NetOS_IF_TxDeallocTaskPost((CPU_INT08U *)pdesc->Addr, &err); (2) (void)&err; (3) pdesc++; }
(1) Set the current pointer descriptor to .TxBufDescPtrStart
of NET_DEV_DATA
.
(2) For each descriptor defined in the configuration, deallocate the network buffer associated with the descriptor.
(3) Any error returned by NetOS_IF_TxDeallocTaskPost()
should be ignored since we are doing a best effort to deallocate the buffer and carry on with the rest of the device stopping procedure.