cancel
Showing results for 
Search instead for 
Did you mean: 

STM32H7 + LwIP: Problems with MPU and Custom Heap at 0x30008000 – No Packets Received

LWI_GUY
Associate II

Hi everyone,

I'm working on a project using an STM32H743 and LwIP (v2.1.2) without an RTOS (NO_SYS=1), and I've been debugging persistent issues related to LwIP's heap allocation and MPU configuration. After days of digging, I’d really appreciate the community's input.

What I'm trying to do:

I want to place the LwIP heap in RAM_D2 at address 0x30008000, enable MPU regions for protection, and successfully use Ethernet and ping using LwIP.

My configuration:

.ld file (.lwip_heap section):

.lwip_heap (NOLOAD) :
{
. = ABSOLUTE(0x30008000);
KEEP(*(.lwip_heap))
. = ALIGN(4);
} >RAM_D2

In mem.c:

attribute((section(".lwip_heap"))) attribute((used))
u8_t lwip_heap[MEM_SIZE];

#define LWIP_RAM_HEAP_POINTER ((void*)lwip_heap)

In lwipopts.h:

#define NO_SYS 1
#define MEM_ALIGNMENT 4
#define MEM_SIZE (64*1024)
#define MEM_LIBC_MALLOC 0
#define LWIP_SUPPORT_CUSTOM_PBUF 1

The symptoms:

  • With MPU disabled: I can receive ARP requests and the STM seems to process them correctly by trying to send a broadcasting ARP Response. But the only thing I can see in Wireshark is the following entry:
    02:00:00:00:00:01 SchneiderEle_a5:92:d0 0x4d35 60 Ethernet II
    The first MAC Address makes sense as I gave it to the STM via config, but the rest doesn't make sense at all.

  • With MPU enabled: the heap at 0x30008000 is not written properly, mem_init() fails silently, and LwIP crashes or behaves unexpectedly.

I got it to work once and i could receive and respond to arp packages and ping requests, but not any more, which seems quite weird.

MPU Configuration:

I configured two regions:
Region 1: 0x30000000, 32 KB for DMA descriptors & pbuf pool
Region 2: 0x30008000, 64 KB for the LwIP heap

My MPU Config with Region 2 commented out:

void MPU_Config(void)
{
MPU_Region_InitTypeDef MPU_InitStruct = {0};

/* Disables the MPU */
HAL_MPU_Disable();

/** Initializes and configures the Region and the memory to be protected
*/
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER0;
MPU_InitStruct.BaseAddress = 0x0;
MPU_InitStruct.Size = MPU_REGION_SIZE_256KB;
MPU_InitStruct.SubRegionDisable = 0x87;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.AccessPermission = MPU_REGION_NO_ACCESS;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;

HAL_MPU_ConfigRegion(&MPU_InitStruct);

/** Initializes and configures the Region and the memory to be protected
*/

MPU_InitStruct.Number = MPU_REGION_NUMBER1;
MPU_InitStruct.BaseAddress = 0x30000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_32KB;
MPU_InitStruct.SubRegionDisable = 0x0;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;

HAL_MPU_ConfigRegion(&MPU_InitStruct);


/*
MPU_InitStruct.Number = MPU_REGION_NUMBER2;
MPU_InitStruct.BaseAddress = 0x30008000;
MPU_InitStruct.Size = MPU_REGION_SIZE_64KB;
MPU_InitStruct.SubRegionDisable = 0x0;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;

HAL_MPU_ConfigRegion(&MPU_InitStruct);
*/


/* Enables the MPU */
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);

}

My MwIP Stackup looks as follows:
RxDescripSection 0x30000000
TxDescripSection 0x30000080
RxBufferSection 0x30000100 (RxBufferLength 1524 and RxBufferCNT 12)
LwIP HEAP 0x30008000 (MEM_SIZE 0x10000)

 

What I’ve tried so far:

  • Disabling/enabling D-Cache and I-Cache

  • Using attribute((used)) and KEEP() in the linker script

  • Verifying .map file shows lwip_heap placed at 0x30008000

  • Debugging mem pointer after mem_init()

  • Checking Ethernet TX via ethernet_output() and linkoutput()

  • Confirming headers are correct before linkoutput(), but incorrect on wire

The issue:

As soon as I enable the MPU region for 0x30008000, writing to lwip_heap no longer works. mem becomes invalid, allocations fail, and Ethernet frames on the wire have corrupted headers. No ICMP replies, no ARP responses. With the MPU region disabled, I can receive packages but cannot respond to them correctly.

My question:

What is the correct MPU configuration for 0x30008000 to safely use it as non-cacheable memory for lwip_heap with LwIP?

Also:

  • Do I need to align anything else?

  • Is there a required TEX/C/B/S config for LwIP memory?

  • Am I missing something obvious regarding MPU or memory attributes?

Bonus Info:

STM32CubeIDE 1.18.0, default HAL drivers, no RTOS.

Thanks in advance for any help – this has been a very tricky issue to debug!
Or is there any other solution besides LwIP to establish communication over ethernet.
P.S.: I am using a custom PCB but I used the layout of the STM32H743VIH6 Nucleo Board for the basic functionality including a LAN8742A PHY IC.

Best regards,
Tobias

16 REPLIES 16
/*
MPU_InitStruct.Number = MPU_REGION_NUMBER2;
MPU_InitStruct.BaseAddress = 0x30008000;
MPU_InitStruct.Size = MPU_REGION_SIZE_64KB;
MPU_InitStruct.SubRegionDisable = 0x0;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;

HAL_MPU_ConfigRegion(&MPU_InitStruct);
*/

Try this configuration..  Works fine.

 

Thanks for your reply.
I will be able to test the MPU configuration in a few days and I will come back to it.

>Region 2: 0x30008000, 64 KB for the LwIP heap

Base address of MPU region must be aligned on its size. So for base 0x30008000 the size can be only 32 KB. To cover 64KB, add another 32 KB region.

 

 

avinash_elec
Associate III
avinash_elec
Associate III

Please check out my tutorial on setting up lwIP on STM32F7 Series using CubeIDE

https://www.youtube.com/watch?v=A8ii-t40r-w

mƎALLEm
ST Employee

hello @LWI_GUY and @Vangelis Fortounas ,

Please use </> button to share a code. See How to insert source code.

Thank you for your undestanding.

To give better visibility on the answered topics, please click on "Accept as Solution" on the reply which solved your issue or answered your question.
LWI_GUY
Associate II

Hi everyone,

I just wanted to give you a quick update on this issue.

I tried your suggestions regarding the MPU configuration and the faulty memory region. However, the same issue still persists.

The code fails in the following section of mem.c within the LWIP stack:

memset(ram, 0, MEM_SIZE);
/* initialize the start of the heap */
mem = (struct mem *)(void *)ram;
mem->next = MEM_SIZE_ALIGNED;
mem->prev = 0;
mem->used = 0;

There’s still a failure when assigning the variable mem, as the specified memory region does not get updated. It does get updated when I disable MPU region 2.

That said, I managed to get ARP working by modifying the code in ethernetif.c, specifically in the low_level_output() function. Here is my current version of the function:

static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
  uint32_t i = 0U;
  struct pbuf *q = NULL;
  err_t errval = ERR_OK;
  ETH_BufferTypeDef Txbuffer[ETH_TX_DESC_CNT] = {0};

  memset(Txbuffer, 0 , ETH_TX_DESC_CNT*sizeof(ETH_BufferTypeDef));

  for(q = p; q != NULL; q = q->next)
  {
    if(i >= ETH_TX_DESC_CNT)
      return ERR_IF;

    Txbuffer[i].buffer = q->payload;
    Txbuffer[i].len = q->len;

    // New code beginning
    uint32_t addr = (uint32_t)q->payload;
    uint32_t aligned_addr = addr & ~0x1F;
    uint32_t end_addr = addr + q->len;
    uint32_t aligned_len = ((end_addr - aligned_addr + 31) & ~0x1F);

    SCB_CleanDCache_by_Addr((uint32_t *)aligned_addr, aligned_len);
    // New code ending

    if(i>0)
    {
      Txbuffer[i-1].next = &Txbuffer[i];
    }

    if(q->next == NULL)
    {
      Txbuffer[i].next = NULL;
    }

    i++;
  }

  TxConfig.Length = p->tot_len;
  TxConfig.TxBuffer = Txbuffer;
  TxConfig.pData = p;

  HAL_ETH_Transmit(&heth, &TxConfig, ETH_DMA_TRANSMIT_TIMEOUT);

  return errval;
}

I’ve marked the newly added code using comments.

What I’m still wondering is why this cache-cleaning code is necessary here and why the MPU setup doesn’t seem to work as expected.

Pavel A.
Super User

As stated earlier, your MPU regions 1 and 2 are invalid and can cause UB. Base address of a region must be aligned on its size.

 

LWI_GUY
Associate II

Hi Pavel,
below is the updated MPU configuration, which I was also using for the post above:

void MPU_Config(void)
{
  MPU_Region_InitTypeDef MPU_InitStruct = {0};

  /* Disables the MPU */
  HAL_MPU_Disable();

  /** Initializes and configures the Region and the memory to be protected
  */
  MPU_InitStruct.Enable = MPU_REGION_ENABLE;
  MPU_InitStruct.Number = MPU_REGION_NUMBER0;
  MPU_InitStruct.BaseAddress = 0x0;
  MPU_InitStruct.Size = MPU_REGION_SIZE_256KB;
  MPU_InitStruct.SubRegionDisable = 0x87;
  MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
  MPU_InitStruct.AccessPermission = MPU_REGION_NO_ACCESS;
  MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
  MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;
  MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
  MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;

  HAL_MPU_ConfigRegion(&MPU_InitStruct);

  /** Initializes and configures the Region and the memory to be protected
  */

  MPU_InitStruct.Number = MPU_REGION_NUMBER1;
  MPU_InitStruct.BaseAddress = 0x30000000;
  MPU_InitStruct.Size = MPU_REGION_SIZE_32KB;
  MPU_InitStruct.SubRegionDisable = 0x0;
  MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
  MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
  MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;
  MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
  MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;

  HAL_MPU_ConfigRegion(&MPU_InitStruct);


  /*
  MPU_InitStruct.Number = MPU_REGION_NUMBER2;
  MPU_InitStruct.BaseAddress = 0x30008000;
  MPU_InitStruct.Size = MPU_REGION_SIZE_32KB;
  MPU_InitStruct.SubRegionDisable = 0x0;
  MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
  MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
  MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;
  MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
  MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;

  HAL_MPU_ConfigRegion(&MPU_InitStruct);
  */


  /* Enables the MPU */
  HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);

}

When I enable Region 2, I get the behavior as described above. Region 1 seems to be working fine.
I can't see where I might have made a mistake in the MPU configuration.