FreeRTOS+TCP: STM32H7 Support

I’m starting this thread to reinvigorate discussion about development of an Ethernet port for the STM32H7xx series.

For a bit of history: I’ve used FreeRTOS+TCP on an STM32F7 with good success and it was relatively painless. There are things I learned while doing that, however, that I think might be of value when developing the driver/port for the H7.

  1. I modified the STM32F7 HAL files to minimize the dependency chain, ending up with the following files:
  • stm32_hal_legacy.h
  • stm32F7xx_hal.h
  • stm32F7xx_hal_conf.h
  • stm32F7xx_hal_def.h
  • stm32F7xx_hal_eth.c
  • stm32F7xx_hal_eth.h

What would be ideal is to reduce these further to a single .C and .H file, which would greatly improve project archiving. I think elimination of the STM HAL dependency chain in this area is quite possible as A) the Ethernet peripheral is quite stand-alone, and B) the STM HAL dependency chain can add a lot of unnecessary clutter to a project.

  1. ST’s HAL_ETH_Init(.) function is abysmal. It should be a threaded process that invokes a hookable callback when complete, passing the success of the operation. Making it threaded implies dependency on FreeRTOS, which IMO would be OK. After all, this is a FreeRTOS TCP stack intended to run alongside FreeRTOS.

  2. Phy initialization could be broken out such that an additional phy-device-specific implementation file could provide phy device initialization and control, adhering to an API contract. That way, community members could submit, if desired, their implementation of the file for a specific phy device. The reason I mention this is because I’m not convinced there is a comm “standard” (yet) that phy device OEMs adhere to for initializing the device. Efforts have been made over the years, but they’re still not there yet and tweaking code in the Ethernet driver init function breaks module abstraction.

Somewhat related is some of the FreeRTOS+TCP stack improvements I made while getting the F7 +TCP port running.

  1. DHCP: Some routers don’t seem to remember IPAddr-to-MACAddr associations when granting an IP lease. Because of this, I noticed the FreeRTOS+TCP stack leasing an incrementing address each time the stack was initialized. The DHCP discover packet has an option field the client can use to specify the desired IP address. I modified the prvSendDHCPDiscover() function to invoke a callback that would return the desired IP address for the option field. This way, the application can hook this callback and it’s then up to the application to provide non-volatile storage of the “lastDhcpIpAddr”, or the callback hook can return {0,0,0,0} to bypass use of the option field.

  2. I added two socket options to set the IP header’s TLL and DSCP value for any specific socket. Perhaps not a Berkley standard, but it provides tighter control over TTL and QoS.

  3. In its current state, the FreeRTOS+TCP stack can in fact act as a Multicast level 1 server, without the need for IGMP (see RFC-1112, Section 3). This, however, requires a modification to the FreeRTOS+TCP function eARPGetCacheEntry(…) that bypasses MAC lookup and instead builds the proper Multicast MAC address if the input IP address falls in the Multicast range. Granted, full IGMP support would be preferable, but that’s quite an undertaking.

  4. It would be nice if a socket provided a callback that could be hooked to indicate when the socket is truly closed and freed from memory. I attempted this but could not get it working.

Hi there RWey,

you are running into open doors here. The ST HAL is a nightmare from whichever side you look at it. I perfectly agree that any software that goes through the trouble of removing any reference to it is a great improvement.

Thanks!

Good to know I’m not going to get beat up making the statement I did.

The only time I’ll use any of the HAL code is for inspection to figure out how they deal with peripheral idiosyncrasies, and I’ve discovered that CubeMX-generated code wasn’t necessarily utilizing their own stm32f765xx.h register definition file. :scream:

As for the micro itself ( the H7 ), it’s an incredible performer (especially with ICache and DCache enabled), and is still simple enough to access its nuts and bolts directly.

For the F7 project I alluded to, I bare-metaled an entire framework ( drivers, utilities, etc ) integrated with FreeRTOS, and it’s been used on six or so F7 related products with good success. I already have the equivalent framework for the H7, but it’s unfortunately missing the Ethernet resource … hence the reason for this thread.

It would be great if we could get some pull requests on the stack changes you made so we can see and discuss them with the aim of getting some upstreamed. The ST driver too once done. What would be the smallest of these changes we could use as a starter PR for initial discussion?

The DHCP fix I made would probably be a good one to start with. I’m not sure how that would get into Git, as I don’t use it. I could post the code here. Would that work ?

( NOTE that I’m working with a relatively old version of the TCP stack, so I’m uncertain if similar fixes have been added or not. )

Attaching the relevant file here would be good - we could then diff and create the Git pull request for you.

@RWey wrote:

The DHCP fix I made would probably be a good one to start with

Yes, I also think that would be a good start, it is a relatively simple change. PS. which DHCP option number do you propose to use for this?

EDIT: DHCP option 50 (“Requested IP Address”) I assume?

@rtel wrote:

Attaching the relevant file here would be good

or if you have, put your proposed sources in a repo on your github page?

A short history of the STM32Hx driver:

On January 11, 2019

  • Nick Pulaski posted “FreeRTOS+TCP for STM32H7?”
    Thomas Kindler sent me his latest version, which I posted on the forum.

July 31, 2020

  • I bought an STM32H747I-DISCO and worked on the network interface. It showed a very good performance.

Oct 21, 2020

  • PR #21 Adding a network interface for STM32H7xx(v2), the driver became “official”.

Dec 23, 2020

  • PR #129 Adapting the STM32Hxx driver for IPv6/multi

June 2021:

  • Current revision of the driver with Robert Wey, in an attempt to make it more independent from ST’s HAL.

@RWey wrote:

the FreeRTOS+TCP function eARPGetCacheEntry(…) that bypasses MAC lookup and instead builds the proper Multicast MAC address

Your proposal is already realised in the latest release:

if( xIsIPv4Multicast( ulAddressToLookup ) != 0 )
{
    /* Get the lowest 23 bits of the IP-address. */
    vSetMultiCastIPv4MacAddress( ulAddressToLookup, pxMACAddress );

    eReturn = eARPCacheHit;
}

meaning that multicast addresses are resolved automatically.

Thanks,

@RWey wrote:

  1. ST’s HAL_ETH_Init(.) function is abysmal. It should be a threaded process that invokes a hookable callback when complete, passing the success of the operation.

The initialisation of the network interfaces is done within the IP-task. HAL_ETH_Init(), or any other initialisation of an EMAC won’t last longer than a second, with the exception of the PHY initialisation, which may take up to 3 seconds. We have always taken that for granted: it means that at start-up, the IP-task is blocked for a few seconds.
Every network interface already has a task like prvEMACHandlerTask(), which handles the reception of packets. It is possible to move the PHY-initialisation to that task.

Making it threaded implies dependency on FreeRTOS, which IMO would be OK. After all, this is a FreeRTOS TCP stack intended to run alongside FreeRTOS.

Indeed, the stack is called FreeRTOS+TCP :slight_smile: It would be a lot of work to simulate queues, semaphores, and event-groups, and ISR handling.

  1. Phy initialization could be broken out such that an additional phy-device-specific implementation…

Have you studied phyHandling.c already?
It is a generic handler for all mainstream 100 Mbps PHY’s. I developed the STM32H driver with that module.
I studied 10 PDF’s of different PHY’s before creating this module.

  1. It would be nice if a socket provided a callback that could be hooked to indicate when the socket is truly closed and freed from memory. I attempted this but could not get it working.

I suppose that you have seen the many call-backs? But indeed, there is no such call-back.
You have a point there. The application sends a message eSocketCloseEvent to the IP-task, and it doesn’t wait for an answer.
Normally, the IP-task has a higher priority than the tasks that make use of the IP-task. But that is not guaranteed, it is only recommended.
So if the IP-task has a higher priority, returning from FreeRTOS_closesocket() means that the socket is really closed.

Correct

I don’t have a github page. I’ll just post the files here.

Great minds think alike! Admittedly, I haven’t updated the +TCP library in quite some time, mostly because my venue does not assign multiple engineers to a project; it assigns multiple projects to an engineer and there’s little time to keep up with changes.

The few seconds presents problems in our products in that impatient customers don’t see anything on the front panel display, so they start hammering buttons (including the power switch). In our case, initialization dependency must wait for the blocked Ethernet before proceeding. It would be nice if we could tell Ethernet initialization to “please initialize and let me know when you’re done”, allowing other startup procedures to complete in the mean time. There may be other ways around this, but it doesn’t fit our framework model at the moment.

It’s been so long that I don’t recall. I do recall, however, ST’s stm32F7xx_hal_eth.c file required certain modification relative to phy-specific calls, and it does have blocking delay loops. These are what I was referring to with respect to being “abysmal” :slight_smile:

I do in fact allow the IP task to have a very high priority, and I suspected the sockets are actually getting closed. The example code on how to properly close a socket, however, states one should first call FreeRTOS_shutdown(.), where subsequent calls to FreeRTOS_recv(…) should eventually return FREERTOS_EINVAL ( a negative value ), but I was never getting a negative value and would ultimately just timeout and force the close. I may be doing something wrong, but during the exercise I thought it would be nice if somehow a socket could communicate when it was shut down (or timed out), as apposed to polling it. Granted, this request is just wishful thinking on my part.

PS. which DHCP option number do you propose to use for this?
DHCP option 50 (“Requested IP Address”) I assume?

Correct

Are you sure that option 50 may be used along with a DHCP DISCOVER message?
I just tried it, but it doesn’t seem to be honoured.

See in this PCAP file:
dhcp_discover.zip (316 Bytes)
My device asks for 192.168.2.16, but it gets x.x.x.15 offered.

EDIT : maybe we should try a REQUEST first, along with option 50. If that fails, start with a new DISCOVER ?

In our case, initialization dependency must wait for the blocked Ethernet before proceeding. It would be nice if we could tell Ethernet initialization to “please initialize and let me know when you’re done”

But this is FreeRTOS: while the IP-task is busy initialising the hardware and the IP-stack, you can initialise the LCD and show a splash screen. phyHandling will mostly be sleeping.

Have you studied phyHandling.c already?

It’s been so long that I don’t recall. I do recall, however, ST’s stm32F7xx_hal_eth.c file required certain modification relative to phy-specific calls, and it does have blocking delay loops. These are what I was referring to with respect to being “abysmal” :slight_smile:

In that case, I think that you will like the approach taken in phyHandling.c.

I do in fact allow the IP task to have a very high priority, and I suspected the sockets are actually getting closed. The example code on how to properly close a socket, however, states one should first call FreeRTOS_shutdown(.), where subsequent calls to FreeRTOS_recv(…) should eventually return FREERTOS_EINVAL ( a negative value ),

Ah yes, this is about the graceful closured of TCP sockets. Many developers get confused by shutdown() and write code llike this:

    if( recv() < 0 )
	{
	    /* The following call is useless because the
         * connection has already been shut down. */
	    shutdown( xSocket, SHUT_RDWR);
		close( xSocket );
		return;
	}

There are two situations:

  1. your device is a HTTP server, and it receives clients on port 80. It serves the client for ever until FreeRTOS_recv() tells that the connection is broken:
   BaseType_t rc = FreeRTOS_recv( xSocket, message, sizeof message, 0 );
   if( ( rc < 0 ) && ( rc != -pdFREERTOS_ERRNO_EWOULDBLOCK ) )
   {
       printf( "The connection is closed: %d\n", ( int ) rc );
       FreeRTOS_closesocket( xSocket );
       return;
   }
  1. Your device is connecting to a web site to store or to retrieve some data. When you are done you can just close your socket, or do a graceful closure of the connection. The latter is far preferred, because it cleans up resources at the peers’ system.
    Here is an example:
    /* Set the socket in shut-down-mode: */
    FreeRTOS_shutdown( xSocket, 0 );

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

        if( ( xEndTime - xStartTime ) > pdMS_TO_TICKS( 1000U ) )
        {
    		/* Time-out waiting for the FIN+ACK. */
            break;
        }
        BaseType_t rc = FreeRTOS_recv( xSocket, ( const void * ) pcBuffer, sizeof pcBuffer, 0 );
        if( ( rc < 0 ) && ( rc != -pdFREERTOS_ERRNO_EWOULDBLOCK ) )
        {
            printf( "Connection gracefully closed\n" );
            break;
        }
    }

Make sure that the socket blocks in FreeRTOS_recv().

IP task to have a very high priority

I always recommend to assign:

  • higher : the task handling the EMAC
  • medium : the IP-task
  • lower : all tasks that make use of the IP-stack.

Tasks that do not use the IP-stack are free to choose any priority.

If you still find that the start-up splash screen is not fast enough, you can temporarily raise the priority of the task handling that. But the EMAC and PHY initialisation should not take too much CPU time.

one of the consequences of this design is that your software won’t pass standardized penetration tests as broadcast storms and DoS attacks will effectively stall your device. Just as a side note…

Hein,

With respect to the DHCP, I am in fact using the request code as defined at the top of FreeRTOS_DHCP.c. Below is my modified version of the discovery function ( see the memcpy(…) statement ). I don’t recall the RFC I investigated to understand the usage of the request option, but I do recall removing the code change once it was in place to observe the problem returning. The router at the time was a Cisco RV-180.

static void prvSendDHCPDiscover( void )
{
uint8_t *pucUDPPayloadBuffer;
struct freertos_sockaddr xAddress;
static const uint8_t ucDHCPDiscoverOptions[] =
{
	/* Do not change the ordering without also changing dhcpCLIENT_IDENTIFIER_OFFSET. */
	dhcpIPv4_MESSAGE_TYPE_OPTION_CODE, 1, dhcpMESSAGE_TYPE_DISCOVER,					/* Message type option. */
	dhcpIPv4_CLIENT_IDENTIFIER_OPTION_CODE, 6, 0, 0, 0, 0, 0, 0,						/* Client identifier. */
	dhcpIPv4_REQUEST_IP_ADDRESS_OPTION_CODE, 4, 0, 0, 0, 0,								/* The IP address being requested. */
	dhcpIPv4_PARAMETER_REQUEST_OPTION_CODE, 3, dhcpIPv4_SUBNET_MASK_OPTION_CODE, dhcpIPv4_GATEWAY_OPTION_CODE, dhcpIPv4_DNS_SERVER_OPTIONS_CODE,	/* Parameter request option. */
	dhcpOPTION_END_BYTE
};
size_t xOptionsLength = sizeof( ucDHCPDiscoverOptions );

	pucUDPPayloadBuffer = prvCreatePartDHCPMessage( &xAddress, (uint8_t)dhcpREQUEST_OPCODE, ucDHCPDiscoverOptions, &xOptionsLength );

	
	// RWey		The original code provided no means to use the last leased IP address and rather just passed 0.0.0.0
	//			in this option field.  The result was the DHCP server offering a different IP address each time the
	//			code requested a lease ... which would inevitably result in DHCP server address exhaustion.
	//
	// Copy the desired IP address.
	memcpy( &pucUDPPayloadBuffer[ dhcpFIRST_OPTION_BYTE_OFFSET + dhcpREQUESTED_IP_ADDRESS_OFFSET ],	vDHCPDesiredIPAddressHook(), 4 );
	

	FreeRTOS_debug_printf( ( "vDHCPProcess: discover\n" ) );
	iptraceSENDING_DHCP_DISCOVER();

	if( FreeRTOS_sendto( xDHCPData.xDHCPSocket, pucUDPPayloadBuffer, ( sizeof( DHCPMessage_t ) + xOptionsLength ), FREERTOS_ZERO_COPY, &xAddress, sizeof( xAddress ) ) == 0 )
	{
		/* The packet was not successfully queued for sending and must be
		returned to the stack. */
		FreeRTOS_ReleaseUDPPayloadBuffer( pucUDPPayloadBuffer );
	}
}

And does it work for you to send dhcpIPv4_REQUEST_IP_ADDRESS_OPTION_CODE at DISCOVER time? For me it doesn’t.

PS. it would be good if you can upgrade your sources.
In your option list there is an error that was later about 1.5 year ago in PR #1696.

Pretty sure it does. From what I recall, the TCP stack calls prvSendDHCPDiscover() for the discovery phase only. I have a test unit on hand and the next time I’m in the code, I’ll try a build without the mod and verify the problem returns. Again, it’s been over a year since I’ve had my head in the TCP stack.

Yeah, I know I’m way overdue on an update. Same applies to the RTOS core. Because the TCP stack and core are used in more than a couple products, however, we’re typically reluctant to do upgrades as it invokes a new drop and full regression test for each. I agree its important and I’ll try to bring it up in our next engineering group meeting. The good news is, I’ve recently centralized our F7 framework source ( as opposed to a copy for each product … we had no choice at the time ), and now might be a good time to update the core and TCP stack in said centralized framework.

I’ll also try to find some time to refamiliarize myself with the phy layer abstraction file. With respect to the HAL Ethernet file, however, I may post the areas I had to change to better explain what I was talking about. If others take a look, maybe they’ll spot something I’m doing wrong.

And does it work for you to send dhcpIPv4_REQUEST_IP_ADDRESS_OPTION_CODE at DISCOVER time? For me it doesn’t.

Pretty sure it does. From what I recall, the TCP stack calls prvSendDHCPDiscover() for the discovery phase only.

You are right!

In a meanwhile I understand it: once a device is leasing an IP-address, the proposal in the DISCOVER message is ignored.
I just tested this by using a local DHCP/DNS server, running on my laptop.
The very first DISCOVER message with option 50 (dhcpIPv4_REQUEST_IP_ADDRESS_OPTION_CODE) was honoured.

however, we’re typically reluctant to do upgrades as it invokes a new drop and full regression test for each.
I agree its important and I’ll try to bring it up in our next engineering group meeting

I know the problem. But can’t you just start playing with newer versions? Just to get the experience and test it?
I always use make/cmake, which makes it easy to play with various versions of libraries, like e.g.:

    FREERTOS_ROOT = $(ROOT_PATH)/framework/FreeRTOS_v10.4.3
    PLUS_TCP_PATH = $(ROOT_PATH)/framework/FreeRTOS-Plus-TCP_v2.3.2

And remember, both FreeRTOS and +TCP releases have always been “downward compatible”, although sometimes old macro’s were kept as “deprecated” in a special header file.
FreeRTOS+TCP will run with any FreeRTOS kernel v8.2 or higher.

At the moment I’m up to my eyeballs in a UWP app, but I’ll update both ASAP. As for downward compatibility, the little tweaks I’ve put in will break that :slight_smile: But, that’s what WinMerge is for. Problem is, I wear too many hats.

I suppose I should also learn the ins and outs of GitHub some day ( ** gasp ** ), so I can look at the updates and understand who/when/why they were made. At present, we use SVN and I have several Windows utilities I’ve written to automate access to that, so we can’t switch.

True, but our product runs on isolated networks and without the IP stack given top priority, we don’t have a product. Packets must be delivered with fairly precise timing.

As for downward compatibility, the little tweaks I’ve put in will break that

Which tweaks do you mean? The ones for DHCP?

I should also learn the ins and outs of GitHub some day

GIT is fun! It has a great philosophy. I always recommend to watch this video, which is a Google tech talk by Linus Torvalds, introduced by Greg Kroah-Hartman.

For Windows, there is an easy-to-use GIT desktop called: SmartGit, made by Syntevo.
EDIT : SmartGit can also be used on Linux.

Actually, I uesd to hate GIT, but after I was forced to learn it, I’ve come to sort of appreciate it. It features one of the steepest learning curves ever - I totally agree that a GUI is a life saver for getting into it. Tortoise GIT is perfect for Windows.

Another thing, it’s about the best tested packets of software in software history. Once you’ve found your way into it, you won’t experience major surprises.