2026-03-02 7:05 AM - last edited on 2026-03-03 2:00 AM by Andrew Neil
Hi everyone,
I am working on a custom board based on the STM32U575RGT6 (TrustZone enabled, FreeRTOS). I have successfully ported the SBSFU example originally provided for the B-U585I-IOT02A.
My configuration is:
To meet my application needs, I added a custom FLASH_STORAGE area at the end of the flash for EEPROM emulation. To do this cleanly, I modified flash_layout.h to dynamically compute the available FLASH_NS_PARTITION_SIZE based on the remaining space.
The Problem: When I use MCUBOOT_OVERWRITE_ONLY, the OTA update (via Y-Modem) works perfectly. However, when I try to use the Swap mode (by commenting out #define MCUBOOT_OVERWRITE_ONLY), the firmware download succeeds, the board reboots, the bootloader validates the signature, starts the swap process, but then crashes with a status write fail:
FWUPDATE
--- Démarrage de la mise à jour Firmware (Non-Secure) ---
-- Erasing download area
-- Send Firmware
-- -- File> Transfer> YMODEM> Send
I/commMonitoring [356203] Demande de mise à jour Firmware reçue !
.e_result = 0 , 3
-- -- Programming Completed Successfully!
-- -- Bytes: 264392
Write Magic Trailer at edff0
-- Firmware téléchargé avec succès ! Redémarrage imminent...
[INF] Flash operation: Op=0x0, Area=0x0, Address=0x0
[INF] Starting bootloader OEMiROT
[INF] Checking BL2 NV area
[INF] Checking BL2 NV area header
[INF] Checking BL2 NV Counter consistency
[INF] Consistent BL2 NV Counter 0 = 0x1000000
[INF] Primary image: magic=good, swap_type=0x1, copy_done=0x3, image_ok=0x1
[INF] Scratch: magic=unset, swap_type=0x1, copy_done=0x3, image_ok=0x3
[INF] Boot source: primary slot
[INF] Swap type: test
[INF] ce, e2, 8e, fc, e0, 8c , 73 ,e9,
[INF] 6d, f7, 78, 68, 56, 52 , 3e ,1f,
[INF] verify counter 0 1000000 1000000
[INF] counter 0 : ok
[INF] verify sig key id 0
[INF] checking public key 47 5b
[INF] verifying signature hlen 20
[INF] signature OK
[INF] a2, 33, da, b4, 77, a0 , ef ,ee,
[INF] 78, 6f, 70, db, c0, de , c3 ,7a,
[INF] Swapping secondary and primary slots: 0x408c8 bytes
[INF] Swapping: swap index 0x0, sector index 0x19, size 0x10000
[INF] Swapping: swap index 0x1, sector index 0x11, size 0x10000
[INF] Swapping: swap index 0x2, sector index 0x9, size 0x10000
[INF] Swapping: swap index 0x3, sector index 0x1, size 0x10000
[INF] Swapping: swap index 0x4, sector index 0x0, size 0x2000
[WRN] 15 status write fails performing the swap
[ERR] panic!What I suspect: Since OVERWRITE works fine, I suspect that resizing the partitions and using the SCRATCH area breaks a hardcoded security rule in SBSFU_Boot (possibly in low_level_device.c with write_vect/secure_vect, or in the SAU/MPU config inside low_level_security.c). I noticed that the single image trailer is physically located in the Non-Secure flash space, which might be rejected during the swap status write.
Here is the relevant part of my custom flash_layout.h showing the dynamic size calculation:
/* 1. USER CUSTOM CONFIGURATION */
#define FLASH_STORAGE_BASE_SIZE (0x10000) /* 64 KB for FlashDB */
/* 2. FIXED SIZES & OFFSETS */
#define _FIXED_BL2_OFFSET_SIZE (FLASH_AREA_BL2_NOHDP_OFFSET + FLASH_AREA_BL2_NOHDP_SIZE)
#define FLASH_S_PARTITION_SIZE (0x18000)
/* 3. DYNAMIC SIZES */
#define _RAW_APP_SPACE (FLASH_TOTAL_SIZE - _FIXED_BL2_OFFSET_SIZE - FLASH_STORAGE_BASE_SIZE)
#define _ALIGN_STEP (FLASH_AREA_IMAGE_SECTOR_SIZE * 2)
#define _ALIGNED_APP_SPACE (_RAW_APP_SPACE - (_RAW_APP_SPACE % _ALIGN_STEP))
/* Swap mode partitions calculation */
#define FLASH_STORAGE_SIZE (FLASH_STORAGE_BASE_SIZE + (_RAW_APP_SPACE - _ALIGNED_APP_SPACE))
#define _HALF_APP_SPACE (_ALIGNED_APP_SPACE / 2)
#define FLASH_NS_PARTITION_SIZE (_HALF_APP_SPACE - FLASH_S_PARTITION_SIZE)My questions:
Thank you in advance for your help!
Solved! Go to Solution.
2026-03-09 3:58 AM
Hi everyone,
I finally managed to find the root cause and completely solve this issue! I am posting the detailed solution here as it might save days of debugging for anyone porting SBSFU to a 1MB Dual Bank STM32U5 (like the STM32U575RGT6).
To answer my own previous questions: No, my dynamic partition calculations in flash_layout.h were perfectly fine, and the MPU/SAU and write_vect arrays were correct.
The crash was actually caused by a logical bug in ST's default low_level_flash.c driver, specifically in how Flash pages are calculated for Bank 2 on 1MB chips.
:magnifying_glass_tilted_left: The Root Cause
If you look at my boot log, the panic happens exactly at the very end of the swap process (swap index 0x4, sector index 0x0). At this exact moment, MCUboot attempts to write the final swap status (magic words) into the image trailer at the end of the primary slot.
Because I resized the partitions, the end of my FLASH_AREA_0 (the trailer) crossed the 512KB boundary and physically landed in Bank 2.
Before writing the trailer, MCUboot must erase that sector. It calls Flash_EraseSector(), which uses the page_number() function to tell the HAL which page to erase. Here is ST's original implementation in SBSFU_Boot/Src/low_level_flash.c:
/* ORIGINAL ST CODE WITH BUG */
static uint32_t page_number(struct arm_flash_dev_t *flash_dev, uint32_t param)
{
uint32_t page = param / flash_dev->data->page_size ;
page = ((page > (flash_dev->data->sector_count))) ? page - ((flash_dev->data->sector_count)) : page;
return page;
}For a 1MB flash with 8KB pages, sector_count is 128. If the address falls in Bank 2 (e.g., absolute page 65), 65 > 128 is false, so the function returns 65. It then passes Banks = FLASH_BANK_2 and Page = 65 to HAL_FLASHEx_Erase().
However, on a 1MB STM32U5, Bank 2 only contains 64 pages (indexed 0 to 63). The HAL rejects Page 65, and the sector is never erased. When MCUboot subsequently tries to write the status quadwords into unerased flash, the hardware rejects it, throwing the 15 status write fails performing the swap panic.
(Note: This is why MCUBOOT_OVERWRITE_ONLY worked! Overwrite mode doesn't use trailers. It only erased the actual firmware payload (~264KB), which stayed safely inside Bank 1, never triggering the bug).
:white_heavy_check_mark: The Solution
To fix this, we need to ensure that the page number correctly wraps back to 0 when entering Bank 2.
In SBSFU_Boot/Src/low_level_flash.c (and also in SBSFU_Loader if you use it), replace the page_number() function with this corrected version:
/* FIXED CODE FOR DUAL BANK */
static uint32_t page_number(struct arm_flash_dev_t *flash_dev, uint32_t param)
{
uint32_t page = param / flash_dev->data->page_size;
uint32_t pages_per_bank = flash_dev->data->sector_count / 2;
/* If the page is located in Bank 2, its index must be relative to Bank 2 (starting from 0) */
if (page >= pages_per_bank)
{
page = page - pages_per_bank;
}
return page;
}After making this change, I cleaned and rebuilt the SBSFU_Boot project, performed a full chip erase, and flashed the system. The OTA Swap mode now works flawlessly!
I hope this helps anyone dealing with custom flash layouts on STM32U5.
2026-03-09 3:58 AM
Hi everyone,
I finally managed to find the root cause and completely solve this issue! I am posting the detailed solution here as it might save days of debugging for anyone porting SBSFU to a 1MB Dual Bank STM32U5 (like the STM32U575RGT6).
To answer my own previous questions: No, my dynamic partition calculations in flash_layout.h were perfectly fine, and the MPU/SAU and write_vect arrays were correct.
The crash was actually caused by a logical bug in ST's default low_level_flash.c driver, specifically in how Flash pages are calculated for Bank 2 on 1MB chips.
:magnifying_glass_tilted_left: The Root Cause
If you look at my boot log, the panic happens exactly at the very end of the swap process (swap index 0x4, sector index 0x0). At this exact moment, MCUboot attempts to write the final swap status (magic words) into the image trailer at the end of the primary slot.
Because I resized the partitions, the end of my FLASH_AREA_0 (the trailer) crossed the 512KB boundary and physically landed in Bank 2.
Before writing the trailer, MCUboot must erase that sector. It calls Flash_EraseSector(), which uses the page_number() function to tell the HAL which page to erase. Here is ST's original implementation in SBSFU_Boot/Src/low_level_flash.c:
/* ORIGINAL ST CODE WITH BUG */
static uint32_t page_number(struct arm_flash_dev_t *flash_dev, uint32_t param)
{
uint32_t page = param / flash_dev->data->page_size ;
page = ((page > (flash_dev->data->sector_count))) ? page - ((flash_dev->data->sector_count)) : page;
return page;
}For a 1MB flash with 8KB pages, sector_count is 128. If the address falls in Bank 2 (e.g., absolute page 65), 65 > 128 is false, so the function returns 65. It then passes Banks = FLASH_BANK_2 and Page = 65 to HAL_FLASHEx_Erase().
However, on a 1MB STM32U5, Bank 2 only contains 64 pages (indexed 0 to 63). The HAL rejects Page 65, and the sector is never erased. When MCUboot subsequently tries to write the status quadwords into unerased flash, the hardware rejects it, throwing the 15 status write fails performing the swap panic.
(Note: This is why MCUBOOT_OVERWRITE_ONLY worked! Overwrite mode doesn't use trailers. It only erased the actual firmware payload (~264KB), which stayed safely inside Bank 1, never triggering the bug).
:white_heavy_check_mark: The Solution
To fix this, we need to ensure that the page number correctly wraps back to 0 when entering Bank 2.
In SBSFU_Boot/Src/low_level_flash.c (and also in SBSFU_Loader if you use it), replace the page_number() function with this corrected version:
/* FIXED CODE FOR DUAL BANK */
static uint32_t page_number(struct arm_flash_dev_t *flash_dev, uint32_t param)
{
uint32_t page = param / flash_dev->data->page_size;
uint32_t pages_per_bank = flash_dev->data->sector_count / 2;
/* If the page is located in Bank 2, its index must be relative to Bank 2 (starting from 0) */
if (page >= pages_per_bank)
{
page = page - pages_per_bank;
}
return page;
}After making this change, I cleaned and rebuilt the SBSFU_Boot project, performed a full chip erase, and flashed the system. The OTA Swap mode now works flawlessly!
I hope this helps anyone dealing with custom flash layouts on STM32U5.