FreeRTOS+TCP and PHY + Switch


I have been working with FreeRTOS+TCP (V2.3.2) on a STM32 and a PHY with a built-in switch (Microchip LAN9303) for a while. At first it seems to run without problems and only when testing intensively do the problems appear. This occurs when there has been no traffic on the Ethernet for a while. In this case, the logic in FreeRTOS starts an auto-negotiation (see code path: prvEthernetUpdateConfig() → prvEthernetUpdateConfig() → xPhyStartAutoNegotiation()). When after this then when another device restarts to send Ethernet packets to my “FreeRTOS device” I get errors like “pxGetNetworkBufferWithDescriptor: INVALID BUFFER” and also no communication with my device is possible anymore (mostly this happens after the second round of silence → auto-negotiation → traffic).
So I have started to fix these errors. In my opinion, it is not the job of FreeRTOS to handle the other PHYs of the LAN9303. In specific, these PHYs are on the other side of the switch (see attached block diagram from the datasheet).

My way to fix this is to allow FreeRTOS to discover zero ID PHYs. So in this case FreeRTOS can find the virtual PHY. Contrary to the comment in phyHandling.c, the LAN9303 datasheet contains a note that IEEE allows all bits of the “ID Register” to be zero (LAN9303 datasheet Note 13-26 / DS00002308A). Therefore, I added a compile flag to allow zero PHY IDs and changed the xPhyDiscover function accordingly:

#if ipconfigPHY_ALLOW_ZERO_ID == 1
        /* A virtual phy may has a zero ID, like the LAN9303 */
        if( ( ulLowerID != ( uint16_t ) ~0U ) )
        /* A valid PHY id can not be all zeros or all ones. */
        if( ( ulLowerID != ( uint16_t ) ~0U ) && ( ulLowerID != ( uint16_t ) 0U ) )

Second, I set the number of expected PHYs to one (define ipconfigPHY_MAX_PORTS to 1).
These changes seem to work for me. So I have stopped auto-negotiation on the real PHYs of the “other side”. I am a bit unhappy about the fact that these changes only work when the virtual PHY is the first PHY in the address list. Perhaps it would be better to limit the range of addresses to the range of the virtual PHY (change the defines phyMIN_PHY_ADDRESS and phyMAX_PHY_ADDRESS).
I would like to know if this is a solid solution or if there are better ways?
Thanks a lot for the help!
Kind regards Sven

Hello @ArtifixLigni,

Welcome to FreeRTOS forums!

I am trying to understand why is the function prvEthernetUpdateConfig called at all? Does the link go down? Or does something change in the link?

In my opinion, it is not the job of FreeRTOS to handle the other PHYs of the LAN9303.

Yes, I would agree. FreeRTOS+TCP can and should only manage the PHY that is connected to it directly. At least for the IPv4 single interface version - that is the one you are using I think.

I would like to know if this is a solid solution

Is the virtual PHY guaranteed to be the first one? If so, then for your case, I think it would work.


P.S. Can you provide your network topology as well? So that I understand what is connected where?

Hi kanherea,

thanks for reply.

The link does not go down. Only the logic in FreeRTOS recognize that there is no traffic (no incoming packets) and then starts the autonegotiation. To understand this, start in networkinterface.c. In my case, at line 951 (FreeRTOS+TCP V2.3.2 for STM32Hxx):

if( xPhyCheckLinkStatus( &xPhyObject, xResult ) != 0 )
    /* Something has changed to a Link Status, need re-check. */
    prvEthernetUpdateConfig( pdFALSE );

Is there is no traffic, the function xPhyCheckLinkStatus returns true and with calling prvEthernetUpdateConfig the autonegotiation starts.

I hope the following block diagram is what you are asked for:

I remain grateful for support.

Kind regards

I’m not at my computer to see why but I don’t know why xPhyCheckLinkStatus() would renegotiate just because there is no traffic - that sounds like your issue.

Ohh… my fault. I see I forgot some details in my explanation.

First of all, I have to remind that I use a switch with one virtual PHY on the STM side and two PHYs on the other side of the switch (all PHYs are configurable by the STM).

When FreeRTOS receives a packet, the corresponding bits in ulLinkStatusMask are set high for all known PHYs:

for( xPhyIndex = 0; xPhyIndex < pxPhyObject->xPortCount; xPhyIndex++, ulBitMask <<= 1 )
    if( ( pxPhyObject->ulLinkStatusMask & ulBitMask ) == 0UL )
         pxPhyObject->ulLinkStatusMask |= ulBitMask;
         FreeRTOS_printf( ( "xPhyCheckLinkStatus: PHY LS now %02lX\r\n", pxPhyObject->ulLinkStatusMask ) );
         xNeedCheck = pdTRUE;

By the way, I don’t understand the logic why every bit in the bitmask has to be set?
Anyway, when a timeout occurs (no packets received), the function xPhyCheckLinkStatus starts testing the link status of all known PHY’s. Take a look at this:

else if( xTaskCheckForTimeOut( &( pxPhyObject->xLinkStatusTimer ), &( pxPhyObject->xLinkStatusRemaining ) ) != pdFALSE )
    for( xPhyIndex = 0; xPhyIndex < pxPhyObject->xPortCount; xPhyIndex++, ulBitMask <<= 1 )
        BaseType_t xPhyAddress = pxPhyObject->ucPhyIndexes[ xPhyIndex ];
        if( pxPhyObject->fnPhyRead( xPhyAddress, phyREG_01_BMSR, &ulStatus ) == 0 )
            if( !!( pxPhyObject->ulLinkStatusMask & ulBitMask ) != !!( ulStatus & phyBMSR_LINK_STATUS ) )
                xNeedCheck = pdTRUE;

In my special test case I have only one plugged Ethernet port. This means FreeRTOS now detects a difference between the previously noted link state and the actually detected link state. As a result this function returns true and accordingly prvEthernetUpdateConfig is called and an autonegotiation is executed.

I hope I could make my request more understandable now.

Sounds like you’re hitting a similar issue to what I had in xPhyCheckLinkStatus Triggering Re-Autonegotiations When Not All Ports Are Connected

The default logic for the phyHandling is to assume all network ports are connected & the xPhyCheckLinkStatus enforces that. If you’re running a switch with multiple ports (not all of them connected) you’ll need to modify that code in your checkout.

Hello @restep,

yes, I think your issue is very similar or even the same.

I also tried to delete the for loop and it works (but I never tested it under all conditions).

In the end, I think that to delete the for loop not consider the fact that it is not in the buisness of FreeRTOS to handle the PHYs which are not directly physically connected. So I think the better solution is to prevent FreeRTOS from “finding” these PHY’s (see my solution). Or do you see it differently?

Kind regards

I did see it differently, but I also didn’t have the same network architecture you have.

In my application all my switch ports are identical and I wanted the phyHandling code to manage all the ports on my switch (which could be optionally connected or not connected). In my mind the phyHandling is doing the switch setup and port configuration for all ports on my switch. Which if phyHandling wasn’t doing that I’d be doing it manually.

Just be aware if phyHandling is not discovering all the ports on your switch, it won’t be configuring all of them. And you’ll be stuck with the default values the switch comes with!