/**
 * \file
 *
 * \brief GMAC (Ethernet MAC) driver for SAM.
 *
 * Copyright (c) 2015-2016 Atmel Corporation. All rights reserved.
 *
 * \asf_license_start
 *
 * \page License
 *
 * 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 Atmel may not be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * 4. This software may only be redistributed and used in connection with an
 *    Atmel microcontroller product.
 *
 * THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
 * EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL 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.
 *
 * \asf_license_stop
 *
 */

/*
 * Support and FAQ: visit <a href="https://www.microchip.com/en-us/support/design-help">Atmel Support</a>
 */


/* Standard includes. */
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

/* FreeRTOS includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"

#include "FreeRTOSIPConfig.h"

/* FreeRTOS+TCP includes. */
#include "FreeRTOS_IP.h"
#include "FreeRTOS_Sockets.h"
#include "FreeRTOS_IP_Private.h"
#include "FreeRTOS_ARP.h"
#include "NetworkBufferManagement.h"
#include "NetworkInterface.h"

#include "compiler.h"
#include "gmac_SAM.h"

#if ( SAME70 != 0 )
    /* This file is included to see if 'CONF_BOARD_ENABLE_CACHE' is defined. */
    #include "conf_board.h"
    #include "core_cm7.h"
#endif

/*/ @cond 0 */
/* *INDENT-OFF* */
#ifdef __cplusplus
    extern "C" {
#endif
/* *INDENT-ON* */
/*/ @endcond */

#ifndef ARRAY_SIZE
    #define ARRAY_SIZE( x )    ( int ) ( sizeof( x ) / sizeof( x )[ 0 ] )
#endif

#if ( GMAC_RX_BUFFERS <= 1 )
    #error Configuration error, GMAC_RX_BUFFERS must be at least 2
#endif

#if ( GMAC_TX_BUFFERS <= 1 )
    #error Configuration error, GMAC_TX_BUFFERS must be at least 2
#endif

/* Interesting bits in the Transmission Status Register. */
#define TSR_TSR_BITS    ( GMAC_TSR_TXCOMP | GMAC_TSR_COL | GMAC_TSR_RLE | GMAC_TSR_UND )

#if ( GMAC_STATS != 0 )
    #if ( ipconfigPORT_SUPPRESS_WARNING == 0 )
        #warning Statistics are enabled
    #endif

    struct SGmacStats gmacStats;
    TransmitStats_t xTransmitStats;
#endif

/**
 * \defgroup gmac_group Ethernet Media Access Controller
 *
 * See \ref gmac_quickstart.
 *
 * Driver for the GMAC (Ethernet Media Access Controller).
 * This file contains basic functions for the GMAC, with support for all modes,
 * settings and clock speeds.
 *
 * \section dependencies Dependencies
 * This driver does not depend on other modules.
 *
 * @{
 */

/*
 *     When BufferAllocation_1.c is used, the network buffer space
 *     is declared statically as 'ucNetworkPackets[]'.
 *     Like the DMA descriptors, this array is located in a non-cached area.
 *     Here an example of the total size:
 *
 *         #define ipconfigNUM_NETWORK_BUFFER_DESCRIPTORS    24
 *         #define GMAC_FRAME_LENTGH_MAX                     1536
 *         Hidden space for back-pointer and IP-type         16
 *
 *     Total size: 24 * ( 1536 + 16 ) = 37248 bytes
 */
__attribute__( ( aligned( 32 ) ) )
__attribute__( ( section( ".first_data" ) ) )
uint8_t ucNetworkPackets[ ipconfigNUM_NETWORK_BUFFER_DESCRIPTORS * NETWORK_BUFFER_SIZE ];

/** TX descriptor lists */
__attribute__( ( section( ".first_data" ) ) )
COMPILER_ALIGNED( 8 )
static gmac_tx_descriptor_t gs_tx_desc[ GMAC_TX_BUFFERS ];

#if ( SAME70 != 0 )
    __attribute__( ( section( ".first_data" ) ) )
    COMPILER_ALIGNED( 8 )
    static gmac_tx_descriptor_t gs_tx_desc_null;
#endif

/** RX descriptors lists */
__attribute__( ( section( ".first_data" ) ) )
COMPILER_ALIGNED( 8 )
static gmac_rx_descriptor_t gs_rx_desc[ GMAC_RX_BUFFERS ];

#if ( ipconfigZERO_COPY_TX_DRIVER == 0 )

/** Send Buffer. Section 3.6 of AMBA 2.0 spec states that burst should not cross the
 * 1K Boundaries. Receive buffer manager write operations are burst of 2 words => 3 lsb bits
 * of the address shall be set to 0.
 */
    __attribute__( ( section( ".first_data" ) ) )
    COMPILER_ALIGNED( 8 )
    static uint8_t gs_uc_tx_buffer[ GMAC_TX_BUFFERS * GMAC_TX_UNITSIZE ];
#endif /* ipconfigZERO_COPY_TX_DRIVER */

#if ( ipconfigZERO_COPY_RX_DRIVER == 0 )
    /** Receive Buffer */
    __attribute__( ( section( ".first_data" ) ) )
    COMPILER_ALIGNED( 8 )
    static uint8_t gs_uc_rx_buffer[ GMAC_RX_BUFFERS * GMAC_RX_UNITSIZE ];
#endif /* ipconfigZERO_COPY_RX_DRIVER */

/** Return count in buffer */
#define CIRC_CNT( head, tail, size )      ( ( ( head ) - ( tail ) ) % ( size ) )

/*
 * Return space available, from 0 to size-1.
 * Always leave one free char as a completely full buffer that has (head == tail),
 * which is the same as empty.
 */
#define CIRC_SPACE( head, tail, size )    CIRC_CNT( ( tail ), ( ( head ) + 1 ), ( size ) )

/** Circular buffer is empty ? */
#define CIRC_EMPTY( head, tail )          ( ( head ) == ( tail ) )
/** Clear circular buffer */
#define CIRC_CLEAR( head, tail )          do { ( head ) = 0; ( tail ) = 0; } while( 0 )

/* Two call-back functions that should be defined in NetworkInterface.c */
extern void xRxCallback( uint32_t ulStatus );
extern void xTxCallback( uint32_t ulStatus,
                         uint8_t * puc_buffer );
extern void returnTxBuffer( uint8_t * puc_buffer );


/** Increment head or tail */
static __inline void circ_inc32( int32_t * lHeadOrTail,
                                 uint32_t ulSize )
{
    ( *lHeadOrTail )++;

    if( ( *lHeadOrTail ) >= ( int32_t ) ulSize )
    {
        ( *lHeadOrTail ) = 0;
    }
}

/**
 * \brief Wait PHY operation to be completed.
 *
 * \param p_gmac HW controller address.
 * \param ul_retry The retry times, 0 to wait forever until completeness.
 *
 * Return GMAC_OK if the operation is completed successfully.
 */
uint8_t gmac_wait_phy( Gmac * p_gmac,
                       const uint32_t ul_retry )
{
    volatile uint32_t ul_retry_count = 0;
    const uint32_t xPHYPollDelay = pdMS_TO_TICKS( 1ul );

    while( !gmac_is_phy_idle( p_gmac ) )
    {
        if( ul_retry == 0 )
        {
            continue;
        }

        ul_retry_count++;

        if( ul_retry_count >= ul_retry )
        {
            return GMAC_TIMEOUT;
        }

        /* Block the task to allow other tasks to execute while the PHY
         * is not connected. */
        vTaskDelay( xPHYPollDelay );
    }

    return GMAC_OK;
}

/**
 * \brief Disable transfer, reset registers and descriptor lists.
 *
 * \param p_dev Pointer to GMAC driver instance.
 *
 */
void gmac_reset_tx_mem( gmac_device_t * p_dev )
{
    Gmac * p_hw = p_dev->p_hw;

    uint32_t ul_index;
    uint32_t ul_address;

    /* Disable TX */
    gmac_enable_transmit( p_hw, 0 );

    {
        for( ul_index = 0; ul_index < ARRAY_SIZE( gs_tx_desc ); ul_index++ )
        {
            uint32_t ulAddr = gs_tx_desc[ ul_index ].addr;

            if( ulAddr )
            {
                returnTxBuffer( ( uint8_t * ) ulAddr );
            }
        }
    }
    /* Set up the TX descriptors */
    CIRC_CLEAR( p_dev->l_tx_head, p_dev->l_tx_tail );

    for( ul_index = 0; ul_index < GMAC_TX_BUFFERS; ul_index++ )
    {
        #if ( ipconfigZERO_COPY_TX_DRIVER != 0 )
        {
            ul_address = ( uint32_t ) 0u;
        }
        #else
        {
            ul_address = ( uint32_t ) ( &( gs_uc_tx_buffer[ ul_index * GMAC_TX_UNITSIZE ] ) );
        }
        #endif /* ipconfigZERO_COPY_TX_DRIVER */
        gs_tx_desc[ ul_index ].addr = ul_address;
        gs_tx_desc[ ul_index ].status.val = GMAC_TXD_USED;
    }

    /* Set the WRAP bit in the last descriptor. */
    gs_tx_desc[ GMAC_TX_BUFFERS - 1 ].status.val = GMAC_TXD_USED | GMAC_TXD_WRAP;

    /* Set transmit buffer queue */
    gmac_set_tx_queue( p_hw, ( uint32_t ) gs_tx_desc );
    #if ( SAME70 != 0 )
    {
        gmac_set_tx_priority_queue( p_hw, ( uint32_t ) &gs_tx_desc_null, GMAC_QUE_1 );
        gmac_set_tx_priority_queue( p_hw, ( uint32_t ) &gs_tx_desc_null, GMAC_QUE_2 );
        /* Note that SAME70 REV B had 6 priority queues. */
        gmac_set_tx_priority_queue( p_hw, ( uint32_t ) &gs_tx_desc_null, GMAC_QUE_3 );
        gmac_set_tx_priority_queue( p_hw, ( uint32_t ) &gs_tx_desc_null, GMAC_QUE_4 );
        gmac_set_tx_priority_queue( p_hw, ( uint32_t ) &gs_tx_desc_null, GMAC_QUE_5 );
    }
    #endif
}

/**
 * \brief Disable receiver, reset registers and descriptor list.
 *
 * \param p_dev Pointer to GMAC Driver instance.
 */
static void gmac_reset_rx_mem( gmac_device_t * p_dev )
{
    Gmac * p_hw = p_dev->p_hw;

    uint32_t ul_index;
    uint32_t ul_address;

    /* Disable RX */
    gmac_enable_receive( p_hw, 0 );

    /* Set up the RX descriptors */
    p_dev->ul_rx_idx = 0;

    for( ul_index = 0; ul_index < GMAC_RX_BUFFERS; ul_index++ )
    {
        #if ( ipconfigZERO_COPY_RX_DRIVER != 0 )
        {
            NetworkBufferDescriptor_t * pxNextNetworkBufferDescriptor;

            pxNextNetworkBufferDescriptor = pxGetNetworkBufferWithDescriptor( GMAC_RX_UNITSIZE, 0ul );
            configASSERT( pxNextNetworkBufferDescriptor != NULL );
            ul_address = ( uint32_t ) ( pxNextNetworkBufferDescriptor->pucEthernetBuffer );
        }
        #else
        {
            ul_address = ( uint32_t ) ( &( gs_uc_rx_buffer[ ul_index * GMAC_RX_UNITSIZE ] ) );
        }
        #endif /* ipconfigZERO_COPY_RX_DRIVER */
        gs_rx_desc[ ul_index ].addr.val = ul_address & GMAC_RXD_ADDR_MASK;
        gs_rx_desc[ ul_index ].status.val = 0;
    }

    /* Set the WRAP bit in the last descriptor. */
    gs_rx_desc[ GMAC_RX_BUFFERS - 1 ].addr.bm.b_wrap = 1;

    /* Set receive buffer queue */
    gmac_set_rx_queue( p_hw, ( uint32_t ) gs_rx_desc );
}


/**
 * \brief Initialize the allocated buffer lists for GMAC driver to transfer data.
 * Must be invoked after gmac_dev_init() but before RX/TX starts.
 *
 * \note If input address is not 8-byte aligned, the address is automatically
 *       adjusted and the list size is reduced by one.
 *
 * \param p_gmac Pointer to GMAC instance.
 * \param p_gmac_dev Pointer to GMAC device instance.
 * \param p_dev_mm Pointer to the GMAC memory management control block.
 *
 * \return GMAC_OK or GMAC_PARAM.
 */
static uint8_t gmac_init_mem( Gmac * p_gmac,
                              gmac_device_t * p_gmac_dev )
{
    /* Assign TX buffers */
    #if ( ipconfigZERO_COPY_TX_DRIVER == 0 )
        if( ( ( ( uint32_t ) gs_uc_tx_buffer ) & 0x7 ) ||
            ( ( uint32_t ) p_dev_mm->p_tx_dscr & 0x7 ) )
        {
            p_dev_mm->ul_tx_size--;
        }

        p_gmac_dev->p_tx_buffer =
            ( uint8_t * ) ( ( ( uint32_t ) gs_uc_tx_buffer ) & 0xFFFFFFF8 );
    #endif

    /* Reset TX & RX Memory */
    gmac_reset_rx_mem( p_gmac_dev );
    gmac_reset_tx_mem( p_gmac_dev );

    /* Enable Rx and Tx, plus the statistics register */
    gmac_enable_transmit( p_gmac, true );
    gmac_enable_receive( p_gmac, true );
    gmac_enable_statistics_write( p_gmac, true );

    /* Set up the interrupts for transmission and errors */
    gmac_enable_interrupt( p_gmac,
                           GMAC_IER_RLEX |   /* Enable retry limit  exceeded interrupt. */
                           GMAC_IER_RXUBR |  /* Enable receive used bit read interrupt. */
                           GMAC_IER_ROVR |   /* Enable receive overrun interrupt. */
                           GMAC_IER_TCOMP |  /* Enable transmit complete interrupt. */
                           GMAC_IER_TUR |    /* Enable transmit underrun interrupt. */
                           GMAC_IER_TFC |    /* Enable transmit buffers exhausted in mid-frame interrupt. */
                           GMAC_IER_HRESP |  /* Enable Hresp not OK interrupt. */
                           GMAC_IER_PFNZ |   /* Enable pause frame received interrupt. */
                           GMAC_IER_PTZ |    /* Enable pause time zero interrupt. */
                           GMAC_IER_RCOMP ); /* Enable receive complete interrupt. */

    return GMAC_OK;
}

/**
 * \brief Initialize the GMAC driver.
 *
 * \param p_gmac   Pointer to the GMAC instance.
 * \param p_gmac_dev Pointer to the GMAC device instance.
 * \param p_opt GMAC configure options.
 */
void gmac_dev_init( Gmac * p_gmac,
                    gmac_device_t * p_gmac_dev,
                    gmac_options_t * p_opt )
{
    /* Disable TX & RX and more */
    gmac_network_control( p_gmac, 0 );
    gmac_disable_interrupt( p_gmac, ~0u );

    gmac_clear_statistics( p_gmac );

    /* Clear all status bits in the receive status register. */
    gmac_clear_rx_status( p_gmac, GMAC_RSR_RXOVR | GMAC_RSR_REC | GMAC_RSR_BNA
                          | GMAC_RSR_HNO );

    #ifndef GMAC_TSR_UND
        /* GMAC_TSR_UND is only defined by SAM4E. */
    #define GMAC_TSR_UND    0ul
    #endif
    /* Clear all status bits in the transmit status register */
    gmac_clear_tx_status( p_gmac, GMAC_TSR_UBR | GMAC_TSR_COL | GMAC_TSR_RLE
                          | GMAC_TSR_TFC | GMAC_TSR_TXCOMP | GMAC_TSR_UND );

    /* Clear interrupts */
    gmac_get_interrupt_status( p_gmac );
    #if !defined( ETHERNET_CONF_DATA_OFFSET )

        /*  Receive Buffer Offset
         * Indicates the number of bytes by which the received data
         * is offset from the start of the receive buffer
         * which can be handy for alignment reasons */
        /* Note: FreeRTOS+TCP wants to have this offset set to 2 bytes */
    #error ETHERNET_CONF_DATA_OFFSET not defined, assuming 0
    #endif

    /* Enable the copy of data into the buffers
     * ignore broadcasts, and not copy FCS. */

    gmac_set_config( p_gmac,
                     ( gmac_get_config( p_gmac ) & ~GMAC_NCFGR_RXBUFO_Msk ) |
                     GMAC_NCFGR_RFCS |                                /*  Remove FCS, frame check sequence (last 4 bytes) */
                     GMAC_NCFGR_PEN |                                 /* Pause Enable */
                     GMAC_NCFGR_RXBUFO( ETHERNET_CONF_DATA_OFFSET ) | /* Set Ethernet Offset  */
                     GMAC_RXD_RXCOEN );                               /* RXCOEN related function */

    /*
     * GMAC_DCFGR_TXCOEN: (GMAC_DCFGR) Transmitter Checksum Generation Offload Enable.
     * Note: SAM4E/SAME70 do have RX checksum offloading
     * but TX checksum offloading has NOT been implemented,
     * at least on a SAM4E.
     */

    {
        uint32_t ulValue = gmac_get_dma( p_gmac );

        /* Let the GMAC set TX checksum's. */
        ulValue |= GMAC_DCFGR_TXCOEN;
        #if ( SAME70 != 0 )
        {
            /* Transmitter Packet Buffer Memory Size Select:
             * Use full configured addressable space (4 KBytes). */
            ulValue |= GMAC_DCFGR_TXPBMS;
        }
        #endif

        /* Clear the DMA Receive Buffer Size (DRBS) field: */
        ulValue &= ~( GMAC_DCFGR_DRBS_Msk );
        /* And set it: */
        ulValue |= ( GMAC_RX_UNITSIZE / 64 ) << GMAC_DCFGR_DRBS_Pos;

        gmac_set_dma( p_gmac, ulValue );
    }

    /* Enable/Disable Copy(Receive) All Valid Frames. */
    gmac_enable_copy_all( p_gmac, p_opt->uc_copy_all_frame );

    /* Disable/Enable broadcast receiving */
    gmac_disable_broadcast( p_gmac, p_opt->uc_no_boardcast );


    /* Initialize memory */
    gmac_init_mem( p_gmac, p_gmac_dev );

    /* Set Mac Address */
    gmac_set_address( p_gmac, 0, p_opt->uc_mac_addr );
}

/**
 * \brief Frames can be read from the GMAC in multiple sections.
 *
 * Returns > 0 if a complete frame is available
 * It also it cleans up incomplete older frames
 */

static uint32_t gmac_dev_poll( gmac_device_t * p_gmac_dev )
{
    uint32_t ulReturn = 0;
    int32_t ulIndex = p_gmac_dev->ul_rx_idx;
    gmac_rx_descriptor_t * pxHead = &gs_rx_desc[ ulIndex ];

    #if ( ipconfigZERO_COPY_RX_DRIVER == 0 )
    {
        /* Discard any incomplete frames */
        while( ( pxHead->addr.val & GMAC_RXD_OWNERSHIP ) &&
               ( pxHead->status.val & GMAC_RXD_SOF ) == 0 )
        {
            pxHead->addr.val &= ~( GMAC_RXD_OWNERSHIP );
            circ_inc32( &ulIndex, GMAC_RX_BUFFERS );
            pxHead = &gs_rx_desc[ ulIndex ];
            p_gmac_dev->ul_rx_idx = ulIndex;
            #if ( GMAC_STATS != 0 )
            {
                gmacStats.incompCount++;
            }
            #endif
        }
    }
    #endif /* ipconfigZERO_COPY_RX_DRIVER == 0 */

    while( ( pxHead->addr.val & GMAC_RXD_OWNERSHIP ) != 0 )
    {
        #if ( ipconfigZERO_COPY_RX_DRIVER == 0 )
        {
            if( ( pxHead->status.val & GMAC_RXD_EOF ) != 0 )
            {
                /* Here a complete frame has been seen with SOF and EOF */
                ulReturn = pxHead->status.bm.b_len;
                break;
            }

            circ_inc32( &ulIndex, GMAC_RX_BUFFERS );
            pxHead = &gs_rx_desc[ ulIndex ];

            if( ( pxHead->addr.val & GMAC_RXD_OWNERSHIP ) == 0 )
            {
                /* CPU is not the owner (yet) */
                break;
            }

            if( ( pxHead->status.val & GMAC_RXD_SOF ) != 0 )
            {
                /* Strange, we found a new Start Of Frame
                 * discard previous segments */
                int32_t ulPrev = p_gmac_dev->ul_rx_idx;
                pxHead = &gs_rx_desc[ ulPrev ];

                do
                {
                    pxHead->addr.val &= ~( GMAC_RXD_OWNERSHIP );
                    circ_inc32( &ulPrev, GMAC_RX_BUFFERS );
                    pxHead = &gs_rx_desc[ ulPrev ];
                    #if ( GMAC_STATS != 0 )
                    {
                        gmacStats.truncCount++;
                    }
                    #endif
                } while( ulPrev != ulIndex );

                p_gmac_dev->ul_rx_idx = ulIndex;
            }
        }
        #else /* ipconfigZERO_COPY_RX_DRIVER */
        {
            if( ( pxHead->status.val & ( GMAC_RXD_SOF | GMAC_RXD_EOF ) ) == ( GMAC_RXD_SOF | GMAC_RXD_EOF ) )
            {
                /* Here a complete frame in a single segment. */
                ulReturn = pxHead->status.bm.b_len;
                break;
            }

            /* Return the buffer to DMA. */
            pxHead->addr.bm.b_ownership = 0;

            /* Let ulIndex/pxHead point to the next buffer. */
            circ_inc32( &ulIndex, GMAC_RX_BUFFERS );
            pxHead = &gs_rx_desc[ ulIndex ];
            /* And remember this index. */
            p_gmac_dev->ul_rx_idx = ulIndex;
        }
        #endif /* ipconfigZERO_COPY_RX_DRIVER */
    }

    return ulReturn;
}

/**
 * \brief Frames can be read from the GMAC in multiple sections.
 * Read ul_frame_size bytes from the GMAC receive buffers to pcTo.
 * p_rcv_size is the size of the entire frame.  Generally gmac_read
 * will be repeatedly called until the sum of all the ul_frame_size equals
 * the value of p_rcv_size.
 *
 * \param p_gmac_dev Pointer to the GMAC device instance.
 * \param p_frame Address of the frame buffer.
 * \param ul_frame_size  Length of the frame.
 * \param p_rcv_size   Received frame size.
 *
 * \return GMAC_OK if receiving frame successfully, otherwise failed.
 */
uint32_t gmac_dev_read( gmac_device_t * p_gmac_dev,
                        uint8_t * p_frame,
                        uint32_t ul_frame_size,
                        uint32_t * p_rcv_size,
                        uint8_t ** pp_recv_frame )
{
    int32_t nextIdx; /* A copy of the Rx-index 'ul_rx_idx' */
    int32_t bytesLeft = gmac_dev_poll( p_gmac_dev );
    gmac_rx_descriptor_t * pxHead;

    if( bytesLeft == 0 )
    {
        return GMAC_RX_NO_DATA;
    }

    /* Return the number of bytes received. */
    *p_rcv_size = bytesLeft;

    /* gmac_dev_poll has confirmed that there is a complete frame at
     * the current position 'ul_rx_idx'
     */
    nextIdx = p_gmac_dev->ul_rx_idx;

    #if ( NETWORK_BUFFERS_CACHED != 0 ) && ( __DCACHE_PRESENT != 0 ) && defined( CONF_BOARD_ENABLE_CACHE )
        SCB_InvalidateDCache();
    #endif

    #if ( ipconfigZERO_COPY_RX_DRIVER == 0 )
    {
        /* The frame will be copied in 1 or 2 memcpy's */
        if( p_frame != NULL )
        {
            const uint8_t * source;
            int32_t left;
            int32_t toCopy;

            source = gs_uc_rx_buffer + nextIdx * GMAC_RX_UNITSIZE;

            /* The driver receives frames up to 1514 bytes long.
             * The actual value of ul_frame_size is 1536, so the
             * following test is not really necessary:
             */

            /* Read +2 bytes because buffers are aligned at -2 bytes */
            left = min( bytesLeft + 2, ( int32_t ) ul_frame_size );
            toCopy = ( GMAC_RX_BUFFERS - nextIdx ) * GMAC_RX_UNITSIZE;

            if( toCopy > left )
            {
                toCopy = left;
            }

            memcpy( p_frame, source, toCopy );
            left -= toCopy;

            if( left != 0ul )
            {
                memcpy( p_frame + toCopy, ( void * ) gs_uc_rx_buffer, left );
            }
        }
    }
    #else /* ipconfigZERO_COPY_RX_DRIVER */
    {
        if( p_frame != NULL )
        {
            /* Return a pointer to the earlier DMA buffer. */
            *( pp_recv_frame ) = ( uint8_t * )
                                 ( ( ( gs_rx_desc[ nextIdx ].addr.val ) & ~( 0x03ul ) ) + 2 );
            /* Set the new DMA-buffer. */
            gs_rx_desc[ nextIdx ].addr.bm.addr_dw = ( ( uint32_t ) p_frame ) / 4;
        }
        else
        {
            /* The driver could not allocate a buffer to receive a packet.
             * Leave the current DMA buffer in place. */
        }
    }
    #endif /* ipconfigZERO_COPY_RX_DRIVER */

    do
    {
        pxHead = &gs_rx_desc[ nextIdx ];
        pxHead->addr.val &= ~( GMAC_RXD_OWNERSHIP );
        circ_inc32( &nextIdx, GMAC_RX_BUFFERS );

        if( pxHead->addr.val )
        {
            /* Just read it back to make sure that
             * it was written to SRAM. */
        }
    } while( ( pxHead->status.val & GMAC_RXD_EOF ) == 0 );

    p_gmac_dev->ul_rx_idx = nextIdx;

    return GMAC_OK;
}

/**
 * \brief Send ulLength bytes from pcFrom. This copies the buffer to one of the
 * GMAC Tx buffers, and then indicates to the GMAC that the buffer is ready.
 * If lEndOfFrame is true then the data being copied is the end of the frame
 * and the frame can be transmitted.
 *
 * \param p_gmac_dev Pointer to the GMAC device instance.
 * \param p_buffer       Pointer to the data buffer.
 * \param ul_size    Length of the frame.
 *
 * \return Length sent.
 */
uint32_t gmac_dev_write( gmac_device_t * p_gmac_dev,
                         void * p_buffer,
                         uint32_t ul_size )
{
    volatile gmac_tx_descriptor_t * p_tx_td;

    Gmac * p_hw = p_gmac_dev->p_hw;

    configASSERT( p_buffer != NULL );
    configASSERT( ul_size >= ipconfigETHERNET_MINIMUM_PACKET_BYTES );

    /* Check parameter */
    if( ul_size > GMAC_TX_UNITSIZE )
    {
        return GMAC_PARAM;
    }

    /* Pointers to the current transmit descriptor */
    p_tx_td = &gs_tx_desc[ p_gmac_dev->l_tx_head ];

    /* If no free TxTd, buffer can't be sent, schedule the wakeup callback */
    if( ( p_tx_td->status.val & GMAC_TXD_USED ) == 0 )
    {
        return GMAC_TX_BUSY;
    }

    /* Set up/copy data to transmission buffer */
    if( p_buffer && ul_size )
    {
        /* Driver manages the ring buffer */

        /* Calculating the checksum here is faster than calculating it from the GMAC buffer
         * because within p_buffer, it is well aligned */
        #if ( ipconfigZERO_COPY_TX_DRIVER != 0 )
        {
            /* Zero-copy... */
            p_tx_td->addr = ( uint32_t ) p_buffer;
        }
        #else
        {
            /* Or memcopy... */
            memcpy( ( void * ) p_tx_td->addr, p_buffer, ul_size );
        }
        #endif /* ipconfigZERO_COPY_TX_DRIVER */
        {
            /* Needs to be called for SAM4E series only. */
            vGMACGenerateChecksum( ( uint8_t * ) p_tx_td->addr, ( size_t ) ul_size );
        }
    }

    /* Update transmit descriptor status */

    /* The buffer size defined is the length of ethernet frame,
     * so it's always the last buffer of the frame. */
    if( p_gmac_dev->l_tx_head == ( int32_t ) ( GMAC_TX_BUFFERS - 1 ) )
    {
        /* No need to 'and' with GMAC_TXD_LEN_MASK because ul_size has been checked
         * GMAC_TXD_USED will now be cleared. */
        p_tx_td->status.val =
            ul_size | GMAC_TXD_LAST | GMAC_TXD_WRAP;
    }
    else
    {
        /* GMAC_TXD_USED will now be cleared. */
        p_tx_td->status.val =
            ul_size | GMAC_TXD_LAST;
    }

    if( p_tx_td->status.val )
    {
        /* Just read it back to make sure that
         * it was written to SRAM. */
    }

    circ_inc32( &p_gmac_dev->l_tx_head, GMAC_TX_BUFFERS );

    /* Now start to transmit if it is still not done */
    gmac_start_transmission( p_hw );

    return GMAC_OK;
}

/**
 * \brief Get current load of transmit.
 *
 * \param p_gmac_dev Pointer to the GMAC device instance.
 *
 * \return Current load of transmit.
 */
uint32_t gmac_dev_get_tx_load( gmac_device_t * p_gmac_dev )
{
    uint16_t us_head = p_gmac_dev->l_tx_head;
    uint16_t us_tail = p_gmac_dev->l_tx_tail;

    return CIRC_CNT( us_head, us_tail, GMAC_TX_BUFFERS );
}

/**
 *  \brief Register/Clear TX wakeup callback.
 *
 * When gmac_dev_write() returns GMAC_TX_BUSY (all transmit descriptor busy), the application
 * task calls gmac_dev_set_tx_wakeup_callback() to register func_wakeup() callback and
 * enters suspend state. The callback is in charge to resume the task once
 * several transmit descriptors have been released. The next time gmac_dev_write() will be called,
 * it shall be successful.
 *
 * This function is usually invoked with NULL callback from the TX wakeup
 * callback itself, to unregister. Once the callback has resumed the
 * application task, there is no need to invoke the callback again.
 *
 * \param p_gmac_dev   Pointer to GMAC device instance.
 * \param func_wakeup    Pointer to wakeup callback function.
 * \param uc_threshold Number of free transmit descriptor before wakeup callback invoked.
 *
 * \return GMAC_OK, GMAC_PARAM on parameter error.
 */
#if ( GMAC_USES_WAKEUP_CALLBACK )
    uint8_t gmac_dev_set_tx_wakeup_callback( gmac_device_t * p_gmac_dev,
                                             gmac_dev_wakeup_cb_t func_wakeup_cb,
                                             uint8_t uc_threshold )
    {
        if( func_wakeup_cb == NULL )
        {
            p_gmac_dev->func_wakeup_cb = NULL;
        }
        else
        {
            if( uc_threshold <= GMAC_TX_BUFFERS )
            {
                p_gmac_dev->func_wakeup_cb = func_wakeup_cb;
                p_gmac_dev->ul_wakeup_threshold = ( uint32_t ) uc_threshold;
            }
            else
            {
                return GMAC_PARAM;
            }
        }

        return GMAC_OK;
    }
#endif /* GMAC_USES_WAKEUP_CALLBACK */

/**
 * \brief Reset TX & RX queue & statistics.
 *
 * \param p_gmac_dev   Pointer to GMAC device instance.
 */
void gmac_dev_reset( gmac_device_t * p_gmac_dev )
{
    Gmac * p_hw = p_gmac_dev->p_hw;

    gmac_reset_rx_mem( p_gmac_dev );
    gmac_reset_tx_mem( p_gmac_dev );
    gmac_network_control( p_hw, GMAC_NCR_TXEN | GMAC_NCR_RXEN
                          | GMAC_NCR_WESTAT | GMAC_NCR_CLRSTAT );
}

void gmac_dev_halt( Gmac * p_gmac );

void gmac_dev_halt( Gmac * p_gmac )
{
    gmac_network_control( p_gmac, GMAC_NCR_WESTAT | GMAC_NCR_CLRSTAT );
    gmac_disable_interrupt( p_gmac, ~0u );
}


/**
 * \brief GMAC Interrupt handler.
 *
 * \param p_gmac_dev   Pointer to GMAC device instance.
 */

#if ( GMAC_STATS != 0 )
    void gmac_show_irq_counts()
    {
        int index;

        for( index = 0; index < ARRAY_SIZE( intPairs ); index++ )
        {
            if( gmacStats.intStatus[ intPairs[ index ].index ] )
            {
                FreeRTOS_printf( ( "%s : %6u\n", intPairs[ index ].name, gmacStats.intStatus[ intPairs[ index ].index ] ) );
            }
        }
    }
#endif /* if ( GMAC_STATS != 0 ) */

void gmac_handler( gmac_device_t * p_gmac_dev )
{
    Gmac * p_hw = p_gmac_dev->p_hw;

    gmac_tx_descriptor_t * p_tx_td;
    uint32_t ul_tx_status_flag;

    #if ( GMAC_STATS != 0 )
        int index;
    #endif

    uint32_t ul_isr = gmac_get_interrupt_status( p_hw );
    uint32_t ul_rsr = gmac_get_rx_status( p_hw );
    uint32_t ul_tsr = gmac_get_tx_status( p_hw );

    #if ( GMAC_STATS != 0 )
    {
        for( index = 0; index < ARRAY_SIZE( intPairs ); index++ )
        {
            if( ul_isr & intPairs[ index ].mask )
            {
                gmacStats.intStatus[ intPairs[ index ].index ]++;
            }
        }
    }
    #endif /* GMAC_STATS != 0 */

    /* RX packet */
    if( ( ul_isr & GMAC_ISR_RCOMP ) || ( ul_rsr & ( GMAC_RSR_REC | GMAC_RSR_RXOVR | GMAC_RSR_BNA ) ) )
    {
        /* Clear status */
        gmac_clear_rx_status( p_hw, ul_rsr );

        if( ul_isr & GMAC_ISR_RCOMP )
        {
            ul_rsr |= GMAC_RSR_REC;
        }

        /* Invoke callbacks which can be useful to wake up a task */
        xRxCallback( ul_rsr );
    }

    /* TX packet */
    if( ( ul_isr & GMAC_ISR_TCOMP ) || ( ( ul_tsr & TSR_TSR_BITS ) != 0U ) )
    {
        ul_tx_status_flag = GMAC_TSR_TXCOMP;
        /* A frame transmitted */

        /* Check RLE */
        if( ul_tsr & GMAC_TSR_RLE )
        {
            /* Status RLE & Number of discarded buffers */
            ul_tx_status_flag = GMAC_TSR_RLE | CIRC_CNT( p_gmac_dev->l_tx_head,
                                                         p_gmac_dev->l_tx_tail, GMAC_TX_BUFFERS );
            gmac_reset_tx_mem( p_gmac_dev );
            gmac_enable_transmit( p_hw, 1 );
        }

        /* Clear status */
        gmac_clear_tx_status( p_hw, ul_tsr );

        if( !CIRC_EMPTY( p_gmac_dev->l_tx_head, p_gmac_dev->l_tx_tail ) )
        {
            /* Check the buffers */
            do
            {
                p_tx_td = &gs_tx_desc[ p_gmac_dev->l_tx_tail ];

                /* Any error? Exit if buffer has not been sent yet */
                if( ( p_tx_td->status.val & GMAC_TXD_USED ) == 0 )
                {
                    break;
                }

                /* Notify upper layer that a packet has been sent */
                xTxCallback( ul_tx_status_flag, ( void * ) p_tx_td->addr ); /* Function call prvTxCallback */
                #if ( ipconfigZERO_COPY_TX_DRIVER != 0 )
                {
                    p_tx_td->addr = 0ul;
                }
                #endif /* ipconfigZERO_COPY_TX_DRIVER */

                circ_inc32( &p_gmac_dev->l_tx_tail, GMAC_TX_BUFFERS );
            } while( CIRC_CNT( p_gmac_dev->l_tx_head, p_gmac_dev->l_tx_tail,
                               GMAC_TX_BUFFERS ) );
        }

        if( ul_tsr & GMAC_TSR_RLE )
        {
            /* Notify upper layer RLE
             * (GMAC_TSR) Retry Limit Exceeded */
            xTxCallback( ul_tx_status_flag, NULL );
        }

        #if ( GMAC_USES_WAKEUP_CALLBACK )

            /* If a wakeup has been scheduled, notify upper layer that it can
             * send other packets, and the sending will be successful. */
            if( ( CIRC_SPACE( p_gmac_dev->l_tx_head, p_gmac_dev->l_tx_tail,
                              GMAC_TX_BUFFERS ) >= p_gmac_dev->ul_wakeup_threshold ) &&
                p_gmac_dev->func_wakeup_cb )
            {
                p_gmac_dev->func_wakeup_cb();
            }
        #endif
    }
}

/*@} */

/*/ @cond 0 */
/* *INDENT-OFF* */
#ifdef __cplusplus
    } /* extern "C" */
#endif
/* *INDENT-ON* */
/*/ @endcond */
