/*
 * Copyright (C) 2010 - 2022 Xilinx, Inc.
 * Copyright (C) 2022 - 2024 Advanced Micro Devices, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
 * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 *
 * This file is part of the lwIP TCP/IP stack.
 *
 */

#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"
#if defined (ARMR5)
#include "xreg_cortexr5.h"
#endif
#ifdef CONFIG_XTRACE
#include "xtrace.h"
#endif
#if !NO_SYS
#include "FreeRTOS.h"
#include "semphr.h"
#include "timers.h"
#endif

#ifdef SDT
#include "xinterrupt_wrap.h"
#else
#define INTC_BASE_ADDR		XPAR_SCUGIC_0_CPU_BASEADDR
#define INTC_DIST_BASE_ADDR	XPAR_SCUGIC_0_DIST_BASEADDR
#endif

/* Byte alignment of BDs */
#define BD_ALIGNMENT (XEMACPS_DMABD_MINIMUM_ALIGNMENT*2)

/* A max of 4 different ethernet interfaces are supported */
static UINTPTR tx_pbufs_storage[4*XLWIP_CONFIG_N_TX_DESC];
static UINTPTR rx_pbufs_storage[4*XLWIP_CONFIG_N_RX_DESC];

#ifndef SDT
static s32_t emac_intr_num;
#endif

#if LWIP_UDP_OPT_BLOCK_TX_TILL_COMPLETE
volatile u32_t notifyinfo[4*XLWIP_CONFIG_N_TX_DESC];
#endif

/******************************************************************************
 * Each BD is of 8 bytes of size and the BDs (BD chain) need to be  put
 * at uncached memory location. If they are not put at uncached
 * locations, the user needs to flush or invalidate for each BD/packet.
 * However, the flush or invalidate can happen over a cache line which can
 * span multiple BDs. This means a flush or invalidate of one BD can actually
 * flush/invalidate multiple BDs adjacent to the targeted BD.Assuming that
 * the user and hardware both update the BD fields, this operation from user
 * can potentially overwrite the updates done by hardware or user.
 * To avoid this, it is always safe to put the BD chains for Rx and tx side
 * at uncached memory location.
 *
 * The Xilinx standalone BSP for Cortex A9 implements only primary page tables.
 * Each table entry corresponds to 1 MB of address map. This means, if a memory
 * region has to be made uncached, the minimum granularity will be of 1 MB.
 *
 * The implementation below allocates a 1 MB of u8 array aligned to 1 MB.
 * This ensures that this array is put at 1 MB aligned memory (e.g. 0x1200000)
 * and accupies memory of 1 MB. The init_dma function then changes 1 MB of this
 * region to make it uncached (strongly ordered).
 * This increases the bss section of the program significantly and can be a
 * wastage of memory. The reason beings, BDs will hardly occupy few KBs of
 * memory and the rest of 1 MB of memory will be unused.
 *
 * If a program uses other peripherals that have DMAs/bus masters and need
 * uncached memory, they may also end of following the same approach. This
 * definitely aggravates the memory wastage issue. To avoid all this, the user
 * can create a new 1 MB section in the linker script and reserve it for such
 * use cases that need uncached memory location. They can then have their own
 * memory allocation logic in their application that allocates uncached memory
 * from this 1 MB location. For such a case, changes need to be done in this
 * file and appropriate uncached memory allocated through other means can be
 * used.
 *
 * The present implementation here allocates 1 MB of uncached memory. It
 * reserves of 64 KB of memory for each BD chain. 64 KB of memory means 8192 of
 * BDs for each BD chain which is more than enough for any application.
 * Assuming that both emac0 and emac1 are present, 256 KB of memory is allocated
 * for BDs. The rest 768 KB of memory is just unused.
 *********************************************************************************/

#if defined __aarch64__
u8_t emac_bd_space[0x200000] __attribute__ ((aligned (0x200000)));
#else
u8_t emac_bd_space[0x100000] __attribute__ ((aligned (0x100000)));
#endif
static volatile u32_t bd_space_index = 0;
static volatile u32_t bd_space_attr_set = 0;

#if !NO_SYS
extern u32 xInsideISR;
#endif

#define XEMACPS_BD_TO_INDEX(ringptr, bdptr)				\
	(((UINTPTR)bdptr - (UINTPTR)(ringptr)->BaseBdAddr) / (ringptr)->Separation)


s32_t xemacps_is_tx_space_available(xemacpsif_s *emac)
{
	XEmacPs_BdRing *txring;
	s32_t 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;
}


static inline
u32_t get_base_index_txpbufsstorage (xemacpsif_s *xemacpsif)
{
	u32_t index;
#ifdef XPAR_XEMACPS_0_BASEADDR
	if (xemacpsif->emacps.Config.BaseAddress == XPAR_XEMACPS_0_BASEADDR) {
		index = 0;
	}
#endif
#ifdef XPAR_XEMACPS_1_BASEADDR
	if (xemacpsif->emacps.Config.BaseAddress == XPAR_XEMACPS_1_BASEADDR) {
		index = XLWIP_CONFIG_N_TX_DESC;
	}
#endif
#ifdef XPAR_XEMACPS_2_BASEADDR
	if (xemacpsif->emacps.Config.BaseAddress == XPAR_XEMACPS_2_BASEADDR) {
		index = 2 * XLWIP_CONFIG_N_TX_DESC;
	}
#endif
#ifdef XPAR_XEMACPS_3_BASEADDR
	if (xemacpsif->emacps.Config.BaseAddress == XPAR_XEMACPS_3_BASEADDR) {
		index = 3 * XLWIP_CONFIG_N_TX_DESC;
	}
#endif
	return index;
}

#if LWIP_UDP_OPT_BLOCK_TX_TILL_COMPLETE
static inline
u32_t get_base_index_tasknotifyinfo (xemacpsif_s *xemacpsif)
{
	u32_t index;
#ifdef XPAR_XEMACPS_0_BASEADDR
	if (xemacpsif->emacps.Config.BaseAddress == XPAR_XEMACPS_0_BASEADDR) {
		index = 0;
	}
#endif
#ifdef XPAR_XEMACPS_1_BASEADDR
	if (xemacpsif->emacps.Config.BaseAddress == XPAR_XEMACPS_1_BASEADDR) {
		index = XLWIP_CONFIG_N_TX_DESC;
	}
#endif
#ifdef XPAR_XEMACPS_2_BASEADDR
	if (xemacpsif->emacps.Config.BaseAddress == XPAR_XEMACPS_2_BASEADDR) {
		index = 2 * XLWIP_CONFIG_N_TX_DESC;
	}
#endif
#ifdef XPAR_XEMACPS_3_BASEADDR
	if (xemacpsif->emacps.Config.BaseAddress == XPAR_XEMACPS_3_BASEADDR) {
		index = 3 * XLWIP_CONFIG_N_TX_DESC;
	}
#endif
	return index;
}
#endif

static inline
u32_t get_base_index_rxpbufsstorage (xemacpsif_s *xemacpsif)
{
	u32_t index;
#ifdef XPAR_XEMACPS_0_BASEADDR
	if (xemacpsif->emacps.Config.BaseAddress == XPAR_XEMACPS_0_BASEADDR) {
		index = 0;
	}
#endif
#ifdef XPAR_XEMACPS_1_BASEADDR
	if (xemacpsif->emacps.Config.BaseAddress == XPAR_XEMACPS_1_BASEADDR) {
		index = XLWIP_CONFIG_N_RX_DESC;
	}
#endif
#ifdef XPAR_XEMACPS_2_BASEADDR
	if (xemacpsif->emacps.Config.BaseAddress == XPAR_XEMACPS_2_BASEADDR) {
		index = 2 * XLWIP_CONFIG_N_RX_DESC;
	}
#endif
#ifdef XPAR_XEMACPS_3_BASEADDR
	if (xemacpsif->emacps.Config.BaseAddress == XPAR_XEMACPS_3_BASEADDR) {
		index = 3 * XLWIP_CONFIG_N_RX_DESC;
	}
#endif
	return index;
}

void xemacps_process_sent_bds(xemacpsif_s *xemacpsif, XEmacPs_BdRing *txring)
{
	XEmacPs_Bd *txbdset;
	XEmacPs_Bd *curbdpntr;
	s32_t n_bds;
	XStatus status;
	s32_t n_pbufs_freed = 0;
	u32_t bdindex;
	struct pbuf *p;
	u32 *temp;
	u32_t index;
#if LWIP_UDP_OPT_BLOCK_TX_TILL_COMPLETE
	u32_t tx_task_notifier_index;
#endif

	index = get_base_index_txpbufsstorage (xemacpsif);
#if LWIP_UDP_OPT_BLOCK_TX_TILL_COMPLETE
	tx_task_notifier_index = get_base_index_tasknotifyinfo (xemacpsif);
#endif

	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 = (u32 *)curbdpntr;
			*temp = 0;
			temp++;
			if (bdindex == (XLWIP_CONFIG_N_TX_DESC - 1)) {
				*temp = 0xC0000000;
			} else {
				*temp = 0x80000000;
			}
			dsb();
			p = (struct pbuf *)tx_pbufs_storage[index + bdindex];
			if (p != NULL) {
				pbuf_free(p);
			}
#if LWIP_UDP_OPT_BLOCK_TX_TILL_COMPLETE
			notifyinfo[tx_task_notifier_index + bdindex] = 0;
#endif
			tx_pbufs_storage[index + 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 emacps_send_handler(void *arg)
{
	struct xemac_s *xemac;
	xemacpsif_s   *xemacpsif;
	XEmacPs_BdRing *txringptr;
	u32_t regval;
#if !NO_SYS
	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);

	/* If Transmit done interrupt is asserted, process completed BD's */
	xemacps_process_sent_bds(xemacpsif, txringptr);
#if !NO_SYS
	xInsideISR--;
#endif
}
#if LWIP_UDP_OPT_BLOCK_TX_TILL_COMPLETE
XStatus emacps_sgsend(xemacpsif_s *xemacpsif, struct pbuf *p,
					u32_t block_till_tx_complete, u32_t *to_block_index)
#else
XStatus emacps_sgsend(xemacpsif_s *xemacpsif, struct pbuf *p)
#endif
{
	struct pbuf *q;
	s32_t n_pbufs;
	XEmacPs_Bd *txbdset, *txbd, *last_txbd = NULL;
	XEmacPs_Bd *temp_txbd;
	XStatus status;
	XEmacPs_BdRing *txring;
	u32_t bdindex = 0;
	u32_t index;
	u32_t max_fr_size;
#if LWIP_UDP_OPT_BLOCK_TX_TILL_COMPLETE
	u32_t tx_task_notifier_index;
#endif

	txring = &(XEmacPs_GetTxRing(&xemacpsif->emacps));

	index = get_base_index_txpbufsstorage (xemacpsif);
#if LWIP_UDP_OPT_BLOCK_TX_TILL_COMPLETE
	tx_task_notifier_index = get_base_index_tasknotifyinfo (xemacpsif);
#endif

	/* 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) {
		LWIP_DEBUGF(NETIF_DEBUG, ("sgsend: Error allocating TxBD\r\n"));
		return XST_FAILURE;
	}

	for(q = p, txbd = txbdset; q != NULL; q = q->next) {
		bdindex = XEMACPS_BD_TO_INDEX(txring, txbd);
		if (tx_pbufs_storage[index + bdindex] != 0) {
			LWIP_DEBUGF(NETIF_DEBUG, ("PBUFS not available\r\n"));
			return XST_FAILURE;
		}

		/* 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. */
		if (xemacpsif->emacps.Config.IsCacheCoherent == 0) {
			Xil_DCacheFlushRange((UINTPTR)q->payload, (UINTPTR)q->len);
		}

		XEmacPs_BdSetAddressTx(txbd, (UINTPTR)q->payload);

#ifdef ZYNQMP_USE_JUMBO
		max_fr_size = MAX_FRAME_SIZE_JUMBO - 18;
#else
		max_fr_size = XEMACPS_MAX_FRAME_SIZE - 18;
#endif
		if (q->len > max_fr_size)
			XEmacPs_BdSetLength(txbd, max_fr_size & 0x3FFF);
		else
			XEmacPs_BdSetLength(txbd, q->len & 0x3FFF);

		tx_pbufs_storage[index + bdindex] = (UINTPTR)q;

		pbuf_ref(q);
		last_txbd = txbd;
		XEmacPs_BdClearLast(txbd);
		txbd = XEmacPs_BdRingNext(txring, txbd);
	}
#if LWIP_UDP_OPT_BLOCK_TX_TILL_COMPLETE
    if (block_till_tx_complete == 1) {
		notifyinfo[tx_task_notifier_index + bdindex] = 1;
		*to_block_index = tx_task_notifier_index + bdindex;
	}
#endif
	XEmacPs_BdSetLast(last_txbd);
	/* 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) {
		LWIP_DEBUGF(NETIF_DEBUG, ("sgsend: Error submitting TxBD\r\n"));
		return XST_FAILURE;
	}
	/* Start transmit */
	XEmacPs_WriteReg((xemacpsif->emacps).Config.BaseAddress,
	XEMACPS_NWCTRL_OFFSET,
	(XEmacPs_ReadReg((xemacpsif->emacps).Config.BaseAddress,
	XEMACPS_NWCTRL_OFFSET) | XEMACPS_NWCTRL_STARTTX_MASK));
	return status;
}

void setup_rx_bds(xemacpsif_s *xemacpsif, XEmacPs_BdRing *rxring)
{
	XEmacPs_Bd *rxbd;
	XStatus status;
	struct pbuf *p;
	u32_t freebds;
	u32_t bdindex;
	u32 *temp;
	u32_t index;

	index = get_base_index_rxpbufsstorage (xemacpsif);

	freebds = XEmacPs_BdRingGetFreeCnt (rxring);
	while (freebds > 0) {
		freebds--;
#ifdef ZYNQMP_USE_JUMBO
		p = pbuf_alloc(PBUF_RAW, MAX_FRAME_SIZE_JUMBO, PBUF_POOL);
#else
		p = pbuf_alloc(PBUF_RAW, XEMACPS_MAX_FRAME_SIZE, PBUF_POOL);
#endif
		if (!p) {
#if LINK_STATS
			lwip_stats.link.memerr++;
			lwip_stats.link.drop++;
#endif
			xil_printf("unable to alloc pbuf in recv_handler\r\n");
			return;
		}
		status = XEmacPs_BdRingAlloc(rxring, 1, &rxbd);
		if (status != XST_SUCCESS) {
			LWIP_DEBUGF(NETIF_DEBUG, ("setup_rx_bds: Error allocating RxBD\r\n"));
			pbuf_free(p);
			return;
		}
		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"));
			}

			pbuf_free(p);
			XEmacPs_BdRingUnAlloc(rxring, 1, rxbd);
			return;
		}
#ifdef ZYNQMP_USE_JUMBO
		if (xemacpsif->emacps.Config.IsCacheCoherent == 0) {
			Xil_DCacheInvalidateRange((UINTPTR)p->payload, (UINTPTR)MAX_FRAME_SIZE_JUMBO);
		}
#else
		if (xemacpsif->emacps.Config.IsCacheCoherent == 0) {
			Xil_DCacheInvalidateRange((UINTPTR)p->payload, (UINTPTR)XEMACPS_MAX_FRAME_SIZE);
		}
#endif
		bdindex = XEMACPS_BD_TO_INDEX(rxring, rxbd);
		temp = (u32 *)rxbd;
		temp++;
		/* Status field should be cleared first to avoid drops */
		*temp = 0;
		dsb();

		/* Set high address when required */
#ifdef __aarch64__
		XEmacPs_BdWrite(rxbd, XEMACPS_BD_ADDR_HI_OFFSET,
			(((UINTPTR)p->payload) & ULONG64_HI_MASK) >> 32U);
#endif
		/* Set address field; add WRAP bit on last descriptor  */
		if (bdindex == (XLWIP_CONFIG_N_RX_DESC - 1)) {
			XEmacPs_BdWrite(rxbd, XEMACPS_BD_ADDR_OFFSET, ((UINTPTR)p->payload | XEMACPS_RXBUF_WRAP_MASK));
		} else {
			XEmacPs_BdWrite(rxbd, XEMACPS_BD_ADDR_OFFSET, (UINTPTR)p->payload);
		}

		rx_pbufs_storage[index + bdindex] = (UINTPTR)p;
	}
}

void emacps_recv_handler(void *arg)
{
	struct pbuf *p;
	XEmacPs_Bd *rxbdset, *curbdptr;
	struct xemac_s *xemac;
	xemacpsif_s *xemacpsif;
	XEmacPs_BdRing *rxring;
	volatile s32_t bd_processed;
	s32_t rx_bytes, k;
	u32_t bdindex;
	u32_t regval;
	u32_t index;
	u32_t gigeversion;

	xemac = (struct xemac_s *)(arg);
	xemacpsif = (xemacpsif_s *)(xemac->state);
	rxring = &XEmacPs_GetRxRing(&xemacpsif->emacps);

#if !NO_SYS
	xInsideISR++;
#endif

	gigeversion = ((Xil_In32(xemacpsif->emacps.Config.BaseAddress + 0xFC)) >> 16) & 0xFFF;
	index = get_base_index_rxpbufsstorage (xemacpsif);
	/*
	 * 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);
	if (gigeversion <= 2) {
			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[index + bdindex];

			/*
			 * Adjust the buffer size to the actual number of bytes received.
			 */
#ifdef ZYNQMP_USE_JUMBO
			rx_bytes = XEmacPs_GetRxFrameSize(&xemacpsif->emacps, curbdptr);
#else
			rx_bytes = XEmacPs_BdGetLength(curbdptr);
#endif
			pbuf_realloc(p, rx_bytes);

			/* Invalidate RX frame before queuing to handle
			 * L1 cache prefetch conditions on any architecture.
			 */
			if (xemacpsif->emacps.Config.IsCacheCoherent == 0) {
				Xil_DCacheInvalidateRange((UINTPTR)p->payload, rx_bytes);
			}

			/* 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);
			}
			curbdptr = XEmacPs_BdRingNext( rxring, curbdptr);
		}
		/* free up the BD's */
		XEmacPs_BdRingFree(rxring, bd_processed, rxbdset);
		setup_rx_bds(xemacpsif, rxring);
	}
#if !NO_SYS
	sys_sem_signal(&xemac->sem_rx_data_available);
	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, (UINTPTR) xemacpsif->tx_bdspace,
			(UINTPTR) 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;
	s32_t i;
	u32_t bdindex;
	volatile UINTPTR tempaddress;
	u32_t index;
	u32_t gigeversion;
	XEmacPs_Bd *bdtxterminate = NULL;
	XEmacPs_Bd *bdrxterminate = NULL;
	u32 *temp;

	xemacpsif_s *xemacpsif = (xemacpsif_s *)(xemac->state);
#ifndef SDT
	struct xtopology_t *xtopologyp = &xtopology[xemac->topology_index];
#endif

	index = get_base_index_rxpbufsstorage (xemacpsif);
	gigeversion = ((Xil_In32(xemacpsif->emacps.Config.BaseAddress + 0xFC)) >> 16) & 0xFFF;
	/*
	 * The BDs need to be allocated in uncached memory. Hence the 1 MB
	 * address range allocated for Bd_Space is made uncached
	 * by setting appropriate attributes in the translation table.
	 * The Bd_Space is aligned to 1MB and has a size of 1 MB. This ensures
	 * a reserved uncached area used only for BDs.
	 */
	if (bd_space_attr_set == 0) {
#if defined (ARMR5)
	Xil_SetTlbAttributes((s32_t)emac_bd_space, STRONG_ORDERD_SHARED | PRIV_RW_USER_RW); // addr, attr
#else
#if defined __aarch64__
	Xil_SetTlbAttributes((u64)emac_bd_space, NORM_NONCACHE | INNER_SHAREABLE);
#else
	Xil_SetTlbAttributes((s32_t)emac_bd_space, DEVICE_MEMORY); // addr, attr
#endif
#endif
		bd_space_attr_set = 1;
	}

	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));

	/* Allocate 64k for Rx and Tx bds each to take care of extreme cases */
	tempaddress = (UINTPTR)&(emac_bd_space[bd_space_index]);
	xemacpsif->rx_bdspace = (void *)tempaddress;
	bd_space_index += 0x10000;
	tempaddress = (UINTPTR)&(emac_bd_space[bd_space_index]);
	xemacpsif->tx_bdspace = (void *)tempaddress;
	bd_space_index += 0x10000;
	if (gigeversion > 2) {
		tempaddress = (UINTPTR)&(emac_bd_space[bd_space_index]);
		bdrxterminate = (XEmacPs_Bd *)tempaddress;
		bd_space_index += 0x10000;
		tempaddress = (UINTPTR)&(emac_bd_space[bd_space_index]);
		bdtxterminate = (XEmacPs_Bd *)tempaddress;
		bd_space_index += 0x10000;
	}

	LWIP_DEBUGF(NETIF_DEBUG, ("rx_bdspace: %p \r\n", xemacpsif->rx_bdspace));
	LWIP_DEBUGF(NETIF_DEBUG, ("tx_bdspace: %p \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 ERR_IF;
	}

	/*
	 * 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, (UINTPTR) xemacpsif->rx_bdspace,
				(UINTPTR) 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 ERR_IF;
	}

	status = XEmacPs_BdRingClone(rxringptr, &bdtemplate, XEMACPS_RECV);
	if (status != XST_SUCCESS) {
		LWIP_DEBUGF(NETIF_DEBUG, ("Error initializing RxBD space\r\n"));
		return ERR_IF;
	}

	XEmacPs_BdClear(&bdtemplate);
	XEmacPs_BdSetStatus(&bdtemplate, XEMACPS_TXBUF_USED_MASK);
	/*
	 * Create the TxBD ring
	 */
	status = XEmacPs_BdRingCreate(txringptr, (UINTPTR) xemacpsif->tx_bdspace,
				(UINTPTR) xemacpsif->tx_bdspace, BD_ALIGNMENT,
				     XLWIP_CONFIG_N_TX_DESC);

	if (status != XST_SUCCESS) {
		return ERR_IF;
	}

	/* 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++) {
#ifdef ZYNQMP_USE_JUMBO
		p = pbuf_alloc(PBUF_RAW, MAX_FRAME_SIZE_JUMBO, PBUF_POOL);
#else
		p = pbuf_alloc(PBUF_RAW, XEMACPS_MAX_FRAME_SIZE, PBUF_POOL);
#endif
		if (!p) {
#if LINK_STATS
			lwip_stats.link.memerr++;
			lwip_stats.link.drop++;
#endif
			xil_printf("unable to alloc pbuf in init_dma\r\n");
			return ERR_IF;
		}
		status = XEmacPs_BdRingAlloc(rxringptr, 1, &rxbd);
		if (status != XST_SUCCESS) {
			LWIP_DEBUGF(NETIF_DEBUG, ("init_dma: Error allocating RxBD\r\n"));
			pbuf_free(p);
			return ERR_IF;
		}
		/* Enqueue to HW */
		status = XEmacPs_BdRingToHw(rxringptr, 1, rxbd);
		if (status != XST_SUCCESS) {
			LWIP_DEBUGF(NETIF_DEBUG, ("Error: committing RxBD to HW\r\n"));
			pbuf_free(p);
			XEmacPs_BdRingUnAlloc(rxringptr, 1, rxbd);
			return ERR_IF;
		}

		bdindex = XEMACPS_BD_TO_INDEX(rxringptr, rxbd);
		temp = (u32 *)rxbd;
		*temp = 0;
		if (bdindex == (XLWIP_CONFIG_N_RX_DESC - 1)) {
			*temp = 0x00000002;
		}
		temp++;
		*temp = 0;
		dsb();
#ifdef ZYNQMP_USE_JUMBO
		if (xemacpsif->emacps.Config.IsCacheCoherent == 0) {
			Xil_DCacheInvalidateRange((UINTPTR)p->payload, (UINTPTR)MAX_FRAME_SIZE_JUMBO);
		}
#else
		if (xemacpsif->emacps.Config.IsCacheCoherent == 0) {
			Xil_DCacheInvalidateRange((UINTPTR)p->payload, (UINTPTR)XEMACPS_MAX_FRAME_SIZE);
		}
#endif
		XEmacPs_BdSetAddressRx(rxbd, (UINTPTR)p->payload);

		rx_pbufs_storage[index + bdindex] = (UINTPTR)p;
	}
	XEmacPs_SetQueuePtr(&(xemacpsif->emacps), xemacpsif->emacps.RxBdRing.BaseBdAddr, 0, XEMACPS_RECV);
	if (gigeversion > 2) {
		XEmacPs_SetQueuePtr(&(xemacpsif->emacps), xemacpsif->emacps.TxBdRing.BaseBdAddr, 1, XEMACPS_SEND);
	}else {
		XEmacPs_SetQueuePtr(&(xemacpsif->emacps), xemacpsif->emacps.TxBdRing.BaseBdAddr, 0, XEMACPS_SEND);
	}
	if (gigeversion > 2)
	{
		/*
		 * This version of GEM supports priority queuing and the current
		 * driver is using tx priority queue 1 and normal rx queue for
		 * packet transmit and receive. The below code ensure that the
		 * other queue pointers are parked to known state for avoiding
		 * the controller to malfunction by fetching the descriptors
		 * from these queues.
		 */
		XEmacPs_BdClear(bdrxterminate);
		XEmacPs_BdSetAddressRx(bdrxterminate, (XEMACPS_RXBUF_NEW_MASK |
						XEMACPS_RXBUF_WRAP_MASK));
		XEmacPs_Out32((xemacpsif->emacps.Config.BaseAddress + XEMACPS_RXQ1BASE_OFFSET),
				   (UINTPTR)bdrxterminate);
		XEmacPs_BdClear(bdtxterminate);
		XEmacPs_BdSetStatus(bdtxterminate, (XEMACPS_TXBUF_USED_MASK |
						XEMACPS_TXBUF_WRAP_MASK));
		XEmacPs_Out32((xemacpsif->emacps.Config.BaseAddress + XEMACPS_TXQBASE_OFFSET),
				   (UINTPTR)bdtxterminate);
	}
#if !NO_SYS
#ifdef SDT
	xPortInstallInterruptHandler(xemacpsif->emacps.Config.IntrId,
				     ( Xil_InterruptHandler ) XEmacPs_IntrHandler,
				     (void *)&xemacpsif->emacps);
#else
	xPortInstallInterruptHandler(xtopologyp->scugic_emac_intr,
				     ( Xil_InterruptHandler ) XEmacPs_IntrHandler,
				     (void *)&xemacpsif->emacps);

#endif
#else
#ifndef SDT
	/*
	 * 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);
#endif
#endif
	/*
	 * Enable the interrupt for emacps.
	 */
#ifdef SDT
	XSetupInterruptSystem(&xemacpsif->emacps, &XEmacPs_IntrHandler,
			      xemacpsif->emacps.Config.IntrId,  xemacpsif->emacps.Config.IntrParent,
			      XINTERRUPT_DEFAULT_PRIORITY);
#else
	XScuGic_EnableIntr(INTC_DIST_BASE_ADDR, (u32) xtopologyp->scugic_emac_intr);
	emac_intr_num = (u32) xtopologyp->scugic_emac_intr;
#endif
	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)
{
	u32_t regctrl;
	u32_t tempcntr;
	u32_t gigeversion;

	gigeversion = ((Xil_In32(xemacpsif->emacps.Config.BaseAddress + 0xFC)) >> 16) & 0xFFF;
	if (gigeversion == 2) {
		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 free_txrx_pbufs(xemacpsif_s *xemacpsif)
{
	s32_t index;
	s32_t index1;
	struct pbuf *p;

	index1 = get_base_index_txpbufsstorage (xemacpsif);

	for (index = index1; index < (index1 + 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;
		}
	}

	index1 = get_base_index_rxpbufsstorage(xemacpsif);
	for (index = index1; index < (index1 + XLWIP_CONFIG_N_RX_DESC); index++) {
		p = (struct pbuf *)rx_pbufs_storage[index];
		pbuf_free(p);

	}
}

void free_onlytx_pbufs(xemacpsif_s *xemacpsif)
{
	s32_t index;
	s32_t index1;
	struct pbuf *p;

	index1 = get_base_index_txpbufsstorage (xemacpsif);
	for (index = index1; index < (index1 + 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;
		}
	}
}

/* reset Tx and Rx DMA pointers after XEmacPs_Stop */
void reset_dma(struct xemac_s *xemac)
{
	u8 txqueuenum;
	u32_t gigeversion;
	xemacpsif_s *xemacpsif = (xemacpsif_s *)(xemac->state);
	XEmacPs_BdRing *txringptr = &XEmacPs_GetTxRing(&xemacpsif->emacps);
	XEmacPs_BdRing *rxringptr = &XEmacPs_GetRxRing(&xemacpsif->emacps);

	XEmacPs_BdRingPtrReset(txringptr, xemacpsif->tx_bdspace);
	XEmacPs_BdRingPtrReset(rxringptr, xemacpsif->rx_bdspace);

	gigeversion = ((Xil_In32(xemacpsif->emacps.Config.BaseAddress + 0xFC)) >> 16) & 0xFFF;
	if (gigeversion > 2) {
		txqueuenum = 1;
	} else {
		txqueuenum = 0;
	}

	XEmacPs_SetQueuePtr(&(xemacpsif->emacps), xemacpsif->emacps.RxBdRing.BaseBdAddr, 0, XEMACPS_RECV);
	XEmacPs_SetQueuePtr(&(xemacpsif->emacps), xemacpsif->emacps.TxBdRing.BaseBdAddr, txqueuenum, XEMACPS_SEND);
}

#ifndef SDT
void emac_disable_intr(void)
{
	XScuGic_DisableIntr(INTC_DIST_BASE_ADDR, emac_intr_num);
}

void emac_enable_intr(void)
{
	XScuGic_EnableIntr(INTC_DIST_BASE_ADDR, emac_intr_num);
}
#endif
