blob: 429b51a7ef059d1201da500fe1b2366e1aa050ca [file] [log] [blame]
/*
* Copyright (c) 2010-2013 Xilinx, Inc. All rights reserved.
*
* Xilinx, Inc.
* XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" AS A
* COURTESY TO YOU. BY PROVIDING THIS DESIGN, CODE, OR INFORMATION AS
* ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, APPLICATION OR
* STANDARD, XILINX IS MAKING NO REPRESENTATION THAT THIS IMPLEMENTATION
* IS FREE FROM ANY CLAIMS OF INFRINGEMENT, AND YOU ARE RESPONSIBLE
* FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE FOR YOUR IMPLEMENTATION.
* XILINX EXPRESSLY DISCLAIMS ANY WARRANTY WHATSOEVER WITH RESPECT TO
* THE ADEQUACY OF THE IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO
* ANY WARRANTIES OR REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE
* FROM CLAIMS OF INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE.
*
*/
#include "lwipopts.h"
#include "lwip/stats.h"
#include "lwip/sys.h"
#include "lwip/inet_chksum.h"
#include "netif/xadapter.h"
#include "netif/xemacpsif.h"
#include "xstatus.h"
#include "xlwipconfig.h"
#include "xparameters.h"
#include "xparameters_ps.h"
#include "xil_exception.h"
#include "xil_mmu.h"
#ifdef CONFIG_XTRACE
#include "xtrace.h"
#endif
#ifdef OS_IS_FREERTOS
#include "FreeRTOS.h"
#include "semphr.h"
#include "timers.h"
#endif
/*** IMPORTANT: Define PEEP in xemacpsif.h and sys_arch_raw.c
*** to run it on a PEEP board
***/
#define INTC_BASE_ADDR XPAR_SCUGIC_CPU_BASEADDR
#define INTC_DIST_BASE_ADDR XPAR_SCUGIC_DIST_BASEADDR
/* Byte alignment of BDs */
#define BD_ALIGNMENT (XEMACPS_DMABD_MINIMUM_ALIGNMENT*2)
static int tx_pbufs_storage[XLWIP_CONFIG_N_TX_DESC];
static int rx_pbufs_storage[XLWIP_CONFIG_N_RX_DESC];
static int EmacIntrNum;
extern u8 _end;
#ifdef OS_IS_FREERTOS
extern BaseType_t xInsideISR;
#endif
#define XEMACPS_BD_TO_INDEX(ringptr, bdptr) \
(((u32)bdptr - (u32)(ringptr)->BaseBdAddr) / (ringptr)->Separation)
int is_tx_space_available(xemacpsif_s *emac)
{
XEmacPs_BdRing *txring;
int freecnt = 0;
txring = &(XEmacPs_GetTxRing(&emac->emacps));
/* tx space is available as long as there are valid BD's */
freecnt = XEmacPs_BdRingGetFreeCnt(txring);
return freecnt;
}
void process_sent_bds(XEmacPs_BdRing *txring)
{
XEmacPs_Bd *txbdset;
XEmacPs_Bd *CurBdPntr;
int n_bds;
XStatus Status;
int n_pbufs_freed = 0;
unsigned int BdIndex;
struct pbuf *p;
unsigned int *Temp;
while (1) {
/* obtain processed BD's */
n_bds = XEmacPs_BdRingFromHwTx(txring,
XLWIP_CONFIG_N_TX_DESC, &txbdset);
if (n_bds == 0) {
return;
}
/* free the processed BD's */
n_pbufs_freed = n_bds;
CurBdPntr = txbdset;
while (n_pbufs_freed > 0) {
BdIndex = XEMACPS_BD_TO_INDEX(txring, CurBdPntr);
Temp = (unsigned int *)CurBdPntr;
*Temp = 0;
Temp++;
*Temp = 0x80000000;
if (BdIndex == (XLWIP_CONFIG_N_TX_DESC - 1)) {
*Temp = 0xC0000000;
}
p = (struct pbuf *)tx_pbufs_storage[BdIndex];
if(p != NULL) {
pbuf_free(p);
}
tx_pbufs_storage[BdIndex] = 0;
CurBdPntr = XEmacPs_BdRingNext(txring, CurBdPntr);
n_pbufs_freed--;
dsb();
}
Status = XEmacPs_BdRingFree(txring, n_bds, txbdset);
if (Status != XST_SUCCESS) {
LWIP_DEBUGF(NETIF_DEBUG, ("Failure while freeing in Tx Done ISR\r\n"));
}
}
return;
}
void vPendableSendCompleteFunction( void *pvParameter, uint32_t ulParameter )
{
( void ) ulParameter;
process_sent_bds(pvParameter);
}
void emacps_send_handler(void *arg)
{
struct xemac_s *xemac;
xemacpsif_s *xemacpsif;
XEmacPs_BdRing *TxRingPtr;
unsigned int regval;
#ifdef OS_IS_FREERTOS
xInsideISR++;
#endif
xemac = (struct xemac_s *)(arg);
xemacpsif = (xemacpsif_s *)(xemac->state);
TxRingPtr = &(XEmacPs_GetTxRing(&xemacpsif->emacps));
regval = XEmacPs_ReadReg(xemacpsif->emacps.Config.BaseAddress, XEMACPS_TXSR_OFFSET);
XEmacPs_WriteReg(xemacpsif->emacps.Config.BaseAddress,XEMACPS_TXSR_OFFSET, regval);
#ifdef OS_IS_FREERTOS
xInsideISR--;
#endif
/* If Transmit done interrupt is asserted, process completed BD's - Replaced
a call to process_sent_bds(TxRingPtr); with a pendable function to prevent
the memory allocation files being accessed from the ISR with not redress if
obtaining the mutex fails. */
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xTimerPendFunctionCallFromISR( vPendableSendCompleteFunction, TxRingPtr, 0, &xHigherPriorityTaskWoken );
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
}
XStatus emacps_sgsend(xemacpsif_s *xemacpsif, struct pbuf *p)
{
struct pbuf *q;
int n_pbufs;
XEmacPs_Bd *txbdset, *txbd, *last_txbd = NULL;
XEmacPs_Bd *temp_txbd;
XStatus Status;
XEmacPs_BdRing *txring;
unsigned int BdIndex;
unsigned int lev;
lev = mfcpsr();
mtcpsr(lev | 0x000000C0);
#ifdef PEEP
while((XEmacPs_ReadReg((xemacpsif->emacps).Config.BaseAddress,
XEMACPS_TXSR_OFFSET)) & 0x08);
#endif
txring = &(XEmacPs_GetTxRing(&xemacpsif->emacps));
/* first count the number of pbufs */
for (q = p, n_pbufs = 0; q != NULL; q = q->next)
n_pbufs++;
/* obtain as many BD's */
Status = XEmacPs_BdRingAlloc(txring, n_pbufs, &txbdset);
if (Status != XST_SUCCESS) {
mtcpsr(lev);
LWIP_DEBUGF(NETIF_DEBUG, ("sgsend: Error allocating TxBD\r\n"));
return ERR_IF;
}
for(q = p, txbd = txbdset; q != NULL; q = q->next) {
BdIndex = XEMACPS_BD_TO_INDEX(txring, txbd);
if (tx_pbufs_storage[BdIndex] != 0) {
mtcpsr(lev);
LWIP_DEBUGF(NETIF_DEBUG, ("PBUFS not available\r\n"));
return ERR_IF;
}
/* Send the data from the pbuf to the interface, one pbuf at a
time. The size of the data in each pbuf is kept in the ->len
variable. */
Xil_DCacheFlushRange((unsigned int)q->payload, (unsigned)q->len);
XEmacPs_BdSetAddressTx(txbd, (u32)q->payload);
if (q->len > (XEMACPS_MAX_FRAME_SIZE - 18))
XEmacPs_BdSetLength(txbd, (XEMACPS_MAX_FRAME_SIZE - 18) & 0x3FFF);
else
XEmacPs_BdSetLength(txbd, q->len & 0x3FFF);
tx_pbufs_storage[BdIndex] = (int)q;
pbuf_ref(q);
last_txbd = txbd;
XEmacPs_BdClearLast(txbd);
dsb();
txbd = XEmacPs_BdRingNext(txring, txbd);
}
XEmacPs_BdSetLast(last_txbd);
dsb();
/* For fragmented packets, remember the 1st BD allocated for the 1st
packet fragment. The used bit for this BD should be cleared at the end
after clearing out used bits for other fragments. For packets without
just remember the allocated BD. */
temp_txbd = txbdset;
txbd = txbdset;
txbd = XEmacPs_BdRingNext(txring, txbd);
q = p->next;
for(; q != NULL; q = q->next) {
XEmacPs_BdClearTxUsed(txbd);
txbd = XEmacPs_BdRingNext(txring, txbd);
}
XEmacPs_BdClearTxUsed(temp_txbd);
dsb();
Status = XEmacPs_BdRingToHw(txring, n_pbufs, txbdset);
if (Status != XST_SUCCESS) {
mtcpsr(lev);
LWIP_DEBUGF(NETIF_DEBUG, ("sgsend: Error submitting TxBD\r\n"));
return ERR_IF;
}
dsb();
/* Start transmit */
XEmacPs_WriteReg((xemacpsif->emacps).Config.BaseAddress,
XEMACPS_NWCTRL_OFFSET,
(XEmacPs_ReadReg((xemacpsif->emacps).Config.BaseAddress,
XEMACPS_NWCTRL_OFFSET) | XEMACPS_NWCTRL_STARTTX_MASK));
dsb();
mtcpsr(lev);
return Status;
}
void setup_rx_bds(XEmacPs_BdRing *rxring)
{
XEmacPs_Bd *rxbd;
XStatus Status;
struct pbuf *p;
unsigned int FreeBds;
unsigned int BdIndex;
unsigned int *Temp;
FreeBds = XEmacPs_BdRingGetFreeCnt (rxring);
while (FreeBds > 0) {
FreeBds--;
Status = XEmacPs_BdRingAlloc(rxring, 1, &rxbd);
if (Status != XST_SUCCESS) {
LWIP_DEBUGF(NETIF_DEBUG, ("setup_rx_bds: Error allocating RxBD\r\n"));
return;
}
BdIndex = XEMACPS_BD_TO_INDEX(rxring, rxbd);
Temp = (unsigned int *)rxbd;
*Temp = 0;
if (BdIndex == (XLWIP_CONFIG_N_RX_DESC - 1)) {
*Temp = 0x00000002;
}
Temp++;
*Temp = 0;
p = pbuf_alloc(PBUF_RAW, XEMACPS_MAX_FRAME_SIZE, PBUF_POOL);
if (!p) {
#if LINK_STATS
lwip_stats.link.memerr++;
lwip_stats.link.drop++;
#endif
LWIP_DEBUGF(NETIF_DEBUG, ("unable to alloc pbuf in recv_handler\r\n"));
XEmacPs_BdRingUnAlloc(rxring, 1, rxbd);
dsb();
return;
}
XEmacPs_BdSetAddressRx(rxbd, (u32)p->payload);
dsb();
rx_pbufs_storage[BdIndex] = (int)p;
Status = XEmacPs_BdRingToHw(rxring, 1, rxbd);
if (Status != XST_SUCCESS) {
LWIP_DEBUGF(NETIF_DEBUG, ("Error committing RxBD to hardware: "));
if (Status == XST_DMA_SG_LIST_ERROR)
LWIP_DEBUGF(NETIF_DEBUG, ("XST_DMA_SG_LIST_ERROR: this function was called out of sequence with XEmacPs_BdRingAlloc()\r\n"));
else
{
LWIP_DEBUGF(NETIF_DEBUG, ("set of BDs was rejected because the first BD did not have its start-of-packet bit set, or the last BD did not have its end-of-packet bit set, or any one of the BD set has 0 as length value\r\n"));
}
return;
}
}
}
void emacps_recv_handler(void *arg)
{
struct pbuf *p;
XEmacPs_Bd *rxbdset, *CurBdPtr;
struct xemac_s *xemac;
xemacpsif_s *xemacpsif;
XEmacPs_BdRing *rxring;
volatile int bd_processed;
int rx_bytes, k;
unsigned int BdIndex;
unsigned int regval;
xemac = (struct xemac_s *)(arg);
xemacpsif = (xemacpsif_s *)(xemac->state);
rxring = &XEmacPs_GetRxRing(&xemacpsif->emacps);
#ifdef OS_IS_FREERTOS
xInsideISR++;
#endif
/*
* If Reception done interrupt is asserted, call RX call back function
* to handle the processed BDs and then raise the according flag.
*/
regval = XEmacPs_ReadReg(xemacpsif->emacps.Config.BaseAddress, XEMACPS_RXSR_OFFSET);
XEmacPs_WriteReg(xemacpsif->emacps.Config.BaseAddress, XEMACPS_RXSR_OFFSET, regval);
resetrx_on_no_rxdata(xemacpsif);
while(1) {
bd_processed = XEmacPs_BdRingFromHwRx(rxring, XLWIP_CONFIG_N_RX_DESC, &rxbdset);
if (bd_processed <= 0) {
break;
}
for (k = 0, CurBdPtr=rxbdset; k < bd_processed; k++) {
BdIndex = XEMACPS_BD_TO_INDEX(rxring, CurBdPtr);
p = (struct pbuf *)rx_pbufs_storage[BdIndex];
/*
* Adjust the buffer size to the actual number of bytes received.
*/
rx_bytes = XEmacPs_BdGetLength(CurBdPtr);
pbuf_realloc(p, rx_bytes);
Xil_DCacheInvalidateRange((unsigned int)p->payload, (unsigned)XEMACPS_MAX_FRAME_SIZE);
/* store it in the receive queue,
* where it'll be processed by a different handler
*/
if (pq_enqueue(xemacpsif->recv_q, (void*)p) < 0) {
#if LINK_STATS
lwip_stats.link.memerr++;
lwip_stats.link.drop++;
#endif
pbuf_free(p);
} else {
#if !NO_SYS
sys_sem_signal(&xemac->sem_rx_data_available);
#endif
}
CurBdPtr = XEmacPs_BdRingNext( rxring, CurBdPtr);
}
/* free up the BD's */
XEmacPs_BdRingFree(rxring, bd_processed, rxbdset);
setup_rx_bds(rxring);
}
#ifdef OS_IS_FREERTOS
xInsideISR--;
#endif
return;
}
void clean_dma_txdescs(struct xemac_s *xemac)
{
XEmacPs_Bd BdTemplate;
XEmacPs_BdRing *TxRingPtr;
xemacpsif_s *xemacpsif = (xemacpsif_s *)(xemac->state);
TxRingPtr = &XEmacPs_GetTxRing(&xemacpsif->emacps);
XEmacPs_BdClear(&BdTemplate);
XEmacPs_BdSetStatus(&BdTemplate, XEMACPS_TXBUF_USED_MASK);
/*
* Create the TxBD ring
*/
XEmacPs_BdRingCreate(TxRingPtr, (u32) xemacpsif->tx_bdspace,
(u32) xemacpsif->tx_bdspace, BD_ALIGNMENT,
XLWIP_CONFIG_N_TX_DESC);
XEmacPs_BdRingClone(TxRingPtr, &BdTemplate, XEMACPS_SEND);
}
XStatus init_dma(struct xemac_s *xemac)
{
XEmacPs_Bd BdTemplate;
XEmacPs_BdRing *RxRingPtr, *TxRingPtr;
XEmacPs_Bd *rxbd;
struct pbuf *p;
XStatus Status;
int i;
unsigned int BdIndex;
char *endAdd = (char *) &_end;
/*
* Align the BD starte address to 1 MB boundary.
*/
char *endAdd_aligned = (char *)(((int)endAdd + 0x100000) & (~0xFFFFF));
xemacpsif_s *xemacpsif = (xemacpsif_s *)(xemac->state);
struct xtopology_t *xtopologyp = &xtopology[xemac->topology_index];
/*
* The BDs need to be allocated in uncached memory. Hence the 1 MB
* address range that starts at address 0xFF00000 is made uncached
* by setting appropriate attributes in the translation table.
*/
Xil_SetTlbAttributes((int)endAdd_aligned, 0xc02); // addr, attr
RxRingPtr = &XEmacPs_GetRxRing(&xemacpsif->emacps);
TxRingPtr = &XEmacPs_GetTxRing(&xemacpsif->emacps);
LWIP_DEBUGF(NETIF_DEBUG, ("RxRingPtr: 0x%08x\r\n", RxRingPtr));
LWIP_DEBUGF(NETIF_DEBUG, ("TxRingPtr: 0x%08x\r\n", TxRingPtr));
xemacpsif->rx_bdspace = (void *)endAdd_aligned;
/*
* We allocate 65536 bytes for Rx BDs which can accomodate a
* maximum of 8192 BDs which is much more than any application
* will ever need.
*/
xemacpsif->tx_bdspace = (void *)(endAdd_aligned + 0x10000);
LWIP_DEBUGF(NETIF_DEBUG, ("rx_bdspace: 0x%08x\r\n", xemacpsif->rx_bdspace));
LWIP_DEBUGF(NETIF_DEBUG, ("tx_bdspace: 0x%08x\r\n", xemacpsif->tx_bdspace));
if (!xemacpsif->rx_bdspace || !xemacpsif->tx_bdspace) {
xil_printf("%s@%d: Error: Unable to allocate memory for TX/RX buffer descriptors",
__FILE__, __LINE__);
return XST_FAILURE;
}
/*
* Setup RxBD space.
*
* Setup a BD template for the Rx channel. This template will be copied to
* every RxBD. We will not have to explicitly set these again.
*/
XEmacPs_BdClear(&BdTemplate);
/*
* Create the RxBD ring
*/
Status = XEmacPs_BdRingCreate(RxRingPtr, (u32) xemacpsif->rx_bdspace,
(u32) xemacpsif->rx_bdspace, BD_ALIGNMENT,
XLWIP_CONFIG_N_RX_DESC);
if (Status != XST_SUCCESS) {
LWIP_DEBUGF(NETIF_DEBUG, ("Error setting up RxBD space\r\n"));
return XST_FAILURE;
}
Status = XEmacPs_BdRingClone(RxRingPtr, &BdTemplate, XEMACPS_RECV);
if (Status != XST_SUCCESS) {
LWIP_DEBUGF(NETIF_DEBUG, ("Error initializing RxBD space\r\n"));
return XST_FAILURE;
}
XEmacPs_BdClear(&BdTemplate);
XEmacPs_BdSetStatus(&BdTemplate, XEMACPS_TXBUF_USED_MASK);
/*
* Create the TxBD ring
*/
Status = XEmacPs_BdRingCreate(TxRingPtr, (u32) xemacpsif->tx_bdspace,
(u32) xemacpsif->tx_bdspace, BD_ALIGNMENT,
XLWIP_CONFIG_N_TX_DESC);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
/* We reuse the bd template, as the same one will work for both rx and tx. */
Status = XEmacPs_BdRingClone(TxRingPtr, &BdTemplate, XEMACPS_SEND);
if (Status != XST_SUCCESS) {
return ERR_IF;
}
/*
* Allocate RX descriptors, 1 RxBD at a time.
*/
for (i = 0; i < XLWIP_CONFIG_N_RX_DESC; i++) {
Status = XEmacPs_BdRingAlloc(RxRingPtr, 1, &rxbd);
if (Status != XST_SUCCESS) {
LWIP_DEBUGF(NETIF_DEBUG, ("init_dma: Error allocating RxBD\r\n"));
return ERR_IF;
}
p = pbuf_alloc(PBUF_RAW, XEMACPS_MAX_FRAME_SIZE, PBUF_POOL);
if (!p) {
#if LINK_STATS
lwip_stats.link.memerr++;
lwip_stats.link.drop++;
#endif
LWIP_DEBUGF(NETIF_DEBUG, ("unable to alloc pbuf in recv_handler\r\n"));
return -1;
}
XEmacPs_BdSetAddressRx(rxbd, (u32)p->payload);
BdIndex = XEMACPS_BD_TO_INDEX(RxRingPtr, rxbd);
rx_pbufs_storage[BdIndex] = (int)p;
/* Enqueue to HW */
Status = XEmacPs_BdRingToHw(RxRingPtr, 1, rxbd);
if (Status != XST_SUCCESS) {
LWIP_DEBUGF(NETIF_DEBUG, ("Error: committing RxBD to HW\r\n"));
return XST_FAILURE;
}
}
/*
* Connect the device driver handler that will be called when an
* interrupt for the device occurs, the handler defined above performs
* the specific interrupt processing for the device.
*/
XScuGic_RegisterHandler(INTC_BASE_ADDR, xtopologyp->scugic_emac_intr,
(Xil_ExceptionHandler)XEmacPs_IntrHandler,
(void *)&xemacpsif->emacps);
/*
* Enable the interrupt for emacps.
*/
XScuGic_EnableIntr(INTC_DIST_BASE_ADDR, (u32) xtopologyp->scugic_emac_intr);
EmacIntrNum = (u32) xtopologyp->scugic_emac_intr;
return 0;
}
/*
* resetrx_on_no_rxdata():
*
* It is called at regular intervals through the API xemacpsif_resetrx_on_no_rxdata
* called by the user.
* The EmacPs has a HW bug (SI# 692601) on the Rx path for heavy Rx traffic.
* Under heavy Rx traffic because of the HW bug there are times when the Rx path
* becomes unresponsive. The workaround for it is to check for the Rx path for
* traffic (by reading the stats registers regularly). If the stats register
* does not increment for sometime (proving no Rx traffic), the function resets
* the Rx data path.
*
*/
void resetrx_on_no_rxdata(xemacpsif_s *xemacpsif)
{
unsigned long regctrl;
unsigned long tempcntr;
tempcntr = XEmacPs_ReadReg(xemacpsif->emacps.Config.BaseAddress, XEMACPS_RXCNT_OFFSET);
if ((!tempcntr) && (!(xemacpsif->last_rx_frms_cntr))) {
regctrl = XEmacPs_ReadReg(xemacpsif->emacps.Config.BaseAddress,
XEMACPS_NWCTRL_OFFSET);
regctrl &= (~XEMACPS_NWCTRL_RXEN_MASK);
XEmacPs_WriteReg(xemacpsif->emacps.Config.BaseAddress,
XEMACPS_NWCTRL_OFFSET, regctrl);
regctrl = XEmacPs_ReadReg(xemacpsif->emacps.Config.BaseAddress, XEMACPS_NWCTRL_OFFSET);
regctrl |= (XEMACPS_NWCTRL_RXEN_MASK);
XEmacPs_WriteReg(xemacpsif->emacps.Config.BaseAddress, XEMACPS_NWCTRL_OFFSET, regctrl);
}
xemacpsif->last_rx_frms_cntr = tempcntr;
}
void FreeTxRxPBufs(void)
{
int Index;
struct pbuf *p;
for (Index = 0; Index < XLWIP_CONFIG_N_TX_DESC; Index++) {
if (tx_pbufs_storage[Index] != 0) {
p = (struct pbuf *)tx_pbufs_storage[Index];
pbuf_free(p);
tx_pbufs_storage[Index] = 0;
}
}
for (Index = 0; Index < XLWIP_CONFIG_N_RX_DESC; Index++) {
p = (struct pbuf *)rx_pbufs_storage[Index];
pbuf_free(p);
}
}
void FreeOnlyTxPBufs(void)
{
int Index;
struct pbuf *p;
for (Index = 0; Index < XLWIP_CONFIG_N_TX_DESC; Index++) {
if (tx_pbufs_storage[Index] != 0) {
p = (struct pbuf *)tx_pbufs_storage[Index];
pbuf_free(p);
tx_pbufs_storage[Index] = 0;
}
}
}
void EmacDisableIntr(void)
{
XScuGic_DisableIntr(INTC_DIST_BASE_ADDR, EmacIntrNum);
}
void EmacEnableIntr(void)
{
XScuGic_EnableIntr(INTC_DIST_BASE_ADDR, EmacIntrNum);
}