/*
 * FreeRTOS+TCP V3.1.0
 * Copyright (C) 2022 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 *
 * SPDX-License-Identifier: MIT
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * http://aws.amazon.com/freertos
 * http://www.FreeRTOS.org
 */

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

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

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

/* Some files from the Atmel Software Framework */
/*_RB_ The SAM4E portable layer has three different header files called gmac.h! */
#include "instance/gmac.h"
#include <sysclk.h>
#include <ethernet_phy.h>

#ifndef BMSR_LINK_STATUS
    #define BMSR_LINK_STATUS    0x0004          /*!< Link status */
#endif

#ifndef PHY_LS_HIGH_CHECK_TIME_MS

/* Check if the LinkSStatus in the PHY is still high after 15 seconds of not
 * receiving packets. */
    #define PHY_LS_HIGH_CHECK_TIME_MS    15000
#endif

#ifndef PHY_LS_LOW_CHECK_TIME_MS
    /* Check if the LinkSStatus in the PHY is still low every second. */
    #define PHY_LS_LOW_CHECK_TIME_MS    1000
#endif

/* Interrupt events to process.  Currently only the Rx event is processed
 * although code for other events is included to allow for possible future
 * expansion. */
#define EMAC_IF_RX_EVENT              1UL
#define EMAC_IF_TX_EVENT              2UL
#define EMAC_IF_ERR_EVENT             4UL
#define EMAC_IF_ALL_EVENT             ( EMAC_IF_RX_EVENT | EMAC_IF_TX_EVENT | EMAC_IF_ERR_EVENT )

#define ETHERNET_CONF_PHY_ADDR        BOARD_GMAC_PHY_ADDR

#define HZ_PER_MHZ                    ( 1000000UL )

#ifndef EMAC_MAX_BLOCK_TIME_MS
    #define EMAC_MAX_BLOCK_TIME_MS    100ul
#endif

#if !defined( GMAC_USES_TX_CALLBACK ) || ( GMAC_USES_TX_CALLBACK != 1 )
    #error Please define GMAC_USES_TX_CALLBACK as 1
#endif

/* Default the size of the stack used by the EMAC deferred handler task to 4x
 *  the size of the stack used by the idle task - but allow this to be overridden in
 *  FreeRTOSConfig.h as configMINIMAL_STACK_SIZE is a user definable constant. */
#ifndef configEMAC_TASK_STACK_SIZE
    #define configEMAC_TASK_STACK_SIZE    ( 4 * configMINIMAL_STACK_SIZE )
#endif

/*-----------------------------------------------------------*/

/*
 * Wait a fixed time for the link status to indicate the network is up.
 */
static BaseType_t xGMACWaitLS( TickType_t xMaxTime );

#if ( ipconfigDRIVER_INCLUDED_TX_IP_CHECKSUM == 1 ) && ( ipconfigHAS_TX_CRC_OFFLOADING == 0 )
    void vGMACGenerateChecksum( uint8_t * apBuffer );
#endif

/*
 * Called from the ASF GMAC driver.
 */
static void prvRxCallback( uint32_t ulStatus );
static void prvTxCallback( uint32_t ulStatus,
                           uint8_t * puc_buffer );

/*
 * A deferred interrupt handler task that processes GMAC interrupts.
 */
static void prvEMACHandlerTask( void * pvParameters );

/*
 * Initialise the ASF GMAC driver.
 */
static BaseType_t prvGMACInit( void );

/*
 * Try to obtain an Rx packet from the hardware.
 */
static uint32_t prvEMACRxPoll( void );

/*-----------------------------------------------------------*/

/* Bit map of outstanding ETH interrupt events for processing.  Currently only
 * the Rx interrupt is handled, although code is included for other events to
 * enable future expansion. */
static volatile uint32_t ulISREvents;

/* A copy of PHY register 1: 'PHY_REG_01_BMSR' */
static uint32_t ulPHYLinkStatus = 0;
static volatile BaseType_t xGMACSwitchRequired;

/* ethernet_phy_addr: the address of the PHY in use.
 * Atmel was a bit ambiguous about it so the address will be stored
 * in this variable, see ethernet_phy.c */
extern int ethernet_phy_addr;

/* LLMNR multicast address. */
static const uint8_t llmnr_mac_address[] = { 0x01, 0x00, 0x5E, 0x00, 0x00, 0xFC };

/* The GMAC object as defined by the ASF drivers. */
static gmac_device_t gs_gmac_dev;

/* Holds the handle of the task used as a deferred interrupt processor.  The
 * handle is used so direct notifications can be sent to the task for all EMAC/DMA
 * related interrupts. */
TaskHandle_t xEMACTaskHandle = NULL;

static QueueHandle_t xTxBufferQueue;
int tx_release_count[ 4 ];

/* xTXDescriptorSemaphore is a counting semaphore with
 * a maximum count of GMAC_TX_BUFFERS, which is the number of
 * DMA TX descriptors. */
static SemaphoreHandle_t xTXDescriptorSemaphore = NULL;

/*-----------------------------------------------------------*/

/*
 * GMAC interrupt handler.
 */
void GMAC_Handler( void )
{
    xGMACSwitchRequired = pdFALSE;

    /* gmac_handler() may call prvRxCallback() which may change
     * the value of xGMACSwitchRequired. */
    gmac_handler( &gs_gmac_dev );

    if( xGMACSwitchRequired != pdFALSE )
    {
        portEND_SWITCHING_ISR( xGMACSwitchRequired );
    }
}
/*-----------------------------------------------------------*/

static void prvRxCallback( uint32_t ulStatus )
{
    if( ( ( ulStatus & GMAC_RSR_REC ) != 0 ) && ( xEMACTaskHandle != NULL ) )
    {
        /* let the prvEMACHandlerTask know that there was an RX event. */
        ulISREvents |= EMAC_IF_RX_EVENT;
        /* Only an RX interrupt can wakeup prvEMACHandlerTask. */
        vTaskNotifyGiveFromISR( xEMACTaskHandle, ( BaseType_t * ) &xGMACSwitchRequired );
    }
}
/*-----------------------------------------------------------*/

static void prvTxCallback( uint32_t ulStatus,
                           uint8_t * puc_buffer )
{
    if( ( xTxBufferQueue != NULL ) && ( xEMACTaskHandle != NULL ) )
    {
        /* let the prvEMACHandlerTask know that there was an RX event. */
        ulISREvents |= EMAC_IF_TX_EVENT;

        vTaskNotifyGiveFromISR( xEMACTaskHandle, ( BaseType_t * ) &xGMACSwitchRequired );
        xQueueSendFromISR( xTxBufferQueue, &puc_buffer, ( BaseType_t * ) &xGMACSwitchRequired );
        tx_release_count[ 2 ]++;
    }
}
/*-----------------------------------------------------------*/

BaseType_t xNetworkInterfaceInitialise( void )
{
    const TickType_t x5_Seconds = 5000UL;

    if( xEMACTaskHandle == NULL )
    {
        prvGMACInit();

        /* Wait at most 5 seconds for a Link Status in the PHY. */
        xGMACWaitLS( pdMS_TO_TICKS( x5_Seconds ) );

        /* The handler task is created at the highest possible priority to
         * ensure the interrupt handler can return directly to it. */
        xTaskCreate( prvEMACHandlerTask, "EMAC", configEMAC_TASK_STACK_SIZE, NULL, configMAX_PRIORITIES - 1, &xEMACTaskHandle );
        configASSERT( xEMACTaskHandle != NULL );
    }

    if( xTxBufferQueue == NULL )
    {
        xTxBufferQueue = xQueueCreate( GMAC_TX_BUFFERS, sizeof( void * ) );
        configASSERT( xTxBufferQueue != NULL );
    }

    if( xTXDescriptorSemaphore == NULL )
    {
        xTXDescriptorSemaphore = xSemaphoreCreateCounting( ( UBaseType_t ) GMAC_TX_BUFFERS, ( UBaseType_t ) GMAC_TX_BUFFERS );
        configASSERT( xTXDescriptorSemaphore != NULL );
    }

    /* When returning non-zero, the stack will become active and
     * start DHCP (in configured) */
    return ( ulPHYLinkStatus & BMSR_LINK_STATUS ) != 0;
}
/*-----------------------------------------------------------*/

BaseType_t xGetPhyLinkStatus( void )
{
    BaseType_t xResult;

    /* This function returns true if the Link Status in the PHY is high. */
    if( ( ulPHYLinkStatus & BMSR_LINK_STATUS ) != 0 )
    {
        xResult = pdTRUE;
    }
    else
    {
        xResult = pdFALSE;
    }

    return xResult;
}
/*-----------------------------------------------------------*/

BaseType_t xNetworkInterfaceOutput( NetworkBufferDescriptor_t * const pxDescriptor,
                                    BaseType_t bReleaseAfterSend )
{
/* Do not wait too long for a free TX DMA buffer. */
    const TickType_t xBlockTimeTicks = pdMS_TO_TICKS( 50u );

    do
    {
        if( ( ulPHYLinkStatus & BMSR_LINK_STATUS ) == 0 )
        {
            /* Do not attempt to send packets as long as the Link Status is low. */
            break;
        }

        if( xTXDescriptorSemaphore == NULL )
        {
            /* Semaphore has not been created yet? */
            break;
        }

        if( xSemaphoreTake( xTXDescriptorSemaphore, xBlockTimeTicks ) != pdPASS )
        {
            /* Time-out waiting for a free TX descriptor. */
            tx_release_count[ 3 ]++;
            break;
        }

        #if ( ipconfigZERO_COPY_TX_DRIVER != 0 )
            {
                /* Confirm that the pxDescriptor may be kept by the driver. */
                configASSERT( bReleaseAfterSend != pdFALSE );
            }
        #endif /* ipconfigZERO_COPY_TX_DRIVER */

        gmac_dev_write( &gs_gmac_dev, ( void * ) pxDescriptor->pucEthernetBuffer, pxDescriptor->xDataLength, prvTxCallback );

        #if ( ipconfigZERO_COPY_TX_DRIVER != 0 )
            {
                /* Confirm that the pxDescriptor may be kept by the driver. */
                bReleaseAfterSend = pdFALSE;
            }
        #endif /* ipconfigZERO_COPY_TX_DRIVER */
        /* Not interested in a call-back after TX. */
        iptraceNETWORK_INTERFACE_TRANSMIT();
    } while( ipFALSE_BOOL );

    if( bReleaseAfterSend != pdFALSE )
    {
        vReleaseNetworkBufferAndDescriptor( pxDescriptor );
    }

    return pdTRUE;
}
/*-----------------------------------------------------------*/

static BaseType_t prvGMACInit( void )
{
    uint32_t ncfgr;

    gmac_options_t gmac_option;

    memset( &gmac_option, '\0', sizeof( gmac_option ) );
    gmac_option.uc_copy_all_frame = 0;
    gmac_option.uc_no_boardcast = 0;
    memcpy( gmac_option.uc_mac_addr, ipLOCAL_MAC_ADDRESS, sizeof( gmac_option.uc_mac_addr ) );

    gs_gmac_dev.p_hw = GMAC;
    gmac_dev_init( GMAC, &gs_gmac_dev, &gmac_option );

    NVIC_SetPriority( GMAC_IRQn, configMAC_INTERRUPT_PRIORITY );
    NVIC_EnableIRQ( GMAC_IRQn );

    /* Contact the Ethernet PHY and store it's address in 'ethernet_phy_addr' */
    ethernet_phy_init( GMAC, ETHERNET_CONF_PHY_ADDR, sysclk_get_cpu_hz() );

    ethernet_phy_auto_negotiate( GMAC, ethernet_phy_addr );
    ethernet_phy_set_link( GMAC, ethernet_phy_addr, 1 );

    /* The GMAC driver will call a hook prvRxCallback(), which
     * in turn will wake-up the task by calling vTaskNotifyGiveFromISR() */
    gmac_dev_set_rx_callback( &gs_gmac_dev, prvRxCallback );
    gmac_set_address( GMAC, 1, ( uint8_t * ) llmnr_mac_address );

    ncfgr = GMAC_NCFGR_SPD | GMAC_NCFGR_FD;

    GMAC->GMAC_NCFGR = ( GMAC->GMAC_NCFGR & ~( GMAC_NCFGR_SPD | GMAC_NCFGR_FD ) ) | ncfgr;

    return 1;
}
/*-----------------------------------------------------------*/

static inline unsigned long ulReadMDIO( unsigned /*short*/ usAddress )
{
    uint32_t ulValue, ulReturn;
    int rc;

    gmac_enable_management( GMAC, 1 );
    rc = gmac_phy_read( GMAC, ethernet_phy_addr, usAddress, &ulValue );
    gmac_enable_management( GMAC, 0 );

    if( rc == GMAC_OK )
    {
        ulReturn = ulValue;
    }
    else
    {
        ulReturn = 0UL;
    }

    return ulReturn;
}
/*-----------------------------------------------------------*/

static BaseType_t xGMACWaitLS( TickType_t xMaxTime )
{
    TickType_t xStartTime = xTaskGetTickCount();
    TickType_t xEndTime;
    BaseType_t xReturn;
    const TickType_t xShortTime = pdMS_TO_TICKS( 100UL );

    for( ; ; )
    {
        xEndTime = xTaskGetTickCount();

        if( ( xEndTime - xStartTime ) > xMaxTime )
        {
            /* Waited more than xMaxTime, return. */
            xReturn = pdFALSE;
            break;
        }

        /* Check the link status again. */
        ulPHYLinkStatus = ulReadMDIO( PHY_REG_01_BMSR );

        if( ( ulPHYLinkStatus & BMSR_LINK_STATUS ) != 0 )
        {
            /* Link is up - return. */
            xReturn = pdTRUE;
            break;
        }

        /* Link is down - wait in the Blocked state for a short while (to allow
         * other tasks to execute) before checking again. */
        vTaskDelay( xShortTime );
    }

    FreeRTOS_printf( ( "xGMACWaitLS: %ld (PHY %d) freq %lu Mz\n",
                       xReturn,
                       ethernet_phy_addr,
                       sysclk_get_cpu_hz() / HZ_PER_MHZ ) );

    return xReturn;
}
/*-----------------------------------------------------------*/

/*#if( ipconfigDRIVER_INCLUDED_TX_IP_CHECKSUM == 1 ) && ( ipconfigHAS_TX_CRC_OFFLOADING == 0 ) */

void vGMACGenerateChecksum( uint8_t * apBuffer )
{
    ProtocolPacket_t * xProtPacket = ( ProtocolPacket_t * ) apBuffer;

    if( xProtPacket->xTCPPacket.xEthernetHeader.usFrameType == ipIPv4_FRAME_TYPE )
    {
        IPHeader_t * pxIPHeader = &( xProtPacket->xTCPPacket.xIPHeader );

        /* Calculate the IP header checksum. */
        pxIPHeader->usHeaderChecksum = 0x00;
        pxIPHeader->usHeaderChecksum = usGenerateChecksum( 0U, ( uint8_t * ) &( pxIPHeader->ucVersionHeaderLength ), ipSIZE_OF_IPv4_HEADER );
        pxIPHeader->usHeaderChecksum = ~FreeRTOS_htons( pxIPHeader->usHeaderChecksum );

        /* Calculate the TCP checksum for an outgoing packet. */
        usGenerateProtocolChecksum( ( uint8_t * ) apBuffer, pdTRUE );
    }
}

/*#endif */
/*-----------------------------------------------------------*/

static uint32_t prvEMACRxPoll( void )
{
    unsigned char * pucUseBuffer;
    uint32_t ulReceiveCount, ulResult, ulReturnValue = 0;
    static NetworkBufferDescriptor_t * pxNextNetworkBufferDescriptor = NULL;
    const UBaseType_t xMinDescriptorsToLeave = 2UL;
    const TickType_t xBlockTime = pdMS_TO_TICKS( 100UL );
    static IPStackEvent_t xRxEvent = { eNetworkRxEvent, NULL };

    for( ; ; )
    {
        /* If pxNextNetworkBufferDescriptor was not left pointing at a valid
         * descriptor then allocate one now. */
        if( ( pxNextNetworkBufferDescriptor == NULL ) && ( uxGetNumberOfFreeNetworkBuffers() > xMinDescriptorsToLeave ) )
        {
            pxNextNetworkBufferDescriptor = pxGetNetworkBufferWithDescriptor( ipTOTAL_ETHERNET_FRAME_SIZE, xBlockTime );
        }

        if( pxNextNetworkBufferDescriptor != NULL )
        {
            /* Point pucUseBuffer to the buffer pointed to by the descriptor. */
            pucUseBuffer = ( unsigned char * ) ( pxNextNetworkBufferDescriptor->pucEthernetBuffer - ipconfigPACKET_FILLER_SIZE );
        }
        else
        {
            /* As long as pxNextNetworkBufferDescriptor is NULL, the incoming
             * messages will be flushed and ignored. */
            pucUseBuffer = NULL;
        }

        /* Read the next packet from the hardware into pucUseBuffer. */
        ulResult = gmac_dev_read( &gs_gmac_dev, pucUseBuffer, ipTOTAL_ETHERNET_FRAME_SIZE, &ulReceiveCount );

        if( ( ulResult != GMAC_OK ) || ( ulReceiveCount == 0 ) )
        {
            /* No data from the hardware. */
            break;
        }

        if( pxNextNetworkBufferDescriptor == NULL )
        {
            /* Data was read from the hardware, but no descriptor was available
             * for it, so it will be dropped. */
            iptraceETHERNET_RX_EVENT_LOST();
            continue;
        }

        iptraceNETWORK_INTERFACE_RECEIVE();
        pxNextNetworkBufferDescriptor->xDataLength = ( size_t ) ulReceiveCount;
        xRxEvent.pvData = ( void * ) pxNextNetworkBufferDescriptor;

        /* Send the descriptor to the IP task for processing. */
        if( xSendEventStructToIPTask( &xRxEvent, xBlockTime ) != pdTRUE )
        {
            /* The buffer could not be sent to the stack so must be released
             * again. */
            vReleaseNetworkBufferAndDescriptor( pxNextNetworkBufferDescriptor );
            iptraceETHERNET_RX_EVENT_LOST();
            FreeRTOS_printf( ( "prvEMACRxPoll: Can not queue return packet!\n" ) );
        }

        /* Now the buffer has either been passed to the IP-task,
         * or it has been released in the code above. */
        pxNextNetworkBufferDescriptor = NULL;
        ulReturnValue++;
    }

    return ulReturnValue;
}
/*-----------------------------------------------------------*/

static void prvEMACHandlerTask( void * pvParameters )
{
    TimeOut_t xPhyTime;
    TickType_t xPhyRemTime;
    UBaseType_t uxLastMinBufferCount = 0, uxCount;
    UBaseType_t uxCurrentCount;

    #if ( ipconfigCHECK_IP_QUEUE_SPACE != 0 )
        UBaseType_t uxLastMinQueueSpace;
    #endif
    #if ( ipconfigZERO_COPY_TX_DRIVER != 0 )
        NetworkBufferDescriptor_t * pxBuffer;
    #endif
    uint8_t * pucBuffer;
    BaseType_t xResult = 0;
    uint32_t xStatus;
    const TickType_t ulMaxBlockTime = pdMS_TO_TICKS( EMAC_MAX_BLOCK_TIME_MS );

    /* Remove compiler warnings about unused parameters. */
    ( void ) pvParameters;

    configASSERT( xEMACTaskHandle != NULL );

    vTaskSetTimeOutState( &xPhyTime );
    xPhyRemTime = pdMS_TO_TICKS( PHY_LS_LOW_CHECK_TIME_MS );

    for( ; ; )
    {
        uxCurrentCount = uxGetMinimumFreeNetworkBuffers();

        if( uxLastMinBufferCount != uxCurrentCount )
        {
            /* The logging produced below may be helpful
             * while tuning +TCP: see how many buffers are in use. */
            uxLastMinBufferCount = uxCurrentCount;
            FreeRTOS_printf( ( "Network buffers: %lu lowest %lu\n",
                               uxGetNumberOfFreeNetworkBuffers(), uxCurrentCount ) );
        }

        #if ( ipconfigCHECK_IP_QUEUE_SPACE != 0 )
            {
                uxCurrentCount = uxGetMinimumIPQueueSpace();

                if( uxLastMinQueueSpace != uxCurrentCount )
                {
                    /* The logging produced below may be helpful
                     * while tuning +TCP: see how many buffers are in use. */
                    uxLastMinQueueSpace = uxCurrentCount;
                    FreeRTOS_printf( ( "Queue space: lowest %lu\n", uxCurrentCount ) );
                }
            }
        #endif /* ipconfigCHECK_IP_QUEUE_SPACE */

        if( ( ulISREvents & EMAC_IF_ALL_EVENT ) == 0 )
        {
            /* No events to process now, wait for the next. */
            ulTaskNotifyTake( pdFALSE, ulMaxBlockTime );
        }

        if( ( ulISREvents & EMAC_IF_RX_EVENT ) != 0 )
        {
            ulISREvents &= ~EMAC_IF_RX_EVENT;

            /* Wait for the EMAC interrupt to indicate that another packet has been
             * received. */
            xResult = prvEMACRxPoll();
        }

        if( ( ulISREvents & EMAC_IF_TX_EVENT ) != 0 )
        {
            /* Future extension: code to release TX buffers if zero-copy is used. */
            ulISREvents &= ~EMAC_IF_TX_EVENT;

            while( xQueueReceive( xTxBufferQueue, &pucBuffer, 0 ) != pdFALSE )
            {
                #if ( ipconfigZERO_COPY_TX_DRIVER != 0 )
                    {
                        pxBuffer = pxPacketBuffer_to_NetworkBuffer( pucBuffer );

                        if( pxBuffer != NULL )
                        {
                            vReleaseNetworkBufferAndDescriptor( pxBuffer );
                            tx_release_count[ 0 ]++;
                        }
                        else
                        {
                            tx_release_count[ 1 ]++;
                        }
                    }
                #else /* if ( ipconfigZERO_COPY_TX_DRIVER != 0 ) */
                    {
                        tx_release_count[ 0 ]++;
                    }
                #endif /* if ( ipconfigZERO_COPY_TX_DRIVER != 0 ) */
                uxCount = uxQueueMessagesWaiting( ( QueueHandle_t ) xTXDescriptorSemaphore );

                if( uxCount < GMAC_TX_BUFFERS )
                {
                    /* Tell the counting semaphore that one more TX descriptor is available. */
                    xSemaphoreGive( xTXDescriptorSemaphore );
                }
            }
        }

        if( ( ulISREvents & EMAC_IF_ERR_EVENT ) != 0 )
        {
            /* Future extension: logging about errors that occurred. */
            ulISREvents &= ~EMAC_IF_ERR_EVENT;
        }

        if( xResult > 0 )
        {
            /* A packet was received. No need to check for the PHY status now,
             * but set a timer to check it later on. */
            vTaskSetTimeOutState( &xPhyTime );
            xPhyRemTime = pdMS_TO_TICKS( PHY_LS_HIGH_CHECK_TIME_MS );
            xResult = 0;
        }
        else if( xTaskCheckForTimeOut( &xPhyTime, &xPhyRemTime ) != pdFALSE )
        {
            /* Check the link status again. */
            xStatus = ulReadMDIO( PHY_REG_01_BMSR );

            if( ( ulPHYLinkStatus & BMSR_LINK_STATUS ) != ( xStatus & BMSR_LINK_STATUS ) )
            {
                ulPHYLinkStatus = xStatus;
                FreeRTOS_printf( ( "prvEMACHandlerTask: PHY LS now %d\n", ( ulPHYLinkStatus & BMSR_LINK_STATUS ) != 0 ) );
            }

            vTaskSetTimeOutState( &xPhyTime );

            if( ( ulPHYLinkStatus & BMSR_LINK_STATUS ) != 0 )
            {
                xPhyRemTime = pdMS_TO_TICKS( PHY_LS_HIGH_CHECK_TIME_MS );
            }
            else
            {
                xPhyRemTime = pdMS_TO_TICKS( PHY_LS_LOW_CHECK_TIME_MS );
            }
        }
    }
}
/*-----------------------------------------------------------*/
