FreeRTOS-Plus-TCP PHY autonegotiate setup

Hello, over the last few days I’ve spent a lot of time trying to figure out why I couldn’t talk to a Zynq 7000 board when executing a very specific boot-up sequence. The (repeatable) problem ocurred when I had a direct Ethernet connection between a desktop and the Zynq 7000. Both devices started powered off. I then started the Zynq board, waited a few seconds, and then started the desktop. If I did this, I just couldn’t talk to the Zynq board, couldn’t ping it, etc.

After looking troubleshooting for days, I (think) finally found the problem. In the function xNetworkInterfaceInitialise() there is a line of code that initializes the PHY, ulLinkSpeed = Phy_Setup( pxEMAC_PS );. Going deeper into this call, the PC ends up in the file x_emacpsif_physpeed.c, specifically in the loop below:

while( !( status & IEEE_STAT_AUTONEGOTIATE_COMPLETE ) )
{
vTaskDelay( MINIMUM_SLEEP_TIME );
#if XPAR_GIGE_PCS_PMA_CORE_PRESENT == 1
#else
XEmacPs_PhyRead( xemacpsp, phy_addr, IEEE_COPPER_SPECIFIC_STATUS_REG_2,
&temp );

    if( temp & IEEE_AUTONEG_ERROR_MASK )
    {
        FreeRTOS_printf( ( "Auto negotiation error \n" ) );
    }
#endif
XEmacPs_PhyRead( xemacpsp, phy_addr, IEEE_STATUS_REG_OFFSET,
                 &status );

}

This loop does not return until the PHY detects a link and autonegotiates. The desktop is still powered off. The problem happens the moment I power on the desktop. Within seconds of it turning it on, the PHY negotiated a speed of 10 Mbps, well before the Windows OS even launched. At this point the Zynq thinks it is talking via 10 Mbps speeds, not the 1000 Mbps that both devices are capable of. After Windows starts up, for some reason the NIC does not go down to 10 Mbps, might be some configuration, not sure.

My knowledge of PHY is very minimal. What is supposed to happen here? I find it very strange that the Zynq’s PHY autonegotiates to 10 Mbps within seconds of the desktop powering on.

Also, looking through the FreeRTOS-Plus-TCP code, I don’t see anywhere where the MAC asks the PHY to re-autonegotiate. Seems like once a speed is picked through xNetworkInterfaceInitialise(), there’s no going back. Even if the PHY link goes down and then high again, it does not autonegotiate. Should it?

Any input or help is much appreciated.

You should create a thread to monitor the status of the PHY connection separately. When the PHY connection status changes, the MAC configuration needs to be changed because the PHY status will change based on the status of the peer network card (10/100/1000), and the MAC configuration needs to match the PHY status

I see, I thought FreeRTOS-Plus-TCP and the stuff inside of the portable directory was responsible for handling the MAC and PHY. Avoid possible race conditions and things like that.

Is there a clean way of “pausing” the IPTask thread while I re-configure the MAC and PHY?

Hello Maty,

The first thing that comes to my mind is: do you use a direct cable between DUT and laptop? You better get a switch!
That will make both devices boot and negotiate independently from the other. Both will probably choose 1 Gb and that works perfectly.

I must say the the PHY driver for Zynq is borrowed from a Xilinx library, and we haven’t changed it much.

But it would be great if we have a method of getting out of this 10 Mb trap.

Normally when the user sees that the connection is non-functional, she/he will disconnect, wait, and reconnect the cable. That event is noticed by the driver, which should start a re-negotiation. Limited by time, e.g. up to 10 seconds (?). And yes you can make the interface down for a while:

void FreeRTOS_NetworkDown( struct xNetworkInterface * pxNetworkInterface );

The function xZynqNetworkInterfaceInitialise() will be called (polled) again until it returns pdPASS:

    return ( xLinkStatus != pdFALSE ) ? pdPASS : pdFAIL;

No need to pause the IP-task. It may continue using other interfaces (if any), and a call to xZynqNetworkInterfaceOutput() will be ignored:

static BaseType_t xZynqNetworkInterfaceOutput( NetworkInterface_t * pxInterface,
                                               NetworkBufferDescriptor_t * const pxBuffer,
                                               BaseType_t bReleaseAfterSend )
    if( ( ulPHYLinkStates[ xEMACIndex ] & niBMSR_LINK_STATUS ) != 0UL )
    {
        /* Actually send the packet. */
    }
    else
    {
        /* Can not use the PHY for now. */
    }

But once again, normally when a switch is placed between the Ethernet devices, no PHY problem occurs.

Another problem that I saw when connecting a DUT to a laptop was that both devices were using the IP address 0.0.0.0 and both devices starting looking for a DHCP server. Very frustrating :slight_smile:

Hi @htibosch ,

Thank you for the detailed response. I agree that we should probably use a switch and when I developed this board, I did use a switch, but in the scenario that I’m in, a switch is completely optional and to save money and space, it’s not used.

The problem that I see though is that once a connection is autonegotiated, whether it is 10 Mbps or 1000 Mbps, there’s no code to renegotiate this. At first I added the function you mentioned void FreeRTOS_NetworkDown( void ), but all this does was fire an event to the IPTask. The IPTask then calls void prvProcessNetworkDownEvent( void ), which in turn calls BaseType_t xNetworkInterfaceInitialise( void ). Within this function the first line of code is:

/* Guard against the init function being called more than once. */
if( xEMACTaskHandle == NULL )
{ 
   ...
}
else
{
    /* Initialisation was already performed, just wait for the link. */
    prvGMACWaitLS( xWaitRelinkDelay );
}

This does not autonegotiate the link (because we already initialised the network interface), it just waits for a link to establish. The code that actually negotiates the speeds is inside the if ( ) statement on the above block of code: Phy_Setup ( pxEMAC_PS );get_IEEE_phy_speed( xemacpsp );

Having said all of this, I’m thinking that we’re looking at different codes because I don’t have a xZynqNetworkInterfaceOutput() function. The last thing that I did before posting on the forums was to update to the latest FreeRTOS version, I’m using FreeRTOS+TCP V3.1.0. Attaching the FreeRTOS code that I’m using.

Zynq7 FreeRTOS.zip (576.9 KB)

Hi @Maty,

I’d like to chime in and agree with @htibosch’s opinion on this matter. It is a common practice to have a switch or intermediary device in the network configuration to mitigate such issues.

You’re correct about the re-initialization flow condition potentially blocking renegotiation. I suggest considering the unplug/re-plug cable flow at the driver level, similar to the RT1060 example. Could you please help investigate how Zynq7000 handles this flow to adapt it for re-initialization?

Thank you.