From 3c21884be2e2df60e43ae01c93416aed5594afdb Mon Sep 17 00:00:00 2001 From: David Garske Date: Thu, 12 Nov 2026 09:52:33 -0800 Subject: [PATCH 1/3] Document the STM32H7R/S GFXMMU window freeze and its MPU fix --- IDE/STM32Cube/README.md | 27 +++++++++++++++++++++++++++ src/port/stm32_hal/stm32_hal_eth.h | 3 +++ 2 files changed, 30 insertions(+) diff --git a/IDE/STM32Cube/README.md b/IDE/STM32Cube/README.md index e9923e25..e1a688bb 100644 --- a/IDE/STM32Cube/README.md +++ b/IDE/STM32Cube/README.md @@ -125,6 +125,33 @@ void HAL_ETH_MspInit(ETH_HandleTypeDef* heth) } ``` +### Freeze with no fault on STM32H7R/S with the D-cache enabled + +Symptom: under load, most easily with TLS, the MCU stops completely. No fault handler runs and the debugger can no longer halt the core or read memory. Disabling the D-cache makes it go away. + +Cause: `0x25000000`-`0x25FFFFFF` is the GFXMMU virtual-buffer window. The GFXMMU clock is off unless the application enables it, and a read in that window then never completes. With the D-cache enabled the Cortex-M7 issues speculative reads to Normal memory. With `MPU_PRIVILEGED_DEFAULT` the default memory map applies outside your MPU regions, and in that map this window is Normal memory, so a speculative read can land there. The CubeMX background region (`SubRegionDisable = 0x87`) only covers `0x60000000`-`0xDFFFFFFF`. + +To confirm, read `0x25000000` from the debugger on an idle board: the board stops responding. + +Fix: make the window inaccessible in `MPU_Config()`, using a free region number: + +```c +MPU_InitStruct.Enable = MPU_REGION_ENABLE; +MPU_InitStruct.Number = MPU_REGION_NUMBER6; +MPU_InitStruct.BaseAddress = 0x25000000; +MPU_InitStruct.Size = MPU_REGION_SIZE_16MB; +MPU_InitStruct.SubRegionDisable = 0x0; +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_NOT_SHAREABLE; +MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; +MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE; +HAL_MPU_ConfigRegion(&MPU_InitStruct); +``` + +A stricter alternative is a 4 GB no-access background region with only the memories in use and the peripheral range (`0x40000000`-`0x5FFFFFFF`) opened. + ## Using TLS with wolfIP To use TLS (HTTPS, secure sockets): diff --git a/src/port/stm32_hal/stm32_hal_eth.h b/src/port/stm32_hal/stm32_hal_eth.h index cec8429b..0e87e6a0 100644 --- a/src/port/stm32_hal/stm32_hal_eth.h +++ b/src/port/stm32_hal/stm32_hal_eth.h @@ -106,6 +106,9 @@ * several descriptors and is dropped * - On Cortex-M7 with the D-cache enabled, RX buffers are invalidated and * TX frames cleaned around each DMA transfer + * - STM32H7R/S with the D-cache enabled: make 0x25000000-0x25FFFFFF (the + * unclocked GFXMMU window) no-access in the MPU, or a speculative read + * there stalls the bus with no fault; see IDE/STM32Cube/README.md * - Assumes CubeMX generates `heth` and `TxConfig` as extern globals */ From f7f5d45c3f76510e8c63369838e060f7bac9aa61 Mon Sep 17 00:00:00 2001 From: David Garske Date: Thu, 12 Nov 2026 12:32:53 -0800 Subject: [PATCH 2/3] Add a TX bounce buffer to the STM32 HAL Ethernet driver for DTCM stacks --- IDE/STM32Cube/README.md | 12 ++++++++++++ src/port/stm32_hal/stm32_hal_eth.c | 18 ++++++++++++++++++ src/port/stm32_hal/stm32_hal_eth.h | 4 ++++ 3 files changed, 34 insertions(+) diff --git a/IDE/STM32Cube/README.md b/IDE/STM32Cube/README.md index e1a688bb..6f4d0553 100644 --- a/IDE/STM32Cube/README.md +++ b/IDE/STM32Cube/README.md @@ -152,6 +152,18 @@ HAL_MPU_ConfigRegion(&MPU_InitStruct); A stricter alternative is a 4 GB no-access background region with only the memories in use and the peripheral range (`0x40000000`-`0x5FFFFFFF`) opened. +### BusFault in an atomic operation on STM32H7R/S + +Symptom: a precise BusFault (`CFSR.PRECISERR`, with `BFAR` pointing at the variable) in `__atomic_fetch_add` or another atomic, for example in wolfSSL built with `FREERTOS`. + +Cause: the MPU region covering the AXI SRAM is marked Shareable. Atomics compile to `LDREX`/`STREX`, and an exclusive access to Shareable memory faults on this family. Set `IsShareable = MPU_ACCESS_NOT_SHAREABLE` for that region. + +Shareable memory is also not held in the Cortex-M7 D-cache, so a Shareable AXI SRAM region runs uncached even with the D-cache enabled. + +### Task stacks in DTCM or CCM + +wolfIP builds outgoing frames on the stack of the task that sends them, and the Ethernet DMA cannot read core-coupled memory. With `STM32_HAL_ETH_TX_BOUNCE` defined, `stm32_hal_eth.c` copies each frame into a static buffer before transmitting, so the stack may be placed anywhere. It is on by default for STM32H7R/S, whose vendor linker scripts put the main stack in DTCM; define `STM32_HAL_ETH_NO_TX_BOUNCE` there to turn it off. Without it, every stack that wolfIP transmits from must be in memory the Ethernet DMA can reach. The driver's own RX and TX buffers are static data, so the section they are linked into (`.bss` by default) must be in DMA-reachable memory as well. + ## Using TLS with wolfIP To use TLS (HTTPS, secure sockets): diff --git a/src/port/stm32_hal/stm32_hal_eth.c b/src/port/stm32_hal/stm32_hal_eth.c index b7990b12..05c9ecd8 100644 --- a/src/port/stm32_hal/stm32_hal_eth.c +++ b/src/port/stm32_hal/stm32_hal_eth.c @@ -233,6 +233,19 @@ static int hal_eth_poll(struct wolfIP_ll_dev *dev, void *frame, uint32_t len) return (int)frame_len; } +/* wolfIP builds outgoing frames on the caller's stack. Sending from a static + * buffer keeps the DMA off that stack, which it cannot reach when the stack + * is in core-coupled memory (DTCM, CCM). On by default for STM32H7R/S, where + * the vendor linker scripts put the main stack in DTCM. Like rx_buffers, the + * buffer must be linked into memory the DMA can reach. */ +#if defined(STM32H7RS) && !defined(STM32_HAL_ETH_NO_TX_BOUNCE) && \ + !defined(STM32_HAL_ETH_TX_BOUNCE) + #define STM32_HAL_ETH_TX_BOUNCE +#endif +#ifdef STM32_HAL_ETH_TX_BOUNCE +static uint8_t tx_frame[ETH_RX_BUFFER_SIZE] __attribute__((aligned(32))); +#endif + /** * @brief Send an Ethernet frame */ @@ -247,6 +260,11 @@ static int hal_eth_send(struct wolfIP_ll_dev *dev, void *frame, uint32_t len) return -1; } +#ifdef STM32_HAL_ETH_TX_BOUNCE + memcpy(tx_frame, frame, len); + frame = tx_frame; +#endif + #ifdef ETH_DCACHE_ENABLED if (ETH_DCACHE_ENABLED()) { SCB_CleanDCache_by_Addr((uint32_t *)frame, (int32_t)len); diff --git a/src/port/stm32_hal/stm32_hal_eth.h b/src/port/stm32_hal/stm32_hal_eth.h index 0e87e6a0..73461192 100644 --- a/src/port/stm32_hal/stm32_hal_eth.h +++ b/src/port/stm32_hal/stm32_hal_eth.h @@ -106,6 +106,10 @@ * several descriptors and is dropped * - On Cortex-M7 with the D-cache enabled, RX buffers are invalidated and * TX frames cleaned around each DMA transfer + * - With STM32_HAL_ETH_TX_BOUNCE, TX frames are copied into a static buffer + * before transmission, so the caller's stack may be in memory the DMA + * cannot reach (DTCM, CCM). Default on STM32H7R/S; turn it off there with + * STM32_HAL_ETH_NO_TX_BOUNCE * - STM32H7R/S with the D-cache enabled: make 0x25000000-0x25FFFFFF (the * unclocked GFXMMU window) no-access in the MPU, or a speculative read * there stalls the bus with no fault; see IDE/STM32Cube/README.md From 6bfbaa8cf8f1de388a2e0aa244ce4e39c827ea6f Mon Sep 17 00:00:00 2001 From: David Garske Date: Thu, 12 Nov 2026 14:27:30 -0800 Subject: [PATCH 3/3] Drop the STM32H7R/S GFXMMU window note from the STM32 docs --- IDE/STM32Cube/README.md | 27 --------------------------- src/port/stm32_hal/stm32_hal_eth.h | 3 --- 2 files changed, 30 deletions(-) diff --git a/IDE/STM32Cube/README.md b/IDE/STM32Cube/README.md index 6f4d0553..7c7b1201 100644 --- a/IDE/STM32Cube/README.md +++ b/IDE/STM32Cube/README.md @@ -125,33 +125,6 @@ void HAL_ETH_MspInit(ETH_HandleTypeDef* heth) } ``` -### Freeze with no fault on STM32H7R/S with the D-cache enabled - -Symptom: under load, most easily with TLS, the MCU stops completely. No fault handler runs and the debugger can no longer halt the core or read memory. Disabling the D-cache makes it go away. - -Cause: `0x25000000`-`0x25FFFFFF` is the GFXMMU virtual-buffer window. The GFXMMU clock is off unless the application enables it, and a read in that window then never completes. With the D-cache enabled the Cortex-M7 issues speculative reads to Normal memory. With `MPU_PRIVILEGED_DEFAULT` the default memory map applies outside your MPU regions, and in that map this window is Normal memory, so a speculative read can land there. The CubeMX background region (`SubRegionDisable = 0x87`) only covers `0x60000000`-`0xDFFFFFFF`. - -To confirm, read `0x25000000` from the debugger on an idle board: the board stops responding. - -Fix: make the window inaccessible in `MPU_Config()`, using a free region number: - -```c -MPU_InitStruct.Enable = MPU_REGION_ENABLE; -MPU_InitStruct.Number = MPU_REGION_NUMBER6; -MPU_InitStruct.BaseAddress = 0x25000000; -MPU_InitStruct.Size = MPU_REGION_SIZE_16MB; -MPU_InitStruct.SubRegionDisable = 0x0; -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_NOT_SHAREABLE; -MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; -MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE; -HAL_MPU_ConfigRegion(&MPU_InitStruct); -``` - -A stricter alternative is a 4 GB no-access background region with only the memories in use and the peripheral range (`0x40000000`-`0x5FFFFFFF`) opened. - ### BusFault in an atomic operation on STM32H7R/S Symptom: a precise BusFault (`CFSR.PRECISERR`, with `BFAR` pointing at the variable) in `__atomic_fetch_add` or another atomic, for example in wolfSSL built with `FREERTOS`. diff --git a/src/port/stm32_hal/stm32_hal_eth.h b/src/port/stm32_hal/stm32_hal_eth.h index 73461192..0997f48a 100644 --- a/src/port/stm32_hal/stm32_hal_eth.h +++ b/src/port/stm32_hal/stm32_hal_eth.h @@ -110,9 +110,6 @@ * before transmission, so the caller's stack may be in memory the DMA * cannot reach (DTCM, CCM). Default on STM32H7R/S; turn it off there with * STM32_HAL_ETH_NO_TX_BOUNCE - * - STM32H7R/S with the D-cache enabled: make 0x25000000-0x25FFFFFF (the - * unclocked GFXMMU window) no-access in the MPU, or a speculative read - * there stalls the bus with no fault; see IDE/STM32Cube/README.md * - Assumes CubeMX generates `heth` and `TxConfig` as extern globals */